---
version: alpha
name: "London Design Festival"
description: "Pure black canvas, alarm-red accent at `#e42313`, and Akzidenz Grotesk Pro with extreme display scaling — an annual design event that dresses its platform like a protest poster."

colors:
  background: "#ffffff"
  surface: "#f8f8f8"
  surface-dark: "#000000"
  ink: "#000000"
  ink-muted: "#4f4f4f"
  ink-subtle: "#828282"
  primary: "#e42313"
  on-primary: "#ffffff"
  primary-hover: "#ff2a17" # was rgb(255, 42, 23) — hover variant
  on-surface-dark: "#ffffff"
  border: "#f8f8f8"
  border-strong: "#000000"
  focus-ring: "#e42313"
  shadow-soft: "#1a1a1a" # was rgba(0, 0, 0, 0.1) — Google format requires hex
  ink-disabled: "#64748b"

typography:
  display-hero:
    fontFamily: "akzidenz-grotesk-pro, Barlow, ui-sans-serif, system-ui, sans-serif"
    fontSize: 173px
    fontWeight: 500
    lineHeight: 1.0
    letterSpacing: -11.5px
  display:
    fontFamily: "akzidenz-grotesk-pro, Barlow, ui-sans-serif, system-ui, sans-serif"
    fontSize: 77px
    fontWeight: 500
    lineHeight: 0.95
    letterSpacing: -4.8px
  display-sub:
    fontFamily: "akzidenz-grotesk-pro, Barlow, ui-sans-serif, system-ui, sans-serif"
    fontSize: 58px
    fontWeight: 500
    lineHeight: 1.0
    letterSpacing: -3.1px
  heading-section:
    fontFamily: "akzidenz-grotesk-pro, Barlow, ui-sans-serif, system-ui, sans-serif"
    fontSize: 42px
    fontWeight: 500
    lineHeight: 1.2
    letterSpacing: -0.96px
  heading-sub:
    fontFamily: "akzidenz-grotesk-pro, Barlow, ui-sans-serif, system-ui, sans-serif"
    fontSize: 27px
    fontWeight: 500
    lineHeight: 2.0
    letterSpacing: -1.15px
  body-large:
    fontFamily: "akzidenz-grotesk-pro, Barlow, ui-sans-serif, system-ui, sans-serif"
    fontSize: 29px
    fontWeight: 400
    lineHeight: 1.5
    letterSpacing: 0px
  body:
    fontFamily: "akzidenz-grotesk-pro, Barlow, ui-sans-serif, system-ui, sans-serif"
    fontSize: 17px
    fontWeight: 400
    lineHeight: 1.5
    letterSpacing: 0px
  nav-link:
    fontFamily: "akzidenz-grotesk-pro, Barlow, ui-sans-serif, system-ui, sans-serif"
    fontSize: 16px
    fontWeight: 400
    lineHeight: 1.5
    letterSpacing: 0px
  button-ui:
    fontFamily: "akzidenz-grotesk-pro, Barlow, ui-sans-serif, system-ui, sans-serif"
    fontSize: 14px
    fontWeight: 500
    lineHeight: 1.5
    letterSpacing: 0px
  caption:
    fontFamily: "akzidenz-grotesk-pro, Barlow, ui-sans-serif, system-ui, sans-serif"
    fontSize: 16px
    fontWeight: 400
    lineHeight: 1.0
    letterSpacing: -0.5px

spacing:
  xs: 4px
  sm: 8px
  md: 16px
  lg: 24px
  xl: 32px
  2xl: 48px
  3xl: 64px
  4xl: 96px

rounded:
  none: 0px

components:
  button-primary:
    backgroundColor: "{colors.ink}"
    textColor: "{colors.on-primary}"
    typography: "{typography.button-ui}"
    rounded: "{rounded.none}"
    padding: 11px 19px 13px
  button-primary-hover:
    backgroundColor: "{colors.primary}"
    textColor: "{colors.on-primary}"
    typography: "{typography.button-ui}"
    rounded: "{rounded.none}"
    padding: 11px 19px 13px
  button-secondary:
    backgroundColor: "{colors.background}"
    textColor: "{colors.ink}"
    typography: "{typography.button-ui}"
    rounded: "{rounded.none}"
    padding: 11px 19px 13px
  button-secondary-hover:
    backgroundColor: "{colors.primary}"
    textColor: "{colors.on-primary}"
    typography: "{typography.button-ui}"
    rounded: "{rounded.none}"
    padding: 11px 19px 13px
  card:
    backgroundColor: "{colors.background}"
    textColor: "{colors.ink}"
    rounded: "{rounded.none}"
    padding: 24px
  card-hover:
    backgroundColor: "{colors.background}"
    textColor: "{colors.ink}"
    rounded: "{rounded.none}"
    padding: 24px
  input:
    backgroundColor: "{colors.surface-dark}"
    textColor: "{colors.on-surface-dark}"
    typography: "{typography.body}"
    rounded: "{rounded.none}"
    padding: 0px
  input-focus:
    backgroundColor: "{colors.surface-dark}"
    textColor: "{colors.on-surface-dark}"
    borderColor: "{colors.focus-ring}"
    typography: "{typography.body}"
    rounded: "{rounded.none}"
    padding: 0px
  nav:
    backgroundColor: "{colors.background}"
    textColor: "{colors.ink}"
    typography: "{typography.nav-link}"
    rounded: "{rounded.none}"
    padding: 16px 0px
  nav-link-hover:
    backgroundColor: "{colors.background}"
    textColor: "{colors.primary}"
    typography: "{typography.nav-link}"
    rounded: "{rounded.none}"
    padding: 16px 0px
  badge:
    backgroundColor: "{colors.primary}"
    textColor: "{colors.on-primary}"
    typography: "{typography.caption}"
    rounded: "{rounded.none}"
    padding: 4px 8px
---

# London Design Festival Design System

## Overview

London Design Festival's web presence reads as a printed programme suddenly given the ability to move. The white canvas (`{colors.background}`) stretches to the full viewport with almost no padding at the page level — content begins at the edge and uses only the structural grid to impose breathing room. The dominant typographic event is scale: Akzidenz Grotesk Pro appears at 173px with letter-spacing pulled to -11.52px, creating display text so compressed it nearly touches its own negative space. At those proportions a heading isn't a label — it's an architectural element. Everything else in the type hierarchy shrinks deliberately toward 16–17px body text, which makes the scale ratio between the largest and smallest text somewhere around 10:1. No other festival website in the world signals its ambitions through type alone this aggressively.

The single accent colour — `{colors.primary}`, a saturated alarm red — functions less as a brand hue and more as a semantic signal. It arrives on hover states (nav links shift from `{colors.ink}` to `{colors.primary}` on mouseover), on the CTA button hover, on arrow-link decorations, and nowhere else at rest. The result is a surface that reads almost entirely in black and white until the cursor touches it, at which point the red flares up like a neon sign activating. This restraint makes every `{colors.primary}` moment feel charged. The red reads precisely as intended: urgent, decisive, London.

The supporting typefaces extend the editorial rigour. Helvetica Neue LT Std (light and italic) and DIN 1451 Engschrift (the compressed German transport typeface) appear as self-hosted companions for secondary registers — catalogue labels, programme numbers, and running metadata. DIN Engschrift in particular is a typographic choice that wears its cultural references openly: it is the letterform of German motorway signage, modernist institutional type, and graphic design history coursework. LDF is a festival for people who find that context meaningful.

**Key Characteristics:**
- Akzidenz Grotesk Pro at 173px, `font-weight: 500`, `letter-spacing: -11.52px` — display type that functions as architecture, not annotation
- Zero border-radius everywhere: `{rounded.none}` on buttons, cards, inputs, navigation strip, and all containers
- `{colors.primary}` activates only on interaction — absent from all resting states except branded hero imagery
- Hover colour-shift on ALL navigational text from `{colors.ink}` to `{colors.primary}` — a consistent, system-wide signal
- Self-hosted type trio: Akzidenz Grotesk Pro (via Adobe Fonts), Helvetica Neue LT Std, and DIN 1451 Engschrift
- Button anatomy is asymmetric: `11px top / 13px bottom` padding — optically centred, not mathematically centred
- Motion is conservative: 0.4s `ease` on card/media transforms, 0.2s `ease-in-out` on button background-color — exactly what's needed, nothing more
- Infinite marquee for the supporters strip at 36s linear — the one moment of perpetual motion in an otherwise still interface
- Eight breakpoints (479px → 640px → 767px → 768px → 991px → 992px → 1280px → 1300px) tracking a fully fluid layout
- Card hover uses `transform` scaling, not a border or tint overlay — depth through physical metaphor
- `{colors.ink-subtle}` (`#828282`) and `{colors.ink-muted}` (`#4f4f4f`) form a two-tier muted text system for de-emphasised metadata

## Colors

### Primary Canvas
- **Festival White** (`{colors.background}`): The complete page canvas. No warm or cool tint — pure white that maximises contrast with black type and lets photography read at full saturation.
- **Near-White** (`{colors.surface}`): `#f8f8f8`, used as a section separator — the `1px solid` top border on article dividers uses this value. Barely visible against white but provides tactile row separation.

### Text / Ink
- **Festival Black** (`{colors.ink}`): `#000000` — the dominant text value appearing in 1,480 CSS instances. Maximum contrast. No softening to near-black; the system commits fully to pure ink on pure paper.
- **Mid Grey** (`{colors.ink-muted}`): `#4f4f4f` — secondary body text, programme metadata, de-emphasised list labels.
- **Pale Grey** (`{colors.ink-subtle}`): `#828282` — the softest text tier; captions, event codes, attribution.

### Brand Accent
- **Festival Red** (`{colors.primary}`): `#e42313` — a warm, high-chroma red sitting at LCH 49% lightness. Applied only to interactive hover states (nav links, buttons, arrow links) and branded GIF imagery. The hover variant `{colors.primary-hover}` at `#ff2a17` intensifies fractionally on further interaction.

### Inverse Surface
- **Absolute Black** (`{colors.surface-dark}`): Used for the footer newsletter strip — the only full-surface departure from the white canvas. Text reverses to `{colors.on-surface-dark}`.

### Borders & Structure
- **Section Divider** (`{colors.border}`): `#f8f8f8` — top-border on article rows, barely visible against the white canvas. Structural without being decorative.
- **Hard Rule** (`{colors.border-strong}`): `#000000` — bottom-border on inputs within the dark newsletter section.

### Shadows
- **Drop Shadow** (`{colors.shadow-soft}`): `rgba(0, 0, 0, 0.1)` flattened to `#1a1a1a` for token compatibility. Applied as `0px 4px 6px -1px` + `0px 2px 4px -2px` on interactive card states — a Tailwind-flavoured double shadow that adds modest lift on hover.

## Typography

### Font Families
- **Primary**: `akzidenz-grotesk-pro` via Adobe Fonts — the foundational grotesque of 20th-century graphic design. Used for all type roles from 14px UI labels to 173px display headers.
- **Secondary / Italic**: `HelveticaNeueLTStd_Lt` and `HelveticaNeueLTStd_LtIt` — self-hosted OTF files for editorial asides and programme italic contexts.
- **Accent**: `DIN_1451_engschrift` — self-hosted TTF, used for compressed informational labels, event codes, and the register-number typography that LDF inherits from print programme culture.
- **Google Font substitute**: `Barlow` — a geometric grotesque with appropriate condensed weights; closest available substitute when Akzidenz and Helvetica are unavailable.

### Hierarchy

| Token | Size | Weight | Use |
|---|---|---|---|
| `display-hero` | 173px | 500 | Full-viewport event headlines; the primary graphic statement |
| `display` | 77px | 500 | Large programme section headers |
| `display-sub` | 58px | 500 | Secondary hero content, featured event names |
| `heading-section` | 42px | 500 | Section anchors — "Stories", "What's On", "Partners" |
| `heading-sub` | 27px | 500 | Feature intro text, card titles at featured scale |
| `body-large` | 29px | 400 | Lead paragraphs, introductory narrative text |
| `body` | 17px | 400 | Standard body copy, event descriptions, navigation dropdown items |
| `nav-link` | 16px | 400 | Top navigation links in default state |
| `button-ui` | 14px | 500 | CTA labels — "See more", form submit buttons |
| `caption` | 16px | 400 | Image credits, metadata labels, tag indicators |

### Principles
- **Letter-spacing scales with size, aggressively.** At 173px the tracking is -11.52px (6.7% of font size). At 42px it is -0.96px (2.3%). Below 20px it relaxes to zero or near-zero. The compressed display is not incidental — it is the visual signature.
- **Weight 500 dominates.** In a font family where 400 is already visually substantial, the 500 weight for display text reads as purposeful authority rather than shout. The system does not use bold (700+) anywhere.
- **Line-height compresses at large sizes.** `display-hero` runs at `1.0`, `display` at `0.95` — lines are designed to stack tightly, creating word-mass rather than word-spacing. Body text opens to `1.5` for legibility.
- **Single family across all registers.** Akzidenz handles everything from 14px button labels to 173px event titles. DIN and Helvetica appear in specialist roles; no serif for quotes, no mono for code.

## Layout

### Spacing System
Base unit: 8px. The scale follows 8px multiples for content spacing, but the type-level micro-spacing (6px, 11.52px, 28.224px) reflects rem-based rhythm from a fluid 16px root.

The spacing personality is controlled tightness at the micro level, then generous section breathing room at the macro level. Navigation has very little padding; content sections open out to 48–96px gutters.

### Grid & Container
- Max content width: approximately 1280–1300px, matching the outermost breakpoints
- Hero carousel: full-bleed — the imagery bleeds to the viewport edge with no horizontal margin
- Content grid: inferred multi-column (2–4 columns depending on breakpoint) with article-row dividers via top border
- Newsletter section: full-width reverse strip (black background, white text)
- Supporters marquee: full-width infinite scroll strip, no grid constraint

### Whitespace Philosophy
- Whitespace is not decorative here — it is produced by the scale differential between the 173px hero and the 17px body. The contrast itself creates visual breathing room.
- Section headings at 42px with tight letter-spacing act as visual anchors; the white space around them comes from the heading's own size, not margin.
- Hard edges everywhere (zero border-radius) mean whitespace is defined by rectangles abutting rectangles — a layout that feels assembled, not floated.

## Elevation & Depth

| Level | Treatment | Use |
|---|---|---|
| Flat (Level 0) | No shadow | Default state for all cards, nav, panels |
| Card Hover (Level 1) | `0px 4px 6px -1px rgba(0,0,0,0.1), 0px 2px 4px -2px rgba(0,0,0,0.1)` | Card and media elements on hover — subtle dual-shadow lift |
| Dark Section (Level 2) | Background flip to `{colors.surface-dark}` | Footer / newsletter strip — depth via surface inversion |
| Focus Ring | `{colors.focus-ring}` outline treatment | Interactive element focus — red ring matching primary accent |

The shadow philosophy is minimal-on-rest, present-on-interaction. Cards and media tiles sit flat until the cursor arrives, at which point the double shadow arrives simultaneously with the `transform: scale` hover effect. The shadow and the physical lift are one gesture.

## Shapes

The complete radius scale is declared in the `rounded:` token block above. The system uses only one shape setting:

| Token | Value | Use |
|---|---|---|
| `none` | 0px | Absolutely everything — buttons, cards, inputs, nav strips, images, carousels, modals |

Zero border-radius is not an aesthetic accident — it is a commitment. London Design Festival's visual identity is built on the authority of the rectangle. Every component is a hard-edged form. Curves appear nowhere in the UI chrome, only in photography, illustration, or the festival's own identity mark. This binary shape system (0px only) is the most opinionated decision in the entire design system.

## Components

The complete component spec lives in the `components:` token block above. Reference component tokens directly (`{components.button-primary}`, `{components.card}`) rather than reconstructing them.

### Button Variants

- **`button-primary`** — Black fill (`{colors.ink}`), white label, `{rounded.none}`. Asymmetric vertical padding (11px top, 13px bottom) for optical centering of the Akzidenz Grotesk descenders. The CTA for content browsing ("See more").
- **`button-primary-hover`** — Background shifts to `{colors.primary}` on hover, transition `0.2s ease-in-out`. The red arrival is fast and declarative.
- **`button-secondary`** — White fill with black text and implied border context; used in editorial sections where a ghost presence is preferred. Hover also flips to red.

### Cards

Cards are hard-edged rectangles (`{rounded.none}`) with a white background (`{colors.background}`). At rest they sit flat with no shadow. On hover, two simultaneous events occur: a `transform: scale` shift (0.4s ease) and the appearance of a double drop shadow. The top-edge `1px solid {colors.surface}` border row divider separates list items in the editorial grid.

### Navigation

The primary nav sits on `{colors.background}` with `{typography.nav-link}` at 16px. All nav items default to `{colors.ink}` and shift to `{colors.primary}` on hover — a uniform color-shift pattern applied to every navigational text element including dropdowns. No background change, no underline: the red text is the only affordance.

### Inputs

Newsletter email inputs are context-inverted: placed on the `{colors.surface-dark}` black strip, text and placeholder in white, zero border, zero padding, zero border-radius. The bottom `1px solid` border rule provided by the containing section is the sole structural delimiter. Focus is handled via CSS variable `--form-input-color-focus`.

### Marquee / Supporters Strip

A full-width infinite scroll container with `animation: 36s linear infinite`. The marquee scrolls supporter logos horizontally; the animation name `LdfSupportersMarquee` is scoped to the component module.

## Do's and Don'ts

### Do
- Use `{colors.primary}` exclusively for interactive hover states and brand moments — its rarity is its power. If it appears at rest, its signal degrades.
- Apply `{rounded.none}` to every component without exception — a single rounded corner anywhere breaks the hard-edge system.
- Scale letter-spacing with `{typography.display-hero}` proportionally — at 173px the -11.52px tracking should be maintained; reducing it makes the headline feel suburban.
- Use the asymmetric button padding (`11px 19px 13px`) as the template — the 2px bottom offset is optically compensating for Akzidenz's descender depth.
- Place full-bleed imagery for heroes — the festival lives in photography; contain it and you kill the energy.
- Use `{colors.ink-muted}` and `{colors.ink-subtle}` for the two supporting grey tiers — de-emphasise by stepping through the grey scale, not by reducing opacity.
- Apply `transform` scale on card hover simultaneously with the shadow — the physical metaphor requires both.
- Use DIN 1451 Engschrift for compressed programme/catalogue numbering when available — it carries the editorial authority of a printed festival guide.

### Don't
- Don't introduce any border-radius, even a small `2px` or `4px`. The system is binary: `{rounded.none}` only. Any curve contradicts the foundational shape identity.
- Don't use `{colors.primary}` at rest on body text or UI chrome — it belongs to hover and brand moments only.
- Don't increase font weight above 500 for display text. The system uses weight 500 at all display sizes. Weight 700+ is not part of the vocabulary.
- Don't add colour to the default canvas. `{colors.background}` is pure white; resist the impulse to introduce a warm off-white or tinted surface for section backgrounds.
- Don't apply the extreme letter-spacing (`-11.52px`) at sizes below 77px — it was calibrated for the 173px display size. Smaller text needs its own proportional tracking.
- Don't introduce secondary accent colours. The CSS variables contain `--color-gdf-blue`, `--color-gdf-yellow`, and `--color-gdf-green` for the Great Design Festival co-branding; these are not part of the LDF palette and should stay scoped.
- Don't animate with durations above 0.4s for UI transitions. The system's longest transition is the card/media 0.4s ease transform; exceeding this makes the interface feel sluggish against the festival's energy.
- Don't add decorative shadows to resting states — shadows exist only as the hover affordance. Static shadows flatten the interactive language.

---

## Responsive Behavior

### Breakpoints
| Name | Width | Key Changes |
|---|---|---|
| Mobile XS | <479px | Single column, full-bleed hero, navigation collapses to hamburger |
| Mobile | 480–640px | Narrow single column; display type scales down |
| Mobile Large | 641–767px | Slightly wider column, 2-up card grids begin |
| Tablet Portrait | 768px | Grid shifts to 2–3 column layout, nav partially expands |
| Tablet Landscape | 769–991px | 3-column content grid, editorial panels full-width |
| Small Desktop | 992–1280px | Full navigation visible, 4-column grids, hero at near-full size |
| Desktop | 1281–1300px | Max container constraint activates, content centred within 1300px |
| Large Desktop | >1300px | Container fixed, outer canvas extends but content stays bounded |

### Touch Targets
- Buttons use asymmetric padding (`11px 19px 13px`) which provides sufficient touch height at 14px font size; minimum tap target approximately 44px via container context.
- Nav links at 16px with 1.5 line-height meet minimum touch target sizing when rendered in the full-height nav strip.

### Collapsing Strategy
- Navigation collapses to hamburger below tablet breakpoints; dropdowns become full-screen overlays.
- Display text sizes scale fluidly via rem-based typography tied to a fluid root; the 173px hero shrinks proportionally to approximately 48–64px at mobile.
- The supporters marquee (`marquee-strip`) remains full-width at all breakpoints — the one layout element immune to column constraints.
- Card grids collapse from 4-column to 2-column to 1-column as breakpoints descend.

### Image Behavior
- Hero carousel images are full-bleed at all breakpoints; the carousel aspect ratio adjusts rather than cropping.
- Card images scale within fixed-height containers; `transform` hover scale adds overflow on interaction.
- The GIF identity mark in the hero scales within the square viewport-proportional container.

---

## Agent Prompt Guide

### Quick Color Reference
- Background: `{colors.background}`
- Text: `{colors.ink}`
- Brand accent (hover/active only): `{colors.primary}`
- Secondary text: `{colors.ink-muted}`
- Subtle text: `{colors.ink-subtle}`
- Border: `{colors.border}`
- Inverse surface: `{colors.surface-dark}`
- CTA button (rest): `{colors.ink}` fill, `{colors.on-primary}` label

### Example Component Prompts

- "Build a hero section for London Design Festival using `{typography.display-hero}` (173px, weight 500, letter-spacing -11.52px, Akzidenz Grotesk Pro or Barlow fallback) on a `{colors.background}` white canvas. The headline should span the full container width. No border-radius anywhere (`{rounded.none}`). All navigation links default to `{colors.ink}` and shift to `{colors.primary}` on hover with `transition: color 0.2s ease-in-out`."

- "Create a programme event card using `{components.card}` — white background `{colors.background}`, hard-edge `{rounded.none}`, `{spacing.lg}` padding. Top-edge `1px solid {colors.surface}` row divider. Card title uses `{typography.heading-sub}`, event date/metadata in `{typography.caption}` at `{colors.ink-subtle}`. On hover: `transform: scale(1.02)` over `0.4s ease` simultaneously with a double drop shadow (`0px 4px 6px -1px rgba(0,0,0,0.1), 0px 2px 4px -2px rgba(0,0,0,0.1)`)."

- "Create a CTA button using `{components.button-primary}`: `{colors.ink}` fill, `{colors.on-primary}` text, `{typography.button-ui}` (14px, weight 500), `{rounded.none}`, padding `11px 19px 13px`. Hover state transitions background to `{colors.primary}` over `0.2s ease-in-out`. No border, no radius, no shadow."

- "Build the top navigation bar for London Design Festival: `{colors.background}` background, `{typography.nav-link}` (16px, weight 400) text in `{colors.ink}`. All nav links change colour to `{colors.primary}` on hover — no underline, no background shift, just the colour transition. Logo mark (tall red wordmark) sits at the far left. Search icon and hamburger at the far right on smaller viewports."

- "Design a newsletter sign-up strip using a full-width `{colors.surface-dark}` black background. Input field: `{components.input}` — no border-radius (`{rounded.none}`), no background, white text on black, bottom-only `1px solid {colors.on-surface-dark}` border. Submit uses `{components.button-primary}` adapted for the dark context. Label in `{typography.body}` at `{colors.on-surface-dark}`."

- "Render a supporters / sponsors marquee strip: full-width container, `overflow: hidden`, child element contains repeating logo sequence. `animation: marquee 36s linear infinite` with `translateX(-50%)` to loop seamlessly. No border, no background tint — sits directly on `{colors.background}` or `{colors.surface-dark}` depending on page context."

### Iteration Guide

1. Start with `{colors.background}` white canvas and `{colors.ink}` black text — every element begins here before any accent is introduced.
2. Never add `{colors.primary}` to a resting state. Always attach it to a `hover`, `focus`, or `active` handler.
3. Zero out all border-radius — `border-radius: 0` on every element, globally. Do not negotiate this.
4. Scale display type using the `{typography.display-hero}` token: 173px at the top, then descend through the hierarchy in large steps. The contrast between levels is the design.
5. Apply letter-spacing proportional to font size: divide roughly by 15 to get the target negative tracking for display text, then relax to near-zero below 24px.
6. For hover elevation on cards: pair `transform: scale(1.02) / 0.4s ease` with the double drop shadow simultaneously — always both, never one without the other.
7. For the dark section inversion, flip `{colors.surface-dark}` as the background and `{colors.on-surface-dark}` as all text — no intermediate grey tokens apply in this context.

---

## Attribution

Independent design analysis from [Design Swatches](https://designmd.santiagoalonso.com) by [Santiago Alonso](https://santiagoalonso.com). Based on publicly observable interface patterns. Not affiliated with or endorsed by London Design Festival. Brand names and trademarks belong to their respective owners.
