# Ludo mobile visitor app guide

> How to build the visitor info app (working name Pavilion) in Ludo style: 44 pt / 48 dp targets, Dynamic Type with the 600 weight cap, thumb zones, outdoor legibility, and conventions for map, wallet and programme screens.

Source of truth: `apps/ui/tokens/ludo.tokens.json` v0.1.0. Served from https://ui.ludo.computer/guides/mobile-info-app.md. Generated 2026-07-26. Self-contained: every value is inline, no images required.

Ownership boundary: token primitives reach the app through the generated contracts (`tokens/dist/tokens.rn.ts`, and `tokens.computer.ts` synced into ludo-computer behind a sha256 gate). App-owned semantics, including the transaction-type palette, payment-method palette and dark mode mapping, live in the app's own theme layer (ludo-computer `src/styles/theme.ts`). This guide sets surface rules; it does not duplicate app-owned palettes.

The visitor is outdoors at a showground, phone in one hand, food or a child in the other, in full sun. Every screen earns its place in under 3 seconds.

## Use these tokens

```text
Touch targets          44 x 44 pt (Apple HIG) / 48 x 48 dp with 8 dp spacing (Material 3) minimum
                       48 pt / 56 dp for primary actions used outdoors
                       WCAG 2.5.8 (24 px) is a legal floor, never a design size

Brand primary          #825FFF   fills, icons, large type; NOT body-size text on white (4.17:1, fails AA)
Purple text on light   #7351EF   (5.06:1 on white, passes AA)
Purple text on dark    #A589FF   (7.22:1 on #09090B)
Pressed                #5A3FD0   pressed states and high-contrast fills (white label 6.87:1)
Soft tint              #F4F2FF   selected-state washes; #EDE7FF stronger tint
Text                   #09090B primary, #71717A muted (4.83:1 on white), #A1A1AA subtle (large or decorative only)
Surfaces               #F7F7F7 app background, #FFFFFF cards, #EBEBEB borders, #D4D4D8 strong borders
Status text            #047857 on #ECFDF5, #B45309 on #FFFBEB, #B91C1C on #FEF2F2, #1D4ED8 on #EFF6FF
Radius                 4 / 6 / 8 / 12 px (sm / md / lg / xl); cards lg 8, sheets and hero cards xl 12
Typeface               Geist; weights 400 / 500 / 600 only
Weight mapping         600 = FontWeight(600) on Android = Font.Weight.semibold on iOS. Never .bold (700).
Motion                 platform defaults; base 200 ms, slow 300 ms, cubic-bezier(0.4, 0, 0.2, 1)
                       springs: snappy (stiffness 300, damping 30), bouncy (stiffness 400, damping 12)
Colour space           sRGB baseline; Display P3 as enhancement only, never meaning-bearing
```

## Layout

- One primary task per screen. List-first layouts. Queue times, gate status and next-event info readable in under 3 seconds.
- Thumb zones: about 75% of phone interactions are thumb-driven (Hoober, "How Do Users Really Hold Mobile Devices?", UXmatters 2013). Primary navigation and actions live in the bottom third: native tab bars (iOS 49 pt, Material 80 dp), floating primary buttons, bottom sheets. Top corners hold rare actions only.
- Screen padding 16 pt / 16 dp; 8 pt / 8 dp minimum between targets.
- Cards on #FFFFFF over the #F7F7F7 app background, radius 8 px, border #EBEBEB or the soft shadow, not both stacked heavily.
- Reflow, never truncate: layouts must survive iOS Dynamic Type accessibility sizes and Android font scale 200%. Test at iOS AX3 minimum.

## Type

The token source carries no mobile type-size scale, so these are the surface rules for new work, in platform units (pt on iOS, sp on Android):

| Role | iOS | Android | Weight |
|---|---|---|---|
| Hero figure (queue time, balance) | 34 pt | 36 sp | 600 |
| Screen title | 22 pt | 22 sp | 500 |
| Section head | 17 pt | 18 sp | 500 |
| Body | 17 pt | 16 sp | 400 |
| Secondary / meta | 15 pt | 14 sp | 400 |
| Caption floor | 12 pt | 12 sp | 400 |

- Support Dynamic Type through the accessibility range: iOS body scales from 17 pt to 53 pt at AX5, roughly 310%. Android 14 applies non-linear scaling to 200%.
- The weight cap changes how hierarchy scales: with nothing above 600 available, hierarchy comes from size and colour, not from piling on weight. At large accessibility sizes keep weights as specified and let the type grow.
- Figures that visitors compare (prices, times, queue minutes) use tabular lining figures or Geist Mono.

## Colour

- Contrast floors: WCAG 2.2 AA 4.5:1 for body text, 3:1 for large text and UI components. Design outdoor-critical screens (tickets, QR codes, wayfinding, wallet balance) to 7:1. Flagship phones reach 2000 nits outdoors but mid-range visitor devices sit at 400 to 800 nits.
- #825FFF is a fill-and-accent colour: tab icons, buttons with large labels, hero numerals. For body-size purple text use #7351EF on light (5.06:1) and #A589FF on dark (7.22:1).
- White text on #825FFF passes only at large sizes (4.17:1). For text-bearing buttons that must work in sun, fill with #5A3FD0 (6.87:1).
- Muted text: #71717A passes AA on white (4.83:1) at body size. #A1A1AA fails (2.56:1); use it only for large or decorative elements.
- Status colours always pair with a word or icon. Raw fills #10B981 and #F59E0B fail as text on white; use the foreground shades listed in the token block.
- Dark mode belongs to the app theme layer. When building dark screens: #09090B or #18181B surfaces, #FAFAFA text, #A589FF purple, foregroundDark status shades (#34D399, #FBBF24, #F87171, #60A5FA).

### Map, wallet and programme conventions

- Map: Ludo is an implementation partner on the venue's existing published maps, offline-capable via a bundled snapshot. Show a last-updated timestamp when serving the offline snapshot. Map pins and clusters are touch targets: 44 pt / 48 dp minimum. Controls (locate me, filters) sit in the bottom third. Proof point when needed: 400,000+ map sessions.
- Wallet: the balance is the hero figure (34 pt+, weight 600, tabular). Specific CTAs: "Top up", "Show ticket", never "Continue". Tickets and QR codes render dark-on-light with a white quiet zone even in dark mode, because scanners need the contrast; raise screen brightness while a ticket is displayed. Card entry belongs to the platform payment sheet, never to a custom form.
- Programme: list-first, grouped by day with filter chips, event times right-aligned in tabular figures, "on now" and "next" surfaced above the fold. A visitor decides in 3 seconds whether to walk to the arena.

## Interaction

- Targets: 44 x 44 pt (Apple HIG) / 48 x 48 dp with 8 dp separation (Material 3). Primary outdoor actions 48 pt / 56 dp. Treat WCAG 2.5.8's 24 px as the legal floor it is, not a target.
- Bottom-third placement for anything tapped often; a grip shift is acceptable only for rare actions.
- Motion: platform defaults first (iOS springs around 300 ms, Material duration tokens 200 to 500 ms). Ludo tokens: base 200 ms, slow 300 ms, easing cubic-bezier(0.4, 0, 0.2, 1); snappy spring (stiffness 300, damping 30) for state changes, bouncy (stiffness 400, damping 12) only for celebratory moments.
- Honour Reduce Motion (iOS) and Remove Animations (Android): tone down, do not kill. Keep opacity fades, drop transforms and loops. Basis: WCAG 2.3.3.
- Offline is normal at a showground: cache the programme and map snapshot, show cached data with a timestamp rather than a spinner, and let the visitor keep reading.

## Do / Don't

- Do size every target at 44 pt / 48 dp minimum, 48 pt / 56 dp for primary outdoor actions. Don't design to WCAG's 24 px floor.
- Do put navigation and primary actions in the bottom third. Don't use web-style top navigation outside the thumb zone.
- Do support Dynamic Type to AX sizes and Android font scale 200%, reflowing layouts. Don't fix type in px or truncate at large sizes.
- Do map weight 600 to Font.Weight.semibold on iOS and FontWeight(600) on Android. Don't let .bold (700) into the app.
- Do use #7351EF / #A589FF for purple text and #5A3FD0 fills for sunlit buttons. Don't set body-size #825FFF text on white.
- Do render tickets and QR codes dark-on-light with a quiet zone in every theme. Don't invert a QR code for dark mode.
- Do keep app-owned semantics (transaction types, payment methods, dark mapping) in the app theme layer. Don't hardcode those palettes from this guide.
- Do write specific labels: "Top up", "Show ticket", "Find a gate". Don't use "Continue", "Learn more", em dashes, or "guest" (always "visitor").

## Checklist before shipping

- [ ] All targets 44 pt / 48 dp minimum with 8 dp spacing; primary outdoor actions 48 pt / 56 dp
- [ ] Primary actions and navigation in the bottom third
- [ ] Dynamic Type tested at iOS AX3 and Android font scale 200% without truncation
- [ ] Body text contrast 4.5:1 minimum; tickets, QR and wayfinding screens at 7:1
- [ ] No body-size #825FFF text on white; purple text uses #7351EF (light) or #A589FF (dark)
- [ ] Nothing rendered above weight 600 on either platform
- [ ] QR codes dark-on-light with quiet zone in all themes; brightness raised on ticket display
- [ ] Programme and map usable offline with a visible last-updated timestamp
- [ ] Reduce Motion honoured: opacity kept, transforms and loops dropped
- [ ] Copy: Australian English, specific verb-first labels, no em dashes, "visitor" never "guest"
