From d7565f448a756c9c5bc879a5ac4cd5a315538acd Mon Sep 17 00:00:00 2001 From: jules Date: Tue, 7 Jul 2026 23:20:55 +1000 Subject: [PATCH] lib-theme-grape: 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 --- README.md | 62 ++++++++ theme.css | 431 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 493 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..01cb811 --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# lib-theme-grape + +**Grape — violet night-lounge.** A bold, identity-forward Crema theme where the +committed color IS the surface: the entire world is a deep luminous +grape-violet field (hue 300) — Skyrise's violet ancestry taken to a full +drench, without glass or iridescence. There are no white cards; cards are +clearly-lighter violet panels lifted off the field, popovers a step above +cards, the sidebar a deeper violet stripe, and everything separates with +white-alpha hairlines — light, not darkness. Ink is near-white with a violet +tint. + +One counterpoint does all the pointing: a hot coral-pink primary +(`oklch(0.7 0.19 20)`) carrying dark wine ink, spent on the action, the +selection, the focus ring, and the user's chat bubble. Destructive stays a +*deep crimson fill with a light label* — a different lightness class and +construction from the bright coral primary, so the two hue-20 neighbours never +read as the same swatch. Success is mint, warning amber, info cool cyan — all +bright swatches with dark ink, tuned to read on violet panels. + +The theme is violet in **both** modes. `.dark` is simply *later that night*: +the field deepens (`0.38 → 0.26 L`), the panels keep the same visible step +above it, and the coral burns slightly brighter. The dark violet ladder keeps +muted above popover: bg `0.26` → card `0.32` → popover `0.34` → muted `0.36`. + +Radius is a soft-lounge `1.125rem`. Motion is languid-confident +(160/320/480/640ms) with a real gentle spring on the emphasized ease. Shadows +are deep violet-black pools. Consumer type register: 1rem body at 1.6 +line-height. + +All contract contrast floors verified in both modes on both surfaces (field +and card) — including the risky one in an all-violet world, +`muted-foreground`, which lands ≥5:1 on field, card, and the muted wash in +both modes. `✓ PASS` from `validate-theme.mjs`. + +## Fonts + +Load in the **consuming app's** entry CSS (very top, before anything else — +themes never `@import` fonts themselves): + +```css +@import url("https://fonts.googleapis.com/css2?family=Instrument+Serif&family=Instrument+Sans:wght@400;500;600;700&family=Geist+Mono:wght@400;500;600&display=swap"); +``` + +| Role | Family | Notes | +|---|---|---| +| Headings | **Instrument Serif** | Single weight (400) — the serif itself is the emphasis; the theme pins headings to `font-weight: 400; letter-spacing: -0.01em` and never fakes bold. | +| Body / UI | **Instrument Sans** | 400 / 500 / 600 / 700. Warm modern grotesque; buttons sit at 500. | +| Code | **Geist Mono** | 400 / 500 / 600. Inline code renders as a white-alpha tint chip with foreground ink. | + +## Usage + +```css +/* app entry CSS */ +@import url("https://fonts.googleapis.com/css2?family=Instrument+Serif&family=Instrument+Sans:wght@400;500;600;700&family=Geist+Mono:wght@400;500;600&display=swap"); +@import "../../lib-theme-grape/theme.css"; +``` + +Validate: + +```bash +node create-crema-app/scripts/validate-theme.mjs lib-theme-grape/theme.css +``` diff --git a/theme.css b/theme.css new file mode 100644 index 0000000..15b52d2 --- /dev/null +++ b/theme.css @@ -0,0 +1,431 @@ +/* + * Grape — violet night-lounge. + * + * A committed, identity-forward theme: the surface itself is the brand. The + * whole world is a deep luminous grape-violet FIELD (hue 300) — Skyrise's + * violet ancestry taken to a full drench, with no glass and no iridescence. + * There are no white cards anywhere: cards are clearly-lighter violet panels + * lifted off the field, popovers a step above cards, the sidebar a deeper + * violet stripe. Ink is near-white with a violet tint; hairlines are + * white-alpha. The one action glows: a hot coral-pink primary (hue 20) with + * dark ink on top, the single warm counterpoint in an otherwise violet room. + * + * Dark mode is later that night — the same lounge with the lights turned + * down: the field deepens, the panels follow, and the coral burns slightly + * brighter against it. The theme is violet in BOTH modes; .dark only changes + * the hour. + * + * Status colors are tuned to read on violet panels: destructive stays a deep + * crimson fill with a light label (a different lightness class from the + * bright coral primary, so the two hue-20 swatches never read as siblings); + * success is mint, warning amber, info cool cyan — all bright swatches with + * dark ink. + * + * Type is a lounge voice: Instrument Serif (single weight 400) for headings, + * Instrument Sans for body, Geist Mono for code. Consumer register — 1rem + * body with a generous 1.6 line-height. Radius is a soft 1.125rem; motion is + * languid-confident with a real gentle spring on the emphasized ease; shadows + * are deep violet-black pools. + * + * Token contract matches lib-theme-caffe-florian/theme.css (Crema v1). + */ + +/* + * Fonts: consuming apps load Instrument Serif + Instrument Sans + Geist 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: "Instrument Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + --font-heading: "Instrument Serif", Georgia, "Times New Roman", serif; + --font-mono: "Geist 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: "Instrument Serif", Georgia, "Times New Roman", serif; + --body-family: "Instrument Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + + /* ── Colors ── the grape field. Background is a deep luminous violet; + * cards are clearly-lighter violet panels (+0.06 L — the step must be + * visible); ink is near-white with a violet tint. NOT white cards — the + * whole world is violet. */ + --background: oklch(0.38 0.13 300); + --foreground: oklch(0.97 0.012 300); + + --card: oklch(0.44 0.12 300); + --card-foreground: oklch(0.97 0.012 300); + + --popover: oklch(0.46 0.115 300); + --popover-foreground: oklch(0.97 0.012 300); + + /* Primary — the hot coral-pink counterpoint. One warm glow against the + * violet, carrying a deep wine-dark ink (bright-swatch/dark-ink pairing, + * ~6.3:1) so the action reads at label sizes. */ + --primary: oklch(0.7 0.19 20); + --primary-foreground: oklch(0.2 0.06 20); + + --secondary: oklch(0.5 0.11 300); + --secondary-foreground: oklch(0.97 0.012 300); + + /* Muted — a recessed violet wash sitting between field and card, so muted + * chrome sinks rather than lifts. Muted ink is a pale lavender, verified + * ≥4.5:1 on field, card, AND the muted wash itself. */ + --muted: oklch(0.42 0.115 300); + --muted-foreground: oklch(0.85 0.045 300); + + /* Accent — a lighter violet wash with near-white ink: hover/selection + * lift inside the violet family, never a second warm fill. */ + --accent: oklch(0.52 0.12 300); + --accent-foreground: oklch(0.98 0.01 300); + + /* ── Status ── tuned to read on violet panels. All four are BRIGHT + * swatches with dark ink: on a mid-lightness violet world, status-colored + * text (badge chips render `text-destructive` on a 10% tint) can only + * clear 4.5:1 if the status color itself is light. Destructive is a + * luminous rose (hue 5) — separated from the coral primary (hue 20) by + * hue and by a visibly higher lightness. */ + --destructive: oklch(0.84 0.11 5); + --destructive-foreground: oklch(0.2 0.04 5); + --success: oklch(0.75 0.13 160); + --success-foreground: oklch(0.2 0.05 160); + --warning: oklch(0.8 0.13 85); + --warning-foreground: oklch(0.22 0.05 85); + --info: oklch(0.75 0.1 220); + --info-foreground: oklch(0.2 0.04 220); + + /* White-alpha hairlines — the violet world separates with light, not + * darkness. */ + --border: oklch(1 0 0 / 0.16); + --input: oklch(1 0 0 / 0.2); + --ring: oklch(0.7 0.19 20 / 0.6); + + /* Chart palette — coral lead, then mint / amber / cyan / pale orchid: + * spaced hues that all read on violet panels, adjacent-index lightness + * varied. */ + --chart-1: oklch(0.72 0.18 20); + --chart-2: oklch(0.78 0.13 160); + --chart-3: oklch(0.85 0.12 85); + --chart-4: oklch(0.72 0.11 220); + --chart-5: oklch(0.86 0.08 320); + + /* Sidebar — a deeper violet stripe against the field. */ + --sidebar: oklch(0.33 0.12 300); + --sidebar-foreground: oklch(0.97 0.012 300); + --sidebar-primary: oklch(0.7 0.19 20); + --sidebar-primary-foreground: oklch(0.2 0.06 20); + --sidebar-accent: oklch(0.44 0.12 300); + --sidebar-accent-foreground: oklch(0.98 0.01 300); + --sidebar-border: oklch(1 0 0 / 0.12); + --sidebar-ring: oklch(0.7 0.19 20 / 0.6); + + /* Syntax — bright, tuned to read on violet card panels. */ + --syntax-keyword: oklch(0.82 0.12 350); + --syntax-string: oklch(0.85 0.1 160); + --syntax-number: oklch(0.87 0.1 85); + --syntax-comment: oklch(0.76 0.04 300); + --syntax-function: oklch(0.83 0.09 220); + --syntax-type: oklch(0.88 0.06 300); + + /* ── Chat surfaces ── + * Contract for lib-agent-chat-ui / lib-agent-dock-ui. User bubble is the + * coral glow with dark ink (the user's turn IS the action); assistant + * bubble is a violet card panel with a white-alpha hairline. */ + --chat-user-bg: oklch(0.7 0.19 20); + --chat-user-fg: oklch(0.2 0.06 20); + --chat-assistant-bg: oklch(0.44 0.12 300); + --chat-assistant-fg: oklch(0.97 0.012 300); + --chat-assistant-border: oklch(1 0 0 / 0.16); + + /* Soft-lounge radius. */ + --radius: 1.125rem; + + /* ── Motion ── languid-confident. Slower than a product theme on + * purpose — the lounge doesn't rush — with a real gentle spring on the + * emphasized ease and the spring family. */ + --duration-fast: 160ms; + --duration-base: 320ms; + --duration-slow: 480ms; + --duration-slower: 640ms; + --duration-spring: 480ms; + + --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.34, 1.24, 0.54, 1); + + --ease-spring-gentle: cubic-bezier(0.38, 1.18, 0.58, 1); + --ease-spring-snappy: cubic-bezier(0.3, 1.35, 0.5, 1); + --ease-spring-bouncy: cubic-bezier(0.3, 1.55, 0.42, 1); + + /* ── Typography ── consumer register, fixed rem, full role ramp. */ + --text-hero-size: 3.5rem; + --text-hero-lh: 1.05; + --text-display-size: 2.75rem; + --text-display-lh: 1.1; + --text-headline-size: 2rem; + --text-headline-lh: 1.2; + --text-title-size: 1.5rem; + --text-title-lh: 1.3; + --text-subtitle-size: 1.1875rem; + --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.0625rem; + + /* ── Elevation ── deep violet-black pools; on a dark field the shadows + * carry more alpha than a daylight theme's. */ + --elevation-0: none; + --elevation-1: 0 1px 2px oklch(0.12 0.06 300 / 0.3); + --elevation-2: 0 2px 4px oklch(0.12 0.06 300 / 0.34), 0 1px 2px oklch(0.12 0.06 300 / 0.24); + --elevation-3: 0 6px 12px -2px oklch(0.12 0.06 300 / 0.38), 0 2px 4px -2px oklch(0.12 0.06 300 / 0.26); + --elevation-4: 0 14px 28px -6px oklch(0.1 0.05 300 / 0.44), 0 4px 10px -4px oklch(0.1 0.05 300 / 0.28); + --elevation-5: 0 24px 48px -8px oklch(0.1 0.05 300 / 0.55); + + --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 — later that night. The field deepens toward violet-black, the + * panels keep the same visible step above it, and the coral counterpoint + * burns slightly brighter. The ladder keeps muted ABOVE popover + * (bg 0.26 → card 0.32 → popover 0.34 → muted 0.36) so muted chrome still + * reads as a lift on the darker field. + */ +.dark { + --background: oklch(0.26 0.1 300); + --foreground: oklch(0.97 0.012 300); + + --card: oklch(0.32 0.1 300); + --card-foreground: oklch(0.97 0.012 300); + + --popover: oklch(0.34 0.1 300); + --popover-foreground: oklch(0.97 0.012 300); + + --primary: oklch(0.73 0.19 20); + --primary-foreground: oklch(0.2 0.06 20); + + --secondary: oklch(0.36 0.1 300); + --secondary-foreground: oklch(0.97 0.012 300); + + --muted: oklch(0.36 0.1 300); + --muted-foreground: oklch(0.83 0.05 300); + + --accent: oklch(0.4 0.11 300); + --accent-foreground: oklch(0.98 0.01 300); + + --destructive: oklch(0.84 0.11 5); + --destructive-foreground: oklch(0.2 0.04 5); + --success: oklch(0.77 0.13 160); + --success-foreground: oklch(0.2 0.05 160); + --warning: oklch(0.82 0.13 85); + --warning-foreground: oklch(0.22 0.05 85); + --info: oklch(0.77 0.1 220); + --info-foreground: oklch(0.2 0.04 220); + + --border: oklch(1 0 0 / 0.14); + --input: oklch(1 0 0 / 0.18); + --ring: oklch(0.73 0.19 20 / 0.6); + + --chart-1: oklch(0.74 0.18 20); + --chart-2: oklch(0.8 0.13 160); + --chart-3: oklch(0.87 0.12 85); + --chart-4: oklch(0.74 0.11 220); + --chart-5: oklch(0.88 0.08 320); + + --sidebar: oklch(0.22 0.09 300); + --sidebar-foreground: oklch(0.97 0.012 300); + --sidebar-primary: oklch(0.73 0.19 20); + --sidebar-primary-foreground: oklch(0.2 0.06 20); + --sidebar-accent: oklch(0.32 0.1 300); + --sidebar-accent-foreground: oklch(0.98 0.01 300); + --sidebar-border: oklch(1 0 0 / 0.1); + --sidebar-ring: oklch(0.73 0.19 20 / 0.6); + + --syntax-keyword: oklch(0.84 0.12 350); + --syntax-string: oklch(0.87 0.1 160); + --syntax-number: oklch(0.89 0.1 85); + --syntax-comment: oklch(0.74 0.04 300); + --syntax-function: oklch(0.85 0.09 220); + --syntax-type: oklch(0.9 0.06 300); + + /* ── Chat surfaces (dark) ── */ + --chat-user-bg: oklch(0.73 0.19 20); + --chat-user-fg: oklch(0.2 0.06 20); + --chat-assistant-bg: oklch(0.32 0.1 300); + --chat-assistant-fg: oklch(0.97 0.012 300); + --chat-assistant-border: oklch(1 0 0 / 0.14); + + --elevation-1: 0 1px 2px oklch(0 0 0 / 0.35); + --elevation-2: 0 2px 4px oklch(0 0 0 / 0.4), 0 1px 2px oklch(0 0 0 / 0.28); + --elevation-3: 0 6px 12px -2px oklch(0 0 0 / 0.45), 0 2px 4px -2px oklch(0 0 0 / 0.3); + --elevation-4: 0 14px 28px -6px oklch(0 0 0 / 0.5), 0 4px 10px -4px oklch(0 0 0 / 0.3); + --elevation-5: 0 24px 48px -8px oklch(0 0 0 / 0.6); +} + +/* + * @layer base — Grape's night-lounge philosophy. + * + * One drenched violet field, panels lifted with light (white-alpha hairlines + * and lighter violet), a serif voice for headings, and the coral glow spent + * only on the action, the selection, and the focus ring. Nothing rushes. + */ +@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 — Instrument Serif, a single weight (400): the serif itself + * is the emphasis, so no faux-bold. */ + 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: 400; + letter-spacing: -0.01em; + /* 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 — a white-alpha tint chip with foreground ink, neutral + * inside the violet world. */ + :not(pre) > code { + padding: 0.1em 0.35em; + border-radius: calc(var(--radius) * 0.5); + background: oklch(1 0 0 / 0.12); + color: var(--foreground); + font-size: 0.9em; + } + + /* Selection — the coral glow. */ + ::selection { + background: color-mix(in oklch, var(--primary) 40%, transparent); + color: var(--foreground); + } + + /* Links — coral-warmed, underline-on-hover. */ + a { + color: color-mix(in oklch, var(--primary) 75%, var(--foreground)); + text-decoration-color: color-mix(in oklch, var(--primary) 45%, 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); + } +} + +/* + * Focus-visible — the coral 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; opacity absorb on press with a languid ease. */ +[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 lounge 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.99); +} + +/* Inputs — card-violet panels, white-alpha hairline, coral focus as a flush + * ring. */ +[data-slot="input"], +[data-slot="textarea"], +[data-slot="select-trigger"] { + background: var(--card); + border: var(--border-width-base) 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="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: oklch(1 0 0 / 0.32); +} +[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; +}