
/* ========================================================================
   1. TOKENS
   ======================================================================== */

:root {
  color-scheme: light;

  /* ---- ramp: neutrals, faintly green-tinted so greys read warm not dead */
  --n-0: #ffffff;    --n-0: oklch(100% 0 142);
  --n-25: #fbfcfa;   --n-25: oklch(99% 0.003 142);
  --n-50: #f6f8f6;   --n-50: oklch(97.8% 0.004 142);
  --n-100: #f0f3f0;  --n-100: oklch(96% 0.005 142);
  --n-150: #eaede9;  --n-150: oklch(94.2% 0.006 142);
  --n-200: #e0e4e0;  --n-200: oklch(91.5% 0.007 142);
  --n-300: #d0d4cf;  --n-300: oklch(86.5% 0.008 142);
  --n-400: #aeb3ad;  --n-400: oklch(76% 0.009 142);
  --n-500: #848984;  --n-500: oklch(62.5% 0.01 142);
  --n-600: #646964;  --n-600: oklch(51.5% 0.01 142);
  --n-700: #494d48;  --n-700: oklch(41.5% 0.009 142);
  --n-800: #2e312e;  --n-800: oklch(31% 0.008 142);
  --n-900: #231f1e;  --n-900: oklch(24.4% 0.006 30);
  --n-950: #111410;  --n-950: oklch(18.5% 0.008 142);

  /* ---- ramp: brand. --brand-700 IS the logo's forest green, measured. */
  --brand-50: #edf7ec;   --brand-50: oklch(96.6% 0.019 142.1);
  --brand-100: #d9efd7;  --brand-100: oklch(93% 0.04 142.1);
  --brand-200: #bbdeb7;  --brand-200: oklch(86.5% 0.065 142.1);
  --brand-300: #90c08b;  --brand-300: oklch(76% 0.09 142.1);
  --brand-500: #357430;  --brand-500: oklch(50% 0.12 142.1);
  --brand-600: #1c5b17;  --brand-600: oklch(41.5% 0.118 142.1);
  --brand-700: #054402;  --brand-700: oklch(33.6% 0.111 142.1);
  --brand-800: #023300;  --brand-800: oklch(28% 0.094 142.1);
  --brand-900: #032301;  --brand-900: oklch(22.5% 0.072 142.1);

  /* ---- ramp: timber. --timber-600 IS the logo's grain amber, measured. */
  --timber-50: #fff3e8;   --timber-50: oklch(97% 0.02 62);
  --timber-100: #ffe4cc;  --timber-100: oklch(93.5% 0.045 62);
  --timber-200: #fbc9a4;  --timber-200: oklch(87% 0.075 58);
  --timber-300: #eca575;  --timber-300: oklch(78% 0.105 55);
  --timber-500: #cb7721;  --timber-500: oklch(64.8% 0.14 60.1);
  --timber-600: #ba5a1b;  --timber-600: oklch(57.6% 0.143 48.6);
  --timber-700: #90400e;  --timber-700: oklch(47% 0.122 47);
  --timber-800: #652b05;  --timber-800: oklch(36.5% 0.096 47);

  /* ---- ramp: alarm */
  --alarm-50: #ffefed;   --alarm-50: oklch(96.5% 0.018 27);
  --alarm-100: #ffdfda;  --alarm-100: oklch(93% 0.038 27);
  --alarm-200: #fbc1b9;  --alarm-200: oklch(86% 0.068 27);
  --alarm-600: #b6322d;  --alarm-600: oklch(52% 0.17 27);
  --alarm-700: #92201e;  --alarm-700: oklch(43.5% 0.15 27);
  --alarm-800: #6c1614;  --alarm-800: oklch(35% 0.12 27);

  /* ---- ramp: info */
  --info-50: #ecf5fe;   --info-50: oklch(96.5% 0.016 250);
  --info-100: #d7eafe;  --info-100: oklch(93% 0.034 250);
  --info-200: #b6d6f8;  --info-200: oklch(86.5% 0.058 250);
  --info-600: #1666aa;  --info-600: oklch(50% 0.13 250);
  --info-700: #004c88;  --info-700: oklch(41% 0.12 250);
  --info-800: #003666;  --info-800: oklch(33% 0.1 250);

  /* ---- semantic aliases. EVERY rule below reads these, never the ramp. */
  --surface-page: var(--n-50);
  --surface-raised: var(--n-0);
  --surface-sunken: var(--n-100);
  --surface-header: var(--n-0);
  --surface-hover: var(--n-50);
  --surface-thead: var(--n-100);

  --ink: var(--n-900);
  --ink-soft: var(--n-600);
  --ink-faint: var(--n-500);
  --ink-inverse: var(--n-0);

  --edge: var(--n-200);          /* decorative hairline */
  --edge-strong: var(--n-300);   /* structural rule */
  --edge-control: var(--n-500);  /* form controls: 3:1, WCAG 1.4.11 */

  --accent: var(--brand-700);
  --accent-hover: var(--brand-800);
  --accent-wash: var(--brand-50);
  /*
   * The focus ring is the BRAND GREEN, not the timber amber it started as.
   * Amber was chosen so a ring could never be confused with the green that
   * means "ready", but on the sign-in screen the autofocused email field then
   * came up wearing a thick orange ring, and an orange ring around an empty
   * required field reads as "you have got this wrong" before a single character
   * is typed. A transient outline around the control you are actually in is
   * never going to be mistaken for a status badge, so green wins: it is the
   * brand, it is 11.45:1 on paper, and it never says "warning".
   */
  --focus: var(--brand-700);

  /* ---- type */
  --font-display: "Bahnschrift", "Segoe UI Variable Display", "Segoe UI Semibold",
                  "Segoe UI", system-ui, "Helvetica Neue", Arial, sans-serif;
  --font-ui: "Segoe UI Variable Text", "Segoe UI", -apple-system, BlinkMacSystemFont,
             system-ui, "Helvetica Neue", Arial, sans-serif;
  --font-mono: "Cascadia Mono", "Cascadia Code", Consolas, ui-monospace,
               SFMono-Regular, Menlo, monospace;

  /* modular scale, ratio 1.25 from a 16px base */
  --t-2xs: 0.6875rem;  /* 11px  eyebrow, column headings */
  --t-xs: 0.75rem;     /* 12px  micro copy */
  --t-sm: 0.8125rem;   /* 13px  small print */
  --t-base: 0.875rem;  /* 14px  dense table body */
  --t-md: 1rem;        /* 16px  body */
  --t-lg: 1.25rem;     /* 20px  h3 */
  --t-xl: 1.5rem;      /* 24px  h2 */
  --t-2xl: 1.875rem;   /* 30px  h1 */
  --t-3xl: 2.375rem;   /* 38px  the figures that matter */

  /* ---- space, 4px base because this is a dense tool */
  --s-1: 4px;   --s-2: 8px;   --s-3: 12px;  --s-4: 16px;
  --s-5: 20px;  --s-6: 24px;  --s-8: 32px;  --s-10: 40px;
  --s-12: 48px; --s-16: 64px;

  --r-sm: 3px; --r-md: 5px; --r-lg: 8px;

  /* ---- elevation. Swiss: hairline first, shadow only to lift off the page. */
  --e-1: 0 1px 2px rgba(20, 24, 20, 0.04);
  --e-2: 0 1px 2px rgba(20, 24, 20, 0.05), 0 2px 8px rgba(20, 24, 20, 0.05);
  --e-3: 0 4px 12px rgba(20, 24, 20, 0.08), 0 12px 32px rgba(20, 24, 20, 0.10);

  --measure: 74ch;
  --shell: 1240px;

  /*
   * THE GRAIN EDGE. The signature detail, and the only ornament in the system.
   *
   * SMOOTH stops, not hard ones, and a long uneven period. The first version of
   * this used hard 1px dividers on a 22px repeat, and at the 3px height it is
   * actually painted at that did not read as timber at all: it read as the tick
   * marks on a ruler. Colour positions with no ranges let the browser
   * interpolate between them, so what lands is a warm band that varies along
   * its length, which is what grain looks like at three pixels. Built from the
   * logo's own wood tones and nothing else.
   */
  --grain-v: repeating-linear-gradient(
    180deg,
    var(--timber-700) 0px,
    var(--timber-500) 11px,
    var(--timber-600) 19px,
    var(--timber-800) 28px,
    var(--timber-500) 37px,
    var(--timber-600) 48px,
    var(--timber-700) 57px
  );
  --grain-h: repeating-linear-gradient(
    90deg,
    var(--timber-700) 0px,
    var(--timber-500) 11px,
    var(--timber-600) 19px,
    var(--timber-800) 28px,
    var(--timber-500) 37px,
    var(--timber-600) 48px,
    var(--timber-700) 57px
  );
}

/* ========================================================================
   2. BASE
   ======================================================================== */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  /*
   * The gutter is reserved so a short page and a long one do not sit at
   * different left edges, which on a portal you navigate by clicking between
   * screens is a visible twitch on every load. The background is set here as
   * well as on body so the reserved strip and any overscroll area are the page
   * colour rather than a flash of white beside it.
   */
  scrollbar-gutter: stable;
  background: var(--surface-page);
}

body {
  margin: 0;
  min-height: 100dvh;
  background: var(--surface-page);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: var(--t-md);
  line-height: 1.55;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/*
 * Tabular figures are the default on this portal rather than an opt-in. Every
 * screen is a column of quantities that get compared to the one above, and a
 * proportional 1 next to a proportional 8 makes a tonnage column ragged. Prose
 * takes them back where a figure sits inside a sentence.
 */
p, .prose { font-variant-numeric: normal; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  text-wrap: balance;
  margin: 0 0 var(--s-2);
  line-height: 1.2;
  letter-spacing: -0.005em;
  color: var(--ink);
}
h1 { font-size: var(--t-2xl); letter-spacing: -0.015em; }
h2 { font-size: var(--t-xl); margin-top: var(--s-10); letter-spacing: -0.01em; }
h3 { font-size: var(--t-lg); margin-top: var(--s-6); }
h4 { font-size: var(--t-md); margin-top: var(--s-5); }
/* A heading that opens a card or a notice has already been spaced by its box. */
.card > h2:first-child, .card > h3:first-child,
.notice > h2:first-child, .notice > h3:first-child { margin-top: 0; }

p { text-wrap: pretty; margin: 0 0 var(--s-3); max-width: var(--measure); }
p:last-child { margin-bottom: 0; }

a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.15em;
}
a:hover { text-decoration-thickness: 2px; }

/*
 * One focus treatment everywhere, in the logo's amber so it never collides with
 * the green that means "ready". The white ring underneath keeps it visible when
 * the element it wraps is itself dark, such as the primary button.
 */
:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

::selection { background: var(--brand-100); color: var(--brand-900); }

hr { border: 0; border-top: 1px solid var(--edge); margin: var(--s-6) 0; }

img { max-width: 100%; height: auto; }

/* ========================================================================
   3. CHROME: header, nav, cog menu, main, footer
   ======================================================================== */

.site-header {
  position: sticky;
  top: 0;
  z-index: 30;
  background: var(--surface-header);
  color: var(--ink);
  padding: 0 var(--s-5);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-6);
  align-items: center;
  border-bottom: 1px solid var(--edge);
  box-shadow: var(--e-1);
}

/* The grain edge, first of four appearances: the firm's mark under its header. */
.site-header::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -3px;
  height: 3px;
  background-image: var(--grain-h);
}

.site-header .brand {
  display: inline-flex;
  align-items: center;
  padding: var(--s-3) 0;
  margin-right: var(--s-2);
}
.site-header .brand img { display: block; height: 34px; width: auto; }
.site-header .brand:focus-visible { outline-offset: 4px; }

.site-header nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center; /* John, 2 Sep: the section links sit centred between the logo and the cog. */
  gap: 0;
  flex: 1 1 auto;
  align-self: stretch;
}

/*
 * Tabs, not pills. The current section is marked with a rule on the baseline of
 * the header in the brand green, which is the Swiss convention for "you are
 * here" and reads at a glance without adding a filled shape to the chrome.
 */
.site-header nav a {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: var(--s-3) var(--s-3);
  font-family: var(--font-display);
  font-size: 0.9375rem;
  color: var(--n-700);
  text-decoration: none;
  white-space: nowrap;
  border-bottom: 3px solid transparent;
  transition: color 120ms ease-out, background-color 120ms ease-out;
}
.site-header nav a:hover {
  color: var(--ink);
  background: var(--surface-hover);
}
.site-header nav a[aria-current="page"] {
  color: var(--accent);
  font-weight: 600;
  border-bottom-color: var(--accent);
}

.site-header .who { color: var(--ink-soft); font-size: var(--t-base); }
.site-header form { margin: 0; }

/* The cog menu: a native <details>, so it works with no script. */
.site-header .menu { position: relative; margin-left: auto; }
.site-header .menu summary {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 44px;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-md);
  color: var(--n-700);
  border: 1px solid transparent;
  transition: background-color 120ms ease-out, border-color 120ms ease-out;
}
.site-header .menu summary::-webkit-details-marker { display: none; }
.site-header .menu summary:hover { background: var(--surface-hover); border-color: var(--edge); }
.site-header .menu[open] summary {
  background: var(--surface-sunken);
  border-color: var(--edge-strong);
  color: var(--ink);
}
/*
 * The cog is marked current when the screen behind it is one of its own, which
 * is what the panel used to say by forcing itself open over the page.
 */
.site-header .menu[data-current] summary {
  color: var(--accent);
  font-weight: 600;
  border-color: var(--brand-200);
  background: var(--accent-wash);
}
.site-header .menu summary svg { flex: none; transition: transform 200ms ease-out; }
.site-header .menu[open] summary svg { transform: rotate(60deg); }

.site-header .menu-panel {
  position: absolute;
  right: 0;
  top: calc(100% + var(--s-2));
  z-index: 40;
  min-width: 16rem;
  background: var(--surface-raised);
  color: var(--ink);
  border: 1px solid var(--edge-strong);
  border-radius: var(--r-lg);
  box-shadow: var(--e-3);
  padding: var(--s-1);
  display: flex;
  flex-direction: column;
}
.site-header .menu-panel .menu-user {
  margin: 0 0 var(--s-1);
  padding: var(--s-3) var(--s-3) var(--s-3);
  font-family: var(--font-display);
  font-weight: 600;
  border-bottom: 1px solid var(--edge);
  max-width: none;
}
.site-header .menu-panel .menu-user span {
  display: block;
  font-family: var(--font-ui);
  font-weight: 400;
  color: var(--ink-soft);
  font-size: var(--t-sm);
}
.site-header .menu-panel a {
  display: flex;
  align-items: center;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-md);
  color: var(--ink);
  text-decoration: none;
  min-height: 44px;
  font-size: var(--t-base);
}
.site-header .menu-panel a:hover { background: var(--surface-hover); }
.site-header .menu-panel a[aria-current="page"] {
  background: var(--accent-wash);
  color: var(--brand-800);
  font-weight: 600;
}
.site-header .menu-panel form {
  border-top: 1px solid var(--edge);
  margin-top: var(--s-1);
  padding-top: var(--s-1);
}
.site-header .menu-panel form button {
  width: 100%;
  text-align: left;
  justify-content: flex-start;
  background: transparent;
  border-color: transparent;
  font-weight: 400;
  color: var(--ink);
}
.site-header .menu-panel form button:hover { background: var(--surface-hover); }

main {
  padding: var(--s-8) var(--s-5) var(--s-16);
  max-width: var(--shell);
  margin: 0 auto;
}

/*
 * The page opener. The heading and the sentence under it are one block with one
 * measure, so the h1 and its explanation share a left edge and the sentence
 * cannot run the full width of a 1240px shell.
 */
main > h1 { margin-bottom: var(--s-2); }
main > h1 + p { color: var(--ink-soft); max-width: 68ch; margin-bottom: var(--s-6); }

footer.site-footer {
  padding: var(--s-6) var(--s-5) var(--s-10);
  max-width: var(--shell);
  margin: 0 auto;
  color: var(--ink-faint);
  font-size: var(--t-sm);
  border-top: 1px solid var(--edge);
}
footer.site-footer p { max-width: 82ch; }

/* ========================================================================
   4. SURFACES
   ======================================================================== */

.card {
  background: var(--surface-raised);
  border: 1px solid var(--edge);
  border-radius: var(--r-lg);
  padding: var(--s-5) var(--s-5);
  margin: 0 0 var(--s-5);
  box-shadow: var(--e-1);
}
.card > :first-child { margin-top: 0; }
.card > :last-child { margin-bottom: 0; }

.stack > * + * { margin-top: var(--s-4); }

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--s-4);
}

/* ---- figures -----------------------------------------------------------
 * The KPI strip. Hairline dividers rather than boxes, because four boxes in a
 * row is four things to look at and one ruled strip is one.
 */
.figure {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  padding-left: var(--s-5);
  border-left: 1px solid var(--edge);
}
.figure:first-child { padding-left: 0; border-left: 0; }
.figure .value {
  font-family: var(--font-display);
  font-size: var(--t-3xl);
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.figure .label {
  color: var(--ink-soft);
  font-size: var(--t-sm);
  text-wrap: pretty;
}
/*
 * A zero is not news. Muting it stops four equal-weight numerals competing when
 * only one of them is telling the office to do something.
 */
.figure.is-zero .value { color: var(--n-400); }

/* ========================================================================
   5. NOTICES
   ======================================================================== */

.notice {
  border: 1px solid var(--edge-strong);
  border-left-width: 4px;
  border-radius: var(--r-md);
  padding: var(--s-4) var(--s-5);
  margin: 0 0 var(--s-5);
  background: var(--surface-raised);
}
.notice > :first-child { margin-top: 0; }
.notice > :last-child { margin-bottom: 0; }
.notice h2, .notice h3 {
  margin-top: 0;
  margin-bottom: var(--s-2);
  font-size: var(--t-md);
  line-height: 1.35;
  display: flex;
  gap: var(--s-2);
  align-items: baseline;
}
/*
 * A glyph before every notice heading. The heading text already carries the
 * meaning; this is a second, non-colour channel for readers who are not
 * separating the tints, and it is aria-hidden by being generated content.
 */
.notice h3::before {
  content: "i";
  flex: none;
  font-family: var(--font-display);
  font-size: var(--t-xs);
  font-weight: 700;
  line-height: 1;
  width: 1.25rem; height: 1.25rem;
  display: inline-grid;
  place-items: center;
  border-radius: 50%;
  border: 1.5px solid currentColor;
  transform: translateY(0.1em);
}
.notice p { max-width: 76ch; }

/*
 * ONLY THE HEADING IS TINTED. The state colour is set on the h3 and on any
 * emphasised names in the prose, never on the notice as a whole.
 *
 * Setting a colour on the notice box itself was wrong, and the rates screen
 * proved it: it puts a 57-row TABLE inside an amber notice, and the tint
 * cascaded into every cell, so a page of site names, customers, products and
 * dates was rendered in brown. A notice tints its edge and its ground; the
 * content inside it stays page ink and stays legible.
 *
 * The dashboard's unmapped-customer alarm is the other half of the same idea:
 * twenty-one bold names in alarm red at full width was a wall, and the same
 * words at ordinary weight with only the NAMES picked out is a list.
 */
.notice { color: var(--ink); }
.notice-info  { border-color: var(--info-200);   border-left-color: var(--info-600);   background: var(--info-50); }
.notice-held  { border-color: var(--timber-200); border-left-color: var(--timber-600); background: var(--timber-50); }
.notice-ready { border-color: var(--brand-200);  border-left-color: var(--brand-700);  background: var(--brand-50); }
.notice-alarm { border-color: var(--alarm-200);  border-left-color: var(--alarm-600);  background: var(--alarm-50); }

.notice-info h2,  .notice-info h3,  .notice-info p strong  { color: var(--info-800); }
.notice-held h2,  .notice-held h3,  .notice-held p strong  { color: var(--timber-800); }
.notice-ready h2, .notice-ready h3, .notice-ready p strong { color: var(--brand-800); }
.notice-alarm h2, .notice-alarm h3, .notice-alarm p strong { color: var(--alarm-800); }
.notice p strong { font-weight: 600; }

.notice-ready h3::before { content: "\2713"; }
.notice-held h3::before  { content: "!"; }
.notice-alarm h3::before { content: "!"; }

.notice a { color: inherit; }

/*
 * A table inside a notice is a panel in its own right, not tinted content. The
 * rates screen holds 57 unpriced jobs inside its amber warning; without this
 * the rows float on the tint with the ground showing through every gap.
 */
.notice .table-scroll {
  background: var(--surface-raised);
  border: 1px solid var(--edge-strong);
  border-radius: var(--r-md);
  box-shadow: none;
  margin-top: var(--s-4);
  margin-bottom: 0;
}

/* ========================================================================
   6. BADGES
   ======================================================================== */

.badge {
  display: inline-block;
  border: 1px solid;
  border-radius: var(--r-sm);
  padding: 0.1rem 0.4rem;
  font-family: var(--font-display);
  font-size: var(--t-xs);
  font-weight: 600;
  line-height: 1.35;
  white-space: nowrap;
  vertical-align: baseline;
  background: var(--n-100);
  color: var(--n-700);
  border-color: var(--n-300);
}
.badge-ready      { background: var(--brand-50);  color: var(--brand-800);  border-color: var(--brand-200); }
.badge-held       { background: var(--timber-50); color: var(--timber-800); border-color: var(--timber-200); }
.badge-ineligible { background: var(--n-100);     color: var(--n-700);      border-color: var(--n-300); }
.badge-info       { background: var(--info-50);   color: var(--info-800);   border-color: var(--info-200); }
.badge-alarm      { background: var(--alarm-50);  color: var(--alarm-800);  border-color: var(--alarm-200); }
.badge + .badge { margin-left: var(--s-1); }

/* ========================================================================
   7. TABLES
   ======================================================================== */

.table-scroll {
  overflow-x: auto;
  margin: 0 0 var(--s-5);
  border: 1px solid var(--edge);
  border-radius: var(--r-lg);
  background: var(--surface-raised);
  box-shadow: var(--e-1);
}
/* A table already inside a card must not draw a second box around itself. */
.card .table-scroll, .group-body.table-scroll {
  border: 0; border-radius: 0; box-shadow: none; background: none; margin-bottom: 0;
}

table {
  border-collapse: collapse;
  width: 100%;
  font-size: var(--t-base);
  background: var(--surface-raised);
}
caption {
  text-align: left;
  font-family: var(--font-display);
  font-weight: 600;
  padding: 0 0 var(--s-2);
}
th, td {
  text-align: left;
  padding: var(--s-3) var(--s-4);
  border-bottom: 1px solid var(--edge);
  vertical-align: top;
}
thead th {
  position: relative;
  background: var(--surface-thead);
  border-bottom: 1px solid var(--edge-strong);
  font-family: var(--font-display);
  font-size: var(--t-2xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--n-700);
  white-space: nowrap;
  padding-top: var(--s-2);
  padding-bottom: var(--s-2);
  vertical-align: bottom;
}
tbody tr:last-child td, tbody tr:last-child th { border-bottom: 0; }
tbody tr { transition: background-color 100ms ease-out; }
tbody tr:hover { background: var(--surface-hover); }
tbody th[scope="row"] { font-weight: 600; }

/*
 * A link in a table cell keeps the accent colour but takes a lighter rule. On
 * the dashboard every customer name is a link, and twenty-one full-strength
 * underlines down one column is a texture rather than a set of affordances.
 * The rule darkens to full strength on hover, so the affordance is still there.
 */
td a, th a { text-decoration-color: var(--brand-300); }
td a:hover, th a:hover { text-decoration-color: currentColor; }

td.numeric, th.numeric { text-align: right; font-variant-numeric: tabular-nums; }
td.tick, th.tick { text-align: center; width: 2.75rem; }
/* A checkbox column is a target, so the whole cell is one. */
td.tick { padding-left: var(--s-2); padding-right: var(--s-2); }

tfoot td {
  font-weight: 700;
  background: var(--surface-sunken);
  border-top: 2px solid var(--edge-strong);
  border-bottom: none;
  font-variant-numeric: tabular-nums;
}

/*
 * Fixed column widths on the repeated tables. Each site group on the customer
 * page is its OWN table, and each reason on the held-loads queue is too, so
 * auto layout would size every one of them to its own content and the columns
 * would stagger down the page. Percentages keep every group's headings on the
 * same vertical lines, which is what makes a page of six groups scannable.
 */
/*
 * FINDING 60 (wave 3a live ingest, 28 August 2026). REAL CERTIFICATION CODES
 * ARE LONGER THAN ANY FIXTURE'S.
 *
 * table-layout: fixed sizes a cell from these percentages and nothing else, and
 * .badge is white-space: nowrap, so a badge wider than its cell does not shrink,
 * wrap or clip: it OVERFLOWS, and the next column is painted on top of it.
 * Measured on the live pending screen at 1440px, the certification badge
 * "FSC Controlled Wood INT-CW-002645" ran 16px past its cell on every row and
 * the ticket-photo badge sat over its last character. The real worst case is
 * "PEFC Controlled Sources INT-COC-PEFC-002645" at 43 characters, on 273 loads,
 * where the overlap buries the ticket-photo badge outright.
 *
 * Two changes, because either alone would leave the other half of the problem:
 *
 *   1. the widths are rebalanced towards the two columns that carry real vendor
 *      strings. Certification takes the largest share, and the ticket photo
 *      column grows because finding 59's third label, "Photo on file, not
 *      checked", is longer than either label it replaces;
 *   2. a badge inside this table may WRAP rather than overflow. That is the
 *      belt and braces: percentages tuned against today's longest string are a
 *      guess about tomorrow's, and a certification scheme with a longer name
 *      must degrade to two tidy lines inside its cell rather than to text
 *      printed over the next column. Short badges are unaffected, because they
 *      fit on one line and never wrap.
 */
table.loads { table-layout: fixed; min-width: 64rem; }
table.loads th:nth-child(1) { width: 3%; }
table.loads th:nth-child(2) { width: 9%; }
table.loads th:nth-child(3) { width: 12%; }
table.loads th:nth-child(4) { width: 8%; }
table.loads th:nth-child(5) { width: 8%; }
table.loads th:nth-child(6) { width: 11%; }
table.loads th:nth-child(7) { width: 23%; }
table.loads th:nth-child(8) { width: 18%; }
table.loads th:nth-child(9) { width: 8%; }
table.loads td .badge {
  max-width: 100%;
  white-space: normal;
  overflow-wrap: anywhere;
}
/*
 * The same guarantee for the cells that are plain text rather than a badge. A
 * three-ticket weighbridge value like 8705/8706/390747 is 135px of content in a
 * 110px cell, and 74 live loads carry one, so without this the column overflows
 * into certification exactly as the badge did.
 */
table.loads td { overflow-wrap: anywhere; }

table.queue { table-layout: fixed; min-width: 52rem; }
table.queue th:nth-child(1) { width: 30%; }
table.queue th:nth-child(2) { width: 34%; }
table.queue th:nth-child(3) { width: 36%; }

/* ========================================================================
   8. SITE GROUPS
   ======================================================================== */

details.site-group {
  border: 1px solid var(--edge);
  border-radius: var(--r-lg);
  background: var(--surface-raised);
  margin: 0 0 var(--s-4);
  box-shadow: var(--e-1);
  overflow: hidden;
}
details.site-group > summary {
  cursor: pointer;
  padding: var(--s-3) var(--s-4);
  font-family: var(--font-display);
  font-weight: 600;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: center;
  min-height: 44px;
  list-style-position: inside;
  background: var(--surface-sunken);
  border-bottom: 1px solid transparent;
  transition: background-color 120ms ease-out;
}
details.site-group[open] > summary { border-bottom-color: var(--edge); }
details.site-group > summary::marker { color: var(--ink-faint); }
details.site-group > summary:hover { background: var(--n-150); }
details.site-group > summary:focus-visible { outline-offset: -3px; }
details.site-group .group-body { padding: 0; }
details.site-group .group-actions {
  padding: var(--s-3) var(--s-4);
  border-top: 1px solid var(--edge);
  background: var(--surface-raised);
}
.summary-spacer { flex: 1 1 auto; }

/* ========================================================================
   9. FORMS
   ======================================================================== */

label {
  font-family: var(--font-display);
  font-weight: 600;
  display: inline-block;
  margin-bottom: var(--s-1);
  color: var(--ink);
}
label.plain { font-family: var(--font-ui); font-weight: 400; }

input[type="text"], input[type="email"], input[type="password"], input[type="date"],
input[type="number"], input[type="search"], select, textarea {
  font: inherit;
  font-family: var(--font-ui);
  padding: 0.5rem 0.625rem;
  min-height: 44px;
  border: 1px solid var(--edge-control);
  border-radius: var(--r-md);
  background: var(--surface-raised);
  color: var(--ink);
  max-width: 100%;
  transition: border-color 120ms ease-out, box-shadow 120ms ease-out;
}
input:hover:not(:disabled), select:hover:not(:disabled) { border-color: var(--n-600); }
/*
 * One ring, not two. The control gets the same green outline as everything else
 * but hugged in tighter, and the border darkens under it; a box-shadow glow on
 * top of the outline was two rings of different colours around one field.
 */
input:focus, select:focus, textarea:focus { border-color: var(--accent); }
input:focus-visible, select:focus-visible, textarea:focus-visible { outline-offset: 1px; }
/*
 * An input in a table cell fills its column. The size attributes on the
 * adjustment rows are content hints from the markup, and honouring them left a
 * 240px box floating in a 370px column with dead space beside it on every row.
 */
table input[type="text"], table input[type="date"], table select { width: 100%; }
input::placeholder { color: var(--ink-faint); }
input[type="checkbox"] {
  width: 1.125rem;
  height: 1.125rem;
  min-height: 0;
  accent-color: var(--accent);
  cursor: pointer;
}
/* An input inside a dense table row keeps the row from growing to 44px. */
table input[type="text"] { min-height: 38px; }

.field { margin: 0 0 var(--s-4); }
.field .hint {
  display: block;
  color: var(--ink-soft);
  font-size: var(--t-sm);
  font-family: var(--font-ui);
  font-weight: 400;
  max-width: 70ch;
  margin-bottom: var(--s-1);
  text-wrap: pretty;
}

.field-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: flex-end;
  margin: 0 0 var(--s-4);
}
/*
  A flex item's default min-width of auto is its CONTENT width, so a select
  sized by its longest option refuses to shrink and pushes past the card it
  sits in. Measured at 390px on the mapping form: the billing-rhythm select
  overhung the card edge by 9px. Setting min-width to 0 lets the item shrink,
  so the select's own max-width of 100% can do its job.
*/
.field-row > * { min-width: 0; }

/*
  WAVE 4. The contact-mapping form groups its three print flags in a fieldset,
  which is the correct element for a set of related checkboxes and the one a
  screen reader announces as a group. The browser default draws a sunken border
  and indents the legend, which puts the group's label at a different left edge
  from every other label on the card. Both are removed so the fieldset aligns
  with the fields above and below it and reads as one column.
*/
fieldset {
  border: 0;
  padding: 0;
  margin: 0 0 var(--s-4);
  min-inline-size: 0;
}
fieldset legend {
  padding: 0;
  font-family: var(--font-display);
  font-weight: 600;
  margin-bottom: var(--s-2);
}
fieldset label.plain {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 44px;
  margin-bottom: 0;
}

/* Long select options must not stretch a card past the viewport. */
select { max-width: min(100%, 46rem); }

/* ========================================================================
   10. BUTTONS
   ======================================================================== */

button, .button {
  font-family: var(--font-display);
  font-size: var(--t-base);
  font-weight: 600;
  letter-spacing: 0.01em;
  padding: 0.5rem 1rem;
  min-height: 44px;
  border-radius: var(--r-md);
  border: 1px solid var(--edge-control);
  background: var(--surface-raised);
  color: var(--ink);
  cursor: pointer;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  text-align: center;
  touch-action: manipulation;
  box-shadow: var(--e-1);
  transition: background-color 120ms ease-out, border-color 120ms ease-out,
              box-shadow 120ms ease-out, color 120ms ease-out;
}
button:hover, .button:hover {
  background: var(--surface-sunken);
  border-color: var(--n-600);
}
button:active, .button:active { box-shadow: none; }
button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  box-shadow: none;
}
button:disabled:hover { background: var(--surface-raised); border-color: var(--edge-control); }

button.primary {
  background: var(--accent);
  border-color: var(--brand-800);
  color: var(--ink-inverse);
}
button.primary:hover { background: var(--accent-hover); border-color: var(--brand-900); }
button.primary:disabled:hover { background: var(--accent); border-color: var(--brand-800); }

button.quiet, .button.quiet {
  background: transparent;
  border-color: transparent;
  box-shadow: none;
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}
button.quiet:hover, .button.quiet:hover {
  background: var(--surface-sunken);
  border-color: transparent;
}

button.danger {
  background: var(--alarm-50);
  border-color: var(--alarm-200);
  color: var(--alarm-800);
}
button.danger:hover { background: var(--alarm-100); border-color: var(--alarm-600); }

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: center;
  margin-top: var(--s-5);
}

/*
  The same row of actions inside a table cell: no top margin, and it wraps to a
  second line rather than widening the column past the viewport. The mapped
  customers table lost its buttons off the right edge before this existed.
*/
.actions-tight {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  align-items: center;
}
.actions-tight button, .actions-tight .button {
  min-height: 36px;
  padding: 0.25rem 0.625rem;
  font-size: var(--t-sm);
}
/* A row's own "Open" link is secondary to the customer name beside it. */
td .button, th .button { min-height: 36px; padding: 0.25rem 0.75rem; font-size: var(--t-sm); }

/* ========================================================================
   11. THE SIGNED-OUT PAGES
   ======================================================================== */

.login-page {
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s-8) var(--s-4);
  background: var(--surface-page);
}
.login-card {
  width: 100%;
  max-width: 27rem;
  padding: var(--s-8) var(--s-6) var(--s-6);
  border-radius: var(--r-lg);
  box-shadow: var(--e-2);
  position: relative;
  overflow: hidden;
}
/* The grain edge, second appearance: the top of the door into the service. */
.login-card::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 4px;
  background-image: var(--grain-h);
}
.login-card input { width: 100%; }
.login-card .actions button { width: 100%; }
.login-mark { display: block; margin: 0 0 var(--s-5); }
.login-mark img { display: block; height: 48px; width: auto; }
.login-card h1 { font-size: var(--t-xl); margin-bottom: var(--s-4); }
.login-card > .stack:first-child { margin-top: 0; }

/* ========================================================================
   12. DETAIL LISTS
   ======================================================================== */

dl.detail {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0;
  margin: 0;
}
dl.detail dt {
  color: var(--ink-soft);
  font-size: var(--t-base);
  padding: var(--s-2) var(--s-6) var(--s-2) 0;
  border-bottom: 1px solid var(--edge);
}
dl.detail dd {
  margin: 0;
  padding: var(--s-2) 0;
  border-bottom: 1px solid var(--edge);
  overflow-wrap: anywhere;
}
dl.detail dt:last-of-type, dl.detail dd:last-of-type { border-bottom: 0; }

/* ========================================================================
   13. THE RELEASE, WHICH IS THE ONE PAGE THAT SPENDS MONEY
   ======================================================================== */

/*
 * Everything in this block exists to make one screen feel different from the
 * eleven screens that only report. A release is the single irreversible act in
 * the portal, so it gets the largest type on the site, the only two-column
 * summary plate, and the signature grain edge at its widest.
 */

.card.plate {
  padding: 0;
  overflow: hidden;
}
.card.plate dl.detail { padding: var(--s-2) var(--s-5); }
.card.plate dl.detail dt { font-family: var(--font-display); }

/* The seal form. Sunken ground, so the page reads as: read, read, then ACT. */
.card.seal {
  position: relative;
  background: var(--surface-sunken);
  border-color: var(--edge-strong);
  padding-left: var(--s-6);
}
/* The grain edge, third and fourth appearances: the two surfaces that commit. */
.card.seal::before, .card.sealed::before {
  content: "";
  position: absolute;
  top: -1px; bottom: -1px; left: -1px;
  width: 6px;
  border-radius: var(--r-lg) 0 0 var(--r-lg);
  background-image: var(--grain-v);
}
.card.sealed { position: relative; padding-left: var(--s-6); }

.card.seal .notice { background: var(--surface-raised); }
.card.seal .table-scroll {
  border: 1px solid var(--edge);
  border-radius: var(--r-md);
  background: var(--surface-raised);
}
.card.seal input[type="text"] { background: var(--surface-raised); }

/*
 * The subtotal. On the preview this is the number the office is agreeing to, so
 * it is set at display size in tabular figures and given the only double rule
 * on the page. It used to be 15px bold in a footer row.
 */
.table-scroll tfoot td {
  padding-top: var(--s-3);
  padding-bottom: var(--s-3);
  font-family: var(--font-display);
  font-size: var(--t-md);
}
.table-scroll tfoot td.numeric {
  font-size: var(--t-lg);
  letter-spacing: -0.01em;
  color: var(--ink);
}

/* The seal button itself carries the weight of the act. */
.card.seal .actions { margin-top: var(--s-6); }
.card.seal .actions button.primary {
  min-height: 52px;
  padding-left: var(--s-6);
  padding-right: var(--s-6);
  font-size: var(--t-md);
  box-shadow: var(--e-2);
}

/* ========================================================================
   14. UTILITIES
   ======================================================================== */

.muted { color: var(--ink-soft); }
.small { font-size: var(--t-sm); }
.mono {
  font-family: var(--font-mono);
  font-size: 0.9em;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}
.nowrap { white-space: nowrap; }
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}

pre.counters {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--t-sm);
  line-height: 1.7;
  white-space: pre-wrap;
  word-break: break-word;
  background: var(--surface-sunken);
  border: 1px solid var(--edge);
  border-radius: var(--r-md);
  padding: var(--s-3) var(--s-4);
  color: var(--n-800);
}

/*
 * An empty table body. A single muted sentence in a cell reads as a broken row;
 * centring it and giving it air makes it read as an answer.
 */
tbody td[colspan] {
  padding: var(--s-10) var(--s-4);
  text-align: center;
  color: var(--ink-soft);
  text-wrap: pretty;
}
tbody td[colspan] a { color: var(--accent); }

/*
 * ONBOARDING (3 September 2026). THE ONE PROSE SCREEN.
 *
 * /how-it-works is the only page in the portal that is running text: paragraphs,
 * lists and captioned figures rather than a table of state. Everything it needs
 * already existed except lists, which no screen had ever used, and a browser's
 * default list is a 40px indent with no rhythm against a 4px spacing scale.
 *
 * Five rules and no more. They are scoped to "main" so a list that appears
 * inside a future notice or card is not silently restyled by a rule written for
 * a different page, and they take the same measure as a paragraph, because a
 * bulleted line running the full width of a 1240px shell is the same reading
 * problem as a sentence doing it.
 */
main ul, main ol {
  margin: 0 0 var(--s-3);
  padding-left: var(--s-6);
  max-width: var(--measure);
}
main li { margin-bottom: var(--s-2); text-wrap: pretty; }
main li:last-child { margin-bottom: 0; }
/* A sample invoice line sits between two paragraphs and needs the gap. */
main > pre.counters { margin-bottom: var(--s-5); max-width: var(--measure); }
/* A figure fills its card; inline layout would leave a descender gap under it. */
.card > img { display: block; margin-bottom: var(--s-3); }

/*
 * ONBOARDING: a TWO-figure strip.
 *
 * The dashboard's four figures fill the shell and read as one ruled band.
 * Getting started has two, and auto-fit stretched them to half the shell
 * each, putting six hundred pixels of nothing between "1 of 3" and the hairline
 * that separates it from "0 of 1". A strip has to look like a strip.
 */
.grid.pair { grid-template-columns: repeat(2, minmax(0, 15rem)); }

/*
 * ONBOARDING: the step names are headings in all but tag, so they get the same
 * balanced wrap the real headings get. Without it the first step broke as
 * "1. Match customers to Xero / contacts", and a one-word last line is the
 * thing the eye trips on hardest in a column of four.
 */
table.steps tbody th { text-wrap: balance; }

/*
 * ONBOARDING: the steps table on a phone.
 *
 * Four columns inside 340px gives two words to a line and pushes the button off
 * the right edge into a horizontal scroll, and this is the FIRST screen a new
 * person sees. Below 640px each row becomes a block and its cells stack in
 * reading order: the step, then its state, then why it matters, then the
 * button. The header row is hidden from sight and kept for a screen reader,
 * because "Step / Status / Why it matters" above a stacked card is noise.
 *
 * Scoped to table.steps alone. The nine-column loads table is NOT treated so:
 * it is a ledger to be compared down its columns and it is right for that one
 * to scroll.
 */
@media (max-width: 640px) {
  table.steps thead {
    position: absolute; width: 1px; height: 1px; margin: -1px;
    clip-path: inset(50%); overflow: hidden; white-space: nowrap;
  }
  table.steps tr { display: block; padding: var(--s-4) var(--s-4) var(--s-5); }
  table.steps tr + tr { border-top: 1px solid var(--edge); }
  table.steps th, table.steps td {
    display: block; border: 0; padding: 0 0 var(--s-2); max-width: none;
  }
  table.steps td:last-child { padding-bottom: 0; padding-top: var(--s-2); }
  table.steps td.nowrap { white-space: normal; }
}

/* ========================================================================
   15. RESPONSIVE
   ======================================================================== */

@media (max-width: 1080px) {
  .site-header { gap: var(--s-1) var(--s-4); }
  .site-header nav a { padding-left: var(--s-2); padding-right: var(--s-2); font-size: var(--t-base); }
}

@media (max-width: 860px) {
  .site-header { padding: 0 var(--s-4); }
  .site-header .brand img { height: 28px; }
  .site-header nav { order: 3; flex-basis: 100%; overflow-x: auto; flex-wrap: nowrap; }
  .site-header .menu { margin-left: auto; }
  .figure { padding-left: 0; border-left: 0; }
}

@media (max-width: 640px) {
  main { padding: var(--s-5) var(--s-4) var(--s-12); }
  footer.site-footer { padding: var(--s-5) var(--s-4) var(--s-8); }
  h1 { font-size: var(--t-xl); }
  h2 { font-size: var(--t-lg); margin-top: var(--s-8); }
  .card { padding: var(--s-4); border-radius: var(--r-md); }
  .card.seal { padding-left: var(--s-5); }
  dl.detail { grid-template-columns: 1fr; }
  dl.detail dt { padding-bottom: 0; border-bottom: 0; }
  dl.detail dd { padding-top: 0; }
  .figure .value { font-size: var(--t-2xl); }
  .actions button, .actions .button { flex: 1 1 auto; }
  .site-header .menu-panel { min-width: min(18rem, calc(100vw - var(--s-8))); }
}

/* ========================================================================
   16. MOTION
   ======================================================================== */

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

/* ========================================================================
   17. PRINT
   ======================================================================== */

/*
 * A release preview gets printed and put in a folder, so the printed sheet is a
 * designed artefact rather than a screenshot of a screen. The chrome goes, the
 * logo stays (it is what makes the sheet Flaherty's), every collapsed group is
 * forced open so nothing is silently missing from the paper, and no table row
 * is allowed to break across a page.
 */
@media print {
  :root { --surface-page: #ffffff; --surface-raised: #ffffff; --surface-sunken: #ffffff; }
  body { background: #ffffff; font-size: 10.5pt; }
  .site-header {
    position: static;
    box-shadow: none;
    border-bottom: 1.5pt solid #000000;
    padding: 0 0 6pt;
    margin-bottom: 12pt;
  }
  .site-header::after { display: none; }
  .site-header nav, .site-header .menu, footer.site-footer, .actions { display: none !important; }
  main { max-width: none; padding: 0; }
  .card, .table-scroll, details.site-group {
    box-shadow: none;
    border: 0.75pt solid #666666;
    break-inside: auto;
  }
  .card.seal::before, .card.sealed::before, .login-card::before { display: none; }
  .card.seal { background: #ffffff; }
  details { display: block; }
  details > summary { display: block; }
  details .group-body, details[open] .group-body { display: block !important; }
  thead { display: table-header-group; }
  /*
   * A4 is 186mm of usable width and the screen tables are laid out for 1024px
   * and up, so on paper the columns that carry nowrap content (the money, the
   * DDS reference, the ticket-photo badge) took their full width and squeezed
   * the description into a nine-line wrap on every row. On paper nothing can
   * scroll sideways, so every one of those has to give: the minimums go, the
   * fixed layout goes, and the badge is allowed to wrap.
   */
  .table-scroll { overflow: visible; }
  table, table.loads, table.queue { min-width: 0; table-layout: auto; }
  th, td { padding: 4pt 6pt; }
  .badge { white-space: normal; }
  td.nowrap, .nowrap { white-space: normal; }
  td.numeric, th.numeric { white-space: nowrap; }
  tr, img { break-inside: avoid; }
  h1, h2, h3 { break-after: avoid; }
  a { color: #000000; text-decoration: underline; }
  .notice { border: 0.75pt solid #666666; border-left-width: 3pt; }
  .badge { border: 0.5pt solid #666666; }
}

/* ========================================================================
   18. DARK THEME: THE SHAPE OF IT, NOT SHIPPED
   ======================================================================== */

/*
 * The portal is a daylight office tool and light is the only theme it needs
 * today. It is written so that adding dark is re-pointing the alias layer and
 * nothing else, because no rule above reads a ramp token directly.
 *
 * The block would be, verbatim, this - with the trigger widened to
 * :root[data-theme="dark"] once there is a control to set it:
 *
 *   @media (prefers-color-scheme: dark) {
 *     :root {
 *       color-scheme: dark;
 *       --surface-page: var(--n-950);
 *       --surface-raised: oklch(23% 0.008 142);
 *       --surface-sunken: oklch(27% 0.008 142);
 *       --surface-header: oklch(23% 0.008 142);
 *       --surface-hover: oklch(27% 0.008 142);
 *       --surface-thead: oklch(27% 0.008 142);
 *       --ink: var(--n-100);
 *       --ink-soft: var(--n-400);
 *       --ink-faint: var(--n-500);
 *       --ink-inverse: var(--n-950);
 *       --edge: oklch(32% 0.008 142);
 *       --edge-strong: oklch(40% 0.008 142);
 *       --edge-control: oklch(48% 0.008 142);
 *       --accent: var(--brand-300);
 *       --accent-hover: var(--brand-200);
 *       --accent-wash: oklch(30% 0.05 142);
 *       --focus: var(--timber-300);
 *     }
 *   }
 *
 * Two rules that are true of dark and are already respected above: elevation is
 * stepped with LIGHTNESS rather than with shadow, which is why the surface
 * aliases exist as four separate tokens rather than one plus box-shadows; and
 * the base surface is never #000, because pure black causes halation against
 * light text and kills the shadow model outright. The badge, notice and grain
 * tints would move to their -900/-200 pairs on the same ramps, which is why
 * every ramp above carries both ends.
 */
