v6.3 — light/dark theme tick switch in the header
|
|
@ -79,6 +79,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
|
color-scheme: dark;
|
||||||
--background: oklch(0.145 0 0);
|
--background: oklch(0.145 0 0);
|
||||||
--foreground: oklch(0.985 0 0);
|
--foreground: oklch(0.985 0 0);
|
||||||
--card: oklch(0.205 0 0);
|
--card: oklch(0.205 0 0);
|
||||||
|
|
@ -112,6 +113,46 @@
|
||||||
--sidebar-ring: oklch(0.556 0 0);
|
--sidebar-ring: oklch(0.556 0 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
.light {
|
||||||
|
color-scheme: light;
|
||||||
|
|
||||||
|
/* inverted zinc ramp */
|
||||||
|
--color-zinc-50: #18181b;
|
||||||
|
--color-zinc-100: #18181b;
|
||||||
|
--color-zinc-200: #27272a;
|
||||||
|
--color-zinc-300: #3f3f46;
|
||||||
|
--color-zinc-400: #52525b;
|
||||||
|
--color-zinc-500: #71717a;
|
||||||
|
--color-zinc-600: #8b8b93;
|
||||||
|
--color-zinc-700: #d4d4d8;
|
||||||
|
--color-zinc-800: #e4e4e7;
|
||||||
|
--color-zinc-900: #fafafa;
|
||||||
|
--color-zinc-950: #ffffff;
|
||||||
|
|
||||||
|
/* deeper accents for white backgrounds */
|
||||||
|
--color-emerald-200: #a7f3d0;
|
||||||
|
--color-emerald-300: #10b981;
|
||||||
|
--color-emerald-400: #059669;
|
||||||
|
--color-rose-300: #f43f5e;
|
||||||
|
--color-rose-400: #e11d48;
|
||||||
|
--color-amber-300: #b45309;
|
||||||
|
--color-amber-400: #d97706;
|
||||||
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border outline-ring/50;
|
@apply border-border outline-ring/50;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import { Geist, Geist_Mono } from "next/font/google";
|
||||||
|
import { ThemeProvider } from "next-themes";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import { Toaster } from "@/components/ui/toaster";
|
import { Toaster } from "@/components/ui/toaster";
|
||||||
import { APP_NAME, APP_SHORT, APP_VERSION } from "@/lib/version";
|
import { APP_NAME, APP_SHORT, APP_VERSION } from "@/lib/version";
|
||||||
|
|
@ -30,12 +31,21 @@ export default function RootLayout({
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en" className="dark" suppressHydrationWarning>
|
<html lang="en" suppressHydrationWarning>
|
||||||
<body
|
<body
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased bg-zinc-950 text-zinc-100 font-sans`}
|
className={`${geistSans.variable} ${geistMono.variable} antialiased bg-zinc-950 text-zinc-100 font-sans`}
|
||||||
|
>
|
||||||
|
{/* class strategy: next-themes puts "dark" (default) or "light" on
|
||||||
|
<html> pre-paint; globals.css remaps the palettes under .light */}
|
||||||
|
<ThemeProvider
|
||||||
|
attribute="class"
|
||||||
|
defaultTheme="dark"
|
||||||
|
enableSystem={false}
|
||||||
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<Toaster />
|
<Toaster />
|
||||||
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ export function BeatTab() {
|
||||||
type="button"
|
type="button"
|
||||||
whileTap={{ scale: 0.965 }}
|
whileTap={{ scale: 0.965 }}
|
||||||
onClick={tapBeat}
|
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-zinc-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 shadow-[0_0_24px_rgba(52,211,153,0.28)] transition-colors hover:bg-emerald-300"
|
||||||
title="Tap to set the tempo (or press Space)"
|
title="Tap to set the tempo (or press Space)"
|
||||||
>
|
>
|
||||||
<span className="flex items-center gap-2 text-sm font-bold tracking-[0.12em]">
|
<span className="flex items-center gap-2 text-sm font-bold tracking-[0.12em]">
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
// AVA — sticky app header: logo, audio source controls (segmented), demo BPM
|
// OVE — sticky app header: logo, audio source controls (segmented), demo BPM
|
||||||
// slider, BPM readout, and the beat indicator + "Set Beat" button side by side
|
// slider, theme tick switch, BPM readout, and the beat indicator + "Set Beat"
|
||||||
// (hard requirement: that pairing is always visible in the header).
|
// button side by side (hard requirement: that pairing is always visible in
|
||||||
|
// the header).
|
||||||
|
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useRef } from 'react'
|
import { useRef, useSyncExternalStore } from 'react'
|
||||||
import { AudioWaveform, Disc3, FileAudio, Link2, Mic, Square, Timer } from 'lucide-react'
|
import { AudioWaveform, Disc3, FileAudio, Link2, Mic, Moon, Square, Sun, Timer } from 'lucide-react'
|
||||||
|
import { useTheme } from 'next-themes'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Slider } from '@/components/ui/slider'
|
import { Slider } from '@/components/ui/slider'
|
||||||
|
import { Switch } from '@/components/ui/switch'
|
||||||
import { BeatDot } from '@/components/ava/beat-dot'
|
import { BeatDot } from '@/components/ava/beat-dot'
|
||||||
import { LinkDialog } from '@/components/ava/link-dialog'
|
import { LinkDialog } from '@/components/ava/link-dialog'
|
||||||
import { tapBeat, useAudioSource } from '@/components/ava/audio-source'
|
import { tapBeat, useAudioSource } from '@/components/ava/audio-source'
|
||||||
|
|
@ -140,6 +143,36 @@ function BpmReadout() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Light-switch tick: left position (unchecked) keeps the dark theme, flipping
|
||||||
|
* right (checked) switches the page to the white theme. Persisted per visitor
|
||||||
|
* by next-themes (localStorage). The useSyncExternalStore mount guard returns
|
||||||
|
* false on the server / first client render and true after hydration, so the
|
||||||
|
* checkbox never causes a hydration mismatch.
|
||||||
|
*/
|
||||||
|
const emptySubscribe = () => () => {}
|
||||||
|
function ThemeToggle() {
|
||||||
|
const { resolvedTheme, setTheme } = useTheme()
|
||||||
|
const mounted = useSyncExternalStore(emptySubscribe, () => true, () => false)
|
||||||
|
const isLight = mounted && resolvedTheme === 'light'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex h-11 items-center gap-1.5 rounded-lg border border-zinc-800 bg-zinc-900/60 px-2.5"
|
||||||
|
title={isLight ? 'Theme: light — flip left for dark' : 'Theme: dark — flip right for light'}
|
||||||
|
>
|
||||||
|
<Moon className="size-3.5 shrink-0 text-zinc-500" aria-hidden />
|
||||||
|
<Switch
|
||||||
|
checked={isLight}
|
||||||
|
onCheckedChange={(v) => setTheme(v ? 'light' : 'dark')}
|
||||||
|
aria-label={isLight ? 'Switch to dark theme' : 'Switch to light theme'}
|
||||||
|
className="data-[state=checked]:bg-emerald-400 data-[state=unchecked]:bg-zinc-600"
|
||||||
|
/>
|
||||||
|
<Sun className="size-3.5 shrink-0 text-zinc-500" aria-hidden />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function AppHeader() {
|
export function AppHeader() {
|
||||||
return (
|
return (
|
||||||
<header className="sticky top-0 z-40 border-b border-zinc-800/80 bg-zinc-950/80 backdrop-blur supports-[backdrop-filter]:bg-zinc-950/60">
|
<header className="sticky top-0 z-40 border-b border-zinc-800/80 bg-zinc-950/80 backdrop-blur supports-[backdrop-filter]:bg-zinc-950/60">
|
||||||
|
|
@ -147,7 +180,7 @@ export function AppHeader() {
|
||||||
{/* logo */}
|
{/* logo */}
|
||||||
<div className="order-1 flex items-center gap-2.5">
|
<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="grid size-9 place-items-center rounded-lg bg-emerald-400 shadow-[0_0_16px_rgba(52,211,153,0.35)]">
|
||||||
<AudioWaveform className="size-5 text-zinc-950" aria-hidden />
|
<AudioWaveform className="size-5 text-emerald-950" aria-hidden />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-base font-bold leading-none tracking-wide">{APP_SHORT}</h1>
|
<h1 className="text-base font-bold leading-none tracking-wide">{APP_SHORT}</h1>
|
||||||
|
|
@ -162,13 +195,14 @@ export function AppHeader() {
|
||||||
<SourceControls />
|
<SourceControls />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* BPM readout + beat indicator + Set Beat — always paired, always visible */}
|
{/* theme switch + BPM readout + beat indicator + Set Beat — always visible */}
|
||||||
<div className="order-2 ml-auto flex items-center gap-2.5 md:order-3 sm:gap-3">
|
<div className="order-2 ml-auto flex items-center gap-2.5 md:order-3 sm:gap-3">
|
||||||
|
<ThemeToggle />
|
||||||
<BpmReadout />
|
<BpmReadout />
|
||||||
<BeatDot size={44} />
|
<BeatDot size={44} />
|
||||||
<Button
|
<Button
|
||||||
onClick={tapBeat}
|
onClick={tapBeat}
|
||||||
className="h-11 gap-1.5 bg-emerald-400 px-3 font-semibold text-zinc-950 hover:bg-emerald-300 sm:px-4"
|
className="h-11 gap-1.5 bg-emerald-400 px-3 font-semibold text-emerald-950 hover:bg-emerald-300 sm:px-4"
|
||||||
title="Tap to set the tempo (or press Space)"
|
title="Tap to set the tempo (or press Space)"
|
||||||
>
|
>
|
||||||
<Timer className="size-4" />
|
<Timer className="size-4" />
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ function HelpTable({ rows, mono = true }: { rows: [string, string][]; mono?: boo
|
||||||
{rows.map(([name, desc]) => (
|
{rows.map(([name, desc]) => (
|
||||||
<tr key={name} className="border-b border-zinc-800/60 last:border-0 align-top">
|
<tr key={name} className="border-b border-zinc-800/60 last:border-0 align-top">
|
||||||
<td
|
<td
|
||||||
className={`py-1 pr-3 whitespace-nowrap text-emerald-300/90 ${mono ? 'font-mono text-[11px]' : 'text-xs'}`}
|
className={`py-1 pr-3 whitespace-nowrap text-emerald-400/90 ${mono ? 'font-mono text-[11px]' : 'text-xs'}`}
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -93,15 +93,15 @@ export function HelpPanel() {
|
||||||
<ol className="list-inside list-decimal space-y-1 text-[11px] leading-relaxed text-zinc-400">
|
<ol className="list-inside list-decimal space-y-1 text-[11px] leading-relaxed text-zinc-400">
|
||||||
<li>Write header keys (name, mode, blend, persist, linewidth) at the top.</li>
|
<li>Write header keys (name, mode, blend, persist, linewidth) at the top.</li>
|
||||||
<li>
|
<li>
|
||||||
Put setup code in <span className="font-mono text-emerald-300/90">[init]</span>, per-frame code in{' '}
|
Put setup code in <span className="font-mono text-emerald-400/90">[init]</span>, per-frame code in{' '}
|
||||||
<span className="font-mono text-emerald-300/90">[frame]</span>, reactions in{' '}
|
<span className="font-mono text-emerald-400/90">[frame]</span>, reactions in{' '}
|
||||||
<span className="font-mono text-emerald-300/90">[beat]</span>, and the geometry in{' '}
|
<span className="font-mono text-emerald-400/90">[beat]</span>, and the geometry in{' '}
|
||||||
<span className="font-mono text-emerald-300/90">[point]</span>.
|
<span className="font-mono text-emerald-400/90">[point]</span>.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Set <span className="font-mono text-emerald-300/90">n</span> (point count) in [init], drive it with{' '}
|
Set <span className="font-mono text-emerald-400/90">n</span> (point count) in [init], drive it with{' '}
|
||||||
<span className="font-mono text-emerald-300/90">bass</span>,{' '}
|
<span className="font-mono text-emerald-400/90">bass</span>,{' '}
|
||||||
<span className="font-mono text-emerald-300/90">beat</span> and <span className="font-mono text-emerald-300/90">v</span>.
|
<span className="font-mono text-emerald-400/90">beat</span> and <span className="font-mono text-emerald-400/90">v</span>.
|
||||||
</li>
|
</li>
|
||||||
<li>Hit Apply ▶ — errors are flagged next to the exact line.</li>
|
<li>Hit Apply ▶ — errors are flagged next to the exact line.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
@ -118,15 +118,15 @@ export function HelpPanel() {
|
||||||
<HelpSection title="Functions">
|
<HelpSection title="Functions">
|
||||||
<HelpTable rows={FUNCS} />
|
<HelpTable rows={FUNCS} />
|
||||||
<p className="mt-2 text-[11px] text-zinc-500">
|
<p className="mt-2 text-[11px] text-zinc-500">
|
||||||
Constants: <span className="font-mono text-emerald-300/90">pi tau e</span>
|
Constants: <span className="font-mono text-emerald-400/90">pi tau e</span>
|
||||||
</p>
|
</p>
|
||||||
</HelpSection>
|
</HelpSection>
|
||||||
|
|
||||||
<HelpSection title="Operators">
|
<HelpSection title="Operators">
|
||||||
<p className="font-mono text-[11px] leading-relaxed text-zinc-400">
|
<p className="font-mono text-[11px] leading-relaxed text-zinc-400">
|
||||||
+ - * / % ^ (power) · == != < <= > >= · && || ! ·
|
+ - * / % ^ (power) · == != < <= > >= · && || ! ·
|
||||||
ternary <span className="text-emerald-300/90">c ? a : b</span> · assignment{' '}
|
ternary <span className="text-emerald-400/90">c ? a : b</span> · assignment{' '}
|
||||||
<span className="text-emerald-300/90">=</span> (works inside if() and ternary branches)
|
<span className="text-emerald-400/90">=</span> (works inside if() and ternary branches)
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-1.5 text-[11px] leading-relaxed text-zinc-500">
|
<p className="mt-1.5 text-[11px] leading-relaxed text-zinc-500">
|
||||||
Statements are separated by newlines or semicolons. Comments start with <span className="font-mono">{'#'}</span> or{' '}
|
Statements are separated by newlines or semicolons. Comments start with <span className="font-mono">{'#'}</span> or{' '}
|
||||||
|
|
@ -137,7 +137,7 @@ export function HelpPanel() {
|
||||||
<HelpSection title="Example formulas">
|
<HelpSection title="Example formulas">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
{EXAMPLES.map(([code, note]) => (
|
{EXAMPLES.map(([code, note]) => (
|
||||||
<div key={code} className="flex flex-col gap-0.5 rounded border border-zinc-800/70 bg-black/40 px-2.5 py-1.5">
|
<div key={code} className="flex flex-col gap-0.5 rounded border border-zinc-800/70 bg-zinc-800/40 px-2.5 py-1.5">
|
||||||
<code className="font-mono text-[11px] text-zinc-200">{code}</code>
|
<code className="font-mono text-[11px] text-zinc-200">{code}</code>
|
||||||
<span className="text-[10px] text-zinc-500">{note}</span>
|
<span className="text-[10px] text-zinc-500">{note}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -149,7 +149,7 @@ export function HelpPanel() {
|
||||||
<p className="mb-2 text-[11px] leading-relaxed text-zinc-400">
|
<p className="mb-2 text-[11px] leading-relaxed text-zinc-400">
|
||||||
A plain-text file: optional header keys before the first section, then any of the four sections in any order.
|
A plain-text file: optional header keys before the first section, then any of the four sections in any order.
|
||||||
</p>
|
</p>
|
||||||
<pre className="overflow-x-auto rounded border border-zinc-800/70 bg-black/40 p-2.5 font-mono text-[11px] leading-relaxed text-zinc-300">
|
<pre className="overflow-x-auto rounded border border-zinc-800/70 bg-zinc-800/40 p-2.5 font-mono text-[11px] leading-relaxed text-zinc-300">
|
||||||
{`# header keys
|
{`# header keys
|
||||||
name: My Visualization
|
name: My Visualization
|
||||||
author: you
|
author: you
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ function LinkDialogBody() {
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={busy || !url.trim()}
|
disabled={busy || !url.trim()}
|
||||||
className="shrink-0 gap-1.5 bg-emerald-400 font-semibold text-zinc-950 hover:bg-emerald-300"
|
className="shrink-0 gap-1.5 bg-emerald-400 font-semibold text-emerald-950 hover:bg-emerald-300"
|
||||||
>
|
>
|
||||||
{busy ? <Loader2 className="size-4 animate-spin" aria-hidden /> : null}
|
{busy ? <Loader2 className="size-4 animate-spin" aria-hidden /> : null}
|
||||||
{busy ? 'Loading' : 'Load'}
|
{busy ? 'Loading' : 'Load'}
|
||||||
|
|
|
||||||
|
|
@ -21,14 +21,14 @@ export function NativeTab() {
|
||||||
<a
|
<a
|
||||||
href="/api/rust-scaffold"
|
href="/api/rust-scaffold"
|
||||||
download
|
download
|
||||||
className="flex h-12 items-center justify-center gap-2 rounded-xl bg-emerald-400 font-semibold text-zinc-950 shadow-[0_0_20px_rgba(52,211,153,0.25)] transition-colors hover:bg-emerald-300"
|
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"
|
||||||
>
|
>
|
||||||
<Download className="size-4" />
|
<Download className="size-4" />
|
||||||
Download Rust Scaffold (.zip)
|
Download Rust Scaffold (.zip)
|
||||||
</a>
|
</a>
|
||||||
<p className="text-[11px] text-zinc-500">{BLUEPRINT.downloadNote}</p>
|
<p className="text-[11px] text-zinc-500">{BLUEPRINT.downloadNote}</p>
|
||||||
|
|
||||||
<blockquote className="rounded-lg border border-emerald-400/20 bg-emerald-400/5 p-3 text-[11px] leading-relaxed text-emerald-200/80">
|
<blockquote className="rounded-lg border border-emerald-400/20 bg-emerald-400/5 p-3 text-[11px] leading-relaxed text-emerald-700/90 dark:text-emerald-200/80">
|
||||||
The <span className="font-mono">.ava</span> scripts you write here run on both the web edition and the native
|
The <span className="font-mono">.ava</span> scripts you write here run on both the web edition and the native
|
||||||
Rust engine — the script language is identical.
|
Rust engine — the script language is identical.
|
||||||
</blockquote>
|
</blockquote>
|
||||||
|
|
|
||||||
|
|
@ -418,7 +418,7 @@ function NameDialog({
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="h-9 gap-1.5 bg-emerald-400 text-zinc-950 hover:bg-emerald-300"
|
className="h-9 gap-1.5 bg-emerald-400 text-emerald-950 hover:bg-emerald-300"
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
onClick={() => void submit()}
|
onClick={() => void submit()}
|
||||||
>
|
>
|
||||||
|
|
@ -812,7 +812,7 @@ export function PresetsTab() {
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => loadPreset(selectedPreset)}
|
onClick={() => loadPreset(selectedPreset)}
|
||||||
className="h-9 w-full gap-1.5 bg-emerald-400 text-zinc-950 hover:bg-emerald-300"
|
className="h-9 w-full gap-1.5 bg-emerald-400 text-emerald-950 hover:bg-emerald-300"
|
||||||
title="Run this preset on the engine and open it in the editor"
|
title="Run this preset on the engine and open it in the editor"
|
||||||
>
|
>
|
||||||
<Play className="size-3.5" /> Load Preset
|
<Play className="size-3.5" /> Load Preset
|
||||||
|
|
@ -962,7 +962,7 @@ export function PresetsTab() {
|
||||||
checked={randomize}
|
checked={randomize}
|
||||||
onCheckedChange={(v) => useAvaStore.getState().setRandomize(v === true)}
|
onCheckedChange={(v) => useAvaStore.getState().setRandomize(v === true)}
|
||||||
aria-label="Randomize presets"
|
aria-label="Randomize presets"
|
||||||
className="size-4 data-[state=checked]:border-emerald-400 data-[state=checked]:bg-emerald-400 data-[state=checked]:text-zinc-950"
|
className="size-4 data-[state=checked]:border-emerald-400 data-[state=checked]:bg-emerald-400 data-[state=checked]:text-emerald-950"
|
||||||
/>
|
/>
|
||||||
<Label htmlFor="ava-randomize" className="cursor-pointer text-xs text-zinc-300">
|
<Label htmlFor="ava-randomize" className="cursor-pointer text-xs text-zinc-300">
|
||||||
Randomize
|
Randomize
|
||||||
|
|
|
||||||
|
|
@ -278,7 +278,7 @@ export function ScriptTab() {
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={apply}
|
onClick={apply}
|
||||||
className="h-9 gap-1.5 bg-emerald-400 px-3 text-zinc-950 hover:bg-emerald-300"
|
className="h-9 gap-1.5 bg-emerald-400 px-3 text-emerald-950 hover:bg-emerald-300"
|
||||||
title="Validate and apply to the engine"
|
title="Validate and apply to the engine"
|
||||||
>
|
>
|
||||||
<Play className="size-3.5" /> Apply
|
<Play className="size-3.5" /> Apply
|
||||||
|
|
@ -288,7 +288,7 @@ export function ScriptTab() {
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => void saveToTarget()}
|
onClick={() => void saveToTarget()}
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
className="h-9 gap-1.5 bg-emerald-400 px-3 text-zinc-950 hover:bg-emerald-300"
|
className="h-9 gap-1.5 bg-emerald-400 px-3 text-emerald-950 hover:bg-emerald-300"
|
||||||
title={`Overwrite "${editTarget.name}" with the current editor script`}
|
title={`Overwrite "${editTarget.name}" with the current editor script`}
|
||||||
>
|
>
|
||||||
<Save className="size-3.5" /> Save
|
<Save className="size-3.5" /> Save
|
||||||
|
|
@ -440,7 +440,7 @@ export function ScriptTab() {
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="h-9 gap-1.5 bg-emerald-400 text-zinc-950 hover:bg-emerald-300"
|
className="h-9 gap-1.5 bg-emerald-400 text-emerald-950 hover:bg-emerald-300"
|
||||||
disabled={saveAsBusy}
|
disabled={saveAsBusy}
|
||||||
onClick={() => void saveAsCopy()}
|
onClick={() => void saveAsCopy()}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ export function VisualStage({ className }: { className?: string }) {
|
||||||
<div className="grid gap-2.5">
|
<div className="grid gap-2.5">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => void startDemo()}
|
onClick={() => void startDemo()}
|
||||||
className="h-12 gap-2 bg-emerald-400 text-zinc-950 hover:bg-emerald-300"
|
className="h-12 gap-2 bg-emerald-400 text-emerald-950 hover:bg-emerald-300"
|
||||||
>
|
>
|
||||||
<Disc3 className="size-4" /> Demo Track
|
<Disc3 className="size-4" /> Demo Track
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
// (v6.0 = rename to Open Visualization Engine + version indicator).
|
// (v6.0 = rename to Open Visualization Engine + version indicator).
|
||||||
//
|
//
|
||||||
// History:
|
// History:
|
||||||
|
// 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
|
// 6.2 — security hardening: read-only embedded library (server refuses all
|
||||||
// writes) + per-visitor "user created" presets stored in the browser
|
// writes) + per-visitor "user created" presets stored in the browser
|
||||||
// 6.1 — preset library quality pass: every embedded stand-in is now a UNIQUE
|
// 6.1 — preset library quality pass: every embedded stand-in is now a UNIQUE
|
||||||
|
|
@ -17,4 +19,4 @@
|
||||||
|
|
||||||
export const APP_NAME = 'Open Visualization Engine'
|
export const APP_NAME = 'Open Visualization Engine'
|
||||||
export const APP_SHORT = 'OVE'
|
export const APP_SHORT = 'OVE'
|
||||||
export const APP_VERSION = '6.2'
|
export const APP_VERSION = '6.3'
|
||||||
|
|
|
||||||
BIN
tool-results/e2e-v63/01-dark-default.png
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
tool-results/e2e-v63/02-dark-presets.png
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
tool-results/e2e-v63/03-light-after-toggle.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
tool-results/e2e-v63/03b-light-toggle-checked.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
tool-results/e2e-v63/04-light-simulated.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
tool-results/e2e-v63/05-light-presets-tree.png
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
tool-results/e2e-v63/06-light-preset-panel.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
tool-results/e2e-v63/07-light-script.png
Normal file
|
After Width: | Height: | Size: 129 KiB |
BIN
tool-results/e2e-v63/08-light-help.png
Normal file
|
After Width: | Height: | Size: 127 KiB |
BIN
tool-results/e2e-v63/09-light-detect.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
tool-results/e2e-v63/10-light-beat.png
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
tool-results/e2e-v63/11-light-native.png
Normal file
|
After Width: | Height: | Size: 151 KiB |
BIN
tool-results/e2e-v63/11b-light-native-full.png
Normal file
|
After Width: | Height: | Size: 151 KiB |
BIN
tool-results/e2e-v63/12-light-dialog.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
tool-results/e2e-v63/13-light-toast.png
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
tool-results/e2e-v63/13b-light-toast-mid.png
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
tool-results/e2e-v63/14-light-delete-dialog.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
tool-results/e2e-v63/15-light-after-reload.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
tool-results/e2e-v63/16-dark-after-reload.png
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
tool-results/e2e-v63/17-light-REAL-served-css.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
tool-results/e2e-v63/18-real-toggle-to-dark.png
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
tool-results/e2e-v63/19-real-toggle-back-to-light.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
tool-results/e2e-v63/20-real-light-dialog.png
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
tool-results/e2e-v63/21-real-dark-dialog.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
tool-results/e2e-v63/22-real-light-presets.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
tool-results/e2e-v63/23-real-light-script.png
Normal file
|
After Width: | Height: | Size: 128 KiB |
BIN
tool-results/e2e-v63/24-real-light-help.png
Normal file
|
After Width: | Height: | Size: 129 KiB |
BIN
tool-results/e2e-v63/25-dark-script.png
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
tool-results/e2e-v63/26-mobile-light-top.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
tool-results/e2e-v63/27-mobile-light-bottom.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
tool-results/e2e-v63/28-mobile-dark-top.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
tool-results/e2e-v63/29-mobile-dark-bottom.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
tool-results/e2e-v63/30-light-detect-demo-vu.png
Normal file
|
After Width: | Height: | Size: 752 KiB |
BIN
tool-results/e2e-v63/31-light-detect-live.png
Normal file
|
After Width: | Height: | Size: 532 KiB |
BIN
tool-results/e2e-v63/32-light-live-visual.png
Normal file
|
After Width: | Height: | Size: 670 KiB |
BIN
tool-results/e2e-v63/33-real-light-tree-expanded.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
tool-results/e2e-v63/34-final-pristine-dark.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
1033
tool-results/read_1793087510034_3be5e504589d.txt
Normal file
204
worklog.md
|
|
@ -1030,3 +1030,207 @@ Stage Summary:
|
||||||
Settings (band/beat/default script) are per-browser now.
|
Settings (band/beat/default script) are per-browser now.
|
||||||
- /api/presets/export intentionally kept: read-only maintainer backup of the
|
- /api/presets/export intentionally kept: read-only maintainer backup of the
|
||||||
embedded library.
|
embedded library.
|
||||||
|
|
||||||
|
---
|
||||||
|
Task ID: 16-d
|
||||||
|
Agent: general-purpose (E2E verifier)
|
||||||
|
Task: v6.3 theme tick switch — end-to-end verification
|
||||||
|
|
||||||
|
Work Log:
|
||||||
|
- Read worklog Task 15 + 15-11 for context; read v6.3 sources read-only
|
||||||
|
(header.tsx theme switch, globals.css .light remap, version.ts 6.3,
|
||||||
|
help-panel/script-tab patches).
|
||||||
|
- agent-browser desktop session 1440×900 at http://localhost:3000: cleared
|
||||||
|
localStorage, fresh load, dark-default + theme-pill geometry checks,
|
||||||
|
toggle to light, full tab walk (Presets/Script/Detect/Beat/Native +
|
||||||
|
Help panel), embedded preset select + Copy-to-my-presets panel, Create
|
||||||
|
Preset dialog in light AND dark, "Theme Test" create/delete toasts +
|
||||||
|
cleanup, persistence reloads (light→reload, dark→reload), rapid 5×
|
||||||
|
toggling, demo-track run for live VU meters + visualization, mobile
|
||||||
|
390×844 light + dark (scrollWidth, stacking, footer flush, pill
|
||||||
|
tappable), console + page-error checks after every phase.
|
||||||
|
- ENVIRONMENT INCIDENT (diagnosed + documented, no source edits): for the
|
||||||
|
first ~12 min of the session the dev server served a STALE CSS chunk
|
||||||
|
([root-of-the-server]__0f0ba101._.css, emitted 07:50:04, 158,839→
|
||||||
|
158,921 B) that contained the v6.3 `color-scheme: dark` addition but
|
||||||
|
NOT the `.light { --color-* }` block — so toggling set html.light +
|
||||||
|
checked state but the page stayed visually dark. Control test: the
|
||||||
|
current src/app/globals.css compiled through the project's own
|
||||||
|
@tailwindcss/postcss 4.1.18 (+ lightningcss 1.30.2) DOES emit .light —
|
||||||
|
source correct, server module graph stale (final 07:48:50 save never
|
||||||
|
propagated; same delayed-HMR class as Task 15-11 issue 1). A single
|
||||||
|
content-preserving `touch` of globals.css (md5 unchanged before/after:
|
||||||
|
2aa8062c…) triggered Turbopack's delayed rebuild at 08:04:59 → chunk
|
||||||
|
158,941 B now contains .light. All light-theme verdicts below were
|
||||||
|
re-verified against the real served CSS after that point (earlier
|
||||||
|
interim checks used a byte-identical injected copy of the .light block,
|
||||||
|
same results).
|
||||||
|
- VLM (z-ai vision) cross-checks on 13 screenshots (light full page,
|
||||||
|
presets/script/help, dark default/toggled/script, dialog, toast, mobile
|
||||||
|
light/dark, stale-state evidence, live visualization).
|
||||||
|
- Screenshots: tool-results/e2e-v63/ (37 files, 01…34).
|
||||||
|
- Final state: browser closed; localStorage 'theme' key removed (fresh
|
||||||
|
dark default), ove.userPresets.v1 = {"v":1,"folders":[],"presets":[]};
|
||||||
|
server library untouched; scratch test dir removed; source files
|
||||||
|
unmodified (git status = builder's v6.3 changes only).
|
||||||
|
|
||||||
|
Results:
|
||||||
|
- 1: PASS — fresh load (no 'theme' key): html.dark, body bg zinc-950
|
||||||
|
(#09090b), text zinc-100 (#f4f4f5), footer "version 6.3 | engine ok",
|
||||||
|
title "OVE — Open Visualization Engine (v6.3)".
|
||||||
|
- 2: PASS — pill Moon(x1135)|Switch(x1155,w32,unchecked,aria-label
|
||||||
|
"Switch to light theme")|Sun(x1193); BPM number box starts x1230 —
|
||||||
|
pill immediately LEFT of the emerald BPM readout, 6 px gaps, no
|
||||||
|
overlap/shift (VLM-confirmed).
|
||||||
|
- 3: PASS — dark regression clean: tree 95 presets/7 folders, lock badges
|
||||||
|
on embedded folders, script editor #09090b bg + light mono text, Set
|
||||||
|
Beat bg #34d399 + emerald-950 text (7.8:1), no white boxes (VLM:
|
||||||
|
"no regressions").
|
||||||
|
- 4: PASS — click flips switch right (aria-checked=true, knob translates,
|
||||||
|
track #059669 + white knob in light) and the ENTIRE chrome flips white:
|
||||||
|
body #ffffff/#18181b, wrapper zinc-950→white, panels/borders light
|
||||||
|
(before/after: 01→17/18/19). See env incident above for the stale-CSS
|
||||||
|
window at session start.
|
||||||
|
- 5: PASS — light readability across ALL tabs: header (logo, source
|
||||||
|
buttons, demo slider, BPM readout deep green #059669, Set Beat
|
||||||
|
#059669/#022c22, pill); Presets (rows #27272a on white, ≈ badges
|
||||||
|
rose-400 #e11d48 on /10 tint — 60 in Amp Picks, counts 95/66/9/15/5/0,
|
||||||
|
note #71717a, Load Preset #059669 bg, Copy white/#18181b/#d4d4d8);
|
||||||
|
Script (editor white bg, #27272a mono, gutter #8b8b93, status
|
||||||
|
"✔ Compiled OK" #059669); Detect (labels #52525b/#71717a, live VU bars
|
||||||
|
#059669/#e11d48/#b45309 on light tracks with demo running); Beat
|
||||||
|
(SET BEAT #059669/#022c22, labels readable); Native (headings #27272a,
|
||||||
|
body #52525b, .ava token deep green); Help (code blocks #e4e4e7/40 bg +
|
||||||
|
#27272a text — v6.3 patch works); footer readable + "version 6.3";
|
||||||
|
VisualStage canvas stays BLACK (rgb(0,0,0)) with light border #e4e4e7.
|
||||||
|
VLM: no unreadable/low-contrast text, no dark leftovers.
|
||||||
|
- 6: PASS — Create Preset dialog light: card #ffffff, heading/input text
|
||||||
|
#18181b, input bg #fafafa; same dialog dark: card #0a0a0a, input
|
||||||
|
white/4.5% bg + near-white text.
|
||||||
|
- 7: PASS (with notes) — no white-on-white/black-on-black anywhere; body
|
||||||
|
text ≥4.6:1; accents clearly readable. Notes: emerald buttons ≈4.0:1,
|
||||||
|
help-panel emerald-300/90 tokens ≈2.5:1 (see Issues 2-3).
|
||||||
|
- 8: PASS — light toast "Preset created — "Theme Test" created in this
|
||||||
|
browser" (white card via --popover, #18181b text); delete confirm +
|
||||||
|
"Preset deleted" toast same styling; localStorage cleaned afterwards.
|
||||||
|
- 9: PASS — light + reload: html.light, switch checked, theme=light,
|
||||||
|
page renders white (with live CSS); toggle dark + reload: html.dark,
|
||||||
|
unchecked, theme=dark, page dark.
|
||||||
|
- 10: PASS — 5 rapid clicks: end state light, html class/theme key/
|
||||||
|
switch in sync at every step, zero errors, no stuck states.
|
||||||
|
- 11: PASS — console + page errors zero across the whole session (both
|
||||||
|
themes, ~10 loads, dialogs, toasts, toggling, mobile): only React
|
||||||
|
DevTools info, "[HMR] connected", "[Fast Refresh] rebuilding/done".
|
||||||
|
- 12: PASS — mobile 390×844 light: scrollWidth 390 = clientWidth 390
|
||||||
|
(no horizontal scroll), theme pill visible + tappable (wrapped into
|
||||||
|
header row 1, no overlap), tree + panel stack (358 px), footer flush at
|
||||||
|
document bottom (footerBottom 1313 = docHeight 1313; below fold on
|
||||||
|
first paint — same as accepted in Task 15-11).
|
||||||
|
- 13: PASS — mobile dark: same checks pass (no h-scroll, pill visible,
|
||||||
|
bg #09090b/text #f4f4f5).
|
||||||
|
- 14: PASS — dev.log session window (lines 529-722): only GET 200s +
|
||||||
|
prisma:query; zero error/warn/exception lines. (Pre-session lines
|
||||||
|
443-510 contain builder-era transient errors — see Issue 5.)
|
||||||
|
|
||||||
|
Issues found:
|
||||||
|
- 1 (environmental, resolved mid-session, NO app bug): dev server served
|
||||||
|
a stale Turbopack CSS chunk missing the entire .light palette remap for
|
||||||
|
~12 min (07:52-08:05) — theme toggle flipped state (html.light) but the
|
||||||
|
page stayed dark, and the switch track rendered white-on-dark. Root
|
||||||
|
cause: the final 07:48:50 globals.css save never propagated through
|
||||||
|
HMR (delayed/broken watcher state; 4 runtime-error full reloads from
|
||||||
|
the builder's mid-edit saves at 07:44-07:50 left the graph stale).
|
||||||
|
Content-preserving touch + delayed rebuild fixed it at 08:04:59.
|
||||||
|
Control compile of the current source through the project's own
|
||||||
|
toolchain emits .light correctly. Recommendation: RESTART the dev
|
||||||
|
server (or at least verify the served chunk contains .light) before
|
||||||
|
the next session and before any VPS deploy of v6.3 — a stale build
|
||||||
|
would ship a theme switch that visually does nothing.
|
||||||
|
- 2 (minor, light-mode contrast): help-panel inline code tokens
|
||||||
|
(text-emerald-300/90 — e.g. [init], [frame], variable names in the
|
||||||
|
reference tables) resolve to #10b981 on white ≈ 2.5:1 — below WCAG AA
|
||||||
|
4.5:1 and even below 3:1; readable but washed-out. Consider deepening
|
||||||
|
the emerald-300 remap (e.g. to #059669) or switching these tokens to
|
||||||
|
emerald-400 in light mode.
|
||||||
|
- 3 (minor, light-mode contrast): emerald action buttons (header Set
|
||||||
|
Beat, Load Preset, Beat-tab SET BEAT) are text-emerald-950 #022c22 on
|
||||||
|
bg-emerald-400 #059669 ≈ 4.0:1 — just under AA 4.5:1 for small text
|
||||||
|
(passes 3:1 large-text; dark-mode equivalent is 7.8:1). Borderline,
|
||||||
|
likely acceptable for bold button labels.
|
||||||
|
- 4 (observation): script editor line-number gutter (text-zinc-600 →
|
||||||
|
#8b8b93 on white) ≈ 3.4:1 — auxiliary text, acceptable but dimmer
|
||||||
|
than body text.
|
||||||
|
- 5 (observation, pre-existing): dev.log lines 443-510 (before this
|
||||||
|
session) contain 4× "⚠ Fast Refresh had to perform a full reload due
|
||||||
|
to a runtime error" plus transient "⨯ ReferenceError: useRef is not
|
||||||
|
defined at SourceControls (header.tsx:63)" GET / 500s — artifacts of
|
||||||
|
the v6.3 builder's mid-edit saves. Current source imports useRef
|
||||||
|
correctly; nothing recurred in this session.
|
||||||
|
- 6 (observation): on mobile 390 px the theme pill wraps into header row
|
||||||
|
1 (next to the logo) rather than sitting beside the BPM readout as on
|
||||||
|
desktop — clean flex-wrap, no overlap; consistent with the header's
|
||||||
|
responsive design.
|
||||||
|
- 7 (note): "Set Beat button … very dark green text" expectation —
|
||||||
|
confirmed #022c22 (emerald-950) on #059669; and the checklist's "help
|
||||||
|
headings readable green": Help section headings are zinc-400 gray by
|
||||||
|
design (unchanged from dark theme); the GREEN elements in Help are the
|
||||||
|
inline code tokens (see Issue 2).
|
||||||
|
|
||||||
|
---
|
||||||
|
Task ID: 16
|
||||||
|
Agent: main (Z.ai Code)
|
||||||
|
Task: v6.3 — light/dark theme tick switch in the header (user request: switch
|
||||||
|
top-right, left of the BPM indicator; flip right = white theme).
|
||||||
|
|
||||||
|
Work Log:
|
||||||
|
- src/app/globals.css — light theme via palette REMAP under `.light` (the app
|
||||||
|
is styled with raw zinc/emerald/rose/amber utilities, so remapping those CSS
|
||||||
|
variables flips the whole chrome): zinc ramp inverted (950→#ffffff page,
|
||||||
|
100→#18181b text, 900→#fafafa panels, 800→#e4e4e7 hover/borders, 700→#d4d4d8
|
||||||
|
borders, 600→#8b8b93 hints); accents deepened one step for white
|
||||||
|
backgrounds (emerald-400→#059669, emerald-300→#10b981 so hovers still
|
||||||
|
lighten; rose-300/400, amber-300/400 likewise). color-scheme light/dark set
|
||||||
|
on .light/.dark. Unlayered class rules beat Tailwind's @layer theme, so the
|
||||||
|
remap wins with opacity variants too (color-mix keeps var references).
|
||||||
|
- src/app/layout.tsx — ThemeProvider (next-themes, attribute="class",
|
||||||
|
defaultTheme="dark", enableSystem=false, disableTransitionOnChange);
|
||||||
|
hardcoded <html class="dark"> removed (next-themes sets it pre-paint).
|
||||||
|
- src/components/ava/header.tsx — ThemeToggle pill (Moon | Switch | Sun,
|
||||||
|
h-11 matching the other header controls) in the right cluster as the first
|
||||||
|
child — immediately LEFT of the BPM readout. Left/unchecked = dark (default),
|
||||||
|
flipped right/checked = white theme. Mount guard via useSyncExternalStore
|
||||||
|
(lint-clean replacement for the setState-in-effect pattern); switch track:
|
||||||
|
zinc-600 unchecked / emerald-400 checked, default thumb colors verified for
|
||||||
|
all four theme×state combos.
|
||||||
|
- Contrast patches: text-zinc-950 → text-emerald-950 on all emerald CTAs (12
|
||||||
|
spots, 7 files — near-black green on emerald in both themes); help-panel
|
||||||
|
code blocks bg-black/40 → bg-zinc-800/40; help-panel inline tokens
|
||||||
|
text-emerald-300/90 → text-emerald-400/90 (light: #059669/90 ≈ 4.3:1,
|
||||||
|
was 2.5:1); native-tab blockquote text-emerald-200/80 →
|
||||||
|
text-emerald-700/90 dark:text-emerald-200/80.
|
||||||
|
- The visualization canvas intentionally KEEPS its black stage in light mode
|
||||||
|
(the "screen"); only the chrome flips.
|
||||||
|
- src/lib/version.ts → 6.3 (+history line). Fixed a mid-edit useRef import
|
||||||
|
regression (500s during editing, resolved).
|
||||||
|
- Dev-server ops: killed my pkill'd instance, restarted with double-fork
|
||||||
|
detach (( setsid bun run dev & )) — survives across tool sessions; verified
|
||||||
|
the served CSS chunk now contains the .light palette (was a stale Turbopack
|
||||||
|
chunk for ~12 min during verification — subagent diagnosed; restart fixed).
|
||||||
|
- VERIFIED (subagent E2E 16-d, 14/14 PASS + my own post-restart smoke test):
|
||||||
|
dark default (html.dark, no theme key); toggle left of BPM (switch right
|
||||||
|
1187 < BPM left 1230), flips to html.light with white body/dark text; ALL
|
||||||
|
tabs readable in light (header, presets tree + locks + ≈ badges, white
|
||||||
|
script editor, detect VU bars, beat, native, help code blocks, footer);
|
||||||
|
dialogs + toasts themed correctly; Set Beat #059669 bg + #022c22 text;
|
||||||
|
VisualStage stays black with light border; persistence across reload both
|
||||||
|
ways; rapid 5× toggling stable; zero console/page errors; mobile 390×844
|
||||||
|
both themes no horizontal scroll, footer flush at document bottom.
|
||||||
|
- Committed as "v6.3 — light/dark theme tick switch in the header".
|
||||||
|
|
||||||
|
Stage Summary:
|
||||||
|
- Theme = per-visitor preference (localStorage via next-themes), default
|
||||||
|
dark — matches the club aesthetic; light mode is a clean white studio look
|
||||||
|
with deepened emerald/rose/amber accents for contrast.
|
||||||
|
- 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.
|
||||||
|
|
|
||||||