Files
lib-theme-hearth/theme.css
jules 58707e5b62 lib-theme-hearth: 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

393 lines
14 KiB
CSS
Raw 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.

/*
* Hearth — evening warmth, dark-first. The room after sunset: deep umber and
* espresso surfaces that step lighter as they come toward you, the way
* furniture catches firelight; a candle-glow amber primary; one slate-teal
* counterpoint so the warmth never suffocates. Dark mode is the theme's
* native identity — light mode is the morning after, a chroma-free off-white
* with warm espresso ink and the same ember accent, deliberately outside the
* saturated-AI cream band. Built for consumer evenings: reading, journaling,
* the phone at 9pm.
*
* Palette rationale: every neutral lives on the umber axis (hue 4070), never
* blue-gray and never pure black — the darks are browns with the lights
* turned down, so elevation can read as firelight (a faint warm rim plus a
* soft umber ambient) instead of hard black drop shadows. The amber primary
* carries dark ink, not white — dark-ink-on-bright is the contrast pattern
* throughout.
*
* Token contract matches lib-theme-pristine/theme.css.
*/
/*
* Fonts: consuming apps load Bitter + Nunito Sans + JetBrains Mono at the top
* of their entry CSS via the Google Fonts URL documented in this theme's
* README. No @import here — PostCSS ordering forbids it once the theme is
* imported after other statements.
*/
:root {
--font-sans: "Nunito Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--font-heading: "Bitter", 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. Mirror --font-heading/--font-sans. */
--heading-family: "Bitter", Georgia, "Times New Roman", serif;
--body-family: "Nunito Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
/* ── Colors (light — the morning after) ──
* True chroma-0 off-white body, warm espresso ink, ember primary.
* The background deliberately avoids the warm-cream band; warmth is
* carried by the ink and the ember, not the paper. */
--background: oklch(0.975 0 0);
--foreground: oklch(0.22 0.02 50);
--card: oklch(1 0 0);
--card-foreground: oklch(0.22 0.02 50);
--popover: oklch(0.99 0 0);
--popover-foreground: oklch(0.22 0.02 50);
/* Ember — burnt orange deep enough for near-white ink by day. */
--primary: oklch(0.48 0.15 40);
--primary-foreground: oklch(0.98 0.008 80);
--secondary: oklch(0.93 0.008 60);
--secondary-foreground: oklch(0.3 0.02 50);
--muted: oklch(0.94 0.006 60);
--muted-foreground: oklch(0.45 0.02 55);
--accent: oklch(0.93 0.03 55);
--accent-foreground: oklch(0.42 0.12 45);
--destructive: oklch(0.52 0.2 28);
--destructive-foreground: oklch(0.98 0.008 80);
--success: oklch(0.62 0.15 150);
--success-foreground: oklch(0.15 0.03 150);
--warning: oklch(0.75 0.13 75);
--warning-foreground: oklch(0.22 0.05 75);
/* Info — the one cool counterpoint: slate-teal. */
--info: oklch(0.48 0.09 210);
--info-foreground: oklch(0.97 0.008 210);
--border: oklch(0.22 0.02 50 / 0.12);
--input: oklch(0.22 0.02 50 / 0.12);
--ring: oklch(0.48 0.15 40 / 0.5);
/* Chart palette — ember / slate-teal / sage / heather / brick.
* Hues ≥60° apart, lightness staggered between neighbours. */
--chart-1: oklch(0.65 0.13 60);
--chart-2: oklch(0.55 0.09 200);
--chart-3: oklch(0.6 0.11 135);
--chart-4: oklch(0.58 0.1 295);
--chart-5: oklch(0.55 0.12 20);
--sidebar: oklch(0.95 0.003 60);
--sidebar-foreground: oklch(0.22 0.02 50);
--sidebar-primary: oklch(0.48 0.15 40);
--sidebar-primary-foreground: oklch(0.98 0.008 80);
--sidebar-accent: oklch(0.92 0.025 55);
--sidebar-accent-foreground: oklch(0.42 0.12 45);
--sidebar-border: oklch(0.22 0.02 50 / 0.08);
--sidebar-ring: oklch(0.48 0.15 40 / 0.5);
--syntax-keyword: oklch(0.48 0.15 40);
--syntax-string: oklch(0.5 0.11 150);
--syntax-number: oklch(0.52 0.13 60);
--syntax-comment: oklch(0.55 0.015 60);
--syntax-function: oklch(0.48 0.09 230);
--syntax-type: oklch(0.5 0.08 195);
/* ── Chat surfaces ──
* User bubble is a filled deep-ember panel with near-white ink;
* assistant bubble is a card surface with a hairline border. */
--chat-user-bg: oklch(0.46 0.14 40);
--chat-user-fg: oklch(0.98 0.008 80);
--chat-assistant-bg: oklch(1 0 0);
--chat-assistant-fg: oklch(0.22 0.02 50);
--chat-assistant-border: oklch(0.22 0.02 50 / 0.12);
/* Generous-cozy radius — armchair, not app-store icon. */
--radius: 1.0625rem;
/* ── Motion ── slow-blink calm: unhurried base, decelerate-heavy.
* Springs alias to the gentle standard eases — personality lives in
* the values, never in absence. */
--duration-fast: 150ms;
--duration-base: 350ms;
--duration-slow: 500ms;
--duration-slower: 700ms;
--duration-spring: 550ms;
--ease-standard: cubic-bezier(0.25, 0.05, 0.2, 1);
--ease-decelerate: cubic-bezier(0.05, 0.7, 0.1, 1);
--ease-accelerate: cubic-bezier(0.3, 0, 1, 1);
--ease-emphasized: cubic-bezier(0.2, 0, 0, 1);
--ease-spring-gentle: var(--ease-standard);
--ease-spring-snappy: var(--ease-standard);
--ease-spring-bouncy: var(--ease-decelerate);
/* ── Typography ── product register: fixed rem, full ramp.
* Bitter for headings (warm slab-serif), Nunito Sans for body.
* Body leans slightly large with generous leading — evening reading. */
--text-hero-size: 3.25rem;
--text-hero-lh: 1.08;
--text-display-size: 2.75rem;
--text-display-lh: 1.1;
--text-headline-size: 2rem;
--text-headline-lh: 1.15;
--text-title-size: 1.5rem;
--text-title-lh: 1.25;
--text-subtitle-size: 1.1875rem;
--text-subtitle-lh: 1.5;
--text-body-size: 1.0625rem;
--text-body-lh: 1.65;
--text-label-size: 0.875rem;
--text-label-lh: 1.25rem;
--text-caption-size: 0.75rem;
--text-caption-lh: 1rem;
/* ── Elevation (light) ── soft warm-umber paper shadows. */
--elevation-0: none;
--elevation-1: 0 1px 2px oklch(0.22 0.02 50 / 0.06);
--elevation-2: 0 1px 3px oklch(0.22 0.02 50 / 0.08), 0 1px 2px oklch(0.22 0.02 50 / 0.05);
--elevation-3: 0 4px 10px -2px oklch(0.22 0.02 50 / 0.09), 0 2px 4px -2px oklch(0.22 0.02 50 / 0.05);
--elevation-4: 0 12px 26px -4px oklch(0.22 0.02 50 / 0.11), 0 4px 8px -4px oklch(0.22 0.02 50 / 0.05);
--elevation-5: 0 22px 44px -8px oklch(0.22 0.02 50 / 0.18);
--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 — the hero. Deep espresso umber (never blue-gray, never black);
* surfaces step LIGHTER as they come forward, like objects catching
* firelight. Primary flips to candle-glow amber carrying dark umber ink.
*/
.dark {
--background: oklch(0.17 0.018 55);
--foreground: oklch(0.93 0.02 75);
--card: oklch(0.23 0.02 55);
--card-foreground: oklch(0.93 0.02 75);
--popover: oklch(0.25 0.022 55);
--popover-foreground: oklch(0.93 0.02 75);
/* Candle-glow amber; dark-ink-on-bright beats white-on-mid-tone. */
--primary: oklch(0.78 0.145 65);
--primary-foreground: oklch(0.22 0.05 55);
--secondary: oklch(0.28 0.025 55);
--secondary-foreground: oklch(0.9 0.02 75);
--muted: oklch(0.26 0.02 55);
--muted-foreground: oklch(0.74 0.03 70);
--accent: oklch(0.3 0.045 60);
--accent-foreground: oklch(0.9 0.07 72);
--destructive: oklch(0.68 0.19 30);
--destructive-foreground: oklch(0.15 0.04 30);
--success: oklch(0.72 0.15 145);
--success-foreground: oklch(0.16 0.03 145);
--warning: oklch(0.8 0.14 80);
--warning-foreground: oklch(0.2 0.05 80);
/* Slate-teal counterpoint, dark ink on top. */
--info: oklch(0.72 0.1 200);
--info-foreground: oklch(0.15 0.03 210);
--border: oklch(0.95 0.03 75 / 0.12);
--input: oklch(0.95 0.03 75 / 0.12);
--ring: oklch(0.78 0.145 65 / 0.5);
--chart-1: oklch(0.78 0.13 65);
--chart-2: oklch(0.7 0.09 200);
--chart-3: oklch(0.74 0.11 135);
--chart-4: oklch(0.72 0.09 295);
--chart-5: oklch(0.68 0.1 20);
/* Sidebar sits a step further from the fire — darker than the page. */
--sidebar: oklch(0.15 0.016 55);
--sidebar-foreground: oklch(0.9 0.02 75);
--sidebar-primary: oklch(0.78 0.145 65);
--sidebar-primary-foreground: oklch(0.22 0.05 55);
--sidebar-accent: oklch(0.26 0.04 60);
--sidebar-accent-foreground: oklch(0.9 0.07 72);
--sidebar-border: oklch(0.95 0.03 75 / 0.08);
--sidebar-ring: oklch(0.78 0.145 65 / 0.5);
--syntax-keyword: oklch(0.8 0.13 65);
--syntax-string: oklch(0.76 0.12 145);
--syntax-number: oklch(0.82 0.12 80);
--syntax-comment: oklch(0.6 0.02 65);
--syntax-function: oklch(0.74 0.09 210);
--syntax-type: oklch(0.76 0.07 180);
/* ── Chat surfaces (dark) ── */
--chat-user-bg: oklch(0.44 0.11 50);
--chat-user-fg: oklch(0.97 0.015 80);
--chat-assistant-bg: oklch(0.26 0.022 55);
--chat-assistant-fg: oklch(0.93 0.02 75);
--chat-assistant-border: oklch(0.95 0.03 75 / 0.1);
/* ── Elevation (dark) — firelight, not drop shadow ──
* Each level pairs a faint warm rim on the top edge (the surface
* catching the glow) with a soft deep-umber ambient — never pure black.
* Lift is mostly conveyed by the lighter surface itself. */
--elevation-1: inset 0 1px 0 oklch(0.9 0.06 70 / 0.05), 0 1px 2px oklch(0.08 0.015 50 / 0.45);
--elevation-2: inset 0 1px 0 oklch(0.9 0.06 70 / 0.07), 0 2px 6px oklch(0.08 0.015 50 / 0.5);
--elevation-3: inset 0 1px 0 oklch(0.9 0.06 70 / 0.09), 0 6px 14px -2px oklch(0.08 0.015 50 / 0.55);
--elevation-4: inset 0 1px 0 oklch(0.9 0.06 70 / 0.1), 0 12px 28px -4px oklch(0.08 0.015 50 / 0.6);
--elevation-5: inset 0 1px 0 oklch(0.9 0.06 70 / 0.12), 0 22px 48px -8px oklch(0.08 0.015 50 / 0.7);
}
/*
* @layer base — Hearth's evening philosophy.
*
* No glass, no hard chrome. Slab-serif headings, generous body leading,
* surfaces separated by warmth (lighter = closer) rather than heavy shadow.
* Interactions settle slowly — nothing snaps in a room lit by a fire.
*/
@layer base {
body {
min-height: 100vh;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-family: var(--font-sans);
font-size: var(--text-body-size);
line-height: var(--text-body-lh);
letter-spacing: 0.002em;
}
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: 600;
letter-spacing: -0.01em;
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 warm tinted chip; the ember stays
* reserved for actions, links and focus. */
:not(pre) > code {
padding: 0.1em 0.35em;
border-radius: calc(var(--radius) * 0.35);
background: color-mix(in oklch, var(--foreground) 7%, transparent);
color: var(--foreground);
font-size: 0.9em;
}
/* Selection — an ember wash, like lamplight across the line. */
::selection {
background: color-mix(in oklch, var(--primary) 26%, transparent);
color: var(--foreground);
}
a {
color: var(--accent-foreground);
text-decoration-color: color-mix(in oklch, var(--primary) 40%, transparent);
text-underline-offset: 0.2em;
transition: text-decoration-color var(--duration-fast) var(--ease-standard);
}
a:hover {
text-decoration-color: var(--primary);
}
/* Cards & overlay surfaces — a hairline plus the firelight elevation.
* In dark mode the lighter surface does most of the lifting; the shadow
* is an accent, not the mechanism. */
[data-slot="card"],
[data-slot="popover-content"],
[data-slot="dropdown-menu-content"],
[data-slot="context-menu-content"],
[data-slot="hover-card-content"],
[data-slot="select-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);
}
[data-slot="dialog-content"],
[data-slot="alert-dialog-content"],
[data-slot="sheet-content"],
[data-slot="drawer-content"] {
box-shadow: var(--elevation-4);
}
[data-slot="sidebar"] {
background: var(--sidebar);
border-right: var(--border-width-base) solid var(--sidebar-border);
}
/* Buttons — settle, don't snap. */
[data-slot="button"] {
font-weight: 600;
transition:
opacity var(--duration-fast) var(--ease-standard),
transform var(--duration-fast) var(--ease-standard);
}
[data-slot="button"]:hover:not(:disabled) {
opacity: 0.9;
}
[data-slot="button"]:active:not(:disabled) {
opacity: 0.82;
transform: scale(0.99);
}
[data-slot="button"]:focus-visible {
outline: 2px solid var(--ring);
outline-offset: 2px;
}
}
/*
* Unlayered control tuning — outside @layer base so it beats Tailwind's
* utility defaults shipped with shadcn primitives.
*/
/* 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 its density; 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;
}
}