/* ═══════════════════════════════════════════════════════════════════════════
   theward.online — everything that is not a token.
   Sections are a flat numbered sequence; renumber rather than appending a
   letter. No literal colours, sizes, families or durations: see tokens.css.
   ═══════════════════════════════════════════════════════════════════════════ */

/* 1 ── ground ───────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--ground);
  color: var(--text);
  font-family: var(--f-txt);
  font-size: 17px;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  transition: background var(--t-slow) ease, color var(--t-slow) ease;
}

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

a { color: inherit; }
:focus-visible { outline: 2px solid var(--live); outline-offset: 3px; }

.skip {
  position: absolute; left: var(--s2); top: calc(var(--s2) * -8);
  background: var(--band-bg); color: var(--band-text);
  font-family: var(--f-sys); font-size: 0.8rem; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase;
  padding: var(--s2) var(--s3); z-index: 10;
  transition: top var(--t-fast);
}
.skip:focus { top: var(--s2); }

.shell { max-width: var(--page); margin: 0 auto; padding: 0 var(--s4); }

/* 2 ── the band ─────────────────────────────────────────────────────────── */
.band {
  background: var(--band-bg);
  color: var(--band-text);
  padding: var(--s3) var(--s4);
  display: flex; align-items: center; gap: var(--s3);
}
.band-mark { width: 46px; height: 46px; flex: none; }
.band-text { display: grid; gap: 3px; min-width: 0; }
.band-name {
  font-family: var(--f-sys); font-weight: 700;
  font-size: clamp(1.15rem, 3.2vw, 1.8rem);
  letter-spacing: 0.2em; text-transform: uppercase; line-height: 1;
}
.band-meta {
  font-family: var(--f-sys); font-size: 0.68rem;
  letter-spacing: 0.14em; text-transform: uppercase; opacity: 0.7;
}

/* 3 ── the view ─────────────────────────────────────────────────────────── */
/* ⚠ A live canvas must keep costing what it costs. Never hide the frame with
   display:none / visibility / opacity:0 / an opaque cover — Chrome drops the
   WebGL surface and restoring it lands as one blocked frame of many seconds. */
.view {
  aspect-ratio: 16 / 10;
  background: var(--ground-2);
  border: 1px solid var(--rule);
  /* ⚠ THE JOINT. The band is near-black and so is a night sky, so the seam
     between the printed page and the live view vanishes for half of every
     day. This rule is the boundary, in the one reserved colour: below it,
     everything is live. It reads against noon and midnight equally. */
  border-top: 3px solid var(--live);
  position: relative; overflow: hidden;
}
@media (max-width: 620px) { .view { aspect-ratio: 4 / 3; } }
.view iframe { width: 100%; height: 100%; display: block; border: 0; }

.layers {
  display: flex; gap: var(--s0); flex-wrap: wrap;
  border: 1px solid var(--rule); border-top: none;
  padding: var(--s1); background: var(--ground-2);
}
.layer {
  font-family: var(--f-sys); font-size: 0.68rem; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase;
  padding: var(--s1) var(--s2); background: transparent;
  color: var(--text-soft); border: 1px solid transparent; cursor: pointer;
  transition: color var(--t-fast), background var(--t-fast);
}
.layer:hover { color: var(--text); }
.layer[aria-pressed="true"] { color: var(--band-text); background: var(--band-bg); }

/* 4 ── sections ─────────────────────────────────────────────────────────── */
.sec { padding: var(--s6) 0; border-top: 1px solid var(--rule); }
.sec:first-of-type { border-top: none; }

.copy { max-width: var(--measure); }
.copy h2 {
  font-family: var(--f-sys); font-weight: 600;
  font-size: clamp(1.4rem, 3.4vw, 2rem);
  letter-spacing: -0.012em; line-height: 1.15;
  margin: 0 0 var(--s3); text-wrap: balance;
}
.copy h3 {
  font-family: var(--f-sys); font-weight: 600; font-size: 1.12rem;
  letter-spacing: -0.005em; line-height: 1.25; margin: var(--s4) 0 var(--s1);
}
.copy p { margin: 0 0 var(--s3); }
.copy p:last-child { margin-bottom: 0; }
.copy strong { font-weight: 600; }
.lede { font-size: 1.14rem; line-height: 1.52; }

/* ⭐ .coda — the sentence a section rests on. Set off with a rule and air
   rather than a box: the ladder above is already five bordered rows, and a
   sixth panel would read as another rung instead of the conclusion. Italic +
   the body face (not the UI face) keeps it a spoken aside, not a label. */
.coda {
  font-family: var(--f-txt); font-style: italic;
  font-size: 1.06rem; line-height: 1.5;
  margin: var(--s4) 0 0; padding: var(--s3) 0 0;
  border-top: 1px solid var(--rule-soft);
  max-width: var(--measure);
}

.eyebrow {
  font-family: var(--f-sys); font-size: 0.66rem; font-weight: 700;
  letter-spacing: 0.2em; text-transform: uppercase; color: var(--live);
  display: block; margin-bottom: var(--s2);
}


/* 5 ── wayside ──────────────────────────────────────────────────────────── */
/* ⭐ FIGURE, THEN CAPTION — not figure BESIDE column. This page is imitating a
   wayside panel at an overlook, and on a real one the picture is large and the
   text is a caption near it. The two-column form was built for 4–6 line
   paragraphs; when the copy became 2–3 lines (2026-08-23) each block turned
   into a short caption floating in a tall empty column, which reads as missing
   content rather than as air.
   ⭐ Stacking means short copy is a FEATURE rather than a shortfall: nobody has
   to write to fill a column again, and the figures can get BIGGER, not smaller.
   ⛔ It also gives the place-card tablet somewhere to be labelled, which the
   side-by-side form never had. */
.wayside {
  border: 1px solid var(--rule); background: var(--ground-2);
  display: grid; grid-template-columns: minmax(0, 1fr);
}
.wayside + .wayside { border-top: none; }
.wayside-fig {
  background: var(--ground-3);
  border-bottom: 1px solid var(--rule-soft);
  min-height: 210px;
}
@media (max-width: 780px) {
}
.wayside-fig svg { width: 100%; height: 100%; display: block; }
/* ⚠ `.wayside-fig svg { display: block }` BEATS the UA's `[hidden]{display:none}`,
   so a hidden figure kept rendering on top of the section below it. State it
   explicitly. Check computed display, never the attribute. */
/* ...and for EVERY kind of figure, not just the svgs — the next one in here
   will not be an svg either. */
.wayside-fig > [hidden] { display: none; }

/* ⛔ CAPPED AT THE MEASURE. Stacked, the caption inherits the full page width —
   about 130 characters, twice what this site sets as readable. The two-column
   form was capping it by accident; now it has to be said. */
.wayside-txt {
  padding: var(--s4); display: grid; gap: var(--s2); align-content: start;
  max-width: var(--measure);
}
/* ⛔ THE PANEL BOX MUST NOT RESIZE WHEN THE PILL CHANGES. Measured, the three
   are 73px (Almanac), 98px (Sky) and 123px (Trees) — a 50px jump, so the whole
   page below shifts under the reader's cursor every time they switch.
   ⭐ ALL THREE OCCUPY THE SAME GRID CELL and the inactive ones are hidden with
   `visibility`, not `display`. So they still take part in layout and the box is
   always as tall as the tallest — automatically, with no magic number to go
   stale the next time the copy is rewritten. A hardcoded min-height would have
   been wrong within a day; this copy has changed twice today alone. */
.inst-panels { display: grid; }
.inst-panels > [data-inst-panel] { grid-area: 1 / 1; }
.inst-panels > [data-inst-panel][hidden] {
  display: block;          /* override the UA rule for [hidden] */
  visibility: hidden;
}

.wayside-txt h3 {
  font-family: var(--f-sys); font-weight: 600; font-size: 1.18rem;
  letter-spacing: -0.005em; line-height: 1.2; margin: 0; text-wrap: balance;
}
.wayside-txt p { margin: 0; font-size: 0.99rem; line-height: 1.58; }

/* A capture from the product, not an illustration of it. Cover, because the
   figure's height is set by the text beside it and the frame should crop
   rather than letterbox. */
/* ⛔ AN ASPECT, NOW THAT IT IS STACKED. In the two-column form the grid row
   capped this figure's height for free; full-width, `height: 100%` of an
   auto-height parent resolves to the image's NATURAL height, so a 578KB
   overhead ran to roughly 850px and swallowed the screen. */
.wayside-fig--shot { padding: 0; overflow: hidden; aspect-ratio: 16 / 9; }
.wayside-fig--shot img {
  width: 100%; height: 100%; display: block;
  object-fit: cover; object-position: center;
}

/* The diorama: one scene, three things to say about it. No padding, because
   the sky reaches the edges of its frame. */
.wayside-fig--diorama {
  padding: 0;
  /* Fixed, and the tallest of the three figures: the sun's arc needs the room
     and the copy must never be what decides it. */
  height: 420px;
}
@media (max-width: 780px) { .wayside-fig--diorama { height: 300px; } }

/* A frame instead of a drawing: the product's own panel, mounted alone.
   `relative` so the scroll guard has something to pin to. */
.wayside-fig--frame { padding: 0; background: var(--ground-2); position: relative; }
.wayside-fig--frame iframe {
  width: 100%; height: 100%; min-height: 420px; display: block; border: 0;
}
/* The live panels keep their generous height — they are things you scroll,
   not pictures you glance at. */
.wayside--directory .wayside-fig--frame { aspect-ratio: 21 / 9; }
@media (max-width: 780px) { .wayside-fig--frame iframe { min-height: 340px; } }
.wayside--directory .wayside-fig { min-height: 420px; }

/* ── The card, in a tablet ─────────────────────────────────────────────────
   Flat, drawn in the page's own ink, correct proportion (a portrait 11-inch is
   about 1 : 1.43). It exists to give the card the size it was designed for and
   to stop a full-width card dominating a section that is not about it. */
.cardwin {
  border: 1px solid var(--rule); border-top: none; background: var(--ground-2);
  display: grid; gap: var(--s3); padding: var(--s5) var(--s4) var(--s4);
}

/* Landscape, and the full width of the section — the card was designed for a
   wide frame (it ships at left-3/right-3 across the app), so a portrait tablet
   squeezed the very thing it was meant to show. */
.tablet {
  position: relative;
  width: 100%;
  aspect-ratio: 1.5 / 1;
  border: 1px solid var(--sign-soft);
  border-radius: 18px;
  /* ⭐ THE BEZEL IS A GRAB SURFACE, not just a drawing. An iframe swallows
     wheel and drag, so a card sitting flush to the page leaves a visitor
     scrolling the card when they meant to scroll the page. The bezel is the
     margin where the page is still the page — generous on purpose. */
  padding: 16px;
  background: var(--ground-3);
}
.tablet-screen {
  width: 100%; height: 100%;
  border-radius: 7px; overflow: hidden;
  background: var(--ground-2);
  border: 1px solid var(--rule-soft);
  position: relative;   /* pins the scroll guard */
}
.tablet-screen iframe { width: 100%; height: 100%; display: block; border: 0; }
/* Transparent, and deliberately not marked: an affordance here would be an
   apology for a problem the reader should never notice. A third each side
   leaves a generous middle for the card itself. */

/* the lens: one dot, because a device drawn without one reads as a picture
   frame. Landscape, so it sits on the left edge where a tablet's camera is. */
.tablet-lens {
  position: absolute; left: 5px; top: 50%; transform: translateY(-50%);
  z-index: 3;
  width: 4px; height: 4px; border-radius: 50%;
  background: var(--sign-soft); opacity: 0.7;
}

/* 7 ── the ladder ───────────────────────────────────────────────────────── */

/* The five participant types, as merit badges.
   ⭐ ONE MASTER FIELD, and that is the design — see --vig-field in tokens.css
   for why a per-emoji ground was the wrong shape. Everything here is the same
   for every badge except the glyph, its SIZE and its CENTRING, and those two
   are generated from a measurement rather than eyeballed.
   ⛔ EVERY COLOUR ARRIVES AS A TOKEN. Rule 1 forbids a literal in this file, so
   a badge whose tokens went stale shows as a FLAT, EMPTY disc rather than a
   wrong one. If that is what you see, run `node tools/build-vignettes.mjs`. */
.role-vig {
  width: 62px; height: 62px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 31px; line-height: 1;
  background: var(--vig-field);
  box-shadow: var(--vig-rim);
  flex-shrink: 0;
}
/* ⛔ NO OUTLINE ON THE GLYPH. One was built — four hairline offsets giving a
   guaranteed 16.5:1 edge — and cut on sight: it read as a sticker. What is left
   is a whisper of drop-shadow for relief, not for legibility; the cream field
   does the reading. See tokens.css for what that costs on a pale glyph.
   The emoji is content, not markup — one source, and it cannot drift. */
.role-vig::before {
  content: var(--vig-emoji, "");
  display: block;
  filter: drop-shadow(0 1px 1px var(--vig-drop));
  transform: scale(var(--vig-scale, 1)) translateY(var(--vig-nudge, 0em));
}

/* ⭐ CENTRED ON THE GRAPHIC, NOT ON THE CHARACTER. `align-items: center` centres
   the LINE BOX — the font's metrics — which has almost nothing to do with where
   the picture sits inside it. Apple Color Emoji reports ascent 1.0 / descent
   0.315, so its content box is 1.315em inside a 1em line box and the baseline
   lands at 0.84em. The nudges are measured in THAT frame and land each graphic's
   ink in the middle of the disc.
   ⛔ An earlier version measured from a canvas 'middle' anchor and applied the
   result here — a different reference — and every badge sat ~0.1em too low.
   ⛔ Horizontal is not corrected: measured, every glyph's inked width is
   0.90–1.00em, so they are already centred side to side.
   Only the glyph differs. */
.role-vig--townie    { --vig-emoji: var(--vig-townie-emoji);    --vig-scale: var(--vig-townie-scale);    --vig-nudge: var(--vig-townie-nudge); }
.role-vig--resident  { --vig-emoji: var(--vig-resident-emoji);  --vig-scale: var(--vig-resident-scale);  --vig-nudge: var(--vig-resident-nudge); }
.role-vig--guardian  { --vig-emoji: var(--vig-guardian-emoji);  --vig-scale: var(--vig-guardian-scale);  --vig-nudge: var(--vig-guardian-nudge); }
.role-vig--keyholder { --vig-emoji: var(--vig-keyholder-emoji); --vig-scale: var(--vig-keyholder-scale); --vig-nudge: var(--vig-keyholder-nudge); }
/* ⭐ The one override, and it earns it: the courier is the deliberate exception,
   and this is the Cary room's verdigris, so the badge points at the section that
   explains it. Field, halo and geometry are untouched. */
.role-vig--courier   { --vig-emoji: var(--vig-courier-emoji);   --vig-scale: var(--vig-courier-scale);   --vig-nudge: var(--vig-courier-nudge); box-shadow: var(--vig-rim-accent); }

.steps { display: grid; margin: var(--s4) 0 0; border-top: 1px solid var(--rule-soft); }
.step {
  display: grid; grid-template-columns: 62px minmax(0, 1fr);
  gap: var(--s3); padding: var(--s3) 0;
  border-bottom: 1px solid var(--rule-soft); align-items: start;
}
.step p { margin: 0; font-size: 1rem; line-height: 1.55; }
.step-t {
  font-family: var(--f-sys); font-weight: 600; font-size: 0.94rem;
  display: block; margin-bottom: 2px;
}


/* 8 ── calls to action ──────────────────────────────────────────────────── */
.cta {
  display: inline-flex; align-items: center; gap: var(--s2);
  background: var(--band-bg); color: var(--band-text);
  font-family: var(--f-sys); font-weight: 700; font-size: 0.82rem;
  letter-spacing: 0.16em; text-transform: uppercase;
  padding: var(--s2) var(--s4); border: none; cursor: pointer;
  margin-top: var(--s4); text-decoration: none;
  transition: opacity var(--t-fast);
}
.cta:hover { opacity: 0.86; }
.cta::after { content: "→"; letter-spacing: 0; }

.terms { display: grid; gap: var(--s2); margin-top: var(--s4); max-width: var(--measure); }
.term { display: grid; grid-template-columns: 132px minmax(0, 1fr); gap: var(--s3); align-items: baseline; }
@media (max-width: 700px) { .term { grid-template-columns: minmax(0, 1fr); gap: 0; } }
.term-k {
  font-family: var(--f-sys); font-size: 0.7rem; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-soft);
}
.term-v { font-size: 0.98rem; line-height: 1.5; }

/* 8 ── the ask, and it is the page's last word ─────────────────────────── */
/* Set apart because it is the one thing the page wants a reader to DO. A rule
   above it and its own ground: quiet, but unmistakably a different kind of
   block from the argument that precedes it. */
.sec--ask {
  border-top: 3px solid var(--sign);
  background: var(--ground-2);
  padding: var(--s5) var(--s4);
  margin: var(--s5) 0 0;
}

/* 7b ── the scroll guard ───────────────────────────────────────────────── */
/* ⛔ AN IFRAME EATS THE MOUSE WHEEL. With the cursor over the directory or the
   card, the wheel scrolls THAT — not the page — so a reader on their way down
   gets caught and has to fight their way out.
   ⛔ THE GUTTER FIX DOES NOT WORK, and this is the record of why so nobody
   rebuilds it: the card's tabs run to the LEFT edge and the directory's
   chevrons to the RIGHT, so there is no margin that is dead on both sides.
   ⭐ SO THE GUARD COVERS THE WHOLE FRAME. It belongs to THIS document, so the
   wheel scrolls the page normally — the reader sails past. A click dismisses
   it and the embed is fully live, every chevron and tab included. Leaving the
   frame re-arms it, so the next scroll-past is safe again. Same shape as a
   Google Maps embed, and for the same reason.
   ⛔ NEVER PUT ONE OVER THE HERO. That frame is WebGL, and index.css records
   the measurement: covering a live canvas makes Chrome drop the surface and
   restoring it costs seconds. These two are DOM-only, which is what makes a
   cover safe here and nowhere else. */
.scrollguard {
  position: absolute; inset: 0; z-index: 4;
  cursor: pointer;
  display: grid; place-items: end center;
  padding-bottom: var(--s2);
}
.scrollguard[hidden] { display: none; }
.scrollguard-hint {
  font-family: var(--f-sys); font-size: 0.62rem; font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase;
  background: var(--band-bg); color: var(--band-text);
  padding: 4px var(--s2);
  opacity: 0; transition: opacity var(--t-fast);
}
.scrollguard:hover .scrollguard-hint { opacity: 0.92; }

/* 8a ── Cary: a separate product, in its own room ──────────────────────── */
/* ⛔ NOT a full-bleed band, deliberately. `.shell` is a max-width container, so
   breaking out needs 50vw arithmetic that fights the scrollbar — and the inset
   panel is the truer statement anyway: Cary is a thing pinned INSIDE the Ward,
   not a different chapter of it. */
.sec--cary {
  background: var(--cary-ground);
  border-top: 3px solid var(--cary-rule);
  padding: var(--s5) var(--s4);
  margin: var(--s5) 0;
}
/* ⚠️ NOT var(--live). Amber is reserved for what is actually running and
   couriers are not open yet — the section's own note says so three lines down. */
.sec--cary .cta { margin-top: var(--s4); }

/* 8b ── the pill, the ticker, the placeholder ──────────────────────────── */
.pill {
  /* justify-self, because a grid stretches its children and the border would
     otherwise run the width of the column with nothing in it */
  display: inline-flex; justify-self: start; gap: 0; margin-bottom: var(--s1);
  border: 1px solid var(--rule);
}
.pill-btn {
  font-family: var(--f-sys); font-size: 0.7rem; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase;
  padding: var(--s1) var(--s2);
  background: transparent; color: var(--text-soft);
  border: 0; cursor: pointer;
  transition: color var(--t-fast), background var(--t-fast);
}
.pill-btn + .pill-btn { border-left: 1px solid var(--rule); }
.pill-btn:hover { color: var(--text); }
.pill-btn[aria-selected="true"] { color: var(--band-text); background: var(--band-bg); }

/* The one place besides the map where something moves, and it moves because
   the subject IS motion — a ticker described in still type is a ticker that
   has not been shown. Reduced motion gets the first phrase and no cycling. */
/* A tracked-caps lead-in on its own line, then the cycling phrase as the
   headline. The lead-in never moves, so only the thing that changes changes. */
.ticker-lede {
  font-family: var(--f-sys); font-size: 0.74rem; font-weight: 700;
  letter-spacing: 0.24em; text-transform: uppercase; color: var(--text-soft);
  margin: 0 0 var(--s1);
}
/* ⚠ The phrases differ in length, so the heading must not resize as they
   cycle — reserve two lines and let a short one leave the space empty. A
   headline that jumps every three seconds is a headline nobody reads. */
.ticker { text-wrap: pretty; min-height: 2.4em; margin-top: 0; }
.ticker-slot { display: inline-block; position: relative; padding-bottom: 4px; }
/* The tube, not a halo: a lit bar under the words. A box-shadow on the span
   glows on all four sides and reads as a border, which is the opposite of
   neon. */
.ticker-slot::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: var(--live);
  box-shadow: 0 0 6px 1px var(--live), 0 0 16px 3px var(--live);
  opacity: 0.9;
}
.ticker-slot > span { transition: opacity var(--t-fast) linear; }
.ticker-slot > span[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) { /* A tracked-caps lead-in on its own line, then the cycling phrase as the
   headline. The lead-in never moves, so only the thing that changes changes. */
.ticker-lede {
  font-family: var(--f-sys); font-size: 0.74rem; font-weight: 700;
  letter-spacing: 0.24em; text-transform: uppercase; color: var(--text-soft);
  margin: 0 0 var(--s1);
}
/* ⚠ The phrases differ in length, so the heading must not resize as they
   cycle — reserve two lines and let a short one leave the space empty. A
   headline that jumps every three seconds is a headline nobody reads. */
.ticker { text-wrap: pretty; min-height: 2.4em; margin-top: 0; }
.ticker-slot { box-shadow: none; } }

/* ⛔ Was `.host-foot`, when the ask lived mid-page inside the host section.
   Same layout, new owner — RENAMED rather than copied, so there is one rule for
   one thing and no dead twin left behind. */
.ask-foot { display: flex; flex-wrap: wrap; gap: var(--s5); align-items: flex-end; }
/* ⚠️ It WRAPS here, and that is fine — but it was paying for the wrap twice.
   The placeholder is a grid and stretches to the column (322px measured), so it
   can never sit beside a 321px button in a 542px column; it then added its own
   top margin on top of the flex gap. One gap, not two. */
.ask-foot .placeholder { margin-top: 0; }
.placeholder { margin: var(--s4) 0 0; display: grid; gap: var(--s1); justify-items: start; }
.placeholder-box {
  width: 104px; height: 104px;
  border: 1px dashed var(--rule);
  background: repeating-linear-gradient(
    45deg, transparent 0 7px, var(--rule-soft) 7px 8px);
}
.placeholder figcaption {
  font-family: var(--f-sys); font-size: 0.66rem;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-faint);
}

/* 9 ── the title block ──────────────────────────────────────────────────── */
.tblock { border: 1px solid var(--rule); background: var(--ground-2); }
.tblock-head {
  display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4);
  justify-content: space-between; align-items: baseline;
  padding: var(--s3) var(--s4); border-bottom: 1px solid var(--rule);
  font-family: var(--f-sys);
}
.tblock-t { font-size: 0.72rem; font-weight: 700; letter-spacing: 0.18em; text-transform: uppercase; }
.tblock-legend { font-size: 0.68rem; color: var(--text-soft); display: flex; gap: var(--s3); flex-wrap: wrap; }

.dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  border: 1px solid var(--sign-soft); margin-right: 6px; vertical-align: 1px;
}
.dot-on { background: var(--sign-soft); }

.tier { padding: var(--s3) var(--s4) var(--s4); border-bottom: 1px solid var(--rule-soft); }
.tier:last-child { border-bottom: none; }
.tier-name {
  font-family: var(--f-sys); font-weight: 700; font-size: 0.8rem;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--sign);
  display: block; margin-bottom: var(--s0);
}
.tier-say { font-size: 0.94rem; line-height: 1.5; color: var(--text-soft); margin: 0 0 var(--s3); max-width: 60ch; }

.row {
  display: grid; grid-template-columns: minmax(0, 190px) minmax(0, 1fr) minmax(0, 148px);
  gap: var(--s2) var(--s3); padding: var(--s2) 0;
  border-top: 1px solid var(--rule-soft); align-items: baseline;
}
.row:first-of-type { border-top: none; }
@media (max-width: 720px) { .row { grid-template-columns: minmax(0, 1fr); gap: var(--s0); } }
.r-name { font-family: var(--f-sys); font-size: 0.84rem; font-weight: 600; line-height: 1.35; }
.r-src { display: grid; gap: 2px; }
.r-src span { font-size: 0.88rem; line-height: 1.4; }
.r-src em { font-style: normal; font-family: var(--f-sys); font-size: 0.74rem; color: var(--text-soft); }
.r-terms {
  font-family: var(--f-sys); font-size: 0.72rem; line-height: 1.4;
  color: var(--text-soft); font-variant-numeric: tabular-nums;
}

/* 10 ── the clock ───────────────────────────────────────────────────────── */

/* 10b ── the sky band ───────────────────────────────────────────────────
   The one band of colour on the page, and it is not chosen: js/site.js sets
   --sky-* from the extracted table for the neighborhood's own minute. It
   divides the instruments from the index — what the Ward holds, from what is
   living in it — and it is why day and night need no widget. */
.skyband {
  /* It lives in the shared figure area now, so it fills it — and a tall frame
     is where the sun's arc finally has somewhere to go. */
  width: 100%; height: 100%;
  cursor: ew-resize;
  touch-action: pan-y;
  position: relative;
  background: linear-gradient(
    to bottom,
    var(--sky-high, var(--ground-3)) 0%,
    var(--sky-mid, var(--ground-3)) 34%,
    var(--sky-low, var(--ground-2)) 70%,
    var(--sky-horizon, var(--ground-2)) 100%);
  transition: background var(--t-slow) linear;
}

/* The sun and moon ride the band at their real altitude, so dragging the sky
   is watching them rise and set.
   ⚠ Positioned in CSS percentages, NOT in an SVG viewBox: the band is wide and
   short, so a viewBox scaled to fit turns every circle into an oval. */

/* ⭐ THE LIVE TREE, standing on the bottom edge of the band.
   ⚠ `background: transparent` on the IFRAME is load-bearing and easy to miss:
   an iframe paints its own opaque base regardless of what the embedded document
   does, so without this the alpha canvas inside buys nothing and you get a grey
   rectangle over the sky. (The embed clears to alpha 0 and drops its own sky
   dome; see `?embed=tree&alpha=1`.)
   Deaf to the pointer so dragging the day still works straight through it. */
.skyband-tree {
  /* ⛔ Stops at the track — full-height is what buried the mark. And the height
     is EXPLICIT, not top+bottom: an iframe is a REPLACED element, so it does not
     stretch between offsets the way a div does. Setting only top/bottom silently
     collapsed it to the iframe's intrinsic 150px default — measured, 472x150 in
     a 420-tall band — which looks exactly like "the tree failed to load." */
  position: absolute; left: 0; right: 0; top: 0;
  width: 100%; height: calc(100% - var(--track-h));
  border: 0; background: transparent;
  pointer-events: none;
}

/* ⛔ THE BAND NO LONGER DRAWS THE SUN, THE MOON OR THE HORIZON WASH.
   They were flat discs positioned in percentages, and the band now contains a
   PERSPECTIVE RENDER (?embed=tree). Two projections cannot share a sky: the
   screenshot that settled it had the moon sitting inside the canopy. The scene
   draws its own bodies, correctly, so these are gone rather than hidden.
   ⭐ Their CSS outlived their DOM by one commit and the AUDIT caught it —
   `unused css` — which is the whole reason that check exists. Removed here,
   with the tokens it stranded. Archived: git show ef50392^:css/site.css */

/* ⚠ THE BAND NEEDS TO LOOK DRAGGABLE. A cursor change is not an affordance for
   anyone who does not happen to hover it — Jacob asked where the slider was, and
   the mark was the answer: it says both "you are here in the day" and "this is a
   thing you can pull".
   ⛔ THEN THE DIORAMA COVERED IT. The tree embed went opaque (it draws its own
   sky, because flat CSS discs cannot share a projection with a perspective
   render) and the mark spent its life behind an iframe. The control still
   worked; nothing said so. Jacob, 2026-08-23: "we lost the actual visible on
   screen time slider so the effect still works but you have to know it's there."
   ⭐ THE FIX IS A TRACK THE TREE CANNOT REACH, not a second control. The frame
   now stops short of the bottom edge and the mark rides the strip that leaves —
   "an unmarked slider under the tree", which is what was asked for, and still
   ONE control. A second visible slider would be the second slider this page
   deliberately does not have. */
.skyband { --track-h: 18px; }
/* The track. A pseudo-element, so it costs no markup and no class. */
.skyband::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: var(--track-h);
  background: var(--band-bg); opacity: 0.9;
  pointer-events: none;
}
.skyband-mark {
  position: absolute; top: 0; bottom: 0; width: 1px; z-index: 2;
  background: var(--live); opacity: 0.55;
  transform: translateX(-50%); pointer-events: none;
}
/* The handle, sitting IN the track where nothing can paint over it. */
.skyband-mark::after {
  content: ""; position: absolute; left: 50%; bottom: 4px;
  width: 17px; height: 10px; border-radius: 5px;
  transform: translateX(-50%);
  background: var(--live);
}
.skyband:hover .skyband-mark { opacity: 0.95; }

.skyband:focus-within { outline: 2px solid var(--live); outline-offset: 2px; }

/* Present for keyboard and assistive tech, invisible to everyone else — the
   band itself is the visible control, and a second visible slider would be
   the second slider we deliberately do not have. */
.skyband-input {
  position: absolute; inset: 0; width: 100%; height: 100%;
  margin: 0; opacity: 0; cursor: ew-resize;
}

/* Appears only once the day has been moved: without it a visitor can strand
   the neighborhood at 3am with no way back. A conditional control, not a
   caption. */
/* ⭐ Appears ONLY when the day is held off live — js/site.js sets
   `nowBtn.hidden = (held === null)`. So the control announces itself exactly
   when there is something to undo, and never before. */
.skyband-now {
  position: absolute; right: var(--s1); bottom: 0; z-index: 3;
  height: var(--track-h); line-height: var(--track-h); padding: 0 var(--s1);
  font-family: var(--f-sys); font-size: 0.64rem; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
  background: var(--band-bg); color: var(--band-text);
  border: 0; padding: 3px var(--s1); cursor: pointer;
}
.skyband-now[hidden] { display: none; }

/* 11 ── colophon ────────────────────────────────────────────────────────── */
.colophon {
  border-top: 1px solid var(--rule); margin-top: var(--s6);
  padding: var(--s4) 0 0;
  display: grid; gap: var(--s3);
  font-family: var(--f-sys); font-size: 0.73rem; line-height: 1.6; color: var(--text-soft);
}
.colophon a { text-underline-offset: 3px; }
.colophon-row { display: grid; grid-template-columns: 132px minmax(0, 1fr); gap: var(--s3); align-items: baseline; }
@media (max-width: 700px) { .colophon-row { grid-template-columns: minmax(0, 1fr); gap: var(--s0); } }
.colophon-k { font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-faint); }
.colophon-foot {
  display: flex; flex-wrap: wrap; gap: var(--s3);
  justify-content: space-between; align-items: center;
  border-top: 1px solid var(--rule-soft); padding: var(--s4) 0 var(--s6);
  letter-spacing: 0.1em; text-transform: uppercase; font-size: 0.68rem;
}
