From 4e7f76ecb1a62c24b80eba87c4f0543baa2592c3 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Mon, 25 May 2026 10:19:45 +0300 Subject: [PATCH] feat(styles): add shadow + motion tokens, surface utilities, mode-switching color vars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Establish a real design system foundation by moving the project from inline arbitrary-value classes to named tokens and reusable utilities. Tokens added to @theme (auto-generate Tailwind utilities): - Shadows: shadow-rest, shadow-stamp-{rest,hover,pressed,card}, shadow-popover, shadow-floating-panel{,-dark}, shadow-overlay - Motion: duration-{fast,normal,slow,slower}; ease-{standard,out-soft,spring-overshoot} Semantic mode-switching colors added to :root / .dark so utilities auto-adapt without dark: variants: - --color-border-subtle, --color-text-subtle, --color-skeleton Utilities migrated to Tailwind v4's @utility directive with direct CSS properties (previously @layer utilities with @apply chains, which silently failed when chaining to other user-defined utilities): - border-subtle, text-subtle, focus-ring - surface-canvas, surface-card, surface-card-elevated, surface-popover, surface-floating - flex-center, skeleton-fill, text-label-mono Notes: - text-secondary was renamed to text-subtle because --color-secondary is registered in @theme (a near-white shadcn surface token), which made Tailwind v4 auto-generate a colliding text-secondary utility that won over the user-defined one — every consumer effectively rendered as near-white text. The text-subtle name pairs cleanly with border-subtle and avoids any @theme collisions. - Dead --space-* variable scale removed (was defined but never wired into @theme; Tailwind's default spacing scale is used everywhere). --- src/app/styles/app.css | 155 +++++++++++++++++++++++++++++++++++------ 1 file changed, 133 insertions(+), 22 deletions(-) diff --git a/src/app/styles/app.css b/src/app/styles/app.css index b00d02a..9febc0a 100644 --- a/src/app/styles/app.css +++ b/src/app/styles/app.css @@ -14,6 +14,12 @@ --swiss-black: #1a1a1a; --swiss-white: #ffffff; + /* Semantic mode-switching colors. These are redefined inside `.dark` + so utilities that reference them auto-adapt without a `dark:` variant. */ + --color-border-subtle: rgb(0 0 0 / 0.05); + --color-text-subtle: var(--neutral-500); + --color-skeleton: var(--neutral-200); + /* Neutral Grays */ --neutral-50: #fafafa; --neutral-100: #f5f5f5; @@ -80,16 +86,6 @@ --sidebar-border: oklch(0.922 0 0); --sidebar-ring: oklch(0.708 0 0); - /* Spacing Scale (rem-based) */ - --space-xs: 0.25rem; - --space-sm: 0.5rem; - --space-md: 0.75rem; - --space-lg: 1rem; - --space-xl: 1.5rem; - --space-2xl: 2rem; - --space-3xl: 3rem; - --space-4xl: 4rem; - /* Typography Scale */ --text-xs: 0.75rem; --text-sm: 0.875rem; @@ -114,6 +110,11 @@ --color-surface: var(--dark-bg); --color-paper: var(--dark-card); + /* Dark-mode overrides for the semantic mode-switching colors. */ + --color-border-subtle: rgb(255 255 255 / 0.1); + --color-text-subtle: var(--neutral-400); + --color-skeleton: var(--neutral-800); + --background: oklch(0.145 0 0); --foreground: oklch(0.985 0 0); --card: oklch(0.145 0 0); @@ -212,6 +213,51 @@ --text-2xs: 0.625rem; /* Monospace label tracking — used in Loader and Footnote */ --tracking-wider-mono: 0.2em; + + /* ============================================ + SHADOW TOKENS + ============================================ */ + + /* Default resting shadow — equivalent to Tailwind's shadow-sm. Used on + buttons, sliders, popover triggers in non-floating state. */ + --shadow-rest: 0 1px 2px 0 rgb(0 0 0 / 0.05); + + /* Swiss "hard offset" stamp — rests at 2px/2px, lifts to 3px/3px on + hover, presses back to 1px/1px on active. Primary button motif. */ + --shadow-stamp-rest: 0.125rem 0.125rem 0 0 rgb(0 0 0 / 0.1); + --shadow-stamp-hover: 0.1875rem 0.1875rem 0 0 rgb(0 0 0 / 0.15); + --shadow-stamp-pressed: 0.0625rem 0.0625rem 0 0 rgb(0 0 0 / 0.1); + + /* Card-tier hard-offset stamp — wider, brand-tinted. Used on + interactive cards (FontSampler hover). */ + --shadow-stamp-card: 5px 5px 0 0 var(--color-brand); + + /* Floating popovers (typography menu, combo control list). */ + --shadow-popover: 0 20px 40px -10px rgb(0 0 0 / 0.15); + + /* Drop-shadow under semi-translucent floating panels like the + comparison slider's character row. */ + --shadow-floating-panel: 0 25px 50px -12px rgb(0 0 0 / 0.05); + --shadow-floating-panel-dark: 0 25px 50px -12px rgb(0 0 0 / 0.2); + + /* Drawer / overlay shadow — full-strength shadow-2xl. */ + --shadow-overlay: 0 25px 50px -12px rgb(0 0 0 / 0.25); + + /* ============================================ + MOTION TOKENS + ============================================ */ + + --duration-fast: 150ms; + --duration-normal: 200ms; + --duration-slow: 300ms; + --duration-slower: 500ms; + + /* Tailwind's default ease-in-out — symmetric, good for layout shifts. */ + --ease-standard: cubic-bezier(0.4, 0, 0.2, 1); + /* Decelerating curve — matches Tailwind's ease-out. Dominant in this codebase. */ + --ease-out-soft: cubic-bezier(0, 0, 0.2, 1); + /* Spring overshoot — used in character pop animation. */ + --ease-spring-overshoot: cubic-bezier(0.34, 1.56, 0.64, 1); } @layer base { @@ -277,21 +323,86 @@ } } -@layer utilities { - /* 21× border-black/5 dark:border-white/10 → single token */ - .border-subtle { - @apply border-black/5 dark:border-white/10; - } - /* Secondary text pair */ - .text-secondary { - @apply text-neutral-500 dark:text-neutral-400; - } - /* Standard focus ring */ - .focus-ring { - @apply focus-visible:ring-2 focus-visible:ring-brand focus-visible:ring-offset-2; +/* ============================================ + DESIGN-SYSTEM UTILITIES + ============================================ + Defined via `@utility` (Tailwind v4) so they integrate with the variant + system (`hover:`, `dark:`, breakpoints) and don't rely on `@apply` + chains. Colors reference the mode-switching semantic vars defined in + `:root`/`.dark` above, so most utilities need no `dark:` variant in + their definition or at call sites. */ + +@utility border-subtle { + border-color: var(--color-border-subtle); +} + +/* Muted text color — paired with `border-subtle` naming. The previous + name `text-secondary` collided with Tailwind v4 auto-generating a + utility from `--color-secondary` (the shadcn near-white surface token + registered in `@theme`), which made every consumer effectively + invisible (near-white text on light backgrounds). */ +@utility text-subtle { + color: var(--color-text-subtle); +} + +@utility focus-ring { + &:focus-visible { + outline: 2px solid transparent; + outline-offset: 2px; + box-shadow: 0 0 0 2px var(--color-background, white), 0 0 0 4px var(--color-brand); } } +/* ── Surface utilities ────────────────────────────────────────── */ + +@utility surface-canvas { + background-color: var(--color-surface); +} + +@utility surface-card { + background-color: var(--color-paper); + border: 1px solid var(--color-border-subtle); +} + +@utility surface-card-elevated { + background-color: var(--color-paper); + border: 1px solid var(--color-border-subtle); + box-shadow: var(--shadow-rest); +} + +@utility surface-popover { + background-color: var(--color-paper); + border: 1px solid var(--color-border-subtle); + box-shadow: var(--shadow-popover); +} + +@utility surface-floating { + background-color: color-mix(in srgb, var(--color-surface) 80%, transparent); + backdrop-filter: blur(12px); + border: 1px solid var(--color-border-subtle); +} + +/* ── Shape / layout ───────────────────────────────────────────── */ + +@utility flex-center { + display: flex; + align-items: center; + justify-content: center; +} + +@utility skeleton-fill { + background-color: color-mix(in srgb, var(--color-skeleton) 70%, transparent); +} + +/* ── Typography ───────────────────────────────────────────────── */ + +@utility text-label-mono { + font-family: var(--font-primary); + font-weight: 700; + letter-spacing: -0.025em; + text-transform: uppercase; +} + /* Global utility - useful across your app */ @media (prefers-reduced-motion: reduce) { * {