From df5e6e33744d4710bac1e06a6b302e08ec2dcc09 Mon Sep 17 00:00:00 2001 From: Z User Date: Tue, 27 Oct 2026 10:31:04 +0000 Subject: [PATCH] =?UTF-8?q?v6.4=20=E2=80=94=20beat=20detection=20suspends?= =?UTF-8?q?=20with=20no=20music=20(no=20ghost=20beats)=20+=20royal=20purpl?= =?UTF-8?q?e=20dark=20theme=20with=20light-yellow=20highlights?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/globals.css | 159 +++++++++++++----- src/app/page.tsx | 6 + src/components/ava/beat-dot.tsx | 16 +- src/components/ava/beat-tab.tsx | 20 ++- src/components/ava/detect-tab.tsx | 6 +- src/components/ava/footer-bar.tsx | 4 +- src/components/ava/header.tsx | 4 +- src/components/ava/native-tab.tsx | 2 +- src/components/ava/shared.ts | 5 +- src/lib/ava/beat.ts | 21 +++ src/lib/ava/engine.ts | 9 +- src/lib/ava/preset-io.ts | 0 src/lib/ava/user-presets.ts | 0 src/lib/preset-paths.ts | 0 src/lib/version.ts | 5 +- tool-results/e2e-v62/01-desktop-presets.png | Bin .../e2e-v62/02-embedded-folder-selected.png | Bin .../e2e-v62/03-embedded-preset-selected.png | Bin .../e2e-v62/04-script-tab-created.png | Bin .../e2e-v62/05-save-blocked-toast.png | Bin .../e2e-v62/06-user-library-empty.png | Bin tool-results/e2e-v62/07-mobile-firstpaint.png | Bin tool-results/e2e-v62/08-mobile-bottom.png | Bin .../e2e-v62/09-mobile-embedded-selected.png | Bin tool-results/e2e-v63/01-dark-default.png | Bin tool-results/e2e-v63/02-dark-presets.png | Bin .../e2e-v63/03-light-after-toggle.png | Bin .../e2e-v63/03b-light-toggle-checked.png | Bin tool-results/e2e-v63/04-light-simulated.png | Bin .../e2e-v63/05-light-presets-tree.png | Bin .../e2e-v63/06-light-preset-panel.png | Bin tool-results/e2e-v63/07-light-script.png | Bin tool-results/e2e-v63/08-light-help.png | Bin tool-results/e2e-v63/09-light-detect.png | Bin tool-results/e2e-v63/10-light-beat.png | Bin tool-results/e2e-v63/11-light-native.png | Bin .../e2e-v63/11b-light-native-full.png | Bin tool-results/e2e-v63/12-light-dialog.png | Bin tool-results/e2e-v63/13-light-toast.png | Bin tool-results/e2e-v63/13b-light-toast-mid.png | Bin .../e2e-v63/14-light-delete-dialog.png | Bin .../e2e-v63/15-light-after-reload.png | Bin tool-results/e2e-v63/16-dark-after-reload.png | Bin .../e2e-v63/17-light-REAL-served-css.png | Bin .../e2e-v63/18-real-toggle-to-dark.png | Bin .../e2e-v63/19-real-toggle-back-to-light.png | Bin tool-results/e2e-v63/20-real-light-dialog.png | Bin tool-results/e2e-v63/21-real-dark-dialog.png | Bin .../e2e-v63/22-real-light-presets.png | Bin tool-results/e2e-v63/23-real-light-script.png | Bin tool-results/e2e-v63/24-real-light-help.png | Bin tool-results/e2e-v63/25-dark-script.png | Bin tool-results/e2e-v63/26-mobile-light-top.png | Bin .../e2e-v63/27-mobile-light-bottom.png | Bin tool-results/e2e-v63/28-mobile-dark-top.png | Bin .../e2e-v63/29-mobile-dark-bottom.png | Bin .../e2e-v63/30-light-detect-demo-vu.png | Bin tool-results/e2e-v63/31-light-detect-live.png | Bin tool-results/e2e-v63/32-light-live-visual.png | Bin .../e2e-v63/33-real-light-tree-expanded.png | Bin .../e2e-v63/34-final-pristine-dark.png | Bin tool-results/ove-containment.png | Bin tool-results/ove-dragonscales.png | Bin tool-results/ove-kingish.png | Bin .../read_1793080556522_6263211d285d.txt | 0 .../read_1793082276516_65b3b8ecd40e.txt | 0 .../read_1793082296314_a82167d4aa24.txt | 0 .../read_1793082298965_cc5347613a20.txt | 0 .../read_1793086254569_65b3b8ecd40e.txt | 0 .../read_1793086294893_e6288764dc88.txt | 0 .../read_1793087510034_3be5e504589d.txt | 0 worklog.md | 69 ++++++++ 72 files changed, 264 insertions(+), 62 deletions(-) mode change 100644 => 100755 src/lib/ava/preset-io.ts mode change 100644 => 100755 src/lib/ava/user-presets.ts mode change 100644 => 100755 src/lib/preset-paths.ts mode change 100644 => 100755 tool-results/e2e-v62/01-desktop-presets.png mode change 100644 => 100755 tool-results/e2e-v62/02-embedded-folder-selected.png mode change 100644 => 100755 tool-results/e2e-v62/03-embedded-preset-selected.png mode change 100644 => 100755 tool-results/e2e-v62/04-script-tab-created.png mode change 100644 => 100755 tool-results/e2e-v62/05-save-blocked-toast.png mode change 100644 => 100755 tool-results/e2e-v62/06-user-library-empty.png mode change 100644 => 100755 tool-results/e2e-v62/07-mobile-firstpaint.png mode change 100644 => 100755 tool-results/e2e-v62/08-mobile-bottom.png mode change 100644 => 100755 tool-results/e2e-v62/09-mobile-embedded-selected.png mode change 100644 => 100755 tool-results/e2e-v63/01-dark-default.png mode change 100644 => 100755 tool-results/e2e-v63/02-dark-presets.png mode change 100644 => 100755 tool-results/e2e-v63/03-light-after-toggle.png mode change 100644 => 100755 tool-results/e2e-v63/03b-light-toggle-checked.png mode change 100644 => 100755 tool-results/e2e-v63/04-light-simulated.png mode change 100644 => 100755 tool-results/e2e-v63/05-light-presets-tree.png mode change 100644 => 100755 tool-results/e2e-v63/06-light-preset-panel.png mode change 100644 => 100755 tool-results/e2e-v63/07-light-script.png mode change 100644 => 100755 tool-results/e2e-v63/08-light-help.png mode change 100644 => 100755 tool-results/e2e-v63/09-light-detect.png mode change 100644 => 100755 tool-results/e2e-v63/10-light-beat.png mode change 100644 => 100755 tool-results/e2e-v63/11-light-native.png mode change 100644 => 100755 tool-results/e2e-v63/11b-light-native-full.png mode change 100644 => 100755 tool-results/e2e-v63/12-light-dialog.png mode change 100644 => 100755 tool-results/e2e-v63/13-light-toast.png mode change 100644 => 100755 tool-results/e2e-v63/13b-light-toast-mid.png mode change 100644 => 100755 tool-results/e2e-v63/14-light-delete-dialog.png mode change 100644 => 100755 tool-results/e2e-v63/15-light-after-reload.png mode change 100644 => 100755 tool-results/e2e-v63/16-dark-after-reload.png mode change 100644 => 100755 tool-results/e2e-v63/17-light-REAL-served-css.png mode change 100644 => 100755 tool-results/e2e-v63/18-real-toggle-to-dark.png mode change 100644 => 100755 tool-results/e2e-v63/19-real-toggle-back-to-light.png mode change 100644 => 100755 tool-results/e2e-v63/20-real-light-dialog.png mode change 100644 => 100755 tool-results/e2e-v63/21-real-dark-dialog.png mode change 100644 => 100755 tool-results/e2e-v63/22-real-light-presets.png mode change 100644 => 100755 tool-results/e2e-v63/23-real-light-script.png mode change 100644 => 100755 tool-results/e2e-v63/24-real-light-help.png mode change 100644 => 100755 tool-results/e2e-v63/25-dark-script.png mode change 100644 => 100755 tool-results/e2e-v63/26-mobile-light-top.png mode change 100644 => 100755 tool-results/e2e-v63/27-mobile-light-bottom.png mode change 100644 => 100755 tool-results/e2e-v63/28-mobile-dark-top.png mode change 100644 => 100755 tool-results/e2e-v63/29-mobile-dark-bottom.png mode change 100644 => 100755 tool-results/e2e-v63/30-light-detect-demo-vu.png mode change 100644 => 100755 tool-results/e2e-v63/31-light-detect-live.png mode change 100644 => 100755 tool-results/e2e-v63/32-light-live-visual.png mode change 100644 => 100755 tool-results/e2e-v63/33-real-light-tree-expanded.png mode change 100644 => 100755 tool-results/e2e-v63/34-final-pristine-dark.png mode change 100644 => 100755 tool-results/ove-containment.png mode change 100644 => 100755 tool-results/ove-dragonscales.png mode change 100644 => 100755 tool-results/ove-kingish.png mode change 100644 => 100755 tool-results/read_1793080556522_6263211d285d.txt mode change 100644 => 100755 tool-results/read_1793082276516_65b3b8ecd40e.txt mode change 100644 => 100755 tool-results/read_1793082296314_a82167d4aa24.txt mode change 100644 => 100755 tool-results/read_1793082298965_cc5347613a20.txt mode change 100644 => 100755 tool-results/read_1793086254569_65b3b8ecd40e.txt mode change 100644 => 100755 tool-results/read_1793086294893_e6288764dc88.txt mode change 100644 => 100755 tool-results/read_1793087510034_3be5e504589d.txt diff --git a/src/app/globals.css b/src/app/globals.css index 3de2ac0..df890d3 100755 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -44,6 +44,11 @@ } :root { + /* v6.4 accent plumbing — same values as .dark (the default theme); the + .light block overrides both for the white theme. */ + --accent-rgb: 253 224 71; + --scrollbar-thumb: #56406f; + --radius: 0.625rem; --background: oklch(1 0 0); --foreground: oklch(0.145 0 0); @@ -78,58 +83,100 @@ --sidebar-ring: oklch(0.708 0 0); } +/* + * OVE dark theme (v6.4) — ROYAL PURPLE with light-yellow highlights. + * + * The app chrome is styled with the Tailwind zinc/emerald utilities directly, + * so — like the .light remap below — the dark palette is produced by remapping + * those ramps under `.dark` (unlayered class rules beat Tailwind's @layer + * theme, and color-mix keeps the var references so opacity variants follow): + * • zinc is re-hued into a royal-purple ramp — 950 is the deep purple page, + * 900 the panel purple, 800/700 borders, 500→50 lavender text; + * • emerald (the old green accent) becomes the LIGHT YELLOW highlight + * family — emerald-400 is #fde047 for CTAs/BPM/active states, hover + * lightens to #fef08a, and emerald-950 (text on the yellow buttons) + * becomes a deep royal purple; + * • the shadcn semantic tokens follow (background/card/border/primary…), + * so dialogs, toasts, sliders and switches pick up the same theme; + * • --accent-rgb carries the highlight color as an rgb triple for the + * .glow-* shadow utilities at the bottom of this file. + * The visualization canvas keeps its own black stage by design. + */ .dark { color-scheme: dark; - --background: oklch(0.145 0 0); - --foreground: oklch(0.985 0 0); - --card: oklch(0.205 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.205 0 0); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.922 0 0); - --primary-foreground: oklch(0.205 0 0); - --secondary: oklch(0.269 0 0); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.269 0 0); - --accent-foreground: oklch(0.985 0 0); + + /* semantic tokens — royal purple chrome, light-yellow accent */ + --background: #1b0f2e; + --foreground: #f5f1fb; + --card: #2a1a42; + --card-foreground: #f5f1fb; + --popover: #2a1a42; + --popover-foreground: #f5f1fb; + --primary: #fde047; + --primary-foreground: #241238; + --secondary: #35244e; + --secondary-foreground: #ece4f8; + --muted: #2a1a42; + --muted-foreground: #ab97d1; + --accent: #35244e; + --accent-foreground: #ece4f8; --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.556 0 0); - --chart-1: oklch(0.488 0.243 264.376); - --chart-2: oklch(0.696 0.17 162.48); - --chart-3: oklch(0.769 0.188 70.08); - --chart-4: oklch(0.627 0.265 303.9); - --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.205 0 0); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.269 0 0); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.556 0 0); + --border: #453063; + --input: #453063; + --ring: #fde047; + --chart-1: #fde047; + --chart-2: #937cbb; + --chart-3: #facc15; + --chart-4: #74599f; + --chart-5: #c9b8e6; + --sidebar: #2a1a42; + --sidebar-foreground: #f5f1fb; + --sidebar-primary: #fde047; + --sidebar-primary-foreground: #241238; + --sidebar-accent: #35244e; + --sidebar-accent-foreground: #ece4f8; + --sidebar-border: #453063; + --sidebar-ring: #fde047; + + /* zinc ramp → royal purple */ + --color-zinc-50: #f5f1fb; + --color-zinc-100: #ece4f8; + --color-zinc-200: #ded2f0; + --color-zinc-300: #c9b8e6; + --color-zinc-400: #ab97d1; + --color-zinc-500: #937cbb; + --color-zinc-600: #74599f; + --color-zinc-700: #56406f; + --color-zinc-800: #3d2957; + --color-zinc-900: #2a1a42; + --color-zinc-950: #1b0f2e; + + /* emerald → light-yellow highlights */ + --color-emerald-200: #fef9c3; + --color-emerald-300: #fef08a; + --color-emerald-400: #fde047; + --color-emerald-500: #facc15; + --color-emerald-950: #241238; + + /* highlight rgb triple for the .glow-* utilities + scrollbar thumb */ + --accent-rgb: 253 224 71; + --scrollbar-thumb: #56406f; } /* - * OVE light theme (v6.3). - * - * The app is styled with the Tailwind zinc/emerald/rose/amber utilities - * directly (no semantic tokens for the app chrome), so the light theme is - * implemented by REMAPPING those palettes under `.light` (next-themes sets - * the class on ): - * • zinc is inverted — 950 (page) becomes white, 100 (text) becomes - * near-black; borders/hover/panels land on their light equivalents; - * • emerald/rose/amber accents are deepened one step so text and buttons - * keep WCAG-ish contrast on white (emerald-400 → emerald-600 etc.), - * hover states still lighten (emerald-300 → emerald-500); - * • the visualization canvas keeps its own black stage by design. + * OVE light theme (v6.3) — the white theme on the flip side of the header + * tick switch. Same remap technique: zinc is inverted (950 page → white, + * 100 text → near-black) and the emerald/rose/amber accents are deepened one + * step so text and buttons keep WCAG-ish contrast on white. The dark theme + * above is completely independent — flipping the switch swaps both ramps. */ .light { color-scheme: light; + /* accent plumbing for the white theme — deep emerald highlights */ + --accent-rgb: 5 150 105; + --scrollbar-thumb: #d4d4d8; + /* inverted zinc ramp */ --color-zinc-50: #18181b; --color-zinc-100: #18181b; @@ -161,3 +208,31 @@ @apply bg-background text-foreground; } } + +/* + * Theme-aware accent glows (v6.4). The old hardcoded emerald rgba() shadows + * could not follow the palette swap, so every glow now reads --accent-rgb: + * light yellow on royal purple in the dark theme, deep emerald on white in + * the light theme. + */ +.glow-logo { + box-shadow: 0 0 16px rgb(var(--accent-rgb) / 0.35); +} +.glow-segment { + box-shadow: inset 0 0 0 1px rgb(var(--accent-rgb) / 0.35); +} +.glow-cta { + box-shadow: 0 0 20px rgb(var(--accent-rgb) / 0.25); +} +.glow-setbeat { + box-shadow: 0 0 24px rgb(var(--accent-rgb) / 0.28); +} +.glow-beat-flash { + box-shadow: 0 0 18px 4px rgb(var(--accent-rgb) / 0.65); +} +.glow-beat-idle { + box-shadow: 0 0 8px 1px rgb(var(--accent-rgb) / 0.3); +} +.glow-status { + box-shadow: 0 0 8px rgb(var(--accent-rgb) / 0.8); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index ecd80c9..a3ac340 100755 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -40,6 +40,12 @@ function useBeatWatcher() { useEffect(() => { if (!stats) return + // No source playing/loaded → beat detection is suspended (v6.4). Also + // drop the phase reference so a resumed source can't false-flash. + if (stats.source === 'idle') { + prevPhaseRef.current = null + return + } const b = stats.beat const prev = prevPhaseRef.current prevPhaseRef.current = b.phase diff --git a/src/components/ava/beat-dot.tsx b/src/components/ava/beat-dot.tsx index 29cf58f..49a7de8 100755 --- a/src/components/ava/beat-dot.tsx +++ b/src/components/ava/beat-dot.tsx @@ -22,6 +22,7 @@ export function BeatDot({ size = 44, className }: BeatDotProps) { const phase = useAvaStore((s) => s.stats?.beat.phase ?? 0) const beatFlash = useAvaStore((s) => s.beatFlash) const bpm = useAvaStore((s) => s.stats?.beat.bpm ?? 0) + const idle = useAvaStore((s) => s.source) === 'idle' // flash state is derived: a flash is "on" until its seq is cleared by the // hold timeout (setState only ever happens inside the timeout callback) @@ -47,7 +48,11 @@ export function BeatDot({ size = 44, className }: BeatDotProps) { className={cn('relative shrink-0 select-none', className)} style={{ width: size, height: size }} role="img" - aria-label={`Beat indicator — ${bpm > 0 ? `${bpm.toFixed(1)} BPM` : 'no tempo'}${flashSource === 'predicted' ? ', predicted' : ''}`} + aria-label={ + idle + ? 'Beat indicator — paused, no audio playing' + : `Beat indicator — ${bpm > 0 ? `${bpm.toFixed(1)} BPM` : 'no tempo'}${flashSource === 'predicted' ? ', predicted' : ''}` + } > {/* phase progress ring */} @@ -74,13 +79,16 @@ export function BeatDot({ size = 44, className }: BeatDotProps) { /> - {/* filled pulse dot (audio / tap beats) */} + {/* filled pulse dot (audio / tap beats) — dimmed while no source is + playing/loaded, since beat detection is suspended (v6.4) */}
s.beatFlash) const beatConfig = useAvaStore((s) => s.beatConfig) const setBeatValue = useAvaStore((s) => s.setBeatValue) + const source = useAvaStore((s) => s.source) const beat = stats?.beat const bpm = beat?.bpm ?? 0 @@ -37,11 +38,14 @@ export function BeatTab() { const beatCount = stats?.beatCount ?? 0 const lastSource = beatFlash?.source ?? 'none' - const tapFeedback = lastTap - ? lastTap.locked - ? `Locked: ${lastTap.bpm.toFixed(1)} BPM` - : `Tap ${lastTap.tapCount}… keep tapping` - : 'Tap along with the music — Space works too' + const tapFeedback = + source === 'idle' + ? 'No audio playing — start a source, then tap along' + : lastTap + ? lastTap.locked + ? `Locked: ${lastTap.bpm.toFixed(1)} BPM` + : `Tap ${lastTap.tapCount}… keep tapping` + : 'Tap along with the music — Space works too' return (
@@ -74,7 +78,7 @@ export function BeatTab() { type="button" whileTap={{ scale: 0.965 }} onClick={tapBeat} - className="flex h-14 w-full flex-col items-center justify-center gap-0.5 rounded-xl bg-emerald-400 font-bold text-emerald-950 shadow-[0_0_24px_rgba(52,211,153,0.28)] transition-colors hover:bg-emerald-300" + className="flex h-14 w-full flex-col items-center justify-center gap-0.5 rounded-xl bg-emerald-400 font-bold text-emerald-950 glow-setbeat transition-colors hover:bg-emerald-300" title="Tap to set the tempo (or press Space)" > @@ -162,7 +166,9 @@ export function BeatTab() { How it works: beats are detected from the smoothed bass energy crossing the sensitivity threshold. Consistent intervals lock a tempo (the confidence meter), and a phase-locked predictor then keeps firing beats on the learned grid — shown as a hollow ring with a PRED badge — - whenever the bass disappears. Set Beat teaches the tempo + whenever the bass disappears. Detection and prediction only run while a source is playing — + they pause (and the BPM resets) whenever no music is loaded.{' '} + Set Beat teaches the tempo directly: tap along and two or more taps lock the period and phase to your rhythm.

diff --git a/src/components/ava/detect-tab.tsx b/src/components/ava/detect-tab.tsx index 352d4f3..e6b22a0 100755 --- a/src/components/ava/detect-tab.tsx +++ b/src/components/ava/detect-tab.tsx @@ -56,8 +56,10 @@ const BANDS: BandSpec[] = [ gainKey: 'midGain', lowRange: { min: 100, max: 5000, step: 25 }, highRange: { min: 300, max: 8000, step: 50 }, - barClass: 'bg-amber-300', - textClass: 'text-amber-300', + // v6.4: orange (was amber — too close to the yellow bass/highlight in + // the royal-purple dark theme; keeps the three VU bands distinct) + barClass: 'bg-orange-400', + textClass: 'text-orange-400', }, { key: 'high', diff --git a/src/components/ava/footer-bar.tsx b/src/components/ava/footer-bar.tsx index ece12fb..3b1163b 100755 --- a/src/components/ava/footer-bar.tsx +++ b/src/components/ava/footer-bar.tsx @@ -58,7 +58,9 @@ export function FooterBar() { diff --git a/src/components/ava/header.tsx b/src/components/ava/header.tsx index fbc7178..be2d923 100755 --- a/src/components/ava/header.tsx +++ b/src/components/ava/header.tsx @@ -42,7 +42,7 @@ function SourceSegmentButton({ className={cn( 'flex h-11 items-center gap-1.5 rounded-md px-3 text-xs font-medium transition-colors', active - ? 'bg-emerald-400/15 text-emerald-300 shadow-[inset_0_0_0_1px_rgba(52,211,153,0.35)]' + ? 'glow-segment bg-emerald-400/15 text-emerald-300' : tone === 'stop' ? 'text-rose-300/70 hover:bg-zinc-800 hover:text-rose-300' : 'text-zinc-400 hover:bg-zinc-800 hover:text-zinc-100', @@ -179,7 +179,7 @@ export function AppHeader() {
{/* logo */}
-
+
diff --git a/src/components/ava/native-tab.tsx b/src/components/ava/native-tab.tsx index 0f19a87..3154506 100755 --- a/src/components/ava/native-tab.tsx +++ b/src/components/ava/native-tab.tsx @@ -21,7 +21,7 @@ export function NativeTab() { Download Rust Scaffold (.zip) diff --git a/src/components/ava/shared.ts b/src/components/ava/shared.ts index e6efb8a..bd1a3c9 100755 --- a/src/components/ava/shared.ts +++ b/src/components/ava/shared.ts @@ -1,7 +1,10 @@ // AVA — shared UI constants & tiny helpers for the components +// v6.4: the Firefox scrollbar thumb color follows the theme via +// --scrollbar-thumb (royal purple in dark, gray in light) — the WebKit +// classes below already use the remapped zinc-700 utility. export const thinScrollbar = - '[scrollbar-width:thin] [scrollbar-color:#52525b_transparent] [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-zinc-700' + '[scrollbar-width:thin] [scrollbar-color:var(--scrollbar-thumb)_transparent] [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-zinc-700' /** 1000 → "1.0 kHz", 100 → "100 Hz" */ export function fmtHz(v: number): string { diff --git a/src/lib/ava/beat.ts b/src/lib/ava/beat.ts index 58c80bf..acbb0e8 100755 --- a/src/lib/ava/beat.ts +++ b/src/lib/ava/beat.ts @@ -81,6 +81,27 @@ export class BeatTracker { return this.beatCount } + /** Constant frame returned while no audio source is playing/loaded. */ + private static readonly STANDBY_FRAME: BeatFrame = { + beat: false, + source: 'none', + bpm: 0, + phase: 0, + confidence: 0, + } + + /** + * Called by the engine every frame while NO audio source is playing or + * loaded (v6.4). Beat detection, the prediction grid and any tapped tempo + * all stop: the indicator goes dark, the BPM readout resets and everything + * learned is dropped — so a stale tempo can never leak ghost beats while + * idle (or into the next track once audio starts again). + */ + standby(): BeatFrame { + this.reset() + return BeatTracker.STANDBY_FRAME + } + getBpm(): number { return this.bpm } diff --git a/src/lib/ava/engine.ts b/src/lib/ava/engine.ts index 0bd645a..9ee72dc 100755 --- a/src/lib/ava/engine.ts +++ b/src/lib/ava/engine.ts @@ -156,7 +156,14 @@ export class AvaEngine { // ---- audio + beat ----------------------------------------------------- const levels = this.audio.getBands(this.bandConfig, dt) - const beatFrame = this.beat.update(levels.bassAtt, now, dt, this.beatConfig) + // Beat detection (and the predictor grid) only runs while a source is + // playing/loaded. With no audio, the tracker goes to standby: no beats + // fire, the BPM resets to 0 and learned tempo is dropped — so nothing + // pulses to ghost beats when the music is stopped (v6.4). + const beatFrame = + this.audio.kind === 'idle' + ? this.beat.standby() + : this.beat.update(levels.bassAtt, now, dt, this.beatConfig) // ---- environment ------------------------------------------------------ const env = this.env diff --git a/src/lib/ava/preset-io.ts b/src/lib/ava/preset-io.ts old mode 100644 new mode 100755 diff --git a/src/lib/ava/user-presets.ts b/src/lib/ava/user-presets.ts old mode 100644 new mode 100755 diff --git a/src/lib/preset-paths.ts b/src/lib/preset-paths.ts old mode 100644 new mode 100755 diff --git a/src/lib/version.ts b/src/lib/version.ts index 40b7106..64df946 100755 --- a/src/lib/version.ts +++ b/src/lib/version.ts @@ -7,6 +7,9 @@ // (v6.0 = rename to Open Visualization Engine + version indicator). // // History: +// 6.4 — beat detection suspends when no music is playing/loaded (no ghost +// beats after Stop, BPM resets) + dark theme restyle: royal purple +// chrome with light-yellow highlights // 6.3 — light/dark theme tick switch in the header (white theme via .light // palette remap; per-visitor, persisted by next-themes) // 6.2 — security hardening: read-only embedded library (server refuses all @@ -19,4 +22,4 @@ export const APP_NAME = 'Open Visualization Engine' export const APP_SHORT = 'OVE' -export const APP_VERSION = '6.3' +export const APP_VERSION = '6.4' diff --git a/tool-results/e2e-v62/01-desktop-presets.png b/tool-results/e2e-v62/01-desktop-presets.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v62/02-embedded-folder-selected.png b/tool-results/e2e-v62/02-embedded-folder-selected.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v62/03-embedded-preset-selected.png b/tool-results/e2e-v62/03-embedded-preset-selected.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v62/04-script-tab-created.png b/tool-results/e2e-v62/04-script-tab-created.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v62/05-save-blocked-toast.png b/tool-results/e2e-v62/05-save-blocked-toast.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v62/06-user-library-empty.png b/tool-results/e2e-v62/06-user-library-empty.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v62/07-mobile-firstpaint.png b/tool-results/e2e-v62/07-mobile-firstpaint.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v62/08-mobile-bottom.png b/tool-results/e2e-v62/08-mobile-bottom.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v62/09-mobile-embedded-selected.png b/tool-results/e2e-v62/09-mobile-embedded-selected.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/01-dark-default.png b/tool-results/e2e-v63/01-dark-default.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/02-dark-presets.png b/tool-results/e2e-v63/02-dark-presets.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/03-light-after-toggle.png b/tool-results/e2e-v63/03-light-after-toggle.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/03b-light-toggle-checked.png b/tool-results/e2e-v63/03b-light-toggle-checked.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/04-light-simulated.png b/tool-results/e2e-v63/04-light-simulated.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/05-light-presets-tree.png b/tool-results/e2e-v63/05-light-presets-tree.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/06-light-preset-panel.png b/tool-results/e2e-v63/06-light-preset-panel.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/07-light-script.png b/tool-results/e2e-v63/07-light-script.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/08-light-help.png b/tool-results/e2e-v63/08-light-help.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/09-light-detect.png b/tool-results/e2e-v63/09-light-detect.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/10-light-beat.png b/tool-results/e2e-v63/10-light-beat.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/11-light-native.png b/tool-results/e2e-v63/11-light-native.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/11b-light-native-full.png b/tool-results/e2e-v63/11b-light-native-full.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/12-light-dialog.png b/tool-results/e2e-v63/12-light-dialog.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/13-light-toast.png b/tool-results/e2e-v63/13-light-toast.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/13b-light-toast-mid.png b/tool-results/e2e-v63/13b-light-toast-mid.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/14-light-delete-dialog.png b/tool-results/e2e-v63/14-light-delete-dialog.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/15-light-after-reload.png b/tool-results/e2e-v63/15-light-after-reload.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/16-dark-after-reload.png b/tool-results/e2e-v63/16-dark-after-reload.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/17-light-REAL-served-css.png b/tool-results/e2e-v63/17-light-REAL-served-css.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/18-real-toggle-to-dark.png b/tool-results/e2e-v63/18-real-toggle-to-dark.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/19-real-toggle-back-to-light.png b/tool-results/e2e-v63/19-real-toggle-back-to-light.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/20-real-light-dialog.png b/tool-results/e2e-v63/20-real-light-dialog.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/21-real-dark-dialog.png b/tool-results/e2e-v63/21-real-dark-dialog.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/22-real-light-presets.png b/tool-results/e2e-v63/22-real-light-presets.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/23-real-light-script.png b/tool-results/e2e-v63/23-real-light-script.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/24-real-light-help.png b/tool-results/e2e-v63/24-real-light-help.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/25-dark-script.png b/tool-results/e2e-v63/25-dark-script.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/26-mobile-light-top.png b/tool-results/e2e-v63/26-mobile-light-top.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/27-mobile-light-bottom.png b/tool-results/e2e-v63/27-mobile-light-bottom.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/28-mobile-dark-top.png b/tool-results/e2e-v63/28-mobile-dark-top.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/29-mobile-dark-bottom.png b/tool-results/e2e-v63/29-mobile-dark-bottom.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/30-light-detect-demo-vu.png b/tool-results/e2e-v63/30-light-detect-demo-vu.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/31-light-detect-live.png b/tool-results/e2e-v63/31-light-detect-live.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/32-light-live-visual.png b/tool-results/e2e-v63/32-light-live-visual.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/33-real-light-tree-expanded.png b/tool-results/e2e-v63/33-real-light-tree-expanded.png old mode 100644 new mode 100755 diff --git a/tool-results/e2e-v63/34-final-pristine-dark.png b/tool-results/e2e-v63/34-final-pristine-dark.png old mode 100644 new mode 100755 diff --git a/tool-results/ove-containment.png b/tool-results/ove-containment.png old mode 100644 new mode 100755 diff --git a/tool-results/ove-dragonscales.png b/tool-results/ove-dragonscales.png old mode 100644 new mode 100755 diff --git a/tool-results/ove-kingish.png b/tool-results/ove-kingish.png old mode 100644 new mode 100755 diff --git a/tool-results/read_1793080556522_6263211d285d.txt b/tool-results/read_1793080556522_6263211d285d.txt old mode 100644 new mode 100755 diff --git a/tool-results/read_1793082276516_65b3b8ecd40e.txt b/tool-results/read_1793082276516_65b3b8ecd40e.txt old mode 100644 new mode 100755 diff --git a/tool-results/read_1793082296314_a82167d4aa24.txt b/tool-results/read_1793082296314_a82167d4aa24.txt old mode 100644 new mode 100755 diff --git a/tool-results/read_1793082298965_cc5347613a20.txt b/tool-results/read_1793082298965_cc5347613a20.txt old mode 100644 new mode 100755 diff --git a/tool-results/read_1793086254569_65b3b8ecd40e.txt b/tool-results/read_1793086254569_65b3b8ecd40e.txt old mode 100644 new mode 100755 diff --git a/tool-results/read_1793086294893_e6288764dc88.txt b/tool-results/read_1793086294893_e6288764dc88.txt old mode 100644 new mode 100755 diff --git a/tool-results/read_1793087510034_3be5e504589d.txt b/tool-results/read_1793087510034_3be5e504589d.txt old mode 100644 new mode 100755 diff --git a/worklog.md b/worklog.md index 98ce838..cf0a232 100755 --- a/worklog.md +++ b/worklog.md @@ -1234,3 +1234,72 @@ Stage Summary: - Implementation note for future styling: new UI should keep using the zinc/emerald utilities (not raw hex) so both themes keep working; anything on the emerald buttons should use text-emerald-950. + +--- +Task ID: 16 +Agent: main (Z.ai Code) +Task: v6.4 — (a) beat detection stops when no music is playing/loaded; (b) dark theme restyled to royal purple with light-yellow highlights + +Work Log: +- Root cause of ghost beats: AvaEngine.loop fed BeatTracker.update() every frame + regardless of audio state, and the phase-locked predictor keeps firing after + confidence builds (music just stopped) or after Set Beat taps (taps block the + confidence decay path) — so the indicator pulsed to a stale grid with no source. +- src/lib/ava/beat.ts — new standby(): full reset + returns a constant + STANDBY_FRAME {beat:false, source:'none', bpm:0, phase:0, confidence:0}. + Called every idle frame; drops everything learned so stale tempo can never + leak ghost beats while idle OR into the next track (source switches relearn + fresh — a feature). +- src/lib/ava/engine.ts — loop gates the tracker: audio.kind === 'idle' → + standby(), else update(). env.beat/bpm/beatphase/beatconf go to 0 while idle; + stats.source still reports the kind. +- src/app/page.tsx — useBeatWatcher: early return on stats.source === 'idle' + (also drops prevPhaseRef so a resumed source can't false-flash). +- src/components/ava/beat-dot.tsx — dot dims to opacity-35 (no glow) while + idle (flashes still override), aria-label "Beat indicator — paused, no audio + playing"; theme-aware glow-beat-flash/glow-beat-idle classes. +- src/components/ava/beat-tab.tsx — Set Beat feedback while idle: "No audio + playing — start a source, then tap along"; SET BEAT glow class; "How it + works" documents the new pause behavior. +- Theme (dark → royal purple + light yellow), same proven remap technique as + v6.3 .light — src/app/globals.css .dark block rewritten: + • semantic tokens: background #1b0f2e, card/popover #2a1a42, secondary + #35244e, border/input #453063, primary/ring #fde047 (+purple foreground + #241238), chart/sidebar purple-yellow family → dialogs/toasts/sliders/ + switches follow. + • zinc ramp → royal purple: 950 #1b0f2e (page/header/footer), 900 #2a1a42 + (panels/tabs), 800 #3d2957 (borders/tracks), 700 #56406f, 600 #74599f, + 500 #937cbb … 50 #f5f1fb (lavender text ramp). + • emerald → light yellow: 200 #fef9c3, 300 #fef08a, 400 #fde047 (CTAs, BPM, + active states, beat dot), 500 #facc15, 950 #241238 (deep purple text on + yellow buttons — the yellow CTAs' label color). + • new --accent-rgb (253 224 71 dark / 5 150 105 light) + --scrollbar-thumb + (also defaulted in :root); seven .glow-* utilities (logo, segment, cta, + setbeat, beat-flash, beat-idle, status) replace ALL hardcoded emerald + rgba() shadows (7 spots, 6 files) so glows follow the theme; Firefox + scrollbar-color now var-driven (shared.ts). + • light theme untouched apart from the two new vars — verified intact. +- src/components/ava/detect-tab.tsx — Mid band VU amber-300 → orange-400 + (amber was indistinguishable from the new yellow bass/highlight; three + bands now yellow / orange / rose in dark, green / orange / rose in light). +- src/lib/version.ts → 6.4 (+history line). bun run lint clean. + +Stage Summary: +- E2E (agent-browser, 1440×900 + 390×844), ALL PASS: + • Beat: demo + 5× Set Beat taps → locks 120.2 BPM, predictor carries; + Stop → ZERO ghost flashes over 8 s (previously ~16 predicted beats), + BPM "—", beat count 0, dot dimmed 0.35, aria "paused, no audio playing"; + idle taps = one flash each, no sustained grid, honest feedback text. + • Demo auto-detect staying unlocked is PRE-EXISTING documented behavior + (worklog Task 8: demo bassline keeps rolling average high; Set Beat / + real music lock fine) — not a v6.4 regression. + • Dark: body #1b0f2e, logo tile/BPM/Native CTA/beat dot #fde047, CTA text + #241238, footer micro text lavender #937cbb/#c9b8e6 (VLM "gray" note was + the intentional muted style), VU bands distinct, zero console errors. + • Light: white body, emerald-600 accents, emerald glow — no yellow leakage. + • Mobile: no horizontal scroll, footer flush at viewport bottom when + scrolled to end; VLM review of dark+mobile screenshots: cohesive royal + purple + light yellow, no breakage. +- New styling rule going forward: keep using zinc/emerald utilities (they are + the remap surface); on yellow CTAs always text-emerald-950; for accent glows + use the .glow-* classes, never rgba() literals.