# Lazy Vacations — Design Tokens

The single source of truth for all visual styling. **Every visual value lives in a
CSS custom property (token).** Components and pages reference tokens and the `.t-*`
role classes only — they never hard-code a font, size, or colour.

> **This file is authoritative and current** (re-synced to the CSS).
> The companion `DEVELOPMENT.md` documents the **Palette Editor** that drives these
> tokens live, plus the architecture, logic, and invariants. Read both.

---

## 0 · The four stylesheets

| File | Owns | Loaded by |
|---|---|---|
| **`palette.css`** | colour **skins** (primitives) + derived **semantic colours** | `@import` at top of `type.css` |
| **`shape.css`** | radius · elevation · spacing/density · glass/scrim · wireframe-chrome colours | `@import` at top of `type.css` |
| **`type.css`** | font families, font roles, type scale, text colours, the `.t-*` classes, `.t-btn`/`.t-pill`, responsive | every design's `<helmet>` |
| **`lazyapp.css`** | the **editor chrome** UI kit (`.la-*`) — a *separate* system, never re-themed by skins | the Palette Editor only |

Loading is transitive — a page only needs `type.css`; it pulls in `palette.css`
and `shape.css` itself:

```html
<helmet>
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Kalam:wght@400;700&display=swap">
  <link rel="stylesheet" href="./type.css">   <!-- pulls in palette.css + shape.css -->
</helmet>
```

---

## 1 · Colour — `palette.css`

Two tiers, exactly like a real design system: **primitives** (5 raw values per
skin) and **derived semantic tokens** (everything components actually read,
computed from the primitives). Swap one attribute → the whole subtree re-themes.

### 1.1 Primitives — per skin
Applied with `data-skin="…"` high in the tree. Five colour values + a default hero
photo per skin:

| Skin | `--brand` | `--accent` | `--neutral` | `--on-brand` | `--on-accent` | `--hero-img` |
|---|---|---|---|---|---|---|
| **`rustic`** *(default)* | `#2f5a4a` | `#cc6b4a` | `#5e6158` | `#ffffff` | `#ffffff` | `hero-rustic.avif` |
| `coastal` | `#1f6f8b` | `#e3a23c` | `#5c6670` | `#ffffff` | `#21303a` | `hero-coastal.png` |
| `urban` | `#ff2d9b` | `#c6ff36` | `#6e6e77` | `#15151a` | `#15150f` | `hero-urban.avif` |
| `classy` | `#1c2b46` | `#c2a14d` | `#5a5d66` | `#ffffff` | `#1c2b46` | `hero-classy.jpg` |
| `cute` | `#13b3bd` | `#ff5aa8` | `#7d7680` | `#ffffff` | `#ffffff` | `hero-cute.avif` |

`rustic` is also declared on `:root`, so it is the fallback skin when no
`data-skin` is set.

### 1.2 Derived semantic tokens — components read ONLY these
Computed from the primitives with `color-mix`. **Declared on `*` (not `:root`)** —
this is load-bearing: a `:root` var freezes at the root, so overriding a primitive
on a *descendant* (`<div data-skin="coastal">`) would not re-derive. On `*`, every
element recomputes against its inherited skin. (This is exactly what lets the
editor's palette inspector preview each skin in its own colours.)

| Token | Derivation | Role |
|---|---|---|
| `--brand-strong` | brand + black 18% | hover / pressed brand |
| `--accent-strong` | accent + black 16% | hover / pressed accent |
| `--background` | neutral + white 94% | page background |
| `--surface` | `#ffffff` | cards & panels |
| `--surface-sunken` | neutral + white 88% | insets, fills, placeholders |
| `--surface-tint` | brand + white 90% | brand-tinted fills |
| `--border` | neutral + white 70% | hairlines, inner grids |
| `--border-strong` | neutral + white 55% | main outlines |
| `--text-muted` | neutral + white 30% | captions, labels |
| `--text` | neutral + black 55% | headings & body |
| `--on-brand` / `--on-accent` | *(primitive)* | text/icons on a brand/accent fill |

**Recipes**
- *Re-theme a page / subtree:* `data-skin="coastal"` on its wrapper.
- *Tweak one colour without changing skin:* set the primitive inline on the
  wrapper, e.g. `style="--brand:#1f6fff"` — inline beats the skin block and the
  derived tokens recompute.
- *Edit a skin:* change the 5 values in its `[data-skin="…"]` block.
- *Add a skin:* copy a `[data-skin]` block, rename, set 5 values + `--hero-img`;
  add `images/hero-<key>.*`; add a `SKINS` row in the Palette Editor (see
  `DEVELOPMENT.md §7`).

---

## 2 · Type — `type.css`

Three tiers: **families** (one token per typeface) → **roles** (3 jobs, each points
at a family) → **scale** (size/leading/weight/tracking). Restyle a whole job in one
line by re-pointing its role.

### 2.1 Families — one token per typeface
| Token | Default | Used by |
|---|---|---|
| `--family-serif` | `'Source Serif 4', Georgia, serif` | heading role |
| `--family-sans` | `'Source Sans 3', system-ui, sans-serif` | body role |
| `--family-lato` | `'Lato', system-ui, sans-serif` | label role |
| `--family-sketch` | `'Kalam', cursive` | wireframe annotation chrome only |

### 2.2 Roles — 3 jobs
| Role token | Default family | Drives |
|---|---|---|
| `--font-heading` | `--family-serif` | display · section · card title · price |
| `--font-body` | `--family-sans` | lead · body · caption · link · chip · input |
| `--font-label` | `--family-lato` | eyebrow · button · field label |

> The **Palette Editor** swaps typefaces by re-pointing these three role tokens at
> whole font stacks (its `PAIRS` catalog), **not** by editing the family tokens.
> Either approach works; per-page you can also set a role inline on the wrapper.

### 2.3 Type scale
Per-role **size / line-height**, plus shared **weight** and **tracking** tokens.

| Role | Size token | Value | Leading token | Value | Weight | Tracking |
|---|---|---|---|---|---|---|
| Display | `--t-size-display` | 32px | `--t-leading-display` | 1.15 | bold | — |
| Section | `--t-size-section` | 26px | `--t-leading-section` | 1.2 | bold | — |
| Card title | `--t-size-card-title` | 19px | `--t-leading-card-title` | 1.25 | bold | — |
| Lead | `--t-size-lead` | 15px | `--t-leading-lead` | 1.4 | regular | — |
| Body | `--t-size-body` | 16px | `--t-leading-body` | 1.55 | regular | — |
| Caption | `--t-size-caption` | 13px | `--t-leading-caption` | 1.45 | regular | — |
| Eyebrow | `--t-size-eyebrow` | 11px | — | — | bold | `--t-track-label` 1.5px |
| Button | `--t-size-button` | 15px | — | — | bold | `--t-track-button` .5px |
| Link | `--t-size-link` | 15px | — | — | medium | — |
| Chip | `--t-size-chip` | 15px | — | — | regular | — |
| Field label | `--t-size-field-label` | 12px | — | — | bold | `--t-track-label` 1.5px |
| Input | `--t-size-input` | 16px | — | — | regular | — |
| Price | `--t-size-price` | 20px | — | — | bold | — |

Weights: `--t-weight-regular` 400 · `--t-weight-medium` 600 · `--t-weight-bold` 700.

### 2.4 Text colours
Wired on `*` so `.t-*` text follows the active skin. Each falls back to a literal
if the skin token is missing:

| Token | Follows | Used by |
|---|---|---|
| `--t-color-display` / `-card-title` / `-lead` / `-body` / `-chip` / `-input` | `--text` | the dark text roles |
| `--t-color-caption` / `-label` / `-input-muted` | `--text-muted` | muted roles |
| `--t-color-brand` | `--brand` | section titles, links, `.t-strong` |
| `--t-color-white` `#fff` · `--t-color-ink` `#444` | *(fixed)* | button text helpers |

### 2.5 Role classes — `.t-*`
**One class per text element. Never inline `font-*`/`color` on text.**

`.t-display` · `.t-section` · `.t-card-title` · `.t-price` *(heading font)* ·
`.t-lead` · `.t-body` · `.t-caption` · `.t-link` · `.t-chip` · `.t-input`
(`.t-input.muted` = placeholder) *(body font)* · `.t-eyebrow` · `.t-button` ·
`.t-field-label` *(label font, all-caps)* · `.t-strong` (bold brand-green run
inside body copy).

**Colour helpers** for role-less colour (buttons): `.tc-teal` (brand) · `.tc-white`
· `.tc-ink` · `.tc-on-brand` · `.tc-on-accent`.
e.g. filled CTA label = `class="t-button tc-white"`, outline = `t-button tc-teal`.

### 2.6 Buttons & pills
`.t-btn` and `.t-pill` compose **intent × style × size**:

- **Intent:** `--brand` (default) · `--brand` (`.t-btn--brand`) · `.t-btn--accent`.
- **Style (btn):** `--filled` · `--outline` · `--ghost` · `--inverse` ·
  `--inverse-outline` · `--inverse-ghost`. **Style (pill):** filled (default) ·
  `--outline` · `--inverse` · `--inverse-outline`; add `--static` for non-clickable
  (no hover).
- **Size:** `--md` (page CTAs) · `--sm` (header).
- Both read **`--btn-radius`** (fallback `--radius-sm`) — this is the hook the
  editor's radius control writes. Hover/active states are pre-wired.

### 2.7 Icon
`.t-icon` — monochrome CSS mask, colour = `currentColor`, size via `--icon-size`
(default 24px), image via `--icon`.

---

## 3 · Structure — `shape.css`

| Group | Tokens |
|---|---|
| **Radius** | `--radius-sm` 6 · `--radius` 10 · `--radius-lg` 16 · `--radius-pill` 999 |
| **Elevation** | `--elev-1` (small) · `--elev-2` (resting card) · `--elev-3` (raised/hover) |
| **Spacing scale** | `--space-1` 4 → `--space-7` 48 |
| **Density (semantic)** | `--gap` 16 · `--pad-card` 22 · `--pad-section` 30 |
| **Glass / scrim** | `--glass-bg` · `--glass-border` · `--glass-blur` 14px · `--glass-shadow` · `--scrim` |
| **Wireframe chrome** *(fixed)* | `--wf-violet` `#7c4dff` · `--wf-ink` `#3a4a47` · `--wf-badge-shadow` |

The editor's **Button radius** control overrides `--btn-radius` (consumed by
`.t-btn`/`.t-pill`); the `--radius-*` scale is the underlying ladder. The `--wf-*`
values are **fixed annotation-scaffolding** colours — they stay constant under every
skin so the wireframe labelling reads the same. (A few older components still inline
these literals, e.g. `#3a4a47` / `#9aa29e` greybox tones — harmless, but prefer the
`--wf-*` tokens / `var(--surface-sunken)` in new work.)

---

## 4 · Editor chrome — `lazyapp.css` (`.la-*`)

A **separate brutalist UI kit** for the tool shell. **It is not part of the site
palette and must never be wired to it** — otherwise the editor would re-theme every
time the user picks a skin. White surfaces, grey thin frames, small hard shadows,
teal/coral accents; everything is a `--la-*` token.

Tokens: `--la-paper` `--la-bg` `--la-ink` `--la-line` `--la-shadow` `--la-teal`
`--la-coral` `--la-font` `--la-frame` `--la-shadow-size` `--la-radius`.
A `[data-theme="dark"]` block remaps paper/bg/ink/line/shadow (the editor's moon/sun
toggle flips this).
Classes: `.la-app` · `.la-panel` · `.la-title` · `.la-label` · `.la-btn`
(`+ --teal` / `--coral`, `[aria-pressed="true"]`) · `.la-swatch` · `.la-iconbtn`.

---

## 5 · Responsive — `type.css §8`

Container queries on the `.lv-page` (`container-name: page`). Inline layout styles
win over sheet rules, so overrides use `!important`.

| Breakpoint | Effect |
|---|---|
| ≤ 900px | two-column body → single column; left rail un-stickies, drops below content |
| ≤ 768px | tighter padding; nav → hamburger drawer (`.lv-burger` / `.lv-nav.is-open`); split hero mosaic → stack; photo grids → 2-up; hero card/media shrink |
| ≤ 430px | photo grids → 1-up; smaller display type |

---

## 6 · Quick reference — "to change X, edit Y"

| Want to change… | Edit |
|---|---|
| Heading typeface | `--family-serif` (or re-point `--font-heading`) |
| Body typeface | `--family-sans` (or re-point `--font-body`) |
| Button/label typeface | `--family-lato` (or re-point `--font-label`) |
| A role's size | `--t-size-<role>` |
| Brand / accent colour | a skin's primitive in `palette.css`, or set `--brand`/`--accent` inline per page |
| Whole palette | `data-skin="…"` on the wrapper |
| Button corner radius | `--btn-radius` |
| Card padding / grid gap | `--pad-card` / `--gap` |
| Annotation chrome colour | `--wf-*` in `shape.css` |

---

## 7 · Rules (don't regress)

1. **One class per text element.** Never inline `font-family`/`font-size`/`color`
   on text — use a `.t-*` role.
2. **Components read semantic / derived tokens only** — never a skin primitive or
   raw hex where a token exists.
3. **Derived colour tokens stay declared on `*`** (not `:root`) so skins re-derive
   on descendants.
4. **Editor chrome (`.la-*`) is isolated** from the site palette.
5. **Edit tokens, not classes.** Restyle by changing token values; the role classes
   are assembled from them.
6. Adding a skin/font/radius is a token-only change plus (for the editor) one
   catalog row — see `DEVELOPMENT.md §7`.
