/* The console's shell — built to the twelve approved mockups of 27/08/2026.
 *
 * BOTH PALETTES EXIST FROM DAY ONE, on purpose. Settings offers Light / Dark / System as a
 * per-device preference (page 12 of the spec, and the mockup that added it). The README's line
 * is that this is "cheap now, expensive retrofitted" — so every colour below is a token, and
 * dark is a token swap rather than a second stylesheet.
 *
 * Hamish generated LIGHT, so light is the default and the dark recommendation is closed. The job
 * the dark shell was going to do — never mistaking the console for a client's portal — is done by
 * the "Fleet running" pill and a completely different nav, which is what the README settled on.
 */

:root {
  --bg: #ffffff;
  --card: #ffffff;
  --sidebar: #ffffff;
  --line: #e5e9f0;
  --ink: #0a203f;          /* deep navy — headings and primary text */
  --ink-2: #5b6b85;        /* secondary */
  --ink-3: #8a99b0;        /* tertiary / timestamps */
  --accent: #2563eb;
  --accent-soft: #eff4ff;
  --green: #16a34a;
  --green-soft: #ecfdf3;
  --amber: #d97706;
  --amber-soft: #fffaeb;
  --red: #dc2626;
  --red-soft: #fef3f2;
  --grey-soft: #f4f6fa;
  --shadow: 0 1px 2px rgba(10, 32, 63, .06), 0 4px 12px rgba(10, 32, 63, .04);
  --radius: 14px;

  /* THE SIDEBAR WIDTH, DECIDED 31/08/2026 (REG-S18-7), AND IT IS OURS, NOT A CANVAS'S.
   *
   * It was 258px because home.png measures 257 — a number lifted off whichever reference
   * somebody opened first. Every other canvas disagrees: eighteen references carry FOURTEEN
   * distinct sidebar edges between 247 and 305, so there was never one value to copy.
   *
   * So it was MEASURED across the whole board instead of argued, at four candidate widths
   * (tools/design-showcase/measure-all.py, 18 screens each):
   *
   *     width   Σregions off   Σdelta   worse   better
   *      243        230        160.55     4       5      the content-derived minimum
   *      258        230        160.06     —       —      current
   *      272        228        159.76     2       4      the phone drawer's value
   *      284        225        159.51     3       6      the biggest reference cluster
   *
   * A 41px span moves the whole eighteen-screen board by 1.04 of 160 — 0.65% — on an
   * instrument whose noise floor is about 9 for a single screen. NO CANDIDATE IS FREE:
   * every one of them makes some screens worse, because the canvases were never on one grid.
   *
   * THE DECISION: there is no grid to find, because the sidebar is not a grid-derived
   * dimension. It is set by the only fixed-size thing in it — the brand lockup, measured live
   * at 167x44 sitting at a 38px inset, so committed to x=205 — and every width that clears
   * that is equally correct to the compare. Given the choice is measurably free, the tie-break
   * is stability: 258px stays, because moving a value shared by eighteen screens to buy 0.65%
   * spends risk for nothing. 243 was the one principled alternative (symmetric 38px insets
   * around the lockup) and it MEASURED WORST — the derivation was tested, not assumed.
   *
   * What changed is that the number is now written ONCE. It was in three places and two of
   * them disagreed. Do not re-point this at a reference; that argument is closed, in
   * design/reference/STATUS.md, with the numbers above. */
  --side-w: 258px;
}

:root[data-theme="dark"] {
  --bg: #0b1c33;
  --card: #12294a;
  --sidebar: #0a203f;
  --line: #1e3a63;
  --ink: #f2f6fc;
  --ink-2: #a9bcd8;
  --ink-3: #7f95b5;
  --accent: #21a9ee;
  --accent-soft: #12314f;
  --green: #2ecc71;
  --green-soft: #10331f;
  --amber: #f2c14e;
  --amber-soft: #33290f;
  --red: #e8664f;
  --red-soft: #38190f;
  --grey-soft: #0f2440;
  --shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 4px 12px rgba(0, 0, 0, .2);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0b1c33; --card: #12294a; --sidebar: #0a203f; --line: #1e3a63;
    --ink: #f2f6fc; --ink-2: #a9bcd8; --ink-3: #7f95b5;
    --accent: #21a9ee; --accent-soft: #12314f;
    --green: #2ecc71; --green-soft: #10331f;
    --amber: #f2c14e; --amber-soft: #33290f;
    --red: #e8664f; --red-soft: #38190f;
    --grey-soft: #0f2440;
    --shadow: 0 1px 2px rgba(0,0,0,.3), 0 4px 12px rgba(0,0,0,.2);
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 15px/1.5 "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}

/* Accessibility group on Settings drives these two. Reduce motion is honoured by the
 * OS setting as well, because an animation nobody asked for is the one that hurts. */
:root[data-motion="reduce"] *, :root[data-motion="reduce"] *::before {
  animation: none !important; transition: none !important;
}
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* ---------------------------------------------------------------- shell */
.shell { display: flex; min-height: 100vh; }

.side {
  width: var(--side-w); flex: 0 0 var(--side-w); background: var(--sidebar);
  border-right: 1px solid var(--line); padding: 24px 16px 18px;
  display: flex; flex-direction: column; position: sticky; top: 0; height: 100vh;
}
/* The real lockup, as in the mockups — mark plus wordmark, not a CSS square plus text. */
/* 🚨 MEASURED OFF design/reference/home.png, NOT CHOSEN. The logo there occupies
   x 38-204 (167 wide) and y 24-67 (44 tall). It was 30px and starting at x=16, which is
   why it read as a different logo at a glance even though it is the same file. */
.brand-img { height: 44px; width: auto; display: block }
:root[data-theme="dark"] .brand-img,
.side .brand-img.on-dark { filter: brightness(0) invert(1) }
.brand { display: flex; align-items: center; gap: 10px; padding: 0 8px 30px 22px; }
.brand-mark {
  width: 30px; height: 30px; border-radius: 9px;
  background: linear-gradient(135deg, #2563eb, #22d3ee); flex: 0 0 30px;
}
.brand-name { font-weight: 700; font-size: 19px; letter-spacing: -.02em; }

.nav { display: flex; flex-direction: column; gap: 2px; }
/* Nav row pitch measured at 55px in the reference; it was 44px, which stacked eleven items
   ~120px higher up the page than the design and threw every vertical alignment below it. */
.nav { gap: 4px }
.nav a {
  display: flex; align-items: center; gap: 13px; padding: 15px 13px;
  border-radius: 11px; color: var(--ink); text-decoration: none; font-size: 15px;
}
.nav a .ico { width: 20px; height: 20px; flex: 0 0 20px; color: var(--ink-2) }
.nav a.on .ico { color: var(--accent) }
.nav-foot { margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--line) }
.topbar .ti .ico { width: 20px; height: 20px }
.nav a:hover { background: var(--grey-soft); }
.nav a.on { background: var(--accent-soft); color: var(--accent); font-weight: 600; }

.nav .spacer { flex: 1; }

/* The pill the portal does not have. It is the glance-test that tells the two apart. */
.fleet-pill {
  margin-top: auto; display: flex; align-items: center; gap: 9px;
  border: 1px solid var(--line); border-radius: 999px; padding: 10px 14px;
  background: var(--card); font-size: 14px; width: 100%; cursor: pointer; color: var(--ink);
}
.fleet-pill .dot { width: 9px; height: 9px; border-radius: 50%; background: var(--green); }
.fleet-pill.stopped .dot { background: var(--red); }
.fleet-pill.stopped { color: var(--red); border-color: var(--red); }

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.topbar { display: flex; justify-content: flex-end; align-items: center; gap: 18px; padding: 18px 30px 0; }
.topbar .ti { color: var(--ink-2); font-size: 17px; cursor: pointer; background: none; border: 0; }
.avatar {
  width: 34px; height: 34px; border-radius: 50%; background: var(--accent-soft);
  color: var(--accent); display: grid; place-items: center; font-weight: 600; font-size: 13px;
}
.page { padding: 20px 30px 46px; }
h1 { font-size: 34px; letter-spacing: -.02em; margin: 4px 0 18px; }

/* ---------------------------------------------------------------- cards */
.card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
}
.card-h { display: flex; align-items: center; gap: 10px; padding: 18px 22px 0; }
.card-h h2 { font-size: 21px; margin: 0; letter-spacing: -.01em; }
.badge {
  background: var(--accent); color: #fff; border-radius: 999px;
  padding: 1px 9px; font-size: 13px; font-weight: 600;
}
.badge.zero { background: var(--grey-soft); color: var(--ink-3); }

.muted { color: var(--ink-2); }
.tiny { font-size: 13px; color: var(--ink-3); }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; }

.btn {
  background: var(--accent); color: #fff; border: 0; border-radius: 9px;
  padding: 9px 17px; font-size: 14px; font-weight: 600; cursor: pointer;
  font-family: inherit;
}
.btn.ghost { background: transparent; color: var(--ink-2); }
.btn.danger-o {
  background: transparent; color: var(--red); border: 1px solid var(--red);
}

.pill { border-radius: 999px; padding: 3px 11px; font-size: 13px; font-weight: 500; }
.pill.green { background: var(--green-soft); color: var(--green); }
.pill.amber { background: var(--amber-soft); color: var(--amber); }
.pill.red   { background: var(--red-soft);   color: var(--red); }
.pill.grey  { background: var(--grey-soft);  color: var(--ink-3); }
.pill.blue  { background: var(--accent-soft);color: var(--accent); }

.dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; flex: 0 0 9px; }
.dot.green { background: var(--green); } .dot.amber { background: var(--amber); }
.dot.red { background: var(--red); }     .dot.grey  { background: var(--ink-3); }
.dot.blue { background: var(--accent); }

/* A real flickable on/off switch (04/09/2026, the deploy-freeze toggle — Hamish's own ask:
 * "there should be a switch, like, on and off switch," not a text button pretending to be one).
 * Plain HTML checkbox underneath so it is keyboard-and-screen-reader operable for free; the
 * checkbox itself is invisible and the .track/.knob pair is what is actually seen. */
.switch { position: relative; display: inline-block; width: 44px; height: 24px; flex: 0 0 44px; }
.switch input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; z-index: 1; }
.switch .track {
  position: absolute; inset: 0; background: var(--grey-soft); border-radius: 999px;
  transition: background .15s ease;
}
.switch .knob {
  position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 3px rgba(10,32,63,.35); transition: transform .15s ease;
}
.switch input:checked ~ .track { background: var(--red); }
.switch input:checked ~ .knob { transform: translateX(20px); }
.switch input:focus-visible ~ .track { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------------------------------------------------------------- filter pills */
.filters { display: flex; gap: 10px; flex-wrap: wrap; margin: 0 0 18px; }
.filters button {
  border: 1px solid var(--line); background: var(--card); color: var(--ink);
  border-radius: 999px; padding: 8px 17px; font-size: 14px; cursor: pointer;
  font-family: inherit;
}
.filters button.on { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); font-weight: 600; }

/* ---------------------------------------------------------------- the loud order
 * "A failure does not get a badge. A red state TAKES OVER its card." — page 1 of the spec.
 * That is why these are card-level modifiers and not a small coloured icon somewhere. */
.card.is-red   { border-color: var(--red);   background: var(--red-soft); }
.card.is-amber { border-color: var(--amber); background: var(--amber-soft); }
/* 🚨 ADDED 08/09/2026, and it was MISSING while being used. `renderSignoffQueue` in
 * tickets.html was written on 07/09 emitting `card is-green` for an approved gate. Both
 * --green and --green-soft existed, this rule did not, so an approved sign-off rendered as an
 * ordinary uncoloured card with no error in the console and nothing to notice. A CSS modifier
 * nothing defines is a silent no-op, which is the whole reason
 * tools/go-live/test-console-emails-tab.js now asserts every colour class a renderer emits is
 * actually defined here. */
.card.is-green { border-color: var(--green); background: var(--green-soft); }

.banner {
  display: flex; align-items: center; gap: 14px; padding: 14px 22px;
  background: var(--red); color: #fff; font-weight: 600;
}
.banner .btn { background: transparent; border: 1px solid #fff; color: #fff; }

/* ---------------------------------------------------------------- modal */
.scrim {
  position: fixed; inset: 0; background: rgba(10, 32, 63, .45);
  display: grid; place-items: center; z-index: 50; padding: 20px;
}
.scrim[hidden] { display: none; }
.modal {
  background: var(--card); border-radius: var(--radius); box-shadow: 0 20px 60px rgba(10,32,63,.3);
  width: min(520px, 100%); padding: 26px;
}
.modal h3 { margin: 0 0 10px; font-size: 21px; }
.modal input[type=text] {
  width: 100%; padding: 11px 13px; border: 1px solid var(--line); border-radius: 9px;
  font: inherit; background: var(--bg); color: var(--ink); margin: 14px 0 20px;
}
.modal .row { display: flex; justify-content: flex-end; gap: 10px; }

/* ---------------------------------------------------------------- drawer */
.drawer {
  position: fixed; top: 0; right: 0; height: 100vh; width: min(430px, 92vw);
  background: var(--card); border-left: 1px solid var(--line);
  box-shadow: -8px 0 30px rgba(10,32,63,.12); z-index: 60; overflow-y: auto; padding: 24px;
}
.drawer[hidden] { display: none; }
.kv { display: flex; justify-content: space-between; gap: 16px; padding: 10px 0; border-bottom: 1px solid var(--line); }
.kv dt { color: var(--ink-2); margin: 0; }
.kv dd { margin: 0; text-align: right; font-weight: 500; }

/* ---------------------------------------------------------------- honest empty state */
.empty { text-align: center; padding: 46px 20px; color: var(--ink-2); }
.empty h3 { color: var(--ink); margin: 0 0 6px; font-size: 19px; }

table { width: 100%; border-collapse: collapse; }
th { text-align: left; font-size: 13px; color: var(--ink-2); font-weight: 500; padding: 10px 14px; }
td { padding: 12px 14px; border-top: 1px solid var(--line); font-size: 14px; }

/* ---- the "Needs you" row (home.html queueRow) ----------------------------------------------
   Moved out of inline styles 31/08/2026 so a media query can reach it. See the note in
   queueRow(): inline `display:flex` with a fixed 230px path column put the button on top of the
   title on a phone, and nothing in a stylesheet can override an inline style's layout. */
.qrow { display: flex; align-items: center; gap: 14px; padding: 14px 22px; border-top: 1px solid var(--line); }
.qrow-ico { width: 36px; height: 36px; border-radius: 10px; flex: 0 0 36px; display: grid; place-items: center; }
.qrow-main { flex: 1; min-width: 0; }
.qrow-title { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qrow-where { flex: 0 0 auto; max-width: 230px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qrow-btn { flex: 0 0 auto; }

/* ---- THE PHONE ------------------------------------------------------------------------------
 *
 * 🚨 THIS BLOCK REPLACED A THREE-LINE ONE THAT MADE THINGS WORSE, 31/08/2026.
 *
 * What was here:
 *     @media (max-width: 900px) { .side { position: static; width: 100%; } .shell { column } }
 *
 * That turns the sidebar into a FULL-WIDTH BLOCK STACKED ON TOP of the page. On a 390px
 * iPhone the first screenful is eleven nav rows, the fleet pill and the topbar icons, and the
 * page's own heading starts somewhere around 1400px down. It was the only mobile rule in the
 * stylesheet. Everything else — 30px page padding, 34px headings, every multi-column grid —
 * had no mobile handling at all, so cards sat in ~150px columns wrapping one word per line.
 *
 * It shipped that way because the console was made INSTALLABLE on a phone without anyone ever
 * looking at it on a phone. The fixture that reproduces it, and now proves this fix, is
 * tools/admin-panel/scripts/mobile-fixture.html (never ships; scripts/ is excluded).
 *
 * The shape below: the sidebar becomes an off-canvas drawer, a fixed app bar carries the way in
 * and the way back, and everything that was a grid becomes one column.
 */
@media (max-width: 860px) {

  /* THE DRAWER. Off-canvas by default, slid in over the page. `visibility` is animated with it
     rather than left visible-but-offscreen, so a closed drawer takes no hit-area and cannot be
     reached by a stray swipe or by tab-focus landing inside it. */
  .side {
    position: fixed; top: 0; left: 0; bottom: 0; z-index: 60;
    /* 🚨 DELIBERATELY NOT var(--side-w), and this is the second half of the REG-S18-7 decision.
       A drawer OVERLAYING the page and a column SHARING the viewport are different problems: the
       drawer costs the page nothing, so it can afford the wider touch targets a thumb wants, and
       84vw caps it on a small phone. It read as drift because nothing said so — one value in the
       stylesheet, one here, and a comment naming a third that had not existed since 31/08. There
       are no phone references in design/reference/, so changing this would be an unmeasured
       change to a layout with nothing to measure it against. It stays 272, on purpose. */
    width: 272px; max-width: 84vw; flex: none; height: 100dvh;
    transform: translateX(-100%); visibility: hidden;
    transition: transform .22s ease, visibility .22s;
    overflow-y: auto; -webkit-overflow-scrolling: touch;
    border-right: 1px solid var(--line);
    /* The notch and the home indicator. Without these the brand row sits under the status bar in
       standalone mode, which is where an installed app spends its whole life. */
    padding-top: calc(18px + env(safe-area-inset-top));
    padding-bottom: calc(16px + env(safe-area-inset-bottom));
  }
  body.side-open .side { transform: translateX(0); visibility: visible; }

  .side-scrim {
    position: fixed; inset: 0; z-index: 55; background: rgba(6, 18, 36, .48);
    opacity: 0; pointer-events: none; transition: opacity .22s ease;
  }
  body.side-open .side-scrim { opacity: 1; pointer-events: auto; }
  body.side-open { overflow: hidden; }

  /* THE APP BAR. Fixed, because in standalone there is no browser chrome to fall back on: if it
     scrolled away, a page scrolled down would have no way out at all. */
  .mbar {
    display: flex; align-items: center; gap: 10px; position: fixed; z-index: 50;
    top: 0; left: 0; right: 0; height: calc(54px + env(safe-area-inset-top));
    padding: env(safe-area-inset-top) 8px 0;
    background: var(--card); border-bottom: 1px solid var(--line);
  }
  .mbar-btn {
    border: 0; background: none; color: var(--ink); font-size: 21px; line-height: 1;
    padding: 10px 12px; border-radius: 10px; cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .mbar-btn:active { background: var(--grey-soft); }
  .mbar-title {
    font-weight: 650; font-size: 17px; letter-spacing: -.01em; flex: 1;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }

  .shell { flex-direction: column; }
  .main { padding-top: calc(54px + env(safe-area-inset-top)); }

  /* The desktop topbar's icons are all in the app bar now. Leaving both is two rows of the same
     three controls, which is what the first screenshot showed. */
  .topbar { display: none; }

  .page {
    padding: 16px 16px calc(40px + env(safe-area-inset-bottom));
    padding-left: max(16px, env(safe-area-inset-left));
    padding-right: max(16px, env(safe-area-inset-right));
  }
  h1 { font-size: 24px; margin: 2px 0 12px; }
  h2 { font-size: 17px; }
  .page-h { flex-wrap: wrap; gap: 10px; }

  /* EVERY multi-column layout in this stylesheet, collapsed in one place. Listing them here
     rather than beside each component is deliberate: a new two-column layout added later will
     be wrong on a phone until it is added, and one list is one place to look. */
  .cols2, .cols3, .tasks-layout, .appr-cols, .refinery-cols, .trace-cols, .map-cols,
  .client-card, .home-cols, .grid2, .grid3 {
    grid-template-columns: 1fr !important;
  }
  .cc-next { border-left: 0; padding-left: 0; }

  .card { padding: 16px 16px; }

  /* THE ROW THAT OVERLAPPED. Icon + title + a 230px path + a button cannot fit in 375px, so the
     button was drawn over the title. The path goes (it is the widest thing in the row and the
     least useful on a phone) and the button drops onto its own line at full width, where it is
     also a proper touch target instead of something to aim at. */
  .qrow { flex-wrap: wrap; padding: 14px 16px; gap: 10px 12px; }
  .qrow-where { display: none; }
  .qrow-btn { width: 100%; }
  .qrow-title { white-space: normal; }

  .row-line { flex-wrap: wrap; gap: 8px; }
  .btn { min-height: 44px; }              /* Apple's minimum touch target */
  .btn.sm { min-height: 36px; }
  /* A pill is a label, not a paragraph. Wrapping "window-closing" onto two lines inside a rounded
     chip makes it read as broken. */
  .pill { white-space: nowrap; }

  /* Wide content scrolls INSIDE its own box. Without this a single long path or a four-column
     table widens the whole document and the entire page scrolls sideways, which on a phone
     reads as the layout being broken. */
  table { display: block; overflow-x: auto; white-space: nowrap; -webkit-overflow-scrolling: touch; }
  pre, .mono-block { overflow-x: auto; }
  .mono { word-break: break-word; }
}

/* The drawer and app bar are MOBILE-ONLY furniture. Above the breakpoint the sidebar is the
   navigation and both must be absent, not merely behind something. */
@media (min-width: 861px) {
  .mbar, .side-scrim { display: none; }
}

/* ---- the sign-in gate (console-auth.js) ---------------------------------------------------
   Full-bleed and opaque. The gate REPLACES the body rather than covering it, so there is no
   page underneath to reveal by deleting a node in dev tools. */
.gate-full {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  padding: 24px; background: var(--bg);
}
.gate-card {
  background: var(--card); border: 1px solid var(--line); border-radius: 14px;
  padding: 34px 30px; width: 100%; max-width: 380px; text-align: center;
}
.gate-card .btn { width: 100%; justify-content: center; }
.gate-msg {
  background: var(--red-soft); color: var(--red); border-radius: 9px;
  padding: 10px 12px; font-size: 14px; margin: 0 0 16px; text-align: left;
}

/* ---- Tasks (08-tasks.png) ------------------------------------------------------------------ */
.tasks-layout { display: grid; grid-template-columns: 1fr 320px; gap: 20px; align-items: start; }
.task-row {
  display: grid; grid-template-columns: 40px 1fr 110px auto 150px;
  align-items: center; gap: 12px; padding: 15px 20px; border-bottom: 1px solid var(--line);
}
.task-row:last-child { border-bottom: 0; }
.task-row.is-current { background: var(--accent-soft); }
.t-title { font-weight: 600; }
.t-ctx { margin-top: 3px; }
.t-due { text-align: right; color: var(--ink-2); font-size: 14px; }
.t-due.od { color: var(--red); font-weight: 600; }
.chip {
  background: var(--grey-soft); color: var(--ink-2); border-radius: 999px;
  padding: 5px 13px; font-size: 13px; font-weight: 500; display: inline-block;
}
.tick {
  width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--line);
  display: inline-block; cursor: pointer;
}
.tick.done { border-color: var(--accent); background: var(--accent); color: #fff;
  text-align: center; line-height: 20px; font-size: 13px; }
/* The checking ring is the mockup's blue partial spinner: one coloured arc on an open circle. */
.tick.checking { border-color: var(--accent); border-right-color: transparent;
  border-bottom-color: transparent; animation: tickspin 900ms linear infinite; }
@keyframes tickspin { to { transform: rotate(360deg); } }
.warn-tri { color: var(--red); cursor: help; }
.t-ev .btn.ghost { border: 1px solid var(--line); font-size: 13px; padding: 6px 12px; }
.rail-body { padding: 6px 20px 18px; }
.rail-row { display: flex; gap: 11px; padding: 13px 0; border-bottom: 1px solid var(--line); }
.rail-row:last-child { border-bottom: 0; }
.rail-ico { width: 30px; height: 30px; border-radius: 8px; flex: 0 0 30px;
  display: flex; align-items: center; justify-content: center; font-size: 14px; }
.rail-ico.red { background: var(--red-soft); color: var(--red); }
.rail-ico.amber { background: var(--amber-soft); color: var(--amber); }
.rail-ico.green { background: var(--green-soft); color: var(--green); }
.rail-t { font-weight: 600; font-size: 14px; }
.rail-due { float: right; }
.rail-due.od { color: var(--red); font-weight: 600; }
.badge.red { background: var(--red); color: #fff; }
.badge.amber { background: var(--amber); color: #fff; }
.badge.blue { background: var(--accent); color: #fff; }

/* ---- Clients (02-clients-crm.png) ----------------------------------------------------------- */
.page-h { display: flex; align-items: center; gap: 18px; margin-bottom: 22px; }
.page-h h1 { margin: 0; flex: 1; }
.seg { display: inline-flex; background: var(--card); border: 1px solid var(--line);
  border-radius: 10px; padding: 4px; gap: 2px; }
.seg button { border: 0; background: none; padding: 8px 18px; border-radius: 7px;
  font: inherit; font-size: 14px; color: var(--ink-2); cursor: pointer; }
.seg button.on { background: var(--grey-soft); color: var(--ink); font-weight: 600; }
.client-card { display: grid; grid-template-columns: 1fr 300px; gap: 26px; padding: 26px; }
.cc-head { display: flex; align-items: center; gap: 20px; }
.cc-logo { width: 74px; height: 74px; border-radius: 50%; background: var(--accent-soft);
  display: flex; align-items: center; justify-content: center; font-weight: 700;
  color: var(--accent); font-size: 21px; flex: 0 0 74px; }
.cc-name { font-size: 25px; font-weight: 700; letter-spacing: -.02em; }
.cc-since { display: flex; align-items: center; gap: 7px; margin-top: 5px; }
.rail-stages { display: flex; margin-top: 26px; }
.stage { flex: 1; text-align: center; position: relative; }
.stage .sdot { width: 24px; height: 24px; border-radius: 50%; background: var(--grey-soft);
  margin: 0 auto; display: flex; align-items: center; justify-content: center;
  color: #fff; font-size: 12px; position: relative; z-index: 1; }
.stage.done .sdot { background: var(--accent); }
.stage.now .sdot { background: var(--card); border: 3px solid var(--accent); }
.stage .sbar { position: absolute; top: 11px; left: 50%; width: 100%; height: 2px;
  background: var(--line); z-index: 0; }
.stage.done .sbar { background: var(--accent); }
.stage:last-child .sbar { display: none; }
.stage .slab { font-size: 12.5px; color: var(--ink-2); margin-top: 9px; }
.cc-next { border-left: 1px solid var(--line); padding-left: 26px; }
.cc-next-h { display: flex; align-items: center; justify-content: space-between; }
.cc-next-h h3 { margin: 0; font-size: 19px; }
.tag { border-radius: 6px; padding: 4px 10px; font-size: 12px; font-weight: 700;
  letter-spacing: .03em; }
.tag.guided { background: var(--amber-soft); color: var(--amber); }
.tag.auto { background: var(--accent-soft); color: var(--accent); }
.cc-next .btn { width: 100%; justify-content: center; margin: 18px 0 14px; }

/* ---- Approvals (04-approvals.png) ----------------------------------------------------------- */
.appr-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; align-items: start; }
.prop { border: 1px solid var(--line); border-radius: 12px; padding: 18px; margin: 0 20px 16px; }
.prop-rule { font-weight: 700; font-size: 16px; margin-bottom: 12px; }
.prop-ev { background: var(--grey-soft); border-radius: 8px; padding: 12px 14px;
  font-size: 14px; color: var(--ink-2); }
/* A capture's eight labelled lines (request-kind.js captureHtml), inside .prop-ev. */
.capture { display: grid; gap: 6px; }
.capline { display: grid; grid-template-columns: minmax(150px, 220px) 1fr; gap: 10px; }
.capline .capk { color: var(--ink-3); font-size: 13px; }
.capline .capv { color: var(--ink); white-space: pre-wrap; }
.prop-att { background: var(--amber-soft); border: 1px solid var(--amber-soft);
  border-left: 3px solid var(--amber); border-radius: 8px; padding: 12px 14px; margin-top: 10px; }
.prop-att .lbl { color: var(--amber); font-size: 13px; font-weight: 600; margin-bottom: 4px; }
.prop-actions { display: flex; align-items: center; gap: 18px; margin-top: 16px; }
.draft { border: 1px solid var(--line); border-radius: 12px; padding: 20px; margin: 0 20px 16px; }
.draft-to { font-size: 14px; color: var(--ink-2); margin-bottom: 8px; }
.draft-subj { font-weight: 700; font-size: 16px; margin-bottom: 12px; }
.draft-body { font-size: 14.5px; line-height: 1.6; }
.draft-body .fade { color: var(--ink-3); }
.draft-path { background: var(--grey-soft); border-radius: 8px; padding: 11px 14px;
  margin: 14px 0; }

/* ---- Refinery (07-refinery.png) -------------------------------------------------------------- */
.dist-bar { display: flex; height: 34px; border-radius: 8px; overflow: hidden; margin: 4px 0 22px; }
.dist-seg { height: 100%; }
.dist-legend { display: flex; flex-wrap: wrap; gap: 18px; }
.dl { flex: 1 1 150px; }
.dl-top { display: flex; align-items: center; gap: 8px; font-size: 15px; }
.dl-n { font-size: 21px; font-weight: 700; margin-top: 6px; }
.dl-n .pc { font-size: 15px; font-weight: 500; color: var(--ink-2); margin-left: 7px; }
.refinery-cols { display: grid; grid-template-columns: 1fr 380px; gap: 22px; align-items: start; }
.clu { display: flex; align-items: center; gap: 16px; padding: 13px 20px;
  border-bottom: 1px solid var(--line); }
.clu:last-child { border-bottom: 0; }
.clu-n { width: 42px; height: 42px; border-radius: 50%; border: 1px solid var(--accent);
  color: var(--accent); display: flex; align-items: center; justify-content: center;
  font-weight: 600; font-size: 14px; flex: 0 0 42px; }
.clu-k { flex: 1; }
.batch { padding: 16px 20px; border-bottom: 1px solid var(--line); }
.batch.superseded .b-name, .batch.superseded .b-meta { text-decoration: line-through;
  color: var(--ink-3); }
.b-head { display: flex; align-items: flex-start; gap: 10px; }
.b-name { font-weight: 600; flex: 1; }
.corpus-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; padding: 20px; }
.c3-n { font-size: 30px; font-weight: 700; letter-spacing: -.02em; }

/* ---- Trace (11-trace.png) -------------------------------------------------------------------- */
.trace-cols { display: grid; grid-template-columns: 1fr 340px; gap: 22px; align-items: start; }
.search-wide { display: flex; align-items: center; gap: 11px; background: var(--card);
  border: 1px solid var(--line); border-radius: 11px; padding: 14px 18px; margin-bottom: 18px; }
.search-wide input { border: 0; background: none; font: inherit; flex: 1; color: inherit;
  outline: none; }
.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--line); margin-bottom: 20px; }
.tabs button { border: 0; background: none; font: inherit; font-size: 15px; cursor: pointer;
  padding: 11px 18px; color: var(--ink-2); border-bottom: 2px solid transparent; }
.tabs button.on { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
.tl { position: relative; padding-left: 26px; }
.tl::before { content: ""; position: absolute; left: 5px; top: 8px; bottom: 8px; width: 1px;
  background: var(--line); }
.tl-item { position: relative; padding: 14px 0; }
.tl-item .tdot { position: absolute; left: -26px; top: 20px; width: 11px; height: 11px;
  border-radius: 50%; }
.tl-item.on { background: var(--card); border: 1px solid var(--line);
  border-left: 3px solid var(--accent); border-radius: 10px; padding: 16px 18px;
  margin-left: -18px; }
.tl-item.on .tdot { left: -8px; }
.tl-t { font-weight: 600; font-size: 16px; }
.plog { width: 100%; border-collapse: collapse; }
.plog td, .plog th { padding: 10px 12px; border-bottom: 1px solid var(--line);
  text-align: left; font-size: 13.5px; }

/* ---- Map (06-blast-radius-map.png) ----------------------------------------------------------- */
.map-cols { display: grid; grid-template-columns: 1fr 320px; gap: 22px; align-items: start; }
.map-canvas { background: var(--grey-soft); border: 1px solid var(--line); border-radius: 12px;
  min-height: 430px; display: flex; align-items: center; justify-content: center;
  padding: 30px; position: relative; }
.map-legend { position: absolute; left: 20px; bottom: 20px; background: var(--card);
  border: 1px solid var(--line); border-radius: 10px; padding: 14px 18px; }
.map-legend div { display: flex; align-items: center; gap: 9px; font-size: 13.5px;
  padding: 3px 0; }
.big-n { font-size: 50px; font-weight: 700; color: var(--accent); letter-spacing: -.03em;
  line-height: 1.1; }
.stale-row { display: flex; gap: 12px; padding: 10px 0; font-size: 14px; }
.stale-ico { width: 30px; height: 30px; border-radius: 8px; flex: 0 0 30px; display: flex;
  align-items: center; justify-content: center; }

@media (max-width: 1100px) {
  .tasks-layout, .appr-cols, .refinery-cols, .trace-cols, .map-cols,
  .client-card { grid-template-columns: 1fr; }
  .cc-next { border-left: 0; padding-left: 0; }
}

/* A disabled button must LOOK disabled. Rendering it in the primary blue makes an action that
   cannot happen look like the main thing to do — and these pages disable buttons precisely where
   the machinery behind them does not exist yet, which is the one place a false affordance costs
   the most. */
.btn[disabled], .btn:disabled {
  background: var(--grey-soft); color: var(--ink-3); border-color: var(--line);
  cursor: not-allowed; opacity: 1;
}
.trace-cols.solo { grid-template-columns: 1fr; }

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   THE APPROVED DESIGN, 31/08/2026. Built to five mockups Hamish supplied: Home, Clients,
   Client detail, Agents, Agent detail.

   🚨 THE LAYOUT IS COPIED. THE CONTENT IS NOT. The mockups carry invented clients (Acme Corp,
   Harbour Medical, Summit Logistics…), invented people (Alex Morgan, Priya Patel, Hannah
   Foster…) and invented agents (Vessel Watcher, Spend Anomaly Detector…). We have ONE client
   and a different twelve agents. Every one of those names is a designer's placeholder, and
   hardcoding any of them would make the console state something untrue about the business —
   which is the one thing this console exists not to do. test-console-pages.js already fails on
   the mockups' invented names for exactly this reason.

   So: the components, spacing, type scale and states below are the picture. What goes in them
   comes from console_state.
   ═══════════════════════════════════════════════════════════════════════════════════════════ */

/* ---- page header ------------------------------------------------------------------------- */
.ph { margin: 6px 0 24px; }
.ph h1 { font-size: 30px; font-weight: 700; letter-spacing: -.025em; margin: 0 0 6px; }
.ph p { margin: 0; color: var(--ink-2); font-size: 15px; }
.ph-row { display: flex; align-items: flex-start; gap: 16px; }
.ph-row .ph { flex: 1; margin-bottom: 0; }

/* ---- the verdict banner (Home) ------------------------------------------------------------ */
.verdict { display: flex; gap: 16px; align-items: flex-start; padding: 20px 22px; }
.vd-ico {
  width: 46px; height: 46px; border-radius: 12px; flex: 0 0 46px;
  display: grid; place-items: center; font-size: 21px;
  background: var(--accent); color: #fff;
}
.vd-ico.green { background: var(--green); }
.vd-ico.amber { background: var(--amber); }
.vd-ico.red   { background: var(--red); }
.vd-ico.grey  { background: var(--grey-soft); color: var(--ink-3); }
.vd-b { min-width: 0; flex: 1 }
.vd-b h2 { margin: 0 0 4px; font-size: 16px; font-weight: 650; }
.vd-b p  { margin: 0; font-size: 15px; color: var(--ink); line-height: 1.5; }
.vd-b .when { margin-top: 6px; font-size: 13px; color: var(--ink-3); }

/* ---- stat tiles -------------------------------------------------------------------------- */
.tiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; margin: 16px 0; }
.tile { display: flex; gap: 14px; align-items: flex-start; padding: 18px 20px; }
.tile-ico {
  width: 44px; height: 44px; border-radius: 50%; flex: 0 0 44px;
  display: grid; place-items: center; font-size: 18px;
  background: var(--accent); color: #fff;
}
.tile-ico.grey  { background: var(--grey-soft); color: var(--ink-3); }
.tile-ico.amber { background: var(--amber); color: #fff; }
.tile-ico.green { background: var(--green); color: #fff; }
.tile-b { min-width: 0 }
.tile-label { font-size: 14px; color: var(--ink-2); margin-bottom: 2px; }
.tile-value { font-size: 27px; font-weight: 700; letter-spacing: -.02em; line-height: 1.15; }
.tile-detail { font-size: 13px; color: var(--ink-3); margin-top: 4px; line-height: 1.45; }
/* 🚨 An unmeasured tile is a DASH and a reason. Never a zero, never green. */
.tile-value.dash { color: var(--ink-3); }

/* ---- the three-column body (Home) --------------------------------------------------------- */
.home-cols { display: grid; grid-template-columns: minmax(0,1.15fr) minmax(0,1fr) minmax(0,.72fr); gap: 16px; align-items: start; }
.home-stack { display: flex; flex-direction: column; gap: 16px; }

/* ---- card header with a count badge ------------------------------------------------------- */
.ch { display: flex; align-items: center; gap: 10px; padding: 17px 20px 13px; }
.ch h2 { margin: 0; font-size: 16px; font-weight: 650; }
.ch .count {
  background: var(--accent-soft); color: var(--accent); border-radius: 999px;
  min-width: 22px; height: 22px; padding: 0 7px; font-size: 12.5px; font-weight: 650;
  display: inline-grid; place-items: center;
}
.ch .spacer { flex: 1 }
.card-foot { padding: 13px 20px 15px; border-top: 1px solid var(--line); }
.card-foot a { color: var(--accent); font-weight: 600; font-size: 14px; text-decoration: none; }

/* ---- a list row with an icon tile and a right-hand button --------------------------------- */
.lrow { display: flex; align-items: center; gap: 13px; padding: 11px 20px; }
.lrow + .lrow { border-top: 1px solid var(--line); }
.lrow-ico {
  width: 32px; height: 32px; border-radius: 9px; flex: 0 0 32px;
  display: grid; place-items: center; font-size: 14px;
}
.lrow-b { flex: 1; min-width: 0 }
.lrow-t { font-size: 14px; font-weight: 600; }
.lrow-s { font-size: 13px; color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lrow .btn { flex: 0 0 auto }

/* ---- the activity timeline (Home) -----------------------------------------------------------
 * 🚨 NAMED .atl, NOT .tl. `.tl` is already trace.html's timeline, with a ::before rule line and
 * a padding-left this design does not want. Overriding it would have quietly restyled a page
 * this work never touched — the same class-collision that laid the Fleet card out sideways, one
 * page further away where nobody would have looked. */
.atl { padding: 4px 20px 6px }
.atl-row { display: grid; grid-template-columns: 58px 22px 1fr; gap: 10px; align-items: start; padding: 9px 0; }
.atl-when { font-size: 12.5px; color: var(--ink-3); text-align: right; padding-top: 2px; }
.atl-dot { position: relative; display: grid; place-items: center; padding-top: 2px; }
.atl-dot i {
  width: 18px; height: 18px; border-radius: 50%; display: grid; place-items: center;
  font-size: 10px; font-style: normal; color: #fff; background: var(--accent);
}
.atl-dot.green i { background: var(--green) } .atl-dot.amber i { background: var(--amber) }
.atl-dot.red i { background: var(--red) }     .atl-dot.grey i { background: var(--ink-3) }
.atl-t { font-size: 14px; font-weight: 600 }
.atl-s { font-size: 13px; color: var(--ink-3) }

/* ---- a label/value stack (Fleet card, Agent details) ---------------------------------------
 * 🚨 NAMED .kvlist, NOT .kv. `.kv` already exists in this stylesheet as a flex dl/dt/dd row, so
 * the first version of this silently inherited `display:flex` and laid the Fleet card's four
 * rows out SIDEWAYS. Caught by rendering it, not by reading. A new component in a stylesheet
 * this size gets a distinct prefix or it collides with something written months ago. */
.kvlist { padding: 4px 20px 16px }
.kvl-row { padding: 9px 0 }
.kvl-row + .kvl-row { border-top: 1px solid var(--line) }
.kvl-k { font-size: 13px; color: var(--ink-2) }
.kvl-v { font-size: 15px; font-weight: 600; margin-top: 2px }
.kvl-v .dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; margin-right: 7px }

/* ---- tables (Clients) ---------------------------------------------------------------------- */
.tbl { width: 100%; border-collapse: collapse }
.tbl th {
  text-align: left; font-size: 13px; font-weight: 600; color: var(--ink-2);
  padding: 13px 18px; border-bottom: 1px solid var(--line); white-space: nowrap;
}
.tbl td { padding: 15px 18px; border-bottom: 1px solid var(--line); font-size: 14.5px; vertical-align: middle }
.tbl tbody tr:last-child td { border-bottom: 0 }
.tbl tbody tr:hover { background: var(--grey-soft) }
.tbl .t-name { font-weight: 600 }
.tbl .t-link { color: var(--accent); text-decoration: none }
.tbl .t-mut  { color: var(--ink-3) }
.tbl-foot { display: flex; align-items: center; padding: 13px 18px; border-top: 1px solid var(--line); font-size: 13.5px; color: var(--ink-2) }
.tbl-foot .spacer { flex: 1 }
.pager { display: flex; gap: 6px; align-items: center }
.pager button {
  width: 30px; height: 30px; border-radius: 8px; border: 1px solid var(--line);
  background: var(--card); color: var(--ink-2); cursor: pointer; font-size: 14px;
}
.pager button.on { border-color: var(--accent); color: var(--accent); font-weight: 600 }
.pager button:disabled { opacity: .45; cursor: default }

/* ---- search + filter row -------------------------------------------------------------------- */
.search { position: relative; flex: 0 1 320px }
.search input {
  width: 100%; padding: 10px 14px 10px 38px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--card); color: var(--ink); font: inherit; font-size: 14.5px;
}
.search input::placeholder { color: var(--ink-3) }
.search .mag { position: absolute; left: 13px; top: 50%; transform: translateY(-50%); color: var(--ink-3); font-size: 15px }

/* ---- detail pages: back link, meta strip ---------------------------------------------------- */
.back { display: inline-flex; align-items: center; gap: 8px; color: var(--accent); text-decoration: none; font-size: 14.5px; font-weight: 600; margin-bottom: 14px }
.title-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-bottom: 10px }
.title-row h1 { margin: 0; font-size: 34px; font-weight: 700; letter-spacing: -.03em }
.title-row .spacer { flex: 1 }
.meta-strip { display: flex; flex-wrap: wrap; align-items: center; gap: 0 22px; color: var(--ink-2); font-size: 14px; margin-bottom: 22px }
.meta-strip b { color: var(--ink); font-weight: 600 }
.meta-strip .sep { color: var(--line) }
/* the boxed variant used on Agent detail */
.meta-box { display: flex; flex-wrap: wrap; padding: 0; margin-bottom: 16px }
.meta-box > div { padding: 14px 22px; flex: 1 1 auto; min-width: 150px }
.meta-box > div + div { border-left: 1px solid var(--line) }
.meta-box .k { font-size: 13px; color: var(--ink-2); margin-bottom: 3px }
.meta-box .v { font-size: 14.5px; font-weight: 600 }

.detail-cols { display: grid; grid-template-columns: minmax(0,1.55fr) minmax(0,1fr); gap: 16px; align-items: start }
.detail-stack { display: flex; flex-direction: column; gap: 16px }

/* ---- a "state" card: icon, title, right-hand pill, two lines -------------------------------- */
.scard { display: flex; gap: 15px; align-items: flex-start; padding: 20px 22px }
.scard-ico {
  width: 42px; height: 42px; border-radius: 11px; flex: 0 0 42px; display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent); font-size: 18px;
}
.scard-b { flex: 1; min-width: 0 }
.scard-h { display: flex; align-items: center; gap: 10px; margin-bottom: 7px }
.scard-h h3 { margin: 0; font-size: 16.5px; font-weight: 650 }
.scard-h .spacer { flex: 1 }
.scard p { margin: 0; font-size: 14.5px; color: var(--ink-2); line-height: 1.55 }
.scard p + p { margin-top: 2px }

/* ---- icon rows (Client details panel) -------------------------------------------------------- */
.irow { display: flex; gap: 13px; align-items: flex-start; padding: 14px 0 }
.irow + .irow { border-top: 1px solid var(--line) }
.irow-ico { width: 22px; flex: 0 0 22px; color: var(--ink-3); font-size: 16px; text-align: center }
.irow-b { flex: 1; min-width: 0 }
.irow-k { font-size: 14.5px; font-weight: 600 }
.irow-v { font-size: 14.5px; color: var(--ink-2); text-align: right }
.irow-n { font-size: 12.5px; color: var(--ink-3); text-align: right; margin-top: 2px }
.irow-r { margin-left: auto; text-align: right }

/* ---- agent grid ------------------------------------------------------------------------------ */
.agrid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px }
.acard { padding: 16px 18px; display: flex; flex-direction: column; gap: 12px }
.acard.notbuilt { border-style: dashed; background: transparent }
.acard-top { display: flex; gap: 12px; align-items: flex-start }
.acard-ico {
  width: 40px; height: 40px; border-radius: 11px; flex: 0 0 40px; display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent); font-size: 18px;
}
.acard-ico.green { background: var(--green-soft); color: var(--green) }
.acard-ico.grey  { background: var(--grey-soft);  color: var(--ink-3) }
.acard-n { font-size: 15px; font-weight: 650; line-height: 1.3 }
.acard-k { font-size: 13.5px; color: var(--ink-2); margin-top: 2px }
.acard-w { font-size: 13px; color: var(--ink-3); margin-top: 3px; display: flex; align-items: center; gap: 6px }
.acard-foot { display: flex; align-items: center; gap: 8px; padding-top: 11px; border-top: 1px solid var(--line); font-size: 13px; color: var(--ink-3) }
.acard-foot .spacer { flex: 1 }
.acard.dim { opacity: .72 }

/* ---- a toggle switch -------------------------------------------------------------------------- */
.tog { display: inline-flex; align-items: center; gap: 10px; font-size: 14.5px; color: var(--ink-2); cursor: pointer }
.tog i {
  width: 42px; height: 24px; border-radius: 999px; background: var(--line); position: relative;
  transition: background .18s ease; flex: 0 0 42px;
}
.tog i::after {
  content: ""; position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; border-radius: 50%;
  background: #fff; transition: transform .18s ease; box-shadow: 0 1px 2px rgba(10,32,63,.2);
}
.tog.on i { background: var(--accent) }
.tog.on i::after { transform: translateX(18px) }

/* ---- code / log output ------------------------------------------------------------------------ */
.logbox {
  background: var(--grey-soft); border-radius: 10px; padding: 14px 16px; margin: 0 20px 18px;
  font: 400 12.5px/1.75 ui-monospace, Menlo, Consolas, monospace; color: var(--ink-2);
  white-space: pre-wrap; word-break: break-word; max-height: 260px; overflow: auto;
}

/* ---- the runs table on Agent detail ----------------------------------------------------------- */
.runs { width: 100%; border-collapse: collapse; font-size: 13.5px }
.runs th { text-align: left; font-weight: 600; color: var(--ink-2); padding: 9px 20px; font-size: 13px }
.runs td { padding: 11px 20px; border-top: 1px solid var(--line) }

/* ---- buttons, to the mockups ------------------------------------------------------------------ */
.btn.sm { padding: 7px 14px; font-size: 13.5px; border-radius: 9px }
.btn.o {                       /* outlined, blue text — "Review", "Answer", "Investigate" */
  background: var(--card); color: var(--accent); border: 1px solid var(--line);
}
.btn.o:hover { border-color: var(--accent) }
.btn.o-red { background: var(--card); color: var(--red); border: 1px solid var(--line) }
.btn.o-red:hover { border-color: var(--red) }
.btn.wide { width: 100%; justify-content: center }
.btn.icon-l { display: inline-flex; align-items: center; gap: 9px; justify-content: center }

/* ---- the sidebar fleet pill, boxed as in the mockups -------------------------------------------- */
.fleet-pill {
  display: flex; align-items: center; gap: 10px; width: 100%; margin-top: auto;
  border: 1px solid var(--line); background: var(--card); border-radius: 12px;
  padding: 12px 14px; cursor: pointer; font: inherit; font-size: 14px; color: var(--ink); text-align: left;
}
.fleet-pill .dot { width: 9px; height: 9px; border-radius: 50%; background: var(--green); flex: 0 0 9px }
.fleet-pill.stopped .dot { background: var(--ink-3) }
.fleet-pill .sub { display: block; font-size: 12.5px; color: var(--ink-3); margin-top: 1px }

/* ---- the topbar, to the mockups ------------------------------------------------------------------ */
.topbar .ti { position: relative; width: 34px; height: 34px; border-radius: 9px; display: grid; place-items: center }
.topbar .ti:hover { background: var(--grey-soft) }
.topbar .ti .dot {
  position: absolute; top: 6px; right: 6px; width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent); border: 1.5px solid var(--card);
}
.avatar { position: relative }
.avatar .dot {
  position: absolute; bottom: -1px; right: -1px; width: 9px; height: 9px; border-radius: 50%;
  background: var(--green); border: 2px solid var(--card);
}

/* ---- phone: the new grids collapse too. Same list as the block above, kept beside it ------------- */
@media (max-width: 860px) {
  .tiles, .agrid, .home-cols, .detail-cols { grid-template-columns: 1fr !important }
  .ph h1 { font-size: 24px }
  .title-row h1 { font-size: 26px }
  .meta-box > div + div { border-left: 0; border-top: 1px solid var(--line) }
  .meta-box > div { flex: 1 1 100% }
  .tbl { display: block; overflow-x: auto; white-space: nowrap }
  .search { flex: 1 1 100% }
  .logbox { margin: 0 16px 16px }
  /* The meta strip wraps to one item per line on a phone, which leaves the "|" separators
     floating at the end of each row pointing at nothing. They are decoration between items on
     one line, so they go when there is no longer one line. */
  .meta-strip { display: block; line-height: 1.9 }
  .meta-strip .sep { display: none }
  .meta-strip > span { display: block }
  .title-row .btn { width: 100% }
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   BATCH 2 COMPONENTS, 31/08/2026 — built to the second set of approved mockups.
   Same rule as batch 1: the layout is copied, the content is read. The references carry
   invented clients, people, agents and figures; these components are filled from console_state.
   ═══════════════════════════════════════════════════════════════════════════════════════════ */

/* ---- stat cards: a row of big numbers with an icon (Health, Refinery, Fleet) ---------------- */
.stats { display: grid; gap: 16px; margin-bottom: 18px; }
.stats.c4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.stats.c5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.stat { display: flex; align-items: center; gap: 14px; padding: 18px 20px; }
.stat-ico { width: 34px; height: 34px; flex: 0 0 34px; display: grid; place-items: center; }
.stat-ico svg { width: 30px; height: 30px; }
.stat-n { font-size: 26px; font-weight: 700; letter-spacing: -.02em; line-height: 1.1; }
.stat-l { font-size: 13.5px; color: var(--ink-2); margin-top: 2px; }
.stat-l.caps { text-transform: uppercase; letter-spacing: .04em; font-size: 12px; font-weight: 600; }

/* ---- tabs (Settings) ------------------------------------------------------------------------ */
.tabs { display: flex; gap: 26px; border-bottom: 1px solid var(--line); margin: 4px 0 22px; }
.tabs button {
  border: 0; background: none; font: inherit; font-size: 15px; color: var(--ink-2);
  padding: 10px 2px 13px; cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.tabs button.on { color: var(--accent); font-weight: 600; border-bottom-color: var(--accent); }

/* ---- segmented control (Theme, Density) ----------------------------------------------------- */
.segbox { display: flex; gap: 0; }
.segbox button {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  border: 1px solid var(--line); background: var(--card); color: var(--ink);
  font: inherit; font-size: 14.5px; padding: 12px 20px; cursor: pointer; flex: 1;
}
.segbox button:first-child { border-radius: 10px 0 0 10px; }
.segbox button:last-child { border-radius: 0 10px 10px 0; }
.segbox button + button { border-left: 0; }
.segbox button.on {
  border-color: var(--accent); color: var(--accent); background: var(--accent-soft);
  font-weight: 600; position: relative; z-index: 1;
}
.segbox button svg { width: 18px; height: 18px; }

/* ---- "not built" rows (Settings) ------------------------------------------------------------- */
.nbrow {
  display: flex; align-items: center; gap: 16px; padding: 18px 22px; margin-bottom: 12px;
  border: 1px dashed var(--line); border-radius: var(--radius); background: transparent;
}
.nbrow h3 { margin: 0; font-size: 16px; font-weight: 650; flex: 0 0 auto; }
.nbrow .why { color: var(--ink-2); font-size: 14.5px; }

/* ---- filter pills with counts (Tasks, Refinery, Health) -------------------------------------- */
.fpills { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 18px; }
.fpills button {
  display: inline-flex; align-items: center; gap: 9px; border: 1px solid var(--line);
  background: var(--card); color: var(--ink); font: inherit; font-size: 14.5px;
  padding: 10px 18px; border-radius: 10px; cursor: pointer;
}
.fpills button.on { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); font-weight: 600; }
.fpills button .n { font-weight: 650; }
.fpills button .n.amber { color: var(--amber) } .fpills button .n.red { color: var(--red) }
.fpills button .n.green { color: var(--green) }
/* the pill-shaped variant the Refinery mockup uses */
.fpills.round button { border-radius: 999px; padding: 9px 20px; }
.fpills.round button.on { background: var(--accent); color: #fff; border-color: var(--accent); }

/* ---- a table row that leads with an icon tile (Tasks) ----------------------------------------- */
.trow-ico {
  width: 34px; height: 34px; border-radius: 9px; flex: 0 0 34px; display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent); font-size: 15px;
}
.trow-main { display: flex; align-items: center; gap: 13px; }
.trow-t { font-weight: 600; font-size: 14.5px; }
.trow-s { font-size: 13px; color: var(--ink-3); margin-top: 1px; }
.tbl td.due-late, .tbl td.due-late a { color: var(--red); }
.tbl td.due-ok { color: var(--green); }

/* ---- the calendar grid ------------------------------------------------------------------------ */
.calwrap { display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 16px; align-items: start; }
.cal { width: 100%; border-collapse: collapse; table-layout: fixed; }
.cal th {
  font-size: 13px; font-weight: 600; color: var(--ink-2); padding: 12px 0; text-align: center;
  border-bottom: 1px solid var(--line);
}
.cal td { border: 1px solid var(--line); vertical-align: top; height: 106px; padding: 8px; }
.cal .daynum { font-size: 13.5px; color: var(--ink-2); margin-bottom: 6px; }
.cal .out .daynum { color: var(--ink-3); opacity: .6 }
.cal .today .daynum { color: var(--red); font-weight: 700 }
.ev { border-radius: 8px; padding: 6px 8px; margin-bottom: 5px; font-size: 12px; line-height: 1.35; }
.ev b { display: block; font-weight: 600 }
.ev.blue { background: var(--accent-soft); color: var(--accent) }
.ev.green { background: var(--green-soft); color: var(--green) }
.ev.amber { background: var(--amber-soft); color: var(--amber) }
.ev.red { background: var(--red-soft); color: var(--red) }

/* ---- the day panel beside the calendar --------------------------------------------------------- */
.dayrow { display: grid; grid-template-columns: 62px 1fr; gap: 10px; padding: 7px 0; align-items: start; }
.dayrow .t { font-size: 12.5px; color: var(--ink-2); padding-top: 9px }
.dayrow .b { border-left: 3px solid var(--accent); border-radius: 8px; background: var(--grey-soft); padding: 9px 11px }
.dayrow .b.green { border-left-color: var(--green) } .dayrow .b.amber { border-left-color: var(--amber) }
.dayrow .b .n { font-weight: 600; font-size: 13.5px }
.dayrow .b .s { font-size: 12.5px; color: var(--ink-2) }

/* ---- Trace's centred search --------------------------------------------------------------------- */
.tracebox { max-width: 1320px; margin: 0 auto; }
/* 1320, not 1120: measured off design/reference/trace.png on 31/08/2026, whose card spans
 * x305-1620. At 1120 the card sat ~200px narrow and the compare's worst region was the empty
 * band at its right edge. Measured, not eyeballed - two rounds of "matching" this by eye is what
 * REG-S18-0 exists to stop. */
.bigsearch { display: flex; gap: 12px; align-items: center; border: 1px solid var(--line);
  border-radius: 12px; padding: 16px 16px 16px 22px; background: var(--card); }
.bigsearch input { flex: 1; border: 0; background: none; font: inherit; font-size: 15.5px;
  color: var(--ink); padding: 12px 0; outline: none; }
/* ---- Trace's centred search, sized off the reference rather than by eye (REG-S18-3, 31/08/2026).
 * Measured on design/reference/trace.png: the field is 85px tall and its Search button 122x51.
 * At the original 8px padding the field came out 61px and the button 81x32, and the compare's
 * worst region was that whole top-right band. This is the third thing measuring has caught that
 * two rounds of matching by eye did not. */
.bigsearch .btn { padding: 14px 30px; font-size: 15px }
/* The card fills the height the reference gives it and centres its empty state, rather than
 * shrinking to its content — in the reference the mark sits at y455 of a card running y80-905,
 * which only happens if the box has a height to centre within. */
.tracebox { display: flex; flex-direction: column; min-height: 820px }
.tracebox .trace-cols { flex: 1 }
.tracebox .trace-cols.solo { display: flex; flex-direction: column; justify-content: center;
  /* 🚨 align-items MUST be restated. .trace-cols sets `align-items: start` for its GRID layout,
   * and that property carries straight into flex — where the cross axis is horizontal, so the
   * empty state packed to its own content width and sat 320px left of centre inside a card that
   * was the right size. The compare caught it; it looked deliberate on screen. */
  align-items: stretch }
/* The explanatory line wraps to two lines in the reference; unconstrained it runs the full 1300px
 * and reads as a banner rather than a caption. */
.bigempty p { max-width: 560px; margin-left: auto; margin-right: auto }
.bigsearch input::placeholder { color: var(--ink-3) }
.srcchips { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; margin: 18px 0 0 }
.srcchip { display: inline-flex; align-items: center; gap: 9px; border: 1px solid var(--line);
  border-radius: 10px; padding: 10px 18px; font-size: 14px; color: var(--ink); background: var(--card) }
.bigempty { text-align: center; padding: 46px 20px 10px }
.bigempty h3 { font-size: 21px; margin: 18px 0 0; font-weight: 650 }
.bigempty p { color: var(--ink-2); font-size: 14.5px; margin: 14px 0 0; line-height: 1.6 }

/* ---- Approvals: the email card ------------------------------------------------------------------- */
.apx { padding: 0; margin-bottom: 16px }
.apx-h { display: flex; gap: 18px; align-items: flex-start; padding: 18px 22px }
.apx-logo { width: 30px; flex: 0 0 30px; display: grid; place-items: center; font-size: 20px }
.apx-kv { display: grid; grid-template-columns: 62px 1fr; gap: 3px 14px; font-size: 13.5px; flex: 1; min-width: 0 }
.apx-kv .k { color: var(--ink-2) }
.apx-kv .v { color: var(--ink); font-weight: 600; overflow: hidden; text-overflow: ellipsis }
.apx-state { display: flex; align-items: center; gap: 8px; font-size: 13.5px; color: var(--ink-2); flex: 0 0 auto }
.apx-btns { display: flex; gap: 10px; flex: 0 0 auto }
.apx-body { padding: 4px 22px 20px; font-size: 14.5px; line-height: 1.65; color: var(--ink);
  border-top: 1px solid var(--line); padding-top: 16px }
.apx-confirm { display: flex; gap: 15px; align-items: center; margin: 0 22px 20px; padding: 16px 18px;
  background: var(--grey-soft); border-radius: 12px }
.apx-confirm .ico { width: 40px; height: 40px; border-radius: 50%; flex: 0 0 40px; display: grid;
  place-items: center; background: var(--accent-soft); color: var(--accent) }
.apx-confirm .b { flex: 1; min-width: 0 }
.apx-confirm .n { font-weight: 650; font-size: 14.5px }
.apx-confirm .s { font-size: 13.5px; color: var(--ink-2) }
.strip { display: flex; align-items: center; gap: 11px; padding: 13px 22px; border-radius: 12px;
  font-size: 14px; margin-bottom: 16px }
.strip.green { background: var(--green-soft); color: var(--green) }
.strip.blue { background: var(--accent-soft); color: var(--accent) }

/* ---- Notifications: the device panel and its state legend ------------------------------------------ */
.dev { display: flex; align-items: center; gap: 18px; padding: 20px 22px }
.dev-ico { width: 46px; height: 46px; flex: 0 0 46px; display: grid; place-items: center;
  color: var(--accent) }
.dev-ico svg { width: 34px; height: 34px }
.dev-b { flex: 1; min-width: 0 }
.dev-k { font-weight: 650; font-size: 15px }
.dev-v { font-size: 14.5px; margin-top: 3px }
.dev-s { font-size: 13px; color: var(--ink-3); margin-top: 2px }
.legend6 { display: grid; grid-template-columns: repeat(6, minmax(0,1fr)); gap: 14px;
  padding: 16px 22px; border-top: 1px solid var(--line) }
.legend6 div { display: flex; gap: 9px; align-items: flex-start; font-size: 13px }
.legend6 .n { font-weight: 600; color: var(--ink) }
.legend6 .s { color: var(--ink-3); font-size: 12.5px }
.nrow { display: flex; align-items: center; gap: 15px; padding: 15px 22px }
.nrow + .nrow { border-top: 1px solid var(--line) }
.nrow-ico { width: 30px; flex: 0 0 30px; display: grid; place-items: center; font-size: 17px }
.nrow-b { flex: 1; min-width: 0 }
.nrow-t { font-weight: 650; font-size: 14.5px }
.nrow-s { font-size: 13.5px; color: var(--ink-2); margin-top: 1px }
.nrow-w { font-size: 13px; color: var(--ink-3); flex: 0 0 auto }
.nrow-p { font-size: 13px; color: var(--ink-2); flex: 0 0 auto; max-width: 320px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap }

/* ---- Map: the column-and-connector estate view ------------------------------------------------------ */
.mapcols { display: grid; grid-template-columns: repeat(5, minmax(0,1fr)); gap: 34px; padding: 22px }
.mapcol h3 { font-size: 15px; font-weight: 650; text-align: center; margin: 0 0 18px }
.mapnode { border: 1px solid var(--line); border-radius: 12px; padding: 12px 14px; margin-bottom: 22px;
  display: flex; align-items: center; gap: 11px; background: var(--card); box-shadow: var(--shadow) }
.mapnode .ico { width: 32px; height: 32px; border-radius: 8px; flex: 0 0 32px; display: grid;
  place-items: center; background: var(--accent-soft); color: var(--accent); font-size: 14px }
.mapnode .b { flex: 1; min-width: 0 }
.mapnode .n { font-size: 13.5px; font-weight: 650; line-height: 1.25 }
.mapnode .s { font-size: 12px; color: var(--ink-3) }
.mapnode .dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px }
.maplegend { display: flex; gap: 20px; justify-content: flex-end; font-size: 13px; color: var(--ink-2);
  padding: 0 22px 4px }
.maplegend span { display: inline-flex; align-items: center; gap: 7px }

@media (max-width: 860px) {
  .stats.c4, .stats.c5, .calwrap, .mapcols, .legend6 { grid-template-columns: 1fr !important }
  .tabs { overflow-x: auto; gap: 18px }
  .segbox { flex-direction: column }
  .segbox button:first-child { border-radius: 10px 10px 0 0 }
  .segbox button:last-child { border-radius: 0 0 10px 10px }
  .segbox button + button { border-left: 1px solid var(--line); border-top: 0 }
  .nbrow { flex-direction: column; align-items: flex-start; gap: 8px }
  .apx-h { flex-wrap: wrap }
  .cal td { height: auto }
}

/* ---- COMPACT DENSITY -----------------------------------------------------------------------
   🚨 THIS BLOCK IS WHY THE DENSITY SWITCH IS NOT A LIE. Until 31/08/2026 the preference was
   written to localStorage and read by NOTHING: the control moved and the console did not change.
   The approved mockup shows the control, so the answer was to make it real rather than to draw
   it dead or drop it — a switch that changes nothing is the false affordance this console
   forbids by name, and it was sitting in Settings.

   Compact tightens the three things that actually cost vertical space on a dense page: row
   height, card padding and the nav pitch. It does NOT shrink type — smaller text is a different
   preference, and Accessibility already owns the opposite of it. */
:root[data-density="compact"] .card { padding: 12px 16px; }
:root[data-density="compact"] .tile,
:root[data-density="compact"] .stat,
:root[data-density="compact"] .scard { padding: 13px 16px; }
:root[data-density="compact"] .ch { padding: 12px 16px 9px; }
:root[data-density="compact"] .lrow,
:root[data-density="compact"] .qrow,
:root[data-density="compact"] .nrow { padding: 8px 16px; }
:root[data-density="compact"] .tbl td { padding: 9px 14px; }
:root[data-density="compact"] .tbl th { padding: 9px 14px; }
:root[data-density="compact"] .nav a { padding: 9px 13px; }
:root[data-density="compact"] .irow,
:root[data-density="compact"] .kvl-row { padding: 8px 0; }
:root[data-density="compact"] .verdict { padding: 14px 16px; }

/* ---- the theme preview thumbnail (Settings) --------------------------------------------------
   MEASURED off design/reference/settings.png: the box occupies x 370-554 (185 wide) and
   y 350-488 (136 tall). The first attempt guessed 196x100 and the compare delta went UP, which
   is the loop earning its place — a guess that looks right is still a guess.
   It was inheriting a thin default and rendering as a sliver. It is DRAWN rather than a
   screenshot so it cannot go stale against the real console. */
.wire {
  width: 185px; height: 136px; flex: 0 0 185px; display: flex; overflow: hidden;
  border: 1px solid var(--line); border-radius: 10px; background: var(--card);
}
.wire i { display: block; height: 100%; }

/* ---- Tasks: the state markers inside the redesigned table ------------------------------------
   These classes are what test-new-console-pages.js asserts the STATE from — a coloured pill is a
   word, and a check cannot tell a word from a decision. They survived the redesign into the
   State cell rather than being dropped for it. */
.tbl .tick { width: 15px; height: 15px; border-radius: 50%; display: inline-block;
  border: 1.5px solid var(--line); vertical-align: -3px; margin-right: 4px; }
.tbl .tick.done { background: var(--green); border-color: var(--green); color: #fff;
  font-size: 10px; line-height: 12px; text-align: center; }
.tbl .tick.checking { border-color: var(--accent); border-top-color: transparent; }
.tbl .t-due.od { color: var(--red); font-weight: 600; }

/* ═══════════════════════════════════════════════════════════════════════════════════════════
 * REG-S18-1 — Notifications + Approvals, rebuilt to the approved designs.
 *
 * APPENDED, NEVER RESTRUCTURED, per the S18 collision rule: every other S18 row owns different
 * .html files but shares this stylesheet. Nothing above this line is touched.
 *
 * The .apx / .dev / .legend6 / .nrow components themselves already existed (added by the Settings
 * and Tasks rebuild, 733702c) and were carrying NO markup — this block only fills the gaps that
 * showed up once real markup was hung on them.
 * ═══════════════════════════════════════════════════════════════════════════════════════════ */

/* 🚨 icon() EMITS <svg class="ico">, AND THREE EXISTING RULES SIZE `.ico` BY DESCENT.
 * `.apx-confirm .ico` is a 40px circle; an icon() svg dropped inside it inherits that 40px and
 * fills the whole circle. Every wrapper below therefore sizes its own child svg explicitly
 * rather than letting it inherit the wrapper's box. */
.apx-confirm .ico svg { width: 20px; height: 20px }
.apx-logo svg, .apx-state svg, .strip svg { width: 18px; height: 18px; flex: 0 0 18px }
.nrow-ico svg { width: 20px; height: 20px }
.legend6 svg { width: 17px; height: 17px; flex: 0 0 17px; margin-top: 1px }

/* The six-state legend's inner text column.
 * 🚨 `.legend6 div` (line ~1000) matches EVERY div inside the legend, the inner text wrapper
 * included, so a nested wrapper became a flex ROW and laid the state's name beside its caption
 * instead of above it. Spans sidestep that selector entirely; these make them stack. */
.legend6 .lg-b, .legend6 .lg-b .n, .legend6 .lg-b .s { display: block }
/* The state this device is ACTUALLY in, marked in the legend. A legend where every cell looks
 * identical is a picture of six possibilities; this makes it say which one you are looking at. */
.legend6 .lg-on .n { color: var(--accent) }
.legend6 .lg-on .s { color: var(--ink-2) }

/* The row's chevron. It is an <a>, not a decorative glyph: the whole point of the design's
 * chevron is that the row goes somewhere, and a chevron that is not a link is a false affordance. */
.nrow-go { display: grid; place-items: center; width: 30px; height: 30px; flex: 0 0 30px;
  border-radius: 8px; color: var(--ink-3) }
.nrow-go:hover { background: var(--grey-soft); color: var(--accent) }
.nrow-go svg { width: 18px; height: 18px }
/* An interrupt whose class is not one of the four is shown LOUDLY rather than dropped. Carried
 * over from the card layout this replaced — the accent is the whole warning at row density. */
.nrow.is-odd { border-left: 3px solid var(--red) }
.nrow-warn { font-size: 12.5px; color: var(--red); margin-top: 5px; max-width: 92ch }

/* Approvals: the honest counterpart to the reference's send strip. See approvals.html's header. */
.apx-note { font-size: 12.5px; color: var(--ink-3); padding: 0 22px 18px; max-width: 96ch }
.apx-confirm .apx-btns { flex: 0 0 auto }

/* 🚨 THE PRIMARY ACTION COMES OFF THE THUMB'S RESTING POSITION ON A PHONE (REG-S18-1).
 * On the desktop design the confirm strip's primary sits bottom-right, which is exactly where a
 * right-handed thumb rests on a 390px screen — so the one control that reaches a client would be
 * the easiest on the page to hit by accident. Below 640px the strip stacks and the order is
 * REVERSED: the cancel lands nearest the thumb and the primary has to be reached for. */
@media (max-width: 640px) {
  .apx-confirm { flex-direction: column; align-items: stretch; gap: 12px }
  .apx-confirm .apx-btns { flex-direction: column-reverse; gap: 10px }
  .apx-confirm .apx-btns .btn { width: 100%; justify-content: center }
  .apx-h { gap: 12px }
  .apx-btns { width: 100% }
  .apx-btns .btn { flex: 1; justify-content: center }
  .nrow { flex-wrap: wrap; gap: 8px 12px }
  .nrow-w, .nrow-p { flex: 0 0 auto }
  .dev { flex-wrap: wrap }
}

/* ---- the sign-in gate, REBUILT to design/reference/sign-in.png (REG-S18-5, 31/08/2026) --------
   APPENDED, never restructured. The four .gate-* rules further up this file are the originals and
   are left exactly as they were; every rule below either adds a new class or beats an original on
   specificity (.gate-full .gate-card is 0,2,0 against .gate-card's 0,1,0). That is the S18
   collision rule: this file is shared, so another session's page must not move under my edit.

   🚨 EVERY NUMBER HERE WAS MEASURED OFF THE REFERENCE, NOT CHOSEN, and three of them were measured
   TWICE because the first reading was wrong. Read at 1672x941, 1:1 with the shoot: card 644 wide
   from x204 to x848, 44px side padding (the button's left edge is x249), button 72 tall from y554
   to y626, "Console" cap height 36px, footer icon 52px across.
     · The logo is 63px tall here, NOT the sidebar's 44px. Guessing that the gate reuses the shell's
       logo height made it 166x43 against a reference of 260x63 — the same class of miss, on the
       same asset, that STATUS.md already records from the shell rebuild.
     · The first pass at the type scale was uniformly ~15% small: title 46 not 49, button label 21
       not 25, footer 17 not 19. Measured off ink extents, not judged by eye.
     · The delta went UP from 5.46 to 6.15 when those sizes were corrected, because a taller card
       re-centres and every horizontal edge moves. The sizes are right and the number got worse;
       that is the compare loop being read properly rather than chased. */
.gate-full .gate-shell { width: 100%; max-width: 642px; }
.gate-logo { height: 63px; width: auto; display: block; margin: 0 auto 50px; }
:root[data-theme="dark"] .gate-logo { filter: brightness(0) invert(1); }

.gate-full .gate-card {
  max-width: none; width: 100%; padding: 46px 44px 62px; border-radius: 16px;
  box-shadow: var(--shadow);
}

.gate-icon { color: var(--accent); margin: 0 auto 32px; width: 110px; }
.gate-icon svg { width: 100%; height: auto; display: block; }
.gate-icon.is-bad { color: var(--red); width: 62px; }

.gate-title { margin: 0; font-size: 49px; line-height: 1.1; font-weight: 700;
  letter-spacing: -.025em; color: var(--ink); }
.gate-lede { margin: 20px 0 0; font-size: 24px; line-height: 1.45; color: var(--ink-2); }

/* The message is the one part of this card the design does NOT get to simplify — it carries the
   account Google actually used, or the reason the config could not be read. It reads as body copy
   rather than as the old red panel, because a red panel around a two-line sentence pushed the
   button below the fold on a phone, but it keeps the red ink so it can never be mistaken for the
   ordinary prompt. That misreading IS the 20/08/2026 incident: two near-identical grey lines. */
.gate-card .gate-msg {
  background: none; padding: 0; margin: 20px 0 0; border-radius: 0;
  font-size: 21px; line-height: 1.5; color: var(--red); text-align: center;
}

.gate-full .gate-card .btn {
  margin-top: 46px; display: flex; align-items: center; justify-content: center; gap: 26px;
  min-height: 72px; padding: 0 20px; border-radius: 11px;
  background: var(--card); color: var(--ink); border: 1px solid var(--line);
  font-size: 25px; font-weight: 700; letter-spacing: -.01em;
}
.gate-full .gate-card .btn:hover { border-color: var(--accent); }
/* A failure state's button is the RECOVERY action, so it is drawn as one — the reference's own
   treatment for both state cards. Selected by the presence of the message rather than by a new
   class, so it cannot drift out of step with the state that produced it. */
.gate-card .gate-msg ~ .btn { color: var(--accent); border-color: var(--accent); font-size: 22px; }
.gate-gicon { width: 36px; height: 36px; flex: 0 0 36px; }

.gate-foot {
  display: flex; align-items: center; gap: 19px; margin: 66px 0 0; text-align: left;
  font-size: 19px; line-height: 1.55; color: var(--ink-2);
}
.gate-foot-icon {
  width: 52px; height: 52px; flex: 0 0 52px; border-radius: 50%;
  background: var(--accent-soft); color: var(--accent);
  display: flex; align-items: center; justify-content: center;
}
.gate-foot-icon svg { width: 26px; height: 26px; }

/* The phone. The card is the whole screen at 390px, so the desktop type scale has to come down or
   "Configuration failed" wraps to three lines above a button nobody can see without scrolling. */
@media (max-width: 620px) {
  .gate-full { padding: 18px; }
  .gate-logo { height: 40px; margin-bottom: 30px; }
  .gate-full .gate-card { padding: 32px 22px 36px; }
  .gate-icon { width: 78px; margin-bottom: 24px; }
  .gate-icon.is-bad { width: 52px; }
  .gate-title { font-size: 30px; }
  .gate-lede, .gate-card .gate-msg { font-size: 16px; }
  .gate-full .gate-card .btn { min-height: 58px; font-size: 17px; margin-top: 28px; gap: 14px; }
  .gate-card .gate-msg ~ .btn { font-size: 16px; }
  .gate-gicon { width: 24px; height: 24px; flex: 0 0 24px; }
  .gate-foot { margin-top: 38px; gap: 14px; font-size: 14.5px; }
  .gate-foot-icon { width: 42px; height: 42px; flex: 0 0 42px; }
  .gate-foot-icon svg { width: 21px; height: 21px; }
}

/* ═════════════════════════════════════════════════════════════════════════════════════════════
 * TASK DETAIL (task.html) + TRACE's chip states — appended for REG-S18-3, 31/08/2026.
 *
 * 🚨 APPENDED, NOT WOVEN IN, and every new class is prefixed `td-`. Three other S18 rows are
 * editing this file at the same time, and a bare new class name here has twice silently restyled
 * a page that already used it (.kv and .tl). Grepped before naming: `td-` appeared zero times.
 *
 * 🚨 THIS BLOCK HAS BEEN LOST ONCE ALREADY TODAY. A concurrent row rewrote console.css wholesale
 * rather than appending, and took this with it — the render came back with display:block on a bar
 * that must be flex, which read as a layout bug in task.html rather than as a missing stylesheet.
 * If it is missing again, that is what happened; re-append rather than restyling the page.
 * ═════════════════════════════════════════════════════════════════════════════════════════════ */
.td-badges { display: flex; align-items: center; gap: 10px; margin: 2px 0 4px }
.td-card { padding: 22px 24px; margin-bottom: 16px }
.td-card h2 { margin: 0 0 10px; font-size: 19px; font-weight: 650 }
.td-card p { margin: 0 0 8px; font-size: 14.5px; line-height: 1.65; color: var(--ink) }
.td-card p:last-child { margin-bottom: 0 }
.td-src { margin-top: 12px; width: 100%; justify-content: flex-start; word-break: break-all }

/* The confirm. Bordered in the accent rather than the danger red on purpose: recording a tick is
 * not destructive, and a red box here would train the eye to ignore red on the pages where the
 * action IS destructive. */
.td-confirm { padding: 20px 22px; border-color: var(--accent); margin-bottom: 16px }
.td-confirm h2 { margin: 0 0 8px; font-size: 18px; font-weight: 650 }
.td-confirm p { margin: 0 0 8px; font-size: 14px; line-height: 1.6 }
.td-cmd { background: var(--grey-soft); border-radius: 8px; padding: 9px 12px;
  font-size: 13px; word-break: break-all }
.td-actions { display: flex; gap: 10px; margin-top: 16px }

/* 🚨 THE PRIMARY ACTION SITS IN ITS OWN FOOTER AT THE END OF THE PAGE, right-aligned but below
 * the fold on a phone rather than under the thumb's resting position. Same reasoning the
 * Approvals row carries: a consequential control where the thumb already rests is one mis-tap
 * from happening. */
.td-foot { display: flex; align-items: center; gap: 12px; padding: 18px 22px; margin-top: 4px }
.td-foot .spacer { flex: 1 }

/* ---- Trace's source chips: the SELECTED state. .srcchip already existed for the centred search
 * but had no selected state, because it was drawn as a static legend. The chips are now the only
 * route to the other three sources, so which one is active has to be visible or the page silently
 * lies about what it just searched. */
.srcchip { cursor: pointer; font: inherit; font-size: 14px }
.srcchip.on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent) }
.bigempty svg { display: block; margin: 0 auto }

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
 * REG-S18-4 — Calendar header + Estate map. APPENDED, nothing above restructured.
 *
 * Every class below was grepped against this file before it was named: console.css is 1,000+ lines
 * and .kv and .tl once silently restyled two pages by colliding. The map's classes carry an `emap-`
 * prefix because `.map-cols` / `.map-canvas` / `.map-legend` already exist above for the OLD
 * blast-radius layout and are still referenced by the responsive blocks at ~336 and ~553. They are
 * left exactly as they are rather than repurposed.
 * ═══════════════════════════════════════════════════════════════════════════════════════════════ */

/* ---- Calendar: the header row the mockup draws — title, Today, ‹ month › --------------------- */
.cal-head { display: flex; align-items: center; gap: 14px; margin-bottom: 20px; flex-wrap: wrap; }
.cal-head h1 { margin: 0; }
.cal-month { font-size: 19px; font-weight: 600; min-width: 148px; text-align: center; }
.btn.cal-nav { padding: 6px 13px; font-size: 17px; line-height: 1; }
.cal-legend { display: flex; gap: 18px; flex-wrap: wrap; margin-top: 14px; }
.cal-key { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--ink-2); }
.cal-more { color: var(--ink-3); padding-left: 2px; }

/* The day panel beside the grid. `.dayrow` above owns the 62px time gutter; `.notime` collapses it,
   because a due date in tasks.yaml is a DAY and an empty gutter reads as a failed read. */
.dayrow.notime { grid-template-columns: 1fr; }
.cal-panel-head { display: flex; align-items: baseline; justify-content: space-between;
  margin-bottom: 10px; }
.cal-panel-head h2 { margin: 0; font-size: 19px; }
.cal-date { font-size: 13px; color: var(--accent); font-weight: 600; }
.cal-none { border: 1px dashed var(--line); border-radius: 10px; padding: 14px; }
.cal-none b { display: block; font-size: 14px; margin-bottom: 5px; }
.cal-none span { font-size: 12.5px; color: var(--ink-2); line-height: 1.45; }
.cal-up { border-top: 1px solid var(--line); margin-top: 16px; padding-top: 14px; }
.cal-up h3 { margin: 0 0 10px; font-size: 14px; }
.cal-up-row { display: flex; gap: 10px; align-items: flex-start; padding: 6px 0; }
.cal-up-t { font-size: 13px; font-weight: 600; line-height: 1.35; }
.cal-up-row .dot { margin-top: 5px; }
.cal-full { display: inline-block; margin-top: 12px; font-size: 13.5px; color: var(--accent);
  font-weight: 600; text-decoration: none; }

/* ---- Estate map: five columns of node cards -------------------------------------------------- */
.emap-card { padding: 20px; }
.emap-bar { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-bottom: 14px; }
.emap-search { flex: 0 1 300px; }
.emap-sel { border: 1px solid var(--line); border-radius: 9px; background: var(--card);
  color: var(--ink); font: inherit; font-size: 13.5px; padding: 9px 12px; }
.emap-legend { display: flex; gap: 16px; margin-left: auto; flex-wrap: wrap; }

.emap-note { margin-bottom: 16px; }
.emap-nr { font-size: 12.5px; color: var(--ink-2); line-height: 1.5;
  background: var(--grey-soft); border-radius: 9px; padding: 10px 13px; }
.emap-nr-h { font-weight: 600; color: var(--ink); }
.emap-sel-line { font-size: 13px; margin-bottom: 10px; display: flex; gap: 10px;
  align-items: center; flex-wrap: wrap; }
.emap-sel-line .btn { padding: 3px 10px; font-size: 12px; }

.emap-scroll { overflow-x: auto; }
.emap-cols { position: relative; display: grid; grid-template-columns: repeat(5, minmax(168px, 1fr));
  gap: 22px; align-items: start; transform-origin: top left; min-width: 940px; }
.emap-col-h { font-size: 13.5px; font-weight: 700; text-align: center; margin-bottom: 12px;
  display: flex; gap: 7px; justify-content: center; align-items: center; }
.emap-count { font-size: 11.5px; font-weight: 600; color: var(--ink-3);
  background: var(--grey-soft); border-radius: 20px; padding: 1px 7px; }
.emap-node { position: relative; border: 1px solid var(--line); border-radius: 11px;
  background: var(--card); padding: 11px 26px 11px 13px; margin-bottom: 12px; cursor: pointer;
  transition: border-color .12s, opacity .12s; }
.emap-node:hover, .emap-node:focus { border-color: var(--accent); outline: none; }
.emap-node.on { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.emap-node.dim { opacity: .38; }
.emap-n-t { font-size: 13px; font-weight: 600; line-height: 1.3; }
.emap-n-s { font-size: 11.5px; color: var(--ink-3); margin-top: 2px; }
.emap-node .dot { position: absolute; right: 11px; top: 14px; }
.emap-nomatch { font-size: 12.5px; color: var(--ink-3); text-align: center; padding: 14px 6px;
  border: 1px dashed var(--line); border-radius: 10px; }

/* The connectors. Drawn ONLY from recorded links — there is no decorative fallback anywhere. */
.emap-svg { position: absolute; inset: 0; pointer-events: none; overflow: visible; }
.emap-link { fill: none; stroke: var(--line); stroke-width: 1.5px; }
.emap-link.on { stroke: var(--accent); stroke-width: 2px; }

.emap-zoom { display: flex; gap: 8px; align-items: center; justify-content: flex-end;
  margin-top: 16px; font-size: 12.5px; color: var(--ink-2); }
.emap-zoom .btn { padding: 5px 11px; }

/* 🚨 THE PHONE STORY IS A LIST, NOT A PAN-AND-ZOOM CANVAS. Five columns at 390px is unusable, so
   under 900px the grid becomes one column per section, opened one at a time by scrolling, and the
   zoom control is hidden rather than shrunk — zooming a single-column list does nothing useful.
   The connectors are hidden too: stacked vertically they would join nodes that sit above and below
   each other, which draws a relationship the layout no longer expresses. */
@media (max-width: 900px) {
  .calwrap { grid-template-columns: 1fr; }
  .emap-scroll { overflow-x: visible; }
  .emap-cols { grid-template-columns: 1fr; min-width: 0; transform: none !important;
    width: 100% !important; gap: 26px; }
  .emap-col-h { justify-content: flex-start; border-bottom: 1px solid var(--line);
    padding-bottom: 8px; }
  .emap-zoom, .emap-svg { display: none; }
  .emap-legend { margin-left: 0; }
}

/* ---- Calendar: the cell must not be stretched by a 100-character task title (REG-S18-4) -------
   The mockup's events are invented and short ("Client Review / Solvatek"). Ours are real task
   titles averaging far longer, and unclamped they blew one week's row to four times the height of
   the others and threw every row below it — measured at delta 11.37, 19/48 regions off.
   🚨 THE CLAMP IS VISUAL ONLY AND HIDES NOTHING. The full title rides in the chip's `title`
   attribute, the Today panel and the Next-7-days list print it in full, and the "+N more" line
   already states how many events a day holds beyond the three drawn. Truncating the DISPLAY of an
   event that is counted and reachable is not the same as dropping it. */
.cal td { overflow: hidden; }
.cal .ev {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  max-height: 3.1em;
}
.cal .ev b { -webkit-line-clamp: 1; }

/* ---- Calendar round 3 was MEASURED AND REVERTED, and the fact is kept (REG-S18-4) -------------
   Clamping the Today panel's rows to two lines and tightening their padding looked like the same
   win as the grid clamp. Measured, it was not: delta went 10.08 -> 10.47 and regions off 15 -> 16,
   because the tighter rows moved every item up and broke the alignment the design keeps against
   the grid beside it. Reverted to the round-2 state. Recorded rather than silently dropped, so the
   next session does not spend its three rounds rediscovering it. */

/* ---- Estate map: the node card's leading mark (REG-S18-4, round 2) ----------------------------
   The design puts an icon on every node card and the first build had none, which left the cards
   short and the whole grid out of alignment against the reference. A client's mark is its real
   initials; every other column gets a neutral shape. No brand logo is drawn — see markFor() in
   map.html for why that is a truth decision rather than an aesthetic one. */
.emap-node { display: flex; align-items: center; gap: 11px; }
.emap-n-body { min-width: 0; flex: 1; }
.emap-mark {
  flex: 0 0 30px; width: 30px; height: 30px; border-radius: 8px; display: flex;
  align-items: center; justify-content: center; font-size: 11.5px; font-weight: 700;
  background: var(--grey-soft); color: var(--ink-2);
}
.emap-mark.clients   { background: var(--accent-soft); color: var(--accent); }
.emap-mark.workspaces{ background: var(--accent-soft); color: var(--accent); font-weight: 400; }
.emap-mark.agents    { background: var(--green-soft);  color: var(--green);  font-weight: 400; }
.emap-mark.gates     { background: var(--amber-soft);  color: var(--amber);  font-weight: 400; }
.emap-mark.systems   { background: var(--grey-soft);   color: var(--ink-3);  font-weight: 400; }

/* ---- Tasks: the state ruling of 31/08/2026 (Hamish) ------------------------------------------
 * "the state should be not done, done, waiting and checking" — and "Not done" is two words, so
 * the pill wrapped to two lines in the State column and the table grew a ragged middle. The pill
 * never wraps; the column takes the width it needs.
 * `trow-gap` is the empty stand-in where a not-done row's icon used to be: same width, nothing
 * drawn. Without it the titles sat ragged, done rows indented and unfinished rows flush. */
.tbl td .pill { white-space: nowrap }
.trow-gap { width: 34px; flex: 0 0 34px }

/* ---- Calendar: an event that opens its task should look like it does (REG-S18-4) -------------
   Added 31/08/2026 when Hamish looked at the live page and asked for it. The affordance matters
   more here than on a table row: a calendar chip reads as a label, so without a hover state
   nobody discovers it is a link. Keyboard focus gets the same treatment - these carry
   role="link" and tabindex, so they are reachable by tab and must be visible when they are. */
.ev.nav-task, .dayrow .b.nav-task, .cal-up-row.nav-task { cursor: pointer; }
.ev.nav-task:hover, .ev.nav-task:focus { filter: brightness(0.95); outline: none; }
.dayrow .b.nav-task:hover, .dayrow .b.nav-task:focus,
.cal-up-row.nav-task:hover, .cal-up-row.nav-task:focus {
  background: var(--grey-soft); outline: none;
}
.ev.nav-task:focus-visible, .dayrow .b.nav-task:focus-visible,
.cal-up-row.nav-task:focus-visible { box-shadow: 0 0 0 2px var(--accent); }
.cal-up-row.nav-task { border-radius: 8px; padding: 6px 8px; margin: 0 -8px; }

/* ---- Tasks: a row the brain CHECKED and could not verify (Hamish, 31/08/2026) -----------------
 * "highlighted red by the whole row because it's been checked and doesn't complete."
 * 🚨 Keyed on evidence === false — the brain looked and found nothing. NOT on evidence null,
 * which is the 490 rows nobody has ever checked. Colouring those red would assert a search that
 * never happened, on almost the whole tracker. */
.trow-failed > td { background: var(--red-soft) }
.trow-failed > td:first-child { box-shadow: inset 3px 0 0 var(--red) }

/* ============================================================================================
 * "SOON" — a control that is drawn and is deliberately NOT offered yet. Added 02/09/2026.
 *
 * WHY. The interaction sweep clicked every control on all 23 console pages and found 73 that
 * did nothing: the topbar Search and Help, the fleet pill, and the home page's "Needs you"
 * buttons. None was broken — none had ever been wired, and every one of them looked exactly
 * like the controls that work. Hamish's ruling, already live on the client portal: anything we
 * do not need yet is marked coming soon.
 *
 * These rules style a <span class="… is-off" data-soon="…">, never a disabled <button>. The tag
 * is the control, not the styling: a disabled button is one stray attribute away from being live
 * again with nothing behind it. Same reasoning as the portal's sf-navitem.is-off.
 * ============================================================================================ */
.is-off[data-soon] {
  opacity: .55;
  cursor: default;
  pointer-events: none;                 /* it cannot even take a hover state it will not honour */
  position: relative;
}
.is-off[data-soon] .soon {
  font-style: normal;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ink-3);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 1px 5px;
  margin-left: 6px;
  white-space: nowrap;
}
/* The topbar icons are square and have no room for a chip beside the glyph, so theirs sits
   under it as a hairline rather than pushing the bar out of shape. */
.ti.is-off[data-soon] .soon {
  position: absolute;
  bottom: -2px;
  left: 50%;
  transform: translateX(-50%);
  border: 0;
  padding: 0;
  font-size: 8px;
  margin: 0;
  background: transparent;
}
/* The fleet pill keeps its layout: it is still a readout, it just is not a control. */
.fleet-pill.is-off[data-soon] { display: flex; align-items: center; gap: 10px; }

/* Sign out. Ruled by Hamish 01/09/2026: until now this console offered no way to sign out at
   all, while health.html has been telling people to "sign out and back in if it persists".
   It sits after the avatar and is deliberately NOT another soon-chip: the controls either side
   of it are inert, so the one that works has to look like it. */
.ti-signout { color: var(--ink-2); }
.ti-signout:hover:not(:disabled) { color: var(--accent); border-color: var(--accent); }
.ti-signout:disabled { opacity: .55; cursor: default; }

/* ---- page to page without a white flash (15/09/2026) ----------------------------------------
 * Every click here is a full page load, so the browser used to blank the screen between pages.
 * This asks it (Chrome/Edge 126+, Safari 18.2+) to hold the old page on screen until the new
 * one has drawn, then swap in a fraction of a second. CSS only: no script, nothing for the
 * Content-Security-Policy to allow, and browsers without it simply load the page as before.
 * Kept very short on purpose: this is about the page feeling instant, not about animating. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
  ::view-transition-old(root), ::view-transition-new(root) { animation-duration: 90ms; }
}

/* ============================================================================================
 * TASKS AS A CARD BOARD (15/09/2026). Hamish: "it almost needs its own long card and you should
 * be able to click on it ... filter by everything." One long card per task, opened in place.
 * The urgency stripe on the left edge is the first thing the eye reads; the state word and its
 * mark keep the 31/08 rulings (no tick on anything not done, the triangle only on evidence false).
 * ============================================================================================ */
.tb-tools { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; margin-bottom: 12px; }
.tb-search { flex: 1 1 320px; min-width: 0; }
.tb-search input {
  width: 100%; box-sizing: border-box; border: 1px solid var(--line); border-radius: 12px;
  background: var(--card); color: var(--ink); font: inherit; font-size: 15px; padding: 11px 14px;
}
.tb-search input:focus { outline: 2px solid var(--accent); outline-offset: 1px; border-color: transparent; }
.tb-sort { display: flex; align-items: center; gap: 8px; color: var(--ink-2); font-size: 14px; }
.tb-sort select {
  border: 1px solid var(--line); border-radius: 10px; background: var(--card); color: var(--ink);
  font: inherit; font-size: 14px; padding: 9px 10px;
}
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.tb-refine { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 18px; }
.tb-lbl { font-size: 12px; font-weight: 650; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-3); margin-right: 2px; }
.tb-sep { width: 1px; height: 22px; background: var(--line); margin: 0 6px; }
.tb-chip {
  border: 1px solid var(--line); background: var(--card); color: var(--ink-2); border-radius: 999px;
  padding: 6px 12px; font: inherit; font-size: 13.5px; cursor: pointer; transition: background .12s, border-color .12s;
}
.tb-chip:hover { border-color: var(--accent); color: var(--ink); }
.tb-chip.on { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
.tb-chip .n { opacity: .75; font-weight: 650; margin-left: 2px; }
.tb-clear { border: 0; background: none; color: var(--accent); font: inherit; font-size: 13.5px; cursor: pointer; padding: 6px 4px; }

.tb-board { display: flex; flex-direction: column; gap: 8px; }
.tb-group {
  margin: 14px 0 2px; font-size: 13px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase;
  color: var(--ink-2); display: flex; align-items: center; gap: 8px;
}
.tb-group:first-child { margin-top: 0; }
.tb-group .n { background: var(--grey-soft); color: var(--ink-2); border-radius: 999px; padding: 1px 8px; font-size: 12px; }
.tb-g-overdue, .tb-g-check { color: var(--red); }

.tb-card {
  position: relative; background: var(--card); border: 1px solid var(--line); border-radius: 14px;
  box-shadow: inset 4px 0 0 var(--tb-stripe, var(--line)); overflow: hidden;
  transition: box-shadow .15s, border-color .15s, transform .15s;
}
.tb-card:hover { border-color: color-mix(in srgb, var(--accent) 35%, var(--line)); }
.tb-card.is-open { border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); box-shadow: inset 4px 0 0 var(--tb-stripe, var(--line)), 0 6px 22px rgba(10, 32, 63, .08); }
.tb-overdue { --tb-stripe: var(--red); }
.tb-today   { --tb-stripe: #f97316; }
.tb-week    { --tb-stripe: var(--amber); }
.tb-later   { --tb-stripe: var(--accent); }
.tb-nodate  { --tb-stripe: var(--line); }
.tb-done    { --tb-stripe: var(--green); opacity: .82; }
.tb-card.trow-failed { background: var(--red-soft); --tb-stripe: var(--red); }

.tb-head { display: flex; align-items: center; gap: 14px; padding: 14px 16px 14px 20px; cursor: pointer; }
.tb-head:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 14px; }
.tb-main { flex: 1 1 auto; min-width: 0; }
.tb-title { font-weight: 620; font-size: 15px; line-height: 1.35; color: var(--ink); }
.tb-meta { display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 4px; font-size: 13px; color: var(--ink-3); }
.tb-ctx { max-width: 44ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tb-side { display: flex; align-items: center; gap: 14px; flex: 0 0 auto; }
.tb-side .t-due { white-space: nowrap; }
.tb-state { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.tb-chev { color: var(--ink-3); font-size: 20px; transition: transform .15s; }
.tb-card.is-open .tb-chev { transform: rotate(90deg); }

.tb-body { border-top: 1px solid var(--line); padding: 14px 20px 16px 68px; animation: tb-in .14s ease-out; }
@keyframes tb-in { from { opacity: 0; transform: translateY(-3px) } to { opacity: 1; transform: none } }
.tb-dl { display: grid; grid-template-columns: 150px 1fr; gap: 8px 16px; margin: 0 0 14px; }
.tb-dl dt { color: var(--ink-3); font-size: 13px; font-weight: 600; }
.tb-dl dd { margin: 0; color: var(--ink); font-size: 14px; line-height: 1.45; overflow-wrap: anywhere; }
.tb-actions { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.tb-actions a.btn { text-decoration: none; }

@media (max-width: 760px) {
  .tb-head { flex-wrap: wrap; padding-left: 16px; }
  .tb-side { width: 100%; justify-content: flex-start; padding-left: 48px; flex-wrap: wrap; }
  .tb-body { padding-left: 16px; }
  .tb-dl { grid-template-columns: 1fr; gap: 2px; }
  .tb-dl dd { margin-bottom: 8px; }
  .tb-sep { display: none; }
}
@media (prefers-reduced-motion: reduce) { .tb-body { animation: none; } .tb-chev, .tb-card { transition: none; } }
