/**
 * app.css — the whole design system (13-INTERFACE.md, ADR-015, ADR-033).
 *
 * This is the Twenty-Four Doors language, implemented. The mock set is the
 * specification: monochrome with one blue, alarm by inversion rather than by
 * hue, a 64px rail of destinations, a 172px column of what lives inside one,
 * and the work as a white window floating on a grey ground.
 *
 * Two postures, not two products. The mock marks each screen "drawn for the
 * phone" or "drawn for the desk" and gives them different chrome: the desk
 * gets the rail and the second column, the phone keeps the bottom dock. So
 * posture here follows width, and the same markup wears both.
 *
 * Three deliberate departures from the mock, each recorded rather than quietly
 * applied:
 *
 *   1. `--muted` is #6e6e77 light / #84848d dark, not #83838c / #7b7b85. The
 *      mock's greys measure 3.76:1 and 4.45:1 on their own surfaces, and
 *      `.meta` is body text at 12.5px, so both fail WCAG AA. Both replacements
 *      are taken from the mock's own chrome palette rather than invented, and
 *      measure 5.05:1 and 5.02:1. U9 is what would have caught it.
 *   2. A state dot carries a glyph as well as a colour. The mock's dots are
 *      coloured circles, and colour alone is not a state (U10).
 *   3. A disclosure row never goes below 44px, at either posture. The mock's
 *      30px row is right for a ledger and wrong for a thumb; the dense row is
 *      scoped to the ledger surfaces that earn it (.sheet, .facet, .r).
 */

/* ── typefaces ───────────────────────────────────────────────────────────
   Served from here, not from a font CDN. A field app that loses its type the
   moment the signal drops is not offline-capable, and a third-party request
   on every page load is a network dependency the capability boundary does not
   cover. Syne and Instrument Sans are variable, so one file carries every
   weight the interface uses. */
@font-face { font-family: "Syne"; font-style: normal; font-weight: 600 800; font-display: swap; src: url(/fonts/syne.woff2) format("woff2"); }
@font-face { font-family: "Instrument Sans"; font-style: normal; font-weight: 400 700; font-display: swap; src: url(/fonts/instrument-sans.woff2) format("woff2"); }
@font-face { font-family: "IBM Plex Mono"; font-style: normal; font-weight: 400; font-display: swap; src: url(/fonts/ibm-plex-mono-400.woff2) format("woff2"); }
@font-face { font-family: "IBM Plex Mono"; font-style: normal; font-weight: 500; font-display: swap; src: url(/fonts/ibm-plex-mono-500.woff2) format("woff2"); }
@font-face { font-family: "IBM Plex Mono"; font-style: normal; font-weight: 600; font-display: swap; src: url(/fonts/ibm-plex-mono-600.woff2) format("woff2"); }

/* ── tokens ─────────────────────────────────────────────────────────────── */
:root {
  --d: "Syne", Georgia, serif;
  --f: "Instrument Sans", -apple-system, "Segoe UI", sans-serif;
  --m: "IBM Plex Mono", ui-monospace, Menlo, monospace;

  --ground: #e4e4e8;
  --win: #ffffff;
  --ink: #0b0b0c;
  --ink-2: #3f3f45;
  --muted: #6e6e77;          /* the mock's #83838c measures 3.76:1 — see header */
  --muted-2: #83838c;        /* the mock's value, kept for marks that are not text */
  --line: #e3e3e6;
  --line-2: #f2f2f4;
  --accent: #1d4ed8;
  --accent-soft: #e8edfc;
  --inv: #0b0b0c;            /* alarm = inversion. Survives a theme flip; hue does not. */
  --inv-ink: #ffffff;

  /* Kept as aliases so every screen written against the old names still reads. */
  --bg: var(--ground);
  --card: var(--win);
  --done: var(--muted);
  --warn: var(--ink);
  --danger: var(--ink);
  --focus: var(--accent);

  --r: 7px;
  --pad: 6px;
  --row: 30px;               /* the ledger row */
  --row-h: 44px;             /* the disclosure row — never below the tap target */
  --font: 13px;
  --font-sm: 12.5px;
  --dock-h: 58px;
  --rail-w: 64px;
  --second-w: 172px;

  /* [T5] The panel width. The inspector and the capture sheet are the same
     surface in two postures, so they are the same number in one place — change
     it once and the record and the form still agree at the edge of the screen. */
  --panel-w: 340px;
  --sheet-ms: 220ms;
  --sheet-ease: cubic-bezier(.32, .72, 0, 1);

  /* [T4] A form's height is mostly the air above its labels and inside its
     boxes. Both were hardcoded, so the Compact button moved every row in the
     app except the one an operator most wants shorter. */
  --label-gap: 10px;
  --field-pad: 7px 9px;
}

/* Compact is a SETTING. Both densities are available at every width. */
:root[data-density="compact"] {
  --pad: 4px;
  --row-h: 44px;             /* padding tightens; the target does not */
  --font: 12.5px;
  --font-sm: 12px;
  --label-gap: 5px;
  --field-pad: 4px 8px;      /* the box shrinks; 16px type does not, or iOS zooms */
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ground: #09090a; --win: #151517;
    --ink: #f4f4f6; --ink-2: #b5b5bd;
    --muted: #84848d;        /* the mock's #7b7b85 measures 4.45:1 — see header */
    --muted-2: #7b7b85;
    --line: #26262b; --line-2: #1b1b1f;
    --accent: #6f9bff; --accent-soft: #17233d;
    --inv: #f4f4f6; --inv-ink: #0b0b0c;
  }
}
:root[data-theme="dark"] {
  --ground: #09090a; --win: #151517;
  --ink: #f4f4f6; --ink-2: #b5b5bd;
  --muted: #84848d;
  --muted-2: #7b7b85;
  --line: #26262b; --line-2: #1b1b1f;
  --accent: #6f9bff; --accent-soft: #17233d;
  --inv: #f4f4f6; --inv-ink: #0b0b0c;
}

/* Oreo: the rail sunk to black, the second column and the window light. The
   mock offers it as a third mode rather than a variant of dark. */
:root[data-theme="oreo"] .rail { background: #0b0b0c; }
:root[data-theme="oreo"] .rail a { color: #84848d; }
:root[data-theme="oreo"] .rail a[aria-current] { color: #fff; }
:root[data-theme="oreo"] .rail a[aria-current] .chip { background: #26262b; }
:root[data-theme="oreo"] .rail a:hover .chip { background: #1b1b1f; }
:root[data-theme="oreo"] .org { background: #fff; color: #0b0b0c; }
:root[data-theme="oreo"] .me { background: #26262b; color: #c9c9d0; }

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font: var(--font)/1.45 var(--f);
  -webkit-font-smoothing: antialiased;
  padding-bottom: calc(var(--dock-h) + env(safe-area-inset-bottom));
}
a { color: inherit; text-decoration: none; }
:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: 4px; }

/* ── the shell ───────────────────────────────────────────────────────────
   Phone posture by default: one column on the ground, the dock fixed to the
   bottom. The rail and the second column do not exist below 900px, because a
   390px screen cannot carry 236px of chrome. */
.rail, .second { display: none; }
main { max-width: 940px; margin: 0 auto; padding: 12px 13px 40px; }

/* ── header ─────────────────────────────────────────────────────────────── */
/* The counters wrap under the title rather than pushing the page wider:
   a record screen that scrolls sideways on a phone loses the left edge
   of every line the operator is reading. */
.top { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 14px; padding: 12px 0 8px; }
.top > div:first-child { min-width: 0; }
.eyebrow { font-size: 11.5px; font-weight: 600; color: var(--accent); margin: 0 0 2px; }
h1 { font: 700 19px/1.15 var(--d); letter-spacing: -.01em; margin: 0; color: var(--ink); }
.stats { display: flex; flex-wrap: wrap; gap: 16px; text-align: right; }
.stats div { font-size: 11px; color: var(--muted); }
.stats b { display: block; font: 600 16px var(--m); color: var(--ink); font-variant-numeric: tabular-nums; }
h2 { font-size: 12px; font-weight: 600; color: var(--muted); margin: 16px 0 6px; }

/* ── lists: level one of three ──────────────────────────────────────────── */
.list { background: var(--win); border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; }
.list.flush { margin: 0; border-radius: 0; border-left: 0; border-right: 0; }
.row {
  display: flex; align-items: center; gap: 9px;
  min-height: var(--row-h); padding: var(--pad) 11px;
  border-top: 1px solid var(--line-2);
}
.list > *:first-child .row, .list > .row:first-child { border-top: 0; }

/* Row containment. Measured on places.html at 3,292 rows: materializing the
   list costs 422.9ms of JavaScript building nodes and 188.4ms of layout, so
   containment — which defers layout and paint and cannot touch the JS — is
   worth about a third of the row cost, not the whole of it. It is applied
   because a third is worth having and it costs one declaration, not because
   it solves the problem on its own; the other two thirds are the
   materializer's, and this campaign does not touch it.

   `auto` on `contain-intrinsic-size` makes the browser remember each row's
   last rendered height instead of trusting the 50px guess forever, which is
   what keeps an open row — one with its detail panel showing — from
   collapsing the scrollbar every time it leaves the viewport. The guess
   itself is the measured average: 164,349px of list over 3,292 rows.

   Scoped to `article[data-row]` rather than to `.row`, because the article is
   the unit that owns both the row and its detail; containing the inner row
   alone would leave the detail outside the contained box.

   OPT-IN per list, and that is a correction rather than caution. This first
   shipped as a blanket `.list > article[data-row]`, and `.list` is shared: it
   also matches the agent transcript (`div.turns.list` of `article.turn`).
   Containment skips layout for off-screen rows, so a DESCENDANT of one
   reports zero geometry — and agent.html's per-turn controls are read exactly
   that way, which turned "is the Stop button still offered on a closed
   question" into a silent no. `test/ask.mjs` K15 caught it; the ledger suite
   did not, because it measured the article, which keeps its intrinsic box,
   rather than anything inside one.

   So a list asks for this, and only the two that measured a win do: lists of
   thousands, where nothing reads an off-screen row's children. A transcript of
   twenty turns gains nothing from it and loses that. */
.list[data-contain] > article[data-row] { content-visibility: auto; contain-intrinsic-size: auto 50px; }
.row:hover { background: var(--line-2); }
.row[aria-selected="true"], .row.sel { background: var(--accent-soft); }
.grow { flex: 1; min-width: 0; }

/* ── the disclosure caret ────────────────────────────────────────────────
   Plane hides its caret until the block is hovered, which suits a surface
   where opening is one of several things a row does. Here the row IS the
   disclosure — a role="button" whose only verb is "open" — so hiding the
   affordance hides the row's only verb. It is drawn always.

   The box is reserved at 16px in BOTH states, which is the whole reason it is
   a fixed box rather than an inline mark. A caret that appears on hover makes
   every name in the list shift sideways under the pointer, and that reads as
   the page reflowing under the press rather than as an affordance arriving.

   One drawing serves both positions: the open state rotates it. Reduced motion
   is already handled globally further down this file, so there is no second
   rule here.

   aria-hidden because the row already carries aria-expanded. The caret is the
   same fact drawn; announcing it twice is announcing it wrong. */
.caret { --ctl-size: 16px; color: var(--muted-2); transition: transform 120ms; }
.row:hover > .caret, .row:focus-visible > .caret { color: var(--ink-2); }
article[data-open="1"] > .row > .caret,
.row[aria-expanded="true"] > .caret { transform: rotate(90deg); }
/* A row is one line. If it wraps, it is carrying a field that belongs a level
   down — that is the rule, and this is what enforces it visibly. */
.name, .nm { font-weight: 600; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.meta { font-size: var(--font-sm); color: var(--muted); display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.when { font-size: var(--font-sm); color: var(--ink-2); white-space: nowrap; font-variant-numeric: tabular-nums; }
.empty { padding: 20px 13px; color: var(--muted); font-size: var(--font-sm); text-align: center; }
.empty b { display: block; color: var(--ink-2); font-weight: 600; margin-bottom: 2px; }

/* ── the empty state has more than one reason ────────────────────────────
   CANVASS already held the principle — an empty state is information, and a
   plausible fill is a lie — and then drew one shape for every reason. That is
   how inbox.html came to say "Nothing waiting" while five things were waiting
   and a filter had excluded all of them. Reproduced by clicking its own Kept
   and Binned buttons, which are labelled with the count they exclude.

   `data-empty` already sat on 29 of these blocks and was read by nothing. It
   now carries the reason, so the attribute that marked the shape names it:

     data-empty            nothing written yet — the original, unchanged
     data-empty="filtered" a filter excluded everything that is there
     data-empty="broke"    the read failed, so the list is unknown, not empty

   There is deliberately no fourth. Plane carries a no-access state; CANVASS
   has no surface where an operator sees a count they may not read, because
   multi-tenancy here is routing rather than a permission check — a tenant does
   not see the list at all. Building one would be inventing a feature.

   The measure is bounded because a centred paragraph that runs the full width
   of a desk window is read as a banner, not as a sentence. */
.empty .m { display: block; max-width: 44ch; margin: 0 auto; }
.empty .acts, .empty .eacts { display: flex; gap: 6px; justify-content: center; margin-top: 10px; opacity: 1; }
.empty [hidden] { display: none; }

/* A mark well, so the three reasons are told apart before the sentence is
   read. 34px because it must read at a glance and must not become an
   illustration; this is still a line of information. */
.empty .mark { --ctl-size: 18px; display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 50%; margin-bottom: 8px;
  background: var(--line-2); color: var(--muted); }

/* The filtered reason. The arithmetic is the point: a count the operator can
   check against the filter they just pressed. */
.empty[data-empty="filtered"] .mark { background: var(--accent-soft); color: var(--accent); }
.empty .sum { display: block; font-family: var(--m); font-variant-numeric: tabular-nums;
  font-size: 11px; color: var(--muted-2); margin-top: 6px; }

/* The broken reason INVERTS. Alarm in this language is a change of ground and
   never a change of hue — a red word in a grey interface is the first thing
   that stops being read, and it disappears entirely in the oreo theme. */
.empty[data-empty="broke"] .mark { background: var(--inv); color: var(--inv-ink); }
.empty[data-empty="broke"] b { color: var(--ink); }
.empty[data-empty="broke"] .why { display: block; font-family: var(--m); font-size: 11px;
  color: var(--muted-2); margin-top: 6px; word-break: break-word; }

/* The ledger row: the mock's `.r`, dense, for a surface that is a table of
   facts rather than a stack of things you tap. */
.r {
  display: flex; align-items: center; gap: 9px;
  min-height: var(--row); padding: var(--pad) 11px;
  border-top: 1px solid var(--line-2);
}
.r:first-child { border-top: 0; }
.r:hover { background: var(--line-2); }

/* ── level two: the row opens in place ──────────────────────────────────── */
/* 63px, not 38: the indent aligned the detail to the dial column (measured
   at 1240px, detail content 289 against dial 288), and the caret costs 16px
   plus the row gap. Moving it by exactly that preserves the alignment that
   was there rather than quietly redesigning it. */
.detail { display: none; padding: 2px 13px 14px 63px; border-top: 1px solid var(--line-2); background: var(--line-2); }
article[data-open="1"] .detail { display: block; }
.detail dl { display: grid; grid-template-columns: auto 1fr; gap: 3px 14px; margin: 8px 0 0; font-size: var(--font-sm); }
.detail dt { color: var(--muted); }
.detail dd { margin: 0; }
.sub { margin: 10px 0 0; padding: 0; list-style: none; font-size: var(--font-sm); }
.sub li { padding: 3px 0; color: var(--ink-2); border-top: 1px solid var(--line); }
.sub li:first-child { border-top: 0; }

/* ── glyphs: state is a class, the mark is a rule. Nothing stored. ────────
   The mock draws a bare coloured circle. Colour alone is not a state (U10),
   so the glyph stays and the mock's palette is applied to it. */
.dot { width: 14px; flex: 0 0 14px; text-align: center; font-size: 11px; line-height: 1; }
.dot::before { content: "\25CB"; color: var(--muted-2); }
.dot.done::before { content: "\25CF"; color: var(--ink); }
.dot.urgent::before { content: "\25C6"; color: var(--ink); }
.dot.declined::before { content: "\2715"; color: var(--muted); }
/* `.warn` is bound on outreach.html and had no rule at all, so that dot said
   nothing. `.cool` was bound nowhere and drew the same U+25CF as `.done`,
   separated from it by hue alone — the U10 violation departure #2 in this
   file's header claims to have avoided. It is deleted rather than restyled. */
.dot.warn::before { content: "\25B3"; color: var(--ink); }

/* ── a mark is drawn, not borrowed ───────────────────────────────────────
   A glyph set in a `content:` rule is drawn by whatever font the device has,
   so the marks below are masks instead: the shape is the mask, the ink is
   `currentColor`, and the element inherits its colour from the text beside
   it. Inversion punches a real hole, so a selected mark works on any ground
   in either theme, and no mark carries a colour of its own. The shapes come
   from scripts/glyphs.mjs; the classes are generated into the block further
   down this file. State is still a class and the mark is still a rule
   (03-ARCHITECTURE.md) — nothing about a glyph is stored or streamed. */
.chip, .ico, .chm { display: flex; align-items: center; justify-content: center; }
/* A chip holding the ORB holds a real canvas, not a mask. Without this it
   would draw BOTH: `mask: var(--g)` with no `--g` computes to `none`, and a
   ::before with no mask and `background: currentColor` is a solid ink square
   sitting behind the logo. */
.chip.orb::before, .ico.orb::before { content: none; }
.chip::before, .ico::before, .chm::before {
  content: ""; display: block;
  width: var(--g-size, 100%); height: var(--g-size, 100%);
  background-color: currentColor;
  -webkit-mask: var(--g) no-repeat center / contain;
  mask: var(--g) no-repeat center / contain;
}

/* ── the control mark ────────────────────────────────────────────────────
   A third family, and a third kind of thing. A destination says where you are;
   a trade says what you are walking into; a control says what pressing will do.
   Drawn at sixteen and shown at sixteen, so it takes one grade where the
   destinations take two, and a sixteen-unit grid where they take twenty-four.

   The mask is on the element itself rather than on a ::before, because a
   control usually sits inside a button next to a label, and `button, .btn`
   declares no `display` — a block-level mark in there stacks above the words
   instead of beside them. inline-block keeps it on the line.

   --ctl-size is the one lever: the caret in a row wants 16, a mark inside a
   dense pill wants 14. It is a local variable, not a design token, so it may
   be set per use without going anywhere near the frozen region. */
.ctl {
  width: var(--ctl-size, 16px); height: var(--ctl-size, 16px);
  flex: 0 0 var(--ctl-size, 16px);
  display: inline-block; vertical-align: -3px;
  background-color: currentColor;
  -webkit-mask: var(--g) no-repeat center / contain;
  mask: var(--g) no-repeat center / contain;
}

/* ── the follow-up dial ──────────────────────────────────────────────────
   The business row's mark. It answers two questions in the fourteen pixels
   the state dot spent on one: how much of the follow-up window is gone, and
   whether the record is closed out. The sweep is `stroke-dasharray`, written
   by the `windowArc` format from `priority` and `scheduledFor` at render time
   (public/app.js) — the same route the sparkline already takes, so nothing
   about the mark is stored. Past due the dial inverts to a solid disc and the
   sweep continues as a notch bitten out of the rim: alarm by inversion, which
   survives a theme flip, rather than by hue, which does not.
   A business with nothing scheduled has no window, so it gets a dotted track
   and no arc. An empty state is information; a plausible fill is a lie. */
.dial { width: 14px; height: 14px; flex: 0 0 14px; display: block; color: var(--ink); }
.dial svg { display: block; width: 100%; height: 100%; overflow: visible; }
.dial .dial-track { opacity: .36; }
.dial .dial-origin { opacity: .55; }
.dial .dial-pip { display: none; }
.dial.visited .dial-pip { display: block; }
.dial.none .dial-track { opacity: .6; stroke-dasharray: 1 1.9; }
.dial.none .dial-arc, .dial.none .dial-origin { display: none; }
.dial.over .dial-track { opacity: 1; fill: currentColor; stroke-width: 2.1; }
.dial.over .dial-arc { stroke: var(--win); }
.dial.over.visited .dial-pip { display: block; fill: var(--win); }

/* ── the trade chit ──────────────────────────────────────────────────────
   What you are walking into, before you read the name. Keyed off the
   business's own `type`, so it is the record rather than a derivation and
   cannot go stale. The 246 trades outside the drawn ten get the blank chit,
   which is an honest "not one of these", not a guess. */
.trade {
  width: 14px; height: 14px; flex: 0 0 14px; display: block;
  background-color: currentColor; color: var(--ink-2);
  -webkit-mask: var(--g) no-repeat center / contain;
  mask: var(--g) no-repeat center / contain;
}

/* GLYPHS:BEGIN — generated by scripts/build-glyph-css.mjs */
/* ── the destinations: the rail at 26, the dock at 15 ──────────────────
   Two grades, not one drawing scaled. `.chip` takes the plan grade and
   `.ico` the plat grade, so a mark is never shown below the size it was
   drawn for. scripts/glyphs.mjs is the source; this block is generated. */
.chip.g-today { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Ccircle cx='12' cy='12' r='6.9' fill='none' stroke-width='1.3'/%3E%3Crect x='9.30' y='9.30' width='5.4' height='5.4' transform='rotate(45 12 12)' stroke='none'/%3E%3Crect x='10.55' y='1.7' width='2.9' height='2.9' stroke='none'/%3E%3Crect x='10.55' y='19.4' width='2.9' height='2.9' stroke='none'/%3E%3Crect x='1.7' y='10.55' width='2.9' height='2.9' stroke='none'/%3E%3Crect x='19.4' y='10.55' width='2.9' height='2.9' stroke='none'/%3E%3C/svg%3E"); }
.ico.g-today { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Ccircle cx='12' cy='12' r='7.2' fill='none' stroke-width='2.2'/%3E%3Crect x='9.00' y='9.00' width='6' height='6' transform='rotate(45 12 12)' stroke='none'/%3E%3C/svg%3E"); }
.chip.g-inbox { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Cpath d='M2.2 13.2H8.8M15.2 13.2H21.8' fill='none' stroke-width='2' stroke-linecap='butt'/%3E%3Crect x='4.2' y='3.6' width='5.8' height='5.8' stroke='none'/%3E%3Crect x='14' y='3.6' width='5.8' height='5.8' stroke='none'/%3E%3Crect x='9.1' y='16.4' width='5.8' height='5.8' stroke='none'/%3E%3C/svg%3E"); }
.ico.g-inbox { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Cpath d='M2 13.2H8.6M15.4 13.2H22' fill='none' stroke-width='2.4' stroke-linecap='butt'/%3E%3Crect x='3.6' y='3.2' width='6.6' height='6.6' stroke='none'/%3E%3Crect x='13.8' y='3.2' width='6.6' height='6.6' stroke='none'/%3E%3Crect x='8.7' y='16.2' width='6.6' height='6.6' stroke='none'/%3E%3C/svg%3E"); }
.chip.g-places { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Cg%3E%3Cpath d='M 6.0 3.4 H 20.6 V 20.0 H 3.4 V 6.8 Z' fill='none' stroke-width='1.3' stroke-linejoin='miter' stroke-linecap='butt'/%3E%3Crect x='6.6' y='7.6' width='7.4' height='5.6' stroke='none'/%3E%3Crect x='14.4' y='13.4' width='4.6' height='4.4' stroke='none'/%3E%3Crect x='6.6' y='15' width='5.2' height='3' stroke='none'/%3E%3C/g%3E%3C/svg%3E"); }
.ico.g-places { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Cpath d='M 6.2 3.6 H 20.4 V 19.8 H 3.6 V 6.8 Z' fill='none' stroke-width='2' stroke-linejoin='miter' stroke-linecap='butt'/%3E%3Crect x='6.6' y='7.6' width='7.4' height='5.4' stroke='none'/%3E%3Crect x='14.2' y='13.6' width='4.6' height='4' stroke='none'/%3E%3C/svg%3E"); }
.chip.g-street { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Cpath d='M8.8 2V8.8M8.8 15.2V22M15.2 2V8.8M15.2 15.2V22M2 8.8H8.8M15.2 8.8H22M2 15.2H8.8M15.2 15.2H22' fill='none' stroke-width='1.6' stroke-linecap='butt'/%3E%3Cpath d='M12 3.2V6M12 18V20.8M3.2 12H6M18 12H20.8' fill='none' stroke-width='1.8' stroke-linecap='butt'/%3E%3C/svg%3E"); }
.ico.g-street { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Cpath d='M8.4 1.8V8.4M8.4 15.6V22.2M15.6 1.8V8.4M15.6 15.6V22.2M1.8 8.4H8.4M15.6 8.4H22.2M1.8 15.6H8.4M15.6 15.6H22.2' fill='none' stroke-width='2.2' stroke-linecap='butt'/%3E%3C/svg%3E"); }
.chip.g-map { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Cpath d='M2.6 5.6L8.6 3.2l6.8 2.4 6-2.4v15.2l-6 2.4-6.8-2.4-6 2.4z' fill='none' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M8.6 3.2v15.2M15.4 5.6v15.2' fill='none' stroke-width='1.4' stroke-linecap='butt'/%3E%3C/svg%3E"); }
.ico.g-map { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Crect x='1.8' y='5.4' width='6' height='15.4' stroke='none'/%3E%3Crect x='9' y='3.2' width='6' height='15.4' stroke='none'/%3E%3Crect x='16.2' y='5.4' width='6' height='15.4' stroke='none'/%3E%3C/svg%3E"); }
.chip.g-problems { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Cg%3E%3Crect x='3' y='4.4' width='11.4' height='10.2' fill='none' stroke-width='1.3' stroke-linejoin='miter'/%3E%3Crect x='9.6' y='9.4' width='11.4' height='10.2' fill='none' stroke-width='1.3' stroke-linejoin='miter'/%3E%3Crect x='9.6' y='9.4' width='4.8' height='5.2' stroke='none'/%3E%3C/g%3E%3C/svg%3E"); }
.ico.g-problems { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Crect x='2.8' y='4.2' width='11' height='10' fill='none' stroke-width='2.2' stroke-linejoin='miter'/%3E%3Crect x='10.2' y='9.6' width='11' height='10' fill='none' stroke-width='2.2' stroke-linejoin='miter'/%3E%3Crect x='10.2' y='9.6' width='3.6' height='4.6' stroke='none'/%3E%3C/svg%3E"); }
.chip.g-contacts { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Crect x='6.4' y='8.8' width='11.2' height='6.4' stroke='none' rx='3.2'/%3E%3Ccircle cx='8.7' cy='5.3' r='1.75' stroke='none'/%3E%3Ccircle cx='12' cy='5.3' r='1.75' stroke='none'/%3E%3Ccircle cx='15.3' cy='5.3' r='1.75' stroke='none'/%3E%3Ccircle cx='8.7' cy='18.7' r='1.75' stroke='none'/%3E%3Ccircle cx='12' cy='18.7' r='1.75' stroke='none'/%3E%3Ccircle cx='15.3' cy='18.7' r='1.75' stroke='none'/%3E%3Ccircle cx='3.5' cy='12' r='1.75' stroke='none'/%3E%3C/svg%3E"); }
.ico.g-contacts { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Crect x='5.2' y='9.4' width='13.6' height='5.2' stroke='none' rx='2.6'/%3E%3Ccircle cx='8.6' cy='4.6' r='2' stroke='none'/%3E%3Ccircle cx='15.4' cy='4.6' r='2' stroke='none'/%3E%3Ccircle cx='8.6' cy='19.4' r='2' stroke='none'/%3E%3Ccircle cx='15.4' cy='19.4' r='2' stroke='none'/%3E%3C/svg%3E"); }
.chip.g-pinboards { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Cg%3E%3Crect x='2.9' y='4.4' width='18.2' height='15.2' fill='none' stroke-width='1.4' stroke-linejoin='miter' stroke-dasharray='3.0 2.2' rx='1'/%3E%3Crect x='5.4' y='6.8' width='5.6' height='4.6' stroke='none'/%3E%3Crect x='12.6' y='6.8' width='5.4' height='2.9' stroke='none'/%3E%3Crect x='12.6' y='11.2' width='5.4' height='6' stroke='none'/%3E%3Crect x='5.4' y='13.2' width='5.6' height='4' stroke='none'/%3E%3C/g%3E%3C/svg%3E"); }
.ico.g-pinboards { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Crect x='2.8' y='4.6' width='7.6' height='6' stroke='none'/%3E%3Crect x='12.6' y='4.6' width='8.6' height='3.6' stroke='none'/%3E%3Crect x='2.8' y='12.6' width='5.4' height='6.8' stroke='none'/%3E%3Crect x='10.8' y='11' width='10.4' height='8.2' stroke='none'/%3E%3C/svg%3E"); }
.chip.g-projects { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Cg%3E%3Cpath d='M 3.8 5.2 H 17.6 L 20.2 8.4 V 18.8 H 3.8 Z' fill='none' stroke-width='1.3' stroke-linejoin='miter' stroke-linecap='butt'/%3E%3Crect x='3.8' y='5.2' width='7' height='13.6' stroke='none'/%3E%3Cline x1='10.8' y1='5.2' x2='10.8' y2='18.8' fill='none' stroke-width='2.4' stroke-linecap='butt'/%3E%3Cline x1='11' y1='9.37' x2='12.33' y2='5.2' fill='none' stroke-width='1.15' stroke-linecap='butt'/%3E%3Cline x1='11.2' y1='18.8' x2='15.5' y2='5.2' fill='none' stroke-width='1.15' stroke-linecap='butt'/%3E%3Cline x1='14.4' y1='18.8' x2='18.41' y2='6.2' fill='none' stroke-width='1.15' stroke-linecap='butt'/%3E%3Cline x1='17.6' y1='18.8' x2='20.2' y2='10.62' fill='none' stroke-width='1.15' stroke-linecap='butt'/%3E%3C/g%3E%3C/svg%3E"); }
.ico.g-projects { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Cpath d='M 3.6 5.4 H 17.6 L 20.4 8.8 V 18.6 H 3.6 Z' fill='none' stroke-width='2.2' stroke-linejoin='miter' stroke-linecap='butt'/%3E%3Crect x='3.6' y='5.4' width='7.2' height='13.2' stroke='none'/%3E%3Cline x1='10.8' y1='5.4' x2='10.8' y2='18.6' fill='none' stroke-width='2.8' stroke-linecap='butt'/%3E%3C/svg%3E"); }
.chip.g-outreach { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Crect x='2.6' y='4' width='2.8' height='16' stroke='none'/%3E%3Cpath d='M5.4 7.2H13.2M5.4 12H18M5.4 16.8H10.2' fill='none' stroke-width='1.5' stroke-linecap='butt'/%3E%3Crect x='13.4' y='5.4' width='3.6' height='3.6' stroke='none'/%3E%3Crect x='18.2' y='10.2' width='3.6' height='3.6' stroke='none'/%3E%3Crect x='10.4' y='15' width='3.6' height='3.6' stroke='none'/%3E%3C/svg%3E"); }
.ico.g-outreach { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Crect x='2.4' y='4' width='3.4' height='16' stroke='none'/%3E%3Cpath d='M5.8 7.6H12.6M5.8 12H17.6' fill='none' stroke-width='2.2' stroke-linecap='butt'/%3E%3Crect x='12.8' y='5.4' width='4.4' height='4.4' stroke='none'/%3E%3Crect x='17.8' y='9.8' width='4.4' height='4.4' stroke='none'/%3E%3Crect x='5.8' y='15.6' width='4.4' height='4.4' stroke='none'/%3E%3C/svg%3E"); }
.chip.g-walkby { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Cg%3E%3Crect x='3.4' y='4.2' width='5' height='5.4' stroke='none'/%3E%3Crect x='9.5' y='4.2' width='4.2' height='5.4' stroke='none'/%3E%3Crect x='14.8' y='4.2' width='5.6' height='5.4' stroke='none'/%3E%3Cline x1='11.6' y1='9.6' x2='11.6' y2='15' fill='none' stroke-width='1.6' stroke-linecap='butt'/%3E%3Cline x1='2.6' y1='15' x2='21.4' y2='15' fill='none' stroke-width='2.3' stroke-linecap='round' stroke-dasharray='0.01 2.99'/%3E%3C/g%3E%3C/svg%3E"); }
.ico.g-walkby { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Crect x='3.4' y='4.4' width='6.4' height='5.8' stroke='none'/%3E%3Crect x='13.4' y='4.4' width='7.2' height='5.8' stroke='none'/%3E%3Cline x1='12' y1='10.2' x2='12' y2='16.8' fill='none' stroke-width='2.6' stroke-linecap='butt'/%3E%3Cline x1='4' y1='16.8' x2='20' y2='16.8' fill='none' stroke-width='3.4' stroke-linecap='round' stroke-dasharray='0.01 5.33'/%3E%3C/svg%3E"); }
.chip.g-doorstep { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Crect x='4.6' y='4.4' width='14.8' height='9.6' stroke='none'/%3E%3Cpath d='M12 14.6V19.2' fill='none' stroke-width='1.6' stroke-linecap='butt'/%3E%3Cpath d='M8.4 19.4H15.6' fill='none' stroke-width='1.8' stroke-linecap='butt'/%3E%3C/svg%3E"); }
.ico.g-doorstep { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Crect x='3.6' y='3.8' width='16.8' height='10.4' stroke='none'/%3E%3Cpath d='M12 14.8V19' fill='none' stroke-width='2.2' stroke-linecap='butt'/%3E%3Cpath d='M7.4 19.6H16.6' fill='none' stroke-width='2.4' stroke-linecap='butt'/%3E%3C/svg%3E"); }
.chip.g-notes { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Crect x='4.4' y='2.8' width='15.2' height='18.4' fill='none' stroke-width='1.3' stroke-linejoin='miter'/%3E%3Crect x='7' y='6.2' width='10' height='1.8' stroke='none'/%3E%3Crect x='7' y='10' width='10' height='1.8' stroke='none'/%3E%3Crect x='7' y='13.8' width='6.4' height='1.8' stroke='none'/%3E%3C/svg%3E"); }
.ico.g-notes { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Crect x='4' y='2.6' width='16' height='18.8' fill='none' stroke-width='2.2' stroke-linejoin='miter'/%3E%3Crect x='7.4' y='7.2' width='9.2' height='2.4' stroke='none'/%3E%3Crect x='7.4' y='12.4' width='6' height='2.4' stroke='none'/%3E%3C/svg%3E"); }
.chip.g-attachments { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Crect x='3.4' y='4.6' width='17.2' height='14.8' fill='none' stroke-width='1.3' stroke-linejoin='miter'/%3E%3Crect x='3.4' y='13.4' width='17.2' height='6' stroke='none'/%3E%3Crect x='6.2' y='7.2' width='3.6' height='3.6' stroke='none'/%3E%3C/svg%3E"); }
.ico.g-attachments { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Crect x='3' y='4.4' width='18' height='15.2' fill='none' stroke-width='2.2' stroke-linejoin='miter'/%3E%3Crect x='3' y='12.8' width='18' height='6.8' stroke='none'/%3E%3Crect x='6' y='6.8' width='4.2' height='4.2' stroke='none'/%3E%3C/svg%3E"); }
.chip.g-reach { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Crect x='10.4' y='10.4' width='3.2' height='3.2' stroke='none'/%3E%3Crect x='7' y='7' width='10' height='10' fill='none' stroke-width='1.4' stroke-linejoin='miter'/%3E%3Crect x='3.2' y='3.2' width='17.6' height='17.6' fill='none' stroke-width='1.4' stroke-linejoin='miter' stroke-dasharray='3.2 2.4'/%3E%3C/svg%3E"); }
.ico.g-reach { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Crect x='9.6' y='9.6' width='4.8' height='4.8' stroke='none'/%3E%3Crect x='5.6' y='5.6' width='12.8' height='12.8' fill='none' stroke-width='2.4' stroke-linejoin='miter'/%3E%3C/svg%3E"); }
.chip.g-research { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Crect x='3.2' y='4.2' width='3.6' height='3.6' stroke='none'/%3E%3Crect x='8.4' y='5.2' width='12.4' height='1.8' stroke='none'/%3E%3Crect x='3.2' y='10.2' width='3.6' height='3.6' stroke='none'/%3E%3Crect x='8.4' y='11.2' width='9' height='1.8' stroke='none'/%3E%3Crect x='3.2' y='16.2' width='3.6' height='3.6' fill='none' stroke-width='1.3' stroke-linejoin='miter'/%3E%3Crect x='8.4' y='17.2' width='5.4' height='1.8' stroke='none'/%3E%3C/svg%3E"); }
.ico.g-research { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Crect x='2.8' y='4' width='4.6' height='4.6' stroke='none'/%3E%3Crect x='9.2' y='5.2' width='12' height='2.4' stroke='none'/%3E%3Crect x='2.8' y='12' width='4.6' height='4.6' stroke='none'/%3E%3Crect x='9.2' y='13.2' width='8' height='2.4' stroke='none'/%3E%3C/svg%3E"); }
.chip.g-brief { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Crect x='4.4' y='2.8' width='15.2' height='18.4' fill='none' stroke-width='1.3' stroke-linejoin='miter'/%3E%3Crect x='4.4' y='2.8' width='15.2' height='6' stroke='none'/%3E%3Crect x='7' y='11.6' width='10' height='1.8' stroke='none'/%3E%3Crect x='7' y='15.6' width='6.4' height='1.8' stroke='none'/%3E%3C/svg%3E"); }
.ico.g-brief { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23000' stroke='%23000'%3E%3Crect x='4' y='2.6' width='16' height='18.8' fill='none' stroke-width='2.2' stroke-linejoin='miter'/%3E%3Crect x='4' y='2.6' width='16' height='6.6' stroke='none'/%3E%3Crect x='7.4' y='13' width='9.2' height='2.4' stroke='none'/%3E%3C/svg%3E"); }

/* ── the kinds of chat, at 18 beside a name ─────────────────────── */
.chm.g-chat-log { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 18' fill='%23000' stroke='%23000'%3E%3Crect x='2.8' y='3.4' width='12.4' height='11.2' rx='1.6' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Cpath d='M5.6 7h6.8M5.6 10h6.8M5.6 13h3.6' fill='none' stroke='%23000' stroke-width='1.3' stroke-linecap='round'/%3E%3C/svg%3E"); }
.chm.g-chat-branch { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 18' fill='%23000' stroke='%23000'%3E%3Cpath d='M5 2.6v12.8M5 7.4h4.2a3.4 3.4 0 0 1 3.4 3.4v4.4' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round'/%3E%3Ccircle cx='5' cy='4.2' r='1.6' fill='%23000' stroke='none'/%3E%3Ccircle cx='12.6' cy='14' r='1.6' fill='%23000' stroke='none'/%3E%3C/svg%3E"); }
.chm.g-chat-persona { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 18' fill='%23000' stroke='%23000'%3E%3Ccircle cx='9' cy='9' r='6.2' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Cpath d='M9 2.8a6.2 6.2 0 0 1 0 12.4z' fill='%23000' stroke='none'/%3E%3C/svg%3E"); }
.chm.g-chat-team { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 18' fill='%23000' stroke='%23000'%3E%3Ccircle cx='6.4' cy='9' r='3.9' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Ccircle cx='11.6' cy='9' r='3.9' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3C/svg%3E"); }
.chm.g-chat-routine { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 18' fill='%23000' stroke='%23000'%3E%3Ccircle cx='9' cy='9' r='6.2' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Cpath d='M9 5.4V9l2.6 1.8' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.chm.g-chat-tool { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 18' fill='%23000' stroke='%23000'%3E%3Cpath d='M4.2 2.6h6.2l3.4 3.4v9.4H4.2z' fill='none' stroke='%23000' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10.4 2.6V6h3.4' fill='none' stroke='%23000' stroke-width='1.3' stroke-linejoin='round'/%3E%3Cpath d='M6.6 9.2l1.9 1.6-1.9 1.6' fill='none' stroke='%23000' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }

/* ── the controls: what pressing will do, at 16 ──────────────────────────
   One grade. A control is drawn at sixteen and shown at sixteen, so the
   plan/plat split the destinations need does not apply to it. */
.ctl.g-chevron { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M6.1 3.3L10.8 8l-4.7 4.7' fill='none' stroke='%23000' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.ctl.g-search { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Ccircle cx='7.1' cy='7.1' r='4.1' fill='none' stroke='%23000' stroke-width='1.6'/%3E%3Cpath d='M10.2 10.2l3.1 3.1' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E"); }
.ctl.g-filter { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M2.5 3.4h11L9.2 8.3v4.1l-2.4 1.4V8.3z' fill='none' stroke='%23000' stroke-width='1.5' stroke-linejoin='miter' stroke-linecap='butt'/%3E%3C/svg%3E"); }
.ctl.g-close { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M4.3 4.3l7.4 7.4M11.7 4.3l-7.4 7.4' fill='none' stroke='%23000' stroke-width='1.7' stroke-linecap='round'/%3E%3C/svg%3E"); }
.ctl.g-plus { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M8 3.2v9.6M3.2 8h9.6' fill='none' stroke='%23000' stroke-width='1.7' stroke-linecap='round'/%3E%3C/svg%3E"); }
.ctl.g-check { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M3.3 8.3l3.2 3.3 6.2-7.1' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.ctl.g-peek { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Crect x='2.3' y='3.1' width='11.4' height='9.8' rx='1.4' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Cpath d='M9.3 3.1h3a1.4 1.4 0 0 1 1.4 1.4v7a1.4 1.4 0 0 1-1.4 1.4h-3z' fill='%23000' stroke='none'/%3E%3C/svg%3E"); }
.ctl.g-centre { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Crect x='2.3' y='3.1' width='11.4' height='9.8' rx='1.4' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Crect x='5.3' y='5.6' width='5.4' height='4.8' rx='1' fill='%23000' stroke='none'/%3E%3C/svg%3E"); }
.ctl.g-full { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Crect x='2.3' y='3.1' width='11.4' height='9.8' rx='1.4' fill='%23000' stroke='none'/%3E%3C/svg%3E"); }
.ctl.g-switchon { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M5.2 4.6h5.6a3.4 3.4 0 0 1 0 6.8H5.2a3.4 3.4 0 0 1 0-6.8zM10.8 5.9a2.1 2.1 0 1 0 0 4.2a2.1 2.1 0 1 0 0-4.2z' fill='%23000' fill-rule='evenodd' stroke='none'/%3E%3C/svg%3E"); }
.ctl.g-switchoff { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Crect x='2.3' y='5.1' width='11.4' height='5.8' rx='2.9' fill='none' stroke='%23000' stroke-width='1.3'/%3E%3Ccircle cx='5.3' cy='8' r='1.6' fill='%23000' stroke='none'/%3E%3C/svg%3E"); }
.ctl.g-minus { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M3.2 8h9.6' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.ctl.g-fit { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M2.6 6.2V3.3a.7.7 0 0 1 .7-.7h2.9M9.8 2.6h2.9a.7.7 0 0 1 .7.7v2.9M13.4 9.8v2.9a.7.7 0 0 1-.7.7H9.8M6.2 13.4H3.3a.7.7 0 0 1-.7-.7V9.8' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3Ccircle cx='8' cy='8' r='1.9' fill='%23000' stroke='none'/%3E%3C/svg%3E"); }
.ctl.g-lasso { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cellipse cx='8.6' cy='6.4' rx='5.6' ry='3.9' fill='none' stroke='%23000' stroke-width='1.5' stroke-dasharray='2.2 1.8'/%3E%3Ccircle cx='4.2' cy='11.2' r='1.6' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Cpath d='M3.4 12.6c-.3 1-1 1.7-1.8 1.9' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.ctl.g-optimise { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Ccircle cx='3.4' cy='12.2' r='1.7' fill='%23000' stroke='none'/%3E%3Ccircle cx='12.6' cy='3.8' r='1.7' fill='%23000' stroke='none'/%3E%3Cpath d='M5 12.2h4.3a2.3 2.3 0 0 0 0-4.6H6.7a2.3 2.3 0 0 1 0-4.6H11' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.ctl.g-undo { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M6 9.4L2.7 6 6 2.7' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2.9 6h7a3.6 3.6 0 0 1 0 7.2H7.2' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.ctl.g-trash { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M2.6 4.6h10.8M6.6 7.2v4.2M9.4 7.2v4.2M4 4.6l.7 8.1a1.3 1.3 0 0 0 1.3 1.2h4a1.3 1.3 0 0 0 1.3-1.2l.7-8.1M6 4.6V2.9a.6.6 0 0 1 .6-.6h2.8a.6.6 0 0 1 .6.6v1.7' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.ctl.g-copy { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Crect x='5.6' y='5.6' width='7.8' height='7.8' rx='1.3' fill='none' stroke='%23000' stroke-width='1.6'/%3E%3Cpath d='M10.4 5.6V3.9a1.3 1.3 0 0 0-1.3-1.3H3.9a1.3 1.3 0 0 0-1.3 1.3v5.2a1.3 1.3 0 0 0 1.3 1.3h1.7' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.ctl.g-qr { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Crect x='2.4' y='2.4' width='4.4' height='4.4' rx='.6' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Crect x='9.2' y='2.4' width='4.4' height='4.4' rx='.6' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Crect x='2.4' y='9.2' width='4.4' height='4.4' rx='.6' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Cpath d='M9 9h2v2H9zM12 12h1.8v1.8H12zM9 12.4h1.6v1.4H9zM12.2 9h1.6v1.6h-1.6z' fill='%23000' stroke='none'/%3E%3C/svg%3E"); }
.ctl.g-go { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M13.7 2.3a.6.6 0 0 0-.7-.1L2.6 6.6a.6.6 0 0 0 .1 1.1l4.3 1.3 1.3 4.3a.6.6 0 0 0 1.1.1l4.4-10.4a.6.6 0 0 0-.1-.7z' fill='%23000' stroke='none'/%3E%3C/svg%3E"); }
.ctl.g-drive { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M3.3 10.6V7.8l1.3-3.1a1.3 1.3 0 0 1 1.2-.8h4.4a1.3 1.3 0 0 1 1.2.8l1.3 3.1v2.8' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2.2 10.6h11.6v1.6a.5.5 0 0 1-.5.5h-1.5a.5.5 0 0 1-.5-.5v-.6H4.7v.6a.5.5 0 0 1-.5.5H2.7a.5.5 0 0 1-.5-.5z' fill='%23000' stroke='none'/%3E%3Cpath d='M3.6 7.8h8.8' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.ctl.g-walk { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Ccircle cx='8.9' cy='2.7' r='1.3' fill='%23000' stroke='none'/%3E%3Cpath d='M6.2 14.2l1.6-4.3 2 1.9v2.4M7.8 9.9l.6-3.6-2.3.9-1 2.2M8.4 6.3l1.4 2.2 2.1.7' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.ctl.g-keys { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Crect x='1.6' y='4' width='12.8' height='8' rx='1.3' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Cpath d='M4 6.4h1.2v1.2H4zM7.4 6.4h1.2v1.2H7.4zM10.8 6.4H12v1.2h-1.2zM5.6 9.2h4.8v1.2H5.6z' fill='%23000' stroke='none'/%3E%3C/svg%3E"); }
.ctl.g-phone { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M3.4 2.6h2.4l1.2 2.9-1.5 1a7.3 7.3 0 0 0 4 4l1-1.5 2.9 1.2v2.4a.7.7 0 0 1-.7.7A10.7 10.7 0 0 1 2.7 3.3a.7.7 0 0 1 .7-.7z' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.ctl.g-web { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Ccircle cx='8' cy='8' r='5.7' fill='none' stroke='%23000' stroke-width='1.5'/%3E%3Cpath d='M2.4 8h11.2M8 2.3c1.6 1.6 2.3 3.5 2.3 5.7S9.6 12.1 8 13.7C6.4 12.1 5.7 10.2 5.7 8S6.4 3.9 8 2.3z' fill='none' stroke='%23000' stroke-width='1.3'/%3E%3C/svg%3E"); }
.ctl.g-directions { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M8 1.8l6.2 6.2L8 14.2 1.8 8z' fill='none' stroke='%23000' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M6.2 10V8.3a1 1 0 0 1 1-1h2.9M9.1 5.6l1.7 1.7-1.7 1.7' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.ctl.g-grip { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M5.4 3.2h1.8V5H5.4zM8.8 3.2h1.8V5H8.8zM5.4 7.1h1.8v1.8H5.4zM8.8 7.1h1.8v1.8H8.8zM5.4 11h1.8v1.8H5.4zM8.8 11h1.8v1.8H8.8z' fill='%23000' stroke='none'/%3E%3C/svg%3E"); }
.ctl.g-reverse { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M4.8 13V3M2.4 5.4L4.8 3l2.4 2.4M11.2 3v10M8.8 10.6l2.4 2.4 2.4-2.4' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }

/* ── the trade: what you are walking into, before you read the name ──── */
.trade-restaurant { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M4.4 2.6h1.1v4h.7v-4h1.1v4h.7v-4h1.1v4.4a1.9 1.9 0 0 1-1.3 1.8v4.6H5.7V8.8A1.9 1.9 0 0 1 4.4 7zM11.9 2.6c1.2.9 1.7 3 1.4 5.1h-1v5.7h-1.3V2.6z' fill='%23000' stroke='none' stroke-width='1'/%3E%3C/svg%3E"); }
.trade-fast-food { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M8 2.9c2.9 0 5 1.4 5.2 2.9H2.8C3 4.3 5.1 2.9 8 2.9zM2.6 6.8h10.8v1.6H2.6zM2.6 9.6h10.8v.9c0 1.6-1 2.6-2.6 2.6H5.2c-1.6 0-2.6-1-2.6-2.6z' fill='%23000' stroke='none' stroke-width='1'/%3E%3C/svg%3E"); }
.trade-beauty { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Ccircle cx='8' cy='5.9' r='3.5' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='miter'/%3E%3Cpath d='M6.9 9.7h2.2v3.3a1.1 1.1 0 0 1-2.2 0z' fill='%23000' stroke='none' stroke-width='1'/%3E%3C/svg%3E"); }
.trade-dentist { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M8 3c1.3-.9 3-1.2 4.1-.3 1.3 1 1.5 3 1 5.2-.4 1.9-.8 3.4-1.3 4.6-.4 1-1.5.9-1.7-.2l-.5-2.7c-.1-.8-1.1-.8-1.2 0l-.5 2.7c-.2 1.1-1.3 1.2-1.7.2-.5-1.2-.9-2.7-1.3-4.6-.5-2.2-.3-4.2 1-5.2C6.9 1.8 8 2.6 8 3z' fill='%23000' stroke='none' stroke-width='1'/%3E%3C/svg%3E"); }
.trade-clinic { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M6.3 2.6h3.4v3.7h3.7v3.4H9.7v3.7H6.3V9.7H2.6V6.3h3.7z' fill='%23000' stroke='none' stroke-width='1'/%3E%3C/svg%3E"); }
.trade-convenience { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M5.4 5.6V4.5a2.6 2.6 0 0 1 5.2 0v1.1' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='miter'/%3E%3Cpath d='M2.7 6.3h10.6l-1 6.1a1.3 1.3 0 0 1-1.3 1H5a1.3 1.3 0 0 1-1.3-1z' fill='%23000' stroke='none' stroke-width='1'/%3E%3C/svg%3E"); }
.trade-hairdresser { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M4.6 1.9l1.2.6 5 9.3-1.2.6zM11.4 1.9l1.2.6-5 9.3-1.2-.6z' fill='%23000' stroke='none' stroke-width='1'/%3E%3Ccircle cx='4.6' cy='11.9' r='1.7' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='miter'/%3E%3Ccircle cx='11.4' cy='11.9' r='1.7' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='miter'/%3E%3C/svg%3E"); }
.trade-car-repair { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M12.4 2.2a3.9 3.9 0 0 1-4.6 5.3l-4 4a1.5 1.5 0 0 1-2.1-2.1l4-4a3.9 3.9 0 0 1 5.3-4.6L9.2 3.5l.3 2 2 .3z' fill='%23000' stroke='none' stroke-width='1'/%3E%3C/svg%3E"); }
.trade-bar { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M2.4 2.7h11.2L8.7 8.5v3.6h2.5v1.3H4.8v-1.3h2.5V8.5zM4.6 4l1.3 1.5h4.2L11.4 4z' fill='%23000' stroke='none' stroke-width='1'/%3E%3C/svg%3E"); }
.trade-cafe { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Cpath d='M2.7 3.6h8.1v5.1A2.8 2.8 0 0 1 8 11.5H5.5a2.8 2.8 0 0 1-2.8-2.8z' fill='%23000' stroke='none' stroke-width='1'/%3E%3Cpath d='M11.3 5.3h1a1.7 1.7 0 0 1 0 3.4h-1' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='miter'/%3E%3Cpath d='M2.7 13.1h9.6' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='miter'/%3E%3C/svg%3E"); }
.trade-other { --g: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000' stroke='%23000'%3E%3Crect x='2.6' y='2.6' width='10.8' height='10.8' rx='1.6' fill='none' stroke='%23000' stroke-width='1' stroke-dasharray='2, 2.2' opacity='0.45'/%3E%3C/svg%3E"); }
/* GLYPHS:END */
.row.done .name { color: var(--muted); text-decoration: line-through; }

.n { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--ink-2); font-size: var(--font-sm); min-width: 16px; text-align: right; }
.mono { font: 500 11.5px var(--m); font-variant-numeric: tabular-nums; }
.small { font-size: 12px; color: var(--muted); }
.hair { height: 1px; background: var(--line); margin: 0; }
.tag { font-size: 11px; font-weight: 550; height: 18px; padding: 0 7px; border-radius: 99px; background: var(--line-2); color: var(--ink-2); white-space: nowrap; display: inline-flex; align-items: center; }
.tag.g, .tag.n { background: var(--line-2); color: var(--muted); }
.tag.w { background: transparent; color: var(--ink); font-weight: 600; padding: 0; }
/* The one alarm treatment: inversion. */
.alarm { background: var(--inv); color: var(--inv-ink); font: 600 11px var(--m); height: 18px; padding: 0 6px; border-radius: 3px; display: inline-flex; align-items: center; font-variant-numeric: tabular-nums; }

/* Sparkline — one series, 2px, endpoint dot, last value direct-labelled. */
.spark { display: inline-flex; align-items: center; gap: 6px; vertical-align: middle; color: var(--ink-2); }
.spark svg { display: block; overflow: visible; }
.spark .sv { font: 500 11px var(--m); color: var(--ink-2); font-variant-numeric: tabular-nums; }

.prog { height: 5px; border-radius: 3px; background: var(--line-2); overflow: hidden; display: flex; }
.prog i { display: block; height: 100%; background: var(--ink); }
.card { background: var(--win); border: 1px solid var(--line); border-radius: var(--r); padding: 11px; }
.kv { display: flex; justify-content: space-between; gap: 12px; padding: 5px 0; border-bottom: 1px solid var(--line-2); font-size: var(--font-sm); }
.kv:last-child { border-bottom: 0; }
.kv span { color: var(--muted); }
.kv b { font-weight: 600; font-variant-numeric: tabular-nums; }

/* ── actions: revealed on hover or focus, never hidden from the keyboard ── */
/* The cluster fades in on hover or focus, and is permanently visible where
   there is no hover to wait for.

   `pointer-events` was declared alongside every one of those opacities in
   phase 3 and WITHDRAWN in phase 6. The reasoning is kept, because the
   invariant it served — a control that cannot be seen must not take a press —
   reads as obviously right, and the next reader will want to put it back.

   Three measurements say not to. (1) The state it guards against could not be
   produced on any configuration this browser can emulate: with a mouse :hover
   applies before the press, so the cluster is lit by the time the press lands,
   and where there is no hover the block below shows it permanently. (2) The
   touch escape that made the change look free was not part of the change —
   `@media (hover: none) { .acts { opacity: 1 } }` was already in this file at
   8cef127, before the campaign, so phase 3 only ever added the dead-at-rest
   half. (3) That half cost three checks in two suites nobody here wrote —
   test/intake.mjs I3 and I4, test/outreach.mjs K6 — all of which press an
   action without arranging a hover first, which is what an ordinary press is.

   The device where the invariant would genuinely pay is the one reporting
   `hover: hover` while driven by touch — a hybrid laptop, a Windows
   touchscreen — where a tap fires no hover and the cluster is invisible and
   live. That pair cannot be emulated here. If it comes back it should come
   back with a way to demonstrate it, not on the strength of the invariant
   alone. test/rows.mjs R3 now guards the other direction: an ordinary press
   must reach the button from rest. */
.acts { display: flex; gap: 5px; opacity: 0; transition: opacity 120ms; }
.row:hover .acts, .row:focus-within .acts, article[data-open="1"] .acts { opacity: 1; }
@media (hover: none) { .acts { opacity: 1; } }

button, .btn {
  font: 500 var(--font-sm) var(--f); cursor: pointer;
  min-height: 32px; padding: 5px 10px; border-radius: 5px;
  border: 1px solid var(--line); background: var(--win); color: var(--ink);
}
button:hover { border-color: var(--muted); }
button.primary, .btnp { background: var(--ink); border-color: var(--ink); color: var(--win); font-weight: 600; }
.btng { background: var(--win); color: var(--ink); border: 1px solid var(--line); }
.btnd { background: var(--win); color: var(--ink); border: 1px solid var(--ink); }
button.quiet { border-color: transparent; background: transparent; color: var(--muted); }
button.danger { color: var(--danger); }
/* A destructive action is never reachable by hover alone (13-INTERFACE.md). */
button.danger { display: none; }
article[data-open="1"] button.danger { display: inline-flex; }

/* ── forms: inline, never modal ─────────────────────────────────────────── */
form { background: var(--win); border: 1px solid var(--line); border-radius: var(--r); padding: 13px; }
label { display: block; font-size: var(--font-sm); font-weight: 600; color: var(--muted); margin: var(--label-gap) 0 3px; }
input, textarea, select {
  width: 100%; padding: var(--field-pad); font: 400 16px var(--f);   /* 16px or iOS zooms the capture form */
  border: 1px solid var(--line); border-radius: 5px;
  background: var(--win); color: var(--ink);
}
textarea { min-height: 72px; resize: vertical; }
.fields { display: grid; grid-template-columns: 1fr 1fr; gap: 0 12px; }
@media (max-width: 460px) { .fields { grid-template-columns: 1fr; } }
.submit { display: flex; justify-content: flex-end; gap: 8px; margin-top: 13px; }
.field { width: 100%; border: 1px solid var(--line); border-radius: 5px; padding: 6px 9px; background: var(--win); font: 400 16px var(--f); color: var(--ink); }
.field.mono { font: 500 16px var(--m); }

/* ── the dock: the phone's whole navigation ─────────────────────────────── */
.dock {
  position: fixed; inset: auto 0 0 0; height: var(--dock-h);
  padding-bottom: env(safe-area-inset-bottom);
  display: flex; background: var(--win); border-top: 1px solid var(--line); z-index: 40;
}
.dock a {
  flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; font-size: 10px; font-weight: 600; color: var(--muted); min-height: 44px;
}
.dock a[aria-current] { color: var(--ink); }
.dock .ico { width: 17px; height: 17px; }
.dock-act {
  flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; font-size: 10px; font-weight: 600; color: var(--accent);
  min-height: 44px; border: 0; border-left: 1px solid var(--line); background: transparent; border-radius: 0;
}
.dock-act .ico { width: 17px; height: 17px; }
@media (max-width: 420px) { .dock a, .dock-act { font-size: 9.5px; } .dock .ico { width: 16px; height: 16px; } }

/* ── the desk posture: rail, second column, floating window ──────────────
   The section you are in is stated by the rail, not repeated in a bar above
   the content, so there is no top bar here at all. */
@media (min-width: 900px) {
  body { padding-bottom: 0; height: 100vh; overflow: hidden; display: flex; }
  .dock, .dock-act { display: none; }

  /* PORTAGE phase 8. The drawn column is `--rail-col`, which DEFAULTS to the
     token and is only ever overridden below, on the element, for the
     labels-off posture. `--rail-w` itself never moves.

     That is not fussiness. The profile menu on the unmerged branch anchors
     itself at `calc(var(--rail-w) - 4px)`; redefining the token for one state
     would silently move a menu this phase cannot see or measure. When that
     branch lands, its anchor becomes `--rail-col` and follows the rail — a
     one-word change — and until then it is unchanged and still correct. */
  .rail {
    display: flex; flex: 0 0 var(--rail-col, var(--rail-w)); width: var(--rail-col, var(--rail-w));
    flex-direction: column; align-items: center; background: transparent;
    transition: flex-basis 140ms, width 140ms;
  }

  /* Marks only. The chip is 26px and the rail's own padding is 2px a side, so
     44px leaves the mark the same air it had and takes 20px back for the work.
     The labels are `display: none` rather than hidden by opacity, because a
     label that still reserves its line would leave the rail the same height
     per destination and take nothing back vertically either. */
  body[data-rail="marks"] { --rail-col: 44px; }
  body[data-rail="marks"] .rail .rl { display: none; }
  body[data-rail="marks"] .rail a { padding: 5px 2px; }
  @media (prefers-reduced-motion: reduce) { .rail { transition: none; } }
  .railhd { width: 100%; height: 46px; min-height: 46px; display: flex; align-items: center; justify-content: center; }
  .org { width: 26px; height: 26px; border-radius: 7px; background: var(--ink); color: var(--win); font: 700 10px var(--m); display: flex; align-items: center; justify-content: center; }
  .rail ul { list-style: none; margin: 0; padding: 2px; width: 100%; display: flex; flex-direction: column; align-items: center; gap: 1px; }
  .rail a { width: 100%; padding: 4px 2px 5px; border-radius: 6px; display: flex; flex-direction: column; align-items: center; gap: 2px; color: var(--muted); }
  .rail .chip { width: 26px; height: 26px; border-radius: 6px; display: flex; align-items: center; justify-content: center; --g-size: 21px; }
  .rail .rl { font-size: 10px; font-weight: 600; letter-spacing: -.01em; }
  .rail a:hover .chip { background: var(--line-2); }
  .rail a[aria-current] { color: var(--ink); }
  .rail a[aria-current] .chip { background: var(--win); }
  .railft { margin-top: auto; width: 100%; padding: 2px 2px 8px; display: flex; flex-direction: column; align-items: center; gap: 6px; }
  .me { width: 24px; height: 24px; border-radius: 50%; background: var(--line); color: var(--ink-2); font: 600 9.5px var(--m); display: flex; align-items: center; justify-content: center; }

  /* The secondary column is navigation, never results. It answers "what else
     is in here", so it holds destinations and their counts and nothing else.
     Every count is a live expression, so none of them can go stale. */
  .second { display: flex; flex: 0 0 var(--second-w); width: var(--second-w); flex-direction: column; min-height: 0; background: transparent; }
  .second > header { height: 46px; min-height: 46px; padding: 0 6px 0 4px; display: flex; align-items: center; }
  .second > header b { font: 700 14px var(--d); color: var(--ink); letter-spacing: -.01em; }
  /* PORTAGE phase 7. The mark is `g-chevron`, the same one the row's caret
     uses, rotated by the state — so the column and the row say "this opens"
     with one drawing. It replaces a literal ‹, which was a Unicode
     character standing in for a control on all sixteen pages and is the one
     thing the phase-2 mark set exists to stop. */
  .collapse { margin-left: auto; display: inline-flex; align-items: center; justify-content: center;
              width: 20px; height: 20px; border: 0; background: transparent; border-radius: 4px;
              color: var(--muted); line-height: 1; min-height: 20px; padding: 0; --ctl-size: 14px; }
  .collapse:hover { background: var(--line-2); border-color: transparent; color: var(--ink); }
  .collapse .ctl { transform: rotate(180deg); transition: transform 140ms; }

  /* The collapsed column. The width goes to a sliver and the content goes to
     nothing, and the element stays mounted the whole time — so coming back is
     a width transition and not a render. Nothing here refetches.

     The sliver is 30px because it holds the one control that closed it. The
     alternative, zero, takes that control down with the column and needs a
     second one somewhere else to bring it back, which is two controls for one
     state. */
  body[data-second="closed"] .second { flex-basis: 30px; width: 30px; }
  body[data-second="closed"] .second > header { padding: 0; justify-content: center; }
  body[data-second="closed"] .second > header b,
  body[data-second="closed"] .secact,
  body[data-second="closed"] .seclist { display: none; }
  body[data-second="closed"] .collapse { margin-left: 0; }
  body[data-second="closed"] .collapse .ctl { transform: rotate(0deg); }
  .second { transition: flex-basis 160ms, width 160ms; }
  @media (prefers-reduced-motion: reduce) {
    .second { transition: none; }
    .collapse .ctl { transition: none; }
  }
  .secact { padding: 2px 4px 6px; display: flex; gap: 6px; }
  .secact button { width: 100%; justify-content: center; }
  /* NEW FROM A PHOTO (ADR-092): an icon beside Walk-by, the same height. */
  .secact .photo {
    position: relative; flex: none; width: 32px; min-height: 32px;
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--line); border-radius: 5px; color: var(--ink);
  }
  .secact .photo::after {
    content: ""; position: absolute; left: 50%; top: 50%;
    transform: translate(-50%, -50%); width: 44px; height: 44px;
  }
  .secact .photo:hover { border-color: var(--ink); }
  .secact .photo svg { width: 15px; height: 15px; display: block; }
  .seclist { padding: 2px 4px 10px; overflow-y: auto; display: flex; flex-direction: column; gap: 1px; }
  .seclist a { display: flex; align-items: center; gap: 7px; padding: 4px 8px; border-radius: 5px; font-size: var(--font-sm); color: var(--ink-2); min-height: 22px; }
  .seclist a:hover { background: var(--line-2); }
  .seclist a[aria-current], .seclist a.cur { background: var(--win); color: var(--ink); font-weight: 600; }
  .seclist .ct { margin-left: auto; font: 500 11px var(--m); color: var(--muted); }
  /* The chats column carries the canon's own gutter, not the sections'. */
  .seclist.chats { padding: 0 8px 10px; }
  .sh { margin: 10px 0 2px; padding: 0 8px; font-size: 11.5px; font-weight: 600; color: var(--muted); }

  /* The window: inset on three sides, floating above the ground. */
  main {
    flex: 1; min-width: 0; max-width: none; margin: 8px 8px 8px 0; padding: 0;
    display: flex; flex-direction: column; overflow: hidden;
    background: var(--win); border: 1px solid var(--line); border-radius: 9px;
    box-shadow: 0 1px 2px rgba(0,0,0,.05), 0 8px 24px rgba(0,0,0,.07);
  }
  :root[data-theme="dark"] main { box-shadow: 0 1px 2px rgba(0,0,0,.5), 0 10px 30px rgba(0,0,0,.45); }
  main > * { flex: 0 0 auto; }
  main > .top { padding: 12px 13px 8px; }
  main > .views { padding: 0 13px 8px; margin: 0; }
  main > .list { margin: 0 13px; }
  main > .list.flush, main > .split { margin: 0; }
  main > .foot { margin-top: auto; padding: 8px 13px; border-top: 1px solid var(--line); }
  main > .bar:first-child { border-radius: 9px 9px 0 0; }
  main > .pad, main > h2, main > p, main > form, main > .card { margin-left: 13px; margin-right: 13px; }

  /* The window scrolls, the shell does not. */
  main > .scrollhost { flex: 1; min-height: 0; overflow-y: auto; }
  .split { flex: 1; min-height: 0; }
}

/* ── a bar inside the window ────────────────────────────────────────────── */
.bar { display: flex; gap: 8px; align-items: center; padding: 8px 13px; border-bottom: 1px solid var(--line); background: var(--win); }
.pad { padding: 10px 13px; }
.body { flex: 1; overflow: hidden; display: flex; flex-direction: column; min-height: 0; }
.split { display: flex; min-height: 0; }
.side { width: 206px; flex: 0 0 206px; border-right: 1px solid var(--line); background: var(--win); overflow-y: auto; }
.side .sh { font-size: 11.5px; font-weight: 600; color: var(--muted); padding: 11px 12px 5px; margin: 0; }
.side a { display: flex; justify-content: space-between; gap: 8px; padding: 4px 12px; font-size: var(--font-sm); color: var(--ink-2); }
.side a:hover { background: var(--line-2); }
.side a[aria-current], .side a.cur { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.main { flex: 1; min-width: 0; overflow: hidden; display: flex; flex-direction: column; }
.scroll { overflow-y: auto; flex: 1; min-height: 0; }

/* ── the ledger table ───────────────────────────────────────────────────── */
.sheet { width: 100%; border-collapse: collapse; font-size: var(--font-sm); }
.sheet th { text-align: left; font-size: 11.5px; font-weight: 600; color: var(--muted); padding: 6px 10px; background: var(--win); border-bottom: 1px solid var(--line); position: sticky; top: 0; z-index: 1; }
.sheet td { padding: 5px 10px; border-bottom: 1px solid var(--line-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 0; height: var(--row); }
.sheet td.num { font: 500 11.5px var(--m); font-variant-numeric: tabular-nums; text-align: right; }
.sheet tbody tr:hover td { background: var(--line-2); }
.sheet tr.selrow td { background: var(--accent-soft); }
.sheet td.selcell { background: var(--accent); color: #fff; font-weight: 600; }
.sheet tr.tot td { font-weight: 700; border-top: 2px solid var(--ink); border-bottom: 0; }

/* The frozen identity unit is DESK-ONLY, gated to the complement of the
   620px phone breakpoint below, where columns 4 and beyond are dropped
   and a frozen column has nothing left to scroll past. */
@media (min-width: 621px) {
  /* ── the frozen identity unit ────────────────────────────────────────────
     Opt-in per table with `data-freeze`, because `.sheet` is shared and the
     only table that has earned this is the search ledger. The frozen unit is
     TWO cells, not one: column 1 is the select checkbox and column 2 is the
     company name, so freezing "the first column" would pin the tickbox and let
     the name — the thing that says which row you are reading — scroll away.

     `--frz` drives both the checkbox column's width and the name column's
     offset, so the two cannot drift apart. Under `box-sizing: border-box`,
     which this stylesheet sets globally, the declared width IS the drawn width.

     `max-width: none` undoes `.sheet td`'s `max-width: 0`. That zero is what
     makes the browser distribute seven columns evenly and ellipsise all of
     them at once; with floors instead, the table takes the width it needs and
     the host scrolls sideways. */
  .sheet[data-freeze] { min-width: max-content; --frz: 30px; }

  /* A sticky cell must be opaque or the rows scroll underneath it. The colour
     is taken from a variable rather than declared, because the row states —
     hover, selected — set the variable on the ROW and custom properties
     inherit into the cell. Declaring the background directly would win the
     cascade against those state rules and freeze the frozen cells out of every
     row state, which is the usual way this pattern goes wrong. */
  .sheet[data-freeze] th.selcol, .sheet[data-freeze] td.selcol,
  .sheet[data-freeze] th.idcol,  .sheet[data-freeze] td.idcol {
    position: sticky; z-index: 2; background: var(--frz-bg, var(--ground));
  }
  .sheet[data-freeze] th.selcol, .sheet[data-freeze] td.selcol { left: 0; width: var(--frz); }
  .sheet[data-freeze] th.idcol,  .sheet[data-freeze] td.idcol  { left: var(--frz); }
  .sheet[data-freeze] tbody tr:hover       { --frz-bg: var(--line-2); }
  .sheet[data-freeze] tbody tr.selrow      { --frz-bg: var(--accent-soft); }
  .sheet[data-freeze] td.selcell           { --frz-bg: var(--accent); }
  /* The header corner wins both ways: it is sticky on both axes, so it must
     out-stack the header row (1) and the frozen body cells (2). */
  .sheet[data-freeze] th.selcol, .sheet[data-freeze] th.idcol { z-index: 3; --frz-bg: var(--win); }

  /* A BAND per column — a floor and a ceiling — not a floor alone. Plane uses
     240px for the title and 144px for a property; CANVASS draws at a denser
     grade, so those are scaled rather than copied.
     The ceiling is the half that was measured into existence. `.sheet td`'s
     `max-width: 0` is what makes the browser distribute seven columns evenly
     and ellipsise all of them at once, so it has to go — but replacing it with
     `none` let `min-width: max-content` size the name column to its widest
     value, 529px, and the table to 1568px inside a 788px host. A floor without
     a ceiling does not widen a column, it unpins it.
     The floors sum to 738px, which fits the 788px desk host, so the sheet
     scrolls sideways only once a column has earned the width. */
  .sheet[data-freeze] th.idcol,  .sheet[data-freeze] td.idcol  { min-width: 176px; max-width: 320px; }
  .sheet[data-freeze] th:nth-child(3), .sheet[data-freeze] td:nth-child(3) { min-width: 160px; max-width: 300px; }
  .sheet[data-freeze] th:nth-child(4), .sheet[data-freeze] td:nth-child(4) { min-width:  96px; max-width: 150px; }
  .sheet[data-freeze] th:nth-child(5), .sheet[data-freeze] td:nth-child(5) { min-width:  88px; max-width: 110px; }
  .sheet[data-freeze] th:nth-child(6), .sheet[data-freeze] td:nth-child(6) { min-width: 108px; max-width: 180px; }
  .sheet[data-freeze] th:nth-child(7), .sheet[data-freeze] td:nth-child(7) { min-width:  80px; max-width: 110px; }

  /* The freeze shadow — a ::after gradient, deliberately NOT a box-shadow.
     Chromium does not paint a cell's own box-shadow under `border-collapse:
     collapse`, which this table uses, so a shadow declared the ordinary way
     simply does not appear. The gradient is positioned against the sticky cell,
     which is already a containing block by virtue of being sticky.

     `.sheet td` sets `overflow: hidden`, which would clip a gradient sitting
     outside the cell. So the clipping moves inward: the cell goes visible and
     its child does the ellipsising instead. That keeps long names truncating —
     the ellipsis is the point of the ceiling above — while letting the shadow
     fall outside the frozen edge where a shadow belongs. */
  .sheet[data-freeze] td.idcol { overflow: visible; }
  .sheet[data-freeze] td.idcol > * {
    display: block; max-width: 100%;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .sheet[data-freeze] th.idcol { overflow: visible; }
  .sheet[data-freeze] th.idcol::after, .sheet[data-freeze] td.idcol::after {
    content: ""; position: absolute; top: 0; bottom: 0; right: -9px; width: 9px;
    pointer-events: none; opacity: 0; transition: opacity 120ms;
    background: linear-gradient(to right, rgba(16, 18, 24, .10), rgba(16, 18, 24, 0));
  }
  :root[data-theme="dark"] .sheet[data-freeze] th.idcol::after,
  :root[data-theme="dark"] .sheet[data-freeze] td.idcol::after {
    background: linear-gradient(to right, rgba(0, 0, 0, .45), rgba(0, 0, 0, 0));
  }
  /* Armed only once the sheet has actually scrolled away from its origin, so an
     unscrolled ledger carries no false edge. `data-scrolled` is set on the
     scroll host by app.js. */
  .scroll[data-scrolled="1"] .sheet[data-freeze] th.idcol::after,
  .scroll[data-scrolled="1"] .sheet[data-freeze] td.idcol::after { opacity: 1; }
}
@media (min-width: 621px) and (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .sheet[data-freeze] th.idcol::after,
  :root:not([data-theme="light"]) .sheet[data-freeze] td.idcol::after {
    background: linear-gradient(to right, rgba(0, 0, 0, .45), rgba(0, 0, 0, 0));
  }
}

/* ── facets: a question with a live number, never a stored tag ───────────── */
/* `.side a` is a flex row, and it outranks a bare `.facet`, which collapsed
   the share bar to zero width. The facet anchor is stacked, not a row, so
   it is qualified to win that cascade wherever it is used. */
.side a.facet, .facet { display: block; padding: 7px 12px; border-bottom: 1px solid var(--line-2); color: var(--ink); }
.facet:hover { background: var(--line-2); }
.facet .fr { display: flex; justify-content: space-between; gap: 8px; font-size: 12px; margin-bottom: 4px; align-items: center; }
.facet .fname { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.facet .fb { display: block; width: 100%; height: 3px; border-radius: 2px; background: var(--line-2); overflow: hidden; }
.facet .fb i { display: block; height: 100%; width: 0; background: var(--ink); }

/* ── saved views: pills ─────────────────────────────────────────────────── */
.views { display: flex; gap: 5px; flex-wrap: wrap; margin: 0 0 10px; }
.views button, .pill {
  font-size: 12px; font-weight: 500; min-height: 22px; height: auto; padding: 2px 9px;
  border-radius: 99px; border: 1px solid var(--line); background: var(--win); color: var(--ink-2);
  white-space: nowrap; display: inline-flex; align-items: center;
}
.views button[aria-pressed="true"], .pill.on { background: var(--ink); border-color: var(--ink); color: var(--win); font-weight: 600; }
.pill.x { background: var(--accent); border-color: var(--accent); color: #fff; }
.views button .n { margin-left: 5px; color: inherit; opacity: .7; min-width: 0; font-size: 11px; }

/* ── time travel: a control, not a debug flag ───────────────────────────── */
.past-banner {
  display: none; align-items: center; gap: 8px;
  background: var(--inv); border: 1px solid var(--inv); color: var(--inv-ink);
  padding: 8px 12px; border-radius: var(--r); font-size: var(--font-sm); margin: 0 0 12px;
}
body.past .past-banner { display: flex; }
body.past form, body.past .acts, body.past .writes, body.past .dock-act, body.past .secact { display: none !important; }
.foot { margin-top: 26px; text-align: center; font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
.foot .live::before { content: "\25CF"; color: var(--ink); margin-right: 4px; font-size: 9px; }
.foot .offline::before { content: "\25CB"; color: var(--muted); margin-right: 4px; font-size: 9px; }

/* ── command menu ───────────────────────────────────────────────────────── */
dialog.cmd {
  border: 1px solid var(--line); border-radius: 10px; padding: 0; width: min(520px, 92vw);
  background: var(--win); color: var(--ink); box-shadow: 0 12px 40px rgba(0,0,0,.14);
  margin-top: 12vh;
}
dialog.cmd::backdrop { background: rgba(10,12,16,.35); }
dialog.cmd input { border: 0; border-bottom: 1px solid var(--line); border-radius: 10px 10px 0 0; padding: 13px 15px; }
dialog.cmd ul { list-style: none; margin: 0; padding: 5px; max-height: 46vh; overflow-y: auto; }
dialog.cmd li { padding: 8px 11px; border-radius: 6px; font-size: var(--font); cursor: pointer; min-height: 36px; display: flex; align-items: center; gap: 9px; }
dialog.cmd li[aria-selected="true"] { background: var(--accent-soft); color: var(--accent); }
dialog.cmd kbd { margin-left: auto; font: 11px var(--m); color: var(--muted); }

/* ── PORTAGE phase 8: the context menu ───────────────────────────────────
   A modal <dialog> like the palette, so Escape, the focus trap and the
   inertness of everything behind it all come free — but positioned at the
   press rather than centred, which is what makes it a context menu.

   `position: fixed` with `inset: auto` because a modal dialog is centred by
   the UA's own `inset: 0` plus auto margins, and left/top alone do not beat
   that. */
dialog.ctxmenu {
  position: fixed; inset: auto; margin: 0; padding: 4px;
  min-width: 156px;
  border: 1px solid var(--line); border-radius: 9px;
  background: var(--win); color: var(--ink);
  box-shadow: 0 1px 2px rgba(0,0,0,.05), 0 8px 24px rgba(0,0,0,.07);
}
:root[data-theme="dark"] dialog.ctxmenu { box-shadow: 0 1px 2px rgba(0,0,0,.5), 0 10px 30px rgba(0,0,0,.45); }
dialog.ctxmenu::backdrop { background: transparent; }
dialog.ctxmenu button {
  display: block; width: 100%; text-align: left;
  border: 0; background: transparent; border-radius: 5px;
  padding: 6px 9px; min-height: 30px;
  font-size: var(--font-sm); color: var(--ink-2);
}
dialog.ctxmenu button:hover { background: var(--line-2); color: var(--ink); }
/* A group heading, not a command: it takes no hit, no selection and no hover,
   and it is `role="presentation"` so a screen reader reads the listbox as a
   list of options rather than as options interleaved with junk. */
dialog.cmd li.grp {
  min-height: 0; padding: 9px 11px 4px; cursor: default;
  font: 600 10.5px var(--d); color: var(--muted-2); letter-spacing: .04em;
}
dialog.cmd li.grp:first-child { padding-top: 5px; }
dialog.cmd li.grp[aria-selected="true"] { background: none; color: var(--muted-2); }

/* ── settings (PORTAGE phase 9) ─────────────────────────────
   A dialog over the screen, not a page, for the reason mock 3 kept: changing a
   preference must not cost the operator the screen they were standing on. It
   takes no new token — every value below is one the app already declares. */
dialog.settings {
  border: 1px solid var(--line); border-radius: 12px; padding: 0;
  width: min(440px, calc(100vw - 32px));
  background: var(--win); color: var(--ink);
  box-shadow: 0 1px 2px rgba(0,0,0,.05), 0 16px 48px rgba(0,0,0,.12);
}
:root[data-theme="dark"] dialog.settings { box-shadow: 0 1px 2px rgba(0,0,0,.5), 0 18px 54px rgba(0,0,0,.5); }
dialog.settings::backdrop { background: rgba(0,0,0,.28); }
dialog.settings header {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; padding: 12px 12px 12px 16px; border-bottom: 1px solid var(--line-2);
}
dialog.settings h2 { margin: 0; font: 600 var(--font-md)/1.2 var(--d); color: var(--ink); }
dialog.settings section { padding: 12px 16px 14px; }
dialog.settings section + section { border-top: 1px solid var(--line-2); }
/* A section heading, and the Agent section is a heading over nothing on
   purpose — see SETTINGS in app.js. It carries no minimum height, so the
   empty one is a label and a gap rather than a box suggesting a control
   failed to load. */
dialog.settings h3 {
  margin: 0 0 8px; font: 600 10.5px var(--d); color: var(--muted-2); letter-spacing: .04em;
}
dialog.settings .srow {
  display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 44px;
}
dialog.settings .srow b { font: 400 var(--font-sm)/1.3 var(--d); color: var(--ink); }
/* A segmented choice, cut by a border the way the filter pills are, so the
   two surfaces that change a preference do not each invent a control. */
dialog.settings .sseg { display: flex; border: 1px solid var(--line); border-radius: 7px; overflow: hidden; }
dialog.settings .sseg button {
  border: 0; background: transparent; color: var(--ink-2);
  font: 400 var(--font-sm)/1 var(--d);
  padding: 0 11px; min-height: 30px; min-width: 56px;
}
dialog.settings .sseg button + button { border-left: 1px solid var(--line); }
dialog.settings .sseg button:hover { background: var(--line-2); color: var(--ink); }
dialog.settings .sseg button[aria-checked="true"] { background: var(--line-2); color: var(--ink); font-weight: 600; }

/* ── capture: the sheet (C3) ─────────────────────────────────────────────
   One sheet, two capture shapes, reachable in one tap from the dock. */
dialog.sheet {
  border: 1px solid var(--line); border-radius: 12px; padding: 0;
  width: min(560px, 94vw); max-height: 88vh;
  background: var(--win); color: var(--ink); box-shadow: 0 12px 40px rgba(0, 0, 0, .16);
}
dialog.sheet::backdrop { background: rgba(10, 12, 16, .4); }
dialog.sheet form { border: 0; border-radius: 12px; padding: 0; max-height: 88vh; overflow-y: auto; }
dialog.sheet header {
  position: sticky; top: 0; z-index: 1;
  display: flex; align-items: center; justify-content: space-between;
  padding: 13px 15px; border-bottom: 1px solid var(--line); background: var(--win);
}
dialog.sheet header h2 { margin: 0; font: 700 14px var(--d); color: var(--ink); }
dialog.sheet .submit { position: sticky; bottom: 0; margin: 0; padding: 11px 15px; border-top: 1px solid var(--line); background: var(--win); }
dialog.sheet .submit button { min-height: 44px; padding: 8px 18px; }
dialog.sheet fieldset { border: 0; border-top: 1px solid var(--line-2); margin: 0; padding: 4px 15px 13px; }
dialog.sheet fieldset:first-of-type { border-top: 0; }
label.check { display: flex; align-items: center; gap: 8px; color: var(--ink); margin-top: 12px; }
label.check input { width: auto; min-width: 20px; min-height: 20px; }
.preview { display: none; max-height: 140px; border-radius: 6px; margin-top: 8px; border: 1px solid var(--line); }
.preview[src] { display: block; }
.capture-status { margin: 0; padding: 0 15px 4px; font-size: var(--font-sm); color: var(--ink-2); min-height: 18px; }

/* ── position: always visible, always honest (C3.4) ─────────────────────── */
button.linkish {
  border: 0; background: transparent; padding: 2px 4px; min-height: 24px;
  font-size: 11px; color: var(--muted); text-decoration: underline; text-underline-offset: 2px;
}
button.linkish[data-state="current"] { color: var(--ink); }
button.linkish[data-state="stale"] { color: var(--ink-2); }
button.linkish[data-state="none"] { color: var(--muted); }

.record { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
.record button { min-height: 40px; }
.record button[data-state="recording"] { background: var(--inv); border-color: var(--inv); color: var(--inv-ink); font-weight: 600; }
.record [data-record-status] { font-size: var(--font-sm); color: var(--muted); }

/* ── an action that carries a value: one row, one field, one button ─────── */
.inline-act { display: flex; align-items: center; gap: 8px; margin-top: 10px; padding: 0; border: 0; background: transparent; }
.inline-act input[data-prop] { flex: 1; min-width: 0; font-size: 16px; }
.inline-act .meta { flex: 1; }
.inline-act button { white-space: nowrap; }
button.danger-always { color: var(--ink); border-color: var(--line); }
button.danger-always:hover { border-color: var(--ink); }

/* ── C11.3 search, facets and the narrowing rail ────────────────────────── */
.queryline { display: flex; gap: 8px; align-items: center; }
.queryline input { flex: 1; min-width: 0; font-family: var(--m); }
.qexpr {
  margin: 0; padding: 6px 13px; border-bottom: 1px solid var(--line); background: var(--win);
  font: 500 11.5px var(--m); color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ── PORTAGE phase 7: the filter pills ───────────────────────────────────
   `.fpill` and not `.pill`, deliberately. `.pill` is already taken — it is
   the static chip on facets.html, styled at the top of this file alongside
   `.views button` and re-sized by a 899px block. A filter pill reusing that
   name would have inherited a chip's padding, a chip's pressed state and a
   height set for a different control, and every one of those would have
   looked like a styling decision this phase made.

   Every value below is drawn from the tokens already declared at the top of
   the file. Nothing here adds a token, moves one, or re-declares one: the
   arrangement is what crossed from Plane, and the palette is CANVASS's. */
.filterbar { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 6px 13px 0; }
.fpills { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.fpills[hidden] { display: none; }

.fpill {
  display: inline-flex; align-items: center; gap: 0;
  border: 1px solid var(--line); border-radius: var(--r); background: var(--win);
  font-size: var(--font-sm); color: var(--ink-2); overflow: hidden;
}
/* THE SEGMENTS. A pill is cut by its own borders rather than by gaps, so the
   three parts read as one object that has parts — which is the whole claim a
   filter pill makes — instead of as three controls that happen to sit
   together. The plan called it three segments; a pill has three only when it
   is a measure. A choice has two, because there is one operator and offering
   it would be offering a decision that does not exist; free text has two,
   because nothing can enumerate a street name. */
.fpill > * { border-left: 1px solid var(--line); }
.fpill > *:first-child { border-left: 0; }
.fpill b { font-weight: 600; color: var(--ink); padding: 0 8px; white-space: nowrap; }
.fpill .pv { padding: 0 8px; white-space: nowrap; max-width: 18ch; overflow: hidden; text-overflow: ellipsis; }
.fpill i { font-style: normal; color: var(--muted); padding: 0 6px 0 2px; border-left: 0; }

/* The controls keep the pill's ground and lose their own chrome, so the pill
   is one shape. They are still a real <select> and a real <input>: the caret,
   the keyboard behaviour and the screen-reader announcement all come free,
   and none of it had to be rebuilt. */
.fpill select, .fpill input {
  appearance: none; -webkit-appearance: none;
  border: 0; border-left: 1px solid var(--line); border-radius: 0;
  background: transparent; color: var(--ink);
  font: inherit; font-size: var(--font-sm);
  padding: 4px 6px; min-height: 26px; height: 26px;
}
.fpill select { padding-right: 4px; }
.fpill input[type="number"] { width: 5ch; text-align: right; font-family: var(--m); }
.fpill select:focus-visible, .fpill input:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.fpill:hover { border-color: var(--ink-2); }

.fpillx {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; min-height: 26px; height: 26px; padding: 0;
  border: 0; border-left: 1px solid var(--line); border-radius: 0;
  background: transparent; color: var(--muted); --ctl-size: 11px;
}
.fpillx:hover { background: var(--line-2); color: var(--ink); }

/* The way in. A native <details>, so it opens on a press, closes on Escape and
   on a press outside, and is reachable by keyboard — none of which had to be
   written. The summary carries the funnel alone: a control says what it does
   by being the shape of it. */
.addfilter { position: relative; }
.addfilter[hidden] { display: none; }
.addfilter > summary {
  list-style: none; cursor: default;
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border: 1px solid var(--line); border-radius: var(--r);
  background: var(--win); color: var(--muted); --ctl-size: 13px;
}
.addfilter > summary::-webkit-details-marker { display: none; }
.addfilter > summary:hover { color: var(--ink); border-color: var(--ink-2); }
.addfilter[open] > summary { color: var(--ink); border-color: var(--ink); }
.addmenu {
  position: absolute; z-index: 5; top: calc(100% + 4px); left: 0;
  min-width: 152px; padding: 4px;
  display: flex; flex-direction: column; gap: 1px;
  background: var(--win); border: 1px solid var(--line); border-radius: 9px;
  box-shadow: 0 1px 2px rgba(0,0,0,.05), 0 8px 24px rgba(0,0,0,.07);
}
:root[data-theme="dark"] .addmenu { box-shadow: 0 1px 2px rgba(0,0,0,.5), 0 10px 30px rgba(0,0,0,.45); }
.addmenu button {
  border: 0; background: transparent; border-radius: 5px;
  text-align: left; padding: 5px 8px; min-height: 26px;
  font-size: var(--font-sm); color: var(--ink-2);
}
.addmenu button:hover { background: var(--line-2); color: var(--ink); }
.grammar dl { display: grid; grid-template-columns: auto 1fr; gap: 3px 10px; margin: 0; padding: 2px 12px 10px; font-size: 12px; }
.grammar dt { font: 500 11.5px var(--m); color: var(--accent); white-space: nowrap; }
.grammar dd { margin: 0; color: var(--muted); }
.note { font-size: 12px; color: var(--muted); line-height: 1.5; margin: 0; padding: 2px 12px 10px; }
.note b { color: var(--ink-2); font-weight: 600; }

/* ── responsive trims ───────────────────────────────────────────────────── */
/* At 390px a row carrying name + count + time + an action has no room left
   for the name. The decoration is not dropped, it moves one level down. */
@media (max-width: 620px) {
  .row .spark, .row .n { display: none; }
  .row .acts { display: none; }
  article[data-open="1"] .detail .acts { display: flex; }
  .row { gap: 8px; }
  .when { font-size: 11.5px; }
  .split { flex-direction: column; }
  .side { width: auto; flex: 0 0 auto; border-right: 0; border-bottom: 1px solid var(--line); }
  .sheet th:nth-child(n+4), .sheet td:nth-child(n+4) { display: none; }
  /* The freeze above this breakpoint is a DESK affordance and is gated to
     desk widths at its own declaration, not undone here. The rule above
     already solved the phone case by dropping columns 4 and beyond, so there
     are three columns left and nothing to scroll past; leaving the freeze
     armed measured WORSE — at 375px the name took its 320px ceiling out of a
     349px host and the sheet grew 301px of sideways scroll to reach an
     address that used to be on screen. */
}
@media (max-width: 380px) { .row .when { display: none; } }

/* A problem summary is a sentence, not a name: the sentence IS the identity,
   so at phone width it wraps in full and the decoration moves one level down. */
@media (max-width: 620px) {
  #triage .row .name { white-space: normal; overflow: visible; text-overflow: clip; }
  #triage .row .tag, #triage .row .when { display: none; }
  #triage .row { align-items: flex-start; }
  #triage .row .dot { margin-top: 3px; }
}

.answer-form { border: 0; padding: 0; background: transparent; margin-top: 8px; }
.answer-form fieldset { border: 0; margin: 0; padding: 0; }
.answer-form .submit { margin-top: 10px; }
#questions .detail .meta { color: var(--ink-2); font-style: italic; margin: 8px 0 0; }

.ready-banner {
  display: flex; align-items: center; gap: 8px; margin: 0 0 14px;
  padding: 10px 13px; border-radius: var(--r); font-size: var(--font-sm);
  border: 1px solid var(--line); background: var(--win); color: var(--ink-2);
}
.ready-banner[data-ready="true"], .ready-banner[data-verdict="one-day-app"] { border-color: var(--ink); color: var(--ink); font-weight: 600; }
.ready-banner[data-ready="false"], .ready-banner[data-verdict="decline"],
.ready-banner[data-verdict="mariposa-project"], .ready-banner[data-verdict="needs-review"] { background: var(--inv); border-color: var(--inv); color: var(--inv-ink); }
#brief .sub li { display: flex; gap: 8px; align-items: baseline; padding: 5px 0; white-space: normal; }
#brief .sub li b { flex: 0 0 auto; max-width: 45%; color: var(--muted); font-weight: 600; }
#brief .sub li span:not(.tag) { flex: 1; color: var(--ink); }
@media (max-width: 620px) { #brief .sub li { flex-direction: column; gap: 2px; } #brief .sub li b { max-width: none; } }
#triggers .sub li { font: 400 11.5px var(--m); color: var(--muted); }

.sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; scroll-behavior: auto !important; }
}

/* ── T1 the inbound lane ─────────────────────────────────────────────────
   A batch either landed or was refused whole, so the readout says which. It
   is inverted rather than coloured when it failed, because alarm in this
   design system is a change of ground and not a change of hue — a red word in
   a grey interface is the first thing that stops being read. */
[data-act-status] { padding: 0 2px 6px; }
[data-act-status][data-state="failed"] {
  background: var(--ink); color: var(--win);
  padding: 6px 10px; border-radius: 5px; font-weight: 600;
}
[data-act-status][data-state="ok"] { color: var(--ink-2); }

/* The provenance mark is a tag, but a quieter one than a status tag: whose
   row this is should be legible without competing with what the row says. */
.row .tag[data-format="sourceMark"] {
  background: transparent; border: 1px solid var(--line); color: var(--muted);
  font-family: var(--m); font-size: 10.5px;
}
@media (max-width: 620px) {
  /* The mark goes one level down with everything else the row cannot fit,
     and the expansion still carries it under "Entered by". */
  .row .tag[data-format="sourceMark"] { display: none; }
}

/* [T1] The narrowing rail is the PHONE posture for filtering, and a 22px pill
   is not a thing you can hit with a thumb while standing up. The desk keeps
   the dense pill, because a cursor is precise and the ledger wants the rows.
   Posture, not breakpoint — the same rule the density setting follows. */
@media (max-width: 899px) {
  .views button, .pill { min-height: 32px; padding: 5px 11px; }
}

/* ── T2 sheet ops: the selection ─────────────────────────────────────────
   The bar states the count BEFORE the action is taken, because the whole risk
   of a bulk action is acting on a different number of records than you think
   you are. It is inverted, like every other alarm in this system, so it reads
   as a mode the screen is in rather than as one more row of controls. */
.selbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 7px;
  margin: 0 0 8px; padding: 9px 12px; border-radius: 7px;
  background: var(--ink); color: var(--win);
}
.selbar[hidden] { display: none; }
.selbar b { font-family: var(--m); font-size: 12px; font-weight: 600; white-space: nowrap; }
.selbar button {
  min-height: 30px; font-size: 12px; padding: 4px 10px;
  background: transparent; border-color: rgba(255,255,255,.28); color: var(--win);
}
.selbar button:hover { border-color: var(--win); }
.selbar button.quiet { border-color: transparent; color: rgba(255,255,255,.62); }
.selbar input { min-height: 30px; padding: 4px 9px; font-size: 13px;
  background: rgba(255,255,255,.08); border-color: rgba(255,255,255,.22); color: var(--win); }
.selbar input::placeholder { color: rgba(255,255,255,.5); }
:root[data-theme="dark"] .selbar button,
:root[data-theme="dark"] .selbar input { border-color: rgba(0,0,0,.3); color: var(--ground); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .selbar button,
  :root:not([data-theme="light"]) .selbar input { border-color: rgba(0,0,0,.3); color: var(--ground); }
  :root:not([data-theme="light"]) .selbar b { color: var(--ground); }
}

/* The column is as narrow as a tick and no narrower. */
.selcol { width: 30px; text-align: center; padding-left: 8px !important; padding-right: 0 !important; }
.selcol input, .selbox { width: auto; min-width: 17px; min-height: 17px; margin: 0; cursor: pointer; }
.selbox { flex: 0 0 auto; }
tr[data-selected], .list [data-selected] > .row { background: var(--accent-soft); }

.selall {
  display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
  margin: 0 0 8px; font-size: var(--font-sm); color: var(--muted);
}
.selall input { width: auto; min-width: 17px; min-height: 17px; margin: 0; }

/* The undo sits beside the readout it belongs to and leaves with it. */
[data-undo] { margin-left: 8px; min-height: 26px; padding: 3px 10px; font-size: 12px; }

/* ── T3 the inspector: level three, beside the list ──────────────────────
   At the desk it is a third column in the same flex row as the rail, the
   second column and the window — so it is a window of its own on the same
   grey ground, not a layer floating over the work. Nothing is covered, and
   the list keeps its scroll position because nothing about it changed. */
.inspector {
  display: flex; flex-direction: column; min-height: 0;
  background: var(--win); border: 1px solid var(--line); border-radius: 9px;
  box-shadow: 0 1px 2px rgba(0,0,0,.05), 0 8px 24px rgba(0,0,0,.07);
  color: var(--ink);
}
.inspector[hidden] { display: none; }
.inspector > header {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 11px; border-bottom: 1px solid var(--line); flex: 0 0 auto;
}
.inspector > header b { font: 700 13px var(--d); letter-spacing: -.01em; flex: 1; }
.inspector > header .btn { font-size: 11.5px; min-height: 26px; padding: 3px 9px; }
.inspector > header [hidden] { display: none; }
.inspectbody { overflow-y: auto; padding: 11px; flex: 1 1 auto; min-height: 0; }
.inspector h3 { font: 700 16px/1.25 var(--d); letter-spacing: -.015em; margin: 0 0 2px; }
.inspector > .inspectbody > section > .meta { margin: 0 0 10px; font-size: var(--font-sm); color: var(--muted); }
.inspector dl { display: grid; grid-template-columns: auto 1fr; gap: 3px 12px; margin: 0; font-size: var(--font-sm); }
.inspector dt { color: var(--muted); white-space: nowrap; }
.inspector dd { margin: 0; overflow-wrap: anywhere; }
/* A fact with no value is not shown at all. Twelve rows of em-dashes reads as
   "this record is empty" when what is true is "nobody asked yet".
   The label is matched through :has(), because the label PRECEDES the value and
   `dd:empty + dt` would hide the next pair's label instead of this one's. */
.inspector dd:empty { display: none; }
.inspector dt:has(+ dd:empty) { display: none; }
/* A section heading is the blue title-case treatment `.eyebrow` established,
   never SET IN CAPITALS. Caps are the house rule this repeatedly breaks: they
   shout, they cost a reader the word-shape they scan by, and a screen reader
   pronounces some of them as initialisms. Title case as authored, accent
   colour, and the weight carries the hierarchy instead. */
.inspector .sh { margin: 14px 0 3px; padding: 0; font-size: 11.5px; font-weight: 600;
  color: var(--accent); }
.inspector .sub { margin: 0; padding: 0; list-style: none; font-size: var(--font-sm); }
.inspector .sub li { padding: 4px 0; border-top: 1px solid var(--line-2); color: var(--ink-2); }
.inspector .sub li:first-child { border-top: 0; }
.inspector .sub b { color: var(--ink); font-weight: 600; }
.inspector .note { padding: 8px 0 0; }
.inspector section[hidden] { display: none; }

@media (min-width: 900px) {
  /* A third column beside the window, sized so the list beside it still shows
     a full row of a ledger rather than being squeezed into a gutter. */
  .inspector { flex: 0 0 var(--panel-w); width: var(--panel-w); margin: 8px 8px 8px 0; }
  :root[data-theme="dark"] .inspector { box-shadow: 0 1px 2px rgba(0,0,0,.5), 0 10px 30px rgba(0,0,0,.45); }

  /* PORTAGE phase 7 — the other two postures. `beside` is the rule above and
     is the default; these two take the same element out of the flex row and
     put it over the window, keeping every other thing about it — the same
     panel, the same body, the same close.

     NO SCRIM. The panel has said "in front" since T3 by having a shadow and a
     border, and nothing here dims the work behind it: the list stays readable,
     which is the reason a person picks centred over full in the first place. */
  body[data-inspect-mode="centre"] .inspector {
    position: fixed; z-index: 40; margin: 0;
    top: 50%; left: 50%; transform: translate(-50%, -50%);
    width: min(760px, calc(100vw - 96px)); flex-basis: auto;
    max-height: min(78vh, 760px);
  }
  body[data-inspect-mode="full"] .inspector {
    position: fixed; z-index: 40; margin: 0;
    inset: 8px; width: auto; flex-basis: auto; max-height: none;
  }

  /* The switch. Three states of one mark, so the pressed one is carried by
     ground and ink rather than by a fourth drawing. */
  .imodes { display: inline-flex; gap: 1px; padding: 1px;
            border: 1px solid var(--line); border-radius: 6px; background: var(--win); }
  .imode { display: inline-flex; align-items: center; justify-content: center;
           width: 22px; height: 22px; min-height: 22px; padding: 0;
           border: 0; border-radius: 4px; background: transparent;
           color: var(--muted); --ctl-size: 13px; }
  .imode:hover { background: var(--line-2); color: var(--ink); }
  .imode[aria-pressed="true"] { background: var(--ink); color: var(--win); }
}

/* Below the desk the panel is a bottom sheet, where "beside the list" and
   "full window" are not two different things — so the switch is not offered
   and the mode is not applied. The preference survives; it just does not
   describe anything at this width. */
@media (max-width: 899px) {
  .imodes { display: none; }
}
@media (min-width: 900px) and (max-width: 1180px) {
  /* Narrow desk: the second column is navigation and the record is the work,
     so the record wins the space. */
  body[data-inspecting] .second { display: none; }
}

@media (max-width: 899px) {
  /* The phone posture: the same element, anchored to the bottom edge, opened
     deliberately from the expansion rather than by every tap. Full width,
     rounded at the top only, and clear of the dock and the home indicator. */
  .inspector {
    position: fixed; inset: auto 0 0 0; z-index: 60;
    max-height: 82vh; border-radius: 13px 13px 0 0; border-bottom: 0;
    box-shadow: 0 -2px 30px rgba(0,0,0,.22);
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  .inspector > header { position: sticky; top: 0; background: var(--win); z-index: 1;
    border-radius: 13px 13px 0 0; }
  /* A grabber, because a panel that rose from the bottom edge should look like
     something you can push back down. */
  .inspector > header::before {
    content: ""; position: absolute; top: 4px; left: 50%; transform: translateX(-50%);
    width: 34px; height: 3px; border-radius: 99px; background: var(--line);
  }
  .inspector > header b { padding-top: 5px; }
  .inspectbody { padding-bottom: 20px; }
}

/* [T3] At the desk the pill bar scrolls instead of growing a second line.
   The panel narrows the window, and a bar that rewraps pushes the list down by
   a row — which is exactly the "lost my place" the panel exists to prevent.
   Below 900px it still wraps: there is no cursor out there to scroll a strip
   with, and the narrowing rail is meant to be read all at once. */
@media (min-width: 900px) {
  main > .views { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: thin; }
  main > .views::-webkit-scrollbar { height: 6px; }
  main > .views::-webkit-scrollbar-thumb { background: var(--line); border-radius: 99px; }
  main > .views > button { flex: 0 0 auto; }
}

/* ── T4 forms that ask what the moment gives ─────────────────────────────
   A field the operator cannot answer standing at a door is a default with an
   override, not a question. The disclosure is where the overrides live: it is
   closed by default, everything inside it still writes, and nothing inside it
   is required — which is the test of whether it belonged there. */
details.more { margin: 8px 0 0; border-top: 1px solid var(--line-2); padding-top: 6px; }
details.more > summary {
  cursor: pointer; list-style: none; display: flex; align-items: center; gap: 6px;
  font-size: var(--font-sm); font-weight: 600; color: var(--muted);
  min-height: 30px; user-select: none;
}
details.more > summary::-webkit-details-marker { display: none; }
details.more > summary::before { content: "▸"; font-size: 10px; transition: transform 120ms; }
details.more[open] > summary::before { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) { details.more > summary::before { transition: none; } }
details.more > summary:hover { color: var(--ink); }
details.more .hint { font-weight: 400; color: var(--muted); opacity: .8; }

/* A pair of short fields is one row, not two. The grid already existed and was
   used on exactly one screen; the doorstep is the screen that needed it. */
dialog.sheet .fields { gap: 0 10px; }

/* Where the placeholder already carries the word, the label is for screen
   readers only — "Name" above a box that says "Name" is two rows for one
   field. The class already existed for exactly this. */

/* ── T5 the sheet, in two postures ───────────────────────────────────────
   A capture is a modal act — you are standing at a door and the rest of the
   workspace can wait — but "modal" has meant "a box dropped in the middle of
   the screen", which on a phone puts the submit button under the keyboard and
   the first field where the thumb cannot reach it. Same element, two postures:
   on a phone it rises from the bottom edge, at a desk it docks to the right
   edge in exactly the inspector's geometry. One surface, two postures (TH-2). */

dialog.sheet { transition-timing-function: var(--sheet-ease); }

@media (max-width: 899px) {
  /* The phone posture. Full width on the bottom edge, rounded at the top only,
     clear of the home indicator, and tall enough to be worth opening without
     covering the screen it came from. */
  dialog.sheet {
    position: fixed; inset: auto 0 0 0;
    width: 100%; max-width: none; margin: 0;
    max-height: 92svh;
    border-radius: 14px 14px 0 0; border-bottom: 0;
    box-shadow: 0 -2px 30px rgba(0, 0, 0, .22);
    translate: 0 0;
    transition:
      translate var(--sheet-ms) var(--sheet-ease),
      display var(--sheet-ms) allow-discrete,
      overlay var(--sheet-ms) allow-discrete;
  }
  dialog.sheet form { max-height: 92svh; padding-bottom: env(safe-area-inset-bottom, 0px); }
  dialog.sheet:not([open]) { translate: 0 100%; }
  @starting-style { dialog.sheet[open] { translate: 0 100%; } }

  /* A grabber, because a surface that rose from the bottom edge should look
     like something a thumb can push back down — and here it actually can. */
  dialog.sheet header { padding-top: 17px; border-radius: 14px 14px 0 0; touch-action: none; }
  dialog.sheet header::before {
    content: ""; position: absolute; top: 6px; left: 50%; translate: -50% 0;
    width: 34px; height: 4px; border-radius: 99px; background: var(--line);
  }
  /* While a thumb is on it the sheet follows the thumb, so it must not also be
     animating toward somewhere else. */
  dialog.sheet[data-dragging] { transition: none; }
}

@media (min-width: 900px) {
  /* The desk posture. The same surface docked to the right edge, in the
     inspector's geometry, so a record and a capture occupy the same place on
     the screen and the eye does not have to find a new one. */
  dialog.sheet {
    position: fixed; inset: 8px 8px 8px auto;
    width: var(--panel-w); max-width: calc(100vw - 16px);
    height: auto; max-height: calc(100svh - 16px); margin: 0;
    border-radius: 9px;
    box-shadow: 0 1px 2px rgba(0,0,0,.05), 0 10px 34px rgba(0,0,0,.14);
    translate: 0 0;
    transition:
      translate var(--sheet-ms) var(--sheet-ease),
      display var(--sheet-ms) allow-discrete,
      overlay var(--sheet-ms) allow-discrete;
  }
  dialog.sheet form { max-height: calc(100svh - 16px); }
  dialog.sheet:not([open]) { translate: calc(100% + 16px) 0; }
  @starting-style { dialog.sheet[open] { translate: calc(100% + 16px) 0; } }
  :root[data-theme="dark"] dialog.sheet { box-shadow: 0 1px 2px rgba(0,0,0,.5), 0 12px 40px rgba(0,0,0,.5); }
}

/* The backdrop fades rather than appearing, in both postures. */
dialog.sheet::backdrop {
  background: rgba(10, 12, 16, .4); opacity: 1;
  transition:
    opacity var(--sheet-ms) var(--sheet-ease),
    display var(--sheet-ms) allow-discrete,
    overlay var(--sheet-ms) allow-discrete;
}
dialog.sheet:not([open])::backdrop { opacity: 0; }
@starting-style { dialog.sheet[open]::backdrop { opacity: 0; } }

/* The command menu stays where it is, on purpose. A sheet is anchored to an
   edge because it is about one record and the edge is where that record's
   surface lives. The command menu is about the whole workspace and belongs to
   no edge; docking it would claim a relationship it does not have. It is also
   the one surface that is always keyboard-first, and a keyboard has no reach
   problem to solve. */

/* ── T6 what you owe ─────────────────────────────────────────────────────
   One card, above the route, and only when there is something on it. It is
   hidden by default and revealed by the presence of a rendered row — the
   keyed diff already stamps `data-rendered-key` on everything it puts in a
   list, so this needs no flag of its own and cannot disagree with the list it
   is describing. A card that is permanently present and usually empty is a
   card people stop seeing. */
/* SEARCH EVERY CHAT (ADR-095). The results take the chat list's place while
   there is a query; the pills sit under the field they were typed in. */
.chatfind { padding: 0 12px 8px; }
.chatfind .field { width: 100%; }
/* `.cfpill`, not `.fpill`: that name is the list pages' filter pill (above),
   and these rules first shipped under it, where the tap-area `::after` of a
   search pill landed on every filter pill in the product and took the clicks
   meant for its remove button. */
.cfpills { display: flex; flex-wrap: wrap; gap: 5px; }
.cfpills:not(:empty) { margin-top: 6px; }
.cfpill {
  display: inline-flex; align-items: center; gap: 4px; min-height: 26px; padding: 0 4px 0 8px;
  background: var(--win); border: 1px solid var(--line); border-radius: 99px;
  font: 500 11.5px var(--m); color: var(--ink); cursor: pointer; position: relative;
}
.cfpill::after { content: ""; position: absolute; inset: -9px 0; }
.cfpill svg { width: 12px; height: 12px; display: block; color: var(--muted); }
.cfpill:hover svg { color: var(--ink); }
aside[data-searching] > .seclist.chats { display: none; }
.findres[hidden] { display: none; }
.findres { display: flex; flex-direction: column; padding: 0 6px 12px; overflow-y: auto; }
.findres .fr {
  display: flex; flex-direction: column; gap: 2px; min-height: 44px; padding: 7px 8px;
  border-radius: 6px; color: var(--ink); text-decoration: none;
}
.findres .fr:hover, .findres .fr:focus-visible { background: var(--win); }
.findres .frc { font: 500 11px var(--m); color: var(--muted); }
.findres .frq { font: 500 12.5px/1.35 var(--f); overflow-wrap: anywhere; }
.findres .fra { font: 12px/1.4 var(--f); color: var(--ink-2); overflow-wrap: anywhere; }
.findres mark { background: none; color: var(--ink); font-weight: 600; box-shadow: inset 0 -2px var(--accent); }
.findres .fnone { margin: 8px; font-size: 12.5px; color: var(--muted); }
/* A turn a link pointed at (`?turn=`), marked where it landed. */
.turn[data-marked] { box-shadow: -3px 0 0 var(--accent); }

/* The morning plan (ADR-093): hidden until the list has drawn a plan into it,
   the same idiom as the follow-ups card below. */
.morning { display: none; margin: 0 0 12px; }
.morning:has(> .mplan > [data-rendered-key]) { display: block; }
.morning > header { display: flex; align-items: center; gap: 8px; padding: 0 0 6px; }
.morning > header b { font: 600 11.5px var(--d); color: var(--accent); }
.mplan { border: 1px solid var(--line); border-radius: 9px; background: var(--win); padding: 12px 14px; }
.mplan .md { font-size: var(--font); color: var(--ink); line-height: 1.5; }
.mp-when {
  display: inline-flex; align-items: center; min-height: 32px; margin-top: 6px;
  font: 500 11.5px var(--m); color: var(--muted); text-decoration: none;
}
.mp-when:hover { color: var(--ink); text-decoration: underline; }
.owed { display: none; margin: 0 0 12px; }
.owed:has(> .owedlist > [data-rendered-key]) { display: block; }
.owed > header {
  display: flex; align-items: center; gap: 8px; padding: 0 0 6px;
}
.owed > header b { font: 600 11.5px var(--d); color: var(--accent); }
.owed .ow-n {
  font: 600 11px var(--m); min-width: 18px; height: 18px; padding: 0 5px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 99px; background: var(--ink); color: var(--win);
}
.owedlist {
  border: 1px solid var(--line); border-radius: 9px; background: var(--win); overflow: hidden;
}
.owe {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 11px; border-top: 1px solid var(--line-2); font-size: var(--font);
}
.owe:first-child { border-top: 0; }
.owe .grow { flex: 1; min-width: 0; }
.owe .what { display: block; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.owe .meta { display: block; font-size: 11.5px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.owe .ow-when { font: 500 11.5px var(--m); color: var(--muted); white-space: nowrap; }
/* Late is a change of ground, not a change of hue. A red word in a grey
   interface is the first thing that stops being read; an inverted chip is
   still legible at arm's length in sunlight, which is where this is read. */
.owe.late .ow-when { background: var(--inv); color: var(--inv-ink); padding: 1px 6px; border-radius: 3px; }
.owe form { margin: 0; }
.owe button { min-height: 28px; padding: 3px 10px; font-size: 12px; }
.owed .ow-status { margin: 4px 0 0; }
@media (max-width: 899px) {
  /* On a phone the promise wins the row and the timing goes under it, because
     a name truncated to nothing to make room for "3 days late" tells you that
     something is late without telling you what. */
  .owe { flex-wrap: wrap; row-gap: 2px; }
  .owe .grow { flex: 1 0 100%; order: 1; }
  .owe .ow-when { order: 2; }
  .owe form { order: 3; margin-left: auto; }
}

/* ── T7 pinboards ────────────────────────────────────────────────────────
   `.actbar` groups a selection bar with the readout that belongs to it, so
   `[data-act-region]` can find the right one on a screen that has two. It
   generates no box: `display: contents` means the layout is exactly what it
   was before the grouping existed, which is the only honest way to add a
   wrapper to a page whose spacing rules are written against `main >`. */
.actbar { display: contents; }

/* The board's own count, in the second column and on a board link. */
.seclist a .ct[data-format="listSize"] { font-variant-numeric: tabular-nums; }

/* Two act forms in one bar need to stay legible when they wrap on a phone.
   16px, not the 12px the bar's other chrome uses: iOS zooms the whole page
   when a control under 16px takes focus, and a bar that zooms the page every
   time you choose a board is a bar nobody uses twice. `U12` caught exactly
   this the first time these selects went in at 12px. */
.selbar .inline-act select {
  min-height: 32px; padding: 3px 8px; font: 400 16px var(--f);
  border: 1px solid var(--line); border-radius: 6px; background: var(--win); color: var(--ink);
  max-width: 44vw;
}
@media (min-width: 900px) {
  /* At a desk there is no zoom to provoke, so the select matches the bar. */
  .selbar .inline-act select { font-size: 12px; }
}
@media (max-width: 899px) {
  .selbar .inline-act { flex: 1 1 100%; }
}

/* ── T8 the street ribbon ────────────────────────────────────────────────
   A door-to-door operator works a street, and the question is which doors on
   it have not been knocked on. NO MAP: a map needs tiles from somebody else's
   server, which is a third party watching where the operator stands, a blank
   rectangle the moment the signal drops, and a picture that answers "where
   am I" when the question is "which door is next". The ribbon answers the
   question that was asked, from data already in the workspace, offline.
   [WAYMARK, ADR-077] The map at /map.html keeps those three reasons as
   constraints — its own road files, offline, the order of the doors — and
   this ribbon stays beside it. */
.ribbon {
  display: flex; gap: 0; overflow-x: auto; padding: 4px 0 12px;
  scroll-snap-type: x proximity; scrollbar-width: thin;
}
.ribbon::-webkit-scrollbar { height: 6px; }
.ribbon::-webkit-scrollbar-thumb { background: var(--line); border-radius: 99px; }

.door {
  flex: 0 0 auto; scroll-snap-align: start; position: relative;
  width: 132px; padding: 10px 11px 12px; text-align: left;
  border: 1px solid var(--line); border-right: 0; background: var(--win); color: var(--ink);
}
.door:first-child { border-radius: 9px 0 0 9px; }
.door:last-child { border-right: 1px solid var(--line); border-radius: 0 9px 9px 0; }
.door + .door { border-left: 1px solid var(--line-2); }
.door:hover { background: var(--line-2); }

/* The pavement: one continuous line under the whole ribbon, so the doors read
   as a street rather than as a row of cards. */
.door::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: 2px; background: var(--line);
}
/* A door somebody actually spoke at is filled. A door nobody has knocked on
   is the gap, and the gap is the point of the whole surface. */
.door.spoke::after { background: var(--ink); }
.door.refused::after { background: repeating-linear-gradient(90deg, var(--ink) 0 3px, transparent 3px 6px); }

.door .no { font: 600 11px var(--m); color: var(--muted); font-variant-numeric: tabular-nums; }
.door .nm {
  display: block; font-size: var(--font-sm); font-weight: 600; margin: 2px 0 4px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.door .mk { display: block; font-size: 11px; color: var(--muted); }
.door.spoke .mk { color: var(--ink); }
.door.even .no::before { content: "◧ "; opacity: .55; }
.door.odd .no::before { content: "◨ "; opacity: .55; }

/* The coverage bar: spoken-at doors as a share of the street, read from the
   same records the ribbon draws. A number nobody has to maintain. */
.coverage { display: flex; align-items: center; gap: 9px; margin: 0 0 10px; font-size: var(--font-sm); color: var(--muted); }
.coverage .track { flex: 1; height: 4px; border-radius: 99px; background: var(--line-2); overflow: hidden; max-width: 260px; }
.coverage .track i { display: block; height: 100%; background: var(--ink); width: 0; }

@media (max-width: 899px) {
  .ribbon { padding-left: 13px; margin-left: -13px; }
  .door { width: 118px; }
}

/* ── [T10] the letter, and the statement that nothing sends it ───────────── */

/* The merged text, shown as text rather than as a field value. `pre-wrap`
   because a letter's line breaks are part of what was approved: collapsing
   them would mean the operator reads one shape and queues another. */
.letter {
  margin: 4px 0 0;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-left: 2px solid var(--accent);
  border-radius: var(--r);
  background: var(--line-2);
  font: 13px/1.55 var(--f);
  color: var(--ink);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* A letter that is a COPY rather than a key is marked as one, by inversion of
   the accent edge
   rather than by a colour anyone has to learn — the same posture the rest of
   the app takes to alarm.

   Keyed on `body:*` — the record holding its own words — and not on the queue
   instant, so a withdrawn message somebody hand-edited reads as a copy too.
   The mark is the record answering, not a flag anyone maintains. */
.letter.frozen { border-left-color: var(--inv); background: var(--win); }

/* NOT a warning banner. It states a fact about the code, so it is drawn as
   ordinary prose with an accent rule, not as an alert. Title case, per the
   house rule — no transform here or anywhere. */
.notsent {
  margin: 0 0 12px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-left: 2px solid var(--inv);
  border-radius: var(--r);
  background: var(--win);
  font: 13px/1.5 var(--f);
  color: var(--ink-2);
  max-width: 68ch;
}
.notsent b { color: var(--ink); font-weight: 600; }

/* The gap count on a row. Reads as a number when there are gaps and
   disappears when there are none, because "0 gaps" on forty rows is forty
   pieces of noise. */
.list .gaps:empty { display: none; }

/* ── the agent orb ───────────────────────────────────────────────────────
   One mark for the agent, and the only one not drawn by the glyph generator,
   because it carries four states and two of them move (ADR-046). `dither.js`
   reads `data-size` and sets both the CSS box and the backing store, so
   nothing here sets a width — a size written in two places is a size that
   disagrees with itself.

   The orb takes `color` from the cascade rather than carrying a fill, which
   is how one asset serves the rail, an ink-filled button and a dark ground.
   It is `aria-hidden` everywhere it appears: the state is said in words
   beside it, because a shape is never the only carrier of meaning (U10). */
canvas.dith { display: block; flex: none; color: var(--ink); }
.rail canvas.dith, .dock canvas.dith { margin: 0 auto; }
button canvas.dith, a canvas.dith { color: inherit; }

/* ── the ask lane (ADR-050) ─────────────────────────────────────────────
   The question box sits above the cards because the card is usually its
   answer, and reading downward should read as ask then answer.

   A row is quiet on purpose: a hairline, not the ink rule a proposal gets.
   The ink rule means "this one is addressed to you and wants a press", and a
   question in flight wants nothing.

   Tokens, because this block got three of them wrong on the first pass and the
   page still rendered: `--bg` is the GREY GROUND and `--win` is the white a
   card floats on, so a card on `--bg` is grey on grey; `--line-2` is LIGHTER
   than the ground, so a border in it is invisible on anything but white; and
   `--fg` does not exist at all, so every `color:` naming it was dropped and
   the text merely inherited. None of it threw. */
.ask, .asks { margin: 0 0 14px; }

/* THE SECTION DISAPPEARS WHEN THERE IS NOTHING OUTSTANDING. A heading reading
   0 over a 115px block explaining what asking does is not help, it is the page
   apologising to somebody who has not done anything yet. Same idiom as `.owed`
   above: hidden by default, shown once the binder has rendered a row. */
.asks { display: none; }
.asks:has(> .list > [data-rendered-key]) { display: block; }

/* One quiet line under the bar, in place of two empty states. */
.askhint { margin: -4px 0 0; font: 400 12px/1.45 var(--s); color: var(--muted); }

/* A compact empty state: a sentence on its own line rather than a heading and
   a paragraph, for a section whose long-form explanation now lives where the
   person is actually looking. */
.empty.flat { padding: 9px 0; text-align: left; }
.ask h2, .asks h2 { margin: 0 0 9px; }
.ask h2 .ct, .asks h2 .ct { margin-left: 6px; font: 500 12px var(--m); color: var(--muted); }

/* WRAP, and this is load-bearing rather than tidy. `.capture-status` is
   `flex: 0 0 100%`, so on a nowrap line it demands the bar's full width
   alongside the field and the button; everything then shrinks proportionally
   and the input settles at 24px — a field too small to type in, with no error
   anywhere. Measured, not guessed. */
.askbar { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 0 0 10px; }
.askbar input[type="text"], .askbar input:not([type]) {
  /* `width: auto` is deliberate. The base `input` rule is `width: 100%`, which
     as a flex basis means the field alone claims the whole line and wraps the
     button underneath it. Growing from content instead keeps them on one row
     and still fills the bar. */
  width: auto; flex: 1 1 200px; min-width: 0;
  padding: 9px 11px;
  border: 1px solid var(--ink); border-radius: 7px;
  background: var(--win); color: var(--ink);
  font: 400 14px/1.3 var(--s);
}
.askbar input::placeholder { color: var(--muted); }
/* The status takes its own line when it has something to say, and no room at
   all when it does not — an empty block here holds open a gap that reads as a
   missing control. */
.askbar .capture-status { flex: 0 0 100%; margin: 2px 0 0; }
.askbar .capture-status:empty { display: none; }

/* The lane's own readout, for the act that lives in it. Flush left like its
   heading rather than inset like a card's, and gone entirely until there is
   something to say — an 18px reserved strip under a heading reads as a missing
   line. */
.asks > .capture-status { padding: 0 0 5px; }
.asks > .capture-status:empty { display: none; }

/* `minmax(0, 1fr)`, and the zero is the whole point. A grid with no template
   gets ONE `auto` column, and an auto column never shrinks below the widest
   row's max-content — the widest row being one whose error came back as a
   paragraph. Measured on 500 rendered questions: a single failure carrying an
   8,467px-wide reason widened this column to 891px inside a 390px screen and
   slid EVERY row, and the page under them, 514px sideways. The rest of the
   lane was fine; one bad sentence took the page with it. */
.asks > .list {
  background: none; border: 0; border-radius: 0; overflow: visible;
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 6px;
}

.askrow {
  display: grid;
  /* Same zero, one level down: the question track is `1fr` but a grid item's
     default `min-width: auto` is its min-content, so a long word in a question
     pushes the track wider than the row it is in. */
  grid-template-columns: 18px minmax(0, 1fr) auto;
  align-items: center;
  gap: 9px;
  padding: 9px 11px;
  border: 1px solid var(--line); border-radius: 7px;
  background: var(--win);
}
.askrow .q { margin: 0; min-width: 0; overflow-wrap: anywhere; font: 400 14px/1.35 var(--s); color: var(--ink); }
/* THE WORDS ARE THE OTHER CARRIER (U10) — the thing a person reads when the
   pattern is not available to them, on a phone, outdoors. `--muted` is the
   weakest colour on this page: 5.05:1 on white and 4.92:1 on the dark ground,
   over the 4.5 a small face must clear and under the 7 it wants. `--ink-2`
   measures 10.5:1 and 8.9:1 and is still quieter than the question beside it.
   The timestamp under an answered question stays muted; it is a caption, not
   a carrier. */
.askrow .s { margin: 0; font: 500 12px var(--m); color: var(--ink-2); white-space: nowrap; }
/* The error takes the full width under the question rather than the status
   slot, because it is a sentence and the status slot is a label. */
.askrow .err {
  grid-column: 2 / -1; margin: 3px 0 0;
  min-width: 0; overflow-wrap: anywhere;
  font: 400 12px/1.4 var(--s);
  /* An error is the one line in this row a person has to read, and `--muted`
     measured 5.05:1 on white and 4.92:1 on the dark ground — over the 4.5
     floor and under the 7 a small face wants. Ink, like the state label beside
     it, which this row already turns to ink when it fails. */
  color: var(--ink-2);
}
.askrow .err:empty { display: none; }
/* A stopped question keeps the hairline but loses the quiet: something went
   wrong and the operator has to decide whether to ask again. */
/* Below the rows, not above them. Static children of a list sit ahead of the
   rows the binder appends, and a count of what is hidden belongs at the end of
   what is shown. */
.asks > .list > .lanemore {
  order: 2; margin: 2px 0 0;
  font: 400 12px var(--s); color: var(--muted);
}
.asks > .list > .lanemore b { font-weight: 600; color: var(--ink-2); }

/* STOP, and the fourth track it needs.
   The track is declared per state rather than always, because an `auto` track
   with nothing in it is still separated from the one before it by the 9px gap
   — so a settled row would sit its state label 9px further from the edge than
   an open one, and six rows of that is a ragged right margin in a list whose
   whole job is to be scannable. The row already carries its state as an
   attribute, so the template follows it exactly. */
.askrow { grid-template-columns: 18px minmax(0, 1fr) auto auto; }
.askrow .stop {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 4px 10px; border: 1px solid var(--line); border-radius: 6px;
  background: var(--win); color: var(--ink-2);
  font: 500 12px var(--m); cursor: pointer; white-space: nowrap;
}
.askrow .stop:hover { border-color: var(--ink); color: var(--ink); }
/* A settled row keeps the BOX and loses the button. `visibility` rather than
   `display` on purpose: it takes the control out of the tab order, off the
   pointer and out of the accessibility tree exactly as `display: none` does,
   and it leaves the track its width — so the state labels of six rows line up
   down one edge instead of stepping in and out by the width of a button
   depending on whether that row can still be stopped. Reserving the track for
   every row and hiding the control is the only way to get both. */
.askrow:not([data-state="asked"]):not([data-state="answering"]) .stop { visibility: hidden; }

/* Only a closed question gets the line, and it replaces a button rather than
   accompanying one: `failed` is terminal in rules.ts, so asking again is a new
   question and the box at the top is where questions are made. */
.askrow .again { display: none; }
.askrow[data-state="failed"] .again {
  display: block; grid-column: 2 / -1; margin: 3px 0 0;
  font: 400 12px/1.4 var(--s); color: var(--muted);
}

.askrow[data-state="failed"] { border-color: var(--ink); }
.askrow[data-state="failed"] .s { color: var(--ink); }

/* A thumb is 44px and every control in this lane measured smaller than that on
   a 390px screen: the field 39px, Ask 44x32, and on the card below it Commit
   70x32 and Cancel 63x32. The operator uses this from a phone on a footpath,
   so the phone gets the bigger targets and the desk keeps app.css's own denser
   sizes. `hover: none` is the real test and the width is there so the same
   rule is measurable in a browser that is merely narrow. */
@media (hover: none), (max-width: 620px) {
  .askbar input[type="text"], .askbar input:not([type]) { min-height: 44px; }
  .askbar button[type="submit"] { min-height: 44px; min-width: 56px; }
  .prop > footer button { min-height: 44px; padding-left: 14px; padding-right: 14px; }

  /* THE STATE LABEL DROPS UNDER THE QUESTION. It is `white-space: nowrap`, so
     beside the question on a 390px screen "Waiting for the agent" claimed
     231px of a 364px row and folded "who did I miss on the west side?" onto
     three lines. The question is what the row is; the label is a caption on
     it. Found by looking at the render, not by the harness — the squeeze check
     asked whether the question column fell under 80px, and 133px is plenty by
     that measure and unreadable in fact. */
  .askrow { grid-template-columns: 18px minmax(0, 1fr); align-items: start; }
  /* An open row keeps a third track, for Stop alone. The label drops under the
     question either way; the button sits beside the label rather than under it
     so stopping something costs no extra height on the screen this page is
     actually used on.

     And here the settled row gives the track BACK, which the desk does not: 58
     px of a 390px screen reserved for a control that is not there costs the
     question a fifth of its width, and there is no column of labels to keep
     straight on a phone because the label is under the question, not beside
     it. */
  .askrow[data-state="asked"], .askrow[data-state="answering"] {
    grid-template-columns: 18px minmax(0, 1fr) auto;
  }
  .askrow:not([data-state="asked"]):not([data-state="answering"]) .stop { display: none; }
  /* The question and its label stack in column two and the button stands
     beside BOTH of them, which is why the rows are placed by hand here. Let
     the button take its own line instead and a 44px thumb target adds 32px to
     every open row — measured: 94px a row against 66px, and six of those is
     168px of lane for one control. */
  .askrow .q { align-self: center; grid-column: 2; grid-row: 1; }
  .askrow .s { grid-column: 2; grid-row: 2; white-space: normal; align-self: center; }
  .askrow .q + .s { margin-top: 2px; }
  .askrow[data-state="asked"] .stop, .askrow[data-state="answering"] .stop {
    grid-column: 3; grid-row: 1 / span 2; align-self: center;
    min-height: 44px; min-width: 56px; padding-left: 14px; padding-right: 14px;
  }
}

/* ── what the agent said (ADR-053) ───────────────────────────────────────
   A question answered in prose used to leave nothing on the page: the answer
   was written to the record and rendered by no element. These rows are that
   answer, and they carry no control at all — there is nothing to approve,
   which is the whole difference between this section and the cards above it.

   Hidden while empty, by the same `:has` idiom the lane uses: a heading over
   an empty state, on a page that already has three of them, is 60px of the
   first screen spent saying nothing happened. */
.said { margin: 0 0 14px; display: none; }
/* The host's own line, said only while it is standing in front of something. */
.proj .earlier { margin: -2px 0 8px; font: 400 12px/1.45 var(--s); color: var(--muted); }
.proj .earlier:empty { display: none; }
/* Below the rows, like the lane's. A static child of a list sits ahead of the
   rows the binder appends, so `order` is what puts a count of what was cut at
   the end of what is shown — with the Open link under it, which is where the
   rest of it is. `.list` is a plain block, so the column has to be declared
   here or `order` is a no-op that reads as the line simply being in the wrong
   place, which is exactly how this first rendered. */
.proj .pv > .list { display: flex; flex-direction: column; }
.proj .pv > .list > .lanemore {
  order: 2; margin: 0; padding: 8px 11px;
  border-top: 1px solid var(--line-2);
  font: 400 12px var(--s); color: var(--muted);
}
.proj .pv > .list > .lanemore b { font-weight: 600; color: var(--ink-2); }
/* `.list > *:first-child .row` loses its top border, and the first CHILD here
   is a static line the binder's rows are ordered above. The first ROW is what
   needs it off. */
.proj .pv > .list > article:first-of-type .row { border-top: 0; }
.said:has(> .list > [data-rendered-key]) { display: block; }
.said h2 { margin: 0 0 9px; }
.said h2 .ct { margin-left: 6px; font: 500 12px var(--m); color: var(--muted); }
.said > .list {
  background: none; border: 0; border-radius: 0; overflow: visible;
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 6px;
}
.saidrow {
  min-width: 0; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: 7px; background: var(--win);
}
/* The QUESTION is context and the ANSWER is what was asked for, so the answer
   takes the ink and the question takes the quiet — the reverse of the lane
   above, where nothing has been answered yet and the question is all there
   is. Quoted for the same reason the card quotes it: it is somebody's words. */
.saidrow .q {
  margin: 0 0 5px; min-width: 0; overflow-wrap: anywhere;
  font: 400 13px/1.4 var(--s); color: var(--muted);
}
.saidrow .q::before { content: "\201C"; }
.saidrow .q::after { content: "\201D"; }
.saidrow .a, .saidrow .full {
  margin: 0; min-width: 0; overflow-wrap: anywhere; white-space: pre-wrap;
  font: 400 14px/1.5 var(--s); color: var(--ink);
}
.saidrow .a:empty { display: none; }
/* The disclosure is there only when something is behind it. The attribute is
   written by a format off the record itself, so this cannot say "more" over an
   answer that is already whole. */
/* `details.more` is the page's disclosure, marker and reduced-motion rule and
   all — a second one styled by hand here lost its triangle to `display: flex`
   on the phone rule below and read as a line of text that happened to open. */
.saidrow .whole { margin: 6px 0 0; }
/* Open, the whole answer REPLACES its own opening rather than repeating it:
   the lead is the first 420 characters of the same text, so leaving both on
   screen makes the reader check whether the second copy is different. */
.saidrow:has(> .whole[open]) > .a { display: none; }
.saidrow:has(> .whole[open]) > .whole { margin-top: 0; border-top: 0; padding-top: 0; }
.saidrow .whole[data-cut="no"], .saidrow .whole:not([data-cut]) { display: none; }
.saidrow .s {
  display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: baseline;
  margin: 7px 0 0; font: 500 12px var(--m); color: var(--muted);
}
.saidrow .s .when { font-weight: 400; }
.saidrow .s .made { color: var(--ink-2); }
.saidrow .s .made:empty { display: none; }
/* No href means the answer produced no list, or produced one over an entity
   with no page of its own — the format writes "" and this drops the link
   rather than leaving an anchor that reloads the page you are standing on. */
.saidrow .s .madelink[href=""], .saidrow .s .madelink:not([href]) { display: none; }
.saidrow .s .madelink { color: var(--ink); text-decoration: underline; text-underline-offset: 2px; }
.saidrow .s .madelink:hover { color: var(--ink-2); }
@media (hover: none), (max-width: 620px) {
  .saidrow .s .madelink { min-height: 44px; display: inline-flex; align-items: center; }
}
.said > .list > .lanemore {
  order: 2; margin: 2px 0 0; font: 400 12px var(--s); color: var(--muted);
}
.said > .list > .lanemore b { font-weight: 600; color: var(--ink-2); }
@media (hover: none), (max-width: 620px) {
  .saidrow .whole > summary { min-height: 44px; }
}

/* ── a proposal (ADR-046) ────────────────────────────────────────────────
   Taken from the design the operator drew: an ink border and a press.

   The cards do not live inside a list's chrome, so the list that holds them
   gives up its own background and border — otherwise every card is a box
   inside a box, which is what the first version looked like and why it was
   right to call it ugly. Each card floats on the grey ground and carries its
   own outline, and the outline is INK rather than a hairline because this one
   is addressed to a person. */
.props > .list { background: none; border: 0; border-radius: 0; overflow: visible; display: grid; gap: 10px; }
.props h2 { margin: 0 0 9px; }
.props h2 .ct { margin-left: 6px; font: 500 12px var(--m); color: var(--muted); }

.prop {
  border: 1px solid var(--ink);
  border-radius: 7px;
  overflow: hidden;
  background: var(--win);
}

/* The header bar says how much and how little: the size of the act on the
   left, and on the right the fact that none of it has happened. */
.prop > header {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 10px;
  background: var(--line-2);
  border-bottom: 1px solid var(--line);
}
.prop > header b { font-size: 12px; font-weight: 600; }
/* WHAT WAS ASKED. The propose route has stored this since the seam was built
   and the card threw it away, while the instructions the agent reads told it
   "`asked` is the request in the words it was made in — it is printed above
   the card". It was not. Two cards on screen at once were two sets of
   operations with nothing saying which question either one came from. */
.prop .asked {
  margin: 0; padding: 8px 11px 0;
  font: 400 13px/1.4 var(--s); color: var(--ink);
  overflow-wrap: anywhere;
}
.prop .asked:empty { display: none; }
.prop .asked::before { content: "\201C"; }
.prop .asked::after { content: "\201D"; }
.prop > header .mark { margin-left: auto; font-size: 12px; font-weight: 600; color: var(--ink); }
.prop > header .mark::before { content: "\25CB\FE0E"; margin-right: 5px; }
.prop[data-state="committed"] > header .mark::before { content: "\25CF\FE0E"; }
.prop[data-state="cancelled"] > header .mark::before { content: "\2715\FE0E"; }
.prop[data-state="cancelled"] > header .mark { color: var(--muted); font-weight: 400; }

/* One line per operation: the verb and the entity, then the values. Mono,
   because the left column is padded to align and a proportional face would
   throw that away. `pre-wrap` so a long value wraps on a phone rather than
   scrolling sideways. */
.prop .rows {
  margin: 0;
  padding: 9px 11px;
  font: 12.5px/1.7 var(--m);
  color: var(--ink);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* WHAT IT COULD NOT FILL, in prose rather than as a badge. An agent that
   found no email writes no email, and a card silent about it reads as
   complete. Empty prints nothing rather than "Left blank: none". */
.prop .gaps {
  margin: 0;
  padding: 7px 11px;
  border-top: 1px solid var(--line-2);
  font: 12px/1.5 var(--f);
  color: var(--muted);
  background: var(--win);
}
.prop .gaps:empty { display: none; }

/* The exact operations, field by field, one press away. Folded by default
   because the two columns above are what a person reads at a door, and open
   to the bytes for anyone who wants to check them. */
.prop > details.more { border-top: 1px solid var(--line-2); }
.prop > details.more > summary { padding: 7px 11px; font-size: 12px; }
.prop .ops {
  margin: 0;
  padding: 0 11px 9px;
  font: 11.5px/1.6 var(--m);
  color: var(--ink-2);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.prop > footer {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 9px 10px;
  border-top: 1px solid var(--line);
  background: var(--win);
}
.prop > footer .by { margin-left: auto; font-size: 11.5px; color: var(--muted); }
.prop > footer .when { font-size: 11.5px; color: var(--muted); }
.prop[data-state="cancelled"] { border-color: var(--line); }
.prop[data-state="cancelled"] .rows { color: var(--muted); }


/* ── a projected view (ADR-048) ──────────────────────────────────────────
   The read half of the agent surface, and deliberately NOT drawn like a
   proposal. A proposal is a question put to the operator and wears an ink
   border to say so; this is an answer and wears a hairline. Nothing here is
   waiting on anybody, so there is nothing to approve and no button to press —
   only a way to go and see it at full size. */
.proj { margin: 0 0 14px; }
.proj h2 { margin: 0 0 9px; }

/* What was asked, in the operator's own words, above what came back. The
   question, not a caption on the answer, so it is set in the reading face
   rather than in the grey of a note. */
.proj .asked {
  margin: 0 0 8px;
  font: 15px/1.5 var(--f);
  color: var(--ink);
  max-width: 68ch;
}

.proj .pv {
  border: 1px solid var(--line);
  border-radius: 7px;
  overflow: hidden;
  background: var(--win);
}
.proj .pv > header {
  display: flex; align-items: center; gap: 7px;
  padding: 6px 10px;
  border-bottom: 1px solid var(--line-2);
  font-size: 11.5px;
  color: var(--muted);
}
.proj .pv > header .vn { color: var(--ink); font-weight: 600; }
.proj .pv > .list { border: 0; border-radius: 0; }
.proj .pv .open {
  display: block;
  padding: 7px 10px;
  border-top: 1px solid var(--line-2);
  font: 600 11.5px var(--f);
  color: var(--accent);
  text-decoration: none;
}
.proj .pv .open:hover { text-decoration: underline; }
/* `display: block` in a class rule out-specifies the user-agent's
   `[hidden] { display: none }`, so an element hidden by the attribute goes on
   showing. It cost a failing check here and it is the same trap `.app input`
   sets elsewhere in this file: a utility loses to anything with a class. */
.proj .pv .open[hidden] { display: none; }


/* ── a thumb is 44px, everywhere it has to be (ADR-053) ──────────────────
   The ask lane and the proposal card got this when the agent surface was
   swept; the rest of the app never did. Fifteen pages rendered at phone and
   tablet width with touch, against a log holding 3,288 companies, reported
   102 controls under 44px: every view chip at 32px tall on the eight pages
   that have a bar of them, the position button at 78x24, a row's selection
   box at 17x17, every select at 38px, every text field at 36px.

   Behind the app's own media query, so the desk keeps its denser sizes — and
   `hover: none` is the real test, which is why the sweep runs those two
   viewports with touch on. A browser context without it never matches this
   block at all, and the first run of that sweep reported the desk's controls
   as failures and could say nothing about a tablet.

   AT THE END OF THE FILE, and that is not tidiness. The first version sat
   beside the rules it was raising, matched its media query, and changed
   nothing: `.views button` is set to 32px by a `max-width: 899px` block and
   `button.linkish` to 24px by its own rule, both at the same specificity and
   both later in the file. Everything about it looked right and every
   measurement came back unchanged. A block whose whole job is to be the last
   word has to be last.

   The 899px rule stays as it is. It is about a narrow window with a MOUSE in
   it, where a 32px pill is reasonable; this is about a thumb.

   `.btn` is deliberately absent. It is the button inside a collapsed row, and
   raising it pushes the row past the height the compact density setting is
   measured at (U8) — a decision, not a number for a rule about thumbs to
   move. Those are still in the sweep's output, named, waiting on that
   decision. */
@media (hover: none), (max-width: 620px) {
  .views button,
  button.linkish,
  button.btnp, button.primary, button.danger-always,
  .actbar button, .selbar button,
  a.facet, label.check,
  select,
  textarea,
  input[type="text"], input[type="search"], input[type="tel"], input[type="email"],
  input[type="number"], input[type="date"], input[type="url"], input:not([type]) {
    min-height: 44px;
  }
  .views button { padding-left: 12px; padding-right: 12px; }
  /* A checkbox cannot simply be made 44px — it would draw as a 44px box. It
     gets a bigger box, and the label around it gets the thumb's worth of
     height, which is the part a thumb actually lands on. */
  .selcol input, .selbox, .selall input { min-width: 24px; min-height: 24px; }

  /* PORTAGE phase 7 — the filter pills, added HERE and nowhere else, for the
     reason this block exists: `.fpill select` and `.fpill input` set an
     explicit 26px height a few hundred lines up, and a rule raising them that
     sat beside that one would lose to it or be lost to it depending on order.
     `height` and `min-height` are raised together, because min-height alone
     leaves a 26px `height` fighting it on a flex item.

     The pill itself is raised so the whole object is a thumb target, not just
     the control inside it — a 44px select inside a 26px pill would overflow
     its own border. The remove keeps a square target at the same height. */
  .fpill { min-height: 44px; }
  .fpill select, .fpill input { min-height: 44px; height: 44px; }
  .fpill b, .fpill .pv { line-height: 44px; }
  /* `width` is not enough on a flex item: `.fpill` is an inline-flex row and
     this button shrinks out of its declared width when the pill is narrow.
     Measured at 21px against a declared 44px on a one-pill query, while a
     two-pill query drew it at 44 — so the defect only appears at some
     widths, which is exactly why the sweep reads the drawn box. */
  .fpillx { min-height: 44px; height: 44px; flex: 0 0 44px; }
  .addfilter > summary { width: 44px; height: 44px; }
  /* The settings segments. The desk size is 30px, which is right beside a
     pointer and wrong under a thumb; out here each half of the choice is a
     44px target. */
  dialog.settings .sseg button { min-height: 44px; }
  /* `width` alone does NOT size this: `.ctl` sets `flex: 0 0 var(--ctl-size)`
     and the header is a flex row, so flex-basis wins and the button drew 44px
     tall and 22px wide — half a target, and the half a thumb misses. Measured
     by the thumb sweep, which is why the sweep reads the drawn box rather than
     the declared rule. */
  dialog.settings header .ctl { width: 44px; height: 44px; flex: 0 0 44px; }
  /* The actions inside an opened row. `.acts` itself is hidden at this width
     and the cluster moves into the row's detail (see `.row .acts` above), so
     these ARE the thumb's targets here — and they were drawing at 32px, the
     `button, .btn` default. PRE-EXISTING: the cluster and its button are in
     the page markup from before this campaign, which rebuilt the reveal around
     them. Scoped to `.acts` rather than raising `.btn` everywhere, because
     `.btn` is used on surfaces this campaign did not measure. */
  .acts .btn, .acts button { min-height: 44px; }
  .selall { min-height: 44px; }
  .selcol { min-height: 44px; display: flex; align-items: center; }
}
/* ══ THE COMPANY RECORD ═══════════════════════════════════════════════════
   The record is a field of module heads over a sheet, and the argument for it
   is the one the inspector's header already makes one screen up: nothing the
   operator is reading should move because they opened something.

   Where this departs from the inspector is the desk posture. The inspector is
   a third column, so opening it narrows the window and the list reflows. A
   record has twelve modules and a four-column field; narrowing that by 340px
   would reflow every tile on every open. So the record's sheet is placed OVER
   the field instead, absolutely, inside a stage that takes no space from it —
   the field keeps its columns, its widths and its scroll position, and the
   price is that the two right-hand columns sit behind the sheet until it
   shuts. That price is stated rather than designed around: reordering the
   field to rescue them would be the reflow this posture exists to avoid.

   Both postures are @media, not @container, because the app renders one
   posture at a time and every other rule in this file breaks at 900px. */

/* ── the identity bar ──────────────────────────────────────────────────── */
.rec-id { background: var(--win); border-bottom: 1px solid var(--line); }
.rec-idr { display: flex; align-items: flex-start; gap: 10px; }
.rec-idl { flex: 1; min-width: 0; }
.rec-id .eyebrow { margin: 0 0 1px; }
.rec-id h1 { font: 700 18px/1.12 var(--d); letter-spacing: -.012em; margin: 0; overflow-wrap: anywhere; }
.rec-idm { margin: 4px 0 0; font-size: 12px; color: var(--muted); display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.rec-acts { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; }
.rec-acts .gl { width: 13px; height: 13px; flex: 0 0 13px; }
.rec-act { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; font-weight: 600; }
.rec-act:hover { border-color: var(--accent); color: var(--accent); }
/* Solid where the fact is there, dashed where it is not, so the pair says
   which of the two you have before you have read either label. 87 of 3,280
   companies have a named contact, so the empty slot is the common case. */
.rec-slot { border-style: dashed; color: var(--accent); background: transparent; white-space: nowrap; }
.rec-slot:hover { border-style: solid; background: var(--accent-soft); }
.rec-plus { font-weight: 400; font-size: 14px; line-height: 1; color: var(--muted-2); }
.rec-slot:hover .rec-plus { color: var(--accent); }

/* ── the stage, and the thing that scrolls inside it ───────────────────── */
.rec-scroll { min-width: 0; }
.rec-none { color: var(--muted); font-size: var(--font-sm); margin: 0; }

/* ── where and who ─────────────────────────────────────────────────────── */
.rec-band {
  display: grid; grid-template-columns: 62px minmax(0, 1fr); gap: 4px 10px;
  align-items: baseline; padding: 8px 13px 9px;
  background: var(--line-2); border-bottom: 1px solid var(--line);
}
.rec-lab { margin: 0; font: 600 11px/1.4 var(--f); color: var(--muted); }
.rec-wl, .rec-bch, .rec-ask, .rec-second { margin: 0; display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; min-width: 0; font-size: var(--font-sm); color: var(--ink); }
.rec-str { font-weight: 600; }
.rec-co { color: var(--muted-2); letter-spacing: -.02em; }
.rec-sep { color: var(--line); }
.rec-person, .rec-reach { min-width: 0; }
.rec-ask > span { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.rec-nmb { font: 700 15.5px/1.25 var(--f); color: var(--ink); letter-spacing: -.012em; }
.rec-nm2 { font: 600 13px/1.3 var(--f); color: var(--ink-2); }
.rec-role { font-size: 11.5px; color: var(--muted); }
.rec-second .rec-also { display: inline-flex; align-items: baseline; gap: 6px; }
.rec-second .rec-also + .rec-also::before { content: "·"; color: var(--line); margin-right: 6px; }
.rec-tel { color: var(--ink); letter-spacing: -.025em; }

/* ── the follow-ups that are due ───────────────────────────────────────── */
.rec-due { background: var(--accent-soft); border-bottom: 1px solid var(--line); padding: 7px 13px 9px; }
.rec-dh { display: flex; align-items: baseline; gap: 7px; margin: 0 0 4px; }
.rec-dn { font: 600 11.5px var(--f); color: var(--accent); }
.rec-dh .ct { font: 500 11px var(--m); color: var(--muted); }
.rec-item { display: flex; align-items: center; gap: 9px; padding: 5px 0; border-top: 1px solid var(--line); }
.rec-item:first-of-type { border-top: 0; }
.rec-item .alarm, .rec-soon { flex: 0 0 auto; min-width: 82px; justify-content: center; }
.rec-soon { font: 600 11px var(--m); color: var(--ink-2); background: var(--win); border: 1px solid var(--line); height: 18px; padding: 0 6px; border-radius: 3px; display: inline-flex; align-items: center; }
.rec-soon:empty, .rec-item .alarm:empty { display: none; }
.rec-txt { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.rec-na { font-size: var(--font-sm); line-height: 1.3; color: var(--ink); font-weight: 500; }
.rec-sub2 { font-size: 11px; line-height: 1.3; color: var(--muted); }
.rec-due .inline-act { flex: 0 0 auto; margin: 0; }
.rec-due .rec-none { padding: 2px 0 0; }

/* ── the field of twelve heads ─────────────────────────────────────────── */
.rec-board { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 7px; padding: 9px 13px 16px; }
.rec-tile {
  align-self: start; min-width: 0; overflow: hidden;
  background: var(--win); border: 1px solid var(--line); border-radius: var(--r);
}
.rec-bare { border-style: dashed; }
.rec-hd {
  display: flex; align-items: center; gap: 6px; width: 100%; margin: 0;
  padding: 0 8px; min-height: var(--row-h); border: 0; border-radius: 0;
  background: transparent; text-align: left; font-weight: 400; color: var(--ink); cursor: pointer;
}
.rec-hd:hover { background: var(--line-2); }
.rec-hd .gl { width: 15px; height: 15px; flex: 0 0 15px; color: var(--muted); }
.rec-hn { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: 600 11.5px var(--f); color: var(--accent); }
.rec-hd .ct { flex: 0 0 auto; font: 500 11px var(--m); color: var(--muted); font-variant-numeric: tabular-nums; }
.rec-op { flex: 0 0 auto; font-size: 10px; line-height: 1; color: var(--muted-2); }
.rec-op::before { content: "\203A"; }
.rec-bare .rec-hn { color: var(--muted); }
.rec-bare .rec-hd .gl, .rec-bare .rec-hd .ct { color: var(--muted-2); }
.rec-body { padding: 2px 9px 7px; }
.rec-peek { list-style: none; margin: 0; padding: 0; }
.rec-peek li { display: flex; gap: 7px; align-items: baseline; padding: 3px 0; font-size: var(--font-sm); line-height: 1.35; border-top: 1px solid var(--line-2); }
.rec-peek li:first-child { border-top: 0; }
.rec-peek .t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-2); }
.rec-peek .w { flex: 0 0 auto; font: 500 10.5px var(--m); color: var(--muted-2); }
.rec-fv { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 1px 10px; margin: 0; font-size: var(--font-sm); }
.rec-fv dt { color: var(--muted); white-space: nowrap; font-size: 11.5px; }
.rec-fv dd { margin: 0; overflow-wrap: anywhere; }
.rec-fv dd:empty { display: none; }
.rec-fv dt:has(+ dd:empty) { display: none; }
.rec-fv.wide { gap: 3px 12px; font-size: var(--font); }

/* ── the sheet ─────────────────────────────────────────────────────────── */
/* THE SCRIM IS A PHONE THING. On a phone the sheet takes most of the screen
   and there is nothing behind it worth pressing, so a wash says "this is in
   front" and a tap outside it dismisses — the gesture people already have.

   At the desk a scrim would be the one thing that makes this posture worse
   than the inspector it borrows from: the field IS how you get from one
   module to the next, and a wash over it turns a one-tap switch into
   close-then-open twelve times over. The sheet carries its own border and
   shadow on the same grey ground every other window here sits on, which is
   how `.inspector` has said "in front" since T3 without dimming anything. */
.rec-scrim { display: none; position: fixed; inset: 0; z-index: 44; margin: 0; padding: 0; border: 0; border-radius: 0; background: rgba(10, 12, 16, .38); cursor: pointer; }
:root[data-theme="dark"] .rec-scrim { background: rgba(0, 0, 0, .46); }
.rec-sheet {
  display: flex; flex-direction: column; min-height: 0; overflow: hidden; z-index: 46;
  background: var(--win); color: var(--ink); border: 1px solid var(--line);
}
.rec-sheet[hidden] { display: none; }
.rec-sheet > header {
  flex: 0 0 auto; position: relative; display: flex; align-items: center; gap: 8px;
  padding: 10px 11px; border-bottom: 1px solid var(--line); background: var(--win);
}
.rec-sheet > header b { flex: 1; min-width: 0; font: 700 14px var(--d); letter-spacing: -.01em; }
.rec-sheet > header .btn { font-size: 11.5px; min-height: 28px; padding: 3px 10px; }
.rec-go { color: var(--accent); font-weight: 600; }
.rec-go:hover { border-color: var(--accent); background: var(--accent-soft); }
.rec-sbody { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 10px 12px 18px; }
.rec-sbody .list { margin: 0; }
/* A sentence in a sheet is allowed only where it says where a record lives,
   which is a fact about the model and not a caption on a form. */
.rec-said { margin: 0 0 9px; font-size: var(--font-sm); line-height: 1.4; color: var(--muted); }

/* ── the desk ──────────────────────────────────────────────────────────── */
@media (min-width: 900px) {
  main > .rec-id { padding: 11px 13px 9px; }
  main > .rec-stage { position: relative; flex: 1 1 auto; min-height: 0; display: flex; }
  .rec-scroll { flex: 1; min-width: 0; overflow-y: auto; }
  .rec-board { grid-template-columns: repeat(4, minmax(0, 1fr)); padding: 9px 13px 18px; }
  .rec-board > .rec-tile:nth-of-type(1) { grid-column: span 4; }
  .rec-board > .rec-tile:nth-of-type(2),
  .rec-board > .rec-tile:nth-of-type(3),
  .rec-board > .rec-tile:nth-of-type(4),
  .rec-board > .rec-tile:nth-of-type(5) { grid-column: span 2; }
  .rec-hd { min-height: 30px; }

  /* 560px against the field's four columns: room for a deal's own rows
     without the two-line wrapping a 340px column forces on every one of them.
     `absolute` on the stage, so the field beneath keeps every pixel it had. */
  .rec-sheet {
    position: absolute; inset: 8px 8px auto auto; width: 560px;
    max-width: calc(100% - 130px); max-height: calc(100% - 16px); border-radius: 9px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, .06), 0 16px 48px rgba(0, 0, 0, .22);
  }
  :root[data-theme="dark"] .rec-sheet { box-shadow: 0 1px 2px rgba(0, 0, 0, .5), 0 18px 50px rgba(0, 0, 0, .5); }
  .rec-sheet .rec-fv.wide { grid-template-columns: repeat(2, auto minmax(0, 1fr)); column-gap: 14px; }
  .rec-sheet .rec-fv.wide > dt:nth-of-type(2n) { padding-left: 18px; }
}

/* ── the phone ─────────────────────────────────────────────────────────── */
@media (max-width: 899px) {
  /* The bar bleeds to both edges, because main carries the page's gutter and
     a strip inset by 13px shows the ground through the gap as it sticks. */
  main > .rec-id { position: sticky; top: 0; z-index: 45; margin: -12px -13px 0; padding: 9px 13px 8px; }
  main > .rec-stage { margin: 0 -13px; }
  .rec-band, .rec-due { padding-left: 13px; padding-right: 13px; }
  .rec-band { grid-template-columns: 58px minmax(0, 1fr); }
  .rec-id h1 { font-size: 17px; }
  .rec-board { padding: 8px 9px 16px; gap: 6px; }
  .rec-board > .rec-tile:nth-of-type(7),
  .rec-board > .rec-tile:nth-of-type(8),
  .rec-board > .rec-tile:nth-of-type(10),
  .rec-board > .rec-tile:nth-of-type(11) { grid-column: span 1; }
  .rec-board > .rec-tile { grid-column: span 2; }

  /* Every control a thumb has to hit is 44px out here. */
  .rec-act, .rec-slot { min-height: 44px; padding: 0 13px; font-size: 12.5px; }
  .rec-due .inline-act button { min-height: 44px; padding: 0 13px; }
  .rec-scrim:not([hidden]) { display: block; }
  /* `.rec-sheet > header .btn` is (0,3,0) and out-specifies a bare
     `.rec-sheet .btn`, which is how the header's Close came back at 28px on a
     phone after this rule was already written. Both selectors, explicitly. */
  .rec-sheet .btn, .rec-sheet > header .btn { min-height: 44px; padding: 3px 13px; }

  /* The app's own bottom-sheet posture, the same one .inspector takes: the
     bottom edge, full width, rounded at the top only, and clear of the dock. */
  .rec-sheet {
    position: fixed; inset: auto 0 var(--dock-h) 0; width: 100%;
    max-height: calc(82vh - var(--dock-h)); border-radius: 14px 14px 0 0; border-bottom: 0;
    box-shadow: 0 -2px 30px rgba(0, 0, 0, .26);
  }
  .rec-sheet > header { padding-top: 19px; }
  /* A grabber, because a surface that rose from the bottom edge should look
     like something you can push back down. */
  .rec-sheet > header::before {
    content: ""; position: absolute; top: 7px; left: 50%; transform: translateX(-50%);
    width: 34px; height: 3px; border-radius: 99px; background: var(--line);
  }
}

/* The channels that are present, separated by the rule rather than by a
   character in the markup, so a missing one takes its separator with it. */
.rec-ch + .rec-ch::before { content: "·"; color: var(--line); margin-right: 7px; }

/* The head the sheet came out of. With no scrim at the desk the field stays
   live and fully legible, which is the whole point of the posture — so the
   only thing left to say is WHICH of the twelve is open, and it is said on the
   head itself rather than by dimming the other eleven. */
[data-record-open][aria-expanded="true"] { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--accent); }
[data-record-open][aria-expanded="true"] .gl, [data-record-open][aria-expanded="true"] .ct,
[data-record-open][aria-expanded="true"] .rec-op { color: var(--accent); }
[data-record-open][aria-expanded="true"] .rec-op::before { content: "\25B8"; }
.rec-tile:has([aria-expanded="true"]) { border-color: var(--accent); }

/* The status line under the follow-ups speaks only after an act; until then
   it is an empty paragraph holding open a third of the band. */
.rec-due .ow-status { margin: 0; }
.rec-due .ow-status:empty { display: none; }


/* ══ THE AGENT SURFACE (PARLEY, ADR-054) ═════════════════════════════════
   One conversation: a header, a scrolling thread, a composer in the foot.
   Every rule below is scoped to `.agentmain`, `.chats` or `.composer` —
   nothing here reaches a page that does not carry one of the three.

   A NOTE ON CLASS NAMES. The card's question line is `.answering`, not
   `.said`, because `.said` is already an inbox lane that carries
   `display: none` until it has rendered rows. A card reusing that name read
   correctly to a test and drew nothing on screen. The newcomer renamed. */

.agentmain { display: flex; flex-direction: column; min-height: 0; }

/* ── THE CHAT IS A COLUMN, NOT THE WINDOW ─────────────────────────────────
   A line of text has a width at which it is comfortable to read and this
   window is wider than that: on a 1440px screen the thread ran the full
   ~1100px of the main pane, so a one-clause answer stretched from one side
   of the screen to the other and a long one was a ~140-character measure.

   So the thread, its header and the composer share ONE column of
   `--thread-w`, centred, with the rest of the window as gutter.

   Implemented as `padding-inline` (and `margin-inline` on the composer,
   which is a bordered box and must not have its border pushed outward), not
   as `max-width` on a wrapper, for two reasons: the scroller keeps its
   scrollbar at the window's own edge rather than floating it in the middle
   of the pane, and the header's bottom rule still runs the full width, which
   is what makes the column read as a column rather than as a floating card.

   `max()` against the base 13px is the narrow posture: below roughly
   `--thread-w` the gutter term goes negative and the phone keeps exactly the
   margins it had. Nothing about this rule needs a media query. */
.agentmain { --thread-w: 46rem; }
.agentmain > .thhd,
.agentmain .turns,
.agentmain > .past-banner { padding-inline: max(13px, (100% - var(--thread-w)) / 2); }
.agentmain > .composer { margin-inline: max(13px, (100% - var(--thread-w)) / 2); }


/* THE THREAD HEADER: the mark, the title, and on desk the kind. */
.agentmain > .thhd {
  display: flex; align-items: center; gap: 9px;
  padding-block: 0; height: 46px; min-height: 46px;
  border-bottom: 1px solid var(--line);
}
.agentmain > .thhd .thhdt { min-width: 0; }
.agentmain > .thhd h1 { margin: 0; font: 600 14px var(--d); color: var(--ink); letter-spacing: -.01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.agentmain > .thhd .kind { margin: 0; font: 11.5px var(--f); color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.agentmain > .thhd .thst { margin: 0 0 0 auto; font: 11.5px var(--f); color: var(--muted); }
.agentmain > .thhd .thst:empty { display: none; }

/* A ROUTINE'S CONTROLS (ADR-090). Icons at the right of its chat's header.
   Pause and resume are two buttons and only the one that applies is drawn,
   off `data-on`; the target is 44px and the paint is 30. */
.agentmain > .thhd .rtctl[hidden] { display: none; }
.agentmain > .thhd .rtctl { display: flex; gap: 6px; margin-left: auto; flex: none; }
.agentmain > .thhd .rtctl button {
  position: relative; width: 30px; height: 30px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 1px solid var(--line); border-radius: 5px;
  color: var(--muted); cursor: pointer;
}
.agentmain > .thhd .rtctl button::after {
  content: ""; position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%); width: 44px; height: 44px;
}
.agentmain > .thhd .rtctl button:hover { color: var(--ink); border-color: var(--ink); }
.agentmain > .thhd .rtctl button svg { width: 15px; height: 15px; display: block; }
.agentmain > .thhd .rtctl[data-on="on"] .rtresume,
.agentmain > .thhd .rtctl[data-on="off"] .rtpause { display: none; }

/* WHO IS HERE, SHUT BY DEFAULT (S5).

   A roster pinned open costs a band of the screen on every turn in a chat
   whose membership changes about once. Shut, it is one sentence — "Nathan,
   Dana and two agents" — which is what a person actually needs to know, and
   it opens to the list with roles when they need more than that. It is absent
   entirely outside a chat with named participants, rather than present and
   saying "just you", because a disclosure that never has anything behind it
   teaches people not to open the ones that do. */
.agentmain > .thhd .who { margin-left: auto; min-width: 0; }
.agentmain > .thhd .who:not([data-has]) { display: none; }
.agentmain > .thhd .who > summary {
  list-style: none; cursor: pointer;
  font: 11.5px var(--f); color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.agentmain > .thhd .who > summary::-webkit-details-marker { display: none; }
.agentmain > .thhd .who > summary::before { content: "\25CB\FE0E"; margin-right: 5px; }
.agentmain > .thhd .who[open] > summary::before { content: "\25CF\FE0E"; }
.agentmain > .thhd .who > summary:hover { color: var(--ink); }
/* Hidden EXPLICITLY when shut. A closed `<details>` hides its non-summary
   children through the UA stylesheet, and an absolutely-positioned child
   escapes that in Chromium — the roster was still laid out, 8,960px² of it,
   behind a disclosure that read as closed. A popover has to say it is hidden
   rather than rely on the element it hangs off being shut. */
.agentmain > .thhd .who:not([open]) > ul { display: none; }
.agentmain > .thhd .who > ul {
  position: absolute; right: 13px; z-index: 5;
  margin: 4px 0 0; padding: 6px; list-style: none;
  min-width: 160px;
  background: var(--win); border: 1px solid var(--line); border-radius: var(--r);
}
.agentmain > .thhd .who > ul > li { padding: 3px 5px; font: 12.5px var(--f); color: var(--ink); }
.agentmain > .thhd .who > ul > li > .rr { color: var(--muted); }

/* THE TURNS. A turn is a question and the answer under it, never two rows
   that have to be matched up by eye. */
/* A SHORT CONVERSATION RESTS ON THE COMPOSER. `agent.html` has claimed this
   in a comment since the page was written and the stylesheet never carried
   the rule, so three turns hung from the header with the rest of the window
   empty beneath them — the shape of a page that has failed to load rather
   than of a conversation that has just started.

   `margin-top: auto` on the list inside a column scroller, NOT
   `justify-content: flex-end` on the scroller: the latter looks identical
   until the thread is longer than the window, at which point it puts the
   overflow off the TOP, where it cannot be scrolled back to. */
/* The thread carries `.list` for the binder, not for the box. `.list` paints
   a bordered white panel, and the window it sits in is already white — so
   the thread drew a rectangle around itself on a ground of its own colour,
   which reads as a card that has failed to fill rather than as a
   conversation. The binder does not care; the border comes off. */
.agentmain .turns.list {
  background: transparent; border: 0; border-radius: 0; overflow: visible;
}
.agentmain > .th-scroll { display: flex; flex-direction: column; }
.turns { padding-block: 14px 6px; margin-top: auto; }
.turn { margin: 0 0 18px; }
.turn > .by { margin: 0 0 4px; font: 500 11.5px var(--f); color: var(--muted); }
.turn .msg { margin: 0 0 7px; }
.turn .msg.you > .q {
  margin: 0; padding: 8px 11px;
  background: var(--accent-soft); border-radius: var(--r);
  font: var(--font)/1.55 var(--f); color: var(--ink);
  overflow-wrap: anywhere;
}
/* THE FILE ASKED WITH IT (ADR-085). A photo is shown as the photo, at a size
   that reads on a phone without taking the screen; any other file is its name
   and size. Nothing is drawn for a question with no file. */
.turn .msg.you > .qabout:empty { display: none; }
.turn .msg.you > .qabout { margin: 0 0 4px; font: 12px var(--f); color: var(--muted); }
.turn .msg.you > .qfile:empty { display: none; }
.turn .msg.you > .qfile { margin: 0 0 5px; }
.turn .msg.you > .qfile img {
  display: block; max-width: min(240px, 100%); max-height: 240px;
  border-radius: var(--r); border: 1px solid var(--line); background: var(--win);
}
.turn .msg.you > .qfile .fchip {
  display: inline-flex; align-items: baseline; gap: 8px; max-width: 100%;
  padding: 5px 10px; border: 1px solid var(--line); border-radius: var(--r);
  font: 12.5px var(--f); color: var(--ink);
}
.turn .msg.you > .qfile .fchip .fname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.turn .msg.you > .qfile .fchip .sz { color: var(--muted); flex: none; }
.turn .msg.agent > .a { margin: 0; font: var(--font)/1.6 var(--f); color: var(--ink); overflow-wrap: anywhere; }
.turn .msg.agent > .a:empty { display: none; }
/* THE ANSWER AS WRITTEN. The model answers in markdown and `answerMd` builds
   it as elements, so these are the rules for reading it: paragraphs with air
   between them, lists that indent, tables that scroll inside the column
   rather than widening the page, and code in the mono face. Every value is an
   existing token — nothing here is a new colour or a new size. */
:is(.turn, .mplan) .md > * { margin: 0; }
:is(.turn, .mplan) .md > * + * { margin-top: 10px; }
:is(.turn, .mplan) .md h3, :is(.turn, .mplan) .md h4, :is(.turn, .mplan) .md h5, :is(.turn, .mplan) .md h6 { font: 600 var(--font)/1.4 var(--f); color: var(--ink); }
:is(.turn, .mplan) .md ul, :is(.turn, .mplan) .md ol { padding-left: 22px; }
:is(.turn, .mplan) .md li + li { margin-top: 4px; }
:is(.turn, .mplan) .md strong { font-weight: 600; }
:is(.turn, .mplan) .md code { font: 0.92em var(--m); background: var(--line-2); border-radius: 4px; padding: 1px 4px; overflow-wrap: anywhere; }
:is(.turn, .mplan) .md pre { background: var(--line-2); border-radius: var(--r); padding: 10px 12px; overflow-x: auto; }
:is(.turn, .mplan) .md pre code { background: none; padding: 0; white-space: pre; overflow-wrap: normal; }
:is(.turn, .mplan) .md a { color: var(--ink); text-decoration: underline; text-underline-offset: 2px; }
:is(.turn, .mplan) .md .mdtable { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--r); }
:is(.turn, .mplan) .md table { border-collapse: collapse; width: 100%; font: var(--font-sm)/1.45 var(--f); }
:is(.turn, .mplan) .md th, :is(.turn, .mplan) .md td { text-align: left; padding: 6px 10px; border-bottom: 1px solid var(--line); vertical-align: top; overflow-wrap: normal; }
:is(.turn, .mplan) .md th { font-weight: 600; color: var(--ink-2); white-space: nowrap; }
:is(.turn, .mplan) .md tr:last-child > td { border-bottom: 0; }
/* A BUSINESS NAMED IN AN ANSWER is a link to its record. Dotted rather than
   solid, so it reads as "this is a record" beside an ordinary web link. */
:is(.turn, .mplan) .md a.rec { text-decoration-style: dotted; text-decoration-thickness: 1px; }

/* A TABLE ON A PHONE IS A STACK OF SMALL BLOCKS (2026-09-22). Five columns in
   390px wrapped every cell onto two lines. Below 480px each row becomes its own
   bordered block: the first cell is its heading, and every other cell is a line
   with its column's name beside it, read off `data-label` (set by the renderer,
   in the header's own words). Nothing is hidden and nothing scrolls sideways. */
@media (max-width: 480px) {
  :is(.turn, .mplan) .md .mdtable { border: 0; overflow: visible; }
  :is(.turn, .mplan) .md .mdtable table, :is(.turn, .mplan) .md .mdtable tbody,
  :is(.turn, .mplan) .md .mdtable tr, :is(.turn, .mplan) .md .mdtable td { display: block; }
  :is(.turn, .mplan) .md .mdtable thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
  :is(.turn, .mplan) .md .mdtable tr { border: 1px solid var(--line); border-radius: var(--r); padding: 6px 10px; }
  :is(.turn, .mplan) .md .mdtable tr + tr { margin-top: 6px; }
  :is(.turn, .mplan) .md .mdtable td { border: 0; padding: 2px 0; display: grid; grid-template-columns: minmax(84px, 38%) 1fr; column-gap: 10px; }
  :is(.turn, .mplan) .md .mdtable td::before { content: attr(data-label); color: var(--muted); }
  :is(.turn, .mplan) .md .mdtable td:first-child { display: block; font-weight: 600; color: var(--ink); padding-bottom: 4px; }
  :is(.turn, .mplan) .md .mdtable td:first-child::before { content: none; }
}

/* A CHART, drawn from the answer's own table (app.js `chart`). Ink with one
   accent — the CRM's tokens, frozen, and no new colour — so the first series is
   the accent, the second ink, the third the quiet grey; a key names them
   whenever there is more than one, and lines past the first are dashed, so
   identity never rests on colour alone. Marks are thin and the grid recedes. */
:is(.turn, .mplan) .md .mdchart { margin: 0; border: 1px solid var(--line); border-radius: var(--r); padding: 10px 12px 6px; }
:is(.turn, .mplan) .md .mdchart svg { display: block; width: 100%; height: auto; overflow: visible; }
:is(.turn, .mplan) .md .mdchart .grid { stroke: var(--line-2); stroke-width: 1; }
:is(.turn, .mplan) .md .mdchart .base { stroke: var(--line); stroke-width: 1; }
:is(.turn, .mplan) .md .mdchart text { font: 11px var(--f); fill: var(--muted); }
:is(.turn, .mplan) .md .mdchart text.lab { fill: var(--ink-2); }
:is(.turn, .mplan) .md .mdchart text.val { fill: var(--ink-2); font-variant-numeric: tabular-nums; }
:is(.turn, .mplan) .md .mdchart .m.s0, :is(.turn, .mplan) .md .mdchart .dot.s0 { fill: var(--accent); }
:is(.turn, .mplan) .md .mdchart .m.s1, :is(.turn, .mplan) .md .mdchart .dot.s1 { fill: var(--ink); }
:is(.turn, .mplan) .md .mdchart .m.s2, :is(.turn, .mplan) .md .mdchart .dot.s2 { fill: var(--muted-2); }
:is(.turn, .mplan) .md .mdchart .m:hover, :is(.turn, .mplan) .md .mdchart .dot:hover { opacity: .8; }
:is(.turn, .mplan) .md .mdchart .dot { stroke: var(--card); stroke-width: 2; }
:is(.turn, .mplan) .md .mdchart .ln { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
:is(.turn, .mplan) .md .mdchart .ln.s0 { stroke: var(--accent); }
:is(.turn, .mplan) .md .mdchart .ln.s1 { stroke: var(--ink); stroke-dasharray: 6 4; }
:is(.turn, .mplan) .md .mdchart .ln.s2 { stroke: var(--muted-2); stroke-dasharray: 2 4; }
:is(.turn, .mplan) .md .mdchart .key { display: flex; flex-wrap: wrap; gap: 4px 14px; margin: 0 0 8px; font: var(--font-sm) var(--f); color: var(--ink-2); }
:is(.turn, .mplan) .md .mdchart .key .k { display: inline-flex; align-items: center; gap: 6px; }
:is(.turn, .mplan) .md .mdchart .key i { display: inline-block; width: 10px; height: 10px; border-radius: 2px; }
:is(.turn, .mplan) .md .mdchart .key .s0 i { background: var(--accent); }
:is(.turn, .mplan) .md .mdchart .key .s1 i { background: var(--ink); }
:is(.turn, .mplan) .md .mdchart .key .s2 i { background: var(--muted-2); }
:is(.turn, .mplan) .md .mdchart figcaption { margin-top: 6px; font: var(--font-sm) var(--f); color: var(--muted); }
.turn .msg.agent .full { margin: 6px 0 0; font: var(--font)/1.6 var(--f); color: var(--ink); overflow-wrap: anywhere; }
/* `--ink-2`, not `--muted`. U10 says a state is never carried by colour or
   pattern alone, so this line IS the carrier for anyone the orb does not
   reach, and `--muted` measures 5.05:1 on white — clear of the 4.5 a small
   face must make and short of the 7 it wants, on a 12px face, outdoors. */
.turn > .s { margin: 0; display: flex; align-items: center; gap: 6px; font: 11.5px var(--f); color: var(--ink-2); }
.turn > .s .sw:empty, .turn > .s .madelink:not([href]) { display: none; }
.turn > .err { margin: 4px 0 0; font: 12px var(--f); color: var(--ink); }
.turn > .err:empty { display: none; }
/* THE LIVE STEP. The time the last step took beside "Step N", and the command
   it ran on its own line in the mono face, clipped to one line so a long
   pipeline cannot push the thread around while it runs. Both are empty once
   the turn settles, and an empty line takes no room. */
.turn > .s .steptime { color: var(--muted); font-variant-numeric: tabular-nums; }
.turn > .s .steptime:empty { display: none; }
.turn > .doing { margin: 2px 0 0; min-width: 0; }
.turn > .doing code {
  display: block; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
  font: 11.5px/1.5 var(--m); color: var(--ink-2);
}
.turn > .doing:has(> code:empty) { display: none; }

/* THE CARD, AND WHAT EACH STATE LOOKS LIKE.

   The card states itself and does not narrate it (S9). OPEN is the only
   state with a footer, because the buttons ARE the open state. WRITTEN gets
   the solid ink border and solid rules between operations — it happened, and
   it reads as settled. NO dims the operations: they are still legible,
   because an operator has to be able to see what they declined, but they are
   plainly not in the book. */
.agentmain .card { margin: 7px 0 0; padding: 0; overflow: hidden; }
.agentmain .card .answering {
  margin: 0; padding: 8px 11px;
  border-bottom: 1px solid var(--line-2);
  font: 12.5px/1.5 var(--f); color: var(--ink-2);
  overflow-wrap: anywhere;
}
.agentmain .card .answering .lbl { font-weight: 600; color: var(--ink); }
/* A LABEL WITH NOTHING AFTER IT IS NOT A LABEL. `Answering` is authored in the
   markup and the question is bound, so a card whose proposal carries no
   `asked` rendered the word on its own — a line that names a question nobody
   asked. The whole line goes when the question is empty, rather than the label
   sitting there waiting for one. */
.agentmain .card .answering:has(> .qq:empty) { display: none; }
.agentmain .card .rows {
  margin: 0; padding: 9px 11px;
  font: 12.5px/1.7 var(--m); color: var(--ink);
  white-space: pre-wrap; overflow-wrap: anywhere;
}
/* THE CARD AS A CHANGE. The record's name, then one row per field in the
   body face: the field in words, what it holds now struck through, and what it
   will hold. `.rows.chg` overrides the mono pre-wrap above, which was the face
   when the card was a two-column line. */
.agentmain .card .rows.chg { font: var(--font)/1.5 var(--f); white-space: normal; }
.agentmain .card .chg .op + .op { margin-top: 9px; padding-top: 9px; border-top: 1px solid var(--line-2); }
.agentmain .card .chg .who { margin: 0 0 4px; font-weight: 600; color: var(--ink); }
.agentmain .card .chg dl {
  margin: 0; display: grid; grid-template-columns: minmax(84px, max-content) 1fr;
  column-gap: 12px; row-gap: 3px;
}
.agentmain .card .chg dt { color: var(--muted); }
.agentmain .card .chg dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
.agentmain .card .chg dd b { font-weight: 600; color: var(--ink); }
/* A tool's body on a card, as written (ADR-096): what the agent will follow
   is what the person reads before pressing, so the lines stay lines. */
.agentmain .card .chg dd .toolbody {
  margin: 0; padding: 8px 10px; border-radius: var(--r); background: var(--line-2);
  font: 400 12px/1.55 var(--m); color: var(--ink);
  white-space: pre-wrap; overflow-wrap: anywhere; max-height: 320px; overflow-y: auto;
}
.agentmain .card .chg dd .toolbody.was { color: var(--muted); text-decoration: line-through; margin-bottom: 6px; }
.agentmain .card .chg .was { color: var(--muted); }
.agentmain .card .chg .moreops { margin: 9px 0 0; color: var(--muted); font-size: var(--font-sm); }
/* A CARD WRITTEN IN PART (ADR-088). A checkbox leads each operation of an
   open card; one left out is drawn quieter, and on a written card an
   operation that was left out is struck, so the card says what was written. */
.agentmain .card .chg .op:has(> .pick) { display: grid; grid-template-columns: 22px 1fr; column-gap: 8px; }
.agentmain .card .chg .op:has(> .pick) > :not(.pick) { grid-column: 2; }
.agentmain .card .chg .op > .pick {
  grid-row: 1 / span 2; width: 16px; height: 16px; margin: 2px 0 0;
  accent-color: var(--ink); cursor: pointer;
}
.agentmain .card .chg .op.off .who, .agentmain .card .chg .op.off dd b { color: var(--muted); font-weight: 400; }
.agentmain .card .chg .op.skipped .who { color: var(--muted); font-weight: 400; text-decoration: line-through; }
.agentmain .card .chg .op.skipped dl { display: none; }
/* Past eight, the list scrolls inside the card rather than growing it. */
.agentmain .card .chg > .long { max-height: 420px; overflow-y: auto; overscroll-behavior: contain; }
.agentmain .card .chg .picks {
  position: sticky; top: 0; z-index: 1; display: flex; justify-content: flex-end; gap: 4px;
  margin: 0 0 6px; padding: 0 0 6px; background: var(--win); border-bottom: 1px solid var(--line-2);
}
.agentmain .card .chg .pickall {
  position: relative; width: 28px; height: 28px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 1px solid var(--line); border-radius: 5px; color: var(--ink); cursor: pointer;
}
.agentmain .card .chg .pickall::after {
  content: ""; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 44px; height: 44px;
}
.agentmain .card .chg .pickall svg { width: 14px; height: 14px; display: block; }
.agentmain .card > footer button[disabled] { opacity: .45; cursor: default; }
/* A DECIDED CARD OFFERS NOTHING BUT ITS UNDO (ADR-089). Write and No were
   still drawn on a written card, and pressing either took a refusal. */
.agentmain .card:not([data-st="open"]) > footer > [data-act] { display: none; }
.agentmain .card > footer > .undo { display: none; }
.agentmain .card[data-st="written"] > footer > .undo[data-u="1"] {
  display: inline-flex; align-items: center; justify-content: center; position: relative;
  width: 32px; height: 32px; padding: 0; margin-left: auto;
  background: none; border: 1px solid var(--line); border-radius: 5px; color: var(--ink); cursor: pointer;
}
.agentmain .card > footer > .undo::after {
  content: ""; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 44px; height: 44px;
}
.agentmain .card > footer > .undo:hover { border-color: var(--ink); }
.agentmain .card > footer > .undo svg { width: 15px; height: 15px; display: block; }
.agentmain .card:is([data-st="no"], [data-st="undone"], [data-st="pending"]) > footer,
.agentmain .card[data-st="written"] > footer:not(:has(> .undo[data-u="1"])) { display: none; }
.agentmain .card[data-st="undone"] .rows, .agentmain .card[data-st="undone"] .ops { color: var(--muted); }
.agentmain .card[data-st="undone"] .chg .who,
.agentmain .card[data-st="undone"] .chg dd b { color: var(--muted); font-weight: 400; text-decoration: line-through; }
.agentmain .card[data-st="no"] .chg .who,
.agentmain .card[data-st="no"] .chg dd b { color: var(--muted); font-weight: 400; }
.agentmain .card .gaps {
  margin: 0; padding: 7px 11px;
  border-top: 1px solid var(--line-2);
  font: 12px/1.5 var(--f); color: var(--muted);
}
.agentmain .card .gaps:empty { display: none; }
.agentmain .card > details.more { border-top: 1px solid var(--line-2); }
.agentmain .card > details.more > summary { padding: 7px 11px; font-size: 12px; }
.agentmain .card .ops {
  margin: 0; padding: 0 11px 9px;
  font: 11.5px/1.6 var(--m); color: var(--ink-2);
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.agentmain .card .bound {
  margin: 0; padding: 7px 11px;
  border-top: 1px solid var(--line-2);
  font: 12px/1.5 var(--f); color: var(--ink);
}
.agentmain .card .bound:empty { display: none; }
.agentmain .card > footer {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 9px 10px; border-top: 1px solid var(--line);
}
/* NO PROPOSAL, NO CARD. `cardState` returns the empty string for a turn that
   produced no operations, so the binder writes `data-st=""` — and without this
   rule the template's card renders anyway: an empty panel carrying Write it
   and No, on an answer that proposed nothing to write. The turn is otherwise
   correct, which is why it read as fine until the controls were counted. */
.agentmain .card:not([data-st]), .agentmain .card[data-st=""] { display: none; }

/* The three states are WRITTEN, NO and OPEN — the words `cardState` emits.
   These selectors said "committed" and "cancelled", the words on the PROPOSAL
   RECORD, and matched nothing: the card drew its open state in all three. The
   format's vocabulary is the card's vocabulary, and only one of them can be
   the authority here. */
.agentmain .card[data-st="written"] { border-color: var(--ink); }
/* WRITTEN ON THIS PHONE, NOT YET IN THE BOOK (ADR-091): the written card's
   weight, with the border broken until the host takes the batch. */
.agentmain .card[data-st="pending"] { border-color: var(--ink); border-style: dashed; }
.agentmain .card[data-st="pending"] .rows { border-bottom: 0; }
.agentmain .card[data-st="written"] .rows { border-bottom: 0; }
.agentmain .card[data-st="no"] .rows,
.agentmain .card[data-st="no"] .ops { color: var(--muted); }

/* FORK IT BACK. A branch is the log to a point and then a different answer;
   nothing in it counts until it is asked again where things count. The
   button is quiet because it is a way out, not the thing to do. */
/* BRANCH, AS A MARK. It repeats on every turn and is pressed on almost none,
   so as an underlined sentence it was the loudest thing in a quiet thread. As
   a 26px glyph it is still a 44px target (the ::after below), still named for
   a screen reader by `aria-label`, and no longer competing with the answer.
   `svg` sizing is explicit because an inline SVG with no width is 300px wide
   in every browser that has ever shipped. */
.turn > .fork, .turn > .tool {
  display: inline-flex; align-items: center; justify-content: center;
  position: relative;
  margin: 6px 0 0; padding: 0;
  width: 26px; height: 26px;
  background: none; border: 0; border-radius: 5px;
  color: var(--muted); cursor: pointer;
}
.turn > .fork svg, .turn > .tool svg { width: 15px; height: 15px; display: block; }
.turn > .fork::after, .turn > .tool::after {
  content: ""; position: absolute;
  left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 28px; height: 44px;
}
.turn > .fork:hover, .turn > .tool:hover { color: var(--ink); background: var(--win); }
/* ONE TIGHT GROUP, SHOWN ON HOVER (Sosa, 2026-09-23). With a pointer the row
   is a cluster 2px apart that appears when the turn is under it or holds
   focus, so a thread of settled turns reads as the conversation and not as a
   column of controls. The targets are 28 wide and 44 tall, so neighbours meet
   and never overlap. Opacity rather than display keeps the row's height, so
   the thread does not jump as the pointer crosses it. */
.turn > .tool + .tool, .turn > .tool + .fork { margin-left: 2px; }
.turn > .tool, .turn > .fork { opacity: 0; transition: opacity 120ms; }
.turn:hover > .tool, .turn:hover > .fork,
.turn:focus-within > .tool, .turn:focus-within > .fork { opacity: 1; }
/* A touch screen has no hover, so there the row stays drawn and keeps the
   spacing a thumb needs: 44px targets that do not overlap. */
@media (hover: none) {
  .turn > .tool, .turn > .fork { opacity: 1; }
  .turn > .fork::after, .turn > .tool::after { width: 44px; }
  .turn > .tool + .tool, .turn > .tool + .fork { margin-left: 18px; }
}
/* Nothing to copy until there is an answer; nothing to ask again while the
   agent is still on it. Editing is always open: it only fills the box. */
.turn:not([data-state="answered"]) > .copy { display: none; }
.turn[data-state="asked"] > .retry, .turn[data-state="answering"] > .retry { display: none; }
.turn:not([data-state="answered"]) > .copy + .retry { margin-left: 0; }
.turn[data-state="asked"] > .edit, .turn[data-state="answering"] > .edit { margin-left: 0; }
/* Read aloud (ADR-094): an answer to read, in a browser that can speak. */
.turn:not([data-state="answered"]) > .speak, html:not([data-speech-out]) .turn > .speak { display: none; }
.turn:not([data-state="answered"]) > .speak + .retry { margin-left: 0; }
.turn > .copy .done { display: none; }
.turn > .copy[data-copied] svg { display: none; }
.turn > .copy[data-copied] .done { display: block; color: var(--ink); }

/* THE WAY OUT OF A BRANCH, and it only exists inside one. The kind is read
   off the chat record by the `?chat=` controller and written to the root, so
   this follows the record rather than the link. Drawn as a real button rather
   than a quiet link because in a branch it is the thing to do: every other
   press in here ends at a rule that refuses it. */
.turn > .forkback { display: none; }
html[data-chat-kind="branch"] .turn > .forkback {
  display: inline-flex; align-items: center;
  margin: 6px 0 0 9px; padding: 0 9px; height: 26px;
  background: none; border: 1px solid var(--line); border-radius: 5px;
  font: 500 12px var(--f); color: var(--ink);
  cursor: pointer;
}
html[data-chat-kind="branch"] .turn > .forkback:hover { border-color: var(--ink); }

/* ══ THE CHATS COLUMN ═════════════════════════════════════════════════════
   This block is the canon mock (docs/mocks/agent-canon/canon.css) put into
   the product, rule for rule. It was not that on the way in: the headings
   were set in caps with letter-spacing, the row was a two-column grid with
   no mark and a COUNT where the canon has a relative time, and the current
   row was filled with the accent. Four divergences, none of them decided —
   each one a default that got written instead of the canon being read.

   The caps were the worst of them, and not because of taste. `app.css` has
   no other `text-transform` anywhere in its four thousand lines: a heading
   here is sentence case because a heading in this product is sentence case.
   The rule was real and nothing enforced it, so one line broke it silently.
   `test/glyphs.mjs` now fails the build if a second one appears. */
/* The canon's 272px, not the house 172px. Every other second column in this
   product lists SECTION NAMES — "Due today", "Unvisited", a street — which fit
   in 172 and look lost in 272. This one lists CONVERSATION TITLES, which are
   sentences a person wrote, and at 172 every one of them ended in an ellipsis:
   "Rent the wh…", "Route plann…". A list you cannot read the names in is not a
   list you can pick from. Scoped by what the column HOLDS rather than by a
   page class, so it travels with the column if the column ever moves. */
.second:has(.chats) { --second-w: 272px; }

.chats .grp { font-size: 11px; font-weight: 600; color: var(--muted);
  padding: 13px 6px 4px; margin: 0; }
.chats .grp:first-child { padding-top: 3px; }

/* The row is a FLEX line of three parts — mark, text, time — not a grid.
   A grid places the time in a column of its own width, so a row whose name
   is long and a row whose name is short put their times in different places
   and the column reads as ragged. Flex lets the name take what is left. */
.chats .ch {
  display: flex; align-items: center; gap: 10px;
  min-height: 42px; padding: 7px 8px; border-radius: 8px;
  color: var(--ink); text-decoration: none; cursor: pointer;
  border: 0; background: transparent; text-align: left; width: 100%;
  font: inherit;
}
.chats .ch:hover { background: var(--line-2); }
/* The current row is the WINDOW's own ground with a hairline around it, so
   the row and the chat it opens are visibly one surface. Filling it with the
   accent made it a selected item in a list instead. */
.chats .ch[aria-current="page"] { background: var(--win); box-shadow: 0 0 0 1px var(--line); }
.chats .ch .chm { flex: 0 0 18px; width: 18px; height: 18px; color: var(--muted); }
.chats .ch[aria-current="page"] .chm { color: var(--ink); }
.chats .ch .cht { flex: 1; min-width: 0; }
.chats .ch .chn { display: block; font-size: 13.5px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chats .ch .chs { display: block; font-size: 11.5px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The right-hand figure is WHEN the chat was last spoken in, not how many
   turns it holds. A count answers a question nobody asked of a list they are
   scanning to find the one they were just in; the time answers that one. */
.chats .ch .chw { flex: 0 0 auto; font: 400 10.5px var(--m); color: var(--muted-2); }
/* `.list` is the product's bordered, white-grounded list. In this column it
   is only a grouping of rows under a heading, so its border, its ground and
   its radius come off — four of them stacked would draw a hairline between
   every group and a box around each one. */
.chats .list, .chats .list.flush {
  background: transparent; border: 0; border-radius: 0; overflow: visible;
  padding: 0; display: flex; flex-direction: column; gap: 1px;
}

/* ══ TOOLS (ADR-096) ══════════════════════════════════════════════════════
   The heading carries the one thing the group offers, a plus, at the right.
   The button is a 44px hit area painted as nothing until hovered, pulled back
   into the heading's own height by negative margins so the heading is the
   same height as every other one in the column.

   A tool row is the chat row's button (name and first line, opening the
   editor) with the switch beside it, outside it: one press target per act.
   The switch is two buttons and one shows — "switch off" while it is on,
   "switch on" while it is off — so the mark drawn is the tool's state and
   the press does the one thing that state allows. An off tool's name and
   mark go quiet, which the switch's shape already says; the colour is only a
   second carrier. */
.chats .grpadd { display: flex; align-items: center; justify-content: space-between; }
.chats .grpnew {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; margin: -16px -6px -14px 0; padding: 0;
  border: 0; border-radius: 8px; background: transparent; color: var(--muted); cursor: pointer;
}
.chats .grpnew:hover { color: var(--ink); background: var(--line-2); }
.chats .tl { display: flex; align-items: center; gap: 2px; }
.chats .tl > .ch { flex: 1; min-width: 0; }
.chats .tlsw {
  flex: 0 0 44px; height: 44px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 8px; background: transparent; color: var(--ink-2); cursor: pointer;
}
.chats .tlsw:hover { background: var(--line-2); color: var(--ink); }
.chats .tl:not([data-on="off"]) > .tlon,
.chats .tl[data-on="off"] > .tloff { display: none; }
.chats .tl[data-on="off"] .chn,
.chats .tl[data-on="off"] .chm { color: var(--muted-2); }

/* The editor's body is a markdown file, so it is set in the mono face at the
   width it will be read at, and it grows with what is in it rather than
   hiding half of a tool behind a scrollbar in a twelve-line box.
   `field-sizing: content` sizes BOTH axes, so without a width a command line
   that does not break widened the box past its sheet and the sheet's Save
   past the window (measured on walk-list, 2026-09-23). The width is the
   sheet's; only the height follows the content. */
#tool-edit textarea {
  font: 400 12.5px/1.55 var(--m); min-height: 220px; resize: vertical;
  field-sizing: content; max-height: 60vh;
  width: 100%; box-sizing: border-box; overflow-wrap: anywhere;
}
#tool-edit .tefields { display: flex; flex-direction: column; gap: 6px; padding: 12px 15px 15px; }
#tool-edit .tefields label { margin-top: 6px; }
#tool-edit input[readonly] { color: var(--muted); background: var(--line-2); }

/* ══ THE COMPOSER — ONE BOX (S6) ══════════════════════════════════════════
   The BOX draws the border. The field inside it draws none, because a field
   with its own border inside a bordered box is two boxes, and a person
   reading two boxes has to work out which one they are typing into.

   The line you type on is on TOP and the controls sit in its FOOT. The input
   stays where it was; the controls moved below it (ADR-052), because they are
   the things that want a press and a press wants a constant place. */
.composer {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin-block: 0 13px; padding: 7px 8px;
  background: var(--win); border: 1px solid var(--line); border-radius: var(--r);
}
.composer:focus-within { border-color: var(--ink); }
/* `.composer .field`, NOT `.composer > .field`: the input is wrapped in a
   `<fieldset display:contents>` that the binder needs to scope the create, so
   it is a flex ITEM of the composer but not a child ELEMENT of it. A child
   combinator here matched nothing and the field kept the generic `.field`
   border, which is exactly the two-box defect this block exists to prevent. */
.composer .field {
  flex: 1 0 100%; width: 100%; order: 0;
  margin: 0 0 2px; padding: 4px 3px;
  background: none; border: 0; border-radius: 0; outline: 0;
  font: var(--font)/1.5 var(--f); color: var(--ink);
}
.composer .field::placeholder { color: var(--muted); }
.composer > .spacer { order: 3; flex: 1; }
/* ATTACH (ADR-085). An icon at the start of the foot, and the chosen file
   as its own line under the words, where it reads as part of the question. */
.composer > .attach { order: 1; width: 32px; padding: 0; }
.composer > .attach svg { width: 15px; height: 15px; display: block; }
.composer > .attach[data-on] { color: var(--ink); background: var(--ground); }
/* TAKE A PHOTO (ADR-092), beside attach and drawn the same. */
.composer > .camera { order: 1; width: 32px; padding: 0; }
.composer > .camera svg { width: 15px; height: 15px; display: block; }
.composer > .camera[data-on] { color: var(--ink); background: var(--ground); }
.composer > .attached[hidden] { display: none; }
.composer > .attached {
  order: 0; flex: 1 0 100%; display: flex; align-items: center; gap: 8px;
  min-width: 0; margin: 0 0 4px; padding: 0 3px;
  font: 12.5px var(--f); color: var(--ink);
}
.composer > .attached img { width: 36px; height: 36px; object-fit: cover; border-radius: 5px; border: 1px solid var(--line); flex: none; }
.composer > .attached img[hidden] { display: none; }
.composer > .attached .fname { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.composer > .attached .drop {
  position: relative; flex: none; width: 24px; height: 24px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 0; border-radius: 5px; color: var(--muted); cursor: pointer;
}
.composer > .attached .drop::after {
  content: ""; position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%); width: 44px; height: 44px;
}
.composer > .attached .drop:hover { color: var(--ink); }
.composer > .attached .drop svg { width: 14px; height: 14px; display: block; }
/* WHAT IT IS ASKED ABOUT (ADR-086): the same line as a file, with a pin. */
.composer > .attached .mk { width: 15px; height: 15px; flex: none; color: var(--muted); }
.composer > .model { order: 1; }
.composer > .modelmenu { order: 2; }
.composer > .connect { order: 3; width: auto; padding-inline: 10px; }
.composer > .connect[hidden] { display: none; }
.composer > .send { order: 4; }
.composer > .capture-status { order: 5; flex: 1 0 100%; margin: 4px 0 0;
  font: 11.5px var(--f); color: var(--muted); }
.composer > .capture-status:empty { display: none; }

/* A THUMB IS 44px EVERYWHERE IT HAS TO BE (S7, ADR-053).

   The TARGET is 44 and the PAINT is what the control is worth. A ＋ drawn at
   44px reads as a button the size of a fingertip; a ＋ drawn at 30px with a
   44px `::after` over it reads right and catches the thumb anyway. The
   pseudo-element is absolute and centred, so it costs no layout — the row
   keeps its height and the targets overlap nothing, because the gap between
   controls is smaller than the overhang only where there is nothing to hit. */
.composer > button {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 0; height: 32px; padding: 0 9px;
  background: none; border: 1px solid var(--line); border-radius: 5px;
  font: 500 12.5px var(--f); color: var(--ink);
  cursor: pointer;
}
.composer > button::after {
  content: ""; position: absolute;
  left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 44px; height: 44px;
}
.composer > .send {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; padding: 0; line-height: 1;
  background: var(--ink); border-color: var(--ink); color: var(--inv-ink);
}
/* ONE BUTTON, TWO GLYPHS, and exactly one of them drawn. Both ship in the
   markup so the swap is a class away rather than a re-render — a control that
   has to be rebuilt to change meaning is one that can be rebuilt at the wrong
   moment, and this one changes meaning while the page is busy. */
.composer > .send svg { width: 15px; height: 15px; display: block; }
.composer > .send .i-stop { display: none; }
.composer > .send[data-mode="stop"] .i-send { display: none; }
.composer > .send[data-mode="stop"] .i-stop { display: block; }
.composer > button:hover { border-color: var(--ink); }
.composer > .send:hover { opacity: .9; }
/* THE FOOT'S TOOLS ARE MARKS, NOT BUTTONS (Sosa, 2026-09-23). Attach,
   routine, camera, voice and the model are drawn bare, in the muted ink, and
   darken under the pointer. Send is the one boxed control in the foot, so it
   is the one that reads as the thing to press. A tool that is ON keeps a
   quiet fill in place of the border it used to darken. */
.composer > :is(.attach, .routine, .camera, .mic, .model) {
  border-color: transparent; color: var(--muted);
}
.composer > :is(.attach, .routine, .camera, .mic, .model):hover {
  border-color: transparent; color: var(--ink); background: var(--ground);
}
.composer > .model { padding-inline: 6px; }

/* WHICH MODEL ANSWERS THIS ONE. The menu is a flex item rather than a floating
   popover: the composer wraps, so it takes the line under the controls and
   pushes nothing off screen at 390px — which a positioned panel anchored to a
   32px button does, on the side a right-hand control has no room on.
   `[hidden]` is honoured explicitly because the rule below sets `display` by
   class, and a class rule beats the UA's `[hidden]` every time. */
.composer > .modelmenu[hidden] { display: none; }
.composer > .modelmenu {
  display: flex; flex-wrap: wrap; gap: 4px;
  flex: 1 0 100%; margin: 4px 0 0;
}
.composer > .modelmenu button {
  position: relative;
  height: 28px; padding: 0 10px;
  background: none; border: 1px solid var(--line); border-radius: 5px;
  font: 500 12px var(--f); color: var(--ink); cursor: pointer;
}
.composer > .modelmenu button::after {
  content: ""; position: absolute;
  left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 44px; height: 44px;
}
.composer > .modelmenu button:hover { border-color: var(--ink); background: var(--win); }

/* MAKE THIS A ROUTINE (ADR-090). The clock sits beside the attach icon; its
   line takes a row of its own under the words, like the model menu, so it
   opens where there is room at 390px. */
/* HOLD TO SPEAK (ADR-094). `.composer > button` sets a display, which would
   beat the `hidden` attribute, so hidden is said again here. Held, the
   button inverts for as long as it listens; pressed (send when let go) it
   carries a second rule of ink inside the first. No text is selected and no
   menu opens under a long press, and the page does not scroll off it. */
.composer > .mic[hidden] { display: none; }
.composer > .mic {
  order: 1; width: 32px; padding: 0;
  touch-action: none; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
}
.composer > .mic svg { width: 15px; height: 15px; display: block; }
.composer > .mic[aria-pressed="true"] { color: var(--ink); background: var(--ground); box-shadow: inset 0 0 0 1px var(--ink); }
.composer > .mic[data-listening] { background: var(--ink); border-color: var(--ink); color: var(--inv-ink); }
.composer > .routine { order: 1; width: 32px; padding: 0; }
.composer > .routine svg { width: 15px; height: 15px; display: block; }
.composer > .routine[data-on] { color: var(--ink); background: var(--ground); }
.composer > .routineset[hidden] { display: none; }
.composer > .routineset {
  order: 2; flex: 1 0 100%; display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin: 4px 0 0;
}
.composer > .routineset select,
.composer > .routineset input {
  height: 32px; padding: 0 8px; min-width: 0;
  background: var(--win); border: 1px solid var(--line); border-radius: 5px;
  font: 13px var(--f); color: var(--ink);
}
.composer > .routineset select[hidden] { display: none; }
.composer > .routineset button {
  position: relative; width: 32px; height: 32px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 1px solid var(--line); border-radius: 5px;
  color: var(--ink); cursor: pointer;
}
.composer > .routineset button::after {
  content: ""; position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%); width: 44px; height: 44px;
}
.composer > .routineset button svg { width: 15px; height: 15px; display: block; }
.composer > .routineset .ok { background: var(--ink); border-color: var(--ink); color: var(--inv-ink); }
.composer > .routineset .ok:disabled { opacity: .35; cursor: default; }
.composer > .routineset .drop { border-color: transparent; color: var(--muted); }
.composer > .routineset .drop:hover { color: var(--ink); }

/* THE @ TYPEAHEAD (ADR-087). A flex line under the field, for the reason the
   model menu is one: a floating panel anchored to a field at the foot of a
   phone has nowhere to open. */
.composer > .mentions[hidden] { display: none; }
.composer > .mentions {
  order: 0; flex: 1 0 100%; margin: 2px 0 0; padding: 0; list-style: none;
  display: flex; flex-direction: column;
}
.composer > .mentions li {
  display: flex; align-items: center; gap: 8px; min-height: 44px; padding: 0 8px;
  border-radius: 5px; cursor: pointer;
  font: 13px var(--f); color: var(--ink);
}
.composer > .mentions li .fname { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.composer > .mentions li .k { flex: none; margin-left: auto; font-size: 11.5px; color: var(--muted); }
.composer > .mentions li[aria-selected="true"] { background: var(--ground); }

/* ══ THE PHONE (S4) ═══════════════════════════════════════════════════════
   The header carries the mark and the title, and nothing else. The kind, the
   collapse and the column are desk furniture; a phone has one conversation on
   screen and does not need to be told which surface it is. */
@media (max-width: 899px) {
  .agentmain > .thhd .thk { display: none; }
  .turns { padding: 12px 11px 6px; }
  .composer { margin: 0 11px 11px; }
}

/* NOTHING IS COMING, and saying so. Drawn like a warning rather than a status
   because it is the one line here that means a person should stop waiting and
   go do something — restart the agent, or ask somewhere else. `[hidden]` is
   respected explicitly: this file sets `display` on plenty of elements by
   class, and a `display` rule beats the UA's `[hidden]` rule every time, which
   is how a "hidden" element ends up on screen. */
.agentdown[hidden] { display: none; }
.agentdown {
  display: block; margin: 0 0 6px;
  font: 500 12px/1.45 var(--f); color: var(--ink);
}
.agentauth {
  margin: 0 max(13px, (100% - var(--thread-w)) / 2) 8px;
  color: var(--muted);
  font-size: 12px;
}
.agentauth:empty { display: none; }
.authsheet .authlink { display: inline-flex; width: fit-content; text-decoration: none; }
.authsheet .capture-status.bad { color: var(--danger, #a12b2b); }

/* The act readout, between the thread and the composer. Empty is absent
   rather than a reserved band: a line holding nothing still pushes the
   composer up, and the composer's place on the screen has to be constant. */
.agentmain > .thact { margin: 0 13px 7px; font: 12.5px var(--f); color: var(--ink); }
.agentmain > .thact:empty { display: none; }
.agentmain > .thact[data-state="failed"] { color: var(--ink); font-weight: 600; }

/* ── the turn's own controls and disclosures ─────────────────────────────
   These behaviours were carried by rules scoped to `.askrow` and `.saidrow`,
   which nothing renders since the five inbox sections were retired. They are
   re-stated against `.turn` rather than re-scoped, because the old selectors
   also carried a ROW layout — a grid of state, question, control — and a turn
   is not a row. Only the behaviour moves. */

/* STOP AND THE "ASK IT AGAIN" LINE BOTH LEFT THE TURN. Stop is one control in
   the composer now (there is only ever one question in flight, and the
   per-turn copy scrolled away from the person waiting on it); the line that
   pointed at the box below it said nothing the box does not. The rules stay
   here, as nothing, so a stray `.stop` or `.again` reintroduced into a turn
   renders as it would anywhere else rather than inheriting a layout that no
   longer exists. */

/* A LINK TO A LIST THAT WAS NOT MADE IS NOT A LINK. `saidMadeLink` writes no
   href when the answer produced nothing, and an anchor with no href reads as
   an offer the page cannot keep. */
.turn .madelink[href=""], .turn .madelink:not([href]) { display: none; }
.turn .madelink { color: var(--ink); text-decoration: underline; text-underline-offset: 2px; }

/* U10 SAYS A STATE IS NEVER CARRIED BY COLOUR OR PATTERN ALONE, so this line
   is the carrier for everyone the orb does not reach — and it was set in
   `--muted`, the weakest colour on the page, which measures 5.05:1 on white.
   That clears the 4.5 a small face must and misses the 7 it wants, on a 12px
   face, on a phone, outdoors. `--ink-2` is what it takes to hold the one line
   that carries meaning to AAA in both themes. */
.turn > .s .sw { color: var(--ink-2); font-weight: 500; }
.turn[data-state="failed"] > .s .sw { color: var(--ink); }

/* ── the agent surface holds its shape on a phone too ────────────────────
   `main > .scrollhost { flex: 1; min-height: 0; overflow-y: auto }` lives
   inside `@media (min-width: 900px)`, so BELOW 900px there is no scroll
   container at all and the whole document scrolls. Every other screen can
   live with that — they are lists, and a list scrolling the page is fine.

   A conversation cannot. Measured on a 390x844 viewport with a thirty-turn
   thread: the box to type into sat at 13,569px and the projection's "Open" at
   16,004px, both far below a fold at 786px. The composer is not furniture you
   scroll to; it is where the next question goes, and it has to be in the same
   place after forty turns as after one.

   So the agent surface declares its own column at every width. `100dvh` and
   not `100vh`: on iOS Safari the URL bar's collapse changes `vh` mid-scroll,
   which would move the composer for the same reason the page scroll did. */
@media (max-width: 899px) {
  main.agentmain {
    height: 100dvh; max-height: 100dvh;
    overflow: hidden;
    padding-bottom: var(--dock-h);   /* the dock is fixed over the bottom band */
    /* THE CHAT IS THE WINDOW ON A PHONE, so it carries the window's ground.
       At desk `main` is a white card inset from the page; at this width the
       rule that gives it that ground does not apply, and the thread was
       sitting on the page's grey. It did not show before only because the
       thread carried `.list`'s white panel, which is a list's box and not a
       background — when the box came off, so did the ground. */
    background: var(--win);
  }
  main.agentmain > * { flex: 0 0 auto; }
  main.agentmain > .scrollhost { flex: 1; min-height: 0; overflow-y: auto; }
}

/* ── WAYMARK: the map (ADR-077) ──────────────────────────────────────────
   Everything inside `section.wy` is built by /map.js, in one of two skins
   picked by the same 900px line the rest of the shell uses: the night desk
   (a ledger beside the map) and the thumb drive (the map under a sheet, with
   the Drive bar at the thumb). Every class below is `wy-` so none of it can
   restyle a list page — app.css is global, and a short class here would.
   Nothing in this block defines or redefines a token; it only reads them. */
.wy { position: relative; display: block; overflow: hidden; background: var(--ground); color: var(--ink);
      font-family: var(--f); font-size: 13px; -webkit-tap-highlight-color: transparent; }
.wy *, .wy *::before, .wy *::after { box-sizing: border-box; }
.wy [hidden] { display: none !important; }
.wy button, .wy input, .wy a { font: inherit; color: inherit; }
.wy button { cursor: pointer; -webkit-appearance: none; appearance: none; background: none; border: 0;
             margin: 0; padding: 0; min-height: 0; border-radius: var(--r); }
.wy button:hover { border-color: transparent; }
.wy input { width: auto; padding: 0; border-radius: var(--r); }
.wy label { display: block; margin: 0; font: inherit; color: inherit; }
.wy ol { list-style: none; margin: 0; padding: 0; }
.wy h1, .wy h2, .wy h3, .wy p { margin: 0; }
.wy :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.wy .wy-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.wy.wy-ro .wy-edit { display: none !important; }
/* One chevron, pointed by the state rather than drawn four times. */
.wy .ctl.wy-back { transform: rotate(180deg); }
.wy .ctl.wy-up { transform: rotate(-90deg); }
.wy .ctl.wy-down { transform: rotate(90deg); }
.wy-cv { position: absolute; inset: 0; display: block; width: 100%; height: 100%; cursor: grab; touch-action: none; }
.wy-cv.wy-pan { cursor: grabbing; }
.wy-cv.wy-hot { cursor: pointer; }
.wy-cv.wy-las { cursor: crosshair; }
.wy .wy-cv:focus-visible { outline-offset: -3px; }
.wy-ib { position: relative; min-width: 44px; height: 44px; display: inline-grid; place-items: center; flex: none;
         color: var(--ink-2); border-radius: var(--r); transition: background-color .12s, color .12s; }
.wy-ib:disabled, .wy-ib[aria-disabled="true"] { opacity: .35; cursor: default; pointer-events: none; }
@media (hover: hover) { .wy-ib:hover { background: var(--line-2); color: var(--ink); } }

/* The page around it. On a phone the section IS the window, fixed between the
   top of the screen and the dock; at the desk it fills the window below the
   banner and above the page's own status line. */
@media (max-width: 899px) {
  body.mappage { overflow: hidden; overscroll-behavior: none; }
  body.mappage main > .wy { position: fixed; left: 0; right: 0; top: 0;
    bottom: calc(var(--dock-h) + env(safe-area-inset-bottom)); z-index: 1; }
  body.mappage.past main > .past-banner { position: fixed; left: 8px; right: 8px; top: 8px; z-index: 20; }
}
@media (min-width: 900px) {
  body.mappage main > .wy { flex: 1 1 auto; min-height: 0; }
}

/* ── the night desk ── */
.wy-desk { position: absolute; inset: 0; display: grid; grid-template-columns: 412px minmax(0, 1fr); grid-template-rows: minmax(0, 1fr); }
.wy-desk .wy-ledger { position: relative; display: flex; flex-direction: column; min-height: 0; min-width: 0;
                      background: var(--win); border-right: 1px solid var(--line); z-index: 2; }
.wy-desk .wy-map { position: relative; min-width: 0; min-height: 0; overflow: hidden; touch-action: none; background: var(--ground); }
.wy-desk .wy-head { padding: 16px 18px 12px; display: grid; gap: 12px; border-bottom: 1px solid var(--line); }
.wy-desk .wy-top { display: grid; gap: 10px; min-width: 0; }
.wy-desk .wy-title { font: 700 22px/1.1 var(--d); letter-spacing: -.01em; color: var(--ink);
                     white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wy-desk .wy-stats { display: flex; align-items: baseline; gap: 14px; font: 400 12px/1.2 var(--m); color: var(--ink-2); white-space: nowrap; }
.wy-desk .wy-stats b { font: 600 20px/1 var(--m); color: var(--ink); margin-right: 4px; letter-spacing: -.02em; }
.wy-desk .wy-st { display: flex; align-items: baseline; }
/* The hours and minutes are two figures in one flex item, where the space
   between them collapses; this puts it back. */
.wy-desk .wy-stats b + b { margin-left: 6px; }
.wy-desk .wy-tools { display: flex; align-items: center; gap: 6px; }
.wy-desk .wy-grow { flex: 1; }
.wy-desk .wy-ib[aria-pressed="true"] { background: var(--accent-soft); color: var(--accent); }
.wy-desk .wy-opt { display: inline-flex; align-items: center; gap: 8px; height: 44px; padding: 0 12px 0 12px;
                   border: 1px solid var(--line); border-radius: var(--r); color: var(--ink-2); background: var(--win);
                   transition: background-color .12s, border-color .12s, color .12s; }
.wy-desk .wy-opt:hover { border-color: var(--muted-2); color: var(--ink); }
.wy-desk .wy-sv { font: 600 12px/1 var(--m); }
.wy-desk .wy-sv:empty { display: none; }
.wy-desk .wy-opt.wy-gain { background: var(--accent); border-color: var(--accent); color: var(--win); }
.wy-desk .wy-opt:disabled { opacity: .4; cursor: default; }
.wy-seg { display: inline-flex; height: 44px; border-radius: var(--r); background: var(--line-2); box-shadow: inset 0 0 0 1px var(--line); }
.wy-seg button { width: 44px; height: 44px; display: grid; place-items: center; color: var(--muted); }
.wy-seg button:hover { color: var(--ink); }
.wy-seg button[aria-pressed="true"] { color: var(--ink); background: var(--win); box-shadow: inset 0 0 0 1px var(--line), inset 0 0 0 4px var(--line-2), inset 0 0 0 5px var(--line); }

.wy-desk .wy-find { position: relative; padding: 10px 18px; border-bottom: 1px solid var(--line); }
.wy-desk .wy-find label { position: relative; }
.wy-desk .wy-mg { position: absolute; left: 13px; top: 14px; color: var(--muted); pointer-events: none; }
.wy-desk .wy-q { width: 100%; height: 44px; padding: 0 44px 0 38px; border: 1px solid var(--line); background: var(--line-2);
                 font: 400 16px/1 var(--f); color: var(--ink); -webkit-appearance: none; appearance: none; }
.wy-desk .wy-q::placeholder { color: var(--muted); }
.wy-desk .wy-q:focus { background: var(--win); border-color: var(--accent); outline: none; }
.wy-desk .wy-q::-webkit-search-cancel-button { display: none; }
.wy-desk .wy-kbd { position: absolute; right: 10px; top: 11px; font: 500 11px/1 var(--m); color: var(--muted);
                   border: 1px solid var(--line); border-radius: 4px; padding: 4px 6px; pointer-events: none; background: var(--win); }
.wy-desk .wy-q:focus + .wy-kbd { display: none; }
.wy-desk .wy-res { position: absolute; left: 18px; right: 18px; top: calc(100% - 4px); z-index: 20; background: var(--win);
                   border: 1px solid var(--line); border-radius: var(--r); box-shadow: 0 10px 30px color-mix(in srgb, var(--ink) 18%, transparent);
                   max-height: min(420px, 60vh); overflow: auto; padding: 4px; }
.wy-desk .wy-ri { display: flex; align-items: stretch; border-radius: calc(var(--r) - 2px); }
.wy-desk .wy-ri.wy-on { background: var(--line-2); }
.wy-desk .wy-go2 { flex: 1; min-width: 0; text-align: left; padding: 7px 10px; min-height: 44px; display: grid; align-content: center; gap: 2px; }
.wy-desk .wy-ri .wy-ib.wy-in { color: var(--accent); }
.wy-desk .wy-none { padding: 14px 12px; font: 400 12px/1.3 var(--m); color: var(--muted); }

.wy-desk .wy-list { flex: 1; min-height: 0; overflow: auto; overscroll-behavior: contain; padding: 6px 0 10px; position: relative; }
.wy-desk .wy-row { position: relative; display: grid; grid-template-columns: 44px minmax(0, 1fr) auto 44px; align-items: center;
                   min-height: 58px; padding: 0 6px 0 10px; background: var(--win); transition: background-color .1s; }
.wy-desk .wy-row::after { content: ""; position: absolute; left: 62px; right: 18px; bottom: 0; height: 1px; background: var(--line-2); }
.wy-desk .wy-row:hover, .wy-desk .wy-row.wy-lit { background: var(--line-2); }
.wy-desk .wy-row.wy-cur { box-shadow: inset 3px 0 0 var(--accent); }
.wy-desk .wy-row:focus-visible { outline-offset: -2px; }
.wy-desk .wy-row.wy-drag { z-index: 5; background: var(--win); border-radius: var(--r);
                           box-shadow: 0 8px 24px color-mix(in srgb, var(--ink) 22%, transparent), 0 0 0 1px var(--line); }
.wy-desk .wy-row.wy-shift { transition: transform .16s cubic-bezier(.2, .7, .2, 1); }
.wy-desk .wy-grip { width: 44px; height: 44px; display: grid; place-items: center; cursor: grab; touch-action: none; }
.wy-desk .wy-grip span { width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center; background: var(--accent);
                         color: var(--win); font: 600 11.5px/1 var(--m); letter-spacing: -.02em; box-shadow: 0 0 0 2px var(--win); }
.wy-desk .wy-row.wy-t2 .wy-grip span { background: var(--win); color: var(--accent); box-shadow: inset 0 0 0 2px var(--accent); }
.wy-desk .wy-row.wy-np .wy-grip { cursor: default; }
.wy-desk .wy-row.wy-np .wy-grip span { width: 12px; height: 12px; background: none; box-shadow: inset 0 0 0 2px var(--muted-2); }
.wy-ro .wy-desk .wy-grip { cursor: default; }
.wy-desk .wy-who { min-width: 0; padding: 8px; display: grid; gap: 3px; }
.wy-desk .wy-nm { font: 600 14px/1.2 var(--f); color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wy-desk .wy-mt { font: 400 11.5px/1.3 var(--m); color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wy-desk .wy-mt i { font-style: normal; color: var(--ink-2); font-family: var(--f); font-size: 12px; }
.wy-desk .wy-leg { font: 400 11.5px/1 var(--m); color: var(--muted); padding-right: 4px; white-space: nowrap; }
.wy-desk .wy-x { opacity: 0; color: var(--muted); }
.wy-desk .wy-row:hover .wy-x, .wy-desk .wy-row.wy-cur .wy-x, .wy-desk .wy-x:focus-visible { opacity: 1; }
@media (hover: none) { .wy-desk .wy-x { opacity: 1; } }
.wy-desk .wy-brk { display: flex; align-items: center; gap: 10px; padding: 14px 18px 6px 20px; font: 500 11px/1 var(--m); color: var(--accent); letter-spacing: .02em; }
.wy-desk .wy-brk::after { content: ""; flex: 1; height: 1px; background: repeating-linear-gradient(90deg, var(--accent) 0 4px, transparent 4px 8px); opacity: .5; }
.wy-desk .wy-brk.wy-brk-np { color: var(--muted); }
.wy-desk .wy-brk.wy-brk-np::after { background: var(--line); opacity: 1; }
.wy-desk .wy-empty { padding: 22px 20px; font: 400 12px/1.3 var(--m); color: var(--muted); }
.wy-desk .wy-list.wy-tl .wy-row[data-i]:not(.wy-intrip) { opacity: .4; }
.wy-desk .wy-list.wy-tl .wy-row.wy-intrip { background: var(--line-2); }

/* The hand-off: where a planned day leaves the desk for a phone. */
.wy-desk .wy-hand { position: relative; border-top: 1px solid color-mix(in srgb, var(--accent) 35%, var(--line)); padding: 16px 18px;
                    display: grid; grid-template-columns: 148px minmax(0, 1fr); gap: 14px;
                    background: color-mix(in srgb, var(--accent-soft) 55%, var(--win)); }
.wy-desk .wy-hand::before { content: ""; position: absolute; left: 0; right: 0; top: -1px; height: 3px; background: var(--accent); }
.wy-desk .wy-qcol { display: grid; gap: 6px; align-content: start; }
.wy-desk .wy-qr { width: 148px; height: 148px; border-radius: var(--r); display: grid; place-items: center; overflow: hidden; position: relative;
                  box-shadow: 0 0 0 2px var(--accent), 0 8px 22px color-mix(in srgb, var(--accent) 22%, transparent); }
.wy-desk .wy-qr canvas { width: 136px; height: 136px; display: block; image-rendering: pixelated; }
.wy-desk .wy-qr.wy-nil canvas { visibility: hidden; }
.wy-desk .wy-step { display: grid; grid-template-columns: 44px minmax(0, 1fr) 44px; align-items: center; height: 44px; }
.wy-desk .wy-step output { text-align: center; font: 600 12px/1 var(--m); color: var(--ink-2); white-space: nowrap; }
.wy-desk .wy-step output span { color: var(--muted); font-weight: 400; }
.wy-desk .wy-tcol { display: grid; gap: 8px; align-content: start; min-width: 0; }
.wy-desk .wy-trips { display: grid; gap: 6px; align-content: start; min-width: 0; max-height: 196px; overflow: auto; }
.wy-desk .wy-trip { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; border: 1px solid var(--line);
                    border-radius: var(--r); background: var(--win); transition: border-color .12s, background-color .12s, opacity .12s; }
.wy-desk .wy-trip.wy-on { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.wy-desk .wy-trip.wy-lit, .wy-desk .wy-trip.wy-near { background: var(--line-2); }
.wy-desk .wy-hand.wy-tl .wy-trip:not(.wy-lit) { opacity: .5; }
.wy-desk .wy-pick { display: grid; grid-template-columns: auto auto; justify-content: start; align-content: center; column-gap: 8px; row-gap: 5px;
                    min-width: 0; min-height: 52px; padding: 6px 4px 6px 10px; text-align: left; white-space: nowrap; }
.wy-desk .wy-tn { grid-column: 1 / -1; display: inline-flex; align-items: center; gap: 5px; font: 600 13px/1 var(--f); }
.wy-desk .wy-tn .ctl { color: var(--accent); --ctl-size: 14px; }
.wy-desk .wy-trip.wy-done:not(.wy-on) .wy-tn { color: var(--muted); }
.wy-desk .wy-tr, .wy-desk .wy-td { font: 400 11.5px/1 var(--m); color: var(--muted); overflow: hidden; text-overflow: ellipsis; }
.wy-desk .wy-acts { display: flex; align-items: center; }
.wy-desk .wy-gm { display: inline-grid; place-items: center; width: 44px; height: 44px; border-radius: calc(var(--r) - 2px); color: var(--accent); --ctl-size: 18px; }
.wy-desk .wy-gm:hover { background: var(--accent-soft); }
.wy-desk .wy-dayrow { display: flex; align-items: center; gap: 6px; }
.wy-desk .wy-dayb { flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; height: 44px; padding: 0 12px 0 12px;
                    border: 1px solid var(--line); border-radius: var(--r); color: var(--ink-2); font: 500 12px/1 var(--m); white-space: nowrap; }
.wy-desk .wy-dayb:hover { border-color: var(--muted-2); color: var(--ink); }
.wy-desk .wy-pt { min-width: 44px; height: 44px; border: 1px solid var(--line); border-radius: var(--r); font: 600 12px/1 var(--m); color: var(--ink-2); }
.wy-desk .wy-pt:hover { border-color: var(--muted-2); color: var(--ink); }
.wy-desk .wy-url { display: flex; min-width: 0; }
.wy-desk .wy-url input { flex: 1; min-width: 0; height: 44px; cursor: copy; padding: 0 10px; border: 1px solid var(--line); background: var(--line-2);
                         font: 400 11px/1 var(--m); color: var(--ink-2); -webkit-appearance: none; appearance: none; }

.wy-desk .wy-mtools { position: absolute; right: 12px; top: 12px; display: flex; flex-direction: column; gap: 8px; z-index: 3; }
.wy-desk .wy-stack { display: flex; flex-direction: column; background: var(--win); border: 1px solid var(--line); border-radius: var(--r); overflow: hidden;
                     box-shadow: 0 1px 2px color-mix(in srgb, var(--ink) 10%, transparent), 0 6px 18px color-mix(in srgb, var(--ink) 10%, transparent); }
.wy-desk .wy-stack .wy-ib { width: 44px; border-radius: 0; }
.wy-desk .wy-stack .wy-ib + .wy-ib { border-top: 1px solid var(--line); }
.wy-desk .wy-stack .wy-ib[aria-pressed="true"] { background: var(--accent); color: var(--win); }
.wy-desk .wy-attrib { position: absolute; left: 10px; bottom: 8px; font: 400 10px/1 var(--m); color: var(--muted); pointer-events: none; z-index: 2; }
.wy-desk .wy-scale { position: absolute; right: 14px; bottom: 10px; display: grid; justify-items: end; gap: 3px; font: 400 10px/1 var(--m);
                     color: var(--muted); pointer-events: none; z-index: 2; }
.wy-desk .wy-scale i { display: block; height: 5px; border: 1px solid var(--muted-2); border-top: 0; }

.wy-desk .wy-card { position: absolute; left: 0; top: 0; z-index: 6; width: 300px; background: var(--win); color: var(--ink);
                    border: 1px solid var(--line); border-radius: calc(var(--r) + 3px); padding: 14px 14px 10px 16px; display: grid; gap: 8px;
                    box-shadow: 0 2px 4px color-mix(in srgb, var(--ink) 8%, transparent), 0 18px 44px color-mix(in srgb, var(--ink) 20%, transparent); }
.wy-desk .wy-ct { display: flex; align-items: center; gap: 8px; min-height: 24px; }
.wy-desk .wy-ty { font: 500 11.5px/1 var(--m); color: var(--muted); }
.wy-desk .wy-no { font: 600 11px/1 var(--m); padding: 4px 6px; border-radius: 4px; background: var(--accent); color: var(--win); }
.wy-desk .wy-cl { margin: -12px -8px -12px auto; }
.wy-desk .wy-card h3 { font: 700 18px/1.15 var(--f); letter-spacing: -.01em; padding-right: 6px; }
.wy-desk .wy-ad { font: 400 12px/1.45 var(--m); color: var(--ink-2); user-select: text; -webkit-user-select: text; }
.wy-desk .wy-ph { font: 500 13px/1.3 var(--m); color: var(--ink); user-select: text; -webkit-user-select: text; }
.wy-desk .wy-ca { display: flex; align-items: center; gap: 4px; margin: 2px -6px 0 -8px; border-top: 1px solid var(--line-2); padding-top: 6px; }
.wy-desk .wy-tg { display: inline-grid; place-items: center; width: 44px; height: 44px; border-radius: 50%; background: var(--accent); color: var(--win);
                  margin-left: auto; transition: background-color .12s; }
.wy-desk .wy-tg.wy-in { background: var(--line-2); color: var(--ink); box-shadow: inset 0 0 0 1px var(--line); }

.wy-desk .wy-toast { position: absolute; left: 50%; max-width: calc(100% - 24px); bottom: 22px; transform: translate(-50%, 8px); z-index: 8;
                     display: flex; align-items: center; padding: 0 2px 0 14px; min-height: 48px; border-radius: var(--r); background: var(--inv);
                     color: var(--inv-ink); font: 500 12.5px/1 var(--m); opacity: 0; pointer-events: none; white-space: nowrap;
                     box-shadow: 0 10px 30px color-mix(in srgb, var(--ink) 30%, transparent); transition: opacity .16s, transform .16s; }
.wy-desk .wy-toast > span { overflow: hidden; text-overflow: ellipsis; margin-right: 6px; }
.wy-desk .wy-toast.wy-show { opacity: 1; transform: translate(-50%, 0); pointer-events: auto; }
.wy-desk .wy-tb { display: inline-flex; align-items: center; gap: 6px; height: 44px; min-width: 44px; padding: 0 12px; color: var(--inv-ink);
                  font: 600 12.5px/1 var(--m); border-radius: calc(var(--r) - 2px); }
.wy-desk .wy-tb:hover { background: color-mix(in srgb, var(--inv-ink) 12%, transparent); }
.wy-desk .wy-tb:focus-visible { outline-color: var(--inv-ink); outline-offset: -3px; }
.wy-desk .wy-keys { position: absolute; right: 68px; top: 12px; z-index: 7; width: 272px; background: var(--win); border: 1px solid var(--line);
                    border-radius: calc(var(--r) + 3px); box-shadow: 0 18px 44px color-mix(in srgb, var(--ink) 20%, transparent); padding: 10px 14px; }
.wy-desk .wy-keys dl { display: grid; grid-template-columns: auto 1fr; gap: 7px 12px; margin: 0; align-items: center; }
.wy-desk .wy-keys dt { display: flex; gap: 3px; }
.wy-desk .wy-keys kbd { font: 500 11px/1 var(--m); border: 1px solid var(--line); border-bottom-width: 2px; border-radius: 4px; padding: 3px 5px;
                        color: var(--ink); background: var(--line-2); min-width: 20px; text-align: center; }
.wy-desk .wy-keys dd { margin: 0; font: 400 12.5px/1.2 var(--f); color: var(--ink-2); }
/* A narrow desk window keeps the ledger usable rather than squeezing the map to nothing. */
@media (min-width: 900px) and (max-width: 1180px) { .wy-desk { grid-template-columns: 360px minmax(0, 1fr); } .wy-desk .wy-hand { grid-template-columns: 1fr; } .wy-desk .wy-qcol { grid-template-columns: 148px 1fr; align-items: start; } }

/* ── the thumb drive ── */
.wy-thumb { position: absolute; inset: 0; }
/* --wy-vis is written on the section by map.js as the sheet moves; a default
   declared here, on a child, would shadow it and pin the controls in place. */
.wy-thumb .wy-map { position: absolute; inset: 0; overflow: hidden; touch-action: none; background: var(--ground); }
.wy-thumb .wy-attrib { position: absolute; left: 8px; top: 8px; font: 10.5px/1.2 var(--m); color: var(--muted); pointer-events: none;
                       background: color-mix(in srgb, var(--ground) 82%, transparent); padding: 3px 6px; border-radius: 4px; }
.wy-thumb .wy-ctlstack { position: absolute; right: 10px; bottom: calc(var(--wy-vis, 124px) + 10px); display: flex; flex-direction: column; gap: 8px; z-index: 2;
                         transition: bottom var(--sheet-ms) var(--sheet-ease), opacity .15s; }
.wy-thumb .wy-ctlstack.wy-gone { opacity: 0; pointer-events: none; }
.wy-thumb .wy-fab { width: 48px; height: 48px; display: grid; place-items: center; background: var(--win); color: var(--ink); border: 1px solid var(--line);
                    border-radius: var(--r); --ctl-size: 18px;
                    box-shadow: 0 1px 2px color-mix(in srgb, var(--ink) 14%, transparent), 0 4px 14px color-mix(in srgb, var(--ink) 8%, transparent); }
.wy-thumb .wy-zm { display: flex; flex-direction: column; background: var(--win); border: 1px solid var(--line); border-radius: var(--r);
                   box-shadow: 0 1px 2px color-mix(in srgb, var(--ink) 14%, transparent), 0 4px 14px color-mix(in srgb, var(--ink) 8%, transparent); }
.wy-thumb .wy-zm .wy-fab { border: 0; box-shadow: none; background: none; }
.wy-thumb .wy-zm .wy-fab + .wy-fab { border-top: 1px solid var(--line); border-radius: 0 0 var(--r) var(--r); }

.wy-thumb .wy-card { position: absolute; left: 8px; right: 8px; bottom: calc(var(--wy-vis, 124px) + 8px); max-width: 460px; z-index: 2; background: var(--win);
                     color: var(--ink); border: 1px solid var(--line); border-radius: calc(var(--r) * 1.6); padding: 12px 6px 8px 14px;
                     box-shadow: 0 2px 4px color-mix(in srgb, var(--ink) 10%, transparent), 0 12px 32px color-mix(in srgb, var(--ink) 14%, transparent);
                     transition: bottom var(--sheet-ms) var(--sheet-ease); }
.wy-thumb .wy-ctop { display: flex; align-items: flex-start; gap: 10px; }
.wy-thumb .wy-badge { flex: none; margin-top: 6px; width: 32px; height: 32px; border-radius: 50%; display: grid; place-items: center;
                      font: 600 14px/1 var(--m); background: var(--accent); color: var(--win); }
.wy-thumb .wy-badge.wy-off { background: none; box-shadow: inset 0 0 0 2px var(--line); }
.wy-thumb .wy-badge.wy-off::after { content: ""; width: 10px; height: 10px; border-radius: 50%; background: color-mix(in srgb, var(--ink) 50%, var(--ground)); }
.wy-thumb .wy-cid { flex: 1; min-width: 0; padding-top: 6px; }
.wy-thumb .wy-cname { font: 700 19px/1.2 var(--d); letter-spacing: -.01em; color: var(--ink); overflow-wrap: anywhere; }
.wy-thumb .wy-ctype { margin-top: 3px; font-size: 13.5px; color: var(--ink-2); }
.wy-thumb .wy-facts { margin: 8px 0 2px; display: grid; gap: 0; }
.wy-thumb .wy-fact { display: flex; align-items: center; gap: 6px; min-height: 44px; }
.wy-thumb .wy-v { flex: 1; min-width: 0; font: 14px/1.35 var(--m); color: var(--ink); -webkit-user-select: text; user-select: text; overflow-wrap: anywhere; }
.wy-thumb .wy-cact { display: flex; align-items: center; gap: 6px; margin: 8px 0 0; padding-right: 2px; }
.wy-thumb .wy-btn { flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 48px; padding: 0 16px;
                    font: 600 16px/1 var(--m); white-space: nowrap; --ctl-size: 18px; }
.wy-thumb .wy-btn.wy-pri { background: var(--accent); color: var(--win); }
.wy-thumb .wy-btn.wy-sec { background: var(--line-2); color: var(--ink); box-shadow: inset 0 0 0 1px var(--line); }

.wy-thumb .wy-sheet { position: absolute; left: 0; right: 0; bottom: 0; height: var(--wy-full, 80%); display: flex; flex-direction: column; z-index: 3;
                      background: var(--win); border-radius: calc(var(--r) * 2) calc(var(--r) * 2) 0 0; will-change: transform;
                      box-shadow: 0 -1px 0 var(--line), 0 -10px 30px color-mix(in srgb, var(--ink) 12%, transparent); }
.wy-thumb .wy-sheet.wy-anim { transition: transform var(--sheet-ms) var(--sheet-ease); }
.wy-thumb .wy-grip { position: absolute; top: 0; left: 50%; transform: translateX(-50%); width: 132px; height: 44px; display: flex; justify-content: center;
                     padding-top: 9px; z-index: 2; touch-action: none; border-radius: 0; }
.wy-thumb .wy-grip i { display: block; width: 40px; height: 5px; border-radius: 3px; background: color-mix(in srgb, var(--ink) 22%, var(--win)); }
.wy-thumb .wy-head { position: relative; display: flex; align-items: center; gap: 10px; min-height: 74px; padding: 22px 12px 8px 16px; touch-action: none; flex: none; }
.wy-thumb .wy-stats { display: flex; gap: 16px; min-width: 0; }
.wy-thumb .wy-st { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.wy-thumb .wy-st b { font: 600 25px/1 var(--m); letter-spacing: -.02em; color: var(--ink); white-space: nowrap; }
.wy-thumb .wy-st span { font-size: 12.5px; line-height: 1; color: var(--muted); white-space: nowrap; }
.wy-thumb .wy-chip { margin-left: auto; display: inline-flex; align-items: center; gap: 7px; height: 44px; padding: 0 13px 0 12px; border-radius: 22px;
                     background: var(--accent-soft); color: var(--accent); white-space: nowrap; }
.wy-thumb .wy-sv { font: 600 14px/1 var(--m); }
.wy-thumb .wy-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; padding-bottom: 16px; border-top: 1px solid var(--line); }
.wy-thumb .wy-body > .foot { margin: 18px 16px 0; }
.wy-thumb .wy-sh { display: flex; align-items: center; gap: 4px; padding: 10px 8px 4px 16px; min-height: 52px; }
.wy-thumb .wy-sh h2 { margin: 0 auto 0 0; font: 600 13px/1.2 var(--f); color: var(--muted); letter-spacing: .01em;
                      min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wy-thumb .wy-ib.wy-hotb { background: var(--accent-soft); color: var(--accent); }
.wy-thumb .wy-pts { display: inline-flex; border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; margin-left: 6px; }
.wy-thumb .wy-pts button { min-width: 48px; height: 44px; padding: 0 12px; font: 600 14px/1 var(--m); color: var(--ink-2); border-radius: 0; }
.wy-thumb .wy-pts button + button { border-left: 1px solid var(--line); }
.wy-thumb .wy-pts button[aria-pressed="true"] { background: var(--ink); color: var(--win); }

.wy-thumb .wy-trips { margin: 0 0 4px; }
.wy-thumb .wy-trip { display: flex; align-items: center; gap: 2px; min-height: 60px; padding: 0 8px 0 12px; border-top: 1px solid var(--line-2); }
.wy-thumb .wy-trip:first-child { border-top: 0; }
.wy-thumb .wy-tgo { flex: 1; min-width: 0; min-height: 52px; display: flex; align-items: center; gap: 12px; padding: 0 4px; text-align: left; }
.wy-thumb .wy-tmk { flex: none; width: 32px; height: 32px; border-radius: 50%; display: grid; place-items: center; box-shadow: inset 0 0 0 2px var(--line); color: var(--muted-2); }
.wy-thumb .wy-tgo[aria-pressed="true"] .wy-tmk { background: var(--accent); box-shadow: none; color: var(--win); }
.wy-thumb .wy-trip.wy-done .wy-tgo[aria-pressed="false"] .wy-tmk { box-shadow: none; background: var(--accent-soft); color: var(--accent); }
.wy-thumb .wy-tid { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.wy-thumb .wy-trng { font: 600 16px/1 var(--m); color: var(--ink); }
.wy-thumb .wy-trip.wy-done .wy-trng { color: var(--muted); }
.wy-thumb .wy-tgo[aria-pressed="true"] .wy-trng { color: var(--accent); }
.wy-thumb .wy-tfrom { font-size: 12.5px; line-height: 1.2; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wy-thumb .wy-share { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 14px; align-items: center; padding: 6px 16px 16px; }
.wy-thumb .wy-qr { width: 148px; height: 148px; padding: 8px; border-radius: var(--r); border: 1px solid var(--line); display: grid; place-items: center; }
.wy-thumb .wy-qr canvas { width: 130px; height: 130px; image-rendering: pixelated; }
.wy-thumb .wy-tn { display: flex; flex-direction: column; gap: 6px; min-width: 0; font-size: 14px; line-height: 1.25; color: var(--ink); }
.wy-thumb .wy-tn span { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wy-thumb .wy-tn b { font: 600 13px/1 var(--m); color: var(--accent); margin-right: 6px; }
.wy-thumb .wy-ar { color: var(--muted-2); font-family: var(--m); font-size: 13px; }
.wy-thumb .wy-url { grid-column: 1 / -1; display: block; width: 100%; height: 44px; padding: 0 10px; border: 1px solid var(--line); background: var(--line-2);
                    font: 13px var(--m); color: var(--ink-2); }

.wy-thumb .wy-list { position: relative; }
.wy-thumb .wy-row { position: relative; display: flex; align-items: center; min-height: 62px; padding: 0 4px 0 12px; background: var(--win); }
.wy-thumb .wy-row + .wy-row::before { content: ""; position: absolute; left: 56px; right: 0; top: 0; border-top: 1px solid var(--line-2); }
.wy-thumb .wy-row.wy-brk::after { content: ""; position: absolute; left: 0; right: 0; top: -1px; border-top: 2px dashed color-mix(in srgb, var(--accent) 55%, var(--win)); }
.wy-thumb .wy-row.wy-sel { background: var(--accent-soft); }
.wy-thumb .wy-row.wy-lift { z-index: 2; box-shadow: 0 8px 24px color-mix(in srgb, var(--ink) 18%, transparent); border-radius: var(--r); }
.wy-thumb .wy-row.wy-shift { transition: transform .16s var(--sheet-ease); }
.wy-thumb .wy-row.wy-nph { min-height: 36px; padding: 10px 16px 4px; font: 500 11.5px/1 var(--m); color: var(--muted); }
.wy-thumb .wy-row.wy-nph::before { display: none; }
.wy-thumb .wy-num { flex: none; width: 32px; height: 32px; margin-right: 12px; border-radius: 50%; display: grid; place-items: center;
                    background: var(--accent); color: var(--win); font: 600 13.5px/1 var(--m); }
.wy-thumb .wy-rt { flex: 1; min-width: 0; min-height: 52px; display: flex; flex-direction: column; justify-content: center; gap: 4px; text-align: left; }
.wy-thumb .wy-nm { font-size: 16px; font-weight: 600; line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.wy-thumb .wy-sub { font: 12.5px/1.2 var(--m); color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.wy-thumb .wy-sub b { font-weight: 600; color: var(--ink-2); }
.wy-thumb .wy-hd { touch-action: none; cursor: grab; color: var(--muted-2); }
.wy-thumb .wy-rm { color: var(--muted-2); }
.wy-thumb .wy-find { display: flex; align-items: center; gap: 8px; margin: 4px 16px 6px; padding: 0 0 0 12px; height: 48px; border: 1px solid var(--line);
                     border-radius: var(--r); background: var(--line-2); color: var(--muted); }
.wy-thumb .wy-find:focus-within { border-color: var(--accent); background: var(--win); }
.wy-thumb .wy-find input { flex: 1; min-width: 0; height: 44px; min-height: 44px; border: 0; background: none; outline: none; font-size: 16px; color: var(--ink); }
.wy-thumb .wy-find input::placeholder { color: var(--muted); }
.wy-thumb .wy-find input::-webkit-search-cancel-button { display: none; }
.wy-thumb .wy-dot { flex: none; width: 32px; height: 32px; margin-right: 12px; display: grid; place-items: center; }
.wy-thumb .wy-dot i { display: block; width: 11px; height: 11px; border-radius: 50%; background: color-mix(in srgb, var(--ink) 50%, var(--ground)); box-shadow: 0 0 0 2px var(--win); }
.wy-thumb .wy-cost { flex: none; font: 600 12.5px/1 var(--m); color: var(--ink-2); padding: 0 2px 0 8px; white-space: nowrap; }
.wy-thumb .wy-add { color: var(--accent); }
.wy-thumb .wy-more { display: flex; justify-content: center; padding: 6px 0 0; }

.wy-thumb .wy-drive { position: absolute; left: 0; right: 0; bottom: 0; z-index: 4; padding: 0 12px 12px; background: var(--win); }
.wy-thumb .wy-drive.wy-lined { box-shadow: 0 -1px 0 var(--line-2), 0 -8px 16px color-mix(in srgb, var(--win) 80%, transparent); }
.wy-thumb .wy-go { display: flex; align-items: center; gap: 13px; width: 100%; height: 60px; padding: 0 18px; border-radius: calc(var(--r) * 1.4);
                   background: var(--accent); color: var(--win); white-space: nowrap; touch-action: manipulation; --ctl-size: 22px;
                   box-shadow: 0 1px 0 color-mix(in srgb, var(--ink) 18%, transparent); }
.wy-thumb .wy-gl { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.wy-thumb .wy-gl b { font: 700 20px/1 var(--m); letter-spacing: -.02em; }
.wy-thumb .wy-gl small { display: flex; align-items: center; gap: 7px; font: 500 12.5px/1 var(--f); opacity: .88; }
.wy-thumb .wy-pips { display: inline-flex; gap: 3px; }
.wy-thumb .wy-pips i { display: block; width: 12px; height: 4px; border-radius: 2px; background: color-mix(in srgb, var(--win) 45%, var(--accent)); }
.wy-thumb .wy-pips i.wy-on { background: var(--win); }
.wy-thumb .wy-gmode { opacity: .88; --ctl-size: 18px; }
.wy-thumb .wy-go:active { transform: translateY(1px); }
.wy-thumb .wy-go[aria-disabled="true"] { background: var(--line-2); color: var(--muted); box-shadow: none; pointer-events: none; }
.wy-thumb .wy-go:focus-visible { outline: 3px solid var(--ink); outline-offset: 2px; }

.wy-thumb .wy-toast { position: absolute; left: 50%; top: 40px; transform: translateX(-50%); max-width: calc(100% - 24px); display: flex; align-items: center;
                      gap: 6px; padding: 4px 4px 4px 16px; min-height: 52px; background: var(--inv); color: var(--inv-ink); border-radius: calc(var(--r) * 1.4);
                      font-size: 14.5px; font-weight: 500; z-index: 6; transition: opacity .18s, transform .18s;
                      box-shadow: 0 8px 24px color-mix(in srgb, var(--ink) 22%, transparent); }
.wy-thumb .wy-toast.wy-out { opacity: 0; transform: translate(-50%, -8px); pointer-events: none; }
.wy-thumb.wy-sfull .wy-toast { top: auto; bottom: calc(var(--wy-dock, 84px) + 12px); }
.wy-thumb.wy-sfull .wy-toast.wy-out { transform: translate(-50%, 8px); }
.wy-thumb .wy-m { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-right: 10px; }
.wy-thumb .wy-toast button { height: 44px; min-width: 52px; display: grid; place-items: center; color: var(--inv-ink);
                             background: color-mix(in srgb, var(--inv-ink) 14%, transparent); }
.wy-thumb .wy-toast button:focus-visible { outline-color: var(--inv-ink); }

@media (prefers-reduced-motion: reduce) {
  .wy *, .wy *::before, .wy *::after { transition: none !important; animation: none !important; }
  .wy-thumb .wy-go:active { transform: none; }
}
.wy > .wy-failed { padding: 22px 20px; font: 400 12px/1.3 var(--m); color: var(--ink-2); }
/* The page's status line rides in the sheet on a phone (map.js, placeFoot), so
   the reset above reaches its position button. Put back what the button is,
   at a thumb's height: in there it is under a thumb, not a pointer. */
.wy .foot button.linkish { padding: 2px 4px; min-height: 44px; font-size: 11px; }

/* ── WAYMARK: the ways in (ADR-081) ───────────────────────────────────────
   One mark, no words: the selection bar's route button and the Lists header's
   map link. Painted at the height of the controls beside them; the tap area is
   44px either way, carried by an invisible ::after rather than by paint. */
.mapway { position: relative; display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; padding-left: 0; padding-right: 0; }
.mapway .ico { width: 18px; height: 18px; flex: 0 0 18px; }
.mapway::after { content: ""; position: absolute; left: 0; right: 0; top: 50%;
  height: 44px; transform: translateY(-50%); }
.mapway[hidden] { display: none; }
