🎨 feat(web/default): add Anthropic theme preset and configurable serif typography
Introduce a switchable Anthropic-inspired color preset and a new Font customization axis so users can adopt the editorial serif look across the entire UI, including sidebar navigation, tabs, form controls, buttons, and table headers. Theme preset Add anthropic to the theme preset registry with warm cream canvas, slate foreground, and clay/coral accent tokens for light and dark modes Define explicit surface colors for the Anthropic preset instead of relying on the semantic surface bridge Exclude anthropic from the primary-color surface bridge so bespoke warm neutrals are not overridden by accent-tinted mixes Typography system Add @fontsource-variable/lora and a global --font-serif token with CJK serif fallbacks (Noto Serif SC, Source Han Serif, Songti SC, etc.) Introduce a --font-body token and drive <body> font-family from it Add a Font axis (default | sans | serif) parallel to radius/scale Resolve font: 'default' against preset defaults (anthropic → serif) Persist font preference via cookie and apply data-theme-font on <body> Apply serif OpenType features (kern, liga, calt, tnum) and heading display tuning when serif is active Remove per-component sans opt-outs so serif inherits through sidebar, tabs, inputs, buttons, badges, and table headers via natural CSS cascade Keep monospace contexts unchanged via Tailwind preflight and .font-mono UI and i18n Add Font selector to the theme config drawer (Auto / Sans / Serif) Add "Font" and "Select body font" translations for en, zh, fr, ja, ru, vi Misc Tighten group and status badge sizing for better balance with serif text
This commit is contained in:
+225
-3
@@ -294,8 +294,20 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
/* ── Semantic surface bridge ──────────────────────────────────────────── */
|
||||
/* Color presets should tint the surfaces most components actually use, not
|
||||
* only primary buttons. These derived tokens keep the app theme-aware without
|
||||
* duplicating per-component dark-mode overrides. */
|
||||
[data-theme-preset]:not([data-theme-preset='default']) {
|
||||
* duplicating per-component dark-mode overrides.
|
||||
*
|
||||
* NOTE: `:not()` contributes its argument's specificity, so this selector
|
||||
* resolves to (0,2,0). Presets that define bespoke surfaces below need to
|
||||
* either match that specificity or opt out here — the latter is cleaner.
|
||||
*
|
||||
* Opt-outs:
|
||||
* - `default`: keeps neutral surfaces from :root.
|
||||
* - `anthropic`: warm cream surfaces are a brand choice, NOT a primary-mix
|
||||
* derivation (the Anthropic system deliberately uses warm neutrals for
|
||||
* cards/borders rather than tinting them with the clay accent). */
|
||||
[data-theme-preset]:not([data-theme-preset='default']):not(
|
||||
[data-theme-preset='anthropic']
|
||||
) {
|
||||
--card: color-mix(in oklch, var(--primary) 3%, var(--background));
|
||||
--popover: color-mix(in oklch, var(--primary) 5%, var(--background));
|
||||
--muted: color-mix(in oklch, var(--primary) 7%, var(--background));
|
||||
@@ -317,7 +329,10 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
--info: var(--chart-1);
|
||||
--neutral: var(--muted-foreground);
|
||||
}
|
||||
.dark [data-theme-preset]:not([data-theme-preset='default']) {
|
||||
.dark
|
||||
[data-theme-preset]:not([data-theme-preset='default']):not(
|
||||
[data-theme-preset='anthropic']
|
||||
) {
|
||||
--card: color-mix(in oklch, var(--primary) 8%, var(--background));
|
||||
--popover: color-mix(in oklch, var(--primary) 12%, var(--background));
|
||||
--muted: color-mix(in oklch, var(--primary) 12%, var(--background));
|
||||
@@ -334,6 +349,213 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
--sidebar-border: color-mix(in oklch, var(--primary) 22%, var(--background));
|
||||
}
|
||||
|
||||
/* ── Anthropic ────────────────────────────────────────────────────────── */
|
||||
/*
|
||||
* Inspired by Anthropic's official brand language: warm cream canvas
|
||||
* (#faf9f5) on warm slate ink (#141413), with clay/coral (#d97757) as the
|
||||
* single primary accent. The dormant accent palette (olive, sky, fig,
|
||||
* cactus) is wired into chart and semantic tokens.
|
||||
*
|
||||
* Defining counter-positioning: a tinted (non-white) canvas with warm
|
||||
* neutral cards and borders — NOT primary-tinted surfaces. This is the
|
||||
* brand's deliberate counter-positioning against every cool-gray AI tool.
|
||||
*
|
||||
* Anthropic is opted out of the semantic surface bridge above so these
|
||||
* bespoke warm-neutral surface tokens win the cascade. Without the opt-out,
|
||||
* the bridge selector (specificity 0,2,0 because of `:not()`) would override
|
||||
* this block (specificity 0,1,0) and tint every surface with the clay
|
||||
* accent — producing the peach/pink look that doesn't match Anthropic.
|
||||
*
|
||||
* OKLCH hue 95 = warm yellow-cream (matches #faf9f5 family);
|
||||
* OKLCH hue 60 = warm slate (matches #141413 family);
|
||||
* OKLCH hue 38 = clay/coral (matches #d97757).
|
||||
*/
|
||||
[data-theme-preset='anthropic'] {
|
||||
/* Canvas + ink — the defining pair. */
|
||||
--background: oklch(0.984 0.004 95); /* ≈ #faf9f5 cream */
|
||||
--foreground: oklch(0.205 0.005 60); /* ≈ #141413 ink */
|
||||
|
||||
/* Warm-neutral surfaces (NOT primary-tinted). Stepped opacity matches
|
||||
* the Anthropic surface ladder: canvas → secondary → card → strong. */
|
||||
--card: oklch(0.945 0.008 92); /* ≈ #efe9de */
|
||||
--card-foreground: oklch(0.205 0.005 60);
|
||||
--popover: oklch(0.97 0.006 92); /* slight cream lift */
|
||||
--popover-foreground: oklch(0.205 0.005 60);
|
||||
|
||||
/* Clay/coral — Anthropic's signature accent, used scarcely on CTAs. */
|
||||
--primary: oklch(0.685 0.142 38); /* ≈ #d97757 */
|
||||
--primary-foreground: oklch(0.99 0.005 95);
|
||||
|
||||
--secondary: oklch(0.925 0.008 92);
|
||||
--secondary-foreground: oklch(0.255 0.005 60);
|
||||
|
||||
--muted: oklch(0.94 0.007 92);
|
||||
--muted-foreground: oklch(0.51 0.006 75); /* ≈ #5e5d59 warm gray */
|
||||
--accent: oklch(0.92 0.009 92);
|
||||
--accent-foreground: oklch(0.205 0.005 60);
|
||||
|
||||
--destructive: oklch(0.55 0.18 27);
|
||||
--destructive-foreground: oklch(0.985 0 0);
|
||||
--success: oklch(0.59 0.082 130); /* olive #788c5d */
|
||||
--success-foreground: oklch(0.985 0 0);
|
||||
--warning: oklch(0.78 0.13 70); /* kraft amber */
|
||||
--warning-foreground: oklch(0.205 0.005 60);
|
||||
--info: oklch(0.67 0.075 248); /* sky #6a9bcc */
|
||||
--info-foreground: oklch(0.985 0 0);
|
||||
--neutral: oklch(0.51 0.006 75);
|
||||
--neutral-foreground: oklch(0.205 0.005 60);
|
||||
|
||||
/* Hairline borders — warm gray, not coral. */
|
||||
--border: oklch(0.895 0.008 92); /* ≈ #e8e6dc */
|
||||
--input: oklch(0.895 0.008 92);
|
||||
--ring: oklch(0.685 0.142 38);
|
||||
|
||||
/* Chart palette uses Anthropic's dormant accent swatches. */
|
||||
--chart-1: oklch(0.685 0.142 38); /* clay */
|
||||
--chart-2: oklch(0.59 0.082 130); /* olive */
|
||||
--chart-3: oklch(0.67 0.075 248); /* sky */
|
||||
--chart-4: oklch(0.7 0.115 0); /* fig */
|
||||
--chart-5: oklch(0.83 0.027 175); /* cactus */
|
||||
|
||||
--sidebar: oklch(0.955 0.008 92);
|
||||
--sidebar-foreground: oklch(0.255 0.005 60);
|
||||
--sidebar-primary: oklch(0.685 0.142 38);
|
||||
--sidebar-primary-foreground: oklch(0.99 0.005 95);
|
||||
--sidebar-accent: oklch(0.915 0.009 92);
|
||||
--sidebar-accent-foreground: oklch(0.205 0.005 60);
|
||||
--sidebar-border: oklch(0.895 0.008 92);
|
||||
--sidebar-ring: oklch(0.685 0.142 38);
|
||||
|
||||
--skeleton-base: oklch(0.93 0.008 92);
|
||||
--skeleton-highlight: oklch(0.96 0.006 92);
|
||||
|
||||
--radius: 0.625rem;
|
||||
|
||||
/* Default typography for the Anthropic preset is the editorial serif.
|
||||
* Users can override this with the Font axis (`data-theme-font='sans'`).
|
||||
* The `--font-serif` token itself is declared once in theme.css. */
|
||||
--font-body: var(--font-serif);
|
||||
}
|
||||
.dark [data-theme-preset='anthropic'] {
|
||||
/* Warm near-black product surfaces, not pure black — keeps the editorial
|
||||
* personality even when inverted. Coral lifts slightly for legibility. */
|
||||
--background: oklch(0.205 0.004 60); /* ≈ #181715 */
|
||||
--foreground: oklch(0.965 0.005 92); /* ≈ #faf9f5 */
|
||||
--card: oklch(0.245 0.004 60);
|
||||
--card-foreground: oklch(0.965 0.005 92);
|
||||
--popover: oklch(0.265 0.004 60);
|
||||
--popover-foreground: oklch(0.965 0.005 92);
|
||||
|
||||
--primary: oklch(0.72 0.135 40);
|
||||
--primary-foreground: oklch(0.18 0.005 60);
|
||||
--secondary: oklch(0.295 0.004 60);
|
||||
--secondary-foreground: oklch(0.945 0.005 92);
|
||||
|
||||
--muted: oklch(0.275 0.004 60);
|
||||
--muted-foreground: oklch(0.76 0.006 75);
|
||||
--accent: oklch(0.32 0.006 60);
|
||||
--accent-foreground: oklch(0.985 0.005 92);
|
||||
|
||||
--destructive: oklch(0.7 0.19 22);
|
||||
--destructive-foreground: oklch(0.985 0 0);
|
||||
--success: oklch(0.7 0.105 135);
|
||||
--success-foreground: oklch(0.18 0.005 60);
|
||||
--warning: oklch(0.78 0.13 70);
|
||||
--warning-foreground: oklch(0.18 0.005 60);
|
||||
--info: oklch(0.72 0.085 248);
|
||||
--info-foreground: oklch(0.18 0.005 60);
|
||||
--neutral: oklch(0.76 0.006 75);
|
||||
--neutral-foreground: oklch(0.18 0.005 60);
|
||||
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 16%);
|
||||
--ring: oklch(0.72 0.135 40);
|
||||
|
||||
--chart-1: oklch(0.72 0.135 40);
|
||||
--chart-2: oklch(0.7 0.105 135);
|
||||
--chart-3: oklch(0.72 0.085 248);
|
||||
--chart-4: oklch(0.78 0.13 0);
|
||||
--chart-5: oklch(0.83 0.027 175);
|
||||
|
||||
--sidebar: oklch(0.175 0.004 60);
|
||||
--sidebar-foreground: oklch(0.95 0.005 92);
|
||||
--sidebar-primary: oklch(0.72 0.135 40);
|
||||
--sidebar-primary-foreground: oklch(0.18 0.005 60);
|
||||
--sidebar-accent: oklch(0.31 0.006 60);
|
||||
--sidebar-accent-foreground: oklch(0.985 0.005 92);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.72 0.135 40);
|
||||
|
||||
--skeleton-base: oklch(0.295 0.004 60);
|
||||
--skeleton-highlight: oklch(0.4 0.004 60);
|
||||
}
|
||||
|
||||
/* ── Font axis ────────────────────────────────────────────────────────────
|
||||
* Mirrors how `data-theme-radius` overrides a preset's default radius:
|
||||
* presets may set `--font-body` (Anthropic → serif), and the user's
|
||||
* explicit Font choice wins because these blocks sit AFTER preset blocks.
|
||||
*
|
||||
* The provider resolves `font: 'default'` → `'sans' | 'serif'` against the
|
||||
* active preset before writing the attribute, so the DOM always carries a
|
||||
* concrete value and CSS only needs the simple `[data-theme-font='serif']`
|
||||
* selector below (no `:not()`, no per-preset branches). */
|
||||
[data-theme-font='sans'] {
|
||||
--font-body: var(--font-sans);
|
||||
}
|
||||
[data-theme-font='serif'] {
|
||||
--font-body: var(--font-serif);
|
||||
}
|
||||
|
||||
/* ── Serif typography refinements ────────────────────────────────────────
|
||||
* When the body runs in serif, three things happen:
|
||||
* 1. Editorial OpenType features (kern/liga + tabular numerals so numeric
|
||||
* columns stay grid-aligned even with proportional serif glyphs).
|
||||
* 2. Every UI surface inherits the editorial voice — buttons, inputs,
|
||||
* tabs, sidebar, table headers, badges, pagination, popovers — all
|
||||
* through Tailwind preflight's `button, input, ... { font: inherit }`
|
||||
* rule plus natural HTML inheritance. We intentionally do NOT carry
|
||||
* a per-slot opt-out list: it adds maintenance cost and only blunts
|
||||
* the Anthropic editorial intent. Monospace contexts are excluded
|
||||
* automatically because:
|
||||
* - `<code>`, `<kbd>`, `<pre>`, `<samp>` are typed monospace by
|
||||
* Tailwind preflight (specificity wins over body inheritance).
|
||||
* - `.font-mono` and `.tabular-nums` utilities declare their own
|
||||
* font-family / font-variant-numeric on the element itself.
|
||||
* 3. Headings adopt the Anthropic display setting (medium weight, slight
|
||||
* negative tracking).
|
||||
*
|
||||
* All keyed off `[data-theme-font='serif']` so they apply to any preset
|
||||
* paired with the serif font — not just Anthropic. */
|
||||
[data-theme-font='serif'] {
|
||||
font-feature-settings:
|
||||
'kern' 1,
|
||||
'liga' 1,
|
||||
'calt' 1,
|
||||
'tnum' 1;
|
||||
}
|
||||
|
||||
/* Heading tuning — applies to <h*> tags and the shadcn title slots so
|
||||
* card/sheet/dialog titles read with the same authoritative voice as
|
||||
* page headers. */
|
||||
[data-theme-font='serif'] :is(h1, h2, h3, h4, h5, h6),
|
||||
[data-theme-font='serif'] [data-slot='sheet-title'],
|
||||
[data-theme-font='serif'] [data-slot='dialog-title'],
|
||||
[data-theme-font='serif'] [data-slot='alert-dialog-title'],
|
||||
[data-theme-font='serif'] [data-slot='drawer-title'],
|
||||
[data-theme-font='serif'] [data-slot='card-title'] {
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.012em;
|
||||
}
|
||||
|
||||
/* Larger displays earn tighter tracking, matching Copernicus/Tiempos
|
||||
* editorial display setting (~ -0.02em at 30px+). */
|
||||
[data-theme-font='serif'] h1,
|
||||
[data-theme-font='serif'] .text-3xl,
|
||||
[data-theme-font='serif'] .text-4xl,
|
||||
[data-theme-font='serif'] .text-5xl {
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
/* ── Border radius ────────────────────────────────────────────────────── */
|
||||
[data-theme-radius='none'] {
|
||||
--radius: 0rem;
|
||||
|
||||
Reference in New Issue
Block a user