The body font-family referenced "Karla", which was never loaded, so
body text silently fell back to system-ui. Point it at the existing
--font-secondary token (Inter + system fallbacks).
Replace Google Fonts CDN delivery of the four UI typefaces (Inter,
Space Grotesk, Space Mono, Syne) with latin-subset woff2 vendored into
app/assets/fonts and wired via a hand-authored @font-face stylesheet.
Variable faces keep wght (Inter also opsz). Vite content-hashes the
binaries for immutable caching.
The decorative dotted-grid background on the paper surface was a
6-line $derived gridStyle string applied via inline style="" plus four
extra utility classes for color and opacity. Replace with two named
utilities and let CSS handle the responsive switch.
app.css:
- New --color-grid-line CSS var (light + dark) so the grid colour and
intensity auto-switch without consumers needing a dark: variant or an
opacity layer.
- @utility bg-grid (20px cells) and @utility bg-grid-sm (10px cells).
Both reference --color-grid-line, so the same markup paints correctly
in light and dark mode.
SliderArea.svelte:
- Drop the gridStyle $derived block and the inline style= attribute.
- Overlay becomes a single line:
<div class="absolute inset-0 pointer-events-none bg-grid-sm md:bg-grid"
aria-hidden="true" />
Mobile picks the tight 10px grid; the md: breakpoint flips to 20px,
matching the prior JS-driven behaviour with no extra runtime cost.
Dark mode unchanged. Targets that were reported as "barely visible" in
light theme:
Surfaces / dividers
- --color-border-subtle (light) bumped from rgb(0 0 0 / 0.05) to
--neutral-300 (matches the Input underline variant's border color and
yields a visible hairline on bg-surface / bg-paper).
- New bg-subtle utility (same color as border-subtle but as
background-color) — used by Divider component and the TypographyMenu
inline column separator. Replaces ad-hoc 'bg-black/5 dark:bg-white/10'
and 'bg-black/10 dark:bg-white/10' bands.
- FontSearch + ComparisonView Search wrapper borders switched from
hand-written 'border-swiss-black/5 dark:border-white/10' to
border-subtle so they participate in the palette.
Muted text
- Button tertiary inactive text (light) bumped neutral-400 → neutral-600
(~2.7:1 → ~7.5:1 contrast). Covers the A/B toggle and the font-list
rows in the sidebar.
- Label/TechText muted variant (light) bumped neutral-400 → neutral-600.
Covers the ComboControl value text.
- Link text aligned to neutral-500 / neutral-400 (subtle but visible).
No behavior changes; pure styling.
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).