v6.4 — beat detection suspends with no music (no ghost beats) + royal purple dark theme with light-yellow highlights
|
|
@ -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 <html>):
|
||||
* • 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 */}
|
||||
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} className="absolute inset-0 -rotate-90">
|
||||
|
|
@ -74,13 +79,16 @@ export function BeatDot({ size = 44, className }: BeatDotProps) {
|
|||
/>
|
||||
</svg>
|
||||
|
||||
{/* 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) */}
|
||||
<div
|
||||
className={cn(
|
||||
'absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-emerald-400 transition-all duration-150 ease-out',
|
||||
flashing && !predicted
|
||||
? 'opacity-100 shadow-[0_0_18px_4px_rgba(52,211,153,0.65)]'
|
||||
: 'opacity-80 shadow-[0_0_8px_1px_rgba(52,211,153,0.3)]',
|
||||
? 'glow-beat-flash opacity-100'
|
||||
: idle
|
||||
? 'opacity-35'
|
||||
: 'glow-beat-idle opacity-80',
|
||||
)}
|
||||
style={{
|
||||
width: dotBase,
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ export function BeatTab() {
|
|||
const beatFlash = useAvaStore((s) => 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,7 +38,10 @@ export function BeatTab() {
|
|||
const beatCount = stats?.beatCount ?? 0
|
||||
const lastSource = beatFlash?.source ?? 'none'
|
||||
|
||||
const tapFeedback = lastTap
|
||||
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`
|
||||
|
|
@ -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)"
|
||||
>
|
||||
<span className="flex items-center gap-2 text-sm font-bold tracking-[0.12em]">
|
||||
|
|
@ -162,7 +166,9 @@ export function BeatTab() {
|
|||
<span className="font-semibold text-zinc-400">How it works:</span> 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. <span className="font-semibold text-zinc-400">Set Beat</span> 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.{' '}
|
||||
<span className="font-semibold text-zinc-400">Set Beat</span> teaches the tempo
|
||||
directly: tap along and two or more taps lock the period and phase to your rhythm.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -58,7 +58,9 @@ export function FooterBar() {
|
|||
<span
|
||||
className={cn(
|
||||
'size-1.5 rounded-full',
|
||||
error ? 'animate-pulse bg-rose-400 shadow-[0_0_8px_rgba(251,113,133,0.8)]' : 'bg-emerald-400 shadow-[0_0_8px_rgba(52,211,153,0.8)]',
|
||||
error
|
||||
? 'animate-pulse bg-rose-400 shadow-[0_0_8px_rgba(251,113,133,0.8)]'
|
||||
: 'glow-status bg-emerald-400',
|
||||
)}
|
||||
aria-hidden
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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() {
|
|||
<div className="mx-auto flex max-w-[1800px] flex-wrap items-center gap-x-3 gap-y-2 px-3 py-2 sm:px-4">
|
||||
{/* logo */}
|
||||
<div className="order-1 flex items-center gap-2.5">
|
||||
<div className="grid size-9 place-items-center rounded-lg bg-emerald-400 shadow-[0_0_16px_rgba(52,211,153,0.35)]">
|
||||
<div className="glow-logo grid size-9 place-items-center rounded-lg bg-emerald-400">
|
||||
<AudioWaveform className="size-5 text-emerald-950" aria-hidden />
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export function NativeTab() {
|
|||
<a
|
||||
href="/api/rust-scaffold"
|
||||
download
|
||||
className="flex h-12 items-center justify-center gap-2 rounded-xl bg-emerald-400 font-semibold text-emerald-950 shadow-[0_0_20px_rgba(52,211,153,0.25)] transition-colors hover:bg-emerald-300"
|
||||
className="glow-cta flex h-12 items-center justify-center gap-2 rounded-xl bg-emerald-400 font-semibold text-emerald-950 transition-colors hover:bg-emerald-300"
|
||||
>
|
||||
<Download className="size-4" />
|
||||
Download Rust Scaffold (.zip)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
0
src/lib/ava/preset-io.ts
Normal file → Executable file
0
src/lib/ava/user-presets.ts
Normal file → Executable file
0
src/lib/preset-paths.ts
Normal file → Executable file
|
|
@ -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'
|
||||
|
|
|
|||
0
tool-results/e2e-v62/01-desktop-presets.png
Normal file → Executable file
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
0
tool-results/e2e-v62/02-embedded-folder-selected.png
Normal file → Executable file
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
0
tool-results/e2e-v62/03-embedded-preset-selected.png
Normal file → Executable file
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
0
tool-results/e2e-v62/04-script-tab-created.png
Normal file → Executable file
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
0
tool-results/e2e-v62/05-save-blocked-toast.png
Normal file → Executable file
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
0
tool-results/e2e-v62/06-user-library-empty.png
Normal file → Executable file
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
0
tool-results/e2e-v62/07-mobile-firstpaint.png
Normal file → Executable file
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
0
tool-results/e2e-v62/08-mobile-bottom.png
Normal file → Executable file
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
0
tool-results/e2e-v62/09-mobile-embedded-selected.png
Normal file → Executable file
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
0
tool-results/e2e-v63/01-dark-default.png
Normal file → Executable file
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
0
tool-results/e2e-v63/02-dark-presets.png
Normal file → Executable file
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
0
tool-results/e2e-v63/03-light-after-toggle.png
Normal file → Executable file
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
0
tool-results/e2e-v63/03b-light-toggle-checked.png
Normal file → Executable file
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
0
tool-results/e2e-v63/04-light-simulated.png
Normal file → Executable file
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 107 KiB |
0
tool-results/e2e-v63/05-light-presets-tree.png
Normal file → Executable file
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 109 KiB |
0
tool-results/e2e-v63/06-light-preset-panel.png
Normal file → Executable file
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
0
tool-results/e2e-v63/07-light-script.png
Normal file → Executable file
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 129 KiB |
0
tool-results/e2e-v63/08-light-help.png
Normal file → Executable file
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
0
tool-results/e2e-v63/09-light-detect.png
Normal file → Executable file
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
0
tool-results/e2e-v63/10-light-beat.png
Normal file → Executable file
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 121 KiB |
0
tool-results/e2e-v63/11-light-native.png
Normal file → Executable file
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 151 KiB |
0
tool-results/e2e-v63/11b-light-native-full.png
Normal file → Executable file
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 151 KiB |
0
tool-results/e2e-v63/12-light-dialog.png
Normal file → Executable file
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
0
tool-results/e2e-v63/13-light-toast.png
Normal file → Executable file
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
0
tool-results/e2e-v63/13b-light-toast-mid.png
Normal file → Executable file
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 114 KiB |
0
tool-results/e2e-v63/14-light-delete-dialog.png
Normal file → Executable file
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
0
tool-results/e2e-v63/15-light-after-reload.png
Normal file → Executable file
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
0
tool-results/e2e-v63/16-dark-after-reload.png
Normal file → Executable file
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
0
tool-results/e2e-v63/17-light-REAL-served-css.png
Normal file → Executable file
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 107 KiB |
0
tool-results/e2e-v63/18-real-toggle-to-dark.png
Normal file → Executable file
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
0
tool-results/e2e-v63/19-real-toggle-back-to-light.png
Normal file → Executable file
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
0
tool-results/e2e-v63/20-real-light-dialog.png
Normal file → Executable file
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
0
tool-results/e2e-v63/21-real-dark-dialog.png
Normal file → Executable file
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
0
tool-results/e2e-v63/22-real-light-presets.png
Normal file → Executable file
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
0
tool-results/e2e-v63/23-real-light-script.png
Normal file → Executable file
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
0
tool-results/e2e-v63/24-real-light-help.png
Normal file → Executable file
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 129 KiB |
0
tool-results/e2e-v63/25-dark-script.png
Normal file → Executable file
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 123 KiB |
0
tool-results/e2e-v63/26-mobile-light-top.png
Normal file → Executable file
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
0
tool-results/e2e-v63/27-mobile-light-bottom.png
Normal file → Executable file
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
0
tool-results/e2e-v63/28-mobile-dark-top.png
Normal file → Executable file
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
0
tool-results/e2e-v63/29-mobile-dark-bottom.png
Normal file → Executable file
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
0
tool-results/e2e-v63/30-light-detect-demo-vu.png
Normal file → Executable file
|
Before Width: | Height: | Size: 752 KiB After Width: | Height: | Size: 752 KiB |
0
tool-results/e2e-v63/31-light-detect-live.png
Normal file → Executable file
|
Before Width: | Height: | Size: 532 KiB After Width: | Height: | Size: 532 KiB |
0
tool-results/e2e-v63/32-light-live-visual.png
Normal file → Executable file
|
Before Width: | Height: | Size: 670 KiB After Width: | Height: | Size: 670 KiB |
0
tool-results/e2e-v63/33-real-light-tree-expanded.png
Normal file → Executable file
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
0
tool-results/e2e-v63/34-final-pristine-dark.png
Normal file → Executable file
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
0
tool-results/ove-containment.png
Normal file → Executable file
|
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 157 KiB |
0
tool-results/ove-dragonscales.png
Normal file → Executable file
|
Before Width: | Height: | Size: 220 KiB After Width: | Height: | Size: 220 KiB |
0
tool-results/ove-kingish.png
Normal file → Executable file
|
Before Width: | Height: | Size: 250 KiB After Width: | Height: | Size: 250 KiB |
0
tool-results/read_1793080556522_6263211d285d.txt
Normal file → Executable file
0
tool-results/read_1793082276516_65b3b8ecd40e.txt
Normal file → Executable file
0
tool-results/read_1793082296314_a82167d4aa24.txt
Normal file → Executable file
0
tool-results/read_1793082298965_cc5347613a20.txt
Normal file → Executable file
0
tool-results/read_1793086254569_65b3b8ecd40e.txt
Normal file → Executable file
0
tool-results/read_1793086294893_e6288764dc88.txt
Normal file → Executable file
0
tool-results/read_1793087510034_3be5e504589d.txt
Normal file → Executable file
69
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.
|
||||
|
|
|
|||