/* Taskboard — v12 palette (DESIGN_SPEC §1), responsive (four width tiers, 2–3 col boards) */
:root {
  /* base neutrals + month tints */
  --bg: #f6f7fb; --card: #fff; --ink: #1d2330; --muted: #6c7488;
  --line: #eef0f4;
  --shadow: 0 1px 3px rgba(20,30,60,.08);  /* rejected/hard = overdue red (one red) */
  --radius: 10px;
  --open-bg: #eef3fc;   /* light blue — My-Created Open panel body + matrix month column */
  /* month tints (alternating) — reuse existing palette: cool month = blue, warm month = amber */
  --m0-head: var(--navy-l); --m0-ink: #fff; --m0-col: var(--open-bg);
  --m1-head: var(--upcoming); --m1-ink: #fff; --m1-col: var(--upcoming-bg);
  --recurring: #ffe9a8;

  /* ── design tokens (DESIGN_SPEC §1) ── */
  --navy: #283A7D; --navy-d: #27306a; --navy-l: #34457f;
  --ink-2: #3a4256; --faint: #9aa1b2; --line-2: #d5d9e2;
  /* category identity — only two (Personal is retired) */
  --routine: #64748b; --routine-bg: #e6ebf2; --routine-bd: #cdd5e1; --routine-ink: #3f4a5c;
  --task: #14a2ae; --task-bg: #e8fafb; --task-bd: #b3e8ec; --task-ink: #0b6d75;
  /* status / urgency */
  --overdue: #c02626; --overdue-bg: #fef2f2; --overdue-bd: #f6caca; --overdue-ink: #a01818;
  --upcoming: #e08700; --upcoming-bg: #fff6e8; --upcoming-bd: #f7d9a3; --upcoming-ink: #9a5d00;
  --approval: #cb6767; --approval-bg: #fbeeee; --approval-bd: #f0cccc; --approval-ink: #a84f4f;
  --done: #15924a; --done-bg: #f0fdf4; --done-bd: #bbe8cb; --done-ink: #166534;
  --radius-lg: 14px; --shadow-sm: 0 1px 2px rgba(20,30,60,.06);
  --fs-xs: .75rem; --fs-sm: .82rem; --fs-md: .9rem; --fs-base: .95rem; --fs-lg: 1.1rem; --fs-xl: 1.4rem;
  /* Per-assignee Actions table (.atbl) geometry. Shared, because the subtask breakdown (.subitem-bd)
     aligns its initials to the Status column edge derived from these — the two must move together.
     --atbl-c1  Staff column.
     --atbl-cw  ONE proof column (link / photo). 64px = a 17px icon + gap + a TWO-DIGIT count pill +
                the cell's own .5rem padding either side: sized to the worst case so a task that
                reaches 10 photos doesn't widen its column and shift Status. */
  --atbl-c1: 220px;
  --atbl-cw: 64px;
  /* The one checkbox. Every tickable box in the product is this size and this ON colour unless a
     context deliberately overrides --box-on (see the .box component). */
  --box-size: 18px;
  --box-on: var(--done);
}
* { box-sizing: border-box; }
body { margin: 0; font-family: "Inter", "DejaVu Sans", sans-serif;
  background: var(--bg); color: var(--ink); line-height: 1.45; }
a { color: var(--navy); text-decoration: none; }
a:hover { text-decoration: underline; }
/* The `hidden` attribute must ALWAYS win. The UA sheet gives it `display:none` at the weakest possible
   specificity, so ANY component rule that sets display (.field / .cdlg / .bellrow — all display:flex)
   silently beats it and the element stays on screen. That trap has now been hit three times and patched
   per-component twice; this is the one global fix, so no future component has to remember. */
[hidden] { display: none !important; }

/* Top bar */
.topbar { background: var(--navy); color: #fff; position: sticky; top: 0; z-index: 20; }
.topbar-inner { display: flex; align-items: center; gap: .75rem; padding: .55rem .9rem; flex-wrap: wrap; }
.brand { color: #fff; font-weight: 700; font-size: 1.1rem; }
/* Nav sits left with a roomier item gap, plus clear air between the brand and the first item
   (agreed 2026-07-17 — a centred nav was tried and reverted same day). */
.nav { display: flex; gap: .6rem; flex: 1; margin-left: 1.25rem; }
/* The nav-pill contract, worn by every top-bar item — the .nav items AND the standalone .inboxlink
   (which sits outside .nav in the right cluster, see below). One rule, so a hover/colour tweak can't
   land on the nav and miss the Inbox. */
.nav a, .inboxlink { color: #cbd5e1; padding: .35rem .6rem; border-radius: 6px; font-size: .9rem; display: inline-flex; align-items: center; gap: .4rem; }
.nav a .svg-ico, .inboxlink .svg-ico, .nav-logout button .svg-ico { width: 16px; height: 16px; }
.nav a:hover, .inboxlink:hover { background: var(--navy-l); text-decoration: none; }
.nav a.active, .inboxlink.active { background: var(--navy-l); color: #fff; }
/* Count on a nav item — how many things there are WAITING ON YOU (My Created: pending approvals).
   Solid --approval, the pending colour used by the PENDING APPROVAL panel + `.pend` badge, so the
   nav, the panel and the card all say "pending" in the same colour. Never rendered at 0. */
/* Unscoped (not `.nav a .navcount`) so the same badge also renders standalone in a page's own <h1>
   (e.g. Inbox's unread count) — same class, same look, wherever it's dropped. */
.navcount { flex: none; min-width: 18px; padding: 0 .3rem; border-radius: 999px;
  background: var(--approval); color: #fff; font-size: .7rem; font-weight: 800;
  line-height: 18px; text-align: center; }
/* Inbox — icon + count only, right cluster ahead of the name chip (sanctioned exception to "every nav
   item is an icon + label", DESIGN_SPEC §2). Wears the shared nav-pill rule above and reuses
   .navcount; the only delta is flex:none, because unlike a .nav item it is a direct child of the
   flex .topbar-inner and must not stretch. Visible at every width — see the ≤720px rule below, it
   does NOT collapse into the hamburger like .nav does. */
.inboxlink { flex: none; }
.actas { display: flex; align-items: center; gap: .35rem; flex: none; }
/* "Acting as" stays on one line — it was wrapping to two and pushing the bar taller. */
.actas label { color: #b6bdd0; font-size: .8rem; white-space: nowrap; }
.actas select { padding: .3rem .4rem; border-radius: 6px; border: 1px solid var(--navy-l);
  background: var(--navy); color: #fff; cursor: pointer; }
/* Solid amber (--upcoming) ONLY while impersonating someone else — a manager left acting-as
   another staffer should notice; acting as yourself stays quiet navy. White-on-amber matches
   .astat.s-upcoming / --m1-head. (Dev has no login, so no principal — never amber there.) */
.actas select.imp { background: var(--upcoming); border-color: var(--upcoming); font-weight: 600; }
.actas-static { color: #fff; font-size: .9rem; font-weight: 700; }
.logout-form { display: contents; }  /* button sits in the topbar flex as if bare */
.logout-link { color: #cbd5e1; font-size: .85rem; padding: .35rem .6rem;
  background: none; border: none; font-family: inherit; cursor: pointer; }
.logout-link:hover { color: #fff; text-decoration: underline; }
/* The hamburger-only logout (last child of .nav): hidden on desktop, where the top-right .logout-form
   is the logout. Shown ≤720px in the dropdown (see the mobile block). */
.nav-logout { display: none; }
/* Mobile-only "Acting as" identity control: a square-user icon (white = acting as yourself, amber via
   .imp = impersonating) that opens the shared .dpanel name picker. Hidden on desktop, where the amber
   .actas <select> is the switcher. Shown ≤720px (mobile block). */
.actas-mini { display: none; position: relative; flex: none; }
.actas-mini > summary { list-style: none; cursor: pointer; display: flex; align-items: center; padding: .3rem; border-radius: 6px; }
.actas-mini > summary::-webkit-details-marker { display: none; }
.actas-mini > summary:hover { background: var(--navy-l); }
.actas-mini .svg-ico { width: 20px; height: 20px; color: #fff; }
.actas-mini.imp .svg-ico { color: var(--upcoming); }   /* amber = impersonating (same signal as the select) */
/* Reuses .dpanel/.dopt but floats from the icon: right-aligned, readable width, buttons full-width. */
.actas-menu { left: auto; right: 0; min-width: 11rem; }
.actas-menu form { display: flex; flex-direction: column; gap: .05rem; }
.actas-menu .dopt { width: 100%; text-align: left; background: none; border: none; font-family: inherit; }
.navtoggle { display: none; background: none; border: none; color: #fff; font-size: 1.3rem; cursor: pointer; }

.container { max-width: 1200px; margin: 0 auto; padding: 1rem; }

/* Headings + toolbar */
.page-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: .75rem; flex-wrap: wrap; }
/* ONE page title everywhere: same size, weight and tracking whether the h1 sits in .pgh (boards) or
   the older .page-head (forms, admin lists). A bare h1 used to fall back to the browser's default 700
   with no tracking, so New Task read noticeably lighter than My Board. */
h1 { font-size: var(--fs-xl); margin: .2rem 0; font-weight: 800; letter-spacing: -.01em; }
h2 { font-size: 1.1rem; }

/* Buttons — permanent system: solid primary + outline secondary; every coloured button MUST
   define its own :hover (never fall through to the neutral .btn:hover). */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: .4rem; min-height: 40px;
  padding: .5rem .85rem; border-radius: 8px; border: 1px solid #cbd5e1;
  background: #fff; color: var(--ink); cursor: pointer; font-size: .9rem; }
.btn:hover { text-decoration: none; border-color: var(--line-2); background: var(--bg); }
.btn-primary { background: var(--navy); border-color: var(--navy); color: #fff; }
.btn-primary:hover { background: var(--navy-d); border-color: var(--navy-d); }
.btn-ok { background: var(--done); border-color: var(--done); color: #fff; }
.btn-ok:hover { background: var(--done-ink); border-color: var(--done-ink); color: #fff; }
.btn-danger { background: #fff; border-color: var(--overdue); color: var(--overdue); }
.btn-danger:hover { background: var(--overdue); border-color: var(--overdue); color: #fff; }
.btn-sm { padding: .3rem .55rem; font-size: .82rem; }

/* Filter tabs */
.tabs { display: flex; gap: .25rem; flex-wrap: wrap; margin-bottom: .75rem; }
.tabs a { padding: .4rem .8rem; border-radius: 999px; background: #fff; border: 1px solid var(--line); color: var(--ink); font-size: .9rem; }
.tabs a.active { background: var(--ink); color: #fff; border-color: var(--ink); }
.filterbar { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; margin-bottom: .75rem; }
.filterbar select, .filterbar input { padding: .4rem .5rem; border: 1px solid var(--line); border-radius: 8px; }

/* Chips / badges */
.chip { display: inline-block; padding: .1rem .5rem; border-radius: 999px; font-size: .75rem; border: 1px solid var(--line); background: #f8fafc; }
.progress { font-size: .78rem; color: var(--muted); }

/* Tables */
table { width: 100%; border-collapse: collapse; background: #fff; border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); }
th, td { text-align: left; padding: .55rem .7rem; border-bottom: 1px solid var(--line); font-size: .9rem; vertical-align: top; }
th { background: #f8fafc; font-size: .78rem; text-transform: uppercase; letter-spacing: .03em; color: var(--muted); }
tr:last-child td { border-bottom: none; }

/* Forms */
/* The legacy `.card` shell is RETIRED — every form page now uses .page-form > .formcard (DESIGN_SPEC
   §5). Don't reintroduce it: a second card shell at a second radius is exactly the drift the radius
   tiers exist to stop. Task detail's `.tdv .card` is a page-scoped class of the same name, self-
   contained in views/tasks/show.php — unrelated to this. */
.form-grid { display: grid; gap: .9rem; max-width: 640px; }
label.field { display: block; font-size: .85rem; font-weight: 600; margin-bottom: .25rem; }
input[type=text], input[type=email], input[type=url], input[type=date], input[type=number], select, textarea {
  width: 100%; padding: .5rem .6rem; border: 1px solid var(--line); border-radius: 8px; font-size: .95rem; font-family: inherit; }
textarea { min-height: 80px; resize: vertical; }
.checkbox { display: flex; align-items: center; gap: .4rem; font-size: .9rem; }
.help { color: var(--muted); font-size: .8rem; margin-top: .2rem; }
.inline { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }
fieldset { border: 1px solid var(--line); border-radius: 8px; padding: .7rem; }
legend { font-size: .82rem; color: var(--muted); padding: 0 .4rem; }

/* ── Chevron (V arrow, --muted) ── */
/* .form-grid select → automatic on all form pages. */
.form-grid select {
  appearance: none; -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1,1 L5,5 L9,1' stroke='%236c7488' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right .65rem center; background-size: 9px 5px;
  padding-right: 2rem;
}

.notice { background: #fffbeb; border: 1px solid #fde68a; color: #92400e; padding: .6rem .8rem; border-radius: 8px; margin-bottom: .8rem; font-size: .88rem; }

/* Mobile top bar: hamburger hard left, brand optically centred, nav as a floating dropdown.
   Right cluster reads: [Acting-as switcher] [Inbox at the far right]. Management keeps the "Acting as"
   switcher here (amber .imp while impersonating — same as desktop); a non-management staffer shows their
   plain name chip in the same slot. Log out is relocated to the BOTTOM of the hamburger (.nav-logout). */
@media (max-width: 720px) {
  .topbar-inner { position: relative; flex-wrap: nowrap; min-height: 44px; }
  .navtoggle { display: block; order: 1; padding: 0 .2rem; }
  /* Absolute, so it centres on the BAR rather than on whatever space the flex items leave over. */
  .brand { position: absolute; left: 50%; transform: translateX(-50%); }
  /* The desktop select is too wide for the bar (it collided with the centred brand) — hide it and the
     non-mgt name chip; Management gets the narrow .actas-mini icon instead, which carries the auto
     margin that pushes the right cluster to the edge. */
  form.actas, .actas-static { display: none; }
  .actas-mini { display: block; order: 2; margin-left: auto; }
  /* Inbox is the far-right item (must NOT collapse into the hamburger — an unread badge can't hide). */
  .inboxlink { order: 3; margin-left: .3rem; }
  /* Top-right logout is relocated into the dropdown; the in-menu .nav-logout takes over. */
  .logout-form { display: none; }
  .nav-logout { display: block; }
  /* Icon + label like every other drawer item — same .4rem gap and 16px icon as the nav-pill
     contract (it borrows that rule's .svg-ico sizing); it can't join the .nav a group outright
     because it needs width:100% + button resets a link doesn't. */
  .nav-logout button { width: 100%; display: flex; align-items: center; gap: .4rem;
    text-align: left; background: none; border: none; cursor: pointer;
    color: #cbd5e1; font-size: .9rem; padding: .5rem .6rem; border-top: 1px solid var(--navy-l); margin-top: .15rem; }
  .nav-logout button:hover { background: var(--navy-l); color: #fff; }
  /* Floats over the page instead of pushing it down (it used to be an in-flow full-width column). */
  .nav { display: none; position: absolute; top: 100%; left: 0; right: 0; z-index: 25;
    flex-direction: column; gap: .15rem; padding: .4rem; background: var(--navy); box-shadow: var(--shadow); }
  .nav.open { display: flex; }

  /* `.matrix-wrap` is shared by TWO tables: IC Rotation's `.matrix.ic` (locked, §10) and the
     Production/Sales `.psm`. These rules are IC Rotation's — its 4-month grid is 738px minimum, so it
     still scrolls INSIDE its wrapper on a phone. Without that the whole PAGE scrolls sideways, and
     the sticky topbar is only viewport-wide, so panning right crops the nav off screen.
     The scroll container becomes the containing block for position:sticky, so the thead can no
     longer pin to the viewport — it MUST be dropped in this same media query (DESIGN_SPEC §9) or it
     pins 44px down inside the scroller instead.
     The `.psm` matrices no longer scroll at all — they become two flex columns, and the MOBILE LAYER
     at the foot of this file overrides both of these for them. */
  .matrix-wrap { overflow-x: auto; }
  .matrix thead th { position: static; }
}

/* Wide / dense screens: let the container use up to 1920. Columns come from .panel-grid — there is
   ONE column rule for every dense board (see "Page shells" below); .board-grid is retired. */
.container.wide { max-width: 1920px; }

/* Team / segment headings */
.team-head { margin: 1.3rem 0 .45rem; font-size: 1rem; color: var(--navy);
  border-bottom: 2px solid var(--line); padding-bottom: .25rem; font-weight: 700; }

/* Legacy board zones (Team / By-Staff board views in views/board/index.php) */
.zone { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: .9rem 1rem; margin-bottom: 1rem; }
.zone-attention { border-left: 4px solid var(--navy); }
.zone h2 { margin: 0 0 .6rem; font-size: 1.05rem; color: var(--navy); }
.zone .sub { color: var(--muted); font-size: .82rem; font-weight: 400; }
details.routine > summary { cursor: pointer; color: var(--navy); font-weight: 700; padding: .3rem 0; }

/* ── Station card (LOCKED, shared — DESIGN_SPEC §3) ───────────────────────────────────────────────
   The grouped-by-station card: filled slate band (h3 = main station + .gc count pill), plain
   sub-station sub-heads (h4), rows inside. Page-shell-independent — usable on any page, not just the
   Production/Sales board. Don't fork a per-page variant. */
/* --radius-lg, NOT --radius: this is a top-level surface like a My Board panel (.group), not a card
   nested inside one (.tc), so it takes the panel radius. overflow:hidden is what rounds the band. */
.st-card { background: #fff; border: 1px solid var(--line); border-radius: var(--radius-lg);
  box-shadow: var(--shadow); padding: .7rem .85rem; break-inside: avoid; overflow: hidden; }
/* Same band as a panel header (.group > .gh) — same 46px height, same .5rem/.9rem padding, type and
   .gc pill, so a station card and a My Board panel sitting side by side line up. Stations carry NO
   icon (the name is a proper noun; an icon would be the same generic glyph on every card). Bled to
   the card's edges by the negative margin, which must stay in step with .st-card's padding. */
.st-card h3 { margin: -.7rem -.85rem .6rem; padding: .5rem .9rem; min-height: 46px;
  background: var(--routine); color: #fff; border: none; border-radius: 0;
  display: flex; align-items: center; gap: .45rem; box-sizing: border-box;
  font-size: var(--fs-sm); font-weight: 700; text-transform: uppercase; letter-spacing: .04em; }
.st-card h4 { margin: .55rem 0 .2rem; font-size: .84rem; color: var(--ink); font-weight: 700; }
/* Due line, shown once per due-date group. Carries the shared .due badge (§ .due.od/.soon/.ok). */
.due-line { margin: .5rem 0 .15rem; font-size: .78rem; }
.due-line .meta { color: var(--muted); }
ul.tg { list-style: none; margin: 0 0 .2rem; padding: 0 0 0 .2rem; }
ul.tg li { padding: .12rem 0; font-size: .9rem; }
ul.tg li.tk { display: flex; align-items: center; gap: .4rem; }
.tk .done-check { flex: none; width: 1rem; height: 1rem; cursor: pointer; }
.tk .bullet { flex: none; color: var(--muted); width: 1rem; text-align: center; }
.tk.done a { text-decoration: line-through; color: var(--muted); }
.zone .progress { font-size: .75rem; color: var(--muted); }
.chip.mini { font-size: .62rem; padding: .02rem .35rem; background: #fff4e0; border-color: #f0d9a8; color: #6b5410; }

/* Overdue zone — red accent, sits at the top of My Tasks */
.zone-overdue { border-left: 4px solid var(--overdue); }
.zone-overdue h2 { color: var(--overdue); }

/* Skill matrix (v6) */
/* The table scrolls with the page (no inner scroll box). The thead is sticky to the viewport, just
   below the sticky topbar (--topbar-h, measured in JS), so headers stay visible as rows scroll. */
.matrix-wrap { overflow: visible; }
/* Production|Sales toggle sits beside the heading — pinned to the far right of a 1920 shell it read
   as detached from the table. */
.matrix-wrap .mx-head { margin: .6rem 0 .35rem; justify-content: flex-start; gap: 1.1rem; }
.matrix-wrap .sk-legend { margin: 0 0 .6rem; }
/* Override the global table's overflow:hidden + rounded clip — it would trap the sticky thead inside
   the table and stop it sticking to the viewport. */
table.matrix { border-collapse: collapse; width: auto; background: #fff; overflow: visible; border-radius: 0; box-shadow: none; }
.matrix th, .matrix td { border: 1px solid var(--line); padding: .4rem .55rem; font-size: 14px; text-align: center; }
/* z-index: the sticky thead must sit ABOVE the body's sticky-left cells (band rows are z-index 3),
   otherwise rows paint over the header as they scroll under it. */
.matrix thead th { position: sticky; top: var(--topbar-h, 44px); z-index: 6; background: var(--navy); color: #fff;
  font-weight: 700; font-size: 13.5px; text-transform: none; letter-spacing: 0; border-color: #4a5a9a;
  vertical-align: bottom; }
.matrix th.mx-task, .matrix td.mx-task { text-align: left; position: sticky; left: 0; background: #fff;
  min-width: 240px; max-width: 340px; z-index: 1; }
.matrix thead th.mx-task { z-index: 7; background: var(--navy); }
.sk-cell { cursor: pointer; width: 2.4rem; font-size: 15px; font-weight: 800; user-select: none; }
.sk-cell:hover { outline: 2px solid var(--navy-l); outline-offset: -2px; }
/* Read-only counterpart (a viewer without can_edit_config()): same box, no click affordance. A
   different class rather than a cursor-only override — the global skill-toggle click handler
   (app.js) is delegated on `.sk-cell`, so a viewer's cell must not carry that class at all. */
.sk-view { width: 2.4rem; font-size: 15px; font-weight: 800; user-select: none; }
.sk-learnt { background: #e2f5ea; color: #15924a; }    /* green tick on green box — learnt */
.sk-learning { background: #e3e8ef; color: #8a93a6; }  /* grey tick on grey box — still learning */
.sk-none { background: transparent; }                  /* clear — no skill record */
.sk-key { display: inline-block; width: 1.3rem; height: 1.3rem; border: 1px solid var(--line);
  border-radius: 4px; text-align: center; line-height: 1.25rem; font-size: .85rem; vertical-align: middle;
  font-weight: 800; }
.sk-key.sk-learnt { background: #e2f5ea; color: #15924a; border-color: #bfe3cd; }
.sk-key.sk-learning { background: #e3e8ef; color: #8a93a6; border-color: #d3d9e2; }
.sk-legend { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap;
  font-size: .82rem; color: var(--ink); margin: -.3rem 0 .6rem; }
.sk-legend .sk-key { margin-left: .5rem; }
.sk-legend .sk-key:first-child { margin-left: 0; }
/* legend as the matrix footer (moved below the table) — clear the negative tuck so it doesn't
   overlap the last row. */
.matrix-wrap + .sk-legend { margin-top: 1.1rem; }

/* Production / Sales Matrix (psm) — LOCKED, see DESIGN_SPEC §9. Re-cut 2026-07-23.
   THE WIDTHS ARE DECLARED, NOT STRETCHED. The view emits the table's width inline as the sum of
   these seven columns — 920 + (60 × people) → Production 1520, Sales 1220 — inside the shared 1520
   .page-dense shell. So BOTH matrices use the SAME column widths and share a left edge: flip the
   Production|Sales toggle and nothing moves, Sales just ends 300px short with the slack on the right.
   There is no stretch factor any more, so a declared width IS the rendered width and a change to one
   column must be paid for out of another.
   NOT width:100% (that stretched Sales' 5-person table across the whole shell and moved every
   column) and NOT width:auto — CSS 2.1 §17.5.2.1 lets an auto-width fixed-layout table expand to its
   containing block, i.e. the same bug again. The width has to be stated.
   Each column is sized to a stated longest one-liner (DESIGN_SPEC §9): Routine "Ready for delivery
   according to sales instructions" (the longest production title, 50ch ≈ 316px), Frequency "When
   Required", Timing "Start of mth, Jan, Apr, Jul, Oct". Anything longer wraps — that is the design.
   Don't nudge these without agreeing the change with Janelle.
   2026-07-23b: Timing 208 → 232, paid out of Routine 372 → 348, so the 4-month quarterly timing fits
   one line once the view abbreviates "month" → "mth". FREQUENCY CANNOT PAY FOR ANYTHING — "When
   Required" measures ~102px inside its 102.4px content box, i.e. it is already at its limit. */
/* The table states its own width, computed HERE from the same numbers as the columns below — the view
   passes only `--psm-n`, the staff count, which is the one fact CSS can't know. Summing the widths in
   PHP instead would duplicate this block's numbers in a second file and let them drift apart silently.
   A CUSTOM PROPERTY, never an inline `width`: an inline width outranks every stylesheet rule including
   the MOBILE LAYER, so the phone layout could not narrow the table back to 100% without !important. */
table.matrix.psm { --psm-fixed: 920px; --psm-person: 60px;
  table-layout: fixed; width: calc(var(--psm-fixed) + var(--psm-n, 0) * var(--psm-person)); }
.psm .c-drag { width: 26px; } .psm .c-task { width: 348px; } .psm .c-log { width: 34px; } .psm .c-freq { width: 120px; }
.psm .c-time { width: 232px; } .psm .c-ic { width: 160px; }
.psm .c-staff { width: 60px; }
/* Every cell in a routine row reads off its TOP line. When one column wraps ("Every 3 months") the
   others were centring against it, so Timing and the IC pills sat a line below the routine title. */
.psm tr.task-row td { vertical-align: top; }
/* activity-log icon reads as part of the Routine name column — no divider between them; keep the
   log cell's RIGHT border (the real Routine|Frequency separator). */
.psm td.mx-task { border-right: 0; }
.psm td.mx-log-cell { border-left: 0; }

/* sticky left: drag handle + task name; sticky top handled by .matrix thead th */
.psm td.mx-drag, .psm th.stub { position: sticky; left: 0; z-index: 2; }
.psm thead th.stub { z-index: 8; left: 0; text-align: center; }
/* The `.fit` Sales variant is GONE (2026-07-23). It existed because production stretched to the 1920
   shell and Sales couldn't, so Sales had to re-declare production's *rendered* widths to match. Now
   that both state their widths outright there is one set of columns for both domains and nothing to
   vary — the shell is the shared .page-dense (1520) for both. Don't reintroduce a per-domain width. */
/* Long routine titles wrap to a second line rather than truncating — the name is the row's identity,
   so it's never cut off. */
.psm td.mx-task { position: sticky; left: 26px; z-index: 1; text-align: left; background: #fff;
  font-size: 14.5px; white-space: normal; overflow-wrap: anywhere; }
.psm td.mx-drag { background: #fff; color: var(--muted); cursor: grab; user-select: none; touch-action: none; }
.psm td.mx-ctx, .psm td.mx-ic { text-align: left; color: var(--ink); white-space: normal; }
.psm td.mx-ic { font-size: 13.5px; }

/* station + sub-station section rows (sticky to the left edge).
   Band height comes from equal top/bottom padding on a normal line box — the all-caps text then
   optically centres. Band borders take the band's own colour so the filled bar runs flush to the
   table edges (a --line border read as an outline narrower than the navy thead above it). */
.psm tr.station-row th { position: sticky; left: 0; z-index: 3; background: var(--routine); color: #fff;
  border-color: var(--routine); text-align: left; vertical-align: middle; line-height: 1.4;
  font-weight: 800; font-size: 15px;
  padding-top: .3rem; padding-bottom: .3rem; padding-left: 2.2rem; border-top: none; }
.psm tr.station-row.recurring th { background: var(--upcoming); color: #fff; border-color: var(--upcoming); }
/* sub-station band: a darker slate than --routine-bg so it reads as a header and doesn't blend into
   the light-grey "learning" skill cells in the rows below. */
/* Sub-station band: same 2.2rem left indent as the main band above it, so the two header texts line up
   on one vertical edge (Janelle 2026-07-24). No collapse caret — the expand/collapse feature was
   retired with it, so the header is not clickable (no cursor:pointer). */
.psm tr.group-row th { position: sticky; left: 0; z-index: 3; background: var(--routine-bd); color: var(--routine-ink);
  border-color: var(--routine-bd); text-align: left; vertical-align: middle; line-height: 1.4;
  font-weight: 700; font-size: 14px;
  padding-top: .3rem; padding-bottom: .3rem; padding-left: 2.2rem; }
.psm tr.group-row.recurring th { background: var(--upcoming-bg); color: var(--upcoming-ink); border-color: var(--upcoming-bg); }

/* Staff name headers: horizontal, bottom-aligned to match other thead cells. */
.psm thead th.staff { vertical-align: bottom; padding: .35rem .2rem; }
.psm thead th.staff .lbl { display: block; transform: none; white-space: normal;
  width: auto; margin: 0; line-height: 1.15; font-size: 12px; font-weight: 700; }
/* IC month toggle buttons in the header, row 1; view-mode buttons (Focus/Fade/Rotation), row 2. */
.psm .ic-head { display: flex; flex-direction: column; gap: .25rem; align-items: center; }
.psm .ic-months { display: flex; gap: .15rem; }
.psm .ic-modes { display: flex; gap: .15rem; }
/* Each pill row takes the type size of the thead label it sits beside, so the block reads as part of
   the header rather than a separate widget: months = the `Timing` label (13.5px), modes = a staff
   header (12px). Sized to fit inside the 160px IC column — the month row measures ~147px. */
.psm .mbtn { font: 800 12px inherit; padding: .1rem .3rem; border-radius: 5px; letter-spacing: 0;
  border: none; background: rgba(255,255,255,.15); color: #fff; cursor: pointer; }
.psm .ic-months .mbtn { font-size: 13.5px; }
.psm .mbtn:hover { background: rgba(255,255,255,.28); }
.psm .mbtn.active { background: #fff; color: var(--navy); }

/* IC pills — reuse the shared .inis/.avi (DESIGN_SPEC §2), never a page-local variant. Three view
   modes, pure CSS off table.psm[data-icmode]:
     FOCUS (default) — everyone grey, the followed person's .avi turns navy (green is NOT used here,
       it means "signed off" elsewhere in the app — navy = "this is the person I'm looking at").
     FADE — Focus, plus every routine that person ISN'T on drops to low opacity (the "my routines" view).
     ROTATION — swaps each position slot's initials pill for its position-name pill (.rotname), so the
       column reads "UV  Laser  SH" instead of who currently holds them.
   Click a th.staff to follow that person (defaults to the acting staffer); click it again to return. */
.psm td.mx-ic .inis { gap: .3rem; row-gap: .22rem; }
.icslot { display: inline-flex; align-items: center; gap: .1rem; cursor: default; }
.mx-ic .none { color: var(--faint); }
.avi .pairsep { font-size: .62rem; font-weight: 700; opacity: .5; margin: 0 .1rem; }
.avi.q { color: var(--faint); }
/* padding 5px, not 7 (2026-07-23b): four codes have to fit the 160px IC column on ONE line, and the
   column is NOT allowed to grow — UV · L · PkA · PkB measures ~146px at 7px padding against a 140px
   content box, ~130px at 5px. The 16px this frees is what makes the row a one-liner. */
.rotname { display: none; align-items: center; height: 20px; padding: 0 5px; border-radius: 999px;
  font-size: .7rem; font-weight: 800; white-space: nowrap; background: var(--upcoming-bg);
  color: var(--upcoming-ink); border: 1px solid var(--upcoming-bd); }
.psm[data-icmode="rot"] .icslot[data-kind="position"] .avi { display: none; }
.psm[data-icmode="rot"] .icslot[data-kind="position"] .rotname { display: inline-flex; }
/* Every mode EXCEPT Rotation highlights the focused person — stated as :not(rot) rather than
   listing focus+fade, so a future person-oriented mode gets the highlight without editing this. */
.psm:not([data-icmode="rot"]) .icslot.hit .avi
  { background: var(--navy); color: #fff; border-color: var(--navy); }
.psm[data-icmode="fade"] tr.task-row.off { opacity: .28; }
.psm tr.task-row { transition: opacity .12s; }
.psm thead th.staff { cursor: pointer; }
.psm thead th.staff:hover .lbl { text-decoration: underline; }
.psm thead th.staff.hit { background: var(--navy-l); box-shadow: inset 0 -3px 0 #fff; }

/* row states */
.psm tr.task-row { cursor: pointer; }
.psm tr.task-row:hover td { background: #f3f7ff; }
.psm tr.task-row:hover td.mx-task, .psm tr.task-row:hover td.mx-drag { background: #f3f7ff; }
.psm tr.task-row:hover td.sk-learnt { background: #e2f5ea; }
.psm tr.task-row:hover td.sk-learning { background: #e3e8ef; }
/* Dragged row: tinted and outlined, not faded — see the .subrow.dragging note. A table row can't carry
   a box-shadow or a radius reliably, so the lift is a top+bottom border on the cells plus the tint. */
.psm tr.task-row.dragging td { background: var(--task-bg);
  border-top: 2px solid var(--task); border-bottom: 2px solid var(--task); }

/* Floating task form over the matrix — centered modal on a dimmed backdrop.
   CENTRED BY `margin:auto` ON THE MODAL, NOT BY `align-items:center` ON THE OVERLAY (2026-07-23b).
   They look identical while the form fits; once it is TALLER than the viewport, `align-items:center`
   overflows it equally in both directions and the part above the scroll origin is unreachable — you
   land mid-form with the routine name, the heading and the ✕ cut off and no way to scroll up to them.
   `flex-start` + `margin:auto` still centres a short modal and pins a tall one to the top. */
.tf-overlay { position: fixed; inset: 0; z-index: 60; background: rgba(20,26,48,.45);
  display: flex; align-items: flex-start; justify-content: center; padding: 3vh 1rem; overflow: auto; }
/* overflow stays visible so the form's floating dropdown panels aren't clipped at the card edge;
   long content scrolls the overlay instead of the modal */
.tf-modal { margin: auto; position: relative; width: min(900px, 100%); background: #fff; border-radius: var(--radius);
  box-shadow: 0 12px 40px rgba(0,0,0,.3); padding: 1.6rem 1.4rem 1.4rem;
  overflow: visible; }
.tf-modal .page-head { margin-bottom: .6rem; }
.tf-body { display: flex; flex-direction: column; align-items: center; }
.tf-body .formcard { width: 100%; }
.tf-close { position: absolute; top: .35rem; right: .45rem; z-index: 2; border: none; background: none; cursor: pointer;
  font-size: 1.5rem; line-height: 1; color: var(--muted); padding: .15rem .35rem; border-radius: 6px; }
.tf-close:hover { color: var(--overdue); background: var(--line); }

/* IC Rotation — horizontal-only borders, clean v6 look. Fills the 1200 .container so the 4 month
   columns are wide enough that a two-word holder name (e.g. "Szu How") never wraps in its dropdown. */
.matrix.ic { width: 100%; }
.matrix.ic td, .matrix.ic th { border: 0; border-bottom: 1px solid var(--line); padding: 7px 10px; font-size: 14px; min-width: 140px; text-align: left; }
.matrix.ic thead th { border-bottom: 1px solid rgba(255,255,255,.18); text-align: center; }
.matrix.ic td.mx-task { background: #eef1f8; color: #27306a; font-size: var(--fs-md); font-weight: 700; white-space: nowrap; }
.matrix.ic th.mx-task, .matrix.ic td.mx-task { min-width: 178px; max-width: 178px; }
.matrix.ic .cm-0 { background: var(--m0-col); }
.matrix.ic thead .cm-0 { background: var(--m0-head); color: var(--m0-ink); }
.matrix.ic .cm-1 { background: var(--m1-col); }
.matrix.ic thead .cm-1 { background: var(--m1-head); color: var(--m1-ink); }

/* Holder picker uses the shared .dsel custom dropdown (§ Dropdown rule) — no native <select> here.
   Plain-text cells (viewer holders, derived group names) get the dropdown's inner inset so they line
   up under the .dsel label above them (dsel border 1px + summary padding .7rem). Scoped to the page
   so the Production/Sales matrix (which also uses .matrix.ic) is untouched. */
/* overflow-wrap: a locked column (below) must not be blown open by a long derived name — wrap it. */
.ic-rot-page .matrix.ic td .muted { display: inline-block; padding-left: calc(.7rem + 1px); overflow-wrap: anywhere; }
/* COLUMN WIDTHS ARE LOCKED — table-layout:fixed (2026-07-21).
   The columns used to move every time anything in them changed: with the default `auto` layout a
   table sizes its columns from their CONTENT, so picking a holder ("—" → "Pei Ling"), or All Packers
   deriving a two-name string ("Guo Rui, Pei Ling"), re-ran the algorithm and nudged EVERY column —
   including the ones you weren't touching. min-width:140px didn't stop it; a minimum only sets the
   floor, the auto algorithm still distributes everything above it.
   `fixed` makes the browser size columns from the first row's declared widths ALONE and never look at
   a cell's content again — so the table is identical whether the month is empty or full, which is the
   actual requirement. Position keeps its 178px; the month columns declare no width, and under `fixed`
   that means they split the remainder EQUALLY — deterministic, and self-adjusting if a 5th month is
   ever shown, so there is no magic number to re-tune. At the 1200 shell that is ~247px per month,
   comfortably more than the longest possible content (the derived pair above, ~17 characters).
   Scoped to .ic-rot-page: the Production/Sales matrix shares .matrix.ic but has many more columns and
   genuinely wants content sizing. */
.ic-rot-page .matrix.ic { table-layout: fixed; }
/* Explicit width, not just the min/max pair above: `fixed` reads `width`, and a column that declares
   none joins the equal-split with the months. */
.ic-rot-page .matrix.ic th.mx-task, .ic-rot-page .matrix.ic td.mx-task { width: 178px; }
/* Group row cell: derived names on top, month clear button on its own line below, right-aligned —
   so the trash reads as "clear the column", not "clear the All-Packers text beside it". */
.ic-rot-page .grp-cell { display: flex; flex-direction: column; gap: .3rem; }
.ic-rot-page .grp-cell .chip-btn { align-self: flex-end; }

/* The completion popup (.modal / .modal-backdrop) is GONE — a task needing a link, photo or approval is
   submitted on its detail page (DESIGN_SPEC §3). Don't reintroduce the classes; the matrix's routine form
   has its own shell (.tf-overlay / .tf-modal). */

/* Small centered confirm dialog — the sanctioned pattern for a one-off yes/no/choice prompt (distinct
   from the .tf-overlay/.tf-modal routine-form shell, which is a full form, not a prompt). Reuses .btn /
   .btn-primary for its actions — never invent a bespoke button style inside it. */
.cdlg { position: fixed; inset: 0; z-index: 70; background: rgba(20,26,48,.45);
  display: flex; align-items: center; justify-content: center; padding: 1rem; }
/* position:relative anchors the shared .tf-close ✕ (same one the routine modal uses). */
.cdlg-card { position: relative; width: min(360px, 100%); background: var(--card); border-radius: var(--radius-lg);
  box-shadow: var(--shadow); padding: 1.4rem; }
.cdlg-card h2 { font-size: var(--fs-lg); margin: 0 0 .3rem; padding-right: 1.6rem; }
.cdlg-card .help { margin: 0 0 1rem; }
.cdlg-actions { display: flex; gap: .5rem; }
.cdlg-actions .btn { flex: 1; }   /* .btn already centres its content */

/* Board row where the acting staffer has already done their part */
.tk.mine-done > a { color: var(--muted); text-decoration: line-through; }

/* ══════════════════════════════════════════════════════════════════════════
   v10 My Board — category panels, attention bar, reference list (DESIGN_SPEC §3)
   ══════════════════════════════════════════════════════════════════════════ */
.svg-ico { width: 1.05em; height: 1.05em; vertical-align: -.16em; stroke: currentColor;
  stroke-width: 2; fill: none; stroke-linecap: round; stroke-linejoin: round; flex: none; }

/* Borderless icon button — muted glyph, navy on hover; `.chip-delete` reddens instead.
   Shared by the Stations chip controls and the IC-rotation month clear. */
.chip-btn { background: none; border: none; cursor: pointer; padding: 0 .1rem; line-height: 1;
  color: var(--muted); display: inline-flex; align-items: center; }
.chip-btn .svg-ico { width: 13px; height: 13px; }
.chip-btn:hover { color: var(--navy); }
.chip-btn.chip-delete:hover { color: var(--overdue); }

/* segmented control (My board | Production | Sales) — rendered as links */
.seg { display: inline-flex; border: 1px solid var(--line-2); border-radius: 8px; overflow: hidden; background: #fff; }
.seg a { border: none; background: #fff; color: var(--ink-2); font: inherit; font-size: var(--fs-sm);
  font-weight: 600; padding: .4rem .8rem; cursor: pointer; }
.seg a.on { background: var(--navy); color: #fff; }
/* A mandatory segmented control submitted with nothing picked (e.g. the task form's Task type). */
.seg.missing { border-color: var(--overdue); box-shadow: 0 0 0 3px var(--overdue-bg); }
.seg a:hover { text-decoration: none; }

/* quiet link sitting beside the segmented control (e.g. "My completed") */
.mb-link { font-size: var(--fs-sm); color: var(--muted); align-self: center; font-style: italic; }
.attn { align-items: flex-end; }
.attn .mb-link { margin-left: auto; align-self: flex-end; }
.mb-link:hover { color: var(--navy); text-decoration: underline; }

/* attention bar */
.attn { display: flex; gap: .6rem; flex-wrap: wrap; margin-bottom: 1.1rem; }
/* display:contents — the stats sit directly in .attn's flex, exactly as before the wrapper existed.
   The wrapper only becomes a real box on mobile, where it turns into a single band. */
.attn .astats { display: contents; }
.astat { display: inline-flex; align-items: center; gap: .55rem; padding: .5rem .85rem;
  border-radius: var(--radius); border: 1px solid var(--line); background: var(--card); box-shadow: var(--shadow-sm);
  font-size: var(--fs-md); color: var(--ink-2); font-weight: 600; }
.astat .ai { width: 28px; height: 28px; border-radius: 8px; display: grid; place-items: center; color: #fff; flex: none; }
.astat .ai .svg-ico { width: 16px; height: 16px; }
.astat .an { font-weight: 800; font-size: var(--fs-lg); letter-spacing: -.01em; }
.astat.s-overdue .ai { background: var(--overdue); }
.astat.s-upcoming .ai { background: var(--upcoming); }
.astat.s-approve .ai { background: var(--approval); }

/* Shared 3-column panel grid — the one grid every panel board uses (My Board, My Created,
   My Completed). One class, one set of breakpoints, so the pages collapse in step. */
.panel-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.1rem; align-items: start; }
@media (max-width: 1200px) { .panel-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 900px) { .panel-grid { grid-template-columns: 1fr; } }

/* category panels — 3-col: Tasks · Routines · Other Routines (reference) */
.group { border: 1px solid var(--line); border-radius: var(--radius-lg); overflow: hidden;
  box-shadow: var(--shadow-sm); background: #fff; }
/* min-height keeps all three panel headers the same height whatever they carry */
.group > .gh { padding: .5rem .9rem; min-height: 46px; display: flex; align-items: center; justify-content: space-between; gap: .5rem; color: #fff; }
.group.routine > .gh, .group.reference > .gh { background: var(--routine); }
.group.task > .gh { background: var(--task); }
.group .gh .gt { display: flex; align-items: center; gap: .45rem; font-weight: 700;
  font-size: var(--fs-sm); text-transform: uppercase; letter-spacing: .04em; }
.group .gh .gt .svg-ico { width: 16px; height: 16px; }
.group .gh .gh-right { display: flex; align-items: center; gap: .5rem; flex: none; }
/* Count pill that rides immediately after a heading on a coloured band — the Routines/Tasks panel
   headers on My Board and the station band on the Production/Sales board use the same one. */
.gc { flex: none; background: rgba(255,255,255,.24); color: #fff; font-weight: 800;
  padding: .05rem .55rem; border-radius: 999px; font-size: var(--fs-xs); line-height: 1.4; }
.group .gh .gh-add { display: inline-flex; align-items: center; gap: .3rem; font-size: var(--fs-xs);
  font-weight: 700; color: #fff; background: rgba(255,255,255,.2); border: 1px solid rgba(255,255,255,.4);
  border-radius: 6px; padding: .2rem .5rem; }
.group .gh .gh-add:hover { background: rgba(255,255,255,.34); text-decoration: none; }
.group .gh .gh-add .svg-ico { width: 13px; height: 13px; }
.group .stack { padding: .75rem; display: flex; flex-direction: column; gap: .6rem; }
.group.routine .stack { background: var(--routine-bg); }
.group.task .stack { background: var(--task-bg); }
.group .gh .gsub { font-style: italic; font-weight: 500; text-transform: none; letter-spacing: 0;
  opacity: .8; margin-left: .15rem; }
.group.reference .stack { background: var(--routine-bg); gap: .2rem; }
.group.reference .refgroup { margin: 0 0 .7rem; }
/* One empty state everywhere ("Nothing here! 🎉") — inside a panel and standalone, desktop and
   mobile. It used to be scoped to `.group .stack`, so the standalone uses rendered unstyled. */
.empty-panel { color: var(--muted); font-size: var(--fs-sm); text-align: center; padding: .8rem .5rem; }

/* card — LOCKED (DESIGN_SPEC §3 "My Board"). This is the reference card for the whole app: every
   page that lists tasks (My Completed, My Created, Archived, …) uses THESE classes and
   this anatomy — line 1 = tick + title + right-pinned .due (title wraps in its own column, never
   under the badge); line 2 = .t2 > .meta with the .typetag pill (count only when Individual) +
   .reqs icons + right-pinned .inis initials (or .stbadge on a pending card). Don't fork a per-page
   variant of any of it, and don't restyle it as a side effect of another page's work. */
.tc { background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
  padding: .7rem .8rem; display: flex; flex-direction: column; gap: .45rem; box-shadow: var(--shadow-sm); }
/* Title row is TOP-aligned: a title that wraps to two lines keeps its first line level with the
   checkbox, the requirement icons and the due badge (DESIGN_SPEC §3). */
.tc .ttl { font-weight: 600; font-size: var(--fs-md); color: var(--ink);
  display: flex; align-items: flex-start; gap: .5rem; }
.tc .ttl a { color: inherit; overflow-wrap: anywhere; }
.tc .ttl a:hover { text-decoration: underline; }
/* Line 1: [tick] title ……… [due]. ONLY the title and the right-pinned due badge live here
   (agreed 2026-07-16): the title gets its own flex column, so every wrapped line breaks before
   the badge — it never runs underneath it. The requirement icons moved to line 2 (.meta .reqs),
   where they describe the task next to its type pill. */
.tc .ttl .due { flex: none; margin: 1px 0 0 auto; }
/* The tick takes its panel's category colour: cyan in Tasks, slate in Routines (DESIGN_SPEC §3). */
.tc .done-check { flex: none; width: 18px; height: 18px; cursor: pointer; accent-color: var(--navy); margin: 1px 0 0; }
.group.task .tc .done-check { accent-color: var(--task); }
.group.routine .tc .done-check { accent-color: var(--routine); }
.tc.mine-done .ttl a, .tc.done .ttl a { color: var(--muted); text-decoration: line-through; }
/* Ticked / awaiting-approval cards sink to the bottom of the panel and read as finished, not urgent. */
.tc.sunk { opacity: .62; }
.tc.sunk .ttl { color: var(--muted); }
/* Struck-through title text — a card whose work is finished (My Completed). */
.tc .strike { flex: 0 1 auto; min-width: 0; color: var(--muted); text-decoration: line-through;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Completed / archived date pinned top-right of the title line — same slot AND same pill geometry as
   the .due badge, so dates line up card-to-card across every page (DESIGN_SPEC §3). White fill +
   --line border = a fact (when it was done); a COLOURED fill (.due.od/.soon) = urgency. It carries a
   leading ✓ (#i-check). `.done` = green ink, used where the card sits among unfinished work and the
   green says "this one is finished" (My Created's Done panel); plain = muted, used where everything on
   the page is already finished (My Created Archived, My Completed) and green would be noise. */
.tc .adate { margin-left: auto; flex: none; font-size: var(--fs-xs); font-weight: 700;
  color: var(--muted); background: #fff; border: 1px solid var(--line); border-radius: 999px;
  padding: .1rem .5rem; white-space: nowrap; display: inline-flex; align-items: center; gap: .25rem; }
.tc .adate .svg-ico { width: 12px; height: 12px; }
.tc .adate.done { color: var(--done); }
/* ── THE CHECKBOX (.box) — one box everywhere ───────────────────────────────────────────────
   Every tickable box in the product renders as this: an 18px rounded square, EMPTY when unchecked,
   filled with a white tick when checked, grey when checked-but-inert. Checked and unchecked differ
   in COLOUR ONLY — same size, same radius, same position — so a half-done checklist reads as one
   column of boxes, not a ragged mix.

   The native <input type=checkbox> is ALWAYS hidden and never styled with accent-color: the browser
   draws its own size, corner radius, tick glyph and baseline, which differ per OS and never line up
   with ours. (That drift is exactly how the subtask lists ended up with three different boxes.)

   Two containers wrap it — the box itself is defined once:
     .chk     = a bare label around the box (task detail, My Completed's undo, the breakdown row)
     .subitem = a whole clickable checklist row, the box being its first child (board cards)
   A context may override ONE thing, the ON colour, via --box-on (e.g. per-group tick colours). */
.chk-form { margin: 0; flex: none; }
.chk { position: relative; display: block; width: var(--box-size); height: var(--box-size);
  cursor: pointer; flex: none; }
.chk input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
.box { width: var(--box-size); height: var(--box-size); border-radius: 5px;
  border: 1.5px solid var(--line-2); display: grid; place-items: center; flex: none;
  color: #fff; background: var(--card); }
.box .svg-ico { width: 12px; height: 12px; opacity: 0; }
input:checked + .box, .subitem.done .box { background: var(--box-on); border-color: var(--box-on); }
input:checked + .box .svg-ico, .subitem.done .box .svg-ico { opacity: 1; }
/* Done, but not reversible from here: an ARCHIVED task's tick would silently snap back
   (TaskService::reopenOwn refuses an archived row — restore it on /my-created/archived first), so
   render it inert and grey rather than let it fail. Same precedent as the board's un-reversible
   routine row, .ro-row .done-check:disabled. Also the read-only subtask list on task detail (a
   viewer who is neither assignee, creator nor management): grey = "done, and not yours to undo".
   An UNCHECKED disabled box stays EMPTY — a grey fill there would read as done. */
input:disabled { cursor: not-allowed; }
input:disabled:checked + .box { background: var(--muted); border-color: var(--muted); }
/* Card footer strip — station pill left, status badge right. */
.tc .foot { display: flex; align-items: center; gap: .5rem; padding-top: .4rem; border-top: 1px dashed var(--line); }
.tc .station { font-size: var(--fs-xs); font-weight: 600; color: var(--muted); background: var(--bg);
  border: 1px solid var(--line-2); border-radius: 999px; padding: .05rem .5rem; }
.tc .pend { margin-left: auto; flex: none; font-size: .68rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .03em; color: var(--approval-ink); background: var(--approval-bg);
  border: 1px solid var(--approval-bd); padding: .08rem .45rem; border-radius: 999px; }
/* Frequency pill on a routine card (slate, mirrors the Routines panel colour). */
.tc .rmeta { display: inline-flex; align-items: center; gap: .3rem; flex: none; font-size: var(--fs-xs);
  font-weight: 700; color: var(--routine-ink); background: var(--routine-bg); border: 1px solid var(--routine-bd);
  padding: .12rem .5rem; border-radius: 999px; }
.tc .rmeta .svg-ico { width: 12px; height: 12px; }
.tc .meta { display: flex; flex-wrap: wrap; gap: .35rem .6rem; font-size: var(--fs-xs); color: var(--muted); align-items: center; }
.tc .meta .m { display: inline-flex; align-items: center; gap: .28rem; }
.tc .meta .m .svg-ico { width: 13px; height: 13px; }
/* Line 2 anatomy (agreed 2026-07-16, one rule for every task card): [type pill] [req icons] … then
   the initials — or the Pending badge on My Board's pending card, which replaces them (Option C) —
   PINNED TO THE RIGHT EDGE, the same edge the .subitem-bd breakdown rows pin theirs to, so initials
   sit in one place on every card variant. The meta spans the row to give the pin something to push
   against. */
.tc .t2 .meta { flex: 1 1 auto; }
.tc .meta .reqs { display: inline-flex; align-items: center; gap: .35rem; flex: none; }
.tc .t2 .meta .inis, .tc .t2 .meta .stbadge { margin-left: auto; }
/* Line 2 (and the checklist below it) aligns under the TITLE, so it clears the card's checkbox — but
   only on cards that HAVE one, and only when that checkbox is on LINE 1. The test is scoped to
   `> .ttl` for exactly that reason: My Created has no line-1 checkbox, but its per-subtask breakdown
   rows each carry an inert `.chk`, so a bare `:has(.chk)` matched deep inside .subs and indented the
   whole card under empty space. */
.tc .t2 { display: flex; justify-content: space-between; align-items: center; gap: .5rem; min-height: 20px; padding-left: 0; }
.tc:has(> .ttl .done-check) .t2, .tc:has(> .ttl .chk) .t2,
.tc:has(> .ttl .done-check) .subs, .tc:has(> .ttl .chk) .subs { padding-left: calc(var(--box-size) + .5rem); }
.due { font-size: var(--fs-xs); font-weight: 700; padding: .1rem .5rem; border-radius: 999px; white-space: nowrap; }
.due.od { color: #fff; background: var(--overdue); }
.due.soon { color: var(--upcoming); background: var(--upcoming-bg); border: 1px solid var(--upcoming-bd); }
.due.ok { color: var(--muted); background: var(--bg); border: 1px solid var(--line); }
.stbadge { display: inline-flex; align-items: center; gap: .3rem; font-size: var(--fs-xs); font-weight: 700;
  padding: .1rem .45rem; border-radius: 999px; border: 1px solid; text-transform: uppercase; letter-spacing: .03em; }
.stbadge.pending { color: var(--approval-ink); background: var(--approval-bg); border-color: var(--approval-bd); }
.stbadge .svg-ico { width: 12px; height: 12px; }

/* subtask checklist shown on a task card (routine cards don't carry one) */
/* padding-left comes from the checkbox-indent rule above (.tc:has(> .ttl .chk)) — a card without a
   line-1 checkbox keeps its checklist flush with the title. */
.tc .subs { display: flex; flex-direction: column; gap: .35rem; border-top: 1px dashed var(--line); padding-top: .5rem; }
/* The box-only hit target: the box lives in a nested .chk label, the title is a sibling text node, so
   only the box toggles (an easy mis-tap on the title does nothing — matches task detail). Its box is
   the shared .box component above — do NOT re-declare the box here, that fork is what made the card's
   checklist 16px while the task page's was 18px. */
/* A plain checklist row, on a board card AND on task detail. `.tdv .subitem` used to live in
   views/tasks/show.php's page <style>; it is here now because a page <style> loads AFTER app.css, so
   its `display:flex` beat `.subitem.subitem-bd`'s `display:grid` on an equal-specificity tie — which
   silently turned the whole breakdown row back into a flex row and made every grid rule written for it
   inert (`.bd-stack` did nothing at all). Shared classes live in app.css precisely so the cascade is
   decided in one file. Only the gap differs between the two surfaces. */
.tc .subitem, .tdv .subitem { display: flex; align-items: center; font-size: var(--fs-sm); color: var(--ink-2); }
.tc .subitem { gap: .45rem; }
.tdv .subitem { gap: .55rem; }
/* Board card strikes the ROW; task detail strikes the row's text SPAN only — kept as it was, because on
   task detail the row also holds the .box, and line-through on the row draws through the checkbox. */
.tc .subitem.done { color: var(--faint); text-decoration: line-through; }
/* The TITLE strikes, not everything in the row. `:not(.inis)` matters now that an assignee's own
   (tickable) row also carries the initials column: a bare `span` selector struck their colleagues'
   initials through as well the moment the assignee ticked the box. Direct children only — the title is
   a bare <span> on a plain row and `.sbtitle` on a breakdown row, and both are direct children. */
.tdv .subitem.done > span:not(.inis) { color: var(--faint); text-decoration: line-through; }

/* reference list — daily / non-belled weekly routines, read-only (Other Routines panel, My Board) */
.refgroup { margin: 0 0 .8rem; break-inside: avoid; }
.refgroup .rgh { font-weight: 700; color: var(--ink); font-size: var(--fs-md); margin: .2rem 0 .3rem; }
.refgroup .rgsub { font-weight: 700; color: var(--ink-2); font-size: var(--fs-sm); margin: .4rem 0 .2rem; }
.refgroup ul { list-style: none; margin: 0; padding: 0; }
.refgroup li { font-size: var(--fs-sm); color: var(--ink-2); padding: .1rem 0 .1rem .95rem; position: relative; }
.refgroup li::before { content: ""; position: absolute; left: .15rem; top: .72em; width: 4px; height: 4px;
  border-radius: 50%; background: var(--faint); }


/* ── Completable row .ro-row (LOCKED, shared — DESIGN_SPEC §3) ────────────────────────────────────
   One line of work inside a station card: [tick] title … [assignee initials] [due badge].
   TOP-aligned like the My Board card title row (§3) — a title wrapping to two lines keeps its first
   line level with the checkbox and the right-hand pill. Not scoped to any page shell: reuse as-is. */
.ro-row { display: grid; grid-template-columns: 18px minmax(0,1fr) 72px 84px; align-items: start; gap: .5rem;
  padding: .3rem 0; border-bottom: 1px dashed var(--line); font-size: var(--fs-sm); }
.ro-row:last-child { border-bottom: none; }
.ro-row .done-check { width: 18px; height: 18px; margin-top: 2px; cursor: pointer; accent-color: var(--routine); }
/* Ticked but not yours to reverse (TaskService::canUntick) — inert rather than silently snapping back. */
.ro-row .done-check:disabled { cursor: not-allowed; }
.ro-title { color: var(--ink); line-height: 1.35; overflow-wrap: anywhere; }
/* Initials here are the SHARED .inis/.avi pair (2026-07-23) — the old `.ro-ini` 20px circle was a
   second initials component for the same job and has been deleted. Grey .avi = whose routine it is;
   green .avi.done = who completed it, which is exactly what .done already means on every board card.
   Only the grid placement is local; the pill itself is never restyled here. */
.ro-row .inis { justify-self: start; }
/* The row's 4th slot, whatever it holds — a .due badge, the green completer pill, or an empty span
   on an undated row. Pinned by the .ro-tail class, never by child position. */
.ro-row > .ro-tail { justify-self: end; }
.ro-row.sunk { opacity: .62; }
.ro-row.done .ro-title { color: var(--muted); text-decoration: line-through; }

/* Routine activity log */
.rlog { display: flex; flex-direction: column; gap: 0; }
.rlog .rlog-row { padding: .5rem 0; border-bottom: 1px solid var(--line); font-size: var(--fs-sm); }
.rlog .rlog-row:last-child { border-bottom: none; }
/* center, not baseline: the right side is the board's .avi initials pills (grey IC + green
   completer), which have no useful text baseline to align the date against. */
.rlog .rlog-top { display: flex; justify-content: space-between; gap: .6rem; align-items: center; }
.rlog .rlog-when { font-size: var(--fs-xs); color: var(--muted); white-space: nowrap; }
.rlog .rlog-meta { font-size: var(--fs-xs); color: var(--muted); margin-top: .15rem; }
.rlog .rlog-note { font-size: var(--fs-xs); color: var(--ink-2); margin-top: .2rem; font-style: italic; }

/* Routine activity log — a narrow popover anchored to the matrix's sticky-note icon (not a modal:
   the full-width modal was unreadable for a two-column list). Grows downward as the list grows,
   scrolls once it gets tall. */
.rlog-pop { position: absolute; z-index: 70; width: 320px; max-height: 60vh; overflow: auto;
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 10px 30px rgba(20,26,48,.22); padding: .7rem .85rem; }
.rlog-pop h3 { margin: 0 1.6rem .35rem 0; font-size: var(--fs-sm); line-height: 1.3;
  overflow-wrap: normal; word-break: normal; }
.rlog-pop .help { margin: 0; }
.rlog-pop .rlog-note { overflow-wrap: normal; word-break: normal; }
.rlog-pop .rlog-close { position: absolute; top: .25rem; right: .35rem; border: none; background: none;
  cursor: pointer; font-size: 1.1rem; line-height: 1; color: var(--muted); padding: .1rem .25rem; }
.rlog-pop .rlog-close:hover { color: var(--overdue); }

/* ═══ v10 forms ═══ */
/* Shared by views/task_form.php (teal --task accent, full page) and views/routine_form.php
   (slate --routine accent, modal fragment). Ports the .formcard look from mockups/v10_*.html. */

.formwrap { max-width: 820px; margin: 0 auto; }

/* ── THE ONE TOP ACCENT — declared ONCE, for the WHOLE project ────────────────────────────────────
   Any card that wants the 4px category accent adds the class `.accent-top` and sets its colour via the
   `--accent-c` custom property (default navy). This ONE rule is the single source of truth — forms
   (`.formcard`) and task-detail cards (`.tdv .card.lead/.acts`) both opt into it; there is no second
   accent rule anywhere, and no `<div class="accent">` bar.
   The selector names both hosts on purpose: the accent is a border-top, and each card's BASE `border:`
   shorthand also sets the top edge, so the accent rule must out-specify it — `.formcard` is 1 class and
   `.tdv .card` is 2, so a bare `.accent-top` (1 class) would LOSE to `.tdv .card` and silently vanish.
   Qualifying by host wins deterministically without `!important` (which this project never uses).
   Why a top BORDER, not a separate bar: a 4px-tall bar's own `border-radius:14px` is clamped by the
   browser to ~its 4px height, so its corners come out tighter than the card's 14px corners and notch
   inside them (the "clipped"/square look). A top *border* follows the card's own 14px radius, so the
   accent corners match the card's — and it needs no `overflow:hidden`, so it works on the forms too
   (they must stay overflow:visible for the floating date-picker / multi-select to spill past the card).
   COLOUR = category, never decoration: task = --task cyan, routine = --routine slate, and EVERY
   config/admin page (staff, station, calendar-sync) = corporate --navy. Cyan is ONLY ever a task,
   slate ONLY ever a routine — a config page never borrows either. There is no accent-less form. */
.formcard.accent-top, .tdv .card.accent-top { border-top: 4px solid var(--accent-c, var(--navy)); }
.formcard { background: #fff; border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); overflow: visible; }
.formcard.task-form { --accent-c: var(--task); }
.formcard.routine-form { --accent-c: var(--routine); }
/* .formcard.admin-form / .cal-form leave --accent-c unset, so the accent falls back to --navy. */
.formbody { padding: 1.4rem 1.5rem; display: flex; flex-direction: column; gap: 1.25rem; }

.field { display: flex; flex-direction: column; gap: .4rem; }
.field > label { font-weight: 700; font-size: var(--fs-md); color: var(--ink-2); }
.formcard .hint { font-size: var(--fs-xs); color: var(--muted); }
.req { color: var(--overdue); font-weight: 700; }
.row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; align-items: start; }
@media (max-width: 640px) { .row2 { grid-template-columns: 1fr; } }
/* Title + Station row (routine form): Title gets more room than Station. */
.row2.titlerow { grid-template-columns: 1.7fr 1fr; }
/* task-type (fixed-width seg) on the left, a moderate Assign-to field on the right with a wide gap */
.row2.row2-assign { grid-template-columns: 300px minmax(0, 1fr); gap: 2.75rem; }
@media (max-width: 640px) { .row2.row2-assign { grid-template-columns: 1fr; gap: 1.25rem; } }
@media (max-width: 640px) { .row2.titlerow { grid-template-columns: 1fr; } }

/* Darker, more legible control borders + v10 padding, scoped to the new forms only. */
/* .actform = the completion form on task detail (submit / mark complete). It is NOT a second form
   shell — it just hosts the same field controls, so the inputs there match the task form instead of
   rendering as browser defaults. Add it to these selectors; never re-declare them per page. */
/* There is deliberately NO input[type=date] in these lists: the project has no native date control.
   Every date field is the .dpick month-grid picker below (§5), because a date field here must grey
   out weekends and closed days and a native control cannot. */
.formcard input[type=text], .formcard textarea, .formcard select,
.actform input[type=text], .actform input[type=url], .actform input[type=file], .actform textarea,
.cal-urlrow input {
  border-color: var(--line-2); border-radius: 8px; padding: .55rem .7rem; font-size: var(--fs-md); }
.formcard textarea, .actform textarea { resize: vertical; min-height: 78px; }
.actform { display: flex; flex-direction: column; gap: 1rem; }
.actform input[type=file] { border: 1px solid var(--line-2); background: #fff; width: 100%; }
/* One prompt colour. A placeholder and the file input's browser-drawn "No file chosen" say the same
   thing — "nothing here yet" — so they must not be two different greys. `color` on the file input is
   what tints that text; the OS-drawn Choose-Files button keeps its own. */
.formcard input::placeholder, .formcard textarea::placeholder,
.actform input::placeholder, .actform textarea::placeholder,
.actform input[type=file] { color: var(--faint); opacity: 1; }
.actform .btn-primary { width: 100%; background: var(--task); border-color: var(--task); }
.actform .btn-primary:hover { background: var(--task-ink); border-color: var(--task-ink); }
/* .btnrow — the completion form's two-button row, shown only when the task requires proof: a plain
   "Save proof" secondary next to the primary Complete/Submit, so proof can be attached early and the
   task completed later. Overrides .btn-primary's 100% width so the two sit evenly side by side. */
.actform .btnrow { display: flex; gap: .6rem; }
.actform .btnrow .btn { flex: 1 1 0; width: auto; }
@media (max-width: 480px) { .actform .btnrow { flex-direction: column; } }
/* Standardize control height so every field matches the Assign-to control (even when empty). */
.formcard input[type=text], .formcard select, .msel > summary, .dsel > summary, .dpick > summary, .formcard .seg,
.actform input[type=text], .actform input[type=url], .actform input[type=file],
.cal-urlrow input { min-height: 44px; }
.formcard input[type=text]:focus, .formcard textarea:focus, .formcard select:focus,
.actform input:focus, .actform textarea:focus {
  outline: none; border-color: var(--task); box-shadow: 0 0 0 3px rgba(14,124,134,.14); }
.formcard.routine-form input[type=text]:focus { border-color: var(--routine); box-shadow: 0 0 0 3px rgba(100,116,139,.18); }

/* segmented type toggle (task form) — reuses the existing .seg flex container, adds button children */
.formcard .seg { align-self: flex-start; width: 300px; max-width: 100%; }
.formcard .seg .segbtn { flex: 1 1 0; display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
  border: none; background: #fff; color: var(--ink-2); font: inherit; font-size: var(--fs-sm);
  line-height: 1; font-weight: 600; padding: .55rem 1rem; cursor: pointer; }
.formcard .seg .segbtn .svg-ico { width: 15px; height: 15px; }
.formcard .seg .segbtn.on { background: var(--task); color: #fff; }

/* multi-select dropdown (Assign to / Timing) */
.msel { border: 1px solid var(--line-2); border-radius: 8px; background: #fff; position: relative; }
.msel[open] { border-color: var(--task); }
.routine-form .msel[open] { border-color: var(--routine); }
.msel > summary { list-style: none; cursor: pointer; padding: .55rem .6rem; display: flex; align-items: center; gap: .4rem; }
.msel > summary::-webkit-details-marker { display: none; }
.msel .val { flex: 1; font-size: var(--fs-md); color: var(--ink); }
.selchips { display: flex; flex-wrap: wrap; gap: .35rem; flex: 1; }
.mchip { display: inline-flex; align-items: center; gap: .35rem; padding: .2rem .3rem .2rem .5rem; border-radius: 999px;
  background: var(--task-bg); border: 1px solid var(--task-bd); color: var(--task-ink); font-size: var(--fs-sm); font-weight: 700; }
.mchip .x { display: grid; place-items: center; width: 15px; height: 15px; border-radius: 50%; color: var(--task-ink); opacity: .7; cursor: pointer; }
.mchip .x .svg-ico { width: 11px; height: 11px; }
.msel .ph { color: var(--muted); font-size: var(--fs-md); }
.msel .cv { color: var(--muted); transition: transform .15s; }
.msel[open] .cv { transform: rotate(180deg); }
.mpanel { position: absolute; left: 0; right: 0; top: calc(100% + 3px); z-index: 30; background: #fff; border: 1px solid var(--task); border-radius: 8px; box-shadow: 0 6px 18px rgba(20,30,60,.16); padding: .4rem; display: flex; flex-direction: column; gap: .05rem; max-height: 230px; overflow: auto;
  scrollbar-width: auto; scrollbar-color: var(--faint) transparent; }
/* Visible thumb, but track inset so it stays clear of the rounded corners. */
.mpanel::-webkit-scrollbar { width: 16px; }
.mpanel::-webkit-scrollbar-track { background: transparent; margin: 8px 0; }
.mpanel::-webkit-scrollbar-thumb { background: var(--faint); border-radius: 999px; border: 4px solid #fff; background-clip: padding-box; }
.routine-form .mpanel { border-color: var(--routine); }   /* slate on the routine form, cyan on the task form */
.mpanel .sepr { border-top: 1px solid var(--line-2); margin: .35rem .25rem; }
.mopt { display: flex; align-items: center; gap: .55rem; padding: .4rem .5rem; border-radius: 6px; font-size: var(--fs-sm); color: var(--ink-2); cursor: pointer; }
.mopt:hover { background: var(--bg); }
.mopt input { width: 16px; height: 16px; accent-color: var(--task); }
.routine-form .mopt input { accent-color: var(--routine); }
.mopt.sel { color: var(--task-ink); font-weight: 600; }
.routine-form .mopt.sel { color: var(--routine-ink); }
/* Assign-to panel: 2-column compact grid of names; group separators span both columns. */
#assignee-msel .mpanel { display: grid; grid-template-columns: 1fr 1fr; gap: .05rem .4rem; align-content: start; }
#assignee-msel .mpanel .sepr { grid-column: 1 / -1; }

/* single-select custom dropdown (IC slots, Frequency — routine form only) */
/* Accent: the .dsel was a routine-form-only control until the task form's Approver picker joined it
   (2026-07-21), so its open border / tick / selected row were hard-coded slate. They now come from
   --dsel-c, which DEFAULTS to slate — keeping every existing host (routine form, IC Rotation, the
   topbar Acting-as picker) exactly as it was — and flips to task cyan on the task form, where every
   other control (the .msel above it, the segmented toggle, the focus ring) is already cyan. */
.dsel, .dpanel { --dsel-c: var(--routine); --dsel-ink: var(--routine-ink); }
.task-form .dsel, .task-form .dpanel { --dsel-c: var(--task); --dsel-ink: var(--task-ink); }
.dsel { border: 1px solid var(--line-2); border-radius: 8px; background: #fff; position: relative; }
.dsel[open] { border-color: var(--dsel-c); }
.dsel > summary { list-style: none; cursor: pointer; padding: .55rem .7rem; display: flex; align-items: center; gap: .4rem; }
.dsel > summary::-webkit-details-marker { display: none; }
.dsel .val { flex: 1; font-size: var(--fs-md); color: var(--ink); }
.dsel .val.ph, .msel .val.ph { color: var(--muted); }   /* unset Frequency / Timing on a new routine */
.dsel .cv { color: var(--muted); transition: transform .15s; }
.dsel[open] .cv { transform: rotate(180deg); }
/* Floats over the page (like .mpanel) instead of pushing the modal taller. */
.dpanel { position: absolute; left: 0; right: 0; top: calc(100% + 3px); z-index: 30; background: #fff;
  border: 1px solid var(--dsel-c); border-radius: 8px; box-shadow: 0 6px 18px rgba(20,30,60,.16);
  padding: .4rem; display: flex; flex-direction: column; gap: .05rem; max-height: 260px; overflow: auto;
  scrollbar-width: auto; scrollbar-color: var(--faint) transparent; }
.dpanel::-webkit-scrollbar { width: 16px; }
.dpanel::-webkit-scrollbar-track { background: transparent; margin: 8px 0; }
.dpanel::-webkit-scrollbar-thumb { background: var(--faint); border-radius: 999px; border: 4px solid #fff; background-clip: padding-box; }
.dpanel .sepr { border-top: 1px solid var(--line-2); margin: .35rem .25rem; }
.dpanel .glabel { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--faint); padding: .2rem .5rem .1rem; }
.dpanel .dopt.dsub { padding-left: 1.1rem; }
.dopt { display: flex; align-items: center; gap: .5rem; padding: .4rem .5rem; border-radius: 6px; font-size: var(--fs-sm); color: var(--ink-2); cursor: pointer; }
.dopt:hover { background: var(--bg); }
.dopt.sel { color: var(--dsel-ink); font-weight: 700; background: #eef1f6; }
.dopt .ck { margin-left: auto; color: var(--dsel-c); opacity: 0; }
.dopt.sel .ck { opacity: 1; }
.icgrid .dsel > summary { padding: .45rem .5rem; }

/* Timing panel: each kind is its own compact grid. The three schedule groups (week, weekday, months)
   share ONE 4-column rhythm so their cells line up down the panel; the two 2-up groups land on every
   other gridline of it. The phone layer at the foot of this file drops all three to 3-up together —
   they must move as a set, or the alignment this buys is lost.
   Every label is 3 characters (Wk1–Wk4, Mon–Fri, Jan–Dec) for exactly this reason: at 4-up a cell
   holds ~52px of text on desktop, and "Thurs" (~34px) is why Tues/Thurs were shortened. */
#timing-msel .mpanel { max-height: none; }
#timing-msel .tgrp { display: grid; grid-template-columns: 1fr 1fr; gap: .05rem .4rem; align-content: start; }
#timing-msel .tgrp-month { grid-template-columns: repeat(4, 1fr); }
#timing-msel .tgrp-weekday { grid-template-columns: repeat(4, 1fr); }   /* Mon–Fri + "—" in 2 rows */
#timing-msel .tgrp-week_pos { grid-template-columns: repeat(4, 1fr); }  /* Wk1–Wk4 in one row */

/* A timing the chosen cadence can't carry (a month on a daily routine, a weekday before a week has
   been picked). Reuses the project's existing "present but not actionable" name — .tc.sunk and
   .ro-row.sunk — but goes fainter than their .62, because at that strength a dimmed checkbox still
   reads as clickable. input:disabled already supplies the cursor; the box itself must stop lighting
   up on hover or the row keeps inviting the click. */
.mopt.sunk { opacity: .45; }
.mopt.sunk:hover { background: none; }

/* Required-field flag on the custom dropdowns (hidden inputs can't use native validation). */
.dsel.invalid, .msel.invalid { border-color: var(--overdue); box-shadow: 0 0 0 3px var(--overdue-bg); }

/* toggle switch rows (task form completion requirements) — real checkbox, visually hidden,
   with a .sw pill sibling reflecting :checked so the field still submits natively. */
.switchrow { display: flex; align-items: center; gap: .8rem; padding: .65rem .85rem; border: 1px solid var(--line-2);
  border-radius: var(--radius); background: var(--bg); cursor: pointer; }
.switchrow input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.switchrow .txt { flex: 1; }
.switchrow .txt .st { font-weight: 700; font-size: var(--fs-md); color: var(--ink-2); display: flex; align-items: center; gap: .4rem; }
.switchrow .txt .st .svg-ico { width: 15px; height: 15px; color: var(--muted); }
.sw { flex: none; width: 42px; height: 24px; border-radius: 999px; background: var(--line-2); position: relative; transition: background .15s; }
.sw::after { content: ""; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; border-radius: 50%; background: #fff; box-shadow: var(--shadow-sm); transition: left .15s; }
.switchrow input:checked ~ .sw { background: var(--task); }
.switchrow input:checked ~ .sw::after { left: 20px; }

/* subtasks (task form) */
.subrows { display: flex; flex-direction: column; gap: .5rem; }
.subrow { display: flex; align-items: center; gap: .55rem; }
.subrow .grip { color: var(--faint); cursor: grab; user-select: none; touch-action: none; }
.subrow.dragging .grip { cursor: grabbing; }
/* The dragged row is HIGHLIGHTED, not faded. It used to be `opacity:.55`, which reads as "disabled"
   rather than "picked up" — and on a phone, where your thumb covers the row, a slight fade is invisible.
   Tinting it with the task cyan and lifting it off the page says which row you are holding at a glance.
   Same treatment on the matrix (`.psm tr.task-row.dragging`), so the gesture looks the same everywhere. */
.subrow.dragging { background: var(--task-bg); box-shadow: var(--shadow);
  outline: 2px solid var(--task); outline-offset: 1px; border-radius: 8px; }
.subrow input { flex: 1; }
.subrow .del { width: 32px; height: 32px; flex: none; display: grid; place-items: center; border-radius: 8px;
  border: 1px solid var(--line-2); background: #fff; color: var(--muted); cursor: pointer; }
.subrow .del:hover { color: var(--overdue); border-color: var(--overdue); }
.addlink { align-self: flex-start; display: inline-flex; align-items: center; gap: .4rem; font-size: var(--fs-sm);
  font-weight: 700; color: var(--task); background: none; border: none; cursor: pointer; padding: .2rem 0; }
.addlink .svg-ico { width: 15px; height: 15px; }

/* custom due-date picker (task form) — closed by default; weekends hatched + not clickable */
.dpick { position: relative; border: 1px solid var(--line-2); border-radius: 8px; background: #fff; max-width: 300px; }
.dpick[open] { border-color: var(--task); }
.dpick > summary { list-style: none; cursor: pointer; padding: .55rem .7rem; display: flex; align-items: center; gap: .5rem; }
.dpick > summary::-webkit-details-marker { display: none; }
.dpick .dv { flex: 1; font-size: var(--fs-md); color: var(--ink); }
.dpick .dv.ph { color: var(--muted); }
.dpick .dcal-ico { color: var(--muted); }
/* Floating overlay — absolute so it hovers over the fields below without pushing them down or
   growing the page height. .formcard uses overflow:visible so this is never clipped. */
.dpop { position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 40; background: #fff;
  border: 1px solid var(--task); border-radius: var(--radius); box-shadow: 0 10px 28px rgba(20,30,60,.18);
  padding: .55rem .6rem .6rem; }
.dpop .dphead { display: flex; align-items: center; justify-content: space-between; margin-bottom: .35rem; font-weight: 800; color: var(--ink); }
/* .dnav, NOT .nav — the topbar owns .nav globally, and its ≤720px rules (flex-direction:column,
   width:100%) leaked in here and stacked the month chevrons vertically. */
.dpop .dphead .dnav { display: flex; flex: 0 0 auto; gap: .25rem; margin-left: auto; }
.dpop .dphead .dnav .btn { min-height: 0; padding: .2rem .35rem; border-radius: 6px; }
.dpop .dphead .dnav .btn .svg-ico { width: 15px; height: 15px; }
.dpop .dgrid { display: grid; grid-template-columns: repeat(7,1fr); gap: 1px; }
.dpop .dhd { text-align: center; font-size: .64rem; font-weight: 800; text-transform: uppercase; color: var(--muted); padding: .1rem 0; }
.dpop .dhd.wknd { color: var(--faint); }
.dpop .dcell { height: 30px; display: grid; place-items: center; font-size: var(--fs-sm); border-radius: 6px; cursor: pointer; color: var(--ink-2); }
.dpop .dcell:hover:not(.wknd):not(.mut) { background: var(--task-bd); color: var(--task-ink); }
.dpop .dcell.mut { color: var(--faint); opacity: .5; }
.dpop .dcell.sel { background: var(--task); color: #fff; font-weight: 800; }
.dpop .dcell.wknd { background: repeating-linear-gradient(135deg,#f1f3f7,#f1f3f7 5px,#eceef3 5px,#eceef3 10px); color: var(--faint); cursor: not-allowed; }
/* A public holiday / company closure day is blocked exactly like a weekend (it carries .wknd too, so
   the cursor and click-block come for free) but tinted amber rather than grey — a shut office is a
   different fact from "it's the weekend", and staff need to see WHY a Tuesday is unpickable. The
   cell's title attribute names it. Amber is --upcoming, already the palette's "attention, not an
   error" colour; flag-red would read as a failure. */
.dpop .dcell.wknd.hol { background: var(--upcoming-bg); color: var(--upcoming-ink); font-weight: 700; }
/* (the ≤640px full-width override lives in the MOBILE LAYER at the foot of this file) */

/* in-charge box (routine form) */
/* --radius: it nests inside the routine form's .formcard, so it takes the card tier, not the surface one. */
.icbox { border: 1px solid var(--done-bd); background: var(--done-bg); border-radius: var(--radius); padding: .9rem 1rem 1rem; }
.icbox .ich { display: flex; align-items: center; gap: .4rem; font-weight: 800; font-size: var(--fs-xs);
  text-transform: uppercase; letter-spacing: .05em; color: var(--done-ink); margin-bottom: .7rem; }
.icbox .ich .svg-ico { width: 14px; height: 14px; }
.icgrid { display: grid; grid-template-columns: repeat(4,1fr); gap: .7rem; }
.icgrid .field > label { font-size: var(--fs-xs); color: var(--ink-2); }
@media (max-width: 640px) { .icgrid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 480px) { .icgrid { grid-template-columns: 1fr; } }

/* bell reminder toggle row (routine form) — the amber board/digest band, with the shared .sw
   sliding toggle (see .switchrow) themed amber instead of the task cyan. The slot reserves its
   height so the card doesn't jump when the row shows/hides with the selected frequency. */
.bellslot { min-height: 56px; }
.bellrow { display: flex; align-items: center; gap: .8rem; padding: .65rem .85rem; border: 1px solid var(--upcoming);
  border-radius: var(--radius); background: #fffdf6; cursor: pointer; }
.bellrow input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.bellrow .txt { flex: 1; }
.bellrow .txt .bt { font-weight: 700; font-size: var(--fs-md); color: var(--ink-2); display: flex; align-items: center; gap: .4rem; }
.bellrow .txt .bt .svg-ico { width: 15px; height: 15px; color: var(--upcoming); }
.bellrow input:checked ~ .sw { background: var(--upcoming); }
.bellrow input:checked ~ .sw::after { left: 20px; }

.formfoot { display: flex; justify-content: flex-end; align-items: center; gap: .6rem; padding: 1rem 1.5rem; background: #fafbfd; border-radius: 0 0 var(--radius-lg) var(--radius-lg); }
.formfoot.formfoot-split { justify-content: space-between; }
/* Routine form: Delete left / Save right (same as the task form, via .formfoot-split); Save carries
   the routine (slate) colour. */
.formcard.routine-form .formfoot .btn-primary { background: var(--routine); border-color: var(--routine); }
.formcard.routine-form .formfoot .btn-primary:hover { background: var(--routine-ink); border-color: var(--routine-ink); }
.formcard.task-form .formfoot .btn-primary { background: var(--task); border-color: var(--task); }
.formcard.task-form .formfoot .btn-primary:hover { background: var(--task-ink); border-color: var(--task-ink); }

/* Task form delete confirm — revealed by "Delete task" and sits below the footer, extending the card;
   plain form styling, no bordered danger card. */
.formcard .delrow { padding: 1rem 1.5rem 1.25rem; background: #fafbfd;
  border-radius: 0 0 var(--radius-lg) var(--radius-lg); }
.formcard .delrow .help { margin: 0 0 .8rem; }
.formcard .delrow .inline { display: flex; align-items: center; gap: .5rem; }
.formcard .delrow input[type=text] { max-width: 16rem; }
.formcard .delrow input[type=text]:focus { border-color: var(--overdue); box-shadow: 0 0 0 3px var(--overdue-bg); }
.formcard .delrow .btn { min-height: 44px; }
/* the revealed delete row becomes the card's bottom edge, so the footer above it goes square */
.formcard:has(.delrow:not([hidden])) .formfoot { border-radius: 0; }

/* ═══ SHARED COMPONENT LAYER (v13 consolidation) — hoisted from per-view <style> blocks; the
   matrices (.psm/.matrix) keep their own scoped rules ═══ */

/* Page shells — pick ONE per page. Width system, FOUR tiers only: 820 (forms) ·
   1200 (general .container) · 1520 (dense boards) · 1920 (.container.wide, wide tables/matrices).
   .page-dense is the ONE dense-board shell — My Board, Production, Sales, My Created, Archived and
   My Completed all sit in it, so their columns and page headers are identical by construction. (It
   replaced .board-v10, a second class with the same width but its own scoped .pgh — the pages were
   drifting apart through that fork.) There is deliberately NO fifth tier between 1200 and 820: task
   detail was briefly given a 1160 `.page-detail` shell to make its reading column land on exactly the
   820 form width, but 1160-vs-1200 is a near-duplicate — the kind of drift this system exists to
   prevent — and the 1200 container already puts it within ~9px. Task detail sits in .container. */
.page-dense { max-width: 1520px; margin: 0 auto; }
.page-form { max-width: 820px; margin: 0 auto; }

/* Dense-board page header: title left, controls (.seg / links) pushed right. Scoped to .page-dense
   on purpose — the bare .pgh below packs its children LEFT, which is what task detail needs (title
   + requirement icons read as one unit). */
.page-dense .pgh { display: flex; align-items: baseline; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem; }
.page-dense .pgh .today { color: var(--muted); font-size: var(--fs-md); }
.page-dense .pgh .actions { display: flex; gap: .5rem; align-items: center; }

/* Shared page header. */
.pgh { display: flex; align-items: center; gap: .8rem; flex-wrap: wrap; margin-bottom: 1.1rem; }
.pgh h1 { margin: 0; font-size: var(--fs-xl); font-weight: 800; letter-spacing: -.01em; }
.back { font-size: var(--fs-sm); color: var(--muted); text-decoration: none; font-weight: 600; }
.back:hover { color: var(--navy); }

/* Individual/Group type pill (+ done count) — used on cards across boards. */
.typetag { display: inline-flex; align-items: center; gap: .3rem; flex: none; font-size: var(--fs-xs);
  font-weight: 700; padding: .12rem .5rem; border-radius: 999px; }
.typetag .svg-ico { width: 12px; height: 12px; }
.typetag.ind { color: var(--task-ink); background: var(--task-bg); border: 1px solid var(--task-bd); }
.typetag.grp { color: var(--routine-ink); background: var(--routine-bg); border: 1px solid var(--routine-bd); }
.typetag .tct { font-weight: 800; font-variant-numeric: tabular-nums; }

/* Completion-requirement icon (approval / link / photo). */
.reqi { display: inline-flex; align-items: center; color: var(--muted); }
.reqi .svg-ico { width: 15px; height: 15px; }
/* Lit state — "this is set/configured" (e.g. staff webhook). Green = --done, same semantic as
   .avi.done / --box-on; a page rendering .reqi as a clickable button adds this class. */
.reqi.on { color: var(--done); }
/* Unlit state — the OTHER half of the .reqi.on toggle, for a page that renders .reqi as a set/unset
   control. Identical to .atbl .proof.off (an unsubmitted link/photo on task detail), because it says the
   same thing: nothing here yet. The plain .reqi --muted is a shade of grey the eye reads as "on" next to
   the green, so a toggle must never rely on the base state to mean "off". Only for toggles: a task's
   requirement icons are NOT this — they are always-on labels, and stay --muted. */
.reqi.off { color: var(--faint); }
.reqi.off .svg-ico { opacity: .4; }

/* ── Hover tip — the ONE explain-this affordance ────────────────────────────────────────────────
   Wraps the thing being explained (a column header, a label) and reveals a short panel below it on
   hover or keyboard focus. **There is deliberately no ⓘ icon** — an icon per column crowded the one
   row that had least space, and the label is already the obvious thing to point at; `cursor: help`
   is the affordance. Give the host `tabindex="0"` so it is reachable without a mouse.
     <span class="tip" tabindex="0">HEADER<span class="tipp">what this does</span></span>
   `.tip` only establishes the positioning context — it sets no `display`, so it works as a grid
   cell, a table cell or inline text without disturbing the host's own layout.
   Panel styling deliberately matches the matrix .rlog-pop popover (same border, radius and lifted
   shadow) — one popover look for the project, just narrower and hover-driven here. It resets
   `text-transform`/`letter-spacing`/`font-weight` so it reads as body text under an uppercase header.
   Use it where a control's meaning genuinely won't fit in its label; it is NOT a general tooltip for
   anything a clearer label would fix, and never a substitute for a form `.hint`. */
.tip { position: relative; cursor: help; }
.tipp { position: absolute; z-index: 70; top: calc(100% + .4rem); left: 50%; transform: translateX(-50%);
  width: 15rem; background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 10px 30px rgba(20,26,48,.22); padding: .55rem .7rem;
  font-size: var(--fs-xs); font-weight: 400; line-height: 1.45; color: var(--ink-2);
  text-align: left; text-transform: none; letter-spacing: 0; white-space: normal;
  opacity: 0; visibility: hidden; transition: opacity .12s; }
.tip:hover .tipp, .tip:focus-within .tipp, .tip:focus .tipp { opacity: 1; visibility: visible; }
/* Right-anchored variant, for a tip near the right edge of its container — a centred panel would
   hang off the page. */
.tip.tip-r .tipp { left: auto; right: 0; transform: none; }

/* Initials — grey by default, green once done. */
.inis { display: inline-flex; align-items: center; gap: .25rem; flex-wrap: wrap; min-width: 0; }
/* The base pill carries a 1px TRANSPARENT border so a done pill (which colours it in) is the exact
   same width — .avi has no explicit width, so a real border only on .done made a done pill 2px wider and
   left a breakdown row with a different done-count starting its right-pinned initials at a different x. */
.avi { display: inline-flex; align-items: center; justify-content: center; min-width: 22px; height: 20px;
  padding: 0 6px; border: 1px solid transparent; border-radius: 999px; font-size: .7rem; font-weight: 800;
  background: var(--routine-bg); color: var(--muted); }
.avi.done { background: var(--done-bg); color: var(--done); border-color: var(--done-bd); }

/* Per-assignee subtask breakdown — one row per subtask on an INDIVIDUAL task, shown to the creator /
   management (the task page and the My Created card share this; never fork a per-page copy). Layout is
   box · title · initials: the box is always disabled and unchecked (a viewer who is not an assignee has
   no copy of the checklist to tick), and the initials sit in ONE alphabetical order on every row so each
   assignee holds a column you can read straight down. An assignee who has submitted their whole part
   reads green on every row (see the views).

   The title column is sized so the initials start exactly where the Actions table's STATUS column does
   (2026-07-21 — they used to line up with the PROOF columns), letting the eye run straight down from a
   person's initials to that person's status pill below. Status starts at --atbl-c1 + both proof columns
   (locked, see .atbl below) + one cell pad; the title column is that, less the box and the two grid gaps. */
.subitem.subitem-bd { display: grid; align-items: center; gap: .55rem; justify-content: start;
  grid-template-columns: var(--box-size)
    minmax(0, calc(var(--atbl-c1) + var(--atbl-cw) * 2 + .5rem - var(--box-size) - 1.1rem)) auto;
  text-decoration: none; }
.subitem.subitem-bd .sbtitle { color: var(--ink-2); min-width: 0; }
/* …but on a BOARD CARD (My Created) there is no Actions table under it to line up with, and the card
   is a third of a dense board wide: holding the initials at the table's column edge stranded them in
   the middle of the card and squeezed the title into two lines. On a card the title takes the elastic
   column on ONE line (ellipsised) and the initials pin to the card's RIGHT edge — the same edge line 1's
   due badge and line 2's initials already sit on, so the card reads as one right-hand column. */
.tc .subitem.subitem-bd { grid-template-columns: var(--box-size) minmax(0, 1fr) auto; gap: .45rem; }
.tc .subitem.subitem-bd .sbtitle { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tc .subitem.subitem-bd .inis { flex-wrap: nowrap; justify-content: flex-end; }

/* Status/month panels — build on the existing global .group. */
.group.st-open > .gh { background: var(--navy); }
.group.st-pending > .gh { background: var(--approval); }
.group.st-done > .gh { background: var(--done); }
.group.st-open .stack { background: var(--open-bg); }
.group.st-pending .stack { background: var(--approval-bg); }
.group.st-done .stack { background: var(--done-bg); }

/* Status pill (detail view / tables). Never wraps: PENDING APPROVAL broken over two lines stopped
   reading as a pill and made its row taller than the others. */
.spill { display: inline-flex; align-items: center; gap: .3rem; font-size: var(--fs-xs); font-weight: 800;
  padding: .15rem .55rem; border-radius: 999px; border: 1px solid; text-transform: uppercase;
  letter-spacing: .03em; white-space: nowrap; }
.spill.pending { color: var(--approval-ink); background: var(--approval-bg); border-color: var(--approval-bd); }
.spill.rejected { color: #fff; background: var(--overdue); border-color: var(--overdue); }
.spill.done { color: var(--done-ink); background: var(--done-bg); border-color: var(--done-bd); }
.spill.open { color: var(--muted); background: var(--bg); border-color: var(--line-2); }

/* Per-assignee actions table (task detail). */
/* overflow:visible undoes the GLOBAL `table { overflow: hidden }` (which exists to clip that shell's
   rounded corners). This table has no corners to clip, and it hosts absolutely-positioned popovers
   (.rlog-pop) that hang below their row — inheriting the clip cut them off. */
.atbl { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); overflow: visible; }
.atbl th { text-align: left; color: var(--muted); font-weight: 700; text-transform: uppercase;
  letter-spacing: .03em; font-size: var(--fs-xs); padding: .35rem .5rem; border-bottom: 1px solid var(--line); }
/* EVERY column is a fixed width, so they land in the SAME place no matter who is looking or what state
   the rows are in. Left to size themselves, Status and Action drifted with their content — a row of wide
   PENDING APPROVAL pills pushed them somewhere different from a row of narrow OPEN ones, so toggling
   between the assignee's and the creator's view of the same task shifted the table.
   ACTION is the column that absorbs the leftover width, NOT Staff. Staff used to take the slack, which
   pushed the whole proof/status block hard against the right-hand edge with a lake of white space under
   the names. Action's contents are right-aligned, so growing IT pulls proof + status back to the left and
   still leaves the approve/reject buttons on the right edge where they belong. */
.atbl th, .atbl td { box-sizing: border-box; }
/* Staff: 220px fits one + the longest real name without wrapping. It is the column whose content
   varies most, so it is the one that must NOT be elastic — see above. The width is a var because the
   subtask breakdown above the table (.subitem-bd) lines its initials up with this column's right edge. */
.atbl th:first-child, .atbl td:first-child { width: var(--atbl-c1); }
/* …except the reject row's single cell, which spans the whole table — a 220 width on a colspan cell is
   a contradiction the browser has to guess its way out of. */
.atbl tr.rejrow td:first-child { width: auto; }
/* Proof columns are LEFT-aligned like every other column — centring made the icons wander away from
   their own header as the column widened. */
.atbl th.c, .atbl td.c { text-align: left; width: var(--atbl-cw); }
/* COLUMN POSITIONS ARE LOCKED TO THE ALL-REQUIREMENTS-ON CASE (2026-07-21). Only the columns a task
   actually requires are rendered — an always-there empty Link column on a photo-only task is a header
   promising a kind of proof nobody was asked for — but every rendered column keeps the x it would have
   had if BOTH proof columns were present, so Link, Photo and Status land in the same place on every
   task and the eye can run down a stack of task pages without the table jumping.
   The absent width is absorbed by the neighbour to its LEFT, which is the only direction that leaves
   the surviving columns where they were:
     both present   staff c1        | link cw   | photo cw  | status @ c1+2cw
     photo only     staff c1+cw     |           | photo cw  | status @ c1+2cw   (photo still @ c1+cw)
     link only      staff c1        | link 2cw  |           | status @ c1+2cw   (link  still @ c1)
     neither        staff c1+2cw    |           |           | status @ c1+2cw
   The view sets .no-link / .no-photo from the same $reqLink/$reqPhoto flags that decide the markup. */
.atbl.no-link th:first-child, .atbl.no-link td:first-child { width: calc(var(--atbl-c1) + var(--atbl-cw)); }
.atbl.no-photo th.c, .atbl.no-photo td.c { width: calc(var(--atbl-cw) * 2); }
.atbl.no-link.no-photo th:first-child, .atbl.no-link.no-photo td:first-child {
  width: calc(var(--atbl-c1) + var(--atbl-cw) * 2); }
/* 172 = the widest pill (PENDING APPROVAL) + its padding + the cell's, with a little headroom. Sized to
   the longest LABEL, not to the label a given task happens to show — the column is fixed, so it has to
   fit the worst case or a status that only appears sometimes would wrap only sometimes. */
.atbl th.st, .atbl td.st { text-align: left; width: 172px; }
/* No width: this is the slack column. min-width holds the two icon buttons when there's nothing spare. */
.atbl th.act, .atbl td.act { text-align: right; white-space: nowrap; min-width: 96px; }
.atbl td { padding: .45rem .5rem; border-bottom: 1px solid var(--line); color: var(--ink-2); font-weight: 600; vertical-align: middle; }
.atbl tr:last-child td { border-bottom: none; }
/* Your OWN row is tinted rather than announced in a sentence underneath the table — the row already
   carries your status pill, so a "your part was approved" line said it twice, and the tint is also why
   the rows carry no "(you)" label. The tint is the WHOLE marker: there is deliberately no vertical
   accent bar down the first cell (removed 2026-07-14) — the cyan card accent already runs above the
   table, and a second cyan rule inside it read as a nested frame. */
.atbl tr.mine td { background: var(--task-bg); }
/* nowrap: the name is the one thing in the row that must never be cropped or broken over two lines —
   the proof icons, the pill and the buttons all have fixed columns, so the name is what gives. */
.atbl .who { display: inline-flex; align-items: center; gap: .5rem; white-space: nowrap; }
.atbl .av { width: 26px; height: 26px; border-radius: 50%; background: var(--routine-bg); color: var(--routine-ink);
  display: grid; place-items: center; font-size: .72rem; font-weight: 800; flex: none; }
/* align-items:center on the flex box + a block-level SVG: without both, the icon sits on the text
   baseline and the count pill next to it hangs low. */
.atbl .proof { display: inline-flex; align-items: center; color: var(--task-ink); }
.atbl .proof .svg-ico { width: 17px; height: 17px; display: block; }
/* The thead proof icon labels the column its rows fill, so it renders at the SAME size as the row
   icons — a smaller glyph read as a different thing. */
.atbl th.c .svg-ico { width: 17px; height: 17px; }
.atbl .proof.off { color: var(--faint); }
.atbl .proof.off .svg-ico { opacity: .4; }
/* A REJECTED person's proof reverts to this same off/grey and loses its count — after a send-back they
   have not supplied proof for the current attempt, and a live cyan "2" said they had. It stays a LINK
   though (an <a class="proof off">, never the inert <span>): the approver still needs to open what was
   sent back to see what was wrong with it. Hover is the only cue that this one is clickable. */
.atbl a.proof.off:hover { color: var(--muted); }
.atbl a.proof.off:hover .svg-ico { opacity: .75; }
/* Several files from one person: the icon carries a count and opens a popover listing them — a single
   link could only ever reach the first. */
.atbl .proofwrap { position: relative; display: inline-flex; }
.atbl .proof-more { border: none; background: none; padding: 0; cursor: pointer; align-items: center; gap: .25rem; }
/* The count wears the same pill as the Subtasks count (.tdv .cpill) — one "how many" chip on the page.
   line-height 1 (not 1.35) so the pill's own box centres on the icon rather than sitting a few px low. */
.atbl .proof-more .pcount { font-size: .7rem; font-weight: 800; line-height: 1; color: var(--task-ink);
  background: var(--task-bg); border: 1px solid var(--task-bd); border-radius: 999px; padding: .18rem .4rem; }
.atbl .proofwrap .rlog-pop { width: 260px; top: calc(100% + 4px); right: 0; text-align: left; }
.atbl .rlog-pop .plist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .35rem; }
.atbl .rlog-pop .plist a { color: var(--task-ink); font-weight: 600; overflow-wrap: anywhere; }
.atbl .dash { color: var(--faint); }
.atbl .ibtn { display: inline-flex; align-items: center; justify-content: center; min-width: 32px; min-height: 32px;
  border-radius: 7px; border: 1px solid var(--line-2); background: #fff; cursor: pointer; color: var(--ink-2); }
.atbl .ibtn .svg-ico { width: 15px; height: 15px; }
.atbl .ibtn.ok { background: var(--done); border-color: var(--done); color: #fff; }
.atbl .ibtn.ok:hover { background: var(--done-ink); }
.atbl .ibtn.danger { border-color: var(--overdue); color: var(--overdue-ink); }
.atbl .ibtn.danger:hover { background: var(--overdue); color: #fff; }
.atbl .rejrow td { background: var(--overdue-bg); }
.atbl .rejbox { display: flex; gap: .5rem; align-items: center; }
.atbl .rejbox input[type=text] { flex: 1; font: inherit; font-size: var(--fs-sm); border: 1px solid var(--overdue-bd);
  border-radius: 8px; padding: .4rem .6rem; min-height: 34px; }
.atbl .rejbox .btn { border-color: var(--overdue); color: var(--overdue-ink); }

/* Activity timeline (task detail, routine log). */
.tl { display: flex; flex-direction: column; gap: .05rem; }
.tli { display: flex; gap: .6rem; padding: .55rem 0; border-bottom: 1px dotted var(--line-2); }
.tli:last-child { border-bottom: none; }
.tli .ico { width: 24px; height: 24px; border-radius: 50%; display: grid; place-items: center; flex: none;
  background: var(--bg); color: var(--muted); }
.tli .ico .svg-ico { width: 13px; height: 13px; }
/* ONLY the two outcomes are tinted — completed/approved green, rejected red. Every other verb
   (created, edited, submitted, commented) keeps the neutral grey dot: tinting them too made
   the column read as a wall of colour with no signal in it. */
.tli.g .ico { background: var(--done-bg); color: var(--done); }
.tli.r .ico { background: var(--overdue-bg); color: var(--overdue); }
/* The initials .avi (activity log, in place of the status .ico) is ALWAYS the neutral grey —
   its purpose is to identify WHO acted, not the outcome, so tinting it green/red just echoed the
   verb and made the column a wall of colour. Outcome tint lives on the .ico dot only (above). */
/* In the timeline the initials pill must be a FIXED width — the shared .avi grows with its content
   (1-letter "H" vs 2-letter "SH"), which would start each row's text column at a different x and
   make the log read ragged. Fixed here only; .avi stays elastic on board cards. */
.tli .avi { width: 28px; min-width: 28px; padding: 0; flex: none; }
.tli .tx { min-width: 0; font-size: var(--fs-sm); display: flex; flex-direction: column; gap: .08rem; }
.tli .tx .top { font-weight: 700; }
/* Timestamp and note read at the SAME size as the verb above them and as the Actions table beside them
   (--fs-sm). At --fs-xs the timeline was the smallest text on the page and the dates stopped being
   legible; a log you have to lean in to read isn't a log. Colour, not size, is what makes them
   secondary. */
.tli .tx .tm { color: var(--muted); font-size: var(--fs-sm); font-weight: 400; }
.tli .tx .tn { color: var(--ink-2); font-size: var(--fs-sm); }
/* The NOTE (a person's reason — reject/submit/comment) reads italic to set it apart from the
   regular-weight changes/diff line above it. Timeline-scoped so it can't touch the matrix .note cell. */
.tli .tx .note { font-style: italic; }
/* .tm is the TIMESTAMP everywhere — on /inbox + /log it is the time pinned right of the verb, on the
   task-detail rail the time under the note. It never holds a link, so it needs no link rule; the
   subject's `color: inherit` lives with the subject, on `.act-tl .tli .tx .top .subj a` below. */

/* Standalone auth pages (login, not_registered) — no topbar, centered card. They now load
   app.css + Inter, so classes are prefixed .auth* to avoid clashing with the topbar .brand and
   the form .card above. */
.authwrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 1rem; }
/* --radius-lg: a top-level surface, like a panel or a .formcard (it was the app's only stray 12px). */
.authcard { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 2.5rem;
  width: 100%; max-width: 380px; text-align: center; box-shadow: 0 2px 10px rgba(20, 30, 60, .06); }
.authbrand { font-weight: 800; font-size: 1.3rem; color: var(--navy); margin: 0 0 .5rem; }
.authsub { color: var(--muted); font-size: var(--fs-md); margin: 0 0 1.75rem; }
.authcard p { color: var(--muted); font-size: var(--fs-md); line-height: 1.5; }
.authcard .g-wrap { display: flex; justify-content: center; }
.authcard .email { color: var(--ink); font-weight: 700; }

/* Activity log page (/activity). The rows themselves reuse the .tl/.tli timeline, .avi initials,
   .team-head day headings and .filterbar — the only genuinely new parts are the pager row and a
   couple of width/air tweaks, which is why they are the only rules here. */
/* A timeline reads as one vertical column, so the page is narrowed to the New Task form width
   (.formwrap = 820) and centred rather than filling the 1200 container. */
.act-wrap { max-width: 640px; margin: 0 auto; }
/* Timeline text runs one tier bigger here than on task detail (--fs-md, not the shared --fs-sm):
   this is the whole page, so it can read comfortably; scoped to .act-tl so the locked task-detail
   rail (DESIGN_SPEC §6) is untouched. */
.act-tl .tli .tx,
.act-tl .tli .tx .tm,
.act-tl .tli .tx .tn { font-size: var(--fs-md); }
/* Line-2 detail (changes / note) reads lighter than the verb — colour, not size, carries the
   hierarchy. Scoped to .act-tl so the locked task-detail rail (§6) keeps its --ink-2. */
.act-tl .tli .tx .tn { color: var(--muted); }
/* Keep the TIME COLUMN clean: the time (.tm) is pinned right of line 1, but the detail lines below
   it (.tn / .tn.note) span the full .tx width and would run under that column. Reserve a right gutter
   the width of the "18:11" stamp so every wrapped line stops short of the time. .act-tl-only — the
   task-detail rail stacks its time below, so it needs no gutter. */
.act-tl .tli .tx .tn { padding-right: 2.75rem; }
/* Row top line: bold action LEFT, muted time RIGHT, on one line — the time no longer sits above the
   action on its own line. Subject (.tm) and detail (.tn) still stack below, unchanged. Scoped to
   .act-tl only (Inbox + Log) so the locked task-detail rail (§6) keeps its own top→note→time order.
   .tx must FILL the row (flex:1) or space-between has no slack and the time hugs the action instead
   of reaching the right edge; the task-detail rail leaves .tx content-width, so this is .act-tl-only. */
.act-tl .tli .tx { flex: 1; }
.act-tl .tli .tx .top-row { display: flex; justify-content: space-between; align-items: baseline; gap: .5rem; }
/* The time is a fixed fact at the row's right edge — never let it shrink or wrap under the action. */
.act-tl .tli .tx .top-row .tm { flex: none; white-space: nowrap; }
/* The verb stays bold (.top is already 700); the subject that now lives inline after it (" — Name")
   reads regular weight and inherits colour whether it's a task link or plain routine/config text —
   same precedent as .tc .ttl a { color: inherit }. .top needs min-width:0 to actually wrap inside the
   space-between row instead of pushing the time off the right edge. */
.act-tl .tli .tx .top { min-width: 0; }
.act-tl .tli .tx .top .subj { font-weight: 400; }
.act-tl .tli .tx .top .subj a { color: inherit; }
/* Each tab wears its item's colour: the Routine tab --routine (slate), the Task tab --task (cyan).
   The active seg button and the day-heading dates carry it, so the two logs read apart at a glance.
   Scoped to .act-routine/.act-task so the shared .seg / .team-head are unaffected elsewhere. */
.act-routine .seg a.on { background: var(--routine); }
.act-task    .seg a.on { background: var(--task); }
.act-routine .team-head { color: var(--routine); }
.act-task    .team-head { color: var(--task); }
.act-pager { display: flex; align-items: center; gap: .75rem; justify-content: center; margin-top: 1.2rem; }
.act-pageof { font-size: var(--fs-sm); color: var(--muted); }
.act-tl .team-head:first-child { margin-top: 0; }
/* Indent the entry rows under each day heading so the .team-head date reads as its own left
   column, distinct from the activity beneath it. Scoped to .act-tl — the task-detail rail (§6)
   keeps its rows flush against the heading. */
/* Roomier rows (esp. one-line ones) for easier reading; the 1.5rem left indent still sets the
   .team-head date off as its own column. */
.act-tl .tli { padding: .8rem 0 .8rem 1.5rem; }
.act-filters { align-items: flex-end; }
/* The 2–3 filters STRETCH to share the reading column's full width (they used to left-pack at a
   fixed 200px and leave dead space on the right). min-width:0 also keeps them on ONE row on mobile
   instead of stacking three-tall. */
.act-filters .field { flex: 1 1 0; min-width: 0; }
/* Filter labels match the Config seg control's size (--fs-sm), not the roomier row text, and never
   wrap — with the short "Jul 2026" month labels every dropdown (closed label + its options) sits on
   one line. Scoped to .act-filters so the shared .dsel elsewhere keeps its --fs-md .val. */
.act-filters .dsel .val { font-size: var(--fs-sm); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.act-filters .dopt { white-space: nowrap; }
/* Inbox: the lone month filter RIDES THE TITLE LINE (inside .page-head, right corner) instead of a
   full-width filter row. Same .filterbar/.act-filters .dsel component as the Log; only the placement
   differs — drop the bottom margin and let it size to its label rather than stretch (flex:1 1 0). */
.page-head .filterbar { margin-bottom: 0; }
.page-head .filterbar .field { flex: 0 0 auto; min-width: 8.5rem; }

/* ── Task detail page (.tdv) — page-specific shell, moved out of views/tasks/show.php 2026-07-21 ──
   It lived in the view's <style> and that was actively breaking the phone layout: a page <style>
   loads AFTER app.css, media queries add NO specificity, and every one of these selectors is also
   declared in the MOBILE LAYER below. So on an equal-specificity tie the PAGE won and silently
   disabled the mobile rule — `.tdv .card .cardbody` never tightened its padding on a phone,
   `.tdv .lead .subsec` kept its desktop padding-top and `.tdv .pgh` kept the wrong margin-bottom.
   Same class of bug as the `.bd-stack` incident (see § Type scale). Keep these HERE, above the
   mobile layer, so the phone overrides can win. */
.tdv .pgh { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin-bottom: .9rem; }
.tdv .pgh h1 { margin: 0; font-size: var(--fs-xl); font-weight: 800; letter-spacing: -.01em; }
/* .reqs sits inside the h1 (see markup) — inline-flex + a leading gap so the icons ride the last word
   of the title and wrap WITH it, never onto a row of their own. */
.tdv .pgh h1 .reqs { display: inline-flex; align-items: center; gap: .35rem; margin-left: .45rem; vertical-align: middle; }
.tdv .pgh .reqi .svg-ico { width: 17px; height: 17px; }
.tdv .pgh .spacer { flex: 1; }
/* Buttons use the global .btn system (primary / ok / danger / sm + hovers).
   The card itself has NO padding — all padding is the body's (a card-plus-body double inset read as
   a box inside a box). */
.tdv .card { background: #fff; border: 1px solid var(--line); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); margin-bottom: 1rem; }
.tdv .card .cardbody { padding: 1rem 1.15rem; }
/* Cyan top accent on the two WORKING cards (lead + Actions); Activity is the rail, so no accent. Both
   carry the shared `.accent-top` class — the project-wide ONE accent style; here we only set its
   colour to the task cyan. No local border-top rule, so there is a single source of truth. */
.tdv .card.lead, .tdv .card.acts { --accent-c: var(--task); }
/* Every card subheader is cyan — Subtasks, Actions, Activity: three sections of one task, so they
   read as one set. A muted heading next to a cyan one looked like two different kinds of card. */
.tdv .h2 { margin: 0 0 .7rem; font-size: var(--fs-xs); font-weight: 800; color: var(--task-ink); text-transform: uppercase; letter-spacing: .05em; display: flex; align-items: center; gap: .4rem; }
.tdv .h2 .svg-ico { width: 14px; height: 14px; }
.tdv .cpill { font-size: .7rem; font-weight: 800; color: var(--done-ink); background: var(--done-bg); border: 1px solid var(--done-bd); border-radius: 999px; padding: .02rem .45rem; letter-spacing: 0; }
/* Type pill + due + description + subtasks are ONE card; the subtask block is a section inside it. */
.tdv .lead .subsec { border-top: 1px solid var(--line); padding-top: 1rem; margin-top: .2rem; }
.tdv .lead .cardbody:first-of-type:has(+ .subsec) { padding-bottom: .9rem; }
.tdv .metarow { display: flex; align-items: center; gap: .6rem; margin-bottom: .7rem; }
.tdv .due { margin-left: auto; }
.tdv .desc { color: var(--ink-2); font-size: var(--fs-md); margin: 0; white-space: pre-wrap; }
.tdv .grid2 { display: grid; grid-template-columns: 1fr 320px; gap: 1.2rem; align-items: start; }
@media (max-width: 860px) { .tdv .grid2 { grid-template-columns: 1fr; } }
.tdv .col-l { display: flex; flex-direction: column; gap: 1.1rem; }
/* The completion form is a separate act from reading the table — real air above it so it doesn't read
   as a table footer. It carries no heading (see markup): the button names the act. */
.tdv #my-part { margin-top: 1.6rem; }
.tdv .subs { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .5rem; }
/* Every box on this page — live, read-only and the breakdown row — is the shared .chk/.box component
   above. Nothing to fork: the box must look and sit identically whoever is looking at it. */

/* ═══ MOBILE LAYER — shared components only ═══════════════════════════════════════════════════════
   Everything here is max-width only, so desktop never evaluates it and the locked desktop layouts
   (DESIGN_SPEC §3/§5/§9) are untouched. Rules live with the component they narrow, by breakpoint.
   The MATRIX phone layout lives here too (2026-07-23) — it used to be an un-narrowed table that
   overflowed the page; see the .psm block below. */

@media (max-width: 720px) {
  /* Inbox/Log rows lose the desktop 1.5rem indent on phones — the initials already give left air. */
  .act-tl .tli { padding-left: 0; }

  /* ── Production / Sales Matrix on a phone — DESIGN_SPEC §9 ──────────────────────────────────────
     TWO COLUMNS AND NOTHING ELSE: the routine, and its IC initials. Frequency, Timing, the skill
     ticks, the drag handle and the log icon are not rendered — on a phone the question is "what is
     it and whose is it", and everything else was noise you had to pan sideways to escape.

     WHY THE TABLE IS ABANDONED RATHER THAN NARROWED. display:none on a <td> does NOT blank a column:
     it removes the cell from the row, so every LATER cell shifts one column left in the <colgroup> —
     the title lands in the 26px drag column and the IC pills land under Frequency. There is no
     CSS-only way to hide a table column without that shift (visibility:collapse is not reliably
     supported). So the rows stop being table rows here: display:block/flex, no colgroup, no column
     algorithm to break. Anything that re-introduces a hidden <td> re-introduces the bug.

     Everything position:sticky must go static — the desktop pins the thead to the viewport and the
     drag/title/band cells to the left edge, and neither has any meaning once the rows are flex. */
  /* Scoped to .page-dense — `.matrix-wrap` is SHARED with IC Rotation (§10), whose 738px grid still
     needs its sideways scroller. Only the psm view sits inside the dense shell, so this narrows the
     override to the matrices that became flex cards without touching the locked page. */
  .page-dense .matrix-wrap { overflow-x: hidden; }
  /* Stated as table.matrix.psm, not .psm — it has to outrank the desktop rule that computes width
     from --psm-n (0,2,1), or the table stays 1520px wide and the page scrolls sideways again. */
  table.matrix.psm { display: block; width: 100%; }
  .psm tbody { display: block; width: 100%; }
  .psm colgroup { display: none; }
  /* The head keeps ONLY the IC controls, as a bar above the list. */
  .psm thead, .psm thead tr { display: block; }
  .psm thead th { display: none; position: static; }
  .psm thead th.h-ic { display: block; border: 0; padding: .45rem .35rem;
    border-radius: var(--radius) var(--radius) 0 0; }
  /* Sales has no month pills and the Focus/Fade/Rota modes are hidden on a phone, so its IC header is
     an empty navy bar here — drop it (the h-ic-empty class is emitted by the view when months <= 1;
     desktop keeps the bar, where it still holds the mode buttons). Production keeps its month pills. */
  .psm thead th.h-ic.h-ic-empty { display: none; }
  .psm .ic-head { flex-direction: row; justify-content: center; gap: .5rem; flex-wrap: wrap; }
  /* Matrix head: push the Production/Sales toggle to the right end on a phone (desktop already sits
     right). The seg is the only control up here, so space-between pins it against the edge. */
  .matrix-wrap .mx-head { justify-content: space-between; }
  /* Focus is the only mode on a phone (it is already the default), so the MONTH pills are the one
     control left — and they take the room Fade/Rota freed up: 32px tall, .5rem apart, a real thumb
     target. Rota loses nothing here: position-name pills only ever earned their space on desktop. */
  .psm .ic-modes { display: none; }
  .psm .ic-months { gap: .5rem; }
  .psm .ic-months .mbtn { min-width: 2.6rem; min-height: 32px; padding: .35rem .55rem;
    border-radius: 7px; font-size: 11.5px; display: inline-flex; align-items: center; justify-content: center; }
  /* Band rows: full-bleed, no side borders, no left stickiness. The th is FLEX, not block, for two
     reasons that are really one: the "+" `.band-add` is `float:right` on desktop, and a float inside a
     block th OVERFLOWS its bottom and intrudes into the first task-row beneath it — a right float
     shrinks that flex row's box and shoves its right-pinned initials ~40px left (the "first row of
     every group isn't right-aligned" bug; only EDITORS saw it, because only they get a "+"/float).
     Flex establishes an independent formatting context AND lets `margin-left:auto` sit the "+" hard
     against the right edge where Janelle wants it. */
  .psm tr.station-row, .psm tr.group-row { display: block; }
  .psm tr.station-row th, .psm tr.group-row th { display: flex; align-items: center; position: static;
    padding: .3rem .6rem; font-size: 12.5px; border-left: 0; border-right: 0; }
  .psm tr.station-row .band-add, .psm tr.group-row .band-add { float: none; margin: 0 0 0 auto; }
  /* The routine row: title flexes, initials pinned in a FIXED column so they line up down the page.
     NOTE the selector on the hide/show pair — both are stated as `tr.task-row > td` (0,4,2). A plain
     `td.mx-task` (0,4,1) loses to the hide rule and the whole card renders empty. */
  .psm tr.task-row { display: flex; align-items: flex-start; gap: .5rem; background: #fff;
    border-bottom: 1px solid var(--line); padding: .45rem .55rem; }
  .psm tr.task-row > td { display: none; border: 0; padding: 0; }
  .psm tr.task-row > td.mx-task { display: block; position: static; flex: 1 1 auto; min-width: 0;
    text-align: left; font-size: 13px; line-height: 1.3; overflow-wrap: break-word; }
  /* 100px = the widest cell the data can produce (four two-letter pills ≈ 92px) plus ~8px of slack.
     A group position is ONE pill and consumes two chain slots, so "pair + 3" cannot occur; pair + 2
     is ≈ 83px and a three-member pill + 1 is ≈ 68px. FIXED, not content-sized: content-sizing made
     the initials jitter row to row.
     RIGHT-aligned (2026-07-23b, Janelle) — the pills sit against the card's right edge, so the LAST
     IC forms a clean column down the page and the slack falls in the middle, beside the title. The
     fixed width still does the anti-jitter work; only which edge the slack lands on changed. */
  /* min-width:0 is LOAD-BEARING, not tidying: a flex item's default `min-width:auto` is its content
     size, which OVERRIDES flex-basis — so any cell whose pills exceed 100px silently widens the
     column and the initials stop lining up row to row (the jitter the fixed width exists to stop).
     With it, 100px is really 100px and an over-wide cell overflows instead of moving its neighbours. */
  .psm tr.task-row > td.mx-ic { display: block; flex: 0 0 100px; width: 100px; min-width: 0;
    text-align: right; font-size: 11.5px; }
  .psm td.mx-ic .inis { flex-wrap: nowrap; justify-content: flex-end; gap: .22rem; }
  /* Shared .avi narrowed for this context only — the same move .tli .avi already makes for the
     Inbox timeline. Not a fork: no new class, and desktop is untouched. */
  .psm td.mx-ic .avi { min-width: 19px; height: 18px; padding: 0 4px; font-size: .62rem; }
}

@media (max-width: 640px) {
  /* Board head on ONE line. The heading keeps the SAME --fs-xl as every other page's <h1> — a page
     title doesn't shrink just because a segmented control shares its row; the room comes from the seg's
     padding instead. The Production/Sales heading sheds its trailing " Board" (.h1-long) here, since the
     seg already names the view ("Production" / "Sales"); desktop keeps the full "Production Board". */
  .page-dense .pgh { flex-wrap: nowrap; gap: .5rem; align-items: center; margin-bottom: .7rem; }
  .page-dense .pgh h1 { font-size: var(--fs-xl); white-space: nowrap; }
  .page-dense .pgh h1 .h1-long { display: none; }
  .page-dense .pgh .actions { min-width: 0; }
  .seg a { padding: .4rem .55rem; font-size: var(--fs-sm); white-space: nowrap; }

  /* Attention bar = ONE band. The stats aren't clickable, so three bordered cards were three
     affordances that lead nowhere; here they're plain items inside a single card. Each takes its
     NATURAL width (space-evenly, not flex:1) — equal thirds cropped "Upcoming". Icon and text run
     bigger than desktop's, not smaller. "Pending approval" sheds " approval" (.al-long); "My
     Completed" drops to its own row, midway between the band and the first panel. */
  .attn { gap: .5rem; margin-bottom: .55rem; }
  /* Stats sit LEFT-aligned at a fixed gap, never spread — space-evenly made the 2-stat Production
     board centre its pair while the 3-stat My Board filled the row, so the two boards disagreed.
     My Board is the default: same position, same size, whatever the stat count. */
  .attn .astats { display: flex; flex: 1 0 100%; align-items: center; justify-content: flex-start;
    gap: .9rem; padding: .4rem .7rem; background: var(--card); border: 1px solid var(--line);
    border-radius: var(--radius); box-shadow: var(--shadow-sm); }
  .astat { flex: none; padding: 0; gap: .4rem; border: none; background: none; box-shadow: none;
    font-size: var(--fs-sm); white-space: nowrap; }
  .astat .ai { width: 24px; height: 24px; border-radius: 6px; }
  .astat .ai .svg-ico { width: 14px; height: 14px; }
  .astat .an { font-size: var(--fs-md); }
  .al-long { display: none; }
  .attn .mb-link { flex: 1 0 100%; margin-left: 0; text-align: right; }

  /* Login card rides near the TOP of the page on phones (agreed 2026-07-17): vertically centring
     against min-height:100vh put it below the visual centre on mobile (the browser UI eats into
     100vh), so it sat near the fold. Desktop stays centred. */
  .authwrap { align-items: flex-start; padding-top: 3.5rem; }

  /* Form controls that carry a fixed 300px/16rem desktop width fill the page instead of stopping
     short of it. */
  .formcard .seg { width: 100%; }
  .dpick { max-width: 100%; }
  .formcard .delrow input[type=text] { max-width: 100%; }

  /* Give the CARDS the width, not the chrome around them: the panel/station shells shed padding, and
     the card sheds its own, so more of the screen is content and less is gutter. */
  .group .stack { padding: .45rem; gap: .45rem; }
  .st-card { padding: .5rem .55rem; }
  .st-card h3 { margin: -.5rem -.55rem .5rem; padding: .5rem .6rem; }
  .tc { padding: .55rem .6rem; gap: .35rem; }
  .container { padding: .6rem; }

  /* Completed-card title: desktop truncates to one line (the card is wide enough that a wrap would
     be a stray word); a narrow card chops the title to a few words, so let it wrap instead. */
  .tc .strike { white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; }

  /* Per-assignee table (task detail): the 96px nowrap action column + the initials push it past the
     card. Drop the initials, let the action column size itself, tighten the proof columns.
     The staff column also sheds its fixed --atbl-c1 (220px) — at phone width that alone is most of the
     row, and the name still wrapped ("Szu / How") because the pill and buttons squeezed it. Auto-width +
     nowrap keeps every name on ONE line; the status pill drops to "PENDING" (its second word rides the
     shared .al-long span, hidden above) so there is room for it, and for the link column when the task
     requires one. */
  .atbl .av { display: none; }
  .atbl th:first-child, .atbl td:first-child { width: auto; }
  /* width:auto alone left the DESKTOP min-width:96px in force. The cell holds two 32px icon buttons
     (~72px with their gap), so 96 was 24px of pure slack — and on a 360px phone the table's minimum
     (staff + two proof cols + the PENDING pill + this) is within ~10px of the viewport, so that slack
     was the difference between fitting and scrolling the page sideways. */
  .atbl th.act, .atbl td.act { width: auto; min-width: 72px; }
  .atbl th.st, .atbl td.st { width: auto; }
  /* 44px, not the desktop --atbl-cw: a phone row has no width to spend locking column positions, so
     the .no-link/.no-photo padding above is deliberately overridden here (same specificity, later in
     the file). 44 still fits a two-digit count; 36 clipped it. */
  .atbl th.c, .atbl td.c { width: 44px; }
  .atbl.no-link th:first-child, .atbl.no-link td:first-child,
  .atbl.no-link.no-photo th:first-child, .atbl.no-link.no-photo td:first-child { width: auto; }
  .atbl.no-photo th.c, .atbl.no-photo td.c { width: 44px; }
  .atbl th, .atbl td { padding-left: .3rem; padding-right: .3rem; }

  /* Subtask breakdown: the title column is sized to line its initials up with the table's 220px staff
     column, which leaves nothing for the title on a phone. Give the title the elastic column instead and
     keep the initials on ONE line (nowrap) — a long title now wraps to a second line UNDER itself rather
     than pushing an assignee's initial onto a line of its own. Top-aligned, so the box and the initials
     ride the title's FIRST line.
     `.bd-stack` (MORE THAN 4 assignees, PHONE ONLY — this is the **640px** layer, settled 2026-07-21;
     the breakdown's phone rules live here, not in the 720px block above): those rows drop the initials
     to their own line under the title, wrapping LEFT-aligned into tidy rows from the title's left edge.
     Past 4 initials a phone can't fit them beside a wrapping title — you get two ragged right-aligned
     rows with an orphan below. **4 or fewer are untouched, and DESKTOP IS UNTOUCHED AT ANY COUNT** —
     Janelle looked at a stacked desktop row and called it wrong; on a full-width row the initials belong
     on the title's line. The `.bd-stack` class is emitted by the view at any width (the `bdStack` flag
     in views/tasks/show.php); only this phone layer acts on it.
     ⚠ Scoped to `.tdv` ON PURPOSE. The board card (`.tc .subitem.subitem-bd`) keeps its 3-column grid
     with initials pinned right — an alignment shared with the card's `.meta` line and LOCKED across all
     six dense boards. Unscoped, `grid-column: 2` would land on those cards too (their `.tc …` rule sets
     flex-wrap but not grid placement, so it would NOT win) and move the initials into the title column
     on every board. */
  /* ⚠ 4 OR FEWER assignees need a phone rule too — this was missing and it overflowed the page
     (found 2026-07-21). The desktop title column is sized to reach the Actions table's STATUS column
     (--atbl-c1 + 2×--atbl-cw ≈ 320px); add the box, the initials and the gaps and the row wants ~456px,
     which is wider than the phone itself, so the whole page scrolled sideways. Only `.bd-stack` (>4)
     had an override, so a 3-assignee task fell through to the desktop grid.
     On a phone there is no Actions table alongside to line up WITH — it is below, and its own columns
     go `auto` here anyway — so the alignment the desktop column buys is worthless. Give the title the
     elastic column and let the initials take what they need. */
  .tdv .subitem.subitem-bd { grid-template-columns: var(--box-size) minmax(0, 1fr) auto; }
  .tdv .subitem.subitem-bd.bd-stack { grid-template-columns: var(--box-size) minmax(0, 1fr);
    align-items: start; row-gap: .3rem; }
  /* Explicit column: with two columns, auto-placement would drop the initials under the CHECKBOX. */
  .tdv .subitem.subitem-bd.bd-stack .inis { grid-column: 2; flex-wrap: wrap; justify-content: flex-start; }

  /* Task detail: spend the phone's width on TEXT, not on chrome. The cards shed most of their inset and
     the page steps its type up ONE tier — done by re-pointing the font tokens on .tdv rather than
     restyling a dozen elements one by one, so the pills, the table, the subtasks and the timeline all
     move together and keep their relative sizes. Scoped to .tdv: this is not a global type change. */
  .tdv { --fs-xs: .8rem; --fs-sm: .9rem; --fs-md: .95rem; }
  .tdv .card .cardbody { padding: .7rem .75rem; }
  .tdv .lead .subsec { padding-top: .7rem; }

  /* Edit shares the Return link's row instead of sitting under the title. A phone title wraps to two or
     three lines, and Edit trailing it pushed the first card most of a screen down. The DOM doesn't move
     (desktop keeps Edit on the title row, DESIGN_SPEC §6) — the button is just pinned to the top-right of
     the page block, and the header takes a top margin to give that row its height. */
  .tdv { position: relative; }
  /* Edit becomes Return's TWIN on the right: same .back styling (muted, 600, --fs-sm, icon + word), no
     border and no fill. The 40px button chrome was the height — not the label — and a phone header that
     is two matching text links reads as one bar instead of a link and a control. It keeps the pencil, so
     it is still recognisable at a glance, and it keeps its vertical padding, so the tap target survives
     losing the box. This is the ONLY button on the page that changes; every other .btn is untouched. */
  .tdv .pgh .btn { position: absolute; top: -.15rem; right: 0; min-height: 0; padding: .3rem 0 .3rem .6rem;
    border: none; background: none; border-radius: 0; box-shadow: none;
    color: var(--muted); font-weight: 600; font-size: var(--fs-sm); }
  .tdv .pgh .btn:hover { background: none; border: none; color: var(--navy); }
  /* A 4-line phone title at the default line-height was mostly leading; tighten it, and the gaps either
     side of the header, so the header costs the words and not the space between them. */
  .tdv .pgh { margin-top: 1.5rem; margin-bottom: .6rem; }
  .tdv .pgh h1 { line-height: 1.2; }
}

@media (max-width: 480px) {
  /* One line: [tick] [1st-IC initial] [title] ……… [due]. The initial LEADS the title instead of
     trailing it, so the title gets one uninterrupted run to the due badge and wraps as little as
     possible. Desktop keeps its locked column order (DESIGN_SPEC §3) — only the grid placement moves,
     the DOM doesn't. */
  /* Column gap is tight overall, but the tick gets more air before the initial — they read as two
     separate controls, not one clump. */
  .ro-row { grid-template-columns: 18px auto minmax(0, 1fr) auto; gap: .4rem; }
  .ro-row .inis { margin-left: .3rem; grid-area: 1 / 2; }
  .ro-row .done-check { grid-area: 1 / 1; }
  .ro-row .ro-title { grid-area: 1 / 3; }
  /* The tail slot (.ro-tail — a .due badge, the green completer .avi, or an empty <span> on an
     undated row) is pinned by its class so the empty one can't auto-place into a cell that's
     already spoken for. */
  .ro-row > .ro-tail { grid-area: 1 / 4; }

  /* Timing panel: the 393px desktop panel becomes ~310px here, so a 4-up cell drops from ~52px of
     text to ~29px — under what "Wed" needs once the fallback face (DejaVu Sans, wider than Inter) is
     in play. All THREE schedule groups go 3-up together so they stay aligned with each other; moving
     one alone re-introduces the mismatch this layout exists to remove. Wk4 lands alone on a second
     row here — accepted 2026-07-30, the cost of not clipping. */
  #timing-msel .tgrp-month { grid-template-columns: repeat(3, 1fr); }
  #timing-msel .tgrp-weekday { grid-template-columns: repeat(3, 1fr); }
  #timing-msel .tgrp-week_pos { grid-template-columns: repeat(3, 1fr); }
}

/* IC Rotation on a phone: ONE month at a time. Four month columns can't fit, so the page JS marks
   every non-active month cell .mcol-off and renders a ‹ MONTH › switcher (.ic-mnav). Both are
   inert on desktop — the classes are always maintained by JS, but only this layer acts on them,
   so the locked desktop grid (all 4 months side by side) never changes. */
.ic-rot-page .ic-mnav { display: none; }
@media (max-width: 640px) {
  /* ‹ › only, riding the title line's right edge (the .page-head flex pushes it there); no month
     label — the table's own sticky month header names the month. */
  .ic-rot-page .ic-mnav { display: flex; align-items: center; gap: .4rem; }
  .ic-rot-page .mcol-off { display: none; }
  /* Two columns only now — let them share the full width instead of holding desktop minimums. */
  .ic-rot-page .matrix.ic { width: 100%; }
  .ic-rot-page .matrix.ic th.mx-task, .ic-rot-page .matrix.ic td.mx-task { min-width: 0; max-width: none; width: 42%; }
  .ic-rot-page .matrix.ic td, .ic-rot-page .matrix.ic th { min-width: 0; }
}
