/* 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;
}

/* Section eyebrow inside the combined "Details" step (separates the work
   description from the customer's contact details, work on top). */
.step-section-eyebrow {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 12px;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--orange-dark, #B86012);
}
.step-section-eyebrow-ic {
  display: inline-flex;
  width: 24px;
  height: 24px;
  align-items: center;
  justify-content: center;
  border-radius: 7px;
  background: var(--orange-soft, #FFF1E5);
  color: var(--orange-dark, #B86012);
}
/* Confirm step: keep the read-only recap snug under the heading. */
.confirm-review { margin: 0 0 4px; }

/* The public Confirm step is a light read-only recap, so the shared
   step-4 card (sized wide for the admin form) reads oversized here.
   Tuck it in for a calmer, more focused review. Scoped to the public
   form (admin keeps the wide form via data-admin-flow). */
#booking-form:not([data-admin-flow]) .step-card[data-active-step="4"] {
  max-width: 720px;
}

/* "How did you hear about us?" survey on the Confirm step — reuses the
   global .success-survey-* chip styles. Centered, set apart from the
   recap with a hairline divider + extra space, and held to a narrow
   column so the chips wrap into tidy, balanced rows rather than one
   long line. */
.confirm-survey {
  margin: 26px auto 4px;
  padding-top: 22px;
  max-width: 440px;
  text-align: center;
  border-top: 1px solid var(--border, rgba(39, 41, 54, 0.10));
}

/* 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;
}
/* Exclude the picked chip — otherwise this hover rule (more specific
   than .is-picked) keeps the just-clicked chip on its light hover
   background instead of the dark selected fill, so the pick looks like
   it did nothing while the cursor is still over it. */
.success-survey-chip:hover:not(:disabled):not(.is-picked) {
  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: transparent;
  box-shadow: var(--sw-focus-shadow, 0 0 0 1px #F38A3F, 0 6px 13px rgba(243, 138, 63, 0.30));
  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;
  /* Top bumped 18 → 30 so the month/date header gets breathing room
     instead of crowding the step tabs/summary directly above the pane. */
  padding: 30px 28px 16px;
}
/* Center the ‹ date › as one cluster. With the slots-pane gone the pane
   is full-width, so the inherited space-between flung the arrows to the
   far corners and read as "not centered". */
.calendar-wrap[data-mode="centric"] .cal-header {
  justify-content: center;
  gap: 14px;
}
.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);
}
