/* Staff app UI. One design system for every page and every audience.
   Standard: VectorFoundry UX Standard v1.0, Kiln palette.
   Read the standard before you change this file. */

/* ---------- Section 21: Kiln design tokens. ----------
   The --vf-* colour tokens now come from /tokens.css (the UI pack's own
   file, loaded before this stylesheet). Every other token stays here. */
:root {
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;

  --radius-sm: 10px;
  --radius-md: 16px;
  --radius-lg: 22px;
  --radius-pill: 999px;

  --shadow-subtle: 0 8px 30px rgba(0,0,0,.055);
  --shadow-overlay: 0 24px 80px rgba(0,0,0,.16);

  --motion-fast: 140ms;
  --motion-standard: 200ms;
  --motion-overlay: 240ms;

  /* Type scale (extends section 21; the standard sets the ranges in
     section 4.2, this project names them as tokens so no raw pixel
     size sits in a rule). Every screen uses five sizes or fewer. */
  --text-xs: 12px;      /* supporting text, table headers, badges */
  --text-sm: 13px;      /* control and label text, compact actions */
  --text-body: 14px;    /* default reading text */
  --text-md: 16px;      /* primary inputs and base body text */
  --text-lg: 18px;      /* card and subsection titles */
  --text-xl: 22px;      /* page titles */
  --text-2xl: 26px;     /* kiosk state confirmation (wall display, read at a distance) */
  --text-display: 32px; /* kiosk brand title only */

  /* Kiosk dark surface (documented exception, section 1 and section 5
     of the restyle spec). The kiosk is a fixed wall display, so it
     stays high-contrast on dark for glare and viewing distance. */
  --kiosk-bg: #17140f;
  --kiosk-surface: #ffffff;
  --kiosk-text: #f7f6f2;
  --kiosk-text-muted: #a39d8f;
  --kiosk-ok: #4fd39a;
  --kiosk-err: #ff8f87;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration: 0s !important; animation-duration: 0s !important; }
}

/* ---------- Base ---------- */
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Helvetica, Arial, sans-serif;
  font-size: var(--text-md);
  line-height: 1.5;
  background: var(--vf-bg);
  color: var(--vf-text);
}
h1, h2, h3 { margin: 0; font-weight: 650; }
h1 { font-size: var(--text-xl); margin: 0 0 var(--space-1); }
h2 { font-size: var(--text-lg); font-weight: 600; margin: var(--space-6) 0 var(--space-2); color: var(--vf-text); }
.card > h2:first-child, .item h2:first-child { margin-top: 0; }
p { margin: 0 0 var(--space-2); }

a { color: var(--vf-accent); }

/* Visible focus ring everywhere (section 11). */
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--vf-accent);
  outline-offset: 2px;
}

/* Screen-reader-only text: pairs with an icon or a colour cue so state
   never depends on colour alone (section 11). */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* ---------- App shell (section 5.1) ---------- */
header {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: var(--vf-surface);
  border-bottom: 1px solid var(--vf-border);
  color: var(--vf-text);
  position: sticky;
  top: 0;
  z-index: 5;
}
/* The logo wordmark in place of the text heading. The source image is a
   500 px square with the word in the middle third, so a fixed window crops
   the cream margin and shows the word at the old heading's size (about 26 px
   tall). The brightness filter lifts the cream ground to the white header. */
.brand { position: relative; width: 60px; height: 28px; overflow: hidden; flex: 0 0 auto; }
.brand img { position: absolute; width: 100px; height: 100px; left: -20px; top: -33px; filter: brightness(1.1); }
/* Signed out: the header shows the logo only. */
body.signed-out header .link { display: none; }
header .link {
  background: none;
  border: 0;
  color: var(--vf-text-muted);
  cursor: pointer;
  font-size: var(--text-sm);
  font-weight: 500;
  padding: var(--space-2) var(--space-1);
  flex: 0 0 auto;
}
header .link:hover { color: var(--vf-text); }
/* Author CSS that sets display overrides the browser's default [hidden]
   behaviour, so the hidden attribute needs its own rule here too. Without
   this, the header bar would show through on the login screen. */
header[hidden] { display: none; }

main { max-width: 880px; margin: 0 auto; padding: var(--space-5) var(--space-4) var(--space-8); }

/* Persistent inline error banner for a failed load or a failed save
   (section 6.9). It sits above the page content and stays until the
   next action clears it, so it is never a transient toast. */
#err { max-width: 880px; margin: var(--space-4) auto 0; padding: 0 var(--space-4); }
#err.notice { margin: var(--space-4) auto 0; }

/* ---------- Buttons (section 6.1) ---------- */
button, .primary, .secondary, .danger { font: inherit; }
button { cursor: pointer; }
button:disabled { opacity: .5; cursor: default; }

/* One accent-filled primary action per section. Anchors can carry the
   same classes so a link that behaves like a button looks like one. */
button.primary, a.primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-3) var(--space-4);
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--vf-accent);
  color: var(--vf-on-accent);
  font-size: var(--text-md);
  font-weight: 600;
  text-decoration: none;
  transition: background var(--motion-fast) ease, transform var(--motion-fast) ease;
}
button.primary:hover, a.primary:hover { background: var(--vf-accent-hover); }
button.primary:active, a.primary:active { transform: translateY(1px); }
form > button.primary, form > .field-group > button.primary { margin-top: var(--space-4); }
.row > button.primary, .row > a.primary, .actions > button.primary { width: auto; margin-top: 0; }

button.secondary, a.secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--vf-border-strong);
  border-radius: var(--radius-sm);
  background: var(--vf-surface);
  color: var(--vf-text);
  font-size: var(--text-sm);
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  transition: border-color var(--motion-fast) ease, background var(--motion-fast) ease;
}
button.secondary:hover, a.secondary:hover { border-color: var(--vf-accent); color: var(--vf-accent); }

button.danger, a.danger { color: var(--vf-danger); border-color: var(--vf-danger); }
button.danger:hover, a.danger:hover { background: var(--vf-danger-bg); border-color: var(--vf-danger); color: var(--vf-danger); }

/* A plain-text button for a dismissal quieter than "secondary" needs, such
   as "Not now" on the notification nudge (section 3.5: a healthy or
   optional state stays visually quiet). */
button.link { background: none; border: 0; padding: var(--space-2) var(--space-1); color: var(--vf-text-muted); font-size: var(--text-sm); text-decoration: underline; }
button.link:hover { color: var(--vf-text); }

/* A button running its request keeps its label and gains a quiet spinner
   (section 6.1, v1.1): never disabled, so a screen reader still reports its
   name, and `aria-busy` says a result is on the way. */
button[aria-busy="true"] { cursor: progress; }
.spinner {
  display: inline-block; width: 14px; height: 14px; margin-right: var(--space-2);
  vertical-align: -2px; border: 2px solid currentColor; border-right-color: transparent;
  border-radius: 50%; animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ---------- Confirm dialog: the v1.1 destructive-action tiers (section 6.8) ----------
   A native <dialog>, opened with showModal so the page behind it is inert.
   The keep button sits apart from a danger confirm button, per the standard. */
dialog.confirm-dialog {
  border: 0; border-radius: var(--radius-lg); box-shadow: var(--shadow-overlay);
  padding: var(--space-5); max-width: min(420px, calc(100vw - 2 * var(--space-5)));
  background: var(--vf-surface); color: var(--vf-text);
}
dialog.confirm-dialog::backdrop { background: rgba(36,34,29,.4); }
dialog.confirm-dialog h2 { margin: 0 0 var(--space-2); }
dialog.confirm-dialog p { margin: 0 0 var(--space-4); color: var(--vf-text-muted); }
/* Two classes: beats .row's own margin-top on specificity, regardless of
   source order, so the actions row sits flush under the body text. */
.row.confirm-actions { justify-content: space-between; margin-top: 0; }
.confirm-actions > * { flex: 0 0 auto; }

/* ---------- Forms (section 6.2) ---------- */
label { display: block; font-size: var(--text-body); color: var(--vf-text-muted); margin: var(--space-3) 0 var(--space-1); }
form > label:first-of-type, .field-group > label:first-of-type { margin-top: 0; }
.optional { font-weight: 400; }
input, select, textarea, button { font: inherit; }
input, select, textarea {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--vf-border-control);
  border-radius: var(--radius-sm);
  background: var(--vf-surface);
  color: var(--vf-text);
  font-size: var(--text-md);
}
/* Checkbox and radio borders (section 4.1, v1.1): the resting border needs
   3:1 against the surface around it, same as a text input. */
input[type="checkbox"], input[type="radio"] { width: auto; border: 1px solid var(--vf-border-control); accent-color: var(--vf-accent); }
input:focus, select:focus, textarea:focus { outline: 2px solid var(--vf-accent); border-color: transparent; }
.field-error { color: var(--vf-danger); font-size: var(--text-body); margin: var(--space-1) 0 0; }

/* A checkbox preference (Activity alerts). No toggle component exists yet
   in the design system (section 15.1), so a native checkbox reuses the
   same label and spacing tokens as every other form control. */
.check-row { display: flex; align-items: center; gap: var(--space-2); margin-top: var(--space-3); }
.check-row input[type="checkbox"] { width: auto; }
.check-row label { margin: 0; color: var(--vf-text); }

.row { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; margin-top: var(--space-3); }
/* A row of three or more filters needs a minimum readable width per
   control, or narrow screens squeeze every select down to a sliver
   (section 10.2: touch targets and legible controls before density). */
.row.filters > * { flex: 1 1 140px; }
.row:first-child { margin-top: 0; }
.row > * { flex: 1; }
/* A field group in a row wraps at phone width instead of squeezing to a
   few pixels (the six Time rules fields on Settings used to clip). A bare
   checkbox in a row (the timesheet's select-all) is not a div, so it keeps
   its natural width. */
.row > div { flex: 1 1 120px; }
/* The kiosk form's name box and site select wrap before they squeeze. */
#kiosk > input, #kiosk > select { flex: 1 1 140px; }
.row > button, .row > a.secondary, .row > a.primary { flex: 0 0 auto; }
.week-label { flex: 2 1 auto; text-align: center; font-weight: 600; }
.text-center { text-align: center; }
/* Fixed-width numeric input columns: a single figure, not prose (section 4.3 layout exception). */
.hours-cell, .tips-cell { width: 110px; }
#cam { margin-top: var(--space-3); }
#cam #v { width: 100%; border-radius: var(--radius-md); display: block; }

/* Quantity stepper (Purchases, staff): a minus button, a number, a plus
   button. Buttons meet the 44px touch-target floor for a phone screen
   (section 10.2). */
.row.stepper { align-items: stretch; }
.row.stepper button { flex: 0 0 auto; min-width: 44px; min-height: 44px; font-size: var(--text-lg); }
.row.stepper input { flex: 0 0 72px; text-align: center; font-variant-numeric: tabular-nums; }

.login { max-width: 360px; margin: var(--space-7) auto; }
.login .pin { letter-spacing: .3em; font-size: var(--text-2xl); text-align: center; }
/* The login logo keeps explicit pixel sizes: section 21 has no token for an
   image size. The radius and the margin come from tokens. */
.login-logo { width: 160px; height: 160px; display: block; margin: 0 auto var(--space-3); border-radius: var(--radius-md); }

/* ---------- Cards, panels, items (section 5.5) ---------- */
/* A card is a real group boundary, not decoration: hairline border,
   quiet surface, no shadow (shadow is reserved for real elevation). */
.card {
  background: var(--vf-surface);
  border: 1px solid var(--vf-border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  margin-bottom: var(--space-3);
}
.card.wrap { overflow-x: auto; }

/* A row inside a list that needs its own boundary uses a hairline
   separator instead of a nested card (section 5.5 forbids nested cards). */
.item { padding: var(--space-3) 0; border-top: 1px solid var(--vf-border); }
.item:first-child { border-top: 0; padding-top: 0; }
.item .row { margin-top: var(--space-2); }

/* Sentence case, not an ALL-CAPS eyebrow (section 14, v1.1): the caps and
   the wide letter spacing are a machine-generated tell. */
.eyebrow { font-size: var(--text-xs); color: var(--vf-text-muted); margin: 0 0 var(--space-1); }
.lede { font-size: var(--text-body); color: var(--vf-text-muted); margin: 0 0 var(--space-4); max-width: 62ch; }
.muted { color: var(--vf-text-muted); font-size: var(--text-body); }
/* A stale freshness line under a dashboard block (section 9, v1.1): the
   last good time stays on screen, in the warning colour, never a guess. */
.warn-text { color: var(--vf-warning); }
.struck { text-decoration: line-through; }
.total { font-size: var(--text-lg); font-weight: 650; font-variant-numeric: tabular-nums; }
.status-block { margin: var(--space-2) 0; }

.actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }
/* Table row actions stay quiet but are always visible, never hover-only (section 10.2). */
.actions button, .actions a { font-size: var(--text-sm); padding: var(--space-1) var(--space-2); }

/* ---------- Notices: empty, warning, success, loading, error (sections 6.9, 6.10) ---------- */
.notice {
  border-radius: var(--radius-md);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
  font-size: var(--text-body);
}
.notice.empty { background: var(--vf-surface-subtle); color: var(--vf-text-muted); border: 1px solid var(--vf-border); }
.notice.ok { background: var(--vf-success-bg); color: var(--vf-success); }
.notice.warn { background: var(--vf-warning-bg); color: var(--vf-warning); }
.notice.danger { background: var(--vf-danger-bg); color: var(--vf-danger); }
.notice.loading { background: var(--vf-surface-subtle); color: var(--vf-text-muted); border: 1px solid var(--vf-border); }
.notice strong, .notice b { font-weight: 650; }

/* ---------- Tables (section 6.3) ---------- */
table { width: 100%; border-collapse: collapse; font-size: var(--text-body); }
th, td { text-align: left; padding: var(--space-2) var(--space-2); border-bottom: 1px solid var(--vf-border); vertical-align: top; }
th { font-size: var(--text-xs); color: var(--vf-text-muted); text-transform: uppercase; letter-spacing: .04em; font-weight: 600; }
td.num, th.num, .row .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
tfoot td { border-top: 2px solid var(--vf-border-strong); border-bottom: 0; font-weight: 650; }
.wrap { overflow-x: auto; }
@media (max-width: 480px) { .hide-sm { display: none; } }

/* ---------- Badges (section 6.7) ---------- */
/* A badge always carries a word, never a symbol alone. */
.badge { display: inline-block; font-size: var(--text-xs); padding: 2px var(--space-2); border-radius: var(--radius-pill); background: var(--vf-surface-subtle); color: var(--vf-text-muted); border: 1px solid var(--vf-border); white-space: nowrap; }
.badge.approved { background: var(--vf-success-bg); color: var(--vf-success); border-color: transparent; }
.badge.pending { background: var(--vf-warning-bg); color: var(--vf-warning); border-color: transparent; }
.badge.void { background: var(--vf-danger-bg); color: var(--vf-danger); border-color: transparent; text-decoration: line-through; }
/* Declined answers a staff request; it is not struck through like a void,
   because nothing approved is being reversed. */
.badge.declined { background: var(--vf-danger-bg); color: var(--vf-danger); border-color: transparent; }

/* ---------- Grids: hours, tips, roster ---------- */
table.grid th, table.grid td { padding: var(--space-1); }
table.grid input { width: 52px; padding: var(--space-1); text-align: center; } /* fixed width: a single number, not prose */
/* A clock-sourced hours cell is tinted. The aria-label and the page legend
   carry the same meaning, so colour is never the only cue. */
input.h.clock { background: var(--vf-accent-subtle); }
table.grid .sun { background: var(--vf-warning-bg); }
table.grid td:first-child { position: sticky; left: 0; background: var(--vf-surface); white-space: nowrap; }
table.grid .p2 { background: var(--vf-accent-subtle); }
table.grid input.t { width: 64px; } /* fixed width: a Rand amount */
/* The kitchen points "from the roster" mark (core-boh-points.md Part C.3):
   text, never colour alone. aria-hidden, since the input's own aria-label
   already says "from the roster". */
.src { margin-left: var(--space-1); font-size: var(--text-xs); font-weight: 600; color: var(--vf-text-muted); }
.pts-cell, .pts-line { white-space: nowrap; }
/* A role box sits in a table cell, so it fits its text, not the cell. */
select.role { width: auto; padding: var(--space-1) var(--space-2); font-size: var(--text-sm); }

.group-row td { background: var(--vf-surface-subtle); color: var(--vf-text-muted); font-weight: 600; }

table.roster td.cell { min-width: 92px; vertical-align: top; } /* fixed width: fits one shift chip */
table.roster button.shift {
  display: block; width: 100%; margin: 2px 0; padding: var(--space-1) var(--space-2);
  border: 1px solid var(--vf-border-strong); border-radius: var(--radius-sm);
  background: var(--vf-surface-subtle); font-size: var(--text-xs); text-align: left; line-height: 1.3;
}
table.roster button.shift .shift-status { display: block; font-weight: 600; }
table.roster button.shift.pending { background: var(--vf-warning-bg); border-color: transparent; }
table.roster button.shift.approved { background: var(--vf-success-bg); border-color: transparent; }
/* Cancelled is a finished state, not a failed one (v1.1 §4.1: red is only
   for destructive, failed, critical, or urgent). It shows only when the
   "Show cancelled shifts" toggle is on, as plain muted text with no fill. */
table.roster .shift-cancelled {
  display: block; margin: 2px 0; padding: var(--space-1) var(--space-2);
  font-size: var(--text-xs); line-height: 1.3; color: var(--vf-text-muted);
}
table.roster .shift-cancelled s { text-decoration: line-through; }
table.roster button.add {
  border: 1px dashed var(--vf-border-strong); background: none; color: var(--vf-text-muted);
  width: 100%; min-height: 44px; padding: var(--space-1); font-size: var(--text-xs);
}
table.roster button.add:hover { border-color: var(--vf-accent); color: var(--vf-accent); }
/* The Fill week form's own row inherits `table.grid input { width: 52px }`
   (built for one number per grid cell), which would squeeze its text and
   time fields to that same width. Its fields need the normal full control. */
table.roster tr[data-colspan] input:not([type="checkbox"]) { width: 100%; text-align: left; }
/* The grid scrolls sideways on a phone; a form opened in one of its rows
   must not scroll away with it. The form sticks to the left edge of the
   scrolling card and never grows past the viewport, so every field and
   every day box stays in view (section 17.1: no clipping). */
table.roster tr[data-colspan] > td > form, table.roster tr[data-colspan] > td > b { display: block; position: sticky; left: var(--space-2); max-width: calc(100vw - 2 * var(--space-5)); }
table.roster tr[data-colspan] .row { flex-wrap: wrap; }

/* ---------- Build week (core-roster-build-week.md C8) ---------- */
table.roster.build td.cell { min-width: 92px; }
table.roster.build button.bcell {
  display: block; width: 100%; min-height: 44px; padding: var(--space-1) var(--space-2);
  border: 1px solid var(--vf-border-strong); border-radius: var(--radius-sm);
  background: var(--vf-surface); font-size: var(--text-sm); text-align: left; line-height: 1.3;
}
table.roster.build button.bcell[data-state="off"], table.roster.build button.bcell[data-state="leave"] { border-style: dashed; color: var(--vf-text-muted); }
table.roster.build button.bcell[data-state="draft"], table.roster.build button.bcell[data-state="custom"] { background: var(--vf-surface-subtle); }
table.roster.build button.bcell.pending { background: var(--vf-warning-bg); border-color: transparent; }
table.roster.build button.bcell.approved { background: var(--vf-success-bg); border-color: transparent; }
/* Reviewer fixes (1c review): the Saving mark shows only while the cell is
   busy; the status word sits on its own line, as the grid's chips do; the
   row header is a name, not a column heading, and it stays put when the
   table scrolls sideways (C5; the grid's sticky rule is on td:first-child). */
table.roster.build button.bcell .saving { display: none; }
table.roster.build button.bcell[aria-busy="true"] .saving { display: block; font-size: var(--text-xs); color: var(--vf-text-muted); }
table.roster.build button.bcell .shift-status { display: block; font-weight: 600; }
table.roster.build tbody th { position: sticky; left: 0; background: var(--vf-surface); font-size: var(--text-sm); color: var(--vf-text); text-transform: none; letter-spacing: 0; font-weight: 600; }
table.roster.build tfoot td { color: var(--vf-text-muted); font-weight: 600; }
.seg { display: inline-flex; gap: var(--space-1); }
.seg button[aria-pressed="true"] { background: var(--vf-primary-subtle); font-weight: 600; }

/* ---------- Build week, sections (core-roster-sections.md C6) ---------- */
/* The label cell is static, not sticky: the row header above makes every
   tbody th stick at left 0, and two sticky cells at one edge overlap when
   the table scrolls sideways (a 1024 px tablet). The name column stays;
   the label scrolls with the grid. */
table.roster.build.sections tbody th.section { position: static; background: var(--vf-surface-subtle); font-weight: 600; vertical-align: top; text-align: left; }
table.roster.build.sections tbody.band > tr:first-child > * { border-top: 2px solid var(--vf-border-strong); }
table.roster.build .section-tag { display: none; font-size: var(--text-xs); color: var(--vf-text-muted); }
@media (max-width: 759px) {
  /* Every Section cell hides: the header's, each band's label, the footer's. */
  table.roster.build.sections .section { display: none; }
  table.roster.build .section-tag { display: block; }
  table.roster.build button[data-move] { min-height: 44px; }
}
table.roster.build button[data-move] { margin-left: var(--space-1); }

/* ---------- Manager shell: the tab row (sections 5.1, 6.6) ----------
   When the open page belongs to an item with tabs (Pay cycle), this row
   lists the peer pages of that item, with the open one marked. It is hidden
   on every other page. */
#tabs {
  display: flex;
  gap: var(--space-1);
  overflow-x: auto;
  scrollbar-width: thin;
  padding: var(--space-2) var(--space-4);
  background: var(--vf-surface-subtle);
  border-bottom: 1px solid var(--vf-border);
}
#tabs[hidden] { display: none; }
#tabs a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--vf-text-muted);
  font-size: var(--text-sm);
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  transition: background var(--motion-fast) ease, color var(--motion-fast) ease;
}
#tabs a:hover { background: var(--vf-surface); color: var(--vf-text); }
#tabs a.active { background: var(--vf-accent-subtle); color: var(--vf-accent); font-weight: 650; }
/* Below 760 px (standard section 10, compact) the tab row wraps, so every
   tab is visible with no horizontal scroll. */
@media (max-width: 759.98px) {
  #tabs { flex-wrap: wrap; overflow-x: visible; }
  #tabs a { min-width: 44px; }
}

/* ---------- Manager shell: the sidebar (sections 5.1, 6.5, 10.2) ----------
   One breakpoint: 1051 px, the start of the Wide class (section 10). The
   rules below are the drawer, for a manager below 1051 px. The media query
   at the end of this block makes it a fixed sidebar. A staff login and a
   signed-out page have no sidebar and no menu button at any width. */
#side {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  width: 248px;
  max-width: 100%;
  overflow-y: auto;
  background: var(--vf-surface);
  border-right: 1px solid var(--vf-border);
  box-shadow: var(--shadow-overlay);
  /* Closed drawer: off screen and out of the tab order. */
  visibility: hidden;
  transform: translateX(-100%);
  transition: transform var(--motion-overlay) ease, visibility 0s linear var(--motion-overlay);
}
#side[hidden] { display: none; }
body.side-open #side { visibility: visible; transform: none; transition: transform var(--motion-overlay) ease, visibility 0s; }
/* While the drawer is open the page behind it does not scroll. */
body.side-open { overflow: hidden; }
#sideBackdrop { position: fixed; inset: 0; z-index: 15; background: var(--vf-text); opacity: .4; }
#sideBackdrop[hidden] { display: none; }
/* The menu button, in the header of a manager below 1051 px. */
#menuBtn { margin-left: auto; min-height: 44px; min-width: 44px; padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); }
#menuBtn:hover { background: var(--vf-surface-subtle); }

.side-brand { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-4); border-bottom: 1px solid var(--vf-border); }
#sideTenant { font-weight: 650; overflow-wrap: anywhere; }
#side nav { flex: 1; padding: var(--space-2); }
.side-group + .side-group, .side-group + .side-link, .side-link + .side-group { margin-top: var(--space-1); }
.side-group ul { list-style: none; margin: 0; padding: 0; }
.side-group ul[hidden] { display: none; }

/* A group button and a link share one row shape. */
.side-group-btn, .side-link {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--vf-text-muted);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--motion-fast) ease, color var(--motion-fast) ease;
}
.side-group-btn { justify-content: space-between; color: var(--vf-text); font-weight: 600; }
/* The chevron shows the group state. Its text is empty for assistive technology. */
.side-group-btn::after { content: "›" / ""; transition: transform var(--motion-fast) ease; }
.side-group-btn[aria-expanded="true"]::after { transform: rotate(90deg); }
.side-group ul .side-link { padding-left: var(--space-5); }
.side-group-btn:hover, .side-link:hover { background: var(--vf-surface-subtle); color: var(--vf-text); }
/* The open page is obvious without colour alone: bold weight plus the accent tint (section 6.5). */
.side-link.active { background: var(--vf-accent-subtle); color: var(--vf-accent); font-weight: 650; }

.side-foot { display: flex; flex-direction: column; gap: var(--space-1); padding: var(--space-2); border-top: 1px solid var(--vf-border); }
#sideUser { padding: var(--space-2) var(--space-3); color: var(--vf-text); font-size: var(--text-sm); font-weight: 600; overflow-wrap: anywhere; }

/* Manager, 1051 px and wider: a fixed sidebar. The header is gone, and the
   page makes room at the left with the body padding, so main, the error
   banner, and the tab row keep their centring in the space that is left. */
@media (min-width: 1051px) {
  #side { visibility: visible; transform: none; box-shadow: none; transition: none; }
  #sideBackdrop { display: none; }
  body.mgr { padding-left: 248px; }
  body.mgr header#top { display: none; }
  .side-group-btn, .side-link { min-height: 0; }
}

/* ---------- Dashboard (manager): action list and KPI row (sections 6.4, 8, 9) ---------- */
.action-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
  padding: var(--space-2) 0;
  border-top: 1px solid var(--vf-border);
  color: var(--vf-text);
  font-size: var(--text-body);
  text-decoration: none;
}
.action-row:first-child { border-top: 0; }
.action-row:hover { color: var(--vf-accent); }

.kpi-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-3); }
@media (min-width: 640px) { .kpi-row { grid-template-columns: repeat(4, 1fr); } }
.kpi {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-4);
  background: var(--vf-surface);
  border: 1px solid var(--vf-border);
  border-radius: var(--radius-md);
  color: var(--vf-text);
  text-decoration: none;
  transition: border-color var(--motion-fast) ease;
}
.kpi:hover { border-color: var(--vf-accent); }
.kpi-label { font-size: var(--text-xs); color: var(--vf-text-muted); }
.kpi-value { font-size: var(--text-2xl); font-weight: 650; font-variant-numeric: tabular-nums; }

/* ---------- Staff shell: home tiles, back link (sections 6.5, 10.3) ----------
   A staff login never sees the manager sidebar; the header carries a
   single "Home" link instead (hidden on #home). Every staff page starts
   with a "back to home" link above its heading. */
header a.link { text-decoration: none; }

a.back {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin: 0 0 var(--space-1) calc(var(--space-2) * -1);
  padding: 0 var(--space-2);
  color: var(--vf-text-muted);
  font-size: var(--text-body);
  font-weight: 500;
  text-decoration: none;
}
a.back:hover { color: var(--vf-accent); }

.tiles { display: grid; grid-template-columns: 1fr; gap: var(--space-3); margin-top: var(--space-5); }
@media (min-width: 640px) { .tiles { grid-template-columns: 1fr 1fr; } }
a.tile {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-1);
  min-height: 72px;
  padding: var(--space-4);
  background: var(--vf-surface);
  border: 1px solid var(--vf-border);
  border-radius: var(--radius-md);
  color: var(--vf-text);
  text-decoration: none;
  transition: border-color var(--motion-fast) ease;
}
a.tile:hover { border-color: var(--vf-accent); }
a.tile .tile-title { font-size: var(--text-lg); font-weight: 650; }
a.tile .tile-sub { font-size: var(--text-sm); color: var(--vf-text-muted); min-height: 1.3em; }
/* The sign-in tile is the first, largest tile: the code must appear with
   zero taps, so its own tile earns the most weight on the home screen. */
a.tile.primary { grid-column: 1 / -1; border-color: var(--vf-accent); }
a.tile.primary .tile-title { font-size: var(--text-xl); }

/* ---------- Toast (low-risk success feedback only, section 6.9) ---------- */
#toast {
  position: fixed; left: 50%; bottom: var(--space-5); transform: translateX(-50%);
  background: var(--vf-text); color: var(--vf-on-accent);
  padding: var(--space-3) var(--space-4); border-radius: var(--radius-sm);
  max-width: 90vw; z-index: 9; box-shadow: var(--shadow-subtle);
  font-size: var(--text-body);
}

/* ---------- Kiosk (dark-surface exception; wall display, section 1) ----------
   The kiosk runs on a fixed device near an entrance. High contrast on a
   dark surface reads better at a distance and under glare than the
   light Kiln surface used everywhere else. This is the one place the
   product departs from --vf-bg / --vf-surface, and it is documented here. */
body.kiosk {
  margin: 0;
  min-height: 100vh;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center;
  background: var(--kiosk-bg);
  color: var(--kiosk-text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Helvetica, Arial, sans-serif;
}
/* The kiosk logo keeps explicit pixel sizes: section 21 has no token for an
   image size. The radius and the margin below it come from tokens. */
.kiosk-logo { width: 120px; height: 120px; border-radius: var(--radius-md); margin-bottom: var(--space-2); }
body.kiosk .kiosk-title { font-size: var(--text-display); font-weight: 650; margin: var(--space-2); }
body.kiosk .kiosk-muted { color: var(--kiosk-text-muted); font-size: var(--text-md); }
body.kiosk #qr { background: var(--kiosk-surface); padding: var(--space-4); border-radius: var(--radius-md); margin: var(--space-4); }
body.kiosk canvas { display: block; width: min(70vw, 70vh); height: auto; }
body.kiosk #pad { display: none; flex-direction: column; gap: var(--space-3); width: min(90vw, 360px); }
body.kiosk #pad input {
  font-size: var(--text-2xl); text-align: center; letter-spacing: .3em;
  padding: var(--space-3); border-radius: var(--radius-sm); border: 0;
  min-height: 56px; /* touch-target floor for a shared wall device, section 10.2 */
}
body.kiosk #pad button.primary {
  font-size: var(--text-lg); padding: var(--space-4); min-height: 56px;
  border-radius: var(--radius-sm); background: var(--vf-accent); color: var(--vf-on-accent); border: 0; font-weight: 650;
}
body.kiosk #pad button.link { background: none; color: var(--kiosk-text-muted); font-size: var(--text-body); text-decoration: underline; border: 0; padding: var(--space-2); }
body.kiosk #showPad { background: none; color: var(--kiosk-text-muted); font-size: var(--text-body); text-decoration: underline; border: 0; padding: var(--space-2); cursor: pointer; }
/* The state confirmation is the one thing a person must read from across the room. */
body.kiosk #msg { min-height: 60px; font-size: var(--text-2xl); font-weight: 650; }
body.kiosk #msg.ok { color: var(--kiosk-ok); }
body.kiosk #msg.err { color: var(--kiosk-err); }

/* Dashboard freshness (standard §9, v1.1): one quiet line per block,
   beside its label, never a second loose line of small text. */
.card-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-3); flex-wrap: wrap; }
.card-head h2 { margin: 0; }
.fresh { color: var(--vf-text-muted); font-size: var(--text-xs); font-weight: 400; font-variant-numeric: tabular-nums; }
.eyebrow .fresh::before { content: " · "; }
.fresh.warn-text { color: var(--vf-warning); }

/* ---------- Rules console and the staff rules page (core-02-brain-lite.md Part E) ----------
   The rule drawer is a real layer over the page, so it earns the overlay
   shadow. Everything else stays on hairlines. */
.rules-heading { margin: var(--space-5) 0 var(--space-2); }
.rules-others > summary { cursor: pointer; min-height: 44px; display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.rules-others h3 { margin: var(--space-4) 0 var(--space-1); font-size: var(--text-md); }
.rule-drawer { border-radius: var(--radius-lg); box-shadow: var(--shadow-overlay); }
.rule-drawer h2 { margin-top: 0; }
.rule-drawer h3 { margin: var(--space-4) 0 var(--space-1); font-size: var(--text-md); }
.rule-block { border-top: 1px solid var(--vf-border); padding-top: var(--space-3); margin-top: var(--space-3); }
.rule-block h3 { margin-top: 0; }
.tests-list, .rule-list { margin: var(--space-2) 0; padding-left: var(--space-5); }
.rule-list li, .tests-list li { margin-bottom: var(--space-1); }
.rule-actions { margin: var(--space-3) 0; }
.rules-table td.num, .rules-table th.num { text-align: right; font-variant-numeric: tabular-nums; }
/* Under 760 px a rule is a stacked block: each cell carries its own label. */
@media (max-width: 759px) {
  .rules-table, .rules-table tbody, .rules-table tr, .rules-table td { display: block; width: 100%; }
  .rules-table thead { display: none; }
  .rules-table tr { padding: var(--space-3) 0; border-bottom: 1px solid var(--vf-border); }
  .rules-table td { border: 0; padding: var(--space-1) 0; }
  .rules-table td.num { text-align: left; }
  .rules-table td[data-label]:not([data-label="Rule"])::before { content: attr(data-label) ": "; color: var(--vf-text-muted); font-size: var(--text-xs); }
  .rules-table .hide-sm { display: block; }
  .rules-table .actions button, .rule-drawer button { min-height: 44px; }
}
