import type { GuitarVoicing } from '~/lib/chord-voicing'; const STRING_COUNT = 6; const FRET_COUNT = 4; const STRING_SPACING = 13; // px between adjacent string lines const FRET_HEIGHT = 18; // px per fret const DOT_R = 5; // finger dot radius const GRID_W = STRING_SPACING * (STRING_COUNT - 1); // 65px const GRID_H = FRET_HEIGHT * FRET_COUNT; // 72px interface Props { voicing: GuitarVoicing | null; } export function GuitarFretboard({ voicing }: Props) { if (!voicing) { return (
no voicing
); } const { frets, baseFret, barre } = voicing; const showFretLabel = baseFret > 0; return (
{/* O/X string indicators above nut */}
{frets.map((f, i) => f === null ? ( ) : f === 0 ? ( ) : null )}
{/* Fret number + grid */}
{/* Fret number label (barre position) */}
{showFretLabel && ( {baseFret} )}
{/* Fretboard grid */}
{/* String lines (6 vertical lines) */} {Array.from({ length: STRING_COUNT }, (_, i) => (
))} {/* Fret lines (horizontal, between frets) */} {Array.from({ length: FRET_COUNT - 1 }, (_, i) => (
))} {/* Barre bar — only for transposed barre chords */} {barre !== null && barre > 0 && (
)} {/* Finger dots */} {frets.map((fret, stringIdx) => { if (fret === null || fret === 0) return null; // Skip strings covered by the barre bar if (barre !== null && fret === barre) return null; const fretIdx = baseFret === 0 ? fret - 1 : fret - baseFret; if (fretIdx < 0 || fretIdx >= FRET_COUNT) return null; return (
); })}
); }