# Ludo web copy UI guide

> How to build marketing pages and long-form reading surfaces in Ludo style: 45 to 75 character measure, the web type rules, computed AA contrast for the zinc ramp, link and CTA conventions, and the print stylesheet every printable page needs.

Source of truth: `apps/ui/tokens/ludo.tokens.json` v0.1.0, mapped to utilities by `registry/ludo/theme.css`. Served from https://ui.ludo.computer/guides/web-copy.md. Generated 2026-07-26. Self-contained: every value is inline, no images required.

This surface fronts a revenue business. Procurement teams quote this copy back in negotiations, so every number must trace to a source and every label must say what it does.

## Use these tokens

```css
/* From tokens/dist/tokens.css (97 --ludo-* variables). The ones web copy touches most: */
--ludo-color-primary: #825FFF;            /* accents, large display, fills */
--ludo-color-primary-readable: #7351EF;   /* inline links and small purple text on white, 5.06:1 */
--ludo-color-primary-action: #7351EF;     /* button fills */
--ludo-color-primary-action-hover: #6F4FEC;
--ludo-color-primary-action-pressed: #5A3FD0;
--ludo-color-primary-soft-tint: #F4F2FF;  /* faint purple wash sections */
--ludo-color-text-primary: #09090B;       /* body */
--ludo-color-text-muted: #71717A;         /* captions; 4.83:1 on white, do not go lighter for text */
--ludo-color-text-subtle: #A1A1AA;        /* 2.56:1: decorative and large elements only */
--ludo-color-surface-background: #F7F7F7;
--ludo-color-surface-background-secondary: #FAFAFA;
--ludo-color-surface-card: #FFFFFF;
--ludo-color-surface-border: #EBEBEB;
--ludo-color-surface-border-strong: #D4D4D8;
--ludo-radius-sm: 4px; --ludo-radius-md: 6px; --ludo-radius-lg: 8px; --ludo-radius-xl: 12px;
--ludo-duration-fast: 150ms; --ludo-duration-base: 200ms; --ludo-duration-slow: 300ms;
--ludo-ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
```

Install the full theme and components from the registry: add `"registries": { "@ludo": "https://ui.ludo.computer/r/{name}.json" }` to components.json, then `npx shadcn@latest add @ludo/theme` before any component. `theme.css` also remaps font weights so nothing renders heavier than 600.

## Layout

- Measure: 45 to 75 characters per line, 66 ideal (Bringhurst, The Elements of Typographic Style). In CSS: `max-width: 65ch` on article bodies, up to `72ch` for wide editorial. Never full-viewport text lines; an unconstrained 1440 px line runs past 100 characters and wrecks the return sweep.
- Reflow: the page works at 320 CSS px width with no horizontal scroll (WCAG 1.4.10) and at 200% zoom without loss (WCAG 1.4.4).
- Rhythm: spacing follows Tailwind v4 default steps (4 px base: 1 / 2 / 3 / 4 / 6 / 8 / 10 / 12). Sections breathe; subheads every 2 to 4 paragraphs.
- Cards and containers: #FFFFFF on #F7F7F7 or #FAFAFA, border #EBEBEB, radius 8 px (lg) for cards, 12 px (xl) for feature panels.
- One idea per section. Real numbers in headings where they exist.

## Type

The token source deliberately carries no type-size scale; web uses the Tailwind v4 defaults under these rules. Geist throughout, Geist Mono for figures and code.

| Role | Size / line height | Weight | Colour |
|---|---|---|---|
| Display / hero | 48 to 60 px / 1.1 | 500 | #09090B |
| H1 | 36 px / 1.2 | 500 | #09090B |
| H2 | 30 px / 1.25 | 500 | #09090B |
| H3 | 24 px / 1.3 | 500 | #09090B |
| Long-form body | 18 px / 1.5 | 400 | #09090B or #3F3F46 |
| UI body (minimum) | 16 px / 1.5 | 400 | #09090B |
| Caption / meta | 14 px / 1.45 | 400 | #71717A |
| Fine print floor | 12 px | 400 | #71717A |

- Weight cap 600: headings 500, emphasis 600. theme.css enforces the remap (bold and heavier resolve to 600). Never fight it.
- Body line height 1.5 (WCAG 1.4.8 guidance; layouts must also tolerate WCAG 1.4.12 text-spacing overrides).
- Large text for contrast purposes: 24 px regular or 18.66 px at weight 600 and above (WCAG 1.4.3 definition, adapted to the 600 cap).

## Colour

Computed WCAG 2.2 contrast for the zinc ramp, on white (#FFFFFF) and on zinc-50 (#FAFAFA, the secondary background):

| Token | Hex | On white | On #FAFAFA | Verdict for text |
|---|---|---|---|---|
| zinc-400 (subtle) | #A1A1AA | 2.56:1 | 2.46:1 | Fails AA. Decorative and large-display only |
| zinc-500 (muted) | #71717A | 4.83:1 | 4.63:1 | Passes AA body. Captions and meta, nothing lighter |
| zinc-600 | #52525B | 7.73:1 | 7.41:1 | Passes AAA. Secondary body text |
| zinc-700 | #3F3F46 | 10.44:1 | 10.01:1 | Long-form body alternative |
| zinc-800 | #27272A | 14.89:1 | 14.27:1 | Strong body |
| zinc-900 | #18181B | 17.72:1 | 16.97:1 | Headings, dark surfaces |
| zinc-950 (text primary) | #09090B | 19.90:1 | 19.06:1 | Default body and headings |

Purple rules, computed:

- #825FFF on white: 4.17:1. Fails AA for body-size text. Use it for fills, icons, display type at 24 px+, and stat numerals.
- #7351EF on white: 5.06:1. The inline link and small-purple-text colour. Underline inline links; colour alone is not a link affordance (WCAG 1.4.1).
- White on #7351EF: 5.06:1. The standard button pairing. White on #825FFF (4.17:1) is for large-label buttons only.
- UI components and focus rings need 3:1 against adjacent colours (WCAG 1.4.11); #825FFF passes on white and on #F7F7F7.
- sRGB is the baseline. Ship P3 only as an enhancement via `color(display-p3 ...)` with an sRGB fallback, never carrying meaning.

## Interaction

- Buttons: 44 px default height (`h-11`, the @ludo button default). Specific verb-first labels: "Book a tour", "Read the Easter Show case study", "Talk to operations". Never "Get started", "Learn more", "Submit" or "Click here".
- Links: internal navigation uses the framework link component; external, tel: and mailto: use plain anchors. Inline body links are #7351EF and underlined. Print full URLs in any context that gets printed.
- Hit areas: 24 x 24 CSS px is the WCAG 2.5.8 legal floor; interactive controls target 44 px. Text links inside paragraphs are exempt from the floor but get generous padding in nav lists.
- Micro-interactions: 150 to 300 ms (fast / base / slow tokens) with cubic-bezier(0.4, 0, 0.2, 1). Honour prefers-reduced-motion with the tone-down policy: keep opacity fades, drop transforms and loops. Nothing auto-plays past 5 s without a pause control (WCAG 2.2.2).
- Focus states visible always: the theme ring tokens give a #825FFF ring at 3:1+ against light surfaces.

### Voice

- Australian English: customise, colour, organisation, programme.
- No em dashes anywhere. Use commas, colons or full stops.
- Say "visitor", never "guest". Active voice. Short scannable sentences.
- Banned words: seamless, robust, scalable, leverage, unlock, empower, ecosystem, journey, holistic, frictionless, streamlined.
- Real numbers only, from the approved proof points: 900K+ attendance at the Sydney Royal Easter Show, 500+ devices, 3 million transactions, 99.9% operational uptime, 400,000+ map sessions, 89% self-service (Ekka 2025). Anything else needs a verified source before it ships.

### Print stylesheet

Any page a procurement team might print (proposals, pricing, case studies) ships one. Without it, the printout is the broken web page that gets stapled into a buying pack.

```css
@media print {
  nav, footer, .chat-widget, .no-print { display: none; }
  body { font-size: 11pt; line-height: 1.45; color: #000; background: #fff; }
  a { color: #000; text-decoration: underline; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 8.5pt; }
  figure, table, blockquote { break-inside: avoid; }
  h2, h3 { break-after: avoid; }
  @page { margin: 20mm; }
}
```

Browsers strip background colours by default when printing, so information carried only by a background fill vanishes on paper. Carry state in text and borders too.

## Do / Don't

- Do cap article measure at 65ch. Don't let text run the full viewport width.
- Do set body at 16 px minimum, 18 px for long-form, line height 1.5. Don't ship 14 px grey body copy.
- Do keep text at zinc-500 (#71717A) or darker; body at #09090B. Don't set body or caption text in zinc-400 (#A1A1AA, 2.56:1).
- Do use #7351EF underlined for inline links and reserve #825FFF for fills and display type. Don't set body-size #825FFF text on white.
- Do write specific CTAs: "Book a tour". Don't write "Get started" or "Learn more".
- Do verify the page at 320 px reflow and 200% zoom. Don't ship layouts that scroll horizontally on small screens.
- Do ship the print stylesheet on printable pages with URLs exposed. Don't let a pricing page print as broken chrome.
- Do use only approved proof points with real numbers. Don't publish a metric you cannot trace to a source.

## Checklist before shipping

- [ ] Measure 45 to 75 characters (max-width 65ch to 72ch) on all reading surfaces
- [ ] Body 16 px+ at line height 1.5; long-form 18 px
- [ ] All text passes AA: 4.5:1 body, 3:1 large text and components; nothing lighter than #71717A as text
- [ ] Inline links #7351EF and underlined; buttons 44 px with verb-first labels
- [ ] #825FFF used sparingly: fills, icons, display type, stat numerals
- [ ] Page reflows at 320 px and survives 200% zoom
- [ ] prefers-reduced-motion honoured; nothing auto-plays past 5 s without pause
- [ ] Print stylesheet present on any printable page; URLs printed; breaks controlled
- [ ] Copy: Australian English, active voice, no em dashes, no banned words, "visitor" never "guest"
- [ ] Every metric traces to the approved proof-point list; weight cap 600 respected
