commit 07a721eba2f59fea0da857cce37c44422b50c1ab Author: jules Date: Tue Jul 7 22:56:29 2026 +1000 lib-theme-blueprint: initial import (calm-pro/dense-utility set, 2026-07) One of four divergent candidates for the operator/console theme family (successor to Skyrise for developer-product screens). Dense/pro type register, keyboard-first focus treatment, tabular numerals, coarse-pointer 44px touch floor, neutral code chips, dark card separation per house sweep. Co-Authored-By: Claude Fable 5 diff --git a/README.md b/README.md new file mode 100644 index 0000000..2a4c287 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# lib-theme-blueprint + +**Technical drawing.** A calm-pro / dense-utility theme for operator, console, +and admin surfaces. Light mode is drafting paper: a faint cool blue-grey field, +pure-white cards, and deep **navy ink** — separated by hairline rules, not +shadow. One azure accent (hue 232) owns primary, links, selection, and focus; +info status is pushed to violet (285) so the two never read as the same swatch. +Dark mode is the iconic inversion — **the dark mode is the blueprint**: a deep +drafting-blue field, near-white blue-tinted ink, and the accent turned luminous +cyan with dark ink. Radius is a sharp 0.375rem, motion is instrument-quick +(120/220/320/450ms, springs aliased), and tables render tabular numerals. + +## Fonts + +Consuming apps must load the fonts at the very top of their entry CSS +(`app/app.css`), before anything else: + +```css +@import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap"); +``` + +Per the Crema token contract, `theme.css` never `@import`s fonts itself. + +| Token | Stack | Role | +|---|---|---| +| `--font-heading` / `--heading-family` | Space Grotesk → Inter → system | Headings, card/dialog/sheet titles — geometric, technical | +| `--font-sans` / `--body-family` | Inter → system | Body, controls, dense UI | +| `--font-mono` | JetBrains Mono → ui-monospace | Code, kbd, terminal surfaces | + +## Usage + +```bash +crema theme blueprint +``` + +Or manually: point the theme `@import` in `app/app.css` at +`../../lib-theme-blueprint/theme.css` and add the Google Fonts `@import` above it. + +Validate with: + +```bash +node create-crema-app/scripts/validate-theme.mjs lib-theme-blueprint/theme.css +``` diff --git a/theme.css b/theme.css new file mode 100644 index 0000000..4b5f746 --- /dev/null +++ b/theme.css @@ -0,0 +1,420 @@ +/* + * Blueprint — technical drawing. + * + * Light-first: a near-achromatic drafting-paper field (whisper of cool at + * hue 240, chroma ≤0.005 — the paper is calm gray; the BLUE lives in the ink + * and the accent, never washed into the field) carrying pure-white cards, + * with deep NAVY ink — the ink is navy, that is the signature. Hairline RULES are the organizing metaphor: + * borders sit slightly more present than usual (ink at ~0.17 alpha) and + * elevation stays minimal — surfaces separate by rule lines like a technical + * drawing, not by shadow. One azure accent (hue ~232) owns primary, links, and + * focus; the info status is pushed to violet (hue 285) so the two never read + * as the same swatch. + * + * Dark mode is the iconic inversion: THE DARK MODE IS THE BLUEPRINT. A deep + * drafting-blue field (low-chroma navy, hue 258 — unmistakably blue-leaning + * but calm), near-white blue-tinted ink, cards stepping a full lightness step + * above the field, and the azure accent turned luminous cyan with dark ink. + * + * Type is technical: Space Grotesk for headings (geometric, drafting-title), + * Inter for body, JetBrains Mono for code. Dense/pro register — 0.9375rem + * body, fixed rem sizes. Radius is a sharp 0.375rem; motion is precise + * (120/220/320/450ms) with springs aliased to the standard eases. Tables get + * tabular numerals — drafting tables demand aligned digits. + * + * Token contract matches lib-theme-caffe-florian/theme.css (Crema v1). + */ + +/* + * Fonts: consuming apps load Space Grotesk + 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: "Space Grotesk", "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: "Space Grotesk", "Inter", -apple-system, BlinkMacSystemFont, sans-serif; + --body-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + + /* ── Colors ── cool drafting-paper field, pure-white cards, deep navy ink, + * one azure accent. Rules do the separating; shadow stays out of it. */ + --background: oklch(0.966 0.0035 240); + --foreground: oklch(0.23 0.045 258); + + --card: oklch(1 0 0); + --card-foreground: oklch(0.23 0.045 258); + + --popover: oklch(0.995 0.003 240); + --popover-foreground: oklch(0.23 0.045 258); + + /* Primary — the azure of the drawing's live line (hue 232, L0.49), deep + * enough to carry a near-white label at ~6:1. Spent on the primary action, + * links, selection, and focus; nowhere else. */ + --primary: oklch(0.49 0.13 232); + --primary-foreground: oklch(0.98 0.008 232); + + --secondary: oklch(0.93 0.005 240); + --secondary-foreground: oklch(0.29 0.04 258); + + --muted: oklch(0.93 0.0045 240); + --muted-foreground: oklch(0.45 0.03 255); + + /* Accent — hover/selection tint: a pale azure wash with deep azure ink, + * not a second competing fill. */ + --accent: oklch(0.925 0.028 232); + --accent-foreground: oklch(0.37 0.09 235); + + /* ── Status ── deep fills with light labels for red/violet, bright fills + * with dark ink for green/amber. Info is pushed to VIOLET (hue 285) so it + * can never be mistaken for the azure primary (hue 232). */ + --destructive: oklch(0.54 0.21 27); + --destructive-foreground: oklch(0.98 0.01 27); + --success: oklch(0.52 0.13 155); + --success-foreground: oklch(0.98 0.01 155); + --warning: oklch(0.76 0.15 78); + --warning-foreground: oklch(0.24 0.05 78); + --info: oklch(0.5 0.13 285); + --info-foreground: oklch(0.98 0.01 285); + + /* Hairline rules — the theme's organizing metaphor. Navy ink at a slightly + * higher alpha than house-usual so every rule reads on the paper. */ + --border: oklch(0.23 0.045 258 / 0.17); + --input: oklch(0.23 0.045 258 / 0.2); + --ring: oklch(0.49 0.13 232 / 0.6); + + /* Chart palette — azure lead, then spaced drafting-ink hues (violet, red, + * amber, teal-green); hues ≥60° apart, adjacent lightness varied. */ + --chart-1: oklch(0.55 0.13 235); + --chart-2: oklch(0.55 0.14 300); + --chart-3: oklch(0.62 0.17 25); + --chart-4: oklch(0.75 0.14 85); + --chart-5: oklch(0.66 0.11 165); + + --sidebar: oklch(0.945 0.004 240); + --sidebar-foreground: oklch(0.23 0.045 258); + --sidebar-primary: oklch(0.49 0.13 232); + --sidebar-primary-foreground: oklch(0.98 0.008 232); + --sidebar-accent: oklch(0.925 0.028 232); + --sidebar-accent-foreground: oklch(0.37 0.09 235); + --sidebar-border: oklch(0.23 0.045 258 / 0.13); + --sidebar-ring: oklch(0.49 0.13 232 / 0.6); + + /* Syntax — muted-precise: navy-family base with disciplined accents. */ + --syntax-keyword: oklch(0.44 0.12 285); + --syntax-string: oklch(0.45 0.1 165); + --syntax-number: oklch(0.48 0.11 60); + --syntax-comment: oklch(0.55 0.02 250); + --syntax-function: oklch(0.45 0.13 235); + --syntax-type: oklch(0.45 0.09 200); + + /* ── Chat surfaces ── + * Contract for lib-agent-chat-ui / lib-agent-dock-ui. User bubble is a + * deep navy-ink panel with light text (the drawing's title block); + * assistant bubble is a clean card with a hairline rule. */ + --chat-user-bg: oklch(0.28 0.05 258); + --chat-user-fg: oklch(0.97 0.006 240); + --chat-assistant-bg: oklch(1 0 0); + --chat-assistant-fg: oklch(0.23 0.045 258); + --chat-assistant-border: oklch(0.23 0.045 258 / 0.17); + + /* Sharp drafting radius. */ + --radius: 0.375rem; + + /* ── Motion ── precise, instrument-quick. Springs alias to the standard + * eases — a drawing never bounces. */ + --duration-fast: 120ms; + --duration-base: 220ms; + --duration-slow: 320ms; + --duration-slower: 450ms; + --duration-spring: 220ms; + + --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.2, 0, 0, 1); + + --ease-spring-gentle: var(--ease-standard); + --ease-spring-snappy: var(--ease-decelerate); + --ease-spring-bouncy: var(--ease-standard); + + /* ── Typography ── dense/pro register, fixed rem, full role ramp. */ + --text-hero-size: 3rem; + --text-hero-lh: 1.05; + --text-display-size: 2.25rem; + --text-display-lh: 1.1; + --text-headline-size: 1.625rem; + --text-headline-lh: 1.25; + --text-title-size: 1.25rem; + --text-title-lh: 1.35; + --text-subtitle-size: 1.0625rem; + --text-subtitle-lh: 1.45; + --text-body-size: 0.9375rem; + --text-body-lh: 1.5; + --text-label-size: 0.8125rem; + --text-label-lh: 1.125rem; + --text-caption-size: 0.75rem; + --text-caption-lh: 1.0625rem; + + /* ── Elevation ── minimal on purpose — rule lines do the separating. + * Popovers and modals get just enough lift to read as floating. */ + --elevation-0: none; + --elevation-1: 0 1px 1px oklch(0.23 0.045 258 / 0.04); + --elevation-2: 0 1px 2px oklch(0.23 0.045 258 / 0.06); + --elevation-3: 0 2px 6px -1px oklch(0.23 0.045 258 / 0.08), 0 1px 2px oklch(0.23 0.045 258 / 0.05); + --elevation-4: 0 8px 16px -4px oklch(0.23 0.045 258 / 0.1), 0 2px 4px -2px oklch(0.23 0.045 258 / 0.06); + --elevation-5: 0 16px 32px -8px oklch(0.23 0.045 258 / 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 — THE BLUEPRINT. Deep drafting-blue field (low-chroma navy, + * hue 258 — blue-leaning but calm, never electric), near-white blue-tinted + * ink, cards stepping a full lightness step above the field, and the accent + * turned luminous cyan carrying dark ink. + */ +.dark { + --background: oklch(0.21 0.035 258); + --foreground: oklch(0.95 0.012 240); + + --card: oklch(0.26 0.035 258); + --card-foreground: oklch(0.95 0.012 240); + + --popover: oklch(0.28 0.035 258); + --popover-foreground: oklch(0.95 0.012 240); + + /* Primary — the luminous cyan line on the blueprint, dark navy ink on + * top (bright-swatch/dark-ink pairing, ~9:1). */ + --primary: oklch(0.78 0.12 220); + --primary-foreground: oklch(0.2 0.04 230); + + --secondary: oklch(0.3 0.035 258); + --secondary-foreground: oklch(0.9 0.012 240); + + --muted: oklch(0.3 0.035 258); + --muted-foreground: oklch(0.72 0.02 245); + + --accent: oklch(0.34 0.06 230); + --accent-foreground: oklch(0.85 0.09 215); + + --destructive: oklch(0.55 0.2 27); + --destructive-foreground: oklch(0.98 0.01 27); + --success: oklch(0.72 0.14 155); + --success-foreground: oklch(0.18 0.04 155); + --warning: oklch(0.78 0.15 80); + --warning-foreground: oklch(0.22 0.05 80); + --info: oklch(0.7 0.12 285); + --info-foreground: oklch(0.17 0.04 285); + + /* Rules invert: light ink hairlines on the blueprint field. */ + --border: oklch(0.95 0.012 240 / 0.15); + --input: oklch(0.95 0.012 240 / 0.18); + --ring: oklch(0.78 0.12 220 / 0.55); + + --chart-1: oklch(0.72 0.12 220); + --chart-2: oklch(0.66 0.13 300); + --chart-3: oklch(0.7 0.16 25); + --chart-4: oklch(0.8 0.13 85); + --chart-5: oklch(0.74 0.11 165); + + --sidebar: oklch(0.23 0.035 258); + --sidebar-foreground: oklch(0.95 0.012 240); + --sidebar-primary: oklch(0.78 0.12 220); + --sidebar-primary-foreground: oklch(0.2 0.04 230); + --sidebar-accent: oklch(0.34 0.06 230); + --sidebar-accent-foreground: oklch(0.85 0.09 215); + --sidebar-border: oklch(0.95 0.012 240 / 0.11); + --sidebar-ring: oklch(0.78 0.12 220 / 0.55); + + --syntax-keyword: oklch(0.74 0.12 300); + --syntax-string: oklch(0.76 0.11 165); + --syntax-number: oklch(0.8 0.12 75); + --syntax-comment: oklch(0.62 0.02 250); + --syntax-function: oklch(0.76 0.11 220); + --syntax-type: oklch(0.74 0.09 195); + + /* ── Chat surfaces (dark) ── */ + --chat-user-bg: oklch(0.33 0.05 252); + --chat-user-fg: oklch(0.96 0.01 240); + --chat-assistant-bg: oklch(0.26 0.035 258); + --chat-assistant-fg: oklch(0.95 0.012 240); + --chat-assistant-border: oklch(0.95 0.012 240 / 0.15); + + --elevation-1: 0 1px 1px oklch(0 0 0 / 0.25); + --elevation-2: 0 1px 2px oklch(0 0 0 / 0.3); + --elevation-3: 0 2px 6px -1px oklch(0 0 0 / 0.35), 0 1px 2px oklch(0 0 0 / 0.2); + --elevation-4: 0 8px 16px -4px oklch(0 0 0 / 0.4), 0 2px 4px -2px oklch(0 0 0 / 0.22); + --elevation-5: 0 16px 32px -8px oklch(0 0 0 / 0.5); +} + +/* + * @layer base — Blueprint's precise, rule-lined drafting philosophy. + * + * No glass, no flourish. Geometric headings, hairline rules doing the + * separating, tabular digits in tables, and the azure line reserved for + * focus, selection, links, and the primary action. + */ +@layer base { + body { + min-height: 100vh; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + letter-spacing: -0.004em; + font-family: var(--font-sans); + font-size: var(--text-body-size); + line-height: var(--text-body-lh); + } + + /* Headings — Space Grotesk, geometric and technical. */ + 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.015em; + color: var(--foreground); + } + + code, kbd, samp, pre, + [data-slot="code"], + [data-slot="code-block"] { + font-family: var(--font-mono); + } + + /* Inline code — neutral ink chip, never accent-colored. */ + :not(pre) > code { + padding: 0.1em 0.35em; + border-radius: calc(var(--radius) * 0.5); + background: color-mix(in oklch, var(--foreground) 7%, transparent); + color: var(--foreground); + font-size: 0.9em; + } + + /* Selection — the azure line. */ + ::selection { + background: color-mix(in oklch, var(--primary) 28%, transparent); + color: var(--foreground); + } + + /* Links — azure, underline-on-hover. */ + a { + color: color-mix(in oklch, var(--primary) 75%, var(--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); + } + + [data-slot="sidebar"] { + background: var(--sidebar); + border-right: var(--border-width-base) solid var(--sidebar-border); + } + + /* Drafting tables demand aligned digits — tabular numerals on every + * table, and a .num utility for any numeric cell/stat elsewhere. */ + [data-slot="table"], + .num { + font-variant-numeric: tabular-nums; + } +} + +/* + * Focus-visible — keyboard-first is a Blueprint signature. A crisp, + * always-visible azure outline, offset (not inset). 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 — quiet weight; opacity absorb on press, instrument-quick. */ +[data-slot="button"] { + font-weight: 500; + letter-spacing: -0.004em; +} + +/* 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 dense drafting 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.9; +} +[data-slot="button"]:active:not(:disabled) { + opacity: 0.82; + transform: scale(0.99); +} + +/* Inputs — clean card surface, hairline rule, azure focus as a flush + * 1px ring plus border-color change. */ +[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(--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: var(--muted-foreground); + opacity: 0.8; +} +[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) 30%, 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; +}