diff --git a/.changeset/beetroot-atlas-rebrand.md b/.changeset/beetroot-atlas-rebrand.md new file mode 100644 index 0000000..2bed52b --- /dev/null +++ b/.changeset/beetroot-atlas-rebrand.md @@ -0,0 +1,10 @@ +--- +"@btravstack/theme": minor +--- + +Beetroot Atlas rebrand — a botanical atlas: engraved, warm, exact. + +- Light warm-ivory paper (`oklch(95.5% 0.015 85)`) with sepia ink is now the canonical scheme; dark becomes the "aubergine" scheme on `.dark`. +- Typography: Fraunces (display serif) + Switzer (body) + JetBrains Mono (code) replace Montserrat. +- Shape language: hairline rules, near-sharp corners, no decorative shadows, solid (non-frosted) navbar, solid ink hero name, still hero mark (no float, no glow). Motion reduced to near-zero. +- Non-breaking: all semantic tokens keep their names and the one-knob rebrand contract is unchanged — consumer sites still override only `--accent` (`:root:root { --accent: … }`) and every derived shade follows via `color-mix`. The new ivory paper is hue-neutral, so per-site accents (orange/indigo/green/violet) harmonise without changes. diff --git a/.hallmark/log.json b/.hallmark/log.json new file mode 100644 index 0000000..22543f7 --- /dev/null +++ b/.hallmark/log.json @@ -0,0 +1,38 @@ +[ + { + "date": "2026-07-16", + "scope": "app", + "macrostructure": "Catalogue", + "theme": "custom", + "theme_axes": "light / high-contrast-serif / chromatic-other (beetroot-pink ~353)", + "vibe": "botanical atlas, engraved plates, warm ivory, exact", + "enrichment": "Tier-B hand-built SVG engravings (plates + full logo set)", + "nav": "N9 edge-aligned", + "footer": "Ft1 mast-headed", + "brief": "btravstack org rebrand v3 — Beetroot Atlas (editorial botanical), replaces rejected v1 Riso + v2 Soft" + }, + { + "date": "2026-07-16", + "scope": "app", + "macrostructure": "Bento Grid", + "theme": "custom", + "theme_axes": "light / rounded-sans / chromatic-other (beetroot-pink ~353)", + "vibe": "warm, rounded, charming beetroot, soft-but-exact", + "enrichment": "H9 original gradient mascot (Tier-B, restored)", + "nav": "N1b", + "footer": "Ft8 marquee", + "brief": "btravstack org rebrand v2 — Beetroot Soft (REJECTED by user; PR #28)" + }, + { + "date": "2026-07-16", + "scope": "app", + "macrostructure": "Manifesto", + "theme": "custom", + "theme_axes": "light / display-heavy / chromatic-other (beetroot-pink ~353)", + "vibe": "beetroot risograph, contract-as-document, raw, technical", + "enrichment": "Tier-B hand-built SVG (flat riso logo set)", + "nav": "N7 brutal slab", + "footer": "Ft4 dense colophon", + "brief": "btravstack org rebrand v1 — Beetroot Riso (REJECTED by user; PR #27)" + } +] diff --git a/apps/website/.vitepress/theme/Landing.vue b/apps/website/.vitepress/theme/Landing.vue index 5c7c877..61c736c 100644 --- a/apps/website/.vitepress/theme/Landing.vue +++ b/apps/website/.vitepress/theme/Landing.vue @@ -10,9 +10,9 @@ function toggleAppearance() { } const principles = [ - { num: "01", title: "The signature is the doc", body: "A contract or a function signature should be enough to understand a system. Declare the interface once and read everything from it — no digging into implementations." }, - { num: "02", title: "Fail fast, everywhere", body: "The sooner the error, the better the code. Anticipated failures are values in the type, malformed data stops at the boundary, missing wiring is a compile error." }, - { num: "03", title: "Coherence at a high level", body: "Contracts pin down how the pieces fit — publisher and consumer, workflow and client, module and dependency can't drift apart while implementations change underneath." }, + { num: "I.", title: "The signature is the doc", body: "A contract or a function signature should be enough to understand a system. Declare the interface once and read everything from it — no digging into implementations." }, + { num: "II.", title: "Fail fast, everywhere", body: "The sooner the error, the better the code. Anticipated failures are values in the type, malformed data stops at the boundary, missing wiring is a compile error." }, + { num: "III.", title: "Coherence at a high level", body: "Contracts pin down how the pieces fit — publisher and consumer, workflow and client, module and dependency can't drift apart while implementations change underneath." }, ]; const aiCards = [ @@ -42,6 +42,8 @@ interface Project { docs: string; } +const plates = ["II", "III", "IV", "V"]; + const projects: Project[] = [ { tag: "Messaging", name: "amqp-contract", pkg: "@amqp-contract/contract", @@ -106,354 +108,406 @@ onMounted(() => { diff --git a/apps/website/public/favicon.svg b/apps/website/public/favicon.svg index b7615ac..2c95385 100644 --- a/apps/website/public/favicon.svg +++ b/apps/website/public/favicon.svg @@ -1,12 +1,27 @@ - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/website/public/logos/amqp-contract-dark.svg b/apps/website/public/logos/amqp-contract-dark.svg index 6f5c947..069d821 100644 --- a/apps/website/public/logos/amqp-contract-dark.svg +++ b/apps/website/public/logos/amqp-contract-dark.svg @@ -1 +1,20 @@ - + + + + + + + + + + + + + + + + + + + + diff --git a/apps/website/public/logos/amqp-contract-light.svg b/apps/website/public/logos/amqp-contract-light.svg index 2aa8ee6..7d7307d 100644 --- a/apps/website/public/logos/amqp-contract-light.svg +++ b/apps/website/public/logos/amqp-contract-light.svg @@ -1 +1,20 @@ - + + + + + + + + + + + + + + + + + + + + diff --git a/apps/website/public/logos/btravstack-dark.svg b/apps/website/public/logos/btravstack-dark.svg index 89f4f6c..e8bdd3e 100644 --- a/apps/website/public/logos/btravstack-dark.svg +++ b/apps/website/public/logos/btravstack-dark.svg @@ -1 +1,27 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/website/public/logos/btravstack-light.svg b/apps/website/public/logos/btravstack-light.svg index 89f4f6c..d332b04 100644 --- a/apps/website/public/logos/btravstack-light.svg +++ b/apps/website/public/logos/btravstack-light.svg @@ -1 +1,27 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/website/public/logos/demesne-dark.svg b/apps/website/public/logos/demesne-dark.svg index d8db6ab..4a5e891 100644 --- a/apps/website/public/logos/demesne-dark.svg +++ b/apps/website/public/logos/demesne-dark.svg @@ -1 +1,30 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/website/public/logos/demesne-light.svg b/apps/website/public/logos/demesne-light.svg index 0301a24..f5d740d 100644 --- a/apps/website/public/logos/demesne-light.svg +++ b/apps/website/public/logos/demesne-light.svg @@ -1 +1,30 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/website/public/logos/temporal-contract-dark.svg b/apps/website/public/logos/temporal-contract-dark.svg index 374fc70..962b904 100644 --- a/apps/website/public/logos/temporal-contract-dark.svg +++ b/apps/website/public/logos/temporal-contract-dark.svg @@ -1 +1,38 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/website/public/logos/temporal-contract-light.svg b/apps/website/public/logos/temporal-contract-light.svg index 0c88007..ef7f1b4 100644 --- a/apps/website/public/logos/temporal-contract-light.svg +++ b/apps/website/public/logos/temporal-contract-light.svg @@ -1 +1,38 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/website/public/logos/unthrown-dark.svg b/apps/website/public/logos/unthrown-dark.svg index b49df8a..4eb68f9 100644 --- a/apps/website/public/logos/unthrown-dark.svg +++ b/apps/website/public/logos/unthrown-dark.svg @@ -1 +1,15 @@ - + + + + + + + + + + + + + + + diff --git a/apps/website/public/logos/unthrown-light.svg b/apps/website/public/logos/unthrown-light.svg index 9d0f2b3..dc6d76e 100644 --- a/apps/website/public/logos/unthrown-light.svg +++ b/apps/website/public/logos/unthrown-light.svg @@ -1 +1,15 @@ - + + + + + + + + + + + + + + + diff --git a/design.md b/design.md new file mode 100644 index 0000000..c4490a0 --- /dev/null +++ b/design.md @@ -0,0 +1,140 @@ +# Design — btravstack · "Beetroot Atlas" + +A locked design system for the btravstack org (landing + `@btravstack/theme` + +the four docs sites). Every page redesign reads this file before emitting code. +Do not regenerate per page — extend or amend this file when the system needs to +grow. + +**The idea.** A botanical atlas. The beetroot (btrav ~ betterave) is a +fine-line engraving on warm ivory paper — hand-tinted linework, hairline rules, +double-rule plate frames, numbered plates, a high-contrast serif. Premium +through restraint: almost no motion, letterspaced small caps, generous +whitespace. Supersedes the rejected "Beetroot Riso" (brutalist) and +"Beetroot Soft" (playful) systems — no hard slabs, no pills, no cards, no +mascot bobbing. + +## Genre + +editorial — premium register. Vibe: *"botanical atlas, engraved plates, warm +ivory, exact"*. + +## Macrostructure family + +- Marketing pages (landing): **Catalogue** — left-biased hero (serif headline + + framed engraving plate), an inventory band, a uniform grid of numbered + double-rule plates (Pl. I is the beetroot; Pl. II–V the packages), then + editorial sections: numbered principles (I–III), ruled prose columns, a + hairline index, a quiet typographic close. +- Docs sites (VitePress content): default VitePress layout wearing the theme + tokens — Fraunces headings, hairline feature frames, still (no float, no + glow), near-sharp corners. +- Nav: **N9 edge-aligned** — wordmark hard-left, toggle + text CTA hard-right. +- Footer: **Ft1 mast-headed** — single band: wordmark, tagline, small links, + MIT line. + +## Theme + +Light "atlas ivory" is canonical; dark "aubergine" via `.dark`. +Only lightness/chroma move between schemes. + +| Token | Light (canonical) | Dark — aubergine | +| --- | --- | --- | +| `--bg` (paper) | `oklch(95.5% 0.015 85)` warm ivory | `oklch(17.5% 0.020 340)` | +| `--card` | `oklch(93% 0.018 85)` | `oklch(21% 0.022 340)` | +| `--text` (ink) | `oklch(25% 0.020 30)` sepia-black | `oklch(93% 0.008 60)` warm cream | +| `--accent` | **#E0589A** = `oklch(65.5% 0.182 353)` — THE knob | same | +| `--text-accent` | accent darkened 30% (AA on ivory) | `var(--accent)` | +| rules | hairlines (22–38% ink mix); plates use double frames | same recipe | +| focus ring | accent darkened 18% — instant, never animated | same | + +**One-knob rebrand is contractual.** Docs sites override only `--accent` +(`:root:root { --accent: … }`); every derived shade flows via `color-mix`. +amqp `#FF6600` · temporal `#6B76F2` · unthrown `#46B86C` · demesne `#B45CE0`. +Note: the paper is hue-neutral warm ivory (hue 85), so it harmonises with any +per-site accent. + +Accent discipline: text-level only (labels, underlines, plate numbers); fills +appear only on docs-site brand buttons. Washes ≤ 13% opacity. + +## Typography + +- Display: **Fraunces** (variable, optical size), weight 550–650, roman, + sentence case. Never italic headers, never all-caps headlines — all-caps is + reserved for small-caps labels (`Pl. II — Messaging`) at 12px, tracked 0.12em. +- Body: **Switzer** (Fontshare), 400–600. +- Mono (outlier): **JetBrains Mono** — code, package identifiers, star counts. +- Hero headline ≤ 50 chars at `--fs-hero: clamp(36px, 4.8vw, 54px)`. +- 2+1 rule: exactly these three families. + +## Spacing / shape + +Named px scale in `packages/theme/src/tokens.css` (`--space-1…6`). Corners: +near-sharp (3–8px; plates fully square). Shadows: none in the design language +(a 1px hairline "rest" shadow at most; toast excepted). No glassmorphism, no +frosted nav — atlas paper is solid. + +**The plate device** is the system's signature: 1px outer rule + 5px gutter + +1px inner rule, square corners, small-caps caption with an accent plate number. +Use it for framed content, never for generic cards. + +## Motion + +- Easings: `--ease: cubic-bezier(0.16, 1, 0.3, 1)`; durations ≤ 0.18s. +- Stance: near-none. Hover = underline/color shifts only. No floats, no lifts, + no marquees, no scroll reveals. The copy toast is the only moving element. +- Reduced-motion: transitions collapse; toast becomes opacity-only. + +## Microinteractions stance + +- Silent success (copy button → small mono toast, 1.6s). +- Hover tooltips 800ms · focus 0ms. Focus rings instant, 2px `--focus`. + +## CTA voice + +- Primary: typographic — Fraunces 600 + 2px accent underline + arrow glyph. + No filled buttons on marketing pages. +- Docs sites keep VitePress's filled brand button (accent fill, ink text, + 4px radius) for functional CTAs. + +## Logos + +All five marks are **fine-line botanical engravings**: sepia-ink linework +(cream-ink on dark), hand-tint fills (soft beet pink, soft leaf green), hatch +shading, hand-drawn irregularity. Concepts: beetroot (org, Pl. I with root +cross-section), rabbit (amqp), hourglass with beet sand (temporal), no-throw +ring (unthrown), double-frame shield (demesne). Per-scheme files swap ink + +tint values only. Never flatten to hard-edge marks; never revert to gradients. + +## What pages MUST share + +- The wordmark voice (Fraunces 600, "btrav" in `--text-accent` + "stack" in ink). +- The one-knob accent architecture; accent as text/underline, not fills. +- The three fonts, hairline-rule language, the plate device, small-caps labels. +- The stillness — motion stays near-zero everywhere. + +## What pages MAY differ on + +- Macrostructure within the family (a future page may be Long Document or + Conversational FAQ — still wearing this theme). +- Per-site accent hue (docs sites). +- Docs sites keep VitePress's functional layout; only the dress is shared. + +## Exports + +### tokens.css +The canonical export lives at `packages/theme/src/tokens.css` (published as +`@btravstack/theme`). It is the single source of truth; do not duplicate here. + +### Quick-reference (other projects) +```css +:root { + --color-paper: oklch(95.5% 0.015 85); + --color-ink: oklch(25% 0.020 30); + --color-accent: #E0589A; + --font-display: "Fraunces", serif; + --font-body: "Switzer", sans-serif; + --font-mono: "JetBrains Mono", monospace; + --ease: cubic-bezier(0.16, 1, 0.3, 1); + --rule-hair: 1px; +} +``` diff --git a/packages/theme/src/fonts.css b/packages/theme/src/fonts.css index 39e3d6d..995c66c 100644 --- a/packages/theme/src/fonts.css +++ b/packages/theme/src/fonts.css @@ -1,6 +1,8 @@ /* - * btravstack web fonts. Montserrat (display/UI) + JetBrains Mono (code). - * Loaded from Google Fonts so the system stays zero-asset and portable; - * the weights here match the --fw-* tokens in tokens.css. + * btravstack web fonts — the "Beetroot Atlas" pairing (2+1 rule). + * Fraunces (display serif, optical size + 500–700) — atlas headings + * Switzer (body/UI, 400–600, Fontshare) — refined neutral + * JetBrains Mono (code + labels) */ -@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,600;9..144,700&family=JetBrains+Mono:wght@400;500;600&display=swap'); +@import url('https://api.fontshare.com/v2/css?f[]=switzer@400,500,600&display=swap'); diff --git a/packages/theme/src/style.css b/packages/theme/src/style.css index 3afaf6d..b2a9a27 100644 --- a/packages/theme/src/style.css +++ b/packages/theme/src/style.css @@ -1,13 +1,16 @@ +/* Hallmark · genre: editorial (premium) · design-system: design.md · designed-as-app + * scope: VitePress chrome mapping for all btravstack sites (docs + landing shell) + */ + /* - * @btravstack/theme — VitePress style entry. + * @btravstack/theme — VitePress style entry ("Beetroot Atlas"). * * Maps the btravstack design tokens onto VitePress's own `--vp-*` variables so * every btravstack site (the website + the docs sites) shares one look. * Because the semantic tokens (--bg, --text, --text-accent, …) flip per scheme * in tokens.css, this mapping is written once and adapts automatically: - * - dark → the btravstack plum surfaces, pink links - * - light → clean warm-white surfaces, brand darkened to deep beetroot - * (--text-accent) so links/headings pass WCAG-AA on white + * - light → warm ivory paper, sepia ink, deep-beet links (WCAG-AA) + * - dark → aubergine surfaces, warm cream ink, vibrant pink links * * The `:root:root` selector raises specificity to (0,2,0) so the mapping wins * over VitePress's own `:root` / `.dark` defaults regardless of load order. @@ -19,7 +22,7 @@ /* Surfaces / text / dividers — scheme-aware via the semantic tokens */ --vp-c-bg: var(--bg); --vp-c-bg-alt: var(--card); - --vp-c-bg-soft: var(--card-soft); + --vp-c-bg-soft: var(--card); --vp-c-bg-elv: var(--card); --vp-c-text-1: var(--text); @@ -33,20 +36,19 @@ /* Brand — links/active use accent-as-TEXT (darkens on light for AA). All shades derive from --accent, so a site override rebrands everything. */ --vp-c-brand-1: var(--text-accent); - --vp-c-brand-2: var(--accent-soft); + --vp-c-brand-2: var(--accent); --vp-c-brand-3: var(--accent-deep); - --vp-c-brand-soft: color-mix(in srgb, var(--accent) 16%, transparent); + --vp-c-brand-soft: color-mix(in srgb, var(--accent) 14%, transparent); --vp-c-brand: var(--text-accent); /* legacy alias */ - /* Inline code — accent-as-text so it stays readable on the (scheme-aware) - code inset: pink on dark, deep beetroot on the light code wash */ + /* Inline code — accent-as-text on the scheme-aware code inset */ --vp-code-color: var(--text-accent); --vp-code-bg: var(--code-bg); --vp-font-family-base: var(--sans); --vp-font-family-mono: var(--mono); - /* Solid brand buttons — dark ink on the pink FILL, readable in both schemes */ + /* Solid brand buttons — dark ink on the accent FILL, readable both schemes */ --vp-button-brand-bg: var(--accent); --vp-button-brand-text: var(--accent-contrast); --vp-button-brand-border: transparent; @@ -65,105 +67,91 @@ --vp-custom-block-warning-border: var(--accent-line); --vp-custom-block-warning-text: var(--text-accent); - /* Home hero: gradient name + accent glow behind the logo */ - --vp-home-hero-name-color: transparent; - --vp-home-hero-name-background: linear-gradient(120deg, var(--text-accent), var(--accent-soft)); - --vp-home-hero-image-background-image: radial-gradient(closest-side, color-mix(in srgb, var(--accent) 30%, transparent), transparent 72%); - --vp-home-hero-image-filter: blur(8px); + /* Home hero: solid ink name in the atlas serif — no gradient, no glow */ + --vp-home-hero-name-color: var(--vp-c-text-1); + --vp-home-hero-name-background: none; + --vp-home-hero-image-background-image: none; + --vp-home-hero-image-filter: none; } -/* ── Playful flourishes — shared across every btravstack site ───── */ - -/* Home hero: a gently floating logo. The beetroot glow lives BEHIND the hero - image itself (--vp-home-hero-image-background-image, set above) so it tracks - the logo wherever the layout places it. We deliberately do NOT add a separate - centered ::before glow: the landing page uses its own bespoke layout (not - .VPHome), and on the docs sites the default home hero is left-aligned with the - image on the right — a hard-centered glow there just floats, disconnected. */ -.VPHome { overflow-x: clip; } -.VPHero .image-container { animation: btv-floaty 6s ease-in-out infinite; } -.VPHero .image-src { filter: drop-shadow(0 18px 40px color-mix(in srgb, var(--accent-deep) 45%, transparent)); } -@keyframes btv-floaty { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-10px); } } - -/* Navbar logo: VitePress's 24px mark reads as a speck next to the wordmark. - Size it up through VitePress's own variable (--vp-nav-logo-height) rather than - overriding `.logo { height }` — VitePress sets the height from a scoped - `:deep(.logo)` selector that out-specifies a plain `.VPNavBarTitle .logo`, so - the variable is the only reliable lever. Spacing stays on the logo's default - margin (no extra `gap`, which would compound with it). - Light/dark switching is left to VitePress: with `logo: { light, dark }` per - repo, its native .VPImage class toggle already shows the right artwork. */ -:root:root { --vp-nav-logo-height: 34px; } -@media (max-width: 768px) { :root:root { --vp-nav-logo-height: 28px; } } - -/* Home hero: tighten the display type. (The hero `image:` is one file per repo, - set in index.md frontmatter; for a crisp mark in both schemes make it - scheme-aware there — hero.image.light / hero.image.dark.) */ -.VPHero .name { letter-spacing: -1.5px; } -.VPHero .text { letter-spacing: -0.5px; } - -/* Feature cards: hairline border + a soft lift on hover, echoing the landing - page's package cards; brand-tinted icon chip. */ +/* ── Atlas chrome — shared across every btravstack site ────────── */ + +/* No horizontal scroll, ever (clip — not hidden — so sticky keeps working). */ +html, body { overflow-x: clip; } + +/* Display serif on the big headings; body stays Switzer for reading. */ +.VPHero .name, +.VPHero .text, +.VPFeature .title, +.vp-doc h1, .vp-doc h2, .vp-doc h3 { + font-family: var(--display); + letter-spacing: var(--tracking-tight); + font-weight: 600; +} +.VPHero .name { font-weight: 650; } +.VPNavBar .title { font-family: var(--display); font-weight: 600; } + +/* Navbar: solid ivory paper with a hairline — atlas pages don't frost. */ +.VPNav, .VPNavBar { background: var(--bg) !important; } +.VPNavBar { backdrop-filter: none !important; -webkit-backdrop-filter: none !important; } +.VPNavBar .content-body { background: transparent !important; } +.VPNavBar:not(.home) { border-bottom: 1px solid var(--border); } + +/* Hero mark: still, engraved — no float, no glow. A fine rule beneath. */ +.VPHome { overflow-x: clip; position: relative; } +.VPHero .image-src { filter: none; } + +/* Navbar logo sizing via VitePress's own variable. */ +:root:root { --vp-nav-logo-height: 30px; } +@media (max-width: 768px) { :root:root { --vp-nav-logo-height: 25px; } } + +/* Feature cards: hairline frames, sharp-ish corners, quiet hover. */ .VPFeature { - border: 1px solid var(--vp-c-divider); + border: 1px solid var(--border); border-radius: var(--radius-lg); - transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease; -} -.VPFeature:hover { - transform: translateY(-4px); - border-color: var(--vp-c-border); - box-shadow: var(--shadow-card); + transition: border-color var(--speed) var(--ease); } +.VPFeature:hover { border-color: var(--border-2); } .VPFeature .icon { border-radius: var(--radius) !important; - background: var(--vp-c-brand-soft) !important; - border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent); + background: var(--accent-wash) !important; + border: 1px solid var(--accent-line); } -.VPFeature .title { letter-spacing: -0.3px; } -/* Brand buttons: springy hover */ -.VPButton.brand { transition: filter 0.15s, transform 0.15s; } -.VPButton.brand:hover { filter: brightness(1.07); transform: translateY(-1px); } +/* Brand buttons: quiet press, no bounce. */ +.VPButton { border-radius: var(--radius) !important; font-weight: 600; } +.VPButton.brand { transition: background-color var(--speed-fast) var(--ease); } +.VPButton.brand:active { transform: translateY(1px); } -/* Rounder, pinker code */ -.vp-doc :not(pre) > code { border-radius: 7px; } +/* Code + admonitions: eased corners, inset paper. */ +.vp-doc :not(pre) > code { border-radius: var(--radius-sm); } .vp-doc div[class*="language-"] { border-radius: var(--radius); } .vp-doc .custom-block { border-radius: var(--radius); } -/* ── Home hero: a bigger mark + a large accent glow shape ───────── - The default hero only has a small glow hugging the logo; the docs - mockups show a broad accent wash bleeding off the top-right. */ -/* position: relative only — the existing `.VPHome { overflow-x: clip }` above - handles the glow's horizontal bleed; a full `overflow: clip` here would also - clip vertical content like the feature-card hover-lift shadows. */ -.VPHome { position: relative; } -.VPHome::before { - content: ""; - position: absolute; top: -40px; right: -120px; z-index: 0; - width: 620px; height: 620px; max-width: 85vw; - background: radial-gradient(closest-side, color-mix(in srgb, var(--accent) 16%, transparent), transparent 70%); - pointer-events: none; -} -.VPHome .VPHero, -.VPHome .VPFeatures { position: relative; z-index: 1; } +/* Hero mark sizing on docs homes (all project logos are square). */ @media (min-width: 960px) { - .VPHero .image-container { width: 340px; height: 340px; } + .VPHero .image-container { width: 300px; height: 300px; } .VPHero .image-bg { width: 100%; height: 100%; } - /* the mark ships at a small intrinsic size, so stretch it explicitly - (all project logos are square) rather than relying on max-width */ .VPHero .image-src { - width: 300px; - height: 300px; - max-width: 300px; - max-height: 300px; + width: 260px; + height: 260px; + max-width: 260px; + max-height: 260px; object-fit: contain; } } +/* Focus is a first-class state — instant, visible, never animated. */ +:focus-visible { + outline: 2px solid var(--focus); + outline-offset: 2px; + transition: none; +} + /* ── "Part of btravstack" org-attribution strip (layout-bottom slot) ── */ .btv-partof { - border-top: 1px solid var(--vp-c-divider); - background: var(--vp-c-bg-alt); + border-top: 1px solid var(--border); + background: var(--bg); padding: 16px 24px; display: flex; justify-content: center; @@ -173,22 +161,17 @@ align-items: center; gap: 8px; font-size: 13px; - font-weight: 600; + font-weight: 500; color: var(--vp-c-text-3); text-decoration: none; - transition: color 0.15s; + transition: color var(--speed-fast) var(--ease); } -.btv-partof-link:hover { color: var(--vp-c-text-2); } +.btv-partof-link:hover { color: var(--vp-c-text-1); } .btv-partof-link:focus-visible { - outline: 2px solid var(--accent); + outline: 2px solid var(--focus); outline-offset: 3px; - border-radius: 4px; color: var(--vp-c-text-1); } .btv-partof-mark { display: inline-flex; line-height: 0; } -.btv-partof-word { font-weight: 700; letter-spacing: -0.3px; color: var(--vp-c-text-2); } -.btv-partof-pink { color: var(--bt-pink); } - -@media (prefers-reduced-motion: reduce) { - .VPHero .image-container { animation: none; } -} +.btv-partof-word { font-family: var(--display); font-weight: 600; color: var(--vp-c-text-2); } +.btv-partof-pink { color: var(--text-accent); } diff --git a/packages/theme/src/tokens.css b/packages/theme/src/tokens.css index 5167a3a..d280edb 100644 --- a/packages/theme/src/tokens.css +++ b/packages/theme/src/tokens.css @@ -1,19 +1,29 @@ +/* Hallmark · genre: editorial (premium) · macrostructure: Catalogue · design-system: design.md · designed-as-app + * theme: custom · vibe: "botanical atlas, engraved plates, warm ivory, exact" + * paper: oklch(95.5% 0.015 85) · accent: #E0589A (oklch(65.5% 0.182 353)) + * display: Fraunces 550–650 (roman) · body: Switzer · outlier: JetBrains Mono + * axes: light / high-contrast-serif / chromatic-other (beetroot-pink ~353°) + * nav: N9 edge-aligned · footer: Ft1 mast-headed · studied: no · context: user-provided + * pre-emit critique: P5 H4 E4 S5 R5 V5 + */ + /* - * btravstack design tokens — the single source of truth. + * btravstack design tokens — the single source of truth ("Beetroot Atlas"). * - * Mirrors the btravstack Design System (tokens/colors.css, themes.css, - * typography.css, spacing.css). Reference the SEMANTIC tokens (--bg, --text, - * --accent, --text-accent, --radius, --space-*) in app code; the raw --bt-* - * palette underneath is the provenance, exposed so adapters (e.g. VitePress) - * can remap freely. + * A botanical atlas: warm ivory paper, sepia ink, hairline rules and + * double-rule plate frames, a high-contrast serif, and the beetroot as a + * fine-line engraving. Premium through restraint — almost no motion, no + * decorative shadows, letterspaced small caps. Reference the SEMANTIC tokens + * (--bg, --text, --accent, --text-accent, --radius, --space-*) in app code; + * the raw --bt-* palette underneath is the provenance. * - * Dark is the canonical default. The light scheme overrides the semantic - * tokens; it activates on `:root:not(.dark)` (VitePress's appearance system) - * as well as an explicit `.light` / `[data-theme="light"]`. + * LIGHT "atlas ivory" is the canonical scheme. The dark "aubergine" scheme + * overrides the semantic tokens on `.dark` / `[data-theme="dark"]` + * (VitePress's appearance system). * - * Fills vs text: keep --accent / --green for FILLS (buttons, glow, washes); + * Fills vs text: keep --accent / --green for FILLS (docs buttons, washes); * use --text-accent / --text-green for COLORED TEXT and icons — light mode - * darkens those to deep beetroot so links/labels pass WCAG-AA on white. + * darkens those to deep beetroot so links/labels pass WCAG-AA on ivory. */ :root { /* ── Brand palette (raw beetroot) ─────────────────────────────── */ @@ -25,98 +35,99 @@ --bt-leaf: #3DAE62; /* center leaf */ --bt-leaf-dark: #2C8B4E; /* side leaves */ --bt-green: #46B86C; /* "success" green (checks) */ - --bt-cream: #F5EFF3; /* near-white text */ - --bt-ink: #1a0c14; /* near-black, for text on accent */ + --bt-cream: #F5F0E5; /* atlas ivory */ + --bt-ink: #2B1E1C; /* sepia-black engraving ink */ - /* ── Surfaces (dark scheme — the canonical btravstack look) ───── */ - --bg: #15101c; - --bg-grad: #1b1326; /* radial hero glow inner stop */ - --card: #1d1726; - --card-soft: #241c31; + /* ── Surfaces (LIGHT scheme — "atlas ivory", canonical) ──────── */ + --bg: oklch(95.5% 0.015 85); /* warm ivory ≈ #f5f0e5 */ + --bg-grad: oklch(94% 0.018 85); /* legacy alias — flat, no glow */ + --card: oklch(93% 0.018 85); /* ≈ #ede7db */ + --card-soft: oklch(90.5% 0.020 85); /* ≈ #e6dfd1 */ - /* ── Borders / dividers ──────────────────────────────────────── */ - --border: rgba(255, 255, 255, 0.08); - --border-2: rgba(255, 255, 255, 0.12); + /* ── Rules — hairlines; plates draw their own double frames ──── */ + --border: color-mix(in srgb, var(--text) 22%, transparent); + --border-2: color-mix(in srgb, var(--text) 38%, transparent); /* ── Text ────────────────────────────────────────────────────── */ - --text: #F5EFF3; - --muted: #AEA1BC; - --faint: #7E7290; + --text: oklch(25% 0.020 30); /* sepia-black ≈ #2b1e1c */ + --muted: oklch(42% 0.018 35); /* ≈ #564a46, ~7:1 on ivory */ + --faint: oklch(50% 0.015 35); /* ≈ #6b605d, ~4.9:1 on ivory */ - /* ── Accent — the single brand knob (v1.4.0) ───────────────────── + /* ── Accent — the single brand knob ────────────────────────────── Set ONE value, `--accent`, and the whole theme rebrands: the deep, hover, soft and wash shades below all derive from it via color-mix, - so a consumer site needs only `:root { --accent: #FF6600 }`. The - beetroot pink here is the default (the btravstack org brand); the - docs sites override it with their own accent. - (Optional finer knobs: override --text-accent for a hand-tuned - light-mode AA link color, or --accent-contrast for ink on the fill.) */ + so a consumer site needs only `:root:root { --accent: #FF6600 }`. + The beetroot pink here is the default (the btravstack org brand); + the docs sites override it with their own accent. */ --accent: #E0589A; /* ← the knob */ - --accent-contrast: #1a0c14; /* readable text on an accent fill */ - --accent-deep: color-mix(in srgb, var(--accent), #000 42%); /* deep root — active/press */ - --accent-hover: color-mix(in srgb, var(--accent), #000 18%); /* button hover fill */ - --accent-soft: color-mix(in srgb, var(--accent) 62%, #fff); /* highlight vein / brand-2 */ - --accent-rgb: 224, 88, 154; /* legacy: prefer color-mix over rgba(var(--accent-rgb),…) */ + --accent-contrast: #2B1E1C; /* readable text on an accent fill */ + --accent-deep: color-mix(in srgb, var(--accent), #000 42%); /* active/press */ + --accent-hover: color-mix(in srgb, var(--accent), #000 16%); /* fill hover */ + --accent-soft: color-mix(in srgb, var(--accent) 62%, #fff); /* hand-tint */ + --accent-rgb: 224, 88, 154; /* legacy: prefer color-mix */ --green: #46B86C; --green-rgb: 70, 184, 108; - /* ── Accent-as-TEXT (split from fill; light darkens it for AA) ─── */ - --text-accent: var(--accent); /* on dark plum the accent reads fine as text */ - --text-green: #46B86C; + /* ── Accent-as-TEXT (split from fill; light darkens it for AA) ── */ + --text-accent: color-mix(in srgb, var(--accent), #000 30%); /* deep beet, AA on ivory */ + --text-green: #15683A; - /* Display accent — the accent as the LARGE display wordmark color in - both schemes (large text only needs 3:1, so it can stay fully vibrant). */ - --display-accent: var(--accent); + /* Display accent — the accent for LARGE display moments only. */ + --display-accent: color-mix(in srgb, var(--accent), #000 18%); - /* ── Glass (frosted sticky headers) ──────────────────────────── */ - --glass: rgba(21, 16, 28, 0.66); + /* ── Focus — instant, never animated ─────────────────────────── */ + --focus: color-mix(in srgb, var(--accent), #000 18%); + + /* ── Header surface (solid — atlas paper doesn't frost) ──────── */ + --glass: var(--bg); /* ── Accent washes / lines / code inset ──────────────────────── */ - --accent-wash: color-mix(in srgb, var(--accent) 10%, transparent); - --accent-wash-2: color-mix(in srgb, var(--accent) 16%, transparent); - --accent-line: color-mix(in srgb, var(--accent) 25%, transparent); - --code-bg: rgba(0, 0, 0, 0.35); /* inline code inset */ + --accent-wash: color-mix(in srgb, var(--accent) 8%, transparent); + --accent-wash-2: color-mix(in srgb, var(--accent) 13%, transparent); + --accent-line: color-mix(in srgb, var(--accent-deep) 35%, transparent); + --code-bg: color-mix(in srgb, var(--text) 5%, var(--bg)); - /* ── Typography: families ────────────────────────────────────── */ - --sans: 'Montserrat', 'Segoe UI', system-ui, sans-serif; - --mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace; + /* ── Typography: families (2+1 — see design.md) ──────────────── */ + --display: 'Fraunces', ui-serif, Georgia, serif; + --sans: 'Switzer', 'Segoe UI', system-ui, sans-serif; + --mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace; /* ── Typography: weights ─────────────────────────────────────── */ --fw-medium: 500; --fw-semibold: 600; --fw-bold: 700; - --fw-extrabold: 800; + --fw-extrabold: 700; /* atlas caps at 700 — engravings, not posters */ /* ── Typography: display / heading scale (clamped, fluid) ─────── */ - --fs-hero: clamp(52px, 9vw, 92px); - --fs-h1: clamp(34px, 6vw, 56px); - --fs-h2: clamp(28px, 4vw, 40px); - --fs-h3: 23px; - --fs-h4: 18px; + --fs-hero: clamp(36px, 4.8vw, 54px); + --fs-h1: clamp(32px, 5vw, 48px); + --fs-h2: clamp(24px, 3vw, 31px); + --fs-h3: 21px; + --fs-h4: 17px; /* ── Typography: body / UI scale ─────────────────────────────── */ - --fs-lead: clamp(20px, 2.6vw, 26px); + --fs-lead: clamp(18px, 2.1vw, 21px); --fs-body-lg: 17px; --fs-body: 15px; --fs-body-sm: 14px; --fs-caption: 13px; - --fs-eyebrow: 13px; + --fs-eyebrow: 12px; /* ── Typography: tracking + line heights ─────────────────────── */ - --tracking-hero: -2.5px; - --tracking-tight: -1px; - --tracking-snug: -0.5px; - --tracking-eyebrow: 1.5px; - --lh-tight: 1.08; - --lh-snug: 1.3; - --lh-body: 1.6; - - /* ── Radii ───────────────────────────────────────────────────── */ - --radius-sm: 9px; - --radius: 12px; - --radius-lg: 16px; - --radius-xl: 20px; - --radius-2xl: 24px; + --tracking-hero: -0.015em; + --tracking-tight: -0.01em; + --tracking-snug: -0.005em; + --tracking-eyebrow: 0.12em; + --lh-tight: 1.12; + --lh-snug: 1.35; + --lh-body: 1.65; + + /* ── Radii — near-sharp; plates are square, docs slightly eased ─ */ + --radius-sm: 3px; + --radius: 4px; + --radius-lg: 6px; + --radius-xl: 8px; + --radius-2xl: 8px; --radius-pill: 999px; /* ── Spacing scale ───────────────────────────────────────────── */ @@ -127,51 +138,47 @@ --space-5: 22px; --space-6: 28px; - /* ── Shadows ─────────────────────────────────────────────────── */ - --shadow-card: 0 24px 60px -28px rgba(0, 0, 0, 0.8); - --shadow-accent: 0 12px 30px -10px color-mix(in srgb, var(--accent) 60%, transparent); - --shadow-toast: 0 18px 40px -16px rgba(0, 0, 0, 0.8); + /* ── Shadows — paper doesn't float; keep them almost nothing ─── */ + --shadow-card: 0 1px 0 color-mix(in srgb, var(--text) 12%, transparent); + --shadow-accent: none; + --shadow-toast: 0 6px 20px -10px color-mix(in srgb, var(--text) 45%, transparent); /* ── Layout & motion ─────────────────────────────────────────── */ - --container: 1180px; - --speed-fast: 0.15s; - --speed: 0.2s; - --ease: ease; + --container: 1120px; + --speed-fast: 0.12s; + --speed: 0.18s; + --ease: cubic-bezier(0.16, 1, 0.3, 1); } /* - * Light scheme — warm, faintly beetroot-tinted near-whites. Only the semantic - * tokens flip; the raw --bt-* palette is unchanged. Brand FILLS stay vibrant - * pink (dark ink on top); accent-as-TEXT darkens to deep beetroot for AA. + * Dark scheme — "aubergine": the atlas printed in negative. Only the semantic + * tokens flip; the raw --bt-* palette never changes. Brand FILLS stay vibrant + * (dark ink on top); accent-as-TEXT reads fine on aubergine. */ -:root:not(.dark), -[data-theme="light"], -.light { - --bg: #FBF7F9; - --bg-grad: #FCEDF4; - --card: #FFFFFF; - --card-soft: #F0E6EC; - - --border: rgba(26, 12, 20, 0.16); - --border-2: rgba(26, 12, 20, 0.24); - - --text: #1F0E18; - --muted: #5E5159; /* ~7.1:1 on white */ - --faint: #766678; /* ~5.0:1 on white (was #978996, 3.1:1 — failed AA) */ - - /* --accent (the FILL) is NOT overridden here — it stays the site's vibrant - accent in both schemes (dark ink sits on top). Only accent-as-TEXT darkens - for WCAG-AA on white. */ - --text-accent: color-mix(in srgb, var(--accent), #000 22%); /* darkened accent, ~AA on white */ - --text-green: #15683A; /* ~5.2:1 on white — affirmative text */ - - --accent-wash: color-mix(in srgb, var(--accent) 12%, transparent); - --accent-wash-2: color-mix(in srgb, var(--accent) 16%, transparent); - --accent-line: color-mix(in srgb, var(--accent-deep) 40%, transparent); - --glass: rgba(255, 255, 255, 0.72); - --code-bg: color-mix(in srgb, var(--accent) 8%, #fff); - - --shadow-card: 0 18px 44px -26px color-mix(in srgb, var(--accent-deep) 55%, transparent); - --shadow-accent: 0 12px 26px -10px color-mix(in srgb, var(--accent) 45%, transparent); - --shadow-toast: 0 18px 40px -16px rgba(40, 16, 30, 0.18); +.dark, +[data-theme="dark"] { + --bg: oklch(17.5% 0.020 340); /* aubergine ≈ #170d13 */ + --bg-grad: oklch(19.5% 0.021 340); + --card: oklch(21% 0.022 340); /* ≈ #1f141b */ + --card-soft: oklch(24.5% 0.022 340); /* ≈ #281c24 */ + + --border: color-mix(in srgb, var(--text) 18%, transparent); + --border-2: color-mix(in srgb, var(--text) 32%, transparent); + + --text: oklch(93% 0.008 60); /* warm cream ≈ #ece7e3 */ + --muted: oklch(75% 0.012 55); /* ≈ #b4aca7 */ + --faint: oklch(63% 0.012 50); /* ≈ #908783 */ + + --text-accent: var(--accent); + --text-green: #46B86C; + --display-accent: var(--accent); + + --glass: var(--bg); + --accent-wash: color-mix(in srgb, var(--accent) 10%, transparent); + --accent-wash-2: color-mix(in srgb, var(--accent) 15%, transparent); + --accent-line: color-mix(in srgb, var(--accent) 28%, transparent); + --code-bg: rgba(0, 0, 0, 0.35); + + --shadow-card: 0 1px 0 rgba(0, 0, 0, 0.5); + --shadow-toast: 0 6px 22px -10px rgba(0, 0, 0, 0.7); }