/* DmitrySocial dashboard - same dark shell as the client deck.
   Fonts: Outfit (display) + Instrument Sans (body). */

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* E4 (constitution section 2). A native control inherits neither family nor
   size, so every <button>, <input>, <select> and <textarea> this sheet never
   typed rendered in Chrome's OWN metrics - Arial on 75 operator nodes,
   13.3333px on 224. Two axes, and they are named one at a time.

   NOT the `font: inherit` shorthand. The shorthand also resets line-height
   (`normal` <- the inherited 1.6) and so changes the HEIGHT of every untyped
   control. It was written exactly as the constitution used to prescribe, run
   against the authoritative baseline and reverted: login on iPhone moved
   0.0316 of its pixels, workspace on mac 0.0168, and two frames changed size
   outright, against 0.000000 on the control hand. The numbers are in
   .autopilot/shifts-dash.md and the constitution's E4 now says font-family +
   font-size. */
button, input, select, textarea, optgroup {
  font-family: inherit;
  font-size: inherit;
}


/* PALETTE AND FAMILIES: brand.css. Until 2026-08-24 this sheet declared all
   seventeen of those names a second time with the same literals - a second
   home, which is the defect "one literal, one home" exists to catch. They are
   DELETED, not copied: every operator template loads brand.css before this
   file, so the names resolve there.
   --radius stays because brand.css POINTS at it (--r-card: var(--radius)) and
   declares it nowhere. Written as an alias it has one home, --px-12, and the
   duplicate-literal rule reads it as one decision rather than two. */
:root {
  --radius: var(--px-12);
}

:root { color-scheme: dark; }  /* native scrollbars and form controls render dark */

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 14.5px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

.side-brand {
  display: flex; align-items: center; gap: 10px;
  font-family: var(--font-display);
  font-size: 12px; font-weight: 700; letter-spacing: 0.18em;
}
.brand-dot { width: 10px; height: 10px; border-radius: var(--r-circle); background: var(--grad); }

/* Login */
.login-body {
  display: grid; place-items: center; min-height: 100dvh; padding: 20px;
  /* the single auto track otherwise sizes to the card's max-content (the
     password input's intrinsic width) and overflows 320px phones by 2px -
     pin the track to the space that actually exists */
  grid-template-columns: 100%;
}
.login-card {
  width: min(380px, 100%);
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-panel);
  padding: 32px; display: flex; flex-direction: column; gap: 14px;
}
.login-card h1 { font-family: var(--font-display); font-size: 22px; font-weight: 700; margin-top: 6px; }
.login-card label { font-size: 12px; color: var(--text-dim); }
.login-card input {
  background: var(--w-05); border: 1px solid var(--line);
  border-radius: var(--r-10); padding: 12px 14px; color: var(--text);
  font-family: var(--font-body); font-size: 15px;
}
.login-card input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.login-card button {
  margin-top: 6px; background: var(--accent); color: var(--accent-ink-2);
  border: none; border-radius: var(--r-card); padding: 12px 18px;
  font-weight: 700; font-size: 14px; cursor: pointer; font-family: var(--font-body);
}
.login-error { background: var(--err-12); color: var(--err); border-radius: var(--r-chip); padding: 10px 12px; font-size: 13px; }

/* Dashboard shell */
.dash { max-width: 1080px; margin: 0 auto; padding: 40px 24px 80px; }
.dash-head { margin-bottom: 32px; display: flex; flex-direction: column; gap: 14px; }
.dash-head h1 { font-family: var(--font-display); font-size: 30px; font-weight: 800; letter-spacing: -0.02em; }
.head-month { color: var(--text-faint); font-weight: 500; font-size: 18px; margin-left: 8px; }
/* A heading breaks BETWEEN its parts, never inside one, and never on the
   separator. The browser had no instruction, so on a phone it tore the month
   value in half ("September" / "2026") and, on the report, ended a line on
   the separator itself ("HiyaPro Corp ·").

   Gluing the separator to the part that follows it fixed the first half and
   created the second: the re-run eyes gate read "· September 2026" starting
   a line and rejected that too. So the meta is one INLINE BLOCK: it wraps
   whole, onto its own line, and a separator is only ever painted with words
   on both sides of it. The parts stay nowrap for the case where the meta is
   wider than the whole line and has to break somewhere. */
.head-month { display: inline-block; vertical-align: baseline; }
.hm-part { white-space: nowrap; }
.back { color: var(--text-dim); text-decoration: none; font-size: 13px; }
.back:hover { color: var(--text); }

.head-actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
/* the WHY of a disabled lifecycle button, visible on every input device
   (audit F4-05: title-only explanations never show on touch) */
.act-note { font-size: 12px; color: var(--text-dim); white-space: nowrap; }
.link-input {
  flex: 1; min-width: 220px; max-width: 460px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-chip); padding: 9px 12px; color: var(--text-dim);
  font-size: 12.5px; font-family: var(--font-body);
}
.head-actions button, .view-as {
  background: var(--line-faint); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--r-card);
  padding: 9px 16px; font-size: 13px; font-weight: 600; cursor: pointer;
  text-decoration: none; font-family: var(--font-body);
}
.head-actions button:hover, .view-as:hover { background: var(--w-10); }
.head-actions .danger { color: var(--err); border-color: var(--err-30); }

/* Count chips (workspace board). The index's own count rows were deleted
   on 2026-08-22 - it carries one decided-of-total reading instead. */
.cnt {
  font-size: 11.5px; font-weight: 600; padding: 4px 10px; border-radius: var(--r-pill);
  background: var(--line-faint); color: var(--text-dim);
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
}
.cnt::before { content: ""; width: 6px; height: 6px; border-radius: var(--r-circle); background: currentColor; flex-shrink: 0; }
.cnt-ok { background: var(--ok-12); color: var(--ok); }
.cnt-rev { background: var(--warn-14); color: var(--warn); }
.cnt-pend { background: var(--pend-12); color: var(--pend); }
.empty { color: var(--text-dim); padding: 40px 0; }

/* Items table */
.items-table { margin-top: 8px; background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-card); overflow: hidden; }
.items-table table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.items-table th {
  text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em;
  color: var(--text-faint); padding: 12px 18px; border-bottom: 1px solid var(--line);
  font-weight: 600;
}
.items-table td { padding: 10px 18px; border-bottom: 1px solid var(--panel); }
.items-table tr:last-child td { border-bottom: none; }
.dim { color: var(--text-faint); font-size: 12px; }

/* (Latest-feedback list retired 2026-08-18: the bell carries it now) */

button, a { -webkit-tap-highlight-color: transparent; touch-action: manipulation; }

@media (max-width: 640px) {
  .dash { padding: 28px 16px 60px; }
  .items-table { overflow-x: auto; }
  .items-table table { min-width: 420px; }
  .login-card input, .link-input { font-size: 16px; }
  .head-actions button, .view-as, .filt { min-height: 44px; }
  .login-card button { min-height: 48px; }
}

/* ── Control room ────────────────────────────────────────────────────────
   One row per deliverable: what it looks like, where it stands, what the
   client said, and the two things that change it. Verdicts stay read-only -
   approvals and change requests are the client's to make. */
.filters { display: flex; flex-direction: column; gap: 10px; margin: 26px 0 18px; }
.filt-group { display: flex; flex-wrap: wrap; gap: 8px; }
.filt-group[data-group="kind"] .filt { font-size: 12px; padding: 7px 13px; }
.filt {
  font-family: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer;
  padding: 8px 14px; border-radius: var(--r-pill);
  border: 1px solid var(--line-mid);
  background: var(--w-03); color: var(--text-dim);
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.filt:hover { border-color: var(--w-26); color: var(--text); }
.filt.on { border-color: var(--accent-50); background: var(--accent-12); color: var(--text); }
.filt span { opacity: 0.55; margin-left: 4px; }
.filt-drift.on { border-color: var(--warn-50); background: var(--warn-12); }

.board { display: flex; flex-direction: column; gap: 12px; }
.row {
  display: grid; grid-template-columns: 92px minmax(0, 1fr); gap: 18px;
  padding: 16px; border-radius: var(--r-14);
  background: var(--w-024); border: 1px solid var(--w-07);
}
.row[hidden] { display: none; }
.thumb { position: relative; }
.thumb img {
  width: 92px; aspect-ratio: 4 / 5; object-fit: cover;
  border-radius: var(--r-10); border: 1px solid var(--line); display: block;
  background: var(--surface);
}
.thumb-tag {
  position: absolute; left: 6px; bottom: 6px;
  font-size: 9.5px; letter-spacing: 0.1em; text-transform: uppercase; font-weight: 700;
  padding: 3px 7px; border-radius: var(--r-hair); background: var(--shale-82); color: var(--text);
}
.row-body { min-width: 0; display: flex; flex-direction: column; gap: 10px; }
.row-head { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; }
.row-label { font-family: var(--font-display); font-weight: 700; font-size: 15px; color: var(--white); }
.round {
  font-size: 10.5px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-42);
}
.row-when { margin-left: auto; font-size: 11.5px; color: var(--ink-32); }
.cnt-upd { background: var(--accent-14); color: var(--accent); border: 1px solid var(--accent-28); }
.cnt-drift { background: var(--warn-13); color: var(--warn); border: 1px solid var(--warn-34); }

.cap-form textarea {
  width: 100%; resize: vertical; min-height: 132px; overflow-y: auto;
  background: var(--w-03); border: 1px solid var(--w-09);
  border-radius: var(--r-10); padding: 10px 12px;
  color: var(--text); font-family: inherit; font-size: 13px; line-height: 1.5;
}
.cap-form textarea:focus { outline: 2px solid var(--accent-60); outline-offset: 1px; }
.row-actions { display: flex; align-items: center; gap: 12px; margin-top: 8px; }
.hint { font-size: 11.5px; color: var(--ink-32); }
.up-form { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.up-form input[type=file] { font-size: 12px; color: var(--ink-55); max-width: 100%; }
.up-form input[type=file]::file-selector-button {
  font-family: inherit; font-size: 12px; font-weight: 600; cursor: pointer;
  margin-right: 10px; padding: 7px 12px; border-radius: var(--r-chip);
  border: 1px solid var(--line-strong); background: var(--panel);
  color: var(--text);
}

.hist-toggle summary {
  cursor: pointer; font-size: 12px; color: var(--ink-50);
  padding: 4px 0; list-style: none;
}
.hist-toggle summary::-webkit-details-marker { display: none; }
.hist-toggle summary::before { content: "▸ "; opacity: 0.5; }
.hist-toggle[open] summary::before { content: "▾ "; }
.hist-toggle summary:hover { color: var(--text); }
.ev-list { list-style: none; margin: 8px 0 0; padding: 0 0 0 2px; display: flex; flex-direction: column; gap: 8px; }
.ev { display: flex; align-items: baseline; gap: 9px; font-size: 12.5px; flex-wrap: wrap; }
.ev-dot { width: 6px; height: 6px; border-radius: var(--r-circle); flex-shrink: 0; background: var(--w-30); }
.ev-approve .ev-dot { background: var(--ok); }
.ev-revise .ev-dot { background: var(--warn); }
.ev-note .ev-dot { background: var(--neon); }
.ev-what { font-weight: 600; color: var(--text); }
.ev-note { color: var(--text-dim); }
.ev-when { margin-left: auto; font-size: 11px; color: var(--ink-30); }
.no-events { font-size: 12px; color: var(--ink-30); }

@media (max-width: 700px) {
  .row { grid-template-columns: 64px minmax(0, 1fr); gap: 12px; padding: 12px; }
  .thumb img { width: 64px; }
  .row-when { margin-left: 0; width: 100%; }
}

/* Board buttons speak the same language as the header controls; the two that
   actually change what the client sees carry the accent. */
.row-body button, .row-detail button {
  /* rail v4: the house radius and the house display face. This generic rule
     was the source of the third radius in the item column. */
  font-family: var(--font-display); font-size: 12.5px; font-weight: 600; cursor: pointer;
  padding: 8px 14px; border-radius: var(--r-chip);
  border: 1px solid var(--accent-40); background: var(--accent-13);
  color: var(--text); white-space: nowrap;
  transition: background-color 120ms ease, border-color 120ms ease;
}
.row-body button:hover, .row-detail button:hover { background: var(--accent-24); border-color: var(--accent-62); }
.row-body button:focus-visible, .row-detail button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.up-form input[type=file] {
  border: 1px dashed var(--line-strong); border-radius: var(--r-10);
  padding: 7px 10px; background: var(--w-02);
}

/* ── Revision panel ──────────────────────────────────────────────────────
   What a designer needs before touching a revision: what is being asked for
   now, and what was already tried so the same mistake is not repeated. */
.rev-panel { border-top: 1px solid var(--w-07); padding-top: 12px; margin-top: 2px; }
.rev-open {
  border-radius: var(--r-card); padding: 12px 14px; margin-bottom: 14px;
  background: var(--warn-09);
  border: 1px solid var(--warn-34);
}
.rev-open-head {
  font-size: 11px; letter-spacing: 0.13em; text-transform: uppercase;
  font-weight: 700; color: var(--warn); margin-bottom: 6px;
}
.rev-open-note { font-size: 13.5px; line-height: 1.55; color: var(--text); }
.rev-open-when { font-size: 11px; color: var(--ink-35); }
.rev-title {
  font-size: 11px; letter-spacing: 0.13em; text-transform: uppercase;
  color: var(--ink-32); margin-bottom: 10px;
}
.rev-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.rev { display: grid; grid-template-columns: 30px minmax(0, 1fr) auto; gap: 10px; align-items: start; }
.rev-round {
  font-size: 10px; font-weight: 700; letter-spacing: 0.06em;
  color: var(--ink-40); padding-top: 2px;
}
.rev-what { font-size: 12.5px; font-weight: 600; color: var(--text); }
.rev-approve .rev-what { color: var(--ok); }
.rev-revise .rev-what { color: var(--warn); }
.rev-delivered .rev-what { color: var(--accent); }
.rev-note {
  font-size: 12.5px; line-height: 1.5; color: var(--ink-66); margin-top: 3px;
}
.rev-when { font-size: 11px; color: var(--ink-28); white-space: nowrap; }
.rev-prev {
  display: inline-flex; align-items: center; gap: 9px; margin-top: 8px;
  text-decoration: none; color: var(--ink-45); font-size: 11.5px;
}
.rev-prev img {
  width: 46px; aspect-ratio: 4 / 5; object-fit: cover; border-radius: var(--r-6);
  border: 1px solid var(--line-mid); display: block;
  filter: grayscale(0.35) brightness(0.85);
}
.rev-prev:hover { color: var(--text); }
.rev-prev:hover img { filter: none; }

/* ── Full-size view ──────────────────────────────────────────────────────
   The thumbnail is a control: click it to actually look at the deliverable. */
.thumb {
  position: relative; padding: 0; border: none; background: none; cursor: pointer;
  display: block; border-radius: var(--r-10);
}
.thumb:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.thumb-zoom {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  border-radius: var(--r-10); background: var(--shale-62);
  font-size: 11px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--white); opacity: 0; transition: opacity 0.15s;
}
.thumb:hover .thumb-zoom, .thumb:focus-visible .thumb-zoom { opacity: 1; }

.lightbox {
  position: fixed; inset: 0; z-index: 200;
  background: var(--pitch-94);
  display: flex; flex-direction: column; padding: 18px 22px 26px;
}
.lightbox[hidden] { display: none; }
.lb-bar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.lb-label { font-family: var(--font-display); font-weight: 700; font-size: 16px; color: var(--white); }
.lb-close {
  font-family: var(--font-body); font-size: 12.5px; font-weight: 600; cursor: pointer;
  padding: 8px 16px; border-radius: var(--r-10);
  border: 1px solid var(--w-16); background: var(--w-05);
  color: var(--text);
}
.lb-close:hover { background: var(--w-10); }
.lb-stage { flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; }
.lb-stage img, .lb-stage video {
  max-width: 100%; max-height: 100%; object-fit: contain;
  border-radius: var(--r-card); border: 1px solid var(--w-10);
}

/* ── Compact board ───────────────────────────────────────────────────────
   Every row open at once is a 24,000px scroll at 60 items. The summary line
   carries what you scan for - state, round, and the open ask - and the work
   surface is one click away. */
.tools { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 4px; }
.search, .sort {
  font-family: var(--font-body); font-size: 12.5px; color: var(--text);
  background: var(--w-03); border: 1px solid var(--line-mid);
  border-radius: var(--r-pill); padding: 8px 14px;
}
.search { min-width: 260px; flex: 1 1 260px; max-width: 420px; }
.search:focus, .sort:focus { outline: 2px solid var(--accent-55); outline-offset: 1px; }
.sort { cursor: pointer; }

.row { padding: 12px 14px; align-items: start; }
.row-head {
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px; width: 100%;
  background: none; border: none; padding: 4px 0; cursor: pointer; text-align: left;
  font-family: var(--font-body);
}
.row-head:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--r-chip); }
.row-caret { color: var(--ink-40); font-size: 11px; transition: transform 0.15s; }
.row.open .row-caret { transform: rotate(90deg); }
.row-ask {
  flex: 1 1 220px; min-width: 0; font-size: 12.5px; color: var(--warn);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.row-detail { display: flex; flex-direction: column; gap: 10px; padding-top: 12px; }
.row-detail[hidden] { display: none; }
.row:not(.open) .thumb img { width: 62px; }

/* The summary line is a disclosure control, not an action: it must not inherit
   the accent styling that Save caption and Upload revision carry. */
.row-body .row-head,
.row-body .row-head:hover,
.row-body .row-head:focus {
  background: none; border: none; border-radius: var(--r-chip);
  padding: 4px 2px; font-size: inherit; font-weight: inherit; white-space: normal;
}
.row-body .row-head:hover .row-label { color: var(--white); }
.row-body .row-head:hover .row-caret { color: var(--text); }
/* a shut row does not need a 92px picture column either */
.row:not(.open) { grid-template-columns: 62px minmax(0, 1fr); align-items: center; }
.row:not(.open) .thumb img { width: 62px; }

/* A change request that was later approved vanishes from `status`. Say so on
   the row, otherwise "in revision 0" reads as "this was never revised". */
.past-rev {
  font-size: 11px; font-weight: 600; letter-spacing: 0.04em;
  color: var(--warn-85);
  border: 1px solid var(--warn-28); border-radius: var(--r-pill);
  padding: 3px 9px; white-space: nowrap;
}

/* ── Action truth (audit U01/U02): outcomes are said, not implied ──────── */
/* visually hidden, still read by AT and password managers */
.vh {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}
.login-body main { display: contents; }
.flash {
  margin-bottom: 22px; padding: 12px 16px; border-radius: var(--r-card);
  font-size: 13.5px; font-weight: 500;
  background: var(--ok-10); color: var(--ok);
  border: 1px solid var(--ok-30);
}
.flash.fail {
  background: var(--warn-09); color: var(--warn);
  border-color: var(--warn-32);
}
.action-note { font-size: 12.5px; color: var(--ok); }
.action-note.fail { color: var(--err); }
.action-note:empty { display: none; }

/* ── Not-found page: a lost client gets a door, not a JSON error ───────── */
.nf-code {
  font-family: var(--font-display); font-weight: 800; font-size: 15px;
  letter-spacing: 0.22em; color: var(--text-faint); margin-top: 4px;
}
.nf-title { font-family: var(--font-display); font-size: 24px; font-weight: 700; letter-spacing: -0.01em; }
/* The 404 and the dead-link page are the two operator-sheet documents the
   CLIENT reads (constitution 4.0a, Dmitry 2026-08-23: "Клиентская - её видит
   клиент"), so their type answers to the client canonical ladder, not to the
   operator tier this sheet runs. `div.login-card` is the not-found card only -
   login.html's card is a <form>. The card itself inherited body's 14.5px, an
   operator rung; this pins it to the client body rung. Every child here
   declares its own size, so nothing on screen moves for the card.
   .nf-body dropped its own 14px - also an operator rung - and inherits 15px.
   That one IS a shift and it is in .autopilot/shifts-dash.md. */
div.login-card { font-size: var(--fs-c-15); }
.nf-body { line-height: 1.65; color: var(--text-dim); }

/* ── Push to Gain ───────────────────────────────────────────────────────
   The button sits with the head actions; the modal is the gate before any
   write reaches Gain. Accent-tinted (an action), never a danger colour. */
.gain-btn {
  border-color: var(--accent-35) !important;
  color: var(--accent) !important;
}
.gain-btn:hover { background: var(--accent-12) !important; }

.gain-back {
  position: fixed; inset: 0; z-index: 220;
  background: var(--pitch-82);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.gain-back[hidden] { display: none; }
.gain-modal {
  width: min(520px, 100%); max-height: 88vh; overflow: auto;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-card);
  padding: 22px 22px 18px; box-shadow: 0 24px 70px var(--k-60);
}
.gain-modal h2 {
  font-family: var(--font-display); font-size: 20px; font-weight: 700;
  letter-spacing: -0.01em; margin: 0 0 14px;
}
.gain-rows { border: 1px solid var(--line); border-radius: var(--r-10); overflow: hidden; }
.gain-row {
  display: flex; justify-content: space-between; gap: 14px; align-items: baseline;
  padding: 10px 12px; font-size: 13px;
}
.gain-row + .gain-row { border-top: 1px solid var(--line); }
.gain-row span { color: var(--text-dim); }
.gain-row b { font-weight: 600; text-align: right; }
.gain-date {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 14px 0 0; font-size: 13px; color: var(--text-dim);
}
.gain-date input {
  background: var(--w-05); border: 1px solid var(--line);
  border-radius: var(--r-chip); color: var(--text); padding: 8px 10px;
  font-family: var(--font-body); font-size: 13px; color-scheme: dark;
}
.gain-note { font-size: 12px; line-height: 1.6; color: var(--text-faint); margin: 12px 0 0; }
.gain-warn {
  font-size: 12.5px; line-height: 1.6; color: var(--warn); margin: 12px 0 0;
  background: var(--warn-08); border: 1px solid var(--warn-25);
  border-radius: var(--r-chip); padding: 9px 11px;
}
.gain-err {
  font-size: 13px; line-height: 1.6; color: var(--err); margin: 0;
  background: var(--err-08); border: 1px solid var(--err-25);
  border-radius: var(--r-chip); padding: 10px 12px;
}
.gain-ok { font-size: 14px; line-height: 1.6; color: var(--ok); margin: 0; font-weight: 600; }
.gain-load { font-size: 13px; color: var(--text-dim); margin: 0; }
.gain-bar {
  height: 6px; border-radius: var(--r-pill); background: var(--w-07);
  overflow: hidden; margin: 12px 0 0;
}
.gain-bar i {
  display: block; height: 100%; width: 100%; background: var(--grad);
  transform: scaleX(0); transform-origin: left; transition: transform 0.3s;
}
.gain-link { color: var(--accent); font-size: 13px; font-weight: 600; }
.gain-foot {
  display: flex; justify-content: flex-end; gap: 10px; margin-top: 18px;
  padding-top: 14px; border-top: 1px solid var(--line);
}
.gain-foot button {
  background: var(--line-faint); border: 1px solid var(--line);
  border-radius: var(--r-9); color: var(--text); padding: 9px 14px;
  font-family: var(--font-body); font-size: 13px; font-weight: 600; cursor: pointer;
}
.gain-foot button:hover { background: var(--w-10); }
.gain-go { border-color: var(--accent-40) !important; color: var(--accent) !important; }
.gain-go:disabled { opacity: 0.4; cursor: not-allowed; }
@media (max-width: 860px) {
  .gain-foot button { min-height: 44px; }
}

/* New workspace: the button sits on the dashboard's title line. */
.head-line {
  display: flex; align-items: center; justify-content: space-between;
  gap: 18px; flex-wrap: wrap;
}
.head-line h1 { margin: 0; }
.head-line .gain-btn {
  background: var(--line-faint); border: 1px solid var(--line);
  border-radius: var(--r-9); padding: 9px 14px;
  font-family: var(--font-body); font-size: 13px; font-weight: 600; cursor: pointer;
}
/* The publish form reuses the Gain modal shell; its controls need dark styling. */
.gain-modal select, .gain-modal input[type="text"] {
  background: var(--w-05); border: 1px solid var(--line);
  border-radius: var(--r-chip); color: var(--text); padding: 9px 10px;
  font-family: var(--font-body); font-size: 13px; color-scheme: dark;
}
.gain-modal select:focus-visible, .gain-modal input:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px;
}
.gain-modal label.gain-date { flex-direction: column; align-items: stretch; }

/* ── Month lifecycle (Dmitry, 2026-08-12) ─────────────────────────────────
   Month state chips, quick actions, and the announce modal fields. The old
   client-group/card grid retired 2026-08-18 with the overview rework; its
   survivors (cg-btn, qa-btn, state chips) now live inside the client cards. */

.cg-btn {
  font-family: var(--font-body); font-size: 12px; font-weight: 600;
  color: var(--text-dim); background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--r-chip);
  padding: 5px 11px; cursor: pointer; transition: color .15s, border-color .15s;
}
.cg-btn:hover { color: var(--text); border-color: var(--accent-45); }

.state-chip {
  flex: none; font-family: var(--font-body); font-size: 10.5px; font-weight: 700;
  letter-spacing: 0.09em; text-transform: uppercase;
  padding: 3px 9px; border-radius: var(--r-pill); border: 1px solid var(--line);
  color: var(--text-dim); white-space: nowrap;
}
.state-chip.st-live { color: var(--accent); border-color: var(--accent-35); background: var(--accent-08); }
.state-chip.st-closed { color: var(--ok); border-color: var(--ok-32); background: var(--ok-07); }
.state-chip.st-hidden { color: var(--pend); border-color: var(--pend-32); background: var(--pend-07); }
.state-chip.st-soon { color: var(--accent); border-color: var(--accent-35); background: var(--accent-08); }

.qa-btn {
  flex: 1; font-family: var(--font-body); font-size: 12px; font-weight: 600;
  color: var(--text-dim); background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--r-chip);
  padding: 7px 10px; cursor: pointer; transition: color .15s, border-color .15s;
}
.qa-btn:hover { color: var(--text); border-color: var(--accent-45); }
.qa-btn:disabled { opacity: 0.45; cursor: default; }
.qa-btn.qa-reveal { color: var(--accent); border-color: var(--accent-35); }
.qa-btn.qa-reveal:hover { color: var(--white); border-color: var(--accent); }

.nw-grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 12px; }
.gain-date select {
  font: inherit; color: var(--text); background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r-chip); padding: 8px 10px;
}

/* the detail header's state control sits with the other actions */
.head-actions .state-chip { align-self: center; }

/* ── Overview rework (concept gate 2026-08-18) ───────────────────────────
   One card per client (Cards) or one band per client (Rail), toggled and
   remembered. Gain's information architecture, DmitrySocial's skin. Both
   projections are server-rendered; only visibility flips. */

.head-tools { display: flex; align-items: center; gap: 12px; }


.num { font-family: var(--font-display); font-variant-numeric: tabular-nums; }
.dot { width: 7px; height: 7px; border-radius: var(--r-circle); display: inline-block; flex: none; }
.dot.ok { background: var(--ok); }
.dot.pend { background: var(--pend); }
.dot.warn { background: var(--warn); }
.dot.cyan { background: var(--accent); }

/* hidden must beat the class display rules (the [hidden]-vs-display trap:
   a bare attribute loses to .rail's display:flex, so both views render) */
.client-grid[hidden], .ov-rail[hidden] { display: none; }


@media (max-width: 640px) {
  .head-tools { width: 100%; justify-content: space-between; }
}

/* ── The bell (phase 2, 2026-08-18) ──────────────────────────────────────
   Grouped operator events, one unread watermark, mark-all-read. Crisp
   dots and hairlines - no glow (round 1 law). */
.bell-wrap { position: relative; }
.bell {
  position: relative; width: 40px; height: 40px; border-radius: var(--r-10);
  border: 1px solid var(--line); background: var(--panel);
  display: grid; place-items: center; cursor: pointer; color: var(--text-dim);
}
.bell:hover { color: var(--text); border-color: var(--line-mid); }
/* ONE badge, on every bell. There were three: this one, `.wsb-bellslot`'s and
   two `.dsx` rules, and the last of them tucked a 14px disc INSIDE a 32px
   button at top/right 3px, where it covered 31% of the glyph - the eyes gate
   read the dashboard bell as destroyed and the workspace one as correct on
   the same visit. That earlier ruling ("THE UNREAD BADGE MUST STAY INSIDE THE
   ROW", 2026-08-18) is reversed here for the dashboard only, and its worry is
   answered instead of ignored: the disc is 15px, not 16px, and hangs 3px, not
   4px, which is the geometry the gate accepted on the top bar. */
.bell-badge {
  position: absolute; top: -3px; right: -3px; min-width: 15px; height: 15px;
  border-radius: var(--r-pill); background: var(--accent); color: var(--accent-ink-2);
  font-family: var(--font-display); font-size: 10px; font-weight: 700;
  display: grid; place-items: center; padding: 0 4px; pointer-events: none;
}
.notif-panel {
  position: absolute; top: 46px; right: 0; width: min(420px, calc(100vw - 32px));
  z-index: 60; background: var(--surface); border: 1px solid var(--line-mid);
  border-radius: var(--r-panel); box-shadow: 0 24px 70px var(--k-55); overflow: hidden;
}
.notif-panel[hidden] { display: none; }
.notif-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px; border-bottom: 1px solid var(--line);
}
.notif-head h3 { font-family: var(--font-display); font-size: 15px; font-weight: 700; margin: 0; }
.notif-read {
  background: none; border: none; color: var(--text-dim); font-size: 12.5px;
  cursor: pointer; font-family: var(--font-body); padding: 0;
}
.notif-read:hover { color: var(--text); }
.notif-list { max-height: min(520px, 70vh); overflow-y: auto; }
.notif {
  display: flex; gap: 12px; padding: 13px 18px; border-bottom: 1px solid var(--line);
  position: relative; font-size: 13px;
}
.notif:last-child { border-bottom: none; }
.notif.unread { background: var(--accent-05); }
.notif.unread::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--grad);
}
.notif-empty { color: var(--text-faint); justify-content: center; padding: 22px 18px; }
.n-icon {
  width: 30px; height: 30px; border-radius: var(--r-9); flex: none; margin-top: 2px;
  display: grid; place-items: center; font-size: 12px;
  background: var(--w-05); border: 1px solid var(--line);
  color: var(--text-dim);
}
.n-icon.n-approvals { color: var(--ok); }
.n-icon.n-revise, .n-icon.n-asset-missing, .n-icon.n-package-drift { color: var(--warn); }
.n-icon.n-rating { color: var(--pend); }
.n-icon.n-publish, .n-icon.n-review-complete, .n-icon.n-gain-push { color: var(--accent); }
.notif-body { min-width: 0; flex: 1; }
.n-title { font-weight: 500; line-height: 1.45; }
.n-title b { font-weight: 700; }
.n-note {
  margin-top: 5px; font-size: 12.5px; color: var(--text-dim);
  border-left: 2px solid var(--line-mid); padding-left: 9px; font-style: italic;
}
.n-items { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 7px; }
.n-items span {
  font-family: var(--font-display); font-size: 10.5px; font-weight: 600;
  color: var(--text-dim); border: 1px solid var(--line); border-radius: var(--r-6);
  padding: 2px 7px;
}
.n-meta {
  margin-top: 6px; font-size: 11.5px; color: var(--text-faint);
  display: flex; gap: 10px;
}
.n-meta a { color: var(--accent); text-decoration: none; }
.n-meta a:hover { text-decoration: underline; }

/* One height for every control that shares a row with the bell (Dmitry,
   2026-08-18: "size of the button and notification are different"). Fixed
   heights, not padding-derived - font metrics vary per machine. */
.head-actions button,
.head-actions .view-as, .head-actions .state-chip { height: 40px; }
.head-tools .gain-btn, .head-actions button, .head-actions .view-as {
  display: inline-flex; align-items: center; padding-top: 0; padding-bottom: 0;
}
.head-actions .state-chip { display: inline-flex; align-items: center; }

/* dark scrollbar inside the panel (Dmitry, 2026-08-18: "make scroll bar black") */
.notif-list { scrollbar-width: thin; scrollbar-color: var(--w-18) transparent; }
.notif-list::-webkit-scrollbar { width: 8px; }
.notif-list::-webkit-scrollbar-track { background: transparent; }
.notif-list::-webkit-scrollbar-thumb {
  background: var(--w-16); border-radius: var(--r-hair);
}
.notif-list::-webkit-scrollbar-thumb:hover { background: var(--w-28); }

/* bell placement on the detail page + whole-card doors (Dmitry, 2026-08-18) */
.crumb-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }

/* ── Phase 3: the board as Gallery + List + slide-over (2026-08-18) ──────
   The old fat collapsible rows die: tiles for looking, compact rows for
   scanning, one slide-over that borrows each item's existing detail DOM. */

.stat-band {
  display: flex; gap: 24px; align-items: center; flex-wrap: wrap;
  border: 1px solid var(--line); background: var(--panel); border-radius: var(--r-14);
  padding: 13px 20px; margin-top: 18px;
}
.stat { display: flex; align-items: baseline; gap: 7px; }
.stat .num { font-size: 21px; font-weight: 800; color: var(--text); }
.stat .lbl { font-size: 12.5px; color: var(--text-dim); }
.stat.pend .num { color: var(--pend); }
.stat.warn .num { color: var(--warn); }
.stat.ok .num { color: var(--ok); }
.stat.upd .num { color: var(--accent); }   /* redelivered, awaiting the client's re-look */
.stat .num-dim { color: var(--text-faint); }
.progress { flex: 1; min-width: 120px; height: 4px; border-radius: var(--r-pill); background: var(--w-05); overflow: hidden; }
.progress i { display: block; height: 100%; background: var(--grad); }

.view-toggle { display: flex; border: 1px solid var(--line); border-radius: var(--r-9); overflow: hidden; margin-left: auto; }
.view-toggle button {
  background: transparent; border: none; color: var(--text-faint); height: 36px;
  padding: 0 14px; font-family: var(--font-body); font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.view-toggle button.on { background: var(--line-faint); color: var(--text); }
.view-toggle button:not(.on):hover { color: var(--text-dim); }

.detail-wrap { display: flex; gap: 22px; align-items: flex-start; }
.detail-wrap .views { flex: 1; min-width: 0; }
#galleryView[hidden], #listView[hidden] { display: none; }

/* gallery */
.gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 14px; }
.tile { cursor: pointer; min-width: 0; }
.tile[hidden] { display: none; }
.tile .frame {
  position: relative; aspect-ratio: 4 / 5; border-radius: var(--r-10); overflow: hidden;
  border: 1px solid var(--line); background: var(--panel);
}
.tile .frame img { width: 100%; height: 100%; object-fit: cover; display: block; }
.tile .frame.frame-reel img { object-fit: contain; background: var(--k-92); }
.tile .frame .play {
  position: absolute; left: 8px; bottom: 8px; width: 24px; height: 24px; border-radius: var(--r-7);
  background: var(--deep-72); border: 1px solid var(--w-18);
  display: grid; place-items: center; color: var(--white); font-size: 9px;
}
.tile[data-updated="1"] .frame { outline: 2px solid var(--accent-pale-70); outline-offset: 1px; }
.tile[data-drift="1"] .frame { outline: 2px solid var(--warn-70); outline-offset: 1px; }
.tile.selected .frame { outline: 2px solid var(--accent); outline-offset: 2px; }
.tile .cap { display: flex; align-items: center; gap: 7px; padding: 7px 2px 0; min-width: 0; }
/* per-item client link (obs 329 promoted to a control): a quiet glyph;
   color speaks only when the copy resolves or fails */
.tcopy {
  flex-shrink: 0; padding: 2px 7px; margin: -2px 0;
  background: none; border: 0; border-radius: var(--r-6); cursor: pointer;
  font-family: var(--font-body); color: var(--text-faint);
  font-size: 13px; line-height: 1.4; transition: color 0.15s;
}
.tcopy:hover { color: var(--text); }
.tcopy:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.tcopy.ok { color: var(--ok); }
.tcopy.fail { color: var(--warn); }
.tcopy-wide {
  border: 1px solid var(--line); padding: 8px 14px; border-radius: var(--r-chip);
  font-size: 13px; font-weight: 600; color: var(--text-dim);
}
.tcopy-wide:hover { color: var(--text); border-color: var(--w-26); }
.row-utility { display: flex; align-items: center; gap: 10px; margin: 2px 0 14px; }
.tile .cap .tname { font-family: var(--font-display); font-size: 12.5px; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tile .cap .dot { margin-left: auto; }
.tile .cap .tround { font-family: var(--font-display); font-size: 11px; color: var(--text-faint); border: 1px solid var(--line); border-radius: var(--r-6); padding: 1px 5px; }
.tile .cap .note-flag { color: var(--warn); font-size: 11px; }

/* compact list rows: the scan view */
.board .row { grid-template-columns: 46px minmax(0, 1fr); gap: 12px; padding: 8px 14px; align-items: center; }
.board .row .thumb img { width: 38px; }
.board .row .row-head { padding: 0; }
.board .row .row-caret { display: none; }

/* slide-over */
.slideover {
  width: 400px; flex: none; background: var(--surface); border: 1px solid var(--line-mid);
  border-radius: var(--r-panel); position: sticky; top: 16px; max-height: calc(100vh - 32px);
  display: flex; flex-direction: column; overflow: hidden;
}
.slideover[hidden] { display: none; }
/* Arrival (2026-09-02). The panel was toggled by `hidden` alone and simply
   popped into place. One short entrance in the house grammar - 200ms on the
   house ease, transform and opacity only, so nothing is laid out twice - and
   the same two properties on the phone sheet from below. Runs on the
   compositor; reduced-motion switches it off with the rest. */
@media (prefers-reduced-motion: no-preference) {
  .slideover:not([hidden]) { animation: so-arrive var(--dur-200) var(--ease-glide) both; }
  @keyframes so-arrive {
    from { opacity: 0; transform: translateX(12px); }
    to   { opacity: 1; transform: none; }
  }
  @media (max-width: 900px) {
    .slideover:not([hidden]) { animation-name: so-arrive-up; }
    @keyframes so-arrive-up {
      from { opacity: 0; transform: translateY(16px); }
      to   { opacity: 1; transform: none; }
    }
  }
}
.so-head { display: flex; align-items: center; gap: 10px; padding: 14px 18px; border-bottom: 1px solid var(--line); flex: none; }
.so-head h3 { font-family: var(--font-display); font-size: 16px; font-weight: 700; margin: 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.so-x { margin-left: auto; background: none; border: none; color: var(--text-faint); cursor: pointer; font-size: 15px; padding: 4px; }
.so-x:hover { color: var(--text); }
.so-scroll { overflow-y: auto; padding: 16px 18px; display: flex; flex-direction: column; gap: 14px; scrollbar-width: thin; scrollbar-color: var(--w-18) transparent; }
.so-scroll::-webkit-scrollbar { width: 8px; }
.so-scroll::-webkit-scrollbar-thumb { background: var(--w-16); border-radius: var(--r-hair); }
.so-preview { display: block; border: 1px solid var(--line); border-radius: var(--r-10); overflow: hidden; background: var(--w-03); padding: 0; cursor: zoom-in; flex: none; }
.so-preview img { width: 100%; display: block; }
#soBody .row-detail { display: flex; flex-direction: column; gap: 14px; }
#soBody .row-detail[hidden] { display: none; }

.ha-spacer { flex: 1; }

@media (max-width: 900px) {
  .slideover {
    position: fixed; inset: auto 0 0 0; top: 8vh; width: auto; max-height: 92vh;
    border-radius: var(--r-panel) var(--r-panel) var(--r-none) var(--r-none); z-index: 70;
  }
  body.so-on { overflow: hidden; }
  .gallery { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 10px; }
}

/* ── Board ergonomics round (Dmitry, 2026-08-18: "I work here") ──────────
   Wide page, a real editor panel, labeled sections, a styled file pick,
   and the destructive controls behind a marked danger zone. */

.dash.dash-wide { max-width: 1560px; }

.slideover { width: clamp(480px, 38vw, 680px); }
.so-preview { align-self: center; max-width: 340px; }
.so-preview img { max-height: 300px; object-fit: contain; }

.so-label {
  font-family: var(--font-display); font-size: 11px; font-weight: 700;
  letter-spacing: 0.15em; text-transform: uppercase; color: var(--text-faint);
  margin: 4px 0 0;
}
.cap-form textarea { min-height: 190px; font-size: 13.5px; line-height: 1.6; }
.btn-primary, .row-detail .btn-primary {
  background: var(--accent) !important; color: var(--accent-ink-2) !important;
  border: none !important; font-weight: 700;
}
.btn-primary:disabled { opacity: 0.35; cursor: not-allowed; }

.file-pick {
  display: flex; align-items: center; gap: 10px; cursor: pointer; min-width: 0;
  border: 1px dashed var(--w-16); border-radius: var(--r-10); padding: 9px 12px;
  flex: 1;
}
.file-pick:hover { border-color: var(--w-28); }
.file-btn {
  flex: none; background: var(--w-07); border: 1px solid var(--line);
  border-radius: var(--r-chip); padding: 6px 12px; font-size: 12.5px; font-weight: 600; color: var(--text);
}
.file-name { color: var(--text-faint); font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.up-form { display: flex; align-items: center; gap: 10px; }

/* THE ZONE IS A REGION, NOT A ROW. Below the suite threshold `.rail` is
   display:contents, so this block lands in the document flow immediately
   under the item board - 0px away, no rule, no name. A red "Danger zone"
   14px under one post's caption reads as that post's. Distance, a rule and
   a name of its own are what make it the workspace's. */
.ws-danger {
  margin-top: 32px; padding-top: 16px; border-top: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 12px; align-items: flex-start;
}
.wsd-title {
  font-family: var(--font-display); font-size: 11px; font-weight: 700;
  letter-spacing: 0.15em; text-transform: uppercase; color: var(--text-faint);
}
.danger-zone { position: relative; }
.danger-zone > summary {
  list-style: none; cursor: pointer; user-select: none;
  height: 40px; display: inline-flex; align-items: center; gap: 7px;
  padding: 0 14px; border-radius: var(--r-card); font-size: 13px; font-weight: 600;
  color: var(--err); border: 1px solid var(--err-30);
  background: var(--err-05);
}
.danger-zone > summary::-webkit-details-marker { display: none; }
.danger-zone[open] > summary { border-radius: var(--r-card) var(--r-card) var(--r-none) var(--r-none); }
.dz-body {
  position: absolute; right: 0; top: 40px; z-index: 50; width: 340px;
  background: var(--surface-2); border: 1px solid var(--err-35);
  border-radius: var(--r-card) var(--r-none) var(--r-card) var(--r-card); padding: 14px; display: flex;
  flex-direction: column; gap: 10px;
  box-shadow: 0 18px 50px var(--k-50);
}
.dz-note { margin: 0; font-size: 12px; color: var(--err-pale-75); line-height: 1.5; }
.dz-body form { display: flex; align-items: center; gap: 10px; }
.dz-body .danger {
  flex: none; background: var(--err-10); color: var(--err);
  border: 1px solid var(--err-40); border-radius: var(--r-9);
  padding: 8px 13px; font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.dz-body .danger:hover { background: var(--err-20); }
.dz-what { font-size: 11.5px; color: var(--text-faint); }

@media (max-width: 1200px) {
  .slideover { width: clamp(420px, 44vw, 560px); }
}

/* ── Phase 4: ClickUp link control + the feedback report ─────────────────── */
.cu-link { position: relative; }
.cu-link > summary {
  list-style: none; cursor: pointer; user-select: none;
  height: 40px; display: inline-flex; align-items: center; padding: 0 14px;
  border-radius: var(--r-card); font-size: 13px; font-weight: 600;
  color: var(--text-dim); border: 1px solid var(--line);
  background: var(--panel);
}
.cu-link > summary::-webkit-details-marker { display: none; }
.cu-body {
  position: absolute; left: 0; top: 44px; z-index: 50; width: 340px;
  background: var(--surface); border: 1px solid var(--line-strong);
  border-radius: var(--r-card); padding: 14px; display: flex; flex-direction: column; gap: 10px;
  box-shadow: 0 18px 50px var(--k-50);
}
.cu-row { display: flex; gap: 8px; }
.cu-row input {
  flex: 1; min-width: 0; background: var(--w-05);
  border: 1px solid var(--line); border-radius: var(--r-9); padding: 9px 11px;
  color: var(--text); font-size: 13px; font-family: var(--font-body);
}
.cu-row button {
  flex: none; background: var(--accent); color: var(--accent-ink-2); border: none;
  border-radius: var(--r-9); padding: 0 16px; font-weight: 700; font-size: 13px; cursor: pointer;
}

.rep-quote {
  margin: 22px 0 0; padding: 16px 20px; border-left: 2px solid var(--pend);
  background: var(--pend-05); border-radius: var(--r-none) var(--r-card) var(--r-card) var(--r-none);
  font-size: 16px; line-height: 1.6; color: var(--text);
}
.rep-quote footer { margin-top: 6px; font-size: 12px; color: var(--text-faint); }
.rep-items { margin-top: 26px; display: flex; flex-direction: column; gap: 10px; }
.rep-item {
  border: 1px solid var(--line); border-radius: var(--r-card); padding: 13px 16px;
  background: var(--panel); display: flex; flex-direction: column; gap: 10px;
}
.rep-item.quiet { opacity: 0.62; }
/* THE REPORT READS AT A MEASURE. At the shell's full 1080px an approved
   item was a label and a chip against the left edge of a 998px row, 86% of
   it empty, and the verbatim client notes ran to ~120 characters a line. */
.dash-report { max-width: 900px; }
/* the row's two anchors, with what the report is about between them */
.rep-head { display: flex; align-items: center; gap: 10px; justify-content: space-between; }
.rep-read { font-size: 12px; color: var(--text-faint); }
.rep-label { font-family: var(--font-display); font-weight: 700; font-size: 14px; }
.rep-note { border-left: 2px solid var(--line-2, var(--line-mid)); padding-left: 12px; }
.rep-note p { margin: 3px 0 0; font-size: 13.5px; line-height: 1.55; color: var(--text); }
.rep-what { font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-faint); font-weight: 700; }
.rep-revise { border-left-color: var(--warn); }
.rep-approve { border-left-color: var(--ok); }

/* ── Link options: the welcome-preview variant behind a chevron on the
   same copy control (Dmitry, 2026-08-18: integrated, no button sprawl) ── */
.cg-copy-more, .copy-more {
  margin-left: -7px; padding: 0 8px 2px;
  border-top-left-radius: 0; border-bottom-left-radius: 0;
  line-height: 1;
}
.cg-copy { border-top-right-radius: 0; border-bottom-right-radius: 0; }
#copyLink { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.copy-more {
  background: var(--line-faint); border: 1px solid var(--line);
  border-left: none; color: var(--text-dim); cursor: pointer;
  border-radius: var(--r-chip); border-top-left-radius: 0; border-bottom-left-radius: 0;
  height: 30px;
}
.copy-more:hover, .cg-copy-more:hover { color: var(--white); }
/* The caret half sits in `.head-actions`, whose own `button` rule weighs
   (0,1,1) and was handing it `--r-card` on all four corners over the flat
   seam above; and the row's 10px gap was only half closed by a -7px margin.
   Two halves of one control, torn apart by the row that holds them. */
.head-actions .copy-more {
  margin-left: -10px;
  border-top-left-radius: 0; border-bottom-left-radius: 0;
}
.link-menu {
  position: absolute; z-index: 80; width: 236px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-10);
  padding: 6px; box-shadow: 0 18px 40px var(--k-50);
}
.link-menu button {
  display: block; width: 100%; text-align: left;
  background: none; border: none; cursor: pointer;
  padding: 8px 10px; border-radius: var(--r-7);
  font: inherit; font-size: 13px; color: var(--text);
}
.link-menu button:hover { background: var(--line-faint); }
.lm-note {
  margin: 4px 10px 6px; font-size: 11px; line-height: 1.5;
  color: var(--text-faint);
}

/* ── The Control Room calendar (Dmitry, 2026-08-18): read-only lens on
   what Gain has scheduled - live per open, snapshot marked stale ── */
.gcal {
  margin: 14px 0 4px; border: 1px solid var(--line);
  border-radius: var(--r-card); background: var(--w-02);
}
.gcal > summary {
  cursor: pointer; list-style: none; padding: 12px 16px;
  font-weight: 600; font-size: 14px;
}
.gcal > summary::-webkit-details-marker { display: none; }
.gcal > summary::before { content: "\25B8"; margin-right: 8px; color: var(--text-dim); }
.gcal[open] > summary::before { content: "\25BE"; }
.gcal-sub { margin-left: 10px; font-weight: 400; font-size: 12px; color: var(--text-dim); }
.gcal-body { padding: 4px 16px 16px; }
.gcal-note { font-size: 13px; color: var(--text-dim); padding: 8px 0 4px; }
.gcal-err { color: var(--warn); }
.gcal-retry {
  margin-top: 8px; padding: 7px 14px; border-radius: var(--r-chip);
  background: var(--line-faint); border: 1px solid var(--line);
  color: var(--text); cursor: pointer; font: inherit; font-size: 13px;
}
.gcal-head {
  display: flex; justify-content: space-between; align-items: center;
  font-size: 12px; color: var(--text-dim); padding: 4px 0 10px;
}
.gcal-head a { color: var(--accent); text-decoration: none; }
.gcal-dow {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px;
  font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-faint); padding-bottom: 4px;
}
.gcal-dow span { padding-left: 7px; }
.gcal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.gcal-day {
  min-height: 52px; border: 1px solid var(--line); border-radius: var(--r-chip);
  padding: 5px 7px; font-size: 11px; color: var(--text-dim);
  background: var(--w-016);
}
.gcal-day .d { display: block; margin-bottom: 4px; }
.gcal-day.past { opacity: 0.45; }
.gcal-day.today { border-color: var(--accent); }
.gcal-day.today .d { color: var(--accent); font-weight: 700; }
.gdot {
  display: inline-block; width: 9px; height: 9px; border-radius: var(--r-circle);
  margin: 0 3px 2px 0; background: var(--accent);
}
.gdot.reel { background: var(--accent-2); }
.gdot.story { background: var(--ink-45); }
.gdot.draft { background: transparent; border: 2px solid currentColor; width: 8px; height: 8px; }
.gdot.post.draft { color: var(--accent); }
.gdot.reel.draft { color: var(--accent-2); }
.gdot.story.draft { color: var(--ink-45); }
.gcal-legend {
  display: flex; gap: 16px; padding-top: 10px;
  font-size: 11px; color: var(--text-dim);
}
.gcal-legend span { display: inline-flex; align-items: center; gap: 5px; }

/* ═══ Audit round (2026-08-19): phone geometry, hit areas, honest ink ═══
   Findings from the live desktop+mobile pass and the static audit, fixed at
   the end of the sheet so the corrections win the cascade. */

/* The workspace slide-over on phones: the wide-page rules below 1200px kept
   overriding the 900px width:auto (later in the sheet wins), pinning the
   panel at 420-560px on 320-390px screens. Reasserted last. */
@media (max-width: 900px) {
  .slideover { width: auto; max-width: 100%; }
}

/* Fixed 340px popovers exceeded a 320px phone's ~288px content width. */
.dz-body, .cu-body { width: min(340px, calc(100vw - 32px)); }

/* The header tool row cannot fit one line under ~620px: labels wrapped
   INSIDE the fixed-height controls. The row itself wraps instead. */
@media (max-width: 620px) {
  .head-tools { flex-wrap: wrap; row-gap: 8px; }
}

/* 44pt on any coarse pointer (DESIGN.md touch law): the area grows, the
   painted control does not - invisible halo, same as the deck's chips. */
@media (pointer: coarse) {
  .bell, .ov-toggle button, .view-toggle button, #soClose, .notif-read,
  .split .more, .tcopy, .row-head { position: relative; }
  .bell::after, .ov-toggle button::after, .view-toggle button::after,
  #soClose::after, .notif-read::after, .split .more::after {
    content: ""; position: absolute; inset: -6px;
  }
  /* .cg-copy-more was named here for the card's chevron, and nothing has
     rendered that class since the split control became .split .more - the
     26px chevron the owner taps on the iPhone had no halo at all. The
     per-tile copy glyph (.tcopy, ~14px wide) never had one (2026-09-02). */
  .tcopy::after { content: ""; position: absolute; inset: -12px; }
}

/* Readable metadata sits at Theme v2's readable floor, not under it: dates,
   hints and round labels are read, so they wear --text-faint (52%). Alphas
   of 0.28-0.45 were decoration-grade ink on words that carry information. */
.row-when, .hint, .rev-open-when, .rev-title, .rev-when, .round,
.rev-prev, .dz-what { color: var(--text-faint); }

/* the gallery tile is now keyboard-reachable (role=button): show the ring */
#galleryView .tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--r-10); }

/* ════════════════════════════════════════════════════════════════════════
   THE WORKING SUITE (Dmitry, 2026-08-20)
   Two panes at >=1200px: rail (identity, actions, working progress, film-
   strip) + canvas (grid mode three across / item mode artwork + edit column).
   Below the threshold every wrapper is display:contents, so phones render
   the exact pre-suite flow. Theme v2 language throughout - void ground,
   hairlines, the four state signals; brand lives in the precise details.
   ════════════════════════════════════════════════════════════════════════ */

/* wrappers are invisible until the suite turns on */
.ws-suite, .rail, .canvas { display: contents; }
.canvas-head, #canvasGrid, #canvasItem { display: none; }

/* the working progress bar exists at EVERY width - segments are filters */
.progress.seg-bar { height: 8px; border-radius: var(--r-hair); display: flex; gap: 2px; background: none; overflow: visible; }
.segf {
  display: block; height: 100%; border: 0; padding: 0; cursor: pointer;
  border-radius: var(--r-3); min-width: 8px; flex-grow: 0; flex-shrink: 1;
  transition: transform 140ms cubic-bezier(0.23, 1, 0.32, 1), filter 140ms ease;
}
.segf:hover { transform: scaleY(1.5); filter: brightness(1.25); }
.segf:active { transform: scaleY(1.2); }
.segf:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sf-ok   { background: var(--ok); }
.sf-upd  { background: var(--accent); }
.sf-warn { background: var(--warn); }
.sf-pend { background: var(--ink-22); }
/* while one segment steers, the others step back; click it again for All */
.seg-bar.seg-filtering .segf { opacity: 0.28; }
.seg-bar.seg-filtering .segf.seg-on { opacity: 1; transform: scaleY(1.5); }
.next-decide {
  border: 1px solid var(--accent-40); background: var(--accent-08);
  color: var(--text); font-family: var(--font-body); font-size: 12px; font-weight: 600;
  padding: 6px 12px; border-radius: var(--r-pill); cursor: pointer; white-space: nowrap;
  transition: background 140ms ease, transform 120ms ease;
}
.next-decide:hover { background: var(--accent-16); }
.next-decide:active { transform: scale(0.97); }

/* ── suite mode (rail v4, 2026-08-20) ──────────────────────────────────────
   ALL that is left of suite-on: the layout switch. The ~140 lines that used
   to restyle the narrow document's header, stat band, filters, tools and
   filmstrip into a 332px column are DELETED - those components are now the
   rail's own (see the RAIL v4 section at the end of this sheet). A suite-on
   rule that sets a font size, a padding or a colour on a phone-page component
   is the defect this rebuild removed; tests/test_layout_contract.py fails if
   one comes back. */
body.suite-on .dash.dash-wide { max-width: none; padding: 0; margin: 0; }
body.suite-on .ws-suite {
  display: grid; grid-template-columns: var(--rail-w) minmax(0, 1fr);
  height: calc(100dvh - var(--bar-h)); overflow: hidden;
}
/* the narrow document's whole shell steps out of the flow: it is HIDDEN, not
   reshaped. Its live parts (the slide-over, the calendar, the Gain modal)
   have already been relocated by the suite glue before this matters. */
body.suite-on .rail { display: none; }
/* clear of the bar, and ABOVE it: at z-index 40 the flash tied with the
   bar and lost on document order, so "Link rotated" and "Reset refused"
   were painted over by opaque chrome and never seen. */
body.suite-on .flash {
  position: fixed; top: calc(var(--bar-h) + 12px); right: 16px; z-index: 60;
  max-width: min(460px, calc(100vw - 32px));
}

/* THE CANVAS */
body.suite-on .canvas {
  display: flex; flex-direction: column; min-height: 0; min-width: 0;
  background: var(--bg); overflow: hidden;
}
body.suite-on .canvas-head {
  display: flex; align-items: center; gap: 16px;
  padding: 13px 26px; border-bottom: 1px solid var(--line); flex: none;
}
body.suite-on .canvas-head[hidden] { display: none; }
body.suite-on .cv-back {
  border: 1px solid var(--line); background: none; color: var(--text-dim);
  font-family: var(--font-body); font-size: 12.5px; font-weight: 600;
  padding: 6px 14px; border-radius: var(--r-pill); cursor: pointer;
  transition: color 140ms ease, border-color 140ms ease, transform 120ms ease;
}
body.suite-on .cv-back:hover { color: var(--text); border-color: var(--line-strong); }
body.suite-on .cv-back:active { transform: scale(0.97); }
body.suite-on .cv-pos { font-size: 12px; color: var(--text-faint); }
body.suite-on .cv-nav { margin-left: auto; display: flex; gap: 6px; }
body.suite-on .cv-nav button {
  width: 34px; height: 30px; border: 1px solid var(--line); background: none;
  color: var(--text-dim); border-radius: var(--r-chip); cursor: pointer; font-size: 13px;
  transition: color 140ms ease, border-color 140ms ease, transform 120ms ease;
}
body.suite-on .cv-nav button:hover { color: var(--text); border-color: var(--line-strong); }
body.suite-on .cv-nav button:active { transform: scale(0.95); }

/* grid mode - the month as the feed reads it, three across */
body.suite-on #canvasGrid {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 20px 16px; padding: 8px 28px 56px; overflow-y: auto; align-content: start;
  /* 3 across at 300px, per present-grids-three-across. It was 400px tiles in a
     1280 cap, which is what "the grid does not need to be that big" was about. */
  width: 100%; max-width: calc(var(--tile-w) * 3 + 32px); margin: 0 auto;
  scrollbar-width: thin; scrollbar-color: var(--line-strong) transparent;
}
body.suite-on #canvasGrid[hidden] { display: none; }   /* author rules outrank the UA hidden */
body.suite-on #canvasGrid .ctile[hidden] { display: none; }
.ctile {
  display: block; border: 0; padding: 0; background: none; cursor: pointer;
  text-align: left; border-radius: var(--r-card);
}
.ctile:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.ctile .ct-frame {
  display: block; position: relative; border-radius: var(--r-14); overflow: hidden;
  background: var(--surface-3); box-shadow: var(--edge), var(--drop-m);
  transition: box-shadow 180ms ease;
}
/* hover LIGHTS the tile. Nothing moves: the lift was the thing that felt wrong.
   The light is an overlay whose opacity animates on the compositor; a filter
   transition repainted the whole photo tile for 180ms on every mouse sweep
   across a 21-tile grid (2026-09-02). */
.ctile .ct-frame::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: var(--w-09); opacity: 0; transition: opacity 180ms ease;
}
.ctile:hover .ct-frame::after { opacity: 1; }
.ctile:hover .ct-frame {
  box-shadow: inset 0 1px 0 var(--w-09), var(--drop-l);
}
.ctile.selected .ct-frame {
  box-shadow: inset 0 0 0 1.5px var(--accent-75), var(--drop-l);
}
.ctile img { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; display: block; }
/* A STORY IS 9:16 AND IS NOT REVIEW WORK. It keeps its own shape rather than
   being cropped to the feed's 4:5 - the whole point of showing it here is to
   check the delivered artwork - and it wears no status dot, because there is
   no decision attached to it. The quieter frame says "delivered" before the
   word does. */
.ctile-story img { aspect-ratio: 9 / 16; }
/* the break that keeps 9:16 out of a 4:5 row */
.cg-label {
  grid-column: 1 / -1; display: flex; align-items: baseline; gap: 10px;
  padding: 22px 2px 2px; border-top: 1px solid var(--line); margin-top: 10px;
}
/* display:flex OUTRANKS the [hidden] attribute, so the filter could set
   hidden on this heading and it stayed on screen - a "Stories" line with
   nothing under it under the Posts filter. Every element that a filter can
   hide and that also sets display needs this. */
.cg-label[hidden] { display: none; }
.cg-label > span:first-child {
  font-family: var(--font-display); font-size: 14px; font-weight: 600; color: var(--text);
}
.cg-label-note {
  font-size: 11px; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--text-faint);
}
.ctile-story .ct-frame { opacity: 0.82; transition: opacity .18s ease; }
.ctile-story:hover .ct-frame { opacity: 1; }
.ct-note {
  font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-faint); white-space: nowrap;
}
.wsr-story .wsr-ith img { object-fit: cover; }
/* a missing derivative never shows the browser's broken glyph: the frame
   keeps its panel ground and the caption still names the piece */
.ctile .ct-frame.noimg img { visibility: hidden; }
.ctile .ct-frame.noimg::after {
  content: "no preview"; position: absolute; inset: 0; display: grid; place-items: center;
  font-size: 11px; color: var(--text-faint);
}
.ctile .ct-frame.noimg { position: relative; }
.ctile .ct-frame .play {
  position: absolute; right: 10px; top: 10px; font-size: 11px;
  color: var(--white); background: var(--k-55); border-radius: var(--r-pill);
  width: 26px; height: 26px; display: grid; place-items: center;
}
.ctile .ct-cap {
  display: flex; align-items: center; gap: 8px; padding: 10px 3px 0; min-width: 0;
}
.ctile .ct-name {
  font-family: var(--font-display); font-size: 13px; font-weight: 600;
  color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ctile .ct-cap .dot { margin-left: auto; flex: none; }

/* item mode - the artwork large, the edit column beside it */
body.suite-on #canvasItem { display: block; flex: 1; min-height: 0; }
body.suite-on #canvasItem[hidden] { display: none; }
body.suite-on #canvasItem .slideover {
  position: static; width: 100%; height: 100%; max-height: none;
  border: 0; border-radius: var(--r-none); background: none;
  display: grid; grid-template-columns: minmax(0, 1fr) 420px;
  grid-template-rows: auto minmax(0, 1fr); overflow: hidden;
}
body.suite-on #canvasItem .slideover[hidden] { display: none; }
body.suite-on #canvasItem .so-head {
  grid-column: 2; grid-row: 1; background: var(--bg);
  border: 0; padding: 24px 26px 4px;   /* no fence */
}
body.suite-on #canvasItem .so-x { display: none; }   /* the canvas-head owns closing */
body.suite-on #canvasItem .so-scroll {
  display: contents;   /* stage and column place themselves on the grid */
}
body.suite-on #canvasItem .so-preview {
  grid-column: 1; grid-row: 1 / span 2; align-self: stretch; justify-self: stretch;
  margin: 0; padding: 56px 44px 64px; max-width: none; min-height: 0;
  display: grid; place-items: center; overflow: hidden;
  /* the ROW must be capped, not just the picture. A grid row sizes itself
     from the image's UNCLAMPED intrinsic height, so the row came out 965px
     inside a 765px content box: the picture was then centred in a row that
     overflowed, which is why it sat low and ate the bottom air. */
  grid-template-rows: minmax(0, 1fr); grid-template-columns: minmax(0, 1fr);
  border: 0; background: none; border-radius: var(--r-none); cursor: zoom-in;
}
body.suite-on #canvasItem .so-preview img {
  /* NO viewport-height term. DESIGN.md bans one on artwork (the phone
     corridor learned it when in-app web views resized mid-scroll), and
     max-height:calc(100dvh - 116px) is exactly why the hero ate the screen.
     The stage owns the air; the picture fills what is left, capped. */
  /* BOTH caps. The container percentage alone did not hold: measured at
     1600x1000 the picture came out 97% of the viewport and overflowed its
     stage by 136px, because the stage row is taller than the space the eye
     actually has. The px ceiling is what guarantees the air. */
  /* HEIGHT, not max-height. The stage is filled with the 480px thumb first
     and the sharp file swaps in behind it, and under a max-height the thumb
     laid out at its own intrinsic 480x600 while the sharp one hit the 640 cap
     at 512x640 - so the artwork POPPED bigger the moment it arrived. Pinning
     the height makes both sources land on the same box: same picture, same
     place, only sharper. max-width still guards an unusually wide render, and
     object-fit keeps it undistorted if it ever bites. */
  height: min(100%, 640px); max-height: min(100%, 640px);
  width: auto; max-width: 100%;
  object-fit: contain; border-radius: var(--r-panel);
  box-shadow: var(--drop-l);
}
body.suite-on #canvasItem #soBody {
  grid-column: 2; grid-row: 2;
  overflow-y: auto; padding: 24px 26px 40px; background: var(--bg);
  scrollbar-width: thin; scrollbar-color: var(--line-strong) transparent;
}
body.suite-on #canvasItem #soBody .row-detail { display: flex; flex-direction: column; gap: 18px; }

/* ============================================================================
   RAIL v4 (Dmitry, 2026-08-20) - the desk surfaces as their OWN components.
   Nothing here may be written under body.suite-on: these are components, not
   adaptations. Tokens only, house scales only. Rail v1 and v3 were the wide
   page squeezed into a narrow column by overrides, and were rejected twice.
   Spec: docs/[C] SPEC - Workspace Rail v4 (2026-08-20).md
   ========================================================================== */
:root {
  --rail-w: 320px;
  --bar-h: 56px;
  --ease-out: cubic-bezier(.23, 1, .32, 1);
  /* CANVAS v5 (Dmitry, 2026-08-20): "everything together feels like I am in a
     prison box - this is a creative suite so elements need to float."
     The cages were four fences: a border under the bar, one down the rail, one
     under the canvas head, and one round every card. They are gone. A raised
     surface is now a white-alpha panel with a 1px TOP highlight (that is the
     edge) and a deep black shadow, on a pure black ground.
     Permitted on OPERATOR surfaces only - the client deck keeps DESIGN.md's
     no-glow law exactly as ratified on 2026-08-19. */
  --lift-1: var(--w-027);   /* the baseline literal exactly: 0.028 and 0.027 are both 8-bit bucket 7 */
  --lift-2: var(--w-055);   /* the baseline literal exactly */
  --edge: inset 0 1px 0 var(--w-055);
  --drop-s: 0 6px 18px -8px var(--k-90);
  --drop-m: 0 18px 44px -16px var(--k-95);
  --drop-l: 0 48px 110px -32px rgba(0, 0, 0, 1);
  --tile-w: 300px;
}
@media (min-width: 1680px) { :root { --rail-w: 356px; } }

/* ---- the house button: one shape, three inks, one press ---- */
.wsb-btn {
  font-family: var(--font-display); font-weight: 600; font-size: 12.5px;
  height: 32px; padding: 0 13px; border-radius: var(--r-9); white-space: nowrap;
  background: var(--lift-1); border: 0; box-shadow: var(--edge); color: var(--text);
  cursor: pointer; text-decoration: none; display: inline-flex; align-items: center;
  transition: background-color 120ms ease, border-color 120ms ease,
              transform 160ms var(--ease-out);
}
.wsb-btn:hover { background: var(--lift-2); }
.wsb-btn:active { transform: scale(0.97); }
.wsb-btn[disabled] { opacity: 0.42; cursor: default; }
.wsb-btn[disabled]:hover { background: var(--lift-1); }
.wsb-btn[disabled]:active { transform: none; }
.wsb-btn.key { background: var(--accent); box-shadow: none; color: var(--accent-ink-3); }
.wsb-btn.key:hover { background: var(--accent-hi-2); }
.wsb-btn.danger { background: none; box-shadow: inset 0 0 0 1px var(--err-28); color: var(--err); }
.wsb-btn.danger:hover { background: var(--err-12); }
.wsb-ibtn {
  width: 32px; height: 32px; border: 0; background: none; border-radius: var(--r-chip);
  display: grid; place-items: center; color: var(--text-dim); cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease, transform 160ms var(--ease-out);
}
.wsb-ibtn:hover { background: var(--lift-2); color: var(--text); }
.wsb-ibtn:active { transform: scale(0.94); }
/* a split button is ONE control with a seam, never a button plus a stray glyph */
.wsb-split { display: inline-flex; }
.wsb-split > .wsb-btn:first-child { border-radius: var(--r-chip) var(--r-none) var(--r-none) var(--r-chip); border-right-color: transparent; }
.wsb-split > .wsb-btn:last-child { border-radius: var(--r-none) var(--r-chip) var(--r-chip) var(--r-none); padding: 0 8px; font-size: 10px; color: var(--text-faint); }
.wsb-btn:focus-visible, .wsb-ibtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- the top bar ---- */
.wsb-bar { display: none; }
.wsb-sep { width: 1px; height: 20px; background: var(--line); flex: 0 0 1px; }
.wsb-id { display: flex; align-items: baseline; gap: 9px; min-width: 0; }
.wsb-name {
  font-family: var(--font-display); font-weight: 700; font-size: 15px;
  letter-spacing: -0.01em; white-space: nowrap;
}
.wsb-month { font-size: 12px; color: var(--text-faint); white-space: nowrap; }
.wsb-chip {
  font-family: var(--font-display); font-size: 10px; font-weight: 600;
  letter-spacing: 0.07em; text-transform: uppercase; padding: 4px 8px;
  border-radius: var(--r-pill); align-self: center; white-space: nowrap;
  background: var(--accent-12); color: var(--accent);
}
.wsb-chip.st-closed { background: var(--ok-12); color: var(--ok); }
.wsb-chip.st-hidden { background: var(--w-07); color: var(--text-dim); }
.wsb-sp { flex: 1; }
/* the bell wears the bar's icon-button shape, not the 40px header one */
.wsb-bellslot:empty { display: none; }
.wsb-bellslot .bell { width: 32px; height: 32px; border-radius: var(--r-chip); background: none; border: 0; }
.wsb-bellslot .bell:hover { background: var(--w-07); color: var(--text); }
.wsb-bellslot .bell svg { width: 15px; height: 15px; }
.wsb-bellslot .notif-panel { top: 40px; }
.wsb-note { font-size: 11.5px; color: var(--text-dim); white-space: nowrap; }
.wsb-note:empty { display: none; }
.wsb-note.fail { color: var(--err); }

.wsb-menu {
  position: absolute; top: calc(var(--bar-h) - 6px); right: 12px; z-index: 30;
  width: 268px; padding: 2px; border-radius: var(--r-card);
  background: var(--surface-2); border: 1px solid var(--line-strong);
  box-shadow: 0 18px 48px var(--k-60);
  transform-origin: top right; transform: scale(0.97); opacity: 0;
  visibility: hidden;   /* a closed popover LEAVES the page, hit-testing included */
  transition: opacity 150ms var(--ease-out), transform 150ms var(--ease-out),
              visibility 150ms linear;
}
.wsb-menu.open { transform: scale(1); opacity: 1; visibility: visible; }
.wsb-ms { padding: 12px 12px 14px; display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
.wsb-ms + .wsb-ms { border-top: 1px solid var(--line); }
.wsb-ms h4 {
  font-family: var(--font-display); font-size: 10px; font-weight: 600;
  letter-spacing: 0.09em; text-transform: uppercase; color: var(--text-faint);
}
.wsb-ms form { display: flex; align-items: center; gap: 8px; width: 100%; }
.wsb-why { font-size: 11px; color: var(--text-faint); line-height: 1.45; }
.wsb-err { font-size: 11px; color: var(--err); }
.wsb-link, .wsb-cu input {
  width: 100%; height: 30px; padding: 0 10px; border-radius: var(--r-chip);
  background: var(--surface); border: 1px solid var(--line);
  color: var(--text-dim); font-size: 11.5px;
}
.wsb-cu input:focus { outline: none; border-color: var(--accent-55); }
.wsb-cu { display: flex; gap: 6px; width: 100%; }
.wsb-cu input { flex: 1; min-width: 0; }
.wsb-danger h4 { color: var(--err); opacity: 0.75; }

/* ---- the rail ---- */
.wsr { display: none; }
.wsr-head { padding: 16px 16px 0; display: flex; flex-direction: column; gap: 16px; }
.wsr-list {
  overflow-y: auto; padding: 2px 10px 20px; min-height: 0;
  scrollbar-width: thin; scrollbar-color: var(--w-10) transparent;
}
.wsr-list::-webkit-scrollbar { width: 8px; }
.wsr-list::-webkit-scrollbar-thumb { background: var(--w-10); border-radius: var(--r-hair); }
.wsr-none { font-size: 12px; color: var(--text-faint); padding: 14px 6px; }

.wsr-decided { display: flex; align-items: baseline; gap: 7px; }
.wsr-n {
  font-family: var(--font-display); font-weight: 800; font-size: 28px; line-height: 1;
  letter-spacing: -0.02em; font-variant-numeric: tabular-nums;
}
.wsr-of {
  font-family: var(--font-display); font-weight: 600; font-size: 15px;
  color: var(--text-faint); font-variant-numeric: tabular-nums;
}
.wsr-w { font-size: 12px; color: var(--text-dim); margin-left: auto; }
.wsr-bar { display: flex; gap: 2px; height: 6px; margin-top: 10px; }
.wsr-bar > span { border-radius: var(--r-pill); min-width: 10px; }
.wsr-s-ok { background: var(--ok); }
.wsr-s-upd { background: var(--accent); }
.wsr-s-warn { background: var(--warn); }
.wsr-s-pend { background: var(--pend); }
/* the UNDECIDED remainder: a track, not a state. It must read as empty, or
   the bar goes back to looking full on a month where nothing is decided. */
.wsr-s-todo { background: var(--w-09); }

.wsr-cards { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-top: 10px; }
.wsr-card {
  display: flex; flex-direction: column; gap: 4px; text-align: left; cursor: pointer;
  padding: 9px 11px; border-radius: var(--r-card); background: var(--lift-1);
  box-shadow: var(--edge), var(--drop-s); border: 0;
  transition: background-color 120ms ease, border-color 120ms ease,
              transform 160ms var(--ease-out);
}
.wsr-card:hover { background: var(--lift-2); }
.wsr-card:active { transform: scale(0.985); }
.wsr-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.wsr-card.on { background: var(--accent-10); box-shadow: inset 0 1px 0 var(--accent-25), var(--drop-s); }
/* A tile at rest is quiet, not invisible. `opacity: 0.42` faded ground, dot,
   number and label together, and 0.62 alpha of `--text-dim` under it landed
   #404041 on #020202 - 2.0:1, measured. Quiet is now carried by the things
   with no legibility duty: the dot, and the lift the tile no longer earns. */
.wsr-card.zero { cursor: default; box-shadow: var(--edge); }
.wsr-card.zero .wsr-d { opacity: 0.4; }
.wsr-card.zero .wsr-cn { color: var(--text-dim); }
.wsr-card.zero:hover { background: var(--lift-1); }
.wsr-card.zero:active { transform: none; }
.wsr-ct { display: flex; align-items: center; gap: 6px; }
.wsr-cn {
  font-family: var(--font-display); font-weight: 700; font-size: 18px; line-height: 1;
  font-variant-numeric: tabular-nums;
}
.wsr-cl { font-size: 11px; color: var(--text-dim); line-height: 1; }
.wsr-d { width: 6px; height: 6px; border-radius: var(--r-circle); flex: 0 0 6px; }
.wsr-d-ok { background: var(--ok); }
.wsr-d-upd { background: var(--accent); }
.wsr-d-warn { background: var(--warn); }
.wsr-d-pend { background: var(--pend); }

#wsrJump {
  display: flex; align-items: center; gap: 8px; width: 100%; height: 40px;
  padding: 0 12px; border-radius: var(--r-10); cursor: pointer;
  background: var(--accent-10); border: 1px solid var(--accent-28);
  color: var(--accent); font-family: var(--font-display); font-weight: 600; font-size: 12.5px;
  transition: background-color 120ms ease, transform 160ms var(--ease-out);
}
#wsrJump:hover { background: var(--accent-16); }
#wsrJump:active { transform: scale(0.985); }
#wsrJump:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.wsr-jt {
  margin-left: auto; font-family: var(--font-body); font-weight: 400;
  font-size: 12px; color: var(--text-faint);
}
.wsr-ja { font-size: 13px; }
.wsr-rest {
  display: flex; align-items: center; gap: 8px; min-height: 40px; padding: 0 12px;
  border-radius: var(--r-card); background: var(--lift-1); box-shadow: var(--edge);
  font-size: 12px; color: var(--text-dim);
}

.wsr-find { display: flex; flex-direction: column; gap: 8px; }
.wsr-search { position: relative; }
.wsr-search svg {
  position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
  color: var(--text-faint); pointer-events: none;
}
.wsr-search input {
  width: 100%; height: 36px; padding: 0 12px 0 33px; border-radius: var(--r-10);
  background: var(--lift-1); border: 0; box-shadow: var(--edge);
  color: var(--text); font-size: 12.5px; transition: box-shadow 140ms ease;
}
.wsr-search input::placeholder { color: var(--text-faint); }
.wsr-search input:focus { outline: none; box-shadow: inset 0 0 0 1px var(--accent-50); }

.wsr-seg {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 3px;
  padding: 3px; border-radius: var(--r-11); background: var(--lift-1); border: 0; box-shadow: var(--edge);
}
.wsr-seg button {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 6px 0 5px; border: 0; background: none; border-radius: var(--r-7); cursor: pointer;
  transition: background-color 140ms ease;   /* no sliding pill: 74px cells jitter */
}
.wsr-seg button:hover:not(.on):not(.zero) { background: var(--w-05); }
.wsr-seg button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.wsr-seg .wsr-sn {
  font-family: var(--font-display); font-weight: 700; font-size: 13px; line-height: 1;
  color: var(--text); font-variant-numeric: tabular-nums;
}
.wsr-seg .wsr-sl { font-size: 10px; color: var(--text-faint); letter-spacing: 0.02em; line-height: 1; }
.wsr-seg button.on { background: var(--lift-2); box-shadow: var(--edge); }
.wsr-seg button.on .wsr-sl { color: var(--text-dim); }
.wsr-seg button.zero { opacity: 0.38; cursor: default; }

.wsr-lhead {
  position: relative; display: flex; align-items: center; height: 30px; margin-top: 4px;
}
.wsr-over {
  font-family: var(--font-display); font-size: 10px; font-weight: 600;
  letter-spacing: 0.09em; text-transform: uppercase; color: var(--text-faint);
}
#wsrSortBtn {
  margin-left: auto; display: flex; align-items: center; gap: 5px;
  border: 0; background: none; color: var(--text-dim); font-size: 11px;
  padding: 3px 6px; border-radius: var(--r-6); cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}
#wsrSortBtn:hover { background: rgba(255, 255, 255, 0.06); color: var(--text); }
#wsrSortBtn i { font-size: 8px; opacity: 0.7; font-style: normal; }
.wsr-sortmenu {
  position: absolute; right: 0; top: 26px; z-index: 20; width: 176px; padding: 4px;
  border-radius: var(--r-10); background: var(--surface-2); border: 1px solid var(--line-strong);
  box-shadow: 0 14px 40px var(--k-60);
  transform-origin: top right; transform: scale(0.97); opacity: 0; visibility: hidden;
  transition: opacity 150ms var(--ease-out), transform 150ms var(--ease-out),
              visibility 150ms linear;
}
.wsr-sortmenu.open { transform: scale(1); opacity: 1; visibility: visible; }
.wsr-sortmenu button {
  display: block; width: 100%; text-align: left; border: 0; background: none;
  color: var(--text-dim); font-size: 12px; padding: 7px 9px; border-radius: var(--r-7); cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}
.wsr-sortmenu button:hover { background: rgba(255, 255, 255, 0.06); color: var(--text); }
.wsr-sortmenu button.on { color: var(--accent); }

.wsr-item {
  position: relative; display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 6px; border: 0; background: none; border-radius: var(--r-10); cursor: pointer;
  text-align: left; transition: background-color 120ms ease, transform 160ms var(--ease-out);
}
.wsr-item[hidden] { display: none; }
.wsr-item:hover { background: var(--lift-1); }
.wsr-item:active { transform: scale(0.995); }
.wsr-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.wsr-item.on { background: var(--lift-1); box-shadow: var(--edge), var(--drop-s); }
.wsr-item.on::before {
  content: ""; position: absolute; left: 0; top: 9px; bottom: 9px; width: 2px;
  border-radius: var(--r-pill); background: var(--accent);
}
.wsr-ith {
  position: relative; width: 44px; height: 44px; flex: 0 0 44px;
  border-radius: var(--r-7); overflow: hidden; background: var(--panel);
}
.wsr-ith img { width: 100%; height: 100%; object-fit: cover; display: block; }
.wsr-ith.noimg img { visibility: hidden; }
.wsr-play {
  position: absolute; top: 3px; right: 3px; width: 16px; height: 16px;
  border-radius: var(--r-circle); background: var(--k-55); color: var(--white);
  font-size: 7px; display: grid; place-items: center;
}
.wsr-itx { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 2px; justify-content: center; }
.wsr-inm {
  font-family: var(--font-display); font-weight: 600; font-size: 13px; line-height: 1.15;
  color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.wsr-isub {
  font-size: 11px; line-height: 1.15; color: var(--text-faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.wsr-isub.warn { color: var(--warn-90); }
.wsr-isub.upd { color: var(--accent-85); }
.wsr-isub.pend { color: var(--pend-90); }
.wsr-irt { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.wsr-ir { font-size: 10px; color: var(--text-faint); font-variant-numeric: tabular-nums; }

/* ---- the canvas mode switch ---- */
.cv-modes { display: none; }
.canvas-cal { display: none; }

@media (min-width: 1200px) {
  .wsb-bar {
    display: flex; align-items: center; gap: 10px;
    height: var(--bar-h); padding: 0 14px 0 8px;
    background: var(--bg);
    /* no fence. The line appears only once the canvas beneath has scrolled,
       so it shows up when it means something. */
    box-shadow: none; transition: box-shadow 200ms ease;
    position: relative; z-index: 40;
  }
  .wsr {
    display: grid; grid-template-rows: auto minmax(0, 1fr); min-height: 0;
    background: var(--bg); padding-left: 16px;   /* a gutter, not a border */
  }
  .cv-modes {
    display: flex; align-items: center; gap: 14px; padding: 10px 28px 10px 24px;
    flex: none;   /* a floating pill group on black, not a bar with a line */
  }
  .cv-modes .cv-modeset {
    display: inline-flex; gap: 3px; padding: 3px; border-radius: var(--r-11);
    background: var(--lift-1); box-shadow: var(--edge);
  }
  .cv-modes [data-mode] {
    border: 0; background: none; padding: 5px 13px; border-radius: var(--r-7);
    font-family: var(--font-display); font-weight: 600; font-size: 12px;
    color: var(--text-faint); cursor: pointer;
    transition: background-color 120ms ease, color 120ms ease;
  }
  .cv-modes [data-mode]:hover:not(.on) { color: var(--text-dim); }
  .cv-modes [data-mode].on { background: var(--lift-2); box-shadow: var(--edge); color: var(--text); }
  .cv-modes [data-mode]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  .cv-meta { margin-left: auto; font-size: 12px; color: var(--text-faint); }
  .canvas-cal { display: block; overflow-y: auto; padding: 24px 20px 40px; flex: 1; min-height: 0; }
  .canvas-cal[hidden] { display: none; }
  /* inside the canvas the fold is not a fold */
  .canvas-cal .gcal { border: 0; background: none; padding: 0; max-width: 1020px; margin: 0 auto; }
  .canvas-cal .gcal > summary { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .ctile .ct-frame, .ctile .ct-frame::after, .cal5-chip::after, .dsx .card,
  .segf, .wsb-bar, .slideover { transition: none; animation: none; }
  .wsb-btn:active, .wsb-ibtn:active, .wsr-card:active,
  .wsr-item:active, #wsrJump:active { transform: none; }
  .wsb-menu, .wsr-sortmenu { transition: opacity 120ms ease, visibility 120ms linear; }
}

/* ---- ONE BUTTON LANGUAGE (rail v4, 2026-08-20) --------------------------
   Seen beside the rebuilt rail, the item column read as four different
   projects: a cyan-outlined pill, a filled cyan button, a teal one, and a
   dark one inside a dashed box, at three radii and two weights. These rules
   give every operator control the same shape the bar and the rail use.
   Deliberately NOT under body.suite-on: one language on both renderings. */
.btn-primary, .row-detail .btn-primary {
  font-family: var(--font-display); font-weight: 600; font-size: 12.5px;
  height: 32px; padding: 0 14px; border-radius: var(--r-chip);
  display: inline-flex; align-items: center;
  background: var(--accent) !important; color: var(--accent-ink-3) !important;
  border: 1px solid var(--accent) !important; cursor: pointer;
  transition: background-color 120ms ease, transform 160ms var(--ease-out);
}
.btn-primary:hover:not(:disabled) { background: var(--accent-hi-2) !important; border-color: var(--accent-hi-2) !important; }
.btn-primary:active:not(:disabled) { transform: scale(0.97); }
.btn-primary:disabled { opacity: 0.42; cursor: default; }
.tcopy-wide, .file-btn {
  font-family: var(--font-display); font-weight: 600; font-size: 12.5px;
  height: 32px; padding: 0 12px; border-radius: var(--r-chip);
  display: inline-flex; align-items: center;
  background: var(--surface-2); border: 1px solid var(--line); color: var(--text);
  transition: background-color 120ms ease, border-color 120ms ease, transform 160ms var(--ease-out);
}
.tcopy-wide:hover, .file-btn:hover { background: var(--line); border-color: var(--line-strong); color: var(--text); }
.tcopy-wide:active, .file-btn:active { transform: scale(0.97); }
.file-pick { border-radius: var(--r-chip); padding: 4px 10px 4px 4px; }
.btn-primary:focus-visible, .tcopy-wide:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}

@media (min-width: 1200px) {
  /* the canvas head speaks the same language as the bar */
  body.suite-on .cv-back {
    font-family: var(--font-display); font-weight: 600; font-size: 12.5px;
    height: 32px; padding: 0 12px; border-radius: var(--r-chip);
    background: var(--surface-2); border: 1px solid var(--line); color: var(--text);
  }
  body.suite-on .cv-back:hover { background: var(--line); border-color: var(--line-strong); color: var(--text); }
  body.suite-on .cv-nav button {
    width: 32px; height: 32px; border-radius: var(--r-chip);
    background: none; border: 0; color: var(--text-dim);
  }
  body.suite-on .cv-nav button:hover { background: var(--w-07); color: var(--text); }
  /* item mode already carries its own "back to grid": the mode switch would
     be a second, contradicting way to say the same thing */
  body.suite-on.item-mode .cv-modes { display: none; }
}

/* ---- THE CONTINUOUS CALENDAR (Dmitry, 2026-08-20) ------------------------
   "I want the option to scroll down to the next month. This is the biggest
   problem, to not see what was before and what is next without clicking here
   and there."
   So it is not a month view with arrows: it is every month this client has, in
   one scroll, opening on the current one. Month names stick while their own
   weeks pass, so you always know where you are. */
@media (min-width: 1200px) {
  .canvas-cal { display: block; overflow-y: auto; padding: 0 28px; flex: 1; min-height: 0; }
  .canvas-cal[hidden] { display: none; }
  .cal5 { width: 100%; max-width: 1280px; margin: 0 auto;
          display: flex; flex-direction: column; gap: 34px; padding: 0 0 48px; }
  .cal5-top {
    position: sticky; top: 0; z-index: 8; display: flex; align-items: center;
    gap: 12px; padding: 10px 2px 12px; background: var(--bg);
  }
  .cal5-src { font-size: 12px; color: var(--text-faint); }
  .cal5-src b { color: var(--text-dim); font-weight: 500; }
  /* orange means change-requested in the four-signal system. Only a real
     failure to reach Gain earns it; "nothing matched" is information. */
  .cal5-src.stale { color: var(--warn); }
  .cal5-src.note { color: var(--text-dim); }
  /* The calendar is already on screen while Gain is still being asked. This
     says so and then goes away - quietly, because it resolves in about a
     second and nothing is wrong. No pulse: a moving thing on a page that just
     opened reads as a problem. */
  .cal5-wait { color: var(--text-faint); }
  .cal5-wait::before {
    content: ""; display: inline-block; width: 5px; height: 5px;
    border-radius: var(--r-circle); background: var(--text-faint);
    margin-right: 6px; vertical-align: 1px;
  }
  .cal5-jump {
    display: inline-flex; gap: 3px; padding: 3px; border-radius: var(--r-10);
    background: var(--lift-1); box-shadow: var(--edge); margin-left: 14px;
  }
  .cal5-jump button {
    padding: 5px 11px; border-radius: var(--r-7); border: 0; background: none; cursor: pointer;
    font-family: var(--font-display); font-weight: 600; font-size: 11.5px; color: var(--text-faint);
    transition: background-color 120ms ease, color 120ms ease;
  }
  .cal5-jump button:hover { color: var(--text); background: var(--lift-2); }
  .cal5-jump button.now { color: var(--accent); }
  .cal5-link { margin-left: auto; font-size: 12px; color: var(--accent); text-decoration: none; }

  /* --cal-top-h is MEASURED from .cal5-top after every render, because that bar is not a
     fixed height: at 1280 the source line wraps and it grows from 53px to 68px. Pinning the
     month header to a hard-coded 46px put it UNDER the bar at every width, and pushed the
     calendar-month label under itself - the overlapping-elements rejection, again. */
  .cal5-month { display: flex; flex-direction: column; gap: 9px;
                scroll-margin-top: var(--cal-top-h, 46px); }
  .cal5-mh {
    position: sticky; top: var(--cal-top-h, 46px); z-index: 5;
    display: flex; align-items: baseline; gap: 11px;
    padding: 10px 2px 11px;
    background: linear-gradient(var(--bg) 62%, var(--k-00));
  }
  .cal5-mh h3 { font-family: var(--font-display); font-size: 22px; font-weight: 700; letter-spacing: -0.01em; }
  .cal5-mh .ord { font-size: 12px; color: var(--text-faint); }
  .cal5-mh .cnt { margin-left: auto; font-size: 12px; color: var(--text-faint); }
  .cal5-mh.now h3 { color: var(--accent); }

  .cal5-dow { display: grid; grid-template-columns: repeat(7, 1fr); gap: 10px; margin-bottom: 2px; }
  .cal5-dow span {
    font-family: var(--font-display); font-size: 10px; font-weight: 600;
    letter-spacing: 0.09em; text-transform: uppercase; color: var(--text-faint); padding-left: 4px;
  }
  .cal5-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 10px; grid-auto-rows: 104px; }
  .cal5-day {
    border-radius: var(--r-14); background: var(--lift-1); box-shadow: var(--edge);
    padding: 9px 10px 10px; display: flex; flex-direction: column; gap: 6px;
    transition: background-color 130ms ease; min-width: 0;
  }
  .cal5-day:hover { background: var(--lift-2); }
  .cal5-day.pad { background: none; box-shadow: none; }
  .cal5-day.pad .dn { opacity: 0.28; }
  .cal5-day.today { box-shadow: inset 0 0 0 1.5px var(--accent-55); }
  .cal5-day .dn {
    font-family: var(--font-display); font-size: 12px; font-weight: 600;
    color: var(--text-faint); font-variant-numeric: tabular-nums;
  }
  .cal5-day.today .dn { color: var(--accent); }
  .cal5-chips { display: flex; flex-wrap: wrap; gap: 6px; min-width: 0; }
  /* the real artwork at cell scale, wearing its state */
  .cal5-chip {
    position: relative; width: 46px; height: 58px; border-radius: var(--r-chip); overflow: hidden;
    background: var(--bg); box-shadow: var(--edge); border: 0; padding: 0; cursor: pointer;
  }
  /* same overlay device as .ct-frame: hundreds of chips, no repaint on hover */
  .cal5-chip::after {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    background: var(--w-16); opacity: 0; transition: opacity 130ms ease;
  }
  .cal5-chip:hover::after { opacity: 1; }
  .cal5-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  .cal5-chip img { width: 100%; height: 100%; object-fit: cover; display: block; }
  /* a missing derivative shows the piece's number on its ground, never the
     browser's broken-image glyph */
  .cal5-chip.noimg img { visibility: hidden; }
  .cal5-chip.noimg::after {
    content: attr(data-n); position: absolute; inset: 0; display: grid; place-items: center;
    font-family: var(--font-display); font-size: 11px; font-weight: 700; color: var(--text-faint);
  }
  .cal5-chip .st { position: absolute; left: 0; right: 0; bottom: 0; height: 4px; }
  .cal5-chip .st.planned { background: var(--w-42); }
  /* DRAFT is achromatic on purpose (ruled 2026-08-22). The bar speaks in opacity:
     translucent means intention, opaque means Gain holds a record. COLOUR on this bar is a
     publish claim - cyan queued, green out - and a draft makes none, so it must not borrow
     --pend or --warn, which already mean "the client has not decided" and "revision
     requested" elsewhere on this page. At 4px, luminance separates better than hue. */
  .cal5-chip .st.draft { background: var(--text); }
  .cal5-chip .st.sched { background: var(--accent); }
  .cal5-chip .st.live { background: var(--ok); }
  /* A piece Gain has that we do not: a story, which is delivered work with no database row
     and so nothing to open. Narrower because the artwork IS 9:16, which is what says
     "story" without a word. */
  .cal5-chip.gonly { width: 33px; cursor: default; }
  .cal5-chip.gonly:hover { filter: none; }
  .cal5-chip .rl {
    position: absolute; top: 3px; right: 3px; width: 12px; height: 12px; border-radius: var(--r-circle);
    background: var(--k-55); color: var(--white); font-size: 6px; display: grid; place-items: center;
  }
  .cal5-legend {
    display: flex; gap: 18px; align-items: center; margin-top: 6px;
    font-size: 11.5px; color: var(--text-dim);
  }
  .cal5-legend i { display: inline-block; width: 16px; height: 3px; border-radius: var(--r-pill); margin-right: 7px; vertical-align: middle; }
  .cal5-legend .hint { margin-left: auto; color: var(--text-faint); }
  .cal5-note { font-size: 12px; color: var(--text-faint); line-height: 1.5; }
  .cal5-empty {
    border-radius: var(--r-14); background: var(--lift-1); box-shadow: var(--edge);
    padding: 18px 20px; font-size: 12.5px; color: var(--text-dim); line-height: 1.6;
  }
  .cal5-empty .wsb-btn { margin-top: 12px; }
}

@media (min-width: 1200px) {
  /* the bar's line, earned by scrolling rather than drawn by default */
  .wsb-bar.lifted { box-shadow: 0 12px 28px -18px var(--bg); }
}

@media (min-width: 1200px) {
  /* a planned window almost always crosses a month boundary; the spilled part
     gets its own labelled block instead of being silently dropped */
  .cal5-sub {
    font-family: var(--font-display); font-size: 13px; font-weight: 600;
    color: var(--text-dim); padding: 14px 2px 2px;
  }
}

/* The reveal gate that used to live here is DELETED (Dmitry, 2026-08-20:
   "Remove the load black screen reveal transition, here it need to load
   instantly"). It held the hero at opacity:0 until onload, which meant the
   stage stayed black for the WHOLE download - and soOpen had already painted
   the row's thumb into that same <img>, so the gate was hiding a placeholder
   that worked. No opacity rule belongs on this picture. */

/* ── Operator clearance (2026-08-20) ─────────────────────────────────────
   Dmitry: a small change should not need the client to approve the whole piece
   again, but it does block the Gain push. The clearance chip is NEUTRAL on
   purpose - green means the client approved it, and a piece we cleared must
   never be able to read as one they signed off. */
.cnt-cleared {
  background: var(--ink-05); color: var(--text-dim);
  border: 1px solid var(--line-strong);
}
.cnt-cleared::before {
  content: "\2713"; width: auto; height: auto; border-radius: var(--r-none);
  background: none; font-size: 10px; line-height: 1;
}
.clr {
  border: 1px solid var(--line); border-radius: var(--r-card); padding: 13px 14px;
  background: var(--w-02);
}
.clr-line { display: flex; align-items: center; gap: 10px; }
.clr-fact { font-size: 13px; color: var(--text-dim); }
.clr-line form { margin-left: auto; display: flex; }
.clr-act { display: flex; align-items: center; gap: 12px; margin-top: 11px; }
.clr-meta { font-size: 12px; color: var(--text-faint); margin-top: 7px; }
.clr.is-done { border-color: var(--line-strong); background: var(--w-035); }
.note-field { margin-top: 10px; }
.note-field input {
  width: 100%; background: var(--w-05); border: 1px solid var(--line);
  border-radius: var(--r-9); padding: 9px 11px; color: var(--text);
  font-family: var(--font-body); font-size: 13px;
}
.note-field input::placeholder { color: var(--ink-26); }
.note-field input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.btn-text { cursor: pointer; transition: color 160ms ease; }
.btn-text:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Sends-as: the consequence of saving is a CHOICE, so it is a switch. It was
   one fixed sentence that was only true half the time. */
.send-as { display: flex; align-items: center; gap: 11px; flex-wrap: wrap; margin: 12px 0 11px; }
.sa-lab { font-size: 12.5px; color: var(--text-faint); }
.sa-note { font-size: 12.5px; color: var(--text-dim); }
.seg {
  display: inline-flex; background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--r-9); padding: 2px; gap: 2px;
}
.seg-b { cursor: pointer; transition: color 160ms ease, background 160ms ease; }
.seg-b:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* note left, action right: the primary button is anchored to its own block and
   never floats under it (Dmitry, 2026-08-20: "save caption is very weird
   positioned"). Both edit blocks share this anatomy so they read as one system. */
.form-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; flex-wrap: wrap;
}
.form-foot .sa-note { flex: 1; min-width: 0; }

/* a last line of one or two words is the defect that got the first round
   rejected; pretty forbids the orphan, balance evens out the short blocks */
.clr-fact, .clr-meta, .sa-note, .g-t, .g-names { text-wrap: balance; }

/* Gain modal: a title line and a names line, then the action. It used to name
   the blockers inside one long sentence and then stop - a dead end. */
/* NOT .gain-warn: the modal still emits bare <p class="gain-warn"> for story and
   channel warnings, and redefining that class stripped their colour and spacing.
   The blocker card carries its own class instead of borrowing theirs. */
.gain-block {
  background: var(--pend-09); border: 1px solid var(--pend-26);
  border-radius: var(--r-10); padding: 12px 13px; margin-bottom: 12px;
}
.gain-cleared {
  background: var(--w-03); border: 1px solid var(--line-strong);
  border-radius: var(--r-10); padding: 12px 13px; margin-bottom: 12px;
}
.g-t { font-size: 13px; font-weight: 600; color: var(--pend); line-height: 1.5; }
.gain-cleared .g-t { color: var(--text); }
.g-names { font-size: 12.5px; color: var(--text-dim); margin-top: 2px; }
.g-act { margin-top: 10px; }
.g-act .btn-ghost {
  font-family: var(--font-body); font-weight: 600; font-size: 13px;
  background: transparent; border: 1px solid var(--line-strong); color: var(--text);
  border-radius: var(--r-10); padding: 8px 14px; cursor: pointer;
  transition: transform 160ms cubic-bezier(0.23, 1, 0.32, 1),
              border-color 160ms ease, background 160ms ease;
}
.g-act .btn-ghost:hover:not(:disabled) {
  border-color: var(--w-30); background: var(--panel);
}
.g-act .btn-ghost:active:not(:disabled) { transform: scale(0.97); }
.g-act .btn-ghost:disabled { opacity: 0.42; cursor: default; }
.clr .btn-ghost {
  font-family: var(--font-body); font-weight: 600; font-size: 13.5px;
  background: transparent; border: 1px solid var(--line-strong); color: var(--text);
  border-radius: var(--r-10); padding: 9px 15px; cursor: pointer;
  transition: transform 160ms cubic-bezier(0.23, 1, 0.32, 1),
              border-color 160ms ease, background 160ms ease;
}
.clr .btn-ghost:hover { border-color: var(--w-30); background: var(--panel); }
.clr .btn-ghost:active { transform: scale(0.97); }

/* Specificity, not novelty: `.row-detail button` (0,1,1) already paints every
   button inside a piece's panel as a cyan pill, so a bare `.seg-b` (0,1,0) lost
   and BOTH halves of the switch rendered as active. These selectors carry two
   classes so they win inside the panel and stay inert everywhere else. */
.seg .seg-b {
  font-family: var(--font-body); font-size: 12.5px; font-weight: 600;
  color: var(--text-faint); background: transparent; border: 1px solid transparent;
  border-radius: var(--r-7); padding: 5px 11px; white-space: nowrap;
}
.seg .seg-b:hover { color: var(--text); background: var(--w-05); }
.seg .seg-b.on { background: var(--w-10); color: var(--text); }
.seg .seg-b.on.warm { background: var(--accent-16); color: var(--accent); }
.clr .btn-text, .clr-line .btn-text {
  background: none; border: none; color: var(--text-faint); font-size: 12.5px;
  font-family: var(--font-body); font-weight: 500; padding: 0; border-radius: var(--r-none);
}
.clr .btn-text:hover, .clr-line .btn-text:hover { color: var(--text); background: none; }

/* .up-form is display:flex/wrap, so anything dropped into it becomes a flex item
   on the SAME row - which is how the file picker, the switch and the upload
   button ended up crammed into one line. Each of my blocks claims a full row. */
.up-form > .file-pick, .up-form > .send-as, .up-form > .form-foot { flex: 0 0 100%; }
/* ═══════════════════════════════════════════════════════════════════════════
   OPERATOR INDEX v1 (Dmitry, 2026-08-22) - "more canvas, more per line, more
   air, and fix the AI-slop". Everything below is scoped to .dsx, which sits
   on the index shell only, so login and the month report keep the 1080 cap
   and their own rules. The superseded index block was DELETED, not overridden.

   NOTE FOR THE NEXT EDITOR: this block sits physically AFTER the
   "RAIL v4 (Dmitry, 2026-08-20)" marker, and tests/test_layout_contract.py
   finds its colour-guard scan window by searching for that marker and reading
   to end of file. So a raw hex added HERE fails with a message that says
   "rail v4". Everything below uses tokens and token-derived rgba() only.

   Measured before: 840px of empty gutter at 1920, a grid frozen at
   332px x 3 at every width, 8 button shapes, 10 font sizes, 9px and 10px
   radii, and a rail whose bands sat flush because .rail lost to a
   display:contents rule written for a different page.
   ═══════════════════════════════════════════════════════════════════════════ */
.dsx { max-width:1760px;margin:0 auto;padding:40px 48px 96px }
@media (max-width:1100px) {
  .dsx { padding:32px 32px 80px }
}
@media (max-width:640px) {
  .dsx { padding:24px 16px 64px }
}
.dsx .dash-head { display:flex;flex-direction:column;gap:16px;margin-bottom:32px }
.dsx .side-brand { display:flex;align-items:center;gap:10px;
  font-family:var(--font-display);font-size:12px;font-weight:700;letter-spacing:0.18em; }
.dsx .brand-dot { width:10px;height:10px;border-radius:var(--r-circle);background:var(--grad) }
.dsx .head-line { display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap }
.dsx .head-line h1 { font-family:var(--font-display);font-size:28px;font-weight:800;letter-spacing:-0.02em }
.dsx .head-tools { display:flex;align-items:center;gap:12px;flex-wrap:wrap }
@media (max-width:640px) {
  .dsx .head-tools { width:100%;row-gap:8px }
}
.dsx .btn { font-family:var(--font-display);font-weight:600;font-size:13px;
  height:32px;padding:0 14px;border-radius:var(--r-chip);white-space:nowrap;
  display:inline-flex;align-items:center;gap:7px;cursor:pointer;text-decoration:none;
  background:var(--lift-1);box-shadow:var(--edge);color:var(--text);
  transition:background-color 120ms ease,color 120ms ease,transform 160ms var(--ease-out); }
.dsx .btn:hover { background:var(--lift-2) }
.dsx .btn:active { transform:scale(0.97) }
.dsx .btn[disabled] { opacity:0.42;cursor:default }
.dsx .btn[disabled]:hover { background:var(--lift-1) }
.dsx .btn[disabled]:active { transform:none }
.dsx .btn.primary { background:var(--accent);box-shadow:none;color:var(--accent-ink-3) }
.dsx .btn.primary:hover { background:var(--accent-hi-2) }
.dsx .ibtn { position:relative;width:32px;height:32px;border-radius:var(--r-chip);flex:none;
  display:grid;place-items:center;color:var(--text-dim);cursor:pointer;
  transition:background-color 120ms ease,color 120ms ease,transform 160ms var(--ease-out); }
.dsx .ibtn:hover { background:var(--lift-2);color:var(--text) }
.dsx .ibtn:active { transform:scale(0.94) }
.dsx .btn:focus-visible, .dsx .ibtn:focus-visible { outline:2px solid var(--accent);outline-offset:2px }
.dsx .split { display:inline-flex;align-items:stretch;border-radius:var(--r-chip);background:var(--lift-1);box-shadow:var(--edge) }
.dsx .split .btn { background:none;box-shadow:none;border-radius:var(--r-chip) var(--r-none) var(--r-none) var(--r-chip)}
.dsx .split .seam { width:1px;background:var(--line);flex:none;margin:6px 0 }
.dsx .split .more { width:26px;padding:0;border-radius:var(--r-none) var(--r-chip) var(--r-chip) var(--r-none);display:grid;place-items:center;
  color:var(--text-faint);cursor:pointer;
  transition:background-color 120ms ease,color 120ms ease; }
.dsx .split .btn:hover, .dsx .split .more:hover { background:var(--lift-2) }
.dsx .split .more:hover { color:var(--text) }
.dsx .split .more:focus-visible { outline:2px solid var(--accent);outline-offset:-2px }
.dsx .split .more svg { display:block }
.dsx .ov-toggle { display:inline-flex;gap:2px;padding:2px;height:32px;border-radius:var(--r-chip);
  background:var(--lift-1);box-shadow:var(--edge); }
.dsx .ov-toggle button { font-family:var(--font-display);font-weight:600;font-size:13px;
  padding:0 13px;border-radius:var(--r-hair);color:var(--text-faint);cursor:pointer;
  transition:background-color 120ms ease,color 120ms ease; }
.dsx .ov-toggle button:hover:not(.on) { color:var(--text-dim) }
.dsx .ov-toggle button.on { background:var(--lift-2);box-shadow:var(--edge);color:var(--text) }
.dsx .ov-toggle button:focus-visible { outline:2px solid var(--accent);outline-offset:1px }
.dsx .bell-wrap { position:relative }
.dsx .inflight { margin-bottom:32px;padding:20px 24px;border-radius:var(--r-panel);
  background:var(--lift-1);box-shadow:var(--edge),var(--drop-s); }
.dsx .if-head { font-family:var(--font-display);font-size:11px;font-weight:600;
  letter-spacing:0.12em;text-transform:uppercase;color:var(--text-faint);
  margin-bottom:14px; }
.dsx .if-grid { display:grid;grid-template-columns:1fr 1fr;gap:24px }
.dsx .if-side { min-width:0 }
.dsx .if-side + .if-side { border-left:1px solid var(--line);padding-left:24px }
@media (max-width:760px) {
  .dsx .if-grid { grid-template-columns:1fr;gap:16px }
  .dsx .if-side + .if-side { border-left:0;padding-left:0;border-top:1px solid var(--line);padding-top:16px }
}
.dsx .if-label { display:flex;align-items:center;gap:8px;
  font-family:var(--font-display);font-size:10px;font-weight:600;
  letter-spacing:0.1em;text-transform:uppercase;color:var(--text-faint);
  margin-bottom:8px; }
.dsx .if-none { font-size:13px;color:var(--text-faint) }
.dsx .if-item { display:flex;flex-direction:column;gap:3px;align-items:flex-start;
  padding:8px 12px;margin:-8px -12px;border-radius:var(--r-card);cursor:pointer;text-align:left;
  transition:background-color 120ms ease,transform 160ms var(--ease-out); }
.dsx .if-item:hover { background:var(--lift-2) }
.dsx .if-item:active { transform:scale(0.99) }
.dsx .if-item:focus-visible { outline:2px solid var(--accent);outline-offset:2px }
.dsx .if-who { display:flex;align-items:center;gap:9px;
  font-family:var(--font-display);font-size:15px;font-weight:700;color:var(--text); }
.dsx .if-who .dot { margin-top:1px }
.dsx .if-mo { font-family:var(--font-display);font-size:13px;font-weight:600;color:var(--text-dim) }
.dsx .if-what { font-size:12px;color:var(--text-dim);padding-left:15px }
/* Undelivered decisions (2026-09-02). Deliberately the in-flight band's own
   anatomy - same panel, same head, same who/what pair, same warn dot - because
   it answers the same question one step further back: not "whose turn is it"
   but "did the turn ever arrive". A second visual language here would read as
   a second product. No new token and no colour literal: the reason line is the
   existing --err, the panel is --lift-1, the rule is --line. */
.dsx .dprob { margin-bottom:32px;padding:20px 24px;border-radius:var(--r-panel);
  background:var(--lift-1);box-shadow:var(--edge),var(--drop-s); }
.dsx .dp-head { display:flex;align-items:baseline;gap:10px;
  font-family:var(--font-display);font-size:11px;font-weight:600;
  letter-spacing:0.12em;text-transform:uppercase;color:var(--text-faint);
  margin-bottom:14px; }
.dsx .dp-window { letter-spacing:0.06em;text-transform:none;font-weight:500 }
.dsx .dp-list { display:flex;flex-direction:column;gap:12px }
.dsx .dp-item { display:flex;flex-direction:column;gap:3px;min-width:0 }
.dsx .dp-item + .dp-item { border-top:1px solid var(--line);padding-top:12px }
.dsx .dp-who { display:flex;align-items:center;gap:9px;
  font-family:var(--font-display);font-size:15px;font-weight:700;color:var(--text); }
.dsx .dp-who .dot { margin-top:1px }
.dsx .dp-mo { font-family:var(--font-display);font-size:13px;font-weight:600;color:var(--text-dim) }
.dsx .dp-what { font-size:12px;color:var(--text-dim);padding-left:15px }
.dsx .dp-state { font-family:var(--font-display);font-size:11px;font-weight:600;
  letter-spacing:0.08em;text-transform:uppercase;color:var(--text-faint) }
/* the reason is the whole point of the block, so it is the line that is
   allowed to be long: it wraps rather than truncating, and keeps its own
   words (a last_error is a machine's sentence and half of one is useless) */
.dsx .dp-why { font-size:12px;color:var(--err);padding-left:15px;
  overflow-wrap:anywhere; }
.dsx .dot { width:6px;height:6px;border-radius:var(--r-circle);display:inline-block;flex:none }
.dsx .dot.ok { background:var(--ok) }
.dsx .dot.pend { background:var(--pend) }
.dsx .dot.warn { background:var(--warn) }
.dsx .chip { font-family:var(--font-display);font-size:10px;font-weight:600;
  letter-spacing:0.08em;text-transform:uppercase;white-space:nowrap;
  padding:4px 9px;border-radius:var(--r-pill);flex:none; }
.dsx .ch-review { background:var(--accent-10);color:var(--accent) }
.dsx .ch-needs { background:var(--warn-12);color:var(--warn) }
.dsx .ch-approved { background:var(--ok-12);color:var(--ok) }
.dsx .ch-pushed { background:var(--accent-10);color:var(--accent) }
.dsx .ch-done { background:var(--w-05);color:var(--text-dim) }
.dsx .bar { display:flex;gap:2px;height:6px;border-radius:var(--r-pill)}
.dsx .bar i { border-radius:var(--r-pill);min-width:6px;display:block }
.dsx .bar .s-ok { background:var(--ok) }
.dsx .bar .s-pend { background:var(--pend) }
.dsx .bar .s-warn { background:var(--warn) }
.dsx .client-grid { display:grid;grid-template-columns:repeat(auto-fit,minmax(min(360px,100%),1fr));
  gap:24px; }
.dsx .client-grid[hidden] { display:none }
.dsx .card { display:flex;flex-direction:column;gap:16px;
  padding:24px;border-radius:var(--r-panel);cursor:pointer;
  background:var(--lift-1);box-shadow:var(--edge),var(--drop-s);
  transition:background-color 140ms ease,box-shadow 180ms ease; }
.dsx .card:hover { background:var(--lift-2);box-shadow:var(--edge),var(--drop-m) }
.dsx .card:focus-visible { outline:2px solid var(--accent);outline-offset:3px }
.dsx .c-head { display:flex;align-items:flex-start;gap:16px;min-width:0;min-height:68px }
.dsx .logo { width:48px;height:48px;border-radius:var(--r-card);flex:none;
  display:grid;place-items:center;overflow:hidden;
  background:var(--w-05);box-shadow:var(--edge);
  font-family:var(--font-display);font-weight:800;font-size:15px;letter-spacing:0.03em; }
.dsx .c-id { min-width:0;flex:1 }
.dsx .c-id h2 { font-family:var(--font-display);font-size:18px;font-weight:700;
  line-height:1.25;letter-spacing:-0.01em;color:var(--white);text-wrap:balance; }
.dsx .c-service { font-family:var(--font-display);font-size:11px;font-weight:600;
  letter-spacing:0.12em;text-transform:uppercase;color:var(--text-faint);margin-top:4px; }
.dsx .c-rating { flex:none;font-family:var(--font-display);font-size:12px;font-weight:700;
  color:var(--pend);white-space:nowrap;padding-top:2px;cursor:default; }
.dsx .months { display:flex;flex-direction:column;gap:12px }
.dsx .month { display:flex;flex-direction:column;gap:10px;
  padding:14px 16px;border-radius:var(--r-card);
  background:var(--w-02);box-shadow:inset 0 0 0 1px var(--line); }
.dsx .m-line { display:flex;align-items:baseline;gap:10px;flex-wrap:wrap }
.dsx .m-ord { font-family:var(--font-display);font-size:15px;font-weight:700;color:var(--text) }
.dsx .m-label { font-size:13px;color:var(--text-dim) }
.dsx .m-line .chip { margin-left:auto;align-self:center }
.dsx .m-comp { font-size:11px;color:var(--text-faint) }
.dsx .m-read { display:flex;align-items:center;gap:12px;flex-wrap:wrap }
.dsx .m-count { font-size:13px;color:var(--text-dim) }
.dsx .m-count b { font-family:var(--font-display);font-weight:600;font-size:13px;color:var(--text);font-variant-numeric:tabular-nums }
.dsx .m-owner { display:inline-flex;align-items:center;gap:7px;font-size:12px;color:var(--text-dim) }
.dsx .m-read .btn { margin-left:auto }
/* The announced month is drawn with an OUTLINE, not a border. Every other
   month block wears an inset box-shadow, which costs no layout; a real 1px
   border does, and it pushed the announced card's month row 1px below its
   neighbours' - 147 against 146, on a row the reserve above exists to keep
   scannable. Same dashes, same pixel, no box. */
.dsx .month.m-soon { box-shadow:none;outline:1px dashed var(--line-strong);
  outline-offset:-1px;background:none }
.dsx .c-foot { margin-top:auto;padding-top:16px;border-top:1px solid var(--line);
  display:flex;flex-direction:column;gap:12px; }
.dsx .activity { font-size:12px;color:var(--text-faint) }
.dsx .activity b { color:var(--text-dim);font-weight:600 }
.dsx .c-actions { display:flex;align-items:center;gap:8px;flex-wrap:wrap }
.dsx .c-actions .sp { flex:1 }
/* the primary action is the row's RIGHT anchor at every width. `.sp` fills
   the rest of line one, so when the row wraps on a phone `Open` used to
   land alone at the LEFT of line two - the same control in two places. */
.dsx .c-actions .c-open { margin-left: auto; }
/* `.ov-rail`, not `.rail`: the workspace sidebar owns `.rail` and
   dash.css:1055 sets it to display:contents below the desk threshold. The
   index section was renamed to escape that and these rules were left behind,
   so the Rail view lost its ground while every per-row rule kept working. */
.dsx .ov-rail { border-radius:var(--r-panel);background:var(--lift-1);box-shadow:var(--edge),var(--drop-s);overflow:hidden;
  /* the rail's six tracks, declared ONCE. The header row and every data row
     read this; nothing else may restate it. The action track is pinned so a
     row with fewer buttons cannot widen the tracks before it (that defect is
     recorded at "THE RAIL IS A TABLE" below), and it is pinned to ONE width at
     every desk size so "Open" cannot wrap onto a second line at 1440. */
  --rail-tracks: minmax(200px,1.4fr) minmax(140px,1fr) 118px minmax(170px,1.2fr) minmax(84px,0.6fr) 400px; }
@media (max-width:1440px) {
  .dsx .ov-rail { --rail-tracks: minmax(150px,1.2fr) minmax(120px,1fr) 110px minmax(140px,1fr) minmax(76px,0.5fr) 400px }
}
.dsx .ov-rail[hidden] { display:none }
.dsx .rail-cols { display:grid;grid-template-columns:var(--rail-tracks);
  gap:16px;align-items:center;padding:12px 24px;
  font-family:var(--font-display);font-size:10px;font-weight:600;
  letter-spacing:0.1em;text-transform:uppercase;color:var(--text-faint);
  border-bottom:1px solid var(--line); }
.dsx .rail-cols > :last-child { text-align:right }
.dsx .rrow { display:grid;grid-template-columns:var(--rail-tracks);
  gap:16px;align-items:center;padding:14px 24px;cursor:pointer;
  transition:background-color 120ms ease; }
.dsx .rrow + .rrow { border-top:1px solid var(--line) }
.dsx .rrow:hover { background:var(--lift-2) }
.dsx .rrow:focus-visible { outline:2px solid var(--accent);outline-offset:-2px }
.dsx .r-id { display:flex;align-items:center;gap:12px;min-width:0 }
.dsx .r-id .logo { width:36px;height:36px;border-radius:var(--r-chip);font-size:12px }
.dsx .r-name { font-family:var(--font-display);font-size:15px;font-weight:700;
  line-height:1.3;color:var(--white);text-wrap:balance; }
.dsx .r-rating { font-family:var(--font-display);font-size:11px;font-weight:700;color:var(--pend);margin-top:2px }
.dsx .r-month { display:flex;flex-direction:column;gap:2px;min-width:0 }
.dsx .r-mo { font-family:var(--font-display);font-size:13px;font-weight:600;color:var(--text) }
.dsx .r-mlabel { font-size:12px;color:var(--text-dim) }
.dsx .r-prog { display:flex;flex-direction:column;gap:7px;min-width:0 }
.dsx .r-read { display:flex;align-items:center;gap:10px;flex-wrap:wrap;font-size:12px;color:var(--text-dim) }
.dsx .r-read b { font-family:var(--font-display);font-weight:600;color:var(--text);font-variant-numeric:tabular-nums }
.dsx .r-owner { display:inline-flex;align-items:center;gap:6px }
.dsx .r-act { font-size:12px;color:var(--text-faint) }
.dsx .r-act b { color:var(--text-dim);font-weight:600 }
.dsx .r-actions { display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end }
.dsx .rrow-soon { cursor:default }
.dsx .rrow-soon .r-month { opacity:0.9 }
@media (max-width:1200px) {
  .dsx .rail-cols { display:none }
  .dsx .rrow { grid-template-columns:minmax(0,1fr) auto;
    grid-template-areas:"id state" "month month" "prog prog" "act act" "actions actions";
    row-gap:10px;padding:16px; }
  .dsx .rrow .r-id { grid-area:id }
  .dsx .rrow .r-state { grid-area:state;justify-self:end }
  .dsx .rrow .r-month { grid-area:month;flex-direction:row;align-items:baseline;gap:8px }
  .dsx .rrow .r-prog { grid-area:prog }
  .dsx .rrow .r-act { grid-area:act }
  .dsx .rrow .r-actions { grid-area:actions;justify-content:flex-start }
}
.dsx .link-menu { position:absolute;z-index:80;width:248px;padding:6px;
  background:var(--surface-2);border:1px solid var(--line-strong);border-radius:var(--r-card);
  box-shadow:var(--drop-m);
  transform-origin:top right;transform:scale(0.97);opacity:0;visibility:hidden;
  transition:opacity 150ms var(--ease-out),transform 150ms var(--ease-out),visibility 150ms linear; }
.dsx .link-menu.open { transform:scale(1);opacity:1;visibility:visible }
.dsx .link-menu button { display:block;width:100%;text-align:left;cursor:pointer;
  padding:8px 10px;border-radius:var(--r-chip);font-size:13px;color:var(--text);
  transition:background-color 120ms ease; }
.dsx .link-menu button:hover { background:rgba(255,255,255,0.06) }
.dsx .link-menu button:focus-visible { outline:2px solid var(--accent);outline-offset:-2px }
.dsx .lm-note { margin:4px 10px 6px;font-size:11px;line-height:1.5;color:var(--text-faint) }
.dsx .notif-panel { position:absolute;top:40px;right:0;z-index:70;width:min(400px,calc(100vw - 32px));
  background:var(--surface);border:1px solid var(--line-strong);border-radius:var(--r-panel);
  box-shadow:var(--drop-m);overflow:hidden;
  transform-origin:top right;transform:scale(0.97);opacity:0;visibility:hidden;
  transition:opacity 150ms var(--ease-out),transform 150ms var(--ease-out),visibility 150ms linear; }
.dsx .notif-panel.open { transform:scale(1);opacity:1;visibility:visible }
/* ONE narrow-width rule for ONE panel (2026-08-24). This rule used to live
   beside the base `.notif-panel` at the top of the sheet, where `.dsx
   .notif-panel` right above outranked it: the dashboard is the only shell
   carrying `.dsx`, so on a phone its panel stayed pinned to the bell and ran
   to 550px in a 390px viewport with "Mark all read" entirely off-screen,
   while the SAME component on report and workspace obeyed the rule and
   opened at x=16. Moved, not rewritten - and the `.dsx` form is listed here
   so the two implementations of this panel can no longer disagree. */
@media (max-width: 640px) {
  .notif-panel, .dsx .notif-panel {
    position: fixed; top: 64px; left: 16px; right: 16px; width: auto;
  }
}
.dsx .notif-head { display:flex;align-items:center;justify-content:space-between;
  padding:14px 18px;border-bottom:1px solid var(--line); }
.dsx .notif-head h3 { font-family:var(--font-display);font-size:15px;font-weight:700 }
.dsx .notif-read { color:var(--text-dim);font-size:12px;cursor:pointer;transition:color 120ms ease }
.dsx .notif-read:hover { color:var(--text) }
.dsx .notif-read:focus-visible { outline:2px solid var(--accent);outline-offset:2px }
.dsx .notif { display:flex;gap:12px;padding:13px 18px;position:relative;font-size:13px }
.dsx .notif + .notif { border-top:1px solid var(--line) }
.dsx .notif.unread { background:var(--accent-05) }
.dsx .notif.unread::before { content:"";position:absolute;left:0;top:0;bottom:0;width:2px;background:var(--grad) }
.dsx .n-title { font-weight:500;line-height:1.45 }
.dsx .n-title b { font-weight:700 }
.dsx .n-when { margin-top:4px;font-size:11px;color:var(--text-faint) }
.dsx .veil { position:fixed;inset:0;z-index:200;background:var(--k-70);
  display:flex;align-items:center;justify-content:center;padding:20px;
  opacity:0;visibility:hidden;transition:opacity 160ms ease,visibility 160ms linear; }
.dsx .veil.open { opacity:1;visibility:visible }
.dsx .dlg { width:min(440px,100%);max-height:88vh;overflow:auto;
  background:var(--surface);border:1px solid var(--line-strong);border-radius:var(--r-panel);
  padding:24px;box-shadow:var(--drop-m);
  transform:scale(0.97);transition:transform 160ms var(--ease-out); }
.dsx .veil.open .dlg { transform:scale(1) }
.dsx .dlg h2 { font-family:var(--font-display);font-size:18px;font-weight:700;letter-spacing:-0.01em;margin-bottom:8px }
.dsx .dlg-body { font-size:13px;color:var(--text-dim);line-height:1.6 }
.dsx .dlg-note { font-size:12px;line-height:1.6;color:var(--text-faint);margin-top:12px }
.dsx .dlg-foot { display:flex;justify-content:flex-end;gap:8px;margin-top:20px;
  padding-top:16px;border-top:1px solid var(--line); }
.dsx .toast { position:fixed;right:20px;bottom:20px;z-index:300;max-width:min(380px,calc(100vw - 40px));
  padding:12px 16px;border-radius:var(--r-card);font-size:13px;color:var(--text);
  background:var(--surface-2);border:1px solid var(--line-strong);box-shadow:var(--drop-m);
  opacity:0;transform:translateY(6px);visibility:hidden;
  transition:opacity 180ms var(--ease-out),transform 180ms var(--ease-out),visibility 180ms linear; }
.dsx .toast.open { opacity:1;transform:translateY(0);visibility:visible }
@media (prefers-reduced-motion:reduce) {
  .dsx .btn:active, .dsx .ibtn:active, .dsx .if-item:active { transform:none }
  .dsx .link-menu, .dsx .notif-panel, .dsx .veil, .dsx .dlg, .dsx .toast { transition:opacity 120ms ease,visibility 120ms linear }
  .dsx .veil.open .dlg, .dsx .toast.open { transform:none }
}

/* Nothing on this page may render as an operating-system widget. The port
   deliberately dropped the mockup's global resets so they could not reach the
   client deck or the report; these are the same rules, scoped to the index. */
.dsx input, .dsx select, .dsx textarea {
  appearance: none; -webkit-appearance: none;
  font: inherit; color: inherit;
}
.dsx input[type=number] { -moz-appearance: textfield; }
.dsx input::-webkit-outer-spin-button,
.dsx input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* appearance:none alone leaves a field with NO surface - transparent text on
   the dialog. These give the index's fields the same body the buttons have.
   The New-workspace dialog injects its two <select>s from JS, which is why
   the browser suite never reached them: it needs a finished order run on
   disk. Verified instead by rendering the production page through the
   deployed code - it still carried two bare selects until this landed. */
.dsx input[type=text], .dsx input[type=number], .dsx input[type=date],
.dsx select, .dsx textarea {
  width: 100%; height: 32px; padding: 0 12px;
  background: var(--lift-1); box-shadow: var(--edge);
  border: 1px solid var(--line); border-radius: var(--r-chip);
  font-family: var(--font-body); font-size: 13px; color: var(--text);
  transition: border-color 120ms ease;
}
.dsx textarea { height: auto; min-height: 72px; padding: 8px 12px; line-height: 1.5; }
.dsx input:hover, .dsx select:hover, .dsx textarea:hover { border-color: var(--line-strong); }
.dsx input:focus, .dsx select:focus, .dsx textarea:focus {
  outline: 2px solid var(--accent); outline-offset: 1px; border-color: transparent;
}
.dsx input::placeholder, .dsx textarea::placeholder { color: var(--text-faint); }
/* a select with appearance:none loses its arrow, so it gets one back - drawn,
   not borrowed from the operating system */
.dsx select {
  padding-right: 32px; cursor: pointer;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23F4F5F7' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round' opacity='.62'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
}
.dsx select option { background: var(--surface-2); color: var(--text); }

/* the ratified gap scale, applied to what the mockup left off it. The ONE
   documented exception is .btn's horizontal padding of 14px, which is the
   Rail v4 button shape Dmitry rated 15/10 on 2026-08-20 and is kept verbatim
   rather than nudged for a scale it predates. */
.dsx .month { gap: 12px; padding: 16px; }
.dsx .m-line { gap: 12px; }
.dsx .m-owner { gap: 8px; }
.dsx .m-read { gap: 12px; }
.dsx .chip { padding: 4px 8px; }
.dsx .btn, .dsx .more { gap: 8px; }
.dsx .r-read { gap: 12px; }
.dsx .r-owner { gap: 8px; }
.dsx .if-who { gap: 8px; }

/* the bell is the page's one icon button and wears the icon-button shape,
   not a shape of its own (it was 40px tall at radius 10 in the browser's
   default 13.333px type, which is three ways off at once) */
.dsx .bell {
  position: relative; width: 32px; height: 32px; border-radius: var(--r-chip); flex: none;
  display: grid; place-items: center; border: 0; cursor: pointer; font-size: 13px;
  /* the bell wears the SAME surface as every other control in this row. It was
     the only naked one - toggle, Brand Lab and New workspace all sit on
     --lift-1 with --edge, and an icon floating between three lifted controls
     is exactly the "does not belong to this project" reading. */
  background: var(--lift-1); box-shadow: var(--edge);
  color: var(--text-dim);
  transition: background-color 120ms ease, color 120ms ease;
}
.dsx .bell:hover { background: var(--lift-2); color: var(--text); }

/* The one exception to the single badge rule above, and it names the ruling
   it obeys: the dashboard header row is 32px tall and
   `test_the_header_row_is_one_family_and_nothing_escapes_it` refuses anything
   that hangs above it (Dmitry, 2026-08-18: "THE UNREAD BADGE MUST STAY INSIDE
   THE ROW"). Inside the row AND off the glyph is a 12px disc in the button's
   own top-right corner: it covers 7% of the bell where the 14px disc at
   top/right 3px covered 31% and erased the dome. */
.dsx .bell-badge {
  top: 0; right: 0; min-width: 12px; height: 12px; padding: 0 3px;
}

.dsx .more { font-size: 13px; }
.dsx .notif-read { font-size: 12px; }

/* every month is its own door, in both views. The label still owns its own
   line and is never truncated - month names are data (Dmitry, 2026-08-18). */
.dsx .m-line, .dsx .r-month { text-decoration: none; color: inherit; }
.dsx .m-line:hover .m-ord, .dsx .r-month:hover .r-mo { color: var(--accent); }
.dsx .m-line:focus-visible, .dsx .r-month:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-hair);
}

/* ── audit round 1 (2026-08-22) ──────────────────────────────────────────────
   Five defects a green suite could not see, because the assertions measured
   sizes and shapes and never colour, image fit, or a dialog's own box. */

/* 1. THE STATE CHIPS WERE INVISIBLE. The template emits ch-live / ch-closed /
   ch-hidden / ch-soon from the workspace state; the block defined ch-review /
   ch-needs / ch-approved / ch-pushed / ch-done. Nothing matched, so every chip
   computed background rgba(0,0,0,0) and one colour - "In review", "Completed"
   and "Announced" were indistinguishable. That is the whole "which status"
   question. Colours are the ratified status triad, same hues the old
   .state-chip carried. */
.dsx .chip.ch-live     { background: var(--accent-10); color: var(--accent); }
.dsx .chip.ch-closed   { background: var(--ok-12); color: var(--ok); }
.dsx .chip.ch-hidden   { background: var(--pend-12); color: var(--pend); }
.dsx .chip.ch-soon     { background: var(--accent-10); color: var(--accent); }

/* 2. REAL LOGOS WERE CROPPED TO A SLIVER. The .logo-tile img rule was deleted
   with the rename and never replaced, so L Stop's 1224x395 logo rendered at
   natural size inside a 48px box with object-fit:fill and max-width:none -
   the top-left corner of a wide wordmark, which is nothing. */
.dsx .logo img {
  max-width: 78%; max-height: 66%; width: auto; height: auto;
  object-fit: contain;
}

/* 3. A CLIENT WITH MANY MONTHS STRETCHED ITS WHOLE ROW. The grid stretches
   every card to the tallest, so one client on month 12 left its neighbours
   with a hole between their months and their footer. The list scrolls inside
   the card instead; the footer stays where the eye expects it. */
.dsx .months { max-height: 320px; overflow-y: auto; scrollbar-width: thin; }
.dsx .months::-webkit-scrollbar { width: 6px; }
.dsx .months::-webkit-scrollbar-thumb {
  background: var(--w-16); border-radius: var(--r-pill);
}

/* 4. REVEAL LOST ITS DISTINCTION. A hidden month is the one state where the
   action is ours and urgent; it read identically to Close and Reopen. */
.dsx .btn.m-act[data-state="live"][data-confirm*="Reveal"] {
  color: var(--accent); box-shadow: var(--edge), inset 0 0 0 1px var(--accent-35);
}

/* 5. THE RAIL'S PRIMARY ACTION UNPINNED BELOW THE FOLD WIDTH while the card's
   stayed right-aligned at every width. Same rule, both views. Kept in step with
   the fold above: it was 1100 until the fold moved to 1200. */
@media (max-width: 1200px) {
  .dsx .rrow .r-actions { justify-content: flex-end; }
}
.dsx .rrow-soon { opacity: 0.82; }
.dsx .rrow-soon .r-read { color: var(--text-faint); }

/* THE RAIL IS A TABLE, SO ITS COLUMNS MUST LINE UP ACROSS ROWS.
   Each .rrow is its own grid, and the last track was `auto` - sized by that
   row's own buttons. A row with one button (the announced month, which only
   offers Remove) therefore left more room for the fr tracks before it, and
   every cell in that row slid right: measured Month at x=605 against x=500 on
   the rows above, the chip at 912 against 738. That is the "random
   positioning of the elements" Dmitry rejected on the other surface.
   Pinning the action track makes every row share one geometry. The pinned
   track lives in --rail-tracks on .dsx .ov-rail. */

/* The in-flight rows were <button> in the approved mockup and became <a> in
   the build so they could carry a real href - and picked up the browser's
   underline with it. Dmitry spotted it against the mockup he had approved.
   Every anchor inside this shell states its own decoration; none inherits
   the default. */
.dsx a { text-decoration: none; }
.dsx .if-item:hover .if-who { color: var(--accent); }

/* ── THE WHITE BUTTONS (Dmitry, 2026-08-22) ──────────────────────────────────
   Every <button> in this shell was rendering `border: 2px outset #FFFFFF` -
   the browser's DEFAULT button border. Measured: 20 of them. The <a> elements
   beside them measured 0px, which is why "Open" looked clean while every real
   button wore a white outline.

   Cause: the mockup leaned on a global `button { border: 0 }` reset. Porting
   it, I stripped the global resets on purpose so they could not reach the
   client deck or the report - and never replaced this one scoped. The page
   then inherited the operating system's button chrome, which is the precise
   thing the whole round was supposed to remove.

   Scoped to .dsx, so it still cannot touch any other surface. */
.dsx button {
  border: 0;
  font: inherit;
  -webkit-tap-highlight-color: transparent;
}
.dsx .btn, .dsx .ibtn, .dsx .bell, .dsx .more,
.dsx .ov-toggle button, .dsx .m-act { border: 0; }

/* ── THE GREY CHEVRON (Dmitry, 2026-08-22) ───────────────────────────────────
   `.more` measured `background: rgb(107, 107, 107)` - ButtonFace, the
   operating system's own button colour. On a near-black card it reads as a
   white block welded to the right of "Copy client link", which is exactly
   what Dmitry circled.

   Same root cause as the white borders: the mockup leaned on a global
   `button { background: none; border: 0 }` reset, the port stripped the
   globals so they could not reach the client deck, and I replaced only the
   border half. Every button in this shell now states its own ground, and the
   two halves of the split control state the SAME ground - a split button is
   ONE control with a seam, never a button plus a stray glyph. */
.dsx button { background: none; }
.dsx .split { background: var(--lift-1); box-shadow: var(--edge); border-radius: var(--r-chip); }
.dsx .split .btn, .dsx .split .more { background: none; box-shadow: none; }
.dsx .split:hover { background: var(--lift-2); }
.dsx .split .more { color: var(--text-dim); }
.dsx .split .more:hover { color: var(--text); }
.dsx .split .seam { background: var(--line); }

/* ── OVERLAYS THAT NEVER PAINTED (Dmitry, 2026-08-22) ────────────────────────
   The chevron menu and the notification panel both opened and both stayed
   invisible. Measured on the live page: after a real click the link menu
   reported hidden=false, display:block, z-index:80, correctly positioned at
   top 684 / left 79 - and opacity:0, visibility:hidden. Dmitry clicked the
   arrow, nothing appeared, and reported the control as dead. It was.

   Cause, and this is the THIRD time in one round: the mockup reveals its
   overlays with an `.open` class, while this app's JavaScript has always
   toggled the `hidden` attribute. I ported the mockup's rules and rewired the
   veil and the toast to `.open`, but the link menu and the notification panel
   are driven by pre-existing app code I did not touch - so they kept toggling
   `hidden` against CSS that was waiting for a class that never arrived.

   Driving them off `:not([hidden])` makes the stylesheet obey the contract
   the JS actually has, instead of the one the mockup assumed, and keeps the
   `.open` form working for the two that use it. */
.dsx .link-menu:not([hidden]),
.dsx .notif-panel:not([hidden]) {
  transform: scale(1); opacity: 1; visibility: visible;
}

/* ── THE RAIL'S ACTION ROW WRAPPED (2026-08-22) ──────────────────────────────
   Measured: the four controls need 387px. The track was pinned at 400px on a
   desk (fits, one line) but narrowed to 340px at <=1440, so every rail row
   wrapped "Open" onto a second line and grew from 71px to 100px. A lone
   primary action hanging under its own cluster, in a different place at a
   different window width, is the "random positioning of the elements" Dmitry
   rejected - and the pinned track existed to prevent exactly that.

   One width for the track at every desk size, and the row is told never to
   wrap so a future label cannot quietly reintroduce this. Both widths live in
   --rail-tracks on .dsx .ov-rail - restating them here is what let the <=1100
   fold be overruled by a rule written for the desk. */
.dsx .r-actions { flex-wrap: nowrap; }

/* ── CARDS STOP INFLATING (Dmitry, 2026-08-22) ───────────────────────────────
   CSS Grid stretches every card in a row to the tallest one. Active Hearing
   has two months, so the three one-month cards beside it grew to match and
   carried a dead band between their month block and their footer - Dmitry
   crossed it out on three cards at once.

   Each card now sizes to its own content and top-aligns. A one-month client
   is short, a ten-month client is tall, and nothing is padded to match a
   neighbour. The months list keeps its own scroll ceiling so a client on
   month 10 cannot push the roster off the screen. */
.dsx .client-grid { align-items: start; }
.dsx .c-foot { margin-top: 16px; }

/* ── EARLIER MONTHS ARE ONE LINE (Dmitry, 2026-08-22) ────────────────────────
   "prepare that some of the clients might have 5 or 6 or even 10 months".
   The current month keeps the full block - progress bar, decided count, owner
   and its lifecycle action. Every earlier month is a single row: number,
   label in full, and what happened to it. A ten-month client grows by about
   32px per past month instead of 112px, and the card still opens on the month
   that matters. The label is never truncated; month names are data. */
.dsx .month-past {
  /* an explicit three-column grid, not a wrapping flex row. With flex-wrap
     plus margin-left:auto on the reading, each past month stacked onto THREE
     lines - "Month 9", then "September 2026", then "13 of 13" - and a
     ten-month card came to 876px. Three tracks cannot wrap. */
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: baseline; gap: 12px;
  padding: 8px 12px; border-radius: var(--r-chip);
  background: var(--lift-1); box-shadow: var(--edge);
  text-decoration: none; color: inherit;
  transition: background-color 120ms ease;
}
.dsx .month-past:hover { background: var(--lift-2); }
.dsx .month-past:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.dsx .mp-ord {
  font-family: var(--font-display); font-size: 13px; font-weight: 700; color: var(--text);
}
.dsx .mp-label { font-size: 12px; color: var(--text-faint); }
.dsx .mp-owner {
  justify-self: end; display: inline-flex; align-items: center; gap: 8px;
  font-size: 12px; color: var(--text-dim); white-space: nowrap;
}
/* the ceiling only bites at genuinely extreme counts now that past months are
   one line rather than a full block */
.dsx .months { max-height: 520px; }

/* the fold. Native <details>, so it is keyboard-reachable and needs no JS -
   but its marker is operating-system chrome, which this page does not wear. */
/* NOT display:flex on the <details> itself - that defeats Chrome's own
   content hiding and the fold renders open while its summary says otherwise.
   Caught by clicking it: nine months visible under a summary reading "5
   earlier months". The flex column belongs on an inner body. */
.dsx .months-more { display: block; }
.dsx .months-more-body { display: flex; flex-direction: column; gap: 12px; margin-top: 12px; }
.dsx .months-more > summary {
  list-style: none; cursor: pointer;
  font-family: var(--font-display); font-size: 12px; font-weight: 600;
  color: var(--text-faint); padding: 4px 12px; border-radius: var(--r-chip);
  display: flex; align-items: center; gap: 8px;
  transition: color 120ms ease;
}
.dsx .months-more > summary::-webkit-details-marker { display: none; }
.dsx .months-more > summary::marker { content: ""; }
.dsx .months-more > summary::before {
  content: ""; width: 0; height: 0; flex: none;
  border-left: 4px solid currentColor;
  border-top: 3.5px solid transparent; border-bottom: 3.5px solid transparent;
  transition: transform 120ms ease;
}
.dsx .months-more[open] > summary::before { transform: rotate(90deg); }
.dsx .months-more > summary:hover { color: var(--text-dim); }
.dsx .months-more > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* with the fold in place the list no longer needs a scroll ceiling */
.dsx .months { max-height: none; overflow: visible; }

/* how many clients are running, beside the title. Quiet - it is a fact, not
   an alarm, and it must not compete with the page name. */
.dsx .head-line h1 + .client-count {
  font-family: var(--font-display); font-size: 13px; font-weight: 600;
  color: var(--text-faint); align-self: center;
  /* hugs the title and pushes the tools to the right edge. Without the auto
     margin the head-line spread it into the middle of the bar, reading as a
     stray chip rather than a fact about the word beside it. */
  margin: 0 auto 0 12px;
  padding: 4px 12px; border-radius: var(--r-pill);
  background: var(--lift-1); box-shadow: var(--edge);
  white-space: nowrap;
}

/* ── ARCHIVE + DELETE (Dmitry, 2026-08-22) ───────────────────────────────────
   "Archiving will give me access to it but just hided from main page.
   Sometimes clients comeback." */

/* the card's overflow control. Secondary and destructive actions live here
   because the footer cannot take a fourth button at a 398px card without
   wrapping - the defect the rail already taught us. */
.dsx .c-menu { flex: none; margin-left: 8px; align-self: flex-start; }
.dsx .c-menu[aria-expanded="true"] { background: var(--lift-2); color: var(--text); }
.dsx .link-menu .cm-danger { color: var(--err); }
.dsx .link-menu .cm-danger:hover { background: var(--err-10); }

/* a destructive button states it. One shape, one language, red ink only. */
.dsx .btn.danger { color: var(--err); }
.dsx .btn.danger:hover:not(:disabled) { background: var(--err-12); }
.dsx .btn:disabled { opacity: 0.4; cursor: not-allowed; }

/* dialog fields */
.dsx .dlg-field {
  display: flex; flex-direction: column; gap: 8px; margin-top: 16px;
  font-family: var(--font-display); font-size: 12px; font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-faint);
}
.dsx .dlg-field b {
  font-family: var(--font-body); font-size: 13px; font-weight: 700;
  letter-spacing: 0; text-transform: none; color: var(--text);
}

/* the archived fold, at the foot of the roster. Same <details> idiom as the
   earlier-months fold so the page carries one way of hiding things, not two. */
.dsx .archived-fold { display: block; margin-top: 32px; }
.dsx .archived-fold > summary {
  list-style: none; cursor: pointer; width: fit-content;
  font-family: var(--font-display); font-size: 13px; font-weight: 600;
  color: var(--text-faint); padding: 8px 16px; border-radius: var(--r-chip);
  background: var(--lift-1); box-shadow: var(--edge);
  display: flex; align-items: center; gap: 8px;
  transition: color 120ms ease, background-color 120ms ease;
}
.dsx .archived-fold > summary::-webkit-details-marker { display: none; }
.dsx .archived-fold > summary::marker { content: ""; }
.dsx .archived-fold > summary::before {
  content: ""; width: 0; height: 0; flex: none;
  border-left: 4px solid currentColor;
  border-top: 3.5px solid transparent; border-bottom: 3.5px solid transparent;
  transition: transform 120ms ease;
}
.dsx .archived-fold[open] > summary::before { transform: rotate(90deg); }
.dsx .archived-fold > summary:hover { color: var(--text-dim); background: var(--lift-2); }
.dsx .archived-fold > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.dsx .archived-body { display: flex; flex-direction: column; gap: 12px; margin-top: 16px; }
.dsx .arch-row {
  display: grid; grid-template-columns: 36px minmax(0, 1fr) auto auto;
  align-items: center; gap: 16px;
  padding: 12px 16px; border-radius: var(--r-card);
  background: var(--lift-1); box-shadow: var(--edge);
}
.dsx .arch-row .logo { width: 36px; height: 36px; border-radius: var(--r-chip); font-size: 12px; }
.dsx .arch-name {
  font-family: var(--font-display); font-size: 15px; font-weight: 700; color: var(--text);
}
.dsx .arch-meta { font-size: 12px; color: var(--text-faint); margin-top: 2px; }
@media (max-width: 640px) {
  .dsx .arch-row { grid-template-columns: 36px minmax(0, 1fr); row-gap: 12px; }
  .dsx .arch-row .btn { grid-column: span 1; }
}

/* The overflow control narrows the name column, so the longest client names
   now wrap to three lines. Truncation is banned - a client's name is data -
   so the head reserves three lines instead of two and every card's month row
   still starts at the same height. */
.dsx .c-head { min-height: 90px; }   /* 3 lines at 18px/1.25 = 67.5, plus the
                                        service line at 11px/1.6 and its 4px
                                        margin = 89.1. 88 left the longest name
                                        one pixel proud of its neighbours. */

/* WHAT THE RESERVATION COST, AND WHO PAID IT (eyes gate, 2026-08-24).
   A one-line name leaves 42px of reserved nothing, and the card's own 16px
   gap turns it into a 58px band between the client's name and that client's
   months - on every card, at every width. The alignment above is not
   reversed; it is charged to the right places only.

   It aligns nothing when the roster is ONE column, and the roster is one
   column below 808px: the grid asks for two 360px tracks and a 24px gap
   (744px), and its own width is the viewport less 64px of shell padding.
   That is the whole change. Where the grid CAN be two columns the reserve
   stays exactly as ratified above, band and all, because that is what keeps
   the rows inside the cards scannable across a row.

   A second move was tried and reverted: bottom-anchoring the identity block
   inside the reserve, so the slack would merge into the card's top padding.
   It cost one pixel of drift on the one card that carries a rating - 147
   against 146 - and test_dashboard_index_v1::test_rows_inside_the_cards_line_up
   caught it in the full run, where that card has been rated and the isolated
   run has not. One pixel is still a different height per card, and the
   reserve exists for exactly that. The dead band on wide screens is the
   price of the ratification, and the blind reviewer narrowed its own finding
   to the widths where nothing is bought with it. */
@media (max-width: 807px) { .dsx .c-head { min-height: 0; } }

/* ===== DELTA ===== */

/* ── Gain status chip (2026-08-22) ───────────────────────────────────────
   Second fact on the head row. The review chip says what the CLIENT said;
   this one says what GAIN holds. Same four state colours as the calendar
   bars (ruled 2026-08-22), so one word means one colour on both surfaces.
   Kept one glance apart from the review family: OUTLINED where review
   chips are filled, and its marker is SQUARE where the review dot is
   round - the workspace-level chips (IN REVIEW) are already the outlined
   family, so machine state reads outlined, human verdict reads filled.
   A planned piece gets no chip at all: no record, no claim. */
.cnt-gain { background: none; border: 1px solid var(--line-strong); padding: 3px 9px; }
/* [hidden] is display:none from the USER AGENT stylesheet, and .cnt above sets
   display:inline-flex, which outranks it - so hiding this chip by attribute alone painted
   an empty outlined pill on every piece Gain has no record of. Caught by a paint test that
   asserted the chip's box, not its attribute. */
.cnt-gain[hidden] { display: none; }
/* THE SAME BUG, TWICE MORE, both pre-existing and both found by the blast-radius audit
   while checking this one. .so-preview sets display:block and .dsx .btn sets
   display:inline-flex, so an item with no thumbnail showed an empty preview button and an
   already-announced client still showed Announce. */
.so-preview[hidden] { display: none; }
.dsx .btn[hidden] { display: none; }
/* padding compensates the border: measured 28px against the review chip's 26px
   without it - the two must sit at the same height on the head row */
.cnt-gain::before { border-radius: var(--r-1); }   /* the square marker */
.cnt-gain .gd { color: var(--text-dim); font-weight: 500; }   /* the date half */
.cnt-gain.g-draft { color: var(--text); }
.cnt-gain.g-sched { color: var(--accent); }
.cnt-gain.g-live  { color: var(--ok); }

/* ── The lock (2026-08-22) ───────────────────────────────────────────────
   A LIVE piece's editors are not disabled, they are NOT RENDERED: the
   template swaps the caption form for a read-only block and the file form
   for one line. Nothing on a locked panel looks pressable and secretly is,
   and nothing that vanished is unexplained - each section label carries
   " · locked" and the notice at the top says why and offers the one way
   out. The notice reuses the clearance anatomy (.clr): chip, fact, action. */
.so-label .lk { color: var(--text-faint); }           /* " · locked" suffix */
.cap-still {                                /* same surface recipe as .clr */
  border: 1px solid var(--line); border-radius: var(--r-card); padding: 13px 14px;
  background: var(--w-02);
  font-size: 13.5px; line-height: 1.6; color: var(--text-dim);
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.lk-hint { font-size: 12.5px; color: var(--text-faint); }

/* Unlocked-while-live: temporary, and the operator should notice they left
   it on. Same panel, the cnt-upd border grammar (accent = operator action;
   never --warn or --pend, which mean revision and undecided elsewhere on
   this page) over a 4% accent wash, and the meta line carries who, when,
   why. Re-lock sits where clearance's Undo sits. The forms below return
   exactly as normal - no half-alive in-between state exists. */
.clr.is-open { border-color: var(--accent-28); background: var(--accent-04); }

/* ── Scheduled note (2026-08-22) ─────────────────────────────────────────
   A scheduled piece stays editable; the panel owes the operator two facts,
   once, quietly: the date it goes out, and that Gain does not pick edits
   up by itself. Plain text at the top of the detail, no box, no colour:
   information, not an alarm. */
.gain-note { font-size: 13px; color: var(--text-dim); }
.gain-note b { font-weight: 600; color: var(--text); }

/* ── THE FOCUS CONVENTION, CARRIED TO THE FAMILIES THAT MISSED IT (2026-08-24)
   `outline: 2px solid var(--accent)` is this sheet's ring - .segf, .thumb,
   .ctile, .wsb-btn, .months-more > summary and a dozen others already carry
   it. These families do not, because they were written before it: keyboard
   focus lands on them and nothing shows. `.lab-*` and `.pal-chip` are brand
   lab, whose template has its own inline <style> and inherited only this
   sheet's TOKENS - `grep :focus` over brandlab.html returns nothing at all,
   so its whole page is in this list.

   One block, not five patches: five separate rules is how the convention got
   five separate holes in the first place. Chromium's own `auto` ring is NOT
   this ring - it is 1px, it is the browser's, and it is why "some outline is
   drawn" is not the bar the test measures.

   Where the ring lives INSIDE a clipping pill (.seg, .view-toggle both carry
   `overflow: hidden`) the offset turns inward, or the ring is cropped away by
   its own parent. */
.filt:focus-visible,
.next-decide:focus-visible,
.rail-more > summary:focus-visible,
.gcal > summary:focus-visible,
.danger-zone > summary:focus-visible,
.cu-link > summary:focus-visible,
.lab-btn:focus-visible,
.pal-chip:focus-visible,
.lab input:focus-visible,
.lab summary:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.view-toggle button:focus-visible,
.seg button:focus-visible {
  outline: 2px solid var(--accent); outline-offset: -2px;
}

/* A chosen filter answered the cursor with nothing: `.filt:hover` (:162) and
   `.filt.on` (:163) both weigh (0,2,0) and the later one wins, so every hover
   declaration was overwritten on exactly the buttons most likely to be
   clicked again. Same cascade error as `.lab-btn:hover` under
   `.lab-btn.primary` in brandlab.html. */
.filt.on:hover { background: var(--accent-16); border-color: var(--accent); }

/* ══ Agency accounts (2026-08-25) ════════════════════════════════════════
   The operator side of one link that holds many companies. Its own page, not
   another control on the client overview: the overview is dense, this is rare,
   and accretion is what got that surface rejected twice. */
.acct-lede { margin-top: 10px; max-width: 68ch; font-size: 13px; line-height: 1.6; color: var(--text-dim); }
.acct-err {
  margin: 16px 0 0; padding: 11px 14px; border-radius: var(--r-card);
  border: 1px solid var(--err-30); background: var(--err-12); color: var(--err);
  font-size: 13px;
}
.acct-list { display: flex; flex-direction: column; gap: 14px; margin-top: 22px; }
.acct {
  border: 1px solid var(--line); border-radius: var(--r-card);
  background: var(--panel); padding: 16px 18px;
}
.acct-off { opacity: 0.55; }
.acct-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.acct-top h2 { font-family: var(--font-display); font-size: 18px; font-weight: 700; letter-spacing: -0.01em; }
.acct-slug { font-size: 11.5px; color: var(--text-faint); margin-top: 3px; }
.acct-acts { display: flex; gap: 8px; flex-wrap: wrap; }
.acct-acts form { display: inline; }
.acct-members { list-style: none; margin: 14px 0 0; display: flex; flex-direction: column; gap: 8px; }
.acct-members li {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  border: 1px solid var(--line); border-radius: var(--r-chip);
  padding: 9px 12px; background: var(--w-03);
}
.acct-m-name { font-weight: 600; font-size: 13.5px; }
.acct-m-slug { font-size: 11.5px; color: var(--text-faint); }
.acct-m-state { font-size: 12px; color: var(--text-dim); margin-left: auto; }
.acct-empty { font-size: 12.5px; color: var(--text-faint); }
.acct-add, .acct-new form {
  display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap; margin-top: 14px;
}
.acct-add label, .acct-new label { display: flex; flex-direction: column; gap: 5px; font-size: 11.5px; color: var(--text-faint); }
.acct-add input, .acct-new input {
  min-width: 220px; padding: 9px 11px; font: inherit; font-size: 13px;
  color: var(--text); background: var(--w-03);
  border: 1px solid var(--line); border-radius: var(--r-chip);
}
.acct-add input:focus-visible, .acct-new input:focus-visible {
  outline: 2px solid var(--accent-55); outline-offset: 1px;
}
.acct-new { margin-top: 26px; border-top: 1px solid var(--line); padding-top: 22px; }
.acct-new h2 { font-family: var(--font-display); font-size: 15px; font-weight: 700; }

/* armed is a STATE, not an action - it wears the shape of a chip, not a button */
.acct-state {
  display: inline-flex; align-items: center; gap: 7px;
  border: 1px solid var(--line); border-radius: var(--r-pill);
  padding: 7px 13px; font-size: 12px; color: var(--text-dim);
  background: var(--w-03);
}
.acct-state::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent);
}

/* ── Client-attached photos (2026-08-26) ──────────────────────────────────
   The picture a client sent with a change request. It renders in two places on
   this page and they are not decoration: the "Needs doing" block is what gets
   read before a revision is touched, and the history is where a photo from an
   earlier round stays findable. Sized to be recognisable at a glance and
   opened in a tab when it matters - the same job .rev-prev already does for
   the version a revision replaced.

   Every value names a token: these nodes are new, so nothing here is covered
   by the grandfathered measurement of the accepted surfaces. */
.rev-shots {
  display: flex; flex-wrap: wrap; gap: var(--sp-8); margin-top: var(--sp-8);
}
.rev-shot {
  display: inline-flex; align-items: center; gap: var(--sp-8);
  max-width: 100%; text-decoration: none;
  color: var(--ink-45); font-size: var(--fs-op-10);
}
.rev-shot img {
  width: var(--px-46); height: var(--px-46); object-fit: cover; display: block;
  border-radius: var(--r-6); border: var(--bw-hair) solid var(--line-mid);
}
.rev-shot-n {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: var(--px-120);
}
.rev-shot:hover { color: var(--text); }
.rev-shot:focus-visible { outline: var(--bw-thick) solid var(--accent); outline-offset: var(--sp-2); }
/* ── END Client-attached photos ──────────────────────────────────────────── */

/* ═══ audit round 2026-09-04 ═══════════════════════════════════════════════ */
/* A5: the rail search shows the house ring on keyboard focus (WCAG 2.4.7) */
#wsrSearch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; box-shadow: none; }

/* A6: an empty in-flight side folds to its one line instead of a column */
.if-side[data-empty="1"] { padding-block: 0; }
.if-side[data-empty="1"] .if-none { margin: 0; }
@media (min-width: 900px) {
  .inflight .if-grid:has(.if-side[data-empty="1"]) { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); }
  .inflight .if-grid:has(.if-side[data-side="client"][data-empty="1"]) { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }
}
/* and the roster's cards share a row's height, footers on one line */
.dsx .client-grid { align-items: stretch; }
.dsx .card .c-foot { margin-top: auto; padding-top: 16px; }

/* feature 7: the cross-client queue of open change requests */
.oq { margin: 0 0 24px; padding: 16px 20px; border-radius: var(--r-panel); background: var(--surface); border: 1px solid var(--line); }
.oq-head { font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-dim); font-weight: 700; display: flex; align-items: center; gap: 10px; }
.oq-n { display: inline-grid; place-items: center; min-width: 22px; height: 22px; padding: 0 7px; border-radius: 999px; background: var(--warn-soft, rgba(255,138,61,.14)); color: var(--warn); font-size: 12px; letter-spacing: 0; }
.oq-list { display: grid; gap: 6px; margin-top: 12px; }
.oq-item { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 2fr) auto; gap: 16px; align-items: baseline; padding: 10px 12px; border-radius: 10px; color: inherit; text-decoration: none; border: 1px solid transparent; }
.oq-item:hover, .oq-item:focus-visible { border-color: var(--line-strong); background: var(--panel); outline: none; }
.oq-who { font-weight: 600; font-size: 14px; display: flex; gap: 8px; align-items: baseline; }
.oq-mo { font-weight: 400; color: var(--text-dim); font-size: 13px; }
.oq-note { color: var(--text); font-size: 13px; font-style: italic; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.oq-when { color: var(--text-faint); font-size: 12px; white-space: nowrap; }
@media (max-width: 720px) { .oq-item { grid-template-columns: 1fr; gap: 4px; } }

/* feature 10: bulk clearance in the rail, and the keyboard cursor */
.wsr-clear { margin: 10px 14px 0; }
.wsr-clear-btn { width: 100%; min-height: 36px; border-radius: 10px; border: 1px solid var(--line-strong); background: var(--panel); color: var(--text); font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer; }
.wsr-clear-btn:hover, .wsr-clear-btn:focus-visible { border-color: var(--accent); outline: none; }
.wsr-item.kb-cursor { box-shadow: inset 0 0 0 2px var(--accent); }

/* feature 2: the note to the client in the slide-over forms */
.so-note { display: block; margin: 10px 0 6px; }
.so-note-lab { display: block; font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-dim); font-weight: 700; margin-bottom: 6px; }
.so-note-lab i { font-style: normal; text-transform: none; letter-spacing: 0; font-weight: 500; color: var(--text-faint); margin-left: 6px; }
.so-note textarea { width: 100%; min-height: 56px; resize: vertical; border-radius: 10px; border: 1px solid var(--line); background: var(--surface-2); color: var(--text); font: inherit; font-size: 13px; padding: 9px 11px; }
.so-note textarea:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* A2: the zone, named once in the bell */
.notif-tz { font-size: 11px; font-weight: 500; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-faint); margin-left: 8px; }

/* A4: the fill segments are decoration now */
.segf { cursor: default; }
.segf:hover { transform: none; filter: none; }

/* A7: the way back is a 44px target on touch */
@media (pointer: coarse) { a.back { display: inline-flex; align-items: center; min-height: 44px; } }

/* A3: the report's quiet fold */
.rep-quiet { margin-top: 14px; padding: 12px 16px; border-radius: 10px; border: 1px dashed var(--line-strong); color: var(--text-dim); }
.rep-quiet summary { cursor: pointer; font-size: 13px; font-weight: 600; color: var(--text); }
.rep-quiet-list { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.rep-quiet-piece { padding: 3px 9px; border-radius: 999px; background: var(--panel); font-size: 12px; }
.rep-none { color: var(--text-dim); font-size: 14px; }
