/* Shared styling for every page that is not the homepage: the legal set, the
   status pages, our-work, blog and about.

   This file is the SINGLE SOURCE for the site header and footer. The two
   homepages carry their own inline copies because PRODUCT.md settles them as
   self-contained files; everything else loads this, and a change to the chrome
   belongs here rather than in any individual page. Behaviour lives beside it in
   page.js.

   Relative URLs below resolve against this file's own location, not the loading
   page's, so `fonts/…` is correct for pages in subdirectories too. */

@font-face{font-family:'Nunito';font-style:normal;font-weight:400 900;font-display:swap;src:url('fonts/nunito-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}
@font-face{font-family:'Nunito';font-style:normal;font-weight:400 900;font-display:swap;src:url('fonts/nunito-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}
@font-face{font-family:'Nunito';font-style:normal;font-weight:400 900;font-display:swap;src:url('fonts/nunito-cyrillic.woff2') format('woff2');unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116}
@font-face{font-family:'Russo One';font-style:normal;font-weight:400;font-display:swap;src:url('fonts/russo-one-400-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}
@font-face{font-family:'Russo One';font-style:normal;font-weight:400;font-display:swap;src:url('fonts/russo-one-400-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}
@font-face{font-family:'Russo One';font-style:normal;font-weight:400;font-display:swap;src:url('fonts/russo-one-400-cyrillic.woff2') format('woff2');unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116}

:root{
  --ground:#f1f0ed; --ink:#222222;
  --gold:#ffc933; --mint:#7fd6c2;
  --display:'Russo One',system-ui,sans-serif;
  --body:'Nunito',system-ui,sans-serif;
  --edge:4px solid var(--ink);
  --edge-2:2px solid var(--ink);
  --focus:3px solid var(--ink);
  /* The three easings DESIGN.md fixes a job to each of: control state changes,
     long-tail deceleration for anything arriving, and shorter departures. */
  --ease:cubic-bezier(.22,1,.36,1);
  --ease-arrive:cubic-bezier(.16,1,.3,1);
  --ease-out:cubic-bezier(.4,0,.2,1);
  --tap:44px;
  --r:16px;
  /* Same two tokens as the homepage's master grid (see index.html) -- the
     lattice's own vertical spacing and row half-height. Kept in step here so
     the loading screen's dice and grid match the homepage's exactly on every
     page that shows one. */
  --tile:80px; --tile-row:calc(var(--tile) * 0.5773525);
  --colw:960px; --gutter:24px;
  --grid:rgba(34,34,34,.085);
  /* The master grid's three layers, held once so body and .ck-load can each
     reference them instead of repeating the same 16-line block twice. */
  --grid-bg-image:
    linear-gradient(30deg,
      transparent calc(50% - 0.5px), var(--grid) calc(50% - 0.5px),
      var(--grid) calc(50% + 0.5px), transparent calc(50% + 0.5px)),
    linear-gradient(-30deg,
      transparent calc(50% - 0.5px), var(--grid) calc(50% - 0.5px),
      var(--grid) calc(50% + 0.5px), transparent calc(50% + 0.5px)),
    linear-gradient(to right, var(--grid) 1px, transparent 1px);
  --grid-bg-size:
    calc(var(--tile) * 2) calc(var(--tile-row) * 2),
    calc(var(--tile) * 2) calc(var(--tile-row) * 2),
    var(--tile) 100%;
  --grid-bg-position:
    calc(50% + var(--tile)) top,
    calc(50% + var(--tile)) top,
    calc(50% + var(--tile) / 2) top;
  --shadow:0 8px 0 var(--ink);
  --shadow-pill:0 4px 0 var(--ink);
}
@media (max-width:1439.98px){:root{--tile:40px}}

*{box-sizing:border-box}
html{scroll-behavior:smooth;scroll-padding-top:146px}
/* The master grid is baseline now (2026-08-15, owner's call), not opt-in,
   and it is the FULL lattice -- both 30-degree families plus the verticals --
   not just the vertical family. This was hero-only at first, on the reasoning
   that only the hero has a dice to roll on the diagonals; the owner's later
   call was that the full isometric pattern is the site's surface everywhere,
   dice or not. Same three gradient layers as index.html's body (see there for
   why gradients rather than a data URI, and why plain, not repeating,
   linear-gradients). This retires the earlier "reading pages and status pages
   drop the grid" rule: the legal set and 404 carry it too, same as every other
   page. `.paper-grid` is no longer read anywhere and can be dropped from
   markup wherever it still appears; it does nothing extra now that body
   itself carries the grid. */
body{margin:0;background-color:var(--ground);color:var(--ink);
  font-family:var(--body);font-size:18px;line-height:1.6;
  overflow-x:clip;overflow-wrap:anywhere;hyphens:none;
  background-image:var(--grid-bg-image);
  background-size:var(--grid-bg-size);
  background-position:var(--grid-bg-position)}

/* ===== Loading screen =====
   Full-bleed graph paper with the dice rolling its diamond in the middle. It
   carries the grid whatever the page underneath does, because this is the card
   table before any cards are on it.

   Three things stop it ever gating content, which the Failsafe Rule forbids:
   the class that reveals it is set by script, so with JavaScript off the page
   is never covered; the same block that sets it also schedules its removal, so
   no later failure can strand it; and it waits before showing at all, so a page
   that is already there never flashes a loader at anybody.

   Below .skip's z-index on purpose — tab during a load and the skip link still
   comes out on top. */
.ck-load{position:fixed;inset:0;z-index:98;display:none;place-items:center;
  background-color:var(--ground);
  /* The full lattice: the loading screen is nothing but paper and the mark.
     Its own --tile, scaling continuously with viewport (see index.html's
     .ck-load) rather than stepping with the page's own layout, which these
     shared pages don't otherwise tie to the grid at all. Gradients, not a
     data URI, so --grid actually reaches every line here too. */
  --tile:min(80px, 11vw);
  background-image:var(--grid-bg-image);
  background-size:var(--grid-bg-size);
  background-position:var(--grid-bg-position)}
.is-loading .ck-load{display:grid}
.ck-load img{width:calc(var(--tile) * 7);max-width:62vw;height:auto}

:focus-visible{outline:3px solid var(--ink);outline-offset:3px;border-radius:2px}
main:focus{outline:none}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

.skip{position:absolute;left:-9999px;top:0;z-index:99;
  background:var(--gold);color:var(--ink);border:var(--edge);border-radius:0 0 8px 0;
  padding:14px 20px;font-family:var(--display);font-size:1rem;text-decoration:none}
/* left:0 with a negative offset, so the whole ring is drawn inside the element's
   own box rather than clipped by the viewport corner. */
.skip:focus{left:0;outline-offset:-3px}

/* ===== Header =====
   Full bleed, so its ground fill and its stuck edge run the whole screen. The
   fill is load-bearing twice: it hides the graph paper behind the bar and stops
   content reading through it. Sticky at every width — the persistent call to
   action is the only conversion path on a long page. */
header{position:sticky;top:0;z-index:10;background:var(--ground);
  box-shadow:0 2px 0 var(--ink)}
/* 1008px literal, NOT var(--colw) + gutters -- see index.html's .head-inner
   for why this is decoupled from the tile-driven grid width. */
.head-inner{display:flex;align-items:center;justify-content:space-between;
  position:relative;
  max-width:1008px;margin:0 auto;
  padding:39px max(var(--gutter),env(safe-area-inset-right)) 20px max(var(--gutter),env(safe-area-inset-left));gap:32px;flex-wrap:wrap}
.menu{display:contents}
.burger{display:none}
.brand{display:flex;align-items:center;gap:16px;color:var(--ink);text-decoration:none;flex:none}
.brand .mark{width:48px;height:55px;flex:none}
/* forced-color-adjust: in forced-colours mode the cube's facets otherwise
   collapse into one silhouette and the mark reads as a blob. */
.brand .mark,.foot-brand-row .mark{forced-color-adjust:none}
.brand .word{font-family:var(--display);font-weight:400;font-synthesis-weight:none;
  font-size:24px;letter-spacing:1.92px;line-height:1}

nav ul{display:flex;gap:26px;list-style:none;margin:0;padding:0;
  font-family:var(--body);font-weight:400;font-size:1rem;flex-wrap:wrap}
nav ul li{display:flex}
nav a{color:var(--ink);text-decoration:none;display:inline-flex;align-items:center;
  justify-content:center;min-height:var(--tap);min-width:var(--tap);padding-inline:8px;
  position:relative}
/* The phantom bold copy sits in the SAME grid cell as the visible label, so the
   box resolves to the wider of the two and is permanently bold-width. Switching
   weight on hover then moves nothing. */
nav .lbl{display:inline-grid;justify-items:center;white-space:nowrap}
nav .lbl::after{content:attr(data-label);grid-area:1/1;font-weight:700;
  height:0;overflow:hidden;visibility:hidden;pointer-events:none}
nav a:hover,nav a:focus-visible,
nav a[aria-current="page"]{font-weight:700}
/* Main menu only, never the footer's legal links. Absolutely positioned inside
   the anchor's own box, so it can never push a neighbour or change row height. */
#site-nav a::before{content:'';position:absolute;left:0;right:0;bottom:4px;height:4px;
  border-radius:2px;background:var(--gold);
  transform:scaleX(0);transform-origin:left center;
  transition:transform .22s var(--ease-out)}
#site-nav a:hover::before,#site-nav a:focus-visible::before,
#site-nav a[aria-current="page"]::before{transform:scaleX(1)}

/* ===== A menu item that is not a link yet =====
   /our-work/ has no project pages behind it, so the nav item that points there
   is disabled rather than pointing at an empty page. A plain span, not an <a>
   with no href or a script click-blocker: a disabled-looking link still reads
   as a link to a screen reader or a hovering mouse, and a span never promises
   anything a click could disappoint. Same box model as nav a, so it takes the
   same width and sits in the same place the link used to.

   Opacity sits on the LABEL, not on .nav-soon itself: opacity composites down
   the whole subtree at paint time, so a child's own opacity:1 cannot undo a
   dimmed ancestor's -- putting it on the container would have faded the tag
   to the same washed-out grey as the label instead of leaving it legible. */
.nav-soon{display:inline-flex;align-items:center;justify-content:center;gap:6px;
  min-height:var(--tap);min-width:var(--tap);padding-inline:8px;position:relative;
  color:var(--ink)}
.nav-soon-lbl{opacity:.55}
.nav-soon .tag{display:inline-flex;align-items:center;justify-content:center;
  padding:2px 8px;border:var(--edge-2);border-radius:var(--r);background:var(--ground);
  font-family:var(--display);font-weight:400;font-synthesis-weight:none;
  /* DESIGN.md's utility tier: reusing the tooltip step (0.8125rem) rather than
     inventing a size below the documented ramp's smallest rung. */
  font-size:.8125rem;line-height:1.4;text-transform:uppercase;white-space:nowrap}
/* ===== Temporarily hidden (2026-08-16, corrected 2026-08-17) =====
   Same treatment as .lang below: display:none rather than deleting the
   markup, so bringing it back is one line. The disabled span and its "Soon"
   tag stay intact underneath -- this just stops the item taking a slot in the
   nav at all, rather than showing as a non-clickable one.
   A class on the <li> itself, not :has(.nav-soon): :has() is a real
   accessibility bug here, not a nice-to-fix compatibility footnote, since
   the whole point of this rule is to hide something and a visitor on a
   browser that predates :has() (any Firefox before 121, released late
   2023) simply sees the rule not apply -- the item stays visible with no
   indication anything is wrong. A hide rule that only sometimes hides
   is worse than not having it. */
.is-soon{display:none}

/* ===== Buttons ===== */
.pill{display:inline-flex;align-items:center;justify-content:center;
  font-family:var(--display);font-weight:400;font-synthesis-weight:none;
  font-size:18px;line-height:1.5;padding:16px;min-height:var(--tap);
  border:3px solid var(--ink);border-radius:var(--r);
  background:var(--ground);color:var(--ink);
  box-shadow:var(--shadow-pill);cursor:pointer;text-transform:uppercase;
  text-decoration:none;white-space:nowrap;
  transition:transform .18s var(--ease),box-shadow .18s var(--ease),
    background-color .18s var(--ease)}
.pill.primary{background:var(--gold)}
.pill.accent{background:var(--mint)}
/* The shadow is 4px of apparent height, so hover lifts into it and press spends
   exactly that 4px: the plate's lower edge always lands where the shadow said. */
/* Gated: on a touch device :hover can latch after a tap and leave the
   control stuck in its lifted state. */
@media (hover:hover){
  .pill:hover{transform:translateY(-2px);box-shadow:0 6px 0 var(--ink)}
}
.pill:active{transform:translateY(4px);box-shadow:0 0 0 var(--ink)}

/* ===== Language switcher ===== */
.head-end{display:flex;align-items:center;gap:16px;flex:none}
/* ===== Temporarily withdrawn (2026-08-14) =====
   display:none, not opacity or visibility: a hidden control must also leave
   the tab order rather than sit there as an invisible stop for a keyboard
   user.

   Serbian is shelved and the site ships English only, so the switcher goes; its
   markup, flag sprites and the script that relocates it into the mobile panel
   all survive untouched.

   Our Projects (2026-08-15) is back in nav and in build-deploy.mjs's
   SUB_PAGES -- see /our-work/index.html, which now carries a proper "coming
   soon" placeholder instead of staying withdrawn. */
.lang{display:none}
.lang{position:relative}
.lang > summary{display:inline-flex;align-items:center;gap:8px;cursor:pointer;
  list-style:none;padding:10px 12px;min-height:var(--tap);
  border:0;border-radius:var(--r);background:transparent;
  font-family:var(--display);font-weight:400;
  font-synthesis-weight:none;font-size:14px;line-height:1;
  transition:background-color .18s var(--ease)}
.lang > summary::-webkit-details-marker{display:none}
.lang > summary::after{content:'';width:0;height:0;flex:none;
  border:5px solid transparent;border-top-color:var(--ink);margin-top:5px;
  transition:transform .2s var(--ease-out)}
.lang[open] > summary::after{transform:rotate(180deg);margin-top:-5px}
/* No plate, so no shadow to spend: hover tints instead of moving. */
@media (hover:hover){.lang > summary:hover{background:rgba(34,34,34,.07)}}
.lang[open] > summary{background:rgba(34,34,34,.07)}
.lang > ul{position:absolute;right:0;top:calc(100% + 10px);z-index:20;
  margin:0;padding:6px;list-style:none;min-width:180px;background:var(--ground);
  border:var(--edge-2);border-radius:var(--r);box-shadow:var(--shadow)}
.lang > ul a{display:flex;align-items:center;gap:10px;padding:10px 12px;
  min-height:var(--tap);border-radius:8px;color:var(--ink);text-decoration:none}
@media (hover:hover){.lang > ul a:hover{background:var(--gold)}}
.lang > ul a[aria-current]{font-weight:700}
/* Inline SVG, never emoji: Windows ships no regional-indicator glyphs, so a
   flag emoji renders there as the bare letter pair. */
.flag{width:24px;height:16px;flex:none;border:1px solid var(--ink);
  border-radius:2px;background-size:cover;background-position:center}
.flag-en{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 16'%3E%3Crect width='24' height='16' fill='%23012169'/%3E%3Cpath d='M0 0l24 16M24 0L0 16' stroke='%23fff' stroke-width='3.4'/%3E%3Cpath d='M0 0l24 16M24 0L0 16' stroke='%23C8102E' stroke-width='1.7'/%3E%3Cpath d='M12 0v16M0 8h24' stroke='%23fff' stroke-width='5.4'/%3E%3Cpath d='M12 0v16M0 8h24' stroke='%23C8102E' stroke-width='3.2'/%3E%3C/svg%3E")}
.flag-sr{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 16'%3E%3Crect width='24' height='16' fill='%23f1f0ed'/%3E%3Crect width='24' height='5.34' fill='%23c6363c'/%3E%3Crect y='5.33' width='24' height='5.34' fill='%230c4076'/%3E%3C/svg%3E")}

/* ===== Reading pages =====
   68ch: long enough not to feel cramped, short enough to stay readable. */
.doc{max-width:68ch;margin:0 auto;padding:80px 24px 120px}
.doc h1{font-family:var(--display);font-weight:400;font-synthesis-weight:none;
  font-size:clamp(2.25rem,6vw,3.5rem);line-height:1.15;margin:0 0 24px;
  text-transform:uppercase}
.doc h2{font-family:var(--display);font-weight:400;font-synthesis-weight:none;
  font-size:1.5rem;line-height:1.3;margin:56px 0 12px;text-transform:uppercase}
.doc p{margin:0 0 18px}
.doc .lead{font-size:1.25rem;line-height:1.55}
.doc .updated{font-size:.9375rem;opacity:.7;margin-bottom:8px}
.doc a{color:var(--ink);text-decoration-thickness:2px;text-underline-offset:3px}
@media (hover:hover){.doc a:hover{text-decoration-thickness:3px}}
.doc strong{font-weight:700}
.doc code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.9em;
  background:rgba(34,34,34,.07);padding:2px 6px;border-radius:4px}

/* Articles run three heading levels where the legal set only needs two: an
   article's subsections are steps in an argument, not clauses. Sized between
   .doc h2 and body so the hierarchy reads without a fourth type step. */
.doc h3{font-family:var(--display);font-weight:400;font-synthesis-weight:none;
  font-size:1.125rem;line-height:1.35;margin:36px 0 10px;text-transform:uppercase}

.facts{margin:0 0 18px;padding-left:22px}
.facts li{margin-bottom:6px}

/* ===== Series callback =====
   A spoke's opening pointer back to the hub, and to the effect-size explainer
   where it uses a figure. No border, no accent colour: a coloured side-tab
   was tried here first and broke two rules at once -- Signal Yellow's one job
   is the primary ask, and borders are ink-only. Reduced size and opacity is
   the same signal .meta already uses to mark itself as secondary, so this
   follows that precedent instead of inventing a new one. */
.doc .callback{font-size:.9375rem;font-style:italic;opacity:.7;
  margin:0 0 32px}
.doc .callback a{font-style:normal}

/* ===== Author note =====
   Sits once, at the foot, ahead of "Back to articles". A rule above it does
   the same separating job .back's own margin does elsewhere, so the two read
   as one closing block rather than two unrelated afterthoughts. */
.doc .author-note{margin:56px 0 0;padding-top:24px;border-top:2px solid var(--ink);
  font-size:.9375rem;line-height:1.6}

/* ===== Reference list =====
   Hanging indent, because a reader scans a reference list down the authors'
   surnames and a flush-left wrap buries them. Set below body size: this is
   apparatus to be checked, not prose to be read. */
.refs{list-style:none;margin:0;padding:0}
.refs li{font-size:.9375rem;line-height:1.5;margin:0 0 14px;
  padding-left:24px;text-indent:-24px}

/* The byline block under an article's H1. */
.doc .meta{font-size:.9375rem;opacity:.7;margin:0 0 32px}

/* ===== Article index =====
   A list of articles is a list, so it is marked up as one and the bullets are
   dropped. Each entry's rule is the separator the reading pages already use
   between sections, at the lighter weight a list needs. */
.posts{list-style:none;margin:48px 0 0;padding:0}
.posts li{padding-top:32px;border-top:2px solid var(--ink)}
.posts li + li{margin-top:32px}
.posts h2{margin:0 0 6px}
.posts h2 a{text-decoration:none}
@media (hover:hover){.posts h2 a:hover{text-decoration:underline;
  text-decoration-thickness:3px;text-underline-offset:4px}}
.posts .meta{margin:0 0 10px}
.posts p:last-child{margin-bottom:0}

.doc table{width:100%;border-collapse:collapse;margin:0 0 18px;
  border:3px solid var(--ink);border-radius:var(--r);overflow:hidden}
.doc th,.doc td{text-align:left;padding:12px 14px;font-size:1rem;
  border-bottom:2px solid var(--ink);vertical-align:top}
.doc thead th{background:var(--gold);font-weight:700}
.doc tbody tr:last-child td{border-bottom:0}

/* ===== Figures =====
   Same plate as .doc table, deliberately: a chart and a table are the same
   kind of object in a reading page -- apparatus lifted out of the prose -- so
   they take the same border, radius and full-width caption rule. Flat, no
   shadow: the Spend-the-Shadow Rule keeps the raised plate for cards, and an
   article's apparatus is not a card.
   An opaque plate matters more here than it does for a table. Table cells let
   the lattice through and read fine; a chart read against moving diagonals
   does not, because the grid lines compete with the plot's own rules. */
.doc figure{margin:32px 0;padding:20px 20px 0;
  border:3px solid var(--ink);border-radius:var(--r);background:var(--ground)}
/* A chart scales as one object, type included, so a 560-unit plot dropped into
   the ~300px column a 390px phone leaves would set its axis labels at about
   6px. Below the floor the plot scrolls sideways instead of shrinking further:
   a chart nobody can read is not a smaller chart, it is a missing one.
   tabindex makes the scroll container keyboard-operable, which a scrollable
   region has to be (SC 2.1.1) -- without it the right-hand half of the plot is
   reachable by pointer only. */
.doc .fig-plot{overflow-x:auto}
.doc .fig-plot:focus-visible{outline:var(--focus);outline-offset:2px;border-radius:4px}
/* The SVG inherits font and colour rather than restating either. Anything
   drawn in currentColor therefore tracks --ink automatically, and only the
   two accents are ever named inside a chart. */
.doc figure svg{display:block;width:100%;min-width:500px;height:auto;
  font-family:var(--body)}
.doc figcaption{margin:16px -20px 0;padding:12px 20px;
  font-size:.9375rem;line-height:1.5;border-top:2px solid var(--ink)}

/* A figure is not prose and does not owe the 68ch reading measure. On a phone
   that measure computes to roughly 320px, which left a dense plot showing its
   label column and the zero axis on first paint -- the reader met the
   apparatus and none of the finding. Below the width where the measure stops
   helping, the plate spans the viewport instead, keeping a 12px gutter.
   Safe against a stray horizontal scrollbar because body carries
   overflow-x:clip, so any sub-pixel spill from 50vw is clipped, not scrolled. */
@media (max-width:820px){
  .doc figure{margin-inline:calc(50% - 50vw + 12px);padding:16px 12px 0}
  .doc figcaption{margin:12px -12px 0;padding:12px}
}

.back{margin-top:64px;font-weight:600}

/* ===== Status pages (404, under construction) =====
   Centred rather than set to a reading measure: there is no document to read,
   only one illustration, one sentence and a way out. The art is capped at 320px
   because these clips are drawn at 480 and upscaling a 480px source to fill a
   desktop column reads as a stretched GIF, which is the exact impression a page
   admitting a gap cannot afford. */
.status{max-width:60ch;margin:0 auto;padding:72px 24px 120px;text-align:center}
.status .art{width:min(320px,72vw);margin:0 auto 8px}
.status .art video,.status .art img{width:100%;height:auto;aspect-ratio:1/1;
  display:block;border-radius:var(--r)}
.status h1{font-family:var(--display);font-weight:400;font-synthesis-weight:none;
  font-size:clamp(2rem,7vw,3.25rem);line-height:1.15;margin:0 0 16px;
  text-transform:uppercase}
.status .lead{font-size:1.1875rem;line-height:1.6;margin:0 auto 32px;max-width:46ch}
.status .actions{display:flex;gap:16px;justify-content:center;flex-wrap:wrap}
/* The .ids block that used to sit here is gone. It existed because these pages
   had no footer, and it restated the four identifiers as a run-on line; the
   shared footer now carries them as copyable plates directly below. */

/* ===== Footer =====
   A band, not a region of the board: its edge and fill run the full viewport,
   and its opaque ground stops the graph paper reading on underneath the
   identifiers. */
#site-footer{position:relative;margin-top:140px;border-top:var(--edge);background:var(--ground);
  padding-top:32px;padding-bottom:max(64px,calc(24px + env(safe-area-inset-bottom)))}
/* 1008px literal, same reasoning as .head-inner: the footer's identifier
   plates are chrome, not grid-aligned cards, and squeezed to a tile-halved
   480px between 1000-1479px they read as four cramped, near-illegible
   plates rather than the four registration numbers they carry. */
.foot-inner{display:flex;flex-direction:column;align-items:center;text-align:center;
  max-width:1008px;margin:0 auto;
  padding-inline:max(var(--gutter),env(safe-area-inset-left)) max(var(--gutter),env(safe-area-inset-right))}
.foot-brand-row{display:flex;align-items:center;justify-content:space-between;
  width:100%;margin-top:64px}
.foot-social{display:flex;gap:8px}
.foot-social a{display:inline-flex;align-items:center;justify-content:center;
  width:44px;height:44px;color:var(--ink);border-radius:var(--r);
  transition:background-color .18s var(--ease),transform .18s var(--ease)}
/* Gated: on a touch device :hover can latch after a tap and leave the
   control stuck in its lifted state. */
@media (hover:hover){
  .foot-social a:hover{background:rgba(34,34,34,.07);transform:translateY(-2px)}
}
.foot-social svg{width:24px;height:24px;display:block}

/* The identifiers get the form-field treatment rather than fine print: for an
   evaluator these ARE the checkable content of the page. */
/* 40px literal, deliberately NOT calc(var(--tile)/2). The token form looks
   tidier and matches the homepage's own line, but --tile is 40 between 1000
   and 1440, which would halve this gap to 20 across that whole range -- a
   desktop change, made as a side effect of a mobile fix. The shipped desktop
   spacing is 40 and stays 40. */
.foot-ids{display:grid;grid-template-columns:repeat(4,1fr);gap:40px;width:100%}
.foot-ids .id{min-width:0}
.foot-ids dt{font-family:var(--display);font-weight:400;font-synthesis-weight:none;
  font-size:1.5rem;line-height:1.5;margin:0 0 8px;text-align:left}
.foot-ids dd{margin:0}
.foot-ids .copy{position:relative;width:100%;display:flex;align-items:center;
  justify-content:center;min-height:72px;padding:16px;
  font-family:inherit;font-size:1.5rem;line-height:1.5;color:var(--ink);
  border:var(--edge);border-radius:var(--r);background:var(--ground);
  box-shadow:var(--shadow-pill);cursor:pointer;
  transition:transform .18s var(--ease),box-shadow .18s var(--ease),
    background-color .18s var(--ease)}
/* Gated: on a touch device :hover can latch after a tap and leave the
   control stuck in its lifted state. */
@media (hover:hover){
  .foot-ids .copy:hover{transform:translateY(-2px);box-shadow:0 6px 0 var(--ink)}
}
.foot-ids .copy:active{transform:translateY(4px);box-shadow:0 0 0 var(--ink)}
.foot-ids .copy.is-copied{background:var(--mint)}
/* Hidden from assistive tech: the button already carries its label and the
   status line announces the result, so nothing is read twice. */
.foot-ids .tip{position:absolute;bottom:calc(100% + 10px);left:50%;
  transform:translate(-50%,4px);white-space:nowrap;pointer-events:none;
  padding:6px 10px;font-family:var(--body);font-size:.8125rem;font-weight:700;
  line-height:1;color:var(--ground);background:var(--ink);border-radius:6px;
  opacity:0;transition:opacity .16s var(--ease-out),transform .16s var(--ease-out)}
.foot-ids .copy:focus-visible .tip,
.foot-ids .copy.is-copied .tip{opacity:1;transform:translate(-50%,0)}
/* :hover split out because a touch user never gets it, but still gets the
   .is-copied confirmation above. */
@media (hover:hover){
  .foot-ids .copy:hover .tip{opacity:1;transform:translate(-50%,0)}
}
/* SC 1.4.13 Dismissable. The tip overlaps that identifier's own label, so the
   "does not obscure" exemption does not apply and it needs an Escape route that
   moves neither pointer nor focus. :is() carries its most specific argument's
   specificity, which puts this one class above each show rule deliberately —
   relying on source order would let a reorder switch dismissal back off. */
.tips-off .foot-ids .copy:is(:hover,:focus-visible,.is-copied) .tip{
  opacity:0;transform:translate(-50%,4px)}

.foot-legal{margin-top:48px;font-size:.9375rem;line-height:1.7}
.foot-legal-nav{display:flex;gap:24px;flex-wrap:wrap;justify-content:center}
.foot-legal-nav a{color:var(--ink);font-weight:600;
  text-decoration-thickness:2px;text-underline-offset:4px;
  display:inline-flex;align-items:center;min-height:var(--tap)}
/* Same specificity as the rule above, so it has to stay after it. */
.foot-legal a[aria-current="page"]{font-weight:700}
.foot-legal p{margin:24px 0 0}

/* ===== Back to top =====
   Square rather than circular: a circle would be the one shape in the system
   with no corner radius at all, a second form language for a single control. */
.totop{position:fixed;z-index:20;
  right:max(24px,env(safe-area-inset-right));
  bottom:max(24px,env(safe-area-inset-bottom));
  width:56px;height:56px;display:inline-flex;align-items:center;justify-content:center;
  border:var(--edge-2);border-radius:var(--r);background:var(--ground);color:var(--ink);
  box-shadow:var(--shadow-pill);cursor:pointer;padding:0;
  opacity:0;transform:translateY(12px);pointer-events:none;
  transition:opacity .22s var(--ease-out),transform .22s var(--ease-out),
    box-shadow .18s var(--ease),background-color .18s var(--ease)}
.totop.is-visible{opacity:1;transform:none;pointer-events:auto}
/* Gated: on a touch device :hover can latch after a tap and leave the
   control stuck in its lifted state. */
@media (hover:hover){
  .totop:hover{transform:translateY(-2px);box-shadow:0 6px 0 var(--ink)}
}
.totop:active{transform:translateY(2px);box-shadow:0 2px 0 var(--ink)}
.totop svg{width:22px;height:22px;display:block}

/* ===== One column ===== */
@media (max-width:1000px){
  html{scroll-padding-top:96px}
  .head-inner{padding:12px max(var(--gutter),env(safe-area-inset-right)) 12px max(var(--gutter),env(safe-area-inset-left));gap:12px;flex-wrap:nowrap}
  /* Mark only. The wordmark plus the button plus the menu overruns a 390px bar,
     and the answer is to drop the word rather than shrink any of the three —
     shrinking it would introduce a display size the ramp does not have. Header
     only: the footer brand has no competition for its row and keeps its word. */
  .head-inner .brand{gap:10px;margin-right:auto}
  .head-inner .brand .word{display:none}
  .head-end{gap:10px}
  /* The details regains a box here so it can hold the panel. order:-1 puts the
     button first in the row without moving it in the DOM, where it belongs
     after the mark for reading order. */
  .menu{display:block;order:-1;position:static}
  .burger{display:inline-flex;align-items:center;justify-content:center;
    width:var(--tap);height:var(--tap);flex:none;cursor:pointer;list-style:none;
    margin-left:-10px}
  .burger::-webkit-details-marker{display:none}
  /* One bar drawn, two more hung off it, so the three stay locked together and
     the close state is a rotation of the same pieces rather than a second icon. */
  .burger > span,
  .burger > span::before,
  .burger > span::after{display:block;width:26px;height:3px;border-radius:2px;
    background:var(--ink);transition:transform .22s var(--ease-out),
      top .22s var(--ease-out),background-color .22s var(--ease-out)}
  .burger > span{position:relative}
  .burger > span::before,.burger > span::after{content:'';position:absolute;left:0}
  .burger > span::before{top:-8px}
  .burger > span::after{top:8px}
  .menu[open] > .burger > span{background:transparent}
  .menu[open] > .burger > span::before{top:0;transform:rotate(45deg)}
  .menu[open] > .burger > span::after{top:0;transform:rotate(-45deg)}
  /* The panel hangs off .head-inner, which is the positioned ancestor, so it
     spans the full bar rather than the padded column. */
  .menu > #site-nav{position:absolute;left:0;right:0;top:100%;z-index:30;
    background:var(--ground);border-bottom:var(--edge);
    padding:8px max(var(--gutter),env(safe-area-inset-left)) 16px}
  .menu > #site-nav ul{flex-direction:column;align-items:stretch;gap:0}
  .menu > #site-nav a,.menu > #site-nav .nav-soon{justify-content:flex-start;
    min-height:52px;padding-inline:0;font-size:1.125rem}
  .menu > #site-nav .lbl{justify-items:start}
  /* The switcher moves in here on a phone (see page.js) rather than being
     hidden, so it stays reachable at every width. Left in the header row it
     overran a 375px viewport and pushed the whole page sideways. */
  .menu > #site-nav .lang{margin-top:8px}
  .lang > ul{right:auto;left:0}
  /* Bottom padding clears the back-to-top control rather than sitting under it.
     That button is fixed 24px off the bottom edge and 56px tall, so at the foot
     of the page it occupies the last 80px of the viewport — which is exactly
     where the address and copyright line was landing. */
  #site-footer{margin-top:96px;
    padding-bottom:max(96px,calc(72px + env(safe-area-inset-bottom)))}
  /* The desktop row of four identifiers becomes a 2x2 block: four across at
     phone width leaves each plate about 60px wide, which cannot hold a
     nine-digit number. Two across keeps the plate treatment legible and keeps
     the pairing (OID/PIC are the EU pair, VAT/Reg. Number the domestic pair)
     reading as pairs. Type drops to the ramp's 18px Body step with it, matching
     the downshift card copy already takes below 1000px. That holds every
     identifier on one line from 360px up; at 320px the 116px column wraps the
     nine-digit numbers whatever the size, so it is not worth going off-ramp to
     chase. */
  .foot-ids{grid-template-columns:repeat(2,1fr);gap:24px}
  .foot-ids dt{font-size:1.125rem;margin-bottom:6px}
  .foot-ids .copy{font-size:1.125rem;min-height:60px;padding:12px}
  /* Two clusters side by side gets tight below 480px or so — the mark, the
     wordmark and two 44px tap targets no longer fit one row without crowding. */
  .foot-brand-row{flex-direction:column;align-items:center;gap:16px;margin-top:48px}
  .foot-legal{margin-top:40px}

}

/* ===== Registering the column on the lattice =====
   The Grid-Aligned Edge Rule needs a centred box's width to be an EVEN
   multiple of --tile: its edges sit at centre +/- w/2 and the lines at
   centre + n*tile, so w/2 has to be a whole tile. --tile stays 40 here, the
   same size the lattice is on the web -- so the column is snapped down to a
   multiple of 80 and the GUTTER absorbs the remainder, rather than the tile
   shrinking to make the arithmetic easier. A finer phone tile was tried and
   rejected: it registered, but the lattice read as too tight against a
   surface that is 40px everywhere else.

   The floor is 16px rather than the usual 24px gutter, which is what lets a
   360px phone reach 320 instead of dropping to 240.

   Guarded because round() is the whole mechanism: without it the width is
   dropped and padding-inline:0 would strip the gutter with nothing to
   replace it. */
@supports (width: round(down, 100px, 40px)){
  @media (max-width:1000px){
    .doc,.status,.foot-inner{
      width:round(down, calc(100% - 32px), calc(var(--tile) * 2));
      max-width:none;margin-inline:auto;padding-inline:0}
  }
}

@media (max-width:700px){
  .doc{padding:56px 24px 88px}
  .doc h2{margin-top:44px}
  /* A table this narrow cannot reflow, so it scrolls inside its own box
     rather than pushing the page sideways. */
  .doc table{display:block;overflow-x:auto}
  .status{padding:48px 24px 88px}
}

/* Motion is removed, not shortened. */
@media (prefers-reduced-motion:reduce){
  *{transition:none!important;animation:none!important;scroll-behavior:auto!important}
  html{scroll-behavior:auto}
  .totop{transition:opacity .01ms linear!important}
}

@media print{
  header,.totop,.skip,#site-footer .foot-ids,.foot-social{display:none!important}
  body{background-image:none}
}
