:root {
  /* Dark corporate theme - see static/style.css redesign. Every component
     in the app reads color from these tokens rather than hardcoding hex,
     so this block is the single place a future palette change happens. */
  --bg: #0B1220;
  --panel: #1A2332;
  --panel-2: #141B29;
  --text: #E5E7EB;
  --muted: #94A3B8;
  --border: #2A3548;
  --accent: #305CDE;
  --accent-hover: #4F74E3;
  --accent-active: #274BB6;
  --accent-tint: #1E2A4A;
  --accent-tint-border: #33447A;
  --accent2: #10b981;
  --accent2-tint: #123128;
  --accent2-text: #6EE7B7;
  /* Same relationship to --accent2 as --danger-solid has to --danger: the
     lighter tone above is tuned for text and borders on a dark background,
     these two are the darker fills that keep white text readable on top.
     (emerald-600 / emerald-700.) */
  --accent2-solid: #059669;
  --accent2-solid-hover: #047857;
  --warn: #f59e0b;
  --warn-tint: #3A2A12;
  --warn-text: #FBBF24;
  /* --danger is for text/borders (tuned lighter than the old light-theme
     value so it stays readable on a dark background); --danger-solid is
     for the one filled-button-with-white-text usage, where the original
     darker red keeps proper contrast against white. */
  --danger: #F87171;
  --danger-solid: #DC2626;
  --danger-tint: #3A1E1E;
  --danger-tint-border: #5A2E2E;
  --teal: #2DD4BF;
  --teal-tint: #123330;
}

* { box-sizing: border-box; }

/* The browser's own [hidden] rule is a UA style, so ANY author rule that
   sets display beats it - a `.thing { display: grid }` element stays fully
   visible with hidden="" set on it, and nothing in the markup hints at why.
   That is how the Add New User modal came to show Name and Email twice: the
   edit-only pair was hidden, and display:grid ignored it. Stated once here
   so `el.hidden = true` means what everyone reading it thinks it means. */
[hidden] { display: none !important; }

body {
  margin: 0;
  font-family: -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
  /* Tells the browser to render native controls (date/month picker icons,
     scrollbars) in their dark variant, so e.g. the <input type="date">
     calendar icon doesn't render as a barely-visible dark icon on our now-
     dark input background. */
  color-scheme: dark;
}

.topbar {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding: 14px 24px;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
}

.brand {
  font-weight: 700;
  font-size: 1.1rem;
  justify-self: center;
  white-space: nowrap;
  color: var(--warn);
  display: flex;
  align-items: center;
}

/* Small colored icon box to the left of the app name - the app's only
   "logo," no image asset needed. */
.brand-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 7px;
  background: var(--warn);
  font-size: 0.95rem;
  margin-right: 8px;
  flex-shrink: 0;
}

.topbar-left { justify-self: start; }
.topbar-right { justify-self: end; display: flex; align-items: center; }

.topbar-username { color: var(--muted); margin-right: 12px; font-size: 0.85rem; }

.role-badge {
  display: inline-block;
  background: var(--accent-tint);
  color: var(--accent);
  font-size: 0.7rem;
  font-weight: 600;
  padding: 2px 7px;
  border-radius: 999px;
  margin-left: 4px;
  vertical-align: middle;
}

.payment-badge {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 0.78rem;
  font-weight: 600;
  white-space: nowrap;
}

.payment-badge.paid { background: var(--accent2-tint); color: var(--accent2-text); }
.payment-badge.pending { background: var(--warn-tint); color: var(--warn-text); }
.payment-badge.gcash { background: var(--accent-tint); color: var(--accent); }
/* Sold on credit, since settled. A fourth colour, so it reads as neither an
   outstanding balance (amber) nor money that came in on the day (green). The
   colour is the lesser half of the distinction: the label itself says
   "Credit · Paid" rather than "Cash", because a difference carried only by
   colour is no difference at all to anyone who cannot see it. */
.payment-badge.settled { background: var(--teal-tint); color: var(--teal); }

/* --- Sold vs paid: the gap, and what the money is doing ------------------
   See static/sale_dates.js. Every one of these states says what it is in
   words as well as in colour ("Unpaid (12d)", "same day", "+5d"), because a
   distinction carried only by colour is no distinction on a printed page or
   to a reader who cannot see it. The colour is the fast path, not the
   only path. */

/* Settled on the day it was sold - stated, but quietly. This is the ordinary
   case and should not compete for attention with the ones that need it. */
.lag-same { color: var(--muted); font-size: 0.8rem; margin-left: 4px; }

/* Settled later. The clock is the glanceable half, "+5d" the exact half. */
.lag-later {
  color: var(--teal);
  font-size: 0.8rem;
  margin-left: 4px;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 2px;
}
.lag-clock { width: 11px; height: 11px; vertical-align: -1px; }

.sale-status {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 0.78rem;
  font-weight: 600;
  white-space: nowrap;
}
/* Paid on the day: green, plain, no icon - nothing to chase. */
.sale-status.paid-same { background: var(--accent2-tint); color: var(--accent2-text); }
/* Paid, but late. Still green - the money did arrive - with the lag stated. */
.sale-status.paid-late { background: var(--teal-tint); color: var(--teal); }
/* Outstanding, but not yet old enough to worry about. */
.sale-status.unpaid { background: var(--warn-tint); color: var(--warn-text); }
/* Outstanding past OVERDUE_AFTER_DAYS. */
.sale-status.overdue { background: var(--danger-tint); color: var(--danger); }

/* The row-level cue. A 3px left border is enough to scan a long list for
   unpaid sales without reading any cell, and narrow enough not to shift the
   table's alignment - the transparent border on every other row is what
   keeps the columns from jumping. */
tbody tr td:first-child { border-left: 3px solid transparent; }
tbody tr.row-unpaid td:first-child { border-left-color: var(--warn-text); }
tbody tr.row-overdue td:first-child { border-left-color: var(--danger); }

/* Shown next to every Sale #/Harvest # (see record_meta.js's
   plantBadgeFromRefNumber) so which plant a plant-scoped reference number
   belongs to is obvious without reading the number string itself. Not
   used for Expense #/Remittance #, which aren't plant-specific. */
.plant-badge {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 600;
  white-space: nowrap;
  margin-left: 4px;
}
.plant-badge.plant-1 { background: var(--accent-tint); color: var(--accent); }
.plant-badge.plant-2 { background: var(--accent2-tint); color: var(--accent2-text); }

.topbar-right a {
  text-decoration: none;
  color: var(--muted);
  font-weight: 500;
  padding: 6px 10px;
  border-radius: 6px;
}

.topbar-right a:hover { color: var(--text); background: var(--bg); }

.brand-link {
  color: inherit;
  text-decoration: none;
}

.brand-link:hover {
  color: var(--accent);
  text-decoration: underline;
}

/* Shared outline-icon set (templates/_icons.html) - fallback size so an
   SVG never renders at the browser's unstyled default (300x150) if used
   somewhere that doesn't set its own width/height. */
.tile-icon { width: 24px; height: 24px; flex-shrink: 0; }

.container {
  max-width: 1100px;
  margin: 24px auto;
  padding: 0 20px;
}

h1 { font-size: 1.4rem; margin-bottom: 4px; }
h2 { font-size: 1.05rem; color: var(--muted); font-weight: 600; margin: 28px 0 12px; }

.card-group { margin-top: 20px; }

.card-group-title {
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  margin: 0 0 10px 2px;
  display: flex;
  align-items: center;
  gap: 8px;
}

.card-group-title::before {
  content: "";
  width: 3px;
  height: 12px;
  border-radius: 2px;
  background: var(--accent-tint-border);
}

.card-group.financials .card-group-title::before { background: var(--accent2); }
.card-group.operations .card-group-title::before { background: var(--accent); }

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 14px;
}

.card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px 18px;
  min-height: 112px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* Shift Roster's 3 shift cards need a fixed 3-column grid (not .cards'
   default auto-fit, which can settle on 1-2 columns and wrap unevenly),
   top-aligned content (not .card's default vertical-centering, meant for
   short label/value/delta summary cards), and equal height regardless of
   how many people are assigned to each shift. */
.shift-roster-cards {
  grid-template-columns: repeat(3, 1fr);
  align-items: stretch;
}
.shift-roster-cards .card {
  justify-content: flex-start;
  /* min-width:0 is what actually lets a grid column shrink below the width
     of its widest child - without it a long personnel name holds the column
     open and the whole row overflows the viewport. overflow:hidden only
     clips the symptom. */
  min-width: 0;
  overflow: hidden;
}
/* The assigned-people table. Auto layout, not fixed percentages: the status
   select and the Remove button are sized by their content and the name takes
   what's left, so neither control gets clipped when the card is narrow -
   which is exactly what fixed 46/34/20 columns did to "Remove". */
.shift-roster-table {
  width: 100%;
  max-width: 100%;
  margin-top: 10px;
  font-size: 0.88rem;
  table-layout: auto;
}
.shift-roster-table td {
  padding: 5px 0;
  vertical-align: middle;
}
.shift-roster-table .sr-name {
  white-space: normal;
  overflow-wrap: anywhere;
  padding-right: 8px;
}
.shift-roster-table .sr-position { color: var(--muted); }
/* Four controls will not fit beside a name in a third-of-a-row card, and
   the fourth was being clipped. The name takes its own line and the
   controls share the next one, at any card width - no breakpoint to get
   wrong, because the wrap is what does it. */
.shift-roster-table tr {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  padding: 4px 0;
}
.shift-roster-table tr + tr { border-top: 1px solid var(--border); }
.shift-roster-table td { padding: 0; border: none; }
.shift-roster-table .sr-name { flex: 1 1 100%; padding-right: 0; }
.shift-roster-table .sr-role,
.shift-roster-table .sr-status,
.shift-roster-table .sr-action { white-space: nowrap; width: auto; flex: 0 0 auto; }
.shift-roster-table .sr-role select,
.shift-roster-table .sr-status select { padding: 3px 5px; font-size: 0.8rem; }
.shift-roster-table .sr-action { margin-left: auto; padding-left: 0; }

/* The rate this shift pays. Read-only until somebody deliberately overrides
   it, because the standing rate is right nearly always. */
.shift-roster-table .sr-rate { white-space: nowrap; flex: 0 0 auto; }
.shift-roster-table .sr-rate.is-editing { flex: 1 1 100%; white-space: normal; }
.sr-rate-value { font-variant-numeric: tabular-nums; font-weight: 600; }
/* What a Half Day actually pays, beside the rate it halves. */
.sr-half { color: var(--accent); font-weight: 600; }
.sr-rate-link {
  background: none;
  border: none;
  padding: 0 0 0 4px;
  color: var(--accent);
  font-size: 0.72rem;
  cursor: pointer;
  text-decoration: underline;
}
.sr-rate-link:disabled { color: var(--muted); cursor: not-allowed; text-decoration: none; }
.sr-rate-edit { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; width: 100%; }
.sr-rate-input { width: 90px; padding: 3px 5px; font-size: 0.8rem; }
.sr-rate-note { flex: 1 1 140px; min-width: 0; padding: 3px 5px; font-size: 0.8rem; }
.sr-rate-actions { white-space: nowrap; }

/* A rate that isn't the standing one, marked so it stands out on a roster
   that is otherwise all defaults. */
.sr-custom-badge {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--warn-tint, var(--accent-tint));
  border: 1px solid var(--warn, var(--border));
  color: var(--warn, var(--accent2-text));
  font-size: 0.66rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
  cursor: help;
}

/* Working a shift as something other than your profile role is unusual
   enough to mark, and ordinary enough not to shout about. */
.sr-override {
  color: var(--accent2-text);
  font-weight: 700;
  cursor: help;
  margin-left: 2px;
}

/* Carried by BOTH of a person's rows that day, so neither shift reads as
   the ordinary one. Title holds the reason that was given for it. */
.sr-double-badge {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--warn-tint, var(--accent-tint));
  border: 1px solid var(--border);
  color: var(--warn, var(--accent2-text));
  font-size: 0.66rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
  cursor: help;
}

/* Two separate stints in a day is a bigger ask than a back-to-back pair, and
   the dialog looks like it. */
/* Its own hooks, deliberately not the shared dialog's .cd-* ones - two
   modals answering to one selector is how a test (or a script) ends up
   reading the wrong one. */
.ds-message { margin: 0 0 12px; color: var(--text); font-size: 0.9rem; }
#double-shift-overlay.ds-severe #double-shift-ok {
  background: var(--danger-solid, var(--danger));
  border-color: var(--danger);
}
.shift-roster-table .sr-action { padding-left: 6px; text-align: right; }
.shift-roster-table .sr-action .btn-delete { padding: 3px 7px; font-size: 0.72rem; }

@media (max-width: 720px) {
  .shift-roster-cards {
    grid-template-columns: 1fr;
  }
}

/* Three staged "add person" rows per card, stacked full-width. They were
   once three dropdowns side by side in one row, which left each about 85px
   wide and truncated the placeholder to "+ Add pe" - stacking is what buys
   each row the width to show its real label and a chosen person's full name
   and role. */
.shift-add {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
.shift-add-rows {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* Select takes the width; the remove button takes what it needs. minmax(0,
   1fr) rather than 1fr is what actually lets the select shrink - a grid
   item's default min-width:auto would hold the row open at the width of the
   longest name and overflow the card. */
.shift-add-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 6px;
  align-items: center;
}
.shift-add-select {
  display: block;
  width: 100%;
  min-width: 0;
  padding: 8px 10px;
  font-size: 0.85rem;
  /* No ellipsis: a stacked row is wide enough for the label, and clipping it
     was the bug this layout exists to fix. */
  text-overflow: clip;
}
.shift-add-select:disabled { opacity: 0.55; }

.shift-add-remove {
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0;
  width: 30px;
  height: 30px;
  font-size: 1rem;
  line-height: 1;
  flex: 0 0 auto;
}
.shift-add-remove:hover:not(:disabled) {
  background: var(--danger-tint);
  border-color: var(--danger);
  color: var(--danger);
}
/* Disabled on the last remaining row - a card with no way to add anybody is
   a dead end. */
.shift-add-remove:disabled { opacity: 0.3; }

.shift-add-more {
  background: transparent;
  border: none;
  color: var(--accent);
  font-size: 0.8rem;
  font-weight: 600;
  padding: 6px 0 0;
  align-self: flex-start;
}
.shift-add-more:hover { text-decoration: underline; }
.shift-add-more[hidden] { display: none; }

/* Confirm is separated from the stack so it doesn't read as another row, and
   only exists while a row is holding a selection. */
.shift-add-confirm {
  display: block;
  width: 100%;
  margin-top: 14px;
  padding: 8px 12px;
  font-size: 0.82rem;
}
.shift-add-confirm[hidden] { display: none; }
.shift-add-confirm:disabled { opacity: 0.45; }

/* With no rows open, the add block is just the "+ Add another" link - the
   divider and its padding would otherwise fence off a single line of text. */
.shift-add:has(.shift-add-rows:empty) {
  margin-top: 8px;
  padding-top: 8px;
}

/* The staged count sits next to the saved one rather than replacing it -
   a pending pick isn't an assignment yet. */
.shift-assigned-count {
  font-size: 0.8rem;
  color: var(--muted);
  white-space: nowrap;
}
.shift-assigned-count.has-staged { color: var(--accent2-text); }

.card .label { color: var(--muted); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; }
/* What the figure counts, on the card itself. This replaced a paragraph
   above the row that had to re-name each card to explain it. Focusable so
   it isn't mouse-only. */
.card-help {
  cursor: help;
  opacity: 0.65;
  font-size: 0.85em;
  text-transform: none;
}
.card-help:hover, .card-help:focus-visible { opacity: 1; }
.card .value { font-size: 1.6rem; font-weight: 700; margin-top: 6px; }
.card .value.value-positive { color: var(--accent2); }
.card .value.value-negative { color: var(--danger); }

.card .delta { font-size: 0.75rem; font-weight: 600; margin-top: 6px; }
.card .delta.delta-up { color: var(--accent2); }
.card .delta.delta-down { color: var(--danger); }
.card .delta.delta-flat { color: var(--muted); font-weight: 500; }

/* Always paired with .card - deliberately omits `display` so .card's own
   `display: flex; flex-direction: column; justify-content: center;`
   keeps vertically centering the label/value/delta stack, whether the
   element underneath is a div, a button, or (for direct navigation) an
   anchor. */
.card-link {
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  transition: box-shadow 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}

.card-link:hover {
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08);
  border-color: var(--accent-tint-border);
  transform: translateY(-1px);
}

.ar-panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  margin-top: 16px;
  overflow: hidden;
}

.ar-panel.card-link-panel {
  transition: box-shadow 0.15s ease, border-color 0.15s ease;
}

.ar-panel.card-link-panel:hover {
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08);
  border-color: var(--accent-tint-border);
}

.ar-summary.clickable { cursor: pointer; }

.ar-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 18px;
}

.ar-summary .label { color: var(--muted); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; }
.ar-summary .value { font-size: 1.6rem; font-weight: 700; margin-top: 6px; color: var(--danger); }
.ar-summary .sub { font-size: 0.8rem; color: var(--muted); margin-top: 4px; }

.ar-summary .value.status-ok { color: var(--accent2); }
.ar-summary .value.status-warn { color: var(--danger); }

td.balance-negative { color: var(--danger); font-weight: 700; background: var(--danger-tint); }

.flash {
  padding: 12px 16px;
  border-radius: 8px;
  margin-bottom: 16px;
  font-size: 0.9rem;
  font-weight: 600;
}

.flash-warning {
  background: var(--danger-tint);
  color: var(--danger);
  border: 1px solid var(--danger-tint-border);
}

.flash-info {
  background: var(--accent-tint);
  color: var(--accent);
  border: 1px solid var(--accent-tint-border);
}

#ar-toggle-btn {
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--border);
  font-weight: 600;
}

#ar-toggle-btn:hover { background: var(--bg); opacity: 1; }

#ar-details table { border: none; border-radius: 0; border-top: 1px solid var(--border); }

.chart-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
  gap: 18px;
  margin-top: 8px;
}

.chart-panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px;
}

.chart-panel h3 { margin: 0 0 10px; font-size: 0.95rem; color: var(--muted); }

.cashflow-toggle { display: flex; gap: 4px; }

.toggle-btn {
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--border);
  padding: 4px 12px;
  font-size: 0.78rem;
  font-weight: 600;
}

.toggle-btn.active { background: var(--accent-tint); color: var(--accent); border-color: var(--accent-tint-border); }
.toggle-btn:hover { background: var(--bg); }
.toggle-btn.active:hover { background: var(--accent-tint); }

.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: end;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px;
  margin-bottom: 16px;
}

.filters label {
  display: flex;
  flex-direction: column;
  font-size: 0.78rem;
  color: var(--muted);
  gap: 4px;
}

.date-filter-label {
  display: flex;
  align-items: center;
  gap: 6px;
  align-self: center;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
}

.date-filter-label .filter-icon { font-size: 1rem; }

.date-filter-group {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  align-self: center;
}

.custom-range-inputs {
  display: none;
  align-items: center;
  gap: 6px;
}

.range-to-label { color: var(--muted); font-size: 0.85rem; }

.period-nav {
  display: none;
  align-items: center;
  gap: 6px;
}

input, select {
  padding: 7px 9px;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 0.9rem;
  background: var(--panel-2);
  color: var(--text);
}

input:focus, select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-tint);
}

button {
  padding: 8px 16px;
  border: none;
  border-radius: 6px;
  background: var(--accent);
  color: white;
  font-weight: 600;
  cursor: pointer;
  font-size: 0.9rem;
}

button:hover { background: var(--accent-hover); }
button:active { background: var(--accent-active); }

button:disabled {
  background: var(--border);
  color: var(--muted);
  cursor: not-allowed;
  opacity: 0.7;
}

/* The one arrow. Every ◀/▶ in the app resolves to this rule - the page-level
   navigators (.nav-arrow) and the ones date_stepper.js builds
   (.date-stepper-arrow) used to be two different sizes, colours and radii
   sitting next to each other on the same control. Same size, same colour,
   same hover, same disabled state, one definition. */
.nav-arrow,
.date-stepper-arrow {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 7px 10px;
  font-size: 0.8rem;
  line-height: 1;
  flex: 0 0 auto;
  /* Both arrows are buttons and both say so. */
  cursor: pointer;
}

.nav-arrow:hover:not(:disabled),
.date-stepper-arrow:hover:not(:disabled) {
  background: var(--bg);
  border-color: var(--accent-tint-border);
}

.nav-arrow:disabled,
.date-stepper-arrow:disabled,
.nav-arrow.is-disabled,
.date-stepper-arrow.is-disabled {
  background: transparent;
  color: var(--muted);
  opacity: 0.35;
  cursor: not-allowed;
}

/* period_picker.js's clean-text-label + calendar/month popover, replacing
   native <input type="date">/<input type="month"> wherever the app has a
   "◀ [value] ▶" single-period navigator. */
.period-nav-wrap { position: relative; display: inline-flex; }

.period-nav-label {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 7px 12px;
  font-size: 0.9rem;
  font-weight: 600;
  white-space: nowrap;
  min-width: 130px;
  text-align: center;
}
.period-nav-label:hover { background: var(--bg); border-color: var(--accent-tint-border); }

.period-nav-popover {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: 20;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08);
  padding: 10px;
  width: 220px;
}

.pnp-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
}
.pnp-title { font-weight: 600; font-size: 0.85rem; }
.pnp-nav {
  background: transparent;
  border: none;
  color: var(--text);
  font-size: 1.1rem;
  line-height: 1;
  padding: 2px 8px;
  border-radius: 6px;
  cursor: pointer;
}
.pnp-nav:hover { background: var(--bg); }

.pnp-weekdays {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  text-align: center;
  font-size: 0.72rem;
  color: var(--muted);
  margin-bottom: 4px;
}

.pnp-grid, .pnm-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
}
.pnm-grid { grid-template-columns: repeat(3, 1fr); gap: 4px; }

.pnp-cell, .pnm-cell {
  background: transparent;
  border: none;
  border-radius: 6px;
  padding: 6px 0;
  font-size: 0.8rem;
  color: var(--text);
  cursor: pointer;
  text-align: center;
}
.pnm-cell { padding: 10px 0; font-weight: 600; }
.pnp-cell:hover:not(:disabled), .pnm-cell:hover:not(:disabled) { background: var(--bg); }
.pnp-cell.pnp-empty { cursor: default; }
.pnp-cell.pnp-today, .pnm-cell.pnp-today { color: var(--accent); font-weight: 700; }
.pnp-cell.pnp-selected, .pnm-cell.pnp-selected { background: var(--accent); color: #fff; }
.pnp-cell.pnp-disabled, .pnm-cell.pnp-disabled { color: var(--muted); opacity: 0.35; cursor: not-allowed; }

.pnp-footer { margin-top: 6px; text-align: center; }
.pnp-today-btn {
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 4px 10px;
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
}
.pnp-today-btn:hover { background: var(--bg); }

.btn-delete {
  background: transparent;
  color: var(--danger);
  border: 1px solid var(--border);
  padding: 4px 10px;
  font-size: 0.8rem;
  font-weight: 600;
}

.btn-delete:hover { background: var(--danger-tint); opacity: 1; }
.btn-delete:active { background: var(--danger-tint-border); }

.btn-undo, .btn-restore, .btn-edit {
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--border);
  padding: 4px 10px;
  font-size: 0.8rem;
  font-weight: 600;
  margin-right: 6px;
  text-decoration: none;
  display: inline-block;
}

.btn-undo:hover, .btn-restore:hover, .btn-edit:hover { background: var(--accent-tint); opacity: 1; }
.btn-undo:active, .btn-restore:active, .btn-edit:active { background: var(--accent-tint-border); }

/* Shared style for every clickable auto-generated reference number (Sale #,
   Harvest #, Expense #, Remittance #) wherever one appears - list rows,
   detail views, and cross-references between records - so they're always
   visually identifiable as "click to open this record" regardless of
   which page/table they show up in. */
.ref-number-link {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: var(--accent);
  cursor: pointer;
  text-decoration: none;
}
.ref-number-link:hover { text-decoration: underline; }

/* Proof Status badges become clickable buttons wherever they're shown
   (see static/proof_modal.js) - visually identical to the plain badge
   text they wrap, just with a pointer cursor and a subtle hover cue. */
.proof-badge-trigger {
  background: none;
  border: none;
  padding: 2px 4px;
  margin: -2px -4px;
  font: inherit;
  cursor: pointer;
  border-radius: 4px;
}
.proof-badge-trigger:hover { background: var(--bg); }

.proof-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: 16px;
}
.proof-modal-overlay[hidden] { display: none; }

.proof-modal {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
  width: 100%;
  max-width: 440px;
  max-height: 85vh;
  overflow-y: auto;
  padding: 20px;
}

.proof-modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}

.proof-modal-close {
  background: none;
  border: none;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  color: var(--muted);
  padding: 2px 6px;
}
.proof-modal-close:hover { color: var(--text); }

/* Generic "review before you commit" modal - same overlay/box recipe as
   .proof-modal, just wider (it holds a full remittance summary, not a
   proof uploader) and named generically since the confirm-before-submit
   pattern is meant to be reused by future significant/irreversible
   actions, not just remittances. See static/new_remittance's inline
   script for the current (only) usage. */
.confirm-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: 16px;
}
.confirm-modal-overlay[hidden] { display: none; }

.confirm-modal {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
  width: 100%;
  max-width: 620px;
  max-height: 85vh;
  overflow-y: auto;
  padding: 20px;
}

.confirm-modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 14px;
}

.confirm-modal-close {
  background: none;
  border: none;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  color: var(--muted);
  padding: 2px 6px;
}
.confirm-modal-close:hover { color: var(--text); }

.confirm-modal-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 7px 0;
  border-bottom: 1px solid var(--border);
  font-size: 0.9rem;
}
.confirm-modal-row:last-child { border-bottom: none; }
.confirm-modal-row .label { color: var(--muted); }
.confirm-modal-row .value { font-weight: 600; text-align: right; }

.confirm-modal-details summary {
  cursor: pointer;
  color: var(--accent);
  font-weight: 600;
  font-size: 0.85rem;
  margin-top: 4px;
}
.confirm-modal-details-list {
  margin-top: 8px;
  max-height: 180px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 10px;
}
.confirm-modal-details-list .detail-line {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-size: 0.82rem;
  padding: 3px 0;
  color: var(--muted);
}
.confirm-modal-details-list .detail-line strong { color: var(--text); font-weight: 600; }

.confirm-modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 18px;
}

/* --- confirmDialog() / alertDialog() -------------------------------------
   The shared confirmation dialog (static/confirm_dialog.js). Only the parts
   the generic .confirm-modal card doesn't already cover live here - the card,
   the header, and the action row are all inherited, which is what keeps it
   looking like the Harvest Grid dialogs rather than like a second system. */

.cd-overlay { z-index: 1200; }        /* above .access-overlay's 60 */
.cd-modal { max-width: 420px; }
.cd-title { margin: 0; font-size: 1rem; }
.cd-message {
  margin: 0;
  color: var(--text);
  font-size: 0.92rem;
  line-height: 1.5;
  /* A name pasted into the message can be long and unbroken; wrap it rather
     than letting it widen the card. */
  overflow-wrap: break-word;
}

.cd-cancel {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border);
  padding: 7px 14px;
  border-radius: 8px;
  font-weight: 600;
  font-size: 0.85rem;
}
.cd-cancel:hover { background: var(--panel-2); }
.cd-cancel[hidden] { display: none; }

/* Destructive confirm inherits .btn-delete so it reads as the same control
   as the red "Remove"/"Delete" the action was launched from - just sized for
   a dialog footer rather than a table row. */
.cd-danger { padding: 7px 14px; border-radius: 8px; font-size: 0.85rem; }

.cd-primary {
  background: var(--accent);
  color: #fff;
  border: 1px solid var(--accent);
  padding: 7px 14px;
  border-radius: 8px;
  font-weight: 600;
  font-size: 0.85rem;
}
.cd-primary:hover { background: var(--accent-hover); }

body.cd-open { overflow: hidden; }

.proof-file-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 0;
}

.proof-thumb, .proof-thumb-placeholder {
  width: 44px;
  height: 44px;
  border-radius: 6px;
  border: 1px solid var(--border);
  flex-shrink: 0;
  object-fit: cover;
}
.proof-thumb-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.2rem;
  background: var(--bg);
}

.proof-pending-previews {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 6px;
}

/* Drag-and-drop zone wrapping the proof upload file input (see
   proofDropzoneMarkup()/wireProofDropzone() in remittance_proof.js) - a
   <label> so a click anywhere in it opens the native file picker via the
   standard label/input association, no separate click handler needed. */
.proof-dropzone {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border: 2px dashed var(--border);
  border-radius: 8px;
  padding: 14px;
  text-align: center;
  cursor: pointer;
  background: var(--bg);
  transition: border-color 0.15s ease, background-color 0.15s ease;
}
.proof-dropzone:hover { border-color: var(--accent-tint-border); background: var(--accent-tint); }
.proof-dropzone.proof-dropzone-active { border-color: var(--accent); background: var(--accent-tint); }
.proof-dropzone input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  opacity: 0;
}
.proof-dropzone-icon { font-size: 1.1rem; }
.proof-dropzone-label { font-size: 0.85rem; color: var(--muted); }
.proof-dropzone-browse { color: var(--accent); font-weight: 600; text-decoration: underline; }

/* Shared "clickable transaction status" language for the Remitted/Not
   Remitted pair rendered by static/remit_status_badge.js, wherever that
   badge appears (Sales, Sales Remittance Status, etc.) - a dotted underline
   signals "clickable" even before hovering, solid + a subtle highlight on
   hover. A small negative margin offsets the added padding so surrounding
   text doesn't shift. (Auto-Remitted gets the same treatment separately,
   via .proof-badge-trigger.) */
.not-remitted-trigger,
.remitted-link {
  background: none;
  border: none;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  padding: 2px 4px;
  margin: -2px -4px;
  border-radius: 4px;
  text-decoration: underline;
  text-decoration-style: dotted;
  text-underline-offset: 2px;
}
.not-remitted-trigger { color: var(--warn); }
.not-remitted-trigger:hover {
  text-decoration-style: solid;
  background: var(--warn-tint);
}
.remitted-link { color: var(--accent2); }
.remitted-link:hover {
  text-decoration-style: solid;
  background: var(--accent-tint);
}

.not-remitted-popover {
  position: absolute;
  z-index: 1100;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
  padding: 8px;
}
.not-remitted-popover[hidden] { display: none; }
.not-remitted-popover .not-remitted-add-btn {
  background: var(--accent);
  color: #fff;
  border: none;
  border-radius: 6px;
  padding: 6px 12px;
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.not-remitted-popover .not-remitted-add-btn:hover { background: var(--accent-hover); }

/* Inline ✓/✗ Verify controls next to a Proof Status badge in list rows
   (see static/proof_verify.js) - Super Admin/Viewer only. */
.proof-verify-controls {
  display: inline-flex;
  gap: 4px;
  margin-left: 6px;
  vertical-align: middle;
}
.proof-verify-btn, .proof-reject-btn {
  width: 22px;
  height: 22px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--panel);
  cursor: pointer;
  font-size: 0.78rem;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.proof-verify-btn { color: var(--accent2); }
.proof-verify-btn:hover { background: var(--accent2-tint); border-color: var(--accent2); }
.proof-reject-btn { color: var(--danger); }
.proof-reject-btn:hover { background: var(--danger-tint); border-color: var(--danger); }

.proof-reject-popover {
  position: absolute;
  z-index: 1100;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
  padding: 12px;
  width: 260px;
}
.proof-reject-popover[hidden] { display: none; }
.proof-reject-note-input {
  width: 100%;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 8px;
  font: inherit;
  font-size: 0.85rem;
  resize: vertical;
}
.proof-reject-popover .proof-reject-confirm-btn {
  background: var(--danger-solid);
  color: #fff;
  border: none;
  border-radius: 6px;
  padding: 5px 10px;
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
}
.proof-reject-popover .proof-reject-cancel-btn {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 5px 10px;
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
}

table {
  width: 100%;
  border-collapse: collapse;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
}

th, td {
  padding: 9px 10px;
  text-align: left;
  border-bottom: 1px solid var(--border);
  font-size: 0.85rem;
  white-space: nowrap;
}

th { background: var(--panel-2); color: var(--muted); font-weight: 600; }
tr:last-child td { border-bottom: none; }

/* Clickable column headers (static/sortable_headers.js) - cursor:pointer
   even before the first click makes the affordance discoverable; the
   indicator only lights up (var(--accent)) on the currently active
   column so unsorted headers stay visually quiet. */
.sortable-th { cursor: pointer; user-select: none; }
.sortable-th:hover { color: var(--text); }
.sort-indicator { color: var(--accent); font-size: 0.75em; margin-left: 4px; }

tbody tr:nth-child(even) { background: var(--panel-2); }
tbody tr:hover { background: var(--accent-tint); }
tbody tr.clickable-row:hover { cursor: pointer; }

/* "Recorded Aug 2, 2026, 3:45 PM - by patrick" line - dropped into any
   existing date cell (see static/record_meta.js) rather than a whole new
   table column. white-space:normal overrides the table's own nowrap so a
   long line wraps instead of forcing the whole row wider. */
.record-meta {
  white-space: normal;
  font-size: 0.72rem;
  color: var(--muted);
  margin-top: 2px;
  font-weight: 400;
}

/* Collapsed variant (static/record_meta.js's recordMetaToggle) - a small
   info icon that reveals the same .record-meta line on click, so list
   rows stay scannable by default instead of repeating "Recorded..." on
   every line. Consistent with the app's other small inline icon-buttons
   (e.g. .proof-badge-trigger). */
.record-meta-toggle {
  background: none;
  border: none;
  padding: 0 2px;
  margin-left: 4px;
  font-size: 0.8rem;
  line-height: 1;
  color: var(--muted);
  cursor: pointer;
  border-radius: 4px;
  vertical-align: middle;
}
.record-meta-toggle:hover { color: var(--accent); background: var(--bg); }

.table-wrap { overflow-x: auto; border-radius: 10px; }
.table-wrap.scroll-tall { max-height: 420px; overflow-y: auto; }
.table-wrap.scroll-tall thead th { position: sticky; top: 0; background: var(--panel-2); }

/* --- Expandable tables (static/table_expand.js) -------------------------
   A wide table (Daily Summary carries up to 15 columns) is readable in the
   page only by scrolling sideways, which hides the very columns being
   compared. The Expand toggle lifts the live table into a near-full-screen
   overlay where the horizontal scroll is traded for wrapped headers - the
   point is that every column is on screen at once, so nothing here may
   reintroduce overflow-x. */
.table-expand-bar { display: flex; justify-content: flex-end; margin-bottom: 6px; }
.table-expand-btn {
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 4px 10px;
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.table-expand-btn:hover { background: var(--accent-tint); border-color: var(--accent-tint-border); }

.table-expand-overlay {
  position: fixed;
  inset: 0;
  background: rgba(11, 18, 32, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2vh 1vw;
  z-index: 1200;
}
.table-expand-overlay[hidden] { display: none; }
.table-expand-panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  width: 98vw;
  max-height: 96vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.table-expand-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.table-expand-title { font-size: 1rem; font-weight: 700; }
.table-expand-close {
  background: transparent;
  border: none;
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  color: var(--muted);
  padding: 2px 6px;
}
.table-expand-close:hover { color: var(--text); }
.table-expand-body { padding: 12px 16px 16px; overflow: auto; flex: 1 1 auto; }
/* The page keeps the table's slot while it is lifted out, so nothing below
   it jumps up and back. */
.table-expand-placeholder { display: none; }
body.table-expand-open { overflow: hidden; }

/* Expanded: every column visible, no sideways scroll. Cell text wraps
   (the app-wide `th, td { white-space: nowrap }` is precisely what forced
   the overflow) and the type steps down a little to buy the room. */
.table-wrap.is-expanded { overflow-x: visible; max-height: none; }
.table-wrap.is-expanded th,
.table-wrap.is-expanded td {
  white-space: normal;
  font-size: 0.8rem;
  padding: 8px 8px;
  vertical-align: top;
}
.table-wrap.is-expanded table { table-layout: auto; width: 100%; }
.table-wrap.is-expanded thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--panel-2);
}

/* Select Mode: the checkbox column is collapsed to nothing until the table
   carries select-mode-active (added by select_mode.js the moment the first
   row is selected), so normal browsing never shows a checkbox. */
.select-col { display: none; width: 30px; }
table.select-mode-active .select-col { display: table-cell; }
.select-col input[type="checkbox"] { cursor: pointer; }

.select-toolbar {
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  background: var(--accent-tint);
  border: 1px solid var(--accent-tint-border);
  border-radius: 10px;
  padding: 10px 14px;
  margin-bottom: 10px;
  position: sticky;
  top: 0;
  z-index: 5;
}
.select-toolbar .select-toolbar-count { font-weight: 600; font-size: 0.85rem; }
.select-toolbar .select-toolbar-spacer { flex: 1; }

.form-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 20px;
  max-width: 700px;
}

.form-grid label {
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 0.85rem;
  color: var(--muted);
}

.hub-wrap {
  max-width: 1040px;
  margin: 40px auto 0;
}

.hub-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 20px;
}

.hub-tile {
  flex: 0 1 230px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 36px 22px;
  min-height: 170px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  text-decoration: none;
  color: var(--text);
  text-align: center;
  transition: box-shadow 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}

.hub-tile:hover {
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08);
  border-color: var(--accent-tint-border);
  transform: translateY(-1px);
  color: var(--accent);
}

.hub-tile-icon { display: flex; color: var(--accent); }
.hub-tile-icon .tile-icon { width: 30px; height: 30px; stroke-width: 1.75px; }
.hub-tile-label { font-weight: 600; font-size: 1.05rem; }

.hub-tile-desc {
  font-size: 0.8rem;
  color: var(--muted);
  line-height: 1.35;
}

.hub-tile:hover .hub-tile-desc { color: var(--accent); }

/* Top-level section nav (Dashboard/Sales/.../Personnel/Users) - sits
   directly on the page background (the darkest level in the palette) so
   it reads as a page-level bar. Active uses solid Royal Blue with white
   text - a filled state, not just a tint+border - so it's unmistakable at
   a glance from the secondary row below (see .subnav-secondary), which
   uses a different accent entirely for its own active state. */
.subnav {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin: 10px 0 12px;
}

.subnav a {
  text-decoration: none;
  color: var(--muted);
  font-weight: 600;
  font-size: 0.85rem;
  padding: 7px 14px;
  border-radius: 6px;
  border: 1px solid var(--border);
}

.subnav a:hover { background: var(--panel-2); color: var(--text); border-color: var(--panel-2); }
.subnav a.active { background: var(--accent); color: #fff; border-color: var(--accent); }

/* Secondary sub-tab row (currently only Personnel's Shift Roster/Payroll
   History/Process Payroll/Shift & Payroll Settings) - one shade lighter
   than the page background (--panel-2) so the two rows read as distinct
   navigation tiers rather than one continuous menu, with its own teal
   active color (never Royal Blue) so neither row's active cue can be
   mistaken for the other's. The divider line only appears in this exact
   composition - a .subnav immediately followed by a .subnav-secondary -
   not on the many single-row pages that only ever render .subnav alone. */
.subnav-secondary {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  background: var(--panel-2);
  padding: 8px 10px;
  border-radius: 8px;
  margin: 0 0 18px;
}

.subnav + .subnav-secondary { border-top: 1px solid var(--border); padding-top: 14px; margin-top: 2px; }

.subnav-secondary a {
  text-decoration: none;
  color: var(--muted);
  font-weight: 600;
  font-size: 0.85rem;
  padding: 6px 12px;
  border-radius: 6px;
  border: 1px solid transparent;
}

.subnav-secondary a:hover { background: var(--panel); color: var(--text); }
.subnav-secondary a.active { background: var(--teal); color: var(--bg); border-color: var(--teal); }

.form-grid .full { grid-column: 1 / -1; }

.form-actions { grid-column: 1 / -1; margin-top: 4px; }

.remit-grid {
  display: grid;
  grid-template-columns: 1fr 1.8fr 1.2fr;
  gap: 20px;
  align-items: start;
}
.remit-col-form .form-grid { grid-template-columns: 1fr; max-width: none; }
.remit-col-breakdown .cards { grid-template-columns: 1fr; }
.remit-col-breakdown { position: sticky; top: 12px; }

@media (max-width: 1100px) {
  .remit-grid { grid-template-columns: 1fr; }
  .remit-col-breakdown { position: static; }
}

.section-label {
  grid-column: 1 / -1;
  font-weight: 600;
  color: var(--text);
  margin-top: 6px;
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.preview-table {
  grid-column: 1 / -1;
  max-width: 360px;
}

.preview-table table { table-layout: fixed; }

.preview-table th, .preview-table td {
  text-align: right;
  padding: 7px 12px;
  white-space: nowrap;
}

.preview-table th:first-child, .preview-table td:first-child {
  text-align: left;
  color: var(--muted);
  width: 30%;
}

.preview-table td { font-weight: 600; }
.preview-table td:last-child { color: var(--accent); }

.running-hint {
  font-size: 0.78rem;
  color: var(--muted);
  margin-top: -6px;
}

/* Read-only formatted echo shown next to an editable type="number" amount
   input (see static/format_currency.js's wireCurrencyPreview) - native
   number inputs can't hold "₱13,052.58" as their own value, so this is a
   separate adjacent element instead. */
.currency-preview {
  font-size: 0.78rem;
  color: var(--muted);
  margin-top: -6px;
  display: block;
}

.running-hint.error {
  color: var(--danger);
  font-weight: 600;
}

.running-hint .manual-tag {
  font-style: italic;
}

/* Quick-select pill for a recent/frequent value (e.g. Add Sale's recent-
   customer row) - click fills the paired field instead of retyping it. */
.chip-btn {
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 4px 11px;
  border-radius: 999px;
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
}
.chip-btn:hover { background: var(--accent-tint); color: var(--accent); border-color: var(--accent-tint-border); }

.plant-tabs {
  grid-column: 1 / -1;
  display: flex;
  gap: 8px;
  margin-top: 6px;
}

.plant-tab-btn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
  padding: 7px 14px;
  border-radius: 6px;
  font-weight: 600;
  font-size: 0.85rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 7px;
}

.plant-tab-btn.active { background: var(--accent-tint); color: var(--accent); border-color: var(--accent-tint-border); }
.plant-tab-btn:hover { background: var(--bg); }
.plant-tab-btn.active:hover { background: var(--accent-tint); }

.plant-tab-btn .tab-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent2);
  display: none;
}

/* --- Page header / welcome banner / quick-action buttons / empty states --
   New shared components for the Dashboard-as-landing-page redesign. Built
   generically so any other page can adopt the same header pattern later
   without new CSS. */

.welcome-banner {
  background: var(--accent2-tint);
  border: 1px solid var(--accent2);
  color: var(--accent2-text);
  padding: 14px 18px;
  border-radius: 10px;
  margin-bottom: 20px;
  font-weight: 600;
}

.page-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 14px;
  margin-bottom: 20px;
}
.page-header h1 { margin-bottom: 0; }
.page-header-subtitle { color: var(--muted); margin-top: 4px; font-size: 0.9rem; }
.page-header-actions { display: flex; gap: 10px; flex-wrap: wrap; }

/* An <a> styled to look like a filled button - for primary actions that
   are plain navigation (no form submit), e.g. Dashboard's quick-create
   links. Add .btn-amber or .btn-emerald for a different fill. */
.btn-link {
  display: inline-block;
  background: var(--accent);
  color: #fff;
  padding: 8px 16px;
  border-radius: 6px;
  font-weight: 600;
  font-size: 0.9rem;
  text-decoration: none;
}
.btn-link:hover { background: var(--accent-hover); }

/* Amber accent variant for remittance-related/urgent actions, distinct
   from the default blue primary action - works on both <button> (inherits
   padding/radius from the bare `button` rule) and .btn-link (<a>). Dark
   text (not white) against the amber fill - white-on-amber fails WCAG AA
   contrast, and readability wins over exactly matching the reference. */
.btn-amber { background: var(--warn); color: #1A1206; }
.btn-amber:hover { background: var(--warn-text); }
.btn-amber:active { background: #D97706; }

/* Emerald variant, from the same accent family the app already uses for
   positive states (the Active status pill, revenue figures). A modifier
   rather than a change to .btn-link itself, because .btn-link is shared by
   ten buttons across the app and only Dashboard's "+ Add Sale" is meant to
   change colour. Fill only - size, padding and radius stay inherited. */
.btn-emerald { background: var(--accent2-solid); color: #fff; }
.btn-emerald:hover { background: var(--accent2-solid-hover); }
.btn-emerald:active { background: #065F46; }

.card .value.value-teal { color: var(--teal); }
.card .value.value-warn { color: var(--warn); }
.card .value.value-accent { color: var(--accent); }
.card .sub { color: var(--muted); font-size: 0.78rem; margin-top: 4px; }
/* The figures a headline number is made of, shown under it so the total can
   be checked without opening the page behind the card. */
.card .card-detail {
  color: var(--text);
  opacity: 0.8;
  font-size: 0.8rem;
  margin-top: 5px;
  font-variant-numeric: tabular-nums;
}

/* The one sentence that says which of two possible revenue figures this
   page reports. It is the difference between a cash position and an
   invoiced one, so it is stated rather than implied. */
.fin-basis {
  margin: -4px 0 16px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: 6px;
  background: var(--panel-2);
  color: var(--muted);
  font-size: 0.84rem;
  max-width: 760px;
}
.fin-basis strong { color: var(--text); }

/* Centered "nothing here yet" panel, replacing plain inline text inside a
   table's empty-state colspan cell (or standalone) - generous padding, a
   muted message, and a real button-styled call to action. */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 40px 20px;
  text-align: center;
  color: var(--muted);
}
.empty-state p { margin: 0; font-size: 0.92rem; }

@media (max-width: 640px) {
  .topbar {
    grid-template-columns: auto 1fr;
    grid-template-areas: "left right" "brand brand";
    row-gap: 8px;
    padding: 12px 16px;
  }

  .topbar-left { grid-area: left; }
  .topbar-right { grid-area: right; }
  .brand { grid-area: brand; font-size: 1rem; }

  .container {
    padding: 0 12px;
    margin: 16px auto;
  }

  h1 { font-size: 1.2rem; }

  .cards {
    grid-template-columns: repeat(auto-fit, minmax(135px, 1fr));
    gap: 10px;
  }

  /* ...but a Shift Roster card carries a table and a full-width control,
     not a single figure, so it never packs two-up. This has to be restated
     here rather than only in the 720px block above: that block and this one
     have the same specificity, and this one comes later, so .cards' auto-fit
     would otherwise win back the stacking. */
  .shift-roster-cards {
    grid-template-columns: 1fr;
  }

  .hub-grid { gap: 12px; }

  .hub-tile {
    flex-basis: 140px;
    padding: 20px 14px;
    min-height: 120px;
  }

  .hub-tile-label { font-size: 0.9rem; }
  .hub-tile-desc { font-size: 0.75rem; }

  .card { padding: 12px 14px; }
  .card .value { font-size: 1.3rem; }

  .chart-grid { grid-template-columns: 1fr; }

  .form-grid {
    grid-template-columns: 1fr;
    padding: 16px;
    gap: 12px;
    max-width: 100%;
  }

  .filters { padding: 12px; gap: 10px; }
  .filters label { flex: 1 1 140px; }

  .ar-summary { flex-wrap: wrap; gap: 10px; }

  th, td { padding: 7px 8px; font-size: 0.8rem; }

  /* Prevent iOS Safari from zooming in on focus, and give inputs/buttons
     a large enough tap target for touch use. */
  input, select, button {
    font-size: 16px;
    min-height: 40px;
  }

  .btn-delete, .btn-undo, .btn-restore, .btn-edit {
    min-height: 32px;
    padding: 6px 10px;
  }
}

/* Plant 1 Storage Grid - 25 rows x 4 tiles, a plain checkbox grid (empty
   vs. harvested/checked). Small and tight by design: this is a quick daily
   checklist, not a complex tool. */
.plant1-row {
  display: flex;
  align-items: center;
  gap: 5px;
  margin-bottom: 3px;
}
.plant1-row-label {
  width: 20px;
  flex-shrink: 0;
  text-align: right;
  font-size: 0.68rem;
  color: var(--muted);
  font-weight: 600;
}
.plant1-row-tiles {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
  flex: 1;
  max-width: 260px;
}

.plant1-tile {
  position: relative;
  border-radius: 5px;
  border: 1px solid var(--border);
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  user-select: none;
  font-size: 0.7rem;
  font-weight: 700;
  transition: transform 0.1s ease;
}
.plant1-tile:hover { transform: translateY(-1px); }
.plant1-tile.selected { outline: 2px solid var(--accent); outline-offset: 1px; }

.plant1-tile.state-empty { background: var(--panel-2); color: var(--muted); border-style: dashed; }
.plant1-tile.state-harvested { background: var(--accent-tint); color: var(--accent-hover); border-color: var(--accent); }

/* Small verification-status corner badge - its own click target (opens
   the detail/verify modal), separate from the tile body's click (which
   toggles checked/unchecked) - stopPropagation keeps the two independent. */
.plant1-tile-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  font-size: 0.62rem;
  line-height: 1;
  cursor: pointer;
}

.plant1-verify-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 14px;
  margin-bottom: 12px;
  font-size: 0.88rem;
  border-color: var(--warn);
  color: var(--warn-text);
}

.plant1-legend {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  font-size: 0.78rem;
  color: var(--muted);
  margin-bottom: 14px;
}
.plant1-legend-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 3px;
  margin-right: 5px;
  vertical-align: middle;
}

.plant1-action-bar {
  position: fixed;
  left: 50%;
  bottom: 20px;
  transform: translateX(-50%);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.35);
  padding: 12px 16px;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  z-index: 900;
}
.plant1-action-bar[hidden] { display: none; }
.plant1-action-note { font-size: 0.8rem; color: var(--muted); flex-basis: 100%; }
.plant1-action-note.on-track { color: var(--accent2-text); }

/* ===================================================================
   Harvest Grid modals (Plant 1 tiles, Plant 2 beds)

   Two hard constraints drive this layout:

   1. Everything must fit the viewport with no scrolling. Tile size is
      therefore derived from viewport height (clamped), never fixed px.
   2. A 25x4 grid is tall and narrow. Rendered as a single block it wastes
      most of the modal's width and forces tiny tiles. So Plant 1 splits
      into two blocks of ~13 rows side by side: the modal fills its width
      honestly, and each tile gets roughly twice the height it otherwise
      could at the same viewport size.

   Spacing follows one scale (--hg-pad) so every section lines up.
   =================================================================== */

.hg-overlay {
  position: fixed;
  inset: 0;
  background: rgba(8, 12, 20, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: 16px;
}
.hg-overlay[hidden] { display: none; }

.hg-modal {
  --hg-pad: 16px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.55);
  width: min(680px, 96vw);
  max-height: 94vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
/* The Plant 1 modal is sized FROM the grid rather than to a guessed pixel
   width, so no dead space is left beside the tiles at any viewport.
   One block = row label + 4 tiles + 4 gaps; three blocks plus two gaps and
   the body padding give the content width. With only ~9 rows stacked per
   block the tile is far less height-constrained than at 13, so it can be
   roughly 60% larger at the same viewport. */
.hg-modal-p1 {
  --hg-tile: clamp(26px, 4.3vh, 40px);
  --hg-gap: clamp(7px, 0.8vh, 10px);
  --hg-block: calc(24px + (4 * var(--hg-tile)) + (4 * var(--hg-gap)));
  --hg-blockgap: 30px;
  /* Width follows the grid (3 tile blocks + gaps + padding) so tiles never
     sit in dead space; the floor just keeps the field bar (Session: date +
     arrows, time, shift) from ever wrapping awkwardly on very narrow
     viewports. */
  width: min(
    max(420px, calc((3 * var(--hg-block)) + (2 * var(--hg-blockgap)) + (2 * var(--hg-pad)))),
    96vw
  );
}

.hg-modal-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: var(--hg-pad);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.hg-modal-title { font-size: 1.05rem; font-weight: 700; letter-spacing: -0.01em; }
.hg-modal-sub { font-size: 0.78rem; color: var(--muted); margin-top: 3px; }

/* Super Admin-only cycle-duration control in the modal header. */
.hg-cycle-duration {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.78rem;
  color: var(--muted);
  flex-shrink: 0;
}
.hg-cycle-duration input {
  width: 52px;
  height: 28px;
  padding: 0 6px;
  font-size: 0.8rem;
  border-radius: 6px;
  box-sizing: border-box;
}
.hg-cycle-duration button {
  height: 28px;
  padding: 0 10px;
  font-size: 0.75rem;
  border-radius: 6px;
}
.hg-cycle-duration .hg-cycle-saved { color: var(--accent2-text); font-size: 0.72rem; }

.hg-body {
  padding: var(--hg-pad);
  display: flex;
  flex-direction: column;
  /* Named so the one place that needs to cancel it can, without hardcoding
     the number in two files - see .hg-fieldbar + .hg-cycle-summary. */
  --hg-body-gap: 11px;
  gap: var(--hg-body-gap);
  flex: 1 1 auto;
  min-height: 0;
  /* The body is the single scroll container. Letting only the grid scroll
     meant an appearing notice banner pushed the legend (and Plant 2's
     bottom bed) outside the modal instead of being absorbed. */
  overflow-y: auto;
  overflow-x: hidden;
}

/* --- Field groups ------------------------------------------------------
   The Session fields (date + arrows, time, shift) read as their own card
   rather than a bare row of inputs. */
.hg-fieldbar {
  display: flex;
  gap: 10px;
  align-items: stretch;
  flex-wrap: nowrap;
  /* Sits directly on top of the summary counts line. The status bar that
     used to separate them is gone, and .hg-body's row gap would otherwise
     leave a hole where it was. */
  margin-bottom: 0;
}
/* The Session fields sit directly on top of the summary counts. This only
   applies when they are actually adjacent - a visible flash or cycle-picking
   banner between them keeps its normal spacing. */
.hg-fieldbar + .hg-cycle-summary { margin-top: calc(-1 * var(--hg-body-gap)); }
.hg-fieldgroup {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  margin: 0;
  padding: 8px 12px 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--panel-2);
  /* One row, always. flex-basis:0 collapsed this to ~66px and clipped
     its fields; auto basis made it wrap and stack, which cost ~100px of
     height and pushed grid rows out of the modal. An explicit basis with
     a min-content floor keeps it on one line at every width. */
  flex: 1 1 auto;
  min-width: min-content;
  flex-wrap: nowrap;
}
.hg-fieldgroup legend {
  padding: 0 6px;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--muted);
}
.hg-fieldgroup label {
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 0.72rem;
  color: var(--muted);
  font-weight: 600;
}
/* One consistent control height across every field in the bar. */
.hg-fieldgroup input,
.hg-fieldgroup select {
  height: 34px;
  padding: 0 10px;
  font-size: 0.85rem;
  border-radius: 7px;
  box-sizing: border-box;
}
.hg-fieldgroup label { flex: 0 0 auto; }
.hg-fieldgroup input,
.hg-fieldgroup select { width: 100%; }
.hg-fieldgroup input[type="date"] { min-width: 120px; }
.hg-fieldgroup input[type="time"] { min-width: 96px; }
.hg-fieldgroup .date-stepper { width: auto; }
/* Wide enough that the longest shift name is never clipped. */
.hg-fieldgroup select { min-width: 88px; }

/* --- Notices (roster match, validation warnings) ----------------------- */
.hg-notice {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 8px 11px;
  border-radius: 8px;
  font-size: 0.78rem;
  line-height: 1.3;
  border: 1px solid var(--border);
  background: var(--panel-2);
  flex-shrink: 0;
}
.hg-notice[hidden] { display: none; }
.hg-notice-icon { font-size: 0.95rem; line-height: 1.2; flex-shrink: 0; }
.hg-notice-warn {
  background: var(--warn-tint);
  border-color: var(--warn);
  color: var(--warn-text);
}
.hg-notice-info {
  background: var(--accent2-tint);
  border-color: var(--accent2);
  color: var(--accent2-text);
}

/* --- Super Admin controls in the grid header ---------------------------
   Start Harvest Cycle and Overrides used to sit in a status bar between the
   Session fields and the summary counts. The bar is gone; the two controls
   it carried moved up here so the override panel stays reachable. */
.hg-header-admin { display: flex; gap: 8px; flex-shrink: 0; }
.hg-header-admin button { height: 30px; padding: 0 12px; font-size: 0.78rem; border-radius: 6px; }

/* --- Grid area ---------------------------------------------------------
   The only region permitted to scroll, and only on very short screens. */
.hg-grid-scroll {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  align-items: center;
  /* Natural height - the modal grows around the grid up to its max-height,
     and .hg-body absorbs any remainder. */
}

/* --- Plant 1: two blocks of ~13 rows, side by side --------------------- */
.hg-p1-grid {
  display: flex;
  gap: var(--hg-blockgap, 30px);
  align-items: flex-start;
  justify-content: center;
  width: 100%;
  flex: 0 0 auto;
}
.hg-p1-block {
  display: flex;
  flex-direction: column;
  gap: var(--hg-gap);
}
.hg-p1-head,
.hg-p1-row {
  display: grid;
  grid-template-columns: 24px repeat(4, var(--hg-tile));
  gap: var(--hg-gap);
  align-items: center;
}
.hg-p1-collabel {
  text-align: center;
  font-size: 0.7rem;
  font-weight: 700;
  color: var(--muted);
  letter-spacing: 0.06em;
  padding-bottom: 3px;
}
.hg-p1-rowlabel {
  text-align: right;
  padding-right: 3px;
  font-size: 0.68rem;
  color: var(--muted);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.hg-tile {
  width: var(--hg-tile);
  height: var(--hg-tile);
  border-radius: 5px;
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  user-select: none;
  font-size: calc(var(--hg-tile) * 0.55);
  font-weight: 700;
  line-height: 1;
  transition: background 0.08s ease, border-color 0.08s ease, transform 0.08s ease;
}
/* --- Freeze-percentage states, shared by .hg-tile and .hg-bed ----------
   A mold starts full: no cycle record at all reads as .ready (100%,
   green) by default - see db.py's _grid_state. Harvesting empties it and
   it counts back up through .empty -> .pct50 -> .pct75 -> .ready, holding
   at ready indefinitely (no "overdue"). Every non-empty percentage state
   carries both a color AND its percentage as text (never color alone),
   for colorblind operators and printed reports; .empty deliberately shows
   no text, matching a genuinely blank mold.

   Every value below is measured against the modal's panel background
   (#1A2332). Borders carry each state's boundary at 3.3:1 or better and
   the percentage text sits at 4.7:1 or better on its own fill.

   The fills used to sit at ~25% opacity, which blended the 75% amber down
   to #4A3A22 - a 1.5:1 dark brown that read as a dead cell rather than a
   nearly-ready one. The amber is now the brighter #E89923 AND carries
   enough of itself (70%) to actually look amber at #AA7628, 4:1 against
   the panel. The blue and green are raised for the same reason, less far
   because neither was muddying. */
.hg-tile.empty,
.hg-bed.empty {
  background: rgba(100, 116, 139, 0.10);
  color: #94A3B8;
  border-color: #64748B;
  border-style: dashed;
  /* A visibly dashed edge at tile size: 1px of #475569 was both too dark
     and too fine to read as "deliberately empty" rather than "blank". */
  border-width: 2px;
}

.hg-tile.pct50,
.hg-bed.pct50 {
  background: linear-gradient(to top, rgba(48, 92, 222, 0.55) var(--cycle-progress, 0%), rgba(48, 92, 222, 0.22) var(--cycle-progress, 0%));
  color: #EAF0FF;
  border-color: #4F74E3;
  border-style: solid;
}
.hg-tile.pct75,
.hg-bed.pct75 {
  background: linear-gradient(to top, rgba(232, 153, 35, 0.70) var(--cycle-progress, 80%), rgba(232, 153, 35, 0.28) var(--cycle-progress, 80%));
  color: #FFFFFF;
  border-color: #E89923;
  border-style: solid;
}
.hg-tile.ready,
.hg-bed.ready {
  background: rgba(46, 158, 91, 0.45);
  color: #EAFBF1;
  border-color: #35B268;
  border-style: solid;
  font-weight: 800;
}
.hg-tile.out_of_service,
.hg-bed.out_of_service {
  background: #475569;
  color: #E2E8F0;
  border-color: #64748B;
  border-style: solid;
  cursor: not-allowed;
  opacity: 0.9;
}
/* Draft selection for this session - deliberately the strongest state, so
   it's obvious what Confirm is about to save. Only ever applied to
   .ready cells that are not locked (the only ones the bulk drag-select can
   pick). */
.hg-tile.picked {
  background: var(--accent);
  color: #fff;
  border-color: #5B82EA;
}
.hg-tile:hover:not(.hg-inert) { transform: translateY(-1px); border-color: var(--accent); }
.hg-tile.out_of_service:hover { transform: none; border-color: #475569; }

/* A gentle attention pulse, not tied to any particular color - used by
   .next-in-sequence below. */
@keyframes hg-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.25); }
  50% { box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.08); }
}

/* The Super Admin harvest sequence's current position - the one cell
   (whatever its own freeze-% state/color) that's actually harvestable
   right now. Layered on top of the normal state color, never replacing it -
   still both a color/border effect AND (on beds, which have room) a text
   label, never animation alone. */
.hg-tile.next-in-sequence,
.hg-bed.next-in-sequence {
  outline: 2px solid #fff;
  outline-offset: -2px;
  animation: hg-pulse 1.6s ease-in-out infinite;
}
.hg-bed-next {
  display: block;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: #fff;
  background: var(--accent);
  border-radius: 4px;
  padding: 1px 6px;
  position: absolute;
  top: 8px;
  right: 8px;
}
.hg-bed-next[hidden] { display: none; }

/* Every cell that isn't the sequence's current position, for every role
   including Super Admin. Genuinely inert rather than merely dimmed:
   pointer-events:none means no hover state, no cursor change and no click
   to swallow in the first place. The JS guard in hgSequenceBlocksClick
   stays regardless - CSS is a presentation layer, not a permission one. */
.hg-tile.hg-inert,
.hg-bed.hg-inert {
  cursor: default;
  pointer-events: none;
}

/* --- Layer: LOCKED ------------------------------------------------------
   A position inside its freeze window (see db.get_harvest_lock_hours) is
   physically unharvestable, so it can never join a selection or a drag -
   enforced in hgSelectable(), and again server-side in check_harvest_lock.

   It is still hit-testable rather than pointer-events:none: clicking it
   opens the read-only detail panel, which is where the lock explains
   itself ("locked until ...", plus what was harvested here). A cell that
   silently ignores every press teaches the operator nothing.

   Layered ON TOP of the percentage state, never replacing it: a locked
   tile still shows whether it is at 50% or 75%, because "how full is it"
   and "may I take it" are different questions.

   The padlock carries this state on its own, with no dimming filter. A
   dim was tried and removed: with a 36h cycle every non-ready tile is
   locked, so dimming them re-darkened the exact amber the contrast pass
   above had just brightened. The one case a dim would have been for - a
   100%/green tile still held by the 24h floor because the cycle setting
   is shorter than that - is precisely where the padlock reads clearest
   anyway, against a bright fill. */
.hg-tile.hg-locked,
.hg-bed.hg-locked {
  cursor: not-allowed;
  position: relative;
}
.hg-tile.hg-locked { overflow: visible; }

/* The padlock itself - a corner badge rather than a replacement glyph, so
   the tile's percentage text is never displaced by it. */
.hg-lock-badge {
  position: absolute;
  top: -3px;
  right: -3px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #0B1220;
  border: 1px solid #94A3B8;
  color: #F8FAFC;
  filter: none;
  pointer-events: none;
}
.hg-lock-badge svg { width: 8px; height: 8px; stroke: currentColor; fill: none; stroke-width: 2.4; }
.hg-bed .hg-lock-badge {
  width: auto;
  height: auto;
  top: 8px;
  left: 8px;
  right: auto;
  border-radius: 5px;
  gap: 4px;
  padding: 2px 7px;
  font-size: 0.66rem;
  font-weight: 800;
  letter-spacing: 0.08em;
}
.hg-bed .hg-lock-badge svg { width: 10px; height: 10px; }

/* --- Layer: HARVESTED ON THIS DATE -------------------------------------
   Independent of both state and lock: it answers "was this position
   harvested during the date+shift currently being viewed", which is a
   question about the selected window, not about the tile's own progress.
   Rendered as an outline plus a corner dot so it reads on top of whatever
   fill the tile already has, and so ◀/▶ navigation visibly changes it. */
.hg-tile.hg-harvested-here,
.hg-bed.hg-harvested-here {
  outline: 2px solid #34D399;
  outline-offset: 1px;
  box-shadow: 0 0 0 1px rgba(52, 211, 153, 0.35);
}
.hg-harvest-marker {
  position: absolute;
  top: -4px;
  left: -4px;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: #34D399;
  border: 1px solid #0B1220;
  filter: none;
  pointer-events: none;
}
.hg-bed .hg-harvest-marker {
  width: auto;
  height: auto;
  top: 8px;
  right: 8px;
  left: auto;
  border-radius: 5px;
  padding: 2px 7px;
  color: #052E20;
  font-size: 0.66rem;
  font-weight: 800;
  letter-spacing: 0.06em;
}
/* Both corner marks need the tile to be a positioning context. .hg-bed
   already is; .hg-tile is not by default. */
.hg-tile { position: relative; }
.hg-lock-badge[hidden], .hg-harvest-marker[hidden] { display: none; }

/* The "N harvested this date" count sits in the same summary line as
   Ready/Freezing/Empty but answers a different question, so it carries the
   marker's own green rather than the line's muted grey. */
.hg-summary-harvested { color: #34D399; }
.hg-summary-harvested strong { color: #34D399; }

/* The block equivalent beside a position count. Quieter than the count
   itself: the number of tiles is what somebody is scanning for, and the
   blocks are the answer to "what is that worth" rather than a second figure
   competing for the same attention. */
.hg-summary-blocks {
  color: var(--muted);
  font-weight: 400;
  font-size: 0.92em;
}

.hg-icon { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 1.6; vertical-align: middle; }

/* Percentage text ("50%"/"75%"/"100%") needs a much smaller font than the
   single-glyph icon/checkmark the base .hg-tile font-size was sized for,
   to actually fit inside a small tile. Heavier than the base weight and
   very slightly tightened: at 26% of a ~30px tile these are 8px glyphs,
   where regular-weight digits smear into the fill behind them. */
.hg-tile.empty, .hg-tile.pct50, .hg-tile.pct75, .hg-tile.ready {
  font-size: calc(var(--hg-tile) * 0.26);
  font-weight: 800;
  letter-spacing: -0.02em;
  text-shadow: 0 1px 2px rgba(11, 18, 32, 0.55);
}

/* --- Plant 2: beds #1/#2 tall side by side, #3 wide beneath ------------ */
/* Bed heights are viewport-relative and in proportion to capacity
   (96/96/60). Percentage heights were tried and collapse here: they need a
   definite parent height, which a content-sized modal cannot give. */
.hg-p2-grid {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 100%;
}
.hg-bed-toprow {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
.hg-bed-bottomrow { display: block; }

.hg-bed {
  position: relative;
  border-radius: 10px;
  border: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  cursor: pointer;
  user-select: none;
  transition: background 0.1s ease, border-color 0.1s ease, transform 0.1s ease;
}
.hg-bed-toprow .hg-bed { height: clamp(120px, 23vh, 230px); }   /* 96 blocks */
.hg-bed-bottomrow .hg-bed { height: clamp(75px, 14.4vh, 144px); } /* 60 blocks */

/* Colors/text for empty/pct50/pct75/ready/out_of_service are shared with
   .hg-tile - see that block above. */
.hg-bed.picked { background: var(--accent); color: #fff; border-color: var(--accent); }
.hg-bed:hover:not(.hg-inert) { transform: translateY(-1px); border-color: var(--accent); }
.hg-bed.out_of_service:hover { transform: none; border-color: #475569; }

.hg-bed-check { font-size: 1.7rem; font-weight: 700; line-height: 1; min-height: 1.7rem; }
.hg-bed-label { font-size: 0.95rem; font-weight: 700; }
.hg-bed-cap { font-size: 0.76rem; opacity: 0.82; }

/* --- Legend ------------------------------------------------------------ */
.hg-legend {
  display: flex;
  gap: 20px;
  padding-top: 2px;
  border-top: 1px solid var(--border);
  margin-top: 2px;
  flex-wrap: wrap;
  font-size: 0.75rem;
  color: var(--muted);
  flex-shrink: 0;
}
.hg-legend span { display: inline-flex; align-items: center; }
.hg-swatch {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  border-radius: 3px;
  margin-right: 5px;
  border: 1px solid var(--border);
}
.hg-swatch .hg-icon { width: 10px; height: 10px; }
/* Swatches are SOLID where the tiles are translucent. A 15px chip carrying
   the tile's own ~25-42% fill washes out against the panel - the 75% amber
   measured 1.5:1 and read as dark brown - so each chip states its state's
   hue at full strength (6.7:1+) while the tiles keep the translucency the
   progress-fill gradient needs. */
.hg-swatch-empty { background: rgba(100, 116, 139, 0.10); border-color: #64748B; border-style: dashed; border-width: 2px; }
.hg-swatch-pct50 { background: #305CDE; border-color: #4F74E3; }
.hg-swatch-pct75 { background: #E89923; border-color: #E89923; }
.hg-swatch-ready { background: #2E9E5B; border-color: #35B268; }
.hg-swatch-out_of_service { background: #475569; border-color: #64748B; color: #E2E8F0; }
.hg-swatch-picked { background: var(--accent); border-color: #5B82EA; }
.hg-swatch-locked { background: #0B1220; border-color: #94A3B8; color: #F8FAFC; }
.hg-swatch-locked .hg-icon { stroke-width: 2.4; }
.hg-swatch-harvested { background: rgba(52, 211, 153, 0.22); border-color: #34D399; outline: 2px solid #34D399; outline-offset: -5px; }

/* --- Live summary counts (above the grid) ------------------------------ */
.hg-cycle-summary {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  font-size: 0.8rem;
  color: var(--muted);
  margin-bottom: 2px;
}
.hg-cycle-summary strong { color: var(--text); font-variant-numeric: tabular-nums; }

/* --- Cell-detail panel --------------------------------------------------
   Reuses the same overlay-on-top-of-the-grid-modal pattern as
   hg-confirm-overlay (z-index and centering both already established
   there), so it needs no new positioning system. */
.hg-cell-panel-body { font-size: 0.85rem; }
.hg-cell-panel-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 0;
  border-bottom: 1px solid var(--border);
}
.hg-cell-panel-row:last-of-type { border-bottom: none; }
.hg-cell-panel-row .label { color: var(--muted); }
.hg-cell-panel-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 14px; }
.hg-cell-panel-note {
  font-size: 0.78rem;
  color: var(--muted);
  background: var(--panel-2);
  border-radius: 6px;
  padding: 8px 10px;
  margin-top: 8px;
}

/* What was harvested from this position during the date+shift being viewed.
   One card per report, never merged: if the same position appears on two
   reports for one date, seeing them as two lines is the whole point. */
.hg-cell-harvests { margin-top: 12px; }
.hg-cell-harvests-title {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: #34D399;
  margin-bottom: 6px;
}
.hg-cell-harvest {
  border: 1px solid var(--border);
  border-left: 3px solid #34D399;
  border-radius: 6px;
  background: var(--panel-2);
  padding: 8px 10px;
  margin-bottom: 6px;
}
.hg-cell-harvest-head {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-size: 0.84rem;
  margin-bottom: 4px;
}
.hg-cell-harvest-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  font-size: 0.76rem;
  color: var(--text);
}
.hg-cell-harvest-meta .label { color: var(--muted); }

/* --- Super Admin override panel -----------------------------------------
   Same overlay pattern as the cell panel, but wider: each row is an action
   plus the context needed to choose it, and the shared reason field sits
   above them all because it applies to whichever one is pressed. */
.hg-override-modal {
  max-width: 560px;
  width: 100%;
  max-height: 88vh;
  overflow-y: auto;
}
.hg-override-current {
  font-size: 0.85rem;
  font-weight: 600;
  padding: 9px 11px;
  border-radius: 8px;
  background: var(--panel-2);
  margin-bottom: 10px;
}
.hg-override-reason {
  display: block;
  font-size: 0.78rem;
  color: var(--muted);
  margin-bottom: 14px;
}
.hg-override-reason input {
  display: block;
  width: 100%;
  margin-top: 5px;
  box-sizing: border-box;
}
.hg-required {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #F0B87A;
  margin-left: 4px;
}
.hg-override-actions { display: flex; flex-direction: column; gap: 2px; }
.hg-override-action {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 11px 0;
  border-bottom: 1px solid var(--border);
}
.hg-override-action:last-child { border-bottom: none; }
.hg-override-action-name { font-size: 0.85rem; font-weight: 600; }
.hg-override-action-note { font-size: 0.75rem; color: var(--muted); margin-top: 2px; }
.hg-override-controls { display: flex; gap: 6px; align-items: center; flex-shrink: 0; }
.hg-override-controls select { max-width: 130px; }
.hg-override-danger .hg-override-action-name { color: #F0B87A; }

/* The log is the point of the reason field - the two belong on the same
   screen, so an override and the record it wrote are never a page apart. */
.hg-override-log { margin-top: 16px; border-top: 1px solid var(--border); padding-top: 12px; }
.hg-override-log-title {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--muted);
  margin-bottom: 8px;
}
.hg-override-log-row { padding: 7px 0; border-bottom: 1px solid var(--border); }
.hg-override-log-row:last-child { border-bottom: none; }
.hg-override-log-head { font-size: 0.8rem; }
.hg-override-log-head span { color: var(--muted); font-variant-numeric: tabular-nums; }
.hg-override-log-meta { font-size: 0.72rem; color: var(--muted); margin-top: 1px; }
.hg-override-log-reason { font-size: 0.76rem; margin-top: 3px; }
.hg-override-log-empty { font-size: 0.78rem; color: var(--muted); }

/* --- Confirm Harvest Report dialog --------------------------------------
   Two clearly separate halves: what is about to be written (read-only) and
   what the writer has to supply. The split is the point of the dialog, so
   it is a visible section break rather than one undifferentiated form. */
.hg-report-modal {
  max-width: 520px;
  width: 100%;
  max-height: 88vh;
  overflow-y: auto;
}
.hg-report-section-title {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--muted);
  margin: 14px 0 6px;
}
.hg-report-section-title:first-of-type { margin-top: 4px; }
.hg-report-summary {
  background: var(--panel-2);
  border-radius: 8px;
  padding: 10px 12px;
  font-size: 0.85rem;
}
.hg-report-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 5px 0;
}
.hg-report-row .label { color: var(--muted); }
.hg-report-positions {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  padding: 6px 0 8px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 4px;
}
.hg-report-positions span {
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 6px;
}
.hg-report-fields label { display: block; font-size: 0.78rem; color: var(--muted); }
.hg-report-fields select,
.hg-report-fields input { display: block; width: 100%; margin-top: 4px; box-sizing: border-box; }
/* Time and Shift sit side by side - they answer the same question ("when"),
   and Shift defaults from Time, so seeing the pair together is what makes
   an override read as deliberate. Harvested By takes the full width because
   its options carry a name and a role. */
.hg-report-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; align-items: start; }
.hg-report-fields > .full,
.hg-report-fields > .hg-notice { grid-column: 1 / -1; }

.hg-field-hint {
  display: block;
  margin-top: 4px;
  font-size: 0.72rem;
  color: var(--warn-text);
  font-weight: 500;
}
.hg-field-hint:empty { display: none; }

.hg-report-fields select:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}
.hg-report-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  margin-top: 16px;
}
.hg-report-actions button:disabled { opacity: 0.45; cursor: not-allowed; }

/* --- Toast --------------------------------------------------------------
   Sits above the grid modal (which is z-index 1000-ish) because it reports
   on an action taken inside it. Not interactive and never blocking - it
   confirms something already written. */
.hg-toast {
  position: fixed;
  left: 50%;
  bottom: 32px;
  transform: translateX(-50%);
  z-index: 4000;
  max-width: min(90vw, 420px);
  padding: 11px 18px;
  border-radius: 999px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  font-size: 0.85rem;
  font-weight: 600;
  text-align: center;
  pointer-events: none;
}
.hg-toast[hidden] { display: none; }
@keyframes hg-toast-in {
  from { opacity: 0; transform: translate(-50%, 10px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}
.hg-toast-in { animation: hg-toast-in 0.18s ease-out; }

/* --- Footer action bar ------------------------------------------------- */
.hg-footer {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 13px var(--hg-pad);
  border-top: 1px solid var(--border);
  background: var(--panel-2);
  flex-shrink: 0;
}
/* display:flex above beats the [hidden] attribute's own display:none, so
   hiding the footer during an active cycle needs this explicitly. */
.hg-footer[hidden] { display: none; }
.hg-summary {
  font-size: 0.82rem;
  color: var(--muted);
  margin-right: auto;
  padding-right: 6px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.hg-summary.has-selection { color: var(--text); font-weight: 600; }
.hg-rejects {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 0.78rem;
  color: var(--muted);
  font-weight: 600;
}
.hg-rejects { flex-shrink: 0; }
.hg-rejects input {
  width: 64px;
  height: 34px;
  padding: 0 9px;
  font-size: 0.85rem;
  border-radius: 7px;
  box-sizing: border-box;
}
/* Every control in the bar shares one height, so nothing sits proud. */
.hg-footer button {
  height: 34px;
  padding: 0 16px;
  font-size: 0.85rem;
  border-radius: 7px;
  font-weight: 600;
  white-space: nowrap;
  flex-shrink: 0;
}
.hg-secondary {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border);
}
.hg-secondary:hover { background: var(--panel); border-color: var(--accent-tint-border); }

/* Short screens: fall back to scrolling rather than shrinking tiles below
   a comfortably clickable size. */
@media (max-height: 660px) {
  .hg-modal { --hg-pad: 12px; max-height: 97vh; }
  .hg-modal-p1 { --hg-tile: 21px; --hg-gap: 5px; --hg-blockgap: 22px; }
  .hg-body { --hg-body-gap: 9px; }
  .hg-modal-header { padding: 12px var(--hg-pad); }
  .hg-modal-sub { display: none; }
}
/* Narrow screens: stack the two blocks and let the grid scroll. */
@media (max-width: 700px) {
  .hg-p1-grid { gap: 20px; }
  .hg-fieldgroup { flex-wrap: wrap; }
  .hg-icon { width: 12px; height: 12px; }
  .hg-cycle-summary { font-size: 0.74rem; gap: 10px; }
  .hg-legend { gap: 12px; font-size: 0.7rem; }
  .confirm-modal { max-width: 92vw !important; }
}

/* Clickable stat cards - hover affordance matching the app's other
   click-through cards. */
.stat-card.clickable,
.glance-card.clickable {
  cursor: pointer;
  transition: transform 0.1s ease, border-color 0.1s ease, box-shadow 0.1s ease;
}
.stat-card.clickable:hover,
.glance-card.clickable:hover {
  transform: translateY(-2px);
  border-color: var(--accent);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.28);
}

/* --- Date stepper ------------------------------------------------------
   Prev/next day arrows around a date input, used for every "log an event"
   date field. Matches the period-nav arrow styling already in the app. */
.date-stepper {
  display: inline-flex;
  align-items: stretch;
  gap: 4px;
}
.date-stepper input[type="date"] {
  flex: 1 1 auto;
  min-width: 0;
}
/* Size, colour, hover and disabled state all come from the shared .nav-arrow
   rule near the top of this file - the two used to be styled separately and
   drifted apart. Only the centring is specific to this one. */
.date-stepper-arrow {
  display: flex;
  align-items: center;
  justify-content: center;
}
.date-stepper-error {
  color: var(--warn-text);
  font-size: 0.74rem;
  margin-top: 4px;
  font-weight: 500;
}
.date-stepper-error[hidden] { display: none; }

/* Inside a Harvest Grid field group the arrows AND the date label match the
   34px control height used by every other input there - the label replaced
   a raw <input type="date"> that was already 34px, so it has to carry that
   height rather than fall back to its own padding. */
.hg-fieldgroup .date-stepper-arrow { height: 34px; }
.hg-fieldgroup .date-stepper { width: 100%; }
.hg-fieldgroup .period-nav-wrap { flex: 1 1 auto; min-width: 0; }
.hg-fieldgroup .period-nav-label {
  height: 34px;
  width: 100%;
  min-width: 0;
  padding: 0 10px;
}

/* Time is de-emphasised when backdating: Shift is the meaningful unit of
   time for a past harvest, not the clock. */
.hg-time-field.is-backdated { opacity: 0.45; }
.hg-time-field.is-backdated input { pointer-events: none; }
.hg-time-note {
  font-size: 0.68rem;
  color: var(--muted);
  font-weight: 500;
}

/* --- Who's Present (Dashboard) -------------------------------------------
   Sits under the Operations stat cards, and reads as part of that group
   rather than as a new section - same panel treatment, full width, because
   it carries a list rather than a single figure. */

.whos-present {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 16px 18px;
  margin-top: 14px;
}

.wp-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}
.wp-title { margin: 0; font-size: 1rem; }
.wp-title-block { display: flex; flex-direction: column; gap: 4px; }

.wp-now-label {
  background: transparent;
  border: 1px solid transparent;
  color: var(--muted);
  font-size: 0.8rem;
  padding: 3px 8px;
  border-radius: 999px;
  text-align: left;
  display: inline-flex;
  gap: 7px;
  align-items: center;
}
.wp-now-label:hover { border-color: var(--border); color: var(--text); }
/* When you're already on today's current shift there is nothing to snap
   back to, so it stops advertising itself as an action and just reads as a
   status line. */
.wp-now-label.is-now {
  color: var(--accent2-text);
  background: var(--accent2-tint);
  cursor: default;
}
.wp-now-label.is-now:hover { border-color: transparent; color: var(--accent2-text); }
.wp-label-sep { opacity: 0.5; }

.wp-controls { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.wp-day-nav { display: flex; align-items: center; gap: 4px; }
.wp-day-nav input[type="date"] { padding: 5px 8px; font-size: 0.83rem; }

/* One section per shift, all three always rendered. The header is a divider
   with a label on it rather than a heading - three headings in a dashboard
   card would out-shout the card's own title, and the thing being scanned is
   the names underneath. */
.wp-shift + .wp-shift { margin-top: 14px; }

.wp-shift-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding-bottom: 5px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--border);
}
.wp-shift-name {
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.wp-shift-window {
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  opacity: 0.8;
}
.wp-shift-count {
  font-size: 0.76rem;
  color: var(--muted);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
/* The shift happening right now is the one being asked about most of the
   time, so it gets a marker - still a divider, just not a grey one. */
.wp-shift.is-current .wp-shift-head { border-bottom-color: var(--accent2); }
.wp-shift.is-current .wp-shift-name,
.wp-shift.is-current .wp-shift-count { color: var(--accent2-text); }

.wp-total {
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--muted);
  margin-left: 4px;
}

.wp-table { width: 100%; font-size: 0.88rem; }
.wp-table td { padding: 7px 4px; border-bottom: 1px solid var(--border); }
.wp-table tr:last-child td { border-bottom: none; }
.wp-name { white-space: normal; overflow-wrap: break-word; }
.wp-position { color: var(--muted); font-size: 0.8rem; margin-left: 6px; }
.wp-status-cell { text-align: right; white-space: nowrap; width: 1%; }

.wp-status {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 999px;
  font-size: 0.74rem;
  font-weight: 600;
}
.wp-present { background: var(--accent2-tint); color: var(--accent2-text); }
.wp-late { background: var(--warn-tint); color: var(--warn-text); }
/* Half Day is its own thing - they were here, and are owed half a
   shift. Distinct from Late, which is paid in full. */
.wp-half { background: var(--accent-tint); color: var(--accent); }
.wp-absent { background: var(--danger-tint); color: var(--danger); }
.wp-unmarked, .wp-scheduled {
  background: var(--panel);
  color: var(--muted);
  font-weight: 500;
}

.wp-empty { color: var(--muted); font-size: 0.85rem; margin: 7px 0 2px; }
.wp-note { color: var(--muted); font-size: 0.78rem; margin: 10px 0 0; }

/* --- User Access Management ---------------------------------------------
   The list, and the Edit Access panel that opens over it. */

.access-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: flex-end;
  margin: 16px 0 12px;
  padding: 14px 16px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 10px;
}
.access-filters label {
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 0.78rem;
  color: var(--muted);
}
.access-filters input,
.access-filters select { min-width: 150px; }
.access-filters #add-user-btn { margin-left: auto; }

.btn-primary {
  background: var(--accent);
  color: #fff;
  border: 1px solid var(--accent);
  padding: 8px 14px;
  border-radius: 8px;
  font-weight: 600;
}
.btn-primary:hover { background: var(--accent-hover); }
.btn-primary:active { background: var(--accent-active); }

.btn-quiet {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border);
  padding: 6px 11px;
  border-radius: 8px;
  font-size: 0.82rem;
}
.btn-quiet:hover { background: var(--panel); border-color: var(--accent-tint-border); }
.btn-quiet:disabled { opacity: 0.45; }

.btn-danger {
  background: var(--danger-solid);
  color: #fff;
  border: 1px solid var(--danger-solid);
  padding: 8px 14px;
  border-radius: 8px;
  font-weight: 600;
}
.btn-danger:hover { background: #B91C1C; }
.btn-danger:disabled { opacity: 0.45; }

.muted-cell { color: var(--muted); font-size: 0.82rem; }
.row-inactive { opacity: 0.62; }
.row-actions { white-space: nowrap; display: flex; gap: 6px; }

.status-pill {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 0.74rem;
  font-weight: 600;
}
.status-active { background: var(--accent2-tint); color: var(--accent2-text); }
.status-inactive { background: var(--danger-tint); color: var(--danger); }
/* The third outcome, used by the Backups run log: the job did its work but
   something about it is not what you want long-term (a backup that never
   left this disk). Neither green nor red would be honest about that. */
.status-warn { background: var(--warn-tint); color: var(--warn-text); }

/* The reason field on a delete confirmation. */
.cd-input-wrap { display: block; margin: 4px 0 14px; }
.cd-input-label { display: block; font-size: 0.82rem; color: var(--muted); margin-bottom: 4px; }
.cd-input { width: 100%; }
.cd-input-error { display: block; margin-top: 4px; }

/* The reason column in Deleted Records - long enough to read, wrapped
   rather than truncated, because a reason nobody can read is not one. */
.del-reason { max-width: 320px; white-space: normal; color: var(--muted); font-size: 0.86rem; }

/* The one-time temporary password on the Users page. Monospace and spaced
   out because its whole job is to be transcribed correctly once - it is
   read off a screen and typed somewhere else, often by two different
   people over a phone. */
.temp-password-box {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 12px 0 8px;
  padding: 12px 14px;
  background: var(--accent-tint, rgba(255,255,255,0.04));
  border: 1px solid var(--border);
  border-radius: 8px;
}
/* --- Period reports ----------------------------------------------------------
   Unlike the Statement of Account, this one stays in the app's own dark
   theme on screen: it is read at a desk by the people who run the business,
   not sent to a customer. It only becomes paper when printed, which is what
   the @media print block at the end of this section does. */
.rep-doc { margin-top: 4px; }

.rep-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 24px;
  padding-bottom: 12px;
  margin-bottom: 18px;
  border-bottom: 2px solid var(--border);
}
.rep-company { font-size: 1.1rem; font-weight: 700; }
.rep-tagline { font-size: 0.78rem; color: var(--muted); }
.rep-head-meta { text-align: right; font-size: 0.85rem; }
.rep-title { font-weight: 700; letter-spacing: 0.12em; font-size: 0.78rem; color: var(--muted); }
.rep-vs { color: var(--muted); font-size: 0.8rem; }

.rep-section { margin-bottom: 26px; }
.rep-section-title {
  font-size: 0.78rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  border-bottom: 1px solid var(--border);
  padding-bottom: 5px;
  margin: 0 0 10px;
}
.rep-sub { font-size: 0.88rem; margin: 16px 0 6px; color: var(--muted); }

.rep-table { width: 100%; }
.rep-table th[scope="row"] { font-weight: 500; text-align: left; }
.rep-table .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.rep-headline th, .rep-headline td { font-weight: 700; font-size: 1.02em; }
.rep-up { color: var(--accent2); }
.rep-down { color: var(--danger); }
.rep-empty { text-align: center; color: var(--muted); padding: 14px 0; }

.rep-chart-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 18px;
}
.rep-chart { height: 260px; }

.rep-overhead {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 8px;
}
.rep-overhead label { display: flex; align-items: center; gap: 8px; font-size: 0.85rem; }
/* On screen the rule is a control; on paper it is a statement of fact, so
   the printed note stands in for the dropdown. */
.rep-method-note { font-size: 0.82rem; color: var(--muted); margin: 0 0 10px; display: none; }

.rep-foot {
  margin-top: 22px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
  font-size: 0.78rem;
  color: var(--muted);
  text-align: center;
}

/* The quarter label in the shared date filter, standing in for a
   period_picker one. Sized to match so the arrows don't shift when the
   preset changes. */
.drf-quarter-label {
  display: inline-block;
  min-width: 86px;
  text-align: center;
  font-weight: 600;
  font-size: 0.85rem;
}

@media print {
  .rep-doc { color: #000; }
  /* A section is the unit somebody reads; splitting one across a sheet
     boundary is what makes a printed report annoying to use. */
  .rep-section { page-break-inside: avoid; page-break-after: always; }
  .rep-section:last-of-type { page-break-after: auto; }
  .rep-head { border-bottom-color: #000; }
  .rep-section-title { color: #000; border-bottom-color: #000; }
  .rep-tagline, .rep-vs, .rep-title, .rep-foot, .field-hint { color: #333 !important; }
  .rep-table th, .rep-table td { border-bottom: 1px solid #ccc; }
  .rep-up { color: #16610f !important; }
  .rep-down { color: #9b1c1c !important; }
  /* Charts are screen furniture. In print they are a grey box that costs a
     page, and every figure behind them is in the tables above. */
  .rep-charts { display: none; }
  /* The dropdown is gone in print, so the sentence saying which rule was
     used takes its place - a printed P&L that doesn't say how shared costs
     were divided is a P&L nobody can check. */
  .rep-method-note { display: block !important; color: #000 !important; }
  /* The company header repeats on every printed sheet, so a page that gets
     separated from the rest still says whose it is and what it covers.
     Done with a real element per section rather than @page { @top-center },
     which is CSS Paged Media that no browser actually implements - and
     since each section is its own page, one header per section is one
     header per page. */
  .rep-print-header {
    display: flex !important;
    justify-content: space-between;
    align-items: baseline;
    gap: 12px;
    padding-bottom: 4px;
    margin-bottom: 10px;
    border-bottom: 1px solid #999;
    font-size: 9pt;
    color: #333;
  }
  .rep-print-header strong { color: #000; }
  @page { margin: 14mm; }
}

/* Only ever on paper. */
.rep-print-header { display: none; }

/* --- Statement of Account ---------------------------------------------------
   The one thing in this app that leaves the building. It is deliberately
   white paper with black text on screen as well as in print: what somebody
   sees before pressing Print is exactly what the customer receives, and a
   document that only turns readable in the print preview is a document
   nobody checks before sending. */
.soa-toolbar {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.soa-toolbar-actions { display: flex; gap: 10px; align-items: center; }
.soa-range { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 20px; }

.soa-doc {
  background: #fff;
  color: #000;
  max-width: 820px;
  margin: 0 auto 28px;
  padding: 34px 38px 28px;
  border-radius: 4px;
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.35);
  font-size: 13px;
  line-height: 1.45;
  /* Statements are read as documents, not as app screens, and every printed
     financial document anyone here has ever handled is set in a serif. */
  font-family: Georgia, "Times New Roman", serif;
}
.soa-doc table { width: 100%; border-collapse: collapse; }
/* The app's global table rules paint their own dark backgrounds and light
   text, and they beat .soa-doc's colour on specificity - so the "white
   paper" document came out with dark bands through every table. This resets
   the inherited theme inside the document, and is placed before the specific
   .soa-* rules below so those still win where they set a colour on purpose. */
.soa-doc table, .soa-doc thead, .soa-doc tbody, .soa-doc tfoot,
.soa-doc tr, .soa-doc th, .soa-doc td,
/* Written out at the same shape as the global rules they have to beat
   (tbody tr:nth-child(even) and tbody tr:hover), because equal specificity
   plus later-in-the-file is what wins. Matching them by name rather than
   reaching for !important keeps this readable and keeps the door open for
   the .soa-* rules below to override it in turn. */
.soa-doc tbody tr:nth-child(even), .soa-doc tbody tr:hover {
  background: transparent;
  color: inherit;
  border-color: #ccc;
}
.soa-doc th, .soa-doc td { text-align: left; }
.soa-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }

.soa-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 24px;
  padding-bottom: 14px;
  border-bottom: 2px solid #000;
}
.soa-company { font-size: 20px; font-weight: 700; letter-spacing: 0.01em; }
.soa-tagline { font-size: 11px; color: #444; margin-top: 2px; }
.soa-title { text-align: right; }
.soa-title-main { font-size: 13px; font-weight: 700; letter-spacing: 0.14em; margin-bottom: 6px; }
.soa-meta { width: auto; margin-left: auto; font-size: 11.5px; }
.soa-meta th { font-weight: 400; color: #444; padding-right: 12px; text-align: right; }
.soa-meta td { text-align: right; font-weight: 600; }

.soa-to { margin: 16px 0 18px; font-size: 12.5px; }
.soa-to-label { font-size: 10px; letter-spacing: 0.12em; color: #555; text-transform: uppercase; }
.soa-to-name { font-size: 15px; font-weight: 700; margin: 2px 0 3px; }

.soa-ledger { font-size: 12px; }
.soa-ledger thead th {
  border-bottom: 1.5px solid #000;
  padding: 6px 8px;
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.soa-ledger tbody td { padding: 5px 8px; border-bottom: 1px solid #ddd; vertical-align: top; }
.soa-c-date { width: 82px; white-space: nowrap; }
.soa-c-ref { width: 110px; }
.soa-opening td { background: #f4f4f4; font-style: italic; }
.soa-payment-row td { color: #16610f; }
.soa-due { color: #666; font-size: 10.5px; white-space: nowrap; }
.soa-empty { text-align: center; color: #666; padding: 18px 0; font-style: italic; }
.soa-ledger tfoot th { padding: 6px 8px; border-top: 1.5px solid #000; font-size: 11.5px; }
.soa-closing th {
  border-top: 1px solid #000;
  border-bottom: 3px double #000;
  font-size: 13.5px;
  letter-spacing: 0.04em;
}

.soa-lower { margin-top: 18px; }
.soa-section-title {
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #444;
  margin-bottom: 5px;
}
.soa-aging-table { font-size: 11.5px; }
.soa-aging-table th, .soa-aging-table td { border: 1px solid #999; padding: 5px 8px; }
.soa-aging-table th { background: #f4f4f4; text-align: center; font-size: 10.5px; }
.soa-overdue { font-weight: 700; }
.soa-terms-note { font-size: 11px; color: #333; margin: 8px 0 0; }

.soa-slip { margin-top: 26px; }
.soa-slip-cut {
  border-top: 1px dashed #666;
  padding-top: 4px;
  font-size: 10px;
  color: #666;
  letter-spacing: 0.06em;
}
.soa-slip-body {
  display: flex;
  justify-content: space-between;
  gap: 28px;
  margin-top: 10px;
  padding: 12px 14px;
  border: 1px solid #999;
}
/* Both halves given explicit flex behaviour. Left to themselves, the
   figures table squeezed the details column until "Epic Ice Hub Centre
   Inc." wrapped under its own label. */
.soa-slip-details { flex: 1 1 auto; min-width: 230px; }
.soa-slip-title { font-size: 11px; font-weight: 700; letter-spacing: 0.12em; margin-bottom: 6px; }
/* A row, not an inline-block label followed by text: the label keeps its
   column and a long company or customer name wraps within its own, instead
   of starting under the label it belongs to. */
.soa-slip-line { display: flex; gap: 8px; font-size: 11.5px; margin-bottom: 3px; }
.soa-slip-line span { flex: 0 0 88px; color: #555; }
.soa-slip-figures { flex: 0 0 300px; width: 300px; font-size: 11.5px; }
.soa-slip-figures th { font-weight: 400; color: #333; padding: 3px 8px 3px 0; white-space: nowrap; }
.soa-slip-figures td { padding: 3px 0; }
.soa-slip-blank { border-bottom: 1px solid #666; min-width: 120px; height: 15px; }

.soa-foot {
  margin-top: 18px;
  padding-top: 8px;
  border-top: 1px solid #ccc;
  font-size: 10.5px;
  color: #555;
  text-align: center;
}

@media print {
  /* Everything that is app rather than document. */
  .no-print, .topbar, .subnav, .subnav-secondary, .conn-banner, .toast-host,
  .flash, .cd-overlay { display: none !important; }
  body, .container { background: #fff !important; margin: 0; padding: 0; max-width: none; }
  .soa-doc {
    box-shadow: none;
    border-radius: 0;
    margin: 0;
    padding: 0;
    max-width: none;
    /* One statement per sheet in a batch run. The last one gets no break
       after it, or every batch prints a trailing blank page. */
    page-break-after: always;
  }
  .soa-doc:last-child { page-break-after: auto; }
  /* A ledger row split across a page boundary is unreadable, and the
     remittance slip must never be orphaned from the amount it is asking for. */
  .soa-ledger tr, .soa-slip, .soa-aging { page-break-inside: avoid; }
  .soa-ledger thead { display: table-header-group; }
  @page { margin: 14mm; }
}

/* --- Connection state ------------------------------------------------------
   The offline banner is fixed to the bottom rather than pushed into the page
   flow: it appears and disappears while somebody is mid-entry, and a bar
   that reflows the form under their cursor is its own small disaster. */
.conn-banner {
  position: fixed;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  z-index: 900;
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: min(680px, calc(100vw - 32px));
  padding: 10px 16px;
  border-radius: 10px;
  font-size: 0.87rem;
  line-height: 1.35;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.35);
}
.conn-offline {
  background: var(--danger-tint);
  border: 1px solid var(--danger);
  color: var(--danger);
}
.conn-pending {
  background: var(--warn-tint);
  border: 1px solid var(--warn);
  color: var(--warn-text);
}
.conn-banner-dot {
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: currentColor;
  animation: conn-pulse 1.6s ease-in-out infinite;
}
@keyframes conn-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
.conn-banner-action {
  flex: none;
  background: transparent;
  border: 1px solid currentColor;
  color: inherit;
  border-radius: 6px;
  padding: 4px 10px;
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
}

/* Toasts: the "it saved" the app never used to say. */
.toast-host {
  position: fixed;
  top: 66px;
  right: 16px;
  z-index: 950;
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-width: min(420px, calc(100vw - 32px));
}
.toast {
  padding: 10px 14px;
  border-radius: 8px;
  font-size: 0.87rem;
  line-height: 1.35;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
  transition: opacity 0.5s ease, transform 0.5s ease;
}
.toast-ok { background: var(--accent2-tint); color: var(--accent2-text); border: 1px solid var(--accent2); }
.toast-warn { background: var(--warn-tint); color: var(--warn-text); border: 1px solid var(--warn); }
.toast-bad { background: var(--danger-tint); color: var(--danger); border: 1px solid var(--danger); }
.toast-out { opacity: 0; transform: translateX(12px); }

/* "Recovered unsaved entry", above the form it refers to. */
.draft-notice {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  margin: 0 0 14px;
  padding: 10px 14px;
  border-radius: 8px;
  background: var(--warn-tint);
  border: 1px solid var(--warn);
  color: var(--warn-text);
  font-size: 0.87rem;
}

/* A submit button mid-flight. The pointer-events rule is the part that
   matters: disabled alone still lets a determined double-tap land on some
   mobile browsers before the attribute takes effect. */
button.is-busy, .is-busy {
  opacity: 0.65;
  cursor: progress;
  pointer-events: none;
}

/* The "last signed in from …" notice on the dashboard. Quieter than a
   normal flash: it is information, not something that went wrong, and it
   only earns attention when the date or address is unfamiliar. */
.last-login-notice {
  font-size: 0.86rem;
  opacity: 0.9;
}
.last-login-notice code { font-size: 0.86rem; }

/* Read-only facts in the Edit Access panel. */
.account-facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 6px 16px;
  margin: 0 0 4px;
  font-size: 0.88rem;
}
.account-facts dt { color: var(--muted); }
.account-facts dd { margin: 0; }

/* A refused sign-in in the Login History table. The pill already says so;
   this is what makes a run of them visible at a glance while scrolling,
   which is the shape a brute-force attempt actually has. */
.login-row-failed > td { background: var(--danger-tint); }

.temp-password-box code {
  flex: 1;
  font-size: 1.25rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  user-select: all;
  word-break: break-all;
}

/* An expense the app wrote for itself. Marked rather than hidden - it is a
   real figure in the totals - but with no Edit or Delete beside it, because
   the record that produced it owns the number. */
.system-expense-badge {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--accent-tint, transparent);
  color: var(--muted);
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
  cursor: help;
}
.system-expense-row td { background: rgba(255, 255, 255, 0.018); }

.access-overlay {
  position: fixed;
  inset: 0;
  background: rgba(4, 8, 16, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  z-index: 60;
}
.access-overlay[hidden] { display: none; }

.access-panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  width: min(920px, 100%);
  max-height: 90vh;
  display: flex;
  flex-direction: column;
}
.access-panel-small { width: min(520px, 100%); }

.access-panel-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 18px 20px 12px;
  border-bottom: 1px solid var(--border);
}
.access-panel-header h2 { margin: 0; font-size: 1.05rem; }
.access-panel-sub { margin: 4px 0 0; color: var(--muted); font-size: 0.84rem; }
.access-close {
  background: transparent;
  border: none;
  color: var(--muted);
  font-size: 1.5rem;
  line-height: 1;
  padding: 0 4px;
}
.access-close:hover { color: var(--text); }

.access-panel-body { padding: 18px 20px; overflow-y: auto; }

.access-panel-footer {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 20px;
  border-top: 1px solid var(--border);
}
.access-footer-spacer { flex: 1; }

.access-field-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 14px;
  margin-bottom: 18px;
}
.access-field-grid label {
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 0.8rem;
  color: var(--muted);
}
.field-hint { color: var(--muted); font-size: 0.75rem; }
/* A requirement that has been met, said in the same place it was asked. */
.field-hint-ok { color: var(--accent2-text); }
.field-hint-ok::before { content: "✓ "; }

/* --- Inline field validation ---------------------------------------------
   The message belongs under the field it is about. A banner elsewhere - or
   worse, behind a modal that closed and threw the form away - describes a
   problem you can no longer see or fix. */
.field-error {
  display: block;
  margin-top: 4px;
  color: var(--danger);
  font-size: 0.75rem;
  font-weight: 600;
}
.field-error[hidden] { display: none; }
.field-invalid,
input.field-invalid,
select.field-invalid,
textarea.field-invalid {
  border-color: var(--danger);
  outline-color: var(--danger);
}
.field-invalid:focus { outline: 1px solid var(--danger); }

/* A heading that divides one form into named parts. */
.form-section-title {
  margin: 18px 0 8px;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.form-section-title:first-child { margin-top: 0; }

.perm-grid-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 4px 0 8px;
}
.perm-grid-header h3 { margin: 0; font-size: 0.95rem; }
.perm-grid-actions { display: flex; align-items: center; gap: 12px; }

.perm-legend { color: var(--muted); font-size: 0.75rem; display: inline-flex; align-items: center; gap: 6px; }

/* The override marker. Deliberately a dot next to the box rather than a
   recolouring of the box itself: the checkbox still has to read as
   checked/unchecked at a glance, and "differs from the role default" is a
   second, independent fact about it. */
.perm-override-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--warn);
  display: inline-block;
  flex: 0 0 auto;
}
.perm-override-dot[hidden] { display: none; }

.perm-grid-wrap { max-height: 340px; overflow-y: auto; }
.perm-grid { width: 100%; }
.perm-grid th[scope="row"] {
  text-align: left;
  font-weight: 500;
  white-space: nowrap;
}
.perm-grid .perm-col { text-align: center; width: 88px; }
.perm-cell {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 6px;
  border-radius: 6px;
}
.perm-cell.is-override { background: var(--warn-tint); }
.perm-na { color: var(--border); }

.perm-row-locked th[scope="row"] { color: var(--muted); }
.perm-locked-note {
  display: block;
  font-size: 0.68rem;
  color: var(--warn-text);
  font-weight: 400;
}

.access-history { margin-top: 22px; border-top: 1px solid var(--border); padding-top: 14px; }
.access-history h3 { margin: 0 0 10px; font-size: 0.95rem; }
.access-history-list { max-height: 220px; overflow-y: auto; }
.access-history-row {
  display: grid;
  grid-template-columns: 140px 1fr;
  gap: 12px;
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
  font-size: 0.82rem;
}
.access-history-row:last-child { border-bottom: none; }
.access-history-when { color: var(--muted); font-variant-numeric: tabular-nums; }

/* --- Data Management -----------------------------------------------------
   The environment banner leads, because the one unrecoverable mistake this
   page allows is running it against the wrong database. */

.env-banner {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  border-radius: 10px;
  margin: 0 0 18px;
  border: 1px solid;
  font-size: 0.88rem;
}
.env-banner-compact { margin-bottom: 12px; padding: 8px 12px; }
.env-banner-badge {
  font-weight: 700;
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  padding: 4px 10px;
  border-radius: 999px;
  white-space: nowrap;
}
.env-production {
  background: var(--danger-tint);
  border-color: var(--danger);
  color: #FECACA;
}
.env-production .env-banner-badge { background: var(--danger-solid); color: #fff; }
.env-local {
  background: var(--accent2-tint);
  border-color: var(--accent2);
  color: var(--accent2-text);
}
.env-local .env-banner-badge { background: var(--accent2); color: #06281E; }

.dm-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: 18px;
  align-items: start;
  margin-top: 18px;
}
@media (max-width: 900px) {
  .dm-layout { grid-template-columns: 1fr; }
}

.dm-panel {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 16px 18px;
  margin-bottom: 18px;
}
.dm-panel-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}
.dm-panel-header h2 { margin: 0; font-size: 1rem; }
.dm-panel-actions { display: flex; gap: 8px; }

.dm-type-list { list-style: none; margin: 0; padding: 0; }
.dm-type { border-bottom: 1px solid var(--border); }
.dm-type:last-child { border-bottom: none; }
.dm-type > label {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 12px;
  align-items: start;
  padding: 11px 2px;
}
.dm-type-body { display: flex; flex-direction: column; gap: 2px; }
.dm-type-label { font-weight: 500; }
.dm-type-desc { color: var(--muted); font-size: 0.78rem; }
.dm-type-count {
  color: var(--accent2-text);
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.dm-type-count.is-zero { color: var(--muted); }

.dm-protected {
  margin: 14px 0 0;
  padding-top: 12px;
  border-top: 1px dashed var(--border);
  color: var(--muted);
  font-size: 0.78rem;
}

.dm-field { display: flex; flex-direction: column; gap: 5px; margin-bottom: 14px; font-size: 0.8rem; color: var(--muted); }
.dm-field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.dm-actions { display: flex; align-items: center; gap: 12px; margin-top: 6px; }

.dm-preview-table { width: 100%; }
.dm-preview-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.dm-preview-table tfoot th { border-top: 2px solid var(--border); }
.dm-skipped { color: var(--warn-text); }

.dm-blocked {
  background: var(--danger-tint);
  border: 1px solid var(--danger);
  border-radius: 10px;
  padding: 12px 14px;
  margin-bottom: 14px;
  font-size: 0.85rem;
}
.dm-blocked ul { margin: 8px 0 0; padding-left: 20px; }
.dm-blocked li { margin-bottom: 5px; }

.dm-confirm-warning {
  background: var(--warn-tint);
  border-left: 3px solid var(--warn);
  padding: 10px 12px;
  border-radius: 6px;
  font-size: 0.83rem;
}
.dm-confirm-list { margin: 8px 0 0; padding-left: 20px; font-size: 0.85rem; }

.dm-result-meta {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 8px 18px;
  margin: 16px 0 0;
  font-size: 0.85rem;
}
.dm-result-meta dt { color: var(--muted); }
.dm-result-meta dd { margin: 0; }
.dm-result-meta code {
  background: var(--panel);
  padding: 2px 6px;
  border-radius: 5px;
  font-size: 0.82rem;
}

/* --- Deductions ----------------------------------------------------------
   The entry rows reuse .shift-add-* wholesale - it is the same interaction,
   and a second implementation of it would be a second thing to keep in step
   with the Shift Roster. Only the per-type inline fields are new. */

.ded-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 14px;
  margin: 16px 0;
}
.ded-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px 16px;
}
.ded-card .label {
  color: var(--muted);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.ded-card .value { font-size: 1.35rem; font-weight: 700; margin-top: 5px; }
.ded-card .value.value-negative { color: var(--danger); }
.ded-card .sub { color: var(--muted); font-size: 0.75rem; margin-top: 3px; }

.ded-add-block {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px 16px;
  margin-bottom: 18px;
}
.ded-add-block[hidden] { display: none; }
.ded-add-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.ded-add-head h2 { margin: 0; font-size: 1rem; }
.ded-add-block .shift-add { border-top: none; padding-top: 6px; margin-top: 6px; }

/* --- Deductions on a person's record -------------------------------------
   Two grouped lists, each with its own subtotal, side by side where there is
   room. The company-wide page is a read-only roll-up now; this is where a
   deduction is actually added and removed. */
.ded-person {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 16px;
  margin-bottom: 12px;
}
/* A column, so the totals can be pushed to the bottom edge: the two cards
   rarely hold the same number of rows, and a total that floats up under a
   short list stops reading as the card's bottom line. */
.ded-person-group {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 14px;
  background: var(--panel);
  display: flex;
  flex-direction: column;
}
.ded-person-group .table-wrap { margin-bottom: 0; }
.ded-person-foot {
  margin-top: auto;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}
.ded-person-total-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  font-size: 0.85rem;
}
.ded-person-total-row strong { font-variant-numeric: tabular-nums; }
.ded-person-total-row + .ded-person-total-row { margin-top: 4px; }
.ded-person-outstanding { color: var(--muted); }
.ded-person-outstanding strong { color: var(--warn, var(--muted)); }

.ded-combined-total {
  margin: 0 0 18px;
  font-size: 0.9rem;
  color: var(--muted);
}
.ded-combined-total strong {
  color: var(--text);
  font-size: 1.05rem;
  font-variant-numeric: tabular-nums;
}
.ded-person-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 4px;
}
.ded-person-head h3 {
  margin: 0;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.ded-person-total {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.ded-person-table { width: 100%; font-size: 0.85rem; }
.ded-person-table td { padding: 5px 6px; }
.ded-person-table .num { text-align: right; white-space: nowrap; }
.ded-person-table .row-actions { text-align: right; white-space: nowrap; }
.ded-person-table .btn-delete { padding: 2px 7px; font-size: 0.7rem; }
.ded-remaining { color: var(--warn, var(--muted)); font-size: 0.8rem; }

/* A row on the Deductions overview opens that person's record. */
.ded-employee-row { cursor: pointer; }
.ded-employee-row:hover td { background: var(--panel); }
.ded-employee-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* --- Filter bar ----------------------------------------------------------
   A grid, not a flex row: fields wrap evenly instead of leaving the last one
   and the Clear button stranded on a line of their own. */
.filter-grid {
  display: grid;
  /* Explicit column counts, not auto-fit. Four fields divide evenly into
     four, two or one - and auto-fit will happily pick three, which leaves
     the fourth alone on a line. That lone leftover is the thing this grid
     exists to prevent, so the counts are chosen rather than computed. A bar
     with a different number of fields wants its own breakpoints.
     230px minimum per column so a track can hold two date inputs side by
     side; below that the range stacks inside its own cell. */
  grid-template-columns: 1fr;
  gap: 12px;
  align-items: end;
  margin-bottom: 16px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--panel);
}
.filter-grid label {
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 0.8rem;
  color: var(--muted);
  min-width: 0;
}
.filter-grid select,
.filter-grid input { width: 100%; min-width: 0; }
/* The date range is one question, so it sits under one label. The cell is an
   ordinary one-column cell - spanning two made it the item that could not
   fit and so wrapped alone, which is the stranding this grid is here to
   avoid. The control wraps inside it instead, staying one labelled control
   at any width.

   Shaped like the grid's own labels so the shared range component sits in a
   filter grid without either side knowing about the other. */
.filter-grid-range {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
}
.filter-grid-range .date-filter-label {
  font-size: 0.8rem;
  font-weight: 400;
  color: var(--muted);
  /* The shared label centres itself in a filters bar; in a grid cell it
     lines up with the labels beside it instead. */
  align-self: flex-start;
}
.filter-grid-range .date-filter-group { align-self: stretch; }
.filter-grid-range .date-filter-group > select { width: 100%; }
.date-range-control {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 6px;
}
.date-range-control input { flex: 1 1 90px; min-width: 0; }
.date-range-control .range-to-label { color: var(--muted); font-size: 0.78rem; }
/* The range control's own inputs must not be stretched to the grid's full
   width the way a lone select is - they sit beside the arrows. */
.date-filter-group .period-nav input,
.date-filter-group .custom-range-inputs input { width: auto; }
/* Anchored to its own full-width line, right-aligned. Left as an ordinary
   grid cell it became the one item that wrapped - which is the "stranded on
   a second line" this grid exists to fix, just moved along by one. A
   deliberate action row reads as deliberate at every width. */
@media (min-width: 620px) {
  .filter-grid { grid-template-columns: repeat(2, minmax(230px, 1fr)); }
}
@media (min-width: 1100px) {
  .filter-grid { grid-template-columns: repeat(4, minmax(230px, 1fr)); }
}

.filter-grid-actions {
  grid-column: 1 / -1;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  padding-top: 2px;
  border-top: 1px solid var(--border);
  margin-top: 2px;
}


/* A chosen type expands its row sideways with only the fields that type
   needs - inline, on the row itself. These used to drop into a bordered
   panel under the select, which turned one line into a block and made two
   staged rows look like two sections. The fields carry placeholders instead
   of labels for the same reason: the row stays a row. */
.ded-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}
.ded-row .shift-add-select { flex: 0 1 150px; width: auto; }
.ded-field {
  flex: 1 1 110px;
  min-width: 0;
  padding: 6px 8px;
  font-size: 0.83rem;
}
.ded-field-wide { flex: 2 1 140px; }
.ded-row .shift-add-remove { margin-left: auto; }

/* Inside a card the entry block is part of the card, not a panel within a
   panel. */
.ded-person-group .ded-add-block {
  background: transparent;
  border: none;
  border-radius: 0;
  padding: 8px 0 0;
  margin-bottom: 6px;
}
.ded-add-error { margin: 8px 0 0; }
/* Auto width, not the full card: a full-bleed primary button inside a card
   this narrow outweighs the list it belongs to. */
.ded-person-group .shift-add-confirm {
  display: inline-block;
  width: auto;
  margin-left: 10px;
  padding: 6px 16px;
}
.ded-person-group .shift-add-more { display: inline-block; }

.ded-kind {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 600;
  white-space: nowrap;
}
.ded-kind-recurring { background: var(--accent-tint); color: #A9BEF5; }
.ded-kind-one_time { background: var(--warn-tint); color: var(--warn-text); }

.ded-status-done { background: var(--accent2-tint); color: var(--accent2-text); }
.ded-status-paused { background: var(--panel); color: var(--muted); }

/* A locked row is history, not a control surface. */
.ded-locked-row { opacity: 0.72; }

#ded-table .num { text-align: right; font-variant-numeric: tabular-nums; }

/* --- Payroll breakdown ---------------------------------------------------
   Opened from a payroll row. Reuses .access-panel for the shell and
   .shift-add-* for the add-earning rows - the third page to use that
   interaction, and still one implementation of it. */

/* A row that opens something has to say so. */
.payroll-row { cursor: pointer; }
.payroll-row:hover td { background: var(--panel); }
.payroll-row:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.bd-header-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: flex-end;
}
/* Position belongs to the person, so it sits with the name - not between
   the two arrows, where it read as the thing the arrows were stepping
   through. */
.bd-identity { min-width: 0; }
.bd-position {
  margin: 2px 0 0;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text);
}
.bd-employee-nav {
  display: inline-flex;
  gap: 4px;
  /* Set apart from Print/Close so the pair reads as one control. */
  margin-right: 6px;
  padding-right: 8px;
  border-right: 1px solid var(--border);
}
.bd-employee-nav .nav-arrow { white-space: nowrap; }

.bd-section-title {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 18px 0 8px;
  padding-bottom: 5px;
  border-bottom: 1px solid var(--border);
}
.bd-section-title:first-child { margin-top: 0; }
.bd-subsection {
  font-size: 0.74rem;
  color: var(--muted);
  margin: 12px 0 4px;
  font-weight: 600;
}

.bd-table { width: 100%; font-size: 0.85rem; }
.bd-table th, .bd-table td { padding: 5px 6px; text-align: left; }
.bd-table .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.bd-table thead th {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
  border-bottom: 1px solid var(--border);
}
.bd-table tfoot th {
  border-top: 1px solid var(--border);
  font-weight: 700;
}
.bd-table .btn-delete { padding: 2px 7px; font-size: 0.7rem; }

.bd-total-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  padding: 9px 6px;
  margin-top: 8px;
  border-top: 1px solid var(--border);
  font-size: 0.9rem;
}
.bd-net-row {
  border-top: 2px solid var(--border);
  border-bottom: 3px double var(--border);
  font-size: 1.05rem;
}
.bd-net-row strong { color: var(--accent2-text); }

.bd-add { border-top: none; padding-top: 4px; margin-top: 8px; }
.bd-add-hint {
  margin: 6px 0 0;
  font-size: 0.75rem;
  color: var(--warn, var(--muted));
}
.bd-add-hint[hidden] { display: none; }

/* --- The add-earning row -------------------------------------------------
   One line: Type · [×multiplier] · Date · Amount · Description · x. Each
   field is named by its own placeholder, so the row stays the height of a
   row instead of growing a caption above every input. */
.bd-earn-row {
  /* Overrides .shift-add-row's two-column grid. That grid is right for the
     roster's one-select-per-line rows; here there are six controls that
     belong on one line, and left as a grid they stacked into three. */
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 6px;
}
.bd-earn-row > input,
.bd-earn-row > select {
  padding: 6px 8px;
  font-size: 0.83rem;
  min-width: 0;
  /* .shift-add-select is width:100%, which fights flex-basis. */
  width: auto;
}
.bd-earn-type { flex: 0 1 190px; }
.bd-earn-mult { flex: 0 0 96px; }
.bd-earn-multval { flex: 0 0 68px; }
.bd-earn-date { flex: 0 0 148px; }
/* Amount is a figure people check, so it gets room to show one - the old
   layout gave it less space than the free-text field beside it. */
.bd-earn-amount { flex: 0 0 120px; text-align: right; }
.bd-earn-desc { flex: 1 1 160px; }

@media (max-width: 700px) {
  .bd-earn-row { flex-wrap: wrap; }
  .bd-earn-row > input,
  .bd-earn-row > select { flex: 1 1 130px; }
  .bd-earn-desc { flex: 1 1 100%; }
}

/* --- Pinned summary footer ----------------------------------------------
   min-height:0 is what actually lets the body scroll: without it a flex
   item refuses to shrink below its content, the panel grows past its
   max-height, and the footer - Net pay and Close - is pushed off the
   bottom of the screen. */
.access-panel-header, .access-panel-footer { flex-shrink: 0; }
.access-panel-body { min-height: 0; }
.bd-footer {
  position: sticky;
  bottom: 0;
  background: var(--panel);
  flex-wrap: wrap;
  gap: 18px;
  border-radius: 0 0 14px 14px;
}
.bd-foot-fig { display: flex; flex-direction: column; line-height: 1.25; }
.bd-foot-label { font-size: 0.72rem; color: var(--muted); }
.bd-foot-fig strong { font-size: 0.95rem; }

.bd-net-label { font-size: 0.78rem; color: var(--muted); }
.bd-net { font-size: 1.05rem; font-weight: 700; color: var(--accent2-text); }

/* --- Payslip -------------------------------------------------------------
   On screen it reads as a document sitting on the app's dark background; in
   print it becomes an actual document. The two are one template, so the
   print rules below are overrides rather than a second layout. */

.payslip-toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
}
.payslip-toolbar-spacer { flex: 1; }
/* The per-shift breakdown's own header row, and the mark on a line
   that was paid at something other than the standing rate. */
.payslip-subhead td { color: var(--muted); font-size: 0.78rem; }
.payslip-custom { color: var(--warn, var(--accent2-text)); font-weight: 600; }

.payslip {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 26px 30px;
  margin: 0 auto 18px;
  max-width: 820px;
}

.payslip-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 20px;
  padding-bottom: 14px;
  border-bottom: 2px solid var(--border);
}
.payslip-company { font-size: 1.15rem; font-weight: 700; }
.payslip-doc {
  font-size: 0.78rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 2px;
}
.payslip-period { text-align: right; }
.payslip-period-value { font-weight: 600; font-size: 0.92rem; }

.payslip-label {
  font-size: 0.68rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 600;
}

.payslip-who {
  display: flex;
  justify-content: space-between;
  gap: 24px;
  padding: 14px 0;
  border-bottom: 1px solid var(--border);
}
.payslip-name { font-size: 1.02rem; font-weight: 700; margin-top: 2px; }
.payslip-role { font-size: 0.82rem; color: var(--muted); }
.payslip-figure { font-size: 1.02rem; font-weight: 700; margin-top: 2px; }

.payslip-table { width: 100%; margin-top: 14px; font-size: 0.9rem; }
.payslip-table td, .payslip-table th { padding: 5px 0; text-align: left; }
.payslip-table .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.payslip-indent { padding-left: 16px !important; }
.payslip-none { color: var(--muted); font-style: italic; }

.payslip-group th {
  padding-top: 12px !important;
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.payslip-gross th, .payslip-gross td { border-bottom: 1px solid var(--border); font-weight: 700; }
.payslip-subtotal th, .payslip-subtotal td {
  border-top: 1px solid var(--border);
  padding-top: 8px !important;
  font-weight: 600;
}
.payslip-net th, .payslip-net td {
  border-top: 2px solid var(--border);
  border-bottom: 3px double var(--border);
  padding: 9px 0 !important;
  font-size: 1.02rem;
  font-weight: 700;
}

.payslip-balances { margin-top: 18px; }
.payslip-balance-table { width: 100%; margin-top: 6px; font-size: 0.84rem; }
.payslip-balance-table th, .payslip-balance-table td { padding: 4px 0; text-align: left; }
.payslip-balance-table .num { text-align: right; font-variant-numeric: tabular-nums; }

.payslip-sign { margin-top: 34px; }
.payslip-sign-line { max-width: 300px; }
.payslip-rule { border-bottom: 1px solid var(--text); height: 30px; }
.payslip-sign-note { font-size: 0.7rem; color: var(--muted); }

/* ==========================================================================
   Print
   ==========================================================================
   White paper, black ink, nothing that isn't the document. Backgrounds and
   borders are restated rather than inherited because the app's palette is
   built for a dark screen and every one of its tokens is wrong on paper. */
@media print {
  @page { size: A4 portrait; margin: 14mm; }

  html, body {
    background: #fff !important;
    color: #000 !important;
  }
  /* The app's chrome, and anything a page marks as screen-only. */
  .topbar, .subnav, .no-print, .flash,
  .table-expand-bar, .cd-overlay, .access-overlay, .confirm-modal-overlay {
    display: none !important;
  }
  .container { max-width: none; margin: 0; padding: 0; }

  .payslip {
    background: #fff !important;
    color: #000 !important;
    border: none;
    border-radius: 0;
    padding: 0;
    margin: 0;
    max-width: none;
  }

  /* The app's table rules paint their own dark fills and light text, which
     survive an ancestor being made white. Every cell has to be reset, not
     just the page around them - this is the difference between a payslip
     that prints and one that comes out as a black rectangle. */
  .payslip table,
  .payslip thead,
  .payslip tbody,
  .payslip tr,
  .payslip th,
  .payslip td,
  .payslip section,
  .payslip header,
  .payslip footer,
  .payslip div,
  .payslip span,
  .payslip p {
    background: transparent !important;
    background-color: transparent !important;
    color: #000 !important;
    box-shadow: none !important;
  }
  .payslip th, .payslip td { border-color: #000 !important; }
  /* Every slip but the last starts a new sheet, and none may be split
     across two. */
  .payslip-break { page-break-after: always; }
  .payslip, .payslip-table, .payslip-balances, .payslip-sign {
    page-break-inside: avoid;
  }

  .payslip-head { border-bottom: 2px solid #000; }
  .payslip-who { border-bottom: 1px solid #000; }
  .payslip-gross th, .payslip-gross td { border-bottom: 1px solid #000; }
  .payslip-subtotal th, .payslip-subtotal td { border-top: 1px solid #000; }
  .payslip-net th, .payslip-net td {
    border-top: 2px solid #000;
    border-bottom: 3px double #000;
  }
  .payslip-rule { border-bottom: 1px solid #000; }
  .payslip-label, .payslip-role, .payslip-none,
  .payslip-group th, .payslip-sign-note {
    color: #333 !important;
  }
}

/* ==========================================================================
   Pointer cursor, app-wide
   ==========================================================================
   Last block in the file on purpose: it is a sweep, and it should lose to
   nothing except the disabled/inert rules that follow it here.

   The rule is "if pressing it does something, it says so". [onclick] does
   most of the work - this app wires handlers inline throughout, so that one
   attribute selector covers every card, row, tab and icon button that would
   otherwise need naming individually - and the class list below covers the
   handful of clickables whose listener is attached from JS instead. */
a[href],
button,
summary,
select,
label[for],
[role="button"],
[role="tab"],
[onclick],
[onmousedown],
input[type="checkbox"],
input[type="radio"],
input[type="submit"],
input[type="button"],
input[type="reset"],
input[type="file"],
input[type="color"],
input[type="range"] {
  cursor: pointer;
}

/* Custom-styled clickables whose handler is bound in JS (so no [onclick]
   attribute exists to match on). */
.card-link,
.clickable-row,
.nav-arrow,
.toggle-btn,
.sortable-th,
.pnp-nav,
.pnp-cell,
.pnm-cell,
.pnp-today-btn,
.record-meta-toggle,
.table-expand-btn,
.table-expand-close,
.access-close,
.btn-primary,
.btn-quiet,
.btn-danger,
.wp-shift-btn,
.wp-now-label,
.confirm-modal-close,
.glance-card-clickable,
.hg-tile,
.hg-bed,
.hg-swatch-picked,
.upload-dropzone,
.ar-summary.clickable {
  cursor: pointer;
}

/* ...and back to default/blocked wherever pressing it does nothing. These
   come last so they beat every pointer rule above at equal specificity. */
:disabled,
[disabled],
[aria-disabled="true"],
.is-disabled,
.pnp-disabled,
.pnm-disabled {
  cursor: not-allowed;
}
.hg-inert,
.pnp-empty,
.table-expand-placeholder {
  cursor: default;
}
.hg-tile.hg-locked,
.hg-bed.hg-locked,
.hg-tile.out_of_service,
.hg-bed.out_of_service {
  cursor: not-allowed;
}

