import { useRef, useEffect } from 'react' import { toRomanNumeral } from '../lib/theory' // Sizes for the chord trail (oldest → current) const TRAIL_SIZES = [ 'text-lg opacity-20', 'text-xl opacity-30', 'text-2xl opacity-45', 'text-3xl opacity-60', 'text-4xl opacity-80', ] const CURRENT_SIZE = 'text-7xl opacity-100' function findLoopPosition(chordHistory, progression) { if (!progression?.length || !chordHistory.length) return -1 const len = progression.length // Walk backwards through the progression to find where current chord sits for (let p = len - 1; p >= 0; p--) { if (progression[p] !== chordHistory[chordHistory.length - 1]) continue let match = true for (let i = 1; i < Math.min(p + 1, chordHistory.length); i++) { if (progression[p - i] !== chordHistory[chordHistory.length - 1 - i]) { match = false; break } } if (match) return p } return progression.indexOf(chordHistory[chordHistory.length - 1]) } export default function ProgressionBanner({ chordHistory, keyInfo, detectedProgression }) { const { root, mode } = keyInfo ?? {} // Show up to 5 previous chords + current const trail = chordHistory.slice(-6, -1) // up to 5 previous const current = chordHistory[chordHistory.length - 1] // Flash the current chord when it changes const currentRef = useRef(null) const prevChord = useRef(null) useEffect(() => { if (current && current !== prevChord.current && currentRef.current) { currentRef.current.animate( [{ opacity: 0, transform: 'translateY(8px) scale(0.9)' }, { opacity: 1, transform: 'translateY(0) scale(1)' }], { duration: 220, easing: 'ease-out', fill: 'forwards' } ) prevChord.current = current } }, [current]) const loopPos = findLoopPosition(chordHistory, detectedProgression) if (!chordHistory.length) { return (

Start listening to detect chords…

) } return (
{/* ── Chord trail ── */}
{trail.map((chord, i) => { const sizeClass = TRAIL_SIZES[Math.max(0, i - (trail.length - TRAIL_SIZES.length))] const rn = root ? toRomanNumeral(chord, root, mode) : '' return (
{chord} {rn}
) })} {/* Arrow between trail and current */} {trail.length > 0 && ( )} {/* Current chord — BIG */} {current && (
{current} {root ? toRomanNumeral(current, root, mode) : ''}
)}
{/* ── Detected loop ── */} {detectedProgression && (

♻ Detected loop

{detectedProgression.map((chord, i) => { const isActive = i === loopPos const rn = root ? toRomanNumeral(chord, root, mode) : chord return (
{chord} {rn}
) })}
→ loop
)}
) }