Files
lib-theme-klein/theme.css
jules bd87175b52 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>
2026-07-07 23:20:55 +10:00

435 lines
17 KiB
CSS
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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 3060% 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;
}