lib-theme-klein: initial import (bold/identity-forward set, 2026-07)
One of four divergent candidates for the bold consumer theme family (committed saturated color at 30-60% surface coverage, strong display type). Includes the post-diverge fixes: heading color:inherit so brand slabs keep their foreground pairing, plus per-theme refinements. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
434
theme.css
Normal file
434
theme.css
Normal file
@@ -0,0 +1,434 @@
|
||||
/*
|
||||
* Klein — ultramarine drench.
|
||||
*
|
||||
* Yves Klein blue as the identity. The background field ITSELF is a committed,
|
||||
* saturated deep ultramarine — not a tint, not an accent, the world you stand
|
||||
* in — carrying near-white ink. Cards are clean near-white panels floating on
|
||||
* the blue, with dark ink on top. This inverts the usual light-theme logic:
|
||||
* :root (light mode) IS the drenched blue world; the blue carries 30–60% of
|
||||
* every surface by construction.
|
||||
*
|
||||
* The counterpoint is a warm amber-orange (hue ~60) spent on the primary
|
||||
* action, focus, selection, and the user chat bubble — the one hot signal that
|
||||
* pops off the ultramarine. Secondary and muted stay inside the blue family
|
||||
* (deeper stripes of the same hue); the sidebar is a deeper band of
|
||||
* ultramarine. Borders are dark hairlines because borders live on the white
|
||||
* cards and inputs; the sidebar gets its own white-alpha hairline token.
|
||||
*
|
||||
* Dark mode is ultramarine at midnight: the field deepens, cards become deep
|
||||
* blue panels stepping LIGHTER off the field, ink stays near-white — the theme
|
||||
* is unmistakably blue in both modes.
|
||||
*
|
||||
* Type is the art-poster voice: Syne (700+) for headings, Inter for body,
|
||||
* JetBrains Mono for code. Consumer register — generous 1rem radius, confident
|
||||
* motion (150/300/450/600ms) with real gentle springs, deep blue-black shadows
|
||||
* (a drenched field casts blue shadow, never gray).
|
||||
*
|
||||
* Token contract matches lib-theme-caffe-florian/theme.css (Crema v1).
|
||||
*/
|
||||
|
||||
/*
|
||||
* Fonts: consuming apps load Syne + Inter + JetBrains Mono at the top of their
|
||||
* entry CSS (where @import statements are allowed), via the Google Fonts URL
|
||||
* documented in this theme's README. Embedding the @import here — or smuggling
|
||||
* the URL in a comment for a build script to lift — breaks PostCSS's ordering
|
||||
* rules, so neither is done in this file.
|
||||
*/
|
||||
|
||||
:root {
|
||||
--font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--font-heading: "Syne", "Inter", -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
--font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
||||
|
||||
/* Tailwind utility families. Distinct names so a consuming app's
|
||||
* `@theme inline` can indirect to these (--font-heading/--font-sans collide
|
||||
* with Tailwind's own font-* namespace). Mirror --font-heading/--font-sans. */
|
||||
--heading-family: "Syne", "Inter", -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
--body-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
|
||||
/* ── Colors ── the drench: deep saturated ultramarine field, near-white
|
||||
* ink on the blue, clean near-white cards carrying dark blue-black ink. */
|
||||
--background: oklch(0.44 0.2 265);
|
||||
--foreground: oklch(0.985 0.008 265);
|
||||
|
||||
--card: oklch(0.99 0.005 265);
|
||||
--card-foreground: oklch(0.21 0.045 265);
|
||||
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.21 0.045 265);
|
||||
|
||||
/* Primary — the warm counterpoint. Vivid amber-orange with near-black warm
|
||||
* ink on top (bright-swatch/dark-ink pairing); the one hot signal that
|
||||
* pops off the blue field. */
|
||||
--primary: oklch(0.72 0.17 60);
|
||||
--primary-foreground: oklch(0.2 0.05 60);
|
||||
|
||||
/* Secondary — a deeper stripe of the same ultramarine, white ink. */
|
||||
--secondary: oklch(0.38 0.18 265);
|
||||
--secondary-foreground: oklch(0.975 0.008 265);
|
||||
|
||||
/* Muted — the family's quiet register: deeper blue surface; the muted ink
|
||||
* is a pale desaturated ultramarine that still clears 4.5:1 on the field. */
|
||||
--muted: oklch(0.39 0.18 265);
|
||||
--muted-foreground: oklch(0.87 0.05 265);
|
||||
|
||||
/* Accent — hover/selection tint for menus and popovers (which are white
|
||||
* panels here): a pale ultramarine wash with deep blue ink. */
|
||||
--accent: oklch(0.93 0.035 265);
|
||||
--accent-foreground: oklch(0.3 0.13 265);
|
||||
|
||||
/* ── Status ── tuned for the CARD surface (chips sit on white panels).
|
||||
* Dark ink on bright swatches for success/warning/info; destructive keeps
|
||||
* a light label on a deep alarm red. Warning sits at yellow ~90 so it
|
||||
* never reads as the amber-orange primary (hue 60). Info is a cyan-sky
|
||||
* (~220) clearly apart from the ultramarine field (265). */
|
||||
--destructive: oklch(0.55 0.21 27);
|
||||
--destructive-foreground: oklch(0.98 0.01 27);
|
||||
--success: oklch(0.62 0.14 150);
|
||||
--success-foreground: oklch(0.17 0.03 150);
|
||||
--warning: oklch(0.8 0.15 90);
|
||||
--warning-foreground: oklch(0.23 0.05 90);
|
||||
--info: oklch(0.7 0.12 220);
|
||||
--info-foreground: oklch(0.16 0.03 220);
|
||||
|
||||
/* Borders live where hairlines are drawn: on white cards and inputs — so
|
||||
* --border is a dark blue-ink hairline. The blue field's own hairlines are
|
||||
* carried by --sidebar-border (white-alpha) below. */
|
||||
--border: oklch(0.21 0.045 265 / 0.14);
|
||||
--input: oklch(0.21 0.045 265 / 0.18);
|
||||
--ring: oklch(0.72 0.17 60 / 0.6);
|
||||
|
||||
/* Chart palette — amber counterpoint leads, then the identity ultramarine,
|
||||
* teal, magenta, green. Hues spaced ≥60°, adjacent lightness varied.
|
||||
* Tuned for the white card surface where charts render. */
|
||||
--chart-1: oklch(0.72 0.17 60);
|
||||
--chart-2: oklch(0.48 0.2 265);
|
||||
--chart-3: oklch(0.65 0.12 190);
|
||||
--chart-4: oklch(0.56 0.2 340);
|
||||
--chart-5: oklch(0.62 0.15 150);
|
||||
|
||||
/* Sidebar — a deeper stripe of ultramarine; hairlines on the blue are
|
||||
* white-alpha; the amber signal marks the active item. */
|
||||
--sidebar: oklch(0.36 0.17 265);
|
||||
--sidebar-foreground: oklch(0.975 0.008 265);
|
||||
--sidebar-primary: oklch(0.72 0.17 60);
|
||||
--sidebar-primary-foreground: oklch(0.2 0.05 60);
|
||||
--sidebar-accent: oklch(0.47 0.2 265);
|
||||
--sidebar-accent-foreground: oklch(0.985 0.008 265);
|
||||
--sidebar-border: oklch(1 0 0 / 0.18);
|
||||
--sidebar-ring: oklch(0.72 0.17 60 / 0.6);
|
||||
|
||||
/* Syntax — code blocks render on light card surfaces: deep inks with the
|
||||
* theme's blue and amber in the leading roles. */
|
||||
--syntax-keyword: oklch(0.45 0.2 265);
|
||||
--syntax-string: oklch(0.48 0.12 150);
|
||||
--syntax-number: oklch(0.55 0.14 60);
|
||||
--syntax-comment: oklch(0.55 0.03 265);
|
||||
--syntax-function: oklch(0.5 0.16 300);
|
||||
--syntax-type: oklch(0.48 0.1 190);
|
||||
|
||||
/* ── Chat surfaces ──
|
||||
* Contract for lib-chat-ui / lib-agent-ui / lib-agent-dock-ui. The user
|
||||
* bubble is the amber counterpoint (dark ink) glowing against the blue
|
||||
* field; the assistant answers on a clean white card with a hairline. */
|
||||
--chat-user-bg: oklch(0.72 0.17 60);
|
||||
--chat-user-fg: oklch(0.2 0.05 60);
|
||||
--chat-assistant-bg: oklch(0.99 0.005 265);
|
||||
--chat-assistant-fg: oklch(0.21 0.045 265);
|
||||
--chat-assistant-border: oklch(0.21 0.045 265 / 0.12);
|
||||
|
||||
/* Generous consumer radius. */
|
||||
--radius: 1rem;
|
||||
|
||||
/* ── Motion ── confident consumer pacing with real gentle springs — the
|
||||
* bold identity is allowed a physical settle. */
|
||||
--duration-fast: 150ms;
|
||||
--duration-base: 300ms;
|
||||
--duration-slow: 450ms;
|
||||
--duration-slower: 600ms;
|
||||
--duration-spring: 450ms;
|
||||
|
||||
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
||||
--ease-decelerate: cubic-bezier(0, 0, 0, 1);
|
||||
--ease-accelerate: cubic-bezier(0.3, 0, 1, 1);
|
||||
--ease-emphasized: cubic-bezier(0.3, 0, 0, 1);
|
||||
|
||||
--ease-spring-gentle: cubic-bezier(0.34, 1.16, 0.64, 1);
|
||||
--ease-spring-snappy: cubic-bezier(0.42, 1.28, 0.56, 1);
|
||||
--ease-spring-bouncy: cubic-bezier(0.36, 1.44, 0.44, 1);
|
||||
|
||||
/* ── Typography ── consumer register, fixed rem, full role ramp. */
|
||||
--text-hero-size: 3.25rem;
|
||||
--text-hero-lh: 1.05;
|
||||
--text-display-size: 2.5rem;
|
||||
--text-display-lh: 1.1;
|
||||
--text-headline-size: 1.75rem;
|
||||
--text-headline-lh: 1.2;
|
||||
--text-title-size: 1.375rem;
|
||||
--text-title-lh: 1.3;
|
||||
--text-subtitle-size: 1.125rem;
|
||||
--text-subtitle-lh: 1.45;
|
||||
--text-body-size: 1rem;
|
||||
--text-body-lh: 1.6;
|
||||
--text-label-size: 0.875rem;
|
||||
--text-label-lh: 1.25rem;
|
||||
--text-caption-size: 0.75rem;
|
||||
--text-caption-lh: 1.125rem;
|
||||
|
||||
/* ── Elevation ── shadows cast on a drenched field are deep blue-black,
|
||||
* never gray — white cards lift off the ultramarine on blue shadow. */
|
||||
--elevation-0: none;
|
||||
--elevation-1: 0 1px 2px oklch(0.12 0.08 265 / 0.2);
|
||||
--elevation-2: 0 2px 4px oklch(0.12 0.08 265 / 0.24), 0 1px 2px oklch(0.12 0.08 265 / 0.16);
|
||||
--elevation-3: 0 6px 12px -2px oklch(0.12 0.08 265 / 0.28), 0 2px 4px -2px oklch(0.12 0.08 265 / 0.18);
|
||||
--elevation-4: 0 14px 28px -6px oklch(0.12 0.08 265 / 0.34), 0 4px 10px -4px oklch(0.12 0.08 265 / 0.2);
|
||||
--elevation-5: 0 24px 48px -8px oklch(0.12 0.08 265 / 0.44);
|
||||
|
||||
--spacing: 0.25rem;
|
||||
|
||||
--border-width-thin: 0.5px;
|
||||
--border-width-base: 1px;
|
||||
--border-width-thick: 2px;
|
||||
--border-width-heavy: 4px;
|
||||
|
||||
/* No glass — inert values so consuming apps referencing these get no-ops. */
|
||||
--glass-blur: 0px;
|
||||
--glass-saturate: 100%;
|
||||
--glass-brightness: 1;
|
||||
--glass-contrast: 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dark mode — ultramarine at midnight. The field deepens (still unmistakably
|
||||
* blue, never charcoal); cards become deep blue panels stepping LIGHTER off
|
||||
* the field (bg 0.30 → card 0.36 → popover 0.385 → muted 0.40); ink stays
|
||||
* near-white; the amber signal brightens a touch against the night.
|
||||
*/
|
||||
.dark {
|
||||
--background: oklch(0.3 0.13 265);
|
||||
--foreground: oklch(0.965 0.01 265);
|
||||
|
||||
--card: oklch(0.36 0.14 265);
|
||||
--card-foreground: oklch(0.965 0.01 265);
|
||||
|
||||
--popover: oklch(0.385 0.14 265);
|
||||
--popover-foreground: oklch(0.965 0.01 265);
|
||||
|
||||
--primary: oklch(0.75 0.16 60);
|
||||
--primary-foreground: oklch(0.2 0.05 60);
|
||||
|
||||
--secondary: oklch(0.4 0.15 265);
|
||||
--secondary-foreground: oklch(0.95 0.012 265);
|
||||
|
||||
--muted: oklch(0.4 0.15 265);
|
||||
--muted-foreground: oklch(0.85 0.045 265);
|
||||
|
||||
--accent: oklch(0.45 0.16 265);
|
||||
--accent-foreground: oklch(0.97 0.01 265);
|
||||
|
||||
--destructive: oklch(0.56 0.21 27);
|
||||
--destructive-foreground: oklch(0.98 0.01 27);
|
||||
--success: oklch(0.7 0.15 150);
|
||||
--success-foreground: oklch(0.17 0.03 150);
|
||||
--warning: oklch(0.83 0.15 90);
|
||||
--warning-foreground: oklch(0.23 0.05 90);
|
||||
--info: oklch(0.74 0.12 220);
|
||||
--info-foreground: oklch(0.16 0.03 220);
|
||||
|
||||
/* At midnight every surface is blue, so hairlines are white-ink alpha. */
|
||||
--border: oklch(0.97 0.01 265 / 0.16);
|
||||
--input: oklch(0.97 0.01 265 / 0.2);
|
||||
--ring: oklch(0.75 0.16 60 / 0.6);
|
||||
|
||||
--chart-1: oklch(0.78 0.16 60);
|
||||
--chart-2: oklch(0.68 0.15 265);
|
||||
--chart-3: oklch(0.74 0.12 190);
|
||||
--chart-4: oklch(0.7 0.17 340);
|
||||
--chart-5: oklch(0.74 0.15 150);
|
||||
|
||||
--sidebar: oklch(0.26 0.12 265);
|
||||
--sidebar-foreground: oklch(0.965 0.01 265);
|
||||
--sidebar-primary: oklch(0.75 0.16 60);
|
||||
--sidebar-primary-foreground: oklch(0.2 0.05 60);
|
||||
--sidebar-accent: oklch(0.37 0.15 265);
|
||||
--sidebar-accent-foreground: oklch(0.97 0.01 265);
|
||||
--sidebar-border: oklch(1 0 0 / 0.14);
|
||||
--sidebar-ring: oklch(0.75 0.16 60 / 0.6);
|
||||
|
||||
--syntax-keyword: oklch(0.82 0.1 265);
|
||||
--syntax-string: oklch(0.82 0.12 150);
|
||||
--syntax-number: oklch(0.84 0.13 60);
|
||||
--syntax-comment: oklch(0.72 0.03 265);
|
||||
--syntax-function: oklch(0.8 0.12 300);
|
||||
--syntax-type: oklch(0.8 0.1 190);
|
||||
|
||||
/* ── Chat surfaces (dark) ── the amber user bubble glows against the
|
||||
* midnight field; the assistant answers on a deep blue card panel. */
|
||||
--chat-user-bg: oklch(0.75 0.16 60);
|
||||
--chat-user-fg: oklch(0.2 0.05 60);
|
||||
--chat-assistant-bg: oklch(0.36 0.14 265);
|
||||
--chat-assistant-fg: oklch(0.965 0.01 265);
|
||||
--chat-assistant-border: oklch(0.97 0.01 265 / 0.14);
|
||||
|
||||
--elevation-1: 0 1px 2px oklch(0.08 0.05 265 / 0.4);
|
||||
--elevation-2: 0 2px 4px oklch(0.08 0.05 265 / 0.45), 0 1px 2px oklch(0.08 0.05 265 / 0.3);
|
||||
--elevation-3: 0 6px 12px -2px oklch(0.08 0.05 265 / 0.5), 0 2px 4px -2px oklch(0.08 0.05 265 / 0.3);
|
||||
--elevation-4: 0 14px 28px -6px oklch(0.08 0.05 265 / 0.55), 0 4px 10px -4px oklch(0.08 0.05 265 / 0.3);
|
||||
--elevation-5: 0 24px 48px -8px oklch(0.08 0.05 265 / 0.65);
|
||||
}
|
||||
|
||||
/*
|
||||
* @layer base — Klein's poster philosophy.
|
||||
*
|
||||
* The field is the identity: content floats on committed ultramarine. Syne
|
||||
* headings at 700 give the art-poster voice; Inter keeps the body legible and
|
||||
* modern; the amber counterpoint does all the pointing — selection, focus,
|
||||
* links' underlines, the CTA.
|
||||
*/
|
||||
@layer base {
|
||||
body {
|
||||
min-height: 100vh;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
letter-spacing: -0.005em;
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-body-size);
|
||||
line-height: var(--text-body-lh);
|
||||
}
|
||||
|
||||
/* Headings — Syne at 700, the poster voice. Syne runs wide; a touch of
|
||||
* negative tracking keeps it confident rather than sprawling. */
|
||||
h1, h2, h3, h4,
|
||||
[data-slot="card-title"],
|
||||
[data-slot="dialog-title"],
|
||||
[data-slot="sheet-title"],
|
||||
[data-slot="alert-dialog-title"] {
|
||||
font-family: var(--font-heading);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.015em;
|
||||
/* inherit, not var(--foreground): identity themes put headings on
|
||||
* colored slabs (bg-primary heroes) — forcing the field ink there
|
||||
* breaks the slab's own foreground pairing. */
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
code, kbd, samp, pre,
|
||||
[data-slot="code"],
|
||||
[data-slot="code-block"] {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
/* Inline code — neutral chip: foreground-tint wash with foreground ink,
|
||||
* so it reads correctly on both the blue field and the white cards. */
|
||||
:not(pre) > code {
|
||||
padding: 0.1em 0.35em;
|
||||
border-radius: calc(var(--radius) * 0.5);
|
||||
background: color-mix(in oklch, var(--foreground) 8%, transparent);
|
||||
color: var(--foreground);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Selection — the amber counterpoint, everywhere. */
|
||||
::selection {
|
||||
background: color-mix(in oklch, var(--primary) 45%, transparent);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
/* Links — ink follows the surface (white on the field, dark on cards);
|
||||
* the amber underline is the constant link signal on both. */
|
||||
a {
|
||||
color: inherit;
|
||||
font-weight: 600;
|
||||
text-decoration-color: color-mix(in oklch, var(--primary) 70%, transparent);
|
||||
text-decoration-thickness: 2px;
|
||||
text-underline-offset: 0.2em;
|
||||
transition: text-decoration-color var(--duration-fast) var(--ease-standard);
|
||||
}
|
||||
a:hover {
|
||||
text-decoration-color: var(--primary);
|
||||
}
|
||||
|
||||
[data-slot="sidebar"] {
|
||||
background: var(--sidebar);
|
||||
border-right: var(--border-width-base) solid var(--sidebar-border);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Focus-visible — the amber ring, offset (not inset), for keyboard users.
|
||||
* Placed outside @layer base so it beats Tailwind's default ring utilities.
|
||||
*/
|
||||
[data-slot="button"]:focus-visible,
|
||||
[data-slot="input"]:focus-visible,
|
||||
[data-slot="textarea"]:focus-visible,
|
||||
[data-slot="select-trigger"]:focus-visible {
|
||||
outline: 2px solid var(--primary);
|
||||
outline-offset: 2px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Buttons — confident weight; a springy press absorb suits the identity. */
|
||||
[data-slot="button"] {
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.005em;
|
||||
}
|
||||
|
||||
/* Card titles — pinned to the subtitle role so the scale is the theme's
|
||||
* decision, not the card primitive's default. */
|
||||
[data-slot="card-title"] {
|
||||
font-size: var(--text-subtitle-size);
|
||||
line-height: var(--text-subtitle-lh);
|
||||
}
|
||||
|
||||
/* Touch targets — desktop keeps the consumer density ladder; coarse pointers
|
||||
* get the 44px floor on every control. */
|
||||
@media (pointer: coarse) {
|
||||
[data-slot="button"] {
|
||||
min-height: 2.75rem;
|
||||
min-width: 2.75rem;
|
||||
}
|
||||
[data-slot="input"],
|
||||
[data-slot="select-trigger"] {
|
||||
min-height: 2.75rem;
|
||||
}
|
||||
}
|
||||
[data-slot="button"]:hover:not(:disabled) {
|
||||
opacity: 0.92;
|
||||
}
|
||||
[data-slot="button"]:active:not(:disabled) {
|
||||
opacity: 0.84;
|
||||
transform: scale(0.98);
|
||||
transition:
|
||||
transform var(--duration-fast) var(--ease-spring-gentle),
|
||||
opacity var(--duration-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
/* Inputs — white card panels with dark hairlines floating on the blue;
|
||||
* the amber focus ring is the constant. */
|
||||
[data-slot="input"],
|
||||
[data-slot="textarea"],
|
||||
[data-slot="select-trigger"] {
|
||||
background: var(--card);
|
||||
border: var(--border-width-base) solid var(--input);
|
||||
border-radius: var(--radius);
|
||||
color: var(--card-foreground);
|
||||
transition:
|
||||
border-color var(--duration-fast) var(--ease-standard),
|
||||
box-shadow var(--duration-fast) var(--ease-standard);
|
||||
}
|
||||
[data-slot="input"]::placeholder,
|
||||
[data-slot="textarea"]::placeholder {
|
||||
color: color-mix(in oklch, var(--card-foreground) 55%, transparent);
|
||||
}
|
||||
[data-slot="input"]:hover:not(:disabled),
|
||||
[data-slot="textarea"]:hover:not(:disabled),
|
||||
[data-slot="select-trigger"]:hover:not(:disabled) {
|
||||
border-color: color-mix(in oklch, var(--card-foreground) 32%, transparent);
|
||||
}
|
||||
[data-slot="input"]:focus,
|
||||
[data-slot="textarea"]:focus,
|
||||
[data-slot="select-trigger"]:focus {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 1px var(--primary);
|
||||
outline: none;
|
||||
}
|
||||
Reference in New Issue
Block a user