Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/beetroot-soft-rebrand.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@btravstack/theme": minor
---

Beetroot Soft rebrand — warm, rounded, soft-but-exact.

- Light warm near-white paper (`oklch(97.5% 0.008 353)`) is now the canonical scheme; dark stays the soft plum, activated on `.dark`.
- Typography: Plus Jakarta Sans (display) + Geist (body) + JetBrains Mono (code) replace Montserrat.
- Rounded, friendly shape language: 12px card corners, pill buttons, soft accent-tinted shadows, hairline rules, hover-lifts; solid ink hero name (no gradient text), gently floating hero mark preserved as the one character moment.
- 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`.
26 changes: 26 additions & 0 deletions .hallmark/log.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[
{
"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 (playful), replaces rejected Beetroot Riso"
},
{
"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)"
}
]
610 changes: 324 additions & 286 deletions apps/website/.vitepress/theme/Landing.vue

Large diffs are not rendered by default.

128 changes: 128 additions & 0 deletions design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Design — btravstack · "Beetroot Soft"

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.** Software can be soft and exact at once. The beetroot is a
charming character, not a logo stamp — one gentle character moment per page
(the floating mascot), warm near-white paper, white card lifts, 12px corners,
soft accent-tinted shadows, and a friendly-but-precise voice. Never childish,
never quirk-for-quirk. (Supersedes the rejected "Beetroot Riso" brutalist
system — hard edges, flat riso marks, and all-caps declarations are out.)

## Genre

playful (post-Linear soft, Hum-adjacent). Vibe: *"warm, rounded, charming
beetroot, soft-but-exact"*.

## Macrostructure family

- Marketing pages (landing): **Bento Grid** — compact centered hero (mascot +
one-line headline + pill CTAs), then one friendly mosaic of mixed-span tiles
(intro, principles, packages, "why now" cluster, inspirations, CTA tile).
- Docs sites (VitePress content): default VitePress layout wearing the theme
tokens — rounded cards, soft hover-lifts, floating hero mark, tinted icon
chips.
- Nav: **N1b** — wordmark left, centered links, toggle + filled pill CTA
right, soft frost.
- Footer: **Ft8 marquee** — gently scrolling brand line (static under
`prefers-reduced-motion`), with a compact meta/link row beneath.

## Theme

Light is canonical; dark is the soft plum the brand grew up on.
Only lightness/chroma move between schemes — the hue family never changes.

| Token | Light (canonical) | Dark — plum |
| --- | --- | --- |
| `--bg` (paper) | `oklch(97.5% 0.008 353)` | `oklch(18.5% 0.025 310)` |
| `--card` | `oklch(99.5% 0.003 353)` white lift | `oklch(22% 0.026 310)` |
| `--text` (ink) | `oklch(24% 0.020 350)` | `oklch(94% 0.008 340)` |
| `--accent` | **#E0589A** = `oklch(65.5% 0.182 353)` — THE knob | same (fills stay vibrant) |
| `--text-accent` | accent darkened 28% (AA on paper) | `var(--accent)` |
| rules | soft hairlines (12–20% ink mix) | same recipe |
| focus ring | accent darkened 16% — 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: fills ≤ 5% of any viewport; large accent areas are washes
(`--accent-wash`, ≤ 14% opacity), never solid blocks.

## Typography

- Display: **Plus Jakarta Sans**, 700–800, roman, tracking −0.025em. Never
italic headers, never all-caps headlines.
- Body: **Geist**, 400.
- Mono (outlier): **JetBrains Mono** — code, package identifiers, small labels.
- Hero headline ≤ 50 chars at `--fs-hero: clamp(38px, 5.5vw, 60px)`.
- 2+1 rule: exactly these three families.

## Spacing / shape

Named px scale in `packages/theme/src/tokens.css` (`--space-1…6`). Corners:
cards `--radius-lg` (12px) max, inputs 8px, pills/chips 999px. Shadows: soft
accent-tinted (`0 8px 24px -10px`), no hard offsets, no glow halos, no
glassmorphism-as-decoration (frosted sticky nav is the one functional frost).

## Motion

- Easings: `--ease: cubic-bezier(0.16, 1, 0.3, 1)`; durations ≤ 0.2s.
- Three primitives per page, max: the mascot idle bob (the character moment),
card hover-lift (`translateY(-2px)` + shadow expansion), and the copy toast.
- No spring/overshoot on UI state. No scroll-triggered reveals.
- Reduced-motion: bob/marquee stop; state changes stay ≤ 150ms.

## Microinteractions stance

- Silent success (copy button → small toast, 1.6s, no celebration).
- Hover tooltips 800ms · focus 0ms. Focus rings instant, 2px `--focus`.

## CTA voice

- Primary: filled pill, accent fill + ink text, Plus Jakarta 700. One per view.
- Secondary: outlined pill or text link with arrow. No gradients, no springs.

## Logos

The original soft gradient marks are the canon — the beetroot mascot, rabbit,
hourglass, no-throw and shield keep their gradient warmth and rounded
construction. Do NOT flatten them into hard-edged print marks. Refinements
stay within the soft register.

## What pages MUST share

- The wordmark voice (Plus Jakarta 700+, "btrav" in accent-as-text + "stack" in ink).
- The one-knob accent architecture and its footprint discipline.
- The three fonts, 12px card corners, soft-shadow language, pill CTAs.
- The single character moment per page (never more than one).

## What pages MAY differ on

- Macrostructure within the family (a future marketing page may be Split
Studio 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(97.5% 0.008 353);
--color-ink: oklch(24% 0.020 350);
--color-accent: #E0589A;
--font-display: "Plus Jakarta Sans", sans-serif;
--font-body: "Geist", sans-serif;
--font-mono: "JetBrains Mono", monospace;
--ease: cubic-bezier(0.16, 1, 0.3, 1);
--radius-card: 12px;
}
```
10 changes: 6 additions & 4 deletions packages/theme/src/fonts.css
Original file line number Diff line number Diff line change
@@ -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 Soft" pairing (2+1 rule).
* Plus Jakarta Sans (display, 600–800) — rounded humanist, warm, never childish
* Geist (body/UI, 400–700)
* JetBrains Mono (code + labels)
* 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=Plus+Jakarta+Sans:wght@600;700;800&family=Geist:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');
140 changes: 62 additions & 78 deletions packages/theme/src/style.css
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
/* Hallmark · genre: playful · 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 Soft").
*
* 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 near-white paper, deep-beet links (WCAG-AA on white)
* - dark → the soft plum surfaces, 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.
Expand Down Expand Up @@ -38,8 +41,7 @@
--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);

Expand All @@ -65,52 +67,43 @@
--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%);
/* Home hero: solid ink name (no gradient text) + a soft accent halo that
tracks the logo wherever the layout places it. */
--vp-home-hero-name-color: var(--vp-c-text-1);
--vp-home-hero-name-background: none;
--vp-home-hero-image-background-image: radial-gradient(closest-side, color-mix(in srgb, var(--accent) 22%, transparent), transparent 72%);
--vp-home-hero-image-filter: blur(8px);
}

/* ── 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. */
/* ── Soft 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; }
.VPFeature .title { font-family: var(--display); letter-spacing: var(--tracking-snug); }

/* Home hero: the gently floating mark — the one character moment per page. */
.VPHome { overflow-x: clip; position: relative; }
.VPHero .image-container { animation: btv-floaty 6s var(--ease) infinite; }
.VPHero .image-src { filter: drop-shadow(0 14px 30px color-mix(in srgb, var(--accent-deep) 40%, transparent)); }
@keyframes btv-floaty { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-9px); } }

/* 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: hairline border + a soft lift on hover; brand-tinted icon chip. */
.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;
transition: transform var(--speed) var(--ease), border-color var(--speed) var(--ease), box-shadow var(--speed) var(--ease);
}
.VPFeature:hover {
transform: translateY(-4px);
transform: translateY(-2px);
border-color: var(--vp-c-border);
box-shadow: var(--shadow-card);
}
Expand All @@ -119,47 +112,38 @@
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); }
/* Brand buttons: soft lift, no bounce. */
.VPButton { font-weight: 600; }
.VPButton.brand { transition: background-color var(--speed-fast) var(--ease), transform var(--speed-fast) var(--ease); }
.VPButton.brand:hover { transform: translateY(-1px); }
.VPButton.brand:active { transform: translateY(0); }

/* Rounder, pinker code */
.vp-doc :not(pre) > code { border-radius: 7px; }
/* Rounded code + admonitions. */
.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: 320px; height: 320px; }
.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: 280px;
height: 280px;
max-width: 280px;
max-height: 280px;
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);
Expand All @@ -176,18 +160,18 @@
font-weight: 600;
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:focus-visible {
outline: 2px solid var(--accent);
outline: 2px solid var(--focus);
outline-offset: 3px;
border-radius: 4px;
border-radius: var(--radius-sm);
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); }
.btv-partof-word { font-family: var(--display); font-weight: 700; letter-spacing: -0.01em; color: var(--vp-c-text-2); }
.btv-partof-pink { color: var(--text-accent); }

@media (prefers-reduced-motion: reduce) {
.VPHero .image-container { animation: none; }
Expand Down
Loading