import { useState } from 'react' import { toRomanNumeral, CHORD_TYPES, NOTES } from '../lib/theory' import { findSimilarProgressions, getChordSubstitutions, progressionInKey, styleVariationInKey, parseChord, } from '../lib/education' // ─── Session Snapshot ───────────────────────────────────────────────────────── function SessionSnapshot({ keyInfo, detectedProgression, chordHistory }) { const { root, mode, confidence } = keyInfo ?? {} const uniqueChords = [...new Set(chordHistory)] const totalPlayed = chordHistory.length return (

Key

{root ? (
{root} {mode} {confidence && {Math.round(confidence * 100)}%}
) : ( Detecting… )}

Detected Loop

{detectedProgression?.length ? (
{detectedProgression.map((chord, i) => ( {chord} {root && {toRomanNumeral(chord, root, mode)} } ))}
) : ( None yet — keep playing! )}

Session

{totalPlayed} chords  ·  {uniqueChords.length} unique

{uniqueChords.length > 0 && (
{uniqueChords.slice(0, 10).map(c => ( {c} ))} {uniqueChords.length > 10 && +{uniqueChords.length - 10}}
)}
) } // ─── Similar Progressions ───────────────────────────────────────────────────── function SimilarProgressions({ similar, keyInfo, onChordClick }) { const [expanded, setExpanded] = useState(null) if (!similar.length) return (

Play more chords and lock a key to find similar famous progressions.

) return (
{similar.map(prog => { const isOpen = expanded === prog.id const chordsInKey = keyInfo?.root ? progressionInKey(prog, keyInfo.root) : [] return (
{/* Header row */} ))} in {keyInfo.root} {keyInfo.mode}
)}
{isOpen ? '▲' : '▼'} {/* Expanded detail */} {isOpen && (

{prog.description}

{prog.tip && (

Insight: {prog.tip}

)} {/* Song examples */}

Famous examples

{prog.songs.map(s => ( {s} ))}
{/* Style variations */} {prog.styleVariations.length > 0 && (

Style variations

{prog.styleVariations.map(sv => { const svChords = keyInfo?.root ? styleVariationInKey(sv, prog, keyInfo.root) : [] return (
{sv.label}
{sv.pattern} {svChords.length > 0 && (
{svChords.map((c, i) => ( ))} in {keyInfo.root}
)}
) })}
)}
)}
) })}
) } // ─── Play It Differently ────────────────────────────────────────────────────── function PlayDifferently({ progression, onChordClick }) { if (!progression?.length) return (

Keep playing — a repeating progression will appear here with substitution ideas.

) return (

Tap any substitution to see how to play it. These are harmonic replacements — same role, different colour.

{progression.map(chord => { const subs = getChordSubstitutions(chord) return (
{/* Original chord */} {/* Substitutions */}
{subs.map(sub => (
{/* Tooltip */}
{sub.tip}
))}
) })}

Hover substitutions to see what they change · click to see voicings

) } // ─── Chord Variation Ideas ──────────────────────────────────────────────────── const VARIATION_ROWS = [ { label: '7th Upgrade', desc: 'Add 7ths throughout — jazz and soul texture', typeMap: { maj: 'maj7', min: 'min7', dom7: 'dom7', maj7: 'maj7', min7: 'min7', dim: 'dim7', add9: 'maj7', sus2: 'sus2', sus4: 'sus4', aug: 'aug', half_dim: 'half_dim', maj6: 'maj6', min6: 'min6' }, }, { label: 'Sus2 Wash', desc: 'Replace triads with sus2 — ambient and spacious', typeMap: { maj: 'sus2', min: 'sus2', dom7: 'sus4', maj7: 'sus2', min7: 'sus2', add9: 'sus2', dim: 'dim', aug: 'aug', sus4: 'sus4', sus2: 'sus2', half_dim: 'half_dim', maj6: 'sus2', min6: 'sus2' }, }, { label: 'Add9 Modern', desc: 'Add9 on majors, m7 on minors — indie and neo-soul', typeMap: { maj: 'add9', min: 'min7', dom7: 'dom7', maj7: 'add9', min7: 'min7', add9: 'add9', sus2: 'sus2', sus4: 'sus4', dim: 'dim', aug: 'aug', half_dim: 'half_dim', maj6: 'add9', min6: 'min7' }, }, { label: 'Blues Dominant', desc: 'All chords → dom7 — instant 12-bar blues energy', typeMap: { maj: 'dom7', min: 'dom7', dom7: 'dom7', maj7: 'dom7', min7: 'dom7', add9: 'dom7', sus2: 'dom7', sus4: 'dom7', dim: 'dim7', aug: 'aug', half_dim: 'dom7', maj6: 'dom7', min6: 'dom7' }, }, ] function ProgressionVariationIdeas({ progression, onChordClick }) { if (!progression?.length) return (

Keep playing — your progression will appear here.

) return (

Your progression re-harmonised four ways. Click any chord to open its voicing explorer.

{VARIATION_ROWS.map(row => { const transformed = progression.map(chord => { const p = parseChord(chord) if (!p) return chord const newType = row.typeMap[p.type] ?? p.type return NOTES[p.rootPc] + (CHORD_TYPES[newType]?.suffix ?? '') }) return (
{row.label} {row.desc}
{progression.map((orig, i) => ( {orig} {i < progression.length - 1 && ·} ))}
) })}
) } // ─── Main ───────────────────────────────────────────────────────────────────── export default function EducationPanel({ chordHistory, keyInfo, detectedProgression, onChordClick }) { const [open, setOpen] = useState(false) const [section, setSection] = useState('similar') // Use detected progression if available, else last 4 unique chords from history const workingProgression = detectedProgression?.length ? detectedProgression : [...new Set([...chordHistory].reverse())].reverse().slice(-4) const similar = findSimilarProgressions(workingProgression, keyInfo) return (
{open && (
{/* Section nav */}
{[ { key: 'snapshot', label: '📊 Session' }, { key: 'similar', label: `🎵 Similar Progressions${similar.length ? ` (${similar.length})` : ''}` }, { key: 'play', label: '🎨 Play Differently' }, { key: 'variations',label: '🔀 Progression Variations' }, ].map(s => ( ))}
{section === 'snapshot' && ( )} {section === 'similar' && ( )} {section === 'play' && ( )} {section === 'variations' && ( )}
)}
) }