/* ---- Cache ----
   This file is served public, max-age=31536000, immutable by vercel.json, and
   there is no build step to hash it. Change anything below and the ?v= query
   on all 16 references has to move in the same commit, or a returning visitor
   keeps this copy for a year. CI enforces it: .github/workflows/asset-version.yml
   ---- */

/* ---- Fonts, first party ----
   Jost and Geist, both SIL OFL 1.1, both variable, both served from this
   origin. Downloaded from Google Fonts at the same 2 weight ranges the site
   asked for and split across the same unicode ranges, so the bytes a visitor
   renders are the bytes they rendered before. Nothing is subsetted: dropping
   a range would be a design change wearing a performance argument.
   The licences sit beside the files in fonts/. ---- */
/* Geist, cyrillic-ext */
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 300 600;
  font-display: swap;
  src: url('fonts/geist-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* Geist, cyrillic */
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 300 600;
  font-display: swap;
  src: url('fonts/geist-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* Geist, vietnamese */
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 300 600;
  font-display: swap;
  src: url('fonts/geist-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* Geist, latin-ext */
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 300 600;
  font-display: swap;
  src: url('fonts/geist-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Geist, latin */
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 300 600;
  font-display: swap;
  src: url('fonts/geist-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* Jost, cyrillic */
@font-face {
  font-family: 'Jost';
  font-style: normal;
  font-weight: 200 500;
  font-display: swap;
  src: url('fonts/jost-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* Jost, latin-ext */
@font-face {
  font-family: 'Jost';
  font-style: normal;
  font-weight: 200 500;
  font-display: swap;
  src: url('fonts/jost-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Jost, latin */
@font-face {
  font-family: 'Jost';
  font-style: normal;
  font-weight: 200 500;
  font-display: swap;
  src: url('fonts/jost-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* odel-labs. Every design value lives in :root. Nothing below it is hardcoded.
   Two families, both from the minimalist set: Jost carries display and the logotype,
   Geist carries text and every label. No serif.
   One accent mechanic: a solid 1px #FF5A00 hairline under a black one, offset 3px
   by --under, which leaves 2px of paper between them.
   No blur, no gradient. It appears on the identity marks and on live state only.
   The dark theme is the same page inverted. The orange does not change. */

:root {
  /* The day values, named. The night block overrides --paper and its
     neighbours, and the print context has to put them back without repeating a
     hex below this block, so each value lives here once and is referenced
     twice. Reading colour: ink is for titles and marks, and a long column set
     in it is harsh, so text sits one step off black, still well clear of AA. */
  --paper-day: #FFFFFF;
  --ink-day: #0B0B0C;
  --text-day: #3B3C40;
  --grey-day: #5C5E64;
  --rule-day: #E7E7EA;
  --fill-day: #F6F6F7;

  --paper: var(--paper-day);
  --ink: var(--ink-day);
  --text: var(--text-day);
  --grey: var(--grey-day);
  --rule: var(--rule-day);
  --fill: var(--fill-day);
  --glow: #FF5A00;

  /* Fixed. Does not invert. The logo sheet needs a real black to demonstrate on. */
  --demo-black: #0B0B0C;

  /* The accent. A solid rule under a black rule, drawn as a zero-blur shadow. */
  --under: 0 3px 0 0 var(--glow);

  --font-body: "Geist", sans-serif;
  --font-display: "Jost", sans-serif;
  --font-ui: "Geist", sans-serif;

  --t-banner: clamp(1.875rem, 4.6vw, 3.5rem);
  --t-display: clamp(1.5rem, 3.2vw, 2.5rem);
  --t-section: clamp(1.25rem, 1.6vw, 1.375rem);
  --t-card: 0.9375rem;
  --t-read: clamp(1rem, 1.15vw, 1.0625rem);
  --t-standfirst: clamp(1.125rem, 1.5vw, 1.25rem);
  --t-lede: 1.0625rem;
  --t-body: 1rem;
  --t-ui: 0.8125rem;
  --t-ui-s: 0.75rem;
  --t-fig: clamp(1.75rem, 3vw, 2.5rem);
  --t-mark: 1.125rem;

  --w-thin: 200;
  --w-light: 300;
  --w-book: 400;
  --w-med: 500;
  --w-semi: 600;

  --lh-banner: 1.14;
  --lh-read: 1.65;
  --lh-body: 1.65;
  --lh-ui: 1.6;
  --ls-banner: 0.1em;
  --ls-section: 0.22em;
  --ls-card: 0.14em;
  --ls-nav: 0.14em;
  --ls-label: 0.12em;
  --ws-label: 0.06em;
  --ls-mark: -0.01em;

  --s1: 0.25rem;
  --s2: 0.5rem;
  --s3: 1rem;
  --s4: 1.5rem;
  --s5: 2rem;
  --s6: 3rem;
  --s7: 4rem;
  --s8: 6rem;
  --s9: 8rem;
  --s10: 10rem;

  --page-max: 90rem;
  --pad-x: clamp(1.25rem, 5vw, 5.5rem);
  --measure: 34rem;
  --measure-read: 30rem;
  --measure-centre: 40rem;
  --gutter: clamp(1.5rem, 4vw, 4rem);
  --rail-w: 11rem;
  --aside-w: 12rem;
  --line: 1px;
  /* The finest line the display can draw. 1px on a 1x screen, half that on
     anything from 2x up, which is every phone and every laptop this site is
     read on. Only a boundary uses it; a rule that carries meaning stays at
     --line, where it is the same weight as the rules inside the drawings. */
  --hair: var(--line);
  --r-screen: 0.75rem;
  /* The module tile's corner. Slight: enough to take the point off a closed
     box, short of the 12px the device screens round to. */
  --r-card: 0.25rem;
  --dot: 0.3125rem;
  --slot-ratio: 4 / 3;

  --dur-fast: 240ms;
  /* Not a design duration. The value transitions collapse to when the reader
     has asked for reduced motion; short of 0 so transitionend still fires. */
  --dur-none: 0.01ms;
  --ease: cubic-bezier(0.16, 0.84, 0.32, 1);

  color-scheme: light;
}

@media (min-resolution: 2dppx) {
  :root { --hair: 0.5px; }
}

/* The negative. Same page, same orange, nothing else moved. */
[data-theme="dark"] {
  --paper: #0B0B0C;
  --ink: #F2F2F3;
  --text: #D5D6D9;
  --grey: #A2A4AA;
  --rule: #26272B;
  --fill: #141416;

  color-scheme: dark;
}

*, *::before, *::after { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  background: var(--paper);
  color: var(--ink);
}

/* Transitions are suppressed for the frame the theme swaps on, so every
   token-driven colour lands at once rather than being held by a transition. */
.is-theming, .is-theming *, .is-theming *::before, .is-theming *::after { transition: none !important; }

body {
  margin: 0;
  background: transparent;
  color: inherit;
  font-family: var(--font-body);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  font-kerning: normal;
  font-variant-ligatures: common-ligatures;
}

h1, h2, h3, p, figure, ul, ol, dl, dd { margin: 0; }
ul, ol { padding: 0; list-style: none; }

a { color: var(--ink); text-decoration: none; }
a:hover { color: var(--ink); }

:focus-visible { outline: var(--line) solid var(--ink); outline-offset: 4px; }

.skip {
  position: absolute;
  left: var(--pad-x);
  top: var(--s3);
  z-index: 30;
  transform: translateY(-300%);
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: var(--t-ui-s);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  padding: var(--s2) var(--s3);
  border: var(--line) solid var(--ink);
}
.skip:focus { transform: none; }

.page { max-width: var(--page-max); margin: 0 auto; padding-left: var(--pad-x); padding-right: var(--pad-x); }

/* ---- The rule. One mechanic, used everywhere.
   A black hairline with a solid orange one under it, drawn as a zero-blur
   shadow offset 3px, which leaves a 2px gap. Where the black line is a
   border-bottom instead, the same offset is measured from the padding box
   above that border and the gap closes to 1px: see .lockup and .link. ---- */
.glow {
  display: block;
  height: var(--line);
  background: var(--ink);
  box-shadow: var(--under);
  border: 0;
}

/* ---- Logotype. The name stands on the rule: a black hairline with a solid
   orange one 1px under it, at the width of the word. The 1px, rather than the
   2px of .glow, is the padding-box measurement described above.
   The hyphen is a hyphen. ---- */
.lockup {
  display: inline-block;
  justify-self: start;
  width: fit-content;
  font-family: var(--font-display);
  font-size: var(--t-mark);
  font-weight: var(--w-book);
  letter-spacing: var(--ls-mark);
  line-height: 1;
  color: var(--ink);
  position: relative;
  padding-bottom: 0.3em;
  border-bottom: var(--line) solid currentColor;
}
.lockup::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -3px;
  height: var(--line);
  background: var(--glow);
}


/* ---- Masthead ---- */
.masthead {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--paper);
  border-bottom: var(--line) solid var(--rule);
}
.masthead__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: var(--s5);
  padding-top: var(--s4);
  padding-bottom: var(--s4);
}
.masthead__nav { display: flex; justify-content: center; gap: clamp(1rem, 2.6vw, 2.75rem); }
.masthead__end { display: flex; justify-content: flex-end; align-items: center; gap: var(--s5); }
/* The anchored section clears the sticky masthead instead of landing under it. */
#work { scroll-margin-top: var(--s8); }
/* Nav. No box. The selected item takes the black rule with the orange under it. */
.nav__link {
  position: relative;
  font-family: var(--font-ui);
  font-size: var(--t-ui-s);
  font-weight: var(--w-med);
  letter-spacing: var(--ls-nav);
  text-transform: uppercase;
  color: var(--grey);
  padding-bottom: var(--s2);
  transition: color var(--dur-fast) var(--ease);
  white-space: nowrap;
}
.nav__link::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: var(--line);
  background: var(--ink);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}
.nav__link:hover { color: var(--ink); }
.nav__link:hover::after { opacity: 1; }
.nav__link--active { color: var(--ink); }
.nav__link--active::after { opacity: 1; box-shadow: var(--under); }
.nav__link[aria-current="true"]::after { box-shadow: none; }

.toggle {
  font-family: var(--font-ui);
  font-size: var(--t-ui-s);
  font-weight: var(--w-med);
  /* A button does not inherit the body line height. Stated, so it shares a
     baseline with the nav links beside it. */
  line-height: var(--lh-body);
  letter-spacing: var(--ls-nav);
  text-transform: uppercase;
  color: var(--grey);
  background: transparent;
  border: 0;
  padding: 0 0 var(--s2) 0;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease);
}
.toggle:hover { color: var(--ink); }

/* ---- Banner ---- */
.banner__inner { text-align: center; padding-top: var(--s8); padding-bottom: var(--s7); }
.banner__eyebrow {
  font-family: var(--font-ui);
  font-size: var(--t-ui-s);
  font-weight: var(--w-med);
  letter-spacing: var(--ls-label);
  word-spacing: var(--ws-label);
  padding-left: var(--ls-label);
  text-transform: uppercase;
  color: var(--grey);
  margin-bottom: var(--s6);
}
.banner__title {
  font-family: var(--font-display);
  font-weight: var(--w-thin);
  font-size: var(--t-banner);
  line-height: var(--lh-banner);
  letter-spacing: var(--ls-banner);
  word-spacing: 0.08em;
  padding-left: var(--ls-banner);
  text-transform: uppercase;
  text-wrap: balance;
  max-width: 20ch;
  margin: 0 auto;
}
.banner__rule { width: 3.5rem; margin: var(--s6) auto; }
.banner__title--name { text-transform: none; letter-spacing: -0.015em; font-weight: var(--w-light); }
.banner__lede {
  max-width: 46ch;
  margin: 0 auto;
  font-size: var(--t-standfirst);
  line-height: var(--lh-body);
  color: var(--grey);
  text-wrap: pretty;
}
.banner__meta {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--s2) var(--s5);
  margin-top: var(--s6);
  font-family: var(--font-ui);
  font-size: var(--t-ui-s);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--grey);
}
.banner__meta span { display: inline-flex; align-items: center; gap: var(--s2); }
.live { width: var(--dot); height: var(--dot); border-radius: 50%; background: var(--glow); flex: none; }

/* ---- Centred section heading ---- */
.section { padding-top: var(--s7); padding-bottom: var(--s7); }
.section--rule { border-top: var(--line) solid var(--rule); }
.section--tight { padding-top: var(--s6); padding-bottom: var(--s6); }
.section__head { max-width: 52rem; margin: 0 0 var(--s7); }
.section__head--rail { text-align: left; max-width: 52rem; }
.section__head--rail .section__title { padding-left: 0; }
.section__head--rail .section__lede { margin-inline: 0; font-size: var(--t-standfirst); max-width: 44ch; }
.section__title {
  font-family: var(--font-display);
  font-weight: var(--w-light);
  font-size: var(--t-section);
  letter-spacing: var(--ls-section);
  word-spacing: 0.12em;
  padding-left: var(--ls-section);
  text-transform: uppercase;
  line-height: 1.4;
  text-wrap: balance;
}
.section__lede { margin-top: var(--s4); font-size: var(--t-lede); line-height: 1.6; color: var(--grey); text-wrap: pretty; max-width: 52ch; }

/* ---- Module index ---- */
.index__grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: var(--gutter); row-gap: var(--s8); }
.index__grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: var(--s8); }
.index__grid--2 .card__text { max-width: 30rem; }
.card { display: block; }
.card__slot { margin-bottom: var(--s5); }
.card__name {
  font-family: var(--font-display);
  font-weight: var(--w-book);
  font-size: 1.5rem;
  letter-spacing: -0.005em;
  line-height: 1.1;
}
.card__text { margin-top: var(--s3); font-size: var(--t-body); line-height: 1.62; color: var(--grey); text-wrap: pretty; }
.card__meta {
  margin-top: var(--s4);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s4);
  font-family: var(--font-ui);
  font-size: var(--t-ui-s);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--grey);
}
.card:hover .slot::after { opacity: 1; }
.card:hover .card__name { opacity: 0.6; }
.card__name { transition: opacity var(--dur-fast) var(--ease); }

/* ---- Asset slots. Empty frames until the real screens land. ---- */
.slot {
  position: relative;
  aspect-ratio: var(--slot-ratio);
  background: var(--fill);
  border: var(--line) solid var(--rule);
  display: grid;
  place-items: center;
  overflow: hidden;
  transition: border-color var(--dur-fast) var(--ease);
}
.slot::after {
  content: "";
  position: absolute;
  inset: auto auto 0 0;
  width: 2.25rem;
  height: var(--line);
  background: var(--ink);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}
.slot__note {
  font-family: var(--font-ui);
  font-size: var(--t-ui-s);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--grey);
  text-align: center;
  padding: var(--s4);
  max-width: 24ch;
  line-height: var(--lh-ui);
}
.slot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.slot--tall { aspect-ratio: 3 / 4; }

/* Index module tiles: the visual is the UI itself. No hard frame around the mock. */
.index__grid .slot {
  border: none;
  background: transparent;
}

/* Name sits above the visual tile now. */
.index__grid .card__name {
  margin-bottom: var(--s3);
}

/* ---- The 4 modules, as cards you can see the edge of ----
   Each one is a link to the module's page, and until 14 August 2026 nothing on
   it said so: the mock sat on bare paper with no boundary, no cursor cue beyond
   the pointer, and the only hover state was a 36px mark at the foot of the
   plate. The tile is now a hairline box in the rule token, which is the shape
   the rest of the page already uses for a field and a device screen, with a
   read-me line at the foot. Hover, focus and press take the box to ink and put
   the accent hairline under it: the same black-rule-over-orange as the nav, the
   button and Book a call, drawn as an absolutely positioned element rather than
   the shadow, so -3px leaves 1px of paper between the 2 lines.
   The paper background is load-bearing. Without it the box is transparent and
   the hairline shows through the tile at the top edge as well. */
.index__grid--2 .card {
  position: relative;
  background: var(--paper);
  border: var(--hair) solid var(--rule);
  border-radius: var(--r-card);
  padding: var(--s5);
  transition: border-color var(--dur-fast) var(--ease);
}
.index__grid--2 .card::after {
  content: "";
  position: absolute;
  left: var(--r-card);
  right: var(--r-card);
  bottom: -3px;
  height: var(--line);
  background: var(--glow);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}
.index__grid--2 .card:hover,
.index__grid--2 .card:focus-visible { border-color: var(--ink); }
.index__grid--2 .card:hover::after,
.index__grid--2 .card:focus-visible::after { opacity: 1; }
/* The card is the target, so the outline goes on the border rather than 4px
   outside it, where it would sit on the neighbouring tile. */
.index__grid--2 .card:focus-visible { outline-offset: 0; }

/* The read-me line. Same label type as the meta row above it, set in ink and
   underlined in the rule token, so it reads as the link the whole card is. */
.card__go {
  margin-top: var(--s3);
  font-family: var(--font-ui);
  font-size: var(--t-ui-s);
  letter-spacing: var(--ls-label);
  word-spacing: var(--ws-label);
  text-transform: uppercase;
  color: var(--ink);
}
.card__go-label {
  border-bottom: var(--line) solid var(--rule);
  padding-bottom: 1px;
  transition: border-color var(--dur-fast) var(--ease);
}
.index__grid--2 .card:hover .card__go-label,
.index__grid--2 .card:focus-visible .card__go-label { border-bottom-color: var(--ink); }

/* Device frames. One flat frame, a hairline, a generous corner. Nothing else. */
.device { width: 100%; max-width: 14rem; border: var(--line) solid var(--rule); border-radius: 1.5rem; padding: var(--s2); background: var(--paper); }
.device__screen {
  aspect-ratio: 9 / 19.5;
  background: var(--fill);
  border: var(--line) solid var(--rule);
  border-radius: 1.125rem;
  display: grid;
  place-items: center;
  overflow: hidden;
}
.device__screen img { width: 100%; height: 100%; object-fit: cover; display: block; }
.device--desk { max-width: none; border-radius: 1.25rem; padding: var(--s2); }
.device--desk .device__screen { aspect-ratio: 16 / 10; border-radius: var(--r-screen); }

/* ---- Mockups. The screens, drawn rather than photographed.
   Geometry lives in each SVG as user units on its viewBox, which is drawing
   dimension and not page design value. Colour lives here, so the token block
   at the top stays the only home for anything that can change, and the night
   view inverts a mockup with the page it sits on. A mockup is a picture of
   software: no motion, no link, and nothing inside it is real. ---- */
.mock {
  display: block;
  width: 100%;
  height: 100%;
  font-family: var(--font-ui);
  font-weight: var(--w-book);
  font-variant-numeric: tabular-nums;
}
.mock text { fill: var(--text); }
/* A mockup carries the type of the product it draws, not of this page. The
   root class sets which family the screen is built in; the other reaches for
   the second family where that product mixes them. */
.mock--display { font-family: var(--font-display); font-weight: var(--w-light); }
.mock__jost { font-family: var(--font-display); font-weight: var(--w-light); }
.mock__ui { font-family: var(--font-ui); font-weight: var(--w-book); }
.mock__label { font-weight: var(--w-med); letter-spacing: var(--ls-label); fill: var(--grey); }
.mock__med { font-weight: var(--w-med); }
/* Colour last, so a label can be set in ink without a second class for it. */
.mock__paper { fill: var(--paper); }
.mock__fill { fill: var(--fill); }
.mock__ink { fill: var(--ink); }
.mock__grey { fill: var(--grey); }

/* The four above are also set on <text>, and there `.mock text` at the top of
   this block is a descendant selector and outranks a single class, so every one
   of them was silently doing nothing. Restated at text specificity so the
   comment above is true rather than intended. Found 11 August 2026 on the
   APPROVE label in the yixxoo mockup: `#D5D6D9` on `#F2F2F3`, measured at
   1.30:1, wrong in both schemes and only noticed in the dark one. */
.mock text.mock__paper { fill: var(--paper); }
.mock text.mock__ink { fill: var(--ink); }
.mock text.mock__grey { fill: var(--grey); }
.mock text.mock__glow { fill: var(--glow); }
/* The accent, under the same rule as the rest of the page: a solid hairline
   under a black one, or a dot that means live. Never a fill, never a panel. */
.mock__glow { fill: var(--glow); }
.mock__rule { fill: none; stroke: var(--rule); shape-rendering: crispEdges; }
.mock__hair { fill: none; stroke: var(--ink); shape-rendering: crispEdges; }
.mock__glow-hair { fill: none; stroke: var(--glow); shape-rendering: crispEdges; }
/* Diagonals and dashed measures are drawn, so they keep their antialiasing. */
.mock__link { fill: none; stroke: var(--rule); }
.mock__dash { fill: none; stroke: var(--grey); }
/* Stroke on its own, so a filled shape can carry a token outline. The classes
   above all set `fill: none`, which a pill or a plate cannot use, and the only
   other way to outline one was a hex in the drawing. Combine with a fill:
   `class="mock__paper mock__edge-rule"`. Added 14 August 2026, when the
   NetworkIQ mockups were found carrying `#FF5A00` inline in 8 places and a
   dozen shapes with no class at all, which the night view painted black on
   black. Every colour in a mockup now comes from the token block. */
.mock__edge-rule { stroke: var(--rule); }
.mock__edge-ink { stroke: var(--ink); }
.mock__edge-grey { stroke: var(--grey); }
.mock__edge-glow { stroke: var(--glow); }

.plate { padding-top: var(--s7); }
.plate__caption {
  margin-top: var(--s4);
  display: flex;
  gap: var(--s4);
  font-family: var(--font-ui);
  font-size: var(--t-ui-s);
  letter-spacing: var(--ls-label);
  word-spacing: var(--ws-label);
  text-transform: uppercase;
  color: var(--grey);
  line-height: var(--lh-ui);
}
.plate__num { color: var(--grey); flex: none; font-variant-numeric: tabular-nums; }

/* ---- Dossier: rail, justified reading column, aside ---- */
.dossier {
  display: grid;
  grid-template-columns: var(--rail-w) minmax(0, 1fr) var(--aside-w);
  column-gap: var(--gutter);
  align-items: start;
  max-width: 68rem;
}
.dossier--pair { grid-template-columns: var(--rail-w) minmax(0, 1fr); max-width: 52rem; }
/* The rail label's cap aligns with the first cap of the reading column, not with
   the top of its line box. 4px is the difference between the two line boxes. */
.dossier--pair .rail { padding-top: var(--s1); }
.rail { font-family: var(--font-ui); font-variant-numeric: tabular-nums; }
.rail__pair + .rail__pair { margin-top: var(--s6); }
.rail__key {
  font-size: var(--t-ui-s);
  font-weight: var(--w-med);
  letter-spacing: var(--ls-label);
  word-spacing: var(--ws-label);
  text-transform: uppercase;
  color: var(--grey);
}
.rail__val { margin-top: var(--s2); font-size: var(--t-ui); line-height: 1.5; color: var(--text); font-variant-numeric: tabular-nums; }
.rail__val--live { display: inline-flex; align-items: center; gap: var(--s2); }

.read {
  color: var(--text);
  font-size: var(--t-read);
  line-height: var(--lh-read);
  max-width: var(--measure-read);
  text-wrap: pretty;
  hanging-punctuation: first last;
}
.read p + p { margin-top: var(--s5); }
.read em { font-style: italic; }

.aside__slot { margin-bottom: var(--s4); }
.aside__note {
  font-family: var(--font-ui);
  font-size: var(--t-ui-s);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--grey);
  line-height: var(--lh-ui);
}

/* Pull quote. One sentence lifted out of the body, as the reference does between image groups. */
.quote { max-width: 44rem; margin: 0 auto; text-align: center; }
.quote p { font-family: var(--font-display); font-weight: var(--w-thin); font-size: clamp(1.375rem, 2.4vw, 1.875rem); line-height: 1.35; letter-spacing: -0.01em; text-wrap: balance; }

/* ---- Figures ---- */
.figures { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: var(--gutter); row-gap: var(--s7); max-width: 60rem; }
.figures__item { border-top: var(--line) solid var(--rule); padding-top: var(--s5); }
.figures__item { text-align: left; }
.figures__fig { font-family: var(--font-display); font-weight: var(--w-thin); font-size: var(--t-fig); letter-spacing: -0.01em; line-height: 1; font-variant-numeric: tabular-nums; }
.figures__what { margin-top: var(--s3); font-size: var(--t-body); color: var(--ink); }
.figures__when { margin-top: var(--s2); font-family: var(--font-ui); font-size: var(--t-ui-s); letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--grey); }
.figures__none { max-width: var(--measure-centre); font-family: var(--font-ui); font-size: var(--t-ui); line-height: var(--lh-ui); color: var(--grey); }

/* ---- Capability list ---- */
.caps { max-width: 60rem; }
.caps__item {
  display: grid;
  grid-template-columns: 12rem minmax(0, 1fr);
  column-gap: var(--gutter);
  align-items: baseline;
  padding: var(--s6) 0;
  border-top: var(--line) solid var(--rule);
}
/* The list opens on the head, not on a rule: the first divider would read as a
   page break under the standfirst. Dividers separate items only. */
.caps__item:first-child { border-top: 0; padding-top: 0; }
.caps__name { font-family: var(--font-display); font-weight: var(--w-book); font-size: var(--t-card); letter-spacing: var(--ls-card); word-spacing: var(--ws-label); text-transform: uppercase; line-height: 1.3; }
.caps__text { max-width: 36ch; font-size: var(--t-body); line-height: 1.62; color: var(--grey); text-wrap: pretty; }

/* ---- Steps ---- */
.steps { max-width: 60rem; }
.steps__item { display: grid; grid-template-columns: 12rem minmax(0, 1fr); column-gap: var(--gutter); padding: var(--s6) 0; border-top: var(--line) solid var(--rule); align-items: start; }
.steps__item:first-child { border-top: 0; padding-top: 0; }
.steps__num { grid-column: 1; grid-row: 1; }
.steps__key { grid-column: 1; grid-row: 2; margin-top: var(--s2); }
.steps__text { grid-column: 2; grid-row: 1 / 3; }
.steps__num { font-family: var(--font-ui); font-size: var(--t-ui-s); font-weight: var(--w-med); letter-spacing: var(--ls-label); color: var(--grey); font-variant-numeric: tabular-nums; }
.steps__key { font-family: var(--font-display); font-size: var(--t-card); font-weight: var(--w-book); letter-spacing: var(--ls-card); word-spacing: var(--ws-label); text-transform: uppercase; line-height: 1.3; }
.steps__text { max-width: 36ch; font-size: var(--t-body); line-height: 1.62; color: var(--grey); text-wrap: pretty; }

/* ---- Pager ---- */
.pager { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--gutter); row-gap: var(--s6); border-top: var(--line) solid var(--rule); padding-top: var(--s7); padding-bottom: var(--s7); }
.pager__link--next { text-align: right; }
.pager__dir { display: block; font-family: var(--font-ui); font-size: var(--t-ui-s); font-weight: var(--w-med); letter-spacing: var(--ls-label); word-spacing: var(--ws-label); text-transform: uppercase; color: var(--grey); margin-bottom: var(--s3); }
.pager__name { font-family: var(--font-display); font-weight: var(--w-light); font-size: var(--t-display); letter-spacing: -0.015em; line-height: 1.2; }
.pager__link:hover .pager__dir { color: var(--ink); }

/* ---- Call to action ---- */
.cta { text-align: center; }
.cta__mail { display: inline-block; font-family: var(--font-ui); font-size: var(--t-read); letter-spacing: 0; color: var(--ink); margin-bottom: var(--s5); }
.cta__line { max-width: 52ch; margin: var(--s6) auto; font-size: var(--t-lede); line-height: 1.6; color: var(--grey); text-wrap: pretty; }
/* The one button. No box. A black rule under the label; the orange arrives on state. */
.button {
  position: relative;
  display: inline-block;
  font-family: var(--font-ui);
  font-size: var(--t-ui);
  font-weight: var(--w-med);
  letter-spacing: var(--ls-nav);
  word-spacing: var(--ws-label);
  text-transform: uppercase;
  color: var(--ink);
  background: transparent;
  border: 0;
  padding: 0 0 var(--s2) 0;
  cursor: pointer;
}
.button::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: var(--line);
  background: var(--ink);
}
.button:hover::after, .button:focus-visible::after, .button:active::after { box-shadow: var(--under); }

/* ---- Contact form ---- */
.form { max-width: var(--measure-read); margin: 0 auto; }
.form[data-busy] { opacity: 0.5; pointer-events: none; }
.field { padding-bottom: var(--s5); }
.field__label { display: block; font-family: var(--font-ui); font-size: var(--t-ui-s); font-weight: var(--w-med); letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--grey); margin-bottom: var(--s2); }
.field__control { position: relative; }
.field__control::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -3px;
  height: var(--line);
  background: var(--glow);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}
.field__control:focus-within::after, .field--bad .field__control::after { opacity: 1; }

/* A field is a hairline box in the rule token. No fill, no corner, no shadow. */
.field__input, .field__area {
  display: block;
  width: 100%;
  font-family: var(--font-body);
  font-size: var(--t-read);
  line-height: var(--lh-body);
  color: var(--ink);
  background: transparent;
  border: var(--line) solid var(--rule);
  padding: var(--s2) var(--s3);
  border-radius: 0;
  transition: border-color var(--dur-fast) var(--ease);
}
.field__input:hover, .field__area:hover { border-color: var(--ink); }
.field__input:focus, .field__area:focus { outline: 0; border-color: var(--ink); }
.field__area { resize: vertical; min-height: 8rem; }
.field__opt { font-weight: var(--w-book); letter-spacing: 0; text-transform: none; color: var(--grey); }
.field__error { margin-top: var(--s3); font-family: var(--font-ui); font-size: var(--t-ui-s); line-height: var(--lh-ui); color: var(--ink); }
.field--bad .field__input, .field--bad .field__area { border-color: var(--ink); }
.booked { max-width: var(--measure-centre); margin: 0 auto; text-align: center; }
.booked__h { font-family: var(--font-display); font-weight: var(--w-thin); font-size: var(--t-display); letter-spacing: var(--ls-banner); text-transform: uppercase; }
.booked__h:focus { outline: 0; }
.booked__line { margin-top: var(--s6); font-size: var(--t-lede); line-height: var(--lh-body); color: var(--grey); }
.link { position: relative; padding-bottom: 1px; border-bottom: var(--line) solid var(--rule); transition: border-color var(--dur-fast) var(--ease); }
.link::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -3px;
  height: var(--line);
  background: var(--glow);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}
.link:hover { border-bottom-color: var(--ink); }
.link:hover::after { opacity: 1; }

/* ---- Colophon ---- */
.colophon { border-top: var(--line) solid var(--rule); }
.colophon__inner { padding-top: var(--s7); padding-bottom: var(--s7); }
.colophon__top { display: grid; grid-template-columns: minmax(0, 1.4fr) repeat(2, minmax(0, 1fr)); column-gap: var(--gutter); row-gap: var(--s6); padding-bottom: var(--s7); }
.colophon__key { font-family: var(--font-ui); font-size: var(--t-ui-s); font-weight: var(--w-med); letter-spacing: var(--ls-label); word-spacing: var(--ws-label); text-transform: uppercase; color: var(--grey); margin-bottom: var(--s5); }
.name { text-transform: none; letter-spacing: 0.02em; }
/* The name in the colophon. Not a column label, and not the mark either:
   the rule belongs to the masthead and the logo sheet. */
.colophon__mark { margin-bottom: var(--s5); }
.colophon__logo { display: block; width: 7.5rem; height: auto; }
.colophon__logo--neg { display: none; }
[data-theme="dark"] .colophon__logo { display: none; }
[data-theme="dark"] .colophon__logo--neg { display: block; }
.colophon__list { display: flex; flex-direction: column; gap: var(--s3); }
/* The cookie control is a button, not a link, because it opens something
   rather than going somewhere. It reads as the rest of the list. */
.colophon__list a, .colophon__list button { color: var(--grey); font-family: var(--font-ui); font-size: var(--t-ui); letter-spacing: 0.02em; transition: color var(--dur-fast) var(--ease); }
.colophon__list a:hover, .colophon__list button:hover { color: var(--ink); }
.colophon__list button { background: 0; border: 0; padding: 0; text-align: left; cursor: pointer; }
.colophon__line { max-width: 26rem; font-size: var(--t-body); line-height: var(--lh-body); color: var(--grey); }
.colophon__foot { display: flex; flex-wrap: wrap; gap: var(--s3) var(--s6); border-top: var(--line) solid var(--rule); padding-top: var(--s6); font-family: var(--font-ui); font-size: var(--t-ui-s); letter-spacing: var(--ls-label); word-spacing: var(--ws-label); text-transform: uppercase; color: var(--grey); font-variant-numeric: tabular-nums; }

/* ---- Logo sheet. Shows the shipped asset files, not a CSS approximation. ---- */
.sheet { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--gutter); max-width: 68rem; margin: 0 auto; }
.sheet--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.sheet__cell { border: var(--line) solid var(--rule); aspect-ratio: 16 / 9; display: grid; place-items: center; padding: var(--s5); background: var(--paper); overflow: hidden; }
.sheet__cell--neg { background: var(--demo-black); border-color: var(--demo-black); }
.sheet__cell--sq { aspect-ratio: 1; max-width: 22rem; margin: 0 auto; }
.sheet__cell--sq img { width: 9.625rem; height: 9.625rem; }
.sheet__cell img { display: block; max-width: 100%; max-height: 100%; width: auto; height: auto; }
.sheet__cell--fill { padding: 0; }
.sheet__cell--fill img { width: 100%; height: 100%; object-fit: contain; }
.sheet__cap { margin-top: var(--s3); font-family: var(--font-ui); font-size: var(--t-ui-s); font-weight: var(--w-med); letter-spacing: var(--ls-label); word-spacing: var(--ws-label); text-transform: uppercase; color: var(--grey); text-align: center; }
.sheet__file { margin-top: var(--s1); font-family: var(--font-ui); font-size: var(--t-ui-s); color: var(--grey); text-align: center; }
.scale { display: flex; flex-wrap: nowrap; align-items: flex-end; gap: var(--s6); justify-content: flex-start; justify-content: safe center; overflow-x: auto; padding-bottom: var(--s3); scrollbar-width: thin; }
.scale figure { flex: none; }
.scale figure { margin: 0; text-align: center; }
.scale img { display: block; }
.scale figcaption { margin-top: var(--s3); font-family: var(--font-ui); font-size: var(--t-ui-s); letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--grey); }

/* ---- Responsive ---- */
@media (max-width: 68rem) {
  .dossier { grid-template-columns: var(--rail-w) minmax(0, 1fr); }
  .dossier .aside { grid-column: 1 / -1; max-width: 20rem; margin-top: var(--s7); }
  /* The screen grid drops to 2 before it drops to 1. A drawn screen at a third
     of a tablet is texture rather than a screen; at a half of one it reads. */
  .index__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 60rem) {
  .masthead { position: static; }
  .masthead__inner { grid-template-columns: minmax(0, 1fr) auto; row-gap: var(--s4); }
  .masthead__nav { grid-column: 1 / -1; grid-row: 2; justify-content: flex-start; flex-wrap: wrap; }
  .masthead__end { grid-column: 2; grid-row: 1; }
  .nav__link, .toggle, .button { display: inline-flex; align-items: flex-end; min-height: 44px; min-width: 44px; }
  /* The logotype is the home link and it was the one masthead control under the
     44px floor at 24px tall. It cannot take a min-height: the black rule is its
     own border-bottom, so a taller box would carry the identity away from the
     word. The hit area is extended around it instead, which moves no pixel. */
  .lockup::before { content: ""; position: absolute; left: 0; right: 0; top: 50%; transform: translateY(-50%); height: 44px; }
  .dossier { grid-template-columns: minmax(0, 1fr); }
  .dossier .rail { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); column-gap: var(--gutter); row-gap: var(--s5); margin-bottom: var(--s7); }
  .rail__pair + .rail__pair { margin-top: 0; }
  .read { max-width: var(--measure); }
  .figures { grid-template-columns: minmax(0, 1fr); }
  .colophon__top { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .sheet { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 52rem) {
  .banner__title { font-weight: var(--w-light); letter-spacing: 0.08em; padding-left: 0.08em; }
}

@media (max-width: 44rem) {
  .sheet, .sheet--3 { grid-template-columns: minmax(0, 1fr); }
  .index__grid, .index__grid--2 { grid-template-columns: minmax(0, 1fr); row-gap: var(--s7); }
  .index__grid--2 { row-gap: var(--s7); }
  /* One column, and the page's own margin is 20px here, so 32 inside the tile
     on top of it reads as a gutter rather than a card. */
  .index__grid--2 .card { padding: var(--s4); }
  .caps__item { grid-template-columns: minmax(0, 1fr); row-gap: var(--s2); }
  .steps__item { grid-template-columns: minmax(0, 1fr); }
  .steps__num, .steps__key, .steps__text { grid-column: 1; grid-row: auto; }
  .steps__text { margin-top: var(--s3); }
  .pager { grid-template-columns: minmax(0, 1fr); }
  .pager__link--next { text-align: left; }
  .colophon__top { grid-template-columns: minmax(0, 1fr); }
}

/* ---- Reduced motion ----
   The page has no load sequence and no scroll animation, deliberately. What is
   left that moves is colour, opacity and border colour on hover and focus, the
   theme swap, and smooth anchor scrolling. Under reduce, none of it moves.
   The universal selector is the point: it catches anything added later without
   the author having to remember this block. Stated with !important because the
   transitions it overrides are declared on class selectors, the same reason
   .is-theming states it. ---- */
/* ---- Consent ----
   A bar on the floor of the window, in the page's own materials: paper ground,
   a hairline boundary, and the 3 answers set as the same button the rest of
   the site uses. No accent on the bar itself, because it is neither a state
   nor the identity; the accent arrives under a button when it is reached, the
   same as everywhere else. A refusal has the same weight and the same reach as
   a yes, which is what the regulator asks for and what the grid gives free. ---- */
/* Until a measurement ID is set there is nothing to consent to, so the
   controls that only make sense alongside the banner are not shown at all. The
   flag goes on the root from JavaScript, which also means a reader with
   JavaScript off never sees a control that could not work for them. */
:root:not([data-consent-live]) [data-consent-open],
:root:not([data-consent-live]) [data-consent-only] { display: none; }

.consent {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  background: var(--paper);
  border-top: var(--line) solid var(--ink);
}
.consent__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  column-gap: var(--gutter);
  row-gap: var(--s3);
  padding-top: var(--s4);
  padding-bottom: var(--s4);
}
.consent__title {
  grid-column: 1;
  font-family: var(--font-ui);
  font-size: var(--t-ui-s);
  font-weight: var(--w-med);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--grey);
  margin-bottom: var(--s2);
}
.consent__text {
  grid-column: 1;
  max-width: var(--measure);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: var(--text);
  text-wrap: pretty;
}
.consent__actions {
  grid-column: 2;
  grid-row: 1 / 3;
  display: flex;
  align-items: flex-end;
  gap: var(--s5);
}
.consent__options {
  grid-column: 1 / -1;
  margin-top: var(--s4);
  padding-top: var(--s4);
  border-top: var(--hair) solid var(--rule);
}
.consent__row + .consent__row { margin-top: var(--s3); }
.consent__row + .consent__row { padding-top: var(--s3); border-top: var(--hair) solid var(--rule); }
.consent__key {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-family: var(--font-ui);
  font-size: var(--t-ui-s);
  font-weight: var(--w-med);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--ink);
}
label.consent__key { cursor: pointer; }
.consent__val {
  margin-top: var(--s2);
  max-width: var(--measure);
  font-size: var(--t-ui);
  line-height: var(--lh-ui);
  color: var(--grey);
}
/* The one control on the site the reader can switch. It takes the ink, never
   the accent: the accent reports state and this reports a choice not yet
   saved. */
.consent__box { accent-color: var(--ink); width: 1rem; height: 1rem; margin: 0; }
.consent__save { margin-top: var(--s4); }

@media (max-width: 46rem) {
  .consent__inner { grid-template-columns: minmax(0, 1fr); }
  .consent__actions { grid-column: 1; grid-row: auto; gap: var(--s4); }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: var(--dur-none) !important;
    animation-delay: var(--dur-none) !important;
    animation-iteration-count: 1 !important;
    transition-duration: var(--dur-none) !important;
    transition-delay: var(--dur-none) !important;
    scroll-behavior: auto !important;
  }
}

/* ---- Print ----
   The floor asks for pages that print legibly in 1 colour. A reader in the
   night view was printing #F2F2F3 type onto white paper, so the day values go
   back on by reference rather than by a repeated hex. Nothing here is a new
   design value; it is the day page, on paper, with the controls removed. ---- */
@media print {
  html, [data-theme="dark"] {
    --paper: var(--paper-day);
    --ink: var(--ink-day);
    --text: var(--text-day);
    --grey: var(--grey-day);
    --rule: var(--rule-day);
    --fill: var(--fill-day);
    color-scheme: light;
  }
  /* The colophon swaps to the negative mark in the night view. On paper the
     negative is white on white, so the positive comes back. */
  [data-theme="dark"] .colophon__logo { display: block; }
  [data-theme="dark"] .colophon__logo--neg { display: none; }
  /* Controls a sheet of paper cannot use. */
  .skip, .toggle, .consent { display: none; }
  .masthead { position: static; }
  /* A card, a step or a figure reads as one thing, so it does not straddle
     2 sheets. */
  .card, .caps__item, .steps__item, .figures__item, .plate, .quote { break-inside: avoid; }
}
