import { useState, useEffect } from 'react' import ChordBox from './ChordBox' import MiniPiano from './MiniPiano' import { getGuitarVoicings, getPianoTechniques, parseChord } from '../lib/voicings' import { CHORD_TYPES, NOTES, getChordsInKey, toRomanNumeral, getSuggestedProgressions } from '../lib/theory' import { FAMOUS_PROGRESSIONS, progressionInKey, getChordSubstitutions, CHORD_PLAYBOOK } from '../lib/education' const CHORD_SUFFIX_OPTIONS = [ { key: 'maj', label: 'Major' }, { key: 'min', label: 'Minor' }, { key: 'dom7', label: '7' }, { key: 'maj7', label: 'maj7' }, { key: 'min7', label: 'm7' }, { key: 'dim', label: 'dim' }, { key: 'dim7', label: 'dim7' }, { key: 'half_dim', label: 'm7♭5' }, { key: 'aug', label: 'aug' }, { key: 'sus4', label: 'sus4' }, { key: 'sus2', label: 'sus2' }, { key: 'maj6', label: '6' }, { key: 'min6', label: 'm6' }, { key: 'add9', label: 'add9' }, ] function chordDisplayName(root, typeKey) { const type = CHORD_TYPES[typeKey] if (!type) return root return root + type.suffix } function GuitarTab({ chordName }) { const voicings = getGuitarVoicings(chordName) if (!voicings.length) { return

No guitar voicings found for {chordName}.

} return (

Click any voicing to learn it. Purple = chord tones. Finger numbers inside dots (1=index, 4=pinky). Barre chords show the fret number on the left.

{voicings.map((v, i) => (

{v.label}

))}

Pro tip: Learn the E-shape and A-shape barres first — they cover all 12 roots. Then add open voicings for the keys you play in most. High-fret voicings (above fret 7) work great as jazz comping shapes in a band mix.

) } function PianoTab({ chordName }) { const parsed = parseChord(chordName) const techniques = getPianoTechniques(chordName) const rootPc = parsed?.rootPc ?? 0 if (!techniques.length) { return

No piano techniques for {chordName}.

} return (

Blue = Left hand  ·  Purple = Right hand  ·  R marks the root.

{techniques.map((t, i) => (

{t.name}

{t.desc}

Tip: {t.tip}

{t.lh.length > 0 && ( LH: {t.lh.map(iv => { const n = NOTES[(rootPc + iv) % 12] return iv === 0 ? `${n} (root)` : n }).join(', ')} )} {t.rh.length > 0 && ( RH: {t.rh.map(iv => { const n = NOTES[(rootPc + iv) % 12] return n }).join(', ')} )}
))}
) } function ChordQuickPick({ label, chords, active, keyInfo, onSelect }) { if (!chords.length) return null return (
{label}
{chords.map(chord => { const rn = keyInfo?.root ? toRomanNumeral(chord, keyInfo.root, keyInfo.mode) : '' return ( ) })}
) } // ─── Progressions sub-tab ───────────────────────────────────────────────────── // Determine whether a chord type is major-ish or minor-ish for matching const MAJOR_TYPES = new Set(['maj','maj7','maj6','add9','sus4','sus2','aug','dom7']) const MINOR_TYPES = new Set(['min','min7','min6','half_dim','dim','dim7']) function isMajorType(t) { return MAJOR_TYPES.has(t) } function isMinorType(t) { return MINOR_TYPES.has(t) } function ProgressionsSubTab({ chordName, onChordClick }) { const parsed = parseChord(chordName) if (!parsed) return null const { rootPc, type } = parsed const root = NOTES[rootPc] // Famous progressions where this chord can be the tonic (degree 0) const isMajor = isMajorType(type) const isMinor = isMinorType(type) const tonicProgs = FAMOUS_PROGRESSIONS.filter(p => { const q0 = p.qualities[0] if (isMajor && isMajorType(q0)) return true if (isMinor && isMinorType(q0)) return true return false }) // Genre-based suggestions from theory.js const genreProgs = getSuggestedProgressions(root, isMajor ? 'major' : 'minor') // Roles this chord plays in other keys const ROLES = [] for (let keyPc = 0; keyPc < 12; keyPc++) { for (const mode of ['major', 'minor']) { const diatonicChords = getChordsInKey(NOTES[keyPc], mode) const idx = diatonicChords.indexOf(chordName) if (idx !== -1) { const rn = toRomanNumeral(chordName, NOTES[keyPc], mode) ROLES.push({ keyRoot: NOTES[keyPc], mode, rn, diatonicChords }) break } } } return (
{/* ── Famous progressions starting from this chord ── */}

Famous progressions — {chordName} as tonic

{tonicProgs.length === 0 && (

No exact matches — try a major or minor chord.

)}
{tonicProgs.slice(0, 6).map(prog => { const chordsHere = progressionInKey(prog, root) return (
{prog.name} {prog.pattern} {prog.genre.map(g => ( {g} ))}
{/* Chord sequence */}
{chordsHere.map((c, i) => ( {i < chordsHere.length - 1 && } ))}

{prog.description}

{prog.songs[0] && (

e.g. {prog.songs.slice(0, 3).join(' · ')}

)}
) })}
{/* ── Genre-based next-chord suggestions ── */} {genreProgs.length > 0 && (

Genre suggestions — starting from {chordName}

{genreProgs.slice(0, 6).map((prog, pi) => (
{prog.genre}
{prog.chords.map((c, i) => ( {i < prog.chords.length - 1 && } ))}
{prog.rn?.join(' – ')}
))}
)} {/* ── Roles this chord plays ── */} {ROLES.length > 0 && (

{chordName} appears in these keys

{ROLES.slice(0, 8).map(({ keyRoot, mode, rn, diatonicChords }) => (
{keyRoot} {mode} {rn}
{diatonicChords.map((c, i) => ( ))}
))}
)}
) } // ─── Theory tab ─────────────────────────────────────────────────────────────── const CHORD_THEORY = { maj: { name: 'Major', formula: 'Root + Major 3rd (4 semitones) + Perfect 5th (7 semitones)', vibe: 'Bright, happy, resolved. The most "complete" sound in Western music.', beginner: 'Major chords are the foundation of almost every song you know. They feel stable and uplifting — like a musical full stop.', tension: 'Low — very stable', color: 'text-yellow-400', }, min: { name: 'Minor', formula: 'Root + Minor 3rd (3 semitones) + Perfect 5th (7 semitones)', vibe: 'Dark, melancholic, introspective. The 3rd is lowered by just one semitone — that one note changes everything.', beginner: 'One note separates major from minor. Minor chords carry emotion and depth — sadness, mystery, tension.', tension: 'Low-medium — stable but moody', color: 'text-blue-400', }, dom7: { name: 'Dominant 7th', formula: 'Major triad + Minor 7th (10 semitones)', vibe: 'Tense, bluesy, urgent. Wants desperately to resolve to a chord a 5th lower.', beginner: 'The 7th chord is the engine of blues and jazz. It creates tension that begs to resolve — like holding your breath. Play G7 then C to feel it.', tension: 'High — strongly pulls to resolution', color: 'text-red-400', }, maj7: { name: 'Major 7th', formula: 'Major triad + Major 7th (11 semitones)', vibe: 'Dreamy, lush, sophisticated. Jazz-infused warmth without the tension of a dominant 7th.', beginner: 'The major 7th is the note just below the octave. Adding it to a major chord gives you that smooth jazz-bossa nova sound — think "Autumn Leaves".', tension: 'Very low — ethereal and floating', color: 'text-purple-400', }, min7: { name: 'Minor 7th', formula: 'Minor triad + Minor 7th (10 semitones)', vibe: 'Smooth, soulful, relaxed. Darker than major 7th but less tense than a dominant 7th.', beginner: 'Minor 7ths are everywhere in soul, R&B, and jazz. They\'re minor chords with added warmth — moody but not harsh.', tension: 'Low-medium — smooth and flowing', color: 'text-indigo-400', }, dim: { name: 'Diminished', formula: 'Root + Minor 3rd (3 semitones) + Diminished 5th (6 semitones)', vibe: 'Dark, tense, unstable. The flattened 5th creates a tritone interval — historically called "diabolus in musica" (the devil in music).', beginner: 'Diminished chords are passing chords — they create maximum tension so the next chord feels like a huge relief. Like a musical cliffhanger.', tension: 'Very high — wants to resolve immediately', color: 'text-orange-400', }, dim7: { name: 'Diminished 7th', formula: 'Diminished triad + Diminished 7th (9 semitones) — fully symmetric, all minor 3rds', vibe: 'Extremely tense and dramatic. Used in horror film scores and dramatic classical passages.', beginner: 'All four notes are equally spaced (all minor 3rds apart), making it the most symmetrical and unstable chord. Classic "villain arrives" sound.', tension: 'Extreme — maximum instability', color: 'text-red-600', }, half_dim: { name: 'Half-Diminished (m7♭5)', formula: 'Diminished triad + Minor 7th (10 semitones)', vibe: 'Dark and tense but with slightly more resolution than full dim7. The "ii" chord in minor ii–V–i jazz progressions.', beginner: 'Half-diminished sits between a minor 7th and a fully diminished chord. It\'s the moody jazz workhorse — think the intro to "Autumn Leaves".', tension: 'High — tense but musical', color: 'text-orange-500', }, aug: { name: 'Augmented', formula: 'Root + Major 3rd (4 semitones) + Augmented 5th (8 semitones) — all major 3rds', vibe: 'Eerie, floating, dreamlike. The raised 5th creates instability that can resolve either up or down.', beginner: 'Augmented chords sound like something is about to happen. They\'re often used as a passing chord between major and minor — the 5th feels like it\'s "reaching" upward.', tension: 'High — ambiguous direction', color: 'text-emerald-400', }, sus4: { name: 'Suspended 4th', formula: 'Root + Perfect 4th (5 semitones) + Perfect 5th (7 semitones)', vibe: 'Open, unresolved, expectant. The 3rd is replaced by a 4th — neither major nor minor, just floating.', beginner: '"Sus" means suspended — the 3rd is suspended in mid-air. It wants to drop down to a major or minor chord. Classic rock move: sus4 → major.', tension: 'Medium — pleasant tension, easy on the ear', color: 'text-cyan-400', }, sus2: { name: 'Suspended 2nd', formula: 'Root + Major 2nd (2 semitones) + Perfect 5th (7 semitones)', vibe: 'Airy, spacious, ambiguous. Like sus4 but lighter — the 2nd sits high above the root.', beginner: 'Sus2 is a favourite of modern pop and ambient music. Without a 3rd, it has no major/minor quality — it just floats. Think Sting, U2, Coldplay.', tension: 'Low-medium — open and spacious', color: 'text-teal-400', }, maj6: { name: 'Major 6th', formula: 'Major triad + Major 6th (9 semitones)', vibe: 'Sweet, vintage, nostalgic. The 6th adds a note from the scale without the tension of a 7th.', beginner: "The 6th is a colour tone that sweetens a major chord. Common in jazz, bossa nova, and 50s pop — \"Misty\" and \"Fly Me To The Moon\" territory.", tension: 'Very low — sweeter than major triad', color: 'text-amber-300', }, min6: { name: 'Minor 6th', formula: 'Minor triad + Major 6th (9 semitones)', vibe: 'Bittersweet, exotic, dramatic. A major 6th over a minor chord creates a striking contrast.', beginner: 'Minor 6ths have a flamenco/tango feel. The bright 6th sitting on top of a dark minor chord creates a sophisticated tension — think Django Reinhardt.', tension: 'Medium — intriguing contrast', color: 'text-amber-400', }, add9: { name: 'Add 9', formula: 'Major triad + Major 9th (14 semitones = octave + 2)', vibe: 'Open, modern, slightly epic. The 9th adds colour without the smoothness of a 7th.', beginner: 'Add9 is the chord of modern rock and pop. Unlike maj9 (which also has a 7th), add9 keeps things clean and direct. Coldplay, Radiohead, and U2 love it.', tension: 'Very low — bright and open', color: 'text-lime-400', }, } const INTERVAL_NAMES = { 0: 'Root', 2: 'Major 2nd', 3: 'Minor 3rd', 4: 'Major 3rd', 5: 'Perfect 4th', 6: 'Tritone (♭5)', 7: 'Perfect 5th', 8: 'Aug 5th', 9: 'Major 6th', 10: 'Minor 7th', 11: 'Major 7th', 14: 'Major 9th', } function TheoryTab({ chordName }) { const parsed = parseChord(chordName) if (!parsed) return

Could not parse chord.

const { rootPc, type } = parsed const typeInfo = CHORD_TYPES[type] const theory = CHORD_THEORY[type] const subs = getChordSubstitutions(chordName) // Actual note names const noteNames = (typeInfo?.intervals ?? []).map(iv => NOTES[(rootPc + iv) % 12]) // Roles this chord can play const ROLES = [] for (let keyPc = 0; keyPc < 12; keyPc++) { for (const mode of ['major', 'minor']) { const diatonicChords = getChordsInKey(NOTES[keyPc], mode) const idx = diatonicChords.indexOf(chordName) if (idx !== -1) { const rn = toRomanNumeral(chordName, NOTES[keyPc], mode) ROLES.push({ keyRoot: NOTES[keyPc], mode, rn }) } } } return (
{/* ── What is this chord? ── */}
{chordName} {theory?.name ?? type}
{theory && ( <>

{theory.beginner}

{theory.vibe}

)}
{/* ── Notes & Formula ── */}

Notes in this chord

{(typeInfo?.intervals ?? []).map((iv, i) => (
{noteNames[i]} {INTERVAL_NAMES[iv] ?? `+${iv}`}
))}
{theory && (
{theory.formula}
)} {theory && (
Tension: {theory.tension}
)}
{/* ── Chord substitutions ── */} {subs.length > 0 && (

Colour swaps — try these instead

{subs.map((sub, i) => (
{sub.chord}

{sub.tip}

))}
)} {/* ── Keys this chord belongs to ── */} {ROLES.length > 0 && (

{chordName} appears in these keys

{ROLES.slice(0, 10).map(({ keyRoot, mode, rn }) => (
{keyRoot} {mode} {rn}
))}

Roman numerals show the chord's role: I/i = home, IV = subdominant, V = dominant tension, vi/♭VI = relative minor/major, etc.

)}
) } // ─── Learn tab ──────────────────────────────────────────────────────────────── function LearnTab({ chordName }) { const parsed = parseChord(chordName) const playbook = parsed ? CHORD_PLAYBOOK[parsed.type] : null if (!playbook) { return

No jam content for {chordName} yet.

} return (
{/* ── Jam role ── */}

Your role in the jam

{playbook.jamRole}

{/* ── Voicings for jamming ── */}

Voicings — when to use which

{playbook.voicings.map((v, i) => (
{i + 1}

{v.name}

{v.use}

))}

See the Guitar tab for the actual fingerings of each shape.

{/* ── Licks & fills ── */}

Licks & fills

{playbook.licks.map((l, i) => (

{l.title}

{l.style}
{l.tab}

Key insight: {l.tip}

))}
{/* ── Jam tips ── */}

Jam tips

{playbook.jamTips.map((tip, i) => (

{tip}

))}
{/* ── Loop station practice ── */} {playbook.loopPractice?.length > 0 && (

Loop station practice

{playbook.loopPractice.map((lp, i) => (

🔁 {lp.title}

{lp.body}

))}
)}
) } // ─── Explore tab ────────────────────────────────────────────────────────────── function ExploreTab({ initialChord, keyInfo, chordHistory }) { const parsed = parseChord(initialChord) const [root, setRoot] = useState(parsed ? NOTES[parsed.rootPc] : 'C') const [typeKey, setTypeKey] = useState(parsed?.type ?? 'maj') const [subTab, setSubTab] = useState('guitar') const [active, setActive] = useState(initialChord ?? '') const chordName = chordDisplayName(root, typeKey) function selectChord(chord) { setActive(chord) const p = parseChord(chord) if (p) { setRoot(NOTES[p.rootPc]); setTypeKey(p.type) } } const recentChords = [...new Set([...(chordHistory ?? [])].reverse())].slice(0, 12) const keyChords = keyInfo?.root ? getChordsInKey(keyInfo.root, keyInfo.mode ?? 'major') : [] return (
{/* ── Contextual quick-picks ── */} {(recentChords.length > 0 || keyChords.length > 0) && (
{keyChords.length > 0 && ( <> {recentChords.length > 0 &&
} )}
)} {/* ── Manual picker ── */}
Root:
{NOTES.map(n => ( ))}
Type:
{chordName}
{/* ── Sub-tabs ── */}
{[ { key: 'guitar', label: '🎸 Guitar' }, { key: 'piano', label: '🎹 Piano' }, ].map(t => ( ))}
{subTab === 'guitar' && } {subTab === 'piano' && }
) } // ─── Main modal ─────────────────────────────────────────────────────────────── export default function ChordDetailModal({ chord, onClose, onChordClick, keyInfo, chordHistory }) { const [tab, setTab] = useState('guitar') // Reset tab when chord changes useEffect(() => { setTab('guitar') }, [chord]) // Close on Escape useEffect(() => { function onKey(e) { if (e.key === 'Escape') onClose() } window.addEventListener('keydown', onKey) return () => window.removeEventListener('keydown', onKey) }, [onClose]) if (!chord) return null const parsed = parseChord(chord) const typeName = parsed ? (CHORD_SUFFIX_OPTIONS.find(o => o.key === parsed.type)?.label ?? parsed.type) : '' return (
{ if (e.target === e.currentTarget) onClose() }} >
{/* Header */}

{chord}

{typeName} chord · tap a voicing to study it

{/* Tab bar */}
{[ { key: 'guitar', label: '🎸 Guitar' }, { key: 'piano', label: '🎹 Piano' }, { key: 'theory', label: '📚 Theory' }, { key: 'learn', label: '🎓 Learn' }, { key: 'progressions', label: '🎵 Progressions' }, { key: 'explore', label: '🔍 Explore' }, ].map(t => ( ))}
{/* Content */}
{tab === 'guitar' && } {tab === 'piano' && } {tab === 'theory' && } {tab === 'learn' && } {tab === 'progressions' && { onChordClick?.(c) }} />} {tab === 'explore' && }
) }