/*
 * The world, in the Console language (decision log #25).
 *
 * Every screen on layouts/world.blade.php, signed in or not. This used to be
 * an inline <style> in that layout, which put 60KB (21KB gzipped) into every
 * page: three quarters of what a screen sent. As a file it is fetched once
 * and revalidated, and a navigate between doors keeps it (dig #2706).
 *
 * ⚠️ Plain CSS with no build step, like runner.css beside it. The layout
 * cannot put a value in here any more, so a rule that needs one from PHP
 * belongs on an element's style attribute or a data attribute instead.
 */

:root {
    --ink: #cfe6bd; --paper: #0E0F0C; --panel: #131510;
    --muted: #7d9464; --rule: #2A2D24; --accent: #9BE564;
    --foot-link: #a3c17f; /* footer links: a shade up from --muted, well short of --ink (dig #2021) */
    --amber: #E5A73C; --alert: #E85D2C;
    /* The unfilled run of a bar. It used to borrow `--rule`, which is a
       *border* colour chosen to sit a hair off the panel — so the empty
       cells all but vanished and a bar had no visible length to read a
       filled fraction against (Tom: "too close to the background
       colour"). Its own value now: clearly present, still clearly
       empty. */
    --bar-off: #4C5A3C;
}
* { box-sizing: border-box; }
html { background: var(--paper); }
body {
    margin: 0;
    background: var(--paper);
    color: var(--ink);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: .95rem;
    line-height: 1.6;
    min-height: 100dvh;
    text-shadow: 0 0 7px rgba(155, 229, 100, .2);
}
/* ⚠️ ABSOLUTE, NEVER FIXED (dig #2308). A viewport-covering fixed
   element puts mobile Safari on a compositing path where the scrolling
   layer is painted up into the status-bar strip above the layout
   viewport, so the strip shows whatever you have scrolled past while
   the sticky header, in another layer, does not reach it. Measured by
   bisection on a simulator; the full note is on `.player::after` in
   layouts/game, where Tom photographed it. A small fixed element is
   innocent, so the buttons in typewriter.css are untouched. The look is
   unchanged — `body` is `min-height: 100dvh` and grows with content, so
   the overlay still covers everything. */
body { position: relative; }
body::after {
    content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 40;
    background: repeating-linear-gradient(to bottom, transparent 0 2px, rgba(0,0,0,.16) 2px 3px);
}
/* `max()` rather than a bare value: with viewport-fit=cover a notched
   phone held sideways puts the cutout over the left gutter, and 1.15rem
   of padding is not enough to clear it. Portrait (and every desktop)
   sees the inset as 0 and the measure is unchanged. */
.wrap {
    /* 44rem, the marketing site's measure (dig #2492, Tom). The world
       sat at 40 and the site at 44, which is a difference nobody can
       name and everybody can see when they cross from one to the
       other. One measure across all three surfaces now: this, `.top`
       below, layouts/game and the error pages. */
    max-width: 44rem; margin: 0 auto;
    padding: 0 max(1.15rem, env(safe-area-inset-right)) 5rem max(1.15rem, env(safe-area-inset-left));
}
/* The site credit (dig #1263) — one muted line under the screen, never
   beside anything live, so it costs the header nothing. */
.site-foot {
    margin-top: 2.5rem; padding-top: .9rem;
    border-top: 1px solid var(--rule);
    color: var(--muted); font-size: .74rem;
}
.site-foot a {
    /* Slightly lighter than the footer's own ink, so a link reads as a
       link on a line that is deliberately quiet (dig #2021). */
    color: var(--foot-link);
    text-decoration: none;
    border-bottom: 1px dotted var(--rule);
}
/* One line, not two (dig #2021): the credit and what's new are the same
   aside. Flex rather than inline text so a narrow phone still wraps it
   cleanly instead of breaking mid-sentence. */
.site-foot { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 .4rem; }
.site-foot .made-by, .site-foot .whats-new { margin: 0; }
/* On the line it follows, not the one it leads (dig #3706): as a ::before on the credit, a wrap put the dot at the start of the second line. */
.site-foot .whats-new:has(+ .made-by)::after { content: '\00b7'; margin-left: .4rem; }
/* The status bar's colour (decision #230, dig #4079). With the bar opaque,
   iOS paints it from a fixed element touching the top of the page, so this
   strip is the header's own panel and the bar reads as part of the terminal.
   It must be a real element: the sampler does not see a ::before. Installed
   iPhone app only, so a desktop window never gets a line across the top. */
.status-tint { display: none; }
@supports (-webkit-touch-callout: none) {
    @media (display-mode: standalone) {
        .status-tint {
            display: block;
            position: fixed; top: 0; left: 0; right: 0; height: 1px;
            z-index: 30;
            background: var(--panel);
            pointer-events: none;
        }
    }
}
/* The header owned the status-bar strip until decision #230 made the bar
   opaque (#74 had the page run under it, and iOS 27 blurs that). The inset
   is 0 in a fresh install now; it stays because a phone that installed the
   app before still runs the page to the top of the screen, and without it
   the prose scrolls up behind the clock. Padding rather than a `top`
   offset, so the panel background extends into the strip. `sticky.js`
   measures this element with getBoundingClientRect(), so `--top-h` and
   `--tw-top-inset` pick the extra height up on their own. */
/* ⚠️ The same measure as `.wrap`, and the same gutters, so the bar and
   the screen under it are one column (dig #2492, Tom). Full-bleed, the
   panel and the accent rule ran the whole width of a desktop window
   while everything they were heading sat in the measure in the middle, and
   the header read as a wider thing than the game. layouts/game's
   `.masthead` has always been content-width — this is that, and the
   padding matches `.wrap`'s so the words line up down the left edge.
   A phone is narrower than the measure, so nothing there changes. */
.top {
    position: sticky; top: 0; z-index: 20;
    max-width: 44rem; margin: 0 auto;
    background: var(--panel);
    border-bottom: 1px solid var(--accent);
    padding: calc(.7rem + env(safe-area-inset-top)) max(1.15rem, env(safe-area-inset-right)) .7rem max(1.15rem, env(safe-area-inset-left));
}
/* Out of the way while you race (dig #4934): sticky.js sets `top-tucked` on
   a page with a runner panel once you scroll down, and takes it off when
   you scroll back up. The panel's half of this is in runner.css. */
.top { transition: transform .2s ease; }
.top-tucked .top { transform: translateY(-100%); }
@media (prefers-reduced-motion: reduce) { .top { transition: none; } }
.top .id { color: var(--accent); letter-spacing: .08em; font-size: .85rem; text-transform: uppercase; }
.top .id::before { content: "█ "; }
/* One line at every width (dig #2781, Tom: "top line sometimes spans two
   lines"). It only had a breakpoint, which hid the game's name under
   34rem, and a long nickname or a window a little wider than that still
   wrapped it. Now it cannot wrap: `pre` keeps the spaces round each `·`
   at the edges of the flex items, nav-overflow.js drops the game's name
   (`.id-tight`) whenever the runner's name is being cut, and the name is
   the one part that ellipsizes, so the level and the clock always show. */
.top .id { display: flex; white-space: pre; min-width: 0; }
.top .id > * { flex: 0 0 auto; }
.top .id .id-name { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.top .id.id-tight .id-game { display: none; }
.top nav { margin-top: .35rem; font-size: .8rem; display: flex; gap: .7rem; flex-wrap: wrap; }
/* ⚠️ Never shrink an item, at any width (dig #2781). This rule lived only
   under the phone breakpoint, so between 34rem and the width the whole
   bar fits in, `.nav-managed`'s nowrap squeezed the items instead:
   `[sign out]` broke across two lines, and nav-overflow.js, measuring the
   squeezed widths, concluded everything fitted and moved nothing. */
.top nav > * { flex: 0 0 auto; white-space: nowrap; }
.top nav a, .top nav button {
    color: var(--muted); text-decoration: none; background: none; border: none;
    font: inherit; cursor: pointer; padding: .18rem 0;
}
/* The sticky header overlaps anything scrolled to by an anchor or by
   scrollIntoView({block:'start'}) — reserve its height. */
[id], .readout, h2 { scroll-margin-top: 5.5rem; }
.top nav a::before, .top nav button::before { content: "["; }
.top nav a::after, .top nav button::after { content: "]"; }
.top nav a.active { color: var(--accent); font-weight: 700; }
/* The second-level strip under the three boards (decision #154). The
   same [word] idiom as the bar, one rung quieter — it is where you are
   within `out`, not one of the five doors — so it sits at the bar's own
   .8rem and never gains the bar's weight. It wraps, because three
   bracketed words fit any phone and there is nothing to measure.
   No row gap (dig #3130): each entry's own .3rem padding already puts
   about the same air between two wrapped rows as sits between two
   words, and a .7rem gap on top of it read as two separate strips. */
.out-strip { display: flex; flex-wrap: wrap; gap: 0 .7rem; font-size: .8rem; margin: 0 0 1rem; }
.out-strip a { color: var(--muted); text-decoration: none; padding: .3rem 0; }
.out-strip a::before { content: "["; }
.out-strip a::after { content: "]"; }
.out-strip a.active { color: var(--accent); font-weight: 700; }
.out-strip a:hover { color: var(--accent); }
/* The shop's shelves (dig #2346) are the same strip with buttons in
   it, because which shelf is out is component state rather than a URL
   — there is nothing to link to. Same glyphs, same colours, so the
   two read as one idiom. */
.out-strip button { font: inherit; color: var(--muted); background: none; border: 0; padding: .3rem 0; cursor: pointer; }
.out-strip button::before { content: "["; }
.out-strip button::after { content: "]"; }
.out-strip button.active { color: var(--accent); font-weight: 700; }
.out-strip button:hover { color: var(--accent); }
/* The overflow menu (dig #1152). `flex-wrap: wrap` above is the no-JS
   fallback — the nav as it was, complete and two rows tall; adding
   `.nav-managed` is nav-overflow.js saying it is now measuring, so the
   bar may keep to one line because nothing will be left off the end
   without somewhere to go. */
.top nav.nav-managed { flex-wrap: nowrap; }
.nav-more { position: relative; }
.nav-more > summary {
    list-style: none; cursor: pointer; color: var(--muted);
    padding: .18rem 0; white-space: nowrap;
}
.nav-more > summary::-webkit-details-marker { display: none; }
/* Same brackets as every other item, so it reads as one of them, plus
   the ▸/▾ this UI already uses for "there is more here" (#700, #1083). */
.nav-more > summary::before { content: "["; }
.nav-more > summary::after { content: " ▸]"; }
.nav-more[open] > summary::after { content: " ▾]"; }
.nav-more > summary:hover, .nav-more[open] > summary { color: var(--accent); }
/* The summary wears `.active` when the page you are on is one of the
   items inside — the bar must never stop naming the current screen. */
.nav-more > summary.active { color: var(--accent); font-weight: 700; }
.nav-menu {
    position: absolute; top: 100%; right: 0; z-index: 25;
    margin-top: .45rem; min-width: 9rem;
    display: flex; flex-direction: column; gap: .1rem;
    background: var(--panel); border: 1px solid var(--accent);
    box-shadow: 0 10px 24px rgba(0,0,0,.55), inset 0 0 28px rgba(155,229,100,.06);
    padding: .5rem .7rem;
}
/* Roomier than the bar: these are only ever reached by tapping, and a
   20px row in a stack of six is a mis-tap waiting to happen. */
.nav-menu > * { display: block; }
.nav-menu a, .nav-menu button { display: block; padding: .35rem 0; text-align: left; }
/* ── The three levels (decision #153) ──────────────────────────────
   Bradley, one day in: "everything is similar volume and competing for
   your attention." The type scale was flat or inverted — body at
   .95rem, an `h2` at .85, a `.readout` at .88 and a card title the same
   size as its own rows — so the one headline that says what to do now
   read at the same volume as a gloss explaining what a number means.

   So: THE CALL (`.call`, 1.15rem ink, at most one per screen, and its
   button is the only filled one) · LIVE STATE (`h2` and `.rt` at 1rem,
   above body size, for anything carrying a consequence, a countdown or
   a warning) · REFERENCE (folded away, its door at .85rem muted, its
   glosses in `.quiet`). The full rule, the budgets and what goes to
   /help instead: docs/screens.md. */
h2 { font-size: 1rem; color: var(--accent); text-transform: uppercase; letter-spacing: .14em; margin: 1.8rem 0 .6rem; }
h2::before { content: "── "; color: var(--muted); }
h2::after { content: " ──"; color: var(--muted); }
/* A division heading (decision #133). Deliberately quieter than an h2:
   the four groups are subdivisions of one board, not four boards, and
   an h2 rule between them would read as four separate sections. */
h3.division-head { font-size: .8rem; color: var(--amber); text-transform: uppercase; letter-spacing: .1em; margin: 1.4rem 0 .5rem; }
h3.division-head::before { content: "▸ "; color: var(--muted); }
.feedline { margin: 0 0 .55em; }
.feedline .t { color: var(--muted); margin-right: .55em; font-size: .8em; white-space: nowrap; }
/* A timestamped line hangs its text clear of the time (dig #3706), so
   the times are a column and every entry starts at the same x. The
   stamp is a fixed number of characters per list — `--stamp`, 9 for
   "Thu 10:48" — and `.t` is set at .8em, so its own `ch` is .8 of the
   line's: the indent and the column are the same width by arithmetic
   rather than by eye. */
.feedline.stamp { --stamp: 9; padding-left: calc(var(--stamp) * .8ch + .5em); text-indent: calc(-1 * (var(--stamp) * .8ch + .5em)); }
.feedline.stamp.stamp-date { --stamp: 12; }
.feedline.stamp.stamp-day { --stamp: 3; }
.feedline.stamp > .t { display: inline-block; text-indent: 0; min-width: calc(var(--stamp) * 1ch); margin-right: .625em; }
.feedline.warn, .feedline.skip { color: var(--amber); }
.feedline.ground { color: var(--alert); }
.feedline.auto { color: var(--muted); }
/* A name in a feed line that has a page behind it (dig #2265).
   Underlined rather than recoloured: a feed line already carries its
   own colour by kind (.ground is alert, .warn amber), so painting the
   link accent would have one sentence in two colours for a reason no
   player could name. The dotted rule is the `.kv` label idiom from
   dig #524 — it says "this is a different kind of thing" without
   saying it loudly. */
/* Generalised at dig #2305: the same grammar now says "this name is a
   door" wherever one is printed — a readout row on the standings, a
   dossier, the career log — so a name that links reads the same on
   every screen instead of being accent here and inherit there. The
   `.rec` half is written by the record-link component and nothing
   else.

   ⚠️ That name is spelled without its angle brackets on purpose.
   A Blade comment is stripped before component tags are compiled; a
   CSS comment inside a Blade file is not a comment to Blade at all,
   so an `x-` tag written in one is COMPILED: an unclosed opening tag
   here took the whole layout out with "unexpected end of file,
   expecting elseif" reported from a line 700 rows further down.
   Decision #70's trap, in a place it had not been before. */
.feedline a, a.rec { color: inherit; text-decoration: underline; text-decoration-style: dotted; text-underline-offset: .22em; }
/* The rule says "you can press this" and nothing more, so it is drawn a
   step quieter than the word it is under (dig #3706): a board of ten
   names was ten bright dotted rules competing with the names. Hover and
   focus still turn it solid accent. */
.feedline a, a.rec, a.term { text-decoration-color: var(--bar-off); }
.feedline a:hover, .feedline a:focus-visible, a.rec:hover, a.rec:focus-visible, a.term:hover, a.term:focus-visible { text-decoration-color: currentColor; }
.feedline a:hover, .feedline a:focus-visible, a.rec:hover, a.rec:focus-visible { color: var(--accent); text-decoration-style: solid; }
/* The other half of the same component: a row whose VALUE is the way
   out ("the record →"), rather than a name you happen to be able to
   press. Accent, because it is a thing to do. */
a.rec-cta { color: var(--accent); }
.readout {
    background: var(--panel); border: 1px solid var(--rule);
    box-shadow: inset 0 0 28px rgba(155,229,100,.06);
    padding: .7rem .9rem; margin: 1rem 0; font-size: .95rem;
}
/* A card title is a heading, so it is a heading's size (decision
   #153). It used to inherit the readout's own .88rem, which made the
   title of a block and the rows inside it indistinguishable by
   anything but colour. */
.readout .rt { color: var(--accent); font-size: 1rem; margin-bottom: .4rem; }
/* The till (dig #501): a readout that stays with you. The shop's
   balance sat at the top of the page above every heading and every buy
   button, so on a phone you had to scroll back up to find out whether
   you could afford the thing you were looking at — and a purchase
   confirmed itself by silently changing that same off-screen number.
   Sticks to the *measured* header height dig #495 publishes, not a
   guess at it, and below the header's own z-index. */
.till {
    position: sticky; top: var(--top-h, 4.2rem); z-index: 15;
    border-left: 2px solid var(--accent);
}
.till-note { margin: .4rem 0 0; color: var(--amber); }
/* The label/value row, used ~146 times across the world screens.
   `min-width: 0` is the load-bearing declaration: a flex item defaults
   to `min-width: auto`, so neither column could shrink below its own
   longest word and BOTH collapsed to min-content and wrapped — the
   "poles / Second-hand poles · 0 / 25 / outings" mess on a 375px phone.
   With shrinking allowed and `flex-wrap`, a pair that fits stays on one
   line (label left, value right, as before) and a pair that doesn't puts
   the value on its own full-width line instead of squashing both. That
   also gives the 21-glyph stat bars room: they're a single unbreakable
   token that previously cleared a 375px readout by 12px. */
/* ⚠️ No value is ever pushed to the right edge (dig #3706). This was
   `space-between`, so a value that fitted beside its label sat against
   the right edge and one that didn't dropped to the LEFT edge of a new
   line: in one readout the eye went right, left, left, right, left
   (Tom: "there are times where the content just isn't as readable as
   it could be"). Now the label is a column of its own width, a value
   that fits starts at the same x every time, and one that doesn't
   drops under its label exactly as before. Both edges a value can sit
   on are on the left. */
.kv {
    display: flex; flex-wrap: wrap; justify-content: flex-start;
    align-items: baseline; gap: 0 .8rem; margin: .18rem 0;
}
.kv > * { min-width: 0; overflow-wrap: break-word; }
/* The column. A minimum rather than a width: a label longer than it
   takes the room it needs instead of breaking across two lines. */
.kv > .k { flex: 0 0 auto; min-width: 7.6rem; max-width: 100%; }
/* The label reads as a *column*, not as dimmer text that happens to be
   on the left (playtest, Tom: "can we make the column on the left stand
   out a bit more — maybe underline"). Muted colour alone stopped
   carrying that once #59 let a long value wrap onto its own line.

   ⚠️ It is NOT underlined (decision #208). It was, with a solid rule,
   and on a phone a solid rule and the dotted one that means a link
   (`.feedline a`, `a.rec`) are the same line: a survey player pressed
   "load" and nothing happened. Case, size and spacing make it the
   column's heading now, the way `.top .id` and `h2` are headings, and
   an underline in this column means one thing: `a.term`, below. */
.kv .k {
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: .08em;
    font-size: .78rem;
}
/* A game term in the label column that has an answer on /help
   (decision #208): the link grammar the rest of the game already uses,
   so the word you can press is the one with a line under it. */
a.term { color: inherit; text-decoration: underline; text-decoration-style: dotted; text-underline-offset: .22em; text-decoration-color: var(--bar-off); }
a.term:hover, a.term:focus-visible { color: var(--accent); text-decoration-style: solid; }
/* A value's parts, on their own line under it (dig #2112). A full-basis
   third child breaks the line deliberately and keeps the figure alone
   on the first one. Indented to the value column rather than right-
   aligned since dig #3706, for `.kv`'s own reason above. */
.kv .kv-more { flex-basis: 100%; padding-left: calc(7.6rem + .8rem); }
/* A checklist (dig #4449): the box hangs in a column of its own width
   and everything after it starts at one x. It was a `.kv` row, whose
   7.6rem label column is far wider than "[ ]", so every step's text
   wrapped under its box and the list read box, line, box, line. The
   gloss goes on its own line under the thing to do, not run on after
   the arrow. */
.checklist { list-style: none; margin: .5rem 0 0; padding: 0; }
.checklist li { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 0 .7rem; margin: 0 0 .65rem; }
.checklist li:last-child { margin-bottom: 0; }
.checklist .box { color: var(--muted); }
.checklist a { color: var(--accent); }
.checklist .quiet { display: block; margin-top: .1rem; }
.checklist li.done { color: var(--muted); }
/* A comparison table, where a `.kv` row was carrying two runners'
   figures in one string (dig #2431). The head-to-head printed
   "66:47 v 72:51 · −6:04" as a single value and nothing in it said
   which figure was yours — Tom: "can we tabularise the you/them
   columns with top labels so you can see which is which?" A heading
   row says it once at the top, and a grid lines every figure up in
   its own column so the eye can run down one runner instead of
   re-parsing each row.

   `auto` on the three figure columns rather than a fixed `ch` width:
   a grid column is as wide as the widest cell in it, which is what
   squares a monospace table up, and a three-digit-minute time widens
   only its own column instead of everything. The label column takes
   `minmax(0, 1fr)` for `.kv`'s reason — a flex/grid item defaults to
   min-content, so without it a long piece of ground refuses to wrap
   and pushes the figures off a phone. */
/* ⚠️ `.tbl` is the grammar and the COLUMNS are the caller's (dig #2432).
   The head-to-head was the first table on the dossier and the rest of
   that page was a stack of one-item `.readout` boxes — four things
   somebody had run cost four bordered boxes and most of a screen, and
   the date sat on a `kv` line of its own under each. Tom: "maybe these
   could be consolidated to a single table, one line each?" So the
   shared part lives here and each table names its own widths below. */
.tbl {
    display: grid;
    grid-template-columns: minmax(0, 1fr) repeat(var(--figures, 3), auto);
    column-gap: .8rem; row-gap: .2rem; align-items: baseline;
}
.tbl > * { min-width: 0; overflow-wrap: break-word; }
.tbl .num { text-align: right; white-space: nowrap; }
/* The heading row borrows `.kv .k`'s grammar on purpose: muted and
   set in capitals is what this UI means by "a column label, not a
   value" (decision #208 took the rule off both together). */
.tbl .h {
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: .08em;
    font-size: .78rem;
}
/* Your own column is marked the way you are marked on every other
   board in the game (`.ground-you`), so which one is which is said
   by colour as well as by the word. */
.tbl .h-you { color: var(--accent); }
/* A time nobody has posted. Muted, and NOT `.warn`: one of you not
   having run a hill is not a result, and the old row painted the
   whole value amber with a ▲ on it, which read as losing. */
.tbl .none { color: var(--muted); }
.bar { color: var(--accent); }
.bar .off { color: var(--bar-off); }
/* Where a bar is heading, against where it is (dig #2508). `.due` is
   work committed and not yet charged to the body; `.shedding` is the
   tail of the filled run that tonight or the walk home takes back off.
   Both are carried by their glyph as well as their colour (▓ and ▒
   against █ and ░), because a bar in the red is already amber and the
   coming run has to stay legible on top of it. */
.bar .due { color: var(--amber); }
.bar .shedding { color: var(--muted); }
/* A bar and its number, as one value (dig #524 — see components/meter).
   The number is right-aligned in a fixed character column so the bars
   that follow it all start and end at the same x; before this each bar
   began wherever its own row's digits had left off. `ch` because this
   is a monospace grid — the column is exactly as wide as the longest
   value it has to hold, and a value that overflows it pushes only its
   own bar, never anyone else's. */
/* Wrapping matters here for the same reason it does on `.kv` itself: a
   20-glyph bar is a single unbreakable token, so it fits on a line of
   its own and on no line that is also carrying something else. Putting
   the value on the bar's line is what made them align — so on a narrow
   screen the bar drops to the next line rather than overflowing, and
   because the whole meter is right-aligned the bars still line up down
   the page.

   WHEN it drops is dig #611. Tom, on the eight attribute rows: "I feel
   like these would all fit on one line on my device." Five did and
   three didn't, and the difference was nothing but the length of the
   label — a meter row is `label + gap + 5ch + gap + 20 cells`, which
   came to about six pixels more than his readout was wide, so the rows
   with a ten-letter label (CONFIDENCE, DESCENDING, REPUTATION) wrapped
   and the eight- and nine-letter ones held. A readout that wraps three
   rows out of eight reads as broken; the two gaps below are what buys
   those pixels back, measured rather than guessed (a 10ch label needs
   312px of row, against 315 on a 360px phone and 330 on a 375px one).

   Below that the bar genuinely cannot share a line, so every meter in
   the readout drops together instead of a ragged mix — the row is its
   own container query, which keys off the width the row actually has
   rather than the viewport's, and 19.5rem IS the requirement above
   said out loud. It holds the alignment either way: on one line the
   bars share a right edge, on two the values do as well. */
.meter {
    display: inline-flex; flex-wrap: wrap; justify-content: flex-end;
    align-items: baseline; gap: 0 .45rem; max-width: 100%;
}
.kv.meter-row { gap: 0 .5rem; container-type: inline-size; }
/* ⚠️ The one row that still pushes its value right (dig #3706). A bar is
   a figure in a column, and the bars line up down the readout by sharing
   a right edge; the 19.5rem below was measured against a label as long
   as its own word, so it must not inherit `.kv`'s label column. */
.kv.meter-row { justify-content: space-between; }
.kv.meter-row > .k { min-width: 0; }
@container (max-width: 19.5rem) {
    .meter { flex-basis: 100%; }
}
.meter-v { min-width: 5ch; text-align: right; flex: none; }
/* A gloss riding in a value: the unit a bar is counting in, or what a
   state word means while it is still true. Subordinate to the figure
   it qualifies, never a row of its own — see components/meter. */
.muted-note { color: var(--muted); font-size: .82rem; }
.muted-note a { color: var(--muted); }
/* The Home Screen nudge (dig #2268). The gap under it is not cosmetic:
   its own action is a .prompt-list button, and on the brief the very
   next thing is "start the day". Flush against each other those read
   as one list of two equal choices, which is dig #610's fault exactly
   — one of them installs the game and the other leaves the screen. */
.install-block { margin: 1.1rem 0 1.5rem; }
.install-line .inline-act { margin-left: .4em; }
/* A small action sitting inside a .kv value, where .prompt-list's rules
   don't reach — without this it renders in the UA's system font. */
.inline-act {
    font: inherit; color: var(--accent); background: transparent;
    border: 1px solid var(--rule); cursor: pointer; padding: .2rem .55rem;
}
.inline-act:hover { border-color: var(--accent); }
/* The chosen one of a row of them (a plan on /game/races, /game/fkt). Every
   .inline-act is already accent-coloured, so colour alone cannot say which is
   picked: it inverts to a filled block, the terminal's own "selected". */
.inline-act.is-on { background: var(--accent); border-color: var(--accent); color: var(--paper); font-weight: 700; }
/* The test bench's folded-away tools (dig #700). The first <details> in
   the world UI, so like the first <select> (decision #38) it is stripped
   of the UA's own affordance and given the terminal's: a ▸ that turns,
   painted in the marker rather than added as an element, so the summary
   stays one selectable line. Muted and quiet by default — it is a tool
   sitting under a story, and it must not compete with the story. */
.bench-tools { margin: 1.4rem 0 .4rem; border-top: 1px dashed var(--rule); padding-top: .6rem; }
.bench-tools > summary {
    cursor: pointer; color: var(--muted); font-size: .74rem;
    list-style: none; padding: .35rem 0; user-select: none;
}
.bench-tools > summary::-webkit-details-marker { display: none; }
.bench-tools > summary::before { content: '▸ '; color: var(--accent); }
.bench-tools[open] > summary::before { content: '▾ '; }
.bench-tools > summary:hover { color: var(--accent); }
/* One place on the banner review page (dig #1337).
   ⚠️ `content-visibility` is what makes the page affordable. A banner
   is ~1,700 <i> elements and the 65 has twenty of them, so a plain
   scroll would lay out and paint ~34,000 elements up front; this way
   the browser does the ones on screen and remembers the height of the
   rest. `auto 460px` is a first guess only — the real size replaces it
   the moment a plate has been seen once. */
.plate {
    margin: 2.2rem 0; padding-top: .9rem; border-top: 1px solid var(--rule);
    content-visibility: auto; contain-intrinsic-size: auto 460px;
}
.plate-head { display: flex; gap: .6rem; align-items: baseline; flex-wrap: wrap; }
/* The slug is the loudest thing in the plate because it is the only
   word on it that is actionable — see the class note on BannerBench. */
.plate-slug {
    color: var(--accent); font-size: 1rem; letter-spacing: .04em;
    user-select: all;
}
.plate-where { color: var(--ink); font-size: .82rem; margin: .15rem 0 .2rem; }
.plate .readout { margin: .9rem 0 0; }
/* The hub's folded reference blocks (dig #1083). Tom: "it's very busy
   and going into the game you don't always know what you should do
   next." The forecast, the kit bill and the money are all things you
   look up when you have a reason to; none of them is the reason you
   opened the app. So they keep the bench's ▸/▾ affordance — one idiom
   for "there is more here" — but they are a SECTION rather than a
   tool, so the summary is an <h2> in everything but the element name.
   The marker sits on the summary and the heading rules are dropped
   from the h2 inside it, or the row reads `▸ ── FORECAST ──`. */
.fold { margin: 1.8rem 0 0; }
/* ⚠️ A DOOR, NOT A HEADING (decision #153). This was deliberately
   byte-identical to an `h2` — same size, same accent, same tracking —
   on the reasoning that a fold is a section. It is not: an `h2` names
   live state you are meant to read, and this names a drawer of
   reference you may open. So it sits visibly BELOW an h2 now, and the
   summary carries the grammar `NAME · state or count` so the drawer
   says what is inside it without being opened. */
.fold > summary {
    cursor: pointer; list-style: none; user-select: none;
    font-size: .85rem; color: var(--muted);
    text-transform: uppercase; letter-spacing: .14em; padding: .2rem 0;
}
.fold > summary:hover, .fold > summary:focus-visible { color: var(--accent); }
/* The tail of that grammar: what is in the drawer, said in the drawer's
   own register so the name stays the thing you read first. */
.fold > summary .fold-count { text-transform: none; letter-spacing: .04em; }
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary::before { content: '▸ '; color: var(--muted); letter-spacing: 0; }
.fold[open] > summary::before { content: '▾ '; }
.fold > summary:hover::before { color: var(--accent); }
/* A fold nested inside one row of a board (the ground log / hut book,
   dig #1317) is a detail of that row rather than a section of the
   page, so it does not want the section's clear air above it. */
.readout > .fold { margin: .9rem 0 0; }
/* The one question the hub answers (dig #1083). It used to carry an
   amber edge down the left; dropped at dig #1175 (Tom's call) — the
   heading already says NOW, and the rule only pushed the block out of
   line with everything else on the page. */
.now { margin: .2rem 0 0; }
.now .rt { font-size: 1rem; }
/* THE CALL (decision #153): the one thing this screen is asking, and
   the one button that answers it. Biggest type on the page, and in
   `--ink` rather than accent, because the accent is already doing the
   work of marking every heading and every link — a headline that is
   merely one more accent line is a headline at the same volume as the
   rest of the screen, which is the complaint.

   `.rt.call` repeats the size on purpose: `.now .rt` has two
   selectors' worth of specificity, so a bare `.call` would lose to it
   on exactly the element the class exists for. */
.call { font-size: 1.15rem; color: var(--ink); line-height: 1.45; }
.rt.call { font-size: 1.15rem; }
/* The only filled button on a screen. A `.prompt-list` button is an
   outline; filling exactly one of them is what makes "do this" read as
   a different kind of thing from "or you could do this", without a
   second shape, a second colour or a word of explanation. A screen that
   is a list of equal choices has no call and so has no filled button. */
.prompt-list .call, .prompt-list button.call, .prompt-list a.call {
    background: var(--accent); color: var(--paper);
    border-color: var(--accent); font-size: 1rem;
    text-shadow: none;
}
.prompt-list .call::before { color: var(--paper); }
.prompt-list .call:hover, .prompt-list .call:focus-visible {
    background: transparent; color: var(--accent);
}
.prompt-list .call:hover::before, .prompt-list .call:focus-visible::before { color: var(--accent); }
/* A lane's second line hangs under its own first one rather than
   running back under the label (dig #3706): the label is a column, so
   a wrapped line that starts at the left edge reads as a new lane. */
.now .lane { margin: 0 0 .4em; padding-left: calc(5.5rem + .5em); text-indent: calc(-1 * (5.5rem + .5em)); }
.now .lane .k {
    color: var(--muted); display: inline-block; text-indent: 0;
    min-width: 5.5rem; margin-right: .5em;
}
.now .lane.shut { color: var(--muted); }
/* The test bench's debugging paste (dig #700). It is a fixed-width
   table of node ids and choice keys, so it must NOT wrap — a wrapped
   column stops lining up and the whole point of the block is that you
   can read down it. Scrolls sideways instead, which on a phone is the
   right trade for a thing you are about to copy wholesale anyway. */
.share-text {
    font: inherit; font-size: .68rem; line-height: 1.5; color: var(--muted);
    background: var(--bg); border: 1px solid var(--rule);
    padding: .6rem .7rem; margin: .6rem 0;
    overflow-x: auto; white-space: pre; -webkit-overflow-scrolling: touch;
    max-height: 22rem; overflow-y: auto;
}
.prompt-list { margin: .6rem 0; }
.prompt-list button, .prompt-list a {
    display: block; width: 100%; text-align: left;
    font: inherit; color: var(--ink); background: transparent;
    border: 1px solid var(--muted); padding: .7rem .9rem; margin: .45rem 0;
    cursor: pointer; text-decoration: none;
    text-shadow: 0 0 7px rgba(155, 229, 100, .2);
}
.prompt-list button::before, .prompt-list a::before { content: "> "; color: var(--accent); }
.prompt-list button:hover, .prompt-list a:hover { border-color: var(--accent); background: var(--panel); }
.prompt-list button:disabled { opacity: .5; cursor: default; }
/* The ground board's one control per piece of ground (dig #1203). Tom:
   "there are a LOT of buttons, 4 under each… could we hide them behind
   a run it button?" The four approaches (decision #88) are a real
   choice and they stay four, but they are a step you take AFTER you
   have picked the hill, so they go behind this.

   It is a <details> summary carrying the ▸/▾ the fold and the bench
   tools already use, dressed as the .prompt-list button it stands in
   for: the same box, the same hover, the marker where the "> " would
   be. A section heading (.fold) would have been the wrong dress — this
   is the thing you press to go running. */
.run-it { margin: .6rem 0; }
.run-it > summary {
    display: block; width: 100%; list-style: none; user-select: none;
    font: inherit; color: var(--ink); background: transparent;
    border: 1px solid var(--muted); padding: .7rem .9rem;
    cursor: pointer; text-shadow: 0 0 7px rgba(155, 229, 100, .2);
}
.run-it > summary::-webkit-details-marker { display: none; }
.run-it > summary::before { content: '▸ '; color: var(--accent); }
.run-it[open] > summary::before { content: '▾ '; }
.run-it > summary:hover { border-color: var(--accent); background: var(--panel); }
.run-it .muted-note { margin: .6rem 0 0; }
.field { margin: .9rem 0; }
.field label { display: block; color: var(--muted); font-size: .78rem; text-transform: uppercase; letter-spacing: .12em; margin-bottom: .3rem; }
.field input {
    width: 100%; font: inherit; color: var(--ink);
    background: var(--panel); border: 1px solid var(--muted);
    padding: .6rem .8rem;
}
.field input:focus { outline: none; border-color: var(--accent); }
/* ⚠️ A placeholder is not a value, and until dig #1037 nothing said so:
   there was no `::placeholder` rule anywhere in the app, so a hint
   inherited its input's `--ink` and rendered exactly as brightly as
   something typed. Tom read a hard-coded `26:53` in an empty par box
   on `/game/admin/ground` as the number that was about to be saved —
   and it was another piece of ground's real par, two rows up the same
   queue. `--muted` is the register this UI already reserves for
   saying-not-being (`.kv .k`, every `.field-note`), so a hint belongs
   in it. `opacity: 1` is load-bearing rather than tidy: Firefox dims
   placeholders by default, and dimming a colour already this quiet is
   how a hint becomes unreadable instead of merely quieter. Written
   against the elements, not `.field`, so a box outside a `.field`
   (the search on the players queue) cannot miss it. */
input::placeholder, textarea::placeholder { color: var(--muted); opacity: 1; }
/* The first <select> in the world UI (dig #610). A stock control is
   the one thing on the page rendered in the UA's own font and colours,
   so it is stripped back to the same box `.field input` draws and given
   its own chevron — two accent triangles, since a background-image is
   the only part of a select we can paint. `option` needs its colours
   set explicitly: on the desktop platforms that render the popup
   themselves, the control's colours are not inherited. */
.field select {
    width: 100%; font: inherit; color: var(--ink);
    background-color: var(--panel); border: 1px solid var(--muted);
    border-radius: 0; padding: .65rem 2.1rem .65rem .8rem;
    -webkit-appearance: none; -moz-appearance: none; appearance: none;
    background-image:
        linear-gradient(45deg, transparent 50%, var(--accent) 50%),
        linear-gradient(135deg, var(--accent) 50%, transparent 50%);
    background-position: calc(100% - 1.15rem) 55%, calc(100% - .8rem) 55%;
    background-size: .35rem .35rem, .35rem .35rem;
    background-repeat: no-repeat;
}
.field select:focus { outline: none; border-color: var(--accent); }
.field select option { background: var(--paper); color: var(--ink); }
/* ⚠️ 16px is a threshold, not a taste (dig #2420). Mobile Safari zooms
   the page in when you focus a text control whose font is under 16px,
   and it does not zoom back out when you leave it, so the screen you
   came back to is the wrong size and you have to pinch it straight
   yourself. Every box in this app was 15.2px, because `font: inherit`
   takes `body`'s .95rem.

   ⚠️ The fix people reach for instead is `user-scalable=no` on the
   viewport, and it is the wrong one twice over: Safari has ignored it
   since iOS 10, so it does not even do this, and everywhere it IS
   honoured it takes pinch-zoom away from somebody reading a par time
   on a phone in the sun. WCAG 1.4.4. The viewport line stays scalable.

   `max()` rather than a bare 16px so that somebody who has enlarged
   their browser's base font gets the larger of the two and the box is
   never smaller than the prose around it. Written against the elements
   rather than `.field`, for dig #1037's reason: a box outside a
   `.field` cannot miss it. Range, radio and checkbox are left out —
   the rule is about typing, and they do not trigger it.

   ⚠️ It must sit AFTER every `font: inherit` above: the shorthand
   resets font-size, and these selectors weigh the same, so order is
   the only thing deciding it. */
input[type="text"], input[type="email"], input[type="number"],
input[type="password"], input[type="search"], input[type="tel"],
input[type="url"], input[type="date"], input[type="time"],
input:not([type]), textarea, select { font-size: max(16px, 1rem); }
/* The first <input type="range"> in the world UI (dig #936), stripped
   for the same reason dig #610 stripped the first <select>: a stock
   control is the one thing on the page rendered in the UA's own colours,
   and a bright blue iOS slider on a phosphor terminal reads as a piece
   of somebody else's software. The track is the box `.field input`
   draws; the thumb is an accent block rather than a circle, because
   nothing else in this UI is round.

   ⚠️ The two thumb selectors CANNOT be combined into one rule — an
   unrecognised selector invalidates the whole declaration block, so
   `::-webkit-slider-thumb, ::-moz-range-thumb { }` silently styles
   neither. They are separate on purpose. */
.dial { margin: .55rem 0; }
.dial label {
    display: flex; justify-content: space-between; gap: .5rem;
    text-transform: none; letter-spacing: 0; font-size: .85rem;
    color: var(--ink); margin-bottom: .3rem;
}
.dial .dial-read { color: var(--accent); }
.dial input[type="range"] {
    -webkit-appearance: none; appearance: none;
    width: 100%; height: 1.4rem; background: transparent;
    border: 0; padding: 0; margin: 0; cursor: pointer;
}
.dial input[type="range"]::-webkit-slider-runnable-track {
    height: .55rem; background: var(--panel); border: 1px solid var(--muted);
}
.dial input[type="range"]::-moz-range-track {
    height: .55rem; background: var(--panel); border: 1px solid var(--muted);
}
.dial input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none; appearance: none;
    width: .8rem; height: 1.15rem; background: var(--accent);
    border: 0; border-radius: 0; margin-top: -.35rem;
}
.dial input[type="range"]::-moz-range-thumb {
    width: .8rem; height: 1.15rem; background: var(--accent);
    border: 0; border-radius: 0;
}
.dial input[type="range"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.field-note { color: var(--muted); font-size: .8rem; margin: .35rem 0 0; }
.error { color: var(--alert); font-size: .8rem; margin-top: .25rem; }
.error-summary { border: 1px solid var(--alert); padding: .6rem .8rem; margin: 1rem 0; }
.error-summary .rt { color: var(--alert); margin-bottom: .3rem; letter-spacing: .1em; }
.choice-card { border: 1px solid var(--rule); padding: .8rem .9rem; margin: .6rem 0; cursor: pointer; display: block; }
.choice-card:hover { border-color: var(--muted); }
.choice-card.selected { border-color: var(--accent); background: var(--panel); }
.choice-card .cb { color: var(--accent); letter-spacing: .06em; }
.choice-card .blurb { color: var(--muted); font-size: .82rem; margin-top: .25rem; }
.choice-card input { position: absolute; opacity: 0; }
/* The class picker (dig #1053). Five cards, each of which used to carry
   its blurb plus three full ability rungs — ~2,400 characters of prose,
   about a screenful per class on a phone, with no way to compare them.
   A shut card is now three short lines: what it's strong and weak at,
   the flavour, and the names of the three rungs it's offering. Opening
   one spells the rungs out. */
/* A fixed label column, so the qualities line up down the whole picker
   and five cards can be read as one table rather than five paragraphs. */
.class-card .signature {
    display: grid; grid-template-columns: 4.2rem 1fr;
    gap: .1rem .3rem; font-size: .78rem; margin-top: .35rem;
}
.class-card .sig { min-width: 0; overflow-wrap: break-word; }
.class-card .sig.strong { color: var(--accent); }
.class-card .sig.weak { color: var(--amber); }
/* The label carries the meaning, so it is a word rather than a glyph —
   an arrow beside a stat name reads as "going up" on a career screen. */
.class-card .signature .lab {
    color: var(--muted); text-transform: uppercase;
    letter-spacing: .1em; font-size: .74rem; line-height: 1.6;
}
/* The rungs delimit themselves off their own level marker rather than off
   a separator: three items wrapping 2-and-1 strands a trailing middot
   at the end of the first line. */
.class-card .rungs { display: flex; flex-wrap: wrap; gap: .15rem 1rem; margin-top: .4rem; }
.class-card .rung-brief { white-space: nowrap; }
.class-card .rung { margin-top: .3rem; }
.class-card .lv { color: var(--accent); letter-spacing: .06em; }
/* The job picker's four axes (decision #49), on the same grid as the
   class signature above — two pairs to a row where there is width for
   it, one where there isn't. A run-on sentence of four quantities is
   read one job at a time; a column is read seven at once. */
.job-card .axes {
    display: grid; grid-template-columns: 4.2rem 1fr;
    gap: .1rem .3rem; font-size: .78rem; margin-top: .35rem;
}
@media (min-width: 34rem) {
    .job-card .axes { grid-template-columns: 4.2rem 1fr 4.2rem 1fr; column-gap: .5rem; }
}
.job-card .axes .lab {
    color: var(--muted); text-transform: uppercase;
    letter-spacing: .1em; font-size: .74rem; line-height: 1.6;
}
/* The axis that decides whether you can train after work — #49's own
   finding that hours are only ever felt through this threshold. */
.job-card .keeps-pm { color: var(--accent); }
.job-card .loses-pm { color: var(--amber); }
.countdown { color: var(--amber); }
/* "You are out there" (dig #494) — the way back to a live race or FKT
   route from any screen. It lives inside the sticky header so it comes
   down the page with you, and it is the only thing in the world UI that
   is allowed to interrupt: it renders on nothing but the days you are
   actually on a start line or up a hill. */
/* Left-aligned rather than space-between: on a phone the pair wraps,
   and space-between then flings the label to the right edge on its own
   line, which reads as two unrelated things rather than one sentence. */
.outing-strip {
    display: flex; flex-wrap: wrap; align-items: baseline;
    gap: 0 1.15rem; margin-top: .45rem; padding: .25rem .5rem;
    border: 1px solid var(--amber); color: var(--amber);
    text-decoration: none; font-size: .78rem; line-height: 1.45;
}
.outing-strip > * { min-width: 0; overflow-wrap: break-word; }
.outing-strip::before { content: "▸ "; color: var(--muted); }
.outing-strip:hover { background: var(--panel); }
.outing-what { text-transform: uppercase; letter-spacing: .08em; }
/* A call is actually standing in front of you — the one state that has
   a clock on it, since a decision window on the shared clock is minutes
   (#68) and a route's is the rest of the day (#54). */
.outing-strip.waiting { border-color: var(--alert); color: var(--alert); }
.outing-strip.waiting::before { content: "> "; color: var(--alert); }
.outing-strip.waiting .cursor { color: var(--alert); }
.dresser { display: block; margin: 1rem 0; }
[x-cloak] { display: none !important; }
.choice-card .tag {
    color: var(--paper); background: var(--muted); font-size: .7rem;
    text-transform: uppercase; letter-spacing: .1em; padding: .05rem .35rem;
    border-radius: 2px; vertical-align: middle;
}
.dresser-controls { flex: 1; min-width: 14rem; }
.dresser-row { display: flex; align-items: center; gap: .6rem; margin: .35rem 0; }
/* Creation, and the shop's shoe colourway dial (decision #84). The
   locked wardrobe on the runner sheet is a `.kv` readout rather than a
   dresser (dig #526), so the hidden spacer that used to keep a
   dial-less row's left edge straight is gone with it — every row that
   still uses this has a live dial on both sides. */
.dresser-row button {
    font: inherit; color: var(--accent); background: transparent;
    border: 1px solid var(--rule); cursor: pointer; padding: .2rem .6rem;
}
.dresser-row button:hover { border-color: var(--accent); }
.dresser-label { flex: 1; font-size: .85rem; color: var(--muted); }
.status-flash { color: var(--accent); margin: .8rem 0; }
.status-flash::before { content: "✓ "; }
.cursor { display: inline-block; width: .58em; color: var(--accent); animation: blink 1.06s steps(2, start) infinite; }
@keyframes blink { 50% { opacity: 0; } }
.aside-note { color: var(--muted); font-size: .74rem; border: 1px dashed var(--rule); padding: .5rem .7rem; margin: 1.1rem 0; }
/* The Home Screen icon preview (dig #496). Shown at the size iOS
   actually draws it, because the whole question is whether the runner
   reads at a glance on a phone. image-rendering matters: the tile is a
   pixel grid, and a browser resample would smudge it into something
   that is no longer this game's artwork. */
.home-icon { display: flex; align-items: center; gap: .9rem; margin: .6rem 0 .2rem; }
.home-icon img { width: 60px; height: 60px; border-radius: 13px; image-rendering: pixelated; border: 1px solid var(--rule); }
.home-icon .home-icon-note { color: var(--muted); font-size: .78rem; }
/* The weather feed's CC BY credit (decision #61) — a licence
   condition, so it is quiet but never hidden. It sits directly under
   the Forecast readout it is crediting (dig #702), tucked against the
   box's right edge at the smallest size anything in the world UI is
   set in: present, and never competing with the numbers above it. */
.weather-credit { color: var(--muted); font-size: .64rem; margin: .2rem 0 1.1rem; text-align: right; opacity: .75; }
.weather-credit a { color: inherit; text-decoration: none; border-bottom: 1px dotted var(--rule); }
/* Segments & crowns (D3): crown states + condition/injury readout. */
.ground-you { color: var(--accent); }
.ground-rival { color: var(--amber); }
.ground-open { color: var(--muted); }
/* The board's "new" mark and the browse map's landing flash
   (excavation #645, sub-dig 9). A tap on the map scrolls the row into
   view and lights it for a moment — never a colour used anywhere else
   on this board, so it reads as "here it is" and nothing more. */
.ground-new-badge {
    margin-left: .4rem;
    padding: 0 .3rem;
    border: 1px solid var(--accent);
    color: var(--accent);
    font-size: .72rem;
    text-transform: uppercase;
    letter-spacing: .06em;
    vertical-align: middle;
}
.readout[id^="ground-"] { transition: box-shadow .5s ease, border-color .5s ease; }
.ground-row--lit { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.spec { color: var(--muted); font-size: .78rem; margin-left: .5rem; }
/* A person, on a list of runners (dig #2429). Decision R6 marks one
   wherever runners are listed, and on a start list that mark is the
   point of the list: sixty of the sixty-one names are the local field,
   and the few people among them are who somebody came to read about.
   Amber rather than `.spec` muted, because muted is the register for a
   fact you can ignore. */
/* A glyph since dig #4935 (`NameMark::person()`), drawn the size and
   place of the gear it stands opposite. */
.person { color: var(--amber); font-size: .9em; margin-left: .35em; white-space: nowrap; }
/* One of the simulated field (dig #4471), after its name wherever a name
   is printed. The opposite end from `.person`: muted and small, because
   on most boards it is most of the names, and a mark on most of the
   names is only readable if it is quiet. */
.bot { color: var(--muted); font-size: .9em; margin-left: .35em; text-decoration: none; }
/* Somebody the reader follows, on a live race board (dig #2479). The
   same size and place as `.person` beside it, and the amber the rest
   of the game already spends on a rival (`.ground-rival`), so a board
   read at a glance says which of these names you came for. */
.watching { color: var(--amber); font-size: .9em; margin-left: .35em; white-space: nowrap; }
/* The runners a long board left out, said rather than hidden. Muted:
   it is a count, not a runner. */
.elided { color: var(--muted); font-size: .78rem; }
/* A break in a run of rows (dig #2479). Tom, reading a live board whose
   places went 4, 8, 10, 12: "can we put a small separator between the
   rows where numbers are missing". The anonymous pack IS those missing
   numbers — most of a field of 90 has no name — so the board was
   printing a place sequence with holes in it and nothing to say the
   holes meant anything. It draws the same rule before the meta rows at
   the foot, which are facts about the race rather than runners in it.
   Dotted and in `--rule`, which is the quietest divider this UI has:
   it is punctuation, not a heading. */
.kv-sep { border-top: 1px dotted var(--rule); margin: .4rem 0; }
/* A run of short facts (dig #3706). Written as one string, a wrap could
   fall anywhere: "load" ended one line and "12" began the next, and a
   `·` started a line. Each fact is its own element that keeps together,
   and the separator is drawn after it, so it rides at the end of a line.
   The whitespace between the elements in the markup is the only place a
   line may break, so it must be kept. */
.facts > * { white-space: nowrap; }
.facts > :not(:last-child)::after { content: " ·"; color: var(--muted); }
/* A ranked board (dig #3706): place · who · time, and anything said
   about the row under the name. Every board was a `.kv` with the place
   as its label, so on a phone the place sat alone on a line whenever a
   name was long, the next name hugged the right edge whenever it was
   short, and the times were wherever the name had left off. A grid puts
   the places, names and times in three columns down the whole board.
   Each row is its own grid, so the columns are FIXED widths (the place
   in `ch`, the time sized to its content and right-aligned) rather than
   `auto`: an auto column would be as wide as that one row's cell. */
.kv.rank {
    display: grid; grid-template-columns: 3ch minmax(0, 1fr) auto;
    column-gap: .7rem; margin: .3rem 0;
}
.kv.rank > .k { min-width: 0; text-align: right; letter-spacing: 0; font-size: .85rem; }
.kv.rank > .tm { text-align: right; white-space: nowrap; }
.kv.rank > .note { grid-column: 2 / -1; color: var(--muted); font-size: .8rem; line-height: 1.4; }
.kv.rank > .note a { color: var(--accent); }
.kv.rank > .elided:last-child { grid-column: 2 / -1; }
/* The name on a board, kept to one line (dig #4935). Tom: "maybe don't
   let the name wrap onto two lines?" A nickname in quotes ran most
   rows onto two on a phone, so the board was as tall as twice its
   field. The name is cut with an ellipsis (the whole of it is the
   title), and what follows it, the marks and "(you)", is never what
   gets cut: a mark is the reason somebody is reading the row. */
.kv.rank > .who { display: flex; align-items: baseline; min-width: 0; white-space: nowrap; }
.who > :first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.who > :not(:first-child) { flex: none; }
.who > :first-child + span:not([role]) { margin-left: .35em; }
/* A race read back, call by call (dig #3706): the kilometre is a column
   of its own, and the place, what happened and what you called hang
   beside it, so the eye can run down the course by distance. */
.call-row { display: grid; grid-template-columns: 4.2rem minmax(0, 1fr); column-gap: .7rem; margin: .55rem 0; align-items: baseline; }
.call-row > .km { color: var(--muted); text-align: right; font-size: .85rem; white-space: nowrap; }
.call-row .where { color: var(--muted); text-transform: uppercase; letter-spacing: .08em; font-size: .78rem; }
.trail { color: var(--muted); font-size: .82rem; margin: .1rem 0 .55rem; }
.cond-fresh { color: var(--accent); }
.cond-niggle { color: var(--amber); }
.cond-strained { color: var(--alert); }
.cond-injured { color: var(--alert); font-weight: 700; }
/* Form of the day (#57) — reuses the condition palette, because it is
   reporting the same thing: how the body reads this morning. */
.form-flying { color: var(--accent); font-weight: 700; }
.form-good { color: var(--accent); }
.form-par { color: var(--fg); }
.form-off { color: var(--amber); }
.form-poor { color: var(--alert); }
/* Conditions forecast (#35): weather/light cost shown before committing. */
.warn { color: var(--amber); }
/* ⚠️ THE GLYPH IS SCOPED, AND THE SCOPE IS THE WHOLE POINT (decision
   #153). A warning should be marked as well as coloured — colour alone
   is not a difference you can scan down a readout for, and it is not a
   difference at all for a colourblind reader. But `.warn` is used ~80
   times and most of those are a CLAUSE riding inside a sentence
   (`Fresh<span class="warn"> · at this load, that is a gamble</span>`),
   where a triangle lands mid-phrase; and a `.meter-v.warn` is a bare
   figure in the fixed-width column dig #524 squared up, where a glyph
   knocks every bar on the screen out of line.

   So it marks `.warn` only where `.warn` IS what is being said: the
   value column of a row, a note block, a card title, a paragraph. Both
   exceptions above are nested deeper than `.kv > *`, so they are
   excluded by construction rather than by a `:not()` list somebody has
   to maintain. */
.kv > .warn::before,
.aside-note.warn::before,
.rt.warn::before,
p.warn::before { content: '▲ '; }
/* One rung up: something is wrong NOW, rather than something will cost
   you if you go. Nothing uses it yet — it is here for the screens the
   rest of this pass touches — so it needs no scoping. */
.alert { color: var(--alert); }
.alert::before { content: '! '; }
/* ── The reference register (decision #153) ────────────────────────
   There were six of these and they all resolved to "muted small text":
   `.aside-note` .74, `.spec` .78, `.field-note` .8, `.trail` .82,
   `.muted-note` .82, `.blurb` .82 — so a live gloss on a number and a
   paragraph teaching you what the number means read identically. This
   is the one register for a reference gloss; new markup uses it, and
   the five below stay as aliases so nothing already written breaks.
   `.aside-note` is NOT an alias: its dashed box is reserved for a
   warning or a consequence, never for teaching (decision #59 sends
   teaching to /help). */
.quiet { color: var(--muted); font-size: .8rem; }
.quiet a { color: var(--muted); }
/* One thing on the shelf — a won badge or a found achievement (both
   halves of the cabinet, dig #2380).

   ⚠️ Deliberately NOT a `.kv`. That row is a label/value pair laid out
   `space-between` with wrap, and a badge's "value" is a name, a clock,
   a place and a paragraph — so the whole block wrapped to a line of its
   own and left the bullet stranded above it, one glyph on an otherwise
   empty row. Tom, on the cabinet: "have the bullet point to the left of
   the text." So the marker is a fixed column that never shrinks and the
   text is one shrinking column beside it, which also hangs every line
   of the blurb under the NAME rather than under the dot. */
.shelf-row { display: flex; gap: .5rem; margin: .5rem 0; }
.shelf-row > .mark { flex: none; }
.shelf-row > .what { flex: 1; min-width: 0; overflow-wrap: break-word; }
/* A clock face and a date are one token to a reader and several to CSS,
   which treats every hyphen in "2026-09-02" as a place it may break —
   so the year ended one line and the day began the next. The PLACE is
   deliberately left wrappable: it is a phrase ("Powell Hut, on
   Jumbo–Holdsworth"), and it can be longer than a phone's line, where a
   nowrap would push it out of the readout instead. */
.shelf-row .fig, .spec.fig { white-space: nowrap; }
/* Race day (D4): the played-through scenario + result. */
.feedline.race { color: var(--accent); }
.race-loc { color: var(--accent); text-transform: uppercase; letter-spacing: .1em; font-size: .78rem; margin: 1.4rem 0 .5rem; }
.race-loc::before { content: "▸ "; color: var(--muted); }
.race-heading { color: var(--muted); text-transform: uppercase; letter-spacing: .12em; font-size: .78rem; margin: 1rem 0 .3rem; }
.feedline.chose { color: var(--muted); }
.race-result { border-top: 1px solid var(--accent); margin-top: 1.4rem; padding-top: .4rem; }
/* Terminal confirm dialog — replaces wire:confirm's native window.confirm. */
.confirm-backdrop {
    position: fixed; inset: 0; z-index: 50;
    background: rgba(14, 15, 12, .78);
    display: flex; align-items: center; justify-content: center;
    padding: 1.2rem;
}
/* An author `display` beats the UA stylesheet's [hidden] rule, so the
   dialog needs this or it can never be closed again once opened. */
.confirm-backdrop[hidden] { display: none; }
.confirm-box {
    background: var(--panel); border: 1px solid var(--accent);
    box-shadow: 0 0 28px rgba(155, 229, 100, .18);
    max-width: 26rem; width: 100%; padding: 1.1rem 1.2rem;
}
.confirm-message { margin: 0 0 1rem; color: var(--ink); }
.confirm-actions { display: flex; gap: .6rem; justify-content: flex-end; }
.confirm-actions button {
    font: inherit; color: var(--ink); background: transparent;
    border: 1px solid var(--muted); padding: .5rem .9rem; cursor: pointer;
}
.confirm-actions button:hover { border-color: var(--accent); }
.confirm-actions button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.confirm-actions .confirm-ok { color: var(--accent); border-color: var(--accent); }
.confirm-actions .confirm-ok.confirm-danger { color: var(--alert); border-color: var(--alert); }
/* The one ask (dig #2515) labels its buttons with phrases rather than
   `yes`/`cancel`, and two phrases side by side squeeze each other into
   two ragged lines apiece. Stack them full width, the answer it is
   asking for on top. It hooks a class of its own rather than the
   dialog's attribute, because an attribute selector would print that
   attribute into every page's <style> block — including the signed-out
   ones the dialog is deliberately kept off, where PushAskTest reads the
   whole page for it. */
.push-ask-actions { flex-direction: column-reverse; }
.push-ask-actions button { width: 100%; padding: .7rem .9rem; }
body.confirm-open { overflow: hidden; }
/* A help answer over the screen it was asked from (dig #3127,
   help-popup.js). The confirm box, a little wider for prose, with the
   /help page's own `? ` in front of a question. */
.help-box { max-width: 30rem; max-height: 80dvh; overflow-y: auto; }
.help-box-title { margin: 0 0 .5rem; color: var(--accent); }
.help-box-title.help-box-q::before { content: "? "; }
.help-box-body p { margin: 0 0 1rem; color: var(--ink); }
.help-box-body a { color: var(--accent); }
.help-box-actions { align-items: center; justify-content: space-between; }
.help-box-more { color: var(--accent); }
@media (prefers-reduced-motion: no-preference) {
    .confirm-backdrop { animation: confirm-fade .12s ease-out; }
}
@keyframes confirm-fade { from { opacity: 0; } to { opacity: 1; } }

/* ── The moment (dig #3949) ──────────────────────────────────────────
   What you just took: a piece of ground, an FKT mark, or your own best.
   Tom, of the `.readout` this used to be: "Needs to be a bit more fan
   fare if you take a ground or FKT. It just sits quietly in my log."

   It was quiet because it was drawn in the one box every card on the
   hub is drawn in, with the time (the whole of what was won) at .78rem
   in `--muted`. So the three things a terminal has for emphasis are
   all spent here and nowhere else in the world UI: reverse video for
   the heading, the largest number on any screen for the time, and an
   `--accent` border where every other box is `--rule`.

   ⚠️ `moment--quiet` is the same layout with the lights off, for a
   personal best (which pays nothing) and a take somebody has already
   had back. The block must not shout what is not true any more.

   ⚠️ The readout inside keeps its rows and loses its box. A lit box
   inside a lit box is two frames around one thing. */
.moment {
    margin: 1.2rem 0 1.8rem; background: var(--panel);
    border: 1px solid var(--muted);
}
.moment--take {
    border-color: var(--accent);
    box-shadow: 0 0 22px rgba(155,229,100,.16), inset 0 0 34px rgba(155,229,100,.08);
}
.moment .moment-flag {
    margin: 0; padding: .45rem .9rem; font-size: 1rem; letter-spacing: .1em;
    color: var(--accent); border-bottom: 1px solid var(--muted);
}
.moment .moment-flag::before, .moment .moment-flag::after { content: none; }
.moment--take .moment-flag {
    background: var(--accent); color: var(--paper); border-bottom: 0; font-weight: 700;
}
.moment-time {
    margin: .7rem .9rem 0; font-size: 2.6rem; line-height: 1; letter-spacing: .02em;
    color: var(--ink); font-variant-numeric: tabular-nums;
}
.moment--take .moment-time { color: var(--accent); text-shadow: 0 0 18px rgba(155,229,100,.5); }
.moment-margin { margin: .35rem .9rem 0; color: var(--ink); font-size: .95rem; }
/* What it won is the other half of the answer to "what did I win",
   so on a take you still hold it is lit like the time above it. */
.moment--take .moment-paid > span:last-child { color: var(--accent); }
.moment .readout {
    margin: .8rem 0 0; background: none; box-shadow: none;
    border: 0; border-top: 1px solid var(--rule);
}
/* A level lost (dig #4462): the quiet layout in the colour this UI keeps
   for a warning. Not lit, because nothing was won; not grey, because a
   tier or a rung of the ladder may just have shut and that is news. */
.moment--down { border-color: var(--amber); }
.moment--down .moment-flag { color: var(--amber); border-bottom-color: var(--amber); }
.moment--down .moment-time { color: var(--amber); }
/* The arrival. public/js/moment.js puts `moment--arriving` on a moment
   this device has not seen before, once, and that is the only thing
   that runs any of this: a moment already seen is simply there, so the
   hub does not put on a show every time it is opened for a day.

   Stepped, because it is a terminal drawing a line and not a card
   sliding in. The banner writes itself left to right, the time lands a
   beat after it, and the frame takes two breaths to settle.

   ⚠️ Every rule is inside the motion guard, and the resting state is
   the finished one. With motion off, or with no script at all, the
   block is exactly as loud and nothing is ever left half-drawn. */
@media (prefers-reduced-motion: no-preference) {
    .moment--arriving .moment-flag { animation: moment-write .55s steps(18, end) both; }
    .moment--arriving .moment-glyph { display: inline-block; animation: blink .32s steps(2, start) .55s 4; }
    .moment--arriving .moment-time { animation: moment-land .5s steps(4, end) .5s both; }
    .moment--arriving .moment-margin,
    .moment--arriving .readout { animation: moment-land .4s steps(3, end) .95s both; }
    .moment--arriving.moment--take { animation: moment-settle 2.4s ease-out .5s both; }
}
@keyframes moment-write { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0); } }
@keyframes moment-land { from { opacity: 0; } to { opacity: 1; } }
@keyframes moment-settle {
    0%, 40% { box-shadow: 0 0 46px rgba(155,229,100,.55), inset 0 0 34px rgba(155,229,100,.2); }
    20% { box-shadow: 0 0 14px rgba(155,229,100,.2), inset 0 0 34px rgba(155,229,100,.08); }
    100% { box-shadow: 0 0 22px rgba(155,229,100,.16), inset 0 0 34px rgba(155,229,100,.08); }
}

/* ── Admin (excavation #645, sub-dig 8) ──────────────────────────────
   The first admin surfaces this app has had, and they are deliberately
   the same terminal rather than a second product: `.readout`, `.kv`,
   `.prompt-list` and the meter component do almost all of the work, and what is
   added here is the handful of shapes an approval screen needs and the
   world does not. Kept in this stylesheet with everything else — an
   admin-only sheet would be one more file every page fetches to use on
   two of them. */
.admin-nav { display: flex; flex-wrap: wrap; gap: .4rem; margin: .2rem 0 1rem; }
.admin-nav a {
    display: inline-block; width: auto; margin: 0; padding: .35rem .7rem;
    font-size: .78rem; border-color: var(--rule); color: var(--muted);
}
.admin-nav a.on { color: var(--accent); border-color: var(--accent); }
/* A row of counts. `auto-fit` rather than a fixed column count so a
   375px phone gets two per line and a desktop four, off one rule. */
.tally { display: grid; grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr)); gap: .5rem; }
.tally > div { border: 1px solid var(--rule); padding: .45rem .6rem; min-width: 0; }
.tally .n { color: var(--accent); font-size: 1.25rem; line-height: 1.2; }
.tally .l { color: var(--muted); font-size: .74rem; text-transform: uppercase; letter-spacing: .1em; }
.tally .n.warn { color: var(--amber); }
/* The usage screen (dig #4705). One series, so one hue and no legend:
   the heading names it, the peak sits top-left as the only axis figure,
   and each bar's own `title` carries its number. A 2px gap between bars
   is the panel showing through, the same separation `.tally` draws
   with its rules. */
.usage-chart {
    position: relative; display: flex; align-items: flex-end; gap: 2px;
    height: 7rem; padding: 1.1rem 0 .9rem; border-bottom: 1px solid var(--rule);
}
.usage-peak { position: absolute; top: 0; left: 0; color: var(--muted); font-size: .74rem; }
.usage-day { position: relative; flex: 1 1 0; height: 100%; display: flex; align-items: flex-end; min-width: 0; }
.usage-bar { display: block; width: 100%; min-height: 1px; background: var(--accent); border-radius: 2px 2px 0 0; }
.usage-new { position: absolute; bottom: -.95rem; left: 0; right: 0; text-align: center; color: var(--muted); font-size: .7rem; line-height: 1; }
.usage-axis { display: flex; justify-content: space-between; color: var(--muted); font-size: .74rem; margin: .5rem 0 .3rem; }
/* A retention figure tinted by its own share, one hue light to strong.
   The text stays in ink so the number reads whatever the fill. */
.usage-cell { padding: 0 .3rem; background: rgb(155 229 100 / calc(var(--share) * .45)); }
.usage-cell.running { text-decoration: underline dotted var(--muted); text-underline-offset: .2em; }
/* A textarea had never appeared in the world UI: every screen until now
   either showed prose or took one line of it. Same box `.field input`
   draws, so the two sit together in a form. */
.field textarea {
    /* Restated rather than inherited: this block sits below the iOS
       focus-zoom threshold rule (dig #2420) and `font: inherit`
       resets the size, so the box would go back to 15.2px. */
    width: 100%; font: inherit; font-size: max(16px, 1rem);
    color: var(--ink); line-height: 1.5;
    background: var(--panel); border: 1px solid var(--muted);
    padding: .6rem .8rem; resize: vertical; min-height: 5rem;
}
.field textarea:focus { outline: none; border-color: var(--accent); }
/* The weights map: a label and a narrow number box per quality. The
   sum lives under it and goes amber until it is 1.0, because that is
   the one thing about a weights map that can be wrong silently. */
.weights-row { display: flex; align-items: center; gap: .6rem; margin: .3rem 0; }
.weights-row label { flex: 1 1 auto; min-width: 0; margin: 0; text-transform: none; letter-spacing: 0; font-size: .85rem; color: var(--ink); }
.weights-row input { flex: 0 0 5.5rem; width: 5.5rem; text-align: right; padding: .4rem .5rem; }
.checkline { display: flex; align-items: flex-start; gap: .55rem; margin: .5rem 0; }
.checkline input { flex: 0 0 auto; width: auto; margin: .25rem 0 0; accent-color: var(--accent); }
.checkline span { min-width: 0; }
/* Read-only ground, drawn with sub-dig 6's browse map. The module and
   its stylesheet load on the review screen only — nothing else in the
   world mounts a track yet, and a module that does nothing on every
   screen is still a module every screen fetches. */
.admin-track { margin: 1rem 0; }
.residual { font-size: .8rem; }
.residual .off { color: var(--amber); }
.residual .agrees { color: var(--muted); }

/* ── Phones ──────────────────────────────────────────────────────────
   This layout had no width breakpoint at all, so every world screen was
   the desktop stylesheet squeezed into 375px. On an iPhone 13 mini the
   sticky header alone was ~156px — the runner line wrapping to two and
   the nav to three — which is a quarter of the visible viewport gone on
   every page, permanently. */
@media (max-width: 34rem) {
    /* Side padding was 9.8% of the viewport at the width where it costs
       the most. Claw back 16px of measure. */
    .wrap {
        padding: 0 max(.65rem, env(safe-area-inset-right))
                 calc(4rem + env(safe-area-inset-bottom))
                 max(.65rem, env(safe-area-inset-left));
    }
    .top {
        padding: calc(.55rem + env(safe-area-inset-top))
                 max(.65rem, env(safe-area-inset-right)) .55rem
                 max(.65rem, env(safe-area-inset-left));
    }

    /* One line, with the tail in the [more ▸] menu — dig #1152. This
       used to be a sideways-scrolling strip (#59), which fixed the
       header's height and paid for it in discoverability: the items
       past the right edge were invisible, nothing said they were
       there, and the strip looked like a finished line of text. The
       height is the same; the menu is a control instead of a gesture. */
    .top nav {
        gap: .85rem;
    }
    /* 20.5px tall with no padding fails every tap-target guideline. */
    .top nav a, .top nav button, .nav-more > summary { padding: .3rem 0; }
    /* The menu is a tap target list on a phone, so give it the width to
       be one and hang it off the right edge of the screen. */
    .nav-menu { min-width: 11rem; padding: .55rem .8rem; }
    .nav-menu a, .nav-menu button { padding: .45rem 0; }

    /* The game's name is redundant on the runner's own screen (it's
       the document title and the home link); dropping it keeps the
       runner/level/clock line to one row. The pre-JS half of
       `.id-tight`: nav-overflow.js makes the same call off a measure. */
    .top .id .id-game { display: none; }
    .top .id { letter-spacing: .04em; }

    [id], .readout, h2 { scroll-margin-top: 4.5rem; }

    /* `── HEADING ──` at .14em tracking overflowed 375px on the longer
       ones ("TUSSOCK CLIMBER — THE LADDER"), orphaning the trailing rule
       onto its own line. */
    /* ⚠️ And the TRAILING rule goes (decision #153). Promoting an h2
       from .85rem to 1rem bought back the overflow this tracking
       existed to fix: the longest headings in the game interpolate a
       route or race name ("The Tararua Southern Crossing · OUT THERE"),
       which at 1rem clears a 354px measure on its own. Dropping the
       closing `──` is the honest trim: it is a terminal flourish, the
       opening one already marks the line as a heading, and an orphaned
       trailing rule on its own row was the reported fault in the first
       place. The scale is what matters and it is unchanged. */
    h2 { letter-spacing: .06em; }
    h2::after { content: none; }

    .readout { padding: .6rem .7rem; }
    .moment .moment-flag { padding: .4rem .7rem; letter-spacing: .06em; }
    .moment-time { margin: .6rem .7rem 0; }
    .moment-margin { margin: .3rem .7rem 0; }
    .aside-note { padding: .45rem .6rem; }

    /* Both were dense stacks of ~22px hit areas. */
    .dresser-row button { padding: .35rem .7rem; }
    .dresser-row { gap: .5rem; margin: .5rem 0; }

    /* The job picker's seven cards (excavation #3003, finding 14): each
       axes block was six full-width label/value rows, so seven cards ran
       several screens deep before a phone reached the region picker below
       them. Pairing the axes two-to-a-row — the same grid the desktop
       breakpoint above already uses — halves that height without changing
       which column a figure sits in card to card. */
    .job-card { padding: .55rem .65rem; }
    .job-card .axes {
        grid-template-columns: 4rem 1fr 4rem 1fr;
        column-gap: .4rem; row-gap: .1rem; font-size: .74rem;
    }
    .job-card .axes .lab { font-size: .7rem; }
}

/* The week row on the hub (decision #210, dig #3094). A day out is the
   accent and everything else is muted, so the eye counts the lit squares
   rather than reading seven glyphs. Letter-spaced a touch so seven squares
   read as seven days and not one bar. */
.week-days { letter-spacing: .12em; }
.week-days .wk-out { color: var(--accent); }
.week-days .wk-missed,
.week-days .wk-ahead,
.week-days .wk-holiday { color: var(--muted); }
/* A day still to come is dimmer than one that went by without an outing,
   so the two hollow squares can't be read as the same thing. */
.week-days .wk-ahead { opacity: .45; }

/* Mates to ask on a group run (decision #214): one line each, a plain box. */
.mate-pick__one { display: block; text-transform: none; letter-spacing: 0; font-size: 1rem; color: var(--ink); margin: .2rem 0; }
.mate-pick__one input { width: auto; accent-color: var(--accent); margin-right: .4rem; }
.field input[type="time"] { margin-top: .4rem; }
/* iOS draws a time or date box as its own control with a minimum width,
   so `width: 100%` loses and the box ran past the card (dig #4597).
   Stripped back to the plain `.field input` box, value on the left where
   every other box puts it rather than centred. */
.field input[type="time"], .field input[type="date"] {
    display: block; min-width: 0; border-radius: 0;
    -webkit-appearance: none; appearance: none;
}
.field input::-webkit-date-and-time-value { text-align: left; }
