/* GENERATED by src/build-css.mjs from mockup/console.html + src/web/extra.css.
   Do not edit this file; edit one of those two and rebuild. */
/* ============================================================
     TOKENS — light is authoritative; dark redefines the same names.
     Neutrals carry a slight blue bias toward the steel accent so the
     greys read as chosen. Semantic colours are DELIBERATELY the
     roadmap sheet's own vocabulary: the owner already reads red as
     "waiting on me", orange as "an agent is on it", green as
     "shipped". Keeping them is honouring an existing system.

     ==================== THE 2026-08 CONSOLE REDESIGN ====================
     The values below are §8 of the console redesign handoff, not the
     originals. They are edited HERE, in the mockup, rather than added as a
     second :root in extra.css — which is the change that suggests itself,
     and is wrong. build-css.mjs concatenates this file's <style> blocks and
     then extra.css, so a second :root would win the cascade and there would
     be TWO writers of --accent with nothing to say which was current. That
     is AGENTS.md rule 2, and the failure it names is exactly this one: the
     dark blocks below would keep the old accent, so a dark-mode reader and a
     light-mode reader would be looking at two different products.

     FOUR COPIES, AND THEY MOVE TOGETHER. Light, the dark media query, the
     dark attribute and the light attribute. The file already said so; it is
     restated because a redesign is when three of the four get updated.

     WHAT IS NEW RATHER THAN CHANGED. The handoff names shades the old
     palette had no word for, so they get names rather than being written as
     literals in components:
       --ink-hd     card header titles          (§8 "ink-3")
       --ink-empty  BLANK AND NOT-SET VALUES ONLY. This one is load-bearing:
                    it is how a reader tells "the sheet says nothing" from
                    "we did not read it", so it may not be reused for
                    ordinary quiet text.
       --on-wait    the ink that sits ON a --wait fill. Light it is white;
                    dark, --wait is a pale red and the ink has to go dark.
                    It exists because .alarm was doing that with two
                    theme-scoped rules, which is the one thing this file
                    says components may never do.
       --rule-3     separators inside a card body
       --accent-tint  a selected card, a reporter's turn
       --wait-bd / --active-bd / --done-bd / --idle-bd
                    each status tone is {fg, bg, border}; the old palette
                    only had two of the three and components invented the
                    third inline.
       --tint-attn  the row tint for "this needs a person"

     --ink-3 IS §8's ink-muted (#6b7280), NOT its ink-quiet (#9ca3af). Every
     existing page uses --ink-3 for secondary prose — the notification body,
     the row meta, the field help — and #9ca3af measures 2.6:1 on white,
     which fails the 4.5:1 floor this file's own comments cite.

     AND --ink-4 IS GONE, WHICH IS THE END OF THAT ARGUMENT. It held #9ca3af
     for "mono labels and timestamps" — 2.54:1 on white. The comment above
     rejected that shade for prose on exactly this ground and then kept it for
     the SMALLEST text in the product: the count beside a card title at 11px,
     and the micro-label above every control on every form at 10px. A token
     whose only job is to be too light is not a token. All 29 users now say
     --ink-3, which is 4.83:1 and was already what the paragraph above wanted.
     ============================================================ */
  /* ============ THE BROWSER'S OWN WIDGETS FOLLOW THE THEME ============
     Every colour below is a token this stylesheet paints. `color-scheme` is the
     one thing it CANNOT paint: the parts of a control the UA draws itself — the
     "Choose File" button, scrollbars, the spinner in a number field, a date
     picker. Without this declaration those default to the LIGHT rendering
     whatever the tokens say, which is why the attach-a-screenshot control on
     the item page rendered as a white pill on a near-black card on his phone.
     Declared alongside the tokens in all four blocks, and for the same reason
     the tokens are: one theme, decided in one place, never per component. */
  :root {
    color-scheme: light;
    --ground: #f6f7f8;  --panel: #ffffff;  --panel-2: #fafbfc;
    --ink: #111827;     --ink-2: #374151;  --ink-3: #6b7280;
    --ink-hd: #4b5563;  --ink-empty: #878d96;  --on-wait: #ffffff;
    --rule: #e5e7eb;    --rule-2: #eef0f2; --rule-3: #f3f4f6;
    --accent: #1d4ed8;  --accent-soft: #eaf0fd; --accent-tint: #f8fafd;
    --wait: #b91c1c;    --wait-bg: #fef2f2;  --wait-bd: #fecaca;
    --active: #b45309;  --active-bg: #fffbeb; --active-bd: #fde68a;
    --done: #15803d;    --done-bg: #f0fdf4;  --done-bd: #bbf7d0;
    --idle: #4b5563;    --idle-bg: #f3f4f6;  --idle-bd: #e5e7eb;
    --clash: #b45309;   --clash-bg: #fffbeb;
    --tint-attn: #fffdf5;
    --shadow: 0 1px 2px rgba(0,0,0,.07);
    --ui: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  }
/* Dark theme. One token list, repeated for the media query and the explicit
     toggle so the two can never drift. Components are styled through tokens
     only — never inside a theme block. */
  @media (prefers-color-scheme: dark) {
    :root {
      color-scheme: dark;
      --ground: #0e1114;  --panel: #171b20;  --panel-2: #1d2228;
      --ink: #e8ecef;     --ink-2: #a3aeb8;  --ink-3: #8b96a1;
      --ink-hd: #c3ccd4;  --ink-empty: #6b7280;  --on-wait: #14171b;
      --rule: #2b323a;    --rule-2: #222831; --rule-3: #1e242b;
      --accent: #7aa7f5;  --accent-soft: #1b2a45; --accent-tint: #161d29;
      --wait: #f08b8b;    --wait-bg: #2d1616;  --wait-bd: #4a2020;
      --active: #e0a94f;  --active-bg: #2c2110; --active-bd: #4a3a18;
      --done: #6ec98a;    --done-bg: #12251a;  --done-bd: #1f4029;
      --idle: #9aa3ad;    --idle-bg: #232930;  --idle-bd: #2b323a;
      --clash: #e0a94f;   --clash-bg: #2c2110;
      --tint-attn: #241f14;
      --shadow: 0 1px 2px rgba(0,0,0,.4);
    }
  }
  :root[data-theme="dark"] {
    color-scheme: dark;
    --ground: #0e1114;  --panel: #171b20;  --panel-2: #1d2228;
    --ink: #e8ecef;     --ink-2: #a3aeb8;  --ink-3: #8b96a1;
    --ink-hd: #c3ccd4;  --ink-empty: #6b7280;  --on-wait: #14171b;
    --rule: #2b323a;    --rule-2: #222831; --rule-3: #1e242b;
    --accent: #7aa7f5;  --accent-soft: #1b2a45; --accent-tint: #161d29;
    --wait: #f08b8b;    --wait-bg: #2d1616;  --wait-bd: #4a2020;
    --active: #e0a94f;  --active-bg: #2c2110; --active-bd: #4a3a18;
    --done: #6ec98a;    --done-bg: #12251a;  --done-bd: #1f4029;
    --idle: #9aa3ad;    --idle-bg: #232930;  --idle-bd: #2b323a;
    --clash: #e0a94f;   --clash-bg: #2c2110;
    --tint-attn: #241f14;
    --shadow: 0 1px 2px rgba(0,0,0,.4);
  }
  :root[data-theme="light"] {
    color-scheme: light;
    --ground: #f6f7f8;  --panel: #ffffff;  --panel-2: #fafbfc;
    --ink: #111827;     --ink-2: #374151;  --ink-3: #6b7280;
    --ink-hd: #4b5563;  --ink-empty: #878d96;  --on-wait: #ffffff;
    --rule: #e5e7eb;    --rule-2: #eef0f2; --rule-3: #f3f4f6;
    --accent: #1d4ed8;  --accent-soft: #eaf0fd; --accent-tint: #f8fafd;
    --wait: #b91c1c;    --wait-bg: #fef2f2;  --wait-bd: #fecaca;
    --active: #b45309;  --active-bg: #fffbeb; --active-bd: #fde68a;
    --done: #15803d;    --done-bg: #f0fdf4;  --done-bd: #bbf7d0;
    --idle: #4b5563;    --idle-bg: #f3f4f6;  --idle-bd: #e5e7eb;
    --clash: #b45309;   --clash-bg: #fffbeb;
    --tint-attn: #fffdf5;
    --shadow: 0 1px 2px rgba(0,0,0,.07);
  }
* { box-sizing: border-box; }
  body { margin: 0; background: var(--ground); color: var(--ink);
         font-variant-numeric: tabular-nums; -webkit-font-smoothing: antialiased; }
  body, body * { font-variant-numeric: tabular-nums !important; }
  /* The legacy class name remains in rendered markup, but it now means numeric
     alignment only. Every visible word uses the same UI font. */
  .mono { font-variant-numeric: tabular-nums; }
  a { color: var(--accent); }
  :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px;
                   border-radius: 4px; }
  @media (prefers-reduced-motion: reduce) { * { transition: none !important; } }

  /* ---------- MOCKUP CHROME (not part of the product) ------------ */
  .shell {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    padding: 7px 14px; background: var(--ink); color: #fff;
    position: sticky; top: 0; z-index: 40;
  }
  .shell .tag {
    font: 600 10.5px/1 var(--ui); letter-spacing: .14em;
    text-transform: uppercase; opacity: .5;
    padding-right: 12px; border-right: 1px solid rgba(255,255,255,.18);
  }
  .shell nav { display: flex; gap: 3px; flex-wrap: wrap; }
  .shell button {
    font: inherit; font-size: 13px; padding: 4px 10px; cursor: pointer;
    background: none; border: 1px solid transparent; border-radius: 4px;
    color: rgba(255,255,255,.72);
  }
  .shell button:hover { background: rgba(255,255,255,.09); color: #fff; }
  .shell button[aria-current="true"] {
    background: rgba(255,255,255,.14); color: #fff; font-weight: 600;
  }
  .shell .hint { margin-left: auto; font-size: 13px; opacity: .45;
                 font-family: var(--ui); }
  @media (max-width: 700px) { .shell .hint { display: none; } }

  .screen { display: none; }
  .screen.on { display: block; }

  /* ---------- SHARED ---------------------------------------------- */
  .pane { background: var(--panel); border: 1px solid var(--rule);
          box-shadow: var(--shadow); overflow: hidden; }
  /* Type, padding and radius for a pill are set in src/web/extra.css. */
  .pill { white-space: nowrap; }
  .pill.wait { color: var(--wait); background: var(--wait-bg); }
  .pill.active { color: var(--active); background: var(--active-bg); }
  .pill.done { color: var(--done); background: var(--done-bg); }
  .pill.idle { color: var(--idle); background: var(--idle-bg); }
  /* THE ONE DEFINITION OF A SECTION HEADING. It was defined THREE times — here,
     again at extra.css .sec-h, and a third time at .cols .pane > .sec > .sec-h,
     which is the only one that ever applied because every .sec-h in the product
     renders inside .cols .pane. Two of the three were dead, and somebody had
     already made the "make it heavier" change in one of the dead ones, where it
     silently did nothing. Defined here once; the extras add the header strip's
     layout and never restate the type. The rail is the single documented
     exception and says so where it overrides. */
  .sec-h { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
           font: 650 16px/1.3 var(--ui); letter-spacing: -.005em;
           text-transform: none; color: var(--ink); }
  .note { font-size: 13px; color: var(--ink-3); }
  .wrap { max-width: 1180px; margin: 0 auto; }

  /* ---------- 1 · PROJECT PICKER ---------------------------------- */
  .picker { min-height: calc(100vh - 40px); display: grid; place-items: center;
            padding: 40px 18px 60px; }
  .picker-in { width: 100%; max-width: 620px; }
  .picker h1 { font: 640 24px/1.2 var(--ui); letter-spacing: -.02em;
               margin: 0 0 6px; text-wrap: balance; }
  .picker .signed {
    display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
    font-size: 13px; color: var(--ink-2); margin-bottom: 26px;
  }
  .avatar { width: 22px; height: 22px; border-radius: 50%;
            background: var(--accent); color: #fff; display: grid;
            place-items: center; font: 600 10.5px/1 var(--ui); }
  .pcards { display: grid; gap: 10px; }
  .pcard {
    display: grid; grid-template-columns: 4px 1fr auto; gap: 0 15px;
    align-items: center; text-align: left; width: 100%;
    background: var(--panel); border: 1px solid var(--rule);
    border-radius: 8px; padding: 0; cursor: pointer; font: inherit;
    color: inherit; overflow: hidden; box-shadow: var(--shadow);
  }
  .pcard:hover { border-color: var(--accent); }
  .pcard .bar { align-self: stretch; background: var(--accent); }
  .pcard .body { padding: 13px 0; min-width: 0; }
  .pcard h2 { font: 640 16px/1.25 var(--ui); margin: 0 0 3px;
              letter-spacing: -.01em; }
  .pcard .sub { font-size: 13px; color: var(--ink-2); }
  .pcard .num { padding: 13px 15px 13px 0; text-align: right;
                font: 500 13px/1.6 var(--ui); color: var(--ink-3); }
  .pcard .num b { display: block; color: var(--wait); font-weight: 600; }
  .pcard[data-role="reporter"] .bar { background: var(--idle); }
  .picker-foot {
    margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--rule);
    font-size: 13px; color: var(--ink-3);
  }

  /* ---------- 2 · PLAN -------------------------------------------- */
  .topbar {
    display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
    background: var(--panel);
    border-bottom: 1px solid var(--rule); position: sticky; top: 40px; z-index: 20;
  }
  .wordmark { font: 600 12px/1 var(--ui);
              text-transform: uppercase; color: var(--ink-3);
              padding-right: 14px; border-right: 1px solid var(--rule-2); }
  .proj-here { display: inline-flex; align-items: baseline; gap: 9px;
               font: 640 16px/1 var(--ui); letter-spacing: -.01em; }
  .proj-here .cnt { font: 500 12px/1 var(--ui); color: var(--ink-3); }
  .topbar .spacer { margin-left: auto; }
  .usermenu { position: relative; }
  .userbtn { display: flex; align-items: center; gap: 8px; cursor: pointer;
             background: none; border: 1px solid var(--rule); border-radius: 4px;
             padding: 4px 9px 4px 5px; font: inherit; font-size: 13px;
             color: var(--ink-2); }
  .userbtn:hover { border-color: var(--accent); }
  .menu {
    position: absolute; right: 0; top: calc(100% + 6px); width: 268px;
    background: var(--panel); border: 1px solid var(--rule); border-radius: 8px;
    box-shadow: var(--shadow); padding: 6px; z-index: 30;
  }
  .menu[hidden] { display: none; }
  .menu .grp { font: 600 10.5px/1 var(--ui); letter-spacing: .12em;
               text-transform: uppercase; color: var(--ink-3);
               padding: 8px 9px 5px; }
  .menu a, .menu button {
    display: flex; align-items: baseline; gap: 8px; width: 100%;
    padding: 7px 9px; border-radius: 4px; text-decoration: none;
    color: var(--ink); font: inherit; font-size: 14px; background: none;
    border: 0; cursor: pointer; text-align: left;
  }
  .menu a:hover, .menu button:hover { background: var(--panel-2); }
  .menu .cur { color: var(--accent); font-weight: 600; }
  .menu .cur::after { content: "current"; margin-left: auto;
                      font: 500 10.5px/1 var(--ui); color: var(--ink-3); }
  .menu hr { border: 0; border-top: 1px solid var(--rule-2); margin: 5px 0; }

  .clash-strip { margin: 14px 0 0; }
  .clash-head {
    display: flex; align-items: center; gap: 13px; flex-wrap: wrap;
    padding: 11px 15px 11px 21px;
    background-image: repeating-linear-gradient(45deg,
      var(--clash) 0 2px, transparent 2px 6px);
    background-size: 6px 100%; background-repeat: no-repeat;
    background-position: left top;
  }
  .clash-title { font-weight: 660; letter-spacing: -.01em; }
  .clash-n { font: 650 13px/1 var(--ui); color: var(--clash);
             background: var(--clash-bg); padding: 4px 7px; border-radius: 4px; }
  .clash-sub { font-size: 13px; color: var(--ink-2); max-width: 68ch; }
  .clash-list { border-top: 1px solid var(--rule-2); }
  .clash-row {
    display: grid; grid-template-columns: 92px minmax(0,1fr) auto;
    gap: 6px 14px; align-items: baseline; padding: 8px 15px 8px 21px;
    border-bottom: 1px solid var(--rule-2); font-size: 14px;
  }
  .clash-row:last-child { border-bottom: 0; }
  .clash-row:hover { background: var(--panel-2); }
  .clash-refs { font: 500 13px/1.5 var(--ui); color: var(--accent); }
  .clash-what strong { font-weight: 620; }
  .clash-what span { color: var(--ink-2); }
  .clash-caught { font: 500 12px/1 var(--ui); color: var(--clash);
                  white-space: nowrap; }

  .console { display: grid; grid-template-columns: minmax(0,1fr) 380px;
             gap: 14px; padding: 14px 0 28px; align-items: start; }
  .axis-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
              padding: 9px 14px; border-bottom: 1px solid var(--rule);
              background: var(--panel-2); }
  .axis { display: flex; gap: 4px; flex-wrap: wrap; }
  .axis button { font: inherit; font-size: 13px; padding: 3px 9px;
                 cursor: pointer; border: 1px solid transparent;
                 border-radius: 4px; background: none; color: var(--ink-2); }
  .axis button:hover { background: var(--ground); }
  .axis button[aria-pressed="true"] { background: var(--panel);
    border-color: var(--rule); color: var(--ink); font-weight: 600; }
  .axis-why { font-size: 13px; color: var(--ink-3); margin-left: auto; }

  .group-head { display: flex; align-items: center; gap: 9px; padding: 6px 14px;
                cursor: pointer; background: var(--panel-2);
                border-bottom: 1px solid var(--rule-2);
                border-top: 1px solid var(--rule-2); }
  .caret { width: 9px; color: var(--ink-3); transition: transform .12s; }
  .group-head[aria-expanded="false"] .caret { transform: rotate(-90deg); }
  .group-name { font-weight: 640; font-size: 14px; }
  .group-n { font: 500 13px/1 var(--ui); color: var(--ink-3); }
  .group-gloss { font-size: 13px; color: var(--ink-3); margin-left: auto; }
  @media (max-width: 620px) { .group-gloss { display: none; } }

  .row { display: grid;
         align-items: center;
         cursor: pointer; border-bottom: 1px solid var(--rule-2); }
  .row:hover { background: var(--panel-2); }
  .row[aria-selected="true"] { background: var(--accent-soft); }
  .row[aria-selected="true"] .r-title { font-weight: 640; }
  .stripe { height: 100%; background: var(--idle); }
  .stripe.wait { background: var(--wait); }
  .stripe.active { background: var(--active); }
  .stripe.done { background: var(--done); }
  .stripe.clash { background-image: repeating-linear-gradient(45deg,
    var(--clash) 0 2px, transparent 2px 5px); }
  .r-ref { font: 500 13px/1 var(--ui); color: var(--accent); }
  .r-title { font-size: 14px; overflow: hidden; text-overflow: ellipsis;
             white-space: nowrap; }
  .r-title .flag { font: 600 10.5px/1 var(--ui); color: var(--clash);
    background: var(--clash-bg); padding: 2px 4px; border-radius: 4px;
    margin-left: 7px; }
  .r-area { font-size: 13px; color: var(--ink-3); overflow: hidden;
            text-overflow: ellipsis; white-space: nowrap; }
  .row .pill { justify-self: start; }
  .r-wait { font-size: 13px; color: var(--ink-2); text-align: right;
            font-family: var(--ui); }
  .r-wait.none { color: var(--ink-3); opacity: .5; }

  .detail { position: sticky; top: 96px; max-height: calc(100vh - 110px);
            display: flex; flex-direction: column; }
  .d-head { padding: 13px 15px 12px; border-bottom: 1px solid var(--rule); }
  .d-crumb { display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
             font: 500 12px/1 var(--ui); color: var(--ink-3);
             margin-bottom: 7px; }
  .d-crumb .here { color: var(--accent); }
  .d-title { font: 640 20px/1.25 var(--ui); letter-spacing: -.015em;
             text-wrap: balance; margin: 0 0 8px; }
  .d-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
  .d-body { overflow-y: auto; }
  .sec { border-bottom: 1px solid var(--rule-2); padding: 12px 15px; }
  .sec:last-child { border-bottom: 0; }
  .sec > .sec-h { margin-bottom: 9px; }
  .assert { background: var(--panel-2); border: 1px solid var(--rule-2);
            border-radius: 4px; padding: 9px 11px; font-size: 13px;
            color: var(--ink-2); }
  .assert b { color: var(--ink); font-weight: 620; }
  .assert .by { display: block; margin-top: 5px; font-family: var(--ui);
                font-size: 12px; color: var(--ink-3); }

  details.q { border-top: 1px solid var(--rule-2); }
  details.q:first-of-type { border-top: 0; }
  details.q > summary { display: grid; grid-template-columns: 42px 1fr auto;
    gap: 9px; align-items: baseline; padding: 7px 0; cursor: pointer;
    list-style: none; font-size: 13px; }
  details.q > summary::-webkit-details-marker { display: none; }
  details.q > summary:hover .q-txt { color: var(--accent); }
  .q-ref { font: 500 12px/1.4 var(--ui); color: var(--accent); }
  .q-txt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .q-age { font: 500 10.5px/1.4 var(--ui); color: var(--wait); }
  .q-age.settled { color: var(--ink-3); }
  .q-open { padding: 2px 0 11px 51px; font-size: 13px; color: var(--ink-2); }
  .q-open p { margin: 0 0 8px; }
  .q-answer { background: var(--done-bg); border-left: 2px solid var(--done);
    padding: 7px 10px; border-radius: 0 8px 8px 0; color: var(--ink); }
  .q-answer i { display: block; font-style: normal; font-family: var(--ui);
    font-size: 10.5px; color: var(--ink-3); margin-bottom: 3px; }

  .lnk { display: grid; grid-template-columns: 64px minmax(0,1fr); gap: 9px;
         align-items: baseline; padding: 4px 0; font-size: 13px; }
  .lnk .kind { font: 600 10.5px/1.6 var(--ui); letter-spacing: .06em;
    text-transform: uppercase; color: var(--ink-3);
    border: 1px solid var(--rule); border-radius: 4px; text-align: center; }
  .lnk a { text-decoration: none; }
  .lnk a:hover { text-decoration: underline; }
  .set { display: grid; grid-template-columns: auto minmax(0,1fr);
         gap: 6px 12px; font-size: 13px; align-items: baseline; }
  .set dt { font: 500 12px/1.7 var(--ui); letter-spacing: .06em;
            text-transform: uppercase; color: var(--ink-3); }
  .set dd { margin: 0; }
  .set dd.empty { color: var(--ink-3); font-style: italic; }

  /* ---------- 3 · FEEDBACK (reporter) ----------------------------- */
  .fb { padding: 18px 0 40px; display: grid; gap: 14px;
        grid-template-columns: 360px minmax(0,1fr); align-items: start; }
  .card-h { border-bottom: 1px solid var(--rule);
            background: var(--panel-2); }
  .field { display: grid; gap: 5px; margin-bottom: 13px; }
  .field label { font: 600 12px/1 var(--ui); letter-spacing: .1em;
                 text-transform: uppercase; color: var(--ink-3); }
  .field input, .field textarea, .field select {
    font: inherit; color: var(--ink); border: 1px solid var(--rule);
    width: 100%;
  }
  .field textarea { min-height: 86px; resize: vertical; }
  .field .help { font-size: 13px; color: var(--ink-3); }
  .auto { display: flex; align-items: baseline; gap: 8px; font-size: 13px;
          color: var(--ink-2); padding: 7px 9px; background: var(--panel-2);
          border: 1px dashed var(--rule); border-radius: 8px; }
  .auto .mono { color: var(--ink); }
  .btn { font-size: 14px; font-weight: 600; border: 1px solid var(--accent);
         background: var(--accent); color: #fff; cursor: pointer; }
  .btn:hover { filter: brightness(1.08); }

  .mine { border-bottom: 1px solid var(--rule-2); padding: 11px 15px;
          display: grid; grid-template-columns: 62px minmax(0,1fr) auto;
          gap: 4px 12px; align-items: baseline; }
  .mine:last-child { border-bottom: 0; }
  .mine .ref { font: 500 13px/1.4 var(--ui); color: var(--accent); }
  .mine .what { font-size: 14px; }
  .mine .meta { grid-column: 2 / -1; font-size: 13px; color: var(--ink-3); }
  .mine .ask { grid-column: 2 / -1; margin-top: 6px; font-size: 13px;
    background: var(--wait-bg); border-left: 2px solid var(--wait);
    padding: 8px 10px; border-radius: 0 4px 4px 0; color: var(--ink); }
  .mine .ask b { display: block; font: 600 10.5px/1.4 var(--ui);
                 letter-spacing: .06em; text-transform: uppercase;
                 color: var(--wait); margin-bottom: 3px; }
  .mine .ask input { margin-top: 7px; }

  /* ---------- 4 · SETTINGS ---------------------------------------- */
  .set-wrap { padding: 18px 0 40px; display: grid;
              grid-template-columns: 190px minmax(0,1fr); gap: 14px;
              align-items: start; }
  .sidenav { padding: 6px; }
  .sidenav button { display: block; width: 100%; text-align: left;
    padding: 7px 10px; border-radius: 4px; border: 0; background: none;
    font: inherit; font-size: 14px; color: var(--ink-2); cursor: pointer; }
  .sidenav button:hover { background: var(--panel-2); }
  .sidenav button[aria-current="true"] { background: var(--accent-soft);
    color: var(--accent); font-weight: 600; }
  .sidenav .grp { font: 600 10.5px/1 var(--ui); letter-spacing: .12em;
    text-transform: uppercase; color: var(--ink-3); padding: 11px 10px 5px; }

  .tabpage { display: none; }
  .tabpage.on { display: block; }
  .tbl-scroll { overflow-x: auto; }
  table { border-collapse: collapse; width: 100%; min-width: 520px; }
  th, td { text-align: left; padding: 9px 12px; font-size: 14px;
           border-bottom: 1px solid var(--rule-2); vertical-align: middle; }
  th { font: 600 10.5px/1 var(--ui); letter-spacing: .1em;
       text-transform: uppercase; color: var(--ink-3);
       background: var(--panel-2); border-bottom: 1px solid var(--rule); }
  td .mono { font-size: 13px; }
  .who-cell { display: flex; align-items: center; gap: 9px; }
  .who-cell .nm { font-weight: 600; }
  .who-cell .em { font-size: 13px; color: var(--ink-3);
                  font-family: var(--ui); }
  .acc { display: inline-flex; gap: 4px; flex-wrap: wrap; }
  .acc span { font: 500 10.5px/1 var(--ui); padding: 3px 6px;
              border-radius: 4px; background: var(--panel-2);
              border: 1px solid var(--rule); color: var(--ink-2); }
  .acc span.admin { background: var(--accent-soft); border-color: transparent;
                    color: var(--accent); font-weight: 600; }
  .acc span.none { color: var(--ink-3); font-style: italic; }
  .rowgap { display: grid; gap: 13px; }
  .split { display: grid; }
  .toggle { display: flex; align-items: flex-start; gap: 10px;
            padding: 10px 12px; border: 1px solid var(--rule-2);
            border-radius: 4px; font-size: 14px; }
  .toggle input { margin: 2px 0 0; }
  .toggle .t { font-weight: 600; }
  .toggle .d { font-size: 13px; color: var(--ink-3); }
  .banner { display: flex; gap: 11px; align-items: flex-start;
    padding: 11px 13px; border-radius: 4px; font-size: 13px;
    background: var(--wait-bg); color: var(--ink); }
  .banner b { font-weight: 660; }
  .banner.ok { background: var(--done-bg); }

  .foot { color: var(--ink-3);
          max-width: 82ch; }
  .foot b { color: var(--ink-2); font-weight: 600; }

  /* ---------- RESPONSIVE ------------------------------------------
     Three real breakpoints rather than one. The console loses the
     detail pane's stickiness first, then its own column, then the
     row's optional columns — in that order, because the row's
     identity and state must survive to the smallest width.
     ---------------------------------------------------------------- */
  @media (max-width: 1080px) {
    .console { grid-template-columns: minmax(0,1fr) 330px; }
    .fb { grid-template-columns: 320px minmax(0,1fr); }
  }
  @media (max-width: 900px) {
    .console, .fb, .set-wrap { grid-template-columns: minmax(0,1fr); }
    .detail { position: static; max-height: none; order: -1; }
    .sidenav { display: flex; gap: 4px; flex-wrap: wrap; padding: 8px; }
    .sidenav .grp { width: 100%; padding: 4px 4px 2px; }
    .sidenav button { width: auto; }
    .topbar { position: static; }
  }
  @media (max-width: 660px) {
    .row { grid-template-columns: 8px 56px minmax(0,1fr) 86px; }
    .r-area, .r-wait { display: none; }
    .clash-row { grid-template-columns: minmax(0,1fr); padding-left: 21px; }
    .clash-caught { justify-self: start; }
    .wrap { padding: 0 12px; }
    .picker h1 { font-size: 20px; }
  }
/* ---------------------------------------------------------------
   APP-ONLY ADDITIONS.
   The mockup renders a row as a <div>; the running product renders it as an
   <a>, because a row IS a navigation and making it anything else means
   reimplementing middle-click, open-in-new-tab and the status bar preview that
   browsers give away for free. That change brings anchor defaults with it —
   underline and link colour — which have to be turned off here rather than in
   the mockup, since the mockup has no anchors to turn them off on.
   --------------------------------------------------------------- */
/* ---------------------------------------------------------------
   `hidden` HAS TO ACTUALLY HIDE. This is the second half of report 14, and the
   more serious half.

   The browser's own stylesheet says `[hidden] { display: none }`. That rule is
   a USER-AGENT rule, and ANY rule we write here beats it — so `.row { display:
   grid }` further down this file quietly cancelled it. The type-to-filter box
   sets `hidden` on the rows that do not match; every one of them went on
   rendering. What still worked was the group folds either side of it, which is
   why the box LOOKED like it half-worked: whole groups vanished when nothing in
   them matched, while the rows inside a surviving group all stayed on screen.

   `.menu[hidden] { display: none; }` in the mockup is the same bug, patched for
   one component in the past. This replaces the whack-a-mole with the rule
   itself, and that one is now redundant rather than wrong.

   IT CARRIES `!important` ON PURPOSE — the one place in this file where that is
   the correct tool rather than a shortcut. `[hidden]` and `.row` have the same
   specificity, so without it the winner is decided by which line happens to sit
   lower in a generated 3,600-line file, and the next `display:` anybody adds to
   a list row silently breaks filtering again. This is a browser contract, not a
   style preference, and nothing should be able to outrank it.
   --------------------------------------------------------------- */
[hidden] { display: none !important; }

code, pre { font-family: var(--ui); }

a.row, a.pcard { text-decoration: none; color: inherit; }
a.row:hover .r-title { color: var(--accent); }
a.row .r-area { color: var(--ink-3); }
a.row:visited { color: inherit; }

/* The axis buttons are wrapped in anchors so grouping survives a page load. */
.axis a { text-decoration: none; }
.axis a button { pointer-events: none; }

/* The sidenav is anchors around buttons for the same reason. */
.sidenav a { text-decoration: none; display: block; }
.sidenav a button { width: 100%; }

/* A settings page has no project, so the chrome's project slot is empty; keep
   the bar from collapsing to nothing. */
.topbar .proj-here:empty { display: none; }

/* ---------------------------------------------------------------
   TOOLTIPS.
   The owner's ask: put the explanation on the thing, so somebody can learn
   what a word means without a permanent note beside every row.

   Pointer-only tooltips are decoration, so this opens on FOCUS as well as
   HOVER, and the same text is also in `title` — which assistive technology and
   every browser already handle — so nothing depends on this CSS existing.
   --------------------------------------------------------------- */
.tip { position: relative; cursor: help; }
.tip::after {
  content: attr(data-tip);
  position: absolute; left: 0; top: calc(100% + 7px); z-index: 50;
  width: max-content; max-width: 320px;
  background: var(--ink); color: #fff;
  font: 400 13px/1.5 var(--ui); letter-spacing: 0; text-transform: none;
  padding: 8px 10px; border-radius: 4px;
  box-shadow: 0 4px 18px rgba(0,0,0,.28);
  opacity: 0; visibility: hidden; transform: translateY(-3px);
  transition: opacity .12s ease, transform .12s ease, visibility .12s;
  pointer-events: none; white-space: normal;
}
.tip::before {
  content: ""; position: absolute; left: 11px; top: calc(100% + 2px);
  border: 5px solid transparent; border-bottom-color: var(--ink);
  opacity: 0; visibility: hidden; transition: opacity .12s ease, visibility .12s;
  z-index: 51; pointer-events: none;
}
.tip:hover::after, .tip:focus-visible::after,
.tip:hover::before, .tip:focus-visible::before {
  opacity: 1; visibility: visible; transform: translateY(0);
}
/* Near the right edge a left-anchored bubble runs off screen. */
.tip.right::after { left: auto; right: 0; }
.tip.right::before { left: auto; right: 11px; }
@media (prefers-reduced-motion: reduce) {
  .tip::after, .tip::before { transition: none; }
}
/* A pill that is also a tooltip keeps the pill's own look. */
.tip.pill { display: inline-block; }
/* A dotted underline is the long-standing signal that a word explains itself. */
.tip.word { border-bottom: 1px dotted var(--ink-3); }

/* The row gained Plumbline's own state beside the sheet's word: two claims, two
   chips, never merged. What each narrow width drops is here; the column track
   itself is declared ONCE, with the rest of .row further down this file. */
.r-area.none { color: var(--ink-3); opacity: .5; font-style: italic; }
.pill.lifecycle { font-weight: 500; opacity: .85; }
@media (max-width: 1180px) { .r-area { display: none; } }
@media (max-width: 860px)  { .pill.lifecycle, .r-wait { display: none; } }

/* Answering happens inside the question, so the form has to sit comfortably in
   a details body rather than looking like a page of its own. */
.answer-form { display: grid; gap: 6px; margin-top: 4px; }
.answer-form label { font: 600 12px/1 var(--ui); letter-spacing: .1em;
                     text-transform: uppercase; color: var(--ink-3); }
.answer-form textarea {
  font: inherit; font-size: 14px; padding: 8px 10px; color: var(--ink);
  background: var(--panel); border: 1px solid var(--rule); border-radius: 4px;
  min-height: 82px; resize: vertical; width: 100%;
}
.answer-form textarea:focus { border-color: var(--accent); outline: none; }
.answer-form .help { font-size: 13px; color: var(--ink-3); margin: 0; }
.answer-form .btn { justify-self: start; }

/* Notes: an instruction has to look different from a passing remark, because
   the whole point is that an agent can tell which is which. */
.note-row { border-left: 2px solid var(--rule); padding: 8px 0 8px 11px;
            margin-bottom: 9px; }
.note-row.instruction { border-left-color: var(--accent); }
.note-row.decision    { border-left-color: var(--done); }
.note-row.info        { border-left-color: var(--rule); }
.note-meta { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
             margin-bottom: 4px; }
.note-meta .kindtag { font: 600 10.5px/1 var(--ui); letter-spacing: .08em;
  text-transform: uppercase; padding: 3px 5px; border-radius: 4px;
  background: var(--panel-2); color: var(--ink-3); }
.note-row.instruction .kindtag { background: var(--accent-soft); color: var(--accent); }
.note-row.decision .kindtag    { background: var(--done-bg); color: var(--done); }
.note-meta .mono { font-size: 12px; color: var(--ink-3); }
.note-body { font-size: 14px; white-space: pre-wrap; }

/* A contradiction is a way IN to the row it is about. */
.clash-refs a { color: var(--accent); text-decoration: none; }
.clash-refs a:hover { text-decoration: underline; }
a.clash-what { display: block; text-decoration: none; color: inherit; }
a.clash-what:hover strong { color: var(--accent); }

/* A screenshot is evidence — big enough to recognise, small enough not to take
   over the row. Clicking opens the full image. */
.shots { grid-column: 2 / -1; display: flex; flex-wrap: wrap; margin-top: 8px; }
.shots img { max-height: 128px; max-width: 220px; border: 1px solid var(--rule);
             border-radius: 4px; display: block; background: var(--panel-2); }
.shots a:hover img { border-color: var(--accent); }

/* Sorting list: draggable rows with an exact position box. */
.sortlist { list-style: none; margin: 0; padding: 0; }
.sortrow {
  display: grid; grid-template-columns: 22px 62px minmax(0,1fr) auto auto;
  align-items: center; gap: 11px;
  padding: 8px 15px; border-bottom: 1px solid var(--rule-2);
  background: var(--panel); cursor: grab;
}
.sortrow:last-child { border-bottom: 0; }
.sortrow:hover { background: var(--panel-2); }
.sortrow.dragging { opacity: .45; cursor: grabbing; }
.sortrow.moved { background: var(--accent-soft); transition: background .5s ease; }
.grip { color: var(--ink-3); font-size: 14px; line-height: 1; cursor: grab; }
.posbox {
  width: 56px; font: 500 13px/1 var(--ui); text-align: center;
  padding: 5px 4px; border: 1px solid var(--rule); border-radius: 4px;
  background: var(--panel); color: var(--ink);
}
.posbox:focus { border-color: var(--accent); outline: none; }
.sortval { font-size: 14px; font-weight: 550; }
.sortn { font-size: 13px; color: var(--ink-3); }
.sortbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
           padding: 12px 15px; border-top: 1px solid var(--rule); }
.sortform.dirty .sortbar { background: var(--active-bg); }
.sortform.dirty .sortbar .note::before { content: "Unsaved — "; font-weight: 600; }
@media (max-width: 620px) {
  .sortrow { grid-template-columns: 22px 52px minmax(0,1fr); }
  .sortn { display: none; }
}

/* ------------------------------------------------------------------
   THE REPORT-STATE LIST: every row is its own editor.

   This is a NEW COMPONENT WITH ITS OWN CLASS, which is what the rule at the
   bottom of AGENTS.md says to do when a form has to break .split's fixed two
   columns — not an inline style on one page. Everything else about a field
   still holds inside it: label above control, control full width of its
   column, one height for every control, fields aligned to the top of the row.

   Why a grid rather than .split repeated: a state has seven facts and they are
   not two-by-two. Position is 60px because it holds at most two digits, the
   colour and openness selects are the same width as each other because they
   are the same kind of question, and the Save button spans both rows on the
   right so that a row reads as one thing you press once.
   ------------------------------------------------------------------ */
.statelist { list-style: none; margin: 0; padding: 0; }
.staterow { border-bottom: 1px solid var(--rule-2); padding: 11px 0; }
.staterow:first-child { padding-top: 0; }
.staterow:last-child { border-bottom: 0; padding-bottom: 0; }
.stateform {
  display: grid; gap: 9px 11px; align-items: start;
  grid-template-columns: 60px minmax(0,1.3fr) minmax(0,1.1fr)
                         minmax(0,1.15fr) minmax(0,1.05fr) minmax(120px, auto);
}
.stateform .f-save { grid-column: 6; grid-row: 1 / span 3; align-self: center;
                     display: flex; flex-direction: column; gap: 6px; align-items: stretch; }
.stateform .f-save .btn { width: 100%; }
.stateform .f-save .pill { text-align: center; }
.stateform .f-held { grid-column: 1 / 3; grid-row: 2;
                     font-size: 12px; line-height: 1.35; color: var(--ink-3); align-self: center; }
/* Court is one word; "what it means" is a sentence. They had a column each and
   the sentence was the one that got 90px — a field whose width contradicts what
   it is for teaches you to keep the sentence short. */
.stateform .f-court { grid-column: 3 / 4; grid-row: 2; }
.stateform .f-tip   { grid-column: 4 / 6; grid-row: 2; }
.stateform .f-pos input { text-align: center; font-family: var(--ui); }
/* Dense fields say so — see .field.dense below. Two ancestors used to shrink
   the label and the control from outside, which is one class meaning two sizes
   depending on where it sat. */

/* A ROW THAT IS NOT ON THE LIST MUST NOT LOOK LIKE ONE THAT IS. Its openness is
   the old fallback rule's guess, not this project's answer, and the only thing
   that ends that is somebody saying which. */
.staterow.stray { background: var(--wait-bg); border-left: 3px solid var(--wait);
                  padding-left: 11px; margin: 4px 0; border-radius: 4px; }
.staterow.stray .field input[disabled] { opacity: .75; }

@media (max-width: 1000px) {
  .stateform { grid-template-columns: 60px minmax(0,1fr) minmax(0,1fr); }
  .stateform .f-save  { grid-column: 1 / -1; grid-row: auto; flex-direction: row; }
  .stateform .f-save .btn { width: auto; }
  .stateform .f-held  { grid-column: 1 / -1; grid-row: auto; }
  .stateform .f-court { grid-column: 1 / 3; grid-row: auto; }
  .stateform .f-tip   { grid-column: 3 / -1; grid-row: auto; }
}

/* THE ROUTING LADDER, WRITTEN AS A LADDER. It is an ordered list because the
   order IS the rule — rendering it as prose was how "program area beats status"
   stayed a fact only the code knew. */
.ladder { margin: 6px 0 0; padding-left: 20px; font-size: 13px; line-height: 1.55;
          color: var(--ink-2); }
.ladder li { margin-bottom: 3px; }

/* ONE ROW PER PROGRAM AREA, ROUTED OR NOT — "who takes each area" answered as a
   list of every area rather than a table of the mapped ones. Each row is its own
   form because each is its own decision; the grid keeps the selects aligned so
   the column reads down as "who has what". */
.arearoutes { display: grid; gap: 6px; }
.arearoute { display: grid; align-items: center; gap: 10px;
             grid-template-columns: minmax(0,1.1fr) minmax(0,1fr) auto minmax(0,1.1fr);
             padding: 6px 9px; border: 1px solid var(--rule-2); border-radius: 8px;
             background: var(--panel); }
.arearoute .ar-name { font-size: 14px; font-weight: 550; }
.arearoute select { height: 32px; min-height: 32px; padding: 5px 8px; font-size: 13px;
                    width: 100%; }
.arearoute .btn { padding: 6px 13px; font-size: 13px; white-space: nowrap; }
.arearoute .ar-now { font-size: 13px; }
@media (max-width: 760px) {
  .arearoute { grid-template-columns: minmax(0,1fr) auto; }
  .arearoute .ar-name { grid-column: 1 / -1; }
  .arearoute .ar-now { grid-column: 1 / -1; }
}

/* A question is a headline and an answer box. The reasoning is one click away
   and never between you and the box. */
/* THE INDENT IS ONE NUMBER. Everything under the headline lines up with the
   summary text rather than with the ref, and it was written as a literal 53px in
   four places — which is exactly the kind of number that goes wrong in three of
   them the day a column is added to the head. Which is what just happened: the
   caret column adds 9px of track and 9px of gap. */
/* THE OLD LIST SHAPE, DELETED. `.qcard` was a row in a borderless list: a
   hairline on top, none on the first, and a 71px indent so the body lined up
   under the ref. The card has a real border and its own padding now, and those
   two rules were still winning — `.qcard:first-of-type` is two classes' worth of
   specificity against the new `.qcard`, and `border-top: 0` with no colour
   resets border-top-COLOR to currentColor, so the merged card came out with a
   near-black top edge and three grey ones. Measured, not read. */
.qc-head { display: grid; grid-template-columns: 9px 44px minmax(0,1fr) auto;
           gap: 9px; align-items: baseline; margin-bottom: 7px; }
.qc-short { font-size: 14px; font-weight: 560; }
.qcard.settled .qc-short { font-weight: 400; color: var(--ink-2); }

/* ---- A SETTLED QUESTION FOLDS TO ITS HEADLINE ----
   Measured cause: Questions was 2153px of a 3723px item page — 57.8% — ten cards
   with seven already answered and every one rendered open. See plan.ts for the
   numbers and for the comment that claimed this already happened.

   THE CARET IS 9px WIDE OPEN OR CLOSED AND IS PRESENT ON BOTH KINDS OF CARD,
   empty on an open one. The same rule the plan's group headers follow: a caret
   that appears only on some rows steps every ref in the list sideways depending
   on whether the question above it was answered. */
.qc-caret { font: 400 12px/1 var(--ui); color: var(--ink-3);
            transform: rotate(-90deg); transition: transform .12s; }
details.qcard[open] > .qc-head .qc-caret { transform: none; }
details.qcard > summary.qc-head { list-style: none; cursor: pointer; }
details.qcard > summary.qc-head::-webkit-details-marker { display: none; }
details.qcard > summary.qc-head:hover .qc-short { color: var(--ink); }
details.qcard > summary.qc-head:focus-visible { outline: 2px solid var(--accent);
                                                outline-offset: 2px; border-radius: 4px; }
/* SHUT, THE CARD IS ITS HEADLINE AND NOTHING ELSE — the same correction the
   .sheetfold needed. `.qc-head` carries 7px beneath itself for the body that is
   no longer there, so a closed card would sit in a band of white and read as one
   that failed to load its answer. */
details.qcard:not([open]) > .qc-head { margin-bottom: 0; }
@media (prefers-reduced-motion: reduce) { .qc-caret { transition: none; } }

/* No indent: the card's own padding does that job now. */
.qc-more { margin: 0 0 8px; }
.qc-more > summary { font-size: 13px; color: var(--accent); cursor: pointer;
                     list-style: none; }
.qc-more > summary::-webkit-details-marker { display: none; }
.qc-more > summary::before { content: "▸ "; }
.qc-more[open] > summary::before { content: "▾ "; }
.qc-more p { font-size: 13px; color: var(--ink-2); white-space: pre-wrap;
             margin: 7px 0 0; }
/* The three indents went with --qc-indent: the card's padding does it now, and
   an undefined variable here would have resolved to `auto` and centred them. */

/* The questions list. */
.qrow { display: grid; grid-template-columns: 56px minmax(0,1fr) 130px 96px;
        gap: 12px; align-items: center; padding: 10px 15px;
        border-bottom: 1px solid var(--rule-2); text-decoration: none;
        color: inherit; }
.qrow:last-child { border-bottom: 0; }
.qrow:hover { background: var(--panel-2); }
.qr-ref { font: 500 13px/1 var(--ui); color: var(--accent); }
.qr-body { display: grid; gap: 2px; min-width: 0; }
.qr-short { font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qr-item { font-size: 13px; color: var(--ink-3); font-family: var(--ui);
           overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qr-item .none { font-style: italic; }
.qr-who { font-size: 13px; color: var(--ink-3); }
details.closedq > summary { cursor: pointer; list-style: none; }
details.closedq > summary::-webkit-details-marker { display: none; }
details.closedq > summary h2::before { content: "▸ "; color: var(--ink-3); }
details.closedq[open] > summary h2::before { content: "▾ "; }
@media (max-width: 760px) {
  .qrow { grid-template-columns: 52px minmax(0,1fr) 90px; }
  .qr-who { display: none; }
}

/* Finished work, folded away at the bottom of the plan. */
details.settled-block { margin-top: 14px; }
details.settled-block > summary { cursor: pointer; list-style: none;
  background: var(--panel-2); }
details.settled-block > summary::-webkit-details-marker { display: none; }
details.settled-block > summary .caret { transform: rotate(-90deg); }
details.settled-block[open] > summary .caret { transform: none; }
details.settled-block .row { opacity: .78; }
details.settled-block .row:hover { opacity: 1; }

/* WHICH KIND OF FINISHED. The block held completed, verified and (0115) closed
   rows under one heading, and the owner could not tell them apart without
   reading every pill: "some are completed and some are verified, and now some
   will be closed, it should show it properly on the list."

   A band per kind, inside the fold. Quieter than a group heading — this is
   still one collapsed block, not three — and it carries its own count so the
   split is readable before anything is opened. */
details.settled-block .subhead {
  display: flex; align-items: baseline; gap: 8px;
  padding: 9px 15px 7px; border-top: 1px solid var(--rule-2);
  background: var(--panel-2);
  font: 600 10.5px/1 var(--ui); letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-3); }
details.settled-block .subhead:first-of-type { border-top: 0; }
details.settled-block .subhead .gloss {
  font: 400 12px/1.3 var(--ui); letter-spacing: 0; text-transform: none; }

/* HOW LONG A REPORT HAS SAT (G6). A cue, never a rule — nothing acts on it.
   A hairline under a day old (drawn as nothing at all), thickening at 24 hours
   and taking the waiting colour at 72, so three days in one state is something
   you SEE rather than something you work out from a date column. The width runs
   to a fortnight and then stops: past two weeks the difference between 15 days
   and 40 is not a difference anybody acts on differently. */
/* ============ IT MUST NOT BE A GRID CELL ============
   The first version was a child of .row, which is a grid with a FIXED column
   list — so every row grew an eighth cell, wrapped to a second line and doubled
   in height, on every desk. The owner saw it immediately: "this is messed up and
   the lines became very big."

   A cue has no business changing the layout it annotates. Absolute, hung on the
   row's own bottom edge, out of the grid entirely: it can never add a column,
   and a row with no dwell is byte-for-byte the row that existed before. */
.row { position: relative; }
.row .dwell { position: absolute; left: 8px; bottom: 0; height: 2px; width: 34px;
              border-radius: 0 2px 0 0; background: transparent; overflow: hidden;
              pointer-events: none; }
.row .dwell i { display: block; height: 100%; background: var(--ink-3); }
.row .dwell.warm i { background: var(--accent); }
.row .dwell.hot   { height: 3px; }
.row .dwell.hot i { background: var(--wait); }
@media (max-width: 860px) { .row .dwell { display: none; } }

/* The feedback desk. A report is a block you read, then three controls in a row. */
.trow { border-bottom: 1px solid var(--rule-2); padding: 13px 15px; }
.trow:last-child { border-bottom: 0; }
.tr-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
           margin-bottom: 6px; }
.tr-ref { font-size: 13px; color: var(--accent); font-weight: 600; }
.tr-who, .tr-when { font-size: 13px; color: var(--ink-3); }
/* A URL HAS NO SPACES, SO pre-wrap CANNOT WRAP IT.
   white-space: pre-wrap honours the newlines a person typed and wraps at
   WHITESPACE — which is right for prose and useless for a 200-character link.
   One unbroken token then runs straight out of the box and is clipped, so the
   end of it is not merely awkward, it is unreadable and uncopyable.
   The owner hit this on a report whose agent result quoted a file path and a
   URL. overflow-wrap: anywhere breaks a token ONLY when it would otherwise
   overflow, so ordinary prose is untouched — unlike word-break: break-all,
   which would hyphenate mid-word everywhere and make the whole page worse to
   fix one line. */
.tr-body { font-size: 14px; white-space: pre-wrap; overflow-wrap: anywhere; }
.tr-meta { font-size: 13px; color: var(--ink-3); margin-top: 4px; }
.tr-linked { font-size: 13px; margin-top: 7px; color: var(--ink-2); }
.tr-actions { margin-top: 10px; }
.form-inline { display: flex; gap: 7px; flex-wrap: wrap; align-items: center; }
.form-inline select, .form-inline input {
  font: inherit; font-size: 13px; padding: 5px 8px; color: var(--ink);
  background: var(--panel); border: 1px solid var(--rule); border-radius: 4px;
}
.form-inline select { max-width: 260px; }
.form-inline input { flex: 1 1 230px; min-width: 160px; }
.form-inline .btn { padding: 5px 13px; font-size: 13px; }
/* THE MERGE PICKER BOTTOM-ALIGNS (fix 2). Its field carries a label above the
   input, so a form-inline centred on the whole flex row floated the "Merge it"
   button above the input it belongs beside. Aligning to the end lines the button
   up with the input; the field itself takes the width so the two do not wrap. */
.merge-form { align-items: flex-end; }
/* .field is a three-row grid (label / control / help). The help row is empty
   here, but the grid still reserves a `gap` gutter above it — so the field's
   box ended ~5px BELOW the input, and flex-end lined the button up with that
   phantom bottom, not the input. Dropping the field to two rows removes the
   trailing gutter, so the input's real bottom is the field's bottom and the
   button sits level with it. */
.merge-form .field.dense { flex: 1 1 auto; grid-template-rows: auto auto; }
.merge-form .field.dense input { width: 100%; }
.acc a { text-decoration: none; }

/* Filter bar sits beside the grouping axis; a divider so the two groups of
   controls do not read as one long row of buttons that all do the same thing. */
.axis-sep { width: 1px; align-self: stretch; background: var(--rule);
            margin: 0 4px; }
.axis button .cnt { color: var(--ink-3); font-size: 12px; margin-left: 3px; }
.axis button[aria-pressed="true"] .cnt { color: var(--accent); }

/* A row is one line high and stays one line high. Long status words and a
   handful of question ids were both bursting it, which turned the list into
   overlapping text the moment a filter surfaced the busy rows. */
.row > * { min-width: 0; overflow: hidden; text-overflow: ellipsis;
           white-space: nowrap; }
.row .stripe { overflow: visible; }
.row .pill { max-width: 100%; display: inline-block; overflow: hidden;
             text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; }
.row .tip.r-wait { display: block; text-align: right; }
.row .tip.r-wait::after { white-space: normal; }

/* Section navigation. Sits next to the project name because it belongs to the
   project — leaving one and the tabs change with it. */
.sectionnav { display: flex; gap: 2px; flex-wrap: wrap; margin-left: 6px;
              padding-left: 14px; border-left: 1px solid var(--rule-2); }
.sectionnav a { font-size: 14px; padding: 4px 10px; border-radius: 4px;
                text-decoration: none; color: var(--ink-2); }
.sectionnav a:hover { background: var(--panel-2); color: var(--ink); }
.sectionnav a[aria-current="page"] { background: var(--accent-soft);
  color: var(--accent); font-weight: 620; }
@media (max-width: 760px) {
  .sectionnav { margin-left: 0; padding-left: 0; border-left: 0; width: 100%; }
}

/* Sub-navigation: what lives INSIDE a section, one level down from the top bar
   so the top bar stays two words long. */
.subnav { display: flex; align-items: center; gap: 4px; flex-wrap: wrap;
          padding: 12px 0 0; }
.subnav a { font-size: 14px; padding: 5px 11px; border-radius: 4px;
            text-decoration: none; color: var(--ink-2); border: 1px solid transparent; }
.subnav a:hover { background: var(--panel); border-color: var(--rule); color: var(--ink); }
.subnav a[aria-current="page"] { background: var(--panel); border-color: var(--rule);
  color: var(--ink); font-weight: 620; }
.subnav a .cnt { color: var(--ink-3); font-size: 13px; margin-left: 3px; }
.subnav .subnav-do { margin-left: auto; color: var(--accent); font-weight: 600; }
.subnav .subnav-do:hover { background: var(--accent-soft); border-color: transparent; }

/* A report's exchange reads as a conversation: who said what, in order.

   THE SIDES ARE SET FURTHER DOWN, in the redesign block — .msg.us goes right in
   the accent tint, .msg.them stays left. I wrote a second set of these rules
   here, having looked at a screenshot where four turns rendered identically, and
   the preview showed it doing nothing: the later block wins and mine had the
   sides INVERTED. Two definitions of one class, which is the exact hazard the
   name audit at the foot of this file exists to catch. Do not add a third.

   The second set has now been RETIRED rather than left to lose quietly: padding,
   radius and background said one thing here and another thing there, and only
   the other thing rendered. What is left here is what this block alone sets. */
.msg { margin-bottom: 11px; border-left: 2px solid var(--rule); }
.msg-top { display: flex; align-items: baseline; flex-wrap: wrap;
           margin-bottom: 4px; min-width: 0; }
.msg-who { margin-bottom: 4px; }
.msg-top .msg-who { margin-bottom: 0; overflow-wrap: anywhere; }
/* WHAT A MESSAGE IS WRITTEN IN IS THE PAGE'S INK.
   The owner: "i think the messages text should be darker, it looks grey not
   black". It had no colour of its own and should have inherited --ink — but the
   notifications page later in this file declared its OWN `.msg-body` at
   `color: var(--ink-3)` for a one-line preview cell: same specificity, further
   down the file, so the cascade handed every word ever said to a customer the
   grey meant for a supporting column. Measured in Chromium: #78838e on
   --panel-2 is 3.6:1, which fails the 4.5:1 floor for body text outright. The
   notification rule is now scoped to `.msgrow`, and this one names its token
   out loud so the next unscoped `.msg-body` cannot quietly take it back.
   --ink measures 16.8:1 on --panel-2 and 14.5:1 on --accent-soft in light;
   13.5:1 and 11.0:1 in dark. Both themes clear AAA. */
.msg-body { font-size: 14px; white-space: pre-wrap; overflow-wrap: anywhere;
            color: var(--ink); }
/* An internal note is only ever shown to someone who can direct agents, and it
   must not read as something the reporter was told. Dashed, not coloured — it
   is a different KIND of message, not a more urgent one.
   THE .82 OPACITY CAME OFF THE BODY. It used to sit on the whole block, which
   dropped an internal note's text to roughly 12:1 before the grey above was
   even applied and made the messages an owner reads most the hardest ones on
   the page. It now dims the header only. The border loses the accent colour in
   exchange, because the fade WAS the second signal and removing it without a
   replacement would leave an internal note looking like an ordinary reply from
   the team — two things that must not look alike. */
.msg.internal { border-left-style: dashed; border-left-color: var(--idle); }
.msg.internal > summary, .msg.internal > .msg-top { opacity: .82; }

/* ---------------------------------------------------------------
   A COLLAPSED MESSAGE.
   <details>, so open/closed, the keyboard binding and aria-expanded come from
   the browser and cannot drift, and the thread still reads with scripting off.
   --------------------------------------------------------------- */
.msg > summary { list-style: none; cursor: pointer; display: flex; gap: 8px;
                 align-items: flex-start; }
.msg > summary::-webkit-details-marker { display: none; }
.msg > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px;
                               border-radius: 4px; }
.msg .caret { color: var(--ink-3); font-size: 12px; line-height: 1.7;
              transition: transform .12s ease; display: inline-block; }
.msg[open] > summary .caret { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) { .msg .caret { transition: none; } }
.msg-head { min-width: 0; flex: 1; }
/* The subject is the message's first line — the part that says what this turn
   is, which is the one thing a top line of who-and-when cannot tell you. */
.msg-subject { display: block; font-size: 14px; font-weight: 600; color: var(--ink);
               overflow-wrap: anywhere; }
/* The first two lines, and never a third: the whole point is that a collapsed
   message is a fixed, scannable height. Clamped rather than trusted to fit. */
.msg-peek { display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2;
            -webkit-box-orient: vertical; overflow: hidden;
            font-size: 13px; line-height: 1.45; color: var(--ink-2);
            margin-top: 3px; overflow-wrap: anywhere; }
/* Open: the summary keeps the top line and drops the preview, because the full
   body is now directly underneath and reading the same words twice is noise. */
.msg[open] > summary .msg-subject, .msg[open] > summary .msg-peek { display: none; }
.msg[open] > summary { margin-bottom: 4px; }
/* THE MESSAGE A LINK POINTED AT.
   TWO SELECTORS, ONE RULE, AND BOTH ARE NEEDED. :target covers a pasted URL, a
   bookmark and the case where scripting is off — the browser navigates the
   fragment itself. .msg-shown covers a scripted click, which sets the URL with
   history.replaceState and therefore never re-runs fragment navigation, so
   :target does not match afterwards (Chromium 148: no ring at all on a scripted
   click until this class existed). They are written together so nobody restyles
   one and leaves the other looking like an ordinary message.

   WITHOUT SCRIPTING THE LINK HIGHLIGHTS AND SCROLLS BUT DOES NOT OPEN. There is
   a way to force a closed <details> visible from CSS (::details-content with
   content-visibility) and it does work in this Chromium — it is deliberately not
   used, because it leaves the element open to the eye and closed to the DOM: the
   caret still says closed, and the next press of the summary sets open=true and
   visibly does nothing. A disclosure control that no longer responds is worse
   than one extra click. So the collapsed target still shows its top line, its
   subject and its first two lines, ringed, and opens on one press. */
.msg:target, .msg.msg-shown { box-shadow: 0 0 0 2px var(--accent); }
.msg:target > summary .msg-subject,
.msg.msg-shown > summary .msg-subject { color: var(--accent); }

/* HOW MANY TIMES THIS EXACT MESSAGE IS STORED. Only ever rendered when the
   answer is more than one. Deliberately loud: it is a claim that something
   wrote the same row twice, not a decoration. */
.msg-copy { display: inline-block; margin-left: 7px; font: 600 10.5px/1.5 var(--ui);
            letter-spacing: .04em; text-transform: uppercase;
            color: var(--clash); background: var(--clash-bg);
            border-radius: 4px; padding: 1px 6px; vertical-align: 1px; }
.shots.big img { max-height: 320px; max-width: 100%; }

/* The alarm bar. Red is reserved for something that is actually wrong and
   actionable — not for warnings, not for information. If this is on the screen
   often it has stopped meaning anything. */
/* ONE TOKEN INSTEAD OF THREE THEME-SCOPED RULES, and it fixes a live fault.
   The ink on a --wait fill was set by styling .alarm inside a theme block —
   the one thing the token comment says a component may never do — and it was
   done for the text only. The LINK stayed #fff unconditionally, so in dark,
   where --wait is a pale red, the alarm read as near-black text with a white
   link beside it at about 1.5:1. Both now take --on-wait, which is white in
   light and near-black in dark, and the theme blocks own no components. */
.alarm { background: var(--wait); color: var(--on-wait); padding: 9px 18px;
         font-size: 14px; line-height: 1.45; }
.alarm b { font-weight: 700; }
.alarm a { color: var(--on-wait); text-decoration: underline; text-underline-offset: 2px; }

/* The paste block. Monospace, selectable, and visually a thing you take away
   rather than a thing you read. */
.paste { font: 400 13px/1.6 var(--ui); background: var(--panel-2);
  border: 1px solid var(--rule); border-radius: 4px; padding: 14px 16px;
  white-space: pre-wrap; overflow-x: auto; margin: 0; color: var(--ink); }
.copywrap { display: flex; align-items: center; gap: 10px; }

/* Project cards carry four counts, because "118 reports" describes nothing.
   Anything above zero that needs a person is marked. */
.pcard .counts { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 6px;
                 font-size: 13px; color: var(--ink-3); }
.pcard .counts b { color: var(--ink-2); font-weight: 650;
                   font-family: var(--ui); font-size: 13px; }
.pcard .counts .hot b { color: var(--wait); }
.fb.one { grid-template-columns: minmax(0,1fr); }
/* A report row is a plan row with a different set of columns — the tracks are
   declared once, with .row.rrow's other geometry, further down this file. */
@media (max-width: 1180px) { .row.rrow .pill.idle:first-of-type { display: none; } }

/* The alarm sits outside .wrap and above the bar, so it inherits from body —
   pin its type so it is the same size on every page rather than picking up
   whatever the first block under it happens to set. */
.alarm { font: 400 14px/1.45 var(--ui) !important; }
.alarm b { font-weight: 700; }

/* ---------------------------------------------------------------
   MOBILE. The report page put a 340px sidebar next to the content at every
   width, so on a phone the content column was squeezed to about 90px and text
   wrapped one character per line. The grid template was only overridden for
   .console in the mockup's breakpoints; these pages set their own inline
   template, which beat the media query. Inline styles win over stylesheets, so
   the fix has to be as specific as the thing it is correcting.
   --------------------------------------------------------------- */
@media (max-width: 900px) {
  main.console[style] { grid-template-columns: minmax(0,1fr) !important; }
  main.console > aside { order: 2; }
  main.console > section { order: 1; }
  .d-head, .sec, .card-b, .card-h { padding-left: 13px; padding-right: 13px; }
  .subnav { gap: 6px; }
  .subnav .subnav-do { margin-left: 0; }
  .wrap { padding: 0 10px; }
}
@media (max-width: 560px) {
  .msg { padding: 8px 10px; }
  .set { grid-template-columns: minmax(0,1fr); gap: 2px 0; }
  .set dt { margin-top: 7px; }
  .row.rrow { grid-template-columns: 6px 54px minmax(0,1fr) 96px; }
  .tbl-scroll table { min-width: 420px; }
}

/* A disabled control has to LOOK disabled. It rendered in full accent blue with
   a pointer cursor while doing nothing — which reads as a broken button rather
   than an unavailable one.

   THERE IS A SECOND RULE FOR THIS, further down, covering :disabled and
   [aria-disabled="true"]. It sets the background, and because [disabled] and
   :disabled match the same elements it always won — the var(--idle-bg) that
   used to sit here never rendered anywhere. Left as two rules because they
   cover different selector sets, but they are one decision: read both. */
.btn:disabled, .btn[disabled] {
  border-color: var(--rule);
  color: var(--ink-3); cursor: not-allowed; filter: none;
}
.btn:disabled:hover { filter: none; }

/* Where a report came from should be openable, not just readable. */
.tr-meta a, .set dd a { color: var(--accent); }

/* THE FACTS PANEL CLIPS A URL, which is the second half of the same bug.
   .msg-body and .tr-body were fixed; this list was not, and it is the one the
   owner circled — PAGE https://mddoor.clinchbid.com/project/773… cut off
   mid-token with no way to read or copy the rest.
   The grid column is already minmax(0,1fr) so it CAN shrink; what it lacked is
   permission to break a token that has no spaces in it. Fixing one element and
   not the other is how a class of bug survives being fixed: the reporter sees
   it again in a different box and reasonably assumes nothing was done. */
.set dd, .set dd a, .tr-meta a { overflow-wrap: anywhere; }
.linkline { display: grid; grid-template-columns: 64px minmax(0,1fr); gap: 9px;
            align-items: baseline; padding: 4px 0; font-size: 13px; }
.linkline .kind { font: 600 10.5px/1.6 var(--ui); letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-3); border: 1px solid var(--rule);
  border-radius: 4px; text-align: center; }
.linkline a { word-break: break-all; }

/* ---------------------------------------------------------------
   TWO LINES ON A PHONE, and the answer to "does it need it": yes.
   The one-line row works on a desktop because six columns fit. At 390px they do
   not, so the previous behaviour DROPPED columns — reporter, date, whose move —
   and a list you scan to find what needs you is useless if the thing that says
   what needs you is the part hidden. Two lines cost height and keep the
   information; hiding it costs nothing and loses the point of the list.
   --------------------------------------------------------------- */
@media (max-width: 700px) {
  /* THREE LINES, NOT TWO COLUMNS. Sharing line two between the title and the
     meta made the title wrap twice as often to leave room for an email address
     nobody reads first. The title gets the full width; the meta gets its own
     line, right-aligned, where it is available without being in the way. */
  .row, .row.rrow {
    grid-template-columns: 6px minmax(0,1fr) auto !important;
    grid-template-areas: "stripe ref state" "stripe title title" "stripe meta meta";
    height: auto; padding: 9px 12px 9px 0; align-items: baseline;
    row-gap: 4px;
  }
  .row .stripe { grid-area: stripe; }
  .row .r-ref  { grid-area: ref; }
  .row .r-title{ grid-area: title; white-space: normal; line-height: 1.4;
                 overflow: visible; }
  .row .pill:not(.lifecycle) { grid-area: state; justify-self: end; }
  .row .pill.lifecycle, .row .r-area { display: none; }
  .row .r-wait, .row .tip.r-wait {
    grid-area: meta; display: block; justify-self: end; text-align: right;
    font-size: 12px;
  }
  .row.rrow .r-area { display: block; grid-area: meta; justify-self: end;
                      font-size: 12px; text-align: right; }
  .row.rrow .r-wait, .row.rrow .tip.r-wait { display: none; }
  /* A report row's kind chip is the second-least useful thing at this width. */
  .row.rrow .pill.idle:first-of-type { display: none; }
}

/* Voting. Small, and quiet until somebody has an opinion. */
.votebar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.votebtn { font: 400 16px/1 var(--ui); width: 30px; height: 28px; cursor: pointer;
           background: var(--panel); color: var(--ink-3);
           border: 1px solid var(--rule); border-radius: 4px; }
.votebtn:hover { border-color: var(--accent); color: var(--accent); }
.votebtn.on.up   { background: var(--done-bg); border-color: transparent; color: var(--done); }
.votebtn.on.down { background: var(--wait-bg); border-color: transparent; color: var(--wait); }
.votescore { font-size: 14px; font-weight: 650; min-width: 26px; text-align: center;
             color: var(--ink-3); }
.votescore.up { color: var(--done); } .votescore.down { color: var(--wait); }
.votecell { font-size: 13px; color: var(--ink-3); text-align: center; }
.votecell.up { color: var(--done); font-weight: 650; }
.votecell.down { color: var(--wait); font-weight: 650; }
.lnk .kind.up   { color: var(--done); border-color: var(--done); }
.lnk .kind.down { color: var(--wait); border-color: var(--wait); }
.row.rrow { grid-template-columns: 8px 34px 62px minmax(0,1fr) 78px 108px 150px 150px; }
@media (max-width: 1180px) { .row.rrow { grid-template-columns: 8px 34px 62px minmax(0,1fr) 108px 120px; } }
@media (max-width: 860px)  { .row.rrow { grid-template-columns: 8px 34px 56px minmax(0,1fr) 108px; } }
@media (max-width: 700px)  { .row.rrow .votecell { grid-area: ref; justify-self: start;
                               margin-left: 46px; } }

/* Grants edited in place, one line per project. */
.grantrow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
            padding: 4px 0; }
.grantkey { font-size: 13px; color: var(--accent); min-width: 68px; }
.grantform { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.grantform label { font: 500 12px/1 var(--ui); letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-3); display: flex; align-items: center; gap: 4px; }
.grantform select { font: inherit; font-size: 13px; padding: 2px 4px;
  border: 1px solid var(--rule); border-radius: 4px; background: var(--panel);
  color: var(--ink); }

/* ---------------------------------------------------------------
   THE TOP BAR STICKS TO THE TOP.
   It was offset 40px because the MOCKUP has its own chrome bar above it — a
   screen-switcher that exists only there. The app has no such bar, so the offset
   left a blank band at the top of every page and the content scrolled underneath
   the bar instead of stopping at it. A number that was right in the file it came
   from and wrong in the file that inherited it.

   The alarm is deliberately NOT sticky: it is read once and then it is in the
   way, and a permanent red band is a red band people stop seeing.
   --------------------------------------------------------------- */
.topbar { top: 0 !important; }
.detail { top: 56px !important; max-height: calc(100vh - 70px) !important; }
.group-head { top: 56px; }

/* Two fields side by side line up on their CONTROLS, not on whatever help text
   sits under one of them. A select and a file input in the same row were sitting
   at different heights because the taller cell stretched the shorter one. */
.split { align-items: start; }
.split > .field { align-self: start; }
.field input[type=file] { padding: 6px 8px; }

/* ===============================================================
   FORM FIELDS — uniform by rule, not by luck. See AGENTS.md.

   Every control is the full width of its column, the same height, and under a
   label in the same place. Two side by side line up top and bottom.

   What was going wrong, all of it invisible one at a time:
     - .split was auto-fit/minmax, so columns took different widths depending on
       their content and no two rows agreed;
     - help text under one field and not its neighbour pushed the next row down
       on one side only;
     - a <select> and an <input type=file> have different intrinsic heights and
       will never match unless told to;
     - stretch alignment made a short field as tall as a tall one, so the control
       inside it floated.
   Together they read as a page nobody laid out.
   =============================================================== */
.split {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px 18px;
  align-items: start;
}
@media (max-width: 720px) { .split { grid-template-columns: minmax(0, 1fr); } }

.field {
  display: grid;
  grid-template-rows: auto auto 1fr;   /* label, control, help — always this order */
  gap: 5px;
  align-content: start;
  margin: 0;
}
/* A DENSITY VARIANT IS A CLASS, NOT AN ANCESTOR. .stateform and .grantgrid
   each reached in and shrank the label to 9.5px and the control to 32px, so
   `.field` meant one size in an ordinary form and another inside those two —
   with nothing in either place saying so. The variant is now written on the
   field, and the two forms carry it. Anything else that needs a tight row asks
   for it by name instead of growing a third ancestor rule. */
.field.dense > label { font-size: 10.5px; letter-spacing: .08em; }
.field.dense input,
.field.dense select { height: 32px; min-height: 32px; padding: 5px 8px; font-size: 13px; }

/* .field > label and the controls' own size, colour and radius are set ONCE,
   in the form block further down this file. This rule keeps only what that
   block does not say. */
.field input,
.field select,
.field textarea {
  width: 100%;
  font: inherit;
  color: var(--ink);
  border: 1px solid var(--rule);
  box-sizing: border-box;
}
.field textarea { resize: vertical; line-height: 1.45; }
.field input[type="file"] { padding: 7px 10px; line-height: 20px; }
.field input:focus, .field select:focus, .field textarea:focus {
  border-color: var(--accent); outline: none;
}
.field .help { font-size: 13px; line-height: 1.45; color: var(--ink-3); }

/* A grant is a card per project: which project, its four scopes, save, remove.
   Everything about one project in one bordered box, so three projects read as
   three things rather than a run-on line and a column of orphaned buttons. */
.grantcard { border: 1px solid var(--rule); border-radius: 4px;
             margin-bottom: 10px; background: var(--panel); }
.grantcard:last-child { margin-bottom: 0; }
.grantcard-h { display: flex; align-items: center; gap: 10px;
               padding: 7px 11px; background: var(--panel-2);
               border-bottom: 1px solid var(--rule-2); border-radius: 4px 4px 0 0; }
.grantcard-h .grantkey { font-size: 13px; font-weight: 650; color: var(--accent); }
.grantgrid { display: grid; grid-template-columns: repeat(2, minmax(0,1fr));
             gap: 10px 14px; padding: 11px; align-items: start; }
/* Dense fields, same as .stateform — see .field.dense. */
.grantsave { grid-column: 1 / -1; }
.grantsave .btn { padding: 6px 14px; font-size: 13px; }
.grantgrid.readonly { font-size: 13px; color: var(--ink-2); }
.grantgrid.readonly .lbl { font: 600 10.5px/1 var(--ui); letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-3); margin-right: 6px; }
.btn.ghost.danger { color: var(--wait); border-color: var(--rule);
                    padding: 4px 10px; font-size: 13px; }
.btn.ghost.danger:hover { border-color: var(--wait); background: var(--wait-bg); }
@media (max-width: 900px) { .grantgrid { grid-template-columns: minmax(0,1fr); } }

/* A BUILD AND ITS SHIP ARE ONE THING ON THE PAGE.
   A report built twice showed two identical ship boxes in a block of their own,
   with nothing tying either to the build it would deploy — you had to count. The
   rule holding it together is a bracket: everything indented under the build's
   own line belongs to that build, and the ship box says which build it is in
   words as well, because indentation alone is not a label. */
.shipbox { border: 1px solid var(--rule); border-radius: 4px; background: var(--panel-2);
           padding: 10px 11px; display: grid; gap: 8px;
           /* THE SAME BUG AS THE FACTS PANEL, AND IT WOULD HAVE SHIPPED AGAIN.
              This box names the pull request it would deploy, and a URL is one
              token with nothing to break on: in a 340px sidebar it pushed the
              whole box past the edge and cut the sentence after it. It was
              visible in the first screenshot taken of this control. Same fix as
              4770c5b, applied at the moment the element was written rather than
              after somebody circled it. */
           overflow-wrap: anywhere; }
.shipbox-h { font: 650 13px/1.2 var(--sans, inherit); letter-spacing: .02em;
             display: flex; align-items: baseline; gap: 8px; }
.shipbox-h .mono { font-size: 12px; color: var(--ink-3); font-weight: 400; }
/* The checklist is the answer to "what do I have to do to ship". Each line is a
   condition the server actually enforces, with its live answer — a ✓ nobody
   checked would be worse than no list at all. */
.shipcheck { list-style: none; margin: 0; padding: 0; display: grid; gap: 5px;
             font-size: 13px; line-height: 1.45; }
.shipcheck li { display: grid; grid-template-columns: 15px minmax(0,1fr); gap: 6px;
                align-items: start; }
.shipcheck .mk { font-weight: 700; line-height: 1.45; }
.shipcheck li.yes .mk { color: var(--done); }
.shipcheck li.no  .mk { color: var(--wait); }
.shipcheck li.no  { color: var(--ink-2); }
.shipsteps > summary { cursor: pointer; font-size: 13px; color: var(--ink-2); }
.shipsteps ol { margin: 2px 0 0; padding-left: 18px; font-size: 13px; line-height: 1.5;
                color: var(--ink-2); display: grid; gap: 4px; }
.shipbox .btn[disabled] { opacity: .45; cursor: not-allowed; }

/* An objection is quoted verbatim, so it is set like every other quoted body on
   this page — and with the same permission to break a long token, because the
   thing somebody types into it is often a path or a URL. */
.objection { margin: 0 0 8px; padding: 6px 9px; font-size: 13px; line-height: 1.5;
             border-left: 2px solid var(--rule-2); color: var(--ink-2);
             overflow-wrap: anywhere; }

/* Project tabs on the per-project settings pages. A real tab strip: the current
   one is attached to the panel below it and the others sit behind. The chip row
   this replaces was borrowed from a component that LISTS somebody's access — a
   label, not a control — so it read as decoration you could not tell was
   clickable. */
.projtabs { display: flex; gap: 3px; flex-wrap: wrap; margin-bottom: -1px;
            position: relative; z-index: 1; }
.projtabs a {
  font-size: 14px; font-weight: 550; padding: 9px 18px;
  text-decoration: none; color: var(--ink-2);
  background: var(--panel-2);
  border: 1px solid var(--rule); border-bottom-color: var(--rule);
  border-radius: 4px 4px 0 0;
}
.projtabs a:hover { color: var(--ink); background: var(--panel); }
.projtabs a[aria-current="page"] {
  background: var(--panel); color: var(--accent); font-weight: 650;
  border-bottom-color: var(--panel);
}
.projtabs + .pane { border-top-left-radius: 0; }

/* ------------------------------------------------------------------ */
/* THE WAY BACK — one component, not a per-page invention.             */
/*                                                                     */
/* IT LOOKS LIKE A BUTTON AT REST, not only under the cursor. The first */
/* attempt was white on the light-grey ground with a hairline border,   */
/* which reads as text until you happen to hover it — a control whose   */
/* affordance only appears once you have already guessed it is a        */
/* control is not an affordance. It now uses the panel surface and the  */
/* same ink as the app's other secondary buttons, so it is legible as   */
/* something you press before anyone touches it.                        */
.backnav { display: flex; align-items: center; padding: 13px 0 0; }
.backnav a {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 14px; font-weight: 550; line-height: 1;
  padding: 9px 15px 9px 12px;
  border: 1px solid var(--rule); border-radius: 4px;
  background: var(--panel); color: var(--ink);
  text-decoration: none;
  box-shadow: 0 1px 1px rgba(16,24,40,.05), 0 1px 0 rgba(16,24,40,.02);
  transition: background .12s, border-color .12s, box-shadow .12s, transform .1s;
}
.backnav a:hover {
  background: var(--panel-2); border-color: var(--ink-3);
  box-shadow: 0 2px 4px rgba(16,24,40,.08);
}
/* The arrow moves, not the button — the motion says "back" rather than
   just "something happened". */
.backnav a:hover .bk-arrow { transform: translateX(-2px); color: var(--accent); }
.backnav a:active { transform: translateY(1px); box-shadow: none; }
.backnav a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.bk-arrow { color: var(--ink-3); font-size: 14px; transition: transform .12s, color .12s; }

@media (prefers-reduced-motion: reduce) {
  .backnav a, .bk-arrow { transition: none; }
  .backnav a:hover .bk-arrow { transform: none; }
}

/* ------------------------------------------------------------------ */
/* A BUTTON WITH NOTHING TO DO                                         */
/*                                                                     */
/* Light, and not clickable. The cursor matters as much as the colour: */
/* `not-allowed` answers "is it broken or am I missing something"      */
/* before the person has to press it to find out.                      */
.btn:disabled,
.btn[aria-disabled="true"] {
  background: var(--panel);
  border-color: var(--rule);
  color: var(--ink-3);
  cursor: not-allowed;
  box-shadow: none;
  opacity: 1; /* the muted palette above IS the disabled look — not a fade */
}
.btn:disabled:hover,
.btn[aria-disabled="true"]:hover {
  background: var(--panel);
  border-color: var(--rule);
  color: var(--ink-3);
}

/* ------------------------------------------------------------------ */
/* MESSAGES — rows, like everything else that is a list of things      */
.msglist { display: flex; flex-direction: column; }
.msgrow {
  display: grid; align-items: start; gap: 12px;
  grid-template-columns: 96px 190px minmax(0,1fr) 90px 118px;
  padding: 11px 16px; border-top: 1px solid var(--rule-2, var(--rule));
  font-size: 14px;
}
.msgrow:first-child { border-top: 0; }
.msgrow .msg-to { color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; }
.msgrow .msg-sub { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.msgrow .msg-sub b { font-weight: 600; }
/* SCOPED TO THIS ROW, and it must stay scoped. Unscoped, this rule sat below
   the report thread's own `.msg-body` at equal specificity and won the cascade,
   so every word of every exchange rendered in the grey meant for this column's
   one-line preview. That is the bug the owner reported as "the messages text
   should be darker, it looks grey not black". */
.msgrow .msg-body { color: var(--ink-3); font-size: 13px; line-height: 1.45;
                    overflow-wrap: anywhere; }
/* Why it did not go is the point of the row, so it is not grey like the body.
   It said var(--warn, #9a5b00) and --warn has never been defined, so the
   literal was what rendered — a fallback that is always taken is just a hex
   with extra syntax. --active is the amber this palette already has. */
.msg-why { color: var(--active); font-size: 13px; }
.msg-about, .msg-when { color: var(--ink-3); font-size: 13px; }
.msg-when { text-align: right; }

@media (max-width: 860px) {
  .msgrow { grid-template-columns: 1fr auto; gap: 4px 10px; }
  .msgrow .pill { grid-row: 1; }
  .msg-when { grid-row: 1; text-align: right; }
  .msg-to, .msg-sub, .msg-about { grid-column: 1 / -1; }
}

/* ------------------------------------------------------------------ */
/* WHOSE MOVE — assigning a question to a person                       */
.assignbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--rule-2, var(--rule)); }
.assignbar .asg-l { font-size: 12px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-3); }
.assignbar select { height: 32px; font-size: 13px; padding: 4px 8px; min-width: 150px; }
.assignbar .asg-c { display: inline-flex; align-items: center; gap: 5px;
  font-size: 13px; color: var(--ink-2); }
.assignbar .asg-c input { width: auto; height: auto; }
.assignbar .btn { padding: 4px 12px; font-size: 13px; }
/* Who holds it now, stated rather than implied by the dropdown's value. */
.assignbar .asg-now { font-size: 13px; color: var(--ink-3); }
.qr-who .held { color: var(--accent); font-weight: 600; }

/* ------------------------------------------------------------------ */
/* THE WEEKLY BUILD REPORT                                             */

/* The email, shown exactly as the outbox holds it. Monospaced and wrapped
   rather than scrolled sideways: the body is written to a plain-text width and
   a horizontal scrollbar would hide the ends of the lines that matter — the
   ones naming a row and who it waits on. */
.wk-pre {
  font: 13px/1.6 var(--ui); white-space: pre-wrap; word-break: break-word;
  margin: 0; padding: 12px 13px; background: var(--panel-2); color: var(--ink);
  border: 1px solid var(--rule); border-radius: 8px; overflow-x: auto;
}

/* `pill clash` was already being rendered by the messages ledger for a HELD
   message and had no rule of its own, so the loudest state on that page looked
   identical to the quietest. It has one now, and the weekly report uses it for
   the questions nobody holds — the state that most needs to be seen. */
.pill.clash { color: var(--clash); }

/* ------------------------------------------------------------------ */
/* A SCREENSHOT OPENS IN PLACE, NOT IN A NEW TAB                       */
.shots .shot-open { padding: 0; border: 1px solid var(--rule); background: none;
  border-radius: 4px; cursor: zoom-in; overflow: hidden; line-height: 0; }
.shots .shot-open:hover { border-color: var(--accent); }
.shots .shot-open img { display: block; }
.shot-lightbox { position: fixed; inset: 0; z-index: 900; display: flex;
  flex-direction: column; align-items: center; justify-content: center; gap: 14px;
  background: rgba(12,16,20,.82); padding: 32px; cursor: zoom-out; }
.shot-full { max-width: 96vw; max-height: 86vh; object-fit: contain;
  border-radius: 4px; box-shadow: 0 18px 50px rgba(0,0,0,.45); cursor: default; }
.shot-hint { color: rgba(255,255,255,.72); font-size: 13px; }

/* ------------------------------------------------------------------ */
/* AN ATTACHMENT WHOSE BYTES ARE GONE                                  */
/* Shown as a stated fact, never as a broken image — a broken image is */
/* indistinguishable from a bug in the viewer.                         */
.shot-gone { display: flex; gap: 10px; align-items: flex-start;
  padding: 9px 11px; margin-top: 8px; border: 1px dashed var(--rule);
  border-radius: 4px; background: var(--panel-2); }
.shot-gone .sg-tag { font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-3); border: 1px solid var(--rule); border-radius: 4px;
  padding: 2px 6px; white-space: nowrap; margin-top: 1px; }
.shot-gone .sg-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; font-size: 14px; }
.shot-gone .sg-body span { color: var(--ink-3); font-size: 13px; }
.shot-gone .sg-url { font-size: 13px; color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; }

/* ARCHIVING A PROJECT — folded away until asked for.
   The control is real and destructive-looking, so it does not sit open next to
   ordinary settings where a mis-click lives. Opening it is deliberate, and what
   it reveals is a typed confirmation rather than a second button. */
.arch > summary { cursor: pointer; color: var(--wait); font-size: 13px;
  list-style: none; user-select: none; }
.arch > summary::-webkit-details-marker { display: none; }
.arch > summary:hover { text-decoration: underline; }
.arch[open] > summary { color: var(--ink-3); }
.arch form { max-width: 460px; }

/* ---------------------------------------------------------------
   A HEADING MUST OUTRANK WHAT IT CONTAINS.

   .sec-h and .field > label were byte-identical: 600 10.5px/1 mono, uppercase,
   letter-spaced, var(--ink-3). So "WORK IT" and "WHAT KIND OF THING IS IT" were
   drawn the same, and nothing on the page said one contained the other — the
   owner circled two headings and said so. A reader then has to parse the words
   to recover the structure the layout should have given them for free.

   This is deliberately fixed on .sec-h, not by shrinking the labels: the labels
   are correctly quiet, and there are dozens of them. Changing the parent once
   fixes every section on every page — the report rail, the settings panes, the
   item page, the notification lists — rather than one screen the owner happened
   to be looking at.

   Darker, heavier, slightly larger, and given room beneath it. Still uppercase
   mono, because the family is what makes these read as structure rather than
   prose; the rank comes from weight and colour, which is where a reader looks
   for it.

   ==================== AND THAT WAS NOT ENOUGH. ROUND TWO. ====================
   The owner, looking at a live item page: "the what this is titles are so
   light." Keeping the mono uppercase family was the mistake. At 12.5px, letter-
   spaced .15em, a heading is optically SMALLER than the 14.5px prose it heads —
   uppercase mono spends its width on tracking rather than on letterforms, so
   "WHAT THIS IS" measures wide and reads small, and the rows underneath it won.
   Weight and colour could not buy back what the family was giving away.

   So the family changes: UI font, sentence case, --ink. 10px mono uppercase is
   not deleted — it is now RESERVED, and the reservation is the point. It means
   "micro-label": a field's name above its input, and the rail's dt column on the
   detail pages. Those are correctly quiet and there are dozens of them. Once one
   family means heading and the other means label, a reader gets the structure
   from the layout instead of parsing the words for it.

   ============ ROUND THREE, AND THIS TIME IT WAS MEASURED ============
   Round two went to 13px/600 and that still fails the invariant. Measured on the
   rendered page, the row directly beneath a section title (`.wn-l`) is
   600 14.5px. A 13px/600 title in the same colour and the same weight is a PEER,
   not a parent. "So light" never meant low-contrast — --ink-hd on --panel-2 is
   7.23:1 — it meant small and out-weighed, and going down in size while going up
   in weight cannot fix that.

   Now 650 15px sentence: larger AND heavier than the rows it heads, on both
   axes, which is the only arrangement that satisfies the invariant rather than
   arguing about it. The alternative on the table was 700 12.5px mono caps with
   .14em tracking — rejected, because it is the same bet round two lost: buying
   presence with tracking at a nominal size SMALLER than the content. The
   reports list is the independent evidence: it already draws a closed group
   summary at 14.5px sentence bold next to an open one at 10px mono caps, and
   the bigger one is obviously the better heading.

   ============ AND THERE WERE THREE DEFINITIONS OF IT ============
   `.sec-h` was declared here, in mockup/console.html, and a third time on
   `.cols .pane > .sec > .sec-h` — which at 0-3-3 beat this rule's 0-1-0 and was
   the only one that ever applied, because every `.sec-h` in the product renders
   inside `.cols .pane`. So the "make it heavier" change had ALREADY been made,
   here, in a dead rule, and had done nothing to the page for however long it had
   been sitting there. Nobody could have caught that by reading: the rule is
   correct, it is simply out-voted.

   THE TYPE IS NOW STATED ONCE, in the mockup, which is where a token belongs —
   build-css concatenates it first for exactly this reason. This block keeps only
   what is genuinely extra: the room beneath a heading and the rule under it. The
   rail is the one documented exception and overrides where it happens. */
.sec > .sec-h { margin-bottom: 13px; }
/* A rule under the heading does what weight alone cannot: it shows where the
   section STARTS. Weight says 'this is important'; a line says 'everything
   below me belongs to me', which is the actual question the owner was asking. */
.sec > .sec-h { padding-bottom: 6px; border-bottom: 1px solid var(--rule); }

/* The count or hint that rides alongside a heading must NOT inherit the new
   weight — it is an aside, and a bold aside competes with the thing it
   qualifies. */
.sec-h .mono, .sec-h span[style] { font-weight: 500; }

/* THE REPORTER HAS NOT BEEN TOLD — an attention box on the report itself.
   Modelled on .staterow.stray: a --wait-bg fill and a --wait left edge, so it
   reads as "this needs a decision" without the red of .alarm, which is reserved
   for a fault. It carries the send / file-away actions right where the person
   already is, instead of on a queue on another screen. */
.tell-reporter { background: var(--wait-bg); border: 1px solid var(--rule);
  border-left: 3px solid var(--wait); border-radius: 6px; }
.tell-reporter > .card-b { padding: 13px 15px; }
.tell-reporter b { color: var(--ink); }
.tell-reporter .note { color: var(--ink-2); }

/* WHAT THE USER FILED — the reporter's own words, first. Their text VERBATIM
   (pre-wrap keeps their line breaks), the page they filed from, any screenshot. */
.userfiled .uf-body { margin: 0; white-space: pre-wrap; font-size: 16px; line-height: 1.5;
  color: var(--ink); }
.userfiled .uf-meta { margin: 9px 0 0; font-size: 13px; color: var(--ink-3);
  word-break: break-all; }
.userfiled .uf-meta a { color: var(--accent); text-decoration: none; }
.userfiled .uf-meta a:hover { text-decoration: underline; }
.userfiled .shots { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 11px; }

/* ---------------------------------------------------------------
   THE STATUS LIST: READABLE CLOSED, COMPLETE OPEN.

   Every row was a form with seven fields showing, so twenty statuses meant a
   hundred and forty controls on screen at once and the list could not be read
   at all — the owner's word was "noisy". A settings page is read far more often
   than it is edited, so the closed state is a line of facts and the editor is
   asked for.

   The summary IS the row, so the whole line is the target. A pencil icon in a
   corner would be a smaller target for the same act, and this list is used on a
   laptop trackpad. */
.stateedit > summary { list-style: none; cursor: pointer; }
.stateedit > summary::-webkit-details-marker { display: none; }
.stateline {
  display: grid; grid-template-columns: 32px minmax(120px,1.4fr) minmax(90px,1fr) 76px 96px 44px;
  gap: 10px; align-items: center; padding: 9px 4px;
  border-bottom: 1px solid var(--rule); font-size: 14px;
}
.stateline:hover { background: var(--panel-2); }
.stateedit[open] > summary .sl-edit { color: var(--ink-3); }
.sl-pos   { color: var(--ink-3); font-size: 13px; }
.sl-label { font-weight: 600; color: var(--ink); }
.sl-key   { color: var(--ink-3); font-size: 13px; overflow-wrap: anywhere; }
.sl-held  { color: var(--ink-3); font-size: 13px; text-align: right; }
.sl-edit  { color: var(--accent); font-size: 13px; text-align: right; }
.stateedit[open] { background: var(--panel-2); border-bottom: 1px solid var(--rule); }
.stateedit[open] .stateform { padding: 12px 4px 4px; }

/* The destination select read as loose text next to a red button — the owner
   said it was "not a clear box". It is a control that decides where live
   reports go, so it is drawn like one. */
.statedel { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; padding: 4px 4px 14px; }
.statedel select {
  min-width: 210px; padding: 7px 9px; font-size: 14px;
  border: 1px solid var(--rule); border-radius: 4px; background: var(--panel);
}

/* WIDER, because the owner asked to see it all. This tab holds seven columns of
   editing; the default settings width was chosen for forms half this size. */
@media (min-width: 1100px) {
  .statuswide { max-width: 1180px; }
}

/* ---------------------------------------------------------------
   THESE CONTROLS NOW LIVE IN THE PAGE COLUMN, NOT THE 340px RAIL.
   The rules above were written against the rail, where everything was as wide
   as it could be and the only risk was overflow. In the page column the risk
   inverts: the send-back field and the ship box would stretch to the full
   measure, and a one-line input 900px wide reads as a mistake, not as room.
   Capped rather than centred — they stay left-aligned with everything else in
   the column, they just stop growing.
   --------------------------------------------------------------- */
/* #work, NOT #agent-work. The card was renamed to "The work itself" — the same
   name and the same id the item page has used since the redesign landed — so
   these had to move with it or they would style nothing at all, silently. */
#work .shipbox { max-width: 640px; }
#work .retrybox .field { max-width: 480px; }
#work .objection { max-width: 70ch; }

/* THE WAY OUT OF A FAILURE. Named for what it is rather than for the control it
   used to sit inside: it is a retry, on a failed turn, and calling it
   `.verdictback` meant two different acts shared a class because they once
   shared a shape. STACKED, NOT SIDE BY SIDE — beside its button the input was
   about 150px wide and its placeholder was cut to "What the next pass h". */
.retrybox { display: grid; gap: 6px; justify-items: start; margin: 0 0 8px; }
.retrybox .field { width: 100%; }
.retrybox .btn { padding: 5px 11px; font-size: 13px; white-space: nowrap; }

/* ---------------------------------------------------------------
   THE SAME TOOLTIP, WORN AS A QUESTION MARK.
   The owner: "in general i hate these long messages if its needed it should
   maybe be a tool tip but these messages take so much screen real estate."
   The report page carried fourteen explanations over 110 characters.

   A MODIFIER, NOT A SECOND COMPONENT — and the first attempt got this wrong in
   a way worth recording. It declared its own `.tip` further down this file,
   which silently RESTYLED every tooltip already on the site: the glossary wraps
   whole WORDS in .tip (with .tip.word underlining them), and `width: 14px` on
   that clips the word to a circle. Two rules for one class name is two
   components sharing one name, and the later one wins everywhere — including on
   pages nobody was looking at. Caught before it deployed, by reading the file
   rather than the diff.

   So: the balloon, the arrow, the focus behaviour and the title fallback all
   stay exactly as they were. This adds only the affordance — a small circled ?
   for places where there is no word to underline.
   --------------------------------------------------------------- */
.tip.q { display: inline-grid; place-items: center; width: 14px; height: 14px;
         padding: 0; margin-left: 4px; vertical-align: middle;
         border: 1px solid var(--rule); border-radius: 50%;
         background: var(--panel-2);
         font: 700 10.5px/1 var(--ui); color: var(--ink-3); }
.tip.q:hover, .tip.q:focus-visible { color: var(--ink); border-color: var(--ink-3); }

/* WHERE THE WORK STANDS, on an item. */
.wstate { margin: 0 0 8px; font-size: 13px; line-height: 1.5; display: grid; gap: 2px; }
.wstate b { font-size: 14px; }
.wstate.good b { color: var(--done); }
.wstate.wait b { color: var(--wait); }
/* --stop was never defined either, so this rendered #b3261e — a red one shade
   off the --wait its sibling above already uses. One red. */
.wstate.bad  b { color: var(--wait); }
.wstate.idle b { color: var(--ink-2); }
/* ============ EVERY TURN IS ITS OWN BOX ============
   The owner, on the question cards: "i actually like that question design in
   general since it encapsulates each item nicely, on the work itself it looks
   like one long list if everything will have its box like this its uch nicer".

   It WAS one long list. A 2px left rule is enough to group two lines and not
   enough to hold a turn that carries a heading, an agent's several-paragraph
   answer, a runbook and three controls — so eight turns ran together and the
   reader had to find the seams. The same border, radius and padding the
   question cards use, because they are the same kind of object: one thing that
   happened, with its own controls. */
.wturn { border: 1px solid var(--rule); border-radius: 8px;
         padding: 11px 13px; margin: 0 0 10px; background: var(--panel);
         display: grid; gap: 4px; }
.wt-h { display: flex; flex-wrap: wrap; gap: 7px; align-items: baseline; font-size: 13px; }
/* AN AGENT'S ANSWER KEEPS THE SHAPE IT WAS WRITTEN IN.
   There was no white-space rule here, so every blank line an agent wrote —
   every paragraph break, every list — collapsed into one continuous block. The
   structure was in the text the whole time and the page was throwing it away,
   which is why a careful answer read as a wall. The owner: "It is very hard to
   read."
   Also given a MEASURE. Prose set to the full width of a page column is hard to
   read for a reason unrelated to its content: the eye loses the line. 78
   characters is the usual answer and it costs nothing. */
/* ============ THE READING MEASURE, AND WHO IT IS FOR ============
   78ch is a real typographic rule — a line much longer than that is hard to
   track back to the start of the next one — and on a 1280px screen it costs
   nothing, because the column is narrower than the measure anyway.

   On a 2000px monitor it is the whole complaint. The owner, on an agent's
   answer: "there is so much dead space on the screen and i have to scroll so
   much to read it." He is looking at 440px of text inside a 1450px card,
   scrolling through six screens of something that would fit in two.

   SO IT FOLLOWS THE WIDTH HE CHOSE. Picking "full" is a person saying they want
   the screen used; keeping a 78-character measure after that is the setting
   being overruled by a stylesheet. Comfortable and wide keep the measure —
   that is what they are for. */
.wt-body { margin: 0; font-size: 14px; line-height: 1.6; color: var(--ink-2);
           white-space: pre-wrap; max-width: 78ch; overflow-wrap: anywhere; }
[data-width="full"] .wt-body,
[data-width="full"] .msg-body,
[data-width="full"] .note-body,
[data-width="full"] .wt-asked,
[data-width="full"] .turn-note,
[data-width="full"] .dc-body,
[data-width="full"] #work .objection,
[data-width="full"] .q-answer,
/* AND THE CONTROLS UNDER A TURN, which are capped at 640px — the composer you
   type the next pass into and the help text under the buttons are both children
   of .turnactions, so both stopped at 640 while the agent's answer above them
   ran to 1450. The owner: "the box and the text under it should also adopt to
   the width." A reading measure on PROSE is a rule; the same cap on a box you
   TYPE INTO is just a narrow box. */
[data-width="full"] .turnactions,
[data-width="full"] .shipbox,
[data-width="full"] .testbox,
[data-width="full"] .retrybox .field { max-width: none; }
.wt-link { margin: 0; font-size: 13px; overflow-wrap: anywhere; }
.wt-link .lbl { font: 600 10.5px/1 var(--ui); letter-spacing: .08em;
                text-transform: uppercase; color: var(--ink-3); margin-right: 6px; }

/* WHAT AN AGENT RETURNED, WHEN IT IS A PICTURE ("maybe even a screenshot").
   CAPPED IN HEIGHT, and that is the whole rule rather than a preference: this
   sits directly above the Accept control, and a full-height screenshot would
   push the one button the person came to press off the bottom of the screen —
   the same "you cannot review it" fault it was added to fix, one step along.
   The image is a link to itself, so full size is one click away. */
.wt-shot { margin: 0; }
.wt-shot img { display: block; max-width: 100%; max-height: 320px; width: auto;
               border: 1px solid var(--rule); border-radius: 4px; background: var(--panel-2); }

/* The one act on an item that no agent can perform. Looks like the ship box it
   follows, because it is the same weight of moment — the difference is that
   this one is answered by a person trying the thing. */
.testbox { border: 1px solid var(--rule); border-radius: 4px; background: var(--panel-2);
           padding: 10px 11px; margin: 0 0 10px; display: grid; gap: 7px;
           overflow-wrap: anywhere; max-width: 640px; }

/* Finished jobs, folded. The count is on the summary so nothing is hidden — a
   fold that does not say how much it holds is indistinguishable from a bug. */
.donefold > summary { cursor: pointer; font-size: 13px; color: var(--ink-2);
                      padding: 8px 14px; }
.donefold[open] > summary { border-bottom: 1px solid var(--rule); }

.seeit { font-weight: 700; }

/* ---------------------------------------------------------------
   INLINE EDITING IN THE RAIL. The values are read here, so they are changed
   here — "the chnage these button shouldnt open a full form like it does now,
   it should be editing inline."

   THE NO-SCRIPT STATE IS THE EDITABLE ONE, deliberately. Without the script
   nothing adds .ro, so the controls are visible and Save works: the fallback is
   a usable form rather than a panel whose only button does nothing. The script
   adds .ro on load to get the tidy read-only view, which is an improvement to
   degrade from, not a requirement to function.
   --------------------------------------------------------------- */
#whose-move .v-edit { width: 100%; height: 28px; padding: 3px 6px; font-size: 13px; }
#whose-move.ro .v-edit { display: none; }
#whose-move .v-read { display: none; }
#whose-move.ro .v-read { display: inline; }
#whose-move .editrow { display: flex; gap: 6px; flex-wrap: wrap; }
#whose-move.ro .editsave, #whose-move.ro .editoff { display: none; }
#whose-move:not(.ro) .editon { display: none; }

/* Report a bug, on every page, out of the way until wanted. Bottom-right and
   quiet: it must be findable at the moment something breaks without competing
   with the work on the page the rest of the time. */
/* ---- IT LIVES IN THE TOP BAR, NOT FLOATING OVER THE PAGE ----
   It was `position: fixed; right: 14px; bottom: 12px`, and a fixed box at the
   bottom-right corner of the viewport covers whatever happens to be scrolled
   under it. Measured: at 1280x900 it sat on top of the RELATED card's subtitle;
   at 390px it covered the sheet cells. A control for reporting that something on
   the page is wrong, permanently hiding part of the page, is a joke this product
   cannot afford twice.

   THE BAR IS WHERE IT BELONGS ANYWAY. It has to be on every screen and always
   reachable, which is the definition of the thing the sticky bar already is —
   and unlike the corner, the bar has its own row and occludes nothing. It also
   puts it next to the identity menu, where "things about me and this session"
   already live.

   z-index BELOW THE BAR'S OWN, because it is now inside it and no longer needs
   to clear the page; the popover gets the stacking, not the trigger. */
.bugbtn { position: relative; margin: 0; flex: none; }
.bugbtn > details > summary { cursor: pointer; list-style: none;
  font: 600 12px/1 var(--ui); letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-3); background: var(--panel); border: 1px solid var(--rule);
  border-radius: 50%; padding: 7px 13px; box-shadow: 0 2px 10px rgba(0,0,0,.10); }
.bugbtn > details > summary::-webkit-details-marker { display: none; }
.bugbtn > details > summary:hover { color: var(--ink); border-color: var(--ink-3); }
.bugbox { width: min(360px, calc(100vw - 32px)); margin-top: 8px; padding: 11px 12px;
  background: var(--panel); border: 1px solid var(--rule); border-radius: 8px;
  box-shadow: 0 8px 28px rgba(0,0,0,.18); display: grid; gap: 7px; }
.bugbox label { font: 600 10.5px/1 var(--ui); letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-3); }
.bugbox textarea { width: 100%; font: 400 13px/1.5 var(--ui); padding: 6px 8px; }
@media print { .bugbtn { display: none; } }

/* ---------------------------------------------------------------
   SUB-ITEMS IN THE LIST — indented under the row they belong to, and foldable.
   "the sub items should be displayed on the list as items, maybe as sub items
   [colapsable and indented]". 4:54a-f were six rows of the same weight as
   everything else, next to their umbrella by luck of the sheet order.

   INDENT IS ON THE REF CELL ONLY, so every other column stays in its grid
   track. Indenting the row itself would shift five columns out of alignment
   with every other row on the page to express one fact about the first.
   --------------------------------------------------------------- */
.row.r-kid .r-ref { padding-left: 18px; position: relative; }
.row.r-kid .r-ref::before { content: "└"; position: absolute; left: 5px;
  color: var(--ink-3); font-size: 12px; }
.row.r-kid { background: color-mix(in srgb, var(--panel-2) 45%, transparent); }
.r-fold { border: 0; background: none; padding: 0 4px 0 0; margin: 0;
  color: var(--ink-3); font-size: 10.5px; cursor: pointer; line-height: 1; }
.r-fold:hover { color: var(--ink); }
.row.r-parent.folded .r-fold { transform: rotate(-90deg); display: inline-block; }
.row.r-kid.hidden { display: none; }

/* The agent's full report, folded under the answer it sent the reporter. One
   tick is one entry; the detail is a click, not a second speaker. */
.agentmore > summary { cursor: pointer; font-size: 13px; color: var(--ink-3);
                       margin-top: 6px; }
.agentmore > summary:hover { color: var(--ink); }
.agentmore[open] > summary { margin-bottom: 5px; }
.agentmore .msg-body { border-left: 2px solid var(--rule-2); padding-left: 9px;
                       font-size: 13px; white-space: pre-wrap; }

/* The project name in the bar navigates now, so it must not look like plain
   text — but it is a location marker, not a call to action, so it keeps its
   weight and only answers on hover. */
.topbar a.proj-here { text-decoration: none; color: inherit; }
.topbar a.proj-here:hover { text-decoration: underline; }

/* A mock-up, shown where it was asked for. The frame is deliberately tall
   enough to judge a layout without opening it, and scrolls its own content. */
.mockwrap { border: 1px solid var(--rule); border-radius: 4px; overflow: hidden;
            margin: 0 0 10px; background: var(--panel); }
.mock-h { display: flex; justify-content: space-between; align-items: baseline;
          gap: 10px; padding: 7px 10px; border-bottom: 1px solid var(--rule);
          font-size: 13px; }
.mock-h a { font-size: 13px; }
.mockframe { display: block; width: 100%; height: 460px; border: 0; background: #fff; }
@media (max-width: 900px) { .mockframe { height: 320px; } }

/* A screenshot somebody sent to an agent. Capped rather than full-bleed: it is
   evidence beside a sentence, not the subject of the page, and a 3000px screen
   grab rendered at its own width pushes the thread off the bottom. The whole
   thing is a link, because the capped version is not readable and "click it to
   see it properly" must not be a thing you have to know. */
.shot { display: inline-block; margin: 7px 8px 0 0; max-width: 260px; text-decoration: none; }
.shot img { display: block; width: 100%; height: auto; max-height: 200px; object-fit: cover;
            border: 1px solid var(--rule-2); border-radius: 8px; background: var(--panel-2); }
.shot .note { display: block; margin-top: 3px; font-size: 12px; word-break: break-word; }
.shots { display: flex; flex-wrap: wrap; gap: 0 8px; }

/* What a conversation concluded should be built, waiting on a person to send or
   set aside. Tinted and ruled off from the thread above it because it is a
   DECISION rather than another turn — the one thing in that column that does
   something when you press it. Its controls are ordinary .field rows, so they
   keep the uniform width and height every other form on the page has. */
.propbox { padding: 10px 11px; border-bottom: 1px solid var(--rule-2);
           background: var(--panel-2); }
.propbox:last-of-type { border-bottom: 0; }
.propbox .who { margin: 0 0 7px; font-size: 13px; color: var(--ink-3); }
.propbox .who .mono { margin-left: 6px; }

/* The colour is set once, further down — this said --done, which is the colour
   of something finished, for a marker whose whole job is "happening now". */
.sec-h .live { font: 600 10.5px/1 var(--ui); letter-spacing: .08em; text-transform: uppercase;
               display: inline-flex; align-items: center; gap: 5px; }
.sec-h .live .dot { width: 6px; height: 6px; border-radius: 50%;
                    animation: pl-pulse 1.6s ease-in-out infinite; }
@keyframes pl-pulse { 0%,100% { opacity: 1 } 50% { opacity: .25 } }
@media (prefers-reduced-motion: reduce) { .sec-h .live .dot { animation: none } }

/* ===================================================================
   ============  THE CONSOLE REDESIGN — 1. THE SHELL  ================
   ===================================================================

   Every screen is the same four parts in the same order: top bar, sidebar,
   page header, then a main column with an optional 316px rail. That
   uniformity IS the redesign — the console this replaces laid out each page
   differently, so the plan's title sat above its controls, the report's below
   a card, and the item page had none at all. A reader had to re-learn where
   things were on every screen.

   Everything below is written against the tokens in mockup/console.html, not
   against the literal hexes in §8 of the handoff, so dark mode keeps working.
   The two lists agree; the token is the one that can be changed once.
   =================================================================== */

/* ---------------- TOP BAR ---------------- */
/* Sticky, and its inner wrapper takes the SAME cap as the content, so the
   wordmark lines up with the first card rather than floating 200px to its left
   on a wide monitor. 56px tall, 44px where the screen is short (see §6).

   ⚠ `.topbar` IS DECLARED IN mockup/console.html TOO, and build-css.mjs
   concatenates that file BEFORE this one. Its rule is
   `@media (max-width: 900px) { .topbar { position: static } }` — for the
   mockup's own bar, which is a different component that happens to share the
   name. It loses today only because the declaration below is unconditional and
   later, and equal specificity is settled by order. Put `position: sticky`
   inside a media query here and that rule can start winning on every tablet and
   phone, which is fault 1 all over again with no line in this file to blame.
   Same failure as `.shell` / `.appshell` above: two components, one name. */
/* THE BAR'S HEIGHT IS A VARIABLE BECAUSE FOUR OTHER RULES DEPEND ON IT.
   It was written as the literal 56 in the bar, 72 on the sidebar's sticky top,
   and 56 twice more in the slide-over's `inset` — so the day the bar is a
   different height on a phone, three of the four are quietly wrong and nothing
   says so. The sidebar hangs 16px below the bar; that is the only number here
   that is a choice. */
/* THE FOLDED RAIL'S WIDTH IS ONE NUMBER, FOR THE SAME REASON THE BAR'S HEIGHT
   IS. Two different things produce the narrow nav — the person folding it, and a
   viewport between 761 and 1100px — and they must produce the SAME rail. It was
   written as the literal 56 in both, and the two are 400 lines apart. Whatever
   the rail is, it is this. */
:root { --topbar-h: 56px; --sidebar-top: calc(var(--topbar-h) + 16px); --rail: 132px; }

.topbar {
  display: block; padding: 0; background: var(--panel);
  border-bottom: 1px solid var(--rule); position: sticky; top: 0; z-index: 20;
}
.topbar-in {
  max-width: var(--content-max, 1560px); margin: 0 auto;
  padding: 0 24px; height: var(--topbar-h);
  display: flex; align-items: center; gap: 14px;
}
.wordmark {
  font: 600 12px/1 var(--ui); letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-3); padding: 0; border-right: 0; white-space: nowrap;
}
.wordmark:hover { color: var(--ink); }
/* A 1x20 rule between the wordmark and the project. It is a separator, not a
   border on either neighbour — hung on one of them it would move whenever that
   element's padding changed. */
.tb-div { width: 1px; height: 20px; background: var(--rule); flex: none; }
.topbar .proj-here {
  display: inline-flex; align-items: baseline; gap: 9px;
  font: 600 16px/1 var(--ui); letter-spacing: -.01em; min-width: 0;
}
.topbar .proj-here .cnt { font: 500 12px/1 var(--ui); color: var(--ink-3); }
.topbar .spacer { margin-left: auto; }
.topbar .userbtn { border-color: var(--rule); font-size: 14px; color: var(--ink-3); }

/* THE WIDTH, SHOWN AND NOT SWITCHED (§2). The three-way control in the
   prototype is a review affordance so a reviewer can see all three; shipping it
   would put a second writer of a stored preference in the one strip of the page
   where nothing should be editable. This states the current value and links to
   the place it is set. "not set — wide" is a full sentence on purpose. */
.widthnote {
  font: 500 12px/1 var(--ui); color: var(--ink-3); text-decoration: none;
  padding: 4px 7px; border-radius: 4px; white-space: nowrap;
}
.widthnote:hover { color: var(--accent); background: var(--accent-soft); }
@media (max-width: 900px) { .widthnote { display: none; } }

/* The slide-over button. Shown below 760px OR on a short landscape viewport —
   see .navopen at the foot for both halves of that test. */
.navbtn { display: none; }

/* ---------------- THE SHELL: SIDEBAR + BODY ---------------- */
/* .appshell, NOT .shell. The mockup already declares `.shell` for its own
   screen-switcher bar — `background: var(--ink); color: #fff` — and both
   stylesheets end up in app.css, so a container named .shell inherited a black
   background and WHITE TEXT. Every plan row's title then rendered white on a
   white card: the rows were there, the refs and pills were there because they
   set their own colour, and the titles were simply invisible. Caught by
   screenshotting the page rather than by reading the diff, which would not have
   shown it. Two rules for one class name is two components sharing one name. */
.appshell {
  max-width: var(--content-max, 1560px); margin: 0 auto; padding: 0 24px;
  display: grid; grid-template-columns: 212px minmax(0, 1fr); gap: 24px;
  align-items: start;
}
.appshell.noside { display: block; }
.shellbody { min-width: 0; padding-bottom: 40px; }

/* .wrap PREDATES THE SHELL AND IS INSIDE IT ON EVERY PAGE.
   It used to be the width cap. Now the shell is, so a .wrap nested in the shell
   must stop capping or the content would be capped twice — 1560px of shell
   holding an 1180px column, with the difference showing as empty space nobody
   asked for. Outside the shell (the picker, sign-in, the refusal pages) it
   still carries the cap, which is why the rule is scoped rather than deleted. */
.wrap { max-width: var(--content-max, 1560px); margin: 0 auto; padding: 0 24px; }
.appshell .wrap { max-width: none; margin: 0; padding: 0; }

/* IT IS A SURFACE, NOT LOOSE WORDS ON THE PAGE (report 12: "in general its not
   so visible"). The nav was grey text sitting directly on the page background
   with nothing behind it, beside a list that IS a white card — so the one
   permanent thing on the screen read as the least present. It now uses the same
   panel and the same hairline as every card in the product, which costs no room
   and makes the fold look like a decision rather than a squeeze.
   The slide-over below overrides this back to a full-height drawer. */
.sidebar {
  padding: 10px 8px; position: sticky; top: var(--sidebar-top);
  display: grid; gap: 14px; align-content: start;
  background: var(--panel); border: 1px solid var(--rule); border-radius: 8px;
}
.navgroup { display: grid; gap: 2px; }
.navitem {
  display: flex; justify-content: space-between; align-items: center; gap: 8px;
  padding: 8px 12px; border-radius: 4px; font-size: 14px;
  color: var(--ink-2); text-decoration: none;
}
.navitem:hover { background: var(--rule-3); color: var(--ink); }
.navitem[aria-current="page"] {
  background: var(--accent-soft); color: var(--accent); font-weight: 500;
}
.nv-n { font-size: 12px; opacity: .7; font-variant-numeric: tabular-nums; }
.navscrim { display: none; }

/* ---------------- THE PERSON'S OWN FOLD (0082) ----------------
   The control, and then the state it writes.

   IT IS THE LAST THING IN THE NAV, not the first. A fold control above the
   destinations would be the first thing the eye lands on in the one component
   whose whole job is to get you somewhere; underneath, it is found when looked
   for and ignored otherwise. */
/* A CONTROL THAT WOULD DO NOTHING IS NOT SHOWN. Below 1101px the nav is already
   a rail or already a slide-over, decided by the viewport — so there is nothing
   for a person's fold to change, and a button that visibly does nothing when
   pressed is the one interaction that cannot be told apart from a broken page.
   It comes back the moment the window is wide enough for it to mean something,
   and the stored preference is untouched throughout: it is still what the person
   said, it is just not what is deciding the layout at this size. */
.navfoldform { display: none; }

@media (min-width: 1101px) {
  .navfoldform { display: block; margin-top: 6px; }
  /* IT LOOKS LIKE A BUTTON NOW. It was borderless, background-less, 12px grey —
     in the folded rail it rendered as a lone "»" floating under the last
     destination, which is the same complaint: a control nobody can see is a
     control nobody presses. A hairline and a surface cost nothing and say
     "this is pressable" without shouting over the destinations above it. */
  .nv-fold {
    display: flex; align-items: center; justify-content: center; gap: 7px;
    width: 100%; padding: 7px 10px;
    border: 1px solid var(--rule); border-radius: 4px; background: var(--panel-2);
    font: inherit; font-size: 13px; color: var(--ink-3); cursor: pointer;
    white-space: nowrap;
  }
  .nv-fold:hover { background: var(--rule-3); color: var(--ink); border-color: var(--ink-3); }
  .nv-fi { font: 400 13px/1 var(--ui); }

  /* ============ THE WORDS SURVIVE THE FOLD (report 12) ============
     The owner, on the folded nav: "it should be clear, show the full words ...
     if its folded its not nice." What he was shown was DISAGR…, QUESTI…,
     WEEKLY…, NOTIFI…, SETTIN… — five of seven destinations named by their first
     six letters and an ellipsis.

     THE CAUSE WAS A 56px TRACK HOLDING UPPERCASE MONOSPACE. That treatment is
     the WIDEST way to set a word — caps are wider than lower case, a monospace
     face gives every letter the width of an 'm', and .04em of tracking is added
     on top — so "DISAGREEMENTS" wanted about 92px in a box with 48px of room.
     The clip was working exactly as written; what was wrong was asking a rail
     that narrow to hold thirteen characters at all.

     SO THE RAIL IS AS WIDE AS ITS LONGEST WORD, and the word is set in the
     ordinary interface face at 11px instead of caps-in-monospace. --rail is
     132px: "Disagreements" measures about 94px there and has 104px of room
     once the panel's padding and the item's are taken out — ten px of slack,
     because the interface face is whatever the machine has and Segoe sets wider
     than SF. Folding still gives the page back 80px, which is what folding is
     for; it just stops buying that back with words nobody can read.

     IT WRAPS RATHER THAN CLIPPING, and that is the part that matters in a year.
     A clip is a silent failure — the label LOOKS deliberate, and the only way to
     find out a destination is called something else is to widen the window. A
     label too long for the rail now runs onto a second line: the rail gets
     taller, which anyone can see, and no word is ever hidden. Height is the
     axis with room to give here; width is not.

     `hyphens: auto` IS THE FAILURE MODE, NOT THE FEATURE. Every label the
     product has today fits on one line. If a longer one arrives, or a machine
     sets the face wider than this was measured on, the browser breaks the word
     with a hyphen and both halves stay readable instead of snapping it
     mid-letter. It needs the `lang` on <html>, which html.ts writes.

     .navfold IS THE PERSON SAYING SO; the media query at 1100px below is the
     VIEWPORT saying so. They produce the same rail from the same
     declarations, and they are still deliberately not one selector list: that
     one exists only between 761 and 1100px, this one only above 1100px, and
     merging them would mean one width bound governing two different facts. The
     day the rail's proportions change, both have to change — which is why they
     sit next to each other in this file and say so. */
  .appshell.navfold { grid-template-columns: var(--rail) minmax(0, 1fr); gap: 16px; }
  .appshell.navfold .navitem {
    flex-direction: column; gap: 3px; padding: 7px 5px; text-align: center;
    min-width: 0;
  }
  .appshell.navfold .nv-l {
    display: block; max-width: 100%;
    font: 600 12px/1.3 var(--ui); letter-spacing: .005em;
    /* WRAPS, NEVER CLIPS — see THE WORDS SURVIVE THE FOLD above. */
    white-space: normal; overflow-wrap: anywhere; hyphens: auto;
  }
  /* THE COUNTS SURVIVE THE FOLD. They are the reason the sidebar was chosen over
     a tab row; a fold that dropped them would give up the thing being paid for
     and leave a rail of words a tab row could have held. */
  .appshell.navfold .nv-n { font-size: 11px; opacity: 1; color: var(--ink-3); }
  /* THE BUTTON KEEPS ITS WORD WHEN FOLDED. It used to drop to the chevron alone,
     which left the one control that undoes the fold as the least legible thing
     on the screen — and it was hidden by the same reasoning that clipped the
     labels. The rail has room for "Unfold"; html.ts writes whichever word
     names what pressing it will do. */
  .appshell.navfold .nv-fold { padding: 7px 6px; }
}

/* WIDTH ALONE IS THE WRONG QUESTION, AND THIS IS WHERE IT WAS ASKED.
   A 1180px tablet in landscape can afford a sidebar; an 844px phone in
   landscape cannot, and both are "landscape". What separates them is not width
   — it is that the phone is 390px TALL. So every nav breakpoint below is a
   pair: how wide, AND how much vertical room there is to spend on chrome.

     SHORT  =  (max-height: 600px) and (max-width: 1100px)   -> the hamburger

   600px sits in a real gap in the hardware: every phone in landscape is at most
   ~430px tall (iPhone 15 Pro Max is 932x430, the SE 667x375), and every tablet
   in landscape is at least ~744px (iPad mini 1133x744). The 1100px width bound
   keeps a short DESKTOP window — a laptop with the dev tools open — out of it,
   because there the sidebar costs 212px of 1600 and nothing is scarce.

   Below 1100px AND tall enough, the sidebar still folds to the rail. THE COUNTS
   SURVIVE THE FOLD — they are the reason the sidebar was chosen over tabs, so
   dropping them at the first breakpoint would give up the thing being paid for.

   AND SO DO THE WORDS, on the same terms as the person's own fold above: one
   --rail, the ordinary face rather than uppercase monospace, and a label that
   wraps rather than one that is cut off. This block is the OTHER half of the
   pair the comment up there describes, and the two were 400 lines apart with the
   number 56 written into each. Read that comment before changing this one. */
@media (max-width: 1100px) and (min-width: 761px) and (min-height: 601px) {
  .appshell { grid-template-columns: var(--rail) minmax(0, 1fr); gap: 16px; }
  .navitem { flex-direction: column; gap: 3px; padding: 7px 5px; text-align: center; }
  .nv-l { font: 600 12px/1.3 var(--ui); letter-spacing: .005em; max-width: 100%;
          white-space: normal; overflow-wrap: anywhere; hyphens: auto; }
  .nv-n { font-size: 11px; opacity: 1; color: var(--ink-3); }
}

/* Below 760px — OR on any short landscape viewport, which is what a phone
   turned sideways is — it is a slide-over behind the button in the top bar.
   Driven by a checkbox so open/closed, the keyboard binding and the focus ring
   come from the browser and the nav still works with scripting off — which
   matters more here than anywhere, because with no nav there is no way off this
   screen.

   THE COMMA IS AN "OR". The second half is the phone-landscape case: at 844x390
   the old rule left a 56px icon rail standing, which is the orientation with the
   least room to give and the one where the owner asked for the hamburger.

   IT HANGS OFF THE BAR, so it must be reachable whenever the bar is — which is
   the whole reason fault 1 had to be fixed first. A slide-over anchored under a
   header that had scrolled away is a nav you can only reach by scrolling back to
   the top, and "you can't navigate" is exactly how that was reported. */
@media (max-width: 760px), (max-height: 600px) and (max-width: 1100px) {
  .navbtn {
    display: inline-grid; place-items: center; width: 32px; height: 32px;
    border: 1px solid var(--rule); border-radius: 4px; cursor: pointer;
    color: var(--ink-3); background: var(--panel); font-size: 16px; flex: none;
  }
  .navbtn:hover { color: var(--ink); border-color: var(--ink-3); }
  .appshell { display: block; }
  .sidebar {
    position: fixed; inset: var(--topbar-h) auto 0 0; width: 244px; z-index: 45;
    /* A DRAWER IS NOT A CARD. The column form above is a panel with a hairline
       all the way round and rounded corners; pinned to the edge of the screen
       that reads as a card that has slid half off it. Squared, and only the edge
       facing the page keeps its rule. */
    background: var(--panel); border: 0; border-right: 1px solid var(--rule);
    border-radius: 0;
    padding: 14px 12px; overflow-y: auto;
    transform: translateX(-105%); transition: transform .16s ease;
  }
  .navopen:checked ~ .appshell .sidebar { transform: none; }
  .navopen:checked ~ .appshell .navscrim {
    display: block; position: fixed; inset: var(--topbar-h) 0 0 0; z-index: 44;
    background: rgba(17,24,39,.34);
  }
}
/* The tighter gutters are a NARROW-screen change, not a short-screen one. 844px
   of landscape phone has room for the same 24px the desktop has; taking it away
   would be borrowing from the axis that is not under pressure. */
@media (max-width: 760px) {
  .appshell { padding: 0 14px; }
  .topbar-in { padding: 0 14px; gap: 10px; }
}

/* THE DRAWER HAS TO FIT THE SHORT SCREEN IT OPENED ON. Eight destinations at
   the portrait spacing come to about 372px, and phone landscape leaves 346
   under the bar — so Settings, the last one, was cut in half. It scrolled, but
   a nav whose final item is shaved is the same complaint this whole change is
   about, one size down. The vertical rhythm tightens on the axis that is short;
   the labels and the counts, which are the reason the sidebar exists at all,
   are untouched. */
@media (max-height: 600px) and (max-width: 1100px) {
  .sidebar { padding: 8px 12px; gap: 8px; }
  .sidebar .navgroup { gap: 0; }
  .navitem { padding: 6px 12px; }
}
@media (prefers-reduced-motion: reduce) { .sidebar { transition: none; } }

/* ---------------- PAGE HEADER ---------------- */
/* .pagehead's own padding is set once, in the redesign block at the foot. */
.ph-row { display: flex; align-items: flex-start; gap: 16px; flex-wrap: wrap; }
.ph-t { min-width: 0; flex: 1 1 340px; }
/* 24px, WHICH REINSTATES A BUMP THIS COMMENT ONCE REVERTED — so here is what it
   said and why it no longer holds. It read: "the 24px bump is reverted; I raised
   this on the theory that the page title had to clear the section heads, and
   raising it while the section heads were left at 13px was papering over the
   actual defect one level down." That was right, and the defect one level down
   was then fixed: the section heads went to 15px, and the ladder it named was
   21 / 15 / 14.5.

   Body is 13px now, not 14.5, so that ladder no longer exists to preserve. 24 is
   the page-title step of the seven and 21 is not a step at all; keeping it would
   mean an eighth size whose only argument was a comparison to a number that has
   moved. The ladder is 24 / 19 / 15 / 13. */
.pagehead h1 { font: 600 24px/1.25 var(--ui); letter-spacing: -.01em; margin: 0; }
.ph-sub { font-size: 14px; line-height: 1.5; color: var(--ink-3); margin: 5px 0 0;
          max-width: 78ch; }
.ph-a { margin-left: auto; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.backnav { padding: 13px 0 0; }
.pagehead + * { margin-top: 18px; }

/* ---------------- MAIN + RAIL ---------------- */
/* 316px, and it COLLAPSES rather than shrinking. A rail narrower than about
   280px cannot hold a label column and a value on one line, and the report page
   spent a year rendering "What the next pass has to fix" as "What the next pass
   h" in a squeezed one. */
.cols {
  display: grid;
  /* THE RAIL WAS A HARD 316px WHILE THE MAIN COLUMN SHRANK, and between 901 and
     1024px that is the ugly band: measured at 901 the split is 441/316, so the
     rail — six of whose eight rows read "not set" — takes 42% of the content
     width from the column carrying the actual record. A percentage with a
     ceiling keeps the desktop exactly as it was (at 1280 the 32% resolves above
     316 and clamps to it, unchanged) and gives the width back where it is
     scarce. The floor stops it collapsing into an unreadable strip just before
     the 900px breakpoint drops it under the main column anyway. */
  grid-template-columns: minmax(0, 1fr) clamp(248px, 32%, 316px);
  gap: 24px; align-items: start; padding: 18px 0 0;
}
.cols > .colmain, .cols > .colrail { min-width: 0; display: grid; gap: 18px;
                                     align-content: start; }
.colrail > .card { margin: 0; }
/* ---- ONE COLUMN, AND THE ANSWER DOES NOT GO TO THE BOTTOM OF IT ----
   Measured on a phone, before this: on /p/erp8/item/4:1 at 390x844 the document
   is 5131px and "Whose move" — the derived one-line answer to the question
   people open the page to ask — started at y=4577. Five and a half screens of
   scrolling, past a description, an empty work section, an empty notes section
   and ten question cards, to read 137px of sentence. Landscape is the same: this
   breakpoint is width-only, so every phone orientation buried it.

   `display: contents` ON THE RAIL, so its cards become items of `.cols` itself
   and `order` can reach ONE of them. Hoisting the whole rail instead would be
   worse than the bug: it is 454px of "What is set on this row", six of whose
   eight rows read "not set", and on an item whose description is the only real
   content that would open the page on a list of blanks.

   SAFE BECAUSE THE PANE IS ALREADY NOTHING HERE. `.cols > .colrail > .pane` is
   stripped to a bare grid container further up this file and every `.sec` inside
   it carries its own background, border and radius — so dissolving the wrapper
   loses no box. The gap comes from `.cols`, which is why it drops to 18px to
   match the one the panes were providing.

   NOT DUPLICATED INTO A SECOND SLOT. One card, one computation, moved by CSS.
   Rendering it twice and hiding one would be two copies of a derived fact, which
   is the thing this page argues against in four separate comments. */
@media (max-width: 900px) {
  .cols { grid-template-columns: minmax(0, 1fr); gap: 18px; }
  /* The rail itself dissolves here; its .pane has to dissolve too, and that
     override CANNOT live in this block — see the note beside
     `.cols > .colmain > .pane` further down, which sets `display: grid` at the
     same specificity and later in the file, so it wins. Split on purpose, and
     each half says where the other is. */
  .cols > .colrail { display: contents; }
}

/* ---------------- CARD ---------------- */
/* .pane is the app's existing card and is restyled rather than replaced, so
   every page that already uses one picks up the redesign without being edited.
   .card is the same thing under the name §1 uses. */
.pane, .card {
  background: var(--panel); border: 1px solid var(--rule); border-radius: 8px;
  overflow: hidden; box-shadow: none;
}
.card-h {
  padding: 11px 18px; background: var(--panel-2);
  border-bottom: 1px solid var(--rule-2);
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
}
/* A CARD'S TITLE, AT HEADING RANK. See the .sec-h note above for the argument;
   this is the same change on the other header component. These are the 29 the
   owner was looking at — every one of them was 10px mono uppercase --ink-hd,
   which is to say drawn exactly like a field label and two sizes under the card
   body it introduces. --ink-hd stays the token for a header, it is just no
   longer the quietest thing on the card. */
.card-h h2, .card-h .ch-t {
  font: 650 16px/1.3 var(--ui); letter-spacing: -.005em; text-transform: none;
  color: var(--ink); margin: 0;
}
/* The meta beside a card title is an aside — quieter, and pushed right so the
   title's left edge is the same on every card in the column. */
.card-h .ch-m { font: 400 12px/1.4 var(--ui); color: var(--ink-3); margin-left: auto;
                text-align: right; }
.card-h .note { flex-basis: 100%; margin: 2px 0 0; }
.card-b { padding: 18px; }
.card-b.tight { padding: 0; }

/* ---------------- PILL ---------------- */
/* radius 5, 500 11.5px, and each tone is {fg, bg, border} — the border is the
   part the old palette had no name for, so components drew pills with no edge
   and they dissolved into a tinted row. */
.pill {
  font: 500 13px/1.7 var(--ui); padding: 2px 8px; border-radius: 4px;
  letter-spacing: 0; white-space: nowrap; text-transform: none;
  border: 1px solid transparent; display: inline-block;
}
.pill.wait   { color: var(--wait);   background: var(--wait-bg);   border-color: var(--wait-bd); }
.pill.active { color: var(--active); background: var(--active-bg); border-color: var(--active-bd); }
.pill.done   { color: var(--done);   background: var(--done-bg);   border-color: var(--done-bd); }
.pill.idle   { color: var(--idle);   background: var(--idle-bg);   border-color: var(--idle-bd); }
.pill.clash  { color: var(--clash);  background: var(--clash-bg);  border-color: var(--active-bd); }
/* A small tag — the check's name, a link's kind, a run tag. 4px, mono, and
   never the same shape as a pill, because one is a state and one is a label. */
.tag {
  font: 600 10.5px/1.5 var(--ui); letter-spacing: .06em; text-transform: uppercase;
  border-radius: 4px; padding: 2px 7px; border: 1px solid var(--rule);
  color: var(--ink-3); background: var(--panel); display: inline-block;
}
.tag.active { color: var(--active); background: var(--active-bg); border-color: var(--active-bd); }
.tag.done   { color: var(--done);   background: var(--done-bg);   border-color: var(--done-bd); }
.tag.wait   { color: var(--wait);   background: var(--wait-bg);   border-color: var(--wait-bd); }

/* ---------------- EMPTY IS A VALUE, AND IT SAYS SO ---------------- */
/* §9.1. "blank" for a cell, "blank on the sheet" for a prose field, "not set"
   for anything a person has not decided. Never an em dash and never nothing:
   the reader has to be able to tell "the sheet says nothing" from "we did not
   read it", and an empty <td> says neither. */
.blank { color: var(--ink-empty); }
.blank.prose { font-style: italic; }

/* ---------------- BUTTONS AND FIELDS (§8) ---------------- */
.btn {
  font: 500 14px/1.2 var(--ui); padding: 9px 17px; border-radius: 4px;
  background: var(--accent); color: #fff; border: 1px solid transparent;
  cursor: pointer;
}
.btn.ghost, .btn.secondary {
  background: var(--panel); color: var(--ink-2); border: 1px solid var(--rule);
  padding: 8px 13px; font-size: 14px;
}
.btn.ghost:hover, .btn.secondary:hover { border-color: var(--ink-3); background: var(--panel-2); }
.btn.danger {
  background: var(--panel); color: var(--wait); border: 1px solid var(--wait-bd);
  padding: 8px 13px; font-size: 14px;
}
.btn.danger:hover { background: var(--wait-bg); }
/* THE ONE PLACE A FORM CONTROL IS SIZED. Everything here was declared twice,
   once here and once beside .field's grid above, and only these values ever
   rendered; the other set has been retired.

   HEIGHT, NOT MIN-HEIGHT, for single-line controls. A select and a text input
   round their intrinsic height differently and came out 36 and 37 — one pixel,
   invisible alone, and the reason a row of four never quite sat straight. A
   textarea is the exception below and has to be, which is why the height sits
   on the group and is narrowed rather than declared three times. */
.field input, .field select, .field textarea {
  border-radius: 4px; padding: 10px 12px; height: 40px; border-color: var(--rule);
  background: var(--panel-2); font-size: 14px;
}
.field textarea { height: auto; min-height: 88px; }
/* THE RANK RULE: a section header must outrank the labels inside it on at least
   two of {family, weight, size}. .sec-h is UI/650/15 and this is mono/600/11, so
   it differs on all three and structure beats content without a reader having to
   parse the words to work out which is which. 600 rather than 500 because at
   11px uppercase mono, 500 is the weight at which a label stops reading as a
   label and starts reading as more small text. */
.field > label {
  font: 600 12px/1.3 var(--ui); letter-spacing: .09em; text-transform: uppercase;
  color: var(--ink-3);
}

/* ---------------- BODY TYPE ---------------- */
/* 13/1.55, DOWN FROM 14.5, AND THE OLD REASON IS WORTH KEEPING. It said 14.5
   because "the old 14/1.45 was chosen for a 1180px column and reads cramped at
   1560" — a fair reading of a page of prose, and the wrong test for this one.

   Almost nothing here inherits it. Every component that carries real text sets
   its own size, so what body reaches is the leftovers — and at 14.5 those
   rendered LARGER than the body text the components choose deliberately, which
   is why odd paragraphs looked oversized next to the thing they described. At 13
   the base agrees with the components instead of contradicting them, and the
   line-height stays at 1.55 because that part of the old argument still stands. */
body { font: 400 14px/1.55 var(--ui); font-variant-numeric: tabular-nums; }
body, body * { font-variant-numeric: tabular-nums !important; }

/* ---------------- THE AMBER STRIP (§4.2a) ---------------- */
/* Used for a disagreement, on the plan and at the head of an item. One
   component, because two things that say the same sentence must not look like
   two different kinds of warning. */
.strip {
  display: block; text-decoration: none; color: inherit;
  background: var(--active-bg); border: 1px solid var(--active-bd);
  border-radius: 8px; padding: 14px 18px; margin: 18px 0 0;
}
a.strip:hover { border-color: var(--active); }
.strip .st-t { font-size: 14px; color: var(--ink); }
.strip .st-t b { font-weight: 600; }
.strip .st-w { font-size: 14px; color: var(--active); margin: 3px 0 0; }
.strip .st-go { color: var(--accent); font-size: 14px; margin-left: auto; white-space: nowrap; }
.strip .st-row { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; }

/* ---------------- SEGMENTED CONTROL AND FILTER CHIPS ---------------- */
.seg { display: inline-flex; background: var(--rule-2); border-radius: 8px; padding: 2px; }
.seg a, .seg button {
  font: 400 14px/1.3 var(--ui); padding: 5px 12px; border-radius: 4px;
  border: 0; background: none; color: var(--ink-3); text-decoration: none;
  cursor: pointer; white-space: nowrap;
}
.seg a[aria-current="page"], .seg button[aria-pressed="true"] {
  background: var(--panel); color: var(--ink); font-weight: 500;
  box-shadow: var(--shadow);
}
.seg .sg-n { font: 400 13px/1 var(--ui); color: var(--ink-3); margin-left: 5px; }

.chip {
  display: inline-flex; align-items: baseline; gap: 6px;
  border: 1px solid var(--rule); border-radius: 4px; padding: 5px 11px;
  font-size: 14px; color: var(--ink-2); text-decoration: none; background: var(--panel);
  white-space: nowrap;
}
.chip:hover { border-color: var(--ink-3); }
.chip .ch-n { font: 400 13px/1 var(--ui); color: var(--ink-3); }
.chip[aria-current="page"], .chip.on {
  border-color: var(--accent); background: var(--accent-soft);
  color: var(--accent); font-weight: 500;
}
.chip[aria-current="page"] .ch-n, .chip.on .ch-n { color: var(--accent); }
/* A CHIP WHOSE COUNT IS ZERO STAYS VISIBLE AND STAYS CLICKABLE.
   Hiding an empty filter is the change that always suggests itself and it
   destroys the one thing the filter row is for: if "Disagrees" vanishes when
   nothing disagrees, the reader cannot tell "none" from "that filter does not
   exist here". Greyed says none. Absent says nothing. */
.chip.empty { border-color: var(--rule-2); background: var(--panel-2); color: var(--ink-3); }
.chip.empty .ch-n { color: var(--ink-3); }

/* The one-line group+filter control. Two groups, a rule between them, and it
   WRAPS rather than scrolling — a horizontal scroller hides filters, and a
   hidden filter is the same failure as a removed one. */
.ctlrow {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 0 0 14px;
}
.ctl-l { font: 500 10.5px/1.4 var(--ui); letter-spacing: .09em; text-transform: uppercase;
         color: var(--ink-3); }
.ctl-div { width: 1px; align-self: stretch; min-height: 26px; background: var(--rule);
           margin: 0 4px; }
.ctl-clear { color: var(--accent); font-size: 14px; text-decoration: none; }
.ctlrow .ctl-find { min-width: 200px; flex: 0 1 260px; margin-left: auto; }
.ctlrow .ctl-find input {
  width: 100%; height: 32px; font: inherit; font-size: 14px; padding: 6px 11px;
  border: 1px solid var(--rule); border-radius: 4px; background: var(--panel-2);
  color: var(--ink);
}

/* ===================================================================
   ============  2. THE PLAN  ========================================
   =================================================================== */

/* THE ROW IS NO LONGER 30px TALL. That height was 6px of air around 13px text
   and it is the single most visible thing the redesign changes: §1 sets a floor
   of 12px of vertical padding and calls the difference deliberate. `height` is
   replaced by `min-height` so a row that wraps grows instead of clipping. */
.row {
  height: auto; min-height: 46px; padding: 0 18px 0 0; gap: 14px;
  border-bottom: 1px solid var(--rule-2);
}
.row .r-ref { font: 400 13px/1.4 var(--ui); color: var(--ink-3); padding: 13px 0; }
.row .r-title { font-size: 14px; padding: 13px 0; }
.row .r-title .flag {
  font: 600 10.5px/1.5 var(--ui); letter-spacing: .06em; text-transform: uppercase;
  color: var(--active); background: var(--active-bg); border: 1px solid var(--active-bd);
  padding: 1px 6px; border-radius: 4px; margin-left: 8px;
}
.row .r-wait { font: 400 13px/1.4 var(--ui); color: var(--ink-3); }
/* "nothing recorded" — a row claiming to be blocked with no question, no job and
   no turn behind it. It is the one value in this column worth catching an eye:
   the others say what is being waited for, this one says the record does not
   know. Same ink as a stripe that wants attention, so the two read as one
   language rather than two decorations. */
.row .r-wait.attn, .row .tip.r-wait.attn { color: var(--wait); font-weight: 500; }
.row:hover { background: var(--panel-2); }
/* A ROW THAT DISAGREES WITH ITSELF IS TINTED, and the tint outranks the hover —
   otherwise moving the mouse down the list makes the one signal that matters
   flicker off under the cursor. */
.row.attn, .row.attn:hover { background: var(--tint-attn); }

/* THE GROUP HEADER IS A <summary>, so open/closed, the keyboard binding and
   aria-expanded come from the browser. The caret is 9px wide open or closed so
   the group names line up down the column either way — a caret that changes
   width shifts every label by two pixels when you fold one block. */
.groupfold > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: 9px;
  padding: 11px 18px; background: var(--panel-2);
  border-bottom: 1px solid var(--rule-2); border-top: 1px solid var(--rule-2);
}
.groupfold > summary::-webkit-details-marker { display: none; }
.groupfold:first-of-type > summary { border-top: 0; }
.groupfold > summary:hover { background: var(--rule-3); }
.groupfold > summary .caret {
  width: 9px; flex: none; font: 400 12px/1 var(--ui); color: var(--ink-3);
  transform: rotate(-90deg); transition: transform .12s;
}
.groupfold[open] > summary .caret { transform: none; }
/* THE GROUP NAME IS A HEADING OVER THE ROWS IT HOLDS, so it takes the same
   treatment as .sec-h and .card-h h2 — and it is the clearest case of the fault,
   because a group header sits DIRECTLY on top of the rows it heads with nothing
   between them, and it was 10px against their 14px. It also carries a value
   from the sheet ("Built", "Decision · on you"), which uppercasing mangles.
   ON .group-head, NOT ON .groupfold > summary. The settled block at the foot of
   the plan is a <details class="pane settled-block">, not a .groupfold, so the
   old selector missed it and its heading fell through to the mockup's 13px/640
   — two group headers on one page drawn two different ways. Keyed to the class
   that actually means "this is a group's header", both are covered. */
.group-head .group-name {
  font: 650 16px/1.3 var(--ui); letter-spacing: -.005em; text-transform: none;
  color: var(--ink);
}
.groupfold > summary .group-n { font: 400 12px/1.4 var(--ui); color: var(--ink-3); }
@media (prefers-reduced-motion: reduce) { .groupfold > summary .caret { transition: none; } }

/* The one-line control row's search box needs a label for screen readers that
   is not on the screen — the placeholder is not a label. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.foot { padding: 14px 0 0; font-size: 14px; color: var(--ink-3); }
.foot b { color: var(--ink-2); font-weight: 600; }

/* ===================================================================
   ============  3. DISAGREEMENTS  ===================================
   =================================================================== */
/* A stack of cards, each with an amber left edge. NO DISMISS CONTROL EXISTS ON
   THIS SCREEN and none should be added: every finding is computed on read, so a
   dismissal would store a fact the check cannot see and the row would go on
   computing while the interface stopped showing it. */
.dstack { display: grid; gap: 12px; padding: 18px 0 0; }
.dcard {
  background: var(--panel); border: 1px solid var(--rule); border-left: 3px solid var(--active);
  border-radius: 8px; padding: 16px 20px;
}
.dc-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.dc-refs { font: 400 13px/1.4 var(--ui); }
.dc-refs a { color: var(--accent); text-decoration: none; }
.dc-refs a:hover { text-decoration: underline; }
.dc-title { font-weight: 600; font-size: 14px; }
.dc-body { color: var(--ink-2); margin: 8px 0 0; max-width: 84ch; }
.dc-act { margin: 12px 0 0; }

/* ---------------- WHAT THE FIRST SCREENSHOT CAUGHT ---------------- */
/* Four faults, none of which a diff would have shown. They are fixed together
   because they were all found in one pass, and recorded because each is a shape
   of mistake this file has made before. */

/* 1. A LINK STYLED AS A BUTTON WAS STILL UNDERLINED. `.btn` is used on <a> in a
      dozen places and inherited the anchor default, so the primary action on
      every page read as underlined text in a blue box. */
a.btn { text-decoration: none; display: inline-block; }
a.btn:hover { text-decoration: none; }

/* 2. THE STATUS PILL WAS CLIPPED AT 132px. "Working on It" and "With MC
      (external build)" are the sheet's own words and it is not our place to
      shorten them, so the column takes the width the vocabulary needs. */
/* SIX COLUMNS, NOT SEVEN — the sheet's status left the row. It was 168px of
   every line repeating either the group heading above it or a second status
   competing with the real one. The width it freed goes to the title, which is
   the thing anybody is actually scanning for. */
.row { grid-template-columns: 8px 96px minmax(0,1fr) 150px 92px 104px; }
@media (max-width: 1180px) { .row { grid-template-columns: 8px 90px minmax(0,1fr) 92px 104px; } }
@media (max-width: 860px)  { .row { grid-template-columns: 8px 80px minmax(0,1fr) 120px; } }

/* 3. A COLUMN OF 139 EM DASHES. lifecycle is NULL on every imported row, so the
      pill rendered as a bordered box containing "—" on every line — a control
      shape carrying no claim, repeated down the page, which reads as data that
      failed to load. It still says nothing rather than nothing being there
      (§9.1), but it says it in the ink reserved for empty values and without the
      box, so the eye passes over it and the pills that DO carry a claim stand
      out. Removing it outright was the other option and is wrong: the column is
      how somebody sees, at a glance, that Plumbline has classified none of this. */
.pill.lifecycle.none {
  border-color: transparent; background: none; color: var(--ink-empty);
  font-weight: 400; opacity: 1;
}

/* 4. THE ROW'S TITLE HAD NO COLOUR OF ITS OWN and inherited whatever the
      container set. That was survivable while nothing set one; it stopped being
      survivable the moment a container did. Named explicitly so the next
      container cannot take it back. */
.row .r-title { color: var(--ink); }
.row .r-title .flag { color: var(--active); }

/* ===================================================================
   ============  4. THE DETAIL SCREENS  ==============================
   ===================================================================

   A `.sec` INSIDE A DETAIL COLUMN IS A CARD. Both detail pages were one tall
   bordered box divided by hairlines, so "the conversation", "from the sheet"
   and "what's next" read as three paragraphs of the same thing. The redesign
   makes each its own card with its own header strip.

   DONE HERE RATHER THAN BY REWRITING THE MARKUP, and that is a decision worth
   recording: there are about thirty .sec blocks across the item page, the report
   page and the settings panes, and rewriting them all would touch files another
   agent is working in right now. The header strip is achieved with a negative
   margin so the existing `.sec` padding still holds its body.
   =================================================================== */
.cols > .colmain > .pane, .cols > .colrail > .pane {
  background: none; border: 0; border-radius: 0; overflow: visible; box-shadow: none;
  display: grid; gap: 18px; align-content: start;
}
/* THE OTHER HALF OF THE NARROW-WIDTH HOIST, AND IT HAS TO BE HERE.
   The @media at the 900px breakpoint above dissolves `.colrail` so one rail card
   can be ordered above the main column. Dissolving the rail is not enough — this
   rule puts `display: grid` back on the .pane INSIDE it, at identical
   specificity (0-3-0) and later in the file, so a `display: contents` written up
   there loses and the card orders within its pane instead of within `.cols`.
   Which is exactly what happened: measured, `.colrail` was `contents`, the pane
   was still `grid`, and `order: -1` moved the card by 178px instead of 4000.
   Kept next to the rule that beat it rather than fixed with a specificity bump,
   because the next person needs to see the pair. */
@media (max-width: 900px) {
  .cols > .colrail > .pane { display: contents; }
  /* The one card that comes up: the derived answer. Everything else in the rail
     keeps order 0 and stays below the main column, in source order. */
  #whose-move-now { order: -1; }
}
.cols .pane > .sec {
  background: var(--panel); border: 1px solid var(--rule); border-radius: 8px;
  padding: 0 18px 18px; margin: 0; overflow: hidden;
}
.cols .pane > .sec:last-child { border-bottom: 1px solid var(--rule); }
.cols .pane > .sec > .sec-h {
  margin: 0 -18px 14px; padding: 11px 18px;
  background: var(--panel-2); border-top: 0; border-bottom: 1px solid var(--rule-2);
  /* NO TYPE HERE. This rule used to restate the font, and because it out-weighs
     the base `.sec-h` 0-3-3 to 0-1-0 it was the only declaration on the page —
     which is how a "make headings heavier" edit landed in the base rule and did
     nothing at all. It now sets the header STRIP (the tinted band, its bleed and
     its rule) and inherits the type from the one place that states it. */
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
}
.cols .pane > .sec > .sec-h .ch-m { margin-left: auto; color: var(--ink-3); font-weight: 400;
                                    font-size: 12px; }
.colrail .pane > .sec { padding: 0 16px 16px; }
/* THE RAIL IS THE EXCEPTION, AND IT IS THE ONLY ONE.
   Everywhere else a card header just went up to heading rank (see .sec-h). The
   rail does not, because it is not a column of sections you read down — it is a
   316px strip of labelled facts, and its headers do the same job as the `dt`
   labels four lines below them. Promoting them would put three ranks of heading
   in a strip narrower than one sentence, and it would make the rail shout over
   the main column it is an aside to. This is the "rail headers" half of the
   reservation: 10px mono uppercase now means micro-label, and here it is one.

   IT HAS A NAME NOW, AND THAT IS THE POINT. This was `.colrail .pane > .sec >
   .sec-h` — an ancestor reaching in — so `.sec-h` meant sans-15 in one column
   and mono-uppercase in the other, with nothing in either rule saying the other
   existed. One class, two values, is the same defect as a restated declaration
   wearing different clothes: the next person to change .sec-h changes one
   column and never learns about the second. The server writes `sec-h rail` on
   the twenty headers in the rail; the look is unchanged and the name is now
   true. Still later in the file than .sec-h, because both are one class and
   source order is what decides. */
.sec-h.rail {
  margin: 0 -16px 12px; padding: 10px 16px;
  font: 600 12px/1.4 var(--ui); letter-spacing: .09em; text-transform: uppercase;
  color: var(--ink-hd);
}

/* The rail's label/value rows. A 104px mono label column, and every value may
   carry a note under it saying what the value MEANS — see §7: "Built" without
   "NOT deployed, and nobody has checked it" is read as its opposite. */
.colrail .set { grid-template-columns: 104px minmax(0,1fr); gap: 9px 12px; }
.colrail .set dt { font: 500 12px/1.5 var(--ui); letter-spacing: .09em;
                   text-transform: uppercase; color: var(--ink-3); }
.colrail .set dd { font-size: 14px; }
.colrail .set dd.empty { color: var(--ink-empty); font-style: normal; }
.dd-note { display: block; font-size: 13px; color: var(--ink-3); line-height: 1.4; margin-top: 2px; }
/* Only visible while the rail is in edit mode; see #whose-move in this file. */
#whose-move.ro .editonly { display: none; }

/* ---------------- FROM THE SHEET (§4.4d) ---------------- */
.sheetprose-stack { display: grid; gap: 14px; }
.sh-l { font: 500 10.5px/1.5 var(--ui); letter-spacing: .09em; text-transform: uppercase;
        color: var(--ink-3); margin-bottom: 3px; }
.sh-v { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; color: var(--ink-2); }
/* Full-bleed grid of the sheet's short columns, hairline-separated. */
.sheetcells {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  margin: 18px -18px -18px; border-top: 1px solid var(--rule-2);
}
.sheetcells .sheetcell {
  padding: 11px 18px; border-right: 1px solid var(--rule-3);
  border-bottom: 1px solid var(--rule-3);
}
.sh-c { font: 400 13px/1.5 var(--ui); overflow-wrap: anywhere; }

/* ---------------- A RETURNED TURN'S ACTIONS (§5) ---------------- */
.turnactions {
  display: grid; gap: 10px; margin-top: 12px; padding-top: 12px;
  border-top: 1px solid rgba(0,0,0,.07); max-width: 640px;
}
.turn-btns { display: flex; gap: 8px; flex-wrap: wrap; }
.turn-why { margin: 0; font-size: 13px; line-height: 1.6; color: var(--ink-3); }
.turn-note { margin: 10px 0 0; font-size: 13px; color: var(--ink-3); max-width: 78ch; }
.turn-settled { margin: 10px 0 0; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
                font-size: 13px; }
.turn-settled .mono { font-size: 13px; color: var(--ink-3); }
.turn-note-in {
  width: 100%; font: inherit; font-size: 14px; padding: 9px 11px;
  border: 1px solid var(--rule); border-radius: 4px; background: var(--panel-2); color: var(--ink);
}
/* ---------------- A LIVE TURN'S TWO WAYS OUT (§LIVE) ---------------- */
/* Two separate forms — call it off, hand it back — laid out as one row of
   controls, because that is what they are to the person reading. The reason box
   belongs to the first one and grows into whatever room is left; both buttons
   sit on the same bottom edge so neither reads as the primary. Wraps to two
   rows on a narrow screen rather than squeezing the input to nothing. */
.live-btns { display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap; }
.live-btns form { display: flex; gap: 8px; align-items: center; margin: 0; }
.live-btns .liveoff { flex: 1 1 320px; min-width: 240px; }
.live-btns .liveoff .turn-note-in { flex: 1 1 auto; width: auto; }
.live-btns .btn { white-space: nowrap; }

/* The compose box sits INSIDE the turn and names what it is replying to, so
   nobody has to remember which of four returns they are looking at. */
.passbox { border: 1px solid var(--rule); border-radius: 8px; background: var(--panel-2);
           padding: 10px 12px; display: grid; gap: 7px; }
.pass-h { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.pass-k { font-size: 12px; color: var(--ink-3); }
.passbox textarea {
  width: 100%; font: inherit; font-size: 14px; padding: 9px 11px; line-height: 1.5;
  border: 1px solid var(--rule); border-radius: 4px; background: var(--panel);
  color: var(--ink); resize: vertical; min-height: 60px;
}

/* ---------------- WHAT'S NEXT (§6) ---------------- */
/* THREE MOVES, AND THE PANELS ARE REVEALED BY :checked ON A SIBLING RADIO.
   No script: a disclosure that needs JavaScript is a control that does not
   exist for anybody whose script failed. With this stylesheet missing, all
   three panels are simply visible, which is a usable page rather than a dead
   one — the fallback is more, not less. */
.wn-r { position: absolute; opacity: 0; pointer-events: none; }
.wn-picks {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px;
}
.wn-card {
  display: grid; gap: 4px; cursor: pointer;
  border: 1px solid var(--rule); border-radius: 8px; padding: 12px 14px; background: var(--panel);
}
.wn-card:hover { border-color: var(--ink-3); }
.wn-l { font-weight: 600; font-size: 14px; }
.wn-d { font-size: 13px; color: var(--ink-3); line-height: 1.45; }
.wn-panels { margin-top: 16px; }
.wn-p { display: none; }
.wn-r:nth-of-type(1):checked ~ .wn-panels > .wn-p:nth-child(1),
.wn-r:nth-of-type(2):checked ~ .wn-panels > .wn-p:nth-child(2),
.wn-r:nth-of-type(3):checked ~ .wn-panels > .wn-p:nth-child(3) { display: block; }
.wn-r:nth-of-type(1):checked ~ .wn-picks > .wn-card:nth-child(1),
.wn-r:nth-of-type(2):checked ~ .wn-picks > .wn-card:nth-child(2),
.wn-r:nth-of-type(3):checked ~ .wn-picks > .wn-card:nth-child(3) {
  border-color: var(--accent); background: var(--accent-tint);
}
.wn-r:focus-visible ~ .wn-picks > .wn-card { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The kind grid. One card per kind, with a run tag saying what it may do. */
.kindgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 10px; }
.kindcard {
  display: grid; gap: 5px; cursor: pointer; position: relative;
  border: 1px solid var(--rule); border-radius: 8px; padding: 12px 14px; background: var(--panel);
}
.kindcard input { position: absolute; opacity: 0; pointer-events: none; }
.kindcard:hover { border-color: var(--ink-3); }
.kindcard:has(input:checked) { border-color: var(--accent); background: var(--accent-tint); }
.kindcard:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.kc-h { display: flex; align-items: baseline; gap: 8px; }
.kc-l { font-weight: 600; font-size: 14px; }
.kc-h .tag { margin-left: auto; }
.kc-d { font-size: 13px; color: var(--ink-3); line-height: 1.45; }
/* SHOWN, NEVER HIDDEN (§9.9). An action this person or this screen cannot offer
   still says what it is and what would grant it — hiding it makes somebody
   guess whether it exists, which is how a grant becomes folklore. */
.kindcard.off { cursor: not-allowed; background: var(--panel-2); border-color: var(--rule-2); }
.kindcard.off .kc-l { color: var(--ink-3); }
.kindcard.off .kc-d { color: var(--ink-3); }

/* THE OPTION CARD IS THE SUBMIT (owner). Each kind is now a submit button that
   posts its own request_type — so the button has to shed the browser's default
   chrome and read as the card it replaces: full width, left-aligned, inherited
   type, and a hover/press that says it is the thing you click to send. */
button.kindcard {
  width: 100%; text-align: left; font: inherit; color: inherit;
  appearance: none; -webkit-appearance: none; margin: 0;
}
button.kindcard:hover { border-color: var(--accent); background: var(--accent-tint); }
button.kindcard:active { background: var(--accent-soft); }
button.kindcard:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.dispatch-lead { font-size: 13px; color: var(--ink-3); }

/* ---------------- THE CONVERSATION (§4.4b) ---------------- */
/* STYLED HERE, NOT REWRITTEN. The markup of this section belongs to the chat
   feature; the redesign only asks that the turns read as an exchange, so the
   existing .turnmsg keeps its element and gains a shape.

   TOKENS, NOT THE HANDOFF'S LITERAL HEXES. §8 gives a person's turn as
   #dbe4f7 on #f4f7fd, which is --accent-soft's family in light and unreadable
   in dark — this app has a dark theme and the handoff does not. Named tokens
   keep both honest. */
/* AN AGENT'S TURN IS ON THE LEFT IN THE AMBER TONE; A PERSON'S IS ON THE RIGHT
   IN THE ACCENT TINT. Which side a turn is on is the fastest possible answer to
   "who said this", and it costs no words.

   `.them` IS THE PERSON, NOT THE AGENT, and the first version of this rule got
   it backwards. The markup is `class="turnmsg${m.from_agent ? "" : " them"}"` —
   so the BARE class is the agent and `.them` is whoever is reading the page.
   Styling by the class name's apparent meaning put every human turn on the
   agent's side, in the agent's colour, and the thread read as though the
   machine had been talking to itself. Caught by screenshotting it. */
.turnmsg {
  border-bottom: 0; border-radius: 8px; padding: 13px 16px; margin-bottom: 10px;
  max-width: 82%; margin-left: 0; margin-right: auto;
  border: 1px solid var(--active-bd); background: var(--active-bg);
}
.turnmsg.them { margin-left: auto; margin-right: 0;
                border-color: var(--accent-soft); background: var(--accent-tint); }
.turnmsg .who { font: 600 13px/1.4 var(--ui); color: var(--ink-hd);
                display: flex; gap: 8px; align-items: baseline; }
.turnmsg .who .mono { margin-left: auto; font-weight: 400; font-size: 12px; color: var(--ink-3); }
.turnmsg .wt-body { margin: 6px 0 0; white-space: pre-wrap; color: var(--ink-2); }
/* Waiting on the agent: a dashed bubble at the foot of the thread, so the
   silence after you press Send is a stated fact rather than an absence. */
.sec-h .live { color: var(--active); }
.sec-h .live .dot { background: var(--active); }

/* ---------------- PICKING AN OPTION (§5) ---------------- */
/* Each option is a submit button, because picking one IS the approval. A radio
   plus a separate Approve is two presses, and the second is the one that stops
   being made — which is how a record ends up saying "the plan stood" with
   nothing saying which plan. */
.optcards { display: grid; gap: 8px; }
/* THE OPTIONS ARE A DECISION, LABELLED AS ONE AT THE TOP (owner FB-2042 +
   review panel). Without a header the cards read as more prose; this line tells
   the reader, before the choices, that clicking one IS the call. */
.optcards-h { margin: 0 0 2px; font: 600 13px/1.4 var(--ui); letter-spacing: .03em;
              text-transform: uppercase; color: var(--accent); }
.optcard {
  display: grid; grid-template-columns: 26px minmax(0,1fr); gap: 10px; align-items: baseline;
  text-align: left; font: inherit; cursor: pointer; width: 100%;
  border: 1px solid var(--rule); border-radius: 8px; padding: 12px 14px;
  background: var(--panel); color: var(--ink);
}
.optcard:hover { border-color: var(--accent); background: var(--accent-tint); }
.optcard:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* SELECT-NOT-COMMIT cards (look-into-it): the card is a <label> holding a radio,
   so clicking selects rather than submits (owner FB-2042). The radio leads the
   row; the whole card highlights when it is the chosen one, and the act happens
   on a button below. */
.optpick .optcard { grid-template-columns: auto 26px minmax(0,1fr); cursor: pointer; }
.optpick .optcard input[type="radio"] { margin: 2px 0 0; accent-color: var(--accent); }
.optpick .optcard:has(input:checked) { border-color: var(--accent); background: var(--accent-tint); }
.optpick .optcard:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }
.oc-k { font-weight: 600; font-size: 14px; color: var(--accent); }
.oc-b { font-size: 14px; line-height: 1.5; }

/* ===================================================================
   ============  5. ONE NAME PER THING  ==============================
   ===================================================================

   The owner, on finding the report page untouched: "this issue is big since
   this means it's not structured design it's pieces". He is right, and the
   fix is not more CSS — it is FEWER NAMES. A concept with two class names is
   two components that happen to look alike today, and the first person to edit
   one of them makes the two pages different again.

   What was removed rather than added, in the pass that wrote this block:

     .crow          a second name for the list row. `.row` is the one every
                    list already uses and carries the whole responsive story;
                    .crow was declared by the redesign and used by nothing.
     .cardstack     a third name for "cards in a column with a gap", which
                    .cols > .colmain and .pane > .sec both already do.
     .verdictform / .verdictq / .verdictrow / .verdictback / .verdicted
                    the report page's own copy of the §5 controls. verdict.ts
                    exists so that the item page and the report page draw one
                    control; the CSS had to stop offering a second one, or the
                    next person to touch the report page would find it waiting.
     .optpick / .optrow
                    a radio picker for options, replaced by .optcards — where
                    picking IS the approval, in one press (§5).
     .shipgroup / .shipunder
                    a turn drawn as two boxes, one nested under the other. A
                    turn is one `.wturn` on both pages now.
     the first .turnmsg block
                    two rules for one class, the second winning. The dead one
                    described the pre-redesign list-shaped thread and would have
                    been the one somebody read.

   The names that are left, and what each one means, everywhere:

     .row           a row in a list you scan (the plan, the desk, questions)
     .pane          a card
     .sec + .sec-h  a card inside a detail column, with its header strip
     .ch-m          the quiet meta beside a card's title
     .cols / .colmain / .colrail   main plus the 316px rail, on BOTH detail pages
     .wturn         one agent turn: what was asked, what came back, what to do
     .turnactions   the §5 controls under a returned turn
     .whatsnext     the three moves (§6)
     .sheetcells    the source record, quoted column by column
     .pill / .tag   a state / a label
     .blank         a value that is not there, in the ink reserved for it
   =================================================================== */

/* THE HAIRLINE-SEPARATED LIST, DELETED — and it is what the owner was looking
   at when he said "the border is not good, on the right it only touches the
   text but on the left and top its totally not good".

   It was the previous answer to the same problem: "the 2px left edge alone is
   not enough of a break between a three-paragraph answer and the next turn's
   heading", solved with a rule above each turn. When the turn became a card
   these three rules were still here and still winning — `.cols .pane > .sec >
   .wturn` is four selectors' worth of specificity against `.wturn` — so a card
   drawn with a border and 11px/13px of padding rendered with `padding: 16px 0
   0` and no left border. Text against the left and right edges, 16px of air on
   top: exactly what he described, and exactly what these rules say.

   A FIX OUTLIVING ITS PROBLEM IS A BUG WITH A JUSTIFICATION ATTACHED. Same
   shape as the #jobs anchor and the old .qcard list rules, three times in one
   day. When a component changes shape, grep for every rule that names it. */
.wturn .wt-h { gap: 8px; }
.wturn .wt-h b { font-size: 14px; }
.wturn .wt-h .kind { flex: none; }
/* A nested ship keeps its own edge, because it IS inside the build it carries. */
/* A nested ship keeps its own edge, because it IS inside the build it carries —
   an inset card rather than a sibling one. */
.wturn.shipped { border-color: var(--done); border-left-width: 3px; margin: 9px 0 0;
                 background: none; }

/* ===================================================================
   ============  6. EVERY SIZE, INCLUDING THE ONE NOBODY CHECKS  =====
   ===================================================================
   The owner reviews this on a phone; that is how he found the report page, and
   then how he found both faults this section now carries the fix for. Phone
   LANDSCAPE is the harsh case — under the 900px where the rail collapses AND
   only ~390px tall, so the bar plus a page header can leave almost nothing.

   Checked at 1920x1080, 1280x800, 1180x820, 1024x768, 820x1180, 768x1024,
   932x430, 844x390, 667x375, 390x844 and 360x800, on ten pages each, against
   three assertions rather than a look: the bar's top is 0 after scrolling to
   the middle of the page, the document is exactly as wide as the viewport, and
   the hamburger is on screen while scrolled. A screenshot proves a layout; only
   scrolling proves a sticky, which is how `position: sticky` sat in this file
   for the whole time the bar did not stay.
   =================================================================== */

/* NOTHING SCROLLS THE PAGE SIDEWAYS — AND THE TOP BAR STAYS PUT.
   These two were in conflict, and the one nobody was looking at lost.

   `html, body { overflow-x: hidden }` used to be the belt to the braces below.
   It also silently made the BODY a scroll container: `overflow-x: hidden` with
   `overflow-y: visible` computes overflow-y to `auto`, and a `position: sticky`
   element inside a scroll container sticks to THAT box rather than to the
   viewport. The body box is as tall as its own content and is therefore never
   scrolled, so the bar never stuck — anywhere. Measured on the item page at
   scrollY 1200: the bar's top was -1200 at 1920x1080 and at 390x844 alike.
   Clearing the rule from `body` ALONE put it back to 0 at both. (It is the body
   half that does the damage; on `html` it merely propagates to the viewport.)

   IT IS NOT COMING BACK IN ANY FORM. A document-level `overflow-x: hidden` does
   not stop content being too wide, it stops you SEEING that it is — the
   segmented control noted at the foot of this section measured 695px inside a
   390px phone and simply ran off the edge, so "Who filed it" existed and could
   not be reached. Whatever actually overflows gets contained where it lives,
   and the page-width probe in the verification matrix is what proves it. */
.wrap, .shellbody, .cols, .colmain, .colrail { min-width: 0; }
.sh-v, .sh-c, .msg-body, .wt-body, .turn-why, .dd-note { overflow-wrap: anywhere; }
.colrail .set dd { overflow-wrap: anywhere; }
/* Wide content scrolls INSIDE its own box, never by moving the page. */
.tbl-scroll { overflow-x: auto; max-width: 100%; }
.sheetcells { max-width: 100%; }

/* A SHUT TOOLTIP WAS STILL TAKING UP ROOM, and it was the whole of the sideways
   scroll the body used to be hiding.

   `.tip::after` is a 320px absolutely-positioned bubble anchored to the LEFT
   edge of whatever it explains, kept off screen with `visibility: hidden` — and
   a visibility-hidden box is still laid out and still counts towards its
   ancestors' scrollable overflow. On a 390px phone the status pill in the item
   page header carried one whose right edge landed at 399px. That was the entire
   9px: with `overflow-x` off html and body, every other page in the matrix
   measured exactly its viewport, and this one measured 399 of 390.

   Taken out of layout when shut rather than merely made invisible. The fade
   survives where `transition-behavior: allow-discrete` does; where it does not
   the bubble simply appears at once.

   THIS PARAGRAPH USED TO END "the same sentence is on the element's `title`",
   and that stopped being true the day the double-tooltip was fixed: `title` made
   the browser draw a SECOND bubble, so it became `aria-label`. Screen readers
   still get the sentence. Nothing draws it on a phone, which was already true —
   `title` never appeared on touch either, so that clause was reassuring rather
   than accurate even when it was written.

   STILL OPEN, AND NAMED HERE BECAUSE IT WILL BE FOUND FROM THIS RULE: the bubble
   is CLIPPED by `.pane, .card { overflow: hidden }` further down, which exists
   to keep children inside the 8px corners. A tooltip near the bottom of a card
   is cut in half. There is no pure-CSS escape from a clipping ancestor, so the
   fix is a real decision — drop the clip and handle corners per child, or flip
   the bubble above its trigger — not a value to nudge. */
.tip::after, .tip::before { display: none; }
.tip:hover::after, .tip:focus-visible::after,
.tip:hover::before, .tip:focus-visible::before { display: block; }
.tip::after {
  transition: opacity .12s ease, transform .12s ease, visibility .12s,
              display .12s allow-discrete;
  /* Open, it still has to fit the screen it opened on. */
  max-width: min(320px, calc(100vw - 32px));
}
.tip::before {
  transition: opacity .12s ease, visibility .12s, display .12s allow-discrete;
}
@starting-style {
  .tip:hover::after, .tip:focus-visible::after,
  .tip:hover::before, .tip:focus-visible::before { opacity: 0; }
}

@media (max-width: 900px) {
  /* The rail is below the main column, not beside it. It keeps its cards. */
  .cols { gap: 18px; }
  /* THE SOURCE GRID NARROWS RATHER THAN COLLAPSING. Forcing one column below
     900px turned nine short values into nine tall rows on a tablet, which is a
     lot of scrolling to read "blank" nine times. 150px still fits two columns
     in a 620px column and the values do not touch. */
  .sheetcells { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
  .wn-picks, .kindgrid { grid-template-columns: minmax(0, 1fr); }
  .turnactions, .shipbox, .testbox, .retrybox .field { max-width: none; }
}

@media (max-width: 760px) {
  /* 18px of card padding on a 390px screen is 36px of the 360 available. */
  .cols .pane > .sec { padding: 0 13px 14px; }
  .cols .pane > .sec > .sec-h { margin: 0 -13px 12px; padding: 10px 13px; }
  .pagehead h1 { font-size: 20px; }
  .ph-sub { font-size: 14px; }
  /* A turn's meta line wraps rather than pushing the card wide. */
  .wturn .wt-h .mono { font-size: 12px; }
  .turnmsg { max-width: 100%; }
  .shots .shot { max-width: 100%; }
}

/* PHONE LANDSCAPE. 844x390: the viewport is 390px TALL, so the top bar is 14%
   of it before anything else.

   THIS USED TO SET `.topbar { position: static }` AND THAT WAS THE SECOND CAUSE
   of "when you scroll you can't navigate". The reasoning written here was that a
   header following you down a 390px window has taken a seventh of the page to
   say where you are, which you already know — true of a header that only says
   where you are. It stopped being true the moment the hamburger moved into that
   bar: the bar is now the ONLY way off the screen in this orientation, so
   letting it scroll away leaves a page you cannot leave without scrolling back
   to the top. A header is worth a seventh of the screen when it is the exit.

   What it costs is paid down instead of paid off: the bar is 44px here rather
   than 56, and every rule that depended on its height reads --topbar-h, so the
   slide-over still hangs off the bottom edge of it and not off a stale 56. */
@media (max-height: 600px) and (max-width: 1100px) {
  :root { --topbar-h: 44px; }
  .backnav { padding-top: 8px; }
  .pagehead { padding-top: 10px; }
  /* Phone gets a smaller bump than the desktop 24 — the bar is shorter and the
     screen is precious, but flush against it still reads as a rendering fault. */
  .pagehead:not(:has(.backnav)) { padding-top: 16px; }
  .cols { padding-top: 10px; }
}

/* ---------------- WHAT THE VIEWPORT MATRIX CAUGHT ---------------- */
/* Measured, not guessed: every screen at 1920, 1280, 820x1180, 1180x820,
   390x844 and 844x390, with a probe reporting any element whose right edge
   passes the viewport. Three did. */

/* 1. THE SEGMENTED CONTROL COULD NOT WRAP. `.seg` is an inline-flex track and
      its items are `white-space: nowrap`, so the desk's six grouping axes
      measured 695px inside a 390px phone. It did not scroll the page — the body
      refuses that — it simply ran off the edge, so "Who filed it" existed and
      could not be reached. It wraps now, and the track's rounded background
      wraps with it, which is the shape §4.2b asks for: "The row wraps rather
      than scrolls." A horizontal scroller hides controls, and a hidden control
      is the same failure as a removed one. */
.seg { flex-wrap: wrap; max-width: 100%; }

/* 2. THE IDENTITY BUTTON. A full email address plus an avatar plus a caret is
      about 230px, and on a 390px phone that is more than half the bar — it
      pushed 16px past the right edge, taking the width note and the project
      name with it. Below 900px the address is the avatar's job. The <button>
      keeps its accessible name because the text is still in the DOM. */
@media (max-width: 900px) {
  .usermenu { min-width: 0; }
  .topbar .userbtn { max-width: 100%; overflow: hidden; }
  .topbar .userbtn { font-size: 0; gap: 4px; }
  .topbar .userbtn .avatar { font-size: 10.5px; }
  .topbar .userbtn::after { content: "▾"; font-size: 12px; color: var(--ink-3); }
}

/* 3. THE BUG BOX OPENED OFF THE SCREEN. `.bugbtn` shrink-wraps its summary and
      the 360px panel inside it grew rightwards from that edge, so on a phone the
      control that reports a broken page was itself unreachable — which is the
      one control that must work when everything else does not. Anchored to the
      right edge of its own trigger instead of flowing from it.

      IT NOW OPENS DOWNWARD, because the trigger moved from the bottom-right
      corner of the viewport into the top bar. `bottom: 100%` there would open it
      upward, off the top of the screen — the same bug it was written to fix,
      mirrored. The max-height keeps a long form scrollable inside a short
      viewport rather than running off the bottom. */
.bugbtn > details { position: relative; }
.bugbtn > details > .bugbox {
  position: absolute; right: 0; top: calc(100% + 8px); margin-top: 0;
  z-index: 60; max-height: calc(100vh - var(--topbar-h) - 24px); overflow-y: auto;
}

/* 4. THE ICON RAIL'S LABELS WERE PAINTED OVER THE CONTENT — and no overflow
      probe would ever have caught it, because they overflowed to the RIGHT into
      the main column rather than past the viewport edge. Found by looking at a
      844x390 phone-landscape screenshot: "DISAGREEMENTS", "NOTIFICATIONS" and
      "WEEKLY REPORT" were drawn across the report's own title.

      THE CAUSE IS ONE WORD. `.nv-l` is a <span>, and `overflow: hidden`,
      `max-width` and `text-overflow` do not apply to an inline box — so the
      rule that was written to clip the labels into the 56px track did nothing
      at all, silently, since the day it was written. The comment above it says
      "the label is abbreviated to its initial rather than hidden"; it was
      neither.

      Between 761px and 1100px is the ONLY range where this bites, and it is
      exactly the range a phone in landscape lands in.

      A PHONE IN LANDSCAPE NO LONGER LANDS IN IT — it gets the hamburger, on the
      height test above.

      AND THE CLIP IS GONE, because report 12 says the clip was itself the bug:
      "it should be clear, show the full words". A label that does not fit is now
      allowed to WRAP onto a second line — see THE WORDS SURVIVE THE FOLD, up at
      the rail — so there is nothing left to paint across the page and nothing
      left to hide either. `display: block` and `min-width: 0` stay: they are
      what made the span a box in the first place, and without them a wrap rule
      would be as inert as the clip rule was. The range must stay in step with
      the rail's own query: two media queries describing "the rail" that disagree
      about when it exists is how the labels got painted across the content the
      first time. */
@media (max-width: 1100px) and (min-width: 761px) and (min-height: 601px) {
  .nv-l { display: block; max-width: 100%; white-space: normal; overflow-wrap: anywhere; }
  .navitem { min-width: 0; }
}

/* THE CARD HEADER'S META WRAPS ONTO ITS OWN LINE ON A PHONE, and a right-
   aligned fragment under a left-aligned title reads as a stray. It keeps
   margin-left:auto on a wide card, where it belongs at the right edge. */
@media (max-width: 760px) {
  .cols .pane > .sec > .sec-h .ch-m, .card-h .ch-m {
    margin-left: 0; text-align: left; flex-basis: 100%;
  }
}

/* ---------------- ONE THREAD, ONE SHAPE (§4.4b applied to §4.6) ----------------
   A CONVERSATION LOOKED LIKE TWO DIFFERENT PRODUCTS DEPENDING WHICH PAGE IT WAS
   ON. The item's `.turnmsg` is a chat: the person's turn on the right in the
   accent tint, the other party's on the left in the amber tone, so "who said
   this" is answered by which side it is on and costs no words. The report's
   `.msg` was a left-bar block list, and worse, its tones were the mirror image —
   the TEAM was tinted accent and the REPORTER was grey, so the same colour
   meant "me" on one screen and "the other side" on the other.

   Same convention on both now: WHOEVER IS READING THE PAGE IS ON THE RIGHT.
   `.us` is the team, which is you; `.them` is the reporter, which is not.

   THE <details> BEHAVIOUR IS UNTOUCHED. The collapse, the subject line, the
   two-line peek and the "copy 3 of 9" marker all still work — this changes the
   box they sit in, not what the box does. That machinery exists because FB-1964
   carries nine byte-identical copies of one reply and the thread was five
   screens of the same paragraph; a redesign that dropped it would put that back.
   ------------------------------------------------------------------------- */
.msg {
  margin: 0 0 10px; padding: 13px 16px; border-radius: 8px;
  max-width: 82%; margin-right: auto; margin-left: 0;
  background: var(--active-bg); border: 1px solid var(--active-bd); border-left-width: 1px;
}
.msg.us {
  margin-left: auto; margin-right: 0;
  background: var(--accent-tint); border-color: var(--accent-soft);
}
.msg.them { background: var(--active-bg); border-color: var(--active-bd); }
/* AN INTERNAL NOTE IS NOT A TURN IN THE EXCHANGE and must not be drawn as one
   (§9.10). It is the team talking to itself, it is never sent, and the reporter
   never sees it — so it takes the full width, the neutral tone and a dashed
   edge, which is the one shape on this page that says "not part of the
   conversation above". */
.msg.internal {
  max-width: none; margin-left: 0; margin-right: 0;
  background: var(--idle-bg); border: 1px dashed var(--idle-bd);
}
.msg.internal > summary, .msg.internal > .msg-top { opacity: 1; }
@media (max-width: 760px) { .msg { max-width: 100%; } }

/* WAITING ON THE AGENT (§4.4b). A dashed amber bubble at the foot of the
   thread, so the silence after you press Send is a stated fact rather than an
   absence — and the header's indicator and this bubble are drawn from ONE
   derivation, so they cannot tell a reader two different things. */
.turnmsg.pending {
  border-style: dashed; border-color: var(--active); background: none;
  max-width: 100%; margin-left: 0; margin-right: 0;
}
.turnmsg.pending .wt-body { color: var(--active); }
.sec-h .live.waiting { color: var(--active); }
.sec-h .live.waiting .dot { background: var(--active); }

/* .muted WAS USED AND NEVER DECLARED. One line in the notifications ledger asks
   for quiet text and got the page's ordinary ink, so a sentence written as an
   aside read as a statement. Found by diffing every class the TypeScript emits
   against every class app.css declares — an undeclared class renders as nothing
   and is invisible to tsc, to a diff and to a screenshot you are not staring at.
   Still declared here rather than swapped for `.note` at the call site, because
   the call site is in a file another agent is editing. */
.muted { color: var(--ink-3); }

/* ONE COLUMN ONLY WHEN TWO GENUINELY WILL NOT FIT. Below about 520px a
   150px minimum leaves 8px between the two, and a mono value wraps mid-word. */
@media (max-width: 520px) {
  .sheetcells { grid-template-columns: minmax(0, 1fr); }
  .sheetcells .sheetcell { border-right: 0; }
}

/* ---------------- THE TOP BAR ON A PHONE ----------------
   Found on the weekly report at 390px: the bar was three lines tall. The
   project name carries a count ("week ending Fri 7 Aug"), the page adds its own
   note ("you are admin"), and both wrapped rather than yielding — so a 56px
   strip that says WHERE YOU ARE took a sixth of the screen to say it, and the
   identity button was squeezed to a sliver.

   The bar keeps, in order of what you actually need on a phone: the way into
   the nav, the wordmark, the project, and who you are signed in as. Everything
   else is context that the page itself repeats. */
.topbar .proj-here {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
@media (max-width: 900px) {
  .topbar .proj-here .cnt { display: none; }
  .tb-right { display: none; }
  .wordmark { overflow: hidden; text-overflow: clip; }
}
@media (max-width: 480px) {
  /* At this width the wordmark and the project name are competing for about
     200px. The project is the one that changes, so it is the one that stays. */
  .topbar .wordmark, .topbar .tb-div { display: none; }
}

/* .sheetprose IS THE GRID CHILD, and it needs the one property a grid child
   always needs: permission to be narrower than its content. Without it a single
   long token in a quoted description — a URL, a path — sets the track's minimum
   and widens the whole card. Declared rather than left implicit because an
   undeclared class is invisible to every check this repo runs. */
.sheetprose { min-width: 0; }

/* ---------------- A CARD THAT FOLDS (§4.4d, and the owner on 2:B2) ----------
   "the from the sheet section should be collapsed and summarize since it takes
   up a lot of space on the page and it's the first item".

   NOT A THIRD WAY OF FOLDING SOMETHING. It is <details>, like a group on the
   plan and on the desk, with the same 9px caret rotating the same way — and the
   summary IS the card's own `.sec-h`, so a shut card is exactly the header
   strip it already had and an open one is exactly the card it already was.
   Nine lines of CSS rather than a component, because there is no new component:
   this is the card, closed.

   THE CARET IS 9px WIDE OPEN OR CLOSED, so the title does not shift sideways
   when you fold it — a caret that changes width moves every label by two pixels
   and makes the fold feel like a layout bug. */
.sec.sheetfold > summary { list-style: none; cursor: pointer; }
.sec.sheetfold > summary::-webkit-details-marker { display: none; }
.sec.sheetfold > summary:hover { background: var(--rule-3); }
.sec.sheetfold > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.sec.sheetfold > summary .caret {
  width: 9px; flex: none; font: 400 12px/1 var(--ui); color: var(--ink-3);
  transform: rotate(-90deg); transition: transform .12s;
}
.sec.sheetfold[open] > summary .caret { transform: none; }
/* SHUT, THE CARD IS ITS HEADER AND NOTHING ELSE. `.sec` carries 18px of bottom
   padding and `.sec-h` 14px beneath itself; left alone, a closed fold would sit
   in 32px of empty white and read as a card that failed to load its body. */
.sec.sheetfold:not([open]) { padding-bottom: 0; }
.sec.sheetfold:not([open]) > summary { margin-bottom: 0; }
/* ...AND THIS IS THE HALF THAT NEVER LANDED. The rule above was written to zero
   both, and on the item page it only ever zeroed the padding: the summary IS a
   `.sec-h`, and `.cols .pane > .sec > .sec-h` sets `margin: 0 -18px 14px` at
   four classes, which out-specifies the two-class rule above it. So every closed
   fold on the item and report pages has been sitting in 16px of empty white
   under its own header — precisely the "card that failed to load its body" the
   comment above says it prevents. Found by measuring the rendered card against
   its own summary (90px vs 74px), which is the only way this shows up: it is
   invisible in the diff and looks like ordinary spacing on screen until you ask
   what is in it. Same-property, higher-specificity, right next to the rule it
   completes. */
.cols .pane > .sec.sheetfold:not([open]) > .sec-h { margin-bottom: 0; }
/* WHAT MAKES THE CLOSED FOLD WORTH READING: the first sentence, in ordinary
   prose. The resets are kept although `.sec-h` is no longer uppercase mono: the
   peek is a full sentence of body text sitting inside a 13px/600 heading, and
   it has to be neither. It takes the full width and wraps, which is why
   `.sec-h` is `flex-wrap: wrap`. */
.fold-peek {
  flex-basis: 100%; margin-top: 5px;
  font: 400 13px/1.45 var(--ui); letter-spacing: 0; text-transform: none;
  color: var(--ink-3); overflow-wrap: anywhere;
}
@media (prefers-reduced-motion: reduce) { .sec.sheetfold > summary .caret { transition: none; } }

/* THE PEEK EXISTS TO MAKE THE SHUT FOLD WORTH READING, so it goes when the fold
   opens — the full description is the very next thing on the screen, and
   printing its first line twice, six pixels apart, reads as a rendering fault. */
.sec.sheetfold[open] > summary .fold-peek { display: none; }

/* ===================================================================
   ============  7. WHAT THE SUB-AGENTS COULD SEE AND NOT FIX  =======
   ===================================================================
   Two people rebuilt six screens under a rule that they may not edit this file,
   which is the right rule — one owner per stylesheet — and it means they end a
   job holding a list of faults they can describe and not repair. Those are
   worth more than the ones they fixed, because a fault somebody CAN fix quietly
   is a fault nobody writes down.
   =================================================================== */

/* 1. A CLAIM THAT VANISHES RATHER THAN SAYING IT IS HIDDEN.
      `.pill.lifecycle { display: none }` below 860px silently drops Plumbline's
      OWN status from every plan row on a tablet and a phone — the one column
      that says whether anybody here has classified the row, gone, with nothing
      saying it went. §9.1 is about empty values; this is the same rule one step
      up: a value that exists and is not drawn is worse than one drawn empty,
      because the reader cannot tell the difference from "nobody has said".

      It goes back, on the row's own line with the rest of the meta, where the
      three-line layout below 700px already puts what does not fit. `.r-wait` is
      restored for the same reason — it carries the open-question flag. */
@media (max-width: 860px) {
  .row .pill.lifecycle, .row .r-wait { display: inline-block; }
}
@media (max-width: 700px) {
  /* Line three already exists and already holds the meta. The lifecycle pill
     joins it rather than fighting the state pill for line one. */
  .row .pill.lifecycle {
    display: inline-block; grid-area: meta; justify-self: start; align-self: center;
  }
  .row .r-wait, .row .tip.r-wait { justify-self: end; }
}

/* 2. THE LAST CELL OF A ROW DISAPPEARED BETWEEN 701px AND 860px, and on the
      questions list that cell is the Answer button — the only control on the
      screen. The 860px rule cuts `.row` to four tracks; a fifth child is placed
      by auto-flow into a row that is not there and is clipped. Give the track
      back: at this width there is room for it, and the alternative is a list of
      things to answer with no way to answer them. */
@media (max-width: 860px) and (min-width: 701px) {
  .row { grid-template-columns: 8px 56px minmax(0,1fr) 92px auto; }
  .row.rrow { grid-template-columns: 8px 34px 56px minmax(0,1fr) 108px auto; }
}

/* 3. A DISABLED GHOST BUTTON LOOKED ENABLED. `.btn.ghost` and the disabled look
      are both "panel background, rule border, quiet ink" — one ink step apart —
      so on a row an unavailable action read as available until you pressed it.
      §9.9 requires the control to STAY VISIBLE; it also requires it to be
      legible as unavailable, or showing it teaches people to press it. Dashed,
      which no enabled control on this site is. */
.btn.ghost:disabled,
.btn.ghost[aria-disabled="true"] {
  border-style: dashed; color: var(--ink-3); background: var(--panel-2);
}

/* 4. THE WEEKLY'S REF LIST WAS CLIPPING ITS LAST LINK. `.qr-item` is the
      questions list's meta line, where `nowrap` + ellipsis is exactly right —
      one line, truncated, in a fixed row. The weekly reuses it for a FLOWING
      list of item refs, where nowrap means the row is as long as the refs are
      and the last one loses two pixels to the parent's own overflow:hidden.
      Not a page-scroll fault, which is why the overflow probe called it
      contained; just a link with its final character shaved off. In a wrapping
      list, wrap. */
.trow .qr-item {
  white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere;
}

/* FINISHED CHATS, FILED AND OUT OF THE WAY (0089).

   The owner: "take the entire conversation and file it colapsed in the bottom
   [chats] and i should be able to go in and see it but it shouldnt be
   rpominent". Both halves are the requirement — reachable, and not competing
   with the live thread for the middle of the page.

   It reuses .sheetfold, which is already the product's word for "kept, and not
   the thing you came for". A new fold shape here would be a second idea of the
   same thing, which is what §5's name audit exists to prevent. */
.pastchats { margin: 14px 11px 0; }
.pastchats .sec.sheetfold { margin: 0 0 8px; }
.pastchats .sec.sheetfold > summary { cursor: pointer; }
/* Quieter than a live turn: same shape, less ink, so a closed chat reads as
   record rather than as conversation. */
.pastchats .turnmsg { background: transparent; border-color: var(--rule); max-width: 100%; }
.pastchats .turnmsg.them { background: transparent; border-color: var(--rule); }

/* THE ASK, ABOVE THE ANSWER. Quieter than the result — it is the question, and
   the thing you came to read is what came back. Same left rule as a quote,
   because that is what it is: somebody's words, repeated. */
.wt-asked { margin: 0 0 7px; padding: 5px 0 5px 10px; font-size: 13px;
            color: var(--ink-2); border-left: 2px solid var(--rule);
            white-space: pre-wrap; overflow-wrap: anywhere; }
.wt-asked .lbl { font: 500 10.5px/1.4 var(--ui); letter-spacing: .09em;
                 text-transform: uppercase; color: var(--ink-3); margin-right: 7px; }

/* AN AGENT'S TURN, INSIDE OUR SIDE.

   The owner could not tell who wrote what on a report where every turn was
   ours: three agent replies and one question a person asked, separated only by
   an 11px grey email address. Sides cannot help there — there was no reporter
   turn to put on the other side — so the distinction that matters is drawn
   here: an agent's words carry a marked rail and its name is legible.

   THE AUTHOR LINE WAS THE REAL FAILURE. 11px in --ink-3 on a tinted card is
   the quietest thing in the box, under a body it is supposed to attribute. */
.msg-who { font-size: 13px; color: var(--ink-2); font-weight: 500; }
.msg.byagent { border-left-width: 3px; border-left-color: var(--active); }
.msg.byagent .msg-who { color: var(--active); }
.msg.byagent .msg-who::before { content: "◆ "; opacity: .75; }

/* CLICK IN TO READ THE WHOLE MESSAGE (0090). A one-line preview is right for a
   list; a message whose point is in its third sentence needs an opening. Only
   rendered when there IS more than the preview shows — a control that reveals
   nothing is worse than no control. */
.msg-open > summary { list-style: none; cursor: pointer; display: flex; gap: 8px;
                      align-items: baseline; }
.msg-open > summary::-webkit-details-marker { display: none; }
/* THE OPENER IS A CONTROL AND IT IS SIZED LIKE ONE (owner, 2026-08-11: "the
   read it all is tiny and to close it back i have to click on the read it all
   again which is grey, it should change to close and it should keep the blue
   color").

   Three faults in one small thing. 9.5px is below anything else clickable on
   the page. The label went on saying "read it all" once it was open, so the way
   to close it was to press the words for opening it. And it went grey — the one
   colour this console uses for "you cannot press this" — on the press that
   matters most, because by then the reader has several screens of text open.

   TWO SPANS, ONE SHOWN AT A TIME, rather than `content:` on a pseudo-element.
   Generated content is not selectable, is skipped by some assistive tech, and
   would put the word "close" only in the stylesheet — where the person editing
   this copy would never find it. */
.msg-open > summary .opener { font-size: 12px; color: var(--accent);
                              text-transform: uppercase; letter-spacing: .09em;
                              white-space: nowrap; font-weight: 600; }
.msg-open[open] > summary .opener { color: var(--accent); }
.msg-open > summary .o-less { display: none; }
.msg-open[open] > summary .o-more { display: none; }
.msg-open[open] > summary .o-less { display: inline; }
.msg-open[open] > summary .note { display: none; }
.msg-open > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.msg-open .msg-body { display: block; margin-top: 6px; white-space: pre-wrap; }

/* THE BACK LINK, THE SUBJECT AND THE STATUS STAY ON SCREEN WHEN YOU SCROLL.

   The owner: "i think the back and the subject and statues should be moved in
   to the top white line when scrolled". On a report or an item — which are the
   two long pages in the product — you scroll past the heading in the first
   flick and then have no idea which row you are reading, which state it is in,
   or how to get back. The pages this matters on are exactly the ones LAYOUT-2
   measured at 2,700 and 3,700 pixels.

   STUCK UNDER THE TOP BAR, NOT MOVED INTO IT. Rewriting the heading into
   `.topbar` would need JavaScript to copy live text into another element and
   keep the two in step — a second copy of a fact, which is the failure this
   codebase produces most. Sticking the real heading directly beneath the real
   bar gets the same result with no second copy and no script: one element, in
   one place, that stops scrolling away.

   IT SHRINKS ONCE STUCK so it costs a strip rather than a banner. `top` is the
   bar's own height; both are `position: sticky` in the same containing block, so
   they stack rather than overlap.

   THE KNOWN TRAP, recorded because it cost an afternoon before: `position:
   sticky` silently does nothing inside a scroll container, and `overflow-x:
   hidden` on `body` creates one. The bar above already relies on this working,
   so if it ever stops, both go together and the cause is that overflow, not
   this rule. */
/* IT IS EXACTLY AS WIDE AS THE LIST UNDER IT, AND IT WAS NOT (report 12).
   The owner: "the white top bar where it says plan is wieder than the list".

   It read `margin: 0 -18px; padding: 12px 18px 10px` — an 18px bleed out of the
   content column on both sides. The intent was that a sticky white strip should
   cover whatever scrolls beneath it, but nothing scrolls beneath it out there:
   the list, the filters and the header all live in the same column, so the bleed
   covered only page background and the white block stood 18px proud of the list
   on each side. On a grey page that reads as two things that were meant to line
   up and do not.

   The padding stays as vertical space and goes to zero horizontally, so the
   header's left edge is the list's left edge and its right edge is the list's
   right edge. If a future card is ever WIDER than its column, that card is the
   thing to fix; widening the chrome to hide it would put the misalignment on
   every page in the product. */
/* AND IT STOPS 16px SHORT OF THE BAR, WHICH ATE THE TOP OF THE TITLE.
   The comment above says "`top` is the bar's own height". It was the literal 41
   while the bar measures 57 — so once stuck, the header slid sixteen pixels
   BEHIND the bar (the bar is z-index 20, this is 15) and the top of "Plan" was
   shaved off for the whole scroll. Same shape as the labels: a sentence
   describing the intent, a number doing something else, and only a screenshot
   able to tell you which one was rendering.
   Written from the bar's own variable now, plus its 1px rule, so the two cannot
   drift again. The @media (max-width:900px) override that set 45 went with it:
   it said "the bar is taller once the hamburger is in it", and the bar measures
   57 at 1440, 900 and 800 — the height it was correcting for does not happen. */
/* ============ AND PADDING ON ALL FOUR SIDES, NOT TWO ============
   He opened DevTools on it: "pagehead has padding top and bottom only and it
   should have all around." It read `padding: 12px 0 10px` — the title and the
   subtitle sat flush against the left and right edges of their own box.

   MY FIRST ANSWER WAS WORSE THAN THE PROBLEM. I padded it and pulled the box out
   by the same 12px with a negative inline margin, so the text would not move
   relative to the cards. That kept the title aligned and made the sticky white
   band 12px wider than every card under it — and a band that overhangs the
   content is more obviously wrong than a title that does not: "now the entire
   bar is offset". Preserving one alignment by breaking a more visible one is not
   a fix, and I should have rendered it rather than reasoning about it.

   SO THE BOX STAYS ON THE COLUMN AND THE TEXT SITS INSIDE IT, which is what
   padding normally means. The title now begins 12px in from the card edge —
   between the card's border and the card's own text, which is where a heading
   over a stack of cards reads comfortably anyway. */
.pagehead {
  position: sticky; top: calc(var(--topbar-h) + 1px); z-index: 15;
  background: var(--panel); margin: 0; padding: 12px 12px 10px;
  border-bottom: 1px solid transparent;
}
/* The back link is inside the header so it sticks with it — see html.ts. It
   keeps its own line above the title and gives up the 13px it used to sit in. */
.pagehead .backnav { padding: 0 0 5px; }

/* ============ A PAGE WITH NO BACK LINK WAS CRAMPED AGAINST THE BAR ============
   Measured, because the difference is invisible in this file: the gap between
   the topbar and the <h1> is 50px on a page that HAS a back link and 12px on one
   that does not. The back link was doing the work of the top padding for the
   pages that happen to have one, and the pages that do not — Weekly report, the
   plan — sat 12px under a sticky bar. He drew an arrow at it.

   `:has()` rather than a modifier class, so nothing has to be remembered at the
   call site: a page grows the space exactly when it lacks the thing that was
   providing it. Already used in this file for .kindcard.

   IT DOES NOT TOUCH THE PAGES THAT WERE FINE. A header with a back link keeps
   its 12px, and its 50px total, unchanged. */
.pagehead:not(:has(.backnav)) { padding-top: 24px; }

/* "IT SHRINKS ONCE STUCK" — WHICH IT DID NOT, UNTIL NOW.
   This block used to be a `transition: font-size` on the h1 and an @supports
   query that only set a border colour. Nothing anywhere changed the font size,
   so the transition had nothing to transition and the comment described a
   behaviour the file did not implement. Measured on item-long: the head stuck
   correctly at 41px and the title stayed 24px the whole way down. A comment is
   not a feature.

   SCROLL-DRIVEN, SO THERE IS NO SCRIPT AND NO SECOND COPY OF THE FACT. The
   title shrinks to the section-header step over the first 90px of scroll, and
   the rule under the header fades in over the same range, so at rest it is a
   heading on the page and below the fold it is a strip of chrome. The subtitle
   stays: it carries the ref, the STATUS pill and the program area, which is the
   "statues" half of what was asked for — shrinking the head must not take the
   status with it.

   @supports, so a browser without scroll-driven animation gets the sticky
   heading at full size — which is what everybody got before this — rather than
   nothing. Reduced motion keeps the border and drops the resize. */
@keyframes pl-pagehead-rule { to { border-bottom-color: var(--rule); } }
@keyframes pl-pagehead-title { to { font-size: 16px; } }
@supports (animation-timeline: scroll()) {
  .pagehead {
    animation: pl-pagehead-rule linear both;
    animation-timeline: scroll(); animation-range: 0 90px;
  }
  .pagehead h1 {
    animation: pl-pagehead-title linear both;
    animation-timeline: scroll(); animation-range: 0 90px;
  }
  @media (prefers-reduced-motion: reduce) {
    .pagehead h1 { animation: none; }
  }
}

/* THE SAME OPENER THE NOTIFICATIONS LIST USES, on a job's result. A live turn
   printed its whole answer inline, so an agent that answered thoroughly made the
   page worse — several screens of one reply above anything else on the item. */
.wt-open > summary { display: block; }
.wt-open > summary .opener { display: inline-block; margin-top: 4px; }
.wt-open[open] > summary .wt-body { display: none; }

/* ===================================================================
   ============  THE SIGN-IN LANDING, AND ITS PLUMB LINES  ===========
   ===================================================================
   OPEN.md §4 asked for this and said "prototype: done — match it". There was no
   prototype: the file it pointed at contains no sign-in screen at all, which is
   why the owner said "there was a design for the sign in page the design tool
   made but i dont see it". What §4 DID leave is a complete brief — wordmark,
   catch phrase, one positioning line, Google primary, dev sign-in tucked away,
   a CSS-only plumb-line motif, and "keep it quiet enough to sign in over". This
   is built from that.

   THE MOTIF IS THE ARGUMENT, NOT DECORATION. A plumb line is the oldest
   reference instrument there is: it does not argue, it hangs, and what it tells
   you is what is actually true. So there are TWO of them. One hangs dead still —
   that is the record. The other never settles beside it. That is the product's
   own sentence drawn rather than written: every disagreement shown, never
   resolved for you. If it ever came to rest the page would be promising
   something the product refuses to do.

   ASSET-FREE AND BEHIND EVERYTHING. Two elements, a border and a clip-path, at
   low opacity. No imagery, no gradient standing in for content, nothing to load
   and nothing to go stale. It sits behind the column at pointer-events: none so
   it can never take a click meant for the button. */
.landing { position: relative; min-height: calc(100vh - 40px);
           display: grid; place-items: center; padding: 40px 18px 60px; }
.landing-in { width: 100%; max-width: 460px; position: relative; z-index: 1; }

/* THE WHOLE MOTIF IS DIMMED HERE, ONCE, so the parts below can be picked in
   colours that are actually visible. Written the other way first — quiet tokens
   at full strength — and it rendered as nothing at all: --rule on --ground is
   #e5e7eb on #f6f7f8, which is invisible rather than quiet. Quiet is a
   measurable amount of visible, not the absence of it. */
.plumbs { position: absolute; inset: 0; overflow: hidden; pointer-events: none;
          z-index: 0; opacity: .5; }
/* DISPLAY: BLOCK, AND THAT IS NOT A DETAIL. These are spans, and width and
   height do nothing to an inline box — the first version measured 0x0 on every
   element and the page rendered with no motif on it at all. It looked exactly
   like CSS that had not loaded, and it was in fact CSS that could never have
   drawn anything. */
.plumb { position: absolute; top: 0; width: 11px; transform-origin: top center; }
.plumb .cord { display: block; width: 1px; margin: 0 auto; }
/* A plumb bob is a cone: wide at the shoulder, a point at the bottom. */
/* A BOB IS A CYLINDER THAT TAPERS TO A POINT, not an arrowhead. Drawn as a
   plain triangle first and it read as a download arrow, which is a different
   object entirely — the shoulder is what makes it weigh something. */
.plumb .bob { display: block; width: 12px; height: 18px; margin: 0 auto;
              clip-path: polygon(18% 0, 82% 0, 82% 52%, 50% 100%, 18% 52%); }

/* THE ONE THAT HANGS TRUE. Still, quiet, the same ink as any other rule on the
   page — it is the reference, and a reference does not ask to be looked at. */
/* THE TWO HANG AS A PAIR, AND AT THE SAME LENGTH. Apart, at opposite edges,
   they read as two ornaments; you never compare them, and comparing them is the
   entire idea. Side by side and identical in every way except ANGLE, the page
   makes its own argument without a caption: one of these is true and the other
   is not, and nothing on screen is going to decide that for you. */
.plumb.true { left: 21%; }
.plumb.true .cord { height: 170px; }
.plumb.true .cord, .plumb.true .bob { background: var(--ink-3); }

/* THE ONE THAT NEVER SETTLES. Accent, because the disagreement is the thing the
   product exists to show you, and it is the only accent on the screen. */
.plumb.off { left: calc(21% + 38px); }
.plumb.off .cord { height: 170px; background: var(--accent); }
.plumb.off .bob { background: var(--accent); }

/* Slow, asymmetric, and it never resolves. 11s so it reads as ambient rather
   than as something demanding attention while somebody signs in. */
@keyframes pl-plumb-swing {
  0%, 100% { transform: rotate(3.5deg); }
  50%      { transform: rotate(-2.5deg); }
}
.plumb.off { animation: pl-plumb-swing 11s ease-in-out infinite; }

/* REDUCED MOTION KEEPS THE MEANING. The swinging line stops OFF TRUE rather
   than snapping straight: the point is that the two do not agree, and that is
   still visible standing still. Turning the animation off must not quietly
   resolve the disagreement. */
@media (prefers-reduced-motion: reduce) {
  .plumb.off { animation: none; transform: rotate(3.5deg); }
}
@media (max-width: 720px) { .plumbs { display: none; } }

.landing .wordmark { display: inline-block; border: 0; padding: 0;
                     margin: 0 0 22px; }
/* The catch phrase is the biggest thing on the page and the only big thing. */
.landing h1 { font: 650 24px/1.3 var(--ui); letter-spacing: -.01em;
              margin: 0 0 10px; text-wrap: balance; color: var(--ink); }
.landing .positioning { font-size: 14px; line-height: 1.55; color: var(--ink-2);
                        margin: 0 0 26px; max-width: 40ch; }
.landing .signin-primary { display: inline-flex; align-items: center; gap: 9px;
  font: 500 14px/1 var(--ui); text-decoration: none; height: 40px;
  padding: 0 18px; border-radius: 4px; border: 1px solid var(--accent);
  background: var(--accent); color: #fff; }
.landing .signin-primary:hover { filter: brightness(1.08); }
.landing .whose { font-size: 13px; color: var(--ink-3); margin: 12px 0 0; }

/* DEV SIGN-IN IS FOLDED AWAY AND SAYS WHAT IT IS. It is not authentication and
   the summary says so before anybody opens it — a fold that hides the warning
   with the control would be worse than no fold. */
.landing .devfold { margin-top: 30px; border-top: 1px solid var(--rule-2);
                    padding-top: 16px; }
.landing .devfold > summary { list-style: none; cursor: pointer;
  font: 600 12px/1.4 var(--ui); letter-spacing: .09em; text-transform: uppercase;
  color: var(--ink-3); }
.landing .devfold > summary::-webkit-details-marker { display: none; }
.landing .devfold > summary:hover { color: var(--ink-2); }
.landing .devfold[open] > summary { margin-bottom: 12px; }

/* The motif again on the project picker, at half presence — see pages.ts. It is
   the same two lines so the first two screens are one place; it is fainter
   because this screen has work on it and the landing did not. */
.projects-frame { position: relative; }

/* IN THE MARGIN, CLEAR OF THE COLUMN, OR NOT AT ALL. Placed by percentage first
   and the cards — which are opaque and sit above it — covered everything except
   two slivers in the gap between two of them. That does not read as atmosphere,
   it reads as a page that failed to paint. Pinned to the left of the 760px
   column instead, and shown only when there is genuinely room beside it. */
.plumbs.faint { opacity: .26; }
.plumbs.faint .plumb.true { left: calc(50% - 470px); }
.plumbs.faint .plumb.off  { left: calc(50% - 432px); }
@media (max-width: 1180px) { .plumbs.faint { display: none; } }
.plumbs.faint .plumb.true .cord,
.plumbs.faint .plumb.off  .cord { height: 130px; }

/* WHAT WOULD RESOLVE IT, beside the control that does. The button names the act
   and this says what the act means — a label alone reads as a guess about which
   side is true, and this page never guesses. */
.dc-act .dc-how { font-size: 13px; color: var(--ink-3); margin-left: 10px; }
@media (max-width: 620px) { .dc-act .dc-how { display: block; margin: 6px 0 0; } }

/* ANYTHING JUMPED TO LANDS BELOW THE STICKY CHROME, NOT UNDERNEATH IT.
   The owner clicked "Say what this row is" on a disagreement, landed on the
   right card, and saw the section BELOW it — because the top bar and the page
   head are both sticky and together cover 152px, and the browser had scrolled
   the target to y=0. Measured, not guessed. Same cause as "the scrolling when
   clicking on the jobs filtering, it jumps around": every in-page link on every
   page landed 152px too high, which reads as the page jumping somewhere random.
   scroll-margin is the property for exactly this and applies to the fragment
   jump AND to scrollIntoView, so the fold-opening script gets it too. */
:target { scroll-margin-top: 168px; }
@media (max-width: 900px) { :target { scroll-margin-top: 150px; } }

/* THE NOTIFICATIONS LIST IS NOT A PLAN ROW, AND IT WAS USING THE PLAN'S GRID.
   `.row` has seven tracks tuned for the plan — 8px stripe, 96px ref, then a
   flexible TITLE. A notification emits a checkbox as its third cell, so every
   cell shifted one track: the tick took the flexible column and the message
   was crammed into the 150px track meant for a plan column, wrapping every two
   or three words with half the row empty beside it. The owner photographed it.
   Its own tracks, so the two tables can never squeeze each other again. */
.row.mrow { grid-template-columns: 8px 96px 34px minmax(0,1fr) 190px 92px 104px; }
/* `.r-area` hides here, so six cells and six tracks. */
@media (max-width: 1180px) { .row.mrow { grid-template-columns: 8px 90px 34px minmax(0,1fr) 92px 104px; } }
/* BELOW THIS IT STACKS RATHER THAN COUNTING TRACKS. Written as a narrower grid
   first and it broke at exactly the width I had not measured: six cells were
   still visible against four tracks, so they wrapped into implicit rows and the
   message column came out 104px — the original bug again, one breakpoint down.
   Counting cells per width is a game you lose the first time a cell's own media
   query disagrees with yours. Stacking cannot squeeze anything. */
@media (max-width: 1000px) {
  .row.mrow { display: block; padding: 12px 15px; }
  .row.mrow > * { display: block; white-space: normal; }
  .row.mrow > .stripe { display: none; }
  .row.mrow > .r-ref { font-size: 12px; margin-bottom: 5px; }
  .row.mrow > .votecell { float: right; margin: -2px 0 0 10px; }
  .row.mrow > .pill, .row.mrow > .r-wait { display: inline-block; margin-top: 6px; }
  .row.mrow > .r-wait { margin-left: 10px; }
}
/* The message column carries several stacked lines, so this row is not the
   single-line list row the plan draws — it must be free to grow. */
.row.mrow { align-items: start; }
.row.mrow > * { white-space: normal; }

/* The report page's agent-question card WAS here, under .agentq / .aq-*. It has
   been merged into the one .qcard both pages now draw — see questionCard() in
   questions.ts and the rules above. Two components for one object is what the
   owner spotted from the outside: "you named it differnt than the items thats
   why its looks different". */

/* ---- THE ROSTER IS THE EDITOR (People and access) ----
   Five scopes per person, changed on the row you are reading rather than in a
   second component further down the page. The selects are dense because there
   are five of them across and the words in them are one or two syllables; the
   row's actions sit in their own cell so Save and Remove never wrap under a
   select and look like they belong to it. */
.rostertbl th { white-space: nowrap; }
.rostertbl td { vertical-align: middle; }
.scopesel {
  width: 100%; min-width: 92px; font-size: 13px; padding: 5px 7px;
  border: 1px solid var(--rule); border-radius: 4px;
  background: var(--panel); color: var(--ink);
}
.scopesel:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.rowacts { white-space: nowrap; text-align: right; }
.rowacts .btn { padding: 5px 11px; font-size: 13px; }
.rowacts .btn + .btn { margin-left: 6px; }

/* ============ THE DOOR — the one dark screen in the product ============
   The owner sent the design tool's version beside mine and asked for the tool's.
   It is dark, centred, and the plumb lines fall from the top of the SCREEN
   through the headline rather than sitting in the margin.

   SCOPED TO .landing.dark AND NOWHERE ELSE. These are the only tokens in the
   stylesheet that are not the console's, and they are redefined as custom
   properties on this one element rather than hard-coded down the block — so
   everything inside inherits the dark world and nothing outside can reach it.
   Written the other way first (overriding colour per rule) and the panel, the
   help text and the fold each needed their own override, which is four places
   to forget one. */
.landing.dark {
  --lg-ground:  #262c46;   /* the deep corner */
  --lg-lift:    #3b4368;   /* the lit corner — a wash, not a spotlight */
  --lg-panel:   rgba(255, 255, 255, .055);
  --lg-edge:    rgba(255, 255, 255, .10);
  --lg-ink:     #eef0f8;
  --lg-ink-2:   #b3bad4;
  --lg-ink-3:   #8f97b6;
  --lg-lit:     #a9b4ff;   /* periwinkle: the accent, on the half nobody expects */
  /* ---- AND THE CONSOLE'S OWN TOKENS, REDEFINED FOR THIS GROUND ----
     So every component that lands inside — a .pane, a .note, a .mono count —
     is dark without one dark-specific rule of its own. This is also the only
     thing that can win against the project cards' INLINE `color: var(--ink)`:
     an inline style beats any selector, but it still resolves the variable
     here. Overriding per component was tried first and lost to exactly that. */
  --ink:     #eef0f8;
  --ink-2:   #b3bad4;
  --ink-3:   #8f97b6;
  --ink-4:   #7d85a4;
  --panel:   rgba(255, 255, 255, .055);
  --ground:  transparent;
  --rule:    rgba(255, 255, 255, .12);
  --rule-2:  rgba(255, 255, 255, .08);
  --accent:  #9aa6ff;
  --accent-tint: rgba(154, 166, 255, .14);
  background: linear-gradient(148deg, var(--lg-lift) 0%, var(--lg-ground) 52%, #21263c 100%);
  color: var(--lg-ink);
  min-height: 100vh; padding: 0 20px;
  place-items: center;
}
/* THE WORDMARK IS PINNED TO THE SCREEN, not stacked above the sentence. In the
   console it is a nav item; here it is a signature in the corner, and putting
   it in the flow pushed the headline off centre by exactly its own height. */
.landing.dark .landing-mark {
  position: absolute; top: 26px; left: 30px; z-index: 2;
  font: 600 12px/1 var(--ui); letter-spacing: .22em; text-transform: uppercase;
  color: var(--lg-ink-3);
}
.landing.dark .landing-in { max-width: 780px; text-align: center; }
/* 780px, MEASURED, NOT CHOSEN. At 620 the sentence broke into four lines with
   "shown — never resolved for" stranded in the middle of the third; the clause
   needs room to sit whole. text-wrap: balance evens what is left. */

/* THE SENTENCE IS THE ONLY BIG THING. clamp() rather than a breakpoint: it is
   two clauses that must not break into four lines on a laptop, and the size at
   which that happens is a function of the viewport, not of a device class. */
.landing.dark h1 {
  font: 650 clamp(26px, 3.6vw, 40px)/1.22 var(--ui);
  letter-spacing: -.022em; color: var(--lg-ink);
  margin: 0 0 18px; text-wrap: balance;
}
.landing.dark h1 .lit { color: var(--lg-lit); }
.landing.dark .positioning {
  font-size: 15px; line-height: 1.6; color: var(--lg-ink-2);
  margin: 0 auto 30px; max-width: 46ch;
}

/* One piece of structure on the whole screen, and the button is the reason for
   it. Full width inside the panel because there is nothing to choose between. */
.landing.dark .signin-panel {
  background: var(--lg-panel); border: 1px solid var(--lg-edge);
  border-radius: 8px; padding: 22px; max-width: 420px; margin: 0 auto;
}
.landing.dark .signin-primary {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  height: 44px; border-radius: 4px; text-decoration: none;
  background: #fff; border: 1px solid #fff; color: #1f2430;
  font: 550 15px/1 var(--ui);
}
.landing.dark .signin-primary .g {
  font: 700 16px/1 var(--ui); color: #4285f4;
}
.landing.dark .signin-primary:hover { background: #f1f3f9; border-color: #f1f3f9; }
.landing.dark .signin-primary:focus-visible {
  outline: 2px solid var(--lg-lit); outline-offset: 3px;
}
.landing.dark .whose {
  font-size: 13px; line-height: 1.55; color: var(--lg-ink-3);
  margin: 14px 0 0; text-align: center;
}
.landing.dark .whose b { color: var(--lg-ink-2); font-weight: 600; }

/* The fold is a foot link here, not a bordered section: on a preview it is the
   way in, and on production it does not render at all. */
.landing.dark .devfold { margin-top: 26px; border: 0; padding: 0; text-align: center; }
.landing.dark .devfold > summary {
  font: 500 13px/1.4 var(--ui); letter-spacing: 0; text-transform: none;
  color: var(--lg-lit);
}
.landing.dark .devfold > summary:hover { color: var(--lg-ink); }
.landing.dark .devfold .note { color: var(--lg-ink-3); font-size: 13px; }
.landing.dark .devfold label { color: var(--lg-ink-2); }

/* ---- THE MOTIF, FALLING THROUGH THE HEADLINE ----
   `hero` is a different placement from the pair used on the project picker, and
   deliberately so. There the two hang SIDE BY SIDE, close enough to compare,
   because that screen has work on it and the motif has to earn its space in one
   glance. Here they flank the sentence and fall the height of the screen: the
   sentence is what you are meant to read, and the lines are the thing it is
   describing happening behind it.

   THEY STILL DISAGREE AND YOU CAN STILL SEE IT. Apart, you cannot compare them
   edge to edge — so the difference is carried by the swing and by the bobs
   coming to rest at different heights, which is legible without a straight edge
   to measure against. */
/* THICKER, AND THERE ARE MORE OF THEM (owner, 2026-08-11).
   The pair was two hairlines at 16% and 30%, tuned on the light console ground
   where anything heavier shouts. On the dark door — where nothing else is on the
   screen — that read as a rendering artefact rather than as the thing the
   product is named after. 2px, roughly double the opacity, and four plain rules
   set well back so the eye reads a row of plumb lines with two in front. */
.plumbs.hero { opacity: 1; }
.plumbs.hero .plumb { top: -10px; }
.plumbs.hero .plumb .cord { width: 2px; }
.plumbs.hero .plumb .bob  { width: 15px; height: 22px; }
.plumbs.hero .plumb.true { left: calc(50% - 285px); }
.plumbs.hero .plumb.off  { left: calc(50% + 215px); }
.plumbs.hero .plumb.true .cord { height: 46vh; background: rgba(255, 255, 255, .34); }
.plumbs.hero .plumb.true .bob  { background: rgba(255, 255, 255, .42); }
.plumbs.hero .plumb.off  .cord { height: 54vh; background: rgba(169, 180, 255, .58); }
.plumbs.hero .plumb.off  .bob  { background: rgba(169, 180, 255, .72); }

/* THE STILL ONES CARRY NO MEANING AND MUST NOT LOOK AS IF THEY DO. No bob, no
   swing, 1px, and faint: they are the room the two hang in. Give one of them a
   weight and the screen is asking which of five is true. */
.plumbs.hero .plumb.still .cord { width: 1px; background: rgba(255, 255, 255, .11); }
.plumbs.hero .plumb.still.s1 { left: calc(50% - 520px); }
.plumbs.hero .plumb.still.s2 { left: calc(50% - 390px); }
.plumbs.hero .plumb.still.s3 { left: calc(50% + 350px); }
.plumbs.hero .plumb.still.s4 { left: calc(50% + 470px); }
.plumbs.hero .plumb.still.s1 .cord { height: 62vh; }
.plumbs.hero .plumb.still.s2 .cord { height: 38vh; }
.plumbs.hero .plumb.still.s3 .cord { height: 70vh; }
.plumbs.hero .plumb.still.s4 .cord { height: 44vh; }
/* Below this the still ones would cross the sentence; the meaningful pair keeps
   its own breakpoint further down. */
@media (max-width: 1320px) { .plumbs.hero .plumb.still { display: none; } }
/* Narrower than this and the lines cross the sentence instead of flanking it. */
@media (max-width: 1080px) { .plumbs.hero { display: none; } }

/* The two notification views sit beside the buttons that act on the ticked
   rows, so the row reads "do this · or look at that" rather than wrapping the
   chips onto a line of their own where they would read as a second heading. */
.nchips { display: inline-flex; gap: 6px; align-items: center; margin-left: 4px; }

/* A bare shell carries no top bar, so it must not reserve the room for one or
   cap its own width — the door is full-bleed and the console is not. */
.appshell.bare { padding: 0; max-width: none; }
.appshell.bare > .shellbody { padding: 0; max-width: none; }


/* ---- THE PROJECT PICKER, ON THE SAME GROUND AS THE DOOR ----
   "sign in is good now make it the same on the porject selector page" — and it
   is the same moment: signed in, still outside every project, nothing on screen
   to work on. Top-aligned rather than centred, because the list has a length and
   a centred list moves every time somebody gains a project. */
.landing.dark.picker { place-items: start center; padding: 84px 20px 60px; }
.pickcol { width: 100%; max-width: 760px; position: relative; z-index: 1; }
.pickcol > h1 { font: 650 clamp(24px, 3vw, 34px)/1.2 var(--ui); letter-spacing: -.02em;
                margin: 0 0 10px; color: var(--lg-ink); }
/* SPECIFICITY, MEASURED. `.pickcol > .positioning` is two classes and loses to
   `.landing.dark .positioning` (three), so the centred `margin: 0 auto` won and
   the subtitle sat 174px to the right of the heading it belongs under. */
.landing.dark.picker .positioning { text-align: left; margin: 0 0 22px;
                                    max-width: 60ch; }
/* A card on this ground: the console's pane, one shade up from the ground, with
   a hairline that is light-on-dark rather than the console's dark-on-light. */
.landing.dark .pane { background: var(--lg-panel); border-color: var(--lg-edge); }
.landing.dark .pane:hover { border-color: rgba(255, 255, 255, .22); }
.landing.dark .banner { background: rgba(255, 255, 255, .05); border-color: var(--lg-edge);
                        color: var(--lg-ink-2); }
.landing.dark .banner.ok { background: rgba(154, 166, 255, .13); }
.landing.dark .btn { color: #1b2036; }
.landing.dark .btn.ghost { color: var(--lg-ink); border-color: var(--lg-edge);
                           background: transparent; }
.landing.dark .foot { color: var(--lg-ink-3); }
/* The picker's column is 760px, so the lines sit where they can be seen beside
   it rather than through the cards — which are opaque and would cover them. */
.landing.dark.picker .plumbs.hero .plumb.true { left: calc(50% - 470px); }
.landing.dark.picker .plumbs.hero .plumb.off  { left: calc(50% + 432px); }
@media (max-width: 1180px) { .landing.dark.picker .plumbs.hero { display: none; } }

/* ---- IT CAME BACK WITH A QUESTION, SO ANSWERING IS THE WHOLE CONTROL ----
   Tinted, because this is somebody's move and the console's accent tint is what
   it uses to say so everywhere else. It replaces the verdict buttons rather
   than sitting beside them, so it carries the weight they had. */
.turn-asked { border: 1px solid var(--accent); border-radius: 8px;
              background: var(--accent-tint); padding: 13px 15px; margin-top: 8px; }
.turn-asked .ta-h { font: 600 14px/1.4 var(--ui); margin: 0 0 10px; }
.turn-asked .ta-sub { display: block; font-weight: 400; font-size: 13px; color: var(--ink-2);
                      margin-top: 2px; }
.turn-asked .ta-q { display: flex; gap: 9px; align-items: baseline; margin: 0 0 9px;
                    font-size: 14px; }
.turn-asked .ta-q .pill { flex: none; text-decoration: none; }
.turn-asked .btn { padding: 5px 13px; font-size: 13px; }

/* A turn nobody has settled is tinted; the finished ones stay plain. Down a card
   of eight, the one that needs somebody is what everybody is looking for, and it
   used to be drawn exactly like the seven that were done. The left edge takes
   the accent too, so the mark survives at the width where the tint is only a few
   pixels wide beside the text. */
/* Open work is tinted and takes the accent edge — the one turn that needs
   somebody, down a card of eight that all look finished. */
.wturn.live { background: var(--accent-tint); border-color: var(--accent); }

/* ---- THE ONE QUESTION CARD (questions.ts) ----
   The item page's fold and options, the report page's look. It was two
   components under two names for one object; the newer had the design and the
   older had the behaviour. These rules are the merge. */
.qcard { border: 1px solid var(--rule); border-radius: 8px; background: var(--panel);
         padding: 12px 14px; margin: 0 0 10px; }
.qcard.open { border-color: var(--accent); background: var(--accent-tint); }
.qcard:last-child { margin-bottom: 0; }
.qcard .qc-head { grid-template-columns: 9px auto minmax(0, 1fr) auto auto; }
.qc-who { font-size: 13px; color: var(--ink-2); white-space: nowrap; }
.qcard .qc-ref { text-align: center; }
.qcard .answer-form, .qcard .q-answer, .qcard .form-inline,
.qcard .assignbar, .qcard .qc-reopen, .qcard .qc-cant { margin-left: 0; }
.qcard .answer-form textarea { width: 100%; }
.qcard .qc-reopen { margin-top: 8px; }
.qcard .assignbar { margin-top: 9px; }
@media (max-width: 760px) {
  /* Below this the who-and-when line cannot share a row with the summary and
     the state pill without all three being clipped to nothing. */
  .qcard .qc-head { grid-template-columns: 9px auto minmax(0, 1fr) auto; }
  .qc-who { display: none; }
}

/* THE KEY TO THE CARD COLOURS — named only where an example is on screen.
   Three treatments and nothing saying what they were is worse than one
   treatment: a colour reads as information, so an unreadable colour is a
   sentence somebody cannot finish. */
.workkey { display: flex; flex-wrap: wrap; align-items: center; gap: 7px;
           margin: 0 0 10px; font-size: 13px; color: var(--ink-3); }
.wk { width: 11px; height: 11px; border-radius: 3px; display: inline-block;
      margin-right: 5px; vertical-align: -1px; border: 1px solid var(--rule); }
.wk.k-live { background: var(--accent-tint); border-color: var(--accent); }
.wk.k-ship { background: none; border-color: var(--done); border-left-width: 3px; }
.wk.k-done { background: var(--panel); }
.wk-sep { color: var(--ink-4, var(--ink-3)); }

/* THE FILTER CHIP CARRIES ITS ROWS' COLOUR, as a dot rather than a fill. The
   chip already uses its background for "selected" and its ink for "zero"; a
   third meaning on the same surface would make the selected state — the one
   that has to be obvious — the hardest to see. */
.ch-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%;
          margin-right: 6px; vertical-align: 1px; background: var(--ink-3); }
.ch-dot.t-done   { background: var(--done); }
.ch-dot.t-active { background: var(--accent); }
.ch-dot.t-wait   { background: var(--wait); }
.ch-dot.t-idle   { background: var(--ink-3); }
.chip.empty .ch-dot { opacity: .4; }

/* "Talk it through" is a link, not a radio, so the item page has two radios
   against three cards and two panels. The reveal rules match BY INDEX, and the
   report page still has three of each — so these are scoped rather than
   renumbered, or that page's Reply and Note panels would have swapped. */
.whatsnext.talklink .wn-r:nth-of-type(1):checked ~ .wn-panels > .wn-p:nth-child(1),
.whatsnext.talklink .wn-r:nth-of-type(2):checked ~ .wn-panels > .wn-p:nth-child(2) { display: block; }
.whatsnext.talklink .wn-r:nth-of-type(1):checked ~ .wn-picks > .wn-card:nth-child(1),
.whatsnext.talklink .wn-r:nth-of-type(2):checked ~ .wn-picks > .wn-card:nth-child(2),
.whatsnext.talklink .wn-r:nth-of-type(3):checked ~ .wn-picks > .wn-card:nth-child(3) {
  border-color: var(--rule); background: var(--panel);
}
.whatsnext.talklink .wn-r:nth-of-type(1):checked ~ .wn-picks > .wn-card:nth-child(2),
.whatsnext.talklink .wn-r:nth-of-type(2):checked ~ .wn-picks > .wn-card:nth-child(3) {
  border-color: var(--accent); background: var(--accent-tint);
}
/* The link card is a link and says so on hover; it is never "selected". */
a.wn-card { text-decoration: none; color: inherit; }
a.wn-card:hover { border-color: var(--accent); }

/* A TURN THAT FAILED, OR A SHIP THAT STOPPED AT A GATE. Red outranks both the
   settled white and the waiting-on-you tint: a verdict having been recorded
   says nothing about whether the thing worked, and this is the one card on the
   page somebody has to see. */
/* --wait IS THIS CONSOLE'S RED (#b91c1c) and it already has a ground and an
   edge to go with it. Inventing --bad here would have been a fourth red that
   dark mode does not know about — the tokens are redefined per theme and a
   hard-coded hex is not. */
.wturn.failed { border-color: var(--wait-bd); background: var(--wait-bg); }
.wturn.failed .wt-h b { color: var(--wait); }
.wk.k-fail { background: var(--wait-bg); border-color: var(--wait-bd); }

/* THE ACT, ON THE LINE THAT NAMES THE PROBLEM. A finding that states something
   and offers nothing is a finding you scroll past — the owner, on PLB-I2:
   "there is nothing i can do". */
.st-do { margin-left: 10px; white-space: nowrap; font-weight: 600; }
/* A phase carries its own state, so an umbrella answers "how much of this is
   done" without opening four rows. */
.phaserow { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.phaserow > a { flex: 1 1 auto; min-width: 0; }
.phaserow > .pill { flex: none; }

/* A note ON a turn, about the turn — not a banner over the card. It explains
   why the agent's name on this job looks wrong, so it belongs inside the box
   that carries the name. */
.wt-note { font-size: 13px; line-height: 1.5; color: var(--ink-2); margin: 0 0 7px;
           padding: 7px 9px; border-radius: 6px; background: var(--wait-bg);
           border: 1px solid var(--wait-bd); }

/* Who carries the work out, chosen while answering — one act, not two. */
.qc-route { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 9px 0 0; }
.qc-route select { max-width: 320px; }
.qcard .qc-cant { margin: 0 0 9px; }

/* Filing a question as a note is a quieter act than answering it, and sits
   under the answer box rather than beside it — it is what you press when the
   box above does not apply. */
.qc-nq { margin-top: 9px; }
.qc-nq .btn { padding: 4px 11px; font-size: 13px; }

/* ---- ONE NOTE FROM THE NEXT ----
   They were separated by a margin, which is enough for two short notes and
   nothing at all for two long ones: an agent's several-hundred-word finding ran
   straight into the next note with no seam. A card each, and the kind tag keeps
   its colour so an instruction still reads differently from an observation. */
.note-row { border: 1px solid var(--rule); border-radius: 8px; background: var(--panel);
            padding: 11px 13px; margin: 0 0 9px; }
.note-row:last-of-type { margin-bottom: 0; }
.note-row.instruction { border-left: 3px solid var(--accent); }
.note-row.decision    { border-left: 3px solid var(--done); }
.note-open > summary { display: block; cursor: pointer; list-style: none; }
.note-open > summary::-webkit-details-marker { display: none; }
.note-open[open] > summary .note-body { display: none; }
.note-open > summary .opener { display: inline-block; margin-top: 4px; }

/* .qgroup IS GONE, AND ITS RULES WITH IT. The settled questions were a fold
   INSIDE the open list's pane with a section header for a summary; PLB-11 was
   the owner reading that as the list carrying on rather than as a separate
   place. They are now `details.pane.settled-block` after the pane, the same
   component the plan's settled work and the desk's finished reports already
   use, so its summary and caret come from .settled-block / .group-head above.
   Nothing renders .qgroup any more — rules kept "in case" are how a stylesheet
   comes to describe a page that no longer exists. */

/* ---- SETTLED WORK: ONE FOLD PER JOB ----
   Modelled on the answered questions directly below it on the same page, not on
   the finished-chat fold: a chat is one object, and several settled jobs are
   several. Each row says what it concluded before you open it, so coming back
   to find one does not mean opening all of them. */
.settledwork .donejob { border: 1px solid var(--rule); border-radius: 8px;
                        background: var(--panel); margin: 0 0 7px; }
.settledwork .donejob:last-child { margin-bottom: 0; }
.donejob > summary { list-style: none; cursor: pointer; display: grid;
  grid-template-columns: 9px 70px 92px minmax(0, 1fr) auto;
  gap: 9px; align-items: baseline; padding: 9px 12px; font-size: 13px; }
.donejob > summary::-webkit-details-marker { display: none; }
.donejob > summary:hover { background: var(--ground); border-radius: 8px; }
.donejob > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.donejob .caret { color: var(--ink-3); transition: transform .12s ease; }
.donejob:not([open]) > summary .caret { transform: rotate(-90deg); }
.donejob .dj-id { color: var(--ink-3); }
.donejob .dj-kind { color: var(--ink-2); }
.donejob .dj-verdict { font-weight: 560; color: var(--ink); }
.donejob .dj-when { color: var(--ink-3); white-space: nowrap; }
/* The turn inside keeps its own card; it does not need a second border against
   the fold's. */
.donejob > .wturn { border: 0; border-radius: 0; margin: 0;
                    border-top: 1px solid var(--rule-2); }
@media (prefers-reduced-motion: reduce) { .donejob .caret { transition: none; } }
@media (max-width: 700px) {
  /* Four columns will not fit a phone; the verdict is the one that must survive. */
  .donejob > summary { grid-template-columns: 9px auto minmax(0, 1fr); }
  .donejob .dj-kind, .donejob .dj-when { display: none; }
}

/* ===================================================================
   ============ THE COLOUR A REFUSAL ARRIVES IN ============

   There was no error variant of `.banner` anywhere in this codebase, and every
   flash was written `<div class="banner ok">` by hand on twelve pages. `.ok` is
   the green done token, so "You cannot plan work on that project" and "Filed as
   PLB-12" came back looking identical. The tone now travels with the message
   (html.ts) and this is the half you can see.

   IT IS NOT JUST THE BASE `.banner`. The bare class is already the alarm fill
   and it is used for empty states — "Nothing has been sent to you" — which are
   information rather than refusals. A refusal has to be louder than an empty
   list or the distinction it just gained is one nobody can read, so it takes the
   full-strength rule down its edge, the same 2px marker the wait-tinted turn
   cards use.

   NOT RED-ON-RED. `--wait` is a saturated red and `--wait-bg` a pale one; the
   text stays `--ink` so a long refusal — and they are long, because they say
   what would grant the thing — is still prose somebody reads to the end rather
   than a warning they skim.
   =================================================================== */
.banner.no { background: var(--wait-bg); border-left: 2px solid var(--wait);
             padding-left: 11px; }
.landing.dark .banner.no { background: rgba(240, 139, 139, .13);
                           border-left-color: var(--wait); color: var(--lg-ink); }

/* ============ THE STATE, SAID OUT LOUD ============
   The plain-English sentence for the report's status, directly under the head
   and above every card. It used to live only in `data-tip` on the status pill,
   which .tip shows on :hover / :focus-visible — so on a phone, where there is
   no hover, the page never explained its own state. Marty: "how the flip am I
   supposed to know what is going on."

   Sized to be read first and not to shout: it is a statement of fact, not an
   alarm, and the page already has an alarm colour that means something else. */
.state-say {
  margin: 14px 0 0;
  font-size: 17px;
  line-height: 1.45;
  color: var(--ink);
  max-width: 68ch;
}
@media (max-width: 900px) { .state-say { font-size: 18px; } }

/* ============ A DECISION THAT SHOULD NOT LOOK LIKE THE OTHER ONE ============
   "No letter needed" sits beside "Read the letter" on the Waiting to hear
   queue. Two solid buttons side by side would make skipping as easy as sending,
   and skipping is the one that quietly swallows a customer. So the gravity goes
   on reading: a button for that, a plain text link for this. */
.linkish {
  background: none; border: 0; padding: 0; margin: 0;
  font: inherit; font-size: 14px; color: var(--ink-3);
  text-decoration: underline; text-underline-offset: 3px;
  cursor: pointer;
}
.linkish:hover { color: var(--ink-2); }
.linkish:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The letter, on its own screen. It IS the page — read it, tap into it to
   change a word, send it from underneath. No toolbar, no chrome: the moment
   this grows a formatting bar it has become the workbench Marty could not
   read. */
.letterbox {
  width: 100%; min-height: 320px; resize: vertical;
  border: 1px solid var(--rule); border-radius: 8px; background: var(--panel);
  color: var(--ink); padding: 16px 18px;
  font: inherit; font-size: 17px; line-height: 1.6;
}
.letterbox:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.letterto {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 4px 14px;
  font-size: 14px; margin: 0 0 14px;
}
.letterto dt { color: var(--ink-3); }
.letterto dd { margin: 0; }

/* A MOCK-UP IS A FOLD NOW. Each one is a whole page in a frame; two of them
   pushed the turns and the questions — the things actually waiting on somebody
   — several screens down. Summary styled as the header it replaced, so the
   closed state still reads as a titled card rather than a stray line. */
details.mockwrap > summary {
  cursor: pointer; list-style: none;
}
details.mockwrap > summary::-webkit-details-marker { display: none; }
details.mockwrap > summary::before {
  content: "▸"; color: var(--ink-3); margin-right: 7px; font-size: 12px;
}
details.mockwrap[open] > summary::before { content: "▾"; }
details.mockwrap > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
details.mockwrap .o-less { display: none; }
details.mockwrap[open] .o-less { display: inline; }
details.mockwrap[open] .o-more { display: none; }

/* ===================================================================
   NEXT MOVE BOX — COLOUR BY STEP, AND A "NOT STARTED" RED (fixes 1-6).
   The owner wanted the corner pill gone and the #move card itself to
   carry the colour of where the report stands, plus a genuine red for a
   report nobody has picked up. The five move colours reuse the existing
   tone tokens (--wait is the red, --active the amber/orange, --done the
   green, --idle the grey, --accent the calm blue); "agreed / planned"
   gets a violet of its own, --ready, defined once here in both themes.
   =================================================================== */
:root { --ready: #7c3aed; --ready-bg: #f5f3ff; --ready-bd: #ddd6fe; }
@media (prefers-color-scheme: dark) {
  :root { --ready: #c4b5fd; --ready-bg: #211a33; --ready-bd: #3b2f5e; }
}
:root[data-theme="dark"]  { --ready: #c4b5fd; --ready-bg: #211a33; --ready-bd: #3b2f5e; }
:root[data-theme="light"] { --ready: #7c3aed; --ready-bg: #f5f3ff; --ready-bd: #ddd6fe; }

/* The box wears the accent as a thick left border and a faint full tint.
   Higher specificity than the base `.cols .pane > .sec` and later in the
   file, so it wins the background and the border colour cleanly. */
.cols .pane > .sec.move-box { border-left-width: 3px; }
.cols .pane > .sec.move-box.step-none     { border-left-color: var(--wait);    background: var(--wait-bg); }
.cols .pane > .sec.move-box.step-ask      { border-left-color: var(--active);  background: var(--active-bg); }
.cols .pane > .sec.move-box.step-approve  { border-left-color: var(--done);    background: var(--done-bg); }
.cols .pane > .sec.move-box.step-active   { border-left-color: var(--accent);  background: var(--accent-soft); }
.cols .pane > .sec.move-box.step-reporter { border-left-color: var(--idle);    background: var(--idle-bg); }
.cols .pane > .sec.move-box.step-ready    { border-left-color: var(--ready);   background: var(--ready-bg); }
.cols .pane > .sec.move-box.step-done     { border-left-color: var(--done-bd); }

/* THE SENTENCE STARTS NEAR THE TOP (fix 4). The card body's 18px top pad
   plus the paragraph's own top margin left a gap the owner asked about
   ("why is there so much space before the sentence in the box?"). */
#move .card-b { padding-top: 9px; }
#move .card-b > .lead-say:first-child { margin-top: 0; }

/* WHAT THE AGENT WOULD GET (fix 6) — the report's own body and shots,
   shown inside the not-started box as a white inset against the tint. */
.move-handover { margin: 10px 0 14px; padding: 12px 14px; border: 1px solid var(--rule);
  border-radius: 8px; background: var(--panel); }
.move-handover .mh-l { margin: 0 0 6px; font: 600 11.5px/1 var(--ui); letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-hd); }
.move-handover .mh-body { margin: 0; color: var(--ink-2); font-size: 14.5px; white-space: pre-wrap; }
.move-handover .mh-shots { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; align-items: center; }
.move-handover .mh-shot { display: inline-block; width: 72px; height: 72px; border: 1px solid var(--rule);
  border-radius: 6px; overflow: hidden; }
.move-handover .mh-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.move-handover .mh-more { font-size: 13px; color: var(--ink-3); text-decoration: none; }
.move-handover .mh-more:hover { text-decoration: underline; }

/* LIST ROW MOVE PILL — the two new tones the badge can now carry: a blue
   "in process", a violet "agreed — build it", and a red "not started"
   (the amber "your move" is retired). The others already exist above. */
.pill.info  { color: var(--accent); background: var(--accent-soft); border-color: var(--accent-soft); }
.pill.ready { color: var(--ready);  background: var(--ready-bg);    border-color: var(--ready-bd); }
.pill.none  { color: var(--wait);   background: var(--wait-bg);     border-color: var(--wait-bd); }

/* 0132: the latest triage finding, shown in the Next-move handover box */
.move-handover .mh-found { margin: 0 0 10px; padding: 0 0 10px; border-bottom: 1px solid var(--rule); }
.move-handover .mh-fl { display:block; font-size:12px; letter-spacing:.04em; text-transform:uppercase; color: var(--ink-3); margin: 0 0 3px; }

/* 0132: the move badge shows its whole verb, never clipped */
.row .r-move { white-space: nowrap; overflow: visible; text-align: right; }
/* THE MOVE BADGE'S COLUMN IS SIZED TO ITS VERB (owner 08-20: "the approve or
   reject label is still not the full text width"). The shared .row grid gave the
   last cell 92px, so the coloured pill box was capped there and its widest verb,
   "approve or reject", spilled OUTSIDE the box. Only the "What's next" axis shows
   this badge, so only its rows (.pnext) get a grid whose move column is
   max-content — the box grows to the whole verb and, as the last column after
   the 1fr title, sits flush right so the badges still line up. Above 700px only;
   at ≤700 the grid-areas block already gives the pill its own auto cell. r-area
   is hidden ≤1180, so its track is dropped there. */
.row.pnext { grid-template-columns: 8px 96px minmax(0,1fr) 150px max-content; }
@media (max-width: 1180px) { .row.pnext { grid-template-columns: 8px 90px minmax(0,1fr) max-content; } }
@media (max-width: 860px)  { .row.pnext { grid-template-columns: 8px 80px minmax(0,1fr) max-content; } }
.row.pnext .r-move { max-width: none; }

/* THE STRIPE BY MOVE (owner: "why is everything red"). The list stripe reads the
   move tone now, so it needs the two colours the status tones never had: blue for
   in-process (accent) and violet for agreed (ready). none maps to wait in the TS. */
.stripe.info  { background: var(--accent); }
.stripe.ready { background: var(--ready); }

/* READ FULL on the latest finding (owner: "2 lines with no read full"). A
   details/summary, so it opens with scripting off: the clipped preview and a
   Read full toggle sit in the summary; opening hides the preview and reveals the
   whole finding below, and the toggle becomes Show less. */
details.mh-full > summary { list-style: none; cursor: pointer; display: block; }
details.mh-full > summary::-webkit-details-marker { display: none; }
.move-handover .mh-clip { display: block; }
.mh-more-l { display: inline-block; margin-top: 5px; font-size: 12px; font-weight: 600;
  color: var(--accent); }
.mh-more-l::after { content: "Read full \25be"; }
details.mh-full[open] > summary .mh-clip { display: none; }
details.mh-full[open] > summary .mh-more-l::after { content: "Show less \25b4"; }

/* DECIDED: lead the Next move with the option that was chosen and a prominent
   Build it (owner). Keyed to the same violet as the agreed step, so the box, the
   badge and this lead read as one state. */
.chose-lead { margin: 0 0 14px; padding: 12px 14px; border: 1px solid var(--ready-bd);
  border-radius: 10px; background: var(--ready-bg); }
.chose-lead .cl-l { margin: 0 0 4px; font: 600 11.5px/1 var(--ui); letter-spacing: .06em;
  text-transform: uppercase; color: var(--ready); }
.chose-lead .cl-opt { margin: 0 0 11px; font-size: 16px; font-weight: 640; color: var(--ink);
  line-height: 1.4; white-space: pre-wrap; }
.chose-lead .cl-go { margin: 0 0 8px; }

/* =====================================================================
   REPORT SCREEN REDESIGN (feat/report-redesign) — the Claude Design
   "productive humans, not geeks" workspace, ported over the existing markup.
   Rewritten 2026-08-25 after a UX/a11y review: uses TOKENS not hex literals so
   it is theme-aware (dark mode was broken by hardcoded #fff), the hero header
   follows the STATE colour (not always-blue), meta is --ink-3 (4.83:1, not the
   2.54:1 --ink-4), with focus-visible + overflow-wrap + bigger tap targets.
   Scoped to `.rpt-redesign` so no other screen is touched.
   ===================================================================== */
.rpt-redesign { display:block; max-width:960px; margin:20px auto 0; padding:0 24px; }
/* Prose blocks keep a comfortable reading measure even on the wider page — the
   column widens for cards/controls, sentences don't sprawl to 120 chars. */
.rpt-redesign .lead-say, .rpt-redesign .lead-do, .rpt-redesign #thread .msg-body,
.rpt-redesign #reported .prose, .rpt-redesign .note { max-width:76ch; }
.rpt-redesign .colmain, .rpt-redesign .colrail { width:auto; float:none; }
.rpt-redesign .colmain > .pane, .rpt-redesign .colrail > .pane { padding:0; background:none; border:0; box-shadow:none; }

/* Cards */
.rpt-redesign .sec { background:var(--panel); border:1px solid var(--rule); border-radius:12px;
  margin:0 0 20px; box-shadow:none; overflow:hidden; }
.rpt-redesign .sec > .sec-h, .rpt-redesign .sec .sec-h {
  padding:13px 20px; border-bottom:1px solid var(--rule); background:var(--panel);
  font:640 15.5px/1.3 var(--ui); letter-spacing:-.005em; color:var(--ink); text-transform:none; }
.rpt-redesign .sec .sec-h .ch-m { font:400 13px/1.4 var(--ui); color:var(--ink-3); letter-spacing:0; }
.rpt-redesign .card-b, .rpt-redesign .sec .card-b { padding:16px 20px; }

/* Buttons — accent primary, quiet ghost, >=40px tall, visible focus */
.rpt-redesign .btn { background:var(--accent); border:1px solid var(--accent); color:#fff;
  border-radius:8px; padding:11px 18px; font:600 15px/1 var(--ui); box-shadow:none; min-height:40px; }
.rpt-redesign .btn.ghost { background:var(--panel); color:var(--ink-2); border:1px solid var(--rule); }
.rpt-redesign .btn:focus-visible, .rpt-redesign a:focus-visible,
.rpt-redesign textarea:focus-visible, .rpt-redesign input:focus-visible,
.rpt-redesign select:focus-visible, .rpt-redesign summary:focus-visible {
  outline:2px solid var(--accent); outline-offset:2px; }

/* Inputs */
.rpt-redesign textarea, .rpt-redesign input[type=text], .rpt-redesign select {
  background:var(--panel-2); border:1px solid var(--rule); border-radius:9px; padding:11px 13px;
  font:400 15.5px/1.5 var(--ui); color:var(--ink); }
.rpt-redesign .field > label, .rpt-redesign .lbl {
  font:600 12px/1.4 var(--ui); letter-spacing:.02em; color:var(--ink-3); text-transform:none; }

/* THE HERO — header + left border in the STATE colour (theme-safe: state-colour
   text on the state tint, strong left border), so the box carries the colour and
   the at-a-glance red/amber/green/blue/grey scan works in both themes. */
.rpt-redesign .move-box { border:1px solid var(--rule); border-left-width:4px;
  border-radius:14px; box-shadow:none; background:var(--panel); }
.rpt-redesign .move-box .card-b { background:var(--panel); }
.rpt-redesign .move-box > .sec-h { border-bottom:0; font-weight:660; }
.rpt-redesign .move-box.step-none     { border-left-color:var(--wait); }
.rpt-redesign .move-box.step-none     > .sec-h { background:var(--wait-bg);    color:var(--wait); }
.rpt-redesign .move-box.step-ask      { border-left-color:var(--active); }
.rpt-redesign .move-box.step-ask      > .sec-h { background:var(--active-bg);  color:var(--active); }
.rpt-redesign .move-box.step-approve  { border-left-color:var(--done); }
.rpt-redesign .move-box.step-approve  > .sec-h { background:var(--done-bg);    color:var(--done); }
.rpt-redesign .move-box.step-active   { border-left-color:var(--accent); }
.rpt-redesign .move-box.step-active   > .sec-h { background:var(--accent-soft);color:var(--accent); }
.rpt-redesign .move-box.step-reporter { border-left-color:var(--idle); }
.rpt-redesign .move-box.step-reporter > .sec-h { background:var(--idle-bg);    color:var(--idle); }
.rpt-redesign .move-box.step-ready    { border-left-color:var(--ready); }
.rpt-redesign .move-box.step-ready    > .sec-h { background:var(--ready-bg);   color:var(--ready); }
.rpt-redesign .move-box.step-done     { border-left-color:var(--done-bd); }
.rpt-redesign .move-box.step-done     > .sec-h { color:var(--ink-2); }
.rpt-redesign .move-box .sec-h .ch-m { color:inherit; opacity:.72; }
.rpt-redesign .move-box .lead-say, .rpt-redesign .move-box .lead-do { font-size:15.5px; line-height:1.5; color:var(--ink); }

/* Report details — the rail folded into one quiet collapsible at the bottom */
.rpt-redesign .colrail { margin:0; }
.rpt-redesign .report-details { border:0; background:none; margin-top:2px; }
.rpt-redesign .report-details > summary {
  list-style:none; cursor:pointer; display:inline-flex; align-items:center; gap:7px;
  font:400 13px/1.4 var(--ui); color:var(--ink-3); padding:8px 2px; min-height:24px; }
.rpt-redesign .report-details > summary::-webkit-details-marker { display:none; }
.rpt-redesign .report-details .rd-caret { font-size:11px; transition:transform .12s; }
.rpt-redesign .report-details[open] .rd-caret { transform:rotate(90deg); }
.rpt-redesign .report-details > .pane { padding-top:10px; }
.rpt-redesign .report-details .sec { border-color:var(--rule); }

/* THE TRAIL — the exchange as a dot timeline */
.rpt-redesign #thread .card-b { position:relative; padding-left:34px; }
.rpt-redesign #thread .card-b::before { content:""; position:absolute; left:15px; top:4px;
  bottom:8px; width:2px; background:var(--rule); }
.rpt-redesign #thread .msg { position:relative; background:none; border:0; border-radius:0;
  padding:0 0 18px 0; margin:0; box-shadow:none; }
.rpt-redesign #thread .msg::before { content:""; position:absolute; left:-27px; top:5px;
  width:9px; height:9px; border-radius:50%; background:var(--panel); border:2px solid var(--ink-3); }
.rpt-redesign #thread .msg.us::before { border-color:var(--active); }
.rpt-redesign #thread .msg-top, .rpt-redesign #thread .msg-head { margin-bottom:2px; }
.rpt-redesign #thread .msg-who { color:var(--ink); font:600 14px/1.4 var(--ui); }
.rpt-redesign #thread .msg-body { font:400 15px/1.55 var(--ui); color:var(--ink-2); }
.rpt-redesign #thread .msg:last-child { padding-bottom:0; }

/* Long strings never force sideways scroll (the phone-reading owner) */
.rpt-redesign .msg-body, .rpt-redesign .wt-link a, .rpt-redesign .set dd,
.rpt-redesign .mono, .rpt-redesign .tr-body { overflow-wrap:anywhere; word-break:break-word; }
.rpt-redesign .set dd { max-width:100%; }

/* Quiet context; comfortable prose measure on wide screens */
.rpt-redesign #reported .sec-h { color:var(--ink-3); }
.rpt-redesign .lead-say, .rpt-redesign .lead-do, .rpt-redesign .msg-body { max-width:62ch; }
.rpt-redesign .mono { font-family:var(--ui); }

/* ---- REDESIGN stage 3 (UX review): bring the actions up ----
   The section cards are direct children of the column, so flex-order relocates
   them with zero markup change (forms untouched): hero, then the two action
   zones (close it, push it forward), then the answer editor, then the work, the
   trail, and 'what was reported' last before the collapsible details. This is
   the efficiency review's #1 — acting no longer means scrolling past the trail. */
.rpt-redesign .colmain > .pane { display:flex; flex-direction:column; }
.rpt-redesign .colmain > .pane > * { order:5; }
.rpt-redesign #move          { order:1; }
.rpt-redesign #bench         { order:2; }
.rpt-redesign #mark-shipped  { order:3; }
.rpt-redesign #close-it      { order:4; }
.rpt-redesign #whats-next    { order:5; }
.rpt-redesign #answer-editor { order:6; }
.rpt-redesign #agent-asked, .rpt-redesign #turn-into-work,
.rpt-redesign #work, .rpt-redesign #shots { order:6; }
.rpt-redesign #reported      { order:7; }
.rpt-redesign #thread        { order:8; }

/* Header "Sections" dropdown — lateral nav for screens without the sidebar
   (native <details>, theme-aware tokens). */
.tb-sections { position:relative; }
.tb-sections > summary { list-style:none; cursor:pointer; font:500 14px/1 var(--ui);
  color:var(--ink-3); padding:6px 9px; border-radius:6px; display:inline-flex; align-items:center; gap:5px; }
.tb-sections > summary::after { content:"▾"; font-size:11px; }
.tb-sections > summary::-webkit-details-marker { display:none; }
.tb-sections > summary:hover, .tb-sections[open] > summary { color:var(--ink); background:var(--panel-2); }
.tb-sections > summary:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
.tb-sections .menu { position:absolute; top:calc(100% + 5px); left:0; z-index:30;
  background:var(--panel); border:1px solid var(--rule); border-radius:9px;
  box-shadow:0 6px 24px rgba(0,0,0,.14); padding:6px; min-width:186px; display:flex; flex-direction:column; }
.tb-sections .menu a { padding:8px 12px; border-radius:6px; color:var(--ink);
  font:400 14.5px/1.3 var(--ui); text-decoration:none; }
.tb-sections .menu a:hover { background:var(--accent-tint); text-decoration:none; }

/* Click-to-copy the report reference (it is the string quoted all day). */
.ref-copy { background:none; border:0; padding:1px 4px; margin:0 -2px; cursor:pointer;
  color:inherit; font:inherit; border-radius:4px; }
.ref-copy:hover { background:var(--panel-2); }
.ref-copy:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
.ref-copy.copied::after { content:" copied"; color:var(--done); font-size:.88em; letter-spacing:.02em; }

.mc-ship-facts { display:grid; gap:8px; margin:12px 0; }
.mc-ship-facts div { display:grid; gap:3px; }
.mc-ship-facts dt { color:var(--muted); font-size:.84rem; text-transform:uppercase; letter-spacing:.04em; }
.mc-ship-facts dd { margin:0; overflow-wrap:anywhere; }
.mc-ship-preview + .mc-ship-preview { border-top:1px solid var(--line); margin-top:16px; padding-top:16px; }
.mc-ship-preview .mc-btn { display:inline-block; margin-top:8px; }
.mc-ship-confirm .mc-run-control { display:flex; align-items:center; gap:10px; }

/* VISUAL COMPARE: server-rendered panes are the no-JS product; image pairs are
   progressively enhanced into one keyboard-operable overlay. */
.mc-compare { min-width:0; max-width:100%; overflow:hidden; margin:18px 0; }
.mc-compare-pick { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr) auto;
  gap:10px; align-items:end; margin:12px 0; }
.mc-compare-pick label { display:grid; gap:5px; min-width:0; }
.mc-compare-pick select { width:100%; min-width:0; }
.mc-compare-fallback { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px;
  max-width:100%; overflow:auto; }
.mc-compare-fallback.mc-compare-enhanced-hidden { display:none; }
.mc-compare-pane { margin:0; min-width:0; overflow:auto; border:1px solid var(--line); border-radius:8px; }
.mc-compare-pane figcaption { padding:8px; overflow-wrap:anywhere; }
.mc-compare-pane img,.mc-compare-pane iframe { display:block; width:100%; max-width:100%; height:auto; max-height:320px; border:0; object-fit:contain; }
.mc-compare-pane iframe { min-height:240px; max-height:320px; }
.mc-compare-wipe { position:relative; width:min(100%,1000px); max-width:100%; max-height:320px; overflow:auto;
  border:1px solid var(--line); border-radius:8px; touch-action:none; }
.mc-compare-wipe[hidden] { display:none; }
.mc-compare-wipe > img,.mc-compare-after img { display:block; width:100%; max-width:100%; height:auto; }
.mc-compare-after { position:absolute; inset:0; overflow:hidden; clip-path:inset(0 50% 0 0); }
.mc-compare-after img { width:100%; max-width:100%; height:auto; }
.mc-compare-handle { position:absolute; z-index:2; top:0; bottom:0; left:50%; width:4px; min-height:100%;
  padding:0; border:0; background:var(--accent); transform:translateX(-50%); cursor:ew-resize; }
.mc-compare-handle:focus-visible { outline:3px solid var(--accent); outline-offset:3px; }
.mc-compare-label { position:absolute; z-index:3; top:8px; padding:5px 8px; border-radius:5px;
  background:var(--panel); color:var(--ink); font-size:13px; }
.mc-compare-before { left:8px; }
.mc-compare-after-label { right:8px; }
@media (max-width:600px) {
  .mc-compare-pick,.mc-compare-fallback { grid-template-columns:minmax(0,1fr); }
}

