/* ============================================================================
   TABLEROCK Cleaning LLC — Site Stylesheet
   ---------------------------------------------------------------------------
   No frameworks. Single stylesheet organised into clearly labelled sections:
     1. Design tokens
     2. Reset & base
     3. Layout containers
     4. Buttons
     5. Cards & panels
     6. Forms & alerts
     7. Site header & footer
     8. Page hero
     9. Sections & trust modules
    10. Service page template
    11. Estimate wizard
    12. Auth (sign-in) card
    13. Portal shells (customer / employee / admin / contract)
    14. Utilities
    15. Responsive
   Brand colors follow the navy lockup in /public/assets/TRCLogo.png and
   FRONTEND_PRODUCT_ARCHITECTURE_ROADMAP.md.
   ========================================================================== */

/* 0. Display typeface ------------------------------------------------------- */
/* Plus Jakarta Sans (SIL OFL 1.1 — license at /assets/fonts/OFL.txt),
   self-hosted variable font, latin subset, headings only. Self-hosting keeps
   font-src 'self' intact — no third-party font CDN ever enters the CSP. */
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('/assets/fonts/PlusJakartaSans-latin-var.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/* 1. Design tokens ---------------------------------------------------------- */
:root {
  /* Brand */
  --color-primary: #1A3A6B;
  --color-primary-dark: #102747;
  --color-primary-light: #E8F0FA;
  /* The marketing header's own navy, deeper than --color-primary. It is a
     token rather than a hex in one rule because the sticky bar, its scrolled
     state and the mobile nav panel that drops out of it all have to agree. */
  --color-header: #0E336A;
  --color-white: #FFFFFF;
  --color-off-white: #F7F8FA;
  --color-text: #172033;
  --color-muted: #5F6B7A;
  --color-border: #D8E0EA;
  /* Two different jobs, two different weights.
     --color-border is the edge of a CONTROL: an input, a select, a textarea —
     something you can put a cursor in. It has to look like an affordance.
     --color-rule is a DIVIDER inside a plane: between two records in a list,
     between a table's rows. It only has to say "these are separate", and at
     the same weight as a control edge it says far more than that. Every
     container that used to draw --color-border around itself now relies on
     its own background plane and shadow instead, which is what removed most
     of the rectangles from the portals. */
  --color-rule: rgba(23, 32, 51, 0.09);
  --color-rule-strong: rgba(23, 32, 51, 0.16);
  --color-success: #287A4B;
  --color-warning: #B7791F;
  --color-danger: #B42318;
  --color-danger-dark: #8D1A12;
  --color-accent: #C9A24D;
  --color-accent-dark: #B8902F;
  /* Gold as TEXT is a different colour from gold as a surface.
     --color-accent-dark is 2.97:1 on white — it reads as brand on a button
     fill and fails WCAG AA the moment it becomes a word. This is the same hue
     carried down to 5.5:1 on white and 4.7:1 on the darkest portal ground, so
     an eyebrow can be gold and still be readable. */
  --color-accent-text: #856312;

  /* Extended brand range — the deep-navy end anchors heroes/footers so the
     lockup navy (#1A3A6B) reads as a mid-tone instead of the darkest value,
     and the warm paper tone keeps marketing sections from feeling clinical. */
  --color-navy-950: #081426;
  --color-navy-900: #0C1D38;
  --color-navy-800: #102747;
  --color-navy-700: #16345E;
  --color-ink: #131C2C;
  --color-cream: #F8F6F0;
  --color-cream-deep: #F1EDE3;
  --color-accent-light: #EAD9AC;
  --color-accent-soft: rgba(201, 162, 77, 0.16);
  /* Two gradient stops either side of --color-accent, and nothing else uses
     them. A flat fill on a 12px strip reads as a painted rectangle; one stop
     up and one stop down give it the small amount of curvature that makes it
     look like a machined part instead. --accent-sheen is the gold lightened
     toward --color-accent-light without reaching it (that tint is a TEXT
     colour for navy grounds and is far too pale to be a surface), and
     --accent-deep continues past --color-accent-dark for the pressed state. */
  --color-accent-sheen: #D8B674;
  --color-accent-deep: #A67F27;

  /* Motion — one easing family so every reveal/hover feels related. */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1);
  --dur-quick: 160ms;
  --dur-med: 320ms;
  --dur-reveal: 700ms;

  /* Feedback surface tokens (alerts, errors, success banners). Paired
     background / border / text variants for each semantic color so the
     signing portal, estimate form, customer dashboard, and alert classes
     all draw from the same palette. */
  --color-info-border: #BCD0EE;
  --color-success-bg: #E5F5EC;
  --color-success-border: #A8D8BB;
  --color-success-text: #1A5A36;
  --color-warning-bg: #FFF5E0;
  --color-warning-border: #F1D39A;
  --color-warning-text: #6B4806;
  --color-danger-bg: #FDE7E5;
  --color-danger-border: #F5B7B1;
  --color-danger-text: #6F1818;

  /* Tier / badge surfaces used by service-area cards and badges. */
  --color-tier-active-bg: #DFF5E7;
  --color-tier-extended-bg: #FFF2DA;
  --color-tier-quote-bg: #EFE6D6;
  --color-tier-quote-text: #6B4F0A;
  --color-badge-danger-bg: #FDE0DC;

  /* Three aliases that were being READ before they were ever declared, and so
     resolved to nothing: the declarations that used them were invalid at
     computed-value time and were dropped in silence. scripts/check-css-tokens.mjs
     now fails the build on that class of bug. The values are the ones the call
     sites plainly intend.
       --color-surface / --color-surface-muted: the two neutral planes, read by
         the admin voice panels and by the customer booking record's lifecycle
         dot, which therefore had no fill at all.
       --color-focus: the focus ring, read by the admin capability switches and
         the mail context rows. Without it those `outline` declarations were
         dropped entirely, so two operator surfaces had no visible focus. */
  --color-surface: var(--color-white);
  --color-surface-muted: var(--color-off-white);
  --color-focus: var(--color-primary);

  /* Disabled control surface used by action buttons. */
  --color-disabled-bg: #E5E8EE;
  /* Disabled text still has to be READ — a cleaner needs to know why the
     finalize button will not press, standing outside in the sun. WCAG 1.4.3
     exempts inactive controls; the employee portal does not get to use that
     exemption. --color-muted on --color-disabled-bg is 4.42:1; this is 5.9:1. */
  --color-disabled-text: #4E5768;

  /* Typography scale */
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    Helvetica, Arial, sans-serif;
  --font-display: 'Plus Jakarta Sans', var(--font-sans);
  /* The step below --fs-xs, for a tracked uppercase micro-label sitting under
     a value it annotates. It was in use in nineteen places across the customer
     property workspace and booking record before it was ever declared, so
     every one of them silently inherited its parent's size. */
  --fs-2xs: 0.6875rem;
  --fs-xs: 0.8125rem;
  --fs-sm: 0.9375rem;
  --fs-base: 1rem;
  --fs-md: 1.125rem;
  --fs-lg: 1.25rem;
  --fs-xl: clamp(1.35rem, 1.1rem + 0.8vw, 1.55rem);
  --fs-2xl: clamp(1.6rem, 1.3rem + 1.2vw, 2rem);
  --fs-3xl: clamp(1.9rem, 1.4rem + 2vw, 2.6rem);
  --fs-4xl: clamp(2.2rem, 1.6rem + 2.6vw, 3.1rem);
  --fs-5xl: clamp(2.4rem, 1.7rem + 3.4vw, 3.9rem);
  --fs-hero: clamp(2.6rem, 1.6rem + 4.6vw, 4.6rem);

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

  /* Radius scale */
  --radius-xs: 4px;
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 14px;
  --radius-xl: 20px;
  --radius-pill: 999px;
  /* The corner on anything you can put a cursor in.
     Its own token rather than a reach for --radius-sm: a field is the one
     control a person touches directly, its radius is a deliberate softness
     rather than a size step, and one declaration here is what keeps the
     portal, the estimate wizard, login, the marketing forms and both staff
     tools from drifting into three different corners. */
  --radius-field: 10px;

  /* Shadows */
  --shadow-xs: 0 1px 2px rgba(16, 39, 71, 0.06);
  --shadow-sm: 0 2px 6px rgba(16, 39, 71, 0.08);
  --shadow-md: 0 6px 18px rgba(16, 39, 71, 0.10);
  --shadow-lg: 0 16px 40px rgba(16, 39, 71, 0.14);

  /* Layout */
  --container-width: 1200px;
  --container-narrow: 720px;
  --container-wide: 1320px;
  --header-height: 72px;

  /* Scrollbar ---------------------------------------------------------------
     Every value the scrollbar uses is a variable, and the rules in section 2
     read nothing else. That is what makes one canonical treatment adapt to a
     navy pane: the pane overrides two variables, not a second copy of the
     rules. See "The TABLEROCK scrollbar" below for the tiers.

     --scrollbar-track is deliberately TRANSLUCENT rather than a flat colour.
     It composites over whatever ground it is drawn on, so the same token
     recedes correctly on cream marketing pages, on the white portal sheet and
     on the off-white admin ground, instead of becoming one fixed strip that
     is wrong on two of the three.

     It is tinted with the deep gold rather than the navy. Navy at 6% is a
     COOL grey, and a cool grey gutter running the full height of a warm cream
     page is the exact thing that reads as browser chrome bolted onto the
     product; the same alpha of --color-accent-text lands within a shade of
     --color-cream on marketing and as a warm gutter on the white portal
     grounds, so the strip belongs to the page either way.

     --scrollbar-thumb-edge is not decoration. Gold on a light ground is about
     2:1, well under the 3:1 WCAG 1.4.11 asks of a control against its
     background, and darkening the thumb enough to fix that would stop it
     being TABLEROCK gold. A one-pixel deep-gold edge answers it instead: the
     thumb's BOUNDARY is 4.6:1 against the track, which is the thing that has
     to be perceivable. */
  --scrollbar-size: 12px;
  --scrollbar-size-pane: 9px;
  --scrollbar-size-strip: 6px;
  --scrollbar-track: rgba(133, 99, 18, 0.07);
  --scrollbar-thumb-top: var(--color-accent-sheen);
  --scrollbar-thumb-mid: var(--color-accent);
  --scrollbar-thumb-bottom: var(--color-accent-dark);
  --scrollbar-thumb-edge: var(--color-accent-text);
}

/* 2. Reset & base ---------------------------------------------------------- */
* { box-sizing: border-box; }
*::before, *::after { box-sizing: inherit; }

html, body {
  margin: 0;
  padding: 0;
}

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

@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

/* --- The TABLEROCK scrollbar ------------------------------------------------
   The browser's grey scrollbar was the last piece of chrome sitting ON the
   product rather than being part of it. This is the one canonical treatment:
   a receding warm track and a gold thumb, defined here and nowhere else. No
   other stylesheet declares scrollbar rules; a surface that needs different
   colours overrides the variables above.

   THREE TIERS, one visual family. The page is the most pronounced thing on
   screen and a chat panel's scrollbar should not be:

     page    html + body, 12px  the document scrollbar
     pane    default, 9px  every deliberate internal scroll region: the
             Bristle conversation, the mail reader and thread list, the admin
             voice cockpit panes, the SMS thread, dialog bodies, the property
             navigator, drawers, the command palette, wide tables
     strip   6px, inset  small horizontal nav and tab strips, where a
             full-width bar would be louder than the thing it belongs to

   The strip tier replaces the `display: none` rule that used to hide the
   overflow on .app-nav__links outright. Hiding it made that strip
   undiscoverable: on a narrow admin screen there was nothing at all to say
   more sections existed off the right edge. (.app-rail__inner had the same
   rule and the same problem; it was fixed better, by wrapping so there is no
   overflow to scroll.)

   Pointer-gated on purpose. Styling ::-webkit-scrollbar turns the platform's
   OVERLAY scrollbar into a permanent one that occupies layout width, which is
   right on a desktop and wrong on a phone, where the OS already draws a
   transient bar over the content and nothing here should argue with it. So
   the whole treatment sits behind (hover: hover) and (pointer: fine); touch
   devices keep native scrolling, native physics and native chrome. This is
   presentation only either way: no JS, no fake scrollbar, no interception. */
@media (hover: hover) and (pointer: fine) {
  /* Firefox and any engine on the standards-track properties only. No
     gradient is possible there, so the fallback is the flat brand gold on the
     same track: the closest honest approximation rather than a hack to force
     one. Set on :root so it inherits to every scroll container, which is what
     keeps a Firefox pane in the same family as a Chromium one.

     The @supports guard is load-bearing, not defensive. Per spec, an element
     with a non-auto `scrollbar-color` or `scrollbar-width` ignores its
     ::-webkit-scrollbar rules entirely - and `scrollbar-color` INHERITS, so
     declaring it on :root silently disabled the whole gradient treatment on
     every element in Chromium and left the browser's own 15px grey bar. The
     two mechanisms cannot both be addressed to the same engine; this hands
     each engine only the one it can actually use. */
  @supports not selector(::-webkit-scrollbar) {
    :root {
      scrollbar-color: var(--color-accent) var(--scrollbar-track);
    }
    .app-nav__links,
    .mail-admin-tabs,
    .mail-folders,
    .mail-reader__actions,
    .time-views ul {
      scrollbar-width: thin;
    }
    @media (forced-colors: active) {
      :root { scrollbar-color: auto; }
    }
  }

  /* The pane tier is the unprefixed default, so a scroll region that nobody
     thought about still gets the right thing. */
  ::-webkit-scrollbar {
    width: var(--scrollbar-size-pane);
    height: var(--scrollbar-size-pane);
  }
  ::-webkit-scrollbar-track {
    background: var(--scrollbar-track);
  }
  ::-webkit-scrollbar-thumb {
    /* Lighter warm gold, TABLEROCK gold, then deeper: enough curvature to
       read as a solid object catching light, and no further. Nothing here
       animates, so the gradient costs one paint. */
    background-image: linear-gradient(
      to bottom,
      var(--scrollbar-thumb-top) 0%,
      var(--scrollbar-thumb-mid) 52%,
      var(--scrollbar-thumb-bottom) 100%
    );
    border: 1px solid var(--scrollbar-thumb-edge);
    border-radius: var(--radius-pill);
  }
  /* A horizontal thumb is lit from the same direction as a vertical one, so
     its gradient runs across rather than down. */
  ::-webkit-scrollbar-thumb:horizontal {
    background-image: linear-gradient(
      to right,
      var(--scrollbar-thumb-top) 0%,
      var(--scrollbar-thumb-mid) 52%,
      var(--scrollbar-thumb-bottom) 100%
    );
  }
  ::-webkit-scrollbar-thumb:hover {
    --scrollbar-thumb-top: var(--color-accent);
    --scrollbar-thumb-mid: var(--color-accent-dark);
    --scrollbar-thumb-bottom: var(--color-accent-deep);
  }
  /* Dragging. Deeper still and a firmer edge, so the grip is obvious under
     the cursor without the bar changing size and fighting the drag. */
  ::-webkit-scrollbar-thumb:active {
    --scrollbar-thumb-top: var(--color-accent-dark);
    --scrollbar-thumb-mid: var(--color-accent-deep);
    --scrollbar-thumb-bottom: var(--color-accent-deep);
    border-color: var(--color-accent-deep);
  }
  /* Where a vertical and a horizontal bar meet. Unstyled this is a bare grey
     square, which is the single most obvious tell that a scrollbar was only
     half done. */
  ::-webkit-scrollbar-corner {
    background: var(--scrollbar-track);
  }
  /* Chromium draws stepper buttons on some platforms; they are not part of
     this design. */
  ::-webkit-scrollbar-button {
    display: none;
  }

  /* The page tier. More presence than a pane because it governs the whole
     document, and it is the one scrollbar always on screen.

     Both `html` and `body`: Blink attributes the VIEWPORT scrollbar to
     whichever of the two paints the canvas background, and this codebase sets
     `background` on body, so `html::-webkit-scrollbar` alone left the document
     bar on the 9px pane tier. Naming both is the only way to say "the page"
     without depending on that detail staying true. */
  html::-webkit-scrollbar,
  body::-webkit-scrollbar {
    width: var(--scrollbar-size);
    height: var(--scrollbar-size);
  }

  /* The strip tier. The same thumb, narrower and inset: 6px in a groove reads
     as a scrollbar, where the 6px flat gold BAR it started as read as a second
     rule underneath the nav and competed with the solid-gold current-item
     indicator sitting directly above it. Keeping the gradient and the edge is
     also what keeps the boundary above 3:1 here; a muted flat fill quiet
     enough not to compete was 1.7:1 and no longer a visible control.

     The inset comes from a transparent border with the fill clipped to the
     padding box, which is the only way to shrink a scrollbar thumb inside its
     track: the track has to stay 6px to keep the strip a usable target. */
  .app-nav__links::-webkit-scrollbar,
  .mail-admin-tabs::-webkit-scrollbar,
  .mail-folders::-webkit-scrollbar,
  .mail-reader__actions::-webkit-scrollbar,
  .time-views ul::-webkit-scrollbar {
    width: var(--scrollbar-size-strip);
    height: var(--scrollbar-size-strip);
  }
  .app-nav__links::-webkit-scrollbar-thumb,
  .mail-admin-tabs::-webkit-scrollbar-thumb,
  .mail-folders::-webkit-scrollbar-thumb,
  .mail-reader__actions::-webkit-scrollbar-thumb,
  .time-views ul::-webkit-scrollbar-thumb {
    border: 1px solid transparent;
    background-clip: padding-box;
  }
  /* Navy panes. Two variables, not a second set of rules: the deep-gold edge
     that rescues contrast on cream is invisible on navy, and a navy track
     drawn over navy would disappear entirely. */
  .app-nav--admin .app-rail,
  .app-nav__links {
    --scrollbar-track: rgba(255, 255, 255, 0.09);
    --scrollbar-thumb-edge: rgba(8, 20, 38, 0.45);
  }

  /* Forced colours. The OS has taken over for a reason; a gradient painted
     over its palette is exactly the wrong answer. Hand the scrollbar back:
     system colours for the parts here, and, in the @supports branch above,
     `auto` so Firefox uses the platform's own rather than the brand pair. */
  @media (forced-colors: active) {
    ::-webkit-scrollbar-track,
    ::-webkit-scrollbar-corner { background: Canvas; }
    ::-webkit-scrollbar-thumb {
      background-image: none;
      background-color: ButtonBorder;
      border: 1px solid Canvas;
    }
  }
}

body {
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  line-height: 1.55;
  color: var(--color-text);
  background: var(--color-white);
  -webkit-font-smoothing: antialiased;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  color: var(--color-text);
  margin: 0 0 var(--space-4);
  line-height: 1.18;
  letter-spacing: -0.01em;
}

h1 { font-size: var(--fs-5xl); font-weight: 800; letter-spacing: -0.025em; line-height: 1.06; }
h2 { font-size: var(--fs-3xl); font-weight: 800; letter-spacing: -0.02em; line-height: 1.12; }
h3 { font-size: var(--fs-xl); font-weight: 700; }
h4 { font-size: var(--fs-lg); font-weight: 600; }

p { margin: 0 0 var(--space-4); }
p:last-child { margin-bottom: 0; }
.lede { font-size: var(--fs-md); color: var(--color-muted); }

a {
  color: var(--color-primary);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
a:hover { text-decoration: none; }

img { max-width: 100%; display: block; }

ul, ol { padding-left: 1.25rem; margin: 0 0 var(--space-4); }
ul.list-plain, ol.list-plain { list-style: none; padding: 0; margin: 0; }

code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.95em; word-break: break-all; }

pre {
  white-space: pre-wrap;
  word-break: break-word;
  overflow-x: auto;
  max-width: 100%;
}

/* The hidden attribute must always win. Several components (estimate steps,
   admin confirm forms) set display:grid/flex at author origin, which
   otherwise overrides the UA's [hidden] rule and leaves "hidden" forms
   visible on load. */
[hidden] { display: none !important; }

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--color-primary-dark);
  color: var(--color-white);
  padding: 0.5em 1em;
  z-index: 100;
  border-radius: 0 0 var(--radius-md) 0;
}
.skip-link:focus { left: 0; top: 0; }

/* The skip link's landing must be visible wherever it lands. Keyed on the id
   rather than the class because the signing and site-access surfaces name
   their main region `sign-main` / `site-access-main`, and a skip link that
   moves focus invisibly is a skip link a keyboard user cannot follow. */
.main:focus,
#main:focus {
  outline: 3px solid var(--color-primary);
  outline-offset: -3px;
}

:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

/* 3. Layout containers ----------------------------------------------------- */
.shell { flex: 1; display: flex; flex-direction: column; }

.main { flex: 1; width: 100%; }

.container {
  width: 100%;
  max-width: var(--container-width);
  margin: 0 auto;
  /* max(), not a bare gutter: the viewport meta carries viewport-fit=cover so
     the portal's fixed bottom bar can clear the home indicator, and on a
     notched phone held sideways the safe-area inset is wider than --space-6.
     Taking the larger of the two means the cover only ever adds room and never
     lets a line of text run under the rounded corner. */
  padding: 0 max(var(--space-6), env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px));
}
.container--narrow { max-width: var(--container-narrow); }
.container--wide   { max-width: var(--container-wide); }

.section {
  padding: var(--space-16) 0;
}
.section--soft    { background: var(--color-off-white); }
.section--brand   { background: var(--color-primary); color: var(--color-white); }
.section--brand h1, .section--brand h2, .section--brand h3 { color: var(--color-white); }
.section--brand a { color: var(--color-white); }
.section--brand .lede, .section--brand p { color: rgba(255,255,255,0.92); }

.section--brand-light { background: var(--color-primary-light); }

.section__head {
  max-width: 760px;
  margin: 0 auto var(--space-10);
  text-align: center;
}
.section__head--left { margin-left: 0; text-align: left; }
.section__eyebrow {
  display: inline-block;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-primary);
  margin-bottom: var(--space-3);
}
/* Gold signature rule under section eyebrows — small, consistent brand mark. */
.section__head .section__eyebrow::after {
  content: "";
  display: block;
  width: 34px;
  height: 3px;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  margin: var(--space-2) auto 0;
}
.section__head--left .section__eyebrow::after { margin-left: 0; margin-right: auto; }
.section--brand .section__eyebrow,
.page-hero .section__eyebrow,
.cta-band .section__eyebrow,
.home-hero .section__eyebrow { color: var(--color-accent); }

.grid { display: grid; gap: var(--space-5); }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.grid--4 { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.grid--auto { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }

/* The service chooser is the first dense comparison surface on /services.
   Keep every card inside its grid track, including on narrow mobile layouts;
   an intrinsic child must never make a card or its decoration bleed across
   the neighboring copy. */
.services-choice-grid,
.services-choice-grid > .card { min-width: 0; }

.stack > * + * { margin-top: var(--space-4); }
.stack-lg > * + * { margin-top: var(--space-6); }

.cluster { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }
.cluster--end { justify-content: flex-end; }
.cluster--between { justify-content: space-between; }
.cluster--center { justify-content: center; }

/* 4. Buttons --------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 0.75rem 1.4rem;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  font-family: inherit;
  font-size: var(--fs-base);
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  min-height: 44px;
  transition: background-color var(--dur-quick) ease, border-color var(--dur-quick) ease,
    color var(--dur-quick) ease, box-shadow var(--dur-quick) ease, transform var(--dur-quick) var(--ease-out);
}
@media (prefers-reduced-motion: no-preference) {
  .btn:hover { transform: translateY(-1px); }
}
.btn:active { transform: translateY(1px); }
.btn:disabled, .btn[disabled], .btn[aria-disabled="true"] {
  cursor: not-allowed; opacity: 0.55;
}

.btn-primary {
  background: var(--color-primary);
  color: var(--color-white);
  border-color: var(--color-primary);
  box-shadow: var(--shadow-sm);
}
.btn-primary:hover { background: var(--color-primary-dark);
  border-color: var(--color-primary-dark); color: var(--color-white); }

.btn-secondary {
  background: var(--color-white);
  color: var(--color-primary);
  border-color: var(--color-primary);
}
.btn-secondary:hover { background: var(--color-primary-light); }

.btn-outline {
  background: transparent;
  color: var(--color-primary);
  border-color: var(--color-border);
}
.btn-outline:hover { border-color: var(--color-primary); background: var(--color-primary-light); }

.btn-ghost {
  background: transparent;
  color: var(--color-primary);
  border-color: transparent;
  padding: 0.5rem 0.75rem;
}
.btn-ghost:hover { color: var(--color-primary-dark); background: var(--color-primary-light); }

.btn-on-brand {
  background: var(--color-white);
  color: var(--color-primary-dark);
  border-color: var(--color-white);
}
.btn-on-brand:hover { background: var(--color-accent); color: var(--color-primary-dark); }

/* Gold conversion CTA — reserved for the single highest-intent action on
   dark surfaces (hero + closing band) so it never competes with itself. */
.btn-gold {
  background: linear-gradient(180deg, var(--color-accent-light) 0%, var(--color-accent) 58%);
  color: var(--color-navy-900);
  border-color: var(--color-accent);
  font-weight: 700;
  box-shadow: 0 10px 26px -12px rgba(201, 162, 77, 0.65);
}
.btn-gold:hover {
  background: linear-gradient(180deg, var(--color-accent-light) 20%, var(--color-accent-dark) 100%);
  color: var(--color-navy-900);
  box-shadow: 0 14px 30px -12px rgba(201, 162, 77, 0.8);
}

.btn-on-brand-outline {
  background: transparent;
  color: var(--color-white);
  border-color: rgba(255,255,255,0.55);
}
.btn-on-brand-outline:hover { border-color: var(--color-white); background: rgba(255,255,255,0.08); }

.btn-danger {
  background: var(--color-danger);
  color: var(--color-white);
  border-color: var(--color-danger);
}
.btn-danger:hover { background: var(--color-danger-dark); }

/* Text-link button — used for low-emphasis inline actions (download a PDF,
   toggle an issue form). Without this it fell through to `.btn`'s baseline,
   which sets no background and so inherited the UA grey button face. */
.btn-link {
  background: transparent;
  border-color: transparent;
  color: var(--color-primary);
  min-height: 0;
  padding: 0.35rem 0.15rem;
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
.btn-link:hover { color: var(--color-primary-dark); text-decoration: none; }
.btn-link:disabled, .btn-link[disabled] { color: var(--color-muted); }

.btn-block { display: flex; width: 100%; }
.btn-sm { padding: 0.5rem 0.9rem; font-size: var(--fs-sm); min-height: 36px; }
.btn-lg { padding: 0.95rem 1.6rem; font-size: var(--fs-md); min-height: 52px; }

/* 5. Cards & panels -------------------------------------------------------- */
.card {
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  box-shadow: var(--shadow-xs);
  /* A column, so the footer can be pushed to the bottom. Cards in a grid row
     are already equal height (grid items stretch), but their actions sat
     immediately after however much copy each card happened to carry — so a
     three-bullet card and a four-bullet card put their buttons at different
     heights, and the row read as ragged. */
  display: flex;
  flex-direction: column;
}
@media (prefers-reduced-motion: no-preference) {
  .card {
    transition: box-shadow 150ms ease, border-color 150ms ease, transform 150ms ease;
  }
  .card:hover {
    box-shadow: var(--shadow-md);
    border-color: var(--color-primary-light);
    transform: translateY(-2px);
  }
}
.card--elevated { box-shadow: var(--shadow-md); border-color: transparent; }
.card--feature {
  background: linear-gradient(180deg, var(--color-white) 0%, var(--color-off-white) 100%);
}
.card--ghost { background: transparent; box-shadow: none; }
.card--brand {
  background: var(--color-primary);
  color: var(--color-white);
  border-color: var(--color-primary);
}
.card--brand h3, .card--brand h4 { color: var(--color-white); }
.card--accent { border-color: var(--color-accent); border-width: 2px; }

.card__icon {
  width: 44px;
  height: 44px;
  border-radius: var(--radius-md);
  background: var(--color-primary-light);
  color: var(--color-primary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.4rem;
  font-weight: 800;
  margin-bottom: var(--space-3);
}
.card--brand .card__icon { background: rgba(255,255,255,0.16); color: var(--color-white); }

.card__title { font-size: var(--fs-lg); margin: 0 0 var(--space-2); }
/* A heading INSIDE a card's body — used by the legal pages, where one
   numbered section covers several distinct topics (cookies, analytics,
   browser storage) and running them together as paragraphs makes the section
   unskimmable. Sized below .card__title so the card still reads as one
   section with parts, not as three cards. */
.card__subtitle {
  font-size: var(--fs-base);
  font-weight: 700;
  margin: var(--space-4) 0 var(--space-2);
}
.card__subtitle:first-child { margin-top: 0; }
/* Explicit margins now that the card is a flex column: adjacent margins no
   longer collapse there, so leaving the browser's default 1em on a <p> would
   silently add a step of space under every card title. */
.card__body { color: var(--color-muted); font-size: var(--fs-sm); margin: 0 0 var(--space-4); }
.card__body:last-child { margin-bottom: 0; }
.card--brand .card__body { color: rgba(255,255,255,0.92); }
/* The actions sit on the floor of the card, whatever the copy above them did.
   `margin-top: auto` eats the leftover space; the padding keeps a minimum gap
   when there is none to eat. */
.card__footer {
  margin-top: auto;
  padding-top: var(--space-4);
  /* One column, so every action is the full width of the card and its label is
     centred — a row of buttons sized to their own text made "Estimate" and
     "Property manager workflow" look like different kinds of control. */
  display: grid;
  gap: var(--space-3);
}

/* Step card (numbered process) */
.step-card { position: relative; padding-top: var(--space-8); }
.step-card__num {
  position: absolute;
  top: var(--space-5);
  right: var(--space-5);
  width: 36px;
  height: 36px;
  border-radius: var(--radius-pill);
  background: var(--color-primary);
  color: var(--color-white);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  font-size: var(--fs-sm);
  border: 2px solid var(--color-accent);
}

/* Add-on card */
.addon-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-5);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  background: var(--color-white);
}
@media (prefers-reduced-motion: no-preference) {
  .addon-card {
    transition: border-color 150ms ease, box-shadow 150ms ease;
  }
  .addon-card:hover {
    border-color: var(--color-primary);
    box-shadow: var(--shadow-sm);
  }
}
/* Name, then price, then hint — the same three lines on every card.
   The name and price used to share one wrapping flex row with the price pushed
   right by `margin-left: auto`, so a card whose two strings fit rendered a tidy
   row and a card whose strings did not rendered the price alone and flush right
   underneath. Two layouts in one grid, chosen by string length. Card names hold
   one line at a 260px column, so stacking them puts every price at the same
   height across a row and it reads as a price column. */
.addon-card__name {
  margin: 0;
  font-weight: 700;
  color: var(--color-text);
  overflow-wrap: anywhere;
}
.addon-card__price {
  margin: 0;
  color: var(--color-primary);
  font-weight: 700;
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
}
.addon-card__hint { margin: 0; color: var(--color-muted); font-size: var(--fs-sm); }

/* Area tier card */
.area-card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  background: var(--color-white);
}
.area-card__head { display: flex; align-items: center; gap: var(--space-3); margin-bottom: var(--space-3); }
.area-card__tier {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.2rem 0.6rem;
  border-radius: var(--radius-pill);
}
.area-card--active .area-card__tier   { background: var(--color-tier-active-bg); color: var(--color-success); }
.area-card--extended .area-card__tier { background: var(--color-tier-extended-bg); color: var(--color-warning-text); }
.area-card--remote .area-card__tier   { background: var(--color-primary-light); color: var(--color-primary); }
.area-card--quote .area-card__tier    { background: var(--color-tier-quote-bg); color: var(--color-tier-quote-text); }
.area-card__towns { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-3); }
.area-card__towns--center { justify-content: center; }
.area-card__town {
  background: var(--color-off-white);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  padding: 0.2rem 0.65rem;
  border-radius: var(--radius-pill);
  font-size: var(--fs-sm);
}

/* Proof chips (trust bar). Renders as a continuous marquee when motion is
   allowed; falls back to a wrapped static row otherwise. The second track
   copy is aria-hidden and only exists to make the loop seamless. */
.proof-bar {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-3) var(--space-6);
  padding: var(--space-5) 0;
  background: var(--color-off-white);
  border-top: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
}
.proof-bar__item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-sm);
  color: var(--color-muted);
  font-weight: 600;
  white-space: nowrap;
}
.proof-bar__dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  flex-shrink: 0;
}
.proof-marquee {
  overflow: hidden;
  padding: var(--space-4) 0;
  background: var(--color-navy-900);
  border-top: 1px solid rgba(255,255,255,0.08);
  border-bottom: 1px solid rgba(255,255,255,0.08);
}
.proof-marquee .proof-bar__item { color: rgba(233,238,246,0.85); }
.proof-marquee__track {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-3) var(--space-8);
  width: 100%;
}
@media (prefers-reduced-motion: no-preference) {
  .proof-marquee__track {
    flex-wrap: nowrap;
    justify-content: flex-start;
    width: max-content;
    animation: marquee-scroll 36s linear infinite;
  }
  .proof-marquee:hover .proof-marquee__track { animation-play-state: paused; }
  @keyframes marquee-scroll {
    to { transform: translateX(-50%); }
  }
}
/* The duplicate track segment is display:none unless the marquee animates. */
.proof-marquee__dup { display: none; }
@media (prefers-reduced-motion: no-preference) {
  .proof-marquee__dup { display: contents; }
}

/* Badge / pill */
.badge {
  display: inline-block;
  font-size: var(--fs-xs);
  font-weight: 700;
  padding: 0.2rem 0.55rem;
  border-radius: var(--radius-pill);
  background: var(--color-primary-light);
  color: var(--color-primary);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.badge-info    { background: var(--color-primary-light); color: var(--color-primary); }
.badge-success { background: var(--color-tier-active-bg); color: var(--color-success); }
.badge-warning { background: var(--color-tier-extended-bg); color: var(--color-warning-text); }
.badge-danger  { background: var(--color-badge-danger-bg); color: var(--color-danger); }

/* 6. Forms & alerts -------------------------------------------------------- */
.form-card {
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-6) var(--space-6) var(--space-5);
  box-shadow: var(--shadow-xs);
}

fieldset {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-5);
  margin: 0 0 var(--space-5);
  background: var(--color-white);
}
legend {
  font-weight: 700;
  color: var(--color-primary);
  padding: 0 var(--space-2);
}

label {
  display: block;
  margin: var(--space-3) 0;
  font-weight: 500;
}
label > input[type="text"],
label > input[type="email"],
label > input[type="tel"],
label > input[type="number"],
label > input[type="password"],
label > input[type="search"],
label > input[type="url"],
label > select,
label > textarea {
  display: block;
  margin-top: var(--space-2);
}

input[type="text"], input[type="email"], input[type="tel"],
input[type="number"], input[type="password"], input[type="search"],
input[type="url"], select, textarea {
  width: 100%;
  max-width: 28rem;
  padding: 0.6rem 0.75rem;
  font-family: inherit;
  font-size: var(--fs-base);
  color: var(--color-text);
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-field);
  min-height: 44px;
}
textarea { min-height: 96px; resize: vertical; }
input:focus, select:focus, textarea:focus {
  border-color: var(--color-primary);
  outline: 2px solid var(--color-primary-light);
  outline-offset: 0;
}
input[type="checkbox"], input[type="radio"] {
  width: auto;
  min-height: auto;
  margin-right: var(--space-2);
  vertical-align: middle;
  accent-color: var(--color-primary);
}

.hint, .form-hint {
  color: var(--color-muted);
  font-size: var(--fs-sm);
  margin-top: var(--space-1);
}

.required-mark { color: var(--color-danger); margin-left: 2px; }

.alert {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  background: var(--color-off-white);
  color: var(--color-text);
  font-size: var(--fs-sm);
  align-items: flex-start;
}
.alert__title { font-weight: 700; margin: 0 0 var(--space-1); color: inherit; }
.alert-info    { background: var(--color-primary-light); border-color: var(--color-info-border); color: var(--color-primary-dark); }
.alert-info .alert__title { color: var(--color-primary); }
.alert-success { background: var(--color-success-bg); border-color: var(--color-success-border); color: var(--color-success-text); }
.alert-warning { background: var(--color-warning-bg); border-color: var(--color-warning-border); color: var(--color-warning-text); }
.alert-danger  { background: var(--color-danger-bg); border-color: var(--color-danger-border); color: var(--color-danger-text); }

/* 7. Site header & footer -------------------------------------------------- */
.site-header {
  background: var(--color-header);
  color: var(--color-white);
  border-bottom: 1px solid rgba(255,255,255,0.08);
  position: sticky;
  top: 0;
  z-index: 50;
  box-shadow: 0 1px 0 rgba(0,0,0,0.06);
  transition: box-shadow var(--dur-med) ease, background-color var(--dur-med) ease;
}
/* site.js adds .is-scrolled after the first ~12px of scroll so the bar
   visibly lifts off the hero it shares a navy background with. */
.site-header.is-scrolled {
  background: rgba(14, 51, 106, 0.92);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  box-shadow: 0 10px 30px -18px rgba(8, 20, 38, 0.9);
}
.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  width: 100%;
  max-width: var(--container-wide);
  margin: 0 auto;
  padding: var(--space-3) var(--space-6);
  min-height: var(--header-height);
}

.brand {
  display: inline-flex;
  align-items: center;
  /* The mark is a 30px-tall landscape silhouette now, not a 44px square, and
     the square was the only thing holding this link above the 44px tap
     target. Pin the target to the link instead of to the artwork, so the mark
     can be whatever height reads best. */
  min-height: 44px;
  gap: var(--space-3);
  text-decoration: none;
  color: inherit;
  font-weight: 700;
  letter-spacing: 0.01em;
}
/* The mark is a 4.19:1 landscape silhouette, not the old square lockup, so it
   is sized by height and left to find its own width. 30px keeps the whole
   brand (mark + wordmark) inside the header of a 360px phone next to the menu
   button; the square it replaced was 44px because a square could afford it. */
.brand img.brand__logo {
  height: 30px;
  width: auto;
  display: block;
}
.brand__text { display: inline-flex; flex-direction: column; line-height: 1.05; }
.brand-mark { font-weight: 800; letter-spacing: 0.06em; font-size: 1.05rem; color: var(--color-white); }
.brand-tag {
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--color-accent);
  font-weight: 600;
}

.site-nav { display: none; }
.site-nav__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  gap: var(--space-2);
  align-items: center;
}
.site-nav__link {
  color: var(--color-white);
  text-decoration: none;
  font-weight: 500;
  font-size: var(--fs-sm);
  padding: 0.45rem 0.75rem;
  border-radius: var(--radius-sm);
  display: inline-flex;
  align-items: center;
  line-height: 1.2;
  transition: background-color 120ms ease, color 120ms ease;
}
.site-nav__link:hover, .site-nav__link.is-active { background: var(--color-primary-dark); color: var(--color-white); }
.site-nav__link[aria-current="page"] {
  background: var(--color-primary-dark);
  color: var(--color-white);
}
.site-nav .nav-cta {
  background: var(--color-accent);
  color: var(--color-primary-dark);
  padding: 0.55rem 1.05rem;
  font-weight: 700;
  border-radius: var(--radius-md);
}
.site-nav .nav-cta:hover { background: var(--color-accent-dark); color: var(--color-primary-dark); }

/* --- the auth-aware CTA ----------------------------------------------------
   One control in two colours. Signed out it is a navy pill: a real button on
   the navy bar rather than a fifth text link, so the signed-in state has
   something to change FROM. Signed in it goes solid gold, which on this header
   is the loudest thing available and therefore has to be the only one — see
   the estimate demotion below.

   The label swaps between "Sign in" (86px) and a dashboard, so the control
   reserves the widest footprint from first paint and the nav does not reflow
   when the session resolves. Measured, not guessed: in Plus Jakarta the widest
   label is "Admin Dashboard" at 165px, NOT the longer-looking "Client
   Dashboard" at 160px. 10.5rem covers it with a little air.
   --------------------------------------------------------------------------- */
.site-nav__cta {
  min-width: 10.5rem;
  justify-content: center;
  padding: 0.55rem 1.05rem;
  font-weight: 700;
  /* --color-primary-dark on --color-header is a 1.2:1 fill difference, so the
     border is what makes this read as a button at all, and WCAG 1.4.11 wants
     3:1 for a boundary doing that job. White at 0.45 over the navy composites
     to 3.4:1 against the bar; the 0.28 it started at was 2.0:1 and looked like
     a shadow. */
  border: 1px solid rgba(255, 255, 255, 0.45);
  border-radius: var(--radius-md);
  background: var(--color-primary-dark);
  color: var(--color-white);
}
.site-nav__cta:hover,
.site-nav__cta.is-active {
  background: var(--color-primary-dark);
  border-color: rgba(255, 255, 255, 0.72);
  color: var(--color-white);
}
.site-nav__cta:active { transform: translateY(1px); }

/* Signed in. Gold surface, navy text: the pairing .nav-cta already uses and
   the audit already measures for AA, rather than white-on-gold, which is
   2.3:1 and unreadable. */
[data-auth-state="signed-in"] .site-nav__cta {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-primary-dark);
}
[data-auth-state="signed-in"] .site-nav__cta:hover,
[data-auth-state="signed-in"] .site-nav__cta.is-active {
  background: var(--color-accent-dark);
  border-color: var(--color-accent-dark);
  color: var(--color-primary-dark);
}
/* Pressed. The 1px nudge alone is invisible on a touch screen under a finger,
   so the press also darkens. */
[data-auth-state="signed-in"] .site-nav__cta:active,
[data-auth-state="signed-in"] .mobile-nav__cta:active {
  background: var(--color-accent-dark);
  border-color: var(--color-accent-dark);
  color: var(--color-primary-dark);
}
.mobile-nav__cta:active { transform: translateY(1px); }
/* The default focus ring is navy-on-navy-bar; on a gold surface it disappears.
   Light gold reads against both the gold fill and the navy bar behind it. */
[data-auth-state="signed-in"] .site-nav__cta:focus-visible,
[data-auth-state="signed-in"] .mobile-nav__cta:focus-visible {
  outline: 3px solid var(--color-accent-light);
  outline-offset: 2px;
}

/* Two solid gold buttons side by side would mean neither of them is the
   signed-in signal. When someone is already in, their dashboard is the
   errand and a fresh quote is the afterthought, so the estimate button steps
   back to an outline and gold-solid keeps meaning one thing. */
[data-auth-state="signed-in"] .site-nav .nav-cta,
[data-auth-state="signed-in"] .mobile-nav .nav-cta {
  background: transparent;
  border: 1px solid var(--color-accent);
  color: var(--color-accent-light);
}
[data-auth-state="signed-in"] .site-nav .nav-cta:hover,
[data-auth-state="signed-in"] .mobile-nav .nav-cta:hover {
  background: var(--color-accent-soft);
  color: var(--color-white);
}

/* Resolving: a credential exists and the server has not answered yet. The
   control keeps its size and its working href and simply declines to claim a
   state, because the one thing worse than a late label is a wrong one. */
[data-auth-state="resolving"] .site-nav__cta,
[data-auth-state="resolving"] .mobile-nav__cta { opacity: 0.55; }
@media (prefers-reduced-motion: no-preference) {
  .site-nav__cta, .mobile-nav__cta { transition: opacity 160ms ease, background-color 160ms ease, border-color 160ms ease, color 160ms ease; }
}

/* The band where five nav items plus the reserved CTA plus the estimate button
   is tightest. Buy the room back from padding rather than letting the row
   wrap. */
@media (min-width: 900px) and (max-width: 1080px) {
  .site-nav__list { gap: var(--space-1); }
  .site-nav__link { padding-inline: var(--space-2); }
  /* Same rule, smaller type metrics: 156px is the widest label here. */
  .site-nav__cta { min-width: 9.875rem; padding-inline: var(--space-3); }
  .site-nav .nav-cta { padding-inline: var(--space-3); }
}

/* Services dropdown — CSS-only hover/focus reveal, JS toggles
   aria-expanded and handles Escape. Sub-items inherit the
   site-nav__dropdown styling, not site-nav__link, so the top-level
   nav count stays at exactly 5. */
.site-nav__item--has-dropdown { position: relative; }
.site-nav__link--with-caret { display: inline-flex; gap: var(--space-1); align-items: center; }
.site-nav__caret {
  font-size: 0.7em;
  line-height: 1;
  margin-left: 0.15rem;
  transition: transform 150ms ease;
}
.site-nav__item--has-dropdown:hover .site-nav__caret,
.site-nav__item--has-dropdown:focus-within .site-nav__caret {
  transform: rotate(180deg);
}
/* The caret is the visible half of `aria-expanded`; it follows the same one
   state, so it cannot point down at an open panel. */
.site-nav__item--has-dropdown[data-nav-enhanced="true"]:not([data-open="true"]) .site-nav__caret {
  transform: none;
}
.site-nav__item--has-dropdown[data-nav-enhanced="true"][data-open="true"] .site-nav__caret {
  transform: rotate(180deg);
}
.site-nav__dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  min-width: 220px;
  margin: 0;
  padding: var(--space-2);
  list-style: none;
  background: var(--color-white);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  display: none;
  z-index: 60;
}
.site-nav__dropdown li { margin: 0; }
.site-nav__dropdown a {
  display: block;
  padding: 0.55rem 0.75rem;
  font-size: var(--fs-sm);
  color: var(--color-text);
  text-decoration: none;
  border-radius: var(--radius-sm);
  font-weight: 500;
  min-height: 36px;
  line-height: 1.25;
}
.site-nav__dropdown a:hover,
.site-nav__dropdown a:focus-visible,
.site-nav__dropdown a.is-active,
.site-nav__dropdown a[aria-current="page"] {
  background: var(--color-primary-light);
  color: var(--color-primary-dark);
}
.site-nav__item--has-dropdown:hover > .site-nav__dropdown,
.site-nav__item--has-dropdown:focus-within > .site-nav__dropdown {
  display: block;
}
/* TRC-A11Y-20260822-002. The rules above are the no-JavaScript baseline and
   nothing else. Once public/js/site.js runs it stamps `data-nav-enhanced` and
   owns one `data-open` state, and these rules make that state the sole
   authority over visibility — so the panel a sighted visitor sees and the
   `aria-expanded` a screen reader is told can no longer disagree. The
   previous `data-force-closed` override was defeated by the very focus move
   the Escape handler performed; a single source of truth cannot be. */
.site-nav__item--has-dropdown[data-nav-enhanced="true"]:not([data-open="true"]) > .site-nav__dropdown {
  display: none;
}
.site-nav__item--has-dropdown[data-nav-enhanced="true"][data-open="true"] > .site-nav__dropdown {
  display: block;
}

.nav-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid rgba(255,255,255,0.35);
  color: var(--color-white);
  width: 44px;
  height: 44px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  padding: 0;
}
.nav-toggle:hover { background: rgba(255,255,255,0.08); }
.nav-toggle__bars {
  display: block;
  width: 22px;
  height: 2px;
  background: currentColor;
  position: relative;
}
.nav-toggle__bars::before, .nav-toggle__bars::after {
  content: "";
  position: absolute;
  left: 0;
  width: 22px;
  height: 2px;
  background: currentColor;
}
.nav-toggle__bars::before { top: -7px; }
.nav-toggle__bars::after  { top:  7px; }

.mobile-nav {
  display: none;
  background: var(--color-primary-dark);
  border-top: 1px solid rgba(255,255,255,0.08);
}
.mobile-nav.is-open { display: block; }
.mobile-nav__list {
  list-style: none;
  margin: 0;
  padding: var(--space-3) var(--space-6) var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.mobile-nav__link {
  display: block;
  color: var(--color-white);
  text-decoration: none;
  font-weight: 500;
  padding: 0.9rem 0.75rem;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  font-size: var(--fs-md);
}
.mobile-nav__link:hover { background: rgba(255,255,255,0.06); }
/* Mobile sub-items live always-visible under their Services parent,
   indented so the hierarchy reads without a JS toggle. They are not
   counted as top-level mobile nav entries. */
.mobile-nav__sub {
  display: block;
  color: rgba(255,255,255,0.85);
  text-decoration: none;
  font-weight: 500;
  font-size: var(--fs-sm);
  padding: 0.7rem 0.75rem 0.7rem var(--space-8);
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  min-height: 44px;
}
.mobile-nav__sub:hover,
.mobile-nav__sub.is-active,
.mobile-nav__sub[aria-current="page"] {
  background: rgba(255,255,255,0.08);
  color: var(--color-white);
}
.mobile-nav .nav-cta {
  background: var(--color-accent);
  color: var(--color-primary-dark);
  font-weight: 700;
  text-align: center;
  margin-top: var(--space-3);
}
/* The same control under a thumb. Rows here are full-width and stacked, so a
   longer label costs nothing in layout; only the colour carries the state. */
.mobile-nav__cta { font-weight: 700; }
[data-auth-state="signed-in"] .mobile-nav__cta {
  background: var(--color-accent);
  color: var(--color-primary-dark);
}
[data-auth-state="signed-in"] .mobile-nav__cta:hover,
[data-auth-state="signed-in"] .mobile-nav__cta.is-active,
[data-auth-state="signed-in"] .mobile-nav__cta[aria-current="page"] {
  background: var(--color-accent-dark);
  color: var(--color-primary-dark);
}

/* Footer */
.site-footer {
  background: var(--color-primary-dark);
  color: rgba(255,255,255,0.92);
  margin-top: auto;
}
.site-footer__inner {
  max-width: var(--container-wide);
  margin: 0 auto;
  padding: var(--space-12) var(--space-6) var(--space-8);
  display: grid;
  gap: var(--space-8);
  /* Brand left, the accordion stack right. Four COLLAPSED columns would be a
     row of four headings with nothing under them, and opening one would grow a
     single grid cell while its neighbours sat empty — so the categories become
     full-width rows instead, and the footer reads the same at 360px as it does
     at 1440px. That sameness is the point of collapsing at every width. */
  grid-template-columns: 1.2fr 1fr;
  align-items: start;
}
.site-footer h3 {
  color: var(--color-white);
  font-size: var(--fs-sm);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin: 0;
  font-weight: 700;
}
.site-footer ul { list-style: none; padding: 0; margin: 0; }
/* The 44px link box now carries the row rhythm; keeping the old margin
   as well would stretch the footer by a third on a phone. */
.site-footer li { margin-bottom: 0; }
.site-footer a {
  color: rgba(255,255,255,0.85);
  text-decoration: none;
  font-size: var(--fs-sm);
  /* Footer nav links are stacked list items, and on a phone the whole footer
     was a column of 17px targets. A 44px box makes each one tappable without
     changing how the column looks. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
/* Links inside the footer's stacked lists span their row, so a short label
   like "FAQ" is still a full-width target instead of a 30px one. */
.site-footer li > a { display: flex; }
.site-footer a:hover { color: var(--color-white); text-decoration: underline; }

/* --- footer categories ---------------------------------------------------
   Four <details> accordions, closed at every viewport, no JavaScript. Drawn
   the same way as the FAQ accordion (see `.faq-item`) so the product has one
   disclosure component rather than two that merely behave alike. */
.site-footer__cats {
  display: grid;
  /* The rule between rows is the only line here; the categories separate by
     spacing and type everywhere else. */
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}
.footer-cat { border-bottom: 1px solid rgba(255, 255, 255, 0.12); }
.footer-cat > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  /* A footer row is a target before it is a heading. */
  min-height: 44px;
  padding: var(--space-2) 0;
  cursor: pointer;
  list-style: none;
}
.footer-cat > summary::-webkit-details-marker { display: none; }
.footer-cat > summary::after {
  content: "+";
  font-size: var(--fs-xl);
  font-weight: 400;
  line-height: 1;
  flex-shrink: 0;
  /* The light gold, not --color-accent-dark: this sits on navy, where the dark
     gold is a fill colour and this is a character someone has to see. */
  color: var(--color-accent-light);
  transition: transform var(--dur-quick) var(--ease-out);
}
.footer-cat[open] > summary::after { transform: rotate(45deg); }
/* The row responds as one thing, and it responds by getting BRIGHTER — on a
   navy ground the darker gold would be a hover state with less contrast than
   its resting state. Colour only: a background highlight would need horizontal
   padding on the summary, which would then disagree with the flush list rows
   underneath it. */
.footer-cat > summary:hover h3 { color: var(--color-accent-light); }
.footer-cat > summary:hover::after { color: var(--color-white); }
.footer-cat > summary:focus-visible {
  outline: 2px solid var(--color-accent-light);
  outline-offset: 2px;
}
/* The panel opens instantly. <details> cannot be height-transitioned without
   ::details-content (Chrome 131+), and a scripted height animation on a footer
   is the sort of transition theatrics this design explicitly rules out — so the
   only thing that moves is the marker. */
.footer-cat > ul { padding-bottom: var(--space-3); }
/* The plain-text lines under Coverage are not links and would otherwise sit at
   a different rhythm from the rows around them. */
.footer-cat li {
  color: rgba(255, 255, 255, 0.85);
  font-size: var(--fs-sm);
  display: flex;
  align-items: center;
  min-height: 44px;
}
/* …but making the row a flex CONTAINER turns its link into a flex ITEM, which
   shrinks to its text — "FAQ" became a 30px target, undoing the full-width row
   `.site-footer li > a` exists to guarantee. Grow it back. */
.footer-cat li > a { flex: 1 1 auto; }
@media (prefers-reduced-motion: reduce) {
  .footer-cat > summary::after { transition: none; }
}
.site-footer__promise { color: rgba(255,255,255,0.78); font-size: var(--fs-sm); margin-top: var(--space-3); }
.site-footer__contact {
  display: grid;
  gap: var(--space-1);
  margin-top: var(--space-4);
  font-size: var(--fs-sm);
}
.site-footer__contact a {
  color: var(--color-accent-light);
  font-weight: 600;
  /* The email and phone in the footer are how someone reaches a person. At
     23px tall they were the smallest targets on the site. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
.site-footer__contact .brand-phone {
  font-family: var(--font-display);
  font-size: var(--fs-md);
  color: var(--color-white);
  text-decoration: none;
}
.site-footer__contact .brand-phone:hover { color: var(--color-accent-light); text-decoration: underline; }
.site-footer__brand img { height: 56px; width: auto; }
.site-footer__legal-heading { margin-top: var(--space-6); }
.site-footer__bottom {
  border-top: 1px solid rgba(255,255,255,0.12);
  padding: var(--space-5) var(--space-6);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-3);
  max-width: var(--container-wide);
  margin: 0 auto;
  font-size: var(--fs-xs);
  color: rgba(255,255,255,0.7);
}
.site-footer__bottom a {
  color: inherit;
  /* The legal row sits at the very bottom of every page and its links were
     30-41px wide. Padding widens the target without moving the type. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--space-2);
}

/* 8. Page hero ------------------------------------------------------------- */
/* Layered navy: deep gradient base, faint topographic contours (nod to the
   Table Rock mesa in the lockup), and a warm gold glow off the top edge. */
.page-hero {
  background:
    radial-gradient(120% 90% at 85% -20%, rgba(201,162,77,0.22), transparent 55%),
    radial-gradient(90% 70% at -10% 110%, rgba(22,52,94,0.9), transparent 60%),
    linear-gradient(178deg, var(--color-navy-900) 0%, var(--color-primary) 90%);
  color: var(--color-white);
  padding: var(--space-16) 0 var(--space-12);
  position: relative;
  overflow: hidden;
}
.page-hero h1 { color: var(--color-white); }
.page-hero p { color: rgba(255,255,255,0.92); }
.page-hero .lede { color: rgba(233,238,246,0.9); font-size: var(--fs-md); max-width: 62ch; }

.home-hero {
  background:
    radial-gradient(110% 80% at 88% -15%, rgba(201,162,77,0.24), transparent 52%),
    radial-gradient(80% 90% at -15% 115%, rgba(22,52,94,0.95), transparent 62%),
    linear-gradient(178deg, var(--color-navy-950) 0%, var(--color-navy-900) 45%, var(--color-primary) 100%);
  color: var(--color-white);
  padding: var(--space-20) 0 var(--space-20);
  position: relative;
  overflow: hidden;
}
/* Faint contour rings — drawn once as repeating radial strokes, so the navy
   field reads as terrain instead of a flat fill. Pure decoration. */
.home-hero::before,
.page-hero::before {
  content: "";
  position: absolute;
  inset: -40% -20%;
  background: repeating-radial-gradient(
    ellipse 90% 70% at 78% 18%,
    transparent 0px,
    transparent 56px,
    rgba(255, 255, 255, 0.045) 57px,
    transparent 58px
  );
  pointer-events: none;
}
/* Fine film-grain over every hero (shared with .home-hero__grain) so the
   navy gradients never band on wide displays and every page hero carries the
   same tactile depth as the homepage. Decorative; sits under the container. */
.page-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
  opacity: 0.045;
  mix-blend-mode: overlay;
}
.home-hero > .container,
.page-hero > .container { position: relative; z-index: 1; }
.home-hero h1 { color: var(--color-white); font-size: var(--fs-hero); }
.home-hero p { color: rgba(255,255,255,0.92); }
.home-hero .lede { color: rgba(233,238,246,0.92); font-size: clamp(1.1rem, 1rem + 0.5vw, 1.3rem); }
.home-hero__layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-10);
  align-items: center;
}
.home-hero__copy h1 { margin-bottom: var(--space-5); }
.home-hero__copy .lede { max-width: 46ch; }
.home-hero__ctas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-8);
}
.home-hero__note {
  margin-top: var(--space-4);
  font-size: var(--fs-sm);
  color: rgba(233,238,246,0.75);
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.home-hero__note::before {
  content: "";
  width: 7px; height: 7px;
  border-radius: var(--radius-pill);
  background: var(--color-success);
  box-shadow: 0 0 0 4px rgba(40, 122, 75, 0.35);
  flex-shrink: 0;
}

/* Hero entrance — copy cascades up, panel floats in from the right.
   Everything lands within 1s; reduced-motion users see the final frame. */
@media (prefers-reduced-motion: no-preference) {
  @keyframes hero-rise {
    from { opacity: 0; transform: translateY(26px); }
    to   { opacity: 1; transform: none; }
  }
  @keyframes hero-drift {
    from { opacity: 0; transform: translateY(34px) rotate(0.6deg); }
    to   { opacity: 1; transform: none; }
  }
  .home-hero__copy > * { animation: hero-rise var(--dur-reveal) var(--ease-out) both; }
  .home-hero__copy > *:nth-child(1) { animation-delay: 60ms; }
  .home-hero__copy > *:nth-child(2) { animation-delay: 140ms; }
  .home-hero__copy > *:nth-child(3) { animation-delay: 230ms; }
  .home-hero__copy > *:nth-child(4) { animation-delay: 320ms; }
  .home-hero__copy > *:nth-child(5) { animation-delay: 400ms; }
  .home-hero__visual { animation: hero-drift 0.9s var(--ease-out) 250ms both; }
}

/* The hero visual is a "job record" product panel: the site's actual output
   (checklist, GPS stamps, photo evidence, price trace) rendered as a card.
   Decorative (aria-hidden) — the real records live in the portal. */
.home-hero__visual {
  display: flex;
  align-items: center;
  justify-content: center;
}
.hero-panel {
  width: min(430px, 100%);
  background: linear-gradient(180deg, #FFFFFF 0%, #F6F8FB 100%);
  color: var(--color-text);
  border-radius: var(--radius-xl);
  box-shadow:
    0 40px 80px -30px rgba(5, 14, 28, 0.65),
    0 12px 28px -12px rgba(5, 14, 28, 0.4);
  border: 1px solid rgba(255,255,255,0.5);
  padding: var(--space-5) var(--space-5) var(--space-4);
  position: relative;
}
.hero-panel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--color-border);
  margin-bottom: var(--space-3);
}
.hero-panel__title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-sm);
  color: var(--color-navy-800);
}
.hero-panel__title small {
  display: block;
  font-family: var(--font-sans);
  font-weight: 500;
  color: var(--color-muted);
  font-size: var(--fs-xs);
  margin-top: 2px;
}
.hero-panel__badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--color-success);
  background: var(--color-success-bg);
  border: 1px solid var(--color-success-border);
  padding: 0.25rem 0.6rem;
  border-radius: var(--radius-pill);
  white-space: nowrap;
}
.hero-panel__badge::before {
  content: "";
  width: 7px; height: 7px;
  border-radius: var(--radius-pill);
  background: var(--color-success);
}
@media (prefers-reduced-motion: no-preference) {
  .hero-panel__badge::before { animation: pulse-dot 2.4s ease-in-out infinite; }
  @keyframes pulse-dot {
    0%, 100% { box-shadow: 0 0 0 0 rgba(40,122,75,0.45); }
    55%      { box-shadow: 0 0 0 5px rgba(40,122,75,0); }
  }
}
.hero-panel__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.hero-panel__item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--fs-sm);
  color: var(--color-text);
  padding: var(--space-2) var(--space-3);
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}
.hero-panel__check {
  width: 20px; height: 20px;
  border-radius: var(--radius-pill);
  background: var(--color-success-bg);
  border: 1px solid var(--color-success-border);
  color: var(--color-success);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.hero-panel__check svg { width: 11px; height: 11px; display: block; }
@media (prefers-reduced-motion: no-preference) {
  .hero-panel__item { animation: hero-rise 0.45s var(--ease-out) both; }
  .hero-panel__item:nth-child(1) { animation-delay: 450ms; }
  .hero-panel__item:nth-child(2) { animation-delay: 600ms; }
  .hero-panel__item:nth-child(3) { animation-delay: 750ms; }
  .hero-panel__check svg { stroke-dasharray: 14; stroke-dashoffset: 14; animation: draw-check 0.4s ease-out forwards; }
  .hero-panel__item:nth-child(1) .hero-panel__check svg { animation-delay: 650ms; }
  .hero-panel__item:nth-child(2) .hero-panel__check svg { animation-delay: 800ms; }
  .hero-panel__item:nth-child(3) .hero-panel__check svg { animation-delay: 950ms; }
  @keyframes draw-check { to { stroke-dashoffset: 0; } }
}
/* Print / static captures must always show the final frame. */
@media print {
  [data-reveal], [data-reveal-group] > *,
  .home-hero__copy > *, .home-hero__visual,
  .hero-panel__item, .hero-panel__chip {
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
  }
}

.hero-panel__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-3) 0;
}
.hero-panel__chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--color-navy-700);
  background: var(--color-primary-light);
  border: 1px solid var(--color-info-border);
  border-radius: var(--radius-pill);
  padding: 0.28rem 0.65rem;
}
.hero-panel__chip svg { width: 12px; height: 12px; flex-shrink: 0; }
@media (prefers-reduced-motion: no-preference) {
  .hero-panel__chip { animation: hero-rise 0.45s var(--ease-out) both; }
  .hero-panel__chip:nth-child(1) { animation-delay: 950ms; }
  .hero-panel__chip:nth-child(2) { animation-delay: 1080ms; }
  .hero-panel__chip:nth-child(3) { animation-delay: 1210ms; }
}
.hero-panel__total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  border-top: 1px dashed var(--color-border);
  padding-top: var(--space-3);
  font-size: var(--fs-sm);
  color: var(--color-muted);
}
.hero-panel__total strong {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  color: var(--color-navy-800);
}

/* Hero atmosphere — a slow-drifting aurora glow + a fine grain overlay layered
   beneath the copy so the navy field reads as depth, not a flat fill. Both are
   decorative (aria-hidden) and sit behind content via z-index. The aura only
   animates when motion is allowed; the grain is static. */
.home-hero__aura,
.home-hero__grain { position: absolute; inset: 0; pointer-events: none; z-index: 0; }
.home-hero__aura {
  background:
    radial-gradient(38% 44% at 82% 8%, rgba(201, 162, 77, 0.30), transparent 70%),
    radial-gradient(46% 50% at 12% 92%, rgba(38, 88, 156, 0.55), transparent 72%),
    radial-gradient(30% 34% at 62% 64%, rgba(84, 140, 210, 0.20), transparent 75%);
  filter: blur(2px);
  opacity: 0.9;
}
@media (prefers-reduced-motion: no-preference) {
  .home-hero__aura { animation: aura-drift 18s var(--ease-out) infinite alternate; }
  @keyframes aura-drift {
    from { transform: translate3d(-1.5%, -1%, 0) scale(1.02); }
    to   { transform: translate3d(2%, 1.5%, 0) scale(1.08); }
  }
}
.home-hero__grain {
  /* Tiny fractal-noise SVG (data URI allowed by CSP img-src data:). Adds a
     film-grain tooth so gradients never band on wide displays. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
  opacity: 0.05;
  mix-blend-mode: overlay;
}
.home-hero .container { position: relative; z-index: 1; }

/* Gold keyword accent on the hero headline — a warm gradient fill with a
   hand-drawn underline stroke that "writes in" once when motion is allowed. */
.hero-accent {
  position: relative;
  color: var(--color-accent);
  background: linear-gradient(96deg, var(--color-accent-light) 0%, var(--color-accent) 55%, #E7CE8F 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  white-space: nowrap;
}
.hero-accent__stroke {
  position: absolute;
  left: -0.04em;
  right: -0.04em;
  bottom: -0.14em;
  height: 0.34em;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='24' viewBox='0 0 240 24' fill='none'%3E%3Cpath d='M4 15 Q60 4 120 11 T236 9' stroke='%23C9A24D' stroke-width='4.5' stroke-linecap='round'/%3E%3C/svg%3E") no-repeat center / 100% 100%;
  opacity: 0.9;
}
@media (prefers-reduced-motion: no-preference) {
  .hero-accent__stroke {
    transform: scaleX(0);
    transform-origin: left center;
    animation: accent-write 0.75s var(--ease-out) 0.65s both;
  }
  @keyframes accent-write { to { transform: scaleX(1); } }
}

/* Reusable gold keyword accent for headlines on dark (navy) surfaces — the
   hero's signature, used sparingly to accent one word per page headline so the
   site reads as one brand. No absolute underline (unlike .hero-accent), so it
   drops into any headline size cleanly. */
.accent-word {
  background: linear-gradient(96deg, var(--color-accent-light) 0%, var(--color-accent) 55%, #E7CE8F 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: var(--color-accent);
}

/* Hero proof-stat row — three factual capability figures (not testimonials)
   that anchor the value prop directly under the CTAs. */
.hero-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-4);
  margin: var(--space-8) 0 0;
  padding-top: var(--space-6);
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  max-width: 30rem;
}
.hero-stats__item { min-width: 0; }
.hero-stats dt {
  font-size: var(--fs-xs);
  color: rgba(233, 238, 246, 0.68);
  letter-spacing: 0.02em;
  margin-bottom: 2px;
}
.hero-stats dd {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--fs-xl);
  color: var(--color-white);
  line-height: 1.05;
  display: flex;
  align-items: baseline;
  gap: 0.15em;
}
.hero-stats dd sup { color: var(--color-accent); font-size: 0.6em; top: -0.5em; }
.hero-stats__unit {
  display: block;
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--color-accent-light);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Pointer-tilt scaffolding for the hero panel. The JS sets --tilt-x/--tilt-y
   custom properties on pointermove (pointer:fine + motion allowed only); the
   transform reads them so the card leans toward the cursor. Defaults to flat. */
.hero-panel[data-tilt] {
  transform: perspective(1100px) rotateX(var(--tilt-x, 0deg)) rotateY(var(--tilt-y, 0deg));
  transition: transform 260ms var(--ease-out);
  will-change: transform;
}
@media (prefers-reduced-motion: no-preference) {
  .home-hero__visual { animation: hero-drift 0.9s var(--ease-out) 250ms both, hero-float 7s ease-in-out 1.2s infinite; }
  @keyframes hero-float {
    0%, 100% { translate: 0 0; }
    50%      { translate: 0 -10px; }
  }
}

/* Mesa ridge divider — the flat-topped silhouette from the logo, drawn as
   the bottom edge of a navy band descending into the next section. Place it
   directly after the navy section; the SVG fill inherits `color`, which must
   match the navy the band ends on. */
.ridge-divider {
  display: block;
  width: 100%;
  height: clamp(30px, 5.4vw, 68px);
  color: var(--color-primary);
  pointer-events: none;
  line-height: 0;
}
.ridge-divider--deep { color: var(--color-navy-950); }
.ridge-divider svg { display: block; width: 100%; height: 100%; }

/* 9. Sections & trust modules --------------------------------------------- */
.feature-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: var(--space-3);
}
/* The bullet is positioned, not a flex sibling.

   `display: flex` on the <li> made EVERY child its own flex item — the dot,
   each text run, and each <strong>/<a>/<em>. So a bullet written as ordinary
   prose with a bold lead-in ("**Cancelling or rescheduling.** Change or cancel
   at least 48 hours ahead…") rendered as narrow vertical columns: the label
   squeezed into a four-line stack beside its own sentence. On /terms the
   STOP/HELP bullet — two <strong>s in one sentence — came out in six columns
   and was close to unreadable, on the page that tells customers how to stop
   receiving texts.

   Twenty-five list items across eleven pages contain inline markup and were
   all bent the same way. Absolute positioning leaves the content in normal
   inline flow, so the markup means what it says, and the dot lands where it
   did before. */
.feature-list li {
  position: relative;
  padding-left: calc(8px + var(--space-3));
}
.feature-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.5rem;
  width: 8px;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--color-primary);
}
.feature-list--accent li::before { background: var(--color-accent); }
.feature-list--light li::before  { background: var(--color-white); }

.cta-band {
  background: var(--color-primary);
  color: var(--color-white);
  padding: var(--space-12) 0;
  text-align: center;
}
.cta-band h2 { color: var(--color-white); margin-bottom: var(--space-3); }
.cta-band p { color: rgba(255,255,255,0.92); max-width: 56ch; margin: 0 auto var(--space-6); }
.cta-band .cluster { justify-content: center; }

.callout-split {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-8);
  align-items: center;
}
.callout-split__media {
  background: var(--color-primary-light);
  border-radius: var(--radius-xl);
  padding: var(--space-6);
  border: 1px solid var(--color-border);
}
.callout-split__media ul { margin: 0; padding-left: 1.1rem; }

/* 10. Service page template ------------------------------------------------ */
.service-section h2 { margin-bottom: var(--space-3); }

.included-grid { display: grid; gap: var(--space-3); grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.included-grid li {
  display: flex; gap: var(--space-2); align-items: flex-start;
  background: var(--color-white); border: 1px solid var(--color-border);
  border-radius: var(--radius-md); padding: var(--space-3) var(--space-4);
}
.included-grid li::before {
  content: "";
  display: inline-block;
  width: 10px; height: 10px;
  border-radius: var(--radius-pill);
  background: var(--color-primary);
  margin-top: 0.45rem;
  flex-shrink: 0;
}

.policy-list { display: grid; gap: var(--space-4); }
.policy-list > div {
  background: var(--color-off-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-4) var(--space-5);
}
.policy-list h3,
.policy-list h4 { margin: 0 0 var(--space-1); color: var(--color-primary); font-size: var(--fs-lg); font-weight: 600; }
.policy-list p  { margin: 0; color: var(--color-muted); font-size: var(--fs-sm); }

/* 11. Estimate wizard ----------------------------------------------------- */
/* Progress: a single slim track with six nodes instead of six pill cards.
   Completed nodes are enabled buttons (jump back); the current node wears a
   halo ring and aria-current; future nodes stay disabled so the rail can
   never skip a step forward. estimate.js sets data-status per node and the
   fill fraction via --wizard-progress on the track. */
.wizard-progress { margin: 0 0 var(--space-6); }
.wizard-progress__meta {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  margin: 0 0 var(--space-3);
}
.wizard-progress__count {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-accent-text);
}
.wizard-progress__label {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-md);
  color: var(--color-ink);
}
.wizard-progress__track { position: relative; height: 44px; }
.wizard-progress__rail,
.wizard-progress__fill {
  position: absolute;
  top: 50%;
  left: 22px;
  height: 3px;
  transform: translateY(-50%);
  border-radius: var(--radius-pill);
}
.wizard-progress__rail { right: 22px; background: var(--color-border); }
.wizard-progress__fill {
  width: calc((100% - 44px) * var(--wizard-progress, 0));
  background: linear-gradient(90deg, var(--color-navy-700), var(--color-primary));
}
@media (prefers-reduced-motion: no-preference) {
  .wizard-progress__fill { transition: width var(--dur-med) var(--ease-out); }
}
.wizard-progress__nodes {
  list-style: none;
  margin: 0;
  padding: 0;
  position: relative;
  display: flex;
  justify-content: space-between;
}
/* The button is a 44x44 touch target; the disc inside is the 28px visual.
   Status styling lives on the disc, interactivity on the button. */
.wizard-progress__node {
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: default;
  font: inherit;
}
.wizard-progress__disc {
  width: 28px;
  height: 28px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--color-border);
  background: var(--color-white);
  color: var(--color-white);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
@media (prefers-reduced-motion: no-preference) {
  .wizard-progress__disc {
    transition:
      border-color var(--dur-quick) var(--ease-out),
      background-color var(--dur-quick) var(--ease-out),
      box-shadow var(--dur-quick) var(--ease-out),
      transform var(--dur-quick) var(--ease-out);
  }
}
.wizard-progress__node:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: -2px;
  border-radius: var(--radius-pill);
}
.wizard-progress__check { display: none; width: 13px; height: 13px; }
.wizard-progress__dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--color-border);
}
.wizard-progress__node[data-status="active"] .wizard-progress__disc {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 4px var(--color-primary-light);
}
.wizard-progress__node[data-status="active"] .wizard-progress__dot {
  background: var(--color-primary);
}
.wizard-progress__node[data-status="done"] { cursor: pointer; }
.wizard-progress__node[data-status="done"] .wizard-progress__disc {
  border-color: var(--color-primary);
  background: var(--color-primary);
}
.wizard-progress__node[data-status="done"] .wizard-progress__check { display: block; }
.wizard-progress__node[data-status="done"] .wizard-progress__dot { display: none; }
.wizard-progress__node[data-status="done"]:hover .wizard-progress__disc {
  background: var(--color-primary-dark);
  border-color: var(--color-primary-dark);
  transform: scale(1.1);
}

.estimate-form { display: block; }
.estimate-form .hint { margin-top: var(--space-2); }

/* Wizard steps. Only the visible step is rendered; the rest are hidden via
   the `hidden` attribute (global [hidden] rule). The step itself is the card:
   an elevated white surface whose fieldset dissolves into it, with the legend
   restyled as the card title and the Back/Continue row separated by a
   hairline. The legend float trick removes the browser's border-notch
   rendering so the title flows like a normal block. */
.estimate-form .estimate-step {
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  box-shadow: 0 1px 2px rgba(16, 39, 71, 0.05), 0 14px 36px -16px rgba(16, 39, 71, 0.16);
  padding: var(--space-8);
}
.estimate-form fieldset {
  border: 0;
  border-radius: 0;
  background: transparent;
  padding: 0;
  margin: 0;
  min-width: 0;
}
.estimate-form legend {
  float: left;
  width: 100%;
  padding: 0;
  margin: 0 0 var(--space-2);
  font-family: var(--font-display);
  font-size: var(--fs-2xl);
  font-weight: 800;
  letter-spacing: -0.015em;
  line-height: 1.15;
  color: var(--color-ink);
}
.estimate-form legend + * { clear: both; }
.estimate-form .step-support {
  margin: 0 0 var(--space-5);
  color: var(--color-muted);
  font-size: var(--fs-sm);
  line-height: 1.55;
  max-width: 52ch;
}
.estimate-nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-rule);
  flex-wrap: wrap;
}
.estimate-nav .btn-back { margin-right: auto; }
.estimate-nav .btn svg { width: 16px; height: 16px; }
.estimate-step-error {
  margin-top: var(--space-4);
  color: var(--color-danger-text);
  background: var(--color-danger-bg);
  border: 1px solid var(--color-danger-border);
  border-left: 3px solid var(--color-danger);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  font-size: var(--fs-sm);
  font-weight: 500;
}

/* Service cards (step 2) — a product-configurator choice, not a form field.
   Each card is a <label> whose radio is visually hidden; the surface carries
   hover/selected/focus states driven entirely by the input's state, so the
   semantics stay native radio. */
.service-cards {
  display: grid;
  gap: var(--space-3);
  margin: 0 0 var(--space-4);
}
.service-card { display: block; margin: 0; font-weight: 500; }
.service-card__surface {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  cursor: pointer;
}
@media (prefers-reduced-motion: no-preference) {
  .service-card__surface {
    transition:
      border-color var(--dur-quick) var(--ease-out),
      background-color var(--dur-quick) var(--ease-out),
      box-shadow var(--dur-quick) var(--ease-out),
      transform var(--dur-quick) var(--ease-out);
  }
}
.service-card:hover .service-card__surface {
  border-color: var(--color-primary);
  box-shadow: var(--shadow-sm);
  transform: translateY(-1px);
}
/* Custom radio ring. The dot springs in on selection. */
.service-card__ring {
  flex-shrink: 0;
  position: relative;
  width: 20px;
  height: 20px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--color-border);
  background: var(--color-white);
}
.service-card__ring::after {
  content: "";
  position: absolute;
  inset: 3px;
  border-radius: var(--radius-pill);
  background: var(--color-primary);
  transform: scale(0);
}
@media (prefers-reduced-motion: no-preference) {
  .service-card__ring { transition: border-color var(--dur-quick) var(--ease-out); }
  .service-card__ring::after { transition: transform var(--dur-quick) var(--ease-spring); }
}
.service-card input:checked + .service-card__surface {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 1px var(--color-primary), var(--shadow-sm);
  background: linear-gradient(0deg, rgba(232, 240, 250, 0.45), rgba(232, 240, 250, 0.45)), var(--color-white);
}
.service-card input:checked + .service-card__surface .service-card__ring {
  border-color: var(--color-primary);
}
.service-card input:checked + .service-card__surface .service-card__ring::after {
  transform: scale(1);
}
.service-card input:focus-visible + .service-card__surface {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.service-card__text { min-width: 0; display: grid; gap: 2px; }
.service-card__name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-md);
  letter-spacing: -0.01em;
  color: var(--color-ink);
  line-height: 1.25;
}
.service-card__desc {
  color: var(--color-muted);
  font-size: var(--fs-sm);
  line-height: 1.45;
}
.service-card__price {
  margin-left: auto;
  flex-shrink: 0;
  display: grid;
  justify-items: end;
  gap: 1px;
  text-align: right;
}
.service-card__price-label {
  font-size: var(--fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--color-muted);
}
.service-card__price-amount {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-md);
  font-variant-numeric: tabular-nums;
  color: var(--color-primary-dark);
}
.service-card__price--quote {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--color-muted);
  align-self: center;
}
@media (max-width: 560px) {
  .service-card__surface { padding: var(--space-3) var(--space-4); gap: var(--space-3); }
  .service-card__price-amount { font-size: var(--fs-base); }
}

/* "Not sure?" comparison — an in-context disclosure, never a navigation. */
.service-compare { margin: 0 0 var(--space-4); }
.service-compare summary {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  cursor: pointer;
  list-style: none;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--color-primary);
  padding: var(--space-1) 0;
  min-height: 44px;
}
.service-compare summary::-webkit-details-marker { display: none; }
.service-compare summary:hover { color: var(--color-primary-dark); text-decoration: underline; }
.service-compare summary::after {
  content: "";
  width: 7px;
  height: 7px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translateY(-1px);
  flex-shrink: 0;
}
@media (prefers-reduced-motion: no-preference) {
  .service-compare summary::after { transition: transform var(--dur-quick) var(--ease-out); }
}
.service-compare[open] summary::after { transform: rotate(225deg) translateY(-1px); }
.service-compare__body {
  margin-top: var(--space-2);
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-off-white);
}
.service-compare__cols {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: 1fr 1fr;
}
@media (max-width: 560px) {
  .service-compare__cols { grid-template-columns: 1fr; }
}
.service-compare__title {
  margin: 0 0 var(--space-1);
  font-size: var(--fs-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--color-accent-text);
}
.service-compare__body ul {
  margin: 0;
  padding: 0 0 0 1.1rem;
  font-size: var(--fs-sm);
  color: var(--color-text);
  display: grid;
  gap: var(--space-1);
}
.service-compare__verdict {
  margin: var(--space-3) 0 0;
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-rule);
  font-size: var(--fs-sm);
  color: var(--color-muted);
}

/* Progressive reveals (cadence, commercial note) — appear the moment the
   selection makes them relevant. */
.wizard-reveal { margin: 0 0 var(--space-2); }
@media (prefers-reduced-motion: no-preference) {
  .wizard-reveal[data-panel-enter] { animation: estimate-panel-in 240ms var(--ease-out) both; }
  @keyframes estimate-panel-in {
    from { opacity: 0; transform: translateY(6px); }
    to { opacity: 1; transform: none; }
  }
}
.wizard-reveal__label {
  margin: 0 0 var(--space-2);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-base);
  color: var(--color-ink);
}
.service-note {
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-info-border);
  border-left: 3px solid var(--color-primary);
  border-radius: var(--radius-md);
  background: var(--color-primary-light);
  color: var(--color-primary-dark);
  font-size: var(--fs-sm);
  line-height: 1.5;
}

/* Segmented cadence control — four visible one-tap choices. */
.segment-group {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-2);
}
@media (max-width: 560px) {
  .segment-group { grid-template-columns: 1fr 1fr; }
}
.segment { display: block; margin: 0; font-weight: 500; }
.segment > span {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-white);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--color-text);
  text-align: center;
  cursor: pointer;
}
@media (prefers-reduced-motion: no-preference) {
  .segment > span {
    transition:
      border-color var(--dur-quick) var(--ease-out),
      background-color var(--dur-quick) var(--ease-out),
      color var(--dur-quick) var(--ease-out);
  }
}
.segment:hover > span { border-color: var(--color-primary); background: var(--color-primary-light); }
.segment input:checked + span {
  border-color: var(--color-primary);
  background: var(--color-primary);
  color: var(--color-white);
}
.segment input:focus-visible + span {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
/* Wrap variant: chip rows whose options size to their text (space types,
   frequencies) instead of a fixed equal-width grid. */
.segment-group--wrap {
  display: flex;
  flex-wrap: wrap;
}
.segment-group--wrap .segment > span { padding: var(--space-2) var(--space-4); }

/* Group label for tap-choice rows inside a step (condition, space type,
   frequency): heavier than a field label, lighter than the card title. */
.choice-group-label {
  margin: var(--space-5) 0 var(--space-2);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-base);
  color: var(--color-ink);
}

/* Steppers (step 3) — large tactile minus/value/plus controls. The center is
   a REAL number input, so keyboard entry and unusual values keep working;
   the buttons are conveniences layered on top. */
.stepper-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4);
  max-width: 26rem;
}
.stepper-field label {
  display: block;
  margin: 0 0 var(--space-2);
  font-weight: 600;
}
.stepper {
  display: inline-flex;
  align-items: stretch;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-white);
  overflow: hidden;
}
.stepper:focus-within {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px var(--color-primary-light);
}
.stepper__btn {
  width: 46px;
  min-height: 52px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: var(--color-white);
  color: var(--color-primary);
  cursor: pointer;
  font: inherit;
}
@media (prefers-reduced-motion: no-preference) {
  .stepper__btn { transition: background-color var(--dur-quick) var(--ease-out); }
}
.stepper__btn:hover { background: var(--color-primary-light); }
.stepper__btn:active { background: var(--color-primary-light); color: var(--color-primary-dark); }
.stepper__btn:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: -2px;
}
.stepper__btn svg { width: 16px; height: 16px; }
.stepper input[type="number"] {
  width: 4.5rem;
  max-width: none;
  min-height: 52px;
  border: 0;
  border-left: 1px solid var(--color-rule);
  border-right: 1px solid var(--color-rule);
  border-radius: 0;
  padding: 0 var(--space-1);
  text-align: center;
  font-size: var(--fs-lg);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--color-ink);
  appearance: textfield;
  -moz-appearance: textfield;
}
.stepper input[type="number"]::-webkit-outer-spin-button,
.stepper input[type="number"]::-webkit-inner-spin-button {
  appearance: none;
  margin: 0;
}
.stepper input[type="number"]:focus { border-color: var(--color-rule); outline: none; }
.stepper-note {
  margin: var(--space-1) 0 0;
  font-size: var(--fs-xs);
  color: var(--color-muted);
}

/* Approximate square footage — deliberately reassuring, deliberately optional. */
.sqft-field { margin: var(--space-5) 0; }
.sqft-field label { display: block; margin: 0 0 var(--space-2); font-weight: 600; }
.sqft-field input {
  max-width: 12rem;
  min-height: 52px;
  font-size: var(--fs-md);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  border-radius: var(--radius-md);
  padding: 0.6rem 1rem;
}

/* Condition cards — the compact sibling of the step-2 service cards. Same
   radio-under-label mechanics, tighter footprint, no price column. */
.condition-cards {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (max-width: 640px) {
  .condition-cards { grid-template-columns: 1fr; }
}
.condition-card { display: block; margin: 0; font-weight: 500; }
.condition-card__surface {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  height: 100%;
  padding: var(--space-3) var(--space-4);
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  cursor: pointer;
}
@media (prefers-reduced-motion: no-preference) {
  .condition-card__surface {
    transition:
      border-color var(--dur-quick) var(--ease-out),
      background-color var(--dur-quick) var(--ease-out),
      box-shadow var(--dur-quick) var(--ease-out);
  }
}
.condition-card:hover .condition-card__surface {
  border-color: var(--color-primary);
  box-shadow: var(--shadow-xs);
}
.condition-card__ring {
  flex-shrink: 0;
  position: relative;
  width: 16px;
  height: 16px;
  margin-top: 2px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--color-border);
  background: var(--color-white);
}
.condition-card__ring::after {
  content: "";
  position: absolute;
  inset: 2px;
  border-radius: var(--radius-pill);
  background: var(--color-primary);
  transform: scale(0);
}
@media (prefers-reduced-motion: no-preference) {
  .condition-card__ring { transition: border-color var(--dur-quick) var(--ease-out); }
  .condition-card__ring::after { transition: transform var(--dur-quick) var(--ease-spring); }
}
.condition-card input:checked + .condition-card__surface {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 1px var(--color-primary);
  background: linear-gradient(0deg, rgba(232, 240, 250, 0.45), rgba(232, 240, 250, 0.45)), var(--color-white);
}
.condition-card input:checked + .condition-card__surface .condition-card__ring {
  border-color: var(--color-primary);
}
.condition-card input:checked + .condition-card__surface .condition-card__ring::after {
  transform: scale(1);
}
.condition-card input:focus-visible + .condition-card__surface {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.condition-card__text { min-width: 0; display: grid; gap: 1px; }
.condition-card__name {
  font-weight: 700;
  font-size: var(--fs-sm);
  color: var(--color-ink);
  line-height: 1.3;
}
.condition-card__desc {
  color: var(--color-muted);
  font-size: var(--fs-xs);
  line-height: 1.45;
}

/* Add-on options (step 4) — compact multi-select cards. Real checkboxes
   under label-wrapped surfaces (the square box + sprung check says
   "multi-select" where the step-2/3 ring says "choose one"). Named
   .addon-option because .addon-card is the marketing-page component. */
/* Commercial + restaurant: appliances are the likely ask, so that group
   leads. Flex order only — DOM (and tab order) stay stable. */
.addon-groups { display: flex; flex-direction: column; gap: var(--space-5); }
.addon-group { display: block; }
.addon-groups--appliances-first [data-addon-group="extras"] { order: 2; }
.addon-groups--appliances-first [data-addon-group="appliances"] { order: 1; }
.addon-group .choice-group-label { margin-top: 0; }
.addon-grid {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (max-width: 640px) {
  .addon-grid { grid-template-columns: 1fr; }
}
.addon-option { display: block; margin: 0; font-weight: 500; }
.addon-option__surface {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  height: 100%;
  padding: var(--space-3) var(--space-4);
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  cursor: pointer;
}
@media (prefers-reduced-motion: no-preference) {
  .addon-option__surface {
    transition:
      border-color var(--dur-quick) var(--ease-out),
      background-color var(--dur-quick) var(--ease-out),
      box-shadow var(--dur-quick) var(--ease-out);
  }
}
.addon-option:hover .addon-option__surface {
  border-color: var(--color-primary);
  box-shadow: var(--shadow-xs);
}
.addon-option__box {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  margin-top: 2px;
  border-radius: var(--radius-xs);
  border: 2px solid var(--color-border);
  background: var(--color-white);
  color: var(--color-white);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.addon-option__box svg { width: 11px; height: 11px; transform: scale(0); }
@media (prefers-reduced-motion: no-preference) {
  .addon-option__box { transition: border-color var(--dur-quick) var(--ease-out), background-color var(--dur-quick) var(--ease-out); }
  .addon-option__box svg { transition: transform var(--dur-quick) var(--ease-spring); }
}
.addon-option input:checked + .addon-option__surface {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 1px var(--color-primary);
  background: linear-gradient(0deg, rgba(232, 240, 250, 0.45), rgba(232, 240, 250, 0.45)), var(--color-white);
}
.addon-option input:checked + .addon-option__surface .addon-option__box {
  border-color: var(--color-primary);
  background: var(--color-primary);
}
.addon-option input:checked + .addon-option__surface .addon-option__box svg { transform: scale(1); }
.addon-option input:focus-visible + .addon-option__surface {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.addon-option__text { min-width: 0; display: grid; gap: 1px; }
.addon-option__name {
  font-weight: 700;
  font-size: var(--fs-sm);
  color: var(--color-ink);
  line-height: 1.3;
}
.addon-option__desc {
  color: var(--color-muted);
  font-size: var(--fs-xs);
  line-height: 1.45;
}
/* "Requires confirmation" — quiet competence, not a warning. */
.addon-option__flag {
  margin-top: 2px;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--color-accent-text);
}
.addon-option__price {
  margin-left: auto;
  flex-shrink: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  color: var(--color-primary-dark);
  white-space: nowrap;
}
/* Commercial: residential amounts are not binding commercial prices, so the
   cards carry no dollars there (the walkthrough quote owns pricing). */
.addon-groups--commercial .addon-option__price { display: none; }

/* Laundry loads: revealed only while Laundry is selected. Spans the grid so
   it sits under its parent card whatever the column count. */
.addon-quantity-row {
  grid-column: 1 / -1;
  display: flex;
  align-items: end;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-4);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-off-white);
}
@media (prefers-reduced-motion: no-preference) {
  .addon-quantity-row[data-panel-enter] { animation: estimate-panel-in 240ms var(--ease-out) both; }
}
.addon-quantity-row .stepper-field label { margin-bottom: var(--space-1); }
.addon-quantity-row .stepper { background: var(--color-white); }
.addon-quantity-total {
  margin: 0 0 var(--space-2);
  font-size: var(--fs-sm);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--color-primary-dark);
}

/* Live add-on subtotal in the nav row: continuous price awareness, not a
   cart. Hidden entirely at zero selections — skipping is a normal path. */
.addon-summary {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.addon-summary strong { color: var(--color-primary-dark); font-weight: 700; }

.sms-optin {
  display: flex; gap: var(--space-3); align-items: flex-start;
  margin: var(--space-3) 0; padding: var(--space-3);
  border: 1px solid var(--color-border); border-radius: var(--radius-md);
  background: var(--color-off-white);
}
.sms-optin input[type="checkbox"] { margin-top: 0.25rem; }
.sms-optin strong { display: block; }

/* Step 6 — the completion moment. Fields that read as product, guidance
   that reacts to what is typed, errors that live beside their cause and
   leave the moment the cause does. */
.step-lead { margin: calc(-1 * var(--space-2)) 0 var(--space-4); max-width: 34rem; }

.contact-fields { display: grid; gap: var(--space-4); max-width: 28rem; }
.contact-field label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-2);
  font-weight: 600;
  font-size: var(--fs-sm);
  color: var(--color-text);
}
.contact-field input {
  min-height: 52px;
  font-size: var(--fs-md);
  border-radius: var(--radius-md);
  padding: 0.6rem 1rem;
  transition: border-color var(--dur-quick) var(--ease-out), box-shadow var(--dur-quick) var(--ease-out);
}
.contact-field .field-help { margin: var(--space-1) 0 0; font-size: var(--fs-xs); }
.contact-field.is-invalid input {
  border-color: var(--color-danger);
  outline-color: var(--color-danger-bg);
}
.field-error {
  margin: var(--space-1) 0 0;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--color-danger);
}
.field-error[hidden] { display: none; }
[aria-invalid="true"] {
  border-color: var(--color-danger) !important;
  box-shadow: 0 0 0 2px var(--color-danger-bg);
}

/* "Recommended", said once, quietly. */
.label-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  background: var(--color-accent-soft);
  color: var(--color-primary-dark);
  font-size: var(--fs-2xs, 0.6875rem);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.contact-channel-note { margin: 0; font-size: var(--fs-sm); }
.contact-channel-note--info {
  padding: var(--space-2) var(--space-3);
  border-left: 3px solid var(--color-accent);
  background: var(--color-off-white);
  color: var(--color-text);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

/* Text updates: a preference with a heading, not a stray checkbox. The
   inner .sms-optin block (tokens, label-wraps-input) is untouched. */
.sms-section { margin: var(--space-5) 0 0; max-width: 28rem; }
.sms-section .choice-group-label { margin-bottom: var(--space-1); }
.sms-section .sms-optin { margin: var(--space-2) 0 0; background: var(--color-white); }
@media (prefers-reduced-motion: no-preference) {
  .sms-section[data-panel-enter] { animation: estimate-panel-in 240ms var(--ease-out) both; }
}

.skip-section { margin: var(--space-5) 0 0; max-width: 34rem; }
.skip-section .choice-group-label { margin-bottom: var(--space-1); }
.skip-section .hint { margin: 0 0 var(--space-2); }
.skip-section textarea {
  max-width: none;
  border-radius: var(--radius-md);
  padding: 0.75rem 1rem;
}
.skip-section textarea::placeholder { color: var(--color-muted); opacity: 0.8; }

/* The culmination: one confident action, two honest facts under it. */
.estimate-nav--final { align-items: flex-start; }
.final-cta {
  display: grid;
  gap: var(--space-2);
  justify-items: end;
  margin-left: auto;
}
.btn-final {
  min-width: 14rem;
  box-shadow: 0 6px 18px rgba(11, 44, 84, 0.22);
}
.btn-final[aria-busy="true"] {
  position: relative;
  color: rgba(255, 255, 255, 0.85);
}
.btn-final[aria-busy="true"]::after {
  content: "";
  display: inline-block;
  width: 14px;
  height: 14px;
  margin-left: 10px;
  vertical-align: -2px;
  border-radius: var(--radius-pill);
  border: 2px solid rgba(255, 255, 255, 0.45);
  border-top-color: var(--color-white);
}
@media (prefers-reduced-motion: no-preference) {
  .btn-final[aria-busy="true"]::after { animation: final-cta-spin 700ms linear infinite; }
  @keyframes final-cta-spin { to { transform: rotate(360deg); } }
}
.final-cta__note {
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--color-muted);
  text-align: right;
  max-width: 18rem;
}
@media (max-width: 640px) {
  .estimate-nav--final { flex-direction: column-reverse; gap: var(--space-3); }
  .final-cta { margin-left: 0; justify-items: stretch; width: 100%; }
  .btn-final { width: 100%; }
  .final-cta__note { text-align: center; max-width: none; }
  .estimate-nav--final .btn-back { align-self: flex-start; }
}
/* Muted detail line under a control's title (SMS consent detail). */
.addon-meta {
  color: var(--color-muted);
  font-size: var(--fs-xs);
  display: block;
  margin-top: var(--space-1);
}

#estimate-result, #login-result, #sign-feedback {
  margin-top: var(--space-4); min-height: 1.2em;
}
#estimate-result[data-loading] { color: var(--color-muted); }

.estimate-result {
  background: var(--color-white);
  padding: var(--space-5);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-xs);
  border: 1px solid var(--color-border);
}
.estimate-result .price-range { font-size: var(--fs-lg); font-weight: 700; color: var(--color-primary); }
.estimate-result .components, .estimate-result .manual-review {
  list-style: none; padding: 0; margin: var(--space-3) 0;
}
.estimate-result .components li {
  display: flex; justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-2) 0; border-bottom: 1px dotted var(--color-border);
}
/* A charge the customer did not select needs its reason next to it, not in a
   support email. The note wraps under the label and never pushes the amount
   off the row. */
.estimate-result .components li > span:first-child { display: grid; gap: 2px; min-width: 0; }
.estimate-result .components li > span:last-child { white-space: nowrap; }
.price-line__note {
  font-size: var(--fs-xs);
  color: var(--color-muted);
  font-weight: 400;
  line-height: 1.4;
}
.estimate-error {
  background: var(--color-danger-bg); border: 1px solid var(--color-danger-border);
  padding: var(--space-3) var(--space-4); border-radius: var(--radius-md);
  color: var(--color-danger-text);
}
.estimate-error__issues {
  margin: var(--space-2) 0 0;
  padding-left: var(--space-5);
}
.estimate-error__issues li { margin-bottom: var(--space-1); }
.estimate-error__issues strong { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

/* Contract acknowledgments — readable checkbox rows, not raw slugs. */
.ack {
  display: flex; gap: var(--space-3); align-items: flex-start;
  padding: var(--space-3) var(--space-4);
  background: var(--color-off-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-2);
  cursor: pointer;
  line-height: 1.5;
}
.ack:hover { border-color: var(--color-primary); background: var(--color-white); }
.ack input[type="checkbox"] {
  margin-top: 0.2rem;
  flex-shrink: 0;
  width: 18px; height: 18px;
}

/* 12. Auth (sign-in) card ------------------------------------------------- */
.auth-shell {
  display: grid;
  grid-template-columns: 1fr;
  min-height: calc(100vh - var(--header-height));
  background: linear-gradient(180deg, var(--color-primary-light) 0%, var(--color-off-white) 65%);
  padding: var(--space-12) var(--space-6);
  align-items: center;
}
.auth-card {
  width: 100%;
  max-width: 460px;
  margin: 0 auto;
  background: var(--color-white);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
  border: 1px solid var(--color-border);
  padding: var(--space-8);
}
.auth-card__logo { display: flex; justify-content: center; margin-bottom: var(--space-5); }
.auth-card__logo img { height: 64px; width: auto; }
.auth-card h1 { font-size: var(--fs-2xl); text-align: center; margin: 0 0 var(--space-2); }
.auth-card__lead { text-align: center; color: var(--color-muted); margin: 0 0 var(--space-6); }
.auth-card form { display: flex; flex-direction: column; gap: var(--space-4); }
.auth-card .btn-primary { width: 100%; }
.auth-card .auth-card__benefits {
  margin: var(--space-6) 0 0;
  padding: var(--space-5);
  background: var(--color-off-white);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
}
.auth-card__benefits h2 { font-size: var(--fs-sm); letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-primary); margin: 0 0 var(--space-3); }
.auth-card__benefits ul { list-style: none; padding: 0; margin: 0; }
.auth-card__benefits li { font-size: var(--fs-sm); color: var(--color-text); padding: var(--space-1) 0 var(--space-1) 1.25rem; position: relative; }
.auth-card__benefits li::before {
  content: ""; position: absolute; left: 0; top: 0.7rem;
  width: 8px; height: 8px; border-radius: var(--radius-pill);
  background: var(--color-accent);
}
.auth-card__legal { text-align: center; color: var(--color-muted); font-size: var(--fs-xs); margin-top: var(--space-5); }
.auth-card__back { text-align: center; margin-top: var(--space-4); }
.auth-card__back a { color: var(--color-primary); font-weight: 600; }
/* Customer login: password (primary) + magic-link (fallback) + quote CTA. */
.auth-card .btn-secondary, .auth-card .btn-ghost { width: 100%; }
.auth-card__divider {
  display: flex; align-items: center; text-align: center;
  color: var(--color-muted); font-size: var(--fs-xs);
  text-transform: uppercase; letter-spacing: 0.08em;
  margin: var(--space-6) 0;
}
.auth-card__divider::before, .auth-card__divider::after {
  content: ""; flex: 1; height: 1px; background: var(--color-border);
}
.auth-card__divider span { padding: 0 var(--space-3); }
.auth-card__alt-title { font-size: var(--fs-md); text-align: center; margin: 0 0 var(--space-1); }
.auth-card__alt-lead { text-align: center; color: var(--color-muted); font-size: var(--fs-sm); margin: 0 0 var(--space-4); }
.auth-card__cta {
  margin-top: var(--space-6); padding: var(--space-5); text-align: center;
  background: var(--color-primary-light);
  border-radius: var(--radius-md); border: 1px solid var(--color-border);
}
.auth-card__cta p { margin: 0 0 var(--space-3); color: var(--color-text); font-size: var(--fs-sm); }
/* Customer activation: accepted-quote summary above the password form. */
.activation-preview { margin-bottom: var(--space-5); }
.activation-summary {
  padding: var(--space-4); border: 1px solid var(--color-border);
  border-radius: var(--radius-md); background: var(--color-primary-light);
  display: grid; gap: var(--space-1);
}
.activation-summary p { margin: 0; }
/* Customer portal: accepted-job reserve-and-pay (deposit) form. */
.reserve-deposit {
  margin-top: var(--space-3); padding: var(--space-3);
  border: 1px dashed var(--color-primary); border-radius: var(--radius-md);
  display: grid; gap: var(--space-2);
}
.reserve-deposit__lead { margin: 0; font-weight: 600; }
.reserve-deposit label { display: grid; gap: var(--space-1); font-size: var(--fs-sm); }
.reserve-deposit input {
  padding: var(--space-2); border: 1px solid var(--color-border); border-radius: var(--radius-sm);
}
.reserve-status { margin: 0; font-size: var(--fs-sm); color: var(--color-muted); }

/* Second factor. Lives in site.css rather than admin.css because the same
   panel appears in three places on two stylesheets' worth of pages: the login
   card, the Mail administration shell, and inside a reschedule decision form.
   It is drawn as an inset block so that, wherever it lands, it reads as a
   demand attached to the thing around it rather than a page of its own. */
.mfa-host[hidden] { display: none; }
.mfa-panel {
  margin: var(--space-4) 0;
  /* Bounded rather than filling its container. This panel asks for six digits;
     stretched across a 1150px admin section it reads as an empty page with a
     small box in the corner, and the eye has nowhere to land. */
  max-width: 32rem;
  padding: var(--space-5);
  background: var(--color-off-white);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-primary);
  border-radius: var(--radius-md);
  display: grid;
  gap: var(--space-3);
  text-align: left;
}
.mfa-panel h3 { margin: 0; font-size: var(--fs-md); }
.mfa-panel .hint { margin: 0; color: var(--color-muted); font-size: var(--fs-sm); }
.mfa-qr {
  /* The QR is the one thing here a phone camera has to resolve, so it gets a
     white ground and a fixed size instead of scaling with the container. */
  width: 176px; height: 176px;
  align-self: center;
  background: var(--color-white);
  padding: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}
.mfa-secret summary { cursor: pointer; font-size: var(--fs-sm); color: var(--color-primary); font-weight: 600; }
.mfa-secret__value {
  display: block; margin-top: var(--space-2); padding: var(--space-2);
  background: var(--color-white); border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--fs-sm);
  /* Base32 secrets are long and have no spaces to break on. */
  overflow-wrap: anywhere;
}
.mfa-fields { display: grid; gap: var(--space-3); }
.mfa-fields label { display: grid; gap: var(--space-1); font-size: var(--fs-sm); font-weight: 600; }
.mfa-fields input {
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-size: var(--fs-lg);
  /* Tracking makes a six-digit code readable in pairs, but it also widens
     every character, and the box was sized as though it did not: a typed
     000000 lost its last digit behind the border. Sized in explicit rem with
     the tracking accounted for, and centred so the code sits in the middle
     rather than drifting left as it is typed. */
  letter-spacing: 0.25em;
  text-indent: 0.25em;
  text-align: center;
  inline-size: 9.5rem;
  font-variant-numeric: tabular-nums;
}
.mfa-fields input:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }
.mfa-fields .cluster { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.mfa-panel .btn { width: auto; }
/* An error has to look like one. Rendered in the same muted grey as the hint
   above it, "That code did not match" read as instructions rather than as a
   rejection, and the operator had no signal that anything had gone wrong. */
.mfa-feedback { margin: 0; font-size: var(--fs-sm); color: var(--color-muted); min-height: 1.25rem; }
.mfa-feedback[data-tone="error"] { color: var(--color-danger); font-weight: 600; }

/* 13. Portal shells -------------------------------------------------------- */
.portal-shell {
  /* The ground tone is set per density (section 20). It is what lets a white
     card read as a card without an outline drawn round it. */
  background: var(--portal-ground, var(--color-off-white));
  min-height: 100vh;
}
.portal-header {
  background: var(--color-white);
  border-bottom: 1px solid var(--color-border);
}
.portal-header__inner {
  max-width: var(--container-wide);
  margin: 0 auto;
  padding: var(--space-4) var(--space-6);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}
/* The legal-admin section nav had no styling at all, so its six links ran
   together as one underlined string — "Dashboard Legal documents Agreement
   bundles Service orders Signed Evidence packets" — with no gap between them
   and nothing distinguishing the page you are on. */
.legal-admin-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
  align-items: center;
}
.legal-admin-nav a {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--color-primary);
  text-decoration: none;
  padding: 0.35rem 0;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  border-bottom: 2px solid transparent;
}
.legal-admin-nav a:hover { border-bottom-color: var(--color-primary); }
/* The current view, marked by the rail itself.
   Legal was six templates, so "which page am I on" was six hardcoded pairs of
   selectors here and adding a view meant editing this stylesheet. It is one
   application now and marks its own rail with aria-current, the way every
   other rail in the product does. */
.legal-admin-nav a[aria-current="page"] {
  border-bottom-color: var(--color-accent);
  color: var(--color-text);
}

.portal-header h1 {
  font-size: var(--fs-xl);
  margin: 0;
  color: var(--color-primary-dark);
}
.portal-header__role {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--color-muted);
  margin-top: var(--space-1);
}
.portal-main {
  max-width: var(--portal-measure, var(--container-wide));
  margin: 0 auto;
  padding: var(--portal-pad-block, var(--space-8)) var(--space-6);
}
/* The white card is the PRE-RENDER state only.
   The shells keep the `.portal-loading` class after JS renders (only the
   data-loading attribute is dropped), and this used to keep painting a white
   bordered card around everything — so once the sections became cards in
   their own right, every portal was cards inside a card, with the outer one
   flattening the contrast that makes a card read as a card. */
.portal-loading[data-loading] {
  background: var(--color-white);
  box-shadow: var(--shadow-xs);
  border-radius: var(--radius-lg);
  padding: var(--space-8);
  color: var(--color-muted);
  text-align: center;
}

/* Skeleton shimmer for portal sections that are still fetching their
   data. Replaces the plain "Loading…" string with shape-of-the-real-thing
   pulse so the layout doesn't snap when the API resolves. Honors
   prefers-reduced-motion via the keyframe wrapper. */
@keyframes skeleton-pulse {
  0%, 100% { opacity: 0.45; }
  50%      { opacity: 0.85; }
}
.skeleton-line {
  height: 1rem;
  border-radius: var(--radius-sm);
  background: var(--color-border);
  margin-bottom: var(--space-2);
  opacity: 0.55;
}
@media (prefers-reduced-motion: no-preference) {
  .skeleton-line {
    animation: skeleton-pulse 1.5s ease-in-out infinite;
  }
}
.skeleton-line--short  { width: 40%; }
.skeleton-line--medium { width: 70%; }
.skeleton-line--long   { width: 90%; }

/* Signed-out "portal gate" — replaces the lonely one-line "You are not signed
   in" state with a purposeful sign-in surface and a preview of what the portal
   holds. Rendered inside the .portal-loading white card, so it resets the
   inherited centering and reads left-aligned. */
.portal-gate {
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: var(--space-8);
  align-items: center;
  text-align: left;
}
.portal-gate__eyebrow {
  display: inline-block;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--color-accent-text);
  margin-bottom: var(--space-3);
}
.portal-gate__title {
  font-size: var(--fs-3xl);
  line-height: 1.1;
  color: var(--color-primary);
  margin: 0 0 var(--space-3);
}
.portal-gate__lede {
  color: var(--color-muted);
  font-size: var(--fs-md);
  line-height: 1.6;
  margin: 0 0 var(--space-6);
  max-width: 42ch;
}
.portal-gate__ctas {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
}
.portal-gate__note {
  margin: var(--space-5) 0 0;
  font-size: var(--fs-sm);
  color: var(--color-muted);
}
.portal-gate__preview {
  list-style: none;
  margin: 0;
  padding: var(--space-4) var(--space-6);
  background: var(--color-cream);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
}
.portal-gate__feature {
  display: flex;
  gap: var(--space-4);
  padding: var(--space-4) 0;
  align-items: flex-start;
}
.portal-gate__feature + .portal-gate__feature {
  border-top: 1px solid var(--color-rule);
}
.portal-gate__icon {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border-radius: var(--radius-md);
  background: var(--color-primary-light);
  color: var(--color-primary);
}
.portal-gate__icon svg { width: 20px; height: 20px; }
.portal-gate__feature > span:last-child {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--color-muted);
}
.portal-gate__feature strong {
  color: var(--color-text);
  font-size: var(--fs-base);
  font-weight: 650;
}
/* Staff variant: a single, professional sign-in card, no marketing preview. */
.portal-gate--staff {
  grid-template-columns: 1fr;
  max-width: 30rem;
}
@media (max-width: 860px) {
  .portal-gate {
    grid-template-columns: 1fr;
    gap: var(--space-6);
  }
}

/* Dashboard grids (used by JS-rendered roots) */
.dashboard-grid, .admin-grid {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  /* Cards size to their own content instead of stretching to the tallest in
     the row — a short Properties card next to a long Bookings card no longer
     shows a lake of empty white space. */
  align-items: start;
}
/* The customer dashboard packs, it does not row-align.
   
   `align-items: start` stops a card stretching to its neighbour's height, but
   a grid ROW is still as tall as its tallest member — so Bookings (long) next
   to Invoices (three lines) left a lake of white below the short cards and
   pushed the second row a screen further down. The portal reads as broken
   before you notice it is merely empty.
   
   Multi-column packs the cards vertically instead: each column fills before
   the next begins, so a short card is followed immediately by the next card.
   DOM order is unchanged, so the reading order a screen reader announces is
   still the order the sections were written in. `break-inside: avoid` keeps a
   card from being sliced across a column boundary.
   
   Below the breakpoint the single-column grid is already correct and stays. */
/* No multicolumn here. CSS columns pack children into whichever hole is
   nearest, so at desktop widths the customer's cards landed in positions
   nobody chose — the next clean could end up in the third column under
   "Account security". The customer portal now composes four ordered layers
   (section 23) and the admin grid stays a plain auto-fit grid, where every
   card occupies a position that was decided. */

/* No border. A white plane on the portal's tinted ground, lifted by a shadow,
   is already a card — outlining it as well was the single biggest contributor
   to the "collection of rectangles" reading. The ground tone per portal (see
   section 20, densities) is what makes the edge legible without a line. */
.dashboard-grid > div, .admin-grid > div, .job-card {
  background: var(--color-white);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  box-shadow: var(--shadow-sm);
  text-align: left;
}
@media (prefers-reduced-motion: no-preference) {
  .dashboard-grid > div {
    transition: box-shadow var(--dur-med) var(--ease-out), transform var(--dur-med) var(--ease-out);
  }
  .dashboard-grid > div:hover {
    box-shadow: var(--shadow-md);
    transform: translateY(-2px);
  }
}
/* Card header: a left-aligned title with a short gold rule beneath, so every
   portal card reads as a titled panel instead of a centered label. */
.dashboard-grid h3, .admin-grid h3 {
  margin: 0 0 var(--space-4);
  padding-bottom: var(--space-3);
  color: var(--color-primary-dark);
  font-size: var(--fs-md);
  font-weight: 700;
  text-align: left;
  position: relative;
}
.dashboard-grid h3::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -1px;
  width: 2.25rem;
  height: 2px;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
}
/* Sub-groupings inside a card (e.g. Bookings "Upcoming (2)" / "Past (1)")
   read as quiet section labels, left-aligned like everything else. */
.dashboard-grid h4 {
  margin: var(--space-4) 0 var(--space-2);
  font-size: var(--fs-sm);
  color: var(--color-muted);
  text-align: left;
  letter-spacing: 0.01em;
}

/* Admin "Business settings" — replaces the prior JSON.stringify dump.
   Each row shows the setting_key, an optional description, and a small
   monospace value preview. Scoped so we don't accidentally restyle
   other definition lists. */
.settings-list {
  margin: 0;
  display: grid;
  gap: var(--space-4);
}
.settings-list__row {
  display: grid;
  /* A grid item's default `min-width: auto` refuses to shrink below its
     content, so a long setting value pushed the whole admin page 21px past a
     360px viewport. */
  min-width: 0;
  gap: var(--space-2);
  padding: var(--space-3);
  background: var(--color-off-white);
  border-radius: var(--radius-md);
}
.settings-list dt {
  display: flex; flex-wrap: wrap; align-items: baseline;
  min-width: 0; overflow-wrap: anywhere;
  gap: var(--space-3);
  font-weight: 600;
  color: var(--color-primary);
}
.settings-list dt code {
  font-size: var(--fs-sm);
  background: var(--color-primary-light);
  color: var(--color-primary);
  padding: 0.1rem 0.45rem;
  border-radius: var(--radius-xs);
}
.settings-list__desc {
  font-weight: 400;
  font-size: var(--fs-sm);
  color: var(--color-muted);
}
.settings-list dd { margin: 0; }
.settings-list__value {
  margin: 0;
  padding: var(--space-3);
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-size: var(--fs-xs);
  overflow-x: auto;
  max-height: 200px;
  overflow-y: auto;
}

.payroll-form {
  display: flex; flex-wrap: wrap; align-items: flex-end;
  gap: var(--space-3);
}
.payroll-form label {
  display: flex; flex-direction: column; gap: var(--space-1);
  font-size: var(--fs-sm);
  color: var(--color-muted);
}
.payroll-form input {
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  min-width: 180px;
}
.dashboard-header, .admin-header, .employee-header {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4); flex-wrap: wrap; margin-bottom: var(--space-6);
}
/* Portal "Welcome, …" heading is in-page (after the .portal-header h1) so
   it's NOT the page title — keep it visibly smaller than h1/section h2. */
.dashboard-header h2, .admin-header h2, .employee-header h2 {
  font-size: var(--fs-xl);
  margin: 0;
  word-break: break-word;
}
.admin-header > div { display: grid; gap: var(--space-1); }
.admin-header p { margin: 0; }
/* Customer dashboard greeting: a warm "Welcome back" with the signed-in email
   as a small muted subline — never the raw email shouted as the headline. */
.dashboard-header__intro { display: grid; gap: 2px; }
.dashboard-header__who {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--color-muted);
}
.dashboard-header__who strong { color: var(--color-text); font-weight: 600; }

.kpi-list, .alert-list, .worker-list, .flags-list, .property-list, .estimate-list,
.booking-list, .contract-list, .recurring-list {
  list-style: none; padding: 0; margin: var(--space-2) 0;
}
.kpi-list li {
  display: flex; justify-content: space-between;
  padding: var(--space-2) 0;
  border-bottom: 1px dotted var(--color-border);
}
.property-list li, .estimate-list li, .booking-list li,
.contract-list li, .recurring-list li {
  border-bottom: 1px dotted var(--color-border);
  padding: var(--space-3) 0;
}
.property-list .meta, .estimate-list .meta, .booking-list .meta,
.contract-list .meta {
  color: var(--color-muted); font-size: var(--fs-sm);
}
/* Filed service-issue status on a customer booking row */
.booking-issue {
  margin-top: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-left: 3px solid var(--color-warning);
  background: var(--color-off-white);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  font-size: var(--fs-sm);
}
/* A crew-reported problem is not the customer's complaint — it is us telling
   them something went wrong before they had to ask. Warning-toned so it reads
   as information rather than as a fault of theirs. */
.booking-issue--crew { border-left-color: var(--color-warning); background: var(--color-warning-bg); }
/* An incident the office has cleared for this customer. Distinct from a crew
   problem notice: it carries the crew's own description and the outcome, and it
   only ever appears when Admin marked it customer-visible. */
.booking-issue--incident {
  border-left-color: var(--color-warning);
  background: var(--color-warning-bg);
}
.booking-issue--incident small { display: block; color: var(--color-muted); }
.booking-issue--resolved { border-left-color: var(--color-success); }
.booking-issue--denied { border-left-color: var(--color-muted); }
.booking-issue__label { color: var(--color-muted); }
.issue-report-form {
  margin-top: var(--space-3);
  display: grid;
  gap: var(--space-3);
  max-width: 480px;
}

/* Break-glass flag toggles (admin dashboard) */
.flag-row {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px dotted var(--color-border);
}
.flag-row { align-items: flex-start; }
.flag-row__main { flex: 1; min-width: 0; }
.flag-row__name {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  margin: 0; font-size: var(--fs-base); font-weight: 700; color: var(--color-text);
}
/* What the flag is doing right now, in a sentence. */
.flag-row__effect { margin: var(--space-1) 0 0; font-size: var(--fs-sm); color: var(--color-muted); }
/* The key is kept — an operator on the phone to support needs to be able to
   say it — but it is a footnote, not the label. */
.flag-row__key { display: inline-block; margin-top: var(--space-1); font-size: var(--fs-xs); color: var(--color-muted); }
.flag-row__state {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.15rem 0.55rem;
  border-radius: var(--radius-pill);
  background: var(--color-off-white);
  color: var(--color-muted);
}
.flag-row__state--on  { background: var(--color-tier-active-bg); color: var(--color-success); }
.flag-row__state--off { background: var(--color-tier-extended-bg); color: var(--color-warning-text); }
.flag-confirm {
  margin-top: var(--space-3);
  padding: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-off-white);
  display: grid; gap: var(--space-3);
}
.flag-confirm__label { margin: 0; font-weight: 600; }

/* Cadence and cancellation (CAP-002). The cancel confirm is revealed rather
   than fired on the first click: ending a standing plan should cost a second,
   deliberate press. */
.recurring-controls {
  margin-top: var(--space-3);
  display: grid;
  gap: var(--space-3);
}
.recurring-cadence-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-2);
}
.recurring-cadence-form label { display: grid; gap: var(--space-1); font-size: var(--fs-sm); }
.recurring-cancel-confirm {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-muted, var(--color-warning-bg));
}

.recurring-actions {
  margin-top: var(--space-2);
  display: flex; gap: var(--space-2); flex-wrap: wrap;
}
.recurring-actions button {
  padding: 0.45rem 0.85rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-primary);
  background: var(--color-white);
  color: var(--color-primary);
  font-weight: 600;
  cursor: pointer;
  min-height: 36px;
}
.recurring-actions button:hover { background: var(--color-primary-light); }
.recurring-feedback { color: var(--color-muted); margin-top: var(--space-1); }
.reschedule-form {
  display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center;
  margin-top: var(--space-2);
}
.reschedule-form input[type="datetime-local"] {
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-size: 14px;
}
.reschedule-form button {
  padding: 0.4rem 0.8rem;
  border: 1px solid var(--color-primary);
  background: var(--color-white);
  color: var(--color-primary);
  border-radius: var(--radius-sm);
  font-weight: 600;
  cursor: pointer;
}
.reschedule-form button:hover { background: var(--color-primary-light); }
.badge-muted { background: var(--color-off-white); color: var(--color-muted); }
/* The customer property editors — standing instructions, access & entry, add a
   property — moved into dialogs and are composed in customer.css §16. The
   boxed-form rules that used to live here (.property-form, .property-actions,
   .access-panel, .access-form and friends) were written for a form embedded in
   a property card and painted a white rounded slab inside what is now the
   dialog's own plane. They are not reintroduced here: a field group inside a
   sheet draws no box of its own. */

/* Before/after proof in the customer portal. */
.job-photos { margin-top: var(--space-2); }
.job-photos-body { margin-top: var(--space-2); }
.job-photos-heading { margin: var(--space-2) 0 var(--space-1); font-size: var(--fs-sm); }
.job-photos-grid {
  list-style: none; padding: 0; margin: 0;
  display: grid; gap: var(--space-2);
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
}
.job-photos-grid img {
  width: 100%;
  /* Fixed box, cropped fill: a mixed portrait/landscape set from a phone
     otherwise renders as a ragged wall of different heights. */
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  display: block;
}
.job-photos-grid .meta { display: block; margin-top: 0.25rem; }

.legal-list { list-style: none; padding: 0; margin: var(--space-2) 0; }
.legal-list li { padding: var(--space-2) 0; border-bottom: 1px solid var(--color-rule); }

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
  display: block;
  overflow-x: auto;
  max-width: 100%;
}
.data-table th, .data-table td {
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--color-rule);
  text-align: left;
  white-space: nowrap;
  vertical-align: top;
}
.data-table th { background: var(--color-off-white); color: var(--color-text); }
/* Admin accept-job form (estimate review queue → send activation link). */
.admin-accept-form {
  margin-top: var(--space-4); padding: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-off-white); display: grid; gap: var(--space-3);
}
.admin-accept-form h4 { margin: 0; }
.admin-accept-form label { display: grid; gap: var(--space-1); font-size: var(--fs-sm); }
.admin-accept-form input, .admin-accept-form textarea {
  padding: var(--space-2); border: 1px solid var(--color-border); border-radius: var(--radius-sm);
}
.admin-accept-form .accept-target { font-weight: 600; margin: 0; }
.admin-accept-actions { display: flex; gap: var(--space-2); }
.data-table .id-short {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  background: var(--color-off-white);
  padding: 0.05rem 0.4rem;
  border-radius: var(--radius-xs);
  color: var(--color-text);
}

/* Employee portal cards (mobile-first) */
.job-card { margin-bottom: var(--space-4); }
/* Scoped away from the hero: `.job-card header h3` (0,1,1) beat
   `.job-hero__title` (0,1,0) and painted the property name navy-on-navy,
   i.e. invisible, on the card a cleaner opens first. */
.job-card header:not(.job-hero) h3 { margin: 0; color: var(--color-primary); }
.job-card header:not(.job-hero) p { color: var(--color-muted); font-size: var(--fs-sm); margin: var(--space-1) 0 0; }

/* Customer skip/avoid instructions are safety-critical in the field — a
   cleaner must never touch a room or item the customer flagged. Elevate them
   into a warning callout so they can't be skimmed past. */
.job-caution {
  margin: var(--space-3) 0;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-warning-border);
  border-left: 4px solid var(--color-warning);
  background: var(--color-warning-bg);
  color: var(--color-warning-text);
}
.job-caution__tag {
  display: inline-block;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-bottom: var(--space-1);
}
.job-caution p { margin: var(--space-1) 0 0; }
.job-caution strong { color: var(--color-warning-text); }
/* Non-critical property brief (pets, parking) — quiet, scannable. */
.job-brief {
  margin: var(--space-3) 0;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-off-white);
  display: grid;
  gap: var(--space-1);
}
.job-brief p { margin: 0; font-size: var(--fs-sm); }
.job-card .access-row { display: flex; flex-wrap: wrap; gap: var(--space-2); margin: var(--space-2) 0; }
.btn-access, .btn-check-in, .btn-check-out {
  padding: 0.65rem 1rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--color-primary);
  background: var(--color-primary);
  color: var(--color-white);
  cursor: pointer;
  font-weight: 600;
  min-height: 44px;
}
.btn-check-in[disabled], .btn-check-out[disabled], .recurring-actions button[disabled] {
  background: var(--color-disabled-bg); border-color: var(--color-border);
  color: var(--color-disabled-text); cursor: not-allowed;
}
.access-output, .time-clock-feedback, .job-photo-feedback {
  margin-top: var(--space-2); color: var(--color-muted); font-size: var(--fs-sm);
}
/* A refusal's reasons, under the sentence that refused. The portal used to
   print only the message and drop the blockers the server sent with it, so a
   cleaner outside a customer's house learned that they were not authorized and
   nothing about which requirement or whose it was to fix. */
.refusal-reasons {
  margin: var(--space-1) 0 0; padding-left: var(--space-4);
  display: grid; gap: var(--space-1);
}
.refusal-reasons li { color: var(--color-text); }
.time-clock { display: grid; gap: var(--space-2); margin-top: var(--space-3); }
.checklist-summary { margin-top: var(--space-2); }
.job-photo { margin-top: var(--space-3); display: flex; flex-direction: column; gap: var(--space-2); }
.job-photo input, .job-photo select { padding: var(--space-2); max-width: 100%; }

/* Contract sign */
.contract {
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  box-shadow: var(--shadow-xs);
}
.rendered-markdown pre {
  white-space: pre-wrap;
  word-wrap: break-word;
  background: var(--color-off-white);
  padding: var(--space-4);
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
}
.contract .ack { display: block; margin: var(--space-2) 0; }
.contract .pricing ul.components { list-style: none; padding: 0; }
.contract .pricing ul.components li { display: flex; justify-content: space-between; }

/* Checklist */
.checklist-block {
  margin-top: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--color-off-white);
  border-radius: var(--radius-sm);
}
.checklist-block h4 { margin: 0 0 var(--space-2); font-size: var(--fs-sm); color: var(--color-primary); }
.checklist-list { list-style: none; padding: 0; margin: 0; }
.checklist-item {
  border-bottom: 1px solid var(--color-rule);
}
.checklist-item:last-child { border-bottom: 0; }
/* The row IS the target: full width, 44px floor, generous padding, so a
   gloved thumb aimed anywhere on the line hits it. */
.checklist-item__row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  cursor: pointer;
  padding: var(--space-3) var(--space-2);
  min-height: 44px;
  border-radius: var(--radius-sm);
  transition: background var(--dur-quick) var(--ease-out);
}
.checklist-item__row:hover { background: var(--color-primary-light); }
.checklist-item input[type="checkbox"] {
  width: 28px;
  height: 28px;
  flex: 0 0 auto;
  margin: 0;
  accent-color: var(--color-primary);
  cursor: pointer;
}
.checklist-item__body { display: grid; gap: var(--space-1); min-width: 0; }
.checklist-item__label { font-weight: 600; line-height: 1.35; }
/* Badges sit with the thing they describe, not on the line under it. */
.checklist-item__tags { display: flex; flex-wrap: wrap; gap: var(--space-1); }
.checklist-item__tags:empty { display: none; }
.checklist-item__note { color: var(--color-muted); }
/* A finished row should read as finished at a glance down the list. */
.checklist-item.is-done .checklist-item__label {
  color: var(--color-muted);
  text-decoration: line-through;
  text-decoration-color: var(--color-border);
}
.checklist-item.is-skipped .checklist-item__row { cursor: not-allowed; opacity: 0.85; }
.checklist-item.is-skipped .checklist-item__row:hover { background: transparent; }
.checklist-feedback { color: var(--color-muted); font-size: var(--fs-sm); margin-top: var(--space-1); }

/* Empty states */
.empty {
  background: var(--color-white);
  padding: var(--space-6);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
  text-align: center;
}
.empty h2 { margin-top: 0; color: var(--color-primary); }
/* File inputs in the field portal.

   A cleaner standing in someone's kitchen taps these more than any other
   control in the product, and they were the browser's raw "Choose File | No
   file chosen" — a small grey system button in the middle of an otherwise
   finished screen, with a hit target set by the platform rather than by us.
   ::file-selector-button is styled to match the portal's own buttons and given
   the same 44px floor as everything else a thumb has to find.

   The input keeps its native behaviour; only the button it draws is restyled,
   so the camera/photo-library sheet on iOS and Android is untouched. */
.job-photo-input,
.checklist-photo-input {
  display: block;
  width: 100%;
  max-width: 100%;
  font: inherit;
  color: var(--color-muted);
  font-size: var(--fs-sm);
}
.job-photo-input::file-selector-button,
.checklist-photo-input::file-selector-button {
  font: inherit;
  font-weight: 600;
  margin-right: var(--space-3);
  padding: 0.6rem 1rem;
  min-height: 44px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-primary);
  background: var(--color-white);
  color: var(--color-primary);
  cursor: pointer;
}
.job-photo-input::file-selector-button:hover,
.checklist-photo-input::file-selector-button:hover {
  background: var(--color-primary-light);
}
.job-photo-input:disabled::file-selector-button,
.checklist-photo-input:disabled::file-selector-button {
  background: var(--color-disabled-bg);
  border-color: var(--color-border);
  color: var(--color-disabled-text);
  cursor: not-allowed;
}

/* Error/utility */
.error {
  background: var(--color-danger-bg);
  color: var(--color-danger-text);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-danger-border);
}
[data-loading="true"] { color: var(--color-muted); }

/* 14. Utilities ----------------------------------------------------------- */
.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0);
  white-space: nowrap; border: 0;
}
.text-center { text-align: center; }
.text-muted  { color: var(--color-muted); }
.text-primary { color: var(--color-primary); }
.mt-0 { margin-top: 0; }
.mt-4 { margin-top: var(--space-4); }
.mt-6 { margin-top: var(--space-6); }
.mb-0 { margin-bottom: 0; }
.mb-4 { margin-bottom: var(--space-4); }
.mb-6 { margin-bottom: var(--space-6); }
.no-pad { padding: 0; }
.full-width { width: 100%; }

/* 15. Responsive ---------------------------------------------------------- */
@media (min-width: 640px) {
  .home-hero__layout { gap: var(--space-12); }
}

@media (min-width: 768px) {
  .callout-split { grid-template-columns: 1.1fr 1fr; }
}

@media (min-width: 900px) {
  .nav-toggle { display: none; }
  .site-nav { display: block; }
  .mobile-nav { display: none !important; }
  .brand img.brand__logo { height: 34px; }
}

@media (min-width: 1024px) {
  .home-hero__layout { grid-template-columns: 1.15fr 0.85fr; }
  .home-hero { padding: var(--space-24) 0 var(--space-20); }
  .section { padding: var(--space-20) 0; }
}

@media (max-width: 899px) {
  .site-footer__inner { grid-template-columns: 1fr; }
  /* Copy leads on small screens; the job-record panel follows as social
     proof once the pitch has landed. */
  .home-hero__visual { order: 1; }
  .hero-panel { width: min(400px, 100%); }
}

@media (max-width: 640px) {
  .site-footer__inner { gap: var(--space-6); }
  .home-hero { padding: var(--space-12) 0 var(--space-10); }
  .section { padding: var(--space-12) 0; }
  .container { padding: 0 var(--space-4); }
  h1 { font-size: var(--fs-3xl); }
  .home-hero h1 { font-size: clamp(2.1rem, 9vw, 2.7rem); }
  h2 { font-size: var(--fs-2xl); }
  /* The wizard card loses its desktop inset on small screens; the progress
     track is intrinsically narrow and needs no override here. */
  .estimate-form .estimate-step { padding: var(--space-5); }
  .estimate-form legend { font-size: var(--fs-xl); }

  /* Christian report #15: the generic circular feature-list bullets read as
     a stray ring around the tightly wrapped comparison text on mobile. The
     chooser uses a compact accent rule instead and an explicit one-column
     track, so there is no circular pseudo-element or auto-fit ambiguity in
     the #choose section at 360-390px. Other feature lists keep their bullets. */
  .services-choice-grid { grid-template-columns: minmax(0, 1fr); }
  .services-choice-grid .feature-list li {
    padding-left: var(--space-4);
    overflow-wrap: anywhere;
  }
  .services-choice-grid .feature-list li::before {
    top: 0.2em;
    width: 3px;
    height: 1.15em;
    border-radius: 1px;
    background: var(--color-accent);
  }
}

/* 16. In-house signing portal --------------------------------------------- */
.sign-shell { background: var(--color-primary-light); min-height: 100vh; }
.sign-main { padding: var(--space-8) 0 var(--space-16); }
.sign-page { max-width: 980px; margin: 0 auto; padding: 0 var(--space-4); }
.sign-page[data-loading="true"] .sign-header__inner h1::after { content: ' …'; }

.sign-header {
  background: var(--color-primary);
  color: var(--color-white);
  border-radius: 16px;
  padding: var(--space-8) var(--space-8) var(--space-10);
  margin-bottom: var(--space-6);
  box-shadow: 0 8px 22px rgba(16,39,71,0.18);
}
.sign-header__eyebrow {
  margin: 0 0 var(--space-2);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  font-size: var(--fs-sm);
  opacity: 0.85;
}
.sign-header h1 {
  margin: 0 0 var(--space-2);
  font-size: var(--fs-3xl);
  line-height: 1.15;
  /* The global `h1` colour rule is more specific for `color` than the
     inherited white on .sign-header, so this heading rendered near-black
     (#172033) on the navy card — 1.44:1, effectively invisible, on the one
     page a customer must read before signing. The eyebrow and subtitle
     around it were white, so only the heading missed the dark-card override. */
  color: inherit;
}
.sign-header__subtitle {
  margin: 0;
  opacity: 0.9;
  max-width: 60ch;
}
.sign-progress {
  margin-top: var(--space-6);
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.sign-progress__step {
  flex: 1 1 auto;
  text-align: center;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.18);
  padding: var(--space-2) var(--space-3);
  border-radius: 999px;
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.sign-progress__step[data-status="done"] {
  background: rgba(255,255,255,0.55);
  color: var(--color-primary);
  font-weight: 500;
  opacity: 0.85;
}
.sign-progress__step[data-status="active"] {
  background: var(--color-white);
  color: var(--color-primary);
  font-weight: 600;
}

.sign-content { display: grid; gap: var(--space-6); }
.sign-card {
  background: var(--color-white);
  border-radius: 14px;
  padding: var(--space-6) var(--space-6);
  box-shadow: 0 4px 14px rgba(16,39,71,0.06);
  border: 1px solid rgba(16,39,71,0.08);
}
.sign-card h2 {
  margin: 0 0 var(--space-3);
  color: var(--color-primary);
  font-size: var(--fs-xl);
}
.sign-card dl { display: grid; grid-template-columns: max-content 1fr; gap: var(--space-1) var(--space-4); margin: 0; }
.sign-card dt { font-weight: 600; color: var(--color-primary); }
.sign-card dd { margin: 0; white-space: pre-wrap; }
.sign-total { font-size: var(--fs-xl); font-weight: 700; color: var(--color-primary); margin: 0 0 var(--space-3); }
.sign-table { width: 100%; border-collapse: collapse; }
.sign-table th, .sign-table td { padding: var(--space-2) var(--space-3); text-align: left; border-bottom: 1px solid var(--color-rule); }
.sign-table th { background: var(--color-off-white); font-size: var(--fs-sm); text-transform: uppercase; letter-spacing: 0.05em; }
.sign-instructions { margin-top: var(--space-3); padding: var(--space-3); background: var(--color-primary-light); border-left: 4px solid var(--color-primary); border-radius: 4px; }

.sign-doc { background: var(--color-primary-light); border: 1px solid rgba(16,39,71,0.1); border-radius: 8px; padding: var(--space-3); margin-bottom: var(--space-2); }
.sign-doc summary { font-weight: 600; cursor: pointer; }
.sign-doc__meta { color: var(--color-muted); font-size: var(--fs-sm); margin: var(--space-2) 0 0; }
.sign-bundle-text h3 { color: var(--color-primary); margin: var(--space-3) 0 var(--space-1); }
.sign-bundle-meta { color: var(--color-muted); font-size: var(--fs-xs); margin: 0; }

.sign-ack-list { display: grid; gap: var(--space-2); }
.sign-ack { display: flex; gap: var(--space-3); padding: var(--space-2) var(--space-3); border-radius: 6px; cursor: pointer; }
.sign-ack:hover { background: var(--color-primary-light); }
.sign-ack input[type="checkbox"] { margin-top: 4px; }
.sign-initials-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: var(--space-3); }
.sign-field { display: grid; gap: var(--space-1); margin-bottom: var(--space-3); }
.sign-field label { font-weight: 600; }
.sign-field input { padding: var(--space-2) var(--space-3); border: 1px solid var(--color-border); border-radius: 6px; font-size: 16px; }
.sign-field--initials input { text-transform: uppercase; max-width: 8ch; }
.sign-help { color: var(--color-muted); font-size: var(--fs-sm); margin: 0; }
.sign-errors { background: var(--color-danger-bg); border: 1px solid var(--color-danger); color: var(--color-danger-text); padding: var(--space-3); border-radius: var(--radius-sm); margin: var(--space-3) 0; }
.sign-errors p { margin: 0 0 var(--space-2); font-weight: 600; }
.sign-errors ul { margin: 0; padding-left: var(--space-5); }
.sign-actions { display: flex; justify-content: flex-end; padding: var(--space-3) 0; }
.sign-failure { background: var(--color-danger-bg); border: 1px solid var(--color-danger); color: var(--color-danger-text); padding: var(--space-4); border-radius: var(--radius-md); }
.sign-success { background: var(--color-success-bg); border-color: var(--color-success-border); }
.sign-success h2 { color: var(--color-success); }
.sign-success__technical { margin-top: var(--space-3); }
.sign-success__technical summary { cursor: pointer; color: var(--color-muted); font-size: var(--fs-sm); }
.sign-success__technical dl { display: grid; grid-template-columns: max-content 1fr; gap: var(--space-1) var(--space-3); margin: var(--space-2) 0 0; font-size: var(--fs-sm); color: var(--color-muted); }
.sign-success__technical dt { font-weight: 600; }
.sign-success__technical dd { margin: 0; word-break: break-all; }

/* TRC-AUDIT-004/005 — legal-admin degraded cards + inline workflow forms. */
.sign-card--degraded .sign-total { color: var(--color-danger); font-size: var(--fs-md); }
.legal-form summary { cursor: pointer; font-weight: 600; color: var(--color-primary); }
.legal-form form { display: grid; gap: var(--space-3); margin-top: var(--space-3); }
.legal-field { display: grid; gap: var(--space-1); font-size: var(--fs-sm); font-weight: 600; }
.legal-field input[type="text"], .legal-field input[type="email"], .legal-field input[type="date"], .legal-field select {
  font-weight: 400; padding: var(--space-2); border: 1px solid var(--color-border); border-radius: var(--radius-sm);
}
.legal-field--check { grid-auto-flow: column; justify-content: start; align-items: center; gap: var(--space-2); font-weight: 400; }
.legal-row { padding: var(--space-3) 0; border-top: 1px solid var(--color-rule); }
.legal-row:first-child { border-top: 0; }
.legal-row__actions { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-2); }
.legal-row__slot { margin-top: var(--space-2); }
.legal-row__slot form { display: grid; gap: var(--space-3); }
.legal-quote-detail-shell { margin-top: var(--space-3); }
.legal-quote-detail {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-off-white);
}
.legal-quote-detail > header { display: flex; align-items: start; justify-content: space-between; gap: var(--space-3); }
.legal-quote-detail > header h4,
.legal-quote-detail > header p,
.legal-quote-detail h5 { margin: 0; }
.legal-quote-detail > header p { color: var(--color-muted); font-size: var(--fs-sm); }
.legal-quote-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); gap: var(--space-2); margin: 0; }
.legal-quote-facts > div { padding: var(--space-2); border: 1px solid var(--color-rule); border-radius: var(--radius-sm); background: var(--color-white); }
.legal-quote-facts dt { color: var(--color-muted); font-size: var(--fs-xs); font-weight: 700; text-transform: uppercase; letter-spacing: .04em; }
.legal-quote-facts dd { margin: var(--space-1) 0 0; overflow-wrap: anywhere; font-weight: 650; }
.legal-quote-detail details { min-width: 0; }
.legal-quote-detail summary { cursor: pointer; font-weight: 700; }
.legal-quote-detail pre { max-height: 24rem; margin: var(--space-2) 0 0; padding: var(--space-3); overflow: auto; border-radius: var(--radius-sm); background: var(--color-primary-dark); color: var(--color-white); font-size: var(--fs-xs); white-space: pre-wrap; overflow-wrap: anywhere; }
.legal-quote-photos { display: grid; grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); gap: var(--space-3); margin-top: var(--space-2); }
.legal-quote-photos a,
.legal-quote-photo--unavailable { display: grid; gap: var(--space-1); align-content: start; padding: var(--space-2); border: 1px solid var(--color-border); border-radius: var(--radius-sm); background: var(--color-white); color: var(--color-text); text-decoration: none; }
.legal-quote-photos img { width: 100%; aspect-ratio: 4 / 3; border-radius: var(--radius-xs); object-fit: cover; }
.legal-quote-photos span,
.legal-quote-photo--unavailable span { color: var(--color-muted); font-size: var(--fs-xs); overflow-wrap: anywhere; }

/* TRC-AUDIT-015 — employee offline sync banner. */

/* 17. Scroll-reveal motion system ------------------------------------------ */
/* site.js registers an IntersectionObserver for [data-reveal] and adds
   .is-revealed once per element. The hidden initial state only applies when
   JS has announced itself (html.js-motion) AND the user allows motion, so
   content is never invisible for no-JS visitors or reduced-motion users. */
@media (prefers-reduced-motion: no-preference) {
  /* Pre-reveal translateX boxes must not widen the page scroll area.
     TRC-PROD-20260826-011 — this was `html.js-motion body { overflow-x: clip }`,
     and on `body` it did nothing: overflow reaches the viewport from body only
     as a scrolling value, so the declaration computed exactly as written while
     `body.scrollWidth` kept reporting the wider content. / and /gallery scrolled
     sideways 10px on a phone and 2px on a tablet.
     Isolating it element by element in the live page named one cause on each:
     `.callout-split__media` and `.record-card`, both `[data-reveal="right"]`,
     both still holding `translateX(26px)` while below the fold, both already
     filling their column, and both inside a parent with `overflow-x: visible`.
     Clipping was then measured at every ancestor: `main` zeroes it, `body` does
     not. So it belongs here, on the content element, not on the root.
     `clip` and not `hidden`, so `main` never becomes a scroll container and
     sticky positioning and scroll anchoring keep working; the y axis stays
     `visible`, which is legal beside `clip` on x where `hidden` is not. */
  html.js-motion .main { overflow-x: clip; }
  html.js-motion [data-reveal] {
    opacity: 0;
    transform: translateY(26px);
    transition:
      opacity var(--dur-reveal) var(--ease-out),
      transform var(--dur-reveal) var(--ease-out);
    transition-delay: calc(var(--reveal-delay, 0) * 90ms);
    will-change: opacity, transform;
  }
  html.js-motion [data-reveal="fade"] { transform: none; }
  html.js-motion [data-reveal="left"] { transform: translateX(-26px); }
  html.js-motion [data-reveal="right"] { transform: translateX(26px); }
  html.js-motion [data-reveal].is-revealed {
    opacity: 1;
    transform: none;
    will-change: auto;
  }
  /* Stagger children of a reveal group without per-element attributes. */
  html.js-motion [data-reveal-group] > * {
    opacity: 0;
    transform: translateY(22px);
    transition:
      opacity var(--dur-reveal) var(--ease-out),
      transform var(--dur-reveal) var(--ease-out);
  }
  html.js-motion [data-reveal-group].is-revealed > * { opacity: 1; transform: none; }
  html.js-motion [data-reveal-group].is-revealed > *:nth-child(1) { transition-delay: 0ms; }
  html.js-motion [data-reveal-group].is-revealed > *:nth-child(2) { transition-delay: 90ms; }
  html.js-motion [data-reveal-group].is-revealed > *:nth-child(3) { transition-delay: 180ms; }
  html.js-motion [data-reveal-group].is-revealed > *:nth-child(4) { transition-delay: 270ms; }
  html.js-motion [data-reveal-group].is-revealed > *:nth-child(5) { transition-delay: 360ms; }
  html.js-motion [data-reveal-group].is-revealed > *:nth-child(6) { transition-delay: 450ms; }
  html.js-motion [data-reveal-group].is-revealed > *:nth-child(n+7) { transition-delay: 540ms; }
}

/* 18. Home / marketing components ------------------------------------------ */
.section--cream { background: var(--color-cream); }
.section--cream .card,
.section--cream .addon-card,
.section--cream .included-grid li { border-color: var(--color-cream-deep); }

/* Numbered process with a connecting thread. */
.step-flow {
  display: grid;
  gap: var(--space-5);
  counter-reset: step;
}
.step-flow__item {
  position: relative;
  padding-left: calc(48px + var(--space-5));
  counter-increment: step;
}
.step-flow__item::before {
  content: counter(step, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: 0;
  width: 48px;
  height: 48px;
  border-radius: var(--radius-pill);
  background: var(--color-white);
  border: 2px solid var(--color-accent);
  color: var(--color-navy-800);
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--fs-sm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.step-flow__item:not(:last-child)::after {
  content: "";
  position: absolute;
  left: 23.5px;
  top: 52px;
  bottom: calc(-1 * var(--space-5));
  width: 1px;
  background: linear-gradient(180deg, var(--color-accent) 0%, var(--color-border) 100%);
}
.step-flow__item h3 { margin-bottom: var(--space-2); }
.step-flow__item p { color: var(--color-muted); margin: 0; max-width: 52ch; }

/* Evidence stat tiles — what every job produces. */
.evidence-grid {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}
.evidence-tile {
  background: var(--color-white);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  position: relative;
  overflow: hidden;
}
.evidence-tile::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 3px;
  background: linear-gradient(90deg, var(--color-accent) 0%, transparent 85%);
  transform: scaleX(0.4);
  transform-origin: left;
  transition: transform var(--dur-med) var(--ease-out);
}
@media (prefers-reduced-motion: no-preference) {
  .evidence-tile {
    transition: box-shadow var(--dur-med) var(--ease-out),
      border-color var(--dur-med) ease, transform var(--dur-med) var(--ease-out);
  }
  .evidence-tile:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow-lg);
    border-color: var(--color-accent-light);
  }
  .evidence-tile:hover::after { transform: scaleX(1); }
}
.evidence-tile__figure {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--fs-4xl);
  letter-spacing: -0.02em;
  color: var(--color-navy-800);
  line-height: 1;
  margin-bottom: var(--space-2);
  display: flex;
  align-items: baseline;
  gap: 2px;
}
.evidence-tile__figure sup {
  font-size: 0.45em;
  color: var(--color-accent-text);
  font-weight: 700;
}
.evidence-tile h3 { font-size: var(--fs-md); margin-bottom: var(--space-2); }
.evidence-tile p { color: var(--color-muted); font-size: var(--fs-sm); margin: 0; }

/* Color-coded rag legend — the hygiene system, drawn not described. */
.rag-legend {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.rag-legend__item {
  /* .section--brand sets color:#fff on the whole section. This card repaints
     the background white and must repaint the text too — without this the
     entire colour-coded rag explainer rendered as four blank cards with a
     coloured square, on both / and /gallery. */
  color: var(--color-text);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  font-size: var(--fs-sm);
  font-weight: 600;
}
.rag-legend__swatch {
  width: 26px;
  height: 26px;
  border-radius: var(--radius-sm);
  flex-shrink: 0;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,0.08);
  position: relative;
  overflow: hidden;
}
.rag-legend__swatch::after {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(45deg, transparent 0 4px, rgba(255,255,255,0.28) 4px 5px);
}
/* The colours are the industry code, not a palette choice: red is washrooms
   and blue is general surfaces wherever cleaning is colour-coded, so a cleaner
   who has worked anywhere else already knows this legend and a customer who
   has seen it in an office recognises it. Green for kitchens follows the same
   convention; purple for floors is ours, because the standard reserves yellow
   for clinical work we do not do. Changing one of these changes what the crew
   is trained to, so change the training with it. */
.rag-legend__swatch--bath    { background: #C8443C; }
.rag-legend__swatch--kitchen { background: #4CAF6E; }
.rag-legend__swatch--floor   { background: #8E6FC8; }
.rag-legend__swatch--surface { background: #3E7BC4; }
.rag-legend__item small { display: block; color: var(--color-muted); font-weight: 400; }

/* Town chips get a subtle hover so the coverage cloud feels alive. */
.area-card__town { transition: border-color var(--dur-quick) ease, background-color var(--dur-quick) ease, transform var(--dur-quick) var(--ease-out); }
@media (prefers-reduced-motion: no-preference) {
  .area-card__town:hover {
    border-color: var(--color-accent);
    background: var(--color-accent-soft);
    transform: translateY(-1px);
  }
}

/* Final CTA band — deep navy with mesa glow, gold CTA. */
.cta-band {
  background:
    radial-gradient(90% 120% at 50% 130%, rgba(201,162,77,0.28), transparent 60%),
    linear-gradient(180deg, var(--color-navy-900) 0%, var(--color-navy-950) 100%);
}
.cta-band .section__eyebrow { color: var(--color-accent); }

/* Portal mock card used inside callout-split media panes. */
.portal-mock {
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  overflow: hidden;
  font-size: var(--fs-sm);
}
.portal-mock__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-3) var(--space-4);
  background: var(--color-navy-900);
  color: var(--color-white);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.portal-mock__bar span { color: var(--color-accent); }
.portal-mock__rows { list-style: none; margin: 0; padding: var(--space-2) 0; }
.portal-mock__rows li {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  border-bottom: 1px dotted var(--color-border);
  color: var(--color-text);
}
.portal-mock__rows li:last-child { border-bottom: 0; }
.portal-mock__rows .badge { flex-shrink: 0; }

/* Gallery "sample completion record" showcase — an honest, high-fidelity
   rendering of the artifact every job actually files (checklist, GPS
   timeline, filed photos, signed scope). Not fake customer photos — the
   real record, drawn as a product surface. */
.record-showcase {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-10);
  align-items: center;
}
@media (min-width: 940px) {
  .record-showcase { grid-template-columns: 1fr 1fr; gap: var(--space-12); }
}
.record-showcase__intro h2 { margin-bottom: var(--space-4); }
.record-showcase__intro p { color: var(--color-muted); max-width: 46ch; }
.record-showcase__points {
  list-style: none;
  margin: var(--space-5) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}
.record-showcase__points li {
  padding-left: var(--space-5);
  position: relative;
  font-size: var(--fs-sm);
  color: var(--color-muted);
}
.record-showcase__points strong { color: var(--color-text); }
.record-showcase__points li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.5em;
  width: 9px; height: 9px;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
}

.record-card {
  background: linear-gradient(180deg, #FFFFFF 0%, #F6F8FB 100%);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  box-shadow: 0 30px 70px -34px rgba(5, 14, 28, 0.5), 0 10px 24px -14px rgba(5, 14, 28, 0.25);
  padding: var(--space-6);
  position: relative;
  overflow: hidden;
}
.record-card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 4px;
  background: linear-gradient(90deg, var(--color-accent), var(--color-primary));
}
.record-card__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--color-rule);
}
.record-card__title {
  display: block;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--fs-lg);
  color: var(--color-navy-800);
}
.record-card__sub { display: block; font-size: var(--fs-xs); color: var(--color-muted); margin-top: 2px; }
.record-card__badge {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--color-success);
  background: var(--color-success-bg);
  border: 1px solid var(--color-success-border);
  padding: 0.25rem 0.6rem;
  border-radius: var(--radius-pill);
}
.record-card__badge::before {
  content: "";
  width: 7px; height: 7px; border-radius: var(--radius-pill); background: var(--color-success);
}
.record-card__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--color-rule);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--color-navy-800);
}
.record-card__k {
  display: block;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--color-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 1px;
}
.record-card__label {
  margin: var(--space-4) 0 var(--space-2);
  font-size: var(--fs-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-muted);
}
.record-card__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.record-card__item {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  font-size: var(--fs-sm);
  color: var(--color-text);
  padding: var(--space-2) var(--space-3);
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}
.record-card__check {
  flex-shrink: 0;
  width: 20px; height: 20px;
  border-radius: var(--radius-pill);
  background: var(--color-success-bg);
  border: 1px solid var(--color-success-border);
  color: var(--color-success);
  display: inline-flex; align-items: center; justify-content: center;
  margin-top: 1px;
}
.record-card__check svg { width: 11px; height: 11px; display: block; }
.record-card__item--skip { color: var(--color-muted); background: var(--color-off-white); }
.record-card__skip {
  flex-shrink: 0;
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-warning-text);
  background: var(--color-warning-bg);
  border: 1px solid var(--color-warning-border);
  border-radius: var(--radius-xs);
  padding: 0.2rem 0.35rem;
  margin-top: 1px;
}
.record-card__timeline {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-4) 0;
  padding: var(--space-3);
  background: var(--color-primary-light);
  border: 1px solid var(--color-info-border);
  border-radius: var(--radius-md);
  font-size: var(--fs-xs);
  color: var(--color-navy-700);
  flex-wrap: wrap;
}
.record-card__stamp { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.record-card__stamp strong { color: var(--color-navy-800); }
.record-card__dot { width: 8px; height: 8px; border-radius: var(--radius-pill); background: var(--color-success); flex-shrink: 0; }
.record-card__dot--out { background: var(--color-primary); }
.record-card__line { flex: 1; min-width: 20px; height: 2px; border-radius: 2px; background: linear-gradient(90deg, var(--color-success), var(--color-primary)); }
.record-card__photos {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
  flex-wrap: wrap;
}
.record-card__thumb {
  width: 40px; height: 40px;
  border-radius: var(--radius-sm);
  background:
    linear-gradient(135deg, var(--color-primary-light) 0%, #DCE7F4 100%);
  border: 1px solid var(--color-info-border);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--fs-xs); font-weight: 700; color: var(--color-navy-700);
  flex-shrink: 0;
}
.record-card__thumb--more { background: var(--color-navy-800); color: var(--color-white); border-color: var(--color-navy-700); }
.record-card__photos-label { font-size: var(--fs-xs); color: var(--color-muted); }
.record-card__foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding-top: var(--space-3);
  border-top: 1px dashed var(--color-border);
  font-size: var(--fs-xs);
  color: var(--color-muted);
}
.record-card__total strong { font-family: var(--font-display); font-size: var(--fs-md); color: var(--color-navy-800); margin-left: 6px; }

/* Section header link-arrow affordance for "see more" rows. */
.arrow-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  /* These are standalone calls to action, not links inside prose, so they
     answer to the 44px target floor. At a 25px line box they were a thumb's
     width short of what a phone user aims at. */
  min-height: 44px;
  font-weight: 700;
  text-decoration: none;
  color: var(--color-primary);
}
.arrow-link::after {
  content: "→";
  transition: transform var(--dur-quick) var(--ease-out);
}
.arrow-link:hover { text-decoration: underline; }
@media (prefers-reduced-motion: no-preference) {
  .arrow-link:hover::after { transform: translateX(4px); }
}
/* On navy sections the arrow link renders in accent gold. */
.arrow-link--light { color: var(--color-accent-light); }
.arrow-link--light:hover { color: var(--color-white); }

/* ---------------------------------------------------------------------------
   Links on dark sections.

   The global link colour is --color-primary, which is the same navy the heroes
   are painted in. On /estimate that produced a link at 1.10:1 — the sentence
   read "…never sets the price. See ." with a hole where the word "privacy"
   should be, on the page where the customer is deciding whether to hand over
   their address and photos. /turnover had the same hole, and /gallery used the
   dark-background arrow link without its --light modifier.
   
   Styling this by SECTION rather than by adding a modifier class to each
   anchor is deliberate: a modifier only protects the links someone remembered
   to mark, and the failure mode is invisible text. Buttons are excluded —
   they paint their own background.
   --------------------------------------------------------------------------- */
.page-hero a:not(.btn),
.home-hero a:not(.btn),
.cta-band a:not(.btn),
.section--navy a:not(.btn) {
  color: var(--color-accent-light);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.page-hero a:not(.btn):hover,
.home-hero a:not(.btn):hover,
.cta-band a:not(.btn):hover,
.section--navy a:not(.btn):hover {
  color: var(--color-white);
}

/* FAQ accordions — native details/summary, zero JS, CSP-safe. */
.faq-list { display: grid; gap: var(--space-3); }
.faq-item {
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 0;
  overflow: hidden;
  transition: border-color var(--dur-quick) ease, box-shadow var(--dur-quick) ease;
}
.faq-item[open] {
  border-color: var(--color-primary);
  box-shadow: var(--shadow-sm);
}
.faq-item summary {
  cursor: pointer;
  font-weight: 700;
  font-family: var(--font-display);
  color: var(--color-navy-800);
  padding: var(--space-4) var(--space-5);
  list-style: none;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-3);
  min-height: 44px;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after {
  content: "+";
  font-size: var(--fs-xl);
  font-weight: 400;
  color: var(--color-accent-dark);
  line-height: 1;
  flex-shrink: 0;
  transition: transform var(--dur-quick) var(--ease-out);
}
.faq-item[open] summary::after { transform: rotate(45deg); }
.faq-item summary:hover { background: var(--color-primary-light); }
.faq-item > p {
  padding: 0 var(--space-5) var(--space-4);
  margin: 0;
  color: var(--color-muted);
  max-width: 70ch;
}

/* Price-anchor line in page heroes — honest "starts around" figures that
   mirror the seeded base prices; the estimate remains the exact number. */
.hero-anchor {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-4);
  padding: var(--space-2) var(--space-4);
  border: 1px solid rgba(233, 217, 172, 0.4);
  border-radius: var(--radius-pill);
  background: rgba(201, 162, 77, 0.14);
  color: var(--color-accent-light);
  font-size: var(--fs-sm);
  font-weight: 600;
}
.hero-anchor__dot {
  width: 7px;
  height: 7px;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  flex-shrink: 0;
}

/* Employee field controls — previously unstyled (default browser buttons
   beside styled .btn elements). Mobile-first, 44px tap targets. */
.job-card--offer { border-left: 4px solid var(--color-accent); }
.offer-actions { display: flex; gap: var(--space-2); margin-top: var(--space-3); flex-wrap: wrap; }
.offer-actions button,
.btn-hygiene,
.btn-finalize {
  padding: 0.65rem 1rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--color-primary);
  background: var(--color-primary);
  color: var(--color-white);
  font-weight: 600;
  cursor: pointer;
  min-height: 44px;
}
.offer-actions button:hover, .btn-hygiene:hover, .btn-finalize:hover { background: var(--color-primary-dark); }
.offer-actions button[data-offer-decline] {
  background: var(--color-white);
  color: var(--color-danger);
  border-color: var(--color-danger-border);
}
.offer-actions button[data-offer-decline]:hover { background: var(--color-danger-bg); }
.btn-hygiene[disabled], .btn-finalize[disabled] {
  background: var(--color-disabled-bg);
  border-color: var(--color-border);
  color: var(--color-disabled-text);
  cursor: not-allowed;
}
.hygiene-confirm {
  margin-top: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--color-off-white);
  border-radius: var(--radius-md);
  display: grid;
  gap: var(--space-2);
}
.hygiene-confirm h4 { margin: 0; font-size: var(--fs-sm); color: var(--color-primary); }
.checklist-photo { display: grid; gap: var(--space-1); margin-top: var(--space-2); }
.checklist-photo-label { font-size: var(--fs-sm); font-weight: 600; }
.checklist-photo-input { max-width: 100%; }
.checklist-photo-status { font-size: var(--fs-xs); color: var(--color-muted); }
.booking-group { margin: var(--space-4) 0 var(--space-2); color: var(--color-primary); font-size: var(--fs-md); }
.booking-money { font-weight: 600; color: var(--color-text); }

/* Per-property invoicing (customer portal). "Invoices kept per property" is
   the claim on /property-managers, so the section groups by property and the
   monthly roll-up sits above the groups. Reuses the portal list rhythm. */
.invoice-list { list-style: none; padding: 0; margin: var(--space-2) 0; }
.invoice-list li { border-bottom: 1px dotted var(--color-border); padding: var(--space-3) 0; }
.invoice-list .meta { color: var(--color-muted); font-size: var(--fs-sm); }
.invoice-group { margin: var(--space-4) 0 var(--space-2); color: var(--color-primary); font-size: var(--fs-md); }
.invoice-money { font-weight: 600; color: var(--color-text); }
.invoice-lines { list-style: none; padding: 0; margin: var(--space-2) 0 0; font-size: var(--fs-sm); }
.invoice-lines li { display: flex; justify-content: space-between; gap: var(--space-3); padding: var(--space-1) 0; border: 0; }
.invoice-scope { font-style: italic; }
.invoice-void { color: var(--color-warning); }
.invoice-statement {
  margin: var(--space-2) 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: var(--color-off-white);
  border-radius: var(--radius-md);
}
.invoice-statement__lines { list-style: none; padding: 0; margin: var(--space-2) 0; font-size: var(--fs-sm); }
.invoice-statement__lines li { display: flex; justify-content: space-between; gap: var(--space-3); padding: var(--space-1) 0; }
.invoice-statement__total { font-weight: 600; color: var(--color-text); }

/* Simple stacked public form (contact page). Deliberately minimal: the base
   input/select/textarea rules already carry the sizing and focus ring, so
   this only owns the vertical rhythm and the result banner. */
.stack-form { display: grid; gap: var(--space-4); }
.stack-form > label { display: grid; gap: var(--space-1); font-weight: 600; }
.stack-form .hint { font-weight: 400; margin-top: calc(var(--space-2) * -1); }
.stack-form > button { justify-self: start; }
.form-status {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  background: var(--color-off-white);
  font-size: var(--fs-sm);
}
.form-status--ok {
  background: var(--color-success-bg);
  border-color: var(--color-success-border);
  color: var(--color-success-text);
}
.form-status--error {
  background: var(--color-danger-bg);
  border-color: var(--color-danger-border);
  color: var(--color-danger-text);
}
@media (max-width: 560px) {
  /* One-handed on a phone: the send button spans the column rather than
     sitting as a small target in the corner. */
  .stack-form > button { justify-self: stretch; width: 100%; }
}

/* ---------------------------------------------------------------
   P0-11 — Safety Data Sheets in the employee field portal.

   Read one-handed on a 390px phone, possibly with wet or gloved hands and
   possibly in a hurry: every control clears 44px, nothing overflows
   horizontally, body text stays at the base size (no zoom), and long
   chemical names wrap instead of pushing the layout sideways.
   --------------------------------------------------------------- */
.portal-safety-link {
  min-height: 44px;
  font-size: var(--fs-sm);
  padding: 0.6rem 1rem;
}

.sds-emergency {
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-danger-border);
  border-radius: var(--radius-md);
  background: var(--color-danger-bg);
}
.sds-emergency__lede {
  margin: 0 0 var(--space-2);
  font-weight: 700;
  color: var(--color-danger-text);
  font-size: var(--fs-sm);
}
.sds-emergency__actions {
  display: grid;
  gap: var(--space-2);
}
.sds-emergency__call {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 0.7rem 1rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--color-danger-border);
  background: var(--color-white);
  color: var(--color-danger-text);
  font-weight: 700;
  text-decoration: none;
  text-align: center;
  overflow-wrap: anywhere;
}
.sds-emergency__call--911 {
  background: var(--color-danger);
  border-color: var(--color-danger);
  color: var(--color-white);
}

.sds-view { max-width: 100%; }
.sds-toolbar { display: grid; gap: var(--space-2); margin-bottom: var(--space-3); }
.sds-search { display: block; }
.sds-search__input {
  width: 100%;
  max-width: 100%;
  min-height: 48px;
  padding: 0.7rem 0.9rem;
  /* 16px minimum: anything smaller makes iOS Safari zoom the page on focus,
     which is how a one-handed search turns into a pinch-to-fix. */
  font-size: 1rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-white);
  color: var(--color-text);
}
.sds-status { margin: 0; font-size: var(--fs-xs); color: var(--color-muted); }

.sds-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3); }
.sds-card {
  border-radius: var(--radius-md);
  background: var(--color-white);
  overflow: hidden;
}
.sds-card--danger { border-left: 4px solid var(--color-danger); }
.sds-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  min-height: 56px;
  padding: var(--space-3) var(--space-4);
  cursor: pointer;
  font-weight: 600;
  color: var(--color-primary);
  list-style: none;
}
.sds-summary::-webkit-details-marker { display: none; }
.sds-summary__name { flex: 1 1 12rem; overflow-wrap: anywhere; }
.sds-summary__signal { flex: 0 0 auto; }
.sds-details[open] .sds-summary { border-bottom: 1px solid var(--color-rule); }

.sds-body { padding: var(--space-3) var(--space-4) var(--space-4); display: grid; gap: var(--space-3); }
.sds-field { display: grid; gap: var(--space-1); }
.sds-field__label {
  margin: 0;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-muted);
}
.sds-field__text { margin: 0; font-size: var(--fs-base); line-height: 1.5; overflow-wrap: anywhere; }
.sds-field--firstaid,
.sds-field--nomix {
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-danger-border);
  background: var(--color-danger-bg);
}
.sds-field--firstaid .sds-field__label,
.sds-field--nomix .sds-field__label { color: var(--color-danger-text); }
.sds-field--nomix .sds-field__text { font-weight: 600; }

.sds-firstaid { margin: 0; display: grid; gap: var(--space-2); }
.sds-firstaid__term { font-weight: 700; font-size: var(--fs-sm); color: var(--color-danger-text); }
.sds-firstaid__detail {
  margin: 0 0 var(--space-1);
  font-size: var(--fs-base);
  line-height: 1.5;
  overflow-wrap: anywhere;
}

.sds-ppe { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--space-2); }
.sds-ppe__item {
  padding: 0.35rem 0.7rem;
  border-radius: var(--radius-pill);
  background: var(--color-primary-light);
  color: var(--color-primary);
  font-size: var(--fs-sm);
  font-weight: 600;
}

.sds-meta { margin: 0; font-size: var(--fs-xs); color: var(--color-muted); overflow-wrap: anywhere; }
.sds-source-note {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-warning-border);
  border-radius: var(--radius-sm);
  background: var(--color-warning-bg);
  color: var(--color-warning-text);
  font-size: var(--fs-sm);
  line-height: 1.45;
}

.sds-doc-row { display: grid; gap: var(--space-2); }
.btn-sds-doc {
  min-height: 44px;
  padding: 0.65rem 1rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--color-primary);
  background: var(--color-white);
  color: var(--color-primary);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
}
.btn-sds-doc:hover { background: var(--color-primary-light); }
.btn-sds-doc[disabled] { color: var(--color-muted); border-color: var(--color-border); cursor: not-allowed; }
.sds-doc-feedback { font-size: var(--fs-sm); color: var(--color-muted); overflow-wrap: anywhere; }

.sds-empty { margin: 0; padding: var(--space-4); color: var(--color-muted); }
.sds-footnote { margin: var(--space-4) 0 0; font-size: var(--fs-xs); color: var(--color-muted); line-height: 1.5; }

@media (min-width: 30rem) {
  .sds-emergency__actions { grid-template-columns: 1fr 1fr; }
}

/* ---------------------------------------------------------------------------
   Touch target floor.

   The public pages were built to a 44px minimum and hold it. The three
   PORTALS were not — the responsive audit could only reach pages it can load
   without logging in, so every control behind a sign-in went unmeasured. When
   the probe was finally pointed at them it found 36px controls throughout:
   .btn-sm (the portal's default action size), the recurring-plan buttons, and
   the file input a cleaner uses to attach proof photos — that last one on a
   phone, outdoors, one-handed, wearing gloves.

   Raising the floor only ever grows a control, and only where a thumb is the
   pointer. Desktop keeps its compact controls: `(pointer: coarse)` covers real
   touch devices, and the width clause covers the emulated viewports the audit
   drives, which report a fine pointer.

   Checkboxes and radios are deliberately excluded — the platform sizes them
   and their label is the real target.
   --------------------------------------------------------------------------- */
@media (max-width: 900px), (pointer: coarse) {
  .btn,
  .btn-sm,
  button,
  /* These carry their own 36px min-height, which beats a bare `button`
     selector on specificity — naming them is the only way the floor applies. */
  .recurring-actions button,
  .reschedule-form button,
  [role="button"],
  summary,
  select,
  textarea,
  input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]) {
    min-height: 44px;
  }
  /* A control can clear 44px tall and still be a sliver to hit. */
  .btn-sm,
  .recurring-actions button,
  .reschedule-form button {
    min-width: 44px;
  }
  /* A file input renders its own button; the box has to be tall enough for it. */
  input[type="file"] {
    padding: 0.5rem 0;
    width: 100%;
  }
  /* Standalone nav links are targets, not prose. */
  .site-nav__link,
  .site-footer a {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }
}

/* The skipped-item note sits under the counter, not beside it: on a phone in
   a doorway the counter is the number and this is the reason for it. */
.checklist-skipnote {
  display: block;
  font-size: var(--fs-sm);
  color: var(--color-muted);
  margin: var(--space-2) 0 0;
}

/* ---------------------------------------------------------------------------
   App footer — the working surfaces.

   Compact by intent: see partials/app-footer.html.raw for why the marketing
   footer does not belong under a job card.
   --------------------------------------------------------------------------- */
.app-footer {
  background: var(--color-navy-900);
  color: rgba(233,238,246,0.9);
  padding: var(--space-4) var(--space-4) var(--space-3);
  margin-top: var(--space-6);
  font-size: var(--fs-sm);
}
.app-footer__inner {
  max-width: var(--container-width);
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-5);
}
.app-footer__brand { font-weight: 600; color: var(--color-white); }
.app-footer__links { display: flex; gap: var(--space-4); margin-left: auto; }
.app-footer a {
  color: var(--color-accent-light);
  text-decoration: underline;
  text-underline-offset: 3px;
  /* A three-character link ("Terms") is 41px wide before this — tall enough
     and still too narrow to hit with a thumb. */
  min-height: 44px;
  min-width: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.app-footer a:hover { color: var(--color-white); }
.app-footer__bottom {
  max-width: var(--container-width);
  margin: var(--space-3) auto 0;
  padding-top: var(--space-3);
  border-top: 1px solid rgba(255,255,255,0.12);
  color: rgba(233,238,246,0.7);
  font-size: var(--fs-xs);
}
@media (max-width: 40rem) {
  .app-footer__links { margin-left: 0; }
}

/* The expected price leads; the range explains it. */
.estimate-price { margin-top: var(--space-1); }
.estimate-price__range {
  display: block;
  font-size: var(--fs-sm);
  color: var(--color-muted);
}

/* The signing page's dead-end exit. Sits outside #sign-content because the
   failure path hides that container — which is why the buttons the code
   already tried to render were never visible. */
.sign-failure-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin: var(--space-4) auto 0;
  max-width: var(--container-narrow);
  padding: 0 var(--space-4);
}

/* Legal-admin counts read as a row, not a stack of banners. */
.legal-stat-row {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  margin-bottom: var(--space-5);
}
.legal-stat-row .sign-card { margin: 0; }

/* 19. Portal design layer --------------------------------------------------
   The three portals are where the business actually runs, and they were the
   least designed surfaces in the product: a white box with a 1px border and
   an <h3>, repeated fourteen times down a scroll. Every record read as one
   undifferentiated paragraph, every status was plain text, every action was
   an underlined link, and the admin dashboard put its most dangerous controls
   (the break-glass flags) in monospace snake_case.

   This layer gives them the same vocabulary the marketing site already has —
   eyebrow, accent rule, card, badge, arrow link — plus the parts a working
   surface needs and a brochure does not: a record row, a stat tile, a table
   that can be scanned, and an empty state that says what fills it.

   Everything here is composition over the existing tokens. No new colours. */

/* --- section card --------------------------------------------------------
   The card body itself is declared once, up in section 13 with
   `.dashboard-grid > div, .admin-grid > div, .job-card`; this used to restate
   it here with an outline, and being the later rule it quietly put the border
   back on every portal card. One declaration, one place. */
.section-card {
  background: var(--color-white);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  box-shadow: var(--shadow-sm);
  text-align: left;
}

/* The heading row: title left, "as of"/refresh right, gold rule beneath.
   Mirrors `.section__head` on the marketing side so a portal section and a
   marketing section read as the same species. */
.section-card__head,
.dashboard-grid > div > h3,
.admin-grid > div > h3 {
  margin: 0 0 var(--space-4);
}
.section-card__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding-bottom: var(--space-3);
}
.section-card__head h3,
.section-card__head h4 {
  margin: 0;
  font-size: var(--fs-md);
  letter-spacing: -0.01em;
}
.section-card__meta {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--fs-xs);
  color: var(--color-muted);
}
/* A portal section heading that is NOT inside a __head still gets the mark,
   so sections that have not been converted do not look like a different
   product from the ones that have. */
.dashboard-grid > div > h3,
.admin-grid > div > h3,
.portal-section > h3 {
  font-size: var(--fs-md);
  letter-spacing: -0.01em;
  padding-bottom: var(--space-3);
  position: relative;
}
.dashboard-grid > div > h3::after,
.admin-grid > div > h3::after,
.section-card__head::after,
.portal-section > h3::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 2.25rem;
  height: 2px;
  border-radius: 2px;
  background: var(--color-accent);
}
.section-card__head { position: relative; }

/* --- record rows ---------------------------------------------------------
   The atom the customer portal repeats: a title line that can carry a status,
   a meta line, and actions. It replaces the <li> of run-together text where a
   clean, its times, its crew and its price were four sentences in a row. */
.record-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-1);
}
.record {
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--color-rule);
}
.record:last-child { border-bottom: 0; padding-bottom: 0; }
.record:first-child { padding-top: 0; }
.record__title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  font-weight: 700;
  font-size: var(--fs-base);
  color: var(--color-text);
  margin: 0;
}
.record__meta {
  margin: var(--space-1) 0 0;
  font-size: var(--fs-sm);
  color: var(--color-muted);
  display: grid;
  gap: 0.15rem;
}
.record__meta strong { color: var(--color-text); font-weight: 600; }
.record__actions {
  margin-top: var(--space-3);
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}
/* A money figure should be findable without reading the sentence around it. */
.record__amount {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: var(--color-text);
}

/* Voluntary post-completion gratuity. It is visually and textually separate
   from the service-price and balance controls above it. */
/* Tipping reads as the warm end of the service record, not a payment form
   bolted to the bottom of it. Cream rather than the grey utility panel, and
   the crew is thanked in a sentence before any control appears (§38, §78). */
.gratuity-form {
  margin-top: var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--color-accent-light);
  border-radius: var(--radius-md);
  background: var(--color-cream);
  display: grid;
  gap: var(--space-3);
}
.gratuity-form__copy { display: grid; gap: var(--space-1); }
.gratuity-form__copy strong { color: var(--color-primary-dark); font-size: var(--fs-md); }
.gratuity-form__copy span,
.gratuity-form__status { color: var(--color-muted); font-size: var(--fs-sm); }
.gratuity-form__suggestions {
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}
.gratuity-form__suggestions legend {
  margin-bottom: var(--space-2);
  color: var(--color-text);
  font-size: var(--fs-sm);
  font-weight: 700;
}
/* An even row at every width. Flex with a rem basis wrapped 2 + 1 at 390px,
   which reads as one option being different from the other two. */
.gratuity-form__suggestion-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-2);
}
/* Dollars lead, the percentage explains. A customer choosing a tip is
   deciding an amount, not a rate, and should not have to do the arithmetic
   themselves (§34). */
.gratuity-form__suggestion {
  min-height: 3.25rem;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-md);
  background: var(--color-white);
  color: var(--color-primary-dark);
  font: inherit;
  cursor: pointer;
}
.gratuity-form__suggestion-amount { display: block; font-size: var(--fs-md); font-weight: 800; font-variant-numeric: tabular-nums; }
.gratuity-form__suggestion-percent { display: block; color: var(--color-muted); font-size: var(--fs-xs); font-weight: 600; }
.gratuity-form__suggestion[aria-pressed="true"] {
  border-color: var(--color-primary);
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
  background: var(--color-primary-light);
}
.gratuity-form__suggestion:focus-visible,
.gratuity-form__no-tip:focus-visible {
  outline: 3px solid var(--color-primary);
  outline-offset: 2px;
}
.gratuity-form__controls {
  display: flex;
  align-items: end;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.gratuity-form__amount {
  display: grid;
  gap: var(--space-1);
  font-size: var(--fs-sm);
  font-weight: 700;
}
.money-input {
  min-height: 2.75rem;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0 var(--space-3);
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-md);
  background: var(--color-white);
  font-variant-numeric: tabular-nums;
}
.money-input:focus-within {
  border-color: var(--color-primary);
  outline: 3px solid var(--color-primary);
  outline-offset: 2px;
}
.money-input input {
  width: 6.5rem;
  border: 0;
  outline: 0;
  padding: 0;
  background: transparent;
  color: var(--color-text);
  font: inherit;
  font-size: max(16px, var(--fs-base));
}
.gratuity-form__choice {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 2.75rem;
  font-size: var(--fs-sm);
}
.gratuity-form__choice input { width: 1.25rem; height: 1.25rem; }
.gratuity-form__choice input:focus-visible {
  outline: 3px solid var(--color-primary);
  outline-offset: 2px;
}
.gratuity-form__exact {
  margin: 0;
  color: var(--color-primary-dark);
  font-size: var(--fs-lg);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.gratuity-form__exact:empty,
.gratuity-form__status:empty { display: none; }
/* Declining sits beside leaving a tip, at the same reading level. Shrinking
   it to grey fine print is the coercion §35 rules out. */
.gratuity-form__decide {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
}
.gratuity-form__no-tip {
  min-height: 2.75rem;
  padding: 0 var(--space-2);
  border: 0;
  background: none;
  color: var(--color-primary);
  font: inherit;
  font-size: var(--fs-sm);
  text-decoration: underline;
  cursor: pointer;
}
.gratuity-form__no-tip:focus-visible,
.gratuity-form__suggestion:focus-visible { outline: 3px solid var(--color-primary); outline-offset: 2px; }

.gratuity-history {
  display: grid;
  gap: var(--space-2);
  margin-top: var(--space-3);
  padding: var(--space-3);
  border-inline-start: 3px solid var(--color-accent);
  background: var(--color-cream);
}
.gratuity-history__rows { display: grid; gap: var(--space-1); margin: 0; }
.gratuity-history__rows > div {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--space-3);
  align-items: baseline;
}
.gratuity-history__rows dt { color: var(--color-text); font-size: var(--fs-sm); }
.gratuity-history__rows dd { margin: 0; color: var(--color-primary-dark); font-weight: 700; font-variant-numeric: tabular-nums; }
.gratuity-history__note { margin: 0; color: var(--color-muted); font-size: var(--fs-sm); }

.employee-gratuities {
  display: grid;
  gap: var(--space-4);
  margin-top: var(--space-8);
  padding-top: var(--space-6);
  border-top: 1px solid var(--color-rule-strong);
}
.employee-gratuities__head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(16rem, 32rem);
  gap: var(--space-4);
  align-items: end;
}
.employee-gratuities__head h2 { margin: 0; }
.employee-gratuities__head p { margin: 0; color: var(--color-muted); }
/* Rows, not a grid of finance tables. A cleaner reads this on a phone in
   somebody's driveway; a card per tip with five label/value pairs each is the
   dense payroll grid §54 rules out. */
.employee-tip-list {
  display: grid;
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}
.employee-tip {
  display: grid;
  gap: var(--space-1);
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--color-rule);
}
.employee-tip:first-child { padding-top: 0; }
.employee-tip:last-child { border-bottom: 0; padding-bottom: 0; }
.employee-tip--correction,
.employee-tip--review {
  padding-inline-start: var(--space-3);
  border-inline-start: 3px solid var(--color-warning);
}
/* The amount is the answer to the only question being asked. */
.employee-tip__amount {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin: 0;
}
.employee-tip__label {
  color: var(--color-muted);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.employee-tip__amount strong {
  color: var(--color-primary-dark);
  font-size: var(--fs-2xl);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.employee-tip__job { margin: 0; color: var(--color-primary-dark); font-size: var(--fs-md); font-weight: 700; }
.employee-tip__split,
.employee-tip__payroll { margin: 0; color: var(--color-muted); font-size: var(--fs-sm); }
.employee-tip--review .employee-tip__payroll { color: var(--color-warning-text); }
.employee-cash-tip-report,
.employee-cash-tip-history {
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-md);
  background: var(--color-off-white);
}
.employee-cash-tip-report > summary,
.employee-cash-tip-history > summary { min-height: 2.75rem; cursor: pointer; font-weight: 800; }
.cash-tip-report-form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3);
  align-items: end;
}
.cash-tip-report-form label { display: grid; gap: var(--space-1); }
.cash-tip-attribution {
  display: grid;
  gap: var(--space-2);
  min-width: 0;
  margin: 0;
  padding: var(--space-3);
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-md);
}
.cash-tip-attribution legend { padding: 0 var(--space-1); font-weight: 800; }
.cash-tip-attribution label {
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  min-height: 2.75rem;
  cursor: pointer;
}
.cash-tip-attribution input { inline-size: 1.25rem; block-size: 1.25rem; }
.cash-tip-report-form .gratuity-form__choice,
.cash-tip-report-form [data-cash-tip-feedback] { grid-column: 1 / -1; }
.cash-tip-report-actions {
  display: grid;
  grid-column: 1 / -1;
  gap: var(--space-2);
}
.cash-tip-report-actions .btn {
  width: 100%;
  min-height: 3rem;
}

.customer-receipt-list {
  display: grid;
  gap: var(--space-2);
  margin: var(--space-3) 0 0;
  padding: 0;
  list-style: none;
}
.customer-receipt {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-md);
  background: var(--color-white);
}
.customer-receipt > div { display: grid; gap: 0.15rem; }
.customer-receipt > div span { color: var(--color-muted); font-size: var(--fs-sm); }
@media (max-width: 40rem) {
  .customer-receipt { align-items: stretch; flex-direction: column; }
  .gratuity-form__controls { align-items: stretch; flex-direction: column; }
  .gratuity-form__amount,
  .gratuity-form__controls .btn { width: 100%; }
  .money-input { width: 100%; }
  .money-input input { width: 100%; }
  .employee-gratuities__head,
  .cash-tip-report-form { grid-template-columns: 1fr; }
}

/* Group headings inside a card ("Upcoming (1)", "Past (2)"). */
.record-group {
  margin: var(--space-4) 0 var(--space-2);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-muted);
}
.record-group:first-of-type { margin-top: 0; }

/* --- stat tiles ----------------------------------------------------------
   The admin KPI row wrapped a wide tile onto its own line, leaving a stub.
   An auto-fit grid keeps them even at any width. */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.stat {
  background: var(--color-off-white);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  display: grid;
  gap: 0.1rem;
  align-content: start;
}
.stat__value {
  font-family: var(--font-display);
  font-size: var(--fs-2xl);
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--color-primary);
  font-variant-numeric: tabular-nums;
}
.stat__label {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-muted);
}
.stat__hint { font-size: var(--fs-xs); color: var(--color-muted); }
/* A stat that needs attention earns colour; the rest stay quiet, so colour
   still means something on a dashboard read every morning. */
.stat--alert { background: var(--color-danger-bg); border-color: var(--color-danger-border); }
.stat--alert .stat__value { color: var(--color-danger-text); }
.stat--warn { background: var(--color-warning-bg); border-color: var(--color-warning-border); }
.stat--warn .stat__value { color: var(--color-warning-text); }
.stat--wide { grid-column: span 2; }
@media (max-width: 30rem) { .stat--wide { grid-column: auto; } }
/* Four of the admin tiles are drilldown buttons. They need the browser's
   button chrome removed and a visible affordance, or they read as the same
   flat box as the static ones while behaving differently. */
button.stat {
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color var(--dur-quick) var(--ease-out),
              box-shadow var(--dur-quick) var(--ease-out);
}
button.stat:hover { border-color: var(--color-primary); box-shadow: var(--shadow-sm); }
button.stat[aria-expanded="true"] { border-color: var(--color-primary); box-shadow: var(--shadow-sm); }

/* --- empty states -------------------------------------------------------- */
.empty-state {
  padding: var(--space-5) var(--space-4);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-off-white);
  color: var(--color-muted);
  font-size: var(--fs-sm);
}
.empty-state strong { display: block; color: var(--color-text); margin-bottom: var(--space-1); }
.empty-state .cluster { margin-top: var(--space-3); }

/* --- tables -------------------------------------------------------------- */
.data-table tbody tr:nth-child(odd) { background: var(--color-off-white); }
.data-table thead th {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-muted);
  background: var(--color-white);
  border-bottom: 2px solid var(--color-border);
}
.data-table td { vertical-align: top; }
/* Money and counts line up on the decimal so a column can be scanned. */
.data-table .num { text-align: right; font-variant-numeric: tabular-nums; }

/* --- portal shell -------------------------------------------------------- */
.portal-header {
  background: linear-gradient(180deg, var(--color-white), var(--color-off-white));
}
.portal-header h1 { letter-spacing: -0.02em; }
.portal-header__role {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-primary);
  margin: 0 0 var(--space-1);
}
/* A tinted page behind white cards is what makes a card read as a card. */
.portal-shell .main { background: var(--portal-ground, var(--color-off-white)); }

/* Business settings, as a readable list rather than an object literal. */
.settings-list__label { display: block; font-size: var(--fs-base); }
.settings-list__row dt code { font-size: var(--fs-xs); color: var(--color-muted); }
.setting-kv-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0 var(--space-4);
  font-size: var(--fs-sm);
}
.setting-kv { display: contents; }
.setting-kv__k { color: var(--color-muted); padding: 0.3rem 0; border-bottom: 1px dotted var(--color-border); }
.setting-kv__v {
  text-align: right;
  min-width: 0;
  overflow-wrap: anywhere;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  padding: 0.3rem 0;
  border-bottom: 1px dotted var(--color-border);
}

/* --- employee field portal ------------------------------------------------
   A cleaner reads this on a phone, in someone's hallway, often in a hurry.
   The two things they check most — am I clocked in, and how much of the
   checklist is left — were plain sentences among other plain sentences. */
.job-card__when { margin: var(--space-1) 0 0; font-weight: 600; color: var(--color-text); }
.job-card__crew { margin: var(--space-1) 0 0; font-size: var(--fs-sm); color: var(--color-muted); }

.clock-strip {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.clock-strip__cell {
  background: var(--color-off-white);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3);
  display: grid;
  gap: 0.15rem;
}
.clock-strip__k {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-muted);
}
.clock-strip__v {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.clock-strip__pending { font-weight: 400; color: var(--color-muted); }

/* The counter IS the heading of the checklist stage — a separate "CHECKLIST"
   label above a "1 of 3 required" line was two headings for one number. */
.checklist-counter {
  margin: 0 0 var(--space-2);
  font-size: var(--fs-lg);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}
.progress-bar {
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--color-border);
  overflow: hidden;
}
.progress-bar__fill {
  display: block;
  height: 100%;
  background: var(--color-primary);
  border-radius: var(--radius-pill);
  transition: width var(--dur-med) var(--ease-out);
}
@media (prefers-reduced-motion: reduce) { .progress-bar__fill { transition: none; } }

/* The photo control had a file input and a bare type dropdown with no label
   between them, so the cleaner picked a type without being told it decides
   who sees the photo. */
.job-photo__field { display: grid; gap: 0.25rem; font-size: var(--fs-sm); font-weight: 600; margin-bottom: var(--space-3); }
.job-photo__field select {
  font: inherit; font-weight: 400;
  padding: 0.55rem 0.7rem; min-height: 44px; width: 100%;
  border: 1px solid var(--color-border); border-radius: var(--radius-sm);
  background: var(--color-white); color: var(--color-text);
}
.job-photo__note { margin: 0; font-size: var(--fs-xs); color: var(--color-muted); }

/* --- employee job card: designed around the stages of the job -------------
   The card was a plain white box with a thin border and eleven sibling
   blocks of equal weight. On a phone, in someone's hallway, that means the
   cleaner reads the whole card to find the one control that is live. */
.job-card {
  padding: 0;
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-md);
}
/* Deliberately NOT `overflow: hidden`. That was here to clip the hero's
   corners, and it silently disables `position: sticky` on any descendant —
   which is exactly how the action dock is pinned. The hero and the dock round
   their own outer corners instead. */
.job-card > .rail,
.job-card > .job-next,
.job-card > .stage { padding-left: var(--space-5); padding-right: var(--space-5); }

/* Hero: the same deep-navy anchor the marketing heroes use, so the portal
   reads as the same product rather than an internal tool bolted on. */
.job-hero {
  position: relative;
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  background:
    radial-gradient(120% 140% at 100% 0%, rgba(201,162,77,0.22), transparent 60%),
    linear-gradient(160deg, var(--color-navy-800), var(--color-navy-950));
  color: var(--color-white);
  padding: var(--space-6) var(--space-5) var(--space-5);
}
.job-hero__eyebrow {
  display: block;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-accent-light);
  margin-bottom: var(--space-2);
}
.job-hero__title {
  font-family: var(--font-display);
  font-size: var(--fs-2xl);
  line-height: 1.12;
  letter-spacing: -0.02em;
  margin: 0;
  color: var(--color-white);
}
.job-hero__when {
  margin: var(--space-3) 0 0;
  font-weight: 700;
  font-size: var(--fs-md);
  color: var(--color-white);
  font-variant-numeric: tabular-nums;
}
.job-hero__crew { margin: var(--space-1) 0 0; font-size: var(--fs-sm); color: rgba(255,255,255,0.82); }
/* Address + directions, side by side, because they are one decision. */
.job-where {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-5);
  margin-bottom: var(--space-4);
}
.job-where__address { margin: 0; font-weight: 600; }
.job-where .btn { flex: 0 0 auto; }
/* "Get Directions" with its glyph is wider than the "Directions" text link it
   replaced, and the address beside it is a full street line. On a phone the two
   stop competing for one row: the address reads, the button sits under it at
   full tap size rather than being squeezed to fit. */
@media (max-width: 30rem) {
  .job-where {
    flex-wrap: wrap;
    align-items: flex-start;
  }
  .job-where .btn { flex: 1 1 auto; }
}

/* The do-not-do block is a liability control, not a note. */
.job-caution {
  border-left: 4px solid var(--color-warning);
  border-radius: var(--radius-md);
  background: var(--color-warning-bg);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
}
.job-caution__tag {
  display: inline-block;
  font-size: var(--fs-xs);
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-warning-text);
  margin-bottom: var(--space-2);
}
.job-caution p { margin: var(--space-1) 0 0; color: var(--color-warning-text); }

/* --- account members ------------------------------------------------------
   Who is on this account, and what each of them can do, is the question this
   list answers. It was a <li> with a bold name and two grey paragraphs. */
.member {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--color-rule);
}
.member:last-child { border-bottom: 0; }
.member__avatar {
  flex: 0 0 auto;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--radius-pill);
  background: var(--color-primary-light);
  color: var(--color-primary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  font-size: var(--fs-sm);
  letter-spacing: 0.02em;
}
.member__body { min-width: 0; flex: 1; }
.member__name {
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  font-weight: 700;
}
.member__email { margin: var(--space-1) 0 0; font-size: var(--fs-sm); color: var(--color-muted); }
.member__can { margin: var(--space-1) 0 0; font-size: var(--fs-sm); color: var(--color-muted); }
.member-actions { margin-top: var(--space-3); display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center; }
.member-actions select {
  font: inherit;
  padding: 0.4rem 0.6rem;
  min-height: 44px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-field);
  background: var(--color-white);
  color: var(--color-text);
}

/* Portal forms: inputs were browser-default in several places. */
.dashboard-grid input[type="text"],
.dashboard-grid input[type="email"],
.dashboard-grid input[type="password"],
.dashboard-grid input[type="number"],
.dashboard-grid select,
.dashboard-grid textarea {
  font: inherit;
  width: 100%;
  max-width: 100%;
  min-height: 44px;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-white);
  color: var(--color-text);
}
.dashboard-grid label { display: grid; gap: 0.25rem; font-size: var(--fs-sm); font-weight: 600; margin-bottom: var(--space-3); }
.dashboard-grid label.checkbox-row { display: flex; align-items: center; gap: var(--space-2); font-weight: 400; }
.dashboard-grid label.checkbox-row input[type="checkbox"] { width: 22px; height: 22px; flex: 0 0 auto; accent-color: var(--color-primary); }

/* --- admin information architecture ---------------------------------------
   The dashboard was fourteen identical cards down a scroll, so "an estimate is
   waiting for you" and "here is the retention schedule" had the same weight
   and the same shape. The bands separate work that is waiting from record you
   look things up in from configuration you touch on purpose. */
/* Standing instructions: label and value are different things. */
.property-instructions {
  display: grid;
  gap: var(--space-1);
  margin: var(--space-3) 0;
  padding: var(--space-3);
  background: var(--color-off-white);
  border-radius: var(--radius-md);
}
.prop-note { display: grid; grid-template-columns: 6.5rem minmax(0, 1fr); gap: var(--space-2); font-size: var(--fs-sm); }
.prop-note__k {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-muted);
  padding-top: 0.15rem;
}
.prop-note__v { color: var(--color-text); overflow-wrap: anywhere; }
/* The two that are instructions NOT to do something carry the caution colour,
   because that is the pair a crew is held to. */
.prop-note--avoid .prop-note__k { color: var(--color-warning-text); }
@media (max-width: 26rem) {
  .prop-note { grid-template-columns: 1fr; gap: 0; }
}

/* 20. Design grammar -------------------------------------------------------
   The portals had a component vocabulary — card, badge, table, button — and no
   grammar: nothing that said "this product has a point of view". Everything
   was a white rectangle with a 1px outline, so the eye had no way to tell a
   container from a divider from a control, and the three portals differed only
   in what data they happened to hold.

   This section supplies the three missing pieces:

     1. ONE drawn hand.  Every glyph comes from public/js/lib/icons.js, on a
        single 24x24 grid with a single stroke weight. `.ico` is its only
        sizing rule, so an icon is never larger than the text it labels.

     2. ONE landform.  The Table Rock contour — foothills, the rise, the flat
        mesa top, the drop — is a single authored profile (RIDGE in icons.js)
        reused at every scale: the mark in each app shell, the terrain behind
        a progress rail, the divider between two planes. Repetition of one
        real shape is what turns a palette into a place.

     3. THREE densities.  Same navy, gold, cream, typeface, geometry and
        motion; three different amounts of air. A customer looking at their
        home is not doing the same thing as a cleaner standing in a driveway
        or an owner triaging a Tuesday, and the surfaces should not pretend
        otherwise.

   No new colours. No new card variants. Every rectangle deleted here was
   deleted because a background plane, a spacing step, or the type scale was
   already doing that job. */

/* --- icons ---------------------------------------------------------------- */
.ico {
  /* Sized in `em` so a glyph always matches the text it sits beside, and
     scales with the density's type scale for free. */
  width: 1.15em;
  height: 1.15em;
  flex: 0 0 auto;
  vertical-align: -0.2em;
}
.ico--lg { width: 1.5em; height: 1.5em; vertical-align: -0.32em; }
.ico--xl { width: 2.25em; height: 2.25em; vertical-align: -0.55em; }
/* An icon that names a state borrows that state's colour rather than
   inventing one. */
.ico--accent  { color: var(--color-accent-text); }
.ico--warn    { color: var(--color-warning-text); }
.ico--danger  { color: var(--color-danger); }
.ico--ok      { color: var(--color-success-text); }
.ico--muted   { color: var(--color-muted); }

/* --- the contour ---------------------------------------------------------- */
.contour-band {
  display: block;
  width: 100%;
  height: 100%;
}
/* Used as a plane divider: the ridgeline replaces what would otherwise be a
   horizontal rule between two sections of a portal. */
.contour-rule {
  height: 26px;
  color: var(--color-primary);
  opacity: 0.16;
  margin: var(--space-6) 0;
}

/* --- the progress rail ---------------------------------------------------
   One component, two readings. For a customer it is the life of a service —
   booked, signed, paid, crew, cleaned, filed. For a cleaner it is the life of
   a shift — travel, arrive, clean, proof, done. Same idea from two sides, so
   it is deliberately the same object.

   Progress is structural, not computed: each step owns the track segment to
   its LEFT and paints it gold once reached. No width percentage means no
   inline style, which is the only way dynamic sizing can survive our CSP. */
.rail {
  position: relative;
  padding: var(--space-5) 0 var(--space-1);
}
.rail__terrain {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2.4rem;
  color: var(--color-primary);
  opacity: 0.22;
  pointer-events: none;
}
.rail__steps {
  position: relative;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  list-style: none;
  margin: 0;
  padding: 0;
}
.rail__step {
  position: relative;
  padding-top: 1.85rem;
  text-align: center;
  min-width: 0;
}
/* The segment arriving at this station. */
.rail__step::before {
  content: "";
  position: absolute;
  top: calc(0.625rem - 1px);
  left: -50%;
  right: 50%;
  height: 2px;
  background: var(--color-rule-strong);
}
.rail__step:first-child::before { display: none; }
.rail__step--done::before,
.rail__step--current::before { background: var(--color-accent); }

.rail__dot {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 50%;
  background: var(--color-white);
  box-shadow: inset 0 0 0 2px var(--color-rule-strong);
  display: grid;
  place-items: center;
}
.rail__step--done .rail__dot {
  background: var(--color-accent);
  box-shadow: none;
  color: var(--color-navy-900);
}
.rail__step--current .rail__dot {
  box-shadow: inset 0 0 0 3px var(--color-accent);
}
.rail__tick { width: 0.72rem; height: 0.72rem; stroke-width: 3.4; }
.rail__label {
  display: block;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--color-muted);
  overflow-wrap: anywhere;
  padding: 0 0.15rem;
}
.rail__step--current .rail__label { color: var(--color-text); }
.rail__step--done .rail__label { color: var(--color-primary); }
.rail__note {
  display: block;
  margin-top: 0.1rem;
  font-size: var(--fs-xs);
  color: var(--color-muted);
  overflow-wrap: anywhere;
}
/* Six stations do not fit across a phone. Below that the rail turns vertical,
   which is also the reading order, so nothing is lost. */
@media (max-width: 34rem) {
  .rail__terrain { display: none; }
  .rail__steps { grid-auto-flow: row; grid-auto-columns: auto; gap: var(--space-1); }
  .rail__step {
    padding: 0 0 0 2rem;
    text-align: left;
    min-height: 1.9rem;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
  .rail__step::before {
    top: -50%;
    bottom: 50%;
    left: calc(0.625rem - 1px);
    right: auto;
    width: 2px;
    height: auto;
  }
  .rail__dot { top: 50%; left: 0; transform: translateY(-50%); }
  .rail__label { display: inline; }
  .rail__note { display: inline; margin-left: var(--space-2); }
}

/* --- three densities ------------------------------------------------------
   Driven off `data-density` on <body>, which the application shells set, with
   the page identity as a fallback so a portal is never left un-tuned. */

/* Hospitality — the customer. This is someone's home being looked after, so
   the surface is warm, wide-margined and quiet. Cream ground, generous
   padding, the largest radius in the product. */
[data-density="hospitality"],
[data-page="customer-dashboard"],
[data-page="account-legal"] {
  --portal-ground: #F3F0E8;
  --portal-pad: var(--space-6);
  --portal-gap: var(--space-5);
  --portal-radius: var(--radius-xl);
  --portal-pad-block: var(--space-10);
  --portal-measure: var(--container-width);
}

/* Instrument — the cleaner. Read at arm's length, outdoors, one-handed, often
   in sun. Cool ground so a white card holds its edge at high ambient light,
   larger base type, bigger controls, and no decoration competing with the one
   thing that matters on screen. */
[data-density="instrument"],
[data-page="employee-dashboard"] {
  --portal-ground: #E9EDF3;
  --portal-pad: var(--space-5);
  --portal-gap: var(--space-4);
  --portal-radius: var(--radius-lg);
  --portal-pad-block: var(--space-6);
  --portal-measure: 56rem;
}
[data-density="instrument"] .portal-main,
[data-page="employee-dashboard"] .portal-main { font-size: var(--fs-md); }

/* Cockpit — the owner. Triage, comparison, a lot of true things at once.
   Tighter everything; the information density IS the design. */
[data-density="cockpit"],
[data-page="admin"],
[data-page^="legal-admin"] {
  --portal-ground: #EDF0F5;
  --portal-pad: var(--space-4);
  --portal-gap: var(--space-3);
  --portal-radius: var(--radius-md);
  --portal-pad-block: var(--space-6);
  --portal-measure: var(--container-wide);
}
[data-density="cockpit"] .portal-main,
[data-page="admin"] .portal-main,
[data-page^="legal-admin"] .portal-main { line-height: 1.45; }

/* The density variables reach the containers that were previously hard-coded,
   so changing one line above re-tunes a whole portal. */
.dashboard-grid > div,
.admin-grid > div,
.job-card,
.section-card {
  padding: var(--portal-pad, var(--space-5));
  border-radius: var(--portal-radius, var(--radius-lg));
}
.dashboard-grid,
.admin-grid { gap: var(--portal-gap, var(--space-5)); }

/* 21. Application shells ---------------------------------------------------
   Three authenticated surfaces, three chromes, one brand.

   Until now all three wore the marketing header: Services / Service Areas /
   About / Sign in / Get an estimate, sitting above a signed-in application. It
   offered a signed-in customer a "Sign in" button, offered a cleaner standing
   in a driveway a services menu, and told every one of them that they were on
   a website page rather than in a tool. The brand stays; the website chrome
   leaves.

   What differs between the three is density and what is persistent, not
   colour or type. Same navy, same gold, same Plus Jakarta, same geometry. */

.app-nav {
  --app-nav-h: 4rem;
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--color-white);
  box-shadow: 0 1px 0 var(--color-rule), 0 2px 10px rgba(16, 39, 71, 0.05);
}
.app-nav__inner {
  max-width: var(--container-wide);
  margin: 0 auto;
  min-height: var(--app-nav-h);
  padding: 0 var(--space-5);
  display: flex;
  align-items: center;
  gap: var(--space-5);
}

/* --- the mark ------------------------------------------------------------- */
.app-nav__brand {
  display: flex;
  min-width: 0;
  align-items: center;
  gap: var(--space-3);
  text-decoration: none;
  color: inherit;
  flex: 0 0 auto;
  /* A floor in both directions: on the narrowest phones the brand word is
     hidden and this link is the mark alone, which is 42px wide on its own. */
  min-height: 44px;
  min-width: 44px;
  padding: var(--space-2) 0;
}
/* The same asset the marketing header uses, at the same 30px cap, so signing
   in does not change the logo a customer just saw. `width:auto` keeps the
   generated aspect ratio; the width/height attributes on the element reserve
   the box so the nav does not reflow when it decodes. */
.app-nav__logo {
  display: block;
  height: 30px;
  width: auto;
  flex: 0 0 auto;
}
.app-nav__word {
  display: grid;
  line-height: 1.1;
}
.app-nav__word strong {
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: 800;
  letter-spacing: 0.02em;
}
.app-nav__sub {
  font-size: var(--fs-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-muted);
}

/* --- section links -------------------------------------------------------- */
.app-nav__links {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  flex: 1 1 auto;
  min-width: 0;
  /* The scrollbar used to be hidden here outright, which left a narrow admin
     screen with nothing to say that more sections existed off the right edge.
     It now gets the strip tier of the canonical scrollbar (section 2). */
  overflow-x: auto;
}
.app-nav__link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-pill);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--color-muted);
  text-decoration: none;
  white-space: nowrap;
}
.app-nav__link:hover { color: var(--color-primary); background: var(--color-primary-light); }
.app-nav__link[aria-current="true"] {
  color: var(--color-primary-dark);
  background: var(--color-accent-soft);
}

/* --- end cluster ---------------------------------------------------------- */
.app-nav__end {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-left: auto;
  flex: 0 0 auto;
}
.app-nav__tool,
.app-nav__signout {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
  padding: 0 var(--space-3);
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-muted);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}
.app-nav__tool:hover,
.app-nav__signout:hover { color: var(--color-primary); background: var(--color-primary-light); }
.app-nav__account:empty { display: none; }

/* Which destination you are standing on.
   Two channels, deliberately. Below 48rem the tool labels are visually hidden,
   so the row is nothing but glyphs, and a colour change alone would leave the
   current screen unreadable to anyone who cannot use colour on exactly the
   widths where there is no other clue. The bar is the second channel. This
   used to be styled for `.app-nav__link` and `.app-rail__link` only, neither of
   which the field tools use, so `aria-current` was announced to screen readers
   and drawn for nobody. */
.app-nav__tool[aria-current="page"] {
  position: relative;
  color: var(--color-primary-dark);
  background: var(--color-accent-soft);
}
.app-nav__tool[aria-current="page"]::after {
  content: "";
  position: absolute;
  left: 25%;
  right: 25%;
  bottom: -2px;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
}

/* --- customer: hospitality ------------------------------------------------ */
.app-nav--customer {
  background: var(--color-white);
}
.app-nav--customer .app-nav__word strong { color: var(--color-primary-dark); }

/* --- field: instrument ---------------------------------------------------
   Navy chrome so the white job document below it reads as the lit surface.
   In a bright driveway, a white bar above a white card is one white shape. */
.app-nav--field {
  --app-nav-h: 3.5rem;
  background: var(--color-navy-900);
  box-shadow: none;
}
.app-nav--field .app-nav__word strong { color: var(--color-white); }
.app-nav--field .app-nav__sub { color: rgba(255, 255, 255, 0.72); }
.app-nav--field .app-nav__tool { color: rgba(255, 255, 255, 0.88); }
.app-nav--field .app-nav__tool:hover { color: var(--color-white); background: rgba(255, 255, 255, 0.12); }
/* The accent-soft pill is invisible on navy chrome, so the dark shells get
   their own current-page fill. The bar underneath is inherited. */
.app-nav--field .app-nav__tool[aria-current="page"],
.app-nav--admin .app-nav__tool[aria-current="page"] {
  color: var(--color-white);
  background: rgba(255, 255, 255, 0.2);
}
/* Signed out, expired, wrong role, or the probe failed. The shell is static so
   it can paint with no session, which also meant a visitor who is not in the
   app was shown a five-tool rail with Jobs marked as the current destination.
   Only the brand remains — it is the way back to a screen that will explain
   itself. The sync chip stays: connection state is a device fact and is still
   true. Matches .app-nav--customer / .app-nav--admin. */
.app-nav--field[data-signed-out="true"] .app-nav__end,
.app-nav--field[data-signed-out="true"] .shift-chip { display: none; }

/* --- admin: cockpit ------------------------------------------------------- */
.app-nav--admin {
  --app-nav-h: 3.25rem;
  background: var(--color-navy-950);
  box-shadow: none;
}
.app-nav--admin .app-nav__word strong { color: var(--color-white); }
.app-nav--admin .app-nav__sub { color: rgba(255, 255, 255, 0.66); }
.app-nav--admin .app-nav__tool,
.app-nav--admin .app-nav__signout { color: rgba(255, 255, 255, 0.86); }
.app-nav--admin .app-nav__tool:hover,
.app-nav--admin .app-nav__signout:hover { color: var(--color-white); background: rgba(255, 255, 255, 0.12); }


/* The palette trigger reads as a search field, because that is what pressing
   it produces. A bare magnifier icon teaches nobody what is searchable. */
.app-nav__search {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  /* A touch target, not a decoration: on mobile the label is hidden and this
     collapses to its icon, so both floors have to be stated. */
  min-height: 44px;
  min-width: 44px;
  justify-content: center;
  padding: 0 var(--space-3);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.08);
  color: rgba(255, 255, 255, 0.8);
  font: inherit;
  font-size: var(--fs-sm);
  cursor: pointer;
  max-width: 22rem;
}
.app-nav__search:hover { background: rgba(255, 255, 255, 0.14); color: var(--color-white); }
.app-nav__search-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.app-nav__kbd {
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  padding: 0.1rem 0.35rem;
  border-radius: var(--radius-xs);
  background: rgba(255, 255, 255, 0.16);
  color: var(--color-white);
}

/* --- the workspace rail ---------------------------------------------------
   Horizontal under the header on narrow screens; a fixed left column from
   64rem up, where a cockpit wants its navigation permanently in view rather
   than scrolled away with the content. Same markup either way. */
.app-rail {
  background: var(--color-navy-800);
}
.app-rail__inner {
  max-width: var(--container-wide);
  margin: 0 auto;
  padding: 0 var(--space-3);
  display: flex;
  gap: var(--space-1);
  /* On a phone this strip is the whole application's navigation. It used to be
     a single scrolling row with hidden scrollbars, so on a 390px screen it cut
     off mid-word after the third item and three of the five workspaces were
     undiscoverable — nothing on screen said there was more. It wraps instead:
     every destination is visible, which is the only acceptable answer for the
     top-level rail.

     This supersedes the strip-tier scrollbar the rail briefly got: a bar you
     can find is a worse answer than having nothing to scroll. `scrollbar-width`
     went with it, because there is no scrollbar left to describe. */
  flex-wrap: wrap;
  overflow-x: visible;
}
.app-rail__link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
  /* Tighter on a phone so five destinations fit two rows rather than running
     off the edge. The 44px target is untouched. */
  padding: 0 var(--space-2);
  color: rgba(255, 255, 255, 0.78);
  font-size: var(--fs-sm);
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  border-bottom: 2px solid transparent;
}
@media (min-width: 30rem) {
  .app-rail__link { padding: 0 var(--space-3); }
}
.app-rail__link:hover { color: var(--color-white); }
.app-rail__link[aria-current="page"] {
  color: var(--color-white);
  border-bottom-color: var(--color-accent);
}

@media (min-width: 64rem) {
  .app-nav--admin .app-rail {
    position: fixed;
    top: var(--app-nav-h);
    left: 0;
    bottom: 0;
    width: 13rem;
    z-index: 39;
    overflow-y: auto;
  }
  .app-rail__inner {
    flex-direction: column;
    flex-wrap: nowrap;
    padding: var(--space-4) var(--space-2);
    gap: var(--space-1);
    overflow-x: visible;
  }
  .app-rail__link {
    min-height: 2.75rem;
    border-bottom: 0;
    border-left: 2px solid transparent;
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  }
  .app-rail__link[aria-current="page"] {
    border-left-color: var(--color-accent);
    background: rgba(255, 255, 255, 0.09);
  }
  /* The workspace column is out of flow and full-height, so everything in the
     page's own flow makes room for it — the footer included, which otherwise
     runs underneath the rail and loses its first few words. */
  [data-density="cockpit"] .main,
  [data-density="cockpit"] .app-footer,
  [data-page="admin"] .main { padding-left: 13rem; }
}

/* --- the sync chip --------------------------------------------------------
   Permanent, not conditional. A banner that appears only when something is
   wrong teaches a cleaner to assume everything is fine when they see nothing,
   which is exactly the assumption that loses a checklist. */
.sync-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  padding: 0.3rem var(--space-3);
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.1);
  color: var(--color-white);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  white-space: nowrap;
  /* The chip yields before the destinations do.
     "Offline · showing last sync" is three times the width of "Online", and
     with `nowrap` and no shrink it pushed the right-hand tools clean off the
     screen the moment a cleaner lost signal - measured at every field width,
     with two tools unreachable on a 375px phone even before Ask existed. The
     tools are what anyone taps; the chip is a readout. So the chip is the thing
     that truncates. */
  flex: 0 1 auto;
  min-width: 0;
}

.sync-chip__text {
  overflow: hidden;
  text-overflow: ellipsis;
}
.sync-chip__dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: #5ED89A;
  flex: 0 0 auto;
}
.sync-chip[data-sync="syncing"] .sync-chip__dot { background: var(--color-accent); }
.sync-chip[data-sync="offline"] .sync-chip__dot { background: #F0A33C; }
.sync-chip[data-sync="offline"] { background: rgba(240, 163, 60, 0.2); }
.sync-chip[data-sync="unavailable"] .sync-chip__dot { background: var(--color-danger); }
.sync-chip[data-sync="unavailable"] { background: color-mix(in srgb, var(--color-danger) 14%, transparent); }
@media (prefers-reduced-motion: no-preference) {
  @keyframes sync-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
  .sync-chip[data-sync="syncing"] .sync-chip__dot { animation: sync-pulse 1.1s ease-in-out infinite; }
}

/* Whether a clock is running, carried across the whole field shell.
   Built on the sync chip because the two are the same object -- a small state
   readout in the nav -- but it is a LINK, not a status line: the one thing
   anybody wants after reading "on the clock" is the screen that stops it.

   It is only in the DOM while a clock is running (employee-shift.js hides it
   otherwise), so unlike the sync chip it is not competing for width most of
   the time, and it keeps its word at every size. Gold rather than the sync
   chip's green: this is the brand mark saying you are being paid, and two
   green dots in one bar would read as one state reported twice. */
.shift-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  padding: 0.3rem var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--color-accent-soft);
  border: 1px solid rgba(234, 217, 172, 0.45);
  color: var(--color-accent-light);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  white-space: nowrap;
  text-decoration: none;
  /* Unlike the sync chip, this one does NOT yield. It is in the DOM only while
     a clock is running, and truncated to "O…" it says nothing at all -- a chip
     that cannot be read is worse than a chip that is not there. The sync chip
     beside it is already down to a dot at these widths and absorbs the squeeze
     it was written to absorb. */
  flex: 0 0 auto;
}
.shift-chip:hover { background: rgba(201, 162, 77, 0.28); color: var(--color-white); }
.shift-chip:focus-visible { outline: 2px solid var(--color-accent-light); outline-offset: 2px; }
.shift-chip__text { overflow: hidden; text-overflow: ellipsis; }
.shift-chip__dot {
  width: 0.5rem;
  height: 0.5rem;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--color-accent-light);
}
.shift-chip[data-shift="on_meal"] {
  background: rgba(240, 163, 60, 0.2);
  border-color: rgba(240, 163, 60, 0.4);
  color: #F6D9A8;
}
.shift-chip[data-shift="on_meal"] .shift-chip__dot { background: #F0A33C; }
/* A reading that came off this device rather than off the server. Never
   pulsing: a stale clock must not animate as though it were live. */
.shift-chip[data-stale="true"] {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.22);
  color: rgba(255, 255, 255, 0.78);
}
.shift-chip[data-stale="true"] .shift-chip__dot { background: rgba(255, 255, 255, 0.5); }
@media (prefers-reduced-motion: no-preference) {
  .shift-chip:not([data-stale="true"]) .shift-chip__dot {
    animation: sync-pulse 2.4s ease-in-out infinite;
  }
}

/* The phone form of the same fact. The chip's word does not fit under 34rem
   without costing a destination its place, so the state moves onto the tool
   that owns it: a gold dot on Time, which is where the tap was going anyway.
   Only one of the two is ever on screen. */
.app-nav__tool[data-shift] { position: relative; }
.app-nav__tool[data-shift]::after {
  content: "";
  position: absolute;
  top: 0.35rem;
  right: 0.15rem;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--color-accent-light);
  display: none;
}
.app-nav__tool[data-shift="on_meal"]::after { background: #F0A33C; }

@media (max-width: 48rem) {
  .app-nav__inner { gap: var(--space-3); padding: 0 var(--space-4); }
  .app-nav__word .app-nav__sub { display: none; }
  .app-nav__tool span,
  .app-nav__signout span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  /* With the label hidden the button shrank to its icon plus padding — 41px,
     under the 44px touch floor. */
  .app-nav__tool,
  .app-nav__signout { min-width: 44px; justify-content: center; padding: 0 var(--space-2); }
  .app-nav__cta { display: none; }
  .app-nav__search-label { display: none; }
  .app-nav__kbd { display: none; }
  /* The wordmark is 4.11:1, so every pixel of height costs four of width in a
     row that has none to spare. It steps down rather than shrinking the tools,
     which are one-tap field affordances. */
  .app-nav__logo { height: 24px; }
}
@media (max-width: 34rem) {
  .app-nav__word strong { font-size: var(--fs-base); }
  .sync-chip { padding: 0.3rem var(--space-2); }
  /* Narrow enough that even a truncated wording costs a tool its place. The
     coloured dot still reports the state, the text stays in the accessibility
     tree for the `role="status"` announcement, and every destination stays on
     screen and tappable. */
  .app-nav--field .sync-chip__text { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .app-nav--field .sync-chip { padding: 0.4rem; gap: 0; }
  /* At 360px the field chrome carries a brand, a sync chip and two tools. The
     brand word yields first: the mark still identifies the app, and the two
     tools are the only things anyone taps up here. */
  .app-nav--field .app-nav__word,
  .app-nav--admin .app-nav__word { display: none; }
  .app-nav__inner { gap: var(--space-2); padding: 0 var(--space-3); }
  .app-nav__logo { height: 16px; }
  /* The chip's word is what makes its colour legible to anyone who cannot use
     colour, so the dot never travels alone. The padding goes instead. */
  .sync-chip { padding: 0.25rem var(--space-1); font-size: var(--fs-xs); }
  /* Measured: five tools, a brand and a sync dot leave about 50px here, and
     the chip needs ~90px to say "On the clock". Rather than truncate it to a
     letter, the chip stands down and the Time tool wears the dot. */
  .app-nav--field .shift-chip { display: none; }
  .app-nav--field .app-nav__tool[data-shift]::after { display: block; }
  /* Five tools, a chip and a brand in 360px. The tools keep their 44px floor;
     the space between them is what yields. */
  .app-nav__end { gap: var(--space-1); }
  .app-nav__tool,
  .app-nav__signout { padding: 0 var(--space-1); }
}

/* --- the five-destination field row --------------------------------------
   Jobs, Time, Mail, Safety and Switch is 5 x 44px of untouchable tap target
   plus a brand, a sync chip and the gaps between them. The row was already at
   its last notch at 34rem before Time existed, so the two steps below are what
   pays for the fifth destination. Nothing here shrinks a tap target: the
   44px floor holds at every width, and no label that is visible today is
   hidden. */
@media (max-width: 26rem) {
  .app-nav--field .app-nav__inner { gap: var(--space-1); padding: 0 var(--space-2); }
  .app-nav--field .app-nav__end { gap: 0; }
  .app-nav--field .app-nav__tool { padding: 0; }
}
@media (max-width: 23rem) {
  /* Last resort, and the cheapest thing in the row: this link's href is
     /employee, byte for byte the same destination as the Jobs tool sitting a
     few pixels to its right. Losing it costs no way to go anywhere, and the
     alternative is a tap target under the floor or a destination pushed off
     the screen. */
  .app-nav--field .app-nav__brand { display: none; }
}

/* --- page head (sub-pages inside a shell) ---------------------------------
   The portals' main screens title themselves from their own content. Their
   sub-pages — legal documents, the six legal-admin views — still need a plain
   title block, and `.portal-header` was built as a two-column flex row for a
   heading beside actions, so a third child (the description) became a third
   column and the copy spread across the full width as three narrow strips. */
.page-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-bottom: var(--space-6);
}
.page-head__text { max-width: 46rem; }
.page-head__eyebrow {
  margin: 0 0 var(--space-1);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-accent-text);
}
.page-head h1 {
  margin: 0;
  font-size: var(--fs-2xl);
  letter-spacing: -0.02em;
}
.page-head__lede {
  margin: var(--space-2) 0 0;
  color: var(--color-muted);
  font-size: var(--fs-sm);
}
.page-head__actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }

/* The pre-JS title. Same size as the rendered one so the layout does not jump
   when the real content arrives. */
.portal-loading__title {
  margin: 0 0 var(--space-3);
  font-size: var(--fs-2xl);
  letter-spacing: -0.02em;
}

/* 22. Field instrument -----------------------------------------------------
   The employee portal, rebuilt around the five states a job actually has.

   What changed, and why:

   - The day now opens with a sentence, not a count. "Today's jobs (3)" is
     inventory; "3 jobs today · 2 to work · 1 finished" is a shift.

   - Each job carries the stage rail (site.css §20) so where you are in the
     job is drawn, not described, and the panel for the current stage carries
     the emphasis while the others recede.

   - The terminal action left the document for a dock pinned to the bottom of
     the screen. `position: sticky; bottom: 0` inside the card means the dock
     belongs to whichever job fills the viewport, with no JavaScript and no
     scroll listener — and it means the one live control is under the thumb of
     someone holding a phone in one hand and a mop in the other.

   - Photographs are taken with a camera, so the proof stage is three camera
     tiles rather than a dropdown and a file input. */

.day-head { margin: 0 0 var(--space-5); }
.day-head__title {
  margin: 0;
  font-size: var(--fs-3xl);
  letter-spacing: -0.025em;
}
.day-head__meta {
  margin: var(--space-1) 0 0;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--color-muted);
}
/* Room to scroll the last thing on the page clear of the dock. Without it the
   final control on the final job can only ever sit underneath it. */
[data-density="instrument"] .portal-main { padding-bottom: 9rem; }
.day-foot {
  margin-top: var(--space-10);
  text-align: center;
  font-size: var(--fs-sm);
  color: var(--color-muted);
}
.day-foot__signout {
  border: 0;
  background: none;
  padding: 0.5rem;
  min-height: 44px;
  font: inherit;
  font-weight: 700;
  color: var(--color-primary);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

/* --- the job document ----------------------------------------------------- */
.job-card { margin-bottom: var(--space-6); }
.job-card > .rail { padding-top: var(--space-5); }

.job-next {
  margin: var(--space-4) 0 0;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-primary-light);
  color: var(--color-primary-dark);
  font-size: var(--fs-base);
  font-weight: 700;
  line-height: 1.4;
}
/* Once the job is closed the instruction is a statement of fact, so it stops
   asking for attention. */
.job-card[data-stage="done"] .job-next {
  background: var(--color-success-bg);
  color: var(--color-success-text);
}

/* --- stage panels --------------------------------------------------------
   Emphasis, never concealment. A disclosure that hides the lockbox code the
   moment you check in is a tool that fails in the exact situation it exists
   for: standing at a door, no signal, one hand free. */
.stage { margin-top: var(--space-6); }
.stage:last-of-type { padding-bottom: var(--space-4); }
.stage__title {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: 0 0 var(--space-3);
  font-size: var(--fs-sm);
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-muted);
}
.stage__num {
  display: grid;
  place-items: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 50%;
  background: var(--color-off-white);
  color: var(--color-muted);
  font-size: var(--fs-xs);
  font-weight: 800;
  flex: 0 0 auto;
}
.stage--current .stage__title { color: var(--color-primary-dark); }
.stage--current .stage__num { background: var(--color-accent); color: var(--color-navy-950); }
.stage--past .stage__num { background: var(--color-primary-light); color: var(--color-primary); }
/* Past and future stages step back. They are still fully readable — this is
   opacity on the HEADING only, never on the content. */
.stage--future .stage__title { opacity: 0.7; }
.stage__sub {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-5) 0 var(--space-2);
  font-size: var(--fs-base);
  font-weight: 700;
}

/* --- the dock ------------------------------------------------------------- */
.job-dock {
  position: sticky;
  bottom: 0;
  z-index: 20;
  margin-top: var(--space-6);
  padding: var(--space-3) var(--space-5) var(--space-4);
  background: var(--color-white);
  border-radius: 0 0 var(--radius-xl) var(--radius-xl);
  box-shadow: 0 -1px 0 var(--color-rule), 0 -8px 24px rgba(16, 39, 71, 0.08);
}
.job-dock__row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.job-dock__meta {
  font-size: var(--fs-sm);
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* One live control, thumb-sized, in the same place at every stage. */
.job-dock__action {
  flex: 1 1 auto;
  min-height: 3.5rem;
  padding: 0 var(--space-4);
  border: 0;
  border-radius: var(--radius-md);
  background: var(--color-primary);
  color: var(--color-white);
  font: inherit;
  font-size: var(--fs-md);
  font-weight: 800;
  letter-spacing: 0.01em;
  cursor: pointer;
}
.job-dock__action:hover { background: var(--color-primary-dark); }
.job-dock__action:disabled {
  background: var(--color-disabled-bg);
  color: var(--color-disabled-text);
  cursor: not-allowed;
}
.job-dock__done {
  flex: 1 1 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 3.5rem;
  border-radius: var(--radius-md);
  background: var(--color-success-bg);
  color: var(--color-success-text);
  font-weight: 800;
}
/* Why the terminal action will not press, said before it is pressed —
   "why won't this button work" standing in a customer's kitchen is a phone
   call to the office. */
.job-dock__why {
  margin: var(--space-2) 0 0;
  font-size: var(--fs-sm);
  color: var(--color-warning-text);
  background: var(--color-warning-bg);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
}
.job-dock__secondary {
  width: 100%;
  min-height: 2.75rem;
  margin-top: var(--space-2);
  border: 1px solid var(--color-primary);
  border-radius: var(--radius-md);
  background: var(--color-white);
  color: var(--color-primary);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}
.job-dock__secondary:hover { background: var(--color-primary-light); }
.job-dock .time-clock-feedback:empty,
.job-dock .checklist-feedback:empty { display: none; }

/* Sticky chrome hides whatever it is scrolled on top of, and `scroll-padding`
   is the mechanism that stops it: anchor jumps, keyboard focus and
   scrollIntoView all come to rest clear of the header above and the dock
   below, rather than parking a control underneath one of them.
   `scroll-padding` only has an effect on the SCROLLPORT, which is why
   `data-density` lives on <html> rather than <body>. */
/* 17rem, not 9.5: the dock grows after check-in. Its row is 91px, the "why"
   note it gains is 109px and the clock feedback line another 23px, so it
   measures 267px exactly when a cleaner is tapping checklist rows — and 9.5rem
   reserved 152px, leaving 115px in which a tapped row could come to rest
   underneath it. Over-reserving when the dock is short only lands a jump a
   little high, which nobody notices; under-reserving hides the control being
   aimed at, which is the whole failure this property exists to prevent. */
html[data-density="instrument"] { scroll-padding: 5rem 0 17rem; }
html[data-density="hospitality"] { scroll-padding: 6rem 0 2rem; }
html[data-density="cockpit"] { scroll-padding: 8rem 0 2rem; }

/* The customer shell has a fixed bottom bar too, and the rule above did not
   know it. Below 52rem the five portal destinations move out of the top bar
   into `.app-tabbar` (public/css/customer.css), which is `position: fixed;
   bottom: 0` at `--app-tabbar-h` plus the safe-area inset. `body.portal-shell`
   already pads the DOCUMENT by that much, but padding decides where the page
   ENDS, not where a scroll STOPS — so a 2rem reserve brought a focused control
   to rest 28px into a 60px bar. Measured on /customer/property at 390x844:
   "Pause this visit" (44px tall) settled at y=768..812 with the bar's top edge
   at 784, putting its lower two thirds and its whole centre — where a pointer
   lands and where a focus ring is read — underneath an opaque bar.
   The token is scoped to `[data-page="customer-dashboard"]` on <body> and so
   cannot be read from the scrollport; the literal matches it, and the `var()`
   picks the real value up if it is ever hoisted. Reserving the bar plus a
   thumb's margin, on the same reasoning the dock comment above gives: landing
   a little high is invisible, landing underneath is the failure. */
@media (max-width: 51.99rem) {
  html[data-density="hospitality"] {
    scroll-padding-bottom: calc(
      var(--app-tabbar-h, 3.75rem) + env(safe-area-inset-bottom, 0px) + var(--space-3)
    );
  }
}

/* --- camera-first proof --------------------------------------------------- */
.proof-shots {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.shot {
  position: relative;
  display: grid;
  justify-items: center;
  gap: var(--space-1);
  padding: var(--space-4) var(--space-2);
  min-height: 6.5rem;
  border-radius: var(--radius-lg);
  background: var(--color-off-white);
  color: var(--color-primary-dark);
  cursor: pointer;
  text-align: center;
}
.shot:hover { background: var(--color-primary-light); }
/* The file input is the control; the tile is its label. Hiding it with
   `display: none` would take it out of the tab order, so it stays focusable
   and the tile shows the focus ring on its behalf. */
.shot input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.shot:focus-within { outline: 3px solid var(--color-accent); outline-offset: 2px; }
.shot__icon { color: var(--color-primary); line-height: 0; }
.shot--incident .shot__icon { color: var(--color-warning-text); }
.shot__label { font-weight: 800; font-size: var(--fs-sm); }
.shot__count { font-size: var(--fs-xs); color: var(--color-muted); font-variant-numeric: tabular-nums; }

.proof-fallback { margin-bottom: var(--space-3); }
.proof-fallback > summary {
  cursor: pointer;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--color-primary);
  min-height: 44px;
  display: flex;
  align-items: center;
}

.hygiene-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 44px;
  font-size: var(--fs-base);
}
.hygiene-row input[type="checkbox"] { width: 1.5rem; height: 1.5rem; flex: 0 0 auto; }

/* --- incident reporting ---------------------------------------------------
   The route existed and nothing called it. Filing damage should not be a
   phone call. */
.incident-form {
  margin-top: var(--space-5);
  padding: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-off-white);
}
.incident-form > summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  cursor: pointer;
  font-weight: 700;
  color: var(--color-primary-dark);
  min-height: 44px;
}
.incident-form__field {
  display: grid;
  gap: var(--space-1);
  margin-top: var(--space-3);
  font-size: var(--fs-sm);
  font-weight: 600;
}
.incident-form__field select,
.incident-form__field textarea {
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font: inherit;
  font-weight: 400;
  /* 16px on the phone or iOS zooms the whole page on focus. */
  font-size: max(16px, var(--fs-base));
}
.btn-incident {
  margin-top: var(--space-3);
  min-height: 44px;
  padding: 0 var(--space-4);
  border: 1px solid var(--color-primary);
  border-radius: var(--radius-md);
  background: var(--color-white);
  color: var(--color-primary);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}
.btn-incident:hover { background: var(--color-primary-light); }
.incident-feedback { margin-top: var(--space-2); font-size: var(--fs-sm); }

@media (max-width: 30rem) {
  .proof-shots { gap: var(--space-2); }
  .shot { padding: var(--space-3) var(--space-1); min-height: 5.75rem; }
  .job-dock__row { flex-wrap: wrap; }
  .job-dock__meta { width: 100%; }
  .job-dock__action { width: 100%; }
}

/* 23. Customer home --------------------------------------------------------
   Four layers, in a fixed order, each answering one question:

     NOW      when is someone coming, and what does it need from me
     CLEANS   what has been done here
     PROPERTY what do you know about my home
     RECORDS  where is the paperwork

   The nine-card dashboard this replaces had no order at all — it was a CSS
   multicolumn, so the browser packed cards into whatever hole was nearest and
   the answer to the first question could land in the third column under
   "Account security". */

.layer { margin-bottom: var(--space-16); }
.layer:last-child { margin-bottom: var(--space-8); }
.layer__title {
  margin: 0 0 var(--space-5);
  font-size: var(--fs-xl);
  letter-spacing: -0.015em;
  color: var(--color-primary-dark);
}
/* The first layer has no heading. It IS the answer; a label above it would be
   a caption on a photograph of one thing. */
.layer--now { margin-bottom: var(--space-12); }

.records-empty {
  margin: 0;
  color: var(--color-muted);
  line-height: 1.55;
}
.estimate-review-note {
  margin: var(--space-2) 0;
  font-size: var(--fs-sm);
  color: var(--color-muted);
  line-height: 1.5;
}
.property-cleans-link {
  display: inline-block;
  margin: var(--space-2) 0;
  font-size: var(--fs-sm);
}



/* --- the clean record ----------------------------------------------------
   The artifact /gallery promises: who was here, when they arrived and left,
   what was worked, what was skipped and why, what hygiene was attested, what
   was photographed, and the signature it was all performed under. */
.clean-record { margin-top: var(--space-4); }
.crecord {
  border-radius: var(--radius-lg);
  background: var(--color-cream);
  padding: var(--space-5);
}
.crecord__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-4);
}
.crecord__title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  font-size: var(--fs-md);
  color: var(--color-primary-dark);
}
.crecord__ref {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}
.crecord__block { margin-top: var(--space-5); }
.crecord__block:first-of-type { margin-top: 0; }
.crecord__block h5 {
  margin: 0 0 var(--space-2);
  font-size: var(--fs-xs);
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-muted);
}
.crecord__list,
.crecord__checks,
.crecord__hygiene { list-style: none; margin: 0; padding: 0; }

.crecord__stamp {
  display: grid;
  gap: 0.15rem;
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-rule);
}
.crecord__stamp:last-child { border-bottom: 0; }
.crecord__who { display: flex; align-items: center; gap: var(--space-2); font-weight: 700; }
.crecord__times { font-size: var(--fs-sm); color: var(--color-muted); font-variant-numeric: tabular-nums; }
.crecord__stamp .badge { justify-self: start; }

.crecord__checks li {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-rule);
}
.crecord__checks li:last-child { border-bottom: 0; }
.crecord__check-label { flex: 1 1 12rem; }
.crecord__resolution-summary {
  display: flex;
  gap: var(--space-2) var(--space-4);
  flex-wrap: wrap;
  margin: 0 0 var(--space-3);
  color: var(--color-muted);
  font-size: var(--fs-sm);
}
.crecord__resolution-summary span + span::before { content: '·'; margin-right: var(--space-4); }
.crecord__categories { display: grid; gap: var(--space-4); }
.crecord__category h6 {
  margin: 0;
  padding-bottom: var(--space-1);
  border-bottom: 1px solid var(--color-rule);
  color: var(--color-primary-dark);
  font-size: var(--fs-sm);
}
.crecord__check-note { flex-basis: 100%; padding-left: 0; }

.crecord__hygiene {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(12rem, 100%), 1fr));
  gap: var(--space-2);
}
.crecord__hygiene li { display: flex; align-items: center; gap: var(--space-2); font-size: var(--fs-sm); }
.crecord__hygiene .is-yes,
.crecord__hygiene li.is-yes { color: var(--color-success-text); }
.crecord__hygiene li.is-no { color: var(--color-danger-text); }

.crecord__photos { margin: 0 0 var(--space-2); font-weight: 600; }
.crecord__block--money .crecord__total {
  margin: 0;
  font-size: var(--fs-xl);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--color-primary-dark);
}
.crecord__block--money .meta { font-weight: 400; font-size: var(--fs-sm); margin-left: var(--space-2); }
.crecord__foot {
  margin: var(--space-5) 0 0;
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-rule);
  font-size: var(--fs-sm);
  color: var(--color-muted);
}
.crecord__scope-proof { display: block; margin-top: var(--space-1); overflow-wrap: anywhere; }

/* 24. Signature interactions ----------------------------------------------
   Four behaviours, then stop. Each is built out of something the operation
   actually produces — a service's progress, a job's completion, a crew's
   before-and-after — rather than decoration applied on top of it. All four
   degrade to a static, complete state under prefers-reduced-motion, which the
   rest of the stylesheet already respects in 21 other places. */

/* --- 1. The proof reveal --------------------------------------------------
   The customer portal's signature piece, and the only one of the four that
   changes what someone can DO: two frames of the same room, and a control that
   moves between them, instead of one grid of "before" and another of "after"
   with the first held in your head while you scroll to the second.

   The control is a real range input. It drags with a mouse, a finger and the
   arrow keys, it announces itself, and it needs no pointer handling of ours to
   manage any of that. Both photos also remain in the grids below — the reveal
   is never the only way to see a frame. */
.proof {
  margin: 0 0 var(--space-5);
}
.proof__stage {
  --proof: 50%;
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--color-navy-950);
  line-height: 0;
  /* The after frame normally sets the height. If it fails to load — an expired
     signed URL, a blocked request — `height: auto` collapses the stage to a
     few pixels and the control becomes untouchable, so the shape is held
     independently of whether the photograph arrives. */
  aspect-ratio: 4 / 3;
  min-height: 12rem;
}
.proof__img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* The before frame is clipped to the seam; the after frame is the ground it
   sits on, so the two are always exactly registered. */
.proof__clip {
  position: absolute;
  inset: 0;
  width: var(--proof);
  overflow: hidden;
}
.proof__clip .proof__img {
  /* Pinned to the stage width so clipping the container does not squeeze the
     photograph — the whole point is that the two frames line up. */
  position: absolute;
  inset: 0;
  width: auto;
  min-width: 100%;
  height: 100%;
}
.proof__seam {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--proof);
  width: 2px;
  margin-left: -1px;
  background: var(--color-accent);
  box-shadow: 0 0 0 1px rgba(8, 20, 38, 0.35);
  pointer-events: none;
}
.proof__seam::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 2.25rem;
  height: 2.25rem;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: var(--color-accent);
  box-shadow: 0 2px 10px rgba(8, 20, 38, 0.45);
}
.proof__tag {
  position: absolute;
  top: var(--space-3);
  padding: 0.2rem 0.6rem;
  border-radius: var(--radius-pill);
  background: rgba(8, 20, 38, 0.72);
  color: var(--color-white);
  font-size: var(--fs-xs);
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1.6;
  pointer-events: none;
}
.proof__tag--before { left: var(--space-3); }
.proof__tag--after { right: var(--space-3); }
/* The input covers the stage and is invisible; the seam is its thumb. */
.proof__range {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: ew-resize;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
}
.proof__range:focus-visible ~ .proof__seam::after {
  outline: 3px solid var(--color-white);
  outline-offset: 2px;
}
.proof__caption {
  margin-top: var(--space-2);
  font-size: var(--fs-sm);
  color: var(--color-muted);
}

/* --- 2. The completion sweep ---------------------------------------------
   A record that has just been filed announces itself once, with a precise
   gold sweep that resolves into the completed state — not confetti, not a
   toast that has to be dismissed. It runs on the element ONCE, keyed by a
   class the renderer adds when a record moves into a completed state, so a
   list of forty past cleans does not shimmer on every load. */
@media (prefers-reduced-motion: no-preference) {
  @keyframes completion-sweep {
    from { transform: translateX(-120%); }
    to { transform: translateX(120%); }
  }
  /* `clip-path`, not `overflow: hidden`. Overflow on an ancestor creates a
     scroll container, which silently disables `position: sticky` on anything
     inside it — and the element this sweep runs on in the field portal IS the
     job card, whose action dock is sticky. Clip-path confines the sweep to the
     card's rounded box without creating a scrollport. */
  .is-completing { position: relative; clip-path: inset(0 round var(--radius-xl)); }
  .is-completing::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 45%;
    pointer-events: none;
    background: linear-gradient(
      100deg,
      transparent,
      var(--color-accent-soft) 45%,
      rgba(255, 255, 255, 0.55) 55%,
      transparent
    );
    animation: completion-sweep 900ms var(--ease-out) 1;
  }
}

/* --- 3. The contour resolves ---------------------------------------------
   The terrain behind a progress rail is not wallpaper: it draws itself in as
   the service proceeds, so the rail's landform is the progress. Stroke-dash
   animation on the ridge paths, once, on first paint — the finished state is
   simply the fully-drawn line, so nothing depends on the animation running. */
@media (prefers-reduced-motion: no-preference) {
  @keyframes contour-draw {
    from { stroke-dashoffset: 220; }
    to { stroke-dashoffset: 0; }
  }
  .rail__terrain .contour-band path {
    stroke-dasharray: 220;
    animation: contour-draw var(--dur-reveal) var(--ease-out) 1 both;
  }
  /* The three lines resolve in sequence, near-to-far, the way a ridge comes
     out of haze. */
  .rail__terrain .contour-band path:nth-child(2) { animation-delay: 90ms; }
  .rail__terrain .contour-band path:nth-child(3) { animation-delay: 180ms; }

  /* A reached station settles into place rather than appearing already there. */
  @keyframes rail-arrive {
    from { transform: translateX(-50%) scale(0.6); }
    to { transform: translateX(-50%) scale(1); }
  }
  .rail__step--current .rail__dot { animation: rail-arrive var(--dur-med) var(--ease-spring) 1 both; }
}

/* --- 4. State morphing ---------------------------------------------------
   The field dock's action does not swap out and swap in; it changes what it
   says while staying the same object in the same place. That continuity is
   what makes the screen read as one instrument moving through a job rather
   than a sequence of different screens.

   `view-transition-name` is progressive: where the browser supports it the
   label cross-fades within the button, and where it does not the button simply
   changes text, which is the current behaviour and is fine. */
.job-dock__action { transition: background var(--dur-quick) var(--ease-out); }
@media (prefers-reduced-motion: no-preference) {
  @keyframes dock-morph {
    from { opacity: 0; transform: translateY(4px); }
    to { opacity: 1; transform: none; }
  }
  .job-card[data-stage] .job-dock__action,
  .job-card[data-stage] .job-dock__done { animation: dock-morph var(--dur-med) var(--ease-out) 1 both; }
  /* The dock's progress figure moves with it. */
  .job-dock__meta { transition: color var(--dur-quick) var(--ease-out); }
}

/* ---------------------------------------------------------------------------
   25. The owners (about.html.raw)
   ---------------------------------------------------------------------------
   The one section on the site that is allowed to be about people rather than
   process, so it deliberately does not reuse the feature-card grid. No card
   border, no shadow, no chrome: two portraits at real size on the page ground,
   with the name, the role, and the paragraph directly under each.

   Both portraits share one ridgeline. The pair is a two-row grid and each
   owner is a subgrid, so the portrait row and the body row are the same rows
   for both people no matter how long the two paragraphs run. That is what lets
   the ridge sit exactly on the bottom edge of both portraits at once, which is
   the whole point: one horizon, two people standing on it, rather than two
   entries in a staff directory.

   Below 900px the two stack, there is no shared horizon to draw, and the ridge
   is removed rather than left floating under the first portrait.
   --------------------------------------------------------------------------- */

.owners__intro {
  max-width: 62ch;
  margin-bottom: var(--space-10);
}
.owners__intro h2 { margin-bottom: var(--space-4); }
.owners__intro p { margin-bottom: var(--space-4); }
.owners__intro p:last-child { margin-bottom: 0; }

.owners__pair {
  display: grid;
  gap: var(--space-10);
}

/* Hidden until there is a side-by-side geometry for it to describe. */
.owners__ridge { display: none; }

.owner {
  display: grid;
  gap: var(--space-5);
  align-content: start;
}

.owner__portrait {
  margin: 0;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--color-cream-deep);
}
.owner__portrait img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
}

.owner__name {
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  line-height: 1.15;
  margin: 0 0 var(--space-1);
  color: var(--color-text);
}
.owner__role {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-accent-text);
  margin: 0 0 var(--space-4);
}
.owner__bio {
  margin: 0;
  color: var(--color-muted);
}

@media (min-width: 900px) {
  /* minmax(0, 1fr), not 1fr: a plain 1fr is minmax(auto, 1fr), so the widest
     unbreakable thing in either column can push that column past its half and
     the two portraits stop being the same size. Equal weight here is a
     requirement, not a preference. */
  .owners__pair {
    position: relative;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-rows: auto auto;
    column-gap: var(--space-10);
    row-gap: 0;
  }
  /* Columns are placed explicitly, not left to auto-placement. The ridge
     takes row 1 across both columns, and an auto-placed item with a definite
     row goes to the first FREE column in that row band, so the two owners were
     landing in implicit columns 3 and 4 while the explicit 1fr columns
     collapsed to zero. That is what left the ridge 40px wide: it was spanning
     two empty columns and a gap. */
  .owner {
    grid-row: 1 / -1;
    grid-template-rows: subgrid;
    row-gap: var(--space-5);
    min-width: 0;
  }
  .owner:nth-of-type(1) { grid-column: 1; }
  .owner:nth-of-type(2) { grid-column: 2; }
  /* Without subgrid the two owners are still laid out correctly; only the
     shared horizon needs the rows to line up, so it stays hidden. */
  @supports (grid-template-rows: subgrid) {
    .owners__ridge {
      display: block;
      grid-column: 1 / -1;
      grid-row: 1;
      align-self: end;
      justify-self: stretch;
      /* The grid item is a plain div, and the SVG fills it. An SVG placed
         directly in the grid brings its own intrinsic sizing to the
         negotiation (a viewBox with no width/height attributes defaults to
         300x150), which sized the columns and left the ridge 40px wide no
         matter what `width` said. A div has no intrinsic size to argue with. */
      min-width: 0;
      width: 100%;
      height: 26px;
      /* Pulled down so the line crosses the bottom edge of both portraits
         rather than floating in the gap beneath them. */
      margin-bottom: -13px;
      color: var(--color-accent);
      pointer-events: none;
      z-index: 1;
    }
    .owners__ridge svg { display: block; width: 100%; height: 100%; }
  }
}

/* Same native date-control sizing gap as the Admin payment form: a 21px hit
   area in a field portal, on a phone, in somebody's driveway. */
.cash-tip-report-form input[type="date"],
.gratuity-form input[type="date"] {
  min-height: 2.75rem;
  padding: 0 var(--space-3);
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-md);
  background: var(--color-white);
  color: var(--color-text);
  font: inherit;
  font-size: max(16px, var(--fs-base));
}

/* A second ask, folded. Somebody who already tipped sees one quiet line
   instead of the full solicitation repeated under their own tip. */
.gratuity-again { margin-top: var(--space-3); }
.gratuity-again > summary {
  display: flex;
  align-items: center;
  min-height: 2.75rem;
  color: var(--color-primary);
  font-size: var(--fs-sm);
  cursor: pointer;
}
.gratuity-form--additional { margin-top: var(--space-2); }

/* 24b. Charts --------------------------------------------------------------

   The primitives public/js/lib/charts.js emits. They lived in admin.css while
   the admin was the only caller; the employee's Hours & Pay draws the same
   donut, and a second copy of the ring in a second stylesheet is how one
   surface ends up with a differently-coloured "travel" slice than the other.

   Three rules hold, and they are the reason this is CSS rather than markup.
   Colour comes from classes, never from an attribute in the generated markup,
   so a chart follows the theme instead of pinning hex values into JavaScript.
   Geometry rides in `data-pct` and is applied through the CSSOM, because the
   CSP carries no 'unsafe-inline' for style-src and a style attribute in the
   markup is simply refused. And every chart is decorative to assistive tech:
   the SVG is aria-hidden and a visually-hidden table beside it carries the
   same numbers, which is navigable in a way a thirty-number aria-label is
   not. Layout that is specific to one screen -- the admin's funnel and
   histogram, the employee's donut row -- stays with that screen. */

.chart { margin: 0; }
.chart__svg { display: block; width: 100%; height: auto; }
.chart__empty {
  margin: 0;
  padding: var(--space-4);
  border: 1px dashed var(--color-rule-strong);
  border-radius: var(--radius-md);
  color: var(--color-muted);
  font-size: var(--fs-sm);
}

.chart--donut .chart__svg { width: 8.75rem; }
.donut__track { stroke: var(--color-rule); }
.donut__arc { transform: rotate(-90deg); transform-origin: 70px 70px; }
.donut__arc--success { stroke: var(--color-success); }
.donut__arc--primary { stroke: var(--color-primary); }
.donut__arc--warning { stroke: var(--color-warning); }
.donut__arc--muted { stroke: var(--color-muted); }
.donut__arc--neutral { stroke: color-mix(in srgb, var(--color-muted) 30%, var(--color-white)); }
.donut__value { fill: var(--color-primary-dark); font-size: 26px; font-weight: 800; font-family: var(--font-sans); }
/* Anything past four characters at 26px overruns the 76-unit hole. */
.donut__value--long { font-size: 18px; letter-spacing: -0.02em; }
.donut__label { fill: var(--color-muted); font-size: 11px; font-family: var(--font-sans); }
@media (prefers-reduced-motion: no-preference) {
  .donut__arcs { animation: chart-fade 500ms 150ms both; }
  @keyframes chart-fade { from { opacity: 0; } to { opacity: 1; } }
}

.chart-legend { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-1); }
.chart-legend li { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: var(--space-2); align-items: center; font-size: var(--fs-sm); }
.chart-legend b { color: var(--color-primary-dark); font-variant-numeric: tabular-nums; }
.chart-legend__key { width: .7rem; height: .7rem; border-radius: 3px; background: var(--color-border); }
.chart-legend__key--success { background: var(--color-success); }
.chart-legend__key--primary { background: var(--color-primary); }
.chart-legend__key--warning { background: var(--color-warning); }
.chart-legend__key--muted { background: var(--color-muted); }
.chart-legend__key--neutral { background: color-mix(in srgb, var(--color-muted) 30%, var(--color-white)); }

/* 25. Availability calendar ------------------------------------------------
   One component, three surfaces: the quote wizard's preferred start, the
   post-quote booking CTA, and the customer portal's reserve form. Everything
   it draws comes from /api/estimates/availability, so the styling problem is
   only ever "make the server's answer legible", never "imply an opening".

   Two states have to be visually distinct at a glance, because confusing them
   is how a customer books a time we cannot work: a day with times, and a day
   without. Open days carry weight and a mark; closed days recede but stay
   readable and stay focusable, so a keyboard user can walk the whole month
   and hear why each closed day is closed. */

.availability-host { display: block; }
.availability-host:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 4px; }

.availability {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-white);
  max-width: 30rem;
}
/* A reload dims the grid rather than replacing it, so the month does not
   flash away underneath the customer's cursor. */
.availability[data-availability-state="loading"] .availability__grid { opacity: 0.45; }

.availability__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}
.availability__month {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-md);
  color: var(--color-text);
}
.availability__nav {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.75rem;
  min-height: 2.75rem;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-white);
  color: var(--color-primary);
  font-size: var(--fs-lg);
  line-height: 1;
  cursor: pointer;
}
.availability__nav:hover:not(:disabled) { background: var(--color-primary-light); }
.availability__nav:disabled {
  color: var(--color-disabled-text);
  background: var(--color-disabled-bg);
  border-color: var(--color-disabled-bg);
  cursor: default;
}

.availability__grid {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
}
.availability__grid th {
  padding: 0 0 var(--space-1);
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--color-muted);
  text-align: center;
}
.availability__grid th abbr { text-decoration: none; border: 0; }
.availability__cell, .availability__pad { padding: 1px; text-align: center; }

.availability__day {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 2.75rem;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-muted);
  font: inherit;
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  cursor: default;
}
/* The three states a customer must never confuse: blue means bookable, navy
   means chosen, grey means not on offer. The legend under the grid names
   them; each cell's aria-label carries the exact reason. */
.availability__day.is-open {
  color: var(--color-primary-dark);
  font-weight: 700;
  background: var(--color-primary-light);
  border-color: var(--color-info-border);
  cursor: pointer;
  transition: background var(--dur-quick) var(--ease-out), border-color var(--dur-quick) var(--ease-out), transform var(--dur-quick) var(--ease-out);
}
.availability__day.is-open::after {
  content: "";
  width: 4px;
  height: 4px;
  margin-top: 2px;
  border-radius: var(--radius-pill);
  background: var(--color-primary);
}
@media (prefers-reduced-motion: no-preference) {
  .availability__day.is-open:hover { transform: translateY(-1px); }
}
.availability__day.is-open:hover { background: var(--color-white); border-color: var(--color-primary); }
.availability__day.is-selected {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-white);
}
.availability__day.is-selected::after { background: var(--color-accent-light); }
.availability__day:focus-visible { outline: 3px solid var(--color-primary); outline-offset: 1px; }
/* Unavailable days stay readable and focusable, but their marks differ by
   reason: a bar for "closed" (a business statement), a hollow ring for
   "fully booked" (was on offer, taken), and nothing at all for days that are
   simply too soon or already gone, which also sit quieter. */
.availability__day[data-unavailable="closed"] { color: var(--color-text); background: var(--color-off-white); }
.availability__day[data-unavailable="closed"]::after {
  content: "";
  width: 9px;
  height: 2px;
  margin-top: 3px;
  border-radius: 1px;
  background: var(--color-muted);
}
.availability__day[data-unavailable="full"] { background: var(--color-off-white); }
.availability__day[data-unavailable="full"]::after {
  content: "";
  width: 5px;
  height: 5px;
  margin-top: 2px;
  border-radius: var(--radius-pill);
  border: 1.5px solid var(--color-muted);
  box-sizing: border-box;
}
.availability__day[data-unavailable="past"],
.availability__day.is-void { opacity: 0.5; }

/* Legend: names for the tile colours. Decorative (each cell announces its
   own state), so it is aria-hidden and stays compact. */
.availability__legend {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  margin: 0;
  padding: 0;
  font-size: var(--fs-xs);
  color: var(--color-muted);
}
.availability__legend li { display: inline-flex; align-items: center; gap: 6px; }
.availability__swatch {
  width: 12px;
  height: 12px;
  border-radius: 3px;
  border: 1px solid var(--color-border);
  background: var(--color-off-white);
}
.availability__swatch--open { background: var(--color-primary-light); border-color: var(--color-info-border); }
.availability__swatch--selected { background: var(--color-primary); border-color: var(--color-primary); }

/* Skeleton: the quiet placeholder a month grid loads into, so the step does not
   jump when the real month lands.

   Named for the grid rather than for this calendar because there are two of
   them. The employee schedule shares month-grid.js and was loading into flat
   grey rectangles purely because the shimmer had been written against
   `.availability__skel`; both now emit `.month-skel` (month-grid.js
   SKELETON_CLASS) and the treatment is defined once, here. */
.month-skel {
  display: inline-block;
  border-radius: var(--radius-sm);
  background: var(--color-off-white);
}
@media (prefers-reduced-motion: no-preference) {
  .month-skel {
    background: linear-gradient(100deg, var(--color-off-white) 40%, var(--color-primary-light) 50%, var(--color-off-white) 60%);
    background-size: 200% 100%;
    animation: month-skel-shimmer 1.4s ease-in-out infinite;
  }
  @keyframes month-skel-shimmer {
    from { background-position: 120% 0; }
    to { background-position: -80% 0; }
  }
}
.availability__skel--nav { width: 2.75rem; height: 2.75rem; }
.availability__skel--month { width: 9rem; height: 1.25rem; border-radius: var(--radius-pill); }
.availability__skel--day { width: 100%; height: 2.6rem; }
.availability__skel--foot { width: 70%; height: 0.9rem; border-radius: var(--radius-pill); }
.availability__grid--skeleton td { padding: 1px; }

/* A real month change slides in; repaints of the same month do not. */
@media (prefers-reduced-motion: no-preference) {
  .availability__body[data-month-enter] { animation: availability-month-in 200ms var(--ease-out) both; }
  @keyframes availability-month-in {
    from { opacity: 0; transform: translateX(6px); }
    to { opacity: 1; transform: none; }
  }
}

/* The way out of an empty month: where the next opening is, one press away. */
.availability__next-opening { display: grid; gap: var(--space-2); justify-items: start; }
.availability__jump {
  min-height: 2.75rem;
  padding: 0 var(--space-4);
  border: 1px solid var(--color-primary);
  border-radius: var(--radius-pill);
  background: var(--color-white);
  color: var(--color-primary);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 700;
  cursor: pointer;
}
.availability__jump:hover { background: var(--color-primary-light); }
.availability__jump:focus-visible { outline: 3px solid var(--color-primary); outline-offset: 2px; }
.availability__closures {
  list-style: none;
  margin: var(--space-2) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-1);
  font-size: var(--fs-xs);
  color: var(--color-muted);
}
.availability__closures strong { color: var(--color-text); font-weight: 700; }

.availability__slots { border-top: 1px solid var(--color-rule); padding-top: var(--space-3); }
.availability__slots-heading {
  margin: 0 0 var(--space-2);
  font-weight: 700;
  font-size: var(--fs-sm);
  color: var(--color-text);
}
.availability__hint { margin: 0; font-size: var(--fs-sm); color: var(--color-muted); }
.availability__slot-row { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.availability__slot {
  min-height: 2.75rem;
  padding: 0 var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background: var(--color-white);
  color: var(--color-text);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.availability__slot:hover { border-color: var(--color-primary); background: var(--color-primary-light); }
.availability__slot.is-selected {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-white);
}
.availability__slot:focus-visible { outline: 3px solid var(--color-primary); outline-offset: 2px; }

/* When the answer was true, and how to ask again. Availability is a snapshot
   the moment it is drawn; stamping it is the difference between a calendar
   that is out of date and one that lies. */
.availability__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  border-top: 1px solid var(--color-rule);
  padding-top: var(--space-2);
}
.availability__zone { margin: 0; font-size: var(--fs-xs); color: var(--color-muted); }
.availability__refresh {
  min-height: 2.75rem;
  padding: 0 var(--space-2);
  border: 0;
  background: none;
  color: var(--color-primary);
  font: inherit;
  font-size: var(--fs-xs);
  font-weight: 700;
  text-decoration: underline;
  cursor: pointer;
}

.availability__banner {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--color-off-white);
  font-size: var(--fs-sm);
  color: var(--color-text);
}
.availability__banner--alert {
  background: var(--color-warning-bg);
  border: 1px solid var(--color-warning-border);
  color: var(--color-warning-text);
}

/* An outage and a business block are different messages and must never be
   rendered as "no times available", which would be a claim about the crew's
   calendar that we cannot make when the lookup failed. */
.availability--notice {
  gap: var(--space-2);
  background: var(--color-off-white);
}
.availability__notice-title { margin: 0; font-weight: 700; color: var(--color-text); }
.availability__notice-body { margin: 0; font-size: var(--fs-sm); color: var(--color-muted); }
.availability--notice .btn { justify-self: start; }

/* The card under the calendar that repeats the choice back, so the customer
   never has to re-read the grid to know what they picked, and never mistakes
   a preference for a reservation. */
.availability-choice {
  margin: var(--space-3) 0 0;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-info-border);
  border-left: 4px solid var(--color-primary);
  border-radius: var(--radius-md);
  background: var(--color-primary-light);
  display: grid;
  gap: 2px;
}
.availability-choice:empty, .availability-choice[hidden] { display: none; }
.availability-choice__eyebrow {
  margin: 0;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-primary);
}
.availability-choice__date {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-md);
  color: var(--color-text);
}
.availability-choice__time {
  margin: 0;
  font-weight: 600;
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  color: var(--color-text);
}
.availability-choice__note {
  margin: var(--space-1) 0 0;
  font-size: var(--fs-xs);
  color: var(--color-muted);
}
.availability-choice__rush {
  margin: var(--space-2) 0 0;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-warning-border);
  border-radius: var(--radius-sm);
  background: var(--color-warning-bg);
  font-size: var(--fs-xs);
  color: var(--color-warning-text);
}

/* "That time was just taken." Not an error the customer made: a calm panel
   with the closest still-open starts as one-press buttons. */
.race-recovery {
  margin: var(--space-3) 0 0;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-warning-border);
  border-radius: var(--radius-md);
  background: var(--color-warning-bg);
  display: grid;
  gap: var(--space-2);
}
.race-recovery:focus { outline: none; }
.race-recovery:focus-visible { outline: 3px solid var(--color-primary); outline-offset: 2px; }
.race-recovery__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  color: var(--color-text);
}
.race-recovery__body { margin: 0; font-size: var(--fs-sm); color: var(--color-text); }
.race-recovery__options { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.race-recovery__option {
  min-height: 2.75rem;
  padding: 0 var(--space-4);
  border: 1px solid var(--color-primary);
  border-radius: var(--radius-pill);
  background: var(--color-white);
  color: var(--color-primary);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.race-recovery__option:hover { background: var(--color-primary-light); }
.race-recovery__option:focus-visible { outline: 3px solid var(--color-primary); outline-offset: 2px; }

/* Rush option: one selection card, details on demand, numbers from the
   pricing feed only. */
.rush-section { margin: var(--space-5) 0 0; }
.rush-section .addon-option { max-width: 30rem; }
.rush-details {
  max-width: 30rem;
  margin: var(--space-2) 0 0;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-off-white);
}
@media (prefers-reduced-motion: no-preference) {
  .rush-details[data-panel-enter] { animation: estimate-panel-in 240ms var(--ease-out) both; }
}
.rush-details__list {
  margin: 0;
  padding-left: 1.1rem;
  display: grid;
  gap: var(--space-1);
  font-size: var(--fs-sm);
  color: var(--color-text);
}
.rush-details__hint { margin: var(--space-2) 0 0; }

/* Photos: a secondary, premium dropzone. Helpful, optional, and explicitly
   not a pricing input. */
.photos-section { margin: var(--space-5) 0 0; }
.photo-dropzone {
  max-width: 30rem;
  display: grid;
  justify-items: center;
  gap: var(--space-2);
  padding: var(--space-5) var(--space-4);
  border: 2px dashed var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-off-white);
  text-align: center;
  transition: border-color var(--dur-quick) var(--ease-out), background var(--dur-quick) var(--ease-out);
}
.photo-dropzone.is-dragover {
  border-color: var(--color-primary);
  background: var(--color-primary-light);
}
.photo-dropzone__icon { width: 28px; height: 28px; color: var(--color-muted); }
.photo-dropzone__lead { margin: 0; font-size: var(--fs-sm); color: var(--color-text); font-weight: 600; }
.photo-dropzone__meta { margin: 0; font-size: var(--fs-xs); color: var(--color-muted); }

.photo-tiles {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
  gap: var(--space-2);
  max-width: 30rem;
}
.photo-tile {
  position: relative;
  display: grid;
  gap: 4px;
}
.photo-tile__img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  background: var(--color-off-white);
  display: block;
}
.photo-tile__img--empty { display: block; }
.photo-tile__name {
  font-size: var(--fs-xs);
  color: var(--color-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.photo-tile__remove {
  position: absolute;
  top: 4px;
  right: 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background: var(--color-white);
  color: var(--color-text);
  cursor: pointer;
}
.photo-tile__remove svg { width: 12px; height: 12px; }
.photo-tile__remove:hover { border-color: var(--color-danger); color: var(--color-danger); }
.photo-tile__remove:focus-visible { outline: 3px solid var(--color-primary); outline-offset: 1px; }
/* Fingers get the full 44px floor; the mouse-sized disc stays tidy. */
@media (pointer: coarse) {
  .photo-tile__remove { width: 2.75rem; height: 2.75rem; }
  .photo-tile__remove svg { width: 14px; height: 14px; }
}

/* --- AI photo review preference ---------------------------------------------
   The second decision in the photo block, and a deliberate trust control
   rather than a legal checkbox stapled under the uploader. It gets a rule
   above it and the same 30rem measure as the dropzone, so it reads as a
   sibling of the uploader instead of an afterthought hanging off it.

   Built on the .condition-card geometry (two radios, visually-hidden input,
   styled surface, animated ring) because that is already the wizard's way of
   asking a two-or-more-way question, and reusing it means this control
   inherits its focus ring, its checked treatment and its reduced-motion
   behaviour rather than growing near-copies of all three. The one addition is
   the badge line, which needs the name and the pill on a baseline together. */
.ai-review-section {
  margin: var(--space-5) 0 0;
  padding: var(--space-5) 0 0;
  border-top: 1px solid var(--color-rule);
  max-width: 30rem;
}
.ai-review-section .hint { margin-bottom: var(--space-3); }
.ai-review-choices { display: grid; gap: var(--space-2); }
.ai-review-card { display: block; margin: 0; font-weight: 500; }
.ai-review-card__surface {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  height: 100%;
  /* A hair taller than a condition card: this one carries two lines of
     explanation and a badge, and the extra room is what stops it reading as
     cramped fine print on a phone. */
  padding: var(--space-4);
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  cursor: pointer;
}
@media (prefers-reduced-motion: no-preference) {
  .ai-review-card__surface {
    transition:
      border-color var(--dur-quick) var(--ease-out),
      background-color var(--dur-quick) var(--ease-out),
      box-shadow var(--dur-quick) var(--ease-out);
  }
}
.ai-review-card:hover .ai-review-card__surface {
  border-color: var(--color-primary);
  box-shadow: var(--shadow-xs);
}
.ai-review-card__ring {
  flex-shrink: 0;
  position: relative;
  width: 16px;
  height: 16px;
  margin-top: 3px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--color-border);
  background: var(--color-white);
}
.ai-review-card__ring::after {
  content: "";
  position: absolute;
  inset: 2px;
  border-radius: var(--radius-pill);
  background: var(--color-primary);
  transform: scale(0);
}
@media (prefers-reduced-motion: no-preference) {
  .ai-review-card__ring { transition: border-color var(--dur-quick) var(--ease-out); }
  .ai-review-card__ring::after { transition: transform var(--dur-quick) var(--ease-spring); }
}
/* Selected state carries a border, a ring, a tint AND the filled dot, so the
   choice is never signalled by colour alone. */
.ai-review-card input:checked + .ai-review-card__surface {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 1px var(--color-primary);
  background: linear-gradient(0deg, rgba(232, 240, 250, 0.45), rgba(232, 240, 250, 0.45)), var(--color-white);
}
.ai-review-card input:checked + .ai-review-card__surface .ai-review-card__ring {
  border-color: var(--color-primary);
}
.ai-review-card input:checked + .ai-review-card__surface .ai-review-card__ring::after {
  transform: scale(1);
}
.ai-review-card input:focus-visible + .ai-review-card__surface {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.ai-review-card__text { min-width: 0; display: grid; gap: 2px; }
.ai-review-card__name {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  font-weight: 700;
  font-size: var(--fs-sm);
  color: var(--color-text);
}
.ai-review-card__desc {
  font-size: var(--fs-xs);
  color: var(--color-muted);
  line-height: 1.45;
}
/* On a phone the block is the full column width and each card is a
   comfortable target with its own breathing room; nothing here shrinks
   toward fine print. */
@media (max-width: 30rem) {
  .ai-review-section { max-width: none; }
  .ai-review-card__surface { padding: var(--space-4) var(--space-3); }
}

.field--schedule { margin: var(--space-4) 0; }
.field__label {
  margin: 0 0 var(--space-1);
  font-weight: 600;
  font-size: var(--fs-sm);
  color: var(--color-text);
}
.field--schedule .hint { margin-bottom: var(--space-3); }

/* At 390 the grid is the width of the phone and the slot chips run two per
   row. A GRID, not a stretching flex: with an odd number of times the last
   chip would grow to the full width and read as a different, more important
   control than the ten identical ones above it. Nothing shrinks below the
   44px touch target. */
@media (max-width: 30rem) {
  .availability { padding: var(--space-3); max-width: none; }
  .availability__day { font-size: var(--fs-xs); }
  .availability__slot-row {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .availability__slot { padding: 0 var(--space-2); }
}


/* Permanent prelaunch / maintenance access surface. */
.site-access-shell {
  min-height: 100vh;
  margin: 0;
  color: var(--color-primary-dark);
  background:
    radial-gradient(circle at 12% 10%, rgba(232, 240, 250, 0.95), transparent 32rem),
    radial-gradient(circle at 90% 88%, var(--color-accent-soft), transparent 28rem),
    var(--color-off-white);
}

.site-access-main {
  min-height: 100vh;
  display: grid;
  place-items: center;
  box-sizing: border-box;
  padding: clamp(1.25rem, 5vw, 4rem);
}

.site-access-card {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1.08fr) minmax(19rem, 0.92fr);
  width: min(100%, 66rem);
  overflow: hidden;
  box-sizing: border-box;
  padding: 0;
  border: 1px solid var(--color-rule);
  border-radius: var(--radius-xl);
  background: var(--color-white);
  box-shadow: 0 1.5rem 4rem rgba(16, 39, 71, 0.16);
}

.site-access-card::before {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0.35rem;
  background: var(--color-accent);
  content: '';
}

.site-access-intro {
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: clamp(2rem, 5vw, 4rem);
}

.site-access-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.8rem;
  color: var(--color-primary);
  font-size: clamp(1rem, 3vw, 1.35rem);
  font-weight: 800;
  letter-spacing: 0.04em;
}

.site-access-brand img {
  width: 4.25rem;
  height: 4.25rem;
}

.site-access-brand strong {
  color: var(--color-accent-text);
}

.site-access-eyebrow {
  margin: 0 0 0.65rem;
  color: var(--color-accent-text);
  font-size: 0.82rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.site-access-card h1 {
  max-width: 14ch;
  margin: 0;
  color: var(--color-primary-dark);
  font-size: clamp(2.25rem, 5vw, 4.25rem);
  line-height: 1.03;
  letter-spacing: -0.045em;
}

.site-access-lead {
  max-width: 42rem;
  margin: 1.25rem 0 0;
  color: var(--color-muted);
  font-size: clamp(1.05rem, 2.5vw, 1.25rem);
  line-height: 1.65;
}

.site-access-copy { margin: auto 0; padding: var(--space-12) 0; }

.site-access-standard {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  margin: 0;
  color: var(--color-primary);
  font-size: var(--fs-sm);
  font-weight: 700;
  line-height: 1.5;
}

.site-access-standard span {
  display: grid;
  flex: 0 0 1.75rem;
  width: 1.75rem;
  height: 1.75rem;
  place-items: center;
  border-radius: var(--radius-pill);
  color: var(--color-white);
  background: var(--color-primary);
}

.site-access-panel-wrap {
  display: grid;
  place-items: center;
  padding: clamp(1.5rem, 4vw, 3rem);
  background: linear-gradient(145deg, var(--color-primary-dark), var(--color-primary));
}

.site-access-panel {
  width: 100%;
  padding: clamp(1.15rem, 4vw, 1.75rem);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: var(--radius-lg);
  background: var(--color-white);
  box-shadow: var(--shadow-lg);
}

.site-access-panel-kicker {
  margin: 0 0 var(--space-2) !important;
  color: var(--color-accent-text) !important;
  font-size: var(--fs-xs);
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.site-access-panel h2 {
  margin: 0 0 0.35rem;
  color: var(--color-primary-dark);
  font-size: 1.2rem;
}

.site-access-panel > p {
  margin: 0 0 1rem;
  color: var(--color-muted);
  line-height: 1.55;
}

.site-access-panel label {
  display: block;
  margin-bottom: 0.45rem;
  color: var(--color-primary-dark);
  font-weight: 700;
}

.site-access-password-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.7rem;
}

.site-access-password-row input {
  width: 100%;
  min-height: 48px;
  box-sizing: border-box;
  border: 1px solid var(--color-border);
  border-radius: 0.7rem;
  padding: 0.7rem 0.85rem;
  color: var(--color-primary-dark);
  background: var(--color-white);
  font: inherit;
}

.site-access-password-row .btn {
  min-height: 48px;
  white-space: nowrap;
}

.site-access-password-row input:focus-visible,
.site-access-password-row .btn:focus-visible,
.site-access-legal a:focus-visible {
  outline: 3px solid var(--color-accent);
  outline-offset: 3px;
}

/* Standing notice while the gate controller has not armed the form yet. The
   controller hides it; a page whose module never ran keeps it, so the disabled
   button is explained rather than simply dead. */
.site-access-hint {
  margin: 0.7rem 0 0;
  color: var(--color-muted);
  font-size: 0.92rem;
}

.site-access-result {
  min-height: 1.5em;
  margin: 0.7rem 0 0;
  color: var(--color-muted);
  font-size: 0.92rem;
}

.site-access-result[data-kind='error'] {
  color: var(--color-danger-text);
  font-weight: 700;
}

.site-access-result[data-kind='success'] {
  color: var(--color-success-text);
  font-weight: 700;
}

.site-access-legal {
  grid-column: 1 / -1;
  display: flex;
  justify-content: center;
  gap: 0.65rem;
  margin: 0;
  padding: var(--space-3) var(--space-5);
  color: var(--color-muted);
  background: var(--color-white);
}

.site-access-legal a {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  color: var(--color-primary);
  font-weight: 700;
  text-underline-offset: 0.2em;
}

@media (max-width: 38rem) {
  .site-access-main {
    align-items: start;
    padding: 0.75rem;
  }

  .site-access-card {
    min-height: calc(100vh - 1.5rem);
    border-radius: 1rem;
  }

  .site-access-password-row {
    grid-template-columns: 1fr;
  }

  .site-access-password-row .btn {
    width: 100%;
  }
}

@media (max-width: 52rem) {
  .site-access-card { grid-template-columns: 1fr; }
  .site-access-intro { padding: clamp(1.5rem, 7vw, 2.5rem); }
  .site-access-copy { padding: var(--space-10) 0; }
  .site-access-panel-wrap { padding: clamp(1.25rem, 6vw, 2rem); }
}

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

/* ------------------------------------------------------------------------ */
/* Employee incident readback (CAP-004)                                      */
/* ------------------------------------------------------------------------ */

/* Read-only by design: a filed incident is evidence, and the person who filed
   it is not the authority who amends it. Styled as a record rather than a form
   so nothing here looks editable. */
.incident-history { margin-top: var(--space-3); }
.incident-history__note { color: var(--color-muted); font-size: var(--fs-sm); }
.incident-history__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3); }
.incident-history__item {
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-warning);
  border-radius: var(--radius-md);
}
.incident-history__meta { color: var(--color-muted); font-size: var(--fs-sm); }
.incident-history__outcome { font-size: var(--fs-sm); }

/* Exact-job deep link (?booking=<id>): the purchased cleaning opens first. */
/* --------------------------------------------------------------------------
   Booking-record mode: /customer?booking=<id> — the booking is the page.
   A living service record: quiet, spacious, one identity, ruled sections
   instead of nested rectangles.
   -------------------------------------------------------------------------- */
.layer--booking-record {
  max-width: 46rem;
  margin-inline: auto;
}
.booking-record__back {
  margin: 0 0 var(--space-6);
  font-size: var(--fs-sm);
}
.booking-record__back a {
  color: var(--color-muted);
  text-decoration: none;
}
.booking-record__back a:hover,
.booking-record__back a:focus-visible {
  color: var(--color-text);
  text-decoration: underline;
}
.record-hero { margin: 0 0 var(--space-6); }
.record-hero__status { display: inline-block; margin-bottom: var(--space-3); }
.record-hero__when {
  margin: 0 0 var(--space-1);
  font-size: var(--fs-3xl);
  line-height: 1.12;
  letter-spacing: -0.01em;
  outline: none;
}
.record-hero__what {
  margin: 0 0 var(--space-3);
  font-size: var(--fs-lg);
  font-weight: 600;
}
.record-hero__where {
  margin: 0 0 var(--space-2);
  color: var(--color-muted);
  line-height: 1.5;
}
.record-hero__refs {
  margin: 0;
  font-size: var(--fs-2xs);
  letter-spacing: 0.04em;
  color: var(--color-muted);
}
.record-lifecycle {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4) var(--space-6);
  list-style: none;
  margin: 0;
  padding: var(--space-4) 0;
  border-block: 1px solid var(--color-rule);
}
.record-lifecycle__step {
  display: flex;
  flex-direction: column;
  gap: 2px;
  position: relative;
  padding-left: var(--space-4);
}
.record-lifecycle__step::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.42em;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  border: 2px solid var(--color-rule);
  background: var(--color-white);
}
.record-lifecycle__step--done::before {
  background: var(--color-success);
  border-color: var(--color-success);
}
.record-lifecycle__step--current::before {
  border-color: var(--color-accent);
  background: var(--color-white);
}
.record-lifecycle__label {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-muted);
}
.record-lifecycle__step--done .record-lifecycle__label,
.record-lifecycle__step--current .record-lifecycle__label {
  color: var(--color-text);
}
.record-lifecycle__sub {
  font-size: var(--fs-xs);
  color: var(--color-muted);
}
.record-next {
  margin: var(--space-5) 0 0;
  font-size: var(--fs-md);
  line-height: 1.55;
}
.record-section {
  margin-top: var(--space-8);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-rule);
}
.record-section__title {
  margin: 0 0 var(--space-4);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--color-muted);
}
.record-facts,
.record-money {
  margin: 0;
}
.record-facts__row,
.record-money__row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-2) 0;
}
.record-facts__row dt,
.record-money__row dt {
  color: var(--color-muted);
}
.record-facts__row dd,
.record-money__row dd {
  margin: 0;
  text-align: right;
}
.record-money__row--balance {
  border-top: 1px solid var(--color-rule);
  margin-top: var(--space-2);
  padding-top: var(--space-3);
  font-weight: 700;
}
.record-money__note {
  margin: var(--space-2) 0 0;
  font-size: var(--fs-sm);
  color: var(--color-muted);
}
.record-scope__service {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-2);
  font-weight: 600;
}
.record-scope__addons {
  margin: 0 0 var(--space-3);
  padding-left: var(--space-5);
}
.record-scope__skips h3,
.record-scope__access h3 {
  margin: var(--space-3) 0 var(--space-1);
  font-size: var(--fs-sm);
  font-weight: 700;
}
.record-scope__skips ul {
  margin: 0;
  padding-left: var(--space-5);
}
.record-scope__access p { margin: 0 0 var(--space-1); }
.record-scope__provenance {
  margin: var(--space-4) 0 0;
  font-size: var(--fs-sm);
  color: var(--color-muted);
}
.record-doc {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: 'name link' 'meta link';
  align-items: center;
  gap: 0 var(--space-4);
  padding: var(--space-3) 0;
}
.record-doc + .record-doc { border-top: 1px solid var(--color-rule); }
.record-doc__name { grid-area: name; margin: 0; font-weight: 600; }
.record-doc__meta {
  grid-area: meta;
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--color-muted);
}
.record-doc__link { grid-area: link; white-space: nowrap; }
.record-manage__contact {
  margin: var(--space-4) 0 0;
  color: var(--color-muted);
  line-height: 1.55;
}
.booking-balance {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}
.booking-balance__amount { font-weight: 600; }
.booking-record--missing .empty-state { margin-top: var(--space-4); }
/* Decorative branding loses every collision with functional content: the
   assistant's corner pose stays off the record entirely — and off the
   property workspace, where it sat over the fact rows and above dialogs. */
body.is-booking-record .tr-bristle,
body.is-booking-record .tr-bristle-probe,
body.is-property-page .tr-bristle,
body.is-property-page .tr-bristle-probe {
  display: none;
}
/* Cleans-list rows are navigation targets, not mini records. */
.record--nav { padding: 0; }
.record--nav .record-link {
  display: block;
  padding: var(--space-4);
  color: inherit;
  text-decoration: none;
  border-radius: var(--radius-lg);
}
.record--nav .record-link:hover,
.record--nav .record-link:focus-visible {
  background: var(--color-off-white);
}
.record--nav .record-link:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}
.record__flag {
  color: var(--color-warning-text);
  font-weight: 600;
}
@media (max-width: 40rem) {
  .record-lifecycle {
    flex-direction: column;
    gap: var(--space-3);
  }
  .record-hero__when { font-size: var(--fs-2xl); }
}
