/* =====================================================================
   style.css — the whole interface's appearance.

   A small utility stylesheet, hand-written: no build step and no framework.
   Authored in the Tailwind idiom (compose from single-purpose classes) but
   with a deliberately narrow vocabulary, per Refactoring UI: a constrained
   scale beats arbitrary values.

   Utilities carry all appearance. The semantic hook classes — cell, issue,
   pick, csmrow, sched — carry none and exist for script.js and the tests to
   select on.

   SCALES
     space   0 .. 8   ->  0 3 6 9 12 16 22 28 36 px
     text    sm md lg xl    -> 14 15 17 20 px   (14px is the floor: nothing
             in the interface is smaller, so every label and table header
             reads at body size)
     ink     1 (primary) 2 (secondary) 3 (muted) — three levels, no more
   ===================================================================== */
:root{
  /* neutrals, warm-tinted so the page reads like the paper it replaces */
  --bg:#f2f0ec;   --surface:#fff;  --surface2:#faf9f7;  --surface3:#f4f2ee;
  /* three ink levels, all >=4.5:1 on every surface below: #6a655d is the
     lightest warm grey that still clears AA on the closed-cell grey. */
  --ink1:#1c1b19; --ink2:#57534d;  --ink3:#6a655d;
  /* Labels sit only on white, --surface2 and --surface3, so they can go a step
     lighter than --ink3 and still clear 4.5:1 on all three (5.06 / 4.81 /
     4.52). Not for the closed-cell grey, where it would fail. */
  --ink-weak:#736e66;
  --line:#e4e0d9; --line2:#cfcac1;
  /* control borders must be identifiable, so they get their own darker
     token (>=3:1) while --line2 stays for decorative separators. */
  /* --quiet is the muted-control grey: a disabled chevron, the drag handle,
     a disabled menu item. It was #a5a096, which cleared 3:1 on white but only
     2.29:1 once the week selector moved off the white header onto the warm
     page ground. #8a8478 is 3.26:1 there and 3.71:1 on white, and still reads
     as plainly muted beside --ink2. */
  --control:#928c81; --quiet:#8a8478;

  /* one accent, used only for actionable and positive things */
  --brand:#2f6f4f; --brand-ink:#1e4a34; --brand-bg:#e9f1ec; --brand-line:#b7d3c4;
  /* status: each pairs an ink, a wash and a border so none relies on hue alone */
  --warn:#7d5a10;  --warn-bg:#fdf5e2; --warn-line:#e9d69c;
  --err:#9d2f2d;   --err-bg:#fcecea;  --err-line:#ebbcb8;
  /* "Off" — a day nobody is working. Red, because that is what this app
     already used for a blocked day on the Roster, but a duller and warmer red
     than --err so an actual error still reads as the more urgent of the two.
     The two tints never appear in the same view. Contrast: white on --off is
     6.33:1, --off-ink on --off-bg is 6.23:1 and on white 7.29:1. */
  --off:#9a463d;   --off-bg:#f9eae5;  --off-line:#e6bfb6; --off-ink:#8c3f36;
  /* the highlighter yellow from the CSM row of the paper sheet */
  --csm:#fdf3c2;  --csm-soft:#fefbe8; --csm-line:#e6d894; --csm-ink:#5c4a05;
  /* the CSM row's "available" cell: the green tint carrying the row's warmth */
  --csm-yes:#eef2df;
  --closed:#eae7e1;

  --s0:0;   --s1:3px;  --s2:6px;  --s3:9px;  --s4:12px;
  --s5:16px;--s6:22px; --s7:28px; --s8:36px;

  --r1:4px; --r2:6px; --r3:8px; --rf:999px;
  --sh1:0 1px 2px rgba(28,27,25,.05);
  --sh2:0 1px 3px rgba(28,27,25,.09), 0 4px 12px rgba(28,27,25,.05);
  --ring:0 0 0 3px var(--brand-bg);

  /* the select caret, drawn so it can be hidden and restored with the border
     rather than left on the page as 70 permanent chevrons */
  --caret: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='%236a655d' \
stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");

  /* Inter comes from the CDN link in the head. It is not guaranteed — no
     network, no webfont — so the fallback is the platform's own UI face rather
     than a generic sans, and the design does not depend on Inter's metrics.
     The product name gets a warm serif instead, so the wordmark reads as a
     name and not as more UI. */
  --font:"Inter",system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI Variable Text",
    "Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
  --font-brand:Georgia,"Iowan Old Style","Palatino Linotype","Times New Roman",
    serif;
  --mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
}

/* ---------- reset ---------------------------------------------------- */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{margin:0;background:var(--bg);color:var(--ink1);
  font:14px/1.55 var(--font);-webkit-font-smoothing:antialiased}
h1,h2,h3,p{margin:0}
button{font:inherit;color:inherit;background:none;border:0;padding:0;cursor:pointer}
input,select,textarea{font:inherit;color:inherit}
/* Native checkboxes read blue out of the box, which is the one hue this
   interface never uses. Green is "yes" everywhere else here, so it is green
   here too. */
input[type=checkbox]{accent-color:var(--brand)}
table{border-collapse:collapse;width:100%}
code{font:14px/1 var(--mono);background:var(--surface3);padding:2px 5px;border-radius:3px}
:focus-visible{outline:2px solid var(--brand);outline-offset:1px}
[hidden]{display:none!important}

/* ---------- layout ---------------------------------------------------
   The vocabulary below is deliberately kept whole even where a class is
   momentarily unused: pruning "dead" utilities has twice broken layout
   when later markup reached for one again. The sheet is small; the rules
   are cheaper than the bugs.
--------------------------------------------------------------------- */
.block{display:block}       .flex{display:flex}         .grid{display:grid}
.hidden{display:none!important} .wrap{flex-wrap:wrap}
.ic{align-items:center}       .is{align-items:flex-start} .jc{justify-content:center}
.col{flex-direction:column}
.w-full{width:100%}
.grow{flex:1}                   .min0{min-width:0}     .ovx{overflow-x:auto}
.nowrap{white-space:nowrap}
.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:280px} .gap-1{gap:var(--s1)} .gap-2{gap:var(--s2)} .gap-3{gap:var(--s3)}
.gap-5{gap:var(--s5)}
.gap-4{gap:var(--s4)} .gap-6{gap:var(--s6)}

.p-0{padding:0}                                   .p-5{padding:var(--s5)}
.px-7{padding-left:var(--s7);padding-right:var(--s7)}
.py-4{padding-top:var(--s4);padding-bottom:var(--s4)}
.py-5{padding-top:var(--s5);padding-bottom:var(--s5)}
.py-6{padding-top:var(--s6);padding-bottom:var(--s6)} .pt-5{padding-top:var(--s5)} .mt-3{margin-top:var(--s3)} .mt-5{margin-top:var(--s5)} .mt-6{margin-top:var(--s6)} .mb-1{margin-bottom:var(--s1)} .mb-2{margin-bottom:var(--s2)} .mb-3{margin-bottom:var(--s3)} .mb-5{margin-bottom:var(--s5)} .mb-6{margin-bottom:var(--s6)}

/* ---------- type ----------------------------------------------------- */
.t-xs{font-size:14px} .t-sm{font-size:14px} .t-md{font-size:14px}
.t-lg{font-size:15px} .fw-m{font-weight:500} .fw-sb{font-weight:600} .fw-b{font-weight:700}
.lh-t{line-height:1.25}
.ink1{color:var(--ink1)} .ink2{color:var(--ink2)} .ink3{color:var(--ink3)}
.c-brand{color:var(--brand-ink)} .c-warn{color:var(--warn)} .c-err{color:var(--err)}
.tnum{font-variant-numeric:tabular-nums}
.mono{font-family:var(--mono)}
.italic{font-style:italic}

/* ---------- surfaces ------------------------------------------------- */
.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--r3);
  box-shadow:var(--sh1);padding:var(--s7)}
.bg-surface{background:var(--surface)} .bg-csm{background:var(--csm)} .r1{border-radius:var(--r1)} .r2{border-radius:var(--r2)} .bt{border-top:1px solid var(--line)}
.bb{border-bottom:1px solid var(--line)}

/* The one place the brand serif is used. Just the store's name now: the way
   back to the schedules is the New schedules tab, so the name does nothing
   when pressed and looks like nothing that could be. */
.wordmark{font-family:var(--font-brand);font-size:19px;font-weight:400;
  letter-spacing:.01em;color:var(--ink1)}

/* A section label, not a headline: quiet, so the data reads first. */
.label{font-size:14px;font-weight:500;text-transform:uppercase;letter-spacing:.05em;
  color:var(--ink-weak)}

/* ---------- controls ------------------------------------------------- */
.field{font:inherit;font-size:14px;padding:8px 10px;border:1px solid var(--control);
  border-radius:var(--r1);
  background:var(--surface);color:var(--ink1);transition:border-color .12s,box-shadow .12s}
.field:hover{border-color:var(--ink3)}
.field:focus{outline:none;border-color:var(--brand);box-shadow:var(--ring)}

.btn{display:inline-flex;align-items:center;gap:var(--s3);padding:9px 15px;font-size:14px;
  border:1px solid var(--control);border-radius:var(--r2);background:var(--surface);
  color:var(--ink1);font-weight:500;box-shadow:var(--sh1);
  transition:background .12s,border-color .12s}
.btn:hover{background:var(--surface2);border-color:var(--ink3)}
.btn:active{background:var(--surface3)}
.btn:disabled{opacity:.45;cursor:not-allowed;box-shadow:none}
.btn:disabled:hover{background:var(--surface);border-color:var(--control)}
/* A disabled primary kept its brand green and lost nearly half its opacity,
   which on white reads as a broken button rather than an unavailable one.
   Unavailable is neutral. */
.btn-primary:disabled,
.btn-primary:disabled:hover{background:var(--surface3);border-color:var(--control);
  color:var(--ink3);opacity:1}
.btn-primary{background:var(--brand);border-color:var(--brand);color:#fff}
.btn-primary:hover{background:var(--brand-ink);border-color:var(--brand-ink)}
.btn-quiet{border-color:transparent;box-shadow:none;background:none;color:var(--ink2)}
.btn-quiet:hover{background:var(--surface3);border-color:var(--line);color:var(--ink1)}
.btn-sm{padding:7px 12px;font-size:14px;gap:var(--s2)}
.btn-danger:hover{background:var(--err-bg);border-color:var(--err-line);color:var(--err)}

.ta{font:inherit;font-size:14px;width:100%;min-height:80px;padding:var(--s4);
  resize:vertical;
  border:1px solid var(--control);border-radius:var(--r2);background:var(--surface)}
.ta:focus{outline:none;border-color:var(--brand);box-shadow:var(--ring)}

/* Toggle buttons carry their state on aria-pressed, so the DOM is the
   source of truth and nothing depends on hue alone. */
.tgl{padding:7px 11px;border:1px solid var(--control);border-radius:var(--r1);
  background:var(--surface);color:var(--ink2);font-size:14px;font-weight:500;
  min-width:34px}
.tgl:hover{border-color:var(--ink3);color:var(--ink1)}
/* A pressed day toggle means the same thing on the Roster and in the
   availability modal — this day is off — so it gets one treatment, not a
   green one here and a red one there. */
.tgl[aria-pressed=true]{background:var(--off);border-color:var(--off);color:#fff;
  font-weight:600}
/* No opacity here on purpose. The pressed rule sets white text, and a 40%
   near-white hatch painted over it made the day letter vanish completely — a
   toggle with no label on it. Disabled state is carried by real colours
   instead, so the contrast is knowable. */
.tgl:disabled{cursor:not-allowed;color:var(--ink3);border-color:var(--line2);
  background:repeating-linear-gradient(45deg,var(--surface2),var(--surface2) 4px,
    var(--surface3) 4px,var(--surface3) 8px)}
/* Off for a reason this modal cannot change — approved time off, or a standing
   Roster block. The tint of the same family, so weight of fill says whether it
   can be cleared here: solid means set here, tinted means set elsewhere. */
.tgl[aria-pressed=true]:disabled{background:var(--off-bg);color:var(--off-ink);
  border-color:var(--off-line);font-weight:600}
.tgl:disabled:hover{border-color:var(--line2);color:var(--ink3)}
.tgl[aria-pressed=true]:disabled:hover{color:var(--off-ink)}

/* ---------- tabs ----------------------------------------------------- */
.tab{padding:12px 16px;font-size:15px;color:var(--ink2);font-weight:500;
  border-bottom:2px solid transparent;margin-bottom:-1px}
.tab:hover{color:var(--ink1)}
.tab[aria-selected=true]{color:var(--brand-ink);border-bottom-color:var(--brand)}

/* ---------- tables --------------------------------------------------- */
.th{padding:10px 12px;text-align:left;font-size:14px;font-weight:500;
  text-transform:uppercase;letter-spacing:.04em;color:var(--ink-weak);
  background:var(--surface2);border-bottom:1px solid var(--line2);white-space:nowrap}
/* Rows separate with a single hairline instead of a full box — fewer borders,
   per Refactoring UI, so the numbers carry the structure. */
.td{padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:middle}
.tr:hover .td{background:var(--surface2)}
.tfoot .th{background:var(--surface);border-top:1px solid var(--line2);border-bottom:0;
  color:var(--ink1);font-size:15px;text-transform:none;letter-spacing:0}

/* ---------- the schedule grid ---------------------------------------
   Hook class is `sched`. A utility named `grid` means display:grid, and
   putting that on a <table> splits thead and tbody into separate anonymous
   table boxes so the header stops aligning with the columns.
--------------------------------------------------------------------- */
.sched{min-width:1260px;table-layout:fixed}
.sched th,.sched td{border:1px solid var(--line)}
.sched thead th{padding:var(--s3) var(--s2);text-align:center;font-size:14px;font-weight:600;
  text-transform:uppercase;letter-spacing:.05em;color:var(--ink3);
  background:var(--surface2)}
.sched thead th small{display:block;font-size:14px;font-weight:400;
  text-transform:none;letter-spacing:0;color:var(--ink3)}
.sched thead th.today{color:var(--brand-ink);background:var(--brand-bg)}
/* the availability grid keeps a shift column; the build grid does not — its
   label is printed inside every cell, as on the paper sheet */
.slot{width:138px;padding:var(--s3) var(--s4);vertical-align:top;
  background:var(--surface2)}
.csmrow .slot{background:var(--csm)}
/* 180, not 160: shift times are written in full now, and "10:30am-7:00pm" is
   wider than "10:30a–7p" was. Under 180 the time menu cut its last letters
   off; with the side panel open the grid scrolls sideways instead. */
.cell{position:relative;width:180px;padding:var(--s3);vertical-align:top}
.csmrow .cell{background:var(--csm-soft)}
.sched tbody tr:hover .cell{background:var(--surface2)}
.sched tbody tr.csmrow:hover .cell{background:var(--csm)}
.cell.bad{background:var(--err-bg)!important;box-shadow:inset 0 0 0 2px var(--err)}
.cell.dis{background:var(--closed);cursor:pointer;padding:0}

/* The person is the answer to "who works this shift", so it gets the weight;
   the time above it is the printed label, so it stays quiet.

   A filled control is BORDERLESS at rest. Seventy outlined boxes read as a
   form to fill in; the point of a posted schedule is to read as names and
   times. The border is kept at 1px transparent so revealing it on approach
   never moves anything. An EMPTY control keeps its outline — that one is
   still a question, and it should look like one. */
.cell-time,.cell-name{-webkit-appearance:none;appearance:none;
  background-color:transparent;background-image:none;background-repeat:no-repeat;
  background-position:right 7px center;background-size:9px 6px}
.cell-time{width:100%;padding:6px 20px 6px 8px;border:1px solid transparent;
  border-radius:var(--r1);font-size:14px;
  font-family:var(--mono);color:var(--ink2)}
.cell-time.custom{color:var(--brand-ink);font-weight:600}
.cell-name{width:100%;margin-top:var(--s1);padding:8px 22px 8px 10px;
  border:1px solid transparent;border-radius:var(--r1);
  font-size:14px;font-weight:600;color:var(--ink1)}
.cell-name.empty{color:var(--ink3);font-weight:400}
/* One control at a time. Hovering the cell used to bring both the time and
   the person forward together, which meant pointing at one of them lit up
   two — so the reveal is scoped to the control actually under the cursor. */
.cell-time.empty,.cell-name.empty{border-color:var(--control);
  background-color:var(--surface);background-image:var(--caret)}
.cell-time:hover,.cell-name:hover{border-color:var(--ink3);
  background-color:var(--surface);background-image:var(--caret)}
/* Prefixed with .sched purely to outrank the :hover rules above, which would
   otherwise repaint the border of the control you are actually focused on. */
.sched .cell-time:focus,.sched .cell-name:focus{outline:none;
  border-color:var(--brand);background-color:var(--surface);
  background-image:var(--caret);box-shadow:var(--ring)}
.cell-timeinput{width:100%;padding:6px 8px;border:1px solid var(--brand);
  border-radius:var(--r1);background:var(--surface);font-size:14px;
  font-family:var(--mono)}
.cell-timeinput:focus{outline:none;box-shadow:var(--ring)}

/* Always visible, not hover-revealed: a control nobody can find is a control
   nobody uses. Quiet by default, loud on hover. */
/* The cell's top line: the shift label, then the lock and close buttons. The
   label reads first because it is the one thing in the cell that is fixed. */
.cell-top{display:flex;align-items:center;gap:var(--s1);margin-bottom:var(--s2)}
/* 24x24 is the floor for a hit target here — the glyph inside is smaller, but
   the button someone has to actually hit is not. */
.cell-btn{flex-shrink:0;display:inline-flex;align-items:center;
  justify-content:center;width:24px;height:24px;padding:0;
  border-radius:var(--r1);color:var(--ink3);line-height:1}
.cell:hover .cell-btn,.extra:hover .cell-btn{color:var(--ink2)}
/* Full muted-ink contrast at rest, not a faint hover-only glyph: this is the
   control that closes a slot, and one nobody can see is one nobody uses. */
.cell-x{font-size:17px}
.cell-x:hover{color:var(--err);background:var(--err-bg)}
.cell-closed{display:block;width:100%;padding:14px 4px;text-align:center;
  font-size:14px;letter-spacing:.1em;color:var(--ink3)}
.cell-closed:hover{background:var(--line);color:var(--ink1)}

/* the shift label, printed in each cell so no column down the side is needed */
.cell-label{flex:1;min-width:0;font-size:14px;font-weight:500;color:var(--ink3);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.csmrow .cell-label{color:var(--csm-ink)}

/* ---------- extra cells ----------------------------------------------
   Up to five per day, added from the grid rather than the template, so a
   one-off shift never becomes a recurring slot row.
--------------------------------------------------------------------- */
.extracell{border:1px solid var(--line);border-top:2px solid var(--line2);
  padding:var(--s2) var(--s3);vertical-align:top;background:var(--surface2)}
.extra{margin-bottom:var(--s2);padding:var(--s2);border:1px dashed var(--line2);
  border-radius:var(--r1);background:var(--surface)}
.extra .cell-label{color:var(--brand-ink)}
/* Quiet until you approach it, but never invisible: a control nobody can find
   is a control nobody uses. */
.addextra{display:flex;align-items:center;justify-content:center;width:100%;
  min-height:24px;padding:3px 0;border:1px dashed var(--line2);
  border-radius:var(--r1);color:var(--ink3);font-size:14px;line-height:1.1;
  opacity:.5;transition:opacity .12s,border-color .12s,background .12s}
.extracell:hover .addextra{opacity:1}
.addextra:hover{background:var(--brand-bg);border-color:var(--brand);
  color:var(--brand-ink);opacity:1}
.addextra:focus-visible{opacity:1}
.addextra:disabled{opacity:.3;cursor:not-allowed}

/* ---------- read-only schedule (History) ------------------------------ */
.sched.ro{table-layout:fixed;min-width:900px}
.cell-ro{padding:var(--s3);vertical-align:top;text-align:center}
.ro-time{display:block;font-size:14px;font-family:var(--mono);color:var(--ink3)}
.ro-label{display:block;font-size:14px;font-weight:600;letter-spacing:.04em;
  text-transform:uppercase;color:var(--ink3)}
.ro-name{display:block;margin-top:2px;font-size:14px;font-weight:600;color:var(--ink1)}
.ro-closed{display:block;padding:10px 0;font-size:14px;letter-spacing:.1em;
  color:var(--ink3)}
.ro-extra{padding:var(--s2) 0;border-top:1px dashed var(--line)}
.ro-extra:first-child{border-top:0}

/* ---------- badges & status ------------------------------------------ */
.badge{display:inline-block;padding:2px 9px;border-radius:var(--rf);font-size:14px;
  text-transform:none;letter-spacing:0;
  font-weight:500;border:1px solid var(--line);background:var(--surface);color:var(--ink3)}
.badge-csm{background:var(--csm);border-color:var(--csm-line);color:var(--csm-ink)}
.badge-count{background:var(--surface3);color:var(--ink2)}
.badge-err{background:var(--err-bg);border-color:var(--err-line);color:var(--err)}
.badge-warn{background:var(--warn-bg);border-color:var(--warn-line);color:var(--warn)}
.badge-ok{background:var(--brand-bg);border-color:var(--brand-line);
  color:var(--brand-ink)}

/* An icon plus a word plus a colour: three cues, so colour is never the only
   thing distinguishing an error from a warning. */
.issue{display:flex;gap:var(--s4);align-items:flex-start;padding:10px 12px;
  border-radius:var(--r1);border-left:3px solid transparent;font-size:14px;
  margin-bottom:3px}
.issue-icon{flex-shrink:0;font-size:14px;line-height:1.6;width:11px;text-align:center}
.issue-tag{flex-shrink:0;width:46px;font-size:14px;font-weight:700;
  text-transform:uppercase;letter-spacing:.06em;line-height:1.6}
.issue.error{background:var(--err-bg);border-left-color:var(--err);color:var(--err)}
.issue.warn{background:var(--warn-bg);border-left-color:var(--warn);color:var(--warn)}
.issue.info{background:var(--surface3);border-left-color:var(--line2);color:var(--ink2)}

/* ---------- availability picker -------------------------------------- */
/* The CELL carries the state colour, not the button inside it.

   A button is inline-block and sizes to its own padding, so as soon as a row
   was taller than the button — a shift label wrapping to two lines is enough —
   the cell's own background showed through as a band above and below every
   hatched, amber and shaded state. height:100% cannot fix it either: the td's
   height is auto, so there is nothing for the percentage to resolve against.
   Painting the td removes the question, and it is the same answer the closed
   cell in the build grid needed. */
.pcell{padding:0}
/* Three states, and the one that needs no colour is the common one. Most
   cells for most people are 'open' — nobody has said anything — so open is
   plain paper and only a stated preference is tinted. Green for the shifts
   somebody usually works, red for the ones to avoid: the two hues the rest of
   the interface already uses for yes and no. */
.pcell.p-open{background:var(--surface)}
.pcell.p-prefer{background:var(--brand-bg)}
.pcell.p-avoid{background:var(--off-bg)}
/* This week's grid uses the same three states as Regular shifts, so the same
   classes: the meanings are the same now, which is what sharing a name should
   say. On top of them, time off — avoid's ground, with an × — which is not
   the manager's to change here; it is set on the Time off page, on dates. */
.pcell.p-off{background:var(--off-bg)}
/* This week disagrees with the regular shifts here. A corner mark rather than
   another ground colour: the ground already carries the state, and a second
   colour on top of it would compete with it. */
.pcell.p-diff{position:relative}
.pcell.p-diff::after{content:'';position:absolute;top:3px;right:3px;
  width:0;height:0;border-top:6px solid var(--ink3);
  border-left:6px solid transparent}
.p-off .pick:disabled{color:var(--off-ink)}
/* the CSM row keeps the sheet's highlighter warmth, as it does in the build
   grid, carried into the green so the row is still identifiable as a row */
.csmrow .pcell.p-prefer{background:var(--csm-yes)}
.csmrow .pcell.p-open{background:var(--csm-soft)}
.pcell.p-closed{background:repeating-linear-gradient(45deg,
  var(--surface2),var(--surface2) 4px,var(--surface3) 4px,var(--surface3) 8px)}
.csmrow .pcell.p-closed{background:repeating-linear-gradient(45deg,
  var(--csm-soft),var(--csm-soft) 4px,var(--surface3) 4px,var(--surface3) 8px)}

/* An open cell carries no mark at all, so the grid reads as the few things
   somebody has actually said rather than 35 answers they never gave. */
.pick{display:block;width:100%;padding:14px 12px;font-size:14px;
  background:none;color:var(--ink2);font-weight:600}
.p-prefer .pick{color:var(--brand-ink)}
.p-avoid  .pick{color:var(--off-ink)}
.pick:disabled{cursor:not-allowed;color:transparent}
/* Hover keeps the state colour and rings the cell instead, so approaching a
   cell never makes it look like a different state. */
.pcell:hover .pick:not(:disabled){color:var(--ink1)}
.pcell:has(.pick:not(:disabled)):hover{box-shadow:inset 0 0 0 2px var(--brand-line)}
.pick:focus-visible{outline:2px solid var(--brand);outline-offset:-2px}
/* A standing line above the daily sheet. Quieter than an issue row, which
   belongs to a list, and not a toast, which is gone in two seconds — this is
   a fact about the sheet you are looking at and stays as long as it is true. */
.note{padding:10px 14px;border-radius:var(--r2);font-size:14px;
  background:var(--surface2);border:1px solid var(--line);color:var(--ink2)}
.note-warn{background:var(--warn-bg);border-color:var(--warn-line);
  color:var(--warn)}

.picklegend{display:flex;flex-wrap:wrap;gap:var(--s5);margin-top:var(--s4);
  font-size:14px;color:var(--ink2)}
.legend-key{display:inline-block;min-width:18px;text-align:center;
  color:var(--brand-ink);font-weight:600}
.legend-no{color:var(--off-ink)}
.legend-swatch{display:inline-block;width:18px;height:14px;vertical-align:-2px;
  border:1px solid var(--line)}
.legend-yes{color:var(--brand-ink)}
.legend-open{background:var(--surface);border-color:var(--line2)}
.legend-off{background:var(--off-bg);border-color:var(--off-line)}
.legend-diff{background:var(--surface);border-color:var(--line2);position:relative}
.legend-diff::after{content:'';position:absolute;top:0;right:0;
  width:0;height:0;border-top:6px solid var(--ink3);
  border-left:6px solid transparent}
.legend-hatch{background:repeating-linear-gradient(45deg,var(--surface2),
  var(--surface2) 4px,var(--surface3) 4px,var(--surface3) 8px)}

/* Print twins: plain text versions of the two cell controls, hidden on screen
   and swapped in for print. See the print block at the foot of this file. */
.prn{display:none}

/* ---------- misc ----------------------------------------------------- */
/* Drawn, not typed: the braille glyph this used to use (U+283F) is not in
   Inter, and a missing glyph falls back to whatever face has it. */
.grab{padding:0 0 0 var(--s2);user-select:none}
/* touch-action:none is the whole reason reordering works on a touchscreen:
   without it the browser claims the gesture for scrolling and the pointermove
   stream stops after the first event. */
.grabbtn{display:flex;align-items:center;justify-content:center;
  width:32px;height:36px;cursor:grab;color:var(--quiet);
  touch-action:none;border-radius:var(--r1)}
.grabbtn:hover{background:var(--surface3);color:var(--ink2)}
.grabbtn:active{cursor:grabbing}
.grabbtn:focus-visible{outline:2px solid var(--brand);outline-offset:-2px}
.grabdots{display:block;width:8px;height:14px;
  background-image:radial-gradient(currentColor 42%,transparent 46%);
  background-size:4px 4px;background-position:0 1px;color:var(--quiet)}

.dragging{opacity:.4}
/* The time-off row currently loaded into the form above, so it is obvious
   which record the fields belong to. */
.tr.editing > .td{background:var(--brand-bg)}
.tr.editing:hover > .td{background:var(--brand-bg)}
.drop-target > .td{box-shadow:inset 0 2px 0 var(--brand)}
.toast{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);z-index:60;
  padding:9px 16px;border-radius:var(--r2);background:var(--ink1);color:#fff;
  font-size:14px;box-shadow:var(--sh2);opacity:0;transition:opacity .18s;
  pointer-events:none}
.toast[data-on]{opacity:1}
/* Stretch rather than start-align: the left column is asked to match the
   matrix's height so the list's box ends where the matrix does, instead of
   leaving a ragged gap of nothing beneath it. */


.scroll-y{max-height:400px;overflow-y:auto}
.page.hidden,.sub.hidden{display:none!important}

/* ---------- the save warning -----------------------------------------
   Full width, under the header, in the error family — the only thing in this
   interface that is allowed to shout, because it is the only one that means
   the work is not being kept.
--------------------------------------------------------------------- */
.savewarn{padding:var(--s4) var(--s7);background:var(--err-bg);
  border-bottom:1px solid var(--err-line);color:var(--err);
  font-size:14px;font-weight:500}

/* ---------- the week selector ---------------------------------------
   One button at the right of the tab strip, naming the week both tabs read,
   with a calendar behind it. Rows are weeks, not days: a week is the only
   unit this app schedules in, so a week is what you click.
--------------------------------------------------------------------- */
.weekpick{margin-left:var(--s6);padding-bottom:var(--s2);align-self:flex-end}
/* 14px is this interface's floor and the caret is rendered text, so it keeps
   to it like everything else — a 11px glyph here was the only thing on the
   page below the floor. */
.caret{font-size:14px;line-height:1;color:var(--ink3)}
.calpop{width:286px;padding:var(--s4)}
.calhead{display:flex;align-items:center;justify-content:space-between;
  margin-bottom:var(--s2)}
.calgrid{width:100%;border-collapse:separate;border-spacing:0 var(--s1)}
.calgrid th{padding:var(--s2) 0;text-align:center;font-size:14px;font-weight:600;
  color:var(--ink-weak);text-transform:uppercase;letter-spacing:.04em}
.calweek{cursor:pointer}
.calweek > td{padding:var(--s2) 0;text-align:center;font-size:14px;
  color:var(--ink2);background:var(--surface2)}
.calweek > td:first-child{border-radius:var(--r1) 0 0 var(--r1)}
.calweek > td:last-child{border-radius:0 var(--r1) var(--r1) 0}
.calweek:hover > td{background:var(--line)}
.calweek[aria-selected=true] > td{background:var(--brand);color:#fff;font-weight:600}
.calweek:focus-visible{outline:2px solid var(--brand);outline-offset:1px}
/* A finished week is shown, so the month reads as a month, but it is not a
   choice here: it is read on the Past schedules tab instead. */
.calweek.past{cursor:not-allowed}
.calweek.past > td{color:var(--quiet);background:var(--surface)}
.calweek.past:hover > td{background:var(--surface)}
.calweek td.other{color:var(--ink3);opacity:.55}
.calweek[aria-selected=true] > td.other{color:#fff;opacity:.72}
.calweek.has > td{color:var(--ink1);font-weight:500}
.calweek.has[aria-selected=true] > td{color:#fff}
.caldot{display:inline-block;width:5px;height:5px;border-radius:var(--rf);
  background:var(--brand);vertical-align:2px;margin-left:3px}
.calweek[aria-selected=true] .caldot{background:#fff}
.calnote{margin-top:var(--s3);line-height:1.4}

/* ---------- the daily sheet -----------------------------------------
   A day picker that carries the date, because "Tuesday" alone is not enough
   to pin a sheet to a board by.
--------------------------------------------------------------------- */
.daytab{display:flex;flex-direction:column;align-items:center;gap:1px;
  min-width:64px;padding:7px 10px;border:1px solid var(--control);
  border-radius:var(--r1);background:var(--surface);color:var(--ink2)}
.daytab:hover{border-color:var(--ink3);color:var(--ink1)}
.daytab-d{font-size:14px;font-weight:500}
.daytab-n{font-size:14px;color:var(--ink3)}
.daytab[aria-selected=true]{background:var(--brand);border-color:var(--brand);
  color:#fff;font-weight:600}
.daytab[aria-selected=true] .daytab-n{color:#fff}
.daytab:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
/* The people in the slide-in grids, as the same boxed tabs. They wrap rather
   than scroll: a row that hides half the rota behind a sideways swipe is a row
   somebody will not know to swipe. */
.whotabs{display:flex;flex-wrap:wrap;gap:var(--s2);width:100%}

/* ---------- the Front End Profile -----------------------------------
   The store's posted daily sheet. It is a ruled form, not a data table, so
   it keeps every border: the hour columns only read as a coverage bar if the
   grid they sit in is drawn.
--------------------------------------------------------------------- */
.profhead{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--s5);
  padding:var(--s3) var(--s4);border:1px solid var(--line2);border-bottom:0;
  border-radius:var(--r2) var(--r2) 0 0;background:var(--surface);
  font-size:14px;color:var(--ink2)}
.proftitle{font-size:15px;font-weight:600;color:var(--ink1)}
#daywrap{border-radius:0 0 var(--r3) var(--r3)}
.profile{table-layout:fixed;min-width:1180px;font-size:14px}
.profile th,.profile td{border:1px solid var(--line2)}
.ph{padding:5px 6px;text-align:center;font-size:14px;font-weight:600;
  background:var(--surface2);color:var(--ink1);white-space:nowrap}
.ph-name{width:132px;text-align:left}
.ph-pos{width:104px}
.ph-t{width:62px}
.ph-hr{width:40px;font-weight:500;color:var(--ink2);font-size:14px}
.pn{padding:3px 6px;font-size:14px}
.pp{padding:3px 6px;text-align:center;font-size:14px;color:var(--ink2)}
.pt{padding:3px 4px;text-align:center;font-size:14px;
  font-variant-numeric:tabular-nums}
/* The coverage bar: the hours this person is on the floor, with the unpaid
   lunch cut out of the middle of it. Drawn, where the paper has it in pen. */
.phr{height:26px;padding:0}
.phr.on{background:var(--brand-bg)}
.phr.lunch{background:var(--surface);color:var(--ink2);text-align:center;
  font-size:14px;font-weight:600}
.prow:hover > td{background:var(--surface2)}
.prow:hover > .phr.on{background:var(--brand-line)}
.prow-gap > td{height:12px;padding:0;background:var(--surface2);border-left:0;
  border-right:0}
/* A typed line's fields sit inside the ruled box rather than beside it, so
   the form keeps its shape whether it is filled in or not. */
.pfield{width:100%;padding:2px 4px;border:1px solid transparent;
  border-radius:var(--r1);background:none;font-size:14px}
.pfield:hover{border-color:var(--line2);background:var(--surface)}
.pfield:focus{outline:none;border-color:var(--brand);background:var(--surface);
  box-shadow:var(--ring)}
.pfield-t{font-family:var(--mono);text-align:center}
.calllog{width:auto;min-width:230px;flex-shrink:0}
.calllog th,.calllog td{border:1px solid var(--line2)}
.calllog .ph{text-align:left;width:74px}
.calllog .pt{height:26px}

/* The three roster tables share one set of column widths, so Front end, Other
   and Removed line up as one roster in three parts. Without table-layout:fixed
   each sizes its columns to its own contents and the three drift apart. */
/* 760 was enough until the Removed tab's actions column grew to hold a pair of
   buttons: fixed columns plus percentages over-constrain, so the percentages
   are scaled down and "Weekly minimum" ran out of room again. Below this the
   table scrolls sideways inside .ovx, which is what that wrapper is for. */
.rostertable{table-layout:fixed;min-width:900px}
.rostertable .td{overflow:hidden;text-overflow:ellipsis}
/* Fixed columns hold their width, so a header too long for its column does not
   widen it — it spills over the next one. "Weekly minimum" did exactly that,
   printing itself across "Preferences" on a narrow window. Let it wrap. */
.rostertable .th{white-space:normal}

/* The preference button reads as the cell's text, not as a control sitting in
   it: ten bordered boxes down a column read as ten things to press, when the
   column is mostly there to be read. It still fills the cell, so the target is
   the whole width rather than the width of the words. */
.prefbtn{width:100%;justify-content:flex-start;text-align:left;
  font-weight:400;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  display:block}
.prefbtn:hover{color:var(--ink1)}

/* One group at a time, so a hidden one renders nothing at all. */
.rpanel.hidden{display:none!important}

/* ---------- past schedules ------------------------------------------- */
.pastlayout{display:grid;gap:var(--s6);align-items:start;
  grid-template-columns:300px minmax(0,1fr)}
@media (max-width:1200px){.pastlayout{grid-template-columns:minmax(0,1fr)}}
.pastlist{display:flex;flex-direction:column;
  border:1px solid var(--line);border-radius:var(--r2);overflow:hidden}
.pastitem{width:100%;text-align:left;padding:10px 12px;
  border-bottom:1px solid var(--line);background:var(--surface);
  color:var(--ink1);font-size:14px}
.pastitem:last-child{border-bottom:0}
.pastitem:hover{background:var(--surface2)}
.pastitem[aria-current=true]{background:var(--brand-bg);
  box-shadow:inset 3px 0 0 var(--brand)}

/* ---------- "set availability" gate ----------------------------------
   Until the week's availability is set there is no grid at all — only this.
   A greyed-out table behind the message was something to read and reason
   about before the one decision the screen was actually asking for, and it
   invited clicks that went nowhere.
--------------------------------------------------------------------- */
.emptybox{display:flex;flex-direction:column;align-items:center;gap:var(--s4);
  padding:var(--s8) var(--s6);text-align:center}
.emptybox p{color:var(--ink2)}
/* On the empty week the message heads a card that also holds the grid, so it
   gives up its own outer padding to the card's and the grid is ruled off
   beneath the two buttons. */
.emptybox:has(+ .weekavail){padding:var(--s3) 0 var(--s7)}
.weekavail{border-top:1px solid var(--line);padding-top:var(--s6);text-align:left}

/* ---------- who can work this week, beside the schedule ----------------
   Part of the page, not laid over it: the weekly view becomes two columns and
   the schedule gives up the room, scrolling sideways inside its own card if
   it has to. The panel sticks while the page scrolls, so a long grid never
   carries it out of reach. */
.view.with-side{display:grid;grid-template-columns:minmax(0,1fr) min(520px,42%);
  gap:var(--s6);align-items:start}
.sidepanel{position:sticky;top:var(--s5);max-height:calc(100vh - var(--s7));
  overflow-y:auto}
.sidehead{display:flex;align-items:center;gap:var(--s3);margin-bottom:var(--s5)}
/* The panel's grid gives up its own minimum width and most of its padding,
   so all seven days fit beside the shift names rather than Friday and
   Saturday scrolling out of sight. */
.sidepanel .mini{min-width:0}
.sidepanel .mini th{padding:8px 2px}
.sidepanel .mini td.slot{padding:var(--s3) var(--s3)}
.sidepanel .pick{padding:12px 0}
/* Too narrow for two columns: the panel follows the schedule instead. */
@media (max-width:1100px){
  .view.with-side{grid-template-columns:minmax(0,1fr)}
  .sidepanel{position:static;max-height:none}
}

/* ---------- popover ---------------------------------------------------
   The week selector's calendar. There was an overflow menu here too, holding
   Clear all and nothing else; it is on the toolbar now, where the sketch puts
   it, and a ••• with one item behind it was a door in front of a door.
--------------------------------------------------------------------- */
.menuwrap{position:relative}
.menu{position:absolute;right:0;top:calc(100% + var(--s2));z-index:30;
  min-width:196px;padding:var(--s2);background:var(--surface);
  border:1px solid var(--line2);border-radius:var(--r2);box-shadow:var(--sh2)}

/* ---------- the preferences panel -------------------------------------
   A side sheet, not a modal: preferences belong to one person, and the page
   it was opened from is worth keeping in view behind it. The scrim dims and
   desaturates that page — a colourful half-lit grid still pulls the eye —
   while leaving it legible as context.
--------------------------------------------------------------------- */
.panelwrap{position:fixed;inset:0;z-index:40;display:flex;justify-content:flex-end}
.panelscrim{position:absolute;inset:0;background:rgba(28,27,25,.35);
  -webkit-backdrop-filter:grayscale(1);backdrop-filter:grayscale(1)}
.panel{position:relative;display:flex;flex-direction:column;
  width:min(680px,100vw);height:100%;background:var(--surface);
  border-left:1px solid var(--line);box-shadow:var(--sh2)}
/* The print dialog is a centred box rather than a side sheet: it asks one
   question about the whole page, not about something the page is showing, so
   there is nothing behind it worth keeping in view. It borrows the panel's
   head, body and foot so the two read as one family. */
.modalwrap{position:fixed;inset:0;z-index:45;display:flex;align-items:center;
  justify-content:center;padding:var(--s6)}
.modal{position:relative;display:flex;flex-direction:column;
  width:min(440px,100%);max-height:100%;background:var(--surface);
  border-radius:var(--r3);box-shadow:var(--sh2);overflow:hidden}
.modalwrap.hidden{display:none}
/* A whole row is the target, not the 13px circle in it. */
.choice{display:flex;align-items:center;gap:var(--s4);padding:11px var(--s4);
  border:1px solid var(--control);border-radius:var(--r2);cursor:pointer;
  background:var(--surface)}
.choice:hover{background:var(--surface2);border-color:var(--ink3)}
.choice:has(input:checked){background:var(--brand-bg);border-color:var(--brand);
  color:var(--brand-ink);font-weight:500}
.choice:has(input:focus-visible){outline:2px solid var(--brand);outline-offset:1px}
.choice input{accent-color:var(--brand);width:16px;height:16px;margin:0}
/* Seven equal columns rather than seven boxes sized to their contents, which
   came to 8px more than the dialog has and left Saturday alone on a second
   line. Each still clears the 44px touch target. */
.daychoice{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:var(--s2)}
.daychoice.hidden{display:none}
/* Seven of these across a 440px box, so they are compact — but each still
   clears the 40px touch target in both directions. */
.daybox{display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:1px;min-width:44px;min-height:44px;padding:var(--s2) 0;
  border:1px solid var(--control);border-radius:var(--r2);
  background:var(--surface);color:var(--ink2);font-size:14px;cursor:pointer}
.daybox:hover{border-color:var(--ink3);color:var(--ink1)}
.daybox[aria-pressed=true]{background:var(--brand-bg);border-color:var(--brand);
  color:var(--brand-ink);font-weight:600}
.daybox:focus-visible{outline:2px solid var(--brand);outline-offset:1px}
.daybox small{font-size:14px;font-variant-numeric:tabular-nums;opacity:.8}

.panelhead{display:flex;align-items:center;gap:var(--s3);
  padding:var(--s5) var(--s6);border-bottom:1px solid var(--line);flex-shrink:0}
.panelbody{padding:var(--s6);overflow-y:auto;flex:1;min-height:0}
.panelfoot{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s3);
  padding:var(--s4) var(--s6);border-top:1px solid var(--line);
  background:var(--surface2);flex-shrink:0}
/* A fixed square, not padding around a glyph: padding sizes the box to the
   glyph, so an "i" made a narrower button than an "×" and the calendar's
   chevrons were narrower still. One size on the spacing scale, comfortably
   over the 24px hit-target floor. */
.iconbtn{display:inline-flex;align-items:center;justify-content:center;
  width:var(--s7);height:var(--s7);padding:0;
  border-radius:var(--r1);color:var(--ink2);font-size:18px;line-height:1}
.iconbtn:hover{background:var(--surface3);color:var(--ink1)}
.iconbtn:disabled{color:var(--quiet);cursor:not-allowed;background:none}

/* ---------- time off -------------------------------------------------- */
.offrow{display:grid;gap:var(--s4);align-items:end;
  grid-template-columns:minmax(200px,1.2fr) minmax(150px,auto) minmax(150px,auto)
    minmax(180px,1.4fr) auto;max-width:1000px}
@media (max-width:860px){.offrow{grid-template-columns:minmax(0,1fr)}}
.offchip{display:inline-flex;align-items:center;gap:var(--s3);padding:var(--s2) var(--s3);
  border-radius:var(--r1);background:var(--off-bg);border:1px solid var(--off-line);
  color:var(--off-ink);font-size:14px}
.cell.off-blocked{background:repeating-linear-gradient(45deg,var(--surface2),
  var(--surface2) 6px,var(--off-bg) 6px,var(--off-bg) 12px)}
.dayoff{display:block;font-size:14px;font-weight:500;color:var(--off-ink);
  text-transform:none;letter-spacing:0}

/* ---------- availability matrix -------------------------------------- */
/* width:100% so the columns use the width the panel gives them; min-width
   keeps them readable and lets the wrapper scroll instead when it is narrow. */
.mini{table-layout:fixed;width:100%;min-width:596px}
.mini th{padding:10px 12px;text-align:center;font-size:14px;font-weight:500;
  text-transform:uppercase;letter-spacing:.05em;color:var(--ink-weak);
  background:var(--surface2);border:1px solid var(--line)}
.mini th small{display:block;font-size:14px;font-weight:400;text-transform:none;
  letter-spacing:0;color:var(--ink3)}
.mini td{padding:0;text-align:center;border:1px solid var(--line)}
/* .mini td (0,1,1) outranks .slot (0,1,0), so the shift column was inheriting
   the zero padding meant for the day cells — its label sat flush against the
   border. It needs the class on the element AND the descendant to win. */
.mini td.slot{padding:var(--s3) var(--s4)}
.mini .slot{position:static;box-shadow:none;text-align:left}
/* "10:30am-7:00pm" is one time and reads as one only on one line. Written in
   full it is a few characters wider than "10:30a–7p" was, which split it in
   two; the seven day columns give up the width instead. */
.mini .slot .mono{white-space:nowrap}
/* Rounded like every other surface here. Overflow clips to the radius, which
   is what rounds the corner cells of a border-collapse table — there is no
   border-radius a collapsed table's own outer cells will honour. The same
   overflow is what lets a narrow modal scroll the matrix sideways. */
.tablebox{overflow:auto;border-radius:var(--r2)}
.cursor-pointer{cursor:pointer}

/* ---------- hours bar ------------------------------------------------
   A one-glance read on how full someone's week is, so the numbers do not
   have to be compared by eye. */
.meter{height:3px;border-radius:2px;background:var(--surface3);overflow:hidden;
  margin-top:3px}
.meter > i{display:block;height:100%;background:var(--brand);border-radius:2px}
.meter.under > i{background:var(--warn-line)}
/* Over the minimum is not a fault: the same brand green, just full. */
.meter.over  > i{background:var(--brand)}

/* ---------- print ---------------------------------------------------- */
@page{size:landscape;margin:10mm}
/* The paper sheet has to say which week it is, exactly as the store's own
   printed schedule does. Screen-hidden, print-only. */
.printonly{display:none}
/* ---------- the posted sheet -----------------------------------------
   One page, always. A schedule pinned to the wall in two halves is not a
   schedule, and extra cells make the height unpredictable — five a day on
   seven days was three pages before this.

   The height is bought back three ways: landscape, tighter cells, and
   printing an extra as one line of text rather than as a stack of three
   controls. Nothing is scaled down, so nothing gets harder to read.
--------------------------------------------------------------------- */
@page{size:landscape;margin:10mm}
#printOut{display:none}
@media print{
  .noprint{display:none!important}
  /* When a print has been built, it is the only thing on the page — the live
     views hold one day at a time and cannot show a week and three sheets at
     once. */
  body.printing > main{display:none}
  body.printing #printOut{display:block}
  /* One sheet per page, and never a trailing blank one. */
  .printsheet{break-after:page;page-break-after:always}
  .printsheet:last-child{break-after:auto;page-break-after:auto}
  .printonly{display:flex;align-items:baseline;gap:var(--s5);
    margin-bottom:var(--s3);padding-bottom:var(--s2);
    border-bottom:2px solid #000;font-size:14px}
  #printWeek{font-variant-numeric:tabular-nums}
  body{background:#fff}
  main{padding:0}
  .card{border:0;box-shadow:none;padding:0}
  /* The Past schedules list is .noprint, so its 300px track would otherwise
     still be reserved and the sheet would be laid out into it — which is how
     the printed page came out two columns wide when the calendar was here. */
  .pastlayout{grid-template-columns:minmax(0,1fr)}
  /* The Front End Profile, as the form it is: every rule drawn, the typed
     fields replaced by their text, and the hour bar in grey so it survives a
     black-and-white printer. */
  .profhead{border:0;padding:0 0 var(--s2);font-size:12px}
  .profile{min-width:0;width:100%;font-size:10px}
  .profile th,.profile td{border:1px solid #000}
  /* Proportions, not pixels. The screen widths add up to 1244px and a
     landscape Letter page is about 980px of printable width, so fixed columns
     ran the last five hours off the right edge — and a sheet that is cut off
     still prints as exactly one page, which is why the page count alone never
     caught it. */
  .ph-name{width:14%}
  .ph-pos{width:10%}
  .ph-t{width:6.5%}
  .ph-hr{width:auto;font-size:9px}
  .ph{background:none;color:#000;font-size:10px;padding:2px 3px}
  .pn,.pp,.pt{font-size:10px;padding:1px 3px;color:#000}
  .phr{height:15px}
  .phr.on{background:#d8d8d8}
  .phr.lunch{background:#fff;font-size:9px}
  .prow-gap > td{height:7px;background:none}
  /* The fields do not print; their text twins do, so the In column still
     carries a time on paper. */
  .pfield{display:none}
  .profile .prn{display:block;color:#000}
  .calllog{min-width:0;width:230px}
  .calllog th,.calllog td{border:1px solid #000}
  .profile tr,.calllog tr{break-inside:avoid;page-break-inside:avoid}
  .ovx{overflow:visible}
  .sched{min-width:0;font-size:12px;width:100%}
  .slot{position:static;box-shadow:none}
  /* The controls do not print at all; their text twins do. */
  .cell-time,.cell-name,.cell-timeinput{display:none}
  .prn{display:block;color:#000;line-height:1.3}
  .prn-time{font-family:var(--mono);font-size:11px}
  .prn-name{font-size:12px;font-weight:600}
  .cell{width:auto;padding:2px 5px}
  .cell-top{margin-bottom:0}
  .cell-label{font-size:11px}
  .sched th{padding:2px 4px;font-size:11px}
  .dayoff{font-size:10px}
  /* controls and affordances are not part of a posted sheet */
  .cell-x,.addextra,.cell-btn{display:none}

  /* An extra is one line on paper — its time and its name, nothing else. On
     screen it is a stack of controls about 90px tall; printed like that, one
     extra was enough to start a second page. */
  .extracell{padding:2px 4px}
  /* tight, because five of these in a day is the case that decides whether the
     sheet is one page: 31px a line was 8px over, 15px a line is 60px under */
  .extra{display:flex;align-items:baseline;gap:6px;line-height:1.15;
    margin:0;padding:0;border:0;border-radius:0}
  .extra .prn-time,.extra .prn-name{font-size:11px;line-height:1.15}
  .extra .cell-top{display:none}
  .extra .prn-time{flex:0 0 auto}
  /* wraps rather than clips: a name cut in half on a posted sheet is worse
     than a name on a second line */
  .extra .prn-name{flex:1 1 auto;min-width:0;overflow-wrap:anywhere}

  /* Never break a day's row across the fold. */
  .sched,.sched tr,.sched td,.sched th{break-inside:avoid;page-break-inside:avoid}
  .printonly{break-after:avoid;page-break-after:avoid}
}

/* =====================================================================
   OVERRIDES — last so they win
   The alignment utilities are declared here rather than up in the type
   section: .th and .td set their own text-align, and at equal specificity
   whichever comes last wins. Declared earlier, `ta-r` on a table header
   silently lost, which is why the Weekly target column read left-aligned
   while its inputs sat right.
   ===================================================================== */
.ta-l{text-align:left}
.ta-c{text-align:center}
.ta-r{text-align:right}
