/* Villa PWA Design System — all variables and component primitives from 02_DESIGN_SYSTEM.md */

/* ========== CSS Custom Properties ========== */
:root {
  /* Brand */
  --brand-primary: #0f4c81;
  --brand-primary-hover: #0c3f6c;
  --brand-primary-light: #e6eef6;
  --brand-accent: #f4a261;
  --brand-cta: #d97706;
  --brand-cta-hover: #b45309;

  /* Brand gradient endpoints — used by the full-viewport auth screens
     (login, onboard, password reset) for the "Davenco navy" wash. Light-mode
     values are the original hardcoded hexes from login.css; dark-mode values
     are reassigned below in the dual-activation pattern. F#10 (Phase 1 audit). */
  --brand-gradient-start: #0a3260;
  --brand-gradient-end: #0e4070;

  /* Neutrals */
  --text-primary: #0f172a;
  --text-secondary: #475569;
  --text-tertiary: #94a3b8;
  --text-inverse: #ffffff;

  --bg-app: #f5f5f4;
  --bg-surface: #ffffff;
  --bg-surface-alt: #f0efed;

  --border-subtle: #e2e8f0;
  --border-strong: #cbd5e1;

  /* Status */
  --status-pending: #f59e0b;
  --status-approved: #10b981;
  --status-rejected: #ef4444;
  --status-info: #3b82f6;
  --status-neutral: #64748b;

  --status-pending-bg: #fef3c7;
  --status-approved-bg: #d1fae5;
  --status-rejected-bg: #fee2e2;
  --status-info-bg: #dbeafe;
  --status-neutral-bg: #f1f5f9;

  /* Text-on-light-bg variants — solid `--status-*` colors fail WCAG AA
     contrast on top of `--status-*-bg` (e.g. amber #f59e0b on light
     amber #fef3c7 ≈ 2.1:1). The `-text` family is the darker shade
     that pairs with the `-bg` family for badge / pill use. */
  --status-pending-text: #92400e;
  --status-approved-text: #166534;
  --status-rejected-text: #991b1b;
  --status-info-text: #1e40af;
  --status-neutral-text: #475569;

  /* Typography */
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-xl: 1.25rem;
  --text-2xl: 1.5rem;
  --text-3xl: 1.875rem;

  --leading-tight: 1.25;
  --leading-normal: 1.5;
  --leading-loose: 1.75;

  --weight-normal: 400;
  --weight-medium: 500;
  --weight-semibold: 600;
  --weight-bold: 700;

  /* Spacing */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --space-12: 3rem;
  --space-16: 4rem;

  /* Layout */
  --radius-sm: 0.25rem;
  --radius-md: 0.5rem;
  --radius-lg: 0.75rem;
  --radius-full: 9999px;

  --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
  --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);

  --header-height: 56px;
  --bottom-nav-height: 72px;
  --sidebar-width: 240px;
}

/* ========== Reset & Base ========== */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  font-size: 16px;
  -webkit-text-size-adjust: 100%;
}

body {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  font-weight: var(--weight-normal);
  line-height: var(--leading-normal);
  color: var(--text-primary);
  background: var(--bg-app);
  min-height: 100dvh;
}

/* ========== Layout ========== */
.app-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--header-height);
  background: var(--brand-primary);
  color: var(--text-inverse);
  display: flex;
  align-items: center;
  padding: 0 var(--space-4);
  z-index: 100;
  box-shadow: var(--shadow-sm);
}

/* Mobile back-chevron variant. When a page sets `app_header_back_href` in
   its context, base.html renders an .app-header__back <a> in place of the
   brand wordmark. Used by every mobile Settings sub-page so the user can
   navigate back to /more without relying on the browser back button.
   Padding is reduced on the left so the chevron hugs the edge. */
.app-header--with-back { padding-left: var(--space-2); }
.app-header__back {
  width: 40px;
  height: 40px;
  border-radius: var(--radius-md);
  color: var(--text-inverse);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  text-decoration: none;
  margin-right: var(--space-1);
}
.app-header__back:hover,
.app-header__back:active { background: rgb(255 255 255 / 0.10); }

/* Phase 0 mobile shell rework — `.app-header__brand` is the static brand
   (always shown on desktop; only on mobile Home). `.app-header__title` is
   the per-page title set via the `app_header_title` Jinja block (only
   shown on mobile non-Home). The visibility toggle is in this file's
   desktop @media block + the body.is-home rule. */
.app-header__brand,
.app-header__title {
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  flex: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.app-header__title {
  display: none; /* mobile non-Home flips this on; desktop CSS overrides */
}

body.is-home .app-header__brand {
  display: inline;
}
body.is-home .app-header__title {
  display: none;
}

/* Inline count chip rendered alongside the page title in the chrome bar
   (e.g. "Punch List  3"). Visually muted so the title still reads first. */
.header-count {
  display: inline-block;
  margin-left: var(--space-2);
  padding: 0 var(--space-2);
  border-radius: var(--radius-md);
  background: rgb(255 255 255 / 0.18);
  color: var(--text-inverse);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  line-height: 1.6;
  vertical-align: middle;
}

.app-header__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.app-content {
  padding-top: calc(var(--header-height) + var(--space-4));
  padding-bottom: calc(var(--bottom-nav-height) + env(safe-area-inset-bottom) + var(--space-4));
  padding-left: var(--space-4);
  padding-right: var(--space-4);
  min-height: 100dvh;
  max-width: 768px;
  margin: 0 auto;
}

/* Bottom nav — mobile */
.bottom-nav {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: calc(var(--bottom-nav-height) + env(safe-area-inset-bottom, 12px));
  padding-bottom: env(safe-area-inset-bottom, 12px);
  background: var(--bg-surface);
  border-top: 1px solid var(--border-subtle);
  display: flex;
  align-items: center;
  justify-content: space-around;
  z-index: 100;
}

.bottom-nav__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-width: 64px;
  min-height: 44px;
  padding: var(--space-1) var(--space-2);
  color: var(--text-secondary);
  text-decoration: none;
  font-size: var(--text-xs);
  border: none;
  background: none;
  cursor: pointer;
  transition: color 150ms;
}

.bottom-nav__item--active,
.bottom-nav__item:hover {
  color: var(--brand-primary);
}

.bottom-nav__item svg {
  width: 24px;
  height: 24px;
  margin-bottom: 2px;
}

/* Sync badge — header */
.sync-badge {
  position: absolute;
  top: 2px;
  right: 0;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 9px;
  background: var(--status-pending);
  color: #fff;
  font-size: 11px;
  font-weight: var(--weight-bold);
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}
.sync-badge--failed {
  background: var(--status-rejected);
}

.mr-badge {
  position: absolute;
  top: 2px;
  right: 50%;
  transform: translateX(18px);
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 9px;
  background: #DC2626;
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}

/* Sidebar — desktop */
.sidebar {
  display: none;
}

/* ========== Module chrome (shared list-page header + tabs) ==========
 *
 * ONE source of truth for the frame every module list page wears:
 * the header bar (title + meta + actions slot) and the tab strip
 * (Items / Reports / Settings / …). Each page writes its own header
 * markup but uses THESE classes, so the frame is identical on Punch
 * list, Material requests, Site Report, Office log, Photos — and any
 * new module (Attendance, Tasks, …) inherits it for free.
 *
 * Only the FRAME is shared. Module-specific content inside the slots
 * (export menus, filter rows, tables, grids) stays in each module's
 * own page CSS — a bug there can't affect another page.
 *
 * Spacing standard (uniformity rules U2–U4):
 *   header : padding 20/24/0, title+actions on one centred line
 *   tabs   : padding 16/24/0, margin-top 16, divider on the strip
 *   tokens : Foundation --space-* / --text-* only (no hard-coded px,
 *            no per-module font stacks for chrome spacing)
 */
.module-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-5) var(--space-6) 0;
  flex-shrink: 0;
}
.module-head__titles {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  min-width: 0;
  margin: 0;
}
.module-head__titles h1,
.module-head__title {
  margin: 0;
  font-size: var(--text-xl);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.018em;
  color: var(--text-primary);
}
.module-head__meta {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}
.module-head__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.module-tabs {
  display: flex;
  gap: 0;
  padding: var(--space-4) var(--space-6) 0;
  margin-top: var(--space-4);
  border-bottom: 1px solid var(--border-subtle);
}
.module-tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0 var(--space-4) 10px;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text-secondary);
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  cursor: pointer;
  letter-spacing: -0.005em;
  text-decoration: none;
}
.module-tab svg { width: 14px; height: 14px; stroke-width: 1.9; }
.module-tab:hover { color: var(--text-primary); }
.module-tab--active {
  color: var(--brand-primary);
  border-bottom-color: var(--brand-primary);
  /* Intentionally NO font-weight change — bumping medium→semibold makes
     the active tab text wider, which pushes the sibling tabs sideways
     every time you click a tab (visible across Punch list / Photos /
     Tasks etc.). Colour + the brand-primary underline accent are the
     active-state cues, same as Linear / GitHub / VSCode tab patterns. */
}
.module-tab__lock {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 4px;
  background: var(--bg-surface-alt);
  color: var(--text-tertiary);
  margin-right: 4px;
}
.module-tab__lock svg { width: 11px; height: 11px; stroke-width: 2.2; }

/* ========== Buttons ========== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 44px;
  min-width: 88px;
  padding: var(--space-3) var(--space-5);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
  border: none;
  border-radius: var(--radius-md);
  cursor: pointer;
  text-decoration: none;
  transition: background-color 150ms, color 150ms;
}

.btn:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
  /* Audit #45 — soft halo alongside the outline so the focus ring stays visible
     against the brand-primary button background where the outline alone fails
     contrast. */
  box-shadow: 0 0 0 4px rgba(15, 76, 129, 0.25);
}

/* Audit #45 — global :focus-visible fallback. Any focusable element that doesn't
   set its own focus style (links, custom interactive elements, native buttons
   without `.btn`) gets a consistent visible ring. Selectors with their own
   `:focus { outline: none; box-shadow: ... }` rule keep winning by specificity. */
a:focus-visible,
button:focus-visible,
[tabindex]:focus-visible,
[role="button"]:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
  box-shadow: 0 0 0 3px rgba(15, 76, 129, 0.25);
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.btn-primary {
  background: var(--brand-primary);
  color: var(--text-inverse);
}
.btn-primary:hover:not(:disabled) { background: var(--brand-primary-hover); }

.btn-accent {
  background: var(--brand-accent);
  color: var(--text-inverse);
}
.btn-accent:hover:not(:disabled) { background: #e0913a; }

.btn-cta {
  background: var(--brand-cta);
  color: var(--text-inverse);
}
.btn-cta:hover:not(:disabled) { background: var(--brand-cta-hover); }

.btn-secondary {
  background: var(--bg-surface);
  color: var(--text-primary);
  border: 1px solid var(--border-strong);
}
.btn-secondary:hover:not(:disabled) { background: var(--bg-surface-alt); }

.btn-danger {
  background: var(--status-rejected);
  color: var(--text-inverse);
}
.btn-danger:hover:not(:disabled) { background: #dc2626; }

/* Warning variant — softer than danger. Used for actions that take an
   item out of circulation but are reversible (e.g. retire an asset). */
.btn-warning {
  background: var(--status-pending);
  color: var(--text-inverse);
}
.btn-warning:hover:not(:disabled) { background: #d97706; }

.btn-ghost {
  background: transparent;
  color: var(--brand-primary);
  min-width: auto;
  padding: var(--space-2) var(--space-3);
}
.btn-ghost:hover:not(:disabled) { background: var(--brand-primary-light); }

.btn-sm {
  min-height: 36px;
  min-width: 64px;
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
}

.btn-lg {
  min-height: 52px;
  padding: var(--space-4) var(--space-6);
  font-size: var(--text-lg);
}

.btn-icon {
  min-width: 44px;
  min-height: 44px;
  padding: var(--space-2);
}

/* Default sizing for inline SVG icons inside any .btn. Without this, an SVG
   that omits width/height renders at its intrinsic size (often 300×150 from
   the viewBox default), blowing out button layouts. 1em scales with the
   button's text — a btn-lg gets a proportionally larger icon. Pages that
   need a different size still win via a more specific selector. */
.btn svg {
  width: 1em;
  height: 1em;
  flex-shrink: 0;
}

/* .btn-icon-prefix — utility for the leading icon (e.g. + on "New Item"
   buttons). Sized at 1.125em so the icon's geometric center sits at the
   text's optical center (text glyphs occupy roughly the upper 70% of the
   line-box, so a 1em icon appears slightly low). Procore + Linear pattern. */
.btn-icon-prefix {
  width: 1.125em;
  height: 1.125em;
}

/* Compact inline action button for table rows (Edit / Delete pencil & trash icons).
   Sits at 28px so a row doesn't grow vertically. Mobile hit area expands via
   touch-callout — desktop is the primary target. */
.btn-icon-sm {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid var(--border-default);
  background: var(--bg-surface);
  color: var(--text-secondary);
  border-radius: var(--radius-sm);
  cursor: pointer;
  margin-right: 4px;
  transition: background 120ms, border-color 120ms, color 120ms;
}
.btn-icon-sm:hover {
  background: var(--bg-surface-alt);
  border-color: var(--brand-primary);
  color: var(--brand-primary);
}
.btn-icon-sm--danger:hover {
  border-color: var(--status-rejected);
  color: var(--status-rejected);
  background: var(--status-rejected-bg, #fee2e2);
}

/* Audit #43 — bump to 36 × 36 on mobile so the table-row action buttons clear
   WCAG 2.5.8 (24 × 24 minimum) with comfortable margin for fat-thumb taps.
   Desktop stays at 28 px because mouse precision doesn't need the extra area
   and the row would grow vertically. */
@media (max-width: 767px) {
  .btn-icon-sm {
    width: 36px;
    height: 36px;
  }
}

.btn-full { width: 100%; }

/* ========== Form Fields ========== */
.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-bottom: var(--space-4);
}

.field-label {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
}

.field-input {
  /* Audit #42 — 60 px min-height for gloved field UX (PWA_STANDARD § "Touch targets ≥ 44 CSS px,
     field screens aim for 60 px"). Inputs auto-grow above this for textareas. */
  min-height: 60px;
  padding: var(--space-3);
  font-size: var(--text-base); /* 16px — prevents iOS zoom */
  font-family: var(--font-sans);
  color: var(--text-primary);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  transition: border-color 150ms, box-shadow 150ms;
}

.field-input:focus {
  outline: none;
  border-color: var(--brand-primary);
  /* Audit #45 — was `0 0 0 2px var(--brand-primary-light)` (#e6eef6) which was barely
     visible against white. rgba halo at 25% gives the same brand colour but with
     enough contrast to read against any surface. */
  box-shadow: 0 0 0 3px rgba(15, 76, 129, 0.25);
}

.field-input::placeholder {
  color: var(--text-tertiary);
}

.field-help {
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.field-error {
  font-size: var(--text-sm);
  color: var(--status-rejected);
}

select.field-input {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23475569' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-3) center;
  padding-right: var(--space-8);
}

textarea.field-input {
  height: auto;
  min-height: 88px;
  resize: vertical;
}

/* ========== Cards ========== */
.card {
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}

.card + .card {
  margin-top: var(--space-3);
}

.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-4);
  border-bottom: 1px solid var(--border-subtle);
}

.card-title {
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
}

.card-body {
  padding: var(--space-4);
}

.card-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--border-subtle);
  background: var(--bg-surface-alt);
}

.card-meta {
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.card-link {
  display: block;
  text-decoration: none;
  color: inherit;
  cursor: pointer;
}
.card-link:hover { background: var(--bg-surface-alt); }

/* ========== Status Badges ========== */
.badge {
  display: inline-flex;
  align-items: center;
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  border-radius: var(--radius-full);
  white-space: nowrap;
}

.badge-pending {
  background: var(--status-pending-bg);
  color: #92400e;
}
.badge-approved {
  background: var(--status-approved-bg);
  color: #065f46;
}
.badge-rejected {
  background: var(--status-rejected-bg);
  color: #991b1b;
}
.badge-info {
  background: var(--status-info-bg);
  color: #1e40af;
}
.badge-neutral {
  background: var(--status-neutral-bg);
  color: #334155;
}

/* ========== Tables ========== */
.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}

.table th {
  text-align: left;
  padding: var(--space-3) var(--space-4);
  font-weight: var(--weight-semibold);
  color: var(--text-secondary);
  border-bottom: 2px solid var(--border-subtle);
  background: var(--bg-surface-alt);
}

.table td {
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border-subtle);
  vertical-align: middle;
}

.table tr:hover td {
  background: var(--bg-surface-alt);
}

/* ========== Modal ========== */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgb(0 0 0 / 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 200;
  padding: var(--space-4);
  animation: fadeIn 200ms ease-out;
}

.modal {
  background: var(--bg-surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  max-width: 480px;
  width: 100%;
  max-height: 90dvh;
  overflow-y: auto;
  animation: slideUp 200ms ease-out;
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border-subtle);
}

.modal-body {
  padding: var(--space-5);
}

.modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-top: 1px solid var(--border-subtle);
}

/* ========== Modal (native <dialog> variant) ==========
   Used by snags + asset_register. Pairs with the `<dialog class="modal">`
   pattern + `dialog.showModal()` JS. The plain .modal-overlay above is
   the older div-based pattern; new modules should prefer this. */
dialog.modal {
  border: none;
  background: var(--bg-surface);
  border-radius: var(--radius-lg);
  padding: 0;
  max-width: 480px;
  width: calc(100% - var(--space-6));
  max-height: 90dvh;
  box-shadow: var(--shadow-lg);
  /* The universal `*, *::before, *::after { margin: 0 }` reset above wins
     over the UA stylesheet's `dialog:modal { margin: auto }`, so without
     this line the dialog pins to the viewport's top-left corner instead
     of centering. Restored explicitly. */
  margin: auto;
}
dialog.modal::backdrop {
  background: rgb(0 0 0 / 0.5);
}
dialog.modal .modal-header,
dialog.modal .modal-body,
dialog.modal .modal-footer {
  padding: var(--space-3) var(--space-4);
}
dialog.modal .modal-header__sub {
  margin: var(--space-1) 0 0;
  color: var(--text-secondary);
  font-size: var(--text-sm);
}
dialog.modal label {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-bottom: var(--space-3);
  font-size: var(--text-sm);
  color: var(--text-primary);
}
dialog.modal input,
dialog.modal textarea,
dialog.modal select {
  font-size: 16px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  color: var(--text-primary);
  min-height: 44px;
  font-family: inherit;
}
dialog.modal textarea { resize: vertical; }

/* Bottom-sheet modifier — docks dialog to viewport bottom on mobile.
   Audit decision #12: extend .modal rather than ship a new bottom-sheet
   primitive. Field defect-report flow lives here. The native <dialog>
   browser default is `inset: 0; margin: auto` (centred); we override
   inset to anchor the box at the bottom edge. */
@media (max-width: 600px) {
  dialog.modal--sheet {
    top: auto;
    bottom: 0;
    left: 0;
    right: 0;
    margin: 0;
    width: 100%;
    max-width: 100vw;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    max-height: 80dvh;
    box-shadow: 0 -8px 16px rgb(0 0 0 / 0.15);
  }
}

/* ========== Typeahead component (shared primitive) ==========
   First user is the diary equipment picker (M5). Future modules
   (supplier picker, user picker) lift the same classes. */
.typeahead-list {
  list-style: none;
  margin: 4px 0 0;
  padding: 0;
  position: absolute;
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  z-index: 50;
  width: 100%;
  max-height: 240px;
  overflow-y: auto;
}
.typeahead-list__item {
  display: flex;
  flex-direction: column;
  padding: var(--space-2) var(--space-3);
  cursor: pointer;
  border-bottom: 1px solid var(--border-subtle);
  min-height: 44px;
  justify-content: center;
}
.typeahead-list__item:last-child { border-bottom: none; }
.typeahead-list__item--active,
.typeahead-list__item:hover { background: var(--bg-surface-alt); }
.typeahead-list__label {
  font-size: var(--text-sm);
  color: var(--text-primary);
  font-weight: 500;
}
.typeahead-list__sub {
  font-size: var(--text-xs);
  color: var(--text-secondary);
  margin-top: 2px;
}

/* ========== Combobox component (shared primitive) ==========
   Single primitive for every "pick from a list" field on the app. Two
   modes (see combobox.js):
     - SELECT-ONLY (.combobox--select): replacement for native <select>.
       Visible input shows the label; a sibling hidden input carries the
       form value. Typing filters; blur reverts to the committed label.
     - FREE-TEXT (default): visible input value === form value. Typing
       commits whatever the user types; the option list is suggestions.

   Used by Manufacturer, Category, Status, Stored Project, Stored Villa
   on the asset register form. Future modules that need a stylable
   dropdown lift this primitive — DO NOT use native <select> for any
   user-visible field; its open menu cannot be CSS-styled and will look
   different from every other dropdown in the app. */
.combobox {
  position: relative;
  display: block;
  width: 100%;
}
/* Reserve room for the chevron button so typed text doesn't slide under
   it. The padding lives on the wrapper child, not on every form input,
   so .asset-form__field input { padding } still applies as the base. */
.combobox__input {
  width: 100%;
  padding-right: var(--space-8);
}
/* Cursor cue: select-only fields are click-to-open even when the input
   itself takes typing for filtering. The pointer cursor sells "this is
   pickable" the same way <select> does. */
.combobox--select .combobox__input { cursor: pointer; }

/* Wrapper-level focus ring — fires whenever the input OR chevron is
   focused, matching the per-field focus ring users see on plain inputs.
   Using :focus-within keeps the ring on while keyboard users tab between
   the input and any internal control. */
.combobox:focus-within .combobox__input,
.combobox:focus-within .combobox__chevron {
  /* No-op; the wrapper itself paints the ring (see ::before below). */
}
.combobox__chevron {
  position: absolute;
  top: 50%;
  right: var(--space-1);
  transform: translateY(-50%);
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  border-radius: var(--radius-sm);
  color: var(--text-secondary);
  cursor: pointer;
  padding: 0;
  transition: background-color 80ms, color 80ms;
}
.combobox__chevron:hover {
  background: var(--bg-surface-alt);
  color: var(--text-primary);
}
.combobox__chevron svg {
  width: 16px;
  height: 16px;
  transition: transform 120ms ease;
}
/* Open state — chevron rotates so the user sees a state cue beyond the
   list rendering (helps when the list is scrolled out of viewport). */
.combobox__input[aria-expanded="true"] ~ .combobox__chevron svg {
  transform: rotate(180deg);
}

/* Dropdown list — absolute, draws below the input. Width set explicitly
   to the parent's inner width via JS-free pure-CSS: the wrapper IS the
   positioning context, and width: 100% maps to its inner box. */
.combobox__list {
  list-style: none;
  margin: 4px 0 0;
  padding: var(--space-1) 0;
  position: absolute;
  width: 100%;
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg, var(--shadow-md));
  /* Above .bottom-nav (z=100) on mobile, below modals (z=200) and
     toasts (z=300). Without this the open list gets hidden behind the
     mobile bottom-nav PWA bar mid-scroll, eating half the suggestions. */
  z-index: 150;
  max-height: 280px;
  overflow-y: auto;
}
/* CRITICAL: [hidden] override. Without this, any explicit `display:` on
   .combobox__list / .combobox__new-hint loses to itself and the element
   shows even when JS sets element.hidden=true. (PR #257 shipped with
   this exact bug on .combobox__new-hint — visible "Will be saved as new"
   text under empty Manufacturer.) Same fix-or-trap on every shipped
   class with a default `display:`. */
.combobox__list[hidden],
.combobox__new-hint[hidden] { display: none !important; }

.combobox__item {
  padding: var(--space-2) var(--space-3);
  cursor: pointer;
  min-height: 40px;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--text-primary);
  border-radius: var(--radius-sm);
  margin: 0 var(--space-1);
  transition: background-color 60ms;
}
.combobox__item--active,
.combobox__item:hover { background: var(--bg-surface-alt); }
.combobox__item--selected { color: var(--text-primary); font-weight: 500; }

/* Check marker — visible only when the item is the committed value. The
   slot is reserved on every item so labels don't shift when scrolling
   between picked / unpicked rows. */
.combobox__item-check {
  display: inline-flex;
  flex: 0 0 16px;
  width: 16px;
  height: 16px;
  align-items: center;
  justify-content: center;
  color: var(--brand-primary);
  visibility: hidden;
}
.combobox__item--selected .combobox__item-check { visibility: visible; }
.combobox__item-check svg { width: 14px; height: 14px; }
.combobox__item-label {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.combobox__new-hint {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  color: var(--brand-primary);
  font-style: italic;
}

.combobox--disabled .combobox__chevron { cursor: not-allowed; opacity: 0.5; }
.combobox--disabled .combobox__input { cursor: not-allowed; }

/* ========== Toast ========== */
.toast-container {
  position: fixed;
  top: calc(var(--header-height) + var(--space-4));
  left: 50%;
  transform: translateX(-50%);
  z-index: 300;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  pointer-events: none;
}

.toast {
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  box-shadow: var(--shadow-md);
  pointer-events: auto;
  animation: slideUp 200ms ease-out;
}

.toast-success {
  background: var(--status-approved);
  color: var(--text-inverse);
}

.toast-error {
  background: var(--status-rejected);
  color: var(--text-inverse);
}

/* ========== Offline Banner ========== */
.offline-banner {
  position: fixed;
  top: var(--header-height);
  left: 0;
  right: 0;
  padding: var(--space-2) var(--space-4);
  background: var(--status-pending);
  color: #78350f;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  text-align: center;
  z-index: 99;
}

/* ========== Service-Worker Update Banner (audit #36) ==========
   Shown when app.js detects a new SW in `waiting` state. Sticks just below
   the offline banner; the refresh button posts `{type: 'skipWaiting'}` to
   the new SW so the user controls activation timing instead of mid-upload
   surprise. */
.sw-update-banner {
  position: fixed;
  top: calc(var(--header-height) + var(--space-2));
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  background: var(--brand-primary);
  color: var(--text-inverse);
  border-radius: var(--radius-md);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  font-size: var(--text-sm);
  z-index: 100;
}
.sw-update-banner__msg {
  font-weight: var(--weight-medium);
}

/* ========== Stale-Data Indicator (W17 #140) ==========
   Small pulsing dot in the header that appears briefly when base.js
   sees `X-Served-From-Cache: 1` on a SWR'd API GET response. Replaces
   the previous full-width banner under the header — that version
   surfaced on every navigation (because the routine MR badge fetch
   always hit the SW cache) and pushing content down on every click was
   disruptive. Auto-hides after 2s. The pulse + `title` attribute convey
   "refreshing in background" without blocking content. A discrete dot
   is intentionally distinct from the static sync icon next to it. */
.stale-indicator {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  margin: 0 var(--space-1);
}
.stale-indicator::before {
  content: "";
  display: block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #ffd166;
  box-shadow: 0 0 0 0 rgba(255, 209, 102, 0.55);
  animation: stale-indicator-pulse 1.4s ease-out infinite;
}
@keyframes stale-indicator-pulse {
  0%   { box-shadow: 0 0 0 0   rgba(255, 209, 102, 0.55); }
  70%  { box-shadow: 0 0 0 6px rgba(255, 209, 102, 0); }
  100% { box-shadow: 0 0 0 0   rgba(255, 209, 102, 0); }
}
/* prefers-reduced-motion is handled globally by the `*, *::before, *::after`
   rule near the bottom of this file (audit #47) — no per-component override. */

/* ========== Skeleton Loading ========== */
.skeleton {
  background: linear-gradient(90deg, var(--bg-surface-alt) 25%, var(--border-subtle) 50%, var(--bg-surface-alt) 75%);
  background-size: 200% 100%;
  animation: skeleton-pulse 1.5s ease-in-out infinite;
  border-radius: var(--radius-md);
}

.skeleton-text {
  height: 1rem;
  margin-bottom: var(--space-2);
}

.skeleton-card {
  height: 120px;
  margin-bottom: var(--space-3);
}

/* ========== Empty State (was here) ==========
   Old `.empty-state` rule consolidated into the canonical block further
   down (search "Empty state" with the polished SVG-illustration variant).
   Kept this comment as a beacon for future devs grep'ing the file. */

.empty-state__message {
  font-size: var(--text-lg);
  margin-bottom: var(--space-4);
}

/* ========== Page Header ========== */
.page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-4);
}

.page-title {
  font-size: var(--text-xl);
  font-weight: 600;
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text-primary);
}

.page-title svg {
  color: var(--brand-primary);
  flex-shrink: 0;
  width: 22px;
  height: 22px;
}

/* Title-block wraps the H1 + subtitle + optional meta chips on the left.
   Used by the page_header() macro in _page_header.html. */
.page-header__title-block {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.page-subtitle {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  margin: 0;
  line-height: var(--leading-normal);
}

.page-header__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-1);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.page-header__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
}

/* On narrow viewports, stack the actions below the title block so the
   primary CTA never gets squeezed off-screen. */
@media (max-width: 640px) {
  .page-header {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-3);
  }
  .page-header__actions {
    justify-content: flex-end;
  }
}

/* ========== KPI cards ========== */
/* Stat strip used at the top of list pages. 2-column on phones,
   auto-fit (min 200px) on desktop. Each card is uppercase label + big value
   + optional hint, with optional icon top-right. Severity modifiers
   (--critical, --major, --overdue) add a coloured left border for
   status-keyed boards (Punch List). */
.kpi-grid {
  display: grid;
  /* Mobile (default): single-column stacked tiles. The previous
     `repeat(2, 1fr)` produced an orphan row when there were 3 tiles
     (Critical full-row + Major half-row + Overdue half-row with dead
     space — see Procore Punch List Dashboard mobile pattern). */
  grid-template-columns: 1fr;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

@media (min-width: 600px) {
  .kpi-grid {
    /* Tablet: 2-up. */
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-3);
  }
}

@media (min-width: 768px) {
  .kpi-grid {
    /* Tiles are summary stats — they should hug-left at desktop widths
       rather than stretching to fill 1/N of the page. minmax(140, 200)
       keeps each tile compact instead of ballooning to ~300 px when
       only 3 tiles are present. justify-content: start aligns the
       group to the left so the empty space sits on the right. */
    grid-template-columns: repeat(auto-fit, minmax(140px, 200px));
    justify-content: start;
  }
}

.kpi-card {
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  position: relative;
  min-height: 64px;
}

.kpi-card__label {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-tertiary);
}

.kpi-card__value {
  font-size: var(--text-2xl);
  font-weight: var(--weight-bold);
  color: var(--text-primary);
  line-height: 1;
}

.kpi-card__hint {
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.kpi-card__icon {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--brand-primary-light);
  color: var(--brand-primary);
  border-radius: var(--radius-md);
}

.kpi-card__icon svg {
  width: 18px;
  height: 18px;
}

/* Severity modifiers — four-name semantic set per UI_LAYOUTS.md D6.
   --critical (red), --warning (amber), --success (green), --neutral (brand).
   The previous --major / --overdue names are folded into --warning /
   --critical (overdue is a critical state). */
.kpi-card--critical,
.kpi-card--warning,
.kpi-card--success,
.kpi-card--neutral {
  border-left-width: 3px;
  border-left-style: solid;
  padding-left: calc(var(--space-4) - 2px);
}
.kpi-card--critical { border-left-color: var(--status-rejected); }
.kpi-card--warning  { border-left-color: var(--status-pending); }
.kpi-card--success  { border-left-color: var(--status-success, #10b981); }
.kpi-card--neutral  { border-left-color: var(--brand-primary); }

/* Interactive KPI card — clickable to filter the list below. Anchor
   element keeps its inherited colour and gets hover + focus-visible +
   active feedback. */
.kpi-card--interactive {
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  transition: background 120ms, transform 120ms;
}
.kpi-card--interactive:hover  { background: var(--bg-surface-alt); }
.kpi-card--interactive:active { transform: scale(0.99); }
.kpi-card--interactive:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}
.kpi-card--active { box-shadow: 0 0 0 2px var(--brand-primary); }

/* Zero-count tile — Procore Punch List Dashboard pattern: tiles with
   no items are visually muted so they don't compete with the live
   counts. The label + value go grey, the heat stripe loses its colour,
   and the card sits on the page surface (not raised) so the eye lands
   on the tiles that actually carry numbers. */
.kpi-card--zero {
  opacity: 0.55;
  border-left-color: var(--border-subtle) !important;
}
.kpi-card--zero .kpi-card__label,
.kpi-card--zero .kpi-card__value {
  color: var(--text-tertiary);
}

/* ========== Page content wrapper ========== */
/* Used inside {% block content %} on list / dashboard pages. Centers a
   max-width column, applies horizontal padding + vertical rhythm. Per
   UI_LAYOUTS.md D15: per-module page CSS no longer restates these. */
.page-content {
  max-width: 1280px;
  margin: 0 auto;
  padding: var(--space-4) var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
@media (min-width: 768px) {
  .page-content {
    padding: var(--space-5) var(--space-6);
    gap: var(--space-5);
  }
}

/* ========== Action banner ========== */
/* Brand-tinted attention-grabbing affordance between header and content.
   Replaces per-module orange CTAs (Punch List walkthrough, etc) so orange
   (.btn-attention) is reserved for in-content "do this now" actions only.
   See UI_LAYOUTS.md D7 + 6.9. */
.action-banner {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--brand-primary-light);
  border-left: 4px solid var(--brand-primary);
  color: var(--text-primary);
  text-decoration: none;
  transition: background 120ms;
  min-height: 64px;
}
.action-banner:hover { background: var(--bg-surface-alt); }
.action-banner:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}
.action-banner__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  color: var(--brand-primary);
  flex-shrink: 0;
}
.action-banner__icon svg { width: 22px; height: 22px; }
.action-banner__body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1 1 auto;
  min-width: 0;
}
.action-banner__title {
  font-weight: 700;
  font-size: var(--text-lg);
  color: var(--text-primary);
}
.action-banner__sub {
  font-size: var(--text-sm);
  color: var(--text-secondary);
}
.action-banner__arrow {
  font-size: var(--text-xl);
  flex-shrink: 0;
  color: var(--brand-primary);
  font-weight: 600;
}

/* ========== Filter bar ========== */
/* Single horizontal row at ≥768px, wraps on mobile. Search input on the
   left, selects/links after, optional trailing slot for action buttons.
   All controls auto-submit (data-auto-submit JS) — no Apply button per
   UI_LAYOUTS.md D3. */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.filter-bar__search {
  /* Grows to fill remaining space but caps at 360px so the dropdowns
     don't get squeezed. On mobile (when row wraps) full width applies. */
  flex: 1 1 240px;
  max-width: 360px;
  position: relative;
  display: flex;
  align-items: center;
}
.filter-bar__search-icon {
  position: absolute;
  left: var(--space-3);
  width: 16px;
  height: 16px;
  color: var(--text-tertiary);
  pointer-events: none;
}
.filter-bar__search input {
  width: 100%;
  min-height: 40px;
  padding: 0 var(--space-3) 0 calc(var(--space-3) * 2 + 16px);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  background: var(--bg-surface);
  color: var(--text-primary);
  font-size: 16px;
}
.filter-bar__search input:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: -1px;
  border-color: var(--brand-primary);
}
.filter-bar select {
  min-height: 40px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  background: var(--bg-surface);
  color: var(--text-primary);
  font-size: var(--text-sm);
  flex: 0 1 auto;
}
.filter-bar select:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: -1px;
  border-color: var(--brand-primary);
}

/* ========== Filter chips ========== */
/* Linear-style active-filter chips. Visible whenever ≥1 filter is
   non-default. Each chip is a link; clicking the × removes that single
   filter and preserves the rest. See UI_LAYOUTS.md D4 + 6.4. */
.filter-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
}
.filter-chips__label {
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-right: var(--space-1);
}
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 4px var(--space-2);
  border-radius: var(--radius-full, 999px);
  background: var(--bg-surface-alt);
  border: 1px solid var(--border-subtle);
  color: var(--text-primary);
  font-size: var(--text-sm);
  text-decoration: none;
  white-space: nowrap;
  transition: background 120ms, border-color 120ms;
}
.chip:hover { background: var(--bg-surface); border-color: var(--border-strong); }
.chip:focus-visible { outline: 2px solid var(--brand-primary); outline-offset: 2px; }
.chip__x {
  font-size: 14px;
  line-height: 1;
  color: var(--text-tertiary);
  font-weight: 600;
}
.chip:hover .chip__x { color: var(--text-primary); }

/* ========== Filter row (unified) ========== */
/* Single-row layout combining tabs + search + active filter chips +
   "+ Add filter" popover. Replaces the old multi-row stack of separate
   tab-bar + KPI + filter-bar + filter-chips. UI quality checklist
   § 1.1, § 1.2, § 13.10 — Linear / Notion / Asana / Jira pattern. */
.filter-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

/* ========== Tab row ========== */
/* Scope tabs (All / Mine) live on their own line above the filter row.
   Previously inline with search + Add filter, but as soon as a chip
   appeared in the row the tabs would push to a wrap line and lose the
   underline alignment with the row baseline. Dedicated row solves it. */
.tab-row {
  display: flex;
  gap: var(--space-3);
  border-bottom: 1px solid var(--border-subtle);
  margin-bottom: var(--space-3);
}
.tab-row__tab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-1);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text-secondary);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color 120ms, border-color 120ms;
  white-space: nowrap;
}
.tab-row__tab:hover { color: var(--text-primary); }
.tab-row__tab--active {
  color: var(--brand-primary);
  border-bottom-color: var(--brand-primary);
  font-weight: var(--weight-semibold);
}
.tab-row__tab:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
  border-radius: 2px;
}
.tab-row__tab-count {
  font-size: var(--text-xs);
  font-weight: var(--weight-normal);
  color: var(--text-tertiary);
  padding: 1px 6px;
  background: var(--bg-surface-alt);
  border-radius: var(--radius-full, 999px);
}
.tab-row__tab--active .tab-row__tab-count {
  background: var(--brand-primary-light);
  color: var(--brand-primary);
}

/* Search occupies whatever space is left after chips + add. */
.filter-row__search-form {
  flex: 1 1 220px;
  max-width: 360px;
  min-width: 180px;
}
.filter-row__search {
  position: relative;
  display: flex;
  align-items: center;
}
/* Magnifier sits at the TRAILING end of the input — Procore + macOS
   Spotlight pattern. Previously leading; user feedback confirmed
   trailing reads cleaner with the rest of the row. */
.filter-row__search-icon {
  position: absolute;
  right: var(--space-3);
  width: 16px;
  height: 16px;
  color: var(--text-tertiary);
  pointer-events: none;
}
.filter-row__search input {
  width: 100%;
  min-height: 36px;
  padding: 0 calc(var(--space-3) * 2 + 16px) 0 var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  background: var(--bg-surface);
  color: var(--text-primary);
  font-size: 16px;
}
.filter-row__search input:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: -1px;
  border-color: var(--brand-primary);
}

/* ========== Add-filter popover ========== */
/* `<details><summary>` pattern — no JS needed for open/close.
   Summary is the chip-style "+ Add filter" trigger; expanded content
   is a small form with a select per filterable field plus an Apply
   button. Submitting navigates to /snags?... with the chosen values
   which then render as standalone chips outside the popover. */
.filter-add { position: relative; }
.filter-add__btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 4px var(--space-2);
  border-radius: var(--radius-full, 999px);
  background: var(--bg-surface);
  border: 1px dashed var(--border-strong);
  color: var(--text-secondary);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  cursor: pointer;
  list-style: none;
  white-space: nowrap;
  transition: background 120ms, color 120ms, border-color 120ms;
}
.filter-add__btn::-webkit-details-marker { display: none; }
.filter-add__btn:hover {
  background: var(--bg-surface-alt);
  color: var(--text-primary);
  border-color: var(--brand-primary);
}
.filter-add[open] .filter-add__btn {
  background: var(--bg-surface-alt);
  color: var(--brand-primary);
  border-color: var(--brand-primary);
  border-style: solid;
}
.filter-add__btn:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}

.filter-add__form {
  position: absolute;
  top: calc(100% + var(--space-2));
  left: 0;
  z-index: 30;
  min-width: 260px;
  padding: var(--space-3);
  background: var(--bg-surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.filter-add__field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.filter-add__field > span {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.filter-add__field select {
  min-height: 36px;
  padding: 0 var(--space-2);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  background: var(--bg-surface);
  color: var(--text-primary);
  font-size: var(--text-sm);
}
.filter-add__field--check {
  flex-direction: row;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--text-primary);
}
.filter-add__field--check input { margin: 0; }
.filter-add__actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-top: var(--space-2);
  border-top: 1px solid var(--border-subtle);
}
.filter-add__clear {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  text-decoration: none;
}
.filter-add__clear:hover { color: var(--brand-primary); text-decoration: underline; }

/* On narrow viewports the popover takes the full width below the row. */
@media (max-width: 639px) {
  .filter-add__form {
    left: auto;
    right: 0;
    min-width: min(320px, calc(100vw - var(--space-6)));
  }
}

/* ========== Saved views ========== */
/* Pinned filter combinations the user has named. UI quality checklist
   § 13.1 — Linear's killer feature. Same Foundation `app.saved_views`
   store as the admin /admin/snags surface; scoped per-user, per-module.
   `<details><summary>` popover; click a saved view to load that
   query-string; "★" marks the user's default. */
.saved-views { position: relative; }
.saved-views__btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 4px var(--space-2);
  border-radius: var(--radius-full, 999px);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  color: var(--text-secondary);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  cursor: pointer;
  list-style: none;
  white-space: nowrap;
  transition: background 120ms, color 120ms, border-color 120ms;
}
.saved-views__btn::-webkit-details-marker { display: none; }
.saved-views__btn:hover {
  background: var(--bg-surface-alt);
  color: var(--text-primary);
  border-color: var(--border-strong);
}
.saved-views[open] .saved-views__btn {
  background: var(--bg-surface-alt);
  color: var(--brand-primary);
  border-color: var(--brand-primary);
}
.saved-views__btn:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}
.saved-views__menu {
  position: absolute;
  right: 0;
  top: calc(100% + var(--space-2));
  z-index: 30;
  list-style: none;
  margin: 0;
  padding: var(--space-1);
  min-width: 240px;
  max-width: 320px;
  background: var(--bg-surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}
.saved-views__row {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
}
.saved-views__row-link {
  flex: 1;
  min-width: 0;
  display: block;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  text-decoration: none;
  font-size: var(--text-sm);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.saved-views__row-link:hover { background: var(--brand-primary-light); }
.saved-views__row-delete {
  flex: none;
  background: none;
  border: none;
  cursor: pointer;
  width: 24px;
  height: 24px;
  padding: 0;
  border-radius: var(--radius-sm);
  color: var(--text-tertiary);
  font-size: 16px;
  line-height: 1;
}
.saved-views__row-delete:hover {
  background: var(--status-rejected-bg);
  color: var(--status-rejected);
}
.saved-views__divider {
  height: 1px;
  background: var(--border-subtle);
  margin: var(--space-1) 0;
  padding: 0;
  list-style: none;
}
.saved-views__action {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  cursor: pointer;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--brand-primary);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
}
.saved-views__action:hover { background: var(--bg-surface-alt); }
.saved-views__empty {
  padding: var(--space-2) var(--space-3);
  color: var(--text-secondary);
  font-size: var(--text-xs);
  font-style: italic;
}

/* ========== Empty state ========== */
/* Polished empty state — illustration + headline + body + CTA + optional
   hint. Centred in a card. Used by every module's empty list. See
   UI_LAYOUTS.md D5 + 6.8. */
.empty-state {
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  padding: var(--space-8) var(--space-6);
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
}
.empty-state__illustration {
  width: 128px;
  height: 128px;
  margin-bottom: var(--space-2);
}
.empty-state__illustration svg {
  width: 100%;
  height: 100%;
}
.empty-state__title {
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
  margin: 0;
}
.empty-state__body {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  max-width: 48ch;
  line-height: var(--leading-relaxed, 1.6);
  margin: 0;
}
.empty-state__cta { margin-top: var(--space-2); }
.empty-state__hint {
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  margin-top: var(--space-2);
}

/* ========== Table list (shared desktop table chrome) ========== */
/* One canonical table chrome class set used by every module's list-page
   desktop table. Per-module CSS keeps only column widths and content-cell
   formatting. See UI_LAYOUTS.md D10 + 6.6. */
.table-list {
  width: 100%;
  border-collapse: collapse;
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  /* IMPORTANT: do NOT add `overflow: hidden` here. It clips every
     position:absolute popover that lives inside a row — the kebab menu
     (.row-kebab__menu), the inline-assignee picker (.inline-assignee
     __menu), etc. Rounded corners are preserved instead by radiusing
     the four corner cells (rules below). */
}
.table-list thead th {
  text-align: left;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: var(--weight-semibold);
  /* Match the asset-register table heading colour — `--text-secondary`
     reads with more contrast against `--bg-surface-alt` than the
     previous `--text-tertiary`. The wider asset register table sets
     the visual benchmark and the snag list inherits the same look so
     the two desktop tables feel like the same component. */
  color: var(--text-secondary);
  padding: var(--space-3);
  background: var(--bg-surface-alt);
  border-bottom: 1px solid var(--border-subtle);
  /* Sticky header — UI quality checklist § 7.7. Stays pinned to the
     top of the viewport when scrolling long lists so the user never
     loses track of which column is which. The page itself is the
     scroll container; `top: 0` references the viewport edge. */
  position: sticky;
  top: 0;
  z-index: 1;
}
/* Corner radii — match the table's outer border-radius so the bg-fill
   on the corner cells doesn't poke through the rounded outline. The
   `:last-of-type` selector reads "the last cell in the row regardless
   of which row that is" — works even when conditional cells (kebab,
   bulk checkbox) shift the column count. */
.table-list thead tr > th:first-child { border-top-left-radius: var(--radius-md); }
.table-list thead tr > th:last-child  { border-top-right-radius: var(--radius-md); }
.table-list tbody tr:last-child > td:first-child { border-bottom-left-radius: var(--radius-md); }
.table-list tbody tr:last-child > td:last-child  { border-bottom-right-radius: var(--radius-md); }
.table-list tbody td {
  padding: var(--space-3);
  font-size: var(--text-sm);
  color: var(--text-primary);
  border-bottom: 1px solid var(--border-subtle);
}
.table-list tbody tr:last-child td { border-bottom: none; }
.table-list tbody tr[data-href] {
  cursor: pointer;
  transition: background 120ms;
}
.table-list tbody tr[data-href]:hover { background: var(--bg-surface-alt); }
.table-list tbody tr[data-href]:focus-within { background: var(--bg-surface-alt); }
.table-list .th-sort { cursor: pointer; user-select: none; }
.table-list .th-sort:hover { color: var(--brand-primary); }
.table-list .th-sort--active { color: var(--brand-primary); }

/* REF column primary identifier — bold dark sans-serif text, no
   underline by default (the whole row is clickable; the inner <a>
   exists for keyboard / middle-click access). Brand-color underline
   on hover so it still reads as a link affordance when the user lands
   directly on the cell. Procore + Linear pattern uses sans-serif for
   refs (monospace is more of a dev-tool / GitHub / Jira convention). */
.table-list__ref {
  color: var(--text-primary);
  font-weight: 600;
  text-decoration: none;
}
.table-list__ref:hover {
  color: var(--brand-primary);
  text-decoration: underline;
}
.table-list__ref:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
  border-radius: 2px;
}

/* Kebab column — narrow trailing column with a `⋮` overflow menu.
   `<details><summary>` pattern (no JS needed for open/close). Reveal-on-
   row-hover keeps the chrome quiet on calmer rows. See UI_LAYOUTS.md. */
.table-list__col-kebab { width: 36px; padding: 0 var(--space-2); text-align: right; }

/* Title cell — truncate long titles with ellipsis instead of wrapping
   (which broke the row's vertical rhythm — UI quality checklist § 9.8).
   The full title is exposed via the `title=` attribute for hover-
   tooltip + screen-reader access. The max-width caps the column so a
   single long title doesn't blow out the layout on narrow viewports. */
.table-list__title-cell {
  max-width: 32ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Assignee cell — wraps the inline picker, keeps row vertical rhythm
   tight (the picker is taller than plain text by ~6 px on hover). */
.table-list__assignee-cell { padding: var(--space-2) var(--space-3); }

/* ========== Inline-assignee picker ========== */
/* Optimistic inline-edit pattern (UI quality checklist § 13.4) — click
   the assignee cell on a row → dropdown of users → pick → cell updates
   instantly, POST happens in the background; on server error, cell
   reverts. Linear / Asana / Notion convention.
   Only rendered for admin / super_admin (assign route is admin-gated). */
.inline-assignee {
  position: relative;
  display: inline-block;
  width: 100%;
}
.inline-assignee > summary {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 4px var(--space-2);
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  font-size: var(--text-sm);
  color: var(--text-primary);
  transition: background 120ms, border-color 120ms;
}
.inline-assignee > summary::-webkit-details-marker { display: none; }
.inline-assignee > summary:hover {
  background: var(--bg-surface-alt);
  border-color: var(--border-subtle);
}
.inline-assignee[open] > summary {
  background: var(--bg-surface-alt);
  border-color: var(--brand-primary);
}
.inline-assignee > summary:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}
.inline-assignee__placeholder {
  color: var(--text-tertiary);
  font-style: italic;
}
.inline-assignee__caret {
  color: var(--text-tertiary);
  flex-shrink: 0;
}
.inline-assignee__menu {
  position: absolute;
  left: 0;
  top: calc(100% + 2px);
  z-index: 25;
  list-style: none;
  margin: 0;
  padding: var(--space-1);
  min-width: 200px;
  max-height: 280px;
  overflow-y: auto;
  background: var(--bg-surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}
.inline-assignee__menu li { margin: 0; padding: 0; }
.inline-assignee__option {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  cursor: pointer;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--text-primary);
  white-space: nowrap;
}
.inline-assignee__option:hover { background: var(--brand-primary-light); }
.inline-assignee__option--selected {
  background: var(--brand-primary-light);
  color: var(--brand-primary);
  font-weight: var(--weight-semibold);
}
.inline-assignee__option--selected::before {
  content: "✓ ";
  color: var(--brand-primary);
  margin-right: 2px;
}

/* ========== Card list (shared mobile card chrome) ========== */
/* Mobile equivalent of .table-list. Each `.card-list__item` is one row
   of data, click anywhere navigates. See UI_LAYOUTS.md 6.7. */
.card-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.card-list__item {
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  transition: background 120ms;
}
/* Gate :hover behind (hover: hover) — without this, mobile taps leave
   the card stuck in the gray hover state until the user taps another
   card (touch screens fire :hover on tap and don't clear it). Linear,
   Procore, PlanRadar, and Fieldwire mobile lists all keep cards
   uniform-white; depth comes from the border + shadow, not from a
   sticky hover fill. */
@media (hover: hover) {
  .card-list__item:hover { background: var(--bg-surface-alt); }
}
.card-list__link {
  display: block;
  padding: var(--space-3);
  text-decoration: none;
  color: inherit;
  min-height: 80px;
}
.card-list__link:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: -1px;
  border-radius: var(--radius-md);
}
.card-list__top {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-bottom: var(--space-1);
}
.card-list__ref {
  font-family: var(--font-mono, monospace);
  font-size: var(--text-sm);
  color: var(--text-tertiary);
  flex: 1;
  min-width: 0;
}
.card-list__title {
  font-weight: 600;
  color: var(--text-primary);
  font-size: var(--text-base);
  margin-top: var(--space-1);
}
.card-list__meta {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  margin-top: var(--space-1);
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
  align-items: center;
}
.card-list__meta-dot {
  display: inline-block;
  width: 3px;
  height: 3px;
  background: var(--text-tertiary);
  border-radius: 50%;
}

/* Severity left-border for cards (Punch List uses this). Mirrors
   .kpi-card--<sev> rules so colour is consistent across surfaces. */
.card-list__item--critical { border-left: 4px solid var(--status-rejected); }
.card-list__item--warning  { border-left: 4px solid var(--status-pending); }
.card-list__item--success  { border-left: 4px solid var(--status-success, #10b981); }
.card-list__item--neutral  { border-left: 4px solid var(--brand-primary); }

/* ========== Tab bar ========== */
/* Underline-style tab row (Linear / Asana / Notion). Sits above the KPI
   strip on list pages with a "scope" filter — All / Mine / Overdue, etc.
   Each tab is a link to a query-string variant of the same page. */
.tab-bar {
  display: flex;
  gap: var(--space-4);
  border-bottom: 1px solid var(--border-subtle);
  margin-bottom: var(--space-1);
}
.tab-bar__tab {
  padding: var(--space-2) var(--space-1);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text-secondary);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color 120ms, border-color 120ms;
  white-space: nowrap;
}
.tab-bar__tab:hover { color: var(--text-primary); }
.tab-bar__tab--active {
  color: var(--brand-primary);
  border-bottom-color: var(--brand-primary);
  font-weight: var(--weight-semibold);
}
.tab-bar__tab:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
  border-radius: 2px;
}

/* ========== Row kebab menu ========== */
/* `<details><summary>` overflow menu shown at the end of a `.table-list`
   row. Procore pattern: triple-dot trigger, menu hangs below-right.
   Reveals on row hover; always visible on touch devices (no hover). */
.row-kebab { position: relative; display: inline-block; }
.row-kebab > summary {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-tertiary);
  transition: background 120ms, color 120ms, border-color 120ms;
}
.row-kebab > summary::-webkit-details-marker { display: none; }
.row-kebab > summary:hover {
  background: var(--bg-surface-alt);
  color: var(--text-primary);
  border-color: var(--border-subtle);
}
.row-kebab[open] > summary {
  background: var(--bg-surface-alt);
  color: var(--brand-primary);
  border-color: var(--border-subtle);
}
.row-kebab__menu {
  position: absolute;
  right: 0;
  top: calc(100% + 4px);
  list-style: none;
  margin: 0;
  padding: var(--space-1);
  min-width: 180px;
  background: var(--bg-surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  z-index: 20;
}
.row-kebab__menu li { margin: 0; padding: 0; }
.row-kebab__item {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  text-decoration: none;
  font-size: var(--text-sm);
  cursor: pointer;
  white-space: nowrap;
}
.row-kebab__item:hover { background: var(--brand-primary-light); }
.row-kebab__item--danger { color: var(--status-rejected); }
.row-kebab__item--danger:hover { background: var(--status-rejected-bg, #fee2e2); }

/* Procore-style reveal-on-row-hover. Always-on for touch devices. */
@media (hover: hover) {
  .table-list tbody tr .row-kebab > summary { opacity: 0.4; }
  .table-list tbody tr:hover .row-kebab > summary,
  .row-kebab[open] > summary { opacity: 1; }
}

/* ========== Utilities ========== */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
}

.text-center { text-align: center; }
.text-sm { font-size: var(--text-sm); }
.text-secondary { color: var(--text-secondary); }
.mt-4 { margin-top: var(--space-4); }
.mb-4 { margin-bottom: var(--space-4); }

/* Inline state utility — colour text + bold weight to flag overdue rows.
   Used inside table cells / card meta lines. Avoids inline style="color:…"
   which the audit gate (test_audit_34) bans. */
.is-overdue { color: var(--status-rejected); font-weight: 600; }

/* Freshness hint — render-timestamp shown on server-rendered list pages
   ("as of HH:MM WITA" → "Refreshed N min ago"). Populated by freshness.js;
   shared by Settings:projects, Subcontractors, Home (UI §7.8). Token-based so
   it auto-themes light/dark. */
.freshness {
  display: inline-block;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text-tertiary);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* ========== Animations ========== */
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes slideUp {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes skeleton-pulse {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}
.spin { animation: spin 1s linear infinite; }

/* Audit #47 — strict reduced-motion. Was a "0.01 ms duration" soft-reduce which
   still let infinite-loop animations like `.spin` keep playing. WCAG 2.3.3
   wants motion fully killed when the user opts out. We also nuke smooth scroll
   so JS-driven scroll-into-view jumps instantly. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }
}

/* ========== Tablet (768px+) ========== */
@media (min-width: 768px) {
  .app-content {
    max-width: 768px;
  }
}

/* ========== Desktop (1024px+) ========== */
@media (min-width: 1024px) {
  .bottom-nav {
    display: none;
  }

  .sidebar {
    display: flex;
    flex-direction: column;
    position: fixed;
    top: var(--header-height);
    left: 0;
    bottom: 0;
    width: var(--sidebar-width);
    background: var(--bg-surface);
    border-right: 1px solid var(--border-subtle);
    padding: var(--space-4) 0;
    z-index: 90;
    overflow-y: auto;
  }

  .sidebar__item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-5);
    color: var(--text-secondary);
    text-decoration: none;
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    min-height: 44px;
    transition: background-color 150ms, color 150ms;
  }

  .sidebar__item:hover {
    background: var(--bg-surface-alt);
    color: var(--text-primary);
  }

  .sidebar__item--active {
    color: var(--brand-primary);
    background: var(--brand-primary-light);
    border-right: 3px solid var(--brand-primary);
  }

  .sidebar__item svg {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
  }

  .sidebar__divider {
    height: 1px;
    background: var(--border-subtle);
    margin: var(--space-3) 0;
  }

  .sidebar__section-label {
    /* Audit #109 — element became <h2> for SR semantics; reset the UA
       margin so layout matches the previous <span> rendering. */
    margin: 0;
    padding: var(--space-2) var(--space-5);
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    color: var(--text-tertiary);
    text-transform: uppercase;
    letter-spacing: 0.05em;
  }

  .app-content {
    margin-left: var(--sidebar-width);
    padding-bottom: var(--space-8);
    max-width: 1280px;
  }

  /* Phase 0 — desktop chrome rules. Brand always visible, page-title
     never (it stays in the body via _page_header per D3). User menu
     visible (mobile uses the drawer instead). Pull-to-refresh disabled. */
  .app-header__brand { display: inline; }
  .app-header__title { display: none; }
  .user-menu { display: inline-flex; }
  .pull-refresh-indicator { display: none; }

  /* Sidebar inline reorder — grip icon on hover, drop indicator line,
     dragging-state opacity. Items in the `bottom` section don't carry
     `--reorderable` so these rules skip them. */
  .sidebar__item {
    position: relative;
  }
  .sidebar__grip {
    margin-left: auto;
    color: var(--text-tertiary);
    opacity: 0;
    transition: opacity 120ms;
    cursor: grab;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
  }
  .sidebar__item--reorderable:hover .sidebar__grip,
  .sidebar__item--reorderable:focus-visible .sidebar__grip {
    opacity: 1;
  }
  .sidebar__item--dragging {
    opacity: 0.5;
    cursor: grabbing;
  }
  .sidebar__drop-indicator {
    height: 2px;
    background: var(--brand-primary);
    margin: 0 var(--space-5);
    border-radius: 1px;
    pointer-events: none;
  }
}

/* ============================================================================
 * v5 desktop chrome — gated by `body.ui-redesign` (env var UI_REDESIGN=1).
 * Scope: desktop only (≥1024px). When the flag is off these rules don't
 * apply and the legacy chrome above renders unchanged.
 *
 * Phase 1 — sidebar rewrite. The sidebar now spans the full viewport
 *   height with the workspace brand block at the top.
 * Phase 2 — topbar rewrite. The legacy `.app-header` is hidden on desktop;
 *   the new `.topbar` carries breadcrumb · search trigger · theme · bell ·
 *   avatar. Mobile keeps the legacy header (`.topbar` is `display: none`
 *   outside the desktop media query).
 * ============================================================================ */

/* Default: hide v5 topbar at any viewport. The desktop media query below
   enables it; mobile never shows it. */
.topbar { display: none; }

/* Legacy `/inbox` page empty-state icon — ships without width/height
   attributes (only viewBox), so without an explicit size the browser
   falls back to 300×150 and blows up the page layout. Scoped to the
   icon wrapper so it applies on both mobile and desktop viewports. */
.inbox-empty__icon svg {
  width: 56px;
  height: 56px;
  stroke-width: 1.5;
  color: var(--text-tertiary);
}
.inbox-empty__icon {
  display: flex;
  justify-content: center;
  margin-bottom: var(--space-3);
}

@media (min-width: 1024px) {
  /* Override the v5 token palette inside the v5 chrome only. Matches
     the v5 mockup's subtle sidebar tint (`#fbfcfd`) and cooler app
     background (`#f6f7f9`). Scoped to body.ui-redesign so the legacy
     chrome's `--bg-app` (`#f5f5f4`, warm) is unaffected.

     CRITICAL: these are LIGHT-theme values. Without an explicit dark
     override the cascade leaks them into dark mode (where they win
     over `:root[data-theme=dark]` because `body.ui-redesign` is more
     specific) — turning the chrome surfaces white in night mode.
     Dark-mode block below restores the dark palette inside ui-redesign. */
  body.ui-redesign {
    --bg-sidebar: #fbfcfd;
    --bg-app: #f6f7f9;
    --bg-surface-alt: #f1f3f6;
  }
  html[data-theme="dark"] body.ui-redesign {
    --bg-sidebar: var(--dark-bg-surface);
    --bg-app: var(--dark-bg-app);
    --bg-surface-alt: var(--dark-bg-surface-alt);
  }
  @media (prefers-color-scheme: dark) {
    html:not([data-theme="light"]) body.ui-redesign {
      --bg-sidebar: var(--dark-bg-surface);
      --bg-app: var(--dark-bg-app);
      --bg-surface-alt: var(--dark-bg-surface-alt);
    }
  }

  body.ui-redesign .sidebar--v5 {
    display: flex;
    flex-direction: column;
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: var(--sidebar-width);
    background: var(--bg-sidebar);
    border-right: 1px solid var(--border-subtle);
    z-index: 90;
    padding: 0;
    overflow: hidden;
  }

  /* Lock the design tokens at the sidebar boundary so per-page CSS that
     overrides them for in-page typography/palette (for example body.mr-v11
     sets --text-sm: 13px to compress the MR list, and darkens --text-tertiary
     to #6b7382 for its content) does not cascade into the sidebar and change
     the nav font size or tint the labels/icons. The sidebar must look
     identical on every page — switching modules should never appear to
     change the nav font weight, size, or colour.

     Mirrors the chrome bg-token lock above (~body.ui-redesign { --bg-sidebar }):
     light values here, with explicit + system dark overrides below so the
     lock itself doesn't leak light tokens into dark mode. The dark selectors
     are intentionally specific enough (≥0,3,2) to beat a module's own dark
     block such as `html[data-theme="dark"] body.mr-v11` (0,2,2). */
  .sidebar,
  body.ui-redesign .sidebar--v5 {
    --text-xs: 0.75rem;
    --text-sm: 0.875rem;
    --text-base: 1rem;
    --text-lg: 1.125rem;
    --text-xl: 1.25rem;
    --brand-primary: #0f4c81;
    --brand-primary-light: #e6eef6;
    --text-primary: #0f172a;
    --text-secondary: #475569;
    --text-tertiary: #94a3b8;
    --border-subtle: #e2e8f0;
  }
  html[data-theme="dark"] .sidebar,
  html[data-theme="dark"] body.ui-redesign .sidebar--v5 {
    --brand-primary: #0f4c81;
    --brand-primary-light: var(--dark-brand-primary-light);
    --text-primary: var(--dark-text-primary);
    --text-secondary: var(--dark-text-secondary);
    --text-tertiary: var(--dark-text-tertiary);
    --border-subtle: var(--dark-border-subtle);
  }
  @media (prefers-color-scheme: dark) {
    html:not([data-theme="light"]) .sidebar,
    html:not([data-theme="light"]) body.ui-redesign .sidebar--v5 {
      --brand-primary: #0f4c81;
      --brand-primary-light: var(--dark-brand-primary-light);
      --text-primary: var(--dark-text-primary);
      --text-secondary: var(--dark-text-secondary);
      --text-tertiary: var(--dark-text-tertiary);
      --border-subtle: var(--dark-border-subtle);
    }
  }

  /* Brand block — sits at the top of the sidebar, the same height as the
     topbar so the topbar's left edge lines up with the start of main
     content. */
  body.ui-redesign .sidebar__brand {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    height: var(--header-height);
    padding: 0 var(--space-4);
    border-bottom: 1px solid var(--border-subtle);
    flex-shrink: 0;
  }
  body.ui-redesign .sidebar__brand-mark {
    width: 28px;
    height: 28px;
    border-radius: var(--radius-sm);
    background: var(--brand-primary);
    color: var(--text-inverse);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: var(--text-xs);
    font-weight: var(--weight-bold);
    letter-spacing: -0.02em;
    flex-shrink: 0;
  }
  body.ui-redesign .sidebar__brand-name {
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* Phase 2 — the legacy `.app-header` is now hidden on desktop when the
     flag is on; the new `.topbar` (below) takes its place. Mobile keeps
     the legacy header because `.topbar` is desktop-only. */
  body.ui-redesign .app-header { display: none; }

  /* v5 topbar — white surface, breadcrumb left, search + actions right.
     Sits to the right of the sidebar; its left edge lines up with the
     start of main content. */
  body.ui-redesign .topbar {
    position: fixed;
    top: 0;
    left: var(--sidebar-width);
    right: 0;
    height: var(--header-height);
    background: var(--bg-surface);
    border-bottom: 1px solid var(--border-subtle);
    box-shadow: var(--shadow-sm);
    display: flex;
    align-items: center;
    padding: 0 var(--space-3) 0 var(--space-4);
    gap: var(--space-2);
    z-index: 100;
  }

  body.ui-redesign .topbar__crumb {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    color: var(--text-secondary);
    flex-shrink: 0;
    min-width: 0;
  }
  body.ui-redesign .topbar__crumb a {
    color: var(--text-secondary);
    text-decoration: none;
  }
  body.ui-redesign .topbar__crumb a:hover { color: var(--text-primary); }
  body.ui-redesign .topbar__crumb-current {
    color: var(--text-primary);
    font-weight: var(--weight-semibold);
    letter-spacing: -0.005em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  body.ui-redesign .topbar__crumb-sep {
    color: var(--text-tertiary);
    font-weight: var(--weight-normal);
    margin: 0 4px;
  }

  /* Search trigger — a button styled to look like an input. Phase 4
     wires the click to open the Cmd-K palette; Phase 2 ships it inert.
     `margin-right: auto` pushes the trailing action group (theme, bell,
     avatar) to the right edge of the topbar, matching Linear/Notion
     convention where chrome actions live in the top-right. */
  body.ui-redesign .topbar__search {
    flex: 1 1 auto;
    max-width: 460px;
    margin: 0 auto 0 var(--space-4);
    height: 32px;
    padding: 0 80px 0 32px;
    background: var(--bg-app);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    font-size: var(--text-sm);
    color: var(--text-tertiary);
    text-align: left;
    cursor: pointer;
    position: relative;
    display: flex;
    align-items: center;
  }
  body.ui-redesign .topbar__search:hover {
    border-color: var(--border-strong);
    background: var(--bg-surface-alt);
  }
  body.ui-redesign .topbar__search > svg {
    position: absolute;
    left: 10px;
    width: 14px;
    height: 14px;
    color: var(--text-tertiary);
    pointer-events: none;
  }
  body.ui-redesign .topbar__search-placeholder {
    flex: 1 1 auto;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  body.ui-redesign .topbar__search-kbd {
    position: absolute;
    right: 10px;
    pointer-events: none;
  }
  body.ui-redesign .topbar__search-kbd kbd {
    background: var(--bg-surface);
    border: 1px solid var(--border-subtle);
    border-bottom-width: 2px;
    border-radius: 3px;
    padding: 1px 5px;
    font-size: 10px;
    color: var(--text-tertiary);
    font-family: var(--font-mono);
  }

  /* Topbar icon buttons — theme + bell. The avatar uses the existing
     user-menu trigger which already has its own styles; we just align it. */
  body.ui-redesign .topbar__icon {
    width: 32px;
    height: 32px;
    border-radius: var(--radius-sm);
    border: 0;
    background: transparent;
    color: var(--text-secondary);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    position: relative;
    flex-shrink: 0;
    text-decoration: none;
  }
  body.ui-redesign .topbar__icon:hover {
    background: var(--bg-surface-alt);
    color: var(--text-primary);
  }
  body.ui-redesign .topbar__icon svg {
    width: 17px;
    height: 17px;
    stroke-width: 1.9;
  }
  /* Theme toggle visibility — pure CSS, no JS dependency. Both SVGs
     are server-rendered so the toggle is keyboard-friendly without
     JS; CSS shows ONLY the one matching the current theme. Default
     is light (sun visible, moon hidden); flipped under data-theme=dark.
     Also handles unset-theme with `prefers-color-scheme: dark` for
     users who follow OS preference. */
  body.ui-redesign .topbar__icon [data-theme-icon="dark"] { display: none; }
  body.ui-redesign .topbar__icon [data-theme-icon="light"] { display: block; }
  html[data-theme="dark"] body.ui-redesign .topbar__icon [data-theme-icon="dark"] { display: block; }
  html[data-theme="dark"] body.ui-redesign .topbar__icon [data-theme-icon="light"] { display: none; }
  @media (prefers-color-scheme: dark) {
    html:not([data-theme]) body.ui-redesign .topbar__icon [data-theme-icon="dark"] { display: block; }
    html:not([data-theme]) body.ui-redesign .topbar__icon [data-theme-icon="light"] { display: none; }
  }
  body.ui-redesign .topbar__icon-badge {
    position: absolute;
    top: 4px;
    right: 4px;
    min-width: 14px;
    height: 14px;
    padding: 0 3px;
    border-radius: var(--radius-full);
    background: var(--status-rejected);
    color: var(--text-inverse);
    font-size: 9px;
    font-weight: var(--weight-bold);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1.5px solid var(--bg-surface);
    line-height: 1;
  }

  /* Profile menu — the legacy `.user-menu__trigger` is styled for the
     dark brand header; recolour it for the white topbar. Avatar size
     and dropdown position are unchanged. */
  body.ui-redesign .topbar .user-menu__trigger {
    color: var(--text-secondary);
    background: transparent;
  }
  body.ui-redesign .topbar .user-menu__trigger:hover {
    background: var(--bg-surface-alt);
    color: var(--text-primary);
  }
  /* The avatar chip inside the trigger keeps its dark-header colours
     (faint-white fill + white initial) — invisible on the white topbar,
     so the chip reads as an empty box. Give it a solid brand circle with
     a white initial here; the base rule still serves the mobile/legacy
     dark header. */
  body.ui-redesign .topbar .user-menu__avatar {
    background: var(--brand-primary);
    color: var(--text-inverse);
  }

  /* Main content drops the legacy `.app-header` top padding (the header
     is hidden) and adds equivalent space for the v5 topbar (same height,
     so the value is the same — but expressed against the topbar for
     clarity). */
  body.ui-redesign .app-content {
    padding-top: calc(var(--header-height) + var(--space-4));
  }

  /* Main scroll area is shifted by the sidebar width — same as the legacy
     chrome, so this is a no-op here for clarity. */
  body.ui-redesign .app-content {
    margin-left: var(--sidebar-width);
  }

  /* Nav scroll region — sits between the brand block and the pinned
     footer. */
  body.ui-redesign .sidebar__nav {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: var(--space-3) 0;
  }
  body.ui-redesign .nav-section {
    padding: 0;
    margin-bottom: var(--space-3);
  }
  body.ui-redesign .nav-section--divided {
    margin-top: var(--space-3);
    padding-top: var(--space-3);
    border-top: 1px solid var(--border-subtle);
  }
  body.ui-redesign .nav-section__heading {
    margin: 0;
    padding: 6px var(--space-4) 4px;
    font-size: 11px;
    font-weight: var(--weight-bold);
    color: var(--text-tertiary);
    letter-spacing: 0.06em;
    text-transform: uppercase;
  }

  /* Nav item — used everywhere in the v5 sidebar (top, modules, directory,
     footer). 32px tall to match the v5 mockup; horizontal padding + left
     stripe on the active item give Linear-style focus affordance. */
  body.ui-redesign .nav-item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: 0 var(--space-4);
    height: 32px;
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    color: var(--text-secondary);
    text-decoration: none;
    border-left: 2px solid transparent;
    transition: background-color 100ms, color 100ms;
  }
  body.ui-redesign .nav-item:hover {
    background: var(--bg-surface-alt);
    color: var(--text-primary);
  }
  body.ui-redesign .nav-item--active {
    background: var(--brand-primary-light);
    color: var(--brand-primary);
    border-left-color: var(--brand-primary);
    font-weight: var(--weight-semibold);
  }
  body.ui-redesign .nav-item--active .nav-item__icon {
    color: var(--brand-primary);
  }
  body.ui-redesign .nav-item--soon {
    color: var(--text-tertiary);
    cursor: default;
  }
  body.ui-redesign .nav-item--soon:hover {
    background: transparent;
    color: var(--text-tertiary);
  }
  body.ui-redesign .nav-item__icon {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    color: var(--text-tertiary);
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  body.ui-redesign .nav-item__icon svg {
    width: 16px;
    height: 16px;
    stroke-width: 1.8;
  }
  body.ui-redesign .nav-item__label {
    flex: 1 1 auto;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    letter-spacing: -0.005em;
  }
  body.ui-redesign .nav-item__count {
    font-size: 11px;
    font-weight: var(--weight-semibold);
    color: var(--text-tertiary);
    flex-shrink: 0;
    font-variant-numeric: tabular-nums;
    padding: 1px 6px;
    border-radius: var(--radius-full);
  }
  body.ui-redesign .nav-item__count--alert {
    color: var(--status-rejected-text);
    background: var(--status-rejected-bg);
  }
  body.ui-redesign .nav-item__soon-badge {
    font-size: 10px;
    font-weight: var(--weight-bold);
    color: var(--text-tertiary);
    background: var(--bg-surface-alt);
    padding: 1px 5px;
    border-radius: var(--radius-sm);
    letter-spacing: 0.04em;
    text-transform: uppercase;
  }
  /* FUTURE badge uses the same chip visual as SOON. The class hook lets
     us re-skin one or the other later without touching the template. */
  body.ui-redesign .nav-item__soon-badge--future {
    /* identical chip — same colours, same size */
  }

  /* Drag-to-reorder for the v5 sidebar. Mirrors the legacy `.sidebar`
     drag rules (~line 2382) but scoped to `.sidebar--v5 .nav-item`.
     Grip appears on hover only; FUTURE/SOON rows are draggable too —
     the click is what's disabled, not the drag. */
  body.ui-redesign .nav-item--reorderable {
    position: relative;
  }
  body.ui-redesign .nav-item__grip {
    margin-left: auto;
    color: var(--text-tertiary);
    opacity: 0;
    transition: opacity 120ms;
    cursor: grab;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
  }
  body.ui-redesign .nav-item--reorderable:hover .nav-item__grip,
  body.ui-redesign .nav-item--reorderable:focus-visible .nav-item__grip {
    opacity: 1;
  }
  body.ui-redesign .nav-item--dragging {
    opacity: 0.5;
    cursor: grabbing;
  }
  body.ui-redesign .nav-item__drop-indicator {
    height: 2px;
    background: var(--brand-primary);
    margin: 0 var(--space-5);
    border-radius: 1px;
    pointer-events: none;
  }

  /* Pinned footer — Settings + Help. Always visible at the bottom of the
     sidebar regardless of scroll position above. */
  body.ui-redesign .sidebar__footer {
    flex: 0 0 auto;
    border-top: 1px solid var(--border-subtle);
    padding: var(--space-2) 0;
    background: var(--bg-sidebar);
  }

  /* Sidebar resize handle — 4px strip on the right edge. Invisible at
     rest; highlights on hover so the drag affordance is discoverable
     without being permanent visual noise. Hidden when the sidebar is
     collapsed (no resizing from the icon rail). */
  body.ui-redesign .sidebar__resize-handle {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    width: 4px;
    cursor: ew-resize;
    background: transparent;
    transition: background-color 120ms;
    z-index: 1;
  }
  body.ui-redesign .sidebar__resize-handle:hover,
  body.ui-redesign .sidebar__resize-handle[data-resizing="true"] {
    background: var(--brand-primary);
  }
  body.ui-redesign[data-sidebar-collapsed="true"] .sidebar__resize-handle {
    display: none;
  }

  /* Collapse / expand toggle — chevron in the brand row that flips the
     sidebar between full-width and the 56px icon rail. Same button,
     just rotated 180° in the collapsed state so it still points the
     direction it would open. */
  body.ui-redesign .sidebar__collapse-btn {
    margin-left: auto;
    width: 24px;
    height: 24px;
    border: 0;
    background: transparent;
    color: var(--text-tertiary);
    border-radius: var(--radius-sm);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    transition: background-color 120ms, color 120ms, transform 180ms;
  }
  body.ui-redesign .sidebar__collapse-btn:hover {
    background: var(--bg-surface-alt);
    color: var(--text-primary);
  }
  body.ui-redesign[data-sidebar-collapsed="true"] .sidebar__collapse-btn {
    transform: rotate(180deg);
  }

  /* Collapsed state — icon rail. Width drops to 56px (var on body),
     and the labels / counts / badges / grips / section headings all
     hide. Tooltip on hover comes free from the existing `title` attr
     on each <a>. */
  body.ui-redesign[data-sidebar-collapsed="true"] .sidebar--v5 {
    /* The body inline style already sets --sidebar-width: 56px when
       collapsed; this rule just locks the brand row to the same width
       so the chevron stays clickable. */
  }
  body.ui-redesign[data-sidebar-collapsed="true"] .sidebar__brand {
    padding: 0 var(--space-2);
    justify-content: center;
  }
  body.ui-redesign[data-sidebar-collapsed="true"] .sidebar__brand-name,
  body.ui-redesign[data-sidebar-collapsed="true"] .nav-section__heading,
  body.ui-redesign[data-sidebar-collapsed="true"] .nav-item__label,
  body.ui-redesign[data-sidebar-collapsed="true"] .nav-item__count,
  body.ui-redesign[data-sidebar-collapsed="true"] .nav-item__soon-badge,
  body.ui-redesign[data-sidebar-collapsed="true"] .nav-item__grip {
    display: none;
  }
  body.ui-redesign[data-sidebar-collapsed="true"] .nav-item {
    justify-content: center;
    padding: var(--space-2);
  }
  /* While dragging the resize handle, suppress text selection across
     the page so the cursor doesn't accidentally highlight content. */
  body.ui-redesign[data-sidebar-resizing="true"] {
    user-select: none;
    cursor: ew-resize;
  }
  body.ui-redesign[data-sidebar-resizing="true"] .sidebar--v5,
  body.ui-redesign[data-sidebar-resizing="true"] .topbar {
    /* No transitions during the drag — width follows the pointer 1:1. */
    transition: none;
  }

  /* Phase 3 — inbox slide-out panel. Always in the DOM; CSS keeps it
     off-screen until <body data-inbox-open="true">. */
  body.ui-redesign .inbox-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.20);
    z-index: 110;
    opacity: 0;
    visibility: hidden;
    transition: opacity 150ms ease, visibility 0s linear 150ms;
  }
  body.ui-redesign[data-inbox-open="true"] .inbox-backdrop {
    opacity: 1;
    visibility: visible;
    transition: opacity 150ms ease;
  }
  body.ui-redesign .inbox-panel {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    width: 380px;
    background: var(--bg-surface);
    box-shadow: var(--shadow-lg);
    display: flex;
    flex-direction: column;
    z-index: 111;
    border-left: 1px solid var(--border-subtle);
    transform: translateX(100%);
    transition: transform 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
  }
  body.ui-redesign[data-inbox-open="true"] .inbox-panel {
    transform: translateX(0);
  }
  body.ui-redesign .inbox-panel__head {
    flex-shrink: 0;
    height: 52px;
    padding: 0 var(--space-3) 0 var(--space-4);
    display: flex;
    align-items: center;
    gap: var(--space-2);
    border-bottom: 1px solid var(--border-subtle);
  }
  body.ui-redesign .inbox-panel__title {
    flex: 1 1 auto;
    font-size: var(--text-base);
    font-weight: var(--weight-semibold);
    color: var(--text-primary);
    display: flex;
    align-items: baseline;
    gap: 8px;
    letter-spacing: -0.005em;
  }
  body.ui-redesign .inbox-panel__count {
    font-size: 11px;
    font-weight: var(--weight-semibold);
    color: var(--brand-primary-hover);
    background: var(--brand-primary-light);
    padding: 1px 7px;
    border-radius: var(--radius-full);
  }
  body.ui-redesign .inbox-panel__body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: var(--space-2) 0 var(--space-4);
  }
  body.ui-redesign .inbox-section { display: flex; flex-direction: column; }
  body.ui-redesign .inbox-section__heading {
    padding: var(--space-3) var(--space-4) 6px;
    font-size: 11px;
    font-weight: var(--weight-bold);
    color: var(--text-tertiary);
    letter-spacing: 0.05em;
    text-transform: uppercase;
  }
  body.ui-redesign .inbox-section--priority .inbox-section__heading {
    color: var(--status-rejected);
  }
  body.ui-redesign .inbox-row {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: 10px var(--space-4);
    cursor: pointer;
    text-align: left;
    background: transparent;
    border: 0;
    width: 100%;
    text-decoration: none;
    color: inherit;
  }
  body.ui-redesign .inbox-row:hover { background: var(--bg-surface-alt); }
  body.ui-redesign .inbox-row__icon {
    width: 28px;
    height: 28px;
    border-radius: 7px;
    background: var(--brand-primary-light);
    color: var(--brand-primary);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    margin-top: 1px;
  }
  body.ui-redesign .inbox-row__icon svg { width: 14px; height: 14px; stroke-width: 2; }
  body.ui-redesign .inbox-row__icon--alert {
    background: var(--status-rejected-bg);
    color: var(--status-rejected);
  }
  body.ui-redesign .inbox-row__body {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
  }
  body.ui-redesign .inbox-row__title {
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    color: var(--text-primary);
    line-height: 1.35;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  body.ui-redesign .inbox-row--unread .inbox-row__title {
    font-weight: var(--weight-semibold);
  }
  body.ui-redesign .inbox-row__sub {
    font-size: var(--text-xs);
    color: var(--text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  body.ui-redesign .inbox-row__sub--alert {
    color: var(--status-rejected-text);
    font-weight: var(--weight-semibold);
  }
  body.ui-redesign .inbox-row__meta {
    font-size: 11px;
    color: var(--text-tertiary);
    flex-shrink: 0;
    font-variant-numeric: tabular-nums;
    align-self: flex-start;
    margin-top: 3px;
    font-weight: var(--weight-medium);
  }
  body.ui-redesign .inbox-row--skeleton {
    height: 48px;
    background: var(--bg-surface-alt);
    margin: 6px var(--space-4);
    border-radius: var(--radius-sm);
    padding: 0;
    cursor: default;
    pointer-events: none;
    animation: ui-redesign-skeleton 1.4s ease-in-out infinite;
  }
  @keyframes ui-redesign-skeleton {
    0%, 100% { opacity: 0.6; }
    50% { opacity: 0.9; }
  }
  body.ui-redesign .inbox-empty {
    padding: var(--space-8) var(--space-6);
    text-align: center;
  }
  body.ui-redesign .inbox-empty__title {
    font-size: var(--text-base);
    font-weight: var(--weight-semibold);
    color: var(--text-primary);
    margin-bottom: var(--space-2);
  }
  body.ui-redesign .inbox-empty__sub {
    font-size: var(--text-sm);
    color: var(--text-secondary);
  }

  /* Phase 4 — Cmd-K palette overlay. Centred, 600px wide, sits above
     everything (z-index above the inbox panel + dropdowns). Hidden
     until <body data-cmdk-open="true">. */
  body.ui-redesign .cmdk-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.30);
    z-index: 200;
    opacity: 0;
    visibility: hidden;
    transition: opacity 120ms ease, visibility 0s linear 120ms;
  }
  body.ui-redesign[data-cmdk-open="true"] .cmdk-backdrop {
    opacity: 1;
    visibility: visible;
    transition: opacity 120ms ease;
  }
  body.ui-redesign .cmdk {
    position: fixed;
    top: 96px;
    left: 50%;
    transform: translateX(-50%) translateY(-8px);
    width: min(600px, calc(100vw - 32px));
    max-height: 540px;
    background: var(--bg-surface);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    z-index: 201;
    opacity: 0;
    pointer-events: none;
    transition: opacity 120ms ease, transform 140ms cubic-bezier(0.2, 0.8, 0.2, 1);
  }
  body.ui-redesign[data-cmdk-open="true"] .cmdk {
    opacity: 1;
    pointer-events: auto;
    transform: translateX(-50%) translateY(0);
  }

  body.ui-redesign .cmdk__search {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 14px var(--space-4);
    border-bottom: 1px solid var(--border-subtle);
  }
  body.ui-redesign .cmdk__search > svg {
    width: 16px;
    height: 16px;
    color: var(--text-tertiary);
    stroke-width: 2;
    flex-shrink: 0;
  }
  body.ui-redesign .cmdk__search input {
    flex: 1 1 auto;
    border: 0;
    background: transparent;
    outline: none;
    font-size: var(--text-base);
    color: var(--text-primary);
    letter-spacing: -0.005em;
  }
  body.ui-redesign .cmdk__search input::placeholder { color: var(--text-tertiary); }
  body.ui-redesign .cmdk__esc {
    background: var(--bg-surface-alt);
    border: 1px solid var(--border-subtle);
    border-bottom-width: 2px;
    border-radius: 3px;
    padding: 1px 5px;
    font-size: 10px;
    color: var(--text-tertiary);
    font-family: var(--font-mono);
  }

  body.ui-redesign .cmdk__results {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: var(--space-2) 0;
  }
  body.ui-redesign .cmdk__group { padding: 4px 0; }
  body.ui-redesign .cmdk__group-h {
    padding: 6px var(--space-4) 4px;
    font-size: 10px;
    font-weight: var(--weight-bold);
    color: var(--text-tertiary);
    letter-spacing: 0.06em;
    text-transform: uppercase;
  }
  body.ui-redesign .cmdk__row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: 8px var(--space-4);
    cursor: pointer;
    text-decoration: none;
    color: inherit;
  }
  body.ui-redesign .cmdk__row:hover { background: var(--bg-surface-alt); }
  body.ui-redesign .cmdk__row--active { background: var(--brand-primary-light); }
  body.ui-redesign .cmdk__icon {
    width: 22px;
    height: 22px;
    border-radius: 5px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-surface-alt);
    color: var(--text-secondary);
    flex-shrink: 0;
  }
  body.ui-redesign .cmdk__icon svg { width: 13px; height: 13px; stroke-width: 2; }
  body.ui-redesign .cmdk__icon--snag {
    background: var(--status-rejected-bg);
    color: var(--status-rejected);
  }
  body.ui-redesign .cmdk__icon--page {
    background: var(--brand-primary-light);
    color: var(--brand-primary);
  }
  body.ui-redesign .cmdk__title {
    flex: 1 1 auto;
    font-size: var(--text-sm);
    color: var(--text-primary);
    font-weight: var(--weight-medium);
    letter-spacing: -0.005em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  body.ui-redesign .cmdk__sub {
    font-size: var(--text-xs);
    color: var(--text-tertiary);
    flex-shrink: 0;
    margin-left: var(--space-2);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 200px;
  }
  body.ui-redesign .cmdk__empty {
    padding: var(--space-6) var(--space-4);
    text-align: center;
    color: var(--text-tertiary);
    font-size: var(--text-sm);
  }
}

/* Phase 3 (inbox slide-out) + Phase 4 (Cmd-K palette) chrome is
   desktop-only — all their `body.ui-redesign .*` rules sit inside the
   `@media (min-width: 1024px)` block above. On smaller viewports
   none of those rules match, so the unstyled divs render as default
   display:block and the unconstrained SVG icons explode to fill the
   container (the giant magnifying-glass user reported 2026-05-12).
   Force the containers to display:none at <1024px so the markup is
   still in the DOM (cmdk.js / inbox-panel.js are universal) but the
   palette stays out of sight on phones + tablets. */
@media (max-width: 1023px) {
  .cmdk,
  .cmdk-backdrop,
  .inbox-panel,
  .inbox-backdrop { display: none !important; }
}

/* Dark theme — adjust sidebar + topbar surfaces to match the rest of
   dark chrome. `--bg-surface` and `--text-*` flip in the dark palette
   already (see `[data-theme="dark"]` root block below), so most rules
   just inherit; only the brand-primary-light (active row background),
   the alert count chip, and the search trigger background need explicit
   dark variants. */
@media (min-width: 1024px) {
  [data-theme="dark"] body.ui-redesign .nav-item--active {
    background: rgba(15, 76, 129, 0.18);
  }
  [data-theme="dark"] body.ui-redesign .nav-item__count--alert {
    color: #fca5a5;
    background: rgba(220, 38, 38, 0.18);
  }
  [data-theme="dark"] body.ui-redesign .nav-item__soon-badge {
    background: rgba(255, 255, 255, 0.06);
    color: var(--text-tertiary);
  }
  /* Topbar */
  [data-theme="dark"] body.ui-redesign .topbar__search {
    background: rgba(255, 255, 255, 0.04);
    border-color: rgba(255, 255, 255, 0.08);
  }
  [data-theme="dark"] body.ui-redesign .topbar__search:hover {
    background: rgba(255, 255, 255, 0.08);
    border-color: rgba(255, 255, 255, 0.16);
  }
  [data-theme="dark"] body.ui-redesign .topbar__search-kbd kbd {
    background: rgba(255, 255, 255, 0.06);
    border-color: rgba(255, 255, 255, 0.10);
  }
}

/* Phase 0 — mobile chrome rules. Brand hidden on non-Home; page title
   visible. User menu hidden (mobile uses the drawer). */
@media (max-width: 1023px) {
  .app-header__brand { display: none; }
  .app-header__title { display: inline; }
  body.is-home .app-header__brand { display: inline; }
  body.is-home .app-header__title { display: none; }
  /* `display: none` here loses to .user-menu's later display: inline-flex
     in the source order without !important — the avatar leaked onto the
     mobile topbar before this fix (Phase 0.5 v15 fidelity follow-up). */
  .user-menu { display: none !important; }
  /* Page title moves into the chrome bar; the body-side `.page-title`
     output of _page_header would duplicate it. Hide it here; subtitle +
     actions remain visible as the user expects. */
  .page-header .page-title { display: none; }
}

/* ============================================================================
 * Phase 0.5 v15 fidelity — mobile-only overrides (max-width: 767px).
 *
 * The Phase 0.5 PR B shipped functional /inbox + /more pages but reused
 * the existing `.app-header` (brand + bell + cog + avatar) and edge-to-edge
 * `.bottom-nav` rectangle. The GOLD v15 mockup specifies a single-action
 * topbar (page title + bell only) and a floating rounded-pill bottom nav.
 * This block makes the rendered chrome match the spec on mobile while
 * leaving desktop pixel-identical (every selector is inside the mobile
 * breakpoint).
 * ============================================================================ */

@media (max-width: 767px) {
  /* Topbar — title left, single action right. Brand never shows on
     mobile (home now sets app_header_title to "Home" via Jinja block).
     The title takes the freed space. */
  .app-header {
    padding: 0 var(--space-2) 0 var(--space-4);
  }
  .app-header__brand,
  body.is-home .app-header__brand {
    display: none;
  }
  .app-header__title,
  body.is-home .app-header__title {
    display: inline;
    flex: 1 1 auto;
    font-size: var(--text-lg, 1.125rem);
    font-weight: var(--weight-semibold);
    letter-spacing: -0.014em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  /* Single icon button on the right (the bell). Square 40×40, rounded
     corners, transparent until pressed. Matches v15 .topbar__icon. */
  .app-header__actions .btn-icon {
    width: 40px;
    height: 40px;
    padding: 0;
    border-radius: var(--radius-md, 12px);
  }
  .app-header__actions .btn-icon svg {
    width: 19px;
    height: 19px;
    stroke-width: 2;
  }
  /* Bell badge — gold pill in the top-right corner of the icon. The
     mobile inbox anchor uses `.topbar__icon-badge` per v15 (matches the
     mockup class name); the legacy `.sync-badge` selector is kept for
     any other mobile chrome that still relies on it. */
  .app-header__actions .btn-icon .sync-badge,
  .app-header__actions .btn-icon .topbar__icon-badge {
    position: absolute;
    top: 4px;
    right: 4px;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    border-radius: var(--radius-full, 999px);
    background: #ffaa00;
    color: #1a1a1a;
    font-size: 10px;
    font-weight: var(--weight-semibold);
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1.5px solid var(--brand-primary);
  }
  .app-header__actions .btn-icon .topbar__icon-badge.is-hidden {
    display: none;
  }

  /* Bottom nav — floating rounded pill, centered, with a soft gradient
     fade so content scrolls under cleanly. */
  .bottom-nav {
    --bottom-nav-h: 76px;
    height: calc(var(--bottom-nav-h) + env(safe-area-inset-bottom, 12px));
    padding: var(--space-2) var(--space-3) calc(var(--space-3) + env(safe-area-inset-bottom, 12px));
    background: linear-gradient(to top, var(--bg-app) 60%, transparent);
    border-top: 0;
    display: flex;
    justify-content: center;
    align-items: flex-start;
    pointer-events: none;
  }
  .bottom-nav__inner {
    pointer-events: auto;
    /* v15 — pill stretches to the parent .bottom-nav width (which has
       its own var(--space-3) horizontal padding). Items grow with
       `flex: 1` to share the available width evenly so the pill no
       longer hugs its content and floats narrowly in the middle. */
    flex: 1 1 auto;
    display: flex;
    align-items: stretch;
    gap: 0;
    padding: 4px;
    background: var(--bg-surface);
    border-radius: var(--radius-full, 999px);
    border: 1px solid var(--border-subtle);
    box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06), 0 8px 24px rgba(15, 23, 42, 0.06);
  }
  .bottom-nav__item {
    /* `flex: 1 1 auto` — items size to content first (so a two-word
       label like "Punch List" stays on one line) and then share any
       remaining width equally so the pill stretches. `flex: 1 1 0`
       forced equal slots and wrapped longer labels. `min-width: 56px`
       keeps tap targets compliant. */
    flex: 1 1 auto;
    min-width: 56px;
    height: 50px;
    padding: 0 var(--space-3);
    border-radius: var(--radius-full, 999px);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    color: var(--text-tertiary);
    background: transparent;
    border: 0;
    font-size: 10px;
    font-weight: var(--weight-medium);
    text-decoration: none;
    position: relative;
    transition: color 100ms;
    white-space: nowrap;
  }
  .bottom-nav__item svg {
    width: 22px;
    height: 22px;
    stroke-width: 2;
    margin-bottom: 0;
  }
  .bottom-nav__item--active {
    color: var(--brand-primary);
    font-weight: var(--weight-semibold);
  }
  /* Push main content above the floating bottom-nav so the last row is
     never hidden under the pill. */
  .app-content {
    padding-bottom: calc(96px + env(safe-area-inset-bottom, 12px));
  }

  /* Dark-theme tweaks per v15 — softer accent on dark surfaces. */
  [data-theme="dark"] .bottom-nav__inner {
    border-color: var(--border-card, #2d333b);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.4);
  }
  [data-theme="dark"] .bottom-nav__item--active {
    color: #93c5fd;
  }
}


/* ========== Login Page ========== */
.login-page {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100dvh;
  padding: var(--space-8) var(--space-4);
  background: var(--bg-app);
}

.login-card {
  width: 100%;
  max-width: 400px;
  background: var(--bg-surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  padding: var(--space-8);
}

.login-card__logo {
  text-align: center;
  margin-bottom: var(--space-6);
}

.login-card__logo h1 {
  font-size: var(--text-2xl);
  font-weight: var(--weight-bold);
  color: var(--brand-primary);
}

.login-card__footer {
  margin-top: var(--space-6);
  text-align: center;
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

/* ========== Stats Cards (Dashboard) ========== */
.stats-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
}

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

.stat-card__value {
  font-size: var(--text-3xl);
  font-weight: var(--weight-semibold);
  color: var(--brand-primary);
  line-height: var(--leading-tight);
}

.stat-card__label {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  margin-top: var(--space-1);
}

@media (min-width: 768px) {
  .stats-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1024px) {
  .stats-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* ========== Action Cards (shared across home, more, modules) ========== */

.action-card {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--space-3);
  padding: var(--space-4);
  text-decoration: none;
  color: var(--text-primary);
  transition: border-color 150ms, box-shadow 150ms;
  min-height: 64px;
}

.action-card:hover {
  border-color: var(--brand-primary);
  box-shadow: 0 2px 8px rgba(15, 76, 129, 0.08);
}

.action-card__icon {
  width: 40px;
  height: 40px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  background: var(--brand-primary-light);
  color: var(--brand-primary);
}

.action-card__icon svg {
  width: 20px;
  height: 20px;
}

.action-card__text {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
}

.action-card__sub {
  font-size: var(--text-xs);
  color: var(--text-secondary);
  font-weight: var(--weight-normal);
  margin-top: 2px;
}

/* The mobile bottom-sheet drawer (`.more-drawer*` rules) was retired in
   the Phase 0.5 mobile chrome rework — the standalone /more route
   replaces it. See app/web/templates/more.html and pages/more.css. */

/* ========== Pull-to-refresh indicator ========== */
.pull-refresh-indicator {
  position: fixed;
  top: calc(var(--header-height) + var(--space-2));
  left: 50%;
  transform: translate(-50%, 0);
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-surface);
  color: var(--brand-primary);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-full);
  box-shadow: var(--shadow-sm);
  opacity: 0;
  pointer-events: none;
  z-index: 90;
  transition: opacity 100ms;
}
.pull-refresh-indicator.is-armed {
  background: var(--brand-primary);
  color: var(--text-inverse);
  border-color: var(--brand-primary);
}
.pull-refresh-indicator.is-firing svg {
  animation: spin 800ms linear infinite;
}
@keyframes spin {
  to { transform: rotate(360deg); }
}

/* ========== Filter row → first card spacing ========== */
/* Phase 0 mobile shell rework — visible breathing room between the filter
   row and the list/cards/grid below it. Token-based, dark-mode-safe. */
.filter-bar + .card-list,
.filter-bar + .table-list,
.filter-bar + .kpi-grid,
.filter-bar + .status-grid {
  margin-top: var(--space-4);
}
.filter-bar {
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border-subtle);
  margin-bottom: var(--space-4);
}

/* ========== Site Photos ========== */

/* Audit #42 — `.field-input--lg` removed; `.field-input` is now 60 px min-height
   universally so the modifier was dead code. */

/* Field-screen primary button: 60px+ height */
.btn-field {
  min-height: 60px;
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
}

.upload-fields {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

/* Photo capture buttons */
.photo-capture-zone {
  margin: var(--space-3) 0 var(--space-4);
}

.photo-capture-buttons {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
}

.photo-capture-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-3);
  background: var(--brand-primary-light);
  border: 1px solid var(--brand-primary);
  border-radius: var(--space-3);
  cursor: pointer;
  min-height: 64px;
  transition: background 150ms, box-shadow 150ms;
  font-family: inherit;
  color: var(--brand-primary);
  -webkit-tap-highlight-color: transparent;
  outline: none;
}

.photo-capture-btn:hover,
.photo-capture-btn:active {
  background: #d4e3f3;
  box-shadow: 0 2px 8px rgba(15, 76, 129, 0.1);
}

.photo-capture-btn--secondary {
  background: var(--bg-surface);
  border-color: var(--border-subtle);
  color: var(--text-primary);
}

.photo-capture-btn--secondary:hover {
  background: var(--bg-surface-alt);
  border-color: var(--brand-primary);
}

.photo-capture-btn__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.photo-capture-btn__icon svg {
  width: 24px;
  height: 24px;
}

.photo-capture-btn__icon--secondary svg {
  stroke: var(--text-secondary);
}

.photo-capture-btn__text {
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
}

/* Full-screen camera overlay */
.camera-overlay {
  position: fixed;
  inset: 0;
  z-index: 300;
  background: #000;
  display: flex;
  flex-direction: column;
}

.camera-overlay video {
  flex: 1;
  width: 100%;
  object-fit: cover;
}

.camera-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-8);
  padding: var(--space-4) var(--space-6) calc(var(--space-6) + env(safe-area-inset-bottom));
  background: rgba(0, 0, 0, 0.7);
  position: relative;
}

.camera-count-badge {
  position: absolute;
  left: var(--space-4);
  top: var(--space-3);
  background: var(--brand-accent);
  color: white;
  padding: var(--space-1) var(--space-3);
  border-radius: 20px;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
}

.camera-right-controls {
  position: absolute;
  right: var(--space-4);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
}

.camera-done-btn {
  background: var(--brand-accent);
  color: white;
  border: none;
  border-radius: var(--space-2);
  padding: var(--space-3) var(--space-5);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  font-family: inherit;
  cursor: pointer;
  min-height: 48px;
  min-width: 80px;
}

.camera-overlay button,
.camera-overlay label {
  -webkit-tap-highlight-color: transparent;
  outline: none;
}

.camera-shutter {
  width: 72px;
  height: 72px;
  border-radius: 50%;
  border: 4px solid white;
  background: transparent;
  cursor: pointer;
  position: relative;
  flex-shrink: 0;
}

.camera-shutter::after {
  content: '';
  position: absolute;
  inset: 4px;
  border-radius: 50%;
  background: white;
  transition: transform 100ms;
}

.camera-shutter:active::after {
  transform: scale(0.85);
}

.camera-ctrl-btn {
  min-width: 64px;
  min-height: 44px;
  background: none;
  border: none;
  color: white;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  font-family: inherit;
}

.camera-ctrl-btn--done {
  background: var(--brand-primary);
  border-radius: var(--space-2);
  padding: var(--space-2) var(--space-4);
}

.camera-flash {
  position: absolute;
  inset: 0;
  background: white;
  opacity: 0;
  pointer-events: none;
  transition: opacity 80ms;
}

.camera-flash.flash {
  opacity: 0.6;
}

/* Batch preview grid */
.photo-previews {
  margin-top: var(--space-3);
}

.photo-previews__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-2);
}

.photo-previews__item {
  position: relative;
  aspect-ratio: 1;
  border-radius: var(--space-2);
  overflow: hidden;
  background: var(--bg-surface-alt);
}

.photo-previews__item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.photo-previews__remove {
  position: absolute;
  top: 0;
  right: 0;
  width: 44px;
  height: 44px;
  border-radius: 0 var(--space-2) 0 var(--space-2);
  background: rgba(0, 0, 0, 0.65);
  color: white;
  border: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  font-weight: bold;
  line-height: 1;
  -webkit-tap-highlight-color: transparent;
}

.photo-previews__clear-all {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  margin-top: var(--space-2);
  padding: var(--space-2);
  font-size: var(--text-xs);
  color: var(--status-rejected);
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
  font-weight: var(--weight-medium);
}

.photo-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-2);
}

.photo-grid--strip {
  grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
}

.photo-thumb {
  position: relative;
  aspect-ratio: 1;
  overflow: hidden;
  border-radius: var(--space-2);
  cursor: pointer;
  background: var(--bg-surface-alt);
}

.photo-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 150ms;
}

.photo-thumb:hover img {
  transform: scale(1.05);
}

/* Audit #46 — was a 10 × 10 colour-only dot, which is unreadable for any of the
   ~8 % of male users with red/green colour-blindness. Now a 16 × 16 pill with
   a white glyph centred inside; colour stays primary but is now redundant with
   the shape (WCAG 1.4.1 Use of Color). The dot grows by 6 px but the corner
   placement on a photo thumbnail still reads as a status indicator, not a
   button. */
.status-dot {
  position: absolute;
  top: 6px;
  right: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid var(--bg-surface);
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  color: var(--text-inverse);
}

.status-dot--pending,
.status-dot--processing {
  background: var(--status-pending);
}
.status-dot--pending::before,
.status-dot--processing::before {
  content: "\2191"; /* ↑ — upload in progress */
}

.status-dot--uploaded {
  background: var(--status-approved);
}
.status-dot--uploaded::before {
  content: "\2713"; /* ✓ — uploaded */
}

.status-dot--failed {
  background: var(--status-rejected);
}
.status-dot--failed::before {
  content: "!";
}

.status-dot--duplicate {
  background: var(--status-neutral);
}
.status-dot--duplicate::before {
  content: "="; /* = — duplicate of an already-uploaded photo */
}

.photo-date-group {
  margin-bottom: var(--space-6);
}

.photo-date-header {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--text-secondary);
  margin-bottom: var(--space-3);
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.photo-count {
  font-weight: var(--weight-normal);
  color: var(--text-tertiary);
  font-size: var(--text-xs);
}

/* Filter drawer */
.filter-row {
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.filter-row .field--compact {
  flex: 1;
  min-width: 120px;
}

/* Lightbox */
.lightbox-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.92);
  z-index: 200;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}

.lightbox-close {
  position: absolute;
  top: max(var(--space-4), env(safe-area-inset-top));
  right: var(--space-4);
  background: none;
  border: none;
  color: white;
  cursor: pointer;
  min-width: 44px;
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 201;
}

.lightbox-body {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  max-width: 100vw;
  flex: 1;
  position: relative;
}

.lightbox-img {
  max-width: calc(100vw - 120px);
  max-height: calc(100dvh - 160px);
  object-fit: contain;
  border-radius: var(--space-2);
}

.lightbox-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  background: rgba(255, 255, 255, 0.15);
  border: none;
  color: white;
  cursor: pointer;
  min-width: 44px;
  min-height: 44px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 150ms;
}

.lightbox-nav:hover {
  background: rgba(255, 255, 255, 0.3);
}

.lightbox-nav--prev { left: var(--space-3); }
.lightbox-nav--next { right: var(--space-3); }

.lightbox-info {
  color: white;
  text-align: center;
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

/* (Old `.empty-state`, `.empty-state svg`, `.empty-state p`,
   `.empty-state--compact` rules removed during the cross-module UI
   unification — the canonical block now lives upstream in this file.
   Photos field-side empty state and snags field-side empty state both
   use the canonical block; layout's flex column + gap handles spacing
   between the illustration SVG and the body copy.) */

@media (max-width: 767px) {
  .lightbox-img {
    max-width: calc(100vw - 16px);
    max-height: calc(100dvh - 140px);
  }
  .lightbox-nav--prev { left: var(--space-1); }
  .lightbox-nav--next { right: var(--space-1); }
}

/* ========== Admin Desktop Dashboard ========== */

.admin-page { max-width: none; }
@media (min-width: 1024px) {
  .admin-page { max-width: 1280px; }
}

.admin-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-6);
  flex-wrap: wrap;
  gap: var(--space-3);
}
.admin-header h1 {
  font-size: var(--text-2xl);
  font-weight: var(--weight-semibold);
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  color: var(--text-primary);
}
.admin-header h1 svg { color: var(--brand-primary); }
.admin-header__actions {
  display: flex;
  gap: var(--space-2);
  align-items: center;
}

/* Tabs (Procore Logs–style underline tabs) */
.admin-tabs {
  display: flex;
  gap: var(--space-1);
  border-bottom: 1px solid var(--border-subtle);
  margin: calc(var(--space-6) * -1 + var(--space-2)) 0 var(--space-5) 0;
  overflow-x: auto;
  scrollbar-width: none;
}
.admin-tabs::-webkit-scrollbar { display: none; }
.admin-tabs__tab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text-secondary);
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  text-decoration: none;
  white-space: nowrap;
  transition: color 150ms, border-color 150ms;
  min-height: 44px;
}
.admin-tabs__tab svg { color: currentColor; flex-shrink: 0; }
.admin-tabs__tab:hover {
  color: var(--text-primary);
  border-bottom-color: var(--border-subtle);
}
.admin-tabs__tab--active,
.admin-tabs__tab--active:hover {
  color: var(--brand-primary);
  border-bottom-color: var(--brand-primary);
}

/* KPI Cards */
.admin-kpi {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}
@media (min-width: 768px) {
  .admin-kpi { grid-template-columns: repeat(4, 1fr); }
}
.admin-kpi__card {
  background: var(--bg-surface);
  border-radius: var(--radius-lg);
  border: 1px solid var(--border-subtle);
  padding: var(--space-4);
  box-shadow: var(--shadow-sm);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  transition: box-shadow 150ms;
}
.admin-kpi__card:hover { box-shadow: var(--shadow-md); }
.admin-kpi__icon {
  width: 44px;
  height: 44px;
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.admin-kpi__icon svg { width: 22px; height: 22px; }
.admin-kpi__icon--blue { background: var(--status-info-bg); color: var(--status-info); }
.admin-kpi__icon--red { background: var(--status-rejected-bg); color: var(--status-rejected); }
.admin-kpi__icon--green { background: var(--status-approved-bg); color: var(--status-approved); }
.admin-kpi__icon--amber { background: var(--status-pending-bg); color: #92400e; }
.admin-kpi__value {
  font-size: var(--text-xl);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
  line-height: var(--leading-tight);
}
.admin-kpi__label {
  font-size: var(--text-xs);
  color: var(--text-secondary);
  font-weight: var(--weight-medium);
}

/* Activity Heatmap */
.admin-heatmap {
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  margin-bottom: var(--space-5);
  box-shadow: var(--shadow-sm);
  overflow-x: auto;
}
.admin-heatmap__title {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
  margin-bottom: var(--space-3);
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.admin-heatmap__grid {
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: 14px repeat(7, 13px);
  gap: 2px;
  align-items: center;
}
.admin-heatmap__month {
  font-size: 0.65rem;
  color: var(--text-tertiary);
  grid-row: 1;
  white-space: nowrap;
  line-height: 1;
}
.admin-heatmap__dow {
  font-size: 0.62rem;
  color: var(--text-tertiary);
  grid-column: 1;
  text-align: right;
  padding-right: 4px;
  line-height: 1;
}
.admin-heatmap__cell {
  width: 13px;
  height: 13px;
  border-radius: 2px;
  background: var(--bg-surface-alt);
  cursor: pointer;
  transition: outline-color 100ms;
}
.admin-heatmap__cell:hover { outline: 1px solid var(--brand-primary); outline-offset: 1px; }
.admin-heatmap__cell--empty { background: transparent; cursor: default; }
.admin-heatmap__cell--empty:hover { outline: none; }
.admin-heatmap__cell--l1 { background: #dbeafe; }
.admin-heatmap__cell--l2 { background: #93c5fd; }
.admin-heatmap__cell--l3 { background: #3b82f6; }
.admin-heatmap__cell--l4 { background: var(--brand-primary); }
.admin-heatmap__cell--disrupted { box-shadow: inset 0 0 0 2px var(--status-rejected); }
.admin-heatmap__legend {
  display: flex;
  align-items: center;
  gap: 3px;
  margin-top: var(--space-3);
  font-size: 0.7rem;
  color: var(--text-tertiary);
  flex-wrap: wrap;
}
.admin-heatmap__legend-label { margin: 0 var(--space-1); }

/* Toolbar */
.admin-toolbar {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-bottom: var(--space-4);
  align-items: flex-end;
}
.admin-toolbar .field { margin-bottom: 0; flex: 0 0 auto; }
.admin-toolbar .field-label {
  font-size: var(--text-xs);
  margin-bottom: 2px;
  font-weight: var(--weight-medium);
  color: var(--text-secondary);
}
.admin-toolbar__search {
  flex: 1 1 200px;
  min-width: 160px;
  position: relative;
}
.admin-toolbar__search input {
  width: 100%;
  height: 40px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  padding: 0 var(--space-3) 0 36px;
  font-size: var(--text-sm);
  font-family: var(--font-sans);
  background: var(--bg-surface);
  transition: border-color 150ms;
}
.admin-toolbar__search input:focus {
  outline: none;
  border-color: var(--brand-primary);
  box-shadow: 0 0 0 3px rgba(15, 76, 129, 0.1);
}
.admin-toolbar__search svg,
.admin-toolbar__search-icon {
  position: absolute;
  left: 10px;
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  color: var(--text-tertiary);
  pointer-events: none;
}
.sort-icon {
  vertical-align: middle;
  opacity: 0.4;
}
.admin-toolbar__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-left: auto;
}
.admin-toolbar__filter {
  height: 40px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  padding: 0 var(--space-3);
  font-size: var(--text-sm);
  font-family: var(--font-sans);
  background: var(--bg-surface);
}
/* Task 5: drop-down labels + selected value render in caps for consistency
   with the uppercase column headers (PROJECT / STATUS / PRIORITY). */
.admin-toolbar__filter { text-transform: uppercase; letter-spacing: 0.03em; }
.admin-toolbar__filter option { text-transform: uppercase; letter-spacing: 0.03em; }
.admin-toolbar select,
.admin-toolbar input[type="date"] {
  height: 40px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  padding: 0 var(--space-3);
  font-size: var(--text-sm);
  font-family: var(--font-sans);
  background: var(--bg-surface);
  min-width: 120px;
  transition: border-color 150ms;
}
.admin-toolbar select {
  appearance: none;
  padding-right: var(--space-8);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23475569' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
}
.admin-toolbar select:focus,
.admin-toolbar input[type="date"]:focus {
  outline: none;
  border-color: var(--brand-primary);
}

/* Data Table */
.admin-table-wrap {
  background: var(--bg-surface);
  border-radius: var(--radius-lg);
  border: 1px solid var(--border-subtle);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
.admin-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}
.admin-table thead {
  position: sticky;
  top: 0;
  z-index: 2;
}
.admin-table th {
  background: var(--bg-surface-alt);
  font-weight: var(--weight-semibold);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-secondary);
  padding: var(--space-3) var(--space-4);
  text-align: left;
  border-bottom: 1px solid var(--border-subtle);
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  transition: color 150ms;
}
.admin-table th:hover { color: var(--brand-primary); }
.admin-table th.sort-asc::after { content: " ▲"; font-size: 0.65em; }
.admin-table th.sort-desc::after { content: " ▼"; font-size: 0.65em; }
.admin-table th:first-child { width: 40px; cursor: default; }
.admin-table th:first-child:hover { color: var(--text-secondary); }
.admin-table td {
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border-subtle);
  color: var(--text-primary);
  vertical-align: middle;
}
.admin-table tr { transition: background-color 100ms; }
.admin-table tbody tr:nth-child(even) { background: var(--bg-surface); }
.admin-table tbody tr:hover { background: var(--bg-surface-alt); cursor: pointer; }
.admin-table tbody tr.selected { background: var(--brand-primary-light); }
.admin-table .col-check { width: 40px; text-align: center; }
.admin-table .col-check input[type="checkbox"] {
  width: 16px;
  height: 16px;
  cursor: pointer;
  accent-color: var(--brand-primary);
}
.admin-table .col-date { white-space: nowrap; font-weight: var(--weight-medium); }
.admin-table .col-num { text-align: right; font-variant-numeric: tabular-nums; }
.admin-table .col-actions { text-align: right; white-space: nowrap; }
.admin-table .disrupted-cell { color: var(--status-rejected); font-weight: var(--weight-semibold); }

/* Table pagination */
.admin-pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm);
  color: var(--text-secondary);
  border-top: 1px solid var(--border-subtle);
  background: var(--bg-surface);
}
.admin-pagination__buttons { display: flex; gap: var(--space-2); }

/* Side Panel */
.admin-panel-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 200;
}
.admin-panel-overlay.open { display: flex; }
.admin-panel-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.3);
}
.admin-panel {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 480px;
  max-width: 100vw;
  background: var(--bg-surface);
  border-left: 1px solid var(--border-subtle);
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  animation: panelSlideIn 200ms ease-out;
  z-index: 1;
}
@keyframes panelSlideIn {
  from { transform: translateX(100%); }
  to { transform: translateX(0); }
}
.admin-panel__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border-subtle);
  flex-shrink: 0;
}
.admin-panel__header h2 {
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
  color: var(--text-primary);
  margin: 0;
}
.admin-panel__nav {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.admin-panel__nav button {
  width: 32px;
  height: 32px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  background: var(--bg-surface);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-secondary);
  transition: background 150ms, color 150ms;
}
.admin-panel__nav button:hover { background: var(--bg-surface-alt); color: var(--text-primary); }
.admin-panel__close {
  width: 32px;
  height: 32px;
  border: none;
  background: none;
  cursor: pointer;
  color: var(--text-secondary);
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-md);
  transition: background 150ms;
}
.admin-panel__close:hover { background: var(--bg-surface-alt); }
.admin-panel__body {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-5);
}
.admin-panel__footer {
  padding: var(--space-4) var(--space-5);
  border-top: 1px solid var(--border-subtle);
  display: flex;
  gap: var(--space-2);
  flex-shrink: 0;
}
.admin-panel__section {
  margin-bottom: var(--space-4);
}
.admin-panel__section-header {
  /* Audit #51 — collapsible variant is now a <button> (keyboard / SR friendly).
     Static section headers are still <div>s. The same class applies to both;
     these resets only matter when the element is a button. */
  width: 100%;
  border: none;
  font-family: inherit;
  text-align: left;
  padding: var(--space-2) var(--space-3);
  font-weight: var(--weight-semibold);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--brand-primary);
  background: var(--brand-primary-light);
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  gap: var(--space-2);
  cursor: pointer;
  user-select: none;
  margin-bottom: var(--space-2);
}
.admin-panel__section-header svg { width: 16px; height: 16px; flex-shrink: 0; }
.admin-panel__section-header .arrow {
  margin-left: auto;
  transition: transform 200ms;
  font-size: 0.7em;
  color: var(--text-tertiary);
}
.admin-panel__section-header.collapsed .arrow { transform: rotate(-90deg); }
.admin-panel__section-body { font-size: var(--text-sm); }
.admin-panel__row {
  display: flex;
  gap: var(--space-3);
  margin-bottom: var(--space-1);
  align-items: baseline;
}
.admin-panel__row-label {
  color: var(--text-secondary);
  min-width: 90px;
  flex-shrink: 0;
  font-weight: var(--weight-medium);
  font-size: var(--text-xs);
}
.admin-panel__row-value { color: var(--text-primary); }
.admin-panel__crew-card {
  background: var(--bg-surface-alt);
  border: 1px solid var(--border-subtle);
  border-left: 3px solid var(--brand-primary);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  margin-bottom: var(--space-2);
}
.admin-panel__crew-title {
  font-weight: var(--weight-semibold);
  font-size: var(--text-sm);
  margin-bottom: var(--space-1);
}
.admin-panel__crew-meta {
  font-size: var(--text-xs);
  color: var(--text-secondary);
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-1);
}
.admin-panel__crew-activities {
  font-size: var(--text-xs);
  padding-left: var(--space-4);
  color: var(--text-primary);
}
.admin-panel__crew-activities li { margin-bottom: 1px; }

/* Bulk Action Bar */
.admin-bulk-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  background: var(--brand-primary);
  color: var(--text-inverse);
  padding: var(--space-3) var(--space-5);
  display: none;
  align-items: center;
  justify-content: space-between;
  z-index: 150;
  box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.15);
  animation: slideUp 200ms ease-out;
}
.admin-bulk-bar.visible { display: flex; }
@media (min-width: 1024px) {
  .admin-bulk-bar { left: var(--sidebar-width); }
}
.admin-bulk-bar__count {
  font-weight: var(--weight-semibold);
  font-size: var(--text-sm);
}
.admin-bulk-bar__actions { display: flex; gap: var(--space-2); }
.admin-bulk-bar .btn {
  background: rgba(255, 255, 255, 0.15);
  color: var(--text-inverse);
  border: 1px solid rgba(255, 255, 255, 0.25);
}
.admin-bulk-bar .btn:hover { background: rgba(255, 255, 255, 0.25); }

/* Empty state */
.admin-empty {
  text-align: center;
  padding: var(--space-12) var(--space-4);
  color: var(--text-secondary);
}
.admin-empty__icon {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background: var(--bg-surface-alt);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-bottom: var(--space-4);
  color: var(--text-tertiary);
}
.admin-empty__title {
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
  margin-bottom: var(--space-2);
}
.admin-empty__text {
  font-size: var(--text-sm);
  max-width: 320px;
  margin: 0 auto;
}

/* Desktop-only table, mobile cards */
.admin-mobile-list { display: block; }
.admin-table-wrap { display: none; }
@media (min-width: 1024px) {
  .admin-mobile-list { display: none; }
  .admin-table-wrap { display: block; }
}
.admin-mobile-card {
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  margin-bottom: var(--space-3);
  text-decoration: none;
  color: var(--text-primary);
  display: block;
  transition: border-color 150ms, box-shadow 150ms;
  border-left: 4px solid var(--status-approved);
}
.admin-mobile-card:hover {
  border-color: var(--brand-primary);
  box-shadow: var(--shadow-md);
}
.admin-mobile-card--disrupted { border-left-color: var(--status-rejected); }
.admin-mobile-card__date {
  font-weight: var(--weight-semibold);
  margin-bottom: var(--space-1);
}
.admin-mobile-card__meta {
  font-size: var(--text-xs);
  color: var(--text-secondary);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

/* Export button */
.admin-export {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: 40px;
  padding: 0 var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  background: var(--bg-surface);
  color: var(--text-primary);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  font-family: var(--font-sans);
  cursor: pointer;
  transition: border-color 150ms, box-shadow 150ms;
}
.admin-export:hover {
  border-color: var(--brand-primary);
  box-shadow: 0 1px 3px rgba(15, 76, 129, 0.1);
}
.admin-export svg { width: 16px; height: 16px; }

/* Mobile card list (admin pages: users, projects, audit) */
.admin-cards { display: block; }
@media (min-width: 1024px) {
  .admin-cards { display: none; }
}

/* Dashboard KPI grid container */
.admin-kpi-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}
@media (min-width: 768px) {
  .admin-kpi-grid { grid-template-columns: repeat(4, 1fr); }
}
.admin-kpi-grid > .admin-kpi {
  background: var(--bg-surface);
  border-radius: var(--radius-lg);
  border: 1px solid var(--border-subtle);
  padding: var(--space-4);
  box-shadow: var(--shadow-sm);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  transition: box-shadow 150ms;
}
.admin-kpi-grid > .admin-kpi:hover { box-shadow: var(--shadow-md); }
.admin-kpi__sub {
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  margin-top: 2px;
}

/* Dashboard two-column grid (pending + activity) */
.admin-dash-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}
@media (min-width: 768px) {
  .admin-dash-grid { grid-template-columns: 1fr 1fr; }
}

/* Pending / activity cards */
.admin-pending {
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
.admin-pending__header {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-4);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
  border-bottom: 1px solid var(--border-subtle);
}
.admin-pending__header svg { flex-shrink: 0; }
.admin-pending__item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  text-decoration: none;
  color: var(--text-primary);
  border-bottom: 1px solid var(--border-subtle);
  transition: background 150ms;
  font-size: var(--text-sm);
}
.admin-pending__item:last-child { border-bottom: none; }
.admin-pending__item:hover { background: var(--bg-surface-alt); }
.admin-pending__item-icon {
  width: 32px;
  height: 32px;
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.admin-pending__item-icon svg { width: 16px; height: 16px; }
.admin-pending__item-text { flex: 1; }
.admin-pending__item-count {
  font-weight: var(--weight-semibold);
  color: var(--text-secondary);
  font-size: var(--text-xs);
  background: var(--bg-surface-alt);
  padding: 2px 8px;
  border-radius: var(--radius-full);
}
.admin-pending__empty {
  padding: var(--space-5);
  text-align: center;
  font-size: var(--text-sm);
  color: var(--text-tertiary);
}

/* Activity timeline */
.admin-timeline {
  list-style: none;
  margin: 0;
  padding: 0;
}
.admin-timeline__item {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--border-subtle);
}
.admin-timeline__item:last-child { border-bottom: none; }
.admin-timeline__avatar {
  width: 32px;
  height: 32px;
  border-radius: var(--radius-full);
  background: var(--brand-primary-light);
  color: var(--brand-primary);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  flex-shrink: 0;
}
.admin-timeline__content { flex: 1; min-width: 0; }
.admin-timeline__text {
  font-size: var(--text-sm);
  color: var(--text-primary);
  line-height: var(--leading-normal);
}
.admin-timeline__time {
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  margin-top: 2px;
}

/* Quick actions grid */
.quick-actions {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-3);
}
@media (min-width: 768px) {
  .quick-actions { grid-template-columns: repeat(4, 1fr); }
}

/* Panel open state (BEM variant) */
.admin-panel-overlay.admin-panel--open { display: flex; }

/* Bulk bar visible state (BEM variant) */
.admin-bulk-bar.admin-bulk-bar--visible { display: flex; }

/* Filter toggle button (mobile only) */
.admin-filter-toggle {
  display: none;
  align-items: center;
  gap: var(--space-1);
  height: 36px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  background: var(--bg-surface);
  color: var(--text-primary);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  font-family: var(--font-sans);
  cursor: pointer;
  margin-bottom: var(--space-3);
}
.admin-filter-toggle svg { width: 16px; height: 16px; flex-shrink: 0; }

/* ---------------------------------------------------------------------------
   Shared queue-page primitives — title / subtitle / summary chip / card
   list used by /admin/mr/{review,price,transfer,release,deliveries}.
   One source of truth so the five pages render with identical rhythm
   (Task 3 — uniformity across the MR queue pages).
   --------------------------------------------------------------------------- */
.queue-title-row {
  display: flex; align-items: flex-end; justify-content: space-between;
  flex-wrap: wrap; gap: var(--space-3); margin-bottom: var(--space-4);
}
.queue-title-row h1 { margin: 0 0 4px 0; }
.queue-subtitle { color: var(--text-secondary); font-size: var(--text-sm); }
.queue-summary {
  font-size: var(--text-sm); font-weight: var(--weight-semibold);
  color: var(--brand-primary); background: var(--brand-primary-light);
  padding: 6px 12px; border-radius: var(--radius-md);
  white-space: nowrap;
}
.queue-summary--alert { color: var(--status-rejected); background: var(--status-rejected-bg); }

/* Mobile card layout — same padding, spacing, typography for every queue. */
.queue-mobile-cards { display: none; }
@media (max-width: 767px) {
  .admin-table-wrap { display: none; }
  .queue-mobile-cards { display: block; }
}
.queue-card {
  background: var(--bg-surface); border-radius: var(--radius-lg);
  padding: var(--space-3) var(--space-4); margin-bottom: var(--space-2);
  box-shadow: var(--shadow-sm); display: block;
  text-decoration: none; color: inherit;
}
.queue-card__top {
  display: flex; justify-content: space-between;
  align-items: flex-start; gap: var(--space-2);
}
.queue-card__ref { font-weight: var(--weight-bold); font-size: var(--text-base); }
.queue-card__meta { font-size: 13px; color: var(--text-secondary); margin-top: 2px; }
.queue-card__sub { font-size: 12px; color: var(--text-tertiary); margin-top: 4px; }
.queue-card__amount { color: var(--brand-primary); font-weight: var(--weight-bold); font-variant-numeric: tabular-nums; white-space: nowrap; }
.queue-card__actions { display: flex; gap: var(--space-2); margin-top: var(--space-3); }
.queue-card__actions .row-action-btn { flex: 1; justify-content: center; min-height: 44px; }
.queue-card__link { color: inherit; text-decoration: none; display: block; }

/* Row bits shared by every queue table (priority pill, action buttons, ref cell). */
.row-priority {
  display: inline-flex; padding: 1px 8px; border-radius: var(--radius-sm);
  font-size: 11px; font-weight: var(--weight-semibold); text-transform: capitalize;
}
.row-priority--urgent { background: var(--status-rejected-bg); color: var(--status-rejected); }
.row-priority--normal { background: var(--bg-surface-alt); color: var(--text-secondary); }
.row-priority--low { background: var(--bg-surface-alt); color: var(--text-tertiary); }

.row-action-btn {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 6px 10px; border-radius: var(--radius-md);
  font-size: var(--text-xs); font-weight: var(--weight-semibold);
  border: 1px solid var(--border-subtle); cursor: pointer;
  text-decoration: none; white-space: nowrap;
}
.row-action-btn--approve { background: var(--status-approved); color: white; border-color: var(--status-approved); }
.row-action-btn--approve:hover { background: #059669; }
.row-action-btn--reject { background: white; color: var(--status-rejected); border-color: var(--status-rejected); }
.row-action-btn--reject:hover { background: var(--status-rejected-bg); }
.row-actions { display: inline-flex; gap: 6px; align-items: center; justify-content: flex-end; }

/* Mobile: hide desktop-only elements, collapse filters, and reflow the
   toolbar so dropdowns sit on row 1 and the search spans row 2 (mirrors
   /admin/mr and /mr field view — one Foundation rule, both pages share). */
@media (max-width: 767px) {
  .admin-export { display: none; }
  .admin-heatmap { display: none !important; }
  .admin-toolbar { display: none; }
  .admin-toolbar--open { display: flex; flex-wrap: wrap; gap: 8px; }
  .admin-filter-toggle { display: inline-flex; }
  .admin-toolbar--open .admin-toolbar__search { flex: 1 1 100%; order: 10; }
  .admin-toolbar--open .admin-toolbar__filter { flex: 1 1 calc(50% - 4px); min-width: 0; }
  .admin-toolbar--open .admin-toolbar__actions { flex: 1 1 100%; order: 20; justify-content: flex-end; }
  .admin-toolbar--open select[name="priority"] { display: none; }
}

/* Home dashboard widgets: the legacy .widget-card / .widget-grid KPI styles
   were removed with the Layout-1 home redesign (the home now styles its own
   cards under .home-v2 in pages/home.css). */

/* ---------------------------------------------------------------------------
   Mobile admin FAB (Q-5) — primary-action floating button on mobile /admin
   list screens. Desktop uses the toolbar "+ New" button; mobile hides that
   toolbar and shows this FAB instead so the main action stays one-thumb
   reachable. Positioned above the 56 px bottom nav + safe-area inset.
   --------------------------------------------------------------------------- */

.admin-fab {
  display: none;
}

@media (max-width: 767px) {
  .admin-fab {
    display: inline-flex;
    position: fixed;
    right: 16px;
    bottom: calc(56px + env(safe-area-inset-bottom, 0px) + 16px);
    z-index: 20;
    width: 56px;
    height: 56px;
    border-radius: 28px;
    background: var(--brand-primary);
    color: var(--text-inverse);
    align-items: center;
    justify-content: center;
    text-decoration: none;
    box-shadow: 0 4px 12px rgba(15, 76, 129, 0.28),
                0 1px 3px rgba(0, 0, 0, 0.1);
    transition: transform 120ms ease-out, box-shadow 120ms ease-out;
  }
  .admin-fab:hover,
  .admin-fab:focus-visible {
    transform: translateY(-1px);
    box-shadow: 0 6px 18px rgba(15, 76, 129, 0.32),
                0 2px 4px rgba(0, 0, 0, 0.12);
  }
  .admin-fab svg { width: 24px; height: 24px; }
}

/* ---------------------------------------------------------------------------
   Settings hub — two-pane layout (left sub-nav + right content pane)
   Benchmark: Linear Settings, Stripe Dashboard Settings, Procore Account Admin.
   Mobile pattern (drill-in) is finalized in M5; this block sets the desktop
   shape and a sensible single-column fallback below 768 px.
   --------------------------------------------------------------------------- */

.settings-shell {
  display: grid;
  grid-template-columns: 260px 1fr;
  gap: var(--space-6);
  align-items: start;
  max-width: 1100px;
  margin: var(--space-5) auto 0;
}

.settings-subnav {
  position: sticky;
  top: calc(var(--header-height, 56px) + var(--space-4));
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.settings-subnav__header {
  padding: 0 var(--space-2) var(--space-2);
}
.settings-subnav__title {
  font-size: var(--text-2xl);
  font-weight: var(--weight-bold);
  color: var(--text-primary);
  margin: 0;
}
.settings-subnav__sub {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  margin: var(--space-1) 0 0;
}

.settings-subnav__group-label {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-tertiary);
  padding: 0 var(--space-3) var(--space-2);
}

.settings-subnav__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.settings-subnav__item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  text-decoration: none;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  min-height: 40px;
  transition: background-color 120ms;
}
.settings-subnav__item:hover {
  background: var(--bg-surface-alt);
}
.settings-subnav__item--active {
  background: var(--bg-surface-alt);
  color: var(--brand-primary);
  font-weight: var(--weight-semibold);
}

.settings-subnav__icon {
  width: 28px;
  height: 28px;
  border-radius: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.settings-subnav__icon svg { width: 16px; height: 16px; }
.settings-subnav__icon--blue   { background: var(--status-info-bg); color: var(--status-info); }
.settings-subnav__icon--green  { background: var(--status-approved-bg); color: var(--status-approved); }
.settings-subnav__icon--amber  { background: var(--status-pending-bg); color: #92400e; }
.settings-subnav__icon--purple { background: #ede9fe; color: #6d28d9; }
.settings-subnav__icon--slate  { background: var(--bg-surface-alt); color: var(--text-secondary); }

.settings-subnav__label {
  flex: 1;
  min-width: 0;
}

.settings-subnav__empty {
  padding: var(--space-5);
  font-size: var(--text-sm);
  color: var(--text-secondary);
  text-align: center;
}

.settings-content {
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  min-height: 360px;
  overflow: hidden;
}

.settings-content__header {
  padding: var(--space-5) var(--space-6);
  border-bottom: 1px solid var(--border-subtle);
}
.settings-content__title {
  font-size: var(--text-xl);
  font-weight: var(--weight-bold);
  color: var(--text-primary);
  margin: 0;
}
.settings-content__desc {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  margin: var(--space-1) 0 0;
}

.settings-content__body {
  padding: var(--space-5) var(--space-6);
}

.settings-empty {
  padding: var(--space-6);
  text-align: center;
  color: var(--text-secondary);
  font-size: var(--text-sm);
}
.settings-empty--lg {
  padding: var(--space-8) var(--space-6);
  font-size: var(--text-base);
}

/* Mobile back link inside the content pane — only visible on mobile in
   --detail mode (iOS Settings drill-in). Hidden on desktop where the sub-nav
   stays visible alongside the content. */
.settings-content__back {
  display: none;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm);
  color: var(--brand-primary);
  font-weight: var(--weight-medium);
  text-decoration: none;
  border-bottom: 1px solid var(--border-subtle);
}
.settings-content__back:hover { background: var(--bg-surface-alt); }

/* Mobile drill-in (iOS Settings pattern): /settings shows the sub-nav as a
   list page; /settings/{key} shows the section content with a back link.
   Desktop is unaffected — both panes stay visible regardless of route. */
@media (max-width: 767px) {
  .settings-shell {
    grid-template-columns: 1fr;
    gap: 0;
    margin: 0;
    max-width: none;
  }
  .settings-subnav {
    position: static;
    top: auto;
    padding: var(--space-4) var(--space-3);
  }
  .settings-content {
    border-radius: 0;
    border-left: none;
    border-right: none;
    border-top: none;
  }
  .settings-content__back {
    display: inline-flex;
  }
  .settings-content__header {
    padding: var(--space-4);
  }
  .settings-content__body {
    padding: var(--space-4);
  }

  /* List mode (URL = /settings): hide the content pane entirely. */
  .settings-shell--list .settings-content { display: none; }

  /* Detail mode (URL = /settings/{key}): hide the sub-nav, show only the
     section's content with the back link at the top. */
  .settings-shell--detail .settings-subnav { display: none; }
}

/* ---------------------------------------------------------------------------
   Top-right user menu — avatar trigger + dropdown (Slack/Linear/GitHub pattern).
   Replaces the old standalone Profile icon. Profile + Sign out live here.
   --------------------------------------------------------------------------- */

.user-menu {
  position: relative;
  display: inline-flex;
}

.user-menu__trigger {
  padding: 0;
  width: 36px;
  height: 36px;
}
.user-menu__avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.18);
  color: var(--text-inverse);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
}

.user-menu__dropdown {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 220px;
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12), 0 2px 6px rgba(0, 0, 0, 0.06);
  padding: var(--space-1);
  z-index: 30;
}
/* Native Popover path. profile-menu.js promotes this dropdown to a top-layer
   popover on browsers with the Popover API + CSS Anchor Positioning (Chrome
   125+, Safari 17.4+, Firefox 132+). In the top layer the fallback
   `position: absolute; top: calc(100% + 6px); right: 0` resolves against the
   viewport — the menu dropped ~100vh below the fold, so clicking the avatar
   looked like it did nothing. Re-pin it under the trigger via anchor
   positioning. profile-menu.js assigns each trigger a unique `anchor-name` and
   sets the matching `position-anchor` on this dropdown inline (a page can have
   more than one profile menu), and only ever adds the `popover` attribute when
   anchor support is present — so `anchor()` is safe to use unconditionally. */
.user-menu__dropdown[popover] {
  position: fixed;
  inset: auto;
  margin: 0;
  top: calc(anchor(bottom) + 6px);
  right: anchor(right);
}

.user-menu__header {
  padding: var(--space-3) var(--space-3) var(--space-3);
  border-bottom: 1px solid var(--border-subtle);
  margin: 0 calc(-1 * var(--space-1)) var(--space-1);
}
.user-menu__name {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
}
.user-menu__sub {
  font-size: var(--text-xs);
  color: var(--text-secondary);
  margin-top: 2px;
}

.user-menu__item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: none;
  background: none;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--text-primary);
  text-align: left;
  text-decoration: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
  min-height: 36px;
}
.user-menu__item:hover {
  background: var(--bg-surface-alt);
}
.user-menu__item--danger {
  color: var(--status-rejected);
}
.user-menu__item--danger:hover {
  background: var(--status-rejected-bg, #fee2e2);
}
.user-menu__item svg {
  flex-shrink: 0;
  opacity: 0.7;
}

/* ---------------------------------------------------------------------------
   Account section content (rendered inside the Settings content pane). Keeps
   the header → cards rhythm of iOS Settings: a profile header strip on top,
   then grouped cards (Account, Security, About) with row + action variants.
   --------------------------------------------------------------------------- */

.account-header {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: 0 0 var(--space-5);
  border-bottom: 1px solid var(--border-subtle);
  margin-bottom: var(--space-5);
}
.account-header__avatar {
  width: 56px;
  height: 56px;
  border-radius: 14px;
  background: var(--brand-primary);
  color: var(--text-inverse);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-xl);
  font-weight: var(--weight-bold);
  flex-shrink: 0;
}
.account-header__name {
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
  color: var(--text-primary);
}
.account-header__role {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  margin-top: 2px;
}

.account-card {
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  overflow: hidden;
  margin-bottom: var(--space-4);
}
.account-card__title {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: var(--space-3) var(--space-4) var(--space-2);
}
.account-row,
.account-action {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border-subtle);
  min-height: 48px;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--text-primary);
}
.account-row:last-child,
.account-action:last-child {
  border-bottom: none;
}
.account-action {
  width: 100%;
  border-left: none;
  border-right: none;
  border-top: none;
  background: none;
  cursor: pointer;
  text-align: left;
  transition: background-color 120ms;
}
.account-action:hover {
  background: var(--bg-surface-alt);
}
.account-row__label,
.account-action__text {
  flex: 1;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  color: var(--text-secondary);
}
.account-row__label svg,
.account-action svg:not(.account-action__chevron) {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  opacity: 0.55;
}
.account-row__value {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text-primary);
}
.account-action__chevron {
  width: 16px;
  height: 16px;
  color: var(--text-tertiary);
  flex-shrink: 0;
}
.account-action--danger,
.account-action--danger .account-action__text {
  color: var(--status-rejected);
}
.account-action--danger svg {
  color: var(--status-rejected);
  opacity: 0.85;
}

.account-collapse {
  padding: var(--space-3) var(--space-4) var(--space-4);
  border-bottom: 1px solid var(--border-subtle);
}
.account-feedback {
  font-size: var(--text-sm);
  margin-bottom: var(--space-3);
}
.account-feedback--error { color: var(--status-rejected); }
.account-feedback--success { color: var(--status-approved); }

.account-footer {
  text-align: center;
  margin-top: var(--space-5);
  font-size: var(--text-xs);
  color: var(--text-tertiary);
}

/* ---------------------------------------------------------------------------
   Utilities (audit #3 + #34 — CSP tightening)
   --------------------------------------------------------------------------
   Strict CSP forbids `style="..."` attributes (see app/csp.py). The patterns
   below cover the highest-volume inline styles that previously sprinkled
   the templates. They are deliberately atomic — one rule per class — so
   templates can compose them. New utilities are welcome here when a pattern
   shows up in 5+ templates; one-off styles belong in the page's own CSS.
   --------------------------------------------------------------------------- */
.is-hidden { display: none !important; }
.u-relative { position: relative; }
.u-flex { display: flex; }
.u-flex-center { display: flex; align-items: center; gap: 8px; }
.u-text-right { text-align: right; }
.u-text-center { text-align: center; }
.u-text-secondary { color: var(--text-secondary); }
.u-text-tertiary { color: var(--text-tertiary); }
.u-text-inverse { color: var(--text-inverse); }
.u-text-danger { color: var(--status-rejected); }
.u-text-warning { color: var(--status-pending); }
.u-text-sm { font-size: var(--text-sm); }
.u-text-xs { font-size: var(--text-xs); }
.u-weight-normal { font-weight: var(--weight-normal); }
.u-weight-medium { font-weight: var(--weight-medium); }
.u-weight-semibold { font-weight: var(--weight-semibold); }
.u-nowrap { white-space: nowrap; }
.u-prewrap { white-space: pre-wrap; }
.u-tabular { font-variant-numeric: tabular-nums; }
.u-cursor-pointer { cursor: pointer; }
.u-full-width { width: 100%; }
.u-margin-left-auto { margin-left: auto; }
.u-mt-1 { margin-top: var(--space-1); }
.u-mt-2 { margin-top: var(--space-2); }
.u-mt-3 { margin-top: var(--space-3); }
.u-mt-4 { margin-top: var(--space-4); }
.u-mb-0 { margin-bottom: 0; }
.u-mb-1 { margin-bottom: var(--space-1); }
.u-mb-2 { margin-bottom: var(--space-2); }
.u-mb-3 { margin-bottom: var(--space-3); }
.u-mb-4 { margin-bottom: var(--space-4); }
.u-p-2 { padding: var(--space-2); }
.u-p-3 { padding: var(--space-3); }
.u-p-4 { padding: var(--space-4); }
/* Sidebar badge: appears on .sidebar__item rows. Replaces the inline
   `style="margin-left:auto;font-size:var(--text-xs);color:...;font-weight:normal;"`
   pattern with two semantic modifiers. */
.sidebar__badge {
  margin-left: auto;
  font-size: var(--text-xs);
  font-weight: var(--weight-normal);
  color: var(--text-tertiary);
}
.sidebar__badge--warning { color: var(--status-pending); }
/* Empty-state cell shared by admin queue tables. */
.queue-empty {
  text-align: center;
  padding: var(--space-8);
  color: var(--status-rejected);
}
/* Skeleton blocks shared by detail/list loading shells. */
.skeleton-row {
  height: 16px;
  width: 60%;
  margin-bottom: var(--space-3);
}
.skeleton-photo {
  height: 80px;
  margin-bottom: var(--space-3);
}
/* Caption / note display: pre-wrap small-print used by detail and list
   views to render multi-line server-supplied text. */
.note-block {
  font-size: var(--text-sm);
  white-space: pre-wrap;
}
/* Color-swatch dot used in admin cards. */
.swatch-dot {
  width: 10px;
  height: 10px;
}

/* ========== Dark mode + theme toggle (audit #44; tuned in W8.1) ========== */
/*
 * Three states cycle from the header toggle (System → Light → Dark):
 *   - System:  no `data-theme` attr; CSS honours `prefers-color-scheme`.
 *   - Light:   `<html data-theme="light">`; forces the light palette.
 *   - Dark:    `<html data-theme="dark">`;  forces the dark palette.
 *
 * Pre-paint FOUC prevention: an inline script in `base.html`'s `<head>` reads
 * `localStorage.theme` and stamps the attr before the stylesheet loads. The
 * runtime click handler + cross-tab sync live in `theme.js`.
 *
 * To avoid duplicating the dark-token values, we DRY them via composing
 * variables: `--dark-*` holds the actual hexes, and the two selectors that
 * activate dark mode (forced + system-pref) just point the live tokens at
 * those `--dark-*` values.
 *
 * W8.1 contrast tuning vs. the original W8 ship:
 *   - `.sidebar__item--active` got an unmistakable left-border accent + a
 *     brighter selected-bg so the active page reads at a glance (was
 *     near-invisible against `--bg-surface`).
 *   - `--border-subtle` lifted from #1e293b → #334155 so cards have a
 *     visible edge against `--bg-surface` (#111827).
 *   - "Review →" / "Open →" link colour brightened to #60a5fa for cards
 *     and KPI tiles (brand-primary on a dark card read as muted).
 *   - Badge bgs lifted slightly so the saturated text doesn't sit on a
 *     near-black tile.
 *   - Dropped the `img { filter: brightness(0.9) }` rule — too aggressive
 *     on the photo grid and didn't actually help eye strain.
 */

:root {
  /* Dark-token VALUES — referenced by both activation selectors below so the
     hexes only live in one place. */
  --dark-text-primary: #f1f5f9;
  --dark-text-secondary: #cbd5e1;
  --dark-text-tertiary: #94a3b8;
  /* W8.3 — DO NOT flip --text-inverse in dark mode. The header bar uses
     `--brand-primary` background (a fixed dark blue) regardless of theme,
     and `u-text-inverse` is the colour for text/icons SITTING ON THAT
     BACKGROUND. White stays right in both themes; flipping to slate-950
     made the header text near-invisible (W8.2 walkthrough caught this). */
  --dark-text-inverse: #ffffff;

  /* W8.2 A9 benchmark vs. GitHub / Linear / Stripe dashboards:
     - Bg-app slightly less saturated-blue (#0d1117 — GitHub's canvas-default).
     - Bg-surface clearly lifted off bg-app (#161b22 — GitHub's surface) so
       cards visibly elevate.
     - bg-surface-alt tuned to match the new ladder (#22272e). */
  --dark-bg-app: #0d1117;
  --dark-bg-surface: #161b22;
  --dark-bg-surface-alt: #22272e;

  /* W8.2 — borders pulled back from the W8.1 #334155 (which read as a
     "drawn outline" against the new surface). #2a3344 is visible enough
     to read as a card edge but doesn't compete with content. */
  --dark-border-subtle: #2a3344;
  --dark-border-strong: #3d4759;

  /* Status backgrounds — pastel-on-light flipped to dark-tinted. W8.1
     bumped saturation a touch so badges visibly pop above the surface
     instead of fading into it. */
  --dark-status-pending-bg: #4a3a0d;
  --dark-status-approved-bg: #0d3a2c;
  --dark-status-rejected-bg: #4a1414;
  --dark-status-info-bg: #143055;
  --dark-status-neutral-bg: #2a3441;

  /* Text-on-dark-bg badge text. Pastel-light shades that read clearly
     against the dark-tinted backgrounds above without losing the
     status hue. */
  --dark-status-pending-text: #fcd34d;
  --dark-status-approved-text: #6ee7b7;
  --dark-status-rejected-text: #fca5a5;
  --dark-status-info-text: #93c5fd;
  --dark-status-neutral-text: #cbd5e1;

  /* Brand tints — `--brand-primary-light` flips from the light pastel
     #e6eef6 to a saturated dark blue that pairs with the brand-primary
     foreground. */
  --dark-brand-primary-light: #1e3a8a;

  /* Brand-gradient endpoints in dark mode. The light hexes #0a3260 / #0e4070
     read as muddy near-black against the dark surrounds (#0d1117 app bg,
     #161b22 card) — F#10 caught the gradient looking identical to light.
     Same hue (~213°), saturation dropped 78→63 so it isn't harsh under
     dark-adapted eyes, lightness lifted 21→38 so it visibly reads as a
     softer "Davenco navy lit from above" rather than disappearing into
     the surface. Stripe / Linear dark logins go more muted still but
     their gradient is incidental — ours is the brand-identity moment, so
     we keep enough saturation to remain unmistakably brand. */
  --dark-brand-gradient-start: #1e4d8c;
  --dark-brand-gradient-end: #2456a0;

  /* W8.1 — bright accent for "Review →" / "Open →" / interactive
     callouts on dark cards. brand-primary itself stays the same (it
     reads as a button), but text-link emphasis needs more saturation. */
  --dark-link-accent: #60a5fa;

  /* W8.2 A9 — black shadows on a dark surface barely register, so cards
     in dark mode also need a thin top inset highlight (Stripe / Linear
     pattern). The 4 % white inset reads as "lit from above" — gives
     implied elevation without making the card look like a popup. */
  --dark-shadow-sm:
    0 1px 0 0 rgb(255 255 255 / 0.04) inset,
    0 1px 3px 0 rgb(0 0 0 / 0.4);
  --dark-shadow-md:
    0 1px 0 0 rgb(255 255 255 / 0.05) inset,
    0 4px 8px -2px rgb(0 0 0 / 0.5),
    0 2px 4px -2px rgb(0 0 0 / 0.4);
  --dark-shadow-lg:
    0 1px 0 0 rgb(255 255 255 / 0.06) inset,
    0 12px 20px -4px rgb(0 0 0 / 0.6),
    0 4px 8px -4px rgb(0 0 0 / 0.5);

  /* Light-mode default for the link-accent so the same `var()` works in
     both modes. */
  --link-accent: var(--brand-primary);
}

/* Mixin block: applies the dark-token values to the live tokens. Used by
   the two activation selectors below. The duplication is unavoidable in
   plain CSS, but the values are DRY (above). */

/* (a) User explicitly forced dark via the toggle. */
:root[data-theme="dark"] {
  --text-primary: var(--dark-text-primary);
  --text-secondary: var(--dark-text-secondary);
  --text-tertiary: var(--dark-text-tertiary);
  --text-inverse: var(--dark-text-inverse);
  --bg-app: var(--dark-bg-app);
  --bg-surface: var(--dark-bg-surface);
  --bg-surface-alt: var(--dark-bg-surface-alt);
  --border-subtle: var(--dark-border-subtle);
  --border-strong: var(--dark-border-strong);
  --status-pending-bg: var(--dark-status-pending-bg);
  --status-approved-bg: var(--dark-status-approved-bg);
  --status-rejected-bg: var(--dark-status-rejected-bg);
  --status-info-bg: var(--dark-status-info-bg);
  --status-neutral-bg: var(--dark-status-neutral-bg);
  --status-pending-text: var(--dark-status-pending-text);
  --status-approved-text: var(--dark-status-approved-text);
  --status-rejected-text: var(--dark-status-rejected-text);
  --status-info-text: var(--dark-status-info-text);
  --status-neutral-text: var(--dark-status-neutral-text);
  --brand-primary-light: var(--dark-brand-primary-light);
  --brand-gradient-start: var(--dark-brand-gradient-start);
  --brand-gradient-end: var(--dark-brand-gradient-end);
  --link-accent: var(--dark-link-accent);
  --shadow-sm: var(--dark-shadow-sm);
  --shadow-md: var(--dark-shadow-md);
  --shadow-lg: var(--dark-shadow-lg);
  color-scheme: dark;
}

/* (b) System pref says dark AND user hasn't overridden to light. The
   `:not([data-theme="light"])` clause lets `[data-theme]` absent (system
   mode) or `[data-theme="dark"]` (already-dark) keep the dark tokens. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --text-primary: var(--dark-text-primary);
    --text-secondary: var(--dark-text-secondary);
    --text-tertiary: var(--dark-text-tertiary);
    --text-inverse: var(--dark-text-inverse);
    --bg-app: var(--dark-bg-app);
    --bg-surface: var(--dark-bg-surface);
    --bg-surface-alt: var(--dark-bg-surface-alt);
    --border-subtle: var(--dark-border-subtle);
    --border-strong: var(--dark-border-strong);
    --status-pending-bg: var(--dark-status-pending-bg);
    --status-approved-bg: var(--dark-status-approved-bg);
    --status-rejected-bg: var(--dark-status-rejected-bg);
    --status-info-bg: var(--dark-status-info-bg);
    --status-neutral-bg: var(--dark-status-neutral-bg);
    --status-pending-text: var(--dark-status-pending-text);
    --status-approved-text: var(--dark-status-approved-text);
    --status-rejected-text: var(--dark-status-rejected-text);
    --status-info-text: var(--dark-status-info-text);
    --status-neutral-text: var(--dark-status-neutral-text);
    --brand-primary-light: var(--dark-brand-primary-light);
    --brand-gradient-start: var(--dark-brand-gradient-start);
    --brand-gradient-end: var(--dark-brand-gradient-end);
    --link-accent: var(--dark-link-accent);
    --shadow-sm: var(--dark-shadow-sm);
    --shadow-md: var(--dark-shadow-md);
    --shadow-lg: var(--dark-shadow-lg);
    color-scheme: dark;
  }
}

/* Component-level dark-mode tweaks. These need explicit selectors because
   they hardcode hex values that don't flow through the token system, OR
   because the dark mode wants a different visual treatment than the light
   mode (e.g. the active sidebar item). All wrapped in a selector group
   that fires on either activation path. */

/* Mixin via selector list: matches BOTH dark activation paths. */
:root[data-theme="dark"] .badge-pending { color: #fde68a; }
:root[data-theme="dark"] .badge-approved { color: #86efac; }
:root[data-theme="dark"] .badge-rejected { color: #fca5a5; }
:root[data-theme="dark"] .badge-info { color: #93c5fd; }
:root[data-theme="dark"] .badge-neutral { color: #cbd5e1; }
:root[data-theme="dark"] .login-box {
  background: var(--bg-surface);
  box-shadow: var(--shadow-lg);
}

/* W8.1 — sidebar active state: solid bright bg + 3 px brand-primary left
   border so the selected page reads at a glance against the dark sidebar.
   The original W8 just darkened the bg by the brand-primary-light tint
   override and left the result indistinguishable from the unselected items. */
:root[data-theme="dark"] .sidebar__item--active {
  background: var(--brand-primary-light);
  border-left: 3px solid var(--brand-primary);
  color: #f1f5f9;
}
:root[data-theme="dark"] .sidebar__item--active svg {
  color: #93c5fd;
}

/* Ghost button text inside dialogs needs brighter contrast against the
   dark surface — the default brand-primary blue blends into the dark
   dialog background and reads as nearly invisible. */
:root[data-theme="dark"] dialog.modal .btn-ghost {
  color: #93c5fd;
}

/* W8.1 — KPI / quick-action / empty-state icon tiles. Light-mode pairs
   `var(--brand-primary)` foreground with `var(--brand-primary-light)`
   background (#0f4c81 on #e6eef6) — a strong, readable pair. In dark
   mode the same vars resolved to #0f4c81 on #1e3a8a, which read as a
   single muddy blob. Bumping the foreground to a brighter blue pulls
   the icon back out of the tile. Status-tinted KPIs (--amber, --green)
   get parallel fg lifts. */
:root[data-theme="dark"] .action-card__icon,
:root[data-theme="dark"] .empty-welcome__icon {
  color: #93c5fd;
}
:root[data-theme="dark"] .admin-kpi__icon--green {
  color: #6ee7b7;
}
:root[data-theme="dark"] .admin-kpi__icon--amber {
  color: #fcd34d;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .badge-pending { color: #fde68a; }
  :root:not([data-theme="light"]) .badge-approved { color: #86efac; }
  :root:not([data-theme="light"]) .badge-rejected { color: #fca5a5; }
  :root:not([data-theme="light"]) .badge-info { color: #93c5fd; }
  :root:not([data-theme="light"]) .badge-neutral { color: #cbd5e1; }
  :root:not([data-theme="light"]) .login-box {
    background: var(--bg-surface);
    box-shadow: var(--shadow-lg);
  }
  :root:not([data-theme="light"]) .sidebar__item--active {
    background: var(--brand-primary-light);
    border-left: 3px solid var(--brand-primary);
    color: #f1f5f9;
  }
  :root:not([data-theme="light"]) .sidebar__item--active svg {
    color: #93c5fd;
  }
  :root:not([data-theme="light"]) dialog.modal .btn-ghost {
    color: #93c5fd;
  }
  :root:not([data-theme="light"]) .action-card__icon,
  :root:not([data-theme="light"]) .empty-welcome__icon {
    color: #93c5fd;
  }
  :root:not([data-theme="light"]) .admin-kpi__icon--green {
    color: #6ee7b7;
  }
  :root:not([data-theme="light"]) .admin-kpi__icon--amber {
    color: #fcd34d;
  }
}

/* ========== Theme-toggle button (audit #44 W8.1; W8.2 fix) ========== */
/* Lives in the header `app-header__actions` row alongside the bell, sync,
   and user menu. The three icons swap via JS (theme.js) — at any given
   moment one is shown and the other two are `hidden`.
   W8.2 — the previous rule used `display: inline-flex` on every icon span,
   which has higher specificity than `[hidden]`'s UA `display: none`. JS was
   correctly setting `hidden=true` on the two inactive icons but they all
   rendered side by side. Scoping the `display` rule to `:not([hidden])`
   means hidden icons fall back to the UA default and actually disappear. */
#theme-toggle [data-theme-icon]:not([hidden]) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* ========== Custom confirm dialog ========== */
/* Replaces native `window.confirm()`. Single shared `<dialog>` lives
   in base.html; `window.appConfirm()` (components/confirm-dialog.js)
   opens it centered with a soft backdrop, no browser-default
   "<host> says" prefix. Falls back to native confirm on browsers
   without `<dialog>` support. */
.app-confirm {
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  /* Use `inset:0` + flex so the inner panel centers regardless of
     the dialog's intrinsic sizing. */
  inset: 0;
  width: 100vw;
  width: 100dvw;
  height: 100vh;
  height: 100dvh;
  max-width: 100vw;
  max-height: 100vh;
  overflow: hidden;
}
.app-confirm::backdrop { background: rgba(15, 23, 42, 0.55); }
.app-confirm[open] {
  display: flex;
  align-items: center;
  justify-content: center;
}
.app-confirm__panel {
  width: min(92vw, 420px);
  background: var(--bg-surface);
  color: var(--text-primary);
  border-radius: var(--radius-lg);
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.25);
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.app-confirm__title {
  margin: 0;
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--text-primary);
}
.app-confirm__message {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--text-secondary);
  line-height: var(--leading-normal);
}
.app-confirm__actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: var(--space-1);
}
.app-confirm__actions .btn { min-height: 40px; }

/* Prompt dialog — reuses .app-confirm panel chrome; adds a single text input.
   16px font-size keeps iOS from auto-zooming the field (PWA standard). */
.app-prompt__input {
  width: 100%;
  box-sizing: border-box;
  font-size: 16px;
  line-height: var(--leading-normal);
  color: var(--text-primary);
  background: var(--bg-surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  padding: 10px 12px;
  min-height: 44px;
}
.app-prompt__input:focus {
  outline: 2px solid var(--brand-primary, #0f4c81);
  outline-offset: 1px;
}

/* =====================================================================
   GOLD TEMPLATE — shared mobile card chrome (Phase 0c)
   =====================================================================
   Source mockup: /tmp/snags-mockup-gold.html (locked spec).
   These rules add the new card patterns + supporting tokens. Per-module
   CSS files (pages/*.css) reference these by class for the actual lists.

   Patterns:
     A   severity stripe + thumbnail right       (Snags, Asset Register)
     B   per-item mix-bar + days-open topline    (MRs, Procurement)
     C   date block left + project title         (Diary)
     D   checkbox + assignee + priority bars     (Tasks)
     E   qty + 5-segment battery gauge           (Inventory)
     F   capture buttons + day-grouped grid      (Site Photos)
     G   action state card                       (Attendance)

   Chrome (top bar / filter / chips / FAB / bottom nav) was already shipped
   in Phase 0; this block layers the card-body refinements on top. */

/* ---- Status pills (assets, attendance approval, etc.) -------------- */
.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px 2px 6px;
  border-radius: var(--radius-full);
  font-size: 10px;
  font-weight: var(--weight-bold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.status-pill__dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  flex-shrink: 0;
}
.status-pill--active { background: var(--severity-success-bg); color: var(--severity-success); }
.status-pill--active .status-pill__dot { background: var(--severity-success); }
.status-pill--in_service { background: var(--severity-major-bg); color: var(--severity-major); }
.status-pill--in_service .status-pill__dot { background: var(--severity-major); }
.status-pill--retired { background: var(--severity-minor-bg); color: var(--severity-minor); }
.status-pill--retired .status-pill__dot { background: var(--severity-minor); }

/* ---- Pattern B — Mix-bar (MR + Procurement) ------------------------ */
/* Per-item segmented bar where each segment is one line item, coloured
   by that item's current stage. Multi-item request reads "X delivered,
   Y ordered, Z review" at a glance — one stepper would falsely imply
   all items move together. */
.mixbar {
  display: flex;
  gap: 2px;
  margin-top: 4px;
  height: 6px;
}
.mixbar__seg {
  flex: 1 1 0;
  border-radius: 2px;
  background: var(--border-subtle);
}
.mixbar__seg--delivered { background: var(--severity-success); }
.mixbar__seg--ordered   { background: var(--brand-primary); }
.mixbar__seg--approved  { background: #93c5fd; }
.mixbar__seg--review    { background: var(--severity-major); }
.mixbar__seg--pending   { background: #7c3aed; }
.mixbar__seg--cancelled { background: var(--text-tertiary); }

.mixbar-legend {
  display: flex;
  gap: 10px;
  margin-top: 6px;
  font-size: 11px;
  color: var(--text-secondary);
  flex-wrap: wrap;
  font-weight: var(--weight-medium);
}
.mixbar-legend__item {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  letter-spacing: -0.005em;
}
.mixbar-legend__dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  flex-shrink: 0;
}
.mixbar-legend__dot--delivered { background: var(--severity-success); }
.mixbar-legend__dot--ordered   { background: var(--brand-primary); }
.mixbar-legend__dot--approved  { background: #93c5fd; }
.mixbar-legend__dot--review    { background: var(--severity-major); }
.mixbar-legend__dot--pending   { background: #7c3aed; }
.mixbar-legend__count { color: var(--text-primary); font-weight: var(--weight-semibold); }

/* ---- Pattern E — 5-segment battery gauge (Inventory) --------------- */
.stock-gauge {
  display: flex;
  gap: 2px;
  margin-top: 6px;
  width: 90px;
}
.stock-gauge__seg {
  flex: 1 1 0;
  height: 8px;
  border-radius: 1.5px;
  background: var(--border-subtle);
  transition: background 120ms;
}
.stock-gauge__seg--ok   { background: var(--severity-success); }
.stock-gauge__seg--warn { background: var(--severity-major); }
.stock-gauge__seg--crit { background: var(--severity-critical); }

/* ---- Pattern D — Tasks priority bars ------------------------------- */
/* Linear-style 3 vertical bars on the right edge of a task card.
   1 active = low, 2 = med, 3 = high; 3 in red = urgent. */
.priority-bars {
  display: flex;
  flex-direction: column-reverse;
  justify-content: flex-start;
  align-items: stretch;
  gap: 2px;
  width: 4px;
  height: 14px;
  flex-shrink: 0;
}
.priority-bars__bar {
  flex: 1 1 0;
  background: var(--border-card, var(--border-subtle));
  border-radius: 1px;
}
.priority-bars--low .priority-bars__bar:nth-child(1) { background: var(--severity-minor); }
.priority-bars--med .priority-bars__bar:nth-child(1),
.priority-bars--med .priority-bars__bar:nth-child(2) { background: var(--severity-major); }
.priority-bars--high .priority-bars__bar { background: var(--severity-major); }
.priority-bars--urgent .priority-bars__bar { background: var(--severity-critical); }

/* ---- Pattern F — Photos: capture zone + day-grouped grid ----------- */
.capture-zone {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
  padding: var(--space-4);
}
.capture-btn {
  height: 100px;
  border-radius: var(--radius-md);
  border: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  cursor: pointer;
  font-weight: var(--weight-bold);
  font-size: var(--text-sm);
  letter-spacing: -0.005em;
  transition: transform 100ms, box-shadow 120ms;
  text-decoration: none;
}
.capture-btn:hover { transform: translateY(-1px); }
.capture-btn:active { transform: translateY(0); }
.capture-btn--camera {
  background: var(--brand-primary);
  color: var(--text-inverse);
  box-shadow: 0 2px 4px rgba(15,76,129,.16), 0 6px 16px rgba(15,76,129,.20);
}
.capture-btn--gallery {
  background: var(--bg-surface);
  color: var(--text-primary);
  border: 1px solid var(--border-card, var(--border-subtle));
}
.capture-btn svg { width: 30px; height: 30px; stroke-width: 2; }

.photo-section-label {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  color: var(--text-secondary);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: var(--space-4) var(--space-3) var(--space-2);
}
.photo-section-label__count {
  color: var(--text-tertiary);
  font-weight: var(--weight-medium);
  letter-spacing: 0.04em;
}
.photo-section-label--first { padding-top: var(--space-2); }

.photo-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2px;
  padding: 0 var(--space-3);
}
.photo-tile {
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-sm);
  overflow: hidden;
  position: relative;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-hover, var(--bg-surface-alt));
}
.photo-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.photo-tile__count {
  position: absolute;
  inset: 0;
  background: rgba(0,0,0,0.55);
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-md, 17px);
  font-weight: var(--weight-bold);
  letter-spacing: -0.012em;
}

/* ---- Pattern G — Attendance state card + recent log ---------------- */
.att-state {
  margin: var(--space-3);
  border-radius: var(--radius-lg);
  padding: var(--space-5) var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  box-shadow: var(--shadow-card, 0 1px 2px rgba(15,23,42,.04));
}
.att-state--in {
  background:
    radial-gradient(circle at 25% 0%, rgba(255,255,255,.18), transparent 60%),
    linear-gradient(135deg, #15532b 0%, #16a34a 60%, #22c55e 100%);
  color: white;
  box-shadow: 0 4px 16px rgba(22,163,74,.25), inset 0 1px 0 rgba(255,255,255,.10);
}
.att-state--out {
  background: var(--bg-surface);
  color: var(--text-primary);
  border: 1px solid var(--border-card, var(--border-subtle));
}
.att-state__status {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-md, 17px);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.005em;
}
.att-state__dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  flex-shrink: 0;
}
.att-state--in .att-state__dot {
  background: #6ee7b7;
  box-shadow: 0 0 0 4px rgba(110,231,183,.30);
  animation: att-pulse 2s ease-in-out infinite;
}
.att-state--out .att-state__dot { background: var(--text-tertiary); }
@keyframes att-pulse {
  0%, 100% { box-shadow: 0 0 0 4px rgba(110,231,183,.30); }
  50% { box-shadow: 0 0 0 8px rgba(110,231,183,.10); }
}
.att-state__since {
  font-size: var(--text-md, 17px);
  font-weight: var(--weight-medium);
  color: rgba(255,255,255,.85);
  letter-spacing: -0.005em;
}
.att-state--out .att-state__since {
  color: var(--text-secondary);
}
/* Hero duration display — clock-style "2:14" with small unit suffix */
.att-state__duration {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: var(--space-1) 0;
}
.att-state__duration-num {
  font-size: 44px;
  font-weight: var(--weight-bold);
  line-height: 1;
  letter-spacing: -0.04em;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
}
.att-state__duration-unit {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.78;
}
.att-state__chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  border-radius: var(--radius-full);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  align-self: flex-start;
}
.att-state--in .att-state__chip {
  background: rgba(255,255,255,.18);
  color: rgba(255,255,255,.96);
}
.att-state--out .att-state__chip {
  background: var(--bg-hover, var(--bg-surface-alt));
  color: var(--text-secondary);
}
.att-state__field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.att-state__field-label {
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  color: var(--text-tertiary);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.att-state__select {
  height: 44px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-card, var(--border-subtle));
  background: var(--bg-surface);
  color: var(--text-primary);
  font-size: var(--text-base, 16px);
  font-weight: var(--weight-medium);
}
.att-state__action {
  margin-top: var(--space-2);
  height: 56px;
  border-radius: var(--radius-md);
  border: 0;
  font-size: var(--text-base, 16px);
  font-weight: var(--weight-bold);
  letter-spacing: -0.005em;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  transition: transform 100ms, opacity 100ms;
}
.att-state__action svg { width: 22px; height: 22px; stroke-width: 2.5; }
.att-state__action:active { transform: translateY(1px); }
.att-state__action--out {
  background: rgba(255,255,255,.18);
  color: white;
  border: 1.5px solid rgba(255,255,255,.32);
}
.att-state__action--out:hover { background: rgba(255,255,255,.24); }
.att-state__action--in {
  background: var(--brand-primary);
  color: var(--text-inverse);
  box-shadow: 0 2px 4px rgba(15,76,129,.16), 0 6px 16px rgba(15,76,129,.20);
}
.att-state__hint {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-xs);
  color: var(--text-secondary);
  font-weight: var(--weight-medium);
  letter-spacing: -0.005em;
  padding: 0 var(--space-1);
  margin-top: var(--space-1);
}
.att-state__hint svg {
  width: 14px; height: 14px;
  stroke-width: 2;
  flex-shrink: 0;
  color: var(--text-tertiary);
}

/* Today's hours stats card */
.att-stats {
  margin: 0 var(--space-3);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  box-shadow: var(--shadow-card, 0 1px 2px rgba(15,23,42,.04));
}
.att-stats__label {
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  color: var(--text-tertiary);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.att-stats__value {
  font-size: var(--text-2xl, 26px);
  font-weight: var(--weight-bold);
  color: var(--text-primary);
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
.att-stats__hint {
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  font-weight: var(--weight-medium);
  margin-top: 2px;
}

/* Recent attendance log — compact Pattern-C variant */
.att-recent {
  padding: var(--space-2) var(--space-3) var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.att-recent__heading {
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  color: var(--text-tertiary);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: var(--space-2) var(--space-2) var(--space-1);
}
.att-row {
  display: grid;
  grid-template-columns: 48px 1fr auto;
  gap: var(--space-3);
  align-items: center;
  padding: var(--space-3) var(--space-3);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card, 0 1px 2px rgba(15,23,42,.04));
  text-decoration: none;
  color: var(--text-primary);
}
.att-row:active { background: var(--bg-hover, var(--bg-surface-alt)); }
.att-row__date {
  width: 48px; height: 48px;
  border-radius: var(--radius-md);
  background: var(--brand-primary-light);
  color: var(--brand-primary-dark);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}
.att-row__date-day {
  font-size: var(--text-xl, 22px);
  font-weight: var(--weight-bold);
  line-height: 1;
}
.att-row__date-month {
  font-size: 9px;
  font-weight: var(--weight-bold);
  color: var(--brand-primary);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1;
  margin-top: 2px;
}
.att-row__main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.att-row__title {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
  letter-spacing: -0.005em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.att-row__meta {
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  display: flex;
  align-items: center;
  gap: 6px;
}
.att-row__status-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  flex-shrink: 0;
}
.att-row__status-dot--ok { background: var(--severity-success); }
.att-row__status-dot--pending { background: var(--severity-major); }
.att-row__hours {
  font-size: var(--text-md, 17px);
  font-weight: var(--weight-bold);
  color: var(--text-primary);
  letter-spacing: -0.012em;
  font-variant-numeric: tabular-nums;
}

/* ---- Age pill — small minor pill for "12d" / "2d" on Snags etc. ---- */
/* Already covered by existing .pill--minor; no new class needed. */

/* ---- Tap feedback flash on all card classes ------------------------ */
.snag-card:active,
.mr-card:active,
.todo-card:active,
.asset-row:active,
.inv-card:active {
  background: var(--bg-hover, var(--bg-surface-alt));
}

/* ---- Severity-success token bg used by GOLD partials --------------- */
:root {
  --severity-success: #1e5728;
  --severity-success-bg: #def0e2;
  --severity-major-bg: #fdefdc;
  --severity-minor-bg: #ebedf0;
}
:root[data-theme="dark"] {
  --severity-success: #86efac;
  --severity-success-bg: #1f3a26;
  --severity-major-bg: #3a2918;
  --severity-minor-bg: #2d333b;
}

/* GOLD Pattern A — Snags mobile card additions */
.card-list__top-sep {
  color: var(--text-tertiary);
  margin: 0 4px;
}
.card-list__top-villa {
  color: var(--text-secondary);
  font-weight: var(--weight-medium, 500);
  font-size: var(--text-sm);
}
.card-list__pills {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: var(--space-2);
}
.card-list__age-pill {
  display: inline-flex;
  align-items: center;
  font-size: 11px;
  font-weight: var(--weight-semibold, 600);
  letter-spacing: 0.01em;
  padding: 3px 8px;
  border-radius: var(--radius-full);
  background: var(--severity-minor-bg);
  color: var(--severity-minor);
}
.card-list__age-pill--overdue {
  background: var(--severity-critical-bg);
  color: var(--severity-critical);
}

/* ============================================================================
 * Phase 0.5 mobile chrome — appended at the end of app.css so existing
 * rules above are untouched. The breakpoint matches the existing
 * `body.is-home` convention (Phase 0 mobile shell rework): 767px is the
 * mobile threshold; everything wider gets desktop chrome.
 *
 * Three new pieces:
 *   1. Surface utilities (`.u-mobile-only` / `.u-desktop-only`) — used by
 *      the bell anchor split in base.html and the home launcher branch
 *      in home.html.
 *   2. Mobile launcher (`.apps-grid` + `.app-tile`) — the new home page
 *      on mobile. Desktop home is unchanged.
 *   3. Dark-theme tweaks for the active bottom-nav slot per v15.
 * ============================================================================ */

/* Surface utilities — global, NOT inside a @media block. The two rules
   stack so each is the inverse of the other and each fires at exactly
   one viewport range. Tablet (768–1023px) follows the desktop side
   because the existing desktop sidebar starts at 1024px+ and the bell
   anchor on tablet should land where the sidebar would expect. */
@media (min-width: 768px) {
  .u-mobile-only { display: none !important; }
}
@media (max-width: 767px) {
  .u-desktop-only { display: none !important; }
}

/* Mobile-only block — every selector below is mobile chrome. */
@media (max-width: 767px) {

  /* Mobile launcher tile grid — replaces the desktop home dashboard on
     mobile. Two-column grid of every visible module + (admin only)
     optional empty banner above. v15 fidelity: `gap` and the parent
     `.app-content` horizontal padding both resolve to `var(--space-3)`
     (12 px), matching the v15 mockup edge-to-tile / tile-to-tile
     spacing. The grid itself adds NO horizontal padding; the parent
     override below ("Home-launcher edge spacing") trims `.app-content`
     to 12 px on mobile only when `body.is-home`. Bottom padding here
     clears the floating bottom-nav. */
  .apps-grid {
    --border-card-fallback: rgba(15, 23, 42, 0.08);
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-3);
    padding: 0 0 calc(var(--space-3) + 96px);
    align-content: start;
  }
  /* Home-launcher edge spacing — override the baseline `.app-content`
     16 px horizontal padding so edge-to-tile = tile-to-tile = 12 px,
     matching the v15 mockup. Scoped to mobile + home page only so
     other module pages (which actually need the 16 px breathing
     room) are untouched. */
  body.is-home .app-content {
    padding-left: var(--space-3);
    padding-right: var(--space-3);
  }

  .apps-grid__empty-banner {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3);
    background: var(--brand-primary-light);
    border: 1px solid var(--border-card, var(--border-card-fallback));
    border-radius: var(--radius-md);
    text-decoration: none;
    color: inherit;
  }
  .apps-grid__empty-icon {
    width: 36px;
    height: 36px;
    border-radius: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--brand-primary);
    color: var(--text-inverse, #fff);
    flex-shrink: 0;
  }
  .apps-grid__empty-icon svg { width: 20px; height: 20px; stroke-width: 2; }
  .apps-grid__empty-body {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
  }
  .apps-grid__empty-title {
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--text-primary);
  }
  .apps-grid__empty-text {
    font-size: var(--text-xs);
    color: var(--text-secondary);
  }
  .apps-grid__empty-chev { color: var(--text-tertiary); flex-shrink: 0; }
  .apps-grid__empty-chev svg { width: 16px; height: 16px; stroke-width: 2; }

  .app-tile {
    --border-card-fallback: rgba(15, 23, 42, 0.08);
    /* Anchor for the absolutely-positioned SOON / FUTURE corner badge. */
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2);
    padding: var(--space-3);
    background: var(--bg-surface);
    border: 1px solid var(--border-card, var(--border-card-fallback));
    border-radius: var(--radius-md);
    text-align: left;
    text-decoration: none;
    color: inherit;
    /* Fixed height so every tile in the grid is visually identical,
       independent of whether it has a sub-line or not. icon (36 px)
       + name (~19 px line) + sub-line (~17 px line) + 24 px vertical
       padding + 2x var(--space-2) inner gaps ≈ 124 px. Long sub-lines
       are truncated by the .app-tile__sub rule below rather than
       stretching the tile. */
    height: 124px;
    transition: transform 100ms;
  }
  .app-tile:active { transform: scale(0.98); }
  /* Greyed-out variant for SOON (non-admin) + FUTURE tiles. Mirrors the
     desktop sidebar's `.nav-item--soon` treatment in app.css: muted text +
     icon, no press feedback. The corner badge carries the explicit state. */
  .app-tile--soon {
    cursor: default;
  }
  .app-tile--soon:active { transform: none; }
  .app-tile--soon .app-tile__icon {
    background: var(--bg-surface-alt);
    color: var(--text-tertiary);
  }
  .app-tile--soon .app-tile__name {
    color: var(--text-tertiary);
  }
  /* Tiny chip in the top-right corner, same visual as the desktop sidebar's
     `nav-item__soon-badge`. The `--future` modifier is a class hook so we can
     re-skin SOON vs FUTURE later without touching the template. */
  .app-tile__soon-badge {
    position: absolute;
    top: var(--space-2);
    right: var(--space-2);
    font-size: 10px;
    font-weight: var(--weight-bold);
    color: var(--text-tertiary);
    background: var(--bg-surface-alt);
    padding: 1px 5px;
    border-radius: var(--radius-sm);
    letter-spacing: 0.04em;
    text-transform: uppercase;
  }
  .app-tile__icon {
    width: 36px;
    height: 36px;
    border-radius: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(15, 76, 129, 0.10);
    color: var(--brand-primary);
    flex-shrink: 0;
  }
  .app-tile__icon svg { width: 20px; height: 20px; stroke-width: 2; }
  .app-tile__name {
    font-size: var(--text-base);
    font-weight: var(--weight-semibold);
    color: var(--text-primary);
    line-height: 1.2;
  }
  .app-tile__sub {
    font-size: var(--text-xs);
    color: var(--text-secondary);
    margin-top: -2px;
    /* v15 launcher rule — sub-lines are single-line glances. A widget
       loader returning verbose desktop-card prose ("1 projects without
       a diary today") gets truncated with an ellipsis instead of
       wrapping and breaking the fixed-height tile grid. The fix to
       the underlying widget copy is a follow-up per-module pass. */
    max-width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .app-tile__sub--alert {
    --severity-high-fallback: #dc2626;
    color: var(--severity-high, var(--severity-high-fallback));
    font-weight: var(--weight-semibold);
  }
  /* v15 — brand-primary `--ok` accent for "all good / on it" states
     such as Attendance "Clocked in · 2:14". Distinct from `--alert`
     (red) and the neutral default (secondary text). */
  .app-tile__sub--ok {
    color: var(--brand-primary);
    font-weight: var(--weight-semibold);
  }

  /* Dark-mode tweaks per v15 — softer accent on dark surfaces. */
  [data-theme="dark"] .app-tile__icon {
    background: rgba(147, 197, 253, 0.12);
    color: #93c5fd;
  }
  [data-theme="dark"] .bottom-nav__item--active {
    color: #93c5fd;
  }
  [data-theme="dark"] .app-tile__sub--alert {
    color: #f87171;
  }
  [data-theme="dark"] .app-tile__sub--ok {
    color: #93c5fd;
  }

  /* Hide module-specific in-content title bars on mobile — the chrome
     topbar already carries the page title. Without these rules the
     module page renders the title twice (e.g. "Material Requests"
     in chrome + "Material Requests · My requests" inside content).
     `!important` is needed because pages/mr_list.css + pages/photos_index.css
     load AFTER app.css and define `display: flex` for these wrappers. */
  .mr-header__titles { display: none !important; }
  .ph-header { display: none !important; }
  .att-header__title { display: none !important; }
}

/* ============================================================================
 * Phase 0.5 v15 fidelity — module-page card patterns (mobile only).
 *
 * Lifted verbatim from docs/mockups/chrome-spec-v15.html so Asset Register
 * (Pattern A reuse), Diary (Pattern C), Inventory (Pattern E), Photos
 * (Pattern F), and Attendance (Pattern G) match the spec on mobile while
 * leaving desktop pixel-identical. Each module template wraps its
 * existing content in `.<module>-desktop u-desktop-only` and renders a
 * mobile branch using these `v15-*` classes inside `.u-mobile-only`.
 * ============================================================================ */
@media (max-width: 767px) {
  .v15-list { flex: 1 1 auto; overflow-y: auto; padding: var(--space-3) var(--space-3) calc(var(--space-6) + 96px); display: flex; flex-direction: column; gap: var(--space-2); margin-left: calc(-1 * var(--space-4)); margin-right: calc(-1 * var(--space-4)); }
  .v15-filterbar { background: var(--bg-surface); padding: var(--space-3) var(--space-4); display: flex; align-items: center; gap: var(--space-2); border-bottom: 1px solid var(--border-subtle); margin-left: calc(-1 * var(--space-4)); margin-right: calc(-1 * var(--space-4)); position: sticky; top: var(--header-height); z-index: 5; }
  .v15-filterbar__search { flex: 1 1 auto; position: relative; display: flex; align-items: center; }
  .v15-filterbar__search svg { position: absolute; left: var(--space-3); width: 17px; height: 17px; color: var(--text-tertiary); pointer-events: none; stroke-width: 2; }
  .v15-filterbar__search input { width: 100%; height: 40px; padding: 0 var(--space-3) 0 calc(var(--space-3) * 2 + 17px); border: 1px solid var(--border-card, rgba(15,23,42,.08)); border-radius: var(--radius-md); background: var(--bg-app); color: var(--text-primary); font-size: var(--text-base); outline: none; }
  .v15-filterbar__search input::placeholder { color: var(--text-tertiary); }
  .v15-filterbar__btn { height: 40px; padding: 0 var(--space-3); border: 1px solid var(--border-card, rgba(15,23,42,.08)); background: var(--bg-surface); border-radius: var(--radius-md); color: var(--text-primary); font-size: var(--text-sm); font-weight: var(--weight-medium); display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
  .v15-filterbar__btn svg { width: 15px; height: 15px; stroke-width: 2; color: var(--text-secondary); }

  .v15-fab { position: fixed; right: 20px; bottom: calc(96px + env(safe-area-inset-bottom, 12px)); width: 56px; height: 56px; border-radius: 50%; border: 0; background: var(--brand-primary); color: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 2px 4px rgba(15,76,129,.16), 0 6px 16px rgba(15,76,129,.20); z-index: 4; text-decoration: none; }
  .v15-fab svg { width: 24px; height: 24px; stroke-width: 2.5; }

  /* Pattern A reuse — Asset Register */
  .v15-card-a { background: var(--bg-surface); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); padding: var(--space-3) var(--space-4); box-shadow: 0 1px 2px rgba(15,23,42,.04); display: grid; grid-template-columns: 1fr 56px; gap: var(--space-3); align-items: center; min-height: 76px; text-decoration: none; color: inherit; }
  .v15-card-a__main { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
  .v15-card-a__topline { font-size: var(--text-xs); font-weight: var(--weight-medium); color: var(--text-tertiary); letter-spacing: 0.02em; }
  .v15-card-a__title { font-size: var(--text-md, 17px); font-weight: var(--weight-semibold); color: var(--text-primary); letter-spacing: -0.014em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.3; }
  .v15-card-a__pills { display: flex; gap: 6px; margin-top: 5px; flex-wrap: wrap; }
  .v15-card-a__thumb { width: 56px; height: 56px; border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center; flex-shrink: 0; border: 1px solid var(--border-subtle); background: var(--brand-primary-light); color: var(--brand-primary); }
  .v15-card-a__thumb svg { width: 28px; height: 28px; stroke-width: 1.6; }

  .v15-status-pill { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px 2px 6px; border-radius: var(--radius-full); font-size: 10px; font-weight: var(--weight-bold); letter-spacing: 0.04em; text-transform: uppercase; }
  .v15-status-pill__dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
  .v15-status-pill--active { background: rgb(222 240 226); color: rgb(30 87 40); }
  .v15-status-pill--active .v15-status-pill__dot { background: rgb(30 87 40); }
  .v15-status-pill--in_service { background: rgb(253 239 220); color: rgb(196 90 0); }
  .v15-status-pill--in_service .v15-status-pill__dot { background: rgb(196 90 0); }
  .v15-status-pill--retired { background: rgb(235 237 240); color: rgb(82 92 105); }
  .v15-status-pill--retired .v15-status-pill__dot { background: rgb(82 92 105); }
  .v15-pill-age { font-size: 10px; font-weight: var(--weight-semibold); padding: 2px 8px; border-radius: var(--radius-full); background: rgb(235 237 240); color: rgb(82 92 105); }

  /* Pattern C — Diary */
  .v15-card-c { background: var(--bg-surface); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); padding: var(--space-3) var(--space-4) var(--space-3) var(--space-3); box-shadow: 0 1px 2px rgba(15,23,42,.04); display: grid; grid-template-columns: 56px 1fr; gap: var(--space-3); align-items: center; min-height: 84px; text-decoration: none; color: inherit; }
  .v15-card-c__date { width: 56px; height: 56px; border-radius: var(--radius-md); background: var(--brand-primary-light); border: 1px solid var(--border-subtle); color: var(--brand-primary-dark, #0a3a64); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; flex-shrink: 0; font-variant-numeric: tabular-nums; }
  .v15-card-c--today .v15-card-c__date { background: var(--brand-primary); color: #fff; border-color: var(--brand-primary); }
  .v15-card-c--today .v15-card-c__date-month { color: rgba(255,255,255,.85); }
  .v15-card-c__date-day { font-size: var(--text-2xl); font-weight: var(--weight-bold); line-height: 1; }
  .v15-card-c__date-month { font-size: 10px; font-weight: var(--weight-bold); color: var(--brand-primary); letter-spacing: 0.08em; text-transform: uppercase; line-height: 1; }
  .v15-card-c__main { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
  .v15-card-c__title { font-size: var(--text-md, 17px); font-weight: var(--weight-semibold); color: var(--text-primary); letter-spacing: -0.014em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.3; }
  .v15-card-c__sub { font-size: var(--text-sm); color: var(--text-secondary); font-weight: var(--weight-medium); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .v15-card-c__meta { display: flex; gap: var(--space-3); align-items: center; margin-top: 4px; font-size: var(--text-xs); color: var(--text-tertiary); font-weight: var(--weight-medium); }
  .v15-card-c__meta-item { display: inline-flex; align-items: center; gap: 4px; }
  .v15-card-c__meta-item--ok { color: rgb(30 87 40); }
  .v15-card-c__meta-item--warn { color: rgb(196 90 0); }

  /* Pattern E — Inventory */
  .v15-card-e { background: var(--bg-surface); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); padding: var(--space-3) var(--space-4); box-shadow: 0 1px 2px rgba(15,23,42,.04); display: grid; grid-template-columns: 1fr auto; gap: var(--space-3); align-items: center; min-height: 76px; text-decoration: none; color: inherit; }
  .v15-card-e__main { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
  .v15-card-e__title { font-size: var(--text-md, 17px); font-weight: var(--weight-semibold); color: var(--text-primary); letter-spacing: -0.014em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.3; }
  .v15-card-e__sub { font-size: var(--text-xs); color: var(--text-tertiary); font-weight: var(--weight-medium); }
  .v15-card-e__gauge { display: flex; gap: 2px; margin-top: 6px; width: 90px; }
  .v15-card-e__gauge-seg { flex: 1 1 0; height: 8px; border-radius: 1.5px; background: var(--border-subtle); }
  .v15-card-e__gauge-seg--ok { background: rgb(30 87 40); }
  .v15-card-e__gauge-seg--warn { background: rgb(196 90 0); }
  .v15-card-e__gauge-seg--crit { background: rgb(198 40 40); }
  .v15-card-e__qty { display: flex; flex-direction: column; align-items: flex-end; flex-shrink: 0; min-width: 80px; }
  .v15-card-e__qty-num { font-size: var(--text-xl); font-weight: var(--weight-bold); color: var(--text-primary); letter-spacing: -0.02em; line-height: 1; }
  .v15-card-e__qty-unit { font-size: var(--text-xs); color: var(--text-tertiary); font-weight: var(--weight-medium); letter-spacing: 0.04em; text-transform: uppercase; margin-top: 2px; }
  .v15-card-e__status { font-size: 10px; font-weight: var(--weight-bold); letter-spacing: 0.04em; text-transform: uppercase; padding: 2px 6px; border-radius: var(--radius-sm); margin-top: 5px; }
  .v15-card-e__status--ok { background: rgb(222 240 226); color: rgb(30 87 40); }
  .v15-card-e__status--warn { background: rgb(253 239 220); color: rgb(196 90 0); }
  .v15-card-e__status--crit { background: rgb(253 236 235); color: rgb(198 40 40); }

  /* Pattern F — Site Photos */
  .v15-capture-zone { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); padding: var(--space-4); }
  .v15-capture-btn { height: 100px; border-radius: var(--radius-md); border: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; cursor: pointer; font-weight: var(--weight-bold); font-size: var(--text-sm); text-decoration: none; }
  .v15-capture-btn--camera { background: var(--brand-primary); color: #fff; box-shadow: 0 2px 4px rgba(15,76,129,.16), 0 6px 16px rgba(15,76,129,.20); }
  .v15-capture-btn--gallery { background: var(--bg-surface); color: var(--text-primary); border: 1px solid var(--border-card, rgba(15,23,42,.08)); box-shadow: 0 1px 2px rgba(15,23,42,.04); }
  .v15-capture-btn svg { width: 30px; height: 30px; stroke-width: 2; }

  .v15-photo-section-label { display: flex; align-items: baseline; justify-content: space-between; font-size: var(--text-xs); font-weight: var(--weight-bold); color: var(--text-secondary); letter-spacing: 0.06em; text-transform: uppercase; padding: var(--space-4) var(--space-3) var(--space-2); }
  .v15-photo-section-label__count { color: var(--text-tertiary); font-weight: var(--weight-medium); }
  .v15-photo-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 2px; padding: 0 var(--space-3); }
  .v15-photo-tile { aspect-ratio: 1 / 1; border-radius: var(--radius-sm); overflow: hidden; position: relative; background: var(--bg-hover); }
  .v15-photo-tile img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .v15-photo-tile__placeholder { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #93c5fd, #6366f1); color: white; }
  .v15-photo-tile__placeholder svg { width: 28px; height: 28px; stroke-width: 1.5; }
  .v15-photo-tile__count { position: absolute; inset: 0; background: rgba(0,0,0,.55); color: white; display: flex; align-items: center; justify-content: center; font-size: var(--text-md, 17px); font-weight: var(--weight-bold); }

  /* Pattern G — Attendance */
  .v15-att-state { margin: var(--space-3); border-radius: var(--radius-lg); padding: var(--space-5) var(--space-4); display: flex; flex-direction: column; gap: var(--space-3); box-shadow: 0 1px 2px rgba(15,23,42,.04); }
  .v15-att-state--in { background: radial-gradient(circle at 25% 0%, rgba(255,255,255,.18), transparent 60%), linear-gradient(135deg, #15532b 0%, #16a34a 60%, #22c55e 100%); color: white; box-shadow: 0 4px 16px rgba(22,163,74,.25), inset 0 1px 0 rgba(255,255,255,.10); }
  .v15-att-state--out { background: var(--bg-surface); color: var(--text-primary); border: 1px solid var(--border-card, rgba(15,23,42,.08)); }
  .v15-att-state__status { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-md, 17px); font-weight: var(--weight-semibold); }
  .v15-att-state__dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
  .v15-att-state--in .v15-att-state__dot { background: #6ee7b7; box-shadow: 0 0 0 4px rgba(110,231,183,.30); animation: v15-att-pulse 2s ease-in-out infinite; }
  .v15-att-state--out .v15-att-state__dot { background: var(--text-tertiary); }
  @keyframes v15-att-pulse { 0%, 100% { box-shadow: 0 0 0 4px rgba(110,231,183,.30); } 50% { box-shadow: 0 0 0 8px rgba(110,231,183,.10); } }
  .v15-att-state__since { font-size: var(--text-md, 17px); font-weight: var(--weight-medium); color: var(--text-secondary); }
  .v15-att-state--in .v15-att-state__since { color: rgba(255,255,255,.85); }
  .v15-att-state__duration { display: flex; align-items: baseline; gap: 8px; margin: var(--space-1) 0; }
  .v15-att-state__duration-num { font-size: 44px; font-weight: var(--weight-bold); line-height: 1; letter-spacing: -0.04em; font-variant-numeric: tabular-nums; }
  .v15-att-state__duration-unit { font-size: var(--text-sm); font-weight: var(--weight-semibold); letter-spacing: 0.06em; text-transform: uppercase; opacity: 0.78; }
  .v15-att-state__chip { display: inline-flex; align-items: center; gap: 6px; padding: 5px 12px; border-radius: var(--radius-full); font-size: var(--text-xs); font-weight: var(--weight-bold); align-self: flex-start; }
  .v15-att-state--in .v15-att-state__chip { background: rgba(255,255,255,.18); color: rgba(255,255,255,.96); }
  .v15-att-state--out .v15-att-state__chip { background: var(--bg-app); color: var(--text-secondary); }
  .v15-att-state__action { margin-top: var(--space-2); height: 56px; border-radius: var(--radius-md); border: 0; font-size: var(--text-base); font-weight: var(--weight-bold); cursor: pointer; display: flex; align-items: center; justify-content: center; gap: var(--space-2); text-decoration: none; }
  .v15-att-state__action svg { width: 22px; height: 22px; stroke-width: 2.5; }
  .v15-att-state__action--out { background: rgba(255,255,255,.18); color: white; border: 1.5px solid rgba(255,255,255,.32); }
  .v15-att-state__action--in { background: var(--brand-primary); color: #fff; box-shadow: 0 2px 4px rgba(15,76,129,.16), 0 6px 16px rgba(15,76,129,.20); }
  .v15-att-state__hint { display: flex; align-items: center; gap: 6px; font-size: var(--text-xs); color: var(--text-secondary); font-weight: var(--weight-medium); margin-top: var(--space-1); }
  .v15-att-state__hint svg { width: 14px; height: 14px; stroke-width: 2; flex-shrink: 0; color: var(--text-tertiary); }
  .v15-att-state__field { display: flex; flex-direction: column; gap: 4px; }
  .v15-att-state__field-label { font-size: var(--text-xs); font-weight: var(--weight-bold); color: var(--text-tertiary); letter-spacing: 0.04em; text-transform: uppercase; }
  .v15-att-state__select { height: 44px; padding: 0 var(--space-3); border-radius: var(--radius-md); border: 1px solid var(--border-card, rgba(15,23,42,.08)); background: var(--bg-surface); color: var(--text-primary); font-size: var(--text-base); font-weight: var(--weight-medium); }

  .v15-att-stats { margin: 0 var(--space-3); background: var(--bg-surface); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); padding: var(--space-3) var(--space-4); display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); box-shadow: 0 1px 2px rgba(15,23,42,.04); }
  .v15-att-stats__label { font-size: var(--text-xs); font-weight: var(--weight-bold); color: var(--text-tertiary); letter-spacing: 0.04em; text-transform: uppercase; }
  .v15-att-stats__value { font-size: var(--text-2xl); font-weight: var(--weight-bold); color: var(--text-primary); letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
  .v15-att-stats__hint { font-size: var(--text-xs); color: var(--text-tertiary); font-weight: var(--weight-medium); margin-top: 2px; }

  .v15-att-recent { padding: var(--space-2) var(--space-3) calc(var(--space-6) + 96px); display: flex; flex-direction: column; gap: var(--space-2); }
  .v15-att-recent__heading { font-size: var(--text-xs); font-weight: var(--weight-bold); color: var(--text-tertiary); letter-spacing: 0.06em; text-transform: uppercase; padding: var(--space-2) var(--space-2) var(--space-1); }
  .v15-att-row { display: grid; grid-template-columns: 48px 1fr auto; gap: var(--space-3); align-items: center; padding: var(--space-3); background: var(--bg-surface); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); box-shadow: 0 1px 2px rgba(15,23,42,.04); text-decoration: none; color: inherit; }
  .v15-att-row__date { width: 48px; height: 48px; border-radius: var(--radius-md); background: var(--brand-primary-light); color: var(--brand-primary-dark, #0a3a64); display: flex; flex-direction: column; align-items: center; justify-content: center; flex-shrink: 0; font-variant-numeric: tabular-nums; }
  .v15-att-row__date-day { font-size: var(--text-xl); font-weight: var(--weight-bold); line-height: 1; }
  .v15-att-row__date-month { font-size: 9px; font-weight: var(--weight-bold); color: var(--brand-primary); letter-spacing: 0.08em; text-transform: uppercase; line-height: 1; margin-top: 2px; }
  .v15-att-row__main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
  .v15-att-row__title { font-size: var(--text-sm); font-weight: var(--weight-semibold); color: var(--text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .v15-att-row__meta { font-size: var(--text-xs); color: var(--text-tertiary); display: flex; align-items: center; gap: 6px; }
  .v15-att-row__status-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
  .v15-att-row__status-dot--ok { background: rgb(30 87 40); }
  .v15-att-row__status-dot--pending { background: rgb(196 90 0); }
  .v15-att-row__hours { font-size: var(--text-md, 17px); font-weight: var(--weight-bold); color: var(--text-primary); font-variant-numeric: tabular-nums; }

  /* Empty state shared across module pages */
  .v15-empty { padding: var(--space-6) var(--space-4); display: flex; flex-direction: column; align-items: center; gap: var(--space-3); text-align: center; }
  .v15-empty__icon { width: 64px; height: 64px; border-radius: var(--radius-full); background: var(--brand-primary-light); display: flex; align-items: center; justify-content: center; color: var(--brand-primary); }
  .v15-empty__icon svg { width: 28px; height: 28px; stroke-width: 1.75; }
  .v15-empty__title { font-size: var(--text-md, 17px); font-weight: var(--weight-semibold); color: var(--text-primary); }
  .v15-empty__text { font-size: var(--text-sm); color: var(--text-secondary); max-width: 260px; line-height: 1.5; }

  /* Dark theme overrides */
  [data-theme="dark"] .v15-card-a__thumb,
  [data-theme="dark"] .v15-card-c__date,
  [data-theme="dark"] .v15-att-row__date,
  [data-theme="dark"] .v15-empty__icon {
    background: rgba(147, 197, 253, 0.14);
    color: #93c5fd;
    border-color: rgba(147, 197, 253, 0.18);
  }
  [data-theme="dark"] .v15-card-c__date-month,
  [data-theme="dark"] .v15-att-row__date-month {
    color: #93c5fd;
  }
}
