/* ===== Popups everywhere (Plan 16, spec 2026-10-17 §5 rules 2-3) =====

   Every popup — the dialogs, the cell editor (layout.css), the bottom sheets
   (cards.css) — keeps its buttons in a footer sticky at its bottom, fully
   shown while its body scrolls; grows with its content up to 92% of the
   screen height; and a long list inside it scrolls by itself under its sticky
   header row, while the title, the pickers and the footer stay put. An open
   dropdown sets the popup's minimum height (js/popups.js, cdgPopupFitDropdown,
   on combobox.js's cdg-dropdown), so it is never cut off. The page behind is
   locked while any popup is open (js/popups.js, cdgScrollLock). These rules
   hold on desktops too (customer 2026-10-17). */

dialog.dlg{max-height:92dvh;overflow:auto;overscroll-behavior:contain}
/* the uploaded source fills its 92% itself and scrolls inside the viewer */
dialog.dlg-full{height:92dvh;overflow:hidden}

/* the button row: sticky at the popup's bottom, edge to edge, so what scrolls
   under it never shows beside or below it. The footer brings the popup's
   bottom padding itself — a scrolling box adds its own padding after the
   content, which a negative margin can't take back. */
dialog.dlg:has(.dlg-foot){padding-bottom:0}
dialog.dlg .dlg-foot{position:sticky;bottom:0;background:var(--surface);z-index:3;
  margin:var(--s3) calc(-1 * var(--s4)) 0;padding:var(--s2) var(--s4) var(--s4)}

/* a list dialog: title and pickers keep their height, the list takes what is
   left and scrolls; a form or a field holding the list passes the column on */
dialog.dlg-flex[open]{display:flex;flex-direction:column}
dialog.dlg-flex[open] :is(form, .field):has(.dlg-scroll){display:flex;flex-direction:column;
  flex:1 1 auto;min-height:0}
.dlg-scroll{flex:1 1 auto;min-height:0;overflow:auto;overscroll-behavior:contain}
.dlg-scroll thead th{position:sticky;top:0;z-index:1;background:var(--surface)}
/* the totals are a div grid (the sidebar is never a table), so their header
   row is marked */
.dlg-scroll .dlg-head{position:sticky;top:0;z-index:1}

/* Tap targets of at least 44 px (the survey's list) and inputs of at least
   16 px, so iOS doesn't zoom in on focus — on touch screens only. */
@media (pointer:coarse){
  .dlg-x, .editor-x, .cal-nav, .ms-clear, .sv-btn, .staff-info, .timefield .time-pick, .tf-done{
    min-width:44px;min-height:44px}
  .dlg-x, .editor-x, .staff-info, .timefield .time-pick{display:inline-flex;align-items:center;justify-content:center}
  .dlg-x{top:0;right:0}
  /* the calendar's 44 px arrows can't give way, so in a sideways tablet's
     240 px column its title wraps instead of pushing › out of the card
     (customer 2026-10-20): the year goes under the month with its ▾
     (layout.css .cal-title-y), and the gap is only a floor — space-between
     spreads any slack. A mouse keeps the one row, as before. */
  .cal-head{gap:4px}
  .cal-title{white-space:normal;min-width:0}
  .ms.has-sel .ms-clear{display:inline-flex;align-items:center;justify-content:center}
  .staff-info{top:0;right:0;padding:0}
  .grid td.staff{padding-right:46px}
  /* the staff list's reorder handle (rowdrag.js takes touch) */
  .staff-table .drag-handle{display:inline-flex;align-items:center;justify-content:center;
    min-width:44px;min-height:44px;padding:0}
  /* the wheel's rows: 44 px, with the band and the end padding to match */
  .tf-wheel li{height:max(44px, calc(32px * var(--fs, 1)));line-height:max(44px, calc(32px * var(--fs, 1)))}
  .tf-band{height:max(44px, calc(32px * var(--fs, 1)))}
  .tf-wheel{height:calc(3 * max(44px, calc(32px * var(--fs, 1))) + 32px)}
  .tf-wheel ul{padding:calc(max(44px, calc(32px * var(--fs, 1))) + 16px) 0}
  input:not([type=checkbox]):not([type=radio]), select, textarea{font-size:max(16px, 1em)}
}

/* ===== Page-wide rules (Plan 16 Task 12, spec 2026-10-17 §5 rules 1, 4, 5) =====

   On phones and tablets, upright or sideways, the page never scrolls
   sideways and can't be pulled past its content (customer 2026-10-17).
   overflow-x:clip on the page: a wide element is cut where it is instead of
   pushing the page wider (documentElement.scrollWidth still reports any
   overhang, which is what the page sweep measures — the cure is to make the
   element fit). Below 1024 layout.css's html{overflow-y:scroll} makes the
   clip compute to hidden, which holds the page just the same. No rubber band
   on the page nor on the app's own scrollers, so a swipe at a list's end
   doesn't drag the page into empty space. The page-level rules are for
   phones and tablets only (the customer's rule): on a desktop the page
   keeps its overscroll, so a trackpad's two-finger swipe still goes back
   and forward. */
@media screen and (max-width:1023.98px){
  html{overflow-x:clip;overscroll-behavior:none}
  body{overscroll-behavior:none}
}
.grid-scroll, #wk-list, .md-list, #md-pane, .asst-msgs{overscroll-behavior:none}

/* No rubber band on a touch-first device, at any size (customer 2026-10-20:
   on a phone or a tablet, upright or sideways, a drag past an end must never
   show the elastic over-scroll that reveals an empty area — on the page or
   inside any box). The rule above stops at 1024, so a tablet held sideways
   (the desktop shell) still bounced. This one is keyed on the POINTER, not
   the width: a phone or a tablet has no hover and a coarse pointer at any
   size, while a desktop or a laptop — whatever its width, a touch laptop
   with a trackpad included — has a fine one, and keeps the page's
   overscroll, so its trackpad's two-finger swipe still goes back and
   forward. Every element gets none, not only the scrollers known today, so
   a new box is covered too; !important, because some boxes ask for
   "contain" (the dialogs, the menus — two of them from script, in
   element.style), and contain still bounces inside the box itself. The
   width rules above stay for a narrow desktop window. The admin has the
   same block in admin_screens.css. */
@media screen and (hover:none) and (pointer:coarse){
  html, body{overscroll-behavior:none}
  html{overflow-x:clip}
  *{overscroll-behavior:none !important}
}

/* A list narrower than the screen is centred instead of hugging the left
   edge: the dashboard's weeks, the roster grid's cards (and the bar over
   them); the staff list's and the leave entitlements' cards centre with
   their card rules below. (The admin's lists centre in admin_screens.css,
   which is what the admin loads.) */
@media screen and (max-width:1023.98px){
  #wk-list,
  :is(.g-cards-boot .grid-pane:not([data-gmode]), .grid-pane[data-gmode="cards"]) :is(.grid, #grid-mobile-bar){max-width:760px;margin-inline:auto}
}

/* ===== The staff list and the leave entitlements (Plan 16 Task 14, spec §4) =====
   Each staff member is a card, not a row: every figure a labelled line (the
   label is the column's heading, data-label on the cell), the buttons in a
   row of their own, the boxes the card's width, the list centred at most
   760 px wide. Nothing scrolls sideways inside the page.
   /staff/ is cards below 1200 (customer 2026-10-20): a tablet held sideways
   (1024, 1180) had a one-line table 13 px wider than its pane, so it
   scrolled sideways. The list's box clips sideways in cards (clip, not
   hidden: hidden would make it a scroller that cuts the dragged card's
   shadow and can still be scrolled by script), so a sideways drag moves
   nothing. The 44 px handle is the (pointer:coarse) rule above, at every
   width. From 1200 the table is as before. */
@media screen and (max-width:1199.98px){
  .staff-table-scroll{overflow-x:clip}
  .staff-table{max-width:760px;margin-inline:auto}
  .staff-table, .staff-table :is(tbody, tr, td){display:block;width:100%}
  .staff-table{border:0;background:none}
  .staff-table thead{display:none}
  .staff-table tr{position:relative;background:var(--surface);border:1px solid var(--border);
    border-radius:var(--radius);padding:var(--s2) var(--s3);margin-bottom:var(--s2)}
  .staff-table td{border:0;padding:var(--s1) 0;white-space:normal;overflow-wrap:anywhere}
  .staff-table td[data-label]{display:flex;justify-content:space-between;align-items:center;gap:var(--s3)}
  .staff-table td[data-label]::before{content:attr(data-label);flex:0 0 auto;
    font-size:calc(11px * var(--fs, 1));font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--muted)}
  /* nothing on file (no joining date, no position): no bare label */
  .staff-table td[data-label]:empty{display:none}
  .staff-table td.st-no{font-weight:700;padding-right:56px}
  /* the handle sits at the card's top corner; a row without one has no gap */
  .staff-table td.drag{position:absolute;top:0;right:0;width:auto;padding:0}
  .staff-table td.drag:empty{display:none}
  .staff-table td.staff-acts{padding-top:var(--s2)}
  .staff-table .cluster{flex-wrap:wrap}
  .staff-table td.staff-acts .btn{min-width:44px;min-height:44px;justify-content:center}
  .staff-table td.empty{text-align:center}
}
/* /entitlements/ is cards below 1024 — a width query, so they need no script
   (backlog 19, ruling 2026-10-20) — and from 1024 only when its table cannot fit
   its pane even with the Note at its minimum (entfit.js's .ent-cards, customer
   2026-10-20: the Note gives way first, app.css). The same rules for both, in the
   same order and at the same weight: CSS can't join a width query and a class in
   one selector, so they are written twice, and core/tests/test_staff_ent_cards.py
   holds the two copies equal. Without JavaScript, from 1024 the table scrolls
   inside its .ent-scroll, as before. */
.ent-scroll.ent-cards{overflow-x:visible}
.ent-scroll.ent-cards .ent-table{max-width:760px;margin-inline:auto;border:0;background:none}
.ent-scroll.ent-cards :is(.ent-table, .ent-table tbody, .ent-table tr, .ent-table td){display:block;width:100%}
.ent-scroll.ent-cards .ent-table thead{display:none}
.ent-scroll.ent-cards .ent-table tr{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--s2);
  background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius);padding:var(--s2) var(--s3);margin-bottom:var(--s2)}
.ent-scroll.ent-cards .ent-table td{border:0;padding:var(--s1) 0;white-space:normal;overflow-wrap:anywhere}
/* the head: number and alias on one line */
.ent-scroll.ent-cards .ent-table td.ent-head{width:auto;font-weight:700}
.ent-scroll.ent-cards .ent-table td.ent-head + td.ent-head{font-weight:400;color:var(--muted)}
/* each figure: its label, then a box as wide as the card allows */
.ent-scroll.ent-cards .ent-table td[data-label]{display:grid;grid-template-columns:minmax(5.5em,34%) 1fr;
  align-items:center;column-gap:var(--s3)}
.ent-scroll.ent-cards .ent-table td[data-label]::before{content:attr(data-label);
  font-size:calc(11px * var(--fs, 1));font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--muted)}
.ent-scroll.ent-cards .ent-table td[data-label]:empty{display:none}
.ent-scroll.ent-cards .ent-table td.ent-days{padding:var(--s1) 0}
.ent-scroll.ent-cards .ent-table td.ent-days:has(.ent-err){min-width:0}
.ent-scroll.ent-cards .ent-table td.ent-days > :not(.ent-input){grid-column:2}
.ent-scroll.ent-cards .ent-table .ent-input{width:100%;min-height:44px}
.ent-scroll.ent-cards .ent-table td.empty{text-align:center}
@media screen and (max-width:1023.98px){
  :is(.ent-scroll, .ent-scroll.ent-cards){overflow-x:visible}
  :is(.ent-scroll, .ent-scroll.ent-cards) .ent-table{max-width:760px;margin-inline:auto;border:0;background:none}
  :is(.ent-scroll, .ent-scroll.ent-cards) :is(.ent-table, .ent-table tbody, .ent-table tr, .ent-table td){display:block;width:100%}
  :is(.ent-scroll, .ent-scroll.ent-cards) .ent-table thead{display:none}
  :is(.ent-scroll, .ent-scroll.ent-cards) .ent-table tr{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--s2);
    background:var(--surface);border:1px solid var(--border);
    border-radius:var(--radius);padding:var(--s2) var(--s3);margin-bottom:var(--s2)}
  :is(.ent-scroll, .ent-scroll.ent-cards) .ent-table td{border:0;padding:var(--s1) 0;white-space:normal;overflow-wrap:anywhere}
  /* the head: number and alias on one line */
  :is(.ent-scroll, .ent-scroll.ent-cards) .ent-table td.ent-head{width:auto;font-weight:700}
  :is(.ent-scroll, .ent-scroll.ent-cards) .ent-table td.ent-head + td.ent-head{font-weight:400;color:var(--muted)}
  /* each figure: its label, then a box as wide as the card allows */
  :is(.ent-scroll, .ent-scroll.ent-cards) .ent-table td[data-label]{display:grid;grid-template-columns:minmax(5.5em,34%) 1fr;
    align-items:center;column-gap:var(--s3)}
  :is(.ent-scroll, .ent-scroll.ent-cards) .ent-table td[data-label]::before{content:attr(data-label);
    font-size:calc(11px * var(--fs, 1));font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--muted)}
  :is(.ent-scroll, .ent-scroll.ent-cards) .ent-table td[data-label]:empty{display:none}
  :is(.ent-scroll, .ent-scroll.ent-cards) .ent-table td.ent-days{padding:var(--s1) 0}
  :is(.ent-scroll, .ent-scroll.ent-cards) .ent-table td.ent-days:has(.ent-err){min-width:0}
  :is(.ent-scroll, .ent-scroll.ent-cards) .ent-table td.ent-days > :not(.ent-input){grid-column:2}
  :is(.ent-scroll, .ent-scroll.ent-cards) .ent-table .ent-input{width:100%;min-height:44px}
  :is(.ent-scroll, .ent-scroll.ent-cards) .ent-table td.empty{text-align:center}
}

/* ===== The dashboard (Plan 16 Task 12, spec §3) =====
   Below 1024 the calendar is not on the page (layout.css hides its column,
   customer 2026-10-20): the 📅 in the title row opens it as a sheet from the
   bottom from wherever the page is (dashboard.js, cdgCalSheet). On a desktop
   it is beside the weeks. */
@media screen and (min-width:1024px){
  #cal-sheet-btn{display:none}
}
/* a week's badges and buttons wrap inside its card rather than push the
   page wider (a confirmed week that missed MSS carries two badges) */
@media screen and (max-width:1023.98px){
  .wk-actions{flex-wrap:wrap;justify-content:flex-end;max-width:100%}
}
/* on a phone the title row stays one row: the title gives way, the buttons
   keep their icons (their names in aria-label / title) */
@media screen and (max-width:599.98px){
  .dash-head{flex-direction:row;flex-wrap:nowrap;align-items:center}
  .dash-head h1{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .dash-head .cluster{flex:0 0 auto;flex-wrap:nowrap}
  .dash-head .btn-lbl{display:none}
  .dash-head .cluster > .btn{min-width:44px;min-height:44px;justify-content:center;padding:8px 10px}
}

/* a popup docked at the bottom of the screen, full width */
dialog.dlg-sheet{margin:auto 0 0;width:100%;max-width:none;min-width:0;
  border-bottom:0;border-radius:16px 16px 0 0;box-shadow:0 -6px 24px rgba(0,0,0,.18);
  animation:cdg-sheet-up .22s ease-out}
dialog.dlg-sheet .dlg-foot{display:flex;justify-content:flex-end;
  padding-bottom:calc(var(--s4) + env(safe-area-inset-bottom, 0px))}
@media (prefers-reduced-motion:reduce){
  dialog.dlg-sheet{animation:none}
}
/* the calendar card, borrowed: no card of its own inside the sheet */
#cal-sheet-body{max-width:520px;margin-inline:auto}
#cal-sheet-body .cal{border:0;box-shadow:none;padding:0}

/* ===== Other pages (Plan 16 Task 13, spec 2026-10-17 §4 and §4a) =====

   The upload pop-up on a touch screen: there is no Ctrl+V, so the hint gives
   way to a box to long-press and choose Paste in (uploadqueue.js takes the
   image and keeps the box empty; the hint shows while it is). The source
   viewer's hint is worded for the hand in use. */
.paste-box, .hint-touch{display:none}
@media screen and (hover:none){
  .paste-hint, .hint-mouse{display:none}
  .hint-touch{display:inline}
  .paste-box{display:block;min-height:56px;margin-top:var(--s2);padding:var(--s3);
    border:1px dashed var(--border);border-radius:var(--radius-sm);background:var(--bg);
    font-size:max(16px, calc(14px * var(--fs, 1)));color:var(--muted);text-align:center}
  .paste-box:empty::before{content:attr(data-i18n-hint)}
  .paste-box:focus{outline:2px solid var(--accent);outline-offset:2px}
}

/* A long app name (or a client id standing in for one) breaks rather than
   widens the page. */
.apps-table .app-name{overflow-wrap:anywhere}

/* On a phone the uploaded changes are one block per staff member (per staff
   and day for a changed cell): the staff number heads it, Existing sits above
   Uploaded, and each cell says what it is (the page's intro says top and
   bottom instead of left and right). Connected apps are a card per app the
   same way. */
.hint-phone{display:none}
@media screen and (max-width:599.98px){
  .hint-wide{display:none}
  .hint-phone{display:inline}
  :is(.changes-table, .apps-table), :is(.changes-table, .apps-table) :is(tbody, tr, td){display:block;width:100%}
  :is(.changes-table, .apps-table){border:0;background:none;overflow:visible}
  :is(.changes-table, .apps-table) thead{display:none}
  :is(.changes-table, .apps-table) tr{background:var(--surface);border:1px solid var(--border);
    border-radius:var(--radius);padding:var(--s2) var(--s3);margin-bottom:var(--s2)}
  :is(.changes-table, .apps-table) td{border:0;padding:var(--s1) 0;overflow-wrap:anywhere}
  :is(.changes-table, .apps-table) td[data-label]::before{content:attr(data-label);display:block;
    font-size:calc(11px * var(--fs, 1));font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--muted)}
  /* the block's head: the staff number and the day, on one line, unlabelled */
  .changes-table td:is(.ch-staff, .ch-day){display:inline-block;width:auto}
  .changes-table td:is(.ch-staff, .ch-day)::before{content:none}
  .changes-table td.ch-day{margin-left:var(--s2);color:var(--muted)}
  .changes-table td:is(.ex-cell, .up-cell){padding:var(--s2);margin-top:var(--s1);border-radius:var(--radius-sm)}
  /* the choice: the radio beside its text, the whole line a tap target */
  .changes-table td label{display:flex;align-items:flex-start;gap:var(--s2);min-height:44px;padding-top:var(--s1)}
  .changes-table td label input[type=radio]{flex:0 0 auto;width:auto;margin:2px 0 0;padding:0}
  .apps-table td{overflow-wrap:anywhere}
  .apps-table td.app-name{font-weight:700}
  .apps-table td.app-name::before{content:none}
  .apps-table td.app-acts{padding-top:var(--s2)}
  .apps-table td.app-acts .btn{min-height:44px}
}

/* ===== Room for the assistant's launcher (Plan 16 Task 17, spec §6) =====
   The ✦ floats over the page's bottom-right corner, so a page reserves room
   for it while it shows: the page's own end where the page scrolls (with
   the phone's safe-area inset, viewport-fit=cover), the inner scrollers'
   end in the desktop shell, where the page itself never scrolls (a grid in
   the master-detail pane is padded by its own scroller, not twice). The
   swap hint and the update notice sit above it (app.css, assistant.css).
   A page outside the three shells (master-detail, review, dashboard) scrolls
   in page flow in the desktop shell too — the uploaded changes, Connected
   apps — so it keeps the page's own room there (2026-10-20). */
html:not(.hdr-sticky) body:has(.asst-launch) main,
html.hdr-sticky body:has(.asst-launch):not(.md-page, .review-page, .dash-page) main{padding-bottom:calc(76px + env(safe-area-inset-bottom, 0px))}
html.hdr-sticky body:has(.asst-launch):not(.asst-open) :where(.grid-scroll, #wk-list, #md-pane:not(:has(.grid-scroll))){padding-bottom:72px}
