/* ==========================================================================
   Werktyd: TIME — the Overview and the four timesheet views (round 3, 2026-09-25).

   Loaded after clean-ui.css and owned by the Overview/Timesheets lane, so the
   two lanes never edit the same rules. Tokens only: every colour below is a
   variable, redefined once for dark in the same three places clean-ui.css uses
   (data-theme="dark", and the OS preference when no theme is set).

   The one idea: a working day drawn as a strip. A lime band is when someone is
   due, a forest bar is when they were clocked in, ochre is the time they were
   late or missing, and a thin red line is now. The same strip reads on the
   Overview and on the daily timesheet, and the week shows each day as a bar on
   one 12-hour scale. No chart library; SVG geometry is in minutes of the day.
   ========================================================================== */

:root {
  --band-bg: #123a2b;          /* the site's forest, committed to one surface */
  --band-ink: #f3f1e9;
  --band-muted: #bccab4;
  --band-rule: rgba(243, 241, 233, .18);
  --band-accent: #d3eb96;      /* lime */
  --band-warn: #f0cf7e;
  --tl-sched: #d9e7b4;
  --tl-work: #123a2b;
  --tl-open: #3f7a58;
  /* Round 19 P4: the pale ground under the hatched "clock-in awaiting review" bar. */
  --tl-pend-bg: #f1f5e7;
  --tl-late: #c7861a;
  --tl-miss: #e7c679;
  --tl-now: #b3261e;
  --tl-tick: #202820;
  --tl-hour: #e4e5dc;
  --hol: #7f5410;
  --hol-bg: #f5ead0;
  --miss-row: #fbf4e2;
  /* Round 7: one token for a flagged row (open shift, clock anomaly) and its edge. */
  --flag-row: #f5ead0;
  --flag-edge: #c7861a;
}
:root[data-theme="dark"] {
  --band-bg: #173a29;
  --band-ink: #f3f1e9;
  --band-muted: #b7c6ad;
  --band-rule: rgba(243, 241, 233, .16);
  --band-accent: #d3eb96;
  --band-warn: #f0cf7e;
  --tl-sched: #34462f;
  --tl-work: #d3eb96;
  --tl-open: #9fc467;
  --tl-pend-bg: #1d2a20;
  --tl-late: #ebc97c;
  --tl-miss: #6f5a2b;
  --tl-now: #f1a99d;
  --tl-tick: #eceee4;
  --tl-hour: #29332b;
  --hol: #ebc97c;
  /* Round 7 (critique): flagged rows and the holiday column were a muddy amber-brown
     (#362d18 / #2a2718). A flagged row is now the palette's raised surface with an
     amber edge; ink on it is 12:1, the amber warn text 9:1 (WCAG AA). */
  --hol-bg: #222b20;
  --miss-row: #232c24;
  --flag-row: #232c24;
  --flag-edge: #ebc97c;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --band-bg: #173a29;
    --band-ink: #f3f1e9;
    --band-muted: #b7c6ad;
    --band-rule: rgba(243, 241, 233, .16);
    --band-accent: #d3eb96;
    --band-warn: #f0cf7e;
    --tl-sched: #34462f;
    --tl-work: #d3eb96;
    --tl-open: #9fc467;
    --tl-pend-bg: #1d2a20;
    --tl-late: #ebc97c;
    --tl-miss: #6f5a2b;
    --tl-now: #f1a99d;
    --tl-tick: #eceee4;
    --tl-hour: #29332b;
    --hol: #ebc97c;
    /* Round 7 (critique): flagged rows and the holiday column were a muddy amber-brown
       (#362d18 / #2a2718). A flagged row is now the palette's raised surface with an
       amber edge; ink on it is 12:1, the amber warn text 9:1 (WCAG AA). */
    --hol-bg: #222b20;
    --miss-row: #232c24;
    --flag-row: #232c24;
    --flag-edge: #ebc97c;
  }
}

/* ---------- The day, on the forest band ---------- */
.today {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  gap: 28px 48px;
  margin: 0 0 28px;
  padding: 40px 44px 36px;
  border-radius: var(--radius);
  background: var(--band-bg);
  color: var(--band-ink);
}
.today-co { margin: 0; color: var(--band-muted); font-size: 15px; font-weight: 600; }
.today h1 {
  margin: 6px 0 14px;
  color: var(--band-ink);
  font-size: 72px;
  line-height: .98;
  letter-spacing: -.035em;
}
.today-line { max-width: 44ch; margin: 0 0 8px; font-size: 20px; line-height: 1.4; }
.today-line strong { color: var(--band-warn); font-weight: 650; }
.today-meta { margin: 0; color: var(--band-muted); font-size: 14px; }
.today-meta a { color: var(--band-accent); }
.today a:focus-visible { outline-color: var(--band-accent); }

.today .kpi-row {
  display: grid;
  grid-template-columns: repeat(5, minmax(88px, auto));
  gap: 0;
  margin: 0;
  border: 0;
  border-radius: 0;
  background: none;
  overflow: visible;
}
.today .kpi-tile { padding: 4px 18px 2px; border-left: 1px solid var(--band-rule); background: none; color: var(--band-ink); }
.today .kpi-tile:first-child { border-left: 0; padding-left: 0; }
.today .kpi-num { color: var(--band-ink); font-size: 48px; }
.today .kpi-of { color: var(--band-muted); }
/* Critique round 4 #12: max-width:11ch broke every label in two ("Not in, past /
   start"). On a wide band each tile is as wide as its label; narrower screens let
   the grid decide and wrap only when a column really is too narrow. */
.today .kpi-label { margin-top: 8px; color: var(--band-muted); font-size: 14px; line-height: 1.3; }
@media (min-width: 1101px) { .today .kpi-label { white-space: nowrap; } }
.today .kpi-tile.has .kpi-num { color: var(--band-warn); }
.today .kpi-tile.kpi-miss.has .kpi-num { color: var(--band-accent); }
.today .kpi-tile.kpi-miss.has .kpi-label { color: var(--band-ink); }

/* ---------- The board ---------- */
.live-head .section-note { margin: 4px 0 0; }
.board-grid td { vertical-align: middle; }
.board-grid .worker-num { margin-left: 6px; color: var(--muted); font-size: 13px; font-variant-numeric: tabular-nums; }
.board-grid .status-cell { min-width: 13rem; }
/* 🔴 Critique round 21 P4: from 761 to 884 px the Overview table was wider than its box (up to
   101 px at 761): the Worker column never wrapped (.grid td is nowrap), so one long name held it
   at 340 px, and the timeline's 18 and 21 and the evening strips sat under the right-edge fade.
   From 761 to 1000 px (the tight band) a name may now wrap inside its own column (the worker
   number stays whole), the strip takes 36% and may be 200 px narrow (its ruler thins its labels,
   .tl-axis above), and the status line under the chip is at most 13rem wide, so it does not take
   the room a name needs: a name wraps only when the row has no room for it (measured: none of
   the walk's 42 names wraps at 1000 px, the longest 12 do at 761). From 1001 px the table is laid
   out exactly as before. Swept at every width from 320 to 1920: nothing scrolls sideways. */
/* 🔴 Round 21 follow-up (defect 6): the Where column never wrapped either, so a worker at a site
   with a long name (sites take up to 120 characters) made the table 641 px wider than its box at
   1001 px and 402 px at 1920. The site's name now wraps inside its pill, between words, or
   anywhere in a name with no spaces, and the pill is at most 16rem wide; the column keeps 10rem so a
   long name takes a few lines, never one word per line. A 12 px corner: one line still reads as a
   pill, and a wrapped name is a tag, not a tall oval. */
.board-grid td[data-label="Where"] { min-width: 10rem; white-space: normal; }
.board-grid .site-pill { max-width: 16rem; border-radius: 12px; white-space: normal; overflow-wrap: anywhere; }
.board-grid .worker-cell .avatar { flex: none; }
.board-grid .worker-num { white-space: nowrap; }
/* 🔴 Critique round 22 P9 (the scroll-length score, 8.8): 39 workers were 5885 px (6.6 screens) at 1440, rows
   about 118 px. The Status column (about 230 px) wrapped to 4-5 lines, the strip took 474 px and the Worker
   column 340 px (a name needs 200), beside a Where column that is mostly a dash. From 761 px:
   - the status line is one paragraph: the chip, then its words, then its links, wrapping together (they were
     three stacked blocks, so a row with words and a link was always at least three lines);
   - the columns have fixed shares, Status the widest (40%, 44% when no row has a site), the strip 27% (36%
     without Where; at least 200 px, its ruler thins its labels), Worker 19% (20%), Where 14% (it keeps 10rem
     and the long-name wrap below);
   - a name may wrap inside its own cell (the number stays whole), which the round-21 tight band already did
     from 761 to 1060 px. Nothing is clipped and no words are dropped; below 761 px the table is cards, as before.
   Measured in real Chrome on the walk data, 320-1920 px: no sideways scroll; median row and page length in
   test/r22a-density-2026-09-29.test.js. */
@media (min-width: 761px) {
  .board-grid td[data-label="Worker"] { white-space: normal; }
  /* .grid td is nowrap, and that also glued the chip to the first word after it (no break at the space between
     them): the cell could not be narrower than 238 px. The chip and the links carry their own nowrap. */
  .board-grid td.status-cell { white-space: normal; }
  .board-grid .status-cell > .status-detail { display: inline; margin: 0 0 0 8px; }
  .board-grid .status-cell > .board-fix { display: inline-block; margin: 0 0 0 10px; }
  .board-grid .status-cell > .board-fixes { display: inline; margin: 0 0 0 10px; }
  .board-grid .status-cell > .board-fixes > li { display: inline; }
  .grid.board-grid th.col-worker { width: 19%; }
  .grid.board-grid th.col-status { width: 40%; }
  .grid.board-grid th.tl-col, .grid.board-grid td.tl-col { width: 27%; min-width: 200px; }
  .grid.board-grid th.col-where { width: 14%; }
  .board-grid:not(.has-where) th.col-worker { width: 20%; }
  .board-grid:not(.has-where) th.col-status { width: 44%; }
  .grid.board-grid:not(.has-where) th.tl-col, .grid.board-grid:not(.has-where) td.tl-col { width: 36%; }
}
/* The tight band (761-1060 px): a name gets a little more of the row (a 940 px table gave it 179 px: three
   lines for "Walk R20 Folded Duplicate"), the strip a little less (it may be 200 px: its ruler thins its labels). */
@media (min-width: 761px) and (max-width: 1060px) {
  .grid.board-grid th.col-worker { width: 24%; }
  .grid.board-grid th.col-status { width: 36%; }
  .grid.board-grid th.tl-col, .grid.board-grid td.tl-col { width: 23%; }
  .grid.board-grid th.col-where { width: 17%; }
  .board-grid:not(.has-where) th.col-worker { width: 25%; }
  .board-grid:not(.has-where) th.col-status { width: 40%; }
  .grid.board-grid:not(.has-where) th.tl-col, .grid.board-grid:not(.has-where) td.tl-col { width: 35%; }
}
.status-detail { display: block; margin-top: 4px; color: var(--muted); font-size: 15px; white-space: normal; }
.status-detail.warn { color: var(--warn); font-weight: 600; }
.chip.miss { border-color: var(--warn-line); background: var(--warn); color: var(--surface); }
:root[data-theme="dark"] .chip.miss { color: #1b1a10; }
@media (prefers-color-scheme: dark) { :root:not([data-theme]) .chip.miss { color: #1b1a10; } }
.grid tr.tone-miss > td, .grid tr.tone-miss:hover > td { background: var(--miss-row); }
.grid tr.tone-neutral > td, .grid tr.tone-out > td { color: var(--muted); }
.grid tr.tone-neutral .worker-name, .grid tr.tone-out .worker-name { color: var(--ink-soft); font-weight: 500; }

/* ---------- The day strip ---------- */
.grid th.tl-col, .grid td.tl-col { width: 42%; min-width: 280px; padding-top: 8px; padding-bottom: 8px; }
.tl { display: block; width: 100%; height: 28px; overflow: visible; }
.tl-hour { stroke: var(--tl-hour); stroke-width: 1; }
.tl-sched { fill: var(--tl-sched); }
.tl-late { fill: var(--tl-late); }
.tl-miss { fill: var(--tl-miss); }
.tl-work { fill: var(--tl-work); }
.tl-work.tl-open { fill: var(--tl-open); }
/* Round 19 P4: an earlier day's shift still running on a clock-in awaiting review: outlined and
   hatched in the open-shift colour, so it reads as "still clocked in, not counted yet". */
.tl-pend { fill: var(--tl-pend-bg); stroke: var(--tl-open); stroke-width: 1; }
.tl-hatch { stroke: var(--tl-open); stroke-width: 1.5; }
.tl-tick { stroke: var(--tl-tick); stroke-width: 2; }
.tl-tick.tl-out { stroke-dasharray: 3 2; }
/* Round 19 follow-up: a break scan with no usable shift (tea / lunch start or end), dotted, never a clock-in. */
.tl-tick.tl-break { stroke-dasharray: 1 2; }
.tl-now { stroke: var(--tl-now); stroke-width: 2; }
.tl-edge { fill: var(--tl-tick); opacity: .55; }
.tl-axis { display: grid; width: 100%; color: var(--muted); font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; line-height: 1; }
.tl-axis > span { justify-self: start; white-space: nowrap; transform: translateX(-50%); }
/* Critique round 20 F5 (W-AUD24-158): the first hour sat left-aligned on its tick while every other
   label is centred on its own, so where the window starts at midnight and the column is narrow
   (the daily sheet at 1000-1120 px: 24 hours in about 180 px) "00" ran into "03" and read "0003".
   Centred like the others it gets the same gap as every other pair; the cell's own padding (8 px
   and more, wider than half a label) holds its left half. The phone ruler under a card has no
   padding beside it, so its first label keeps starting on its tick. */
.tl-axis.tl-axis-phone > span:first-child { transform: none; }
/* Round 20 follow-up (defect 2): no two labels on the ruler above the column ever touch. The hour
   columns are minmax(0, 1fr), so a label no longer widens its column (it used to, and at 761-900 px
   the grid ran out of its 51-135 px cell with every label butted against the next). The usual
   labels (tl-s, every 1-3 h, at most 8 of them) need 18 px each (15 px wide plus a 3 px gap):
   below 144 px the ruler shows the 6-hourly ones (tl-x6, at most 4), below 72 px the 12-hourly
   ones, below 36 px none. The strips keep every hour line; the words beside each keep the times.
   Round 30 (critic r29 item 1): 3 px was too tight to read ("00 03 06 09" ran together at 761-768 and 900, gaps 3.3 / 4.2 / 2.9 px,
   and at 1101-1115): each label now gets 23 px (15 wide plus at least 8 px of air), so the 3-hourly set (8 labels) needs 185 px, the
   6-hourly (4) 88 px and the 12-hourly (2) 44 px (22 px a label); where the ruler is narrower it thins to the next set. Above the daily sheet the 3-hourly set needs 191 px (8.8 px of
   air): its Day column steps down 23 px once, at 861 px, where the page's own side margin steps (16 px to 28 px), and at 185 the ruler
   would go 3-hourly, 6-hourly, 3-hourly between 860 and 890 px. */
.tl-axis:not(.tl-axis-phone) { container-type: inline-size; }
.tl-axis > span.tl-x6:not(.tl-s) { visibility: hidden; }
@container (max-width: 184.9px) {
  .tl-axis > span.tl-s:not(.tl-x6) { visibility: hidden; }
  .tl-axis > span.tl-x6 { visibility: visible; }
}
@container (max-width: 190.9px) {
  .ts-grid .tl-axis > span.tl-s:not(.tl-x6) { visibility: hidden; }
  .ts-grid .tl-axis > span.tl-x6 { visibility: visible; }
}
@container (max-width: 87.9px) { .tl-axis > span.tl-x6:not(.tl-x12) { visibility: hidden; } }
@container (max-width: 43.9px) { .tl-axis > span.tl-x12 { visibility: hidden; } }
.tl-n1 { grid-template-columns: repeat(1, minmax(0, 1fr)); } .tl-n2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.tl-n3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } .tl-n4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.tl-n5 { grid-template-columns: repeat(5, minmax(0, 1fr)); } .tl-n6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
.tl-n7 { grid-template-columns: repeat(7, minmax(0, 1fr)); } .tl-n8 { grid-template-columns: repeat(8, minmax(0, 1fr)); }
.tl-n9 { grid-template-columns: repeat(9, minmax(0, 1fr)); } .tl-n10 { grid-template-columns: repeat(10, minmax(0, 1fr)); }
.tl-n11 { grid-template-columns: repeat(11, minmax(0, 1fr)); } .tl-n12 { grid-template-columns: repeat(12, minmax(0, 1fr)); }
.tl-n13 { grid-template-columns: repeat(13, minmax(0, 1fr)); } .tl-n14 { grid-template-columns: repeat(14, minmax(0, 1fr)); }
.tl-n15 { grid-template-columns: repeat(15, minmax(0, 1fr)); } .tl-n16 { grid-template-columns: repeat(16, minmax(0, 1fr)); }
.tl-n17 { grid-template-columns: repeat(17, minmax(0, 1fr)); } .tl-n18 { grid-template-columns: repeat(18, minmax(0, 1fr)); }
.tl-n19 { grid-template-columns: repeat(19, minmax(0, 1fr)); } .tl-n20 { grid-template-columns: repeat(20, minmax(0, 1fr)); }
.tl-n21 { grid-template-columns: repeat(21, minmax(0, 1fr)); } .tl-n22 { grid-template-columns: repeat(22, minmax(0, 1fr)); }
.tl-n23 { grid-template-columns: repeat(23, minmax(0, 1fr)); } .tl-n24 { grid-template-columns: repeat(24, minmax(0, 1fr)); }
.tl-key { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 18px; margin: 12px 0 0; color: var(--muted); font-size: 14px; }
.tl-key > span { white-space: nowrap; }
.tl-key .k { display: inline-block; width: 18px; height: 10px; margin-right: 7px; vertical-align: middle; }
.tl-key .k-sched { background: var(--tl-sched); }
.tl-key .k-work { background: var(--tl-work); }
.tl-key .k-open { background: var(--tl-open); }
.tl-key .k-pend { background: repeating-linear-gradient(135deg, var(--tl-open) 0 1.5px, var(--tl-pend-bg) 1.5px 4.5px); box-shadow: inset 0 0 0 1px var(--tl-open); }
.tl-key .k-late { background: var(--tl-late); }
.tl-key .k-miss { background: var(--tl-miss); }
.tl-key .k-now { width: 2px; height: 14px; background: var(--tl-now); }
/* Round 11b P7: the edge notch (.tl-edge) for a scan outside the drawn hours, same colour. */
.tl-key .k-edge { width: 4px; height: 14px; background: var(--tl-tick); opacity: .55; }
/* Round 10 #6: a past day's open shift is drawn as its clock-in tick alone, so its key
   is that tick, in the tick's own colour (.tl-tick). */
.tl-key .k-noout { width: 2px; height: 14px; margin-left: 8px; margin-right: 15px; background: var(--tl-tick); }
/* Round 15 P3: the scan ticks themselves, keyed like the one above. The clock-out swatch
   is dashed 3 on 2, the same pattern as .tl-tick.tl-out on the strip. */
.tl-key .k-tick-in, .tl-key .k-tick-out { width: 2px; height: 14px; margin-left: 8px; margin-right: 15px; }
.tl-key .k-tick-in { background: var(--tl-tick); }
.tl-key .k-tick-out { background: repeating-linear-gradient(to bottom, var(--tl-tick) 0 3px, transparent 3px 5px); }
.tl-key .k-tick-break { width: 2px; height: 14px; margin-left: 8px; margin-right: 15px; background: repeating-linear-gradient(to bottom, var(--tl-tick) 0 1px, transparent 1px 3px); }

/* ---------- Quick clock: real labels, the error inside ---------- */
.quick-clock { grid-template-columns: minmax(0, 1.2fr) 150px minmax(0, 1fr) auto; align-items: end; }
/* This file loads after clean-ui.css, so its four columns also beat the phone
   rule there and squeezed the Worker box to two letters at 390 px. */
@media (max-width: 760px) { .quick-clock { grid-template-columns: minmax(0, 1fr); } }
.qc-field { display: grid; gap: 6px; color: var(--ink-soft); font-size: 14px; font-weight: 600; }
.qc-field input, .qc-field select { width: 100%; }
.quick-clock-wrap > .notice { margin: 14px 16px 0; }

/* ---------- One timesheet toolbar (all four views) ---------- */
.ts-bar { margin: 0 0 18px; }
.ts-bar-top { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px 24px; }
.ts-bar-top h1 { margin: 0; }
.ts-bar-tools { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px 24px; margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--line); }
.ts-when { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.ts-pick { display: flex; align-items: center; gap: 8px; }
.ts-pick input { width: auto; min-width: 0; }
.ts-pick input.ts-year { width: 7.5rem; }
.ts-step { width: 44px; padding: 0; }
.ts-step .icon { width: 18px; height: 18px; }
.ts-now { margin-left: 4px; font-weight: 600; }
.ts-day { margin-top: 0; }
.ts-day strong, .period-grid + p strong { color: var(--ink); }

/* ---------- Timesheet tables ---------- */
.grid .num { font-variant-numeric: tabular-nums; }
.ts-grid td.num > span[role="status"] { text-align: left; }
/* The per-row unpaid-break mark: compact, on the Worked line (said once above the table). */
.ab-mark { margin-left: 4px; color: var(--ink-soft); font-size: 14px; white-space: nowrap; }
/* Round 6 #8: leaving early, beside the clock-out it is measured from. Same plain
   words as the Late column ("1 h 30 min"), on its own line so the Out column stays narrow. */
.early-v { display: block; color: var(--ink-soft); font-size: 14px; font-weight: 600; white-space: nowrap; }
.ab-line { margin-top: -4px; }
/* Narrower desktops: the timeline gives way before the table scrolls sideways. */
@media (max-width: 1200px) { .ts-grid th.tl-col, .ts-grid td.tl-col { min-width: 200px; } }
@media (max-width: 1100px) { .ts-grid th.tl-col, .ts-grid td.tl-col { min-width: 168px; } }
/* W-AUD24-74 (round 6): since durations read "8 h 6 min" the daily table was 952 px in a
   942 px box at 1000, so the timeline scrolled behind the fade. Between the phone cards
   and 1100 px the cells give up some padding instead: the table fits and the strip keeps
   its width. */
@media (min-width: 761px) and (max-width: 1100px) {
  .ts-grid th, .ts-grid td { padding-left: 6px; padding-right: 6px; }
  /* ^ Round 28 (critic r28 N1 + N4): 6 px sides (were 8) so the "#" column can hold a worker number of 8 characters and the timeline
     keeps 160 px or more at 761 px; the "#" cell has its own padding (below, from 761 px). */
  .ts-grid td.ts-out { min-width: 7.5rem; }
  /* The timeline cell keeps 10 px on its left: the ruler's first label ("00") is centred on the cell's edge and its left half (7.5 px) lives in
     that padding (round 20 F5), and 10 px leaves 8.5 px between "Late" and that label (it was 8 px, 6.5 px from 900 px, round 30 item 1). */
  .ts-grid th.tl-col, .ts-grid td.tl-col { padding-left: 10px; padding-right: 4px; }
}
.grid tr.miss > td { background: var(--miss-row); }
.day-state { color: var(--muted); }
.day-state.miss { color: var(--warn); font-weight: 600; }
.fix-row > td { padding: 14px 16px 16px; background: var(--warn-bg); white-space: normal; }
.grid tr.fix-row:hover > td { background: var(--warn-bg); }
.fixform { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px 12px; }
.fixform .fix-title { flex: 1 0 100%; margin: 0; color: var(--ink); }
.fixform .notice { flex: 1 0 100%; margin: 0; }
.fix-field { display: grid; gap: 5px; color: var(--ink-soft); font-size: 14px; font-weight: 600; }
.fix-reason { flex: 1 1 18rem; }
.fix-reason input { width: 100%; }
/* Round 5 #2: one form at a time. A hidden fix row must stay hidden even where a
   phone rule below gives table rows display:block (the UA [hidden] rule loses). */
.ts-grid tr.fix-row[hidden] { display: none !important; }
/* The Out cell may wrap between the chip and its link, so a row of open shifts
   never pushes the timeline out of the table. */
.ts-grid td.ts-out { min-width: 8.5rem; white-space: normal; }
.ts-grid td.ts-out .chip { white-space: nowrap; }
/* Critique round 22 P10: on a day of several sessions the In time is the day's first clock-in ("first in"),
   and an on-site row says when the current session began ("since 13:58"): a quiet second line under the time. */
.ts-first { display: block; margin-top: 1px; color: var(--muted); font-size: 12px; font-weight: 500; line-height: 1.2; white-space: nowrap; }
.ts-first.ts-last { display: inline; margin-left: 3px; } /* Round 28 N5: "06:00 last out" on one line where there is room (the Out cell of a row whose open session is an earlier one) */
.fix-link { display: inline-block; margin-left: 6px; color: var(--brand); font-size: 14px; font-weight: 600; white-space: nowrap; }
.fix-link[aria-expanded="true"] { color: var(--ink); text-decoration: none; }
/* Round 15 P17: Cancel was centred on the whole line (labels + inputs), so it sat 14 px higher
   than "Set clock-out" at 1000 px and up. It is bottom-aligned like the button and as tall
   (44 px, centred), so its text shares the button's line, and it is a full-size tap target. */
.fixform .fix-cancel { align-self: flex-end; display: inline-flex; align-items: center; min-height: 44px; color: var(--ink-soft); font-size: 14px; font-weight: 600; }
/* Keep the two correction actions together when the reason field needs another row. */
@media (min-width: 761px) and (max-width: 1100px) {
  .fixform { display: grid; grid-template-columns: max-content minmax(0, 1fr) max-content max-content; }
  .fixform .fix-title, .fixform .notice { grid-column: 1 / -1; }
  .fixform .fix-reason { grid-column: 1 / 3; min-width: 0; }
}
/* Round 5 #5: the Late column holds a duration, never a sentence. */
.ts-grid th.ts-late, .ts-grid td.ts-late { width: 1%; white-space: nowrap; }
.ts-grid td.ts-late > span[role="status"] { display: block; min-width: 11rem; max-width: 13rem; white-space: normal; }
/* Critique round 9 B2 (2026-09-27): the daily sheet was 1238 px in its 1182 px box at
   1280-1920 (a long name and the "Did not clock in (due ...)" state set the column
   widths, a 23:22 clock-out widened the axis), so the timeline shrank under the right-edge
   fade and opening Set clock-out scrolled the box sideways. On a desktop the columns now
   have set widths: the number columns and In/Out are fixed, long names, the Out chip and
   the day state wrap inside their own column, and the timeline takes everything left.
   No row can widen the table past its box any more. Phone cards (below 761 px) untouched. */
@media (min-width: 761px) {
  .ts-grid { table-layout: fixed; }
  /* Round 28 (critic r28 N1): the "#" column holds a worker number of 6 to 8 characters (RT9201, EMP00123) inside its own cell at
     every width from here up. It was 64 px (56 at 761-1100) holding nowrap text 7-12 px wider than itself: at 761-1000 "RT9201"
     touched the first letter of the name beside it. 96 px = 12 px left + 4 px right padding + 80 px of 13 px text (the widest
     8-character number measured, "MWMW9999", is 76 px); a longer or wider number wraps inside its cell, never over its neighbour. */
  .ts-grid th.ts-num { width: 96px; }
  .ts-grid th.ts-num, .ts-grid td.ts-num { padding-left: 12px; padding-right: 4px; }
  .ts-grid td.ts-num { font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* Round 30b (F2): one line, "..." when it does not fit (the full number is in the cell's title); it was wrapped mid-word */
  /* The week, month and year hold the same number in a nowrap cell, and 8-character numbers (EMP00123, EMP00124 in the walk copy) made
     the week 1193 px in its 1182 px frame at 1240+ (956 in 942 at 1000), with the Name column pushed narrower by the same 14-24 px. The same
     13 px type and 4 px right padding take that back: the week fits 1182 at 1240+, 944 in 942 at 1000; 6-character numbers only gain room. */
  .period-grid td.ts-num { font-size: 13px; max-width: 78px; overflow: hidden; text-overflow: ellipsis; } /* Round 30b (F3): the column is never wider than 78 px; a longer number ends in "..." (title = the full number) instead of widening the table */
  .period-grid th.ts-num, .period-grid td.ts-num { padding-right: 2px; } /* Round 30 (item 7): was 4 px, see the 1000-1239 note below */
  /* Round 21 P5: 22% and 136, were 15% and 164 (see the note after this block). */
  .ts-grid th.ts-name { width: 22%; }
  .ts-grid th.ts-in { width: 68px; }
  .ts-grid th.ts-out { width: 136px; }
  .ts-grid th.ts-dur.ts-worked { width: 110px; }
  .ts-grid th.ts-dur, .ts-grid th.ts-late, .ts-grid td.ts-late { width: 104px; }
  .ts-grid th.tl-col, .ts-grid td.tl-col { width: auto; min-width: 0; }
  /* Round 27 (critic r27 ship-block + polish 3): break-word, not anywhere. `anywhere` lets a flex item shrink to ONE character
     (min-content), and the Out chip is a flex box: "awaiting review" came out as a column of single letters at 768-1100 and
     "await / ing" at 1200+. A word now moves to the next line whole, and is cut only when it cannot fit a line by itself. */
  .ts-grid td.ts-name, .ts-grid td.ts-state, .ts-grid td.ts-out, .ts-grid td.num { min-width: 0; white-space: normal; overflow-wrap: break-word; }
  /* The Out chip is ONE block of text (not inline-flex: the .nb "clock-out" and the words after it were two flex items), wrapping
     between words only, as a tag with a corner, not a tall oval. */
  .ts-grid td.ts-out .chip { display: inline-block; max-width: 100%; padding: 2px 10px; border-radius: 12px; line-height: 1.35; text-align: left; overflow-wrap: normal; word-break: normal; vertical-align: middle; }
  .ts-grid td.ts-out .chip, .ts-grid td.ts-out .early-v, .ts-grid td.ts-name .ev-flag { white-space: normal; }
  .ts-grid td.ts-late > span[role="status"] { min-width: 0; max-width: none; }
}
/* 🔴 Critique round 21 P5: at 1440 the Name column wrapped most names onto two lines while about
   200 px stood empty between a short Out ("04:34", in a 164 px column) and the Worked figure. Name
   now takes 22% (260 px of the 1182 px box, was 177) and Out 136 px (was 164; its chip and Set
   clock-out still wrap inside it); from 1101 to 1199 px Name 180 px (was 164, below) and Out 136
   (was 152), so the timeline there keeps what it had. The timeline takes the rest (232 px at
   1200+). Measured on the walk's 37 names at 1280-1920: 0 wrap (was 29). The duration columns
   are unchanged: "12 h 30 min" still fits on one line. */
/* Above 1100 px the number columns and Out keep 10 px sides, so "10 h 21 min" stays on one line. */
@media (min-width: 1101px) {
  .ts-grid th.ts-out, .ts-grid td.ts-out, .ts-grid th.num, .ts-grid td.num { padding-left: 10px; padding-right: 10px; }
  /* Round 28 (critic r28 N1): the wider "#" column is paid for here (In 60 px, was 68: "08:00" and "first in" are 39 px) and by the Name
     column below, so the timeline keeps 175 px at 1101 and 214 at 1200 (it was 167 / 201) and its hour ruler keeps its 3-hourly labels. */
  .ts-grid th.ts-in { width: 60px; }
}
@media (min-width: 1200px) {
  .ts-grid th.ts-name { width: calc(22% - 22px); }
}
@media (min-width: 1101px) and (max-width: 1199px) {
  .ts-grid th.ts-name { width: 11%; }
  .ts-grid th.ts-out { width: 136px; }
  .ts-grid th.ts-dur.ts-worked { width: 108px; }
  .ts-grid th.ts-dur, .ts-grid th.ts-late, .ts-grid td.ts-late { width: 96px; }
}
/* 761-1100 px (8 px sides already, above): 15 px figures, and the Out chip may take two lines. */
@media (min-width: 761px) and (max-width: 1100px) {
  table.grid.ts-grid { font-size: 15px; }
  /* (Round 30: the column widths of this range are one ramp, in the "Round 30" block at the end of the daily-sheet rules below.) */
  /* A duration that does not fit one line breaks into two even halves ("1 h" over "37 min"), never "1 h 37" over "min". */
  .ts-grid td.num { text-wrap: balance; }
}
/* Round 5 #12: a paid clock event that was flagged when written. Information, not an alarm. */
/* On its own line under the name, so it never widens the Name column. */
.ev-flag { display: table; margin-top: 3px; padding: 0 7px; border: 1px solid var(--line); border-radius: 999px; color: var(--ink-soft); font-size: 13px; font-weight: 600; line-height: 1.6; vertical-align: 1px; white-space: nowrap; }

/* The week: each day's hours as a bar on one 12-hour scale. */
.period-grid th.day-col, .period-grid td.day-col { min-width: 5rem; } /* was 5.2rem: a week with Statutory OT + Auto break was 4 px over its 1182 px frame (2026-09-27) */
/* Round 6 #2: the week was 1247 px in a 1182 px box at 1280-1920, so the Auto break
   column (the lunch deduction) sat off-screen. Narrower cell padding and two-word
   headers that may wrap ("Statutory / OT") bring every column inside the frame. */
.period-grid th, .period-grid td { padding-left: 9px; padding-right: 9px; }
.period-grid th:first-child, .period-grid td:first-child { padding-left: 10px; } /* Round 30 (item 7): was 14 px: the widest 8-character number (MWMW9999, 76 px) made the week 3 px wider than its frame at 1240+ */
.period-grid th:last-child, .period-grid td:last-child { padding-right: 14px; }
.period-grid thead th.th-wrap { min-width: 4.5rem; white-space: normal; }
/* A 1000-1200 px laptop: slightly smaller figures and padding, so the week still fits. */
@media (min-width: 1000px) and (max-width: 1200px) {
  table.grid.period-grid { font-size: 15px; }
  .period-grid th, .period-grid td { padding-left: 6px; padding-right: 6px; }
  .period-grid th.day-col, .period-grid td.day-col { min-width: 4rem; }
  /* Round 26 P1: the week with the Limits column (someone over a BCEA ceiling) was 1021 px in a 942-982 px frame at
     1000-1040 px: the Limits cell, 8.5rem wide, sat half under the scroll edge ("1 day over" cut). Its words wrap, so
     it may be 5rem here, and a day column may be 4rem (its chips wrap inside it). Measured in Chrome, the walk
     database, the week of 28 Sep: the table needs 927 px (was 1021) and fits at every width 1000-1200. */
  .period-grid th.limits, .period-grid td.limits { min-width: 5rem; }
}
/* Under ~1200 px (was 1060; a 13-column week was 1077 px in 1062 at 1120, 2026-09-27) a week with every optional column can still be a little wider than
   the frame: the name then stays put while the days scroll (as on a phone before). */
@media (min-width: 1000px) and (max-width: 1200px) {
  .period-grid .ts-name { position: sticky; left: 0; z-index: 1; background: var(--surface); box-shadow: 1px 0 0 var(--line-soft); }
  .period-grid thead .ts-name { background: var(--surface-2); }
  .period-grid tr.open > .ts-name { background: var(--warn-bg); }
  .period-grid .team-row > .ts-name { background: var(--surface-2); }
}
/* Critique round 14 P3: the year's limit words sit under the name and may wrap, and from
   600 to 1200 px a long name may wrap too, so the twelve months, the totals and the limits
   all fit the frame from 1000 px up (the table was 1251 px in 1182, 1119 in 942). */
.year-grid .yr-limits { white-space: normal; }
/* Round 15 follow-up (orchestrator, measured 2026-09-27): the WEEK table was 1205 px in its
   1182 px frame at 1280-1920 (and 1088 in 942 / 1062 at 1000 / 1120) because one long name
   ("Walk Throwaway R15 Presence") held the Name column at 241 px on one line, so the last
   column hid behind the scroll fade even on a wide screen. From 761 px up a name may wrap
   between 9rem and 13rem on every period table (week, month, year), the same move round 15
   made for the Workers table. Phones keep their card layout. */
@media (min-width: 1000px) {
  .period-grid td.ts-name { white-space: normal; min-width: 9rem; max-width: 13rem; overflow-wrap: anywhere; }
}

@media (min-width: 1000px) {
  .year-grid td.ts-name { white-space: normal; }
}
/* Critique round 16 P5 (measured 2026-09-27, walk DB, Chrome, 1000 px, frame 942): the year was
   961 (Acme) and 1010 (demo) wide, so Total sat half under the scroll fade; the week was 1020
   and 1077, so OT sat under it (Acme: OT at 923-1021). The Name column held 9rem and the day and
   month cells never wrapped. From 761 to 1200 px: the name may go down to 6.5rem (it already
   wraps), the outer cells keep 8 px, and a day or month figure that does not fit may take two
   balanced lines ("72 h" over "24 min"). Totals (Normal, OT, Total) never wrap. Every one of
   these gives only when the frame is short: where a table already fits, nothing moves. After:
   week and year fit 942 at 1000 for both companies, except the 13-column demo week, whose OT is
   inside the frame and whose Statutory OT and Auto break scroll under the pinned name. */
@media (min-width: 1000px) and (max-width: 1200px) {
  .period-grid td.ts-name { min-width: 6rem; } /* Round 30 (item 7): was 6.5rem, 6 px sides on the outer cells (were 8): see the note below */
  .period-grid td.ts-num { max-width: 75px; } /* Round 30b (F3): 6 px left + 2 px right here, so EMP00123 (65.4 px) still fits whole */
  .period-grid th:first-child, .period-grid td:first-child { padding-left: 6px; }
  .period-grid th:last-child, .period-grid td:last-child { padding-right: 6px; }
  .period-grid td.day-col, .period-grid td.mon-col { white-space: normal; text-wrap: balance; }
}
/* Round 30 (critic r29, W-AUD24-203 = item 7): the week with 8-character worker numbers (EMP00123, 65 px of 13 px type, 19 px wider than
   RT9201) was 9 px wider than its 942 px frame at 1000 px, and 23 px wider at 1208 (up to 1230): the compact rules above end at 1200 px
   while the frame is still only 1143-1181 px, so the full-size week (9 px cell sides, Name 9rem, Limits 8.5rem, 16 px type, which needs
   1173 px) overflowed for 30 px. Four things: the number cell's right side is 2 px (was 4), the outer cells' sides are 6 in 1000-1200 (were 8)
   and the first one 10 everywhere (was 14, the widest 8-character number, MWMW9999, 76 px, still made the full-size week 3-5 px too wide),
   the Name may be 6rem (was 6.5rem) in 1000-1200, which together take 10 px there, and the compact rules of 1000-1200 now run to 1239 for
   the WEEK, whose frame reaches its full 1182 px at 1240 (the month and the year fit already and keep their own rules). Measured in
   Chrome with EMP00123, EMP00124 and MWMW9999 the week fitted at 1012-1920 but NOT at 1000-1011 (7 px over at 1000: the widest 8-character
   number needs 88 px of cell and the table had 81): round 30b (F3) caps the number cell instead of squeezing the other columns for it, at 78 px
   (75 px where the outer cells have 6 px sides), so a number of any length - EMP000123, a 12-character one - ends in "..." and can never widen the
   table; the week then fits its frame at every width 1000-1920 with those numbers. The rows over 80 px that hold a two-line
   "awaiting review" mark are the mark's, not the number's (18 of 80 rows with 6- or 8-character numbers alike); not changed here. */
@media (min-width: 1201px) and (max-width: 1239px) {
  table.grid.period-grid:has(th.day-col) { font-size: 15px; }
  .period-grid:has(th.day-col) th, .period-grid:has(th.day-col) td { padding-left: 6px; padding-right: 6px; }
  .period-grid:has(th.day-col) th.ts-num, .period-grid:has(th.day-col) td.ts-num { padding-right: 2px; }
  .period-grid:has(th.day-col) th:first-child, .period-grid:has(th.day-col) td:first-child { padding-left: 6px; }
  .period-grid:has(th.day-col) th:last-child, .period-grid:has(th.day-col) td:last-child { padding-right: 6px; }
  .period-grid:has(th.day-col) th.day-col, .period-grid:has(th.day-col) td.day-col { min-width: 4rem; }
  .period-grid:has(th.day-col) th.limits, .period-grid:has(th.day-col) td.limits { min-width: 5rem; }
  .period-grid:has(th.day-col) td.ts-name { min-width: 6rem; }
  .period-grid:has(th.day-col) td.ts-num { max-width: 75px; }
}
/* Critique round 16 P8: the key above the week, month and year ("What the amber means" /
   "What the marks mean") was a wall of filled amber pills, heavier than the table it explains.
   In the key only, a sample is an outline in the same amber with the same words (the marks in
   the table keep their fill), at the text's own weight. Contrast of the amber text on the page:
   5.8:1 light, 11.3:1 dark (AA). */
.wk-key .chip.warn { background: transparent; font-weight: 500; }
.wk-key .wk-over { font-weight: 500; }
/* Round 6 #7: a clock-in with no clock-out, linked to its fix. */
.period-grid .wk-open { margin-top: 3px; padding: 1px 8px; min-height: 24px; } /* Round 26 P6: 24 px (WCAG 2.2 SC 2.5.8), was 22 */
.period-grid td.day-col > .wk-open:first-child { margin-top: 0; }
/* Critique round 15 P2: the month marks the worker-days behind "N to check" under the name. */
.period-grid .mo-marks { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 3px; white-space: normal; }
.period-grid .mo-marks > .wk-open { margin-top: 0; }
/* ...and a week day with two marks (on site + unverified) stacks them, right-aligned, instead of
   widening the day column (the week was 73 px wider with them side by side). */
@media (min-width: 1000px) {
  .period-grid td.day-col > .wk-open + .wk-open { display: flex; width: max-content; margin-left: auto; }
}
.hbar { display: block; width: 100%; height: 4px; margin-top: 5px; }
.hbar-bg { fill: var(--tl-hour); }
.hbar-v { fill: var(--tl-work); }
.hbar.over .hbar-v { fill: var(--tl-late); }
.hbar.team .hbar-v { fill: var(--tl-open); }
.grid .hol { background: var(--hol-bg); }
.grid tbody tr:hover > td.hol { background: var(--hol-bg); }
.hol-mark { display: block; margin-top: 2px; color: var(--hol); font-size: 13px; font-weight: 600; white-space: normal; text-align: right; }
.hol-line { margin: -6px 0 14px; color: var(--ink-soft); }
.hol-line .hol-mark { display: inline; margin-right: 6px; }
.team-row > th, .team-row > td { border-top: 1px solid var(--line); background: var(--surface-2); color: var(--ink-soft); font-weight: 600; }
.team-row > th { text-align: left; }
/* Round 5 #14: the totals row says what it totals under its own name, not in an
   orphan cell under the Normal column. */
.team-sub { display: block; color: var(--muted); font-size: 13px; font-weight: 500; }

/* ---------- Narrower screens ---------- */
@media (max-width: 1100px) {
  .today { grid-template-columns: 1fr; padding: 32px 32px 28px; }
  .today .kpi-row { grid-template-columns: repeat(5, minmax(0, 1fr)); padding-top: 20px; border-top: 1px solid var(--band-rule); }
}
@media (max-width: 860px) {
  .today h1 { font-size: 52px; }
}
@media (max-width: 760px) {
  /* Critique round 5 #13: on a phone the band is a compact header, so the worker
     list starts above the fold. Five tiles as 3 + 2 small figures (the band was
     640 px tall at 390 px wide: 1 + 2 + 2 big tiles). */
  .today { gap: 14px; margin: -6px 0 18px; padding: 18px 16px 16px; }
  .today h1 { margin: 2px 0 8px; font-size: 38px; }
  .today-line { margin-bottom: 4px; font-size: 17px; }
  .today-meta { font-size: 13px; }
  .today .kpi-row { grid-template-columns: repeat(3, minmax(0, 1fr)); row-gap: 12px; padding-top: 14px; }
  .today .kpi-tile { padding: 0 10px; }
  .today .kpi-tile:nth-child(3n+1) { border-left: 0; padding-left: 0; }
  .today .kpi-num { font-size: 28px; }
  .today .kpi-of { font-size: 13px; }
  .today .kpi-label { margin-top: 4px; font-size: 13px; line-height: 1.25; }

  /* The board and the daily sheet become cards: the status and the strip are
     always on screen, nothing hides off the right edge. */
  .board-grid, .board-grid tbody, .ts-grid, .ts-grid tbody { display: block; }
  .board-grid thead, .ts-grid thead { display: none; }
  .board-grid tr, .ts-grid tr { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px 12px; padding: 14px 14px; border-bottom: 1px solid var(--line-soft); }
  .board-grid td, .ts-grid td { padding: 0; border: 0; white-space: normal; background: none !important; }
  .grid.board-grid tr.tone-miss, .grid.ts-grid tr.miss { background: var(--miss-row); }
  /* Name left, chip right on one line; the words and the strip under them. */
  .board-grid td.status-cell { display: contents; }
  .board-grid td.status-cell > .chip { grid-column: 2; grid-row: 1; align-self: center; justify-self: end; }
  /* Round 14 SB-1: a long chip ("OUT · AWAITING REVIEW", "NOT IN · 2 h 30 min late") took its
     full width and the name column shrank to nothing, so at 320 px the name ran UNDER the chip.
     The name keeps its longest word; the chip wraps onto a second line instead. */
  .board-grid tr { grid-template-columns: minmax(min-content, 1fr) auto; }
  .board-grid td.status-cell > .chip { white-space: normal; text-align: right; }
  .board-grid td.status-cell > .status-detail { grid-column: 1 / -1; margin-top: 0; }
  .board-grid .avatar { width: 28px; height: 28px; font-size: 12px; }
  .board-grid td.tl-col, .ts-grid td.tl-col { grid-column: 1 / -1; width: auto; min-width: 0; padding: 2px 0 0; }
  .board-grid td[data-label="Where"] { grid-column: 1 / -1; }
  .board-grid td[data-label="Where"]:has(> .muted:only-child) { display: none; }
  .ts-grid td.ts-num { grid-column: 2; grid-row: 1; color: var(--muted); font-size: 13px; }
  .ts-grid td.ts-name { grid-column: 1; grid-row: 1; font-weight: 600; }
  /* Label left, the value (and its marks, e.g. the "-1h00" unpaid break) packed right TOGETHER. With
     space-between a value plus its mark spread into three columns and the mark floated off on its own. */
  .ts-grid td:not(.ts-num):not(.ts-name):not(.tl-col) { display: flex; justify-content: flex-start; align-items: baseline; gap: 8px; grid-column: 1 / -1; text-align: left; }
  .ts-grid td:not(.ts-num):not(.ts-name):not(.tl-col)::before { margin-right: auto; }
  /* In and Out share a line: read as "In 07:56" | "Out 17:02", not "07:56 Out". */
  .ts-grid td.ts-in:not(.ts-num):not(.ts-name):not(.tl-col)::before,
  .ts-grid td.ts-out:not(.ts-num):not(.ts-name):not(.tl-col)::before { margin-right: 0; }
  .ts-grid td.is-empty { display: none !important; }
  /* Round 22 P10: "first in" and "since 13:58" wrap under the time inside their half of the card at 320 px
     (they poked 12 px past it on one line). */
  .ts-grid td.ts-in, .ts-grid td.ts-out { flex-wrap: wrap; }
  .ts-first { white-space: normal; }
  .ts-grid tr:not(.fix-row) { grid-template-columns: 1fr 1fr; }
  .ts-grid td.ts-in, .ts-grid td.ts-out { grid-column: auto !important; }
  .ts-grid td.ts-in:not(.is-empty) + td.ts-out { grid-column: 2 !important; }
  .ts-grid td.ts-in.is-empty + td.ts-out { grid-column: 1 / -1 !important; }
  /* Round 5 #2: an open shift carries its "Set clock-out" link, so its Out line takes the
     full card width and wraps instead of pushing the link off the edge. */
  .ts-grid td.ts-in:not(.is-empty) + td.ts-out.has-fix, .ts-grid td.ts-out.has-fix { grid-column: 1 / -1 !important; flex-wrap: wrap; row-gap: 4px; }
  .ts-grid td.ts-out.has-fix .fix-link { margin-left: 0; }
  /* Round 6 #8: "15:30 · 1 h 30 min early" takes the whole card line, like a fix link. */
  .ts-grid td.ts-in:not(.is-empty) + td.ts-out.has-early, .ts-grid td.ts-out.has-early { grid-column: 1 / -1 !important; flex-wrap: wrap; }
  .ts-grid td.ts-out .early-v { display: inline; }
  .ts-grid td.ts-num { justify-self: end; }
  .ts-grid td:not(.ts-num):not(.ts-name):not(.tl-col)::before { content: attr(data-label); color: var(--muted); font-size: 13px; font-weight: 600; }
  .ts-grid td.num > span[role="status"] { min-width: 0; }
  /* The desktop rule sizes the Late column to its content (width:1%); as a phone card row that
     shrank the row so "27 min" sat beside its label instead of on the right (W-AUD24-73). */
  .ts-grid td.ts-late { width: auto; }
  .ts-grid tr.fix-row { display: block; }
  .ts-grid tr.fix-row > td { display: block; padding: 14px; }
  .ts-grid tr.fix-row > td::before { content: none; }
  .fix-field, .fix-reason { flex: 1 1 100%; }
  .fix-field input { width: 100%; }
  .fixform .btn { width: 100%; }

  /* Wide period tables keep scrolling, but the name stays put. */
  .period-grid .ts-num { display: none; }
  .period-grid .ts-name { position: sticky; left: 0; z-index: 1; max-width: 9.5rem; overflow: hidden; text-overflow: ellipsis; background: var(--surface); border-right: 1px solid var(--line-soft); }
  .period-grid thead .ts-name { background: var(--surface-2); }
  .period-grid tr.open > .ts-name { background: var(--warn-bg); }
  .period-grid .team-row > .ts-name { background: var(--surface-2); }

  .ts-bar-tools { align-items: stretch; flex-direction: column; }
  /* WCAG 1.4.10 (critique round 4 #6): this row was nowrap and pushed the page
     sideways at 320 px (scrollWidth 367/392). It wraps now, and on a narrow phone
     the picker takes its own full line with the arrows and Today beneath it. */
  .ts-when { flex-wrap: wrap; }
  .ts-pick { flex: 1 1 auto; min-width: 0; }
  .ts-pick input { flex: 1 1 auto; width: 100%; min-width: 0; }
  .ts-bar .exports { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .ts-bar .exports .btn { min-width: 0; white-space: normal; }
  /* Round 10 #9: "7 to check" drops under "Export XLSX" when the half-width button
     cannot hold both on one line, instead of running out of the button. */
  .ts-bar .exports .btn { flex-wrap: wrap; row-gap: 4px; padding-block: 6px; }
  .ts-bar .exports .btn:last-child { grid-column: 1 / -1; }
  .ts-now { white-space: nowrap; }
  .tl-axis { display: none; }
}
/* Round 6 #14: below 600 px a period row is a CARD, totals first, so nobody has to
   scroll a table sideways to find a person's total. Days (or months) follow in a
   three-across grid, each labelled; an empty one is left out, as on the daily sheet.
   The markup is the same table; only its layout changes.
   🔴 Round 21 follow-up (defect 3): up to 999 px now (was 599). From 600 px the week, month
   and year tables were wider than their box (week up to +400 px, year +306, month +77, measured
   at 1 px steps on the walk data) and scrolled under the right-edge fade until 880 / 973 / 675 px:
   a person's days and totals were hidden. Seven days or twelve months and three totals need
   about 900 px as a table; below 1000 px they are the same cards a phone gets, four across. */
@media (max-width: 999px) {
  .table-scroll:has(> table.period-grid) { overflow: visible; border: 0; background: transparent; -webkit-mask-image: none; mask-image: none; }
  table.grid.period-grid { display: block; border: 0; background: transparent; }
  .period-grid thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  /* Round 30 (critic r29 item 6): the hidden head's cells were laid out as grid items of a 1 px row, so every header word ("Name" by 2 px,
     "Normal" by 47) overhung its own cell box. They are one line each, as wide as their word (the head is still clipped: screen readers read
     it, nobody sees it), so no header text sits outside its cell at any width. */
  .period-grid thead tr { display: block; padding: 0; border: 0; }
  .period-grid thead tr > th { width: max-content; white-space: nowrap; }
  .period-grid tbody, .period-grid tfoot { display: grid; gap: 10px; }
  .period-grid tfoot { margin-top: 10px; }
  .period-grid tr { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px 12px; padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
  .period-grid tr.open { border-color: var(--warn-line); background: var(--warn-bg); }
  .period-grid tr.team-row { background: var(--surface-2); }
  .period-grid tr > td, .period-grid tr > th, .period-grid tr:hover > td { display: block; position: static; max-width: none; min-width: 0; padding: 0; border: 0; background: transparent !important; overflow: visible; text-align: left; white-space: normal; }
  .period-grid tr > .ts-name { grid-column: 1 / -1; order: 0; max-width: none; border: 0; color: var(--ink); font-weight: 600; }
  .period-grid tr > td.num { text-align: left; }
  .period-grid tr > .ts-num { display: none; }
  .period-grid td.tot { order: 2; font-weight: 600; }
  .period-grid td.tot-first { order: 1; }
  .period-grid td.day-col, .period-grid td.mon-col { order: 3; padding-top: 8px; border-top: 1px solid var(--line-soft); }
  .period-grid td.limits { order: 4; grid-column: 1 / -1; }
  .period-grid td[data-label]::before { content: attr(data-label); display: block; margin-bottom: 2px; color: var(--muted); font-size: 13px; font-weight: 600; }
  .period-grid td.hol[data-label]::before { color: var(--hol); }
  .period-grid td.is-empty { display: none; }
  .period-grid tr.row-empty { display: block; }
  .period-grid .hbar { max-width: 6rem; }
}
/* A 320-400 px phone: two across, so "24 h 16 min" stays on one line. */
@media (max-width: 400px) {
  .period-grid tr { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* Round 21 follow-up: a tablet-width card has room for four across. */
@media (min-width: 720px) and (max-width: 999px) {
  .period-grid tr { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (max-width: 420px) {
  .today h1 { font-size: 34px; }
  .ts-pick { order: -1; flex: 1 1 100%; }
}

/* Round 5 #20: the per-row way into the day an anomaly is about. */
.row-fix { font-weight: 600; white-space: nowrap; }
/* Round 13 #4: a day with a pending event links to Review clock-ins AND, when it has
   another flag, to the timesheet: two links side by side, never run together. */
.row-fixes { display: inline-flex; flex-wrap: wrap; justify-content: flex-end; gap: 4px 14px; }
/* Round 13 #8: the anomaly check on a phone is a two-line card: the date with who it is
   (plus the payroll code only when it is not the worker number), then why. */
@media (max-width: 999px) {
  .rows-as-cards.anomaly-days td.cell-primary { display: block; }
  .anomaly-days .anom-who { font-weight: 500; }
  .rows-as-cards.anomaly-days td.cell-why { display: block; padding: 8px 0 0; }
  .rows-as-cards.anomaly-days td.cell-why[data-label]::before { content: none; }
}

/* ---------- Round 7 (critique 2026-09-26) ---------- */
/* #1: the fix link in the hero and on a board row with a missing clock-out. */
.today-line a { color: var(--band-accent); font-weight: 600; }
.today-also { display: block; margin-top: 6px; color: var(--band-ink); font-size: 17px; }
.board-fix { display: inline-block; margin-top: 4px; font-size: 14px; }
/* Round 21 follow-up (defect 3): the Anomaly check's public-holiday worker-days table has ten columns;
   one long name and one-line heads ("Minimum (hrs eq)") made it 1170 px, so from 1000 to 1127 px its
   rand column sat off the edge. On a desktop its heads and names wrap between words. */
@media (min-width: 1000px) {
  .grid.hf-days th, .grid.hf-days td[data-label="Name"] { white-space: normal; }
}
/* Round 21 P10: two or more fix links on a row are a list (dashboard.ejs), a dot between them
   on screen, never "Set clock-out Check it". The dot is decoration: its alternative text is empty,
   so it is not read (the list and the links' own names separate them for a screen reader). */
.board-fixes { display: flex; flex-wrap: wrap; align-items: baseline; margin: 4px 0 0; padding: 0; list-style: none; }
.board-fixes > li { display: inline-flex; align-items: baseline; }
.board-fixes > li + li::before { content: "\00B7"; content: "\00B7" / ""; margin: 0 8px; color: var(--muted); font-weight: 700; }
.board-fixes .board-fix { margin-top: 0; }
/* #11: the search has a visible label, and its answer sits in the flow above the rows
   it filters instead of floating over them. A filtered-out row stays hidden even where
   the phone rule makes rows display:grid. */
.live-head .search label { display: block; margin: 0 0 6px; color: var(--ink-soft); font-size: 14px; font-weight: 600; }
.live-head .qres { position: static; margin-top: 6px; }
.board-grid tr[hidden] { display: none !important; }
/* #2: the flagged worker-days /export would hold, on the Export buttons. */
/* Round 10 #9: the badge carries words now ("7 to check"); nowrap keeps the pill whole
   when a phone's two-up export buttons wrap their label. */
.ex-badge { display: inline-block; white-space: nowrap; min-width: 1.6em; margin-left: 6px; padding: 1px 6px; border-radius: 999px; background: var(--warn); color: var(--surface); font-size: 13px; font-weight: 700; line-height: 1.4; text-align: center; font-variant-numeric: tabular-nums; }
/* #4: a day with no hours says what it is. Round 8 #10: on every screen, not only the
   phone ("day off", "holiday" and "—" used to vanish above 600 px, leaving a blank Sat
   and Sun beside a phone that named them). Quiet, so the hours stay what the eye reads. */
.period-grid .wk-state.chip { padding: 1px 8px; min-height: 22px; }
.period-grid .wk-quiet { color: var(--muted); font-weight: 500; }
@media (min-width: 1000px) { .period-grid .wk-quiet { font-size: 14px; white-space: nowrap; } }
/* Flagged rows, one look in light and dark: the flag tint plus an amber edge. */
.grid.ts-grid tr.open > td, .grid.ts-grid tr.warn > td, .grid.period-grid tr.open > td,
.grid.ts-grid tr.open:hover > td, .grid.period-grid tr.open:hover > td { background: var(--flag-row); }
.grid.ts-grid tr.open > td:first-child, .grid.ts-grid tr.warn > td:first-child, .grid.ts-grid tr.miss > td:first-child,
.grid.period-grid tr.open > td:first-child, .grid.board-grid tr.tone-miss > td:first-child, .grid.board-grid tr.tone-warn > td:first-child { box-shadow: inset 3px 0 0 var(--flag-edge); }
.period-grid tr.open > .ts-name { background: var(--flag-row); }
@media (max-width: 760px) {
  .board-grid td.status-cell > .board-fix, .board-grid td.status-cell > .board-fixes { grid-column: 1 / -1; justify-self: start; margin-top: 0; }
  /* Phone cards: the amber edge moves from the first cell to the card itself. */
  .grid.board-grid tr.tone-miss, .grid.board-grid tr.tone-warn, .grid.ts-grid tr.miss, .grid.ts-grid tr.open { box-shadow: inset 3px 0 0 var(--flag-edge); }
  .grid.board-grid tr.tone-miss > td:first-child, .grid.board-grid tr.tone-warn > td:first-child,
  .grid.ts-grid tr.miss > td:first-child, .grid.ts-grid tr.open > td:first-child { box-shadow: none; }
  .grid.ts-grid tr.open { background: var(--flag-row); }
}
@media (max-width: 599px) {
  /* #6: a phone card drops a strip with nothing drawn on it. */
  .board-grid td.tl-col.tl-empty { display: none; }
}
/* Round 21 follow-up (defect 3): the period cards run to 999 px (see above). */
@media (max-width: 999px) {
  /* #4: totals first, then ALWAYS the seven days (or twelve months) as a fixed strip:
     the first day starts its own row, so a totals cell never sits beside "Mon 21". */
  .period-grid td.day-first { grid-column-start: 1; }
  .period-grid tr.open { border-color: var(--flag-edge); background: var(--flag-row); }
  .period-grid tr.open > .ts-name { background: transparent; }
  /* The strip is compact: each day (or month) is one line, its name left and its
     hours right, so seven days take four short lines instead of seven tall cells. */
  .period-grid td.day-col, .period-grid td.mon-col { display: flex; align-items: baseline; justify-content: space-between; flex-wrap: wrap; gap: 2px 8px; padding-top: 6px; text-align: right; }
  .period-grid td.day-col[data-label]::before, .period-grid td.mon-col[data-label]::before { display: inline; margin: 0; text-align: left; }
  .period-grid td.day-col .hbar { display: none; }
  .period-grid td.day-col > .wk-open, .period-grid td.day-col > .wk-state.chip { margin-top: 0; }
}

/* ---------- Round 8 (critique 2026-09-26) ---------- */
/* #12: the search answer sits under the input and grows DOWN; the heading beside it stays
   where it is instead of being pushed ~100 px lower by a taller search column. */
.live-head { align-items: flex-start; }
@media (max-width: 599px) {
  /* #12: the Overview key names only the marks of strips a phone still shows; with every
     strip dropped there is no key at all (same breakpoint as the dropped strip). */
  .tl-key.tl-key-wide, .tl-key .k-wide { display: none; }
}
@media (max-width: 760px) {
  /* #12: on the daily sheet's card the day's state reads "Status  day off", packed left
     like "Out 17:02" (the card layout starts at 760 px). */
  .ts-grid td.ts-state:not(.ts-num):not(.ts-name):not(.tl-col)::before { margin-right: 0; }
}

/* ---------- Round 12 (critique 2026-09-27) ---------- */
/* SB-2: a flagged row's notes on a full-width line directly under it, starting under the
   name, in the row's own tint and with its amber edge, so they read as part of the row and
   never run over the next column (they used to be 240 px wide in a 110 px Worked cell). */
.row-notes { display: grid; gap: 2px; margin: 0; padding: 0; list-style: none; color: var(--ink-soft); font-size: 15px; line-height: 1.45; white-space: normal; }
.row-notes li { margin: 0; }
.row-notes li.is-warn { color: var(--ink); }
.ts-grid tr:has(+ tr.note-row) > td { border-bottom-color: transparent; }
.grid.ts-grid tr.note-row > td { padding-top: 0; padding-bottom: 12px; }
.grid.ts-grid tr.note-row:hover > td { background: transparent; }
.grid.ts-grid tr.note-row.note-open > td, .grid.ts-grid tr.note-row.note-warn > td,
.grid.ts-grid tr.note-row.note-open:hover > td, .grid.ts-grid tr.note-row.note-warn:hover > td { background: var(--flag-row); }
.grid.ts-grid tr.note-row.note-open > td:first-child, .grid.ts-grid tr.note-row.note-warn > td:first-child { box-shadow: inset 3px 0 0 var(--flag-edge); }
@media (max-width: 760px) {
  /* The phone card: the notes are its last lines, not a card of their own. */
  .ts-grid tr:has(+ tr.note-row) { padding-bottom: 8px; border-bottom: 0; }
  .grid.ts-grid tr.note-row { display: block; padding: 0 14px 14px; }
  .grid.ts-grid tr.note-row > td.note-lead { display: none; }
  .grid.ts-grid tr.note-row > td.ts-note { display: block; padding: 0; }
  .grid.ts-grid tr.note-row > td.ts-note::before { content: none; }
  .grid.ts-grid tr.note-row.note-open, .grid.ts-grid tr.note-row.note-warn { background: var(--flag-row); box-shadow: inset 3px 0 0 var(--flag-edge); }
  .grid.ts-grid tr.note-row.note-open > td:first-child, .grid.ts-grid tr.note-row.note-warn > td:first-child { box-shadow: none; }
}
/* SB-3: warning and success marks are SVG icons (partials/icon.ejs), not text glyphs. */
.note-icon, .notice-icon { width: 16px; height: 16px; margin: 0 6px 0 0; vertical-align: -3px; }
.row-notes li.is-warn .note-icon { color: var(--warn); }
.notice.err > .notice-icon { color: var(--err); }
.notice.ok > .notice-icon { color: var(--ok); }
@media (max-width: 760px) {
  /* #4: a flagged (warn) card looks like an open one: the tint and the amber edge on the
     card itself. The phone rules above covered tr.miss and tr.open only, so a warn card
     lost its tint and kept the desktop edge on its first cell, a bar over "W001". */
  .grid.ts-grid tr.warn { background: var(--flag-row); box-shadow: inset 3px 0 0 var(--flag-edge); }
  .grid.ts-grid tr.warn > td:first-child { box-shadow: none; }
}
/* #9: a paid-event pill ("Set by manager", "PIN · Offline") is one line, never broken
   inside its round border. It needs ~106 px; the Name column was 10-11% (78-117 px of
   text) at 900-1199 px, so it wrapped at 1000-1120. From 900 px the column has room for
   it; the timeline, which takes whatever is left, stays 138+ px (238 at 1000). */
/* Round 15 P5: at 1000-1120 px the Name column (124 / 136 px) wrapped "Walk Throwaway R13m"
   onto three lines and the one-line pill "Offline · Set by manager" (158 px) ran 36-42 px
   into the In column. Each reason is its own pill now (timesheet.ejs .ev-flags), so two
   reasons wrap BETWEEN pills, and from 1000 to 1199 px the column is 164 px: a two-word
   name keeps to two lines and the longest single reason ("Location unconfirmed", 147 px)
   fits its pill. The timeline keeps what is left (198 px at 1000, 218 at 1120). */
.ev-flags { display: flex; flex-wrap: wrap; gap: 3px 4px; margin-top: 3px; }
.ev-flags > .ev-flag { margin-top: 0; }
@media (min-width: 1000px) {
  .ts-grid td.ts-name .ev-flag { white-space: nowrap; }
}
/* (Round 30: the Name column of 761-1100 px is the ramp in the "Round 30" block, no longer 88 / 124 / 164 px in three steps.) */
/* Round 21 P5: 180 px from 1101 to 1199 (Out gave up 16 px there, so the timeline keeps its 218 at 1120). */
@media (min-width: 1101px) and (max-width: 1199px) { .ts-grid th.ts-name { width: 180px; } }
@media (min-width: 761px) and (max-width: 999px) {
  /* Below 1000 px there is no width to give the name (the table is already at its limit),
     so a pill may take two lines there (between words, never inside one), as a
     square-cornered tag rather than a broken pill. */
  .ts-grid td.ts-name .ev-flag { padding: 0 5px; border-radius: 4px; font-size: 12px; overflow-wrap: normal; }
}
@media (min-width: 761px) and (max-width: 899px) {
  /* Round 28 (critic r28 N4): at 761 px the timeline was 59 px (66 at 768, bars 1-12 px, the ruler thinned to "00 12" beside "Late").
     The table's type is 14 px here and In is 52 px; the other columns are one ramp from 761 to 1100 px (the "Round 30" block below). The
     hour ruler above the timeline thins itself by its own width (the container query), and "Late" keeps 8 px from the first hour label. */
  table.grid.ts-grid { font-size: 14px; }
  .ts-grid th.ts-in, .ts-grid td.ts-in { padding-left: 8px; }
  /* The Out chip's sides are 6 px here (10 elsewhere), so "open, no" fits one line of its 68 px cell ("open," / "no" / "clock-out" before). */
  .ts-grid td.ts-out .chip { padding-left: 6px; padding-right: 6px; }
  /* "Set clock-out" is 84 px on one line and the cell's text is 68: the link takes two lines here ("Set" over "clock-out"), never over the next column. */
  .ts-grid td.ts-out .fix-link { white-space: normal; line-height: 1.25; padding: 2px 0; }
}
/* ===== Round 30 (critic r29 items 1, 2, 3): the daily sheet's columns at 761-1100 px are ONE RAMP, not three steps. =====
   Measured on the walk database at every width (Chrome, 1 px steps): the Day column was 161 px at 761, 275 at 899 and 158 at 900, the
   Name column 88 px to 899 and 124 at 900, 164 at 1000 (Out 80 -> 100, Worked 70 -> 84, the figures 60 -> 76 and the type 14 -> 15 px
   all at 900), and below 900 a name of three words took three lines (47 of 66 rows). Every column that changed at 900 now moves a
   little with the window instead (the widths are lengths in vw, so they follow the window, not the table): "#" is 74 px (a 6-8
   character number is 40-66 px of 13 px type; 6 px on its left, none on its right), Name 110 px at 761 (a two-word name "Walk Throwaway"
   is 104 px, so it takes two lines, not three) growing to 180 at 1058 (166 at 1000, as before), Out 80 -> 100 at 900, Worked 70 -> 84 and
   the figures 60 -> 76 at 1100. At 1100 every column is the width it was (Name 180 = its width at 1101), so the Day column only ever grows
   with the window here: 161 px at 761, 196 at 840, 204 at 860, 198 at 900, 256 at 1000 and 324 at 1100 (the one drop, 23 px, is the page's own
   side margin, 16 px to 28 px at 861 px, clean-ui.css). The median row is 62 px at 761-899 (was 82) and 65 from 900. The ruler above the
   column thins itself (container query, above) so no two hour labels are closer than 8 px, and its first label keeps 8.5 px from "Late". */
@media (min-width: 761px) and (max-width: 1100px) {
  .ts-grid th.ts-num { width: 74px; }
  .ts-grid th.ts-num, .ts-grid td.ts-num { padding-left: 6px; padding-right: 0; }
  .ts-grid th.ts-in { width: 52px; }
  .ts-grid th.ts-name { width: clamp(110px, calc(110px + (100vw - 761px) * .2357), 180px); }
  .ts-grid th.ts-name, .ts-grid td.ts-name { padding-right: 0; }
  .ts-grid th.ts-out { width: clamp(80px, calc(80px + (100vw - 761px) * .1439), 100px); }
  .ts-grid th.ts-dur.ts-worked { width: clamp(70px, calc(70px + (100vw - 761px) * .0413), 84px); }
  .ts-grid th.ts-dur, .ts-grid th.ts-late, .ts-grid td.ts-late { width: clamp(60px, calc(60px + (100vw - 761px) * .0472), 76px); }
}
/* #6: two or more sessions on a day, listed under the row (folded), times left to right. */
.row-notes details.sessions > summary { min-height: 28px; font-size: 15px; }
.session-list { display: flex; flex-wrap: wrap; gap: 2px 20px; margin: 2px 0 0; padding: 0 0 0 24px; list-style: none; font-variant-numeric: tabular-nums; }
.session-list li { white-space: nowrap; }
.session-span { color: var(--ink); }

/* ---------- Round 15 (critique 2026-09-27) ---------- */
/* P4: a phone card's strip had no hours on it (the table head, with the ruler, is hidden
   below 760 px). Each card now has a compact ruler under its strip: 00 / 06 / 12 / 18,
   whichever fall inside the window, on the strip's own grid (partials/timeline_axis.ejs
   compact). Hidden on a desktop, where the ruler above the column does the job. */
.tl-axis.tl-axis-phone { display: none; }
@media (max-width: 760px) {
  .board-grid td.tl-col .tl-axis.tl-axis-phone { display: grid; margin-top: 3px; font-size: 12px; }
}

/* ---------- Round 17 (critique 2026-09-28) ---------- */
/* SB-2: the Year page's badge and marks come ~0.5 s after the page (they need the full-year
   walk the page must not wait on), and made it jump: CLS 0.161 at 390, 0.082 at 1000 and 0.065
   at 1280. Their places are now held from the first paint, so filling them moves nothing.
   Each Export button keeps room for a badge of two digits ("00 to check": an invisible
   stand-in in the same font, under the real badge), and each name keeps a line of marks,
   which never widens the Name column (width 0, min-width 100%: the marks wrap inside it). */
.ex-badge-slot { display: inline-grid; }
.ex-badge-slot::before, .ex-badge-slot > .ex-badge { grid-area: 1 / 1; }
.ex-badge-slot::before { content: "00 to check"; visibility: hidden; min-width: 1.6em; margin-left: 6px; padding: 1px 6px; font-size: 13px; font-weight: 700; line-height: 1.4; white-space: nowrap; font-variant-numeric: tabular-nums; }
.ex-badge-slot > .ex-badge { justify-self: start; }
.period-grid .yr-marks { display: flow-root; width: 0; min-width: 100%; min-height: 25px; }
.period-grid .yr-marks > .mo-marks { margin-top: 3px; }
/* P2 + SB-2: a day can carry several marks now (every kind, not the first), and from 761 to
   1200 px the Year's Name column is narrow (about 140 px of text at 1000): three marks do not
   fit one line there. Its marks are a little smaller and each name holds TWO lines for them,
   so marks arriving late still move nothing. Measured at 1000: W008's unverified + open +
   check took two lines and pushed every row under it down 26 px (CLS 0.0113 in view). */
/* Round 21 follow-up: from 1000 px (below it the Year is cards, defect 3). Found on the defect-3 walk,
   pre-existing: two lines of 20 px marks 3 px apart failed axe target-size at 1000 px (a 24 px circle
   round one reached the next). 5 px between the lines keeps each mark 25 px from its neighbour; the
   reserved height grows by the same 2 px (3 + 20 + 5 + 20 = 48). */
/* Round 28 (critic r28 N3): the Year's compact marks, its 10rem Name column, its 2 px figure padding and its nowrap month figures (below)
   were 1000-1200 px rules; from 1240 px the same page came out with a 188 px Name column, a 72 px median row (63 at 1200), 12 rows between
   91 and 139 px and a 109 px Sep beside 42 px months. They are every width from 1000 px now (the Year is cards below it). */
@media (min-width: 1000px) {
  .year-grid td.ts-name:has(> .yr-marks) { padding-top: 7px; padding-bottom: 7px; }
  /* Round 26 critic defect 2: the slot is NOT pre-sized for two lines of 24 px marks any more (56 px, for EVERY worker, so an empty slot
     made every Year row 92 px against 84 live and the page 7536 against 7064 at 1000). An empty slot is the base 25 px; a worker whose marks
     arrive keeps them in the slot (3 + 24 for one line, a second 5 + 24 only for the few with two kinds), the same as at 1280. */
  .year-grid .yr-marks .mo-marks { gap: 5px 3px; }
  .year-grid .yr-marks .wk-open { min-height: 24px; padding: 0 6px; font-size: 12px; }
}
/* P9: a caution, not a success. The Anomaly check's "Still on shift, not flagged" box sat in the
   default notice (a green-tinted info box) though those hours are missing from the payroll file
   until the clock-out. The app's amber caution tokens (the ones the flagged rows, the hold
   panel and the warn chips use), with the text in ink: 12.7:1 light, 11.6:1 dark (computed from the tokens). */
.notice.warn { border-color: var(--warn-line); background: var(--warn-bg); }
/* P13: a month that has not started yet is not a month with no hours ("—"). Its column head says
   "not yet" under the month, and its cells are blank for the eye and "not yet" to a screen reader;
   on a phone card (no column heads) the cell says "not yet" on screen, quietly. */
.period-grid th.mon-future .mon-notyet-h { display: block; font-size: 11px; font-weight: 500; line-height: 1.2; white-space: nowrap; }
.period-grid td.mon-future .mon-notyet { color: var(--muted); font-size: 14px; font-weight: 500; }
@media (min-width: 1000px) {
  .period-grid td.mon-future .mon-notyet { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
}

/* ===== Round 22, builder B (2026-09-29): P1, P4, P7, P8. Kept together at the end of this file. ===== */
/* P1: the timesheet tab bar (Daily / Weekly / Monthly / Yearly) was 295 px in a 286 px box at 320 px,
   so it scrolled 9 px and clipped "Yearly" (the box is 286 px at 320 and gains 1 px per px of width).
   Under 360 px the tabs take 8 px either side (was 14) and may shrink to their words; from 360 px the
   bar is as before. Measured in real Chrome at every width 320-1920: no scroll at any. */
@media (max-width: 359px) {
  .ts-tabs { gap: 0; }
  .ts-tab { min-width: 0; padding-left: 8px; padding-right: 8px; }
}
/* P7: Team at 1000-1119 px: the Email column was 166 px (the table's 942), so an address broke
   inside its domain ("example.in / valid"). The cells take 8 px either side instead of 12 there
   (six columns: +48 px), and team.ejs offers a line break right after the "@". From 1120 px the
   address already fits on one line and nothing changes. */
@media (min-width: 1000px) and (max-width: 1119px) {
  .team-table th, .team-table td { padding-left: 8px; padding-right: 8px; }
}
/* P8: the Year at 1000-1200 px: the Name column was 104-116 px, so a name took three lines and a row
   147 px. The name column may be 10rem (160 px) and the other cells take 4 px either side (was 6), which
   is what pays for it: the twelve months, the totals and the limits still fit the frame, measured at
   every width 1000-1240 with no sideways scroll. */
@media (min-width: 1000px) {
  .year-grid td.ts-name { min-width: 10rem; }
  .year-grid th, .year-grid td { padding-left: 2px; padding-right: 2px; } /* Round 26 P9: was 4px; see below */
  .year-grid th:first-child, .year-grid td:first-child { padding-left: 8px; }
  .year-grid th:last-child, .year-grid td:last-child { padding-right: 8px; }
}
/* P4: the week, month and year marks say "awaiting review" (they said "unverified", which no other
   page did). The longer words must never widen a table: in a cell that is short of room a mark may take
   two centred lines (a soft-cornered pill, not a 999px one) instead of holding its whole word on one. */
.period-grid .chip.wk-open { white-space: normal; text-align: center; line-height: 1.25; border-radius: 12px; }
/* ...and a mark that follows the day's own state chip ("absent" + "awaiting review") stacks under it
   from 1000 px, as two marks already do, instead of adding its width to the day column. */
@media (min-width: 1000px) {
  .period-grid td.day-col > .wk-state + .wk-open { display: flex; margin-left: auto; }
  .period-grid td.day-col > .wk-open + .wk-open, .period-grid td.day-col > .wk-state + .wk-open { width: fit-content; max-width: 100%; }
}

/* ===== Round 26, builder A (2026-10-01): P9, P6. Kept together at the end of this file. ===== */
/* P9: the Year at 1000-1200 px fitted its frame only because a month figure took two lines ("1 h" over "37 min": every Sep
   figure at 1000 px, 67 px wide). A month figure never wraps now (93 px for "112 h 14 min"); the Name column keeps its 10rem
   (so the rows keep their height: median 92 px, as before) and the cells give 2 px either side instead of 4 (the round-22 P8
   rule above), which pays for it. Measured in Chrome on the walk database, both companies (the second has the Auto break
   column): the table needs 892 and 909 px in a 942 px frame at 1000 px and fits at 1040, 1100 and 1200 px; no month figure wraps
   at any of them. The Year only: the week and month keep balanced lines. */
@media (min-width: 1000px) {
  .year-grid td.mon-col { white-space: nowrap; }
}
/* P6: in-row targets that were 20-22 px (the fix links on the Overview, the daily sheet and the Anomaly check) are 24 px
   (WCAG 2.2 SC 2.5.8): a row is taller than that already, so density does not move. */
.row-fix, .board-fix, .fix-link { display: inline-block; min-width: 24px; min-height: 24px; line-height: 24px; text-align: center; }

/* Round 26 critic defect 3: "clock-out" never breaks at its hyphen and orphans "out" (the daily sheet's Out chip, the Overview status text at 390). */
.nb { white-space: nowrap; }

/* ===== Round 30 (2026-10-03): critic r29 item 4. ===== */
/* The Year page's columns were uneven (measured on the walk database, 1000-1920 px): Jan-Aug 28-38 px, Sep 89-115, Normal / OT / Total
   72-126. The browser gives a table's spare width to its widest cells, so one month with hours (Sep, 112 h 14 min in one line, 93 px)
   and the bold Total took it all and the empty months stayed slivers. From 1100 px (below it the frame has no spare width: the table is
   as wide as its figures need) every month column asks for the same 3.2% and the totals (Normal, OT, Auto break, Total) for the same
   9%: eight empty months are one width (38 px at 1240), Sep keeps what its figure needs (86-95 px, so it no longer grows), the totals
   are one width (106 px). These are hints, not floors: a month with hours in all twelve is still as wide as its figure and the table
   still scrolls behind its pinned name, as before. Name stays 200-255 px (it was 205-249); no month figure wraps, no overflow, the
   median row is unchanged (63-64 px). Both walk companies measured (the second has the Auto break column). */
@media (min-width: 1100px) {
  .year-grid td.mon-col { width: 3.2%; }
  .year-grid td.tot { width: 9%; min-width: 98px; } /* Round 30b (F5a): at 1100 the bold Total needed 98 px and Normal / OT got 94; all three are 98 now */
}

/* ===== Round 30c (2026-10-03) ===== */
/* F3 for real: at 1000-1004 px the week's table needed 947.1 px in its 942 px frame (so it scrolled 1-5 px), with ANY worker numbers: replacing
   every number with W001 still gave 942.0. The number cell was not the cause. The three widest day columns (80 px) are sized by their marks
   ("awaiting review", "open", "check", "absent": 68 px of words plus 8 px of padding either side) and the three after them (71 px) by
   "absent" / "not in". At 1000-1239 px the week's marks take 6 px sides (8 before), so those columns are 4 px narrower each:
   the table then needs 923 px at 1000 (19 px to spare) and fits at every width from 1000 up with the long numbers too. The month and the year
   already fit (their marks have their own padding) and are not touched. */
@media (min-width: 1000px) and (max-width: 1239px) {
  .period-grid:has(th.day-col) .chip.wk-open, .period-grid:has(th.day-col) .chip.wk-state { padding-left: 6px; padding-right: 6px; }
}
