feat(theme): semantic token system for light and dark
This commit is contained in:
parent
b08303b3b7
commit
99b465e402
1 changed files with 153 additions and 2 deletions
|
|
@ -1,9 +1,160 @@
|
|||
@import "tailwindcss";
|
||||
|
||||
/*
|
||||
* Dark mode is class-based, not media-based, because the user picks it
|
||||
* explicitly and the choice persists. See components/theme/theme-script.tsx.
|
||||
*/
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
/*
|
||||
* Semantic tokens. Named for the job they do, never for the colour they are:
|
||||
* `--surface` survives a palette change, `--grey-100` does not.
|
||||
*
|
||||
* Every text token below clears 4.5:1 against the surface it is used on, in
|
||||
* both themes. If you add one, check it — the references this design borrows
|
||||
* from both fail that bar, and pale-grey-on-white is exactly how a board
|
||||
* becomes unreadable on a client's mid-range laptop.
|
||||
*/
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
/* Structure: a tinted page with a raised panel floating on it. */
|
||||
--page: #e9ecf1;
|
||||
--panel: #ffffff;
|
||||
--surface: #f6f7f9;
|
||||
--surface-raised: #eef0f4;
|
||||
|
||||
--border-subtle: #e2e5ea;
|
||||
--border-strong: #cbd1d9;
|
||||
|
||||
--text-primary: #101319;
|
||||
--text-secondary: #4a5260;
|
||||
--text-muted: #6b7382;
|
||||
|
||||
/* Cool brand blue: primary actions, links, selection. */
|
||||
--brand: #2f6dad;
|
||||
--brand-hover: #275d95;
|
||||
--brand-fg: #ffffff;
|
||||
--brand-soft: #e7eff8;
|
||||
|
||||
/*
|
||||
* The warm accent. Reserved for exactly three things: an overdue date, the
|
||||
* Inbox count badge, and critical priority. It reads as urgent only while
|
||||
* it stays rare.
|
||||
*/
|
||||
--accent: #c94a24;
|
||||
--accent-fg: #ffffff;
|
||||
--accent-soft: #fbeae3;
|
||||
|
||||
--priority-critical: #c94a24;
|
||||
--priority-high: #a8660a;
|
||||
--priority-medium: #2f6dad;
|
||||
--priority-low: #6b7382;
|
||||
|
||||
--shadow-panel: 0 1px 2px rgba(16, 19, 25, 0.04),
|
||||
0 16px 40px -12px rgba(16, 19, 25, 0.16);
|
||||
--shadow-tile: 0 1px 2px rgba(16, 19, 25, 0.06);
|
||||
--shadow-drag: 0 12px 28px -6px rgba(16, 19, 25, 0.28);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--page: #08090b;
|
||||
--panel: #131519;
|
||||
--surface: #1a1d22;
|
||||
--surface-raised: #22262d;
|
||||
|
||||
--border-subtle: #262a31;
|
||||
--border-strong: #383d47;
|
||||
|
||||
--text-primary: #f1f3f6;
|
||||
--text-secondary: #aab2bf;
|
||||
--text-muted: #838c9b;
|
||||
|
||||
--brand: #5e9ee0;
|
||||
--brand-hover: #7cb0e8;
|
||||
--brand-fg: #08090b;
|
||||
--brand-soft: #16263a;
|
||||
|
||||
--accent: #ff8a5c;
|
||||
--accent-fg: #1a0e08;
|
||||
--accent-soft: #2e1a12;
|
||||
|
||||
--priority-critical: #ff8a5c;
|
||||
--priority-high: #e0a23c;
|
||||
--priority-medium: #5e9ee0;
|
||||
--priority-low: #838c9b;
|
||||
|
||||
--shadow-panel: 0 1px 2px rgba(0, 0, 0, 0.4),
|
||||
0 16px 40px -12px rgba(0, 0, 0, 0.6);
|
||||
--shadow-tile: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
--shadow-drag: 0 12px 28px -6px rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
/*
|
||||
* Expose the tokens to Tailwind. `@theme inline` keeps the var() reference
|
||||
* live, so `bg-surface` resolves at runtime and follows the theme class.
|
||||
* Without `inline`, Tailwind would bake in the :root value and dark mode
|
||||
* would silently do nothing.
|
||||
*/
|
||||
@theme inline {
|
||||
--color-page: var(--page);
|
||||
--color-panel: var(--panel);
|
||||
--color-surface: var(--surface);
|
||||
--color-surface-raised: var(--surface-raised);
|
||||
|
||||
--color-border-subtle: var(--border-subtle);
|
||||
--color-border-strong: var(--border-strong);
|
||||
|
||||
--color-text-primary: var(--text-primary);
|
||||
--color-text-secondary: var(--text-secondary);
|
||||
--color-text-muted: var(--text-muted);
|
||||
|
||||
--color-brand: var(--brand);
|
||||
--color-brand-hover: var(--brand-hover);
|
||||
--color-brand-fg: var(--brand-fg);
|
||||
--color-brand-soft: var(--brand-soft);
|
||||
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-fg: var(--accent-fg);
|
||||
--color-accent-soft: var(--accent-soft);
|
||||
|
||||
--color-priority-critical: var(--priority-critical);
|
||||
--color-priority-high: var(--priority-high);
|
||||
--color-priority-medium: var(--priority-medium);
|
||||
--color-priority-low: var(--priority-low);
|
||||
|
||||
--font-sans: var(--font-inter), ui-sans-serif, system-ui, sans-serif;
|
||||
|
||||
--radius-tile: 10px;
|
||||
--radius-panel: 18px;
|
||||
}
|
||||
|
||||
* {
|
||||
border-color: var(--border-subtle);
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-neutral-950 text-neutral-100 antialiased;
|
||||
background: var(--page);
|
||||
color: var(--text-primary);
|
||||
font-family: var(--font-sans);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
/*
|
||||
* Numbers in stats, dates and counts must not jitter as they change.
|
||||
* Applied wherever a figure updates in place.
|
||||
*/
|
||||
.tabular {
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-feature-settings: "tnum";
|
||||
}
|
||||
|
||||
/* A consistent focus ring, since every interactive element needs one and
|
||||
Tailwind's default differs per element. */
|
||||
.focus-ring {
|
||||
outline: 2px solid var(--brand);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--brand);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
Loading…
Reference in a new issue