/* =============================================================================
   ATUM — components.css
   Everything that appears on more than one page: the mark, header, status strip,
   language toggle, buttons, cards, the spine, the timeline, spec tables,
   placeholders and the footer. A second page reuses all of it unchanged.
   ========================================================================== */

/* ---------- The mark -------------------------------------------------------
   The benben: a low mound of solid ground, cut by the horizon. Two shapes and
   one line. No pyramid, no sun disc, nothing that reads as costume.          */

/* THE MARK. Round eight, mark 6, RING — approved by Angelo on 2026-08-13.

   TWO ROLES, NOT FIVE COLOURS. `form` is the hub disc and two of the three
   blades; `accent` is the amber ring and the third blade. Naming them for
   their role is what lets ONE inline drawing be correct on paper and on night:
   form inherits the link's colour, and accent is the only thing that has to be
   re-tinted when the light changes.

   THE PATHS ARE GENERATED, NOT DRAWN HERE. projects/atum-brand/src/weblogo.js
   emits them from the same ringmark.js the media kit is built from, so the
   site cannot drift from the kit. Before today it had: the site drew a
   flat-topped trapezoid, the kit drew a circle, and nobody had decided that.
   Re-run `node src/weblogo.js --apply` from the brand folder, never hand-edit
   the <svg> in the partials.

   `overflow: visible` is deliberate — the blades reach 27.5 of the 32-unit
   half-artboard, so there is 4.5 units of built-in margin and nothing clips,
   but a future variation with a rim would. */
.mark { width: 1.75rem; height: 1.75rem; flex: none; overflow: visible; }
.mark__form   { fill: currentColor; }
.mark__accent { fill: var(--ochre); transition: fill var(--ph-mark) var(--ease-dusk) var(--ph-d-mark); }
.on-night .mark__accent { fill: var(--sun); }

/* ---------- The header mark sits on its own night plate --------------------
   Angelo, 2026-08-13: "the atum logo is black? its barely visible, can we use
   the coloured version?"

   He is describing the paper artwork, which is what the kit specifies for a
   light ground and is what was already shipping — this is NOT a case of the
   monochrome variant having been wired up by mistake. On cream the drawing has
   to be ink to be seen at all, and the ink is roughly four fifths of it: three
   of the four painted shapes. The amber is then `--ochre` #9E5410, which is the
   only amber that clears contrast on paper (5.3:1, against 2.3:1 for `--sun`,
   both measured) and is a dark brown. So at 28 px the whole thing reads dark,
   and it reads dark for a good reason rather than by accident.

   The one move that makes the mark read as COLOURED on a light bar, without
   inventing a second amber and without redrawing anything, is to give it a
   ground it was drawn for. `MEDIA-KIT.md` §6.5 already names this as the
   answer for a ground the mark cannot sit on: put it on a solid plate. The
   plate carries the night artwork, so the header mark is now identical to the
   footer one, which is the "coloured version" being asked for.

   Nothing here recolours the mark (§6.1) and nothing redraws it (§6.7): the
   same two role classes are pointed at the two night tokens, and the plate is
   a CSS box behind them. The footer is untouched — it is already night on
   night.

   The padding is the kit's clear space, 10 of 64 artboard units, so the blades
   never touch the plate edge. Keep the drawing at 28 px if you change the box:
   the box is the drawing plus twice the padding.                              */
.header .mark {
  width: 2.375rem;
  height: 2.375rem;
  padding: 0.3125rem;
  box-sizing: border-box;
  background: var(--ink);
  border-radius: 0.625rem;
}
.header .mark__form   { fill: var(--paper); }
.header .mark__accent { fill: var(--sun); }

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: var(--s3);
  text-decoration: none;
  color: var(--ink);
  font-weight: 800;
  font-size: 1.375rem;
  letter-spacing: 0.02em;
  line-height: 1;
}
.on-night .wordmark { color: var(--on-night); }
.wordmark__text { display: block; padding-top: 0.1em; }

/* ---------- Header ---------------------------------------------------------
   Sticky. Holds nav and the language toggle and nothing else — the page's one
   call to action lives in the page, not in the furniture.                    */

.header {
  position: sticky;
  top: 0;
  z-index: 60;
  background: color-mix(in srgb, var(--paper) 92%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: var(--border) solid var(--rule);
}
@supports not (backdrop-filter: blur(4px)) { .header { background: var(--paper); } }

.header__bar {
  display: flex;
  align-items: center;
  gap: var(--s5);
  min-height: 4.25rem;
}

.nav {
  display: none;
  margin-left: auto;
  align-items: center;
  gap: clamp(var(--s4), 2vw, var(--s6));
}
@media (min-width: 62rem) { .nav { display: flex; } }

.nav__link {
  position: relative;
  text-decoration: none;
  font-size: var(--t-sm);
  font-weight: 600;
  letter-spacing: 0.005em;
  color: var(--ink-2);
  padding-block: var(--s2);
  transition: color var(--dur-1) var(--ease);
}
.nav__link:hover { color: var(--ink); }
.nav__link::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: -2px;
  height: 2px;
  background: var(--ochre);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-2) var(--ease);
}
.nav__link:hover::after { transform: scaleX(1); }
.nav__link[aria-current='page'] { color: var(--ink); }
.nav__link[aria-current='page']::after { transform: scaleX(1); }

/* ---------- Language picker ------------------------------------------------
   Six languages. The old EN|NL pair of toggles was the right control for two
   and the wrong one for six: six segments are 210 px wide, which does not fit
   a 360 px header next to a wordmark and a menu button, and it would break
   again at seven. So it is a disclosure button and a list.

   The trigger shows the two-letter code because that is what fits and what is
   universally read as a language control; the list carries each language's
   own name in its own script, which is what a visitor actually recognises.
   The accessible name on the trigger carries the full language name, so the
   code is never the only thing a screen reader gets.                       */

.lang {
  position: relative;
  margin-left: auto;
}
@media (min-width: 62rem) { .lang { margin-left: 0; } }

.lang__trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  padding: 0.4rem 0.5rem 0.4rem 0.6rem;
  border: var(--border) solid var(--rule-strong);
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  font-weight: 500;
  letter-spacing: var(--track-mono);
  color: var(--ink);
  background: transparent;
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
.lang__trigger:hover { background: var(--paper-3); }
.lang__trigger[aria-expanded='true'] { background: var(--ink); color: var(--paper); }
.lang__trigger[aria-busy='true'] { opacity: 0.55; }
.lang__current { display: block; }
.lang__chev {
  flex: none;
  color: var(--ink-3);
  transition: transform var(--dur-1) var(--ease);
}
.lang__trigger[aria-expanded='true'] .lang__chev { color: var(--paper); transform: rotate(180deg); }

/* Anchored to the control and right-aligned, so it can never run off a phone
   screen — the control itself is the rightmost thing in the bar. */
.lang__menu {
  position: absolute;
  top: calc(100% + var(--s2));
  right: 0;
  z-index: 70;
  min-width: 12.5rem;
  padding: var(--s2) 0 var(--s2);
  background: var(--paper);
  border: var(--border) solid var(--rule-2);
  border-radius: var(--radius);
  box-shadow: 0 18px 40px -22px rgb(20 26 40 / 0.5);
}
.lang__menu[hidden] { display: none !important; }

.lang__head {
  margin: 0 0 var(--s1);
  padding: var(--s1) var(--s4) var(--s2);
  max-width: none;
  color: var(--ink-3);
  border-bottom: var(--border) solid var(--rule);
}

.lang__opt {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  width: 100%;
  padding: var(--s2) var(--s4);
  font-size: var(--t-sm);
  color: var(--ink-2);
  border-left: 3px solid transparent;
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
.lang__opt:hover { background: var(--paper-2); color: var(--ink); }
/* On a touch screen these six sit close together and a mis-tap sends someone
   to a language they cannot read, which is a hard state to get out of. The
   trigger keeps the header's height so the bar still lines up; only the
   options in the panel grow. */
@media (pointer: coarse) {
  .lang__opt { min-height: 44px; align-items: center; }
}
.lang__opt[aria-current='true'] {
  border-left-color: var(--ochre);
  background: var(--paper-2);
  color: var(--ink);
  font-weight: 650;
}
/* min-width, not width: `2ch` is two zeros, and two letter-spaced capitals are
   wider than that, so a fixed width broke "EN" onto two lines. */
.lang__code {
  flex: none;
  min-width: 2.6ch;
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: var(--t-2xs);
  letter-spacing: 0.06em;
  color: var(--ink-3);
}
.lang__opt[aria-current='true'] .lang__code { color: var(--ochre-2); }
/* the native name is set in its own script, so it must not inherit the
   document's tracking or the Thai marks separate from their base letters */
.lang__name { letter-spacing: normal; line-height: 1.45; }

/* mobile menu */
.burger {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  margin-left: var(--s3);
  padding: 0.4rem 0.55rem;
  border: var(--border) solid var(--rule-strong);
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink);
}
@media (min-width: 62rem) { .burger { display: none; } }
.burger__ico { width: 14px; height: 10px; position: relative; }
.burger__ico::before, .burger__ico::after {
  content: '';
  position: absolute;
  left: 0; right: 0;
  height: 2px;
  background: currentColor;
}
.burger__ico::before { top: 0; }
.burger__ico::after { bottom: 0; }

.menu {
  position: absolute;          /* inside the header, never over its own button */
  left: 0; right: 0; top: 100%;
  background: var(--paper);
  border-bottom: var(--border) solid var(--rule-2);
  box-shadow: 0 18px 40px -24px rgb(20 26 40 / 0.45);
  padding: var(--s4) 0 var(--s5);
}
.menu[hidden] { display: none !important; }
.menu__link {
  display: block;
  padding: var(--s3) var(--gutter);
  text-decoration: none;
  font-size: var(--t-md);
  font-weight: 650;
  color: var(--ink);
  border-left: 3px solid transparent;
}
.menu__link[aria-current='page'] { border-left-color: var(--ochre); background: var(--paper-2); }
.menu__link:hover { background: var(--paper-2); }

/* ---------- Status strip ---------------------------------------------------
   Site furniture that argues the proposition: the page knows what time it is
   and says what that means. One clock drives this, the hero and the chat.    */

/* The strip answers to the clock along with the hero. It darkens as the day
   ends and picks up a single warm pixel along its bottom edge — the horizon,
   at one-pixel scale, in the one piece of furniture that is on every page.
   It moves LAST, after the sky: the world changes and the interface catches
   up, which is the difference between light changing and a theme toggle. */
.status {
  background: var(--strip-bg);
  color: var(--on-night-2);
  border-bottom: var(--border) solid var(--strip-rule);
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.05em;
  transition: background-color var(--ph-ui) var(--ease-dusk) var(--ph-d-ui),
              border-color     var(--ph-ui) var(--ease-dusk) var(--ph-d-ui);
}
.status__bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s2) var(--s4);
  min-height: 2.5rem;
  padding-block: var(--s2);
}
/* a <p> used as a layout row, so it has to opt out of the global measure.
   flex: 1 1 auto + min-width: 0 lets it wrap internally instead of pushing
   the live dot onto a line of its own on a narrow screen. */
.status__group {
  display: flex;
  align-items: center;
  gap: var(--s2) var(--s3);
  flex-wrap: wrap;
  flex: 1 1 auto;
  min-width: 0;
  max-width: none;
  margin: 0;
}
/* The live dot lives inside this paragraph, not beside it. A flex line breaks
   on base size before it shrinks, so as a sibling the long text pushed the dot
   onto a line of its own on a phone. */
.status__group .dot { margin-right: var(--s1); }
.status__time { color: var(--on-night); font-weight: 500; font-variant-numeric: tabular-nums; }
/* --night-500 is a BORDER token (3.6:1) and was being used here as a text
   colour, which is the one place on the site that fell under AA — found by the
   contrast pass, not by eye, and it predates the six-language work. The
   separators are aria-hidden decoration, so the rule is arguable, but the
   cheapest honest answer is to use the tertiary text token that is already
   checked at 5.6:1 rather than to exempt it from the test. */
.status__sep { color: var(--on-night-3); }
.status__state { color: var(--on-night-2); }
.status__answer {
  color: var(--strip-accent);
  font-weight: 500;
  transition: color var(--ph-ui) var(--ease-dusk) var(--ph-d-ui);
}

/* The two live readouts are replaced by site.js, not faded by it, so the words
   change in one frame while the sky is still moving. This puts a short lift
   under the swap so it reads as part of the same event.

   Two animation NAMES for one set of keyframes, because that is what makes it
   re-run: the name has to change for the browser to restart an animation, and
   the attribute flipping between day and night is what changes it. No
   JavaScript timing, so there is no callback to miss and nothing to leave the
   text stuck at zero opacity. It writes `opacity` and nothing else — no
   transform, so no keyframe can overwrite one. */
@keyframes phase-lift-a { from { opacity: 0.2; } to { opacity: 1; } }
@keyframes phase-lift-b { from { opacity: 0.2; } to { opacity: 1; } }
:root[data-phase='day']   :where(.status__state, .status__answer, .chat__launch-label)
  { animation: phase-lift-a var(--ph-ui) var(--ease-dusk) var(--ph-d-ui) both; }
:root[data-phase='night'] :where(.status__state, .status__answer, .chat__launch-label)
  { animation: phase-lift-b var(--ph-ui) var(--ease-dusk) var(--ph-d-ui) both; }

/* The live dot. Static state is a solid dot with a ring — complete without
   the pulse. The pulse animates a pseudo-element that owns no other
   transform, so nothing is overwritten. */
.dot {
  position: relative;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--strip-dot);
  flex: none;
  transition: background-color var(--ph-ui) var(--ease-dusk) var(--ph-d-ui);
}
.dot::after {
  content: '';
  position: absolute;
  inset: -4px;
  border-radius: 50%;
  border: 1px solid var(--strip-dot);
  opacity: 0.55;
  transition: border-color var(--ph-ui) var(--ease-dusk) var(--ph-d-ui);
}
@media (prefers-reduced-motion: no-preference) {
  .dot::after { animation: dot-ring 2.6s var(--ease) infinite; }
}
@keyframes dot-ring {
  0%   { transform: scale(0.6); opacity: 0.9; }
  70%  { transform: scale(1.35); opacity: 0; }
  100% { transform: scale(1.35); opacity: 0; }
}

/* THE DEMO SWITCH'S BUTTON USED TO LIVE HERE. Removed 2026-08-13 — Angelo read
   it as the only control on the page and went looking for a login behind it.

   THE FUNCTION IT DROVE IS STILL THERE, on his instruction: "the button needs
   to go, not the function". It is turned on with ?demo=1 on any page. The
   button is gone from the MARKUP rather than hidden with a rule here, because
   a display:none button is still focusable and still announced — a control
   that exists for keyboard and screen-reader users and not for anyone else.
   There is therefore nothing left to style, which is why there is no rule.

   `.demo-tag` below is NOT part of this and must stay: chat.js draws the same
   badge in the chat window's title bar to say the assistant is a demo. */
@media (max-width: 47.99rem) {
  .status { font-size: var(--t-2xs); }
  .status__bar { gap: var(--s2) var(--s3); min-height: 0; padding-block: var(--s2); }
  /* The header bar does not wrap, and at 320 px the wordmark, the language
     picker and the burger came to 0.9 px more than the row. Found by measuring
     documentElement.scrollWidth at 320, not by eye, and `overflow-x: hidden` on
     the body was hiding it. The 24 px flex gap already separates it from the
     picker, so the extra 12 px was doing no work at this width. Pre-dates the
     Client setup control and is nothing to do with it: the same 0.9 px is there
     with no injected control at all. */
  .burger { margin-left: 0; }
}
.demo-tag {
  font-family: var(--font-mono);
  font-size: var(--t-2xs);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--night-900);
  background: var(--sun);
  padding: 0.1rem 0.3rem;
  border-radius: 2px;
}

/* ---------- Buttons --------------------------------------------------------
   The rule follows the palette: dark on light ground, warm on dark ground.
   One primary per screen.                                                    */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s3);
  padding: 0.85rem 1.35rem;
  border-radius: var(--radius);
  font-size: var(--t-base);
  font-weight: 650;
  letter-spacing: -0.005em;
  text-decoration: none;
  border: var(--border) solid transparent;
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease),
              border-color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}
.btn__arrow { flex: none; }

.btn--primary { background: var(--ink); color: var(--paper); }
.btn--primary:hover { background: #232C42; }
.btn--primary:active { transform: translateY(1px); }

.on-night .btn--primary,
.btn--sun { background: var(--sun); color: var(--night-900); }
.on-night .btn--primary:hover,
.btn--sun:hover { background: var(--sun-hi); }

.btn--ghost {
  background: transparent;
  color: var(--ink);
  border-color: var(--rule-strong);
}
.btn--ghost:hover { background: var(--paper-2); border-color: var(--ink); }
.on-night .btn--ghost { color: var(--on-night); border-color: var(--night-500); }
.on-night .btn--ghost:hover { background: var(--night-700); border-color: var(--on-night-3); }

/* the quiet, in-flow "read more" link — never competes with a button */
/* inline, not inline-flex: when the label wraps, the arrow has to follow the
   last word rather than being pushed to the far edge of the column */
.tlink {
  display: inline;
  font-weight: 650;
  font-size: var(--t-base);
  color: var(--ochre-2);
  text-decoration: none;
  border-bottom: 2px solid var(--paper-3);
  padding-bottom: 1px;
  transition: border-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
.tlink:hover { border-bottom-color: var(--ochre); }
.on-night .tlink { color: var(--sun); border-bottom-color: var(--night-600); }
.on-night .tlink:hover { border-bottom-color: var(--sun); }
.tlink__arrow { font-family: var(--font-mono); font-size: 0.9em; margin-left: 0.35em; white-space: nowrap; }

/* ---------- Placeholder marker ---------------------------------------------
   Everything that needs a real value from Angelo is drawn like this. It is
   deliberately loud: filler that looks plausible is how a placeholder ships
   to production by accident.                                                 */

.ph {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #8A2B12;
  background: #FBEDE6;
  border: 1px dashed #C2603F;
  border-radius: 2px;
  padding: 0.12rem 0.42rem;
  /* must be allowed to wrap: some of these labels are long, and a chip that
     cannot wrap pushes the whole page sideways on a phone */
  overflow-wrap: anywhere;
  max-width: 100%;
}
.on-night .ph { color: #FFC9B3; background: #33150C; border-color: #A2563A; }

.ph-block {
  border: 1px dashed var(--rule-strong);
  border-radius: var(--radius);
  background:
    repeating-linear-gradient(135deg, transparent 0 10px, rgb(142 128 105 / 0.07) 10px 20px);
  padding: var(--s6);
  display: grid;
  gap: var(--s3);
  place-items: center;
  text-align: center;
  min-height: 9rem;
}
.on-night .ph-block {
  border-color: var(--night-500);
  background: repeating-linear-gradient(135deg, transparent 0 10px, rgb(85 104 156 / 0.12) 10px 20px);
}

/* ---------- Cards and panels ----------------------------------------------
   One panel component, three surfaces. Not a different rounded card per
   section — the site is arguing that everything is one system.               */

.panel {
  background: var(--paper);
  border: var(--border) solid var(--rule-2);
  border-radius: var(--radius-lg);
  padding: clamp(var(--s5), 3vw, var(--s7));
}
.panel--sunk { background: var(--paper-2); border-color: var(--rule); }
.panel--night {
  background: var(--night-850);
  border-color: var(--night-700);
  color: var(--on-night);
}

/* ---------- Spec list ------------------------------------------------------
   A definition list drawn as hairline rows. Used for "what happens",
   "what you have to do", pricing, and the onboarding weeks.                  */

.spec { border-top: var(--border) solid var(--rule); }
.spec__row {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s1) var(--s6);
  padding-block: var(--s4);
  border-bottom: var(--border) solid var(--rule);
}
@media (min-width: 44rem) {
  .spec__row { grid-template-columns: 9.5rem minmax(0, 1fr); }
}
.spec__key {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
  padding-top: 0.25rem;
}
.spec__val { color: var(--ink); }
.spec__val > p { max-width: var(--measure); }
.on-night .spec { border-color: var(--night-700); }
.on-night .spec__row { border-color: var(--night-700); }
.on-night .spec__key { color: var(--on-night-3); }
.on-night .spec__val { color: var(--on-night); }

/* ---------- Marked list ----------------------------------------------------
   The one bullet style on the site: a short horizon dash, not a disc.        */

.mlist > li {
  position: relative;
  padding-left: var(--s5);
  max-width: var(--measure);
}
.mlist > li + li { margin-top: var(--s3); }
.mlist > li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.72em;
  width: 0.75rem;
  height: 2px;
  background: var(--ochre);
}
.on-night .mlist > li::before { background: var(--sun); }

.mlist--tight > li + li { margin-top: var(--s2); }

/* a "will not do" list — same rhythm, different mark, no red-alert styling */
.nlist > li { position: relative; padding-left: var(--s5); max-width: var(--measure); }
.nlist > li + li { margin-top: var(--s3); }
.nlist > li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 0.6rem; height: 0.6rem;
  border: 2px solid var(--ink-3);
  border-radius: 50%;
}
.on-night .nlist > li::before { border-color: var(--on-night-3); }

/* ---------- Data table (the report artefact) ------------------------------ */

.tablewrap { overflow-x: auto; -webkit-overflow-scrolling: touch; min-width: 0; max-width: 100%; }
.dtable {
  /* narrow enough to fit the rail's content column on a desktop, so the
     horizontal scroll only appears where it is genuinely needed */
  min-width: 33rem;
  font-size: var(--t-sm);
  border-top: var(--border) solid var(--rule-2);
}
.dtable th:last-child, .dtable td:last-child { width: 8rem; padding-right: 0; }
.dtable th {
  font-family: var(--font-mono);
  font-size: var(--t-2xs);
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-3);
  padding: var(--s3) var(--s4) var(--s3) 0;
  border-bottom: var(--border) solid var(--rule-2);
  white-space: nowrap;
}
.dtable td {
  padding: var(--s4) var(--s4) var(--s4) 0;
  border-bottom: var(--border) solid var(--rule);
  vertical-align: top;
  color: var(--ink-2);
  /* A .vh label inside a cell is position:absolute, so it resolves against the
     nearest positioned ancestor. Without this it resolved against the page, and
     because this table is wider than a phone and scrolls inside .tablewrap, the
     1px box landed at the table's own x and dragged the whole document sideways:
     51 px of horizontal scroll on the body at 390 px wide, in every language.
     Caught by measuring documentElement.scrollWidth, not by looking at it. */
  position: relative;
}
.dtable td:first-child {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  color: var(--ink);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.dtable tr:hover td { background: var(--paper-2); }

/* ---------- Footer -------------------------------------------------------- */

.footer {
  background: var(--night-900);
  color: var(--on-night-2);
  padding-block: var(--s9) var(--s6);
}
.footer__grid {
  display: grid;
  gap: var(--s7) var(--s6);
  grid-template-columns: 1fr;
}
@media (min-width: 46rem) { .footer__grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 68rem) { .footer__grid { grid-template-columns: 1.6fr 1fr 1fr 1.2fr; } }

.footer__title {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--on-night-3);
  margin-bottom: var(--s4);
}
.footer__list > li + li { margin-top: var(--s2); }
.footer__link {
  text-decoration: none;
  color: var(--on-night-2);
  font-size: var(--t-sm);
  border-bottom: 1px solid transparent;
  transition: color var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}
.footer__link:hover { color: var(--on-night); border-bottom-color: var(--sun); }
/* The standing build-status line. It appears on every page, so it is a stated
   fact rather than a disclaimer: an amber rule marks it the way the section
   numbers are marked, and it is not greyed down into a legal footnote. */
.footer__build {
  margin-top: var(--s5);
  max-width: 34ch;
  padding-left: var(--s4);
  border-left: 2px solid var(--sun);
  color: var(--on-night-2);
}
.footer__legal {
  margin-top: var(--s8);
  padding-top: var(--s5);
  border-top: var(--border) solid var(--night-700);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3) var(--s5);
  align-items: center;
  font-size: var(--t-sm);
  color: var(--on-night-3);
}

/* The staff links. They inherit the legal line's size and its muted colour so
   they read as the same class of thing as the copyright notice, and they are
   pushed to the far end of the row so they never sit next to anything a
   prospect is meant to click. Underlined only on hover and focus: without the
   underline it is a word, and a word at --on-night-3 on --night-900 is 5.6:1,
   which clears AA for body text without shouting. */
.footer__legal-link {
  color: inherit;
  text-decoration: none;
}
/* They are wrapped in one element rather than sitting loose in the legal row,
   and that is a layout decision, not tidiness. Loose, the auto margin has to go
   on one of them and the row's own wrapping then breaks the PAIR: at 360 px in
   French and in Russian the notice and the first link shared a line and the
   second dropped alone to the next one, which reads as three unrelated items
   instead of a copyright notice and two staff doors. As a group they wrap
   together or not at all. */
.footer__staff {
  margin-left: auto;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3) var(--s5);
}
.footer__legal-link:hover,
.footer__legal-link:focus-visible { color: var(--on-night-2); text-decoration: underline; }
@media (max-width: 47.99rem) {
  /* `margin-left: auto` on a wrapped flex row strands the group on a line of
     its own. At this width the row is already stacked, so it just follows the
     notice. */
  .footer__staff { margin-left: 0; }
}

/* ---------- Section numbering --------------------------------------------- */

.snum {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  font-weight: 500;
  letter-spacing: 0.1em;
  color: var(--ochre);
}
.on-night .snum { color: var(--sun); }
