Modernize the UI and fix the equalizer sizing bug

Bug fix

- EqualizerIcon: the SVG was rendering at the wrong size (filling
  its parent) because it had no explicit width/height attributes.
  Now it accepts a size prop with a 24px default and the SVG element
  carries the size as width/height attributes, with the className
  on top. Can't accidentally balloon.

Design

- Stripped the light-mode CSS — the app is dark-only now, and the
  theme toggle was a no-op (clicking it just toggled a class that
  had no matching rules). ThemeToggle.tsx and lib/theme.ts are
  gone; initTheme() call removed from main.tsx.

- Reworked the input form:
    • brand row is now small + refined (icon + wordmark, no huge
      gradient title)
    • Options is a single subtle collapsible card with a chevron and
      a count badge showing how many options are non-default
    • Vocals is now a segmented pill (single rounded container, two
      flat segments) instead of two full-width grid buttons
    • Generate button is slightly smaller (py-3.5, text-sm) and
      uses a softer shadow (shadow-violet-900/20)

- Empty state in the results panel: dashed-border card, soft
  gradient blur behind the music icon, cleaner copy.

- Results header: smaller heading, the 'Regenerate All' and
  'Cancel' buttons are now tiny ghost-style pills in the top-right.

- Sticky ZIP bar: softer shadow, more refined copy.

- Result cards: smaller icon, single-line title, the Regenerate
  button is now an inline pill that fits next to Revert + Copy.

- Home page header now has a tiny equalizer icon + 'MelodyMuse'
  wordmark on the left (was just text). Settings page header has
  the back arrow on the left and the title, no theme toggle.

- Global CSS: added modern focus-visible ring, custom selection
  color, subtle scrollbar thumb, slightly tweaked the gradient
  text to a softer violet-300 → cyan-300 instead of the loud
  violet-400 → fuchsia-400 → cyan-400.
This commit is contained in:
2026-06-03 10:49:58 +02:00
parent 3096669a13
commit 81aefe0700
12 changed files with 460 additions and 379 deletions
+25 -22
View File
@@ -18,15 +18,15 @@ export type RegeneratingMap = Partial<Record<SectionKey | "all", boolean>>;
interface ResultsPanelProps {
assets: SongAssets | null;
originalAssets: SongAssets | null; // last *generated* values, for Revert
loading: boolean; // initial "Generate all" skeleton
originalAssets: SongAssets | null;
loading: boolean;
selectedTitleIndex: number;
onSelectTitle: (i: number) => void;
onChange: <K extends keyof SongAssets>(key: K, value: SongAssets[K]) => void;
onChangeVideoPrompt: (index: number, value: string) => void;
onRegenerateAll: () => void;
onRegenerateSection: (section: SectionKey) => void;
onCancel?: () => void; // cancel the in-flight generation
onCancel?: () => void;
anyRegenerating: boolean;
regenerating: RegeneratingMap;
}
@@ -49,17 +49,17 @@ export function ResultsPanel({
return (
<div>
<div className="sticky top-0 z-30 -mx-4 sm:-mx-6 lg:-mx-8 px-4 sm:px-6 lg:px-8 py-3 bg-bg/80 backdrop-blur border-b border-border mb-6 flex items-center justify-between gap-3">
<h2 className="text-lg font-semibold text-fg">Results</h2>
<div className="sticky top-14 z-30 -mx-4 sm:-mx-6 lg:-mx-8 px-4 sm:px-6 lg:px-8 py-3 bg-bg/80 backdrop-blur border-b border-border mb-6 flex items-center justify-between gap-3">
<h2 className="text-sm font-semibold text-fg">Results</h2>
<div className="flex items-center gap-1">
{anyRegenerating && onCancel && (
<button
type="button"
onClick={onCancel}
className="btn-ghost"
className="px-2.5 py-1.5 rounded-md text-xs font-medium text-fg-muted hover:text-fg hover:bg-bg-hover transition-colors"
aria-label="Cancel generation"
>
<span className="text-rose-300">Cancel</span>
Cancel
</button>
)}
{assets && (
@@ -67,12 +67,12 @@ export function ResultsPanel({
type="button"
onClick={onRegenerateAll}
disabled={anyRegenerating}
className="btn-ghost"
className="inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-md text-xs font-medium text-fg-muted hover:text-fg hover:bg-bg-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<RefreshCw
className={`w-4 h-4 ${regenerating.all ? "animate-spin" : ""}`}
className={`w-3.5 h-3.5 ${regenerating.all ? "animate-spin" : ""}`}
/>
<span>Regenerate All</span>
Regenerate all
</button>
)}
</div>
@@ -136,25 +136,28 @@ export function ResultsPanel({
function EmptyState() {
return (
<div className="card p-10 text-center">
<div className="mx-auto w-14 h-14 rounded-full gradient-bg-soft flex items-center justify-center mb-4">
<Music className="w-7 h-7 text-violet-300" />
<div className="flex flex-col items-center justify-center text-center px-6 py-20 rounded-2xl border border-dashed border-border">
<div className="relative w-16 h-16 mb-5">
<div className="absolute inset-0 rounded-full bg-gradient-to-br from-violet-500/20 to-cyan-500/20 blur-xl" />
<div className="relative w-full h-full rounded-full bg-bg-card border border-border flex items-center justify-center">
<Music className="w-7 h-7 text-violet-300" />
</div>
</div>
<h3 className="text-base font-semibold text-fg mb-1">
<h3 className="text-base font-semibold text-fg mb-1.5">
Your song will appear here
</h3>
<p className="text-sm text-fg-muted max-w-sm mx-auto">
<p className="text-sm text-fg-muted max-w-sm leading-relaxed">
Describe a music idea on the left and press{" "}
<kbd className="px-1.5 py-0.5 rounded bg-bg-hover border border-border text-[11px] font-mono">
<kbd className="px-1.5 py-0.5 rounded bg-bg-hover border border-border text-[11px] font-mono text-fg">
Generate
</kbd>{" "}
to create titles, lyrics, style, video prompts, and a YouTube
description all in one go.
to get titles, lyrics, style, video prompts, and a YouTube description
in one go.
</p>
<div className="mt-5 flex items-center justify-center gap-2 text-xs text-fg-muted">
<Sparkles className="w-3.5 h-3.5" />
Tip: open <span className="text-fg">Settings</span> to add your API key
first.
<div className="mt-5 flex items-center gap-1.5 text-xs text-fg-muted">
<Sparkles className="w-3 h-3" />
First time? Open <span className="text-fg">Settings</span> to add your
API key.
</div>
</div>
);