/* =========================================================================
   Planning duty roster - planning.php.

   The leave grid runs dates across; this one runs them down, the way the
   workbook's planning sheets do, with a column per dosimetrist. It borrows
   leave.css for the tabs, the save clock and the dialogs.
   ========================================================================= */

.plan-scroll { max-height: 76vh; }

.plan-grid {
    border-collapse: separate; border-spacing: 0;
    font-size: 12px; line-height: 1.35; white-space: nowrap;
}
.plan-grid th, .plan-grid td {
    border-right: 1px solid #eef2f7; border-bottom: 1px solid #eef2f7;
    padding: 3px 8px; text-align: center;
}

/* Sticky header. The name row has to sit exactly one group row down, and the
   group row's height depends on its contents - a title with a station code
   under it - so the measurement is made once on load and kept in --head-h.
   The fallback covers the moment before that runs. */
.plan-grid thead th {
    position: sticky; background: #eef2f7; z-index: 2;
    font-weight: 700; color: #374151;
}
.plan-grid .row-head th { top: 0; font-size: 12px; }
.plan-grid .row-name th { top: var(--head-h, 42px); font-size: 12px; }

/* sticky date column */
.plan-grid .date-col {
    position: sticky; left: 0; z-index: 3; background: #fff;
    text-align: left; width: 78px; min-width: 78px; font-weight: 600;
    color: #374151; border-right: 1px solid #e5e7eb;
}
.plan-grid .dow-col {
    position: sticky; left: 78px; z-index: 3; background: #fff;
    text-align: left; width: 88px; min-width: 88px; color: #6b7280;
    border-right: 2px solid #d1d5db;
}
.plan-grid thead .date-col, .plan-grid thead .dow-col { z-index: 4; background: #eef2f7; }

.plan-grid .name-cell { min-width: 108px; }
.plan-grid .name-cell .n { display: block; font-size: 12px; color: #111827; }
.plan-grid .name-cell .abbr {
    display: inline-block; margin-top: 1px;
    font-size: 9px; font-weight: 700; letter-spacing: .03em;
    background: #f1f5f9; color: #475569; border: 1px solid #e2e8f0;
    padding: 0 4px; border-radius: 3px;
}

/* week banner */
.plan-grid .row-week th, .plan-grid .row-week td {
    background: #eff6ff; color: #1e40af; font-weight: 700;
    text-transform: uppercase; letter-spacing: .04em; font-size: 10px;
    text-align: left; padding: 2px 8px;
}

/* Day rows that are not ordinary working days.

   A holiday can land on a Saturday, so a row can be both at once - and then the
   holiday is the fact worth seeing, because it is the one that says nobody is
   in. Yellow is excluded rather than merely overridden: these selectors carry
   the same weight, so without the :not() whichever is written last would win,
   and that is not a thing to leave to the order of the file. */
.plan-grid tr.is-sun td, .plan-grid tr.is-sun th { background: #f1f5f9; color: #9ca3af; }
.plan-grid tr.is-ph  td, .plan-grid tr.is-ph  th { background: #fecaca; }
.plan-grid tr.is-sat:not(.is-ph):not(.is-sun) td,
.plan-grid tr.is-sat:not(.is-ph):not(.is-sun) th { background: #fffbeb; }
.plan-grid tr.is-duty td, .plan-grid tr.is-duty th { background: #f5f3ff; }
.plan-grid tbody tr:hover td:not(.plan-cell) { background: #f8fafc; }

.tag {
    display: inline-block; margin-left: 4px; padding: 0 5px; border-radius: 3px;
    font-size: 9px; font-weight: 700; letter-spacing: .03em;
}
.tag-ph   { background: #fee2e2; color: #991b1b; }
.tag-sat  { background: #fef08a; color: #713f12; }
.tag-duty { background: #ddd6fe; color: #4c1d95; }

/* group banding, so four groups read as four blocks */
.plan-grid .grp-head { font-size: 11px; }
.plan-grid .grp-head .grp-code {
    display: block; font-size: 9px; font-weight: 700; letter-spacing: .04em;
    color: #6b7280;
}
.plan-grid .grp-plan  { background: #e0e7ff; color: #3730a3; }
.plan-grid .grp-cover { background: #d1fae5; color: #065f46; }
/* ---- where one person ends and the next begins --------------------------
   The grid's own hairline is #eef2f7, which is nearly white. That is right
   between one day and the next - the rows are read as a block - but across
   the sheet it left two people's columns running into each other, so a shift
   read off the wrong column was an easy mistake to make. The line between
   staff is drawn properly; the line between stations stays the stronger of
   the two, so the sheet still reads as groups first and people second.
   ========================================================================= */
.plan-grid [data-col] { border-right: 1px solid #cbd5e1; }
.plan-grid .grp-start { border-left: 2px solid #94a3b8; }

/* ---- the cells: hours on the left, who or where on the right ------------ */
.plan-grid td.plan-cell {
    min-width: 132px; height: 26px; background: #fff; position: relative;
    padding: 2px 3px;
}
.plan-grid td.plan-cell[data-staff] { cursor: pointer; }
.plan-grid td.plan-cell[data-staff]:hover { outline: 2px solid #93c5fd; outline-offset: -2px; }

.plan-grid .slot {
    display: inline-block; vertical-align: middle; border-radius: 3px;
    font-size: 11px; line-height: 16px; min-height: 16px;
}
.plan-grid .slot-shift { font-weight: 700; letter-spacing: -.01em; min-width: 66px; }
/* Hours that follow from the Saturday duty rather than being set here. */
.plan-grid .slot-shift.is-auto { font-style: italic; font-weight: 600; opacity: .8; }
.plan-grid .slot-side {
    min-width: 40px; margin-left: 3px; padding: 0 4px;
    font-weight: 700; font-size: 10px; color: #6b7280;
    border: 1px dashed transparent;
    /* It holds a name now rather than three letters, and a long one must not
       widen every column on a nine-week grid. Cut to fit; the whole of it is in
       the cell's own tooltip, which already names who is covering. */
    /* 74px, because the longest code another centre can send somebody to -
       "AKN Unity" - needs 69px and was being cut. This centre's own stations
       are shown without their prefix and are well inside it. */
    max-width: 74px; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap; vertical-align: bottom;
}
.plan-grid td[data-staff] .slot-side { border-color: #e5e7eb; }
.plan-grid td[data-staff] .slot-side:hover { border-color: #60a5fa; background: #eff6ff; }

/* That hint border is #e5e7eb, and so is an AL cell - grey on the same grey,
   so the slot disappeared exactly where it matters most: an AL day is the day
   cover has to be named. Darkened on the grey cells only, so an ordinary white
   cell keeps its quiet hint and the grid does not turn into a page of boxes.

   `:not(:hover)` because the hover rule above weighs the same as this one and
   comes first, so without it this would win and the blue hover border would
   never show. `is-set` and `is-derived` paint their own and are already plain. */
.plan-grid td.st-al[data-staff]    .slot-side:not(.is-set):not(.is-derived):not(:hover),
.plan-grid td.sh-leave[data-staff] .slot-side:not(.is-set):not(.is-derived):not(:hover),
.plan-grid td.sh-off[data-staff]   .slot-side:not(.is-set):not(.is-derived):not(:hover),
.plan-grid td.is-away[data-staff]  .slot-side:not(.is-set):not(.is-derived):not(:hover) {
    border-color: #6b7280;
}

/* named by hand */
.plan-grid .slot-side.is-set, .lg.is-set {
    background: #fef3c7; border-style: solid; border-color: #fcd34d; color: #92400e;
}
/* taken from the leave list rather than typed here */
.plan-grid .slot-side.is-derived, .lg.is-derived {
    background: #ecfdf5; border-style: solid; border-color: #6ee7b7; color: #0f766e;
}
/* a slot edited but not written */
.plan-grid .slot.is-pending {
    outline: 2px solid #f59e0b; outline-offset: 0; background: #fffbeb;
}

/* The three shifts, told apart by colour rather than by reading the hours:
   early is blue, middle is green, late is purple. */
.plan-grid td.sh-1, .lg.sh-1 { background: #dbeafe; }
.plan-grid td.sh-2, .lg.sh-2 { background: #dcfce7; }
.plan-grid td.sh-3, .lg.sh-3 { background: #ede9fe; }
.plan-grid td.sh-1 .slot-shift, .lg.sh-1 { color: #1e40af; }
.plan-grid td.sh-2 .slot-shift, .lg.sh-2 { color: #15803d; }
.plan-grid td.sh-3 .slot-shift, .lg.sh-3 { color: #5b21b6; }
.plan-grid td.sh-other { background: #fef3c7; }
.plan-grid td.sh-other .slot-shift { color: #92400e; }
.plan-grid td.sh-off, .lg.sh-off  { background: #f3f4f6; }
.plan-grid td.sh-off .slot-shift, .lg.sh-off { color: #6b7280; }
.plan-grid td.sh-al .slot-shift, .plan-grid td.sh-sl .slot-shift { color: #b45309; }

/* why someone is not on an ordinary shift - a short tag, where there is one */
.plan-grid td .why {
    display: inline-block; vertical-align: middle; margin-left: 3px;
    border-radius: 3px; padding: 0 4px;
    font-size: 9px; font-weight: 700; letter-spacing: .02em; color: #6b7280;
}
.plan-grid td.st-comp .why, .lg.st-comp { background: #fef9c3; color: #854d0e; }
/* A station worked on a day that is not an ordinary working day: called in on
   a holiday or a special week Saturday, or rostered onto an ordinary Saturday.
   The same fact and the same purple, whichever page named it. */
.plan-grid td.st-called .why, .lg.st-called,
.plan-grid td.st-satduty .why, .lg.st-satduty { background: #ddd6fe; color: #4c1d95; }
.plan-grid td.st-satmark .why { background: #fecaca; color: #7f1d1d; }
.plan-grid td.st-closed .why, .plan-grid td.st-ph .why, .plan-grid td.st-satfree .why {
    background: transparent; color: #9ca3af;
}

/* Away, so no hours are expected. The grey carries that message on its own,
   which is why the hours on top of it are muted rather than coloured - but
   muted has to stay readable: 09:00-17:00 in #9ca3af on the AL grey measured
   about 2:1, which is a shape where a time should be. #4b5563 reads at 6:1 and
   is still obviously quieter than a shift's blue, green or purple. It is also
   what the machine sheet already uses for the same cell. */
.plan-grid td.is-away { background: #f3f4f6; }
.plan-grid td.is-away .slot-shift { color: #4b5563; }
.plan-grid td.st-al, .lg.lg-al { background: #e5e7eb; }
.plan-grid td.st-al .slot-shift { color: #4b5563; }

/* The cell says so itself: AL, SL, or a typed reason marked as leave. Same
   grey, because it means the same thing - and the same readable grey on it. */
.plan-grid td.sh-leave, .lg.sh-leave { background: #e5e7eb; }
.plan-grid td.sh-leave .slot-shift { color: #4b5563; font-weight: 700; }

.plan-grid .cover-col {
    width: 34px; min-width: 34px; color: #6b7280; font-weight: 700; font-size: 11px;
}
.plan-grid td.cover-col.is-none { color: #b91c1c; background: #fef2f2; }

/* ---- remarks ------------------------------------------------------------ */
/* The column grows with what is in it rather than being pinned at one width.
   A remark may be 200 characters, and at 220px anything past about thirty was
   cut off - "Jimmy to AKN, Hanes to R3 (1230-1..." tells you a time was set
   and not what it was. Between the two figures it takes the width it needs, so
   the ordinary remark is simply readable; past the ceiling it still ends in an
   ellipsis, and the whole of it is on the cell's own tooltip, which the grid
   shows instantly (see assets/tips.js). */
.plan-grid .remark-col {
    min-width: 220px; max-width: 460px;
    text-align: left; font-size: 11px; color: #4b5563;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    border-left: 2px solid #cbd5e1;
}
/* On a phone the sheet is already wider than the screen, so the remark takes
   what it can rather than pushing everything else further out of reach. */
@media screen and (max-width: 820px) {
    .plan-grid .remark-col { min-width: 150px; max-width: 240px; }
}
.plan-grid td.remark-col[data-remark] { cursor: text; }
.plan-grid td.remark-col[data-remark]:hover { background: #f8fafc; outline: 1px dashed #93c5fd; }
.plan-grid td.remark-col.has-note { background: #fffbeb; color: #78350f; }
.plan-grid td.remark-col.is-pending { outline: 2px solid #f59e0b; outline-offset: -2px; }

/* ---- reset a station ----------------------------------------------------
   Reached from the red button beside Save. It reads as a warning rather than a
   menu, because every row in it throws nine weeks of work away. */
.reset-note {
    margin: 4px 4px 12px; padding: 10px 12px; border-radius: 6px;
    background: #fef2f2; border: 1px solid #fecaca;
    font-size: 12px; line-height: 1.45; color: #7f1d1d;
}
.reset-note .link { color: #b91c1c; }
/* What survives it, said as plainly as what does not. */
.reset-keeps {
    margin: -6px 4px 12px; padding: 10px 12px; border-radius: 6px;
    background: #f0fdf4; border: 1px solid #bbf7d0;
    font-size: 12px; line-height: 1.45; color: #14532d;
}
.reset-keeps .link { color: #15803d; }
.move-opt.reset-opt {
    justify-content: space-between; gap: 12px; margin-bottom: 4px;
    border: 1px solid #e5e7eb; background: #fff;
}
.move-opt.reset-opt:hover { background: #fef2f2; border-color: #fecaca; }
.reset-opt-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.reset-opt-name { font-weight: 600; }
.reset-opt-meta { font-size: 11px; color: #6b7280; }
.reset-opt-go {
    flex: 0 0 auto; font-size: 12px; font-weight: 700;
    color: #b91c1c; background: #fef2f2; border: 1px solid #fecaca;
    padding: 4px 10px; border-radius: 999px;
}
.move-opt.reset-opt:hover .reset-opt-go { background: #ef4444; color: #fff; border-color: #ef4444; }

.remark-field { display: block; padding: 4px; }
.remark-field span { display: block; font-size: 12px; color: #6b7280; margin-bottom: 6px; }
.remark-field input {
    display: block; width: 100%; padding: 8px 10px; font: inherit; font-size: 14px;
    border: 1px solid #d1d5db; border-radius: 6px;
}
.remark-field input:focus { outline: 2px solid #2563eb; outline-offset: -1px; }
.remark-actions { display: flex; gap: 8px; align-items: center; }
.remark-actions .btn-primary { margin-left: auto; }

/* ---- batch panel -------------------------------------------------------- */
/* The fold itself is `.fold-card` in leave.css, shared with the panels above
   the leave grid; only what is inside this one is here. */
.batch-grid {
    display: grid; gap: 12px 22px; margin-top: 12px;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    padding-top: 12px; border-top: 1px solid #eef2f7;
}
.batch-field label {
    display: block; font-size: 11px; font-weight: 700; text-transform: uppercase;
    letter-spacing: .05em; color: #6b7280; margin-bottom: 5px;
}
.batch-opts { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }

.chip {
    display: inline-flex; align-items: center; gap: 5px;
    padding: 4px 9px; border-radius: 999px; border: 1px solid #d1d5db;
    background: #fff; font-size: 12px; color: #374151; cursor: pointer;
    text-transform: none; letter-spacing: 0; font-weight: 500; margin: 0;
}
.chip input { margin: 0; }

/* A chip is a <label>, and so is the heading above each field - and
   `.batch-field label` is the more specific of the two, so it was winning and
   laying every chip out as a block. That quietly turned off the flex centring
   and the gap: the chips that look right only do so because their markup
   happens to have a newline between the control and its word, and the one
   written without a space had its box and its label touching.

   Only the layout is taken back. The panel's own type - uppercase, its size
   and colour - is left where it was, so nothing changes but the alignment. */
.batch-field .chip {
    display: inline-flex; align-items: center; gap: 5px;
    margin-bottom: 0; flex: 0 0 auto;
}
.chip:hover { background: #f8fafc; }
.chip:has(input:checked) { border-color: #2563eb; background: #eff6ff; color: #1e40af; }
.chip.sh-1:has(input:checked) { border-color: #3b82f6; background: #dbeafe; color: #1e40af; }
.chip.sh-2:has(input:checked) { border-color: #22c55e; background: #dcfce7; color: #15803d; }
.chip.sh-3:has(input:checked) { border-color: #8b5cf6; background: #ede9fe; color: #5b21b6; }
.chip.sh-off:has(input:checked),
.chip.chip-clear:has(input:checked) { border-color: #9ca3af; background: #f3f4f6; color: #374151; }
/* "Keep hours": picked, it reads as a held-back rather than a value. */
.chip.chip-keep:has(input:checked) { border-color: #6b7280; background: #f3f4f6; color: #111827; font-style: italic; }
/* The warning chip in the batch panel: the mark's red when it is picked. */
.chip.chip-alert:has(input:checked) { border-color: #dc2626; background: #fef2f2; color: #991b1b; }

/* AL and SL mean away wherever they appear, so the chip greys like the cell. */
.chip.sh-al:has(input:checked),
.chip.sh-sl:has(input:checked) { border-color: #6b7280; background: #e5e7eb; color: #374151; }
.chip.sh-sim:has(input:checked) { border-color: #0ea5e9; background: #e0f2fe; color: #075985; }

/* Hours typed into the panel, for the days no chip covers. */
.batch-typed {
    font-size: 12px; padding: 5px 9px; width: 160px;
    border: 1px solid #d1d5db; border-radius: 999px;
}
.batch-typed:focus { outline: 2px solid #2563eb; outline-offset: -1px; }
.batch-typed.is-bad { border-color: #f87171; background: #fef2f2; }

.lnk {
    background: none; border: 0; padding: 0; font: inherit; font-size: 10px;
    color: #2563eb; cursor: pointer; text-transform: none; letter-spacing: 0;
}
.lnk:hover { text-decoration: underline; }

.batch-foot {
    display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
    margin-top: 12px; padding-top: 10px; border-top: 1px solid #eef2f7;
}
.batch-foot .spacer { margin-left: auto; }
/* Who a batch leaves alone: three choices in a column, so the two that read
   almost alike can be compared a line apart rather than remembered. */
.batch-skip {
    display: flex; flex-direction: column; gap: 3px;
    font-size: 12px; color: #4b5563;
}
.batch-skip-head {
    font-size: 10px; font-weight: 700; color: #6b7280;
    letter-spacing: .06em; margin-bottom: 1px;
}
.batch-skip label {
    display: flex; align-items: flex-start; gap: 7px;
    cursor: pointer; line-height: 1.35;
}
.batch-skip input { margin: 2px 0 0; flex: 0 0 auto; }
.batch-skip label:hover { color: #111827; }
/* The one that is set: the whole point of three lines is telling them apart. */
.batch-skip label:has(input:checked) { color: #111827; font-weight: 600; }
.batch-skip b { font-weight: 800; color: #b45309; }
#batch-count { min-width: 150px; text-align: right; }

/* ---- shift dialog ------------------------------------------------------- */
/* Wide enough that each section's type-in box sits beside its buttons rather
   than dropping to a line of its own. */
#shift-dialog { max-width: 660px; }
.shift-note {
    margin: 4px 4px 0; padding: 7px 10px; border-radius: 6px;
    background: #fffbeb; border: 1px dashed #fcd34d; color: #92400e; font-size: 12px;
}
.shift-group {
    margin: 12px 4px 6px; font-size: 10px; font-weight: 700;
    text-transform: uppercase; letter-spacing: .05em; color: #6b7280;
}
.shift-opts {
    display: flex; flex-wrap: wrap; gap: 6px; padding: 6px 4px 0;
    align-items: stretch;
}
.shift-opt {
    display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
    justify-content: center; position: relative;
    padding: 7px 11px; border-radius: 7px; border: 1px solid #d1d5db;
    background: #fff; cursor: pointer; font: inherit; font-size: 13px;
}
.shift-opt:hover { border-color: #9ca3af; }
.shift-opt .v { font-weight: 700; }
.shift-opt .d { font-size: 10px; color: #6b7280; }
.shift-opt.is-current { outline: 2px solid #2563eb; outline-offset: -1px; }
/* Each option is painted as the cell will be, so the list previews itself. */
.shift-opt.sh-1 { background: #dbeafe; border-color: #93c5fd; color: #1e40af; }
.shift-opt.sh-2 { background: #dcfce7; border-color: #86efac; color: #15803d; }
.shift-opt.sh-3 { background: #ede9fe; border-color: #c4b5fd; color: #5b21b6; }
/* Off the rota, but not away - lighter than the leave grey it sits beside. */
.shift-opt.sh-off { background: #f3f4f6; border-color: #d1d5db; color: #4b5563; }
/* Away: the same grey these paint the cell. */
.shift-opt.sh-al,
.shift-opt.sh-sl { background: #e5e7eb; border-color: #9ca3af; color: #374151; }
.shift-opt.sh-al .d,
.shift-opt.sh-sl .d { color: #4b5563; }

/* Typed in, for what the list beside it does not cover. Sits in the same row
   as the buttons and drops to its own line when there is no room. */
.shift-custom {
    display: flex; gap: 6px; flex: 1 1 200px; min-width: 170px;
}
.shift-opts > .shift-custom { padding: 0; }
.shift-custom-input {
    flex: 1 1 auto; min-width: 0; padding: 7px 10px; font: inherit; font-size: 13px;
    border: 1px solid #d1d5db; border-radius: 7px;
}
.shift-custom-input::placeholder { color: #9ca3af; font-size: 12px; }
.shift-custom-input:focus { outline: 2px solid #2563eb; outline-offset: -1px; }
/* the "is it a leave" toggle beside a typed reason */
.reason-leave {
    display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
    margin: 7px 4px 0; font-size: 12px; color: #4b5563; cursor: pointer;
}
.reason-leave input { margin: 0; flex: 0 0 auto; }
.reason-leave:hover { color: #111827; }
.reason-leave-hint { font-size: 11px; color: #9ca3af; }
.reason-leave:has(input:checked) { color: #92400e; font-weight: 600; }

.shift-custom-warn:not(:empty) {
    margin: 6px 4px 0; padding: 6px 9px; border-radius: 6px;
    background: #fef2f2; border: 1px solid #fecaca; color: #b91c1c; font-size: 11px;
}

/* the reliever / station picker */
.side-opts { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 4px; }
.side-opt {
    display: flex; align-items: center; gap: 7px;
    padding: 7px 11px; border-radius: 7px; border: 1px solid #d1d5db;
    background: #fff; cursor: pointer; font: inherit; font-size: 13px;
}
.side-opt:hover { border-color: #9ca3af; background: #f8fafc; }
.side-opt.is-current { outline: 2px solid #2563eb; outline-offset: -1px; }
.side-opt .rl-abbr {
    font-size: 10px; font-weight: 800; letter-spacing: .04em;
    background: #f1f5f9; color: #475569; border: 1px solid #e2e8f0;
    padding: 1px 5px; border-radius: 3px;
}
.side-opt .rl-name { font-weight: 600; color: #111827; }
.side-opt.station .rl-name { font-weight: 700; letter-spacing: .02em; }

/* ---- batch: the Who list, grouped ---------------------------------------- */
.batch-who { grid-column: span 2; }
.batch-stack { flex-direction: column; align-items: stretch; gap: 6px; }

/* The heading sits in a column of its own, so a group that wraps keeps its
   people lined up under each other rather than starting under the label. */
.batch-row { display: flex; gap: 5px; align-items: flex-start; }
.batch-row .grp {
    flex: 0 0 78px; padding-top: 5px; text-align: left;
    font-size: 10px; font-weight: 700;
    text-transform: uppercase; letter-spacing: .04em; color: #6b7280;
}
.batch-row .grp:hover { color: #2563eb; }
/* the people themselves wrap within their own column */
.batch-row .chip { margin: 0; }
.batch-row-people {
    display: flex; flex-wrap: wrap; gap: 5px; align-items: center; flex: 1 1 auto;
}

/* ---- legend ------------------------------------------------------------- */
.plan-legend .lg:empty { min-height: 13px; min-width: 22px; vertical-align: -2px; }
.plan-legend .lg.is-derived, .plan-legend .lg.is-set { border-width: 1px; border-style: solid; }

/* ---- the off slot -------------------------------------------------------- */
/* Off / AL / SL, or a reason, beside the hours rather than in place of them:
   a morning off still leaves hours worth recording and cover worth naming. */
.plan-grid td.plan-cell { min-width: 176px; }
.plan-grid .slot-status {
    min-width: 26px; margin-right: 3px; padding: 0 4px; border-radius: 3px;
    font-size: 10px; font-weight: 700; color: #9ca3af;
    border: 1px dashed transparent;
}
/* An empty note box has to be seen to be clicked. A hairline dashed border
   in near-white was invisible on a coloured cell - the simulator's green, a
   Saturday's yellow - so the box got clicked only by people who already knew
   it was there. A mid-grey dash and a white fill read on every cell colour,
   and the pale blue tint says "there is a control here" without shouting;
   hover deepens both. The two are only drawn on a cell that opens. */
.plan-grid td[data-staff] .slot-status {
    border-color: #9ca3af; background: rgba(255, 255, 255, .85);
    box-shadow: inset 0 0 0 1px rgba(37, 99, 235, .12);
    min-height: 15px;
}
.plan-grid td[data-staff] .slot-status:hover {
    border-color: #2563eb; background: #dbeafe; box-shadow: none;
}
.plan-grid .slot-status.is-set { border-style: solid; box-shadow: none; }
.plan-grid td.sh-off .slot-status { background: #f3f4f6; color: #4b5563; border-color: #d1d5db; }
.plan-grid td.sh-al .slot-status,
.plan-grid td.sh-sl .slot-status { background: #d1d5db; color: #374151; border-color: #9ca3af; }
.plan-grid td.sh-reason .slot-status {
    background: #fef3c7; color: #92400e; border-color: #fcd34d;
    max-width: 74px; overflow: hidden; text-overflow: ellipsis;
}
/* away by the cell's own reckoning: grey, but the hours stay readable */
.plan-grid td.sh-leave { background: #e5e7eb; }
.plan-grid td.sh-leave .slot-shift { color: #4b5563; }

/* the batch panel's two jobs */
#batch-panel[data-what="shift"]  .only-status,
#batch-panel[data-what="shift"]  .only-remark,
#batch-panel[data-what="status"] .only-shift,
#batch-panel[data-what="status"] .only-remark,
#batch-panel[data-what="remark"] .only-shift,
#batch-panel[data-what="remark"] .only-status { display: none; }

/* A remark is the centre's line for the day, not anybody's cell, so who is
   ticked cannot change what it writes. Both questions come off the panel
   rather than sitting there being ignored. */
#batch-panel[data-what="remark"] .batch-who,
#batch-panel[data-what="remark"] .batch-skip { display: none; }

.batch-remark { flex: 1 1 260px; min-width: 0; }
#batch-status .batch-typed { width: 170px; }

/* Folding the off column away. The cell keeps its colour - grey when somebody
   is away - so the chip is the only thing that goes, and the width with it. */
.plan-grid.hide-status .slot-status { display: none; }
.plan-grid.hide-status td.plan-cell { min-width: 132px; }

/* ---- the cover slot, locked -------------------------------------------
   Naming the cover is the leave & duty list's alone - see 'roster.relief' in
   config.php. The slot still shows what the list says; it just does not open,
   and a small padlock says why without a word of explanation in every cell.

   Drawn as a background on the slot rather than an <img> in it: there are
   thousands of these cells on a nine-week grid, and one rule costs nothing per
   cell where an element costs a node each. In the bottom right corner, faint,
   and behind nothing - the name sits on its own line and the lock tucks into
   the corner beside it rather than over it. */
.plan-grid .slot-side.is-locked {
    position: relative;
    cursor: default;
    padding-right: 12px;            /* the corner the lock sits in, kept clear */
}
.plan-grid .slot-side.is-locked::after {
    content: '';
    position: absolute; right: 1px; bottom: 1px;
    width: 8px; height: 8px;
    background: url('../icons/lock.png') no-repeat center / contain;
    opacity: .38;
    pointer-events: none;           /* never in the way of the cell beneath */
}
/* Nothing about it should invite a click: no hover lift, no pointer. */
.plan-grid td[data-staff] .slot-side.is-locked:hover {
    border-color: #e5e7eb; background: transparent;
}
.plan-grid td[data-staff] .slot-side.is-locked.is-set:hover { background: #ecfdf5; }

@media print {
    /* On paper the lock says nothing - there is nothing to click. */
    .plan-grid .slot-side.is-locked::after { display: none; }
    .plan-grid .slot-side.is-locked { padding-right: 4px; }
}

/* The day and its tag, folded into the date cell. Hidden here and shown only
   on a phone - written before the media query, because both selectors weigh
   the same and the last one wins; after it, this would have hidden them on the
   one screen that wants them. */
.plan-grid .dow-mini, .plan-grid .tag-mini { display: none; }

/* ---- the rosters on a phone ---------------------------------------------
   Two frozen columns - the date and the day - were taking 166px of a 390px
   screen before a single roster cell, and the whole point of freezing them is
   to leave room for the roster beside them.

   So the day joins the date: "5 Sep (Mon)", in one frozen column. The Day
   column is not removed - a colspan on the week rows counts it, and a column
   that vanishes underneath one leaves the row a cell too wide - it is
   collapsed to nothing and unstuck, which is the space that was wanted back.
   Its tag comes with it: the same string, printed in the date cell instead. */
@media screen and (max-width: 820px) {
    .plan-grid .date-col {
        width: 96px; min-width: 96px; max-width: 96px;
        border-right: 2px solid #d1d5db;
        white-space: normal; line-height: 1.25;
    }
    .plan-grid .dow-col {
        position: static;               /* the 88px of frozen width, given back */
        width: 0; min-width: 0; max-width: 0;
        padding: 0; border-right: 0; font-size: 0; overflow: hidden;
    }
    .plan-grid .dow-col * { display: none; }

    /* The day, and the tag that was beside it, now in the date cell. */
    .plan-grid .dow-mini {
        display: inline; margin-left: 4px;
        font-size: 10px; font-weight: 600; color: #6b7280;
    }
    .plan-grid .dow-mini::before { content: '('; }
    .plan-grid .dow-mini::after  { content: ')'; }
    .plan-grid .tag-mini { display: inline; }
    .plan-grid .tag-mini .tag { margin-left: 4px; font-size: 9px; padding: 0 3px; }
}

/* ---- "check the remark" ------------------------------------------------
   A cell holds hours and nothing else, and some days need a sentence - a late
   start for a meeting, a machine down at noon. The remark column already takes
   the sentence; this is the sign, at the cell, that there is one to read.

   Drawn as a stencil rather than the picture itself: the file is black line
   art, and the cells it sits in are pale green, blue, purple, yellow and grey
   by turns. Laid down as a mask it takes a colour of its own - amber, which is
   what the rest of the site uses for "look at this" - and reads on all of
   them. Its own element, always in the markup and shown by the flag, so the
   script turns it on without building anything. */
.plan-grid .shift-alert { display: none; }

.plan-grid td[data-alert="1"] .shift-alert {
    display: inline-block;
    /* Big enough to read as a warning triangle rather than a speck, and red
       rather than amber: amber is the colour half this grid already is - the
       Saturdays, the auto-filled hours, the Res chips - and a warning has to
       be the thing that is not like its neighbours. */
    width: 15px; height: 15px; flex: 0 0 15px;
    margin-left: 4px;
    vertical-align: middle;
    background: #dc2626;
    -webkit-mask-image: url('../icons/caution.png');
            mask-image: url('../icons/caution.png');
    -webkit-mask-position: center;  mask-position: center;
    -webkit-mask-size: contain;     mask-size: contain;
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
    cursor: pointer;
}
/* The mark is for whoever reads the sheet, so it keeps its hover and its click
   on a sheet that is not theirs: the cell beneath takes no pointer while the
   sheet is read-only, the mark takes it back. And the remark it points at,
   lit for a moment after the click - see the alert handler in planning.js
   and machine.js. */
.sheet-readonly .plan-grid td[data-staff] .shift-alert { pointer-events: auto; }
.plan-grid td.remark-col.is-lit { animation: remark-lit 1.8s ease-out; }
@keyframes remark-lit {
    0%, 55% { box-shadow: inset 0 0 0 3px #dc2626; background-color: #fef2f2; }
    100%    { box-shadow: none; }
}
/* On a pink public-holiday cell the red is the background's own colour, so it
   goes darker to keep its edges. */
.plan-grid td.st-ph[data-alert="1"] .shift-alert,
.plan-grid td.is-ph[data-alert="1"] .shift-alert { background: #7f1d1d; }

/* The tick in the hours dialog - above the shifts, so it is read before
   the hours are picked rather than found underneath them afterwards. */
.shift-alert-toggle {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    margin: 4px 0 8px; padding: 9px 11px;
    border: 1px solid #fde68a; border-radius: 8px; background: #fffbeb;
    font-size: 13px; color: #78350f; cursor: pointer;
}
.shift-alert-toggle input { margin: 0; flex: 0 0 auto; }
.shift-alert-toggle:hover { border-color: #fcd34d; }
.shift-alert-hint { flex: 1 1 100%; font-size: 11px; color: #a16207; }
.shift-alert-toggle:has(input:checked) {
    border-color: #f59e0b; background: #fef3c7; font-weight: 600;
}

/* Lazy mode, in the head of the hours dialog, and the Confirm that stands
   in for the click-and-close when it is off. */
.lazy-switch { margin-left: auto; }
.shift-confirm { margin-left: auto; }
/* A shift held but not yet confirmed.

   The saved shift wears a still, solid blue ring. A held one wore blue too
   and the two could not be told apart, so a held pick is now nothing like it:
   an **orange** dashed ring that runs clockwise round the button - marching
   ants, four striped gradients sliding along their edges - and a small NEW
   tag on its corner. A different colour, a different line, and a word, so it
   reads at a glance and still reads in print or with motion turned off. */
.shift-opt.is-held::after {
    content: ''; position: absolute; inset: -4px; border-radius: 10px;
    pointer-events: none; overflow: hidden;
    --ant: #ea580c; --dash: 16px; --w: 2.5px;
    background:
        linear-gradient(90deg, var(--ant) 50%, transparent 50%) 0 0    / var(--dash) var(--w) repeat-x,
        linear-gradient(90deg, var(--ant) 50%, transparent 50%) 0 100% / var(--dash) var(--w) repeat-x,
        linear-gradient(0deg,  var(--ant) 50%, transparent 50%) 0 0    / var(--w) var(--dash) repeat-y,
        linear-gradient(0deg,  var(--ant) 50%, transparent 50%) 100% 0 / var(--w) var(--dash) repeat-y;
    animation: held-ants .8s linear infinite;
}
.shift-opt.is-held::before {
    content: 'new'; position: absolute; top: -9px; right: -6px; z-index: 1;
    padding: 1px 6px; border-radius: 999px;
    background: #ea580c; color: #fff;
    font-size: 9px; font-weight: 800; line-height: 1.4;
    text-transform: uppercase; letter-spacing: .06em;
    box-shadow: 0 0 0 2px #fff;
}
@keyframes held-ants {
    to {
        background-position:
            var(--dash) 0,
            calc(-1 * var(--dash)) 100%,
            0 calc(-1 * var(--dash)),
            100% var(--dash);
    }
}
@media (prefers-reduced-motion: reduce) {
    .shift-opt.is-held::after { animation: none; }
}
.shift-opt.is-dropped { opacity: .45; text-decoration: line-through; }

/* ---- the week copier ----------------------------------------------------
   Copy mode paints the sheet so it is obvious which cells may be picked and
   which have been: planning cells get the crosshair, everything else the
   "not here" cursor, the cells copied a dashed ring, and a cell just pasted
   flashes. The status line reads as a sentence and changes colour with the
   phase, like the save line. */
.copier-status { font-size: 12px; color: #4b5563; }
/* `.btn` sets display and so beats the browser's own [hidden]; the three
   buttons take turns, so hidden has to mean hidden here. */
.copier-foot .btn[hidden] { display: none !important; }
.copier-status[data-phase="pick"]  { color: #1d4ed8; font-weight: 600; }
.copier-status[data-phase="paste"] { color: #b45309; font-weight: 600; }
.copier-status[data-phase="bad"]   { color: #b91c1c; font-weight: 600; }

body.copier-on .plan-grid td.plan-cell[data-staff][data-group="planning"] { cursor: crosshair; }
body.copier-on .plan-grid td.plan-cell[data-staff]:not([data-group="planning"]) { cursor: not-allowed; }
/* Copied from: a thick dotted blue ring and a light blue wash. */
body.copier-on .plan-grid td.plan-cell.is-copy-src {
    outline: 3px dotted #2563eb; outline-offset: -3px;
    background-image: linear-gradient(rgba(37, 99, 235, .12), rgba(37, 99, 235, .12));
}
/* Pasting onto: the same ring in orange - on the cells the pointer would
   paste onto while it hovers, and on the cells a paste has landed on. */
body.copier-on .plan-grid td.plan-cell.is-copy-tgt,
body.copier-on .plan-grid td.plan-cell.is-pasted {
    outline: 3px dotted #ea580c; outline-offset: -3px;
    background-image: linear-gradient(rgba(234, 88, 12, .12), rgba(234, 88, 12, .12));
}
/* A cell that is both - pasting a week back over itself - shows the source. */
body.copier-on .plan-grid td.plan-cell.is-copy-src.is-copy-tgt {
    outline-color: #2563eb;
}
/* A paste that has just landed flashes as well, so the eye is taken to it. */
.plan-grid td.plan-cell.is-pasted-now { animation: pasted-lit 1.2s ease-out; }
@keyframes pasted-lit {
    0%, 50% { box-shadow: inset 0 0 0 3px #ea580c; }
    100%    { box-shadow: none; }
}

/* ---- the edit lock ------------------------------------------------------
   Shared by the planning and treatment rosters, which is why it lives in the
   stylesheet both of them load. The button stands beside Save because the two
   are one action split in half: take the sheet, write to it, give it back.
   Behaviour is in assets/lock.js, the rule itself in lock_lib.php.
   ========================================================================= */
.lock-slot {
    display: inline-flex; align-items: center; gap: 8px;
}
.lock-ico { width: 15px; height: 15px; flex: 0 0 15px; }

.lock-btn {
    display: inline-flex; align-items: center; gap: 6px;
    background: #fff; color: #1d4ed8; border: 1px solid #bfdbfe;
}
.lock-btn:hover:not(:disabled) { background: #eff6ff; border-color: #60a5fa; }
/* Holding the sheet is the state to be aware of, so it is the loud one. */
.lock-btn.is-mine {
    background: #ecfdf5; color: #047857; border-color: #6ee7b7;
}
.lock-btn.is-mine:hover:not(:disabled) { background: #d1fae5; border-color: #34d399; }
.lock-btn.is-force {
    background: #fff; color: #b91c1c; border-color: #fecaca;
}
.lock-btn.is-force:hover:not(:disabled) { background: #fef2f2; border-color: #fca5a5; }

.lock-note {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: 12.5px; font-weight: 600; color: #6b7280;
}
.lock-note.is-mine   { color: #047857; }
.lock-note.is-theirs { color: #b45309; }
.lock-note b { font-weight: 800; }


/* The moment after a save when the sheet is still held: this is yours and
   somebody may be waiting - a nudge towards Unlock. */
.lock-slot.is-nudge .lock-btn.is-mine {
    animation: lock-nudge 1s ease 3;
}
@keyframes lock-nudge {
    0%, 100% { box-shadow: 0 0 0 0 rgba(4,120,87,0); }
    50%      { box-shadow: 0 0 0 4px rgba(4,120,87,.22); }
}
@media (prefers-reduced-motion: reduce) {
    .lock-slot.is-nudge .lock-btn.is-mine { animation: none; outline: 2px solid #34d399; }
}

/* The lock's own four states on the save line, dot and all. They are read at
   a glance and two of them mean opposite things - "the sheet is free" and
   "somebody else has it" - so they cannot share a colour the way they did. */

/* Free again. The success the eye is looking for after pressing Unlock. */
.save-state[data-status="lock-open"] { color: #065f46; }
.save-state[data-status="lock-open"]::before { background: #059669; }

/* Yours. Green is already the free sheet, so holding one is its own colour. */
.save-state[data-status="lock-held"] { color: #1d4ed8; }
.save-state[data-status="lock-held"]::before { background: #3b82f6; }

/* Refused - somebody else holds it, or the lock could not be reached. */
.save-state[data-status="lock-denied"] { color: #b91c1c; }
.save-state[data-status="lock-denied"]::before { background: #ef4444; }

/* About to happen: the last minute, the reminder, the nudge after a save. */
.save-state[data-status="lock-warn"] { color: #b45309; }
.save-state[data-status="lock-warn"]::before { background: #f59e0b; }

/* ---- the sheet while it is not yours ------------------------------------
   The grid still reads, scrolls and prints exactly as it did; only the parts
   that write are shut. The server refuses the save anyway - this is so that
   nobody spends ten minutes editing before finding that out. */
.sheet-readonly .plan-grid td[data-staff],
.sheet-readonly .plan-grid td[data-remark],
.sheet-readonly .plan-grid td[data-hours],
.sheet-readonly .plan-grid .slot-side {
    pointer-events: none;
}
.sheet-readonly .plan-grid td.plan-cell[data-staff] { cursor: default; }
/* The on-call boxes shut with the rest of it. A select can be reached by the
   keyboard as well as the pointer, so the script puts a change back too - see
   the sheet-readonly branch in planning.js. */
.sheet-readonly .oncall-pick { pointer-events: none; background: #f8fafc; color: #6b7280; }

/* ---- one person's column ------------------------------------------------
   The picker beside Save, and what the grid does when it is used. Shared by
   the planning and treatment rosters; behaviour is in assets/colfilter.js.
   ========================================================================= */
/* A row of its own, directly above the grid it narrows. */
.col-filter {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    margin: 0 0 10px;
}
.col-pick {
    font: inherit; font-size: 13px; font-weight: 600; color: #374151;
    padding: 6px 9px; border: 1px solid #d1d5db; border-radius: 7px;
    background: #fff; max-width: 230px;
}
.col-pick:focus {
    outline: none; border-color: #2563eb; box-shadow: 0 0 0 3px rgba(37,99,235,.15);
}

/* Icon buttons: square, so the two of them read as one control beside the
   list rather than as two more things to read. */
.col-go, .col-all {
    padding: 0; width: 32px; height: 32px; flex: 0 0 32px;
    justify-content: center;
    background: #fff; color: #1d4ed8; border: 1px solid #bfdbfe;
}
.col-ico { width: 16px; height: 16px; }
.col-go:hover:not(:disabled), .col-all:hover { background: #eff6ff; border-color: #60a5fa; }
.col-go:disabled { color: #16a34a; border-color: #bbf7d0; background: #f0fdf4; }
.col-all { color: #b45309; border-color: #fde68a; }
.col-all:hover { background: #fffbeb; border-color: #fcd34d; }

/* ---- the grid's size and place, at the right of the same row -------------
   The - and + scale the table alone; the slider is its sideways scroll. Both
   are wired in assets/gridtools.js. margin-left: auto is what puts the cluster
   at the right, and keeps it there when the row wraps. */
.grid-tools {
    display: inline-flex; align-items: center; gap: 10px;
    margin-left: auto; flex: 0 1 auto; min-width: 0;
}
.grid-zoom {
    display: inline-flex; align-items: center; gap: 2px;
    padding: 2px; border: 1px solid #d1d5db; border-radius: 999px; background: #fff;
}
.grid-zoom-btn {
    width: 26px; height: 26px; padding: 0; flex: 0 0 26px;
    justify-content: center; border-radius: 999px; border: 0;
    background: none; color: #374151; font-size: 16px; line-height: 1; font-weight: 800;
}
.grid-zoom-btn:hover:not(:disabled) { background: #eff6ff; color: #1d4ed8; }
.grid-zoom-btn:disabled { color: #d1d5db; cursor: default; }
.grid-zoom-pct {
    min-width: 40px; text-align: center; font-size: 11.5px; font-weight: 700;
    color: #4b5563; font-variant-numeric: tabular-nums;
}
/* A width, not a flex-basis: the cluster round it sizes to its contents, and
   a range input's own width is a modest 130px - a basis on the wrapper had
   nothing to grow into and the slider sat at that 130px whatever was asked.
   max-width lets it give way on a narrow row rather than push past the edge. */
.grid-slide-wrap {
    display: inline-flex; align-items: center;
    width: 390px; max-width: 100%; min-width: 180px;
}
.grid-slide { width: 100%; margin: 0; accent-color: #2563eb; cursor: pointer; }
.grid-slide:disabled { opacity: .4; cursor: default; }

/* What the grid is showing, in words. Quiet with no filter on, and coloured
   the moment one is - the state worth noticing is the narrowed sheet. */
.col-state { font-size: 12.5px; font-weight: 600; color: #9ca3af; }
.col-state[data-on="1"] { color: #15803d; }
.col-state[data-on="1"]::before {
    content: ""; display: inline-block; vertical-align: middle;
    width: 7px; height: 7px; border-radius: 999px; background: #22c55e;
    margin-right: 6px;
}

/* `hidden` alone is not enough here: the grid sets a display on th and td and
   .btn sets one on the button, and a class beats the browser's bare [hidden].
   Both need saying explicitly or the hidden cells and the Show all button
   stay on screen. */
.plan-grid [hidden],
.col-filter [hidden] { display: none !important; }

/* Filtered down to one person, the grid no longer needs to fill the page -
   letting it shrink to its own width is what makes the column readable. */
.plan-grid.is-col-filtered { width: auto; min-width: 0; }

/* A label that only a screen reader reads. The picker is understood from the
   names in it, so on screen the word would just be noise beside the button. */
.sr-only {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}


/* ---- on call & stand by --------------------------------------------------
   The block at the foot of every Cycle sheet of the workbook: two dosimetrists
   named on call, and a few more standing behind them. It is one list for the
   whole cycle rather than anything belonging to a day, so it sits under the
   grid instead of in it - which is where the workbook puts it too.

   Drawn as a label and a row of boxes, the shape it has on the sheet, so that
   somebody who has read the workbook recognises it without being told.
   ========================================================================= */
/* The foot of the sheet: on call on the left, missing hours on the right,
   half each, with room above them so they read as the sheet's foot rather
   than as another row of it. One under the other where the screen is narrow. */
.plan-foot-row {
    display: grid; grid-template-columns: 1fr 1fr; gap: 16px;
    align-items: stretch; margin-top: 26px;
}
.plan-foot-row > .card { min-width: 0; margin-bottom: 0; }
@media (max-width: 900px) {
    .plan-foot-row { grid-template-columns: 1fr; }
    .plan-foot-row > .missing-card { height: auto; min-height: 0; }
    .plan-foot-row > .missing-card .missing-body { max-height: 320px; }
}

.oncall-card { padding: 16px 20px; }

/* ---- missing hours ------------------------------------------------------ */
/* No taller than the on-call card beside it: the card contributes nothing
   to the row's height (height: 0) and then stretches to fill what the other
   card set, with its list scrolling inside. Two lines to a row in the list. */
.missing-card {
    padding: 16px 20px; box-sizing: border-box;
    height: 0; min-height: 100%;
    display: flex; flex-direction: column; overflow: hidden;
}
.missing-card .oncall-head { flex: none; }
.missing-count {
    margin-left: auto; font-size: 12px; font-weight: 700;
    padding: 2px 9px; border-radius: 999px; background: #fef3c7; color: #92400e;
}
.missing-count.is-clear { background: #dcfce7; color: #166534; }
.missing-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.missing-kind {
    font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
    color: #6b7280; margin: 10px 0 5px;
}
.missing-kind:first-child { margin-top: 0; }
.missing-list {
    list-style: none; margin: 0; padding: 0;
    display: grid; grid-template-columns: 1fr 1fr; gap: 4px;
}
.missing-list li { min-width: 0; }
.missing-item {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    padding: 5px 9px; border-radius: 6px; cursor: pointer;
    border: 1px solid #fde68a; background: #fffbeb; color: #78350f; font-size: 12.5px;
    font: inherit; font-size: 12.5px; text-align: left; width: 100%;
}
.missing-item:hover { background: #fef3c7; border-color: #fcd34d; }
.missing-item .m-date { font-weight: 700; min-width: 78px; }
.missing-item .m-who  { font-weight: 600; }
.missing-item .m-why  { color: #92400e; font-size: 11.5px; margin-left: auto; }
.missing-all-in { font-size: 12.5px; color: #166534; font-weight: 600; margin: 0; }
/* The cell a line in the list points at, lit for a moment. */
.plan-grid td.plan-cell.is-found { animation: found-lit 1.6s ease-out; }
@keyframes found-lit {
    0%, 55% { box-shadow: inset 0 0 0 3px #d97706; }
    100%    { box-shadow: none; }
}
@media print {
    .missing-card { break-inside: avoid; }
    .missing-body { max-height: none; overflow: visible; }
}
.oncall-head {
    display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
    padding-bottom: 10px; margin-bottom: 12px; border-bottom: 1px solid #e5e7eb;
}
.oncall-head h2 { font-size: 15px; margin: 0; }
.oncall-sub { font-size: 12px; color: #6b7280; }

/* Label and boxes on one line, wrapping to two where the screen is narrow.
   The label is a fixed width so the two rows' boxes start at the same place -
   a ragged left edge would read as two unrelated controls. */
.oncall-line {
    display: flex; align-items: center; gap: 10px;
    flex-wrap: wrap; margin-bottom: 8px;
}
.oncall-label {
    flex: 0 0 auto; min-width: 104px;
    font-size: 12.5px; font-weight: 700; color: #374151;
}
/* min-width: 0 is what lets the boxes wrap at all. A flex item will not shrink
   below its contents by default, so without it the row keeps its full width,
   its boxes never wrap, and on a phone the second one hangs off the card. */
.oncall-row {
    display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
    flex: 1 1 240px; min-width: 0;
}

.oncall-pick {
    font: inherit; font-size: 12.5px; padding: 5px 8px;
    flex: 0 1 auto; min-width: 0; width: 148px;
    border: 1px solid #d1d5db; border-radius: 6px; background: #fff; color: #111827;
}
/* Nothing chosen yet is the ordinary state of the spare box at the end of the
   row, so it is quiet rather than looking like something left undone. Read from
   the box itself rather than from a class, so it follows a pick immediately
   instead of waiting for the sheet to be saved and drawn again. */
.oncall-pick:not(:has(option:checked:not([value=""]))) {
    color: #9ca3af; border-style: dashed; border-color: #cbd5e1;
}
.oncall-pick:focus {
    outline: none; border-color: #2563eb; box-shadow: 0 0 0 3px rgba(37, 99, 235, .15);
}

/* Changed but not written - the same amber the grid's own pending cells use,
   on the whole row because the row is what gets saved. */
.oncall-row.is-pending .oncall-pick { border-color: #f59e0b; background: #fffbeb; }

/* Read-only, for everybody who is not running the roster. Who to ring is worth
   knowing whoever you are, so it is shown rather than hidden. */
.oncall-chip {
    font-size: 12.5px; font-weight: 700; color: #1f2937;
    background: #f1f5f9; border: 1px solid #e2e8f0;
    padding: 3px 9px; border-radius: 999px;
}
.oncall-none { font-size: 12.5px; color: #9ca3af; }

.oncall-note {
    margin: 10px 0 0; font-size: 11.5px; line-height: 1.45; color: #6b7280;
}

/* On a phone the label goes above its boxes rather than beside them: 104px of
   a 390px screen spent on the words leaves less than one box's width for the
   names, and two boxes side by side is the shape worth keeping. */
@media screen and (max-width: 560px) {
    .oncall-line { align-items: flex-start; margin-bottom: 12px; }
    .oncall-label { flex: 1 1 100%; min-width: 0; }
    .oncall-pick { width: auto; flex: 1 1 132px; max-width: 100%; }
}

/* On paper the boxes are not boxes: the sheet says who is on call, and a
   dropdown arrow printed beside a name is furniture nobody needs. The empty
   spare goes entirely - it is a way of adding a name, not a fact about the
   cycle. */
@media print {
    .oncall-card { padding: 0; box-shadow: none; margin-top: 10px; }
    .oncall-head { border-bottom: 1px solid #cbd5e1; }
    .oncall-pick {
        -webkit-appearance: none; appearance: none;
        border: 0; background: none; padding: 0 10px 0 0; min-width: 0;
        font-weight: 700; color: #111827;
    }
    .oncall-pick:not(:has(option:checked:not([value=""]))) { display: none; }
    .oncall-note { display: none; }
}

/* ---- where the batch panel sits ---------------------------------------
   Above the sheet, or beside it. Beside gives the panel and the sheet the
   screen at once - a batch can be set up while the cells it will land on are
   in view - and pays for it with a quarter of the width. Above is the sheet
   at full width, and is what every load opens with.

   Here rather than in machine.css because both rosters have the control and
   both load this sheet; it was the machine sheet's alone for a day.

   The two are one block in the markup, with the panel first, so "above" is
   nothing but the document's own order. "Beside" is a two-column grid on
   the same block with the panel sent to the second column; the panel's own
   fields already reflow to one column at that width, so nothing inside it
   needs a second rule.
   ========================================================================= */
.mach-main[data-layout="side"] {
    display: grid; gap: 16px; align-items: start;
    /* Seven-tenths and three-tenths, and the panel never under 260px: the
       sheet scrolls sideways in whatever is left, so a window that is not
       maximised still keeps the layout that was picked. */
    grid-template-columns: minmax(0, 7fr) minmax(260px, 3fr);
}
.mach-main[data-layout="side"] .mach-left   { order: 1; min-width: 0; }
/* The block holding batch assign and the week copier is the second column.
   It, not the panels, is the grid item - two panels each taking a column is
   how the sheet lost its own. */
.mach-main[data-layout="side"] .mach-panels {
    order: 2;
    /* Stays put while the page scrolls, so it is beside the sheet however far
       down the sheet has been read. */
    position: sticky; top: 10px;
    /* A grid item will not go narrower than its contents unless told it may,
       and the panel has to: the column is a quarter of the screen. */
    min-width: 0;
    /* Two long panels open at once can outrun the window; the block scrolls
       rather than the page losing the foot of the second one. */
    max-height: calc(100vh - 20px); overflow-y: auto;
}
.mach-main[data-layout="side"] .mach-panels > .card:last-child { margin-bottom: 0; }
.mach-main[data-layout="side"] .copier-grid { grid-template-columns: 1fr; }
/* Inside the narrow panel, one column of fields. The Who list spans two of
   them on the wide layout, and a span in a one-column grid conjures a second
   column out of nothing - which is exactly the overflow this rules out. The
   two boxes with fixed widths give them up for the same reason. */
.mach-main[data-layout="side"] #batch-panel .batch-grid { grid-template-columns: 1fr; }
.mach-main[data-layout="side"] #batch-panel .batch-who  { grid-column: auto; }
.mach-main[data-layout="side"] #batch-panel .batch-typed { width: 100%; max-width: 200px; }
.mach-main[data-layout="side"] #batch-panel #batch-count { min-width: 0; text-align: left; }

/* The two buttons at the foot of the panel.

   On the wide layout the wrapper is invisible - display: contents - and the
   buttons sit in the footer's row exactly as they did. Beside the sheet the
   footer stacks: the skip choices, then the count, then the two buttons on
   one row of their own at the very bottom, each taking half. Half a row is
   enough for "Clear options" and "Apply to grid" at any browser zoom short of
   the one that stacks the layout anyway, and the pair cannot wrap because
   the row is the wrapper's, not the footer's. */
.batch-actions { display: contents; }
.mach-main[data-layout="side"] #batch-panel .batch-foot {
    flex-direction: column; align-items: stretch; gap: 10px;
}
.mach-main[data-layout="side"] #batch-panel .batch-foot .spacer { display: none; }
.mach-main[data-layout="side"] #batch-panel .batch-actions {
    display: flex; gap: 8px; flex-wrap: nowrap;
}
.mach-main[data-layout="side"] #batch-panel .batch-actions .btn {
    flex: 1 1 0; min-width: 0; justify-content: center;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* A phone is the one screen with no room for two columns at all; there the
   panel goes back above the sheet whatever was picked, and the control that
   picks it goes. A window merely not maximised keeps the side layout. */
@media screen and (max-width: 640px) {
    .mach-main[data-layout="side"] { display: block; }
    .mach-main[data-layout="side"] .mach-panels {
        position: static; max-height: none; overflow: visible; margin-bottom: 16px;
    }
    /* The control's own rule below is the later one and would win without
       this - the picker was staying on screen at phone width. */
    .layout-pick { display: none !important; }
}

/* The control itself: two words in a pill, the way the notes tick is. */
.layout-pick {
    /* Last in the toolbar, and when the toolbar wraps it goes to the right of
       its own line rather than the left - "at the right of the page" was the
       whole of where it was asked to be. */
    margin-left: auto;
    display: inline-flex; align-items: center; gap: 2px;
    padding: 3px 4px 3px 10px; border: 1px solid #d1d5db; border-radius: 999px;
    background: #fff; font-size: 12px;
}
.layout-pick-head {
    font-weight: 700; color: #6b7280; margin-right: 6px;
    text-transform: uppercase; letter-spacing: .05em; font-size: 10.5px;
}
.layout-opt {
    display: inline-flex; align-items: center; padding: 3px 10px;
    border-radius: 999px; color: #4b5563; font-weight: 600; cursor: pointer;
}
.layout-opt input { position: absolute; opacity: 0; width: 0; height: 0; }
.layout-opt:hover { background: #f1f5f9; }
.layout-opt.is-on { background: #1d4ed8; color: #fff; }
.layout-opt input:focus-visible + span { text-decoration: underline; }
