/* Bizwings Control Centre - plain, framework-free stylesheet.
   "Editorial luxury" restyle (7 Sep 2026) - see HANDOVER.md. Warm off-white
   ground, pure white cards, hairline borders instead of drop shadows, a
   single red accent used only for the primary action / a thin rule under
   titles / active nav / focus / danger, and a self-hosted sans (Manrope)
   for both body and display type. Every existing class name below still
   works - only the token values and a handful of rules changed.
   Manrope + alignment + photography pass (7 Sep 2026): replaced the serif
   (Fraunces) with Manrope everywhere, added shared alignment primitives
   (.form-grid/.form-row, tightened .filter-bar/.ticket-filters/.table),
   and added Pexels photography to login/setup and client-facing pages -
   see the "Manrope, alignment and photography" section of HANDOVER.md. */

@font-face {
    font-family: 'Manrope';
    font-style: normal;
    /* Variable font (wght axis) - one file covers the whole weight range
       used for body and headings, so no extra request per weight. */
    font-weight: 200 800;
    font-display: swap;
    src: url('../fonts/manrope-variable.woff2') format('woff2');
}

:root {
    /* ===================================================================
       Design system tokens (DESIGN_SYSTEM.md v1, 7 Sep 2026) - the single
       source of truth for colour/type/space/shape/elevation across the
       whole app. Every legacy --bw-* name below is now an ALIAS that
       resolves through these, so no consuming rule elsewhere in this file
       had to be rewritten by hand to pick up spec-correct values.
       =================================================================== */

    /* 1. Palette */
    --ink-900: #111111;
    --ink-700: #2B2B2B;
    --ink-500: #6B6560;
    --ink-300: #A8A29B;
    --ground: #F7F5F2;
    --surface: #FFFFFF;
    --surface-2: #F1EEE9;
    --line: #E4DFD8;
    --line-strong: #CFC8BF;
    --brand: #C40000;
    --brand-hover: #A80000;
    --brand-tint: #FBECEC;
    --dark: #15130F;
    --on-dark: #FFFFFF;
    --on-dark-muted: rgba(255, 255, 255, .72);
    --success: #1E6B3A;
    --success-tint: #E7F3EA;
    --warning: #8A5A00;
    --warning-tint: #FBF1DC;
    --danger: var(--brand);
    --danger-tint: var(--brand-tint);
    --info: #2F4A6B;
    --info-tint: #E9EFF6;

    /* 2. Type (Manrope only) */
    --font: 'Manrope', -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
    --text-display-1: 40px;
    --lh-display-1: 1.1;
    --text-display-2: 30px;
    --lh-display-2: 1.15;
    --text-h3: 20px;
    --lh-h3: 1.3;
    --text-card-title: 16px;
    --lh-card-title: 1.3;
    --text-body: 15px;
    --lh-body: 1.55;
    --text-small: 13px;
    --lh-small: 1.5;
    --text-label: 12px;

    /* 3. Space, shape, elevation */
    --space-1: 8px;
    --space-2: 16px;
    --space-3: 24px;
    --space-4: 32px;
    --space-5: 40px;
    --card-padding: 32px;
    --card-padding-mobile: 20px;
    --section-gap: 40px;
    --grid-gap: 24px;
    --radius: 2px;
    --radius-pill: 999px;
    /* Shadows are reserved for floating layers only (menus, modals, the
       mobile sidebar, toasts) - surfaces use a 1px hairline border instead. */
    --shadow-float: 0 12px 32px rgba(21, 19, 15, .12);
    --max-admin: 1120px;
    --max-client: 760px;
    --max-auth-form: 440px;
    --ease: cubic-bezier(.22, .7, .3, 1);
    --fast: 120ms;
    --focus-ring: 0 0 0 3px rgba(196, 0, 0, .2), 0 0 0 1px var(--brand);

    /* --- Legacy --bw-* aliases (pre-dating the design system pass). Every
       rule further down this file still references these names; aliasing
       them onto the tokens above means the whole stylesheet renders with
       spec-correct values with no per-rule rewrite required. --- */
    --bw-navy: var(--ink-900);
    --bw-brand: var(--brand);
    --bw-brand-dark: var(--brand-hover);
    --bw-brand-tint: var(--brand-tint);
    --bw-brand-tint-strong: #F6D6D6;
    --bw-blue: var(--brand);
    --bw-success: var(--success);
    --bw-green: var(--success);
    --bw-success-tint: var(--success-tint);
    --bw-red: var(--brand);
    --bw-danger-tint: var(--brand-tint);
    --bw-amber: var(--warning);
    --bw-warning-tint: var(--warning-tint);
    --bw-info: var(--info);
    --bw-info-tint: var(--info-tint);
    --bw-bg: var(--ground);
    --bw-surface: var(--surface-2);
    --bw-border: var(--line);
    --bw-border-strong: var(--line-strong);
    --bw-ink: var(--ink-900);
    --bw-ink-2: var(--ink-700);
    --bw-text: var(--ink-700);
    --bw-muted: var(--ink-500);

    --font-body: var(--font);
    --font-display: var(--font);
    --bw-font-display: var(--font);
    --bw-text-xs: var(--text-label);
    --bw-text-sm: var(--text-small);
    --bw-text-base: var(--text-body);
    --bw-text-md: var(--text-h3);
    --bw-text-lg: var(--text-display-2);
    --bw-text-xl: var(--text-display-2);

    --bw-radius: var(--radius);
    --bw-radius-sm: var(--radius);
    --bw-radius-pill: var(--radius-pill);
    --bw-shadow: var(--shadow-float);
    --bw-shadow-sm: 0 2px 8px rgba(21, 19, 15, .06);
    --bw-shadow-pop: var(--shadow-float);
    --bw-space-1: var(--space-1);
    --bw-space-2: var(--space-2);
    --bw-space-3: var(--space-3);
    --bw-space-4: var(--space-4);
    --bw-space-5: var(--space-5);
    --bw-ease: var(--ease);
    --bw-fast: var(--fast);
    --bw-sidebar-w: 240px;
    --bw-topbar-h: 64px;
    --bw-content-max: var(--max-admin);
    --bw-focus-ring: var(--focus-ring);
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

/* Defensive fallback so any image not already covered by a more specific
   rule (mobile audit, 13 Sep 2026) can never force a phone to scroll
   sideways. Only caps growth - never shrinks a smaller image - so the
   existing fixed-size logos (.logo, .auth-logo img, etc, all styled with
   higher-specificity class selectors) are completely unaffected. */
img { max-width: 100%; height: auto; }

/* Long unbroken tokens (verification hashes, IDs) must never force the
   page to scroll sideways on a phone (mobile audit, 13 Sep 2026). Applied
   directly to the hash spans on the quote-approval and job-stage-approval
   pages. */
.hash-text { word-break: break-all; overflow-wrap: anywhere; }

body {
    margin: 0;
    font-family: var(--font-body);
    background: var(--bw-bg);
    color: var(--bw-text);
    font-size: var(--bw-text-base);
    font-weight: 400;
    line-height: 1.55;
    text-rendering: optimizeLegibility;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

a { color: var(--bw-blue); }

.col-money, .kpi-number, .money { font-variant-numeric: tabular-nums; }

.container {
    max-width: var(--max-client);
    margin: 0 auto;
    padding: 0 20px;
}

/* Definition-list primitive for client-facing pages (spec 5: "definition
   lists instead of tables"). One row = one <div class="def-row"> with a
   <dt>/<dd> pair; label uses the Label type style, value is body-strength. */
.def-list { margin: 0; }
.def-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 16px;
    padding: 10px 0;
    border-bottom: 1px solid var(--line);
}
.def-row:last-child { border-bottom: none; }
.def-row dt {
    font-size: var(--text-label);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--ink-500);
    margin: 0;
    flex: 0 0 auto;
}
.def-row dd {
    margin: 0;
    text-align: right;
    color: var(--ink-900);
    font-weight: 600;
}
.def-row dd.text-normal { font-weight: 400; color: var(--ink-700); }

/* Clean list of quote/invoice line items (spec 5: "line items as a clean
   list, right-aligned tabular prices") - replaces a literal <table> on
   client-facing quote/statement pages. */
.line-items { list-style: none; margin: 16px 0; padding: 0; }
.line-item {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 16px;
    padding: 12px 0;
    border-bottom: 1px solid var(--line);
}
.line-item:last-child { border-bottom: none; }
.line-item-main { flex: 1 1 auto; min-width: 0; }
.line-item-name { font-weight: 600; color: var(--ink-900); }
.line-item-detail { font-size: var(--text-small); color: var(--ink-500); margin-top: 2px; }
.line-item-price {
    flex: 0 0 auto;
    font-variant-numeric: tabular-nums;
    text-align: right;
    white-space: nowrap;
    font-weight: 600;
    color: var(--ink-900);
}
.line-item-heading { padding-top: 20px; font-weight: 700; color: var(--ink-900); border-bottom: none; }
.line-item-heading:first-child { padding-top: 12px; }
.line-item-note { color: var(--ink-500); font-style: italic; border-bottom: none; padding-bottom: 4px; }

/* Top-of-page loading progress bar. JS toggles .active on submit/navigation
   and .done just before the new page paints. */
.progress-bar {
    position: fixed;
    top: 0; left: 0;
    height: 2px;
    width: 0;
    background: var(--bw-brand);
    z-index: 9999;
    opacity: 0;
    transition: width .25s ease, opacity .2s ease;
}
.progress-bar.active {
    opacity: 1;
    width: 72%;
    transition: width 2.4s cubic-bezier(.1, .6, .2, 1), opacity .2s ease;
}
.progress-bar.done {
    width: 100%;
    opacity: 0;
    transition: width .2s ease, opacity .4s ease .15s;
}

header.site-header {
    background: #ffffff;
    color: var(--bw-ink);
    padding: 12px 0;
    border-bottom: 1px solid var(--bw-border);
}
header.site-header .container {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
}
header.site-header a { color: var(--bw-ink); text-decoration: none; font-weight: 600; }
header.site-header nav a { margin-left: 16px; font-weight: 400; color: var(--bw-muted); transition: color var(--bw-fast) ease; }
header.site-header nav a:hover { color: var(--bw-brand); text-decoration: underline; }
header.site-header .wordmark { display: inline-flex; align-items: center; line-height: 0; }
header.site-header .wordmark .logo { display: block; height: 56px; width: auto; }
header.site-header .client-name { color: var(--bw-muted); font-size: 14px; font-weight: 600; }

main { padding: 30px 0 60px; }

footer.site-footer {
    padding: 20px 0;
    text-align: center;
    color: var(--bw-muted);
    font-size: 13px;
}

h1, h2, h3 { color: var(--bw-navy); line-height: 1.25; font-weight: 700; }
h1, h2 { font-family: var(--font-display); font-weight: 800; letter-spacing: -0.02em; }
h1 { font-size: var(--bw-text-xl); }
h2 { font-size: var(--bw-text-lg); margin-top: 0; }
h3 { font-size: var(--bw-text-md); font-weight: 700; }

/* Display 1 (spec 2.2) - client-page titles on photo bands, dashboard hero
   number. Applied explicitly (not to a bare h1) since the app-wide h1 is
   Display 2 (admin page titles). */
.display-1 { font-size: var(--text-display-1); line-height: var(--lh-display-1); font-weight: 800; letter-spacing: -0.03em; }

/* Card-title style (spec 4 "Cards"): admin card titles use the 16px "Card
   title" size; client-facing pages (everything outside the admin shell,
   flagged by body.admin-body) use the larger 20px "H3" size instead - see
   DESIGN_SYSTEM.md section 2. Both share the same 16px gap to content. */
.card > h2, .card > h3, .card > .card-title,
.card-header h2, .card-header h3, .card-header .card-title {
    font-size: var(--text-card-title);
    font-family: inherit;
    font-weight: 600;
    letter-spacing: 0;
    line-height: var(--lh-card-title);
    margin: 0 0 16px;
}
body:not(.admin-body) .card > h2, body:not(.admin-body) .card > h3, body:not(.admin-body) .card > .card-title,
body:not(.admin-body) .card-header h2, body:not(.admin-body) .card-header h3, body:not(.admin-body) .card-header .card-title {
    font-size: var(--text-h3);
    font-weight: 700;
    line-height: var(--lh-h3);
}

.card {
    background: var(--surface);
    border: 1px solid var(--bw-border);
    border-radius: var(--bw-radius);
    padding: var(--card-padding);
    margin-bottom: 24px;
}
.card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 14px;
}
.card-header h2, .card-header h3, .card-header .card-title { margin: 0; }
.card-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }

table, .table { width: 100%; border-collapse: collapse; font-size: var(--bw-text-sm); }
table th, table td, .table th, .table td {
    text-align: left;
    padding: 12px 16px;
    border-bottom: 1px solid var(--bw-border);
}
table th:first-child, table td:first-child, .table th:first-child, .table td:first-child { padding-left: 0; }
table th:last-child, table td:last-child, .table th:last-child, .table td:last-child { padding-right: 0; }
table th {
    color: var(--bw-muted);
    font-weight: 600;
    font-size: var(--bw-text-xs);
    text-transform: uppercase;
    letter-spacing: .04em;
    vertical-align: middle;
}
table td { vertical-align: middle; }
table tbody tr { transition: background-color var(--bw-fast) ease; }
table tbody tr:hover { background: var(--bw-surface); }
table tbody tr:last-child td { border-bottom: none; }

.badge, .pill {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 9px;
    border-radius: var(--bw-radius-pill);
    font-size: var(--bw-text-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .06em;
    line-height: 1.5;
    color: var(--bw-info);
    background: var(--bw-info-tint);
    white-space: nowrap;
}
/* Semantic pill tones: success / warning / danger / info / neutral. Existing
   badge-* status classes below all map onto these five soft-background tones
   instead of solid colour fills. */
.badge-active, .badge-signed, .badge-approved, .badge-quote-approved,
.badge-invoice-paid, .badge-ticket-closed {
    background: var(--bw-success-tint); color: #0F7A38;
}
.badge-suspended, .badge-partial, .badge-quote-sent, .badge-quote-viewed,
.badge-invoice-open, .badge-priority-normal, .badge-ticket-open,
.pill-warning {
    background: var(--bw-warning-tint); color: #A15C05;
}
.badge-cancelled, .badge-declined, .badge-quote-declined, .badge-quote-expired,
.badge-invoice-overdue, .badge-priority-high, .badge-ticket-awaiting_bizwings {
    background: var(--bw-danger-tint); color: var(--bw-brand-dark);
}
.badge-draft, .badge-sent, .badge-signed_partial, .badge-quote-draft,
.badge-quote-superseded, .badge-invoice-void, .badge-priority-low,
.badge-ticket-awaiting_client {
    background: var(--bw-info-tint); color: var(--bw-info);
}
.badge-ticket-status-priority-low { background: var(--bw-info-tint); color: var(--bw-info); }

.btn, .btn-secondary, .btn-danger, .btn-ghost {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 44px;
    padding: 8px 18px;
    border-radius: var(--bw-radius-sm);
    border: 1px solid var(--bw-brand);
    background: var(--bw-brand);
    color: #fff;
    text-decoration: none;
    font-size: var(--bw-text-base);
    font-weight: 600;
    letter-spacing: .01em;
    cursor: pointer;
    transition: transform var(--bw-fast) ease, box-shadow var(--bw-fast) ease,
        background-color var(--bw-fast) ease, border-color var(--bw-fast) ease,
        opacity var(--bw-fast) ease;
}
.btn:active, .btn-secondary:active, .btn-danger:active, .btn-ghost:active { transform: translateY(1px); box-shadow: none; }
.btn:focus-visible, .btn-secondary:focus-visible, .btn-danger:focus-visible, .btn-ghost:focus-visible,
input:focus-visible, textarea:focus-visible, select:focus-visible {
    outline: none;
    box-shadow: var(--bw-focus-ring);
}

/* Micro-details (DESIGN_SYSTEM.md section 7): "focus ring everywhere via
   :focus-visible" - the rule above only ever covered buttons and form
   controls. This catch-all covers every other focusable element (plain
   links - KPI cards, table row links, nav items, the dashboard's
   attention-list rows - plus anything with an explicit tabindex) with the
   same token, so nothing relies on the browser's default outline. More
   specific rules (e.g. .tab-btn:focus-visible) still take precedence. */
a:focus-visible, summary:focus-visible, [tabindex]:focus-visible {
    outline: none;
    box-shadow: var(--bw-focus-ring);
    border-radius: 2px;
}
.btn:hover { background: var(--bw-brand-dark); border-color: var(--bw-brand-dark); }
.btn-secondary { background: #fff; color: var(--bw-ink); border-color: var(--bw-border-strong); }
.btn-secondary:hover { background: var(--bw-surface); border-color: var(--bw-border-strong); }
.btn-danger { background: var(--bw-red); border-color: var(--bw-red); }
.btn-danger:hover { background: #8f1e18; border-color: #8f1e18; }
.btn-ghost { background: transparent; color: var(--bw-ink); border-color: transparent; }
.btn-ghost:hover { background: var(--bw-surface); }
.btn-sm, .btn-small { padding: 4px 10px; font-size: var(--bw-text-sm); font-weight: 600; min-height: 28px; border-radius: var(--bw-radius-sm); }
.btn-block { display: flex; width: 100%; text-align: center; }
.btn[disabled], .btn-secondary[disabled], .btn-danger[disabled], .btn-ghost[disabled], .btn.is-loading {
    opacity: .55;
    cursor: not-allowed;
    pointer-events: none;
    transform: none;
    box-shadow: none;
}
.btn-spinner {
    display: inline-block;
    width: 14px;
    height: 14px;
    margin-right: 2px;
    border: 2px solid rgba(255, 255, 255, .45);
    border-top-color: #fff;
    border-radius: 50%;
    animation: bw-spin .65s linear infinite;
    flex-shrink: 0;
}
@keyframes bw-spin { to { transform: rotate(360deg); } }

form.inline { display: inline; }

label { display: block; font-weight: 600; margin-bottom: 4px; font-size: var(--bw-text-base); }
input[type=text], input[type=email], input[type=password], input[type=number],
input[type=date], input[type=url], input[type=tel], input[type=search], textarea, select {
    width: 100%;
    min-height: 44px;
    padding: 8px 12px;
    border: 1px solid var(--bw-border);
    border-radius: var(--bw-radius-sm);
    font-size: var(--bw-text-base);
    font-family: inherit;
    color: var(--bw-ink);
    background: var(--surface);
    margin-bottom: 14px;
    transition: border-color var(--bw-fast) ease, box-shadow var(--bw-fast) ease;
}
input:hover, textarea:hover, select:hover { border-color: var(--bw-border-strong); }
input:focus, textarea:focus, select:focus { border-color: var(--ink-900); }
textarea { min-height: 160px; font-family: monospace; }
.field-hint { color: var(--bw-muted); font-size: var(--bw-text-sm); margin-top: -10px; margin-bottom: 14px; }

.alert {
    padding: 10px 14px;
    border-radius: var(--bw-radius-sm);
    margin-bottom: 18px;
    font-size: var(--bw-text-base);
    border: 1px solid transparent;
}
.alert-error { background: var(--bw-danger-tint); color: var(--bw-brand-dark); border-color: #F5C6C6; }
.alert-success { background: var(--bw-success-tint); color: #0F7A38; border-color: #BFE7CD; }
.alert-info { background: var(--bw-surface); color: var(--bw-ink); border-color: var(--bw-border); }

.plan-cards { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 20px; }
.plan-card {
    position: relative;
    flex: 1 1 220px;
    border: 2px solid var(--bw-border);
    border-radius: var(--bw-radius);
    padding: 18px 18px 18px 44px;
    cursor: pointer;
    background: #fff;
    transition: border-color var(--bw-fast) ease, box-shadow var(--bw-fast) ease,
        background var(--bw-fast) ease, transform var(--bw-fast) ease;
}
.plan-card:hover { border-color: #e59a9a; }
.plan-card:focus-within {
    outline: none;
    box-shadow: 0 0 0 3px rgba(218, 0, 0, .3), 0 0 0 1px var(--bw-brand);
}
.plan-card input[type=radio] {
    position: absolute;
    left: 16px;
    top: 20px;
    margin: 0;
    width: 18px;
    height: 18px;
    accent-color: var(--bw-brand);
}
.plan-card .plan-name { font-weight: 700; font-size: 15px; margin-bottom: 2px; }
.plan-card.selected {
    border-color: var(--bw-brand);
    background: #fdeeee;
    box-shadow: 0 0 0 1px var(--bw-brand);
}
.plan-card.selected::after {
    content: "\2713";
    position: absolute;
    top: 12px;
    right: 14px;
    width: 22px;
    height: 22px;
    line-height: 22px;
    text-align: center;
    font-size: 12px;
    font-weight: 700;
    color: #fff;
    background: var(--bw-brand);
    border-radius: 50%;
    animation: bw-pop .22s var(--bw-ease);
}
@keyframes bw-pop { from { transform: scale(0); opacity: 0; } to { transform: scale(1); opacity: 1; } }
.plan-card .price { font-size: 26px; font-weight: 700; color: var(--bw-navy); margin: 4px 0 2px; }
.plan-card .text-muted { font-size: 13px; }

/* Client-facing page hero (sign / quote share / job progress / pay pages):
   a full-bleed 240px (160px mobile) photo band under the white site-header,
   dark gradient overlay, white Display-1 title + a 24px red rule sitting on
   the photo (DESIGN_SYSTEM.md section 4 "Photo band"). Falls back to a
   plain near-black band if a page sets no background image. */
.page-hero {
    position: relative;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
    width: 100vw;
    min-height: 240px;
    display: flex;
    align-items: flex-end;
    background-color: var(--dark);
    background-size: cover;
    background-position: center;
    margin-bottom: 24px;
}
.page-hero::before {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, rgba(21, 19, 15, .25) 0%, rgba(21, 19, 15, .70) 100%);
}
.page-hero-inner { position: relative; z-index: 1; max-width: var(--max-client); width: 100%; margin: 0 auto; padding: 32px 20px 24px; }
.page-hero h1 {
    margin: 0 0 8px;
    color: var(--on-dark);
    font-size: var(--text-display-1);
    line-height: var(--lh-display-1);
    letter-spacing: -0.03em;
    text-shadow: 0 1px 2px rgba(0, 0, 0, .4);
}
.page-hero-meta { color: var(--on-dark-muted); margin: 0 0 16px; font-size: var(--bw-text-base); text-shadow: 0 1px 2px rgba(0, 0, 0, .4); max-width: 640px; }
.page-hero-rule {
    width: 24px;
    height: 2px;
    background: var(--bw-brand);
    border: 0;
    margin: 0;
    padding: 0;
}
@media (max-width: 600px) {
    .page-hero { min-height: 160px; }
    .page-hero-inner { padding: 20px 20px 18px; }
    .page-hero h1 { font-size: 28px; }
}

/* Step indicator (1 Review, 2 Sign, 3 Pay) on the public sign/pay flow -
   shared by the sign page, the done page and the pay page. Bug 3 (7 Sep
   2026): sat tight under the header with unequal top/bottom space. Now
   32px top+bottom margin (24px mobile), centred, 12px between circle and
   label, 40px connector lines. */
.sign-steps {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    list-style: none;
    margin: 32px 0;
    padding: 0;
}
.sign-step { display: flex; align-items: center; color: var(--bw-muted); }
.sign-step:not(:last-child)::after {
    content: "";
    width: 40px;
    height: 2px;
    background: var(--line);
    margin-left: 12px;
}
.sign-step-dot {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: 999px;
    border: 2px solid var(--line-strong);
    font-size: 12px;
    font-weight: 700;
    background: #fff;
    color: var(--bw-muted);
}
.sign-step-label { margin-left: 12px; font-size: 13px; font-weight: 600; }
/* Stepper states (spec 4): done = ink-900 fill + white tick; current =
   brand ring 2px + a small red dot; pending = line-strong ring (the
   default .sign-step-dot styling above). */
.sign-step.is-current .sign-step-dot { position: relative; border-color: var(--brand); background: #fff; color: transparent; }
.sign-step.is-current .sign-step-dot::after { content: ""; position: absolute; width: 8px; height: 8px; border-radius: 50%; background: var(--brand); }
.sign-step.is-current .sign-step-label { color: var(--bw-ink); }
.sign-step.is-done .sign-step-dot { border-color: var(--ink-900); background: var(--ink-900); color: #fff; }
.sign-step.is-done .sign-step-label { color: var(--bw-ink); }
@media (max-width: 480px) {
    .sign-steps { margin: 24px 0; }
    .sign-step-label { display: none; }
    .sign-step:not(:last-child)::after { width: 16px; margin-left: 8px; }
}

/* Agreement viewer: outer wrapper carries the top/bottom fade masks,
   inner .agreement-body is the actual scroll region. */
.agreement-viewer { position: relative; margin-bottom: 8px; }
.agreement-viewer::before, .agreement-viewer::after {
    content: "";
    position: absolute;
    left: 1px; right: 1px;
    height: 28px;
    pointer-events: none;
    z-index: 1;
}
.agreement-viewer::before {
    top: 1px;
    background: linear-gradient(to bottom, #fff, rgba(255, 255, 255, 0));
    border-radius: var(--bw-radius) var(--bw-radius) 0 0;
}
.agreement-viewer::after {
    bottom: 1px;
    background: linear-gradient(to top, #fff, rgba(255, 255, 255, 0));
    border-radius: 0 0 var(--bw-radius) var(--bw-radius);
}
.agreement-body {
    background: #fff;
    border: 1px solid var(--bw-border);
    border-radius: var(--bw-radius);
    padding: 24px 28px;
    max-height: 60vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    font-size: 16px;
    line-height: 1.6;
}
.agreement-body h1, .agreement-body h2, .agreement-body h3 {
    margin-top: 22px;
    margin-bottom: 8px;
    border-left: 3px solid var(--bw-brand);
    padding-left: 10px;
}
.agreement-body h1:first-child, .agreement-body h2:first-child, .agreement-body h3:first-child { margin-top: 0; }
.agreement-body p { margin: 0 0 12px; }
.agreement-body ul, .agreement-body ol { margin: 0 0 12px; padding-left: 22px; }
.agreement-body table { margin: 12px 0 18px; }
.agreement-body table th, .agreement-body table td { padding: 8px; }
.agreement-body table th { background: #f7f8fa; }

.scroll-hint {
    text-align: center;
    color: var(--bw-muted);
    font-size: 12.5px;
    margin: 10px 0 20px;
    transition: opacity .25s ease;
}
.scroll-hint.hidden { opacity: 0; height: 0; margin: 0; overflow: hidden; }

.agree-section {
    background: #f7f8fa;
    border: 1px solid var(--bw-border);
    border-radius: var(--bw-radius);
    padding: 16px 18px;
    margin: 20px 0;
}
.agree-section label { font-weight: 400; }

/* Mobile sticky bar - JS-created (initStickyCta() in app.js), shown once
   the primary submit/pay button scrolls out of view. Desktop never shows
   this (see the max-width:600px block below). Bug 1 (7 Sep 2026,
   mobile): .sign-signature-card used to ALSO be position:sticky at the
   bottom of the viewport, which made this bar permanently inert (the
   primary button never actually left the viewport) and covered most of a
   phone screen with the whole checkbox/name/email/button card while the
   client was still trying to read the agreement above it. The card is a
   normal (non-sticky) card again now - see .sign-signature-card in the
   mobile block below - so this slim bar is the only sticky affordance on
   mobile. Hidden by default here; the mobile media query below turns it on. */
.sticky-cta { display: none; }

/* Bug (5 Sep 2026 walkthrough round 4, Malik: "the margins are not big
   enough it looks unprofessional"): this page overrode .card's own 20px
   padding down to almost nothing (8px top/bottom, 0 horizontal), and had no
   spacing at all between the confirmation text, the buttons and the
   multi-payer payment-shares table. Generous padding + a proper card around
   the shares table now, matching the sign page's treatment. */
.success-state { text-align: center; padding: 40px 40px 36px; }
.success-state h1 { margin-top: 4px; }
.success-share-card {
    text-align: left;
    background: var(--bw-bg);
    border: 1px solid var(--bw-border);
    border-radius: var(--bw-radius);
    padding: 24px;
    margin: 28px 0;
}
.success-share-card > h2 { margin-top: 0; }
.success-share-card table { margin-top: 12px; }
.success-next-steps { text-align: left; margin-top: 32px; }
.success-icon {
    width: 56px;
    height: 56px;
    border-radius: 999px;
    background: #e8f5ea;
    margin: 0 auto 14px;
    display: flex;
    align-items: center;
    justify-content: center;
}
.success-icon svg { width: 32px; height: 32px; overflow: visible; }
.success-icon-circle {
    fill: none;
    stroke: var(--bw-green);
    stroke-width: 2.5;
    stroke-dasharray: 160;
    stroke-dashoffset: 160;
    animation: bw-draw .5s var(--bw-ease) forwards;
}
.success-icon-check {
    fill: none;
    stroke: var(--bw-green);
    stroke-width: 3.5;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: 40;
    stroke-dashoffset: 40;
    animation: bw-draw .3s var(--bw-ease) .45s forwards;
}
@keyframes bw-draw { to { stroke-dashoffset: 0; } }
.success-actions { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; margin: 20px 0; }

.next-steps {
    text-align: left;
    max-width: 460px;
    margin: 24px auto 0;
    padding-left: 20px;
}
.next-steps li { margin-bottom: 8px; }

/* Scroll/first-paint reveal. Stagger via inline style="--i:N" (40ms steps). */
.reveal {
    opacity: 0;
    transform: translateY(14px);
    transition: opacity .5s var(--bw-ease), transform .5s var(--bw-ease);
    transition-delay: calc(var(--i, 0) * 40ms);
}
.reveal.is-visible { opacity: 1; transform: none; }

.text-muted { color: var(--bw-muted); }
.mt-0 { margin-top: 0; }
.flex-between { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px; }
.nowrap { white-space: nowrap; }

@media (max-width: 600px) {
    header.site-header .container { flex-direction: column; align-items: flex-start; }
    .plan-cards { flex-direction: column; }
    .card { padding: var(--card-padding-mobile); }
    .agreement-body { max-height: 50vh; padding: 18px; }
    .success-actions { flex-direction: column; }
    .success-actions .btn, .success-actions .btn-secondary { width: 100%; text-align: center; }
    .success-state { padding: 20px 20px 24px; }
    .success-share-card { padding: 18px; margin: 20px 0; }

    /* Room at the bottom of the page for the sticky CTA bar. */
    .sign-page-padding { padding-bottom: 84px; }

    /* Bug 1 (7 Sep 2026, mobile): this used to be position:sticky/bottom:0
       too, which pinned the whole checkbox/name/email/button card (or, in
       $paymentOnly mode, the per-payer pay-share table) across most of a
       phone screen while the client was still scrolling through the
       agreement text above it - the .sticky-cta bar below could never
       trigger because the real button never left the viewport. The card is
       a normal (non-sticky) card again; .sticky-cta is the only sticky
       affordance on mobile now, and only appears once this card's own
       button/table has scrolled out of view. */
    .sign-signature-card {
        margin-bottom: 0;
    }

    .sticky-cta {
        display: block;
        position: fixed;
        left: 0; right: 0; bottom: 0;
        z-index: 500;
        background: #fff;
        border-top: 1px solid var(--bw-border);
        padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
        box-shadow: 0 -6px 18px rgba(0, 0, 0, .08);
        transform: translateY(100%);
        transition: transform .25s ease;
    }
    .sticky-cta.visible { transform: translateY(0); }
    .sticky-cta .btn { width: 100%; }
}

/* ===================================================================
   Client portal ("My account") - sidebar shell, dashboard cards,
   ticket chat bubbles, and admin mail-status/confirm additions.
   =================================================================== */

.mail-status-bar {
    background: #fff8e6;
    border-top: 1px solid #f0e2b8;
    font-size: 12.5px;
    color: #6b5a1e;
}
.mail-status-bar .container { padding-top: 6px; padding-bottom: 6px; }

/* Ticket/priority/quote/invoice status colours are declared once, near the
   top of this file, as soft-background pill tones (see .badge-active etc). */

.sr-only {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* --- Ticket chat thread --- */
.ticket-thread { display: flex; flex-direction: column; gap: 8px; }
.ticket-bubble-row { display: flex; }
.ticket-bubble-row.them, .ticket-bubble-row.internal { justify-content: flex-start; }
.ticket-bubble-row.me { justify-content: flex-end; }
.ticket-bubble {
    /* Bug: 5 Sep 2026 walkthrough - bubbles looked sparse for short
       messages. Widened from 78% and given a floor so a one-line message
       still reads as a real bubble, not a thin sliver. */
    max-width: 92%;
    min-width: min(220px, 100%);
    width: fit-content;
    border-radius: var(--bw-radius-sm);
    padding: 10px 14px;
    background: var(--bw-surface);
}
.ticket-bubble-row.me .ticket-bubble { background: var(--bw-brand-tint); border: 1px solid var(--bw-brand-tint-strong); }
.ticket-bubble-row.them .ticket-bubble { background: var(--bw-surface); border: 1px solid var(--bw-border); }
.ticket-bubble-row.internal .ticket-bubble { background: var(--bw-warning-tint); border: 1px solid #F3DBAE; }
.ticket-bubble-meta { font-size: 11.5px; color: var(--bw-muted); margin-bottom: 4px; font-weight: 600; }
.ticket-bubble-body { font-size: 14px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; }

/* --- Account portal shell --- */
.acct-body { background: var(--bw-bg); margin: 0; }
.acct-topbar {
    display: none;
    align-items: center;
    justify-content: space-between;
    background: #fff;
    border-bottom: 1px solid var(--bw-border);
    padding: 10px 16px;
}
.acct-topbar .wordmark { line-height: 0; }
.acct-topbar .logo { height: 40px; width: auto; display: block; }
.acct-topbar-title { font-weight: 700; color: var(--bw-ink); font-size: 14px; }
.acct-menu-toggle {
    width: 40px; height: 40px;
    border: 1px solid var(--bw-border);
    border-radius: var(--bw-radius-sm);
    background: #fff;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 4px;
    cursor: pointer;
}
.acct-menu-toggle span { display: block; width: 18px; height: 2px; background: var(--bw-ink); }

.acct-shell { display: flex; min-height: 100vh; }
.acct-sidebar {
    width: 240px;
    flex-shrink: 0;
    background: #fff;
    border-right: 1px solid var(--bw-border);
    display: flex;
    flex-direction: column;
    padding: 24px 18px;
}
.acct-sidebar-logo { display: block; margin-bottom: 28px; line-height: 0; }
.acct-sidebar ul { list-style: none; margin: 0; padding: 0; flex: 1; }
.acct-sidebar li { margin-bottom: 4px; }
.acct-sidebar a {
    display: block;
    padding: 10px 12px;
    border-radius: var(--bw-radius-sm);
    color: var(--bw-muted);
    text-decoration: none;
    font-weight: 600;
    font-size: 14.5px;
    border-left: 2px solid transparent;
    transition: background var(--bw-fast) ease, color var(--bw-fast) ease;
}
.acct-sidebar a:hover { background: var(--bw-surface); color: var(--bw-ink); }
.acct-sidebar a.active { background: transparent; color: var(--bw-ink); border-left-color: var(--bw-brand); margin-left: -2px; padding-left: 12px; }
.acct-sidebar-footer { border-top: 1px solid var(--bw-border); padding-top: 14px; margin-top: 14px; }
.acct-sidebar-client { font-size: 12.5px; color: var(--bw-muted); margin-bottom: 8px; font-weight: 600; }
.acct-sidebar-signout { display: block; font-size: 14px; font-weight: 600; color: var(--bw-red); text-decoration: none; }
.acct-sidebar-signout:hover { text-decoration: underline; }
.acct-sidebar-admin-back { display: block; font-size: 12.5px; font-weight: 600; color: var(--bw-muted); text-decoration: none; margin-bottom: 10px; }
.acct-sidebar-admin-back:hover { color: var(--bw-ink-2); text-decoration: underline; }
.acct-switch-account { margin-bottom: 10px; }
.acct-switch-label { display: block; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: var(--bw-muted); margin-bottom: 4px; }
.acct-switch-form { margin: 0; }
.acct-switch-btn { display: block; width: 100%; text-align: left; background: none; border: 0; padding: 4px 0; font: inherit; font-size: 13px; font-weight: 600; color: var(--bw-ink-2); cursor: pointer; }
.acct-switch-btn:hover { color: var(--bw-brand); text-decoration: underline; }

.acct-main { flex: 1; padding: 32px 36px 60px; max-width: 1040px; }
.acct-header { margin-bottom: 20px; }
.acct-header .badge { margin-left: 8px; vertical-align: middle; }
.acct-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; }
.acct-grid .card:nth-child(3), .acct-grid .card:nth-child(4) { grid-column: 1 / -1; }
.acct-stat { font-size: 30px; font-weight: 700; color: var(--bw-navy); margin: 6px 0 0; }
.acct-ticket-mini-list { list-style: none; margin: 0 0 6px; padding: 0; }
.acct-ticket-mini-list li {
    display: flex; justify-content: space-between; align-items: center;
    padding: 8px 0; border-bottom: 1px solid var(--bw-border);
    gap: 10px;
}
.acct-ticket-mini-list li:last-child { border-bottom: none; }

@media (max-width: 860px) {
    .acct-topbar { display: flex; }
    .acct-shell { flex-direction: column; }
    .acct-sidebar {
        width: auto;
        border-right: none;
        border-bottom: 1px solid var(--bw-border);
        display: none;
    }
    .acct-sidebar.open { display: flex; }
    .acct-sidebar-logo { display: none; }
    .acct-main { padding: 20px 16px 50px; }
    .acct-grid { grid-template-columns: 1fr; }
    .acct-grid .card:nth-child(3), .acct-grid .card:nth-child(4) { grid-column: auto; }
    .ticket-bubble { max-width: 96%; min-width: 0; }
}

@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    .progress-bar, .progress-bar.active, .progress-bar.done { transition: none !important; }
    .reveal { transition: none; opacity: 1; transform: none; }
    .btn, .btn-secondary, .btn-danger, .plan-card, header.site-header nav a, .scroll-hint, .sticky-cta {
        transition: none !important;
    }
    .btn:hover, .btn-secondary:hover, .btn-danger:hover, .plan-card:hover { transform: none; }
    .plan-card.selected::after { animation: none; }
    .success-icon-circle, .success-icon-check { animation: none; stroke-dashoffset: 0; }
}

/* ===================================================================
   Auth pages (client sign-in / sign-in link errors) - calm, minimal,
   centred card on a very light surface with a subtle radial highlight.
   =================================================================== */
body.auth-page {
    background: radial-gradient(circle at 50% -10%, #ffffff 0%, var(--bw-surface) 55%);
    min-height: 100vh;
}
body.auth-page main.container { padding: 0; max-width: none; }

/* Photography pass (7 Sep 2026): split layout on the three login/setup
   pages - a full-height photo left, the existing centred form right.
   Stacks to a 220px photo band on top on mobile. Text on the photo is
   always white with a shadow for contrast; the form pane keeps the plain
   off-white ground it already had. */
.auth-split { display: flex; min-height: 100vh; }
.auth-photo {
    position: relative;
    flex: 0 0 45%;
    min-height: 100vh;
    background-size: cover;
    background-position: center;
    display: flex;
    align-items: flex-end;
    overflow: hidden;
}
.auth-photo::before {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, rgba(21, 19, 15, .35) 0%, rgba(21, 19, 15, .65) 100%);
}
.auth-photo-content { position: relative; z-index: 1; padding: 48px; color: #fff; }
.auth-photo-word {
    font-family: var(--font-display);
    font-weight: 800;
    font-size: 22px;
    letter-spacing: -0.01em;
    margin: 0 0 12px;
    color: #fff;
    text-shadow: 0 1px 2px rgba(0, 0, 0, .4);
}
.auth-photo-content p {
    margin: 0;
    max-width: 320px;
    font-size: 15px;
    color: #fff;
    text-shadow: 0 1px 2px rgba(0, 0, 0, .4);
}
.auth-photo-rule { width: 40px; height: 2px; background: var(--bw-brand); border: 0; margin: 16px 0 0; }
.auth-form-pane { flex: 1; display: flex; min-width: 0; }
.auth-form-pane .auth-shell { flex: 1; }
@media (max-width: 860px) {
    .auth-split { flex-direction: column; min-height: 0; }
    .auth-photo { flex: 0 0 220px; min-height: 220px; }
    .auth-photo-content { padding: 24px; }
    /* Stacked layout: the form sits directly under the photo band instead of
       being vertically centred in the leftover viewport height (which left a
       large empty gap on phones). */
    .auth-form-pane .auth-shell { min-height: 0; justify-content: flex-start; padding: 28px 20px 40px; }
}

.auth-shell {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 40px 20px;
}
.auth-logo { display: block; margin: 0 auto 22px; line-height: 0; }
.auth-logo img { height: 44px; width: auto; display: block; }
.auth-card {
    width: 100%;
    max-width: var(--max-auth-form);
    padding: 36px 32px;
    text-align: center;
}
.auth-title { font-family: var(--bw-font-display); font-size: 28px; font-weight: 600; margin-bottom: 6px; }
.auth-help { font-size: 14px; margin: 0 0 24px; }
.auth-form { text-align: left; }
.field-float { position: relative; margin-bottom: 16px; }
.field-float input {
    height: 48px;
    padding: 20px 14px 6px;
    margin-bottom: 0;
    font-size: 15px;
}
.field-float input:focus {
    outline: none;
    border-color: var(--bw-brand);
    box-shadow: 0 0 0 3px rgba(218, 0, 0, .2);
}
.field-float label {
    position: absolute;
    left: 14px;
    right: 14px;
    top: 15px;
    margin: 0;
    font-size: 15px;
    font-weight: 400;
    color: var(--bw-muted);
    pointer-events: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    transition: top .15s ease, font-size .15s ease, color .15s ease;
    background: transparent;
}
.field-float input:focus + label,
.field-float input:not(:placeholder-shown) + label {
    top: 7px;
    font-size: 11px;
    font-weight: 600;
    color: var(--bw-brand);
}
.auth-card .btn-block { height: 48px; min-height: 48px; font-size: 15px; }
.auth-chooser-list { display: flex; flex-direction: column; gap: 10px; }
.auth-chooser-form { margin: 0; }
.auth-chooser-btn { height: 48px; font-size: 15px; }
.auth-success .success-icon { margin-top: 4px; }
.auth-alt-link {
    display: inline-block;
    margin-top: 18px;
    font-size: 13.5px;
    font-weight: 600;
    color: var(--bw-brand);
    text-decoration: none;
}
.auth-alt-link:hover { text-decoration: underline; }
.auth-footnote {
    max-width: var(--max-auth-form);
    width: 100%;
    text-align: center;
    color: var(--bw-muted);
    font-size: 12.5px;
    margin: 18px 0 6px;
}
.auth-copyright {
    text-align: center;
    color: var(--bw-muted);
    font-size: 11.5px;
    opacity: .75;
    margin: 0;
}

@media (max-width: 480px) {
    .auth-shell { padding: 24px 20px; }
    .auth-card { max-width: none; margin: 0 20px; padding: 28px 20px; width: calc(100% - 40px); }
}

/* ===================================================================
   Ticketing v2 - priority pills, unread dots, attachments, avatars,
   activity timeline, SLA highlighting, admin sidebar layout, dropzone.
   =================================================================== */

.unread-dot {
    display: inline-block;
    width: 8px; height: 8px;
    border-radius: 999px;
    background: var(--bw-brand);
}

.ticket-filter-bar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.ticket-filter-tabs { display: flex; gap: 4px; flex-wrap: wrap; }
.ticket-filter-tab {
    display: inline-block;
    padding: 6px 12px;
    border-radius: 999px;
    font-size: 13px;
    font-weight: 600;
    color: var(--bw-muted);
    text-decoration: none;
    border: 1px solid transparent;
}
.ticket-filter-tab:hover { color: var(--bw-brand); }
.ticket-filter-tab.active { background: #fdeeee; color: var(--bw-brand); border-color: #f3c6c1; }

.ticket-filters { display: flex; flex-direction: column; gap: 12px; }
.ticket-filters-row { display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-end; }
.ticket-filters-row > div { min-width: 140px; display: flex; flex-direction: column; gap: 6px; }
.ticket-filters label { font-size: 12px; margin: 0; }
.ticket-filters select, .ticket-filters input { min-height: 44px; margin-bottom: 0; }
.ticket-filters-row select { width: auto; max-width: 180px; }
/* Was align-items:center, which vertically centred the Filter button
   against the Search field's label+input column instead of foot-aligning
   it - same class of bug as the Quotes "New from template" row. */
.ticket-filters-row-2 { align-items: flex-end; }
.ticket-filters-row-2 input[type=search] { width: auto; max-width: 220px; }
.ticket-filters-row-2 .btn, .ticket-filters-row-2 .btn-secondary { min-height: 44px; }
.ticket-filters-check { display: flex; align-items: center; gap: 6px; font-weight: 400; min-height: 44px; margin: 0; }

.ticket-bulk-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.ticket-bulk-bar[hidden] { display: none; }
.ticket-bulk-selectall { display: flex; align-items: center; gap: 6px; font-weight: 400; font-size: 13px; height: 36px; margin: 0; }
.ticket-bulk-count { font-size: 13px; }
.ticket-bulk-sep { color: var(--bw-muted); }
.ticket-bulk-bar select { width: auto; max-width: 180px; min-height: 36px; margin-bottom: 0; }
.ticket-bulk-bar .btn-small { min-height: 36px; }
@media (max-width: 600px) {
    .ticket-bulk-bar select { max-width: 150px; }
}

.ticket-avatar {
    flex-shrink: 0;
    width: 24px; height: 24px;
    border-radius: 50%;
    background: var(--bw-navy);
    color: #fff;
    font-size: 10.5px;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0 6px;
}
.ticket-bubble-row.me { flex-direction: row-reverse; }
.ticket-bubble-row.them, .ticket-bubble-row.internal { flex-direction: row; }
.ticket-bubble-row.internal .ticket-avatar { background: #a86400; }

.ticket-attachments { margin-top: 10px; display: flex; flex-wrap: wrap; gap: 8px; }
.ticket-attachment-thumb {
    width: 72px; height: 72px;
    object-fit: cover;
    border-radius: var(--bw-radius-sm);
    border: 1px solid var(--bw-border);
    display: block;
}
.ticket-attachment-file {
    display: inline-block;
    padding: 6px 10px;
    border-radius: var(--bw-radius-sm);
    background: #fff;
    border: 1px solid var(--bw-border);
    font-size: 12.5px;
    text-decoration: none;
}

.activity-timeline { list-style: none; margin: 10px 0 0; padding: 0; font-size: var(--bw-text-sm); position: relative; }
.activity-timeline li {
    position: relative;
    padding: 0 0 14px 18px;
    border-bottom: none;
}
.activity-timeline li::before {
    content: "";
    position: absolute;
    left: 0; top: 5px;
    width: 7px; height: 7px;
    border-radius: 50%;
    background: var(--bw-brand);
    box-shadow: 0 0 0 3px var(--bw-brand-tint);
}
.activity-timeline li::after {
    content: "";
    position: absolute;
    left: 3px; top: 13px; bottom: 0;
    width: 1px;
    background: var(--bw-border);
}
.activity-timeline li:last-child::after { display: none; }
.activity-timeline li:last-child { padding-bottom: 0; }

.sla-overdue { color: var(--bw-red); font-weight: 700; }
.sla-amber { color: var(--bw-amber); font-weight: 700; }

.ticket-admin-layout { display: grid; grid-template-columns: 1fr 300px; gap: 20px; align-items: start; }
.ticket-admin-sidebar { position: sticky; top: calc(var(--bw-topbar-h) + 16px); }
.ticket-admin-sidebar .card h3 { margin-bottom: 8px; }

.dropzone {
    border: 2px dashed var(--bw-border);
    border-radius: var(--bw-radius);
    padding: 16px;
    text-align: center;
    margin-bottom: 14px;
    transition: border-color var(--bw-fast) ease, background var(--bw-fast) ease;
}
.dropzone.dragover { border-color: var(--bw-brand); background: #fdeeee; }
.dropzone input[type=file] { margin-bottom: 8px; }

@media (max-width: 860px) {
    .ticket-admin-layout { grid-template-columns: 1fr; }
}

/* ===================================================================
   Quotes - status pills, builder item/schedule tables, client-facing
   quote layout (also used, read-only, by the admin Preview action).
   =================================================================== */

.quote-items-table input[type=text], .quote-items-table input[type=number] { width: 100%; }
.quote-items-table td { vertical-align: top; }
.quote-items-table .col-kind { width: 110px; }
.quote-items-table .col-qty, .quote-items-table .col-price, .quote-items-table .col-total { width: 100px; }
.quote-items-table .col-actions { width: 100px; white-space: nowrap; }
.quote-items-table tr.kind-heading td:not(.col-actions) { background: #f7f8fa; }
.quote-item-total { font-weight: 600; }

.quote-totals { max-width: 340px; margin-left: auto; }
.quote-totals div { display: flex; justify-content: space-between; padding: 4px 0; }
.quote-totals .quote-total-grand { font-weight: 700; font-size: 17px; border-top: 1px solid var(--bw-border); margin-top: 6px; padding-top: 8px; }

.schedule-rows-table input[type=text], .schedule-rows-table input[type=number], .schedule-rows-table select, .schedule-rows-table input[type=date] { width: 100%; }
.schedule-sum-ok { color: var(--bw-green); font-weight: 600; }
.schedule-sum-bad { color: var(--bw-red); font-weight: 600; }

.quote-builder-layout { display: grid; grid-template-columns: 1fr 340px; gap: 20px; align-items: start; }
.quote-builder-side { position: sticky; top: calc(var(--bw-topbar-h) + 16px); }
.quote-builder-side .quote-totals { max-width: none; }
@media (max-width: 960px) {
    .quote-builder-layout { grid-template-columns: 1fr; }
    .quote-builder-side { position: static; }
}

.quote-view-layout { display: grid; grid-template-columns: 1fr 320px; gap: 20px; align-items: start; }
.quote-view-side { position: sticky; top: calc(var(--bw-topbar-h) + 16px); }
@media (max-width: 960px) {
    .quote-view-layout { grid-template-columns: 1fr; }
    .quote-view-side { position: static; }
}

.quote-doc { max-width: 760px; margin: 0 auto; padding: 40px 32px 32px; }
.quote-doc-header { display: flex; justify-content: space-between; align-items: flex-start; border-bottom: 1px solid var(--bw-border); padding-bottom: 20px; margin-bottom: 24px; }
.quote-doc h1 { margin: 0 0 10px; }
.quote-doc h1::after {
    content: "";
    display: block;
    width: 24px;
    height: 2px;
    background: var(--bw-brand);
    margin-top: 10px;
}
.quote-doc-meta { color: var(--bw-muted); font-size: 14px; }
.quote-doc-items { width: 100%; border-collapse: collapse; margin: 16px 0; }
.quote-doc-items th, .quote-doc-items td { text-align: left; padding: 8px 6px; border-bottom: 1px solid var(--bw-border); }
.quote-doc-items tr.kind-heading td { font-weight: 700; border-bottom: none; padding-top: 18px; }
.quote-doc-items tr.kind-note td { color: var(--bw-muted); font-style: italic; border-bottom: none; }
.quote-doc-items .optional-tag { color: var(--bw-muted); font-size: 12px; }
.quote-doc-schedule { margin: 16px 0; }
.quote-doc-approval { margin-top: 24px; padding: 16px; border: 1px solid var(--bw-green); border-radius: var(--bw-radius); background: #f2fbf3; }

/* ===================================================================
   Print stylesheet (spec 7): quote / agreement / statement pages print
   with no nav/buttons/chrome and plain black text on white. Scoped to the
   specific document wrappers each of those three page types uses, so
   printing any other admin/client page is unaffected.
   =================================================================== */
@media print {
    header.site-header, .site-footer, .progress-bar, .mail-status-bar,
    .admin-sidebar, .admin-topbar, .admin-sidebar-backdrop,
    .acct-sidebar, .acct-topbar,
    .page-hero, .sign-steps, .sticky-cta, .toast-stack,
    nav, form, .btn, .btn-secondary, .btn-danger, .btn-ghost,
    .quote-doc-actions, .filter-bar, .statement-quick-ranges,
    .quote-approve-card, .quote-secondary-action, .quote-optional-items .bw-switch {
        display: none !important;
    }
    body, .admin-content, .admin-main, .acct-main { margin: 0 !important; padding: 0 !important; background: #fff !important; }
    body, h1, h2, h3, p, td, th, dt, dd, span { color: #000 !important; text-shadow: none !important; }
    .card, .agreement-body, table, .statement-table { border-color: #ccc !important; box-shadow: none !important; }
    .quote-doc { max-width: none; padding: 0; }
    .agreement-body { max-height: none !important; overflow: visible !important; }
    .agreement-viewer::before, .agreement-viewer::after { display: none !important; }
}

/* ---------------------------------------------------------------------
   Quote client action area (app/templates/quotes/actions.php) - used by
   both /account/quotes/{id} and the public /q/{token} share page.
   --------------------------------------------------------------------- */
.quote-actions { max-width: 760px; margin: 20px auto 40px; }
.quote-actions .card { margin-bottom: 16px; }

.quote-toggle-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 0;
    border-bottom: 1px solid var(--bw-border);
    cursor: pointer;
}
.quote-toggle-row:last-of-type { border-bottom: none; }
.quote-toggle-row > span:first-child { flex: 1 1 auto; }
.quote-toggle-price { color: var(--bw-muted); white-space: nowrap; }

/* Checkbox styled as an iOS-like toggle switch, no external library. */
.bw-switch { position: relative; display: inline-block; width: 44px; height: 26px; flex: 0 0 auto; }
.bw-switch input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; z-index: 1; }
.bw-switch-track {
    position: absolute; inset: 0;
    background: var(--bw-border);
    border-radius: 999px;
    transition: background var(--bw-fast) var(--bw-ease);
}
.bw-switch-track::after {
    content: "";
    position: absolute;
    top: 3px; left: 3px;
    width: 20px; height: 20px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 3px rgba(0,0,0,.3);
    transition: transform var(--bw-fast) var(--bw-ease);
}
.bw-switch input:checked ~ .bw-switch-track { background: var(--bw-success); }
.bw-switch input:checked ~ .bw-switch-track::after { transform: translateX(18px); }
.bw-switch input:focus-visible ~ .bw-switch-track { outline: 2px solid var(--bw-brand); outline-offset: 2px; }

.quote-live-total {
    display: flex;
    justify-content: space-between;
    font-weight: 700;
    font-size: 18px;
    padding-top: 12px;
    margin-top: 4px;
    border-top: 2px solid var(--bw-navy);
}

.quote-terms-details { background: #fff; border: 1px solid var(--bw-border); border-radius: var(--bw-radius); padding: 14px 18px; margin-bottom: 16px; }
.quote-terms-details summary { cursor: pointer; font-weight: 600; }
.quote-terms-body { margin-top: 10px; color: var(--bw-muted); font-size: 14px; }

.quote-secondary-action { background: #fff; border: 1px solid var(--bw-border); border-radius: var(--bw-radius); padding: 14px 18px; margin-bottom: 16px; }
.quote-secondary-action summary { cursor: pointer; font-weight: 600; }
.quote-secondary-action form { margin-top: 12px; }

.quote-agree-row { display: flex; align-items: flex-start; gap: 10px; margin: 12px 0; cursor: pointer; }
.quote-agree-row input { margin-top: 3px; }

.quote-approve-card .btn-block { margin-top: 12px; }

.quote-status-banner { max-width: 760px; margin: 20px auto; }

.quote-approval-block { border: 1px solid var(--bw-green); background: #f2fbf3; }

.quote-approved-check { text-align: center; padding: 24px 0 8px; color: var(--bw-success); }
.quote-approved-check p { color: var(--bw-text); font-weight: 600; margin-top: 8px; }
.quote-check-svg { color: var(--bw-success); }
.quote-check-path {
    stroke-dasharray: 40;
    stroke-dashoffset: 40;
    animation: bw-check-draw .5s var(--bw-ease) .15s forwards;
}
@media (prefers-reduced-motion: reduce) {
    .quote-check-path { animation: none; stroke-dashoffset: 0; }
}
@keyframes bw-check-draw {
    to { stroke-dashoffset: 0; }
}

/* ===================================================================
   Reporting layer - dashboard KPI cards, invoice status pills, the
   invoices/reports/statement pages' shared table + filter conventions.
   =================================================================== */

.kpi-row {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--grid-gap);
    margin-bottom: 24px;
}
.kpi-card {
    display: block;
    background: #fff;
    border: 1px solid var(--bw-border);
    border-radius: var(--bw-radius);
    padding: 18px 20px;
    text-decoration: none;
    color: inherit;
    transition: border-color var(--bw-fast) ease;
}
.kpi-card:hover { border-color: var(--bw-border-strong); }
.kpi-card-alert { border-color: #F5C6C6; background: var(--bw-danger-tint); }
.kpi-label { font-size: var(--bw-text-xs); font-weight: 700; color: var(--bw-muted); text-transform: uppercase; letter-spacing: .06em; }
.kpi-number { font-family: var(--bw-font-display); font-size: 36px; line-height: 1; font-weight: 800; letter-spacing: -0.02em; color: var(--bw-navy); margin: 6px 0 2px; font-variant-numeric: tabular-nums; }
.kpi-sub { font-size: var(--bw-text-sm); color: var(--bw-muted); }
.text-danger { color: var(--bw-brand-dark); font-weight: 600; }

@media (max-width: 900px) {
    .kpi-row { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 560px) {
    .kpi-row { grid-template-columns: 1fr; }
}

/* Primary dashboard KPI row (DESIGN_SYSTEM.md section 6): exactly the 4
   spec cards - Monthly recurring, Outstanding, Overdue, Active jobs. */
.kpi-row-primary { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 900px) { .kpi-row-primary { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .kpi-row-primary { grid-template-columns: 1fr; } }

.kpi-sparkline { display: block; margin-top: 10px; height: 28px; }
.kpi-sparkline svg { display: block; width: 100px; height: 28px; }

/* "Needs attention" list card (DESIGN_SYSTEM.md section 6): icon, text,
   age, link per row - replaces the old aggregate-count banner/pill strip. */
.attention-list { margin: 0; padding: 0; list-style: none; }
.attention-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 0;
    border-bottom: 1px solid var(--line, var(--bw-border));
    text-decoration: none;
    color: inherit;
    transition: background-color var(--fast, var(--bw-fast)) ease;
}
.attention-row:last-child { border-bottom: none; }
.attention-row:hover { background: var(--ground, var(--bw-bg)); }
.attention-row-icon {
    flex: 0 0 auto;
    width: 32px;
    height: 32px;
    border-radius: var(--bw-radius-pill);
    background: var(--brand-tint, var(--bw-danger-tint));
    color: var(--brand, var(--bw-brand));
    display: flex;
    align-items: center;
    justify-content: center;
}
.attention-row-icon svg { width: 16px; height: 16px; }
.attention-row-text { flex: 1; min-width: 0; font-size: var(--bw-text-sm); color: var(--ink-700, var(--bw-ink)); }
.attention-row-age { flex: 0 0 auto; font-size: var(--bw-text-xs); color: var(--ink-500, var(--bw-muted)); white-space: nowrap; }

/* A card containing a table scrolls horizontally within itself on a narrow
   viewport rather than pushing the whole page wide (found during the
   design pass B screenshot check at 390px - the dashboard's 7-column
   Clients table, and every other admin list table, overflowed the
   viewport). Scoped with :has() so cards with no table (KPI cards, forms)
   are never affected. */
.card:has(table) { overflow-x: auto; }

/* Consistency sweep (design pass B): two small utilities replacing repeated
   inline style="" alignment hacks across the admin templates.
   .table-scroll replaces the many hand-written `style="overflow-x:auto;"`
   table wrappers (now largely redundant with .card:has(table) above, but
   kept as an explicit class for tables that aren't inside a .card, e.g.
   client_view.php's payer-edit table). .check-inline replaces the repeated
   `style="display:flex;align-items:center;gap:Npx;font-weight:400;"` used
   on every checkbox+label row throughout the app. */
.table-scroll { overflow-x: auto; }
.check-inline { display: flex; align-items: center; gap: 8px; font-weight: 400; }
/* A plain inline control row (e.g. the reports page's custom date-range
   form sitting next to the period-preset buttons) that wraps onto its own
   line on a narrow viewport instead of overflowing it - lighter than
   .filter-bar/.form-grid, which also apply a surface-panel background and
   are meant for a form's own dedicated filter row, not one inline item
   among several plain buttons. */
.flex-wrap-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
/* Inputs default to width:100% app-wide (see input[type=text] etc. above) -
   inside a plain flex row (not a .form-row column) that forces a single
   field to claim the whole row's width as its flex-basis, wrapping every
   sibling. Direct flex-row children size to their own content instead. */
.flex-wrap-row > input, .flex-wrap-row > select { width: auto; }

/* Right-aligned, tabular-figure money/numeric columns. .num is the general
   primitive name (per the alignment sweep); .col-money is the older name
   used throughout the reporting layer - both do the same thing. */
.col-money, td.col-money, th.col-money, .num, td.num, th.num { text-align: right; }
.col-money, .num { font-variant-numeric: tabular-nums; white-space: nowrap; }

table th.sortable a { color: inherit; text-decoration: none; display: inline-flex; align-items: center; gap: 4px; }
table th.sortable a:hover { text-decoration: underline; }
table th.sortable .sort-arrow { font-size: 10px; color: var(--bw-muted); }

/* ===================================================================
   Shared alignment primitives (7 Sep 2026 alignment pass). One baseline
   system for every "label above control, button at the end" row -
   Quotes' "New from template" row, every list page's filter bar, the
   ticket filters. A .form-row is one label+control column; .form-grid
   lays a run of them out with a trailing button on the same baseline.
   Every input/select/button inside either gets forced to the same 44px
   height with no stray bottom margin, so the button never floats lower
   than the fields next to it (the bug Malik flagged on Quotes). .filter-bar
   is the same primitive, pre-styled with the muted panel background list
   pages already used it for.
   =================================================================== */
.form-grid, .filter-bar {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    align-items: flex-end;
}
.form-row { display: flex; flex-direction: column; gap: 6px; margin: 0; min-width: 0; }
.form-row > label,
.filter-bar > label, .filter-bar label {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin: 0;
    font-size: 12px;
    font-weight: 600;
    color: var(--bw-muted);
    text-transform: uppercase;
    letter-spacing: .06em;
}
.form-grid input, .form-grid select, .form-row input, .form-row select,
.filter-bar input, .filter-bar select {
    min-height: 44px;
    margin-bottom: 0;
    background: #fff;
}
.filter-bar .filter-actions {
    display: flex;
    gap: 8px;
    align-items: center;
}
.form-grid .btn, .form-grid .btn-secondary, .form-grid .btn-danger, .form-grid .btn-ghost,
.filter-bar .btn, .filter-bar .btn-secondary, .filter-bar .btn-danger, .filter-bar .btn-ghost,
.filter-bar .btn-sm, .filter-bar .btn-small {
    min-height: 44px;
    padding: 8px 18px;
    font-size: var(--bw-text-base);
}
.filter-bar {
    margin-bottom: 16px;
    padding: 12px;
    background: var(--bw-surface);
    border: 1px solid var(--bw-border);
    border-radius: var(--bw-radius-sm);
}

.statement-quick-ranges { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.statement-quick-ranges .btn-ghost { border: 1px solid var(--bw-border); }
.statement-quick-ranges .btn-ghost.is-active { background: var(--bw-brand-tint); border-color: var(--bw-brand); color: var(--bw-brand); }

.totals-row td { font-weight: 700; border-top: 2px solid var(--bw-border-strong); }

.empty-state {
    padding: 56px 24px;
    text-align: center;
    color: var(--bw-muted);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
}
.empty-state-icon {
    width: 48px; height: 48px;
    display: flex; align-items: center; justify-content: center;
    border-radius: var(--bw-radius-pill);
    background: var(--bw-surface);
    color: var(--ink-300);
}
.empty-state-icon svg { width: 24px; height: 24px; }
.empty-state-title { font-weight: 600; color: var(--bw-ink); font-size: var(--bw-text-base); }
.empty-state-action { margin-top: 4px; }

.statement-header { display: flex; justify-content: space-between; gap: 24px; flex-wrap: wrap; margin-bottom: 20px; }
.statement-header h1 { margin-bottom: 4px; }
.statement-bank-details { font-size: 13px; color: var(--bw-muted); }
.statement-table tfoot td { font-weight: 700; border-top: 2px solid var(--bw-navy); }

.report-section { margin-bottom: 28px; }
.report-chart { width: 100%; height: auto; }
.report-chart rect { transition: opacity var(--bw-fast) ease; }
.report-chart rect:hover { opacity: .8; }
.period-selector { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 20px; align-items: center; }
.period-selector a.active { background: var(--bw-brand); color: #fff; }

/* ===================================================================
   Admin shell - fixed 240px left sidebar + 64px top bar. Replaces the
   old top-nav header for every authenticated admin page. Only markup
   inside app/templates/layout/base.php's admin branch uses these.
   =================================================================== */

.admin-shell { display: flex; min-height: 100vh; background: var(--bw-bg); }

.admin-sidebar {
    width: var(--bw-sidebar-w);
    flex-shrink: 0;
    background: var(--bw-bg);
    border-right: 1px solid var(--bw-border);
    display: flex;
    flex-direction: column;
    position: fixed;
    top: 0; bottom: 0; left: 0;
    z-index: 40;
}
.admin-sidebar-logo {
    display: flex;
    align-items: center;
    padding: 18px 20px;
    line-height: 0;
    border-bottom: 1px solid var(--bw-border);
    height: var(--bw-topbar-h);
}
.admin-sidebar-logo img { height: 32px; width: auto; display: block; }

.admin-nav { list-style: none; margin: 0; padding: 12px; flex: 1; overflow-y: auto; }
.admin-nav li { margin-bottom: 2px; }
.admin-nav a {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    border-radius: var(--bw-radius-sm);
    color: var(--bw-muted);
    text-decoration: none;
    font-weight: 500;
    font-size: var(--bw-text-base);
    border-left: 2px solid transparent;
    transition: background-color var(--bw-fast) ease, color var(--bw-fast) ease;
}
.admin-nav a svg { flex-shrink: 0; width: 16px; height: 16px; }
.admin-nav a:hover { background: #fff; color: var(--bw-ink); }
.admin-nav a.active {
    background: #fff;
    color: var(--bw-ink);
    font-weight: 600;
    border-left-color: var(--bw-brand);
    margin-left: -2px;
    padding-left: 12px;
}
.admin-nav-badge {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: var(--bw-radius-pill);
    background: var(--bw-brand);
    color: #fff;
    font-size: 10.5px;
    font-weight: 700;
}

.admin-sidebar-footer {
    border-top: 1px solid var(--bw-border);
    padding: 12px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.admin-mail-pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    align-self: flex-start;
    padding: 2px 9px;
    border-radius: var(--bw-radius-pill);
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .03em;
    text-transform: uppercase;
}
.admin-mail-pill::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
/* LIVE is red, not green - real emails/SMS reach real clients in this
   state, which is the one Malik (a novice) needs to notice, not feel
   reassured by. SAFE MODE amber, ALLOWLIST blue-grey, per HANDOVER.md
   Part C section 7 "Email safe mode". */
.admin-mail-pill.mail-live { background: var(--bw-danger-tint); color: var(--bw-brand-dark); }
.admin-mail-pill.mail-safe { background: var(--bw-warning-tint); color: #A15C05; }
.admin-mail-pill.mail-allowlist { background: var(--bw-info-tint); color: var(--bw-info); }
/* SMS status pill (see layout/base.php's $smsMode) reuses the same
   mail-* colour tokens for "live/safe/allowlist"; "not configured" gets its
   own neutral grey rather than the amber "safe" tone, since it isn't a
   safety-net outcome - it's simply nothing set up yet. */
.admin-mail-pill.mail-not_configured { background: var(--bw-border); color: var(--bw-muted); }
/* Stripe TEST/LIVE pill (layout/base.php's $stripeBadgeMode) - deliberately
   its own class names, never mail-test/mail-live, so this stylesheet can't
   accidentally end up coupling the Stripe key badge to the email
   safe-mode one (see HANDOVER.md Part C section 7 on why they must stay
   visually and semantically separate). Stripe's own TEST/LIVE pill keeps
   its original amber/green treatment - only the EMAIL safe-mode "LIVE"
   pill above is red, since that is the one signalling real clients get
   real mail. */
.admin-mail-pill.stripe-test { background: var(--bw-warning-tint); color: #A15C05; }
.admin-mail-pill.stripe-live { background: var(--bw-success-tint); color: #0F7A38; }
.admin-sidebar-user { font-size: var(--bw-text-sm); color: var(--bw-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.admin-sidebar-signout {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--bw-text-sm);
    font-weight: 600;
    color: var(--bw-muted);
    text-decoration: none;
}
.admin-sidebar-signout:hover { color: var(--bw-brand); }

.admin-content { flex: 1; margin-left: var(--bw-sidebar-w); min-width: 0; }

.admin-topbar {
    height: var(--bw-topbar-h);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 0 32px;
    background: #fff;
    border-bottom: 1px solid var(--bw-border);
    position: sticky;
    top: 0;
    z-index: 30;
}
.admin-topbar-titles { min-width: 0; }
.admin-topbar-breadcrumb {
    font-size: var(--bw-text-xs);
    color: var(--bw-muted);
    margin: 0 0 2px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.admin-topbar-breadcrumb a { color: var(--bw-muted); text-decoration: none; }
.admin-topbar-breadcrumb a:hover { color: var(--bw-brand); text-decoration: underline; }
.admin-topbar-breadcrumb .sep { margin: 0 5px; }
.admin-topbar-title {
    margin: 0;
    font-size: var(--bw-text-lg);
    font-weight: 600;
    color: var(--bw-ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.admin-topbar-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }

.admin-menu-toggle {
    display: none;
    width: 36px; height: 36px;
    border: 1px solid var(--bw-border);
    border-radius: var(--bw-radius-sm);
    background: #fff;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    flex-shrink: 0;
}
.admin-menu-toggle svg { width: 18px; height: 18px; }

.admin-main {
    max-width: var(--bw-content-max);
    margin: 0 auto;
    padding: 32px;
}

.admin-sidebar-backdrop { display: none; }

@media (max-width: 960px) {
    .admin-sidebar {
        transform: translateX(-100%);
        transition: transform var(--bw-fast) var(--bw-ease);
        box-shadow: var(--bw-shadow-pop);
    }
    .admin-sidebar.is-open { transform: translateX(0); }
    .admin-content { margin-left: 0; }
    .admin-menu-toggle { display: flex; }
    .admin-topbar {
        padding: 10px 16px;
        height: auto;
        flex-wrap: wrap;
        row-gap: 8px;
    }
    .admin-topbar-titles { flex: 1 1 auto; min-width: 0; }
    .admin-topbar-actions { flex: 1 1 100%; justify-content: flex-start; flex-wrap: wrap; }
    .admin-main { padding: 20px 16px 48px; }
    .admin-sidebar-backdrop {
        display: block;
        position: fixed;
        inset: 0;
        background: rgba(17, 17, 17, .35);
        z-index: 39;
        opacity: 0;
        pointer-events: none;
        transition: opacity var(--bw-fast) ease;
    }
    .admin-sidebar-backdrop.is-open { opacity: 1; pointer-events: auto; }
}

/* Breadcrumb used stand-alone (rare - most pages use the topbar's). */
.breadcrumb { font-size: var(--bw-text-xs); color: var(--bw-muted); margin: 0 0 8px; }
.breadcrumb a { color: var(--bw-muted); text-decoration: none; }
.breadcrumb a:hover { color: var(--bw-brand); }

/* ===================================================================
   Tabs - used by the client view (Overview / Payers & plans /
   Agreement / Invoices & statement / Tickets / Notes) and anywhere
   else a page is split into sections. JS (app.js) toggles [hidden] on
   .tab-panel and syncs the URL hash; CSS alone renders the strip.
   =================================================================== */

.tabs {
    display: flex;
    gap: 4px;
    border-bottom: 1px solid var(--bw-border);
    margin-bottom: 20px;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}
.tab-btn {
    appearance: none;
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    padding: 10px 4px;
    margin: 0 12px 0 0;
    font: inherit;
    font-size: var(--bw-text-base);
    font-weight: 600;
    color: var(--bw-muted);
    cursor: pointer;
    white-space: nowrap;
    transition: color var(--bw-fast) ease, border-color var(--bw-fast) ease;
}
.tab-btn:hover { color: var(--bw-ink); }
.tab-btn.active { color: var(--bw-brand); border-bottom-color: var(--bw-brand); }
.tab-btn:focus-visible { outline: none; box-shadow: var(--bw-focus-ring); border-radius: 4px; }
.tab-panel[hidden] { display: none; }

/* ===================================================================
   Toasts - flash messages, top-right, auto-dismiss. role="status" is
   set in markup; app.js removes each toast after its timeout or on
   click of the dismiss control.
   =================================================================== */

/* Flash messages render in-flow at the top of the content area, per
   DESIGN_SYSTEM.md section 7 ("top of content, hairline, coloured left
   border 3px, auto-dismiss not required") - previously a fixed top-right
   overlay; the id/class/dismiss-button markup and app.js's initToasts()
   wiring are unchanged, only the positioning. */
.toast-stack {
    margin-bottom: 20px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.toast {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    background: #fff;
    border: 1px solid var(--bw-border);
    border-left: 3px solid var(--bw-info);
    border-radius: var(--bw-radius-sm);
    box-shadow: var(--bw-shadow-pop);
    padding: 12px 14px;
    font-size: var(--bw-text-base);
    animation: bw-toast-in .2s var(--bw-ease);
}
.toast-success { border-left-color: var(--bw-success); }
.toast-error { border-left-color: var(--bw-red); }
.toast-info { border-left-color: var(--bw-info); }
.toast-body { flex: 1; }
.toast-close {
    appearance: none;
    background: none;
    border: none;
    color: var(--bw-muted);
    cursor: pointer;
    font-size: 14px;
    line-height: 1;
    padding: 2px;
}
.toast-close:hover { color: var(--bw-ink); }
@keyframes bw-toast-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }

/* ===================================================================
   Confirm panel - "confirm before sending" rendered as a modal-style
   card (app/templates/admin/confirm_send.php), not a full modal
   overlay (it's a real page, no JS dependency to work).
   =================================================================== */
.confirm-panel { max-width: 520px; margin: 40px auto; }
.confirm-panel .card { box-shadow: var(--bw-shadow-pop); }
.confirm-panel-icon {
    width: 40px; height: 40px;
    border-radius: var(--bw-radius-pill);
    background: var(--bw-warning-tint);
    color: #A15C05;
    display: flex; align-items: center; justify-content: center;
    margin-bottom: 12px;
}
.confirm-recipients { background: var(--bw-surface); border-radius: var(--bw-radius-sm); padding: 10px 12px; margin: 12px 0; font-size: var(--bw-text-sm); }
.confirm-actions { display: flex; gap: 10px; margin-top: 16px; }

/* ===================================================================
   Definition list - compact label/value pairs (client contact block,
   quote/ticket properties sidebars).
   =================================================================== */
.def-list { margin: 0; }
.def-list .def-row {
    display: flex;
    gap: 12px;
    padding: 7px 0;
    border-bottom: 1px solid var(--bw-border);
    font-size: var(--bw-text-sm);
}
.def-list .def-row:last-child { border-bottom: none; }
.def-list dt { flex: 0 0 40%; color: var(--bw-muted); font-weight: 500; margin: 0; }
.def-list dd { flex: 1; margin: 0; color: var(--bw-ink); text-align: right; }

/* ===================================================================
   Settings sub-nav - left-hand list of sections inside the Settings
   page (Mail / Billing / Price book / Security, etc.).
   =================================================================== */
.settings-layout { display: grid; grid-template-columns: 200px 1fr; gap: 24px; align-items: start; }
.settings-subnav { list-style: none; margin: 0; padding: 0; position: sticky; top: calc(var(--bw-topbar-h) + 16px); }
.settings-subnav li { margin-bottom: 2px; }
.settings-subnav a {
    display: block;
    padding: 7px 10px;
    border-radius: var(--bw-radius-sm);
    color: var(--bw-muted);
    text-decoration: none;
    font-size: var(--bw-text-sm);
    font-weight: 600;
}
.settings-subnav a:hover { background: var(--bw-surface); color: var(--bw-ink); }
.settings-subnav a.active { background: var(--bw-brand-tint); color: var(--bw-brand-dark); }
@media (max-width: 760px) {
    .settings-layout { grid-template-columns: 1fr; }
    .settings-subnav { position: static; display: flex; gap: 4px; overflow-x: auto; padding-bottom: 4px; }
}

/* Section header with actions on the right - generic utility used
   throughout (mirrors .card-header but for non-card contexts). */
.section-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 14px;
    flex-wrap: wrap;
}
.section-header h1, .section-header h2 { margin: 0; }

/* Slim "needs attention" banner (dashboard). */
.attention-banner {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    padding: 10px 16px;
    margin-bottom: 20px;
    background: var(--bw-warning-tint);
    border: 1px solid #F3DBAE;
    border-radius: var(--bw-radius-sm);
    font-size: var(--bw-text-sm);
}
.attention-banner strong { color: #A15C05; }
.attention-banner a.pill { text-decoration: none; }

@media (prefers-reduced-motion: reduce) {
    .admin-sidebar, .admin-sidebar-backdrop, .tab-btn, .toast, .kpi-card, table tbody tr { transition: none !important; }
    .toast { animation: none; }
}

/* ===== Jobs ===== */

/* Job/stage status badges not already covered by the generic badge-active/
   badge-suspended/badge-cancelled/badge-draft palette (see the .badge-*
   rules near the top of this file) - aliased onto the same colour tokens
   rather than introducing new ones, so status colouring stays consistent
   across invoices/quotes/tickets/jobs. */
.badge-waiting_on_client, .badge-in_progress, .badge-awaiting_approval { background: var(--bw-warning-tint); color: #A15C05; }
.badge-on_hold { background: var(--bw-info-tint); color: var(--bw-info); }
.badge-complete, .badge-done, .badge-answered { background: var(--bw-success-tint); color: #0F7A38; }
.badge-skipped { background: var(--bw-border); color: var(--bw-muted); }
.badge-pending { background: var(--bw-info-tint); color: var(--bw-info); }
.badge-approved { background: var(--bw-success-tint); color: #0F7A38; }

/* Overall job progress bar (admin job page + client progress page). */
.job-progress-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.job-progress-bar {
    height: 10px;
    border-radius: var(--bw-radius-pill);
    background: var(--bw-border);
    overflow: hidden;
    margin: 10px 0;
}
.job-progress-bar-fill {
    height: 100%;
    background: var(--bw-brand);
    border-radius: var(--bw-radius-pill);
    transition: width 300ms var(--bw-ease);
}

/* Client-facing stage bar - the N-stage equivalent of .sign-steps. */
.job-stagebar { list-style: none; margin: 16px 0 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.job-stagebar-step {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border: 1px solid var(--bw-border);
    border-radius: var(--bw-radius-sm);
    background: var(--bw-surface);
}
.job-stagebar-step.is-done { border-color: var(--line-strong); background: var(--surface-2); }
.job-stagebar-step.is-current { border-color: var(--bw-brand); box-shadow: 0 0 0 1px var(--bw-brand); }
.job-stagebar-step.is-skipped { opacity: .55; }
/* Stepper dots (spec 4, "Stepper (client + admin): 28px circles; done =
   ink-900 fill white tick; current = brand ring 2px + red dot; pending =
   line-strong ring"). */
.job-stagebar-dot {
    position: relative;
    width: 28px; height: 28px; min-width: 28px;
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: #fff;
    border: 2px solid var(--line-strong);
    color: var(--bw-navy);
    font-size: var(--bw-text-xs);
    font-weight: 700;
    box-sizing: border-box;
}
.job-stagebar-step.is-done .job-stagebar-dot { background: var(--ink-900); border-color: var(--ink-900); color: #fff; }
.job-stagebar-step.is-current .job-stagebar-dot { border-color: var(--bw-brand); background: #fff; color: transparent; }
.job-stagebar-step.is-current .job-stagebar-dot::after { content: ""; position: absolute; width: 9px; height: 9px; border-radius: 50%; background: var(--bw-brand); }
.job-stagebar-label { flex: 1; font-weight: 600; }
.job-stagebar-date { font-size: var(--bw-text-sm); color: var(--bw-muted); }

/* Timeline (admin + client progress page). */
.job-timeline { display: flex; flex-direction: column; gap: 14px; }
.job-timeline-item { border-left: 3px solid var(--bw-border); padding-left: 12px; }
.job-timeline-item.job-timeline-client { border-left-color: var(--bw-brand); }
.job-timeline-item.job-timeline-system { border-left-color: var(--bw-info); }
.job-timeline-meta { display: flex; align-items: center; gap: 8px; font-size: var(--bw-text-sm); margin-bottom: 2px; }
.job-timeline-body { white-space: pre-line; }
.job-file-thumb { width: 96px; height: 96px; object-fit: cover; border-radius: var(--bw-radius-sm); border: 1px solid var(--bw-border); margin: 6px 6px 0 0; }
.job-file-link { display: inline-block; margin-top: 6px; }
.job-deliverables { margin: 0; padding-left: 20px; }

.notify-tick { font-size: var(--bw-text-xs); color: var(--bw-muted); display: inline-flex; align-items: center; gap: 4px; margin-right: 6px; }
.notify-tick input { width: auto; }

/* Star rating - pure CSS (radio inputs laid out right-to-left so the
   "checked ~ following siblings" selector highlights every star up to and
   including the clicked one, hovered via :hover ~). No JS required. */
.job-star-rating { display: flex; flex-direction: row-reverse; justify-content: flex-end; gap: 4px; font-size: 28px; margin: 8px 0; }
.job-star-rating input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.job-star-rating label { cursor: pointer; color: var(--bw-border-strong); line-height: 1; }
.job-star-rating input:checked ~ label,
.job-star-rating input:hover ~ label,
.job-star-rating label:hover,
.job-star-rating label:hover ~ label { color: var(--bw-amber); }

/* Repeatable stage-template row editor (Settings > Stage templates). */
.stage-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; padding: 8px 0; border-bottom: 1px dashed var(--bw-border); }
.stage-row input[type="text"] { width: auto; flex: 1 1 160px; }
.stage-row input[type="number"] { width: 80px; }

/* ===================================================================
   Jobs v2 redesign (JOBS_SPEC_V2.md) - 6 Sep 2026. 8px spacing scale,
   one primary action per screen, no horizontal scroll at 375px, tap
   targets >= 44px. Reuses .card/.badge-*/.btn/.tabs from above.
   =================================================================== */

.job-dismissible-panel { margin-bottom: 16px; }
.job-howto-panel ul { margin: 8px 0; padding-left: 20px; }
.job-howto-panel li { margin-bottom: 4px; }

.job-header-strip { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: flex-start; gap: 12px; margin-bottom: 8px; }
.job-header-strip-badges { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }

.job-current-stage-card .btn-block { margin-top: 8px; }
.job-secondary-links { display: flex; flex-wrap: wrap; gap: 16px; margin-top: 12px; font-size: var(--bw-text-sm); }
.job-secondary-links a, .job-secondary-links .link-btn { color: var(--bw-brand); text-decoration: none; }
.job-secondary-links a:hover, .job-secondary-links .link-btn:hover { text-decoration: underline; }
.link-btn { background: none; border: none; padding: 0; font: inherit; cursor: pointer; color: var(--bw-brand); }

.job-waiting-panel { margin-top: 16px; }
.job-waiting-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.job-waiting-list li { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 8px 0; border-bottom: 1px solid var(--bw-border); }
.job-waiting-list li:last-child { border-bottom: none; }

.job-quick-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 16px 0; }
.job-quick-actions .btn, .job-quick-actions .btn-small { min-height: 44px; display: inline-flex; align-items: center; }

.job-two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin: 16px 0; }
@media (max-width: 700px) {
    .job-two-col { grid-template-columns: 1fr; }
}
.job-upnext-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }

.btn-block { width: 100%; display: block; text-align: center; min-height: 48px; line-height: 1; box-sizing: border-box; }

/* Stages tab: vertical stepper (replaces the old table). */
.job-stepper { list-style: none; margin: 16px 0; padding: 0; position: relative; }
.job-stepper-item { display: flex; gap: 12px; align-items: flex-start; padding-bottom: 16px; position: relative; }
.job-stepper-item:not(:last-child)::before {
    content: ""; position: absolute; left: 13px; top: 28px; bottom: -4px; width: 2px; background: var(--line);
}
.job-stepper-circle {
    position: relative;
    flex: none; width: 28px; height: 28px; border-radius: 50%; box-sizing: border-box; display: flex; align-items: center; justify-content: center;
    font-size: var(--bw-text-sm); font-weight: 600; background: #fff; border: 2px solid var(--line-strong); color: var(--bw-muted); z-index: 1;
}
.job-stepper-item.is-current .job-stepper-circle { border-color: var(--bw-brand); background: #fff; color: transparent; }
.job-stepper-item.is-current .job-stepper-circle::after { content: ""; position: absolute; width: 9px; height: 9px; border-radius: 50%; background: var(--bw-brand); }
.job-stepper-item.is-done .job-stepper-circle { background: var(--ink-900); border-color: var(--ink-900); color: #fff; }
.job-stepper-item.is-skipped .job-stepper-circle { background: #fff; color: var(--bw-muted); border: 2px dashed var(--bw-border-strong); }
.job-stepper-card { flex: 1; min-width: 0; }
.job-stepper-card .card-header { flex-wrap: wrap; gap: 8px; }
.job-stepper-card .btn-block { max-width: 320px; }

/* "..." per-stage menu: a native <details>/<summary> so it works with no
   JS at all (JOBS_SPEC_V2.md section 1) - CSS only styles it here. */
.job-stage-menu { margin-top: 12px; }
.job-stage-menu > summary {
    display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px;
    border-radius: var(--bw-radius-sm); border: 1px solid var(--bw-border); cursor: pointer; list-style: none; font-size: 20px;
}
.job-stage-menu > summary::-webkit-details-marker { display: none; }
.job-stage-menu-body { margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--bw-border); display: flex; flex-direction: column; gap: 10px; }
.job-stage-menu-body label { display: block; font-size: var(--bw-text-sm); color: var(--bw-muted); margin-top: 6px; }
.job-stage-menu-actions { display: flex; flex-wrap: wrap; gap: 8px; }
/* Tap targets: every button/link a Jobs admin/client screen exposes must be
   >=44px tall (JOBS_SPEC_V2.md section 5) - .btn-small elsewhere in the app
   (dense admin lists) intentionally stays at 28px, so these are scoped to
   the Jobs "..." menu, the dismissible howto panel, and standalone text
   links rather than changed globally. */
.job-stage-menu-body .btn-small, .job-stage-menu-actions .btn-small, .job-howto-panel .btn-small {
    min-height: 44px; display: inline-flex; align-items: center; justify-content: center;
}
.job-secondary-links a, .job-secondary-links .link-btn, .job-stepper-card .link-btn {
    min-height: 44px; display: inline-flex; align-items: center;
}

.job-add-stage-row > summary {
    cursor: pointer; padding: 12px 16px; border: 1px dashed var(--bw-border-strong); border-radius: var(--bw-radius-sm);
    color: var(--bw-brand); font-weight: 600; list-style: none; min-height: 44px; display: flex; align-items: center;
}
.job-add-stage-row > summary::-webkit-details-marker { display: none; }
.job-add-stage-row .card { margin-top: 12px; }
.job-add-stage-row .btn-small { min-height: 44px; }
/* Every action button on the admin job page (Overview/Stages/Requests/
   Timeline/Files/Settings tabs) is a standalone tap target, not a dense
   list row, so all of them meet the 44px minimum (JOBS_SPEC_V2.md
   section 5) rather than the app-wide 28px .btn-small default. */
[data-tab-group="job"] .btn-small, .card-actions .btn-small {
    min-height: 44px; display: inline-flex; align-items: center; justify-content: center;
}

.job-danger-card { border-color: var(--bw-danger-tint); }

/* Requests tab: grouped rows (replaces the old table). */
.job-request-rows { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
.job-request-row { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 8px; padding: 10px 0; border-bottom: 1px solid var(--bw-border); }
.job-request-answer-preview { margin: 4px 0 0; }

/* Timeline tab: avatar initial + filter chips. */
.job-avatar-initial {
    display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: 50%;
    background: var(--bw-brand-tint); color: var(--bw-brand-dark); font-size: var(--bw-text-xs); font-weight: 700;
}
.job-filter-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.chip {
    border: 1px solid var(--bw-border); background: #fff; border-radius: var(--bw-radius-pill); padding: 6px 14px;
    font-size: var(--bw-text-sm); cursor: pointer; min-height: 32px;
}
.chip.active { background: var(--bw-brand); border-color: var(--bw-brand); color: #fff; }

/* Ask-the-client template picker. */
.request-template-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; margin-top: 12px; }
.request-template-card {
    display: flex; flex-direction: column; gap: 4px; padding: 16px; border: 1px solid var(--bw-border); border-radius: var(--bw-radius-sm);
    text-decoration: none; color: var(--bw-ink); min-height: 44px; transition: border-color .15s ease, box-shadow .15s ease;
}
.request-template-card:hover { border-color: var(--bw-brand); }
.request-template-icon { font-size: 24px; }
.request-template-name { font-weight: 600; }
.request-template-desc { font-size: var(--bw-text-sm); color: var(--bw-muted); }
.request-question-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; padding: 6px 0; border-bottom: 1px dashed var(--bw-border); }
.request-question-row input[type="text"] { width: auto; flex: 1 1 200px; }
.job-request-submit { max-width: 320px; margin-top: 8px; }

/* Client progress page: "we need something from you" card and photo grid. */
.job-needs-something { border-left: 3px solid var(--bw-brand); }
.job-need-row { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 8px; padding: 8px 0; border-bottom: 1px solid var(--bw-border); }
.job-need-row:last-child { border-bottom: none; }
/* Client-facing tap targets are >=44px everywhere, incl. these pill
   buttons (JOBS_SPEC_V2.md section 2 - min-height 48px on mobile). */
.job-need-row .btn { min-height: 44px; padding: 8px 16px; font-size: var(--bw-text-base); }
/* Client-facing progress page: inputs stay >=16px (JOBS_SPEC_V2.md section
   2) so mobile Safari never auto-zooms on focus - scoped to this page via
   $bodyClass rather than changing the admin-wide 14px input default. */
body.job-progress-page input, body.job-progress-page textarea, body.job-progress-page select {
    font-size: var(--bw-text-md);
}
@media (max-width: 600px) {
    .job-need-row { flex-direction: column; align-items: stretch; }
    .job-need-row .btn { width: 100%; min-height: 48px; }
}
/* Updates photo grid (spec 5, "Updates (photo grid 3-up)"). */
.job-photo-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 8px; }
@media (max-width: 480px) {
    .job-photo-grid { grid-template-columns: repeat(3, 1fr); gap: 6px; }
}
.job-photo-grid .job-file-thumb { width: 100%; height: 88px; margin: 0; }
.job-comment-details > summary {
    cursor: pointer; list-style: none; font-weight: 600; color: var(--bw-brand); min-height: 44px; display: flex; align-items: center;
}
.job-comment-details > summary::-webkit-details-marker { display: none; }

/* Mobile: everything above already wraps via flex-wrap/grid, but pin down
   full-width, >=48px buttons on the client-facing pages at 375px per
   JOBS_SPEC_V2.md section 2. */
@media (max-width: 600px) {
    .job-quick-actions .btn, .job-quick-actions .btn-small { flex: 1 1 auto; justify-content: center; }
    .job-header-strip { flex-direction: column; }
}

/* ===================================================================
   Itemised one-off invoices (7 Sep 2026) - repeatable line rows on the
   client page's "Create a one-off invoice" form, and the read-only items
   table on the invoice detail page / portal invoice page.
   =================================================================== */
.invoice-line-row {
    display: flex;
    gap: 8px;
    align-items: center;
    margin-bottom: 8px;
}
.invoice-line-row .invoice-line-desc { flex: 3 1 auto; min-width: 0; }
.invoice-line-row .invoice-line-qty { flex: 0 1 70px; min-width: 0; }
.invoice-line-row .invoice-line-unit { flex: 0 1 120px; min-width: 0; }
.invoice-line-row .invoice-line-desc,
.invoice-line-row .invoice-line-qty,
.invoice-line-row .invoice-line-unit {
    min-height: 44px;
    margin-bottom: 0;
}
.invoice-line-total {
    flex: 0 0 90px;
    text-align: right;
    font-variant-numeric: tabular-nums;
    color: var(--bw-muted);
    font-size: 13px;
}
.invoice-detail-split {
    border-left: 3px solid var(--bw-brand);
    padding: 12px 16px;
    background: var(--bw-surface-2, var(--bw-ground));
    border-radius: var(--bw-radius-sm);
    margin-bottom: 16px;
}
.invoice-paid-stamp {
    display: inline-block;
    color: var(--bw-success, #1E6B3A);
    border: 2px solid currentColor;
    border-radius: 4px;
    padding: 2px 10px;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    font-size: 12px;
}

/* ===================================================================
   Quote details (12 Sep 2026): the rich per-quote specification, its
   attachments, the admin toolbar editor that writes it, and the
   floating "Approve quote" bar on the client-facing share page.

   Spacing follows the same rhythm as the rest of the quote document -
   32px between document sections, 16px inside one - so the new section
   sits between the payment schedule and the terms without looking
   bolted on.
   =================================================================== */

/* ---- client-facing: the details section inside the quote document ---- */
.quote-doc-details { margin: 32px 0 0; }
.quote-doc-details > h3 { margin: 0 0 12px; }

/* This block renders admin-authored HTML (App\Quotes\QuoteDetailsHtml
   allows <table>) directly on the client-facing /q/{token} page, with no
   .card ancestor - so, unlike every other table in the app, it has no
   .card:has(table) safety net. If an admin ever pastes in a wide table,
   this contains its scroll instead of letting it push the whole page wide
   on a phone (mobile audit, 13 Sep 2026). */
.quote-details-body { color: var(--bw-ink); font-size: 15px; line-height: 1.65; overflow-x: auto; }
.quote-details-body > *:first-child { margin-top: 0; }
.quote-details-body > *:last-child { margin-bottom: 0; }
.quote-details-body h2 { font-size: 19px; margin: 24px 0 8px; }
.quote-details-body h3 { font-size: 17px; margin: 20px 0 8px; }
.quote-details-body h4 { font-size: 15px; margin: 18px 0 6px; }
.quote-details-body p { margin: 0 0 12px; }
.quote-details-body ul,
.quote-details-body ol { margin: 0 0 12px; padding-left: 22px; }
.quote-details-body li { margin-bottom: 6px; }
.quote-details-body li:last-child { margin-bottom: 0; }
.quote-details-body a { color: var(--bw-brand); }
.quote-details-body blockquote {
    margin: 16px 0;
    padding: 10px 16px;
    border-left: 3px solid var(--bw-border);
    color: var(--bw-muted);
}
.quote-details-body hr { border: 0; border-top: 1px solid var(--bw-border); margin: 20px 0; }
/* An inserted image must never blow out the document width or the print
   layout, whatever its natural size. */
.quote-details-body img {
    display: block;
    max-width: 100%;
    height: auto;
    margin: 16px 0;
    border-radius: var(--bw-radius);
    border: 1px solid var(--bw-border);
}
.quote-details-body table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 14px; }
.quote-details-body th,
.quote-details-body td { padding: 8px 10px; border: 1px solid var(--bw-border); text-align: left; }
.quote-details-body th { background: var(--bw-surface); font-weight: 600; }

/* ---- attachments list (client-facing) ---- */
.quote-files { margin-top: 24px; }
.quote-files-title { font-size: 14px; margin: 0 0 10px; color: var(--bw-muted); text-transform: uppercase; letter-spacing: .04em; }
.quote-files-list { list-style: none; margin: 0; padding: 0; }
/* A row is: name + size on the left, actions pinned right. Wraps to two
   lines on a narrow screen rather than squashing the buttons. */
.quote-files-list li {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 10px 16px;
    padding: 14px 0;
    border-bottom: 1px solid var(--bw-border);
}
.quote-files-list li:last-child { border-bottom: none; }
.quote-file-meta { display: flex; align-items: baseline; gap: 10px; min-width: 0; }
.quote-file-name { font-weight: 600; word-break: break-word; }
.quote-file-size { color: var(--bw-muted); font-size: 13px; white-space: nowrap; }
.quote-file-actions { display: flex; gap: 8px; flex-shrink: 0; }
.quote-files-list a { word-break: break-word; }

/* ---- admin: the upload control ----
   The browser's native file input is unstyleable and looks out of place next
   to everything else, so it is visually hidden and a label styled as a button
   stands in for it; the filename appears beside it once chosen. */
.quote-files-upload-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
    margin: 16px 0 10px;
    padding: 14px 16px;
    background: var(--bw-surface);
    border: 1px dashed var(--bw-border);
    border-radius: var(--bw-radius);
}
.quote-files-upload-row input[type="file"] {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}
.quote-file-pick {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    background: #fff;
    border: 1px solid var(--bw-border);
    border-radius: 6px;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
}
.quote-file-pick:hover { border-color: var(--bw-ink-2); }
.quote-file-chosen { color: var(--bw-muted); font-size: 13px; min-width: 0; word-break: break-word; }
.quote-files-upload-row .btn { margin-left: auto; }

@media (max-width: 600px) {
    .quote-files-upload-row .btn { margin-left: 0; width: 100%; }
    .quote-file-actions { width: 100%; }
    .quote-file-actions .btn { flex: 1; text-align: center; }
}

/* ---- admin: the toolbar editor ---- */
.rte-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    padding: 8px;
    border: 1px solid var(--bw-border);
    border-bottom: 0;
    border-radius: var(--bw-radius) var(--bw-radius) 0 0;
    background: var(--bw-surface);
}
.rte-btn {
    min-width: 34px;
    height: 30px;
    padding: 0 8px;
    font-size: 13px;
    line-height: 1;
    background: #fff;
    border: 1px solid var(--bw-border);
    border-radius: 5px;
    cursor: pointer;
    color: var(--bw-ink);
}
.rte-btn:hover { border-color: var(--bw-ink-2); }
.rte-btn:active { background: var(--bw-surface); }
.rte-sep { width: 1px; height: 20px; background: var(--bw-border); margin: 0 4px; }

.rte-surface {
    min-height: 220px;
    max-height: 60vh;
    overflow-y: auto;
    padding: 16px;
    border: 1px solid var(--bw-border);
    border-radius: 0 0 var(--bw-radius) var(--bw-radius);
    background: #fff;
    font-size: 15px;
    line-height: 1.65;
}
.rte-surface:focus { outline: 2px solid var(--bw-brand); outline-offset: -2px; }
.rte-surface > *:first-child { margin-top: 0; }
.rte-surface h2 { font-size: 19px; margin: 20px 0 8px; }
.rte-surface h3 { font-size: 17px; margin: 18px 0 8px; }
.rte-surface ul, .rte-surface ol { padding-left: 22px; }
.rte-surface img { max-width: 100%; height: auto; border-radius: var(--bw-radius); }
.rte-surface blockquote {
    margin: 12px 0; padding: 8px 14px;
    border-left: 3px solid var(--bw-border); color: var(--bw-muted);
}
/* The real posted field. Hidden from view but deliberately still in the
   form: it is what actually submits, and it is the fallback editing box
   if the editor script never runs. */
.rte-source { position: absolute; left: -9999px; width: 1px; height: 1px; }

/* ---- admin: attachments panel ---- */
.quote-files-panel { margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--bw-border); }
.quote-files-panel-title { font-size: 15px; margin: 0 0 6px; }
.quote-files-admin-list { list-style: none; margin: 12px 0 0; padding: 0; }
.quote-files-admin-list li {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 0;
    border-bottom: 1px solid var(--bw-border);
}
.quote-files-admin-list li:last-child { border-bottom: none; }
.quote-files-admin-list form { margin: 0 0 0 auto; }

/* ---- client-facing: floating approve bar ----
   Hidden until the script adds .visible (which it only does once the real
   button has scrolled out of view), so it never covers content
   unnecessarily and never appears at all without JS. */
.quote-sticky-cta {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 500;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 16px;
    padding: 12px 20px calc(12px + env(safe-area-inset-bottom));
    background: #fff;
    border-top: 1px solid var(--bw-border);
    box-shadow: 0 -6px 18px rgba(0, 0, 0, .08);
    transform: translateY(110%);
    transition: transform var(--bw-fast) ease;
}
.quote-sticky-cta.visible { transform: translateY(0); }
.quote-sticky-total { font-weight: 700; font-size: 16px; margin-right: auto; }
.quote-sticky-cta .btn { white-space: nowrap; }
/* Keep the last of the document reachable above the bar while it is up. */
body.quote-sticky-padding { padding-bottom: 84px; }

@media (max-width: 600px) {
    .quote-sticky-cta { padding: 10px 14px calc(10px + env(safe-area-inset-bottom)); gap: 10px; }
    .quote-sticky-total { font-size: 15px; }
    .quote-sticky-cta .btn { flex: 0 0 auto; }
}

/* Never print the floating bar or the editor chrome. */
@media print {
    .quote-sticky-cta { display: none !important; }
    body.quote-sticky-padding { padding-bottom: 0; }
    .quote-doc-details { break-inside: avoid; }
}

/* ===================================================================
   Job stage notes (12 Sep 2026): the optional formatted message an
   admin attaches to a stage action - highlighted above the client's
   Approve button, and kept on the job timeline afterwards.
   =================================================================== */

/* The one the client reads before approving. Deliberately the most
   prominent thing in the approve card after its heading. */
.job-approval-note {
    margin: 0 0 20px;
    padding: 16px 18px;
    background: #fbf7ec;
    border: 1px solid #efe2c4;
    border-left: 3px solid var(--bw-brand);
    border-radius: var(--bw-radius);
}
.job-approval-note-title {
    margin: 0 0 8px;
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--bw-ink-2);
}
.job-approval-note-body { font-size: 15px; line-height: 1.65; color: var(--bw-ink); }
.job-approval-note-body > *:first-child { margin-top: 0; }
.job-approval-note-body > *:last-child { margin-bottom: 0; }
.job-approval-note-body p { margin: 0 0 10px; }
.job-approval-note-body h3 { font-size: 16px; margin: 14px 0 6px; }
.job-approval-note-body h4 { font-size: 14px; margin: 12px 0 6px; }
.job-approval-note-body ul,
.job-approval-note-body ol { margin: 0 0 10px; padding-left: 20px; }
.job-approval-note-body li { margin-bottom: 4px; }
.job-approval-note-body a { color: var(--bw-brand); }
.job-approval-note-body blockquote {
    margin: 10px 0; padding: 8px 14px;
    border-left: 2px solid var(--bw-border); color: var(--bw-muted);
}

/* The same note, read back later in the timeline - quieter, since by
   then it is a record rather than an instruction. */
.job-timeline-note {
    margin-top: 10px;
    padding: 12px 14px;
    background: #fbf7ec;
    border-left: 3px solid var(--bw-brand);
    border-radius: 4px;
    font-size: 14px;
    line-height: 1.6;
}
.job-timeline-note > *:first-child { margin-top: 0; }
.job-timeline-note > *:last-child { margin-bottom: 0; }
.job-timeline-note p { margin: 0 0 8px; }
.job-timeline-note ul, .job-timeline-note ol { margin: 0 0 8px; padding-left: 20px; }
.job-timeline-note h3, .job-timeline-note h4 { font-size: 14px; margin: 10px 0 4px; }

/* Admin: the collapsed "Add a note for the client" box on a stage action. */
.stage-note-details { margin: 0 0 12px; }
.stage-note-details > summary {
    cursor: pointer;
    font-size: 13px;
    font-weight: 600;
    color: var(--bw-ink-2);
    padding: 6px 0;
}
.stage-note-details > summary:hover { color: var(--bw-brand); }
.rte-toolbar-compact { padding: 6px; gap: 3px; }
.rte-toolbar-compact .rte-btn { min-width: 28px; height: 26px; padding: 0 6px; font-size: 12px; }
.rte-surface-compact { min-height: 110px; max-height: 40vh; padding: 12px; font-size: 14px; }

/* .field-hint carries margin-top:-10px so it tucks under a normal input.
   After a rich-text editor (or an upload row) that negative margin pulls it
   UP OVER the box above - which is exactly what it did under the stage-note
   editor: the hint text printed on top of the editor's bottom edge. These
   contexts set it back to normal flow. (12 Sep 2026, found in live testing.) */
.rte-surface + .field-hint,
.rte-source + .field-hint,
.quote-files-upload-row + .field-hint,
.stage-note-details .field-hint,
.quote-files-panel .field-hint,
/* A hint after a BUTTON is the same bug (13 Sep 2026, spotted on the "Change
   password" button on /admin/account): the -10px tucks the text up over the
   button's lower edge, so the two overlap. Fixed globally rather than on the
   one page - four templates put a hint directly after a button, and every one
   of them was doing it. */
.btn + .field-hint,
button + .field-hint,
/* A hint after a whole FORM is the identical bug one level up (20 Sep 2026,
   spotted on the invite/set-password page: Malik's screenshot showed the
   "At least 12 characters..." hint printed over the "Set password" button).
   `button + .field-hint` above only matches a hint whose IMMEDIATE previous
   sibling is a bare <button> - it does nothing when the button is the LAST
   child of a <form> and the hint comes after the form's closing tag, which
   is the more common shape (setup.php, invite_accept.php and quote_view.php
   all do this). Same fix, one selector wider. */
form + .field-hint {
    margin-top: 10px;
}

/* The editor needs its own breathing room from whatever follows it. */
.rte-surface { margin-bottom: 4px; }
.stage-note-details .rte-surface-compact { margin-bottom: 0; }
