lib-theme-lantern: initial import (access-first set, 2026-07)
One of three candidates for the access-first / maximum-legibility theme family (WCAG AAA as identity): 7:1+ contrast on every pair (passes validate-theme.mjs --aaa), 1.125rem body floor with nothing under 14px, 44px controls for everyone, 3px focus rings, links underlined at rest, reduced-motion built in. Built for IGH/NDIS-facing surfaces first. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
85
README.md
Normal file
85
README.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# lib-theme-lantern
|
||||
|
||||
**Warm paper, kitchen table not clinic.**
|
||||
|
||||
Lantern is one of three divergent candidates for Crema's access-first theme
|
||||
family: WCAG AAA as identity, not compliance. Its thesis is that maximum
|
||||
legibility can feel warm and human — a document you read at the kitchen table
|
||||
with someone you trust, not a form handed across a clinic counter. Built for
|
||||
NDIS participants, their families, and older support coordinators (the
|
||||
IGH/IveGotHome portals): dignity, clarity, zero squinting. The discipline IS
|
||||
the personality.
|
||||
|
||||
## The idea
|
||||
|
||||
- **Light mode** — warm ivory paper (`oklch(0.97 0.012 85)`), warm-white cards,
|
||||
near-black warm umber ink at **17.8:1**. The surface is deliberately high in
|
||||
lightness so 7:1 ink is easy everywhere and nothing has to shout.
|
||||
- **Primary** — a deep amber-brown gold (`oklch(0.4 0.11 70)`): a lantern's
|
||||
brass, not a yellow hazard. Warm-white label at **9.0:1**. Links share the
|
||||
same gold and are **underlined by default** (thicker on hover).
|
||||
- **Dark mode** — the lantern lit: warm dark umber (`oklch(0.17 0.012 60)`),
|
||||
warm-white ink at **16.5:1**, and the gold turned luminous
|
||||
(`oklch(0.78 0.13 80)`) with a black-umber label at **9.7:1**.
|
||||
- **Type** — Lexend for body AND headings (weight 600 headings). Lexend was
|
||||
designed to improve reading proficiency; here it earns its keep. JetBrains
|
||||
Mono for code.
|
||||
|
||||
## Access-family contract (shared DNA)
|
||||
|
||||
- Type ramp starts larger: body 1.125rem/1.65, label 1rem/1.5, caption
|
||||
0.875rem/1.4 (never below 14px). Generous line-heights throughout.
|
||||
- Body ink and `muted-foreground` ≥ 7:1 on background AND card, both modes.
|
||||
Primary label ≥ 7:1 on primary. Status label pairs all ≥ 7:1.
|
||||
- Focus: 3px solid outline + 2px offset on buttons; inputs get a border-color
|
||||
change plus a 2px ring. Always visible.
|
||||
- Touch targets: buttons and inputs min-height 2.75rem **globally**, not just
|
||||
behind a coarse-pointer media query.
|
||||
- Borders strong — ink at 0.25–0.32 alpha. Boundaries, not hairlines
|
||||
(`--border-width-base` is 1.5px; no sub-pixel widths).
|
||||
- Radius soft but modest (0.625rem). Elevation low; borders do the separating.
|
||||
- Motion minimal and quick (120/200/300/400ms); all springs alias the standard
|
||||
ease; `prefers-reduced-motion: reduce` zeroes transitions on interactive
|
||||
slots. Nothing essential is motion-only.
|
||||
|
||||
## Verified contrast (WCAG 2.1 relative luminance, exact oklch→sRGB)
|
||||
|
||||
| Pair | Light | Dark |
|
||||
|---|---|---|
|
||||
| foreground / background | 17.81:1 | 16.54:1 |
|
||||
| foreground / card | 19.14:1 | 14.98:1 |
|
||||
| muted-foreground / background | 9.99:1 | 9.55:1 |
|
||||
| muted-foreground / card | 10.73:1 | 8.65:1 |
|
||||
| muted-foreground / muted | 8.86:1 | 7.54:1 |
|
||||
| primary-foreground / primary | 9.00:1 | 9.73:1 |
|
||||
| status labels / fills (worst) | 8.09:1 | 7.40:1 |
|
||||
|
||||
## Fonts
|
||||
|
||||
Consuming apps load fonts at the very top of their entry CSS (never inside
|
||||
`theme.css` — see the token contract):
|
||||
|
||||
```css
|
||||
@import url("https://fonts.googleapis.com/css2?family=Lexend:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap");
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
node create-crema-app/scripts/validate-theme.mjs lib-theme-lantern/theme.css
|
||||
# ✓ PASS — all mandatory tokens present, contrast floors met, no ban violations.
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Warning is a deep amber-**orange** (hue 55, L 0.42) in light mode — well
|
||||
separated from the hue-70/80 brass so the two never read as one swatch, and
|
||||
dark enough that it never becomes a pale hazard stripe.
|
||||
- Dark-mode surface ladder: background 0.17 → sidebar 0.20 → card 0.22 →
|
||||
popover 0.245 → muted/secondary 0.27 (popover above card, below muted).
|
||||
- Chart hues 80/150/220/290/20 (≥60° apart), adjacent lightness varied.
|
||||
- Chat: user bubble is a deep warm-umber panel (13:1 light / 11:1 dark);
|
||||
assistant bubble is a card with the strong border.
|
||||
- Headings use `color: inherit` so titles inside primary buttons, status
|
||||
banners, and chat bubbles keep their surface's ink.
|
||||
- No glass — inert stubs (`0px / 100% / 1 / 1`).
|
||||
454
theme.css
Normal file
454
theme.css
Normal file
@@ -0,0 +1,454 @@
|
||||
/*
|
||||
* Lantern — warm paper, kitchen table not clinic.
|
||||
*
|
||||
* An access-first theme: WCAG AAA as identity, not compliance. Lantern is the
|
||||
* proof that AAA can feel warm and human. The surface is warm ivory paper —
|
||||
* high lightness on purpose, so 7:1 ink is easy and nothing has to shout.
|
||||
* Ink is a near-black warm umber (~15:1 on paper). The single accent is a deep
|
||||
* amber-brown gold — a lantern's brass, not a yellow hazard — carrying a
|
||||
* warm-white label well above 7:1. Links share the same deep gold and are
|
||||
* underlined by default. Built for people who deserve dignity, clarity, and
|
||||
* zero squinting: NDIS participants, their families, older support
|
||||
* coordinators. The discipline IS the personality.
|
||||
*
|
||||
* Dark mode is the lantern lit: warm dark umber, warm-white ink, and the gold
|
||||
* turned luminous with a black-umber label on top (bright-swatch/dark-ink, the
|
||||
* high-contrast direction).
|
||||
*
|
||||
* Family DNA (shared across the access candidates):
|
||||
* - Type ramp starts larger (body 1.125rem/1.65); captions never below 14px.
|
||||
* - Body ink AND muted-foreground >= 7:1 on background AND card, both modes.
|
||||
* - Primary label >= 7:1 on primary; status labels >= 7:1 where feasible.
|
||||
* - Focus: 3px solid outline + 2px offset; inputs get border change + ring.
|
||||
* - Links underlined by default; underline thickens on hover.
|
||||
* - Touch targets: 2.75rem min-height on buttons/inputs GLOBALLY.
|
||||
* - Borders strong (ink at ~0.25-0.3 alpha) — boundaries, not decoration.
|
||||
* - Motion minimal and quick; springs alias standard; reduced-motion honored.
|
||||
*
|
||||
* Computed contrast (exact WCAG 2.1 relative-luminance math, oklch -> sRGB;
|
||||
* sketched first with Y ~ L^3, then verified with the validator's converter):
|
||||
* LIGHT
|
||||
* foreground L0.16 on background L0.97 ..... 17.81:1
|
||||
* foreground L0.16 on card L0.995 .......... 19.14:1
|
||||
* muted-foreground L0.36 on background ..... 9.99:1
|
||||
* muted-foreground L0.36 on card ........... 10.73:1
|
||||
* muted-foreground L0.36 on muted L0.93 .... 8.86:1
|
||||
* primary-foreground L0.985 on primary L0.40 9.00:1
|
||||
* status labels L0.985 on L0.40-0.42 fills . 8.09-8.77:1
|
||||
* DARK
|
||||
* foreground L0.95 on background L0.17 ..... 16.54:1
|
||||
* foreground L0.95 on card L0.22 ........... 14.98:1
|
||||
* muted-foreground L0.78 on background ..... 9.55:1
|
||||
* muted-foreground L0.78 on card ........... 8.65:1
|
||||
* muted-foreground L0.78 on muted L0.27 .... 7.54:1
|
||||
* primary-foreground L0.15 on primary L0.78 9.73:1
|
||||
* status inks L0.16 on L0.72-0.80 fills .... 7.40-10.22:1
|
||||
*
|
||||
* Token contract: Crema v1 (create-crema-app/TOKEN_CONTRACT.md).
|
||||
*/
|
||||
|
||||
/*
|
||||
* Fonts: consuming apps load Lexend (400/500/600/700) + JetBrains Mono
|
||||
* (400/500/600) at the top of their entry CSS via the URL documented in this
|
||||
* theme's README. Lexend is chosen deliberately — it was designed to improve
|
||||
* reading proficiency — and serves body AND headings. No @import here (PostCSS
|
||||
* ordering) and no URL smuggled in comments.
|
||||
*/
|
||||
|
||||
:root {
|
||||
--font-sans: "Lexend", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--font-heading: "Lexend", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
||||
|
||||
/* Tailwind utility families — mirror --font-heading/--font-sans. */
|
||||
--heading-family: "Lexend", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--body-family: "Lexend", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
|
||||
/* ── Colors ── warm ivory paper, warm-white cards, near-black warm ink.
|
||||
* High-lightness surface keeps every 7:1 pair easy. */
|
||||
--background: oklch(0.97 0.012 85);
|
||||
--foreground: oklch(0.16 0.01 60); /* ~17.8:1 on background */
|
||||
|
||||
--card: oklch(0.995 0.006 85);
|
||||
--card-foreground: oklch(0.16 0.01 60); /* ~19.1:1 on card */
|
||||
|
||||
--popover: oklch(0.99 0.007 85);
|
||||
--popover-foreground: oklch(0.16 0.01 60);
|
||||
|
||||
/* Primary — the lantern's brass: deep amber-brown gold with a warm-white
|
||||
* label. L0.40 fill vs L0.985 label ~8.8:1 (>= 7:1 AAA). */
|
||||
--primary: oklch(0.4 0.11 70);
|
||||
--primary-foreground: oklch(0.985 0.008 85);
|
||||
|
||||
--secondary: oklch(0.91 0.025 80);
|
||||
--secondary-foreground: oklch(0.25 0.03 65); /* ~12.2:1 on secondary */
|
||||
|
||||
/* Muted stays light so muted-foreground can hold 7:1 on it too. */
|
||||
--muted: oklch(0.93 0.015 83);
|
||||
--muted-foreground: oklch(0.36 0.015 65); /* ~9.96:1 bg / ~10.7:1 card / ~8.8:1 muted */
|
||||
|
||||
/* Accent — warm gold wash for hover/selection with deep gold-brown ink. */
|
||||
--accent: oklch(0.9 0.055 85);
|
||||
--accent-foreground: oklch(0.33 0.09 70); /* ~9.1:1 on accent */
|
||||
|
||||
/* ── Status ── deep fills carrying warm-white labels, all >= 7:1.
|
||||
* Warning is pulled to deep amber-ORANGE (hue 55) and darker than any
|
||||
* yellow so it never reads as a pale hazard stripe. */
|
||||
--destructive: oklch(0.42 0.16 27);
|
||||
--destructive-foreground: oklch(0.985 0.008 27); /* ~8.1:1 */
|
||||
--success: oklch(0.4 0.1 150);
|
||||
--success-foreground: oklch(0.985 0.008 150); /* ~8.8:1 */
|
||||
--warning: oklch(0.42 0.12 55);
|
||||
--warning-foreground: oklch(0.985 0.008 55); /* ~8.1:1 */
|
||||
--info: oklch(0.42 0.1 250);
|
||||
--info-foreground: oklch(0.985 0.008 250); /* ~8.1:1 */
|
||||
|
||||
/* Borders STRONG — ink at 0.25-0.3 alpha. Visible boundaries. */
|
||||
--border: oklch(0.16 0.01 60 / 0.27);
|
||||
--input: oklch(0.16 0.01 60 / 0.32);
|
||||
--ring: oklch(0.4 0.11 70);
|
||||
|
||||
/* Charts — hues >= 60 degrees apart (80/150/220/290/20), adjacent
|
||||
* lightness varied, all deep enough to sit legibly on paper. */
|
||||
--chart-1: oklch(0.55 0.12 80);
|
||||
--chart-2: oklch(0.45 0.1 150);
|
||||
--chart-3: oklch(0.6 0.1 220);
|
||||
--chart-4: oklch(0.45 0.12 290);
|
||||
--chart-5: oklch(0.52 0.14 20);
|
||||
|
||||
/* Sidebar — slightly deeper paper, same ink, same brass. */
|
||||
--sidebar: oklch(0.945 0.014 84);
|
||||
--sidebar-foreground: oklch(0.16 0.01 60);
|
||||
--sidebar-primary: oklch(0.4 0.11 70);
|
||||
--sidebar-primary-foreground: oklch(0.985 0.008 85);
|
||||
--sidebar-accent: oklch(0.9 0.055 85);
|
||||
--sidebar-accent-foreground: oklch(0.33 0.09 70);
|
||||
--sidebar-border: oklch(0.16 0.01 60 / 0.22);
|
||||
--sidebar-ring: oklch(0.4 0.11 70);
|
||||
|
||||
/* Syntax — deep, calm hues on the paper; comments stay >= 7:1 too. */
|
||||
--syntax-keyword: oklch(0.42 0.12 290);
|
||||
--syntax-string: oklch(0.42 0.1 150);
|
||||
--syntax-number: oklch(0.44 0.12 55);
|
||||
--syntax-comment: oklch(0.44 0.015 65);
|
||||
--syntax-function: oklch(0.42 0.1 250);
|
||||
--syntax-type: oklch(0.42 0.08 200);
|
||||
|
||||
/* ── Chat surfaces ──
|
||||
* User bubble is a deep warm-umber panel with warm-white text (~13:1);
|
||||
* assistant bubble is a warm-white card with a strong border. */
|
||||
--chat-user-bg: oklch(0.3 0.05 65);
|
||||
--chat-user-fg: oklch(0.985 0.008 85);
|
||||
--chat-assistant-bg: oklch(0.995 0.006 85);
|
||||
--chat-assistant-fg: oklch(0.16 0.01 60);
|
||||
--chat-assistant-border: oklch(0.16 0.01 60 / 0.27);
|
||||
|
||||
/* Soft but modest. */
|
||||
--radius: 0.625rem;
|
||||
|
||||
/* ── Motion ── minimal and quick; nothing essential is motion-only.
|
||||
* Springs alias to standard — calm by construction. */
|
||||
--duration-fast: 120ms;
|
||||
--duration-base: 200ms;
|
||||
--duration-slow: 300ms;
|
||||
--duration-slower: 400ms;
|
||||
--duration-spring: 200ms;
|
||||
|
||||
--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-standard);
|
||||
--ease-spring-bouncy: var(--ease-standard);
|
||||
|
||||
/* ── Typography ── access ramp: starts larger, generous line-heights,
|
||||
* caption never below 14px. Fixed rem, product register. */
|
||||
--text-hero-size: 3rem;
|
||||
--text-hero-lh: 1.1;
|
||||
--text-display-size: 2.5rem;
|
||||
--text-display-lh: 1.15;
|
||||
--text-headline-size: 1.875rem;
|
||||
--text-headline-lh: 1.25;
|
||||
--text-title-size: 1.5rem;
|
||||
--text-title-lh: 1.35;
|
||||
--text-subtitle-size: 1.25rem;
|
||||
--text-subtitle-lh: 1.5;
|
||||
--text-body-size: 1.125rem;
|
||||
--text-body-lh: 1.65;
|
||||
--text-label-size: 1rem;
|
||||
--text-label-lh: 1.5;
|
||||
--text-caption-size: 0.875rem;
|
||||
--text-caption-lh: 1.4;
|
||||
|
||||
/* ── Elevation ── low and warm; separation is the borders' job. */
|
||||
--elevation-0: none;
|
||||
--elevation-1: 0 1px 2px oklch(0.16 0.01 60 / 0.05);
|
||||
--elevation-2: 0 1px 3px oklch(0.16 0.01 60 / 0.07), 0 1px 2px oklch(0.16 0.01 60 / 0.04);
|
||||
--elevation-3: 0 4px 8px -2px oklch(0.16 0.01 60 / 0.08), 0 2px 4px -2px oklch(0.16 0.01 60 / 0.04);
|
||||
--elevation-4: 0 10px 20px -6px oklch(0.16 0.01 60 / 0.1), 0 4px 8px -4px oklch(0.16 0.01 60 / 0.05);
|
||||
--elevation-5: 0 18px 36px -8px oklch(0.16 0.01 60 / 0.14);
|
||||
|
||||
--spacing: 0.25rem;
|
||||
|
||||
/* Border widths — no sub-pixel hairlines in an access theme. */
|
||||
--border-width-thin: 1px;
|
||||
--border-width-base: 1.5px;
|
||||
--border-width-thick: 3px;
|
||||
--border-width-heavy: 5px;
|
||||
|
||||
/* 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 lantern lit. Warm dark umber, warm-white ink, cards a full
|
||||
* +0.05 L above the background, popover above card and below muted so the
|
||||
* altitude ladder stays readable. The gold turns luminous and carries a
|
||||
* black-umber label (bright-swatch/dark-ink), as do all dark status fills.
|
||||
*/
|
||||
.dark {
|
||||
--background: oklch(0.17 0.012 60);
|
||||
--foreground: oklch(0.95 0.01 80); /* ~16.5:1 on background */
|
||||
|
||||
--card: oklch(0.22 0.012 60);
|
||||
--card-foreground: oklch(0.95 0.01 80); /* ~15.0:1 on card */
|
||||
|
||||
--popover: oklch(0.245 0.012 60);
|
||||
--popover-foreground: oklch(0.95 0.01 80);
|
||||
|
||||
/* Luminous warm gold, black-umber ink — ~9.8:1. */
|
||||
--primary: oklch(0.78 0.13 80);
|
||||
--primary-foreground: oklch(0.15 0.03 70);
|
||||
|
||||
--secondary: oklch(0.27 0.02 65);
|
||||
--secondary-foreground: oklch(0.93 0.01 80);
|
||||
|
||||
--muted: oklch(0.27 0.015 62);
|
||||
--muted-foreground: oklch(0.78 0.015 75); /* ~9.6:1 bg / ~8.7:1 card / ~7.5:1 muted */
|
||||
|
||||
--accent: oklch(0.3 0.05 75);
|
||||
--accent-foreground: oklch(0.85 0.11 82); /* ~8.6:1 on accent */
|
||||
|
||||
/* Status — bright fills, dark umber inks, all >= 7:1. */
|
||||
--destructive: oklch(0.72 0.14 25);
|
||||
--destructive-foreground: oklch(0.16 0.03 25); /* ~7.8:1 */
|
||||
--success: oklch(0.78 0.15 150);
|
||||
--success-foreground: oklch(0.16 0.03 150); /* ~9.7:1 */
|
||||
--warning: oklch(0.8 0.14 60);
|
||||
--warning-foreground: oklch(0.16 0.03 60); /* ~10.4:1 */
|
||||
--info: oklch(0.75 0.1 250);
|
||||
--info-foreground: oklch(0.16 0.03 250); /* ~8.7:1 */
|
||||
|
||||
--border: oklch(0.95 0.01 80 / 0.25);
|
||||
--input: oklch(0.95 0.01 80 / 0.3);
|
||||
--ring: oklch(0.78 0.13 80);
|
||||
|
||||
--chart-1: oklch(0.8 0.13 80);
|
||||
--chart-2: oklch(0.7 0.13 150);
|
||||
--chart-3: oklch(0.75 0.1 220);
|
||||
--chart-4: oklch(0.68 0.12 290);
|
||||
--chart-5: oklch(0.72 0.14 20);
|
||||
|
||||
--sidebar: oklch(0.2 0.012 60);
|
||||
--sidebar-foreground: oklch(0.95 0.01 80);
|
||||
--sidebar-primary: oklch(0.78 0.13 80);
|
||||
--sidebar-primary-foreground: oklch(0.15 0.03 70);
|
||||
--sidebar-accent: oklch(0.3 0.05 75);
|
||||
--sidebar-accent-foreground: oklch(0.85 0.11 82);
|
||||
--sidebar-border: oklch(0.95 0.01 80 / 0.2);
|
||||
--sidebar-ring: oklch(0.78 0.13 80);
|
||||
|
||||
--syntax-keyword: oklch(0.78 0.1 290);
|
||||
--syntax-string: oklch(0.78 0.12 150);
|
||||
--syntax-number: oklch(0.82 0.12 70);
|
||||
--syntax-comment: oklch(0.72 0.015 70);
|
||||
--syntax-function: oklch(0.75 0.1 250);
|
||||
--syntax-type: oklch(0.78 0.08 200);
|
||||
|
||||
/* ── Chat surfaces (dark) ── */
|
||||
--chat-user-bg: oklch(0.32 0.04 70);
|
||||
--chat-user-fg: oklch(0.96 0.01 80);
|
||||
--chat-assistant-bg: oklch(0.22 0.012 60);
|
||||
--chat-assistant-fg: oklch(0.95 0.01 80);
|
||||
--chat-assistant-border: oklch(0.95 0.01 80 / 0.25);
|
||||
|
||||
--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 8px -2px oklch(0 0 0 / 0.4), 0 2px 4px -2px oklch(0 0 0 / 0.25);
|
||||
--elevation-4: 0 10px 20px -6px oklch(0 0 0 / 0.45), 0 4px 8px -4px oklch(0 0 0 / 0.25);
|
||||
--elevation-5: 0 18px 36px -8px oklch(0 0 0 / 0.55);
|
||||
}
|
||||
|
||||
/*
|
||||
* @layer base — Lantern's warm, legible philosophy.
|
||||
*
|
||||
* Lexend for everything (it earns its keep at reading time), strong visible
|
||||
* boundaries, one brass accent, links that look like links, and focus you can
|
||||
* find from across the kitchen table.
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
|
||||
/* Headings — Lexend 600. `color: inherit` so headings inside colored
|
||||
* surfaces (primary buttons, status banners, chat bubbles) stay legible. */
|
||||
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: inherit;
|
||||
}
|
||||
|
||||
code, kbd, samp, pre,
|
||||
[data-slot="code"],
|
||||
[data-slot="code-block"] {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
/* Inline code — foreground-tint chip, foreground ink. */
|
||||
: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.92em;
|
||||
}
|
||||
|
||||
/* Selection — the brass, as a wash. */
|
||||
::selection {
|
||||
background: color-mix(in oklch, var(--primary) 30%, transparent);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
/* Links — deep gold, UNDERLINED BY DEFAULT (a link must look like a link
|
||||
* without color vision); underline thickens on hover. */
|
||||
a {
|
||||
color: var(--primary);
|
||||
text-decoration-line: underline;
|
||||
text-decoration-thickness: 0.07em;
|
||||
text-underline-offset: 0.18em;
|
||||
transition: text-decoration-thickness var(--duration-fast) var(--ease-standard);
|
||||
}
|
||||
a:hover {
|
||||
text-decoration-thickness: 0.14em;
|
||||
}
|
||||
|
||||
[data-slot="sidebar"] {
|
||||
background: var(--sidebar);
|
||||
border-right: var(--border-width-base) solid var(--sidebar-border);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Focus-visible — 3px solid brass outline, 2px offset. Always visible,
|
||||
* findable at arm's length. Placed outside @layer base so it beats Tailwind's
|
||||
* default ring utilities.
|
||||
*/
|
||||
[data-slot="button"]:focus-visible,
|
||||
[data-slot="select-trigger"]:focus-visible {
|
||||
outline: 3px solid var(--ring);
|
||||
outline-offset: 2px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Buttons — medium weight, generous letterforms, no shrinking targets. */
|
||||
[data-slot="button"] {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 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 — 44px floor GLOBALLY, not just for coarse pointers. Tremor,
|
||||
* arthritis, and a hurried mouse all deserve the same landing pad.
|
||||
*/
|
||||
[data-slot="button"] {
|
||||
min-height: 2.75rem;
|
||||
min-width: 2.75rem;
|
||||
}
|
||||
[data-slot="input"],
|
||||
[data-slot="select-trigger"] {
|
||||
min-height: 2.75rem;
|
||||
}
|
||||
/* Coarse pointers keep their explicit guarantee too. */
|
||||
@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;
|
||||
}
|
||||
|
||||
/* Inputs — warm-white field, STRONG border, brass focus: border-color change
|
||||
* plus a 2px ring so focus survives low vision and bright rooms. */
|
||||
[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);
|
||||
}
|
||||
[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) 45%, transparent);
|
||||
}
|
||||
[data-slot="input"]:focus,
|
||||
[data-slot="input"]:focus-visible,
|
||||
[data-slot="textarea"]:focus,
|
||||
[data-slot="textarea"]:focus-visible,
|
||||
[data-slot="select-trigger"]:focus {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 2px var(--ring);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Reduced motion — zero transitions on the interactive slots; nothing in
|
||||
* Lantern depends on movement to be understood. */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
[data-slot="button"],
|
||||
[data-slot="input"],
|
||||
[data-slot="textarea"],
|
||||
[data-slot="select-trigger"],
|
||||
a {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user