diff --git a/.changeset/beetroot-riso-rebrand.md b/.changeset/beetroot-riso-rebrand.md new file mode 100644 index 0000000..6b667f5 --- /dev/null +++ b/.changeset/beetroot-riso-rebrand.md @@ -0,0 +1,10 @@ +--- +"@btravstack/theme": minor +--- + +Beetroot Riso rebrand — the theme becomes a risograph-printed contract. + +- Light cream paper (`oklch(96.5% 0.010 353)`) is now the canonical scheme; dark is the "night edition" soil-black override on `.dark`. +- Typography: Bricolage Grotesque (display) + Geist (body) + JetBrains Mono (code) replace Montserrat. +- Square corners everywhere (`--radius-*: 0`), hard-offset shadows (no blur, no glow), 2px ink rules, solid non-frosted navbar, solid ink hero name (no gradient text, no hero glow/float). +- 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`. diff --git a/.hallmark/log.json b/.hallmark/log.json new file mode 100644 index 0000000..17649cd --- /dev/null +++ b/.hallmark/log.json @@ -0,0 +1,14 @@ +[ + { + "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 — landing + @btravstack/theme + logos (Beetroot Riso, design.md locked)" + } +] diff --git a/apps/website/.vitepress/theme/Landing.vue b/apps/website/.vitepress/theme/Landing.vue index 5c7c877..e0390c2 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: "§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." }, ]; const aiCards = [ @@ -106,111 +106,101 @@ onMounted(() => { diff --git a/apps/website/public/favicon.svg b/apps/website/public/favicon.svg index b7615ac..52c7efc 100644 --- a/apps/website/public/favicon.svg +++ b/apps/website/public/favicon.svg @@ -1,12 +1 @@ - - - - - - - - - - - - \ 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..a6053b1 100644 --- a/apps/website/public/logos/amqp-contract-dark.svg +++ b/apps/website/public/logos/amqp-contract-dark.svg @@ -1 +1 @@ - + diff --git a/apps/website/public/logos/amqp-contract-light.svg b/apps/website/public/logos/amqp-contract-light.svg index 2aa8ee6..b54d345 100644 --- a/apps/website/public/logos/amqp-contract-light.svg +++ b/apps/website/public/logos/amqp-contract-light.svg @@ -1 +1 @@ - + diff --git a/apps/website/public/logos/btravstack-dark.svg b/apps/website/public/logos/btravstack-dark.svg index 89f4f6c..44ddd39 100644 --- a/apps/website/public/logos/btravstack-dark.svg +++ b/apps/website/public/logos/btravstack-dark.svg @@ -1 +1 @@ - + diff --git a/apps/website/public/logos/btravstack-light.svg b/apps/website/public/logos/btravstack-light.svg index 89f4f6c..44ddd39 100644 --- a/apps/website/public/logos/btravstack-light.svg +++ b/apps/website/public/logos/btravstack-light.svg @@ -1 +1 @@ - + diff --git a/apps/website/public/logos/demesne-dark.svg b/apps/website/public/logos/demesne-dark.svg index d8db6ab..9d25a59 100644 --- a/apps/website/public/logos/demesne-dark.svg +++ b/apps/website/public/logos/demesne-dark.svg @@ -1 +1 @@ - + diff --git a/apps/website/public/logos/demesne-light.svg b/apps/website/public/logos/demesne-light.svg index 0301a24..88be3d1 100644 --- a/apps/website/public/logos/demesne-light.svg +++ b/apps/website/public/logos/demesne-light.svg @@ -1 +1 @@ - + diff --git a/apps/website/public/logos/temporal-contract-dark.svg b/apps/website/public/logos/temporal-contract-dark.svg index 374fc70..d69cd77 100644 --- a/apps/website/public/logos/temporal-contract-dark.svg +++ b/apps/website/public/logos/temporal-contract-dark.svg @@ -1 +1 @@ - + diff --git a/apps/website/public/logos/temporal-contract-light.svg b/apps/website/public/logos/temporal-contract-light.svg index 0c88007..684e930 100644 --- a/apps/website/public/logos/temporal-contract-light.svg +++ b/apps/website/public/logos/temporal-contract-light.svg @@ -1 +1 @@ - + diff --git a/apps/website/public/logos/unthrown-dark.svg b/apps/website/public/logos/unthrown-dark.svg index b49df8a..76c5be8 100644 --- a/apps/website/public/logos/unthrown-dark.svg +++ b/apps/website/public/logos/unthrown-dark.svg @@ -1 +1 @@ - + diff --git a/apps/website/public/logos/unthrown-light.svg b/apps/website/public/logos/unthrown-light.svg index 9d0f2b3..ed7112e 100644 --- a/apps/website/public/logos/unthrown-light.svg +++ b/apps/website/public/logos/unthrown-light.svg @@ -1 +1 @@ - + diff --git a/design.md b/design.md new file mode 100644 index 0000000..45b8a25 --- /dev/null +++ b/design.md @@ -0,0 +1,131 @@ +# Design — btravstack · "Beetroot Riso" + +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.** The brand is a risograph-printed contract. Riso printing = flat +spot-colors on cream paper, hard edges, visible ink, slight misregistration — +and beetroot pink is a classic riso ink. btrav ~ betterave (beetroot); the pun +is carried by the ink, not by a mascot. Pages read as printed documents: +declarations, numbered clauses, spec-sheet tables, a colophon signature block. +"The signature is the doc" — literally. + +## Genre + +editorial — brutalist register. Vibe: *"beetroot risograph, contract-as-document, +raw, technical"*. + +## Macrostructure family + +- Marketing pages (landing): **Manifesto** — declaration hero (left-biased, + all-caps display), numbered clauses, ink-bleed blocks as section dividers, + oversized solid block CTA far below the fold. Packages presented as an + **F3 tabular spec-sheet**, never cards. +- Docs sites (VitePress content): default VitePress layout wearing the theme + tokens — square corners, 2 px rules, mono labels, flat blocks. No glow, no + float, no gradient text. +- Nav: **N7 brutal slab** (2 px ink border-bottom, tracked caps links). +- Footer: **Ft4 dense colophon** — a mono signature block, contract-style. + +## Theme + +Light "riso print" is canonical; dark "night edition" via `.dark`. +Only lightness/chroma move between schemes — the hue never changes. + +| Token | Light (canonical) | Dark | +| --- | --- | --- | +| `--bg` (paper) | `oklch(96.5% 0.010 353)` ≈ #f9f1f4 | `oklch(17% 0.022 335)` ≈ #150c13 | +| `--card` (paper-2) | `oklch(93.5% 0.014 353)` | `oklch(21% 0.024 335)` | +| `--text` (ink) | `oklch(21% 0.020 350)` ≈ #20151a | `oklch(93% 0.010 340)` | +| `--muted` | `oklch(38% 0.018 350)` | `oklch(76% 0.014 340)` | +| `--accent` | **#E0589A** = `oklch(65.5% 0.182 353)` — THE knob | same (fills stay vibrant) | +| `--text-accent` | `oklch(51% 0.19 355)` ≈ #b11e6b (AA on cream) | `var(--accent)` | +| `--green` (leaf) | #3DAE62 `oklch(66.8% 0.151 151)` | same | +| focus ring | `oklch(60% 0.21 353)` — 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`. + +Accent discipline: ≤ 5 % of any viewport. The oversized block CTA is the one +sanctioned large accent fill per page. + +## Typography + +- Display: **Bricolage Grotesque**, weight 700–800, roman, uppercase for + manifesto headlines, tracking −0.02em. Never italic headers. +- Body: **Geist**, weight 400 (350 visual weight on dark via 400 + dark scheme). +- Mono (outlier): **JetBrains Mono** — code and clause numerals only. +- Display scale anchor: `--fs-hero: clamp(44px, 7.5vw, 92px)`; all-caps display + line-height 1.02–1.08. +- 2+1 rule: exactly these three families, nothing else. + +## Spacing / shape + +4-pt-ish named scale in `packages/theme/src/tokens.css` (`--space-1…6`). Pages +use tokens, never raw values. +Corners: **square** (`--radius-*: 0`). Shadows: **hard offsets only** +(`4px 4px 0 `), no blur, no glow ever. +Rules: 2 px solid ink for structure; hairlines only inside dense tables. + +## Motion + +- Easings: `--ease-out: cubic-bezier(0.16, 1, 0.3, 1)`; durations ≤ 0.2 s. +- Stance: near-none. State changes are instant flips (ink↔paper inversion on + hover). No scroll reveals, no float loops, no glow pulses. +- Reduced-motion: everything already ≤ 150 ms opacity/color; nothing spatial. + +## Microinteractions stance + +- Silent success (copy button shows a small mono toast, 1.6 s, no celebration). +- Hover tooltips 800 ms · focus 0 ms. Focus rings instant, 2 px solid focus color. + +## CTA voice + +- Primary: solid block, square, ink-on-accent or paper-on-ink, uppercase + Bricolage, generous padding. One oversized block CTA per page, far below fold. +- Secondary: C3 typographic link — word + arrow + 2 px underline. No pills, + no gradients, no rounded buttons anywhere. + +## Logos + +Flat riso marks: solid spot-color shapes (project accent + leaf green + ink), +hard edges, **no gradients, no radial highlights**, one deliberate +misregistration offset layer (deep shade, offset ~3 px) as the riso signature. +Each project keeps its concept (rabbit, clock, no-throw, keep) rebuilt in this +construction. Per-scheme variants only swap the "cutout" color to the paper. + +## What pages MUST share + +- The wordmark voice (Bricolage 800, "btrav" in accent + "stack" in ink). +- The one-knob accent architecture and its ≤ 5 % footprint. +- The three fonts, square corners, hard-offset shadows, 2 px rule language. +- The colophon-style footer voice. + +## What pages MAY differ on + +- Macrostructure within the family (a future marketing page may be Long + Document — 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(96.5% 0.010 353); + --color-ink: oklch(21% 0.020 350); + --color-accent: #E0589A; + --font-display: "Bricolage Grotesque", sans-serif; + --font-body: "Geist", sans-serif; + --font-mono: "JetBrains Mono", monospace; + --ease-out: cubic-bezier(0.16, 1, 0.3, 1); +} +``` diff --git a/packages/theme/src/fonts.css b/packages/theme/src/fonts.css index 39e3d6d..caca748 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 Riso" pairing (2+1 rule). + * Bricolage Grotesque (display, 700/800) — riso-bold declaration voice + * Geist (body/UI, 400–700) + * JetBrains Mono (code + clause numerals) + * Loaded from Google Fonts so the system stays zero-asset and portable. */ -@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=Bricolage+Grotesque:opsz,wght@12..96,600;12..96,700;12..96,800&family=Geist:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap'); diff --git a/packages/theme/src/style.css b/packages/theme/src/style.css index 3afaf6d..9e9e015 100644 --- a/packages/theme/src/style.css +++ b/packages/theme/src/style.css @@ -1,13 +1,16 @@ +/* Hallmark · genre: editorial (brutalist) · 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 Riso"). * * 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 → cream riso paper, deep-beet links (WCAG-AA on cream) + * - dark → soil-black "night edition", full spot-color 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); @@ -27,26 +30,25 @@ --vp-c-text-3: var(--faint); --vp-c-divider: var(--border); - --vp-c-border: var(--border-2); + --vp-c-border: var(--border); --vp-c-gutter: var(--border); /* 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: 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 spot-color FILL, both schemes */ --vp-button-brand-bg: var(--accent); --vp-button-brand-text: var(--accent-contrast); --vp-button-brand-border: transparent; @@ -65,105 +67,75 @@ --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 — no gradient text, no glow (riso prints flat) */ + --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. */ +/* ── Riso chrome — shared across every btravstack site ─────────── */ + +/* No horizontal scroll, ever (clip — not hidden — so sticky keeps working). */ +html, body { overflow-x: clip; } + +/* Display voice on the big headings; docs body stays Geist for reading. */ +.VPHero .name, +.VPHero .text { font-family: var(--display); letter-spacing: var(--tracking-hero); } +.VPHero .name { font-weight: 800; } + +.VPHome { overflow-x: clip; position: relative; } + +/* Navbar: solid paper, hard 2px ink rule — riso prints don't frost. */ +.VPNav, .VPNavBar { background: var(--bg) !important; } +.VPNavBar { backdrop-filter: none !important; -webkit-backdrop-filter: none !important; } +.VPNavBar.has-sidebar .content-body, +.VPNavBar .content-body { background: transparent !important; } +.VPNavBar:not(.home) { border-bottom: 2px solid var(--border-2); } +.VPNavBar .title { font-family: var(--display); font-weight: 800; } + +/* Navbar logo sizing via VitePress's own variable. */ +:root:root { --vp-nav-logo-height: 32px; } +@media (max-width: 768px) { :root:root { --vp-nav-logo-height: 26px; } } + +/* Feature cards: flat paper-2 blocks with a hard offset on hover. */ .VPFeature { - border: 1px solid var(--vp-c-divider); - border-radius: var(--radius-lg); - transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease; + border: 2px solid var(--border-2); + border-radius: 0; + transition: transform var(--speed) var(--ease), box-shadow var(--speed) var(--ease); } .VPFeature:hover { - transform: translateY(-4px); - border-color: var(--vp-c-border); + transform: translate(-3px, -3px); box-shadow: var(--shadow-card); } .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); -} -.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); } - -/* Rounder, pinker code */ -.vp-doc :not(pre) > code { border-radius: 7px; } -.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; + border-radius: 0 !important; + background: var(--accent-wash) !important; + border: 1px solid var(--accent-line); } -.VPHome .VPHero, -.VPHome .VPFeatures { position: relative; z-index: 1; } -@media (min-width: 960px) { - .VPHero .image-container { width: 340px; height: 340px; } - .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; - object-fit: contain; - } +.VPFeature .title { font-family: var(--display); letter-spacing: var(--tracking-tight); } + +/* Brand buttons: square slabs, instant press. */ +.VPButton { border-radius: 0 !important; font-weight: 700; } +.VPButton.brand { transition: background-color var(--speed-fast) var(--ease), transform var(--speed-fast) var(--ease); } +.VPButton.brand:active { transform: translateY(1px); } + +/* Square code + admonitions. */ +.vp-doc :not(pre) > code { border-radius: 0; } +.vp-doc div[class*="language-"] { border-radius: 0; } +.vp-doc .custom-block { border-radius: 0; border-width: 2px; } + +/* 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: 2px solid var(--border-2); + background: var(--bg); padding: 16px 24px; display: flex; justify-content: center; @@ -174,21 +146,17 @@ gap: 8px; font-size: 13px; font-weight: 600; + font-family: var(--mono); color: var(--vp-c-text-3); text-decoration: none; - transition: color 0.15s; + transition: color var(--speed-fast); } -.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: 800; letter-spacing: -0.01em; 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..e3560dc 100644 --- a/packages/theme/src/tokens.css +++ b/packages/theme/src/tokens.css @@ -1,85 +1,96 @@ +/* Hallmark · genre: editorial (brutalist) · macrostructure: Manifesto · design-system: design.md · designed-as-app + * theme: custom · vibe: "beetroot risograph, contract-as-document, raw, technical" + * paper: oklch(96.5% 0.010 353) · accent: oklch(65.5% 0.182 353) (#E0589A) + * display: Bricolage Grotesque 800 · body: Geist · outlier: JetBrains Mono + * axes: light / display-heavy / chromatic-other (beetroot-pink ~353°) + * nav: N7 brutal slab · footer: Ft4 dense colophon · studied: no · context: user-provided + * pre-emit critique: P5 H4 E4 S5 R4 V5 + */ + /* - * btravstack design tokens — the single source of truth. + * btravstack design tokens — the single source of truth ("Beetroot Riso"). * - * 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. + * The brand is a risograph-printed contract: flat spot-colors on beet-tinted + * cream paper, hard edges, square corners, hard-offset shadows, 2px ink rules. + * 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. * - * 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 ("riso print") is the canonical scheme. The dark "night edition" + * overrides the semantic tokens on `.dark` / `[data-theme="dark"]` + * (VitePress's appearance system). * - * Fills vs text: keep --accent / --green for FILLS (buttons, glow, 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. + * Fills vs text: keep --accent / --green for FILLS (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 cream. */ :root { - /* ── Brand palette (raw beetroot) ─────────────────────────────── */ - --bt-pink: #E0589A; /* beetroot flesh — primary accent */ - --bt-pink-soft: #EE9CC4; /* highlight veins */ + /* ── Brand palette (raw beetroot — riso spot-colors) ─────────── */ + --bt-pink: #E0589A; /* beetroot ink — primary accent, oklch(65.5% .182 353) */ + --bt-pink-soft: #EE9CC4; /* light overprint */ --bt-magenta: #CE3D80; /* mid root */ - --bt-deep: #8E1A52; /* dark root */ + --bt-deep: #8E1A52; /* deep root — misregistration layer */ --bt-deep-2: #7A1646; /* root tail */ - --bt-leaf: #3DAE62; /* center leaf */ - --bt-leaf-dark: #2C8B4E; /* side leaves */ + --bt-leaf: #3DAE62; /* leaf green spot */ + --bt-leaf-dark: #2C8B4E; /* leaf shadow */ --bt-green: #46B86C; /* "success" green (checks) */ - --bt-cream: #F5EFF3; /* near-white text */ - --bt-ink: #1a0c14; /* near-black, for text on accent */ + --bt-cream: #F9F1F4; /* riso paper */ + --bt-ink: #20151A; /* beetroot-black 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 — the canonical riso print) ──────── */ + --bg: oklch(96.5% 0.010 353); /* cream paper ≈ #f9f1f4 */ + --bg-grad: oklch(94% 0.016 353); /* legacy alias — flat wash, no glow */ + --card: oklch(93.5% 0.014 353); /* paper-2 ≈ #f2e6ea */ + --card-soft: oklch(91% 0.016 353); /* paper-3 ≈ #ebdde2 */ - /* ── Borders / dividers ──────────────────────────────────────── */ - --border: rgba(255, 255, 255, 0.08); - --border-2: rgba(255, 255, 255, 0.12); + /* ── Rules / dividers — decisive, not misty ──────────────────── */ + --border: color-mix(in srgb, var(--text) 24%, transparent); /* table hairline */ + --border-2: var(--text); /* 2px structural rule */ /* ── Text ────────────────────────────────────────────────────── */ - --text: #F5EFF3; - --muted: #AEA1BC; - --faint: #7E7290; + --text: oklch(21% 0.020 350); /* ink ≈ #20151a */ + --muted: oklch(38% 0.018 350); /* ≈ #4a3f44, ~8:1 on paper */ + --faint: oklch(47% 0.016 350); /* ≈ #62575c, ~5.5:1 on paper */ - /* ── 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 spot-color. */ --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: #20151A; /* ink on an accent fill */ + --accent-deep: color-mix(in srgb, var(--accent), #000 42%); /* misregistration / press */ + --accent-hover: color-mix(in srgb, var(--accent), #000 18%); /* fill hover */ + --accent-soft: color-mix(in srgb, var(--accent) 62%, #fff); /* light overprint */ + --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 cream */ + --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 — the accent as the LARGE display wordmark color + (large text only needs 3:1, so it can stay full spot-color). */ --display-accent: var(--accent); - /* ── Glass (frosted sticky headers) ──────────────────────────── */ - --glass: rgba(21, 16, 28, 0.66); + /* ── Focus — instant, never animated ─────────────────────────── */ + --focus: color-mix(in srgb, var(--accent), #000 12%); + + /* ── Header surface (solid — riso prints don'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) 12%, transparent); + --accent-wash-2: color-mix(in srgb, var(--accent) 18%, transparent); + --accent-line: color-mix(in srgb, var(--accent-deep) 45%, transparent); + --code-bg: color-mix(in srgb, var(--accent) 8%, 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: 'Bricolage Grotesque', 'Segoe UI', system-ui, sans-serif; + --sans: 'Geist', 'Segoe UI', system-ui, sans-serif; + --mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace; /* ── Typography: weights ─────────────────────────────────────── */ --fw-medium: 500; @@ -87,15 +98,15 @@ --fw-bold: 700; --fw-extrabold: 800; - /* ── Typography: display / heading scale (clamped, fluid) ─────── */ - --fs-hero: clamp(52px, 9vw, 92px); + /* ── Typography: display / heading scale (clamped, fluid) ────── */ + --fs-hero: clamp(44px, 7.5vw, 92px); --fs-h1: clamp(34px, 6vw, 56px); --fs-h2: clamp(28px, 4vw, 40px); --fs-h3: 23px; --fs-h4: 18px; /* ── Typography: body / UI scale ─────────────────────────────── */ - --fs-lead: clamp(20px, 2.6vw, 26px); + --fs-lead: clamp(19px, 2.4vw, 24px); --fs-body-lg: 17px; --fs-body: 15px; --fs-body-sm: 14px; @@ -103,21 +114,21 @@ --fs-eyebrow: 13px; /* ── Typography: tracking + line heights ─────────────────────── */ - --tracking-hero: -2.5px; - --tracking-tight: -1px; - --tracking-snug: -0.5px; - --tracking-eyebrow: 1.5px; - --lh-tight: 1.08; + --tracking-hero: -0.02em; + --tracking-tight: -0.01em; + --tracking-snug: -0.005em; + --tracking-eyebrow: 0.1em; + --lh-tight: 1.05; --lh-snug: 1.3; --lh-body: 1.6; - /* ── Radii ───────────────────────────────────────────────────── */ - --radius-sm: 9px; - --radius: 12px; - --radius-lg: 16px; - --radius-xl: 20px; - --radius-2xl: 24px; - --radius-pill: 999px; + /* ── Radii — square. The riso press has no rounded dies. ─────── */ + --radius-sm: 0px; + --radius: 0px; + --radius-lg: 0px; + --radius-xl: 0px; + --radius-2xl: 0px; + --radius-pill: 0px; /* ── Spacing scale ───────────────────────────────────────────── */ --space-1: 6px; @@ -127,51 +138,48 @@ --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 — hard offsets only. No blur, no glow, ever. ────── */ + --shadow-card: 6px 6px 0 color-mix(in srgb, var(--text) 88%, transparent); + --shadow-accent: 4px 4px 0 var(--accent-deep); + --shadow-toast: 4px 4px 0 color-mix(in srgb, var(--text) 88%, transparent); /* ── Layout & motion ─────────────────────────────────────────── */ --container: 1180px; - --speed-fast: 0.15s; - --speed: 0.2s; - --ease: ease; + --speed-fast: 0.1s; + --speed: 0.15s; + --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 — the "night edition": same print, soil-black paper. Only the + * semantic tokens flip; the raw --bt-* palette and the hue never change. + * Brand FILLS stay vibrant (dark ink on top); accent-as-TEXT can stay full + * spot-color on the dark paper. */ -: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 */ +.dark, +[data-theme="dark"] { + --bg: oklch(17% 0.022 335); /* soil ≈ #150c13 */ + --bg-grad: oklch(19% 0.024 335); + --card: oklch(21% 0.024 335); /* ≈ #1f141d */ + --card-soft: oklch(24.5% 0.024 335); /* ≈ #281c25 */ - --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); + --border: color-mix(in srgb, var(--text) 26%, transparent); + --border-2: var(--text); + + --text: oklch(93% 0.010 340); /* ≈ #ede5ea */ + --muted: oklch(76% 0.014 340); /* ≈ #b8aeb4 */ + --faint: oklch(64% 0.012 340); /* ≈ #91898e */ + + --text-accent: var(--accent); /* full spot-color reads fine on soil */ + --text-green: #46B86C; + + --glass: var(--bg); + --accent-wash: color-mix(in srgb, var(--accent) 14%, transparent); + --accent-wash-2: color-mix(in srgb, var(--accent) 20%, transparent); + --accent-line: color-mix(in srgb, var(--accent) 30%, transparent); + --code-bg: rgba(0, 0, 0, 0.35); + + --shadow-card: 6px 6px 0 rgba(0, 0, 0, 0.85); + --shadow-accent: 4px 4px 0 var(--accent-deep); + --shadow-toast: 4px 4px 0 rgba(0, 0, 0, 0.85); }