From 19a5d978a3297248f94753a16c4eb00d73b26436 Mon Sep 17 00:00:00 2001 From: jules Date: Tue, 7 Jul 2026 22:35:56 +1000 Subject: [PATCH] lib-theme-solarium: 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 --- README.md | 60 ++++++++ theme.css | 405 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 465 insertions(+) create mode 100644 README.md create mode 100644 theme.css diff --git a/README.md b/README.md new file mode 100644 index 0000000..27d1515 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# lib-theme-solarium + +**Botanical sunroom.** Morning light through a glass room full of plants — humid air, +leaf shadows, citrus on the table. Solarium is FRESH-warm, not baked-warm: surfaces are +a pale green-amber leaf-light (hue ~120, never the cream/sand band), ink is deep +green-charcoal, the primary is a deepened fern carrying warm-white labels at 7.5:1, and +a single apricot accent supplies the consumer-warm note. Dark mode is the sunroom at +night — deep green-charcoal (never blue-gray), a luminous fern primary with dark-ink +labels, and the apricot dimmed to lamp glow. Organic-large radius (1.25rem), soft +green-tinted daylight shadows, buoyant-but-calm motion (~300ms base, decelerating +entries, gentle springs). + +## Fonts + +Fonts are loaded by the **consuming app**, not by `theme.css` (see the Crema token +contract). Add this at the very top of the app's entry CSS (`app/app.css`), before any +other statement: + +```css +@import url("https://fonts.googleapis.com/css2?family=Gantari:wght@500;600;700&family=Figtree:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap"); +``` + +| Role | Family | Weights | +|---|---|---| +| `--font-heading` | Gantari | 500, 600, 700 | +| `--font-sans` | Figtree | 400, 500, 600 | +| `--font-mono` | JetBrains Mono | 400, 500 | + +## Usage + +```css +/* app/app.css — after the fonts @import, under CREMA:SOURCES conventions */ +@import "../../lib-theme-solarium/theme.css"; +``` + +Class-based `.dark` toggles dark mode (the app owns the toggle). No glass — the +`--glass-*` tokens are inert stubs (`0px` / `100%` / `1` / `1`). + +## Contract + +Token contract v1 (`create-crema-app/TOKEN_CONTRACT.md`). Validate with: + +```bash +node ../create-crema-app/scripts/validate-theme.mjs theme.css +``` + +Passing as of 2026-07-07: all Tier-1 tokens present in both modes, all contrast floors +met (foreground/background exceeds the 7:1 target in light and dark), no ban violations. + +## Palette notes + +- **Primary** `oklch(0.42 0.11 145)` — mid-L fern deepened until warm-white text + clears 7:1; mid-lightness greens under white labels fail 4.5:1, so the brand green + sinks rather than the label darkening. +- **Success** shifts to emerald (h160, dark-ink label) so status green stays + distinguishable from the fern primary (h145). +- **Accent** is the one warm pop — apricot tint surface with a rust-apricot label in + light; a deep apricot lamp-glow surface in dark. +- **Charts**: fern / apricot / sky / orchid / citrus-gold, hues ≥60° apart with + adjacent-index lightness variation. diff --git a/theme.css b/theme.css new file mode 100644 index 0000000..bfbcd19 --- /dev/null +++ b/theme.css @@ -0,0 +1,405 @@ +/* + * Solarium — botanical sunroom theme. Morning light through glass, humid air, + * leaf shadows, citrus on the table. FRESH-warm, not baked-warm: the warmth + * comes from light and living things, not from brown or orange pigment. Body + * surfaces are a pale neutral tinted toward the theme's own green-amber hue + * (~120) — light through leaves, never parchment. Ink is a deep green-charcoal; + * the primary is a confident deepened fern that carries warm-white labels; the + * single warm pop is an apricot accent that keeps the palette consumer-warm + * rather than clinical-green. Success shifts toward emerald so status green + * never collides with the brand fern. Dark mode is the sunroom at night: deep + * green-charcoal (never blue-gray), plants as silhouettes, an apricot lamp + * glow, and a primary that brightens to a luminous fern with dark-ink labels. + * + * Token contract matches lib-theme-pristine/theme.css. + */ + +/* + * Fonts: consuming apps should load Gantari + Figtree + 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 when the theme is imported after + * other statements, so neither is done here. + */ + +:root { + --font-sans: "Figtree", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + --font-heading: "Gantari", "Figtree", -apple-system, BlinkMacSystemFont, sans-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: "Gantari", "Figtree", -apple-system, BlinkMacSystemFont, sans-serif; + --body-family: "Figtree", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + + /* ── Colors ── pale leaf-light surfaces (green-amber tint, hue ~120 — + * deliberately outside the cream/sand band), deep green-charcoal ink, + * deepened fern primary, one apricot pop. */ + --background: oklch(0.965 0.012 120); + --foreground: oklch(0.21 0.025 145); + + --card: oklch(0.99 0.006 120); + --card-foreground: oklch(0.21 0.025 145); + + --popover: oklch(0.985 0.008 120); + --popover-foreground: oklch(0.21 0.025 145); + + /* Deepened fern — mid-L greens fail 4.5:1 under white labels, so the + * brand green sinks to L 0.42 and carries warm-white text at 7.5:1. */ + --primary: oklch(0.42 0.11 145); + --primary-foreground: oklch(0.975 0.012 110); + + --secondary: oklch(0.93 0.02 125); + --secondary-foreground: oklch(0.28 0.04 140); + + --muted: oklch(0.925 0.018 120); + --muted-foreground: oklch(0.45 0.03 140); + + /* Apricot — the citrus on the table. Tinted surface with a deep + * rust-apricot label; the one warm note in an otherwise green room. */ + --accent: oklch(0.92 0.045 60); + --accent-foreground: oklch(0.42 0.11 50); + + --destructive: oklch(0.55 0.2 25); + --destructive-foreground: oklch(0.975 0.012 110); + /* Success shifts to emerald (h160) so it reads as status, not brand — + * the fern primary sits at h145. Dark ink label on the bright green. */ + --success: oklch(0.66 0.13 160); + --success-foreground: oklch(0.15 0.03 160); + /* Warning — sun-through-glass amber, dark ink on top. */ + --warning: oklch(0.78 0.14 75); + --warning-foreground: oklch(0.25 0.05 70); + /* Info — clear-sky blue, warm-white label. */ + --info: oklch(0.52 0.12 235); + --info-foreground: oklch(0.975 0.012 110); + + --border: oklch(0.21 0.025 145 / 0.12); + --input: oklch(0.21 0.025 145 / 0.12); + --ring: oklch(0.42 0.11 145 / 0.5); + + /* Chart palette — fern / apricot / sky / orchid / citrus-gold. + * Hues ≥60° apart, adjacent-index lightness varied. */ + --chart-1: oklch(0.55 0.12 150); + --chart-2: oklch(0.72 0.14 60); + --chart-3: oklch(0.6 0.11 240); + --chart-4: oklch(0.68 0.11 320); + --chart-5: oklch(0.8 0.12 100); + + --sidebar: oklch(0.945 0.014 120); + --sidebar-foreground: oklch(0.21 0.025 145); + --sidebar-primary: oklch(0.42 0.11 145); + --sidebar-primary-foreground: oklch(0.975 0.012 110); + --sidebar-accent: oklch(0.9 0.03 125); + --sidebar-accent-foreground: oklch(0.28 0.04 140); + --sidebar-border: oklch(0.21 0.025 145 / 0.08); + --sidebar-ring: oklch(0.42 0.11 145 / 0.5); + + --syntax-keyword: oklch(0.42 0.11 145); + --syntax-string: oklch(0.48 0.11 50); + --syntax-number: oklch(0.52 0.12 235); + --syntax-comment: oklch(0.52 0.02 135); + --syntax-function: oklch(0.45 0.1 320); + --syntax-type: oklch(0.46 0.09 195); + + /* ── Chat surfaces ── + * Contract for lib-agent-chat-ui / lib-agent-dock-ui. User bubble is a + * filled fern panel with warm-white text; assistant bubble is a bright + * card surface with a hairline leaf-ink border. */ + --chat-user-bg: oklch(0.42 0.1 145); + --chat-user-fg: oklch(0.975 0.012 110); + --chat-assistant-bg: oklch(0.99 0.006 120); + --chat-assistant-fg: oklch(0.21 0.025 145); + --chat-assistant-border: oklch(0.21 0.025 145 / 0.12); + + /* Organic-large radius — softness is part of Solarium's voice. */ + --radius: 1.25rem; + + /* ── Motion ── buoyant but calm: unhurried base, decelerating entries, + * soft real springs (mild overshoot) for playful affordances. */ + --duration-fast: 140ms; + --duration-base: 300ms; + --duration-slow: 420ms; + --duration-slower: 560ms; + --duration-spring: 480ms; + + --ease-standard: cubic-bezier(0.3, 0, 0.2, 1); + --ease-decelerate: cubic-bezier(0.05, 0.7, 0.1, 1); + --ease-accelerate: cubic-bezier(0.3, 0, 0.8, 0.15); + + --ease-spring-gentle: linear(0, 0.04 8%, 0.12 15%, 0.23 22%, 0.37 30%, 0.51 38%, 0.63 46%, 0.74 54%, 0.83 62%, 0.89 70%, 0.94 78%, 0.97 86%, 0.99 94%, 1); + --ease-spring-snappy: linear(0, 0.06 6%, 0.18 12%, 0.33 18%, 0.5 25%, 0.65 32%, 0.8 40%, 0.9 48%, 0.98 56%, 1.015 64%, 1.02 72%, 1.015 80%, 1.005 88%, 1); + --ease-spring-bouncy: linear(0, 0.08 8%, 0.22 16%, 0.4 24%, 0.58 32%, 0.75 40%, 0.88 48%, 0.98 56%, 1.04 64%, 1.06 72%, 1.04 80%, 1.01 86%, 0.995 92%, 1); + + --ease-emphasized: var(--ease-spring-gentle); + + /* ── Typography ── organic-modern product register: fixed rem sizes, + * Gantari display over Figtree body. Generous line-heights — the room + * has air in it. */ + --text-hero-size: 3.25rem; + --text-hero-lh: 3.5rem; + --text-display-size: 2.75rem; + --text-display-lh: 3rem; + --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.75rem; + --text-body-size: 1rem; + --text-body-lh: 1.625rem; + --text-label-size: 0.875rem; + --text-label-lh: 1.25rem; + --text-caption-size: 0.75rem; + --text-caption-lh: 1.0625rem; + + /* ── Elevation ── soft diffuse daylight, green-tinted and airy — shade + * under a leaf, not a hard drop shadow. */ + --elevation-0: none; + --elevation-1: 0 1px 2px oklch(0.35 0.04 145 / 0.06); + --elevation-2: 0 2px 6px -1px oklch(0.35 0.04 145 / 0.08), 0 1px 2px oklch(0.35 0.04 145 / 0.05); + --elevation-3: 0 6px 16px -4px oklch(0.35 0.04 145 / 0.1), 0 2px 6px -2px oklch(0.35 0.04 145 / 0.06); + --elevation-4: 0 14px 32px -6px oklch(0.35 0.04 145 / 0.12), 0 4px 10px -4px oklch(0.35 0.04 145 / 0.06); + --elevation-5: 0 24px 56px -10px oklch(0.35 0.04 145 / 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 sunroom at night. Deep green-charcoal (never blue-gray), + * plants as silhouettes, apricot lamp glow. The primary brightens to a + * luminous fern carrying dark-ink labels. + */ +.dark { + --background: oklch(0.185 0.018 145); + --foreground: oklch(0.93 0.015 120); + + --card: oklch(0.245 0.02 145); + --card-foreground: oklch(0.93 0.015 120); + + --popover: oklch(0.255 0.02 145); + --popover-foreground: oklch(0.93 0.015 120); + + --primary: oklch(0.78 0.16 145); + --primary-foreground: oklch(0.16 0.05 145); + + --secondary: oklch(0.27 0.02 145); + --secondary-foreground: oklch(0.9 0.015 120); + + --muted: oklch(0.26 0.02 145); + --muted-foreground: oklch(0.72 0.025 130); + + /* Apricot lamp glow — deep warm surface, luminous apricot label. */ + --accent: oklch(0.32 0.05 55); + --accent-foreground: oklch(0.88 0.09 60); + + --destructive: oklch(0.68 0.19 28); + --destructive-foreground: oklch(0.14 0.03 25); + --success: oklch(0.72 0.15 160); + --success-foreground: oklch(0.15 0.04 160); + --warning: oklch(0.8 0.13 75); + --warning-foreground: oklch(0.2 0.05 70); + --info: oklch(0.72 0.12 235); + --info-foreground: oklch(0.15 0.04 235); + + --border: oklch(0.93 0.015 120 / 0.1); + --input: oklch(0.93 0.015 120 / 0.1); + --ring: oklch(0.78 0.16 145 / 0.5); + + --chart-1: oklch(0.72 0.14 150); + --chart-2: oklch(0.78 0.13 60); + --chart-3: oklch(0.7 0.11 240); + --chart-4: oklch(0.74 0.11 320); + --chart-5: oklch(0.84 0.11 100); + + --sidebar: oklch(0.2 0.018 145); + --sidebar-foreground: oklch(0.93 0.015 120); + --sidebar-primary: oklch(0.78 0.16 145); + --sidebar-primary-foreground: oklch(0.16 0.05 145); + --sidebar-accent: oklch(0.3 0.035 145); + --sidebar-accent-foreground: oklch(0.9 0.05 130); + --sidebar-border: oklch(0.93 0.015 120 / 0.08); + --sidebar-ring: oklch(0.78 0.16 145 / 0.5); + + --syntax-keyword: oklch(0.78 0.14 145); + --syntax-string: oklch(0.82 0.1 60); + --syntax-number: oklch(0.74 0.11 235); + --syntax-comment: oklch(0.6 0.02 135); + --syntax-function: oklch(0.76 0.1 320); + --syntax-type: oklch(0.76 0.08 195); + + /* ── Chat surfaces (dark) ── */ + --chat-user-bg: oklch(0.45 0.1 145); + --chat-user-fg: oklch(0.97 0.012 110); + --chat-assistant-bg: oklch(0.28 0.02 145); + --chat-assistant-fg: oklch(0.93 0.015 120); + --chat-assistant-border: oklch(0.93 0.015 120 / 0.1); + + --elevation-1: 0 1px 2px oklch(0 0 0 / 0.3); + --elevation-2: 0 2px 6px -1px oklch(0 0 0 / 0.38), 0 1px 2px oklch(0 0 0 / 0.25); + --elevation-3: 0 6px 16px -4px oklch(0 0 0 / 0.4), 0 2px 6px -2px oklch(0 0 0 / 0.28); + --elevation-4: 0 14px 32px -6px oklch(0 0 0 / 0.45), 0 4px 10px -4px oklch(0 0 0 / 0.28); + --elevation-5: 0 24px 56px -10px oklch(0 0 0 / 0.55); +} + +/* + * @layer base — Solarium's botanical philosophy. + * + * No glass, no hard edges. Rounded-humanist display type, generous + * line-height, big soft corners, and shadows that read as shade under a + * leaf. Cards sit on the leaf-lit ground as bright clean panes — a plant + * table by the window, not floating UI chrome. + */ +@layer base { + body { + min-height: 100vh; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + letter-spacing: -0.008em; + font-family: var(--font-sans); + font-size: var(--text-body-size); + line-height: var(--text-body-lh); + } + + /* Rounded-humanist display type — Gantari's open counters want a touch + * of negative tracking at display sizes and confident weight. */ + 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.018em; + 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 leaf-tinted chip; the fern 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 — apricot wash: the citrus note surfacing when you touch + * the text. */ + ::selection { + background: color-mix(in oklch, var(--accent-foreground) 24%, transparent); + color: var(--foreground); + } + + /* Anchor links — fern, 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 plus the soft daylight + * shadow; altitude comes from the elevation ladder, not decoration. */ + [data-slot="card"] { + background: var(--card); + border: var(--border-width-base) solid var(--border); + border-radius: var(--radius); + box-shadow: var(--elevation-1); + transition: box-shadow var(--duration-base) var(--ease-standard); + } + [data-slot="card"]:hover { + box-shadow: var(--elevation-2); + } + + [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"] { + border: var(--border-width-base) solid var(--border); + box-shadow: var(--elevation-3); + } + + [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-4); + } + + /* Buttons — soft weight, buoyant press. */ + [data-slot="button"] { + font-weight: 500; + transition: + opacity var(--duration-fast) var(--ease-standard), + transform var(--duration-fast) var(--ease-standard); + } + [data-slot="button"]:hover:not(:disabled) { + opacity: 0.92; + } + [data-slot="button"]:active:not(:disabled) { + opacity: 0.84; + transform: scale(0.985); + } + [data-slot="button"]:focus-visible { + outline: 2px solid var(--ring); + outline-offset: 2px; + } + + [data-slot="sidebar"] { + background: var(--sidebar); + border-right: var(--border-width-base) solid var(--sidebar-border); + } +} + +/* + * 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; + } +}