/* 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}
/* The page's room (review I1, 2026-11-02): with the assistant pinned beside
   it the page has the window less the panel, which can also be dragged
   wider, so a page laid out for the window ran under the panel. <main> is a
   size container, `page`; the rules that turn a page narrower (the staff and
   Connected apps cards, the master-detail list as a dropdown) ask it with
   @container page (…), each threshold the window's breakpoint less main's
   2 × 16 px padding, so without the panel nothing moved. Screen only, as the
   width queries (print has its own page). inline-size containment changes no
   containing block: fixed and absolute popups inside place as before. */
@media screen{ main{container:page / inline-size} }
.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}
/* a card short of room (the calendar beside it, the assistant pinned —
   review I1, 2026-11-02) puts its badges and buttons under the week rather
   than cut them off at its list's edge: the name keeps 220 px (scaled) or
   the line, the actions wrap and keep to the right */
.wk{flex-wrap:wrap}
.wk .meta{flex:1 1 calc(220px * var(--fs, 1));min-width:0}
.wk-actions{display:flex;align-items:center;gap:var(--s3,12px);margin-left:auto;
  flex-wrap:wrap;justify-content:flex-end;max-width:100%}
.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}
/* Alias and Position wrap when the room runs out (customer 2026-11-03: at 1319 px
   in Chinese, real long names pushed Status and the buttons off the right edge);
   the number, section, date, status and buttons stay on one line */
.staff-table td.st-wrap{white-space:normal}
.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}
/* ...and in the app-shell too when the page is short of room for the outlet
   column beside its pane (review I1, 2026-11-02: Template & Pattern beside
   the pinned assistant kept its 340 px column and left the pane ~100 px, its
   table under the panel). The room is the `page` container's (main) in em,
   which follow the font scale as the column does: 46em is the column
   (22.7em), the gap and ~22em of pane. The staff page's drawer (.md-overlay)
   already gives the pane the whole width and keeps its own rules. */
@media screen{ @container page (max-width:46em){
  html.hdr-sticky .md-page .md-layout:not(.md-overlay){flex-direction:column;position:relative}
  html.hdr-sticky .md-page .md-layout:not(.md-overlay) #md-pane{align-self:stretch;max-height:none}
  html.hdr-sticky .md-page:has(.md-layout:not(.md-overlay)) .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}
  html.hdr-sticky .md-page:has(.md-layout:not(.md-overlay)) .md-side-toggle{display:none}
  html.hdr-sticky .md-page .md-layout:not(.md-overlay) .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;margin:0;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.hdr-sticky .md-page .md-layout:not(.md-overlay) .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}
/* The account menu (customer 2026-10-29, .superpowers/scratch/mockups/top-menu.html,
   "Option 2"): the person's name on a pill, ellipsised (the full name in its
   title and inside); the panel drops dark under it, right-aligned, inside the
   window. js/acctmenu.js opens it. Folded behind ☰ it is shown inline (below). */
.acct{position:relative;flex:0 0 auto}
.acct-btn{display:inline-flex;align-items:center;gap:6px;max-width:var(--acct-max,none);
  padding:6px 12px;border:1px solid rgba(255,255,255,.3);border-radius:var(--pill,20px);
  background:transparent;color:#e9e6df;cursor:pointer;line-height:1.5;white-space:nowrap}
.acct-btn:hover,.acct-btn[aria-expanded="true"]{background:rgba(255,255,255,.16);color:#fff}
.acct-ico{flex:0 0 auto;width:1em;height:1em;color:var(--brand-mark,#D4AF50)}
.acct-who{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.acct-caret{flex:0 0 auto;font-size:calc(11px * var(--fs, 1))}
.acct-pop{position:absolute;top:calc(100% + 8px);right:0;z-index:40;display:flex;flex-direction:column;
  gap:var(--s2,8px);min-width:300px;max-width:min(calc(360px * var(--fs, 1)),calc(100vw - 32px));
  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))}
.acct-name{color:#fff;font-weight:600;padding:2px 4px;overflow-wrap:anywhere}
.acct-pop a{padding:8px 12px}
.acct-pop hr{width:100%;margin:2px 0;border:0;border-top:1px solid rgba(255,255,255,.14)}
.acct-lbl,.acct-ver{padding:0 4px;color:rgba(233,230,223,.6);font-size:calc(12px * var(--fs, 1))}
.acct-ver{color:rgba(233,230,223,.55)}
.acct-fs,.acct-lang,.acct-out{display:flex;flex-direction:column;gap:var(--s2,8px)}
/* The steps (customer 2026-10-30, .superpowers/scratch/mockups/top-menu-gradual.html
   round 9): app.js::cdgNavFit sets html[data-nav] to the first that fits on one
   row. 0 everything in the bar (the .nav-wide copies); 1 the text size in the
   menu under the name; 2 + the language; 3 + Log out; 4 the name shortened
   (--acct-max); 5 Admin and the menu in one ☰ (the account button, its menu
   starting with Admin). Beyond, html.nav-fold. No data-nav (before it runs,
   folded, below 1024 px) reads as 3: the bar copies hidden, the menu whole. */
.nav-wide{display:none}
html[data-nav="0"] .nav-wide,
html[data-nav="1"] .nav-lang,html[data-nav="1"] .nav-out,html[data-nav="2"] .nav-out{display:inline-flex}
html[data-nav="0"] .acct{display:none}
html[data-nav="1"] .acct-lang,html[data-nav="1"] .acct-out,html[data-nav="2"] .acct-out{display:none}
.acct-burger,.acct-admin{display:none}
html[data-nav="5"] .nav-admin,html[data-nav="5"] .acct-ico,html[data-nav="5"] .acct-who,
html[data-nav="5"] .acct-caret{display:none}
html[data-nav="5"] .acct-burger{display:inline}
html[data-nav="5"] .acct-admin{display:block}
html[data-nav="5"] .acct-btn{border-radius:var(--radius-sm,6px);padding:4px 12px;line-height:1.2;
  font-size:calc(20px * var(--fs, 1))}
/* the version: beside the brand; it moves into the menu only where even the
   brand, it and ☰ do not fit (html.nav-nover) */
.acct-ver{display:none}
html.nav-nover .nav-ver{display:none}
html.nav-nover .acct-ver{display:block}
html.nav-nover .nav-ver-panel{display:block;padding:2px 12px;color:rgba(233,230,223,.55);
  font-size:calc(12px * var(--fs, 1))}
/* until cdgNavFit has decided the step (boot.js sets html.nav-pending before
   the first paint) the bar shows the brand alone, on one row: no frame draws
   it undecided. The animation shows the rest after 3 s should app.js never run. */
html.nav-pending .site-header .bar{flex-wrap:nowrap;overflow:hidden}
html.nav-pending .site-header .bar>:not(.brand){visibility:hidden;animation:cdg-nav-show 0s 3s forwards}
@keyframes cdg-nav-show{to{visibility:visible}}
.acct-pop .switch{align-self:flex-start}
.acct-pop select{width:100%}
.acct-pop form{display:block !important}
.acct-pop form .btn{width:100%;justify-content:center}
/* Log out as light as the menu's links: the muted grey of .btn-quiet read as disabled on the dark panel */
.acct-pop form .btn-quiet,.nav-out .btn-quiet{color:#e9e6df}
/* A CDG sign-in's Log out (customer 2026-11-02, .superpowers/scratch/mockups/logout-choice.html
   option A): in the bar, "Log out ▾" opens a small panel in the account menu's look
   (js/outmenu.js), right-aligned under it; there and in the account menu, two lines,
   each a form whose button carries a grey second line (templates/_logout_choice.html).
   Every rule names #out or an .out-* class: today's <form class="nav-out"> is untouched */
#out{position:relative}
.out-caret{font-size:calc(11px * var(--fs, 1))}
.site-header .out-btn[aria-expanded="true"]{background:rgba(255,255,255,.16);color:#fff}
.out-pop{position:absolute;top:calc(100% + 8px);right:0;z-index:40;display:flex;flex-direction:column;
  gap:4px;width:max-content;min-width:min(300px,calc(100vw - 32px));
  max-width:min(calc(360px * var(--fs, 1)),calc(100vw - 32px));white-space:normal;
  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))}
.out-line{display:block;margin:0}
.site-header .btn.out-choice{display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-start;
  gap:2px;width:100%;padding:8px 12px;text-align:left;line-height:1.35;white-space:normal;
  font-weight:400;color:#e9e6df}
.site-header .btn.out-choice b{font-weight:600}
.site-header .btn.out-choice small{color:rgba(233,230,223,.6);font-size:calc(12px * var(--fs, 1))}
.site-header .btn.out-choice:hover small{color:rgba(255,255,255,.75)}
@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}
  .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:not(.nav-wide){display:block !important}
  .site-nav form button{width:100%;justify-content:center}
  /* the account section, inline after the links: the name a heading, not a button */
  .nav-me{display:block;margin:0}
  .acct{padding-top:var(--s2,8px);border-top:1px solid rgba(255,255,255,.14)}
  .acct-btn{display:none}
  .acct-pop,.acct-pop[hidden]{display:flex !important;position:static;min-width:0;max-width:none;
    padding:0;background:transparent;border:0;border-radius:0;box-shadow:none}
  .acct-name,.acct-lbl,.acct-ver{padding-left:12px;padding-right:12px}
}

/* nav-fold:begin — the same rules, wherever the bar does not fit on one row at
   1024 px and wider (app.js::cdgNavFit sets html.nav-fold; customer 2026-10-29).
   A copy of the block above, each selector under html.nav-fold: a media query
   and a class cannot share one rule; core/tests/test_nav_fold.py keeps the two
   identical. */
  html.nav-fold .site-header .bar{position:relative;flex-wrap:nowrap;gap:var(--s3,12px)}
  html.nav-fold .nav-toggle{display:inline-block;flex:0 0 auto}
  html.nav-fold .brand{display:flex;align-items:center;gap:8px;min-width:0;flex:0 1 auto;padding:6px 4px}
  html.nav-fold .brand-logo{height:28px;min-width:0;flex:0 1 auto;object-fit:contain;object-position:left center}
  html.nav-fold .brand b{flex:0 0 auto}
  html.nav-fold .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))}
  html.nav-fold .site-header.nav-open .site-nav{display:flex}
  html.nav-fold .site-nav a{padding:10px 12px}
  html.nav-fold .site-nav .switch{align-self:flex-start}
  html.nav-fold .site-nav select{width:100%}
  html.nav-fold .site-nav form:not(.nav-wide){display:block !important}
  html.nav-fold .site-nav form button{width:100%;justify-content:center}
  html.nav-fold .nav-me{display:block;margin:0}
  html.nav-fold .acct{padding-top:var(--s2,8px);border-top:1px solid rgba(255,255,255,.14)}
  html.nav-fold .acct-btn{display:none}
  html.nav-fold .acct-pop,html.nav-fold .acct-pop[hidden]{display:flex !important;position:static;min-width:0;max-width:none;
    padding:0;background:transparent;border:0;border-radius:0;box-shadow:none}
  html.nav-fold .acct-name,html.nav-fold .acct-lbl,html.nav-fold .acct-ver{padding-left:12px;padding-right:12px}
/* nav-fold:end */
/* cdgNavFit's measurement: the unfolded bar on one row, nothing shrinking or
   wrapping, so the links and the account button show their whole width */
html.nav-measure .site-header .bar>*{flex-shrink:0}
html.nav-measure .site-nav{flex:0 0 auto;flex-wrap:nowrap}
html.nav-measure .site-nav>*{flex-shrink:0;white-space:nowrap}
html.nav-measure .nav-me>*{flex-shrink:0;white-space:nowrap}

/* ---- 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{align-items:center}
  .review-page .title-row>*{flex:0 0 auto}
  .review-page .title-row :is(.page-title, .title-sub){flex:0 1 auto}
  .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}
/* Short of room the title row wraps, keyed off the room, not the window
   (2026-10-30: the assistant pinned beside the page from 1024 px). It was
   nowrap up to 1299 px and squeezed "Cafe Deco Airside · Dining" to one word
   a line beside the status. Now the title keeps its whole width and, when
   the rest doesn't fit beside it, takes a line of its own: everything after
   it is one box (.title-rest) that moves under it whole and fills that line.
   In it the status, the 💡 and the counts are one group (.title-sub,
   cards.css), and the spreadsheet switch and Back another, kept at the right
   of whatever line they land on (.title-end). Below 1024 both boxes are
   display:contents and Back leads the row (cards.css). */
.review-page .title-row .title-rest{display:flex;flex:1 1 auto;flex-wrap:wrap;align-items:center;gap:var(--s2)}
.review-page .title-row .title-end{display:inline-flex;align-items:center;gap:var(--s2);margin-left:auto}

/* ---- 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)}
