Files
lib-theme-porcelain/theme.css
jules 80817b4eb6 lib-theme-porcelain: initial import (warm-consumer set, 2026-07)
Includes the 2026-07-07 UX sweep: neutral inline-code chips (accent
reserved for actions/links/focus), dark-mode card/popover lightness
lift for visible surface separation, 44px touch-target floor on
coarse pointers, card-title pinned to a named ramp role.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 22:35:56 +10:00

458 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.

/*
* Porcelain — calm, neutral bone-china surfaces warmed by a single restrained
* clay accent. Built as the "iCloud / Apple-Settings calm, but warm" answer for
* a back-of-house personal-cloud utility: quiet chrome, confident whitespace,
* hairline separation, one warm note that points at the next action.
*
* Palette rationale. The field is a true off-white porcelain, not a cream band:
* chroma is held to a whisper (≤0.004) and the hue leans faintly COOL (~265,
* bone china) so the background reads clean-neutral at very high L (0.985) —
* deliberately above and to the cool side of the banned AI cream/sand region
* (L 0.840.97, warm hue 40100). Cards step up to pure white; separation is a
* hairline border plus the faintest neutral elevation. Ink is a warm-leaning
* near-black charcoal (hue ~55) — the one place quiet warmth lives in the text.
* The lone accent is a muted clay / soft terracotta (hue ~47), a restrained
* echo of siena used on ≤10% of the surface: primary action, selection, focus,
* links — never as a wash. Status quartet is held clearly apart in hue (clay 47
* / red 27 / green 150 / amber 78 / blue 245) so meaning never muddies against
* the warm accent.
*
* Token contract matches lib-theme-caffe-florian/theme.css (Crema v1).
*/
/*
* Fonts: consuming apps should load Albert Sans + Newsreader + 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
* inside this file — or smuggling the URL in a comment for a build script to
* lift — breaks PostCSS's ordering rules and is banned by the contract, so
* neither is done here.
*/
:root {
--font-sans: "Albert Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--font-heading: "Newsreader", Georgia, "Times New Roman", serif;
--font-mono: "JetBrains Mono", ui-monospace, 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: "Newsreader", Georgia, "Times New Roman", serif;
--body-family: "Albert Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
/* ── Colors ── cool bone-china porcelain field, warm charcoal ink, one
* muted clay accent. Kept out of the cream band by very low chroma and a
* faint cool (bone) hue at high L. */
--background: oklch(0.985 0.004 265);
--foreground: oklch(0.21 0.008 55);
--card: oklch(1 0 0);
--card-foreground: oklch(0.21 0.008 55);
--popover: oklch(0.995 0.003 265);
--popover-foreground: oklch(0.21 0.008 55);
/* Primary — muted clay. Deepened so white label clears the 4.5:1 floor
* (5.13:1) while still reading calm and premium, not loud. */
--primary: oklch(0.54 0.11 47);
--primary-foreground: oklch(0.99 0.004 60);
--secondary: oklch(0.955 0.004 265);
--secondary-foreground: oklch(0.30 0.008 55);
--muted: oklch(0.955 0.004 265);
--muted-foreground: oklch(0.50 0.008 55);
/* Accent — faint clay tint surface with deep clay ink. The single warm
* note; used for hovers/selection, never a body wash. */
--accent: oklch(0.94 0.02 47);
--accent-foreground: oklch(0.42 0.10 47);
--destructive: oklch(0.55 0.20 27);
--destructive-foreground: oklch(0.99 0.004 60);
/* Success — held at hue 150 (well clear of clay 47), dark ink on top. */
--success: oklch(0.57 0.13 150);
--success-foreground: oklch(0.16 0.03 150);
/* Warning — amber ochre, dark ink on top. */
--warning: oklch(0.75 0.14 78);
--warning-foreground: oklch(0.22 0.04 78);
/* Info — calm blue, light label on top. */
--info: oklch(0.53 0.145 245);
--info-foreground: oklch(0.99 0.004 60);
--border: oklch(0.21 0.008 55 / 0.10);
--input: oklch(0.21 0.008 55 / 0.10);
--ring: oklch(0.54 0.11 47 / 0.5);
/* Chart palette — clay lead, then hues spaced ≥60° apart (clay / teal /
* blue / violet / green), adjacent lightness varied. Calm, low-shout. */
--chart-1: oklch(0.62 0.11 47);
--chart-2: oklch(0.60 0.08 195);
--chart-3: oklch(0.55 0.10 255);
--chart-4: oklch(0.66 0.09 315);
--chart-5: oklch(0.64 0.10 150);
--sidebar: oklch(0.97 0.004 265);
--sidebar-foreground: oklch(0.21 0.008 55);
--sidebar-primary: oklch(0.54 0.11 47);
--sidebar-primary-foreground: oklch(0.99 0.004 60);
--sidebar-accent: oklch(0.94 0.02 47);
--sidebar-accent-foreground: oklch(0.42 0.10 47);
--sidebar-border: oklch(0.21 0.008 55 / 0.08);
--sidebar-ring: oklch(0.54 0.11 47 / 0.5);
--syntax-keyword: oklch(0.50 0.11 47);
--syntax-string: oklch(0.50 0.11 150);
--syntax-number: oklch(0.54 0.11 78);
--syntax-comment: oklch(0.58 0.008 60);
--syntax-function: oklch(0.50 0.13 255);
--syntax-type: oklch(0.52 0.08 210);
/* ── Chat surfaces ──
* Contract for lib-agent-chat-ui / lib-agent-dock-ui. User bubble is a
* filled deep clay with a light label; assistant bubble is a clean white
* card with a hairline border. */
--chat-user-bg: oklch(0.50 0.11 47);
--chat-user-fg: oklch(0.99 0.004 60);
--chat-assistant-bg: oklch(1 0 0);
--chat-assistant-fg: oklch(0.21 0.008 55);
--chat-assistant-border: oklch(0.21 0.008 55 / 0.10);
/* Calm-modern radius. */
--radius: 0.875rem;
/* ── Motion ── calm, product-fast. */
--duration-fast: 150ms;
--duration-base: 300ms;
--duration-slow: 420ms;
--duration-slower: 560ms;
--duration-spring: 300ms;
--ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
--ease-decelerate: cubic-bezier(0, 0, 0.2, 1);
--ease-accelerate: cubic-bezier(0.4, 0, 1, 1);
--ease-emphasized: cubic-bezier(0.2, 0, 0, 1);
/* Calm theme — springs alias to standard easings (no overshoot). Personality
* lives in the values, never in absence (the avalon rule). */
--ease-spring-gentle: cubic-bezier(0.4, 0, 0.2, 1);
--ease-spring-snappy: cubic-bezier(0.4, 0, 0.2, 1);
--ease-spring-bouncy: cubic-bezier(0.4, 0, 0.2, 1);
/* ── Typography ── product register, fixed rem sizes, full role ramp.
* Newsreader supplies a restrained modern-serif heading; Albert Sans is the
* humanist body/UI sans. */
--text-hero-size: 3.5rem;
--text-hero-lh: 3.75rem;
--text-display-size: 3rem;
--text-display-lh: 3.25rem;
--text-headline-size: 2rem;
--text-headline-lh: 2.375rem;
--text-title-size: 1.5rem;
--text-title-lh: 1.875rem;
--text-subtitle-size: 1.1875rem;
--text-subtitle-lh: 1.625rem;
--text-body-size: 1.0625rem;
--text-body-lh: 1.625rem;
--text-label-size: 0.9375rem;
--text-label-lh: 1.25rem;
--text-caption-size: 0.8125rem;
--text-caption-lh: 1.125rem;
/* ── Elevation ── whisper-soft neutral shadows, near-black at very low
* alpha. Calm resting cards, low-key lift. */
--elevation-0: none;
--elevation-1: 0 1px 2px oklch(0.21 0.008 55 / 0.04);
--elevation-2: 0 1px 3px oklch(0.21 0.008 55 / 0.06), 0 1px 2px oklch(0.21 0.008 55 / 0.03);
--elevation-3: 0 4px 10px -3px oklch(0.21 0.008 55 / 0.07), 0 2px 4px -2px oklch(0.21 0.008 55 / 0.04);
--elevation-4: 0 12px 28px -6px oklch(0.21 0.008 55 / 0.09), 0 4px 8px -4px oklch(0.21 0.008 55 / 0.04);
--elevation-5: 0 24px 48px -12px oklch(0.21 0.008 55 / 0.14);
--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 — warm graphite (#232120-ish), not blue-black and not umber-brown.
* Very low chroma at a warm hue (~60) keeps it a neutral warm charcoal; the clay
* accent brightens just enough and carries dark ink per the contract.
*/
.dark {
--background: oklch(0.20 0.006 60);
--foreground: oklch(0.94 0.006 70);
--card: oklch(0.26 0.007 60);
--card-foreground: oklch(0.94 0.006 70);
--popover: oklch(0.275 0.007 60);
--popover-foreground: oklch(0.94 0.006 70);
--primary: oklch(0.66 0.12 50);
--primary-foreground: oklch(0.18 0.02 50);
--secondary: oklch(0.28 0.007 60);
--secondary-foreground: oklch(0.90 0.006 70);
--muted: oklch(0.28 0.007 60);
--muted-foreground: oklch(0.70 0.008 65);
--accent: oklch(0.32 0.04 50);
--accent-foreground: oklch(0.85 0.08 50);
--destructive: oklch(0.62 0.19 27);
--destructive-foreground: oklch(0.16 0.02 27);
--success: oklch(0.70 0.15 150);
--success-foreground: oklch(0.16 0.03 150);
--warning: oklch(0.80 0.14 78);
--warning-foreground: oklch(0.20 0.04 78);
--info: oklch(0.72 0.13 245);
--info-foreground: oklch(0.18 0.03 245);
--border: oklch(0.95 0.006 70 / 0.10);
--input: oklch(0.95 0.006 70 / 0.10);
--ring: oklch(0.66 0.12 50 / 0.5);
--chart-1: oklch(0.70 0.12 50);
--chart-2: oklch(0.70 0.08 195);
--chart-3: oklch(0.68 0.10 255);
--chart-4: oklch(0.74 0.09 315);
--chart-5: oklch(0.72 0.11 150);
--sidebar: oklch(0.22 0.006 60);
--sidebar-foreground: oklch(0.94 0.006 70);
--sidebar-primary: oklch(0.66 0.12 50);
--sidebar-primary-foreground: oklch(0.18 0.02 50);
--sidebar-accent: oklch(0.32 0.04 50);
--sidebar-accent-foreground: oklch(0.85 0.08 50);
--sidebar-border: oklch(0.95 0.006 70 / 0.08);
--sidebar-ring: oklch(0.66 0.12 50 / 0.5);
--syntax-keyword: oklch(0.74 0.12 50);
--syntax-string: oklch(0.74 0.13 150);
--syntax-number: oklch(0.78 0.13 78);
--syntax-comment: oklch(0.58 0.008 60);
--syntax-function: oklch(0.72 0.13 255);
--syntax-type: oklch(0.72 0.08 210);
/* ── Chat surfaces (dark) ── deep clay bubble, dark ink label
* (dark-ink-on-bright, 5.56:1). */
--chat-user-bg: oklch(0.64 0.115 50);
--chat-user-fg: oklch(0.16 0.02 50);
--chat-assistant-bg: oklch(0.26 0.007 60);
--chat-assistant-fg: oklch(0.94 0.006 70);
--chat-assistant-border: oklch(0.95 0.006 70 / 0.10);
--elevation-1: 0 1px 2px oklch(0 0 0 / 0.3);
--elevation-2: 0 1px 3px oklch(0 0 0 / 0.4), 0 1px 2px oklch(0 0 0 / 0.25);
--elevation-3: 0 4px 10px oklch(0 0 0 / 0.35), 0 2px 4px oklch(0 0 0 / 0.25);
--elevation-4: 0 12px 28px oklch(0 0 0 / 0.4), 0 4px 8px oklch(0 0 0 / 0.25);
--elevation-5: 0 24px 48px oklch(0 0 0 / 0.5);
}
/*
* @layer base — Porcelain's calm-utility philosophy.
*
* No glass, no offsets. Humanist sans body; restrained modern-serif headings
* used sparingly. Cards sit on the porcelain field with a hairline border and a
* whisper-soft neutral shadow — calm resting surfaces, not floating chrome. The
* clay accent points; everything else is neutral.
*/
@layer base {
body {
min-height: 100vh;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
letter-spacing: -0.006em;
font-family: var(--font-sans);
font-size: var(--text-body-size);
line-height: var(--text-body-lh);
}
/* Serif headings — Newsreader, kept calm (not editorial-loud): moderate
* weight, tight-but-gentle tracking. */
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: 560;
letter-spacing: -0.012em;
color: var(--foreground);
}
code, kbd, samp, pre,
[data-slot="code"],
[data-slot="code-block"] {
font-family: var(--font-mono);
}
/* Inline code — neutral ink on a faint chip; the clay accent stays
* reserved for actions, links and focus. */
:not(pre) > code {
padding: 0.1em 0.35em;
border-radius: calc(var(--radius) * 0.4);
background: color-mix(in oklch, var(--foreground) 7%, transparent);
color: var(--foreground);
font-size: 0.9em;
}
/* Selection — the one warm note, faint clay wash. */
::selection {
background: color-mix(in oklch, var(--primary) 18%, transparent);
color: var(--foreground);
}
/* Links — clay, quietly underlined on hover. */
a {
color: var(--primary);
text-decoration-color: color-mix(in oklch, var(--primary) 35%, transparent);
text-underline-offset: 0.2em;
transition: text-decoration-color var(--duration-fast) var(--ease-standard);
}
a:hover {
text-decoration-color: var(--primary);
}
/* Card & overlay surfaces — hairline border + whisper elevation so cards
* read as calm resting tiles on porcelain. */
[data-slot="card"],
[data-slot="popover-content"],
[data-slot="dropdown-menu-content"],
[data-slot="context-menu-content"],
[data-slot="menubar-content"],
[data-slot="hover-card-content"],
[data-slot="select-content"],
[data-slot="combobox-content"],
[data-slot="command"],
[data-slot="tooltip-content"],
[data-slot="sheet-content"],
[data-slot="dialog-content"],
[data-slot="alert-dialog-content"],
[data-slot="drawer-content"] {
border: var(--border-width-base) solid var(--border);
box-shadow: var(--elevation-2);
}
.dark [data-slot="card"],
.dark [data-slot="popover-content"],
.dark [data-slot="dropdown-menu-content"],
.dark [data-slot="context-menu-content"],
.dark [data-slot="menubar-content"],
.dark [data-slot="hover-card-content"],
.dark [data-slot="select-content"],
.dark [data-slot="combobox-content"],
.dark [data-slot="command"],
.dark [data-slot="tooltip-content"],
.dark [data-slot="sheet-content"],
.dark [data-slot="dialog-content"],
.dark [data-slot="alert-dialog-content"],
.dark [data-slot="drawer-content"] {
box-shadow: var(--elevation-3);
}
[data-slot="button"] {
font-weight: 500;
}
[data-slot="sidebar"] {
background: var(--sidebar);
border-right: var(--border-width-base) solid var(--sidebar-border);
}
}
/*
* Unlayered component polish — placed outside @layer base so it beats Tailwind's
* default utility classes shipped with shadcn. Porcelain keeps controls calm and
* comfortably sized with clay focus: inputs are ≥44px everywhere; buttons keep
* their density under a mouse and clear the 44px floor on coarse pointers.
*/
[data-slot="button"]:hover:not(:disabled) {
opacity: 0.9;
}
[data-slot="button"]:active:not(:disabled) {
opacity: 0.8;
transform: scale(0.99);
}
[data-slot="button"]:focus-visible {
outline: 2px solid var(--primary);
outline-offset: 2px;
box-shadow: none;
}
[data-slot="input"],
[data-slot="textarea"],
[data-slot="select-trigger"] {
min-height: 2.75rem;
padding-inline: 0.875rem;
font-size: 0.9375rem;
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--foreground);
transition:
border-color var(--duration-fast) var(--ease-standard),
box-shadow var(--duration-fast) var(--ease-standard);
}
[data-slot="textarea"] {
min-height: 6.25rem;
padding-block: 0.75rem;
line-height: 1.55;
resize: vertical;
}
[data-slot="input"]::placeholder,
[data-slot="textarea"]::placeholder {
color: var(--muted-foreground);
opacity: 0.7;
}
[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(--foreground) 22%, transparent);
}
[data-slot="input"]:focus,
[data-slot="input"]:focus-visible,
[data-slot="textarea"]:focus,
[data-slot="textarea"]:focus-visible,
[data-slot="select-trigger"]:focus,
[data-slot="select-trigger"]:focus-visible {
border-color: var(--primary);
box-shadow: 0 0 0 1px var(--primary);
outline: none;
}
[data-slot="card"] {
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius);
transition:
border-color var(--duration-base) var(--ease-standard),
box-shadow var(--duration-base) var(--ease-standard);
}
/* 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 — buttons clear the 44px floor on coarse pointers
* (inputs are already ≥44px everywhere above). */
@media (pointer: coarse) {
[data-slot="button"] {
min-height: 2.75rem;
min-width: 2.75rem;
}
}