/* =========================================================================
   Leave & duty grid - the wide 9-week calendar on leave.php.
   Follows the same palette as style.css; only the grid is new.
   ========================================================================= */

.container.full { max-width: none; padding: 0 18px; }

/* ---- centre tabs -------------------------------------------------------- */
.centre-tabs {
    display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
    margin-bottom: 12px;
}
.centre-tab {
    padding: 7px 16px; border-radius: 999px; font-size: 14px; font-weight: 600;
    text-decoration: none; color: #374151; background: #fff;
    border: 1px solid #d1d5db;
}
.centre-tab:hover { background: #eef2f7; }
.centre-tab.active { background: #2563eb; border-color: #2563eb; color: #fff; }
.centre-tabs .spacer { margin-left: auto; }

/* Drop the borrowed relieving / runner rows from the grid. Admin only - nobody
   else is shown the other centres' staff in the first place. */
.grid-filter {
    display: flex; align-items: center; gap: 7px; max-width: 340px;
    padding: 5px 10px; border-radius: 999px; background: #fff;
    border: 1px solid #d1d5db;
    font-size: 11px; line-height: 1.3; color: #4b5563; cursor: pointer;
}
.grid-filter:hover { color: #111827; background: #f8fafc; }
.grid-filter input { flex: 0 0 auto; margin: 0; }
.grid-filter .cnt {
    flex: 0 0 auto; padding: 0 6px; border-radius: 999px;
    background: #eef2f7; color: #6b7280; font-weight: 700; font-size: 10px;
}
.grid-filter:has(input:checked) { border-color: #2563eb; color: #1e40af; }
.grid-filter:has(input:checked) .cnt { background: #dbeafe; color: #1d4ed8; }

/* The grid's only writer. Amber while there is something outstanding, so the
   page never looks saved when it is not. */
.grid-save { display: flex; align-items: center; gap: 7px; }
.grid-save:not(:disabled) { background: #d97706; }
.grid-save .cnt:empty { display: none; }
.grid-save .cnt {
    padding: 0 6px; border-radius: 999px; font-size: 11px; font-weight: 700;
    background: rgba(255, 255, 255, .28);
}

/* ---- the grid ----------------------------------------------------------- */
.grid-scroll {
    overflow: auto; max-height: 78vh; border: 1px solid #e5e7eb;
    border-radius: 8px; background: #fff;
}
/* macOS floats the horizontal scrollbar over the content, which hid the last
   row. This keeps clear space under the table for it to sit in. */
.grid-spacer { height: 16px; }
.leave-grid {
    border-collapse: separate; border-spacing: 0;
    font-size: 11px; line-height: 1.3; white-space: nowrap;
}
.leave-grid th, .leave-grid td {
    border-right: 1px solid #eef2f7; border-bottom: 1px solid #eef2f7;
    padding: 0; text-align: center;
}

/* sticky header rows.

   The ladder matters and reads bottom-up: the frozen body columns (2) sit
   above the date cells they slide over, the frozen header (3) sits above
   both, and the two header corners (4) sit above everything because they are
   frozen in both directions at once. Get this the other way round and a
   group band - whose title is allowed to run past its column - slides up
   over the dates and the weekday names as the grid is scrolled. */
.leave-grid thead th {
    position: sticky; background: #f8fafc; z-index: 3;
    font-weight: 700; color: #374151; padding: 3px 4px;
}
.leave-grid .row-week  th { top: 0;    font-size: 11px; background: #eef2f7; }
.leave-grid .row-date  th { top: 21px; font-weight: 600; color: #6b7280; }
.leave-grid .row-dow   th { top: 41px; font-size: 10px; color: #9ca3af; }

/* sticky name column */
.leave-grid .name-col {
    position: sticky; left: 0; z-index: 2;
    background: #fff; text-align: left; width: 340px; min-width: 340px;
    padding: 3px 10px; border-right: 2px solid #d1d5db;
}
.leave-grid thead .name-col { z-index: 4; background: #eef2f7; font-size: 13px; }

/* ---- the Saturday tally, pinned to the right ---------------------------
   The mirror of the name column: that one holds the who while the dates scroll
   past, this one holds the how many. Nine weeks is wider than any screen, so a
   count at the far end of the row would only ever be read by scrolling to the
   end of the cycle - which is the one place nobody is looking. */
.leave-grid .total-col {
    position: sticky; right: 0; z-index: 2;
    width: 58px; min-width: 58px;
    background: #fffbeb;                    /* the ordinary Saturday's own yellow */
    border-left: 2px solid #d1d5db;
    text-align: center; font-weight: 800; color: #854d0e;
    font-variant-numeric: tabular-nums;
}
.leave-grid thead .total-col {
    z-index: 4; background: #fef3c7; color: #854d0e;
    line-height: 1.15; padding: 2px 4px;
}
.leave-grid thead .total-col .tc-label { display: block; font-size: 10px; }
.leave-grid thead .total-col .tc-of {
    display: block; font-size: 9px; font-weight: 600; color: #a16207;
}
/* A nought is the thing being looked for, so it is quiet rather than bold. */
.leave-grid .total-col.is-none { color: #d1d5db; font-weight: 600; }
.leave-grid tbody tr:hover .total-col { background: #fef3c7; }
.leave-grid .name-staff { display: flex; align-items: center; gap: 5px; }
.leave-grid .name-staff .rank,
.leave-grid .name-staff .abbr,
.leave-grid .name-staff .note-tag { flex: 0 0 auto; }
.leave-grid .name-staff .n {
    font-weight: 500; color: #111827; white-space: nowrap;
}
.leave-grid .name-staff .rank { font-size: 9px; padding: 0 5px; }
.leave-grid .name-staff .note-tag { font-size: 9px; }
/* Carried on every row for the phone grid, which leads with it; the desktop
   grid has room for the whole name and never shows it. */
.leave-grid .name-staff .nick { display: none; }
.leave-grid .name-staff .abbr {
    font-size: 9px; font-weight: 700; letter-spacing: .03em;
    background: #f1f5f9; color: #475569; border: 1px solid #e2e8f0;
    padding: 0 4px; border-radius: 3px; cursor: help;
}

/* ---- AL import bar ------------------------------------------------------ */
/* ---- a card that folds away ---------------------------------------------
   A tall panel sitting above a grid that wants the height: shut, it is one
   line saying what it holds; open, it is the whole thing. Used by the batch
   panel on both rosters and by the import and reset panels above the leave
   grid, so it lives here, in the stylesheet all of them load.

       <details class="card fold-card no-print" id="...">
           <summary class="fold-head">
               <span class="fold-caret" aria-hidden="true"></span>
               <b>Title</b>
               <span class="muted small fold-hint">shown when open</span>
               <span class="muted small fold-shut">shown when shut</span>
           </summary>
           ... the panel ...
       </details>
*/
.fold-card { margin-bottom: 12px; }
.fold-card[open] { padding-bottom: 14px; }

.fold-head {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    cursor: pointer; list-style: none; user-select: none;
    margin: -2px 0; padding: 2px 0;
}
.fold-head::-webkit-details-marker { display: none; }   /* our own caret below */
.fold-head:hover b { color: #1d4ed8; }

.fold-caret {
    flex: 0 0 auto; width: 9px; height: 9px; margin-right: 1px;
    border-right: 2px solid #6b7280; border-bottom: 2px solid #6b7280;
    transform: rotate(-45deg); transition: transform .15s ease;
}
.fold-card[open] .fold-caret { transform: rotate(45deg); }

/* One line of guidance when open, and when shut the one thing worth knowing
   without opening it - what was last imported, what is marked now. */
.fold-shut { display: inline; }
.fold-card[open] .fold-shut { display: none; }
.fold-hint { display: none; }
.fold-card[open] .fold-hint { display: inline; }

/* The body sits under a rule, so the summary reads as a heading rather than as
   the panel's first row. */
.fold-body {
    margin-top: 12px; padding-top: 12px; border-top: 1px solid #eef2f7;
}
.fold-body.al-import { margin-bottom: 0; }

/* ---- a duty-date card that folds ---------------------------------------
   The whole card head becomes the summary, so a shut card is the one line
   that already says what it holds: the date, how many are on, the SRT,
   whether it has gone out, and the station tally. Shut, the head's rule and
   the space under it go with the body - a folded card is a row, not a card
   with a gap in it. */
.duty-day.fold-card > .duty-day-head { cursor: pointer; }
.duty-day.fold-card:not([open]) > .duty-day-head {
    margin-bottom: 0; padding-bottom: 0; border-bottom: none;
}
.duty-day.fold-card:not([open]) { padding-bottom: 12px; }
.duty-day.fold-card > .duty-day-head h2 { margin: 0; }

/* Buttons living in the head - Release, Reset, Show all staff - are pressed,
   not used to fold; assets/fold.js keeps their click off the summary. The
   cursor says so too. */
.duty-day.fold-card .sat-inline,
.duty-day.fold-card .fold-keep { cursor: default; }

/* They stay at the right-hand end of the head, whatever else is in it. The
   head wraps once a date carries a dozen station pills, and the buttons are
   the one thing that must not drift into the middle of the row - so the auto
   margin rides on them rather than on the spacer alone, which only ever
   pushed the *first* line's contents over. */
.duty-day-head .sat-inline,
.duty-day-head .fold-keep { margin-left: auto; }

.fold-all {
    display: inline-flex; gap: 10px; align-items: center; margin-left: auto;
    padding-left: 12px;
}
.fold-all button {
    background: none; border: 0; padding: 0; font: inherit; font-size: 11.5px;
    font-weight: 600; color: #2563eb; cursor: pointer;
}
.fold-all button:hover { text-decoration: underline; }

.al-import {
    display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
    margin-bottom: 12px;
}
.al-import-actions {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    margin-left: auto;
}
.al-import-actions input[type=file] { font-size: 13px; max-width: 260px; }

/* Station heading row.

   It divides the grid into stations, and at #f8fafc across the days it was a
   tint you had to look for: on a nine-week grid the eye needs the break to
   carry all the way across, not stop at the name column. So the band is darker
   and it is ruled top and bottom - a heading with a line above and below reads
   as a divider, which is what it is, rather than as one more row of the
   roster. */
.leave-grid .row-group th.name-col {
    background: #dbeafe; font-weight: 700; color: #1e3a8a;
    text-transform: uppercase; letter-spacing: .04em; font-size: 10px;
}
.leave-grid .row-group td { background: #e2e8f0; }
.leave-grid .row-group th,
.leave-grid .row-group td {
    border-top: 1px solid #111827;
    border-bottom: 1px solid #111827;
}
/* The pinned column at the right edge is part of the same band. */
.leave-grid .row-group .total-col { background: #e2e8f0; border-left-color: #94a3b8; }

/* The on-loan filter: borrowed relievers and runners, and any group heading
   they were the whole of. */
.leave-grid.hide-on-loan tbody tr[data-from] { display: none; }
.leave-grid tbody tr.row-group.is-empty { display: none; }

/* ---- day cells ---------------------------------------------------------- */
.leave-grid tbody td {
    min-width: 34px; height: 22px; vertical-align: middle;
}
/* A cell carries only what differs from the default: the reliever, or "0" on a
   closed day. The station itself is the row's heading. */
.leave-grid tbody td .mk {
    display: block; font-size: 10px; font-weight: 700; color: #b45309;
}
.leave-grid td.is-sun .mk { color: #9ca3af; font-weight: 600; }
/* Dark red on the holiday's red, not the Sunday's grey: the two columns are
   different closures and now look it. */
.leave-grid td.is-ph  .mk { color: #7f1d1d; font-weight: 600; }

.leave-grid td.is-sun, .leave-grid th.is-sun { background: #f1f5f9; color: #9ca3af; }
/* Red-200 rather than the red-50 this was. A public holiday is the department
   shut, which is the strongest thing a column of this grid can say, and at
   #fef2f2 it was a tint you had to already know about to see - on a screen
   across a room, or printed, it was white. */
.leave-grid td.is-ph,  .leave-grid th.is-ph  { background: #fecaca; }
.leave-grid th.is-ph { color: #7f1d1d; }

/* Imported AL: one grey, whichever centre's slot the name sat in.
   The colours used to say which centre - blue, light blue, orange - and that
   was three more things to read on a grid whose question is only ever "is this
   person here today". Grey answers it and says nothing else.

   #d8dce1 rather than a lighter one because Sunday is already #f1f5f9: the two
   sit next to each other all down the grid and have to tell apart at a glance
   (1.26:1, against 1.13:1 for the obvious #e5e7eb). */
.leave-grid td.al-hv,
.leave-grid td.al-pt,
.leave-grid td.al-akn,
.lg.al-hv, .lg.al-pt, .lg.al-akn { background: #d8dce1; }
.leave-grid td.al-hv .mk,
.leave-grid td.al-pt .mk,
.leave-grid td.al-akn .mk { color: #374151; }
/* A half day keeps its corner notch - the folded-page look the grid has always
   had - but the fold now says which half of the day is taken:
   
       (pm)  notch on the left        (am)  notch on the right
   
   The notch is the part they are *working*, cut out of the shading, so the
   shaded side is the half they are away for. Mirrored, so am and pm are told
   apart at a glance down a column rather than by reading a tooltip.
   
   A half day the feed gave no am/pm for keeps the original right-hand notch:
   it is still visibly half, without claiming which half. */
.leave-grid td.al-half {
    background-image: linear-gradient(to bottom right, transparent 62%, rgba(255,255,255,.85) 62%);
}
.leave-grid td.al-am {
    background-image: linear-gradient(to bottom right, transparent 62%, rgba(255,255,255,.85) 62%);
}
.leave-grid td.al-pm {
    background-image: linear-gradient(to bottom left, transparent 62%, rgba(255,255,255,.85) 62%);
}
/* Edited but not yet written. Nothing on this page saves by itself, so every
   cell touched since the last Save carries the amber ring until it is. */
.leave-grid tbody td.is-pending, .lg.is-pending {
    outline: 2px solid #f59e0b; outline-offset: -2px;
    background-image: linear-gradient(135deg, rgba(245,158,11,.18), rgba(245,158,11,.18));
}

/* a relieved day: the reliever's abbreviation, underlined green */
.leave-grid td.has-relief { box-shadow: inset 0 -3px 0 #16a34a; }
.leave-grid td.has-relief .mk { color: #15803d; }
/* On a grey AL cell the ordinary green is too light to read at 9px, so the
   abbreviation - who is covering that day, the one thing the cell must not
   hide - goes a shade darker. The green underline stays as it is. */
.leave-grid td.has-relief.al-hv .mk,
.leave-grid td.has-relief.al-pt .mk,
.leave-grid td.has-relief.al-akn .mk { color: #166534; }

/* Cover from another centre: the same underline and letters, in blue.
   
   Green is a colleague from the next room; blue is somebody who came across
   town to hold the station. Reading a week, that is the difference worth
   seeing without hunting for the name - which centre the abbreviation belongs
   to is not something anybody carries in their head for seventy-six people.
   
   These come after the green so they win on equal specificity, and the AL
   variant is a shade darker for the same reason the green one is: 9px type on
   the grey needs it (4.86:1 becomes 6.33:1). */
.leave-grid td.has-relief.relief-other { box-shadow: inset 0 -3px 0 #2563eb; }
.leave-grid td.has-relief.relief-other .mk { color: #1d4ed8; }
.leave-grid td.has-relief.relief-other.al-hv .mk,
.leave-grid td.has-relief.relief-other.al-pt .mk,
.leave-grid td.has-relief.relief-other.al-akn .mk { color: #1e40af; }

/* the other side of the same mark: a reliever covering a station that day */
.leave-grid td.is-covering { background: #ecfdf5; box-shadow: inset 0 -3px 0 #0d9488; }
.leave-grid td.is-covering .mk {
    color: #0f766e; font-weight: 800; font-size: 9px; letter-spacing: -.01em;
}

/* A reliever with nowhere to be, on a day they are in: "Rel". The same shape
   as a covering cell - a green line under a green word on a pale green - and
   a shade apart from it, so that "in and available" and "in and covering the
   simulator" read as kin without being taken for each other. */
.leave-grid td.is-relfree { background: #f0fdf4; box-shadow: inset 0 -3px 0 #22c55e; }
.leave-grid td.is-relfree .mk {
    color: #15803d; font-weight: 800; font-size: 9px; letter-spacing: .02em;
}
.lg.has-relief { background: #dcfce7; color: #15803d; border-color: #86efac; }
.lg.is-covering { background: #ecfdf5; color: #0f766e; border-color: #5eead4; }
.lg.is-comp-off { background: #fef9c3; color: #854d0e; border-color: #ca8a04; }
.lg.sat-reserved { background: #bbf7d0; color: #14532d; border-color: #4ade80; }
.lg.sat-reqoff   { background: #fecaca; color: #7f1d1d; border-color: #f87171; }
.lg.sat-has { background: #fef08a; color: #713f12; border-color: #eab308; }

/* ---- saturday picker ---------------------------------------------------- */
.sat-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; padding: 10px; }
.sat-grid.is-days { grid-template-columns: repeat(5, 1fr); }
.sat-opt {
    padding: 10px 8px; border: 1px solid #e5e7eb; background: #fff; border-radius: 7px;
    font: inherit; font-size: 13px; font-weight: 600; color: #374151; cursor: pointer;
    transition: background .13s ease, border-color .13s ease, transform .07s ease;
}
.sat-opt:hover { background: #fefce8; border-color: #eab308; }
.sat-opt:active { transform: translateY(1px) scale(.98); }
.sat-opt.is-current { background: #fef08a; border-color: #ca8a04; color: #713f12; }

/* the two extra marks sit on their own row under the weekdays */
.sat-extra {
    grid-column: 1 / -1; display: grid; grid-template-columns: 1fr 1fr; gap: 6px;
    margin-top: 4px; padding-top: 10px; border-top: 1px solid #e5e7eb;
}
.sat-opt-reserved { background: #f0fdf4; border-color: #86efac; color: #166534; }
.sat-opt-reserved:hover { background: #dcfce7; border-color: #22c55e; }
.sat-opt-reserved.is-current { background: #bbf7d0; border-color: #16a34a; color: #14532d; }
.sat-opt-reqoff { background: #fef2f2; border-color: #fca5a5; color: #991b1b; }
.sat-opt-reqoff:hover { background: #fee2e2; border-color: #ef4444; }
.sat-opt-reqoff.is-current { background: #fecaca; border-color: #dc2626; color: #7f1d1d; }
.lg.is-sun { background: #f1f5f9; color: #64748b; }
.lg.is-ph  { background: #fecaca; color: #7f1d1d; }

.is-admin .leave-grid tbody td[data-staff] { cursor: pointer; }
.is-admin .leave-grid tbody td[data-staff]:hover { outline: 2px solid #2563eb; outline-offset: -2px; }

/* reliever rows are not editable - no pointer, no hover outline */
.is-admin .leave-grid tbody td:not([data-staff]) { cursor: default; }

/* ---- saturday: two cells, and the weekday it pays for ------------------- */
.leave-grid th.is-sat { background: #fefce8; color: #854d0e; font-size: 9px; }
.leave-grid td.sat-cell { background: #fffdf3; min-width: 44px; }
.leave-grid td.sat-cell.has-val { background: #fef08a; }
/* An ordinary Saturday can be asked off, or held for duty - and being held for
   it now sits *beside* the weekday taken in lieu rather than instead of it. So
   green has to beat the yellow of a named weekday: it comes after .has-val
   deliberately, and the two selectors weigh the same. A green cell reading Mon
   is "reserved, and Monday is the day off"; a green cell reading Res is
   "reserved, and the weekday is not settled yet". */
.leave-grid td.sat-comp.is-reserved { background: #bbf7d0; }
.leave-grid td.sat-comp.is-reserved .mk { color: #14532d; }
.leave-grid td.sat-comp.is-reqoff   { background: #fecaca; }
.leave-grid td.sat-comp.is-reqoff .mk { color: #7f1d1d; }
.leave-grid td.sat-cell .mk { color: #713f12; font-size: 9px; font-weight: 800; }
.leave-grid td.sat-comp { border-right: 1px dashed #e5d9a8; }
.leave-grid td.sat-duty { border-right: 2px solid #d1d5db; }
.is-admin .leave-grid td.sat-cell { cursor: pointer; }
.is-admin .leave-grid td.sat-cell:hover { outline: 2px solid #ca8a04; outline-offset: -2px; }

/* the weekday taken off in lieu of working that Saturday */
.leave-grid td.is-comp-off {
    background: #fef9c3;
    border: 1px solid #ca8a04;   /* the thinnest a border draws */
}

/* Relieving and runner rows keep the yellow and lose the ring.
   Their rows are already the busiest on the grid - a green cover mark on most
   days, and the station they are covering written in it - and an outlined cell
   among all that read as one more thing to look at rather than as a day off.
   The fill says it on its own there.

   The border is made transparent rather than dropped, and the ordinary grid
   lines put back on the two sides that carry them, so the cell stays exactly
   the size it was: with border-collapse separate, taking a border away moves
   everything after it. */
.leave-grid tbody tr[data-reliever] td.is-comp-off {
    border-color: transparent;
    border-right-color: #eef2f7;
    border-bottom-color: #eef2f7;
}

/* call-in days: public holidays and special week Saturdays, one column each */
.leave-grid th.is-duty { background: #ede9fe; color: #5b21b6; }
.leave-grid td.duty-cell { background: #f5f3ff; min-width: 44px; }
.leave-grid td.duty-cell.has-val { background: #ddd6fe; }
.leave-grid td.duty-cell .mk { color: #4c1d95; font-size: 9px; font-weight: 800; }
.is-admin .leave-grid td.duty-cell { cursor: pointer; }
.is-admin .leave-grid td.duty-cell:hover { outline: 2px solid #7c3aed; outline-offset: -2px; }
.lg.duty-cell { background: #ddd6fe; color: #4c1d95; border-color: #a78bfa; }

/* ---- holiday duty page -------------------------------------------------- */
.duty-day { margin-bottom: 16px; }
.duty-day-head {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    padding-bottom: 10px; margin-bottom: 10px; border-bottom: 1px solid #e5e7eb;
}
.duty-day-head h2 { font-size: 16px; }
.duty-day-head .spacer { margin-left: auto; }
.duty-tally { display: flex; gap: 4px; flex-wrap: wrap; }
.duty-cols { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
@media (max-width: 1100px) { .duty-cols { grid-template-columns: 1fr; } }
.duty-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.duty-table th {
    text-align: left; font-size: 10px; text-transform: uppercase; letter-spacing: .05em;
    color: #6b7280; padding: 4px 6px; border-bottom: 1px solid #e5e7eb;
}
.duty-table td { padding: 4px 6px; border-bottom: 1px solid #f3f4f6; }
.duty-table tr:hover td { background: #f8fafc; }
.duty-chip {
    display: inline-block; font-size: 11px; font-weight: 700; padding: 1px 7px;
    border-radius: 4px; background: #ede9fe; color: #5b21b6; white-space: nowrap;
}

/* reveal panel on the holiday duty page */
.reveal-btn.is-open { background: #eef2f7; border-color: #9ca3af; }
.reveal-panel {
    margin-top: 14px; padding-top: 12px; border-top: 1px dashed #d1d5db;
}
.reveal-hint {
    display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
    margin-bottom: 10px;
}
.panel-save {
    margin-left: auto; font-size: 12px; font-weight: 600; white-space: nowrap;
    display: inline-flex; align-items: center; gap: 6px;
    padding: 4px 10px; border-radius: 999px;
    background: #f1f5f9; color: #6b7280; border: 1px solid #e5e7eb;
}
.panel-save::before {
    content: ""; width: 7px; height: 7px; border-radius: 50%; background: #9ca3af;
}
.panel-save[data-status="dirty"]  { background: #eff6ff; color: #1d4ed8; border-color: #93c5fd; }
.panel-save[data-status="dirty"]::before  { background: #3b82f6; }
.panel-save[data-status="saving"] { background: #eff6ff; color: #1d4ed8; border-color: #bfdbfe; }
.panel-save[data-status="saving"]::before { background: #3b82f6; }
.panel-save[data-status="saved"]  { background: #ecfdf5; color: #047857; border-color: #a7f3d0; }
.panel-save[data-status="saved"]::before  { background: #10b981; }
.panel-save[data-status="error"]  { background: #fef2f2; color: #b91c1c; border-color: #fecaca; }
.panel-save[data-status="error"]::before  { background: #ef4444; }
.reveal-panel .duty-table tr.is-on td { background: #f5f3ff; }
.reveal-panel .duty-table td { vertical-align: middle; }
/* ---- the station picker ------------------------------------------------
   The browser's own select arrow sits on top of the text once the column is
   narrow, which is exactly what this column is. So the native control is
   turned off and the chevron drawn as a background image with padding kept
   clear for it - the value can then never run underneath.

   The picker also carries its own answer: violet and filled once a station is
   chosen, matching .duty-chip elsewhere on the page, and quiet grey while the
   person is not called in. The row tint says the same thing, but the eye lands
   on the control being read. */
/* Room for the longest option there is - "On Duty 4" - so the value reads in
   full rather than as "On D…". The column it sits in is told to hold that
   width and no more, and the station beside it gives way instead: a station
   name wraps and still reads, a truncated duty code does not. */
.reveal-panel .duty-table td:nth-child(4),
.reveal-panel .duty-table th:nth-child(4) { width: 1%; white-space: nowrap; }
.reveal-panel .duty-table td:nth-child(3) { min-width: 0; overflow-wrap: anywhere; }
.reveal-panel .duty-table td:first-child  { min-width: 96px; }

.duty-pick {
    -webkit-appearance: none; appearance: none;
    font: inherit; font-size: 12.5px; font-weight: 600;
    width: 100%; min-width: 116px;
    padding: 5px 26px 5px 10px;
    border: 1px solid #d1d5db; border-radius: 6px;
    color: #6b7280;
    cursor: pointer;
    text-overflow: ellipsis;
    background-color: #fff;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%239ca3af' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 8px center;
    background-size: 9px 6px;
    transition: border-color .15s ease, background-color .15s ease, color .15s ease;
}
.duty-pick:hover { border-color: #9ca3af; background-color: #f8fafc; }
.duty-pick:focus-visible { outline: 2px solid #7c3aed; outline-offset: 1px; }
/* Chosen: the station reads as the violet chip it is elsewhere on the page. */
.duty-pick.is-set {
    color: #5b21b6; background-color: #ede9fe; border-color: #ddd6fe;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%237c3aed' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.duty-pick.is-set:hover { border-color: #c4b5fd; background-color: #e5e0fb; }
/* The list itself is the browser's, so put the colours back for it. */
.duty-pick option { color: #1f2937; background: #fff; font-weight: 500; }
/* an edit not yet saved reads blue, over either state above */
.duty-pick.is-dirty, .duty-comp.is-dirty {
    border-color: #2563eb; background-color: #eff6ff; color: #1e3a8a;
    box-shadow: 0 0 0 2px rgba(37, 99, 235, .18);
}
/* Written last so it beats .is-set, hover included: an unsaved edit is the one
   thing on this row that still needs doing. */
.duty-pick.is-dirty, .duty-pick.is-dirty:hover {
    border-color: #2563eb; background-color: #eff6ff; color: #1e3a8a;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%232563eb' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.duty-pick:disabled { opacity: .6; cursor: wait; }

.duty-comp {
    font: inherit; font-size: 11px; padding: 3px 5px; width: 100%;
    border: 1px solid #d1d5db; border-radius: 5px; background: #fff;
    transition: border-color .15s ease, background .15s ease;
}
.duty-comp:hover:not(:disabled) { border-color: #9ca3af; }
.duty-comp:focus { outline: 2px solid #7c3aed; outline-offset: -1px; }
.duty-comp:disabled { opacity: .4; background: #f8fafc; cursor: not-allowed; }


/* per-column sort toggle */
.side-title { display: flex; align-items: center; gap: 8px; }
.sort-btn {
    margin-left: auto; display: inline-flex; align-items: center; gap: 4px;
    font: inherit; font-size: 10px; font-weight: 700; letter-spacing: .03em;
    text-transform: none; color: #4b5563; cursor: pointer;
    background: #fff; border: 1px solid #d1d5db; border-radius: 999px;
    padding: 2px 9px; transition: background .13s ease, border-color .13s ease;
}
.sort-btn:hover { background: #eef2f7; border-color: #9ca3af; color: #111827; }
.sort-btn:active { transform: translateY(1px); }
.sort-btn[data-sort="station"] {
    background: #ede9fe; border-color: #a78bfa; color: #5b21b6;
}
.sort-ic { font-size: 11px; line-height: 1; }

/* the blue "unsaved" badge at a card's top right */
.card-modified {
    margin-left: auto; font-size: 11px; font-weight: 700; white-space: nowrap;
    padding: 4px 11px; border-radius: 999px;
    background: #dbeafe; color: #1e3a8a; border: 1px solid #60a5fa;
    box-shadow: 0 1px 3px rgba(37, 99, 235, .2);
}
.card-modified[hidden] { display: none; }

/* Save sits beside Transfer and only lights up when there is something to do */
#save-all { background: #d1d5db; color: #4b5563; border-color: #d1d5db; }
#save-all:disabled { opacity: .55; }
#save-all.has-pending {
    background: #2563eb; border-color: #2563eb; color: #fff;
    box-shadow: 0 2px 8px rgba(37, 99, 235, .3);
}
#save-all.has-pending:hover { background: #1d4ed8; border-color: #1d4ed8; filter: none; }
.save-count { font-variant-numeric: tabular-nums; }

/* jump menu across the top of the holiday duty page */
.duty-jump {
    display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
    padding: 10px 12px; margin-bottom: 14px;
    background: #fff; border: 1px solid #e5e7eb; border-radius: 8px;
    position: sticky; top: 0; z-index: 5;
}
.duty-jump-label {
    font-size: 11px; font-weight: 700; text-transform: uppercase;
    letter-spacing: .06em; color: #6b7280; margin-right: 2px;
}
.duty-jump-btn {
    display: inline-flex; flex-direction: column; align-items: center; line-height: 1.15;
    padding: 4px 10px; border-radius: 6px; text-decoration: none;
    border: 1px solid #e5e7eb; background: #fafafa; color: #4b5563;
    transition: background .13s ease, border-color .13s ease, transform .07s ease;
}
.duty-jump-btn:hover { background: #eef2f7; border-color: #9ca3af; color: #111827; }
.duty-jump-btn:active { transform: translateY(1px); }
.duty-jump-btn .jd-day { font-size: 12px; font-weight: 700; }
.duty-jump-btn .jd-yr  { font-size: 9px; color: #9ca3af; }
/* the two kinds of duty date read differently at a glance */
.duty-jump-btn.kind-public-holiday {
    background: #fee2e2; border-color: #fca5a5; color: #991b1b;
}
.duty-jump-btn.kind-public-holiday:hover { background: #fecaca; border-color: #ef4444; }
.duty-jump-btn.kind-public-holiday .jd-yr { color: #dc2626; }

.duty-jump-btn.kind-special-sat {
    background: #dbeafe; border-color: #93c5fd; color: #1e3a8a;
}
.duty-jump-btn.kind-special-sat:hover { background: #bfdbfe; border-color: #3b82f6; }
.duty-jump-btn.kind-special-sat .jd-yr { color: #2563eb; }

/* a date in this cycle keeps its kind colour but gains a ring */
.duty-jump-btn.in-cycle {
    box-shadow: 0 0 0 2px #7c3aed; font-weight: 700;
}

.duty-jump-note {
    flex: 1 1 100%; margin-top: 4px; display: flex; gap: 14px; flex-wrap: wrap;
    font-size: 11px; color: #6b7280;
}
.duty-jump-note .key { display: inline-flex; align-items: center; gap: 5px; }
.duty-jump-note .sw {
    width: 12px; height: 12px; border-radius: 3px; display: inline-block;
    border: 1px solid #d1d5db;
}
.duty-jump-note .sw.kind-public-holiday { background: #fee2e2; border-color: #fca5a5; }
.duty-jump-note .sw.kind-special-sat    { background: #dbeafe; border-color: #93c5fd; }
.duty-jump-note .sw.in-cycle-sw         { background: #fff; box-shadow: 0 0 0 2px #7c3aed; }

.pill-ok { background: #dcfce7; color: #166534; }
.normal-service-note {
    padding: 12px 14px; border-radius: 8px; background: #f0fdf4;
    border: 1px solid #bbf7d0; color: #166534; font-size: 13px; line-height: 1.5;
}
.normal-service-note b { display: block; margin-bottom: 2px; }

/* anchored cards should not hide under the sticky menu */
.duty-day { scroll-margin-top: 74px; }
.duty-day.is-outside { opacity: .82; background: #fcfcfd; }
.duty-day.is-outside .duty-day-head h2 { color: #4b5563; }

/* a public-holiday card reads yellow; special Saturdays stay white.
   Declared after .is-outside so an out-of-cycle holiday keeps the colour. */
.duty-day.kind-public-holiday {
    background: #fefce8; border-color: #fde68a;
}
.duty-day.kind-public-holiday .duty-day-head { border-bottom-color: #fde68a; }
.duty-day.kind-public-holiday .duty-table th { border-bottom-color: #fde68a; }
.duty-day.kind-public-holiday .duty-table td { border-bottom-color: #fef3c7; }
.duty-day.kind-public-holiday .duty-table tr:hover td { background: #fef9c3; }

/* ---- ordinary Saturday cards -------------------------------------------
   These read the other way round from the holiday cards above: the roster is
   the leave & duty list's, and what the card owns is the hours and the
   requests to be let off. Cool blue, so the eye can tell one kind of card
   from the other down a long page. */
.duty-day.kind-saturday { background: #f8fafc; border-color: #dbeafe; }
.duty-day.kind-saturday .duty-day-head { border-bottom-color: #dbeafe; }
.pill-sat      { background: #dbeafe; color: #1e40af; }
.pill-released { background: #dcfce7; color: #166534; }

/* The three kinds of duty date, told apart at a glance and coloured as the
   holiday page colours them: a special week Saturday blue, a public holiday
   the same yellow the grid shades its header with, an ordinary Saturday plain
   because it is the ordinary case. */
.duty-day.kind-special-sat {
    background: #f5f9ff; border-color: #bfdbfe;
}
.duty-day.kind-special-sat .duty-day-head { border-bottom-color: #bfdbfe; }
.pill-kind.k-special-sat    { background: #dbeafe; color: #1e40af; }
.pill-kind.k-public-holiday { background: #fef3c7; color: #92400e; }
.duty-jump-btn.kind-saturday {
    background: #f1f5f9; border-color: #cbd5e1; color: #334155;
}
.duty-jump-btn.kind-saturday:hover { background: #e2e8f0; border-color: #64748b; }
.duty-jump-btn.kind-saturday .jd-yr { color: #64748b; }
.duty-jump-note .sw.kind-saturday { background: #f1f5f9; border-color: #cbd5e1; }
/* A released Saturday is ringed green, the way the holiday page rings the
   dates inside its cycle: the jump bar says at a glance how much of the cycle
   has gone out. */
.duty-jump-btn.is-released { box-shadow: 0 0 0 2px #10b981; }
.duty-jump-note .sw.released-sw { background: #fff; box-shadow: 0 0 0 2px #10b981; }

/* ---- a section per centre ----------------------------------------------
   Three centres run three Saturdays that happen to share a date, and reading
   them as one list of blocks is how somebody gets sent to the wrong building.
   Each keeps the colour it has everywhere else on this site: HV blue, PT sky,
   AKN orange - and the department's own On Duty places, belonging to no
   centre, stay grey. */
.sat-centre {
    margin-bottom: 14px; padding: 8px 10px 10px; border-radius: 10px;
    background: #fafafa; border: 1px solid #eef2f7; border-left: 4px solid #cbd5e1;
}
.sat-centre-head {
    display: flex; align-items: baseline; gap: 10px; margin-bottom: 8px;
}
.sat-centre-name {
    font-size: 12px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase;
    color: #475569;
}
.sat-centre-n { font-size: 11px; color: #94a3b8; }

.sat-centre[data-centre="HV"]  { background: #f5f9ff; border-color: #dbeafe; border-left-color: #2563eb; }
.sat-centre[data-centre="PT"]  { background: #f4fbff; border-color: #bae6fd; border-left-color: #0ea5e9; }
.sat-centre[data-centre="AKN"] { background: #fffaf5; border-color: #fed7aa; border-left-color: #ea580c; }
.sat-centre[data-centre="HV"]  .sat-centre-name { color: #1e40af; }
.sat-centre[data-centre="PT"]  .sat-centre-name { color: #075985; }
.sat-centre[data-centre="AKN"] .sat-centre-name { color: #9a3412; }

/* The station code carries its centre's colour too, so a block read on its own
   still says which building it is in. */
.sat-centre[data-centre="HV"]  .duty-chip { background: #dbeafe; color: #1e3a8a; }
.sat-centre[data-centre="PT"]  .duty-chip { background: #e0f2fe; color: #075985; }
.sat-centre[data-centre="AKN"] .duty-chip { background: #ffedd5; color: #9a3412; }

.centre-pill[data-centre="HV"]  { background: #dbeafe; color: #1e3a8a; }
.centre-pill[data-centre="PT"]  { background: #e0f2fe; color: #075985; }
.centre-pill[data-centre="AKN"] { background: #ffedd5; color: #9a3412; }

/* One block per station that actually has somebody on it. Stations nobody is
   working are not drawn at all - a Saturday runs a handful of machines. */
.sat-groups {
    display: grid; gap: 14px;
    /* A block holds a name, a time range and a remark box side by side, so it
       cannot be squeezed below this without the names breaking one word to a
       line. Two across a laptop, three across a wide screen. */
    grid-template-columns: repeat(auto-fill, minmax(480px, 1fr));
}
@media (max-width: 520px) { .sat-groups { grid-template-columns: 1fr; } }
.sat-group {
    border: 1px solid #e5e7eb; border-radius: 8px; background: #fff;
    padding: 8px 10px 4px;
}
.sat-group-head {
    display: flex; align-items: center; gap: 8px;
    padding-bottom: 6px; margin-bottom: 4px; border-bottom: 1px solid #eef2f7;
}
.sat-group-name { font-size: 13px; font-weight: 700; color: #111827; }
.sat-group-name small {
    display: block; font-size: 10px; font-weight: 600; color: #6b7280;
}
.sat-group-n {
    margin-left: auto; font-size: 11px; font-weight: 700; color: #6b7280;
    background: #f1f5f9; border-radius: 999px; padding: 1px 8px;
}

/* ---- the machine, and the people not yet on one --------------------------
   The leave list says "HV Tx"; the senior running the day says which machine.
   Until they do, those people sit in a block of their own with a dashed edge:
   an unfinished Saturday should look unfinished at a glance rather than read
   as a station called Treatment. */
.sat-group.is-unplaced { border-style: dashed; border-color: #fcd34d; background: #fffbeb; }
.sat-group-note {
    font-size: 10px; font-weight: 700; text-transform: uppercase;
    letter-spacing: .03em; color: #92400e;
    background: #fef3c7; border-radius: 999px; padding: 1px 7px;
}
.sat-station { white-space: nowrap; }
.sat-station-pick, .sat-duty-pick {
    font-size: 12px; padding: 3px 6px; border: 1px solid #d1d5db;
    border-radius: 6px; background: #fff; font-family: inherit; max-width: 150px;
}
.sat-station-pick:focus, .sat-duty-pick:focus { outline: 2px solid #2563eb; outline-offset: -1px; }
/* Nobody picked yet: amber, the same "still to do" the block's edge uses. */
.sat-station-pick.is-none { border-color: #fcd34d; background: #fffbeb; color: #92400e; }
/* The station picker sits above the machine one, so the two read as one
   decision narrowing: which station, then which machine of it. */
.sat-duty-pick { display: block; margin-bottom: 3px; font-weight: 700; }
/* Moved for the day: violet, so it is plainly this page's doing and not what
   the leave & duty list says. The list's own station is spelled out under it. */
.sat-duty-pick.is-moved { border-color: #c4b5fd; background: #f5f3ff; color: #5b21b6; }
.sat-moved-from {
    display: block; margin-top: 2px; font-size: 9.5px; font-weight: 700;
    color: #6d28d9; text-transform: uppercase; letter-spacing: .03em;
}

/* ---- bringing a centre over ----------------------------------------------
   The only door between the two lists and this page, so it sits above the day
   and reads as a row of three deliberate presses rather than part of the form
   below it. */
.sat-import {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    margin: 8px 0 12px; padding: 8px 10px;
    border: 1px dashed #cbd5e1; border-radius: 8px; background: #f8fafc;
}
.sat-import-label { font-size: 12px; font-weight: 700; color: #374151; }
.sat-import-btn[data-centre="HV"]  { border-color: #93c5fd; }
.sat-import-btn[data-centre="AKN"] { border-color: #fdba74; }
.sat-import-btn[data-centre="PT"]  { border-color: #7dd3fc; }
.sat-table { table-layout: auto; }
.sat-table td { vertical-align: middle; }
.sat-table tr.is-off td { background: #fef2f2; }
.sat-table tr.is-off .sat-name { text-decoration: line-through; color: #9ca3af; }
.sat-who { min-width: 150px; }
.sat-name { font-weight: 600; }
.sat-sub {
    font-size: 10.5px; color: #6b7280; margin-top: 1px;
    display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
}
.sat-who .rl-sex { float: right; margin-left: 6px; }

/* Hours: two time boxes reading as one range. A box that has been changed
   away from the standard day is tinted, so a scan of the card shows which
   rows somebody decided and which are simply 09:00-17:00. */
.sat-hours { white-space: nowrap; width: 1%; }
.sat-hours.is-set { font-weight: 700; color: #1d4ed8; }
/* Wide enough for the 12-hour rendering some browsers give a time input -
   "09:00 AM" plus its clock - so the value never reads as "09:00 .". */
.sat-hours input[type="time"] {
    font: inherit; font-size: 11.5px; padding: 3px 4px; width: 104px;
    border: 1px solid #d1d5db; border-radius: 5px; background: #fff; color: #6b7280;
}
.sat-hours input[type="time"].is-set {
    background: #eff6ff; border-color: #93c5fd; color: #1d4ed8; font-weight: 600;
}
.sat-dash { color: #9ca3af; padding: 0 2px; }
.sat-note {
    font: inherit; font-size: 12px; padding: 4px 7px; width: 100%; min-width: 0;
    border: 1px solid #d1d5db; border-radius: 5px;
}
/* The remark is the column that gives: it takes what the name and the times
   leave, and never widens the table past the block holding it. */
.sat-table { width: 100%; }
.sat-table td:last-child { width: auto; min-width: 0; }
.sat-actions {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 12px;
}
.sat-inline { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* ---- request off -------------------------------------------------------- */
.sat-off { margin-top: 14px; padding-top: 12px; border-top: 1px dashed #cbd5e1; }
.sat-off-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.sat-off-table { margin-top: 8px; }
.sat-off-table td { vertical-align: middle; }
.sat-ask {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 10px;
}
.sat-ask select, .sat-ask input[type="text"] {
    font: inherit; font-size: 13px; padding: 5px 8px;
    border: 1px solid #d1d5db; border-radius: 6px; background: #fff;
}
.sat-ask input[type="text"] { min-width: 200px; flex: 1 1 200px; }
.sat-status {
    display: inline-block; font-size: 10px; font-weight: 800; padding: 1px 7px;
    border-radius: 999px; text-transform: uppercase; letter-spacing: .04em;
}
.sat-status.s-pending  { background: #fef3c7; color: #92400e; }
.sat-status.s-approved { background: #dcfce7; color: #166534; }
.sat-status.s-rejected { background: #fee2e2; color: #b91c1c; }
.sat-decide { white-space: nowrap; }
/* The requester's own column: one Cancel button, or a dash saying why not. */
.sat-cancel { white-space: nowrap; }
.sat-decide .sat-note { width: 160px; min-width: 0; }

/* What is being asked for. A whole day and sick leave take the person off the
   Saturday, so they read as the strong colours; time off inside a day they are
   still working is quieter, because they are still coming in. */
.sat-kind {
    display: inline-block; font-size: 10px; font-weight: 800; padding: 1px 7px;
    border-radius: 4px; white-space: nowrap;
}
.sat-kind.k-full  { background: #fee2e2; color: #b91c1c; }
.sat-kind.k-sl    { background: #ede9fe; color: #5b21b6; }
.sat-kind.k-am    { background: #e0f2fe; color: #075985; }
.sat-kind.k-pm    { background: #e0f2fe; color: #075985; }
.sat-kind.k-hours { background: #f1f5f9; color: #334155; }
.sat-away { font-size: 10.5px; font-weight: 700; color: #475569; }

/* The times belong to "some hours off" alone. */
.sat-ask-hours { display: none; align-items: center; gap: 4px; }
.sat-ask[data-kind="hours"] .sat-ask-hours { display: inline-flex; }
.sat-ask-hours input[type="time"] {
    font: inherit; font-size: 12px; padding: 4px 5px; width: 104px;
    border: 1px solid #d1d5db; border-radius: 6px;
}

/* The SRT leading the day, beside the save. */
.pill-srt { background: #f1f5f9; color: #334155; }
.pill-srt.is-none { color: #94a3b8; }
.sat-srt-pick { display: inline-flex; align-items: center; gap: 6px; }
.sat-srt-pick > span {
    font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
    color: #6b7280;
}
.sat-srt-pick select {
    font: inherit; font-size: 12.5px; padding: 5px 8px;
    border: 1px solid #d1d5db; border-radius: 6px; background: #fff;
}

/* ---- the two views of the holiday page ---------------------------------- */
.view-bar {
    display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
    margin-bottom: 12px;
}
.view-tabs {
    display: inline-flex; border: 1px solid #e5e7eb; border-radius: 8px;
    background: #fff; overflow: hidden;
}
.view-tab {
    padding: 6px 12px; font-size: 12.5px; font-weight: 600; color: #4b5563;
    text-decoration: none; border-right: 1px solid #eef2f7; white-space: nowrap;
}
.view-tab:last-child { border-right: 0; }
.view-tab:hover { background: #f8fafc; color: #111827; }
.view-tab.active { background: #2563eb; color: #fff; }

/* ---- by staff: the workbook's own shape ---------------------------------
   Names down the side, a Duty and a Compensation column per date, one table
   per centre. Wide by nature - fourteen dates is twenty-eight columns - so it
   scrolls sideways inside its card with the name column pinned. */
.byst { margin-bottom: 16px; }
.byst-head {
    display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
    padding-bottom: 10px; margin-bottom: 10px; border-bottom: 1px solid #e5e7eb;
}
.byst-head h2 { font-size: 15px; }
.byst-scroll { overflow-x: auto; }

.byst-grid { border-collapse: separate; border-spacing: 0; font-size: 12px; }
.byst-grid th, .byst-grid td {
    border-bottom: 1px solid #f1f5f9; border-right: 1px solid #f1f5f9;
    padding: 4px 8px; white-space: nowrap;
}
.byst-grid thead th {
    background: #f8fafc; text-align: center; font-size: 11px; color: #374151;
    border-bottom: 1px solid #e5e7eb; position: sticky; top: 0; z-index: 3;
}
.byst-grid thead th.sub {
    font-size: 10px; text-transform: uppercase; letter-spacing: .04em;
    color: #6b7280; font-weight: 600; top: 26px;
}
.byst-grid thead th.sub-comp { border-right: 1px solid #e5e7eb; }

/* The date heading carries its own kind, the same red and blue the cards and
   the jump bar use, and a purple ring when it is inside this cycle. */
.byst-grid thead th.kind-public-holiday { background: #fee2e2; color: #991b1b; }
.byst-grid thead th.kind-special-sat    { background: #dbeafe; color: #1e40af; }
.byst-grid thead th.in-cycle { box-shadow: inset 0 -3px 0 #7c3aed; }

/* The name column stays put while the dates scroll past it. */
.byst-grid .name-col {
    position: sticky; left: 0; z-index: 2; background: #fff;
    text-align: left; font-weight: 600; min-width: 200px;
    border-right: 1px solid #e5e7eb;
}
.byst-grid thead .name-col { z-index: 4; background: #f8fafc; }
.byst-grid tbody tr:hover .name-col { background: #f8fafc; }
.byst-grid tbody tr:hover td { background: #f8fafc; }

.byst-grid td.duty { text-align: center; }
.byst-grid td.duty.has-val { background: #f5f3ff; }
.byst-grid td.comp {
    text-align: center; color: #4b5563; border-right: 1px solid #e5e7eb;
}
.byst-grid tfoot th {
    background: #f8fafc; font-size: 11px; color: #6b7280; text-align: center;
    border-top: 1px solid #e5e7eb;
}
.byst-grid tfoot th.is-none { color: #b91c1c; background: #fef2f2; }
.byst-grid tfoot .name-col { text-align: left; }

/* ---- editing one centre's sheet ----------------------------------------
   The Edit button sits top right of the card, level with the centre it edits,
   and only one centre is ever in this state - three tables of pickers at once
   is a page nobody can read. */
.byst-head .spacer { flex: 1 1 auto; }
.byst-head .btn { align-self: center; }
.byst-head .byst-note { align-self: center; }
/* The card rings itself instead of explaining itself: a picker in every cell
   says what to do with it. */
.byst.is-editing { box-shadow: 0 0 0 2px #c4b5fd, 0 1px 3px rgba(15, 23, 42, .06); }

/* The cells are the cards' own controls, shrunk to sheet size: twenty-eight
   columns of them have to stay readable, so they lose the chip padding and the
   date box drops to the narrowest that still shows a full date. */
.byst-grid td.duty.is-edit, .byst-grid td.comp.is-edit { padding: 2px 4px; }
.byst-grid .byst-pick {
    min-width: 92px; max-width: 116px; padding: 2px 4px; font-size: 11px;
    border-radius: 5px;
}
.byst-grid .byst-comp {
    width: 118px; padding: 2px 4px; font-size: 11px; border-radius: 5px;
}
.byst-grid td.comp.is-edit .al-dot { margin-left: 3px; }

/* On paper there is nothing to scroll inside: let the table run to whatever
   width it needs and print the colours the screen shows. */
@media print {
    .byst-scroll { overflow: visible; }
    .byst-grid { font-size: 10px; }
    .byst-grid .name-col { position: static; }
    .byst-grid thead th { position: static; }
    .byst-grid thead th.kind-public-holiday,
    .byst-grid thead th.kind-special-sat,
    .byst-grid td.duty.has-val {
        -webkit-print-color-adjust: exact; print-color-adjust: exact;
    }
}

/* ---- the staff list ----------------------------------------------------- */
.staff-card { margin-bottom: 14px; }
.staff-table td { vertical-align: middle; }
.staff-table input[type="text"], .staff-table select {
    font: inherit; font-size: 13px; padding: 4px 7px;
    border: 1px solid #d1d5db; border-radius: 6px; background: #fff;
}
.staff-table input[type="text"] { width: 100%; min-width: 200px; }
.staff-table td:first-child { width: 34%; }
/* An override left empty falls back to the AL name list, and the placeholder
   shows what that is - so these two read as "as the list has it" until somebody
   types over them. */
.staff-table .staff-nick { min-width: 110px; }
.staff-table .staff-abbr { min-width: 72px; text-transform: uppercase; }
.staff-table .staff-nick::placeholder,
.staff-table .staff-abbr::placeholder { color: #94a3b8; font-style: italic; }
/* The id is the thing that never changes, so it is shown but quiet: it is read
   when something has to be looked up in a file, not when reading the list. */
.staff-id { font-family: ui-monospace, Menlo, monospace; font-size: 10px; color: #9ca3af; }
.staff-actions {
    display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
    position: sticky; bottom: 12px;
}
.staff-add {
    display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap;
}
.staff-add label { display: flex; flex-direction: column; gap: 4px; }
.staff-add label > span {
    font-size: 11px; font-weight: 700; text-transform: uppercase;
    letter-spacing: .05em; color: #6b7280;
}
.staff-add input[type="text"], .staff-add select {
    font: inherit; font-size: 13px; padding: 5px 8px;
    border: 1px solid #d1d5db; border-radius: 6px; background: #fff;
}
.staff-add input[type="text"] { min-width: 260px; }

/* ---- legend ------------------------------------------------------------- */
.leave-legend { margin-top: 12px; font-size: 12px; }
.leave-legend .lg {
    display: inline-block; min-width: 30px; text-align: center;
    padding: 1px 6px; border-radius: 4px; font-weight: 700; font-size: 10px;
    border: 1px solid #e5e7eb; margin-right: 3px;
}
/* Some marks are a person's abbreviation, which is theirs and not a code to
   look up - those swatches show the colour alone. */
.leave-legend .lg:empty { min-height: 13px; vertical-align: -2px; }

/* ---- mark dialog -------------------------------------------------------- */
.mark-opts { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; padding: 4px; }
.mark-opt {
    display: flex; align-items: center; gap: 8px; text-align: left;
    padding: 8px 10px; border: 1px solid #e5e7eb; background: #fff;
    border-radius: 6px; font: inherit; font-size: 13px; cursor: pointer;
}
.mark-opt:hover { background: #eef2f7; }
.mark-opt[aria-pressed="true"] { border-color: #2563eb; background: #eff6ff; }
.mark-opt b { font-size: 11px; min-width: 30px; }
.mark-relief {
    display: block; padding: 10px 8px 4px; font-size: 12px; font-weight: 600;
    color: #374151;
}
.mark-relief input {
    display: block; width: 100%; margin-top: 4px; padding: 8px 10px;
    font: inherit; font-size: 14px; border: 1px solid #d1d5db; border-radius: 6px;
}
.mark-relief input:focus { outline: 2px solid #2563eb; outline-offset: -1px; }
#mark-dialog .actions { padding: 8px; }


/* ---- reliever picker ---------------------------------------------------- */
#mark-dialog { max-width: 520px; }

/* Hold the picker to this centre. Relievers are normally drawn from all three,
   so this is opt-in, and the tick is remembered per centre. */
.rl-filter {
    display: flex; align-items: flex-start; gap: 8px;
    margin-top: 8px; font-size: 12px; color: #4b5563; cursor: pointer;
    line-height: 1.35;
}
.rl-filter input { margin-top: 2px; flex: 0 0 auto; }
.rl-filter:hover { color: #111827; }

.rl-hidden-note {
    margin: 10px 4px 2px; padding: 7px 10px; border-radius: 6px;
    background: #f8fafc; border: 1px dashed #d1d5db;
    font-size: 11px; color: #6b7280; text-align: center;
}

/* group header, tinted by the centre it belongs to */
.rl-group {
    display: flex; align-items: center; gap: 8px;
    margin: 12px 4px 6px; padding: 6px 10px; border-radius: 6px;
    background: #f8fafc; border-left: 4px solid #94a3b8;
}
.rl-group:first-child { margin-top: 4px; }
.rl-group[data-centre="hv"]  { background: #eff6ff; border-left-color: #2563eb; }
.rl-group[data-centre="pt"]  { background: #f0f9ff; border-left-color: #0ea5e9; }
.rl-group[data-centre="akn"] { background: #fff7ed; border-left-color: #ea580c; }
.rl-centre { font-size: 11px; font-weight: 800; letter-spacing: .06em; color: #1f2937; }
.rl-role {
    font-size: 11px; font-weight: 600; text-transform: uppercase;
    letter-spacing: .05em; color: #6b7280;
}
.rl-count {
    margin-left: auto; font-size: 10px; font-weight: 700; color: #6b7280;
    background: #fff; border: 1px solid #e5e7eb; border-radius: 999px;
    min-width: 18px; text-align: center; padding: 0 6px;
}

.move-opt.reliever {
    gap: 10px; align-items: center; padding: 7px 10px; margin-bottom: 2px;
    border: 1px solid transparent;
}
.move-opt.reliever:hover { background: #eef2f7; border-color: #dbe3ec; }
.move-opt.reliever.is-current { background: #ecfdf5; border-color: #6ee7b7; }

/* Already covering another station that day. Still pickable - a short day can
   need it - but flagged amber, and the pick asks before it goes through. */
.move-opt.reliever.is-busy { background: #fffbeb; border-color: #fde68a; }
.move-opt.reliever.is-busy:hover { background: #fef3c7; border-color: #fcd34d; }
.move-opt.reliever.is-busy .rl-from { color: #b45309; font-weight: 600; }

/* ---- reaching outside relieving and runner ------------------------------ */
/* Hidden until the button below the search is pressed, because pulling
   somebody off their own station is the exception, not the everyday pick. */
.rl-others-btn {
    margin-top: 8px; width: 100%; justify-content: center; gap: 8px;
}
.rl-others-btn[aria-pressed="true"] {
    background: #eff6ff; border-color: #bfdbfe; color: #1e40af;
}
.rl-others-hint { font-size: 11px; color: #6b7280; font-weight: 400; }
.rl-others-btn[aria-pressed="true"] .rl-others-hint { color: #3b82f6; }

.rl-others-head {
    display: flex; flex-direction: column; gap: 2px;
    margin: 16px 4px 2px; padding: 8px 10px; border-radius: 6px;
    background: #f8fafc; border: 1px dashed #cbd5e1;
}
.rl-others-head b { font-size: 12px; color: #1f2937; }
.rl-others-head span { font-size: 11px; color: #6b7280; line-height: 1.35; }

/* An "other" is on the list by exception, so the row stays quieter than a
   reliever's: no filled abbreviation badge, and the station in plain grey. */
.rl-group.is-other { background: #f8fafc; border-left-color: #cbd5e1; }
.move-opt.reliever.is-other .rl-abbr {
    background: #fff; color: #64748b; border-color: #e2e8f0;
}

/* abbreviation badge, carrying the centre colour */
.rl-abbr {
    flex: 0 0 auto; min-width: 46px; text-align: center;
    font-size: 12px; font-weight: 800; letter-spacing: .04em;
    padding: 5px 7px; border-radius: 6px;
    background: #f1f5f9; color: #334155; border: 1px solid #e2e8f0;
}
.reliever[data-centre="hv"]  .rl-abbr { background: #dbeafe; color: #1e3a8a; border-color: #bfdbfe; }
.reliever[data-centre="pt"]  .rl-abbr { background: #e0f2fe; color: #075985; border-color: #bae6fd; }
.reliever[data-centre="akn"] .rl-abbr { background: #ffedd5; color: #9a3412; border-color: #fed7aa; }

.rl-main { min-width: 0; flex: 1; display: block; }
.rl-name {
    display: block; font-size: 13px; font-weight: 600; color: #111827;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rl-meta { display: flex; align-items: center; gap: 5px; margin-top: 2px; flex-wrap: wrap; }
.rl-meta .rank { font-size: 9px; padding: 0 5px; }
.rl-from { font-size: 10px; color: #9ca3af; }

/* sex, read from the courtesy title the roster already carries */
.rl-sex {
    font-size: 9px; font-weight: 800; line-height: 1.6;
    min-width: 15px; text-align: center; border-radius: 3px; padding: 0 4px;
    cursor: help;
}
.rl-sex[data-sex="M"] { background: #dbeafe; color: #1d4ed8; }
.rl-sex[data-sex="F"] { background: #fce7f3; color: #be185d; }

.rl-tick { flex: 0 0 auto; width: 16px; text-align: center; color: #059669; font-weight: 800; }
.mark-actions {
    padding: 10px 12px; border-top: 1px solid #e5e7eb; background: #fafafa;
    border-radius: 0 0 10px 10px;
}

/* "No relief" is the destructive option, so it reads red and reacts on press */
#mark-clear {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 8px 15px; border-radius: 8px; font-weight: 600;
    background: #fef2f2; color: #b91c1c; border: 1px solid #fecaca;
    box-shadow: 0 1px 2px rgba(15, 23, 42, .04);
    transition: background .15s ease, border-color .15s ease, color .15s ease,
                box-shadow .15s ease, transform .07s ease;
}
#mark-clear .x { font-size: 15px; font-weight: 800; line-height: 1; }

#mark-clear:hover {
    background: #ef4444; color: #fff; border-color: #ef4444;
    box-shadow: 0 4px 12px rgba(239, 68, 68, .30);
    transform: translateY(-1px);
    filter: none;                 /* the global .btn:hover dim would muddy this */
}
#mark-clear:active {
    background: #b91c1c; border-color: #b91c1c; color: #fff;
    transform: translateY(1px) scale(.98);
    box-shadow: inset 0 2px 5px rgba(0, 0, 0, .22);
}
#mark-clear:focus-visible { outline: 2px solid #ef4444; outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
    #mark-clear { transition: background .15s ease, color .15s ease; }
    #mark-clear:hover, #mark-clear:active { transform: none; }
}

/* ---- print -------------------------------------------------------------- */
@media print {
    .grid-scroll { max-height: none; overflow: visible; border: none; }
    .leave-grid { font-size: 8px; }
    .leave-grid .name-col { width: 210px; min-width: 210px; }
    @page { size: A3 landscape; margin: 8mm; }
}

/* ---- undo & versions ----------------------------------------------------
   Three things stacked: the one button that matters, the steps behind it, and
   the versions kept by name. */
.hist-row {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    margin-bottom: 10px;
}
.hist-save input[type="text"] {
    font: inherit; font-size: 13px; padding: 6px 9px; flex: 1 1 320px;
    border: 1px solid #d1d5db; border-radius: 6px;
}
.hist-steps {
    margin: 0 0 12px; padding: 8px 10px 8px 26px; list-style: decimal;
    background: #f8fafc; border: 1px solid #eef2f7; border-radius: 8px;
    font-size: 12px; color: #4b5563;
}
.hist-steps li { padding: 2px 0; }
.hist-when { font-weight: 700; color: #111827; margin-right: 6px; }
.hist-next {
    display: inline-block; margin-left: 6px; padding: 0 7px; border-radius: 999px;
    background: #dbeafe; color: #1e40af; font-size: 10px; font-weight: 800;
    text-transform: uppercase; letter-spacing: .04em;
}
.hist-table { margin-top: 4px; }
.hist-table td { vertical-align: middle; }
.hist-actions { white-space: nowrap; text-align: right; }
/* A version nobody named: kept because Save was pressed. */
.hist-auto {
    display: inline-block; margin-left: 6px; padding: 0 6px; border-radius: 999px;
    background: #f1f5f9; color: #64748b; font-size: 9px; font-weight: 800;
    text-transform: uppercase; letter-spacing: .04em; vertical-align: 1px;
}

/* ---- names the board does not know -------------------------------------
   A short table, so a name and how often it appears read as a pair. Narrow on
   purpose: it is a list to act on, not a grid to study. */
.unmatched-table { margin-bottom: 10px; }
.unmatched-table td { vertical-align: top; }
.unmatched-table .un-n { text-align: right; width: 60px; white-space: nowrap; }
.unmatched-how { max-width: 700px; line-height: 1.5; }

/* Each day the name was found, as a chip: date, weekday, and the centre column
   it sat in - which together are an address in the AL calendar rather than a
   number to go hunting with. They wrap, because a name mistyped all cycle is a
   long line and a scrollbar would hide the end of it. */
.un-days { display: flex; flex-wrap: wrap; gap: 4px; }
.un-day {
    display: inline-flex; align-items: baseline; gap: 4px;
    padding: 1px 7px; border-radius: 999px;
    background: #fff7ed; border: 1px solid #fed7aa; color: #9a3412;
    font-size: 11px; font-weight: 700; white-space: nowrap;
}
.un-dow { color: #c2703c; font-weight: 600; }
.un-centre {
    padding: 0 5px; border-radius: 999px;
    background: #fed7aa; color: #7c2d12; font-size: 10px; letter-spacing: .02em;
}

/* ---- when the AL data arrived ------------------------------------------
   In the page head, so the age of half this grid's cells is answered before
   anyone reads a row. Everybody's question, so it is not in the admin's import
   panel, and it prints - a roster on paper is worth dating. */
.al-stamp {
    display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
    margin-top: 5px; color: #4b5563;
}
.al-stamp b { color: #111827; font-weight: 700; }
.al-stamp-label { color: #6b7280; }
/* Green: the calendar sent it here itself, which is the ordinary way. Amber:
   somebody uploaded a file, so it is only as current as they were. Hollow:
   nothing has arrived at all. */
.al-stamp-dot {
    width: 8px; height: 8px; border-radius: 999px; flex: none;
    box-shadow: 0 0 0 2px rgba(255, 255, 255, .9);
}
.al-stamp-dot.is-live   { background: #10b981; }
.al-stamp-dot.is-manual { background: #f59e0b; }
.al-stamp-dot.is-none   { background: #fff; border: 1.5px solid #d1d5db; }
.al-stamp-ago {
    padding: 0 7px; border-radius: 999px; background: #f1f5f9; color: #475569;
    font-size: 11px; font-weight: 700;
}
/* A day without a delivery is worth a second look, not a warning - it may be a
   quiet week rather than a broken feed. */
.al-stamp-ago.is-stale { background: #fef3c7; color: #92400e; }

/* Which station's past is being shown. Only the planning roster needs these -
   it carries four stations at once, and each keeps its own history. The machine
   sheet is one station already, so it shows no tabs. */
.hist-tabs {
    display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
    margin: 0 0 12px; padding-bottom: 10px; border-bottom: 1px solid #eef2f7;
}
.hist-tab {
    padding: 3px 11px; border: 1px solid #d1d5db; border-radius: 999px;
    background: #fff; color: #374151; font-size: 12px; font-weight: 600;
    text-decoration: none;
}
.hist-tab:hover { border-color: #9ca3af; }
.hist-tab.active {
    background: #2563eb; border-color: #2563eb; color: #fff;
}

/* ---- the AL check on a compensation day ---------------------------------
   A holiday worked is paid back with a day off, and that day only exists once
   it is in the AL calendar. Green says the calendar has it; grey says it does
   not, which is the thing this page is being read for. Hollow says the feed
   has not been imported since the check existed - a question not yet asked
   must not look like an answer of "no". */
.al-dot {
    display: inline-block; width: 7px; height: 7px; border-radius: 50%;
    margin-left: 6px; vertical-align: 1px; cursor: help;
}
.al-dot.is-on      { background: #10b981; }
.al-dot.is-off     { background: #cbd5e1; }
.al-dot.is-unknown { background: transparent; box-shadow: inset 0 0 0 1.5px #cbd5e1; }
.byst-grid td.comp .al-dot { margin-left: 4px; }

/* Bare arrows on the staff list: they nudge a row into place, so they carry no
   button chrome - just the arrow, quiet until the row is hovered. Greyed at the
   ends of a rank, where there is nowhere further to go. */
.staff-move { white-space: nowrap; width: 1%; padding-right: 0; }
.move-btn {
    background: none; border: 0; padding: 2px 3px; cursor: pointer;
    color: #cbd5e1; line-height: 0; border-radius: 4px;
}
.staff-table tr:hover .move-btn { color: #94a3b8; }
.move-btn:hover:not(:disabled) { color: #2563eb; background: #eff6ff; }
.move-btn:disabled { color: #f1f5f9; cursor: default; }

/* The row that has just moved, named for a moment so the eye can find it
   without the page having jumped anywhere. */
.staff-table tr.just-moved > td { animation: staff-moved 1.4s ease-out; }
@keyframes staff-moved {
    0%   { background: #dbeafe; }
    100% { background: transparent; }
}
/* An anchor landing (no script, or a fresh session) should not tuck the row
   under the top edge of the window. */
.staff-table tr[id] { scroll-margin-top: 90px; }
.move-btn:focus-visible { outline: 2px solid #2563eb; outline-offset: -2px; color: #2563eb; }

/* ---- Export PDF ---------------------------------------------------------
   The button, and the document it builds.

   There is no PDF library on the host and there does not need to be: the one
   thing asked of this export is that it look like the page, and the page is
   already rendered. So the export takes the three centres' own grids and opens
   them in a tab of their own - the browser's Save as PDF does the rest, and
   what comes out cannot drift from what the screen shows, because it *is* what
   the screen shows.

   Sits in .page-head .right, at the top right of the leave list. */
.pdf-btn {
    display: inline-flex; align-items: center; gap: 10px;
    padding: 5px 20px 5px 5px;
    border: 0; border-radius: 999px;
    background: #e03c3c; color: #fff;
    font: inherit; font-size: 13px; font-weight: 800;
    letter-spacing: .08em; text-transform: uppercase;
    cursor: pointer; white-space: nowrap;
    box-shadow: 0 2px 8px rgba(190, 30, 30, .32), 0 1px 2px rgba(15, 23, 42, .10);
    transition: background .14s ease, box-shadow .14s ease, transform .14s ease;
}
.pdf-btn:hover  { background: #c92f2f; box-shadow: 0 4px 12px rgba(190,30,30,.38); }
.pdf-btn:active { transform: translateY(1px); box-shadow: 0 1px 4px rgba(190,30,30,.30); }
.pdf-btn:focus-visible { outline: 3px solid #1d4ed8; outline-offset: 2px; }
.pdf-btn[disabled] { opacity: .65; cursor: progress; }

/* The white disc the icon sits in, as on the department's own button. */
.pdf-btn-ico {
    flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%;
    background: #fff; display: inline-flex; align-items: center; justify-content: center;
}
.pdf-btn-ico svg { width: 17px; height: 17px; color: #e03c3c; display: block; }

@media (prefers-reduced-motion: reduce) {
    .pdf-btn, .pdf-btn:active { transition: none; transform: none; }
}

/* ---- the exported document ----------------------------------------------
   A tab of its own. It links this stylesheet, so the grids arrive already
   wearing every colour the screen gives them.

   The small type is set for screen as well as print, and deliberately: the tab
   is a preview of the PDF, so it should read as one - and the page is measured
   before it is saved, which can only be done if what is measured is the size it
   will come out at. */
.pdf-doc { background: #fff; margin: 0; padding: 62px 16px 24px; }
.pdf-doc .pdf-head { margin-bottom: 14px; }
.pdf-doc .pdf-head h1 { font-size: 18px; margin: 0 0 2px; }
.pdf-doc .pdf-sub { font-size: 12px; color: #6b7280; }
.pdf-doc .pdf-centre { margin-bottom: 22px; }
.pdf-doc .pdf-centre h2 {
    font-size: 13px; margin: 0 0 6px; color: #1e40af;
    text-transform: uppercase; letter-spacing: .06em;
}
/* Wide is shown at the size the screen shows it, because nothing is making it
   smaller: the page is cut to the grid rather than the grid to the page. A3
   sheets are the other way round - the paper is fixed, so the type gives. */
.pdf-doc .leave-grid { font-size: 8px; }
.pdf-doc.mode-wide .leave-grid { font-size: 11px; }
.pdf-doc .leave-grid .name-col { width: 210px; min-width: 210px; }

/* Nothing sticks on a saved page. The name column and the three header rows
   are pinned on screen so they survive scrolling; in the export there is one
   long page instead, and left pinned they would sit on top of the grid. */
.pdf-doc .leave-grid thead th,
.pdf-doc .leave-grid .name-col { position: static; }

/* Chrome drops every background colour when it prints unless told otherwise,
   and on this grid the colour *is* the information - yellow Saturdays, red
   holidays, green cover, grey AL. */
.pdf-doc .leave-grid th, .pdf-doc .leave-grid td, .pdf-doc .leave-grid span {
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
}

/* Only the A3 layout uses this; it is what scales a sheet that came out a
   little wider than the paper. The wide layout never needs it - the page is cut
   to the grid rather than the grid to the page. */
.pdf-doc .pdf-zoom { transform-origin: top left; }

/* ---- the bar across the top of the export -------------------------------
   Two ways to lay the same grids out, and the choice belongs to whoever is
   looking at them rather than to this file:

     **Wide** keeps each centre whole - all nine weeks joined, scrolled
     sideways - and cuts the page to fit it. That is a document to read on a
     screen; sent to a printer it would be shrunk to nothing.

     **A3 sheets** cuts each centre into sheets a few weeks wide, on week
     boundaries, every sheet keeping the names and the Saturday count. That is
     the one that survives being printed.

   Fixed rather than merely placed, so it stays put while nine weeks scroll
   past underneath. It never prints - .no-print sees to that. */
.pdf-bar {
    position: fixed; top: 0; left: 0; right: 0; z-index: 10;
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    padding: 8px 16px; background: #fff;
    border-bottom: 1px solid #e5e7eb;
    box-shadow: 0 1px 3px rgba(15, 23, 42, .06);
    font-size: 12px;
}
.pdf-bar-label { font-weight: 700; color: #374151; }
.pdf-bar-mode {
    font: inherit; padding: 5px 11px; border-radius: 999px; cursor: pointer;
    border: 1px solid #d1d5db; background: #fff; color: #374151;
}
.pdf-bar-mode:hover { border-color: #9ca3af; }
.pdf-bar-mode.is-on {
    background: #1e40af; border-color: #1e40af; color: #fff; font-weight: 700;
}
.pdf-bar-note { color: #6b7280; flex: 1 1 260px; min-width: 0; }
.pdf-bar .pdf-btn { margin-left: auto; }

@media print {
    .pdf-doc { padding: 0; }
    /* A centre to a page: two grids on one would be unreadable, and each is a
       thing somebody reads on its own. */
    .pdf-doc .pdf-centre { margin-bottom: 0; }
    .pdf-doc .pdf-centre + .pdf-centre { break-before: page; }
    .pdf-doc .pdf-head { break-after: avoid; }
    /* The page size itself is written in by the export, once it has measured
       the grids - A3 landscape for sheets, or one page cut to the grid. */
}

/* ---- instant tooltips ---------------------------------------------------
   The browser's own tooltip waits the best part of a second, which on a grid
   read one cell at a time is the difference between glancing along a row and
   waiting on every square in it. This one appears on the mouseover itself.

   Styled as the browser's is - dark, small, quiet - because it is standing in
   for it and should not announce that it is doing so. */
.tip {
    position: fixed; z-index: 9000;
    max-width: 320px;
    padding: 6px 10px; border-radius: 6px;
    background: #374151; color: #fff;
    font-size: 12px; line-height: 1.4;
    box-shadow: 0 4px 12px rgba(15, 23, 42, .22), 0 1px 3px rgba(15, 23, 42, .18);
    pointer-events: none;          /* never in the way of the cell it describes */
    opacity: 0; visibility: hidden;
    /* Wraps rather than runs off: the grid sets nowrap on everything in it, and
       a tooltip is prose. */
    white-space: normal; text-align: left; font-weight: 400;
}
.tip.is-on { opacity: 1; visibility: visible; }

/* ---- "new AL data" ------------------------------------------------------
   The page asks once a minute whether the calendar has sent anything. When it
   has, the stamp says so and offers the reload - it does not take it, because
   an admin may have an afternoon of unsaved cells on the page. */
.al-stamp-new {
    margin-left: 6px; padding: 1px 7px; border-radius: 999px;
    background: #dcfce7; color: #166534; font-weight: 700; font-size: 11px;
}
.al-stamp-reload {
    margin-left: 4px; padding: 1px 9px; border-radius: 999px;
    border: 1px solid #16a34a; background: #fff; color: #166534;
    font: inherit; font-size: 11px; font-weight: 700; cursor: pointer;
}
.al-stamp-reload:hover { background: #16a34a; color: #fff; }
.al-stamp-reload:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 1px; }

/* The reload reminder's own dot.

   The stamp opens with a dot that says where the data came from - green for the
   calendar's own delivery, amber for a file somebody uploaded. That one is a
   state. This one is a request: new AL data has landed, the grid below it is
   out of date, and only a reload will fix that. So it is amber - the colour the
   rest of the site uses for "worth your attention" - and it pulses, because it
   has to be noticed on a page that has been open all afternoon. */
.al-stamp-new {
    display: inline-flex; align-items: center; gap: 5px;
}
.al-stamp-newdot {
    width: 8px; height: 8px; border-radius: 999px; flex: none;
    background: #f59e0b;
    box-shadow: 0 0 0 0 rgba(245, 158, 11, .65);
    animation: al-stamp-ping 1.8s ease-out infinite;
}
@keyframes al-stamp-ping {
    0%   { box-shadow: 0 0 0 0   rgba(245, 158, 11, .65); }
    70%  { box-shadow: 0 0 0 7px rgba(245, 158, 11, 0); }
    100% { box-shadow: 0 0 0 0   rgba(245, 158, 11, 0); }
}
/* Somebody who has asked not to be moved still gets the dot and its colour -
   it is the pulsing they turned off, not the reminder. */
@media (prefers-reduced-motion: reduce) {
    .al-stamp-newdot { animation: none; box-shadow: 0 0 0 3px rgba(245, 158, 11, .28); }
}

/* ---- the foot of the grid ----------------------------------------------
   How each ordinary Saturday is covered, and which weekdays are being taken
   off in lieu of it - each under the column it is about, because the question
   is always asked of a particular week.

   It has to sit inside the table to line up with the dates, but it is not more
   roster: nothing here is a person and nothing is editable. So it is drawn as
   its own strip rather than as more rows - lifted off the grid by a rule and a
   band of its own, with the numbers as labelled chips instead of bare cells. */
.leave-grid .grid-foot th,
.leave-grid .grid-foot td {
    background: #f8fafc;
    border-bottom: none;
    border-right-color: #eef2f7;
}
/* The break between roster and arithmetic. */
.leave-grid .grid-foot .foot-cover th,
.leave-grid .grid-foot .foot-cover td {
    border-top: 2px solid #cbd5e1;
    padding-top: 6px;
}
.leave-grid .grid-foot .name-col { background: #f1f5f9; }
.leave-grid .grid-foot .total-col { background: #f1f5f9; border-left-color: #e5e7eb; }

/* The heading, and the fold. Kept a real button so it takes focus and answers
   the keyboard; the caret turns rather than the label changing. */
.foot-toggle {
    display: inline-flex; align-items: center; gap: 6px;
    border: 0; background: none; padding: 0;
    font: inherit; font-size: 11px; font-weight: 800; color: #334155;
    letter-spacing: .02em; cursor: pointer;
}
.foot-toggle:hover .foot-title { color: #1d4ed8; }
.foot-toggle:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 2px; border-radius: 4px; }
.foot-caret {
    width: 0; height: 0; flex: none;
    border-left: 5px solid #94a3b8;
    border-top: 4px solid transparent; border-bottom: 4px solid transparent;
    transform: rotate(90deg); transform-origin: 30% 50%;
    transition: transform .15s ease;
}
.grid-foot.is-shut .foot-caret { transform: rotate(0deg); }
.foot-sub {
    font-size: 10px; font-weight: 700; color: #64748b;
    text-transform: uppercase; letter-spacing: .05em;
}

/* The Saturday's own cell: three labelled numbers stacked in the two columns
   the Saturday already has. `display: contents` drops the label and the figure
   straight into the little grid, so they line up as a column of pairs however
   wide the numbers get. */
.leave-grid .foot-cell { padding: 2px 3px 4px; background: #fffbeb !important; }
.sc-mini {
    display: grid; grid-template-columns: auto auto;
    justify-content: center; align-items: center;
    gap: 0 5px; line-height: 1.35;
}
.sc-chip { display: contents; }
.sc-chip i {
    font-style: normal; text-align: right;
    font-size: 9px; font-weight: 700; letter-spacing: .04em;
    text-transform: uppercase; color: #b45309;
}
.sc-chip b {
    text-align: left; font-size: 11px; font-weight: 800; color: #78350f;
    font-variant-numeric: tabular-nums;
}
/* A nought is the thing being looked for - a Saturday with nobody on a station
   - so it is quiet rather than bold. */
.sc-chip.is-none i, .sc-chip.is-none b { color: #cbd5e1; font-weight: 600; }

/* The weekday counts are not the Saturday's own columns, so they do not carry
   its yellow: a small pill under each weekday. */
.leave-grid .foot-wd-cell { padding: 3px 2px 5px; }
.wd-pill {
    display: inline-block; min-width: 19px; padding: 0 5px;
    border-radius: 999px; background: #e2e8f0; color: #334155;
    font-size: 10px; font-weight: 800; line-height: 15px;
    font-variant-numeric: tabular-nums;
}
.wd-pill.is-none { background: transparent; color: #cbd5e1; font-weight: 600; }

/* Days with nothing to count: Sundays, and weeks whose Saturday is a holiday or
   a call-in. Left blank, not zeroed - "nothing is owed this week" is a
   different fact from "nobody chose this day". */
.leave-grid .foot-blank { background: #f8fafc; }

/* Folded: the heading stays, the numbers go. */
.grid-foot.is-shut .sc-mini,
.grid-foot.is-shut .foot-wd { display: none; }
.grid-foot.is-shut .foot-cell { background: #f8fafc !important; }

/* ---- the activity log ---------------------------------------------------
   A reader's table: dense, quiet, and sorted newest first. Nothing in it is
   editable, so nothing in it invites a click. */
.log-filters {
    display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap;
}
.log-filters label { display: flex; flex-direction: column; gap: 2px; }
.log-filters select, .log-filters input[type="search"] {
    font: inherit; font-size: 13px; padding: 5px 8px;
    border: 1px solid #d1d5db; border-radius: 6px; background: #fff;
}
.log-filters .log-search { flex: 1 1 240px; }
.log-filters .log-search input { width: 100%; }

.log-card { padding: 0; overflow: hidden; }
.log-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.log-table thead th {
    position: sticky; top: 0; z-index: 1;
    background: #f1f5f9; color: #334155; text-align: left;
    font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
    padding: 7px 10px; border-bottom: 1px solid #e2e8f0;
}
.log-table td { padding: 6px 10px; border-bottom: 1px solid #f1f5f9; vertical-align: top; }
.log-table tbody tr:hover td { background: #f8fafc; }

.log-when { white-space: nowrap; font-variant-numeric: tabular-nums; }
.log-who  { white-space: nowrap; }
.log-name { margin-left: 5px; font-weight: 600; color: #334155; }
.log-act code {
    font-size: 11px; background: #f1f5f9; color: #475569;
    padding: 1px 5px; border-radius: 4px;
}
.log-what { color: #1f2937; }
.log-ip   { white-space: nowrap; font-size: 11px; }

/* A stripe down the left, by family, so a run of one kind of action reads as a
   run without anybody reading the words. */
.log-table tbody tr { border-left: 3px solid transparent; }
.log-table tr.log-auth     td:first-child { box-shadow: inset 3px 0 0 #94a3b8; }
.log-table tr.log-password td:first-child { box-shadow: inset 3px 0 0 #dc2626; }
.log-table tr.log-leave    td:first-child { box-shadow: inset 3px 0 0 #2563eb; }
.log-table tr.log-swap     td:first-child,
.log-table tr.log-station  td:first-child { box-shadow: inset 3px 0 0 #16a34a; }
.log-table tr.log-phduty   td:first-child,
.log-table tr.log-satduty  td:first-child { box-shadow: inset 3px 0 0 #d97706; }
.log-table tr.log-planning td:first-child,
.log-table tr.log-machine  td:first-child { box-shadow: inset 3px 0 0 #7c3aed; }
.log-table tr.log-board    td:first-child,
.log-table tr.log-staff    td:first-child { box-shadow: inset 3px 0 0 #0891b2; }

/* ---- phones -------------------------------------------------------------
   The grid is nine weeks wide and freezes two columns: the name on the left
   and the Saturday tally on the right. Together they are 402px. On a desktop
   that is a third of the window and nobody notices; on a 390px phone it is
   wider than the space they sit in - 306px once the page margins are paid -
   so every date cell lands underneath them and the sideways scroll appears to
   do nothing. It is in fact scrolling: the first 96px of travel only uncovers
   what the frozen columns overlap, so the finger never reaches a date.

   Three things give the width back. The page margins go, because on a phone
   the grid is the page. The tally shrinks to the digit it exists to show. And
   the name column carries the abbreviation instead of the name - the
   shorthand the department already uses when marking relief, already in the
   markup, and 62px instead of 340px. That is the difference between no
   visible dates at all and most of a week.
   ========================================================================= */
/* `screen` matters: A4 at 96dpi is about 794px wide, so a bare
   max-width query would match the printer too and hand the PDF the
   phone layout. */
@media screen and (max-width: 820px) {

    /* 84px of a 390px screen went on margins - a fifth of the phone, spent on
       white space beside a table that has none to spare. Narrowed only on the
       pages that carry a frozen-column grid; every other page keeps the
       breathing room, which it can afford. */
    body:has(.grid-scroll),
    body:has(.byst-scroll) {
        padding-left:  calc(8px + env(safe-area-inset-left, 0px));
        padding-right: calc(8px + env(safe-area-inset-right, 0px));
    }
    .container.full {
        padding-left:  calc(4px + env(safe-area-inset-left, 0px));
        padding-right: calc(4px + env(safe-area-inset-right, 0px));
    }

    /* A sideways swipe on a scroller that has hit its end is read as "go
       back" by both mobile browsers. Losing the page mid-scroll is worse than
       not scrolling, and this grid is scrolled sideways constantly. */
    .grid-scroll, .byst-scroll { overscroll-behavior-x: contain; }

    /* ---- the leave grid ----
       A width is only a suggestion to a table. One cell of nowrap prose - and
       the group bands carry the longest text on the page - widens the whole
       column regardless, which is what pushed the dates back off the screen
       on the first attempt. **max-width** is what actually holds the column to
       its 64px. */
    .leave-grid .name-col  {
        width: 64px; min-width: 64px; max-width: 64px; padding: 3px 4px;
    }

    /* No `overflow` on this cell, and that is the whole of a bug worth naming.
       The column is frozen with position: sticky, and Safari stops honouring
       that the moment a sticky table cell is also given an overflow - so on an
       iPhone the names scrolled away with the dates while on Android they
       stayed put. max-width is what holds the column to its 64px anyway; the
       clipping belongs to what is inside it, below. */
    .leave-grid .name-col > * { max-width: 100%; }

    /* A group band is empty across the rest of its row, so its title is left
       to spill over that empty space and stays whole and readable - cutting it
       to "PLANN..." would name nothing. Nothing to switch off now that the
       cell itself no longer clips. */
    .leave-grid .row-group th.name-col > * { max-width: none; }
    /* The tally sits at the end of the row and stays there - not pinned.

       Pinning it costs 38px of a 390px screen on every one of nine weeks, to
       hold a number that is read once. Unpinned it is where it always was, the
       last column after week 9, and the swipe past the final week is what
       reaches it. */
    .leave-grid .total-col {
        position: static;
        width: 44px; min-width: 44px;
    }

    /* Both headers span all three header rows, so they have some 60px of
       height to wrap into and do not need the nowrap the date cells live by. */
    .leave-grid thead .name-col,
    .leave-grid thead .total-col {
        white-space: normal; font-size: 9px; line-height: 1.2;
        letter-spacing: 0;
    }

    /* The staff row: what the person is called, and nothing else. The rank
       and the on-loan tag are what a narrow column cannot afford; both are
       still on the desktop grid and on the printed sheet.

       Three steps down, because not every row has every label: the nickname
       the department actually uses, else the abbreviation, else the name cut
       to fit. An empty column identifying nobody is the one outcome worth
       ruling out. */
    .leave-grid .name-staff .n,
    .leave-grid .name-staff .rank,
    .leave-grid .name-staff .note-tag { display: none; }

    .leave-grid .name-staff .nick {
        display: block; min-width: 0; max-width: 100%; white-space: nowrap;
        /* 10px is the size at which the longest nickname on the board,
           "Stephanie", still fits a column narrow enough to leave the
           whole of a week beside it. */
        font-size: 10px; font-weight: 700; letter-spacing: 0; color: #111827;
        overflow: hidden; text-overflow: ellipsis;
    }
    .leave-grid .name-staff:has(.nick) .abbr { display: none; }

    /* Except on a relieving or runner row, where the abbreviation comes back
       beside the nickname: "Jordan (LYY1)".

       It is the code you type into a cell when you name that person as somebody
       else's cover, and their own row is the one place on the grid it can be
       looked up. On a wide screen it is a chip beside the name; the phone was
       dropping it, which left the nickname you say out loud and no sign of the
       letters you actually have to write.

       The cell is a flex row, so this is done in flex terms rather than by
       going inline - `display: inline` on a flex item is blockified back to
       block, and the nickname was ending up a shrunk column reading "J...".
       Wrapping instead: both keep their natural width, side by side where the
       64px allows and stacked where it does not. Nothing widens. */
    .leave-grid tr[data-reliever] .name-staff {
        flex-wrap: wrap; gap: 0 4px; align-items: baseline;
    }
    .leave-grid tr[data-reliever] .name-staff .nick {
        flex: 0 0 auto;                 /* its own width, never shrunk to dots */
        overflow: visible; text-overflow: clip;
    }
    .leave-grid tr[data-reliever] .name-staff:has(.nick) .abbr {
        display: block; flex: 0 0 auto;
        font-size: 9px; font-weight: 700; letter-spacing: 0;
        color: #4b5563; background: none; border: 0; padding: 0;
    }
    .leave-grid tr[data-reliever] .name-staff:has(.nick) .abbr::before { content: '('; }
    .leave-grid tr[data-reliever] .name-staff:has(.nick) .abbr::after  { content: ')'; }

    .leave-grid .name-staff:not(:has(.nick)) .abbr {
        font-size: 12px; font-weight: 700; letter-spacing: .02em;
        color: #111827; background: none; border: 0; padding: 0;
    }
    .leave-grid .name-staff:not(:has(.nick)):not(:has(.abbr)) .n {
        display: block; min-width: 0; max-width: 100%; font-size: 10px;
        overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }

    /* The group bands and the two footer rows have prose where the codes go.
       A band is empty across the rest of its row, so its title is left to
       spill over it and stays readable at full size; the footer rows have
       figures underneath, so theirs wrap inside the column instead. */
    .leave-grid .grid-foot .name-col {
        white-space: normal; font-size: 8px; line-height: 1.25;
    }
    /* "Saturday" is one pixel too wide to wrap cleanly at 9px and was being
       cut mid-word; 8px is the size at which both labels fit whole. */
    .grid-foot .foot-toggle, .foot-sub { font-size: 8px; letter-spacing: .02em; }
    .grid-foot .foot-title { white-space: normal; }

    /* ---- the public-holiday board ----
       The same frozen-name-column shape, less badly off at 200px, but 200px
       of 358 is still more than half the screen. No abbreviations in this
       markup, so the name is cut instead; the rank and station stay in the
       cell's own tooltip where they already were. */
    .byst-grid .name-col { min-width: 124px; max-width: 124px; }
    /* The clipping goes on what is inside the cell, never on the cell. An
       overflow on a sticky table cell is what stops Safari freezing it - the
       same bug the leave grid's name column names above - and this column was
       still carrying it, so on an iPhone the names slid away with the dates. */
    .byst-grid .name-col .byst-name {
        display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }
}

/* ---- the leave grid, a week at a time ----------------------------------
   On a phone the grid stops being a thing you scroll and becomes a thing you
   turn: one whole week on the screen, swipe for the next.

   Free horizontal scrolling on a nine-week grid means never quite knowing
   which week you are looking at, and a half-week view where Friday is cut down
   the middle. Paging trades that for the one question the grid is read for -
   what does this week look like - and answers it a week at a time.

   Built and switched on by assets/leave-weeks.js, which adds .is-weekly. A
   phone with no JS keeps the ordinary scroll rather than a grid it cannot move
   at all, and the pager below is hidden until that class arrives.

   Written before the phone block, because both selectors weigh the same and
   the last one wins. */
.week-pager { display: none; }

@media screen and (max-width: 820px) {
    /* A real scroller, and that is load-bearing rather than a preference.

       This was `overflow-x: hidden` once, on the reasoning that only the
       script should move it and a stray drag landing between two weeks is the
       thing being fixed. It cost two bugs on iPhones and neither on Android,
       because WebKit builds no scrolling node for an axis it has been told is
       hidden:

         - `position: sticky; left: 0` on the name column gets no horizontal
           offset from that axis, so the names scrolled away with the dates -
           the frozen column simply left the screen;
         - and the offset of a hidden axis is not a scroll position WebKit
           keeps. Any relayout puts it back to nought, so opening the marking
           dialog by tapping a cell threw the grid back to week 1.

       Auto gives both back. Landing between two weeks is handled where it
       should be - assets/leave-weeks.js settles the scroll onto the nearest
       week once the finger has left - and the smoothness is asked for per
       scroll there rather than declared here, so re-fitting on a turned phone
       can still be instant. */
    .grid-scroll.is-weekly { overflow-x: auto; }

    /* Every day column the same width, worked out by the script so the widest
       week of the cycle - eight columns, where an ordinary Saturday splits into
       its two - fills the screen beside the name column exactly. */
    .is-weekly.grid-scroll .leave-grid thead .row-dow th,
    .is-weekly.grid-scroll .leave-grid tbody td:not(.total-col):not([colspan]),
    .is-weekly.grid-scroll .leave-grid tfoot td:not(.total-col):not([colspan]) {
        width: var(--day-w, 40px);
        min-width: var(--day-w, 40px);
        max-width: var(--day-w, 40px);
    }

    /* The pager: which week you are on, and a way to either side of it. */
    .week-pager {
        display: flex; align-items: center; gap: 8px;
        margin: 0 0 6px; padding: 5px 6px;
        background: #f1f5f9; border-radius: 10px;
    }
    .week-pager-btn {
        flex: 0 0 auto; width: 36px; height: 32px;
        border: 1px solid #d1d5db; border-radius: 8px; background: #fff;
        font-size: 15px; font-weight: 800; color: #334155; line-height: 1;
        cursor: pointer;
    }
    .week-pager-btn:disabled { opacity: .3; cursor: default; }
    .week-pager-now {
        flex: 1 1 auto; min-width: 0; text-align: center;
        font-size: 12px; font-weight: 800; color: #1f2937;
    }
    .week-pager-dates {
        display: block; font-size: 10px; font-weight: 600; color: #64748b;
    }
}

/* The Saturday picker with nothing to pick: a day asked off has no station on
   it, so the dialog says why rather than offering a list that would only be
   refused. Its own class - .sat-note is an input on the Saturday duty page. */
.sat-grid .sat-blocked {
    grid-column: 1 / -1;
    padding: 10px 12px; border-radius: 8px;
    background: #fef2f2; border: 1px solid #fecaca;
    color: #7f1d1d; font-size: 13px; line-height: 1.4;
}

/* The one press that catches the whole page up, beside the legend it belongs
   with rather than on any single card - it is about all of them. */
/* ---- the tools row under the jump bar --------------------------------
   Retrieve on the left, the draft-only switch on the right; the date picker
   for a partial retrieve is a card of its own under the row. */
.sat-tools {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    margin: -6px 0 14px;
}
.sat-tools .sat-inline { margin: 0; }
.sat-tools .spacer { flex: 1 1 auto; }
.sat-tools .btn.is-open,
.sat-tools .btn.is-on { background: #1e40af; border-color: #1e40af; color: #fff; }

.sat-pick { padding: 14px 16px; margin: -6px 0 14px; }
.sat-pick-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.sat-pick-links { font-size: 12px; color: #6b7280; margin-left: auto; white-space: nowrap; }
.sat-pick-links button {
    background: none; border: 0; padding: 0; font: inherit; font-size: 12px;
    color: #2563eb; cursor: pointer;
}
.sat-pick-links button:hover { text-decoration: underline; }
.sat-pick-dates { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 12px; }
.sat-pick-date {
    display: inline-flex; align-items: center; gap: 7px;
    padding: 5px 10px 5px 8px; border-radius: 6px; cursor: pointer;
    border: 1px solid #e5e7eb; background: #fafafa; color: #374151; font-size: 12px;
}
.sat-pick-date:hover { background: #eef2f7; border-color: #9ca3af; }
.sat-pick-date:has(input:checked) { background: #dbeafe; border-color: #60a5fa; color: #1e3a8a; }
.sat-pick-date.kind-special-sat    { border-color: #93c5fd; }
.sat-pick-date.kind-public-holiday { border-color: #fca5a5; }
.sat-pick-day  { font-weight: 700; }
.sat-pick-note { font-size: 10px; text-transform: uppercase; letter-spacing: .05em; color: #9ca3af; }
/* Released: greyed and dashed, the box disabled - the rule made visible. */
.sat-pick-date.is-released {
    cursor: not-allowed; border-style: dashed; color: #9ca3af; background: transparent;
}
.sat-pick-date.is-released:hover { background: transparent; border-color: #e5e7eb; }
.sat-pick-date.is-released .sat-pick-note { color: #10b981; }
.sat-pick-foot { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.sat-pick-foot .muted { margin-right: auto; }

/* The switch: the released cards and their jump buttons step out of the way. */
.sat-only-unreleased .duty-day.is-released,
.sat-only-unreleased .duty-jump-btn.is-released { display: none; }

/* The ACHS data entry, beside the SRT it is saved with. It carries the same
   small uppercase label as the pickers either side of it - they are one row of
   controls and should read as one - and a wider select, because it lists
   everybody on the day rather than the handful of SRTs. */
.sat-achs-pick select { max-width: 250px; }

.sat-achs-note { flex: 1 1 180px; min-width: 0; }
.sat-achs-note input {
    flex: 1 1 auto; min-width: 0;
    font: inherit; font-size: 12.5px; color: #374151;
    padding: 5px 9px; border: 1px solid #d1d5db; border-radius: 6px; background: #fff;
}
.sat-achs-note input::placeholder { color: #9ca3af; }
.sat-achs-note input:focus {
    outline: none; border-color: #2563eb; box-shadow: 0 0 0 3px rgba(37,99,235,.15);
}

/* Who is on the ACHS data entry, at the foot of the day - read by everybody,
   set by the senior in the panel above. A quiet line: it is one fact about the
   day rather than a section of its own. */
.sat-achs-line {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    margin-top: 14px; padding-top: 12px; border-top: 1px dashed #cbd5e1;
    font-size: 13px; color: #374151;
}
.sat-achs-tag {
    font-size: 10px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase;
    color: #3730a3; background: #e0e7ff; border: 1px solid #c7d2fe;
    padding: 2px 8px; border-radius: 999px; white-space: nowrap;
}
.sat-achs-remark { color: #6b7280; }
.sat-achs-remark::before { content: "\201C"; }
.sat-achs-remark::after  { content: "\201D"; }

/* ---- picking a Saturday's machines --------------------------------------
   A tick box per machine rather than a multi-select. Somebody can cover two
   on one Saturday, and a native multi-select both hides that it allows a
   second and cannot show which are on without being read - it also needs a
   held modifier key, which a phone does not have.

   Each row says its state twice over: a dot that fills green, and the name in
   bold. Colour alone would leave it unreadable to anyone who cannot see the
   difference, so the weight carries it as well.
   ========================================================================= */
.sat-machines {
    display: flex; flex-direction: column; gap: 2px;
    margin: 0; padding: 3px; border: 1px solid #d1d5db; border-radius: 7px;
    background: #fff; min-width: 0;
}
/* Nothing picked yet - the same quiet outline the old picker had. Read from
   the boxes themselves rather than a class, so it follows a tick immediately
   instead of waiting for the page to be saved and drawn again. */
.sat-machines:not(:has(input:checked)) { border-style: dashed; border-color: #cbd5e1; }

.sat-machine {
    display: flex; align-items: center; gap: 7px;
    padding: 3px 7px; border-radius: 5px;
    font-size: 12px; line-height: 1.3; color: #6b7280;
    cursor: pointer; white-space: nowrap;
}
.sat-machine:hover { background: #f8fafc; color: #374151; }

/* The box itself is off the screen, not gone: it is still what is clicked,
   what a keyboard reaches and what the form posts. */
.sat-machine input {
    position: absolute; width: 1px; height: 1px; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); border: 0;
}
.sat-machine input:focus-visible + .sat-machine-dot {
    box-shadow: 0 0 0 3px rgba(37,99,235,.35);
}

.sat-machine-dot {
    flex: 0 0 auto; width: 9px; height: 9px; border-radius: 999px;
    border: 1.5px solid #cbd5e1; background: #fff;
    transition: background .12s, border-color .12s;
}
/* Driven by the box, not by a class the server wrote: ticking one turns it
   green there and then. The .is-on the page renders is kept alongside so the
   state is right on the first paint and in a browser without :has(). */
.sat-machine.is-on,
.sat-machine:has(input:checked)               { color: #065f46; background: #ecfdf5; }
.sat-machine.is-on:hover,
.sat-machine:has(input:checked):hover         { background: #d1fae5; }
.sat-machine.is-on .sat-machine-dot,
.sat-machine:has(input:checked) .sat-machine-dot { background: #10b981; border-color: #059669; }
.sat-machine.is-on .sat-machine-name,
.sat-machine:has(input:checked) .sat-machine-name { font-weight: 700; }

/* Unticked while the class still says otherwise - the moment after a tick is
   removed, before any save. The box is the truth. */
.sat-machine.is-on:not(:has(input:checked)) {
    color: #6b7280; background: transparent; font-weight: 400;
}
.sat-machine.is-on:not(:has(input:checked)) .sat-machine-dot {
    background: #fff; border-color: #cbd5e1;
}
.sat-machine.is-on:not(:has(input:checked)) .sat-machine-name { font-weight: 400; }


/* ---- the sideways scrollbar you can always reach -------------------------
   A copy of the grid's own horizontal bar, pinned to the bottom of the window,
   shown only while the real one is below the fold. Built and wired up by
   assets/hscroll.js, which explains why.

   The scrollbar is styled explicitly rather than left to the browser, and that
   is the point rather than decoration: macOS draws an unstyled bar as an
   overlay that fades out until something moves, and a bar nobody can see is
   the problem being solved, not a solution to it.
   ========================================================================= */
.hscroll-bar {
    display: none;
    position: fixed; bottom: 0; z-index: 30;
    height: 16px; overflow-x: auto; overflow-y: hidden;
    background: #f8fafc; border-top: 1px solid #e2e8f0;
    box-shadow: 0 -2px 6px rgba(15, 23, 42, .07);
    /* Firefox */
    scrollbar-width: thin; scrollbar-color: #94a3b8 #eef2f7;
}
.hscroll-bar.is-on { display: block; }
/* Something to scroll, and nothing to see: the width is the whole of it. */
.hscroll-inner { height: 1px; }

.hscroll-bar::-webkit-scrollbar { height: 12px; }
.hscroll-bar::-webkit-scrollbar-track { background: #eef2f7; }
.hscroll-bar::-webkit-scrollbar-thumb {
    background: #94a3b8; border-radius: 6px; border: 2px solid #eef2f7;
}
.hscroll-bar::-webkit-scrollbar-thumb:hover { background: #64748b; }

@media print { .hscroll-bar { display: none !important; } }
