:root {
  /* Same off-white palette the mobile app uses before an employee clocks in
     (see ClockInDock/theme/colors.js lightColors) — applies to the body and
     content panel. The topbar keeps the original black/lime palette via its
     own local overrides below, since that's the "header" this was asked to
     leave alone. */
  --bg: #f5f5f0;
  --surface: #ffffff;
  --surface-alt: #ececE5;
  --border: #dedeD4;
  --text: #16180f;
  --text-secondary: #5c5f54;
  --text-muted: #8b8e82;
  --accent: #5fcc00;
  --accent-soft: rgba(95, 204, 0, 0.14);
  /* Text placed ON the accent fill. White on this lime measures 2.07:1 —
     below every legibility threshold, and the reason the green buttons
     were hard to read. Near-black on the same lime measures 8.64:1, so the
     brand colour is kept and only the text on it changes. */
  --on-accent: #16180f;
  /* The accent used as TEXT on a light surface. The brand lime manages
     2.07:1 on white — technically visible, genuinely hard to read at the
     11-13px this panel uses. This darker green reads at 5.3:1 while still
     being unmistakably the same brand colour. Borders and fills keep the
     bright lime; only text switches. */
  --accent-ink: #367a00;
  --danger: #d6362a;
  /* A deeper red, used when white text sits ON the colour rather than next
     to it. #d6362a is tuned to be readable AS text on a light background;
     as a fill it only reaches ~4:1 against white, which is why the
     "Delete forever" button was hard to read. This clears 5.5:1. */
  --danger-strong: #a8271c;
  --danger-soft: rgba(214, 54, 42, 0.10);
  /* A distinct "send / broadcast" colour for the one deliberate button that
     notifies staff (Save & send on the rota). Deliberately NOT the lime accent
     — the accent is everywhere, so reusing it wouldn't make this action stand
     out — and not the red danger. This blue reads as a confident, positive
     "send"; white on it clears 5.2:1. */
  --send: #2563eb;
  --send-strong: #1d4ed8;
  --warning: #b4780a;
  --warning-soft: rgba(180, 120, 10, 0.12);
  /* Corner rounding. Bumped up a notch across the board — the panel reads
     softer without losing the density an admin tool needs. */
  --radius-sm: 10px;
  --radius-md: 16px;
  --radius-lg: 22px;

  /* Shared motion vocabulary — every transition in this file should pull
     from here instead of hardcoding its own duration/curve, so the panel
     stays cohesive as more of it gets animated. Mirrors
     ClockInDock/constants/motion.js on the mobile side. */
  --duration-fast: 150ms; /* hover/press state changes on controls */
  --duration-content: 120ms; /* tab-switch content crossfade */
  --duration-accordion: 220ms; /* expand/collapse reveals */
  --duration-modal: 200ms; /* overlay/dialog entrance & exit */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1); /* entering/exiting content */
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1); /* on-screen movement, e.g. the disclosure arrow */
}

/* Reduced motion means fewer/gentler animations, not zero — this collapses
   every transition and animation to effectively instant rather than
   removing the state changes themselves, so nothing relies on motion to be
   understood. Equivalent to ClockInDock/hooks/useReducedMotion.js on the
   mobile side. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

* {
  box-sizing: border-box;
}

/* Every interactive element in the dashboard (tabs, buttons, inputs,
   selects, links) used to snap instantly between states — hover, active,
   focus all cut with zero transition. This is a single global rule rather
   than one added to every button/tab/input selector individually, so every
   control gets the same easing and nothing gets missed as new ones are
   added. 150ms ease-out reads as immediate but not abrupt — this is a
   dense admin dashboard clicked constantly, not a marketing site, so
   nothing here is slow enough to ever feel like it's in the way. */
button,
select,
input,
a,
.tab,
.icon-btn {
  transition: background-color var(--duration-fast) ease-out,
    border-color var(--duration-fast) ease-out,
    color var(--duration-fast) ease-out, opacity var(--duration-fast) ease-out,
    transform var(--duration-fast) ease-out;
}

/* Press feedback: a near-imperceptible dip while the mouse button is down.
   Deliberately at the bottom of the perceptibility range (0.98, 150ms) —
   this dashboard's buttons are clicked constantly, so the press should
   register as tactile confirmation without ever reading as "an animation".
   Buttons only; inputs/selects/links don't press. */
button:active {
  transform: scale(0.98);
}

/* Keyboard navigation previously had NO visible focus anywhere — tabbing
   through the dashboard was flying blind. :focus-visible (not :focus) so
   the ring appears for keyboard users only and never flashes on mouse
   clicks. The accent ring works on both the light content palette and the
   dark topbar/login overrides, since --accent is re-declared in each. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* --- Base control look ---------------------------------------------------
   Written with element selectors (specificity 0,0,1) on purpose: every
   component rule further down this file still wins, so this changes nothing
   that was already styled. What it catches is everything that was NEVER
   styled — the inputs and selects inside table rows, which fell through to
   the browser's defaults and so appeared with hard square corners next to
   the rounded fields in the forms. That inconsistency was the whole
   complaint, and it came from absence of styling rather than from a rule
   worth overriding. */
input:not([type="checkbox"]):not([type="radio"]),
select,
textarea {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: var(--radius-sm);
  padding: 8px 12px;
  font-size: 13px;
  font-family: inherit;
}

input:not([type="checkbox"]):not([type="radio"]):hover,
select:hover,
textarea:hover {
  border-color: var(--text-muted);
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

.app {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* The `hidden` attribute is only `display: none` in the browser's default
   stylesheet, which authored CSS with an explicit `display` on the same
   element (like .app and .login-view below) silently overrides — so toggling
   `.hidden = true/false` in app.js did nothing visually without these. */
.app[hidden] {
  display: none;
}

.topbar {
  /* Keeps the original black/lime header look regardless of the light
     palette now set on :root — these locally override the same custom
     property names, and everything inside .topbar (tabs, buttons, brand)
     inherits these instead of the root ones. */
  --bg: #0a0a0a;
  --surface: #161616;
  --surface-alt: #1f1f1f;
  --border: rgba(255, 255, 255, 0.08);
  --text: #ffffff;
  --text-secondary: #9a9a9a;
  --text-muted: #6b6b6b;
  --accent: #7bfc0c;
  --accent-soft: rgba(123, 252, 12, 0.12);
  --on-accent: #0a0a0a;
  --accent-ink: var(--accent); /* lime on black is already ~14:1 */
  --danger: #ff4d4d;
  /* Brighter on black so the send button keeps its presence in dark mode. */
  --send: #5b8cff;
  --send-strong: #4f7dff;
  --warning: #ffc940;
  --warning-soft: rgba(255, 201, 64, 0.14);
  /* Re-assert color explicitly — inherited `color` is a resolved value, not
     a live var() reference, so without this, children with no color of
     their own (like .brand-name) would keep inheriting the light theme's
     dark text from body instead of picking up the override above. */
  color: var(--text);

  display: flex;
  align-items: center;
  gap: 24px;
  padding: 16px 28px;
  border-bottom: 1px solid var(--border);
  background: #000000;
  position: sticky;
  top: 0;
  z-index: 10;
  flex-wrap: wrap;
}

.brand {
  display: flex;
  align-items: center;
  gap: 12px;
}

.brand-mark {
  width: 38px;
  height: 38px;
  border-radius: 10px;
  background: var(--accent-soft);
  border: 1.5px solid var(--accent);
  color: var(--accent-ink);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  font-size: 16px;
}

.brand-name {
  font-weight: 700;
  font-size: 16px;
}

.brand-name span {
  color: var(--accent-ink);
}

.brand-sub {
  font-size: 12px;
  color: var(--text-secondary);
}

.tabs {
  display: flex;
  gap: 6px;
  flex: 1;
}

.tab {
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-secondary);
  padding: 9px 16px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.tab:hover {
  color: var(--text);
}

.tab.active {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent-ink);
}

/* Sub-tab toggle within a tab's own content (e.g. Timesheet's
   History / Currently active switch) — same pill look as the main nav,
   reused at a smaller scale so it doesn't compete with it visually. */
.sub-tabs {
  display: flex;
  gap: 6px;
  margin-bottom: 16px;
}

.sub-tabs .tab {
  border: 1px solid var(--border);
  padding: 7px 14px;
}

.refresh {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-secondary);
  padding: 9px 14px;
  border-radius: 999px;
  font-size: 13px;
  cursor: pointer;
}

.refresh:hover {
  color: var(--text);
  border-color: var(--accent);
}

.content {
  flex: 1;
  padding: 28px;
  max-width: 1080px;
  width: 100%;
  margin: 0 auto;
  /* Paired with the opacity toggle in renderTab() (app.js) — switching tabs
     used to be a hard instant swap since it's a raw innerHTML replace, not
     a component re-render. This turns that into a quick crossfade instead. */
  transition: opacity var(--duration-content) var(--ease-out);
}

.content.content-loading {
  opacity: 0;
}

.section-title {
  font-size: 20px;
  font-weight: 700;
  margin: 0 0 20px;
}

.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 14px;
  margin-bottom: 28px;
}

.stat-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 18px;
}

.stat-value {
  font-size: 26px;
  font-weight: 700;
}

.stat-label {
  color: var(--text-muted);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-top: 4px;
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 18px;
  margin-bottom: 12px;
  /* A whisper of a shadow — enough to lift the card off the off-white page
     so the rounded edge reads as a surface rather than an outline. Any
     heavier and a dense admin screen starts to look cluttered. */
  box-shadow: 0 1px 2px rgba(22, 24, 15, 0.04), 0 4px 12px rgba(22, 24, 15, 0.03);
}

.card-row {
  display: flex;
  align-items: center;
  gap: 12px;
}

.filters {
  display: flex;
  gap: 10px;
  margin-bottom: 18px;
  flex-wrap: wrap;
}

.filters select,
.filters input {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: var(--radius-sm);
  padding: 8px 12px;
  font-size: 13px;
}

.filters button {
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  border-radius: var(--radius-sm);
  padding: 8px 16px;
  font-weight: 700;
  font-size: 13px;
  cursor: pointer;
}

.inline-filter {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text-secondary);
}

.inline-filter input {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: var(--radius-sm);
  padding: 7px 10px;
  font-size: 13px;
}

/* Wraps every <table> in the dashboard. Previously the table itself carried
   border-radius + overflow:hidden, which on a narrow viewport (or a column
   with an unusually wide value) didn't make room for the extra columns —
   it clipped them, so the last column could end up silently cut off mid-
   character while an earlier column wrapped its text onto a second line for
   no reason a person looking at it could tell. Moving the rounded-corner
   framing to this wrapper and letting IT scroll means every table now
   always renders each column at its natural, un-clipped width; on a screen
   too narrow to show them all, the fix is a horizontal scroll rather than a
   silent squeeze. */
.table-scroll {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  -webkit-overflow-scrolling: touch;
  background: var(--surface);
  box-shadow: 0 1px 2px rgba(22, 24, 15, 0.04), 0 4px 12px rgba(22, 24, 15, 0.03);
}

table {
  width: 100%;
  min-width: 100%;
  border-collapse: collapse;
  background: var(--surface);
}

thead th {
  text-align: left;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--text-muted);
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

tbody td {
  padding: 14px 16px;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
  /* Column headers are short, known-fixed labels ("Reported", "Status",
     "Duration"...) so their values read best as one unbroken line — a
     timestamp or a pill badge wrapping mid-value is what looked broken
     before. Free-text fields (a note, a reason) opt back into wrapping
     explicitly with .cell-wrap, since those genuinely need it. */
  white-space: nowrap;
}

tbody td.cell-wrap {
  white-space: normal;
}

tbody tr:last-child td {
  border-bottom: none;
}

/* The trailing actions column was set flush against the table's rounded
   right edge — buttons visually touched (or clipped into) the border. Give
   the last column the same breathing room the card's own padding implies,
   and keep an action set on one line rather than kissing the edge. */
thead th:last-child,
tbody td:last-child {
  padding-right: 24px;
}
tbody td:last-child .row-actions {
  justify-content: flex-end;
}

/* Four actions don't fit on one line without shoving the table past its
   own edge — .wrap lets an action set fold into a tidy right-aligned
   2-by-2 stack instead of one long strip that clips at the border. */
.row-actions.wrap {
  flex-wrap: wrap;
  max-width: 240px;
  row-gap: 6px;
}

.pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.3px;
  background: var(--surface-alt);
  color: var(--text-secondary);
}

.pill.open {
  background: var(--warning-soft);
  color: var(--warning);
}

.pill.resolved {
  background: var(--accent-soft);
  color: var(--accent-ink);
}

.resolve-btn {
  border: 1px solid var(--accent);
  background: transparent;
  color: var(--accent-ink);
  border-radius: 999px;
  padding: 6px 12px;
  font-size: 11px;
  font-weight: 700;
  cursor: pointer;
}

.resolve-btn:hover {
  background: var(--accent-soft);
}

/* Flagged incidents: each flag row expands to show the clock events that
   were counted toward it. The whole row is the click target (with the
   chevron as the visual cue), because a tiny disclosure triangle alone is
   easy to miss in a dense table. */
.flag-row {
  cursor: pointer;
}

.flag-row:hover td {
  background: var(--surface-alt);
}

.flag-chevron {
  width: 24px;
  color: var(--text-muted);
  font-size: 12px;
  user-select: none;
}

.flag-detail-cell {
  background: var(--surface-alt);
  padding: 12px 16px 14px;
}

/* The nested events table: visually subordinate to the outer table —
   smaller text, no outer border, tighter cells. */
.flag-events {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}

.flag-events th {
  text-align: left;
  color: var(--text-muted);
  font-weight: 600;
  padding: 4px 10px;
  border-bottom: 1px solid var(--border);
}

.flag-events td {
  padding: 6px 10px;
  border-bottom: 1px solid var(--border);
  background: transparent;
}

.flag-events tr:last-child td {
  border-bottom: none;
}

/* A table cell holding more than one action button (Reopen + Clear, Approve
   + Deny). Without this they wrap onto separate lines the moment the column
   gets tight — flex + wrap keeps them on one row when there's room and
   drops to a tidy two-row stack instead of an uneven one when there isn't. */
.row-actions {
  /* inline-flex so this sizes like ordinary inline content for the table's
     column-width measurement, rather than stretching to fill the td the
     way a block-level flex container would.
     nowrap is deliberate, not the default-and-forgot: flex-wrap:wrap here
     was the actual cause of the two buttons stacking — a *wrappable* flex
     row's min-content width is only its single widest child, so the
     table's shrink-to-fit column sizing measured this as one button wide
     and wrapped the second one underneath, even with plenty of space
     elsewhere in the row that the column was never given. nowrap makes the
     browser measure (and the column grow to) the two buttons' full combined
     width instead; the .table-scroll wrapper on every table is what handles
     the case where a whole row genuinely doesn't fit. */
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-wrap: nowrap;
}

/* icon-btn defaults to the small-radius "utility" shape, which reads oddly
   next to a pill-shaped .resolve-btn in the same row (see .row-actions) —
   this variant matches the pill so a paired action set reads as one
   family of buttons rather than two different styles that happen to be
   next to each other. */
.icon-btn.pill {
  border-radius: 999px;
}

/* The collapsed "Adjust the exact location manually" section on the Venues
   form — coordinates are what the geofence actually runs on, but making an
   admin look at raw lat/lng numbers to add a venue is exactly the kind of
   thing that reads as "confusing" to someone who just wants to type an
   address. This keeps them reachable for the rare correction without
   putting them in the way of the normal flow. */
.venue-advanced {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}

.venue-advanced summary {
  cursor: pointer;
  color: var(--text-secondary);
  font-size: 12px;
  font-weight: 600;
  user-select: none;
}

.venue-advanced summary:hover {
  color: var(--text);
}

.venue-advanced .form-row {
  margin-top: 10px;
}

/* Account settings overlay (change password / add admin).
   Was the one surface left in the panel that still snapped in and out with
   zero transition. Entrance/exit are pure CSS against the existing
   [hidden] toggle in app.js — no JS change: `transition: display ...
   allow-discrete` keeps the overlay rendered while the fade runs on close,
   and @starting-style provides the from-state on open. Both are
   no-ops on browsers without support, which degrade to today's instant
   snap rather than breaking. The backdrop fades in place (it has no
   spatial story to tell); the card adds a 0.97→1 settle so it reads as
   arriving, not materializing. Modals stay centered — no origin trickery. */
.account-modal {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 8vh;
  z-index: 50;
  transition: opacity var(--duration-modal) var(--ease-out),
    display var(--duration-modal) allow-discrete;
}
.account-modal[hidden] {
  display: none;
  opacity: 0;
}
@starting-style {
  .account-modal:not([hidden]) {
    opacity: 0;
  }
}
.account-modal-card {
  transition: transform var(--duration-modal) var(--ease-out);
}
.account-modal[hidden] .account-modal-card {
  transform: scale(0.97);
}
@starting-style {
  .account-modal:not([hidden]) .account-modal-card {
    transform: scale(0.97);
  }
}
.account-modal-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 22px;
  width: min(420px, 92vw);
  max-height: 84vh;
  overflow-y: auto;
}
.account-modal-card h4 {
  color: var(--text-secondary);
  font-weight: 600;
}

/* --- Venue address combobox ---------------------------------------------
   The suggestion list floats over the form rather than sitting in the flow,
   so the fields below it don't jump down and back on every keystroke. */
.address-combo {
  position: relative;
}

.address-combo-caret {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-muted);
  pointer-events: none;
  display: flex;
}

.address-combo input {
  padding-right: 34px; /* room for the caret */
}

.address-combo-list {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 40;
  max-height: 320px;
  overflow-y: auto;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md, 10px);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
}

.address-combo-list[hidden] {
  display: none;
}

.address-combo-empty {
  padding: 10px;
  font-size: 12.5px;
  color: var(--text-muted);
}

.geocode-result {
  display: block;
  width: 100%;
  text-align: left;
  padding: 9px 10px;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--text);
  font-size: 13px;
  cursor: pointer;
}

/* Hover and keyboard selection must look identical — someone arrowing down
   the list should see exactly what a mouse user sees. */
.geocode-result:hover,
.geocode-result.is-active {
  background: var(--surface-alt);
}

.geocode-primary {
  display: block;
  font-weight: 600;
}

.geocode-secondary {
  display: block;
  margin-top: 2px;
  font-size: 12px;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.address-country-note {
  font-size: 12px;
  color: var(--text-muted);
  margin: 6px 0 10px;
}

/* "This page is out of date." Bottom-centred so it can't cover the nav or
   a modal's action buttons, and it never steals focus — the operator
   decides when to reload. */
.version-banner {
  position: fixed;
  left: 50%;
  bottom: 20px;
  transform: translateX(-50%);
  z-index: 200;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 16px;
  border-radius: 12px;
  background: var(--surface);
  border: 1px solid var(--accent);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.22);
  font-size: 13px;
  color: var(--text);
  max-width: min(560px, calc(100vw - 40px));
}

.version-banner button {
  border: none;
  border-radius: 8px;
  padding: 7px 14px;
  background: var(--accent);
  color: #10240a;
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
  white-space: nowrap;
}

/* The live address dropdown. Floats over the form under the search box —
   the same anatomy as the results list, plus positioning. */
.suggest-dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 30;
  margin-top: 4px;
  padding: 4px;
  background: var(--surface);
  border: 1px solid var(--card-border);
  border-radius: 10px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.18);
  max-height: 280px;
  overflow-y: auto;
}

.suggest-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 9px 10px;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--text);
  font-size: 13px;
  cursor: pointer;
}

/* Hover and keyboard selection must look identical — someone arrowing down
   the list should see exactly what a mouse user sees. */
.suggest-item:hover,
.suggest-item.active {
  background: var(--surface-alt);
}

/* Attendance-record stat cards that open their detail list.
   The first version used a 1px lift and a soft shadow — so subtle that the
   cards read as static text and nobody knew they could be clicked. These
   are the entry point to somebody's whole attendance history, so the
   affordance is now unmistakable: the card lights up green under the
   cursor. */
.stat-card-link {
  cursor: pointer;
  border: 1px solid var(--card-border, rgba(128, 128, 128, 0.25));
  transition: transform 160ms var(--ease-out, ease), box-shadow 160ms ease, border-color 160ms ease,
    background-color 160ms ease;
}

.stat-card-link:hover,
.stat-card-link:focus-visible {
  transform: translateY(-2px);
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, var(--surface));
  /* Two shadows: a soft green halo, and a normal drop shadow under it so
     the card still reads as lifted rather than just tinted. */
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 28%, transparent),
    0 6px 18px rgba(0, 0, 0, 0.12);
  outline: none;
}

.stat-card-link:hover .stat-value,
.stat-card-link:focus-visible .stat-value {
  color: var(--accent-ink, var(--accent));
}

.stat-card-link:active {
  transform: translateY(0);
}

/* The section a clicked card lands on announces itself once, then settles. */
@keyframes rec-flash-fade {
  0% { background: color-mix(in srgb, var(--accent) 18%, transparent); }
  100% { background: transparent; }
}

.rec-flash {
  animation: rec-flash-fade 1.2s ease-out 1;
  border-radius: 10px;
}

@media (prefers-reduced-motion: reduce) {
  .stat-card-link,
  .rec-flash {
    transition: none;
    animation: none;
  }
}

.address-country-note[hidden] {
  display: none;
}

.address-country-ok {
  color: var(--accent-ink);
}

.link-btn {
  background: none;
  border: none;
  padding: 0 0 0 6px;
  color: var(--accent-ink);
  font-size: 12px;
  cursor: pointer;
  text-decoration: underline;
}

/* --- Payroll weeks -------------------------------------------------------
   One collapsible card per week. The header carries the summary a manager
   scans for (who, how long, how much) so most questions are answered
   without opening anything. */
.payroll-week {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  margin-bottom: 10px;
  overflow: hidden;
  box-shadow: 0 1px 2px rgba(22, 24, 15, 0.04), 0 4px 12px rgba(22, 24, 15, 0.03);
}

.payroll-week-head {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 16px 20px;
  background: transparent;
  border: none;
  cursor: pointer;
  text-align: left;
  color: var(--text);
  font-size: 14px;
}

.payroll-week-head:hover {
  background: var(--surface-alt);
}

.payroll-chevron {
  color: var(--text-muted);
  font-size: 12px;
  transition: transform var(--duration-accordion) var(--ease-in-out);
}

.payroll-week.is-open .payroll-chevron {
  transform: rotate(90deg);
}

.payroll-week-name {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-weight: 700;
  flex: 1;
  min-width: 0;
}

.payroll-week-dates {
  font-weight: 400;
  color: var(--text-secondary);
}

.payroll-week-figures {
  display: flex;
  align-items: center;
  gap: 18px;
  color: var(--text-secondary);
  font-size: 13px;
  white-space: nowrap;
}

.payroll-week-figures strong {
  color: var(--text);
  font-size: 15px;
}

.payroll-week-body {
  padding: 0 20px 18px;
  /* When a week (or, in the Employees tab, a venue group) is opened, the
     content settles in rather than snapping. The chevron beside it already
     rotates on a transition; this brings the body in step so the whole
     disclosure moves as one thing. `display ... allow-discrete` keeps the
     row rendered for the fade, and @starting-style gives the from-state on
     open — both no-ops on older browsers, which fall back to the instant
     reveal. Only opacity + a short translate move (GPU-cheap); no height
     animation, so a tall table can't jank on expand. */
  transition: opacity var(--duration-accordion) var(--ease-out),
    transform var(--duration-accordion) var(--ease-out),
    display var(--duration-accordion) allow-discrete;
}

.payroll-week-body[hidden] {
  display: none;
  opacity: 0;
  transform: translateY(-4px);
}

@starting-style {
  .payroll-week-body:not([hidden]) {
    opacity: 0;
    transform: translateY(-4px);
  }
}

/* A paid week that no longer matches what was paid. Amber rather than red:
   it needs looking at, it isn't necessarily wrong. */
.payroll-warning {
  background: var(--warning-soft);
  color: var(--warning);
  border-radius: var(--radius-sm);
  padding: 10px 14px;
  font-size: 12.5px;
  line-height: 1.5;
  margin-bottom: 12px;
}

.pill.done {
  background: var(--accent-soft);
  color: var(--accent-ink);
}

tfoot td {
  border-top: 1px solid var(--border);
  padding: 12px 16px;
  font-size: 13px;
}

/* --- Venue assignment picker --------------------------------------------
   A checkbox list in a modal rather than a multi-select: "which of these
   sites does this person cover" is a list question, and a multi-select
   <select> (ctrl-click to add, click to wipe your selection) is one of the
   easiest controls on the web to use wrong. */
.venue-picker {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(10, 10, 10, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  /* Entrance only (the element is removed outright on close, so there's no
     exit to animate). Same language as the account modal: the backdrop fades
     in place — it has no spatial story — and the card settles 0.97→1 so it
     reads as arriving, not materialising. @starting-style is a no-op where
     unsupported, degrading to today's instant appearance rather than
     breaking. Modals stay centered; no transform-origin trickery. */
  transition: opacity var(--duration-modal) var(--ease-out);
}

@starting-style {
  .venue-picker {
    opacity: 0;
  }
}

.venue-picker-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 22px;
  width: 100%;
  max-width: 460px;
  max-height: 80vh;
  overflow-y: auto;
  box-shadow: 0 20px 50px rgba(22, 24, 15, 0.22);
  transition: transform var(--duration-modal) var(--ease-out);
}

@starting-style {
  .venue-picker-card {
    transform: scale(0.97);
  }
}

.venue-picker-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.venue-picker-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: 13px;
}

.venue-picker-item:hover {
  background: var(--surface-alt);
}

.venue-picker-item input {
  width: 16px;
  height: 16px;
  accent-color: var(--accent);
  flex: none;
}

.timesheet-venue {
  padding: 20px;
}

.timesheet-venue-title {
  margin: 0 0 16px;
  font-size: 15px;
  font-weight: 700;
}

.timesheet-venue-id {
  color: var(--text-muted);
  font-weight: 400;
  font-size: 12px;
}

.timesheet-employee {
  margin-bottom: 18px;
}

.timesheet-employee:last-child {
  margin-bottom: 0;
}

.timesheet-employee-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 8px;
  font-size: 13px;
}

.timesheet-employee-total {
  color: var(--text-muted);
  font-size: 11px;
}

.timesheet-employee table {
  border-radius: var(--radius-sm);
}

/* The disclosure arrow (below) already animated on open/close; this table
   used to just teleport open underneath it — half-animated, which reads as
   more broken than not animating at all. `display: grid` here overrides
   the browser's default `display: none` on non-<summary> <details>
   children (an author rule always wins over that UA default regardless of
   selector specificity), so the row stays present and its height can be
   transitioned via the grid-template-rows 0fr/1fr trick instead of
   snapping. The inner wrapper needs its own `overflow: hidden` +
   `min-height: 0` for the 0fr collapse to actually clip the content. */
.timesheet-employee-body {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  transition: grid-template-rows var(--duration-accordion) var(--ease-out),
    opacity var(--duration-accordion) var(--ease-out);
}

.timesheet-employee[open] .timesheet-employee-body {
  grid-template-rows: 1fr;
  opacity: 1;
}

.timesheet-employee-body-inner {
  overflow: hidden;
  min-height: 0;
}

.timesheet-employee-body-inner table {
  margin-top: 10px;
}

.timesheet-employee-summary {
  display: flex;
  align-items: center;
  gap: 18px;
  cursor: pointer;
  list-style: none;
  font-size: 13px;
  padding: 4px 0;
}

.timesheet-employee-summary::-webkit-details-marker {
  display: none;
}

.timesheet-employee-name {
  flex: 1;
  font-weight: 700;
}

.timesheet-employee-toggle {
  display: flex;
  align-items: center;
  gap: 4px;
  color: var(--accent-ink);
  font-size: 11px;
  font-weight: 600;
}

/* Only the arrow glyph rotates on open — the label text used to be rotated
   along with it, which flipped "Show hours" upside down. */
.toggle-text-open {
  display: none;
}

.timesheet-employee[open] .toggle-text-closed {
  display: none;
}

.timesheet-employee[open] .toggle-text-open {
  display: inline;
}

.toggle-arrow {
  display: inline-block;
  transition: transform var(--duration-fast) var(--ease-in-out);
}

.timesheet-employee[open] .toggle-arrow {
  transform: rotate(180deg);
}

.timesheet-venue-total {
  display: flex;
  align-items: center;
  gap: 18px;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
  font-weight: 700;
  font-size: 13px;
}

.timesheet-venue-total span:first-child {
  flex: 1;
}

.empty-state {
  text-align: center;
  padding: 60px 20px;
  color: var(--text-muted);
}

.loading {
  color: var(--text-muted);
  padding: 40px;
  text-align: center;
}

.login-view {
  /* Not part of "the panel" this change targets — keeps its original
     black card look via the same local-override trick as .topbar. */
  --bg: #0a0a0a;
  --surface: #161616;
  --surface-alt: #1f1f1f;
  --border: rgba(255, 255, 255, 0.08);
  --text: #ffffff;
  --text-secondary: #9a9a9a;
  --text-muted: #6b6b6b;
  --accent: #7bfc0c;
  --accent-soft: rgba(123, 252, 12, 0.12);
  --on-accent: #0a0a0a;
  --accent-ink: var(--accent); /* lime on black is already ~14:1 */
  --danger: #ff4d4d;
  /* Brighter on black so the send button keeps its presence in dark mode. */
  --send: #5b8cff;
  --send-strong: #4f7dff;
  --warning: #ffc940;
  --warning-soft: rgba(255, 201, 64, 0.14);
  color: var(--text);

  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  padding: 20px;
}

.login-view[hidden] {
  display: none;
}

/* Same UA-default-vs-authored-display trap as .app/.login-view above: the
   login card's explicit display:flex silently defeats the `hidden`
   attribute, which made the Reset password card render permanently beside
   the sign-in card instead of waiting for its toggle. */
.login-card[hidden] {
  display: none;
}

.login-card {
  width: 100%;
  /* Sign-in is the only thing on this screen, so it can afford to be the
     size of the decision it represents. Everything below scales with it. */
  max-width: 460px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 48px 44px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.login-mark {
  width: 60px;
  height: 60px;
  font-size: 25px;
  margin-bottom: 22px;
}

.login-title {
  font-size: 27px;
  font-weight: 700;
  letter-spacing: -0.02em; /* larger type needs tighter tracking to stay tidy */
  margin: 0 0 8px;
}

.login-title span {
  color: var(--accent-ink);
}

.login-sub {
  color: var(--text-secondary);
  font-size: 15px;
  line-height: 1.5;
  margin: 0 0 30px;
}

/* The agreement line and the terms/privacy footer under the sign-in card.
   Quiet by design — present and readable, never competing with the form. */
.login-legal {
  color: var(--text-muted);
  font-size: 12px;
  line-height: 1.6;
  text-align: center;
  margin: 18px 0 0;
}

.login-legal a {
  color: var(--text-secondary);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.login-legal a:hover {
  color: var(--accent);
}

.login-legal-footer {
  margin-top: 22px;
  padding-top: 14px;
  border-top: 1px solid var(--card-border, rgba(128, 128, 128, 0.2));
}

.login-legal-footer span {
  margin: 0 6px;
  opacity: 0.5;
}

.field-label {
  align-self: flex-start;
  font-size: 13px;
  color: var(--text-muted);
  margin-bottom: 8px;
}

.login-card input {
  width: 100%;
  background: var(--surface-alt);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: var(--radius-sm);
  padding: 14px 16px;
  /* 16px is deliberate: iOS Safari zooms the whole page when it focuses an
     input smaller than this, which on a phone looks like the login screen
     jumping. */
  font-size: 16px;
  margin-bottom: 18px;
}

.login-card input:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* Phones: keep the same proportions but stop the padding eating the screen. */
@media (max-width: 520px) {
  .login-card {
    padding: 32px 22px;
  }

  .login-title {
    font-size: 23px;
  }
}

.password-field {
  position: relative;
  width: 100%;
}

.password-field input {
  padding-right: 40px;
}

.password-toggle {
  position: absolute;
  top: 0;
  right: 0;
  height: calc(100% - 16px); /* matches the input's own bottom margin */
  width: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  padding: 0;
}

/* Inline variant for password fields inside a .form-row. Two fights with
   ancestor rules, both explicit: the base .password-field is width:100%
   (right for the login card, wrong here — it shoved the field onto its own
   full-width row), and .form-row button paints every button green (right
   for real actions, absurd for the eye). */
.password-field.inline {
  position: relative;
  display: inline-flex;
  width: auto;
}
.password-field.inline input {
  width: 280px;
  padding-right: 36px;
}
.form-row .password-toggle {
  background: transparent;
  color: var(--text-muted);
  border: none;
  padding: 0;
  width: 36px;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.form-row .password-toggle:hover {
  color: var(--text);
}

/* Money inputs: the £ sits inside the field's left edge (and an optional
   /hr unit after it), so an amount can never be mistaken for a bare
   number. The input keeps its own border; the prefix is absolutely
   positioned so it doesn't affect the value's alignment for the browser's
   native number spinners. */
.money-field {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.money-prefix {
  position: absolute;
  left: 10px;
  color: var(--text-muted);
  font-size: 13px;
  pointer-events: none;
}
/* Make room for the absolutely-positioned £ so it never overlaps the value.
   The two :not() attribute matchers are here purely for specificity: the base
   `input:not([type="checkbox"]):not([type="radio"])` control rule (added when
   the panel's corners were rounded) carries two attribute selectors and was
   quietly winning the padding fight in table cells, pushing the value back
   under the £. Matching that shape and adding the .money-field class puts this
   rule clearly ahead, in every context — form rows AND table cells. */
.money-field input:not([type="checkbox"]):not([type="radio"]) {
  padding-left: 24px;
  /* Wide enough for "1234.56" without the last digit falling off the end.
     Set here rather than inline so a rate in a table cell and a rate in a
     form are the same size — they were 80px and 110px respectively, and the
     narrow one clipped. */
  width: 108px;
  /* Money in a vertical column: tabular figures keep the decimal points
     aligned down the page instead of drifting with each row's digits. */
  font-variant-numeric: tabular-nums;
}

/* The native number spinner is actively harmful on a money field. It steps by
   `step` — a penny — so getting from £12.00 to £12.70 is seventy clicks, and
   in a narrow cell its arrows sit on top of the last digit, which is how
   "12.70" came to render as "12.7". Typing and the arrow keys both still
   work; only the tiny stepper is gone. */
.money-field input::-webkit-outer-spin-button,
.money-field input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  appearance: none;
  margin: 0;
}

.money-field input[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}
.money-suffix {
  color: var(--text-muted);
  font-size: 12px;
}

/* A trailing unit label ("metres", "/hr") next to an input that has no
   leading symbol — so, unlike .money-field, the input keeps its normal left
   padding. */
.suffix-field {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.suffix-field input {
  width: 110px;
}

.suffix-label {
  color: var(--text-muted);
  font-size: 12px;
}

/* A field that failed validation — outlined in red until it's edited. */
.field-invalid {
  border-color: var(--danger) !important;
}
/* The password field's border lives on the input, but the invalid mark may
   land while the eye button is focused — cover the wrapper's input too. */
.password-field.inline input.field-invalid {
  border-color: var(--danger) !important;
}

.password-toggle:hover {
  color: var(--text);
}

.password-toggle[aria-pressed="true"] {
  color: var(--accent-ink);
}

.login-error {
  width: 100%;
  background: var(--warning-soft);
  color: var(--warning);
  border-radius: var(--radius-sm);
  padding: 11px 14px;
  font-size: 13.5px;
  line-height: 1.45;
  margin-bottom: 16px;
  text-align: left;
}

/* Billing lockout: unlike ordinary sign-in mistakes (amber), a suspended
   subscription is a hard stop and reads in red. */
.login-error.billing {
  background: rgba(214, 54, 42, 0.14);
  color: var(--danger);
  font-weight: 600;
}

.login-submit {
  width: 100%;
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  border-radius: var(--radius-sm);
  padding: 15px;
  font-weight: 700;
  font-size: 15.5px;
  cursor: pointer;
  transition: opacity 140ms ease-out, transform 140ms ease-out;
}

.login-submit:hover {
  opacity: 0.9;
}

/* Press feedback: the one moment on this screen worth animating, because
   sign-in can take a second and people otherwise wonder if it registered. */
.login-submit:active {
  transform: scale(0.985);
}

.login-submit:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .login-submit {
    transition-duration: 1ms;
  }

  .login-submit:active {
    transform: none;
  }
}

/* --- Bot check (Cloudflare Turnstile) ------------------------------------
   Hidden until a sign-in fails; see turnstile.js. Reserving no height while
   hidden keeps the card from jumping on first paint. */
.login-turnstile {
  width: 100%;
  margin-bottom: 16px;
  display: flex;
  justify-content: center;
}

.login-turnstile[hidden] {
  display: none;
}

/* --- Labelled form fields ------------------------------------------------
   Placeholder text is not a label: it vanishes the moment someone types,
   and a <select> can't have one at all — which left the role dropdown and
   the pay field with nothing explaining what they decide. */
.field-group {
  padding: 14px 0 4px;
  border-top: 1px solid var(--border);
  margin-top: 4px;
}

.field-group:first-of-type {
  border-top: none;
  padding-top: 0;
  margin-top: 0;
}

.field-group-title {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-secondary);
  margin: 0 0 10px;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin-bottom: 12px;
  flex: 1;
  min-width: 180px;
}

/* Inside a .form-row the fields already provide the bottom spacing. */
.form-row .field {
  margin-bottom: 0;
}

.field-title {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text);
}

.field-hint {
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--text-muted);
  margin: 4px 0 10px;
}

.field-hint strong {
  color: var(--text-secondary);
}

.form-row {
  display: flex;
  gap: 10px;
  margin-bottom: 12px;
  flex-wrap: wrap;
}

/* .field is listed alongside .form-row so a labelled field standing on its
   own (not inside a row) gets the same control styling. */
.form-row input,
.form-row select,
.field input,
.field select {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: var(--radius-sm);
  padding: 8px 12px;
  font-size: 13px;
}

/* Full width only inside a labelled field, where the label above defines the
   column. Deliberately NOT applied to bare .form-row controls — those are
   flex items across the rest of the panel and would each claim a whole row. */
.field > input,
.field > select {
  width: 100%;
}

.form-row button {
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  border-radius: var(--radius-sm);
  padding: 8px 16px;
  font-weight: 700;
  font-size: 13px;
  cursor: pointer;
}

.icon-btn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-secondary);
  border-radius: var(--radius-sm);
  padding: 5px 10px;
  font-size: 12px;
  cursor: pointer;
}

.icon-btn:hover {
  color: var(--text);
  border-color: var(--danger);
}

/* `.form-row button` (the green primary style) is one class + one element,
   which outranks a bare `.icon-btn` — so every secondary button that
   happened to sit inside a form row was silently painted as a primary
   action. Matching that specificity puts the outline style back where it
   belongs; it's also what left "Delete forever" as red text on a green
   fill, since .icon-btn.danger set a colour but no background. */
.form-row .icon-btn {
  background: transparent;
  color: var(--text-secondary);
  border: 1px solid var(--border);
  font-weight: 400;
  padding: 5px 10px;
  font-size: 12px;
}

/* Reserved for truly irreversible actions (e.g. permanently deleting an
   archived employee). Solid fill rather than red-on-white: this is the one
   button in the panel that destroys payroll history, and it should be
   impossible to click by accident or to misread. White on --danger-strong
   clears 5.5:1; the old red-on-green was under 1.5:1. */
.icon-btn.danger,
.form-row .icon-btn.danger {
  background: var(--danger-strong);
  border-color: var(--danger-strong);
  color: #ffffff;
  font-weight: 700;
}

.icon-btn.danger:hover,
.form-row .icon-btn.danger:hover {
  background: #8d1f16;
  border-color: #8d1f16;
  color: #ffffff;
}

/* Save & send: THE deliberate action in the rota bar — it notifies staff — so
   it's the one filled, coloured button among the outlined secondaries, in its
   own "send" blue (see --send). Scoped to .rota-bar so it beats the generic
   `.filters button` accent fill on specificity (0,2,0 vs 0,1,1). */
.rota-bar .btn-release {
  background: var(--send);
  color: #ffffff;
  border: 1px solid var(--send);
  border-radius: var(--radius-sm);
  padding: 8px 16px;
  font-weight: 700;
  font-size: 13px;
  cursor: pointer;
}
.rota-bar .btn-release:hover:not(:disabled) {
  background: var(--send-strong);
  border-color: var(--send-strong);
}
.rota-bar .btn-release:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Clear week: a QUIET destructive. Red-tinted outline, not the solid-red fill
   of .icon-btn.danger — that fill is reserved for permanently deleting payroll
   history, and clearing only cancels not-yet-worked shifts (undoable by
   rebuilding). It should give pause, not alarm. */
.rota-bar .btn-clear {
  background: transparent;
  color: var(--danger);
  border: 1px solid var(--danger);
  border-radius: var(--radius-sm);
  padding: 8px 14px;
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
}
.rota-bar .btn-clear:hover:not(:disabled) {
  background: var(--danger-soft);
}
.rota-bar .btn-clear:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* --- Timetable (the rota) -------------------------------------------------
   A dense scanning surface. The grid itself deliberately doesn't animate:
   this is data people read across, and motion on rows and cells costs
   legibility while buying nothing. The motion budget goes on the two
   moments that need to feel responsive — the press on a shift chip, and
   the modal's arrival (which reuses .account-modal's existing transition,
   so the panel keeps one vocabulary rather than growing a second). */

.rota-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-bottom: 10px;
}

.rota-week-nav {
  display: flex;
  align-items: center;
  gap: 6px;
}

.rota-week-label {
  min-width: 190px;
  text-align: center;
  font-weight: 600;
  font-size: 14px;
  /* Slightly tighter than body copy: it's a heading-weight label, and at
     this size the default tracking reads loose. */
  letter-spacing: -0.01em;
  color: var(--text-primary);
}

.rota-hint {
  margin: -2px 0 14px;
  line-height: 1.55;
}

.rota-summary {
  display: block;
  margin-top: 4px;
  color: var(--text-secondary);
  font-weight: 600;
}

.rota-scroll {
  border-radius: var(--radius-md);
}

.rota-grid {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  table-layout: fixed;
}

.rota-grid th,
.rota-grid td {
  border-bottom: 1px solid var(--border);
  vertical-align: top;
  padding: 8px;
}

.rota-grid thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  /* Translucent rather than opaque, so rows scrolling underneath stay
     faintly visible and the header reads as a floating layer over the
     grid instead of a lid on it. */
  background: color-mix(in srgb, var(--surface) 82%, transparent);
  backdrop-filter: blur(12px) saturate(160%);
  text-align: center;
  font-size: 12px;
  color: var(--text-secondary);
  text-transform: none;
}

.rota-day-name {
  display: block;
  font-weight: 600;
}

.rota-day-num {
  display: block;
  font-size: 17px;
  font-weight: 700;
  color: var(--text-primary);
  /* Larger text wants negative tracking — at 17px the digits read too far
     apart with the default. */
  letter-spacing: -0.02em;
  line-height: 1.15;
}

.rota-grid th.is-today .rota-day-num,
.rota-grid th.is-today .rota-day-name {
  color: var(--accent-ink);
}

.rota-cell.is-today {
  background: color-mix(in srgb, var(--accent) 6%, transparent);
}

.rota-name-col {
  width: 168px;
  text-align: left !important;
  font-weight: 600;
  font-size: 13px;
  color: var(--text-primary);
  background: var(--surface);
  position: sticky;
  left: 0;
  z-index: 1;
}

.rota-grid thead .rota-name-col {
  z-index: 3;
}

.rota-cell {
  min-height: 56px;
}

/* Shift chip. The whole thing is the hit target, and it responds on press
   rather than on release — feedback that waits for mouseup reads as lag. */
.rota-chip {
  display: block;
  width: 100%;
  text-align: left;
  margin-bottom: 4px;
  padding: 7px 9px;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border));
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--accent) 10%, var(--surface));
  color: var(--text-primary);
  cursor: pointer;
  font: inherit;
  font-size: 12px;
  line-height: 1.35;
  transition: transform var(--duration-fast) var(--ease-out),
    background-color var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out);
}

.rota-chip:hover {
  background: color-mix(in srgb, var(--accent) 18%, var(--surface));
}

.rota-chip:active {
  /* Subtle on purpose. These get clicked many times in a rostering session,
     and a deep press animation turns into a drag on a repetitive task. */
  transform: scale(0.975);
}

.rota-chip-time {
  display: block;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.rota-chip-label {
  display: block;
  color: var(--text-secondary);
}

.rota-chip-tag {
  display: inline-block;
  margin-top: 3px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--warning-soft);
  color: var(--warning);
  font-weight: 700;
  font-size: 10px;
}

.rota-chip.is-ot {
  border-color: color-mix(in srgb, var(--warning) 50%, var(--border));
  background: color-mix(in srgb, var(--warning) 9%, var(--surface));
}

.rota-chip-lock {
  float: right;
  font-size: 11px;
  opacity: 0.75;
}

.rota-chip.is-locked {
  border-style: dashed;
}

/* The add affordance stays quiet until the cell is hovered — seven of these
   per row, always visible, would compete with the shifts themselves for
   attention. Keyboard focus reveals it too, so it isn't hover-only. */
.rota-add {
  display: block;
  width: 100%;
  padding: 4px;
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 13px;
  line-height: 1.2;
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out);
}

.rota-cell:hover .rota-add,
.rota-add:focus-visible {
  opacity: 1;
}

.rota-add:hover {
  border-color: var(--accent);
  color: var(--accent-ink);
}

/* Touch and coarse pointers have no hover, so the affordance would never
   appear. Show it permanently there instead. */
@media (hover: none), (pointer: coarse) {
  .rota-add {
    opacity: 1;
  }
}

.rota-time-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.rota-modal-actions {
  display: flex;
  gap: 8px;
  margin-top: 16px;
}

.rota-lock-warning {
  margin: 0 0 14px;
  padding: 11px 13px;
  border: 1px solid color-mix(in srgb, var(--warning) 45%, var(--border));
  border-radius: var(--radius-sm);
  background: var(--warning-soft);
  color: var(--text-primary);
  font-size: 12.5px;
  line-height: 1.5;
}

/* NOTE: .icon-btn.danger is already defined above as a solid-filled button
   (white on --danger-strong), deliberately, because it is the panel's marker
   for destructive actions. Do not redefine it here — an earlier version of
   this block set only `color`, which left dark red text sitting on the dark
   red fill and made the Delete label unreadable. */

/* --- Time fields ----------------------------------------------------------
   Typed, not picked. The native <input type="time"> dropdown is a pair of
   scrolling number columns, which is a mouse trip per entry — and rostering a
   week is dozens of entries. These are plain text fields that understand "9",
   "930" and "9:30" alike.

   Nothing here animates on interaction. This control gets used dozens of
   times in one sitting, and at that frequency motion reads as lag rather than
   craft. The single exception is the rejection flash below, which is a colour
   change rather than movement — it tells you something didn't parse without
   asking anyone's inner ear to join in. */
.time-field {
  /* Tabular figures so 09:00 and 11:45 occupy the same width and a column of
     times stays optically aligned instead of shimmering as it changes. */
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
  text-align: center;
  min-width: 5.5em;
  transition: border-color var(--duration-fast) ease,
    background-color var(--duration-fast) ease;
}

.time-field::placeholder {
  color: var(--text-muted);
  opacity: 0.55;
  letter-spacing: 0.01em;
}

/* Entry we couldn't read. The field has already put back the last value that
   made sense, so this explains the revert rather than announcing a failure. */
.time-field-rejected {
  border-color: var(--danger) !important;
  background: var(--danger-soft) !important;
}

/* Chrome and Safari draw a clock glyph inside time inputs. These are text
   inputs now so it shouldn't appear, but this covers any field that hasn't
   been upgraded yet — during the first paint, or if scripting fails. */
input[type="time"]::-webkit-calendar-picker-indicator {
  display: none;
}
input[type="time"] {
  font-variant-numeric: tabular-nums;
}

/* Rota rules summarised in the Venues table. Chips rather than a sentence so
   two venues can be compared by scanning down the column — the whole reason
   these live per venue is that sites differ, and a difference you have to
   open two dialogs to notice may as well not be visible at all. */
.venue-name {
  font-weight: 600;
  color: var(--text-primary);
}

/* Chips wrap under the venue name. Kept quiet — secondary colour, small
   type — because the venue's name is what someone scans this column for;
   the rules are there to be noticed once, when they differ from the site
   above. */
.venue-rules-line {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 5px;
}

.venue-rule-chip {
  padding: 1px 7px;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text-muted);
  font-size: 10.5px;
  line-height: 1.6;
  white-space: nowrap;
  /* These carry a title attribute with the full sentence, so make it obvious
     that hovering says more. */
  cursor: help;
}

/* Three buttons that must never wrap into each other. */
.row-actions {
  white-space: nowrap;
}

/* Overnight shifts.
   The rota form asks for two times and nothing else, so "22:00 to 02:00" has
   to be readable as "finishes tomorrow" rather than as a mistake. Two places
   say so: a live preview in the form, and a +1 on the grid chip. */
.rota-span-preview {
  margin: 10px 0 4px;
  padding: 9px 11px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-alt, transparent);
  color: var(--text-secondary);
  font-size: 12.5px;
  line-height: 1.6;
}

.rota-span-preview strong {
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}

.rota-span-hours {
  margin-left: 6px;
  color: var(--text-muted);
}

/* Stated, not warned about — a night shift is normal in this industry, so
   this is a label rather than an alarm. */
.rota-span-overnight {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--accent-soft, rgba(95, 204, 0, 0.14));
  color: var(--accent-ink);
  font-size: 11px;
  font-weight: 700;
}

.rota-chip-nextday {
  margin-left: 2px;
  font-size: 9px;
  font-weight: 700;
  color: var(--accent-ink);
  vertical-align: super;
  line-height: 0;
}

/* A shift starting soon. Advisory, not a gate — it can still be changed, so
   this is a quiet marker rather than the padlock, which now means "someone is
   working this right now". */
.rota-chip.is-soon {
  border-color: color-mix(in srgb, var(--warning) 35%, var(--border));
}

/* A shift whose times were changed after it was first rostered. Small and
   grey: it's a fact for later, not something to act on now. */
.rota-chip-moved {
  display: inline-block;
  margin-top: 3px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--surface-alt, transparent);
  border: 1px solid var(--border);
  color: var(--text-muted);
  font-size: 9.5px;
  font-weight: 700;
  cursor: help;
}

.rota-soon-note {
  margin: 0 0 12px;
  padding: 9px 11px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-alt, transparent);
  color: var(--text-secondary);
  font-size: 12.5px;
  line-height: 1.5;
}

/* --- Shift change requests ------------------------------------------------
   Read as a conversation, not a form. The employee's own words are the thing
   being decided on, so they get the room and a quotation mark; the decision
   is two buttons rather than a workflow. */
.request-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
  padding: 16px 18px;
  margin-bottom: 12px;
}

/* Answered ones stay readable but stop competing with the queue. */
.request-card.is-answered {
  opacity: 0.72;
  background: transparent;
}

.request-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.request-shift {
  display: block;
  margin-top: 2px;
  color: var(--text-secondary);
  font-size: 12.5px;
}

/* Their words, set apart. The left rule does the quoting so the text itself
   needs no punctuation added to it. */
.request-message {
  margin: 12px 0 10px;
  padding: 2px 0 2px 14px;
  border-left: 3px solid color-mix(in srgb, var(--accent) 55%, var(--border));
  color: var(--text-primary);
  font-size: 14px;
  line-height: 1.55;
}

.request-note {
  margin: 0 0 10px;
  padding: 8px 11px;
  border-radius: var(--radius-sm);
  background: var(--warning-soft);
  color: var(--warning);
  font-size: 12.5px;
}

.request-meta {
  margin: 0;
  color: var(--text-muted);
  font-size: 11.5px;
  line-height: 1.6;
}

.request-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 12px;
}

.request-note-input {
  flex: 1 1 260px;
  min-width: 200px;
}

.request-error {
  margin-top: 10px;
  margin-bottom: 0;
}

/* Count of what's waiting, on the sub-tab itself — so an unanswered request
   is visible without opening the tab to look for it. */
.sub-tab-count {
  display: inline-block;
  min-width: 18px;
  margin-left: 5px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 10.5px;
  font-weight: 700;
  line-height: 17px;
  text-align: center;
}

/* Copy-week preview. The skip list is usually the interesting part — it's
   where everything that changed since last week shows up — so it gets real
   room rather than being hidden behind a count. */
.copy-summary {
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-alt, transparent);
  font-size: 14px;
  color: var(--text-primary);
}

.copy-skip-count {
  margin-left: 8px;
  color: var(--warning);
  font-weight: 600;
  font-size: 12.5px;
}

.copy-skipped {
  margin-top: 14px;
  max-height: 240px;
  overflow-y: auto;
}

.copy-skip-row {
  display: grid;
  gap: 1px;
  padding: 9px 0;
  border-bottom: 1px solid var(--border);
  font-size: 12.5px;
}

.copy-skip-row:last-child {
  border-bottom: none;
}

.copy-skip-row span {
  color: var(--text-secondary);
}

.copy-skip-row em {
  color: var(--text-muted);
  font-style: normal;
}

/* Audit search. Sits first in the filter row because it's how someone finds
   a specific entry; the dropdowns beside it are for narrowing, which is a
   different job. */
.audit-search-field {
  flex: 1 1 320px;
  min-width: 240px;
}

.audit-search-field input {
  width: 100%;
}

/* A week that can't be closed yet. Amber rather than red: nothing has gone
   wrong, there's just a decision outstanding — and the operator needs to see
   it on the week header before they reach for "Mark as paid", not as a
   refusal after they press it. */
.pill.blocked {
  background: var(--warning-soft);
  color: var(--warning);
  font-weight: 700;
}
