UI overhaul "Living Codex" A: tokens + fonts
- Replace the design tokens in globals.css with the Living Codex system (parchment light · candlelit dark, gilt accent), keeping every existing --app-*/utility name so nothing breaks. Adds surface-2/panel-2/line-strong/ink-soft/faint/accent-deep/ accent-soft/accent-glow/verdigris + --app-accent-hue (one-number reskin) and paper-grain/gilt-rule/smallcaps helpers. - Self-host the type system via @fontsource (offline-safe, no CDN/CSP change): Spectral (serif headings), Hanken Grotesk (UI), Spline Sans Mono (stats). - Kept: dice/crit animations, print rule, reduced-motion, scrollbars. 223 unit + 34 e2e green; no console errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,9 @@
|
||||
"": {
|
||||
"name": "ttrpg-manager",
|
||||
"dependencies": {
|
||||
"@fontsource-variable/hanken-grotesk": "^5.2.8",
|
||||
"@fontsource-variable/spline-sans-mono": "^5.2.8",
|
||||
"@fontsource/spectral": "^5.2.8",
|
||||
"@tanstack/react-router": "^1.95.0",
|
||||
"@tanstack/react-virtual": "^3.14.2",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -345,6 +348,12 @@
|
||||
|
||||
"@fastify/websocket": ["@fastify/websocket@11.2.0", "", { "dependencies": { "duplexify": "^4.1.3", "fastify-plugin": "^5.0.0", "ws": "^8.16.0" } }, "sha512-3HrDPbAG1CzUCqnslgJxppvzaAZffieOVbLp1DAy1huCSynUWPifSvfdEDUR8HlJLp3sp1A36uOM2tJogADS8w=="],
|
||||
|
||||
"@fontsource-variable/hanken-grotesk": ["@fontsource-variable/hanken-grotesk@5.2.8", "", {}, "sha512-K7hu09ZKReBc7EifRLeM4K94NZBrxFEg0nGcISmVwlFQOJo4EmYkLXTWEwr5JcNW4z2hr5zy8vLS2sxC8JDmrQ=="],
|
||||
|
||||
"@fontsource-variable/spline-sans-mono": ["@fontsource-variable/spline-sans-mono@5.2.8", "", {}, "sha512-Z1yYiEtP+8Gll5BJaR9dICK/ZTxoTWH+ZBH6rOTMWajPSOxwl7IZplkwX2J1QhMxqulhhdMrkRx6SkkLM+jLQw=="],
|
||||
|
||||
"@fontsource/spectral": ["@fontsource/spectral@5.2.8", "", {}, "sha512-0CqZ8/z8A38BdeSz5t2NSd5tCO1R9P1k7SF2VpRLMbZTMSSR6+2Dk0ZBMfEQK/IrBLQS1HgHLMqpdTwRpnKpGQ=="],
|
||||
|
||||
"@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="],
|
||||
|
||||
"@humanfs/node": ["@humanfs/node@0.16.8", "", { "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="],
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
"data:build": "tsx scripts/build-srd.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/hanken-grotesk": "^5.2.8",
|
||||
"@fontsource-variable/spline-sans-mono": "^5.2.8",
|
||||
"@fontsource/spectral": "^5.2.8",
|
||||
"@tanstack/react-router": "^1.95.0",
|
||||
"@tanstack/react-virtual": "^3.14.2",
|
||||
"clsx": "^2.1.1",
|
||||
|
||||
@@ -3,6 +3,15 @@ import { createRoot } from 'react-dom/client';
|
||||
import { RouterProvider } from '@tanstack/react-router';
|
||||
import { router } from './router';
|
||||
import { useUiStore, applyTheme } from './stores/uiStore';
|
||||
// "The Living Codex" type system (self-hosted, offline-safe): Spectral (editorial
|
||||
// serif headings), Hanken Grotesk (UI), Spline Sans Mono (stats/dice).
|
||||
import '@fontsource/spectral/400.css';
|
||||
import '@fontsource/spectral/500.css';
|
||||
import '@fontsource/spectral/600.css';
|
||||
import '@fontsource/spectral/700.css';
|
||||
import '@fontsource/spectral/400-italic.css';
|
||||
import '@fontsource-variable/hanken-grotesk/index.css';
|
||||
import '@fontsource-variable/spline-sans-mono/index.css';
|
||||
import './styles/globals.css';
|
||||
|
||||
// Apply persisted theme before first paint.
|
||||
|
||||
+100
-30
@@ -3,56 +3,106 @@
|
||||
/* Class-based dark mode: toggling `.dark` on <html> swaps the token values below. */
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
/* ---- Semantic design tokens (light defaults) ---- */
|
||||
/* ============================================================================
|
||||
THE LIVING CODEX — design tokens. Parchment + gilt (light) · candlelit (dark).
|
||||
Every --app-* name components already use is kept; values are swapped and a few
|
||||
new tokens added. Retheme the entire accent by changing --app-accent-hue
|
||||
(73 gilt · 38 ember · 12 oxblood · 285 arcane · 178 verdigris · 150 forest).
|
||||
============================================================================ */
|
||||
|
||||
/* ---- Light: aged parchment ---- */
|
||||
:root {
|
||||
--app-surface: #f5f2ec; /* page background */
|
||||
--app-panel: #fffdf9; /* cards / panels */
|
||||
--app-elevated: #ece6da; /* raised controls */
|
||||
--app-line: #d8cfbe; /* borders */
|
||||
--app-ink: #232018; /* primary text */
|
||||
--app-muted: #6b6457; /* secondary text */
|
||||
--app-accent: #9a6b1f; /* brand (deep gold) */
|
||||
--app-accent-soft: #c79a4a;
|
||||
--app-accent-ink: #fffdf9; /* text on accent */
|
||||
--app-danger: #b3261e;
|
||||
--app-success: #2e7d32;
|
||||
--app-warning: #b06f00;
|
||||
--app-info: #1565c0;
|
||||
--app-surface: oklch(0.945 0.018 84);
|
||||
--app-surface-2: oklch(0.915 0.020 82); /* sunken wells */
|
||||
--app-panel: oklch(0.972 0.013 88);
|
||||
--app-panel-2: oklch(0.955 0.015 86);
|
||||
--app-elevated: oklch(0.900 0.022 80);
|
||||
--app-line: oklch(0.855 0.025 78);
|
||||
--app-line-strong:oklch(0.760 0.030 76);
|
||||
--app-ink: oklch(0.280 0.022 64);
|
||||
--app-ink-soft: oklch(0.400 0.020 66); /* body copy */
|
||||
--app-muted: oklch(0.520 0.020 70);
|
||||
--app-faint: oklch(0.620 0.018 74); /* tertiary */
|
||||
|
||||
--app-accent: oklch(0.585 0.098 var(--app-accent-hue));
|
||||
--app-accent-deep: oklch(0.500 0.090 calc(var(--app-accent-hue) - 5));
|
||||
--app-accent-soft: oklch(0.680 0.110 calc(var(--app-accent-hue) + 5));
|
||||
--app-accent-ink: oklch(0.985 0.010 88);
|
||||
--app-accent-glow: oklch(0.585 0.098 var(--app-accent-hue) / 0.16);
|
||||
--app-accent-hue: 73; /* change this ONE number to retheme the accent */
|
||||
|
||||
--app-verdigris: oklch(0.540 0.058 178);
|
||||
--app-danger: oklch(0.520 0.150 32);
|
||||
--app-danger-glow:oklch(0.520 0.150 32 / 0.16);
|
||||
--app-success: oklch(0.560 0.085 150);
|
||||
--app-warning: oklch(0.560 0.110 70);
|
||||
--app-info: oklch(0.500 0.095 285);
|
||||
|
||||
--app-paper-mul: 1; /* set 0 to disable the paper grain */
|
||||
--paper-url: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
|
||||
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
/* ---- Dark: candlelit ink ---- */
|
||||
.dark {
|
||||
--app-surface: #0f0f17;
|
||||
--app-panel: #181826;
|
||||
--app-elevated: #21212f;
|
||||
--app-line: #2e2e40;
|
||||
--app-ink: #e9e7ef;
|
||||
--app-muted: #9a98ab;
|
||||
--app-accent: #d4af37; /* gold */
|
||||
--app-accent-soft: #b08d2e;
|
||||
--app-accent-ink: #15151f;
|
||||
--app-danger: #ef5350;
|
||||
--app-success: #66bb6a;
|
||||
--app-warning: #ffb74d;
|
||||
--app-info: #64b5f6;
|
||||
--app-surface: oklch(0.185 0.012 66);
|
||||
--app-surface-2: oklch(0.150 0.012 66);
|
||||
--app-panel: oklch(0.225 0.014 64);
|
||||
--app-panel-2: oklch(0.255 0.015 62);
|
||||
--app-elevated: oklch(0.290 0.016 60);
|
||||
--app-line: oklch(0.330 0.018 58);
|
||||
--app-line-strong:oklch(0.420 0.022 56);
|
||||
--app-ink: oklch(0.925 0.016 82);
|
||||
--app-ink-soft: oklch(0.820 0.016 80);
|
||||
--app-muted: oklch(0.660 0.018 76);
|
||||
--app-faint: oklch(0.520 0.018 72);
|
||||
|
||||
--app-accent: oklch(0.795 0.115 var(--app-accent-hue));
|
||||
--app-accent-deep: oklch(0.700 0.105 calc(var(--app-accent-hue) - 4));
|
||||
--app-accent-soft: oklch(0.860 0.120 calc(var(--app-accent-hue) + 3));
|
||||
--app-accent-ink: oklch(0.180 0.020 60);
|
||||
--app-accent-glow: oklch(0.795 0.115 var(--app-accent-hue) / 0.18);
|
||||
--app-accent-hue: 82;
|
||||
|
||||
--app-verdigris: oklch(0.700 0.070 178);
|
||||
--app-danger: oklch(0.640 0.155 36);
|
||||
--app-danger-glow:oklch(0.640 0.155 36 / 0.20);
|
||||
--app-success: oklch(0.700 0.095 152);
|
||||
--app-warning: oklch(0.780 0.120 75);
|
||||
--app-info: oklch(0.700 0.105 288);
|
||||
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
/* Expose tokens as Tailwind color utilities: bg-surface, text-ink, border-line, etc. */
|
||||
/* Expose tokens as Tailwind utilities: bg-surface, text-faint, border-line-strong, bg-accent-glow… */
|
||||
@theme inline {
|
||||
--color-surface: var(--app-surface);
|
||||
--color-surface-2: var(--app-surface-2);
|
||||
--color-panel: var(--app-panel);
|
||||
--color-panel-2: var(--app-panel-2);
|
||||
--color-elevated: var(--app-elevated);
|
||||
--color-line: var(--app-line);
|
||||
--color-line-strong: var(--app-line-strong);
|
||||
--color-ink: var(--app-ink);
|
||||
--color-ink-soft: var(--app-ink-soft);
|
||||
--color-muted: var(--app-muted);
|
||||
--color-faint: var(--app-faint);
|
||||
--color-accent: var(--app-accent);
|
||||
--color-accent-deep: var(--app-accent-deep);
|
||||
--color-accent-soft: var(--app-accent-soft);
|
||||
--color-accent-ink: var(--app-accent-ink);
|
||||
--color-accent-glow: var(--app-accent-glow);
|
||||
--color-verdigris: var(--app-verdigris);
|
||||
--color-danger: var(--app-danger);
|
||||
--color-danger-glow: var(--app-danger-glow);
|
||||
--color-success: var(--app-success);
|
||||
--color-warning: var(--app-warning);
|
||||
--color-info: var(--app-info);
|
||||
|
||||
--font-display: 'Iowan Old Style', 'Palatino Linotype', 'Book Antiqua', Georgia, serif;
|
||||
--font-display: 'Spectral', 'Iowan Old Style', 'Palatino Linotype', Georgia, serif;
|
||||
--font-sans: 'Hanken Grotesk Variable', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
||||
--font-mono: 'Spline Sans Mono Variable', ui-monospace, 'SF Mono', Menlo, monospace;
|
||||
}
|
||||
|
||||
html,
|
||||
@@ -65,10 +115,30 @@ body {
|
||||
margin: 0;
|
||||
background: var(--app-surface);
|
||||
color: var(--app-ink);
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
||||
font-family: var(--font-sans);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
/* ---- Pressed-paper grain. Put class="paper-grain" on any positioned panel. ---- */
|
||||
.paper-grain { position: relative; }
|
||||
.paper-grain::before {
|
||||
content: ''; position: absolute; inset: 0; z-index: 0; pointer-events: none;
|
||||
background-image: var(--paper-url);
|
||||
background-size: 240px 240px;
|
||||
opacity: calc(0.30 * var(--app-paper-mul));
|
||||
}
|
||||
.dark .paper-grain::before { opacity: calc(0.42 * var(--app-paper-mul)); }
|
||||
.paper-grain > * { position: relative; z-index: 1; }
|
||||
|
||||
/* ---- Gilt hairline rule ---- */
|
||||
.gilt-rule { height: 1px; border: 0; opacity: .5;
|
||||
background: linear-gradient(90deg, transparent, var(--app-accent) 18%, var(--app-accent) 82%, transparent); }
|
||||
|
||||
/* ---- Small-caps section label ---- */
|
||||
.smallcaps { font-family: var(--font-sans); text-transform: uppercase;
|
||||
letter-spacing: .16em; font-size: 11px; font-weight: 600; color: var(--app-muted); }
|
||||
|
||||
/* Reduced-motion friendliness */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
|
||||
Reference in New Issue
Block a user