/* ═══════════════════════════════════════════════════════════════
   TABLE OF CONTENTS — grep `═══ N\.` to jump to a section.
   ───────────────────────────────────────────────────────────────
   Mobile / tablet rules live as nested @media inside their base
   rule (e.g. inside .stepper, .cal-pane). Multi-component / multi-
   selector mobile rules that don't fit cleanly inside any one
   component live in section 20.
   ───────────────────────────────────────────────────────────────
     1. Variables & base
     2. Site header
     3. Help menu
     4. Site footer
     5. Booking page chrome
     6. Hero
     7. Stepper
     8. Step card (FLIP wrapper)
     9. Step panels base
     10. Step 2 prelude/summary
     11. Property-type pills
     12. Commercial notice
     13. Trip-charge agreement
     14. Trip-charge admin input
     15. Serif heading utility
     16. Step 3 ledger textarea
     17. Step 1 service cards
     18. Service-card issues list
     19. Forms / fields
     20. Cross-component mobile rules
     24. Buttons
     25. Progressive exposure
     26. Button arrows
     27. Address field
     28. ZIP field
     29. Calendar (centric)
     30. Calendar centric drawer
     31. Calendar skeleton
     32. Alerts
     33. Step 5 success
   ═══════════════════════════════════════════════════════════════ */

/* ═══ 1. Variables & base ═══ */
:root {
  /* All dark text / lines / chrome use a single unified ink color
     (#272936) — was a mix of #1E293B / #0F172A / #1D1D1F (text). */
  --navy: #272936;
  --navy-dark: #272936;
  --orange: #E07A2A;
  --orange-dark: #B86012;
  --orange-soft: #FFF1E5;
  --orange-dim: #C68A5C;
  --cream: #F2EEE6;
  /* Canonical Sunwave warm canvas, per INTERFACE_DESIGN_PHILOSOPHY_NEW
     ("Color Palette and Accent Strategy"). The doc names this the
     base canvas and pairs it specifically with #FCFAF6 cards on top
     ("close cousins, not the same color... subtle enough to feel
     premium, but visible enough that cards still read as physical
     objects resting on the canvas"). Earlier passes drifted to
     #FAF9F6 (too close to card, page read cold) and then to #F3EDDF
     (drifted into beige, which the doc explicitly warns against).
     Keep this at the canonical value unless the doc changes. */
  --bg: #F8F5EF;
  --card: #FFFFFF;
  --card-tint: #F4F5F7;
  /* Muted "selected" tone for non-CTA selections (service cards,
     property pills, slot buttons). Orange is reserved for the primary
     action button + the currently selected calendar date — every
     other "selected" state uses this charcoal so the orange stays a
     guide, not noise. */
  --selected-ink: #272936;
  --selected-soft: rgba(39, 41, 54, 0.10);
  --text: #272936;
  /* Deep editorial ink — used on serif headers / important wordmarks
     so they read as authored content rather than UI text. Slightly
     darker than --text, never pure black. */
  --ink: #1A1C1E;
  --text-muted: #86868B;
  --border: #E4E4E7;
  --border-soft: #ECECEE;
  --success: #15803D;
  --success-bg: #DCFCE7;
  --warning-bg: #FEF3C7;
  --warning-text: #92400E;
  --error: #B91C1C;
  --error-bg: #FEE2E2;
  --radius: 14px;
  --radius-sm: 10px;
  --shadow: 0 1px 2px rgba(39, 41, 54, 0.04);
  --shadow-lg: 0 12px 36px rgba(39, 41, 54, 0.10);
}

html {
  font-feature-settings: "tnum" 1, "ss01" 1;
  /* Reserve space for the vertical scrollbar always, so the layout
     doesn't jolt left when a step transition (e.g. step 1 → step 2)
     pushes the page tall enough to need scrolling. Without this,
     the scrollbar appears mid-FLIP and ~15px of content width
     vanishes, shifting the whole card. */
  scrollbar-gutter: stable;
}

* { box-sizing: border-box; }
/* Kill the iOS/Android default tap highlight — that translucent grey
   rectangle that ignores border-radius and flashes on every tap. We
   rely on the per-element :active/:hover/:focus styles for tap
   feedback (they respect rounded corners since they style the
   element's own background/shadow). */
* { -webkit-tap-highlight-color: transparent; }
html, body { margin: 0; padding: 0; }
body {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  background: var(--bg);
  color: var(--text);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
body.embed-mode { background: transparent; }

/* Admin-booking-page mesh canvas — layered pastel blobs on top of
   the warm canvas. Body gets a peach blob anchored top-right and a
   pale-blue blob anchored bottom-left, both at very low opacity so
   they read as atmospheric depth rather than decorative color. The
   blobs are fixed-position so they stay anchored as the page scrolls
   (the canvas feels like an actual lighting environment, not a
   pattern). Scoped to the admin booking page so the public flow
   keeps the cleaner flat canvas. Philosophy: "extremely subtle,
   diffused mesh gradients where soft blobs of pastel colors bleed
   seamlessly into one another." */
body:has(.booking-page--admin) {
  background:
    radial-gradient(ellipse 60% 50% at 100% 0%, rgba(243, 138, 63, 0.08), transparent 60%) fixed,
    radial-gradient(ellipse 55% 60% at 0% 100%, rgba(191, 224, 243, 0.18), transparent 60%) fixed,
    radial-gradient(ellipse 40% 35% at 80% 90%, rgba(75, 170, 120, 0.05), transparent 65%) fixed,
    var(--bg);
}
/* Fallback for browsers without :has() — slap the same gradients on
   the booking-page--admin element directly. Less smooth (the blobs
   scroll with the page) but the visual effect lands. Drops in on
   Firefox <121 and pre-2024 Safari/Chrome. */
@supports not (selector(:has(*))) {
  .booking-page--admin {
    background:
      radial-gradient(ellipse 60% 50% at 100% 0%, rgba(243, 138, 63, 0.08), transparent 60%),
      radial-gradient(ellipse 55% 60% at 0% 100%, rgba(191, 224, 243, 0.18), transparent 60%),
      radial-gradient(ellipse 40% 35% at 80% 90%, rgba(75, 170, 120, 0.05), transparent 65%),
      var(--bg);
  }
}

/* When the admin booker is embedded inside the dashboard shell, drop its own
   canvas (both the :has mesh above and the no-:has fallback) so the dashboard
   background flows straight through the transparent iframe with no seam. This
   rule sits after the mesh so it wins on source order. */
body.embed-mode,
body.embed-mode .booking-page--admin {
  background: transparent;
}

a { color: var(--orange-dark); text-decoration: none; }
a:hover { text-decoration: underline; }

.container { max-width: 1200px; margin: 0 auto; padding: 0 24px; }
.muted { color: var(--text-muted); font-size: 0.9rem; }
.mt { margin-top: 16px; }

/* ═══ 2. Site header ═══ */
/* Header */
.site-header {
  background: var(--navy);
  color: #fff;
  padding: 18px 0;
}
.header-inner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
}
.brand {
  display: flex;
  align-items: center;
  gap: 12px;
  color: #fff;
  text-decoration: none;
}
.brand:hover { text-decoration: none; }
/* The brandmark SVG has a portrait viewBox (730×1080). The old
   width:48; height:48; object-fit:contain combo letterboxed it into
   a square — content rendered at ~32×48px so the mark looked tiny
   and "fuzzy" inside a half-empty box. Sizing by height with auto
   width lets the SVG render at its natural aspect ratio (no
   letterboxing, no anamorphic scaling). */
.brand-mark {
  display: block;
  height: 56px;
  width: auto;
}
.brand-text { display: flex; flex-direction: column; line-height: 1; }
.brand-text strong {
  font-size: 1.6rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: #fff;
  @media (max-width: 540px) { font-size: 1.3rem; }
}
.brand-text small { font-size: 0.78rem; opacity: 0.75; margin-top: 4px; }

/* Phone CTA — modern translucent pill with icon */
.phone-cta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px 10px 14px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  border: 1px solid rgba(255, 255, 255, 0.22);
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: 0.01em;
  text-decoration: none;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease, transform 0.18s ease, box-shadow 0.18s ease;
  @media (max-width: 540px) {
    padding: 9px 14px 9px 12px;
    font-size: 0.9rem;
  }
}
.phone-cta-icon {
  flex-shrink: 0;
  opacity: 0.9;
  transition: transform 0.25s ease, opacity 0.18s ease;
}
.phone-cta-text { white-space: nowrap; }
.phone-cta:hover {
  background: #fff;
  color: var(--navy, #0e2a47);
  border-color: #fff;
  text-decoration: none;
  transform: translateY(-1px);
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.18);
}
.phone-cta:hover .phone-cta-icon { opacity: 1; transform: rotate(-12deg); }
.phone-cta:active { transform: translateY(0); box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18); }
.phone-cta:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 3px;
}
/* ═══ 3. Help menu ═══ */
/* "Need help?" menu — replaces the prominent single phone CTA. The trigger
   is intentionally quieter (ghost-style on the navy header) so it doesn't
   compete with the address-entry task. The popover lists every location's
   phone, since we don't know which one the customer should call until they
   give us an address. */
.help-menu {
  position: relative;
}
.help-menu-trigger {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px 8px 12px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  color: rgba(255, 255, 255, 0.85);
  border: 1px solid rgba(255, 255, 255, 0.16);
  font-weight: 500;
  font-size: 0.88rem;
  letter-spacing: 0.01em;
  text-decoration: none;
  cursor: pointer;
  list-style: none;
  user-select: none;
  transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease;
  @media (max-width: 540px) { padding: 7px 12px 7px 10px; font-size: 0.82rem; }
}
.help-menu-trigger::-webkit-details-marker { display: none; }
.help-menu-trigger:hover {
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  border-color: rgba(255, 255, 255, 0.28);
}
.help-menu-icon { flex-shrink: 0; opacity: 0.85; }
.help-menu-chevron {
  font-size: 0.65rem;
  opacity: 0.7;
  transition: transform 0.2s ease;
}
.help-menu[open] .help-menu-chevron { transform: rotate(180deg); }
.help-menu[open] .help-menu-trigger {
  background: rgba(255, 255, 255, 0.16);
  border-color: rgba(255, 255, 255, 0.32);
  color: #fff;
}

.help-menu-panel {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  min-width: 280px;
  max-width: 360px;
  background: #FFFFFF;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: 0 18px 40px rgba(39, 41, 54, 0.18);
  padding: 8px;
  z-index: 200;
  animation: helpPanelIn 0.22s cubic-bezier(0.22, 1, 0.36, 1);
  @media (max-width: 540px) { min-width: 240px; right: -4px; }
}
@keyframes helpPanelIn {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}
.help-menu-eyebrow {
  margin: 4px 8px 6px;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.help-menu-row {
  display: grid;
  grid-template-columns: 24px 1fr;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: 10px;
  text-decoration: none;
  color: var(--text);
  transition: background 0.15s ease, transform 0.15s ease;
}
.help-menu-row:hover {
  background: var(--orange-soft);
  text-decoration: none;
  transform: translateX(2px);
}
.help-menu-row[aria-disabled="true"] {
  opacity: 0.55;
  cursor: not-allowed;
  pointer-events: none;
}
.help-menu-row-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  color: var(--orange-dark);
}
.help-menu-row-body { display: flex; flex-direction: column; min-width: 0; }
.help-menu-row-body strong {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--text);
}
.help-menu-row-body small {
  font-size: 0.82rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  margin-top: 1px;
}
/* ═══ 4. Site footer ═══ */
/* Footer */
.site-footer {
  margin-top: 80px;
  padding: 30px 0;
  background: var(--navy-dark);
  color: rgba(255,255,255,0.7);
  font-size: 0.9rem;
  text-align: center;
}

/* Sticky footer for the booking page. Without this the footer follows
   whatever height the active step happens to render at — step 1
   (services grid, ~560px) pushes it way down, step 5 (success card,
   ~280px) pulls it up, and the page rolls up and down each transition.
   Body becomes a flex column with min-height 100vh, main grows to fill
   the gap, footer always anchors at the bottom of the viewport (or
   below content when the page does need to scroll). Scoped with :has()
   so admin/auth/embed pages are untouched. */
body:has(.booking-page) {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  /* Lock horizontal scroll on the booking page. If any child element
     (drawer slot text, suggestion list during open, etc.) briefly
     exceeds viewport width during render, mobile browsers compensate
     by zooming the whole page out. Clipping x-overflow keeps every
     transient overhang invisible and preserves the native 1.0 zoom
     level. Vertical scroll still works via min-height: 100vh above. */
  @media (max-width: 600px) { overflow-x: hidden; }
}
body:has(.booking-page) > main { flex: 1 0 auto; }
body:has(.booking-page) > .site-footer { flex-shrink: 0; }

/* ───── Booking-page header + footer blend ─────
   The default header/footer use navy backgrounds (good for admin
   chrome). On the booking page that high-contrast cap fights the
   peaceful "warm paper" stage — the card should feel like it's
   floating, not bookended by dark bars. These overrides drop the
   dark fills and recolor the text to the warm-neutral palette, only
   when the body has a .booking-page (so admin/auth pages stay as
   they were). */
body:has(.booking-page) > .site-header {
  background: transparent;
  color: var(--navy);
  padding: 22px 0 0; /* tighter, since there's no dark band to fill */
}
body:has(.booking-page) > .site-header .brand,
body:has(.booking-page) > .site-header .brand-text strong {
  color: var(--navy);
}
body:has(.booking-page) > .site-header .brand-text small {
  color: var(--text-muted);
  opacity: 1;
}
body:has(.booking-page) > .site-footer {
  background: transparent;
  color: var(--text-muted);
  margin-top: 0; /* the booking-page padding-bottom handles the gap */
}
/* Footer Help-menu trigger — was white-on-navy for the dark footer.
   Re-skinned for the now-transparent booking footer: dark text on
   a soft warm-paper pill. */
body:has(.booking-page) .help-menu--footer .help-menu-trigger {
  background: rgba(39, 41, 54, 0.04);
  border-color: rgba(39, 41, 54, 0.10);
  color: var(--text);
}
body:has(.booking-page) .help-menu--footer .help-menu-trigger:hover,
body:has(.booking-page) .help-menu--footer[open] .help-menu-trigger {
  background: rgba(39, 41, 54, 0.08);
  border-color: rgba(39, 41, 54, 0.18);
  color: var(--navy);
}
.site-footer-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}
.site-footer-copy { margin: 0; }

/* Help-menu in the footer: opens UPWARD instead of down (since the
   trigger is near the bottom of the page) and centers itself on the
   trigger instead of right-aligning. The trigger pill itself adopts a
   subtle white-on-navy treatment to match the footer surface. */
.help-menu--footer { position: relative; }
.help-menu--footer .help-menu-trigger {
  background: rgba(255, 255, 255, 0.10);
  border-color: rgba(255, 255, 255, 0.18);
  color: rgba(255, 255, 255, 0.92);
}
.help-menu--footer .help-menu-trigger:hover {
  background: rgba(255, 255, 255, 0.18);
  border-color: rgba(255, 255, 255, 0.32);
  color: #fff;
}
.help-menu--footer[open] .help-menu-trigger {
  background: rgba(255, 255, 255, 0.18);
  border-color: rgba(255, 255, 255, 0.32);
  color: #fff;
}
.help-menu--footer .help-menu-panel {
  top: auto;
  right: auto;
  bottom: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%);
  animation: helpPanelInUp 0.22s cubic-bezier(0.22, 1, 0.36, 1);
  /* Mobile: anchor to the screen rather than translateX-centered when
     very narrow, so it never overflows the viewport. */
  @media (max-width: 600px) { min-width: 92vw; }
}
@keyframes helpPanelInUp {
  from { opacity: 0; transform: translate(-50%, 6px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}

/* ═══ 5. Booking page chrome ═══ */
/* Booking page */
/* "Stage" effect — the white card sits with breathing room on every
   side so it reads as a centered object, not a tab pinned to the top
   of the page. Was 88/120 — tightened to 36/80 so the top stack
   doesn't dominate and the card is closer to vertical center on
   typical laptop viewports. */
.booking-page {
  padding: 36px 24px 80px;
  @media (min-width: 601px) and (max-width: 760px) { padding: 56px 18px 80px; }
  /* Mobile: tighter top + bottom padding so the page doesn't look
     letter-boxed on a 360x640 phone. Side padding stays generous
     enough that the card has air on the left/right edges. */
  @media (max-width: 600px) { padding: 18px 14px 36px; }
  @media (max-width: 380px) { padding: 22px 10px 48px; }
}
body.embed-mode .booking-page { padding: 24px; }
/* ═══ 6. Hero ═══ */
.hero { text-align: center; margin-bottom: 32px; }
.hero h1 {
  font-size: 2.4rem;
  font-weight: 800;
  color: var(--navy);
  margin: 0;
  letter-spacing: -0.025em;
  line-height: 1.1;
}
@media (max-width: 540px) {
  .hero h1 { font-size: 1.8rem; }
}
/* Legacy sub-heading (some flows still render it). New booking page hero
   is a single heading. */
.hero-sub {
  color: var(--text-muted);
  font-size: 1rem;
  margin: 8px 0 0;
  font-weight: 400;
}

/* ═══ 7. Stepper ═══ */
/* ───── Stepper (architectural / fluid) ─────
   Goal: a delicate, lifestyle-tech progress indicator. Thin 1px
   connecting lines, a "looming" larger active circle with a soft
   orange halo, completed steps replaced by a flat checkmark
   (instead of holding the number forever), and an animated "ink
   flow" through each connector when the previous step completes.
   Generous bottom margin so the stepper feels like it's floating
   above the card rather than sitting on top of it. */
.stepper {
  list-style: none;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 0;
  padding: 0;
  /* Tightened from 64 → 24 so the stepper sits close to the card
     instead of floating in a slab of empty space above it. */
  margin: 0 auto 24px;
  position: relative;
  max-width: 620px;
  /* Mobile: sticky-pinned at the top of the viewport so it stays locked
     in place while the .step-card morphs height during transitions.
     Without this the entire stack (stepper + card) shifts together
     during the FLIP, reading as "everything jiggles". Negative side
     margins extend the cream background to viewport edges so there's
     no fade-line where the page padding ends. */
  @media (max-width: 600px) {
    position: sticky;
    top: 0;
    z-index: 10;
    max-width: 100%;
    margin: 0 -14px 18px;
    padding: 14px 14px 12px;
    background: var(--bg);
    flex-wrap: nowrap;
  }
  @media (max-width: 380px) {
    /* Tiny-phone (≤380) page padding is 22px/10px — re-tune side
       margins so the cream background still bleeds to viewport edges
       without overlapping the card on first load. */
    margin: 0 -10px 18px;
    padding: 16px 10px 12px;
  }
}
/* Old continuous .stepper::before line removed — connectors now live
   on each .step + .step pair (rule below) so we can independently
   animate each segment as the user advances. */

.stepper .step {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0;
  background: transparent;
  border: 0;
  font-size: 0.86rem;
  /* Letter-spacing — the "luxury brand" airy tracking. */
  letter-spacing: 0.04em;
  font-weight: 400;
  color: var(--text-muted);
  position: relative;
  z-index: 1;
  /* Soft fades for color/opacity. Weight changes between active/done
     are discrete (font-weight can't smoothly tween) but the opacity
     and color crossfade carry most of the perceived smoothness. */
  transition: color 0.32s ease;
  /* Mobile: tighten gap and size so all four pills fit on a 360px
     screen without wrapping. */
  @media (max-width: 600px) { padding: 0; font-size: 0.76rem; gap: 8px; }
  @media (max-width: 380px) { gap: 0; padding: 0; }
}

/* Spacing between steps to host the line connector. Doesn't apply to
   the first step (no connector to its left). */
.stepper .step + .step {
  margin-left: 56px;
  @media (max-width: 600px) { margin-left: 28px; }
  @media (max-width: 380px) { margin-left: 22px; }
}

/* Base connector — thin 1px gray line between this step and the
   previous one. Sits behind the marker circles. */
.stepper .step + .step::before {
  content: '';
  position: absolute;
  top: 50%;
  right: calc(100% + 6px); /* small breath off the marker */
  width: 44px;
  height: 1px;
  background: var(--border);
  transform: translateY(-50%);
  pointer-events: none;
  z-index: 0;
  border-radius: 999px;
  @media (max-width: 600px) { width: 18px; right: calc(100% + 5px); }
  @media (max-width: 380px) { width: 14px; right: calc(100% + 4px); }
}

/* Foreground "ink flow" — orange line that animates from 0 to full
   width when the previous step becomes .done (or this step itself
   becomes .done while navigating back). The width transition reads
   as ink flowing through the line into the next circle. */
.stepper .step + .step::after {
  content: '';
  position: absolute;
  top: 50%;
  right: calc(100% + 6px);
  width: 0;
  height: 1px;
  background: var(--orange);
  transform: translateY(-50%);
  pointer-events: none;
  z-index: 1;
  border-radius: 999px;
  transition: width 0.65s cubic-bezier(0.22, 1, 0.36, 1);
  @media (max-width: 600px) { right: calc(100% + 5px); }
  @media (max-width: 380px) { right: calc(100% + 4px); }
}

/* Fill the connector if the previous step is done OR if this step
   itself is done (handles the user navigating back, where step N
   stays .done even though step N-1 is now .active). */
.stepper .step.done + .step::after,
.stepper .step + .step.done::after {
  width: 44px;
  @media (max-width: 600px) { width: 18px; }
  @media (max-width: 380px) { width: 14px; }
}

/* The marker circle — number when inactive/active, checkmark when
   .done. Position relative so the absolutely-positioned num/check
   stack on top of each other and crossfade. */
.stepper .step .step-marker {
  position: relative;
  /* Locked size across all states. Size used to bump 24 → 30 on active
     and that 6px delta re-flowed the centered stepper on every step
     change ("pills moving all over the place"). The active emphasis
     is now carried by background + halo only — no layout impact. */
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--bg);
  border: 1px solid var(--border);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 500;
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
  flex-shrink: 0;
  transition:
    background 0.28s ease,
    border-color 0.28s ease,
    color 0.28s ease,
    box-shadow 0.32s ease;
  /* Mobile: locked size across all states — same reasoning as desktop
     above. Active emphasis stays via the orange fill + halo. */
  @media (max-width: 600px) { width: 26px; height: 26px; font-size: 0.74rem; }
}

/* Number + checkmark stacked. .step-num is shown for inactive/active,
   .step-check is shown when .done. Both fade. */
.stepper .step .step-num,
.stepper .step .step-check {
  position: absolute;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: opacity 0.28s ease;
}
.stepper .step .step-check {
  opacity: 0;
  width: 14px;
  height: 14px;
  @media (max-width: 600px) { width: 12px; height: 12px; }
}

/* Ghost inactive — faint outline, paper-tone fill. The user sees
   the steps coming but they read as quiet roadmap markers. */
.stepper .step .step-marker {
  background: var(--bg);
  border-color: var(--border);
}

/* Active — orange fill + soft halo. The size used to bump from 24 →
   30, but that re-flowed the row; emphasis is carried by color +
   shadow instead now. */
.stepper .step.active .step-marker {
  background: var(--orange);
  border-color: var(--orange);
  color: #fff;
  box-shadow:
    0 0 0 6px rgba(224, 122, 42, 0.10),
    0 4px 14px rgba(224, 122, 42, 0.22),
    inset 0 1px 0 rgba(255, 255, 255, 0.18);
}

/* Done — flat orange circle, checkmark in white. Crossfade swaps
   number → checkmark. */
.stepper .step.done .step-marker {
  background: var(--orange);
  border-color: var(--orange);
  color: #fff;
  cursor: pointer;
}
.stepper .step.done .step-num   { opacity: 0; }
.stepper .step.done .step-check { opacity: 1; }

/* Labels — single locked weight so font-weight changes don't cause
   glyph-width drift on each step transition (the other half of the
   "stepper moves all over the place" symptom). State emphasis is
   carried by color + opacity only. */
.stepper .step .step-label {
  transition: color 0.32s ease, opacity 0.32s ease;
  font-weight: 500;
  opacity: 0.55;
  letter-spacing: 0.04em;
  white-space: nowrap;
  /* Tiny phones: drop the text labels and leave only the marker
     circles — the active/done coloring still tells the user
     which step they're on. */
  @media (max-width: 380px) { display: none; }
}
.stepper .step.done .step-label {
  opacity: 0.85;
  color: var(--text);
}
.stepper .step.active .step-label {
  opacity: 1;
  color: var(--text);
}
.stepper .step.done:hover .step-label { color: var(--text); }

/* Step panels — luminous card with light-catch inner highlight */
/* ═══ 8. Step card (FLIP wrapper) ═══ */
/* Persistent outer card (.step-card) — owns the visual chrome (bg,
   border, radius, shadow, max-width). The step panels inside are
   just content containers. JS animates `height` on this wrapper
   between steps via the FLIP technique (capture old height, swap
   visible panel, capture new height, transition between them). The
   user sees ONE card smoothly resizing instead of separate cards
   flashing in different sizes. */
.step-card {
  /* Bento hero card: warm card surface (#FCFAF6) replaces the pure
     white that used to anchor the right column, soft warm border
     instead of cool charcoal, and a quiet ambient shadow with a
     warm undertone so the card hovers above the canvas like the
     philosophy doc describes. The 22px squircle radius stays. */
  background: #FCFAF6;
  border-radius: 22px;
  border: 1px solid #E5DED3;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.9),
    0 14px 36px rgba(55, 59, 77, 0.05),
    0 2px 6px rgba(55, 59, 77, 0.03);
  max-width: 1080px;
  margin: 0 auto;
  overflow: hidden; /* clip rounded corners; suggestions list is portaled */
  /* Slow + symmetric sine ease-in-out so big size changes read as one
     smooth morph. Both height AND max-width transition — the card
     visibly narrows as the user progresses through the flow (calendar
     = wide, form = narrower, success = narrowest). 0.85s is long
     enough to feel like a deliberate morph rather than a snap; sine
     curve eases in and out symmetrically so the card never lurches. */
  transition:
    height 0.85s cubic-bezier(0.65, 0, 0.35, 1),
    max-width 0.85s cubic-bezier(0.65, 0, 0.35, 1);
}

/* Per-step card width — set by JS via data-active-step on the card.
   The transitions above interpolate between these values so the card
   visibly resizes side-to-side as well as top-to-bottom on each
   showStep. Steps with grid/calendar content stay wide; content-light
   steps (description / success) tuck in narrower for focus. */
.step-card[data-active-step="1"] { max-width: 1200px; } /* services grid */
.step-card[data-active-step="2"] { max-width: 1200px; } /* calendar */
.step-card[data-active-step="3"] { max-width: 1040px; } /* description */
.step-card[data-active-step="4"] { max-width: 920px; }  /* form */
.step-card[data-active-step="5"] { max-width: 720px; }  /* success */

/* Step 2 + Commercial picked — calendar is hidden, so the wide
   card has nothing to fill and reads as oversized. Pull it in to
   match the form/success widths so the notice + address sit
   centered in a card sized to the content. The same height
   transition runs on the .step-card so the narrowing animates
   smoothly when the user toggles between Home and Commercial. */
.step-card[data-active-step="2"]:has(input[name="property_type"][value="commercial"]:checked) {
  max-width: 720px;
}

/* ═══ 9. Step panels base ═══ */
/* Step panels — chrome moved to .step-card above. Every panel is a
   flex column with a moderate baseline min-height. Steps with more
   content (services grid, calendar, form) will grow past it; steps
   with less content (single textarea, success card) will sit at the
   baseline rather than collapsing to ~280px. The result: the card
   has visible-but-modest variance between steps, which gives the
   FLIP height-transition something to actually animate (it's a
   no-op when first/last heights match). The actions row gets
   margin-top: auto so Continue/Back anchor to the bottom of the
   panel regardless of how much content is above them. */
.step-panel {
  padding: 48px 52px;
  min-height: 540px;
  display: flex;
  flex-direction: column;
  transform-origin: top center;
  will-change: opacity;
  /* Mid-tablet / large phone (601-760px) — lighter padding than desktop
     so the panel doesn't waste edge real estate at narrower widths. */
  @media (min-width: 601px) and (max-width: 760px) {
    padding: 36px 32px;
    min-height: 480px;
  }
  /* Drop the desktop min-height on phones — the card already fills the
     viewport and a fixed minimum just creates dead space below short
     steps when the user can already see the whole thing at once.
     Heights go natural on mobile. */
  @media (max-width: 600px) {
    padding: 24px;
    min-height: auto;
  }
}
.step-panel .actions { margin-top: auto; }
/* Step 1 (Service pick): a wider panel with normal padding so the
   service-grid has room to breathe and fit 3-4 cards comfortably.
   The panel is a flex column with a generous min-height so it
   pre-reserves the vertical space a card's expanded "Common issues"
   list will need. The Continue button (.actions) is pushed to the
   bottom with margin-top: auto. When a card expands, the grid grows
   into the slack between itself and Continue — Continue itself stays
   put. Net: clicking cards doesn't make the bottom of the page
   bounce around, and the elastic press scale is preserved (transforms
   don't affect layout flow). */
.step-panel[data-step="1"] {
  /* No per-step layout overrides on desktop — the base .step-panel rule
     already gives every step flex column + min-height + margin-top:
     auto on actions. Mobile drops the desktop min-height and tightens
     padding so phones don't read as letter-boxed. */
  @media (max-width: 600px) {
    padding: 18px 16px;
    min-height: auto;
  }
  @media (max-width: 380px) { padding: 22px 14px; }
}
/* `display: flex` above outweighs the UA stylesheet's `[hidden] { display: none }`
   on specificity, so without this explicit override the panel stays visible
   even after JS sets `step1.hidden = true`. Result: step 1 and step 2 both
   render on the page. Same trick used for .save-bar and .field elsewhere. */
.step-panel[hidden] { display: none !important; }
/* Step 2 (Address + Calendar) keeps the edge-to-edge treatment so the
   #availability calendar pills can extend the full panel width. The
   panel has 0 horizontal padding; every direct child gets margin by
   DEFAULT, and only #availability opts out via :not().
   This is intentional — earlier we used an allow-list of named
   children and every new element added to step 2 (e.g. the Back
   button's .actions wrapper) ended up flush against the card edge.
   Inverting to a default-on/explicit-opt-out pattern makes the layout
   self-correcting for future additions. */
.step-panel[data-step="2"] {
  /* Outer card chrome (max-width, bg, shadow) from .step-panel.
     Horizontal padding overridden to 0 so #availability (the calendar
     + slots two-column grid) can extend edge-to-edge inside the card.
     Every other child gets symmetric horizontal margin via the
     deny-list selector below — see comment above .step-panel about why
     that's a deny-list rather than an allow-list. */
  padding: 44px 0 48px;
  @media (max-width: 760px) { padding: 28px 0 28px; }
}
.step-panel[data-step="2"] > *:not(#availability) {
  margin-left: 52px;
  margin-right: 52px;
  @media (max-width: 760px) { margin-left: 20px; margin-right: 20px; }
}
/* Generous vertical rhythm between every section in step 2. Each
   margin was tighter before (4–24px) and the page felt squished;
   bumped across the board so property-type → address → calendar
   → actions each have real room to breathe. */
.step-panel[data-step="2"] > .zip-field { margin-bottom: 32px; }
/* Property type / address now live inside `.step2-prelude` so their
   margins use descendant selectors instead of `>`. The prelude itself
   gets the bottom margin that previously lived on `.address-field`. */
.step-panel[data-step="2"] .address-field { margin-top: 0; margin-bottom: 0; }
.step-panel[data-step="2"] .address-help { margin-top: 18px; margin-bottom: 12px; }
.step-panel[data-step="2"] .property-type-field {
  margin-top: 0;
  margin-bottom: 18px;
  @media (max-width: 600px) { margin-bottom: 16px; }
}
.step-panel[data-step="2"] .commercial-notice { margin-top: 18px; margin-bottom: 18px; }
.step-panel[data-step="2"] > .step2-prelude {
  margin-bottom: 18px;
  /* Pull the prelude in by 8px and add 8px internal padding so the
     address input's focus ring has clearance to render without being
     clipped by the prelude's overflow: hidden (needed for collapse).
     Net visual indent matches sibling elements (44 + 8 = 52). */
  margin-left: 44px;
  margin-right: 44px;
  padding-left: 8px;
  padding-right: 8px;
  /* Mobile: panel uses 20px margin on direct children (deny-list above).
     Pull prelude in to 14 + 6 padding = 20 so input has focus-ring
     clearance. */
  @media (max-width: 600px) {
    margin-bottom: 14px;
    margin-left: 14px;
    margin-right: 14px;
    padding-left: 6px;
    padding-right: 6px;
  }
}
/* Tightened 18 → 8. With #availability margin-top trimmed below, the
   total gap between summary chip and "May 2026" is ~14px, not ~34. */
.step-panel[data-step="2"] > .step2-summary {
  margin-bottom: 8px;
  @media (max-width: 600px) { margin-bottom: 14px; padding: 10px 12px; }
}
.step-panel[data-step="2"] > #availability {
  margin-top: 6px;
  @media (max-width: 600px) { margin-top: 8px; }
}
.step-panel[data-step="2"] > .actions {
  margin-top: 32px;
  padding-top: 4px;
  @media (max-width: 600px) { margin-top: 28px; }
}

/* ═══ 10. Step 2 prelude/summary ═══ */
/* ───── Step 2 collapse: prelude → summary ─────
   Prelude (property type pills + address input) collapses to a thin
   summary row once the address verifies. Saves ~350px so the calendar
   lands near the top of the viewport rather than below the fold. */
.step2-prelude {
  overflow: hidden;
  /* Cap above any realistic content height; collapses to 0 when
     `.is-collapsed` is added. The transition runs on max-height +
     opacity together so the section visibly fades while shrinking. */
  max-height: 720px;
  opacity: 1;
  transition:
    max-height 0.5s cubic-bezier(0.4, 0, 0.2, 1),
    opacity 0.32s ease,
    margin-bottom 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.step2-prelude.is-collapsed {
  max-height: 0;
  opacity: 0;
  margin-bottom: 0 !important;
  pointer-events: none;
}
/* Used during step-card re-entry to suppress the prelude's own
   transition so the showStep FLIP can animate the card height in a
   single smooth motion instead of competing with the inner collapse. */
.step2-prelude.no-transition { transition: none !important; }
/* The collapsed summary chip — Home icon + property label + address
   + edit pencil, all on a single ~52px row. Reads as a "you've told us
   this; click to edit" affordance rather than competing with the
   calendar for attention. */
.step2-summary {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  background: #FAF7F1;
  border: 1px solid rgba(39, 41, 54, 0.10);
  border-radius: 14px;
  /* Settle in: tiny drop + fade so the row appears as the prelude
     collapses, not after it. The animation duration matches the
     prelude collapse so they finish in lockstep. */
  animation: step2SummaryIn 0.46s cubic-bezier(0.22, 1, 0.36, 1);
}
.step2-summary[hidden] { display: none !important; }

/* Rotating loader caption under the address summary. Phrases swap on
   fixed beats (see startLoadingCaption in calendar.js) while the
   /api/availability request is in flight. Visual hierarchy:
   - Sits aligned with the address summary, NOT inside the calendar
     skeleton, so it reads as "the page is still working on it"
     rather than "the calendar is broken."
   - Warm muted ink so it's clearly secondary copy.
   - The 5s "sleepy servers" line picks up .is-slow which warms the
     color a touch toward the brand orange — same as the page's
     "we know this is taking longer" voice. */
.calendar-loading-caption {
  margin: 10px 4px 14px;
  font-size: 0.95rem;
  line-height: 1.4;
  color: var(--text-muted);
  font-style: italic;
  letter-spacing: 0.005em;
  transition: color 0.3s ease;
  /* Fade-in on swap — the textContent change retriggers the animation
     via a key swap below; here we just ensure the very first render
     fades up rather than snapping in. */
  animation: captionFadeIn 0.28s ease both;
}
.calendar-loading-caption[hidden] { display: none !important; }
.calendar-loading-caption.is-slow { color: var(--orange-dark, #B86012); }
@keyframes captionFadeIn {
  from { opacity: 0; transform: translateY(-2px); }
  to   { opacity: 1; transform: translateY(0); }
}
.step2-summary-icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  color: var(--text-muted);
}
.step2-summary-icon svg { width: 18px; height: 18px; }
.step2-summary-text {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-size: 0.95rem;
  color: var(--text);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.step2-summary-prop {
  font-weight: 600;
  flex: 0 0 auto;
}
.step2-summary-sep {
  color: var(--text-muted);
  flex: 0 0 auto;
}
.step2-summary-addr {
  color: var(--text);
  font-weight: 400;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  min-width: 0;
}
.step2-summary-edit {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: 0;
  background: transparent;
  color: var(--text-muted);
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.18s ease, color 0.18s ease;
}
.step2-summary-edit:hover {
  background: rgba(39, 41, 54, 0.06);
  color: var(--orange);
}
@keyframes step2SummaryIn {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ═══ 11. Property-type pills ═══ */
/* ───── Property type — segmented control ─────
   Sits directly under the address; defaults to Home so the common
   path is zero-click. Picking Commercial flips the calendar off and
   shows a redirect notice. */
.property-type-field {
  border: 0;
  padding: 0;
  margin: 0;
}

/* ═══════════════════════════════════════════════════════════════
   Admin booking banner + customer search
   Rendered on /admin/book/:slug only. The banner sits ABOVE the
   stepper to make it unmistakable that this is an admin-mode page,
   and to hold the customer name-lookup typeahead that's only useful
   when the admin is filling out a booking on someone else's behalf.
   Visual language: same cream/charcoal-soft skin as the booking
   card, with a subtle orange accent stripe so it reads as elevated
   without being loud.
   ═══════════════════════════════════════════════════════════════ */
.admin-booking-banner {
  width: 100%;
  max-width: none;
  margin: 0 auto 18px;
  /* Bento card: warm card surface, generous padding, soft-warm border
     instead of cool gray, large border-radius for the "squircle"
     softness called for in the philosophy doc. Soft ambient shadow
     replaces the previous flat panel so the card hovers above the
     warm canvas like a physical object. */
  padding: 22px 24px;
  background: #FCFAF6;
  border: 1px solid #E5DED3;
  border-left: 3px solid #F38A3F;
  border-radius: 22px;
  box-shadow: 0 12px 32px rgba(55, 59, 77, 0.05),
              0 1px 0 rgba(255, 255, 255, 0.85) inset;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
@media (max-width: 600px) {
  .admin-booking-banner {
    padding: 16px 18px;
    margin-bottom: 14px;
    border-radius: 18px;
  }
}
.admin-booking-banner-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.admin-booking-banner-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  background: rgba(243, 138, 63, 0.14);
  color: #B86012;
  border-radius: 999px;
  font-size: 0.66rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
/* Single-location case (no picker dropdown). Sized to match the
   .admin-loc-picker-summary pill above so single-location and
   multi-location banners read at the same height. */
.admin-booking-banner-loc {
  display: inline-flex;
  align-items: center;
  padding: 7px 14px;
  min-height: 32px;
  background: #FFFDF9;
  border: 1px solid #E5DED3;
  border-radius: 999px;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.8) inset;
  min-width: 0;
}
.admin-booking-banner-loc strong {
  font-size: 0.92rem;
  font-weight: 700;
  color: #373B4D;
  letter-spacing: -0.005em;
  line-height: 1.2;
}
/* Secondary action pill — philosophy doc: "Secondary Actions...
   wrapped in a faint, light-gray pill or a thin 1px outline."
   Warm-card fill + warm border + pill radius matches the rest of
   the page's secondary affordances (admin-cal-nav, etc.). */
.admin-booking-banner-back {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.85rem;
  font-weight: 600;
  color: #373B4D;
  text-decoration: none;
  padding: 7px 14px;
  background: #FFFDF9;
  border: 1px solid #E5DED3;
  border-radius: 999px;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.85) inset;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.06s ease;
}
.admin-booking-banner-back svg { transition: transform 0.18s ease; }
.admin-booking-banner-back:hover {
  background: #FFFAF1;
  border-color: rgba(243, 138, 63, 0.35);
  color: #373B4D;
}
.admin-booking-banner-back:hover svg { transform: translateX(-2px); }
.admin-booking-banner-back:active { transform: scale(0.98); }

/* ─── Booking stats strip (admin, quiet mode) ─────────────────────
   Single subtle row tucked below the keyboard shortcuts card. Three
   small chips: today / week / personal best. Filtered to admin-
   actor bookings only (the public widget's bookings don't count).
   No celebration emoji, no gradient washes, no status pill — just
   a quiet reference so the admin can glance over and see "I've done
   N today." The value bumps very lightly after a successful submit
   and that's the only feedback. Designed to disappear into the rail
   when not in focus. */
.admin-stats-strip {
  display: flex;
  gap: 6px;
  margin-top: 10px;
  padding: 2px;
}
.admin-stats-strip-chip {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  padding: 8px 10px;
  background: #FCFAF6;
  border: 1px solid #ECE5D6;
  border-radius: 10px;
  min-width: 0;
}
.admin-stats-strip-label {
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #8A8475;
  line-height: 1.1;
}
.admin-stats-strip-value {
  font-size: 1.05rem;
  font-weight: 700;
  color: #373B4D;
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
  letter-spacing: -0.01em;
}
/* Light bump animation on successful submit — much subtler than the
   bento card's full pop. Just a 4% scale + brief color tick so the
   admin notices the value changed without it stealing focus. */
@keyframes statsStripBump {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.04); }
  100% { transform: scale(1); }
}
.admin-stats-strip-value.is-bumping {
  animation: statsStripBump 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
  transform-origin: left center;
  display: inline-block;
}
@media (prefers-reduced-motion: reduce) {
  .admin-stats-strip-value.is-bumping { animation: none; }
}

/* ─── Recent bookings rail card (admin) ─────────────────────────────
   Stacked directly under the stats strip. Shows the last ~15 admin-
   filed bookings as stacked mini cards. The list scrolls internally
   so the card matches the height of the scheduling form to its right
   without pushing the page taller. Each row is collapsed by default;
   clicking expands to reveal the customer's stated problem, full
   address, and an "Open in HCP" link that opens the parent job
   page. Quiet visual treatment — warm-canvas cards, no shadows on
   the items themselves; the only emphasis is the colored avatar
   chip on the left and the time pill on the right. */
.admin-recent {
  display: flex;
  flex-direction: column;
  /* Take the remaining vertical space in the rail column (which is
     stretched to match the form column's height — see
     .admin-rail-column.align-self: stretch above). min-height: 0 is
     critical for the internal list to scroll instead of pushing the
     card taller. */
  flex: 1 1 auto;
  min-height: 220px;
  background: #FFFCF6;
  border: 1px solid #ECE5D6;
  border-radius: 14px;
  padding: 12px 6px 10px 12px;
  box-shadow: 0 1px 0 rgba(55, 59, 77, 0.02);
}
.admin-recent-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 0 6px 8px 0;
  border-bottom: 1px solid #F1ECDC;
  margin-bottom: 4px;
}
.admin-recent-title {
  margin: 0;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #8A8475;
}
.admin-recent-refresh {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
  color: #8A8475;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease, border-color 120ms ease, transform 200ms ease;
}
.admin-recent-refresh:hover {
  background: #F5EFE0;
  border-color: #E5DDC8;
  color: #373B4D;
}
.admin-recent-refresh:focus-visible {
  outline: 2px solid #B6D4F2;
  outline-offset: 2px;
}
.admin-recent-refresh.is-spinning svg {
  animation: adminRecentSpin 700ms linear;
}
@keyframes adminRecentSpin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .admin-recent-refresh.is-spinning svg { animation: none; }
}
/* Internal scroll container. min-height: 0 is critical — without it
   the flex child refuses to shrink below its content height and the
   overflow:auto never engages. Custom scrollbar styling keeps the
   rail looking quiet on macOS (where scrollbars overlay) and on
   Windows (where they take up gutter). */
.admin-recent-list {
  list-style: none;
  margin: 0;
  padding: 6px 6px 6px 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  overflow-y: auto;
  flex: 1 1 auto;
  min-height: 0;
  scrollbar-width: thin;
  scrollbar-color: #DCD2BA #FFFCF6;
}
.admin-recent-list::-webkit-scrollbar { width: 8px; }
.admin-recent-list::-webkit-scrollbar-track { background: transparent; }
.admin-recent-list::-webkit-scrollbar-thumb {
  background: #E5DDC8;
  border-radius: 999px;
  border: 2px solid #FFFCF6;
}
.admin-recent-list::-webkit-scrollbar-thumb:hover { background: #DCD2BA; }

.admin-recent-empty {
  padding: 28px 12px;
  text-align: center;
  color: #9A9485;
  font-size: 0.82rem;
  line-height: 1.5;
}
.admin-recent-empty strong { display: block; color: #6B6555; margin-bottom: 4px; font-weight: 700; }

/* Mini card row. <li> is the click target (set role=button via JS so
   keyboard still works); the expanded body is a sibling that toggles
   via [data-open] / [hidden]. Native <details> would be simpler but
   we want the avatar+name+slot row to be the summary surface and
   <summary> with custom content is harder to style consistently. */
.admin-recent-item {
  display: flex;
  flex-direction: column;
  background: #FCFAF6;
  border: 1px solid #ECE5D6;
  border-radius: 10px;
  overflow: hidden;
  transition: border-color 140ms ease, transform 140ms ease, box-shadow 140ms ease;
}
.admin-recent-item:hover {
  border-color: #DDD3BA;
}
.admin-recent-item[data-open="1"] {
  border-color: #C8BDA0;
  box-shadow: 0 2px 6px rgba(55, 59, 77, 0.05);
}
.admin-recent-item.is-new {
  animation: adminRecentSlide 420ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
@keyframes adminRecentSlide {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .admin-recent-item.is-new { animation: none; }
}

.admin-recent-summary {
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr) auto;
  align-items: center;
  gap: 9px;
  padding: 8px 10px;
  background: transparent;
  border: 0;
  width: 100%;
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: inherit;
}
.admin-recent-summary:focus-visible {
  outline: 2px solid #B6D4F2;
  outline-offset: -2px;
  border-radius: 10px;
}
.admin-recent-avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #FFFFFF;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  background: #C68C5B; /* default warm; overridden per item via inline style */
  border: 1.5px solid rgba(255, 255, 255, 0.9);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.15);
  flex-shrink: 0;
}
.admin-recent-stack {
  display: flex;
  flex-direction: column;
  min-width: 0;
  gap: 1px;
}
.admin-recent-name {
  font-size: 0.84rem;
  font-weight: 600;
  color: #373B4D;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: 1.2;
}
.admin-recent-sub {
  font-size: 0.7rem;
  color: #7A7466;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: 1.25;
}
.admin-recent-sub b {
  color: #4A4434;
  font-weight: 600;
}
.admin-recent-pill {
  font-size: 0.64rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #7A7466;
  background: #F2EAD3;
  padding: 3px 7px;
  border-radius: 999px;
  white-space: nowrap;
  flex-shrink: 0;
}

.admin-recent-body {
  padding: 4px 12px 12px 12px;
  border-top: 1px dashed #ECE5D6;
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: #FFFEFA;
}
.admin-recent-body[hidden] { display: none; }
.admin-recent-row {
  font-size: 0.78rem;
  color: #4A4434;
  line-height: 1.4;
}
.admin-recent-row dt {
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #9A9485;
  margin-bottom: 2px;
}
.admin-recent-row dd { margin: 0; }
.admin-recent-row--summary dd {
  background: #FCFAF6;
  border-left: 2px solid #D8C99F;
  padding: 6px 9px;
  border-radius: 0 6px 6px 0;
  color: #3A3424;
  white-space: pre-wrap;
}
.admin-recent-actions {
  display: flex;
  justify-content: flex-end;
  gap: 6px;
  padding-top: 2px;
}
.admin-recent-hcp {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 6px 10px;
  background: #373B4D;
  color: #FFFFFF;
  border-radius: 8px;
  font-size: 0.74rem;
  font-weight: 600;
  text-decoration: none;
  letter-spacing: 0.01em;
  transition: background 140ms ease, transform 100ms ease;
}
.admin-recent-hcp:hover { background: #143666; }
.admin-recent-hcp:active { transform: translateY(1px); }
.admin-recent-hcp svg { flex-shrink: 0; }
.admin-recent-hcp.is-disabled {
  background: #E5DDC8;
  color: #9A9485;
  pointer-events: none;
}

.admin-customer-search {
  position: relative;
  width: 100%;
}
/* Scope toggle. Lives inline in the banner row beside the location
   picker so it doesn't eat vertical space above the customer search.
   Slowness warning is tucked behind a help icon (tooltip on
   hover/focus). Minimal chrome — reads as a subtle control, not a
   panel. */
.admin-customer-search-scope {
  display: flex;
  align-items: center;
  gap: 6px;
}
.admin-customer-search-scope-toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px 4px 4px;
  background: transparent;
  border: 0;
  border-radius: 999px;
  font: inherit;
  cursor: pointer;
  color: #373B4D;
  transition: background 0.12s ease;
}
.admin-customer-search-scope-toggle:hover,
.admin-customer-search-scope-toggle:focus-visible {
  outline: none;
  background: rgba(191, 224, 243, 0.22);
}
.admin-customer-search-scope-toggle[data-scope-state="on"] {
  background: rgba(31, 90, 134, 0.10);
}
.admin-customer-search-scope-switch {
  position: relative;
  width: 30px;
  height: 17px;
  background: #E5DED3;
  border-radius: 999px;
  flex: 0 0 auto;
  transition: background 0.18s ease;
}
.admin-customer-search-scope-switch-knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: #FFFDF9;
  box-shadow: 0 1px 3px rgba(55, 59, 77, 0.18);
  transition: transform 0.18s ease;
}
.admin-customer-search-scope-toggle[data-scope-state="on"] .admin-customer-search-scope-switch {
  background: #1F5A86;
}
.admin-customer-search-scope-toggle[data-scope-state="on"] .admin-customer-search-scope-switch-knob {
  transform: translateX(13px);
}
.admin-customer-search-scope-label {
  font-size: 0.82rem;
  font-weight: 600;
  color: #373B4D;
  letter-spacing: 0.01em;
}
.admin-customer-search-scope-toggle[data-scope-state="on"] .admin-customer-search-scope-label {
  color: #1F5A86;
}
/* Help affordance: question-mark badge with a tooltip. Uses
   data-tooltip + ::after so we don't depend on a third-party tooltip
   lib. Tooltip shows on hover or keyboard focus. */
.admin-customer-search-scope-help {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  color: #7B8190;
  background: #F3EFE7;
  cursor: help;
  transition: background 0.12s ease, color 0.12s ease;
}
.admin-customer-search-scope-help:hover,
.admin-customer-search-scope-help:focus-visible {
  outline: none;
  color: #1F5A86;
  background: #BFE0F3;
}
.admin-customer-search-scope-help::after {
  content: attr(data-tooltip);
  position: absolute;
  top: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%) translateY(-4px);
  width: max-content;
  max-width: 300px;
  padding: 8px 10px;
  background: #373B4D;
  color: #FFFDF9;
  font-size: 0.74rem;
  line-height: 1.35;
  font-weight: 500;
  border-radius: 8px;
  box-shadow: 0 6px 18px rgba(55, 59, 77, 0.18);
  opacity: 0;
  pointer-events: none;
  white-space: normal;
  text-align: left;
  z-index: 50;
  transition: opacity 0.14s ease, transform 0.14s ease;
}
.admin-customer-search-scope-help::before {
  /* Tooltip caret. Same color as the bubble; positioned at the top so
     it visually anchors to the help icon. */
  content: '';
  position: absolute;
  top: calc(100% + 3px);
  left: 50%;
  transform: translateX(-50%) translateY(-4px);
  border: 5px solid transparent;
  border-bottom-color: #373B4D;
  opacity: 0;
  pointer-events: none;
  z-index: 51;
  transition: opacity 0.14s ease, transform 0.14s ease;
}
.admin-customer-search-scope-help:hover::after,
.admin-customer-search-scope-help:focus-visible::after,
.admin-customer-search-scope-help:hover::before,
.admin-customer-search-scope-help:focus-visible::before {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
.admin-customer-search-input-wrap {
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
}
.admin-customer-search-icon {
  position: absolute;
  left: 12px;
  color: var(--text-muted);
  pointer-events: none;
}
.admin-customer-search-input {
  width: 100%;
  /* Bento input: warm card fill (not pure white per philosophy), warm
     soft border, larger 14px radius for soft-tactile feel. */
  padding: 12px 38px 12px 38px;
  border: 1px solid #E5DED3;
  border-radius: 14px;
  background: #FCFAF6;
  font-size: 0.95rem;
  color: #373B4D;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.85) inset;
  transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
}
.admin-customer-search-input::placeholder { color: #7B8190; }
.admin-customer-search-input:hover { background: #FFFDF9; }
.admin-customer-search-input:focus {
  outline: none;
  background: #FFFDF9;
  border-color: #F38A3F;
  box-shadow: 0 0 0 3px rgba(243, 138, 63, 0.18);
}
.admin-customer-search-clear {
  position: absolute;
  right: 8px;
  width: 24px;
  height: 24px;
  border: 0;
  background: transparent;
  color: var(--text-muted);
  border-radius: 50%;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.admin-customer-search-clear:hover {
  background: rgba(39, 41, 54, 0.08);
  color: var(--text);
}
.admin-customer-search-results {
  list-style: none;
  margin: 8px 0 0;
  padding: 6px;
  background: #FCFAF6;
  border: 1px solid #E5DED3;
  border-radius: 18px;
  /* Ambient soft shadow per philosophy — diffused, low-opacity warm
     tint rather than a hard drop shadow. */
  box-shadow: 0 18px 48px rgba(55, 59, 77, 0.10),
              0 2px 6px rgba(55, 59, 77, 0.04),
              0 1px 0 rgba(255, 255, 255, 0.85) inset;
  max-height: 320px;
  overflow-y: auto;
  z-index: 10;
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
}
.admin-customer-search-result {
  padding: 10px 12px;
  border-radius: 10px;
  cursor: pointer;
  transition: background 0.12s;
}
.admin-customer-search-result:hover,
.admin-customer-search-result:focus {
  background: rgba(243, 138, 63, 0.10);
  outline: none;
}
.admin-customer-search-result-main {
  font-size: 0.95rem;
  color: var(--text);
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px;
}
.admin-customer-search-result-main strong { font-weight: 600; }
.admin-customer-search-result-addr { color: var(--text); }
.admin-customer-search-result-meta {
  margin-top: 2px;
  font-size: 0.82rem;
  color: var(--text-muted);
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}
.admin-customer-search-hint {
  margin: 6px 2px 0;
  font-size: 0.82rem;
  color: var(--text-muted);
}

/* ─── Admin location + scope picker ───────────────────────────────
   Bento-style dropdown that replaces both the native <select>
   location switcher and the old segmented "Location / All locations"
   scope toggle. Lives in the admin booking banner row.

   Layout: <details> wrapper with a soft-card <summary> button. The
   <summary>'s "Booking for" label sits above the current location
   name; on click, the menu drops below with the locations list
   (current pinned to top) on top and the cross-location search
   toggle below, separated by a faint divider. The whole thing reads
   as one premium control rather than two unrelated widgets. */
.admin-loc-picker {
  position: relative;
  min-width: 220px;
}
.admin-loc-picker[open] .admin-loc-picker-caret {
  transform: rotate(180deg);
}
.admin-loc-picker-summary {
  /* Single-line pill — value + caret. Used to be a two-line stack
     ("Booking for" eyebrow over the location name) which made this
     control noticeably taller than every other banner pill. Now
     matches the height of .admin-booking-banner-pill and
     .admin-booking-banner-back so the whole header row reads as one
     consistent control strip. */
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 7px 14px;
  min-height: 32px;
  background: #FFFDF9;
  border: 1px solid #E5DED3;
  border-radius: 999px;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.8) inset;
  cursor: pointer;
  list-style: none;
  user-select: none;
  transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
  min-width: 0;
}
.admin-loc-picker-summary::-webkit-details-marker { display: none; }
.admin-loc-picker-summary:hover {
  border-color: rgba(243, 138, 63, 0.45);
  background: #FFFBF3;
}
.admin-loc-picker-summary:focus-visible {
  outline: none;
  border-color: #F38A3F;
  box-shadow: 0 0 0 3px rgba(243, 138, 63, 0.18);
}
.admin-loc-picker[open] .admin-loc-picker-summary {
  border-color: rgba(243, 138, 63, 0.55);
  background: #FFFBF3;
}
.admin-loc-picker-value {
  font-size: 0.92rem;
  font-weight: 700;
  color: #373B4D;
  letter-spacing: -0.005em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 260px;
  line-height: 1.2;
}
.admin-loc-picker-caret {
  color: #7B8190;
  flex: 0 0 auto;
  transition: transform 0.18s ease;
}

/* ── Menu ────────────────────────────────────────────────────── */
.admin-loc-picker-menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 30;
  min-width: 280px;
  max-width: 340px;
  background: #FCFAF6;
  border: 1px solid #E5DED3;
  border-radius: 18px;
  padding: 10px;
  box-shadow: 0 18px 48px rgba(55, 59, 77, 0.14),
              0 2px 6px rgba(55, 59, 77, 0.06),
              0 1px 0 rgba(255, 255, 255, 0.85) inset;
}
.admin-loc-picker-eyebrow {
  font-size: 0.6rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #7B8190;
  margin: 4px 6px 6px;
}
.admin-loc-picker-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.admin-loc-picker-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  background: transparent;
  border: 0;
  border-radius: 10px;
  font: inherit;
  text-align: left;
  cursor: pointer;
  color: #373B4D;
  transition: background 0.12s ease, color 0.12s ease;
}
.admin-loc-picker-item:hover,
.admin-loc-picker-item:focus-visible {
  outline: none;
  background: rgba(243, 138, 63, 0.10);
  color: #373B4D;
}
.admin-loc-picker-item.is-current {
  background: rgba(243, 138, 63, 0.10);
  cursor: default;
}
.admin-loc-picker-item.is-current:hover {
  background: rgba(243, 138, 63, 0.14);
}
.admin-loc-picker-bullet {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 8px;
  background: #F3EFE7;
  color: #373B4D;
  font-size: 0.78rem;
  font-weight: 800;
  flex: 0 0 26px;
}
.admin-loc-picker-item.is-current .admin-loc-picker-bullet {
  background: #F38A3F;
  color: #fff;
}
.admin-loc-picker-item-name {
  flex: 1 1 auto;
  font-size: 0.92rem;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.admin-loc-picker-current-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 9px;
  border-radius: 999px;
  background: rgba(243, 138, 63, 0.18);
  color: #B86012;
  font-size: 0.6rem;
  font-weight: 800;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}
.admin-loc-picker-current-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
}
.admin-loc-picker-arrow {
  color: #7B8190;
  flex: 0 0 auto;
  opacity: 0.6;
  transition: transform 0.15s ease, opacity 0.15s ease, color 0.15s ease;
}
.admin-loc-picker-item:hover .admin-loc-picker-arrow {
  opacity: 1;
  color: #F38A3F;
  transform: translateX(2px);
}

.admin-loc-picker-divider {
  height: 1px;
  background: linear-gradient(to right,
              rgba(229, 222, 211, 0),
              rgba(229, 222, 211, 1) 12%,
              rgba(229, 222, 211, 1) 88%,
              rgba(229, 222, 211, 0));
  margin: 10px 6px 6px;
}

/* ── Scope toggle row (inside menu) ─────────────────────────────
   Reads like an iOS settings row: icon + title + sub-hint + switch
   on the far right. Default state OFF; tap toggles. The same JS
   `scope` variable that the old segmented control wrote to is
   updated here, so search behavior is identical. */
.admin-loc-picker-scope-toggle {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 10px;
  background: transparent;
  border: 0;
  border-radius: 12px;
  font: inherit;
  text-align: left;
  cursor: pointer;
  color: #373B4D;
  transition: background 0.12s ease;
}
.admin-loc-picker-scope-toggle:hover,
.admin-loc-picker-scope-toggle:focus-visible {
  outline: none;
  background: rgba(191, 224, 243, 0.18);
}
.admin-loc-picker-scope-toggle[data-scope-state="on"] {
  background: rgba(191, 224, 243, 0.28);
}
.admin-loc-picker-scope-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 9px;
  background: #BFE0F3;
  color: #1F5A86;
  flex: 0 0 30px;
}
.admin-loc-picker-scope-toggle[data-scope-state="on"] .admin-loc-picker-scope-icon {
  background: #1F5A86;
  color: #fff;
}
.admin-loc-picker-scope-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1 1 auto;
}
.admin-loc-picker-scope-title {
  font-size: 0.88rem;
  font-weight: 700;
  color: #373B4D;
}
.admin-loc-picker-scope-hint {
  font-size: 0.72rem;
  line-height: 1.32;
  color: #7B8190;
}
.admin-loc-picker-scope-switch {
  position: relative;
  width: 32px;
  height: 18px;
  background: #E5DED3;
  border-radius: 999px;
  flex: 0 0 auto;
  transition: background 0.18s ease;
}
.admin-loc-picker-scope-switch-knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #FFFDF9;
  box-shadow: 0 1px 3px rgba(55, 59, 77, 0.18);
  transition: transform 0.18s ease;
}
.admin-loc-picker-scope-toggle[data-scope-state="on"] .admin-loc-picker-scope-switch {
  background: #1F5A86;
}
.admin-loc-picker-scope-toggle[data-scope-state="on"] .admin-loc-picker-scope-switch-knob {
  transform: translateX(14px);
}

/* Inline flag next to the dropdown button in the banner row.
   Hidden by default; surfaces only when the search-scope toggle
   is ON. Tells the admin at a glance that searches will fan out
   across every franchise's HCP account (slower). */
.admin-loc-scope-flag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  background: rgba(191, 224, 243, 0.35);
  color: #1F5A86;
  border: 1px solid rgba(31, 90, 134, 0.18);
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
}
.admin-loc-scope-flag[hidden] { display: none; }
.admin-loc-scope-flag-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #1F5A86;
}

@media (max-width: 600px) {
  .admin-loc-picker-menu {
    min-width: calc(100vw - 48px);
  }
}

/* ─── Cross-location address warning ─────────────────────────
   Renders inside #zone-message when the typed address geocodes to a
   sister franchise's territory in admin mode. The customer must
   either switch to that franchise (deep-link) or force-book the
   current one (which sets bypass_outside_area + records an audit
   override that lands on the HCP job note). */
.cross-loc-warn {
  padding: 18px 20px;
  background: #FCFAF6;
  border: 1px solid rgba(211, 154, 46, 0.32);
  border-left: 4px solid #D39A2E;
  border-radius: 16px;
  color: #373B4D;
  box-shadow: 0 10px 26px rgba(55, 59, 77, 0.05),
              0 1px 0 rgba(255, 255, 255, 0.85) inset;
}
.cross-loc-warn-head {
  display: flex;
  /* flex-start so the warning icon pins to the top of the first
     line of text instead of vertically centering against the whole
     wrapped block (which made the icon look like it was floating
     above the message on narrow widths). */
  align-items: flex-start;
  gap: 9px;
  color: #6F4D14;
  margin-bottom: 6px;
  font-size: 0.95rem;
  font-weight: 700;
  line-height: 1.35;
}
.cross-loc-warn-head svg {
  color: #D39A2E;
  flex: 0 0 16px;
  /* Optical alignment: SVG baseline doesn't match the text cap-line
     by default; nudge down a hair so it visually centers with the
     first line of bolded heading text. */
  margin-top: 2px;
}
.cross-loc-warn-body {
  margin: 0 0 14px;
  font-size: 0.88rem;
  line-height: 1.45;
  color: #7B8190;
}
.cross-loc-warn-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.cross-loc-warn-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 9px 16px;
  border-radius: 999px;
  font-size: 0.86rem;
  font-weight: 700;
  cursor: pointer;
  border: 1px solid transparent;
  font-family: inherit;
  text-decoration: none;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.cross-loc-warn-btn--primary {
  background: #F38A3F;
  color: #fff;
}
.cross-loc-warn-btn--primary:hover {
  background: #E07A2A;
}
.cross-loc-warn-btn--ghost {
  background: #FFFDF9;
  color: #373B4D;
  border-color: #E5DED3;
}
.cross-loc-warn-btn--ghost:hover {
  background: #FFFAF1;
  border-color: rgba(243, 138, 63, 0.35);
}

/* Persistent banner above the day-by-tech grid when the bypass is
   in effect. Smaller, less alarming than the initial warning — just
   a reminder that the admin force-booked. Carries a clear (×) button
   so the admin can drop the override without retyping the address. */
.cross-loc-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: rgba(211, 154, 46, 0.12);
  border: 1px solid rgba(211, 154, 46, 0.32);
  border-radius: 12px;
  margin-bottom: 12px;
  color: #6F4D14;
  font-size: 0.86rem;
  font-weight: 600;
}
.cross-loc-banner svg { flex: 0 0 14px; color: #D39A2E; }
.cross-loc-banner > span { flex: 1 1 auto; }
.cross-loc-banner-clear {
  border: 0;
  background: transparent;
  color: #6F4D14;
  font-size: 1.2rem;
  line-height: 1;
  padding: 4px 9px;
  border-radius: 999px;
  cursor: pointer;
  font-family: inherit;
  transition: background 0.12s ease;
}
.cross-loc-banner-clear:hover { background: rgba(211, 154, 46, 0.22); }

/* ─── Capability-check banner ────────────────────────────────
   Surfaces at the top of the admin booking page when the per-location
   /capabilities probe returns either a hard fail (no HCP key set —
   red error banner) or a soft fail (some endpoints failed — amber
   warn banner). Hidden when everything's healthy. */
.admin-cap-banner {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 16px;
  border-radius: 18px;
  margin-bottom: 14px;
  font-size: 0.9rem;
  line-height: 1.5;
  /* Default warm-card fill so the banner is always legible even if a
     variant class (--error / --warn / --ok) is missing. Variants
     override this with their own tinted fills below. */
  background: #FCFAF6;
  border: 1px solid #E5DED3;
  color: #373B4D;
  box-shadow: 0 12px 32px rgba(55, 59, 77, 0.05),
              0 1px 0 rgba(255, 255, 255, 0.85) inset;
}
/* Honor the HTML `hidden` attribute. Without this, `display: flex`
   above wins over the UA `[hidden] { display: none }` rule, and the
   empty banner shell renders as a floating bar above the search
   input even when JS keeps it semantically hidden. */
.admin-cap-banner[hidden] { display: none; }
.admin-cap-banner svg { flex: 0 0 18px; margin-top: 2px; }
.admin-cap-banner-body { flex: 1 1 auto; min-width: 0; }
.admin-cap-banner-body strong {
  display: block;
  font-size: 0.95rem;
  font-weight: 700;
  margin-bottom: 3px;
  color: inherit;
}
.admin-cap-banner-detail {
  display: block;
  font-size: 0.85rem;
  opacity: 0.92;
}
.admin-cap-banner-action {
  display: inline-flex;
  align-items: center;
  padding: 8px 14px;
  font-size: 0.84rem;
  font-weight: 700;
  text-decoration: none;
  border-radius: 999px;
  white-space: nowrap;
  align-self: center;
  border: 1px solid currentColor;
  transition: background 0.12s ease, color 0.12s ease;
}
.admin-cap-banner-close {
  border: 0;
  background: transparent;
  color: inherit;
  font-size: 1.2rem;
  line-height: 1;
  padding: 4px 8px;
  margin-left: 4px;
  border-radius: 999px;
  cursor: pointer;
  font-family: inherit;
  opacity: 0.7;
  transition: opacity 0.12s ease, background 0.12s ease;
}
.admin-cap-banner-close:hover { opacity: 1; background: rgba(55, 59, 77, 0.06); }

/* Error state — uses the philosophy's unpaid coral (#D65F4F) on the
   unpaid wash (#FCEBE7) rather than the previous harsh red. Still
   reads as serious, but stays within the warm-palette discipline. */
.admin-cap-banner--error {
  background: #FCEBE7;
  border: 1px solid rgba(214, 95, 79, 0.35);
  color: #8A2C1F;
}
.admin-cap-banner--error .admin-cap-banner-action {
  background: #D65F4F;
  color: #fff;
  border-color: #D65F4F;
}
.admin-cap-banner--error .admin-cap-banner-action:hover {
  background: #BA4938;
  border-color: #BA4938;
}
/* Warn state — warning gold (#D39A2E) on a soft warm wash so it
   stands apart from error without competing with it. */
.admin-cap-banner--warn {
  background: rgba(211, 154, 46, 0.12);
  border: 1px solid rgba(211, 154, 46, 0.36);
  color: #6F4D14;
}

/* In the 2-col admin grid this banner needs to span both columns. */
.booking-page--admin > .admin-cap-banner { grid-column: 1 / -1; }

/* ─── Smart suggestions (above the day-by-tech grid) ─────────
   Three at-a-glance cards: Best / Soonest / Previous tech. The
   "Previous tech" card only shows when a customer has been picked
   AND the day's availability includes a tech who's been to that
   customer before. "Best" picks previous-tech if within ~2 days of
   soonest, else falls back to soonest. */
.admin-cal-suggestions {
  margin-bottom: 12px;
}
.admin-cal-suggestions-title {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  font-weight: 700;
  margin: 0 0 8px 2px;
}
.admin-cal-suggestions-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 14px;
}
.suggestion-card {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 14px 16px;
  /* Bento mini-card: warm card-highlight fill (lifts off the rail's
     warm-card surface), soft-warm border, 16px squircle radius, soft
     ambient warm shadow. */
  background: #FFFDF9;
  border: 1px solid #E5DED3;
  border-left: 4px solid #E5DED3;
  border-radius: 16px;
  text-align: left;
  cursor: pointer;
  font-family: inherit;
  color: #373B4D;
  box-shadow: 0 6px 16px rgba(55, 59, 77, 0.04),
              0 1px 0 rgba(255, 255, 255, 0.85) inset;
  transition: background 0.12s ease, border-color 0.12s ease,
              transform 0.06s ease, box-shadow 0.12s ease;
}
.suggestion-card:hover {
  background: #FFFAF1;
  border-color: rgba(243, 138, 63, 0.35);
  box-shadow: 0 10px 22px rgba(55, 59, 77, 0.06),
              0 1px 0 rgba(255, 255, 255, 0.85) inset;
}
.suggestion-card:focus { outline: none; box-shadow: 0 0 0 3px rgba(243, 138, 63, 0.22); }
/* Variant accents stay inside the warm Sunwave palette per philosophy:
     --soonest  → logo navy (calm baseline for "earliest slot")
     --previous → warning gold (continuity nudge)
     --combined → orange (escalated: previous tech AND earliest free at
                   the same slot — the unambiguous best pick) */
.suggestion-card--soonest { border-left-color: #1F5A86; }
.suggestion-card--previous { border-left-color: #D39A2E; }
.suggestion-card--combined { border-left-color: #F38A3F; }
.suggestion-label {
  font-size: 0.64rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-weight: 800;
  color: #7B8190;
}
.suggestion-card--soonest .suggestion-label { color: #1F5A86; }
.suggestion-card--previous .suggestion-label { color: #8A6418; }
.suggestion-card--combined .suggestion-label { color: #B86012; }
.suggestion-when {
  font-size: 1rem;
  font-weight: 800;
  color: #373B4D;
  line-height: 1.2;
  letter-spacing: -0.005em;
  font-variant-numeric: tabular-nums;
}
.suggestion-tech {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
  margin-top: 1px;
}
.suggestion-reason {
  font-size: 0.78rem;
  color: var(--text-muted);
  line-height: 1.35;
  margin-top: 2px;
}

/* ─── Admin booking · day-by-tech calendar ───────────────────
   Replaces the public month-grid calendar in admin mode. Day view:
   rows = slot times, columns = bookable techs. The CSR can see the
   whole day's open / booked picture at a glance and pick a specific
   tech-slot combo instead of letting the auto-assigner choose. */
.admin-cal {
  /* Hero scheduler card: warm card surface with the 22px squircle
     radius the philosophy reserves for major cards. Generous padding +
     soft ambient shadow. */
  background: #FCFAF6;
  border: 1px solid #E5DED3;
  border-radius: 22px;
  padding: 22px 24px 24px;
  box-shadow: 0 14px 36px rgba(55, 59, 77, 0.05),
              0 1px 0 rgba(255, 255, 255, 0.85) inset;
}
/* Stacked header layout: top row holds the centered selected-day
   pill + the Today shortcut anchored right; bottom row holds the
   full-width week strip. The empty spacer on the left mirrors the
   Today pill width so the date pill stays truly centered between
   them without resorting to absolute positioning. */
.admin-cal-header {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 12px;
  margin-bottom: 14px;
}
.admin-cal-top-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
}
.admin-cal-top-spacer {
  /* Same intrinsic width as the Today pill (~78px) so the centered
     pill is truly centered relative to the strip below. Falls back
     to 0 width on narrow screens via the media query at the bottom. */
  flex: 0 0 78px;
  display: inline-block;
}
/* "Open day in HCP" shortcut on the left side of the header. Same
   pill shape as the Today button on the right so the selected-day
   label between them stays centered. Navy accent + external-link
   icon distinguishes it from the orange Today pill — both are
   peer-level affordances but they do different things. */
.admin-cal-open-hcp {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  margin-right: 6px;
  border: 1px solid #E5DED3;
  background: transparent;
  color: #7B8190;
  border-radius: 999px;
  font: inherit;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-decoration: none;
  cursor: pointer;
  transition: border-color 0.14s ease, color 0.14s ease,
              background 0.14s ease, transform 0.12s ease;
}
.admin-cal-open-hcp svg { color: currentColor; flex: 0 0 13px; }
.admin-cal-open-hcp-label {
  white-space: nowrap;
}
.admin-cal-open-hcp:hover {
  border-color: #373B4D;
  color: #373B4D;
  background: rgba(55, 59, 77, 0.05);
}
.admin-cal-open-hcp:active { transform: scale(0.97); }
.admin-cal-open-hcp:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgba(55, 59, 77, 0.18);
}
.admin-cal-strip-row {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
}
/* Selected-day label — clickable pill that opens the native date
   picker. Replaces the old admin-cal-date-trigger; same shape and
   weight so the page rhythm doesn't change. */
.admin-cal-selected-label {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 8px 16px;
  min-height: 36px;
  background: #FFFDF9;
  border: 1px solid #E5DED3;
  border-radius: 999px;
  color: #373B4D;
  cursor: pointer;
  font-family: inherit;
  font-size: 0.98rem;
  font-weight: 700;
  letter-spacing: -0.005em;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.85) inset;
  transition: background 0.12s ease, border-color 0.12s ease,
              color 0.12s ease, transform 0.06s ease;
  white-space: nowrap;
}
.admin-cal-selected-label:hover {
  background: #FFFAF1;
  border-color: rgba(243, 138, 63, 0.35);
}
.admin-cal-selected-label:focus {
  outline: none;
  border-color: #F38A3F;
  box-shadow: 0 0 0 3px rgba(243, 138, 63, 0.22),
              0 1px 0 rgba(255, 255, 255, 0.85) inset;
}
.admin-cal-selected-label:active { transform: scale(0.98); }
.admin-cal-selected-label svg { color: #7B8190; flex: 0 0 14px; }
.admin-cal-selected-label:hover svg { color: #F38A3F; }
.admin-cal-selected-text { font-variant-numeric: tabular-nums; }
/* Selected-label wrapper — gives the date pill a positioning context
   so the custom date-picker popover can absolutely-position directly
   beneath it. Inline-block so the wrapper hugs the pill's natural
   width. Centered in the top row via the surrounding flex container. */
.admin-cal-selected-wrap {
  position: relative;
  display: inline-flex;
  align-items: center;
}
/* ─── Custom date-picker popover ────────────────────────────────
   Drops beneath the selected-day pill. Replaces the native <input
   type="date"> picker, which the browser dropped to the LEFT of
   the trigger and couldn't be styled to fit the warm-canvas
   aesthetic. Philosophy: bento card surface, 18px squircle radius,
   ambient warm-shadow, generous internal padding, KPI hierarchy
   (month title is bold + sized up, day-of-week labels muted and
   tracked, date numbers tabular-nums for stable alignment). */
.admin-cal-datepop {
  position: absolute;
  top: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 20;
  width: 320px;
  padding: 16px 18px 14px;
  background: #FCFAF6;
  border: 1px solid #E5DED3;
  border-radius: 18px;
  box-shadow: 0 18px 40px rgba(55, 59, 77, 0.10),
              0 2px 6px rgba(55, 59, 77, 0.04),
              0 1px 0 rgba(255, 255, 255, 0.85) inset;
  font-family: inherit;
  color: #373B4D;
}
.admin-cal-datepop[hidden] { display: none; }
@media (max-width: 480px) {
  /* On narrow viewports, anchor to the LEFT edge of the wrapper
     instead of centering — otherwise a centered 320px popover
     overflows a tight phone column. */
  .admin-cal-datepop {
    left: 0;
    transform: none;
    width: min(320px, calc(100vw - 32px));
  }
}
.admin-cal-datepop-head {
  display: grid;
  grid-template-columns: 36px 1fr 36px;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}
.admin-cal-datepop-title {
  font-size: 1rem;
  font-weight: 700;
  text-align: center;
  letter-spacing: -0.005em;
  font-variant-numeric: tabular-nums;
}
.admin-cal-datepop-nav {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid #E5DED3;
  background: #FFFDF9;
  color: #373B4D;
  border-radius: 999px;
  cursor: pointer;
  padding: 0;
  transition: background 0.12s ease, border-color 0.12s ease,
              color 0.12s ease, transform 0.08s ease;
}
.admin-cal-datepop-nav:hover:not(:disabled) {
  background: rgba(243, 138, 63, 0.08);
  border-color: rgba(243, 138, 63, 0.45);
  color: #B86012;
}
.admin-cal-datepop-nav:active:not(:disabled) { transform: scale(0.92); }
.admin-cal-datepop-nav:disabled {
  opacity: 0.35;
  cursor: default;
}
.admin-cal-datepop-dow {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 0;
  margin-bottom: 4px;
}
.admin-cal-datepop-dow-cell {
  text-align: center;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.10em;
  color: #8A8475;
  padding: 6px 0;
  text-transform: uppercase;
}
.admin-cal-datepop-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
}
.admin-cal-datepop-cell {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  background: transparent;
  border: 0;
  border-radius: 10px;
  font-family: inherit;
  font-size: 0.92rem;
  font-weight: 600;
  color: #373B4D;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  padding: 0;
  position: relative;
  transition: background 0.12s ease, color 0.12s ease,
              transform 0.06s ease;
}
.admin-cal-datepop-cell:hover {
  background: rgba(243, 138, 63, 0.10);
  color: #B86012;
}
.admin-cal-datepop-cell:active { transform: scale(0.94); }
/* Other-month cells (spill-over from prev/next month). Muted so the
   eye locks onto the current month without losing the surrounding
   context. */
.admin-cal-datepop-cell--other-month {
  color: #C9C2B0;
  font-weight: 500;
}
.admin-cal-datepop-cell--other-month:hover { color: #B86012; }
/* Out-of-window (outside the bookable horizon). Still clickable so
   the admin can scrub into a fully-booked future date if they want
   to peek; click routes through showOutOfRangeDay() which surfaces
   the "fully booked" warn band. */
.admin-cal-datepop-cell--out-of-window {
  color: #B7BBC7;
  font-weight: 500;
}
.admin-cal-datepop-cell--out-of-window:hover { color: #B86012; }
/* Today — small orange dot under the number. Matches the strip's
   today indicator so the two surfaces read as one visual system. */
.admin-cal-datepop-cell--today::after {
  content: '';
  position: absolute;
  bottom: 5px;
  left: 50%;
  transform: translateX(-50%);
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: #F38A3F;
}
/* Selected — solid orange pill behind the number. Highest-emphasis
   state in the picker; the orange brand color carries the "this is
   the day I'm scheduling for" meaning everywhere else in the app. */
.admin-cal-datepop-cell--selected {
  background: #F38A3F;
  color: #FFFFFF;
  font-weight: 700;
}
.admin-cal-datepop-cell--selected:hover {
  background: #E37A2F;
  color: #FFFFFF;
}
.admin-cal-datepop-cell--selected.admin-cal-datepop-cell--today::after {
  background: #FFFFFF;
}
.admin-cal-datepop-foot {
  display: flex;
  justify-content: flex-end;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid #ECE5D6;
}
.admin-cal-datepop-today {
  background: transparent;
  border: 1px solid #E5DED3;
  color: #373B4D;
  padding: 6px 14px;
  border-radius: 999px;
  font-family: inherit;
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease,
              color 0.12s ease, transform 0.08s ease;
}
.admin-cal-datepop-today:hover {
  background: rgba(243, 138, 63, 0.08);
  border-color: rgba(243, 138, 63, 0.45);
  color: #B86012;
}
.admin-cal-datepop-today:active { transform: scale(0.96); }
/* Strip — no container box. Floats inside the calendar card so it
   reads as a clean Sun-Sat week ledger instead of a separate widget.
   Arrows + 7 cells + arrows sit in a single flex row that spans the
   available width so each cell gets generous breathing room. */
.admin-cal-strip-wrap {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 auto;
  min-width: 0;
  /* Wider than the previous pass — the strip now owns its own row so
     it can stretch across the full calendar card. Capped at 640px so
     each cell stays a readable tap target without ballooning on
     ultra-wide displays. */
  max-width: 640px;
  background: transparent;
  border: 0;
  padding: 0;
}
.admin-cal-strip-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  flex: 0 0 28px;
  background: transparent;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  color: #7B8190;
  transition: background 0.12s ease, color 0.12s ease, transform 0.06s ease;
}
.admin-cal-strip-arrow:hover:not(:disabled) {
  background: rgba(243, 138, 63, 0.08);
  color: #F38A3F;
}
.admin-cal-strip-arrow:active:not(:disabled) { transform: scale(0.92); }
.admin-cal-strip-arrow:disabled { opacity: 0.25; cursor: default; }
.admin-cal-strip {
  display: flex;
  align-items: stretch;
  justify-content: space-between;
  list-style: none;
  margin: 0;
  padding: 0;
  flex: 1 1 auto;
  min-width: 0;
}
/* Each day cell — equal flex share so the strip fills its row,
   with day-of-week initial on top, date number, and an availability
   dot beneath. Cells are tappable list items (no real <button>) so
   selected + today states layer cleanly. Wider than the previous
   pass per the design feedback. */
.admin-cal-strip-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 2px;
  flex: 1 1 0;
  padding: 6px 4px 10px;
  border-radius: 10px;
  cursor: pointer;
  position: relative;
  color: #373B4D;
  transition: background 0.12s ease, color 0.12s ease;
}
.admin-cal-strip-cell:hover { background: rgba(243, 138, 63, 0.05); }
.admin-cal-strip-dow {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: #7B8190;
  text-transform: uppercase;
  line-height: 1.1;
}
.admin-cal-strip-date {
  font-size: 1.05rem;
  font-weight: 700;
  color: #373B4D;
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
  position: relative;
}
/* Availability dot — historically green when the day had open
   slots; removed per design pass. Availability is now signaled by
   the date number color (dark = bookable, gray = fully booked /
   out-of-range). The element is still rendered but kept invisible
   so today's marker can still light up without shifting layout. */
.admin-cal-strip-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: transparent;
  margin-top: 4px;
}
/* Selected day — thin orange underline beneath the date number
   instead of a full pill/box. Reads as "this is the day I'm
   scheduling for" without the heavy chrome of the prior pill,
   matching the "floats inside the box" design direction. */
.admin-cal-strip-cell.is-selected .admin-cal-strip-date::before {
  content: '';
  position: absolute;
  left: 50%;
  bottom: -3px;
  transform: translateX(-50%);
  width: 22px;
  height: 2.5px;
  border-radius: 999px;
  background: #F38A3F;
}
.admin-cal-strip-cell.is-selected .admin-cal-strip-date,
.admin-cal-strip-cell.is-selected .admin-cal-strip-dow { color: #373B4D; }
.admin-cal-strip-cell.is-selected .admin-cal-strip-dow { color: #B86012; }
/* Today marker — small orange dot under the date number. Stays
   even when today is fully booked, so the strip always anchors
   visually around "now." */
.admin-cal-strip-cell.is-today .admin-cal-strip-dot {
  background: #F38A3F;
}
/* Fully-booked + out-of-range days — date number washes out to gray
   to signal "you can click, but there's nothing to schedule here."
   Cells stay clickable so the admin gets a clear "fully booked"
   confirmation in the warn band instead of a silent no-op. */
.admin-cal-strip-cell.is-empty .admin-cal-strip-date,
.admin-cal-strip-cell.is-out-of-range .admin-cal-strip-date {
  color: #B7BBC7;
}
.admin-cal-strip-cell.is-empty .admin-cal-strip-dow,
.admin-cal-strip-cell.is-out-of-range .admin-cal-strip-dow {
  color: #B7BBC7;
}
/* Selected wins over gray — if the admin actually clicks into a
   fully-booked day, the date should pop back to navy so it reads
   as "this is the day I'm looking at, it's just empty." */
.admin-cal-strip-cell.is-selected.is-empty .admin-cal-strip-date,
.admin-cal-strip-cell.is-selected.is-out-of-range .admin-cal-strip-date {
  color: #373B4D;
}
/* Snap-to-today pill. Three states:
   - is-current (default when admin lands on today): muted, no
     hover affordance — already there.
   - is-active (off-today, within window): orange-tinted, signals
     "click me to come back to now."
   - :disabled (today is outside the bookable window): faded out. */
.admin-cal-today {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  margin-left: 6px;
  border: 1px solid #E5DED3;
  background: transparent;
  color: #7B8190;
  border-radius: 999px;
  font: inherit;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: border-color 0.14s ease, color 0.14s ease,
              background 0.14s ease, transform 0.12s ease;
}
.admin-cal-today svg { color: currentColor; flex: 0 0 13px; }
/* Label inside the Today pill. Width fluctuates as the admin moves
   off-today ("Today" → "+5 days" → "+12 days"); tabular-nums keeps
   the digits from jittering and nowrap stops the pill from wrapping
   when the delta hits two-digit territory. */
.admin-cal-today-label {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.admin-cal-today:hover:not(:disabled) {
  border-color: #F38A3F;
  color: #B86012;
  background: rgba(243, 138, 63, 0.06);
}
.admin-cal-today:active:not(:disabled) { transform: scale(0.97); }
.admin-cal-today:disabled { opacity: 0.4; cursor: default; }
/* Active state — admin is on a different day. Pill takes on a soft
   orange wash + colored border to draw the eye. Subtle pulse on the
   clock icon hints "now is moving along without you." */
.admin-cal-today.is-active {
  border-color: rgba(243, 138, 63, 0.55);
  color: #B86012;
  background: rgba(243, 138, 63, 0.10);
}
.admin-cal-today.is-active svg { animation: todayPulse 2.4s ease-in-out infinite; }
@keyframes todayPulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}
/* Current state — viewing today. Pill stays present (so the admin
   knows their reference point) but reads as quiet/inactive. */
.admin-cal-today.is-current {
  color: #7B8190;
  background: transparent;
  border-color: #E5DED3;
}
/* Date trigger — the entire date label is the clickable surface
   that opens the native date picker (calendar.js wires showPicker()
   on click). Reads as a quiet pill: warm card bg + soft border + the
   day label inside. Icon hints "calendar" so admins know it's
   interactive even on a touch device where hover doesn't fire. */
.admin-cal-date-trigger {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 8px 16px;
  min-height: 36px;
  background: #FFFDF9;
  border: 1px solid #E5DED3;
  border-radius: 999px;
  color: #373B4D;
  cursor: pointer;
  font-family: inherit;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.85) inset;
  transition: background 0.12s ease, border-color 0.12s ease,
              color 0.12s ease, transform 0.06s ease;
}
.admin-cal-date-trigger:hover {
  background: #FFFAF1;
  border-color: rgba(243, 138, 63, 0.35);
  color: #373B4D;
}
.admin-cal-date-trigger:focus {
  outline: none;
  border-color: #F38A3F;
  box-shadow: 0 0 0 3px rgba(243, 138, 63, 0.22),
              0 1px 0 rgba(255, 255, 255, 0.85) inset;
}
.admin-cal-date-trigger:active { transform: scale(0.98); }
.admin-cal-date-trigger-icon {
  color: #7B8190;
  flex: 0 0 14px;
}
.admin-cal-date-trigger:hover .admin-cal-date-trigger-icon { color: #F38A3F; }
.admin-cal-date-label {
  font-size: 0.98rem;
  font-weight: 700;
  color: #373B4D;
  letter-spacing: -0.005em;
  font-variant-numeric: tabular-nums;
}
/* Native date input is offscreen — only used to launch the picker
   via showPicker() and to receive the change event. The trigger
   button above is the visible affordance. */
.admin-cal-date-picker {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
  border: 0;
  clip: rect(0 0 0 0);
}

/* Day-grid meter — hero-scale KPI tiles per philosophy doc:
   "the most important piece of data in a card... must be massive.
   It should be the heaviest font weight available and scaled to
   dominate the card's real estate." Numbers bumped from 1.25rem to
   1.55rem and labels tightened so each stat reads as an anchor for
   the eye, matching .acc-name / .empty-slots-kpi-value scale. */
.admin-cal-meter {
  display: flex;
  flex-wrap: wrap;
  gap: 22px 36px;
  padding: 18px 22px;
  background: #FFFDF9;
  border: 1px solid #E5DED3;
  border-radius: 16px;
  margin-bottom: 14px;
}
.admin-cal-meter .meter-stat { display: flex; flex-direction: column; gap: 4px; }
.admin-cal-meter .meter-num {
  font-size: 1.55rem;
  font-weight: 800;
  color: #373B4D;
  line-height: 1.05;
  letter-spacing: -0.015em;
  font-variant-numeric: tabular-nums;
}
.admin-cal-meter .meter-label {
  font-size: 0.68rem;
  color: #7B8190;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-weight: 800;
}

.admin-cal-warn {
  padding: 10px 14px;
  background: rgba(211, 154, 46, 0.12);
  border: 1px solid rgba(211, 154, 46, 0.32);
  color: #6F4D14;
  font-size: 0.84rem;
  border-radius: 12px;
  margin-bottom: 12px;
}

.admin-cal-grid-wrap {
  /* Horizontal scroll is no longer the failure mode — adding techs
     extends the grid downward (vertical scroll, which the page
     already handles). Keeping overflow-x: auto as a safety net for
     very narrow viewports / many time slots, but in normal use it
     never triggers. */
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.admin-cal-grid {
  display: grid;
  /* Flipped axes: first column = tech label (wider, fits "Firstname
     Lastname"), remaining columns = time slots (fixed, small set).
     --num-slots is set by the JS render (typically 3-4) so the grid
     hugs the slot count and doesn't reserve empty columns. */
  grid-template-columns: 168px repeat(var(--num-slots, 1), minmax(120px, 1fr));
  gap: 4px;
  min-width: max-content;
}
.admin-cal-corner {
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: #7B8190;
  font-weight: 800;
  padding: 8px 12px;
  align-self: end;
}
/* Tech rows — name label sits in the LEFT column of each tech's row.
   Now reads as a row leader (left-aligned, vertical name + last name)
   rather than the old column header treatment. Background still warm
   so the row label stands off the bare grid cells to its right. */
.admin-cal-tech-head {
  padding: 12px 14px;
  text-align: left;
  background: #F3EFE7;
  border-radius: 12px;
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto auto;
  align-items: center;
  column-gap: 10px;
  row-gap: 4px;
}
/* Colored avatar circle — left-anchored ID for each tech. Stable
   color per id (calendar.js generates it via a hash → palette pick),
   set inline as `background` on the element. White initials sit
   against the color with a thin warm-canvas ring so the bubble
   reads as a physical object on the warm card background, not as a
   flat color block. Spans both rows of the head's grid so it stays
   centered next to the name + badges stack. Philosophy: "left-
   aligned primary identifying element (an avatar, a colorful
   circular icon)". */
.admin-cal-tech-avatar {
  grid-row: 1 / span 2;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.78rem;
  font-weight: 800;
  letter-spacing: 0.02em;
  color: #fff;
  /* Inner highlight + outer halo simulate the bento "ambient glow"
     on a small surface — keeps the bubble from feeling flat-printed. */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18),
              0 1px 0 rgba(255, 255, 255, 0.9),
              0 4px 10px rgba(55, 59, 77, 0.10);
  /* Faint warm-canvas ring keeps the color from bleeding into the
     warm tech-head background — same trick used on stacked avatars. */
  border: 2px solid #FCFAF6;
  flex: 0 0 34px;
  user-select: none;
}
.admin-cal-tech-name {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.admin-cal-tech-badges {
  grid-column: 2;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  align-items: center;
}
/* Click-to-jump variant — applied when the tech has at least one
   open slot in the visible horizon. Cursor + hover treatment hint
   "this name is a control"; click jumps the calendar to that tech's
   soonest opening and selects the cell. */
.admin-cal-tech-head--clickable {
  cursor: pointer;
  transition: background 0.14s ease, transform 0.12s ease,
              box-shadow 0.14s ease;
}
.admin-cal-tech-head--clickable:hover {
  background: #ECE5D6;
  box-shadow: 0 2px 8px rgba(60, 42, 16, 0.06);
}
.admin-cal-tech-head--clickable:active { transform: scale(0.99); }
.admin-cal-tech-head--clickable:focus-visible {
  outline: 2px solid #F38A3F;
  outline-offset: 2px;
}
/* No-availability variant — applied when the tech has zero open
   slots in the horizon. Muted so the admin sees at a glance that
   clicking won't help; tooltip on the element explains. */
.admin-cal-tech-head--no-avail {
  opacity: 0.55;
  cursor: not-allowed;
}
/* Previous-tech column: this tech has been to the picked customer's
   house before. Warning-gold accent now (philosophy palette), mirrors
   .suggestion-card--previous so the day grid and the suggestion strip
   read as one visual system. The badge sits at the bottom of the
   tech head; cells in the column get a thin left rail of the same
   hue and a pale gold fill on open slots. */
.admin-cal-tech-head--prev {
  background: rgba(211, 154, 46, 0.14);
}
.admin-cal-tech-prev-badge {
  display: inline-block;
  margin-top: 5px;
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: #8A6418;
  background: rgba(211, 154, 46, 0.22);
  padding: 2px 9px;
  border-radius: 999px;
}
/* Visit count tucked inside the PREVIOUS pill. Slightly less
   contrast than the "Previous" word so the eye still leads with
   the label; the count is supporting evidence. */
.admin-cal-tech-prev-count {
  margin-left: 1px;
  opacity: 0.78;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
}
/* "Last visit" badge — flags the single most-recent tech to have
   been to this customer. Sits next to the Previous pill on the
   tech-head, distinct orange tint so it doesn't visually merge
   with the gold Previous family. Only one tech in the grid carries
   this badge at a time. */
.admin-cal-tech-last-badge {
  display: inline-block;
  margin-top: 5px;
  margin-left: 4px;
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: #B86012;
  background: rgba(243, 138, 63, 0.16);
  padding: 2px 9px;
  border-radius: 999px;
}
/* Subtle reinforcement on the last-tech ROW: a hair more vibrant
   gold than a generic previous row so the eye picks it out of the
   "previous techs" cluster without needing to read the badge. */
.admin-cal-tech-head--last {
  background: rgba(243, 138, 63, 0.10);
}
/* Previous-tech ROW highlight. Every cell in this tech's row gets a
   gold tint regardless of busy/open state so the whole row reads
   as "this is the tech who's been to this customer's house before."
   Pairs with the .admin-cal-tech-head--prev row label (gold bg +
   "Previous" badge) and the legend pill at the top of the calendar. */
.admin-cal-cell--prev {
  background: rgba(211, 154, 46, 0.07);
  border-color: rgba(211, 154, 46, 0.22);
}
.admin-cal-cell--prev.admin-cal-cell--open {
  background: rgba(211, 154, 46, 0.16);
  border-color: rgba(211, 154, 46, 0.42);
  color: #6F4D14;
}
.admin-cal-cell--prev.admin-cal-cell--open:hover {
  background: rgba(211, 154, 46, 0.26);
  border-color: rgba(211, 154, 46, 0.55);
}
.admin-cal-cell--prev.is-selected {
  /* Selected state still wins — orange override above the gold tint. */
  box-shadow: 0 2px 8px rgba(243, 138, 63, 0.32);
}
/* Soonest-availability slot highlight. Applied to OPEN cells in the
   earliest time slot of the day where any visible tech is free.
   Uses the Sunwave light-blue (#BFE0F3) calm support accent per
   the philosophy doc so it complements the orange (selection) and
   gold (previous tech) without competing. */
.admin-cal-cell--soonest.admin-cal-cell--open {
  background: rgba(31, 90, 134, 0.10);
  border-color: rgba(31, 90, 134, 0.32);
  color: #1F5A86;
}
.admin-cal-cell--soonest.admin-cal-cell--open:hover {
  background: rgba(31, 90, 134, 0.18);
  border-color: rgba(31, 90, 134, 0.45);
}
/* If a cell is BOTH soonest AND in the previous-tech row, the
   soonest blue wins — that's the strongest recommendation and
   should read clearly. */
.admin-cal-cell--soonest.admin-cal-cell--prev.admin-cal-cell--open {
  background: rgba(31, 90, 134, 0.10);
  border-color: rgba(31, 90, 134, 0.32);
  color: #1F5A86;
}
/* Legend pill row above the grid — describes the in-grid color
   highlights. Pills are non-interactive (just a key), small, and
   only render when their highlight is actually present on this
   day (calendar.js filters the list). */
.admin-cal-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 12px;
}
.admin-cal-legend[hidden] { display: none; }
.admin-cal-legend-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 12px;
  background: #FFFDF9;
  border: 1px solid #E5DED3;
  border-radius: 999px;
  font: inherit;
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: #373B4D;
  white-space: nowrap;
}
/* Clickable variant — renders as a <button>. Cursor pointer +
   subtle hover wash + active press feedback so the pill reads as
   "I can click this." */
button.admin-cal-legend-pill {
  cursor: pointer;
  transition: background 0.14s ease, border-color 0.14s ease,
              transform 0.08s ease, box-shadow 0.14s ease;
}
button.admin-cal-legend-pill:hover {
  background: #FFFAF1;
  border-color: rgba(243, 138, 63, 0.40);
  color: #B86012;
  box-shadow: 0 2px 8px rgba(60, 42, 16, 0.06);
}
button.admin-cal-legend-pill:hover .admin-cal-legend-dot {
  box-shadow: 0 0 0 3px rgba(243, 138, 63, 0.12);
}
button.admin-cal-legend-pill:active { transform: scale(0.97); }
button.admin-cal-legend-pill:focus-visible {
  outline: 2px solid #F38A3F;
  outline-offset: 2px;
}
/* Empty-state variant — rendered when there's nothing to jump to
   (no soonest slot in the visible window / no previous tech for
   the picked customer). Muted so the pill reads as a "this is
   what would be here" placeholder, not an actionable control. */
.admin-cal-legend-pill.is-empty {
  background: #F3EFE7;
  color: #8A8475;
  border-color: rgba(229, 222, 211, 0.7);
  cursor: not-allowed;
  opacity: 0.7;
}
.admin-cal-legend-pill.is-empty .admin-cal-legend-dot {
  opacity: 0.55;
}
.admin-cal-legend-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex: 0 0 9px;
}
.admin-cal-legend-pill--soonest .admin-cal-legend-dot {
  background: #1F5A86;
}
.admin-cal-legend-pill--prev .admin-cal-legend-dot {
  background: #D39A2E;
}
.admin-cal-tech-head strong {
  display: block;
  font-size: 0.98rem;
  font-weight: 800;
  color: #373B4D;
  line-height: 1.2;
  letter-spacing: -0.005em;
}
.admin-cal-tech-head small {
  display: block;
  font-size: 0.74rem;
  color: #7B8190;
  margin-top: 3px;
  font-weight: 600;
  letter-spacing: 0.02em;
}
.admin-cal-time-head {
  /* Now a top-row COLUMN header (was a left-column row label before
     the axis flip). Centered text, slot label in tabular nums, sits
     above each slot column. Bolder than the old side-label
     treatment so it reads as a real header for the column under it. */
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.82rem;
  font-weight: 800;
  color: #373B4D;
  padding: 10px 10px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  text-align: center;
  letter-spacing: 0.02em;
}
.admin-cal-cell {
  border: 1px solid transparent;
  border-radius: 10px;
  padding: 11px 6px;
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
  font-family: inherit;
  text-align: center;
  font-variant-numeric: tabular-nums;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease, transform 0.06s ease;
}
.admin-cal-cell:focus { outline: none; box-shadow: 0 0 0 3px rgba(243, 138, 63, 0.22); }
.admin-cal-cell--open {
  /* Positive-green wash on warm canvas — same green family as the
     paid-invoice pill, so "open slot" and "paid" both read as good
     things at a glance. */
  background: rgba(75, 170, 120, 0.10);
  color: #2E7D4F;
  border-color: rgba(75, 170, 120, 0.22);
}
.admin-cal-cell--open:hover {
  background: rgba(75, 170, 120, 0.18);
  border-color: rgba(75, 170, 120, 0.38);
}
.admin-cal-cell--busy {
  background: #F3EFE7;
  color: rgba(55, 59, 77, 0.32);
  border-color: transparent;
  cursor: not-allowed;
}
.admin-cal-cell--busy.admin-cal-cell--overrideable {
  cursor: pointer;
}
.admin-cal-cell--busy.admin-cal-cell--overrideable:hover {
  background: rgba(243, 138, 63, 0.10);
  color: #C4631F;
  border-color: rgba(243, 138, 63, 0.24);
}
.admin-cal-cell.is-selected {
  background: #F38A3F;
  color: #fff;
  border-color: #F38A3F;
  box-shadow: 0 4px 14px rgba(243, 138, 63, 0.32);
}
.admin-cal-cell.is-selected.is-override {
  background: #C4631F;
  border-color: #C4631F;
}
.admin-cal-cell.is-selected:hover {
  background: #E07A2A;
}

/* ─── Multi-slot booking states ─────────────────────────────────
   Cells that are individually open but don't fit a multi-slot run
   ("Short"): legible-but-demoted, cursor not-allowed so admins can't
   accidentally click a slot lacking consecutive coverage. */
.admin-cal-cell--short {
  background: rgba(75, 170, 120, 0.04);
  color: rgba(46, 125, 79, 0.50);
  border-color: rgba(75, 170, 120, 0.18);
  cursor: not-allowed;
}
.admin-cal-cell--short:hover {
  background: rgba(75, 170, 120, 0.07);
  border-color: rgba(75, 170, 120, 0.28);
}
/* Reject shake — bounces the cell back when the admin clicks a slot
   that lacks consecutive coverage. Short, single-axis horizontal
   shake (≤4px). Self-removes after the keyframe so the cell goes
   back to its short-state appearance. */
@keyframes adminCalRejectShake {
  0%, 100% { transform: translateX(0); }
  20%      { transform: translateX(-3px); }
  40%      { transform: translateX(3px); }
  60%      { transform: translateX(-2px); }
  80%      { transform: translateX(2px); }
}
.admin-cal-cell--reject-shake {
  animation: adminCalRejectShake 0.42s ease;
}
@media (prefers-reduced-motion: reduce) {
  .admin-cal-cell--reject-shake { animation: none; }
}
/* Hover preview band for multi-slot runs. Lights the N cells that
   would be reserved as the admin hovers a valid start cell. Softer
   than .is-selected so the difference between "previewing" and
   "actually picked" reads clearly. Combined with the bracket-like
   border treatment via a tinted backdrop instead of an outline so
   stacked cells in the run merge visually. */
.admin-cal-cell--run-preview {
  background: rgba(243, 138, 63, 0.14) !important;
  border-color: rgba(243, 138, 63, 0.38) !important;
  color: #C4631F !important;
}
.admin-cal-cell--run-preview:hover {
  background: rgba(243, 138, 63, 0.22) !important;
}
/* Trail cells in a selected multi-slot run. The CLICKED cell uses
   the standard .is-selected orange + pulse; trail cells share the
   selected fill but get a slight ribbon-style left border so the
   group reads as one continuous reservation rather than separate
   selections. The bookend-style left border on each trail visually
   stitches them into a band. */
.admin-cal-cell--run-trail {
  position: relative;
}
.admin-cal-cell--run-trail::before {
  content: '';
  position: absolute;
  left: -4px;
  top: 50%;
  width: 7px;
  height: 2px;
  background: #F38A3F;
  transform: translateY(-50%);
  border-radius: 2px;
  pointer-events: none;
}

/* Job-length picker row above the calendar grid. Inline label +
   small select + optional hint chip. Sits between the warn banner
   and the legend pill row so it's clearly tied to "what kind of
   job we're booking." */
.admin-cal-length-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
  padding: 6px 8px;
  background: #FCFAF6;
  border: 1px solid #ECE5D6;
  border-radius: 10px;
  flex-wrap: wrap;
}
.admin-cal-length-label {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #8A8475;
  margin: 0;
}
.admin-cal-length-select {
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  color: #373B4D;
  padding: 5px 28px 5px 10px;
  background: #FFFFFF;
  border: 1px solid #DDD3BA;
  border-radius: 8px;
  cursor: pointer;
  appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6' fill='none' stroke='%237A7466' stroke-width='1.6'><path d='M1 1l4 4 4-4'/></svg>");
  background-repeat: no-repeat;
  background-position: right 9px center;
  background-size: 9px 5px;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
.admin-cal-length-select:hover { border-color: #C8BDA0; }
.admin-cal-length-select:focus-visible {
  outline: none;
  border-color: #F38A3F;
  box-shadow: 0 0 0 3px rgba(243, 138, 63, 0.18);
}
.admin-cal-length-hint {
  font-size: 0.72rem;
  font-weight: 600;
  color: #7A7466;
  background: #F2EAD3;
  padding: 3px 9px;
  border-radius: 999px;
}
.admin-cal-length-hint[hidden] { display: none; }

/* ─── Drive-time annotation on admin cells ───────────────────────
   Small text that appears at the bottom of each OPEN cell once the
   /drive-times overlay resolves. Sits inside the cell in normal flow,
   stacked under the existing "Open" label. The cell becomes flex
   column so the label centers and the drive line tucks under it with
   a hairline of gap. Only present after the lazy fetch lands — until
   then the cell is exactly as before. */
.admin-cal-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
}
.admin-cal-cell-drive {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.6rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: none;
  color: #7A7466;
  background: rgba(255, 255, 255, 0.55);
  padding: 1px 7px;
  border-radius: 999px;
  line-height: 1.3;
  white-space: nowrap;
  /* Subtle fade-in so the overlay arrival doesn't feel jumpy. The
     0.18s matches the grid cell stagger ramp at the top of renderDay
     so the drive-time line feels like part of the same paint. */
  animation: adminCalDriveFade 0.25s ease both;
}
@keyframes adminCalDriveFade {
  from { opacity: 0; transform: translateY(2px); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .admin-cal-cell-drive { animation: none; }
}
/* Quieter variant for techs whose first job of the day IS this slot.
   No driving cost, but admins still benefit from knowing the tech
   hasn't started yet (vs. "missing" annotation which could mean
   anything). Italic + lower contrast so it reads as a side-note. */
.admin-cal-cell-drive--first {
  color: #9A9485;
  font-style: italic;
  background: transparent;
}
/* Shortest-drive winner at a given slot time. Sage-green pill so the
   admin can scan a slot row and pick the tech with minimum routing
   impact. Only applied when ≥2 techs are competing for that slot. */
.admin-cal-cell-drive--shortest {
  background: #DDEFE3;
  color: #1F6A41;
}
/* Selected cell flips drive-time to a translucent white-on-orange
   pill so it stays legible against the bright orange selected
   background. Otherwise the muted gray vanishes. */
.admin-cal-cell.is-selected .admin-cal-cell-drive {
  background: rgba(255, 255, 255, 0.22);
  color: rgba(255, 255, 255, 0.9);
}
.admin-cal-cell.is-selected .admin-cal-cell-drive--shortest {
  background: rgba(255, 255, 255, 0.32);
  color: #fff;
}
/* Loading placeholder — three tiny dots that pulse in sequence while
   the /drive-times fetch is in flight. Minimum visual weight: only
   ~10px wide so it doesn't shift the grid much; replaced inline once
   the response arrives. */
.admin-cal-cell-drive--loading {
  padding: 3px 7px;
  background: transparent;
  gap: 3px;
}
.admin-cal-cell-drive--loading > span {
  width: 3px;
  height: 3px;
  background: #BBB3A0;
  border-radius: 50%;
  animation: adminCalDriveDots 1.2s ease-in-out infinite;
}
.admin-cal-cell-drive--loading > span:nth-child(2) { animation-delay: 0.15s; }
.admin-cal-cell-drive--loading > span:nth-child(3) { animation-delay: 0.3s; }
@keyframes adminCalDriveDots {
  0%, 80%, 100% { opacity: 0.25; transform: scale(0.8); }
  40%           { opacity: 1;    transform: scale(1.1); }
}
@media (prefers-reduced-motion: reduce) {
  .admin-cal-cell-drive--loading > span { animation: none; opacity: 0.5; }
}

/* ─── Admin booking page · 2-col left-rail layout ──────────────
   Activated by the booking-page--admin class on <main>. Banner spans
   both columns at the top; customer-history rail occupies column 1;
   the form occupies column 2. Collapses to single-column under 960px
   so phone-booked-by-CSR-on-a-tablet still works. */
.booking-page--admin {
  width: 100%;
  max-width: 1680px;
  display: grid;
  grid-template-columns: minmax(300px, 360px) minmax(760px, 1fr);
  /* Tighter horizontal gap between the customer rail and the form
     column, plus a tighter vertical gap between the full-width banner
     row and the rail+form row. Was 28px; the mockup landed closer to
     20px and the page reads denser without feeling crowded. */
  gap: 20px;
  align-items: start;
}
.booking-page--admin > .alert,
.booking-page--admin > .admin-booking-banner { grid-column: 1 / -1; }
/* Left rail wrapper — stacks the customer rail + keyboard shortcuts
   card with a small gap, so the card sits directly under the
   customer rail without inheriting the grid's row 3 placement
   (which would push it down to align with the bottom of the form). */
.booking-page--admin > .admin-rail-column {
  grid-column: 1;
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
  /* Stretch the rail column to the same height as the form column so
     the recent-bookings card at the bottom can grow to fill remaining
     vertical space (its own list scrolls internally). Without this,
     the rail would only be as tall as its natural content and the
     recent-bookings card would sit short, leaving a big void next to
     the scheduler. */
  align-self: stretch;
  min-height: 0;
}
.booking-page--admin > form#booking-form { grid-column: 2; }
@media (max-width: 1180px) {
  .booking-page--admin {
    grid-template-columns: minmax(280px, 320px) minmax(0, 1fr);
    gap: 22px;
  }
}
@media (max-width: 960px) {
  .booking-page--admin {
    grid-template-columns: 1fr;
    max-width: 100%;
  }
  .booking-page--admin > .admin-rail-column { grid-column: 1; }
  .booking-page--admin > form#booking-form { grid-column: 1; }
}

/* ─── Customer-context panel ────────────────────────────────────
   Lives as the left rail in admin mode (booking-page--admin). Sticky
   so it stays visible as the admin scrolls through the form below.
   Populated by booking.js from
   /admin/api/booking/:slug/customer/:id. Shows empty state until a
   customer is picked, then swaps to profile + stat row +
   recent-jobs + recent-invoices. Invoices section degrades gracefully
   when the location's HCP plan doesn't expose /invoices. */
.admin-customer-context {
  /* Hero bento card on the rail: warm card surface, large 22px
     squircle radius, soft-warm border instead of cool gray, ambient
     soft shadow with warm undertone. Padding bumped from 18/20 to 24
     per the philosophy's "oceans of whitespace" rule for major
     cards. */
  background: #FCFAF6;
  border: 1px solid #E5DED3;
  border-radius: 22px;
  /* Was 24px on every side. The rail reads denser in the mockup with
     ~18px padding — the inner blocks (head, stats, notes, history)
     have their own internal margins so the rail wrapper doesn't need
     this much of its own. */
  padding: 18px;
  box-shadow: 0 14px 36px rgba(55, 59, 77, 0.05),
              0 1px 0 rgba(255, 255, 255, 0.85) inset;
}
.admin-customer-context--rail {
  /* No inner scroll. The rail flows naturally with the page now —
     previously this had `position: sticky; max-height: calc(100vh
     - 32px); overflow-y: auto;` which made the rail a separate
     scrolling container, which read as funky next to the page's
     own scroll. The form column is almost always taller than the
     rail, so dropping the sticky behavior costs nothing in the
     common case, and tall histories now just extend the rail
     downward into the available page space. */
}

/* Empty state shown until a customer is picked. Calm, low-density
   so the rail doesn't feel like a wall of placeholder text. */
.acc-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 28px 12px 20px;
  color: var(--text-muted);
}
.acc-empty-icon {
  color: rgba(243, 138, 63, 0.55);
  margin-bottom: 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 60px;
  height: 60px;
  background: rgba(243, 138, 63, 0.08);
  border-radius: 50%;
}
.acc-empty-title {
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--text);
  margin: 0 0 6px;
}
.acc-empty-body {
  font-size: 0.83rem;
  line-height: 1.45;
  margin: 0;
  max-width: 240px;
}
/* New-customer tag editor — surfaces in the empty rail once the
   admin starts typing a name (signaling "I'm creating a new
   customer"). Soft inset divider above so it reads as a separate
   sub-section, not part of the introductory empty state. */
.acc-empty-tags {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px dashed rgba(55, 59, 77, 0.18);
  width: 100%;
  max-width: 280px;
  text-align: left;
}
.acc-empty-tags-eyebrow {
  display: block;
  font-size: 0.66rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #7B8190;
  margin-bottom: 8px;
}
/* Customer mode banner — sits at the very top of the customer rail
   card when a customer is matched. Tells the admin what's happening
   ("you're editing this customer's HCP record") and carries the
   Start Fresh escape hatch for wrong-customer matches. Subtle warm
   inset wash so it reads as meta-context, not as primary content;
   the actual customer info below is still the hero. */
.acc-mode {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
  padding: 9px 12px;
  background: #F3EFE7;
  border: 1px solid #E5DED3;
  border-radius: 12px;
}
.acc-mode-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1 1 auto;
  min-width: 0;
}
.acc-mode-eyebrow {
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #7B8190;
}
.acc-mode-detail {
  font-size: 0.78rem;
  color: var(--text-muted);
  line-height: 1.35;
}
.acc-mode-start-fresh {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 11px;
  flex: 0 0 auto;
  background: #FFFDF9;
  border: 1px solid #E5DED3;
  border-radius: 999px;
  color: #373B4D;
  font-size: 0.74rem;
  font-weight: 700;
  cursor: pointer;
  font-family: inherit;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.85) inset;
  transition: background 0.12s ease, border-color 0.12s ease,
              color 0.12s ease, transform 0.06s ease;
  white-space: nowrap;
}
.acc-mode-start-fresh svg { color: #7B8190; transition: color 0.12s ease, transform 0.2s ease; }
.acc-mode-start-fresh:hover {
  background: #FFFAF1;
  border-color: rgba(243, 138, 63, 0.4);
  color: #373B4D;
}
.acc-mode-start-fresh:hover svg { color: #F38A3F; transform: rotate(-90deg); }
.acc-mode-start-fresh:active { transform: scale(0.97); }
/* Future-proofing: variant for the "Creating a new customer" mode.
   Same structural rules, slightly orange-tinted wash so the admin
   can tell at a glance "no customer matched, this is fresh." Not
   yet emitted by booking.js — wired up in a later phase that adds
   an explicit "new customer" mode signal. */
.acc-mode--new {
  background: rgba(243, 138, 63, 0.08);
  border-color: rgba(243, 138, 63, 0.24);
}
.acc-mode--new .acc-mode-eyebrow { color: #B86012; }

.acc-head {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin-bottom: 14px;
}
.acc-head-text { flex: 1 1 auto; min-width: 0; }
.acc-name {
  display: block;
  font-size: 1.2rem;
  font-weight: 800;
  color: #373B4D;
  line-height: 1.25;
  letter-spacing: -0.015em;
  margin-bottom: 4px;
}
.acc-email, .acc-phone {
  display: inline-block;
  font-size: 0.85rem;
  color: #7B8190;
  margin-right: 12px;
  font-variant-numeric: tabular-nums;
}
.acc-meta {
  display: block;
  font-size: 0.74rem;
  color: #7B8190;
  margin-top: 6px;
  font-weight: 600;
  letter-spacing: 0.02em;
}

.acc-details {
  margin: 0 0 16px;
  padding: 12px 14px;
  background: #FFFDF9;
  border: 1px solid #E5DED3;
  border-radius: 14px;
  display: grid;
  gap: 10px;
}
.acc-detail-row {
  display: grid;
  gap: 3px;
}
.acc-detail-label {
  margin: 0;
  color: #7B8190;
  font-size: 0.58rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.acc-detail-value {
  margin: 0;
  color: #373B4D;
  font-size: 0.82rem;
  line-height: 1.35;
  font-weight: 650;
  overflow-wrap: anywhere;
}

/* Tags row — soft inset pills, fully-rounded ends, all sit on one
   horizontal flow that wraps. Used for whatever HCP labels the
   customer with (VIP, do-not-service, frequent, etc.). */
.acc-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 16px;
}
.acc-tag {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(191, 224, 243, 0.42);
  color: #1F5A86;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.04em;
}
/* Derived (synthesized server-side) tags read differently from HCP
   tags so admins know which labels came from Housecall Pro and which
   we computed. Warm inset wash + dashed hairline border + lighter
   weight — quieter than the blue HCP pill, but still clearly a tag. */
.acc-tag--derived {
  background: rgba(243, 138, 63, 0.10);
  color: #B86012;
  border: 1px dashed rgba(243, 138, 63, 0.42);
  font-weight: 600;
  padding: 3px 9px;
}

/* ─── Tag editor (admin-only) ──────────────────────────────────────
   Variant pills used by renderTagEditor when the customer rail is in
   edit mode. The four states map to the four tag kinds: hcp (real,
   removable), pending-add (queued, undo with ×), pending-remove
   (queued for delete, struck through, revert with ↶), and derived
   (read-only, lighter style — same as .acc-tag--derived above).
   The + Add chip sits at the end of the row. */
.acc-tags--editable {
  align-items: center;
}
/* Slight spacing tweak so the × button has visual room. */
.acc-tags--editable .acc-tag {
  padding-right: 4px;
}
.acc-tag-label {
  /* Reserves the same baseline as the bare pills so adding a button
     doesn't shift the pill height. */
  padding: 0 4px 0 2px;
}
.acc-tag-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  margin-left: 2px;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  border-radius: 50%;
  cursor: pointer;
  font-size: 0.85rem;
  line-height: 1;
  opacity: 0.55;
  transition: opacity 0.12s ease, background 0.12s ease;
}
.acc-tag-btn:hover,
.acc-tag-btn:focus-visible {
  opacity: 1;
  outline: none;
  background: rgba(55, 59, 77, 0.10);
}
/* Pending-add: green pill, signals "this is queued, not yet on HCP". */
.acc-tag--pending-add {
  background: rgba(46, 138, 86, 0.16);
  color: #1F6B40;
  box-shadow: inset 0 0 0 1px rgba(46, 138, 86, 0.32);
}
.acc-tag--pending-add::before {
  content: '+';
  margin-right: 4px;
  font-weight: 800;
  font-size: 0.7rem;
  line-height: 1;
}
/* Pending-remove: strikethrough + muted color. Pill stays in place so
   the admin can revert with the ↶ button. */
.acc-tag--pending-remove {
  background: rgba(196, 53, 53, 0.10);
  color: #8B1F1F;
  text-decoration: line-through;
  text-decoration-color: rgba(139, 31, 31, 0.55);
}
/* + Add tag chip. Reads as a "do something" affordance — dashed
   hairline, neutral fill, snaps to a solid orange border on hover. */
.acc-tag-add {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px 4px 8px;
  border: 1px dashed rgba(55, 59, 77, 0.32);
  border-radius: 999px;
  background: transparent;
  color: #373B4D;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  cursor: pointer;
  transition: border-color 0.14s ease, background 0.14s ease, color 0.14s ease;
}
.acc-tag-add:hover,
.acc-tag-add:focus-visible {
  outline: none;
  border-style: solid;
  border-color: #F38A3F;
  background: rgba(243, 138, 63, 0.10);
  color: #B86012;
}
.acc-tag-add svg { stroke: currentColor; }

/* Combobox dropdown — opens below the tag row when + Add fires.
   Warm-card chrome, soft border, generous padding so the suggestion
   list is comfortable to scan. */
.acc-tag-combobox {
  margin: 8px 0 16px;
  padding: 10px;
  background: #FFFDF9;
  border: 1px solid #E5DED3;
  border-radius: 14px;
  box-shadow: 0 6px 18px rgba(55, 59, 77, 0.06),
              0 1px 0 rgba(255, 255, 255, 0.9) inset;
}
.acc-tag-combo-input-wrap {
  display: flex;
  align-items: center;
  gap: 6px;
}
.acc-tag-combo-input {
  flex: 1 1 auto;
  padding: 8px 12px;
  border: 1px solid #E5DED3;
  border-radius: 10px;
  background: #FCFAF6;
  font-size: 0.88rem;
  color: #373B4D;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
.acc-tag-combo-input:focus {
  outline: none;
  background: #FFFDF9;
  border-color: #F38A3F;
  box-shadow: 0 0 0 3px rgba(243, 138, 63, 0.18);
}
.acc-tag-combo-close {
  width: 26px;
  height: 26px;
  border: 0;
  background: transparent;
  color: #7B8190;
  font-size: 1rem;
  line-height: 1;
  border-radius: 50%;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}
.acc-tag-combo-close:hover,
.acc-tag-combo-close:focus-visible {
  outline: none;
  background: rgba(55, 59, 77, 0.08);
  color: #373B4D;
}
.acc-tag-combo-list {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  max-height: 240px;
  overflow-y: auto;
}
.acc-tag-combo-item {
  padding: 7px 10px;
  border-radius: 8px;
  cursor: pointer;
  font-size: 0.86rem;
  color: #373B4D;
  transition: background 0.12s ease;
}
.acc-tag-combo-item:hover,
.acc-tag-combo-item:focus {
  outline: none;
  background: rgba(191, 224, 243, 0.28);
}
.acc-tag-combo-empty {
  margin: 8px 4px 0;
  font-size: 0.78rem;
  color: #7B8190;
  font-style: italic;
}
/* Soft dupe guard. "Did you mean X?" appears above the create row
   when the typed text is close to an existing tag. */
.acc-tag-combo-guard {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
  padding: 7px 10px;
  background: rgba(243, 138, 63, 0.10);
  border: 1px solid rgba(243, 138, 63, 0.34);
  border-radius: 10px;
}
.acc-tag-combo-guard-text {
  font-size: 0.8rem;
  color: #6E4218;
  flex: 1 1 auto;
}
.acc-tag-combo-guard-text strong {
  color: #B86012;
}
.acc-tag-combo-guard-btn {
  flex: 0 0 auto;
  padding: 5px 10px;
  border: 1px solid #B86012;
  background: transparent;
  color: #B86012;
  border-radius: 999px;
  font-size: 0.74rem;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}
.acc-tag-combo-guard-btn:hover,
.acc-tag-combo-guard-btn:focus-visible {
  outline: none;
  background: #B86012;
  color: #FFFDF9;
}
/* Create-new row. Dashed border + plus glyph + sage green styling so
   it reads as the "new thing" action, distinct from picking an
   existing tag. */
.acc-tag-combo-create {
  margin-top: 8px;
}
.acc-tag-combo-create-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  border: 1px dashed rgba(46, 138, 86, 0.46);
  background: rgba(46, 138, 86, 0.06);
  color: #1F6B40;
  border-radius: 999px;
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease;
}
.acc-tag-combo-create-btn:hover,
.acc-tag-combo-create-btn:focus-visible {
  outline: none;
  border-style: solid;
  background: rgba(46, 138, 86, 0.14);
}
.acc-tag-combo-create-btn strong {
  font-weight: 700;
}

.acc-duplicates {
  margin: 0 0 16px;
  padding: 14px 16px;
  background: rgba(243, 138, 63, 0.08);
  border: 1px solid rgba(243, 138, 63, 0.24);
  border-radius: 14px;
}
.acc-duplicates-eyebrow {
  display: block;
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #B86012;
  margin-bottom: 6px;
}
.acc-duplicates-body {
  margin: 0 0 10px;
  font-size: 0.8rem;
  line-height: 1.45;
  color: #60452D;
}
.acc-duplicate-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 8px;
}
.acc-duplicate-row {
  display: grid;
  gap: 3px;
  padding: 9px 10px;
  border-radius: 11px;
  background: #FFFDF9;
  border: 1px solid rgba(243, 138, 63, 0.18);
}
.acc-duplicate-name {
  font-size: 0.86rem;
  font-weight: 800;
  color: #373B4D;
}
.acc-duplicate-meta {
  font-size: 0.72rem;
  line-height: 1.35;
  color: #7B8190;
}

/* Notes block — quiet inset card with eyebrow + body paragraph.
   Reads as context, not alert; HCP "Customer notes" are free-text. */
.acc-notes {
  margin: 0 0 18px;
  padding: 14px 16px;
  background: #F3EFE7;
  border: 1px solid #E5DED3;
  border-radius: 14px;
}
.acc-notes-eyebrow {
  display: block;
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #7B8190;
  margin-bottom: 6px;
}
.acc-notes-body {
  margin: 0;
  font-size: 0.85rem;
  line-height: 1.5;
  color: #373B4D;
  white-space: pre-wrap;
}

/* Past technicians list — chip-per-tech with avatar bullet, name,
   and a meta line ("3 visits · last Sep 18"). Sorted most-recent
   first server-side; first row is the tech who saw this customer
   most recently and is the natural rebook candidate. */
.acc-techs {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.acc-techs > li {
  margin: 0;
  padding: 0;
}
.acc-tech {
  display: grid;
  gap: 8px;
  padding: 10px 12px;
  background: #FFFDF9;
  border: 1px solid #E5DED3;
  border-radius: 12px;
}
.acc-tech-main {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  min-width: 0;
  list-style: none;
  cursor: pointer;
}
.acc-tech-main::-webkit-details-marker { display: none; }
.acc-tech-bullet {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: rgba(243, 138, 63, 0.16);
  color: #B86012;
  font-size: 0.78rem;
  font-weight: 800;
  flex: 0 0 28px;
  order: 1;
}
.acc-tech-name {
  font-size: 0.9rem;
  font-weight: 700;
  color: #373B4D;
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  order: 2;
}
.acc-tech-meta {
  font-size: 0.72rem;
  color: #7B8190;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  flex: 1 1 100%;
  text-align: left;
  max-width: none;
  line-height: 1.35;
  min-width: 90px;
  margin-left: 38px;
  order: 4;
}
.acc-tech-caret {
  color: #7B8190;
  font-size: 0.92rem;
  font-weight: 800;
  line-height: 1;
  transform-origin: center;
  transition: transform 0.15s ease;
  margin-left: auto;
  order: 3;
}
.acc-tech[open] .acc-tech-caret {
  transform: rotate(180deg);
}
.acc-tech-appointments {
  list-style: none;
  margin: 2px 0 0 38px;
  padding: 0;
  display: grid;
  gap: 5px;
}
.acc-tech-appointment {
  display: grid;
  grid-template-columns: minmax(74px, auto) auto 1fr;
  gap: 6px;
  align-items: baseline;
  font-size: 0.72rem;
  color: #7B8190;
  line-height: 1.35;
}
.acc-tech-appt-date {
  font-weight: 800;
  color: #373B4D;
  font-variant-numeric: tabular-nums;
}
.acc-tech-appt-status {
  font-size: 0.58rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 2px 7px;
  border-radius: 999px;
  background: #F3EFE7;
  color: #7B8190;
  font-weight: 800;
}
.acc-tech-appt-desc {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.acc-tech-appointment--empty {
  display: block;
  color: #7B8190;
  font-size: 0.74rem;
  font-style: italic;
}
/* Inactive tech (no longer bookable). Subtler look + lower contrast
   so admins know not to count on this tech for new bookings. */
.acc-tech--inactive {
  opacity: 0.7;
}
.acc-tech--inactive .acc-tech-bullet {
  background: #F3EFE7;
  color: #7B8190;
}
/* Z-pattern action cluster — sits in the top-right corner of the
   rail head. Pairs the "Open in HCP" jump-link (primary action,
   per philosophy doc's "primary action / filter top-right" rule)
   with the close button as a secondary affordance. Vertically
   centered against the name on the left. */
.acc-head-actions {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
.acc-hcp-link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 6px 12px;
  background: rgba(243, 138, 63, 0.10);
  color: #B86012;
  border-radius: 999px;
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-decoration: none;
  transition: background 0.15s ease, color 0.15s ease;
  white-space: nowrap;
}
.acc-hcp-link:hover {
  background: rgba(243, 138, 63, 0.18);
  color: #8C4710;
}
.acc-hcp-link svg { transition: transform 0.18s ease; }
.acc-hcp-link:hover svg { transform: translate(2px, -2px); }
/* Quieter sibling to the "Open in HCP" pill. Opens the raw HCP API
   diagnostics viewer for this customer in a new tab. Same shape as
   the HCP link but tinted neutral so it reads as a developer/admin
   tool rather than a primary action. */
.acc-diag-link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 6px 12px;
  background: rgba(55, 59, 77, 0.06);
  color: #4a4f60;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-decoration: none;
  transition: background 0.15s ease, color 0.15s ease;
  white-space: nowrap;
}
.acc-diag-link:hover {
  background: rgba(55, 59, 77, 0.12);
  color: #2A2A2A;
}
.acc-close {
  border: 0;
  background: transparent;
  font-size: 1.4rem;
  line-height: 1;
  color: #7B8190;
  cursor: pointer;
  padding: 4px 10px;
  border-radius: 999px;
  transition: background 0.12s ease, color 0.12s ease;
}
.acc-close:hover { background: rgba(55, 59, 77, 0.06); color: #373B4D; }

.acc-stats {
  display: flex;
  flex-wrap: wrap;
  /* KPI band: warm card-highlight surface (one tone lighter than the
     rail's own card so the stats lift), generous gap so each cluster
     has air, larger 16px squircle radius. */
  gap: 20px 28px;
  padding: 18px 20px;
  background: #FFFDF9;
  border: 1px solid #E5DED3;
  border-radius: 16px;
  margin-bottom: 18px;
}
.acc-stat {
  display: flex;
  flex-direction: column;
  gap: 4px;
  /* Tight cluster per philosophy "Rule of Proximity" — number and
     label are one visual unit. */
}
.acc-stat-num {
  /* Hero number per philosophy: large, heavy weight, deep navy ink so
     it acts as an anchor for the eye. Tabular-nums prevents layout
     shift when the value updates. */
  font-size: 1.55rem;
  font-weight: 800;
  color: #373B4D;
  letter-spacing: -0.015em;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
}
.acc-stat-label {
  font-size: 0.7rem;
  color: #7B8190;
  text-transform: uppercase;
  letter-spacing: 0.10em;
  font-weight: 700;
}
.acc-stat--warn .acc-stat-num { color: #D65F4F; }
.acc-stat--info .acc-stat-num { color: #1F5A86; }

/* ─── Loading skeleton — basic phase ─────────────────────────────
   While the slow customer-history fetch is in flight, the timeline +
   stats sections render as pulsing placeholders. Same dimensions as
   the real content so nothing reflows when history loads. Pulse
   alternates between two cream tones from the Bento-Box palette to
   stay quiet against the surrounding rail. */
@keyframes accSkelPulse {
  0%, 100% { background-color: rgba(195, 184, 159, 0.22); }
  50%      { background-color: rgba(195, 184, 159, 0.42); }
}
.acc-skel-bar {
  display: inline-block;
  height: 0.78em;
  background: rgba(195, 184, 159, 0.30);
  border-radius: 6px;
  animation: accSkelPulse 1.4s ease-in-out infinite;
  vertical-align: middle;
}
.acc-skel-bar--sm { width: 48px; height: 0.65em; }
.acc-skel-bar--md { width: 96px; }
.acc-skel-bar--lg { width: 140px; height: 1.05em; }
.acc-stat--skeleton {
  background: rgba(245, 240, 228, 0.55);
  border-color: rgba(195, 184, 159, 0.25);
}
.acc-stat--skeleton .acc-stat-num,
.acc-stat--skeleton .acc-stat-label {
  background: transparent;
  display: block;
}
.acc-stat--skeleton .acc-stat-num {
  margin-bottom: 8px;
}
.acc-section-pill--loading {
  background: rgba(195, 184, 159, 0.18);
  color: #8a7d63;
  animation: accSkelPulse 1.4s ease-in-out infinite;
}
.acc-tl-visit--skeleton .acc-tl-dot {
  background: rgba(195, 184, 159, 0.40);
  border-color: rgba(195, 184, 159, 0.55);
  animation: accSkelPulse 1.4s ease-in-out infinite;
}
.acc-tl-visit--skeleton .acc-tl-connector {
  background: rgba(195, 184, 159, 0.25);
}
.acc-tl-visit--skeleton .acc-skel-bar { background: rgba(195, 184, 159, 0.28); }
.acc-section-note--loading {
  color: #8a7d63;
  font-style: italic;
  font-size: 0.78rem;
}
.acc-section--loading .acc-timeline--skeleton {
  list-style: none;
  margin: 0;
  padding: 0;
}
/* Respect reduced-motion: drop the pulse entirely. */
@media (prefers-reduced-motion: reduce) {
  .acc-skel-bar,
  .acc-section-pill--loading,
  .acc-tl-visit--skeleton .acc-tl-dot {
    animation: none;
  }
}

.acc-section { margin-top: 14px; }
.acc-section + .acc-section { margin-top: 18px; }
.acc-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin: 0 0 10px;
  padding: 0 2px;
}
.acc-section-title {
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: #7B8190;
  font-weight: 800;
  margin: 0;
  padding: 0;
}
.acc-section-pill {
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  padding: 4px 10px;
  background: #F3EFE7;
  color: #373B4D;
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  white-space: nowrap;
}
.acc-list {
  list-style: none;
  margin: 0;
  padding: 0;
  /* Bento sub-card: warm card-highlight fill (lifted off the rail's
     own card), soft-warm 1px border, 14px squircle radius. */
  border: 1px solid #E5DED3;
  border-radius: 14px;
  background: #FFFDF9;
  overflow: hidden;
}
.acc-list > li {
  /* Taller row rhythm + airier columns per the philosophy doc's
     "tall row heights" guidance for list architecture. */
  padding: 12px 14px;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px 14px;
  font-size: 0.88rem;
  color: #373B4D;
}
.acc-list > li + li { border-top: 1px solid #E5DED3; }

/* Visit history timeline. Each <li> is one visit, laid out as a
   two-column grid: left column holds a vertical "rail" with a dot
   + connector line; right column holds the date/tech header and
   one-or-more clickable document chips (invoices, estimates).
   The connector line visually links each visit's dot to the
   next, giving the section a real timeline feel rather than a
   bare list. */
.acc-timeline {
  list-style: none;
  margin: 0;
  padding: 0;
}
.acc-tl-visit {
  display: grid;
  grid-template-columns: 18px minmax(0, 1fr);
  gap: 14px;
  padding: 0 6px;
}
.acc-tl-node {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Nudge the dot down to vertically align with the date label
     in the content column. */
  padding-top: 7px;
}
.acc-tl-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #FCFAF6;
  border: 2px solid #D39A2E;
  flex: 0 0 12px;
  box-shadow: 0 0 0 3px rgba(211, 154, 46, 0.10);
  z-index: 1;
}
.acc-tl-connector {
  position: absolute;
  top: 19px;
  bottom: -14px;
  width: 2px;
  background: #E5DED3;
}
/* Last visit in the timeline has no downward connector — caps
   the rail at the bottom. */
.acc-tl-node--last .acc-tl-connector { display: none; }

.acc-tl-content {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-bottom: 18px;
  min-width: 0;
}
.acc-tl-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
}
.acc-tl-date {
  font-size: 0.92rem;
  font-weight: 700;
  color: #373B4D;
  letter-spacing: -0.005em;
}
.acc-tl-tech {
  font-size: 0.82rem;
  color: var(--text-muted);
  font-weight: 500;
}
/* Appointment scope-of-work text — sits between the date/tech header
   and the document chips. Quieter than the date but more substantial
   than the meta line so admins notice it as the visit's purpose
   without it competing for attention. */
.acc-tl-job-desc {
  margin: 4px 0 6px;
  font-size: 0.85rem;
  color: #444a5a;
  line-height: 1.45;
  white-space: pre-line;
}

/* Document chips — each invoice / estimate / "no paperwork"
   placeholder is its own clickable pill that expands inline to
   show its details. Multiple chips stack vertically with a
   small gap so a visit with both an invoice and an estimate
   reads as two related but distinct documents. */
.acc-tl-docs {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.acc-tl-doc {
  background: #FFFDF9;
  border: 1px solid #E5DED3;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.85) inset;
}
.acc-tl-doc--invoice { border-left: 3px solid rgba(75, 170, 120, 0.55); }
.acc-tl-doc--estimate { border-left: 3px solid rgba(31, 90, 134, 0.55); }
.acc-tl-doc--empty {
  border-left: 3px solid rgba(123, 129, 144, 0.30);
  padding: 9px 13px;
  background: #F3EFE7;
}
.acc-tl-doc-label--muted {
  color: var(--text-muted);
  font-size: 0.82rem;
  font-style: italic;
}

.acc-tl-doc-summary {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr) auto auto 14px;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 12px;
  background: transparent;
  border: 0;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 0.12s ease;
}
.acc-tl-doc-summary:hover { background: #FFFAF1; }
.acc-tl-doc-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 6px;
  background: #F3EFE7;
}
.acc-tl-doc--invoice .acc-tl-doc-icon { color: #2E7D4F; background: rgba(75, 170, 120, 0.14); }
.acc-tl-doc--estimate .acc-tl-doc-icon { color: #1F5A86; background: rgba(31, 90, 134, 0.14); }
.acc-tl-doc-label {
  font-size: 0.86rem;
  font-weight: 700;
  color: #373B4D;
  letter-spacing: -0.005em;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.acc-tl-doc-amount {
  font-size: 0.88rem;
  font-weight: 700;
  color: #373B4D;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.acc-tl-doc-status {
  font-size: 0.66rem;
  font-weight: 800;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--text-muted);
  padding: 3px 8px;
  background: #F3EFE7;
  border-radius: 999px;
  white-space: nowrap;
}
.acc-tl-doc--invoice .acc-tl-doc-status { color: #2E7D4F; background: rgba(75, 170, 120, 0.16); }
.acc-tl-doc--estimate .acc-tl-doc-status { color: #1F5A86; background: rgba(31, 90, 134, 0.16); }
.acc-tl-doc-caret {
  color: var(--text-muted);
  font-size: 0.92rem;
  transition: transform 0.2s ease;
  user-select: none;
}
.acc-tl-doc-summary[aria-expanded="true"] .acc-tl-doc-caret {
  transform: rotate(180deg);
}
.acc-tl-doc-detail {
  padding: 0 14px 12px;
  border-top: 1px solid #E5DED3;
}
.acc-tl-doc-detail[hidden] { display: none; }
.acc-tl-doc-desc {
  font-size: 0.82rem;
  color: var(--text);
  line-height: 1.5;
  margin: 10px 0 8px;
  /* HCP description fields come through with embedded \n newlines
     for multi-paragraph content (Scope of Work, Detailed Scope of
     Work, etc.). Without pre-line, those collapse into one run of
     text and a multi-paragraph description reads as a single
     hard-to-parse blob. */
  white-space: pre-line;
}
.acc-tl-doc-lines { margin: 0; }
.acc-tl-doc-lines[hidden] { display: none; }
.acc-tl-doc-link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 0.78rem;
  font-weight: 600;
  color: #1F5A86;
  margin-top: 10px;
}
.acc-tl-doc-link[hidden] { display: none; }
.acc-tl-doc-link:hover { color: #373B4D; }
/* End of timeline rules. */

.acc-visit-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto 26px;
  grid-template-areas:
    "date amount blank"
    "tech status blank";
  gap: 2px 10px;
  padding: 0 16px 8px;
  color: #7B8190;
  font-size: 0.56rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.acc-visit-head span:nth-child(1) { grid-area: date; }
.acc-visit-head span:nth-child(2) { grid-area: tech; }
.acc-visit-head span:nth-child(3) {
  grid-area: amount;
  text-align: right;
}
.acc-visit-head span:nth-child(4) {
  grid-area: status;
  text-align: right;
}
.acc-visit-head span:nth-child(5) { grid-area: blank; }
.acc-list.acc-visit-list {
  display: grid;
  gap: 10px;
  border: 0;
  border-radius: 0;
  background: transparent;
  overflow: visible;
}
.acc-visit-list > li.acc-visit {
  display: block;
  padding: 0;
  border: 1px solid #E5DED3;
  border-radius: 18px;
  background: #FFFDF9;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.86),
    0 8px 22px rgba(55, 59, 77, 0.045);
  overflow: hidden;
}
.acc-visit-list > li.acc-visit + li.acc-visit { border-top: 1px solid #E5DED3; }
.acc-visit-summary {
  width: 100%;
  border: 0;
  background: transparent;
  color: inherit;
  cursor: pointer;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto 26px;
  grid-template-areas:
    "date money caret"
    "tech status caret";
  gap: 7px 10px;
  align-items: center;
  padding: 15px 16px;
  text-align: left;
  font: inherit;
  transition: background 0.16s ease;
}
.acc-visit-summary:hover {
  background: linear-gradient(180deg, #FFFDF9, #FCFAF6);
}
.acc-visit-summary[aria-expanded="true"] {
  background: linear-gradient(180deg, #FFF8EF, #FFFDF9);
}
.acc-visit-date,
.acc-visit-tech,
.acc-visit-money {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.acc-visit-date {
  grid-area: date;
  font-weight: 800;
  color: #373B4D;
  font-size: 0.98rem;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.acc-visit-tech {
  grid-area: tech;
  color: #7B8190;
  font-size: 0.78rem;
  font-weight: 750;
}
.acc-visit-money {
  grid-area: money;
  color: #373B4D;
  font-weight: 800;
  font-size: 1.02rem;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.acc-visit--open .acc-visit-money { color: #D65F4F; }
.acc-visit-status {
  grid-area: status;
  justify-self: start;
  font-size: 0.58rem;
  text-transform: uppercase;
  letter-spacing: 0.10em;
  padding: 3px 9px;
  border-radius: 999px;
  background: #F3EFE7;
  color: #7B8190;
  font-weight: 800;
  white-space: nowrap;
  justify-self: end;
}
.acc-visit--paid .acc-visit-status {
  background: rgba(75, 170, 120, 0.16);
  color: #2E7D4F;
}
.acc-visit--open .acc-visit-status {
  background: rgba(211, 154, 46, 0.18);
  color: #8A6418;
}
.acc-visit--unknown .acc-visit-status {
  background: rgba(191, 224, 243, 0.55);
  color: #1F5A86;
}
.acc-visit-caret {
  grid-area: caret;
  align-self: center;
  justify-self: end;
  width: 24px;
  height: 24px;
  border-radius: 999px;
  background: #F3EFE7;
  color: #7B8190;
  font-size: 0.95rem;
  font-weight: 800;
  line-height: 24px;
  text-align: center;
  transition: transform 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.acc-visit-summary[aria-expanded="true"] .acc-visit-caret {
  transform: rotate(180deg);
  background: #FFEEDC;
  color: #C66518;
}
.acc-visit-detail {
  margin: 0 12px 12px;
  padding: 12px;
  display: grid;
  gap: 10px;
  border: 1px solid #E5DED3;
  border-radius: 14px;
  background: #F8F5EF;
}
.acc-visit-detail[hidden] {
  display: none;
}
.acc-visit-detail-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}
.acc-visit-detail-grid > div {
  min-width: 0;
  padding: 10px 11px;
  border-radius: 12px;
  background: #FFFDF9;
  border: 1px solid #E5DED3;
  display: grid;
  gap: 2px;
}
.acc-visit-detail-label {
  font-size: 0.56rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #7B8190;
}
.acc-visit-invoice,
.acc-visit-amount,
.acc-visit-detail-tech {
  min-width: 0;
  color: #373B4D;
  font-size: 0.76rem;
  font-weight: 800;
  overflow-wrap: anywhere;
}
.acc-visit-desc {
  margin: 0;
  color: #373B4D;
  font-size: 0.82rem;
  line-height: 1.45;
  white-space: pre-wrap;
  padding: 10px 11px;
  border-radius: 12px;
  background: #FFFDF9;
  border: 1px solid #E5DED3;
}
.acc-visit-link {
  width: fit-content;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 7px 12px;
  border-radius: 999px;
  background: #FFEEDC;
  color: #C66518;
  border: 1px solid rgba(243, 138, 63, 0.28);
}
.acc-job-date {
  font-weight: 700;
  min-width: 96px;
  color: #373B4D;
  font-variant-numeric: tabular-nums;
}
/* Status badges use the philosophy's pill pattern: fully rounded
   ends, soft wash background, same-hue strong text. */
.acc-job-status {
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.10em;
  padding: 3px 10px;
  border-radius: 999px;
  background: #F3EFE7;
  color: #7B8190;
  font-weight: 800;
}
.acc-job-status--completed {
  background: rgba(75, 170, 120, 0.16);
  color: #2E7D4F;
}
.acc-job-status--in_progress {
  background: rgba(211, 154, 46, 0.18);
  color: #8A6418;
}
.acc-job-status--scheduled {
  background: rgba(191, 224, 243, 0.55);
  color: #1F5A86;
}
.acc-job-status--canceled,
.acc-job-status--cancelled {
  background: #F3EFE7;
  color: #7B8190;
}
.acc-job-tech { font-size: 0.85rem; color: #373B4D; font-weight: 600; }
.acc-job-invoice {
  font-size: 0.78rem;
  color: #1F5A86;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.acc-job-desc { flex-basis: 100%; color: #7B8190; font-size: 0.82rem; }
.acc-source {
  display: inline-flex;
  align-items: center;
  width: fit-content;
  max-width: 100%;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(191, 224, 243, 0.36);
  color: #1F5A86;
  font-size: 0.66rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.acc-open-link {
  color: #B86012;
  font-size: 0.76rem;
  font-weight: 800;
  text-decoration: none;
}
.acc-open-link:hover {
  text-decoration: underline;
}
.acc-list > li.acc-invoice {
  display: block;
  padding: 0;
}
.acc-invoice-summary {
  width: 100%;
  border: 0;
  background: transparent;
  color: inherit;
  cursor: pointer;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  padding: 12px 14px;
  text-align: left;
  font: inherit;
}
.acc-invoice-summary:hover {
  background: rgba(243, 138, 63, 0.05);
}
.acc-invoice-summary[aria-expanded="true"] {
  background: #FFF8EF;
}
.acc-invoice-num {
  font-weight: 700;
  min-width: 110px;
  color: #373B4D;
  font-variant-numeric: tabular-nums;
}
.acc-invoice-amt {
  font-variant-numeric: tabular-nums;
  min-width: 80px;
  font-weight: 600;
}
.acc-invoice-balance {
  font-size: 0.72rem;
  color: #B86012;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
/* Status pill on each invoice row. Default is the philosophy's inset
   wash with muted graphite text — quiet, archival reading for
   ambiguous statuses (canceled, voided, draft, anything we don't
   recognize). Modifier classes paint the meaningful states:
     --paid     → green wash + emerald text  (money in)
     --open     → gold wash + warm-brown text (money still owed) */
.acc-invoice-status {
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.10em;
  padding: 3px 10px;
  border-radius: 999px;
  background: #F3EFE7;
  color: #7B8190;
  font-weight: 800;
}
.acc-invoice--paid .acc-invoice-status {
  background: rgba(75, 170, 120, 0.16);
  color: #2E7D4F;
}
.acc-invoice--open { background: rgba(243, 138, 63, 0.05); }
.acc-invoice--open .acc-invoice-status {
  background: rgba(211, 154, 46, 0.18);
  color: #8A6418;
}
/* Synthetic invoice rows — synthesized server-side from job records
   when HCP's invoices endpoint didn't return amount data for this
   customer. Renders with the deep-navy "informational" hue (same
   family as the .acc-stat--info color) so the row reads as "data
   exists in HCP, just not in this API response" rather than as a
   warning. Amount column shows "—" instead of $0. */
.acc-invoice--synthetic { background: rgba(191, 224, 243, 0.20); }
.acc-invoice--synthetic .acc-invoice-status {
  background: rgba(191, 224, 243, 0.55);
  color: #1F5A86;
}
.acc-invoice--synthetic .acc-invoice-amt {
  color: #7B8190;
  font-weight: 600;
}
.acc-invoice-detail {
  padding: 0 14px 14px;
  display: grid;
  gap: 10px;
}
.acc-invoice-detail[hidden] {
  display: none;
}
.acc-invoice-desc {
  margin: 0;
  color: #373B4D;
  font-size: 0.82rem;
  line-height: 1.45;
  white-space: pre-wrap;
}
.acc-invoice-lines {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 6px;
}
.acc-invoice-line {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 3px 10px;
  padding: 8px 10px;
  background: #FCFAF6;
  border: 1px solid #E5DED3;
  border-radius: 10px;
}
.acc-invoice-line-name {
  color: #373B4D;
  font-size: 0.78rem;
  font-weight: 800;
  min-width: 0;
}
.acc-invoice-line-amount {
  color: #373B4D;
  font-size: 0.78rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
/* Quantity badge — small inline marker sitting on its own row so
   the multi-paragraph description below isn't visually prefixed by
   "Qty X · " (which ran the two together when the description
   spans multiple paragraphs). */
.acc-invoice-line-qty {
  grid-column: 1 / -1;
  color: #7B8190;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  margin-top: 2px;
}
.acc-invoice-line-qty[hidden] { display: none; }
.acc-invoice-line-desc {
  grid-column: 1 / -1;
  color: #7B8190;
  font-size: 0.72rem;
  line-height: 1.35;
  /* Same multiline-preservation rule as .acc-tl-doc-desc above.
     Line-item descriptions from HCP (the rich "Scope of Work /
     Detailed Scope of Work" content the user pasted as a screenshot)
     ship with \n separators between paragraphs. */
  white-space: pre-line;
  margin-top: 4px;
}
.acc-invoice-line-desc[hidden] { display: none; }
.acc-invoice-profile {
  margin: 0;
}
.acc-invoice-link {
  width: fit-content;
}
@media (max-width: 560px) {
  .acc-visit-head {
    display: none;
  }
  .acc-visit-summary {
    grid-template-columns: minmax(0, 1fr) auto 26px;
    gap: 7px 8px;
  }
  .acc-visit-date {
    grid-area: date;
  }
  .acc-visit-money {
    grid-area: money;
    text-align: right;
  }
  .acc-visit-caret {
    grid-area: caret;
  }
  .acc-visit-tech {
    grid-area: tech;
  }
  .acc-visit-status {
    grid-area: status;
    justify-self: end;
  }
  .acc-visit-detail-grid {
    grid-template-columns: 1fr;
  }
  .acc-tech-main {
    align-items: flex-start;
    flex-wrap: wrap;
  }
  .acc-tech-meta {
    flex: 1 1 100%;
    max-width: none;
    text-align: left;
    margin-left: 38px;
  }
  .acc-tech-appointments {
    margin-left: 0;
  }
  .acc-tech-appointment {
    grid-template-columns: 1fr;
  }
  .acc-tech-appt-desc {
    white-space: normal;
  }
  .acc-invoice-summary {
    gap: 7px;
  }
  .acc-invoice-num,
  .acc-invoice-amt {
    min-width: 0;
  }
}
.acc-cap-note {
  margin: 10px 0 0;
  font-size: 0.82rem;
  color: var(--text-muted);
  font-style: italic;
}
/* Small footnote under a list when the data is known-partial (e.g.
   invoices were capped at the HCP page size). Quieter than .acc-cap-note
   because it's a clarification, not a missing-capability warning. */
.acc-section-note {
  margin: 6px 4px 0;
  font-size: 0.75rem;
  color: var(--text-muted);
  line-height: 1.4;
}

/* Cross-location result row. Softer surface + a badge that names the
   sister franchise the customer actually belongs to. Picking one of
   these prompts a switch/keep confirmation before the form fills. */
.admin-customer-search-result--other-loc {
  background: rgba(243, 138, 63, 0.04);
}
.admin-customer-search-result--other-loc:hover,
.admin-customer-search-result--other-loc:focus {
  background: rgba(243, 138, 63, 0.10);
}
.admin-customer-search-result-loc-badge {
  display: inline-block;
  padding: 1px 7px;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--orange, #E07A2A);
  background: rgba(243, 138, 63, 0.14);
  border-radius: 10px;
  margin-left: 2px;
}

/* ───── Admin override toggles ─────
   Collapsible <details> panel inside the admin booking banner.
   Hosts the bypass switches for business hours + trip charge.
   Lead-time bypass is automatic (no toggle, just a status line).
   Defaults closed so the surface stays calm — admins only open it
   when they actually need to force-book something unusual. */
.admin-overrides {
  border-top: 1px dashed rgba(39, 41, 54, 0.10);
  padding-top: 10px;
}
.admin-overrides-summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px 4px 6px;
  margin-left: -6px;
  border-radius: 8px;
  font-size: 0.85rem;
  color: var(--text-muted);
  cursor: pointer;
  list-style: none;
  user-select: none;
  transition: background 0.15s, color 0.15s;
  &::-webkit-details-marker { display: none; }
  &:hover { background: rgba(39, 41, 54, 0.04); color: var(--text); }
  & svg { transition: transform 0.2s ease; }
}
.admin-overrides[open] > .admin-overrides-summary {
  color: var(--text);
  font-weight: 600;
  & svg { transform: rotate(180deg); }
}
.admin-overrides-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  background: var(--orange, #E07A2A);
  color: #fff;
  border-radius: 999px;
  font-size: 0.7rem;
  font-weight: 700;
  line-height: 1;
}
.admin-overrides-body {
  margin-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px 14px;
  background: rgba(255, 255, 255, 0.55);
  border: 1px solid rgba(39, 41, 54, 0.08);
  border-radius: 10px;
}
.admin-overrides-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  font-size: 0.82rem;
  color: var(--text-muted);
}
.admin-overrides-status svg { flex-shrink: 0; }
.admin-overrides-toggle {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 8px 10px;
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.12s;
  &:hover { background: rgba(39, 41, 54, 0.04); }
  & input[type="checkbox"] {
    margin-top: 2px;
    accent-color: var(--orange, #E07A2A);
    cursor: pointer;
  }
  &:has(input:checked) {
    background: rgba(224, 122, 42, 0.08);
  }
}
.admin-overrides-toggle-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 0.88rem;
  color: var(--text);
}
.admin-overrides-toggle-text strong { font-weight: 600; }

/* ═══════════ Admin single-page flow ═══════════
   When the booking form has data-admin-flow="1" (set in EJS only when
   the page was rendered via /admin/book/:slug), every step panel is
   visible at once and they're reordered into:
     1. Describe the issue (step 3)
     2. Customer information incl. address (step 4 — booking.js
        hoists the address autocomplete into here at init so the
        autocomplete + structured fields read as one section)
     3. Pick tech (tech-easter-egg dropdown, inside step 2)
     4. Schedule (calendar, inside step 2)
     5. Submit (admin-flow-submit-row, lives outside step panels)

   Implemented entirely with CSS reordering — the public flow shares
   the same markup and behaves unchanged. */
form[data-admin-flow="1"] .stepper { display: none; }
/* Step-card padding + flex layout — scope DOUBLE-COVERED. The
   original `form[data-admin-flow="1"]` selector silently failed on
   at least one render path (sub-cards visibly flush against the
   step-card border in admin booking screenshots), so the same
   rules are also scoped under .booking-page--admin which we know
   is being applied (it's what activates the 2-column grid with
   the customer rail on the left). Belt-and-suspenders: whichever
   selector matches, the padding + flex layout still fires.
   The two selectors share one rule body — equal specificity, no
   conflict. */
form[data-admin-flow="1"] .step-card,
.booking-page--admin form[data-admin-flow="1"] .step-card {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 0;
  height: auto;
  transition: none;
  max-width: none;
  width: 100%;
  overflow: visible;
  /* Padding — bumped to 32/40 so every inner sub-card (suggestions
     strip, calendar, textarea card, customer-info grid) has a clear
     inset from the step-card's edge. Earlier passes ran 26/30; the
     visible gap kept reading as "sub-cards touching the sides"
     because (a) 30px wasn't large enough at 1200px+ widths and
     (b) the step-card's old #FCFAF6 background was effectively the
     same color as the canvas (#FAF9F6), so the outer step-card
     border was invisible and the inner cards looked like they were
     floating directly on the page. We bump padding here AND give
     the step-card a visibly distinct surface below. */
  padding: 32px 40px;
  /* Step-card sits visibly above the warm canvas. Was #FFFCF4 (read
     as yellow). #FCFAF6 is the palette's standard warm-card cream —
     less yellow, still clearly brighter than the new #F3EDDF canvas
     (cream-on-cream contrast with ~10 hex points of headroom). Inner
     sub-cards use #FFFDF9 / lighter highlights so they still nest. */
  background: #FCFAF6;
  border-color: #DED5C8;
}
@media (max-width: 1100px) {
  form[data-admin-flow="1"] .step-card,
  .booking-page--admin form[data-admin-flow="1"] .step-card { padding: 26px 28px; }
}
@media (max-width: 760px) {
  form[data-admin-flow="1"] .step-card,
  .booking-page--admin form[data-admin-flow="1"] .step-card { padding: 20px 18px; }
}
/* Reordering — admin booking starts with the verified service address
   and schedule, then the job description, then editable customer and
   address details. */
form[data-admin-flow="1"] .step-panel[data-step="2"] { order: 1; }
form[data-admin-flow="1"] .step-panel[data-step="3"] { order: 2; }
form[data-admin-flow="1"] .step-panel[data-step="4"] { order: 3; }
form[data-admin-flow="1"] .admin-flow-submit-row { order: 4; }
form[data-admin-flow="1"] .step-panel[data-step="2"] > .actions { display: none !important; }
/* Drawer-internal Back/Continue buttons (rendered by calendar.js inside
   the .cal-drawer-tray when a date is picked) are step-nav buttons
   that don't belong on a single-page admin layout — the bottom
   "Schedule booking" CTA is the only submit. */
form[data-admin-flow="1"] .cal-drawer-actions { display: none !important; }
/* Keep the verified address control expanded in admin mode. The public
   flow collapses this into #step2-summary after verification, but the
   admin scheduler should show one address control at the top and never
   a second summary chip. */
form[data-admin-flow="1"] #step2-prelude,
form[data-admin-flow="1"] #step2-prelude.is-collapsed {
  display: block !important;
  max-height: none !important;
  opacity: 1 !important;
  visibility: visible !important;
  overflow: visible !important;
  pointer-events: auto !important;
  margin-bottom: 0 !important;
}
form[data-admin-flow="1"] #step2-summary { display: none !important; }

/* Section headings — only injected where the shared public-flow markup
   does not already provide a useful admin heading. Step 3 has its own
   <h2>; step 4 + the loaded calendar get admin labels so the single-
   page card scans as clear sections. The tech picker used to get one
   too, but its inline icon + "Technician filter" label now serve as
   the section header inline. */
/* Section headings — bolded label per block (Schedule / What is going
   on? / Customer information). No underline, no divider above. The
   labels alone carry the visual hierarchy and the page reads as a
   continuous form instead of stacked tiles. */
form[data-admin-flow="1"] #availability:not([hidden])::before {
  display: block;
  font-size: 1.05rem;
  font-weight: 800;
  letter-spacing: -0.015em;
  color: #373B4D;
  margin: 0 0 16px;
  content: 'Schedule';
}
form[data-admin-flow="1"] .step-panel[data-step="3"] h2 {
  font-size: 1.05rem;
  font-weight: 800;
  letter-spacing: -0.015em;
  color: #373B4D;
  margin: 0 0 16px;
}
form[data-admin-flow="1"] .step-panel[data-step="4"]::before {
  content: 'Customer information';
  display: block;
  font-size: 1.05rem;
  font-weight: 800;
  letter-spacing: -0.015em;
  color: #373B4D;
  margin: 0 0 16px;
}
/* Tech picker — strip the easter-egg pill chrome in admin mode so it
   reads as a regular section. The cream box + icon + "Specific tech"
   label make sense as a hidden customer-facing surprise; in admin
   mode where the picker is the primary surface for tech routing,
   we want a plain dropdown under the "Pick a tech" heading. */
form[data-admin-flow="1"] .tech-easter-egg {
  background: transparent;
  border: 0;
  padding: 0;
  margin: 0;
  max-height: none;
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  transition: none;
}
form[data-admin-flow="1"] .step-panel[data-step="2"] {
  display: grid;
  /* Was 22px between the address card, tech picker, suggestions, and
     calendar. The mockup ran at ~14px and read tighter without
     blurring the sub-card boundaries. */
  gap: 14px;
}
/* Tech filter row — icon + label + select sit inline as a single
   bento control row, left-anchored. The select sizes to its content
   (tech names) and the icon/label/select are dimensioned to feel
   proportional to a single-word value, not stretched to fill space.
   Extra bottom margin so the row reads as a distinct control above
   the calendar card rather than touching its top edge. */
form[data-admin-flow="1"] .tech-easter-egg {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 9px;
  flex-direction: row;
  margin-bottom: 6px;
}
form[data-admin-flow="1"] .tech-easter-egg-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  flex: 0 0 24px;
  background: #F3EFE7;
  border-radius: 999px;
  color: #7B8190;
}
form[data-admin-flow="1"] .tech-easter-egg-icon svg { width: 12px; height: 12px; }
form[data-admin-flow="1"] .tech-easter-egg-label {
  display: inline-block;
  font-size: 0.82rem;
  font-weight: 700;
  color: #373B4D;
  letter-spacing: -0.005em;
  flex: 0 0 auto;
  white-space: nowrap;
}
/* Custom-styled select — strips the native browser chrome and
   replaces the dropdown arrow with an inline-SVG background image
   (warm muted chevron, swaps to orange on hover/focus). Pill shape +
   warm card-highlight bg + soft border match the rest of the page's
   form controls per the philosophy doc. */
form[data-admin-flow="1"] .tech-easter-egg-select {
  /* Pill sized for a single tech name. Earlier passes ran 40px tall
     × 320px max-width, which felt huge for "Lincoln Ashcom" or
     "All". Tightened to 32px × 240px so the control reads as a
     compact filter chip, not a hero input. */
  flex: 0 1 auto;
  width: auto;
  max-width: 240px;
  min-height: 32px;
  padding: 6px 30px 6px 12px;
  background-color: #FFFDF9;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%237B8190' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'><path d='M1 1.5l5 5 5-5'/></svg>");
  background-repeat: no-repeat;
  background-position: right 11px center;
  background-size: 10px 6px;
  border: 1px solid #E5DED3;
  border-radius: 999px;
  color: #373B4D;
  font-size: 0.86rem;
  font-weight: 600;
  font-family: inherit;
  letter-spacing: -0.005em;
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  cursor: pointer;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.85) inset;
  transition: border-color 0.12s ease, background-color 0.12s ease,
              box-shadow 0.12s ease, color 0.12s ease;
}
form[data-admin-flow="1"] .tech-easter-egg-select:hover {
  border-color: rgba(243, 138, 63, 0.4);
  background-color: #FFFAF1;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23F38A3F' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'><path d='M1 1.5l5 5 5-5'/></svg>");
}
form[data-admin-flow="1"] .tech-easter-egg-select:focus {
  outline: none;
  border-color: #F38A3F;
  background-color: #FFFDF9;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23F38A3F' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'><path d='M1 1.5l5 5 5-5'/></svg>");
  box-shadow: 0 0 0 3px rgba(243, 138, 63, 0.22),
              0 1px 0 rgba(255, 255, 255, 0.85) inset;
}
form[data-admin-flow="1"] .tech-easter-egg-icon:hover,
form[data-admin-flow="1"] .tech-easter-egg:focus-within .tech-easter-egg-icon {
  color: #F38A3F;
  background: rgba(243, 138, 63, 0.14);
}
form[data-admin-flow="1"] #zone-message {
  margin: 0;
}

/* Property type — admin booking is overwhelmingly residential. Keep
   the radio inputs in the DOM (so the form ships property_type=
   'residential' by default) but hide the visual control. If the
   admin needs to file a commercial booking they can do it directly
   in HCP via the link at the bottom. */
form[data-admin-flow="1"] .property-type-field { display: none; }
form[data-admin-flow="1"] .commercial-notice { display: none; }

/* Admin-only "confirmed this is a residence" gate. Replaces the
   property-type pills (hidden above) with a single confirmation
   checkbox the admin must tick before submitting. The point isn't
   the data — every booking is residential anyway — it's the
   prompt: forcing an empty checkbox encourages the admin to
   actually ask the customer "is this a home?" instead of auto-
   assuming. Default-unchecked is the design.

   Visual treatment: warm-inset card with a custom-styled check box
   (philosophy avoids the OS default look) + label + small hint.
   Validation state (.is-invalid) fires on submit attempt if still
   unchecked, with a coral border + inline error message. */
.admin-residential-confirm {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  margin: 10px 0 4px;
  background: #FCFAF6;
  border: 1px solid #E5DED3;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.14s ease, border-color 0.14s ease;
  position: relative;
}
.admin-residential-confirm:hover { background: #FFFAF1; }
/* Hide the native input — the custom .admin-residential-confirm-box
   is the visible check surface. The native input still tracks state
   + receives the click (the label proxies it). */
.admin-residential-confirm input[type="checkbox"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  width: 0; height: 0;
}
.admin-residential-confirm-box {
  flex: 0 0 15px;
  width: 15px;
  height: 15px;
  border: 1.5px solid #C9C2B0;
  border-radius: 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: #FFFFFF;
  color: #FFFFFF;
  transition: background 0.12s ease, border-color 0.12s ease;
}
.admin-residential-confirm-box svg { opacity: 0; transition: opacity 0.12s ease; }
.admin-residential-confirm input[type="checkbox"]:checked ~ .admin-residential-confirm-box {
  background: #4BAA78;
  border-color: #4BAA78;
}
.admin-residential-confirm input[type="checkbox"]:checked ~ .admin-residential-confirm-box svg { opacity: 1; }
.admin-residential-confirm input[type="checkbox"]:focus-visible ~ .admin-residential-confirm-box {
  box-shadow: 0 0 0 3px rgba(243, 138, 63, 0.22);
}
.admin-residential-confirm-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.admin-residential-confirm-text strong {
  font-size: 0.78rem;
  font-weight: 600;
  color: #373B4D;
  letter-spacing: -0.005em;
  line-height: 1.2;
}
.admin-residential-confirm-hint {
  font-size: 0.72rem;
  color: #7B8190;
  font-weight: 500;
  line-height: 1.35;
}
/* Invalid state — admin tried to submit without ticking. Coral
   border + soft wash mirrors the unpaid-coral semantic from the
   philosophy palette (clear, serious, not screaming). Auto-clears
   on the next change event in JS. */
.admin-residential-confirm.is-invalid {
  background: #FCEBE7;
  border-color: rgba(214, 95, 79, 0.55);
}
.admin-residential-confirm.is-invalid .admin-residential-confirm-box {
  border-color: #D65F4F;
}
.admin-residential-confirm-error {
  display: none;
  font-size: 0.74rem;
  color: #B14535;
  font-weight: 600;
  margin: 4px 14px 0;
}
.admin-residential-confirm.is-invalid + .admin-residential-confirm-error {
  display: block;
}

/* Compact-but-airy spacing — the admin user already knows what every
   field is for, so the public flow's hero breathing is wasted, but
   the previous 14px row-gap left labels and inputs cramped against
   each other. 18px row-gap pairs with the 8px label-to-input gap
   in .field for a comfortable read.

   max-width: none + margin: 0 overrides the public flow's 760px
   centered grid so the form fills the card's content area on
   admin. Was leaving ~80-160px of empty padding on either side of
   the grid even when the card itself was full-width. */
form[data-admin-flow="1"] .your-info-grid {
  row-gap: 18px;
  column-gap: 18px;
  grid-template-columns: repeat(2, minmax(220px, 1fr));
  max-width: none;
  margin: 0;
  align-items: start;
}
@media (max-width: 720px) {
  form[data-admin-flow="1"] .your-info-grid {
    grid-template-columns: 1fr;
  }
  form[data-admin-flow="1"] .your-info-grid .span-2 {
    grid-column: auto;
  }
}
form[data-admin-flow="1"] .ledger-field { margin: 0; }

/* The verified service address lives at the top of admin step 2, above
   the tech picker and calendar. Strip the public flow's narrow centering
   so the input lines up with the rest of the admin card. */
form[data-admin-flow="1"] .admin-address-field {
  max-width: none;
  margin: 0;
  width: 100%;
}
form[data-admin-flow="1"] .admin-address-field .address-input-wrap,
form[data-admin-flow="1"] .admin-address-field input {
  width: 100%;
}

/* Step 4's address-summary block duplicates the verified address field
   at the top of the admin scheduler. Hide only that display summary;
   the structured inputs (.address-edit-field rows of street/city/state)
   stay editable and submit normally. */
form[data-admin-flow="1"] .step-panel[data-step="4"] .address-summary { display: none; }

/* Strip the customer-flow validation chrome in admin mode so the form
   reads as plain, consistent input pills (matches the mockup). The
   public flow uses these affordances heavily — green check circles
   confirm each field validated, autofilled address rows lock to a
   muted "confirmed" state with a pencil to unlock for edit. Admins
   don't need any of that: they're filling phone-in bookings and the
   green-check / locked / pencil chrome is visual noise. The inputs
   themselves still validate (form submit still gates on required),
   just no visible decoration. */
form[data-admin-flow="1"] .field-check,
form[data-admin-flow="1"] .field-edit-btn {
  display: none !important;
}
/* Reset the locked-state styling so prefilled fields look identical
   to blank ones. Without this, an autofilled street/city/state read
   as grey/muted inset boxes while a blank first/last name reads as
   the warm-card pill. */
form[data-admin-flow="1"] .field.is-locked input,
form[data-admin-flow="1"] .field.is-locked input:hover,
form[data-admin-flow="1"] .field.is-locked input:focus {
  background: #FFFDF9 !important;
  border-color: #E5DED3 !important;
  color: var(--text);
  cursor: text;
  font-weight: 400;
  padding-right: 14px !important;
  -webkit-text-fill-color: var(--text);
}
/* Remove the right-side padding reservation the check icon needed,
   since the icon is hidden in admin mode. */
form[data-admin-flow="1"] .your-info-grid .field.has-check input {
  padding-right: 14px;
}
/* Strip the emerald border-tint on .is-valid — the green check was
   the primary confirmation signal, the border was secondary. With
   the check hidden, leaving the border in green would just confuse. */
form[data-admin-flow="1"] .field.is-valid input {
  border-color: #E5DED3;
}

/* Trip-charge agreement IS shown in admin mode — it acts as a
   memory prompt so the admin remembers to ask the customer on the
   phone and only ticks the box once they've confirmed verbally.
   The hidden trip_charge_amount input nested inside still ships
   with the form. Re-style the wrapper to fit the admin bento
   aesthetic: warm card-highlight fill against the step-card's warm
   card surface, soft warm border, no big padding — reads as a
   compact confirmation row, not a callout. */
form[data-admin-flow="1"] .trip-charge-agree {
  background: #FFFDF9;
  border: 1px solid #E5DED3;
  border-radius: 14px;
  padding: 12px 16px;
  margin: 18px 0 4px;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.85) inset;
}
form[data-admin-flow="1"] .trip-charge-agree-row {
  display: flex;
  align-items: center;
  gap: 12px;
  cursor: pointer;
}
form[data-admin-flow="1"] .trip-charge-agree-text {
  font-size: 0.92rem;
  color: var(--text);
  line-height: 1.45;
}
form[data-admin-flow="1"] .trip-charge-agree-text strong {
  color: #373B4D;
  font-weight: 700;
}

/* Hide every per-step Back/Continue group. The one button we DO want
   visible (the bottom "Schedule booking" submit) lives outside the
   step panels in .admin-flow-submit-row. !important here because some
   per-step rules later in the file (e.g. step 3's max-width centering
   on `.step-panel[data-step="3"] > .actions`) match with equal
   specificity and were declared after, so the cascade would otherwise
   make .actions visible. */
form[data-admin-flow="1"] .step-panel .actions,
.booking-page--admin form[data-admin-flow="1"] .step-panel .actions { display: none !important; }

/* Per-step section margins — strip the cosmetic chrome so each
   panel reads as a section, not a separate "page." The flex gap on
   the step-card supplies all the visual separation. The min-height
   reset is critical: the public flow gives every panel a 540px
   min-height so the FLIP morph has something to interpolate between.
   In admin mode all panels are visible at once, so that floor turns
   into hundreds of pixels of empty scroll-space below short sections
   like "Describe the issue."

   Dual-scoped (same reason as the step-card padding above) so it
   can't silently fail if data-admin-flow isn't set. Without the
   step-panel padding reset, the public flow's 48/52 step-panel
   padding would compound with the new 40/56 step-card padding. */
form[data-admin-flow="1"] .step-panel,
.booking-page--admin form[data-admin-flow="1"] .step-panel {
  padding: 0;
  background: transparent;
  border: 0;
  box-shadow: none;
  margin: 0;
  min-height: 0;
}
/* Step 2 in admin contains the address verifier, tech picker, and
   calendar; strip its own internal padding so it lines up with the rest. */
form[data-admin-flow="1"] .step-panel[data-step="2"],
.booking-page--admin form[data-admin-flow="1"] .step-panel[data-step="2"] { padding: 0; min-height: 0; }
form[data-admin-flow="1"] .step-panel[data-step="2"] > *,
.booking-page--admin form[data-admin-flow="1"] .step-panel[data-step="2"] > * {
  margin-left: 0 !important;
  margin-right: 0 !important;
}
form[data-admin-flow="1"] .step-panel[data-step="2"] > #availability,
.booking-page--admin form[data-admin-flow="1"] .step-panel[data-step="2"] > #availability {
  margin-top: 0;
}
/* Steps 1 + 3 + 4 also each carry their own padding/min-height
   overrides (step 1's service grid, step 3's tight text panel, step
   4's centered grid). None of those apply in admin mode — every
   section sits flat inside the step-card and shares its rhythm. */
form[data-admin-flow="1"] .step-panel[data-step="1"],
form[data-admin-flow="1"] .step-panel[data-step="3"],
form[data-admin-flow="1"] .step-panel[data-step="4"],
.booking-page--admin form[data-admin-flow="1"] .step-panel[data-step="1"],
.booking-page--admin form[data-admin-flow="1"] .step-panel[data-step="3"],
.booking-page--admin form[data-admin-flow="1"] .step-panel[data-step="4"] {
  padding: 0;
  min-height: 0;
}
form[data-admin-flow="1"] .step-panel[data-step="3"],
form[data-admin-flow="1"] .step-panel[data-step="4"] {
  /* Section rhythm — whitespace only, no visible divider rule.
     The earlier hairline border-top kept slicing the page into
     three labeled tiles when the natural reading flow was a
     single continuous form. With the section labels still bolded
     above each block, the visual hierarchy holds without a line.
     Bumped the gap to 28px to give each section a confident
     breathing room now that there's no rule to anchor on. */
  padding-top: 28px;
  margin-top: 8px;
  border-top: 0;
}
/* Step 3's contents (heading + textarea + actions) center inside an
   880px window in the public flow so the textarea has presence
   without sprawling. In admin mode every section needs to share the
   step-card's content width — otherwise the "Describe the issue"
   textarea sits in a narrow column while the customer-info grid
   below it spans the full width. */
form[data-admin-flow="1"] .step-panel[data-step="3"] > .heading-serif,
form[data-admin-flow="1"] .step-panel[data-step="3"] > .ledger-field,
form[data-admin-flow="1"] .step-panel[data-step="3"] > .field,
form[data-admin-flow="1"] .step-panel[data-step="3"] > .actions {
  max-width: none;
  margin-left: 0;
  margin-right: 0;
}

/* Work-summary textarea — convert the ledger ("writing on lined
   paper") style to a proper bento field in admin mode. The public
   flow uses the bare underline because the customer is filling in a
   single thought; in admin the textarea reads as "floating text on
   canvas" without a visible field card around it. Bento treatment:
   warm card-highlight fill, warm hairline border, 14px squircle, soft
   inset highlight, internal padding so the text + placeholder don't
   hug the edges. */
form[data-admin-flow="1"] .ledger-field { gap: 8px; }
form[data-admin-flow="1"] .textarea-ledger {
  border: 1px solid #E5DED3;
  border-radius: 14px;
  background: #FFFDF9;
  padding: 14px 16px;
  /* Override the textarea's rows="5" default (which renders ~150px
     tall) and let admins drag-resize vertically when they need
     more room. Cap so it can't blow out the page. */
  height: 96px;
  min-height: 96px;
  max-height: 280px;
  resize: vertical;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.85) inset;
  font-size: 1rem;
  line-height: 1.5;
  color: #373B4D;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
form[data-admin-flow="1"] .textarea-ledger::placeholder {
  color: #7B8190;
  font-style: normal;
  opacity: 1;
}
form[data-admin-flow="1"] .textarea-ledger:hover:not(:focus) {
  border-color: rgba(243, 138, 63, 0.35);
  background: #FFFAF1;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.85) inset;
}
form[data-admin-flow="1"] .textarea-ledger:focus {
  outline: none;
  background: #FFFDF9;
  border-color: #F38A3F;
  box-shadow:
    0 0 0 3px rgba(243, 138, 63, 0.22),
    0 1px 0 rgba(255, 255, 255, 0.85) inset;
}
/* Char count + Required label sit beneath the textarea, not against
   its underline (no underline anymore). Tight upper margin since the
   textarea now has its own border-bottom. */
form[data-admin-flow="1"] .ledger-meta {
  padding: 0 4px;
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  color: #7B8190;
}
form[data-admin-flow="1"] .ledger-required {
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  color: #7B8190;
}
/* Tech picker → calendar gap. The tech picker is its own section
   with its own ::before heading, so leave a clean gap before the
   calendar heading kicks in. The step-card's 32px flex gap handles
   the major section-to-section rhythm; this nudges the tech-picker
   section closer to its calendar so the pair reads as related (both
   pick "when to send a tech"). */
form[data-admin-flow="1"] .tech-easter-egg { margin-bottom: 0; }

form[data-admin-flow="1"] .admin-calendar-placeholder {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 22px 24px;
  /* Bento-style empty state: warm dashed border, warm inset fill,
     larger squircle radius matching the rest of the form's sub-cards.
     Reads as "this section is waiting" without feeling like an alert. */
  border: 1px dashed rgba(243, 138, 63, 0.35);
  border-radius: 16px;
  background: #F3EFE7;
  color: #7B8190;
}
form[data-admin-flow="1"] .admin-calendar-placeholder[hidden] { display: none !important; }
form[data-admin-flow="1"] .admin-calendar-placeholder strong {
  color: #373B4D;
  font-size: 1rem;
  font-weight: 800;
  letter-spacing: -0.01em;
}
form[data-admin-flow="1"] .admin-calendar-placeholder span {
  font-size: 0.88rem;
  line-height: 1.5;
}
form[data-admin-flow="1"] #availability:not([hidden]) {
  display: block;
  min-width: 0;
}
form[data-admin-flow="1"] .admin-cal {
  border-radius: 18px;
  padding: 20px;
}
form[data-admin-flow="1"] .admin-cal-header {
  gap: 12px;
  margin-bottom: 14px;
}
form[data-admin-flow="1"] .admin-cal-meter {
  /* Keep the hero-scale gap/padding from the base rule rather than
     tightening them — the admin scheduler is the canonical use of
     this meter and deserves the editorial-KPI presence per the
     design philosophy doc. */
  gap: 22px 36px;
  margin-bottom: 16px;
}
form[data-admin-flow="1"] .admin-cal-grid-wrap {
  max-width: 100%;
}
form[data-admin-flow="1"] .admin-cal-grid {
  /* Flipped axes: tech-label column (left, fits "Firstname Lastname")
     + N time-slot columns. Slots are the small fixed axis (3-4 per
     day at the 3-hour cadence), so each column gets equal share of
     the remaining width. */
  grid-template-columns: 180px repeat(var(--num-slots, 1), minmax(140px, 1fr));
}
@media (max-width: 720px) {
  form[data-admin-flow="1"] .admin-cal {
    padding: 16px;
    border-radius: 16px;
  }
  /* Phone: keep the clustered nav (prev | date | next) center-aligned;
     the cluster fits comfortably even on narrow viewports because the
     nav buttons are now circular 36px icons and the date trigger
     auto-shrinks via padding. No longer need the old wrap-and-stack
     layout that was tuned for the bigger pill-shaped Prev/Next buttons. */
  form[data-admin-flow="1"] .admin-cal-top-row {
    /* Phone: drop the centering spacer so the date pill flows left
       and Today stays right. Tight enough that they share one line. */
    gap: 8px;
  }
  form[data-admin-flow="1"] .admin-cal-top-spacer { display: none; }
  form[data-admin-flow="1"] .admin-cal-strip-cell {
    padding: 4px 3px 8px;
  }
  form[data-admin-flow="1"] .admin-cal-strip-date {
    font-size: 0.96rem;
  }
  form[data-admin-flow="1"] .admin-cal-grid {
    /* Phone: tighter tech-label column, slots still fill remaining
       width. Same flipped axis logic as the desktop rule above. */
    grid-template-columns: 140px repeat(var(--num-slots, 1), minmax(96px, 1fr));
  }
}

/* Submit row — sits inside the step-card, after the last step panel.
   The step-card's flex gap handles the major rhythm; no border-top
   needed (the Review sub-card below already provides a strong visual
   boundary, and the prior hairline read as an orphaned divider with
   no card chrome to anchor it). */
.admin-flow-submit-row {
  padding-top: 24px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  align-items: center;
}

/* Review block — recap of the form contents, sits above the submit
   button. Each row hides itself when the corresponding value is
   empty; the empty-state line shows when no values are populated yet.
   Booking.js (updateAdminReview) drives the populate/hide.

   Bento sub-card: warm card-highlight fill lifts off the step-card's
   own warm card surface, warm soft border + 16px squircle, generous
   padding, soft ambient shadow with warm undertone. */
.admin-flow-review {
  align-self: stretch;
  background: #FFFDF9;
  border: 1px solid #E5DED3;
  border-radius: 16px;
  padding: 20px 22px;
  box-shadow:
    0 6px 18px rgba(55, 59, 77, 0.04),
    0 1px 0 rgba(255, 255, 255, 0.85) inset;
}
.admin-flow-review-heading {
  /* Hero-ish recap title: heavy deep navy ink. Was a serif italic
     header; the bento system uses one geometric sans-serif family
     with weight contrast doing the hierarchy work. */
  font-size: 0.78rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #7B8190;
  margin: 0 0 14px;
  padding-bottom: 10px;
  border-bottom: 1px solid #E5DED3;
}
/* Plan block — "When you submit" preview that lists what will
   happen to HCP (file under existing vs create new, plus per-field
   updates). Sits between the Review heading and the existing key/
   value review rows. Warm inset wash so it reads as a deliberate
   summary, not just another row in the list. */
.admin-flow-plan {
  margin: 0 0 14px;
  padding: 12px 14px;
  background: #F3EFE7;
  border: 1px solid #E5DED3;
  border-radius: 12px;
}
.admin-flow-plan[hidden] { display: none; }
.admin-flow-plan-eyebrow {
  display: block;
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #7B8190;
  margin-bottom: 8px;
}
.admin-flow-plan-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.admin-flow-plan-item {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  font-size: 0.88rem;
  line-height: 1.4;
  color: var(--text);
}
.admin-flow-plan-bullet {
  flex: 0 0 7px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  margin-top: 7px;
  background: #7B8190;
}
/* Header bullet (the "file under existing X" / "create new X" row)
   is the lede; deep navy so it reads as the primary action. */
.admin-flow-plan-item--header .admin-flow-plan-bullet {
  background: #373B4D;
}
.admin-flow-plan-item--header .admin-flow-plan-text {
  font-weight: 700;
  color: #373B4D;
}
/* Update bullets — orange dot mirrors the per-field .is-modified
   indicator on the form fields above so the admin can connect
   "this field has a dot" with "this bullet describes the change." */
.admin-flow-plan-item--update .admin-flow-plan-bullet {
  background: #F38A3F;
}
/* Warn bullets — used for slot overrides (double-book, force-book past
   slot, force-book closed day). Red dot + bolder text + tinted
   background so the admin can't miss it. This is the second-of-two
   warnings the admin sees for a forced slot pick: the calendar popup
   confirms intent, this bullet re-states what's about to happen at
   submit time. */
.admin-flow-plan-item--warn {
  background: rgba(196, 53, 53, 0.07);
  border: 1px solid rgba(196, 53, 53, 0.22);
  border-radius: 10px;
  padding: 8px 10px;
  align-items: center;
}
.admin-flow-plan-item--warn .admin-flow-plan-bullet {
  margin-top: 0;
  background: #C43535;
  box-shadow: 0 0 0 3px rgba(196, 53, 53, 0.15);
}
.admin-flow-plan-item--warn .admin-flow-plan-text {
  color: #8B1F1F;
  font-weight: 600;
}
.admin-flow-plan-text {
  flex: 1 1 auto;
  min-width: 0;
}
.admin-flow-review-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.admin-flow-review-row {
  display: grid;
  grid-template-columns: 104px 1fr;
  gap: 16px;
  align-items: baseline;
  font-size: 0.92rem;
  line-height: 1.4;
}
.admin-flow-review-row[hidden] { display: none; }
.admin-flow-review-label {
  /* Label = uppercase eyebrow. Strong letter-spacing, muted graphite,
     800 weight so it reads as a label not a sentence. */
  color: #7B8190;
  text-transform: uppercase;
  letter-spacing: 0.10em;
  font-size: 0.66rem;
  font-weight: 800;
}
.admin-flow-review-value {
  /* Value = deep navy hero ink, slightly heavier weight so the
     filled rows visually anchor against the labels. */
  color: #373B4D;
  font-weight: 600;
  word-break: break-word;
  font-variant-numeric: tabular-nums;
}
.admin-flow-review-empty {
  margin: 0;
  font-size: 0.85rem;
  color: #7B8190;
  font-style: italic;
}
.admin-flow-review-empty[hidden] { display: none; }
@media (max-width: 600px) {
  .admin-flow-review { padding: 16px 16px 14px; border-radius: 14px; }
  .admin-flow-review-row {
    grid-template-columns: 1fr;
    gap: 2px;
  }
}
/* Hero CTA — this is THE action on the admin booking page. Bigger
   pad, slightly heavier weight, and a soft orange ring under the
   pill so it carries the visual weight of "primary action" per
   philosophy doc: "solid blocks of the accent color... text inside
   is bold and centered." */
.admin-flow-submit-row .btn-primary {
  align-self: center;
  justify-content: center;
  min-width: 260px;
  padding: 16px 40px;
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.005em;
  box-shadow:
    0 8px 22px rgba(243, 138, 63, 0.28),
    0 1px 0 rgba(255, 255, 255, 0.20) inset;
  transition: background 0.18s ease, box-shadow 0.18s ease, transform 0.08s ease;
}
.admin-flow-submit-row .btn-primary:hover:not(:disabled) {
  box-shadow:
    0 12px 28px rgba(243, 138, 63, 0.36),
    0 1px 0 rgba(255, 255, 255, 0.22) inset;
}
.admin-flow-submit-row .btn-primary:active:not(:disabled) {
  box-shadow:
    0 4px 14px rgba(243, 138, 63, 0.30),
    0 1px 0 rgba(255, 255, 255, 0.18) inset;
}
.admin-flow-submit-row .alert {
  margin: 0;
  align-self: stretch;
}
/* Housecall Pro escape-hatch link — quiet so it doesn't compete with
   the primary submit, but always visible at the bottom for the admin
   to jump to HCP for anything the booking page can't do (reschedules,
   line-item edits, attaching photos, refunds, etc.). */
.admin-flow-hcp-link {
  align-self: center;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  margin-top: 4px;
  border: 1px solid rgba(39, 41, 54, 0.10);
  border-radius: 999px;
  background: rgba(39, 41, 54, 0.02);
  font-size: 0.85rem;
  color: var(--text-muted);
  text-decoration: none;
  transition: background 0.15s, color 0.15s, border-color 0.15s, transform 0.05s;
}
.admin-flow-hcp-link:hover {
  background: rgba(39, 41, 54, 0.06);
  color: var(--text);
  border-color: rgba(39, 41, 54, 0.16);
}
.admin-flow-hcp-link:active {
  transform: translateY(1px);
}
.admin-flow-hcp-link svg {
  flex-shrink: 0;
  opacity: 0.7;
}

/* ───── Easter-egg tech picker ─────
   Hidden by default. Three clicks on the home icon inside the
   collapsed #step2-summary chip reveal it. Sits BETWEEN the address
   summary and the calendar, deliberately sharing the summary chip's
   visual language (cream tint, charcoal-soft border, 14px radius)
   so it reads as a related "where + when" refinement, not a separate
   admin tool.

   Reveal mechanics:
   - HTML ships with the `hidden` attribute as a no-JS / SSR
     fallback — the element really is display:none until JS loads.
   - On JS init, the booking script removes the `hidden` attribute
     and the COLLAPSED ruleset below takes over: the element stays
     in layout but has zero visible footprint (max-height 0,
     opacity 0, visibility hidden, pointer-events off, no padding,
     no border, untabbable). Click count <3 keeps it that way.
   - Three clicks on the home icon adds .is-revealed, transitioning
     max-height + padding + margin + border + opacity together for
     a smooth grow-in. Visibility flips at the start of the reveal
     and at the END of the collapse (delay) so keyboard focus only
     enters when the picker is fully open. */
.tech-easter-egg {
  display: flex;
  align-items: center;
  gap: 12px;
  background: #FAF7F1;
  border: 1px solid rgba(39, 41, 54, 0.10);
  border-radius: 14px;
  /* Collapsed visual: zero space + invisible + untabbable. */
  max-height: 0;
  margin-top: 0;
  padding: 0 14px;
  border-top-width: 0;
  border-bottom-width: 0;
  opacity: 0;
  overflow: hidden;
  visibility: hidden;
  pointer-events: none;
  transition:
    max-height 0.45s cubic-bezier(0.22, 1, 0.36, 1),
    opacity 0.30s ease,
    padding 0.45s cubic-bezier(0.22, 1, 0.36, 1),
    margin-top 0.45s cubic-bezier(0.22, 1, 0.36, 1),
    border-top-width 0s linear 0.45s,
    border-bottom-width 0s linear 0.45s,
    visibility 0s linear 0.45s;
}
/* No-JS / pre-init fallback. Without this, view-source visitors and
   disabled-JS visitors would still get a real (zero-sized but DOM-
   present) element. The hidden attribute keeps it truly gone. */
.tech-easter-egg[hidden] { display: none !important; }
.tech-easter-egg.is-revealed {
  max-height: 100px;
  margin-top: 10px;
  padding: 10px 14px;
  border-top-width: 1px;
  border-bottom-width: 1px;
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition:
    max-height 0.45s cubic-bezier(0.22, 1, 0.36, 1),
    /* Slight opacity delay so the fade-in lands AFTER the height
       has started growing — feels like the row "opens", not flashes. */
    opacity 0.32s ease 0.08s,
    padding 0.45s cubic-bezier(0.22, 1, 0.36, 1),
    margin-top 0.45s cubic-bezier(0.22, 1, 0.36, 1),
    border-top-width 0s linear 0s,
    border-bottom-width 0s linear 0s,
    visibility 0s linear 0s;
}
.tech-easter-egg-icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  color: var(--text-muted);
}
.tech-easter-egg-icon svg { width: 18px; height: 18px; }
.tech-easter-egg-label {
  flex: 0 0 auto;
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--text);
}
.tech-easter-egg-select {
  flex: 1 1 auto;
  min-width: 0;
  appearance: none;
  -webkit-appearance: none;
  background-color: #FFFFFF;
  border: 1px solid rgba(39, 41, 54, 0.12);
  border-radius: 10px;
  padding: 8px 34px 8px 12px;
  font-size: 0.95rem;
  color: var(--text);
  cursor: pointer;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23272936' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'><path d='M4 6l4 4 4-4'/></svg>");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 14px;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.tech-easter-egg-select:focus {
  outline: none;
  border-color: var(--orange, #E07A2A);
  box-shadow: 0 0 0 3px rgba(224, 122, 42, 0.12);
}
.property-type-legend {
  display: block;
  font-weight: 600;
  font-size: var(--t-xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 8px;
  padding: 0;
}
.property-type-segmented {
  /* Bento segmented control: warm inset wash (was cool #F4F2EE),
     larger squircle radius. */
  display: inline-flex;
  background: #F3EFE7;
  border-radius: 14px;
  padding: 4px;
  gap: 4px;
}
@media (max-width: 600px) {
  .property-type-segmented { gap: 8px; }
}
.property-type-pill {
  position: relative;
  cursor: pointer;
  user-select: none;
}
.property-type-pill input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.property-type-pill > span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 9px 18px;
  font-size: 0.92rem;
  font-weight: 600;
  color: #7B8190;
  border-radius: 11px;
  transition: background 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}
@media (max-width: 600px) {
  .property-type-pill > span {
    padding: 14px 16px;
    font-size: 0.95rem;
    min-height: 48px;
  }
}
.property-type-pill > span svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}
.property-type-pill:hover > span { color: #373B4D; }
.property-type-pill input:checked + span {
  /* Selected = warm card-highlight fill, deep navy ink, soft warm
     shadow + 1px hairline border so the picked option reads as a
     lifted tactile chip (not the previous cool charcoal ring). */
  background: #FFFDF9;
  color: #373B4D;
  box-shadow:
    0 2px 8px rgba(55, 59, 77, 0.08),
    0 0 0 1px #E5DED3;
}

/* ═══ 12. Commercial notice ═══ */
/* ───── Commercial redirect notice ─────
   Warm card that softly explains why we don't book commercial here
   and offers a phone CTA. Animates in from above when revealed. */
.commercial-notice {
  /* Muted neutral notice — was orange-tinted bg/border/shadow which
     read as an alert/warning. The redirect message is informational,
     not an error, so it gets the same warm-paper-on-cream treatment
     as the rest of the page chrome. */
  display: flex;
  gap: 14px;
  padding: 18px 20px;
  background: var(--bg);
  border: 1px solid rgba(39, 41, 54, 0.10);
  border-radius: 14px;
  box-shadow: none;
  /* Capped + centered so the notice loads at its final display size
     and stays put while the surrounding card morphs from the wide
     calendar layout (1200px) down to the narrower commercial layout
     (720px). Without this cap, the notice expanded to fill the card
     and visibly shrunk along with the card width, reading as content
     that "loaded wrong then corrected itself." */
  max-width: 540px;
  margin-left: auto;
  margin-right: auto;
  animation: commercialNoticeIn 0.34s cubic-bezier(0.22, 1, 0.36, 1);
}
.commercial-notice[hidden] { display: none; }
.commercial-notice-icon {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 10px;
  background: rgba(39, 41, 54, 0.06);
  color: var(--text);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.commercial-notice-icon svg { width: 22px; height: 22px; }
.commercial-notice-body { flex: 1; min-width: 0; }
.commercial-notice h4 {
  margin: 0 0 6px;
  font-size: 1.02rem;
  font-weight: 650;
  color: var(--navy);
  letter-spacing: -0.005em;
}
.commercial-notice p {
  margin: 0;
  font-size: 0.92rem;
  line-height: 1.5;
  color: var(--text);
}
@keyframes commercialNoticeIn {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}

/* ═══ 13. Trip-charge agreement ═══ */
/* ───── Trip-charge agreement (booking step 4) ─────
   Sits above the Confirm button. Custom checkbox treatment so the tick
   reads as deliberate — same orange-warm palette as the rest of the
   booking flow. The Confirm button starts disabled (set via the EJS)
   and is wired in booking.js to enable when the box is checked. */
.trip-charge-agree {
  /* Muted neutral card — was a faint orange-tinted bg + border. The
     trip-charge agreement is a passive consent; the orange is reserved
     for the Confirm Booking button below it. */
  margin: 16px 0 4px;
  padding: 14px 16px;
  background: #FAF9F6;
  border: 1px solid rgba(39, 41, 54, 0.06);
  border-radius: 12px;
  /* Mobile: same padding (no change) — kept here as a hook for future. */
}
.trip-charge-agree-row {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  cursor: pointer;
  user-select: none;
  @media (max-width: 600px) { padding: 4px 0; }
}
.trip-charge-agree-row input[type="checkbox"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.trip-charge-agree-box {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  border: 1.5px solid rgba(39, 41, 54, 0.30);
  border-radius: 6px;
  background: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  transition: background 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
  margin-top: 1px;
  /* Mobile: bigger box for confident tap. */
  @media (max-width: 600px) { width: 24px; height: 24px; }
}
.trip-charge-agree-box svg { opacity: 0; transition: opacity 0.16s ease; }
.trip-charge-agree-row:hover .trip-charge-agree-box {
  border-color: var(--selected-ink);
}
.trip-charge-agree-row input[type="checkbox"]:checked + .trip-charge-agree-box {
  /* Charcoal "checked" — was orange. Same Single-Accent rule. */
  background: var(--selected-ink);
  border-color: var(--selected-ink);
}
.trip-charge-agree-row input[type="checkbox"]:checked + .trip-charge-agree-box svg {
  opacity: 1;
}
.trip-charge-agree-row input[type="checkbox"]:focus-visible + .trip-charge-agree-box {
  box-shadow: 0 0 0 3px rgba(39, 41, 54, 0.18);
}
.trip-charge-agree-text {
  font-size: 0.95rem;
  line-height: 1.45;
  color: var(--text);
  @media (max-width: 600px) { font-size: 0.95rem; line-height: 1.5; }
}
.trip-charge-agree-text strong {
  color: var(--text);
  font-weight: 700;
}
.trip-charge-agree-note {
  display: block;
  margin-top: 2px;
  font-size: 0.8rem;
  color: var(--text-muted);
}

/* ═══ 14. Trip-charge admin input ═══ */
/* ───── Trip-charge admin input ─────
   Per-location fee field in the location settings card. Inline $
   prefix tells admins the unit without an extra label. */
/* Wrapper owns the border + bg so the $ prefix and the number share a
   single visual control. Input is borderless and lays inside the
   wrapper, separated from $ by a thin divider so the eye reads them as
   "currency · amount" instead of two stacked glyphs. The earlier
   absolute-positioned $ overlapped the input's text area at certain
   font sizes. */
.trip-charge-input-wrap {
  display: inline-flex;
  align-items: stretch;
  max-width: 160px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #fff;
  overflow: hidden;
  transition: border-color 0.18s ease, box-shadow 0.18s ease;
}
.trip-charge-input-wrap:focus-within {
  border-color: var(--orange);
  box-shadow: 0 0 0 2px rgba(224, 122, 42, 0.18);
}
.trip-charge-prefix {
  display: inline-flex;
  align-items: center;
  padding: 0 10px;
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--text);
  background: var(--card-tint, #FAF7F1);
  border-right: 1px solid var(--border);
  user-select: none;
}
.trip-charge-input {
  font-family: inherit;
  font-size: 0.95rem;
  font-weight: 600;
  padding: 8px 12px;
  border: 0;
  border-radius: 0;
  background: transparent;
  width: 100%;
  -moz-appearance: textfield;
}
.trip-charge-input:focus { outline: none; }
.trip-charge-input::-webkit-outer-spin-button,
.trip-charge-input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
/* Content fade-in synced with the card morph. The card's height
   transition is 0.7s (cubic-bezier(0.4, 0, 0.2, 1)). The new panel
   fades from opacity 0 → 1 over 0.55s with a 0.18s delay so the card
   has visibly started morphing before the content materializes —
   reads as "the card reshapes, then the new content settles in"
   rather than "card and content both swap simultaneously." */
.step-panel:not([hidden]) {
  animation: stepFadeIn 0.55s cubic-bezier(0.4, 0, 0.2, 1) 0.18s backwards;
}
.step-panel h2 {
  margin: 0 0 6px;
  font-size: 1.4rem;
  color: var(--navy);
  font-weight: 700;
  letter-spacing: -0.01em;
}
.step-panel h2 + p.muted { margin-top: 0; margin-bottom: 24px; }

/* ═══ 15. Serif heading utility ═══ */
/* ───── Serif heading utility ─────
   Editorial system-serif stack — Charter on Apple, Iowan as a close
   sibling, Cambria/Georgia as fallbacks elsewhere. Used on premium
   prompts (Step 3 "How can we help you today?") so the question
   reads as an expert's voice rather than form chrome. */
.heading-serif {
  font-family: 'Charter', 'Iowan Old Style', 'Source Serif Pro', Cambria, Georgia, 'Times New Roman', serif;
  font-weight: 600;
  font-size: 1.85rem;
  line-height: 1.2;
  letter-spacing: -0.012em;
  color: var(--ink);
  margin: 0 0 28px;
}

/* ═══ 16. Step 3 ledger textarea ═══ */
/* ───── Ledger textarea (Step 3 description) ─────
   Bottom-rule-only input style. No box, no rounded chrome — feels
   like writing on lined paper instead of filling out a form. The
   underline broadens + turns orange on focus so the customer feels
   their pen has caught the page. */
/* Step 3 ("What is going on?") — public flow.
   Standard bordered textarea on a warm card surface — the familiar
   "comment box" shape most customers expect. Admin booking has its
   own compact layout overrides above this block and isn't affected. */
.ledger-field {
  display: flex;
  flex-direction: column;
  /* Bumped 10 → 16 to give the new bottom-edge progress bar room to
     sit below the textarea without crowding the counter row beneath.
     The bar uses absolute-positioning bottom: -12px, so the gap
     needs to be at least that plus the bar's height (5px) plus a
     few px breathing room before the counter text. */
  gap: 16px;
  width: 100%;
}
.textarea-ledger {
  width: 100%;
  border: 1px solid #E5DED3;
  border-radius: 14px;
  background: #FFFDF9;
  padding: 14px 16px;
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.5;
  color: var(--ink);
  resize: vertical;
  min-height: 140px;
  max-height: 360px;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.7) inset;
  transition: border-color 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
  -webkit-appearance: none;
}
.textarea-ledger::placeholder {
  color: var(--text-muted);
  opacity: 0.95;
}
.textarea-ledger:hover:not(:focus) {
  border-color: #C9BDA8;
}
.textarea-ledger:focus {
  outline: none;
  border-color: var(--orange);
  box-shadow:
    0 0 0 3px rgba(243, 138, 63, 0.18),
    0 1px 0 rgba(255, 255, 255, 0.7) inset;
}

/* Meta row below the textarea — char count + (optional) required
   indicator. */
.ledger-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  font-size: 0.82rem;
  letter-spacing: 0.02em;
  color: var(--text-muted);
}

/* Textarea-with-progress-bar wrapper. Positioning context for the
   bottom-edge fill bar that visualizes progress toward the 20-char
   minimum. The wrapper takes the textarea's outer dimensions; the
   fill bar lives at its bottom edge, inset just enough to land on
   top of the textarea's bottom border without escaping the rounded
   corners. --char-progress is set by JS as a 0..1 value on the
   ledger-field root each time the textarea fires input. */
.ledger-field-wrap {
  position: relative;
  width: 100%;
}
.textarea-ledger-progress {
  position: absolute;
  left: 12px;
  right: 12px;
  /* Sits BELOW the textarea now (was bottom: 0 sitting on the border).
     The -12 / 5 combo lands the bar 7px below the textarea's bottom
     edge with a 5px-tall fill, so it reads as its own visual element
     rather than a thicker border. The .ledger-field gap was bumped
     to 16px (was 10px) so the counter row below still has room. */
  bottom: -12px;
  height: 5px;
  border-radius: 999px;
  background: rgba(55, 59, 77, 0.08);
  overflow: hidden;
  pointer-events: none;
  /* Hidden until the customer types something — empty state is the
     same chrome as the original "no progress bar" design. */
  opacity: 0;
  transition: opacity 0.25s ease;
}
.ledger-field[data-has-progress="1"] .textarea-ledger-progress {
  opacity: 1;
}
.textarea-ledger-progress::after {
  content: '';
  display: block;
  height: 100%;
  width: calc(var(--char-progress, 0) * 100%);
  background: var(--orange, #F38A3F);
  border-radius: 999px;
  transition: width 0.18s ease, background 0.28s ease;
}
.ledger-field[data-progress-complete="1"] .textarea-ledger-progress::after {
  background: #4BAA78; /* sage-green at the threshold */
}

/* Char-counter goal style. "12 / 20" before threshold, "20 ✓" after.
   The number color ramps from muted → orange (typing) → sage-green
   (goal met) via the .is-typing and .is-good classes set by JS. */
.char-count {
  font-variant-numeric: tabular-nums;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  transition: color 0.28s ease;
}
.char-count-current { font-weight: 600; }
.char-count-sep { opacity: 0.6; margin: 0 2px; }
.char-count-goal { opacity: 0.8; }
.char-count-check {
  display: inline-flex;
  margin-left: 4px;
  color: #4BAA78;
  opacity: 0;
  transform: scale(0.5) rotate(-20deg);
  transition: opacity 0.22s ease, transform 0.32s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.char-count.is-typing {
  color: var(--orange, #F38A3F);
}
.char-count.is-good {
  color: #2F7A52;
  font-weight: 600;
}
/* When the goal is reached, the "/ 20" separator + goal dim away
   and the green check pops in. Keeps the counter from forever
   flashing the target after the customer cleared it. */
.char-count.is-good .char-count-sep,
.char-count.is-good .char-count-goal {
  opacity: 0;
  width: 0;
  margin: 0;
  overflow: hidden;
  transition: opacity 0.22s ease, width 0.22s ease, margin 0.22s ease;
}
.char-count.is-good .char-count-check {
  opacity: 1;
  transform: scale(1) rotate(0);
}
@media (prefers-reduced-motion: reduce) {
  .textarea-ledger-progress,
  .textarea-ledger-progress::after,
  .char-count,
  .char-count-check { transition: none; }
}

.ledger-required {
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--text-muted);
}

@keyframes stepFadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
/* (stepIn / stepOut keyframes removed — old slide-and-scale entrance
    was replaced by the quieter fade above. Card resize handles space.) */

/* Hero entrance */
.hero { animation: heroIn 0.6s ease-out; }
@keyframes heroIn {
  from { opacity: 0; transform: translateY(-6px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ═══ 17. Step 1 service cards ═══ */
/* ─────────────────────────────────────────────────────────────────
   SERVICE CARDS — booking step 1
   Tactile, elastic interaction model:
   • Rest: subtle external lift (cards feel raised off the page)
   • Hover: scale 1.02 + warm cream surface (anticipation)
   • Press (:active): scale 0.96, fast (the squish)
   • Release: bouncy cubic-bezier with overshoot (snap into place)
   • Selected: external shadow REMOVED, swapped for an inset
     depression + thick inset orange border (the "depressed" feel)
   • Procedural separator line draws from center outward on open
   No chevron — title is the only indicator; visual state does the
   talking. Title sits vertically centered when collapsed; the bullet
   list grows below it on expand.
   ───────────────────────────────────────────────────────────────── */
.service-grid {
  /* 2 columns on desktop. Was auto-fit which fit 3-4 cards across at
     wider widths and made the cards feel cramped — explicit 2-col
     gives each card room to breathe and matches the customer's read
     of the form ("a couple of options at a time"). Gap is moderate
     — enough separation between cards but not so much that the list
     becomes a vertical hike on phones. */
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
  margin-bottom: 20px;
  align-items: start;
  /* Mobile: 2 columns stays — at 14px page padding + 6px column gap,
     each card is ~155-200px wide on common phone widths and
     comfortably fits short service names. */
  @media (max-width: 600px) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 6px;
    margin-bottom: 14px;
  }
}
.service-card {
  cursor: pointer;
  display: flex;
  /* Reserve the GPU layer up front so the elastic transform doesn't
     re-rasterize text on every press. Combined with transform: scale(1)
     at rest, this keeps subpixel rendering stable. */
  will-change: transform;
}
.service-card input { position: absolute; opacity: 0; pointer-events: none; }

.service-card-inner {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center; /* title vertically centered when collapsed */
  text-align: center;
  gap: 4px;
  /* Compact button-like card on desktop. Tighter than before so 4-6
     services don't push the Continue button below the fold on phones.
     Reads as a refined chip rather than a slab. */
  padding: 12px 18px;
  border: 1.5px solid #E5DED3;
  border-radius: 12px;
  /* Warm card surface that nests inside the canvas (#F8F5EF). The
     previous pure-#fff fill read as a bright white slab against the
     beige page background; the warm tint sits on the canvas the way
     a sheet of cream paper would, matching the bento-card palette
     used elsewhere in the admin surfaces. */
  background: #FCFAF6;
  min-height: 60px;
  width: 100%;
  font-weight: 500;
  color: var(--text);
  /* Default: soft external lift. Two-layer shadow gives both an
     ambient and contact feel — cards look pressable. */
  box-shadow:
    0 1px 1px rgba(39, 41, 54, 0.03),
    0 4px 12px rgba(39, 41, 54, 0.04);
  transform: scale(1);
  /* The "release" transition: bouncy cubic-bezier with slight overshoot.
     This is the spring back to rest after a press, and the snap when a
     card becomes selected. Press itself overrides this with a faster,
     non-bouncy curve below so the squish feels immediate. */
  transition:
    transform 0.42s cubic-bezier(0.34, 1.56, 0.64, 1),
    border-color 0.22s ease,
    background 0.22s ease,
    box-shadow 0.32s cubic-bezier(0.4, 0, 0.2, 1),
    color 0.22s ease;
  /* Mobile: compact pill in a 2-column grid. Tap target stays ≥44px.
     Padding is tight because the cards live side-by-side now and a
     roomier card would feel overfilled in the narrower column. */
  @media (max-width: 600px) {
    padding: 10px 10px;
    min-height: 48px;
    border-radius: 10px;
  }
}
.service-card.has-issues .service-card-inner {
  @media (max-width: 600px) { padding-top: 11px; padding-bottom: 11px; }
}

/* Hover (anticipation): scale up slightly and brighten the surface
   toward the lighter highlight tint so the card lifts out of the
   warm rest state. Border + shadow use the charcoal accent so the
   hover state stays neutral — orange is reserved for the picked
   state, and the contrast between charcoal-hover and orange-selected
   tells the customer at a glance which card is the active choice. */
.service-card:hover .service-card-inner {
  transform: scale(1.02);
  border-color: var(--selected-ink);
  background: #FFFDF9;
  box-shadow:
    0 2px 3px rgba(39, 41, 54, 0.05),
    0 8px 22px rgba(39, 41, 54, 0.10);
}

/* Press (the squish): drop below 100% briefly. Override the transition
   to a fast, non-bouncy curve so it feels snappy on the way down. The
   release back to rest/hover/selected uses the parent's bouncy curve
   and naturally springs with overshoot. */
.service-card:active .service-card-inner {
  transform: scale(0.96);
  transition:
    transform 0.09s ease-out,
    border-color 0.22s ease,
    background 0.22s ease,
    box-shadow 0.18s ease,
    color 0.22s ease;
}

/* Selected: warm orange highlight — matches the calendar day pill so
   the customer sees the same "picked" visual language across the
   booking flow. Filled orange wash instead of a pure orange border so
   the card actually reads as the active choice at a glance, with a
   soft orange-tinted glow to lift it off the canvas. */
.service-card input:checked + .service-card-inner {
  transform: scale(1);
  border-color: transparent;
  background: #FFE9D3;
  color: var(--navy);
  box-shadow:
    inset 0 0 0 1.5px var(--orange),
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    0 4px 16px rgba(224, 122, 42, 0.18);
}
/* While the card is selected AND being hovered, suppress the hover
   scale so the depressed feel survives. */
.service-card:hover input:checked + .service-card-inner {
  transform: scale(1);
}
/* Same idea for the press — selected cards still get a tiny squish
   on re-click, but no scale-up bounce on release. */

.service-name {
  font-weight: 650;
  font-size: 0.98rem;
  letter-spacing: -0.005em;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  transition: color 0.22s ease;
  line-height: 1.25;
  @media (max-width: 600px) { font-size: 0.88rem; line-height: 1.2; }
}
.service-card:hover .service-name,
.service-card input:checked + .service-card-inner .service-name {
  color: var(--navy);
}
.service-desc {
  font-size: 0.82rem;
  color: var(--text-muted);
  line-height: 1.4;
  margin-top: 2px;
  transition: color 0.22s ease;
  @media (max-width: 600px) { font-size: 0.74rem; }
}
.service-card input:checked + .service-card-inner .service-desc {
  color: var(--orange-dark);
}

/* Cards with an expandable issues list: same vertical centering as
   plain cards. The list grows below the title when the card is
   expanded, naturally pushing it to fit. */
.service-card.has-issues .service-card-inner {
  padding-top: 16px;
  padding-bottom: 14px;
}

/* ═══ 18. Service-card issues list ═══ */
/* ───── Expanded issues list ─────
   Collapsed: max-height 0, fully transparent. Open: max-height grows
   smoothly and a procedural separator line (::before) draws from
   center outward on a slight delay. */
/* Accordion body height animation via CSS Grid 0fr → 1fr.
   Why this and not max-height: max-height has to be a fixed pixel
   number, so it either undershoots real content (clipping) or
   overshoots (forcing the visible "fully open" moment to land at a
   fraction of the transition duration). With max-height 480 vs ~200px
   real content, the old card's "visible close" and the new card's
   "visible open" landed at different times, which made the row total
   height oscillate during a swap and the Continue button bounce.
   grid-template-rows 0fr → 1fr animates TRUE content height. At any t
   during a swap, old-shrink + new-grow = constant, so the row total
   stays put. */
.service-issues-wrap {
  display: grid;
  grid-template-rows: 0fr;
  margin-top: 0;
  transition:
    grid-template-rows 0.36s cubic-bezier(0.22, 1, 0.36, 1),
    margin-top 0.36s cubic-bezier(0.22, 1, 0.36, 1);
}
.service-card.is-expanded .service-card-inner .service-issues-wrap {
  grid-template-rows: 1fr;
  margin-top: 14px;
}
.service-issues-list {
  list-style: none;
  margin: 0;
  padding: 0;
  width: 100%;
  text-align: left;
  /* min-height: 0 + overflow: hidden are required so the grid track
     can actually collapse to 0 without content spilling out. */
  min-height: 0;
  overflow: hidden;
  position: relative;
  opacity: 0;
  transition: opacity 0.28s ease;
}
.service-card.is-expanded .service-card-inner .service-issues-list {
  opacity: 1;
}
/* Each bullet: custom dot via ::before (so we can color it orange and
   keep tight control over spacing). Items fade in on a small staggered
   delay when the parent card is expanded, giving the "reveal" effect.
   Note: opacity-only animation (no transform) — earlier we used a
   translateY for a slide-in but that puts the <li> on a GPU compositing
   layer at non-integer offsets, which renders small text blurry. The
   parent <ul>'s max-height transition already provides the slide-down
   effect for the whole list. */
.service-issues-list li {
  position: relative;
  padding-left: 14px;
  margin: 0 0 6px;
  font-size: 0.82rem;
  line-height: 1.4;
  color: var(--text);
  opacity: 0;
}
.service-issues-list li:last-child { margin-bottom: 0; }
.service-issues-list li::before {
  content: '';
  position: absolute;
  left: 2px;
  top: 0.55em;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  /* Bullets are charcoal — matches the divider line. Was orange. */
  background: var(--selected-ink);
  opacity: 0.7;
}
.service-card.is-expanded .service-card-inner .service-issues-list li {
  animation: bulletReveal 0.34s ease-out forwards;
}
.service-card.is-expanded .service-card-inner .service-issues-list li:nth-child(1) { animation-delay: 0.08s; }
.service-card.is-expanded .service-card-inner .service-issues-list li:nth-child(2) { animation-delay: 0.13s; }
.service-card.is-expanded .service-card-inner .service-issues-list li:nth-child(3) { animation-delay: 0.18s; }
.service-card.is-expanded .service-card-inner .service-issues-list li:nth-child(4) { animation-delay: 0.23s; }
.service-card.is-expanded .service-card-inner .service-issues-list li:nth-child(5) { animation-delay: 0.28s; }
.service-card.is-expanded .service-card-inner .service-issues-list li:nth-child(6) { animation-delay: 0.33s; }
.service-card.is-expanded .service-card-inner .service-issues-list li:nth-child(n+7) { animation-delay: 0.36s; }
.service-card.is-expanded .service-card-inner .service-issues-list li {
  color: var(--text);
}
@keyframes bulletReveal {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Continue button: hidden entirely until a service is picked. The
   disabled toggle is already wired in JS — we just translate that
   state into actual visibility. Wrapping .actions stays in the layout
   so we get a consistent gap above whatever follows. */
/* (Was `#continue-step-1[disabled] { display: none }` — replaced by
    the smooth opacity/visibility transition in the Progressive
    Exposure block below, which applies to ALL gated CTAs across
    steps 1–4 and supports a fade-in instead of a snap.) */
.step-panel[data-step="1"] .actions {
  display: flex;
  justify-content: flex-end;
  /* margin-top: auto in a flex column absorbs all available space
     above this element, anchoring it to the bottom of the panel.
     Combined with the panel's min-height, this means a card's
     expansion takes from the slack between grid and Continue
     instead of pushing Continue (and the page below) downward. */
  margin-top: auto;
  padding-top: 24px;
  min-height: 44px;
  /* Mobile: pin Continue to the bottom of the viewport while the
     user scrolls through services, releases naturally when the
     panel's bottom comes into view. Soft top fade so the service list
     dissolves under the button instead of bumping against a hard line.
     Negative horizontal margins extend the fade across the panel's
     side padding so the gradient reaches edge-to-edge. */
  @media (max-width: 600px) {
    position: sticky;
    bottom: 8px;
    z-index: 5;
    margin-top: 6px;
    margin-left: -16px;
    margin-right: -16px;
    padding: 18px 16px 6px;
    background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0, #fff 22px, #fff 100%);
  }
}
.step-panel[data-step="1"] #continue-step-1 {
  animation: continueIn 0.36s cubic-bezier(0.22, 1, 0.36, 1) backwards;
  box-shadow: 0 6px 16px rgba(224, 122, 42, 0.22);
  transition: box-shadow 0.22s ease, background 0.18s ease;
  /* Stronger lift on mobile while the button is floating, so it reads
     as elevated above the scrolling list rather than welded to the
     panel surface. */
  @media (max-width: 600px) { box-shadow: 0 6px 18px rgba(224, 122, 42, 0.32); }
}
.step-panel[data-step="1"] #continue-step-1:hover {
  box-shadow: 0 8px 22px rgba(224, 122, 42, 0.32);
}
@keyframes continueIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* Step 1 heading polish: a touch more presence on the question and a
   slightly warmer, longer subhead. Centered to balance the grid below. */
.step-panel[data-step="1"] > h2 {
  text-align: center;
  font-size: 1.55rem;
  letter-spacing: -0.018em;
  margin-bottom: 6px;
  @media (max-width: 600px) { font-size: 1.25rem; margin-bottom: 14px; }
}
.step-panel[data-step="1"] > p.muted {
  text-align: center;
  font-size: 0.95rem;
  margin-bottom: 28px;
  color: var(--text-muted);
  @media (max-width: 600px) { margin-bottom: 14px; font-size: 0.88rem; }
}

/* Locked work-summary — appears only after a service is picked */
.work-summary-wrap.is-locked {
  opacity: 0.4;
  pointer-events: none;
  filter: saturate(0.6);
  transition: opacity 0.32s ease, filter 0.32s ease;
}
.work-summary-wrap {
  transition: opacity 0.32s ease, filter 0.32s ease;
}
/* Note: hover/selected states for .service-card live in the unified
   service-card block earlier in this file. The previous `servicePop`
   scale-bounce animation was removed because the transform created a
   GPU-compositing layer on the card-inner that rendered the bullet
   text blurry. The new feedback is box-shadow elevation + a soft
   gradient surface — no transforms on text-bearing elements. */

/* ═══ 19. Forms / fields ═══ */
/* Forms */
.grid { display: grid; gap: 16px; }
.grid-2 {
  grid-template-columns: repeat(2, 1fr);
  @media (max-width: 600px) { grid-template-columns: 1fr; }
}
/* Step 4 (.your-info-grid) gets a roomier gap than the default .grid
   so the inputs don't crowd each other on the wider panel. The .grid
   gap stays at 16 for admin/elsewhere where density is preferred.
   max-width caps the input grid at a comfortable form-reading width
   so we don't spread inputs across the full 1040px panel — the panel
   chrome stays uniform with the other steps, but the form inside is
   the proportions a customer expects. */
.your-info-grid {
  gap: 22px;
  max-width: 760px;
  margin: 0 auto;
  /* Mobile: tighter gap (collapsed to 1-col so 22px between vertically
     stacked inputs is too much). */
  @media (max-width: 600px) { gap: 16px; }
}
.your-info-grid .field > span {
  @media (max-width: 600px) { font-size: 0.88rem; }
}
/* Step 3 (description) — content cap is wider than step 4's form
   because the heading + textarea benefit from a roomier reading
   measure (the heading is serif, the textarea is a single ledger
   line). Card itself is 1040px on this step; cap inner content at
   880 so the textarea has presence without sprawling. */
.step-panel[data-step="3"] > .heading-serif,
.step-panel[data-step="3"] > .ledger-field,
.step-panel[data-step="3"] > .field {
  max-width: 880px;
  margin-left: auto;
  margin-right: auto;
}
/* Actions row deliberately escapes the 880px cap so Back sits at the
   card's bottom-left corner and Continue at the bottom-right. The base
   .actions rule already applies `justify-content: space-between`, but
   it can only push the buttons as far apart as the row is wide — when
   the row was capped, the pair ended up clustered in the middle of a
   much wider card. Letting actions span the full card width restores
   the corner-to-corner layout. */
.step-panel[data-step="3"] > .actions {
  max-width: none;
  margin-left: 0;
  margin-right: 0;
  justify-content: space-between;
}
/* 8px between the eyebrow label and the input — was 6px, which was
   tight enough that the uppercase label felt glued to the field. */
.field { display: flex; flex-direction: column; gap: 8px; }
/* Modified-field indicator. Phase 2 of the customer-edit flow:
   when a matched customer is loaded and the admin edits a tracked
   identity field (name, email, phone, address), the .field wrapper
   gets .is-modified added by booking.js. A small orange dot
   appears next to the field label so the admin can scan the form
   at a glance and see what they've changed since the autofill.
   Submit-time preview block (admin-flow-review) lists the same
   diffs as plain-English bullets. Only present in admin mode — the
   public flow doesn't track edits since there's nothing to diff
   against. */
.field.is-modified > span:first-child {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
.field.is-modified > span:first-child::after {
  content: '';
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #F38A3F;
  box-shadow: 0 0 0 2px rgba(243, 138, 63, 0.18);
  flex: 0 0 7px;
}
/* `[hidden]` from the UA stylesheet has the same specificity as `.field`
   and loads first — without this our hidden Apt/Suite slot still shows
   because the .field rule wins. Force display:none for any hidden field. */
.field[hidden] { display: none !important; }
.field.span-2 {
  grid-column: span 2;
  @media (max-width: 600px) { grid-column: auto; }
}
.field > span {
  /* Field labels become eyebrow-style: tiny uppercase per the
     philosophy doc's typography hierarchy, deep-navy weight,
     stronger letter-spacing so they read as a label not a sentence. */
  font-weight: 700;
  font-size: 0.7rem;
  color: #7B8190;
  text-transform: uppercase;
  letter-spacing: 0.10em;
}
.field input, .field textarea, .field select {
  font-family: inherit;
  font-size: 1rem;
  padding: 12px 14px;
  /* Bento input: warm soft border (not cool gray), warm card fill
     (not pure white), squircle radius, soft inset highlight so the
     input reads as a recessed-yet-tactile surface. */
  border: 1px solid #E5DED3;
  border-radius: 14px;
  background: #FFFDF9;
  color: #373B4D;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.85) inset;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
.field input::placeholder,
.field textarea::placeholder { color: #7B8190; }
.field input:hover:not(:focus),
.field textarea:hover:not(:focus),
.field select:hover:not(:focus) {
  background: #FFFAF1;
  border-color: rgba(243, 138, 63, 0.35);
}
.field input:focus, .field textarea:focus, .field select:focus {
  outline: none;
  background: #FFFDF9;
  border-color: #F38A3F;
  box-shadow:
    0 0 0 3px rgba(243, 138, 63, 0.22),
    0 1px 0 rgba(255, 255, 255, 0.85) inset;
}
.field small { font-size: 0.78rem; color: #7B8190; }

/* ═══ 20. Cross-component mobile rules ═══ */
/* Multi-selector mobile rules that don't fit cleanly inside any one
   component. Single-selector / single-component mobile rules live as
   nested @media inside their base rule. Tiny-phone overrides for the
   booking page. */
@media (max-width: 380px) {
  .step-panel { border-radius: 16px; }
}
@media (max-width: 600px) {
  html,
  body {
    max-width: 100%;
    overflow-x: hidden;
  }
  body:has(.booking-page),
  .booking-page,
  #booking-form,
  .step-card,
  .step-panel {
    min-width: 0;
    max-width: 100%;
  }
  .booking-page {
    overflow-x: hidden;
    overflow-x: clip;
  }

  /* iOS will auto-zoom the page when an input with font-size <16px
     receives focus. Explicitly sizing every form input to 16px
     prevents that jarring zoom-in/zoom-out cycle as the user moves
     between fields. */
  .field input,
  .field textarea,
  .field select,
  .address-field input,
  .zip-field input {
    font-size: 16px !important;
    padding: 12px 14px;
  }
  .address-field input {
    /* re-add icon padding (overrides above generic) */
    padding-left: 44px;
    padding-right: 44px;
  }

  /* Primary CTAs full-width — easier thumb tap on phones. Back stays
     auto-width on the left so it doesn't dominate. The .actions row
     is already a flex row with space-between; on mobile we force
     primary to flex-grow so it spans the rest. */
  .step-panel .actions { gap: 12px; }
  .step-panel .actions .btn-primary {
    flex: 1 1 auto;
    min-width: 0;
  }
  /* Step 1 has only Continue (no Back), already centered. */
  .step-panel[data-step="1"] .actions { justify-content: stretch; }
  .step-panel[data-step="1"] .actions .btn-primary { width: 100%; }

  /* Pull Back + Continue out to the card's edges on phones so they
     sit in the corners where the thumb naturally lands. Two things
     conspire on mobile to bury them in the middle:
       1. .step-panel has 24px horizontal padding (the card's gutter
          for content like text + inputs), and the actions row sits
          inside it.
       2. Step 3 specifically has `max-width: 880px; margin: 0 auto`
          on its actions row — desktop centering that on a flex-column
          parent collapses the row to content-width on mobile, leaving
          big empty gutters on both sides.
     Cancel both on phones: kill step 3's max-width centering, and
     pull every step's actions row out by -16px against the panel's
     24px padding. Net result: Back sits ~8px from the card's left
     edge, Continue (flex-grown) reaches to ~8px from the right
     edge, and `justify-content: space-between` puts the gap in the
     middle where it belongs. */
  .step-panel > .actions,
  .step-panel[data-step="3"] > .actions {
    max-width: none;
    margin-left: -16px;
    margin-right: -16px;
  }
  .step-panel[data-step="3"] > .actions,
  .step-panel[data-step="4"] > .actions {
    max-width: none;
    margin-left: 0;
    margin-right: 0;
    margin-top: 30px;
    padding-top: 8px;
    gap: 18px;
    align-items: stretch;
  }
  .step-panel[data-step="3"] > .actions .btn,
  .step-panel[data-step="4"] > .actions .btn {
    min-height: 58px;
    padding: 13px 18px;
  }
  .step-panel[data-step="3"] > .actions .btn-secondary,
  .step-panel[data-step="4"] > .actions .btn-secondary {
    flex: 0 0 clamp(112px, 34%, 150px);
  }
  .step-panel[data-step="3"] > .actions .btn-primary,
  .step-panel[data-step="4"] > .actions .btn-primary {
    flex: 1 1 0;
    margin-left: 0;
  }

  /* Service-card hover/checked overrides — flatten the lift effects on
     mobile since hover doesn't really apply on touch and the lift can
     read as flicker on tap. The compact-pill sizing lives next to the
     base .service-card-inner rule. */
  .service-card:hover .service-card-inner {
    transform: scale(1);
    border-color: var(--border);
    background: #FFFFFF;
    box-shadow:
      0 1px 1px rgba(39, 41, 54, 0.03),
      0 4px 12px rgba(39, 41, 54, 0.05);
  }
  .service-card input:checked + .service-card-inner,
  .service-card:hover input:checked + .service-card-inner {
    transform: scale(1);
    border-color: var(--orange);
    background: #FAF7F1;
    box-shadow:
      inset 0 0 0 1px var(--orange),
      0 4px 14px rgba(224, 122, 42, 0.10);
  }

  /* Confirmed-address hint stays visible without overflowing on
     narrow screens (long city names). Lower-specificity selector
     than the .address-field .address-hint base above — kept here
     since the value matches the base's font-size on desktop. */
  .address-hint { font-size: 0.85rem; word-break: break-word; }
}

/* ═══ 24. Buttons ═══ */
/* Buttons — flat */
.btn {
  font-family: inherit;
  font-size: 1rem;
  font-weight: 600;
  padding: 12px 24px;
  border-radius: 999px;
  border: 0;
  cursor: pointer;
  transition: background 0.18s ease, color 0.18s ease, opacity 0.2s ease;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  position: relative;
}
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn:active:not(:disabled) { transform: scale(0.98); transition: transform 0.08s ease, background 0.18s ease; }
.btn-primary { background: var(--orange); color: #fff; }
.btn-primary:hover:not(:disabled) { background: var(--orange-dark); }

/* ═══ 25. Progressive exposure ═══ */
/* ───── Progressive exposure (booking flow) ─────
   Hide the Continue / Confirm CTA entirely until the current step is
   complete. Removes the visual "pressure" of seeing the next button
   when the user hasn't done anything yet. The buttons already toggle
   their `disabled` attribute via JS based on each step's gate
   (service picked, slot picked, work summary typed, trip-charge
   agreed) — we just translate that disabled state into a fade+rise
   reveal instead of the desaturated "click me but you can't" look.
   Scoped under .step-card so the specificity (1,2,0) matches the
   per-step button rules elsewhere — without that, .step-panel
   [data-step="1"] #continue-step-1 wins for step 1 and snaps the
   transition. The trailing `[id]` token also keeps it ahead of the
   plain .btn:disabled rule that dims admin buttons. */
.step-card #continue-step-1[disabled],
.step-card #continue-step-2[disabled],
.step-card #continue-step-3[disabled],
.step-card #submit-booking[disabled] {
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px);
  pointer-events: none;
  /* visibility flips at the END of the transition when going OUT
     so the fade reads cleanly; flips at the START going IN so the
     button is immediately interactive. */
  transition:
    opacity 0.32s ease,
    transform 0.42s cubic-bezier(0.22, 1, 0.36, 1),
    visibility 0s linear 0.32s;
}
.step-card #continue-step-1:not([disabled]),
.step-card #continue-step-2:not([disabled]),
.step-card #continue-step-3:not([disabled]),
.step-card #submit-booking:not([disabled]) {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition:
    opacity 0.32s ease,
    transform 0.42s cubic-bezier(0.22, 1, 0.36, 1),
    visibility 0s linear 0s;
}

/* ═══ 26. Button arrows ═══ */
/* ───── Button arrows ─────
   Inline SVG chevrons inside Back/Continue/Submit/Finish buttons. The
   .btn class already has display:inline-flex + gap:8px, so adding the
   arrow as a direct child gets correct spacing automatically. */
.btn-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  /* Default-state position. Hover and progressive-exposure rules
     below override transform with their own transitions. */
  transform: translateX(0);
  transition: transform 0.22s cubic-bezier(0.22, 1, 0.36, 1);
  will-change: transform;
}

/* Hover nudge — forward arrow scoots right, back arrow scoots left.
   Reads as "this button is going to take you that direction." */
.btn:hover:not(:disabled) .btn-arrow-forward { transform: translateX(3px); }
.btn:hover:not(:disabled) .btn-arrow-back    { transform: translateX(-3px); }
.btn:active:not(:disabled) .btn-arrow-forward { transform: translateX(1px); }
.btn:active:not(:disabled) .btn-arrow-back    { transform: translateX(-1px); }

/* Continue/Submit arrow entrance — runs as a one-shot @keyframes
   animation (not a transition) so the transform value reverts to
   the base after the animation ends, leaving the hover-nudge rule
   above free to take over without specificity conflicts.
   Delay 0.4s so the arrow lands AFTER the button itself has settled
   from its progressive-exposure fade-up. Elastic curve gives it a
   "nock into place" snap. */
.step-card #continue-step-1:not([disabled]) .btn-arrow-forward,
.step-card #continue-step-2:not([disabled]) .btn-arrow-forward,
.step-card #continue-step-3:not([disabled]) .btn-arrow-forward,
.step-card #submit-booking:not([disabled]) .btn-arrow-forward {
  animation: btnArrowEnter 0.5s cubic-bezier(0.34, 1.5, 0.64, 1) 0.4s backwards;
}
@keyframes btnArrowEnter {
  0%   { opacity: 0; transform: translateX(-10px); }
  100% { opacity: 1; transform: translateX(0); }
}
.btn-secondary {
  /* Bento ghost-card button: warm card fill, hairline warm border so
     it reads as a secondary chip beside the orange primary instead
     of a flat cool inset rectangle. */
  background: #FFFDF9;
  color: #373B4D;
  box-shadow: 0 0 0 1px #E5DED3 inset, 0 1px 0 rgba(255, 255, 255, 0.85) inset;
}
.btn-secondary:hover:not(:disabled) {
  background: #FFFAF1;
  box-shadow: 0 0 0 1px rgba(243, 138, 63, 0.42) inset, 0 1px 0 rgba(255, 255, 255, 0.85) inset;
}
.btn-ghost { background: transparent; color: #373B4D; }
.btn-ghost:hover { background: rgba(243, 138, 63, 0.10); color: #373B4D; }
.btn-block { width: 100%; }
/* Loading state — keeps the button label visible (the JS swaps it to
   "Confirming details…" or similar on click) and replaces the trailing
   arrow icon with a spinner so the customer gets both verbal AND
   visual feedback the instant they tap. The old behavior hid the
   label entirely (color: transparent) and showed only a small middle
   spinner, which read as "did anything happen?" during the 1-3s
   network round-trip. */
.btn.is-loading { pointer-events: none; }
.btn.is-loading .btn-arrow,
.btn.is-loading .btn-arrow-forward,
.btn.is-loading .btn-arrow-back { display: none; }
.btn.is-loading::after {
  content: '';
  display: inline-block;
  width: 14px; height: 14px;
  margin-left: 4px;
  border: 2px solid rgba(255,255,255,0.4);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
  vertical-align: middle;
}
.btn-secondary.is-loading::after { border-color: rgba(31,44,68,0.3); border-top-color: var(--navy); }
@keyframes spin { to { transform: rotate(360deg); } }

/* Success-state morph. Replaces the loading spinner with a sage-green
   fill sweep + an SVG checkmark that draws itself via stroke-dasharray.
   Plays for ~700ms after a successful submit before the page hands
   off to the step 5 success card. The button keeps its width so the
   layout doesn't shift mid-animation. */
.btn.is-success {
  pointer-events: none;
  position: relative;
  overflow: hidden;
  background: #2E8A56 !important;
  color: #FFFDF9 !important;
  border-color: #2E8A56 !important;
  transition: background 0.28s ease, border-color 0.28s ease;
}
.btn.is-success .btn-arrow,
.btn.is-success .btn-arrow-forward,
.btn.is-success .btn-arrow-back { display: none; }
.btn.is-success::after { display: none; }
/* The sweep — a paler-green wash that races across the button left to
   right behind the label, finishing just as the checkmark draws.
   Reads as "the booking is shipping" rather than "the form is dead." */
.btn.is-success::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.22) 50%, transparent 100%);
  transform: translateX(-100%);
  animation: btnSuccessSweep 0.55s ease-out forwards;
  pointer-events: none;
}
@keyframes btnSuccessSweep {
  from { transform: translateX(-100%); }
  to   { transform: translateX(120%); }
}
/* Checkmark sits centered over the label text. The label fades out
   while the check fades+draws in. Using stroke-dasharray = perimeter
   + dashoffset animation = the classic "draw" effect, fast (300ms)
   so the button doesn't linger past the human "yes!" moment. */
.btn-success-check {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 22px;
  height: 22px;
  transform: translate(-50%, -50%) scale(0.6);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.32s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.btn-success-check path {
  fill: none;
  stroke: #FFFDF9;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 22;
  stroke-dashoffset: 22;
  transition: stroke-dashoffset 0.32s cubic-bezier(0.55, 0.06, 0.4, 0.95) 0.22s;
}
.btn.is-success .btn-success-check {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}
.btn.is-success .btn-success-check path { stroke-dashoffset: 0; }
.btn.is-success .btn-label,
.btn.is-success > span:not(.btn-success-check) {
  opacity: 0;
  transition: opacity 0.16s ease;
}

/* Success card reveal. The existing step-5 reveal is a hard show/hide;
   this layers a smooth slide-up + fade-in so the recap doesn't pop
   into existence. Triggered by the .is-revealing class added by JS
   right before showStep(5). */
.step-panel[data-step="5"].is-revealing {
  animation: successCardReveal 0.42s cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes successCardReveal {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Confetti — purely CSS particles spawned in a one-shot container at
   the top of the success card. ~14 elements, each with a slightly
   different fall arc + rotation so they don't read as identical
   sprites. Self-cleans after 1.6s; the container is removed by JS. */
.success-confetti {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 1;
}
.success-confetti-piece {
  position: absolute;
  top: -10px;
  width: 8px;
  height: 14px;
  border-radius: 2px;
  opacity: 0;
  animation: confettiFall 1.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
@keyframes confettiFall {
  0%   { opacity: 0; transform: translateY(-20px) rotate(0deg); }
  10%  { opacity: 1; }
  100% { opacity: 0; transform: translateY(360px) rotate(540deg); }
}

.actions {
  display: flex;
  justify-content: space-between;
  margin-top: 24px;
  gap: 12px;
}
.actions .btn-primary { margin-left: auto; }

/* Address field on step 1 — wider than ZIP since it accepts a full address */
/* ═══ 27. Address field ═══ */
.address-field {
  max-width: 520px;
  margin-bottom: 28px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  @media (max-width: 760px) { max-width: 100%; }
}
.address-field > span {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.address-input-wrap {
  position: relative;
  display: block;
  /* Establish a stacking context so .pac-container (which Google appends
     to <body>) remains positioned correctly relative to the page, while
     the input below sits cleanly above any sibling layers. */
  z-index: 1;
}
.address-field input {
  /* Opaque background — non-negotiable. With transparent we were letting
     browser autofill suggestion chips and password-manager icon bars bleed
     through underneath the input, which produced the row of mystery glyphs
     in the user's earlier screenshot. Soft cream-white instead of pure
     white for the modern "luminous" look without sacrificing autofill
     suppression. */
  background: #FCFCFA !important;
  border: 1px solid var(--border-soft, #ECE7DD);
  border-radius: 14px;
  padding: 16px 44px 16px 46px;
  width: 100%;
  font-size: 1rem;
  font-weight: 500;
  color: var(--text);
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  /* Suppress browser-native UI that loves to live inside text inputs. */
  -webkit-text-fill-color: var(--text);
  caret-color: var(--orange);
  transition: border-color 0.22s ease, box-shadow 0.22s ease, background 0.22s ease, border-radius 0.22s ease;
  position: relative;
  z-index: 2;
}
/* Kill the yellow autofill background some browsers paint behind the text. */
.address-field input:-webkit-autofill,
.address-field input:-webkit-autofill:hover,
.address-field input:-webkit-autofill:focus {
  animation: addressAutofillStart 0.01s both;
  -webkit-box-shadow: 0 0 0 1000px #FCFCFA inset !important;
  box-shadow: 0 0 0 1000px #FCFCFA inset !important;
  -webkit-text-fill-color: var(--text) !important;
  transition: background-color 9999s ease-in-out 0s;
}
@keyframes addressAutofillStart {
  from { opacity: 0.999; }
  to { opacity: 1; }
}
/* Hide unrelated WebKit-injected credential buttons while allowing
   the contact/address autofill affordance Safari uses on iOS. */
.address-field input::-webkit-credentials-auto-fill-button,
.address-field input::-webkit-credit-card-auto-fill-button {
  visibility: hidden !important;
  display: none !important;
  pointer-events: none !important;
  position: absolute !important;
  right: 0 !important;
}
.address-field input::-webkit-search-cancel-button,
.address-field input::-webkit-search-decoration {
  -webkit-appearance: none;
  display: none;
}
/* Atmospheric placeholder — charcoal at low opacity reads as "tracing
   paper" rather than as a solid medium-saturation beige. The example
   should hint at what to type without competing with real entered
   text. -webkit-text-fill-color must be set explicitly here because
   the input rule above sets it to var(--text) for autofill suppression,
   which silently overrides ::placeholder { color } on iOS Safari + on
   Chrome's autofill state. */
.address-field input::placeholder {
  color: rgba(39, 41, 54, 0.30);
  -webkit-text-fill-color: rgba(39, 41, 54, 0.30);
  font-weight: 400;
  opacity: 1;
}
/* Static "next thing" hint: while the field is empty AND not focused,
   the border picks up a super-light orange tint. No halo, no
   animation — just a quiet visual cue that this is where to type
   next. The :not(:focus) guard means it disappears the moment the
   user clicks in (the focus ring takes over). */
.address-field input:placeholder-shown:not(:focus) {
  border-color: rgba(245, 133, 54, 0.22);
}
.address-field input:hover:not(:focus) {
  /* Hover stays inside the warm palette — lighter card-highlight
     fill (was pure #FFFFFF) plus an orange-tinged border so the
     hover-state matches the rest of the bento inputs. */
  border-color: rgba(243, 138, 63, 0.35);
  background: #FFFAF1 !important;
}
.address-field input:focus {
  outline: none;
  background: #FFFDF9 !important;
  border-color: #F38A3F;
  /* Stronger focus ring than the previous 10%-opacity one so the
     focused address bar reads as the page's primary edit-target —
     matches every other .field input on the page. */
  box-shadow: 0 0 0 3px rgba(243, 138, 63, 0.22);
}
.address-field input.is-valid {
  border-color: var(--border-soft, #ECE7DD);
  /* Settle animation — when the address is verified, the input bar
     does a tiny spring "settle" that reads as a haptic confirmation.
     The animation is short and very subtle (1px translate, 0.99 →
     1.0 scale) so it feels physical, not bouncy. */
  animation: addressSettle 0.42s cubic-bezier(0.22, 1, 0.36, 1);
}
@keyframes addressSettle {
  0%   { transform: translateY(0)    scale(1); }
  35%  { transform: translateY(-2px) scale(1.005); }
  70%  { transform: translateY(1px)  scale(0.998); }
  100% { transform: translateY(0)    scale(1); }
}

/* Drawer effect: when the dropdown is open, square off the input's
   bottom corners so the suggestions list appears to grow OUT of it. */
.address-input-wrap.has-open-suggestions input {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
  border-bottom-color: transparent;
}

/* Map-pin icon, anchored inside the input on the left. Subtly orange-shifts
   on focus to signal "this field is active." */
.address-leading-icon {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  color: var(--text-muted);
  pointer-events: none;
  z-index: 3;
  transition: color 0.22s ease, transform 0.22s ease;
}
.address-input-wrap:focus-within .address-leading-icon {
  color: var(--orange);
  transform: translateY(-50%) scale(1.05);
}
.address-leading-icon svg {
  width: 18px;
  height: 18px;
  display: block;
}

/* Fallback split-field address form — soft, friendly, NOT a warning.
   Hidden by default. Revealed either by the user clicking the manual
   toggle below the autocomplete input, or by gm_authFailure when
   Google rejects the key. The yellow "didn't load" banner only shows
   in the auth-failure case; the user-opt-in case stays neutral. */
/* "Can't find your address? Call us at <phone>" — replaces the old
   manual-entry form. We can't book without a geocoded address (the
   service-area check needs one), so genuine edge cases route to a
   human instead. */
.address-help {
  margin-top: 14px;
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--text-muted);
}
.address-help a {
  color: var(--orange-dark);
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}
.address-help a:hover { color: var(--orange); text-decoration: underline; }
.address-field .address-hint {
  font-size: 0.82rem;
  line-height: 1.4;
  transition: color 0.22s ease;
  min-height: 1.2em;
}
/* Warm success state shown right after the customer picks an address. */
.address-field .address-hint.is-success {
  color: #1f7a4d;
  font-weight: 500;
  animation: hintSuccess 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}
@keyframes hintSuccess {
  0%   { opacity: 0; transform: translateY(-2px); }
  100% { opacity: 1; transform: translateY(0); }
}

/* "Your details" step — animated green check per field as it validates,
   plus a hideable Apt/Suite slot revealed by a button next to the
   street-address label. Visual goal: the form rewards completion in
   real time without ever feeling noisy. */
.your-info-grid .field.has-check {
  position: relative;
}

/* Address summary block — the collapsed default state for the address
   on step 4. Looks like a label-on-top + plain text below + an edit
   pencil on the right. Deliberately NOT input-cell styled (no border,
   no background) so the section reads as "this is your address"
   rather than "fill in this field". */
.address-summary {
  padding-top: 4px;
  padding-bottom: 4px;
}
.address-summary-label {
  display: block;
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--text);
  margin-bottom: 6px;
}
.address-summary-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  @media (max-width: 600px) { gap: 8px; }
}
.address-summary-text {
  font-size: 1rem;
  line-height: 1.5;
  color: var(--text);
  flex: 1 1 auto;
  word-break: break-word;
  @media (max-width: 600px) { font-size: 0.96rem; }
}
.address-summary-edit {
  flex-shrink: 0;
  background: transparent;
  border: 1px solid transparent;
  color: var(--muted, #8B847A);
  padding: 8px;
  border-radius: 10px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color 0.18s ease, background 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
  /* Mobile: bigger tap area (≥44px). */
  @media (max-width: 600px) {
    padding: 12px;
    min-width: 44px;
    min-height: 44px;
  }
}
.address-summary-edit:hover {
  /* Muted hover (was orange-tinted) — the edit pencil is a quiet
     reveal control, not a primary action. */
  color: var(--text);
  background: rgba(39, 41, 54, 0.06);
  border-color: rgba(39, 41, 54, 0.14);
}
.address-summary-edit:active {
  transform: scale(0.94);
}

/* Show/hide toggle. Class lives on .your-info-grid; flipped by JS in
   booking.js when the user clicks the edit pencil. Default (no class)
   is summary mode. .is-editing-address swaps to the editable grid. */
.your-info-grid:not(.is-editing-address) .address-edit-field {
  display: none !important;
}
.your-info-grid.is-editing-address .address-summary {
  display: none !important;
}
/* Subtle entrance for the edit grid so the swap doesn't pop. */
.your-info-grid.is-editing-address .address-edit-field {
  animation: addressEditIn 0.28s cubic-bezier(0.22, 1, 0.36, 1);
}
@keyframes addressEditIn {
  0%   { opacity: 0; transform: translateY(-2px); }
  100% { opacity: 1; transform: translateY(0); }
}
.your-info-grid .field.has-check input {
  padding-right: 40px; /* leave room for the check icon */
}
.field-check {
  position: absolute;
  right: 12px;
  bottom: 11px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  /* Soft modern green — emerald-400-ish. Old var(--st-ok-fg) was a dark
     forest green that read as harsh on a clean form. */
  background: #34D399;
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transform: scale(0.6);
  pointer-events: none;
  box-shadow: 0 1px 2px rgba(16, 185, 129, 0.18);
  transition:
    opacity 0.22s ease,
    transform 0.32s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.field-check::after {
  content: '';
  width: 11px;
  height: 6px;
  border-left: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg) translate(1px, -1px);
  display: block;
  /* Stroke-style checkmark draws in once visible */
  opacity: 0;
  transition: opacity 0.18s ease 0.12s;
}
.field.is-valid .field-check {
  opacity: 1;
  transform: scale(1);
}
.field.is-valid .field-check::after {
  opacity: 1;
}
/* Subtle border highlight when valid — soft emerald, not forest green. */
.field.is-valid input {
  border-color: rgba(52, 211, 153, 0.45);
}

/* Locked state for prefilled address/city/state fields. The input stays
   in the DOM (so form submission Just Works) but visually reads as a
   confirmed value: muted background, no border, no focus ring, with a
   small pencil edit button at the right side. Click the pencil to
   unlock and edit. Protects against Chrome autofill silently overwriting
   verified data + signals "you've already done this part." */
.field.is-locked input,
.field.is-locked input:focus,
.field.is-locked input:hover {
  background: #F5F7F8 !important;
  border-color: transparent !important;
  color: var(--text);
  cursor: default;
  box-shadow: none !important;
  outline: none;
  font-weight: 500;
  padding-right: 70px; /* room for green check + pencil button */
  -webkit-text-fill-color: var(--text);
}
.field.is-locked .field-check {
  /* Show the green check immediately for prefilled-and-locked fields. */
  right: 40px;
}
.field-edit-btn {
  position: absolute;
  right: 8px;
  bottom: 9px;
  width: 26px;
  height: 26px;
  border: 0;
  background: transparent;
  color: var(--text-muted);
  border-radius: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
  transition: background 0.15s ease, color 0.15s ease;
  /* Mobile: grow tap target. */
  @media (max-width: 600px) { width: 36px; height: 36px; }
}
.field-edit-btn:hover {
  background: rgba(245, 133, 54, 0.10);
  color: var(--orange-dark);
}
.field-edit-btn:focus-visible {
  outline: 2px solid var(--orange);
  outline-offset: 1px;
}

/* Street + Apt toggle row */
.street-label-row {
  display: flex !important;
  align-items: center !important;
  justify-content: space-between !important;
  gap: 10px;
  flex-direction: row !important;
}
.apt-toggle {
  background: transparent;
  border: 0;
  padding: 0;
  font-family: inherit;
  font-size: 0.78rem;
  font-weight: 600;
  /* Muted gray — was orange. Inline reveal links are guides, not
     primary actions; they shouldn't compete with Continue. */
  color: var(--text-muted);
  cursor: pointer;
  text-transform: none;
  letter-spacing: 0;
  transition: color 0.18s ease;
}
.apt-toggle:hover { color: var(--text); text-decoration: underline; }
.apt-toggle.is-active { display: none; } /* hide once Apt field is shown */
.apt-field { animation: alertIn 0.32s cubic-bezier(0.22, 1, 0.36, 1); }

/* Reverse-search match banner — Phase 4 of the customer-edit
   flow. Appears above the customer-info form when no customer is
   matched AND the admin types an email or phone that hits an
   existing HCP customer record. Light-blue calm support tone
   (Sunwave palette: #BFE0F3) because this is a HELPFUL signal
   ("hey, this person already exists, want to attach to them?"),
   not a warning. Single action: switch to the matched record. */
.reverse-match-banner {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 14px;
  padding: 11px 14px;
  background: rgba(191, 224, 243, 0.32);
  border: 1px solid rgba(31, 90, 134, 0.28);
  border-radius: 14px;
}
.reverse-match-banner[hidden] { display: none !important; }
.reverse-match-banner-body {
  display: flex;
  align-items: center;
  gap: 9px;
  flex: 1 1 auto;
  min-width: 0;
  color: #1F5A86;
}
.reverse-match-banner-body svg { color: #1F5A86; flex: 0 0 16px; }
.reverse-match-banner-text {
  font-size: 0.88rem;
  line-height: 1.4;
  color: var(--text);
}
.reverse-match-banner-text strong {
  font-weight: 700;
  color: #373B4D;
}
.reverse-match-banner-hint {
  display: inline;
  font-size: 0.78rem;
  color: var(--text-muted);
}
.reverse-match-banner-action {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 14px;
  flex: 0 0 auto;
  background: #1F5A86;
  border: 1px solid #1F5A86;
  border-radius: 999px;
  color: #fff;
  font-size: 0.82rem;
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease, transform 0.06s ease;
  white-space: nowrap;
}
.reverse-match-banner-action:hover {
  background: #144971;
  border-color: #144971;
}
.reverse-match-banner-action:active { transform: scale(0.98); }
.reverse-match-banner-action svg { transition: transform 0.18s ease; }
.reverse-match-banner-action:hover svg { transform: translateX(2px); }

/* Identity-change confirmation banner — Phase 4 of the customer-
   edit flow. Appears above the customer-info form when the admin
   renames a matched customer to something dramatically different
   (trigram-overlap check in booking.js). Coral / unpaid-wash tones
   per the palette doc, since this is a "stop and think" before
   submitting a potentially destructive action (renaming the wrong
   HCP record). Two actions: keep the match (update existing) or
   drop the match (create new customer with typed values). */
.identity-change-banner {
  margin: 0 0 18px;
  padding: 14px 16px;
  background: #FCEBE7;
  border: 1px solid rgba(214, 95, 79, 0.32);
  border-radius: 14px;
}
.identity-change-banner[hidden] { display: none !important; }
.identity-change-banner-head {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-bottom: 4px;
  color: #8A2C1F;
}
.identity-change-banner-head svg { color: #D65F4F; flex: 0 0 16px; }
.identity-change-banner-head strong {
  font-size: 0.92rem;
  font-weight: 700;
  color: #373B4D;
  letter-spacing: -0.005em;
}
.identity-change-banner-body {
  margin: 0 0 10px;
  font-size: 0.82rem;
  color: var(--text-muted);
  line-height: 1.4;
}
.identity-change-banner-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.identity-change-banner-action {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  border-radius: 999px;
  font-size: 0.84rem;
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease,
              color 0.12s ease, transform 0.06s ease;
}
.identity-change-banner-action--keep {
  background: #FFFDF9;
  border: 1px solid #E5DED3;
  color: #373B4D;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.85) inset;
}
.identity-change-banner-action--keep:hover {
  background: #FFFAF1;
  border-color: rgba(243, 138, 63, 0.45);
}
.identity-change-banner-action--swap {
  background: #D65F4F;
  border: 1px solid #D65F4F;
  color: #fff;
}
.identity-change-banner-action--swap:hover {
  background: #BA4938;
  border-color: #BA4938;
}
.identity-change-banner-action:active { transform: scale(0.98); }

/* Address-change prompt — surfaces in admin mode when the typed
   address differs from a matched customer's stored address. Forces
   an explicit choice (update vs add as new) so admins can never
   silently overwrite a customer's address. Warm-gold accent because
   it's a "stop and think" moment, not a celebratory or error state.
   Sits in the .span-2 column of the customer-info-grid so it spans
   the full form width below the address fields. */
.address-change-prompt {
  /* Lift the .field flex column reset — this isn't a labeled input. */
  flex-direction: column;
  gap: 0;
  margin-top: 6px;
  padding: 14px 16px;
  background: rgba(211, 154, 46, 0.10);
  border: 1px solid rgba(211, 154, 46, 0.30);
  border-radius: 14px;
}
.address-change-prompt[hidden] { display: none !important; }
.address-change-prompt-head {
  display: flex;
  align-items: center;
  gap: 9px;
  color: #6F4D14;
  margin-bottom: 4px;
}
.address-change-prompt-head svg { color: #D39A2E; flex: 0 0 16px; }
.address-change-prompt-head strong {
  font-size: 0.92rem;
  font-weight: 700;
  color: #373B4D;
  letter-spacing: -0.005em;
}
.address-change-prompt-body {
  margin: 0 0 10px;
  font-size: 0.82rem;
  color: var(--text-muted);
  line-height: 1.4;
}
.address-change-prompt-options {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.address-change-option {
  /* `position: relative` anchors the absolutely-positioned hidden
     <input type="radio"> below to this option card. Without it the
     radio escapes upward to the nearest positioned ancestor (could
     be the form, the page, anywhere), which silently breaks keyboard
     focus + can drag layout. The radio is opacity:0 so you'd never
     see it directly, but tab navigation lands on a moved-offscreen
     element. */
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 10px 12px;
  background: #FFFDF9;
  border: 1px solid #E5DED3;
  border-radius: 12px;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.address-change-option:hover {
  border-color: rgba(243, 138, 63, 0.35);
  background: #FFFAF1;
}
.address-change-option input[type="radio"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.address-change-option-radio {
  flex: 0 0 16px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid #D8D2C6;
  background: #fff;
  margin-top: 2px;
  position: relative;
  transition: border-color 0.15s ease;
}
.address-change-option-radio::after {
  content: '';
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  background: #F38A3F;
  opacity: 0;
  transform: scale(0.6);
  transition: opacity 0.15s ease, transform 0.18s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.address-change-option input[type="radio"]:checked ~ .address-change-option-radio {
  border-color: #F38A3F;
}
.address-change-option input[type="radio"]:checked ~ .address-change-option-radio::after {
  opacity: 1;
  transform: scale(1);
}
.address-change-option input[type="radio"]:focus-visible ~ .address-change-option-radio {
  box-shadow: 0 0 0 3px rgba(243, 138, 63, 0.22);
}
.address-change-option-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1 1 auto;
  min-width: 0;
}
.address-change-option-text strong {
  font-size: 0.88rem;
  font-weight: 700;
  color: #373B4D;
  letter-spacing: -0.005em;
}
.address-change-option-text small {
  font-size: 0.78rem;
  color: var(--text-muted);
  line-height: 1.4;
  font-weight: 400;
}

/* Access notes — collapsed by default to keep step 3 from feeling
   overwhelming. The wrap is just a positioning container; the toggle
   button shows by default, the field reveals inline on click. */
.access-notes-wrap {
  margin-top: 4px;
}
.access-notes-toggle {
  background: transparent;
  border: 0;
  padding: 8px 12px;
  margin-left: -12px; /* visually align with field labels above */
  font-family: inherit;
  font-size: 0.86rem;
  font-weight: 600;
  /* Muted, like the apt-toggle. The orange-soft hover bg is replaced
     with a neutral cream tint so reveal-toggles never compete with
     the orange primary CTA. */
  color: var(--text-muted);
  cursor: pointer;
  text-align: left;
  border-radius: 8px;
  transition: background 0.15s ease, color 0.15s ease;
}
.access-notes-toggle:hover {
  background: rgba(39, 41, 54, 0.05);
  color: var(--text);
}
.access-notes-toggle.is-active { display: none; }
.access-notes-field { animation: alertIn 0.32s cubic-bezier(0.22, 1, 0.36, 1); }

/* Success recap card — shows on step 4 after a successful booking. The
   booking summary that used to live at the bottom of step 3 moved here
   where it reads as a confirmation, not another wall of info. */
.success-recap {
  margin: 18px auto 18px;
  max-width: 420px;
  padding: 14px 16px;
  background: var(--card-tint);
  border: 1px solid var(--border-soft);
  border-radius: 14px;
  text-align: left;
}
.success-recap-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.success-recap-row {
  display: grid;
  grid-template-columns: 64px 1fr;
  gap: 12px;
  align-items: baseline;
  font-size: 0.9rem;
}
.success-recap-label {
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-muted);
  padding-top: 2px;
}
.success-recap-value {
  font-weight: 500;
  color: var(--text);
  word-break: break-word;
}

/* Marketing-attribution survey on the success page. Asks the customer
   how they first heard about us. Answer POSTs to /api/attribution and
   never reaches Housecall Pro — pure first-party analytics. Sits below
   the recap, above the Finish button. */
.success-survey {
  margin: 8px auto 4px;
  max-width: 480px;
  text-align: center;
}
.success-survey-q {
  margin: 0 0 12px;
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--text);
}
.success-survey-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
}
.success-survey-chip {
  font-family: inherit;
  font-size: 0.88rem;
  font-weight: 600;
  padding: 9px 14px;
  border-radius: 999px;
  border: 1.5px solid var(--border, rgba(39, 41, 54, 0.14));
  background: #FFFFFF;
  color: var(--text);
  cursor: pointer;
  transition:
    border-color 0.18s ease,
    background 0.18s ease,
    color 0.18s ease,
    transform 0.18s ease;
}
.success-survey-chip:hover:not(:disabled) {
  border-color: rgba(39, 41, 54, 0.30);
  background: #FBFAF7;
}
.success-survey-chip.is-picked {
  background: var(--selected-ink);
  color: #FFFFFF;
  border-color: var(--selected-ink);
}
.success-survey-chip:disabled:not(.is-picked) {
  opacity: 0.45;
  cursor: default;
}
.success-survey-thanks {
  margin: 12px 0 0;
  font-size: 0.85rem;
  color: var(--text-muted);
  animation: alertIn 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}

/* ─── Admin success card ─────────────────────────────────────────
   Distinct layout for the admin booking flow. Same green-check
   reveal animation as the customer card (shared chrome), but the
   copy + recap + actions are tuned for "I just scheduled this for
   someone else" instead of "I just booked this for myself."
   Tightens up the spacing, surfaces customer + tech as the headline
   data, and replaces the marketing chips + Finish link with three
   admin-relevant actions. */
.success-admin-eyebrow {
  display: block;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--success);
  margin-bottom: 4px;
  animation: fadeUp 0.5s ease-out 0.7s backwards;
}
.success-admin-title {
  font-size: 1.5rem;
  font-weight: 800;
  color: var(--navy);
  letter-spacing: -0.02em;
  margin: 0 0 6px;
  animation: fadeUp 0.5s ease-out 0.78s backwards;
}
.success-admin-sub {
  font-size: 0.95rem;
  color: var(--text-muted);
  margin: 0 0 22px;
  animation: fadeUp 0.5s ease-out 0.86s backwards;
}
.success-admin-recap {
  max-width: 460px;
  margin: 0 auto 24px;
  text-align: left;
  background: #FFFDF9;
  border: 1px solid #E5DED3;
  border-radius: 16px;
  padding: 16px 20px;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.85) inset;
  animation: fadeUp 0.55s ease-out 0.95s backwards;
}
.success-admin-recap-list {
  display: grid;
  grid-template-columns: 100px 1fr;
  gap: 8px 14px;
  margin: 0;
  padding: 0;
}
.success-admin-recap-row {
  display: contents;
}
.success-admin-recap-row dt {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
  align-self: start;
  padding-top: 2px;
}
.success-admin-recap-row dd {
  margin: 0;
  font-size: 0.92rem;
  color: var(--navy);
  font-weight: 500;
  line-height: 1.4;
  word-break: break-word;
}
.success-admin-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
  align-items: stretch;
  max-width: 360px;
  margin: 0 auto;
  animation: fadeUp 0.55s ease-out 1.05s backwards;
}
.success-admin-actions .btn {
  width: 100%;
  justify-content: center;
}
.success-admin-actions .btn svg {
  margin-right: 2px;
}
.success-admin-hcp[hidden] { display: none !important; }
.btn-ghost {
  background: transparent;
  color: #4a4f60;
  border: 1px solid #E5DED3;
}
.btn-ghost:hover {
  background: rgba(55, 59, 77, 0.04);
  color: var(--navy);
  border-color: rgba(55, 59, 77, 0.20);
}
/* Compact the .success-card padding when in admin mode — no
   marketing chips to leave room for, no Finish CTA chrome. Reads
   tighter and more "operational." */
.success-card--admin {
  padding: 16px 8px 24px;
}
.success-card--admin .success-mark {
  width: 64px;
  height: 64px;
  margin-bottom: 14px;
}
@media (max-width: 600px) {
  .success-admin-recap-list {
    grid-template-columns: 80px 1fr;
    gap: 6px 10px;
  }
  .success-admin-title { font-size: 1.3rem; }
}

/* Admin marketing-attribution chart. Horizontal bars per source, sorted
   desc by count. Uses --orange so the brand thread is visible on the
   admin side too. The "by location" sub-list below is a plain breakdown
   for shops with multiple franchises. */
.attribution-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.attribution-row {
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.attribution-label {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 0.92rem;
}
.attribution-bar {
  position: relative;
  height: 8px;
  border-radius: 999px;
  background: rgba(39, 41, 54, 0.06);
  overflow: hidden;
}
.attribution-bar-fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--orange, #E07A2A), var(--orange-dark, #B86012));
  transition: width 0.4s cubic-bezier(0.22, 1, 0.36, 1);
}
.attribution-sub-h {
  margin: 24px 0 10px;
  font-size: 0.95rem;
}
.attribution-locations,
.attribution-loc-sub {
  list-style: none;
  margin: 0;
  padding: 0;
}
.attribution-loc-row {
  padding: 10px 0;
  border-top: 1px solid var(--border-soft, rgba(39, 41, 54, 0.08));
}
.attribution-loc-row:first-child { border-top: 0; }
.attribution-loc-head { font-size: 0.92rem; }
.attribution-loc-sub {
  margin: 6px 0 0 12px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  font-size: 0.85rem;
  color: var(--text-muted);
}

/* Custom address autocomplete dropdown — server-driven, replaces the
   legacy Google Places JS widget which was unreliable across browsers
   and Google Cloud project configurations.
   When `.is-floating` is set (booking.js portals it to <body>), it
   uses absolute positioning relative to the page so it escapes any
   parent's overflow:hidden — the booking step-panel was clipping it. */
.address-suggestions {
  position: absolute;
  /* Sit flush against the input's bottom edge so it reads as one continuous
     "drawer" — no gap, no horizontal seam. The input's bottom border goes
     transparent (.has-open-suggestions input above), and we have NO top
     border at all so the input's interior flows directly into ours. The
     remaining left/right/bottom orange borders, plus the input's matching
     left/right/top orange focus border, complete the unified rectangle. */
  top: 100%;
  left: 0;
  right: 0;
  margin: 0;
  padding: 6px;
  list-style: none;
  background: #FFFFFF;
  border: 1px solid var(--orange);
  border-top: 0;
  border-radius: 0 0 14px 14px;
  box-shadow: 0 14px 32px rgba(39, 41, 54, 0.10), 0 0 18px rgba(245, 133, 54, 0.06);
  z-index: 100;
  max-height: 320px;
  overflow-y: auto;
  /* Drawer slide — the list slides down from behind the input as it fades. */
  animation: drawerOpen 0.28s cubic-bezier(0.22, 1, 0.36, 1);
  transform-origin: top center;
}
@keyframes drawerOpen {
  0%   { opacity: 0; transform: translateY(-6px) scaleY(0.96); }
  100% { opacity: 1; transform: translateY(0) scaleY(1); }
}
.address-suggestions.is-floating {
  /* When portaled to <body>, booking.js sets left/top/width inline from
     input.getBoundingClientRect() + window.scroll. We use absolute (not
     fixed) so the dropdown stays glued to the input as the user scrolls;
     `top` already includes window.scrollY. */
  position: absolute;
  top: 0;
  right: auto;
  z-index: 2147483647; /* above any parent stacking context */
  margin: 0;
}
.address-suggestions[hidden] { display: none; }
.address-suggestions li {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 11px 12px;
  border-radius: 10px;
  cursor: pointer;
  font-size: 0.92rem;
  line-height: 1.35;
  color: var(--text);
  /* Each item fades + slides in with a 30ms staggered delay set inline
     via --i (see booking.js renderSuggestions). The result is a cascade
     that reads as "the system is finding matches as it sees them." */
  opacity: 0;
  transform: translateY(-4px);
  animation: suggIn 0.26s cubic-bezier(0.22, 1, 0.36, 1) forwards;
  animation-delay: calc(var(--i, 0) * 30ms);
  transition: background 0.16s ease, transform 0.16s ease, padding-left 0.16s ease;
}
@keyframes suggIn {
  to { opacity: 1; transform: translateY(0); }
}
.address-suggestions li.is-active,
.address-suggestions li:hover {
  background: var(--orange-soft);
  /* Boutique-site hover slide — text + icon slip 4px right on hover. */
  padding-left: 16px;
}
.address-suggestions .sugg-icon {
  width: 18px; height: 18px;
  margin-top: 1px;
  color: var(--text-muted);
  flex-shrink: 0;
}
.address-suggestions li.is-active .sugg-icon,
.address-suggestions li:hover .sugg-icon {
  color: var(--orange-dark);
}
.address-suggestions .sugg-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.address-suggestions .sugg-main {
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.address-suggestions .sugg-secondary {
  font-size: 0.82rem;
  color: var(--text-muted);
  margin-top: 1px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Legacy Google Places autocomplete dropdown — Google appends `.pac-container` to
   document.body and absolutely-positions it under the input. Without our own
   styles the suggestions inherit zero font-family from <body> in some Safari
   versions and render as fallback glyphs. These rules force the same look
   as our other surfaces, plus a clean drop-shadow + rounded card. */
.pac-container {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
  border: 1px solid var(--border);
  border-radius: 12px;
  margin-top: 6px;
  box-shadow: 0 12px 28px rgba(39, 41, 54, 0.12);
  background: #fff;
  overflow: hidden;
  /* Above any browser-injected autofill chip bar (typically ~10000) and
     iOS Safari's contact-card overlay. */
  z-index: 2147483647 !important;
}
.pac-container:after { display: none !important; } /* hide "powered by Google" attribution row that breaks rounded corners */
.pac-item {
  font-family: inherit !important;
  font-size: 0.92rem;
  color: var(--text);
  padding: 10px 14px;
  border-top: 1px solid var(--border-soft);
  cursor: pointer;
  line-height: 1.35;
  display: flex;
  align-items: center;
  gap: 10px;
}
.pac-item:first-child { border-top: 0; }
.pac-item:hover,
.pac-item.pac-item-selected,
.pac-item-selected {
  background: var(--orange-soft);
}
.pac-icon {
  flex: 0 0 18px;
  width: 18px;
  height: 18px;
  margin-top: 0;
  background-position: center;
  background-size: 18px 18px;
}
.pac-item-query {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--text);
  padding-right: 4px;
}
.pac-matched {
  font-weight: 700;
  color: var(--orange-dark);
}
.pac-item span:not(.pac-item-query):not(.pac-icon):not(.pac-matched) {
  color: var(--text-muted);
  font-size: 0.85rem;
  font-weight: 400;
}

/* ZIP field on step 1 — clean stroked input, no fill */
/* ═══ 28. ZIP field ═══ */
.zip-field {
  max-width: 280px;
  margin-bottom: 28px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.zip-field > span {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.zip-input-wrap {
  position: relative;
  display: block;
}
.zip-field input {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px 44px 14px 16px;
  width: 100%;
  font-size: 1.05rem;
  font-weight: 700;
  font-family: ui-monospace, 'SF Mono', 'Menlo', 'Consolas', monospace;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.08em;
  color: var(--text);
  transition: border-color 0.25s ease, box-shadow 0.25s ease, background 0.25s ease;
}
.zip-field input::placeholder {
  color: var(--navy);
  font-weight: 700;
  letter-spacing: 0.08em;
  opacity: 0.18;
}
.zip-field input:hover:not(:focus) { border-color: #C9C2B5; }
.zip-field input:focus {
  outline: none;
  background: #FFFFFF;
  border-color: var(--orange);
  box-shadow: 0 0 0 3px rgba(245, 133, 54, 0.18);
  animation: none;
}
.zip-field input.is-valid {
  border-color: var(--border);
  background: transparent;
}
.zip-field input:not(.is-valid):not(:focus) {
  animation: zipPulse 2.4s ease-in-out infinite;
}
@keyframes zipPulse {
  0%, 100% {
    border-color: var(--border);
    box-shadow: 0 0 0 0 rgba(245, 133, 54, 0);
  }
  50% {
    border-color: rgba(245, 133, 54, 0.55);
    box-shadow: 0 0 0 4px rgba(245, 133, 54, 0.06);
  }
}
.zip-icon {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%) translateX(10px) scale(0.4);
  color: #fff;
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--orange);
  border-radius: 50%;
  opacity: 0;
  transition: opacity 0.28s ease-out, transform 0.36s cubic-bezier(0.34, 1.4, 0.64, 1);
  pointer-events: none;
}
.zip-icon svg { width: 14px; height: 14px; display: block; }
.zip-icon.visible {
  opacity: 1;
  transform: translateY(-50%) translateX(0) scale(1);
}

.booking-summary {
  margin: 12px 0 0;
  padding: 10px 14px;
  background: var(--cream);
  border-radius: var(--radius-sm);
  font-size: 0.92rem;
}
.booking-summary:empty { display: none; }

/* ═══ 29. Calendar (centric) ═══ */
/* Calendar (Calendly-style, premium) */
.calendar-wrap {
  display: grid;
  /* Slots column bumped 340 → 380 so individual slot buttons have
     more horizontal room (was reading as a tight stripe of pills). */
  grid-template-columns: minmax(0, 1fr) 380px;
  gap: 0;
  align-items: stretch;
  animation: calendarUnfurl 0.5s cubic-bezier(0.34, 1.4, 0.64, 1);
  border-top: 1px solid var(--border-soft);
  transform-origin: top center;
}
@keyframes calendarUnfurl {
  0%   { opacity: 0; transform: translateY(-12px) scaleY(0.96); }
  60%  { opacity: 1; transform: translateY(0) scaleY(1.005); }
  100% { opacity: 1; transform: translateY(0) scaleY(1); }
}
.cal-pane {
  min-width: 0;
  /* Roomy on every side. Was 36/32/40/40 — bumped to 48px so the
     grid (now 12px gap, 68px-tall cells) has real breath inside the
     panel instead of butting against the edges. */
  padding: 48px 44px 52px 48px;
  @media (max-width: 760px) { padding: 24px; }
}
.cal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Trimmed 22 → 12. The DOW row + header still read as distinct
     bands but without the slab of whitespace between them. */
  margin-bottom: 12px;
  animation: dayRowIn 0.32s ease-out backwards;
  @media (max-width: 600px) { margin-bottom: 8px; }
}
.cal-title {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--navy);
  font-variant-numeric: tabular-nums;
}
.cal-nav {
  background: transparent;
  border: 0;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  color: var(--navy);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s;
}
.cal-nav:hover:not(:disabled) { background: var(--cream); }
.cal-nav:disabled { opacity: 0.25; cursor: not-allowed; }

.cal-grid {
  display: grid;
  /* minmax(0, 1fr) — not bare 1fr — so the columns can't inflate to
     fit content. The drawer row (.cal-drawer with grid-column 1/-1)
     contains slot buttons whose natural width varies between days
     (1 vs 3 slots, "Earliest" badge etc.). Bare 1fr resolves to
     minmax(auto, 1fr) which lets that content push the column min
     wider, which propagates back up and visibly widens the whole
     card when the customer clicks a day with more slots. The 0 floor
     pins each column to a fixed share of the grid, so the drawer
     never reshapes the calendar. */
  grid-template-columns: repeat(7, minmax(0, 1fr));
  /* Generous gap so each date tile reads as its own object, not part
     of a tight grid. Was 8px — bumped to 12px desktop. */
  gap: 12px;
}
.cal-dow {
  font-size: 0.7rem; /* was 0.62 — more legible */
  font-weight: 700;
  color: var(--text-muted);
  text-align: center;
  /* Trimmed bottom 14 → 8. Combined with the header margin trim
     above, the dow row no longer floats in a whitespace gap. */
  padding: 4px 0 8px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  @media (max-width: 600px) { padding: 2px 0 6px; }
}
.cal-cell {
  /* Explicit comfortable height instead of aspect-ratio:1 — cells
     read as proper "date tiles" with vertical room for the number.
     Trimmed from 68 → 52: with 6 weeks visible, that's ~96px saved
     across the grid. Still ≥44px Apple HIG tap target. */
  min-height: 52px;
  border: 0;
  background: transparent;
  cursor: pointer;
  font-family: inherit;
  font-size: 1.05rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: color 0.15s;
  padding: 0;
  @media (max-width: 600px) { min-height: 44px; }
}
.cal-empty { cursor: default; }
.cal-day { color: #B8B3A4; opacity: 0.5; }
.cal-day:disabled { cursor: default; }
.cal-day.cal-available {
  color: var(--navy);
  font-weight: 700;
  opacity: 1;
  transition: color 0.15s;
}
/* Row-staggered fade for ALL cells (50ms per row, after header) */
.cal-cell {
  animation: dayRowIn 0.32s ease-out backwards;
  animation-delay: calc(0.1s + var(--row-delay, 0s));
}
.cal-dow {
  animation: dayRowIn 0.32s ease-out backwards;
  animation-delay: 0.05s;
}
@keyframes dayRowIn {
  from { opacity: 0; transform: translateY(6px); }
}
/* Subtle dot under available date numbers — the availability
   indicator. Stays small + charcoal-tinted at rest, then physically
   GROWS into the orange selected-pill below (.cal-day.cal-selected
   ::before transitions width/height/etc on the same pseudo-element,
   so the dot literally expands up into the bubble rather than
   disappearing and being replaced). Positioned via top:50% +
   translateY-with-offset so the same transform property can move
   the element from "bottom-area" at rest to "exact center" when
   selected — that's how the morph stays smooth.
   With the bigger cells (min-height 68px) the dot has plenty of
   vertical room under the number. */
.cal-day.cal-available::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--selected-ink);
  opacity: 0.55;
  /* Center horizontally (-50% X) + drop into the lower portion of
     the cell (-50% Y for the element's own height + 18px push down). */
  transform: translate(-50%, calc(-50% + 18px));
  z-index: 0;
  transition:
    width 0.42s cubic-bezier(0.34, 1.56, 0.64, 1),
    height 0.42s cubic-bezier(0.34, 1.56, 0.64, 1),
    border-radius 0.32s ease,
    background 0.32s ease,
    box-shadow 0.32s ease,
    transform 0.42s cubic-bezier(0.34, 1.56, 0.64, 1),
    opacity 0.18s ease;
}
.cal-day.cal-available:hover { color: var(--selected-ink); }
.cal-day.cal-available:hover::before {
  opacity: 0.85;
  /* Bigger nudge that still respects the down-offset positioning. */
  transform: translate(-50%, calc(-50% + 18px)) scale(1.3);
}
/* Halo glow on hover — soft charcoal halo that follows the cursor.
   Was an orange-tinted ghost slab; switched to a near-invisible
   charcoal halo so the only orange hit is the actually-picked date. */
.cal-day.cal-available::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 48px;
  height: 48px;
  border-radius: 16px;
  background: rgba(39, 41, 54, 0.05);
  transform: translate(-50%, -50%) scale(0.9);
  opacity: 0;
  transition: opacity 0.18s ease, transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1),
              background 0.18s ease;
  pointer-events: none;
  z-index: -1;
}
.cal-day.cal-available:hover::after {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}
.cal-day.cal-available.cal-selected::after { opacity: 0; }
/* Today: charcoal outline ring (was orange). The "today" indicator is
   a passive cue, not a CTA, so it stays muted. */
.cal-day.cal-today::after {
  background: transparent;
  border: 1.5px solid rgba(39, 41, 54, 0.30);
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}
.cal-day.cal-today:hover::after {
  background: rgba(39, 41, 54, 0.05);
}
.cal-day.cal-today.cal-selected::after { opacity: 0; }
.cal-day.cal-today { color: var(--selected-ink); font-weight: 800; }
.cal-day.cal-today.cal-selected { color: #fff; }
/* Closed-but-bypassed cell (admin override on, force-booking a
   Sunday/holiday/blackout). Quiet diagonal stripe overlay tells the
   admin "this isn't a normal business day" without disabling the
   click target. Same hover/select transitions still apply on top. */
.cal-day.cal-closed-bypassed {
  background-image: repeating-linear-gradient(
    -45deg,
    transparent 0,
    transparent 5px,
    rgba(224, 122, 42, 0.10) 5px,
    rgba(224, 122, 42, 0.10) 6px
  );
}
.cal-day.cal-closed-bypassed.cal-selected {
  background-image: none;
}

/* Pulsing dot for the earliest available date — quietly says "this
   one's available soonest". Only shows when the date is NOT the
   current selection (the orange slab takes over once selected). */
.cal-day.cal-earliest:not(.cal-selected)::before {
  background: var(--selected-ink);
  box-shadow: 0 0 0 0 rgba(39, 41, 54, 0.45);
  animation: dotIn 0.4s cubic-bezier(0.18, 0.89, 0.32, 1.28) backwards,
             earliestPulse 2.2s ease-in-out 0.6s infinite;
}
@keyframes earliestPulse {
  /* Keep the down-offset translate that positions the dot in the
     lower portion of the cell — only the scale changes. Without
     the calc(-50% + 18px) Y offset, the pulse would jump the dot
     up to center mid-animation and re-snap on each cycle. */
  0%, 100% {
    transform: translate(-50%, calc(-50% + 18px)) scale(1);
    box-shadow: 0 0 0 0 rgba(39, 41, 54, 0.45);
  }
  50% {
    transform: translate(-50%, calc(-50% + 18px)) scale(1.25);
    box-shadow: 0 0 0 5px rgba(39, 41, 54, 0);
  }
}
/* Selected: the dot literally grows into a rounded orange slab.
   width / height / border-radius / background / transform are all
   in the base ::before's transition list, so changing them here
   produces a smooth morph: 4px charcoal circle in the lower part
   of the cell → 48px orange rounded slab in the exact center. The
   cubic-bezier(0.34, 1.56, 0.64, 1) timing on width/height/transform
   gives the morph a small overshoot that reads as a satisfying
   "thunk into place." No separate selectPop animation needed —
   transitions handle it cleanly. */
.cal-day.cal-available.cal-selected::before,
.cal-day.cal-selected::before {
  width: 48px;
  height: 48px;
  border-radius: 16px;
  background: linear-gradient(180deg, var(--orange), #D86F22);
  opacity: 1;
  transform: translate(-50%, -50%);
  /* Glow trimmed back: the prior 6px y-offset + 16px blur extended
     ~22px below the pill's bottom edge, swallowing the V chevron
     that lives in that same band. 3px / 12px keeps the lift but
     leaves the V room to breathe. */
  box-shadow: 0 3px 12px rgba(224, 122, 42, 0.30), inset 0 1px 0 rgba(255, 255, 255, 0.22);
}
.cal-day.cal-available.cal-selected:hover::before,
.cal-day.cal-selected:hover::before {
  transform: translate(-50%, -50%);
  opacity: 1;
}
.cal-day.cal-selected,
.cal-day.cal-selected:hover {
  color: #fff;
  font-weight: 700;
}
.cal-num {
  line-height: 1;
  position: relative;
  z-index: 1;
  @media (max-width: 600px) { font-size: 1rem; }
}
.cal-dot { display: none; }
.cal-legend { display: none; }

/* ═══════════════════════════════════════════════════════════════
   Centric Expansion — drawer that opens INSIDE the calendar grid,
   beneath the row containing the selected date. Replaces the right-
   side slots-pane: slot pills + Back/Continue all live in the
   drawer. Non-active weeks dim to spotlight the selection.
   ═══════════════════════════════════════════════════════════════ */
/* ═══ 30. Calendar centric drawer ═══ */
.calendar-wrap[data-mode="centric"] {
  /* Single column — slots-pane is gone. */
  grid-template-columns: minmax(0, 1fr);
}
.calendar-wrap[data-mode="centric"] .cal-pane {
  border-right: 0;
  /* Tightened from 36/28 → 18/16. The card already provides outer
     breathing room; the pane's own pad was double-padding. */
  padding: 18px 28px 16px;
}
.calendar-wrap[data-mode="centric"] .cal-grid {
  /* Tight 4px row gap so the 6 weeks of cells don't add an extra
     ~40px of seams. Cells still read as separate tiles thanks to
     their own internal spacing. */
  gap: 4px;
}
/* Drawer cell — spans the full row, opens to its natural height
   beneath the selected date.

   Recessed look: the inner tray is a soft cream tone (vs the
   surrounding white card), with an inner shadow at the top edge.
   The arrow lives in the clear space above that tray instead of
   being clipped inside it.

   Easing: standard `cubic-bezier(0.4, 0, 0.2, 1)` instead of a spring
   curve — at the drawer's height (~200px) the spring overshoot was
   reading as "loose" rather than premium. Standard easing settles
   without bounce. */
.cal-drawer {
  grid-column: 1 / -1;
  position: relative;
  max-height: 0;
  opacity: 0;
  margin: 0;
  /* clip-path replaces overflow:hidden so the collapse still hides
     the tray when max-height is 0, but the V chevron at top:-10px
     can escape UPWARD into the gap between the day pill and the
     tray. Negative top inset (-30px) gives the V breathing room
     above the box; the other three edges still clip at the box
     bounds so the closed-state collapse continues to work. */
  clip-path: inset(-30px 0 0 0);
  background: transparent;
  border-radius: 0;
  transition:
    max-height 0.42s cubic-bezier(0.4, 0, 0.2, 1),
    opacity 0.28s ease,
    margin 0.42s cubic-bezier(0.4, 0, 0.2, 1),
    box-shadow 0.32s ease;
}
.cal-drawer.is-open {
  /* Cap that comfortably fits 3 slot pills + actions row. */
  max-height: 260px;
  opacity: 1;
  margin: 6px 0 8px;
  box-shadow: none;
}
/* Filled "speech tail" connector from the cream tray up toward the
   selected date pill. Built with the rotated-square trick: a small
   square turned 45 degrees so its upper half reads as a triangle
   pointing up. The diamond's CENTER is anchored exactly on the
   tray's top edge (drawer top + the tray's 6px margin-top), so the
   lower half blends invisibly into the tray's cream fill while the
   upper half renders as a visible cream triangle with a 1px
   charcoal-soft outline on its two angled sides. Reads as part of
   the tray pointing up at the selected date rather than a separate
   floating mark. */
.cal-drawer-pointer {
  position: absolute;
  top: 6px;
  left: var(--pointer-x, 50%);
  width: 18px;
  height: 18px;
  /* Match the tray's fill exactly so the lower half of the diamond
     disappears against the tray bg with no seam. */
  background: #FAF7F1;
  border: 1px solid rgba(39, 41, 54, 0.10);
  /* Hide bottom + right borders so after the 45deg rotation only the
     upper-left and upper-right edges of the diamond carry an outline.
     The lower edges sit inside the tray, where no border is wanted
     (would otherwise create a stray X seam at the tray top). */
  border-bottom: 0;
  border-right: 0;
  transform: translate(-50%, -50%) rotate(45deg);
  pointer-events: none;
  z-index: 2;
  opacity: 0;
  transition:
    left 0.42s cubic-bezier(0.4, 0, 0.2, 1),
    opacity 0.22s ease;
}
.cal-drawer.is-open .cal-drawer-pointer {
  opacity: 1;
}

.cal-drawer-tray {
  margin-top: 6px;
  background: #FAF7F1;
  border-radius: 14px;
  /* Roomier interior: was 18/22, bumped to 24/32 so the slot pills
     and Back/Continue actions don't crowd the cream edges. The
     extra horizontal padding in particular gives 1- and 2-slot
     days more breathing room around the centered pill. */
  padding: 24px 32px 22px;
  display: flex;
  flex-direction: column;
  /* Internal gap between slot row and actions row (was 14). */
  gap: 18px;
  box-shadow:
    inset 0 2px 5px -3px rgba(39, 41, 54, 0.05),
    inset 0 0 0 1px rgba(39, 41, 54, 0.04);
}
/* Slot row layout. Centered flex with bounded-width pills so 1, 2,
   and 3 slot days all sit balanced inside the same-width tray:
     - 1 slot:  centered, capped at ~280px (no longer hugging the
                left edge with the rest of the row visually empty).
     - 2 slots: each ~280px, spread evenly with a comfortable gap.
     - 3 slots: each ~280px, fills the row.
   The cap prevents pills from stretching ridiculously wide on a
   1-slot day; the flex grow lets them share the row evenly when
   there's enough content. */
.cal-drawer-slots {
  display: flex;
  flex-wrap: nowrap;
  justify-content: center;
  gap: 14px;
  width: 100%;
}
.cal-drawer-slots > * {
  flex: 1 1 0;
  min-width: 0;
  /* Cap so a single-slot day doesn't stretch the pill to absurd
     widths — and so the pill stays close to the visual size users
     get on 3-slot days (no jarring scale change between days). */
  max-width: 320px;
}
.slot-btn-drawer {
  position: relative;
  border: 1.5px solid var(--border, rgba(39, 41, 54, 0.12));
  border-radius: 14px;
  background: #FFFFFF;
  /* Slightly roomier pills (was 14/10). The extra vertical padding
     gives the time text a more substantial frame; the extra horizontal
     keeps "12:00–12:15 PM" from hugging the pill edges. */
  padding: 16px 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: inherit;
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--text);
  cursor: pointer;
  /* Cascade reveal: stagger via --slot-delay set inline by JS.
     Base state matches the keyframe `from` so there's no scale snap
     when the animation kicks in after the delay. */
  opacity: 0;
  transform: translateY(6px) scale(0.96);
  animation: slotDrawerIn 0.34s cubic-bezier(0.22, 1, 0.36, 1) forwards;
  animation-delay: var(--slot-delay, 0s);
  transition:
    border-color 0.18s ease,
    background 0.18s ease,
    color 0.18s ease,
    box-shadow 0.22s ease,
    transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes slotDrawerIn {
  /* Subtle scale + lift so each pill feels "laid out on a table"
     instead of just appearing. Scale stays close to 1 (0.96) so text
     doesn't render blurry mid-animation. */
  from { opacity: 0; transform: translateY(6px) scale(0.96); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
/* Same-row swap (user clicks a different date in the same week): pills
   are already in position, so a scale-up cascade reads as the row
   "growing" beneath the cursor. Strip the lift + scale and just
   crossfade opacity. JS toggles data-swap on .cal-drawer-slots when it
   detects same-row replacement (see renderSlots in calendar.js). */
.cal-drawer-slots[data-swap] .slot-btn-drawer {
  transform: none;
  animation-name: slotDrawerSwap;
}
@keyframes slotDrawerSwap {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.slot-btn-drawer:hover {
  border-color: rgba(39, 41, 54, 0.30);
  background: #FAF7F1;
  /* Suppress the base .slot-btn halo; in a 3-up tight grid that halo
     would bleed across the gap onto neighboring buttons. */
  box-shadow: 0 1px 2px rgba(39, 41, 54, 0.04);
}
.slot-btn-drawer.selected {
  background: linear-gradient(180deg, var(--selected-ink), #1A1C1E);
  color: #FFFFFF;
  border-color: transparent;
  box-shadow: 0 4px 14px rgba(39, 41, 54, 0.22);
  /* Explicit opacity + transform: the .slot-btn.selected rule (more
     specific) replaces our cascade-reveal animation with slotPop,
     which only animates transform: scale. That leaves the cascade's
     opacity-0 base intact and the auto-selected first slot renders
     invisible. Force the visible end state here. */
  opacity: 1;
  transform: translateY(0);
}
/* Suppress slotPop's scale bounce on drawer pills. In the tight
   3-up grid the 1 → 1.08 → 1 bounce makes the card edges and the
   calendar appear to flex left/right whenever the customer clicks
   between slots. The dark fill + ring shadow already give clear
   selection feedback. Specificity (3 classes) edges out
   .slot-btn.selected so this wins on tie-break. */
.slot-btn.slot-btn-drawer.selected {
  animation: none;
}
.slot-btn-drawer.selected .slot-time { color: #FFFFFF; }
.slot-btn-drawer.selected .slot-badge-inline {
  color: var(--selected-ink);
  background: #FFFFFF;
  border-color: rgba(255, 255, 255, 0.5);
}
.cal-drawer-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.cal-drawer-back-placeholder { width: 1px; }
.cal-drawer-continue {
  margin-left: auto;
}

/* Dim non-active weeks: only the available (eye-catching, full-opacity)
   cells in non-active weeks fade to 0.32. Unavailable cells across
   all weeks keep their natural ~0.5 grey, so the dim effect doesn't
   produce two competing levels of grey in the same row. */
.cal-grid[data-active-week] .cal-day.cal-available {
  opacity: 0.32;
  transition: opacity 0.32s ease;
}
.cal-grid[data-active-week="0"] .cal-day.cal-available[data-week="0"],
.cal-grid[data-active-week="1"] .cal-day.cal-available[data-week="1"],
.cal-grid[data-active-week="2"] .cal-day.cal-available[data-week="2"],
.cal-grid[data-active-week="3"] .cal-day.cal-available[data-week="3"],
.cal-grid[data-active-week="4"] .cal-day.cal-available[data-week="4"],
.cal-grid[data-active-week="5"] .cal-day.cal-available[data-week="5"] {
  opacity: 1;
}
/* Hover on a dimmed cell partially restores opacity so the user
   knows it's still clickable. */
.cal-grid[data-active-week] .cal-day.cal-available:hover {
  opacity: 0.85;
}

/* Inline skeleton loader (single-column variant): sits below the grid
   skeleton in the centric layout instead of in a separate right
   column. */
.slots-loading-inline {
  margin-top: 24px;
  padding: 16px 0 0;
  border-top: 1px solid var(--border-soft);
  text-align: center;
  color: var(--text-muted);
}

/* When the calendar is showing, hide the legacy bottom .actions row
   on step 2 — the drawer's own Back/Continue take over. The :has()
   check keeps it visible during the address-only / commercial paths
   when #availability isn't rendered. */
.step-panel[data-step="2"]:has(#availability:not([hidden]) .calendar-wrap[data-mode="centric"]) > .actions {
  display: none;
}

/* Mobile tightening — the drawer becomes a stacked tray so long slot
   labels never force the booking card wider than the phone. */
@media (max-width: 600px) {
  .step-card,
  .step-panel,
  .step-panel[data-step="2"],
  .step-panel[data-step="2"] > #availability,
  .calendar-wrap[data-mode="centric"],
  .calendar-wrap[data-mode="centric"] .cal-pane,
  .calendar-wrap[data-mode="centric"] .cal-grid,
  .cal-drawer,
  .cal-drawer-tray {
    max-width: 100%;
    min-width: 0;
    box-sizing: border-box;
  }
  .step-panel[data-step="2"] > #availability,
  .calendar-wrap[data-mode="centric"],
  .calendar-wrap[data-mode="centric"] .cal-pane,
  .calendar-wrap[data-mode="centric"] .cal-grid,
  .cal-drawer,
  .cal-drawer-tray {
    overflow-x: hidden;
    overflow-x: clip;
  }
  .calendar-wrap[data-mode="centric"],
  .calendar-wrap[data-mode="centric"] .cal-pane,
  .calendar-wrap[data-mode="centric"] .cal-grid {
    width: 100%;
  }
  .booking-page {
    padding-left: 10px;
    padding-right: 10px;
  }
  .step-card {
    width: min(100%, calc(100vw - 20px));
    max-width: calc(100vw - 20px);
    margin-left: auto;
    margin-right: auto;
    border-radius: 20px;
  }
  .step-panel[data-step="2"] {
    overflow-x: hidden;
    padding-top: 22px;
    padding-bottom: 30px;
  }
  .step-panel[data-step="2"] > *:not(#availability) {
    margin-left: 16px;
    margin-right: 16px;
  }
  .step-panel[data-step="2"] > .step2-summary {
    margin-left: 16px;
    margin-right: 16px;
    padding: 10px 12px;
    gap: 10px;
  }
  .step-panel[data-step="2"] > #availability {
    width: 100%;
    overflow: hidden;
  }
  .calendar-wrap[data-mode="centric"] .cal-pane {
    width: 100%;
    padding: 14px 10px 18px;
    overflow: hidden;
  }
  .calendar-wrap[data-mode="centric"] .cal-grid {
    width: 100%;
    gap: 2px;
  }
  .cal-header {
    margin-bottom: 4px;
  }
  .cal-title {
    font-size: 1rem;
  }
  .cal-nav {
    width: 30px;
    height: 30px;
  }
  .cal-dow {
    font-size: 0.64rem;
    letter-spacing: 0.12em;
    padding-bottom: 4px;
    overflow: hidden;
  }
  .cal-cell {
    min-height: 40px;
  }
  .cal-day.cal-available::before {
    transform: translate(-50%, calc(-50% + 15px));
  }
  .cal-day.cal-available:hover::before {
    transform: translate(-50%, calc(-50% + 15px)) scale(1.2);
  }
  .cal-day.cal-available.cal-selected::before,
  .cal-day.cal-selected::before {
    width: 44px;
    height: 44px;
    border-radius: 14px;
  }
  .cal-drawer.is-open {
    max-height: 560px;
    margin: 8px 0 12px;
  }
  .cal-drawer-tray {
    width: min(100%, 360px);
    margin: 12px auto 0;
    padding: 20px 16px 22px;
    gap: 14px;
    border-radius: 18px;
  }
  .cal-drawer-slots,
  .cal-drawer-slots[data-slot-count="1"],
  .cal-drawer-slots[data-slot-count="2"] {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 12px;
    justify-content: stretch;
    width: 100%;
  }
  .cal-drawer-slots > *,
  .cal-drawer-slots[data-slot-count="1"] .slot-btn-drawer,
  .cal-drawer-slots[data-slot-count="2"] .slot-btn-drawer,
  .cal-drawer-slots[data-slot-count="3"] > *,
  .cal-drawer-slots:not([data-slot-count]) > * {
    width: 100%;
    max-width: none;
    min-width: 0;
    flex: none;
  }
  .slot-btn-drawer {
    min-height: 58px;
    padding: 15px 12px;
    font-size: 0.98rem;
    border-radius: 13px;
  }
  .slot-time {
    white-space: nowrap;
  }
  .slot-badge-inline {
    /* Smaller badge so it doesn't overflow the compact pill on mobile. */
    font-size: 0.54rem;
    padding: 2px 7px;
    top: -7px;
    left: 10px;
  }
  .cal-drawer-actions {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.18fr);
    gap: 14px;
    align-items: stretch;
    margin-top: 2px;
  }
  .cal-drawer-back-placeholder { display: none; }
  .cal-drawer-continue { margin-left: 0; }
  .cal-drawer-actions .btn {
    width: 100%;
    min-width: 0;
    flex: 1 1 0;
    min-height: 56px;
    padding: 12px 14px;
    font-size: 0.96rem;
    justify-content: center;
  }
}

@media (max-width: 380px) {
  .cal-drawer-tray {
    padding: 18px 14px 20px;
  }
  .cal-drawer-actions {
    grid-template-columns: minmax(0, 1fr);
  }
  .step-panel[data-step="3"] > .actions,
  .step-panel[data-step="4"] > .actions {
    gap: 14px;
  }
  .step-panel[data-step="3"] > .actions .btn,
  .step-panel[data-step="4"] > .actions .btn {
    padding-left: 14px;
    padding-right: 14px;
  }
}

/* (Legacy `.slots-pane` / `.slots-list` / `.slot-btn-block` / `.slots-prompt`
    rules removed — replaced by Centric Expansion drawer that lives inside
    the calendar grid. Drawer markup is `.cal-drawer` + `.slot-btn-drawer`
    further down.) */
.slot-time {
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: 0.01em;
}
/* Floating EARLIEST badge — overlaps top-left of the slot button.
   Charcoal text + neutral border so it reads as a quiet annotation,
   not a second accent competing with the orange Continue button. */
.slot-badge-inline {
  position: absolute;
  top: -8px;
  left: 12px;
  font-size: 0.58rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--selected-ink);
  background: #fff;
  padding: 3px 8px;
  border-radius: 999px;
  border: 1px solid rgba(39, 41, 54, 0.20);
  z-index: 2;
  transition: color 0.2s, background 0.2s, border-color 0.2s;
}
.slot-btn.selected .slot-badge-inline {
  color: var(--selected-ink);
  background: #fff;
  border-color: rgba(255, 255, 255, 0.5);
}
/* Legacy date-list (still used by tests / fallback) */
.date-list { display: flex; flex-direction: column; gap: 12px; margin-bottom: 8px; }
.date-row {
  display: grid;
  grid-template-columns: 200px 1fr;
  align-items: center;
  gap: 16px;
  padding: 14px;
  background: var(--cream);
  border-radius: var(--radius-sm);
  @media (max-width: 600px) { grid-template-columns: 1fr; }
}
.date-label { font-weight: 600; color: var(--navy); }
.slots { display: flex; gap: 8px; flex-wrap: wrap; }
.slot-btn {
  font-family: inherit;
  font-size: 0.95rem;
  font-weight: 600;
  padding: 12px 16px;
  border-radius: 12px;
  border: 1px solid #E5E7EB;
  background: var(--card);
  color: var(--text);
  cursor: pointer;
  transition: border-color 0.2s, color 0.2s, background 0.2s, box-shadow 0.2s;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.slot-btn:hover:not(.selected) {
  /* Soft halo on hover — neutral charcoal, not orange. The user feels
     the response without the page screaming "primary action!" The
     ring (box-shadow) extends slightly beyond the button as a halo. */
  border-color: rgba(39, 41, 54, 0.30);
  background: #FBFAF7;
  color: var(--text);
  box-shadow: 0 0 0 4px rgba(39, 41, 54, 0.04), 0 1px 2px rgba(39, 41, 54, 0.04);
}
.slot-btn:active:not(:disabled) { transform: scale(0.98); }
/* Selected slot — charcoal, not orange. The selected slot is a
   confirmation of choice, not the primary action; the orange
   Continue button below is the call to action. Was a solid orange
   gradient with orange glow — now charcoal-on-charcoal with a
   subtle ring so it reads as "locked in" but not as "click me". */
.slot-btn.selected {
  background: linear-gradient(180deg, var(--selected-ink), var(--selected-ink));
  color: #fff;
  border: 1.5px solid var(--selected-ink);
  box-shadow:
    0 0 0 4px rgba(39, 41, 54, 0.10),
    0 6px 18px rgba(39, 41, 54, 0.16),
    inset 0 1px 0 rgba(255, 255, 255, 0.10);
  animation: slotPop 0.4s cubic-bezier(0.34, 1.4, 0.64, 1);
}
@keyframes slotPop {
  0% { transform: scale(1); }
  40% { transform: scale(1.08); }
  100% { transform: scale(1); }
}

.date-row { animation: rowIn 0.4s ease-out backwards; }
.date-row:nth-child(1) { animation-delay: 0.02s; }
.date-row:nth-child(2) { animation-delay: 0.06s; }
.date-row:nth-child(3) { animation-delay: 0.10s; }
.date-row:nth-child(4) { animation-delay: 0.14s; }
.date-row:nth-child(5) { animation-delay: 0.18s; }
.date-row:nth-child(n+6) { animation-delay: 0.22s; }
@keyframes rowIn {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ═══ 31. Calendar skeleton ═══ */
/* Calendar-shaped loading state — feels alive while data fetches */
.calendar-loading { animation: fadeIn 0.18s ease-out; }
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

.cal-title-skeleton {
  width: 130px;
  height: 16px;
  border-radius: 6px;
  background: rgba(39, 41, 54, 0.06);
  animation: titleBreathe 1.6s ease-in-out infinite;
}
@keyframes titleBreathe {
  0%, 100% { opacity: 0.5; }
  50% { opacity: 1; }
}

/* Skeleton cells — quiet, no orange pulse. Just a soft fade-in that matches
   the look of unavailable date cells, so the swap to the real calendar
   feels like numbers being drawn over the same grid. */
.cal-skeleton-cell {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  animation: skeletonDot 1.6s ease-in-out infinite;
  animation-delay: var(--delay, 0s);
}
.cal-skeleton-cell::before {
  content: '';
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: rgba(39, 41, 54, 0.16);
}
@keyframes skeletonDot {
  0%, 100% { opacity: 0.5; }
  50% { opacity: 1; }
}

.slots-loading {
  display: flex;
  flex-direction: column;
  gap: 10px;
  animation: fadeIn 0.18s ease-out;
}
.slots-loading-label {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--navy);
  margin-bottom: 6px;
  font-variant-numeric: tabular-nums;
}
.ld-dot {
  display: inline-block;
  margin-left: 1px;
  opacity: 0.2;
  animation: dotFade 1.4s ease-in-out infinite;
  animation-delay: var(--delay, 0s);
}
@keyframes dotFade {
  0%, 60%, 100% { opacity: 0.2; }
  30% { opacity: 1; }
}
.slots-skeleton-bar {
  height: 50px;
  border-radius: 12px;
  background: rgba(39, 41, 54, 0.05);
  animation: barEnter 0.4s cubic-bezier(0.18, 0.89, 0.32, 1.28) backwards,
             barWave 1.6s ease-in-out infinite;
  animation-delay: var(--delay, 0s), calc(var(--delay, 0s) + 0.4s);
}
@keyframes barEnter {
  from { opacity: 0; transform: translateX(-8px); }
  to { opacity: 1; transform: translateX(0); }
}
@keyframes barWave {
  0%, 100% { background: rgba(39, 41, 54, 0.05); }
  50% { background: rgba(224, 122, 42, 0.14); }
}

/* Alerts */
/* ═══ 32. Alerts ═══ */
.alert {
  padding: 14px 18px;
  border-radius: var(--radius-sm);
  margin: 12px 0;
  font-size: 0.92rem;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  border: 1px solid transparent;
  animation: alertIn 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Step 2's #zone-message uses .alert-warning when the address falls
   outside the service area (or for "online booking not live" etc.).
   Two layouts share this element:
   1. Plain-text message variants (hcp_not_configured, no online
      booking yet, etc.) — get the tucked-in cream banner skin.
   2. Admin cross-loc warning — the inner .cross-loc-warn element
      brings its own card chrome (bg, border, radius, shadow, accent
      stripe). When it's present we strip ALL the outer alert chrome
      via :has() so we don't render two stacked cards.
   The :has() check is safe to leave standalone: any browser that
   doesn't support it just gets the original cream skin, which still
   reads acceptably (just doubled chrome). */
#zone-message.alert.alert-warning {
  margin: 14px auto 0;
  /* Narrower than the booking card so it reads as a tucked-in note,
     not a full-width banner. The hcp_not_configured copy uses an
     explicit <br> to force a two-line layout; this width keeps the
     second line from running too long even without the break. */
  max-width: 520px;
  background: #FAF7F1;
  border: 1px solid rgba(39, 41, 54, 0.10);
  border-left: 3px solid var(--orange, #E07A2A);
  border-radius: 14px;
  color: var(--text);
  padding: 14px 18px;
  font-size: 0.94rem;
  line-height: 1.5;
  text-align: center;
}
#zone-message.alert.alert-warning::before {
  /* Drop the warning glyph inside this skin — the orange accent stripe
     already carries the "heads up" weight, and a triangle on a soft
     cream surface reads as decoration rather than signal. */
  content: none;
}
/* When the alert contains the cross-loc-warn admin warning, strip
   the outer chrome — that block has its own card chrome and the
   stacked layers were reading as a card-in-a-card. Forces left-
   align too since the inner block uses a flex head + body that
   shouldn't inherit the parent's text-align:center. */
#zone-message.alert.alert-warning:has(.cross-loc-warn) {
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: 0;
  max-width: none;
  text-align: left;
  margin: 14px 0 0;
}
.alert::before {
  font-weight: 700;
  flex-shrink: 0;
  font-size: 0.95rem;
  line-height: 1.4;
}
.alert:empty { display: none; }
.alert[hidden] { display: none; }
.alert-error {
  background: var(--error-bg);
  color: var(--error);
  border-color: #FECACA;
}
.alert-error::before { content: '⚠'; }
.alert-warning {
  background: var(--warning-bg);
  color: var(--warning-text);
  border-color: #FCD34D;
}
.alert-warning::before { content: '⚠'; }
.alert-success {
  background: var(--success-bg);
  color: var(--success);
  border-color: #BBF7D0;
}
.alert-success::before { content: '✓'; }
@keyframes alertIn {
  from { opacity: 0; transform: translateY(-6px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Success */
/* ═══ 33. Step 5 success ═══ */
/* Step 5 (success): the panel inherits flex column + min-height 720
   from .step-panel, but the success content is short. Center it
   vertically inside the card rather than top-aligning + auto-pushing
   the Finish button to the bottom (which would leave the success
   mark/headline floating in the upper portion). */
.step-panel[data-step="5"] {
  justify-content: center;
}
.step-panel[data-step="5"] .actions { margin-top: 28px; }
.success-card {
  text-align: center;
  padding: 24px 8px;
  @media (max-width: 600px) { padding: 24px 4px; }
}
.success-mark {
  width: 84px;
  height: 84px;
  margin: 0 auto 24px;
  position: relative;
}
.success-mark svg { width: 100%; height: 100%; }
.success-mark .ring {
  fill: none;
  stroke: var(--success);
  stroke-width: 4;
  stroke-dasharray: 240;
  stroke-dashoffset: 240;
  animation: drawRing 0.6s cubic-bezier(0.65, 0, 0.45, 1) 0.1s forwards;
}
.success-mark .check {
  fill: none;
  stroke: var(--success);
  stroke-width: 6;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 50;
  stroke-dashoffset: 50;
  animation: drawCheck 0.4s cubic-bezier(0.65, 0, 0.45, 1) 0.55s forwards;
}
@keyframes drawRing {
  to { stroke-dashoffset: 0; }
}
@keyframes drawCheck {
  to { stroke-dashoffset: 0; }
}
.success-card h2 {
  color: var(--navy);
  margin-bottom: 12px;
  animation: fadeUp 0.5s ease-out 0.7s backwards;
  @media (max-width: 600px) { font-size: 1.5rem; margin-bottom: 8px; }
}
.success-card p {
  animation: fadeUp 0.5s ease-out 0.85s backwards;
}
/* Confirmation-text callout — sits between the headline summary and
   the recap card. Orange-soft pill skin makes it the most visually
   prominent element on the success page after the green checkmark,
   so customers immediately understand they should also be watching
   for an SMS. Inherits the same fade-up cadence as the headline +
   summary so it lands in sequence. */
.success-confirm-note {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin: 18px auto 4px;
  padding: 12px 20px;
  background: var(--orange-soft);
  color: var(--orange-dark);
  border: 1px solid rgba(184, 96, 18, 0.18);
  border-radius: 999px;
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.3;
  text-align: left;
  max-width: 100%;
  animation: fadeUp 0.5s ease-out 0.95s backwards;
}
.success-confirm-note-icon {
  display: inline-flex;
  flex-shrink: 0;
}
@media (max-width: 600px) {
  .success-confirm-note {
    font-size: 0.95rem;
    padding: 11px 16px;
    gap: 8px;
  }
}
/* CSR-facing reminder on the admin success state. Same anatomy as
   .success-confirm-note (icon + text inline) but a quieter sage
   wash instead of the customer-facing orange pill — this is a
   gentle nudge to the admin, not a status announcement to the
   customer. Sits between the recap and the action row, centered. */
.success-admin-reminder {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin: 14px auto 4px;
  padding: 10px 16px;
  background: #EDF4EE;
  color: #2F5C42;
  border: 1px solid #CFE2D4;
  border-radius: 999px;
  font-size: 0.9rem;
  font-weight: 600;
  line-height: 1.3;
  text-align: left;
  max-width: 100%;
  animation: fadeUp 0.5s ease-out 1.0s backwards;
}
.success-admin-reminder-icon {
  display: inline-flex;
  flex-shrink: 0;
  color: #4BAA78;
}
@media (max-width: 600px) {
  .success-admin-reminder {
    font-size: 0.85rem;
    padding: 9px 14px;
    gap: 8px;
  }
}
@media (prefers-reduced-motion: reduce) {
  .success-admin-reminder { animation: none; }
}
/* Finish button on the success card. The default .actions row sets
   justify-content: space-between (sensible for a Back/Continue pair),
   but here we have a single CTA on a centered card — override to
   center it and give it the same fade-in cadence as the headline. */
.success-actions {
  justify-content: center;
  margin-top: 28px;
}
.success-actions .btn-primary { margin-left: 0; }
.success-finish {
  text-decoration: none; /* it's an <a>, not a <button> */
  animation: fadeUp 0.5s ease-out 1s backwards;
  min-width: 180px;
  /* Mobile: full-width since it's the only CTA on the screen. */
  @media (max-width: 600px) { width: 100%; max-width: 320px; }
}
@keyframes fadeUp {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Auto-redirect countdown — appears 15s after the success page lands
   and counts down the final 5 seconds before navigating to the
   marketing thank-you URL. Sits under the Finish button as a quiet
   secondary signal; the Stop button lets the customer hold the page.
   Animated entrance so it never just snaps in. */
.success-redirect-countdown {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin: 14px auto 0;
  padding: 8px 16px;
  font-size: 0.86rem;
  color: var(--text-muted);
  animation: fadeUp 0.32s ease-out both;
}
.success-redirect-countdown[hidden] { display: none !important; }
.success-redirect-text { letter-spacing: 0.01em; }
.success-redirect-seconds {
  display: inline-block;
  min-width: 1ch;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: var(--orange-dark, #B86012);
}
.success-redirect-stop {
  appearance: none;
  background: transparent;
  border: 1px solid rgba(39, 41, 54, 0.18);
  color: var(--ink);
  font-family: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  padding: 5px 14px;
  border-radius: 999px;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.success-redirect-stop:hover {
  border-color: var(--orange);
  background: #FFF6EC;
  color: var(--orange-dark, #B86012);
}
.success-redirect-stop:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgba(243, 138, 63, 0.28);
}

/* Auth pages — match the booking page warmth, premium card */
.auth-card .btn { margin-top: 12px; }

/* Admin service-area picker */
.sa-search-wrap {
  position: relative;
  margin-bottom: 14px;
}

#sa-search {
  width: 100%;
  padding: 12px 14px 12px 38px;
  font-size: var(--t-md);
  font-family: inherit;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: #fff;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%2386868B' stroke-width='1.6'><circle cx='7' cy='7' r='5'/><path d='M11 11l3 3' stroke-linecap='round'/></svg>");
  background-repeat: no-repeat;
  background-position: 12px center;
  background-size: 16px;
}

#sa-search:focus {
  outline: none;
  border-color: var(--orange);
  box-shadow: 0 0 0 3px rgba(224,122,42,0.16);
}

.sa-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 4px 0;
}

.sa-tag {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 6px 6px 12px;
  border-radius: 999px;
  background: var(--orange-soft);
  border: 1px solid rgba(224,122,42,0.22);
  color: var(--text);
  font-size: var(--t-sm);
  font-weight: 600;
}

.sa-tag-icon,
.address-suggestions .sugg-icon {
  width: 14px;
  height: 14px;
  color: var(--orange-dark);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 14px;
}

.sa-tag-icon svg,
.address-suggestions .sugg-icon svg {
  width: 14px;
  height: 14px;
  display: block;
  flex: 0 0 14px;
}

.sa-tag-type {
  display: inline-flex;
  align-items: center;
  padding: 2px 7px;
  border-radius: 999px;
  background: rgba(255,255,255,0.7);
  color: var(--orange-dark);
  font-size: 0.6rem;
  font-weight: 800;
  letter-spacing: 0.06em;
}

.sa-tag-remove {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 0;
  background: transparent;
  color: var(--text-muted);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: inherit;
}

.sa-tag-remove:hover {
  background: rgba(0,0,0,0.06);
  color: var(--st-danger-fg);
}

.sa-empty { width: 100%; }

/* ─── Keyboard shortcuts card ────────────────────────────────────
   Inline collapsible card under the customer rail. Native <details>
   handles the disclosure (a tiny chevron rotates as it opens — no
   JS needed). Closed: just the title row. Open: a 2-column key list.
   Quiet by default so it doesn't compete with the customer rail
   above it. */
.kbd-card {
  margin-top: 12px;
  background: #FCFAF6;
  border: 1px solid #E5DED3;
  border-radius: 14px;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.9) inset;
  overflow: hidden;
}
.kbd-card-summary {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  cursor: pointer;
  list-style: none;
  user-select: none;
  color: #7B8190;
  transition: background 0.12s ease, color 0.12s ease;
}
.kbd-card-summary::-webkit-details-marker { display: none; }
.kbd-card-summary:hover,
.kbd-card-summary:focus-visible {
  outline: none;
  background: rgba(243, 138, 63, 0.06);
  color: #373B4D;
}
.kbd-card-summary-icon { color: currentColor; flex: 0 0 14px; }
.kbd-card-summary-label {
  flex: 1 1 auto;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.02em;
}
.kbd-card-summary-caret {
  color: currentColor;
  flex: 0 0 11px;
  transition: transform 0.18s ease;
}
.kbd-card[open] .kbd-card-summary-caret { transform: rotate(180deg); }
.kbd-card[open] .kbd-card-summary { color: #373B4D; }
.kbd-card-body {
  padding: 4px 14px 14px;
  border-top: 1px dashed rgba(55, 59, 77, 0.14);
}
.kbd-card-list {
  margin: 10px 0 0;
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: 14px;
  row-gap: 10px;
  align-items: center;
}
.kbd-card-list dt {
  display: inline-flex;
  gap: 4px;
  align-items: center;
}
.kbd-card-list dd {
  margin: 0;
  font-size: 0.8rem;
  color: #373B4D;
  line-height: 1.35;
}
/* Mac-style keycap. Slight top-light gradient, 2px bottom border
   for a soft "pressed in" feel, fixed minimum width so single-letter
   and word keys read at the same visual weight. */
.kbd-card-list kbd {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  border: 1px solid #D6CFC2;
  border-bottom-width: 2px;
  border-radius: 6px;
  background: linear-gradient(180deg, #FFFDF9 0%, #F3EFE7 100%);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.72rem;
  font-weight: 600;
  color: #373B4D;
  box-shadow: 0 1px 0 rgba(55, 59, 77, 0.04);
}
.kbd-card-list kbd.kbd-mod { font-size: 0.78rem; }

/* ═══════════════════════════════════════════════════════════════
   Animation pass — admin booking polish
   ═══════════════════════════════════════════════════════════════
   A coherent set of micro-interactions that make the admin flow
   feel smoother. Every animation respects prefers-reduced-motion
   by way of the global guard at the bottom of this block. */

/* Auto-fill flash — when applyCustomer or the cross-loc address
   stash dispatches synthetic 'input' events on the form fields,
   each field gets briefly highlighted with an amber wash so the
   admin's eye can follow the cascade of fills. The class is added
   by booking.js right before the value is set, then removed after
   the keyframe ends. */
@keyframes fieldAutofillFlash {
  0%   { background: rgba(243, 138, 63, 0.22); box-shadow: 0 0 0 2px rgba(243, 138, 63, 0.35); }
  60%  { background: rgba(243, 138, 63, 0.10); box-shadow: 0 0 0 1px rgba(243, 138, 63, 0.18); }
  100% { background: transparent; box-shadow: 0 0 0 0 rgba(243, 138, 63, 0); }
}
.field input.is-autofill-flash,
.field textarea.is-autofill-flash,
input.is-autofill-flash,
textarea.is-autofill-flash {
  animation: fieldAutofillFlash 0.85s cubic-bezier(0.2, 0.85, 0.4, 1) forwards;
}

/* Calendar grid stagger — each tech-head + cell fades up after the
   grid renders. delay is set per-element by booking/calendar.js
   via inline style.animationDelay so the cascade reads as one
   sweep across the grid. */
@keyframes gridCellAppear {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.admin-cal-grid .admin-cal-tech-head,
.admin-cal-grid .admin-cal-cell,
.admin-cal-grid .admin-cal-corner,
.admin-cal-grid .admin-cal-time-head {
  animation: gridCellAppear 0.32s cubic-bezier(0.2, 0.85, 0.4, 1) backwards;
}

/* Cell selection pulse — a brief halo ring expands and fades when
   the admin clicks a cell so the selection feels physically clicked
   rather than instantly swapped. Pure CSS via an ::after pseudo. */
@keyframes cellSelectPulse {
  0%   { opacity: 0.45; transform: scale(0.98); }
  60%  { opacity: 0.18; transform: scale(1.08); }
  100% { opacity: 0;    transform: scale(1.14); }
}
.admin-cal-cell.is-pulsing::after {
  content: '';
  position: absolute;
  inset: -1px;
  border-radius: 12px;
  background: rgba(243, 138, 63, 0.35);
  pointer-events: none;
  animation: cellSelectPulse 0.55s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
.admin-cal-cell { position: relative; }

/* Tag-chip pop-in — newly added pending tags spring into view with
   a small overshoot. .is-popping is added by booking.js when a tag
   is first inserted into the editor, removed after the keyframe. */
@keyframes tagChipPop {
  0%   { opacity: 0; transform: scale(0.6); }
  60%  { opacity: 1; transform: scale(1.08); }
  100% { opacity: 1; transform: scale(1); }
}
.acc-tag.is-popping {
  animation: tagChipPop 0.32s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}

/* Number ticker — the bento stats (12 jobs on file, $7,292 billed,
   etc.) count up from 0 on first render. JS handles the value
   transition; CSS just gives the digits a slight breathing tabular
   width so the layout doesn't reflow as digits change. */
.acc-stat-num,
.success-admin-recap-row dd {
  font-variant-numeric: tabular-nums;
}

/* Skeleton → real content crossfade — the customer rail's two-phase
   load swaps from pulsing skeleton blocks to the real timeline /
   stats. Without a transition the swap snaps. .acc-section,
   .acc-stat, and .acc-timeline get a quick fade-in when they're
   replaced with real content. */
@keyframes contentFadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.acc-section:not(.acc-section--loading),
.acc-stats > .acc-stat:not(.acc-stat--skeleton) {
  animation: contentFadeIn 0.35s ease-out;
}

/* Submit button hover lift — gentle vertical nudge + shadow growth
   so the primary CTA reads as physical. Only fires on hover, not
   focus, so keyboard navigation doesn't get jiggling. */
.admin-flow-submit-row .btn-primary,
#submit-booking {
  transition: transform 0.16s ease, box-shadow 0.16s ease;
}
.admin-flow-submit-row .btn-primary:hover:not(:disabled),
#submit-booking:hover:not(:disabled) {
  transform: translateY(-1px);
  box-shadow: 0 8px 22px rgba(243, 138, 63, 0.30);
}

/* Cross-location warning banner slide-down — the in-calendar
   "address belongs to {other franchise}" banner pops in with a
   subtle drop instead of just appearing. */
@keyframes bannerSlideDown {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}
.cross-loc-warn {
  animation: bannerSlideDown 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Strip viewport — clips the strip horizontally so the ghost
   overlay can slide off-screen during the week-shift animation
   without overflowing the surrounding calendar card. The strip
   inherits flex-row layout from its existing rules; the viewport
   just provides a positioning context + clipping window. */
.admin-cal-strip-viewport {
  position: relative;
  overflow: hidden;
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: stretch;
}
.admin-cal-strip-viewport > .admin-cal-strip {
  flex: 1 1 auto;
  min-width: 0;
}
/* Ghost overlay — absolutely positioned copy of the previous
   strip's cells. Sits on top of the live strip during a week
   swap, slides off-screen, then gets removed. The .admin-cal-strip
   base rules give it the same flex layout so cells line up exactly
   with the live ones during the swap. */
.admin-cal-strip--ghost {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  align-items: stretch;
  justify-content: space-between;
}

/* ── Wave 2 — search dropdown, rail enter, timeline stagger,
   doc chip height, address-resolve dots, cross-loc page xition ── */

/* Search dropdown — each typeahead match slides up with a small
   stagger (set inline via animationDelay on each <li>) so the
   results read as a coordinated reveal instead of a flicker dump.
   The container itself doesn't animate (it's the parent dropdown);
   only the rows do. */
@keyframes searchRowSlideUp {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}
.admin-customer-search-result.is-appearing {
  animation: searchRowSlideUp 0.24s cubic-bezier(0.22, 1, 0.36, 1) backwards;
}

/* Customer rail slide-in from left — fires once when basic data
   lands on a fresh customer pick. JS adds .is-entering to the panel
   when basic data first renders; class self-removes after the
   keyframe. Re-renders triggered by history-phase or tag edits do
   NOT re-trigger this (would feel jumpy). */
@keyframes railSlideIn {
  from { opacity: 0; transform: translateX(-14px); }
  to   { opacity: 1; transform: translateX(0); }
}
#admin-customer-context.is-entering {
  animation: railSlideIn 0.42s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Visit timeline rows — stagger fade-up so the history reads as
   "this is the customer's story" instead of a wall of paperwork.
   Per-row animationDelay set by booking.js after innerHTML. */
@keyframes timelineRowAppear {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}
.acc-timeline:not(.acc-timeline--skeleton) > .acc-tl-visit {
  animation: timelineRowAppear 0.34s cubic-bezier(0.22, 1, 0.36, 1) backwards;
}

/* Doc chip expand/collapse. Earlier passes tried a fancy grid-
   template-rows 1fr/0fr height animation, but that trick only
   constrains the FIRST explicit row — the detail has three children
   (description, line items, open-in-HCP link) so children 2 and 3
   landed in implicit auto-sized rows and took their natural height
   anyway, leaving the card looking permanently half-expanded.
   We removed the animation and went back to the browser default
   show/hide. Snap behavior is fine for this surface — the visual
   weight is in the timeline, not in micro-animations on the chips. */
.acc-tl-doc-caret {
  transition: transform 0.22s cubic-bezier(0.22, 1, 0.36, 1);
  display: inline-block;
}
.acc-tl-doc-summary[aria-expanded="true"] .acc-tl-doc-caret {
  transform: rotate(180deg);
}

/* Address-resolving hint dot trail — when setHint() runs with a
   "Resolving address…" / "Calendar loading…" message, the trailing
   ellipsis becomes three animated dots that breathe in sequence.
   JS replaces the literal "…" with a span structure; CSS handles
   the dot animation. */
@keyframes hintDotPulse {
  0%, 80%, 100% { opacity: 0.2; transform: translateY(0); }
  40%           { opacity: 1;   transform: translateY(-1.5px); }
}
.cs-hint-dots {
  display: inline-flex;
  gap: 2px;
  margin-left: 4px;
}
.cs-hint-dots span {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: currentColor;
  display: inline-block;
  animation: hintDotPulse 1.2s ease-in-out infinite;
}
.cs-hint-dots span:nth-child(2) { animation-delay: 0.15s; }
.cs-hint-dots span:nth-child(3) { animation-delay: 0.30s; }

/* Page transition on cross-location nav — body fades to a quiet
   cream wash before navigation, then fades back in on the new
   page's load. Class toggled by JS in two places: before
   window.location nav (.is-page-leaving) and on initial DOM ready
   (.is-page-entering, removed after the keyframe). */
@keyframes pageFadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes pageFadeOut {
  from { opacity: 1; }
  to   { opacity: 0; }
}
body.is-page-entering {
  animation: pageFadeIn 0.30s ease-out;
}
body.is-page-leaving {
  animation: pageFadeOut 0.22s ease-in forwards;
  pointer-events: none;
}

/* Global reduced-motion override — kill every animation in this
   block when the user has prefers-reduced-motion: reduce set. The
   underlying state (selected, pending, etc.) still updates, just
   without the motion. */
@media (prefers-reduced-motion: reduce) {
  .field input.is-autofill-flash,
  .field textarea.is-autofill-flash,
  input.is-autofill-flash,
  textarea.is-autofill-flash,
  .admin-cal-grid .admin-cal-tech-head,
  .admin-cal-grid .admin-cal-cell,
  .admin-cal-grid .admin-cal-corner,
  .admin-cal-grid .admin-cal-time-head,
  .admin-cal-cell.is-pulsing::after,
  .acc-tag.is-popping,
  .acc-section:not(.acc-section--loading),
  .acc-stats > .acc-stat:not(.acc-stat--skeleton),
  .cross-loc-warn,
  .admin-customer-search-result.is-appearing,
  .customer-search-result.is-appearing,
  #admin-customer-context.is-entering,
  .acc-timeline:not(.acc-timeline--skeleton) > .acc-tl-visit,
  .cs-hint-dots span,
  body.is-page-entering,
  body.is-page-leaving {
    animation: none !important;
  }
  .admin-flow-submit-row .btn-primary:hover:not(:disabled),
  #submit-booking:hover:not(:disabled) {
    transform: none;
  }
}
