/* Every width breakpoint here (and in app.css) is scoped to `screen`: the
   printable roster inlines both stylesheets, and PAPER has a width too. A4
   landscape less the margins is ~1063 CSS px — a hair above the 1024px
   breakpoint — so a different paper size, margin or print scale flipped the
   printed table into the phone stacking rules, and the same roster came out
   looking different from one print dialog to the next (customer 2026-08-27).
   Print styling belongs in rosters/print.html, not in a viewport query. */
/* Edge-to-edge: every page uses the full window width (the design system caps main/header at 1100px). */
main{max-width:none}
.site-header .bar,.banners{max-width:none}
/* Review page two-pane: 30/70 source/grid, edge-to-edge when no source. */
.week-controls{margin-bottom:var(--s3,12px)}
.two-pane{display:flex;gap:var(--s4,16px);align-items:flex-start}
.grid-pane{flex:1;min-width:0}
@media screen and (min-width:1024px){
  .two-pane .grid-pane .grid-scroll{max-height:calc(100vh - 230px)}  /* the review page's; the template grid's .grid-pane keeps its own (Plan 16) */
}

/* below 1024px the review page's sidebar goes under the grid. The grid's own
   card views (By day / Week) follow the GRID's width, not the window's:
   they live in cards.css, keyed on the pane's mode (Plan 16). */
@media screen and (max-width:1023.98px){
  .two-pane{flex-direction:column}
}

/* dashboard cards: clickable + bigger status/actions */
.wk{cursor:pointer}
.wk:focus-visible{outline:2px solid var(--accent,#A8842C);outline-offset:2px}
.wk-actions{display:flex;align-items:center;gap:var(--s3,12px);margin-left:auto}
.wk-actions .badge{font-size:calc(13px * var(--fs, 1));padding:5px 14px}
.wk-actions .btn{font-size:calc(15px * var(--fs, 1));padding:9px 18px}

/* dashboard multi-select filter bar */
#wk-filters{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:var(--s3,12px)}
.ms{position:relative}
.ms-btn{border:1px solid var(--border,#ddd);background:var(--surface,#fff);color:var(--ink,#1c1b19);
  border-radius:var(--pill,20px);padding:6px 14px;font-weight:600;font-size:calc(13px * var(--fs, 1));line-height:1;font-family:system-ui;cursor:pointer}
.ms.has-sel .ms-btn{background:var(--accent,#A8842C);color:#fff;border-color:var(--accent,#A8842C)}
/* per-category clear ✕ beside the label — shown once the category has selections */
.ms-clear{display:none;border:1px solid var(--border,#ddd);background:var(--surface,#fff);color:var(--muted,#6f6a61);
  border-radius:50%;width:calc(23px * var(--fs, 1));height:calc(23px * var(--fs, 1));line-height:1;cursor:pointer;margin-left:2px;
  align-items:center;justify-content:center;font-size:calc(13px * var(--fs, 1));vertical-align:middle}
.ms.has-sel .ms-clear{display:inline-flex}
.ms-clear:hover{color:var(--error,#C0392B);border-color:var(--error,#C0392B)}
/* Ten rows at every font scale, including row padding and the popup's padding/border.
   It scrolls down only: a long name ends in an ellipsis (its label's title has
   it in full) rather than scrolling sideways or wrapping onto a second row
   (customer 2026-10-20). */
.ms-pop{--ms-item-height:calc(max(20px,calc(19.5px * var(--fs, 1))) + 12px);
  display:none;position:absolute;top:100%;left:0;margin-top:4px;z-index:60;min-width:160px;max-height:calc(10 * var(--ms-item-height) + 14px);overflow-y:auto;overflow-x:hidden;
  max-width:min(calc(400px * var(--fs, 1)), calc(100vw - 12px));
  background:var(--surface,#fff);border:1px solid var(--border,#ddd);border-radius:var(--radius-sm,6px);box-shadow:var(--shadow-lg,0 6px 24px rgba(0,0,0,.16));padding:6px}
/* a menu that would cross the screen's right edge opens leftwards from its
   button instead (dashboard.js sets it on open; Plan 16, spec §3) */
.ms-pop.ms-pop-end{left:auto;right:0}
.ms.open .ms-pop{display:block}
.ms-pop label{display:flex;align-items:center;gap:10px;height:var(--ms-item-height);padding:6px 8px;border-radius:4px;font-size:calc(13px * var(--fs, 1));line-height:1.5;cursor:pointer;white-space:nowrap}
.ms-pop input[type="checkbox"]{width:max(20px,calc(16px * var(--fs, 1)));height:max(20px,calc(16px * var(--fs, 1)));flex-shrink:0;margin:0;cursor:pointer;accent-color:var(--accent,#A8842C)}
.ms-pop label > span{min-width:0;overflow:hidden;text-overflow:ellipsis}
.ms-pop label:hover{background:var(--bg,#f7f4ef)}
#wk-clear{padding:6px 10px}

/* master-detail (staff / portfolio) */
#md-pane:has(.staff-table){max-width:100%}
.staff-table-scroll{overflow-x:auto}
.staff-table th,.staff-table td{white-space:nowrap}
.staff-table .cluster{flex-wrap:nowrap}
.staff-filter{min-width:min(220px,100%)}
.md-layout{display:flex;gap:var(--s4,16px);align-items:flex-start}
.md-list{flex:0 0 calc(340px * var(--fs, 1));display:flex;flex-direction:column;gap:8px;overflow-x:hidden}  /* wide enough for logo + name on one line */
.md-list .card{overflow-wrap:anywhere}
/* the type-to-filter box rides the top of the scrolling outlet list; the spread
   shadow paints an opaque strip over the 8px flex gap so cards vanish under it */
#md-filter{position:sticky;top:0;z-index:5;flex-shrink:0;box-shadow:0 0 0 8px var(--bg,#F7F4EF)}
html:not(.hdr-sticky) .md-page #md-filter{box-shadow:0 0 0 8px var(--surface,#fff)}
.md-list .card{display:block;text-decoration:none;color:var(--ink,#1c1b19);cursor:pointer}
.md-list .card.active{outline:2px solid var(--accent,#A8842C);outline-offset:-1px}
#md-pane{flex:1;min-width:0}
.crumbs{font-size:calc(13px * var(--fs, 1));color:var(--muted,#6f6a61);margin-bottom:var(--s3,12px)}
/* breadcrumb row with a right-edge action (staff/template list panes) */
.crumb-row{display:flex;justify-content:space-between;align-items:center;gap:var(--s3,12px);flex-wrap:wrap;margin-bottom:var(--s3,12px)}
.crumb-row .crumbs{margin-bottom:0}
.crumbs a{color:var(--accent,#A8842C);text-decoration:none;padding:2px 6px;margin:-2px -6px;border-radius:4px}
/* highlight, not underline, on hover and while pressed (customer 2026-09-09) */
.crumbs a:hover,.crumbs a:active,.crumbs a:focus-visible{background:#F3EAD3;text-decoration:none}
.crumb-sep{margin:0 6px;color:var(--muted,#9a9384)}

/* Desktop app-shell: the top bar and page heading stay put while the outlet list and
   the detail pane each scroll on their own; the page itself never scrolls, so nothing
   drifts at any screen size or content length. Without hdr-sticky -> normal page flow. */
html.hdr-sticky .md-page{height:100vh;display:flex;flex-direction:column;overflow:hidden}
html.hdr-sticky .md-page .site-header,html.hdr-sticky .md-page .banners{flex:0 0 auto}
html.hdr-sticky .md-page main{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;padding-top:0;overflow:hidden;margin:0}  /* margin:0 auto would center & shrink main inside the flex column */
html.hdr-sticky .md-page .md-title-row{flex:0 0 auto}
html.hdr-sticky .md-page .md-layout{flex:1 1 auto;min-height:0}
html.hdr-sticky .md-page .md-list,html.hdr-sticky .md-page #md-pane{align-self:stretch;min-height:0;max-height:calc(100% + 4px);overflow:auto}  /* + the ring's room, below */
/* A pane hosting a grid (the template editor) must not scroll itself — the
   grid's own .grid-scroll is the single vertical scroller (keeps its sticky
   day-header row working and avoids double scrollbars when a cell editor
   opens). Other panes (staff list/forms) keep the pane scroll above. */
html.hdr-sticky .md-page #md-pane:has(.grid-scroll){display:flex;flex-direction:column;overflow:hidden}
html.hdr-sticky .md-page #md-pane:has(.grid-scroll) > *{flex:0 0 auto}
html.hdr-sticky .md-page #md-pane:has(.grid-scroll) .grid-scroll{flex:0 1 auto;min-height:0;max-height:none}
/* the template grid sits in a .grid-pane (Plan 16: the split grid's host), so
   the pane shrinks with the grid and hands the height on to .grid-scroll */
html.hdr-sticky .md-page #md-pane:has(.grid-scroll) > .grid-pane{flex:0 1 auto;min-height:0;display:flex;flex-direction:column}
/* Room for a focus ring (customer 2026-10-20: the Section select at the
   pane's top-left showed its ring only on the right and bottom): a scroller
   clips what is drawn outside its padding box, and the ring reaches 4 px out
   (2 px + a 2 px offset). The padding is given back by an equal negative
   margin, so the content and the columns' edges stay where they were; the
   :where() keeps the launcher's 72 px bottom room (screens.css) winning. A
   sticky child's top:0 counts from inside the padding, so the outlet filter
   keeps that room above it when stuck. */
html.hdr-sticky .md-page :where(.md-list, #md-pane){padding:4px;margin:-4px -4px 0}
html.hdr-sticky .md-page .md-list>*{flex-shrink:0}  /* keep cards their natural size so the list overflows & scrolls (not squashed) */
html.hdr-sticky .md-page .md-list{padding-right:8px}  /* column mode: breathing room between the cards and the scrollbar (the overlay drawer has its own 8px padding) */
.md-list-toggle{display:none}  /* desktop app-shell: the outlet list is the left column, no toggle needed */

/* "Outlet list" toggle beside the page title: desktop app-shell only — on
   page-flow/mobile the dropdown above already collapses the list. Choosing an
   outlet adds .md-list-hidden (server-rendered when one is active) so the pane
   takes the full width; the button brings the column back. */
.md-title-row{display:flex;align-items:center;gap:var(--s3,12px);margin:0 0 var(--s3,12px)}
.md-title-row .md-title{margin:0}
.md-side-toggle{display:none}
html.hdr-sticky .md-page .md-side-toggle{display:inline-flex}
/* the two states look different: pressed (list shown) = accent outline + filled
   left-panel glyph; unpressed (hidden) = plain secondary + hollow glyph */
.md-side-toggle::before{content:"◧";color:var(--accent,#A8842C)}
.md-side-toggle[aria-pressed="true"]{border-color:var(--accent,#A8842C);background:var(--bg,#F4F1E9);box-shadow:inset 0 0 0 1px var(--accent,#A8842C)}
.md-side-toggle[aria-pressed="false"]::before{content:"◻";color:var(--muted,#6f6a61)}
html.hdr-sticky .md-page .md-layout.md-list-hidden .md-list{display:none}
/* Staff page (.md-overlay): the shown outlet list floats over the full-width
   content as a drawer instead of squeezing the pane. Patterns keeps the
   side-by-side column (picking an outlet still leaves a template to choose). */
html.hdr-sticky .md-page .md-layout.md-overlay{position:relative}
html.hdr-sticky .md-page .md-layout.md-overlay .md-list{position:absolute;top:0;left:0;bottom:0;z-index:30;
  width:calc(340px * var(--fs, 1));background:var(--surface,#fff);border:1px solid var(--border,#e3dccb);
  border-radius:var(--radius,8px);padding:8px;box-shadow:0 10px 30px rgba(0,0,0,.18);
  margin:0;max-height:none}   /* the drawer's own 8 px holds the ring */

/* Without the app-shell (html lacks hdr-sticky): normal page flow, and the
   outlet list collapses into a dropdown so it doesn't consume the screen. */
html:not(.hdr-sticky) .md-page .md-layout{flex-direction:column;position:relative}
/* the pane takes the page's width (the row's align-items:flex-start would
   shrink it to its content), so the staff cards centre in it (spec §5 rule 5) */
html:not(.hdr-sticky) .md-page #md-pane{align-self:stretch}
html:not(.hdr-sticky) .md-page .md-list-toggle{display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;
  margin-bottom:var(--s3);padding:10px 12px;border:1px solid var(--border,#e3dccb);border-radius:var(--radius,8px);
  background:var(--surface,#fff);color:var(--ink,#1c1b19);cursor:pointer;font:inherit;font-weight:600}
.md-list-toggle-caret{color:var(--muted,#6f6a61)}
html:not(.hdr-sticky) .md-page .md-list{display:none;position:absolute;top:0;left:0;right:0;z-index:40;flex-direction:column;width:auto;
  max-height:60vh;overflow:auto;padding:8px;background:var(--surface,#fff);
  border:1px solid var(--border,#e3dccb);border-radius:var(--radius,8px);box-shadow:0 10px 30px rgba(0,0,0,.18)}
html:not(.hdr-sticky) .md-page .md-list.open{display:flex}

/* dashboard week-sort toggle */
#wk-sort{border:1px solid var(--border,#ddd);background:var(--surface,#fff);color:var(--ink,#1c1b19);
  border-radius:var(--pill,20px);padding:6px 14px;font-weight:600;font-size:calc(13px * var(--fs, 1));line-height:1;font-family:system-ui;cursor:pointer}

/* brand logo in the dark header — shown white (the source SVG is near-black + purple) */
.brand-logo{height:32px;width:auto;vertical-align:middle;filter:brightness(0) invert(1)}
/* the brand must never break internally ("ROSTER" wrapping under the logo when the bar is tight) */
.brand{white-space:nowrap;flex-shrink:0}
/* mid widths (600-1023.98): the bar's content doesn't fit one row — wrap the nav to a clean second row
   (app.css only wraps it below 600, max-width:599.98px) */
@media screen and (max-width:1023.98px){
  .site-header .bar{flex-wrap:wrap;gap:var(--s2) var(--s4)}
  .site-nav{flex-wrap:wrap}
}

/* keep native form controls (e.g. the outlet <select>) from overflowing their card */
main form select, main form input, main form textarea{max-width:100%}

/* Review uploaded changes: decisions left, live source viewer right */
.changes-layout{display:flex;gap:var(--s4,16px);align-items:flex-start}
.changes-main{flex:1;min-width:0}
.changes-src{flex:0 0 38%;min-width:300px}
@media screen and (max-width:1100px){ .changes-layout{flex-direction:column} .changes-src{flex:0 0 auto;width:100%} }
.ex-cell{background:var(--bg,#F7F4EF)}
.up-cell{background:#eaf6ef}
.changes-table td{vertical-align:top}

/* Review page desktop app-shell (hdr-sticky = desktop): the page itself never
   scrolls — the staff rows scroll inside .grid-scroll under the sticky thead
   (thead/day headers are already position:sticky within it). */
html.hdr-sticky body.review-page{height:100vh;display:flex;flex-direction:column;overflow:hidden}
html.hdr-sticky .review-page .site-header,html.hdr-sticky .review-page .banners{flex:0 0 auto}
html.hdr-sticky .review-page main{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;overflow:hidden;margin:0}
html.hdr-sticky .review-page .two-pane{flex:1 1 auto;min-height:0}
html.hdr-sticky .review-page .grid-pane{display:flex;flex-direction:column;min-height:0;height:100%}  /* align-self:stretch loses to the flex line's intrinsic size here */
html.hdr-sticky .review-page .grid-scroll{flex:1 1 auto;min-height:0;max-height:none}

/* One-row action bar on desktop: no wrapping, no visible scrollbar; when the
   button set is too wide, edge chevrons (sticky flex items, shown by
   cdgBarNav in app.js) scroll it horizontally. */
html.hdr-sticky .review-page .title-row{margin-bottom:4px}
html.hdr-sticky .review-page main{padding-top:10px}
html.hdr-sticky .review-page .actionbar{position:static;flex-wrap:nowrap;flex-shrink:0;overflow-x:auto;scrollbar-width:none;padding:7px var(--s3);gap:8px;margin-bottom:4px}  /* sticky+top offset would displace it inside the no-scroll shell; flex-shrink:0 — it is an item of main's column flex, which otherwise SQUASHES it to make room for the grid, vertically cropping the button text (customer 2026-08-18) */
html.hdr-sticky .review-page .actionbar::-webkit-scrollbar{display:none}
html.hdr-sticky .review-page .actionbar .btn{font-size:min(calc(14px * var(--fs, 1)), 1.2vw);padding:7px 12px;white-space:nowrap}
html.hdr-sticky .review-page .actionbar .bar-nav{display:flex;align-items:center;justify-content:center;position:sticky;
  flex:0 0 auto;width:28px;align-self:stretch;border:1px solid var(--border,#e3dccb);border-radius:6px;
  background:var(--surface,#fff);color:var(--ink,#1c1b19);cursor:pointer;font-size:min(calc(16px * var(--fs, 1)), 1.3vw);
  z-index:3;box-shadow:0 0 6px rgba(0,0,0,.12)}
html.hdr-sticky .review-page .actionbar .bar-nav[hidden]{display:none}
/* the empty submission-panel / week-controls spacers otherwise add ~24px
   between the bar and the grid (flex items: margins don't collapse) */
html.hdr-sticky .review-page #submission-panel,
html.hdr-sticky .review-page .week-controls{margin:0}
.bar-nav{display:none}
.bar-prev{left:0}
.bar-next{right:0}
/* the empty submission-panel / week-controls spacers otherwise add ~24px
   between the bar and the grid (flex items: margins don't collapse) */
html.hdr-sticky .review-page #submission-panel,
html.hdr-sticky .review-page .week-controls{margin:0}


/* ---- below laptop width (<1024): the top bar collapses behind ☰ ----
   Everything in .site-nav (links, text-size, language, user, log out) moves
   into a dropdown panel anchored to the bar; app.js::cdgNavToggle opens it. */
.nav-toggle{display:none;margin-left:auto;background:transparent;border:1px solid rgba(255,255,255,.3);
  color:#fff;border-radius:var(--radius-sm,6px);padding:4px 12px;line-height:1.2;cursor:pointer;
  font-size:calc(20px * var(--fs, 1))}
.nav-toggle:hover{background:rgba(255,255,255,.16)}
/* the version only sits in the panel at collapsed widths (see below) */
.nav-ver-panel{display:none}
@media screen and (max-width:1023.98px){
  .site-header .bar{position:relative;flex-wrap:nowrap;gap:var(--s3,12px)}
  .nav-toggle{display:inline-block;flex:0 0 auto}
  /* The bar holds just the brand and ☰ here, and the brand is 284px wide on
     its own: with nothing able to give, the toggle was pushed clean off the
     right edge on a phone — visible only by scrolling sideways (customer
     2026-09-03). So the LOGO gives way instead. object-fit keeps it
     undistorted as it narrows, and the toggle keeps its whole width. */
  .brand{display:flex;align-items:center;gap:8px;min-width:0;flex:0 1 auto;padding:6px 4px}
  .brand-logo{height:28px;min-width:0;flex:0 1 auto;object-fit:contain;object-position:left center}
  .brand b{flex:0 0 auto}
  /* no room for it beside them — it reappears at the foot of the panel */
  .nav-ver{display:none}
  .nav-ver-panel{display:block;padding:2px 12px;color:rgba(233,230,223,.55);
    font-size:calc(12px * var(--fs, 1))}
  .site-nav{display:none;position:absolute;top:100%;right:var(--s4,16px);z-index:40;
    flex-direction:column;align-items:stretch;justify-content:flex-start;gap:var(--s2,8px);
    min-width:min(280px,86vw);margin:0;padding:var(--s3,12px);
    background:var(--ink,#1c1b19);border:1px solid rgba(255,255,255,.18);
    border-radius:var(--radius,8px);box-shadow:var(--shadow-lg,0 6px 24px rgba(0,0,0,.16))}
  .site-header.nav-open .site-nav{display:flex}
  .site-nav a{padding:10px 12px}                    /* comfortable tap targets */
  .site-nav .switch{align-self:flex-start}
  .site-nav select{width:100%}
  .site-nav form{display:block !important}
  .site-nav form button{width:100%;justify-content:center}
}

/* ---- below laptop width: keep the vertical scrollbar visible ----
   (the page scrolls normally here — the app-shell doesn't apply — so
   the bar must be a permanent affordance, not an overlay that fades out.) */
@media screen and (max-width:1023.98px){
  html{overflow-y:scroll;scrollbar-gutter:stable}
  .grid-scroll,.md-pane,#md-pane{scrollbar-width:auto}
  .grid-scroll::-webkit-scrollbar,html::-webkit-scrollbar{width:12px;height:12px}
  .grid-scroll::-webkit-scrollbar-track,html::-webkit-scrollbar-track{background:var(--bg,#f7f4ef)}
  .grid-scroll::-webkit-scrollbar-thumb,html::-webkit-scrollbar-thumb{
    background:#c9c2b3;border-radius:6px;border:2px solid var(--bg,#f7f4ef)}
}

/* ---- the cell editor always opens as a centered popup (all widths) ----
   The editor is swapped into the cell's own <td> (that td is the HTMX target,
   so it must stay put): lift the .editor panel itself out of the table into a
   fixed, centered dialog with a full-screen backdrop. The td keeps its place
   and its id, so Save/Reset/Cancel swap back in place. (Was <1300px-only;
   made universal 2026-07-27 by request.) */
/* centred with inset+margin:auto, NOT a transform — a transform would make
   the panel the containing block for the backdrop below (and clip it). */
/* While the popup is open the page behind doesn't move: js/popups.js
   (cdgScrollLock) fixes the body where it was and puts the scroll position
   back on close — the iOS-safe variant, so Cancel still takes one tap
   (rosters/tests/browser/test_popups.py). overscroll-behavior on the panel
   and touch-action:none on the backdrop stay as the first line. Up to 92%
   of the screen height, like every popup (spec 2026-10-17 §5). */
:is(.grid td, .editor-host) .editor{
  position:fixed;inset:0;margin:auto;z-index:120;overscroll-behavior:contain;
  width:min(1660px,96vw);height:max-content;max-height:92dvh;overflow:hidden;
  background:var(--surface,#fff);border:1px solid var(--border,#e3dccb);
  border-radius:var(--radius,8px);box-shadow:var(--shadow-lg,0 6px 24px rgba(0,0,0,.16));
  padding:var(--s4,16px) var(--s4,16px) 0}   /* no bottom pad: the sticky button bar IS the footer */
/* backdrop on the cell (a static td = no stacking context), so it paints
   under the panel and over the page instead of inside the panel */
:is(.grid td, .editor-host):has(.editor){position:static}
:is(.grid td, .editor-host):has(.editor)::before{
  content:"";position:fixed;inset:0;z-index:110;background:rgba(28,27,25,.45);
  touch-action:none}                    /* a drag over the dim area must not scroll the page */
:is(.grid td, .editor-host) .editor .rec{margin-bottom:var(--s3,12px)}
/* the action row stays put while the form scrolls, so Save/Cancel are
   always one tap away (never below the fold) */
:is(.grid td, .editor-host) .editor form>.cluster{position:sticky;bottom:0;z-index:2;
  margin:0;padding:10px 0;
  background:var(--surface,#fff);border-top:1px solid var(--border,#e3dccb)}
/* the ✕ rides along at the panel's top-right while the form scrolls
   (a flex column gives the sticky button its own right-aligned row) */
:is(.grid td, .editor-host) .editor{display:flex;flex-direction:column}
/* Desktop 2-col: the ✕ floats in the panel's top-right corner (absolute — a
   sticky flex row can't sit this high, top:0 clamps it to the scrollport) and
   the record column gets matching top clearance so the first card never sits
   under it. The scrolling stacked/mobile modes keep it sticky in flow. */
:is(.grid td, .editor-host) .editor .editor-x{display:inline-flex;position:absolute;top:8px;right:10px;flex:0 0 auto}
:is(.grid td, .editor-host) .editor .ed-mid{padding-top:calc(34px * var(--fs, 1))}
:is(.grid td, .editor-host) .editor.ed-stack .editor-x{position:sticky;top:0;align-self:flex-end;margin:0 0 6px 0}
:is(.grid td, .editor-host) .editor.ed-stack .ed-mid{padding-top:0}
:is(.grid td, .editor-host) .editor button{touch-action:manipulation}   /* no double-tap-zoom delay */
/* 2-column editor: the left column stacks context → hints (description) →
   per-field highlights; the record fields take the whole remaining width.
   Buttons row spans the bottom. The LEFT column never scrolls or squeezes —
   its rows floor at their content (the flexible row's min-content floor), so
   only the RECORD column scrolls at the 92dvh cap; when even the left
   column's content cannot fit, editorstack.js switches to the stacked
   layout. The button bar never leaves the bottom. */
:is(.grid td, .editor-host) .editor .ed-grid{display:grid;grid-template-columns:560px 1fr;gap:0 14px;
  grid-template-areas:"ctx mid" "hints mid" "right mid" "btns btns";
  flex:1 1 auto;min-height:0;grid-template-rows:auto auto minmax(min-content,1fr) auto}
:is(.grid td, .editor-host) .editor .ed-ctx{grid-area:ctx;min-width:0}
:is(.grid td, .editor-host) .editor .ed-hints{grid-area:hints;min-width:0}
:is(.grid td, .editor-host) .editor .ed-mid{grid-area:mid;min-width:0;min-height:0;overflow-y:auto;
  contain:size}  /* the record column must not inflate the left column's flexible row (its box comes from the grid; it scrolls internally) */
:is(.grid td, .editor-host) .editor .ed-right{grid-area:right;min-width:0;margin-top:18px}
:is(.grid td, .editor-host) .editor .ed-btns{grid-area:btns}
/* THE stacked layout — one ordering everywhere it stacks: context, record
   fields, hints, colour panel, buttons; the whole popup scrolls as one
   column and the button bar stays sticky at the bottom. Applied by
   editorstack.js (.ed-stack, when the window is too short for the side
   cells to show unscrolled) AND, for the shift-pattern editor
   (.editor-host, outside any grid), by the <1024 media query below. The
   grid's own editor stacks with the card views, whatever the window
   (cards.css: a bottom sheet). */
:is(.grid td, .editor-host) .editor.ed-stack{overflow-y:auto;overflow-x:hidden}
/* flex/min-height reset: without it the desktop rule's flex:1 1 auto +
   min-height:0 clamp the form to the popup height and the auto rows get
   COMPRESSED — every section's content then paints over the next. Stacked
   mode must let the form grow and the popup scroll it. */
:is(.grid td, .editor-host) .editor.ed-stack .ed-grid{grid-template-columns:1fr;grid-template-rows:none;
  grid-template-areas:"ctx" "mid" "hints" "right" "btns";flex:0 0 auto;min-height:auto}
:is(.grid td, .editor-host) .editor.ed-stack .ed-hints,:is(.grid td, .editor-host) .editor.ed-stack .ed-mid,
:is(.grid td, .editor-host) .editor.ed-stack .ed-right{overflow-y:visible;min-height:auto}
:is(.grid td, .editor-host) .editor.ed-stack .ed-mid{contain:none}   /* stacked: the column grows with its content again */
/* `html` matches the specificity of the :is(.grid td, .editor-host) rules
   above, which these must override */
@media screen and (max-width:1023.98px){
  html .editor-host .editor{overflow-y:auto;overflow-x:hidden}
  html .editor-host .editor .editor-x{position:sticky;top:0;align-self:flex-end;margin:0 0 6px 0}
  html .editor-host .editor .ed-mid{padding-top:0}
  html .editor-host .editor .ed-grid{grid-template-columns:1fr;grid-template-rows:none;
    grid-template-areas:"ctx" "mid" "hints" "right" "btns";flex:0 0 auto;min-height:auto}
  html .editor-host .editor .ed-hints,html .editor-host .editor .ed-mid,html .editor-host .editor .ed-right{overflow-y:visible;min-height:auto}
  html .editor-host .editor .ed-mid{contain:none}
}

/* ---- review-page flash messages: chips in the title row ----
   Confirm/reopen feedback shows beside the error/amber counts instead of the
   global top banners (suppressed via the banners block). Successes never
   coincide with the counts (confirm needs 0 errors + 0 amber); the
   cannot-confirm chip simply sits next to them. */
.review-page .title-msg{display:inline-flex;align-items:center;margin:0;
  flex:0 1 auto;min-width:0;padding:3px 10px;
  font-size:calc(13px * var(--fs, 1))}

/* ---- below desktop width: the review header fits its rows ----
   (1) the action bar stays ONE row — buttons shrink, and if they still
   overflow the bar scrolls sideways with the ‹ › chevrons cdgBarNav shows;
   (2) "Back" stays on the title row, pinned to the right. */
@media screen and (max-width:1299px){
  .review-page .actionbar{flex-wrap:nowrap;flex-shrink:0;overflow-x:auto;scrollbar-width:none;gap:8px}
  .review-page .actionbar::-webkit-scrollbar{display:none}
  .review-page .actionbar .btn{flex:0 0 auto;white-space:nowrap;
    font-size:min(calc(14px * var(--fs, 1)), 2.1vw);padding:7px 11px}
  .review-page .actionbar .bar-nav{display:flex;align-items:center;justify-content:center;position:sticky;
    flex:0 0 auto;width:26px;align-self:stretch;border:1px solid var(--border,#e3dccb);border-radius:6px;
    background:var(--surface,#fff);color:var(--ink,#1c1b19);cursor:pointer;
    font-size:calc(15px * var(--fs, 1));z-index:3;box-shadow:0 0 6px rgba(0,0,0,.12)}
  .review-page .actionbar .bar-nav[hidden]{display:none}
  .review-page .title-row{flex-wrap:nowrap;align-items:center}
  .review-page .title-row>*{flex:0 0 auto}
  .review-page .title-row .page-title{flex:0 1 auto;min-width:0}
  .review-page .title-row .spacer{flex:1 1 auto}
}

/* the week date rides smaller than the outlet/section, so the review title
   keeps to one row (em keeps it tied to the title's own font scale) */
.review-page .title-row .wk-date{font-size:.78em;font-weight:400;white-space:nowrap}

/* ---- dashboard: sticky title row, calendar left, weeks right ----
   (customer 2026-08-27) The heading and its buttons stay put while the weeks
   scroll under them. The row sticks only where the header does NOT (below
   1024, or a short desktop window): there the header scrolls away, so the
   row sticks at the top of the screen, under the UAT stripe (--env-top) —
   at the header's height it left a see-through gap (Plan 16, spec §3). In
   the desktop app shell it is static: the page itself never scrolls. */
.dash-head{position:sticky;top:var(--env-top, 0px);z-index:6;background:var(--bg,#F7F4EF);
  padding:var(--s2,8px) 0;margin-bottom:var(--s2,8px)}
/* the title sits right under the bar (customer 2026-08-27): the design
   system's 24px page padding left a band of empty cream above it. The 8px
   left is the row's own padding, so it keeps a band of cream when stuck. */
.dash-page main{padding-top:0}
.dash-layout{display:flex;gap:var(--s4,16px);align-items:flex-start}
/* The COLUMN sticks, not the card inside it: .dash-layout aligns its items to
   flex-start, so the aside is exactly as tall as the calendar and a sticky
   card has no room to travel in (customer 2026-08-27 — it scrolled away).
   It never scrolls sideways (customer 2026-10-20: a tablet held sideways
   dragged it): clip, which the app shell's overflow-y:auto turns into
   hidden; the calendar is made to fit it below (.cal-title). */
.dash-side{flex:0 0 clamp(240px, 22vw, 320px);position:sticky;align-self:flex-start;
  top:calc(var(--env-top, 0px) + var(--dash-head-h, 64px) + 8px);overflow-x:clip}
.dash-main{flex:1;min-width:0}
/* Below 1024 the calendar is not on the page at all (customer 2026-10-20):
   the 📅 in the title row opens it as a sheet (dashboard.js, cdgCalSheet),
   which borrows the card from this hidden column and gives it back. */
@media screen and (max-width:1023.98px){
  .dash-layout{flex-direction:column}
  .dash-side{display:none}
  .dash-main{width:100%}
}

/* ---- month calendar ---- */
.cal{padding:var(--s3,12px)}
.cal-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:var(--s2,8px)}
.cal-title{font-size:calc(15px * var(--fs, 1))}
.cal-nav{display:inline-flex;align-items:center;justify-content:center;width:calc(24px * var(--fs, 1));
  height:calc(24px * var(--fs, 1));border-radius:50%;color:var(--ink,#1c1b19);text-decoration:none;
  font-size:calc(16px * var(--fs, 1));line-height:1}
.cal-nav:hover{background:var(--bg,#F7F4EF);text-decoration:none}
.cal-grid{width:100%;table-layout:fixed;border-collapse:collapse}
.cal-grid th{font-size:calc(11px * var(--fs, 1));font-weight:600;color:var(--muted,#6f6a61);
  padding:2px 0;text-align:center}
.cal-grid td{text-align:center;padding:3px 0;font-size:calc(13px * var(--fs, 1));border-radius:4px}
.cal-grid th.cal-we,.cal-grid td.cal-we{color:var(--error,#C0392B)}   /* Sat/Sun read red */
.cal-grid td.cal-out{color:#c9c2b3}                                    /* the months either side */
.cal-grid td.cal-today{outline:2px solid var(--accent,#A8842C);font-weight:700}
/* a day carrying a holiday takes that holiday's colour, PH and SH apart */
.cal-grid td.cal-ph{background:#F6DEDE;color:#8E2B2B;font-weight:700}
.cal-grid td.cal-sh{background:#DCEBF5;color:#1F5C7A;font-weight:700}
.cal-grid td.cal-out.cal-ph,.cal-grid td.cal-out.cal-sh{opacity:.45}
/* the month's holidays, named */
.cal-hols{list-style:none;margin:var(--s3,12px) 0 0;padding:0;display:flex;flex-direction:column;gap:4px}
.cal-hol{display:flex;align-items:baseline;gap:6px;font-size:calc(12px * var(--fs, 1));line-height:1.3}
.cal-hol-date{flex:0 0 auto;font-weight:700;white-space:nowrap}
.cal-hol-kind{flex:0 0 auto;font-size:calc(10px * var(--fs, 1));font-weight:700;letter-spacing:.04em;
  padding:1px 5px;border-radius:var(--pill,20px);color:#fff}
.cal-hol-name{min-width:0;overflow-wrap:anywhere}
.cal-hol-ph{color:#8E2B2B}
.cal-hol-ph .cal-hol-kind{background:#C23B3B}
.cal-hol-sh{color:#1F5C7A}
.cal-hol-sh .cal-hol-kind{background:#2C6E8F}
/* a special day wears the amber the roster grid pills it with */
.cal-grid td.cal-sp{background:#F4E8C8;color:#7A5C12;font-weight:700}
.cal-hol-sp{color:#7A5C12}
.cal-hol-sp .cal-hol-kind{background:#C09A3E}
/* every row picks its own date, like the day in the grid does */
.cal-hol>a{display:flex;align-items:baseline;gap:6px;color:inherit;text-decoration:none;
  padding:2px 4px;border-radius:var(--radius-sm,6px)}
.cal-hol>a:hover{background:var(--bg,#F7F4EF);text-decoration:none}
.cal-hol.on>a{background:var(--accent,#A8842C);color:#fff}
.cal-hol.on .cal-hol-kind{background:rgba(255,255,255,.28)}
/* ‹ 1 / 3 › under a long list */
.cal-hols-nav{display:flex;align-items:center;justify-content:center;gap:8px;margin-top:4px}
.cal-hols-nav button[disabled]{opacity:.35;cursor:default}
.cal-none{margin:var(--s3,12px) 0 0}

/* the calendar title opens a month/year picker (customer 2026-08-27) */
.cal-title{border:0;background:transparent;color:var(--ink,#1c1b19);cursor:pointer;
  font-weight:700;font-size:calc(14px * var(--fs, 1));padding:2px 6px;border-radius:var(--radius-sm,6px);
  white-space:nowrap}   /* "December 2026 ▾" must not drop its arrow to a second line */
/* the year keeps its arrow when the title wraps on touch (screens.css,
   beside the 44 px arrows that make it wrap; customer 2026-10-20) */
.cal-title-y{white-space:nowrap}
.cal-title:hover{background:var(--bg,#F7F4EF)}
.cal-pick{margin-bottom:var(--s2,8px);padding:8px;border:1px solid var(--border,#e3dccb);
  border-radius:var(--radius-sm,6px);background:var(--bg,#F7F4EF)}
.cal-pick-year{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px;
  font-size:calc(14px * var(--fs, 1))}
.cal-pick-months{display:grid;grid-template-columns:repeat(3, 1fr);gap:4px}
.cal-pick-m{display:block;text-align:center;padding:5px 0;border-radius:var(--radius-sm,6px);
  color:var(--ink,#1c1b19);text-decoration:none;font-size:calc(12px * var(--fs, 1))}
.cal-pick-m:hover{background:var(--surface,#fff);text-decoration:none}
.cal-pick-m.on{background:var(--accent,#A8842C);color:#fff;font-weight:700}
/* every day is a link: pick it to narrow the list, pick it again to let go */
.cal-grid td a{display:block;color:inherit;text-decoration:none;padding:3px 0;border-radius:4px}
.cal-grid td a:hover{background:var(--bg,#F7F4EF);text-decoration:none}
.cal-grid td{padding:0}
.cal-grid td.cal-sel{background:var(--accent,#A8842C)}
.cal-grid td.cal-sel a{color:#fff;font-weight:700}
/* the hover tint must not paint over the picked day, which then reads as
   unpicked while the pointer rests on it */
.cal-grid td.cal-sel a:hover{background:transparent}
/* the picked date, named beside the filters, with the way back out */
.wk-datepick{display:inline-flex;align-items:center;gap:6px;border-radius:var(--pill,20px);
  background:var(--accent,#A8842C);color:#fff;text-decoration:none;padding:6px 14px;
  font-weight:600;font-size:calc(13px * var(--fs, 1));line-height:1}
.wk-datepick:hover{background:#8c6d23;color:#fff;text-decoration:none}

/* ---- dashboard app-shell: the WEEK LIST scrolls, not the page ----
   (customer 2026-08-27) Same shape as the review page: on desktop
   (html.hdr-sticky) the page itself never scrolls, so the title row, the
   filters and the calendar are always there and only the records move. The
   calendar column scrolls on its own when a short window cannot hold it. */
html.hdr-sticky body.dash-page{height:100vh;display:flex;flex-direction:column;overflow:hidden}
html.hdr-sticky .dash-page .site-header,html.hdr-sticky .dash-page .banners{flex:0 0 auto}
html.hdr-sticky .dash-page main{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;
  overflow:hidden;margin:0}
html.hdr-sticky .dash-page .dash-head{position:static;flex:0 0 auto}
html.hdr-sticky .dash-page .dash-layout{flex:1 1 auto;min-height:0}
html.hdr-sticky .dash-page .dash-side{position:static;align-self:stretch;overflow-y:auto}
html.hdr-sticky .dash-page .dash-main{display:flex;flex-direction:column;min-height:0;height:100%}
html.hdr-sticky .dash-page #wk-filters{flex:0 0 auto}
html.hdr-sticky .dash-page #wk-list{flex:1 1 auto;min-height:0;overflow-y:auto;
  padding-right:4px}   /* room for the scrollbar beside the cards */

/* the shift-pattern editor's host on the Template & Pattern page (2026-09-09): an
   empty div in the list pane; filled, it shows the cell editor's popup above */
.editor-host{position:static}

/* ---- the app's own scroll containers get a bar you can grab ----
   The browser's default is 15px, which is a small target beside a full-height
   list of roster weeks or outlets (customer 2026-09-15). 18px, with the same
   thumb the narrow-width bars already use, so it reads as one system rather
   than a raw OS bar.

   Chrome/Edge only, and deliberately so: Firefox's `scrollbar-width` takes
   only auto|thin|none (no length), and setting either standard property here
   would make Chrome 121+ IGNORE these pseudo-elements entirely — which is
   also why .dash-side no longer asks for `thin`. Firefox keeps the OS width.

   Below 1023px the narrow-width block further up wins for .grid-scroll and
   the page itself, which is intended: that layout styles its own 12px bars. */
#wk-list::-webkit-scrollbar,
.dash-page .dash-side::-webkit-scrollbar,
.md-list::-webkit-scrollbar,
#md-pane::-webkit-scrollbar,
.md-pane::-webkit-scrollbar,
.grid-scroll::-webkit-scrollbar{width:18px;height:18px}
#wk-list::-webkit-scrollbar-track,
.dash-page .dash-side::-webkit-scrollbar-track,
.md-list::-webkit-scrollbar-track,
#md-pane::-webkit-scrollbar-track,
.md-pane::-webkit-scrollbar-track,
.grid-scroll::-webkit-scrollbar-track{background:var(--bg,#f7f4ef)}
#wk-list::-webkit-scrollbar-thumb,
.dash-page .dash-side::-webkit-scrollbar-thumb,
.md-list::-webkit-scrollbar-thumb,
#md-pane::-webkit-scrollbar-thumb,
.md-pane::-webkit-scrollbar-thumb,
.grid-scroll::-webkit-scrollbar-thumb{background:#c9c2b3;border-radius:9px;
  border:3px solid var(--bg,#f7f4ef)}   /* 18px track less 2x3 = a 12px thumb */
#wk-list::-webkit-scrollbar-thumb:hover,
.dash-page .dash-side::-webkit-scrollbar-thumb:hover,
.md-list::-webkit-scrollbar-thumb:hover,
#md-pane::-webkit-scrollbar-thumb:hover,
.md-pane::-webkit-scrollbar-thumb:hover,
.grid-scroll::-webkit-scrollbar-thumb:hover{background:#b3ab99}

/* ---- the in-page menus' scrollbar, at every width (customer 2026-10-20) ----
   The ONE rule for every menu that can scroll inside the page — the dashboard
   filters, the action-bar menus (More on a phone, Sorting, the selection
   menu) and the combobox list — in the platform's style (the narrow-width
   block's thumb, #c9c2b3, on the menu's own colour) instead of a raw OS bar.
   Firefox, and Chrome/Edge 121+, read the standard pair; setting it makes
   Chrome ignore the ::-webkit-scrollbar rules below (see the note above), so
   those are for Safari, which only reads them. Add a new menu here, not
   beside its own styles (guard: rosters/tests/test_dashboard_screens.py). */
.ms-pop, .bar-menu, .sort-panel, #sel-menu, #m-more, .combo-list{
  scrollbar-width:thin;scrollbar-color:#c9c2b3 transparent}
.ms-pop::-webkit-scrollbar, .bar-menu::-webkit-scrollbar, .sort-panel::-webkit-scrollbar,
#sel-menu::-webkit-scrollbar, #m-more::-webkit-scrollbar, .combo-list::-webkit-scrollbar{width:10px}
.ms-pop::-webkit-scrollbar-track, .bar-menu::-webkit-scrollbar-track, .sort-panel::-webkit-scrollbar-track,
#sel-menu::-webkit-scrollbar-track, #m-more::-webkit-scrollbar-track,
.combo-list::-webkit-scrollbar-track{background:var(--surface,#fff)}
.ms-pop::-webkit-scrollbar-thumb, .bar-menu::-webkit-scrollbar-thumb, .sort-panel::-webkit-scrollbar-thumb,
#sel-menu::-webkit-scrollbar-thumb, #m-more::-webkit-scrollbar-thumb,
.combo-list::-webkit-scrollbar-thumb{background:#c9c2b3;border-radius:6px;border:2px solid var(--surface,#fff)}
