Design Prot Merge #1

Merged
NotS1mp merged 217 commits from design-prot into master 2026-09-21 15:48:16 +00:00
6 changed files with 60 additions and 83 deletions
Showing only changes of commit 7b1b81c45a - Show all commits
+1 -1
View File
@@ -774,7 +774,7 @@ Tareas:
| ----- | ------ | ------ | -------------------------- | | ----- | ------ | ------ | -------------------------- |
| P0 | ✅ | (pendiente) | Button primary/secondary se mantienen iguales (decisión del usuario) | | P0 | ✅ | (pendiente) | Button primary/secondary se mantienen iguales (decisión del usuario) |
| P1 | ✅ | (pendiente) | Verificado con `build` (Tailwind compila `@apply sm:` dentro de `@utility`); sin QA visual autenticada (Docker no disponible en esta sesión) | | P1 | ✅ | (pendiente) | Verificado con `build` (Tailwind compila `@apply sm:` dentro de `@utility`); sin QA visual autenticada (Docker no disponible en esta sesión) |
| P2 | ⬜ | | | | P2 | ✅ | (pendiente) | `<style>{expr}</style>` no interpola en Svelte (se trata como CSS del componente); solución: `{@html \`<style>${css}</style>\`}` |
| P3 | ⬜ | | | | P3 | ⬜ | | |
| P4 | ⬜ | | | | P4 | ⬜ | | |
| P5 | ⬜ | | | | P5 | ⬜ | | |
+7
View File
@@ -3,6 +3,13 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
try {
if (localStorage.getItem('musify.theme') === 'light') {
document.documentElement.dataset.theme = 'light';
}
} catch {}
</script>
%sveltekit.head% %sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover">
+7 -66
View File
@@ -1,55 +1,17 @@
:root { :root {
/* Dynamic tokens: overwritten at runtime from the playing track's hue. /* Hue-based tokens (--mf-accent*, --mf-bg/elevated/panel-bg/bar-bg/hairline,
These are the fallback values for the default accent (hue 145) so the --mf-ambient, --mf-hero-bg/spotlight-bg/modal-glow, --mf-logo-grad,
page never flashes unstyled before hydration. See theme/tokens.ts. */ --mf-cover-grad) are NOT defined here: they come only from
--mf-bg: oklch(0.068 0.008 145); buildThemeTokens() in theme/tokens.ts, inlined as SSR <style> by
--mf-elevated: oklch(0.11 0.012 145); +layout.svelte and reapplied on every hue change. A duplicate default
--mf-panel-bg: oklch(0.09 0.01 145 / 0.28); here could never be overridden by a light-mode selector (see A.2). */
--mf-bar-bg: oklch(0.105 0.012 145 / 0.9);
--mf-hairline: oklch(0.62 0.03 145 / 0.08);
--mf-ambient:
radial-gradient(52% 38% at 14% 0%, oklch(0.2611 0.0913 145 / 0.26), transparent 54%),
radial-gradient(46% 34% at 78% 0%, oklch(0.2347 0.085 145 / 0.2), transparent 56%);
--mf-hero-bg: linear-gradient(
105deg,
oklch(0.2295 0.0838 145 / 0.34) 0%,
oklch(0.1558 0.0663 145 / 0.11) 37%,
oklch(0.1084 0.055 145 / 0.03) 65%,
rgba(6, 6, 9, 0) 92%
);
--mf-spotlight-bg: linear-gradient(
100deg,
oklch(0.2189 0.0813 145 / 0.28) 0%,
oklch(0.1453 0.0638 145 / 0.1) 40%,
oklch(0.1084 0.055 145 / 0.03) 69%,
rgba(6, 6, 9, 0) 96%
);
--mf-modal-glow: linear-gradient(
135deg,
oklch(0.2295 0.0838 145 / 0.34) 0%,
oklch(0.1558 0.0663 145 / 0.11) 28%,
oklch(0.1084 0.055 145 / 0.03) 50%,
rgba(6, 6, 9, 0) 70%
);
--mf-logo-grad: linear-gradient(140deg, oklch(0.78 0.15 145), oklch(0.62 0.13 185) 92%);
--mf-cover-grad: linear-gradient(150deg, oklch(0.5 0.13 145), oklch(0.16 0.035 145));
--mf-liked-grad: linear-gradient(150deg, oklch(0.4 0.09 330), oklch(0.18 0.04 10)); --mf-liked-grad: linear-gradient(150deg, oklch(0.4 0.09 330), oklch(0.18 0.04 10));
--mf-accent-dim: color-mix(in oklch, var(--mf-accent) 22%, transparent);
--mf-tile-l: 0.34; --mf-tile-l: 0.34;
--mf-tile-c: 0.1; --mf-tile-c: 0.1;
--mf-tile-shade-l: 0.14; --mf-tile-shade-l: 0.14;
--mf-tile-shade-c: 0.035; --mf-tile-shade-c: 0.035;
--mf-accent: oklch(0.74 0.145 145);
--mf-accent-title: oklch(0.87 0.07 145);
--mf-accent-muted: oklch(0.79 0.035 145);
--mf-accent-soft-bg: oklch(0.74 0.145 145 / 0.08);
--mf-accent-line: oklch(0.74 0.145 145 / 0.2);
--mf-accent-hair: oklch(0.74 0.145 145 / 0.09);
--mf-accent-btn-bg: oklch(0.74 0.145 145 / 0.15);
--mf-accent-btn-bg-hover: oklch(0.74 0.145 145 / 0.24);
--mf-accent-glow: color-mix(in oklch, var(--mf-accent) 45%, transparent);
--mf-accent-dim: color-mix(in oklch, var(--mf-accent) 22%, transparent);
/* Fixed neutrals: deliberately hue-less, so only the accent carries color. */ /* Fixed neutrals: deliberately hue-less, so only the accent carries color. */
--mf-text: #d9d8dc; --mf-text: #d9d8dc;
--mf-text-2: #8a8a8e; --mf-text-2: #8a8a8e;
@@ -124,12 +86,6 @@
palette. Components that reference these tokens re-theme correctly; palette. Components that reference these tokens re-theme correctly;
components hardcoding literal dark-mode hex colors do not. */ components hardcoding literal dark-mode hex colors do not. */
:root[data-theme='light'] { :root[data-theme='light'] {
--mf-bg: oklch(0.97 0.006 90);
--mf-elevated: oklch(0.99 0.003 90);
--mf-panel-bg: oklch(0.97 0.006 90 / 0.7);
--mf-bar-bg: oklch(0.97 0.006 90 / 0.9);
--mf-hairline: rgba(0, 0, 0, 0.08);
--mf-text: #17171a; --mf-text: #17171a;
--mf-text-2: #4a4a4e; --mf-text-2: #4a4a4e;
--mf-text-3: #6b6b6f; --mf-text-3: #6b6b6f;
@@ -159,19 +115,4 @@
--mf-tile-shade-l: 0.42; --mf-tile-shade-l: 0.42;
--mf-tile-shade-c: 0.05; --mf-tile-shade-c: 0.05;
--mf-liked-grad: linear-gradient(150deg, oklch(0.62 0.12 330), oklch(0.46 0.08 10)); --mf-liked-grad: linear-gradient(150deg, oklch(0.62 0.12 330), oklch(0.46 0.08 10));
--mf-ambient: none;
--mf-hero-bg: linear-gradient(oklch(0.789 0.0926 145 / 0.16), oklch(0.789 0.0926 145 / 0.16));
--mf-spotlight-bg: linear-gradient(
oklch(0.789 0.0926 145 / 0.16),
oklch(0.789 0.0926 145 / 0.16)
);
--mf-modal-glow: linear-gradient(
135deg,
oklch(0.7531 0.1172 145 / 0.34) 0%,
oklch(0.789 0.0926 145 / 0.2) 30%,
oklch(0.8303 0.0641 145 / 0.1) 55%,
oklch(0.8717 0.0357 145 / 0.04) 80%,
oklch(0.88 0.03 145 / 0.02) 100%
);
} }
+6
View File
@@ -97,3 +97,9 @@ export function applyThemeTokens(
) { ) {
for (const [key, value] of Object.entries(tokens)) target.style.setProperty(key, value); for (const [key, value] of Object.entries(tokens)) target.style.setProperty(key, value);
} }
export function tokensToCss(tokens: ThemeTokens): string {
return Object.entries(tokens)
.map(([key, value]) => `${key}:${value}`)
.join(';');
}
+10 -16
View File
@@ -16,7 +16,7 @@
import { liked } from '$lib/player/liked.svelte'; import { liked } from '$lib/player/liked.svelte';
import { createPlaylistModal } from '$lib/playlists.svelte'; import { createPlaylistModal } from '$lib/playlists.svelte';
import { ACCENT_HUE } from '$lib/theme/color'; import { ACCENT_HUE } from '$lib/theme/color';
import { buildThemeTokens, applyThemeTokens } from '$lib/theme/tokens'; import { buildThemeTokens, applyThemeTokens, tokensToCss } from '$lib/theme/tokens';
import { themeMode } from '$lib/theme/mode.svelte'; import { themeMode } from '$lib/theme/mode.svelte';
import { trackNavigation } from '$lib/navigation.svelte'; import { trackNavigation } from '$lib/navigation.svelte';
import { page } from '$app/state'; import { page } from '$app/state';
@@ -32,6 +32,8 @@
const isAuthPage = $derived(page.url.pathname === '/login'); const isAuthPage = $derived(page.url.pathname === '/login');
const hasTrack = $derived(player.currentId !== null); const hasTrack = $derived(player.currentId !== null);
const initialThemeCss = `:root{${tokensToCss(buildThemeTokens(ACCENT_HUE, 'dark'))}}:root[data-theme='light']{${tokensToCss(buildThemeTokens(ACCENT_HUE, 'light'))}}`;
let hadTrack = false; let hadTrack = false;
$effect(() => { $effect(() => {
if (hasTrack && !hadTrack) queuePanel.show(); if (hasTrack && !hadTrack) queuePanel.show();
@@ -83,7 +85,10 @@
<svelte:window onkeydown={onWindowKeydown} /> <svelte:window onkeydown={onWindowKeydown} />
<svelte:head><link rel="icon" href={favicon} /></svelte:head> <svelte:head>
<link rel="icon" href={favicon} />
{@html `<style>${initialThemeCss}</style>`}
</svelte:head>
{#if isAuthPage || (!data.user && !data.allowAnonymousListening)} {#if isAuthPage || (!data.user && !data.allowAnonymousListening)}
<div class="min-h-screen bg-bg text-fg antialiased"> <div class="min-h-screen bg-bg text-fg antialiased">
@@ -91,14 +96,8 @@
</div> </div>
{:else} {:else}
<div class="relative flex min-h-screen text-fg antialiased"> <div class="relative flex min-h-screen text-fg antialiased">
<div <div class="app-backdrop"></div>
class="pointer-events-none fixed inset-0 -z-10 bg-bg" <div class="app-vignette"></div>
style="background-image:var(--mf-ambient)"
></div>
<div
class="pointer-events-none fixed inset-0 -z-10"
style="background:radial-gradient(130% 85% at 50% 118%, color-mix(in oklch, var(--mf-scrim) 80%, transparent), transparent 62%)"
></div>
<Sidebar user={data.user} playlists={data.sidebarPlaylists ?? []} /> <Sidebar user={data.user} playlists={data.sidebarPlaylists ?? []} />
@@ -106,12 +105,7 @@
<div class="flex min-w-0 flex-1 flex-col"> <div class="flex min-w-0 flex-1 flex-col">
<MobileHeader user={data.user} accountUrl={data.accountUrl} /> <MobileHeader user={data.user} accountUrl={data.accountUrl} />
<TopBar user={data.user} accountUrl={data.accountUrl} /> <TopBar user={data.user} accountUrl={data.accountUrl} />
<main <main class="flex-1 app-main" data-has-track={hasTrack ? '' : undefined}>
class="flex-1 transition-[padding]"
style="padding-bottom:calc(var(--mf-nav-h) + var(--mf-safe-b) + {hasTrack
? 'var(--mf-player-h)'
: '0px'})"
>
{#key page.url.pathname} {#key page.url.pathname}
<div class="animate-fade">{@render children()}</div> <div class="animate-fade">{@render children()}</div>
{/key} {/key}
+29
View File
@@ -168,6 +168,35 @@
grid-template-columns: repeat(auto-fill, minmax(16.5rem, 1fr)); grid-template-columns: repeat(auto-fill, minmax(16.5rem, 1fr));
} }
@utility app-backdrop {
pointer-events: none;
position: fixed;
inset: 0;
z-index: -10;
background-color: var(--mf-bg);
background-image: var(--mf-ambient);
}
@utility app-vignette {
pointer-events: none;
position: fixed;
inset: 0;
z-index: -10;
background: radial-gradient(
130% 85% at 50% 118%,
color-mix(in oklch, var(--mf-scrim) 80%, transparent),
transparent 62%
);
}
@utility app-main {
--mf-dock-h: 0px;
padding-bottom: calc(var(--mf-nav-h) + var(--mf-safe-b) + var(--mf-dock-h));
transition: padding 0.3s;
&[data-has-track] {
--mf-dock-h: var(--mf-player-h);
}
}
@layer base { @layer base {
svg.lucide { svg.lucide {
stroke-width: 1.8; stroke-width: 1.8;