feat(styles): add shadow + motion tokens, surface utilities, mode-switching color vars
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).
This commit is contained in:
+133
-22
@@ -14,6 +14,12 @@
|
|||||||
--swiss-black: #1a1a1a;
|
--swiss-black: #1a1a1a;
|
||||||
--swiss-white: #ffffff;
|
--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 Grays */
|
||||||
--neutral-50: #fafafa;
|
--neutral-50: #fafafa;
|
||||||
--neutral-100: #f5f5f5;
|
--neutral-100: #f5f5f5;
|
||||||
@@ -80,16 +86,6 @@
|
|||||||
--sidebar-border: oklch(0.922 0 0);
|
--sidebar-border: oklch(0.922 0 0);
|
||||||
--sidebar-ring: oklch(0.708 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 */
|
/* Typography Scale */
|
||||||
--text-xs: 0.75rem;
|
--text-xs: 0.75rem;
|
||||||
--text-sm: 0.875rem;
|
--text-sm: 0.875rem;
|
||||||
@@ -114,6 +110,11 @@
|
|||||||
--color-surface: var(--dark-bg);
|
--color-surface: var(--dark-bg);
|
||||||
--color-paper: var(--dark-card);
|
--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);
|
--background: oklch(0.145 0 0);
|
||||||
--foreground: oklch(0.985 0 0);
|
--foreground: oklch(0.985 0 0);
|
||||||
--card: oklch(0.145 0 0);
|
--card: oklch(0.145 0 0);
|
||||||
@@ -212,6 +213,51 @@
|
|||||||
--text-2xs: 0.625rem;
|
--text-2xs: 0.625rem;
|
||||||
/* Monospace label tracking — used in Loader and Footnote */
|
/* Monospace label tracking — used in Loader and Footnote */
|
||||||
--tracking-wider-mono: 0.2em;
|
--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 {
|
@layer base {
|
||||||
@@ -277,21 +323,86 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
/* ============================================
|
||||||
/* 21× border-black/5 dark:border-white/10 → single token */
|
DESIGN-SYSTEM UTILITIES
|
||||||
.border-subtle {
|
============================================
|
||||||
@apply border-black/5 dark:border-white/10;
|
Defined via `@utility` (Tailwind v4) so they integrate with the variant
|
||||||
}
|
system (`hover:`, `dark:`, breakpoints) and don't rely on `@apply`
|
||||||
/* Secondary text pair */
|
chains. Colors reference the mode-switching semantic vars defined in
|
||||||
.text-secondary {
|
`:root`/`.dark` above, so most utilities need no `dark:` variant in
|
||||||
@apply text-neutral-500 dark:text-neutral-400;
|
their definition or at call sites. */
|
||||||
}
|
|
||||||
/* Standard focus ring */
|
@utility border-subtle {
|
||||||
.focus-ring {
|
border-color: var(--color-border-subtle);
|
||||||
@apply focus-visible:ring-2 focus-visible:ring-brand focus-visible:ring-offset-2;
|
}
|
||||||
|
|
||||||
|
/* 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 */
|
/* Global utility - useful across your app */
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
* {
|
* {
|
||||||
|
|||||||
Reference in New Issue
Block a user