/* PetroLink Design System — PetroOps Style */

:root {
  --brand:          #10B981;
  --brand-dark:     #059669;
  --brand-dim:      #064E3B;
  --brand-glow:     rgba(16,185,129,0.14);
  --brand-rgb:      16,185,129;
  --accent:         #38BDF8;
  --accent-dark:    #0284C7;
  --status-green:   #22C55E;
  --status-red:     #EF4444;
  --status-yellow:  #F59E0B;
  --status-blue:    #38BDF8;
  --status-orange:  #F97316;
  --status-purple:  #A78BFA;
  --brand-ink:      #04140F;              /* text on emerald fills */
  --font-sans:      'Barlow', sans-serif;
  --font-cond:      'Barlow Condensed', sans-serif;
  --font-mono:      'JetBrains Mono', monospace;

  /* Radii — refined (modern-minimal rollout) */
  --radius-sm: 6px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --r-control: 8px;    /* buttons, inputs */
  --r-pill:    999px;

  /* Spacing — 4-pt scale, named by role */
  --space-2xs: 0.25rem;  --space-xs: 0.5rem;   --space-sm: 0.75rem;
  --space-md:  1rem;      --space-lg: 1.5rem;   --space-xl: 2.5rem;
  --space-2xl: 4rem;      --space-3xl: 6rem;

  /* Type scale */
  --text-2xl:       clamp(1.9rem, 1.4rem + 1.6vw, 2.4rem);
  --tracking-tight: -0.015em;

  /* Motion — one easing, honoured everywhere */
  --ease-out:    cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --dur-short:   140ms;
  --dur:         200ms;
}

:root, [data-theme="light"] {
  --bg-void:    #F4F7F6;
  --bg-deep:    #FFFFFF;
  --bg-panel:   #FFFFFF;
  --bg-raised:  #EEF4F2;
  --border-col: #E4EDEA;                  /* refined: softer, less-saturated */
  --muted-col:  #8AADA4;
  --text-primary:   #0C1F1A;
  --text-secondary: #1E3530;
  --text-tertiary:  #4A706A;
  --text-disabled:  #A8C4BF;
  /* Neutral elevation — no more emerald-tinted glow */
  --shadow-panel: 0 1px 2px rgba(12,31,26,0.04), 0 1px 3px rgba(12,31,26,0.05);
  --shadow-hover: 0 6px 20px rgba(12,31,26,0.08), 0 2px 6px rgba(12,31,26,0.05);
  --focus-ring:   0 0 0 3px rgba(var(--brand-rgb), 0.30);
  /* Badge colors — WCAG AA on light backgrounds */
  --badge-green-bg:   #DCFCE7; --badge-green-text:  #166534;
  --badge-red-bg:     #FEE2E2; --badge-red-text:    #991B1B;
  --badge-yellow-bg:  #FEF3C7; --badge-yellow-text: #92400E;
  --badge-blue-bg:    #DBEAFE; --badge-blue-text:   #1E40AF;
  --badge-orange-bg:  #FFEDD5; --badge-orange-text: #9A3412;
}

[data-theme="dark"] {
  --bg-void:    #0A0F0D;
  --bg-deep:    #101814;
  --bg-panel:   #172018;
  --bg-raised:  #1E2B22;
  --border-col: #223229;                  /* refined */
  --muted-col:  #3A5C4A;
  --text-primary:   #E8F5F0;
  --text-secondary: #B8D4CA;
  --text-tertiary:  #7AA898;
  --text-disabled:  #3A5C4A;
  --shadow-panel: 0 1px 2px rgba(0,0,0,0.40);
  --shadow-hover: 0 6px 22px rgba(0,0,0,0.55);
  --focus-ring:   0 0 0 3px rgba(var(--brand-rgb), 0.40);
  /* Badge colors — neon-on-dark style */
  --badge-green-bg:   rgba(34,197,94,0.12);  --badge-green-text:  #22C55E;
  --badge-red-bg:     rgba(239,68,68,0.12);  --badge-red-text:    #EF4444;
  --badge-yellow-bg:  rgba(245,158,11,0.12); --badge-yellow-text: #F59E0B;
  --badge-blue-bg:    rgba(56,189,248,0.12); --badge-blue-text:   #38BDF8;
  --badge-orange-bg:  rgba(249,115,22,0.12); --badge-orange-text: #F97316;
}

/* Accent themes — light surfaces (inherited from :root) with a recolored brand. */
[data-theme="blue"] {
  --brand:      #3B82F6;
  --brand-dark: #2563EB;
  --brand-dim:  #1E3A8A;
  --brand-glow: rgba(59,130,246,0.14);
  --brand-rgb:  59,130,246;
}

[data-theme="red"] {
  --brand:      #EF4444;
  --brand-dark: #DC2626;
  --brand-dim:  #7F1D1D;
  --brand-glow: rgba(239,68,68,0.14);
  --brand-rgb:  239,68,68;
}

* { box-sizing: border-box; margin: 0; padding: 0; }
body {
  font-family: var(--font-sans);
  background: var(--bg-void);
  color: var(--text-secondary);
  font-size: 15px;
  line-height: 1.6;
}

/* ── TOP NAV ── */
.topnav {
  position: fixed; top: 0; left: 0; right: 0; height: 56px; z-index: 1000;
  background: var(--bg-deep); border-bottom: 1px solid var(--border-col);
  display: flex; align-items: center; padding: 0 1.5rem; gap: 1rem;
  box-shadow: var(--shadow-panel);
}
.brand-name {
  font-family: var(--font-cond); font-weight: 700; color: var(--brand);
  letter-spacing: 0.06em; text-transform: uppercase; font-size: 1.1rem;
  text-decoration: none; white-space: nowrap;
}
.brand-name .brand-hex {
  display: inline-block; width: 22px; height: 22px; background: var(--brand);
  clip-path: polygon(50% 0%,90% 25%,90% 75%,50% 100%,10% 75%,10% 25%);
  margin-right: 6px; vertical-align: middle;
}

/* ── SIDEBAR ── */
.sidebar {
  position: fixed; top: 56px; left: 0; width: 220px; bottom: 0;
  background: var(--bg-deep); border-right: 1px solid var(--border-col);
  overflow-y: auto; overflow-x: hidden; z-index: 900;
  transition: width 0.22s ease;
  display: flex; flex-direction: column;
}
.sidebar-nav { flex: 1; overflow-y: auto; overflow-x: hidden; }
.sidebar-item {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 18px;
  color: var(--text-tertiary); text-decoration: none;
  font-family: var(--font-sans); font-size: 0.9rem; font-weight: 500;
  letter-spacing: 0; text-transform: none;
  border-left: 3px solid transparent;
  transition: color var(--dur-short) var(--ease-out),
              background-color var(--dur-short) var(--ease-out),
              border-color var(--dur-short) var(--ease-out);
  white-space: nowrap;
}
.sidebar-item:hover { color: var(--text-primary); background: var(--bg-raised); }
.sidebar-item.active { color: var(--brand); font-weight: 600; border-left-color: var(--brand); background: var(--brand-glow); }
.sidebar-item i { font-size: 1rem; flex-shrink: 0; }
.sidebar-label { overflow: hidden; transition: opacity 0.15s, width 0.22s; }
.sidebar-section {
  font-family: var(--font-mono); font-size: 0.6rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--muted-col);
  padding: 16px 18px 6px; margin-top: 4px;
  white-space: nowrap; overflow: hidden;
  transition: opacity 0.15s;
}

/* Sidebar collapse toggle button */
.sidebar-collapse-btn {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 18px;
  color: var(--text-tertiary); cursor: pointer;
  font-family: var(--font-sans); font-size: 0.9rem; font-weight: 500;
  letter-spacing: 0; text-transform: none;
  border-top: 1px solid var(--border-col);
  border-left: 3px solid transparent;
  transition: color var(--dur-short) var(--ease-out),
              background-color var(--dur-short) var(--ease-out);
  user-select: none;
  white-space: nowrap;
  flex-shrink: 0;
}
.sidebar-collapse-btn:hover { color: var(--text-primary); background: var(--bg-raised); }
.sidebar-collapse-btn i { font-size: 1rem; flex-shrink: 0; }

/* ── COLLAPSED STATE (desktop only) ── */
.sidebar.sidebar-collapsed { width: 56px; }
.sidebar.sidebar-collapsed .sidebar-label { opacity: 0; width: 0; }
.sidebar.sidebar-collapsed .sidebar-section { opacity: 0; padding-top: 8px; padding-bottom: 8px; }
.sidebar.sidebar-collapsed .sidebar-item {
  padding: 10px 0; justify-content: center; gap: 0;
}
.sidebar.sidebar-collapsed .sidebar-collapse-btn {
  padding: 10px 0; justify-content: center; gap: 0;
}
.sidebar.sidebar-collapsed ~ .main-content { margin-left: 56px; }

/* ── MAIN CONTENT ── */
.main-content {
  margin-left: 220px; margin-top: 56px; padding: 2rem;
  min-height: calc(100vh - 56px);
  transition: margin-left 0.22s ease;
}
.page-header { margin-bottom: 1.75rem; }
.page-eyebrow {
  font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--text-tertiary); margin-bottom: 4px;
  display: flex; align-items: center; gap: 8px;
}
.page-eyebrow::before { content: ''; display: inline-block; width: 18px; height: 1px; background: var(--brand); }
.page-title {
  font-family: var(--font-cond); font-size: var(--text-2xl); font-weight: 600;
  color: var(--text-primary); line-height: 1.08; letter-spacing: var(--tracking-tight);
}
.breadcrumb-petro {
  display: flex; flex-direction: row; align-items: center; gap: 6px;
  font-family: var(--font-mono); font-size: 0.72rem;
  color: var(--muted-col); list-style: none; flex-wrap: nowrap;
  padding: 0; padding-left: 0; margin: 0 0 8px;
}
.breadcrumb-petro li { padding: 0; margin: 0; list-style: none; flex-shrink: 0; }
.breadcrumb-petro li a { color: var(--text-tertiary); text-decoration: none; }
.breadcrumb-petro li a:hover { color: var(--brand); }
.breadcrumb-petro li.active { color: var(--text-secondary); }
.breadcrumb-petro .sep { color: var(--border-col); user-select: none; }
.page-meta {
  font-family: var(--font-mono); font-size: 0.72rem; color: var(--muted-col);
  display: flex; flex-direction: row; align-items: center; gap: 20px; flex-wrap: wrap; margin-top: 4px;
}

/* ── PHOTO GRID ── */
.photo-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1rem;
  padding: 1rem;
}
@media (max-width: 575px) { .photo-grid { grid-template-columns: 1fr; } }

/* ── HTMX LOADING INDICATOR ── */
.htmx-indicator { opacity: 0; transition: opacity 200ms ease-in; pointer-events: none; }
.htmx-request .htmx-indicator { opacity: 1; }
.htmx-request.htmx-indicator { opacity: 1; }

/* ── STAT CARD ── */
.stat-card {
  background: var(--bg-panel); border: 1px solid var(--border-col);
  border-radius: var(--radius-md); padding: var(--space-lg);
  box-shadow: var(--shadow-panel);
  transition: box-shadow var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out),
              transform var(--dur) var(--ease-out);
}
.stat-card:hover { box-shadow: var(--shadow-hover); }
a.stat-card-link { display:block; text-decoration:none; color:inherit; cursor:pointer; }
a.stat-card-link:hover,
a.settings-link-card:hover {
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--brand) 45%, var(--border-col));
  box-shadow: var(--shadow-hover);
}
a.settings-link-card { color: inherit;
  transition: box-shadow var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out),
              transform var(--dur) var(--ease-out); }
.stat-value {
  font-family: var(--font-cond); font-size: 2.2rem; font-weight: 700;
  color: var(--text-primary); line-height: 1;
  font-variant-numeric: tabular-nums;
}
.stat-label {
  font-family: var(--font-mono); font-size: 0.65rem; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--muted-col); margin-top: 4px;
}

/* ── DATA TABLE ── */
.data-table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
.data-table th {
  font-family: var(--font-mono); font-size: 0.65rem; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--muted-col);
  padding: 0.6rem 1rem; border-bottom: 1px solid var(--border-col);
  text-align: left; background: var(--bg-raised); font-weight: 600;
}
.data-table td { padding: 0.75rem 1rem; border-bottom: 1px solid var(--border-col); }
.data-table tr:last-child td { border-bottom: none; }
.data-table tbody tr:hover td { background: var(--bg-raised); }

/* Clickable drill-down rows (#481): any <tr data-href="…"> navigates to that
   URL when the row is clicked. Interactive children (links, buttons, form
   fields) still behave normally — see the delegated handler in base.html.
   Keep this the single source of truth: pages should NOT re-declare cursor /
   hover for [data-href] rows, and should NOT set an inline onclick. */
.data-table tbody tr[data-href] { cursor: pointer; }
.data-table tbody tr[data-href]:hover td { background: var(--brand-glow); }
.data-table tbody tr[data-href]:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: -2px;
}

/* ── BADGE STATUS ── */
.badge-status {
  font-family: var(--font-mono); font-size: 0.65rem; letter-spacing: 0.06em;
  text-transform: uppercase; padding: 3px 8px; border-radius: var(--radius-sm); font-weight: 600;
}
.badge-green  { background: var(--badge-green-bg);  color: var(--badge-green-text); }
.badge-red    { background: var(--badge-red-bg);    color: var(--badge-red-text); }
.badge-yellow { background: var(--badge-yellow-bg); color: var(--badge-yellow-text); }
.badge-blue   { background: var(--badge-blue-bg);   color: var(--badge-blue-text); }
.badge-orange { background: var(--badge-orange-bg); color: var(--badge-orange-text); }
/* Neutral chip for history / ended states (#581). Built from the existing
   surface tokens rather than new colour variables so it stays theme-aware. */
.badge-grey   { background: var(--bg-raised); color: var(--text-tertiary); }

/* Emergency / rush urgency badge (#394 item 4). Layered on .badge-red so it
   inherits the theme-aware red, plus a border and icon gap that make it read
   as louder than an ordinary red status chip at a glance. */
.badge-emergency {
  display: inline-flex; align-items: center; gap: 4px;
  border: 1px solid var(--status-red);
  white-space: nowrap;
}

/* ── STATUS PIPELINE (#711) ──────────────────────────────────────────────
   The dot-and-connector progress indicator shown at the top of a document
   that moves through a known sequence of states. Shipped for invoices
   (draft → sent → received) with its CSS inline in invoice_detail.html;
   generalised here when quotes needed the same aid.

   It lives in this stylesheet rather than in a page's <style> block because
   the markup is now a shared partial, and check_undefined_css.py treats a
   partial relying on its parent's inline CSS as a hard failure — that is the
   HTMX-fragment bug from #435, and it would be a real one here the first time
   this component is rendered into a swap target.

   Three step states: `done` (behind you), `active` (where you are), and
   `terminal` (where it stopped — rejected, cancelled, superseded). Terminal
   is the quote case; a document that ends off the happy path still travelled
   the steps before it, and that history is the useful part. */
.status-pipeline {
  display: flex;
  align-items: center;
  gap: 0;
}
.pipeline-step {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.25rem;
}
.pipeline-dot {
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 50%;
  border: 2px solid var(--border-col);
  background: var(--bg-raised);
  transition: background 0.2s, border-color 0.2s, box-shadow 0.2s;
}
.pipeline-step.done .pipeline-dot {
  background: var(--brand);
  border-color: var(--brand);
}
.pipeline-step.active .pipeline-dot {
  background: var(--brand);
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-glow);
}
.pipeline-step.terminal .pipeline-dot {
  background: var(--status-red);
  border-color: var(--status-red);
}
.pipeline-label {
  font-family: var(--font-mono);
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted-col);
  white-space: nowrap;
}
.pipeline-step.done .pipeline-label { color: var(--brand); }
.pipeline-step.active .pipeline-label { color: var(--text-primary); font-weight: 700; }
.pipeline-step.terminal .pipeline-label { color: var(--status-red); font-weight: 700; }
/* The record a step produced (#713) — e.g. the job a quote was promoted into.
   Inherits the label's size and casing so it reads as part of the step rather
   than a button bolted onto it, but takes the brand colour and an underline so
   it is visibly reachable. */
.pipeline-link {
  color: var(--brand);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.pipeline-link:hover { color: var(--brand-dark); }
.pipeline-connector {
  width: 1.75rem;
  height: 2px;
  background: var(--border-col);
  margin-bottom: 1.05rem;
  flex-shrink: 0;
  transition: background 0.2s;
}
.pipeline-connector.done { background: var(--brand); }
/* A connector into a terminal step is the path that was actually taken, so it
   reads as travelled — in the terminal colour, not the brand one. */
.pipeline-connector.terminal { background: var(--status-red); }
@media (max-width: 575px) {
  .status-pipeline { display: none; }
}

/* ── BUTTONS ── */
.btn-brand {
  background: var(--brand); color: var(--brand-ink);
  font-family: var(--font-sans); font-weight: 600; letter-spacing: 0;
  text-transform: none; font-size: 0.9rem;
  border: none; border-radius: var(--r-control); padding: 0.6rem 1.15rem;
  cursor: pointer; display: inline-flex;
  align-items: center; gap: 6px; text-decoration: none;
  box-shadow: var(--shadow-panel);
  transition: background-color var(--dur) var(--ease-out),
              transform 120ms var(--ease-out),
              box-shadow var(--dur) var(--ease-out);
}
.btn-brand:hover { background: var(--brand-dark); color: var(--brand-ink);
  transform: translateY(-1px); box-shadow: var(--shadow-hover); }
.btn-brand:active { transform: translateY(0); }
.btn-ghost {
  background: transparent; color: var(--text-secondary);
  font-family: var(--font-sans); font-weight: 600; letter-spacing: 0;
  text-transform: none; font-size: 0.9rem;
  border: 1px solid var(--border-col); border-radius: var(--r-control);
  padding: 0.6rem 1.15rem; cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px; text-decoration: none;
  transition: background-color var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out),
              color var(--dur) var(--ease-out);
}
.btn-ghost:hover { background: var(--bg-raised); color: var(--text-primary);
  border-color: color-mix(in srgb, var(--brand) 40%, var(--border-col)); }

/* Focus rings appear instantly — never transitioned in. */
.btn-brand:focus-visible,
.btn-ghost:focus-visible,
.form-input:focus-visible,
.btn-block:focus-visible {
  outline: 2px solid transparent;
  box-shadow: var(--focus-ring);
}

/* ── FORM CONTROLS ── */
.form-label-mono {
  font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--muted-col); display: block; margin-bottom: 5px;
}
.form-input {
  width: 100%; background: var(--bg-deep); border: 1px solid var(--border-col);
  border-radius: var(--r-control); color: var(--text-primary);
  padding: 0.6rem 0.85rem; font-family: var(--font-sans); font-size: 0.95rem;
  transition: border-color var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out);
}
.form-input:focus { outline: none; border-color: var(--brand); box-shadow: var(--focus-ring); }
.form-label-mono {
  color: var(--text-tertiary);
}

/* ── ALERTS ── */
.alert-success { background: rgba(34,197,94,0.1);  border-left: 3px solid #22C55E; color: #166534; padding: 0.75rem 1rem; border-radius: var(--radius-sm); }
.alert-danger  { background: rgba(239,68,68,0.1);  border-left: 3px solid #EF4444; color: #991B1B; padding: 0.75rem 1rem; border-radius: var(--radius-sm); }
.alert-warning { background: rgba(245,158,11,0.1); border-left: 3px solid #F59E0B; color: #92400E; padding: 0.75rem 1rem; border-radius: var(--radius-sm); }
.alert-info    { background: rgba(56,189,248,0.1); border-left: 3px solid #38BDF8; color: #0C4A6E; padding: 0.75rem 1rem; border-radius: var(--radius-sm); }

/* ── RESPONSIVE ── */

/* Hamburger: hidden on desktop, shown on tablet/mobile */
.topnav-hamburger { display: none; }

/* Tablet and smaller: hide sidebar off-screen, full-width content */
@media (max-width: 1199.98px) {
  .topnav-hamburger { display: block; }
  .sidebar {
    left: -220px; width: 220px !important;
    transition: left 0.3s ease-in-out;
    z-index: 1050;
  }
  .sidebar.sidebar-open {
    left: 0;
  }
  .sidebar-collapse-btn { display: none; }
  .sidebar-label { opacity: 1 !important; width: auto !important; }
  .sidebar-section { opacity: 1 !important; }
  .sidebar-item { padding: 10px 18px !important; justify-content: flex-start !important; gap: 10px !important; }
  .main-content {
    margin-left: 0 !important;
    padding: 1rem;
    transition: none;
  }
}

/* Backdrop shown behind open sidebar on tablet/mobile */
.sidebar-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 1040;
}
.sidebar-backdrop.active {
  display: block;
}

/* Data tables: horizontal scroll on narrow screens */
.table-responsive-wrapper {
  overflow-x: auto;
  min-width: 0;
}

/* ── GLOBAL SEARCH ── */
.topnav-search {
  flex: 1 1 0;
  max-width: 320px;
  position: relative;
}
.search-wrap {
  position: relative;
  display: flex;
  align-items: center;
}
.search-icon-left {
  position: absolute;
  left: 10px;
  color: var(--muted-col);
  font-size: 0.85rem;
  pointer-events: none;
  z-index: 1;
}
.search-input {
  width: 100%;
  background: var(--bg-raised);
  border: 1px solid var(--border-col);
  border-radius: 20px;
  color: var(--text-primary);
  padding: 0.35rem 2rem 0.35rem 2rem;
  font-family: var(--font-sans);
  font-size: 0.85rem;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.search-input::placeholder { color: var(--muted-col); }
.search-input:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-glow);
  background: var(--bg-deep);
}
.search-slash {
  position: absolute;
  right: 10px;
  background: var(--bg-panel);
  border: 1px solid var(--border-col);
  border-radius: 3px;
  font-family: var(--font-mono);
  font-size: 0.65rem;
  color: var(--muted-col);
  padding: 1px 5px;
  pointer-events: none;
  transition: opacity 0.15s;
}
.search-input:focus ~ .search-slash { opacity: 0; }

/* Dropdown */
.search-dropdown {
  display: none;
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  right: 0;
  background: var(--bg-panel);
  border: 1px solid var(--border-col);
  border-radius: var(--radius-md);
  box-shadow: 0 8px 32px rgba(0,0,0,0.18);
  z-index: 2000;
  overflow: hidden;
  max-height: 480px;
  overflow-y: auto;
  min-width: 320px;
}
.search-dropdown.open { display: block; }
.search-group-header {
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted-col);
  padding: 10px 14px 4px;
  border-top: 1px solid var(--border-col);
  background: var(--bg-raised);
}
.search-group-header:first-child { border-top: none; }
.search-result-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px;
  text-decoration: none;
  color: var(--text-secondary);
  cursor: pointer;
  transition: background 0.1s;
  border: none;
  background: transparent;
  width: 100%;
  text-align: left;
}
.search-result-item:hover,
.search-result-item.active {
  background: var(--brand-glow);
  color: var(--text-primary);
}
.search-result-item i {
  font-size: 0.9rem;
  color: var(--brand);
  flex-shrink: 0;
  width: 18px;
  text-align: center;
}
.search-result-label {
  font-family: var(--font-cond);
  font-weight: 600;
  font-size: 0.9rem;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.search-result-sub {
  font-family: var(--font-mono);
  font-size: 0.65rem;
  color: var(--text-tertiary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 1px;
}
.search-view-all {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 14px 8px 42px;
  font-family: var(--font-mono);
  font-size: 0.65rem;
  letter-spacing: 0.04em;
  color: var(--accent);
  text-decoration: none;
  transition: color 0.1s;
}
.search-view-all:hover { color: var(--accent-dark); }
.search-empty {
  padding: 24px 14px;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--muted-col);
}
.search-loading {
  padding: 16px 14px;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--muted-col);
}

/* Mobile search toggle button */
.search-toggle-btn {
  display: none;
  background: transparent;
  border: none;
  color: var(--brand);
  font-size: 1.15rem;
  padding: 0.3rem 0.5rem;
  cursor: pointer;
  line-height: 1;
}

/* Mobile search overlay (slides down from topnav) */
.search-overlay {
  display: none;
  position: fixed;
  top: 56px;
  left: 0;
  right: 0;
  background: var(--bg-deep);
  border-bottom: 1px solid var(--border-col);
  padding: 10px 1rem;
  z-index: 1100;
  box-shadow: 0 4px 20px rgba(0,0,0,0.12);
}
.search-overlay.open { display: block; }
.search-overlay .search-dropdown {
  left: 0;
  right: 0;
  min-width: 0;
}

@media (max-width: 991.98px) {
  .topnav-search { display: none; }
  .search-toggle-btn { display: block; }
}

/* ── SEARCH RESULTS PAGE ── */
.search-results-section { margin-bottom: 2.5rem; }
.search-results-section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.75rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--border-col);
}
.search-results-section-title {
  font-family: var(--font-cond);
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--text-primary);
  display: flex;
  align-items: center;
  gap: 8px;
}
.search-results-section-title i { color: var(--brand); }
.search-count-badge {
  font-family: var(--font-mono);
  font-size: 0.65rem;
  background: var(--brand-glow);
  color: var(--brand);
  border-radius: 20px;
  padding: 2px 9px;
  font-weight: 600;
}
.search-no-results {
  padding: 3rem 0;
  text-align: center;
}
.search-no-results i {
  font-size: 3rem;
  color: var(--muted-col);
  display: block;
  margin-bottom: 1rem;
}
.search-no-results p {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--muted-col);
}

/* ── NAV CLOCK-IN INDICATOR (#95) ─────────────────────────────────────── */
.nav-clock-indicator {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
}

/* Corner count badge overlaid on a top-bar icon button (#429) */
.nav-icon-badge {
  position: absolute;
  top: -2px;
  right: -2px;
  min-width: 1.05rem;
  height: 1.05rem;
  padding: 0 0.25rem;
  border-radius: 999px;
  background: var(--status-red);
  color: #fff;
  font-family: var(--font-mono);
  font-size: 0.6rem;
  font-weight: 600;
  line-height: 1.05rem;
  text-align: center;
  box-sizing: border-box;
  pointer-events: none;
}

.nav-clock-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px 3px 7px;
  border-radius: 20px;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;
  transition: opacity 0.15s;
}
.nav-clock-badge:hover { opacity: 0.85; }

.nav-clock-badge--active {
  background: rgba(34, 197, 94, 0.15);
  color: #22c55e;
  border: 1px solid rgba(34, 197, 94, 0.35);
}
.nav-clock-badge--stale {
  background: rgba(239, 68, 68, 0.15);
  color: #ef4444;
  border: 1px solid rgba(239, 68, 68, 0.35);
}

.nav-clock-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex-shrink: 0;
}
.nav-clock-dot--green {
  background: #22c55e;
  box-shadow: 0 0 5px rgba(34, 197, 94, 0.7);
  animation: navClockPulse 2s ease-in-out infinite;
}
.nav-clock-dot--red {
  background: #ef4444;
  box-shadow: 0 0 5px rgba(239, 68, 68, 0.7);
  animation: navClockPulse 1.2s ease-in-out infinite;
}

@keyframes navClockPulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}

.nav-clock-label {
  font-weight: 700;
}
.nav-clock-elapsed {
  opacity: 0.85;
  margin-left: 2px;
}
.nav-clock-job {
  opacity: 0.75;
}
.nav-clock-warning {
  opacity: 0.85;
}

.nav-clock-team-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 8px;
  border-radius: 20px;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 700;
  background: rgba(var(--brand-rgb, 16,185,129), 0.13);
  color: var(--brand);
  border: 1px solid rgba(var(--brand-rgb, 16,185,129), 0.3);
  white-space: nowrap;
  transition: opacity 0.15s;
}
.nav-clock-team-badge:hover { opacity: 0.8; }

/* ── Recent Customers nav dropdown (desktop only) ───────────────── */
.nav-recent-btn {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 0.3rem 0.65rem;
  background: transparent; border: 1px solid var(--border-col);
  border-radius: var(--radius-sm); cursor: pointer;
  color: var(--text-tertiary);
  font-family: var(--font-cond); font-size: 0.78rem; font-weight: 600;
  letter-spacing: 0.04em; text-transform: uppercase;
  transition: all 0.15s;
  white-space: nowrap;
}
.nav-recent-btn:hover,
.nav-recent-btn.show { color: var(--brand); border-color: var(--brand); background: var(--brand-glow); }
.nav-recent-btn::after { display: none; }
.nav-recent-menu {
  background: var(--bg-panel); border: 1px solid var(--border-col);
  border-radius: var(--radius-md); box-shadow: var(--shadow-hover);
  padding: 4px 0; min-width: 220px; max-width: 300px;
}
.nav-recent-header {
  display: block;
  font-family: var(--font-mono); font-size: 0.6rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--muted-col);
  padding: 6px 14px 4px;
}
.nav-recent-item {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 14px;
  color: var(--text-secondary); text-decoration: none;
  font-size: 0.85rem; font-family: var(--font-sans);
  transition: background 0.1s, color 0.1s;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.nav-recent-item i { color: var(--muted-col); font-size: 0.85rem; flex-shrink: 0; }
.nav-recent-item:hover { background: var(--bg-raised); color: var(--brand); }
.nav-recent-item:hover i { color: var(--brand); }
.nav-recent-all { color: var(--text-tertiary); font-size: 0.78rem; }

/* On very small screens, trim down to just the dot + label */
@media (max-width: 400px) {
  .nav-clock-elapsed { display: none !important; }
}

/* ═══════════════════════════════════════════════════════════════════
   FIELD TECH MOBILE UI — Issues #112 / #113 / #114
   Phone-first surface: large tap targets, no tables, dead simple.
   ═══════════════════════════════════════════════════════════════════ */

/* ── Body / Layout ─────────────────────────────────────────────── */
/* The field shell is a fixed-height flex column: <body> itself never
   scrolls — only .ft-main does. This keeps the bottom nav physically
   pinned (it's a flex child, not position:fixed) so it can no longer
   ride up on mobile overscroll/rubber-band and expose a blank strip
   below it. overflow:hidden + overscroll-behavior:none kill the bounce.
   Scoped to field pages via :has(.ft-body) — a bare `html` selector here
   leaked overflow:hidden onto the whole site and broke desktop scrolling. */
html:has(.ft-body) {
  height: 100dvh;
  overflow: hidden;
  overscroll-behavior: none;
}
.ft-body {
  background: var(--bg-void);
  height: 100dvh;
  overflow: hidden;
  overscroll-behavior: none;
  display: flex;
  flex-direction: column;
}

/* ── Top Bar ───────────────────────────────────────────────────── */
.ft-topbar {
  position: sticky;
  top: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 1rem;
  height: 52px;
  background: var(--bg-deep);
  border-bottom: 1px solid var(--border-col);
}
.ft-topbar-brand {
  display: flex;
  align-items: center;
  gap: 8px;
}
.ft-topbar-hex {
  width: 24px;
  height: 24px;
  background: var(--brand);
  clip-path: polygon(50% 0%, 90% 25%, 90% 75%, 50% 100%, 10% 75%, 10% 25%);
  flex-shrink: 0;
}
.ft-topbar-logo {
  height: 24px;
  width: auto;
  object-fit: contain;
}
.ft-topbar-name {
  font-family: var(--font-cond);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--brand);
}
.ft-topbar-right {
  display: flex;
  align-items: center;
  gap: 10px;
}
.ft-topbar-user {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--text-tertiary);
}
.ft-topbar-logout {
  color: var(--text-tertiary);
  font-size: 1.1rem;
  padding: 4px 6px;
  border-radius: var(--radius-sm);
  transition: color 0.15s;
  text-decoration: none;
}
.ft-topbar-logout:hover { color: var(--status-red); }
.ft-topbar-desktop-link {
  color: var(--text-tertiary);
  font-size: 1.1rem;
  padding: 4px 6px;
  border-radius: var(--radius-sm);
  transition: color 0.15s;
  text-decoration: none;
}
.ft-topbar-desktop-link:hover { color: var(--brand); }

/* ── Bottom Nav (#112) — 5-item field tech nav with central scan ── */
.ft-bottom-nav {
  position: relative; /* flex child of .ft-body — no longer position:fixed (#112 overscroll fix) */
  flex-shrink: 0;
  z-index: 200;
  display: flex;
  align-items: stretch;
  background: var(--bg-deep);
  border-top: 1px solid var(--border-col);
  height: 64px;
  padding-bottom: env(safe-area-inset-bottom); /* clear the iOS home-bar */
  box-shadow: 0 -2px 16px rgba(0,0,0,0.15);
  overflow: visible;
}
.ft-nav-item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  color: var(--text-tertiary);
  text-decoration: none;
  font-family: var(--font-cond);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 8px 4px;
  transition: color 0.15s, background 0.15s;
  -webkit-tap-highlight-color: transparent;
}
.ft-nav-item i {
  font-size: 1.35rem;
  line-height: 1;
  transition: color 0.15s;
}
.ft-nav-item:hover,
.ft-nav-item--active {
  color: var(--brand);
}
.ft-nav-item--active i {
  color: var(--brand);
}

/* Central scan button — promoted action (styleguide §13) */
.ft-nav-item--scan {
  position: relative;
  justify-content: flex-end;
  padding-bottom: 10px;
  color: var(--text-tertiary);
}
.ft-nav-scan-bubble {
  position: absolute;
  top: -18px;
  width: 48px;
  height: 48px;
  background: var(--brand);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 0 0 4px var(--bg-deep), 0 4px 12px rgba(16,185,129,0.4);
  transition: transform 0.15s, box-shadow 0.15s;
}
.ft-nav-item--scan i {
  font-size: 1.4rem;
  color: var(--bg-void);
  line-height: 1;
}
.ft-nav-item--scan:hover .ft-nav-scan-bubble,
.ft-nav-item--scan.ft-nav-item--active .ft-nav-scan-bubble {
  transform: scale(1.08);
  box-shadow: 0 0 0 4px var(--bg-deep), 0 6px 18px rgba(16,185,129,0.55);
}
.ft-nav-item--scan span {
  font-family: var(--font-cond);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* ── Flash Messages ────────────────────────────────────────────── */
.ft-flash-wrap {
  padding: 0.5rem 1rem 0;
}
.ft-flash {
  padding: 0.65rem 1rem;
  border-radius: var(--radius-md);
  font-size: 0.9rem;
  margin-bottom: 0.4rem;
  font-family: var(--font-sans);
}
.ft-flash--success { background: rgba(34,197,94,0.15); color: #22c55e; border: 1px solid rgba(34,197,94,0.3); }
.ft-flash--danger  { background: rgba(239,68,68,0.15);  color: #ef4444; border: 1px solid rgba(239,68,68,0.3); }
.ft-flash--warning { background: rgba(245,158,11,0.15); color: #f59e0b; border: 1px solid rgba(245,158,11,0.3); }
.ft-flash--info    { background: rgba(56,189,248,0.15); color: #38bdf8; border: 1px solid rgba(56,189,248,0.3); }

/* #498: inline upload confirmation shown next to the photo upload button so the
   field tech sees it without scrolling back up to the top-of-page flash area. */
.ft-inline-confirm {
  margin-top: 0.5rem;
  padding: 0.55rem 0.85rem;
  border-radius: var(--radius-md);
  font-family: var(--font-sans);
  font-size: 0.88rem;
  background: rgba(34,197,94,0.15);
  color: #22c55e;
  border: 1px solid rgba(34,197,94,0.3);
  display: flex;
  align-items: center;
  gap: 0.4rem;
  opacity: 0;
  transition: opacity 0.2s ease;
}
.ft-inline-confirm--show { opacity: 1; }
/* Error variant is swapped in by HTMX rather than revealed by JS, so it opts
   out of the opacity-0 default above — otherwise it lands invisible (#564). */
.ft-inline-confirm--error {
  opacity: 1;
  background: rgba(239,68,68,0.15);
  color: #ef4444;
  border-color: rgba(239,68,68,0.3);
}

/* ── Photo file picker (#564) ──────────────────────────────────────
   The input omits `capture`: with it, mobile browsers open the camera directly
   and the tech can never reach photos shot earlier.  Without it, iOS and
   Android show their own chooser, which already lists Take Photo next to the
   library — so no separate camera button is needed here. */
.ft-filepick { display: flex; gap: 0.5rem; }
.ft-filepick-input {
  /* Visually hidden but still focusable/labellable — display:none would drop
     it from the form submission on some mobile browsers. */
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
.ft-filepick-btn {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  min-height: 44px; /* iOS minimum tap target — techs wear gloves */
  padding: 0.6rem 0.5rem;
  background: var(--bg-raised);
  border: 1px solid var(--border-col);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  font-family: var(--font-cond);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
  user-select: none;
}
.ft-filepick-btn:active { background: var(--bg-panel); }
.ft-filepick-input:focus-visible + .ft-filepick-btn { outline: 2px solid var(--brand); }
.ft-filepick-name {
  margin-top: 0.4rem;
  font-size: 0.8rem;
  color: var(--text-secondary);
  font-family: var(--font-sans);
  word-break: break-all;
}
.ft-filepick-name:empty { display: none; }

/* ── Page Wrapper ──────────────────────────────────────────────── */
.ft-main {
  padding: 0;
  flex: 1;
  min-height: 0; /* allow the flex child to shrink so it can scroll */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.ft-page {
  padding: 1rem;
  max-width: 540px;
  margin: 0 auto;
}

/* ── Clock Hero (#113) ─────────────────────────────────────────── */
.ft-clock-hero {
  border-radius: var(--radius-lg);
  padding: 1.25rem 1.25rem 1rem;
  margin-bottom: 1rem;
  transition: background 0.3s, border-color 0.3s;
}
.ft-clock-hero--active {
  background: rgba(34,197,94,0.1);
  border: 1.5px solid rgba(34,197,94,0.35);
}
.ft-clock-hero--idle {
  background: var(--bg-panel);
  border: 1.5px solid var(--border-col);
  box-shadow: var(--shadow-panel);
}

.ft-clock-status-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 0.6rem;
}
.ft-clock-label {
  font-family: var(--font-cond);
  font-size: 1rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-primary);
}
.ft-clock-timer {
  font-family: var(--font-mono);
  font-size: 0.9rem;
  font-weight: 600;
  color: #22c55e;
  margin-left: auto;
}

/* Pulse dot — clocked in */
.ft-pulse-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #22c55e;
  box-shadow: 0 0 6px rgba(34,197,94,0.8);
  animation: ftPulse 1.8s ease-in-out infinite;
  flex-shrink: 0;
}
/* Idle dot */
.ft-idle-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--muted-col);
  flex-shrink: 0;
}
@keyframes ftPulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%       { opacity: 0.5; transform: scale(0.85); }
}

/* Clock job info line */
.ft-clock-job {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--text-tertiary);
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 0.75rem;
}
.ft-clock-job i { color: var(--brand); }
.ft-clock-job--none { color: var(--text-disabled); }

/* Clock form (idle state) */
.ft-clock-form {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.ft-form-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.ft-form-label {
  font-family: var(--font-cond);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.ft-optional {
  font-weight: 400;
  opacity: 0.7;
  font-size: 0.72rem;
}
.ft-form-select {
  width: 100%;
  padding: 0.75rem 1rem;
  font-size: 1rem;
  font-family: var(--font-sans);
  background: var(--bg-raised);
  color: var(--text-primary);
  border: 1px solid var(--border-col);
  border-radius: var(--radius-md);
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  min-height: 48px;
}
.ft-form-select:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-glow);
}

/* ── Action Buttons ────────────────────────────────────────────── */
.ft-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  min-height: 56px;
  padding: 0.875rem 1.5rem;
  border: none;
  border-radius: var(--radius-md);
  font-family: var(--font-cond);
  font-size: 1.1rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  cursor: pointer;
  transition: all 0.15s;
  -webkit-tap-highlight-color: transparent;
}
.ft-btn i { font-size: 1.3rem; }

.ft-btn--start {
  background: var(--brand);
  color: #0C1F1A;
}
.ft-btn--start:hover, .ft-btn--start:active {
  background: var(--brand-dark);
  transform: translateY(-1px);
  box-shadow: 0 4px 14px rgba(16,185,129,0.4);
}
.ft-btn--stop {
  background: rgba(239,68,68,0.15);
  color: #ef4444;
  border: 1.5px solid rgba(239,68,68,0.4);
}
.ft-btn--stop:hover, .ft-btn--stop:active {
  background: rgba(239,68,68,0.25);
  transform: translateY(-1px);
}

/* ── Hours Bar ─────────────────────────────────────────────────── */
.ft-hours-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.6rem 1rem;
  background: var(--bg-panel);
  border: 1px solid var(--border-col);
  border-radius: var(--radius-md);
  margin-bottom: 1rem;
}
.ft-hours-label {
  font-family: var(--font-cond);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.ft-hours-value {
  font-family: var(--font-mono);
  font-size: 1rem;
  font-weight: 600;
  color: var(--brand);
}

/* Date separator above each Upcoming job card (#394). Replaces an inline
   margin override on .ft-hours-bar; sits tight against the card below it. */
.ft-upcoming-date {
  display: flex;
  align-items: center;
  padding: 0.4rem 1rem;
  background: var(--bg-panel);
  border: 1px solid var(--border-col);
  border-radius: var(--radius-md);
  margin-bottom: 0;
}

/* ── Section Headers ───────────────────────────────────────────── */
.ft-section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.5rem;
}
.ft-section-header--mt { margin-top: 1.25rem; }
.ft-section-title {
  font-family: var(--font-cond);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.ft-section-count {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--brand);
  background: var(--brand-glow);
  border-radius: 99px;
  padding: 1px 8px;
}

/* ── Job Cards (#114) ──────────────────────────────────────────── */
.ft-job-card {
  background: var(--bg-panel);
  border: 1px solid var(--border-col);
  border-radius: var(--radius-lg);
  padding: 1rem 1rem 0.875rem;
  margin-bottom: 0.625rem;
  cursor: pointer;
  transition: border-color 0.15s, box-shadow 0.15s;
  -webkit-tap-highlight-color: transparent;
}
.ft-job-card:hover, .ft-job-card:active {
  border-color: var(--brand);
  box-shadow: var(--shadow-hover);
}
.ft-job-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.3rem;
}
.ft-job-number {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--brand);
}
.ft-job-badge {
  font-family: var(--font-cond);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 2px 9px;
  border-radius: 99px;
}
.ft-badge--active {
  background: rgba(34,197,94,0.15);
  color: #22c55e;
  border: 1px solid rgba(34,197,94,0.3);
}
.ft-badge--on-hold {
  background: rgba(245,158,11,0.15);
  color: #f59e0b;
  border: 1px solid rgba(245,158,11,0.3);
}
/* #641: a punch that is recorded on the device but not yet on the server. The
   queued clock hero used Bootstrap's `badge bg-warning text-dark`, which is a
   different yellow from anything else in the field palette and reads as a
   warning — but a queued punch is not a problem, it is the offline path working
   as designed. Amber-on-amber, matching the other "in flight, not final" cues,
   and it says so in words next to the icon rather than relying on colour. */
.ft-queued-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: 0.5rem;
  padding: 2px 9px;
  border-radius: 99px;
  font-family: var(--font-cond);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  background: rgba(245,158,11,0.15);
  color: #f59e0b;
  border: 1px solid rgba(245,158,11,0.3);
}
.ft-badge--completed {
  background: rgba(56,189,248,0.12);
  color: #38bdf8;
  border: 1px solid rgba(56,189,248,0.25);
}
/* #653: work-order statuses. A WorkOrder's lifecycle is open → in_progress →
   completed, alongside the on_hold / cancelled the job pills above already
   cover, so only these two are new. `open` reads as the same "live work" green
   as an active job, and `in_progress` is deliberately the brand colour: on this
   screen it means the tech has started it, which is the one state that is
   about them rather than about the record. */
.ft-badge--open {
  background: rgba(34,197,94,0.15);
  color: #22c55e;
  border: 1px solid rgba(34,197,94,0.3);
}
.ft-badge--in-progress {
  background: var(--brand-glow);
  color: var(--brand);
  border: 1px solid var(--brand-glow);
}
.ft-badge--cancelled {
  background: rgba(239,68,68,0.12);
  color: #ef4444;
  border: 1px solid rgba(239,68,68,0.25);
}
/* Emergency job flag (#394 item 4). Unlike the translucent status pills this
   is solid — it has to survive being read at arm's length on a phone in
   daylight, and it must not be mistaken for the "cancelled" red. */
.ft-badge--emergency {
  background: #dc2626;
  color: #fff;
  border: 1px solid #dc2626;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
/* Groups the emergency + status pills on the right of the card header so the
   header keeps its two-column space-between balance. */
.ft-job-badges {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  justify-content: flex-end;
}

.ft-job-customer {
  font-family: var(--font-sans);
  font-size: 1rem;
  font-weight: 600;
  color: var(--text-primary);
  margin-bottom: 2px;
}
.ft-job-type {
  font-family: var(--font-sans);
  font-size: 0.8rem;
  color: var(--text-tertiary);
  margin-bottom: 4px;
}
/* Scheduled window for the day (#394). Mono + brand so the tech can scan the
   day's start times down the left edge — this is the card's primary sort key. */
.ft-job-time {
  display: flex;
  align-items: center;
  gap: 5px;
  font-family: var(--font-mono);
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--brand);
  margin-bottom: 4px;
}
.ft-job-time i { font-size: 0.8rem; }
.ft-job-location {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 5px;
  font-size: 0.8rem;
  color: var(--text-tertiary);
  margin-top: 4px;
}
.ft-job-location i { color: var(--brand); font-size: 0.85rem; }
.ft-map-link {
  color: var(--accent);
  text-decoration: none;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 1px 6px;
  border-radius: var(--radius-sm);
  border: 1px solid rgba(56,189,248,0.3);
  background: rgba(56,189,248,0.07);
}
.ft-map-link:hover { background: rgba(56,189,248,0.15); color: var(--accent); }
.ft-job-desc {
  font-size: 0.82rem;
  color: var(--text-tertiary);
  margin-top: 5px;
  line-height: 1.45;
}

/* ── Empty State ───────────────────────────────────────────────── */
.ft-empty-state {
  text-align: center;
  padding: 2.5rem 1rem;
  color: var(--text-tertiary);
}
.ft-empty-state i {
  font-size: 2.5rem;
  color: var(--muted-col);
  margin-bottom: 0.75rem;
  display: block;
}
.ft-empty-state p {
  font-family: var(--font-cond);
  font-size: 1rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin: 0;
}
.ft-empty-sub {
  font-family: var(--font-sans);
  font-size: 0.82rem;
  font-weight: 400 !important;
  text-transform: none !important;
  letter-spacing: 0 !important;
  color: var(--text-disabled) !important;
  margin-top: 4px !important;
}

/* ── Today's Entries List ──────────────────────────────────────── */
.ft-entry-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.65rem 0.875rem;
  background: var(--bg-panel);
  border: 1px solid var(--border-col);
  border-radius: var(--radius-md);
  margin-bottom: 0.4rem;
}
.ft-entry-times {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--text-primary);
}
.ft-entry-sep { color: var(--muted-col); }
.ft-entry-open {
  color: var(--status-green);
  font-weight: 600;
}
.ft-entry-meta {
  display: flex;
  align-items: center;
  gap: 8px;
}
.ft-entry-job {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--brand);
  background: var(--brand-glow);
  border-radius: 99px;
  padding: 1px 7px;
}
.ft-entry-hours {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--text-tertiary);
}
/* Day total footer inside the Today's Entries list */
.ft-entry-row--total {
  background: transparent;
  border-style: dashed;
  font-weight: 600;
}
.ft-entry-row--total .ft-entry-times { color: var(--text-secondary); }
.ft-entry-row--total .ft-entry-hours { color: var(--text-primary); }

/* ── Job Detail Hub (#480) ─────────────────────────────────────── */
.ft-jd-header {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 1rem 1rem 0.75rem;
  background: var(--bg-panel);
  border-bottom: 1px solid var(--border-col);
}
.ft-jd-back {
  color: var(--brand);
  text-decoration: none;
  font-size: 1.4rem;
  line-height: 1;
  padding-top: 2px;
  flex-shrink: 0;
}
.ft-jd-headinfo { flex: 1; min-width: 0; }
.ft-jd-num {
  font-family: var(--font-cond);
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--brand);
  letter-spacing: 0.05em;
}
.ft-jd-sub {
  font-size: 0.85rem;
  color: var(--text-tertiary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ft-jd-info { padding: 0.75rem 1rem; }
.ft-jd-row {
  display: flex;
  gap: 0.6rem;
  padding: 0.45rem 0;
  font-size: 0.88rem;
  color: var(--text-secondary);
  border-bottom: 1px solid var(--border-col);
}
.ft-jd-row:last-child { border-bottom: none; }
.ft-jd-label {
  color: var(--text-tertiary);
  min-width: 92px;
  flex-shrink: 0;
  font-family: var(--font-cond);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 0.72rem;
  padding-top: 2px;
}
.ft-jd-val { flex: 1; min-width: 0; }
.ft-jd-chips { display: flex; flex-wrap: wrap; gap: 4px; }
.ft-jd-chip {
  background: var(--bg-raised);
  border: 1px solid var(--border-col);
  border-radius: 999px;
  padding: 2px 10px;
  font-size: 0.75rem;
  color: var(--text-secondary);
}
.ft-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  width: calc(100% - 2rem);
  margin: 0.75rem 1rem;
  padding: 0.85rem;
  border: none;
  border-radius: var(--radius-md);
  background: var(--brand);
  color: #0C1F1A;
  font-family: var(--font-cond);
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
}
.ft-cta--secondary {
  background: var(--bg-raised);
  color: var(--text-primary);
  border: 1px solid var(--border-col);
}
/* Notes */
.ft-note-form { padding: 0 1rem 0.5rem; }
.ft-note-input {
  width: 100%;
  background: var(--bg-raised);
  border: 1px solid var(--border-col);
  border-radius: var(--radius-md);
  padding: 0.5rem 0.75rem;
  font-size: 0.88rem;
  color: var(--text-primary);
  font-family: var(--font-sans);
  resize: vertical;
  min-height: 52px;
}
.ft-note-input:focus { outline: none; border-color: var(--brand); }
.ft-note-list { padding: 0.25rem 1rem 0.5rem; list-style: none; margin: 0; }
.ft-note-row {
  background: var(--bg-panel);
  border: 1px solid var(--border-col);
  border-radius: var(--radius-md);
  padding: 0.6rem 0.75rem;
  margin-bottom: 0.5rem;
}
.ft-note-meta { display: flex; justify-content: space-between; gap: 0.5rem; margin-bottom: 0.25rem; }
.ft-note-author { font-weight: 600; font-size: 0.8rem; color: var(--text-primary); }
.ft-note-time { font-size: 0.72rem; color: var(--text-tertiary); font-family: var(--font-mono); }
.ft-note-body {
  font-size: 0.88rem;
  color: var(--text-secondary);
  line-height: 1.4;
  white-space: pre-wrap;
  word-break: break-word;
}
.ft-note-empty { font-size: 0.85rem; color: var(--text-disabled); padding: 0.25rem 1rem 0.75rem; }
/* Photo gallery */
.ft-photo-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; padding: 0.25rem 1rem 0.5rem; }
.ft-photo-item {
  position: relative;
  aspect-ratio: 1;
  overflow: hidden;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-col);
}
.ft-photo-img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ft-photo-badge {
  position: absolute;
  bottom: 3px;
  left: 3px;
  background: rgba(0,0,0,0.6);
  color: #fff;
  font-size: 0.6rem;
  padding: 1px 5px;
  border-radius: 3px;
  font-family: var(--font-cond);
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

/* Photo annotations (#463) — marker on tiles that carry a drawing overlay.
   Defined here rather than in job_detail.html's inline <style> because the tile
   partial is HTMX-swapped after upload, outside the parent page's styles. */
.ft-photo-annotated {
  position: absolute;
  top: 3px;
  right: 3px;
  background: var(--brand);
  color: #fff;
  font-size: 0.6rem;
  line-height: 1;
  padding: 3px 4px;
  border-radius: 3px;
  pointer-events: none;
}
.ft-photo-annotated--off { display: none; }

/* ── Photo annotation editor (#463) ──────────────────────────────
   Shared by the field job hub and the desktop job detail page, hence the
   neutral `pa-` (photo-annotator) prefix rather than the field-only `ft-`.

   Layout is a full-viewport flex column with the toolbar pinned as the last
   row and the image given only the space that is left. Centering the whole
   stack instead pushed the Save button underneath the 64px .ft-bottom-nav on
   a phone, which is where it was found unreachable in device testing. */
.pa-modal {
  position: fixed;
  inset: 0;
  /* Above .ft-bottom-nav (z-index 200) and every in-page overlay. */
  z-index: 10000;
  background: rgba(0,0,0,0.93);
  display: none;
  flex-direction: column;
}
.pa-modal--open { display: flex; }

.pa-viewport {
  flex: 1;
  min-height: 0;          /* let the image shrink instead of pushing the toolbar out */
  display: flex;
  padding: 0.5rem;
}
/* The stage fills the available area and the image letterboxes inside it via
   object-fit. It deliberately does NOT shrink-wrap the image: sizing the image
   with `max-height: 100%` against an auto-height parent silently fails (a
   percentage against an auto height does not resolve), so on a wide desktop
   viewport — where `max-width` does not bind either — a tall phone photo
   rendered at full natural size and overflowed the screen. The canvas is then
   positioned over the image's true displayed rect, computed in JS. */
.pa-stage {
  position: relative;
  flex: 1;
  min-width: 0;
  min-height: 0;
  line-height: 0;
  overflow: hidden;     /* clip the photo to the stage when zoomed in */
}
/* Carries the zoom/pan transform. The image and canvas live inside it together
   so they stay aligned at any magnification, and pointer positions can be read
   straight off the canvas's already-transformed bounding rect. */
.pa-zoom {
  position: absolute;
  inset: 0;
  transform-origin: 0 0;
  will-change: transform;
}
.pa-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.pa-canvas {
  position: absolute;
  /* left/top/width/height are set from the contain-fit rect in JS. */
  touch-action: none;   /* a drag draws instead of panning the page */
  border-radius: var(--radius-md);
}
.pa-canvas--draw { cursor: crosshair; }
.pa-canvas--pan { cursor: grab; }
.pa-canvas--pan:active { cursor: grabbing; }

.pa-toolbar-sep {
  width: 1px;
  align-self: stretch;
  background: rgba(255,255,255,0.18);
  margin: 0 0.15rem;
}

.pa-hint {
  flex-shrink: 0;
  color: rgba(255,255,255,0.55);
  font-size: 0.75rem;
  text-align: center;
  padding: 0 1rem 0.35rem;
  min-height: 1.1rem;
}

.pa-toolbar {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.4rem;
  padding: 0.5rem 0.75rem;
  /* Clear the iOS home bar; the modal covers the app's own bottom nav. */
  padding-bottom: calc(0.5rem + env(safe-area-inset-bottom));
  background: rgba(0,0,0,0.9);
  border-top: 1px solid rgba(255,255,255,0.12);
  width: 100%;
}
.pa-btn {
  background: rgba(255,255,255,0.1);
  border: 1px solid rgba(255,255,255,0.2);
  color: #fff;
  border-radius: var(--radius-sm);
  /* 44px min touch target — used one-handed, sometimes with gloves on. */
  min-width: 44px;
  min-height: 44px;
  padding: 0 0.6rem;
  font-family: var(--font-cond);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
}
.pa-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.pa-btn--on { background: var(--brand); border-color: var(--brand); }
.pa-btn--save { background: var(--success, #34c759); border-color: transparent; }
.pa-swatch {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 2px solid rgba(255,255,255,0.35);
  cursor: pointer;
  padding: 0;
  flex-shrink: 0;
}
.pa-swatch--on { border-color: #fff; transform: scale(1.15); }

.pa-textbox {
  position: absolute;
  z-index: 2;
  background: rgba(0,0,0,0.9);
  border: 1px solid var(--brand);
  border-radius: var(--radius-sm);
  padding: 0.3rem;
  display: flex;
  gap: 0.3rem;
  line-height: normal;
}
.pa-textbox input {
  background: transparent;
  border: none;
  outline: none;
  color: #fff;
  font-size: 1rem;   /* >=16px stops iOS auto-zooming on focus */
  width: 10rem;
}
.pa-textbox button {
  background: var(--brand);
  border: none;
  color: #fff;
  border-radius: 3px;
  min-width: 32px;
  cursor: pointer;
}

/* Desktop job-detail gallery: annotate affordance + marker (#463). */
.pa-tile-mark {
  position: absolute;
  top: 6px;
  right: 6px;
  background: var(--brand);
  color: #fff;
  font-size: 0.7rem;
  line-height: 1;
  padding: 4px 5px;
  border-radius: 3px;
  pointer-events: none;
}
.pa-tile-mark--off { display: none; }
.pa-tile-wrap { position: relative; }
.pa-annotate-btn {
  width: 100%;
  background: var(--bg-raised);
  border: 1px solid var(--border-col);
  border-top: none;
  color: var(--text-secondary);
  font-size: 0.78rem;
  padding: 0.35rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
}
.pa-annotate-btn:hover { color: var(--brand); border-color: var(--brand); }
.ft-photo-empty { font-size: 0.85rem; color: var(--text-disabled); padding: 0.25rem 1rem 0.5rem; }

/* ── Dark theme overrides ──────────────────────────────────────── */
[data-theme="dark"] .ft-clock-hero--idle {
  background: var(--bg-raised);
}
[data-theme="dark"] .ft-job-card {
  background: var(--bg-raised);
}
[data-theme="dark"] .ft-entry-row {
  background: var(--bg-raised);
}
[data-theme="dark"] .ft-form-select {
  background: var(--bg-panel);
}

/* ── Tutorial overlay ─────────────────────────────────────────────────────── */
.tut-overlay {
  position: fixed;
  bottom: 1.5rem;
  right: 1.5rem;
  z-index: 1080;
  width: 320px;
  max-width: calc(100vw - 2rem);
  pointer-events: auto;
}

.tut-card {
  background: var(--bg-panel);
  border: 1.5px solid var(--brand);
  border-radius: var(--radius-md);
  box-shadow: 0 8px 32px rgba(0,0,0,0.18);
  overflow: hidden;
}

.tut-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.6rem 0.9rem;
  background: var(--brand);
  color: #fff;
  gap: 0.5rem;
}

.tut-card-title {
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tut-close-btn {
  background: transparent;
  border: none;
  color: rgba(255,255,255,0.85);
  cursor: pointer;
  padding: 0.1rem 0.25rem;
  border-radius: 3px;
  line-height: 1;
  flex-shrink: 0;
  transition: color 0.15s;
}
.tut-close-btn:hover { color: #fff; }

.tut-card-body {
  padding: 0.85rem 0.9rem 0.6rem;
}

.tut-step-title {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-tertiary);
  font-family: var(--font-mono);
  margin-bottom: 0.35rem;
}

.tut-step-body {
  font-size: 0.88rem;
  line-height: 1.5;
  color: var(--text-primary);
}

.tut-card-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.6rem 0.9rem;
  border-top: 1px solid var(--border-col);
  gap: 0.5rem;
  flex-wrap: wrap;
}

.tut-progress {
  display: flex;
  align-items: center;
  gap: 5px;
}

.tut-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--border-col);
  transition: background 0.2s;
}
.tut-dot.done { background: var(--brand); opacity: 0.45; }
.tut-dot.active { background: var(--brand); width: 14px; border-radius: 4px; }

.tut-nav-btn {
  font-size: 0.8rem !important;
  padding: 0.25rem 0.7rem !important;
}

/* Element highlight — pulsing outline */
.tut-highlight {
  outline: 2.5px solid var(--brand) !important;
  outline-offset: 3px !important;
  border-radius: 4px;
  animation: tut-pulse 1.6s ease-in-out infinite;
}

@keyframes tut-pulse {
  0%, 100% { outline-color: var(--brand); box-shadow: 0 0 0 0 rgba(var(--brand-rgb, 37,99,235), 0.35); }
  50%       { outline-color: var(--brand); box-shadow: 0 0 0 6px rgba(var(--brand-rgb, 37,99,235), 0); }
}

/* Mobile: full-width at bottom */
@media (max-width: 575.98px) {
  .tut-overlay {
    bottom: 0 !important;
    right: 0 !important;
    left: 0 !important;
    top: auto !important;
    width: 100%;
    max-width: 100%;
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
  }
  .tut-card {
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
    border-left: none;
    border-right: none;
    border-bottom: none;
  }

}

/* Field UI: sit the card ABOVE the bottom nav, not on top of it (#708).
   .ft-bottom-nav is a z-index:200 flex child pinned to the bottom of the
   viewport-height .ft-body; .tut-overlay is z-index:1080 and fixed to
   bottom:0, so the card covered all five nav items — including the very
   ones a step tells the tech to tap. The offset matches the nav's own
   height plus the iOS home-bar inset it pads itself with.

   !important and the extra specificity are both load-bearing: the mobile
   block above sets `bottom: 0 !important`, which no ordinary rule can beat
   whatever the source order. Not inside a media query — the field UI keeps
   its bottom nav at every width, so the card is fixed-bottom on a tablet
   too (the overlay script mirrors this; see _overlay_data.html). */
.ft-body .tut-overlay {
  bottom: calc(64px + env(safe-area-inset-bottom)) !important;
  top: auto !important;
  left: 0 !important;
  right: 0 !important;
  width: 100%;
  max-width: 100%;
}
.ft-body .tut-card {
  border-radius: 0;
  border-left: none;
  border-right: none;
}

/* ═══════════════════════════════════════════════════════════════════════════
   MODERN-MINIMAL ROLLOUT — folded from the approved pilot (login + reports).
   Page component classes shared by standalone (auth) and hub (reports) pages.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── AUTH (login / standalone) ── */
.auth-shell {
  display: flex; align-items: center; justify-content: center;
  min-height: calc(100vh - 56px - 4rem); padding: var(--space-lg);
}
.auth-card {
  width: 100%; max-width: 400px; background: var(--bg-panel);
  border: 1px solid var(--border-col); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-hover); padding: var(--space-xl);
}
.auth-mark {
  display: inline-block; width: 34px; height: 34px; background: var(--brand);
  clip-path: polygon(50% 0%, 90% 25%, 90% 75%, 50% 100%, 10% 75%, 10% 25%);
  margin-bottom: var(--space-md);
}
.auth-title {
  font-family: var(--font-cond); font-size: 2rem; font-weight: 600;
  letter-spacing: var(--tracking-tight); line-height: 1.1; color: var(--text-primary);
}
.auth-sub { color: var(--text-tertiary); font-size: 0.9rem; margin-top: var(--space-2xs); }
.auth-field { margin-bottom: var(--space-lg); }
.auth-forgot {
  display: inline-block; margin-top: var(--space-xs); font-size: 0.82rem;
  color: var(--text-tertiary); text-decoration: none;
}
.auth-forgot:hover { color: var(--brand); text-decoration: underline; }
.auth-remember {
  display: flex; align-items: center; gap: var(--space-xs);
  font-size: 0.88rem; color: var(--text-secondary); margin-bottom: var(--space-lg);
}
.btn-block { width: 100%; justify-content: center; padding-block: 0.7rem; }

/* ── REPORTS / HUB bands ── */
.report-band { margin-bottom: var(--space-2xl); }
.report-band:last-child { margin-bottom: 0; }
.band-head { display: flex; align-items: baseline; gap: var(--space-sm); margin-bottom: var(--space-lg); }
.band-title {
  font-family: var(--font-cond); font-size: 1.15rem; font-weight: 600;
  letter-spacing: -0.005em; color: var(--text-primary);
}
.band-rule { flex: 1; height: 1px; background: var(--border-col); }
.report-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-lg); }
@media (max-width: 1100px) { .report-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 680px)  { .report-grid { grid-template-columns: 1fr; } }
.report-card { display: flex; flex-direction: column; height: 100%; }
.report-cat {
  display: flex; align-items: center; gap: var(--space-xs);
  font-family: var(--font-mono); font-size: 0.66rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--text-tertiary); margin-bottom: var(--space-sm);
}
.report-cat::before {
  content: ''; width: 7px; height: 7px; border-radius: 2px;
  background: var(--brand); flex-shrink: 0;
}
.report-name {
  font-family: var(--font-cond); font-size: 1.15rem; font-weight: 600;
  color: var(--text-primary); letter-spacing: -0.01em; margin-bottom: var(--space-2xs);
}
.report-desc { color: var(--text-secondary); font-size: 0.9rem; line-height: 1.5; margin-bottom: var(--space-md); }
.report-actions { display: flex; gap: var(--space-xs); flex-wrap: wrap; margin-top: auto; }
.report-actions .btn-ghost { padding: 0.4rem 0.8rem; font-size: 0.82rem; }

/* ── Small button size (table-row / inline actions) ── */
.btn-brand.btn-xs, .btn-ghost.btn-xs, .btn-danger.btn-xs { padding: 0.3rem 0.65rem; font-size: 0.78rem; }
.btn-brand.btn-sm, .btn-ghost.btn-sm, .btn-danger.btn-sm { padding: 0.4rem 0.8rem; font-size: 0.82rem; }

/* ── Destructive (outline) button ── */
.btn-danger {
  background: transparent; color: var(--status-red);
  font-family: var(--font-sans); font-weight: 600; letter-spacing: 0; text-transform: none; font-size: 0.9rem;
  border: 1px solid color-mix(in srgb, var(--status-red) 35%, var(--border-col)); border-radius: var(--r-control);
  padding: 0.6rem 1.15rem; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; text-decoration: none;
  transition: background-color var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out);
}
.btn-danger:hover { background: color-mix(in srgb, var(--status-red) 12%, transparent); border-color: var(--status-red); }
.btn-danger:focus-visible { outline: 2px solid transparent; box-shadow: 0 0 0 3px rgba(239,68,68,0.30); }

/* ── Neutral badge (for 'draft'/secondary statuses) ── */
.badge-gray { background: var(--bg-raised); color: var(--text-tertiary); }

/* ── Panel with a header (design-system replacement for Bootstrap card+card-header) ── */
.panel { background: var(--bg-panel); border: 1px solid var(--border-col);
  border-radius: var(--radius-md); box-shadow: var(--shadow-panel); overflow: hidden; }
.panel-head {
  display: flex; align-items: center; gap: var(--space-sm);
  padding: var(--space-md) var(--space-lg); border-bottom: 1px solid var(--border-col);
  font-family: var(--font-cond); font-weight: 600; font-size: 1.05rem; color: var(--text-primary);
}
.panel-body { padding: var(--space-lg); }
.panel-body.p-0 { padding: 0; }
.panel .data-table { border-radius: 0; }

/* ── ERROR PAGES (403 / 404 / 500) ── */
.error-page {
  min-height: 70vh; display: flex; flex-direction: column;
  align-items: center; justify-content: center; text-align: center;
  padding: var(--space-lg);
}
.error-inner { max-width: 460px; }
.error-icon { font-size: 3.5rem; line-height: 1; }
.error-icon--muted  { color: var(--muted-col); }
.error-icon--warn   { color: var(--status-yellow); }
.error-icon--danger { color: var(--status-red); }
.error-code {
  font-family: var(--font-cond); font-size: clamp(3rem, 2rem + 5vw, 4.5rem);
  font-weight: 700; line-height: 1; letter-spacing: -0.02em;
  color: var(--text-primary); margin-top: var(--space-sm);
  font-variant-numeric: tabular-nums;
}
.error-title {
  font-family: var(--font-cond); font-size: 1.4rem; font-weight: 600;
  color: var(--text-primary); margin-top: var(--space-2xs);
}
.error-desc { color: var(--text-tertiary); margin-top: var(--space-xs); margin-bottom: var(--space-lg); }
.error-actions { display: flex; gap: var(--space-sm); justify-content: center; flex-wrap: wrap; }
.error-links { display: flex; gap: var(--space-md); justify-content: center; flex-wrap: wrap; margin-top: var(--space-lg); font-size: 0.88rem; }
.error-links a { color: var(--text-tertiary); text-decoration: none; }
.error-links a:hover { color: var(--brand); }

/* ── Reduced motion — collapse transforms/transitions ── */
@media (prefers-reduced-motion: reduce) {
  .stat-card, a.stat-card-link, a.settings-link-card,
  .btn-brand, .btn-ghost, .form-input { transition: none; }
  a.stat-card-link:hover, a.settings-link-card:hover,
  .btn-brand:hover, .btn-brand:active { transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   BOOTSTRAP COMPATIBILITY BRIDGE — main app only.
   Re-skins raw Bootstrap components (card / table / badge / btn / form-control /
   text-muted) to design tokens so pages that still use Bootstrap markup pick up
   the modern-minimal system without per-file rewrites. Scoped to `.main-content`
   (the authenticated main-app wrapper) so it CANNOT leak into the customer
   portal (portal_base.html) or the field UI (base_field.html), which also load
   petrolink.css but use their own layout wrappers.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Muted text */
.main-content .text-muted { color: var(--text-tertiary) !important; }

/* Cards — driven by Bootstrap's --bs-card-* custom properties */
.main-content .card {
  --bs-card-bg: var(--bg-panel);
  --bs-card-color: var(--text-secondary);
  --bs-card-border-color: var(--border-col);
  --bs-card-border-radius: var(--radius-md);
  --bs-card-inner-border-radius: calc(var(--radius-md) - 1px);
  --bs-card-cap-bg: var(--bg-raised);
  --bs-card-cap-color: var(--text-primary);
  box-shadow: var(--shadow-panel);
}

/* Tables — driven by Bootstrap's --bs-table-* custom properties */
.main-content .table {
  --bs-table-bg: transparent;
  --bs-table-color: var(--text-secondary);
  --bs-table-border-color: var(--border-col);
  --bs-table-striped-bg: var(--bg-raised);
  --bs-table-striped-color: var(--text-secondary);
  --bs-table-hover-bg: var(--bg-raised);
  --bs-table-hover-color: var(--text-primary);
}
.main-content .table > thead th,
.main-content .table > thead td {
  font-family: var(--font-mono); font-size: 0.65rem; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--muted-col); background: var(--bg-raised);
  font-weight: 600; border-bottom: 1px solid var(--border-col);
}
.main-content .table > :not(caption) > * > * { padding: 0.7rem 1rem; }

/* Badges — Bootstrap .bg-* utilities use !important, so match it */
.main-content .badge {
  font-family: var(--font-mono); font-weight: 600; letter-spacing: 0.04em;
  border-radius: var(--radius-sm); padding: 0.32em 0.6em;
}
.main-content .badge.bg-primary   { background: var(--badge-blue-bg)   !important; color: var(--badge-blue-text)   !important; }
.main-content .badge.bg-info      { background: var(--badge-blue-bg)   !important; color: var(--badge-blue-text)   !important; }
.main-content .badge.bg-success   { background: var(--badge-green-bg)  !important; color: var(--badge-green-text)  !important; }
.main-content .badge.bg-warning   { background: var(--badge-yellow-bg) !important; color: var(--badge-yellow-text) !important; }
.main-content .badge.bg-danger    { background: var(--badge-red-bg)    !important; color: var(--badge-red-text)    !important; }
.main-content .badge.bg-secondary { background: var(--bg-raised)        !important; color: var(--text-tertiary)     !important; }
.main-content .badge.bg-light     { background: var(--bg-raised)        !important; color: var(--text-tertiary)     !important; }
.main-content .badge.bg-dark      { background: var(--text-secondary)   !important; color: var(--bg-panel)          !important; }

/* Buttons — recolor Bootstrap variants; base geometry stays from Bootstrap */
.main-content .btn { border-radius: var(--r-control); font-family: var(--font-sans); font-weight: 600; }
.main-content .btn-primary {
  --bs-btn-bg: var(--brand); --bs-btn-border-color: var(--brand); --bs-btn-color: var(--brand-ink);
  --bs-btn-hover-bg: var(--brand-dark); --bs-btn-hover-border-color: var(--brand-dark); --bs-btn-hover-color: var(--brand-ink);
  --bs-btn-active-bg: var(--brand-dark); --bs-btn-active-border-color: var(--brand-dark); --bs-btn-active-color: var(--brand-ink);
}
.main-content .btn-outline-primary {
  --bs-btn-color: var(--brand); --bs-btn-border-color: var(--brand);
  --bs-btn-hover-bg: var(--brand-glow); --bs-btn-hover-border-color: var(--brand); --bs-btn-hover-color: var(--brand);
  --bs-btn-active-bg: var(--brand-glow); --bs-btn-active-color: var(--brand);
}
.main-content .btn-secondary {
  --bs-btn-bg: var(--bg-raised); --bs-btn-border-color: var(--border-col); --bs-btn-color: var(--text-primary);
  --bs-btn-hover-bg: var(--bg-panel); --bs-btn-hover-border-color: var(--border-col); --bs-btn-hover-color: var(--text-primary);
  --bs-btn-active-bg: var(--bg-panel); --bs-btn-active-color: var(--text-primary);
}
/* All neutral outlines (secondary/info/warning/light/dark) calm to one ghost look */
.main-content .btn-outline-secondary,
.main-content .btn-outline-info,
.main-content .btn-outline-warning,
.main-content .btn-outline-light,
.main-content .btn-outline-dark {
  --bs-btn-color: var(--text-secondary); --bs-btn-border-color: var(--border-col);
  --bs-btn-hover-bg: var(--bg-raised); --bs-btn-hover-border-color: var(--border-col); --bs-btn-hover-color: var(--text-primary);
  --bs-btn-active-bg: var(--bg-raised); --bs-btn-active-color: var(--text-primary);
}
.main-content .btn-danger {
  --bs-btn-bg: var(--status-red); --bs-btn-border-color: var(--status-red); --bs-btn-color: #fff;
  --bs-btn-hover-bg: #DC2626; --bs-btn-hover-border-color: #DC2626; --bs-btn-hover-color: #fff;
  --bs-btn-active-bg: #DC2626; --bs-btn-active-color: #fff;
}
.main-content .btn-outline-danger {
  --bs-btn-color: var(--status-red); --bs-btn-border-color: color-mix(in srgb, var(--status-red) 45%, var(--border-col));
  --bs-btn-hover-bg: color-mix(in srgb, var(--status-red) 12%, transparent); --bs-btn-hover-border-color: var(--status-red); --bs-btn-hover-color: var(--status-red);
}
.main-content .btn-success {
  --bs-btn-bg: var(--status-green); --bs-btn-border-color: var(--status-green); --bs-btn-color: #06210F;
  --bs-btn-hover-bg: #16A34A; --bs-btn-hover-border-color: #16A34A; --bs-btn-hover-color: #06210F;
}
.main-content .btn-warning {
  --bs-btn-bg: var(--status-yellow); --bs-btn-border-color: var(--status-yellow); --bs-btn-color: #3D2A05;
  --bs-btn-hover-bg: #D97706; --bs-btn-hover-border-color: #D97706; --bs-btn-hover-color: #fff;
}

/* Bootstrap form controls → design-system field look */
.main-content .form-control,
.main-content .form-select {
  background-color: var(--bg-deep); border-color: var(--border-col);
  color: var(--text-primary); border-radius: var(--r-control);
}
.main-content .form-control:focus,
.main-content .form-select:focus {
  border-color: var(--brand); box-shadow: var(--focus-ring);
}
.main-content .form-control::placeholder { color: var(--muted-col); }
.main-content .input-group-text {
  background: var(--bg-raised); border-color: var(--border-col); color: var(--text-tertiary);
}

/* Modals — Bootstrap defaults render white; map to tokens so dark mode works */
.main-content .modal-content {
  background: var(--bg-panel); color: var(--text-secondary);
  border: 1px solid var(--border-col); border-radius: var(--radius-md);
}
.main-content .modal-header,
.main-content .modal-footer { border-color: var(--border-col); }
.main-content .modal-title { color: var(--text-primary); font-family: var(--font-cond); }
[data-theme="dark"] .main-content .btn-close { filter: invert(1) grayscale(1) brightness(1.6); }

/* ═══════════════════════════════════════════════════════════════════════
   SHARED COMPONENTS — promoted from page-scoped styles (issue #435)

   These classes were previously re-invented in per-page inline <style> blocks
   (or referenced but never defined), so elements rendered unstyled — most often
   on HTMX fragment swaps, where a partial only inherited its parent page's
   inline styles. They are defined ONCE here. Do not re-add page-scoped copies;
   add new shared classes to this section instead. Tokens live in :root above.
   ═══════════════════════════════════════════════════════════════════════ */

/* Semantic status badges — modifiers on .badge (Bootstrap base geometry).
   Mirror the ".main-content .badge.bg-*" overrides above; !important so they
   win regardless of scope, matching how Bootstrap's .bg-* utilities behave. */
.badge--success { background: var(--badge-green-bg)  !important; color: var(--badge-green-text)  !important; }
.badge--warning { background: var(--badge-yellow-bg) !important; color: var(--badge-yellow-text) !important; }
.badge--error   { background: var(--badge-red-bg)    !important; color: var(--badge-red-text)    !important; }

/* Text utilities */
.font-mono     { font-family: var(--font-mono); }
.text-tertiary { color: var(--text-tertiary); }
.text-link {
  color: var(--brand); text-decoration: none;
  transition: color var(--dur-short) var(--ease-out);
}
.text-link:hover { color: var(--brand-dark); text-decoration: underline; }

/* Empty state — centered muted placeholder for empty lists / tables */
.empty-state      { text-align: center; color: var(--text-tertiary); padding: 2.5rem 1rem; }
.empty-state-icon { display: block; font-size: 2rem; color: var(--muted-col); margin-bottom: 0.5rem; }
.empty-state-text { margin: 0; font-size: 0.9rem; color: var(--text-tertiary); }

/* Status pill filter bar (e.g. billing status filters) */
.pill-filter-bar { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-bottom: 1rem; }
.pill-filter {
  display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.2rem 0.7rem; border-radius: var(--r-pill);
  font-size: 0.78rem; font-family: var(--font-mono);
  border: 1px solid var(--border-col); color: var(--text-tertiary);
  background: var(--bg-raised); text-decoration: none;
  transition: background var(--dur-short), color var(--dur-short), border-color var(--dur-short);
}
.pill-filter:hover { background: var(--brand-glow); border-color: var(--brand); color: var(--brand); }
.pill-filter.active { background: var(--brand); border-color: var(--brand); color: #fff; }
.pill-filter .pill-count {
  background: rgba(255,255,255,0.25); border-radius: var(--r-pill);
  padding: 0 0.4rem; font-size: 0.72rem; min-width: 1.4rem; text-align: center;
}
.pill-filter:not(.active) .pill-count { background: var(--border-col); color: var(--text-tertiary); }

/* Count pill inside a sub-nav button (Time & Labor: Live Crew / Exceptions /
   Approvals). Sits on .btn-ghost by default; on the active .btn-brand button
   the brand fill wins, so the pill switches to a translucent white overlay. */
.tl-nav-count {
  /* Both buttons are inline-flex with gap:6px, so the pill needs no margin. */
  display: inline-block; flex: none;
  min-width: 1.3rem; padding: 0 0.35rem;
  border-radius: var(--r-pill);
  font-family: var(--font-mono); font-size: 0.7rem; font-weight: 600;
  line-height: 1.45; text-align: center;
}
/* --live is a headcount, not an alert — green, matching the nav clock badge. */
.tl-nav-count--live    { background: var(--badge-green-bg);  color: var(--badge-green-text); }
.tl-nav-count--warning { background: var(--badge-yellow-bg); color: var(--badge-yellow-text); }
.tl-nav-count--info    { background: var(--badge-blue-bg);   color: var(--badge-blue-text); }
.btn-brand .tl-nav-count { background: rgba(255, 255, 255, 0.28); color: inherit; }

/* Pagination bar — shared across billing, site-equipment, portal lists */
.pag-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem;
  margin-top: 0.75rem; padding: 0.5rem 1rem;
  border-top: 1px solid var(--border-col); font-size: 0.8rem;
}
.pag-count { color: var(--text-tertiary); flex: 1; white-space: nowrap; }
.pag-pages { display: flex; flex-wrap: wrap; gap: 0.25rem; align-items: center; }
.pag-link {
  padding: 0.15rem 0.55rem; border-radius: var(--radius-sm);
  border: 1px solid var(--border-col); color: var(--text-tertiary);
  background: var(--bg-raised); text-decoration: none;
  font-family: var(--font-mono); font-size: 0.78rem;
  min-width: 2rem; text-align: center;
}
.pag-link:hover { border-color: var(--brand); color: var(--brand); }
.pag-link.current { background: var(--brand); border-color: var(--brand); color: #fff; }
.pag-link.disabled { opacity: 0.35; pointer-events: none; }
.pag-ellipsis { color: var(--muted-col); padding: 0 0.2rem; }
/* Compact "page X / Y" indicator variant (used where numbered links are omitted) */
.pag-cur { color: var(--text-tertiary); font-family: var(--font-mono); padding: 0 0.5rem; }
.pag-per-page-form { display: flex; align-items: center; gap: 0.4rem; }
.pag-per-page-form select {
  background: var(--bg-raised); border: 1px solid var(--border-col);
  color: var(--text-primary); border-radius: var(--radius-sm);
  padding: 0.1rem 0.4rem; font-size: 0.78rem; cursor: pointer;
}

/* ── Clock hero (relocated from main/profile.html so the HTMX-swapped
      main/partials/clock_hero.html fragment is styled standalone, #435) ── */
.clock-hero {
  display: flex; align-items: center; gap: 2rem; flex-wrap: wrap;
  padding: 1.75rem 2rem; border-radius: var(--radius-lg); margin-bottom: 0;
}
.clock-hero--active {
  background: linear-gradient(130deg, var(--brand-dim) 0%, #0d3a2c 100%);
  border: 1px solid rgba(16,185,129,0.4);
  box-shadow: 0 0 0 1px rgba(16,185,129,0.1), 0 8px 32px rgba(16,185,129,0.1);
}
.clock-hero--idle { background: var(--bg-panel); border: 1px solid var(--border-col); box-shadow: var(--shadow-panel); }
.clock-hero-state { display: flex; align-items: center; gap: 0.55rem; flex-shrink: 0; }
.clock-pulse-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--brand); display: inline-block; animation: clockPulse 1.8s ease-in-out infinite; }
.clock-idle-dot  { width: 10px; height: 10px; border-radius: 50%; background: var(--muted-col); display: inline-block; }
.clock-state-label { font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted-col); }
.clock-hero--active .clock-state-label { color: var(--brand); }
.clock-hero-center { flex: 1; min-width: 0; }
.clock-hero-timer { font-family: var(--font-mono); font-size: 2.75rem; font-weight: 700; color: var(--brand); line-height: 1; letter-spacing: 0.02em; margin-bottom: 0.3rem; }
.clock-hero-subtitle { font-size: 0.88rem; display: flex; align-items: center; gap: 0.4rem; color: var(--text-tertiary); }
.clock-hero--active .clock-hero-subtitle { color: rgba(184,212,202,0.8); }
.clock-hero-form { flex: 1; min-width: 0; }
.clock-form-row { display: flex; align-items: flex-end; gap: 1rem; flex-wrap: wrap; }
.clock-form-field { display: flex; flex-direction: column; gap: 0.3rem; }
.clock-form-field--job { flex: 1; min-width: 180px; }
.clock-form-label { font-family: var(--font-mono); font-size: 0.62rem; letter-spacing: 0.09em; text-transform: uppercase; color: var(--muted-col); }
.clock-form-input {
  padding: 0.55rem 0.75rem; height: 2.75rem; background: var(--bg-raised);
  border: 1px solid var(--border-col); border-radius: var(--radius-sm);
  color: var(--text-primary); font-family: var(--font-mono); font-size: 1.05rem; font-weight: 600;
  outline: none; transition: border-color 0.15s, box-shadow 0.15s;
}
.clock-form-input:focus { border-color: var(--brand); box-shadow: 0 0 0 3px rgba(16,185,129,0.15); }
.clock-form-select {
  padding: 0.55rem 0.75rem; height: 2.75rem; background: var(--bg-raised);
  border: 1px solid var(--border-col); border-radius: var(--radius-sm);
  color: var(--text-primary); font-family: var(--font-sans); font-size: 1.05rem;
  width: 100%; outline: none; transition: border-color 0.15s, box-shadow 0.15s;
}
.clock-form-select:focus { border-color: var(--brand); box-shadow: 0 0 0 3px rgba(16,185,129,0.15); }
.btn-hero {
  display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.65rem 1.75rem;
  border: none; border-radius: var(--radius-md); font-family: var(--font-cond);
  font-size: 1.05rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
  cursor: pointer; flex-shrink: 0; white-space: nowrap; transition: filter 0.15s, transform 0.1s;
}
.btn-hero:hover  { filter: brightness(1.1); transform: translateY(-1px); }
.btn-hero:active { transform: translateY(0); }
.btn-hero--start { background: var(--brand); color: #fff; }
.btn-hero--stop  { background: var(--status-red); color: #fff; }
@keyframes clockPulse {
  0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(16,185,129,0.6); }
  50%       { opacity: 0.85; box-shadow: 0 0 0 7px rgba(16,185,129,0); }
}
@media (max-width: 575px) {
  .clock-hero { padding: 1.25rem; gap: 1rem; flex-direction: column; align-items: stretch; }
  .clock-hero-timer { font-size: 2rem; }
  .clock-hero--active form { width: 100%; }
  .btn-hero { width: 100%; justify-content: center; }
  .clock-form-row { flex-direction: column; align-items: stretch; }
  .clock-form-field--job { min-width: 0; }
}

/* ── Field workflow checklist (relocated from field/workflow.html so the
      HTMX-swapped field/partials/phase_check_row.html fragment is styled, #435) ── */
.wf-checklist { margin: 0.75rem 0 0; list-style: none; padding: 0; }
.wf-check-item { display: flex; align-items: flex-start; gap: 0.6rem; padding: 0.5rem 0; border-bottom: 1px solid var(--border-col); }
.wf-check-item:last-child { border-bottom: none; }
.wf-check-btn {
  width: 28px; height: 28px; border-radius: 6px; border: 2px solid var(--border-col);
  background: transparent; display: flex; align-items: center; justify-content: center;
  flex-shrink: 0; cursor: pointer; color: transparent; transition: all 0.15s;
  -webkit-tap-highlight-color: transparent;
}
.wf-check-btn.checked { background: var(--brand); border-color: var(--brand); color: #fff; }
.wf-check-btn i { font-size: 0.9rem; }
.wf-check-text { flex: 1; font-size: 0.88rem; color: var(--text-secondary); line-height: 1.4; padding-top: 2px; }
.wf-check-text.done { text-decoration: line-through; color: var(--text-disabled); }
.wf-check-ts { font-family: var(--font-mono); font-size: 0.68rem; color: var(--muted-col); white-space: nowrap; padding-top: 4px; }

/* ── Centered modal (site_equipment/detail transfer/interval dialogs, #435).
      Opened by toggling inline display:flex; kept generic so pages that ship
      their own bottom-sheet .modal-overlay (e.g. assets/scan.html) still win. ── */
.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 1000; align-items: center; justify-content: center; }
.modal-box {
  background: var(--bg-panel); border: 1px solid var(--border-col);
  border-radius: var(--radius-md); box-shadow: var(--shadow-hover);
  width: 100%; max-width: 480px; margin: 1rem; max-height: 90vh; overflow-y: auto;
  padding: 1.5rem;
}
.modal-close {
  background: none; border: none; color: var(--muted-col); font-size: 1.5rem;
  line-height: 1; cursor: pointer; padding: 0 0.25rem;
}
.modal-close:hover { color: var(--text-primary); }
/* Chrome for the custom (.modal-box) dialogs — scoped so the Bootstrap
   .modal-content modals styled above are untouched (#434). */
.modal-box .modal-header {
  display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
  margin-bottom: 1rem; padding-bottom: 0.75rem; border-bottom: 1px solid var(--border-col);
  font-family: var(--font-cond); font-size: 1.1rem; font-weight: 700; color: var(--text-primary);
}
.modal-box .modal-body { margin-bottom: 1.25rem; }
.modal-box .modal-footer {
  display: flex; align-items: center; justify-content: flex-end; gap: 0.5rem;
  margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--border-col);
}

/* ── Form layout helpers (modals, inline forms) ── */
.form-group { display: flex; flex-direction: column; gap: 0.3rem; margin-bottom: 1rem; }
.form-row { display: flex; gap: 1rem; flex-wrap: wrap; }
.form-row .form-group { flex: 1; min-width: 160px; }
.btn-danger-ghost { color: var(--status-red); border-color: color-mix(in srgb, var(--status-red) 40%, var(--border-col)); }
.btn-danger-ghost:hover { background: var(--badge-red-bg); color: var(--badge-red-text); border-color: var(--status-red); }

/* ── Section eyebrow (in-card variant of .page-eyebrow) ── */
.section-eyebrow {
  font-family: var(--font-mono); font-size: 0.64rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--text-tertiary); margin-bottom: 0.4rem;
}
.page-subtitle { color: var(--text-tertiary); font-size: 0.9rem; margin-top: 0.25rem; }

/* ── Compliance stat-card label (pairs with .stat-value) ── */
.stat-card-label { font-family: var(--font-mono); font-size: 0.65rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted-col); margin-top: 4px; }

/* ── Metric card (owner dashboard) — stat-card with a colored top accent ── */
.metric-card {
  position: relative; background: var(--bg-panel); border: 1px solid var(--border-col);
  border-top: 3px solid var(--border-col); border-radius: var(--radius-md);
  padding: var(--space-md) var(--space-lg); box-shadow: var(--shadow-panel);
  transition: box-shadow var(--dur) var(--ease-out), transform var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out);
}
a.metric-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-hover); }
.mc-brand  { border-top-color: var(--brand); }
.mc-orange { border-top-color: var(--status-orange); }
.mc-red    { border-top-color: var(--status-red); }
.mc-green  { border-top-color: var(--status-green); }
.mc-blue   { border-top-color: var(--status-blue); }

/* ── Asset autocomplete widget (assets/_autocomplete_widget.html, assets/_search_dropdown.html) ── */
.asset-ac-wrap { position: relative; }
.ac-result-row {
  display: flex; align-items: center; gap: 0.6rem; padding: 0.5rem 0.75rem;
  cursor: pointer; border-bottom: 1px solid var(--border-col);
}
.ac-result-row:last-child { border-bottom: none; }
.ac-result-row:hover, .ac-result-row.active { background: var(--brand-glow); }

/* ── Scheduling day-pick job list (scheduling/partials/job_list.html) ──
   Browsable, paginated list of active jobs that the search box above it
   filters in place. Reuses .ac-result-row for the rows, which already carry
   the hover/keyboard highlight and the pointer cursor. There is no persistent
   .selected state: since #602 a click on a row goes straight to step 2, so no
   selection ever has to survive between clicks. */
.job-pick-meta {
  display: flex; align-items: baseline; justify-content: space-between; gap: 0.5rem;
  font-family: var(--font-mono); font-size: 0.65rem; color: var(--muted-col);
  text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 0.35rem;
}
/* No max-height here on purpose: the modal panel is the single scroll
   container, so a long page of jobs never creates a scrollbar inside a
   scrollbar. overflow:hidden is only to clip rows to the rounded corners. */
.job-pick-list {
  border: 1px solid var(--border-col); border-radius: 8px; overflow: hidden;
}
.job-pick-pager {
  display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
  margin-top: 0.5rem; font-size: 0.75rem; color: var(--muted-col);
}
.job-pick-empty {
  padding: 1rem 0.75rem; text-align: center; font-size: 0.8rem; color: var(--muted-col);
  border: 1px dashed var(--border-col); border-radius: 8px;
}

/* ── Sortable drag handle (line items, phase items) ── */
.drag-handle { cursor: grab; color: var(--muted-col); touch-action: none; }
.drag-handle:active { cursor: grabbing; }

/* ── Inline validation message (schedule time fields) ── */
.schedule-time-error { color: var(--status-red); font-size: 0.78rem; margin-top: 0.25rem; }

/* ── Editable line-item cells (invoice/PO builders) ── */
.line-item, .part-row { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.line-qty, .line-price, .line-subtotal, .qty-display { font-family: var(--font-mono); font-variant-numeric: tabular-nums; text-align: right; }

/* ── Billing / customer location panels (HTMX partials) ── */
.loc-form-wrap { padding: 1rem; background: var(--bg-raised); border: 1px solid var(--border-col); border-radius: var(--radius-md); }
.loc-row { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; padding: 0.6rem 0; border-bottom: 1px solid var(--border-col); }
.loc-row:last-child { border-bottom: none; }

/* ── Phase-template item panel (HTMX partial) ── */
.phase-header-row { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
.phase-header-label { font-family: var(--font-cond); font-weight: 600; color: var(--text-primary); }
.rename-phase-toggle { background: none; border: none; color: var(--muted-col); cursor: pointer; padding: 0.2rem; }
.rename-phase-toggle:hover { color: var(--brand); }
.move-phase-btn { background: none; border: none; color: var(--muted-col); cursor: pointer; padding: 0.2rem; }
.move-phase-btn:hover { color: var(--brand); }
.delete-phase-btn { background: none; border: none; color: var(--status-red); cursor: pointer; padding: 0.2rem; opacity: 0.7; }
.delete-phase-btn:hover { opacity: 1; }
.rename-phase-form { display: flex; align-items: center; gap: 0.4rem; }
.new-phase-label-wrap { display: flex; align-items: center; gap: 0.5rem; margin-top: 0.5rem; }

/* ── Job-form template picker + tech chooser (mostly JS hooks; inline styles win) ── */
.tech-item { display: flex; align-items: center; gap: 0.5rem; cursor: pointer; }
/* #394 item 2: rolling workload readout beside a technician in the assign
   pickers. Muted on purpose — it is orientation while scanning the roster, not
   a status the eye should land on before the person's own name. Absent
   entirely for a tech with a clear week, so what shows up is what's booked. */
.tech-load-badge {
  display: inline-flex; align-items: center; gap: 0.3rem; white-space: nowrap;
  font-family: var(--font-mono); font-size: 0.68rem; color: var(--text-tertiary);
}
.tech-load-badge i { font-size: 0.62rem; }

/* #394 item 3: double-booking warning in the day-assign modal. Amber rather
   than red — nothing has gone wrong, and the dispatcher is allowed to proceed;
   red here would read as a hard failure and get clicked past on reflex. */
.sched-conflict-warning {
  border: 1px solid rgba(245, 158, 11, 0.45);
  background: rgba(245, 158, 11, 0.1);
  border-radius: var(--radius-sm);
  padding: 0.5rem 0.65rem;
  margin-bottom: 0.5rem;
}
.sched-conflict-title {
  display: flex; align-items: center; gap: 0.35rem;
  font-size: 0.78rem; font-weight: 600; color: #FBBF24;
}
.sched-conflict-list {
  margin: 0.35rem 0 0; padding-left: 1.1rem;
  font-size: 0.74rem; color: var(--text-secondary);
}
.sched-conflict-hint {
  margin-top: 0.35rem; font-size: 0.7rem; color: var(--text-tertiary);
}
/* ── Event-detail modal (#624) ────────────────────────────────────────────────
   The body grew one control at a time — trims in #593, notify in #616, resize
   in #620 — and each one landed as another margin-stacked <div>. A job with
   three technicians therefore rendered as nine loose stripes in a 420px panel
   with nothing to say where one person ended and the next began.

   The rules below impose three zones instead: identity, a recessed context
   strip, and one bordered card per person. Type is held to five steps —
   0.95 / 0.85 / 0.8 / 0.72 / 0.65rem — because the seven ad-hoc sizes it had
   were being asked to do the job that grouping should do. */

.sched-evt-notice { font-size: 0.8rem; margin-bottom: 0.6rem; }

.sched-evt-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 0.5rem; margin-bottom: 0.7rem;
}
/* Job number and day sit above the customer, not beside it: the old
   "JOB-0042 — Acme Fuel" line made an id compete with a name, and the modal
   never stated which day's times it was editing at all. Mono uppercase marks
   it as reference data rather than something to read first. */
.sched-evt-eyebrow {
  font-family: var(--font-mono); font-size: 0.65rem; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--muted-col);
}
/* Condensed, matching .board-range-label — the modal should read as part of
   the board it was opened from. */
.sched-evt-title {
  font-family: var(--font-cond); font-weight: 700; font-size: 1.05rem;
  line-height: 1.2; color: var(--text-primary); margin-top: 0.1rem;
}

/* Location and both outbound links share one recessed strip. They used to be
   separate blocks holding two full ghost buttons, which gave "View Job" and
   "Maps" the same weight as Save; on bg-raised they read as the panel's
   context rather than as its controls. */
.sched-evt-context {
  background: var(--bg-raised); border-radius: var(--radius-md);
  padding: 0.55rem 0.65rem; margin-bottom: 0.85rem;
}
/* A job with no location leaves only "View Job" in the strip, and a filled bar
   holding one small link reads as an empty input. Nothing to recess, so don't. */
.sched-evt-context-bare { background: none; padding: 0 0 0 0.1rem; margin-bottom: 0.75rem; }
.sched-loc { display: flex; align-items: flex-start; gap: 0.45rem; }
.sched-loc > i { color: var(--muted-col); font-size: 0.78rem; line-height: 1.5; }
.sched-loc-label { font-size: 0.8rem; font-weight: 600; color: var(--text-primary); }
.sched-loc-addr { font-size: 0.72rem; color: var(--text-tertiary); line-height: 1.35; }
.sched-evt-links { display: flex; flex-wrap: wrap; align-items: center; gap: 0.9rem; }
/* Only ruled off when there is an address above them to be ruled off from. */
.sched-loc + .sched-evt-links {
  margin-top: 0.5rem; padding-top: 0.45rem;
  border-top: 1px solid var(--border-col);
}
/* Text links, not buttons: leaving the modal should never look like an action
   taken inside it. */
.sched-evt-link {
  display: inline-flex; align-items: center; gap: 0.3rem;
  font-size: 0.72rem; font-weight: 600; color: var(--text-tertiary);
  text-decoration: none;
  transition: color var(--dur) var(--ease-out);
}
.sched-evt-link:hover { color: var(--brand); }
.sched-evt-link i { font-size: 0.7rem; }

.sched-team-head { display: flex; align-items: center; gap: 0.4rem; margin-bottom: 0.5rem; }
.sched-team-count {
  font-family: var(--font-mono); font-size: 0.65rem; color: var(--text-tertiary);
  background: var(--bg-raised); border-radius: var(--r-pill); padding: 0.05rem 0.4rem;
}

/* One person, one card. The border is what tells people apart, which is what
   frees everything inside it to stay quiet. */
.sched-person {
  border: 1px solid var(--border-col); border-radius: var(--radius-md);
  padding: 0.6rem 0.65rem;
}
.sched-person + .sched-person { margin-top: 0.5rem; }
/* Wraps rather than truncates: a long display name plus a full date range does
   not fit on one line at 420px, and both are worth more than the tidy row. */
.sched-person-head {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 0.3rem 0.5rem; margin-bottom: 0.45rem;
}
.sched-person-name { font-size: 0.85rem; font-weight: 600; color: var(--text-primary); }
/* The booked run was a sentence *below* the trim buttons ("Booked Aug 12–Aug 14
   (3 days)."). It is the answer to "what am I about to trim?", so it belongs
   beside the name where it is read first — and as a read-out, not a control,
   which is why it borrows .roster-span-badge's treatment and not a button's. */
.sched-person-span {
  display: inline-flex; align-items: center; gap: 0.3rem; white-space: nowrap;
  font-family: var(--font-mono); font-size: 0.65rem; color: var(--text-tertiary);
  background: var(--bg-raised); border-radius: var(--r-pill); padding: 0.1rem 0.45rem;
}

/* Per-person time editor. Stacked rather than one wide flex row: the modal is
   420px and the old single-row layout pushed Save/Clear off the panel's edge.
   Mirrors the day-assign modal — fields first, actions on their own line,
   left-aligned. Spacing is the card's job now, so the block carries none. */
.sched-time-block { margin: 0; }
.sched-time-row { display: flex; align-items: center; gap: 0.4rem; margin-bottom: 0.5rem; }
.sched-time-row .form-input { font-size: 0.8rem; }
.sched-time-sep { font-size: 0.72rem; color: var(--muted-col); }
.sched-time-block .schedule-time-error { margin: 0 0 0.45rem; }
.sched-time-actions { display: flex; align-items: center; gap: 0.4rem; }
.sched-time-actions .btn-brand,
.sched-time-actions .btn-ghost { font-size: 0.8rem; padding: 0.3rem 0.9rem; }

/* Card footer: notify on the left, the trims pushed to the far right. The
   positive action leads and the destructive ones sit where a mis-aimed click
   is least likely. Wrapping at this width is expected — when the trims drop to
   their own line, margin-left:auto keeps them on their right edge so the row
   still reads as one group rather than a new stripe. */
.sched-person-foot {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem 0.5rem;
  margin-top: 0.55rem; padding-top: 0.5rem;
  border-top: 1px dashed var(--border-col);
}
.sched-notify { display: flex; align-items: center; min-width: 0; }
.sched-notify form { margin: 0; }
.sched-notify-btn { font-size: 0.72rem; padding: 0.2rem 0.6rem; }
.sched-notify-done i { color: var(--status-green); margin-right: 0.15rem; }

/* Day-level unschedule on the event modal (#593). Destructive, so it is muted
   until hover — a dispatcher opens this modal to set times far more often than
   to unbook someone. The rule that used to separate it from the time form now
   belongs to .sched-person-foot, which separates the whole footer at once.

   Given the whole second footer line, right-aligned: at 420px these never
   shared a line with the notify button without breaking into three ragged rows
   — the stripe problem this pass exists to remove, reproduced inside the card.
   Claiming the line outright is also why every card's footer is the same shape
   whether a person has one trim or three. */
.sched-trims {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end;
  gap: 0.15rem; flex: 1 1 100%;
}
.sched-trim-block { margin: 0; }
.sched-trim-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.15rem; }
/* Borderless, unlike the notify button beside them. The one framed control in
   the footer is the constructive one; a destructive action that has to be
   aimed at is safer than one wearing a button's affordance. */
.sched-trim-btn, .sched-quiet-btn {
  font-size: 0.72rem; padding: 0.2rem 0.45rem;
  color: var(--text-tertiary); border-color: transparent; background: transparent;
}
.sched-trim-btn:hover {
  color: var(--status-red); background: var(--badge-red-bg); border-color: transparent;
}
/* "Edit all days" opens the month picker — navigation, not a trim, so it must
   not inherit a red hover that means "this unbooks someone". Same reasoning
   that kept the notify button off .sched-trim-btn in #616. */
.sched-quiet-btn:hover {
  color: var(--brand); background: var(--bg-raised); border-color: transparent;
}
.sched-trim-hint { font-size: 0.72rem; color: var(--muted-col); }

/* ── Board day picker (#593): the scattered-days editor ported from the job
      page, plus bulk time editing across an existing booking. ── */
.daypick-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.5rem; margin-bottom: 0.6rem; }
.daypick-title { font-weight: 700; font-size: 0.95rem; }
.daypick-sub { font-size: 0.75rem; color: var(--text-tertiary); margin-top: 0.15rem; }
.daypick-close { background: none; border: none; padding: 0; cursor: pointer; color: var(--muted-col); font-size: 0.9rem; }
.daypick-nav { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.5rem; }
.daypick-nav-btn { font-size: 0.8rem; padding: 0.2rem 0.6rem; }
.daypick-month { font-family: var(--font-mono); font-size: 0.8rem; color: var(--text-secondary); }
.daypick-grid { width: 100%; border-collapse: collapse; }
.daypick-grid th {
  font-size: 0.65rem; font-weight: 600; color: var(--muted-col);
  text-transform: uppercase; letter-spacing: 0.05em; padding-bottom: 0.3rem;
}
.daypick-grid td { padding: 1px; text-align: center; }
.daypick-day {
  width: 100%; min-width: 2rem; padding: 0.35rem 0; cursor: pointer;
  background: none; border: 1px solid transparent; border-radius: 4px;
  font-size: 0.8rem; color: var(--text-secondary);
}
.daypick-day:hover { border-color: var(--border-col); }
.daypick-day-on { background: var(--brand-col, #2563eb); color: #fff; font-weight: 600; }
.daypick-day-outside { opacity: 0.35; }
/* Shaded, not disabled: working days govern expansion, never what a dispatcher
   may deliberately book. */
.daypick-day-nonwork { background-image: linear-gradient(135deg, transparent 45%, var(--border-col) 45%, var(--border-col) 55%, transparent 55%); }
.daypick-day-on.daypick-day-nonwork { background-image: none; }
.daypick-removed, .daypick-notice {
  display: flex; align-items: flex-start; gap: 0.4rem;
  font-size: 0.78rem; line-height: 1.35; margin-bottom: 0.5rem;
  padding: 0.4rem 0.55rem; border-radius: 4px; border: 1px solid var(--border-col);
}
.daypick-removed { border-color: var(--warning, #ca8a04); }
.daypick-times { margin-top: 0.75rem; border-top: 1px solid var(--border-col); padding-top: 0.6rem; }
.daypick-times-head {
  font-size: 0.65rem; color: var(--muted-col); text-transform: uppercase;
  letter-spacing: 0.05em; margin-bottom: 0.4rem;
}
.daypick-time-row { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; margin-bottom: 0.35rem; }
.daypick-time-date { font-size: 0.75rem; min-width: 6.5rem; color: var(--text-secondary); }
.daypick-time-row .form-input { font-size: 0.75rem; padding: 0.15rem 0.3rem; }
.daypick-time-sep { font-size: 0.7rem; color: var(--muted-col); }
.daypick-time-mixed { font-size: 0.65rem; color: var(--muted-col); font-style: italic; }
.daypick-time-error { font-size: 0.7rem; color: #EF4444; }
.daypick-times-actions { margin-top: 0.5rem; }
.daypick-times-actions .btn-brand { font-size: 0.8rem; padding: 0.3rem 0.9rem; }

/* ── Job roster (#593): the job page is a roster now, not a scheduler ── */
/* role_label edits in place — it was previously writable only while creating an
   assignment, through a route #593 deletes. Borderless until focus so a roster
   of blank roles reads as a list, not a form. */
.roster-role-form { margin: 0.2rem 0 0; }
.roster-role-input {
  width: 100%; padding: 0.1rem 0.25rem;
  background: none; border: 1px solid transparent; border-radius: 3px;
  font-size: 0.75rem; color: var(--text-secondary);
}
.roster-role-input::placeholder { color: var(--muted-col); font-style: italic; }
.roster-role-input:hover { border-color: var(--border-col); }
.roster-role-input:focus {
  outline: none; border-color: var(--brand-col, #2563eb);
  background: var(--bg-panel); color: var(--text-primary);
}
/* Read-out, not a button: editing the schedule happens on the board. */
.roster-span-badge {
  display: inline-flex; align-items: center; gap: 0.25rem;
  flex-shrink: 0; white-space: nowrap;
  font-size: 0.7rem; color: var(--text-tertiary);
  padding: 0.15rem 0.4rem; border-radius: 3px; background: var(--bg-panel);
}
.roster-span-count { color: var(--muted-col); }

.tmpl-filter-item { cursor: pointer; }
.tmpl-checkbox { cursor: pointer; }
.tmpl-label-text { color: var(--text-secondary); }

/* ── Misc single-use element hooks (kept minimal; give them a real definition
      so they render intentionally and satisfy the undefined-class gate, #435) ── */
.tag-chip {
  display: inline-flex; align-items: center; gap: 0.3rem; padding: 0.1rem 0.5rem;
  border-radius: var(--r-pill); background: var(--bg-raised); border: 1px solid var(--border-col);
  font-family: var(--font-mono); font-size: 0.72rem; color: var(--text-tertiary);
}
.qr-modal-btn, .reorder-edit-btn, .pull-part-btn, .map-toggle-btn, .theme-btn, .audit-expandable { cursor: pointer; }
.open-cust-doc-modal { cursor: pointer; }
.interval-field { display: flex; flex-direction: column; gap: 0.3rem; }
.approval-comment-input {
  width: 100%; background: var(--bg-raised); border: 1px solid var(--border-col);
  border-radius: var(--radius-sm); color: var(--text-primary); padding: 0.4rem 0.6rem; font-size: 0.85rem;
}

/* ── SHARED TABLE / FORM UTILITIES (#581) ──────────────────────────────────
   Extracted from the equipment-allocation card. These replace the repeated
   inline `style="..."` runs that the older job partials use; they live here
   rather than in a page <style> block because HTMX fragments are swapped in
   without the page's inline CSS (#435). */

/* Small uppercase mono section heading — the pattern already repeated by hand
   across the job, template and scheduling cards. */
.section-label-mono {
  font-family: var(--font-mono); font-size: 0.7rem; text-transform: uppercase;
  color: var(--muted-col); letter-spacing: 0.08em; margin: 0;
}

/* Table cell helpers */
.cell-nowrap  { white-space: nowrap; }
.cell-mono    { font-family: var(--font-mono); font-size: 0.8rem; }
.cell-muted   { color: var(--muted-col); }
.cell-subtext { color: var(--text-tertiary); font-size: 0.8rem; }
.table-empty  { text-align: center; color: var(--muted-col); padding: 2rem; }

/* Table row treatments */
.row-raised { background: var(--bg-raised); }
.row-dim    { opacity: 0.62; }

/* Inline links inside data tables */
.link-brand { color: var(--brand); text-decoration: none; }
.link-brand:hover { text-decoration: underline; }

/* Secondary hint text beside a form label */
.label-hint { color: var(--text-tertiary); font-weight: 400; text-transform: none; }

/* Ghost button that reads as destructive without becoming a solid red block */
.btn-danger-ghost { color: var(--status-red); }

/* Inline advisory strip: availability answers, reassign explanations.
   Colour-coded but never colour-only — each variant carries an icon. */
.alloc-hint {
  display: flex; align-items: flex-start; gap: 0.5rem;
  padding: 0.5rem 0.75rem; border-radius: var(--radius-sm);
  font-size: 0.8rem; line-height: 1.4;
}
.alloc-hint-ok {
  background: var(--badge-green-bg); color: var(--badge-green-text);
}
.alloc-hint-warn {
  background: var(--badge-yellow-bg); color: var(--badge-yellow-text);
}
.alloc-hint-note {
  background: var(--bg-raised); color: var(--text-tertiary);
  margin-top: 0.6rem;
}

/* Reassign row expands inside the table, so it needs its own padding */
.reassign-form { padding: 0.85rem 1rem 1rem; }

/* Compact modal chrome shared by the scheduling calendar's popovers (#581).
   Defined here, not in scheduling/calendar.html's <style>, because the modal
   bodies are swapped in as HTMX fragments (#435). */
.modal-title-sm { font-weight: 700; font-size: 0.95rem; }
.modal-body-sm  { font-size: 0.85rem; }
.modal-close-btn {
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--muted-col); font-size: 0.9rem;
}
.modal-section-divider {
  border-top: 1px solid var(--border-col); padding-top: 0.6rem; font-size: 0.85rem;
}

/* ═══════════════════════════════════════════════════════════════════════════
   DISPATCH BOARD (#589)

   A resource timeline: one lane (row) per technician, then one per equipment
   asset, with work drawn as chips along a time axis. Replaces the calendar's
   fixed-width day cells, where crowding turned into overlap and "+3 more".

   Defined here rather than in scheduling/board.html's inline style block
   because the grid is swapped in as an HTMX fragment on every navigation,
   filter change and drop — a class living only in the parent page would be
   missing from the swapped markup (#435, scripts/check_undefined_css.py).

   Layout: each lane is a label cell plus a CSS-grid track. Chips set an
   explicit grid-column and let auto-placement pick the row, so two chips that
   overlap in time are pushed onto separate rows automatically and the lane
   grows taller. That is the whole point — a double-booking becomes visible
   geometry instead of something to go hunting for.
   ═══════════════════════════════════════════════════════════════════════════ */

.board-layout { display: flex; gap: var(--space-md); align-items: flex-start; }
.board-main   { flex: 1 1 auto; min-width: 0; }
/* 200px, not 240px (#616): the rail holds two-line job chips and was costing
   the grid more width than it needed. A partial answer only — the real fix is
   splitting the alert panels out of the rail entirely, which is its own change. */
.board-side   { flex: 0 0 200px; }

/* Toolbar: range nav, view switch, filters */
.board-toolbar {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-sm);
  margin-bottom: var(--space-sm);
}
.board-toolbar-spacer { flex: 1 1 auto; }
.board-range-label {
  font-family: var(--font-cond); font-weight: 700; font-size: 1.05rem;
  color: var(--text-primary); white-space: nowrap;
}
.board-filters {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-xs);
  margin-bottom: var(--space-sm);
}
.board-filter-input {
  font-size: 0.8rem; padding: 0.3rem 0.55rem; border-radius: var(--r-control);
  border: 1px solid var(--border-col); background: var(--bg-panel);
  color: var(--text-primary); font-family: var(--font-sans);
}
.board-filter-input:focus { outline: none; box-shadow: var(--focus-ring); }
.board-filter-check {
  display: inline-flex; align-items: center; gap: 0.35rem; margin: 0;
  font-size: 0.78rem; color: var(--text-tertiary); cursor: pointer;
  white-space: nowrap;
}

/* The board scrolls on its own; the page body never does.
 *
 * max-height is what makes the sticky header below actually work (#616), and
 * it is not obvious why. `overflow-x: auto` with overflow-y left at `visible`
 * computes overflow-y to `auto` — so this element was ALREADY the scrollport
 * in both axes. But with a content-driven height it never scrolls vertically
 * (the page does), and a sticky child resolves `top` against its scrollport,
 * not the viewport. Result: `position: sticky; top: 56px` on .board-head did
 * nothing at all — verified in a browser, the header scrolled clean off.
 * Bounding the height makes this a real vertical scrollport, so the header
 * sticks to the top of the *board* (top: 0, not the 56px topnav offset) and
 * the lane labels stick to its left edge. Filters and range nav stay put
 * above it as a side effect, which is what you want on a 20+ lane board.
 */
.board-scroll {
  overflow-x: auto; max-height: calc(100vh - 17rem);
  border: 1px solid var(--border-col);
  border-radius: var(--radius-md); background: var(--bg-panel);
}
.board-inner { min-width: 720px; }

/* Column headers. Sticky to the scrollport's top edge so "which column is
   Thursday" survives scrolling a long lane list. Above the lane labels, which
   are in turn above the chips (z-index 1). */
.board-head {
  display: flex; border-bottom: 1px solid var(--border-col);
  position: sticky; top: 0; z-index: 3; background: var(--bg-panel);
}
/* The corner: sticky in both axes at once, so it holds the top-left while
   either scroll runs. Needs its own opaque background or the column headings
   slide visibly underneath it. */
.board-head-spacer {
  flex: 0 0 var(--board-label-w, 150px); border-right: 1px solid var(--border-col);
  position: sticky; left: 0; z-index: 1; background: var(--bg-panel);
}
.board-head-cols {
  flex: 1 1 auto; display: grid; min-width: 0;
  grid-template-columns: repeat(var(--board-cols), minmax(0, 1fr));
}
.board-col-head {
  padding: 0.4rem 0.35rem; text-align: center; font-family: var(--font-cond);
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--text-tertiary);
  border-left: 1px solid var(--border-col);
}
.board-col-head:first-child { border-left: none; }
.board-col-head-date {
  display: block; font-family: var(--font-sans); font-weight: 600;
  font-size: 0.8rem; color: var(--text-secondary); letter-spacing: 0;
  text-transform: none;
}
.board-col-today { background: var(--brand-glow); color: var(--brand); }
.board-col-today .board-col-head-date { color: var(--brand); }

/* A non-working day is a label, not a status (#616).
   It used to fill the header *and* every cell in the column with --bg-raised,
   which was wrong twice over: at #EEF4F2-on-#FFFFFF (and #1E2B22-on-#172018)
   it was a ~3% luminance step nobody could see, and --bg-raised is this
   sheet's "muted / inactive" token (.board-group-head, .badge-grey,
   .board-lane-empty) — so it read as "you cannot schedule here" on a live
   drop target. Weekend and after-hours work is normal in this business.
   The cue is now header-only and structural: an underline in --muted-col,
   the token already used for metadata (breadcrumbs, captions), plus one seam
   at the workweek boundary. Lane cells are left completely uniform, which is
   the point — a Saturday cell is pixel-identical to a Tuesday one.

   *Which* days these are comes from AppSettings.scheduling_working_days, not
   from weekday() >= 5, so an install that works Saturdays marks none of them
   and one that takes Wednesdays off marks the Wednesday. That also means the
   count is not fixed at two, and the seam is not always Fri→Sat — the
   adjacent-sibling rule below draws it wherever the boundary actually falls,
   including more than once for a split week.

   Today still outranks it: the template's {% if is_today %}...{% elif
   is_non_working %} keeps the two mutually exclusive. */
.board-col-nonworking { border-bottom: 2px solid var(--muted-col); }
.board-col-head:not(.board-col-nonworking) + .board-col-nonworking {
  border-left: 2px solid var(--muted-col);
}

/* Group separator: TECHNICIANS / EQUIPMENT */
.board-group-head {
  padding: 0.3rem var(--space-sm); background: var(--bg-raised);
  border-top: 1px solid var(--border-col); border-bottom: 1px solid var(--border-col);
  font-family: var(--font-cond); font-size: 0.7rem; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-tertiary);
}

/* Lanes */
.board-lane { display: flex; border-bottom: 1px solid var(--border-col); }
.board-lane:last-child { border-bottom: none; }
/* Sticky left, so a horizontal scroll can never leave you reading an anonymous
   row (#616). Note this is currently defensive rather than load-bearing: the
   track's columns are minmax(0, 1fr), so day view's 56 quarter-hour columns
   compress to fit instead of widening the track, and .board-scroll therefore
   does not overflow horizontally at any realistic width — verified in a
   browser. It engages the moment something does widen the track, which giving
   short day-view chips a minimum width would do. Opaque background required
   either way: chips pass underneath, and they sit at z-index 1. */
.board-lane-label {
  flex: 0 0 var(--board-label-w, 150px); padding: 0.45rem var(--space-sm);
  border-right: 1px solid var(--border-col); min-width: 0;
  font-size: 0.82rem; font-weight: 600; color: var(--text-secondary);
  display: flex; flex-direction: column; justify-content: center;
  position: sticky; left: 0; z-index: 2; background: var(--bg-panel);
}
.board-lane-name  { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.board-lane-sub   { font-size: 0.68rem; font-weight: 400; color: var(--text-tertiary); }
.board-lane-empty .board-lane-label { color: var(--text-tertiary); font-weight: 400; }

/* The chip track. Chips are the grid items: each sets an explicit grid-column
   and lets auto-placement choose the row, so overlapping bookings stack and
   the lane grows. Column backgrounds and drop targets therefore CANNOT be
   grid items in the same grid — they would consume placement slots and push
   chips around. They live in an absolutely-positioned mirror grid underneath. */
.board-track {
  flex: 1 1 auto; min-width: 0; position: relative; min-height: 1.9rem;
  display: grid; grid-template-columns: repeat(var(--board-cols), minmax(0, 1fr));
  grid-auto-rows: minmax(1.75rem, auto); gap: 2px; padding: 2px 0;
}
.board-cells {
  position: absolute; inset: 0; display: grid;
  grid-template-columns: repeat(var(--board-cols), minmax(0, 1fr));
}
/* Real buttons, not decorated divs: clicking an empty slot is how work gets
   scheduled, so it has to be reachable from the keyboard too.

   `position: relative` is the containing block for .board-cell-add below, the
   same way .board-chip-draggable is one for the resize grips. Leave it without
   a z-index: a positioned element with z-index: auto creates no stacking
   context, so the add button's own z-index still resolves against the chips
   rather than being trapped under them. That is no longer load-bearing now the
   affordance lives in a reserved strip, but it is free to keep. */
.board-cell {
  appearance: none; background: transparent; padding: 0; cursor: pointer;
  border: none; border-left: 1px solid var(--border-col);
  position: relative;
}
.board-cell:first-child { border-left: none; }
.board-cell:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* #628: an empty cell used to give no hover feedback whatsoever — only
   :focus-visible — so nothing said the background was a button. Half of "it's
   hard to click the day" was that the target was never advertised. */
.board-cell:hover { background: var(--brand-glow); }
.board-cell-today   { background: var(--brand-glow); }
.board-cell-droptarget {
  background: var(--brand-glow); box-shadow: inset 0 0 0 2px var(--brand);
}

/* #628: reaching a day that already has work on it.
   A chip covers its cell exactly — align-self: stretch across the full column
   — so the only pixels left of an occupied .board-cell were the track's 2px
   gap. There was no "empty part of the cell" to aim at, by construction, and
   scheduling a second job (or a second tech) on a busy day had no click path
   at all. Dragging from the Unassigned panel was the only way round it, and
   that panel drops a job the moment anyone is on it, so it could never staff
   a second person.

   This is a span, not a button: nesting a button inside .board-cell would be
   invalid HTML and would add a second tab stop for a day that already has one.
   It is decorative and aria-hidden — the click bubbles to the .board-cell it
   sits in, so board.html's existing handler resolves the same cell, the same
   date and the same lane it would for a click on open background. No JS knows
   this element exists.

   The affordance sits in a reserved strip along the bottom of the lane rather
   than on top of the chip. The first cut painted it over the chip, centred,
   and QA rejected it on two counts that turned out to be the same count: it
   covered the job number, and it had to be revealed for the whole lane at once
   (seven plus signs to offer one), because a chip swallows the hover of the
   cell beneath it and a per-column reveal would have lit up every column
   *except* the occupied ones it exists for.

   Reserving space fixes both at once. .board-cells is inset: 0, which resolves
   against the containing block's **padding** box, so the cells already reach
   into the padding below; chips are grid items and stay in the content box.
   The strip is therefore chip-free by construction, which means nothing
   swallows its hover either — .board-cell:hover fires there normally and the
   reveal is per column in plain CSS, no mousemove handler required.

   Only lanes that actually hold chips pay the height: an empty lane has no
   chip to dodge, and .board-lane-empty is already on it.

   z-index 2 is belt-and-braces now rather than the mechanism — nothing
   overlaps the strip — but it costs nothing and keeps the control on top if a
   future chip style grows into the padding. */
.board-lane:not(.board-lane-empty) .board-track { padding-bottom: 1.15rem; }
.board-cell-add {
  position: absolute; z-index: 2; bottom: 1px; left: 50%;
  transform: translateX(-50%);
  display: flex; align-items: center; justify-content: center;
  width: 1rem; height: 1rem; border-radius: var(--radius-sm);
  font-family: var(--font-sans); font-size: 0.8rem; line-height: 1;
  color: var(--muted-col); opacity: 0;
  transition: opacity 0.12s ease, background 0.12s ease, color 0.12s ease;
}
.board-cell-add::before { content: "+"; }
/* Per column, and the cell is the unit — hovering anywhere in this column's
   strip arms this column. There is deliberately no separate :hover state on
   the span itself: it is not independently targetable (the whole cell is the
   click target), and a `.board-cell-add:hover` rule at (0,2,0) would in any
   case lose to the (0,3,0) below and sit there looking disabled while armed,
   which is exactly how the first cut broke. */
.board-cell:hover .board-cell-add,
.board-cell:focus-visible .board-cell-add {
  opacity: 1; background: var(--brand); color: var(--brand-ink);
}
/* A drag or a resize has its own running feedback — the droptarget outline and
   the chip preview. Leaving a row of plus signs lit underneath advertises a
   click that is not what the pointer is currently doing. */
.board-dragging .board-cell-add,
.board-resizing .board-cell-add { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  .board-cell-add { transition: none; }
}
/* While a drag is in flight the chips must stop swallowing pointer events, or
   dropping onto an already-occupied day would be impossible — which is most of
   the interesting drops.

   **Every chip except the one being dragged** (#620). Without the :not(), this
   rule also hit the drag source, because `board-dragging` goes on the grid in
   the same dragstart handler that starts the drag — so the chip made itself
   non-hit-testable mid-gesture and Chromium abandoned the drag. No drop event
   ever fired and no move was ever posted: chip drag-and-drop was dead on the
   board from #589 onwards, while dragging an *unassigned* job kept working the
   whole time because `.board-unassigned-item` is not a `.board-chip` and was
   never matched here. That asymmetry is exactly what QA reported.

   No pytest covers this. board_move's tests POST the endpoint directly and all
   passed throughout; what was broken was the browser gesture that reaches it.

   The other half of the rule is load-bearing and must stay: with it removed
   entirely, an occupying chip swallows `dragover`, `closest('.board-cell')`
   returns null, and dropping onto a day that already has work is impossible. */
.board-dragging .board-chip:not(.board-chip-dragging) { pointer-events: none; }

/* Transient confirmation for a modal action whose effect lands off-screen.
   Above the modals (z-index 1080/1085) because it reports on what just closed
   one. Not interactive, so it never takes the pointer. */
.sched-toast {
  position: fixed; bottom: 1.5rem; left: 50%; transform: translate(-50%, 0.75rem);
  z-index: 1090; pointer-events: none; opacity: 0;
  max-width: min(28rem, calc(100vw - 2rem));
  padding: 0.6rem 1rem; border-radius: var(--radius-md);
  background: var(--bg-panel); color: var(--text-primary);
  border: 1px solid var(--border-col); box-shadow: var(--shadow-hover);
  font-size: 0.85rem; text-align: center;
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.sched-toast-in { opacity: 1; transform: translate(-50%, 0); }
@media (prefers-reduced-motion: reduce) {
  .sched-toast { transition: none; }
}

/* Chips */
.board-chip {
  z-index: 1; align-self: stretch; display: flex; align-items: center; gap: 0.3rem;
  min-width: 0; padding: 0.2rem 0.45rem; border-radius: var(--radius-sm);
  font-size: 0.74rem; line-height: 1.25; text-align: left;
  background: var(--badge-blue-bg); color: var(--badge-blue-text);
  border: 1px solid transparent; cursor: pointer; overflow: hidden;
  font-family: var(--font-sans);
}
.board-chip:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.board-chip-label {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  min-width: 0; flex: 1 1 auto;
}
.board-chip-time { font-variant-numeric: tabular-nums; opacity: 0.85; flex: 0 0 auto; }
/* A one-day chip is narrow, and the start time was winning the fight for that
   space — leaving "J... 06:30 AM", which identifies nothing. The job number is
   what a dispatcher scans for, so the time gives way below the width where
   both fit. It is still on the hover title and in the detail modal. */
.board-chip { container-type: inline-size; }
@container (max-width: 150px) {
  .board-chip-time { display: none; }
}
.board-chip-draggable { cursor: grab; }
.board-chip-dragging  { opacity: 0.45; cursor: grabbing; }

/* #620: resize grips. Absolutely positioned inside the chip, which needs to be
   a containing block for them — `position: relative` here rather than on the
   base rule so the chip's z-index/grid behaviour above is left alone. */
.board-chip-draggable { position: relative; }
.board-chip-grip {
  position: absolute; top: 0; bottom: 0; width: 9px;
  cursor: col-resize; opacity: 0; transition: opacity 0.12s ease;
  display: flex; align-items: center; justify-content: center;
}
.board-chip-grip-start { left: 0; }
.board-chip-grip-end   { right: 0; }
/* The grip is wider than the mark it draws: 9px is a forgiving target on a
   chip that can be a single narrow day, while the 2px bar keeps a busy week
   from reading as a row of handles. */
.board-chip-grip::before {
  content: ""; width: 2px; height: 60%; border-radius: 1px;
  background: currentColor; opacity: 0.7;
}
/* Revealed on approach, and kept visible for the whole drag — a grip that
   vanished the moment the pointer left the chip would blink out mid-resize. */
.board-chip:hover > .board-chip-grip,
.board-chip:focus-visible > .board-chip-grip,
.board-chip-resizing > .board-chip-grip { opacity: 1; }
/* The pointer leaves the chip almost immediately once a resize starts, so the
   col-resize cursor has to be asserted across the grid or it reverts to
   whatever is under it and the gesture stops looking like a drag. */
.board-resizing, .board-resizing * { cursor: col-resize !important; }
/* A resize sweeps the pointer across whatever else is in the lane. Without
   this those chips light up their own grips and offer a grab cursor,
   advertising gestures that are not available until this one finishes. The
   chip being resized keeps its pointer events so it can still show them. */
.board-resizing .board-chip:not(.board-chip-resizing) { pointer-events: none; }
@media (prefers-reduced-motion: reduce) {
  .board-chip-grip { transition: none; }
}

/* Equipment reads amber with a truck icon, matching the calendar (#391/#581). */
.board-chip-equipment {
  background: var(--badge-orange-bg); color: var(--badge-orange-text);
}
/* Planned but not yet out of the shop — hollowed out rather than merely paler,
   so the distinction survives greyscale and does not rest on colour alone. */
.board-chip-planned {
  background: transparent; border-style: dashed;
  border-color: var(--badge-orange-text);
}
/* #640: an assigned work order. Neutral rather than a fifth colour — the warm
   palette is spoken for by dispatchable work (blue job, amber equipment, red
   emergency), and maintenance is precisely the block a dispatcher cannot act
   on. Grey says "this day is spoken for, but not by me", which is the whole
   message. The wrench carries the distinction into greyscale. */
.board-chip-workorder {
  background: var(--bg-raised); color: var(--text-secondary);
  border-color: var(--border-col);
}
/* Emergency last so it wins over the equipment treatment, as on the calendar. */
.board-chip-emergency {
  background: var(--badge-red-bg); color: var(--badge-red-text); font-weight: 700;
}
/* "Every day it is open" — no date rows behind it, so it cannot be dragged.
   Striped to read as a background condition rather than a placed booking. */
.board-chip-unscoped {
  background: repeating-linear-gradient(
    45deg, var(--bg-raised), var(--bg-raised) 6px,
    transparent 6px, transparent 12px);
  color: var(--text-tertiary); border-color: var(--border-col);
}
/* Runs off the edge of the view: square that side off and dot the border, so a
   clipped span never reads as work that stops there. */
/* #616: the tech has been texted about this day. Deliberately an icon in the
   brand colour rather than a chip background: a chip's fill is already spoken
   for by kind (assignment / equipment / emergency), and notified is orthogonal
   to all three — an emergency can be notified or not. Sized down so it reads as
   a status mark beside the job number, not a second subject. */
.board-chip-notified {
  color: var(--brand); font-size: 0.7rem; flex: 0 0 auto;
}

.board-chip-cont-before {
  border-top-left-radius: 0; border-bottom-left-radius: 0;
  border-left: 2px dotted currentColor;
}
.board-chip-cont-after {
  border-top-right-radius: 0; border-bottom-right-radius: 0;
  border-right: 2px dotted currentColor;
}

.board-empty-msg {
  padding: var(--space-lg); text-align: center; color: var(--text-tertiary);
  font-size: 0.85rem;
}

/* Unassigned side panel — jobs with no coverage in the visible range. */
.board-unassigned-list { display: flex; flex-direction: column; gap: 0.35rem; }
.board-unassigned-item {
  display: block; width: 100%; text-align: left; padding: 0.4rem 0.55rem;
  border: 1px solid var(--border-col); border-radius: var(--radius-sm);
  background: var(--bg-panel); color: var(--text-secondary);
  font-size: 0.78rem; cursor: grab; font-family: var(--font-sans);
}
.board-unassigned-item:hover { border-color: var(--brand); color: var(--text-primary); }
.board-unassigned-item.board-chip-dragging { opacity: 0.45; cursor: grabbing; }
.board-unassigned-num { display: block; font-weight: 700; }
.board-unassigned-cust {
  display: block; color: var(--text-tertiary); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
.board-unassigned-emergency { border-color: var(--status-red); }

/* Drift guard (#593): coverage running out, and equipment nobody brought back.
   Same shape as the unassigned list so the side rail reads as one column, but
   not draggable — these are things to go and fix, not things to drop on a lane. */
.board-drift-list { display: flex; flex-direction: column; gap: 0.35rem; }
.board-drift-item {
  display: block; width: 100%; padding: 0.4rem 0.55rem;
  border: 1px solid var(--border-col); border-radius: var(--radius-sm);
  background: var(--bg-panel); color: var(--text-secondary);
  font-size: 0.78rem; text-decoration: none;
}
a.board-drift-item:hover { border-color: var(--brand); color: var(--text-primary); }
.board-drift-num { display: block; font-weight: 700; }
.board-drift-when {
  display: block; color: var(--text-tertiary); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
/* Already lapsed, rather than about to — the case worth interrupting someone for. */
.board-drift-lapsed { border-left: 3px solid var(--status-red); }

/* Legend */
.board-legend {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-md);
  margin-top: var(--space-sm); font-size: 0.75rem; color: var(--text-tertiary);
}
.board-legend-item { display: inline-flex; align-items: center; gap: 0.35rem; }
.board-legend-swatch {
  width: 0.8rem; height: 0.8rem; border-radius: 2px; display: inline-block;
}

@media (max-width: 900px) {
  .board-layout { flex-direction: column; }
  .board-side { flex: 1 1 auto; width: 100%; }
}
