Design Prot Merge #1
@@ -774,7 +774,7 @@ Tareas:
|
||||
| ----- | ------ | ------ | -------------------------- |
|
||||
| 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) |
|
||||
| P2 | ⬜ | | |
|
||||
| P2 | ✅ | (pendiente) | `<style>{expr}</style>` no interpola en Svelte (se trata como CSS del componente); solución: `{@html \`<style>${css}</style>\`}` |
|
||||
| P3 | ⬜ | | |
|
||||
| P4 | ⬜ | | |
|
||||
| P5 | ⬜ | | |
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<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%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
|
||||
@@ -1,55 +1,17 @@
|
||||
:root {
|
||||
/* Dynamic tokens: overwritten at runtime from the playing track's hue.
|
||||
These are the fallback values for the default accent (hue 145) so the
|
||||
page never flashes unstyled before hydration. See theme/tokens.ts. */
|
||||
--mf-bg: oklch(0.068 0.008 145);
|
||||
--mf-elevated: oklch(0.11 0.012 145);
|
||||
--mf-panel-bg: oklch(0.09 0.01 145 / 0.28);
|
||||
--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));
|
||||
/* Hue-based tokens (--mf-accent*, --mf-bg/elevated/panel-bg/bar-bg/hairline,
|
||||
--mf-ambient, --mf-hero-bg/spotlight-bg/modal-glow, --mf-logo-grad,
|
||||
--mf-cover-grad) are NOT defined here: they come only from
|
||||
buildThemeTokens() in theme/tokens.ts, inlined as SSR <style> by
|
||||
+layout.svelte and reapplied on every hue change. A duplicate default
|
||||
here could never be overridden by a light-mode selector (see A.2). */
|
||||
--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-c: 0.1;
|
||||
--mf-tile-shade-l: 0.14;
|
||||
--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. */
|
||||
--mf-text: #d9d8dc;
|
||||
--mf-text-2: #8a8a8e;
|
||||
@@ -124,12 +86,6 @@
|
||||
palette. Components that reference these tokens re-theme correctly;
|
||||
components hardcoding literal dark-mode hex colors do not. */
|
||||
: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-2: #4a4a4e;
|
||||
--mf-text-3: #6b6b6f;
|
||||
@@ -159,19 +115,4 @@
|
||||
--mf-tile-shade-l: 0.42;
|
||||
--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-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%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -97,3 +97,9 @@ export function applyThemeTokens(
|
||||
) {
|
||||
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(';');
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import { liked } from '$lib/player/liked.svelte';
|
||||
import { createPlaylistModal } from '$lib/playlists.svelte';
|
||||
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 { trackNavigation } from '$lib/navigation.svelte';
|
||||
import { page } from '$app/state';
|
||||
@@ -32,6 +32,8 @@
|
||||
const isAuthPage = $derived(page.url.pathname === '/login');
|
||||
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;
|
||||
$effect(() => {
|
||||
if (hasTrack && !hadTrack) queuePanel.show();
|
||||
@@ -83,7 +85,10 @@
|
||||
|
||||
<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)}
|
||||
<div class="min-h-screen bg-bg text-fg antialiased">
|
||||
@@ -91,14 +96,8 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div class="relative flex min-h-screen text-fg antialiased">
|
||||
<div
|
||||
class="pointer-events-none fixed inset-0 -z-10 bg-bg"
|
||||
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>
|
||||
<div class="app-backdrop"></div>
|
||||
<div class="app-vignette"></div>
|
||||
|
||||
<Sidebar user={data.user} playlists={data.sidebarPlaylists ?? []} />
|
||||
|
||||
@@ -106,12 +105,7 @@
|
||||
<div class="flex min-w-0 flex-1 flex-col">
|
||||
<MobileHeader user={data.user} accountUrl={data.accountUrl} />
|
||||
<TopBar user={data.user} accountUrl={data.accountUrl} />
|
||||
<main
|
||||
class="flex-1 transition-[padding]"
|
||||
style="padding-bottom:calc(var(--mf-nav-h) + var(--mf-safe-b) + {hasTrack
|
||||
? 'var(--mf-player-h)'
|
||||
: '0px'})"
|
||||
>
|
||||
<main class="flex-1 app-main" data-has-track={hasTrack ? '' : undefined}>
|
||||
{#key page.url.pathname}
|
||||
<div class="animate-fade">{@render children()}</div>
|
||||
{/key}
|
||||
|
||||
@@ -168,6 +168,35 @@
|
||||
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 {
|
||||
svg.lucide {
|
||||
stroke-width: 1.8;
|
||||
|
||||
Reference in New Issue
Block a user