/* ═══════════════════════════════════════════════════════════════════════════
   PaperOS Support — ticket board
   Near-black + 14k gold. The board is an instrument: mono for data, serif for
   moments, one signature (the living thread), one load overture. Everything
   else disciplined.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  --bg: #0a0a0c;
  --bg-raised: #101014;
  --surface: #141418;
  --surface-2: #1a1a1f;
  --border: #26262d;
  --border-strong: #34343c;
  --border-subtle: #1d1d22;
  /* the seam (ruled 2026-08-21): the hairline that splits ONE block into its
     sections — the filter block's halves, the charts, the rail's families.
     Wears the product switch's deep purple, cut to read at 1px on this
     ground. Outer block borders stay --border; a seam is never a border. */
  --seam: #7a4f72;
  --text: #ededef;
  --text-strong: #ffffff;
  --text-muted: #b0b0b8;
  --text-subtle: #808088;
  --gold: #d4a72c;
  --gold-hover: #e6b836;
  --gold-soft: #f5d57a;
  --gold-deep: #b88b1e;
  --gold-04: rgba(212, 167, 44, 0.04);
  --gold-08: rgba(212, 167, 44, 0.08);
  --gold-15: rgba(212, 167, 44, 0.15);
  --gold-25: rgba(212, 167, 44, 0.25);
  --gold-40: rgba(212, 167, 44, 0.4);
  --success: #3dd68c;
  --warn: #edb058;
  --error: #ea6660;
  --info: #6db9e8;
  /* the soft green every quiet-positive surface shares (origin-task, card
     tags, form links, praise) — was a literal #6fae87 in seven places;
     ONE definition since the calibration (ruled 2026-08-24) */
  --green-soft: #66c08c;
  /* the switch's solid seat (T1, ruled 2026-08-24) */
  --switch-ink: #8e5583;
  /* Status is one colour system across the board, the charts and the decks —
     the same three inks the deck cards stripe with, so a red card in the docket
     and a red card on the board mean the same thing. Open and in progress had
     been two golds, which read as one. Gold is the brand, not a status.
     THIS IS THE ONLY DEFINITION: dashboard.js and observatory.js read it from
     here rather than keeping their own copies. */
  /* DARK-CALIBRATED (ruled 2026-08-21, set D1): the originals were light-theme
     values (Bootstrap-era reds/greens meant for white) and a day of sliding
     their lightness only made them sink. On this ground an ink needs mid-
     high luminance and moderate saturation; the 13% tints carry the field.
     The status inks are the task lanes' anchors by the mirror ruling, so
     they move together — Support's columns, chips and charts follow. */
  --st-open: #e0564f;
  --st-progress: #4f8fe0;
  --st-closed: #4fb36e;
  /* Task lane inks (ruled 2026-08-20, re-inked 2026-08-21): the anchors
     mirror Support — birth wears the open-red, Completed the closed-green —
     Other is gray, and the middle four: orange for staged paperwork (the
     build's pick, dark-calibrated), then Jai's own three — deep teal
     #004D61 for in-flight (the in-progress rhyme ends here by his choice),
     lavender #B39CD0 for the wait on the customer, berry #822659 for the
     wait on the partner — used exactly as given. THE ONLY DEFINITION — the
     chart reads these from here. */
  --tp-awaiting: var(--st-open);
  --tp-ready: #e38a3c;
  --tp-submitted: #004D61;
  --tp-client: #B39CD0;
  --tp-partner: #822659;
  --tp-other: #8d8d96;
  --tp-completed: var(--st-closed);
  /* Jai's three (ruled 2026-08-21, used EXACTLY as given — no muting, no
     lightening): deep teal for in-flight, lavender for the wait on the
     customer, berry for the wait on the partner. Fields — dot, head
     ground, card edge, chart bar — wear the hex itself. Where the ink is
     TEXT (the progress chips) the two dark ones borrow a companion of the
     same hue lifted to read — the lifted-tail precedent — ruled the same
     evening. Lavender is light enough to speak for itself. */
  --tp-submitted-text: #64c1d8;
  --tp-partner-text: #dd7eb3;
  /* the subtype ramp, calibrated (ruled 2026-08-24): gold → ember, floor
     lifted so the tail reads. ONE definition — the chips wear these and
     the donut reads them (dashboard.js cssInk), ending the two-copy era. */
  --sub-billing: #f7dc7e;
  --sub-workflow: #f2c34c;
  --sub-account: #eca43e;
  --sub-documents: #e2884a;
  --sub-legal: #d8764f;
  --sub-bug: #cc6355;
  --sub-other: #ab8168;
  --prio-high: #7a64e0;      /* lifted off the floor 2026-08-24 — same family */
  --prio-normal: #9880e6;
  --prio-low: #cbb8f2;
  --sans: 'Geist', system-ui, sans-serif;
  --serif: 'Instrument Serif', Georgia, serif;
  --mono: 'Geist Mono', ui-monospace, monospace;
  --ease: cubic-bezier(0.4, 0, 0.2, 1);

  /* ── the design language (DESIGN.md, ruled 2026-08-13) ──────────────────
     Type has ROLES, not sizes. Mono had been doing all five jobs, which is
     why a status, a button, a promise and an email address all looked alike.
     If a human wrote it or reads it as a sentence, it is not mono. */
  --t-display: 400 22px/1.25 var(--serif);
  --t-heading: 600 15px/1.35 var(--sans);
  --t-body: 400 14px/1.55 var(--sans);
  --t-caption: 400 11.5px/1.4 var(--sans);
  --t-machine: 500 10px/1.3 var(--mono);      /* + uppercase + tracking */
  --tracking-machine: 0.07em;

  /* Proximity is meaning: tight WITHIN a fact, clear BETWEEN facts, wide
     between sections. Replaces the ' · ' essay lines. */
  --sp-tight: 4px;
  --sp-fact: 14px;
  --sp-group: 28px;
}

/* The machine voice, one definition — every mono-caps run uses this instead
   of respelling the four properties (and drifting on the fifth). */
.machine {
  font: var(--t-machine);
  text-transform: uppercase;
  letter-spacing: var(--tracking-machine);
}

* { box-sizing: border-box; margin: 0; }

html { color-scheme: dark; }

body {
  background: var(--bg);
  color: var(--text);
  font: 14px/1.5 var(--sans);
  min-height: 100vh;
  overflow-x: hidden;
  /* a column, so the workspace can take whatever height the chrome above it
     leaves (asked 2026-08-21: the columns reach the page bottom, even empty) */
  display: flex;
  flex-direction: column;
}

/* Ambient observatory glow, anchored top-center where the thread lives */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(900px 360px at 50% -8%, rgba(212, 167, 44, 0.07), transparent 70%),
    radial-gradient(600px 300px at 92% 110%, rgba(212, 167, 44, 0.04), transparent 70%);
}

::selection { background: var(--gold-40); }

::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 4px; }
::-webkit-scrollbar-track { background: transparent; }

/* ═══ The thread ═══════════════════════════════════════════════════════════
   A continuous 2px gold line under the topbar. Its glow (a bright travelling
   segment) parks beneath whichever control owns the active filter. */
#thread {
  position: sticky;
  top: 0;
  height: 2px;
  z-index: 60;
  background: linear-gradient(90deg,
    transparent 0%, var(--gold-25) 18%, var(--gold-40) 50%, var(--gold-25) 82%, transparent 100%);
  transform-origin: left;
}
#thread-glow {
  position: absolute;
  top: -1px;
  left: 0;
  /* 150px matches GLOW_W in dashboard.js, which centres the light on the
     pointer. Width is fixed now — it used to take the size of whichever filter
     was clicked. */
  width: 150px;
  height: 4px;
  border-radius: 4px;
  background: radial-gradient(closest-side, var(--gold-soft), var(--gold) 55%, transparent);
  box-shadow: 0 0 18px 2px var(--gold-40), 0 0 46px 6px var(--gold-15);
  opacity: 0;
  /* 0.7s was right for jumping between filters and far too slow to follow a
     pointer — the light would arrive long after you did. At 0.2s, restarted
     each frame, it trails just enough to read as something with weight. */
  transition: transform 0.2s linear, opacity 0.45s var(--ease);
}
#thread-glow.lit { opacity: 1; }

/* ═══ Topbar ═══ */
#topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 18px 28px 14px;
}
.wordmark { display: flex; align-items: baseline; gap: 12px; }
/* The PaperOS mark. It replaces a ◈ glyph that turned on a 9s facet animation —
   the gradient text-clip and the spin were both built for type, and neither
   survives a raster: background-clip has nothing to clip, and a rotating logo
   reads as a loading spinner. The mark sits still and lets the wordmark talk. */
.wordmark-logo {
  width: 22px;
  height: 22px;
  transform: translateY(3px);
  opacity: 0.92;
}
.wordmark h1 {
  font-family: var(--serif);
  font-size: 26px;
  font-weight: 400;
  color: var(--text-strong);
  letter-spacing: 0.01em;
}
.wordmark h1 em { color: var(--gold-soft); }
.wordmark-env {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-subtle);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  padding-left: 6px;
}
.topbar-right { display: flex; align-items: center; gap: 14px; }

/* search lives in the filter block now (2026-08-20) — it fills its row, so
   the old focus-grows-width trick retired with the topbar seat */
#search-wrap { position: relative; flex: 1; min-width: 0; }
#search {
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  font: 13px var(--sans);
  padding: 8px 34px 8px 12px;
  transition: border-color 0.18s var(--ease);
}
#search::placeholder { color: var(--text-subtle); }
#search:focus { outline: none; border-color: var(--gold-40); }
#search-wrap kbd {
  position: absolute;
  right: 8px;
  top: 50%;
  translate: 0 -50%;
  font: 11px var(--mono);
  color: var(--text-subtle);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 6px;
}
/* ── the session corner (2026-08-20) ─────────────────────────────────────
   Identity is IDENTITY: plain text, no costume (DESIGN.md §2) — the email
   stopped being a secret door. The doors live behind one quiet menu. */
.session-corner { position: relative; }
.ident { font: 12.5px var(--mono); color: var(--text-subtle); }
.menu-btn {
  display: inline-grid;
  place-items: center;
  width: 31px; height: 31px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: none;
  color: var(--text-subtle);
  cursor: pointer;
  font-size: 15px;
  line-height: 1;
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease);
}
.menu-btn:hover, .menu-btn[aria-expanded="true"] { color: var(--text); border-color: var(--border-strong); }
.menu-pop {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  width: 190px;
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  box-shadow: 0 14px 40px rgba(0, 0, 0, 0.6);
  padding: 5px;
  z-index: 70;             /* above the thread (60) — it hangs from the nav */
}
.menu-pop[hidden] { display: none; }
.menu-pop .mp-id {
  font: 11px var(--mono);
  color: var(--text-subtle);
  padding: 7px 9px 8px;
  border-bottom: 1px solid var(--border-subtle);
  margin-bottom: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.menu-pop a, .menu-pop .mp-out {
  display: block;
  width: 100%;
  text-align: left;
  padding: 7px 9px;
  font: 12.5px var(--sans);
  color: var(--text-muted);
  border: 0;
  background: none;
  border-radius: 4px;
  text-decoration: none;
  cursor: pointer;
}
.menu-pop a:hover, .menu-pop .mp-out:hover { background: var(--gold-08); color: var(--text); }
.menu-pop .mp-out { color: var(--text-subtle); border-top: 1px solid var(--border-subtle); border-radius: 0 0 4px 4px; margin-top: 4px; }

/* ═══ Viz strip ═══ */
/* Collapsed, this section is nothing — no padding, no height. It exists to
   host the decks; its toggle moved into the filter row with the other board
   controls, and the ~50px band it used to occupy went back to the board. */
#viz { padding: 0 28px; }
#viz[data-expanded="true"] { padding-top: 4px; }
/* the count tiles were removed 2026-08-06; this focus ring is NOT theirs —
   it is the dashboard's only one, for every button, input and select */
button:focus-visible, input:focus-visible, select:focus-visible {
  outline: 2px solid var(--gold-40);
  outline-offset: 2px;
}
/* The door to the walnut room wears the room's own brass — the same material
   as the Decks' breadcrumb plates, deliberately NOT the board's black-gold.
   This control opens a window; it filters nothing, and it shouldn't dress
   like the filters (item 46). Two states, neither of them "normal": unlit
   brass when the room is shut, lit brass with a lamp-glow while it's open. */
#viz-toggle {
  display: grid;
  place-items: center;
  width: 30px; height: 30px;
  background: linear-gradient(180deg, #c9a86a, #a5824a);
  border: 1px solid rgba(141, 111, 66, 0.9);
  border-radius: 6px;
  color: #2a2117;
  cursor: pointer;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 240, 210, 0.4);
  transition: all 0.18s var(--ease);
}
#viz-toggle:hover { filter: brightness(1.1); }
#viz-toggle[data-open="true"] {
  background: linear-gradient(180deg, #eac67e, #bb9159);
  color: #1c150d;
  box-shadow: 0 0 0 1px rgba(201, 168, 106, 0.55),
              0 0 18px rgba(201, 168, 106, 0.4),
              inset 0 1px 0 rgba(255, 240, 210, 0.55);
}

.chart-card {
  /* the block owns the ground and the border; a card is a section of it */
  background: none;
  border: 0;
  border-radius: 0;
  padding: 14px 16px 10px;
  height: 196px;
  display: flex;
  flex-direction: column;
}
.chart-card + .chart-card { border-top: 1px solid var(--seam); }
/* the donut's nine legend rows sit UNDER the ring here — even the widened
   rail has no room beside it — so this one card is taller than its siblings */
.chart-card.tall { height: 340px; }
.chart-card h3 {
  font-size: 12px;
  font-weight: 500;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin-bottom: 8px;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}
.chart-hint { font-size: 10.5px; color: var(--text-subtle); text-transform: none; letter-spacing: 0; font-style: italic; }
/* Chart.js measures the canvas's PARENT to size itself. With the canvas as a
   direct flex child it claimed the whole card — heading included — drew into a
   box 26px taller than the element and let flexbox squash the result. Hit
   testing doesn't know about that squash, so every click read ~14% too low and
   the legend's 10px rows landed a full row above the one you aimed at. Giving
   the canvas its own box means the size Chart.js measures is the size it gets. */
.chart-canvas { flex: 1; min-height: 0; position: relative; }
.chart-canvas canvas { position: absolute; inset: 0; }

/* ═══ The filter block (recomposed 2026-08-20) ═══
   One dedicated band under the nav, enclosed by hairlines on raised ground,
   split by a vertical seam into two BEHAVIOURS: select/deselects left (tap
   it, it lights), instruments right (things that hold a value). Search fills
   the instruments' second row — widest instrument, closest to the cards it
   narrows. The brass door holds the far-end sliver. */
#filters.fblock {
  margin-top: 14px;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  background: var(--bg-raised);
  display: flex;
  align-items: stretch;
}
.f-left {
  flex: 1 1 52%;
  min-width: 0;
  padding: 12px 22px 14px 28px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  justify-content: center;
}
.filter-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.f-right {
  flex: 1 1 48%;
  border-left: 1px solid var(--seam);
  display: flex;
  align-items: stretch;
}
.f-instr {
  flex: 1;
  min-width: 0;
  padding: 12px 22px 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  justify-content: center;
}
/* the top row's three instruments share its width evenly (asked 2026-08-20);
   Clear joins the distribution when it appears */
.f-instr .filter-row:first-child { justify-content: space-between; }
/* the door: not an instrument — its own sliver, its own ground */
.f-door {
  border-left: 1px solid var(--seam);
  display: grid;
  place-items: center;
  padding: 0 16px;
  background: var(--surface);
}
/* below the board's own breakpoint the block stacks; the seam turns horizontal */
@media (max-width: 1100px) {
  #filters.fblock { flex-direction: column; }
  .f-right { border-left: 0; border-top: 1px solid var(--seam); }
}
/* max-height rather than the 0fr→1fr grid trick: the fr version resolved to a
   zero row in the engine this is rendered in, so the row opened logically and
   stayed invisible. 170px clears the chips even when they wrap. */
#subtype-wrap, #progress-wrap {
  overflow: hidden;
  max-height: 0;
  opacity: 0;
  /* visibility rides along (review 2026-08-24): a folded row's chips stayed
     TABBABLE — Tab+Enter on an invisible progress chip silently emptied the
     Support board. Hidden removes them from the tab order; the 0.3s delay
     lets the fold play before they vanish. */
  visibility: hidden;
  transition: max-height 0.3s var(--ease), opacity 0.22s var(--ease), visibility 0s 0.3s;
}
#subtype-wrap[data-open="true"], #progress-wrap[data-open="true"] {
  max-height: 170px; opacity: 1;
  visibility: visible;
  transition: max-height 0.3s var(--ease), opacity 0.22s var(--ease), visibility 0s 0s;
}
.subtype-row { padding-top: 3px; }
.subtype-lead {
  font-family: var(--mono);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--text-subtle);
}
/* The instrument switch (Jai's pick from the mockups, 2026-08-20): a true
   segmented control in the house metal — squared, hairline-bordered,
   machine-caps, live counts as flat facts inside. An Action (§2). Sits 28px
   clear of the wordmark: context, not a filter (§3). */
#product-toggle {
  display: inline-flex;
  /* the control that decides WHICH WORLD wears the seam's ink on its frame
     (T1, ruled 2026-08-24) — nothing else on the page is bordered purple */
  border: 1px solid var(--seam);
  /* the search bar beside it is the row's other instrument: same 8px radius,
     same 35px height (via the segment padding below) — one rank, one row
     (caught by Jai's eye, 2026-08-20) */
  border-radius: 8px;
  overflow: hidden;
  /* 4px + the topbar's own 24px gap = the law's 28 (--sp-group). A bare 28
     here stacked to 52 — measured live, 2026-08-20 */
  margin-left: 4px;
  align-self: center;
  /* the row's height is the search bar's 35 — pinned, not approximated */
  height: 35px;
  /* the topbar is nowrap flex — without this the switch is the element that
     gives, collapsing to its borders (caught live in the bench, 2026-08-20) */
  flex-shrink: 0;
}
#product-toggle button {
  border: 0;
  background: transparent;
  cursor: pointer;
  padding: 0 16px;
  height: 100%;
  display: flex;
  align-items: center;
  gap: 8px;
  /* the labels are the NAMES of the two products, not machine facts — they
     rank as headings (§1), above the search's text beside them: the control
     that decides which product exists on screen speaks loudest on its row
     (Jai's ruling, 2026-08-20) */
  font: 600 13.5px var(--sans);
  color: var(--text-subtle);
  transition: all 0.18s var(--ease);
}
#product-toggle button + button { border-left: 1px solid var(--border); }
#product-toggle button:hover { color: var(--text-muted); background: var(--surface-2); }
#product-toggle button[aria-selected="true"] {
  /* the switch's own ink (ruled 2026-08-20; SOLID SEAT ruled 2026-08-24):
     deep purple — the eggplant, worn FILLED. Gold means "a selection inside
     this world"; the control that decides WHICH WORLD is the only solid
     purple on the page, so it outranks everything at a glance. Shares the
     family with priority and the seams by ruling. */
  background: var(--switch-ink);
  color: #f6eaf2;
}
#product-toggle .n {
  /* the count stays a machine fact riding the name */
  font: var(--t-machine);
  letter-spacing: var(--tracking-machine);
  color: var(--text-subtle);
}
#product-toggle button[aria-selected="true"] .n { color: #e6cfdd; }

.filter-group { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* DE-PILLED (ruled 2026-08-20): a filter tag is an ACTION — bordered,
   squared, quiet until chosen. The 999px pill costume is dead on this page;
   the squared hairline family is the one the product switch already speaks. */
.label-chip, .filter-chip {
  border: 1px solid var(--border);
  background: none;
  color: var(--text-muted);
  font: 12px var(--sans);
  border-radius: 4px;
  padding: 5px 11px;
  cursor: pointer;
  transition: all 0.15s var(--ease);
}
.label-chip:hover, .filter-chip:hover { border-color: var(--gold-25); color: var(--text); }
/* the chip carries the same dot the cards do, so the control and the signal
   it isolates are visibly the same thing */
#filter-needs-reply { display: inline-flex; align-items: center; gap: 7px; }
/* `hidden` must WIN over the inline-flex above (the 2026-08-17 lesson, again):
   the chip hides under Tasks, where no channel can ever light it */
#filter-needs-reply[hidden] { display: none !important; }
#filter-needs-reply i {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--warn);
  box-shadow: 0 0 0 3px rgba(232, 168, 76, 0.14);
}
#filter-needs-reply[data-active="true"] { border-color: var(--warn); background: rgba(232, 168, 76, 0.1); color: var(--warn); }
.label-chip[data-active="true"] {
  border-color: var(--gold-40);
  background: var(--gold-08);
  color: var(--gold-soft);
}
.filter-select {
  /* the block is raised ground now — instruments recess to the page ink */
  background: var(--bg);
  border: 1px solid var(--border-subtle);
  border-radius: 4px;
  color: var(--text-subtle);
  font: 11.5px var(--mono);
  letter-spacing: 0.03em;
  padding: 7px 9px;
  cursor: pointer;
  transition: border-color 0.15s var(--ease), color 0.15s var(--ease);
}
.filter-select:hover { color: var(--text-muted); border-color: var(--border); }
/* One box holding both ends, so a range reads as a single instrument rather
   than two loose fields. color-scheme makes the browser's own calendar dark —
   without it a white picker drops out of the sky onto a black dashboard. */
.filter-dates {
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--bg);
  border: 1px solid var(--border-subtle);
  border-radius: 4px;
  padding: 3px 9px;
  color-scheme: dark;
}
.filter-dates:hover { border-color: var(--border); }
.date-kind {
  font: 11px var(--mono);
  letter-spacing: 0.04em;
  color: var(--text-subtle);
  padding-right: 2px;
  border-right: 1px solid var(--border-subtle);
  margin-right: 3px;
}
.date-arrow { color: var(--text-subtle); font-size: 11px; }
.filter-date {
  background: none;
  border: 0;
  color: var(--text-subtle);
  font: 11.5px var(--mono);
  padding: 4px 0;
  cursor: pointer;
}
.filter-date:focus { outline: none; color: var(--text); }
.filter-date:not([value=""]) { color: var(--text); }
.filter-select:not([value=""]) { color: var(--text); }
#filter-clear {
  border: 0;
  background: none;
  color: var(--text-subtle);
  font: 12px var(--sans);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
#filter-clear:hover { color: var(--gold-soft); }

/* ═══ Board ═══ */
/* Board and charts share one row: the cards were running ~394px per column
   against a ~300px kanban norm, and that slack is almost exactly what the rail
   needs — so the charts come down without anything getting cramped. */
#workspace {
  display: grid;
  /* the rail scales with the screen instead of sitting at a fixed 300px —
     ~26vw lands at 333px on a 1280 laptop and 420px on a big display, which
     is the difference between charts you squint at and charts you read */
  grid-template-columns: minmax(0, 1fr) clamp(320px, 26vw, 420px);
  gap: 14px;
  padding: 12px 28px 40px;
  /* fills the page (asked 2026-08-21): the board stretches to the bottom so
     empty lanes read as lanes, not as a band of nothing under the cards */
  flex: 1;
  align-items: stretch;
}
#board {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  /* columns stretch to the board's height — the board's one row takes all of
     it (grid's align-content: normal is stretch), so the lanes reach the
     bottom even when empty; min-height on .col stays as the floor */
  align-items: stretch;
}
/* ── the Tasks board: seven progress lanes (ruled 2026-08-20) ─────────────
   Each lane sets --lane-ink once; dot, head ground and card edge inherit it,
   so a lane's colour is written in exactly one place. */
#board[data-product="tasks"] {
  /* full-size cards, the ROW scrolls (asked 2026-08-20): seven stages at the
     kanban norm instead of seven squeezed columns. Sortable's auto-scroll
     walks the row when a drag nears an edge, so off-screen lanes are still
     drop targets. The row scrolls, never the page. */
  grid-template-columns: none;
  grid-auto-flow: column;
  grid-auto-columns: 300px;
  overflow-x: auto;
  padding-bottom: 6px;              /* the scrollbar sits off the lanes */
  gap: 12px;
}
.col[data-progress="Awaiting Review"] { --lane-ink: var(--tp-awaiting); }
.col[data-progress="Ready for Filing"] { --lane-ink: var(--tp-ready); }
.col[data-progress="Filing Submitted"] { --lane-ink: var(--tp-submitted); }
.col[data-progress="Waiting on Client"] { --lane-ink: var(--tp-client); }
.col[data-progress="Waiting on Partner"] { --lane-ink: var(--tp-partner); }
.col[data-progress="Other"] { --lane-ink: var(--tp-other); }
.col[data-progress="Completed"] { --lane-ink: var(--tp-completed); }
.col[data-progress] .col-dot { background: var(--lane-ink); box-shadow: 0 0 10px var(--lane-ink); }
.col[data-progress="Completed"] .col-dot { box-shadow: none; opacity: 0.8; }
.col[data-progress] .col-head { background: color-mix(in srgb, var(--lane-ink) 13%, transparent); }
/* Jai's three heads show their hue (ruled 2026-08-21 — "just not mute them
   or make them paler. Use as it is", then "too bright" at 100%): a 32% tint
   of the exact ink — the colour is plainly there, the head stays a ground.
   White title and count read on all three; the dot keeps the ink. */
.col[data-progress="Filing Submitted"] .col-head,
.col[data-progress="Waiting on Client"] .col-head,
.col[data-progress="Waiting on Partner"] .col-head { background: color-mix(in srgb, var(--lane-ink) 32%, transparent); }
.col[data-progress="Filing Submitted"] .col-head :is(h2, .col-count),
.col[data-progress="Waiting on Client"] .col-head :is(h2, .col-count),
.col[data-progress="Waiting on Partner"] .col-head :is(h2, .col-count) { color: #f2f2f4; }
.col[data-progress] .card { border-left-color: var(--lane-ink); }
.col[data-progress="Completed"] .card { opacity: 0.75; }
/* full-width lanes need no head squeeze — one compensation survives: seven
   names, one line each */
#board[data-product="tasks"] .col-head h2 { white-space: nowrap; }
/* Sticky, or the charts scroll away exactly when you'd reach for one to filter.
   Stacked they run a little past the viewport; the trend is last on purpose,
   so what falls below the fold is the chart you consult rather than watch. */
/* one block, not three cards (asked 2026-08-20) — the rail wears the roof
   and the charts become its sections, seamed the way the filter block is */
#charts-rail {
  position: sticky;
  top: 14px;
  align-self: start;                /* content-sized; the stretch is the board's */
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
}
/* (the charts-below-Tasks experiment lived here for an hour on 2026-08-20 —
   reverted once the lanes learned to scroll: the rail serves both products) */
/* the board wins the squeeze first — below this the rail would be unreadable
   before the columns are, so it steps out of the row entirely */
@media (max-width: 1180px) {
  #workspace { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr) auto; }
  #charts-rail { position: static; display: grid; grid-template-columns: repeat(3, 1fr); }
  /* 3-across: the seams turn vertical with the layout */
  .chart-card + .chart-card { border-top: 0; border-left: 1px solid var(--seam); }
}
/* Sharp and defined (asked 2026-08-20), the filter block's grammar page-wide:
   OUTER boundaries wear the visible --border ink and tight 8px corners;
   internal seams wear --seam (purple, ruled 2026-08-21). Soft 14px lozenges and barely-there
   outlines are gone with the pills. */
.col {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 8px;
  min-height: 300px;
  display: flex;
  flex-direction: column;
}
.col-head {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 13px 16px 11px;
  border-bottom: 1px solid var(--border-subtle);
  /* the head follows the column's rounding, or its ground squares the corners */
  border-radius: 7px 7px 0 0;
}
/* the heads carry their status GROUND (asked 2026-08-20): the one status
   colour system, tinted to sit on near-black. Alphas differ because the inks
   do — the blue is darkest and needs the most, the red loudest and the least. */
.col[data-status="open"] .col-head { background: color-mix(in srgb, var(--st-open) 12%, transparent); }
.col[data-status="in_progress"] .col-head { background: color-mix(in srgb, var(--st-progress) 15%, transparent); }
.col[data-status="closed"] .col-head { background: color-mix(in srgb, var(--st-closed) 13%, transparent); }
.col-dot { width: 8px; height: 8px; border-radius: 50%; }
.col[data-status="open"] .col-dot { background: var(--st-open); box-shadow: 0 0 10px var(--st-open); }
.col[data-status="in_progress"] .col-dot { background: var(--st-progress); box-shadow: 0 0 10px var(--st-progress); }
.col[data-status="closed"] .col-dot { background: var(--st-closed); box-shadow: none; opacity: 0.8; }
.col-head h2 {
  font: var(--t-heading);
  font-size: 13px;
  color: var(--text);
}
/* a count is data — flat machine voice, no chip around it (DESIGN.md §2) */
.col-count {
  margin-left: auto;
  font: var(--t-machine);
  font-size: 12px;
  letter-spacing: var(--tracking-machine);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.col-cards {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px;
  min-height: 120px;
  flex: 1;
}

/* Cards */
.col-cards { perspective: 900px; }
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left-width: 3px;
  border-radius: 4px 8px 8px 4px;
  padding: 11px 13px 10px;
  /* grab, not pointer: a card is draggable and that was invisible — the
     cursor is the only affordance present before you try it */
  cursor: grab;
  position: relative;
  transform-style: preserve-3d;
  will-change: transform;
  transition: border-color 0.18s var(--ease), transform 0.22s var(--ease), box-shadow 0.22s var(--ease);
}
/* gold light moving across dark lacquer — driven by --mx/--my from JS */
.card::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(320px circle at var(--mx, 50%) var(--my, 50%),
    rgba(245, 213, 122, 0.10), rgba(212, 167, 44, 0.04) 40%, transparent 65%);
  opacity: 0;
  transition: opacity 0.25s var(--ease);
  pointer-events: none;
}
.card:hover {
  border-color: var(--gold-25);
  border-left-color: inherit;
  transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) translateZ(6px);
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.55);
}
.card:hover::after { opacity: 1; }
.sortable-drag {
  transform: rotate(2.5deg) scale(1.04) !important;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.7), 0 0 30px var(--gold-15) !important;
  border-color: var(--gold-40) !important;
  opacity: 0.98 !important;
}
.card[data-status="open"] { border-left-color: var(--st-open); }
.card[data-status="in_progress"] { border-left-color: var(--st-progress); }
.card[data-status="closed"] { border-left-color: var(--st-closed); opacity: 0.75; }
.card.dragging { opacity: 0.4; cursor: grabbing; }
.card:active { cursor: grabbing; }
/* the drag hint, last line of the hover popover */
.hp-drag {
  margin-top: 7px;
  padding-top: 6px;
  border-top: 1px dashed var(--border);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--gold-soft);
  opacity: 0.75;
}
.sortable-ghost { opacity: 0.25; border-style: dashed; }

.card-top {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 4px;
}
/* a machine fact — the one voice mono keeps (DESIGN.md §1) */
.card-number {
  font: var(--t-machine);
  font-size: 11px;
  letter-spacing: var(--tracking-machine);
  color: var(--gold-soft);
}
.card-name {
  /* the timestamp beside it is now ~15 characters, not "8d" — the name is the
     part that gives way, since #number and the stamp are both fixed facts */
  min-width: 0;
  flex: 0 1 auto;
  font: var(--t-heading);
  font-size: 13px;
  color: var(--text-strong);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* a caption, not machine: it is a human date, read as language */
.card-when {
  margin-left: auto;
  font: var(--t-caption);
  font-size: 10.5px;
  color: var(--text-subtle);
  white-space: nowrap;
}
/* the QUESTION is why anyone opens this card — it outranks the chrome above
   it (DESIGN.md §1: rank tells the truth about importance) */
.card-query {
  font: var(--t-body);
  font-size: 13px;
  color: var(--text);
  line-height: 1.45;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin-bottom: 8px;
}
/* facts grouped apart, not strung together (DESIGN.md §3) */
.card-bottom {
  display: flex;
  align-items: center;
  gap: var(--sp-fact);
  flex-wrap: wrap;
}
/* A card chip is a FACT about the ticket, not a control — flat, no border
   (DESIGN.md §2). On a board where the cards themselves are draggable and
   clickable, bordered chips read as nested buttons that do nothing. */
.chip {
  font: var(--t-machine);
  text-transform: uppercase;
  letter-spacing: var(--tracking-machine);
  padding: 0;
  border: 0;
  background: none;
  color: var(--text-subtle);
}
.chip.origin-support { color: var(--gold-soft); }
.chip.origin-knowledge_gap { color: var(--info); }
/* the third kind (MVP 2026-08-18) — its own ink, like knowledge gaps have
   theirs; a fact, so flat text, no border (DESIGN.md §2). Status inks stay
   literal by standing rule; this is the green the other surfaces use. */
.chip.origin-task { color: var(--green-soft); }
/* Every subtype wears its CHART ink at rest (ruled 2026-08-20; calibrated
   2026-08-24) — the ramp lives in :root's --sub-* tokens, the chips wear
   them here and the donut reads the SAME tokens, so the lifted-tail era of
   two diverging copies is over. Hover and the gold active state outrank. */
.label-chip[data-label]:not(:hover):not([data-active="true"]) { color: var(--sub-ink, var(--text-muted)); }
.label-chip[data-label="billing"] { --sub-ink: var(--sub-billing); }
.label-chip[data-label="workflow_stuck"] { --sub-ink: var(--sub-workflow); }
.label-chip[data-label="account_access"] { --sub-ink: var(--sub-account); }
.label-chip[data-label="documents"] { --sub-ink: var(--sub-documents); }
.label-chip[data-label="legal_question"] { --sub-ink: var(--sub-legal); }
.label-chip[data-label="bug"] { --sub-ink: var(--sub-bug); }
.label-chip[data-label="other"] { --sub-ink: var(--sub-other); }
.label-chip[data-label="knowledge_gap"] { --sub-ink: var(--info); }
/* the progress chips wear their LANE inks at rest — the same chart-ink rule
   the subtype row follows (asked 2026-08-20) */
.label-chip[data-progress]:not(:hover):not([data-active="true"]) { color: var(--sub-ink, var(--text-muted)); }
.label-chip[data-progress="Awaiting Review"] { --sub-ink: var(--tp-awaiting); }
.label-chip[data-progress="Ready for Filing"] { --sub-ink: var(--tp-ready); }
.label-chip[data-progress="Filing Submitted"] { --sub-ink: var(--tp-submitted-text); }   /* text companion */
.label-chip[data-progress="Waiting on Client"] { --sub-ink: var(--tp-client); }
.label-chip[data-progress="Waiting on Partner"] { --sub-ink: var(--tp-partner-text); }   /* text companion */
.label-chip[data-progress="Other"] { --sub-ink: var(--tp-other); }
.label-chip[data-progress="Completed"] { --sub-ink: var(--tp-completed); }
/* knowledge gap is an ORIGIN riding in the subtype row, not an eighth
   subtype (ruled 2026-08-20) — a seam says so before colour has to */
.label-chip[data-label="knowledge_gap"] {
  position: relative;
  margin-left: 15px;
}
.label-chip[data-label="knowledge_gap"]::before {
  content: '';
  position: absolute;
  left: -12px;
  top: 50%;
  translate: 0 -50%;
  width: 1px;
  height: 20px;
  background: var(--seam);
}
.card-progress {
  font: var(--t-machine);
  text-transform: uppercase;
  letter-spacing: var(--tracking-machine);
  color: var(--green-soft);
  margin-top: 4px;
}
.card-assignee {
  margin-left: auto;
  font-size: 11px;
  color: var(--text-subtle);
  display: flex;
  align-items: center;
  gap: 5px;
}
.assignee-ini {
  display: grid;
  place-items: center;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--gold-deep), var(--gold-soft));
  color: var(--bg);
  font: 600 9px var(--sans);
}
.prio {
  width: 8px; height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}
/* Priority is the deck's indigo → violet → lavender ramp, not three unrelated
   hues. High was --error red, which now says "open"; normal was gold, which
   says nothing. The deck's exact indigo (#3a2a8c) is painted on pale stock
   under a lamp — unlit on a near-black card it disappears, so high is lifted
   to stay the same colour you'd name. Emphasis rides on the glow, which keeps
   high loudest without inverting the ramp. */
.prio-high { background: var(--prio-high); box-shadow: 0 0 8px color-mix(in srgb, var(--prio-high) 70%, transparent); }
.prio-normal { background: var(--prio-normal); }
.prio-low { background: var(--prio-low); }
.prio-none { background: transparent; border: 1.5px solid var(--border-strong); }

.needs-reply {
  position: absolute;
  top: -4px;
  right: -4px;
  width: 11px; height: 11px;
  border-radius: 50%;
  background: var(--warn);
  border: 2px solid var(--bg-raised);
  animation: pulse 2.2s ease infinite;
}
@keyframes pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(232, 168, 76, 0.5); }
  55% { box-shadow: 0 0 0 6px rgba(232, 168, 76, 0); }
}
/* Reopened into a column you didn't drop it in. The card announces itself where
   it landed — held long enough to find with your eye after the drag, then gone,
   because it describes a moment and not a state of the ticket. It draws in that
   column's own status ink, so the mark and the destination say the same thing. */
.card.landed {
  animation: landed 1.6s var(--ease) 1;
}
.card[data-status="open"].landed { --landed-ink: var(--st-open); }
.card[data-status="in_progress"].landed { --landed-ink: var(--st-progress); }
@keyframes landed {
  0%   { box-shadow: 0 0 0 2px var(--landed-ink), 0 0 22px 2px var(--landed-ink); }
  70%  { box-shadow: 0 0 0 2px var(--landed-ink), 0 0 22px 2px transparent; }
  100% { box-shadow: 0 0 0 0 transparent, 0 0 0 0 transparent; }
}
.predecessor {
  margin-top: 7px;
  padding-top: 7px;
  border-top: 1px dashed var(--border);
  font-size: 10.5px;
  color: var(--text-subtle);
}
.predecessor b { color: var(--text-muted); font-weight: 500; }
/* a self-closed card: the reason is the whole story, so it gets the line —
   clipped to one, with the full text on hover */
.self-closed {
  color: var(--st-closed);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.self-closed b { color: var(--text-muted); }

.col-empty {
  margin: auto;
  text-align: center;
  color: var(--text-subtle);
  font-family: var(--serif);
  font-style: italic;
  font-size: 15px;
  padding: 28px 10px;
}

/* ═══ Hover popover: the full query ═══ */
#hover-pop {
  position: fixed;
  z-index: 80;
  max-width: 360px;
  background: var(--surface-2);
  border: 1px solid var(--gold-25);
  border-radius: 4px 8px 8px 8px;
  padding: 12px 14px;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--text);
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.6), 0 0 24px var(--gold-08);
  pointer-events: none;
  animation: rise 0.16s var(--ease);
}
#hover-pop .hp-meta {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--border);
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--text-subtle);
  word-break: break-all;
}

/* ═══ Ticket modal ═══ */
#modal-backdrop[hidden], #confirm-backdrop[hidden], .modal-backdrop[hidden], #hover-pop[hidden] {
  display: none;
}

/* ═══ The Docket — its own warm paper world, deliberately not black-gold ═══
   No `hidden` any more: collapsed is a CSS state (max-height 0, folded toward
   the toggle below), so opening UNFURLS upward from the click and closing
   folds back into it — the click and the thing it caused visibly connect
   (item 46). visibility waits for the fold, keeping the collapsed room out of
   the tab order; max-height rather than the 0fr grid trick, same reason as
   the subtype row above. The global reduced-motion rule flattens all of it. */
#viz-observatory {
  position: relative;
  height: 400px;
  margin-top: 0;               /* collapsed leaves no seam; open restores 12px */
  background:
    radial-gradient(900px 420px at 50% 28%, rgba(255, 214, 150, 0.1), transparent 70%),
    linear-gradient(180deg, #2b2119, #1a130e 78%);
  border: 1px solid rgba(176, 141, 87, 0.55);
  border-radius: 8px;
  overflow: hidden;
  max-height: 0;
  opacity: 0;
  visibility: hidden;
  border-width: 0;
  transform: translateY(16px) scaleY(0.97);
  transform-origin: 50% 100%;
  transition: max-height 0.45s var(--ease), opacity 0.3s var(--ease),
              transform 0.45s var(--ease), margin-top 0.45s var(--ease),
              visibility 0s 0.45s;
}
#viz[data-expanded="true"] #viz-observatory {
  max-height: 400px;
  opacity: 1;
  visibility: visible;
  margin-top: 12px;
  border-width: 1px;
  transform: none;
  transition: max-height 0.45s var(--ease), opacity 0.35s 0.08s var(--ease),
              transform 0.45s var(--ease), margin-top 0.45s var(--ease);
}
#viz-observatory canvas { display: block; }
.obs-legend {
  position: absolute;
  left: 16px;
  top: 14px;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 5px;
  pointer-events: none;
}
.obs-hint {
  font-family: var(--serif);
  font-style: italic;
  font-size: 13.5px;
  color: #d8c9a8;
  margin-bottom: 4px;
}
.obs-key {
  display: flex;
  align-items: center;
  gap: 7px;
  font-family: var(--mono);
  font-size: 10.5px;
  color: #b8a888;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.obs-key i { width: 11px; height: 7px; border-radius: 2px 2px 0 0; display: inline-block; }
#modal-backdrop, #confirm-backdrop, .modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(10, 10, 12, 0.72);
  backdrop-filter: blur(10px);
  display: grid;
  place-items: center;
  animation: fade 0.2s var(--ease);
}
#modal-backdrop { perspective: 1100px; }
#ticket-modal {
  width: min(560px, calc(100vw - 40px));
  max-height: calc(100vh - 80px);
  overflow-y: auto;
  background: var(--bg-raised);
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  padding: 20px 22px;
  animation: modal-in 0.32s var(--ease);
  position: relative;
}
@keyframes modal-in {
  from { opacity: 0; transform: rotateX(5deg) translateY(14px) scale(0.985); }
  to { opacity: 1; transform: none; }
}
#ticket-modal::before {
  content: '';
  position: absolute;
  top: 0; left: 24px; right: 24px;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--gold), transparent);
}
.tm-head { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
.tm-number { font-family: var(--mono); font-size: 15px; font-weight: 600; color: var(--gold-soft); }
.tm-status-pill {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 3px 10px;
  border-radius: 999px;
  border: 1px solid;
}
/* borders derive from the ink instead of restating it — the old rgba() copies
   were the same three colours written a second time, and drifted the moment
   the ink changed */
.tm-status-pill.open { color: var(--st-open); border-color: color-mix(in srgb, var(--st-open) 40%, transparent); }
.tm-status-pill.in_progress { color: var(--st-progress); border-color: color-mix(in srgb, var(--st-progress) 40%, transparent); }
.tm-status-pill.closed { color: var(--st-closed); border-color: color-mix(in srgb, var(--st-closed) 40%, transparent); }
.tm-close {
  margin-left: auto;
  border: 0; background: none;
  color: var(--text-subtle);
  font-size: 16px;
  cursor: pointer;
}
.tm-close:hover { color: var(--text-strong); }
.tm-query {
  font-family: var(--serif);
  font-size: 19px;
  line-height: 1.4;
  color: var(--text-strong);
  margin-bottom: 14px;
}
.tm-meta {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 6px 18px;
  margin-bottom: 16px;
  font-size: 12.5px;
}
.tm-meta .m-row { display: flex; gap: 8px; align-items: baseline; min-width: 0; }
.tm-meta .m-k {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--gold);
  flex-shrink: 0;
}
.tm-meta .m-v { color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tm-meta .m-v.mono {
  font-family: var(--mono);
  font-size: 11.5px;
  /* the URL rows scroll, never truncate — same contract as the rail's
     raise-url chip (Jai, 2026-08-27); no scrollbar properties on purpose:
     the house's one global ruling paints the thumb */
  overflow-x: auto;
  text-overflow: clip;
}
.tm-controls {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr;
  gap: 10px;
  margin-bottom: 16px;
}
.tm-controls label {
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-subtle);
}
.tm-controls select {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font: 13px var(--sans);
  padding: 7px 9px;
}
.tm-notes h4 {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-subtle);
  font-weight: 500;
  margin-bottom: 8px;
}
.tm-notes-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.note {
  background: var(--gold-04);
  border: 1px solid var(--gold-15);
  border-radius: 4px 10px 10px 10px;
  padding: 8px 11px;
  font-size: 12.5px;
  color: var(--text-muted);
}
.note-meta { font-family: var(--mono); font-size: 10px; color: var(--text-subtle); margin-bottom: 3px; }
.tm-note-add { display: flex; gap: 8px; }
.tm-note-add input {
  flex: 1;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font: 12.5px var(--sans);
  padding: 7px 10px;
}
.tm-note-add button, .tm-open-convo {
  border: 0;
  border-radius: 8px;
  background: var(--gold);
  color: var(--bg);
  font: 600 12.5px var(--sans);
  padding: 7px 14px;
  cursor: pointer;
  transition: all 0.18s var(--ease);
}
.tm-note-add button:hover, .tm-open-convo:hover {
  background: var(--gold-hover);
  transform: translateY(-1px);
  box-shadow: 0 0 24px var(--gold-25);
}
.tm-foot {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--border-subtle);
  display: flex;
  justify-content: flex-end;
}

/* ═══ Confirm modal ═══ */
#confirm-modal, .confirm-modal {
  width: min(400px, calc(100vw - 40px));
  background: var(--bg-raised);
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  padding: 22px;
  animation: rise 0.22s var(--ease);
}
#confirm-modal h3, .confirm-modal h3 { font-family: var(--serif); font-size: 20px; font-weight: 400; color: var(--text-strong); margin-bottom: 8px; }
#confirm-modal p, .confirm-modal p { font-size: 13px; color: var(--text-muted); margin-bottom: 16px; }
.confirm-actions { display: flex; justify-content: flex-end; gap: 10px; }
#other-input {
  width: 100%;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font: 13px var(--sans);
  padding: 9px 12px;
  margin-bottom: 16px;
}
#other-input:focus { outline: none; border-color: var(--gold-40); }
/* the affirmative confirm — note-edit's Save joined 2026-08-27: its dialog
   arrived with unmatched IDs and rendered native grey (the ID-bound-costume
   trap, third instance — see the widget sheet and msgdel below) */
#other-confirm, #noteedit-confirm {
  border: 0;
  background: var(--gold);
  color: var(--bg);
  border-radius: 8px;
  font: 600 12.5px var(--sans);
  padding: 7px 14px;
  cursor: pointer;
}
#other-confirm:hover:not(:disabled), #noteedit-confirm:hover:not(:disabled) { background: var(--gold-hover); }
#other-confirm:disabled, #noteedit-confirm:disabled { opacity: 0.4; cursor: default; }
/* the delete dialogs' safe act wears the safe color (Jai, 2026-08-27):
   Keep it = green, Delete = red — the pair reads at a glance. Both delete
   popups, message and ticket. */
#msgdel-cancel, #delete-cancel {
  border: 0;
  background: var(--success);
  color: var(--bg);
  border-radius: 8px;
  font: 600 12.5px var(--sans);
  padding: 7px 14px;
  cursor: pointer;
}
#msgdel-cancel:hover, #delete-cancel:hover { background: color-mix(in srgb, var(--success) 85%, white); }
#confirm-cancel, #other-cancel, #noteedit-cancel {
  border: 1px solid var(--border-strong);
  background: transparent;
  color: var(--text-muted);
  border-radius: 8px;
  font: 12.5px var(--sans);
  padding: 7px 14px;
  cursor: pointer;
}
/* the committing button of a destructive dialog is red, never green —
   message-delete joined 2026-08-27 (same costume as ticket-delete, which
   was the ruled reference for the feature) */
#delete-confirm, #msgdel-confirm {
  border: 0;
  background: var(--error);
  color: var(--bg);
  border-radius: 8px;
  font: 600 12.5px var(--sans);
  padding: 7px 14px;
  cursor: pointer;
}
#delete-confirm:hover, #msgdel-confirm:hover:not(:disabled) { background: color-mix(in srgb, var(--error) 85%, white); }
#msgdel-confirm:disabled { opacity: 0.4; cursor: default; }
#confirm-close {
  border: 0;
  background: var(--success);
  color: var(--bg);
  border-radius: 8px;
  font: 600 12.5px var(--sans);
  padding: 7px 14px;
  cursor: pointer;
}

/* ═══ Overture ═══ */
@keyframes rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes thread-draw { from { transform: scaleX(0); } to { transform: scaleX(1); } }

body.overture #thread { animation: thread-draw 0.7s var(--ease); }
body.overture #topbar { animation: fade 0.5s var(--ease) both; }
body.overture #filters { animation: fade 0.4s var(--ease) 0.3s both; }
body.overture .card { animation: rise 0.35s var(--ease) both; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* ═══ Responsive ═══ */
@media (max-width: 1100px) {
  #board { grid-template-columns: 1fr; }
}

/* ═══ Conversation page — one case, read + reply fast ═══ */
.back-link {
  font-family: var(--mono);
  color: var(--text-subtle);
  text-decoration: none;
  font-size: 16px;
  padding: 4px 10px 4px 0;
  transition: color 0.15s var(--ease);
}
.back-link:hover { color: var(--gold-soft); }

#convo-shell[hidden] { display: none; }
#convo-shell {
  display: grid;
  /* two rails (ruled 2026-08-21): col 1 is what the admin DOES to the ticket,
     col 2 is the user's side. The shell widened to hold it, still centred —
     on a wide screen both margins shrink and everything shifts left. */
  grid-template-columns: minmax(0, 1fr) 324px 324px;
  gap: 18px;
  align-items: start;
  width: 100%;
  max-width: 1544px;
  margin: 0 auto;
  padding: 16px 28px 40px;
}
#convo-main {
  display: flex;
  flex-direction: column;
  height: calc(100vh - 130px);
  min-height: 480px;
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow: hidden;
  animation: rise 0.35s var(--ease);
}

#case-caption {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border-subtle);
  background: linear-gradient(180deg, var(--gold-04), transparent);
}
.cc-number { font-family: var(--mono); font-size: 17px; color: var(--gold); padding-top: 1px; }
.cc-title { flex: 1; min-width: 0; }
.cc-title h2 { font: var(--t-heading); font-size: 15.5px; color: var(--text-strong); }
.cc-workspace { color: var(--text-subtle); font-weight: 400; font-size: 13px; }
/* facts, then actions — grouped apart, not strung together (DESIGN.md §3) */
.cc-flags { display: flex; align-items: center; gap: var(--sp-fact); }
.cc-flags .cc-botchat { margin-left: var(--sp-fact); }

/* FACTS wear no border (DESIGN.md §2). They were pills, which is why a status
   you cannot click looked exactly like a button you should. */
.cc-needs-reply {
  font: var(--t-machine);
  text-transform: uppercase;
  letter-spacing: var(--tracking-machine);
  color: var(--warn);
}
.pill {
  font: var(--t-machine);
  text-transform: uppercase;
  letter-spacing: var(--tracking-machine);
  color: var(--text-muted);
  border: 0;
  padding: 0;
}
/* status ink stays; the border is what left */
.pill[data-status="open"] { color: var(--st-open); }
.pill[data-status="in_progress"] { color: var(--st-progress); }
.pill[data-status="closed"] { color: var(--st-closed); }

/* The manual half of the live pair. The page polls on its own; this is the
   on-demand trigger and — because a poll that fails does so silently — the
   retry. The gold while busy is not decoration: the global reduced-motion rule
   kills the spin outright, so colour is the feedback that always survives. */
.cc-refresh {
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1;
  color: var(--text-subtle);
  background: none;
  border: 1px solid var(--border);
  /* rectangular like every other button — round is for states, not actions */
  border-radius: 8px;
  padding: 5px 8px;
  cursor: pointer;
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease);
}
.cc-refresh:hover { color: var(--gold-soft); border-color: var(--gold-25); }
.cc-refresh[data-busy="true"] { color: var(--gold-soft); animation: cc-spin 0.7s linear infinite; }
@keyframes cc-spin { to { transform: rotate(360deg); } }

#transcript {
  flex: 1;
  overflow-y: auto;
  padding: 20px 22px;
  display: flex;
  flex-direction: column;
  /* Three distances, and they must be UNMISTAKABLE (DESIGN.md §3). A stamp
     3px under its own bubble and 4px above the next one belongs to neither —
     it floated between two messages and read as ambiguous. */
  gap: 12px;                         /* between messages of one run */
}
/* a change of speaker opens it up: consecutive messages from one person read
   as one utterance, a new speaker starts a new block */
.msg + .msg-lead, .msg-event + .msg, .msg + .msg-event { margin-top: var(--sp-group); }
.msg { max-width: 76%; align-self: flex-start; }

/* the sender — named ONCE, when the speaker changes (DESIGN.md §4) */
.msg-who {
  font: var(--t-heading);
  font-size: 12.5px;
  color: var(--text-muted);
  margin-bottom: 5px;
}
.msg-support .msg-who { color: var(--gold-soft); }

/* the bubble's own facts: time, and how it travelled. Caption, not machine —
   and grouped by space, never by dots. */
/* On EVERY message, always — never behind a hover (2026-08-13: hover is a
   desktop-only affordance and this transcript is a record; a fact that
   materializes only under a cursor is a quiet lie). Noise is handled by
   RANK: caption-small and dim, present without being loud. */
.msg-foot {
  display: flex;
  align-items: center;
  gap: var(--sp-fact);
  font: var(--t-caption);
  color: var(--text-subtle);
  margin-top: 2px;                   /* hugs ITS bubble — 2px vs the 12px below */
}
.msg-badge { color: var(--gold-soft); white-space: nowrap; }

/* deleted messages (2026-08-26): the tombstone keeps the row's place, dim
   and italic; the ✕ is an ACTION (§2 — bordered when armed) that hides
   until its bubble is hovered and stays faintly present without hover. */
.msg-body--deleted { font-style: italic; color: var(--text-subtle); }
.msg-del {
  border: none;
  background: none;
  padding: 0 2px;
  font: var(--t-caption);
  color: var(--text-subtle);
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.15s;
}
.msg:hover .msg-del, .msg-del:focus-visible { opacity: 0.8; }
@media (hover: none) { .msg-del { opacity: 0.35; } }

/* note edits (2026-08-26): the ✎ is the author's own quiet control; the
   'edited' marker is a FACT — flat, dim, no costume (§2). */
.note-edited { font-style: italic; color: var(--text-subtle); }
.note-edit {
  border: none;
  background: none;
  padding: 0 2px;
  margin-left: 4px;
  font: var(--t-caption);
  color: var(--text-subtle);
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.15s;
}
.note:hover .note-edit, .note-edit:focus-visible { opacity: 0.8; }
@media (hover: none) { .note-edit { opacity: 0.35; } }
#noteedit-text {
  width: 100%;
  margin: 10px 0 2px;
  resize: vertical;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--bg-raised, rgba(255, 255, 255, 0.05));
  color: var(--text);
  font: var(--t-body);
  padding: 8px 10px;
}

/* the date, once — a divider, not a repeated field on every message */
.day-divider {
  align-self: stretch;
  display: flex;
  align-items: center;
  gap: var(--sp-fact);
  margin: var(--sp-group) 0 var(--sp-fact);
  color: var(--text-subtle);
}
.day-divider::before, .day-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border-subtle);
}
.day-divider span {
  font: var(--t-machine);
  text-transform: uppercase;
  letter-spacing: var(--tracking-machine);
}
.day-divider:first-child { margin-top: 0; }
.msg-body {
  padding: 10px 14px;
  border-radius: 12px;
  font-size: 13.5px;
  line-height: 1.55;
}
.msg-user .msg-body { background: var(--surface-2); border: 1px solid var(--border); border-top-left-radius: 4px; }
.msg-bot .msg-body { border: 1px solid var(--border-subtle); color: var(--text-muted); border-top-left-radius: 4px; }
/* The message that raised the ticket — the one thing an admin opening a room
   is looking for, so it does not ask to be found: a gold edge, a lit ground,
   and a tag naming what it is. Everything else is grey and bordered, so this
   reads from across the page without reading. (Returned 2026-08-10 for
   channel mode; its case-file ancestor died with the case-file.) */
.msg-raise .msg-body {
  background: var(--gold-08);
  border: 1px solid var(--gold-40);
  box-shadow: 0 0 0 1px var(--gold-15), 0 6px 28px rgba(212, 167, 44, 0.12);
  color: var(--text);
  /* pulses once on arrival, so the eye is taken there — then stops */
  animation: raise-land 1.8s var(--ease) 1;
}
/* a FACT about this message — flat, no border (DESIGN.md §2). The gold
   against the muted run-name is what makes it stand out; it never needed a
   pill, which only made it look pressable. */
.raise-tag {
  align-self: flex-start;
  font: var(--t-machine);
  text-transform: uppercase;
  letter-spacing: var(--tracking-machine);
  color: var(--gold-soft);
  background: none;
  border: 0;
  border-radius: 0;
  padding: 2px 8px;
  margin-bottom: 5px;
}
@keyframes raise-land {
  0%, 100% { box-shadow: 0 0 0 1px var(--gold-15), 0 6px 28px rgba(212, 167, 44, 0.12); }
  35% { box-shadow: 0 0 0 3px var(--gold-40), 0 6px 34px rgba(212, 167, 44, 0.34); }
}
/* The message that raised the ticket. It is the one thing an admin opening a
   conversation is looking for, so it does not ask to be found: a gold edge, a
   lit ground, and a tag that says what it is. Everything else in the transcript
   is grey and bordered, so this reads from across the page without reading. */
/* on arrival it pulses once, so the eye is taken there rather than having to
   sweep for the gold — then it stops, because a permanent animation is noise */
/* ── the ribbon ───────────────────────────────────────────────────────────
   Every row carries THIS ticket's state at its moment as a left edge in the
   status inks. You see the shape of the case — open, held, closed, flared up
   again — before reading a word. */
/* #transcript prefixes match the base rule's ID specificity — without it the
   shorthand above kept every edge transparent and the whole ribbon was
   invisible, which a computed-color check caught and an attribute count hadn't */
#transcript [data-tstate="open"] { border-left-color: var(--st-open); }
#transcript [data-tstate="in_progress"] { border-left-color: var(--st-progress); }
/* chapter seams, heavier than the assign/unassign ticks between them. A seam
   wears the state it PRODUCES: close makes it closed (green), reopen makes it
   open again (red). */
#transcript .msg-event[data-kind="closed"] {
  color: var(--st-closed);
  border-left-color: var(--st-closed);
  font-weight: 600;
}
#transcript .msg-event[data-kind="reopened"] {
  color: var(--st-open);
  border-left-color: var(--st-open);
  font-weight: 600;
}
/* a folded stretch of bot-only chatter — native details/summary, no machinery */
.msg-support { align-self: flex-end; }
.msg-support .msg-who, .msg-support .msg-foot { justify-content: flex-end; text-align: right; }
.msg-support .msg-body { background: var(--gold-08); border: 1px solid var(--gold-25); border-top-right-radius: 4px; }
/* an event is a machine fact — the one place mono-caps still belongs */
.msg-event {
  align-self: center;
  display: flex;
  align-items: center;
  gap: 10px;
  margin: var(--sp-fact) 0;
  font: var(--t-machine);
  text-transform: uppercase;
  letter-spacing: var(--tracking-machine);
  color: var(--text-subtle);
  text-align: center;
}
.msg-event::before, .msg-event::after {
  content: '';
  width: 34px;
  height: 1px;
  background: var(--border-strong);
  flex: none;
}

/* ── the model's working, in the transcript and the drawer ─────────────────
   Native details/summary, same as the lull collapse. It sits above the reply
   it produced and stays shut: an admin came for the case, not the search log,
   but "where did that answer come from" is one click away. */
.msg-tool {
  align-self: flex-start;
  max-width: 76%;
  margin-bottom: 2px;
  font-size: 11.5px;
  border-left: 2px solid var(--border-strong);
  padding-left: 10px;
}
.msg-tool > summary {
  cursor: pointer;
  list-style: none;
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--text-subtle);
  line-height: 1.6;
}
.msg-tool > summary::-webkit-details-marker { display: none; }
.msg-tool > summary::before { content: '▸ '; }
.msg-tool[open] > summary::before { content: '▾ '; }
.msg-tool > summary:hover { color: var(--text-muted); }
.msg-tool[data-failed="true"] > summary { color: var(--error); }
.msg-tool b { color: var(--gold-soft); font-weight: 500; }
.msg-tool-hits { display: flex; flex-direction: column; gap: 2px; padding: 4px 0 2px; }
.msg-tool-hits a, .msg-tool-hits span {
  color: var(--text-muted);
  text-decoration: none;
  line-height: 1.45;
  overflow-wrap: anywhere;
}
.msg-tool-hits a::before, .msg-tool-hits span::before { content: '· '; color: var(--text-subtle); }
.msg-tool-hits a:hover { color: var(--gold-soft); text-decoration: underline; }
.msg-tool-hits .tool-none { font-style: italic; color: var(--text-subtle); }

#composer {
  position: relative;
  display: flex;
  align-items: flex-end;
  gap: 10px;
  padding: 13px 16px;
  border-top: 1px solid var(--border-subtle);
}
#composer-input {
  flex: 1;
  resize: none;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--text);
  padding: 9px 12px;
  font: 13.5px/1.5 var(--sans);
  max-height: 160px;
  transition: border-color 0.15s var(--ease);
}
#composer-input:focus { outline: none; border-color: var(--gold-40); }
#kb-btn {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--gold-soft);
  background: none;
  border: 1px solid var(--gold-25);
  border-radius: 8px;
  padding: 10px 12px;
  cursor: pointer;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease);
}
#kb-btn:hover { background: var(--gold-08); border-color: var(--gold-40); }
#send-btn {
  font: 600 13px var(--sans);
  color: #0a0a0c;
  background: var(--gold);
  border: none;
  border-radius: 9px;
  padding: 11px 18px;
  cursor: pointer;
  transition: background 0.15s var(--ease), opacity 0.15s var(--ease);
}
#send-btn:hover:not(:disabled) { background: var(--gold-hover); }
#send-btn:disabled { opacity: 0.35; cursor: default; }

#kb-pop {
  position: absolute;
  bottom: calc(100% + 6px);   /* rides above the composer however tall it grows */
  left: 16px;
  width: 400px;
  max-width: calc(100% - 32px);
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: 12px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55);
  overflow: hidden;
  z-index: 30;
  animation: rise 0.18s var(--ease);
}
#kb-search {
  width: 100%;
  background: var(--surface);
  border: none;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  padding: 11px 14px;
  font: 13px var(--sans);
}
#kb-search:focus { outline: none; }
#kb-list { max-height: 260px; overflow-y: auto; }
.kb-item {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  border-bottom: 1px solid var(--border-subtle);
  font: inherit;
  color: inherit;
  padding: 10px 14px;
  cursor: pointer;
}
.kb-item:last-child { border-bottom: none; }
.kb-item:hover { background: var(--gold-04); }
.kb-item h5 { font-size: 12.5px; color: var(--text-strong); margin-bottom: 3px; }
.kb-item p {
  font-size: 12px;
  color: var(--text-subtle);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.kb-empty { padding: 16px 14px; font-size: 12.5px; color: var(--text-subtle); }

#case-rail, #case-rail-2 {
  display: flex;
  flex-direction: column;
  gap: 28px;                       /* between FAMILIES: --sp-group (DESIGN.md §3) */
  position: sticky;
  top: 16px;
}
/* families (ruled 2026-08-21): blocks sit with their family, families sit
   apart. One block per family in the charts-block grammar — outer --border at
   8px, sections seamed inside in --seam. A hidden section leaves no seam. */
.rail-family {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  animation: rise 0.35s var(--ease);
}
.rail-family > .rail-block { border: 0; border-radius: 0; background: transparent; animation: none; }
.rail-family > .rail-block:not([hidden]) ~ .rail-block:not([hidden]) { border-top: 1px solid var(--seam); }
/* destructive keeps its dashed seam — danger at rest, in the seam ink. Same
   shape as the seam rule so it outranks it (the shorthand above sets style). */
.rail-family > .rail-block:not([hidden]) ~ .rail-block.rail-danger:not([hidden]) { border-top-style: dashed; }
/* ── Note to user (ruled 2026-08-21): a note's costume plus how it travelled —
   the badge says it went to their task (DESIGN.md §4) ── */
.note-out { margin-bottom: 8px; }
.note-out .note-meta .travel { font-family: var(--sans); font-weight: 600; color: var(--gold-soft); margin-left: 6px; }
.tm-note-add.ext { margin-top: 4px; }
/* ── Task history: the app's timeline in the Timeline row grammar; the dot
   carries the app's own icon meaning ── */
.th-item { display: flex; align-items: flex-start; gap: 9px; font-size: 12px; color: var(--text-muted); padding: 4px 0; }
/* the row's ink: the app's icon meaning, worn by the glyph (or by the dot when
   the name is outside our inlined set) */
.th-item { --th-ink: var(--border-strong); }
.th-item[data-icon="check"], .th-item[data-icon="check-circle"] { --th-ink: var(--st-closed); }
.th-item[data-icon="history"] { --th-ink: var(--warn); }
.th-item[data-icon="hourglass"] { --th-ink: var(--st-progress); }
.th-item[data-icon="comment"], .th-item[data-icon="pen-to-square"] { --th-ink: var(--gold); }   /* our notes */
.th-item i { flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--th-ink); margin-top: 5px; }
/* the app's own glyph (asked 2026-08-21): same ink, no dot */
.th-item i.th-ico { width: 13px; height: 13px; border-radius: 0; background: none; color: var(--th-ink); margin-top: 2px; display: inline-flex; }
.th-item i.th-ico svg { width: 100%; height: 100%; display: block; }
.th-item span { flex: 1; min-width: 0; }
.th-item span b { font-weight: 600; color: var(--text); }
.th-item span small { display: block; font-size: 11.5px; color: var(--text-muted); margin-top: 1px; overflow-wrap: anywhere; }
.th-item em { font-style: normal; font-family: var(--mono); font-size: 10.5px; color: var(--text-subtle); white-space: nowrap; }
.th-unavail { font-size: 12px; color: var(--text-subtle); }
.rail-block {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px 16px;
  animation: rise 0.35s var(--ease);
}
/* section heads are HEADINGS, not machine facts (DESIGN.md §1) — mono-caps
   here was the tell that every role had collapsed into one voice */
.rail-block h4 {
  font: var(--t-heading);
  font-size: 12px;
  color: var(--text-muted);
  margin-bottom: 10px;
}
#btn-close-ticket, #btn-reopen-ticket {
  width: 100%;
  font: 600 13px var(--sans);
  border-radius: 9px;
  padding: 11px 0;
  cursor: pointer;
  transition: background 0.15s var(--ease);
}
#btn-close-ticket { color: #0a0a0c; background: var(--gold); border: none; }
#btn-close-ticket:hover { background: var(--gold-hover); }
#btn-reopen-ticket { color: var(--gold-soft); background: none; border: 1px solid var(--gold-25); }
#btn-reopen-ticket:hover { background: var(--gold-08); }

/* destructive wears danger AT REST (DESIGN.md §2 + audit: a destructive
   control identical to its neighbours until hover is a trap) */
.rail-danger { border-top: 1px dashed var(--border-strong); }
#btn-delete-ticket {
  width: 100%;
  font: 600 13px var(--sans);
  border-radius: 9px;
  padding: 11px 0;
  cursor: pointer;
  color: var(--error);
  background: none;
  border: 1px solid color-mix(in srgb, var(--error) 35%, transparent);
  transition: background 0.15s var(--ease);
}
#btn-delete-ticket:hover { background: color-mix(in srgb, var(--error) 10%, transparent); }
.rail-hint { font-size: 11.5px; color: var(--text-subtle); margin-top: 8px; text-align: center; }
.rail-field {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font-size: 12.5px;
  color: var(--text-muted);
  margin-bottom: 8px;
}
/* progress (task tickets, MVP 2026-08-18): an input that reads like its
   select siblings, and five suggestion chips — ACTIONS, so bordered and
   pressable (DESIGN.md §2), unlike the flat fact-chips above them */
#rail-progress {
  flex: 1;
  min-width: 0;
  background: none;
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  font-size: 12.5px;
  padding: 4px 6px;
}
#rail-progress[data-set="false"] { color: var(--text-subtle); }
#progress-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 8px;
}
#progress-chips .chip-suggest {
  font: var(--t-machine);
  text-transform: uppercase;
  letter-spacing: var(--tracking-machine);
  color: var(--text-muted);
  background: none;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 2px 8px;
  cursor: pointer;
}
#progress-chips .chip-suggest:hover { color: var(--text); border-color: var(--text-subtle); }
#progress-chips .chip-suggest[aria-pressed="true"] {
  color: var(--green-soft);
  border-color: var(--green-soft);
}
.rail-field select {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  padding: 6px 8px;
  font: 12.5px var(--sans);
  max-width: 190px;
}
.rail-labels-row { margin-top: 10px; align-items: flex-start; }
#rail-labels { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; }
/* an unset priority reads as an empty control — dim it so "None" looks like
   the absence it is, and a set value looks deliberate */
/* ONE rule for every select's none-state (2026-08-14): "Unassigned" and
   "None" are absences, and absences read dull — on all four selects, both
   pages, not on whichever one happened to get the treatment first. */
select[data-set="false"] { color: var(--text-subtle); }
/* the same data the board card prints, and the same kind: a FACT. The card's
   chips lost their borders on 2026-08-13; the rail's copy did not (sweep). */
.chip-static {
  font: var(--t-machine);
  text-transform: uppercase;
  letter-spacing: var(--tracking-machine);
  color: var(--gold-soft);
  border: 0;
  padding: 0;
  background: none;
}
.chip-static.chip-kg { color: var(--info); }
.kv {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: 12.5px;
  padding: 3px 0;
}
.kv span { color: var(--text-subtle); flex: none; }
.kv b { font-weight: 500; color: var(--text); text-align: right; overflow-wrap: anywhere; }
/* the predecessor: a label, then its facts on their own line with room to
   breathe — it used to be one string in a squeezed flex row that broke
   mid-name (DESIGN.md §3) */
.kv-pred {
  border-top: 1px dashed var(--border-strong);
  margin-top: var(--sp-fact);
  padding-top: var(--sp-fact);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-tight) var(--sp-fact);
  font: var(--t-caption);
  color: var(--text-subtle);
}
.kv-pred-label {
  flex: 1 0 100%;
  font: var(--t-machine);
  text-transform: uppercase;
  letter-spacing: var(--tracking-machine);
  color: var(--text-subtle);
}
.kv-pred-ticket { color: var(--text-muted); }
.kv-pred-who { color: var(--text-muted); white-space: nowrap; }
/* the copy affordance rides OUTSIDE the scroll area, far left, so it holds
   still while the URL scrolls past. The ack floats above the icon — small,
   local, gone in a breath. */
.raise-url-row { display: flex; align-items: stretch; gap: 6px; margin-top: 8px; position: relative; }
.raise-url-row .raise-url { flex: 1; min-width: 0; margin-top: 0; }
.url-copy {
  flex: none;
  display: flex;
  align-items: center;
  font: 12px/1 var(--mono);
  color: var(--gold-soft);
  background: var(--gold-04);
  border: 1px solid var(--gold-15);
  border-radius: 7px;
  padding: 0 8px;
  cursor: pointer;
}
.url-copy:hover { color: #ffde70; }
.url-copy.copied { color: #ffde70; }
/* the ack lives INSIDE the chip's own footprint (re-ruled 2026-08-27 after
   the floating version landed on the "Opened" row above): a solid-backed
   pill over the start of the URL it just copied — it can never touch a
   neighbor, and it speaks the chip's own gold, not a foreign green. */
.copy-ack {
  position: absolute;
  left: 34px;                       /* over the chip, just past the button */
  top: 50%;
  transform: translateY(-50%);
  padding: 3px 8px;
  border: 1px solid var(--gold-15);
  border-radius: 6px;
  background: #1b1610;              /* solid — nothing bleeds through */
  font: 500 10px/1 var(--mono);
  letter-spacing: 0.08em;
  color: var(--gold-soft);
  pointer-events: none;
  animation: ack-breath 1.2s ease forwards;
}
@keyframes ack-breath { 0% { opacity: 0; } 12% { opacity: 1; } 70% { opacity: 1; } 100% { opacity: 0; } }

.raise-url {
  display: block;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--gold-soft);
  text-decoration: none;
  background: var(--gold-04);
  border: 1px solid var(--gold-15);
  border-radius: 7px;
  padding: 6px 9px;
  margin-top: 8px;
  /* scrolls, never truncates (Jai, 2026-08-27): the whole URL must be
     readable and copyable — ellipsis hid the tail with no way in */
  overflow-x: auto;
  white-space: nowrap;
  /* no scrollbar properties on purpose (ruled 2026-08-27): the chip wears
     the app's ONE scrollbar ruling (the global webkit rule) like every other
     scrolling region — touching scrollbar-width here opts Chromium out of it
     and paints the browser's stock gray instead */
}
.tl-item {
  display: flex;
  align-items: baseline;
  gap: 9px;
  font-size: 12px;
  color: var(--text-muted);
  padding: 4px 0;
}
.tl-item i {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--border-strong);
  align-self: center;
}
.tl-item[data-kind="created"] i { background: var(--st-open); }
/* a second escalation is a raise like the first, so it carries the same ink */
.tl-item[data-kind="raised_again"] i { background: var(--st-open); }
.tl-item[data-kind="assigned"] i { background: var(--gold); }
.tl-item[data-kind="closed"] i { background: var(--st-closed); }
.tl-item[data-kind="reopened"] i { background: var(--warn); }
.tl-item span { flex: 1; }
.tl-item em { font-style: normal; font-family: var(--mono); font-size: 10.5px; color: var(--text-subtle); }

#not-found {
  min-height: 60vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
}
#not-found[hidden] { display: none; }
#not-found a { font-family: var(--mono); font-size: 12.5px; color: var(--gold-soft); text-decoration: none; }

/* the second rail steps under the first before the transcript starves */
@media (max-width: 1180px) {
  #convo-shell { grid-template-columns: minmax(0, 1fr) 324px; }
  #case-rail-2 { grid-column: 2; position: static; }
}
@media (max-width: 920px) {
  #convo-shell { grid-template-columns: 1fr; }
  #convo-main { height: auto; }
  #transcript { max-height: 60vh; }
  #case-rail, #case-rail-2 { position: static; }
}

/* ═══ Admin management page — the door policy, kept quiet ═══ */
#admins-shell {
  max-width: 680px;
  margin: 0 auto;
  padding: 20px 28px 48px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
#admins-head h2 { font-size: 19px; color: var(--text-strong); }
#admins-head h2 span {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--gold-soft);
  margin-left: 6px;
}
.admins-sub {
  font-family: var(--serif);
  font-style: italic;
  font-size: 14.5px;
  color: var(--text-muted);
  margin-top: 4px;
}
#admins-list {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 12px;
  animation: rise 0.35s var(--ease);
}
.admin-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 13px 16px;
  border-bottom: 1px solid var(--border-subtle);
}
.admin-row:last-child { border-bottom: none; }
.admin-avatar {
  flex: none;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  font-family: var(--mono);
  font-size: 12px;
  color: #0a0a0c;
  background: linear-gradient(135deg, var(--gold-deep), var(--gold-soft));
}
.admin-id { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.admin-id b { font-size: 13.5px; font-weight: 600; color: var(--text-strong); }
.admin-id span { font-family: var(--mono); font-size: 11.5px; color: var(--text-subtle); }
.admin-added { font-size: 11.5px; color: var(--text-subtle); white-space: nowrap; }
.admin-open {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--warn);
  min-width: 52px;
  text-align: right;
}
.admin-remove {
  font: 500 12px var(--sans);
  color: var(--text-muted);
  background: none;
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  padding: 6px 12px;
  cursor: pointer;
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease);
}
.admin-remove:hover:not(:disabled) { color: var(--error); border-color: rgba(232, 90, 90, 0.45); }
.admin-remove:disabled { opacity: 0.35; cursor: default; }
#admins-add {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 16px;
  animation: rise 0.35s var(--ease);
}
#admins-add h4 {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-subtle);
  margin-bottom: 10px;
}
#add-form { display: flex; gap: 10px; }
#add-form input {
  flex: 1;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 9px;
  color: var(--text);
  padding: 9px 12px;
  font: 13px var(--sans);
  transition: border-color 0.15s var(--ease);
}
#add-form input:focus { outline: none; border-color: var(--gold-40); }
#add-btn {
  font: 600 13px var(--sans);
  color: #0a0a0c;
  background: var(--gold);
  border: none;
  border-radius: 9px;
  padding: 0 16px;
  cursor: pointer;
  transition: background 0.15s var(--ease);
}
#add-btn:hover { background: var(--gold-hover); }
.add-hint { font-size: 11.5px; color: var(--text-subtle); margin-top: 9px; }
.add-hint.warn { color: var(--warn); }
/* It carries your identity but it IS an action — it navigates to admin
   management — so it reads pressable rather than status-like (DESIGN.md §2). */

/* ═══ Coming-soon chrome — admin side only, one quiet treatment ═══ */
.soon-tag {
  font-family: var(--mono);
  font-size: 8.5px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--gold-soft);
  opacity: 0.55;
  border: 1px solid var(--gold-25);
  border-radius: 999px;
  padding: 1px 6px;
  vertical-align: 1px;
}
.env-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-subtle);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 7px 12px;
  cursor: default;
}
#composer { flex-direction: column; align-items: stretch; gap: 9px; }
#composer-row { display: flex; align-items: flex-end; gap: 10px; }
#composer-channels { display: flex; align-items: center; }
/* The channel row is ONE control — which medium does this reply travel by —
   so it renders as one segmented group, not three sibling pills. The active
   segment is filled; disabled segments sit dim inside the same frame. */
.channel-seg {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--mono);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-subtle);
  background: none;
  border: 1px solid var(--border);
  border-radius: 0;
  margin-left: -1px;                 /* share the border between segments */
  padding: 5px 12px;
  cursor: default;
}
.channel-seg:first-of-type { border-radius: 8px 0 0 8px; margin-left: 0; }
.channel-seg:nth-of-type(3) { border-radius: 0 8px 8px 0; }
.channel-seg[data-active="true"] {
  color: var(--gold-soft);
  border-color: var(--gold-40);
  background: var(--gold-08);
  position: relative;                /* its border wins the shared edge */
  z-index: 1;
}
.channel-seg:disabled { color: var(--text-subtle); opacity: 0.75; background: rgba(255, 255, 255, 0.015); }
/* Chat: lit AND disabled — a fact, not a control (simplified 2026-08-13).
   Without this the :disabled dimming above wins the tie and grays it. */
.channel-seg[data-active="true"]:disabled {
  color: var(--gold-soft);
  opacity: 1;
  border-color: var(--gold-40);
  background: var(--gold-08);
}

/* the transcript chip — a modifier on the email, not a channel: it stands
   apart from the segment strip and wears dashes until chosen */
.transcript-chip {
  display: inline-flex;
  align-items: center;
  font-family: var(--mono);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-subtle);
  background: none;
  border: 1px dashed var(--border);
  border-radius: 8px;
  margin-left: 10px;
  padding: 5px 12px;
  cursor: pointer;
}
.transcript-chip[hidden] { display: none; }
.transcript-chip[data-active="true"] {
  color: var(--gold-soft);
  border: 1px solid var(--gold-40);
  background: var(--gold-08);
}

/* `hidden` must WIN. #composer sets display:flex on an id selector, which
   outranks the UA's [hidden]{display:none} — so hiding it did nothing but
   set an attribute (caught live 2026-08-17). Same trap as any element whose
   own rule sets display; state the override once, here. */
#composer[hidden], .rail-block[hidden], #convo-shell [hidden] { display: none !important; }

/* a tombstone's conversation page: the record, not an empty room */
.deleted-note {
  margin: auto;
  max-width: 460px;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: var(--sp-fact);
  color: var(--text-muted);
}
.deleted-note b { font: var(--t-display); font-size: 17px; color: var(--error); }
.deleted-note span { font: var(--t-caption); font-size: 12.5px; color: var(--text-subtle); }
.pill[data-status="deleted"] { color: var(--error); }

/* the Deleted axis: a chip that swaps WHICH SET the board draws, so it wears
   the error ink rather than gold — it is not a narrowing of the live docket */
#filter-deleted i {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--error);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--error) 14%, transparent);
}
#filter-deleted[data-active="true"] {
  border-color: var(--error);
  background: color-mix(in srgb, var(--error) 10%, transparent);
  color: var(--error);
}
/* a tombstone card: the archive is a graveyard, and it reads like one */
.card[data-deleted="true"] { opacity: 0.62; border-left: 2px solid var(--error); }
.card-deleted {
  margin-top: 7px; padding-top: 7px;
  border-top: 1px dashed var(--border);
  font: var(--t-caption);
  color: var(--error);
}

/* how a reply travelled — the transcript is the record (ruled 2026-08-13) */
.msg-via { color: var(--gold-soft); white-space: nowrap; }
/* coming-soon wears dashes — a slot where a control will be, not a control */
#attach-btn {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-subtle);
  background: none;
  border: 1px dashed var(--border-strong);
  border-radius: 8px;
  padding: 4px 11px;
  opacity: 0.7;
  cursor: default;
}
/* inside the composer chrome the soon-tag drops its own pill — a bordered
   badge inside a bordered control read as two nested buttons */
.channel-seg .soon-tag, #attach-btn .soon-tag {
  border: none;
  padding: 0;
  font-size: 8px;
}
.kb-soon {
  padding: 8px 14px;
  border-top: 1px solid var(--border-subtle);
  font-size: 11.5px;
  color: var(--text-subtle);
}

/* Admins page: destructive confirm is red, not the board's close-green */
body.admins #confirm-close { background: var(--error); }
body.admins #confirm-close:hover { background: #f07070; }
/* long names/emails ellipsize instead of colliding */
.admin-id b, .admin-id span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 640px) {
  #admins-shell { padding-inline: 16px; }
  .admin-added { display: none; }
  #add-form { flex-wrap: wrap; }
  #add-form input { flex-basis: 100%; }
}

/* ═══ Live-data surfaces: a toast for failures, a page for fatal ones ═══ */
/* Top centre, not bottom (2026-08-19): the bottom edge is where the board's
   own controls and the conversation composer live, and a toast that explains
   what to do instead landed on top of them. It also enters downward from the
   top, so the motion reads as arriving rather than rising out of the page. */
#toast {
  position: fixed;
  left: 50%;
  top: 22px;
  translate: -50% 0;
  z-index: 200;
  max-width: min(560px, calc(100vw - 32px));
  padding: 11px 18px;
  border-radius: 10px;
  background: var(--surface-2);
  border: 1px solid rgba(232, 90, 90, 0.45);
  color: var(--text);
  font-size: 13px;
  box-shadow: 0 14px 40px rgba(0, 0, 0, 0.6);
  opacity: 0;
  transform: translateY(-8px);
  transition: opacity 0.2s var(--ease), transform 0.2s var(--ease);
  pointer-events: none;
}
#toast[data-show="true"] { opacity: 1; transform: translateY(0); }
#fatal {
  min-height: 70vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 24px;
  text-align: center;
}
#fatal .fatal-detail {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--text-subtle);
  max-width: 60ch;
  line-height: 1.7;
}
#fatal .fatal-detail b { color: var(--gold-soft); font-weight: 600; }
/* a captionless card takes no caption space (defensive: every cause should
   bring its words, but an empty figcaption must not leave a gap) */
.fatal-meme figcaption:empty { display: none; }

/* ── the fetching state (Jai's pick 2026-08-27: emblem + hairline; born of
   the stall incident). The emblem ships IN each primary region's markup —
   the page identity breathing, one machine line under it — and the render
   replaces it; the meme card covers failure. The hairline under the topbar
   runs only while an emblem is in the DOM (body:has), so both vanish the
   moment the payload lands. No timing code anywhere. */
.fetching-emblem {
  grid-column: 1 / -1;             /* holds the room whatever the container is */
  width: 100%;
  min-height: 42vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* the mark rides at ~25% of the region, not its center (Jai, 2026-08-27):
     the board carries chrome above this container, and a centered emblem
     sank into the screen's bottom hemisphere */
  justify-content: flex-start;
  padding-top: 10.5vh;             /* 25% of the 42vh room */
}
.fe-mark {
  font: italic 30px/1.2 var(--serif);
  color: var(--text);
  animation: fe-breathe 1.8s ease-in-out infinite;
}
.fe-mark em { color: #ffde70; }
@keyframes fe-breathe { 50% { opacity: 0.35; } }
.fe-sub {
  margin-top: 10px;
  font: 500 11px/1 var(--mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-subtle);
}
#loadbar {
  display: none;
  height: 2px;
  position: relative;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.06);
}
body:has(.fetching-emblem) #loadbar { display: block; }
.lb-bar {
  position: absolute;
  top: 0; bottom: 0; left: -34%;
  width: 34%;
  background: linear-gradient(90deg, transparent, #ffc609, #ffde70, transparent);
  animation: lb-slide 1.2s ease-in-out infinite;
}
@keyframes lb-slide { 100% { left: 100%; } }
/* ── the 403 meme card ─────────────────────────────────────────────────────
   Caption overlaid ON the image, meme-style: bold white sans, mid-frame.
   The image is optional (onerror removes it) — captionless-image never
   happens, imageless-caption reads fine on the dark ground. */
.fatal-meme {
  position: relative;
  margin: 0 auto 14px;
  max-width: 420px;
  border-radius: 12px;
  overflow: hidden;
}
.fatal-meme img {
  display: block;
  width: 100%;
  border-radius: 12px;
  margin-bottom: 14px;
}
/* below the frame, not on it — whatever the meme of the week is, no text
   lands on its face; with no image it's simply a bold line on the page */
.fatal-meme figcaption {
  font: 700 21px/1.3 var(--sans);
  color: #fff;
  text-align: center;
  letter-spacing: 0.01em;
}
#fatal .fatal-fine {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--text-subtle);
  max-width: 56ch;
  line-height: 1.7;
}
#fatal a { font-family: var(--mono); font-size: 12.5px; color: var(--gold-soft); text-decoration: none; }
#fatal a:hover { text-decoration: underline; }

/* modal footer: close/reopen live beside "open conversation" */
.tm-foot { display: flex; align-items: center; gap: 10px; }
.tm-close-ticket, .tm-reopen-ticket {
  font: 600 12.5px var(--sans);
  border-radius: 8px;
  padding: 9px 14px;
  cursor: pointer;
}
.tm-close-ticket { color: #0a0a0c; background: var(--gold); border: none; }
.tm-close-ticket:hover { background: var(--gold-hover); }
.tm-reopen-ticket { color: var(--gold-soft); background: none; border: 1px solid var(--gold-25); }
.tm-reopen-ticket:hover { background: var(--gold-08); }
.tm-open-convo { margin-left: auto; }
.tm-status-note {
  font-size: 11.5px;
  color: var(--text-subtle);
  align-self: flex-end;
  padding-bottom: 7px;
}

/* ═══ The Decks: breadcrumb + clickable plates ═══ */
.obs-crumb {
  position: absolute;
  right: 16px;
  top: 14px;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 7px;
}
.obs-crumb[hidden] { display: none; }
.crumb-seg {
  font-family: var(--mono);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: #2a2117;
  background: linear-gradient(180deg, #c9a86a, #a5824a);
  border: none;
  border-radius: 3px;
  padding: 4px 10px;
  cursor: pointer;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 240, 210, 0.4);
}
.crumb-seg:disabled {
  cursor: default;
  background: none;
  box-shadow: none;
  color: #b8a888;          /* the separator's own muted ink */
  padding-left: 2px;
  padding-right: 2px;
}
.crumb-seg:not(:disabled):hover { filter: brightness(1.08); }
.crumb-sep { font-family: var(--mono); font-size: 10px; color: #b8a888; }

/* legend: two groups only — stripe means status, stamp means priority */
.obs-row { display: flex; align-items: center; gap: 13px; }
.obs-group { color: #8d7b58; letter-spacing: 0.12em; min-width: 116px; }


/* ═══ The bot-chat drawer — the user's paper-pilot conversation, read-only,
   sliding over the transcript's right edge (channel mode only) ═══ */
#transcript-wrap { position: relative; display: flex; flex-direction: column; min-height: 0; flex: 1; overflow: hidden; }
#transcript-wrap > #transcript { flex: 1; min-height: 0; }
/* ACTIONS look pressable: filled, not merely outlined, so the eye separates
   them from the facts beside them without reading a word (DESIGN.md §2). */
.cc-botchat {
  font: var(--t-machine);
  text-transform: uppercase;
  letter-spacing: var(--tracking-machine);
  color: var(--gold-soft);
  background: var(--gold-08);
  border: 1px solid var(--gold-25);
  border-radius: 8px;
  padding: 5px 11px;
  cursor: pointer;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease);
}
.cc-botchat:hover, .cc-botchat[aria-expanded="true"] {
  background: var(--gold-15);
  border-color: var(--gold-40);
}
#bot-drawer {
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: min(400px, 88%);
  display: flex;
  flex-direction: column;
  background: #121317;   /* OPAQUE — the channel must not bleed through */
  border-left: 1px solid var(--gold-25);
  box-shadow: -24px 0 48px rgba(0, 0, 0, 0.55);
  transform: translateX(102%);
  transition: transform 0.28s var(--ease);
  z-index: 5;
}
#bot-drawer.in { transform: translateX(0); }
#bot-drawer > header {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
}
#bot-drawer > header b { font: 600 11px var(--mono); letter-spacing: 0.08em; color: var(--gold-soft); text-transform: uppercase; }
#bot-drawer > header span { font-size: 11px; color: var(--text-muted); flex: 1; }
#bot-drawer > header button {
  border: none; background: none; color: var(--text-muted);
  font-size: 14px; cursor: pointer; padding: 2px 4px;
}
#bot-drawer > header button:hover { color: var(--text); }
#bot-drawer-body {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
#bot-drawer-body .msg { max-width: 92%; }
/* two-party conversation: the bot speaks from the left, the user from the
   right — there is no third channel in here (ruled 2026-08-06) */
#bot-drawer-body .msg-user { align-self: flex-end; }
#bot-drawer-body .msg-user .msg-who,
#bot-drawer-body .msg-user .msg-foot { justify-content: flex-end; text-align: right; }
#bot-drawer-body .msg-user .msg-body {
  border-top-left-radius: 10px;
  border-top-right-radius: 4px;
}
#bot-drawer-body .empty { color: var(--text-muted); font-size: 12px; text-align: center; padding: 28px 8px; }


/* per-message page context — the user's in-app location, admin-only */
.msg-page {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--gold-soft);
  opacity: 0.75;
}

/* ── the feedback shelf (@feedback verb, 2026-08-11) ──────────────────── */

#feedback-shell {
  max-width: 680px;
  margin: 0 auto;
  padding: 20px 28px 48px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
#feedback-head h2 { font-size: 19px; color: var(--text-strong); }
#feedback-head #feedback-count {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--text-subtle);
}
#feedback-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.feedback-row {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 13px 16px;
  animation: rise 0.35s var(--ease);
}
.feedback-row header {
  display: flex;
  align-items: baseline;
  gap: 10px;
}
.feedback-row header b { font-size: 13px; color: var(--text-strong); }
/* the page chip rides the header, but a long query string must not shove the
   timestamp off the end — one line, truncated, full value on hover */
.feedback-row header .msg-page {
  max-width: 42%;
  /* same contract as .raise-url: scrolls, never truncates */
  overflow-x: auto;
  white-space: nowrap;
  /* no scrollbar properties on purpose (ruled 2026-08-27): the chip wears
     the app's ONE scrollbar ruling (the global webkit rule) like every other
     scrolling region — touching scrollbar-width here opts Chromium out of it
     and paints the browser's stock gray instead */
}
.feedback-row header time {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-subtle);
}
.feedback-row p {
  margin-top: 7px;
  font-size: 13px;
  line-height: 1.55;
  white-space: pre-wrap;
}
.feedback-cat {
  font-family: var(--mono);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  padding: 2px 8px;
  border-radius: 9px;
  background: rgba(255, 255, 255, 0.07);
  color: var(--text-subtle);
}
.feedback-cat--bug     { background: rgba(224, 116, 112, 0.16); color: #e07470; }
.feedback-cat--idea    { background: rgba(255, 198, 9, 0.14);  color: var(--gold-soft); }
.feedback-cat--unclear { background: rgba(134, 169, 219, 0.16); color: #86a9db; }
.feedback-cat--praise  { background: color-mix(in srgb, var(--green-soft) 16%, transparent); color: var(--green-soft); }

/* the type filter (2026-08-25): the board's chip family — an ACTION, so the
   squared hairline costume (the de-pilled ruling). Idle chips wear their
   category's ink the way the board's subtype chips wear the ramp; the
   chosen one goes gold, the decided active costume. Counts stay quiet. */
.fb-filterbar {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 10px;
}
.fb-filterbar:last-of-type { margin-bottom: 18px; }
#feedback-filter .label-chip { text-transform: capitalize; }
.fb-filterbar .label-chip:not(:hover):not([data-active="true"]) {
  color: var(--cat-ink, var(--text-muted));
}
.fb-filterbar .label-chip span {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--text-subtle);
  margin-left: 3px;
}
.feedback-chip--bug     { --cat-ink: #e07470; }
.feedback-chip--idea    { --cat-ink: var(--gold-soft); }
.feedback-chip--unclear { --cat-ink: #86a9db; }
.feedback-chip--praise  { --cat-ink: var(--green-soft); }

/* outcomes (2026-08-27): resolved is a FACT — flat tinted machine caps, the
   green the board already means "done" with (§2, §5). The controls are
   ACTIONS — the board's hairline chip costume. The reply is the team's
   margin note on the entry, quiet and indented. */
.feedback-resolved {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  padding: 2px 8px;
  border-radius: 9px;
  background: color-mix(in srgb, var(--green-soft) 16%, transparent);
  color: var(--green-soft);
}
.feedback-reply {
  margin: 8px 0 0 12px;
  padding: 6px 10px;
  border-left: 2px solid color-mix(in srgb, var(--green-soft) 40%, transparent);
  color: var(--text);
  font: var(--t-body);
}
.feedback-reply-meta {
  display: block;
  font: var(--t-caption);
  color: var(--text-subtle);
  margin-bottom: 2px;
}
.feedback-acts { display: flex; gap: 8px; margin-top: 8px; }
.fb-act {
  border: 1px solid var(--line);
  border-radius: 7px;
  background: none;
  padding: 3px 10px;
  font: var(--t-caption);
  color: var(--text-muted);
  cursor: pointer;
}
.fb-act:hover { color: var(--text); border-color: var(--border-strong, rgba(255,255,255,0.25)); }
.fb-act--go { color: var(--green-soft); border-color: color-mix(in srgb, var(--green-soft) 45%, transparent); }
.fb-act:disabled { opacity: 0.5; cursor: default; }
.fb-editor { margin-top: 8px; }
.fb-editor textarea {
  width: 100%;
  resize: vertical;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--bg-raised, rgba(255, 255, 255, 0.05));
  color: var(--text);
  font: var(--t-body);
  padding: 6px 10px;
}
.fb-editor-acts { display: flex; gap: 8px; justify-content: flex-end; margin-top: 6px; }

/* ── the updates shelf (2026-08-25) ────────────────────────────────────────
   One entry per release, the feedback shelf's page grammar: date as a
   machine fact, title as the heading, items as a list whose kind is a flat
   tinted fact (§2 — no borders on facts). Existing inks only (§5):
   added = green, changed = gold, fixed = info. */
.update-entry { margin: 0 0 30px; }
.update-entry header {
  display: flex;
  align-items: baseline;
  gap: 14px;
  margin: 0 0 10px;
}
.update-entry time {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-subtle);
  white-space: nowrap;
}
.update-entry h3 {
  margin: 0;
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
}
.update-entry ul {
  margin: 0;
  padding: 0;
  list-style: none;
}
.update-entry li {
  display: flex;
  gap: 10px;
  align-items: baseline;
  padding: 6px 0;
  font-size: 13px;
  line-height: 1.55;
  color: var(--text-muted);
}
.update-kind {
  flex: none;
  font-family: var(--mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  min-width: 62px;
}
.update-kind--added   { color: var(--green-soft); }
.update-kind--changed { color: var(--gold-soft); }
.update-kind--fixed   { color: var(--info); }
/* two voices, one item (2026-08-26): the technical line is Body; the plain
   TL;DR under it is Caption — small, muted, riding its element — at
   --sp-tight inside the one fact they jointly state */
.update-item { display: flex; flex-direction: column; gap: 4px; }
.update-item p { margin: 0; }
.update-plain { font-size: 12px; line-height: 1.55; color: var(--text-subtle); }
/* the [hidden] trap again (adversarial review): the menu dot's own
   display rule outranks the UA's [hidden] — restate it or the dot never
   clears after the shelf is read */
#menu-updates .upd-dot[hidden] { display: none; }

/* the quiet dot: unseen release notes, gold, on the menu button and its
   Updates row — cleared by visiting the shelf */
.menu-btn { position: relative; }
.menu-btn.has-update::after {
  content: '';
  position: absolute;
  top: 2px;
  right: 2px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--gold-soft);
}
#menu-updates .upd-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--gold-soft);
  margin-left: 5px;
  vertical-align: middle;
}
.feedback-ticket {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--gold-soft);
  text-decoration: none;
}
.feedback-ticket:hover { text-decoration: underline; }
.feedback-files {
  margin-top: 9px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.feedback-files button {
  font: inherit;
  font-size: 11.5px;
  padding: 3px 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: none;
  color: var(--text);
  cursor: pointer;
}
.feedback-files button:hover { border-color: var(--gold-soft); }
.feedback-files button span {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--text-subtle);
}
.feedback-file-err { border-color: #e07470 !important; }
.feedback-link {
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-subtle);
  text-decoration: none;
}
.feedback-link:hover { color: var(--gold-soft); }

/* ── ticket files on the conversation rail (2026-08-11) ────────────────── */
#rail-files {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.rail-file {
  font: inherit;
  font-size: 11.5px;
  padding: 3px 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: none;
  color: var(--text);
  cursor: pointer;
}
.rail-file:hover { border-color: var(--gold-soft); }
.rail-file em {
  font-style: normal;
  font-family: var(--mono);
  font-size: 10px;
  color: var(--text-subtle);
}
.rail-file-err { border-color: #e07470 !important; }

/* ── §1.2 parity (2026-08-20): the ticket carries what HubSpot's does ────
   The snapshot is the room's opening record — machine-made, so it reads in
   mono, pre-wrapped, never escaping its bubble. The labels line above it is
   the workflow chain in one breath. */
.msg.task-details { max-width: 100%; }
.task-details-labels {
  font: var(--t-machine);
  text-transform: uppercase;
  letter-spacing: var(--tracking-machine);
  color: var(--green-soft);
  margin-bottom: 6px;
}
.task-details-snapshot {
  font: 400 12px/1.6 var(--mono);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  margin: 0;
}
.rail-form-line { margin: 0 0 4px; }
.rail-form-line a { color: var(--green-soft); }

/* ── admins & teams (ruled 2026-08-21) ──────────────────────────────────────
   The bird's-eye page and the team controls on the ticket. Facts flat
   (role, team tags), actions bordered (the forms' buttons inherit
   .tm-note-add), machine caps for keys and roles — DESIGN.md applied, not
   amended. */
.role-tag {
  font: var(--t-machine);
  text-transform: uppercase;
  letter-spacing: var(--tracking-machine);
  color: var(--text-subtle);
  padding: 3px 7px;
  border-radius: 4px;
  background: var(--surface-2);
  white-space: nowrap;
}
.role-tag[data-role="super"] { color: var(--gold-soft); background: var(--gold-tint-08); }
.admin-teams { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 5px; }
.admin-teams span, .chip.chip-team {
  font: var(--t-machine);
  text-transform: uppercase;
  letter-spacing: var(--tracking-machine);
  color: var(--text-muted);
  background: var(--surface-2);
  padding: 2px 6px;
  border-radius: 4px;
}
.admins-head2 { margin-top: var(--sp-group); }
.admins-head2 h2 { font-size: 19px; color: var(--text-strong); }
.admins-head2 h2 span { font-family: var(--mono); font-size: 13px; color: var(--gold-soft); margin-left: 6px; }
.admins-form {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.admins-form h4 { font: var(--t-heading); color: var(--text-strong); }
.admins-form .rail-hint { margin: 0; }
.team-picks { display: flex; flex-wrap: wrap; gap: 8px 16px; }
.team-picks label {
  display: flex; align-items: center; gap: 6px;
  font: var(--t-caption); color: var(--text-muted); cursor: pointer;
}
.team-picks input { accent-color: var(--gold); }
.team-picks .none, .team-members .none { font: var(--t-caption); color: var(--text-subtle); font-style: italic; }
#teams-list { display: flex; flex-direction: column; gap: 10px; }
.team-row {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 13px 16px;
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.team-row header { display: flex; align-items: baseline; gap: 10px; }
.team-row header b { font: var(--t-heading); color: var(--text-strong); }
.team-row header span {
  font: var(--t-machine); text-transform: uppercase; letter-spacing: var(--tracking-machine);
  color: var(--text-subtle);
}
.team-members { display: flex; flex-wrap: wrap; gap: 6px; }
.team-members span { font: var(--t-caption); color: var(--text); background: var(--surface-2); padding: 3px 8px; border-radius: 4px; }
.team-members .none { background: none; padding: 0; }
.team-add { display: flex; gap: 8px; align-items: center; }
.team-add select {
  flex: 1; min-width: 0;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
  color: var(--text); font: 13px var(--sans); padding: 7px 9px;
}
.team-add button {
  background: var(--gold); color: #0a0a0c; border: none; border-radius: 8px;
  font: 600 12.5px var(--sans); padding: 8px 14px; cursor: pointer;
}
.team-add button:hover { background: var(--gold-hover); }

/* the email-them checkbox beside Assignee — a caption-sized choice, not a
   field; the modal's label rule (column, uppercase) is overridden here */
.tm-controls label.tm-notify, .rail-notify {
  display: flex; flex-direction: row; align-items: center; gap: 6px;
  font: var(--t-caption); text-transform: none; letter-spacing: 0;
  color: var(--text-muted); cursor: pointer;
}
.tm-controls label.tm-notify { align-self: end; padding-bottom: 8px; }
.tm-notify input, .rail-notify input { accent-color: var(--gold); margin: 0; }
.rail-notify { margin: -2px 0 10px; }

/* `hidden` must WIN over the flex above — the 2026-08-17 lesson, a third
   time (review 2026-08-21): .tm-controls label sets display:flex, an author
   rule that beats the UA's [hidden]{display:none}, so the modal's Team
   control could never actually hide. */
.tm-controls label[hidden] { display: none !important; }

/* ── the environment chip + the picker (ruled 2026-09-01) ─────────────────
   One URL serves every environment, so the topbar must say which world it
   is showing; the chip is an ACTION (DESIGN §2: bordered, pressable) — the
   door back to the picker. The picker itself is a short list of doors. */
.env-chip {
  display: inline-flex; align-items: center;
  padding: 3px 9px; margin-right: var(--sp-fact, 14px);
  border: 1px solid var(--border, rgba(255,255,255,.14)); border-radius: 4px;
  font: var(--t-machine, 600 11px/1 "Geist Mono", monospace);
  text-transform: uppercase; letter-spacing: .08em;
  color: var(--gold, #d4a72c); text-decoration: none;
}
.env-chip:hover { border-color: var(--gold, #d4a72c); }

.choose-page main { max-width: 520px; margin: 12vh auto 0; padding: 0 24px; }
.choose-page main h1 { font: var(--t-display, 400 34px/1.15 "Instrument Serif", serif); margin: 0 0 var(--sp-fact, 14px); }
.choose-page main p { opacity: .65; margin: 0; }
.choose-env { display: flex; flex-direction: column; gap: var(--sp-fact, 14px); margin-top: var(--sp-group, 28px); }
.choose-env button {
  display: flex; align-items: baseline; gap: var(--sp-fact, 14px);
  text-align: left; padding: 14px 18px;
  border: 1px solid var(--border, rgba(255,255,255,.14)); border-radius: 8px;
  background: none; color: inherit; font: inherit; cursor: pointer;
}
.choose-env button:hover:not(:disabled) { border-color: var(--gold, #d4a72c); }
.choose-env button:disabled { opacity: .4; cursor: default; }
.choose-env b {
  font: var(--t-machine, 600 11px/1 "Geist Mono", monospace);
  text-transform: uppercase; letter-spacing: .08em; color: var(--gold, #d4a72c);
  min-width: 80px;
}
.choose-env span { opacity: .65; }
.choose-note { opacity: .5; }

/* Each door's session light (Jai, 2026-09-01): the server's own verdict on
   that environment's stored token. Green wears the house heartbeat — the
   needs-reply pulse, in the green family; red stands still: not signed in,
   expired, or rejected. */
.env-dot {
  flex: none; margin-left: auto; align-self: center;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--border-strong, rgba(255,255,255,.28));
}
.env-dot[data-state="valid"] {
  background: var(--green-soft, #66c08c);
  animation: heartbeat-green 2.2s ease infinite;
}
.env-dot[data-state="dead"], .env-dot[data-state="none"], .env-dot[data-state="unknown"] {
  background: var(--error, #ea6660);
}
@keyframes heartbeat-green {
  0%, 100% { box-shadow: 0 0 0 0 rgba(102, 192, 140, 0.5); }
  55% { box-shadow: 0 0 0 6px rgba(102, 192, 140, 0); }
}
