/*
 * cupel.css — the frame, and the glass over it.
 *
 * THE ONE RULE THIS FILE EXISTS TO KEEP, and the one it got wrong twice.
 *
 * There is no opaque fill anywhere in this document: one photograph is fixed
 * behind the whole page and every band is a translucent pane over it. That
 * much a previous build already had. What it did NOT have — and what made it
 * read as a flat page anyway — was two things, both invisible in a stylesheet
 * review and both obvious the moment the painted pixels were measured:
 *
 *   1. `filter: blur(2.5px)` on the photograph itself. Applied to make body
 *      copy sit more comfortably on top, it destroys the frame's fine detail
 *      EVERYWHERE — including the four fifths of the page where no body copy
 *      sits. A blurred photograph behind glass is a wash, and a wash is paint.
 *      There is no blur on `.field` here, at all.
 *
 *   2. Bands at alpha 0.44-0.52. Every contrast check passed, because at that
 *      thickness there is a real surface to check ink against — which is
 *      precisely the problem. The reference runs its full-width sections at
 *      0.06 and 0.14, and the reason it can is structural, not chromatic:
 *      every paragraph on it sits on a 0.62-0.78 CARD. The band is glass, the
 *      card is the surface, and trying to get both jobs out of one layer is
 *      what drove the last solver's body ink to near-black.
 *
 * So: bands at 0.07 and 0.15 that nothing is set on directly, a 0.40 wash the
 * width of a heading where a section head needs one, and cards at 0.66 for
 * everything that carries running text. `tools/validate.mjs` refuses an alpha
 * of 1 anywhere, and `tools/audit-live.mjs` measures the opaque page height in
 * real Chrome and requires it to be zero.
 *
 * EVERY COLOUR BELOW IS SOLVED, NOT PICKED. `tools/palette.mjs` samples the
 * photograph, composites each surface over the darkest and lightest parts of
 * it IN GAMMA SPACE as a browser does, and solves the ink to clear 4.5:1
 * against the WORST end of that range rather than against an average that
 * exists nowhere on the page. The scrim came back at 0% — this frame's dark
 * end is luminance 0.034, not 0.003, so there is nothing to buy back and the
 * photograph is shown undimmed.
 */

/* ---- the solved palette ------------------------------------------- */
:root {
/* TOKENS:BEGIN */
  /* GENERATED by tools/build.mjs from data/palette.json — do not edit.
     Solved against the hero photograph itself: each surface composited over
     the darkest and lightest parts of the frame, ink cleared against the worse
     end. Scrim 0% — the solver searched
     upward from zero and zero was enough, so the photograph is undimmed. */
  --ink: #21190D;              /* worst 8.95:1 on a card, 4.71:1 on the head wash */
  --ink-2: #1E1912;
  --ink-deep: #EDE3D7;
  --ink-deep-2: #EAE3D9;

  --accent: #011F10;
  --ember: #311001;
  --accent-chip: #095333;   /* solved against the card only */
  --ember-chip: #793308;
  --accent-lg: #06452A;       /* large text only: AA is 3.0:1 there */
  --ember-lg: #652905;
  --accent-deep: #4BFCA8;
  --ember-deep: #FCD8C7;
  --ember-deep-lg: #FCA278;

  /* THE BANDS ARE GLASS: 0.07 and 0.15 of white over the photograph. Nothing
     is set on one directly — see css/cupel.css. */
  --pane-3: rgba(255, 255, 255, 0.07);
  --pane-2: rgba(255, 255, 255, 0.15);
  --pane-1: rgba(255, 255, 255, 0.4);
  --pane-deep: rgba(23, 17, 10, 0.72);
  --card: rgba(255, 255, 255, 0.66);
  --card-deep: rgba(51, 40, 26, 0.7);
  --glass: rgba(255, 255, 255, 0.46);
  --glass-thin: rgba(255, 255, 255, 0.3);
/* TOKENS:END */
  --line: rgba(45, 34, 16, 0.14);
  --line-2: rgba(45, 34, 16, 0.08);
  --line-deep: rgba(236, 220, 196, 0.20);

  /* BLUR THE SMALL THINGS HARD, THE BIG THINGS BARELY, THE GROUND NOT AT ALL.
     The reference's own numbers: 6px under a section, 11px under the alternate
     one, 18px under a card. The saturate() is not decoration either — a
     near-white pane over a photograph desaturates it, and pushing the chroma
     back up is what keeps a gold frame gold instead of beige. */
  /* NO BLUR ON A FULL-WIDTH BAND. The reference gets away with 6px and 11px
     because its frame is a macro shot that is already soft; this one is an
     aspen stand with fine trunks and leaf edges, and a scrolled-viewport
     capture at 9px showed the top third of the page as a gold smear. The
     saturate() stays and does the real work — a near-white pane over a
     photograph desaturates it, and pushing the chroma back up is what keeps a
     gold frame gold instead of beige. */
  --blur-band: saturate(140%);
  --blur-band-2: saturate(148%);
  --blur-card: saturate(154%) blur(18px);

  --shadow-sm: 0 1px 2px rgba(43, 30, 12, .05), 0 3px 14px rgba(43, 30, 12, .06);
  --shadow-md: 0 10px 34px rgba(43, 30, 12, .12);
  --shadow-lg: 0 26px 70px rgba(38, 26, 10, .20);

  --r: 22px;
  --r-sm: 14px;
  --r-lg: 32px;
  --maxw: 1160px;

  --display: "Newsreader", Georgia, "Times New Roman", serif;
  --sans: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
}

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

html { scroll-behavior: smooth; }
/* The nav is sticky, so an anchor that lands flush with the top lands UNDER
   it. */
:target, [id] { scroll-margin-top: 6rem; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.62;
  color: var(--ink);
  /* The fallback under the photograph, and the only flat colour in the file.
     It is visible for the instant before the image decodes and nowhere else,
     so it is the frame's own median rather than a neutral — a grey flash under
     a gold photograph is a visible seam on a slow connection. */
  background: #A9843C;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/**
 * The photograph, fixed behind everything, and SHARP.
 *
 * `position: fixed` on a child rather than `background-attachment: fixed` on
 * the body: iOS Safari does not honour the attachment property, and the
 * failure is not a fallback but a photograph that jumps a whole viewport on
 * every scroll frame.
 *
 * NO `filter` HERE. See the header of this file — a two-and-a-half pixel blur
 * on the ground is what made the last build flat after its alphas were already
 * correct. If a piece of text is uncomfortable over the frame, the answer is a
 * surface under that text, not a softer photograph.
 *
 * A fixed background shows THE SAME REGION behind every section, so the frame
 * has to hold texture across its whole area rather than in one good corner. An
 * aspen stand shot upward is chosen for exactly that: trunks, canopy and leaf
 * litter reach every edge, and there is no smooth sky to become a wash below
 * the fold.
 */
.field {
  position: fixed;
  inset: 0;
  z-index: -2;
  background: url("../img/hero.jpg") center 46% / cover no-repeat;
}

/**
 * NO SCRIM. `tools/palette.mjs` searched upward from zero for the smallest
 * wash under which every surface still carries its ink and found that zero is
 * enough: this frame's dark end is luminance 0.034, where a 0.66 card still
 * clears 8.9:1. A sibling set a scrim by eye "for legibility", veiled its
 * photograph, and moved the measured contrast by three hundredths of one per
 * cent. The rule is that a scrim is solved or it is absent.
 *
 * The pools below are colour, not light: two wide radial tints at 6-9% that
 * pull the corners slightly cool and slightly deep so the nav and the footer
 * have something to sit against. They are inside the audit's measurement,
 * which composites `.field` and `.field::after` together before grading.
 */
.field::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(58% 42% at 84% 2%, rgba(18, 52, 38, .09), rgba(18, 52, 38, 0) 62%),
    radial-gradient(60% 46% at 6% 100%, rgba(38, 22, 6, .09), rgba(38, 22, 6, 0) 62%);
}

/* ---- type ---------------------------------------------------------- */
h1, h2, h3 { font-family: var(--display); font-weight: 400; letter-spacing: -0.014em; margin: 0; }
h1 { font-size: clamp(3.5rem, 9vw, 6.8rem); line-height: 0.95; }
h2 { font-size: clamp(2.1rem, 4.5vw, 3.4rem); line-height: 1.06; }
h3 { font-size: 1.3rem; line-height: 1.28; }
/* The accent inside a heading, in italic — the reference's own move, and the
   one thing that stops a page of serif display type reading as a document.
   `--accent-lg` and not `--accent`: a heading here is 34px+, where WCAG's
   bound is 3.0:1, and solving a 50px word to the body-text target costs it
   most of its colour for a rule that does not apply at that size. */
h2 em, h1 em { font-style: italic; color: var(--accent-lg); }
.band-deep h2 em, .band-deep h1 em { color: var(--accent-deep); }
p { margin: 0 0 1.05em; }
a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 3px; }
strong { font-weight: 600; }
code, .mono { font-family: var(--mono); font-size: 0.88em; font-variant-ligatures: none; }

/* A PILL, NOT BARE TEXT. An eyebrow set directly on a band has to clear AA
   against the thinnest one, and no dark ink does that — black itself reaches
   2.6:1 over the dark end of this frame at alpha 0.07. Give it a surface of
   its own and the colour is solved against the card instead, where the gamut
   has room for an accent that is actually a colour. */
.eyebrow {
  display: inline-block;
  font: 500 0.72rem/1 var(--mono);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--ember-chip);
  background: rgba(255, 252, 246, .72);
  border: 1px solid rgba(255, 255, 255, .5);
  padding: .42rem .72rem .38rem;
  border-radius: 7px;
  margin: 0 0 1.1rem;
}
.band-deep .eyebrow { color: var(--ember-deep); background: rgba(46, 32, 14, .5); border-color: var(--line-deep); }
.lede { font-size: 1.18rem; color: var(--ink-2); max-width: 62ch; }

/* ---- layout -------------------------------------------------------- */
.wrap { width: min(var(--maxw), calc(100% - 3rem)); margin-inline: auto; }
.band { padding: clamp(4rem, 9vw, 7.5rem) 0; position: relative; }

/**
 * THE BANDS ARE GLASS. 0.07 and 0.15, which are the reference's own 0.06 and
 * 0.14 solved against this frame rather than copied off it. At this thickness
 * a band does not cover the photograph, it modulates it — which is the entire
 * visual difference between this page and the flat one it replaces, and it is
 * why no text is ever set directly on one.
 *
 * The light blur is the reference's too, and it is small on purpose: 5px and
 * 9px soften the frame just enough that a card edge reads, while leaving the
 * trunks and leaves legible as a photograph. At 14px across a full-width band
 * the alpha stops mattering and the page arrives flat no matter what it is.
 */
.band-1, .band-3 { background: var(--pane-3); backdrop-filter: var(--blur-band); -webkit-backdrop-filter: var(--blur-band); }
.band-2 { background: var(--pane-2); backdrop-filter: var(--blur-band-2); -webkit-backdrop-filter: var(--blur-band-2); }
.band-deep {
  background: var(--pane-deep); color: var(--ink-deep);
  backdrop-filter: var(--blur-band-2); -webkit-backdrop-filter: var(--blur-band-2);
}
/**
 * THE DARK MOMENT IS AN OBJECT, NOT A BAND — and this is the correction the
 * frame statistic forced.
 *
 * Two full-width `.band-deep` sections in a row measured 72% of the viewport
 * within a hair of its own median at scrollY 1284: a dark band at alpha 0.72
 * covering an 800px viewport IS a flat rectangle, whatever is behind it. The
 * reference has no dark full-height section at all — only a 353px footer —
 * and that is why its own figure never leaves 4-11%.
 *
 * So the darkness is kept and the full bleed is not: an inset slab with the
 * photograph running past it on all four sides. Same colour, same drama, and
 * the frame it sits in is still mostly photograph.
 */
.slab {
  border-radius: var(--r-lg);
  padding: clamp(1.9rem, 4vw, 2.8rem) clamp(1.4rem, 4vw, 3rem);
  border: 1px solid rgba(255, 255, 255, .10);
  box-shadow: var(--shadow-lg);
}
/* The slab's own heading is a size down. At the page's h2 scale this block
   ran to 780px and filled an 800px viewport on its own, which is the same
   failure as a full-bleed dark band wearing a border radius. */
.slab h2 { font-size: clamp(1.8rem, 3.2vw, 2.5rem); }
.slab .stat .n { font-size: clamp(2.2rem, 4.6vw, 3.1rem); }
/* The band that carries a slab does not also need a full section's padding
   above and below it — that is what pushed it into the middle of a frame with
   nothing else in view. */
.band.has-slab { padding-block: clamp(2.5rem, 5vw, 4rem); }
.band-deep h1, .band-deep h2, .band-deep h3 { color: var(--ink-deep); }
.band-deep .lede, .band-deep p { color: var(--ink-deep); }
.band-deep a { color: var(--accent-deep); }

/**
 * THE WASH UNDER A SECTION HEAD — the piece the last build did not have.
 *
 * A heading and its lead have to sit somewhere. On a 0.07 band they are on
 * bare photograph and no ink is readable; on a card they look like a card, and
 * a page whose every heading is boxed reads as a dashboard. So they get a
 * wash: 0.40 of white, elliptical, faded to nothing well inside its own box,
 * so it registers as light in the frame rather than as an object in the
 * layout. The ink is solved against it at its weakest useful point, which is
 * why `pane-1` is in the ink's `on` list in the solver.
 */
.band-head {
  position: relative;
  max-width: 64rem;
  margin-inline: auto;
  padding: clamp(1.5rem, 3.5vw, 2.4rem) clamp(1.5rem, 5vw, 3.2rem);
  border-radius: clamp(28px, 4vw, 48px);
  /* A FLAT rgba(), NOT a gradient and NOT a ::before.
     The live audit walks computed `backgroundColor` up the tree to find the
     surface a node is really on. A radial-gradient leaves backgroundColor
     transparent, so the walk falls through to the band and grades a heading
     that is comfortably readable at 1.71:1 — the same trap that once reported
     white button text on a gradient at 1:1. A pseudo-element wash is worse
     again: invisible to the walk AND painted behind the band's own fill.
     So the wash is a real background on a real element, and what keeps it
     from reading as a card is the geometry — very large radius, no border, no
     shadow, wider than the text it holds. */
  background: var(--pane-1);
}
/* A wash inside the slab is a box in a box. The slab IS the surface — the
   dark ink is solved against it at 6.5:1 — so the head only needs the wash
   where it is sitting on a bare band. */
.band-deep .band-head { background: rgba(20, 14, 6, .42); }
.slab .band-head { background: none; padding: 0; max-width: none; }
.band-head.center { text-align: center; }
.band-head.center .lede { margin-inline: auto; }
.band-head > :last-child { margin-bottom: 0; }

/**
 * BLUR THE SMALL THINGS. On a card or a pill a backdrop blur is doing the
 * opposite job to the one it does on a band: these are small, they sit ON the
 * photograph rather than replacing it, and frosting the few hundred pixels
 * behind them is what makes them read as glass rather than as paper.
 */
.nav-inner, .card, .hero-card, .chip, .btn-ghost, .strip { backdrop-filter: var(--blur-card); -webkit-backdrop-filter: var(--blur-card); }

.hair { height: 1px; background: var(--line); border: 0; margin: 0; }

/* ---- nav ----------------------------------------------------------- */
.nav { position: sticky; top: 0; z-index: 50; padding: 0.85rem 0; }
.nav-inner {
  display: flex; align-items: center; gap: 1.4rem;
  padding: 0.5rem 0.6rem 0.5rem 1.1rem;
  /* The CARD surface, not the thin glass. At 0.55 a heading scrolling under
     the pill reads straight through it and both are illegible; a nav is a
     small element, so it is held to the card bar rather than the band bar, and
     it is the one element on the page that has to stay readable over content
     it has never seen. */
  background: var(--card);
  border: 1px solid rgba(255, 255, 255, .55);
  border-radius: 999px;
  box-shadow: var(--shadow-sm);
  transition: box-shadow .25s ease;
}
.nav-inner:hover { box-shadow: var(--shadow-md); }
.brand { display: inline-flex; align-items: center; gap: .6rem; font-family: var(--display); font-size: 1.44rem; letter-spacing: .01em; color: var(--ink); text-decoration: none; }
.brand svg { display: block; }
.nav-links { display: flex; gap: 1.25rem; margin-left: .6rem; flex: 1; }
.nav-links a { color: var(--ink-2); text-decoration: none; font-size: .93rem; }
.nav-links a:hover, .nav-links a[aria-current] { color: var(--accent); text-decoration: underline; }
@media (max-width: 900px) { .nav-links { display: none; } }

.btn {
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .7rem 1.3rem; border-radius: 999px;
  font: 500 .95rem/1 var(--sans); text-decoration: none; cursor: pointer;
  border: 1px solid transparent; transition: transform .15s ease, box-shadow .15s ease, background .2s ease;
  white-space: nowrap;
}
.btn:active { transform: translateY(1px); }
/* A GRADIENT IS AN OPAQUE BACKGROUND AND `backgroundColor` CANNOT SEE IT.
   Every primary button here is a gradient over a transparent background-color,
   and an earlier contrast walk fell through to the pane behind it and reported
   near-white button text at 1:1 on a control that actually reads at 9:1. The
   audit composites the gradient's own stops now. */
.btn-primary { background: linear-gradient(#12482E, #0A3120); color: #F4FBF6; box-shadow: var(--shadow-sm); }
.btn-primary:hover { box-shadow: var(--shadow-md); transform: translateY(-1px); }
.btn-ghost { background: var(--glass); color: var(--ink); border-color: rgba(255, 255, 255, .62); }
.btn-ghost:hover { background: var(--card); }
.btn-lg { padding: .95rem 1.7rem; font-size: 1rem; }
.btn .arrow { display: inline-grid; place-items: center; width: 1.55rem; height: 1.55rem; margin: -.4rem -.55rem -.4rem .1rem; border-radius: 999px; background: rgba(255, 255, 255, .2); font-size: .8rem; }

/* The X link in the nav. A square target at the size Apple and WCAG both ask
   for, with the glyph itself smaller inside it — an icon sized to its artwork
   is a tap target that misses on a phone. */
.icon-link {
  display: inline-grid; place-items: center;
  width: 40px; height: 40px; border-radius: 999px;
  color: var(--ink); text-decoration: none;
  background: var(--glass-thin); border: 1px solid rgba(255, 255, 255, .45);
  transition: background .15s ease;
}
.icon-link:hover { background: var(--glass); }
.icon-link svg { display: block; }

/* ---- hero ---------------------------------------------------------- */
.hero { padding: clamp(2rem, 6vw, 4rem) 0 clamp(2.5rem, 6vw, 5rem); }
.hero-card {
  max-width: 54rem; margin-inline: auto;
  background: var(--glass);
  border: 1px solid rgba(255, 255, 255, .5);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
  padding: clamp(2.4rem, 5vw, 3.6rem) clamp(1.5rem, 5vw, 3.4rem) clamp(2rem, 4vw, 3rem);
  text-align: center;
}
.hero-card h1 { margin: .55rem 0 1.15rem; }
.hero-card .lede { margin-inline: auto; text-align: center; font-size: clamp(1.02rem, 2vw, 1.18rem); }
.hero-actions { display: flex; gap: .8rem; justify-content: center; flex-wrap: wrap; margin: 1.9rem 0 1.4rem; }
.chips { display: flex; gap: .55rem; justify-content: center; flex-wrap: wrap; }
.chip {
  display: inline-flex; align-items: center; gap: .45rem;
  padding: .42rem .9rem; border-radius: 999px;
  /* THE CARD ALPHA BY DEFAULT, the thin one only where something is already
     underneath. A chip is small and often lands directly on a 0.07 band; at
     0.30 that composites to 4.08:1 over the dark end of the frame, which is a
     fail by four hundredths. Inside the hero card or a card it is sitting on
     0.55-0.66 already, and 0.30 on top of that is the right amount of glass. */
  background: var(--card); border: 1px solid rgba(255, 255, 255, .5);
  font-size: .84rem; color: var(--ink);
}
.hero-card .chip, .card .chip, .slab .chip { background: var(--glass-thin); }
.chip .mono { color: var(--ink-2); }
/* A LINK ON ITS OWN IS A CONTROL, so it gets a control's surface. Two of them
   were the last contrast failures on the index: a caption and a "read more"
   sitting on bare glass at 1.72:1. */
a.chip { text-decoration: none; color: var(--ink); }
a.chip:hover { background: var(--card); }
.caption {
  display: inline-block;
  background: var(--card);
  backdrop-filter: var(--blur-card); -webkit-backdrop-filter: var(--blur-card);
  border-radius: var(--r-sm);
  padding: .5rem .8rem;
}
.chip svg { color: var(--accent-chip); }

/* The stat strip directly under the hero — the reference's `.strip`, and the
   one full-width element allowed to be thicker than a band, because it is
   66px tall rather than 900 and it carries small text. */
.strip { background: var(--pane-1); border-block: 1px solid rgba(255, 255, 255, .28); }
.strip-inner { display: flex; align-items: center; justify-content: center; gap: 1.8rem; flex-wrap: wrap; padding: 1.15rem 1.5rem; font-size: .92rem; color: var(--ink-2); }
.strip-inner b { color: var(--ink); font-weight: 600; }
.strip-dot { width: 4px; height: 4px; border-radius: 50%; background: var(--accent-chip); opacity: .55; }

/* ---- cards --------------------------------------------------------- */
.grid { display: grid; gap: 1.1rem; }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(275px, 1fr)); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); }
.card {
  background: var(--card);
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  padding: 1.6rem 1.6rem 1.35rem;
  box-shadow: var(--shadow-sm);
  transition: transform .2s ease, box-shadow .2s ease;
}
.card h3 { margin-bottom: .55rem; }
.card p:last-child { margin-bottom: 0; }
.card-lift:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); }
.card-icon {
  width: 38px; height: 38px; border-radius: 11px; display: grid; place-items: center;
  background: rgba(238, 248, 240, .82); color: var(--accent-chip); margin-bottom: 1rem;
}
.band-deep .card { background: var(--card-deep); border-color: var(--line-deep); color: var(--ink-deep); }
.band-deep .card-icon { background: rgba(120, 220, 168, .16); color: var(--accent-deep); }

/* ---- numbers ------------------------------------------------------- */
/* A STAT IS A SURFACE TOO. Three figures and their captions set straight on a
   0.07 band measured 1.1:1 on the number and 1.72:1 on the caption. Inside the
   dark slab they already have one — the slab — so the fill is dropped there
   rather than doubled. */
.stat {
  text-align: center;
  background: var(--card);
  backdrop-filter: var(--blur-card); -webkit-backdrop-filter: var(--blur-card);
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  padding: 1.5rem 1.1rem 1.35rem;
}
.slab .stat, .band-deep .stat, .card .stat { background: none; border: 0; backdrop-filter: none; -webkit-backdrop-filter: none; padding: 0; }
.stat .n { font-family: var(--display); font-size: clamp(2.6rem, 6vw, 4rem); line-height: 1; display: block; }
/* THE LARGE-TEXT ACCENT, and it is not a shortcut. A stat number renders at
   40px+, where WCAG's bound is 3.0:1, not 4.5:1 — solving a 42px figure to the
   body-text target is a rule applied where it does not apply, and it costs the
   page most of its colour. The small captions next to them stay at 4.5. */
.stat .n.warm { color: var(--ember-lg); }
.stat .n.cool { color: var(--accent-lg); }
.band-deep .stat .n.warm { color: var(--ember-deep-lg); }
.band-deep .stat .n.cool { color: var(--accent-deep); }
.stat .c { font-size: .88rem; color: var(--ink-2); display: block; margin-top: .6rem; }
.band-deep .stat .c { color: var(--ink-deep-2); }

table { width: 100%; border-collapse: collapse; font-size: .92rem; }
th, td { text-align: left; padding: .62rem .7rem; border-bottom: 1px solid var(--line-2); }
th { font: 500 .74rem/1.3 var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--ink-2); }
td.num, th.num { text-align: right; font-family: var(--mono); font-size: .86rem; }
.scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }
/* A TABLE IS A DOCUMENT, so it gets a sheet for the same reason the article
   pages do: 43 rows of mono type on a 0.07 band audited at 2.2:1. */
.band > .wrap > .scroll-x, .band > .wrap > .band-head + .scroll-x {
  background: var(--card);
  backdrop-filter: var(--blur-card); -webkit-backdrop-filter: var(--blur-card);
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  padding: .4rem 1.1rem;
}

.tag { display: inline-block; padding: .16rem .5rem; border-radius: 6px; font: 500 .74rem/1.5 var(--mono); }
/* A TINT THAT DARKENS ITS OWN GROUND COSTS THE TEXT ON IT. These were tinted
   with the accent colour at 12% — which drops the local background below the
   surface the accent was solved against, and "ok" landed at 3.97:1. A
   near-white wash raises it instead, so the chip is lighter than the card and
   the ink keeps every point of the ratio it was solved for. */
.tag-bad { background: rgba(255, 250, 243, .66); color: var(--ember-chip); }
.tag-good { background: rgba(243, 251, 245, .66); color: var(--accent-chip); }
.tag-mute { background: rgba(251, 250, 247, .6); color: var(--ink-2); }
.band-deep .tag-bad { background: rgba(120, 51, 8, .32); color: var(--ember-deep); }
.band-deep .tag-good { background: rgba(9, 83, 51, .38); color: var(--accent-deep); }
.band-deep .tag-mute { background: rgba(226, 220, 206, .16); color: var(--ink-deep-2); }

/* ---- prose --------------------------------------------------------- */
/**
 * A LONG-FORM PAGE IS A SHEET, NOT TEXT ON A PHOTOGRAPH.
 *
 * This is the rule the live audit forced, and it is the same rule as the one
 * about section heads. The article pages set an entire h1-plus-2000-words
 * directly on a 0.07 band; over the dark end of the frame that measured
 * 1.71:1, and five hundred and twenty-five nodes said so at once. There is no
 * ink that fixes it — black itself only reaches 2.6:1 there.
 *
 * The alternatives were a thicker band (paint, which is the whole failure
 * being corrected) or a 50% scrim over the photograph (measured: that is what
 * bare text at 0.07 would need, and it veils the frame everywhere to buy
 * legibility in one place). A sheet costs neither: the band stays glass, the
 * photograph runs past the sheet on both sides, and the ink is solved against
 * the surface it is actually on.
 */
.band > .wrap.prose {
  background: var(--card);
  backdrop-filter: var(--blur-card); -webkit-backdrop-filter: var(--blur-card);
  border: 1px solid var(--line-2);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-md);
  padding: clamp(1.8rem, 4vw, 3.2rem) clamp(1.4rem, 4vw, 3.4rem);
  width: min(74ch, calc(100% - 3rem));
}
.band-deep > .wrap.prose { background: var(--card-deep); border-color: var(--line-deep); }
.prose { max-width: 68ch; }
.prose h2 { margin: 2.6rem 0 1rem; }
.prose h3 { margin: 2rem 0 .6rem; }
.prose ul { padding-left: 1.15rem; }
.prose li { margin-bottom: .5rem; }
pre {
  background: rgba(30, 22, 10, .90); color: #EFE6D8; padding: 1.1rem 1.2rem;
  border-radius: var(--r-sm); overflow-x: auto; font-family: var(--mono); font-size: .82rem; line-height: 1.6;
}
.band-deep pre { background: rgba(16, 11, 4, .84); }

/* ---- footer -------------------------------------------------------- */
.foot { background: var(--pane-deep); color: var(--ink-deep); padding: 3.2rem 0 2.4rem; backdrop-filter: var(--blur-band-2); -webkit-backdrop-filter: var(--blur-band-2); }
.foot a { color: var(--accent-deep); }
.foot-grid { display: grid; gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
.foot h4 { font: 500 .74rem/1 var(--mono); letter-spacing: .12em; text-transform: uppercase; color: var(--ink-deep-2); margin: 0 0 .9rem; }
.foot ul { list-style: none; margin: 0; padding: 0; }
.foot li { margin-bottom: .45rem; font-size: .92rem; }
/* `.small` was defined next to the footer and given the footer's LIGHT ink,
   then used on light panes all over the app — pale grey on pale glass, 1.1:1.
   The default is the light-surface ink; the deep surfaces override it, which
   is the direction that cannot go wrong by being reused somewhere new. */
.small { font-size: .84rem; color: var(--ink-2); }
.foot .small, .band-deep .small { color: var(--ink-deep-2); }

/* ---- app ----------------------------------------------------------- */
.field-row { display: flex; gap: .6rem; align-items: stretch; }
input[type="text"], input[type="number"] {
  flex: 1; min-width: 0; padding: .72rem .9rem; font: 400 1rem var(--mono);
  color: var(--ink); background: rgba(255, 255, 255, .8);
  border: 1px solid var(--line); border-radius: var(--r-sm);
}
input:focus-visible, .btn:focus-visible, a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.kv { display: flex; justify-content: space-between; gap: 1rem; padding: .5rem 0; border-bottom: 1px solid var(--line-2); }
.kv:last-child { border-bottom: 0; }
.kv .k { color: var(--ink-2); font-size: .92rem; }
.kv .v { font-family: var(--mono); font-size: .9rem; text-align: right; }
/* A 42-character address in a mono face is wider than a 390px phone, and a
   flex row will not break it — so the row pushed the document sideways by
   73px and only the narrow-width audit noticed. Wrap the value, and stack the
   pair once the two halves stop fitting beside each other. */
.kv .v { overflow-wrap: anywhere; min-width: 0; }
@media (max-width: 620px) {
  .kv { flex-direction: column; align-items: flex-start; gap: .15rem; }
  .kv .v { text-align: left; }
}

/* ---- reveal -------------------------------------------------------- */
/**
 * SCOPED TO `.js`, WHICH AN INLINE SCRIPT IN THE HEAD SETS.
 *
 * Written unscoped, `.rise { opacity: 0 }` hides thirteen elements from every
 * reader without JavaScript — permanently, with no fallback and no error. The
 * live audit caught it as "13 elements at opacity 0" and the first reading of
 * that was that the observer had not fired. It had. The rule was simply
 * unconditional.
 */
.js .rise { opacity: 0; transform: translateY(14px); transition: opacity .6s ease, transform .6s ease; }
.js .rise.in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .js .rise { opacity: 1; transform: none; transition: none; } }
