/* =================================================================
   Sparkz Electric - Operations · v2 (minimalist redesign)
   Color is meaning. Type and space carry the design.
   ================================================================= */

/* ---------- Fonts (self-hosted) ----------
   Inter and JetBrains Mono are served from static/fonts/ rather than the
   Google Fonts CDN, so the app never blocks first paint on a third-party
   origin and renders identically offline or behind a filtering proxy.

   Both are VARIABLE fonts: one file per subset covers every weight, which is
   why each family gets a single @font-face per subset declaring the full
   `font-weight` range instead of one block per weight.

   Only latin + latin-ext are shipped. The `unicode-range` on each block is
   Google's own, so latin-ext is fetched lazily -- only when a page actually
   contains an accented character -- and costs nothing on a typical page.

   Files are named with their upstream version and are immutable; to update a
   family, add the new version rather than overwriting. Both families are SIL
   OFL 1.1 -- see LICENSE-Inter.txt / LICENSE-JetBrainsMono.txt in
   static/fonts/, which the license requires us to ship alongside them.
   ---------------------------------------------------------------- */
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url('../fonts/inter-v20-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url('../fonts/inter-v20-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: 'JetBrains Mono';
    font-style: normal;
    font-weight: 100 800;
    font-display: swap;
    src: url('../fonts/jetbrains-mono-v24-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'JetBrains Mono';
    font-style: normal;
    font-weight: 100 800;
    font-display: swap;
    src: url('../fonts/jetbrains-mono-v24-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI',
                 Roboto, system-ui, sans-serif;
    font-size: 14px;
    line-height: 1.55;
    color: var(--ink);
    background: var(--bg);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
}
button { font: inherit; border: none; background: none; cursor: pointer; color: inherit; }
input, select, textarea { font: inherit; color: inherit; }
a { color: inherit; text-decoration: none; }
img { max-width: 100%; display: block; }
/* Media never overflows its container on any viewport (req 7: images resize,
   no crop/overflow). object-fit keeps aspect ratio when a box is constrained. */
video, iframe, canvas, svg { max-width: 100%; }
table { border-collapse: collapse; width: 100%; }

/* ---------- Responsive foundation ----------
   Global safety nets so nothing forces a horizontal scrollbar and long,
   unbreakable strings (emails, envelope ids, urls) wrap instead of pushing
   the layout wide. Scoped to the elements that actually carry long strings —
   a blanket `overflow-wrap: anywhere` on body would harm desktop typography. */
.mono, dd, td, th, .chip, .queue-row-title, .job-card-title,
.workcard .headline, .who-name, .peek-title, .modal-title,
.log-summary, .feed-text, .breadcrumb {
    overflow-wrap: anywhere;
    word-break: break-word;
}
/* Flex/grid text children need min-width:0 or they refuse to shrink and
   overflow their track. These are the ones the audit flagged as at-risk. */
.topbar-title, .live-roster-name, .live-roster-who, .who-name, .who-role,
.spread > *:first-child, .job-header-text { min-width: 0; }

/* Off-canvas nav scrim: inert at desktop, activated inside the <=920 block. */
.sidebar-scrim { display: none; }

/* Final production safety net: never allow an accidental horizontal scrollbar
   (req 2). `clip` — not `hidden` — avoids creating a scroll container, so
   position: sticky keeps working and the audit's scrollWidth check still
   catches real overflow regressions rather than silently absorbing them. */
html, body { overflow-x: clip; }

/* Kill the 300ms tap delay + the grey tap flash on touch devices. */
button, a, .tab, .nav-item, .radio-card, [role="button"], [data-peek] {
    -webkit-tap-highlight-color: transparent;
    touch-action: manipulation;
}
/* Tactile press feedback on touch devices (compositor-only transform, no
   layout shift). Disabled under reduced-motion. */
@media (hover: none) {
    .btn:active, .job-card:active,
    .queue-row.clickable:active, .who-row:active { transform: scale(0.985); }
}
@media (prefers-reduced-motion: reduce) {
    .btn:active, .job-card:active,
    .queue-row.clickable:active, .who-row:active { transform: none; }
}
/* Home-indicator clearance for the main scroll regions + bottom-pinned UI. */
@supports (padding: env(safe-area-inset-bottom)) {
    .crew-content { padding-bottom: calc(var(--space-10) + env(safe-area-inset-bottom)); }
    .toast-stack { bottom: calc(12px + env(safe-area-inset-bottom)); }
    .modal-footer { padding-bottom: calc(var(--space-4) + env(safe-area-inset-bottom)); }
}

/* Responsive utility helpers used by templates (mobile-first opt-ins). */
@media (max-width: 600px) {
    .stack-600 { flex-direction: column !important; align-items: stretch !important; }
    .full-600 { width: 100% !important; max-width: 100% !important; }
    .hide-600 { display: none !important; }
    .show-600 { display: revert; }
}
@media (min-width: 601px) { .show-600 { display: none; } }

/* ---------- Tokens ---------- */
:root {
    --bg:         #FFFFFF;
    --surface:    #FAFAF9;
    --surface-2:  #F4F4F2;
    --ink:        #0A0A0A;
    --ink-2:      #525252;
    --ink-3:      #A3A3A3;
    --line:       #E7E5E4;
    --line-2:     #F0EFEC;

    /* Aliases for names used further down the sheet that were never defined
       here. An undefined var() is invalid at computed-value time, so each of
       these silently dropped its declaration: `background: var(--bg-elev)` fell
       back to transparent (the Time tab's session headers lost their tint and
       the video card's background VANISHED on hover), and `color: var(--muted)`
       inherited full-strength ink instead of going grey. */
    --bg-elev:      var(--surface);
    --muted:        var(--ink-2);
    --surface-soft: var(--surface-2);

    --alert:      #B91C1C;
    --alert-soft: #FEF2F2;
    --ok:         #15803D;
    --ok-soft:    #F0FDF4;
    --warn:       #B45309;
    --info:       #1D4ED8;
    --purple:     #6D28D9;

    --radius-sm: 6px;
    --radius:    10px;
    --radius-lg: 14px;

    --shadow-pop:  0 12px 32px rgba(10, 10, 10, 0.08);
    --shadow-edge: 0 1px 0 rgba(10, 10, 10, 0.04);

    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 20px;
    --space-6: 24px;
    --space-8: 32px;
    --space-10: 40px;
    --space-12: 48px;
    --space-16: 64px;
    --space-20: 80px;

    --sidebar-w: 220px;
    --topbar-h:  56px;
    --canvas-max: 1080px;
    --reading-max: 720px;

    --ease: cubic-bezier(0.4, 0, 0.2, 1);
    --ring: 0 0 0 2px var(--bg), 0 0 0 4px var(--ink);
}

/* ---------- Global interaction polish ----------
   Keyboard-visible focus rings, tasteful text selection, and a global
   reduced-motion guard. These lift every page at once without changing
   any layout. Focus rings only show for keyboard users (:focus-visible),
   never on mouse click, so the calm look is preserved. */
::selection { background: rgba(10, 10, 10, 0.10); }

a:focus-visible,
.btn:focus-visible,
.nav-item:focus-visible,
.cmdk:focus-visible,
.demo-select:focus-visible,
.job-card:focus-visible,
.queue-row.clickable:focus-visible,
.sum-chip:focus-visible,
.radio-card:focus-visible,
/* .eod-video-open is deliberately NOT here: this ring is dark-on-white and the
   video poster is a black stage. It defines its own white ring instead. */
[data-peek]:focus-visible,
[tabindex]:focus-visible {
    outline: none;
    box-shadow: var(--ring);
    border-radius: var(--radius-sm);
}
/* Inputs keep their own soft ring; give keyboard users a touch more. */
.input:focus-visible,
.select:focus-visible,
.textarea:focus-visible {
    border-color: var(--ink);
    box-shadow: 0 0 0 3px rgba(10, 10, 10, 0.12);
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        scroll-behavior: auto !important;
    }
}

/* ---------- Typography ---------- */
h1, h2, h3, h4 { margin: 0; font-weight: 600; letter-spacing: -0.012em;
                 color: var(--ink); }
h1 { font-size: 32px; line-height: 1.15; letter-spacing: -0.02em; }
h2 { font-size: 20px; line-height: 1.25; }
h3 { font-size: 17px; line-height: 1.3; }
h4 { font-size: 13px; line-height: 1.3; }
p  { margin: 0; }

.eyebrow {
    font-size: 11px; font-weight: 600;
    letter-spacing: 0.08em; text-transform: uppercase;
    color: var(--ink-3);
}
.eyebrow.alert { color: var(--alert); }
.eyebrow.ok    { color: var(--ok); }

.muted { color: var(--ink-2); }
.dim   { color: var(--ink-3); }
.tiny  { font-size: 12px; }

/* "After this:" consequence line shown beneath a primary action so users see
   the downstream effect before they click. Used on workflow cards, Today
   queues, direct-action buttons, and inside confirmation modals. */
.after-this { font-size: 12px; color: var(--ink-3); margin-top: 8px;
              line-height: 1.5; }
.after-this strong { color: var(--ink-2); font-weight: 600; }
.mono  { font-family: 'JetBrains Mono', ui-monospace, 'SF Mono',
                       Menlo, Consolas, monospace; }
.numeric { font-variant-numeric: tabular-nums; }
.alert-text { color: var(--alert); }
.ok-text    { color: var(--ok); }

/* Hero numbers (contract value, paid to date) */
.numero {
    font-size: 32px; font-weight: 600;
    letter-spacing: -0.02em; line-height: 1.1;
    font-variant-numeric: tabular-nums;
}
.numero-lg { font-size: 44px; font-weight: 700; }

/* ---------- App shell ---------- */
.app {
    display: grid;
    grid-template-columns: var(--sidebar-w) 1fr;
    grid-template-rows: var(--topbar-h) 1fr;
    grid-template-areas:
        "sidebar topbar"
        "sidebar main";
    min-height: 100vh;
    background: var(--bg);
}

.sidebar {
    grid-area: sidebar;
    background: var(--surface);
    border-right: 1px solid var(--line);
    padding: var(--space-5) var(--space-4);
    display: flex; flex-direction: column;
    position: sticky; top: 0;
    height: 100vh;
    overflow-y: auto;
}

/* ---------- Brand ----------
   The logo is a wide horizontal lockup (3.05:1), so it stacks above the
   "Operations" label rather than sitting beside it. Both images are declared
   with their intrinsic width/height in the markup and sized here by width
   alone, so the browser reserves the right box before the PNG arrives.

   The sidebar gives 188px of content (220px less 16px padding either side);
   148px leaves the wordmark room to breathe against the nav below it. */
.brand {
    display: flex; flex-direction: column; align-items: flex-start; gap: 5px;
    padding: 4px 6px var(--space-6);
    color: var(--ink);
}
.brand-logo { width: 148px; height: auto; }
.brand.is-lg .brand-logo { width: 190px; }      /* login card */
.brand-sub  { font-size: 11px; color: var(--ink-3); }

.nav { display: flex; flex-direction: column; gap: 1px; flex: 1; }
.nav-item {
    display: flex; align-items: center; gap: 10px;
    padding: 8px 10px;
    border-radius: var(--radius-sm);
    color: var(--ink-2);
    font-size: 13.5px; font-weight: 500;
    transition: background 0.12s, color 0.12s;
}
.nav-item:hover { background: var(--surface-2); color: var(--ink); }
.nav-item.active {
    background: var(--ink); color: white;
}
.nav-item.active .nav-icon { opacity: 1; }
.nav-icon { width: 16px; height: 16px; opacity: 0.75; flex-shrink: 0; }
.nav-section {
    font-size: 10px; font-weight: 600;
    letter-spacing: 0.10em; text-transform: uppercase;
    color: var(--ink-3);
    padding: var(--space-5) 10px var(--space-2);
}

/* Demo role switcher (sidebar bottom) */
.demo-strip {
    margin-top: var(--space-4);
    padding-top: var(--space-4);
    border-top: 1px solid var(--line);
}
.demo-label {
    font-size: 10px; font-weight: 600; letter-spacing: 0.10em;
    color: var(--ink-3); text-transform: uppercase;
    margin-bottom: 6px;
    display: flex; align-items: center; justify-content: space-between;
}
.demo-label .dot {
    width: 6px; height: 6px; background: var(--ink); border-radius: 50%;
    margin-right: 6px;
}
.demo-select {
    width: 100%;
    background: white;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    padding: 7px 10px;
    font-size: 13px;
    color: var(--ink);
    cursor: pointer;
}
.demo-select:focus { outline: none; border-color: var(--ink); }

.who {
    display: flex; align-items: center; gap: 10px;
    padding-top: var(--space-3);
    color: var(--ink-2);
}
.who form { margin-left: auto; }
.who .avatar { width: 28px; height: 28px; font-size: 11px; }
.who-name { font-size: 13px; font-weight: 500; color: var(--ink); }
.who-role { font-size: 11px; color: var(--ink-3); text-transform: uppercase; letter-spacing: 0.06em; }

.sign-out {
    color: var(--ink-3); padding: 4px 6px;
    font-size: 11px; letter-spacing: 0.06em;
    text-transform: uppercase;
}
.sign-out:hover { color: var(--ink); }

/* Topbar */
.topbar {
    grid-area: topbar;
    border-bottom: 1px solid var(--line);
    background: var(--bg);
    display: flex; align-items: center;
    padding: 0 var(--space-8);
    gap: var(--space-4);
    position: sticky; top: 0; z-index: 30;
}
.topbar-title {
    font-size: 14px; font-weight: 600;
    color: var(--ink);
    letter-spacing: -0.005em;
}
.topbar-spacer { flex: 1; }
.cmdk {
    display: flex; align-items: center; gap: 10px;
    padding: 6px 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    color: var(--ink-2);
    font-size: 12.5px;
    background: white;
    transition: border-color 0.12s, color 0.12s;
}
.cmdk:hover { border-color: var(--ink-3); color: var(--ink); }
.cmdk kbd {
    display: inline-flex; align-items: center; justify-content: center;
    padding: 2px 6px;
    border: 1px solid var(--line);
    border-radius: 4px;
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-size: 11px;
    color: var(--ink-2);
    background: var(--surface);
}

/* Main canvas */
.main {
    grid-area: main;
    padding: var(--space-12) var(--space-8) var(--space-16);
    overflow-y: auto;
}
.canvas {
    max-width: var(--canvas-max);
    margin: 0 auto;
}
.canvas-narrow { max-width: var(--reading-max); margin: 0 auto; }

.page-header {
    margin-bottom: var(--space-10);
}
.page-eyebrow {
    font-size: 11px; font-weight: 600;
    letter-spacing: 0.10em; text-transform: uppercase;
    color: var(--ink-3);
    margin-bottom: 6px;
}
.page-title {
    font-size: 32px; font-weight: 700;
    letter-spacing: -0.022em; line-height: 1.15;
    color: var(--ink);
}
.page-sub {
    color: var(--ink-2);
    margin-top: 8px;
    max-width: 560px;
}

/* ---------- Crew shell (mobile-first, no sidebar) ---------- */
.crew-shell {
    min-height: 100vh;
    background: var(--bg);
    display: flex; flex-direction: column;
}
.crew-bar {
    display: flex; align-items: center; gap: 10px;
    padding: var(--space-4) var(--space-5);
    border-bottom: 1px solid var(--line);
    background: var(--bg);
    position: sticky; top: 0; z-index: 20;
}
/* SPARKZ-only wordmark by default; the phone breakpoint swaps in the square
   bolt (the bar also holds search, EN/ES and Sign out, and 101px of wordmark is
   too much of a narrow bar to spend on branding). Only one is ever displayed. */
.crew-bar .brand-logo { width: auto; height: 22px; flex: none; }
.crew-bar .brand-icon { display: none; width: 24px; height: 24px; flex: none; }
.crew-bar form { margin-left: auto; }

/* ---------- Language toggle (crew bar only) ----------
   A two-state segmented control rather than a dropdown: there are exactly two
   languages, and one tap beats open-scan-pick with a phone in one hand. It
   takes over the right-alignment from the sign-out form so the pair sits
   together at the end of the bar. Each option is labelled in its OWN language
   (EN / ES) — someone hunting for Spanish scans for "ES". */
.crew-bar .lang-toggle { margin-left: auto; }
.crew-bar .lang-toggle ~ form { margin-left: var(--space-2); }
.lang-toggle {
    display: inline-flex;
    padding: 2px;
    gap: 2px;
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
}
.lang-opt {
    min-width: 34px;
    /* 32px + the bar's own padding clears a 44px tap target without making
       the sticky header taller. */
    min-height: 32px;
    padding: 0 var(--space-2);
    border: 0;
    border-radius: 4px;
    background: transparent;
    color: var(--ink-3);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.06em;
    cursor: pointer;
    transition: background 120ms var(--ease), color 120ms var(--ease);
}
.lang-opt:hover:not(.is-active) { color: var(--ink); }
.lang-opt.is-active {
    background: var(--bg);
    color: var(--ink);
    box-shadow: var(--shadow-edge);
}
.lang-opt[disabled] { opacity: 0.5; cursor: default; }
.crew-content {
    flex: 1;
    padding: var(--space-6) var(--space-5) var(--space-12);
    max-width: 560px;
    width: 100%;
    margin: 0 auto;
}

/* ---------- Avatar ---------- */
.avatar {
    width: 32px; height: 32px;
    border-radius: 50%;
    background: var(--surface-2);
    color: var(--ink);
    display: grid; place-items: center;
    font-size: 12px; font-weight: 600;
    letter-spacing: -0.01em;
    flex-shrink: 0;
    border: 1px solid var(--line);
}
.avatar.sm { width: 24px; height: 24px; font-size: 10px; }
.avatar.lg { width: 44px; height: 44px; font-size: 15px; }

/* ---------- Buttons ---------- */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    height: 36px; padding: 0 14px;
    border-radius: var(--radius-sm);
    font-size: 13.5px; font-weight: 500;
    border: 1px solid transparent;
    transition: background 0.12s, color 0.12s, border-color 0.12s, opacity 0.12s;
    white-space: nowrap;
    line-height: 1;
}
.btn:active:not(:disabled):not(.is-busy) {
    transform: scale(0.97);
    transition: transform 0.06s ease-out;
}
/* A <label class="btn"> that fronts a file input: clicking the label opens the
   chooser natively, so the dialog it sits in STAYS OPEN (a real button would
   resolve the modal and the dialog would vanish before the chooser appeared).
   Labels don't get the pointer cursor buttons do. */
label.btn { cursor: pointer; }

/* Hide an input while keeping it focusable and, crucially, still activatable by
   its <label>. NOT display:none — Safari can ignore label activation for a
   display:none file input, and the crew is on iPhones. */
.visually-hidden {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0; border: 0;
    clip: rect(0 0 0 0); clip-path: inset(50%);
    overflow: hidden; white-space: nowrap;
}
.btn-primary {
    background: var(--ink); color: white;
}
.btn-primary:hover {
    background: #2a2a2a;
    box-shadow: 0 1px 2px rgba(10, 10, 10, 0.12),
                0 2px 6px rgba(10, 10, 10, 0.08);
}
.btn-secondary {
    background: white; color: var(--ink);
    border-color: var(--line);
}
.btn-secondary:hover { border-color: var(--ink-3); background: var(--surface-2); }
.btn-ghost { color: var(--ink-2); }
.btn-ghost:hover { color: var(--ink); background: var(--surface-2); }
.btn-danger { background: var(--alert); color: white; }
.btn-danger:hover { background: #991B1B; }
.btn-ok { background: var(--ok); color: white; }
.btn-ok:hover { background: #166534; }
.btn-sm { height: 30px; padding: 0 10px; font-size: 12.5px; gap: 4px; }
.btn-lg { height: 48px; padding: 0 22px; font-size: 15px; border-radius: var(--radius); }
.btn-block { width: 100%; }
.btn:disabled, .btn[aria-disabled="true"] {
    opacity: 0.4; cursor: not-allowed; pointer-events: none;
}
/* In-flight state: text fades, spinner overlays, button is locked. The
 * caller adds .is-busy via UI.busy() while an async action is pending,
 * and the page reload (or the helper's `finally`) clears it. */
.btn.is-busy {
    color: transparent !important;
    pointer-events: none;
    position: relative;
    opacity: 0.85;
}
.btn.is-busy::after {
    content: '';
    position: absolute;
    top: 50%; left: 50%;
    width: 14px; height: 14px;
    margin: -7px 0 0 -7px;
    border: 2px solid rgba(255, 255, 255, 0.35);
    border-top-color: #fff;
    border-radius: 50%;
    animation: btn-spin 0.6s linear infinite;
}
/* Light-background buttons need a dark spinner so it's visible. */
.btn-secondary.is-busy::after,
.btn-ghost.is-busy::after {
    border-color: rgba(10, 10, 10, 0.15);
    border-top-color: var(--ink);
}
@keyframes btn-spin {
    to { transform: rotate(360deg); }
}
/* Required-field highlight when a user tries to submit an incomplete modal. */
.field-missing > .input,
.field-missing > .textarea,
.field-missing > .select,
.field-missing > input,
.field-missing > textarea,
.field-missing > select,
.field.field-missing > .input,
.field.field-missing > .textarea,
input.field-missing, textarea.field-missing, select.field-missing {
    outline: 2px solid #e54d4d;
    outline-offset: 2px;
    border-radius: 4px;
}
.btn-arrow::after { content: "→"; font-size: 14px; }
.btn-group { display: inline-flex; gap: 8px; flex-wrap: wrap; }

/* ---------- Forms ---------- */
.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: var(--space-4); }
.label {
    font-size: 11px; font-weight: 600;
    letter-spacing: 0.06em; text-transform: uppercase;
    color: var(--ink-2);
}
.label .req { color: var(--alert); margin-left: 2px; }
.input, .select, .textarea {
    width: 100%;
    padding: 9px 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: white;
    color: var(--ink);
    transition: border-color 0.12s, box-shadow 0.12s;
}
.input:focus, .select:focus, .textarea:focus {
    outline: none;
    border-color: var(--ink);
    box-shadow: 0 0 0 3px rgba(10, 10, 10, 0.06);
}
.textarea { resize: vertical; min-height: 84px; font-family: inherit; }
.input::placeholder, .textarea::placeholder { color: var(--ink-3); }
.input:disabled, .select:disabled, .textarea:disabled {
    background: var(--surface-2);
    color: var(--ink-3);
    cursor: not-allowed;
}
.help { font-size: 12px; color: var(--ink-3); }
/* UI.unitField() — the unit <select> with its "Custom…" text box stacked under
   it (hidden until Custom is picked). Behaves as a single cell wherever a bare
   unit <input> used to sit: a grid cell in .ml-row.ml-edit / .eml-row /
   .field-row, a flex child in .coe-field. */
.unit-field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
/* UI.upgradeMoney() — the visible "$1,234.56" box with its hidden raw-number
   twin and a cap hint underneath. Same deal as .unit-field: it stands in for a
   bare <input> wherever one used to sit (a .field, a .field-row cell, a
   .coe-field flex child, the right-aligned tax box), so it has to fill its
   slot rather than shrink-wrap the input. */
.money-field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.money-field > .input { width: 100%; }
.money-field > .input.is-capped {
    border-color: var(--warn);
    box-shadow: 0 0 0 3px rgba(180, 120, 0, 0.10);
}
.money-hint { font-size: 12px; color: var(--warn); font-weight: 600; }
/* .qbo-links moved to the Supplier invoices block below, with the rest of the
   recorded-order CSS. */
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); }
.field-row.three { grid-template-columns: 1fr 1fr 1fr; }
@media (max-width: 600px) {
    .field-row, .field-row.three { grid-template-columns: 1fr; }
}

/* Radio cards (intake job-type selector) */
.radio-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-3); }
@media (max-width: 720px) { .radio-grid { grid-template-columns: 1fr; } }
.radio-card {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--space-4);
    cursor: pointer;
    transition: border-color 0.12s, background 0.12s;
}
.radio-card:hover { border-color: var(--ink-3); }
.radio-card.selected {
    border-color: var(--ink);
    background: var(--ink);
    color: white;
}
.radio-card.selected .radio-card-desc { color: rgba(255,255,255,0.7); }
.radio-card-title { font-weight: 600; font-size: 14px; }
.radio-card-desc { font-size: 12px; color: var(--ink-2); margin-top: 4px; line-height: 1.45; }

/* ---------- Workflow Continuation Card ---------- */
/* ---------- Milestone progress bar (job header) ----------
   Horizontal stepper that makes the active phase unmistakable. Nodes are
   connected by a rule; the connector to the LEFT of a done/active node is
   filled (progress), so completed milestones read as a continuous track.
   Uses only design tokens, so auto dark mode carries through. */
.milestone-bar {
    display: flex;
    align-items: flex-start;
    margin: var(--space-4) 0 var(--space-5);
    overflow-x: auto;
}
.milestone-bar .ms-node {
    flex: 1 1 0;
    min-width: 72px;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 6px;
    position: relative;
}
/* Connector line between nodes (drawn to the left of each node but the first). */
.milestone-bar .ms-node + .ms-node::before {
    content: "";
    position: absolute;
    top: 10px;              /* vertical center of the 21px dot */
    right: 50%;
    left: -50%;
    height: 2px;
    background: var(--line);
    z-index: 0;
}
/* A done or active node's incoming connector is filled to show progress. */
.milestone-bar .ms-node.is-done + .ms-node::before,
.milestone-bar .ms-node.is-active + .ms-node::before { background: var(--ok); }

.milestone-bar .ms-dot {
    position: relative;
    z-index: 1;
    width: 21px; height: 21px;
    border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 12px; font-weight: 700; line-height: 1;
    border: 2px solid var(--line);
    background: var(--bg);
    color: #fff;
}
.milestone-bar .ms-label {
    font-size: 12px;
    font-weight: 500;
    color: var(--ink-3);
    line-height: 1.25;
}
.milestone-bar .ms-active-tag {
    display: block;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--info);
}
/* Done: solid green with a check. */
.milestone-bar .ms-node.is-done .ms-dot {
    background: var(--ok);
    border-color: var(--ok);
}
.milestone-bar .ms-node.is-done .ms-label { color: var(--ink-2); }
/* Active: ringed, emphasized label. */
.milestone-bar .ms-node.is-active .ms-dot {
    background: var(--info);
    border-color: var(--info);
    box-shadow: 0 0 0 3px var(--surface-2);
}
.milestone-bar .ms-node.is-active .ms-label { color: var(--ink); font-weight: 600; }
/* Upcoming: muted, hollow. */
.milestone-bar .ms-node.is-upcoming .ms-dot { background: var(--bg); }

.workcard {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--space-8);
    background: white;
    animation: fadeup 0.18s ease-out;
}
@keyframes fadeup {
    from { opacity: 0; transform: translateY(2px); }
    to   { opacity: 1; transform: translateY(0); }
}
.workcard .eyebrow { margin-bottom: 10px; }
.workcard .headline {
    font-size: 22px; font-weight: 600;
    line-height: 1.3; letter-spacing: -0.01em;
    color: var(--ink);
}
.workcard .detail {
    color: var(--ink-2);
    margin-top: 8px;
    line-height: 1.5;
}
.workcard .actions {
    margin-top: var(--space-5);
    display: flex; gap: 8px; flex-wrap: wrap;
}
.workcard.tone-blocked { background: var(--surface); }
.workcard.tone-done {
    background: var(--ok-soft);
    border-color: rgba(21, 128, 61, 0.20);
}
.workcard.tone-done .eyebrow { color: var(--ok); }
.workcard.tone-alert {
    background: var(--alert-soft);
    border-color: rgba(185, 28, 28, 0.20);
}
.workcard.tone-alert .eyebrow { color: var(--alert); }

/* Signature follow-up: a compact facts card (Sent to / Sent / Last viewed)
   stacked below the workflow card while a job waits on the customer's e-sign. */
.sig-followup {
    margin-top: var(--space-3);
    padding: var(--space-5);
    background: var(--surface);
}
.sig-followup .eyebrow { margin-bottom: var(--space-3); }
.sig-facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: var(--space-3) var(--space-6);
    margin: 0;
}
.sig-fact dt {
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em;
    color: var(--ink-3); margin-bottom: 2px;
}
.sig-fact dd { margin: 0; color: var(--ink); font-size: 14px; }
.sig-followup .actions { margin-top: var(--space-5); }

/* Change-order follow-up card (Estimates & COs tab) — shown while a CO is out for
   the customer's e-signature. A warn-accented panel so it reads as "waiting on
   them", with the amount, who/when it was sent, whether they've opened it, and
   the material work it's holding. */
.co-sent-card {
    margin: var(--space-4) 0;
    padding: var(--space-4) var(--space-5);
    background: var(--surface);
    border: 1px solid var(--line);
    border-left: 3px solid var(--warn);
    border-radius: var(--radius);
}
.co-sent-head { align-items: flex-start; gap: var(--space-4); }
.co-sent-head .status { margin-left: var(--space-2); }
.co-sent-amount { font-size: 15px; font-weight: 600; white-space: nowrap; }
.co-sent-meta { margin-top: var(--space-2); }
.co-sent-meta .status { font-size: inherit; }
.co-blocked { margin-top: var(--space-3); }
.co-blocked > summary {
    cursor: pointer;
    color: var(--warn);
    font-size: 12.5px;
}
.co-blocked-list {
    list-style: none;
    margin: var(--space-2) 0 0;
    padding: var(--space-2) 0 0;
    border-top: 1px solid var(--line);
}
.co-blocked-list li {
    display: flex;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-1) 0;
}
.co-sent-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-top: var(--space-4);
}

/* Secondary action card stacked directly below the main workflow card —
   visually subordinate (lighter, smaller, accent-flagged) so it reads as a
   "also do this" task rather than competing with the primary next action. */
.workcard-secondary {
    margin-top: var(--space-3);
    padding: var(--space-5);
    background: var(--surface);
    border-left: 3px solid var(--warn);
}
.workcard-secondary .eyebrow { margin-bottom: 6px; }
.workcard-secondary .headline { font-size: 16px; }
.workcard-secondary .detail { font-size: 13px; margin-top: 4px; }
.workcard-secondary .actions { margin-top: var(--space-3); }

/* Held-on-materials explicit-start card: same compact shape as the secondary
   card but flagged with the alert accent — the crew is scheduled but required
   materials aren't ready, so the SM/office must make an explicit call. */
.workcard-alert {
    margin-top: var(--space-3);
    padding: var(--space-5);
    background: var(--alert-soft);
    border-left: 3px solid var(--alert);
    border-radius: var(--radius-sm);
}
.workcard-alert .eyebrow { margin-bottom: 6px; color: var(--alert); }
.workcard-alert .headline { font-size: 16px; }
.workcard-alert .detail { font-size: 13px; margin-top: 4px; color: var(--ink-2); }
.workcard-alert .actions { margin-top: var(--space-3); }

/* Compact workflow card preview (used in queues) */
.workcard-mini {
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    padding: var(--space-4);
    background: white;
}
.workcard-mini .eyebrow { margin-bottom: 4px; }
.workcard-mini .headline {
    font-size: 15px; font-weight: 600;
    color: var(--ink);
}
.workcard-mini .detail { color: var(--ink-2); font-size: 12.5px; margin-top: 2px; }

/* ---------- Lists / queues (the heart of v2) ---------- */
.queue {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}
.queue-row {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-4);
    background: white;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    transition: border-color 0.10s, box-shadow 0.10s;
}
.queue-row:hover {
    border-color: var(--ink-3);
    box-shadow: var(--shadow-edge);
}
.queue-row.clickable { cursor: pointer; }
.queue-row-main { min-width: 0; }
.queue-row-title {
    font-weight: 500; color: var(--ink);
    font-size: 14px;
}
.queue-row-meta {
    color: var(--ink-2);
    font-size: 12.5px;
    margin-top: 2px;
    display: flex; align-items: center; gap: 8px;
    flex-wrap: wrap;
}
.queue-row-meta .sep { color: var(--ink-3); }
.queue-row-actions { display: flex; gap: 6px; }

/* ---------- Today job cards: one card per job, tasks listed inside ----------
   The Today pages used to emit one .queue-row per THING TO DO, so a job with a
   punch review, two material requests and an order to call in rendered as four
   sibling rows and the page read like four jobs. `.has-tasks` keeps every
   .queue-row behaviour (hover, focus ring, list-in stagger, the [hidden]
   override the stage filter needs) and only drops the 1fr/auto grid, so the
   header sits above a full-width task list instead of beside one action. */
.queue-row.has-tasks { display: block; }
.jc-head {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: var(--space-4);
}
.jc-foot { display: flex; justify-content: flex-end; margin-top: var(--space-3); }
.task-list { margin-top: var(--space-3); border-top: 1px solid var(--line); }
.task-row {
    display: flex; align-items: center; gap: var(--space-3);
    padding: 10px 0;
    border-bottom: 1px solid var(--line-2);
}
.task-row:last-child { border-bottom: 0; padding-bottom: 0; }
.task-main { flex: 1; min-width: 0; }
.task-line { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.task-title { font-size: 13.5px; font-weight: 500; color: var(--ink);
              min-width: 0; }
.task-dot { gap: 0; flex-shrink: 0; }
/* A job with nothing outstanding still renders a card — just an empty list, so
   the divider and its spacing would sit over nothing. :has() rather than :empty
   because Jinja leaves whitespace text nodes inside the element. */
.task-list:not(:has(.task-row)) { margin-top: 0; border-top: 0; }
.task-detail { font-size: 12px; color: var(--ink-3); margin-top: 2px; }
.task-actions { display: flex; gap: 6px; flex-shrink: 0; align-items: center; }
/* Same trap as .queue-row above: these set `display`, which outranks the UA
   stylesheet's [hidden] rule. */
.task-row[hidden] { display: none !important; }

.section-head {
    display: flex; align-items: baseline; justify-content: space-between;
    margin: var(--space-10) 0 var(--space-3);
}
.section-head:first-child { margin-top: var(--space-2); }
.section-title {
    font-size: 13px; font-weight: 600;
    letter-spacing: 0.04em; text-transform: uppercase;
    color: var(--ink);
}
.section-count {
    font-size: 12px; color: var(--ink-3);
    font-variant-numeric: tabular-nums;
}

/* ---------- Site-manager Today: stage filter ----------
   The Summary chips are buttons that filter the page to one stage. .sum-chip
   resets the button back to the plain <span> these used to be, so the bar looks
   unchanged until you hover one. */
.sum-chip {
    background: none; border: 0; padding: 2px 6px; margin: -2px 0;
    font: inherit; color: inherit; line-height: inherit;
    border-radius: var(--radius-sm); cursor: pointer;
    transition: background 0.10s, color 0.10s;
}
.sum-chip:hover { background: var(--line-2); }
.sum-chip[aria-pressed="true"] { background: var(--ink); color: white; }
.sum-chip[aria-pressed="true"] .dim { color: rgba(255, 255, 255, 0.72); }

.stage-filter-bar {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-3);
    margin: calc(-1 * var(--space-2)) 0 var(--space-3);
    padding: 8px 12px;
    background: var(--surface); border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    font-size: 13px; color: var(--ink-2);
}

/* Same trap as .upload-lock below: these all set `display`, which outranks the
   UA stylesheet's `[hidden] { display: none }`, so every `el.hidden = true` in
   the filter would be ignored without this. */
.queue-row[hidden], .queue[hidden], .svc[hidden],
.section-head[hidden], .stage-filter-bar[hidden] { display: none !important; }

/* ---------- Today screens (per-role landing) ---------- */
.today-hero {
    margin-bottom: var(--space-12);
}
.today-hero .number-line {
    font-size: 44px; font-weight: 700;
    letter-spacing: -0.025em; line-height: 1.1;
    color: var(--ink);
}
.today-hero .number-line .alert-text { color: var(--alert); }
.today-hero .number-line .ok-text { color: var(--ok); }
.today-hero .sub {
    font-size: 15px;
    color: var(--ink-2);
    margin-top: 8px;
}

/* Inline pulse strip (Owner today) */
.pulse-strip {
    display: flex; gap: var(--space-8); flex-wrap: wrap;
    padding: var(--space-5) 0;
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
    margin-bottom: var(--space-10);
}
.pulse-stat {
    display: flex; flex-direction: column; gap: 4px;
}
.pulse-label {
    font-size: 11px; font-weight: 600;
    letter-spacing: 0.08em; text-transform: uppercase;
    color: var(--ink-3);
}
.pulse-value {
    font-size: 24px; font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}

/* Crew-shell hero (Crew Leader / Member) */
.crew-hero {
    padding: var(--space-6) 0 var(--space-8);
    border-bottom: 1px solid var(--line);
    margin-bottom: var(--space-6);
}
.crew-hero .eyebrow { margin-bottom: 10px; }
.crew-hero .address {
    font-size: 28px; font-weight: 700;
    letter-spacing: -0.02em; line-height: 1.15;
    color: var(--ink);
}
.crew-hero .meta {
    margin-top: 8px;
    color: var(--ink-2);
    font-size: 14px;
}
.crew-hero .meta a { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }

.crew-action {
    margin-top: var(--space-6);
}
.crew-action .btn { width: 100%; height: 56px; font-size: 16px; }

/* Big punch checklist (crew) */
.checklist {
    list-style: none; padding: 0; margin: 0;
    display: flex; flex-direction: column;
}
.checklist li {
    display: flex; align-items: center; gap: 12px;
    padding: 14px 0;
    border-top: 1px solid var(--line);
}
.checklist li:last-child { border-bottom: 1px solid var(--line); }
.check-box {
    width: 22px; height: 22px;
    border: 1.5px solid var(--ink-3);
    border-radius: 5px;
    display: grid; place-items: center;
    flex-shrink: 0;
    background: white;
}
.check-box.done {
    background: var(--ok); border-color: var(--ok); color: white;
}
.check-label {
    flex: 1;
    color: var(--ink); font-size: 14.5px;
    font-weight: 500;
}
.check-meta { color: var(--ink-3); font-size: 12px; }
.checklist li.done .check-label { color: var(--ink-3); text-decoration: line-through; }

/* ---------- Punch list cards (job page) ---------- */
.punch-grid {
    display: flex; flex-direction: column;
    gap: var(--space-3);
}
.punch-card {
    display: grid;
    grid-template-columns: 96px 1fr;
    gap: var(--space-4);
    padding: var(--space-3);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    transition: border-color 0.12s, box-shadow 0.12s;
}
.punch-card:hover { border-color: var(--ink-3); }
.punch-card.is-closed { opacity: 0.6; }
.punch-card-photo {
    width: 96px; height: 96px;
    border-radius: var(--radius-sm);
    overflow: hidden;
    background: var(--surface-2);
    display: grid; place-items: center;
    color: var(--ink-3);
    flex-shrink: 0;
    padding: 0;  /* reset for <button> variant */
}
button.punch-card-photo:hover > img { transform: scale(1.05); }
.punch-card-photo > img { transition: transform 0.2s ease; width: 100%; height: 100%; object-fit: cover; }
button.punch-card-photo { position: relative; }
/* Closed cards: small "Before" thumb tucked into the bottom-right
   corner of the After photo, so both photos are discoverable at a
   glance and the lightbox is one tap away. */
.punch-card-photo-overlay {
    position: absolute;
    bottom: 4px;
    right: 4px;
    width: 32px;
    height: 32px;
    border-radius: 4px;
    overflow: hidden;
    border: 1.5px solid #fff;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
    background: #000;
}
.punch-card-photo-overlay img {
    width: 100%; height: 100%;
    object-fit: cover;
    transition: none;
}
.punch-card-photo-overlay-label {
    position: absolute;
    bottom: 0; left: 0; right: 0;
    background: rgba(0, 0, 0, 0.65);
    color: #fff;
    font-size: 7px;
    font-weight: 700;
    text-align: center;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    line-height: 1.4;
}
@media (max-width: 480px) {
    .punch-card-photo-overlay { width: 28px; height: 28px; }
}
.punch-card-photo img {
    width: 100%; height: 100%; object-fit: cover;
}
.punch-card-photo-empty { display: grid; place-items: center; }
.punch-card-body {
    display: flex; flex-direction: column;
    gap: 6px;
    min-width: 0;  /* lets long titles ellipsize inside grid */
}
.punch-card-head {
    display: flex; align-items: flex-start;
    gap: var(--space-3);
}
.punch-card-title {
    flex: 1;
    font-size: 15px; font-weight: 600;
    color: var(--ink);
    line-height: 1.3;
    word-break: break-word;
}
.is-closed .punch-card-title {
    text-decoration: line-through;
    text-decoration-color: var(--ink-3);
}
.punch-card-meta {
    display: flex; align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}
.punch-tag {
    display: inline-block;
    padding: 2px 8px;
    background: #FEF3C7;
    color: #92400E;
    font-size: 10px; font-weight: 700;
    letter-spacing: 0.08em;
    border-radius: 4px;
}
.punch-loc {
    display: inline-flex; align-items: center;
    gap: 4px;
    color: var(--ink-3);
    font-size: 12.5px;
}
.punch-loc svg { flex-shrink: 0; }
.punch-card-desc {
    color: var(--ink-2);
    font-size: 13px;
    line-height: 1.4;
    word-break: break-word;
    /* Cap at two lines so a chatty entry doesn't blow the card height up.
       The user can still see the full text by tapping into the item. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.punch-card-foot {
    color: var(--ink-3);
    font-size: 12px;
    margin-top: auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
}
.punch-card-foot-date { flex: 1; min-width: 0; }
.punch-card-action {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
    white-space: nowrap;
}
.punch-card-action svg { flex-shrink: 0; }
.punch-status { cursor: default; }
/* Newly-inserted card flashes briefly so the user sees where it landed. */
.punch-card.is-fresh {
    animation: punch-flash 0.9s ease-out;
}
@keyframes punch-flash {
    0%   { background: #FEF3C7; }
    100% { background: var(--surface); }
}
/* A table row changed by the last action flashes so the user can spot it
   after a reload (invoices, schedule). Animates the cells and ends
   transparent so zebra/hover/tbl-stack styling isn't clobbered. */
tr.is-fresh > td { animation: row-flash 1.2s ease-out; }
@keyframes row-flash {
    0%   { background: #FEF3C7; }
    100% { background: transparent; }
}
.punch-status {
    flex-shrink: 0;
    padding: 3px 10px;
    font-size: 11px; font-weight: 600;
    letter-spacing: 0.04em;
    border-radius: 999px;
    border: 1px solid transparent;
    background: var(--surface-2);
    color: var(--ink-2);
    line-height: 1.4;
}
.punch-status.is-open {
    background: var(--alert-soft);
    color: var(--alert);
}
.punch-status.is-pending {
    background: #FEF3C7;
    color: #92400E;
}
.punch-status.is-closed {
    background: var(--ok-soft);
    color: var(--ok);
}
.punch-card.is-pending {
    border-color: #FCD34D;
    background: #FFFBEB;
}
.punch-card-hint {
    font-size: 12px;
    color: var(--ink-3);
    font-style: italic;
}
.punch-card-hint.is-rejected {
    color: var(--alert);
    font-style: normal;
}
.punch-card-review {
    display: inline-flex;
    gap: var(--space-2);
    flex-shrink: 0;
}
.punch-video-link {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: var(--ink-2);
    font-size: 12.5px;
    text-decoration: underline;
    text-decoration-color: var(--ink-3);
    text-underline-offset: 2px;
}
.punch-video-link:hover { color: var(--ink); }
@media (max-width: 480px) {
    .punch-card { grid-template-columns: 72px 1fr; gap: var(--space-3); }
    .punch-card-photo { width: 72px; height: 72px; }
    .punch-card-title { font-size: 14px; }
}

/* ---------- Punch sections: Open / Submitted for review / Closed ---------- */
.punch-sections { display: flex; flex-direction: column; gap: var(--space-5); }
.punch-group[hidden] { display: none; }
.punch-group-head {
    display: flex; align-items: center; gap: var(--space-2);
    margin-bottom: var(--space-3);
}
.punch-group-title {
    font-size: 12px; font-weight: 600;
    text-transform: uppercase; letter-spacing: 0.06em;
    color: var(--ink-3);
}
.punch-group-count {
    display: inline-grid; place-items: center;
    min-width: 20px; height: 20px; padding: 0 6px;
    border-radius: 10px;
    background: var(--surface-2); color: var(--ink-2);
    font-size: 11px; font-weight: 600;
}
/* Closed is a <details> so it collapses with no JS. Style the summary as the
   group head and drop the default disclosure marker for a custom caret. */
.punch-group--closed > summary { cursor: pointer; list-style: none; }
.punch-group--closed > summary::-webkit-details-marker { display: none; }
.punch-group--closed > summary::before {
    content: "\25B8"; color: var(--ink-3); font-size: 10px; margin-right: 2px;
}
.punch-group--closed[open] > summary::before { content: "\25BE"; }

/* Wider screens: the Closed section is always expanded and not collapsible —
   the collapse is a phone affordance only. */
@media (min-width: 481px) {
    .punch-group--closed > .punch-grid { display: flex !important; }
    .punch-group--closed > summary { cursor: default; pointer-events: none; }
    .punch-group--closed > summary::before { display: none; }
}

/* Phones: Closed cards collapse to compact single rows (title + status + date,
   no photo/meta) so a long history of finished items stays scannable. */
@media (max-width: 480px) {
    .punch-group--closed .punch-card {
        grid-template-columns: 1fr;
        gap: var(--space-1);
        padding: var(--space-2) var(--space-3);
    }
    .punch-group--closed .punch-card-photo,
    .punch-group--closed .punch-card-meta,
    .punch-group--closed .punch-card-desc,
    .punch-group--closed .punch-card-hint { display: none; }
}

/* ---------- Field connectivity / upload-queue banner ---------- */
.field-banner {
    position: fixed;
    top: 0; left: 0; right: 0;
    z-index: 1000;
    padding: 8px 16px;
    text-align: center;
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1.3;
    color: #fff;
    box-shadow: 0 1px 6px rgba(0, 0, 0, 0.25);
}
.field-banner.is-offline { background: #B45309; }   /* amber-700 */
.field-banner.is-warn    { background: #B91C1C; }   /* red-700 */
.field-banner.is-sending { background: #1D4ED8; }   /* blue-700 */
.field-banner.is-tappable { cursor: pointer; text-decoration: underline; }

/* Drop-down under the banner listing permanently-failed uploads. These never
   retry on their own, so Retry / Discard here is the only way to clear them. */
.field-banner-panel {
    position: fixed;
    top: 34px; left: 0; right: 0;
    z-index: 1000;
    background: var(--surface);
    border-bottom: 1px solid var(--line);
    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.12);
    padding: 6px 12px;
}
.field-banner-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 5px 0;
}
.field-banner-row + .field-banner-row { border-top: 1px solid var(--line); }
.field-banner-label {
    flex: 1;
    min-width: 0;
    font-size: 12.5px;
    color: var(--ink-2);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Make room for the fixed banner: pad the whole page down, and keep the sticky
   bars parked just below it (preserving their position:sticky). */
body.has-field-banner { padding-top: 34px; }
body.has-field-banner .topbar,
body.has-field-banner .crew-bar { top: 34px; }

/* ---------- End-of-day upload lock ----------
   Holds the crew app shut while the day's video is still on the phone. Above
   the field banner (1000) so nothing shows through: the banner would only
   repeat what this screen already says, in less detail. */
.upload-lock {
    position: fixed;
    inset: 0;
    z-index: 1100;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: rgba(10, 10, 10, 0.82);
    overscroll-behavior: contain;
}
/* The `display: flex` above is an AUTHOR declaration and beats the UA
   stylesheet's `[hidden] { display: none }`, so every `el.hidden = true` in
   upload-lock.js was a no-op: this fixed, inset-0, z-index 1100 scrim sat over
   the whole crew app on EVERY page load, dimming it and swallowing every tap —
   whether or not a video was ever queued. It read as "the app is dead", and it
   survived a reload because ensureRoot() rebuilds the element each time.
   Same trap, same fix as .ml-badge, .coe-shell and the plan-markup toggles. */
.upload-lock[hidden] { display: none !important; }
.upload-lock-card {
    width: 100%;
    max-width: 420px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: 22px 20px;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.3);
    text-align: center;
}
.upload-lock-title {
    margin: 0 0 10px;
    font-size: 17px;
    font-weight: 700;
    color: var(--ink);
}
.upload-lock-status {
    margin: 0 0 10px;
    font-size: 14px;
    line-height: 1.45;
    color: var(--ink-2);
}
.upload-lock-note {
    margin: 14px 0 0;
    font-size: 12.5px;
    line-height: 1.4;
    color: var(--ink-2);
}
/* Parts landed. On a slow link this is the only honest sign of movement, so it
   is a real measure (staged.done / staged.parts), never an animation. */
.upload-lock-track {
    height: 8px;
    margin: 14px 0 4px;
    border-radius: 999px;
    background: var(--line);
    overflow: hidden;
}
.upload-lock-bar {
    height: 100%;
    border-radius: 999px;
    background: #1D4ED8;                        /* matches .is-sending */
    transition: width 240ms ease;
}
.upload-lock-actions {
    display: flex;
    justify-content: center;
    gap: 10px;
    margin-top: 18px;
    flex-wrap: wrap;
}
/* The page underneath must not scroll behind the overlay — on a phone that
   reads as the app half-working rather than as being held. */
body.is-upload-locked { overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
    .upload-lock-bar { transition: none; }
}

/* ---------- Plan version banner (crew plan safety) ---------- */
.plan-version-banner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-1) var(--space-3);
    padding: var(--space-2) var(--space-3);
    margin-bottom: var(--space-3);
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    font-size: 12.5px;
}
.plan-version-no { font-weight: 700; color: var(--ink); }
.plan-version-eff { color: var(--ink-2); }
.plan-version-src {
    color: var(--ink-3);
    flex-basis: 100%;
    font-size: 11.5px;
}

/* ---------- Photo dropzone (camera-style upload) ---------- */
.photo-dropzone {
    display: block;
    position: relative;
    border: 1.5px dashed var(--line);
    border-radius: var(--radius);
    background: var(--surface);
    cursor: pointer;
    overflow: hidden;
    transition: border-color 0.12s, background 0.12s;
}
.photo-dropzone:hover {
    border-color: var(--ink-3);
    background: var(--surface-2);
}
.photo-dropzone-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    padding: var(--space-6) var(--space-4);
    color: var(--ink-3);
}
.photo-dropzone-hint {
    font-size: 13px;
    color: var(--ink-3);
}
.photo-dropzone-preview {
    width: 100%;
    max-height: 280px;
    object-fit: cover;
    display: block;
}

/* ---------- Lightbox (zoomable image viewer) ---------- */
.lightbox-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(10, 10, 10, 0.94);
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    touch-action: none;
    overscroll-behavior: contain;
    animation: lightbox-fade 0.15s ease-out;
}
@keyframes lightbox-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
}
.lightbox-stage {
    width: 100vw;
    height: 100vh;
    display: grid;
    place-items: center;
    overflow: hidden;
}
.lightbox-image {
    max-width: 92vw;
    max-height: 92vh;
    transform-origin: center;
    transition: transform 0.06s linear;
    user-select: none;
    -webkit-user-drag: none;
    will-change: transform;
}
.lightbox-close {
    position: absolute;
    top: 16px;
    right: 16px;
    width: 40px;
    height: 40px;
    display: grid;
    place-items: center;
    background: rgba(10, 10, 10, 0.6);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    border: 1px solid rgba(255, 255, 255, 0.18);
    color: white;
    border-radius: 999px;
    transition: background 0.12s;
    z-index: 1;
}
.lightbox-close:hover {
    background: rgba(10, 10, 10, 0.8);
}
.lightbox-toggle {
    position: absolute;
    top: 20px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    gap: 0;
    background: rgba(10, 10, 10, 0.6);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    border: 1px solid rgba(255, 255, 255, 0.18);
    border-radius: 999px;
    padding: 3px;
    z-index: 1;
}
.lightbox-toggle-btn {
    padding: 6px 18px;
    color: rgba(255, 255, 255, 0.7);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.04em;
    border-radius: 999px;
    transition: background 0.15s, color 0.15s;
}
.lightbox-toggle-btn:hover { color: #fff; }
.lightbox-toggle-btn.is-active {
    background: #fff;
    color: #0A0A0A;
}
button.punch-card-photo { cursor: zoom-in; }

/* ---------- Job page ---------- */
.breadcrumb {
    color: var(--ink-3);
    font-size: 12.5px;
    margin-bottom: 10px;
}
.breadcrumb a { color: var(--ink-2); }
.breadcrumb a:hover { color: var(--ink); }

.job-header {
    margin-bottom: var(--space-8);
    display: flex; gap: var(--space-4); align-items: flex-start;
}
.job-photo {
    flex: 0 0 auto; display: block;
    width: 96px; height: 96px;
    border-radius: 8px; overflow: hidden;
    border: 1px solid var(--line-2);
}
.job-photo img { width: 100%; height: 100%; object-fit: cover; }
.job-header-text { flex: 1 1 auto; min-width: 0; }

/* Permit / filing rows: title + progression on the left, advance button on the
   right at desktop; stacked with a full-width button on phones. */
.permit-doc-row {
    display: flex; justify-content: space-between;
    align-items: flex-start; gap: var(--space-3);
}
.permit-doc-main { flex: 1 1 auto; min-width: 0; }
.permit-doc-action { flex: 0 0 auto; }
@media (max-width: 600px) {
    .permit-doc-row { flex-direction: column; }
    .permit-doc-action, .permit-doc-action .btn { width: 100%; }
}
.job-title { font-size: 32px; font-weight: 700; letter-spacing: -0.022em;
             line-height: 1.15; color: var(--ink); }
.job-meta-line {
    margin-top: 10px;
    color: var(--ink-2);
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    font-size: 13.5px;
}
.job-meta-line .sep { color: var(--ink-3); }
.job-meta-line .type-chip { vertical-align: 0; margin-right: 4px; }

.kpis {
    display: grid; grid-template-columns: repeat(3, 1fr);
    gap: var(--space-8);
    padding: var(--space-6) 0;
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
    margin: var(--space-8) 0 var(--space-6);
}
@media (max-width: 720px) { .kpis { grid-template-columns: 1fr; gap: var(--space-4); } }
.kpi-label {
    font-size: 11px; font-weight: 600;
    letter-spacing: 0.08em; text-transform: uppercase;
    color: var(--ink-3); margin-bottom: 6px;
}
.kpi-value {
    font-size: 24px; font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}
.kpi-extra { font-size: 12.5px; color: var(--ink-3); margin-top: 2px; }
.kpi-bar {
    height: 5px;
    background: var(--surface-2);
    border-radius: 999px;
    overflow: hidden;
    margin: 9px 0 5px;
    max-width: 180px;
}
.kpi-bar > span {
    display: block; height: 100%;
    background: var(--ok);
    border-radius: 999px;
    transition: width 0.5s var(--ease);
}

/* Tabs */
.tabs {
    display: flex; gap: var(--space-5);
    border-bottom: 1px solid var(--line);
    margin: var(--space-6) 0 var(--space-6);
    overflow-x: auto;
    scrollbar-width: none;
    /* When scrolled to via a deep link or the "Manage Punch List" action,
       clear the sticky topbar so the tabs/section aren't tucked underneath. */
    scroll-margin-top: calc(var(--topbar-h) + var(--space-4));
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
    padding: 10px 0;
    color: var(--ink-3);
    font-size: 13.5px; font-weight: 500;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    white-space: nowrap;
    transition: color 0.12s, border-color 0.12s;
}
.tab:hover { color: var(--ink-2); }
.tab.active { color: var(--ink); border-color: var(--ink); font-weight: 600; }
/* Per-tab attention badge (e.g. "1 due", "3 open"): a subtle tinted pill so a
   tab's outstanding state is visible without opening it. Tone color drives the
   text + hairline border; the color-mix fill degrades to a plain bordered pill
   where color-mix is unsupported. */
.tab-badge {
    display: inline-block;
    margin-left: 6px;
    padding: 1px 6px;
    border-radius: 999px;
    font-size: 11px; font-weight: 600; line-height: 1.5;
    vertical-align: middle; white-space: nowrap;
    border: 1px solid currentColor;
    background: color-mix(in srgb, currentColor 10%, transparent);
}
.tab-badge--alert { color: var(--alert); }
.tab-badge--warn  { color: var(--warn); }
.tab-badge--info  { color: var(--info); }
.tab-panel { display: none; }
.tab-panel.active { display: block; animation: fadeup 0.16s ease-out; }

/* Flat tables */
.tbl { width: 100%; border-collapse: collapse; }
.tbl th {
    text-align: left;
    font-size: 11px; font-weight: 600;
    letter-spacing: 0.06em; text-transform: uppercase;
    color: var(--ink-3);
    padding: 10px 12px;
    border-bottom: 1px solid var(--line);
}
.tbl td {
    padding: 13px 12px;
    border-bottom: 1px solid var(--line-2);
    font-size: 13.5px;
    vertical-align: middle;
}
/* Cushion the outer edges back to flush with page content. */
.tbl th:first-child, .tbl td:first-child { padding-left: 0; }
.tbl th:last-child, .tbl td:last-child { padding-right: 0; }
.tbl tbody tr { transition: background 0.12s var(--ease); }
.tbl tbody tr:hover { background: var(--surface); }
.tbl tbody tr.clickable { cursor: pointer; }
.tbl tr:last-child td { border-bottom: none; }
.tbl td.right, .tbl th.right { text-align: right; }
.tbl .mono, .tbl .numeric { font-variant-numeric: tabular-nums; }

/* ---------- Responsive tables ----------
   Two opt-in patterns for the two kinds of table this app has.

   .tbl-scroll  — wrap a table in <div class="tbl-scroll"> when column
   alignment IS the content (numeric ledgers). It becomes an intentional
   horizontal scroll container that bleeds to the page edges, matching the
   calendar/tabs pattern. Allowed by the "no accidental scroll" rule.

   .tbl-stack   — add to a <table class="tbl tbl-stack"> when the table reads
   fine as a stack of cards on a phone. At <=600px the header row is hidden and
   each row becomes a labelled card; put the column name in each cell as
   data-th="Label" and it shows as a small caption. */
.tbl-scroll { overflow: visible; }
@media (max-width: 600px) {
    .tbl-scroll {
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        margin-inline: calc(var(--space-4) * -1);
        padding-inline: var(--space-4);
        overscroll-behavior-x: contain;
    }
    .tbl-scroll > .tbl { min-width: 520px; }

    .tbl-stack thead { display: none; }
    .tbl-stack tr {
        display: block;
        padding: var(--space-3) 0;
        border-bottom: 1px solid var(--line);
    }
    .tbl-stack tr:last-child { border-bottom: none; }
    .tbl-stack td {
        display: flex;
        justify-content: space-between;
        align-items: baseline;
        gap: var(--space-4);
        padding: 3px 0;
        border: none;
        text-align: right;
    }
    .tbl-stack td:first-child { padding-left: 0; }
    .tbl-stack td[data-th]::before {
        content: attr(data-th);
        flex: 0 0 auto;
        text-align: left;
        font-size: 11px; font-weight: 600;
        letter-spacing: 0.06em; text-transform: uppercase;
        color: var(--ink-3);
    }
    /* A cell holding an action button spans full width and the button fills it. */
    .tbl-stack td.cell-action { justify-content: stretch; }
    .tbl-stack td.cell-action .btn { width: 100%; }
    /* The row's primary label reads as a heading, not a right-aligned value. */
    .tbl-stack td.cell-title {
        justify-content: flex-start;
        text-align: left;
        font-weight: 600; font-size: 15px;
        padding-bottom: var(--space-1);
    }
    .tbl-stack td.cell-title[data-th]::before { display: none; }
}

/* Status text + icon (no pills) */
.status {
    display: inline-flex; align-items: center; gap: 5px;
    font-size: 12.5px;
    color: var(--ink-2);
}
.status .dot {
    width: 6px; height: 6px;
    border-radius: 50%;
    background: var(--ink-3);
}
.status.alert { color: var(--alert); }
.status.alert .dot { background: var(--alert); }
.status.ok { color: var(--ok); }
.status.ok .dot { background: var(--ok); }
/* `status warn` was already in use (the SM's material-list review row, the
   estimating page's deferred-list row) with no rule behind it, so it rendered
   as the plain grey default. */
.status.warn { color: var(--warn); }
.status.warn .dot { background: var(--warn); }
.status.live .dot {
    background: var(--ok);
    animation: pulse-live 1.6s ease-in-out infinite;
}
@keyframes pulse-live {
    0%, 100% { box-shadow: 0 0 0 0 rgba(21, 128, 61, 0.40); }
    50% { box-shadow: 0 0 0 5px rgba(21, 128, 61, 0); }
}

/* ---------- Service / emergency tickets ---------- */
.svc {
    padding: 14px 16px;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--bg);
    margin-bottom: 10px;
    transition: border-color 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.svc:hover {
    border-color: var(--ink-3);
    box-shadow: 0 1px 2px rgba(10, 10, 10, 0.04),
                0 8px 20px rgba(10, 10, 10, 0.06);
}
.svc-emergency {
    border-color: #F3C4C4;
    border-left: 3px solid var(--alert);
    background: var(--alert-soft);
}
.svc-emergency:hover { border-color: var(--alert); }
.svc-issue {
    font-size: 15px; font-weight: 600;
    color: var(--ink); margin-bottom: 4px;
}
.svc-meta {
    color: var(--ink-2); font-size: 12.5px;
    display: flex; gap: 12px; flex-wrap: wrap;
}

/* ---------- Login (simple vertical list) ---------- */
.login-page {
    min-height: 100vh; background: var(--surface);
    display: grid; place-items: center;
    padding: var(--space-6);
}
.login-card {
    width: 100%; max-width: 380px;
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--space-8);
}
.login-card .brand { padding-left: 0; padding-right: 0; }
.login-title {
    font-size: 17px; font-weight: 600;
    color: var(--ink);
    margin: var(--space-2) 0 var(--space-2);
}
.login-help { color: var(--ink-2); font-size: 13px; margin-bottom: var(--space-6); }
.who-list { display: flex; flex-direction: column; gap: 4px; }
.who-row {
    display: flex; align-items: center; gap: 10px;
    padding: 10px;
    border-radius: var(--radius-sm);
    cursor: pointer;
    color: var(--ink);
    transition: background 0.12s;
    width: 100%;
    text-align: left;
}
.who-row:hover { background: var(--surface-2); }
.who-row .name { font-weight: 500; font-size: 14px; }
.who-row .role { font-size: 11px; color: var(--ink-3);
                  letter-spacing: 0.06em; text-transform: uppercase; }
.who-row .arrow { margin-left: auto; color: var(--ink-3); }

/* ---------- Modal ---------- */
.modal-backdrop {
    position: fixed; inset: 0;
    background: rgba(10, 10, 10, 0.42);
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
    display: grid; place-items: center;
    z-index: 100;
    animation: fadein 0.15s ease-out;
}
@keyframes fadein { from { opacity: 0; } to { opacity: 1; } }
.modal {
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    width: 100%; max-width: 520px;
    box-shadow: var(--shadow-pop);
    max-height: calc(100vh - 80px);
    overflow-y: auto;
    margin: 40px;
    animation: pop 0.2s var(--ease);
}
.modal.lg { max-width: 720px; }
.modal.xl { max-width: 920px; }
.modal.xxl { max-width: 1180px; }

/* ---------- Record-materials modal: invoice preview beside the form ---------- */
.mat-modal-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
    gap: var(--space-6);
    align-items: start;
}
/* Keep the invoice in view while the line-item list scrolls. The whole .modal
   is the scroll container and its header is sticky at top:0, so offset below it. */
.mat-preview { position: sticky; top: 64px; }
.mat-form { min-width: 0; }
.invoice-preview {
    display: flex;
    flex-direction: column;
    gap: 8px;
    height: min(72vh, 640px);
}
.invoice-preview iframe,
.invoice-preview img {
    flex: 1 1 auto;
    width: 100%;
    min-height: 0;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface-2);
}
.invoice-preview img { object-fit: contain; }
.invoice-preview-open { align-self: flex-start; }
/* Placeholder before upload — same box so the preview doesn't jump in later. */
.invoice-preview-empty {
    flex: 1 1 auto;
    display: grid;
    place-items: center;
    text-align: center;
    padding: 24px;
    border: 1px dashed var(--line);
    border-radius: var(--radius);
    background: var(--surface-2);
}
@media (max-width: 860px) {
    .mat-modal-grid { grid-template-columns: 1fr; }
    .mat-preview { position: static; }
    .invoice-preview { height: 44vh; }
}
@keyframes pop {
    from { opacity: 0; transform: translateY(8px) scale(0.985); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}
.modal-header {
    padding: var(--space-5) var(--space-6);
    border-bottom: 1px solid var(--line);
    display: flex; align-items: center; justify-content: space-between;
    /* The whole .modal scrolls; pin the header + footer so the action
       buttons stay reachable even when the body is very tall (e.g. the
       estimate builder with its plans iframe on a phone). */
    position: sticky; top: 0; z-index: 2;
    background: var(--bg);
}
.modal-title { font-size: 15px; font-weight: 600; color: var(--ink); }
.modal-body { padding: var(--space-6); }
.modal-footer {
    padding: var(--space-4) var(--space-6);
    border-top: 1px solid var(--line);
    display: flex; justify-content: flex-end; gap: 8px;
    position: sticky; bottom: 0; z-index: 2;
    background: var(--bg);
}
.modal-close { padding: 4px; color: var(--ink-3); }
.modal-close:hover { color: var(--ink); }

/* ---------- Capacity-aware site-manager picker ---------- */
.assisting-sm-list {
    list-style: none; margin: 0 0 4px; padding: 0;
}
.assisting-sm-list li {
    display: flex; align-items: center; gap: 6px; line-height: 1.7;
}

.smcap-sm {
    border: 1px solid var(--line); border-radius: 10px;
    padding: 14px 16px; margin-bottom: 10px;
    transition: border-color 0.12s ease, background 0.12s ease;
}
.smcap-sm:hover { border-color: var(--ink-3); }
.smcap-sm:has(input:checked) { border-color: var(--ink); background: var(--surface-2); }
.smcap-sm input[type="radio"] { accent-color: var(--ink); }
.smcap-sm .smcap-name { font-weight: 600; font-size: 14px; }
.smcap-sm .smcap-summary { display: block; margin-top: 4px; line-height: 1.5; }
.smcap-dot {
    display: inline-block; width: 8px; height: 8px; border-radius: 50%;
    margin-left: 8px; vertical-align: middle; background: var(--ink-3);
}
.smcap-dot.ok   { background: var(--ok); }
.smcap-dot.busy { background: #C2871A; }
/* The drill-in hosts the real CalendarGrid (calendar-grid.css); separate it
   from the summary line and let it scroll horizontally inside the modal. */
.smcap-sched {
    margin-top: 12px; padding-top: 12px;
    border-top: 1px solid var(--line-2);
    overflow-x: auto;
}
.smcap-toggle { white-space: nowrap; flex: 0 0 auto; }

/* ---------- Peek slide-over ---------- */
.peek-backdrop {
    position: fixed; inset: 0;
    background: rgba(10, 10, 10, 0.30);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
    z-index: 90;
    animation: fadein 0.15s ease-out;
}
.peek {
    position: fixed; top: 0; right: 0; bottom: 0;
    width: 100%; max-width: 480px;
    background: var(--bg);
    border-left: 1px solid var(--line);
    z-index: 95;
    overflow-y: auto;
    animation: slidein 0.22s var(--ease);
    box-shadow: -8px 0 40px rgba(10, 10, 10, 0.12);
}
@keyframes slidein {
    from { transform: translateX(20px); opacity: 0; }
    to   { transform: translateX(0); opacity: 1; }
}
.peek-header {
    padding: var(--space-5) var(--space-6);
    border-bottom: 1px solid var(--line);
    display: flex; align-items: center; justify-content: space-between;
    position: sticky; top: 0;
    background: var(--bg);
}
.peek-title { font-size: 16px; font-weight: 600; color: var(--ink); }
.peek-sub { font-size: 12px; color: var(--ink-3); margin-top: 2px; }
.peek-body { padding: var(--space-6); }
.peek-section { margin-top: var(--space-6); }
.peek-section:first-child { margin-top: 0; }

/* ---------- Command palette ---------- */
.palette-backdrop {
    position: fixed; inset: 0;
    background: rgba(10, 10, 10, 0.42);
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
    display: grid; place-items: flex-start center;
    padding-top: 120px;
    z-index: 200;
    animation: fadein 0.12s ease-out;
}
.palette {
    width: 100%; max-width: 600px;
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow-pop);
    overflow: hidden;
    animation: pop 0.14s ease-out;
}
.palette-input-wrap {
    border-bottom: 1px solid var(--line);
    padding: 14px 18px;
}
.palette-input {
    width: 100%; border: none; outline: none;
    background: transparent;
    font-size: 16px;
    color: var(--ink);
}
.palette-input::placeholder { color: var(--ink-3); }
.palette-results {
    max-height: 50vh;
    overflow-y: auto;
    padding: 6px 0;
}
.palette-group {
    font-size: 10px; font-weight: 600;
    letter-spacing: 0.10em; text-transform: uppercase;
    color: var(--ink-3);
    padding: 10px 18px 4px;
}
.palette-row {
    display: flex; align-items: center; gap: 10px;
    padding: 9px 18px;
    cursor: pointer;
    transition: background 0.10s;
}
.palette-row:hover, .palette-row.active {
    background: var(--surface-2);
}
.palette-row .text { flex: 1; min-width: 0; }
.palette-row .label { font-size: 13.5px; color: var(--ink); font-weight: 500; }
.palette-row .sub { font-size: 12px; color: var(--ink-3); margin-top: 1px; }
.palette-row .kind {
    font-size: 10px; color: var(--ink-3);
    letter-spacing: 0.06em; text-transform: uppercase;
    border: 1px solid var(--line);
    padding: 2px 6px;
    border-radius: 3px;
}
.palette-empty {
    padding: 20px; text-align: center; color: var(--ink-3);
    font-size: 13px;
}

/* ---------- Toast ---------- */
.toast-stack {
    position: fixed; right: 24px; bottom: 24px; z-index: 220;
    display: flex; flex-direction: column; gap: 8px;
    pointer-events: none;
}
.toast {
    background: var(--ink); color: white;
    border-radius: var(--radius-sm);
    padding: 10px 14px;
    box-shadow: var(--shadow-pop);
    pointer-events: auto;
    font-size: 13.5px;
    animation: slidein 0.18s ease-out;
    max-width: 380px;
}
.toast.alert { background: var(--alert); }
.toast.ok    { background: var(--ok); }
.toast {
    display: inline-flex; align-items: center; gap: 14px;
}
.toast-action {
    color: white;
    font-weight: 600;
    font-size: 12.5px;
    letter-spacing: 0.04em;
    padding: 4px 10px;
    border-radius: 5px;
    border: 1px solid rgba(255,255,255,0.30);
    background: rgba(255,255,255,0.10);
    cursor: pointer;
    flex-shrink: 0;
    transition: background 0.12s;
}
.toast-action:hover { background: rgba(255,255,255,0.20); }
.toast-action:disabled { opacity: 0.5; cursor: default; }

/* ---------- Skeleton loading ---------- */
.skeleton {
    background: var(--surface-2);
    border-radius: var(--radius-sm);
    animation: skeleton-pulse 1.4s ease-in-out infinite;
}
.skeleton-line { height: 14px; margin-bottom: 8px; }
.skeleton-line.tall { height: 22px; }
.skeleton-line.short { width: 40%; }
.skeleton-line.mid   { width: 70%; }
.skeleton-block { height: 80px; margin-bottom: 16px; }
@keyframes skeleton-pulse {
    0%, 100% { opacity: 0.6; }
    50%      { opacity: 1; }
}

/* ---------- Top navigation progress bar (fires on every navigation) ---------- */
#nav-progress {
    position: fixed;
    top: 0;
    left: 0;
    height: 3px;
    width: 0;
    background: var(--ink);
    box-shadow: 0 0 8px var(--ink);
    border-radius: 0 2px 2px 0;
    z-index: 99999;
    opacity: 0;
    pointer-events: none;
    transition: width 0.2s ease, opacity 0.25s ease;
}
@media (prefers-reduced-motion: reduce) {
    #nav-progress { transition: opacity 0.15s ease; }
}

/* ---------- Searchable combobox (intake customer picker) ---------- */
.combo { position: relative; }
.combo-menu {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    z-index: 50;
    max-height: 280px;
    overflow-y: auto;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm, 8px);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
    padding: 4px;
}
.combo-menu[hidden] { display: none; }
.combo-option {
    padding: 8px 10px;
    border-radius: 6px;
    cursor: pointer;
    line-height: 1.3;
}
.combo-option:hover,
.combo-option.active { background: var(--surface-2); }
.combo-option[hidden] { display: none; }
.combo-empty { padding: 10px; }

/* ---------- Keyboard focus ---------- */
:focus-visible {
    outline: none;
    box-shadow: 0 0 0 2px var(--ink), 0 0 0 4px rgba(10,10,10,0.10);
    border-radius: var(--radius-sm);
}
.queue-row:focus-visible,
.queue-row.kbd-focus {
    border-color: var(--ink);
    box-shadow: inset 3px 0 0 var(--ink);
    outline: none;
}

/* ---------- Cheatsheet (?) ---------- */
.cheatsheet {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 8px 32px;
}
.cheatsheet-row {
    display: flex; justify-content: space-between; align-items: center;
    padding: 6px 0;
}
.cheatsheet-label { color: var(--ink-2); font-size: 13px; }
.cheatsheet-keys { display: flex; gap: 4px; }
.cheatsheet-keys kbd {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 22px; height: 22px;
    padding: 0 6px;
    border: 1px solid var(--line);
    border-bottom-width: 2px;
    border-radius: 4px;
    background: var(--surface);
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-size: 11px;
    font-weight: 600;
    color: var(--ink);
}
@media (max-width: 720px) { .cheatsheet { grid-template-columns: 1fr; } }

/* ---------- Persistent live strip (Crew) ---------- */
.live-strip {
    display: flex; align-items: center; justify-content: space-between;
    gap: 12px;
    padding: 10px 14px;
    background: var(--ok-soft);
    color: var(--ok);
    border: 1px solid rgba(21, 128, 61, 0.18);
    border-radius: var(--radius-sm);
    font-size: 13px;
    font-weight: 500;
    margin-bottom: var(--space-4);
    position: sticky; top: 0; z-index: 5;
}
.live-strip .live-dot {
    width: 8px; height: 8px;
    background: var(--ok);
    border-radius: 50%;
    box-shadow: 0 0 0 0 rgba(21, 128, 61, 0.45);
    animation: pulse-live 1.6s ease-in-out infinite;
    flex-shrink: 0;
}
.live-strip .live-meta {
    color: var(--ok); opacity: 0.75;
    font-weight: 400; font-size: 12.5px;
}

/* ---------- Live roster (per-employee tiles under live-strip) ---------- */
.live-roster {
    display: flex; flex-direction: column;
    gap: 6px;
    margin: -4px 0 var(--space-4) 0;
    padding: 10px 12px;
    background: var(--bg-elev);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
}
.live-roster-row {
    display: flex; align-items: center; gap: 10px;
    font-size: 13px;
}
/* Name over clock-in stamp. The stamp is a full "Aug 2, 2026 · 7:12 AM", which
   won't fit on one line beside the running timer and the Clock out button on a
   phone — so the two stack and the row grows instead of overflowing. */
.live-roster-who {
    flex: 1; min-width: 0;
    display: flex; flex-direction: column; gap: 1px;
}
.live-roster-when { color: var(--muted); font-size: 11px; }
.live-roster-meta { color: var(--muted); font-size: 12px; }
.live-roster-row[data-status="out"] .live-roster-who { opacity: 0.55; }
.avatar-xs {
    width: 22px; height: 22px;
    border-radius: 50%;
    color: white;
    font-size: 10px; font-weight: 600;
    display: inline-flex; align-items: center; justify-content: center;
    flex-shrink: 0;
}
.btn-xs {
    padding: 2px 8px;
    font-size: 11px;
    line-height: 1.4;
}

/* ---------- Time tab session list ---------- */
.time-session {
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    margin-bottom: var(--space-3);
    padding: 0;
    background: var(--bg);
}
.time-session > summary {
    list-style: none;
    cursor: pointer;
    padding: 12px 16px;
    display: flex; align-items: center; gap: 12px;
    font-size: 13px;
}
.time-session > summary::-webkit-details-marker { display: none; }
.time-session[open] > summary {
    border-bottom: 1px solid var(--line);
    background: var(--bg-elev);
}
.time-session .data-table {
    margin: 8px 16px 12px;
    width: calc(100% - 32px);
}

/* The crew's end-of-day video, inside the session that recorded it. Two parts:
   a STAGE that starts as a poster and becomes the <video> in the same box (so
   pressing play shifts nothing), and a BAR that keeps the label and Download
   reachable in every state — including the one where the clip won't load, which
   on a job-site connection is a state that happens. No <video> exists until the
   stage is pressed, so a job with a long history costs nothing to render.
   Shares the .data-table inset. */
.eod-video {
    margin: 12px 16px 0;
    width: calc(100% - 32px);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--surface-2);
}
.eod-video-stage {
    position: relative;
    aspect-ratio: 16 / 9;   /* re-set to the clip's own ratio on loadedmetadata */
    background: #0A0A0A;
    display: grid;
    place-items: center;
}
.eod-video-player {
    width: 100%; height: 100%;
    /* contain, so a vertical phone clip is letterboxed cleanly rather than
       cropped — the stage has already taken its aspect ratio by then. */
    object-fit: contain;
    display: block;
    background: #000;
}
/* The poster. A button, so it is keyboard-reachable and announces itself. */
.eod-video-open {
    position: absolute; inset: 0;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center; gap: 10px;
    width: 100%; height: 100%;
    padding: 0;
    font: inherit;
    color: #FFFFFF;
    background: none;
    border: 0;
    cursor: pointer;
}
.eod-video-play {
    display: grid; place-items: center;
    /* 56px clears the 44px touch minimum with room to spare — this is the one
       target a site manager hits with a thumb, outdoors. */
    width: 56px; height: 56px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.16);
    border: 1.5px solid rgba(255, 255, 255, 0.85);
    transition: background 0.18s var(--ease), transform 0.18s var(--ease);
}
.eod-video-play svg { width: 22px; height: 22px; margin-left: 3px; }
.eod-video-hint { font-size: 12px; letter-spacing: 0.01em; opacity: 0.9; }
.eod-video-open:hover .eod-video-play { background: rgba(255, 255, 255, 0.3); }
.eod-video-open:focus-visible { outline: none; }
.eod-video-open:focus-visible .eod-video-play {
    box-shadow: 0 0 0 3px rgba(10, 10, 10, 0.9), 0 0 0 6px #FFFFFF;
}
/* Spinner while the first bytes are in flight — never a silent black box. */
.eod-video-stage.is-loading::after {
    content: '';
    position: absolute;
    width: 26px; height: 26px;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, 0.25);
    border-top-color: #FFFFFF;
    animation: eod-video-spin 0.7s linear infinite;
}
@keyframes eod-video-spin { to { transform: rotate(360deg); } }
.eod-video-error {
    display: flex; flex-direction: column; gap: 4px;
    padding: var(--space-5);
    text-align: center;
    color: #FFFFFF;
    font-size: 13px;
}
.eod-video-error span { font-size: 12px; color: rgba(255, 255, 255, 0.75); }
.eod-video-bar {
    display: flex; align-items: center; gap: var(--space-3);
    padding: 10px 12px;
    border-top: 1px solid var(--line);
}
.eod-video-meta {
    display: flex; flex-direction: column; gap: 1px;
    min-width: 0; flex: 1;
    font-size: 13px;
}
.eod-video-dl {
    flex: none;
    display: inline-flex; align-items: center; gap: 6px;
    min-height: 36px;
}
.eod-video-dl svg { width: 15px; height: 15px; }
.time-video-chip {
    display: inline-flex; vertical-align: -2px;
    color: var(--ink-2);
}
.time-video-chip svg { width: 13px; height: 13px; }
@media (max-width: 480px) {
    /* Stack, so a long "clocked out …" line never squeezes Download. */
    .eod-video-bar { align-items: stretch; flex-direction: column; gap: var(--space-2); }
    .eod-video-dl { justify-content: center; min-height: 44px; }
}
@media (prefers-reduced-motion: reduce) {
    .eod-video-stage.is-loading::after { animation-duration: 2.4s; }
    .eod-video-play { transition: none; }
}

/* ---------- Onboarding tour ---------- */
.tour-backdrop {
    position: fixed; inset: 0;
    background: rgba(10, 10, 10, 0.50);
    z-index: 240;
    animation: fadein 0.16s ease-out;
}
.tour-spotlight {
    position: absolute;
    border-radius: var(--radius-sm);
    box-shadow: 0 0 0 9999px rgba(10, 10, 10, 0.50);
    transition: top 0.22s, left 0.22s, width 0.22s, height 0.22s;
    pointer-events: none;
}
.tour-card {
    position: absolute;
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--space-5);
    width: 320px;
    max-width: calc(100vw - 24px);
    box-shadow: var(--shadow-pop);
    z-index: 1;
    transition: top 0.22s, left 0.22s;
}
.tour-eyebrow {
    font-size: 10px; font-weight: 600; letter-spacing: 0.10em;
    color: var(--ink-3); text-transform: uppercase;
    margin-bottom: 6px;
}
.tour-title { font-weight: 600; font-size: 16px; color: var(--ink); }
.tour-body { color: var(--ink-2); font-size: 13.5px; margin-top: 8px; line-height: 1.5; }
.tour-actions {
    display: flex; align-items: center; justify-content: space-between;
    margin-top: var(--space-4);
}
.tour-dots { display: flex; gap: 6px; }
.tour-dot {
    width: 6px; height: 6px; border-radius: 50%;
    background: var(--ink-3);
    transition: background 0.16s;
}
.tour-dot.active { background: var(--ink); }

/* ---------- Misc small bits ---------- */
.checkbox {
    width: 16px; height: 16px; accent-color: var(--ink);
    cursor: pointer;
}
.snooze-row {
    color: var(--ink-3); font-size: 12.5px; font-style: italic;
}

/* Bulk selection bar (Office) */
.bulk-bar {
    position: sticky; bottom: 16px;
    margin: var(--space-4) auto 0;
    background: var(--ink); color: white;
    padding: 10px 16px;
    border-radius: var(--radius);
    box-shadow: var(--shadow-pop);
    display: flex; align-items: center; gap: 16px;
    z-index: 10;
    width: fit-content;
    animation: pop 0.16s ease-out;
}
.bulk-bar .count {
    font-weight: 600;
}
.bulk-bar button { color: white; }
.bulk-bar .btn-primary { background: white; color: var(--ink); }

/* "Why stuck" link */
.why-stuck {
    color: var(--ink-2);
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
}
.why-stuck:hover { color: var(--ink); }

/* Star (pinned) */
.pin-star {
    color: var(--ink-3);
    padding: 4px;
    font-size: 16px;
    line-height: 1;
    transition: color 0.12s;
}
.pin-star:hover { color: var(--ink); }
.pin-star.pinned { color: var(--ink); }

/* Yarn timeline */
.yarn {
    display: flex; flex-direction: column; gap: 0;
    border-left: 2px solid var(--line);
    padding-left: 16px;
    margin-left: 6px;
}
.yarn-step {
    position: relative;
    padding: 10px 0;
    border-bottom: 1px solid var(--line-2);
}
.yarn-step:last-child { border-bottom: none; }
.yarn-step::before {
    content: ''; position: absolute;
    left: -22px; top: 16px;
    width: 8px; height: 8px;
    border-radius: 50%;
    background: var(--ink-3);
    border: 2px solid var(--bg);
}
.yarn-step.current::before { background: var(--ink); }
.yarn-step.held-long::before { background: var(--alert); }
.yarn-who {
    font-weight: 500; color: var(--ink);
    font-size: 13px;
}
.yarn-meta {
    color: var(--ink-3); font-size: 12px; margin-top: 2px;
}
.yarn-action { color: var(--ink-2); font-size: 12.5px; margin-top: 4px; }

/* Predictive sub-card under Workflow Card */
.predictive-card {
    margin-top: var(--space-3);
    padding: 14px 18px;
    border: 1px dashed var(--line);
    border-radius: var(--radius-sm);
    color: var(--ink-2);
    font-size: 13px;
    display: flex; align-items: center; gap: 12px;
}
.predictive-card .eyebrow { margin: 0; }

/* Autocomplete dropdown */
.autocomplete {
    position: absolute;
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-pop);
    z-index: 110;
    max-height: 220px;
    overflow-y: auto;
    min-width: 240px;
}
.autocomplete-item {
    padding: 8px 12px;
    cursor: pointer;
    font-size: 13px;
}
.autocomplete-item:hover, .autocomplete-item.active {
    background: var(--surface-2);
}

/* Drag-over receipt zone */
.drop-zone {
    border: 2px dashed var(--line);
    border-radius: var(--radius);
    padding: var(--space-6);
    text-align: center;
    color: var(--ink-3);
    transition: border-color 0.12s, background 0.12s, color 0.12s;
}
.drop-zone.drag-over {
    border-color: var(--ink);
    background: var(--surface);
    color: var(--ink);
}

/* Mic button */
.mic-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 32px; height: 32px;
    border: 1px solid var(--line);
    border-radius: 50%;
    color: var(--ink-2);
    transition: color 0.12s, border-color 0.12s;
}
.mic-btn:hover { color: var(--ink); border-color: var(--ink-3); }
.mic-btn.recording {
    color: var(--alert); border-color: var(--alert);
    animation: pulse-mic 1.1s ease-in-out infinite;
}
@keyframes pulse-mic {
    0%, 100% { box-shadow: 0 0 0 0 rgba(185, 28, 28, 0.40); }
    50%      { box-shadow: 0 0 0 6px rgba(185, 28, 28, 0); }
}

/* Presence dot (Wave 7 / B1) */
.presence-dot {
    width: 6px; height: 6px;
    background: var(--ok);
    border-radius: 50%;
    margin-right: 4px;
    box-shadow: 0 0 0 1.5px var(--bg);
}

/* ---------- Job-type tag (quiet uppercase label, no dot, no border) ---------- */
.type-chip {
    display: inline-block;
    padding: 2px 7px;
    border-radius: 3px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.10em;
    text-transform: uppercase;
    line-height: 1.6;
    white-space: nowrap;
    background: var(--surface-2);
    color: var(--ink-2);
    vertical-align: 1px;
}
.type-chip .type-chip-dot { display: none; }   /* legacy: kept hidden */
.type-chip.type-standard,
.type-chip.type-tm,
.type-chip.type-service {
    background: var(--surface-2);
    color: var(--ink-2);
}
.type-chip.type-emergency {
    background: var(--alert-soft);
    color: var(--alert);
}
.type-chip.type-warn {
    background: var(--alert-soft);
    color: var(--alert);
    border: 1px solid var(--alert);
}
.type-chip.type-ok {
    background: var(--ok-soft);
    color: var(--ok);
    border: 1px solid var(--ok);
}
.type-chip-sm  { font-size: 9.5px; padding: 1px 6px; }
.type-chip-hero { font-size: 11px; padding: 3px 9px; vertical-align: 2px; }

/* ---------- Time-in-phase: just a duration, plain text ---------- */
.in-phase {
    color: var(--ink-2);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.in-phase.long { color: var(--alert); font-weight: 500; }

/* ---------- Jobs list (card rows) ---------- */
.job-list { display: flex; flex-direction: column; gap: 10px; }
.job-card {
    display: flex;
    align-items: stretch;
    gap: 16px;
    padding: 14px 16px;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--bg);
    cursor: pointer;
    transition: border-color 0.15s ease, box-shadow 0.15s ease,
                transform 0.15s ease;
}
.job-card:hover {
    border-color: var(--ink-3);
    box-shadow: 0 1px 2px rgba(10, 10, 10, 0.04),
                0 8px 20px rgba(10, 10, 10, 0.06);
}
.job-card:active { transform: translateY(1px); }
.job-card-body {
    flex: 1; min-width: 0;
    display: flex; flex-direction: column; gap: 6px;
}
.job-card-title {
    font-size: 15px; font-weight: 600;
    color: var(--ink); line-height: 1.25;
}
.job-card-chips {
    display: flex; gap: 6px; flex-wrap: wrap; align-items: center;
}
.job-card-meta {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    font-size: 12.5px; color: var(--ink-2);
}
.job-card-meta .sep { color: var(--ink-3); }
.job-card-next { color: var(--ink-3); }
.job-card-aside {
    flex: 0 0 auto;
    display: flex; flex-direction: column;
    align-items: flex-end; justify-content: space-between;
    gap: 10px;
}
/* The spelled-out timer is long; let it wrap inside the card instead of
   overflowing or colliding with the Open button. */
.job-card-meta .in-phase { white-space: normal; }

/* Pager under a paged list. Prev / position / Next, centered. The disabled
   ends are <span class="btn"> rather than buttons — nothing to focus and
   nothing to click, so the tab order only ever holds live pages. */
.pager {
    display: flex; align-items: center; justify-content: center;
    gap: 12px; margin-top: 20px;
}
.pager-pos { color: var(--ink-3); font-size: 13px; }
.pager [aria-disabled="true"] { opacity: 0.45; pointer-events: none; }

/* Phone: stack the card so the stage tag + Open sit in a row below the body,
   never overlapping the text. */
@media (max-width: 640px) {
    .job-card { flex-direction: column; align-items: stretch; gap: 12px; }
    .job-card-aside {
        flex-direction: row; align-items: center;
        justify-content: space-between;
    }
    .svc .spread { flex-direction: column; align-items: stretch; gap: 12px; }
    .svc .spread .row { justify-content: flex-end; }
}

/* Subtle one-time entrance: the first several list items cascade in, the rest
   appear instantly so long lists stay snappy. The global prefers-reduced-motion
   rule above collapses this to instant for users who ask for less motion. */
@keyframes list-in {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: translateY(0); }
}
.job-list .job-card,
.svc,
.queue .queue-row {
    animation: list-in 0.26s var(--ease) both;
}
.job-list .job-card:nth-child(n+9),
.svc:nth-child(n+9),
.queue .queue-row:nth-child(n+9) { animation: none; }
.job-list .job-card:nth-child(2), .svc:nth-child(2), .queue .queue-row:nth-child(2) { animation-delay: 25ms; }
.job-list .job-card:nth-child(3), .svc:nth-child(3), .queue .queue-row:nth-child(3) { animation-delay: 50ms; }
.job-list .job-card:nth-child(4), .svc:nth-child(4), .queue .queue-row:nth-child(4) { animation-delay: 75ms; }
.job-list .job-card:nth-child(5), .svc:nth-child(5), .queue .queue-row:nth-child(5) { animation-delay: 100ms; }
.job-list .job-card:nth-child(6), .svc:nth-child(6), .queue .queue-row:nth-child(6) { animation-delay: 125ms; }
.job-list .job-card:nth-child(7), .svc:nth-child(7), .queue .queue-row:nth-child(7) { animation-delay: 150ms; }
.job-list .job-card:nth-child(8), .svc:nth-child(8), .queue .queue-row:nth-child(8) { animation-delay: 175ms; }

/* Stage indicator: colored dot + label, color-coded by stage category.
   Reuses the stage_pill_class() categories; matches the app's dot style. */
.stage-tag {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: 12.5px; font-weight: 500;
    color: var(--ink); white-space: nowrap;
}
.stage-tag .dot {
    width: 7px; height: 7px; border-radius: 50%;
    background: var(--ink-3); flex: 0 0 auto;
}
.stage-tag.pill-success .dot { background: var(--ok); }
.stage-tag.pill-warn    .dot { background: var(--warn); }
.stage-tag.pill-info    .dot { background: var(--info); }
.stage-tag.pill-purple  .dot { background: var(--purple); }
.stage-tag.pill-danger  .dot { background: var(--alert); }

/* ---------- Empty states ---------- */
.empty {
    text-align: center;
    padding: var(--space-12) var(--space-6);
}
.empty .icon {
    width: 40px; height: 40px;
    margin: 0 auto var(--space-4);
    color: var(--ink-3);
}
.empty .title { font-weight: 600; color: var(--ink); font-size: 15px; }
.empty .sub { margin-top: 6px; font-size: 13px; color: var(--ink-3); }

/* ---------- Full-page error states (404 / 403 / 500) ----------
   templates/error.html renders this inside whichever shell layout.html chose,
   so it must sit happily in the office canvas, the crew content area AND a
   .login-card. It borrows .page-eyebrow / .page-title / .page-sub and
   .btn-lg rather than defining its own type scale — an error page is still
   an app page. No entrance animation on purpose: this screen appears when
   something already went wrong, and motion reads as another glitch. */
.error-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    /* ~46ch keeps the two-line body inside a comfortable measure; the centered
       .page-sub would otherwise inherit its 560px left-aligned cap. */
    max-width: 46ch;
    margin: 0 auto;
    padding: var(--space-16) var(--space-4) var(--space-12);
}
.error-glyph {
    display: block;
    color: var(--ink-3);
    margin-bottom: var(--space-4);
}
.error-glyph svg { display: block; }
/* Scales down to the 390px crew phone without a separate breakpoint. */
.error-state .page-title { font-size: clamp(22px, 6vw, 32px); }
.error-state .page-sub { max-width: none; }
/* Centered short copy strands a one-word last line otherwise ("...out of /
   date."). balance evens the lines out; browsers without it just wrap. */
.error-state .page-title,
.error-state .page-sub { text-wrap: balance; }
.error-state .btn {
    margin-top: var(--space-6);
    /* .btn-lg is already 48px tall — comfortably past the 44px touch minimum. */
    text-decoration: none;
}

/* Signed out there is no shell, so error.html reuses the sign-in card. The
   card supplies its own padding; don't stack the block's on top of it. */
.login-card .error-state {
    padding: var(--space-2) 0 0;
    max-width: none;
}

@media (max-width: 640px) {
    .error-state { padding-top: var(--space-10); }
    /* Full-bleed target on a phone — the crew shell is the main audience for
       a 403, and a thumb shouldn't have to find a centered pill. */
    .error-state .btn { width: 100%; }
}

/* ---------- Banners (only for alert / ok) ---------- */
.banner {
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    padding: var(--space-3) var(--space-4);
    font-size: 13px;
    color: var(--ink-2);
    background: var(--surface);
    display: flex; gap: 10px; align-items: center;
}
.banner.alert {
    border-color: rgba(185, 28, 28, 0.25);
    background: var(--alert-soft);
    color: var(--alert);
}
.banner.ok {
    border-color: rgba(21, 128, 61, 0.25);
    background: var(--ok-soft);
    color: var(--ok);
}
.banner.warn {
    border-color: rgba(180, 83, 9, 0.28);
    background: rgba(180, 83, 9, 0.08);
    color: var(--warn);
}

/* ---------- Activity feed ---------- */
.feed { display: flex; flex-direction: column; }
.feed-day {
    font-size: 11px; font-weight: 600;
    letter-spacing: 0.08em; text-transform: uppercase;
    color: var(--ink-3);
    padding: var(--space-5) 0 var(--space-2);
    border-bottom: 1px solid var(--line);
    margin-bottom: var(--space-2);
}
.feed-day:first-child { padding-top: 0; }
.feed-item {
    position: relative;
    display: grid;
    grid-template-columns: 78px 1fr;
    gap: var(--space-5);
    padding: 11px 0;
}
/* Timeline: a connecting line down the gap, with a dot per event. */
.feed-item::before {
    content: '';
    position: absolute;
    left: 84px; top: 0; bottom: 0;
    width: 1px; background: var(--line);
}
.feed-item::after {
    content: '';
    position: absolute;
    left: 81px; top: 16px;
    width: 7px; height: 7px;
    border-radius: 50%;
    background: var(--ink-3);
    box-shadow: 0 0 0 3px var(--bg);
}
.feed-item:first-child::before { top: 16px; }
.feed-item:last-child::before { bottom: auto; height: 16px; }
.feed-time {
    font-size: 11.5px;
    color: var(--ink-3);
    padding-top: 2px;
    line-height: 1.45;
}
.feed-text { color: var(--ink); font-size: 13.5px; padding-left: 14px; }
.feed-actor { color: var(--ink-3); font-size: 12px; margin-top: 2px; padding-left: 14px; }

/* Instant hover tooltip (exact times on relative-time labels, etc.). */
[data-tip] { cursor: help; }
.feed-time[data-tip] { text-decoration: underline dotted var(--ink-3); text-underline-offset: 2px; }
.ui-tip {
    position: fixed; z-index: 1000;
    background: var(--ink); color: var(--bg);
    padding: 5px 9px; border-radius: 6px;
    font-size: 12px; font-weight: 500;
    white-space: nowrap; pointer-events: none;
    box-shadow: var(--shadow-pop);
}

/* ---------- Integrations log ---------- */
.log-row {
    border: 1px solid var(--line);
    border-left: 3px solid var(--ink-3);
    border-radius: var(--radius-sm);
    padding: var(--space-4);
    margin-bottom: 8px;
    background: white;
}
.log-row.qbo      { border-left-color: var(--ink); }
.log-row.docusign { border-left-color: var(--ink-2); }
.log-row.whatsapp { border-left-color: var(--ink); }
.log-row.email    { border-left-color: var(--ink-3); }
.log-head {
    display: grid; grid-template-columns: 90px 1fr auto;
    gap: var(--space-4); align-items: center;
}
.log-channel {
    font-size: 10px; font-weight: 600;
    letter-spacing: 0.10em; text-transform: uppercase;
    color: var(--ink-2);
}
.log-summary { color: var(--ink); font-size: 13.5px; font-weight: 500; }
.log-time { color: var(--ink-3); font-family: 'JetBrains Mono', ui-monospace, monospace;
            font-size: 11.5px; white-space: nowrap; }
.log-body {
    margin-top: 10px;
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-size: 12px;
    color: var(--ink-2);
    background: var(--surface);
    padding: 10px 12px;
    border-radius: 4px;
    white-space: pre-wrap;
    line-height: 1.55;
}

/* ---------- Aging strip ---------- */
.aging-strip {
    display: grid; grid-template-columns: repeat(4, 1fr);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    overflow: hidden;
}
.aging-cell {
    padding: var(--space-4);
    border-right: 1px solid var(--line);
}
.aging-cell:last-child { border-right: none; }
.aging-cell.alert .aging-amount { color: var(--alert); }
.aging-label {
    font-size: 10px; font-weight: 600;
    letter-spacing: 0.10em; text-transform: uppercase;
    color: var(--ink-3); margin-bottom: 4px;
}
.aging-amount {
    font-size: 18px; font-weight: 600;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}
.aging-count { font-size: 12px; color: var(--ink-3); margin-top: 2px; }

/* ---------- Line-item editor (estimate / CO) ---------- */
.lineitems { display: flex; flex-direction: column; gap: 6px; }
.li-row {
    display: grid;
    grid-template-columns: 2fr 2.6fr 0.9fr 1fr 1.1fr 28px;
    gap: 6px; align-items: start;
}
/* Each field is a cell so it can carry a mobile label; on desktop the cell is
   invisible (just wraps its input) and the `.li-head` row supplies the labels. */
.li-cell { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.li-cell .input { width: 100%; box-sizing: border-box; }
.li-lbl {
    display: none;               /* shown only when the row becomes a card (mobile) */
    font-size: 10px; text-transform: uppercase; letter-spacing: .04em;
    color: var(--ink-3);
}
.li-cell-remove { align-items: flex-end; }
.li-row .input { padding: 7px 9px; font-size: 13px; }

/* ---------- Reminder cadence settings ---------- */
.reminder-group { margin: 18px 0 22px; }
.reminder-group-title {
    margin: 0 0 8px; font-size: 13px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.03em; color: var(--ink-2);
    border-bottom: 1px solid var(--line); padding-bottom: 5px;
}
.reminder-head, .reminder-row {
    display: grid; grid-template-columns: 1fr 80px 80px 80px;
    gap: 8px; align-items: center;
}
.reminder-head {
    font-size: 11px; text-transform: uppercase; letter-spacing: 0.02em;
    color: var(--ink-2); margin-bottom: 4px;
}
.reminder-head span { text-align: center; }
.reminder-head span:first-child { text-align: left; }
.reminder-row { margin-bottom: 6px; }
.reminder-label { font-size: 13.5px; color: var(--ink); }
.reminder-num { padding: 6px 8px; font-size: 13px; text-align: center; }
.reminder-toggle-row { align-items: center; gap: 10px; margin-bottom: 8px; }
.reminder-toggle {
    display: flex; align-items: center; gap: 8px;
    font-size: 13.5px; cursor: pointer;
}
.reminder-inline { display: flex; align-items: center; gap: 6px; font-size: 13px; }
.reminder-inline .reminder-num { max-width: 70px; }
@media (max-width: 640px) {
    .reminder-head, .reminder-row { grid-template-columns: 1fr 56px 56px 56px; gap: 5px; }
}
/* Description is a textarea so it supports line breaks; starts ~1 input tall
   and can be dragged taller. */
.li-row textarea.li-desc {
    resize: vertical;
    min-height: 34px;
    line-height: 1.35;
    font-family: inherit;
}
.li-head { margin-bottom: 2px; }
.li-head-cell {
    font-size: 11px; font-weight: 600; letter-spacing: 0.02em;
    text-transform: uppercase; color: var(--ink-2);
    padding: 0 2px;
}
.li-head-total { text-align: right; }
.li-row .total {
    text-align: right;
    font-variant-numeric: tabular-nums;
    color: var(--ink); font-weight: 500;
}
.li-add {
    align-self: flex-start;
    color: var(--ink-2); padding: 6px 10px;
    border-radius: var(--radius-sm); font-size: 12.5px;
}
.li-add:hover { background: var(--surface-2); color: var(--ink); }
.li-totals {
    margin-top: var(--space-4); padding-top: var(--space-4);
    border-top: 1px solid var(--line);
    display: grid; grid-template-columns: 1fr auto;
    gap: 4px 24px;
    font-size: 13.5px;
}
.li-totals .grand {
    grid-column: 1 / -1;
    display: grid; grid-template-columns: 1fr auto;
    font-size: 17px; font-weight: 700;
    color: var(--ink);
    margin-top: 6px;
}
/* Phone/tablet: the line-item row collapses into a labeled card. The column
   header row is dropped and each field surfaces its own `.li-lbl`, so a stacked
   row stays self-describing (item + remove on top, description full width, then
   qty / unit price / total in a compact three-up strip). */
@media (max-width: 720px) {
    .li-head { display: none; }
    .li-row {
        grid-template-columns: 1fr 1fr 1fr;
        grid-template-areas:
            "item  item  remove"
            "desc  desc  desc"
            "qty   price total";
        gap: var(--space-2);
        align-items: end;
        border: 1px solid var(--line);
        border-radius: var(--radius);
        padding: var(--space-3);
        background: var(--bg);
    }
    .li-cell-item   { grid-area: item; }
    .li-cell-desc   { grid-area: desc; }
    .li-cell-qty    { grid-area: qty; }
    .li-cell-price  { grid-area: price; }
    .li-cell-total  { grid-area: total; }
    .li-cell-remove { grid-area: remove; align-self: start; }
    .li-lbl { display: block; }
    .li-row .total { text-align: left; padding-top: 6px; }
}

/* ---------- Estimate builder modal: two-pane body (builder + plans) ---------- */
.est-builder-body { display: flex; gap: var(--space-4); flex-wrap: wrap; }
.est-builder-pane {
    flex: 1 1 50%; min-width: 320px;
    max-height: 700px; overflow-y: auto; padding-right: 8px;
}
.est-plans-pane { flex: 1 1 50%; min-width: 300px; }
.est-plans-empty {
    padding: var(--space-6); background: var(--surface-soft);
    border-radius: var(--radius-sm); text-align: center;
}
.est-plans-frame {
    width: 100%; height: 540px;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
}
@media (max-width: 720px) {
    /* Panes wrap to stacked at their min-widths — drop the builder pane's inner
       scroll box so the whole modal scrolls as one surface, and shrink the plans
       PDF so it doesn't dominate the phone viewport (still inline per request). */
    .est-builder-pane { max-height: none; overflow-y: visible; padding-right: 0; }
    .est-plans-frame { height: 360px; }
}
@media (max-width: 600px) {
    .est-builder-toolbar { flex-wrap: wrap; }
    /* Builder inputs must render at 16px so iOS Safari doesn't zoom the modal on
       focus — `.li-row .input` (0,2,0) otherwise beats the global phone rule. */
    .li-row .input, .li-row textarea.li-desc {
        font-size: 16px; padding: 11px 12px; min-height: 44px;
    }
    .li-row textarea.li-desc { min-height: 48px; }
}

/* ---------- Item autocomplete (QBO picker) ----------
   Custom dropdown that replaces the native <datalist>, which mobile browsers
   only partially render (they cap visible suggestions, truncating long item
   lists). Body-anchored + positioned in JS, so it never gets clipped by the
   line-item row's overflow. */
.item-ac {
    position: absolute; z-index: 1200;
    margin: 4px 0 0; padding: 4px; list-style: none;
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-pop);
    max-height: 280px; overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}
.item-ac-option {
    padding: 10px 10px; min-height: 40px;
    display: flex; align-items: center;
    font-size: 13.5px; color: var(--ink);
    border-radius: var(--radius-sm); cursor: pointer;
}
.item-ac-option:hover,
.item-ac-option.is-active { background: var(--surface-2); }

/* ---------- Planned materials (internal list) ---------- */
.material-list-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.ml-status-line { margin-bottom: 10px; }
/* Each phase is a bounded card so it's obvious which materials belong to which
   phase. The card is the recessed *tray* the rows sit in: it's deliberately a
   step darker (--surface-2) than the white row blocks inside it, which is what
   makes one material read as separate from the next. Every phase keeps identical
   box geometry (1px border + 10px inline padding) so rows across all cards line
   up under the single column header — the current-phase accent is drawn with an
   inset shadow so it never shifts content. */
.ml-phase {
    margin-bottom: 14px;
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--surface-2); padding: 2px 10px 10px;
}
/* A tray with no .ml-phase-head (the open crew requests) — the 2px top above is
   sized for a heading, so give the first row real breathing room instead. */
.ml-phase--bare { padding-top: 10px; }
.ml-phase-head {
    font-size: 12.5px; font-weight: 700; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--ink);
    padding: 10px 0 8px; border-bottom: 1px solid var(--line); margin-bottom: 2px;
}
/* Last row sits flush against the card's bottom padding — drop its trailing gap
   so the tray doesn't end with an uneven band of empty surface. */
.ml-phase > .ml-row:last-child,
.ml-phase--done > .ml-row:last-child { margin-bottom: 0; }
/* Current phase: the standout. Accent bar via inset shadow (no geometry change),
   faint info tint (color-mix falls back to --surface-2 where unsupported), and a
   bold info-colored title. */
.ml-phase--current {
    border-color: var(--info);
    box-shadow: inset 3px 0 0 var(--info);
    background: var(--surface-2);
    background: color-mix(in srgb, var(--info) 7%, var(--surface-2));
}
.ml-phase--current .ml-phase-head { color: var(--info); }
/* Upcoming / unassigned phases: present but secondary. */
.ml-phase--upcoming .ml-phase-head,
.ml-phase--plain .ml-phase-head { color: var(--ink-2); }
/* Completed phases: muted; when collapsed, drop the head divider so a lone
   underline doesn't float in the card. */
.ml-phase--done .ml-phase-head { color: var(--ink-3); }
.ml-phase--done:not([open]) > .ml-phase-head { border-bottom: none; }

/* ---------- Phase-first grouping (tabs) ----------
   Shared by the Materials (.ml-phase*), Schedule, Time, and Progress tabs so the
   current phase always leads and earlier phases collapse into a <details> —
   history stays reachable, never hidden. Tokens only → dark mode carries. */
.phase-sect { margin-bottom: 16px; }
.phase-sect-head {
    font-size: 11px; font-weight: 600; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--ink-3);
    padding: 6px 0; border-bottom: 1px solid var(--line);
    margin-bottom: 8px;
    display: flex; align-items: center; gap: 8px;
}
/* A phase-state tag ("Current" / "Completed" / "Upcoming") on a section head. */
.phase-sect-tag, .ml-phase-tag {
    font-size: 10px; font-weight: 700; letter-spacing: 0.06em;
    text-transform: uppercase;
    padding: 1px 7px; border-radius: 999px;
    border: 1px solid var(--line); color: var(--ink-3);
    background: var(--surface-2);
}
.phase-sect-tag.is-current, .ml-phase-tag.is-current {
    color: var(--info); border-color: var(--info); background: transparent;
}
.phase-sect-tag.is-done, .ml-phase-tag.is-done {
    color: var(--ok); border-color: var(--ok); background: transparent;
}
.phase-sect-tag.is-upcoming, .ml-phase-tag.is-upcoming { color: var(--ink-3); }

/* Collapsed (completed) phase: the summary is the section head; hide the native
   marker and provide our own disclosure triangle so it matches the punch list. */
.phase-sect--done > summary, .ml-phase--done > summary { cursor: pointer; list-style: none; }
.phase-sect--done > summary::-webkit-details-marker,
.ml-phase--done > summary::-webkit-details-marker { display: none; }
.phase-sect--done > summary::before, .ml-phase--done > summary::before {
    content: "\25B8"; color: var(--ink-3); font-size: 10px; margin-right: 2px;
}
.phase-sect--done[open] > summary::before,
.ml-phase--done[open] > summary::before { content: "\25BE"; }
/* One material = one white block on the tray, separated by a real gap rather
   than a hairline. A row is often several lines tall (notes, order bits, invoice
   line, the full-width note input), and a 1px rule was far too weak to show where
   one item ended and the next began. */
.ml-row {
    display: grid;
    /* Fixed qty/status/actions tracks (only the description flexes) so every
       row AND the 3-cell header share identical column positions — otherwise a
       variable-width actions column throws Qty/Status out from under their
       headers. */
    grid-template-columns: minmax(0, 1fr) 84px 150px 150px;
    gap: 10px; align-items: center;
    padding: 11px 12px; margin-bottom: 8px;
    background: var(--bg); border-radius: var(--radius-sm);
}
.ml-row.ml-edit { grid-template-columns: 2fr 0.8fr 0.8fr 1fr 1.6fr auto; }
.ml-head {
    /* 23px inline padding = phase card's 1px border + 10px padding + the row
       block's own 12px padding, so the single column header lands directly over
       the row cells inside every card. Keep in sync with .ml-phase and .ml-row
       padding above — change either and the Qty/Status labels drift off-column.
       The header is an .ml-row too, so it also has to opt out of the block
       surface (it's a label strip on the page, not a material). */
    padding: 2px 23px 6px;
    background: none; border-radius: 0; margin-bottom: 0;
    font-size: 11px; font-weight: 600; letter-spacing: .04em;
    text-transform: uppercase; color: var(--ink-3);
}
.ml-head .ml-qty, .ml-head .ml-status { color: var(--ink-3); }
.ml-cell { min-width: 0; }
.ml-desc { font-size: 14px; }
.ml-qty { font-variant-numeric: tabular-nums; color: var(--ink-2); font-size: 13.5px; }
.ml-qty-num { font-weight: 500; color: var(--ink); }
.ml-actions, .ml-edit-actions {
    display: flex; gap: 6px; align-items: center; justify-content: flex-end;
}
/* Full-width sub-blocks of a row. Indented so they visibly hang off the material
   above them — flush-left they sat at the same indent as the NEXT item's title,
   which is half of why rows blurred together. */
.ml-history { grid-column: 1 / -1; padding: 6px 0 2px 12px; }
.ml-note { grid-column: 1 / -1; padding: 6px 0 0 12px; display: flex; gap: 8px; align-items: center; }
.ml-note .input-sm { flex: 1; min-width: 0; }
.ml-note .btn-sm { flex: none; }
.ml-hist-row { font-size: 12px; color: var(--ink-2); padding: 2px 0; }
/* Per-phase add control: lives INSIDE its phase card, under that phase's rows,
   separated by a hairline so it reads as the foot of this list rather than as a
   button floating between two cards. Full-width on phones for a clean tap target. */
.ml-phase-add { margin-top: 8px; padding: 10px 0 2px; border-top: 1px solid var(--line); }
.ml-phase-add .btn { max-width: 100%; white-space: normal; }
/* An empty phase while editing — there's still a card so there's somewhere to
   add the first item. Muted, unlike the estimate builder's required-red variant. */
.ml-phase-empty { color: var(--ink-3); padding: 10px 0 6px; }
/* Read-only "which phase is this for" line at the top of a modal whose phase is
   decided by context rather than picked (e.g. the crew's material request). */
.phase-note {
    font-size: 13px; color: var(--ink-2);
    background: var(--surface-2); border: 1px solid var(--line);
    border-radius: var(--radius-sm); padding: 8px 10px; margin-bottom: 12px;
}
.phase-note strong { color: var(--ink); }
/* Crew Requests: the "asked by X · need by Y" / notes / denial-reason lines
   under a request's title. Also used by the crew Today rows. */
.ml-req-meta { font-size: 12.5px; color: var(--ink-3); margin-top: 2px; }
/* Collapsed "Completed (N)" group on the crew's Today view. */
.crew-req-done { margin-top: 8px; }
.crew-req-done > summary {
    cursor: pointer; padding: 6px 0; list-style: none;
}
.crew-req-done > summary::-webkit-details-marker { display: none; }
.crew-req-done > summary::before { content: "\25B8"; margin-right: 6px; }
.crew-req-done[open] > summary::before { content: "\25BE"; }
/* Crew "Scope of work" on Today: the current contract with the money removed.
   Same collapsed-details treatment as the crew requests group above. The job
   page's est-current diff styling can't be reused — it's table-scoped
   (.co-diff-removed td) and this is a list of queue-rows. */
.crew-scope { margin: var(--space-5) 0 var(--space-4); }
.crew-scope > summary {
    cursor: pointer; padding: 6px 0; list-style: none;
    display: flex; align-items: baseline; gap: 8px;
}
.crew-scope > summary::-webkit-details-marker { display: none; }
.crew-scope > summary::before { content: "\25B8"; color: var(--ink-3); }
.crew-scope[open] > summary::before { content: "\25BE"; }
.crew-scope-title { font-weight: 600; font-size: 15px; color: var(--ink); }
.crew-scope-sub { margin-bottom: var(--space-3); }
/* Struck, not hidden: crew may have been building to the old scope and need to
   see that it came out. Only the title strikes — the note explains why. */
.crew-scope-row.is-removed .queue-row-title {
    text-decoration: line-through; color: var(--ink-3);
}
.crew-scope-row.is-added { background: var(--ok-soft); }
.crew-scope-group {
    font-size: 11px; font-weight: 600; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--ink-3);
    margin-top: var(--space-3);
}
.input-sm, .select-sm { padding: 6px 8px; font-size: 13px; height: 32px; }
.ml-badge {
    display: inline-flex; align-items: center; gap: 4px;
    font-size: 11.5px; font-weight: 600; padding: 2px 9px;
    border-radius: 999px; border: 1px solid var(--line);
    color: var(--ink-2); background: var(--surface-2); white-space: nowrap;
}
.ml-badge.ok { color: var(--ok); }
.ml-badge.warn { color: var(--warn); }
.ml-badge.alert { color: var(--alert); }
.ml-badge.info { color: var(--info); }
.ml-badge.dim { color: var(--ink-3); }
/* inline-flex above beats the [hidden] UA rule — restore hide-when-hidden. */
.ml-badge[hidden] { display: none; }
/* Phase select on an estimate material that still needs a deliberate pick. */
.ml-phase-required {
    border-color: var(--alert);
    color: var(--alert);
    box-shadow: 0 0 0 1px var(--alert) inset;
}

/* ---------- Supplier invoices (recorded orders, Materials tab) ----------
   THE RULE for this whole section: a change in BACKGROUND means "this is a
   different order". Nothing else may change background.

   The old markup did the opposite — an order had no box of its own (just a
   trailing hairline) while the QuickBooks bill status inside it was a filled
   --surface-2 panel. That fill read as a divider, so one order looked like
   three (white header / grey bill panel / white items + receipt). Now one
   order is one bounded white card and every section INSIDE it is separated by
   a hairline only. Do not add a background to any .si-sect. */
.si-list { display: flex; flex-direction: column; gap: var(--space-3); }
.si-card {
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 0;
}
/* No hover-lift: .queue-row / .job-card lift because they're clickable, and
   this card isn't — a hover effect would falsely imply it opens something. */
.si-sect { padding: 12px 16px; }
.si-sect + .si-sect { border-top: 1px solid var(--line); }
.si-head {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: 12px; flex-wrap: wrap;
}
.si-head-main { min-width: 0; }
.si-head-aside {
    display: flex; flex-direction: column; align-items: flex-end; gap: 4px;
    text-align: right;
}
.si-title { font-size: 14px; font-weight: 600; color: var(--ink); }
.si-title .si-vendor { font-weight: 500; color: var(--ink-2); }
.si-meta { font-size: 12.5px; color: var(--ink-3); margin-top: 2px; }
.si-total { font-size: 15px; font-weight: 600; font-variant-numeric: tabular-nums; }
/* Section label inside a card (e.g. "QuickBooks vendor bill", "Uploaded
   invoice"): the same uppercase eyebrow treatment used elsewhere, so a section
   is announced by its label rather than by a fill. */
.si-label {
    font-size: 11px; font-weight: 600;
    letter-spacing: 0.06em; text-transform: uppercase;
    color: var(--ink-3);
}
.si-label-row {
    display: flex; align-items: center; justify-content: space-between;
    gap: 10px; flex-wrap: wrap; margin-bottom: 8px;
}
/* Total / Paid / Due as labelled cells rather than a run-on dim sentence. */
.si-money { display: flex; flex-wrap: wrap; gap: 4px 22px; }
.si-money-cell { min-width: 0; }
.si-money-label {
    font-size: 10.5px; font-weight: 600;
    letter-spacing: 0.06em; text-transform: uppercase;
    color: var(--ink-3); margin-bottom: 1px;
}
.si-money-val {
    font-size: 14px; font-weight: 600; color: var(--ink);
    font-variant-numeric: tabular-nums;
}
.si-money-val.is-due { color: var(--alert); }
.si-money-val.is-clear { color: var(--ok); }
.si-note { font-size: 12.5px; color: var(--ink-3); margin-top: 8px; }
.si-note.is-error { color: var(--alert); }
.si-inline-err { color: var(--alert); }
.si-label-solo { margin-bottom: 6px; }
/* Deep links out to QuickBooks on a recorded supplier invoice. Wraps on narrow
   screens rather than pushing the card sideways. */
.qbo-links { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.qbo-links > .dim, .qbo-links > .si-note { flex-basis: 100%; margin-top: 0; }
/* These are ghost buttons in flowing text — kill any inherited link rule so
   they don't pick up an underline inside the card. */
.qbo-links .btn { border-bottom: none; }
.si-links { margin-top: 10px; }
/* The line items read as a small aligned table (description / qty × cost /
   source), not an indented bullet list. */
.si-items { list-style: none; margin: 0; padding: 0; }
.si-item {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    gap: 4px 16px; align-items: baseline;
    padding: 4px 0;
    font-size: 13px; color: var(--ink-2);
}
.si-item + .si-item { border-top: 1px solid var(--line-2); }
.si-item-name { color: var(--ink); min-width: 0; }
.si-item-qty, .si-item-cost { font-variant-numeric: tabular-nums; white-space: nowrap; }
.si-item-cost { color: var(--ink-3); }
.si-item-src { font-size: 12px; color: var(--ink-3); white-space: nowrap; }
/* The invoice's own arithmetic, directly under the list it totals. */
.si-foot {
    margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--line);
    font-size: 12.5px; color: var(--ink-3);
    font-variant-numeric: tabular-nums;
}
.si-foot strong { color: var(--ink); }
.si-receipt-img {
    max-height: 150px; max-width: 100%;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    cursor: zoom-in;
}
/* Job-wide bill totals above the list. Deliberately fill-free (hairline strip,
   no border, no radius) so the ONLY boxes in this section are order cards. */
.si-summary {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: 16px;
    padding: 14px 0; margin-bottom: 14px;
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
}
.si-summary .kpi-value { font-size: 20px; }
.si-summary .kpi-value.is-paid { color: var(--ok); }
.si-summary .kpi-value.is-due { color: var(--alert); }
.si-summary-note { grid-column: 1 / -1; font-size: 12.5px; color: var(--ink-3); }
@media (max-width: 560px) {
    .si-sect { padding: 12px 14px; }
    .si-head { flex-direction: column; }
    .si-head-aside {
        flex-direction: row; align-items: baseline; gap: 10px;
        text-align: left;
    }
    /* Tighten the gap rather than stacking — Total/Paid/Due then wrap to two
       lines instead of always costing three. */
    .si-money { gap: 8px 16px; }
    /* Source moves under the qty column, right-aligned with it, so an item
       stays visually one entry. */
    .si-item { grid-template-columns: minmax(0, 1fr) auto; gap: 2px 12px; }
    .si-item-src { grid-column: 2; text-align: right; }
}

/* Estimate-builder material list: one required section per build phase, so the
   list reads as a first-class requirement rather than an optional afterthought. */
.eml-phase { margin-bottom: 14px; }
.eml-phase-head {
    display: flex; align-items: center; gap: 8px;
    padding: 6px 0; border-bottom: 1px solid var(--line); margin-bottom: 8px;
}
.eml-phase-title {
    font-size: 12px; font-weight: 700; letter-spacing: 0.04em;
    text-transform: uppercase; color: var(--ink-2);
}
.eml-phase-deferred .eml-phase-title { color: var(--ink-3); }
.eml-phase-badge { margin-left: 2px; }
.eml-phase-rows { display: flex; flex-direction: column; gap: 8px; }
/* Matches .ml-phase-add — the add button sits under the rows it adds to. */
.eml-phase-add { padding: 8px 0 2px; }
.eml-phase-add .btn { max-width: 100%; white-space: normal; }
.eml-phase-empty { color: var(--alert); font-weight: 600; padding: 2px 0; }
.eml-phase-deferred .eml-phase-empty { color: var(--ink-3); font-weight: 500; }
.eml-row {
    display: grid;
    grid-template-columns: 2fr 0.8fr 0.8fr 1.8fr auto;
    gap: 10px; align-items: center;
}
.stack-2 { display: flex; flex-direction: column; gap: 6px; }
@media (max-width: 640px) {
    .eml-row { grid-template-columns: 1fr auto; }
    /* .unit-field wraps the unit input, so it — not the [data-eml-field] inside
       it — is the direct grid child that needs the full-width span. */
    .eml-row > [data-eml-field],
    .eml-row > .unit-field { grid-column: 1 / -1; }
}

/* Deferral context on the Materials tab — why the list was deferred + due date
   + which phases are outstanding, for whoever builds it later. */
.ml-defer-banner {
    margin-bottom: 12px; padding: 10px 12px;
    background: var(--surface); border-left: 3px solid var(--warn);
    border-radius: 6px;
}
.ml-defer-head { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.ml-defer-due { font-size: 12.5px; font-weight: 600; color: var(--warn); }
.ml-defer-phases {
    font-size: 13px; color: var(--ink-2);
    display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-bottom: 4px;
}
.ml-defer-reason { font-size: 13px; color: var(--ink-2); }
.warn-list { margin: 8px 0 4px; padding-left: 18px; }
.warn-list li { margin: 3px 0; color: var(--ink-2); font-size: 13.5px; }
.check {
    display: flex; gap: 8px; align-items: flex-start;
    font-size: 13px; color: var(--ink-2); margin-top: 8px;
}
.hairline { border: none; border-top: 1px solid var(--line); }
@media (max-width: 640px) {
    .ml-row, .ml-row.ml-edit { grid-template-columns: 1fr auto; }
    /* See the .eml-row note above — .unit-field is the direct child now. */
    .ml-row.ml-edit > [data-ml-field],
    .ml-row.ml-edit > .unit-field { grid-column: 1 / -1; }
}

/* ---------- Detail list (key-value pairs) ---------- */
.dl {
    display: grid;
    grid-template-columns: 150px 1fr;
    gap: 0 var(--space-5);
    align-items: baseline;
}
.dl dt, .dl dd {
    padding: 11px 0;
    border-top: 1px solid var(--line-2);
}
.dl dt:first-of-type, .dl dd:first-of-type {
    padding-top: 0;
    border-top: none;
}
.dl dt {
    font-size: 11px; font-weight: 600;
    letter-spacing: 0.08em; text-transform: uppercase;
    color: var(--ink-3);
}
.dl dd { margin: 0; color: var(--ink); font-size: 13.5px; }
.dl dd a { color: var(--ink); text-decoration: none; border-bottom: 1px solid var(--line); }
.dl dd a:hover { border-bottom-color: var(--ink-3); }

@media (max-width: 560px) {
    .dl { grid-template-columns: 1fr; gap: 0; }
    .dl dt { padding-bottom: 2px; border-top: 1px solid var(--line-2); }
    .dl dd { padding-top: 0; border-top: none; }
    .dl dt:first-of-type { border-top: none; }
}

/* ---------- Settings sections ---------- */
.settings-section {
    padding: var(--space-8) 0;
    border-bottom: 1px solid var(--line);
}
.settings-section:first-child { padding-top: 0; }
.settings-section:last-child { border-bottom: none; }
.settings-section h3 { margin-bottom: 10px; }
.settings-section .help { margin-bottom: var(--space-4); }

/* ---------- Loaders ---------- */
.spinner {
    display: inline-block;
    width: 14px; height: 14px;
    border: 2px solid rgba(255,255,255,0.4);
    border-top-color: white;
    border-radius: 50%;
    animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Invisible full-page shield while a UI.busy() action is in flight.
 * Transparent on purpose: the pressed button's own spinner stays the
 * only visible loading indicator; the shield just swallows input. */
.busy-shield {
    position: fixed;
    inset: 0;
    z-index: 100000; /* above everything: modals are z-index 100, highest other layer is 99999 */
    background: transparent;
    cursor: progress;
}

/* ---------- Utility ---------- */
.row { display: flex; align-items: center; gap: var(--space-3); }
.col { display: flex; flex-direction: column; gap: var(--space-3); }
.spread { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
.gap-2 { gap: 8px; } .gap-3 { gap: 12px; } .gap-4 { gap: 16px; }
.mt-2 { margin-top: 8px; } .mt-4 { margin-top: 16px; } .mt-6 { margin-top: 24px; } .mt-8 { margin-top: 32px; }
.mb-2 { margin-bottom: 8px; } .mb-4 { margin-bottom: 16px; } .mb-6 { margin-bottom: 24px; } .mb-8 { margin-bottom: 32px; }
.text-right { text-align: right; }
.right { margin-left: auto; }
.full { width: 100%; }
.hide { display: none !important; }
.no-wrap { white-space: nowrap; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------- Mobile ---------- */
.mobile-toggle { display: none; }

/* Tablet / small laptop — sidebar collapses, layout relaxes a bit */
@media (max-width: 920px) {
    .app {
        grid-template-columns: 1fr;
        grid-template-areas: "topbar" "main";
    }
    .sidebar {
        position: fixed; left: -260px; width: 260px;
        height: 100vh; z-index: 100;
        transition: left 0.22s ease;
    }
    .sidebar.open { left: 0; box-shadow: var(--shadow-pop); }
    /* Dim scrim behind the open drawer; tap it to close. */
    .sidebar-scrim {
        display: block;
        position: fixed; inset: 0; z-index: 99;
        background: rgba(10, 10, 10, 0.4);
        opacity: 0; visibility: hidden;
        transition: opacity 0.22s ease, visibility 0.22s ease;
    }
    body.sidebar-open .sidebar-scrim { opacity: 1; visibility: visible; }
    body.sidebar-open { overflow: hidden; }   /* lock page scroll while open */
    .mobile-toggle {
        display: inline-flex; align-items: center; justify-content: center;
        width: 44px; height: 44px; margin-left: calc(var(--space-2) * -1);
        color: var(--ink-2);
    }

    .topbar { padding: 0 var(--space-5); gap: var(--space-3); }
    .cmdk { padding: 6px 10px; }
    .cmdk span:not(kbd) { display: none; }

    /* Reduce hero / page-header padding so the first real content sits closer to the top */
    .main { padding: var(--space-6) var(--space-5) var(--space-12); }
    .page-header { margin-bottom: var(--space-6); }
    .page-title, .job-title { font-size: 28px; }
    .today-hero { margin-bottom: var(--space-8); }
    .today-hero .number-line { font-size: 36px; }

    /* Section spacing — keep separators clear without wasting screen */
    .section-head { margin: var(--space-6) 0 var(--space-3); }

    /* KPIs collapse to 2-col then stack on phone */
    .kpis {
        grid-template-columns: 1fr 1fr;
        gap: var(--space-4);
        padding: var(--space-5) 0;
        margin: var(--space-6) 0 var(--space-5);
    }

    /* Workcard + modals breathe a little tighter */
    .workcard { padding: var(--space-6); }
    .modal { margin: 24px; max-height: calc(100vh - 48px); }
    .modal-body { padding: var(--space-5); }
}

/* Phone — generous tap targets, looser vertical rhythm, content gets full width */
@media (max-width: 600px) {
    /* iOS-zoom-safe form inputs + bigger tap area. Cover EVERY native control
       (not just the .input/.select/.textarea classes) — any focusable field
       under 16px makes iOS Safari zoom the whole page on focus. */
    .input, .select, .textarea {
        font-size: 16px;
        padding: 11px 14px;
    }
    input:not([type=checkbox]):not([type=radio]):not([type=range]):not([type=hidden]),
    select, textarea, .demo-select, .palette-input,
    .select-sm, .input-sm {
        font-size: 16px;
    }
    .field { margin-bottom: var(--space-5); }
    .field-row, .field-row.three {
        grid-template-columns: 1fr;
        gap: var(--space-3);
    }

    /* Buttons — meet the 44px touch-target minimum. btn-sm is a real action on
       phones (row CTAs, permit steps), so it also gets 44px here rather than
       staying at the desktop 36px. btn-xs stays visually small for inline meta
       chips but gets an expanded invisible hit area below. */
    .btn { height: 44px; font-size: 14px; padding: 0 16px; }
    .btn-lg { height: 52px; font-size: 16px; }
    .btn-sm { height: 44px; padding: 0 14px; font-size: 13.5px; }
    .btn-xs { height: 32px; padding: 2px 10px; position: relative; }
    .btn-xs::after { content: ""; position: absolute; inset: -8px; }

    /* Page header / titles */
    .page-header { margin-bottom: var(--space-5); }
    .page-title, .job-title { font-size: 24px; letter-spacing: -0.018em; }
    .page-sub { margin-top: 6px; font-size: 13.5px; }

    /* Topbar tighter on phones */
    .topbar { padding: 0 var(--space-4); }
    .topbar-title { font-size: 13.5px; }
    .main { padding: var(--space-5) var(--space-4) var(--space-10); }

    /* Crew shell — bar/content padding tuned for thumbs */
    .crew-bar { padding: var(--space-3) var(--space-4); gap: 8px; }
    /* Wordmark out, bolt in — see .crew-bar .brand-logo above. */
    .crew-bar .brand-logo { display: none; }
    .crew-bar .brand-icon { display: block; }
    .crew-content {
        padding: var(--space-5) var(--space-4) var(--space-10);
    }
    .crew-hero {
        padding: var(--space-4) 0 var(--space-6);
        margin-bottom: var(--space-5);
    }
    .crew-hero .address { font-size: 22px; }
    .crew-action { margin-top: var(--space-5); }
    .crew-action .btn { height: 52px; font-size: 16px; }

    /* Today hero / pulse strip */
    .today-hero { margin-bottom: var(--space-6); }
    .today-hero .number-line { font-size: 28px; }
    .pulse-strip {
        gap: var(--space-4) var(--space-5);
        padding: var(--space-4) 0;
        margin-bottom: var(--space-6);
    }
    .pulse-stat { flex: 1 1 calc(50% - var(--space-3)); min-width: 120px; }
    .pulse-value { font-size: 19px; }

    /* Workcard a hair tighter on phone */
    .workcard { padding: var(--space-5); }
    .workcard .headline { font-size: 19px; line-height: 1.3; }
    .workcard .actions { margin-top: var(--space-4); }
    .workcard .actions .btn { flex: 1 1 auto; }
    .workcard-mini { padding: var(--space-3); }

    /* Tabs — bleed to screen edges so the underline runs full-width,
       wider tap targets, easier swipe */
    .tabs {
        gap: var(--space-4);
        margin: var(--space-5) calc(var(--space-4) * -1);
        padding: 0 var(--space-4);
    }
    .tab { padding: 12px 0; font-size: 14px; }

    /* Section heads with a trailing button wrap cleanly */
    .section-head {
        flex-wrap: wrap;
        gap: var(--space-2);
        margin: var(--space-6) 0 var(--space-2);
    }
    .section-head:first-child { margin-top: var(--space-1); }

    /* Queue rows: stack content + actions */
    .queue-row {
        grid-template-columns: 1fr;
        gap: var(--space-2);
        padding: var(--space-4);
    }
    .queue-row-actions { justify-content: flex-start; flex-wrap: wrap; gap: 8px; }
    .queue-row-meta { gap: 6px; }

    /* Task rows: the buttons drop to their own full-width line rather than
       squeezing beside the title. They inherit the 44px .queue-row .btn-sm
       tap target below. */
    .task-row { flex-wrap: wrap; align-items: flex-start; }
    .task-actions { width: 100%; flex-wrap: wrap; }
    .task-actions .btn { flex: 1 1 auto; }
    .jc-foot .btn { flex: 0 0 auto; }

    /* Tap-target sizes for row/section/service buttons */
    .queue-row .btn-sm,
    .svc .btn-sm,
    .section-head .btn-sm {
        height: 44px; padding: 0 14px; font-size: 13.5px;
    }
    .pin-star { padding: 8px; font-size: 20px; }

    /* Service cards: content stacks above the action buttons */
    .svc { padding: var(--space-5) 0; }
    .svc .spread { flex-direction: column; align-items: stretch; gap: var(--space-3); }
    .svc .spread > .row { justify-content: flex-start; flex-wrap: wrap; }

    /* Line-item rows become labeled cards at 720px (see the `.li-row` card block
       above); nothing more needed here. */

    /* Detail list (key/value) stacks vertically */
    .dl { grid-template-columns: 1fr; gap: 2px var(--space-3); }
    .dl dt { margin-top: var(--space-3); }
    .dl dt:first-of-type { margin-top: 0; }

    /* Activity feed — time above text instead of side-by-side */
    .feed-item {
        grid-template-columns: 1fr;
        gap: 2px;
        padding: var(--space-3) 0;
    }
    .feed-time { padding-top: 0; }

    /* Integrations log — let the summary use full width */
    .log-row { padding: var(--space-3) var(--space-4); }
    .log-head {
        grid-template-columns: 1fr auto;
        gap: 4px var(--space-3);
    }
    .log-channel { grid-column: 1 / -1; }
    .log-body { padding: 10px; font-size: 11.5px; }

    /* Aging strip — 2x2 instead of cramming 4 across */
    .aging-strip { grid-template-columns: 1fr 1fr; }
    .aging-cell { padding: var(--space-3) var(--space-4); }
    .aging-cell:nth-child(2n) { border-right: none; }
    .aging-cell:nth-child(-n+2) { border-bottom: 1px solid var(--line); }

    /* Cheatsheet — single column */
    .cheatsheet { grid-template-columns: 1fr; gap: 4px; }

    /* Modal — near-edge-to-edge with proper internal padding */
    .modal {
        margin: 12px;
        max-height: calc(100vh - 24px);
        border-radius: var(--radius);
    }
    .modal-header { padding: var(--space-4) var(--space-5); }
    .modal-body { padding: var(--space-5) var(--space-4); }
    .modal-footer {
        padding: var(--space-4);
        flex-direction: column-reverse;
        gap: var(--space-2);
    }
    .modal-footer .btn { width: 100%; }

    /* Peek slides over full width */
    .peek { max-width: 100%; }
    .peek-header { padding: var(--space-4) var(--space-5); }
    .peek-body { padding: var(--space-5) var(--space-4); }
    .peek-section { margin-top: var(--space-5); }

    /* Command palette — sits higher and uses the edges */
    .palette-backdrop {
        padding-top: 40px;
        padding-left: var(--space-3);
        padding-right: var(--space-3);
    }
    .palette-input-wrap { padding: 12px 14px; }
    .palette-row { padding: 12px 14px; }
    .palette-group { padding: 10px 14px 4px; }

    /* Bulk-action bar — span the width, wrap cleanly */
    .bulk-bar {
        bottom: 12px;
        width: auto;
        max-width: none;
        margin: var(--space-4) var(--space-3) 0;
        flex-wrap: wrap;
        gap: 10px var(--space-3);
        padding: var(--space-3) var(--space-4);
    }

    /* Toast slides in from the bottom, not pinned to one corner */
    .toast-stack {
        left: 12px;
        right: 12px;
        bottom: 12px;
    }
    .toast { max-width: 100%; }

    /* Onboarding card fits viewport */
    .tour-card {
        width: calc(100vw - 24px);
        max-width: calc(100vw - 24px);
        padding: var(--space-4);
    }

    /* Job header — smaller thumbnail beside the title, text wraps beneath */
    .job-header { gap: var(--space-3); }
    .job-photo { width: 64px; height: 64px; }

    /* Checklist (crew punch) — bigger touch and label */
    .checklist li { padding: 16px 0; gap: 14px; }
    .check-box { width: 26px; height: 26px; }
    .check-label { font-size: 15px; }

    /* Login who-rows easier to tap */
    .who-row { padding: 12px 10px; }
    .login-card { padding: var(--space-6); }

    /* Live strip + roster a little roomier */
    .live-strip { padding: 10px 12px; flex-wrap: wrap; gap: 6px var(--space-3); }
    .live-roster { padding: var(--space-3); gap: 8px; }
    .live-roster-row { font-size: 13.5px; }

    /* Settings */
    .settings-section { padding: var(--space-6) 0; }

    /* Radio cards (intake) — keep some breathing room */
    .radio-card { padding: var(--space-3) var(--space-4); }
}

/* Very narrow phones (iPhone SE, narrow Androids) — minor squeezes */
@media (max-width: 380px) {
    .main { padding: var(--space-4) var(--space-3) var(--space-10); }
    .crew-content { padding: var(--space-4) var(--space-3) var(--space-10); }
    .page-title, .job-title { font-size: 22px; }
    .today-hero .number-line { font-size: 26px; }
    .crew-hero .address { font-size: 20px; }
    .modal { margin: 8px; max-height: calc(100vh - 16px); }
    .modal-body { padding: var(--space-4) var(--space-3); }
    .peek-body { padding: var(--space-4) var(--space-3); }
    .pulse-stat { flex-basis: 100%; }
}

/* ---------- Estimate send progress (in-modal streaming) ---------- */
.estimate-progress {
    padding: var(--space-6) var(--space-4);
    max-width: 480px;
    margin: 0 auto;
}
.estimate-progress-title {
    font-size: 15px;
    font-weight: 600;
    color: var(--ink);
    margin-bottom: var(--space-5);
    text-align: center;
}
.estimate-progress-steps {
    list-style: none;
    padding: 0;
    margin: 0 0 var(--space-5);
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}
.est-step {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    font-size: 14px;
    color: var(--ink-3);
    transition: color .2s;
}
.est-step-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    border: 1.5px solid var(--line);
    background: var(--bg);
    flex-shrink: 0;
    position: relative;
}
.est-step.pending .est-step-icon::after {
    content: '';
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--ink-3);
    opacity: .35;
}
.est-step.active {
    color: var(--ink);
    font-weight: 500;
}
.est-step.active .est-step-icon {
    border-color: var(--ink);
}
.est-step.active .est-step-icon::after {
    content: '';
    width: 10px;
    height: 10px;
    border-radius: 50%;
    border: 2px solid var(--ink);
    border-top-color: transparent;
    animation: est-spin .7s linear infinite;
}
.est-step.done {
    color: var(--ink-2);
}
.est-step.done .est-step-icon {
    background: var(--ok);
    border-color: var(--ok);
}
.est-step.done .est-step-icon::after {
    content: '';
    width: 9px;
    height: 5px;
    border-left: 1.8px solid #fff;
    border-bottom: 1.8px solid #fff;
    transform: translateY(-1px) rotate(-45deg);
}
.estimate-progress-detail {
    text-align: center;
    font-size: 13px;
    color: var(--ink-2);
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    min-height: 38px;
    display: flex;
    align-items: center;
    justify-content: center;
}
.estimate-progress-detail.done {
    background: var(--ok-soft);
    color: var(--ok);
}
.estimate-progress-detail.error {
    background: var(--alert-soft);
    color: var(--alert);
}
@keyframes est-spin {
    to { transform: rotate(360deg); }
}

/* ======================================================================
   Full-contract Change Order editor (co_editor.html)
   ====================================================================== */
.co-editor-body { background: var(--surface); color: var(--ink); margin: 0; }
.coe-shell { min-height: 100vh; display: flex; flex-direction: column; }
/* Class-level display (flex/grid) beats the UA [hidden] rule, so make hidden
   explicit — otherwise the loader/stage/footer show through their toggles. */
.coe-shell [hidden] { display: none !important; }

.coe-topbar {
    position: sticky; top: 0; z-index: 20;
    display: flex; align-items: center; gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    background: var(--bg); border-bottom: 1px solid var(--line);
}
.coe-back {
    display: inline-flex; align-items: center; justify-content: center;
    width: 34px; height: 34px; border-radius: var(--radius-sm);
    color: var(--ink-2); border: 1px solid var(--line); text-decoration: none;
}
.coe-back:hover { background: var(--surface-2); }
.coe-title { flex: 1; min-width: 0; }
.coe-address { font-weight: 600; font-size: 15px; }
.coe-sub { color: var(--ink-3); font-size: 12px; }

.coe-loading {
    flex: 1; display: flex; flex-direction: column; align-items: center;
    justify-content: center; gap: var(--space-3); color: var(--ink-2);
}

.coe-stage {
    flex: 1; display: grid; gap: var(--space-4);
    grid-template-columns: minmax(0, 1fr) 360px;
    align-items: start;
    padding: var(--space-4);
    max-width: 1280px; width: 100%; margin: 0 auto;
}
.coe-pending-banner {
    grid-column: 1 / -1;
    background: var(--alert-soft); color: var(--alert);
    border: 1px solid var(--alert); border-radius: var(--radius);
    padding: var(--space-3) var(--space-4); font-size: 13px;
}
.coe-edit-banner {
    grid-column: 1 / -1;
    background: #EFF4FF; color: var(--info);
    border: 1px solid var(--info); border-radius: var(--radius);
    padding: var(--space-3) var(--space-4); font-size: 13px;
}
.coe-side { display: flex; flex-direction: column; gap: var(--space-4); }

.coe-panel {
    background: var(--bg); border: 1px solid var(--line);
    border-radius: var(--radius-lg); padding: var(--space-4);
}
.coe-panel-head {
    display: flex; align-items: center; justify-content: space-between;
    margin-bottom: var(--space-3);
}
.coe-panel-head h2 { font-size: 14px; font-weight: 600; margin: 0; }
.coe-hint { color: var(--ink-3); font-size: 12px; margin: 0 0 var(--space-3); }

/* Contract lines: one card per line — inherently responsive, touch-friendly. */
.coe-lines { display: flex; flex-direction: column; gap: var(--space-2); }
.coe-line {
    border: 1px solid var(--line); border-radius: var(--radius);
    padding: var(--space-3); background: var(--bg);
}
.coe-line-removed { opacity: .6; }
.coe-line-top {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: var(--space-2); margin-bottom: var(--space-2);
}
.coe-line-desc { flex: 1; min-width: 0; font-weight: 500; overflow-wrap: anywhere; }
.coe-desc-text.coe-struck { text-decoration: line-through; color: var(--ink-3); }
.coe-line-act { flex-shrink: 0; }
.coe-line-fields {
    display: grid; grid-template-columns: 1fr 1fr 1fr;
    gap: var(--space-2); align-items: end;
}
.coe-field { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.coe-field-lbl {
    font-size: 10px; text-transform: uppercase; letter-spacing: .04em;
    color: var(--ink-3);
}
.coe-field-total { text-align: right; }
.coe-linetotal { font-variant-numeric: tabular-nums; font-weight: 600; padding: 6px 0; }
.coe-line-delta { margin-top: var(--space-2); min-height: 0; text-align: right; }
.coe-line-delta:empty { margin-top: 0; }

.coe-in {
    width: 100%; padding: 8px 10px; font-size: 14px; min-height: 40px;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--bg); color: var(--ink);
}
.coe-in-num { text-align: right; font-variant-numeric: tabular-nums; }
.coe-in:focus { outline: none; border-color: var(--ink-2); box-shadow: 0 0 0 3px rgba(10,10,10,.06); }

.coe-add-btn { width: 100%; margin-top: var(--space-3); min-height: 44px; }

.coe-tag {
    display: inline-block; font-size: 10px; color: var(--ink-3);
    background: var(--surface-2); border-radius: 4px; padding: 1px 5px;
    margin-left: 4px; vertical-align: middle;
}
.coe-tag-new { color: var(--info); background: #EFF4FF; }

.coe-rowbtn {
    background: none; border: 1px solid var(--line); color: var(--ink-2);
    border-radius: var(--radius-sm); padding: 8px 12px; font-size: 13px;
    min-height: 40px; cursor: pointer;
}
.coe-rowbtn:hover { background: var(--surface-2); }
.coe-rowbtn-icon { min-width: 44px; }
.coe-restore { color: var(--info); border-color: var(--info); }

.coe-delta { font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; }
.coe-delta-pos, span.coe-delta-pos { color: var(--ok); }
.coe-delta-neg, span.coe-delta-neg { color: var(--alert); }

.coe-notes { margin-top: var(--space-4); }
.coe-notes label { display: block; font-size: 12px; color: var(--ink-2); margin-bottom: 4px; }
.coe-notes textarea {
    width: 100%; padding: 8px; font: inherit; font-size: 13px;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--bg); color: var(--ink); resize: vertical;
}

.coe-inv-row {
    display: grid; grid-template-columns: 1fr auto auto; gap: var(--space-2);
    align-items: center; padding: 6px 0; border-bottom: 1px solid var(--line-2);
    font-size: 13px;
}
.coe-inv-milestone { text-transform: capitalize; }
.coe-inv-status { font-size: 11px; text-transform: capitalize; color: var(--ink-3); }
.coe-inv-paid { color: var(--ok); }
.coe-inv-overridden { color: var(--warn); }
.coe-inv-amt { text-align: right; font-variant-numeric: tabular-nums; }
.coe-inv-totals { margin-top: var(--space-3); font-size: 13px; }
.coe-inv-totals > div { display: flex; justify-content: space-between; padding: 3px 0; }
.coe-inv-contract { font-weight: 600; border-top: 1px solid var(--line); margin-top: 4px; padding-top: 6px !important; }
.coe-invoice-warning {
    margin-top: var(--space-3); background: #FFF7ED; color: var(--warn);
    border: 1px solid var(--warn); border-radius: var(--radius-sm);
    padding: var(--space-2) var(--space-3); font-size: 12px;
}

.coe-mat-group { margin-bottom: var(--space-3); }
.coe-mat-phase { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--ink-3); margin-bottom: 4px; }
.coe-mat-row.coe-mat-locked {
    display: grid; grid-template-columns: 1fr auto auto; gap: var(--space-2);
    align-items: center; padding: 6px 0; font-size: 13px; border-bottom: 1px solid var(--line-2);
}
.coe-mat-desc { overflow-wrap: anywhere; }
.coe-mat-qty { color: var(--ink-2); font-variant-numeric: tabular-nums; }
.coe-mat-status { color: var(--ink-3); font-size: 11px; }
/* Editable existing material: description on top, qty/unit/remove below. */
.coe-mat-edit {
    border: 1px solid var(--line); border-radius: var(--radius);
    padding: var(--space-3); margin-bottom: var(--space-2);
}
.coe-mat-descin { margin-bottom: var(--space-2); }
.coe-mat-fields {
    display: grid; grid-template-columns: 1fr 1fr auto; gap: var(--space-2);
    /* Top-aligned so the labels — and therefore the controls under them — stay
       on one line when the unit cell grows a "Custom…" box beneath its select.
       Identical to bottom-alignment when every cell is one control tall; the
       Remove button keeps its own align-self: end. */
    align-items: start;
}
.coe-mat-remove { align-self: end; }
/* Per-phase add control inside a CO material group, under that phase's rows. */
.coe-mat-add { padding-top: var(--space-2); }
.coe-mat-add .btn { width: 100%; min-height: 40px; white-space: normal; }
.coe-newmat-row {
    display: flex; flex-direction: column; gap: var(--space-2);
    margin-bottom: var(--space-2);
    border: 1px dashed var(--line); border-radius: var(--radius); padding: var(--space-3);
}

/* Plan-markup panel in the CO editor side rail. */
.coe-markup-body { margin-bottom: var(--space-3); }
.coe-markup-attached {
    display: flex; gap: var(--space-3); align-items: flex-start;
    border: 1px solid var(--line); border-radius: var(--radius);
    padding: var(--space-3);
}
.coe-markup-thumb {
    width: 72px; height: 72px; object-fit: cover; flex: 0 0 auto;
    border: 1px solid var(--line-2); border-radius: var(--radius-sm);
    background: var(--surface-2);
}
.coe-markup-note { font-size: 12px; color: var(--ink-2); }
.coe-markup-note p { margin: 4px 0 6px; }

.coe-footbar {
    position: sticky; bottom: 0; z-index: 20;
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-3); flex-wrap: wrap;
    padding: var(--space-3) var(--space-4);
    padding-bottom: calc(var(--space-3) + env(safe-area-inset-bottom, 0px));
    background: var(--bg); border-top: 1px solid var(--line);
}
.coe-foot-summary { font-size: 13px; color: var(--ink-2); flex: 1; min-width: 180px; }
.coe-foot-actions { display: flex; gap: var(--space-2); }

.btn-xs { padding: 3px 10px; font-size: 12px; }

/* ---- Tablet / phone ---- */
@media (max-width: 900px) {
    .coe-stage { grid-template-columns: 1fr; }
}
@media (max-width: 640px) {
    /* iOS zooms any focused control under 16px — bump all editor inputs. */
    .coe-in, .coe-notes textarea { font-size: 16px; min-height: 44px; }
    .coe-rowbtn { min-height: 44px; }
    .coe-back { width: 44px; height: 44px; }
    .coe-topbar .btn { display: none; }          /* footer holds the actions on phone */
    .coe-stage {
        padding: var(--space-3);
        padding-left: calc(var(--space-3) + env(safe-area-inset-left, 0px));
        padding-right: calc(var(--space-3) + env(safe-area-inset-right, 0px));
    }
    .coe-footbar { flex-direction: column; align-items: stretch; }
    .coe-foot-actions { width: 100%; }
    .coe-foot-actions .btn { flex: 1; min-height: 48px; }
    .coe-mat-fields { grid-template-columns: 1fr 1fr; }
    .coe-mat-remove { grid-column: 1 / -1; }
}
@media (prefers-reduced-motion: reduce) {
    .coe-in, .coe-rowbtn, .coe-line { transition: none !important; }
}

/* ---- CO diff modal (opened from the job page) ---- */
.co-diff-section { margin-bottom: var(--space-4); }
.co-diff-section h4 { font-size: 13px; margin: 0 0 var(--space-2); }
.co-diff-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.co-diff-table th {
    text-align: left; font-size: 11px; text-transform: uppercase;
    color: var(--ink-3); padding: 0 var(--space-2) 4px; border-bottom: 1px solid var(--line);
}
.co-diff-table td { padding: 5px var(--space-2); border-bottom: 1px solid var(--line-2); }
.co-diff-num { text-align: right; font-variant-numeric: tabular-nums; }
.co-diff-added { background: var(--ok-soft); }
.co-diff-removed td { text-decoration: line-through; color: var(--ink-3); }
.co-diff-changed .co-diff-new { color: var(--ink); font-weight: 600; }
.co-diff-oldval { color: var(--ink-3); }

/* ---- Estimate: "Original signed" / "Current contract" toggle ----
   The current view — the same table marked up with what the signed COs did —
   is the default; the original signed estimate is opt-in.
   Tokens only → auto dark mode carries. */
.est-view-toggle {
    display: inline-flex;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    overflow: hidden;
}
.est-view-btn {
    appearance: none;
    border: 0;
    background: var(--surface);
    color: var(--ink-2);
    font: inherit;
    font-size: 12px;
    font-weight: 600;
    padding: 5px var(--space-3);
    cursor: pointer;
    white-space: nowrap;
}
.est-view-btn + .est-view-btn { border-left: 1px solid var(--line); }
.est-view-btn:hover { background: var(--surface-2); }
.est-view-btn.is-active { background: var(--ink); color: var(--bg); }
.est-view-btn:focus-visible { outline: 2px solid var(--ink); outline-offset: -2px; }

/* A removed line strikes only its value spans, never the whole <td>: an
   inherited strikethrough cannot be turned off by a descendant, so striking the
   cell would drag the .est-row-note in with it. */
.est-current .co-diff-removed td { text-decoration: none; color: var(--ink-3); }
.est-current .co-diff-removed .cv-cell { text-decoration: line-through; }

/* Per-line "why", on its own line under the description. */
.est-row-note {
    display: block;
    width: 100%;
    margin-top: 2px;
    font-size: 11px;
    line-height: 1.4;
    color: var(--ink-3);
}
.est-row-note::before { content: '↳ '; }
/* Keeps "old → new" as one unit when tbl-stack turns the cell into a flex row. */
.cv-delta { white-space: nowrap; }
@media (max-width: 600px) {
    /* tbl-stack makes each cell a flex row, which would sit the note beside the
       description instead of under it. */
    .est-current .cell-title { flex-wrap: wrap; }
}
.est-current .est-co-group > td {
    background: var(--surface-2);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ink-2);
}
.est-co-group-note {
    display: inline-block;
    margin-left: var(--space-2);
    font-weight: 500;
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink-3);
}
.est-current-foot > td {
    font-weight: 700;
    color: var(--ink);
    border-top: 2px solid var(--line);
}
.est-current-foot > td:first-child { color: var(--ink-2); }
.est-current-foot-was { font-weight: 500; color: var(--ink-3); }

/* ---------- Phase completion: preview / stepper / success / sign-offs ------- */

/* Consequence preview (what approving a phase complete will do). */
.consequence-preview { display: grid; gap: 6px; }
.cp-eyebrow {
    font-size: 11px; font-weight: 700; letter-spacing: 0.04em;
    text-transform: uppercase; color: var(--ink-2);
}
.cp-title { font-size: 15px; font-weight: 650; color: var(--ink); }
.cp-list { margin: 4px 0 0; padding-left: 18px; display: grid; gap: 6px; }
.cp-list li { color: var(--ink); line-height: 1.45; }

/* Staged-progress stepper. */
.stepper { list-style: none; margin: 0; padding: 2px 0; display: grid; gap: 2px; }
.stepper-step {
    display: flex; align-items: flex-start; gap: 12px;
    padding: 9px 4px; position: relative;
}
.stepper-step + .stepper-step::before {
    content: ""; position: absolute; left: 12px; top: -8px; height: 12px;
    width: 2px; background: var(--line); transform: translateX(-1px);
}
.stepper-text { flex: 1; min-width: 0; }
.stepper-label { color: var(--ink); font-weight: 550; line-height: 1.3; }
.stepper-step.is-pending .stepper-label { color: var(--ink-2); }
.stepper-err { color: var(--alert); font-size: 12px; margin-top: 3px; line-height: 1.4; }
.stepper-ic {
    flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 13px; font-weight: 700; line-height: 1;
}
.stepper-ic.is-pending { border: 2px solid var(--line); }
.stepper-ic.is-skipped { border: 2px solid var(--line); color: var(--ink-2); }
.stepper-ic.is-done   { background: var(--ok); color: #fff; }
.stepper-ic.is-failed { background: var(--alert); color: #fff; }
.spinner.stepper-spin {
    width: 22px; height: 22px; border-width: 3px;
    border-color: var(--line); border-top-color: var(--ink); flex: 0 0 auto;
}

/* Strong success state after a phase is approved complete. */
.phase-success { text-align: center; display: grid; gap: 10px; padding: 6px 0 2px; }
.ps-check {
    width: 48px; height: 48px; border-radius: 50%; margin: 0 auto;
    background: var(--ok); color: #fff; font-size: 26px; font-weight: 700;
    display: flex; align-items: center; justify-content: center;
}
.ps-headline { font-size: 17px; font-weight: 700; color: var(--ink); }
.ps-facts {
    margin: 6px 0 0; display: grid; gap: 0;
    border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
}
.ps-row {
    display: flex; justify-content: space-between; gap: 12px;
    padding: 9px 14px; text-align: left;
}
.ps-row + .ps-row { border-top: 1px solid var(--line-2); }
.ps-row dt { color: var(--ink-2); font-size: 13px; }
.ps-row dd { margin: 0; color: var(--ink); font-weight: 600; font-size: 13px; }

/* Sign-off history rows. */
.signoff-row { border: 1px solid var(--line); border-radius: var(--radius); padding: 11px 14px; }
.signoff-top { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; flex-wrap: wrap; }
.signoff-phase { font-weight: 650; color: var(--ink); }
.signoff-pills { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
.signoff-evidence { margin-top: 9px; display: flex; gap: 6px; flex-wrap: wrap; }
.signoff-evidence .chip {
    font-size: 11px; padding: 2px 8px; border-radius: 999px;
    background: var(--line-2); color: var(--ink-2);
}
.pill {
    display: inline-block; font-size: 11px; font-weight: 600;
    padding: 2px 9px; border-radius: 999px;
    border: 1px solid var(--line); color: var(--ink-2); white-space: nowrap;
}
.pill.pill-ok    { border-color: var(--ok);    color: var(--ok); }
.pill.pill-warn  { border-color: var(--warn);  color: var(--warn); }
.pill.pill-alert { border-color: var(--alert); color: var(--alert); }
