/* The tasks page's mode-invariant layer: what is the same in both skins.

   The COLOURS moved out on 2026-09-28, and the reason is the whole point
   of the fleet theme. A custom property that references another resolves
   against the element it is DECLARED on, so a colour written here, on
   `:root`, and composed from a token the dark skin redefines, would
   resolve once at the root and be inherited unchanged into a nested
   `[data-theme]` section — the section would swap the brand's tokens and
   keep the board's. So every mode-variant value now lives in a block the
   server writes under each theme scope: `theme-light.css` (light),
   `theme-dark.css` (dark), and `theme-derived.css` for the washes and
   state aliases, which are the same formulas in both skins and are
   emitted into both (app.py, TASKS_SHEET).

   What stays here is what no skin changes: the one-off card that does not
   follow the skin, the mono face, and the table's geometry. One semantic
   layer still, two skins; "System" is still the absence of data-theme,
   which `theme.js` stamps nothing for and the media query answers.

   The board anatomy follows a teammate's "Expand in Place" direction: a mint
   Questions band over a Focus table with priority-colored edges. Its
   soft washes are color-mixed against --bg, so both skins derive them. */

:root {
  /* The pink is the brand's CTA at full strength in both skins — it is
     the accent, not a surface, so it does not re-tint per mode. What DOES
     differ is the wash made from it (`--hot-soft`), which is per skin. */
  --hot: var(--c-cta);
  /* The explanation card (.boardtip) is the one surface that does NOT
     follow the skin: it sits over the board like a label held above it,
     and a card that matched the page would disappear into it. */
  --tip-bg: var(--c-navy-800);
  --tip-ink: #F2F6FB;
  --tip-line: color-mix(in srgb, white 16%, transparent);
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* The row's columns come in two halves. `--cols` is the data — it is what
     the responsive breakpoints yield away. `--acts-w` is the row's own
     actions, which hold a REAL column: a row that shows its finish button
     only on hover is telling the reader it has more room than it has, and
     a phone has no hover to tell it with. (There used to be a preference
     for the old hover overlay; it is gone as of 2026-09-21 and the
     actions are simply pinned.) */
  /* cb · the standing columns. The track list is WRITTEN BY JS from
     store.gridTemplate(), which is the one list the head and the rows
     are also drawn from (store.js §the table's columns). This value is
     the fallback for a paint before the board has been measured — the
     widest set, so nothing flashes narrow. A 26px track used to close
     the list, holding the disclosure chevron; the chevron is the Expand
     button in the action cluster now (the operator, 2026-09-22), so the
     track is gone and `--acts-w` carries its width instead. */
  --cols: 26px 34px 84px minmax(min(240px, 100%), 1fr) 96px 96px 92px 76px 62px 96px 104px;
  /* View · Expand · ✓ · ⋯ — the cluster measures 149px on the live
     board, and the rest is the room a focus ring and a wider fallback
     face need. It was 128px when there were three of them; the Expand
     button costs 56, and 22 of those came back from the disclosure's
     own track. */
  --acts-w: 168px;
  --grid: var(--cols) var(--acts-w);
  /* The row's priority edge. The head draws no edge, so it pads by the
     same width instead — or every column in the head sits that much left
     of the column it names, and the head's Task track is that much wider
     than the rows' (measured 2026-09-15: head at 325px and 305px wide,
     the rows' Task cell at 328px and 302px). One number, so the head and
     the rows cannot disagree. */
  --row-edge: 3px;
  /* The priority pill's one width — the board's Pri track, the planning
     sheet's pill and its picker, the questions band, the standup. */
  --pri-chip-w: 34px;
  --row-h: 40px;
}
:root,
[data-theme="light"] {
  color-scheme: light;
  --bg: var(--bg-body);
  --panel: var(--bg-alt);
  --raise: var(--bg-tint);
  --line: var(--border-divider);
  --text: var(--c-charcoal);
  --dim: var(--c-gray-dark);
  --faint: var(--c-gray-light);
  --hot-soft: color-mix(in srgb, var(--c-cta) 9%, transparent);
  /* Links carry the brand hue but must stay READABLE, and the CTA pink
     is a fill color, not an ink one: at full strength on the light skin
     it measures 3.3:1, under AA. Darkened toward charcoal it clears
     4.5:1 and still reads as the same pink. The dark skin needs no such
     help — there the pink is the bright end.

     The brand has a role for the same idea now (`--ink-cta-page`, the CTA
     as ink on the page) at a different value, #B8256E. This keeps its own
     mix: the board's link colour is measured and the brand's role is not
     a drop-in for it, and swapping a live ink is not what this story is. */
  --link: color-mix(in srgb, var(--c-cta) 62%, var(--c-charcoal));
  --purple: var(--c-purple, #91459A);
  --cool: var(--c-mint-800);
  --cool-soft: var(--c-mint-150);
  --warm: var(--c-amber-700);
  --warm-soft: var(--c-amber-100);
  --ok: var(--c-green-600);
  --bad: var(--c-danger);
  /* The brand's blue is its navy, and until now the palette had no ink
     made of it — "in progress" borrowed the purple, which nobody could
     tell from the p2 priority chip beside it. navy-400 measures 7:1 on
     the light ground; the dark skin lifts it (theme-dark.css), because
     there the ground IS navy. */
  --blue: var(--c-navy-400);
  --blue-ink: var(--c-navy-500);
  --blue-soft: color-mix(in srgb, var(--c-navy-400) 10%, var(--bg));
  --shadow: 0 1px 3px rgb(0 0 0 / 0.08);
  /* The lift under anything that stands OVER the page rather than in it:
     the settings panel, the pickers' lists, a property's popover. It is
     a second token and not `--shadow` because the two jobs are different
     sizes — `--shadow` is the hairline under a card that is part of the
     page, and this is the drop that says "this is in front of what you
     were reading". On paper the drop is what does the separating, because
     there is nothing lighter than white to raise a surface to. */
  --pop-shadow: 0 8px 24px rgb(0 0 0 / 0.12), 0 2px 6px rgb(0 0 0 / 0.08);
  /* The Questions band, mixed against the page. Both skins compose it the
     same way at different strengths: on paper the mint carries the band on
     its own, and on the navy ground a fourteenth of it is already a
     visible wash (theme-dark.css). */
  --band: color-mix(in srgb, var(--c-mint) 14%, var(--bg));
  --band-hover: color-mix(in srgb, var(--c-mint) 26%, var(--bg));
  --band-line: color-mix(in srgb, var(--c-mint) 45%, var(--line));
  /* derived washes — both skins compose these from --bg */
  --hair: color-mix(in srgb, var(--line) 55%, var(--bg));
  --bad-soft: color-mix(in srgb, var(--bad) 10%, var(--bg));
  --bad-line: color-mix(in srgb, var(--bad) 32%, var(--bg));
  --bad-ink: color-mix(in srgb, var(--bad) 78%, var(--text));
  --purple-soft: color-mix(in srgb, var(--purple) 9%, var(--bg));
  --purple-line: color-mix(in srgb, var(--purple) 30%, var(--bg));
  --ok-soft: color-mix(in srgb, var(--ok) 10%, var(--bg));
  --ok-ink: color-mix(in srgb, var(--ok) 76%, var(--text));
  --warm-line: color-mix(in srgb, var(--c-amber) 55%, var(--bg));

  /* -- narrowing, in colour ------------------------------------------
     An active filter is NOT the accent. The pink says "this is the thing
     to press"; a funnel that is cutting the list, and the chips that say
     which cuts are standing, are saying something else — "what you are
     looking at is less than what is here". Painting both in one colour
     made a head where the sorted column and the filtered column were the
     same pink, and a reader had to work out which pink meant which (the
     operator, 2026-09-23).

     So narrowing speaks in the teal this palette already owns, and it is
     aliased rather than given a value of its own: `--cool` is defined per
     skin (dark teal on the light ground, bright mint on the navy one), so
     both skins answer without a second table to drift. */
  --filter: var(--cool);
  --filter-soft: color-mix(in srgb, var(--cool) 12%, var(--bg));
  --filter-line: color-mix(in srgb, var(--cool) 50%, var(--line));

  /* -- where the work stands, in colour -----------------------------
     One property per state, named by `domain/status.js` (STATUS_TOKENS)
     and read by `.st.<class>` in `surfaces/board/row.css`. They are
     aliases of the semantic tokens above rather than values of their
     own, so both skins answer them without a second table: the dark
     skin redefines `--ok`, `--bad`, `--warm` and `--blue`, and these
     follow.

     The one that was wrong: "Confirm done" — the brain believes this finished
     and nobody has said so — was drawn in the same green as Done, so a
     column of green dots was half questions (a user test, 2026-09-21).
     It is amber: something waiting on a person. Blocked took the red,
     being the state that needs someone. */
  /* -- why a queued task is held, in colour --------------------------
     Three properties, read by `.held.is-*` in `domain/held.css` and named
     by `domain/held.js`. Aliases of the semantic tokens above rather than
     values of their own, so both skins answer without a second table: the
     dark skin redefines `--dim`, `--blue` and `--c-amber`, and these
     follow.

     The three are the three KINDS of wait, and they are deliberately the
     colours this palette already uses for those meanings. Grey is a wait
     on the clock, which needs nobody: the same `--dim` every quiet aside
     on the board is written in. Blue is a wait on other work, the hue "in
     progress" wears. Amber is a wait on a PERSON, which is the one a
     reader has to act on, and it is the amber "Confirm done" wears for
     exactly that reason (see the state aliases below). A fourth colour was
     considered at intake and refused: it would not change what anybody
     does about the row. */
  --held-time: var(--dim);
  --held-work: var(--blue);
  --held-person: var(--c-amber);

  --st-neutral: var(--faint);
  --st-blue: var(--blue);
  --st-blue-ink: var(--blue-ink);
  --st-red: var(--bad);
  --st-red-ink: var(--bad-ink);
  --st-amber: var(--c-amber);
  --st-amber-ink: var(--warm);
  --st-green: var(--ok);
  --st-green-ink: var(--ok-ink);
  --st-grey: var(--line);
}
[data-theme="dark"] {
  color-scheme: dark;
  /* The surfaces, off the brand's dark surface roles — and the pairing is
     deliberate. This board's deepest ground is the BOARD, with the bars and
     the rail a step up out of it; the brand's dark set is the other way
     round, `--bg-body` a step above `--bg-alt`. Mapped by name the two
     would swap places, which is a change to how the dark board reads and
     not a change any acceptance criterion asks for. So the page takes the
     deeper of the two roles and the chrome the lighter: every value is the
     brand's, and which surface is lighter is still the board's own answer
     (to the pixel, these are the values this file already carried).
     Flagged for review at Approve Staged — read by name instead if the
     brand's hierarchy is what is wanted here. */
  --bg: var(--bg-alt);
  --panel: var(--bg-body);
  /* The ground a popover stands on, a clear step above the page. The step
     is the brand's now; tests/test_task_page.py measures it in L*. */
  --raise: var(--bg-tint);
  /* Every hairline on the board: the brand's divider, which is a good deal
     more visible on this ground than the `--c-navy-600` it replaces. */
  --line: var(--border-divider);
  --text: var(--text-body);
  --dim: var(--text-muted);
  /* The quietest ink — the numbers Debug adds, a placeholder, a kbd. It
     stays local: the brand's dark set has three ink roles (heading, body,
     muted) and this board reads four weights, and the one role whose NAME
     suggests this job, `--bg-placeholder`, is a dark surface colour in the
     dark set rather than an ink. Aliasing it here would have made the
     faintest text on the board all but invisible. */
  --faint: var(--c-navy-300);
  --hot-soft: color-mix(in srgb, var(--c-cta) 16%, transparent);
  --link: var(--c-cta); /* on navy the pink IS the bright, readable end */
  --purple: var(--c-purple-light, #b678bd);
  --cool: var(--c-mint);
  --cool-soft: color-mix(in srgb, var(--c-mint) 13%, transparent);
  --warm: var(--c-amber);
  --warm-soft: color-mix(in srgb, var(--c-amber) 13%, transparent);
  --ok: var(--c-green-400);
  --bad: color-mix(in srgb, var(--c-danger) 80%, white);
  /* THE BLUE HAS TO BE A BLUE.
     This is the colour "In progress" is drawn in — the dot in every
     status cell on the board, the word beside it, and the same pair in
     the menu the cell opens and in a task page's Status line. (The state
     tokens in theme-derived.css alias these, so both skins answer "what
     colour is in progress" without a second table.)

     It was `navy-150`, #C7CBD9 — the ramp's light tint, which is what
     you write TEXT in on a navy ground. Sixteen per cent saturation is a
     grey, and the neutral that "Not started" wears, #6C779C, is a grey
     too: both dots differed in lightness and in nothing else, so a
     column of statuses read as one colour at a glance and the state had
     to be read word by word (the operator, on the live board,
     2026-09-23: "the status cells all show the same-coloured dot").
     A state colour is a signal, not a text colour; it needs hue.

     So: a saturated blue from the same family as the brand's navy —
     #5B87E8, which is `--c-navy-500`'s hue carried up to the saturation
     a signal needs. 5.6:1 on the page as a dot (3:1 is the floor for a
     graphic), and `--blue-ink` lifts it towards the page's own ink for
     the word beside it, which is smaller and needs 4.5:1. */
  --blue: #5B87E8;
  --blue-ink: color-mix(in srgb, #5B87E8 62%, var(--c-navy-100));
  --blue-soft: color-mix(in srgb, #5B87E8 14%, transparent);
  --shadow: 0 1px 3px rgb(0 0 0 / 0.5);
  /* Deeper than the paper skin's, and it has to be: a black drop on a
     near-black ground is nothing at all. What separates a popover here is
     the surface under it (`--raise`, a clear step lighter than the page)
     with this under that. */
  --pop-shadow: 0 8px 24px rgb(0 0 0 / 0.45), 0 2px 8px rgb(0 0 0 / 0.35);
  --band: color-mix(in srgb, var(--c-mint) 7%, var(--bg));
  --band-hover: color-mix(in srgb, var(--c-mint) 12%, var(--bg));
  --band-line: color-mix(in srgb, var(--c-mint) 22%, var(--line));
  /* derived washes — both skins compose these from --bg */
  --hair: color-mix(in srgb, var(--line) 55%, var(--bg));
  --bad-soft: color-mix(in srgb, var(--bad) 10%, var(--bg));
  --bad-line: color-mix(in srgb, var(--bad) 32%, var(--bg));
  --bad-ink: color-mix(in srgb, var(--bad) 78%, var(--text));
  --purple-soft: color-mix(in srgb, var(--purple) 9%, var(--bg));
  --purple-line: color-mix(in srgb, var(--purple) 30%, var(--bg));
  --ok-soft: color-mix(in srgb, var(--ok) 10%, var(--bg));
  --ok-ink: color-mix(in srgb, var(--ok) 76%, var(--text));
  --warm-line: color-mix(in srgb, var(--c-amber) 55%, var(--bg));

  /* -- narrowing, in colour ------------------------------------------
     An active filter is NOT the accent. The pink says "this is the thing
     to press"; a funnel that is cutting the list, and the chips that say
     which cuts are standing, are saying something else — "what you are
     looking at is less than what is here". Painting both in one colour
     made a head where the sorted column and the filtered column were the
     same pink, and a reader had to work out which pink meant which (the
     operator, 2026-09-23).

     So narrowing speaks in the teal this palette already owns, and it is
     aliased rather than given a value of its own: `--cool` is defined per
     skin (dark teal on the light ground, bright mint on the navy one), so
     both skins answer without a second table to drift. */
  --filter: var(--cool);
  --filter-soft: color-mix(in srgb, var(--cool) 12%, var(--bg));
  --filter-line: color-mix(in srgb, var(--cool) 50%, var(--line));

  /* -- where the work stands, in colour -----------------------------
     One property per state, named by `domain/status.js` (STATUS_TOKENS)
     and read by `.st.<class>` in `surfaces/board/row.css`. They are
     aliases of the semantic tokens above rather than values of their
     own, so both skins answer them without a second table: the dark
     skin redefines `--ok`, `--bad`, `--warm` and `--blue`, and these
     follow.

     The one that was wrong: "Confirm done" — the brain believes this finished
     and nobody has said so — was drawn in the same green as Done, so a
     column of green dots was half questions (a user test, 2026-09-21).
     It is amber: something waiting on a person. Blocked took the red,
     being the state that needs someone. */
  /* -- why a queued task is held, in colour --------------------------
     Three properties, read by `.held.is-*` in `domain/held.css` and named
     by `domain/held.js`. Aliases of the semantic tokens above rather than
     values of their own, so both skins answer without a second table: the
     dark skin redefines `--dim`, `--blue` and `--c-amber`, and these
     follow.

     The three are the three KINDS of wait, and they are deliberately the
     colours this palette already uses for those meanings. Grey is a wait
     on the clock, which needs nobody: the same `--dim` every quiet aside
     on the board is written in. Blue is a wait on other work, the hue "in
     progress" wears. Amber is a wait on a PERSON, which is the one a
     reader has to act on, and it is the amber "Confirm done" wears for
     exactly that reason (see the state aliases below). A fourth colour was
     considered at intake and refused: it would not change what anybody
     does about the row. */
  --held-time: var(--dim);
  --held-work: var(--blue);
  --held-person: var(--c-amber);

  --st-neutral: var(--faint);
  --st-blue: var(--blue);
  --st-blue-ink: var(--blue-ink);
  --st-red: var(--bad);
  --st-red-ink: var(--bad-ink);
  --st-amber: var(--c-amber);
  --st-amber-ink: var(--warm);
  --st-green: var(--ok);
  --st-green-ink: var(--ok-ink);
  --st-grey: var(--line);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
  color-scheme: dark;
  /* The surfaces, off the brand's dark surface roles — and the pairing is
     deliberate. This board's deepest ground is the BOARD, with the bars and
     the rail a step up out of it; the brand's dark set is the other way
     round, `--bg-body` a step above `--bg-alt`. Mapped by name the two
     would swap places, which is a change to how the dark board reads and
     not a change any acceptance criterion asks for. So the page takes the
     deeper of the two roles and the chrome the lighter: every value is the
     brand's, and which surface is lighter is still the board's own answer
     (to the pixel, these are the values this file already carried).
     Flagged for review at Approve Staged — read by name instead if the
     brand's hierarchy is what is wanted here. */
  --bg: var(--bg-alt);
  --panel: var(--bg-body);
  /* The ground a popover stands on, a clear step above the page. The step
     is the brand's now; tests/test_task_page.py measures it in L*. */
  --raise: var(--bg-tint);
  /* Every hairline on the board: the brand's divider, which is a good deal
     more visible on this ground than the `--c-navy-600` it replaces. */
  --line: var(--border-divider);
  --text: var(--text-body);
  --dim: var(--text-muted);
  /* The quietest ink — the numbers Debug adds, a placeholder, a kbd. It
     stays local: the brand's dark set has three ink roles (heading, body,
     muted) and this board reads four weights, and the one role whose NAME
     suggests this job, `--bg-placeholder`, is a dark surface colour in the
     dark set rather than an ink. Aliasing it here would have made the
     faintest text on the board all but invisible. */
  --faint: var(--c-navy-300);
  --hot-soft: color-mix(in srgb, var(--c-cta) 16%, transparent);
  --link: var(--c-cta); /* on navy the pink IS the bright, readable end */
  --purple: var(--c-purple-light, #b678bd);
  --cool: var(--c-mint);
  --cool-soft: color-mix(in srgb, var(--c-mint) 13%, transparent);
  --warm: var(--c-amber);
  --warm-soft: color-mix(in srgb, var(--c-amber) 13%, transparent);
  --ok: var(--c-green-400);
  --bad: color-mix(in srgb, var(--c-danger) 80%, white);
  /* THE BLUE HAS TO BE A BLUE.
     This is the colour "In progress" is drawn in — the dot in every
     status cell on the board, the word beside it, and the same pair in
     the menu the cell opens and in a task page's Status line. (The state
     tokens in theme-derived.css alias these, so both skins answer "what
     colour is in progress" without a second table.)

     It was `navy-150`, #C7CBD9 — the ramp's light tint, which is what
     you write TEXT in on a navy ground. Sixteen per cent saturation is a
     grey, and the neutral that "Not started" wears, #6C779C, is a grey
     too: both dots differed in lightness and in nothing else, so a
     column of statuses read as one colour at a glance and the state had
     to be read word by word (the operator, on the live board,
     2026-09-23: "the status cells all show the same-coloured dot").
     A state colour is a signal, not a text colour; it needs hue.

     So: a saturated blue from the same family as the brand's navy —
     #5B87E8, which is `--c-navy-500`'s hue carried up to the saturation
     a signal needs. 5.6:1 on the page as a dot (3:1 is the floor for a
     graphic), and `--blue-ink` lifts it towards the page's own ink for
     the word beside it, which is smaller and needs 4.5:1. */
  --blue: #5B87E8;
  --blue-ink: color-mix(in srgb, #5B87E8 62%, var(--c-navy-100));
  --blue-soft: color-mix(in srgb, #5B87E8 14%, transparent);
  --shadow: 0 1px 3px rgb(0 0 0 / 0.5);
  /* Deeper than the paper skin's, and it has to be: a black drop on a
     near-black ground is nothing at all. What separates a popover here is
     the surface under it (`--raise`, a clear step lighter than the page)
     with this under that. */
  --pop-shadow: 0 8px 24px rgb(0 0 0 / 0.45), 0 2px 8px rgb(0 0 0 / 0.35);
  --band: color-mix(in srgb, var(--c-mint) 7%, var(--bg));
  --band-hover: color-mix(in srgb, var(--c-mint) 12%, var(--bg));
  --band-line: color-mix(in srgb, var(--c-mint) 22%, var(--line));
  /* derived washes — both skins compose these from --bg */
  --hair: color-mix(in srgb, var(--line) 55%, var(--bg));
  --bad-soft: color-mix(in srgb, var(--bad) 10%, var(--bg));
  --bad-line: color-mix(in srgb, var(--bad) 32%, var(--bg));
  --bad-ink: color-mix(in srgb, var(--bad) 78%, var(--text));
  --purple-soft: color-mix(in srgb, var(--purple) 9%, var(--bg));
  --purple-line: color-mix(in srgb, var(--purple) 30%, var(--bg));
  --ok-soft: color-mix(in srgb, var(--ok) 10%, var(--bg));
  --ok-ink: color-mix(in srgb, var(--ok) 76%, var(--text));
  --warm-line: color-mix(in srgb, var(--c-amber) 55%, var(--bg));

  /* -- narrowing, in colour ------------------------------------------
     An active filter is NOT the accent. The pink says "this is the thing
     to press"; a funnel that is cutting the list, and the chips that say
     which cuts are standing, are saying something else — "what you are
     looking at is less than what is here". Painting both in one colour
     made a head where the sorted column and the filtered column were the
     same pink, and a reader had to work out which pink meant which (the
     operator, 2026-09-23).

     So narrowing speaks in the teal this palette already owns, and it is
     aliased rather than given a value of its own: `--cool` is defined per
     skin (dark teal on the light ground, bright mint on the navy one), so
     both skins answer without a second table to drift. */
  --filter: var(--cool);
  --filter-soft: color-mix(in srgb, var(--cool) 12%, var(--bg));
  --filter-line: color-mix(in srgb, var(--cool) 50%, var(--line));

  /* -- where the work stands, in colour -----------------------------
     One property per state, named by `domain/status.js` (STATUS_TOKENS)
     and read by `.st.<class>` in `surfaces/board/row.css`. They are
     aliases of the semantic tokens above rather than values of their
     own, so both skins answer them without a second table: the dark
     skin redefines `--ok`, `--bad`, `--warm` and `--blue`, and these
     follow.

     The one that was wrong: "Confirm done" — the brain believes this finished
     and nobody has said so — was drawn in the same green as Done, so a
     column of green dots was half questions (a user test, 2026-09-21).
     It is amber: something waiting on a person. Blocked took the red,
     being the state that needs someone. */
  /* -- why a queued task is held, in colour --------------------------
     Three properties, read by `.held.is-*` in `domain/held.css` and named
     by `domain/held.js`. Aliases of the semantic tokens above rather than
     values of their own, so both skins answer without a second table: the
     dark skin redefines `--dim`, `--blue` and `--c-amber`, and these
     follow.

     The three are the three KINDS of wait, and they are deliberately the
     colours this palette already uses for those meanings. Grey is a wait
     on the clock, which needs nobody: the same `--dim` every quiet aside
     on the board is written in. Blue is a wait on other work, the hue "in
     progress" wears. Amber is a wait on a PERSON, which is the one a
     reader has to act on, and it is the amber "Confirm done" wears for
     exactly that reason (see the state aliases below). A fourth colour was
     considered at intake and refused: it would not change what anybody
     does about the row. */
  --held-time: var(--dim);
  --held-work: var(--blue);
  --held-person: var(--c-amber);

  --st-neutral: var(--faint);
  --st-blue: var(--blue);
  --st-blue-ink: var(--blue-ink);
  --st-red: var(--bad);
  --st-red-ink: var(--bad-ink);
  --st-amber: var(--c-amber);
  --st-amber-ink: var(--warm);
  --st-green: var(--ok);
  --st-green-ink: var(--ok-ink);
  --st-grey: var(--line);
  }
}

* { box-sizing: border-box; margin: 0; }
html, body { height: 100%; }
body {
  background: var(--bg); color: var(--text);
  font: 14px/1.45 var(--font-body, "Inter", system-ui, sans-serif);
}

/* Every link is themed, not just the ones we remembered to name. The
   UA's default blue survives on any <a> a rule misses, and against the
   dark board that blue has almost no contrast — a link nobody can read
   is the same as no link. Base rule first; specific rules still win. */
a { color: var(--link); text-decoration-color: color-mix(in srgb, var(--link) 55%, transparent); }
a:hover { text-decoration-color: currentColor; }
a:visited { color: var(--link); }

/* -- one control system ---------------------------------------------- */
/* Every box a person types in, picks from or presses wears the same edge:
   `--line` at rest, `--hot` with a soft ring when it has focus, one
   radius. Before this rule the three date pickers on a task's page and
   the planning sheet's inputs and selects had no rule at all, so they
   wore the browser's own black 1px outline beside the board's light one
   — two products in one row (the operator, 2026-09-14). Sizes and fonts
   stay each control's own; a field that deliberately hides its edge (the
   search box inside its pill) or dashes it (an invitation to add) says
   so in its own rule, later in the sheet, and wins. */
:root { --ctl-radius: 7px; --ring: 0 0 0 3px var(--hot-soft); }
input:not([type="checkbox"], [type="radio"], [type="range"]), select, textarea {
  height: 28px; padding: 0 9px;
  border: 1px solid var(--line); border-radius: var(--ctl-radius);
  background: var(--raise); color: var(--text);
  font: 12px var(--font-body, sans-serif);
}
textarea { height: auto; padding: 8px 10px; line-height: 1.5; }
input::placeholder, textarea::placeholder { color: var(--faint); }
:is(input, select, textarea, .btn, .mseg):focus-visible,
:is(input, select, textarea):focus,
.mseg:focus-within {
  outline: 0; border-color: var(--hot); box-shadow: var(--ring);
}
.mbtn:focus-visible { outline: 2px solid var(--hot); outline-offset: -2px; }

/* The segmented control itself — a label and a row of buttons, one of
   which is the value. Four surfaces wear it: a row's detail panel and
   the task page (State, Priority, Due), the bulk bar's shared day, the
   add form and the capture page. It lived in `questions.css` until
   2026-09-22, under the band that is now the Agent suggested section,
   which is how a shared idiom ends up filed under one screen's name. It
   is here because it belongs to no screen; the focus rules above have
   always been. */
.mlab {
  font: 9.5px var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint); white-space: nowrap;
}
.mseg { display: inline-flex; border: 1px solid var(--line); border-radius: var(--ctl-radius); overflow: hidden; }
.mbtn {
  font: 10px var(--font-body, sans-serif); color: var(--text);
  background: var(--raise); border: 0; border-left: 1px solid var(--line);
  padding: 3px 8px; cursor: pointer; white-space: nowrap;
}
.mbtn:first-child { border-left: 0; }
.mbtn:hover { background: var(--purple-soft); color: var(--purple); }
/* The chosen value, wherever the segment stands. This rule lived under
   `.editrow` alone, so the task page's State and Priority — the same
   buttons outside an editrow — had no chosen look at all: "Confirm done" landed
   on the record and the page went on reading "Not started" (the operator,
   2026-09-17, POP-3901). Filled, so the pointer resting on a neighbour
   (the soft hover above) is never mistaken for the choice. */
.mbtn.on, .mbtn.on:hover { background: var(--purple); color: #fff; font-weight: 700; }

tasks-app { display: flex; flex-direction: column; height: 100vh; }

/* A phone (POP-7839). Every text box is 16px, because iOS zooms the page
   into any field set smaller the moment it is tapped and does not zoom
   back out. `html body` and `:is` are only weight: the surfaces set their
   own sizes on their own fields, and this has to outrank every one of
   them at this width and at no other. */
@media (max-width: 768px) {
  html body :is(input:not([type="checkbox"]):not([type="radio"]):not([type="range"]), textarea, select) {
    font-size: 16px;
  }
}
/* The one spinner on the board (lib/loading.js).

   Every place that waits on the server draws the same two things — a
   turning ring and, where there is room, the word — so a reader learns
   the shape once and then recognises it in the table, in a section head
   and beside a count. The colours are the palette's own tokens, so both
   skins answer without a second table here: `--line` is the ring, `--hot`
   the arc that turns on it, `--faint` the word.

   Reduced motion keeps the shape and stills the turn: a dot that pulses
   in the same 14 pixels. That is not decoration — a badge's ring is the
   only thing standing where a number will be, so it has to stay visible
   and stay clearly not-a-number. */

.loading {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--faint);
}

.loading-ring {
  display: inline-block;
  flex: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid var(--line);
  border-top-color: var(--hot);
  animation: loading-turn .8s linear infinite;
}

/* Beside a count, where a badge's own type size is the measure. */
.loading-ring.inline {
  width: 9px;
  height: 9px;
  vertical-align: -1px;
}

/* In a space with nothing else in it — the first load's skeleton. */
.big .loading-ring { width: 18px; height: 18px; border-width: 3px; }

@keyframes loading-turn { to { transform: rotate(360deg); } }
@keyframes loading-pulse { 0%, 100% { opacity: .3; } 50% { opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
  .loading-ring {
    border: 0;
    background: var(--hot);
    animation: loading-pulse 1.4s ease-in-out infinite;
  }
}
/* -- header ---------------------------------------------------------- */
/* One row: the name and the view, the Live dot, the numbers debug adds,
   the search filling whatever is left, and the settings. The right
   padding is the BOARD's (18px), not the old 16px, so the row ends where
   the table under it ends rather than a couple of pixels past it. */
.bar {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 18px; border-bottom: 1px solid var(--line); background: var(--panel);
  /* Opened from the home screen, the app draws under a phone's status
     bar (`black-translucent`, tasks.html), so the bar pads itself clear
     of it and of the notch. `env()` is 0 in an ordinary browser tab, so
     nothing moves on a desk. */
  padding-top: calc(10px + env(safe-area-inset-top));
  padding-left: calc(18px + env(safe-area-inset-left));
  padding-right: calc(18px + env(safe-area-inset-right));
}
/* The title row is read at a glance, from a metre away, in a bright
   room: the name and the view beside it are the page's own ink
   (`--text`), not the quiet grey the rest of the chrome speaks in. The
   caption sat on `--dim` and the date beside it on `--faint` — 4.0:1 and
   2.9:1 on the light skin — and the 2026-09-21 user test could not read
   either. The date is gone; the caption is the view's name, and it reads
   as a peer of the title. Both skins get it: these are tokens, defined
   per skin (`palette.css`, `theme-dark.css`). */
.bar h1 { font: 600 16px var(--font-heading, "Poppins", sans-serif); letter-spacing: .01em; color: var(--text); }
/* The name is a control: it reads as the heading, tabs like a button. */
.bar h1 .brand {
  padding: 0; border: 0; border-radius: 5px; background: none;
  color: inherit; font: inherit; letter-spacing: inherit; cursor: pointer;
}
.bar h1 .brand:hover { color: var(--hot); }
.bar h1 .brand:focus-visible { outline: 2px solid var(--hot); outline-offset: 3px; }
.bar .grow { flex: 1; }
.caption { color: var(--text); font-size: 12.5px; font-weight: 500; }
/* The page's arithmetic, drawn only when the footer's Debug says so. */
.stats { font: 11px var(--mono); color: var(--faint); white-space: nowrap; }

/* The search takes the whole of what the rest of the row does not. It
   used to stop at 460px with a lane of empty panel to its right, which
   is a wide box drawn narrow for no reason anybody could see (the
   operator's walk, 2026-09-22) — and its own suggestion list hangs off
   this box, so the list was narrow with it. */
search-box.grow { flex: 1 1 auto; min-width: 180px; display: flex; }
.search {
  display: flex; align-items: center; gap: 8px;
  border: 1px solid var(--line); background: var(--raise);
  border-radius: 999px; padding: 0 14px; height: 30px;
  flex: 1 1 auto; min-width: 0;
}
.search .glass { color: var(--faint); font-size: 13px; }
.search:focus-within { border-color: var(--hot); box-shadow: var(--ring); }
.search input {
  border: 0; outline: 0; box-shadow: none; background: transparent; flex: 1; height: 100%;
  padding: 0; border-radius: 0; font: 13px var(--font-body, sans-serif); color: var(--text);
}
.search input::placeholder { color: var(--faint); }
.search kbd {
  font: 9.5px var(--mono); color: var(--faint);
  border: 1px solid var(--line); border-radius: 4px; padding: 1px 4px;
}

/* -- what the finder offers (surfaces/board/find.js) ------------------ */
/* The box asks the record and draws what came back under itself. The
   list hangs off `search-box`, which is the bar's own flex item, so it
   is as wide as the box however wide the bar lets that be. Over
   everything: the bar is sticky and the rows scroll under it, and a
   list of answers half-clipped by the table's first row is a list you
   cannot read the bottom of. */
search-box { position: relative; }
.find-list {
  position: absolute; z-index: 60; left: 0; right: 0; top: calc(100% + 4px);
  max-height: 60vh; overflow-y: auto;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--raise); box-shadow: var(--pop-shadow);
  padding: 4px;
}
.find-item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 6px 8px; border: 0; border-radius: 6px;
  background: transparent; text-align: left; cursor: pointer;
  font: 13px var(--font-body, sans-serif); color: var(--text);
}
.find-item:hover, .find-item.on { background: var(--hot-soft); }
/* The title takes the room, and a long one is cut rather than wrapping:
   every row the same height is what makes the arrow keys predictable. */
.find-word { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.find-key { flex: none; }
.find-state { flex: none; color: var(--faint); font-size: 11.5px; }
/* "Jump to", on the row a typed key names. It leads the row because the
   key is the surest thing a person can type and the answer to it should
   be the first thing under the box. */
.find-lead { flex: none; color: var(--purple); font-size: 11.5px; font-weight: 600; }
/* The three things the list says when it is offering nothing — waiting,
   no such key, nothing matched. Not an option: there is nothing to pick. */
.find-note { padding: 8px; font: 12.5px var(--font-body, sans-serif); color: var(--faint); }

/* -- the quick line, over the list it lands in ------------------------ */
/* Quiet: a field and a ＋ on the board's own gutter, so it reads as part
   of the list rather than as a second header. Full width, because every
   single-line box a summary is typed into takes the width of its column
   (the 2026-09-23 walk). */
.board > quick-line { display: block; margin: 0 0 12px; }

/* -- layout ---------------------------------------------------------- */
.frame { display: flex; flex: 1; min-height: 0; }
/* No horizontal padding of its own: the rail's one gutter is
   `--rail-gutter` (rail.css), applied by every row, label and box in it,
   so the sticky top's background can still run edge to edge while its
   contents line up with everything below. */
project-rail {
  width: var(--rail-w, 220px); flex: none; overflow-y: auto; padding: 12px 0;
  border-right: 1px solid var(--line); background: var(--panel);
}
.board { flex: 1; overflow-y: auto; padding: 12px 18px calc(60px + env(safe-area-inset-bottom)); }

/* -- one action-button system ---------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  height: 28px; padding: 0 12px; border-radius: 7px;
  border: 1px solid var(--line); background: var(--raise); color: var(--dim);
  font: 500 12px/1 var(--font-body, sans-serif); cursor: pointer; white-space: nowrap;
}
.btn:hover { border-color: var(--hot); color: var(--text); }
.btn.on { background: var(--hot-soft); border-color: var(--hot); color: var(--text); font-weight: 600; }
.btn.primary { background: var(--hot); border-color: var(--hot); color: #fff; font-weight: 600; }
.btn.primary:hover { background: var(--c-cta-dark); border-color: var(--c-cta-dark); color: #fff; }
.btn.tall { height: 34px; padding: 0 16px; font-size: 13px; }
.btn.quiet { border-color: transparent; background: transparent; color: var(--faint); }
.btn.quiet:hover { color: var(--text); border-color: transparent; background: var(--hot-soft); }
.btn:disabled { opacity: .5; cursor: default; }
settings-menu .btn { border-radius: 999px; }
/* The gear is an icon now, not a character in the button's 12px type. */
settings-menu .gear .icon { width: 15px; height: 15px; }
/* The brand's light/dark toggle, dressed as one of this board's own
   controls. It renders a plain <button> and takes its class from the
   `button-class` attribute (`board.js`), so all this has to say is that it
   holds no width of its own and wears the same pill as the ⚙ next to it.
   The icon is the first span, and it sits a touch large next to 12px text
   at its natural size. */
mp-theme-toggle { flex: none; display: inline-flex; }
mp-theme-toggle .btn { border-radius: 999px; }
mp-theme-toggle .btn span[aria-hidden] { font-size: 13px; line-height: 1; }
.linkbtn {
  border: 0; background: transparent; padding: 0; cursor: pointer;
  color: var(--hot); font: 600 11px var(--font-body, sans-serif);
  text-decoration: underline; text-underline-offset: 2px;
}

/* -- loading, unmistakable --------------------------------------------
   The first load's skeleton: shimmer rows where the table will be, under
   the one spinner at its larger size (`.big`, lib/loading.css). Both
   skins compose the shimmer from their own tokens; reduced motion keeps
   the shape and stills it — the "Loading tasks…" label beside the ring
   carries the meaning either way. */
.skel { display: flex; flex-direction: column; gap: 14px; padding: 4px 2px 40px; }
.skel-row {
  height: 14px; width: var(--skel-w, 80%); border-radius: 4px;
  background: linear-gradient(90deg, var(--raise) 25%,
    color-mix(in srgb, var(--line) 55%, var(--raise)) 50%, var(--raise) 75%);
  background-size: 200% 100%;
  animation: skel-sheen 1.4s linear infinite;
}
@keyframes skel-sheen {
  from { background-position: 180% 0; }
  to { background-position: -80% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .skel-row { animation: none; background: var(--raise); }
}
/* A rail count still counting: dots, never a zero nobody served. */
.count.wait { color: var(--faint); letter-spacing: 1px; }
.error-note { color: var(--bad); font-size: 12px; }
.notice-note { color: var(--ok-ink); font-size: 12px; }

/* -- condensed / expanded ---------------------------------------------
   Nothing here. Condensed and Expanded used to change the row height, the
   actions column's width and the type size as well as what a row showed,
   so the one control answered two questions and a reader who wanted the
   whole of a long summary got a differently-proportioned board with it.
   Since 2026-09-21 the preference means exactly one thing — does the
   Summary cell wrap — and that lives in `row.css` beside the cell. The
   row's own height is `--row-h`, one number, in `palette.css`. */

/* -- the always-on instrument -----------------------------------------
   `Live` and one dot. The dot IS the state — green live, yellow
   refreshing or retrying, red offline — and it is a fixed-size inline
   block, so nothing beside it moves when its colour changes. The line
   used to carry the state in words of three different widths, remeasured
   every second ("● live · 8s", "● refreshing…", "● offline since
   14:02"), which shifted the whole header under the reader's eye. The
   words survive as the tooltip and as the element's accessible name, so
   the colour is never the only thing saying it. */
.beat {
  display: inline-flex; align-items: center; gap: 6px;
  font: 11px var(--mono); color: var(--dim); white-space: nowrap;
}
.beatdot {
  display: inline-block; flex: none; width: 8px; height: 8px;
  border-radius: 50%; background: var(--faint);
}
.beatdot.ok { background: var(--ok); }
.beatdot.wait { background: var(--warm); }
.beatdot.down { background: var(--bad); }
/* Debug's age in seconds, the one number on this board that ticks. */
.beatage { color: var(--faint); }
/* A fetch in the air breathes — "refreshing…" and "retrying" must read
   as activity, not as a stuck label. Reduced motion stills it; the
   words themselves already say what is happening. */
.beat.asking { animation: beat-pulse 1.1s ease-in-out infinite; }
@keyframes beat-pulse { 50% { opacity: 0.35; } }
@media (prefers-reduced-motion: reduce) {
  .beat.asking { animation: none; }
}
.delta { font: 11px var(--mono); color: var(--dim); white-space: nowrap; }

.banner {
  padding: 8px 12px; border-radius: 7px; margin-bottom: 10px;
  font-size: 12.5px; border: 1px solid var(--line); background: var(--panel);
}
.banner.warn { background: var(--warm-soft); border-color: var(--warm-line); color: var(--warm); }
.banner.note { background: var(--panel); color: var(--dim); }
.banner .linkbtn { margin-left: 6px; }
/* One change per row. Three failures joined onto one line ran off the
   edge and the reader got one and a half of them (the operator,
   2026-09-14); a list with no bullets fits every name and every sentence
   on its own line, and wraps rather than clipping. */
.banner .troubles {
  list-style: none; margin: 5px 0 0; padding: 0; display: grid; gap: 3px;
}
.banner .troubles li { overflow-wrap: anywhere; }
.banner .troubles li.more { opacity: .8; }
/* Waiting is not failing: amber words, but a dashed edge rather than the
   filled warning ground, so a held change never reads as a lost one. */
.banner.note.holding {
  border-style: dashed; border-color: var(--warm-line); color: var(--warm);
}

/* A new row announces itself briefly; a moved one flashes once (story 2). */
task-row.fresh .t-line { animation: arrive 2.4s ease-out; }
@keyframes arrive {
  0% { background: var(--cool-soft); }
  100% { background: transparent; }
}
task-row.moved .t-line { animation: settle 1.8s ease-out; }
@keyframes settle {
  0% { background: var(--warm-soft); }
  100% { background: transparent; }
}

/* -- the settings, at the top where settings live ----------------------
   Habits, none of them a fact about the work. They were buttons under
   the table that CYCLED — press "◐ System" and find out what it says
   next — at the bottom of a page nobody scrolls to for settings. Now:
   one ⚙ at the end of the header, and a panel with every option showing
   as a radio group (`chrome.js`).

   The panel is a native auto popover, so Escape and a click outside both
   close it with no JavaScript, and the browser paints it over everything
   without a z-index race. `:popover-open` is the only state it has.

   HOW IT READS was wrong until 2026-09-22. The operator, on the live board:
   "the color is so bright and the pills are so big compared to the
   option". Each answer was a full-width segment stretched to a third of
   the panel, and the standing one was filled with the brand's CTA pink
   at full strength — so a panel of six quiet choices was six pink slabs,
   louder than anything on the board behind it, with the words naming
   them at a fraction of their size. A setting panel is a thing you read,
   not a thing that should win the page.

   So: the panel is paper (`--panel`, the card ground every other raised
   surface here stands on), each answer is a pill no wider than its own
   word, and the standing one is the accent as INK on its softest wash
   (`--link` on `--hot-soft`) rather than a fill. The name of each
   setting is small, uppercase and muted, in a column of its own that
   every row shares, so the answers all start at the same x — the panel
   reads down as a list rather than as six independent widgets. */
settings-menu { flex: none; position: relative; }
/* AND IT HAS TO LOOK LIKE IT IS IN FRONT. The operator, on the live
   board in the dark skin, 2026-09-23: "I want the menu to be a little
   more visually distinct from the background." It was standing on
   `--panel`, which in the dark skin is #0D152E against a #070C1A page —
   three and a half points of lightness apart, which is not a step a
   reader's eye reads as an edge — under a black drop shadow at 18%,
   which on a near-black ground is nothing at all. So a panel of settings
   floated over the board looking like a part of it.

   It stands on `--raise` now: the same raised ground every OTHER popover
   on this board already uses (the pickers' lists, a property's popover),
   ten points of lightness above the page in the dark skin, and on paper a
   hair off white where the drop does the separating instead. The drop is
   `--pop-shadow`, which is a token precisely because it has to be a
   different depth in each skin. Three things say "in front", together:
   the surface, the hairline, and the lift. */
.setpanel {
  /* The label column, shared by every row in both halves of the panel,
     so a reader's eye runs down one edge. Wide enough for the longest
     name the panel carries — which is "Quick task line", at 97px, and
     was not: the column was sized for "Add all mine" and never grown
     when a longer setting was added under it, so that one name wrapped
     to two lines and its row was taller than every other row in the
     panel (the operator, 2026-09-23). */
  --set-label: 108px;
  position: absolute; inset: auto; margin: 0; padding: 12px 14px 13px;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--raise); color: var(--text);
  box-shadow: var(--pop-shadow); min-width: 276px;
}
.setpanel:popover-open {
  position: fixed; top: 48px; right: 18px; left: auto; bottom: auto;
  display: grid; gap: 9px;
}
/* The panel's own name, and the Experimental part's: quiet structural
   markers, not headings that compete with the settings under them. */
.sethead {
  font: 600 9.5px var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint);
}
/* One row per setting: the name in the shared column, the answers beside
   it. */
.setgroup {
  display: grid; grid-template-columns: var(--set-label) 1fr;
  align-items: center; gap: 10px;
}
.setword {
  font: 600 10.5px var(--font-body, sans-serif); letter-spacing: .06em;
  text-transform: uppercase; color: var(--dim);
}
/* Every answer at once, the standing one marked — a segmented row, not a
   thing that cycles. `justify-self: start` and `flex: none` are the
   whole of "sized to the text": the pills measure their own words and
   stop, instead of sharing out the panel's width between them. */
.setopts { display: flex; gap: 0; justify-self: start; }
.setopt {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  padding: 2px 8px; cursor: pointer; white-space: nowrap;
  border: 1px solid var(--line); border-left-width: 0;
  color: var(--dim); font: 500 12px var(--font-body, sans-serif);
}
.setopt:first-child { border-left-width: 1px; border-radius: 5px 0 0 5px; }
.setopt:last-child { border-radius: 0 5px 5px 0; }
.setopt:hover { color: var(--text); background: var(--hot-soft); }
/* The standing answer wears the accent as ink on its softest wash, and
   takes a border of its own all the way round — pulled back a pixel, so
   nothing beside it moves. Both skins answer this without a second
   table: `--hot-soft` is mixed against the ground and `--link` is the
   readable end of the brand pink on each of them (palette.css,
   theme-dark.css). */
.setopt.on {
  background: var(--hot-soft); color: var(--link); font-weight: 600;
  border-color: color-mix(in srgb, var(--hot) 45%, var(--line));
  border-left-width: 1px; margin-left: -1px;
}
.setopt.on:first-child { margin-left: 0; }
/* The radio itself is the state and the keyboard's way in; the label
   around it is what a person sees and clicks. */
.setopt input { position: absolute; opacity: 0; width: 0; height: 0; }
.setopt:focus-within { outline: 2px solid var(--link); outline-offset: 1px; }

/* Experimental: the settings one person asked for, under a hairline and
   one line saying who they are for (`chrome.js`). Its rows stand on the
   same label column as the ones above, so the hairline is the only thing
   that separates them and the panel still reads as one list. */
.setzone {
  display: grid; gap: 9px; margin-top: 2px; padding-top: 11px;
  border-top: 1px solid var(--hair);
}
.setzone .setsays {
  margin-top: -6px;
  font: 400 11px var(--font-body, sans-serif); color: var(--faint);
}
/* A task's page stands in for the table and takes the table's footer
   with it (`task-page.css`); the legend is the table's, so it steps
   aside there and the row itself simply has nothing left to draw. */
tasks-app.paged .tfoot .legend { display: none; }

/* -- the My work tabs ------------------------------------------------- */
/* The pair above the table on My work — what I owe, what I just finished.
   They were `.qtab` under the questions band's stylesheet, because the
   band wore the same pill for its Me / All scopes; the band is gone and
   the tabs are not, so the rules come here, to the shell that draws them.
   (`.minetabs` positions them in the Focus line — planning.css.) */
.qtabs { display: inline-flex; gap: 2px; padding: 2px; border-radius: 999px; background: var(--raise); }
.qtab {
  border: 0; background: none; cursor: pointer; border-radius: 999px;
  padding: 3px 10px; color: var(--dim); font: 500 11.5px var(--font-body, sans-serif);
}
.qtab:hover { color: var(--text); }
.qtab.on { background: var(--hot); color: #fff; font-weight: 600; }
.qtab .n-small { opacity: .75; font-size: 10.5px; margin-left: 3px; }
/* -- rail ------------------------------------------------------------ */
/* ONE left gutter. The section labels, the filter boxes, "All projects"
   and "Everyone", and every row's own text all start at the same x — a
   rail whose labels, boxes and names each began a few pixels apart read
   as four lists rather than one (the operator's screenshot,
   2026-09-22). Anything that changes a row's padding changes this
   number and nothing else. */
project-rail { --rail-gutter: 12px; }

/* -- the two collapsible sections ------------------------------------
   Projects and People are each a bordered container with a header bar
   you can click shut — the model every code editor's file explorer
   uses. Shutting one is not a choice of the other: both can stand open,
   both can be put away, and the height a shut one gives up goes to
   whatever is still open (the split rules further down).

   Views is not one of these. It is seven fixed entries at the top of the
   rail, and there is nothing about it to put away. */
.rail-part.rail-views { padding: 0 0 10px; }
.rail-part.rail-projects, .rail-part.rail-people {
  border: 1px solid var(--line); border-radius: 8px;
  margin: 0 6px 8px; overflow: hidden;
}
/* The whole bar is the control: a chevron on its own is a 12px target in
   a column 220px wide. */
.rail-head {
  display: flex; align-items: center; gap: 6px; width: 100%;
  padding: 6px var(--rail-gutter); border: 0; background: var(--raise);
  color: var(--dim); text-align: left; cursor: pointer;
}
.rail-head:hover { color: var(--text); }
.rail-head .chev { flex: none; width: 10px; font-size: 10px; line-height: 1; }
.rail-head .count {
  margin-left: auto; flex: none; min-width: 2.2ch; text-align: right;
  font-size: 11px; color: var(--faint);
}
/* A section label is a heading and reads like one: the table's own head
   type, in the colour the board's dim text uses rather than the faint
   one it had, which measured under 3:1 on the light skin. */
.rail-title {
  font: 600 9.5px var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: inherit;
}
/* Views keeps a plain label, on the same gutter as everything else. */
.rail-views .rail-title {
  display: block; padding: 0 var(--rail-gutter); margin-bottom: 4px; color: var(--dim);
}
.rail-body-part { padding: 6px 0 4px; }
/* A shut section is SHUT. `hidden` is display:none in the browser's own
   sheet, which any author `display` beats — and the split layout below
   sets `display: flex` on this very element, so putting a section away
   hid nothing at all (found on the 2026-09-22 walk). Said here, first,
   so every rule after it has to say `:not([hidden])` to disagree. */
.rail-body-part[hidden] { display: none; }
/* A view is a row like any other: same gutter, and its count in the same
   right-hand column the projects' and people's counts stand in. */
.rail-item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 5px var(--rail-gutter); border: 0; border-radius: 6px; background: none;
  color: var(--dim); font: inherit; text-align: left; cursor: pointer;
}
/* Where a section's choices are made somewhere else — the standup's own
   scope chips, the planning week — the rail says so in their place
   rather than drawing buttons that page would not read. */
.rail-note {
  padding: 6px var(--rail-gutter); color: var(--faint); font-size: 12px; font-style: italic;
}
/* A section still waiting on its list says so with the word and a
   spinner, like every other waiting area on the board. The spinner is
   minimal on purpose: the shared loading helper lands on its own branch
   and this is the one place the rail draws a wait. */
.rail-loading { display: flex; align-items: center; gap: 6px; }
.rail-spin {
  flex: none; width: 9px; height: 9px; border-radius: 50%;
  border: 1.5px solid var(--line); border-top-color: var(--faint);
  animation: rail-spin 0.8s linear infinite;
}
@keyframes rail-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .rail-spin { animation: none; } }
.rail-item:hover { background: var(--hot-soft); }
.rail-item.on { background: var(--hot-soft); color: var(--text); font-weight: 600; }
.rail-item .count { margin-left: auto; font-size: 11px; color: var(--faint); }
/* The phone's fold toggle (POP-5466). The desktop never draws it, so the
   rail there is exactly what it was; the phone rules sit in the
   responsive block further down. */
.rail-fold { display: none; }
/* The phone's ☰ and the scrim behind its drawer exist only at phone
   width (responsive.css); beside a desktop board neither is drawn. */
.railmenu, .rail-scrim { display: none; }

/* -- the rail's sticky top: one way to make a task ------------------- */
/* One button, and only one. The "Quick task…" line stood here with the
   ＋ beside it and "+ New Task" underneath, so the thing a person does
   most often lived in two controls that did nearly the same thing, in a
   column of places to GO. Folding the rail and opening it again left the
   line wrapped over two lines, which is what made anybody look. The line
   is over the table now (`surfaces/board/board.js`); this is the way to
   the full form, and it stays put while the sections under it scroll. */
.rail-top {
  position: sticky; top: 0; z-index: 2;
  padding: 6px var(--rail-gutter) 10px;
  background: var(--panel); border-bottom: 1px solid var(--line);
}
/* A BUTTON, filled, in the board's own accent — the one thing at the top
   of the sidebar and the one thing there that acts, so it looks like it
   (the operator, 2026-09-23: "+ New Task is a button again, not a text
   link"). It takes the column's width and stays on one line whatever
   that width is: a button that wraps is the defect this whole section
   was rewritten for. */
.btn.primary.rail-new {
  display: flex; width: 100%; height: 32px; margin: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.btn.primary.rail-new.on { opacity: 0.85; }

/* -- a row of the Projects or People section -------------------------- */
/* Two controls, two acts: the name navigates to just this one (the rail
   is navigation, the operator's walk 2026-09-22), and the pin keeps it
   in the rail. "Show me several of these" is the column funnel's
   checklist, in the column a person is reading. */
/* The name starts at the gutter and the count and pin end in one
   right-hand column, so the numbers line up down the rail instead of
   ending wherever each name happens to stop. The pin keeps its width
   whether it is drawn or not, or every row with one would sit a glyph
   narrower than the rows without. */
.rail-row {
  display: flex; align-items: center; gap: 6px;
  padding: 0 var(--rail-gutter); border-radius: 6px;
}
.rail-row:hover { background: var(--hot-soft); }
.rail-row.on { background: var(--hot-soft); }
.rail-row .rail-name {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding: 5px 0; border: 0; background: none;
  color: var(--dim); font: inherit; text-align: left; cursor: pointer;
}
.rail-row.on .rail-name { color: var(--text); font-weight: 600; }
.rail-row .count {
  flex: none; min-width: 2.2ch; text-align: right;
  font-size: 11px; color: var(--faint);
}
/* The second number on a client's row: how much open work the RECORD
   carries under it, beside what the page in hand holds. Quieter than the
   page's own count and separated by a middle dot, so "3 · 76" reads as
   one fact about two scopes rather than as two numbers colliding. Drawn
   alone — no dot — when the page holds none of that client. */
.rail-row .count.all { color: var(--faint); opacity: 0.75; }
.rail-row .count + .count.all { min-width: 0; }
.rail-row .count + .count.all::before { content: "·"; margin: 0 3px; opacity: 0.6; }
/* A pin is a habit, not a narrowing, so it is quiet — but it is always
   DRAWN. It used to appear only under the pointer, which makes pinning
   something you have to already know you can do (the operator,
   2026-09-22). Grey at rest, in colour when it is on. */
.rail-pin {
  flex: none; width: 16px; border: 0; background: none; padding: 0; cursor: pointer;
  font-size: 10px; line-height: 1; opacity: .35; filter: grayscale(1);
}
.rail-row:hover .rail-pin, .rail-pin:focus-visible { opacity: 0.7; }
.rail-pin.on { opacity: 1; filter: none; }

/* -- the filter box each section has --------------------------------- */
/* The list under it is everything the board knows; this narrows it. The
   box is exactly as wide as the rows it filters and starts at the same
   gutter — it sat two pixels inside them, which is the kind of gap a
   reader cannot name and can see. */
.rail-find { position: relative; padding: 0 var(--rail-gutter) 6px; }
.rail-find input {
  width: 100%; height: 26px; padding: 0 8px;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--raise); color: var(--text); font: 12px var(--font-body, sans-serif);
}
.rail-find input::placeholder { color: var(--faint); }
/* The way back out, drawn only while there is something to clear. */
.rail-clear {
  position: absolute; top: 0; right: var(--rail-gutter); height: 26px; width: 22px;
  display: flex; align-items: center; justify-content: center;
  border: 0; background: none; padding: 0; cursor: pointer;
  color: var(--faint); font: 14px var(--font-body, sans-serif); line-height: 1;
}
.rail-clear:hover, .rail-clear:focus-visible { color: var(--text); }
.rail-find:has(.rail-clear) input { padding-right: 28px; }

/* -- A/B: how the rail stands (pref tasks.rail.layout) ---------------- */
/* `split` (the default): the rail is the viewport's height, Views takes
   what it needs, and Projects and People split the rest with a scroll
   each — so neither list can push the other out of sight. `scroll`: one
   column, the whole rail scrolling, which is what it did before. The
   footer's toggle writes the pref; this file is the whole difference. */
project-rail[data-layout="split"] {
  overflow: hidden;
  height: 100%;
}
project-rail[data-layout="split"] .rail-body {
  display: flex; flex-direction: column; height: 100%; min-height: 0;
}
project-rail[data-layout="split"] .rail-views { flex: none; }
/* An OPEN section takes a share of what is left and scrolls inside
   itself; a shut one is just its header bar, and the share it is not
   taking goes to whatever is still open. Two open: half each. One open:
   all of it. This is why the collapse is not exclusive — putting
   Projects away is how you give People the whole rail. */
project-rail[data-layout="split"] :is(.rail-projects, .rail-people) {
  flex: 1 1 50%; min-height: 0; display: flex; flex-direction: column;
}
project-rail[data-layout="split"] :is(.rail-projects, .rail-people).shut { flex: none; }
project-rail[data-layout="split"] .rail-body-part:not([hidden]) {
  flex: 1; min-height: 0; display: flex; flex-direction: column;
}
project-rail[data-layout="split"] .rail-list { flex: 1; min-height: 0; overflow-y: auto; }
project-rail[data-layout="split"] :is(.rail-top, .rail-head, .rail-find) { flex: none; }
/* The phone has no room to split anything, and the rail there is already
   one folding block above the board (POP-5466) — so the split layout is
   the scrolling one at phone width, whatever the pref says. */
@media (max-width: 768px) {
  project-rail[data-layout="split"] { height: auto; overflow-y: auto; }
  project-rail[data-layout="split"] .rail-body { display: block; height: auto; }
  project-rail[data-layout="split"] .rail-body-part:not([hidden]) { display: block; }
  project-rail[data-layout="split"] .rail-list { overflow-y: visible; }
}

/* -- the rail, folded to icons ---------------------------------------
   the operator's walk, 2026-09-23: "all sidebars collapsible (the rail and the
   properties)" — with both folded, the board and a task page inside it
   are a clean content view. The chevron is in the header bar
   (`board.js`), the answer is a habit (`tasks.rail.collapsed`), and the
   whole difference is these rules: nothing repaints when the rail folds,
   so a rail scrolled halfway keeps its place.

   Folded, the rail is the seven VIEWS as their marks and nothing else.
   The projects and people lists, the quick line and the New Task button
   go, because none of them says anything in 52px — and the mark of the
   view you are standing on is still lit, so the strip answers "where am
   I" without being opened. */
tasks-app.railshut project-rail { width: 52px; }
tasks-app.railshut project-rail :is(.rail-top, .rail-projects, .rail-people, .rail-fold) {
  display: none;
}
tasks-app.railshut .rail-views .rail-title { display: none; }
tasks-app.railshut .rail-item {
  justify-content: center; gap: 0; padding: 7px 0; margin: 0 6px; border-radius: 6px;
}
tasks-app.railshut .rail-item :is(.rail-word, .count) { display: none; }
/* Open, the marks are not drawn at all: the words are the rail. */
.rail-mark { display: none; }
tasks-app.railshut .rail-mark { display: block; font-size: 15px; line-height: 20px; }
.rail-item .rail-word { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* The header's own chevron, left of the board's name. */
.railfold {
  flex: none; display: grid; place-items: center; width: 24px; height: 24px;
  padding: 0; border: 0; border-radius: 6px; background: transparent;
  color: var(--faint); font-size: 14px; line-height: 1; cursor: pointer;
}
.railfold:hover { color: var(--text); background: var(--raise); }
.railfold:focus-visible { outline: 2px solid var(--hot); outline-offset: -2px; }
/* -- the handle that drags the rail wider (POP-7839) -----------------
   A 6px strip laid over the rail's right border, a sibling of the rail
   in the frame (`rail.js`), so it neither scrolls with the list nor takes
   any width of its own: the negative margins give back what it occupies.
   The width it sets is `--rail-w` on the root, read by the rail's own
   rule in board.css. Folded to icons the rail is 52px whatever the
   variable says (the rule above outranks it), and the handle goes. */
.rail-grip {
  flex: none; width: 6px; margin: 0 -3px; position: relative; z-index: 2;
  cursor: col-resize; touch-action: none; background: transparent;
}
.rail-grip:hover, .rail-grip.dragging, .rail-grip:focus-visible {
  background: color-mix(in srgb, var(--hot) 45%, transparent); outline: 0;
}
tasks-app.railshut .rail-grip { display: none; }
/* A phone's rail is already one folding block above the board
   (POP-5466); there is no second fold to make there. */
@media (max-width: 768px) {
  .rail-grip { display: none; }
  .railfold { display: none; }
  tasks-app.railshut project-rail { width: auto; }
  tasks-app.railshut project-rail :is(.rail-top, .rail-projects, .rail-people, .rail-fold) {
    display: revert;
  }
  tasks-app.railshut .rail-views .rail-title { display: block; }
  tasks-app.railshut .rail-item { justify-content: flex-start; gap: 8px; }
  tasks-app.railshut .rail-item :is(.rail-word, .count) { display: revert; }
  tasks-app.railshut .rail-mark { display: none; }
}
/* -- Agent suggested ------------------------------------------------ */
/* The section above the board's table: what the brain proposed and
   nobody has taken up. It is the table's own head and rows (head.css,
   row.css) standing on a grid of their own, so almost everything here is
   the container around them — the header you can put away, the empty
   line, and the two-word action pair at the end of each row.

   Served where `questions.css` was, which is where these rules stood
   when the board had one stylesheet: after the rail, before the head. It
   wins over `.thead`/`.t-line` on the one thing it changes — which grid
   the cells stand on — by naming the section as well as the part. */
/* The host is a plain block with NO spacing of its own. Nothing waiting
   and `proposed.js` draws nothing inside it, so an empty one is a block
   with no content and no margin: zero pixels, and the Focus head under
   it is the next thing on the board. The 14px that stands this section
   off the table belongs to the section itself (`.prop`) — it used to sit
   here, where an empty element still spent it: fourteen pixels of
   nothing above the work, every morning the brain proposed nothing. */
proposed-section { display: block; }
/* Every rule in this file that names `.prop` says `proposed-section`
   first, and has to. `.prop` is this band's own container — one raised,
   rounded, shadowed card — and it is ALSO the class a property line on a
   task page carries (`domain/prop-menu.js`). This sheet is served before
   that one, but nothing there redeclared a border or a radius, so each of
   the eight properties in the task page's rail was drawn as a copy of
   this card: eight little shadowed boxes stacked down the rail, which is
   what the operator saw on 2026-09-23 ("stacked rounded boxes look weird"). Two
   parts of the app wanting the same short class name is ordinary; a rule
   that does not say which part it belongs to is the defect. */
proposed-section .prop {
  background: var(--band); border: 1px solid var(--band-line);
  border-radius: 8px; overflow: visible;
  box-shadow: var(--shadow);
  margin-bottom: 14px;
}

/* The header, and the whole of it is the control: click it, or Tab to it
   and press Enter, and the section puts itself away. The chevron is
   always drawn — nothing on this board waits for a pointer to show
   itself (the operator, 2026-09-22). */
.prophead {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 14px; cursor: pointer; user-select: none;
}
proposed-section .prophead:hover { background: var(--band-hover); }
.prophead:focus-visible { outline: 2px solid var(--hot); outline-offset: -2px; }
.prophead h3 { font: 600 14px var(--font-heading, "Poppins", sans-serif); }
.propchev { color: var(--faint); font-size: 13px; width: 12px; line-height: 1; }
.prophead .n {
  font: 600 11px var(--mono); background: var(--raise);
  border: 1px solid var(--band-line); border-radius: 999px; padding: 1px 8px;
}
/* Whose proposals — Mine (the ones an agent put on you) or All. A
   segmented pair, the same shape the settings panel uses for every
   choice it offers: both answers visible, the standing one filled in,
   nothing cycles. It sits inside the header, which is itself a control,
   so it stops the press from reaching it (`proposed.js`). */
.propscope { display: inline-flex; margin-left: 2px; }
.propseg {
  border: 1px solid var(--line); border-left-width: 0; background: none;
  cursor: pointer; padding: 2px 9px; color: var(--dim);
  font: 500 11.5px var(--font-body, sans-serif);
}
.propseg:first-child { border-left-width: 1px; border-radius: 6px 0 0 6px; }
.propseg:last-child { border-radius: 0 6px 6px 0; }
.propseg:hover { color: var(--text); background: var(--hot-soft); }
.propseg.on { background: var(--hot); border-color: var(--hot); color: #fff; font-weight: 600; }
.propseg.on + .propseg { border-left-color: var(--hot); }
.propseg:focus-visible { outline: 2px solid var(--hot); outline-offset: 1px; }

/* Add all mine: the whole of Mine, taken up in one press.

   It stood beside the Mine / All pair, in the middle of the head, in a
   pink of its own — which put a thing that WRITES among the things that
   only choose what you are looking at, and shouted while it did it. It
   is at the far right end of the head now, alone (`margin-left: auto`),
   and it wears the row's own action button (`.ract`, row.css) rather
   than a face invented for it: the same height, the same border, the
   same green on hover as the Add on each row below, which is the button
   it does forty of (the operator, on the live board, 2026-09-22). */
.propall { margin-left: auto; }
/* Its place while the sweep runs — the same end of the head saying how
   far it has got, so nothing beside it moves when the button goes. */
.propall-doing {
  margin-left: auto; color: var(--dim);
  font: 500 11.5px var(--font-body, sans-serif);
}
/* Show more: the section draws one short page of suggestions and says
   how many of the whole it is showing, with the row's own action button
   for the next page. Right-aligned under the rows, where each row's own
   Add stands, and drawn only when more are waiting — an empty line is
   no line at all. */
proposed-section .propmore:empty { display: none; }
proposed-section .propmore {
  display: flex; align-items: center; justify-content: flex-end; gap: 10px;
  padding: 6px 14px; border-top: 1px solid var(--hair);
}
.propmore-n { color: var(--dim); font: 500 11.5px var(--font-body, sans-serif); }
/* The question stands under the header, inside the section's own box, so
   the press that opened it and the answer are in one place. */
proposed-section .prop > .why-prompt { padding: 0 14px 10px; }

/* There is no rule here for a sentence along the head, because the head
   no longer says one. It read "Read out of your email and meetings —
   nobody has said a word about them yet", in italics, on every paint
   (the operator, on the live board, 2026-09-22) — a caption a reader needs once
   and then reads past for good, spending the width the count and the
   controls want. */

/* -- nothing waiting -------------------------------------------------- */
/* There are no rules here, because there is nothing to dress.
   Most mornings the brain proposes nothing, and what stood here then was
   first a bordered box with a header, a column head and an empty table in
   it, and then — after the operator said on 2026-09-22 that it "takes up
   way too much space when there are no tasks" — one quiet line with an ×
   on it. A line is cheaper than a box and it is still a row of the
   board's height spent, above the work, saying zero. So the section is
   not drawn at all when it is empty (the operator, on the live board,
   2026-09-23), and neither is the spinner line it used to wear while its
   page was in the air: `.prop.oneline`, `.propnone`, `.propx` and
   `.propwait` are all gone with the states they dressed.

   How a reader still reaches proposals sitting under **All** while
   **Mine** is empty: the Focus head's own quiet link (`.focusprop`,
   head.css), because the Mine / All pair rides in a head that is not
   there. */

/* Put away: the header alone. The rows are not hidden here — the section
   draws none of them (proposed.js), because a row held open inside a
   `display: none` box is a menu the page believes is still up. */
proposed-section .prop.shut .propbody { display: none; }
.propbody { border-top: 1px solid var(--band-line); }

/* The table's own head and rows, on the section's grid. One string, set
   on the element by `proposed.js` from the one column list, so the head
   and the rows cannot disagree about where a column starts. */
proposed-section .prop .thead, proposed-section .prop .t-line { grid-template-columns: var(--prop-grid); }
/* The head belongs to this box, not to the window: a sticky head inside
   a section that scrolls with the page sticks to the wrong thing. */
proposed-section .prop .thead {
  position: static; border: 0; border-radius: 0; background: transparent;
  padding-left: calc(14px + var(--row-edge)); padding-right: 14px;
}
proposed-section .prop .t-line { padding-left: 14px; padding-right: 14px; background: transparent; }
proposed-section .prop task-row:last-child .t-line { border-bottom: 0; border-radius: 0 0 7px 7px; }
.proprows { border-top: 1px solid var(--hair); }

/* The pair at the end of a proposed row. They are the board's own
   `.ract` buttons (row.css) — same box, same restraint until the row is
   under the pointer — with words instead of glyphs, because "Add" and
   "Discard" are not things a reader should have to learn a symbol for.
   Add leads by weight, and by weight only.

   It used to lead by lighting up: the green fill that means "the pointer
   is on this button" was keyed on `.t-line:hover` / `.t-line:focus-within`
   — the ROW's state, not the button's — so putting the pointer on Expand,
   or on Discard, lit Add as well and the row offered two answers at once
   (the operator, on the deployed board, 2026-09-22). Nothing in the cluster is
   keyed on the row's state now except the shared rest/forward emphasis
   every button in it takes together (`.rowacts` in row.css); a highlight
   belongs to the button under the pointer, and `.ract:hover` there is the
   only rule that gives one. */
proposed-section .prop .rowacts { gap: 4px; }
proposed-section .prop .ract.add-act { font-weight: 700; }
proposed-section .prop .ract.discard-act:hover {
  border-color: var(--bad); background: var(--bad-soft); color: var(--bad-ink);
}
/* The head's word over them. It names both buttons, so it is not a
   column label in the usual sense and does not pretend to sort. */
proposed-section .prop .th-acts { justify-content: flex-end; text-align: right; }

/* Nobody has this one yet, so Add would give it to the reader: the cell
   says **Me** before the press rather than after. It is still the Person
   cell's own control — clicking it opens the same picker. */
proposed-section .prop .prop-me { color: var(--text); font-weight: 600; }

/* ---- the agent-candidate tick beside Add (POP-8883) ----------------- */

/* One optional box in the action cluster of a suggestion's row, before
   Add. It is a LABEL rather than a button because it chooses rather than
   acts: ticking it changes what Add will say, and nothing is written
   until Add is pressed. Sized and coloured as the quiet half of the
   cluster so it never competes with the two words that do act. */
.cand-tick {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-right: 6px;
  font-size: 10.5px;
  color: var(--faint);
  white-space: nowrap;
  cursor: pointer;
}
.cand-tick input {
  margin: 0;
  cursor: pointer;
}
.cand-tick:hover {
  color: var(--text);
}
/* The focus table's HEAD — the heading line, the order it stands in and
   the way back to the default, and the column headers themselves.
   Served before the standup because that is where these rules stood when
   the board had one stylesheet, and `.thead`/`.th` are beaten by rules
   that come after them. The row itself is `row.css`, after the standup,
   for the same reason. */
/* -- focus table ------------------------------------------------------ */
.focushead { display: flex; align-items: baseline; gap: 10px; padding: 4px 2px 6px; }
/* Toggled by board.js for the views that carry their own heading; the
   class was set with no rule behind it, so the head showed regardless. */
.focushead.off, .tfoot .legend.off { display: none; }
.focushead h3 { font: 600 14px var(--font-heading, "Poppins", sans-serif); }
.focushead .sub { font-size: 11px; color: var(--faint); }
/* Off the default order the line says which order holds, and is the way
   back to it: one quiet button, the sub line's own size. */
.focushead .sortreset {
  font-size: 11px; color: var(--faint); background: transparent;
  border: 1px solid var(--line); border-radius: 999px; padding: 1px 8px; cursor: pointer;
}
.focushead .sortreset:hover, .focushead .sortreset:focus-visible { color: var(--hot); border-color: var(--hot); }
/* Collapse all / Expand all (POP-8540), and the column picker's phone-width
   way in. Both are quiet controls over the whole list rather than facts
   about it, so they wear the same pill the order's own way back wears: a
   reader learns that shape once, at the right-hand end of this line.

   The picker's button is `.colpick`, the same class the head's trailing
   cell uses, because it opens the same menu (`renderColumnMenu`); the
   holder around it is what moves. On a phone the head's own cell is
   `display: none` (responsive.css), which is why there is a second holder
   at all, and the menu hangs off whichever one is standing. */
.focushead .foldall { margin-left: auto; }
.focushead .phonecols { position: relative; display: inline-flex; }
.focushead .phonecols[hidden] { display: none; }
.focushead .phonecols .colpick {
  font-size: 11px; color: var(--faint); background: transparent;
  border: 1px solid var(--line); border-radius: 999px; padding: 1px 8px; cursor: pointer;
  min-height: 28px;
}
.focushead .phonecols .colpick:hover, .focushead .phonecols .colpick:focus-visible {
  color: var(--hot); border-color: var(--hot);
}

/* "3 suggested for everyone" — the only trace the Agent suggested section
   leaves when it has nothing for THIS reader and something for somebody
   else. It is a link in a line that already exists, not a band of its
   own: a box saying "nothing for you" is the empty state the section was
   deleted to be rid of (proposed.js, 2026-09-23). Underlined so it reads
   as something to press, and the sub line's own size and grey so it does
   not compete with the heading it stands beside. */
.focusprop {
  font: 11px var(--font-body, sans-serif); color: var(--faint);
  background: none; border: 0; padding: 0; cursor: pointer;
  text-decoration: underline; text-underline-offset: 2px;
}
.focusprop:hover { color: var(--hot); }
.focusprop:focus-visible { outline: 2px solid var(--hot); outline-offset: 2px; border-radius: 3px; }

.thead {
  display: grid; grid-template-columns: var(--grid);
  align-items: center; gap: 8px; height: 28px; padding: 0 10px 0 calc(10px + var(--row-edge));
  background: var(--panel); border: 1px solid var(--line); border-radius: 6px 6px 0 0;
  position: sticky; top: 0; z-index: 3;
}
/* A head cell holds TWO controls (plan 2.4, 2026-09-21): the sort arrow
   it always had, and a funnel that narrows the list by that column. So
   the grid item is `.thcell` and the sortable part is the button inside
   it — a button may not contain a button, and the cell a menu opens out
   of may not clip its own overflow. `.th` keeps `data-key`, so the sort
   is clicked and tested exactly where it always was. */
/* One baseline for the three things a head cell holds — the label, the
   sort arrow and the funnel — and they sit TOGETHER, left, over the
   cells they name. The label used to be `flex: 1`, which pushed the two
   glyphs to the far right of the track and left a lane of white between
   a word and the arrow that belongs to it (the operator's walk,
   2026-09-22). No horizontal padding of its own: the head and the rows
   are one grid with one gap, so a head that padded itself would start
   its word somewhere its column's cells do not. */
.thcell {
  display: flex; align-items: center; gap: 4px; min-width: 0;
  position: relative; overflow: visible;
}
.thcell > * { min-width: 0; }
.th {
  font: 600 9.5px var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint); background: transparent; border: 0; padding: 0;
  text-align: left; display: flex; align-items: center; gap: 4px; cursor: pointer;
  flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.th:hover { color: var(--hot); }
/* The column doing the sorting says so with its ARROW and nothing else.
   It used to say it in accent-pink text as well — and the column being
   filtered said it in the same pink — so a head with one sort and one
   filter on it showed two pink words meaning two different things (the
   operator, 2026-09-23: "never highlight two columns the same colour").
   The label keeps the head's own colour whatever the column is doing. */
/* Every sortable head shows its arrow at ALL times: the column doing the
   sorting points its direction in ink, and every other column carries a
   muted ↕ saying "this one sorts too". They were invisible until you
   hovered, which is a control you have to already know about to find
   (the operator, 2026-09-22). Nothing on this board hides until hover. */
.th .ind { font-size: 14px; line-height: 1; color: var(--faint); opacity: .55; }
/* In ink — the page's own text colour, not the accent: it is a statement
   about the list, not a control asking to be pressed. */
.th .ind.on { color: var(--text); opacity: 1; }
.th.nosort { cursor: default; }
.th.nosort:hover { color: var(--faint); }
/* A view that owns its order offers no sort: the columns still name the
   data, they just stop pretending to be buttons. */
.thead.fixed .th { cursor: default; }
.thead.fixed .th:hover { color: var(--faint); }
.thead.fixed .th .ind { display: none; }

/* -- the funnel: narrowing by a column -------------------------------- */
/* Quiet at rest — ten columns must not read as a row of buttons — plain
   under the pointer, and unmistakable while it is ACTING, because a
   narrowed list that does not say so is the board lying about what it
   holds. (The chips above the table say it in words as well.) */
/* The funnel is an ICON, not a character: `⏷` is a small solid triangle,
   the same shape as the sort arrow beside it, so a head read as two sort
   controls until you hovered one. It is always drawn — muted at rest,
   in the accent while it is narrowing — because a list cut by a control
   nobody can see is the board lying about what it holds. */
.thf {
  flex: none; display: inline-flex; align-items: center;
  border: 0; background: none; padding: 0 2px; cursor: pointer;
  color: var(--faint); line-height: 1; border-radius: 3px;
  opacity: .55; transition: opacity .12s ease, color .12s ease;
}
/* The funnel speaks in the filter colour, at rest under the pointer and
   while it is acting, so one glyph in one colour means "this column is
   narrowing the list" wherever it is seen. The column's LABEL is never
   coloured for a filter: the head has two things to say and only one
   place to say each of them. */
.thf:hover, .thf:focus-visible { opacity: 1; color: var(--filter); background: var(--filter-soft); }
.thf.on { opacity: 1; color: var(--filter); }
@media (prefers-reduced-motion: reduce) {
  .thf { transition: none; }
}
/* A funnel's own menu, out from under the head's type: `.th` is 9.5px
   uppercase mono with letter-spacing, which is right for a column label
   and wrong for a list of sentences. (Same reasoning, same shape, as the
   column picker's `.colmenu` in row.css.) */
.thmenu {
  position: absolute; top: 24px; left: 0; z-index: 20; min-width: 200px;
  padding: 6px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--panel); box-shadow: 0 10px 28px rgb(0 0 0 / 28%);
  text-align: left; cursor: default;
  font: 400 12.5px var(--font-body, "Inter", system-ui, sans-serif);
  text-transform: none; letter-spacing: normal; color: var(--text);
}
.thmenu-head {
  padding: 2px 6px 6px; font: 600 9.5px var(--mono);
  letter-spacing: .1em; text-transform: uppercase; color: var(--faint);
}
.thmenu .item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 6px 9px; border: 0; border-radius: 5px; background: none;
  color: var(--text); font: 12.5px var(--font-body, sans-serif); text-align: left; cursor: pointer;
}
.thmenu .item:hover { background: var(--hot-soft); }
.thmenu .item.on { font-weight: 600; }
.thmenu .item .dim { margin-left: auto; color: var(--faint); font-size: 10.5px; }
.thmenu .item .pick { flex: none; width: 1em; text-align: center; color: var(--faint); }
.thmenu .item.on .pick { color: var(--hot); }
.thmenu .suggest-list { max-height: 220px; overflow-y: auto; }
/* A checklist's own pair: take everything listed, or drop the narrowing.
   These were the rail's section check-alls until 2026-09-22; they belong
   beside the list they act on. */
.thmenu-foot {
  display: flex; gap: 12px; padding: 6px 9px 2px; margin-top: 4px;
  border-top: 1px solid var(--line);
}
/* The last columns' menus would open past the board's right edge. */
.thcell:nth-last-child(-n+4) .thmenu { left: auto; right: 0; }
/* -- the standup: story cards, the ritual's own surface --------------- */
/* One card per person, walked blockers-first. The cards are NOT the
   table: no columns, no strikethrough — a completion leads with ✓
   because on this surface it is the content, not the noise. Tokens
   only, so both skins carry every piece. */
.thead.off { display: none; }
#rows.cards { border: 0; border-radius: 0; }

standup-surface { display: block; }
/* The surface's own bars — the day headline and the scope strip — share
   one flexible panel shape, stacked and joined like the old furniture. */
.sbar {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  padding: 8px 12px; background: var(--panel);
  border: 1px solid var(--line);
}
.sbar:first-child { border-radius: 8px 8px 0 0; }
.sbar + .sbar { border-top: 0; border-radius: 0 0 8px 8px; margin-bottom: 12px; }
.daystamp {
  font: 700 12px var(--font-heading, "Poppins", sans-serif);
  letter-spacing: .04em; text-transform: uppercase; color: var(--text);
  padding-right: 4px;
}

/* One card per ROW, stacked in walk order — the vertical order IS the
   walk (the operator, 2026-08-27: columns of unequal heights wasted space and
   starved the titles). Full width means each card takes only the height
   it needs; no column grid, no masonry, no equal-height gymnastics. */
.cardstack { display: flex; flex-direction: column; gap: 12px; align-items: stretch; }
.scard {
  border: 1px solid var(--line); border-radius: 10px; background: var(--panel);
  box-shadow: var(--shadow); overflow: hidden;
}
/* A card with a blocker announces it at the edge — the walk order put it
   first; the ink says why. */
.scard.warn { border-color: var(--warm-line); box-shadow: inset 0 3px 0 0 var(--warm), var(--shadow); }
.scard.mine { border-color: color-mix(in srgb, var(--hot) 45%, var(--line)); }
.scard-head {
  display: flex; align-items: baseline; gap: 8px;
  padding: 9px 12px 7px; border-bottom: 1px solid var(--hair);
}
.savatar {
  align-self: center; flex: none; width: 24px; height: 24px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font: 700 10px var(--mono); color: #fff; background: var(--purple);
  letter-spacing: .02em;
}
.scard.mine .savatar { background: var(--hot); }
.scard-head h4 { font: 600 13px var(--font-heading, "Poppins", sans-serif); color: var(--text); }
.scard-head .ssum { font: 11px var(--mono); color: var(--dim); margin-left: auto; text-align: right; }

.ssec { padding: 6px 12px 7px; min-width: 0; }
.ssec + .ssec { border-top: 1px solid var(--hair); }
/* A heavy card's internal split: sections FLOW into two balanced columns
   at wide viewports (Shipped beside In motion), newspaper order — down
   the left, then the right — so the walk's section order survives and no
   short section leaves a hole beside a tall one. Judged per card in JS,
   sections kept whole, never at the cost of title room, and never below
   the media floor: narrow viewports stay single-column inside cards
   whatever the class says. */
@media (min-width: 1000px) {
  .ssecs.twocol {
    columns: 2; column-gap: 33px; column-rule: 1px solid var(--hair);
    padding: 0 12px;
  }
  .ssecs.twocol .ssec {
    break-inside: avoid; padding-left: 0; padding-right: 0;
  }
}
.sseclab {
  font: 600 9.5px var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint); padding: 2px 0 3px;
}
.sseclab .stail { text-transform: none; letter-spacing: 0; color: var(--faint); font-weight: 400; }

/* One spoken line, three cells: glyph | title | rail (quiet facts, the
   receipt, the quick words). The title owns the flexible middle and may
   WRAP — at full card width a line has room, so a title carries its
   meaning and ellipsizes only past genuine excess (the clamp) — and the
   rail keeps the meta in a stable right-aligned place, the same
   discipline the table's columns give a row. Shipped work is never
   struck through — here it is the achievement, not the noise. */
.sline {
  display: grid; grid-template-columns: 14px minmax(0, 1fr) max-content;
  align-items: baseline; column-gap: 7px;
  padding: 2px 0; font-size: 12.5px; min-width: 0;
}
/* The title cell is a flex ROW: the clamp forces the anchor to be a
   block (-webkit-box), and an inline mark written after a block falls
   onto its own line — the operator's screenshot, every shipped line twice as
   tall. As flex items they sit side by side, baseline-aligned, so the
   ⧗ mark rides INLINE immediately after the title text and an item
   stays one line tall (plus genuine title wrap). */
.smain { min-width: 0; display: flex; align-items: baseline; gap: 5px; }
.smain .mark { flex: none; margin-left: 0; }
.sline .sname {
  color: var(--text); text-decoration: none; font-weight: 500; min-width: 0;
  white-space: normal; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.sline .sname:hover { color: var(--hot); text-decoration: underline; text-underline-offset: 2px; }
.sline.on .sname { color: var(--hot); }
.sglyph { flex: none; width: 14px; text-align: center; font-size: 11px; color: var(--faint); }
.sglyph.did { color: var(--ok); font-weight: 700; }
.sglyph.doing { color: var(--purple); }
.sglyph.blocked { color: var(--warm); }
.sglyph.slated { color: var(--dim); }
.sglyph.asks { color: var(--purple); }
.sglyph.recorded { color: var(--faint); }
/* A won't-do is a deliberate decision, spoken quietly: ✕-led and muted —
   NEVER struck through, the standup law. */
.sglyph.wontdo { color: var(--faint); }
.sline.wontdo .sname { color: var(--dim); font-weight: 400; }
.sline.wontdo .smeta { color: var(--faint); }
.srail {
  display: inline-flex; align-items: baseline; gap: 8px;
  justify-content: flex-end; white-space: nowrap;
}
.smeta { font: 10.5px var(--mono); color: var(--faint); text-align: right; }
.sline.blocked .smeta { color: var(--warm); }
.sline.did .smeta { color: var(--dim); }
.sreceipt {
  flex: none; border: 0; background: none; padding: 0 2px; cursor: pointer;
  color: var(--faint); font: 11px var(--mono); opacity: 0; text-decoration: none;
}
.sline:hover .sreceipt { opacity: 1; }
.sreceipt:hover { color: var(--hot); }
/* Quick words ride the line's right edge, emphasized under the pointer —
   the same rest/forward move the table's pinned actions make. */
.sacts { flex: none; display: inline-flex; gap: 4px; }
.sact {
  height: 20px; padding: 0 7px; border-radius: 4px; cursor: pointer;
  border: 1px solid var(--hair); background: transparent;
  font: 600 10px var(--font-body, sans-serif); color: var(--dim);
  white-space: nowrap; opacity: .55;
}
.sline:hover .sact, .sact:focus-visible { opacity: 1; border-color: var(--line); background: var(--raise); color: var(--text); }
.sact:hover { border-color: var(--ok); background: var(--ok-soft); color: var(--ok-ink); }
.sact.primary:hover { border-color: var(--purple); background: var(--purple-soft); color: var(--purple); }
/* The brain's ask, quoted under its line in the brain's own voice. */
.sask {
  margin: 0 0 4px 21px; padding: 4px 9px; font-size: 11.5px; color: var(--dim);
  border-left: 3px solid var(--purple-line); background: var(--purple-soft);
  border-radius: 0 4px 4px 0;
}
.smore {
  display: block; border: 0; background: none; cursor: pointer; text-align: left;
  padding: 2px 0 0 21px; font: 11px var(--mono); color: var(--link);
}
.smore:hover { text-decoration: underline; text-underline-offset: 2px; }
/* Backfill is narrated, not enumerated — one quiet sentence, the list a
   disclosure filed under its true dates. */
.srecorded {
  padding: 6px 12px 8px; border-top: 1px dashed var(--hair);
  font-size: 11px; color: var(--faint); font-style: italic;
}
.srecorded .linkbtn { margin-left: 6px; font-style: normal; }
.srecorded .sline { font-style: normal; }
.srecorded .sline .sname { color: var(--dim); font-weight: 400; }
/* A person with nothing to say says so in one quiet line — the card
   still earns its place in the room. */
.squiet, .sallquiet {
  padding: 10px 12px; color: var(--faint); font-size: 12px; font-style: italic;
}
/* An opened line expands into the full task row + detail — the table's
   own component, its width contained so the card never pushes the page. */
.sopen { overflow-x: auto; border: 1px solid var(--hair); border-radius: 6px; margin: 4px 0 6px; }
@media (max-width: 820px) {
  .scard-head .ssum { flex-basis: 100%; text-align: left; margin-left: 32px; }
  .sacts .sact { opacity: 1; } /* no hover to reveal with */
  /* A finger, not a pointer (POP-7839): the actions keep their look and
     take a 44px square to be hit in. */
  .sacts .sact { min-height: 44px; min-width: 44px; }
  .sline .sname { font-size: 15px; }
  .sreceipt { opacity: 1; }
  /* The title owns the line — a third breath before the ellipsis at
     375px — and the rail drops under it, left-aligned and wrapping, so
     the meta never squeezes the title to nothing. */
  .sline { grid-template-columns: 14px minmax(0, 1fr); }
  .sline .sname { -webkit-line-clamp: 3; }
  .srail {
    grid-column: 2; justify-content: flex-start; flex-wrap: wrap;
    white-space: normal;
  }
  .smeta { text-align: left; }
}

/* The day's figures: quiet, each a disclosure. */
.scopestrip .scopelab {
  font: 600 10.5px var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint);
}
.bstat {
  display: inline-flex; align-items: baseline; gap: 5px; cursor: pointer;
  font-size: 11.5px; padding: 2px 9px; border-radius: 999px;
  background: var(--raise); color: var(--dim); border: 1px solid var(--hair);
}
.bstat:hover { color: var(--text); }
.bstat.on { border-color: var(--hot); color: var(--text); }
.bstat .bnum { font: 700 12px var(--mono); color: var(--text); }
.bstat.warn .bnum { color: var(--bad-ink); }
.bstat.zero { opacity: .55; cursor: default; }
.ballclear { font-size: 11.5px; color: var(--ok-ink); }
.bdrill { flex-basis: 100%; border-top: 1px solid var(--hair); padding-top: 6px; }
.bdrill .bnote { display: block; font: 10.5px var(--mono); color: var(--faint); padding: 0 0 5px; }
/* The drill's rows keep the board's wide grid; what is too wide scrolls
   inside its own box — never the page. */
.bdrill-rows { overflow-x: auto; border: 1px solid var(--hair); border-radius: 5px; }

/* The standup's scope strip: whose day the board tells, said in chips —
   the narrowing is on the screen, never a hidden filter. */
.scopechip {
  font-size: 11px; padding: 2px 9px; border-radius: 999px; cursor: pointer;
  background: var(--raise); color: var(--dim); border: 1px solid var(--hair);
}
.scopechip:hover { color: var(--text); }
.scopechip.on { background: var(--hot); color: #fff; border-color: transparent; font-weight: 600; }
.scopechip .n-small { opacity: .75; font-size: 10px; margin-left: 3px; }
.teamchip {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 10.5px; padding: 1px 7px; border-radius: 999px;
  background: var(--cool-soft); color: var(--cool); border: 1px solid transparent;
}
.teamchip .x {
  border: 0; background: none; color: inherit; cursor: pointer;
  font-size: 12px; line-height: 1; padding: 0 1px; opacity: .6;
}
.teamchip .x:hover { opacity: 1; }
.scopeadd {
  font-size: 11px; padding: 2px 8px; border-radius: 999px; width: 110px;
  background: var(--bg); color: var(--text); border: 1px dashed var(--hair);
}
.scopestrip .scopenote { font: 10.5px var(--mono); color: var(--faint); }
/* Collapsed, the roster is a line of NAMES — it can shorten, never push. */
.scopestrip .scopewho {
  font-size: 11px; color: var(--dim); min-width: 0; flex: 0 1 auto;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.scopeedit {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  flex-basis: 100%; padding-top: 6px; border-top: 1px dashed var(--hair);
}
/* One row of the focus table, and everything that dresses one: the
   priority edge, the checkbox, the title and its key pill, the subtask
   wire, how a delivered row reads, the detail that opens beneath, the
   column picker in the head's last cell, the row menus, and the cells
   that are affordances.

   Served AFTER `standup.css`, which looks wrong beside the folder names
   and is not: the old single stylesheet ran head, standup, row,
   completion styles, and a row rule that comes before the standup's
   loses fights it used to win. `head.css` and this file are the table,
   either side of the standup, and the served order is the order these
   rules have always been in.

   (Until 2026-09-21 the first 114 lines of this file were in
   `surfaces/standup/standup.css`, because the old file put them inside
   the standup's section banner. A banner was not a boundary — the same
   lesson that produced three cascade fixes in this PR.) */
/* No overflow clipping: a row menu opening near the last row would be
   cut off by it. The corners are rounded on the rows themselves. */
#rows { border: 1px solid var(--line); border-top: 0; border-radius: 0 0 6px 6px; }
task-row:last-child .t-line { border-radius: 0 0 5px 5px; }

task-row { display: block; }
.t-line {
  display: grid; grid-template-columns: var(--grid);
  align-items: center; gap: 8px; min-height: var(--row-h); padding: 0 10px;
  border-bottom: 1px solid var(--hair); border-left: var(--row-edge) solid var(--line);
  background: var(--bg); font-size: 12.5px; position: relative;
}
task-row:last-child .t-line { border-bottom: 0; }
/* -- the stripe down the left of a row --------------------------------
   It is the plain hairline set on `.t-line` above, the same on every row.
   It used to be keyed on priority — a hot stripe for P1, purple for P2,
   faint for P3 — so the whole line read as tinted by how urgent it was.
   Nobody liked it (the operator, 2026-09-22: "no one likes the color coding of
   the summary by priority, just disable that"), and the board already
   says the same thing twice over: the **Pri** chip in its own column,
   which keeps its colours, and the default order, which puts the urgent
   work at the top where a reader is already looking.
   The 3px track itself stays — it is not decoration. Selecting a row
   paints it (`bulk.css`) and an overdue row hatches it (below), and both
   overlay a track that has to exist for the head's matching inset to
   line up (`head.css`). */
.t-line:hover { background: var(--panel); }
/* AN OVERDUE ROW IS A ROW WITH A RED DATE IN IT, AND NOTHING ELSE.
   There is no rule here because there is nothing left to draw.

   It went in three steps, each one the operator reading the live board.
   It began as a red wash across the whole width, so the one thing a
   reader is trying to read — the summary — sat on a coloured ground and
   looked as though the summary itself were the thing that was wrong.
   The wash came off and the row's 3px left track hatched instead, which
   is quieter but is still the ROW saying something a DATE has to say.
   Then, plainly (2026-09-23): "overdue colouring should not be on the
   task summary, it should ONLY be on the due date."

   So the only thing an overdue row has that another row does not is the
   red, bold date in its Due cell (`.cell.late`, `button.due-btn.late`,
   below). Its background, its border, its left track and its summary's
   ink are the ones every other row has, and a test measures exactly that
   (`test_board_render`). Finding overdue work at a glance is the default
   order's job — it puts the urgent work at the top, where a reader is
   already looking — not a mark down the edge of the table. */
/* `task-row.pending .t-line` and `task-row.holding .t-line` — the dimming
   of a row whose word the board has not heard back yet — live further
   down this file, immediately IN FRONT of the settled row's own dimming.
   The two selectors weigh the same, so source order decides which wins on
   a row that is both, and a row you have just ticked is both until the
   fold lands (2026-09-21 review of #272, HIGH-1;
   tests/test_dashboard_unfolded.py holds the order). */
task-row.open-detail { background: var(--raise); box-shadow: var(--shadow); }
task-row.open-detail .t-line { background: var(--raise); }

.cb { width: 14px; height: 14px; accent-color: var(--ok); cursor: pointer; }
.pri {
  font: 700 9.5px var(--mono); text-align: center; border-radius: 4px; padding: 2px 0;
  color: var(--dim); background: var(--panel); border: 1px solid var(--hair);
}
.pri.p1 { background: color-mix(in srgb, var(--hot) 20%, var(--bg)); border-color: var(--hot); color: var(--text); }
.pri.p2 { background: var(--purple-soft); border-color: var(--purple-line); color: var(--purple); }
/* The pill domain/task-key.js draws everywhere: one width whatever it says (P1,
   P4, –), so nothing beside it moves when a priority changes. */
.pri-chip {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: var(--pri-chip-w); font-variant-numeric: tabular-nums;
}

.t-title { display: flex; align-items: center; min-width: 0; overflow: hidden; white-space: nowrap; font-weight: 500; }
.t-title .t-name { overflow: hidden; text-overflow: ellipsis; }

/* -- Condensed and Expanded: what the preference MEANS ---------------- */
/* It used to mean five things at once — the row's height, the actions
   column's width, the type size, whether a description showed, and
   whether the finish button said its word — so a reader who wanted to see
   the whole of a long summary got a differently-proportioned board with
   it. Since 2026-09-21 it means exactly one thing: does the Summary cell
   wrap.

   Expanded wraps every summary. Condensed keeps one line and ends it in
   an ellipsis — and offers `.t-more`, a real button at the end of the
   cell, so THIS row's summary can be opened on its own without changing
   the board. The button only appears where the text actually does not fit
   (the row's own painter measures it: scrollWidth against clientWidth,
   surfaces/board/row.js), because an ellipsis control beside a sentence
   that is already whole is a control that does nothing.

   Asked as the positive question — `[data-density="expanded"]` — so the
   condensed side is everything else, whether the stored value says
   `condensed` (the word a reader is given) or `dense` (the old spelling).
   `lib/table/table.js` asks it the same way in JS. */
:root[data-density="expanded"] .t-title,
.t-title.opened { white-space: normal; padding: 4px 0; }
:root[data-density="expanded"] .t-title .t-name,
.t-title.opened .t-name { overflow: visible; text-overflow: clip; white-space: normal; }
/* The elbow and guides are drawn against the cell's own height, so a
   wrapped summary must not stretch them down the whole of it. */
:root[data-density="expanded"] .t-title,
.t-title.opened { align-items: flex-start; }
:root[data-density="expanded"] .t-title .wire,
.t-title.opened .wire { align-self: stretch; max-height: 1.6em; }
.t-more {
  flex: none; margin-left: 4px; padding: 0 5px; cursor: pointer;
  border: 1px solid var(--hair); border-radius: 4px; background: var(--raise);
  color: var(--faint); font: 11px var(--font-body, sans-serif); line-height: 1.4;
}
.t-more:hover, .t-more:focus-visible { color: var(--hot); border-color: var(--hot); background: var(--hot-soft); }
/* Expanded shows every summary whole, so there is nothing for it to open. */
:root[data-density="expanded"] .t-more { display: none; }
/* The subtask wire: a sleek tree — one 22px column per level, a hairline
   guide where an ancestor continues, a rounded elbow at the row's own
   level (├ with siblings below, └ for the last). */
:root { --wire: color-mix(in srgb, var(--faint) 55%, transparent); }
.t-title .wire { display: inline-flex; align-self: stretch; flex: none; margin-right: 6px; }
.t-title .w { position: relative; display: inline-block; width: 22px; align-self: stretch; }
.t-title .w.v::before { content: ""; position: absolute; left: 10px; top: -1px; bottom: -1px; border-left: 1.5px solid var(--wire); }
.t-title .w.t::before { content: ""; position: absolute; left: 10px; top: -1px; bottom: -1px; border-left: 1.5px solid var(--wire); }
.t-title .w.t::after { content: ""; position: absolute; left: 10px; top: 50%; width: 11px; border-top: 1.5px solid var(--wire); }
.t-title .w.l::before {
  content: ""; position: absolute; left: 10px; top: -1px; width: 11px; height: calc(50% + 1px);
  border-left: 1.5px solid var(--wire); border-bottom: 1.5px solid var(--wire); border-radius: 0 0 0 7px;
}
task-row:hover .t-title .w::before, task-row:hover .t-title .w::after { border-color: var(--hot); }
/* The task's key, ahead of its title: mono so it reads as an identifier,
   quiet so the title still leads, and never allowed to shrink. It is also
   the control that copies itself — and it did not look like one (the operator,
   on the deployed board, 2026-09-03: "the copy on click thing either
   doesn't work or has no visual indication"). Faint text whose only
   answer to a pointer was a colour tells nobody it is a control, and its
   hit area was the glyphs themselves. So: a pill, padding a person can
   hit without aiming, and the ⧉ the row's Copy link already wears, so
   the two read as the same act. Quiet at rest — fifty rows must not
   become a wall of buttons — and unmistakable under the pointer.
   The glyph's SPACE is always held (a flex gap and a zero-opacity
   ::after), so revealing it never shifts the title beside it, and it
   lives in a pseudo-element so the copy beat's textContent swap leaves
   it alone. */
.t-key {
  display: inline-flex; align-items: center; gap: 4px; cursor: pointer;
  border: 1px solid var(--hair); border-radius: 4px; background: var(--raise);
  letter-spacing: 0.02em;
  /* One width for every key up to POP-99999 (the operator, 2026-09-15: on the
     Done view POP-60 and POP-5474 started their titles at different
     places). Mono and tabular, so `ch` IS a character: nine of them and
     the glyph's one, their letter-spacing, the gap, the padding and the
     border. The key sits left and the ⧉ at the right edge. */
  justify-content: space-between; font-variant-numeric: tabular-nums;
  min-width: calc(10ch + 0.2em + 4px + 2 * var(--key-pad-x) + 2px);
}
/* The copy mark, drawn at all times and quietly: a control that appears
   only under the pointer is a control a reader has to already know
   about (the operator, 2026-09-22). */
.t-key::after {
  content: "⧉"; opacity: .45; transition: opacity 0.12s ease;
  display: inline-block; width: 1ch; text-align: center;
}
.t-key { flex: none; --key-pad-x: 5px; padding: 2px var(--key-pad-x); font: 10.5px var(--mono); color: var(--faint); }
/* The Task ID column (2026-09-21): the pill used to sit inside the
   Summary cell, spending 84px of the one column a reader reads sentences
   out of — and a row with no id yet started its sentence 84px to the
   left of the row above it. Its own cell, so the ids line up and the
   summaries start in one place. */
/* And the chip sits in the MIDDLE of it, across and down: a track
   holding a pill hard against its left edge reads as a ragged column of
   ids rather than a column (the operator's walk, 2026-09-22).

   The pill itself was centred and it did not LOOK centred, which is the
   only kind of centred that matters: every pill is one width (so the ids
   line up as a column) and its two parts were pushed to its two ends, so
   what a reader saw was POP-3 against the left edge with the copy mark
   a lane of white away (the operator, measured on the served page,
   2026-09-22). In this column the pill's own content is centred too, and
   the two parts stand together. Elsewhere — a task's page, the parent
   picker — the pill keeps the ends it always had. */
.cell.col-key { display: flex; align-items: center; justify-content: center; }
.cell.col-key .t-key { justify-content: center; }
/* …and the head over them says its word and its two glyphs in the middle
   of the same track, so the column reads as one thing from the heading
   down. Every other head is left, over cells that are left. */
.thead .thcell.col-key { justify-content: center; }
task-row:hover .t-key { color: var(--dim); border-color: var(--line); }
/* Scoped to task-row on purpose: `task-row:hover .t-key` outweighs a bare
   `.t-key:hover`, so the key's own hover never landed — the pointer sat on
   it and the row's rule kept answering. Half of "no visual indication"
   was that, and it was true before the pill. Equal weight, said later. */
task-row .t-key:hover, task-row .t-key:focus-visible,
.t-key:hover, .t-key:focus-visible {
  color: var(--hot); border-color: var(--hot); background: var(--hot-soft);
}
.t-key:hover::after, .t-key:focus-visible::after { opacity: 1; }
.t-key:focus-visible {
  outline: 2px solid var(--hot); outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .t-key::after { transition: none; }
}
/* The title is an `<a>` at the task's own address (domain/task-link.js),
   so ⌘-click opens a tab and a right click offers the address — and it
   has to keep reading as the row's own ink rather than as a blue link. */
/* A summary you have already opened must not change colour: the page's
   `a:visited { color: var(--link) }` (base.css) outweighs `.t-name`, so
   every task the viewer had opened turned accent pink and read as a rule
   about priority (2026-09-23). The visited state is named here so the
   summary keeps the row's ink in every state but hover. */
.t-name, .t-name:visited { cursor: pointer; color: inherit; text-decoration: none; }
.t-name:hover { color: var(--hot); text-decoration: underline; text-underline-offset: 2px; }

/* -- a finished row is struck through, whole -------------------------- */
/* What was wrong: crossing a row out used to be `text-decoration:
   line-through` on the title, and only the title — so a delivered row was
   a struck-out sentence sitting in a line of perfectly upright project
   names, dates and people, and the eye had to check each row's first
   column to know what it was looking at. Then it was a swappable registry
   of three styles behind a footer toggle (`colors`, `strikeout`,
   `faded`), which meant three answers to "what does done look like" and
   a preference nobody wanted to make.

   It is one answer now (plan 2.4, 2026-09-21): one continuous line
   across the WHOLE row. The line is a grid item of the row's own grid —
   `grid-area: 1 / 1 / 2 / -1` puts it in the row's single row and across
   every column INCLUDING the gaps between them, `align-self: center`
   parks it on the text's middle, and `pointer-events: none` keeps it out
   of the way of everything underneath. No per-cell text-decoration, no
   token bundles, nothing to choose.

   `::after` rather than `::before` because `.t-line::before` is already
   the overdue barber-pole edge above, and an element has one of each.

   Won't-do is struck through too — settled is settled — and keeps its
   own dimming, because work that was never delivered must not read like
   work that was. Only `task-row` is addressed: the standup's story cards
   carry their own ✓ idiom and are never struck through. */
/* A word the board has not heard back yet. The change stays on screen —
   the person spoke it and it is not lost — but the row must not read as
   landed while the door is unreachable. Same dimming as any unfolded
   word, and for a held word the priority edge goes dashed: the board's
   one visual word for "provisional" (.tcomment.pending, .hrow.pending). */
task-row.pending .t-line { opacity: .75; }
task-row.holding .t-line { opacity: .75; border-left-style: dashed; }

task-row.done .t-line::after, task-row.dropped .t-line::after {
  content: "";
  grid-area: 1 / 1 / 2 / -1;
  align-self: center;
  border-top: 1px solid var(--faint);
  pointer-events: none;
}
/* And the sentence itself steps back, so the line reads as the row's
   verdict rather than as damage to the title. */
task-row.done .t-name { color: var(--dim); font-weight: 400; }

.t-title .chip { margin-left: 6px; }
/* The fold chevron, beside the lineage, and the gap a childless row keeps
   in its place (POP-8540).

   The gap is not decoration. Without it the sentences of parents and of
   rows with nothing under them start at different places, which is exactly
   the raggedness the Task ID column was lifted out of this cell to cure
   (columns.js, the `key` column). One width, whichever the row is. */
.t-title > .foldbtn, .t-title > .fold-gap { flex: 0 0 auto; width: 14px; margin-right: 4px; }
.t-title > .foldbtn {
  display: inline-flex; align-items: center; justify-content: center;
  height: 16px; padding: 0; border: 0; border-radius: 3px;
  background: transparent; color: var(--dim); cursor: pointer;
  font-size: 10px; line-height: 1;
}
.t-title > .foldbtn:hover { background: var(--hot-soft); color: var(--hot); }
.t-title > .foldbtn:focus-visible { outline: 0; box-shadow: var(--ring); }
/* The held mark rides with the chips beside the title, so it takes their
   own gap: it is one more thing the summary says about the row. */
.t-title .held { margin-left: 6px; }
/* The one mark a line can wear: ⧗, your own word, recorded and not yet
   folded into the graph behind the board. It used to be nine pixels of
   faint colour carrying the only explanation of itself, and nobody found
   it (the operator, 2026-09-14: "I didn't think there were tooltips at
   first because the mouseover area is quite small and unobvious"). It is
   a 22px disc now, in the soft wash its own chips already use: prominent
   enough to be seen, a target a pointer can hit without aiming, and still
   one glyph wide, so the title cell is no wider on a phone than it was.

   A second mark rode beside it until 2026-09-21 — the brain's word,
   waiting for somebody to agree with it. The brain's unconfirmed work is
   a section of the board now (Agent suggested), so the mark has
   nothing left to say and is gone from every surface. */
.mark {
  display: inline-flex; align-items: center; justify-content: center;
  flex: none; min-width: 22px; height: 22px; margin-left: 6px;
  border-radius: 999px; font-size: 13px; line-height: 1;
}
.mark.unfolded { color: var(--cool); background: var(--cool-soft); }

/* No cell may widen the row. A grid item's automatic minimum size is its
   own content, so one unbroken email in a 96px track pushed the table 70px
   past the board and put a horizontal scrollbar under a board that fit.
   Every child of the two grids says min-width: 0 and clips; the tracks are
   the agreement, and the content lives inside them. */
.t-line > *, .thead > * { min-width: 0; }
.st, .th, .pri-btn { overflow: hidden; text-overflow: ellipsis; }
.cell { color: var(--dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; }
.cell.mono { font-family: var(--mono); }
.cell.late { color: var(--bad-ink); font-weight: 700; }
.cell.today { color: var(--text); font-weight: 600; }
.cell.none { color: var(--faint); }

/* -- where the work stands, in colour --------------------------------- */
/* One class per state (domain/status.js STATUS_CLASSES) and one colour
   token per state (STATUS_TOKENS, declared in palette.css and lifted for
   the dark skin in theme-dark.css). Each class sets two local properties
   and the two shared rules below read them, so adding a state is one row
   in each table and never a new rule here.

   What was wrong (a user test, 2026-09-21): "Confirm done" — the state that
   means the brain believes this finished and no person has said so — was
   drawn in the same green as Done. Half the green dots on the board were
   questions, so the colour said "delivered" about work nobody had
   confirmed. It is amber now: waiting on a person. Blocked, which had the
   amber, takes the red — it is the state that needs somebody. In progress
   is the brand's blue rather than the purple it shared with the p2
   priority chip sitting two columns away. */
.st { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; color: var(--st-ink, var(--dim)); white-space: nowrap; }
.st .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--st-hue, var(--faint)); flex: none; }
.st.not_started { --st-hue: var(--st-neutral); --st-ink: var(--dim); }
.st.in_progress { --st-hue: var(--st-blue); --st-ink: var(--st-blue-ink); }
.st.blocked { --st-hue: var(--st-red); --st-ink: var(--st-red-ink); }
/* `review` is what the sheet calls review_if_done (domain/status.js). */
.st.review { --st-hue: var(--st-amber); --st-ink: var(--st-amber-ink); font-weight: 600; }
.st.complete { --st-hue: var(--st-green); --st-ink: var(--st-green-ink); }
.st.cancelled { --st-hue: var(--st-grey); --st-ink: var(--faint); }

/* Row actions, pinned: a grid item in the last column. Nothing about the
   row's geometry changes on hover — only emphasis does. */
.rowacts {
  display: flex; align-items: center; justify-content: flex-end; gap: 2px;
  min-width: 0;
}
.ract {
  height: 22px; padding: 0 8px; border-radius: 4px; cursor: pointer;
  border: 1px solid var(--line); background: var(--raise);
  font: 600 10.5px var(--font-body, sans-serif); color: var(--text);
  display: inline-flex; align-items: center; gap: 4px; white-space: nowrap;
}
/* Emphasis, not geometry: at rest the pinned buttons sit back; the hovered
   or keyboard-focused row brings them forward. Same box either way. Carried
   by inherited custom properties rather than by more specific selectors, so
   `.ract:hover` below still wins on the button the pointer is actually on. */
.rowacts .ract { background: var(--act-bg, transparent); color: var(--act-ink, var(--dim)); border-color: var(--act-line, var(--hair)); }
.t-line:hover .rowacts,
.t-line:focus-within .rowacts { --act-bg: var(--raise); --act-ink: var(--text); --act-line: var(--line); }
.ract:hover { border-color: var(--ok); background: var(--ok-soft); color: var(--ok-ink); }
/* View is an ANCHOR wearing the button (`row.js`), so the browser's own
   link behaviours all work — ⌘-click and middle-click open a tab,
   right-click offers "open in new tab" and "copy link address". These
   two rules are only what an anchor needs that a button does not. */
.ract.view-act { text-decoration: none; }
.ract.view-act:visited { color: var(--act-ink, var(--dim)); }
/* Expand: the details under this row, here, without leaving the board.
   It was a bare chevron in a 26px track of its own past the last column
   — a shape you had to already know, sitting apart from every labelled
   thing a row can do (the operator, 2026-09-22). It is a word in the
   cluster now, the same box as View beside it, so the row's four
   controls read as four controls, and it LEADS them: the button that
   shows you more of the row you are already on comes before the one
   that takes you off the board (the operator, 2026-09-22).

   The word does not change when the panel opens — the cluster stands in
   one fixed track, and a label that grows by two characters on every
   press would shove the buttons next to it sideways. `aria-expanded` is
   what changes, and the open row's button is held forward so the eye has
   the state too. */
.ract.disc-act[aria-expanded="true"] {
  border-color: var(--hot); color: var(--hot); background: var(--hot-soft);
}
/* (Condensed used to hide the button's word and narrow its padding. The
   preference means one thing now — does the Summary cell wrap — so the
   finish button says ✓ Complete at either setting.) */

/* (An escape hatch used to live here: `data-actions="hover"` dropped the
   actions column out of the grid and floated the buttons back over the
   row's right edge, behind a gradient, on a pointer-width screen. The
   preference it read is gone as of 2026-09-21 — the actions are pinned,
   full stop — so the rules that answered it are gone with it rather than
   waiting for an attribute nothing stamps.) */

/* -- detail (expand in place under a table row) ----------------------- */
.detail {
  margin: 0; padding: 10px 14px 12px 46px;
  border-bottom: 1px solid var(--hair); background: var(--raise);
  box-shadow: inset 3px 0 0 0 var(--hot);
}
.detail p.desc { color: var(--dim); white-space: pre-wrap; margin-bottom: 8px; font-size: 12.5px; }
.detail .facts { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.detail .verdicts { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
/* The line for adding a subtask from a row's expanded detail. Every
   selector says `.detail` first, and has to: `.sub-add` is also the class
   a task page puts round its own line, this sheet is served before that
   one, and an unscoped `.sub-add input` here was capping that page's box
   at 420px in the middle of a 880px column — the same shape of defect as
   the `.prop` collision next door. A rule that does not say which part it
   belongs to belongs to all of them. */
.detail .sub-add { display: flex; gap: 6px; margin-top: 8px; }
.detail .sub-add input {
  /* No cap: a box for a summary takes the width of its column, here as
     everywhere else (the 2026-09-23 walk). */
  flex: 1; min-width: 0; height: 28px; padding: 0 9px; border: 1px solid var(--line);
  border-radius: 7px; background: var(--bg); color: var(--text); font: 12px var(--font-body, sans-serif);
}
.detail .sub-add input::placeholder { color: var(--faint); }
.detail .sub-add .btn { height: 28px; }

.empty { color: var(--faint); padding: 40px 0; text-align: center; }
/* The waiting line: a board that has not been answered yet says so — the
   one spinner (lib/loading.css) beside the words, never a full-page
   takeover. The ring itself is not drawn here: every surface that waits
   draws the same one, so it lives in one file. */
.empty.waiting { display: flex; align-items: center; justify-content: center; gap: 8px; }
/* -- the column picker ------------------------------------------------ */
/* It lives in the head's own last cell — the one that stands over the
   row's action cluster and names no column, which is the one place in
   the head that is about the TABLE rather than about a column. (It had a
   26px cell of its own until 2026-09-22, over the rows' disclosure
   chevron; the chevron became the Expand button in the cluster and the
   track went with it.) Right-aligned, like the buttons under it. */
.th-cols { position: relative; overflow: visible; }
.thead .th-acts { justify-content: flex-end; }
.colpick {
  border: 0; background: none; color: var(--faint); cursor: pointer;
  font-size: 14px; line-height: 1; padding: 2px 4px; border-radius: 4px;
}
.colpick:hover { color: var(--text); background: var(--raise); }
.colmenu {
  position: absolute; top: 24px; right: 0; z-index: 20; min-width: 190px;
  padding: 6px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--panel); box-shadow: 0 10px 28px rgb(0 0 0 / 28%);
  text-align: left; white-space: nowrap; cursor: default;
  /* Out from under the head's own type: .th is 9.5px uppercase mono with
     letter-spacing, which is right for a column label and wrong for a
     menu of sentences. */
  font: 400 12.5px var(--font-body, "Inter", system-ui, sans-serif);
  text-transform: none; letter-spacing: normal; color: var(--text);
}
.colmenu-head { padding: 2px 6px 6px; font-weight: 600; }
.colmenu-row {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 6px; border-radius: 5px; cursor: pointer;
}
.colmenu-row:hover { background: var(--raise); }
.colmenu-row .dim { margin-left: auto; font-size: 11px; }
.colmenu-reset {
  width: 100%; margin-top: 4px; padding: 5px 6px; cursor: pointer;
  border: 0; border-top: 1px solid var(--line); border-radius: 0 0 5px 5px;
  background: none; color: var(--dim); font: inherit; text-align: left;
}
.colmenu-reset:hover { color: var(--text); background: var(--raise); }

/* -- inline editing in the detail ------------------------------------- */
.editrow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
.editrow .due-edit {
  height: 24px; padding: 0 6px; border: 1px solid var(--line); border-radius: var(--ctl-radius);
  background: var(--raise); color: var(--text); font: 11px var(--font-body, sans-serif);
}
.own-edit { display: inline-flex; gap: 4px; }
.own-edit input {
  height: 24px; width: 180px; padding: 0 8px; border: 1px solid var(--line);
  border-radius: var(--ctl-radius); background: var(--raise); color: var(--text);
  font: 11px var(--font-body, sans-serif);
}
.own-edit .btn { height: 24px; padding: 0 9px; font-size: 11px; }
.q-type.waiting { background: color-mix(in srgb, var(--c-navy-500, #1D2E66) 8%, var(--bg)); border-color: color-mix(in srgb, var(--c-navy-500, #1D2E66) 26%, var(--bg)); color: var(--dim); }

/* -- provenance: what the work is about, and where it came from ------- */
.chip.regarding {
  background: var(--purple-soft); color: var(--purple); border-color: transparent;
  max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  display: inline-block; vertical-align: bottom;
}
.t-title .chip.regarding { margin-left: 8px; font-size: 10.5px; }

/* -- won't do: settled, and never mistaken for delivered -------------- */
/* The strike is the whole row's, above, and shared with Done — both are
   settled. What is a won't-do row's alone is the dimming: work that was
   decided against was never delivered, and the two endings must not read
   alike. */
task-row.dropped .t-name { color: var(--faint); font-weight: 400; }
task-row.dropped .t-line { opacity: .72; }
task-row.dropped .pri { opacity: .5; }
/* (What a cancelled status cell looks like is `.st.cancelled` above, with
   the other five states. It used to be answered twice, and the copy down
   here — written before the state classes existed — won by specificity.) */
.droprow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.drop-why { display: flex; gap: 6px; flex: 1; min-width: 260px; }
/* The reason prompt put under a row the row menu dropped from. */
task-row > .why-prompt { margin: 4px 0 8px 40px; }
/* "Are you sure?" — the same prompt with the field taken out
   (lib/reason.js). One line: the question, the yes, the way out. */
.sure-ask { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.sure-ask .sure-q { font-size: 12.5px; color: var(--text); }
.drop-why input {
  flex: 1; max-width: 420px; height: 28px; padding: 0 9px; border: 1px solid var(--line);
  border-radius: 7px; background: var(--bg); color: var(--text); font: 12px var(--font-body, sans-serif);
}
.btn.drop:hover { border-color: var(--bad); color: var(--bad); }
.btn.drop.on { background: var(--bad-soft); border-color: var(--bad-line); color: var(--bad-ink); }

/* -- split action + row menus ----------------------------------------- */
.t-line { position: relative; }
.split { display: inline-flex; }
.split .ract { border-radius: 4px 0 0 4px; }
/* The halves share one 1px seam: the caret keeps its own left border
   tucked under its neighbor, and the hovered half raises so its border
   paints on all four sides. (`border-left: 0` here was the defect — a
   hovered caret drew a highlight box with no left edge, which read as
   one button overlapping the other.) */
.split .caret { border-radius: 0 4px 4px 0; margin-left: -1px; padding: 0 5px; }
.split .ract:hover { position: relative; z-index: 1; }
.pri-btn { cursor: pointer; }
.pri-btn:hover { border-color: var(--purple); color: var(--purple); }

.menu {
  position: absolute; top: calc(100% - 3px); right: 10px; z-index: 30;
  min-width: 170px; padding: 4px; border-radius: 8px;
  background: var(--raise); border: 1px solid var(--line); box-shadow: var(--pop-shadow);
}
.menu.pri-menu { right: auto; left: 34px; min-width: 64px; }
.menu .item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 6px 9px; border: 0; border-radius: 5px; background: none;
  color: var(--text); font: 12px var(--font-body, sans-serif); text-align: left; cursor: pointer;
}
.menu .item:hover { background: var(--hot-soft); }
.menu .item.on { background: var(--purple-soft); color: var(--purple); font-weight: 600; }
.menu .item .dim { margin-left: auto; color: var(--faint); font-size: 10.5px; }
.menu-sep { height: 1px; margin: 4px 6px; background: var(--hair); }

.menu.panel { min-width: 280px; }
.suggest-in {
  width: 100%; height: 28px; padding: 0 9px; margin-bottom: 4px;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--bg); color: var(--text); font: 12px var(--font-body, sans-serif);
}
.suggest-list { max-height: 220px; overflow-y: auto; }

/* -- a description the board clipped says so, and opens -------------- */
.rowdesc {
  display: flex; align-items: flex-start; gap: 6px;
  padding: 0 10px 7px 63px; margin-top: -3px;
  border-bottom: 1px solid var(--hair); background: inherit;
}
task-row:last-child .rowdesc { border-bottom: 0; }
.rowdesc .disclose {
  flex: none; border: 0; background: none; cursor: pointer; padding: 0 2px;
  color: var(--faint); font-size: 11px; line-height: 1.5;
}
.rowdesc .disclose:hover { color: var(--hot); }
.rowdesc .preview {
  color: var(--faint); font-size: 11.5px; line-height: 1.5; cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rowdesc .preview:hover { color: var(--dim); }
.rowdesc .md { font-size: 12px; color: var(--dim); max-width: 82ch; }
.rowdesc .loading { color: var(--faint); font-style: italic; }
task-row.picked .rowdesc { background: var(--hot-soft); }
:root:not([data-density="expanded"]) .rowdesc { display: none; }

/* -- the due cell is an affordance ------------------------------------ */
button.due-btn {
  border: 0; background: none; padding: 0; cursor: pointer; text-align: left;
  color: var(--dim); font: inherit; font-family: var(--mono); font-size: 11px;
}
button.due-btn:hover { color: var(--hot); text-decoration: underline; text-underline-offset: 2px; }
button.due-btn.late { color: var(--bad-ink); font-weight: 700; }
button.due-btn.today { color: var(--text); font-weight: 600; }
button.due-btn.none { color: var(--faint); }
/* The due cell opens the shared date popover now (POP-9246,
   `domain/value-pop.js`), so what is left here is the ANCHOR: under the
   row, at the column's own x, which `wireMenu` measures at open. The box
   itself is `.popover` in `domain/prop-menu.css`, which is the whole
   point of the change — one box wherever a date is set. */
.row-due-pop { top: 100%; right: auto; width: 240px; } /* left is measured at open */

/* -- every cell edits its own value ----------------------------------- */
/* Click or right-click a cell to change what it says: move the task to
   another project (or a brand-new one), change where it stands, hand it
   to somebody — without opening the task (a teammate, 2026-08-26).
   Filtering by a column is the column HEAD's job (head.css); a cell that
   offered "⊕ Filter: Acme" as the first line of its menu is how a person
   trying to move one task narrowed the whole board instead (plan 2.4,
   2026-09-21). */
button.proj-btn {
  border: 0; background: none; padding: 0; cursor: pointer; text-align: left;
  font: inherit; font-size: 11px;
}
button.proj-btn:hover { color: var(--hot); text-decoration: underline; text-underline-offset: 2px; }
button.st-btn { border: 0; background: none; padding: 0; cursor: pointer; text-align: left; font: inherit; }
button.st-btn:hover { color: var(--hot); }
/* The Person cell is the same kind of control, and the chip IS the button
   (domain/person.js `tag`) rather than a chip inside one, so there is one
   explanation of it and not two. */
button.own-btn {
  border: 0; background: none; padding: 0; cursor: pointer; text-align: left;
  font: inherit; font-size: 11px;
}
button.own-btn:hover { color: var(--hot); text-decoration: underline; text-underline-offset: 2px; }
.menu.proj-menu { right: auto; min-width: 240px; } /* left is measured at open */
/* The state list marks where the work stands with ● and offers the rest
   with ○, so a reader can see the answer and change it in one look. */
/* The status menu's box. Its CONTENTS are the task page's menu, drawn by
   `domain/status-menu.js` and dressed by `domain/prop-menu.css`; what
   this rule owns is where the box hangs and that its options stack the
   way they do in the rail. */
.menu.st-menu {
  right: auto; min-width: 210px; /* left is measured at open */
  display: flex; flex-direction: column; gap: 1px; padding: 5px;
}
/* An option's state mark, at the menu's size rather than a table's: the
   `.st` pair is 11px where it stands in a status CELL, and a menu row is
   not a table row. Its colours are its own and come with it. */
.st-menu .opt .st { font-size: inherit; }
.menu .item .pick { flex: none; width: 1em; text-align: center; color: var(--faint); }
.menu .item.on .pick { color: inherit; }

/* -- renaming a task from the board ----------------------------------- */
/* A plain click on the summary opens this; ⌘-click and the row's own
   View button still go to the task's page. */
.t-edit {
  flex: 1; min-width: 0; height: 22px; padding: 0 6px;
  border: 1px solid var(--hot); border-radius: var(--ctl-radius);
  background: var(--bg); color: var(--text);
  font: 500 12.5px var(--font-body, sans-serif);
}

/* -- done earlier: a completion carries the person's own day ---------- */
/* The status menus' pickrow, the due menu's own idiom in the acts menu. */
.acts-menu .pickrow.doneon {
  display: flex; align-items: center; gap: 8px; padding: 4px 9px 2px;
  font: 11.5px var(--font-body, sans-serif); color: var(--faint); white-space: nowrap;
}
.acts-menu .fin-edit {
  flex: 1; height: 26px; padding: 0 6px; border: 1px solid var(--line); border-radius: var(--ctl-radius);
  background: var(--bg); color: var(--text); font: 11.5px var(--font-body, sans-serif);
}
/* The panel's completion date sits beside Due, dressed exactly like it. */
.editrow .fin-edit {
  height: 24px; padding: 0 6px; border: 1px solid var(--line); border-radius: var(--ctl-radius);
  background: var(--raise); color: var(--text); font: 11px var(--font-body, sans-serif);
}
/* The ask's optional day: quiet beside Confirm, the quickact's height. */
.q-date {
  height: 22px; padding: 0 5px; border: 1px solid var(--line); border-radius: var(--ctl-radius);
  background: var(--raise); color: var(--dim); font: 10.5px var(--font-body, sans-serif);
}
.sdate {
  height: 20px; padding: 0 4px; border: 1px solid var(--hair); border-radius: var(--ctl-radius);
  background: transparent; color: var(--dim); font: 10px var(--font-body, sans-serif);
  opacity: .75;
}
.sline:hover .sdate, .sdate:focus-visible, .sdate:focus { opacity: 1; border-color: var(--line); background: var(--raise); color: var(--text); }
/* The bulk sweep's shared day. */
.bulk-panel.done-on { flex-direction: row; align-items: center; max-width: 520px; }
.bulk-panel.done-on .mlab {
  font: 9.5px var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint); white-space: nowrap;
}
.bulk-panel.done-on input[type="date"] {
  height: 26px; padding: 0 6px; border: 1px solid var(--line); border-radius: var(--ctl-radius);
  background: var(--bg); color: var(--text); font: 11.5px var(--font-body, sans-serif);
}

/* -- the object chip is not the project chip -------------------------- */
.chip.regarding {
  background: transparent; color: var(--purple);
  border: 1px dashed var(--purple-line); border-radius: 4px;
}
.t-title .chip.regarding { font-style: italic; }

/* -- editing a description in place ----------------------------------- */
.desc.md.editable { cursor: text; border-radius: 4px; padding: 2px 4px; margin-left: -4px; }
.desc.md.editable:hover { background: var(--panel); outline: 1px dashed var(--line); }
.desc.addesc {
  border: 1px dashed var(--line); background: none; border-radius: 6px; cursor: pointer;
  padding: 4px 10px; color: var(--faint); font: 11.5px var(--font-body, sans-serif); margin-bottom: 8px;
}
.desc.addesc:hover { border-color: var(--hot); color: var(--hot); }
.desc-edit {
  width: 100%; max-width: 82ch; min-height: 84px; margin-bottom: 8px; padding: 8px 10px;
  border: 1px solid var(--hot); border-radius: var(--ctl-radius); resize: vertical;
  background: var(--bg); color: var(--text); font: 12.5px/1.55 var(--font-body, sans-serif);
}
/* -- the fold's own progress: honesty, drawn ------------------------- */
fold-progress { display: block; }
fold-progress .prog {
  display: flex; flex-direction: column; gap: 7px;
  padding: 9px 12px; margin-bottom: 10px;
  border: 1px solid var(--line); border-radius: 6px; background: var(--panel);
}
.prog-row {
  /* Fixed, not max-content: each row is its own grid, and the two rows'
     tracks must start at the same x. Sized to the longer word. */
  display: grid; grid-template-columns: 108px 1fr auto;
  gap: 12px; align-items: center; min-width: 0;
}
.prog-word {
  font: 600 10.5px var(--font-body, sans-serif); color: var(--dim);
  text-transform: uppercase; letter-spacing: 0.06em; white-space: nowrap;
}
.prog-track {
  height: 6px; border-radius: 3px; background: var(--raise);
  box-shadow: inset 0 0 0 1px var(--hair); overflow: hidden; min-width: 60px;
}
.prog-fill {
  display: block; height: 100%; border-radius: 3px;
  background: var(--purple); position: relative;
  transition: width 0.9s ease;
}
.prog-fill.interpret { background: var(--hot); }
/* A slow sheen says "alive" between polls; stillness for those who asked. */
.prog-fill::after {
  content: ""; position: absolute; inset: 0; border-radius: 3px;
  background: linear-gradient(100deg, transparent 30%,
    color-mix(in srgb, var(--panel) 45%, transparent) 50%, transparent 70%);
  background-size: 220% 100%;
  animation: prog-sheen 2.6s linear infinite;
}
@keyframes prog-sheen {
  from { background-position: 180% 0; }
  to { background-position: -80% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .prog-fill { transition: none; }
  .prog-fill::after { animation: none; }
}
.prog-nums { font: 10.5px var(--mono); color: var(--dim); white-space: nowrap; }
.prog-row.settled .prog-nums { color: var(--ok-ink); font-weight: 600; }
/* A scoped reread is its own shape: green, and segmented — one cell per
   capture — so two captures read as two steps, never as a stalled bar. */
.prog-fill.reread { background: var(--ok); }
.prog-track.segmented {
  display: flex; gap: 3px;
  background: transparent; box-shadow: none; overflow: visible;
}
.prog-seg {
  flex: 1; border-radius: 2px; min-width: 3px;
  background: var(--raise); box-shadow: inset 0 0 0 1px var(--hair);
}
.prog-seg.done { background: var(--ok); box-shadow: none; }
.prog-seg.now {
  background: color-mix(in srgb, var(--ok) 40%, var(--raise));
  animation: seg-pulse 1.6s ease-in-out infinite;
}
@keyframes seg-pulse {
  0%, 100% { opacity: 0.45; }
  50% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .prog-seg.now { animation: none; opacity: 1; }
}
.prog-row.stopped .prog-nums { color: var(--bad-ink); font-weight: 600; }
.prog-stale { color: var(--bad-ink); }
/* The honest down-state: the board itself cannot be read. Same family as
   the journey rows — amber, full-width, breathing while the page retries.
   The board rows beneath stay drawn: stale and labeled, never a false 0. */
.prog-row.down .prog-word { color: var(--warm); }
.prog-row.down .prog-nums { color: var(--warm); font-weight: 600; }
.prog-fill.down {
  width: 100%; background: var(--warm);
  animation: pulse 1.6s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .prog-fill.down { animation: none; }
}
@media (max-width: 768px) {
  .prog-row { grid-template-columns: 1fr auto; }
  .prog-word { display: none; }
  .prog-frac { display: none; } /* percent + ETA carry it; the counts live in the title */
}

.tfoot {
  display: flex; align-items: center; gap: 12px; padding: 8px 4px;
  font-size: 10.5px; color: var(--faint);
}
.tfoot .grow { flex: 1; }
.legend { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.legend span { display: inline-flex; align-items: center; gap: 5px; }
.legend .sw { width: 3px; height: 12px; border-radius: 2px; display: inline-block; }
/* No swatches at all. The priority ones (`.s1`–`.s4`) went with the
   priority tint on a row's left edge on 2026-09-22, and the overdue
   hatch's went with the hatch on 2026-09-23 — a key to a code nothing
   paints is worse than no key. `.legend` and `.sw` above are kept:
   they are the shape the next legend takes, if a row ever wears a
   mark again. */
/* -- chips ------------------------------------------------------------ */
.chip {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 10.5px; padding: 1px 7px; border-radius: 999px;
  background: var(--panel); color: var(--dim); border: 1px solid var(--hair);
  text-decoration: none;
}
.chip.subcount { background: var(--cool-soft); color: var(--cool); border-color: transparent; }
.chip.under { color: var(--faint); cursor: pointer; max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: inline-block; }
.chip.under:hover, .chip.under:focus-visible { color: var(--hot); border-color: var(--hot); }
/* A priority held up by a subtask: the served rank is the child's, and
   the chip names the child. Faint like `under` — a caption, not a mark. */
.chip.held { color: var(--faint); cursor: pointer; max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chip.held:hover, .chip.held:focus-visible { color: var(--hot); border-color: var(--hot); }
.chip.held .tlink { color: inherit; text-decoration: none; }
.chip.agent { background: var(--cool-soft); color: var(--cool); border-color: transparent; }
/* A delivered chip carries the link to what was handed over; it reads as
   part of the chip rather than as a stray blue word. */
.chip.agent a { color: inherit; text-decoration: underline; }
.chip.agent .pulse {
  width: 6px; height: 6px; border-radius: 50%; background: var(--cool);
  animation: pulse 1.6s ease-in-out infinite;
}
@keyframes pulse { 50% { opacity: .25; } }
.chip.perma { font-family: var(--mono); }

/* -- a tag is a link -------------------------------------------------- */
/* Chips that DO something look like it: a tag narrows the board to the
   work filed under that idea, so it takes the hot colour on hover the
   way every other actionable chip here does. */
button.chip.tagchip { cursor: pointer; font: inherit; font-size: 10.5px; }
button.chip.tagchip:hover, button.chip.tagchip:focus-visible { color: var(--hot); border-color: var(--hot); }
.tagset { display: flex; flex-wrap: wrap; gap: 6px; }
.tagrow { display: inline-flex; align-items: center; gap: 2px; }
.tagrow .x { color: var(--faint); font-size: 11px; line-height: 1; padding: 0 2px; }
.tagrow .x:hover, .tagrow .x:focus-visible { color: var(--bad); }

/* -- the blocked mark, readable at a glance --------------------------- */
/* A task waiting on an unfinished prerequisite rides a pause mark after
   its state word (`domain/status.js` `blockedMark`). Drawn as the bare
   glyph at the state's 11px it was easy to miss (the operator,
   2026-09-28, POP-8069), so it is a red chip that says the word. One
   rule, so every surface drawing the mark (board rows, linked lists,
   sub-tasks, flow cards) gets it at once; the word rides in CSS so no
   caller changed. */
.st.bmark {
  gap: 3px; padding: 0 6px; border-radius: 999px; font-weight: 600;
  color: var(--st-red-ink);
  background: color-mix(in srgb, var(--st-red) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--st-red) 55%, transparent);
}
.st.bmark::after { content: "blocked"; }
/* The held mark and its popover: why a queued task has not been picked up
   (POP-8540, `domain/held.js`).

   One shape everywhere — a static, filled pause badge — and three colours
   by what the task is waiting on. The colours are roles, not values
   (`theme-derived.css`): grey for a wait on the clock, blue for a wait on
   other work, amber for a wait on a person, each aliased to a token the
   two skins already define so neither needs a table of its own.

   STATIC, and that is the one thing about this mark that is load-bearing.
   The same boards draw a RUNNING indicator a few pixels away — a filled
   dot that pulses — and a second moving mark would read as a second kind
   of activity. A held task is the opposite of activity. */
.held {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; padding: 0; flex: 0 0 auto;
  border: 0; border-radius: 4px; background: transparent;
  color: var(--held-time); cursor: pointer; vertical-align: middle;
}
/* `flex: none` because on a held line the badge is a flex item beside a
   sentence that ellipses, and a flex item shrinks by default: a long
   reason squeezed the badge smaller than a short one's (POP-9247). */
.held svg { width: 13.5px; height: 13.5px; flex: none; }
.held.is-time { color: var(--held-time); }
.held.is-work { color: var(--held-work); }
.held.is-person { color: var(--held-person); }
.held:hover { background: var(--hot-soft); }
.held:focus-visible { outline: 0; box-shadow: var(--ring); }

/* THE SAME MARK, SAYING ITS SENTENCE OUT LOUD (POP-9247). On a flow card
   the mark is a full-width line — badge, then the reason — because a phone
   has no hover and a reason nobody can reach is a reason nobody has. One
   line and ellipsized: the card is a glance, the full text is still in the
   tooltip and in the popover a press opens.

   Only where a caller asks for it (`heldMark(row, ctx, { words: true })`).
   The board table and a subtask line give this mark a cell a few
   characters wide, and they keep the badge alone. */
.held.held-line {
  width: 100%; height: auto; min-height: 16px;
  justify-content: flex-start; gap: 5px;
  padding: 0; text-align: left; font: inherit;
}
.held-say {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 10.5px; line-height: 1.3; color: inherit;
}

/* And the mark a queued card nothing holds wears (`pickupMark`). A span
   rather than a button, because it hides nothing behind a press — so it
   takes neither the pointer nor the button reset above. Its colour is the
   age band the card is already in (`surfaces/flow/flow.css`), which is why
   there is no `is-pickup` colour here. */
span.held { cursor: default; }
span.held:hover { background: transparent; }

/* The popover, at body level for the reason the tooltip card is: every
   cell on the board clips its overflow, and a panel drawn inside one would
   be sliced off at the cell's edge.

   It exists because the board's one tooltip cannot do this job alone. That
   card draws TEXT (`lib/tip.js` sets `textContent`), which is right for
   every other explanation here and cannot hold the LINK to the task doing
   the holding — and a phone has no hover at all, so on the surface where
   this mark matters most the words have to arrive on a tap. */
.heldpop {
  position: fixed; z-index: 60; max-width: min(320px, calc(100vw - 16px));
  display: flex; flex-direction: column; gap: 6px;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--panel); box-shadow: var(--pop-shadow);
  font: 400 12px var(--font-body, sans-serif); color: var(--text);
}
.heldpop[hidden] { display: none; }
.heldpop-say { line-height: 1.35; }
.heldpop-link { align-self: start; font-weight: 600; color: var(--link); }

/* No act button here (POP-9296). POP-8631 gave the two due-date reasons one
   press - it set the due date to today - and styled it `heldpop-act`. The
   due-date gate is retired, no surviving reason has a one-press fix, and the
   rule went with the button rather than being left for a reason that might
   want it later. */
/* -- what a glyph means, and where it says it -------------------------- */
/* The operator, 2026-09-14: "make the glyphs next to things (statuses like that)
   with the tooltips more prominent, and make the entire element containing
   them hold the tooltip. I didn't think there were tooltips at first
   because the mouseover area is quite small and unobvious. make the cursor
   change too."

   So the explanation belongs to the ELEMENT — the whole chip, mark or
   button — and never to the few pixels its glyph paints. Three rules carry
   that, and lib/tip.js draws the card:

   1. a real target: at least 22px tall, whatever the type size inside it;
   2. one edge under the pointer or keyboard focus, drawn in the element's
      OWN ink at a third strength, so a mint chip does not turn grey to say
      "there are words here" — the one-border rule (PR #196) at chip scale,
      as an inset shadow so nothing moves when it appears;
   3. a cursor that says which kind of element this is: `help` where it only
      explains, `pointer` where it also acts. Never `help` on a control —
      which is why the rule keys on the attribute lib/tip.js writes, not on
      each call site remembering. */
[data-tip] { cursor: help; }
[data-tip][data-tip-acts] { cursor: pointer; }
/* `.linkbtn` was on this list and had almost no subject: on main all but
   ONE of them still wore a native `title`, so all but one never matched
   `[data-tip]`. Step 6 gave sixteen of them one on the planning sheet
   alone, and the rule found them — a text link is 13px tall and this
   made it 22, which pushed the questions band 5px down and with it the
   head row and every row under it, and grew three planning cards by 7-9.
   Nothing in that diff said `display: flex`; the change arrived by a
   pre-existing rule finding new matches (2026-09-21 review, MED-1).

   A word you read is not a chip. The target rule is for the things that
   ARE chip-shaped — a pill, a mark, a status — and a link inside a
   sentence keeps the height of the sentence.

   `.hreceipt` is named because it is the ONE `.linkbtn` that already
   carried a tip on main, and so the one this rule was already sizing: a
   history row's `#39` is a receipt chip wearing the link's ink, and it
   keeps the 22px target it has always had. */
:is(.chip, .mark, .st, .hreceipt, .plan-person, .plan-order, .plan-progress, .plan-due, .carried-word)[data-tip] {
  display: inline-flex; align-items: center; min-height: 22px;
}
.st[data-tip] { border-radius: 4px; }
[data-tip]:hover { box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 40%, transparent); }
[data-tip]:focus-visible { outline: 0; box-shadow: inset 0 0 0 1px currentColor, var(--ring); }

/* The card. It is drawn at the end of <body> (lib/tip.js) rather than as a
   ::after on the element, because the row's title cell, every table cell
   and the planning cards clip their overflow — a tip drawn inside one
   would be sliced off at the cell edge. It wraps at about 36 characters,
   never takes a pointer event, and lib/tip.js keeps it inside the viewport
   on all four sides. */
.boardtip {
  position: fixed; z-index: 90; pointer-events: none;
  max-width: 36ch; padding: 6px 9px;
  border-radius: var(--ctl-radius); border: 1px solid var(--tip-line);
  background: var(--tip-bg); color: var(--tip-ink);
  font: 11.5px/1.45 var(--font-body, sans-serif); font-weight: 400;
  box-shadow: 0 6px 18px rgb(0 0 0 / 0.28);
  white-space: pre-wrap; overflow-wrap: anywhere;
}
/* -- responsive ------------------------------------------------------- */
/* Columns are no longer yielded HERE. Which ones stand is decided in
   store.standingColumns() against the board's measured width, and the
   cells for the ones that do not stand are never rendered — so a track
   list and a `display: none` rule can no longer drift apart, which is
   what let ten columns crowd a 200px title at 1167px. What stays below
   is the page furniture, which is genuinely about the WINDOW. */
/* Phones. Two separate defects live at this width and both are the same
   lie about space: the top bar used to push the whole PAGE sideways, and
   the rail took 220 of 375. The bar wraps, the rail yields, and what is
   still too wide (the table) scrolls inside .board — never the page. */
@media (max-width: 768px) {
  .bar {
    flex-wrap: wrap; row-gap: 8px;
    padding: calc(8px + env(safe-area-inset-top)) calc(10px + env(safe-area-inset-right))
      8px calc(10px + env(safe-area-inset-left));
  }
  .bar h1 { font-size: 15px; }
  /* The bar is the title on the left and the gear and ☰ tucked at the
     right, and nothing else (POP-8069): the live pulse, the counts, the
     timings, the delta, the view caption and the header's light/dark
     toggle all go — Color mode is in the gear menu at this width. */
  #caption, #beat, #stats, #timing, #delta, mp-theme-toggle { display: none; }
  /* ☰ at the left, on the side the drawer slides out from; the title
     centered between it and the gear; both icons bare, no border or
     ground, at a finger's 44px (the operator at local review, POP-8069). */
  .bar h1 { flex: 1 1 auto; margin: 0; text-align: center; }
  .railmenu { display: inline-flex; }
  .bar .railmenu, .bar settings-menu .gear {
    width: 44px; min-width: 44px; height: 44px; min-height: 44px; padding: 0;
    border: 0; border-radius: 10px; background: transparent; color: var(--text);
  }
  .bar .railmenu:hover, .bar settings-menu .gear:hover { background: var(--hot-soft); }
  .bar .railmenu .icon, .bar settings-menu .gear .icon { width: 22px; height: 22px; }
  /* The search on a full row of its own under them. The rule has to land
     on the bar's own flex item, `search-box.grow`; it used to land on the
     label inside it, so the box kept its 180px floor and was cut off. `.bar` in front
     because the bar's own `.bar .grow { flex: 1 }` outranks a bare
     `search-box.grow` and would put the box back on the first row. */
  .bar search-box.grow { order: 9; flex: 1 1 100%; min-width: 0; }
  .search { max-width: none; margin-left: 0; }
  /* The gear's panel opens under a two-row bar, not a one-row one. */
  .setpanel:popover-open { top: calc(92px + env(safe-area-inset-top)); right: 10px; max-width: calc(100vw - 20px); }
  /* The rail is a drawer that slides in from the left over the board,
     behind ☰, with the board dimmed under it (POP-8069; it used to fold
     above the board, POP-5466/POP-7839). Folded, it is off-canvas and out
     of the tab order; it still starts folded on every launch. */
  project-rail {
    position: fixed; inset: 0 auto 0 0; z-index: 40;
    width: min(85vw, 320px); max-height: none; overflow-y: auto;
    padding: env(safe-area-inset-top) 10px env(safe-area-inset-bottom);
    background: var(--panel); border-right: 1px solid var(--line);
    transform: translateX(-100%);
    transition: transform .2s ease, visibility 0s linear .2s; visibility: hidden;
  }
  project-rail:not(.folded) {
    transform: none; visibility: visible; box-shadow: var(--pop-shadow);
    transition: transform .2s ease;
  }
  .rail-scrim {
    display: block; position: fixed; inset: 0; z-index: 39;
    background: color-mix(in srgb, #000 45%, transparent);
    opacity: 0; pointer-events: none; transition: opacity .2s ease;
  }
  project-rail:not(.folded) ~ .rail-scrim { opacity: 1; pointer-events: auto; }
  body.drawer-open { overflow: hidden; }
  /* The drawer's header: where you are standing, and ✕ to put it away. */
  .rail-fold {
    display: flex; align-items: center; gap: 8px; width: 100%; min-height: 40px;
    padding: 6px 4px; border: 0; background: var(--panel); cursor: pointer;
    position: sticky; top: 0; z-index: 1;
    color: var(--text); font: 600 13px var(--font-body, sans-serif); text-align: left;
  }
  .rail-fold .chev { display: none; }
  .rail-fold-here { margin-left: auto; font-weight: 400; color: var(--dim); }
  .rail-fold-x { width: 28px; text-align: center; font-size: 16px; color: var(--dim); }
  .rail-fold:focus-visible { outline: 0; box-shadow: var(--ring); border-radius: 6px; }
  /* Finger-sized: 44px is the smallest target Apple and Google both
     publish for a tap, and the rail is tapped more than anything. */
  .rail-fold { min-height: 44px; font-size: 15px; }
  .rail-item, .rail-head, .rail-name { min-height: 44px; font-size: 15px; }
  .rail-item { padding: 5px 6px; }
  .board { padding: 10px 10px calc(60px + env(safe-area-inset-bottom)); }
  /* Every button of the one action-button system is finger-sized here:
     the new-task form's, planning's and standup's included. */
  .btn { height: auto; min-height: 44px; }
  /* A phone row is its priority, its summary and its status, on one line
     (POP-7839). No select box: there is no range to shift-click on glass,
     and bulk work is a desk's job. No action cluster: the summary opens
     the task, and every action is on its page. `--acts-w: 0` is read
     back by the board's measure (board.js), and store.rowCost drops the
     select box's track, so the fit does not pay for either. */
  :root { --acts-w: 0px; }
  .t-line > .cb, #thead > .thcell:first-child, .t-line .rowacts, #thead .th-acts { display: none; }
  .t-line { min-height: 44px; }
  /* "Confirm if Done" is wider than the status track; on a phone it wraps
     inside its cell rather than being cut. */
  .t-line .st-btn.col-status { white-space: normal; text-align: left; line-height: 1.2; }
}
@media (max-width: 768px) and (prefers-reduced-motion: reduce) {
  project-rail, project-rail:not(.folded), .rail-scrim { transition: none; }
}
/* -- markdown in descriptions, notes and comments --------------------- */
.desc.md { color: var(--dim); font-size: 12.5px; line-height: 1.55; margin-bottom: 8px; }
.desc.md > *:first-child { margin-top: 0; }
.desc.md > *:last-child { margin-bottom: 0; }
.desc.md p { margin: 0 0 6px; }
.desc.md h3, .desc.md h4, .desc.md h5, .desc.md h6 {
  font: 600 12.5px var(--font-heading, "Poppins", sans-serif); color: var(--text); margin: 8px 0 4px;
}
.desc.md ul, .desc.md ol { margin: 0 0 6px; padding-left: 18px; }
.desc.md li { margin: 1px 0; }
.desc.md code {
  font: 11.5px var(--mono); background: var(--panel); border: 1px solid var(--hair);
  border-radius: 3px; padding: 0 4px;
}
.desc.md pre {
  background: var(--panel); border: 1px solid var(--hair); border-radius: 6px;
  padding: 8px 10px; overflow-x: auto; margin: 0 0 6px;
}
.desc.md pre code { border: 0; background: none; padding: 0; }
.desc.md blockquote {
  margin: 0 0 6px; padding-left: 10px; border-left: 3px solid var(--line); color: var(--faint);
}
.desc.md a { color: var(--link); }
.desc.md strong { color: var(--text); }
/* A description written in parts — What is wrong / The fix / Why, and
   Record where the work is technical. The label leads its paragraph on
   its own line, small and quiet, so the three answers read as a shape a
   person can skim instead of three bold runs inside prose. Nothing here
   depends on a parser: the labels are markdown's own <strong> at the head
   of a paragraph, and the `parts` class is only put on when the text
   actually is in parts. */
.md.parts p { margin: 0 0 10px; }
.md.parts p > strong:first-child {
  display: block;
  color: var(--dim);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-bottom: 1px;
}
.desc.comment { color: var(--dim); }
.desc.notes { border-left: 3px solid var(--hair); padding-left: 10px; }
.desc.loading { color: var(--faint); font-style: italic; font-size: 11.5px; }
/* -- the source drawer: a receipt read beside the board --------------- */
/* The Source cell's chip: an `<a>` since step 6, and a `<button>` before
   it, which is why these two blocks used to name their element. They do
   not any more — one receipt chip, one look. */
.origin {
  border: 0; background: none; padding: 0; cursor: pointer; text-decoration: none;
  color: var(--dim); font: inherit; font-size: 11px; text-align: left;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.origin:hover { color: var(--hot); text-decoration: underline; text-underline-offset: 2px; }
.chip.perma { cursor: pointer; }
.chip.perma:hover { border-color: var(--hot); color: var(--hot); }

.scrim { position: fixed; inset: 0; background: rgb(0 0 0 / 0.34); z-index: 40; }
.drawer {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 41;
  width: min(560px, 92vw); display: flex; flex-direction: column; gap: 8px;
  padding: 14px 16px; overflow-y: auto;
  background: var(--raise); border-left: 1px solid var(--line);
  box-shadow: -12px 0 32px rgb(0 0 0 / 0.28);
}
/* The head of the drawer never compresses and never runs off its edge.
   In a column flexbox every child shrinks by default, and nothing here
   wrapped: a long actor address ("firstname.lastname@…") is one
   unbreakable token, so it ran past the drawer's right edge and read as
   truncated (the operator's screenshot, 2026-08-25). Both halves are fixed —
   the lines hold their height, and a long word breaks. */
.drawer > header, .drawer > .summary, .drawer > .when,
.drawer > .drawer-find, .drawer > .stated { flex-shrink: 0; }
.drawer header { display: flex; align-items: center; gap: 10px; }
.drawer .who {
  display: flex; align-items: center; flex-wrap: wrap; gap: 4px 8px; flex: 1; min-width: 0;
}
.drawer .kindword {
  font: 600 12px var(--font-heading, "Poppins", sans-serif);
  text-transform: uppercase; letter-spacing: .06em; color: var(--purple);
}
.drawer .offset { font-size: 11px; color: var(--faint); }
.drawer .summary {
  font-size: 13.5px; color: var(--text); font-weight: 500;
  min-width: 0; overflow-wrap: anywhere;
}
.drawer .when { font-size: 11px; color: var(--faint); }

/* A statement receipt: who said it, the chain it carries, and — only
   behind a disclosure — the fields exactly as they were stated. */
.stated { display: flex; flex-direction: column; gap: 8px; }
.stated-said {
  margin: 0; font-size: 13.5px; line-height: 1.5; color: var(--dim);
  min-width: 0; overflow-wrap: anywhere;
}
.stated-said strong { color: var(--text); font-weight: 600; }
.stated-kind { margin: -6px 0 0; font-size: 11px; color: var(--faint); }
.stated-chain { display: flex; flex-wrap: wrap; gap: 6px; }
.stated-chain .chip.under { max-width: 100%; }
.stated-alone { margin: 0; }
.stated-words > summary {
  cursor: pointer; font-size: 11.5px; color: var(--faint); list-style-position: outside;
}
.stated-words > summary:hover { color: var(--hot); }
.stated-list {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 4px 12px;
  margin: 8px 0 0; padding: 8px 10px;
  background: var(--bg); border: 1px solid var(--hair); border-radius: 6px;
}
.stated-list dt { font: 11px var(--mono); color: var(--faint); }
.stated-list dd {
  margin: 0; font-size: 12.5px; color: var(--dim); min-width: 0; overflow-wrap: anywhere;
}
.stated-list dd .desc.md { margin-bottom: 0; }
.drawer .muted { color: var(--faint); font-size: 12.5px; }
.drawer footer { margin-top: auto; padding-top: 8px; font-size: 11px; }
.drawer footer a { color: var(--faint); text-decoration: none; }
.drawer footer a:hover { color: var(--hot); text-decoration: underline; }
.evidence-text {
  flex: 1; margin: 0; padding: 10px 12px; overflow: auto;
  background: var(--bg); border: 1px solid var(--hair); border-radius: 6px;
  font: 12px/1.55 var(--mono); color: var(--dim); white-space: pre-wrap; word-break: break-word;
}
.chip.warmchip { background: var(--warm-soft); color: var(--warm); border-color: transparent; }
/* -- selection and the bulk bar --------------------------------------- */
task-row.picked .t-line { background: var(--hot-soft); }
task-row.picked .t-line::after {
  content: ""; position: absolute; left: calc(-1 * var(--row-edge)); top: 0; bottom: 0; width: var(--row-edge); background: var(--hot);
}
.cb { cursor: pointer; }
.thead .cb { margin: 0; }

bulk-bar { display: block; position: sticky; top: 28px; z-index: 12; }
.bulk {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 10px; margin-bottom: 6px; border-radius: 8px;
  background: var(--purple-soft); border: 1px solid var(--purple-line);
  box-shadow: var(--shadow);
}
.bulk .count { font: 600 12px var(--font-body, sans-serif); color: var(--purple); white-space: nowrap; }
.bulk .grow { flex: 1; }
.bulk-panel {
  display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
  padding: 8px 10px; margin: -2px 0 8px; border-radius: 8px;
  background: var(--raise); border: 1px solid var(--line); box-shadow: var(--shadow);
}
.bulk-panel.wide { flex-direction: column; align-items: stretch; max-width: 380px; }
/* Won't do on a selection: the one reason prompt (lib/reason.js), in a panel. */
.bulk-panel.why-host { flex-direction: row; align-items: center; max-width: 560px; }
.bulk-panel .item {
  padding: 5px 10px; border: 1px solid var(--line); border-radius: var(--ctl-radius); background: var(--bg);
  color: var(--text); font: 12px var(--font-body, sans-serif); cursor: pointer; text-align: left;
}
.bulk-panel .item:hover { border-color: var(--hot); color: var(--hot); }
.bulk-panel .item .dim { margin-left: auto; color: var(--faint); font-size: 10.5px; }
.bulk-panel .suggest-list { display: flex; flex-direction: column; gap: 4px; max-height: 220px; overflow-y: auto; }
/* "Tag as…": the task page's Tags section, said for a whole selection —
   the same picker above, the same ✕ chips below, so a person who has
   filed one task recognises the gesture for twenty. */
.bulk-panel.bulk-tags tag-pick { margin-top: 0; }
.bulk-panel.bulk-tags .tagset { margin-top: 2px; }
.bulk-panel.bulk-tags .chip .dim { color: var(--faint); font-size: 10px; }
/* -- the Task List's own filter bar (surfaces/board/listsearch.js) ---- */
/* The Agent Flow board's box, over the Task List's rows: the same pill,
   so the two in-view filters read as one control in two places rather
   than as two features. Full width, because what it narrows is the full
   width of the table under it — and a box drawn narrow over a wide list
   reads as a search of something smaller than the list. */
list-search { display: block; margin: 0 0 8px; }
list-search[hidden] { display: none; }
.listsearch {
  display: flex; align-items: center; gap: 8px;
  height: 30px; padding: 0 14px;
  border: 1px solid var(--line); border-radius: 999px; background: var(--raise);
}
.listsearch:focus-within { border-color: var(--hot); box-shadow: var(--ring); }
.listsearch .glass { color: var(--faint); font-size: 13px; }
.listsearch input {
  flex: 1; min-width: 0; height: 100%; padding: 0;
  border: 0; border-radius: 0; outline: 0; box-shadow: none; background: transparent;
  font: 13px var(--font-body, sans-serif); color: var(--text);
}
.listsearch input::placeholder { color: var(--faint); }

/* -- the filter chips: every narrowing named, each removable ---------- */
.fchips { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 0 2px 6px; }
/* The row says, in words, every way this list is cut — and it says it in
   the filter colour, the same teal the funnels in the head wear, so the
   glyph on a column and the pill naming what it did read as one thing
   (the operator, 2026-09-23: the chips "become more noticeable"). Quiet
   greys had it reading as a caption about the table rather than as the
   reason the table is short. */
.fchips .flabel {
  font: 600 9.5px var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--filter);
}
.fchip {
  display: inline-flex; align-items: center; gap: 5px;
  border: 1px solid var(--filter-line); border-radius: 999px; background: var(--filter-soft);
  padding: 2px 9px; font-size: 11px; color: var(--filter); cursor: pointer;
}
/* Pressing one REMOVES that narrowing, so under the pointer it says so in
   the colour every removal on this board speaks in. */
.fchip:hover { border-color: var(--bad); background: var(--bad-soft); color: var(--bad-ink); }
.fchip .x { color: var(--filter); opacity: .7; }
.fchip:hover .x { color: var(--bad-ink); opacity: 1; }
.due-menu .pickrow {
  display: flex; align-items: center; gap: 8px; padding: 4px 9px 2px;
  font: 11.5px var(--font-body, sans-serif); color: var(--faint);
}
.due-menu .due-pick {
  flex: 1; height: 26px; padding: 0 6px; border: 1px solid var(--line); border-radius: var(--ctl-radius);
  background: var(--bg); color: var(--text); font: 11.5px var(--font-body, sans-serif);
}
/* -- find inside a source --------------------------------------------- */
.drawer-find {
  display: flex; align-items: center; gap: 8px; padding: 0 10px; height: 30px;
  border: 1px solid var(--line); border-radius: 999px; background: var(--bg);
}
.drawer-find:focus-within { border-color: var(--hot); box-shadow: var(--ring); }
.drawer-find input {
  flex: 1; border: 0; outline: 0; box-shadow: none; background: transparent; height: 100%;
  padding: 0; border-radius: 0;
  color: var(--text); font: 12px var(--font-body, sans-serif);
}
.drawer-find .hits { font: 10.5px var(--mono); color: var(--faint); white-space: nowrap; }
.evidence-text mark { background: var(--warm); color: #16224D; border-radius: 2px; }

/* -- a capture's own page (surfaces/source/source.js) ----------------------------- */
/* The conversation a task came out of, read whole. It borrows the task
   page's chrome — the same back link, the same section headings, the
   same two-column body — so a reader who has seen one has seen both;
   what is its own is the evidence, which is the only place on this board
   where somebody READS rather than scans. */
.spage { display: block; max-width: 1180px; padding: 4px 0 40px; }
.spage-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.spage-head .grow { flex: 1; }
.spage-kind {
  font: 600 11px var(--font-heading, "Poppins", sans-serif);
  text-transform: uppercase; letter-spacing: .06em; color: var(--purple);
}
.spage-offset {
  padding: 3px 6px; border: 1px solid var(--hair); border-radius: 4px;
  background: var(--raise); font: 10.5px var(--mono); color: var(--faint);
}
.spage-offset[role="button"] { cursor: pointer; }
.spage-offset[role="button"]:hover, .spage-offset:focus-visible {
  color: var(--hot); border-color: var(--hot); background: var(--hot-soft);
}
.spage-title {
  margin: 0 0 8px; font: 600 21px/1.3 var(--font-heading, "Poppins", sans-serif);
  color: var(--text); overflow-wrap: anywhere;
}
.spage-meta { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-bottom: 8px; }
.spage-meta .chip.why { color: var(--faint); font-style: italic; max-width: 46ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Who was in it, one line per part, so a reader can tell "sent it" from
   "was written to" without reading a legend. */
.spage-parts { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; }
.spage-part { display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline; }
.spage-part .mlab { min-width: 84px; }
.chip.person { background: var(--panel); }
.spage-hostile {
  margin: 10px 0; padding: 8px 12px; border-radius: 8px; font-size: 12.5px;
  background: var(--bad-soft); border: 1px solid var(--bad-line); color: var(--bad-ink);
}
.spage-withheld { max-width: 70ch; margin-top: 16px; }
.spage-withheld p { margin: 0 0 8px; font-size: 13.5px; line-height: 1.6; color: var(--text); }
.spage-withheld .muted { color: var(--faint); font-size: 12.5px; }

.spage-body { display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 24px; align-items: start; }
@media (max-width: 1080px) {
  .spage-body { grid-template-columns: minmax(0, 1fr); }
  /* On a narrow screen the FINDINGS come first: a reader who opened this
     from a task wants to see the row they clicked, and a transcript above
     it is a thousand lines of scrolling before the answer. */
  .spage-side { order: -1; }
}
.spage-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 6px; }
.spage-tools .grow { flex: 1; }
.spage-tools .tiny { font-size: 10.5px; }
.chip.lines-said { background: var(--warm-soft); color: var(--warm); border-color: var(--warm-line); gap: 6px; }
.chip.lines-said .linkbtn { font-size: 10.5px; color: inherit; opacity: .75; }
/* A link that points past the end of this receipt says so, rather than
   sitting there naming lines nothing is highlighting. */
.chip.lines-said.adrift { background: var(--bad-soft); color: var(--bad-ink); border-color: var(--bad-line); }
.chip.lines-said .linkbtn:hover { opacity: 1; }

/* The evidence. A meeting transcript is PROSE — people talking — so it
   reads in the body font at a comfortable measure and wraps like text; a
   mail's headers and a model's payload are STRUCTURE, so they stay
   monospace. Both keep a numbered gutter, because a line number is the
   thing a citation points at and the thing a reader clicks to point a
   link back. */
.evidence {
  max-height: min(72vh, 900px); overflow: auto;
  border: 1px solid var(--hair); border-radius: 8px; background: var(--bg);
  padding: 8px 0;
}
.ev-line { display: grid; grid-template-columns: 52px minmax(0, 1fr); gap: 12px; padding: 0 12px 0 0; }
.ev-line:hover { background: color-mix(in srgb, var(--line) 22%, transparent); }
.ev-num {
  border: 0; background: none; padding: 0 8px 0 0; cursor: pointer; user-select: none;
  font: 11px/1.7 var(--mono); color: var(--faint); text-align: right;
}
.ev-num:hover { color: var(--hot); }
.ev-text { min-width: 0; overflow-wrap: anywhere; white-space: pre-wrap; }
.evidence.prose .ev-text { font: 13.5px/1.7 var(--font-body, sans-serif); color: var(--text); max-width: 84ch; }
.evidence.mono .ev-text { font: 12px/1.7 var(--mono); color: var(--dim); }
.evidence.prose .ev-num { line-height: 1.7; }
/* The cited lines. A left bar carries the emphasis and the wash stays
   pale, so the highlight survives both skins without turning the words
   it is pointing at into low-contrast ink on a slab of color. */
.ev-line.lit { background: color-mix(in srgb, var(--c-amber) 16%, var(--bg)); box-shadow: inset 3px 0 0 var(--warm); }
.ev-line.lit .ev-num { color: var(--warm); font-weight: 600; }
.evidence mark { background: var(--warm); color: #16224D; border-radius: 2px; }

/* What the brain drew from this — the citation join, grouped by kind. */
.spage-drew { position: sticky; top: 34px; max-height: calc(100vh - 60px); overflow: auto; padding-right: 4px; }
@media (max-width: 1080px) { .spage-drew { position: static; max-height: none; } }
.spage-drew-head {
  margin: 0 0 12px; font: 600 13px var(--font-heading, "Poppins", sans-serif); color: var(--text);
}
.spage-drew .tpage-sec { margin-bottom: 16px; }
.drew-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.drew-item {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px;
  padding: 7px 9px; border: 1px solid var(--hair); border-radius: 7px; background: var(--raise);
}
.drew-name { font-size: 12.5px; font-weight: 500; color: var(--link); text-decoration: none; flex: 1 1 100%; overflow-wrap: anywhere; }
.drew-name:hover { text-decoration: underline; }
.drew-name.plain { color: var(--text); }
.drew-prose { flex: 1 1 100%; margin: 0; font-size: 11.5px; line-height: 1.5; color: var(--dim); overflow-wrap: anywhere; }
.drew-said { display: flex; flex-wrap: wrap; gap: 4px; }
.drew-said .chip { font-size: 10.5px; }
/* Whose word it is, said in words rather than a symbol: a reader who has
   never met this board should not have to learn that purple means the
   model guessed. */
.drew-basis { font: 10.5px var(--mono); color: var(--purple); white-space: nowrap; }
.drew-basis.stated { color: var(--ok-ink); }
.drew-lines { font: 10.5px var(--mono); color: var(--warm); }
.drew-watch { font-size: 10.5px; color: var(--link); text-decoration: none; white-space: nowrap; }
.drew-watch:hover { text-decoration: underline; }
/* -- the Objectives view: the week's objectives as a ranked card grid -- */
.objview { padding: 4px 0 24px; }
.obj-head { margin-bottom: 14px; }
.obj-head h2 { margin: 0 0 2px; font-size: 18px; font-weight: 600; }
.obj-head .sub { font-size: 12px; color: var(--faint); }
/* A refused reorder says so where the count stands, in the bad ink: the
   one place on this page that can speak, and the one thing it has to say
   that is not a number. */
.obj-head .sub.obj-refused { color: var(--bad-ink); font-weight: 600; }

/* One card to a row, ranked top to bottom (the operator, 2026-10-02,
   trying it in place of the mockup's 1 2 3 / 4 5 6 grid). */
.obj-grid {
  display: grid; gap: 12px;
  grid-template-columns: minmax(0, 1fr);
}
/* The empty line and the spinner are prose, not a card: squeezed into a
   260px card track they wrapped to six lines. */
.obj-grid.empty-grid { grid-template-columns: 1fr; }
/* A card grows to show its whole title. Title, then progress with the
   demote button at the end of the same line; were the grid ever several cards to a row again, the
   row would stretch each card to the tallest and the spare height sits
   under the title, so the bars still line up across it. */
.obj-card {
  display: grid; grid-template-columns: auto 1fr auto; grid-template-rows: 1fr auto;
  gap: 6px 8px; align-items: start;
  padding: 10px 14px; border: 1px solid var(--hair); border-radius: 10px;
  background: var(--panel); color: var(--text);
  cursor: pointer; user-select: none;
  /* The whole card is the drag body, so a finger pressing one has to be
     the browser's for scrolling — vertical pan is how a phone reads a
     list of cards. Ranking is a desk gesture; on touch the page keeps
     scrolling and gives up the drag, deliberately. */
  touch-action: pan-y;
}
.obj-card:hover, .obj-card:focus-within { border-color: var(--hot); }
.obj-card.dragging {
  cursor: grabbing; pointer-events: none; opacity: 0.9;
  border-color: var(--hot); box-shadow: 0 10px 28px rgba(0,0,0,0.3);
}
/* Where the card will land. Drawn in the grid's own flow, so the cards
   around it move aside exactly as they will when it drops. */
.obj-slot {
  border: 1px dashed var(--hot); border-radius: 10px;
  background: var(--purple-soft); min-height: 92px;
}
.obj-rank {
  grid-row: 1; min-width: 20px; text-align: right;
  font-variant-numeric: tabular-nums; font-weight: 600; color: var(--faint);
}
.obj-rank i { font-style: normal; opacity: 0.6; }
.obj-title {
  grid-row: 1; grid-column: 2 / 4; color: inherit; text-decoration: none; font-weight: 600; font-size: 14px;
  /* The whole title, never clamped: a card grows rather than cut it off. */
  overflow-wrap: anywhere;
}
/* No hover mark of its own: the whole card is the link, and the card's
   border already says so (the operator, 2026-10-02). That includes the
   edge every tipped element draws under the pointer (`lib/tip.css`), which
   boxed the title inside the card's own border. Keyboard focus keeps its
   ring, which is how a tab reader sees where they are. */
.obj-title[data-tip]:hover { box-shadow: none; }
.obj-progress {
  grid-row: 2; grid-column: 2; align-self: end; display: flex; align-items: center; gap: 8px; font-size: 12px;
}
/* Lighter than a full-width rule: a thin bar that stops at 500px, so a
   wide card does not draw a progress line across the whole page. */
.obj-bar {
  flex: 1; max-width: 500px; display: block; height: 4px; border-radius: 999px;
  background: var(--line); overflow: hidden;
}
.obj-bar i { display: block; height: 100%; background: var(--ok); }
.obj-count { white-space: nowrap; font-variant-numeric: tabular-nums; color: var(--dim); }
.obj-demote {
  grid-row: 2; grid-column: 3; align-self: end;
  height: 24px; padding: 0 9px; font-size: 11px;
}
/* -- the task page ---------------------------------------------------- */
/* One task read whole (a teammate, 2026-09-01: "I love the single page view
   of a thing… I expect that description field to hold a lot"). The page
   stands in for the board inside the same frame — the rail stays, the
   table, the band and the head step aside (`paged`) — so
   the prose column has the width the row never had. */
tasks-app.paged .board > :is(questions-band, quick-line, .focushead, filter-chips, bulk-bar, #thead, #rows, .tfoot) {
  display: none;
}
.tpage { display: block; padding: 4px 0 40px; }
.tpage-head { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.tpage-head .grow { flex: 1; }
.tpage-back { font-size: 12px; text-decoration: none; }
.tpage-back:hover { text-decoration: underline; }
/* The three ways to take this task away with you: its name, its link,
   and — only while Debug is on — the entity id an ingest write needs.
   One row at the top right of the page, three identical controls, each
   the word and the ⧉ that says it copies.

   They used to be four affordances in two shapes in two places: two
   buttons up here, and under the summary a key pill and a grey hex chip
   that each also copied when clicked and neither of which looked
   pressable, on a line of their own that lined up with nothing. */
.tpage-copies { display: flex; align-items: center; gap: 4px; }
.copyact {
  display: inline-flex; align-items: center; gap: 6px;
  height: 26px; padding: 0 9px; border: 1px solid transparent; border-radius: 6px;
  background: transparent; color: var(--faint); cursor: pointer;
  font: 500 11.5px var(--font-body, sans-serif); white-space: nowrap;
}
.copyact .copyact-word { font-family: var(--mono); font-size: 11px; }
.copyact .copyact-mark { opacity: .55; font-size: 11px; line-height: 1; }
.copyact:hover {
  color: var(--hot); border-color: var(--hot); background: var(--hot-soft);
}
.copyact:hover .copyact-mark { opacity: 1; }
.copyact:focus-visible { outline: 2px solid var(--hot); outline-offset: -2px; }

/* The copy toast: the one thing said out loud when anything is copied
   (lib/copy.js). The control's own beat is a whisper in a narrow column —
   on a row "✓ copied" can clip — and a person who cannot tell whether
   the copy landed clicks again. Polite, one at a time, gone in a beat,
   and never in the way of the pointer. */
.copy-toast {
  position: fixed; z-index: 70; left: 50%; bottom: 28px;
  transform: translate(-50%, 6px); pointer-events: none;
  max-width: min(90vw, 460px); padding: 8px 14px;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--panel); color: var(--text); box-shadow: var(--shadow);
  font: 12px var(--font-body, sans-serif);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  opacity: 0; transition: opacity 0.12s ease, transform 0.12s ease;
}
.copy-toast.up { opacity: 1; transform: translate(-50%, 0); }
@media (prefers-reduced-motion: reduce) { .copy-toast { transition: none; } }
.tpage-title {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0 0 10px;
  font: 600 22px/1.25 var(--font-heading, "Poppins", sans-serif); color: var(--text);
}
.tpage.done .tpage-title .ttext { color: var(--dim); }
.tpage-title .ttext { cursor: text; border-radius: 4px; padding: 0 4px; margin-left: -4px; }
.tpage-title .ttext:hover { background: var(--panel); outline: 1px dashed var(--line); }
/* Renaming a task: the box takes the whole column, and reads at the size
   the summary it is replacing reads at. It stopped at 80 characters in
   the middle of a much wider column, so a long summary was edited through
   a slot narrower than the line it came from (the 2026-09-23 walk: every
   single-line box for a summary takes the width of its column). */
.title-edit {
  width: 100%; max-width: none; height: 40px; padding: 0 10px;
  border: 1px solid var(--hot);
  border-radius: var(--ctl-radius); background: var(--bg); color: var(--text);
  font: 600 22px/1.25 var(--font-heading, "Poppins", sans-serif);
}
/* The controls: a panel of labelled segments and inputs, the row's own
   idiom (mlab / mseg / mbtn / due-edit / own-edit) laid out to breathe. */
.tpage-controls {
  display: flex; flex-wrap: wrap; gap: 10px 22px; align-items: center;
  padding: 10px 14px; margin-bottom: 10px; border: 1px solid var(--hair); border-radius: 8px;
  background: var(--raise);
}
.tpage-controls .ctl { display: inline-flex; align-items: center; gap: 8px; }
.tpage-controls .mseg .mbtn { height: 26px; padding: 0 9px; }
.tpage-controls .proj-edit { display: inline-flex; gap: 4px; }
.tpage-controls .proj-edit input {
  height: 26px; width: 180px; padding: 0 8px; border: 1px solid var(--line);
  border-radius: var(--ctl-radius); background: var(--raise); color: var(--text); font: 12px var(--font-body, sans-serif);
}
.tpage-controls .proj-edit .btn { height: 26px; padding: 0 9px; font-size: 11px; }
/* The page's three date pickers stand at the owner box's height. */
.tpage-controls .due-edit, .tpage-controls .fin-edit { height: 26px; padding: 0 8px; }
.tpage-controls .droprow { display: inline-flex; gap: 8px; align-items: center; }
.tpage-controls .drop-why:not([hidden]) { display: inline-flex; gap: 6px; }
.tpage-controls .drop-why input {
  height: 26px; width: 260px; padding: 0 8px; border: 1px solid var(--line); border-radius: var(--ctl-radius);
  background: var(--bg); color: var(--text); font: 11.5px var(--font-body, sans-serif);
}
/* `.tpage-facts` — the chip row of about / origin / opened / finished /
   phase that used to sit in the rail between Flow and Where this came
   from — is gone with POP-7710. Every chip on it was a second copy of
   something the page already says: the origin chip of the "Where this
   came from" section directly under it, `about` and the dates of the
   properties above it. A rail that repeats itself reads as more
   information than the page holds. */
/* The body: prose on the left, the properties on the right; one column
   when narrow, with the properties under the prose (see the narrow rules
   at the foot of this file). */
.tpage-body { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: 24px; align-items: start; }
@media (max-width: 900px) {
  .tpage-body { grid-template-columns: minmax(0, 1fr); }
}
.tpage-sec { margin-bottom: 22px; }
/* A section is announced the way the design mockup announces one (ruling
   8, 2026-09-23: "sections visually distinct — the mockup does this
   better; match its section rhythm"): a small, quiet, wide-tracked label
   in capitals, then a hairline running from it to the far edge of the
   column. The rule is drawn by the heading itself rather than by an
   element in the markup, so every section on the page gets one by having
   a heading and nothing has to remember to add a span. */
.tpage-sec h3 {
  display: flex; align-items: center; gap: 12px; margin: 0 0 14px;
  font: 700 10.5px var(--font-heading, "Poppins", sans-serif); letter-spacing: .13em;
  text-transform: uppercase; color: var(--faint);
}
.tpage-sec h3::after {
  content: ""; flex: 1 1 auto; height: 1px; background: var(--hair);
}
.tpage-sec h3 .n-small { font: 10.5px var(--mono); color: var(--faint); }
.tpage-sub { margin: 12px 0 4px; font: 600 11px var(--font-heading, "Poppins", sans-serif); color: var(--faint); text-transform: uppercase; }
/* The description gets a reading column — the whole point of the page. */
/* THE READING SIZE, AND IT HAS TO OUTWEIGH THE ROW'S.
   A description on this page is a `<div class="desc md tpage-desc">`, and
   `.desc.md` — the markdown block as a ROW draws it, in a table, at
   12.5px and in `--dim` (`surfaces/board/row.css`) — names two classes to
   this rule's one. Two beats one whatever the order, so a page asking for
   a size in one class got the row's size instead, and the stylesheet test
   that pinned the page's read the declaration rather than the cascade and
   passed the whole time (fixed in the same commit: it now weighs the rule
   against every other rule that could claim the same element).
   Naming all three classes is the fix and not `!important`: the page is
   saying "a markdown block that is THIS page's description", which is a
   more specific thing than "a markdown block", and specificity is the
   language CSS has for exactly that.
   THE SIZE ITSELF IS 12.5px (POP-7710), in `--text` rather than the row's
   `--dim`. It was 15.5px for one release, on the argument that the
   description is what the reader came for and a page drawing it at a
   property label's size does not say so. At 15.5px and the full column
   width this page also had, a long description read as a wall
   (2026-09-25). Both halves are fixed together: the measure above and the
   size here, which is the size the page rendered at before the rewrite.
   What says "this is the thing you came for" is the measure and the ink,
   not the type size. The rule is kept rather than deleted so the cascade
   is explicit — falling through to `domain/description.css` by accident
   is how the page got 12.5px the first time, and an accident that agrees
   with you is still an accident. */
.tpage-desc.desc.md { max-width: var(--tp-prose); font-size: 12.5px; line-height: 1.55; color: var(--text); }
.tpage-desc h3, .tpage-desc h4, .tpage-desc h5, .tpage-desc h6 { font-size: 14px; margin: 14px 0 6px; }
.tpage-desc h3 { font-size: 16px; }
.tpage-desc p { margin: 0 0 10px; }
.tpage-desc ul, .tpage-desc ol { margin: 0 0 10px; padding-left: 22px; }
.tpage-desc li { margin: 2px 0; }
.tpage-desc hr { border: 0; border-top: 1px solid var(--hair); margin: 12px 0; }
.tpage-desc input[type="checkbox"] { vertical-align: -2px; margin-right: 4px; }
.desc-edit.tall { min-height: 220px; max-width: var(--tp-prose); }
.desc-editor { max-width: var(--tp-prose); }
.tform-acts { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
.tpage .muted { color: var(--faint); font-size: 12px; }
/* Links: a labelled list per relation, each entry the task's own address. */
.tpage-side .tlinks { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; }
.tpage-side .tlinks ul { list-style: none; margin: 0; padding: 0; }
.tpage-side .tlinks li { margin: 2px 0; }
.tlink { color: var(--link); text-decoration: none; font-size: 12.5px; }
.tlink:hover { text-decoration: underline; }
/* The thread: newest last, a byline when the record holds one. */
.tcomment { padding: 8px 10px; margin-bottom: 8px; border: 1px solid var(--hair); border-radius: 8px; background: var(--raise); }
.tcomment.pending { border-style: dashed; }
.tbyline { font: 10.5px var(--mono); color: var(--faint); margin-bottom: 4px; }
.tcomment .desc.md { margin-bottom: 0; font-size: 12.5px; }
/* The box a comment is typed into stands one step below whatever came
   before it — the last comment in the thread, or the line saying there
   are none. It sat tight under "No comments yet." with four pixels
   between them, so a sentence about the thread and the control for
   adding to it read as one block (the 2026-09-23 walk). */
.comment-add { margin-top: var(--tp-step); }
.comment-add textarea {
  width: 100%; max-width: var(--tp-prose); padding: 8px 10px; border: 1px solid var(--line); border-radius: var(--ctl-radius);
  resize: vertical; background: var(--bg); color: var(--text); font: 12.5px/1.5 var(--font-body, sans-serif);
}
/* History: one line per word the record holds — when, who, what, and
   the changed fields as chips; the brain's word reads in purple, a
   person's in the text color. The pushes
   are counted at the top (a teammate: "pushed and pushed and pushed"). */
.hnote { display: inline-block; margin-bottom: 8px; padding: 3px 9px; border-radius: 999px; font: 600 11px var(--font-body, sans-serif); }
.hnote.warn { background: var(--warm-soft); color: var(--warm); border: 1px solid var(--warm-line); }
.hlist { display: flex; flex-direction: column; }
.hrow {
  display: grid; grid-template-columns: 96px 170px 90px minmax(0, 1fr) auto; gap: 8px; align-items: baseline;
  padding: 5px 0; border-bottom: 1px solid var(--hair); font-size: 12px;
}
.hrow.pending { border-bottom-style: dashed; }
.hwhen { font-size: 10.5px; color: var(--faint); white-space: nowrap; }
.hwho { color: var(--text); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hrow.derived .hwho { color: var(--purple); }
.hkind { color: var(--dim); }
.hchanges { display: flex; flex-wrap: wrap; gap: 4px; }
.hchip {
  display: inline-flex; gap: 4px; align-items: baseline; max-width: 100%;
  padding: 1px 7px; border-radius: 999px; border: 1px solid var(--hair); background: var(--panel);
  font-size: 10.5px; color: var(--dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.hchip b { font-weight: 600; color: var(--faint); }
.hchip.due { border-color: var(--warm-line); color: var(--warm); }
.hchip.state { color: var(--text); }
.hrestated { font-size: 10.5px; color: var(--faint); font-style: italic; }
.hreceipt { font: 10.5px var(--mono); color: var(--faint); text-decoration: none; }
.hreceipt:hover { color: var(--hot); }
@media (max-width: 700px) {
  .hrow { grid-template-columns: 90px minmax(0, 1fr); }
  .hrow .hkind, .hrow .hreceipt { display: none; }
}

/* Where a task came from: one row per receipt, each a link to that
   moment's own page. */
.rcpt-list { list-style: none; margin: 0 0 8px; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.rcpt {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0 8px; align-items: baseline;
  padding: 5px 8px; border: 1px solid var(--hair); border-radius: 6px;
  background: var(--raise); text-decoration: none;
}
.rcpt:hover { border-color: var(--hot); background: var(--hot-soft); }
.rcpt-word {
  grid-column: 1 / -1; font: 600 9.5px var(--font-heading, "Poppins", sans-serif);
  text-transform: uppercase; letter-spacing: .08em; color: var(--purple);
}
.rcpt-name { font-size: 12px; color: var(--text); min-width: 0; overflow-wrap: anywhere; }
.rcpt-day { font-size: 10.5px; color: var(--faint); white-space: nowrap; }
.tpage-objective { margin: 8px 0 0; display: flex; align-items: center; gap: 10px; }

/* -- the page, rebuilt to the 2026-09-21 mockup (POP-7055) --------------------
   "Too crowded right now. No visual separation… Linear isn't either,
   its spacing just helps a ton" (a teammate, 2026-09-21) — and then, on the
   2026-09-23 walk: "the description is the most important thing and
   should be visually obvious — a wide content column like reading a
   blog; the properties sidebar narrow and secondary; no dead area
   between description and properties".

   So the page is TWO columns and ONE gap. The properties stand in a 250px
   rail that ends where the page ends. Vertical rhythm is one step
   (`--tp-step`) instead of a dozen margins, and every colour is a palette
   token, so both skins answer.

   THE READING MEASURE IS BACK (POP-7710). It was `100%` for one release,
   on the argument that a 65ch cap left a dead quarter of white between
   the prose and the rail. The cure was worse: at 100% and the 15.5px this
   page also set, a description on a wide window ran the full width of the
   column in large type, which is the one shape prose cannot be read in
   (the 2026-09-25 brief: "the description runs full width in large
   type"). So the measure is `90ch`, the value the page carried before the
   rewrite — wide enough that no band of white opens up at 1440, narrow
   enough that a line ends where the eye can find the next one. It bounds
   the whole main column, title and comments included, so the page reads
   as one measure rather than as a column of different widths. */
/* 125ch, not 90ch, and centered (POP-7839, the operator, 2026-09-28: "a maximum
   width of 125ch, and the title, description and every section sit
   centered horizontally instead of leaving blank space on the right").

   ONE width for every element that reads it. `ch` is the width of a
   zero in the element's OWN font, so `max-width: 125ch` on the 26px title
   and on the 12.5px description are two different widths, the title's
   about twice the other's. Registering the property as a <length> makes
   it compute to pixels where it is declared, on `.tpage`, and children
   inherit those pixels rather than re-reading `ch` against their own
   font. It is declared as 125 zeros of the description's 12.5px: the
   page's 14px `ch` scaled down, since `ch` scales with the font size. */
@property --tp-prose {
  syntax: "<length>";
  inherits: true;
  initial-value: 0px;
}
.tpage {
  --tp-step: 32px;
  --tp-prose: calc(125ch * 12.5 / 14);
  --tp-rail: 250px;
  --tp-gap: 40px;
  /* The reading column a reader gets when Full width is OFF — what the
     page measures on a 1440-wide laptop, and nothing wider. It is applied
     only through `.tpage.capped`, so with the setting on (the default)
     the page takes the window and there is no unexplained band of white
     down the right. */
  --tp-page: 1170px;
  /* The text and the properties are one group, centered in the window
     with equal space either side, the way Linear lays out an issue
     (the operator's answer at Approve Plan, c3). The group is the measure, the
     gap and the rail, so the title and the header line up with the
     column under them rather than with the window's edge. */
  max-width: calc(var(--tp-prose) + var(--tp-gap) + var(--tp-rail));
  margin-inline: auto;
}
.tpage:has(> .tpage-body.sideshut) { --tp-gap: 20px; --tp-rail: 28px; }
.tpage.capped { max-width: min(var(--tp-page), calc(var(--tp-prose) + var(--tp-gap) + var(--tp-rail))); }
/* The breadcrumb: where this task sits, above its name. */
.tpage-crumbs { display: inline-flex; align-items: baseline; gap: 6px; min-width: 0; }
.tpage-crumb {
  font: 11px var(--mono); color: var(--faint); text-decoration: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 22ch;
}
a.tpage-crumb:hover { color: var(--hot); text-decoration: underline; }
.tpage-crumb.here { color: var(--dim); }
.tpage-crumb-sep { color: var(--faint); font-size: 11px; }
/* The summary, large — the one thing a reader came for. */
.tpage-title { margin: 6px 0 4px; font-size: 26px; line-height: 1.22; letter-spacing: -0.01em; }
.tpage-title .ttext { max-width: var(--tp-prose); }
/* The summary is the last thing before the body: the two names this task
   answers to moved into the header's copy row, where the controls that
   take them away already were. */
.tpage-title { margin-bottom: var(--tp-step); }

/* The description as its labelled PARTS: a small eyebrow, then the
   text, with a whole step between one part and the next. */
.tpage-body-sec { position: relative; margin-bottom: calc(var(--tp-step) * 1.5); }
/* Edit, at the top right of the body — where the mockup puts it.
   FLOATED, not positioned absolutely. Absolute took it out of the flow
   and laid it over whatever the description's first line happened to
   be: at a wide window the line usually stopped short of the right edge
   and nothing showed, and at a phone's width it never did, so the
   control sat on top of the first two words of the task's description
   (measured at 390px, 2026-09-23 review). A transparent control over
   body text is not a near miss that wide windows forgive; it is the
   same defect, hidden by the window.
   A float is the one thing in CSS that means "put this at the edge and
   let the text go round it": the first line wraps short of the control
   and every line below it takes the full column back. The button is
   first in the markup, which is what a float needs, and which is also
   the order a screen reader should meet it in. */
.tpage-pencil {
  float: right; margin-left: 12px; font-size: 11.5px; color: var(--faint);
  opacity: 0; transition: opacity .12s ease;
}
.tpage-body-sec:hover .tpage-pencil,
.tpage-pencil:focus-visible { opacity: 1; }
@media (hover: none) { .tpage-pencil { opacity: 1; } }
/* The float is cleared by the section that holds it, so the sub-tasks
   below never ride up beside it on a one-line description. */
.tpage-body-sec::after { content: ""; display: block; clear: both; }
.tpage-parts { max-width: var(--tp-prose); display: flex; flex-direction: column; gap: var(--tp-step); }
/* A part of the description — WHAT IS WRONG, THE FIX, WHY, RECORD — is
   announced in the page's own ink, not the quiet grey a section label
   speaks in. The mockup draws them that way and it is the right way
   round: the section labels are furniture a reader skims past, and these
   are the first four words of what they came to read. */
.tpage-part-label {
  margin: 0 0 10px; font: 600 12px var(--font-heading, "Poppins", sans-serif);
  letter-spacing: .09em; text-transform: uppercase; color: var(--text);
}
/* A labelled part is a `<div class="desc md tpage-part-text">`, so it
   needs the same three-class rule the whole description gets above, for
   the same cascade reason — and the same size, so a description that
   happens to carry the four labels never reads at a different size from
   one that does not. The ink is the page's `--text`, not the row's
   `--dim`; that is what this rule is still here for. */
.tpage-part-text.desc.md { font-size: 12.5px; line-height: 1.55; color: var(--text); }
.tpage-part-text > :last-child { margin-bottom: 0; }
.tpage-part-text p { margin: 0 0 10px; }
.tpage-part-text ul, .tpage-part-text ol { margin: 0 0 10px; padding-left: 22px; }
.tpage-part.lead .tpage-part-text { color: var(--dim); }

/* Sub-issues, under the description: a count, a bar, and one line per
   piece of work — key, summary, where it stands, whose it is. */
.tsubs { margin-bottom: calc(var(--tp-step) * 1.5); max-width: var(--tp-prose); }
.tsub-count { font: 11px var(--mono); color: var(--dim); letter-spacing: 0; text-transform: none; }
.tsub-bar {
  flex: 1; max-width: 140px; height: 4px; border-radius: 999px;
  background: var(--hair); overflow: hidden;
}
.tsub-bar-fill { display: block; height: 100%; background: var(--ok); transition: width .2s ease; }
/* The same bar again, directly under the summary (POP-7710): how much of
   the work under this task is finished, answered before the reader has
   scrolled anywhere. A task with nothing under it draws nothing here, so
   the bar's presence is itself the news that this task has pieces.
   It borrows `.tsub-bar`'s track and fill and only says where it stands
   and how wide it runs — one bar, drawn twice, described once.

   The title already carries a whole step of air beneath it, and the bar
   belongs to the title rather than to the body, so it takes three
   quarters of that step back and then restores the step below itself.
   Written as a calc off `--tp-step` and not as a magic -24px, so the two
   move together if the page's rhythm ever changes. */
.tpage-progress {
  display: flex; align-items: center; gap: 8px;
  margin: calc(var(--tp-step) * -0.75) 0 var(--tp-step);
  max-width: var(--tp-prose);
}
.tpage-progress .tsub-bar { flex: 0 1 220px; max-width: 220px; }
.tpage-progress .tsub-count { font: 11px var(--mono); color: var(--dim); }
.tsub-list { list-style: none; margin: 0; padding: 0; }
/* One task as a line, wherever a task page lists tasks: its Sub-tasks and
   every Linked tasks section (lists.js TASK_LINE_COLUMNS). Two lines a
   task (the operator, local review, POP-8069): what it is
   on top, its key then its title; how it stands underneath, its priority,
   its status with the agent dot, and at the right the blocked chip when
   there is one and the due date. The owner's initials sit
   at the far right across both lines. The key and the title are one cell
   (`lists.js`), laid out here as two grid items by `display: contents`. */
.tline {
  display: grid; grid-template-columns: auto auto minmax(0, 1fr) auto auto 26px;
  grid-template-areas: "key name name name name who" "pri tree flow blocked when who";
  column-gap: 10px; row-gap: 3px;
  align-items: center; padding: 7px 0; border-bottom: 1px solid var(--hair);
}
.tline[hidden] { display: none; }
.tline .tline-name { display: contents; }
.tline .tline-name > .t-key { grid-area: key; justify-self: start; }
.tline .tline-name > .tlink { grid-area: name; }
.tline > .bmark { grid-area: blocked; justify-self: end; }
.tline > .tline-who { grid-area: who; }
/* Priority and due on every sub-task (POP-8069): the board's own pill,
   and the date in the board's own words, red when it is past. */
.tline-when { font-size: 11px; color: var(--dim); white-space: nowrap; text-align: right; }
.tline-when.late { color: var(--bad); }

/* THE TREE'S PROGRESS, WHERE THE STATUS WORD WAS (POP-9246).

   A fixed 96px, and the width is the whole point: three different things
   stand in this slot — a bar with done/total, "No subtasks", "✓ Done" —
   and a slot that sized itself to each would put every row's second line
   at a different x. What the three mean is `lists.treeSlot`. */
.tline > .tline-tree {
  grid-area: tree; justify-self: start; display: inline-flex; align-items: center; gap: 6px;
  width: 96px;
}
.tline-tree .tsub-bar { flex: none; width: 56px; }
.tline-tree .tsub-count { font-size: 10.5px; }
.tline-tree.leaf { font-size: 11px; color: var(--faint); white-space: nowrap; }
/* A finished task's slot: the word, in the colour the app gives done. */
.tline-tree.finished { gap: 4px; font-size: 11px; font-weight: 600; color: var(--ok); white-space: nowrap; }

/* PRIORITY AND DUE, SET IN PLACE (POP-9246). The wrapper is the anchor
   the shared popover hangs off and nothing else; the chip and the date
   are the same two the line always drew. A date nobody has set is
   invisible until the row is under the pointer, so a list of undated
   work does not read as a column of "set date". */
.tline > .tline-pri-edit { grid-area: pri; justify-self: start; }
.tline > .tline-when-edit { grid-area: when; justify-self: end; }
.tline-edit { position: relative; display: inline-flex; }
.tline-edit > button { cursor: pointer; }
.tline-edit > .pri-chip:hover, .tline-edit > .pri-chip[aria-expanded="true"] { border-color: var(--hot); }
.tline-edit > .tline-when {
  border: 1px solid transparent; border-radius: 4px; background: none; padding: 1px 5px; margin: -2px -6px -2px 0;
}
.tline-edit > .tline-when:hover, .tline-edit > .tline-when[aria-expanded="true"] { border-color: var(--line); color: var(--text); background: var(--raise); }
.tline-edit > .tline-when.none { color: var(--faint); }
.tline-edit > .tline-when.none:not(:hover) { opacity: 0; }
.tline:hover .tline-edit > .tline-when.none { opacity: 1; }
.tline-edit > .tline-when:focus-visible { opacity: 1; }
/* Where it hangs, and only that: the box is `.popover` in
   `domain/prop-menu.css`. Priority opens from the left edge of its chip,
   the date from the right edge of its date, so neither runs off the side
   of a narrow rail. */
.tline-edit > .popover { top: calc(100% + 4px); }
.tline-pri-edit > .popover { left: 0; right: auto; width: 180px; }
.tline-when-edit > .popover { right: 0; width: 260px; }
/* MOVING THE TASK BY HAND, at the foot of the Flow box (POP-8540).
   Two labelled controls and one button, stacked: the box is in the
   properties rail, which is narrow, and a row of five things would wrap
   somewhere different at every width. The button is full width because it
   is the only thing here that acts. */
.flow-move {
  display: grid; grid-template-columns: 1fr; gap: 4px;
  margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--hair);
}
.flow-move .flow-lab { font-size: 10px; text-transform: uppercase; letter-spacing: .04em; color: var(--faint); }
.flow-move select, .flow-move input {
  width: 100%; min-height: 30px; font: 400 12px var(--font-body, sans-serif);
  color: var(--text); background: var(--bg);
  border: 1px solid var(--line); border-radius: var(--ctl-radius); padding: 3px 6px;
}
.flow-move .flow-move-go { margin-top: 4px; min-height: 30px; font-size: 12px; }
.flow-move .flow-move-go[disabled] { opacity: .5; cursor: not-allowed; }
/* The orphan-run question, asked in place under the button that would do
   it (`lib/reason.js` askSure). It stands inside this grid, so it takes
   the whole width rather than squeezing beside the control. */
.flow-move .sure-ask { grid-column: 1 / -1; }

/* The Sort control rides the heading, pushed to its right edge. */
.tsubs h3 { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.tsub-sortbox { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; }
.tsub-then { font: 400 11px var(--font-body, sans-serif); color: var(--faint); text-transform: none; letter-spacing: normal; }
.tsub-done { display: block; margin: 6px 0 2px; font-size: 12px; color: var(--dim); }
.tsub-sort { display: inline-flex; border: 1px solid var(--hair); border-radius: 6px; overflow: hidden; }
.tsub-sort button {
  font: 500 11px var(--font-body, sans-serif); padding: 2px 9px; border: 0; background: transparent;
  color: var(--dim); cursor: pointer;
}
.tsub-sort button + button { border-left: 1px solid var(--hair); }
.tsub-sort button { display: inline-flex; align-items: center; gap: 3px; }
.tsub-arrow { font-size: 12px; line-height: 1; color: var(--hot); }
.tsub-sort button[aria-pressed="true"] { background: var(--panel); color: var(--text); }
.tsub-sort button:hover, .tsub-sort button:focus-visible { color: var(--hot); }
/* The green-and-amber lane dot went with the state cell it coloured
   (POP-9246): there is no status word on a task line now, and the lane's
   own label below says in a word what the dot said in a colour. */
/* WHERE a subtask is in the pipeline, in words (POP-8540): the lane's own
   label, then the machine a running one is on, or the held mark for a
   queued one that something is holding.

   It takes the track between the state and the blocked chip, which was
   empty — the `.` of the grid above. That is the right room for it: the
   line's second row reads priority, state, where it is in the flow, what
   blocks it, when it is due, which is the order a reader asks. */
.tline > .tline-flow { grid-area: flow; justify-self: start; }
.tline-flow { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.tline-lane {
  font-size: 11px; color: var(--dim); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
/* The same chip the Agent Flow cards wear (`domain/claim.js`), at this
   line's size: one word means one thing on both surfaces. */
.tline-host {
  font: 500 10px var(--mono); color: var(--dim); white-space: nowrap;
  padding: 0 5px; border: 1px solid var(--hair); border-radius: 999px;
}
.tline-host.is-popbox { color: var(--blue-ink); border-color: var(--blue); }
/* A row carrying no `q:` tag at all (POP-8631): the same slot, saying the
   pipeline is not going to pick this one up. Quieter than a lane label,
   because it is the absence of a position rather than a position, and
   italic for the same reason the cell's own empty states are. */
.tline-none {
  font-size: 11px; font-style: italic; color: var(--faint); white-space: nowrap;
}
.tline:last-child { border-bottom: 0; }
.tline-name { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.tline-name .tlink { font-size: 13px; color: var(--text); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tline-name .tlink:hover { color: var(--link); }
.tline.settled .tline-name .tlink { color: var(--dim); }
.who-mark, .tline-who {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 999px;
  background: var(--raise); border: 1px solid var(--hair);
  font: 600 9.5px var(--font-body, sans-serif); color: var(--dim); letter-spacing: .03em;
}
.who-mark.dim, .tline-who.dim { color: var(--faint); border-style: dashed; }
/* Adding one: a button you can see, and the line it opens. The line used
   to be a bare box with a placeholder, drawn with a transparent border
   that only appeared under the pointer — furniture on a page of prose,
   which is why it did not read as a control at all. Now the button is
   the control, and the box it opens is a box: a real border, the page's
   own background, and Add and Cancel beside it. */
.tsubs .sub-open { margin-top: 14px; }
/* The line takes the column's whole width and Cancel sits under it, so
   the box a summary is typed into is never a stub beside two buttons. */
.tsubs .sub-add { display: block; margin-top: 14px; }
.tsubs .sub-cancel { margin-top: 6px; font-size: 11.5px; }
/* Linked tasks and Mentioned by keep the rail's list shape. */
.tpage-main .links .tlinks { margin-bottom: 10px; }
.tpage-main .links .tlink-list { list-style: none; margin: 0; padding: 0; }
.mentions .tlinks { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; }
.mentions .tlink-list { list-style: none; margin: 0; padding: 0; }

/* The sidebar: a 250px rail of properties beside the reading column,
   with a head of its own — the word, a rule across the rest of its
   width, and the chevron that folds it away.

   the operator's walk, 2026-09-23: "all sidebars collapsible (the rail and the
   properties)", so both folded leaves a clean content view. Folded, this
   one is a 28px edge strip with the word running down it and the chevron
   that brings it back; the reading column takes everything it gave up.
   The answer is a habit (`tasks.page.props`), so it stays as it is
   left. */
.tpage-body { grid-template-columns: minmax(0, 1fr) var(--tp-rail); gap: var(--tp-gap); }
.tpage-side { display: flex; flex-direction: column; gap: var(--tp-step); }
.tpage-side .tpage-sec { margin-bottom: 0; }
.side-head { display: flex; align-items: center; gap: 10px; }
.side-word {
  font: 700 10.5px var(--font-heading, "Poppins", sans-serif);
  letter-spacing: .13em; text-transform: uppercase; color: var(--faint); white-space: nowrap;
}
.side-rule { flex: 1 1 auto; height: 1px; background: var(--hair); }
.side-fold {
  flex: none; display: grid; place-items: center; width: 20px; height: 20px;
  padding: 0; border: 0; border-radius: 4px; background: transparent;
  color: var(--faint); font-size: 14px; line-height: 1; cursor: pointer;
}
.side-fold:hover { color: var(--text); background: var(--raise); }
.side-fold:focus-visible { outline: 2px solid var(--hot); outline-offset: -2px; }
.side-body { display: flex; flex-direction: column; gap: var(--tp-step); }
.side-body[hidden] { display: none; }
/* Folded. */
.tpage-body.sideshut { grid-template-columns: minmax(0, 1fr) var(--tp-rail); gap: var(--tp-gap); }
.tpage-body.sideshut .tpage-side { border-left: 1px solid var(--hair); padding-left: 4px; }
.tpage-body.sideshut .side-head { flex-direction: column; gap: 10px; align-items: center; }
.tpage-body.sideshut .side-word { writing-mode: vertical-rl; letter-spacing: .16em; }
.tpage-body.sideshut .side-rule { display: none; }
/* No gap: the rows are a table, and a hairline between them is the
   only line there is (`domain/prop-menu.css`). */
.props { display: flex; flex-direction: column; gap: 0; }

/* History, as a table that a reader opens — not a box of wrapped
   sentences (a teammate, 2026-09-21: "the status updates are messy: one
   single box, random wrapping, no columns"). Five columns that line up
   down the page: when, who, what, the detail, the receipt. */
.tpage-fold > summary {
  display: flex; align-items: center; gap: 12px; cursor: pointer; list-style: none;
  padding: 0 0 14px;
}
.tpage-fold > summary::after {
  content: ""; flex: 1 1 auto; height: 1px; background: var(--hair);
}
.tpage-fold > summary::-webkit-details-marker { display: none; }
.tpage-fold > summary::before {
  content: "›"; display: inline-block; color: var(--faint);
  transition: transform .12s ease; font-size: 14px; line-height: 1;
}
.tpage-fold[open] > summary::before { transform: rotate(90deg); }
.fold-word {
  font: 700 10.5px var(--font-heading, "Poppins", sans-serif);
  letter-spacing: .13em; text-transform: uppercase; color: var(--faint);
}
.history .hhead, .history .hrow {
  display: grid; grid-template-columns: 104px 130px 92px minmax(0, 1fr) auto;
  gap: 10px; align-items: baseline; padding: 6px 0;
}
.history .hhead {
  border-bottom: 1px solid var(--line); color: var(--faint);
  font: 600 9.5px var(--font-heading, "Poppins", sans-serif);
  letter-spacing: .09em; text-transform: uppercase;
}
.history .hhead .hend { text-align: right; }
.history .hrow:hover { background: var(--raise); }
.history .hnote.warn { padding: 1px 8px; font-size: 10.5px; }
@media (max-width: 760px) {
  .history .hhead { display: none; }
  .history .hrow { grid-template-columns: 96px minmax(0, 1fr); }
  .history .hrow .hkind, .history .hrow .hreceipt { display: none; }
}
/* Where this came from: one receipt, then the rest behind "⋯ n more". */
.rcpts .rcpt { margin-bottom: 4px; }
.rcpt-more > summary {
  list-style: none; cursor: pointer; padding: 4px 0;
  font-size: 11.5px; color: var(--link);
}
.rcpt-more > summary::-webkit-details-marker { display: none; }
.rcpt-more > summary:hover { text-decoration: underline; }
.rcpt-more[open] > summary { color: var(--faint); }
.rcpt-more .rcpt { margin-top: 4px; }

/* FLOW: the four lanes as a stepper, and where this task sits in them.
   Drawn for every flow-tagged task now (POP-7710) — the
   `tasks.experimental.flow` pref that used to gate it is gone, along with
   the Approve / Send back pair the box carried (`.flow-acts`), which is
   the approvals story's (POP-5475). What is left is a reading.

   THE BOX IS `.tflow-card`, AND THE `t` IS LOAD-BEARING. It was
   `.flow-card` — the same class the Agent Flow view gives its gate cards
   (`surfaces/flow/flow.css`), which concatenates AFTER this file in
   `TASKS_SHEET`. So this box was being drawn with that view's block,
   padding, shadow and hover-lift: two surfaces, one class name, and the
   later one silently won. The flow view's spelling is pinned by a dozen
   baseline assertions and this box's by one, so this is the side that
   moved. */
.tflow-card {
  display: flex; flex-direction: column; gap: 10px;
  padding: 12px; border: 1px solid var(--hair); border-radius: 8px; background: var(--raise);
}
.flow-steps {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px;
  list-style: none; margin: 0; padding: 0;
}
.flow-steps li {
  display: flex; flex-direction: column; gap: 5px;
  font: 600 9px var(--font-heading, "Poppins", sans-serif);
  letter-spacing: .08em; text-transform: uppercase; color: var(--faint);
}
.flow-steps .bead { display: block; height: 3px; border-radius: 999px; background: var(--hair); }
.flow-steps li.done { color: var(--dim); }
.flow-steps li.done .bead { background: var(--ok); }
.flow-steps li.now { color: var(--hot); }
.flow-steps li.now .bead { background: var(--hot); }
.flow-row { display: grid; grid-template-columns: 52px minmax(0, 1fr); gap: 8px; align-items: baseline; font-size: 12px; }
.flow-lab { color: var(--faint); font-size: 11px; }
.flow-val { min-width: 0; overflow-wrap: anywhere; }
.flow-val.strong { font-weight: 600; color: var(--text); }
.flow-val.mono { font: 11px var(--mono); color: var(--dim); }
/* `.flow-acts` and the `.flow .why-prompt` pair that dressed Send back's
   question went with the buttons (POP-7710). The box is a reading now;
   moving a task along the ladder is POP-5475's. */

/* The reading column keeps its width wherever prose is drawn, so the
   description, the thread and the history all measure the same. */
.tpage-main .tpage-sec { max-width: var(--tp-prose); }
.tpage-main .tpage-sec + .tpage-sec { margin-top: calc(var(--tp-step) * 1.5); }
.tpage-main .comment-add textarea, .tpage-main .desc-editor { max-width: none; }
.tcomment { border: 0; border-radius: 0; background: none; padding: 0 0 4px; margin-bottom: 16px; }
.tcomment.pending { opacity: .75; }

/* Narrow: one column, the description first and the properties under it.
   They stood ABOVE the description for a while, on the argument that
   where the work stands is what a phone reader wants first. Ruling 6
   settles it the other way: the description is the page, on a phone as
   much as on a laptop, and a reader who opens a task to read it should
   not have to scroll past eight property lines to reach the first
   sentence. This rule stands LAST so it wins over the two-column grid
   above it. */
@media (max-width: 900px) {
  .tpage-body, .tpage-body.sideshut {
    grid-template-columns: minmax(0, 1fr); gap: var(--tp-step);
  }
  .tpage-title { font-size: 22px; }
  .tpage-head { flex-wrap: wrap; }
}
@media (max-width: 560px) {
  .tpage { --tp-step: 20px; }
  /* A phone: the same two lines, without the owner's initials. */
  .tline {
    grid-template-columns: auto auto minmax(0, 1fr) auto auto;
    grid-template-areas: "key name name name name" "pri tree flow blocked when";
  }
  .tline .tline-who { display: none; }
  .tline .tline-when.none { display: none; }
  .props { --prop-label: 66px; }
}

/* THE PROPERTIES, ON A PHONE: A PANEL THAT SLIDES IN FROM THE RIGHT
   (POP-9246, the approved mockup).

   Under 900px the rail used to drop below the description, which is the
   right order for reading and the wrong place for acting: the status,
   the owner and the dates were eight scrolls past the thing you opened
   the task to change. So the rail leaves the page and waits off the
   right edge, and a tab stands on that edge holding it.

   THE TAB IS THE HANDLE, not a button somewhere else: open, it rides the
   panel's left edge, so the word on the handle names what slid out and
   closing it is the same gesture in the same place. It is the brand
   secondary rather than the hot ink — standing on the edge of the screen
   is already how it is found, so it need not shout over the prose.

   Shut, the panel is `visibility: hidden` and casts no shadow: a panel
   merely translated off-screen is still in the tab order and still
   leaves a seam of shadow down the edge. */
.tpage-proptab, .side-close, .tpage-scrim { display: none; }
@media (max-width: 900px) {
  .tpage { --drawer-w: min(360px, 88vw); }
  .tpage-body > .tpage-side {
    position: fixed; top: 0; right: 0; bottom: 0; z-index: 41;
    width: var(--drawer-w); padding: 14px 16px; overflow-y: auto;
    background: var(--bg); border-left: 1px solid var(--line);
    transform: translateX(100%); visibility: hidden;
    transition: transform .22s ease, visibility 0s linear .22s;
  }
  .tpage.details-open .tpage-body > .tpage-side {
    transform: none; visibility: visible; box-shadow: -12px 0 32px rgb(0 0 0 / 0.28);
    transition: transform .22s ease;
  }
  .tpage-proptab {
    position: fixed; z-index: 42; top: 50%; right: 0; transform: translateY(-50%);
    display: flex; flex-direction: column; align-items: center; gap: 6px;
    padding: 12px 6px; border: 0; border-radius: 8px 0 0 8px;
    background: var(--c-secondary); color: #fff; cursor: pointer;
    box-shadow: -4px 0 14px rgb(0 0 0 / 0.18);
    transition: right .22s ease, background .15s ease;
  }
  .tpage-proptab:hover { background: var(--fill-secondary-hover); }
  .tpage-proptab:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }
  .proptab-word {
    writing-mode: vertical-rl;
    font: 700 10.5px var(--font-heading, "Poppins", sans-serif); letter-spacing: .13em; text-transform: uppercase;
  }
  .proptab-mark { font-size: 14px; line-height: 1; }
  .tpage.details-open .tpage-proptab { right: var(--drawer-w); }
  .tpage.details-open .tpage-scrim {
    display: block; position: fixed; inset: 0; z-index: 40; background: rgb(0 0 0 / 0.38);
  }
  /* Inside the panel the desktop fold has nothing to fold into — a 28px
     edge strip is a rail and a phone has no room beside the prose for
     one — so the chevron goes and a ✕ takes its place. The body is shown
     whatever the fold habit says, because the panel IS the answer to
     "are the properties up". */
  .tpage-side .side-fold { display: none; }
  .tpage-side .side-body[hidden] { display: flex; }
  /* The desktop's folded skin, undone: inside the panel the rail is not
     an edge strip, so its head reads across and its word reads along. */
  .tpage-body.sideshut .tpage-side { padding-left: 16px; }
  .tpage-body.sideshut .side-head { flex-direction: row; align-items: center; }
  .tpage-body.sideshut .side-word { writing-mode: horizontal-tb; }
  .tpage-body.sideshut .side-rule { display: block; }
  .side-close {
    display: grid; place-items: center; width: 28px; height: 28px; flex: none; order: 3;
    border: 0; border-radius: 6px; background: transparent; color: var(--dim); font-size: 14px; cursor: pointer;
  }
  .side-close:hover { background: var(--raise); color: var(--text); }
}
@media (prefers-reduced-motion: reduce) {
  .tpage-body > .tpage-side, .tpage-proptab { transition: none; }
}

/* What a section that is still fetching says, in the board's own words
   (`lib/loading.js`). It sits where the content will be, so the page
   does not jump when it lands. */
.tpage-loading { margin: 0 0 10px; }

/* A phone (POP-7839): one column already (the 900px rule above); what is
   left is type that reads without zooming, a breadcrumb that cuts rather
   than wraps, and property rows a finger can hit. */
@media (max-width: 768px) {
  .tpage-desc.desc.md, .tpage .desc.md, .desc.md.comment { font-size: 15px; line-height: 1.55; }
  .tpage-head { min-width: 0; }
  .tpage-crumbs { overflow: hidden; white-space: nowrap; flex: 1 1 auto; }
  .tpage-copies { flex: none; }
  .prop-line { min-height: 44px; }
  .tpage-title { font-size: 23px; }
}
/* THE QUESTION BOX: the calls a phase of the agent pipeline is waiting
   on this task's reader to answer (POP-7765, `domain/calls.js`).

   It sits at the TOP of the reading column, above the description: a
   reader who opened the task came to answer something, and everything
   else is context for the question. The second mockup review
   (2026-09-26, "1b") took the shouting out of it: no coloured edge, no
   eyebrow, no beads, no answered count. It is a raised card, a quiet
   Recap, one question at a time with a bar beside its place, and a
   review card at the end holding the only two buttons.

   Emphasis is the brand's PRIMARY, which on this board is `--purple`,
   and never the CTA pink (`--hot`) the rest of the page uses for its own
   buttons: the operator asked for primary here by name.

   Every name here starts `q`. The box it replaced used `.flow-card`,
   `.flow-row`, `.flow-acts` and friends, and those names are ALSO the
   Agent Flow board's (`surfaces/flow/flow.css`, served after this file,
   which quietly won every tie). Nothing here may be spelled `flow-`.

   Colour comes from the semantic layer in `palette.css` and nothing
   else, so `theme-dark.css` dresses both skins from one list. */

.qbox {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 18px 20px;
  border: 1px solid var(--hair);
  border-radius: 12px;
  background: var(--panel);
  box-shadow: var(--shadow);
}
.qbox:focus { outline: none; }

/* The small uppercase word the Recap, each call's kind, and the review
   card's "What approving does" all wear: one style, asked for by name
   ("same styling as Decide"). */
.qbox-k {
  display: block;
  font: 600 10.5px var(--font-body, sans-serif);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--dim);
}

/* ---- the recap: where the work stands, over every step -------------- */

.qbox-recap {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--hair);
}
.qbox-recap p { margin: 0; font-size: 12.5px; line-height: 1.5; color: var(--dim); }

/* ---- where the reader is: the place and a bar, on one row ----------- */

.qbox-place { display: flex; align-items: center; gap: 12px; }
.qbox-of { flex: none; font-size: 12.5px; font-weight: 600; color: var(--purple); white-space: nowrap; }
.qbox-bar { flex: 1; height: 5px; border-radius: 999px; background: var(--purple-soft); overflow: hidden; }
.qbox-fill { display: block; height: 100%; border-radius: inherit; background: var(--purple); transition: width .3s ease; }

/* ---- one call at a time -------------------------------------------- */

.qcall, .qrev { display: flex; flex-direction: column; gap: 10px; }

/* Two classes on purpose: the question is an h3 inside a .tpage-sec, and
   `.tpage-sec h3` (the page's small grey section-label style) outranks a
   lone class, which drew every question as a 10.5px caption. */
.qbox .qcall-q,
.qbox .qrev-decision {
  display: block;
  margin: 0;
  letter-spacing: normal;
  text-transform: none;
  font: 600 16px/1.35 var(--font-heading, "Poppins", sans-serif);
  color: var(--text);
}
.qbox .qcall-q::after,
.qbox .qrev-decision::after { content: none; }

/* The call's one sentence of context, under the question and in the
   same quiet type as an option's detail, so the heading stays a
   headline. */
.qbox .qcall-detail { margin: -4px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--dim); }

/* ---- the options: a label, and what choosing it does ---------------- */

.qcall-opts { list-style: none; margin: 4px 0 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.qopt {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  width: 100%;
  padding: 11px 12px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--bg);
  color: var(--text);
  font: 13px var(--font-body, sans-serif);
  text-align: left;
  cursor: pointer;
  transition: border-color .15s ease, background-color .15s ease;
}
.qopt:hover { border-color: var(--purple-line); background: var(--purple-soft); }
.qopt:focus-visible { outline: 2px solid var(--purple); outline-offset: 2px; }
.qopt.picked { border-color: var(--purple); background: var(--purple-soft); box-shadow: 0 0 0 1px var(--purple); }

.qopt-key {
  flex: none;
  width: 22px;
  height: 22px;
  margin-top: 1px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: 5px;
  background: var(--raise);
  color: var(--dim);
  font: 600 11px var(--mono);
}
.qopt.picked .qopt-key { background: var(--purple); border-color: var(--purple); color: var(--panel); }
.qopt-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.qopt-top { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.qopt-label { flex: 1; min-width: 0; font-weight: 600; overflow-wrap: anywhere; }
.qopt-detail { font-size: 12.5px; line-height: 1.5; color: var(--dim); }
.qopt.picked .qopt-detail { color: var(--text); }
.qopt-rec {
  flex: none;
  padding: 1px 8px;
  border: 1px solid var(--purple-line);
  border-radius: 999px;
  background: var(--purple-soft);
  color: var(--purple);
  font-size: 10.5px;
  font-weight: 600;
}
/* An option that hands the task back says so in the warm colour, beside
   its label, so nobody picks it thinking it is just choice number two. */
.qopt-back { flex: none; font-size: 10.5px; color: var(--warm); white-space: nowrap; }
.qopt-back::before { content: "↺ "; }

.qcall-note,
.qcall-own,
.qrev-comment textarea {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: var(--ctl-radius);
  background: var(--bg);
  color: var(--text);
  font: 12.5px var(--font-body, sans-serif);
  resize: vertical;
}
.qcall-note:focus-visible,
.qcall-own:focus-visible,
.qrev-comment textarea:focus-visible { outline: none; border-color: var(--purple); box-shadow: 0 0 0 2px var(--purple-soft); }

.qcall-ack { display: flex; }
.qack {
  padding: 0;
  border: 0;
  background: none;
  color: var(--dim);
  font: 12px var(--font-body, sans-serif);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.qack:hover { color: var(--purple); }
/* An FYI has nothing else to press, so its acknowledge is a real button. */
.qack.fyi {
  padding: 6px 14px;
  border: 1px solid var(--purple-line);
  border-radius: var(--ctl-radius);
  color: var(--purple);
  font-weight: 600;
  text-decoration: none;
}
.qack.fyi:hover { background: var(--purple-soft); }
.qack.on { color: var(--ok-ink); }
.qack.fyi.on { border-color: var(--ok); background: var(--ok-soft); }

.qcall-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 6px;
  padding-top: 10px;
  border-top: 1px solid var(--hair);
}
.qstep { font-size: 12.5px; font-weight: 600; color: var(--purple); }
.qstep[disabled] { color: var(--faint); opacity: .5; cursor: default; }
.qbox-keys { font-size: 10.5px; color: var(--faint); }

/* ---- the review card ----------------------------------------------- */

.qbox-before { margin: 0; padding-left: 18px; font-size: 12px; color: var(--dim); }
.qbox-before li { margin: 2px 0; }

.qrev-rows { list-style: none; margin: 4px 0 0; padding: 0; border: 1px solid var(--line); border-radius: 9px; overflow: hidden; }
.qrev-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
  gap: 12px;
  align-items: baseline;
  padding: 10px 12px;
  border-top: 1px solid var(--hair);
  font-size: 12.5px;
}
.qrev-row:first-child { border-top: 0; }
.qrev-q { color: var(--dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qrev-q b { color: var(--text); margin-right: 4px; }
.qrev-a { font-weight: 600; color: var(--text); overflow-wrap: anywhere; }
.qrev-a::before { content: "✓ "; color: var(--purple); }
.qrev-row.open .qrev-a { font-weight: 400; color: var(--faint); }
.qrev-row.open .qrev-a::before { content: "○ "; color: var(--faint); }
.qrev-edit { font-size: 12px; color: var(--purple); }

/* A packet with nothing to ask, where the answer rows would sit. The
   card says why it is short in the SOP's own words, so the board and the
   SOP read the same sentence (POP-7902). */
.qrev-none { margin: 4px 0 0; font-size: 12.5px; color: var(--dim); }

/* STOPS AFTER APPROVAL: two buttons that read as one control, so the
   choice is visibly a choice and not a checkbox nobody notices. */
.qbox-stops { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 11.5px; }
.qbox-stops .qbox-k { display: inline; }
.qbox-seg { display: inline-flex; border: 1px solid var(--line); border-radius: 999px; overflow: hidden; background: var(--bg); }
.qseg { padding: 3px 11px; border: 0; background: none; color: var(--dim); font: 11.5px var(--font-body, sans-serif); cursor: pointer; }
.qseg:hover { color: var(--text); }
.qseg.on { background: var(--purple-soft); color: var(--purple); font-weight: 600; }
.qbox-hint { color: var(--faint); }

.qrev-comment { display: flex; flex-direction: column; gap: 6px; }
.qrev-comment-k { font-size: 12.5px; font-weight: 600; color: var(--text); }
.qrev-opt { font-weight: 400; color: var(--dim); }
.qrev-comment textarea[aria-invalid="true"] { border-color: var(--warm); }
.qrev-err { font-size: 11.5px; color: var(--warm); }

/* The record this will leave, before it leaves it: shut until asked. */
.qbox-record { border: 1px solid var(--line); border-radius: 9px; overflow: hidden; }
.qbox-record > summary {
  list-style: none;
  cursor: pointer;
  padding: 9px 12px;
  color: var(--purple);
  font-size: 12.5px;
  font-weight: 600;
}
.qbox-record > summary::-webkit-details-marker { display: none; }
.qbox-record > summary::before { content: "▸ "; }
.qbox-record[open] > summary::before { content: "▾ "; }
.qbox-preview {
  margin: 0;
  padding: 10px 12px;
  border-top: 1px solid var(--hair);
  background: var(--raise);
  color: var(--dim);
  font: 11px/1.6 var(--mono);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.qrev-acts { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: 8px; }
.qrev-acts .btn { height: 32px; font-size: 12px; }
.qbox-back { border-color: var(--purple-line); color: var(--purple); background: var(--bg); }
.qbox-back:hover { background: var(--purple-soft); }
.qbox-submit { border-color: var(--purple); background: var(--purple); color: var(--panel); font-weight: 600; }
.qbox-submit:hover { filter: brightness(1.08); }
.qbox-submit[disabled] { opacity: .45; cursor: not-allowed; filter: none; }
.qrev .qcall-foot { border-top: 0; padding-top: 0; margin-top: 0; }

@media (max-width: 640px) {
  .qbox { padding: 14px; }
  .qbox-keys { display: none; }
  .qrev-row { grid-template-columns: 1fr auto; }
  .qrev-q { grid-column: 1 / -1; white-space: normal; }
  .qrev-acts { flex-direction: column-reverse; }
  .qrev-acts .btn { width: 100%; }
}

/* A phone (POP-7839). The box already stands full width with one option
   to a line; what it needs at this width is type a person reads without
   zooming, and the Back / Next bar kept in reach while a long call is
   scrolled, clear of the home indicator. */
@media (max-width: 768px) {
  .qbox { padding: 16px 14px; }
  .qbox-recap p, .qbox .qcall-detail, .qopt-detail { font-size: 15px; }
  .qopt { min-height: 44px; }
  .qcall-foot {
    position: sticky; bottom: 0; z-index: 1;
    margin-inline: -14px; padding: 6px 14px calc(6px + env(safe-area-inset-bottom));
    background: var(--panel);
  }
  .qstep { min-height: 44px; padding: 0 12px; font-size: 15px; }
}
/* -- one property, one popover ---------------------------------------
   The shape a teammate asked for: "options not all visible at once: a
   property that opens a modal to select the value". A line with a name
   and a value; pressing it opens a list of every value the property can
   take, with the one it carries now ticked. Both skins answer, because
   every colour here is a palette token. */
/* The group reads as a TABLE: one grid of label and value, rows stacked
   flat with a hairline between them and square corners, the whole row the
   control. the operator's walk, 2026-09-23: "the labels sit too close to the
   left edge; alignment is unprofessional — one gutter, labels and values
   on one grid" and "stacked rounded boxes look weird: sharp rectangles
   stacked so it reads as a table, or an actual table / definition list".

   The gutter is 12px from the rail's own edge, for the label and for
   nothing else, so every label and every value in the group begins at
   exactly one x. */
.props { --prop-gutter: 12px; --prop-label: 74px; border-top: 1px solid var(--hair); }
.prop { position: relative; border-bottom: 1px solid var(--hair); }
.prop-line {
  display: grid; grid-template-columns: var(--prop-label) minmax(0, 1fr); gap: 10px;
  align-items: center;
  width: 100%; min-height: 34px; padding: 7px var(--prop-gutter); margin: 0;
  border: 0; border-radius: 0; background: transparent; text-align: left;
  color: var(--text); font: 12.5px var(--font-body, sans-serif); cursor: pointer;
}
.prop-line:hover, .prop.open .prop-line { background: var(--raise); }
.prop-line:focus-visible { outline: 2px solid var(--hot); outline-offset: -2px; }
/* The label is the table's stub column: small caps, quiet, never
   competing with the value beside it. */
.prop-label {
  color: var(--faint); font-size: 10px; font-weight: 600;
  letter-spacing: .07em; text-transform: uppercase;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.prop-value { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-width: 0; }
.prop-said { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.prop-said.dim { color: var(--faint); }
.prop-said.late { color: var(--bad-ink); font-weight: 600; }
.prop-value .prop-mark { width: 20px; height: 20px; font-size: 9px; }
/* The popover itself: anchored under the line it belongs to, above
   everything beside it, and never wider than the rail it sits in.

   On the raised ground and under the drop that every popover on this
   board takes (`--raise`, `--pop-shadow`), rather than on `--panel`
   under the hairline `--shadow`. Those two together are what the
   operator read as "not visually distinct from the background" in the ⚙
   panel on 2026-09-23; this box is the same two, on the same skin, and
   it is the popover a reader of a task opens most.

   THE BOX IS THE POPOVER'S; WHERE IT HANGS IS ITS HOST'S (POP-9246).
   These rules said `.prop .popover` while a property line was the only
   thing that opened one. The same box now opens under a subtask's
   priority chip, under its due date, and under a board row's date
   (`domain/value-pop.js`), and a second copy of "raised ground, hairline
   border, the board's drop" in each place is three boxes that come to
   disagree. So the BOX is named here and each host says only its own
   anchor — the rail stretches the line's width, a task line hangs off
   the control's edge. */
.popover {
  position: absolute; z-index: 40;
  display: flex; flex-direction: column; gap: 1px; padding: 5px;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--raise); box-shadow: var(--pop-shadow);
}
.prop .popover { top: calc(100% - 1px); left: 0; right: 0; }
/* WHAT AN OPTION LOOKS LIKE IS THE OPTION'S, NOT THE BOX'S.
   These rules said `.prop .popover .opt` while the only listbox on the
   board that drew options this way hung under a property line. Since
   2026-09-23 the board's status cell opens the same menu
   (`domain/status-menu.js`) inside a box of its own, anchored under a
   table cell — so the rules name the option, and the two hosts keep only
   their anchors. */
.opt {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 8px; align-items: center;
  padding: 6px 8px; border: 0; border-radius: 5px; background: transparent;
  color: var(--text); font: 12.5px var(--font-body, sans-serif); text-align: left;
  text-decoration: none; cursor: pointer;
}
/* The row under the pointer wears the wash every option list on this
   board wears (`--hot-soft`). It used to be `--raise` — which is the
   ground the popover itself now stands on, and a highlight the colour of
   the thing under it is no highlight. */
.opt:hover { background: var(--hot-soft); }
.opt .opt-word { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The tick is drawn on every option and shown on the chosen one, so the
   rows do not change width as the choice moves. */
.opt .tick { color: var(--hot); visibility: hidden; font-size: 11px; }
.opt.on .tick { visibility: visible; }
.opt.on { font-weight: 600; }
.popover .pickrow, .st-menu .pickrow {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 6px 8px; border-top: 1px solid var(--hair); margin-top: 3px;
  font-size: 11.5px; color: var(--dim);
}
.popover .pickrow input[type="date"], .st-menu .pickrow input[type="date"] {
  height: 26px; padding: 0 6px; border: 1px solid var(--line); border-radius: var(--ctl-radius);
  background: var(--bg); color: var(--text); font: 11.5px var(--font-body, sans-serif);
}
.popover-foot {
  padding: 6px 8px 2px; border-top: 1px solid var(--hair); margin-top: 3px;
  font-size: 10.5px; line-height: 1.4; color: var(--faint);
}
.prop .popover .tagset { display: flex; flex-wrap: wrap; gap: 4px; padding: 4px 6px 6px; }
/* The why-question, asked inside whichever popover the date moved in:
   the rail's Due line, a task line, a board row (POP-9246). It is the
   box's rule for the same reason the box is: one question, one shape,
   wherever it is asked. */
.popover .why-prompt { display: flex; flex-wrap: wrap; gap: 6px; padding: 6px 8px; border-top: 1px solid var(--hair); }
.popover .why-prompt input {
  flex: 1 1 100%; height: 28px; padding: 0 8px; border: 1px solid var(--line);
  border-radius: var(--ctl-radius); background: var(--bg); color: var(--text);
  font: 11.5px var(--font-body, sans-serif);
}
/* The one button under the group that is not a property but a DEED:
   "Remove as objective" (a teammate: it "needs to be more obvious"). */
.prop-action {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  width: 100%; margin-top: 12px; height: 30px; font-size: 12px;
}
/* -- the new-task form ------------------------------------------------ */
/* The dedicated add interface (the operator, 2026-09-01) — the quick-add line
   stays the fast path; this is the whole shape the door vets, one field
   each, defaults spoken (the viewer as owner, today as start). */
.tnew { max-width: 900px; }
.tfield { display: flex; flex-direction: column; gap: 4px; margin-bottom: 12px; }
.tfield .mlab { font: 9.5px var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--faint); }
.tfield input, .tfield select {
  height: 32px; padding: 0 10px; border: 1px solid var(--line); border-radius: 7px;
  background: var(--raise); color: var(--text); font: 13px var(--font-body, sans-serif);
}
.tfield textarea { max-width: none; margin-bottom: 0; border-color: var(--line); }
.tnew-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 16px; }
.tnew-grid .wide { grid-column: 1 / -1; }
@media (max-width: 760px) { .tnew-grid { grid-template-columns: 1fr; } }
.tnew-note { color: var(--dim); font-size: 12px; }
/* -- people-pick: one way to say who, everywhere --------------------- */
people-pick { position: relative; display: inline-flex; align-items: center; gap: 6px; }
people-pick .pp-in {
  height: 26px; width: 200px; padding: 0 9px; border: 1px solid var(--line); border-radius: var(--ctl-radius);
  background: var(--raise); color: var(--text); font: 12px var(--font-body, sans-serif);
}
people-pick[compact] .pp-in { height: 24px; width: 180px; font-size: 11px; }
people-pick[wide] .pp-in { width: 100%; height: 30px; }
people-pick[wide] { display: flex; }
people-pick .pp-said { min-width: 12px; color: var(--ok); font: 12px var(--mono); }
people-pick .pp-list {
  position: absolute; z-index: 30; top: calc(100% + 4px); left: 0; min-width: 260px; max-height: 240px;
  overflow-y: auto; padding: 4px; border-radius: 8px;
  background: var(--raise); border: 1px solid var(--line); box-shadow: var(--pop-shadow);
}
people-pick .pp-item {
  display: flex; align-items: baseline; gap: 8px; width: 100%;
  padding: 6px 9px; border: 0; border-radius: 5px; background: none;
  color: var(--text); font: 12px var(--font-body, sans-serif); text-align: left; cursor: pointer;
}
people-pick .pp-item:hover, people-pick .pp-item.on { background: var(--hot-soft); }
people-pick .pp-item .pp-mail { margin-left: auto; color: var(--faint); font-size: 10.5px; }
people-pick .pp-item .pp-why { color: var(--faint); font: 9.5px var(--mono); letter-spacing: .08em; text-transform: uppercase; }
people-pick .pp-item.pp-new .pp-name { color: var(--purple); }
people-pick .pp-note { padding: 6px 9px; color: var(--dim); font: 11px var(--font-body, sans-serif); }
.tpage-controls .proj-edit .pp-ok, .tpage-controls .own-edit .pp-ok { color: var(--ok); font: 12px var(--mono); }
.tpage-error { margin: -4px 0 10px; padding: 6px 10px; border-radius: 6px; border: 1px solid color-mix(in srgb, var(--warm) 40%, var(--bg)); color: var(--warm); font: 12px var(--font-body, sans-serif); }
.tpage-error .linkbtn { margin-left: 8px; }
.pp-also { margin-left: 4px; font-size: 0.8em; color: var(--dim); border: 1px solid var(--line); border-radius: 8px; padding: 0 5px; }
/* -- the look a picker has, wherever one stands ----------------------- */
/* The box, the list under it, one option, the note: the parts every
   "type to find a thing" on this board is made of. It is one file
   because the two pickers that read from it — `task-pick` (a parent) and
   `project-pick` (a client) — are ONE widget with different rows in it,
   which is the same reason `lib/combobox.js` is one file.

   The people picker still carries its own copy of these rules
   (domain/person-pick.css) and reads identically; folding it in here is
   a tidy-up, not a behaviour, so it is left for the lane that next opens
   that file. */
:is(task-pick, project-pick) {
  position: relative; display: inline-flex; align-items: center; gap: 6px;
}
:is(task-pick, project-pick) .pick-in {
  height: 26px; width: 200px; padding: 0 9px;
  border: 1px solid var(--line); border-radius: var(--ctl-radius);
  background: var(--raise); color: var(--text); font: 12px var(--font-body, sans-serif);
}
:is(task-pick, project-pick)[wide] { display: flex; }
:is(task-pick, project-pick)[wide] .pick-in { width: 100%; height: 30px; }
:is(task-pick, project-pick) .pick-said { min-width: 12px; color: var(--ok); font: 12px var(--mono); }
:is(task-pick, project-pick) .pick-list {
  position: absolute; z-index: 30; top: calc(100% + 4px); left: 0;
  min-width: 280px; max-width: 460px; max-height: 240px;
  overflow-y: auto; padding: 4px; border-radius: 8px;
  background: var(--raise); border: 1px solid var(--line);
  box-shadow: var(--pop-shadow);
}
:is(task-pick, project-pick) .pick-item {
  display: flex; align-items: baseline; gap: 8px; width: 100%;
  padding: 6px 9px; border: 0; border-radius: 5px; background: none;
  color: var(--text); font: 12px var(--font-body, sans-serif); text-align: left; cursor: pointer;
}
:is(task-pick, project-pick) .pick-item:hover,
:is(task-pick, project-pick) .pick-item.on { background: var(--hot-soft); }
/* One line per option, ellipsised: a task's summary is a sentence, and a
   list whose rows wrap to three lines each cannot be walked with the
   arrow keys. */
:is(task-pick, project-pick) .pick-word {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
:is(task-pick, project-pick) .pick-count { color: var(--faint); font-size: 10.5px; }
/* The key pill, as every surface draws it (domain/task-key.js) — without
   its copy affordance, which inside an option would be a control
   fighting the pick. */
task-pick .pick-key { flex: none; }
:is(task-pick, project-pick) .pick-item.pick-new .pick-word { color: var(--purple); }
:is(task-pick, project-pick) .pick-note {
  padding: 6px 9px; color: var(--dim); font: 11px var(--font-body, sans-serif);
}

/* -- the one quick line ----------------------------------------------
   The box that files a task in three seconds, wherever it stands: down
   the rail, where it lands on your own plate under `#quicktask`, and
   under a task's sub-tasks, where it lands under that task. One element
   (`domain/quick-line.js`) and one look, because it was two of each and
   the two behaved differently.

   The box takes the whole width it is given and the ＋ takes what it
   needs, so the line fills its column whatever column that is (the
   2026-09-23 walk: every single-line entry uses the whole width).

   These rules stand here, beside the pickers, rather than in a part of
   their own: the line borrows the pickers' option rows for the work it
   offers, and the served stylesheet is a list of parts in a declared
   order that a deploy has to be restarted to re-read. One part fewer is
   one ordering decision fewer. */
/* One height, said once: the box, the ＋ beside it and the drop the list
   hangs at are the same 30px, and the list is anchored off it rather
   than off the host — which is a different height depending on whether
   anything has just been filed. */
quick-line { display: block; position: relative; --qline-h: 30px; }
/* Over the table it takes the board's whole body: the box grows and the
   ＋ sits at the right edge, in line with the ends of the head and the
   rows under it (the operator, 2026-09-23). */
.board > quick-line, .board > quick-line .qline { width: 100%; }
.qline { display: flex; gap: 6px; align-items: center; }
.qline-in {
  flex: 1; min-width: 0; width: 100%; height: var(--qline-h); padding: 0 10px;
  border: 1px solid var(--line); border-radius: var(--ctl-radius);
  background: var(--bg); color: var(--text); font: 13px var(--font-body, sans-serif);
}
.qline-in::placeholder { color: var(--faint); }
.qline-in:focus { border-color: var(--hot); box-shadow: var(--ring); outline: none; }
.btn.qline-go { flex: none; height: var(--qline-h); padding: 0 11px; font-size: 14px; }
/* What landed, or that the board has not caught up yet. One line, kept
   for a beat, then gone.

   AND NOTHING AT ALL WHEN THERE IS NOTHING TO SAY. It used to hold a
   14px floor open so that the rows below would not jump when a message
   arrived — which meant that most of the time, which is all the time
   nobody has just filed anything, the line was followed by an empty
   element eighteen pixels tall (the operator, on the live board,
   2026-09-23: "some other random empty element under it"). A reserved
   gap costs every reader, always, to spare one reader one small shift
   once; the trade is the wrong way round.

   `:empty` rather than a class the code toggles, because the element is
   a live region (`role="status"`): it has to stay in the document and
   keep its identity so that a screen reader announces what lands in it.
   Its box is a consequence of its content, and there is no second state
   for the JavaScript to get wrong. */
.qline-said {
  display: block; margin: 4px 0 0;
  color: var(--ok); font: 11px var(--font-body, sans-serif);
}
.qline-said:empty { display: none; margin: 0; }
/* The list of work that already exists, for the line that offers it. It
   wears the pickers' own list (`domain/pick.css` draws `.pick-item` and
   `.pick-word` inside those two elements only), so what is repeated here
   is the box around it and nothing about a row.

   IT HANGS OFF THE BOX, AND IT IS NOT THERE WHEN IT IS EMPTY. Two
   things were wrong with one element, and together they are what the
   operator kept seeing over the quick line (2026-09-23, three times:
   "the second element is still over the quick task bar").

   It was anchored at `calc(100% - 18px)` — the HOST's height less the
   line that says what landed, which is a way of saying "under the box"
   that stops being true the moment that line is not there. When the
   empty message line lost its reserved height the host became the box's
   own 30px, and `30 - 18` put the list twelve pixels down: over the
   input, not under it. It is anchored off the box's height now, which is
   the thing it actually wants to clear.

   And it was drawn — filled, bordered, ten pixels tall, at z-index 30 —
   whenever it held nothing, which is whenever nobody is typing. A
   picker's list is its rows; with no rows there is nothing to put a box
   around. */
quick-line .qline-list {
  position: absolute; z-index: 30; top: calc(var(--qline-h) + 4px); left: 0; right: 0;
  max-height: 260px; overflow-y: auto; padding: 4px; border-radius: 8px;
  background: var(--raise); border: 1px solid var(--line);
  box-shadow: var(--pop-shadow);
}
quick-line .qline-list:empty { display: none; }
quick-line .pick-item {
  display: flex; align-items: baseline; gap: 8px; width: 100%;
  padding: 6px 9px; border: 0; border-radius: 5px; background: none;
  color: var(--text); font: 12.5px var(--font-body, sans-serif);
  text-align: left; cursor: pointer;
}
quick-line .pick-item:hover, quick-line .pick-item.on { background: var(--hot-soft); }
quick-line .pick-word {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
quick-line .pick-key { flex: none; }
/* -- tag-pick: one way to say what idea this is ----------------------- */
/* The people picker's twin, and deliberately its twin in look too: the
   two gestures answer the same kind of question ("which of the things
   this board knows"), so they must not read as two different mechanisms. */
tag-pick { position: relative; display: flex; align-items: center; gap: 6px; margin-top: 8px; }
tag-pick .tag-in {
  height: 26px; flex: 1; min-width: 0; padding: 0 9px; border: 1px solid var(--line);
  border-radius: var(--ctl-radius); background: var(--raise); color: var(--text);
  font: 12px var(--font-body, sans-serif);
}
tag-pick .pp-said { min-width: 12px; color: var(--ok); font: 12px var(--mono); }
tag-pick .pp-list {
  position: absolute; z-index: 30; top: calc(100% + 4px); left: 0; right: 0; min-width: 220px;
  max-height: 240px; overflow-y: auto; padding: 4px; border-radius: 8px;
  background: var(--raise); border: 1px solid var(--line);
  box-shadow: var(--pop-shadow);
}
tag-pick .pp-item {
  display: flex; align-items: baseline; gap: 8px; width: 100%;
  padding: 6px 9px; border: 0; border-radius: 5px; background: none;
  color: var(--text); font: 12px var(--font-body, sans-serif); text-align: left; cursor: pointer;
}
tag-pick .pp-item:hover, tag-pick .pp-item.on { background: var(--hot-soft); }
tag-pick .pp-item .pp-why { margin-left: auto; color: var(--faint); font: 9.5px var(--mono); letter-spacing: .08em; text-transform: uppercase; }
tag-pick .pp-item.pp-new .pp-name { color: var(--purple); }
tag-pick .pp-note { padding: 6px 9px; color: var(--dim); font: 11px var(--font-body, sans-serif); }
/* -- the Ideas view: the board grouped by idea ------------------------ */
.tagsview { padding: 4px 0 24px; }
.tags-head h2 { font-size: 15px; margin: 0 0 2px; }
.tags-head p { font-size: 12px; margin: 0 0 14px; }
.tags-capped { font-size: 11.5px; margin: -8px 0 12px; }
.tags-grid {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
.tags-card {
  display: flex; flex-direction: column; gap: 6px; align-items: stretch;
  padding: 12px 14px; border: 1px solid var(--hair); border-radius: 10px;
  background: var(--panel); color: var(--text); text-align: left;
}
.tags-card:hover, .tags-card:focus-within { border-color: var(--hot); }
.tags-head-row { display: flex; align-items: baseline; gap: 8px; }
/* The name is the link to the work; the describe control is its own
   button beside it, never inside it (buttons do not nest, and a span
   with role=button reaches no keyboard). */
.tags-name {
  font-weight: 600; font-size: 13px; padding: 0; border: 0; background: none;
  color: var(--text); font-family: inherit; text-align: left; cursor: pointer;
}
.tags-name:hover, .tags-name:focus-visible { color: var(--hot); }
.tags-desc { margin: 0; font-size: 11.5px; color: var(--dim); }
/* Whose word the description is, on its face: a person's is the rule the
   next reading follows, the brain's is only a summary of what it read. */
.tags-desc.curated { color: var(--text); border-left: 2px solid var(--ok); padding-left: 7px; }
.tags-desc.guessed { font-style: italic; color: var(--faint); }
.tags-desc.none { color: var(--faint); }
.tags-counts { display: flex; gap: 10px; font-size: 11px; color: var(--dim); margin: 0; }
.tags-open { color: var(--hot); font-weight: 600; }
.tags-bar { display: block; height: 3px; border-radius: 999px; background: var(--hair); overflow: hidden; }
.tags-bar span { display: block; height: 100%; background: var(--ok); }
.tags-pen { font-size: 10.5px; margin-left: auto; color: var(--faint); }
.tags-card:hover .tags-pen, .tags-pen:focus-visible { color: var(--hot); }
.tags-write { display: flex; flex-direction: column; gap: 6px; }
.tags-write textarea {
  width: 100%; padding: 6px 8px; border: 1px solid var(--line); border-radius: var(--ctl-radius);
  background: var(--bg); color: var(--text); font: 11.5px var(--font-body, sans-serif);
  resize: vertical;
}
.tags-write-acts { display: flex; gap: 6px; }
.tags-write-acts .btn { height: 24px; font-size: 11px; }
/* -- Agent Flow: the pipeline as swim lanes ---------------------------
   Four columns, one per phase, and two rows: yours on top, the agents'
   underneath. White space separates the cells — there are no rules at
   all — and every cell is its own washed panel. A wash steps along one
   hue path, accent -> purple -> blue -> cool, deepening toward Done; the
   agent panel takes its column's hue one step deeper, so the two read as
   siblings across the gap that divides them.

   Every colour here is mixed from a SEMANTIC var against `--bg`, never
   from a brand token and never from a hex. That is the whole of what
   makes the dark skin work: `--bg` and the hues both flip, so the
   washes follow with no second block to keep in step. -------------- */

.flowview {
  --flow-hue-1: var(--hot);
  --flow-hue-2: var(--purple);
  --flow-hue-3: var(--blue);
  --flow-hue-4: var(--cool);
  /* the space between a cell's cards and its edge, held inside the wash */
  --flow-gutter: 13px;
  /* white space, not rules, separates the cells. The row gap has to hold
     a down arrow and the column gap a hop, so both are sized for one. */
  --flow-col-gap: 20px;
  --flow-row-gap: 28px;
  --flow-radius: 14px;
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding: 2px 0 8px;
  gap: 8px;
}

.flow-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
}
.flow-head h2 {
  font-size: 15px;
  margin: 0;
}
.flow-live {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
  color: var(--dim);
  font-variant-numeric: tabular-nums;
}
.flow-beat {
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: var(--ok);
  flex: none;
}
@media (prefers-reduced-motion: no-preference) {
  .flow-beat {
    animation: flow-blink 1.4s step-end infinite;
  }
}
@keyframes flow-blink {
  50% {
    opacity: 0.25;
  }
}
.flow-capped {
  font-size: 11.5px;
  color: var(--dim);
  margin: 0;
}
/* the board shell's own children: the body holds the board, so the gap
   between head, toolbar and board is the view's one gap */
.flow-body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-height: 0;
}

/* ---- the toolbar: search this board, and the order its cards take ---
   The search pill is the header box's shape (board.css `.search`), held
   narrower: it narrows one board, not every task. */

.flow-toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
}
.flow-search {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 1 380px;
  min-width: 200px;
  height: 30px;
  padding: 0 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--raise);
}
.flow-search:focus-within {
  border-color: var(--hot);
  box-shadow: var(--ring);
}
.flow-search .glass {
  color: var(--faint);
  font-size: 13px;
}
.flow-search input {
  flex: 1;
  min-width: 0;
  height: 100%;
  padding: 0;
  border: 0;
  border-radius: 0;
  outline: 0;
  box-shadow: none;
  background: transparent;
  font: 13px var(--font-body, sans-serif);
  color: var(--text);
}
.flow-search input::placeholder {
  color: var(--faint);
}
.flow-sort {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
  color: var(--dim);
}
.flow-duefilter {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
  color: var(--dim);
}
.flow-duedays {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
  color: var(--dim);
}
.flow-duedays[hidden] {
  display: none;
}
.flow-duedays input {
  width: 64px;
  height: 30px;
  padding: 0 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--raise);
  font: 12.5px var(--font-body, sans-serif);
  color: var(--text);
}
.flow-sort select,
.flow-duefilter select {
  height: 30px;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--raise);
  font: 12.5px var(--font-body, sans-serif);
  color: var(--text);
}
.flow-sort select:focus-visible,
.flow-duefilter select:focus-visible,
.flow-duedays input:focus-visible {
  outline: 0;
  border-color: var(--hot);
  box-shadow: var(--ring);
}

/* the direction: the order's own words ("Longest first"), not asc/desc */
.flow-dir {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 30px;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--raise);
  font: 12.5px var(--font-body, sans-serif);
  color: var(--text);
  cursor: pointer;
  white-space: nowrap;
}
.flow-dir [data-arrow] {
  color: var(--dim);
}
.flow-dir:hover {
  border-color: var(--dim);
}
.flow-dir:focus-visible {
  outline: 0;
  border-color: var(--hot);
  box-shadow: var(--ring);
}

/* ---- the fold under a long cell ------------------------------------ */

.flow-more {
  align-self: flex-start;
  margin: 2px 0 0;
  padding: 3px 9px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  font: 600 11px var(--font-body, sans-serif);
  color: var(--dim);
  cursor: pointer;
}
.flow-more:hover,
.flow-more:focus-visible {
  outline: 0;
  background: color-mix(in srgb, var(--panel) 70%, transparent);
  color: var(--text);
}

/* a due date, where a card has one; red once it is late */
.flow-due {
  font-size: 10.5px;
  color: var(--dim);
  white-space: nowrap;
}
.flow-due.is-late {
  color: var(--bad);
  font-weight: 700;
}

/* ---- the grid ------------------------------------------------------ */

.flow-board {
  display: grid;
  flex: 1 1 auto;
  min-height: 0;
  min-width: 0;
  grid-template-columns: 52px repeat(3, minmax(236px, 1fr)) minmax(210px, 0.94fr);
  /* The two card rows HUG their contents, and the board grows with them
     (POP-7710). They used to take a share of the window's height
     (`minmax(min-content, 0.85fr)` and `1.15fr`), which is what made each
     cell a scroll container and what took the wheel away from the page —
     see `.flow-cell` below. Hugging is what lets a lane show all its work
     without trapping a gesture; the fold at five cards is what keeps the
     page from running away. */
  grid-template-rows: auto auto auto;
  column-gap: var(--flow-col-gap);
  row-gap: var(--flow-row-gap);
  position: relative;
  overflow-x: auto;
  overflow-y: visible;
}
.flow-board > * {
  position: relative;
  min-width: 0;
}

.flow-c-l1 {
  --flow-wash: color-mix(in srgb, var(--flow-hue-1) 7%, var(--bg));
  --flow-deep: color-mix(in srgb, var(--flow-hue-1) 15%, var(--bg));
  grid-column: 2;
}
.flow-c-l2 {
  --flow-wash: color-mix(in srgb, var(--flow-hue-2) 10%, var(--bg));
  --flow-deep: color-mix(in srgb, var(--flow-hue-2) 19%, var(--bg));
  grid-column: 3;
}
.flow-c-l3 {
  --flow-wash: color-mix(in srgb, var(--flow-hue-3) 12%, var(--bg));
  --flow-deep: color-mix(in srgb, var(--flow-hue-3) 22%, var(--bg));
  grid-column: 4;
}
.flow-c-l4 {
  --flow-wash: color-mix(in srgb, var(--flow-hue-4) 14%, var(--bg));
  --flow-deep: color-mix(in srgb, var(--flow-hue-4) 24%, var(--bg));
  grid-column: 5;
}
.flow-r-you {
  grid-row: 2;
}
.flow-r-agent {
  grid-row: 3;
}
.flow-r-full {
  grid-row: 2 / 4;
}

/* ---- rail: names the two halves, nothing else ---------------------- */

.flow-rail {
  grid-column: 1;
  display: flex;
  align-items: flex-start;
  justify-content: flex-end;
  padding: 10px 8px 0 0;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--faint);
  text-align: right;
  line-height: 1.3;
}
.flow-rail-you {
  color: var(--hot);
}

/* ---- column head: over the column, on the page, not on the wash ---- */

.flow-col-head {
  display: flex;
  align-items: baseline;
  padding: 0 var(--flow-gutter) 0 2px;
  /* the row gap is sized for an arrow; a heading needs a hair of room */
  margin-bottom: calc(8px - var(--flow-row-gap));
}
.flow-col-head h3 {
  margin: 0;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text);
  line-height: 1.3;
}

/* ---- cells: each one a panel, bounded by white space alone --------- */

/* THE WHEEL BELONGS TO THE PAGE (POP-7710). A cell used to be its own
   scroll container — `overflow: hidden auto` with `overscroll-behavior:
   contain` — so a wheel turned anywhere over a cell, which is to say
   anywhere over a card, scrolled the cell and never chained out to the
   board behind it. On a cell with nothing to scroll that reads as a dead
   wheel: the pointer is over the view, the view does not move, and the
   reader has to find a strip of white space to scroll the page from
   (reported 2026-09-25).

   So the cells do not scroll at all now, and the grid's two card rows hug
   their contents (`.flow-board`) instead of taking a share of the window's
   height. The page grows and the board's own scroll carries it, which is
   the ordinary way a long page behaves. What keeps that growth bounded is
   the fold: a cell draws five cards and a "Show N more" (`flow.js`), so
   only a column a reader has opened is long. */
.flow-cell {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px var(--flow-gutter) 12px;
  background: var(--flow-wash);
  border-radius: var(--flow-radius);
}
.flow-cell.flow-r-agent {
  background: var(--flow-deep);
}
.flow-lbl {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 1px 2px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--hot);
}
.flow-lbl-agent {
  color: var(--dim);
}

/* The two group labels inside an agent cell (POP-8631): Running over the
   claimed half, Queued over the half still waiting for pickup.

   Quieter and smaller than the cell's own `.flow-lbl`, which is the
   column's context ("In planning") and has to stay the heading a reader
   sees first. These sit between cards, so they carry top room and none
   above the first one. */
.flow-grp {
  padding: 4px 1px 1px;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--faint);
}
.flow-lbl + .flow-grp {
  padding-top: 0;
}
.flow-lbl-cand {
  color: var(--dim);
}
.flow-none {
  margin: 2px 1px;
  font-size: 11px;
  color: var(--faint);
  font-style: italic;
}

/* ---- the intake corner: Agent suggested ---------------------------- */

/* The band is the whole of the first column's top cell, so it needs no
   line above it and no panel around it: no border, no background. A box
   here would read as a third column in a board that has four (POP-8883).
   Empty it draws `.flow-none` above, one quiet line, which is the whole
   of "nothing bulky when there is nothing". */
/* A LIST OF BLOCKS, NOT A TABLE (POP-9247). It was a `table-layout: fixed`
   table whose acts column declared `width: 1%` with `white-space: nowrap`:
   a column asking to be as narrow as its content, in a layout that hands
   out width by declaration. Three pill buttons in the first column of a
   four-column board, and on a phone, overflowed the card they belong to,
   and the last of them could not be reached at all.

   Each row is its two parts now — what it is, and the three words that
   answer it — and the acts drop under the title when there is not room
   beside it. `min-width: 0` on both parts is what keeps the row inside the
   cell: without it a long title sets a floor the row cannot shrink below,
   which is the table's failure again in flex clothing. */
.flow-cand-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}
.flow-cand-row {
  display: flex;
  flex-wrap: wrap;
  align-items: start;
  justify-content: space-between;
  gap: 2px 6px;
  min-width: 0;
}
.flow-cand-what {
  flex: 1 1 60%;
  min-width: 0;
}
/* Two lines of title, then an ellipsis: a suggestion is read as a list and
   a four-line title would push the one under it out of the corner. */
.flow-cand-title {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 11.5px;
  line-height: 1.35;
  color: var(--text);
  text-decoration: none;
}
.flow-cand-title:hover {
  text-decoration: underline;
}
/* Who flagged it and why, in one line that does not compete with the
   title: a reader skims the titles and reads this one only for the row
   they are deciding. Clipped to two lines rather than ellipsed on one —
   a reason is a sentence, and half a sentence is not a reason. */
.flow-cand-why {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 10.5px;
  line-height: 1.3;
  color: var(--faint);
}
/* The band waiting on its own page: the house ring and a word, at the size
   the quiet line beside it reads at (`lib/loading.css` owns the ring). */
.flow-cand-wait {
  margin: 2px 1px;
  font-size: 11px;
  color: var(--faint);
}
/* The three answers: beside the title where the column is wide enough for
   them, wrapped under it where it is not, and never outside the card. They
   may wrap among themselves too — one pill per line is a narrow phone's
   honest answer and is still inside the card. */
.flow-cand-acts {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 4px;
  min-width: 0;
}
.flow-cand-act {
  padding: 2px 7px;
  font-size: 10.5px;
  font-weight: 600;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 999px;
  cursor: pointer;
}
.flow-cand-act:hover,
.flow-cand-act:focus-visible {
  border-color: var(--hot);
  color: var(--hot);
}
/* Plan it's due-date prompt, put in place under the band (lib/reason.js
   `askDate`). The same one-line shape the drop reason and "are you
   sure?" wear, narrowed to the room a column has. */
.flow-cand .date-ask {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}
.flow-cand .date-ask .sure-q {
  font-size: 11px;
  color: var(--text);
  flex-basis: 100%;
}
.flow-cand .date-ask input {
  height: 26px;
  padding: 0 6px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--bg);
  color: var(--text);
  font: 11px var(--font-body, sans-serif);
}

/* ---- the gate card: a raised block, nothing more ------------------- */

/* TALLER THAN IT WAS (POP-9247), and deliberately. The top row no longer
   wraps and the card now carries a line of its own underneath — why a
   queued card has not been picked up, or how long a gate card has been
   waiting on its reader — so the room the wrap used to take at random is
   given back as padding every card has. A card is three bands now: the
   chips, the title, and the mark. */
.flow-card {
  position: relative;
  display: block;
  background: var(--panel);
  border-radius: 8px;
  box-shadow: var(--shadow);
  padding: 7px 9px 9px;
  overflow: hidden;
  transition: box-shadow 0.15s ease, transform 0.15s ease;
}
.flow-card:hover,
.flow-card:focus-within {
  box-shadow: var(--pop-shadow);
  transform: translateY(-1px);
}
.flow-hit {
  display: block;
  text-decoration: none;
  color: inherit;
}
/* NO BORDER ON HOVER (POP-7710). The card already answers the pointer, and
   it answers it the way this view's cards are meant to: the lift and the
   deeper shadow above. What drew a hairline rectangle inside the card as
   well was `[data-tip]:hover` (`lib/tip.css`) — the one-border rule that
   tells a reader "there are words here", written for chips and marks. This
   link carries `data-tip` and covers the whole card, so at card scale that
   rule drew a box round a block that was already lifting, and the two read
   as two different pieces of feedback for one gesture.
   Reset HERE rather than narrowing the global rule: it feeds hover
   feedback on chips, pills and planning cards across the whole app, and
   this view is the one place it lands on something card-sized. Focus keeps
   its edge — a keyboard reader has nothing else telling them where they
   are, and `:focus-visible` in tip.css is not touched. */
.flow-hit:hover {
  box-shadow: none;
}
/* the whole card is the hit area, without nesting anything in the link */
.flow-hit::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}
.flow-card-top,
.flow-dcard-top {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 10.5px;
  color: var(--dim);
  line-height: 1.2;
}
/* The key and the priority pills are the house's own renderers
   (domain/task-key.js, domain/priority.js); they bring their width and
   their type with them, so nothing here restyles them. */
/* The one member of the top row that gives way, so it is the only one that
   may shrink below its content and the only one that ellipses. */
.flow-proj {
  color: var(--faint);
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.flow-age,
.flow-elapsed {
  margin-left: auto;
  font-size: 10.5px;
  font-variant-numeric: tabular-nums;
  color: var(--dim);
  white-space: nowrap;
}
.flow-elapsed {
  font-weight: 700;
  color: var(--ok-ink);
}
/* time in lane, banded: fresh up to 4h, staling from 2d, stuck from 9d */
.age-2 .flow-age,
.age-2 .flow-elapsed {
  color: var(--warm);
  font-weight: 700;
}
.age-3 .flow-age,
.age-3 .flow-elapsed {
  color: var(--bad);
  font-weight: 700;
}
.flow-card-title {
  display: block;
  margin: 3px 0 0;
  font-size: 12px;
  line-height: 1.24;
  color: var(--text);
}

/* ---- agent cards: the same block, quieter -------------------------- */

.flow-dcard {
  position: relative;
  display: block;
  background: color-mix(in srgb, var(--panel) 66%, transparent);
  border-radius: 8px;
  padding: 6px 9px 8px;
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transition: background-color 0.15s ease, box-shadow 0.15s ease;
}
.flow-dcard:hover,
.flow-dcard:focus-within {
  background: var(--panel);
  box-shadow: var(--shadow);
}
.flow-dcard-title {
  display: -webkit-box;
  margin: 3px 0 0;
  font-size: 11.5px;
  line-height: 1.22;
  color: var(--dim);
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.flow-dcard.running .flow-dcard-title {
  font-weight: 500;
  color: var(--text);
}
/* The agent card's second row, its state (POP-9247): the dot, then the
   machine a running card is on or the reason a queued one is held. Every
   agent card has one, so every card reads in the same three bands. The
   held mark is a button and stands above the card's hit layer; the rest of
   the row stays under it, so a click there still opens the task. */
.flow-dcard-state {
  display: flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
  min-height: 16px;
  margin-top: 4px;
  line-height: 1.2;
}
.flow-dcard-state .held.held-line {
  position: relative;
  z-index: 2;
  flex: 1 1 auto;
  width: auto;
  min-width: 0;
}

/* ---- copy the id, and where the work is running (POP-8069) ---------- */

/* Room at the top right for ⧉, which sits above the card's hit layer.

   AND THE TOP ROW NEVER WRAPS (POP-9247). It used to: a narrow card wrapped
   its time in lane to a second line rather than clipping it, which was the
   kinder of the two answers then available. On the live board it was the
   defect a reader met first — in the two narrow columns, and on a phone,
   nearly every card carried a second line holding one word, so the chips a
   card is read by (priority, key, due) sat above a stray "3d" and every
   card in the lane was a different height.

   Clipping is the answer now because nothing on this row needs to be
   clipped: the project is the one member that can be any length and it
   ellipses, and everything else is a chip or a short word with a width of
   its own. `min-width: 0` is what lets the project actually shrink — a flex
   item will not go below its content's width without it, which is how a
   nowrap row overflows its card instead of ellipsing inside it. */
.flow-card-top,
.flow-dcard-top {
  padding-right: 16px;
  flex-wrap: nowrap;
  min-width: 0;
}
/* Everything on the row but the project holds its size, so the project is
   what gives way. Named here rather than on the chips' own rules: their
   width and type are the house renderers' (domain/priority.js,
   domain/task-key.js) and this is only how they behave in this row. */
.flow-card-top > :is(.pri, .t-key, .flow-due, .flow-age, .flow-elapsed, .flow-host, .flow-dot),
.flow-dcard-top > :is(.pri, .t-key, .flow-due, .flow-age, .flow-elapsed, .flow-host, .flow-dot) {
  flex: none;
}
.flow-copy {
  position: absolute;
  top: 3px;
  right: 4px;
  z-index: 2;
  padding: 0 3px;
  border: 0;
  border-radius: 4px;
  background: transparent;
  font-size: 11px;
  line-height: 16px;
  color: var(--faint);
  cursor: pointer;
}
.flow-copy:hover,
.flow-copy:focus-visible {
  color: var(--hot);
  background: var(--raise);
}
.flow-host {
  padding: 0 5px;
  border-radius: 999px;
  font: 600 9.5px var(--mono);
  letter-spacing: 0.02em;
  white-space: nowrap;
  border: 1px solid var(--hair);
  color: var(--dim);
}
.flow-host.is-popbox {
  color: var(--cool);
  background: var(--cool-soft);
  border-color: transparent;
}

/* ---- the two state dots -------------------------------------------- */

.flow-dot {
  width: 8px;
  height: 8px;
  border-radius: 999px;
  flex: none;
  display: inline-block;
}
.flow-dot-run {
  background: var(--ok);
}
@media (prefers-reduced-motion: no-preference) {
  .flow-dcard.running .flow-dot-run {
    animation: flow-blink 1.4s step-end infinite;
  }
  /* out of lockstep, so four running agents do not strobe as one */
  .flow-dcard:nth-of-type(2n) .flow-dot-run {
    animation-delay: -0.47s;
  }
  .flow-dcard:nth-of-type(3n) .flow-dot-run {
    animation-delay: -0.93s;
  }
}

/* ---- the two marks a card can carry -------------------------------- */

.flow-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 5px;
  position: relative;
  z-index: 2;
}
.flow-chip {
  display: inline-flex;
  align-items: center;
  padding: 1px 6px;
  border-radius: 4px;
  font-size: 10px;
  font-weight: 700;
  line-height: 1.5;
}
.flow-chip.is-bounced {
  background: var(--warm-soft);
  color: var(--warm);
}
.flow-chip.is-hung {
  background: var(--bad-soft);
  color: var(--bad-ink);
}

/* ---- the mark that is a line, not a chip (POP-9247) ----------------- */

/* Why a queued card has not been picked up, and on a gate card how long it
   has been waiting on its reader: the same mark (`domain/held.js`,
   `domain/held.css`), drawn as a full line of the card rather than as one
   more chip in the row. A whole row of its own, because it is a sentence:
   sharing the line with the bounced and hung chips would leave it a few
   characters wide on exactly the narrow cards that need it most. */
.flow-chips .held.held-line {
  flex: 1 0 100%;
  min-width: 0;
}

/* The pickup mark takes the card's own age band. The bands are the
   board's, not this mark's — they are the same four `.flow-age` wears a few
   pixels above — so a card that reads "stuck" reads stuck twice rather than
   once in each of two vocabularies. */
.age-2 .held.is-pickup {
  color: var(--warm);
  font-weight: 700;
}
.age-3 .held.is-pickup {
  color: var(--bad);
  font-weight: 700;
}

/* ---- flow arrows, drawn only in the white space --------------------
   `down` sits in the row gap: a gate releasing the work beneath it.
   `hop`  sits in the column gap, carrying finished agent work right and
          up to the next gate. Neither overlaps a panel, so nothing has
          to fight for stacking order. ------------------------------- */

.flow-arrow {
  position: relative;
  z-index: 2;
  pointer-events: none;
  overflow: visible;
  fill: none;
  stroke: color-mix(in srgb, var(--text) 34%, transparent);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.flow-arrow.down {
  justify-self: center;
  align-self: end;
  width: 16px;
  height: var(--flow-row-gap);
  margin-bottom: calc(-1 * var(--flow-row-gap));
}
.flow-arrow.hop {
  justify-self: end;
  align-self: start;
  width: var(--flow-col-gap);
  height: calc(var(--flow-row-gap) + 52px);
  margin-top: calc(-1 * (var(--flow-row-gap) + 30px));
  margin-right: calc(-1 * var(--flow-col-gap));
}

/* ---- footer legend -------------------------------------------------- */

.flow-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 22px;
  padding: 8px 2px 0;
  border-top: 1px solid var(--hair);
  font-size: 10.5px;
  color: var(--dim);
}
.flow-footer span {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.flow-footer kbd {
  font-family: var(--mono);
  font-size: 10px;
  padding: 1px 5px;
  border: 1px solid var(--line);
  border-bottom-width: 2px;
  border-radius: 4px;
  background: var(--raise);
  color: var(--text);
}
.flow-footer b {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}
.sw-fresh {
  color: var(--dim);
}
.sw-warm {
  color: var(--warm);
}
.sw-hot {
  color: var(--bad);
}
.flow-odd {
  color: var(--bad);
}

/* Narrow: the project is the first thing a card can spare. */
@media (max-width: 1320px) {
  .flow-proj {
    display: none;
  }
}
@media (max-width: 900px) {
  .flow-board {
    grid-template-columns: 40px repeat(3, minmax(200px, 1fr)) minmax(180px, 0.94fr);
  }
}
/* A phone: one column, the lanes in ladder order (POP-7839). The four-lane
   grid stands on an 820px minimum, which at 390 was a board scrolling
   sideways inside the page. Each lane becomes its heading with its two
   cells under it, the reader's half first; the rail that names the halves
   and the arrows that live in the grid's gaps have no gaps to live in,
   and each cell still carries its own label. `order` does the regrouping,
   so the markup, and every desktop rule above, is untouched. */
@media (max-width: 768px) {
  .flow-board {
    display: flex;
    flex-direction: column;
    gap: 8px;
    overflow-x: visible;
  }
  .flow-board > :is(.flow-rail, .flow-arrow) {
    display: none;
  }
  .flow-col-head {
    margin: 10px 0 0;
  }
  .flow-c-l1 { order: 10; }
  .flow-c-l2 { order: 20; }
  .flow-c-l3 { order: 30; }
  .flow-c-l4 { order: 40; }
  .flow-c-l1.flow-r-you { order: 11; }
  .flow-c-l1.flow-r-agent { order: 12; }
  .flow-c-l2.flow-r-you { order: 21; }
  .flow-c-l2.flow-r-agent { order: 22; }
  .flow-c-l3.flow-r-you { order: 31; }
  .flow-c-l3.flow-r-agent { order: 32; }
  .flow-c-l4.flow-r-full { order: 41; }
}
/* ---- leaving a page with unsaved words ------------------------------ */
.leave-veil {
  position: fixed; inset: 0; z-index: 60;
  background: color-mix(in srgb, var(--bg) 60%, transparent);
  display: grid; place-items: center;
}
.leave-card {
  background: var(--panel); color: var(--text);
  border: 1px solid var(--line); border-radius: 12px;
  padding: 20px 22px; width: min(440px, calc(100vw - 32px));
  box-shadow: 0 18px 60px rgba(0, 0, 0, 0.45);
}
.leave-card h3 { margin: 0 0 6px; font-size: 1.05rem; }
.leave-card p { margin: 0 0 14px; color: var(--dim); }
