feat(banner): slim loop strip — big Now Playing chord removed (task L-50, commit 1/2)

Key chip + last-5 history + loop chips on one wrapping row; the
playhead loop chip and the enlarged current history chip are the
"now"; p-2 chrome trim. The text-6xl Now Playing column, its divider,
and the dead currentRN computation are gone — the loop shows once.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
vadimwit
2026-07-11 16:28:25 +01:00
parent 8e7a648660
commit 800fd43778
+67 -84
View File
@@ -2,15 +2,21 @@ import { useRef, useEffect } from 'react'
import { toRomanNumeral } from '../lib/theory' import { toRomanNumeral } from '../lib/theory'
import { findLoopPosition } from '../lib/match' import { findLoopPosition } from '../lib/match'
const HISTORY_SHOWN = 8 // ─── ProgressionBanner — the SLIM LOOP STRIP (task L-50, one-screen.md §1.2) ──
//
// One full-width status row: key chip · chord history (last 5, age-faded) ·
// ♻ loop chips. The old right-30% "Now Playing" text-6xl chord + its divider
// are GONE per the user directive — the enlarged current history chip and the
// accent-glowing active loop chip ARE the now-playing display. Chrome trimmed
// (p-4 → p-2, history pb-1 dropped) so the strip lands at ~76px.
const HISTORY_SHOWN = 5
export default function ProgressionBanner({ chordHistory, keyInfo, detectedProgression, currentChord, onChordClick }) { export default function ProgressionBanner({ chordHistory, keyInfo, detectedProgression, onChordClick }) {
const { root, mode, confidence } = keyInfo ?? {} const { root, mode, confidence } = keyInfo ?? {}
const visible = chordHistory.slice(-HISTORY_SHOWN) const visible = chordHistory.slice(-HISTORY_SHOWN)
const current = visible[visible.length - 1] const current = visible[visible.length - 1]
const loopPos = findLoopPosition(chordHistory, detectedProgression) const loopPos = findLoopPosition(chordHistory, detectedProgression)
const currentRN = root && current ? toRomanNumeral(current, root, mode) : ''
const currentRef = useRef(null) const currentRef = useRef(null)
const prevChord = useRef(null) const prevChord = useRef(null)
@@ -25,70 +31,68 @@ export default function ProgressionBanner({ chordHistory, keyInfo, detectedProgr
}, [current]) }, [current])
return ( return (
<div className="bg-panel border border-border rounded-2xl p-4 mb-3 flex gap-4"> <div className="bg-panel border border-border rounded-2xl p-2 mb-3 flex flex-wrap items-end gap-x-3 gap-y-1">
{/* ── Left: key + chord history + loop ── */} {/* ── Key chip ── */}
<div className="w-full lg:w-[70%] min-w-0 flex flex-col gap-2"> <div className="shrink-0 flex items-baseline gap-1.5 self-center">
{root ? (
{/* Key + history on one row */} <>
<div className="flex items-end gap-3"> <span className="text-2xl font-bold text-accent">{root}</span>
<div className="shrink-0 flex items-baseline gap-1.5"> <span className="text-gray-400 text-sm">{mode}</span>
{root ? ( {confidence && (
<> <span className="text-xs text-gray-600">{Math.round(confidence * 100)}%</span>
<span className="text-2xl font-bold text-accent">{root}</span>
<span className="text-gray-400 text-sm">{mode}</span>
{confidence && (
<span className="text-xs text-gray-600">{Math.round(confidence * 100)}%</span>
)}
</>
) : (
<span className="text-gray-600 text-sm">Detecting key</span>
)} )}
</div> </>
) : (
<span className="text-gray-600 text-sm">Detecting key</span>
)}
</div>
<div className="w-px h-6 bg-border shrink-0" /> <div className="w-px h-6 bg-border shrink-0 self-center" />
{!chordHistory.length ? ( {/* ── Chord history (the enlarged current chip is the "now") ── */}
<p className="text-gray-600 text-sm">Start listening</p> {!chordHistory.length ? (
) : ( <p className="text-gray-600 text-sm self-center">Start listening</p>
<div className="flex items-end gap-1 overflow-x-auto pb-1"> ) : (
{visible.map((chord, i) => { <div className="flex items-end gap-1 overflow-x-auto">
const isCurrent = i === visible.length - 1 {visible.map((chord, i) => {
const age = visible.length - 1 - i const isCurrent = i === visible.length - 1
const opacity = Math.max(0.25, 1 - age * 0.09) const age = visible.length - 1 - i
const rn = root ? toRomanNumeral(chord, root, mode) : '' const opacity = Math.max(0.25, 1 - age * 0.09)
return ( const rn = root ? toRomanNumeral(chord, root, mode) : ''
<div return (
key={i} <div
ref={isCurrent ? currentRef : null} key={i}
style={{ opacity }} ref={isCurrent ? currentRef : null}
onClick={() => onChordClick?.(chord)} style={{ opacity }}
className={`flex flex-col items-center shrink-0 px-2 py-1 rounded-xl transition-colors duration-200 cursor-pointer ${ onClick={() => onChordClick?.(chord)}
isCurrent className={`flex flex-col items-center shrink-0 px-2 py-1 rounded-xl transition-colors duration-200 cursor-pointer ${
? 'bg-accent/10 border border-accent/40 ring-1 ring-accent/20 hover:bg-accent/20' isCurrent
: 'border border-transparent hover:border-border hover:bg-panel' ? 'bg-accent/10 border border-accent/40 ring-1 ring-accent/20 hover:bg-accent/20'
}`} : 'border border-transparent hover:border-border hover:bg-panel'
> }`}
<span className={`font-black leading-none tracking-tight ${ >
isCurrent ? 'text-3xl text-accent' : 'text-xl text-gray-200' <span className={`font-black leading-none tracking-tight ${
}`}> isCurrent ? 'text-3xl text-accent' : 'text-xl text-gray-200'
{chord} }`}>
</span> {chord}
<span className={`text-xs font-semibold mt-0.5 ${ </span>
isCurrent ? 'text-amber-400' : 'text-gray-500' <span className={`text-xs font-semibold mt-0.5 ${
}`}> isCurrent ? 'text-amber-400' : 'text-gray-500'
{rn || '\u00A0'} }`}>
</span> {rn || ' '}
</div> </span>
) </div>
})} )
</div> })}
)}
</div> </div>
)}
{/* Loop */} {/* ── Loop — on the same row (divider between); playhead chip = the now ── */}
{detectedProgression && ( {detectedProgression && (
<div className="flex items-center gap-1.5 flex-wrap"> <>
<div className="w-px h-6 bg-border shrink-0 self-center" />
<div className="flex items-center gap-1.5 flex-wrap self-center">
<span className="text-xs text-gray-500"></span> <span className="text-xs text-gray-500"></span>
{detectedProgression.map((chord, i) => { {detectedProgression.map((chord, i) => {
const isActive = i === loopPos const isActive = i === loopPos
@@ -112,29 +116,8 @@ export default function ProgressionBanner({ chordHistory, keyInfo, detectedProgr
})} })}
<span className="text-gray-600 text-xs"> loop</span> <span className="text-gray-600 text-xs"> loop</span>
</div> </div>
)} </>
</div> )}
{/* ── Divider ── */}
<div className="hidden lg:block w-px bg-border shrink-0" />
{/* ── Right: big chord ── */}
<div className="hidden lg:flex w-[30%] flex-col items-center justify-center gap-1">
{current ? (
<button
onClick={() => onChordClick?.(current)}
className="flex flex-col items-center gap-1 px-4 py-2 rounded-xl hover:bg-accent/10 transition-colors group"
title="Click to see voicings"
>
<p className="text-xs text-gray-600 uppercase tracking-widest">Now Playing</p>
<div className="text-6xl font-black text-amber-400 leading-none group-hover:text-accent transition-colors">{current}</div>
<div className="text-sm text-gray-500">{currentRN}</div>
<p className="text-[10px] text-gray-700 group-hover:text-gray-500 transition-colors">tap for voicings</p>
</button>
) : (
<p className="text-gray-600 text-xs text-center">Play a chord</p>
)}
</div>
</div> </div>
) )