/* ===== CDG Roster Platform — design system (no build step) ===== */
:root{
  --bg:#F7F4EF;--surface:#FFFFFF;--ink:#1C1B19;--muted:#6F6A61;--border:#ECE6DA;
  --accent:#A8842C;--primary:#1C1B19;--primary-ink:#FFFFFF;--brand-mark:#D4AF50;
  --error:#C0392B;--warn:#B7791F;--ok:#2E7D52;--info:#2C6E8F;
  --cell-amber:#FFF3D6;--cell-amber-bd:#E8C766;--cell-err:#FBECEB;--cell-err-bd:#E2A6A1;
  --s1:4px;--s2:8px;--s3:12px;--s4:16px;--s5:24px;--s6:32px;
  --radius:8px;--radius-sm:6px;--pill:20px;--shadow:0 1px 3px rgba(0,0,0,.08);--shadow-lg:0 6px 24px rgba(0,0,0,.16);
  --font:system-ui,-apple-system,"Segoe UI","PingFang TC","Microsoft JhengHei","Noto Sans TC",sans-serif;--maxw:1100px;
}
/* Font scale. The width-based auto default IS the maximum: it's the largest size
   that still reads comfortably at that width (>1800px CSS -> 175%, 1101-1800 ->
   150%, <=1100 -> 125%). Users may pick a SMALLER step; larger steps are capped
   to the default and hidden from the switcher. (CSS width ~ physical width, since
   OS display scaling divides device pixels out.) */
html{--fs:1.75}
@media screen and (max-width:1800px){ html{--fs:1.5} }
@media screen and (max-width:1100px){ html{--fs:1.25} }
html[data-fs="100"]{--fs:1}
html[data-fs="125"]{--fs:1.25}
html[data-fs="150"]{--fs:1.5}
html[data-fs="175"]{--fs:1.75}
@media screen and (max-width:1800px){ html[data-fs="175"]{--fs:1.5} }
@media screen and (max-width:1100px){ html[data-fs="150"],html[data-fs="175"]{--fs:1.25} }
/* hide switcher steps above the width's default (no non-functional choices) */
@media screen and (max-width:1800px){ button[data-fs-btn="175"]{display:none} }
@media screen and (max-width:1100px){ button[data-fs-btn="150"]{display:none} }
*{box-sizing:border-box}
[hidden]{display:none !important}  /* the hidden attribute must beat component display rules (.btn's inline-flex etc.) */
html{font-family:var(--font)}
body{margin:0;background:var(--bg);color:var(--ink);font-size:calc(15px * var(--fs, 1));line-height:1.5}
main{display:block;max-width:var(--maxw);margin:0 auto;padding:var(--s5) var(--s4)}
a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}
h1{font-size:calc(22px * var(--fs, 1));margin:0 0 var(--s3)}
h1.page-title{font-size:calc(18px * var(--fs, 1))}h2{font-size:calc(18px * var(--fs, 1));margin:var(--s5) 0 var(--s3)}h3{font-size:calc(15px * var(--fs, 1));margin:0 0 var(--s2)}
hr{border:0;border-top:1px solid var(--border)}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
button{font:inherit}
input,select,textarea{font:inherit;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);padding:7px 9px}
/* a box the staff master owns: shown, not edited (customer 2026-08-27) */
.input.ro,input.ro{background:var(--bg);color:var(--muted);cursor:default}
.input.ro:focus,input.ro:focus{outline-color:var(--border);border-color:var(--border)}
input:focus,select:focus,textarea:focus{outline:2px solid var(--accent);outline-offset:1px;border-color:var(--accent)}
table{border-collapse:collapse}
main form p{margin:0 0 var(--s3)}
main form label{display:block;font-weight:600;font-size:calc(13px * var(--fs, 1));margin-bottom:var(--s1);color:var(--ink)}
.errorlist{list-style:none;padding:0;margin:var(--s1) 0;color:var(--error);font-size:calc(13px * var(--fs, 1))}
.muted{color:var(--muted)}.small{font-size:calc(13px * var(--fs, 1))}
/* header / nav */
.site-header{background:var(--ink);color:#fff}
.site-header .bar{max-width:var(--maxw);margin:0 auto;padding:10px var(--s4);display:flex;align-items:center;gap:var(--s4)}
.brand{font-weight:700;letter-spacing:.05em;color:#fff;text-decoration:none;padding:6px 10px;border-radius:var(--pill)}
.brand:hover{background:rgba(255,255,255,.16);text-decoration:none}
.brand b{color:var(--brand-mark)}
.site-nav{margin-left:auto;display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:4px var(--s3);font-size:calc(14px * var(--fs, 1))}
.site-nav a{color:#e9e6df;padding:6px 12px;border-radius:var(--pill);text-decoration:none}
.site-nav a:hover{color:#fff;background:rgba(255,255,255,.16);text-decoration:none}
.nav-ver{color:rgba(233,230,223,.55);font-size:calc(12px * var(--fs, 1))}
.switch{display:inline-flex;border:1px solid rgba(255,255,255,.3);border-radius:var(--radius-sm);overflow:hidden}
.switch button{background:transparent;color:#cfc9bd;border:0;padding:4px 10px;cursor:pointer;font-size:calc(12px * var(--fs, 1))}
.switch button.on{background:rgba(255,255,255,.18);color:#fff}
/* buttons */
.btn{display:inline-flex;align-items:center;gap:6px;border:1px solid transparent;border-radius:var(--radius-sm);padding:8px 15px;font-weight:600;font-size:calc(14px * var(--fs, 1));cursor:pointer;text-decoration:none;line-height:1}
.btn:hover{text-decoration:none}
.btn-primary{background:var(--primary);color:var(--primary-ink)}
.btn-primary:hover{background:#3B3936}
.btn-secondary{background:var(--surface);color:var(--ink);border-color:var(--border)}
.btn-secondary:hover{background:var(--bg);border-color:var(--accent)}
.btn-quiet{background:transparent;color:var(--muted)}
/* filled but quiet: reads as a control against the cream page without
   competing with .btn-primary (customer 2026-08-27) */
.btn-soft{background:#EDE7DA;color:var(--ink);border-color:var(--border)}
.btn-soft:hover{background:#E3DBC9}
.btn-quiet:hover{background:rgba(28,27,25,.08);color:var(--ink)}
.site-header .btn-quiet:hover{background:rgba(255,255,255,.16);color:#fff}  /* dark bar needs a light tint */
.btn-danger{background:transparent;color:var(--error);border-color:var(--border)}
.btn-danger:hover{background:var(--cell-err);border-color:var(--cell-err-bd)}
.btn[disabled],.btn.is-disabled{background:#cfc8bb;color:#fff;cursor:not-allowed;filter:none}
/* badges */
.badge{display:inline-block;font-size:calc(11px * var(--fs, 1));font-weight:600;color:#fff;padding:3px 10px;border-radius:var(--pill);white-space:nowrap}
.status-draft{background:#8A8F99}.status-uploaded,.status-extracting,.status-submitting{background:#3B82C4}
.status-review{background:var(--warn)}.status-validated,.status-generated{background:#2F8FBF}
.status-confirmed{background:#4F73C4}.status-submitted{background:var(--ok)}
.status-failed,.status-submit_failed{background:var(--error)}
/* manage-staff list badges: active / future joiner / scheduled leaver */
.st-active{background:var(--ok)}.st-joins{background:#3B82C4}.st-leaves{background:var(--warn)}
/* dashboard: confirmed-but-never-submitted week past the MSS cutoff */
.st-missed{background:var(--error)}
/* review-grid day header: festival tags + PH/SH icons */
.day-marks{display:flex;gap:2px;flex-wrap:wrap;margin:0;justify-content:center;align-items:center}
.day-tag{display:inline-block;position:relative;font-size:calc(13px * var(--fs, 1));font-weight:700;line-height:1;padding:1px 6px;border-radius:var(--pill);background:#F4E8C8;color:#7A5C12;white-space:nowrap;cursor:pointer;border:1px dashed #C09A3E}
/* the special-day name bubble lives at body level (positioned by app.js) so the
   grid's scroll container / sticky columns can never clip or cover it */
.title-row{margin-bottom:var(--s3)}
.submission-panel{margin:var(--s3) 0}
.submission-panel:not(:has(*)){margin:0}
.status-tip{cursor:pointer;user-select:none;display:inline-flex;align-items:center;color:var(--muted,#6f6a61)}
.status-tip svg{width:calc(20px * var(--fs, 1));height:calc(20px * var(--fs, 1))}
.status-tip:hover,.status-tip:focus-visible{color:var(--accent,#A8842C)}
.status-tip:focus-visible{outline:2px solid var(--accent,#A8842C);outline-offset:2px;border-radius:4px}
#day-tip{position:fixed;display:none;z-index:1000;background:var(--ink);color:#fff;padding:5px 9px;
  border-radius:6px;font-weight:600;font-size:calc(12px * var(--fs, 1));white-space:nowrap;
  box-shadow:var(--shadow-lg);pointer-events:none;max-width:94vw;text-align:center;
  white-space:pre-line}   /* multi-line guides (spreadsheet-mode controls) keep their lines */
.day-tag:hover,.day-tag:focus{background:#EDDCA4;border-style:solid}   /* reacts -> reads as interactive */
.day-tag:focus{outline:2px solid var(--accent);outline-offset:1px}
.day-ico{display:inline-block;font-size:calc(13px * var(--fs, 1));font-weight:700;line-height:1;padding:1px 4px;border-radius:4px;color:#fff}
.day-ico-ph{background:#C23B3B}
.day-ico-sh{background:#2F6FBF}
/* cards / panels / empty */
.card,.panel{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:var(--s3) var(--s4)}
.card{box-shadow:var(--shadow)}
.empty{border:1px dashed var(--border);background:var(--surface);border-radius:var(--radius);padding:var(--s6);text-align:center;color:var(--muted)}
.empty h3{color:var(--ink);margin:var(--s2) 0}
/* banners (messages) */
.banners{list-style:none;padding:0;max-width:var(--maxw);margin:0 auto var(--s4)}
.banner{padding:10px var(--s3);border-radius:var(--radius-sm);margin-bottom:var(--s2);border:1px solid;font-size:calc(14px * var(--fs, 1))}
.banner-success{background:#eaf6ef;border-color:#bfe3cd;color:#1d6b43}
.banner-error{background:#fbeceb;border-color:#f1c9c5;color:#9a2b1f}
.banner-warning{background:#fff6e6;border-color:#f0d9a8;color:#8a6314}
.banner-info,.banner-debug{background:#eaf2f7;border-color:#c3dcea;color:#245874}
/* fields */
.field{margin-bottom:var(--s3)}
.field>label,.lbl{display:block;font-weight:600;font-size:calc(13px * var(--fs, 1));margin-bottom:var(--s1);color:var(--ink)}
.lbl-sm{display:block;font-weight:700;font-size:calc(9px * var(--fs, 1));line-height:1;font-family:var(--font);letter-spacing:.06em;text-transform:uppercase;color:var(--muted);margin-bottom:4px}
.hint{color:var(--muted);font-size:calc(12px * var(--fs, 1));margin-top:4px}
.input,.select{width:100%}
/* list tables */
.table{width:100%;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden}
.table th,.table td{text-align:left;padding:9px var(--s3);border-bottom:1px solid var(--border)}
.table thead th{background:var(--bg);font-size:calc(12px * var(--fs, 1));text-transform:uppercase;letter-spacing:.04em;color:var(--muted)}
.table tr:last-child td{border-bottom:0}
.row-inactive{color:var(--muted)}
/* separator bands: a note across the grid instead of a staff member
   (customer 2026-08-27). The note keeps the lines it was typed with. */
.grid tr.sep-row td.sep-cell{text-align:left;padding:6px 10px;font-weight:600;
  background:var(--bg);overflow-wrap:anywhere}
/* pre-line belongs to the TEXT, not the cell: on the <td> it also honoured the
   template's own indentation and line breaks, and a one-line band came out
   three times its height (customer 2026-08-27) */
.grid tr.sep-row .sep-body{min-height:1.2em;cursor:pointer;white-space:pre-line}
.grid tr.sep-row .sep-body:hover{text-decoration:underline dotted}
/* Editing a DARK band: the quiet and danger buttons colour themselves (muted
   grey, error red) and neither reads on red, dark green or blue, so they take
   the band's own light ink instead. A light band keeps them as they are —
   there the red still says "remove" (customer 2026-08-28). */
.sep-cell.sep-dark .btn-quiet,.sep-cell.sep-dark .btn-danger{color:#fff;border-color:rgba(255,255,255,.55)}
.sep-cell.sep-dark .btn-quiet:hover,.sep-cell.sep-dark .btn-danger:hover{background:rgba(255,255,255,.18);color:#fff}
.sep-cell.sep-dark .hl-sw-none span{color:#fff;border-color:rgba(255,255,255,.6)}
.grid tr.sep-editing .sep-form{display:flex;flex-direction:column;gap:6px}
.sep-text{width:100%;resize:vertical;font-weight:400}
.sep-controls{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.btn-sm{padding:4px 10px;font-size:calc(13px * var(--fs, 1))}
/* leave entitlement page: identity is text, the ceilings are boxes that save
   themselves; a box for a ceiling this staff member has not got is disabled */
.ent-scroll{overflow-x:auto}
.ent-table td{vertical-align:top;white-space:nowrap}
.ent-table th{white-space:nowrap}
/* the identity columns are the ones that give: when the boxes would not all
   fit, alias and position wrap rather than push the page into a sideways
   scroll (customer 2026-08-26) */
.ent-table td.ent-wrap{white-space:normal;overflow-wrap:break-word}
.ent-table .ent-days{padding:5px var(--s2)}
.ent-input{width:4.5em;padding:4px 6px}
.ent-note .ent-input{width:100%;min-width:6em;resize:vertical}
.ent-input:disabled{background:var(--bg);color:var(--muted);cursor:not-allowed}
input.field-error{border-color:var(--error);background:var(--cell-err)}
/* the refusal sits under its own box, in normal flow: floated out it was
   clipped by the scrolling table whenever the column sat near an edge. The
   cell widens to hold it, so the whole message is always readable
   (customer 2026-08-26) */
.ent-table td.ent-days:has(.ent-err){min-width:11em}
.ent-err{white-space:normal;margin-top:4px;
  color:var(--error);font-size:calc(12px * var(--fs, 1));line-height:1.3}
/* a figure that SAVED but is above what the rules work out (PH/SH, customer
   2026-09-09): amber where a refusal would be red, on the box and its note */
input.field-warn{border-color:var(--warn);background:#FFF6E0}
.ent-err.ent-warn{color:var(--warn)}
/* the same alert where a popup's save lands (the staff list, the 📁 details):
   it stays until dismissed — unlike "Saved", it is something to read */
.warn-note{display:flex;align-items:flex-start;gap:var(--s2);margin:var(--s2) 0;
  padding:8px 12px;border:1px solid var(--warn);border-radius:var(--radius, 6px);
  background:#FFF6E0;color:#7A4E00;font-size:calc(13px * var(--fs, 1));line-height:1.35}
.warn-note-x{margin-left:auto;border:0;background:none;color:inherit;cursor:pointer;
  font-size:calc(13px * var(--fs, 1));line-height:1;padding:0 2px}
/* "Saved", said where the save happened and fading out on its own, so there is
   nothing to dismiss (customer 2026-08-26) */
.saved-note{color:var(--ok);font-weight:600;font-size:calc(12px * var(--fs, 1));
  white-space:nowrap;margin:var(--s1) 0;animation:cdg-fade-out 3s ease-in forwards}
/* ...or floating at the top centre, where the surface that was saved has just
   closed (the staff popup): fixed, so it lands there wherever the fragment it
   arrives in sits, and click-through while it fades */
/* the fallback assumes a top bar: --hdr-h is only measured while it is sticky,
   and a toast tucked under the dark bar would not be seen at all */
.saved-toast{position:fixed;top:calc(12px + var(--hdr-h, 64px));left:50%;transform:translateX(-50%);
  z-index:60;margin:0;padding:9px 18px;border-radius:var(--pill);
  background:var(--ok);color:#fff;font-size:calc(14px * var(--fs, 1));
  box-shadow:var(--shadow-lg);pointer-events:none}
@keyframes cdg-fade-out{0%,55%{opacity:1}100%{opacity:0}}
@media (prefers-reduced-motion:reduce){.saved-note{animation:none}}
/* dashboard */
.wk{display:flex;align-items:center;gap:var(--s3);background:var(--surface);border:1px solid var(--border);border-left:4px solid var(--muted);border-radius:var(--radius);padding:var(--s3) var(--s4);margin-bottom:10px}
.wk .meta{flex:1}.wk .sub{color:var(--muted);font-size:calc(13px * var(--fs, 1));margin-top:3px}
.chips{display:flex;gap:7px;flex-wrap:wrap;margin-bottom:var(--s3)}
.chip{font-size:calc(12px * var(--fs, 1));padding:4px 12px;border-radius:var(--pill);border:1px solid var(--border);color:var(--muted);background:var(--surface);cursor:pointer;text-decoration:none}
.chip.on{background:var(--ink);color:#fff;border-color:var(--ink)}
.note-error{color:var(--error);font-size:calc(12px * var(--fs, 1));background:var(--cell-err);border:1px solid var(--cell-err-bd);border-radius:var(--radius-sm);padding:4px 8px;display:inline-block}
/* review grid */
.actionbar{position:sticky;top:0;z-index:5;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:11px var(--s4);display:flex;align-items:center;gap:var(--s2);flex-wrap:wrap;margin-bottom:var(--s3)}
.count-err{color:var(--error);font-weight:600;font-size:calc(14px * var(--fs, 1))}.count-amber{color:#9a7400;font-weight:600;font-size:calc(14px * var(--fs, 1))}
.two-pane{display:flex;gap:var(--s4);align-items:flex-start;flex-wrap:wrap}
.grid-scroll{overflow:auto;max-height:70vh;border:1px solid var(--border);border-radius:var(--radius);background:var(--surface)}
.grid{width:100%;border-collapse:separate;border-spacing:0;font-size:calc(13px * var(--fs, 1));font-variant-numeric:tabular-nums}
.grid th,.grid td{border-right:1px solid var(--border);border-bottom:1px solid var(--border);padding:6px 8px;text-align:center;background:var(--surface);color:var(--ink)}
.grid thead th{position:sticky;top:0;background:var(--bg);z-index:2;font-weight:700}
.grid .staff{position:sticky;left:0;background:var(--surface);text-align:left;font-weight:600;z-index:1}
.grid thead .staff{z-index:3}
/* per-field highlight chips: the value sits on its resolved palette colour
   (full strength — identical to the exported cell's fill) */
.fld-hl{background:var(--hl);border-radius:3px;padding:0 3px;
  color:var(--hl-ink, var(--ink,#1c1b19))}   /* light text on a dark fill (customer 2026-08-27) */
/* hover highlight on clickable cells (editable grids render hx-get on them) */
.grid td.cell[hx-get]:hover{background:#F1EBDC}
.grid td.cell.cell-amber[hx-get]:hover{background:#F6E7B8}
.grid td.cell.cell-error[hx-get]:hover{background:#F4D9D5}
.grid td.staff[hx-get]:hover{background:#F1EBDC}
.grid td.cell-amber{background:var(--cell-amber)}
.grid td.cell-error{background:var(--cell-err);color:var(--error);font-weight:600;box-shadow:inset 0 0 0 2px var(--cell-err-bd)}
.grid .totals th,.grid .totals td{background:var(--bg);font-weight:700}
.grid .totals-cell{white-space:nowrap;font-size:calc(11px * var(--fs, 1));color:var(--muted)}
.grid td.drag,.grid th.drag{width:22px;padding:4px 2px;text-align:center;vertical-align:middle;position:sticky;left:0;background:var(--surface);z-index:1}
.grid thead th.drag{z-index:3;background:var(--bg)}
/* the permanent add-a-row band at the grid's bottom: whole row clickable */
.grid .grid-add td{cursor:pointer;text-align:center;background:#E9DAAE;color:#6B5314;
  font-weight:700;font-size:calc(13px * var(--fs, 1));padding:9px 8px;user-select:none}
/* action-bar overflow menu (position:fixed so the scrolling bar can't clip it) */
.bar-menu{position:fixed;z-index:60;display:flex;flex-direction:column;min-width:220px;padding:6px;
  background:var(--surface,#fff);border:1px solid var(--border,#e3dccb);border-radius:8px;
  box-shadow:0 10px 30px rgba(0,0,0,.18)}
.bar-menu[hidden]{display:none}
/* A row per item so the parts line up down the menu (customer 2026-09-22):
   the glyphs differ in width by a lot — ∑ and ↩ are text, 🖨 and 🧾 are emoji —
   so each sits in a box of its own width, and the tick is pushed to the far
   side rather than trailing whatever the label happens to be. */
.bar-menu .menu-item{display:flex;align-items:center;gap:8px;width:100%;text-align:left;
  padding:9px 12px;border:0;background:transparent;
  color:var(--ink,#1c1b19);font:inherit;font-size:calc(13.5px * var(--fs, 1));border-radius:6px;cursor:pointer;text-decoration:none}
.menu-ico{flex:none;width:1.4em;text-align:center}
.bar-menu .menu-item:hover{background:var(--bg,#F7F4EF)}
/* the Sorting panel: fields ticked and dragged into the order they apply in,
   each with its own direction, applied on the button (customer 2026-08-27) */
.sort-panel{min-width:260px;padding:10px}
.sort-hint{margin:0 0 8px;color:var(--muted);font-size:calc(12px * var(--fs, 1));max-width:22em}
.sort-list{list-style:none;margin:0;padding:0}
/* Each ticked field is indented one step further than the one above it, so
   the panel SHOWS that the first decides and the rest break its ties
   (customer 2026-08-28). Unticked fields sit at the margin. */
.sort-item{display:flex;align-items:center;gap:8px;padding:5px 4px;border-radius:6px;
  margin-left:calc(var(--lvl, 0) * 18px)}
.sort-item.sort-applied>.sort-lbl{font-weight:600}
.sort-item:hover{background:var(--bg,#F7F4EF)}
.sort-item.dragging{background:#e6dbb9;opacity:.9}
.sort-item .sort-grip{cursor:grab;touch-action:none;color:var(--muted);user-select:none}
.sort-lbl{flex:1;display:flex;align-items:center;gap:7px;font-size:calc(13.5px * var(--fs, 1));cursor:pointer}
.sort-on{width:calc(15px * var(--fs, 1));height:calc(15px * var(--fs, 1));accent-color:var(--accent,#A8842C)}
.sort-dir-btn{border:1px solid var(--border);background:var(--surface);border-radius:6px;
  cursor:pointer;padding:2px 8px;color:var(--ink);font-size:calc(13px * var(--fs, 1));line-height:1.3}
.sort-dir-btn:hover{border-color:var(--accent)}
.sort-actions{margin-top:10px}
.grid .grid-add td:hover,.grid .grid-add td:focus-visible{background:#DFCC96}
.grid .staff{left:22px}
.drag-handle{cursor:grab;color:var(--muted);font-size:calc(14px * var(--fs, 1));user-select:none;touch-action:none;display:inline-block;padding:4px 2px}
tr.dragging{opacity:.95;box-shadow:0 4px 16px rgba(0,0,0,.35);position:relative;z-index:5}
tr.dragging td,.grid tr.dragging td.staff,.grid tr.dragging td.drag,.table tr.dragging td{background:#e6dbb9}
/* the staff list (a .table, not a .grid) is draggable too — narrow handle column, no sticky needed */
.table td.drag,.table th.drag{width:22px;padding:4px 2px;text-align:center;vertical-align:middle}
/* cell drag-swap (cellswap.js): a thin full-width rectangle across a day cell's top
   starts the drag — the whole bar is the hit area, not just the dots. Negative margins
   cancel the td's 6px/8px padding so the bar bleeds to the cell edges; cells are
   top-aligned so it always hugs the top border. The glyph is ⠿ rotated 90° via the
   ::before so the six dots read as 2 rows × 3 columns (horizontal, like the bar). */
.grid td.cell{vertical-align:top}
.cell-drag-handle{display:block;margin:-6px -8px 5px;padding:2px 0 3px;background:#F0DCCE;
  border-bottom:1px solid #E2C4AE;text-align:center;line-height:1;cursor:grab;
  color:#A6572E;font-size:calc(10px * var(--fs, 1));user-select:none;touch-action:none}
.cell-drag-handle::before{content:"⠿";display:inline-block;transform:rotate(90deg);opacity:.7}
td.cell:hover .cell-drag-handle{background:#E8CDB9}
td.cell:hover .cell-drag-handle::before{opacity:1}
/* the staff-row ⠿ column wears the same tint as the cell bars, so the two drag
   affordances read as one family (warm terracotta: distinct from the cream page,
   the gold action colour, and the amber/red cell states) */
.grid tbody td.drag{background:#F0DCCE}
.drag-handle{color:#A6572E}
/* cell cut/copy/paste (cellclip.js): hover buttons — a small floating toolbar in the
   cell's bottom-right corner (own surface + border so it reads over cell content) */
.grid td.cell{position:relative}
.cell-actions{position:absolute;bottom:2px;right:2px;display:none;z-index:2;gap:2px;
  background:var(--surface);border:1px solid #E2C4AE;border-radius:var(--radius-sm);
  box-shadow:var(--shadow);padding:1px}
td.cell:hover .cell-actions,td.cell.cell-active .cell-actions{display:inline-flex}
.cell-actions button{border:0;background:transparent;cursor:pointer;color:#A6572E;
  line-height:0;padding:5px 6px;border-radius:4px;display:inline-flex;align-items:center}
.cell-actions button svg{width:calc(16px * var(--fs, 1));height:calc(16px * var(--fs, 1))}
.cell-actions button:hover{background:#E8CDB9}
.cell-actions .ca-paste{display:none}
body.has-clip .cell-actions .ca-paste{display:inline-flex}
td.cell.clip-src{outline:2px dashed #A6572E;outline-offset:-2px}
/* two-tap swap (⇄): the phone gesture — desktop swaps by dragging the bar, so the
   button only shows in the stacked layout (layout.css); first tap arms the cell */
.cell-actions .ca-swap{display:none}
.grid td.cell.swap-armed{box-shadow:inset 0 0 0 2px #A6572E;background:#F7E8DE}
/* first-tap feedback: the armed cell's ⇄ fills in, and a status toast explains the next tap */
.grid td.cell.swap-armed .cell-actions .ca-swap{background:#A6572E;border-color:#A6572E;color:#fff}
.swap-hint{position:fixed;left:50%;bottom:18px;transform:translateX(-50%);z-index:1000;max-width:92vw;
  background:#A6572E;color:#fff;padding:10px 16px;border-radius:var(--pill);box-shadow:var(--shadow-lg);
  font-size:calc(13px * var(--fs, 1));line-height:1.35;text-align:center}
body.swap-dragging{user-select:none;cursor:grabbing}
.grid td.cell.swap-src{opacity:.45}
.grid td.cell.swap-target{box-shadow:inset 0 0 0 2px var(--accent);background:#F1EBDC}
.grid td.cell.swap-pending{opacity:.5;pointer-events:none}
.swap-ghost{position:fixed;z-index:1000;pointer-events:none;max-width:240px;padding:6px 10px;
  border:1px solid var(--accent);border-radius:var(--radius-sm);background:var(--surface);
  box-shadow:var(--shadow-lg);opacity:.92}
/* editors */
.editor{box-shadow:var(--shadow-lg);border:1px solid var(--border);border-radius:var(--radius);background:var(--surface)}
.rec{border:1px solid var(--border);border-radius:var(--radius-sm);padding:10px var(--s3);margin-bottom:10px}
.rec-head{display:flex;justify-content:space-between;font-weight:700;font-size:calc(10px * var(--fs, 1));line-height:1;font-family:var(--font);letter-spacing:.05em;text-transform:uppercase;color:var(--accent);margin-bottom:8px}
.spinner{width:12px;height:12px;border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;display:inline-block;animation:spin .7s linear infinite;vertical-align:-1px}
@keyframes spin{to{transform:rotate(360deg)}}
.htmx-request .spinner{display:inline-block}.spinner.htmx-indicator{display:none}.htmx-request.spinner,.htmx-request .spinner{display:inline-block}
/* helpers */
.cluster{display:flex;gap:var(--s2);align-items:center;flex-wrap:wrap}
.stack>*+*{margin-top:var(--s3)}
.toolbar{display:flex;justify-content:space-between;align-items:center;gap:var(--s3);flex-wrap:wrap;margin-bottom:var(--s3)}
.spacer{flex:1}
/* accessibility */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
/* responsive */
@media screen and (max-width:640px){
  main{padding:var(--s4) var(--s3)}
  .site-header .bar{flex-wrap:wrap;gap:var(--s2)}
  .site-nav{font-size:calc(13px * var(--fs, 1));gap:var(--s2);flex-wrap:wrap}
  h1{font-size:calc(19px * var(--fs, 1))}
  .wk{flex-wrap:wrap}
  .wk .meta{width:100%}
  .two-pane{flex-direction:column}
  .actionbar{gap:var(--s2)}
  .grid-scroll{max-height:55vh}
  main form input,main form select,main form textarea{width:100%}
  .toolbar{flex-direction:column;align-items:flex-start}
}
/* dashboard status left-edge stripe */
.status-edge-draft{border-left-color:#8A8F99}.status-edge-review{border-left-color:var(--warn)}
.status-edge-validated,.status-edge-generated{border-left-color:#2F8FBF}.status-edge-confirmed{border-left-color:#4F73C4}
.status-edge-submitting,.status-edge-uploaded,.status-edge-extracting{border-left-color:#3B82C4}
.status-edge-submitted{border-left-color:var(--ok)}.status-edge-failed,.status-edge-submit_failed{border-left-color:var(--error)}
/* version footer (full-width: version sits at the right edge of the canvas) */

/* per-outlet brand logo shown before the outlet name */
.outlet-logo{height:calc(30px * var(--fs, 1));max-width:calc(200px * var(--fs, 1));width:auto;vertical-align:middle;margin-right:8px;object-fit:contain}
.outlet-logo-lg{height:calc(32px * var(--fs, 1));max-width:calc(240px * var(--fs, 1))}
/* Laptop and up: a smaller mark on the review title row, so "Back" (and
   the spreadsheet-mode toggle beside it) stay on the SAME line — at
   1366px the 32px logo pushed them onto a second row, 95px tall against
   41px (customer 2026-08-27). Measured: 26px still wrapped, 22px fits. */
@media screen and (min-width:1025px){
  .title-row .outlet-logo-lg{height:calc(22px * var(--fs, 1));max-width:calc(150px * var(--fs, 1))}
}

/* modal dialogs (native <dialog>) */
dialog.dlg{border:1px solid var(--border);border-radius:var(--radius);background:var(--surface);
  color:var(--ink);padding:var(--s4);min-width:320px;max-width:92vw;box-shadow:var(--shadow-lg,0 10px 36px rgba(0,0,0,.25))}
dialog.dlg::backdrop{background:rgba(0,0,0,.35)}
dialog.dlg h3{margin-top:0}
dialog.dlg .field{margin-bottom:var(--s3)}
dialog.dlg-wide{width:min(1200px,94vw)}
/* shared confirm/alert dialog (base.html + js/confirm.js) */
dialog.dlg-confirm{width:min(440px,92vw)}
dialog.dlg-confirm p{margin:0 0 var(--s4)}
/* staff-edit popup (staff list pane) */
#staff-edit-dlg{width:min(520px,92vw)}
.grid th.day-weekend{color:var(--error)}   /* Sat/Sun day headers read red (web + printable PDF) */
/* grid row-selection checkboxes (bulk delete) */
.row-sel,.row-sel-all{width:calc(18px * var(--fs, 1));height:calc(18px * var(--fs, 1));
  accent-color:var(--accent,#A8842C);cursor:pointer;vertical-align:middle}
/* leave-list dialog: outlet–section filter list */
#leave-dlg{width:min(560px,92vw)}
/* fixed row height so the list shows up to 10 rows (10 rows + 9 gaps + padding)
   and scrolls internally beyond — capped by the viewport so the POPUP itself
   never becomes the scroller on short screens */
#leave-os-list{max-height:min(calc(240px * var(--fs, 1) + 88px), 44vh);overflow:auto;display:flex;flex-direction:column;gap:4px;
  border:1px solid var(--border);border-radius:var(--radius-sm);padding:8px;margin-top:4px}
.os-item{display:flex;gap:8px;align-items:center;cursor:pointer;min-height:calc(24px * var(--fs, 1));flex-shrink:0}
.os-item input{width:calc(16px * var(--fs, 1));height:calc(16px * var(--fs, 1));
  accent-color:var(--accent,#A8842C);cursor:pointer;flex:0 0 auto}
/* below desktop: the list shows every record and the POPUP scrolls instead */
@media screen and (max-width:1024px){
  #leave-os-list{max-height:none;overflow:visible}
  #leave-dlg{max-height:92vh;overflow-y:auto}
}
/* full-viewport dialog (uploaded source): sized by the screen, no page scroll;
   close via the corner ✕ or a click on the backdrop */
dialog.dlg-full{width:94vw;height:92vh;max-width:none;overflow:hidden;padding:var(--s3) var(--s4)}
dialog.dlg-full .source-panel{height:100%;display:flex;flex-direction:column;border:0;padding:0;margin:0}
dialog.dlg-full .source-panel>div{flex:1;min-height:0;max-height:none !important;overflow:auto}
dialog.dlg-full .sv-viewport{max-height:calc(92vh - 150px) !important}
dialog.dlg-full iframe{height:calc(92vh - 150px) !important}
.dlg-x{position:absolute;top:8px;right:12px;border:0;background:transparent;cursor:pointer;
  color:var(--muted);font-size:calc(20px * var(--fs, 1));line-height:1;padding:6px;z-index:2}
.dlg-x:hover{color:var(--ink)}

/* top bar: html.hdr-sticky is set by cdgStickyRule (app.js) — desktop-sized
   window AND the bar consumes <= ~20% of it; otherwise the bar scrolls away
   so the content gets the full viewport */
html.hdr-sticky .site-header{position:sticky;top:0;z-index:30}
html.hdr-sticky .actionbar{top:var(--hdr-h, 60px)}  /* stack below the measured bar */
/* filtered-list drag: the row that will swap with the dragged one */
tr.swap-hover>td{box-shadow:inset 0 0 0 2px var(--accent)}
/* icon-only buttons (e.g. the Edit pencil): square-ish padding */
.btn-ico{padding:8px 10px}

/* submission-success popup: the shared dlg-confirm alert shape with a large
   success-green message; OK is the only way out (its label stays centred) */
.submit-ok .submit-ok-msg{font-size:calc(24px * var(--fs, 1));font-weight:700;color:var(--ok);text-align:center;margin:var(--s3) 0 var(--s4)}
.submit-ok .submit-ok-row{justify-content:center}
.submit-ok .submit-ok-btn{min-width:7em;justify-content:center}

/* per-date remarks in the grid header (customer 2026-08-19) */
.day-remark{margin-top:2px;font-weight:400}
/* bold, on the grid and on paper alike (customer 2026-08-27) — the weight sits
   on the TEXT, not on .day-remark, which also wraps the editor's controls */
.day-remark-text{display:inline-block;max-width:100%;font-size:calc(11px * var(--fs, 1));
  font-weight:700;color:var(--muted,#6f6a61);cursor:pointer;line-height:1.2;
  overflow-wrap:anywhere;white-space:pre-line}   /* remarks may be several lines */
.day-remark-text:hover{color:var(--ink);text-decoration:underline dotted}
.day-remark-add{opacity:.35}
th:hover .day-remark-add{opacity:.9}
/* the editor stacks: the note first, then its colours, then the buttons
   (customer 2026-08-27) — in one row the twelve swatches squeezed the text
   box down to nothing in a day-wide column */
.day-remark form{display:flex;flex-direction:column;gap:4px;align-items:stretch}
.day-remark-colors{display:flex;flex-wrap:wrap;gap:3px;justify-content:center}
.day-remark-colors .hl-sw span{width:calc(15px * var(--fs, 1));height:calc(15px * var(--fs, 1))}
.day-remark-actions{display:flex;gap:4px;justify-content:center}
.day-remark-input{font-size:calc(11px * var(--fs, 1));padding:2px 4px;min-width:0;width:100%;
  resize:vertical;font-family:inherit}

/* "Sorting" menu button in the grid's Staff header (customer 2026-08-19) */
.sort-btn{display:inline-block;margin-left:6px;padding:2px 8px;border:1px solid var(--border);
  border-radius:var(--pill);background:var(--surface);color:var(--muted);cursor:pointer;
  font-size:calc(11px * var(--fs, 1));font-weight:600;white-space:nowrap;vertical-align:middle}
.sort-btn:hover{color:var(--ink);border-color:var(--accent)}
.bar-menu .menu-item .sort-dir{float:right;margin-left:12px;color:var(--accent);font-weight:700}
/* the reference columns beside Total — quiet figures, narrow headings */
.grid th.lw-ot-head{font-size:calc(11px * var(--fs, 1));line-height:1.2;white-space:normal;max-width:7em}
.grid td.ot-acc,.grid td.lw-hours,.grid td.tw-ot{color:var(--muted)}
/* "OT Hours (Accumulation)" is TYPED, not worked out (customer 2026-09-22):
   a box the width of the column, which posts itself on change. It is refused
   in our own words, so a bad figure outlines red and says why on hover. */
/* Both OT boxes are NARROW (customer 2026-09-25: the day cells need the room
   to be double-clicked). A number box's own width is far wider than "999.5",
   and at width:100% it set the column — as wide as a day, measured 109px
   beside a 107px day at 1366 — so the box has a width of its own and the
   cell shrinks to it (width:1% on an auto table), the heading wrapping. */
.grid td.ot-acc,.grid td.tw-ot{padding:2px;width:1%}
.ot-acc-in{width:calc(5ch + 12px);min-width:0;box-sizing:border-box;padding:2px 4px;text-align:right;
  border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--surface);
  color:var(--ink);font-family:inherit;font-size:calc(12px * var(--fs, 1))}
.ot-acc-in:focus{outline:2px solid var(--accent);outline-offset:-1px}
.ot-acc-bad{border-color:var(--cell-err-bd);background:var(--cell-err);color:var(--error)}
/* day header: the weekday on its own line, the month and day beneath, both
   centred (customer 2026-09-07) */
.grid th.day-head{text-align:center}
.grid th .day-wd{display:block}
.grid th .day-md{white-space:nowrap}
/* the two foot rows: total man-hours and staff on duty per day (customer 2026-09-07) */
.grid tfoot td{background:var(--bg);border-top:2px solid var(--border);font-weight:600;
  font-size:calc(12px * var(--fs, 1));text-align:center;white-space:nowrap;padding:6px 8px}
.grid tfoot td.dt-label{text-align:left;position:sticky;left:0;z-index:2;color:var(--muted);
  font-size:calc(11px * var(--fs, 1));letter-spacing:.04em;text-transform:uppercase}
.grid tfoot td.dt-week{color:var(--ink);background:#EDE7DA}
.grid tfoot td.dt-rest{background:var(--bg)}

/* spreadsheet mode: cell cursor + multi-cell selection (customer 2026-08-19) */
.sheet-mode .grid td.cell{cursor:cell}
.sheet-mode .grid td.cell.cell-sel{background:#EAF1F8}
.sheet-mode .grid tbody td.cell.cell-sel.cell-amber{background:#F3ECD2}
.sheet-mode .grid tbody td.cell.cell-sel.cell-error{background:#F6E2E0}
.sheet-mode .grid td.cell.cell-active{outline:3px solid #3B6EA5;outline-offset:-3px}
/* the grid body is the keyboard's focus host (tabindex="-1", customer
   2026-09-23); the cursor ring above says where the keys will land, so the
   host itself takes no ring of its own */
#grid-body:focus,#tp-body:focus{outline:none}
.hl-sel-palette{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:var(--s2) 0 var(--s3)}
.hl-sel-palette .hl-sw{margin:0}

/* the toggle and its ⓘ read as one control: tight together, normal gap to Back */
.sheet-ctl{display:inline-flex;align-items:center;gap:3px}
.sheet-ctl .status-tip svg{width:calc(18px * var(--fs, 1));height:calc(18px * var(--fs, 1))}
/* spreadsheet-mode toggle: the state must read at a glance, not only via aria */
#sheet-toggle{border:1px solid var(--border);gap:6px}
#sheet-toggle[aria-pressed="true"]{background:#EAF1F8;border-color:#3B6EA5;color:#24435F}
#sheet-toggle[aria-pressed="false"]{background:transparent;color:var(--muted);border-style:dashed}
#sheet-toggle[aria-pressed="true"]:hover{background:#DCE8F4}
#sheet-toggle[aria-pressed="false"]:hover{color:var(--ink);border-color:var(--accent)}

/* per-column select checkbox in the day headers — deliberately larger than a
   default box: it is a header-level control and an easy target (2026-08-20) */
/* on its own line under the date, centred, so every column reads the same
   whether or not the date fits beside it (customer 2026-09-07) */
.grid th .col-sel{display:block;margin:4px auto 0;vertical-align:middle;cursor:pointer;
  width:calc(15px * var(--fs, 1));height:calc(15px * var(--fs, 1));accent-color:#3B6EA5}
.grid th .col-sel:disabled,.row-sel:disabled,.row-sel-all:disabled{cursor:not-allowed;opacity:.4}

/* spreadsheet-mode guide popover: keys as chips, meaning beside them */
.guide-btn{display:inline-flex;align-items:center;justify-content:center;border:0;background:transparent;
  padding:2px;cursor:pointer;color:var(--muted)}
.guide-btn:hover,.guide-btn[aria-expanded="true"]{color:var(--accent)}
.guide-btn svg{width:calc(18px * var(--fs, 1));height:calc(18px * var(--fs, 1))}
.guide-pop{position:fixed;z-index:70;display:grid;gap:5px 14px;padding:10px 14px;
  background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
  box-shadow:var(--shadow-lg);font-size:calc(12.5px * var(--fs, 1));color:var(--ink);
  max-width:min(420px,92vw)}
.guide-pop[hidden]{display:none}
.guide-row{display:grid;grid-template-columns:auto 1fr;gap:10px;align-items:center}
.guide-keys{display:inline-flex;gap:3px;align-items:center;white-space:nowrap}
/* the same key chips serve the cell editor's hints (customer 2026-09-15) */
.guide-pop kbd,.kbd-hint kbd{display:inline-block;min-width:16px;text-align:center;border:1px solid #c9c2b3;
  border-bottom-width:2px;border-radius:4px;background:#f7f4ec;padding:1px 6px;font-weight:600;
  font-size:calc(11px * var(--fs, 1));line-height:1.4;font-family:ui-monospace,Consolas,monospace;color:#5a513c}
.guide-row>span:last-child{color:var(--muted)}

/* cell-highlight dialog: the "leave unchanged" option */
.hl-sw-keep span{font-weight:700;color:var(--muted)}

/* manual leave-balance column (customer 2026-08-20) */
.grid td.lv-bal{white-space:normal;overflow-wrap:anywhere}
.lv-bal-text{display:inline-block;min-width:2em;cursor:pointer}
.lv-bal-text:hover{text-decoration:underline dotted}
.lv-bal-add{opacity:.35}
/* a figure the platform worked out (entitlement minus this year's leave),
   not one that was typed */
/* a figure a rule worked out, beside ones that were typed: the leave
   balances. (`.ot-auto` went with the OT-hours rule, customer 2026-09-23.) */
.lv-bal-auto{color:var(--muted);font-style:italic}
.grid td.lv-bal:hover .lv-bal-add{opacity:.9}
.lv-bal-form{display:flex;gap:2px;align-items:center;justify-content:center}
.lv-bal-form .input{font-size:calc(12px * var(--fs, 1));padding:2px 4px;min-width:0;width:100%}

/* --- staff cell: 📁 details button + its popup (customer 2026-08-20) ---
   Absolutely placed in the cell's top-right corner. NO `position:relative` on
   the td: that would override the column's `position:sticky` and the staff
   cells would scroll out over the Monday column (they did). A sticky element is
   already a containing block for its absolute children. */
.grid td.staff{padding-right:30px}
.staff-info{position:absolute;top:2px;right:4px;border:0;background:transparent;cursor:pointer;
  padding:0 2px;line-height:1;opacity:.75;font-size:calc(19px * var(--fs, 1))}
.grid td.staff:hover .staff-info,.staff-info:focus-visible{opacity:1}
.staff-info-list{display:grid;grid-template-columns:auto 1fr;gap:5px 16px;margin:var(--s3) 0 0;
  font-size:calc(13px * var(--fs, 1));text-align:left}
.staff-info-list dt{font-weight:600;color:var(--muted)}
.staff-info-list dd{margin:0}
#staff-info-dlg{min-width:min(420px,92vw)}

/* row picker: its own narrow column right of the staff cell, headed by the
   ☑ menu (all rows / all columns / none) — customer 2026-08-20 */
.grid th.sel,.grid td.sel{width:30px;padding:4px 2px;text-align:center;vertical-align:middle}
.sel-btn{border:0;background:transparent;cursor:pointer;color:var(--ink);padding:2px 3px;
  border-radius:4px;font-size:calc(13px * var(--fs, 1));line-height:1}
.sel-btn:hover,.sel-btn[aria-expanded="true"]{background:var(--surface)}
.menu-item .menu-tick{margin-left:auto;padding-left:12px}
.dlg-actions{display:flex;justify-content:center;margin-top:var(--s4)}

/* --- Leave Balance: split header + the More... menu toggle that shows it --- */
/* 13px, not 11: it heads three columns and was the smallest thing in the
   header band (customer 2026-08-27) */
.grid th.bal-group{font-size:calc(13px * var(--fs, 1));white-space:nowrap}
.grid thead tr.head-sub th{top:var(--head1-h, 0px)}
.grid.no-bal th.bal-head,.grid.no-bal td.lv-bal,.grid.no-bal tr.head-sub{display:none}
/* the accumulation + last-week columns, behind "Last week statistics" in More... (customer 2026-09-08) */
.grid.no-lw th.lw-head,.grid.no-lw td.ot-acc,.grid.no-lw td.lw-hours{display:none}
/* Staff another roster lent us (customer 2026-09-22): behind "Show borrowed
   staff" in More..., and drawn so nobody takes one for a row of their own —
   a quieter band, with the roster that wrote it named under the person. */
.grid.no-bor tr.row-borrowed{display:none}
/* ...and the two foot rows add up to what the grid is DRAWING (customer
   2026-09-23): the week's own figure while the borrowed rows are hidden, the
   figure that counts them in while they are shown. */
.grid .dt-all{display:none}
.grid:not(.no-bor) .dt-own{display:none}
.grid:not(.no-bor) .dt-all{display:inline}
.grid tbody tr.row-borrowed td{background:var(--bg)}
.bor-from{font-size:calc(11px * var(--fs, 1));color:var(--muted);white-space:nowrap}
.menu-item .menu-tick{display:inline-block;width:1em;visibility:hidden}
.menu-item[aria-checked="true"] .menu-tick{visibility:visible}

/* staff-details popup: the Edit button beside the name, and the entitlement block */
/* the ✕ is absolutely placed in the dialog's corner, so the heading row keeps
   clear of it however long the name is */
.info-head{display:flex;align-items:center;gap:var(--s3);flex-wrap:wrap;padding-right:2.6em}
/* the staff form brings its own Cancel/Save, so the hosting dialog's ✕ and
   Close stand down while it is showing — three ways out, one of them below
   Save, read as "leave without saving" (customer 2026-08-26) */
#staff-info-dlg:has(.staff-edit-form) .dlg-x,
#staff-info-dlg:has(.staff-edit-form) .dlg-actions{display:none}
#staff-info-dlg .dlg-title{margin:0}
.ent-head{margin:var(--s4) 0 0;font-size:calc(13px * var(--fs, 1))}
/* the staff popup (Manage staff AND the roster grid's 🗂 popup) is two columns
   on a laptop — the staff record left, the year's leave ceilings right — so it
   fits without scrolling (customer 2026-08-26) */
/* ids, to outrank #staff-edit-dlg's own narrow width above */
#staff-edit-dlg:has(.staff-edit-form),
#staff-info-dlg:has(.staff-edit-form){width:min(1000px,94vw)}
.sf-cols{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:var(--s4);align-items:start}
.sf-cols.sf-one{grid-template-columns:minmax(0,1fr)}
.sf-cols .ent-fields{margin:0}
/* the staff record is itself two columns of fields, so the popup stays short
   enough for a laptop screen: both columns lay their fields out two-a-row, each field a block that carries
   its own error under its own box (customer 2026-08-26) */
.ff-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 var(--s3)}
.ff-field{margin:0 0 var(--s2);min-width:0}
.ff-wide{grid-column:1/-1}
.ff-err{color:var(--error);font-size:calc(12px * var(--fs, 1));line-height:1.3;margin-top:3px}
.ff-bad input,.ff-bad textarea,.ff-bad select{border-color:var(--error);background:var(--cell-err)}
.sf-side .ent-fields .small{margin:0 0 var(--s2)}
.sf-cols+.cluster{margin-top:var(--s3)}
.sf-cols input,.sf-cols select,.sf-cols textarea{width:100%;box-sizing:border-box}
@media screen and (max-width:860px){.sf-cols,.ff-grid{grid-template-columns:minmax(0,1fr)}}
.ent-fields{border:1px solid var(--border);border-radius:var(--radius);padding:var(--s3);margin:var(--s3) 0}
/* a ceiling this staff member has not got: greyed out and empty, as on the
   Leave entitlement page (customer 2026-08-26) */
.ent-fields input:disabled{background:var(--bg);color:var(--muted);cursor:not-allowed}
.ent-fields legend{padding:0 6px;font-weight:600}

/* the staff-list reminder: a banner with its own way out (customer 2026-08-28) */
.staff-stale{display:flex;align-items:center;gap:var(--s3,12px);flex-wrap:wrap}
.staff-stale .btn{margin-left:auto}

/* The portal's own reply, quoted: labelled so its wording is never mistaken
   for one of this platform's checks (customer 2026-08-28). */
.portal-said{margin:var(--s2) 0}
.portal-said-lbl{display:block;font-size:calc(12px * var(--fs, 1));font-weight:600;
  color:var(--muted,#6f6a61);margin-bottom:2px}
.portal-said pre{margin:0;white-space:pre-line;display:block;
  border-left:3px solid var(--cell-err-bd,#E2A6A1);padding-left:var(--s2,8px)}

/* "A new version is available" (customer 2026-08-28): bottom-right, above the
   page but below the dialogs, and it waits to be pressed — a manager mid-edit
   decides when to reload. */
.app-update{position:fixed;right:16px;bottom:16px;z-index:70;display:flex;align-items:center;gap:12px;
  padding:10px 14px;border:1px solid var(--border,#e3dccb);border-radius:var(--radius,8px);
  background:var(--surface,#fff);box-shadow:var(--shadow-lg,0 6px 24px rgba(0,0,0,.16));
  font-size:calc(13px * var(--fs, 1));max-width:min(92vw, 30em)}
.app-update .btn-sm{padding:5px 12px;font-size:calc(13px * var(--fs, 1))}
@media print{ .app-update{display:none} }
/* Template & Pattern tabs (customer 2026-09-09) */
.tabs{display:flex;gap:4px;border-bottom:1px solid var(--border);margin-bottom:var(--s3)}
.tabs .tab{padding:8px 14px;text-decoration:none;color:var(--muted);border-bottom:2px solid transparent;
  margin-bottom:-1px;font-size:calc(14px * var(--fs, 1));font-weight:600}
.tabs .tab:hover{color:var(--ink);background:var(--bg)}
.tabs .tab[aria-current="page"]{color:var(--ink);border-bottom-color:var(--accent)}
/* the shift-pattern editor's record block (customer 2026-09-09) */
.sp-records{border:1px solid var(--border);border-radius:6px;padding:var(--s3);margin:var(--s3) 0;max-width:720px}
.sp-records .rec{margin-bottom:var(--s2)}
/* a More… item that waits for a selection (Save shift as pattern, 2026-09-09) */
.menu-item[aria-disabled="true"]{opacity:.5;cursor:not-allowed}
/* the Add and Save-as-template popups hold comboboxes whose list drops below the
   panel's edge; a scrolling dialog clipped it (seen 2026-09-09), and both are short */
dialog#startfrom-dlg,dialog#save-pattern-dlg{overflow:visible}
