/* soscitea's one stylesheet. This header is the platform's design spec: any
 * new surface — builder, widget, whatever comes after — follows it.
 *
 * The point of view, stated once so nothing below has to guess: every page
 * here is a rendering of a JSONL trace. Not a dashboard — a record. The
 * record itself is still set like one: alignment, soft warm rules between
 * rows, figures in tabular monospace so columns of numbers line up down the
 * page.
 *
 * The material is clay. The page is a slab of warm cream, and everything the
 * reader can touch — the header, the bars, the panels, every button — is a
 * piece pressed out of that same slab. Depth is molded, not drawn: a raised
 * piece carries light on its upper-left, shade on its lower-right, and a
 * diffuse shadow underneath, all at once; a well — an input, the scrubber's
 * groove — is the same recipe turned inside out. No glass, no blur, no
 * hairline around a slab: the light on the material is the edge. Corners are
 * big and soft, because nothing hand-molded is sharp.
 *
 * The material has to move like clay too: a pressed control squishes — it
 * scales down and its shadow flattens, because a pressed thing is nearer the
 * ground — and settles back on a soft spring. That squish is the whole
 * tactile argument of the platform: the world is a toy you shape, the
 * credits are play money, and the chrome should feel like it.
 *
 * Colour is spent, not scattered. Warm ink and cream carry the interface;
 * the colour system is four pastels of equal weight — Vis Vis yellow,
 * Pale Green, Perano blue, Wewak pink — each paired with a deep tone of
 * its own hue, because an 82%-lightness pastel can fill but never carry
 * text — and, sitting at the cream ground's own luminance, a pastel is a
 * slab or a wash, never a hairline: deeps draw the lines and the words.
 * Two pastels are chrome and two are status: Vis Vis is the mark, the
 * primary control and the header's own slab; Perano, thinned toward the
 * cream, is the material of the rails — the surfaces where the platform
 * speaks; Pale Green is the good word and Wewak the bad one, and neither
 * is ever a panel, because a green panel says "success" and a pink one
 * says "alarm". Saturated colour beyond
 * the four lives in exactly one place — the town artwork, which is the
 * only thing on the site that is a picture, and which was always a
 * hand-molded toy village: the one part of the old design that was clay
 * before the rest of it.
 *
 * The system:
 *
 *   Type    16px root, a seven-step rem ramp (--text-*), set in the rounded
 *           system face. Never px: a reader who enlarges their browser font
 *           enlarges this whole page.
 *   Space   4px unit, --space-1..8. Tight inside a block, generous between
 *           blocks — that is where density comes from.
 *   Colour  Semantic roles only. Raw hex appears in this block and in the
 *           town's art, nowhere else.
 *   Depth   One clay ground, two recipes: --clay-raise for anything that
 *           stands up off the slab, --clay-sink for anything pressed into
 *           it. Content — tables, logs, feeds — sits directly on the ground
 *           and is ruled, not boxed.
 *   States  Every control answers hover, focus-visible, active, disabled,
 *           busy. Active is always the squish.
 *
 * Contrast is checked, not asserted: every ink and accent clears 4.5:1 on
 * every cream it can land on, and the focus ring clears 3:1. Ink is a warm
 * dark grey rather than black — softer than print, still 11:1 on the ground.
 */

:root {
  /* Tell the UA which way round we are, so it draws form controls, scrollbars
     and the default caret in light — otherwise a dark-mode browser supplies
     its own chrome, and its own chrome is blue. */
  color-scheme: light;

  /* --- surfaces: one warm clay ground, and the creams a piece of it shows
         when it stands up (lighter — it catches the light) or is pressed in
         (deeper — it holds the shade). The steps are gentle on purpose: the
         tiers should be felt as one material molded, not seen as boxes
         drawn. --- */
  --surface-page:   #f3ede3;  /* the slab everything is pressed from */
  --surface-raised: #faf5eb;  /* a piece standing up into the light */
  --surface-sunken: #e9e2d3;  /* wells: code, track grooves */
  --surface-deep:   #ded5c2;  /* the deepest press — table heads, firm ground */
  --surface-hover:  #f7f2e7;  /* the one step a row moves under a pointer */
  --surface-solid:  #faf5eb;
  /* The rails' material: Perano thinned 40% into the raised cream. Raw
     Perano fails muted ink (3.4:1); at this mix muted reads 4.94 and every
     deep clears 5.4. The number is the reason for the hex. */
  --surface-voice:  #d7dbf3;

  /* --- ink. Warm dark grey, not black: type in this material stops short of
         the bottom of the range. --- */
  --ink:           #35302a;  /* 11.5:1 on the ground — body and headings */
  --ink-secondary: #55493c;  /*  7.0:1 — supporting copy */
  --ink-muted:     #635a4c;  /*  5.4:1 — labels, captions, column heads */
  --ink-disabled:  #8f8574;  /*  ~2.9:1 — disabled only, which is exempt */

  /* --- rules. Not borders: a slab has no outline in this material. These
         are the soft warm lines that divide rows *inside* the record. --- */
  --line:      #ddd3c1;  /* row rules, dividers */
  --line-firm: #c4b8a1;  /* a divider that has to be seen */
  --line-heavy:#a89c86;  /* the heaviest mark on the site, at 2px */
  --edge:      #8f8574;  /* the skip link's border; slabs have none */

  /* --- accents: the four-pastel system (the user's palette, 2026-09-08).
         Four hues at equal saturation and lightness — Vis Vis #FFEDA2,
         Pale Green #B4FFA2, Perano #A2B4FF, Wewak #FFA2B4. A pastel this
         light can fill or wash but never carry text, so each hue is a pair:
         the pastel itself, and a deep tone of the same hue for ink. The
         roles: Vis Vis is the mark and the one thing you press; Pale Green
         is the good word; Wewak is the bad one, and live; Perano is
         attention and credit. The division of labour is by weight, not by
         hue: a pastel is a slab or a wash — it sits at the ground's own
         luminance (1.01:1 on cream), so a molded shadow must give it an
         edge; a deep is a line or a word. A pastel hairline is invisible;
         a deep slab is a bruise. --- */
  --accent:        #5f4c05;  /* Vis Vis deep — the mark: text, lines, bars */
  --accent-bright: #ffeda2;  /* Vis Vis — slabs and washes, never a hairline */
  --accent-wash:   #fff7d9;  /* Vis Vis at a whisper: selection, current row */
  --positive:      #256018;  /* Pale Green deep — 6.5:1 */
  --positive-bright: #b4ffa2; /* Pale Green — a slab, when good is a place */
  --positive-wash: #dcffd1;  /* Pale Green, quieted: solved rows (deep: 7.0) */
  --danger:        #9c2a43;  /* Wewak deep — 6.4:1 (never on the pink itself) */
  --danger-bright: #ffa2b4;  /* Wewak — the live slab; ink on it reads 6.9 */
  --attention:     #2e3f94;  /* Perano deep — 8.0:1 */
  --attention-bright: #a2b4ff; /* Perano — a slab, where the agent speaks */
  --attention-wash: #e3e9ff; /* Perano, quieted: notes (deep on it: 7.7) */
  --rose:          #b03750;  /* Wewak, half a step lighter — 5.1:1 */

  /* --- clay. Two recipes and a press, used everywhere so the whole site is
         molded from one material. Three shadows at once make a raised piece:
         light inside the upper-left where it catches the room, shade inside
         the lower-right where it turns away, and a diffuse drop underneath.
         A sunken piece is the same thing inverted. The alphas are quiet on
         purpose — louder and it stops being clay and becomes a 2010 bevel.
         The shade is never pure black: it is the ink, thinned. --- */
  --clay-raise: inset 5px 5px 10px rgba(255, 255, 255, .65),
                inset -5px -5px 10px rgba(53, 48, 42, .07),
                8px 10px 24px rgba(53, 48, 42, .13);
  --clay-raise-sm: inset 3px 3px 6px rgba(255, 255, 255, .65),
                   inset -3px -3px 6px rgba(53, 48, 42, .08),
                   4px 5px 12px rgba(53, 48, 42, .13);
  --clay-sink: inset 4px 4px 9px rgba(53, 48, 42, .10),
               inset -4px -4px 9px rgba(255, 255, 255, .55);
  /* Pressed: the drop shadow collapses (a pressed thing is nearer the
     ground) and the inner shade deepens. Pair it with scale(.97). */
  --clay-press: inset 3px 3px 7px rgba(53, 48, 42, .12),
                inset -3px -3px 7px rgba(255, 255, 255, .45),
                2px 3px 6px rgba(53, 48, 42, .10);

  /* --- type: 16px root, ramp named for the job, not the size --- */
  --text-caption:  0.75rem;    /* 12 — sequence numbers, clock, meta */
  --text-footnote: 0.8125rem;  /* 13 — feed lines, column heads, footer */
  --text-label:    0.875rem;   /* 14 — table cells, controls, nav */
  --text-body:     0.9375rem;  /* 15 — body copy */
  --text-lead:     1.0625rem;  /* 17 — stat values */
  --text-title:    1.125rem;   /* 18 — section headings */
  --text-display:  2rem;       /* 32 — the one h1 per page */

  --weight-normal: 400;
  --weight-medium: 500;
  --weight-bold:   600;
  --weight-heavy:  700;

  /* --- space: 4px unit --- */
  --space-1: 0.25rem;  /*  4 */
  --space-2: 0.5rem;   /*  8 */
  --space-3: 0.75rem;  /* 12 */
  --space-4: 1rem;     /* 16 */
  --space-5: 1.5rem;   /* 24 */
  --space-6: 2rem;     /* 32 */
  --space-7: 3rem;     /* 48 */
  --space-8: 4rem;     /* 64 */

  /* --- radii: big and soft. Nothing hand-molded has a sharp corner. --- */
  --radius-sm:   10px;
  --radius-md:   14px;
  --radius-lg:   22px;
  --radius-pill: 999px;

  --shadow-sm: 2px 3px 8px rgba(22, 19, 15, .10);

  /* --- motion: one duration pair, one easing curve, and one spring for the
         settle after a squish — honoured or dropped wholesale --- */
  --ease: cubic-bezier(.32, .72, 0, 1);
  --spring: cubic-bezier(.34, 1.56, .64, 1);
  --fast: 120ms;
  --slow: 320ms;

  --header-h: 3rem;
  --measure: 68rem;       /* a reading width for pages made of words */
  --measure-wide: 106rem; /* a watching width for the page made of world */
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--surface-page);
  color: var(--ink);
  /* The rounded cut where the system has one: chunky, soft-cornered type is
     the letterform the material asks for. The fallbacks are the plain stack,
     which still sits fine on clay. */
  font-family: ui-rounded, "SF Pro Rounded", system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: var(--text-body);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* The browser's own accents are blue by default — selection, the caret, and
   any control drawn from accent-color. Overriding them is not decoration;
   it is the difference between a palette and an intention. */
::selection { background: var(--accent-wash); color: var(--ink); }
:root { accent-color: var(--accent); caret-color: var(--accent); }

/* Every figure on the site is tabular, so columns of numbers line up whether
   they sit in a table, a readout or a sentence. */
.mono, code, .seq, .num, td.num, .v, .clockface, .stat .v, .ev .etext b {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-variant-numeric: tabular-nums;
}

/* --- focus: one ring, everywhere, never removed ---
   :focus-visible rather than :focus, so a mouse user does not get a ring they
   did not ask for and a keyboard user always does. The UA ring is blue, so
   this rule is doing colour work as well as design work. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* --- skip link: the first thing in the tab order, invisible until focused ---
   The town page is one enormous SVG; a keyboard user should not have to walk
   past the whole header to reach it. */
.skip {
  position: absolute;
  left: var(--space-3);
  top: calc(-1 * var(--space-8));
  z-index: 20;
  padding: var(--space-2) var(--space-4);
  background: var(--surface-solid);
  color: var(--ink);
  border: 1px solid var(--edge);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  text-decoration: none;
  transition: top var(--fast) var(--ease);
}
.skip:focus { top: var(--space-3); }

/* --- header: a bar of the material. It stays square across the top — a slab
       the width of the window is the shelf the page hangs from, and rounding
       its window-edge corners would just say "screenshot". */
header.site {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: var(--space-6);
  padding: 0 var(--space-5);
  min-height: var(--header-h);
}

/* One molded edge, four slabs, written once so they cannot drift apart: the
   header, the control bar and the two sticky rails — the pieces of chrome
   that stand up off the page. None of them has a border: the molded shadow
   is the edge. The material differs by voice: the header is a slab of the
   mark's own pastel, the rails are the Perano tint — the surfaces where
   the platform speaks — and the control bar stays cream, because it is the
   tray the yellow and pink controls sit on and tinting it would drown them. */
header.site,
.controls,
.state,
.townside {
  box-shadow: var(--clay-raise);
}
header.site { background: var(--accent-bright); }
.controls { background: var(--surface-raised); }
.state,
.townside { background: var(--surface-voice); }

/* The mark: a small deep-olive ball and the word, set tight. It is the one
   piece of pure ornament on the site, which is why it gets to be the only
   one. */
.brand {
  display: inline-flex;
  align-items: center;
  gap: .55em;
  color: var(--ink);
  font-weight: var(--weight-heavy);
  font-size: var(--text-label);
  letter-spacing: -.02em;
  text-decoration: none;
}
/* The ball is the mark's deep, molded in miniature — on the header's own
   Vis Vis slab a pastel ball would vanish. */
.brand::before {
  content: "";
  width: .55rem; height: .55rem;
  background: var(--accent);
  border-radius: var(--radius-pill);
  box-shadow: inset 1px 1px 2px rgba(255, 255, 255, .7),
              inset -1px -1px 2px rgba(53, 48, 42, .25),
              1px 2px 3px rgba(53, 48, 42, .2);
  flex: none;
}
.brand:hover { text-decoration: none; }

header.site nav { display: flex; gap: var(--space-4); }
header.site nav a {
  color: var(--ink-muted);
  text-decoration: none;
  font-size: var(--text-label);
  line-height: var(--header-h);
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
header.site nav a:hover { color: var(--ink); text-decoration: none; }
header.site nav a:active { color: var(--accent); }
/* Where you are is marked by weight and a rule, not by colour alone. */
header.site nav a[aria-current="page"] {
  color: var(--ink);
  font-weight: var(--weight-bold);
  border-bottom-color: var(--ink);
}

main { max-width: var(--measure); margin: 0 auto; padding: var(--space-6) var(--space-5) var(--space-8); }
footer.site {
  max-width: var(--measure);
  margin: 0 auto;
  padding: var(--space-4) var(--space-5) var(--space-6);
  color: var(--ink-muted);
  font-size: var(--text-footnote);
  border-top: 1px solid var(--line);
}

/* --- the masthead: title left, the run's particulars right, closed by a firm
       rule. One per page, and it is what tells you at a glance which page you
       are on and which trace you are looking at. --- */
.masthead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  padding-bottom: var(--space-3);
  border-bottom: 2px solid var(--line-heavy);
  margin-bottom: var(--space-6);
}
.masthead h1 { margin: 0; }
.masthead .meta {
  margin: 0;
  color: var(--ink-muted);
  font-size: var(--text-footnote);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 var(--space-3);
}
/* The separators are drawn, not typed, so no template has to carry a "·". */
.masthead .meta > * + *::before {
  content: "";
  display: inline-block;
  width: 1px; height: .8em;
  margin-right: var(--space-3);
  background: var(--line-firm);
  vertical-align: -.05em;
}
.masthead .lede {
  flex-basis: 100%;
  max-width: 44rem;
  margin: var(--space-2) 0 0;
  color: var(--ink-secondary);
}

/* --- headings: the ramp, applied. Section headings are words in ink at a
       larger size — not letterspaced capitals, which is a label, not a title. */
h1 {
  font-size: var(--text-display);
  line-height: 1.08;
  letter-spacing: -.025em;
  font-weight: var(--weight-heavy);
  margin: 0 0 var(--space-2);
}
h1 .sub { font-size: var(--text-label); font-weight: var(--weight-normal); }
h2 {
  font-size: var(--text-title);
  line-height: 1.3;
  letter-spacing: -.012em;
  font-weight: var(--weight-bold);
  color: var(--ink);
  margin: 0 0 var(--space-3);
}
section + section { margin-top: var(--space-7); }

a { color: var(--accent); text-decoration: none; }
/* A visited link is purple by UA default. Not here. */
a:visited { color: var(--accent); }
a:hover { text-decoration: underline; text-underline-offset: 2px; }
a:active { color: var(--attention); }

.sub { color: var(--ink-muted); font-size: var(--text-label); }
p.sub { max-width: 44rem; }
code {
  background: var(--surface-sunken);
  padding: .1em .35em;
  border-radius: 6px; /* tighter than --radius-sm: at pill radius an inline code span reads as a status chip */
  font-size: .875em;
}

/* --- tables. A firm rule under the column heads, hairlines between rows, and
       nothing else: the alignment does the work a border would do badly. --- */
table { border-collapse: collapse; width: 100%; font-size: var(--text-label); }
th, td { text-align: left; padding: var(--space-2) var(--space-4) var(--space-2) 0; }
th:last-child, td:last-child { padding-right: 0; }
td { border-bottom: 1px solid var(--line); }
th {
  color: var(--ink-muted);
  font-weight: var(--weight-normal);
  font-size: var(--text-footnote);
  padding-bottom: var(--space-2);
  border-bottom: 1.5px solid var(--line-heavy);
  white-space: nowrap;
}
th.num, td.num { text-align: right; }
tbody tr { transition: background var(--fast) var(--ease); }
/* A wide row is hard to track across nine columns; the wash is a guide rail,
   not decoration. */
tbody tr:hover { background: var(--surface-hover); }
tbody tr:hover td { border-bottom-color: var(--line-firm); }
/* The rank column: the number is the point, so it is set like one. */
td.rank { font-weight: var(--weight-bold); color: var(--ink); }

/* --- status. Not a pill: a coloured dot and the word. The word is always the
       message — the colour only agrees with it — so a reader who cannot see
       the hue loses nothing. --- */
.badge {
  display: inline-flex;
  align-items: baseline;
  gap: .45em;
  font-size: inherit;
  font-weight: var(--weight-medium);
  color: var(--ink-secondary);
  white-space: nowrap;
}
.badge::before {
  content: "";
  width: .4em; height: .4em;
  border-radius: var(--radius-pill);
  background: currentColor;
  transform: translateY(-.15em);
  flex: none;
}
.badge.ok, .badge.alive { color: var(--positive); }
.badge.warn, .badge.dead { color: var(--danger); }
.badge.open, .badge.imported { color: var(--attention); }
/* Left is neither alive nor dead: the wallet is whole, the body is gone. */
.badge.gone { color: var(--ink-muted); }
/* "sim · unranked" and "judged" are the absence of a score, not a state worth
   announcing. They get no dot at all. */
.badge.sim, .badge.judged { color: var(--ink-muted); font-weight: var(--weight-normal); }
.badge.sim::before, .badge.judged::before { display: none; }
/* Live is the one status that is still happening, so its dot is the one thing
   on the site that moves for its own sake. */
.badge.live { color: var(--danger); }
.badge.live::before { animation: pulse 2s var(--ease) infinite; }
@keyframes pulse { 50% { opacity: .25; } }

.ast { color: var(--attention); text-decoration: none; font-weight: var(--weight-heavy); }
.ast:hover { text-decoration: underline; }

/* --- the readout: a row of figures between two rules. It replaces a row of
       cards — cards would give five equal boxes five equal emphases, which is
       exactly the hierarchy these numbers do not have. --- */
.stats {
  display: flex;
  flex-wrap: wrap;
  margin: 0 0 var(--space-6);
  border-top: 1.5px solid var(--line-heavy);
  border-bottom: 1px solid var(--line);
}
.stat {
  flex: 0 0 auto;
  padding: var(--space-3) var(--space-5);
  border-left: 1px solid var(--line);
}
.stat:first-child { padding-left: 0; border-left: 0; }
.stat .k {
  display: block;
  color: var(--ink-muted);
  font-size: var(--text-footnote);
  line-height: 1.4;
}
.stat .v {
  display: block;
  font-size: var(--text-lead);
  font-weight: var(--weight-medium);
  letter-spacing: -.01em;
}
/* Credits are the unit the platform runs on; dollars are what they cost. The
   figure leads and the money follows it, quietly — the old card ran the two
   together in one string and neither could be read at a glance. */
.stat .u {
  display: block;
  color: var(--ink-muted);
  font-size: var(--text-caption);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-variant-numeric: tabular-nums;
  line-height: 1.4;
}

/* --- sparkline: a line and a baseline. A chart of one series does not need a
       frame around it. --- */
.spark { width: 100%; max-width: 44rem; height: auto; display: block; margin-bottom: var(--space-2); }
.spark-line { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linejoin: round; }
.spark-zero { stroke: var(--line-firm); stroke-width: 1; }
.spark-dot  { fill: var(--accent); }

/* --- bounty history: a numbered record with the sequence in a left gutter, so
       the entries hang off one spine. --- */
ol.history { list-style: none; padding: 0; margin: 0; }
ol.history li {
  display: grid;
  grid-template-columns: 3.5rem 1fr;
  gap: 0 var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--line);
  font-size: var(--text-label);
}
ol.history .seq { color: var(--ink-muted); font-size: var(--text-caption); text-align: right; }
ol.history .line { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 var(--space-2); }
ol.history .when { color: var(--ink-muted); font-size: var(--text-footnote); }
table.book { width: auto; min-width: 14rem; grid-column: 2; margin: var(--space-2) 0 var(--space-1); }
table.book th, table.book td { padding: var(--space-1) var(--space-4) var(--space-1) 0; font-size: var(--text-footnote); }
table.book td { border-bottom: 0; }
table.book tbody tr:hover { background: none; }

/* --- the control bar: a clay slab, sticky under the header, so the thing
       you steer stays where you left it however far the page scrolls --- */
.controls {
  position: sticky;
  top: calc(var(--header-h) + var(--space-2));
  z-index: 5;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin: 0 0 var(--space-5);
  /* The slab bleeds a little past the measure so it reads as a shelf the
     column passes under, not as a card sitting inside the column. */
  padding: var(--space-3) var(--space-4);
  margin-left: calc(var(--space-4) * -1);
  margin-right: calc(var(--space-4) * -1);
  border-radius: var(--radius-lg);
}

/* --- buttons and selects: pieces of the material, borderless, molded up off
       the bar. All six states, because a control that only has a default
       state has not told you it is a control --- */
.controls button, .controls select {
  background: var(--surface-raised);
  color: var(--ink);
  border: 0;
  border-radius: var(--radius-md);
  padding: var(--space-1) var(--space-3);
  min-height: 1.9rem;
  font: inherit;
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  cursor: pointer;
  box-shadow: var(--clay-raise-sm);
  /* The settle rides the spring: releasing a squished button lets it bounce
     back up a touch past rest, which is what makes it read as soft. */
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease),
              box-shadow var(--fast) var(--ease), transform var(--slow) var(--spring);
}
.controls button:hover, .controls select:hover {
  background: var(--surface-hover);
}
.controls button:active {
  /* The squish: the piece scales down, its drop shadow collapses, its inner
     shade deepens. A press should feel like a thumb in clay. */
  transform: scale(.96);
  box-shadow: var(--clay-press);
  transition-duration: var(--fast);
}
.controls button:disabled, .controls select:disabled {
  color: var(--ink-disabled);
  background: var(--surface-sunken);
  box-shadow: var(--clay-sink);
  cursor: not-allowed;
  transform: none;
}
/* Loading: the control stays in place and in size, so nothing reflows around
   a spinner. It is busy, so it is also inert. */
.controls button[aria-busy="true"] {
  color: transparent;
  pointer-events: none;
  position: relative;
}
.controls button[aria-busy="true"]::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: .875rem; height: .875rem;
  border: 2px solid var(--line-firm);
  border-top-color: var(--accent);
  border-radius: var(--radius-pill);
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Play is the one thing on these pages you are meant to press, so it is the
   one Vis Vis piece of clay on the site: the mark's own colour, molded with
   the same recipe as everything else — its highlight is the yellow lightened,
   its shade the yellow's own deep. Ink on it reads 11:1, so the glyph is
   legible rather than merely visible. */
#play {
  min-width: 2.5rem;
  background: var(--accent-bright);
  color: var(--ink);
  font-size: var(--text-body);
  box-shadow: inset 3px 3px 6px rgba(255, 250, 224, .8),
              inset -3px -3px 6px rgba(95, 76, 5, .22),
              4px 5px 12px rgba(53, 48, 42, .18);
}
#play:hover { background: #ffe88a; }
#play:active {
  background: #f6da6e;
  box-shadow: inset 3px 3px 7px rgba(95, 76, 5, .3),
              inset -3px -3px 7px rgba(255, 250, 224, .45),
              2px 3px 6px rgba(53, 48, 42, .12);
}
#play:disabled { background: var(--surface-sunken); color: var(--ink-disabled); box-shadow: var(--clay-sink); }
#play[aria-busy="true"]::after { border-color: color-mix(in srgb, var(--ink) 25%, transparent); border-top-color: var(--ink); }

/* --- the scrubber: a groove pressed into the bar, with a pebble riding in
   it. accent-color alone gets the fill right and the rest wrong: Chromium
   picks the *unfilled* half of the track itself and invents its own
   foreground, so the track is drawn here rather than delegated. Played is
   the mark's deep — the pastel is the ground's own luminance and would
   vanish in the groove — unplayed is the groove's own shade, and the thumb
   is a raised cream pebble ringed in the same deep, so it stays findable
   over either half. --p is the played
   fraction, written by replay.js on every move, because WebKit has no
   ::-moz-range-progress. --- */
.controls input[type=range] {
  flex: 1;
  min-width: 8rem;
  height: 1.5rem;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
}
.controls input[type=range]::-webkit-slider-runnable-track {
  height: 8px;
  border-radius: var(--radius-pill);
  background: linear-gradient(to right,
    var(--accent) 0 var(--p, 0%), var(--surface-deep) var(--p, 0%) 100%);
  box-shadow: inset 2px 2px 4px rgba(53, 48, 42, .16),
              inset -1px -1px 3px rgba(255, 255, 255, .5);
}
.controls input[type=range]::-moz-range-track {
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--surface-deep);
  box-shadow: inset 2px 2px 4px rgba(53, 48, 42, .16),
              inset -1px -1px 3px rgba(255, 255, 255, .5);
}
.controls input[type=range]::-moz-range-progress {
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--accent);
}
.controls input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 50%;
  background: var(--surface-solid);
  border: 2px solid var(--accent);
  margin-top: -5.2px; /* centres the ~18px pebble on the 8px groove */
  box-shadow: inset 1px 1px 2px rgba(255, 255, 255, .8),
              2px 3px 6px rgba(53, 48, 42, .22);
}
.controls input[type=range]::-moz-range-thumb {
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 50%;
  background: var(--surface-solid);
  border: 2px solid var(--accent);
  box-shadow: inset 1px 1px 2px rgba(255, 255, 255, .8),
              2px 3px 6px rgba(53, 48, 42, .22);
}
.controls input[type=range]:hover::-webkit-slider-thumb { background: var(--accent-wash); }
.controls input[type=range]:hover::-moz-range-thumb { background: var(--accent-wash); }
.controls input[type=range]:active::-webkit-slider-thumb { background: var(--accent-bright); }
.controls input[type=range]:active::-moz-range-thumb { background: var(--accent-bright); }
.controls input[type=range]:disabled { cursor: not-allowed; }
.controls input[type=range]:disabled::-webkit-slider-runnable-track { background: var(--surface-sunken); }
.controls input[type=range]:disabled::-moz-range-track { background: var(--surface-sunken); }
.controls input[type=range]:disabled::-webkit-slider-thumb { border-color: var(--line-firm); }
.controls input[type=range]:disabled::-moz-range-thumb { border-color: var(--line-firm); }

/* Live is the one state worth a whole pastel: while following, the button is
   a slab of Wewak clay, molded like the play button — highlight the pink
   lightened, shade the pink's own deep. The label is ink (6.9:1 on the
   pastel); Wewak's deep never sits on its own pastel, it only clears 3.9.
   Off, it goes back to being an ordinary cream button. */
#livebtn {
  color: var(--ink);
  background: var(--danger-bright);
  box-shadow: inset 2px 2px 5px rgba(255, 228, 234, .8),
              inset -2px -2px 5px rgba(156, 42, 67, .22),
              3px 4px 8px rgba(53, 48, 42, .15);
}
#livebtn:hover { background: #ff8fa5; }
#livebtn.off { color: var(--ink-muted); background: var(--surface-raised); box-shadow: var(--clay-raise-sm); }
#livebtn.off:hover { color: var(--ink); background: var(--surface-hover); }

#pos { color: var(--ink-muted); font-size: var(--text-caption); }
.controls .hint { color: var(--ink-muted); font-size: var(--text-caption); margin-left: auto; }

/* --- replay ---
   18rem of readout beside the log at desktop; one column below 50rem, where
   two would leave neither of them readable. */
.replay { display: grid; grid-template-columns: 18rem 1fr; gap: var(--space-6); align-items: start; }

.state {
  position: sticky;
  top: calc(var(--header-h) + var(--space-4));
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-lg);
}
/* In the rail the readout turns on its side: label left, figure right, one per
   line — an instrument panel rather than a row of tiles. */
/* The page readout is closed top and bottom by rules because it floats in the
   column. In the rail the pane's own edge already does that, so both rules go
   — otherwise the heavy one lands a hairsbreadth inside the border and reads
   as a mistake rather than a mark. */
.state .stats { display: block; border-top: 0; border-bottom: 0; margin-bottom: var(--space-6); }
.state .stat {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-left: 0;
  border-bottom: 1px solid var(--line);
}
.state .stat .k { font-size: var(--text-label); }
.state .stat .v { font-size: var(--text-label); font-weight: var(--weight-bold); }
.state h2 { font-size: var(--text-label); margin-bottom: var(--space-1); }
.state h2 + * { margin-top: 0; }
.state section + section { margin-top: var(--space-5); }

.ag { padding: var(--space-2) 0; border-bottom: 1px solid var(--line); font-size: var(--text-label); }
.ag-name { font-weight: var(--weight-medium); }
.ag.dead .ag-name { color: var(--ink-muted); }
/* Retirement is a word, so it survives colour-blindness and forced colours;
   the ink is dropped rather than reddened, because a retired agent is over,
   not alarming. */
.ag-name .fate {
  font-weight: var(--weight-normal);
  font-size: var(--text-caption);
  letter-spacing: 0;
  color: var(--danger);
}
.ag-bal { float: right; color: var(--ink-secondary); }
.bar {
  height: 3px;
  /* The groove takes the deepest press of the material, so an empty bar is
     visible as an empty bar. */
  background: var(--surface-deep);
  box-shadow: inset 1px 1px 2px rgba(53, 48, 42, .14);
  margin-top: var(--space-1);
  overflow: hidden;
  border-radius: var(--radius-pill);
}
.bar .fill { height: 100%; background: var(--accent); transition: width var(--slow) var(--ease); }
.ag.dead .bar .fill { background: var(--danger); }

.bo { padding: var(--space-1) 0; font-size: var(--text-footnote); }
.bo .tier { color: var(--rose); margin-left: var(--space-2); font-weight: var(--weight-medium); }
.bo .sub { display: block; font-size: var(--text-caption); }

.log {
  position: relative; /* row offsetTop measures against the pane, for its scroll math */
  border-top: 1.5px solid var(--line-heavy);
  max-height: 78vh;
  overflow-y: auto;
  font-size: var(--text-footnote);
}
/* Three columns: the sequence in a right-aligned gutter, the kind of thing
   that happened, and then the sentence. Lines wrap rather than truncate —
   the log's whole job is to be read. */
.ev {
  display: grid;
  grid-template-columns: 3rem 5.5rem 1fr;
  gap: 0 var(--space-3);
  padding: var(--space-2) var(--space-2) var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--line);
  cursor: pointer;
  transition: background var(--fast) var(--ease);
}
.ev:hover { background: var(--surface-hover); }
.ev:active { background: var(--surface-sunken); }
.ev .seq { color: var(--ink-muted); font-size: var(--text-caption); text-align: right; }
.ev .etype { font-size: var(--text-caption); letter-spacing: .01em; }
.ev .etext { color: var(--ink-secondary); overflow-wrap: anywhere; }
/* The operative figure in each line — what was paid, burned, bid, granted. */
.ev .etext b { color: var(--ink); font-weight: var(--weight-bold); }
.ev .etext .who { color: var(--ink); font-weight: var(--weight-medium); }
/* The current row is marked three ways — wash, rule and weight — so it survives
   both a colour-blind reader and a monochrome screen. */
.ev.current { background: var(--accent-wash); box-shadow: inset 3px 0 0 var(--accent); }
.ev.current .etext { color: var(--ink); }
.ev.future { opacity: .38; }

/* --- event kinds. Five tones, every one a deep of the palette's own hues,
       and each one always accompanied by the kind in words. --- */
.t-bounty .etype     { color: var(--accent); }
.t-bid .etype        { color: var(--positive); }
.t-credit .etype     { color: var(--attention); }
.t-model_call .etype { color: var(--rose); }
.t-agent .etype      { color: var(--danger); }
.t-episode .etype    { color: var(--ink); font-weight: var(--weight-bold); }
.t-suite .etype, .t-note .etype { color: var(--ink-muted); }
/* A round boundary is the one structural event in the stream, so it draws the
   only heavy rule in the log. */
.ev.divide { border-top: 1.5px solid var(--line-heavy); }

/* ============================== town ==============================
   The town is a world being watched, not a document being read: it gets the
   whole window, because at 68rem the residents are three pixels tall. The
   page announces itself by what it contains, so the shared layout does not
   have to grow a page-name class — on the agent and bounty pages that name
   is a trace-supplied id, which has no business steering CSS. */
body:has(.townpage) main { max-width: var(--measure-wide); }
.townpage { display: grid; grid-template-columns: 1fr 19rem; gap: var(--space-6); align-items: start; }

/* No frame. The town is the page here, not a picture hung on it; a border and
   a drop shadow would put a box around the one thing that should be looked
   straight at. */
.townmap { position: relative; }
.townmap svg { width: 100%; height: auto; display: block; }

/* The hand. The map can be dragged, zoomed and asked about. Zoomed out, a
   finger moving down the page still scrolls the page; zoomed in, it looks
   around. The card and the zoom buttons are clay pieces resting on the town —
   the only slabs allowed on it, because they are what the visitor is holding. */
.townmap { touch-action: pan-y; user-select: none; -webkit-user-select: none; }
.townmap.zoomed { touch-action: none; cursor: grab; }
.townmap.zoomed:active { cursor: grabbing; }
.townmap .walker, .townmap .place, .townmap .built { cursor: pointer; outline: none; }
/* The depth the clay spec held back until the town read flat beside the
   chrome: one soft shadow under every standing thing. */
.townmap .place, .townmap .built, .townmap .walker { filter: drop-shadow(0 3px 2.5px rgba(74, 54, 34, .26)); }
.townmap .walker.chosen, .townmap .walker:focus-visible,
.townmap .place:focus-visible, .townmap .built:focus-visible {
  filter: drop-shadow(0 0 2px var(--accent)) drop-shadow(0 0 5px var(--accent-bright));
}
.mapui { position: absolute; top: var(--space-3); right: var(--space-3); display: grid; gap: var(--space-2); }
.mapui button, .mapcard-close {
  width: 2.25rem; height: 2.25rem; border: 0; border-radius: var(--radius-md);
  background: var(--surface-raised); color: var(--ink); font: inherit; font-size: var(--text-title);
  line-height: 1; cursor: pointer; box-shadow: var(--clay-raise-sm);
  transition: box-shadow var(--fast) var(--ease), transform var(--slow) var(--spring);
}
.mapui button:hover, .mapcard-close:hover { background: var(--surface-hover); }
.mapui button:active, .mapcard-close:active { transform: scale(.94); box-shadow: var(--clay-press); transition-duration: var(--fast); }
.mapcard {
  position: absolute; left: var(--space-3); bottom: var(--space-3); width: min(21rem, calc(100% - 2 * var(--space-3)));
  padding: var(--space-4) var(--space-5); border-radius: var(--radius-lg);
  background: var(--surface-raised); box-shadow: var(--clay-raise);
  user-select: text; -webkit-user-select: text;
}
.mapcard h3 { margin: 0 var(--space-6) var(--space-1) 0; font-size: var(--text-title); display: flex; align-items: center; gap: var(--space-2); }
.mapcard .dot { width: .8rem; height: .8rem; border-radius: 50%; flex: none; box-shadow: inset -1px -2px 3px rgba(53, 48, 42, .25); }
.mapcard p { margin: var(--space-2) 0 0; font-size: var(--text-label); line-height: 1.45; color: var(--ink-secondary); }
.mapcard .blurb { color: var(--ink-muted); margin-top: 0; }
.mapcard q { color: var(--ink); }
.mapcard-close { position: absolute; top: var(--space-3); right: var(--space-3); width: 1.9rem; height: 1.9rem; }
.townres { cursor: pointer; }
.townres.chosen { background: var(--accent-wash); box-shadow: var(--clay-sink); border-radius: var(--radius-sm); }

/* The outline. Every solid thing in the town is drawn with the same warm
   edge — that single stroke is most of what makes the style read as one. It
   was always the toy-village idiom, which is to say the town was clay before
   the chrome was: all the material change asks of it is a softer line, warm
   brown rather than near-black, rounded at every end. */
.townmap .ol  { stroke: #54402c; stroke-width: 1.6; stroke-linejoin: round; stroke-linecap: round; }
.townmap .ol2 { stroke: #54402c; stroke-width: 2.6; stroke-linejoin: round; stroke-linecap: round; }

/* the ground, and the earth it sits on */
.townmap .plinth-shadow { fill: rgba(74, 54, 34, .22); }
.townmap .plinth        { fill: #8a5f3a; stroke: #54402c; stroke-width: 2.6; stroke-linejoin: round; }
.townmap .plinth-top    { fill: #5d8f3c; }
.townmap .tuft   { stroke: #3f7a2c; stroke-width: 1.6; fill: none; stroke-linecap: round; }
.townmap .curb   { stroke: #a8916a; stroke-width: 2; stroke-linecap: round; }
.townmap .shadow { fill: rgba(20, 12, 6, .22); }

/* roofs */
.townmap .tileshade { stroke: rgba(59, 42, 29, .42); stroke-width: 1.8; }
.townmap .tilelip   { stroke: rgba(255, 244, 224, .3); stroke-width: 1.1; }
.townmap .ridge     { stroke: #3b2a1d; stroke-width: 4; stroke-linecap: round; }

/* A building whose resident is home turns see-through, so you can watch them
   in it. Everything below the eaves — the footing and the floor — stays
   solid. */
.townmap .shell { transition: opacity .45s; }
.townmap .shell.open { opacity: .28; }
.townmap .plank { stroke: #8a5f3a; stroke-width: 1.4; }

.townmap .spray { fill: #eef6f0; opacity: .9; }
.townmap .sign text { fill: #fff3d6; font-size: 11px; font-weight: 800; letter-spacing: .08em;
                      text-anchor: middle; }
.townmap .arealabel { fill: #fdf8ec; font-size: 10px; font-weight: 800; letter-spacing: .14em; text-anchor: middle;
                      paint-order: stroke; stroke: rgba(24, 16, 8, .6); stroke-width: 3.5px; }

/* the people: legs, arms and facing are set as attributes by the animator, so
   nothing here has to know which way anybody is walking */
.townmap .res .tag rect { fill: rgba(28, 20, 12, .72); }
.townmap .res .tag text { fill: #fff4dd; font-size: 10px; font-weight: 700; text-anchor: middle; }

/* the sky: render() reads the clock and flips these classes; everything
   below dims through dusk into night, except the lights layer, which is
   exactly what comes on instead. Night is warm here — a lamplit brown-black
   rather than the navy it used to be, which keeps the one forbidden family
   out of the picture and reads truer against a paper-white page anyway. */
.townmap .veil { pointer-events: none; transition: opacity 1.2s; opacity: 0; }
.townmap .veil-warm { fill: #ff7b3c; }
.townmap .veil-cool { fill: #241a12; }
.townmap.dusk  .veil-warm { opacity: .16; }
.townmap.dusk  .veil-cool { opacity: .16; }
.townmap.night .veil-cool { opacity: .46; }
.townmap .lights { opacity: 0; transition: opacity 1.2s; pointer-events: none; }
.townmap.dusk  .lights { opacity: .65; }
.townmap.night .lights { opacity: 1; }
.townmap .winlit    { fill: #ffd76a; }
.townmap .lamp-glow { fill: #ffca5e; opacity: .28; }
.townmap .lamp-core { fill: #ffe9ad; }
.townmap .flame     { fill: #ffb43c; animation: flick 1.6s ease-in-out infinite; }
@keyframes flick { 50% { opacity: .72; } }

/* The clock is the town's one big number. It is set like one. */
.clockface {
  margin-left: auto;
  color: var(--ink);
  font-weight: var(--weight-bold);
  font-size: var(--text-lead);
  letter-spacing: -.02em;
}

/* --- the side rail: a slab of the material standing beside the world, the
       shelf its roster and feed sit on. --- */
.townside {
  position: sticky;
  top: calc(var(--header-h) + var(--space-4));
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-lg);
}
.townside h2 { font-size: var(--text-label); margin-bottom: var(--space-1); }
.townside section + section { margin-top: var(--space-6); }

/* A resident is a name and a place, on two lines, hung off one swatch. */
.townres {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0 var(--space-2);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--line);
  font-size: var(--text-label);
}
.townres:last-child { border-bottom: 0; }
/* The swatch is a persona colour handed over from the animator, so it is the
   one place a raw value is set inline. A ring keeps a pale costume from
   disappearing into a white page. */
.townres .dot {
  grid-row: 1 / span 2;
  width: .5rem; height: .5rem;
  margin-top: .45em;
  border-radius: var(--radius-pill);
  box-shadow: 0 0 0 1px rgba(59, 42, 29, .35);
}
.townres-name { font-weight: var(--weight-medium); }
.townres .where { color: var(--ink-muted); font-size: var(--text-footnote); line-height: 1.4; }

/* The feed: the time in a quiet left column, the sentence beside it, so the
   eye reads down the events and only glances left for when. */
.feed { max-height: 46vh; overflow-y: auto; }
.feedline {
  display: grid;
  grid-template-columns: 2.75rem 1fr;
  gap: 0 var(--space-2);
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--line);
  font-size: var(--text-footnote);
  color: var(--ink-secondary);
}
.feedline:last-child { border-bottom: 0; }
.feedline .when { color: var(--ink-muted); font-size: var(--text-caption); line-height: 1.7; }
.feedline b { color: var(--ink); font-weight: var(--weight-medium); }
.feedline.met b { color: var(--accent); font-weight: var(--weight-bold); }
.feedline q { color: var(--ink-secondary); font-style: italic; quotes: "\201C" "\201D"; }
.feedline.reflected { background: var(--accent-wash); border-radius: var(--radius-sm, 6px); padding-inline: var(--space-1); }
.feedline.reflected b { color: var(--accent); }

/* The fair's money lines, in the same palette: gold for money landing where
   somebody wanted it, danger only where credits actually died, and the quiet
   ink of a notice board for the office's own doings. */
.feedline.posted { font-style: italic; }
.feedline.posted b, .feedline.shelved b, .feedline.nobids b, .feedline.voided b { color: var(--ink-secondary); }
.feedline.awarded b { color: var(--accent); font-weight: var(--weight-bold); }
.feedline.solved b { color: var(--positive); font-weight: var(--weight-bold); }
.feedline.solved { background: var(--positive-wash); border-radius: var(--radius-sm, 6px); padding-inline: var(--space-1); }
.feedline.failed b, .feedline.bankrupt b { color: var(--danger); }
.feedline.nobids, .feedline.shelved { color: var(--ink-muted); }
/* Money spent on nothing but presence: named in the accent so it reads as a
   deliberate purchase, and left unshaded because it bought no outcome. */
.feedline.stayed b { color: var(--accent); }
.feedline.bought b { color: var(--accent); }
/* A memo costs nothing and settles nothing, so it takes no accent and no wash
   — the quietest line in the feed. Its text is the agent's own, of unknown
   shape and up to 512 bytes, so it wraps on any character rather than pushing
   the column wide. */
.feedline.memo { color: var(--ink-muted); }
.feedline.memo q { overflow-wrap: anywhere; }

/* The speech bubble lives inside the town SVG, one per speaker at a time. It
   is world furniture rather than chrome, so it takes the town's own outline
   ink at a lighter weight — but on the page palette: white paper, gold-less. */
.townmap .bubble rect, .townmap .bubble path { fill: rgba(255, 255, 255, .93); stroke: #8b7a5e; stroke-width: 1.2; }
.townmap .bubble text {
  font: 600 9.5px/1 ui-rounded, -apple-system, "SF Pro Rounded", system-ui, sans-serif;
  fill: #4a4136;
}

/* ===================== empty, loading and error =====================
   Designed with the success state rather than after it. The loading and empty
   cases need no JavaScript at all: a container that has not been filled yet is
   :empty, and what it should say when empty is a property of the container. */

/* Loading. The map is the only region that is never legitimately empty once
   the animator has run, so an unfilled map is a map still arriving. */
#map:empty {
  display: block;
  min-height: 28rem;
  background: linear-gradient(100deg, var(--surface-sunken) 30%, var(--surface-solid) 50%, var(--surface-sunken) 70%);
  background-size: 220% 100%;
  animation: shimmer 1.4s linear infinite;
}
@keyframes shimmer { to { background-position: -220% 0; } }

/* Empty. A roster, feed or log with nothing in it is a real state of the
   world, not a failure — so it says what is true and what to do next. */
#roster:empty::after, #feed:empty::after, #log:empty::after, .empty {
  display: block;
  padding: var(--space-4) 0;
  color: var(--ink-muted);
  font-size: var(--text-footnote);
  text-wrap: balance;
}
#roster:empty::after { content: "no residents in this trace"; }
#feed:empty::after   { content: "nothing has happened yet — press play"; }
#log:empty::after    { content: "no events in this trace"; }

/* Error. The only failure these pages can actually suffer is the one that
   stops them being pages at all, and until now it said nothing. */
.notice {
  display: block;
  margin: var(--space-4) 0;
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid var(--ink-muted);
  background: var(--surface-raised);
  border-radius: var(--radius-md);
  box-shadow: var(--clay-raise-sm);
  font-size: var(--text-label);
  color: var(--ink-secondary);
}
.notice.error { border-left-color: var(--danger); }
.notice strong { color: var(--ink); }

/* ========================= responsive =========================
   Four widths, and a reason for each.

   ≥76rem   the design width. Town: map + a 19rem rail beside it. Replay:
            readout + log side by side.
   56–76rem the rail is still worth having but not worth 19rem, so it narrows
            rather than wrapping — a rail that wraps costs the map its height.
   <56rem   one column. The rail drops below the map, loses its dividing line
            for a top rule, and stops being sticky: a sticky panel on a short
            screen is a panel that covers the thing it describes.
   <40rem   phone. The control bar becomes two rows with the scrubber given a
            full row of its own, since a 6rem scrubber cannot be aimed. */
@media (max-width: 76rem) {
  .townpage { grid-template-columns: 1fr 16rem; gap: var(--space-5); }
}
@media (max-width: 56rem) {
  .townpage { grid-template-columns: 1fr; }
  /* Stacked under the map the rail stays a slab — the material does not
     change with the width — it just stops being sticky: a sticky panel on a
     short screen is a panel that covers the thing it describes. */
  .townside { position: static; }
  .feed { max-height: 22rem; }
}
@media (max-width: 50rem) {
  .replay { grid-template-columns: 1fr; gap: var(--space-5); }
  .state { position: static; }
  .log { max-height: 60vh; }
}
@media (max-width: 40rem) {
  main { padding: var(--space-5) var(--space-4) var(--space-7); }
  header.site { padding: 0 var(--space-4); gap: var(--space-4); }
  footer.site { padding: var(--space-4) var(--space-4) var(--space-5); }
  h1 { font-size: 1.5rem; }
  .masthead { margin-bottom: var(--space-5); }
  .stats { display: grid; grid-template-columns: 1fr 1fr; }
  .stat { padding: var(--space-2) var(--space-3); border-left: 0; }
  .stat:nth-child(odd) { padding-left: 0; }
  .controls input[type=range] { order: 1; flex-basis: 100%; min-width: 0; }
  .controls .hint { display: none; }
  .clockface { margin-left: 0; }
  table { font-size: var(--text-footnote); }
  th, td { padding-right: var(--space-3); }
  .ev { grid-template-columns: 2.5rem 1fr; padding-left: var(--space-2); }
  .ev .etype { grid-column: 2; }
  .ev .etext { grid-column: 2; }
}

/* Touch. 44px is the smallest thing a finger can reliably hit; a pointer can
   do the job at 28. Only coarse pointers pay for the extra height. */
@media (pointer: coarse) {
  .controls button, .controls select { min-height: 2.75rem; padding: var(--space-2) var(--space-4); }
  .controls input[type=range] { height: 2.75rem; }
  .ev { padding-top: var(--space-3); padding-bottom: var(--space-3); }
}

/* ===================== user preferences =====================
   Three system settings, honoured properly rather than gestured at. (The
   glass system also answered prefers-reduced-transparency; clay is opaque
   everywhere, so that block earned its deletion.) */

/* Motion. Everything animated here is ornament — a shimmer, a flicker, a
   pulsing live dot, a button that squishes — so under Reduce Motion it all
   stops, the squish transform included: a scale change is motion too. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  .townmap .flame { animation: none; }
  .badge.live::before { animation: none; }
  #map:empty { animation: none; background: var(--surface-sunken); }
  .controls button:active { transform: none; }
  .mapui button:active, .mapcard-close:active { transform: none; }
}

/* Contrast. Ink darkens, the row rules firm up, and every slab gets the edge
   the material normally does without — molded shadows are exactly the kind
   of low-contrast boundary this setting exists to overrule. */
@media (prefers-contrast: more) {
  :root {
    --ink-muted: #46403a;
    --ink-secondary: #2c2823;
    --ink: #221f1a;
    --line: #b3a88f;
    --line-firm: #8a7f68;
    --line-heavy: #665d4d;
    --edge: #665d4d;
  }
  header.site, .controls, .state, .townside, .notice { border: 1px solid var(--line-heavy); }
  /* The tinted chrome falls back to cream: more contrast means the ink
     ramp at full strength, not a coloured panel underneath it. */
  header.site, .state, .townside { background: var(--surface-raised); }
  .ev.future { opacity: .6; }
  a:hover { text-decoration-thickness: 2px; }
}

/* Forced colours (Windows high contrast). Stop painting; let the OS paint.
   Clay's shadows vanish under a forced palette, and a slab with no border
   and no shadow is invisible — so every slab takes a drawn edge here. */
@media (forced-colors: active) {
  header.site, .controls, .state, .townside, .notice, .log, .stats { border: 1px solid CanvasText; }
  #play, .controls button, .controls select { border: 1px solid CanvasText; }
  /* The hand-drawn track exists to avoid one dark fill; under a palette the OS
     chose there is no such fill to avoid, so give the slider back to the UA. */
  .controls input[type=range] { -webkit-appearance: auto; appearance: auto; }
  :focus-visible { outline: 2px solid Highlight; }
  .ev.current { box-shadow: inset 3px 0 0 Highlight; }
  .badge::before { forced-color-adjust: none; }
}
