/* ============================================================================
   BookingSprint dashboard v2 — shared shell
   Tier-adaptive: builder | recovery | recovery-only
   Warm-stone palette · Geist body & display · JetBrains Mono caps
   ========================================================================== */

/* ============================================================================
   :root = DARK by default (Anthropic-grade editorial restraint).
   [data-theme="light"] is the explicit override.
   The JS in dashboard-v2.js sets data-theme="dark" on <html> by default and
   only switches to "light" when the user opts in via the theme toggle.
   ========================================================================== */
:root {
  /* Dark surfaces — deep warm-ink bg with cream foreground */
  --cream:        oklch(20% 0.018 60);
  --cream-2:      oklch(24% 0.020 60);
  --cream-3:      oklch(28% 0.022 60);
  --hairline:     oklch(96% 0.012 80 / 0.10);
  --hairline-2:   oklch(96% 0.012 80 / 0.18);
  --hairline-3:   oklch(96% 0.012 80 / 0.28);
  --orange:       oklch(72% 0.16 38);
  --orange-deep:  oklch(64% 0.18 38);
  --orange-soft:  oklch(78% 0.14 38);
  --orange-wash:  oklch(72% 0.16 38 / 0.14);
  --ink:          oklch(96% 0.012 80);
  --ink-2:        oklch(92% 0.014 80);
  --ink-mute:     oklch(74% 0.014 80);
  --ink-soft:     oklch(64% 0.012 80);
  --ink-faint:    oklch(52% 0.010 80);
  --green:        oklch(70% 0.12 140);
  --green-soft:   oklch(28% 0.06 140);
  --red:          oklch(64% 0.18 38);
  --blue:         #2A6FE1;
  --blue-soft:    rgba(42, 111, 225, 0.18);
  /* Layered surfaces */
  --paper:        oklch(24% 0.020 60);
  --bone:         oklch(26% 0.020 60);
  /* Unified "top band" colour — Safari chrome (theme-color meta) +
     mob-topbar + mobile drawer + the topmost wave-bands rect all use
     this so the chrome → topbar → sidebar surface reads as one
     continuous layer when the drawer slides out. Dark-theme value
     matches --cream so the same blend works under dark. */
  --topband:      oklch(20% 0.018 60);
  /* Premium glass */
  --glass-bg:     rgba(31, 22, 17, 0.78);
  --glass-blur:   saturate(140%) blur(14px);
  /* Easing */
  --ease-out:     cubic-bezier(0.2, 0, 0, 1);
  --ease-in-out:  cubic-bezier(0.4, 0, 0.2, 1);
  /* Space tokens (4px grid) */
  --space-1:  4px;  --space-2:  8px;  --space-3: 12px;
  --space-4: 16px;  --space-5: 20px;  --space-6: 24px;
  --space-7: 32px;  --space-8: 40px;  --space-9: 48px; --space-10: 64px;
  /* Card geometry (Anthropic-grade restraint) */
  --card-pad:        36px;
  --card-pad-tight:  22px;
  --card-radius:      12px;
  --card-radius-data:  8px;
  /* Content widths */
  --content-max:        940px;
  --content-max-narrow: 720px;
}

/* === Light theme override (Claude-style near-neutral) === */
[data-theme="light"] {
  /* Surfaces. Reads as plain white at a glance; only on close
     inspection is there a microscopic warm tint. Matches the
     anthropic.com / claude.ai page surface. */
  --cream:        #FAFAF8;                   /* page bg, near-neutral near-white */
  --cream-2:      #F1F1F1;                   /* hover bg, soft warm-grey step */
  --cream-3:      #E5E3DD;                   /* deep hover / pressed */
  --paper:        #FFFFFF;                   /* card surface, pure white */
  --bone:         #FCFCFA;                   /* alt card surface */
  --topband:      #FEFEFD;                   /* mobile chrome blend (theme-color, topbar, drawer, wave-bands top) — near-white paper */

  /* Hairlines. warm-ink-based alpha so they stay quiet on any cream */
  --hairline:     rgba(31, 22, 17, 0.08);
  --hairline-2:   rgba(31, 22, 17, 0.14);
  --hairline-3:   rgba(31, 22, 17, 0.22);

  /* Ink. warm near-black, matches landing */
  --ink:          #1F1611;
  --ink-2:        #2A1F17;
  --ink-mute:     #6B5A4C;
  --ink-soft:     #8A7866;
  --ink-faint:    oklch(72% 0.012 75);

  /* Accent. keep orange but slightly recalibrated for white bg contrast */
  --orange:       oklch(58% 0.18 38);       /* slightly deeper, sits well on white */
  --orange-deep:  oklch(50% 0.20 38);
  --orange-soft:  oklch(70% 0.14 38);
  --orange-wash:  oklch(58% 0.18 38 / 0.06);

  /* Status. refined */
  --green:        oklch(50% 0.12 145);
  --green-soft:   oklch(94% 0.04 145);
  --red:          oklch(54% 0.20 25);
  --blue:         #2A6FE1;
  --blue-soft:    rgba(42, 111, 225, 0.12);

  /* Glass. pure white with high opacity */
  --glass-bg:     rgba(255, 255, 255, 0.85);
}

/* Light-theme body: solid surface, no noise overlay */
[data-theme="light"] body {
  background-image: none;
}

/* Light-theme site-thumb adjustment */
[data-theme="light"] .site-thumb {
  background: linear-gradient(135deg, oklch(96% 0.003 250) 0%, oklch(92% 0.005 250) 100%);
  color: oklch(30% 0.012 240);
}
[data-theme="light"] .site-thumb-content { color: oklch(28% 0.012 240); }
[data-theme="light"] .site-thumb-content em { color: var(--orange); }

/* Light-theme sidebar: uses --topband so it (a) blends with the Safari
   chrome via theme-color on mobile, (b) reads as a subtly warmer "panel"
   sitting next to the neutral page on desktop. */
[data-theme="light"] .side {
  background: var(--topband);
  border-right: 1px solid rgba(0, 0, 0, 0.06);
}
[data-theme="light"] .side-link.is-active {
  background: var(--cream-3);
  color: var(--ink);
}

/* Light-theme. primary button keeps ink dark for crisp contrast on white */
[data-theme="light"] .btn-primary {
  background: var(--ink);
  color: #fff;
  border-color: var(--ink);
}
[data-theme="light"] .btn-primary:hover {
  background: var(--ink-2);
  border-color: var(--ink-2);
}

/* Light-theme. diamond-rosette hero stays orange but slightly deeper */
[data-theme="light"] .home-mark-svg { fill: var(--orange); }

/* Light-theme. modals / glass elements pure white */
[data-theme="light"] .cmdk-modal {
  background: #FFFFFF;
  box-shadow: 0 24px 64px rgba(15, 25, 50, 0.10), 0 0 0 1px var(--hairline);
}

[data-theme="light"] .cmdk-backdrop {
  background: rgba(15, 25, 50, 0.32);
}

[data-theme="light"] .mobile-nav {
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: saturate(140%) blur(14px);
  -webkit-backdrop-filter: saturate(140%) blur(14px);
}

[data-theme="light"] .dash-toast {
  background: var(--ink);
  color: #fff;
}

/* Legacy hex fallback for browsers without OKLCH support.
   Dark base + light override — mirrors the OKLCH block above. */
@supports not (color: oklch(0% 0 0)) {
  :root {
    --cream:        #1F1611;
    --cream-2:      #2A1F17;
    --cream-3:      #36281E;
    --hairline:     rgba(245, 245, 245, 0.10);
    --hairline-2:   rgba(245, 245, 245, 0.18);
    --hairline-3:   rgba(245, 245, 245, 0.28);
    --orange:       #E26A39;
    --orange-deep:  #C8521E;
    --orange-soft:  #EE8E60;
    --orange-wash:  rgba(226, 106, 57, 0.14);
    --ink:          #F5F5F5;
    --ink-2:        #ECECEC;
    --ink-mute:     #B6A99A;
    --ink-soft:     #998C7E;
    --ink-faint:    #786C61;
    --green:        #87B084;
    --green-soft:   #2C3F2C;
    --red:          #E26A39;
    --blue:         #8FB7C8;
    --blue-soft:    #213340;
    --paper:        #2A1F17;
    --bone:         #2D2218;
  }
  [data-theme="light"] {
    --cream:        #F8F9FB;
    --cream-2:      #F1F3F6;
    --cream-3:      #E6E9ED;
    --hairline:     rgba(15, 25, 50, 0.10);
    --hairline-2:   rgba(15, 25, 50, 0.16);
    --hairline-3:   rgba(15, 25, 50, 0.24);
    --orange:       #C8511E;
    --orange-deep:  #A8400F;
    --orange-soft:  #E07A4A;
    --orange-wash:  rgba(200, 81, 30, 0.06);
    --ink:          #14181F;
    --ink-2:        #1E232C;
    --ink-mute:     #5B6470;
    --ink-soft:     #79828F;
    --ink-faint:    #A2AAB6;
    --green:        #2E7D5B;
    --green-soft:   #E5F1EC;
    --red:          #C73E2C;
    --blue:         #1E5FB8;
    --blue-soft:    #E5EEF9;
    --paper:        #FFFFFF;
    --bone:         #FCFCFD;
  }
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }

/* 2026-05-22: html bg explicit so the safe-area-inset region below the
   builder chat input doesn't show a white seam between the cream body
   and the iOS Safari URL chrome. Without this, html defaults to white
   and any reveal (rubber-band, safe-area, content > viewport) flashes
   white. Matches the body bg one-to-one. */
html { background-color: var(--cream); }

/* 2026-05-22 belt-and-braces: in builder-chat mode every ancestor
   that could render below the chat input gets explicit cream bg.
   Without this, the safe-area-inset-bottom padding on the chat-pane
   showed white on iOS Safari because the inherited transparent
   chain ended at an element whose computed bg wasn't being applied
   (cache, parse-cascade, or iOS-specific safe-area painting behaviour
   — fixing each layer makes any of those root causes go away). */
body.is-builder-chat,
body.is-builder-chat .shell,
body.is-builder-chat .shell > main.dash,
body.is-builder-chat .builder-shell,
body.is-builder-chat .builder-shell--centered,
body.is-builder-chat .builder-chat-pane,
body.is-builder-chat .builder-input-wrap {
  /* Builder page bg = --topband so the WHOLE AI builder surface reads
     as one continuous warm-cream panel matching the sidebar + topbar +
     composer. (Was --cream / #FAFAF8 earlier — different shade from the
     sidebar #FEFEFD, founder flagged that mismatch 2026-05-22.) */
  background-color: var(--topband) !important;
}
/* Ensure [hidden] beats display: grid/flex/inline-flex set on .side-id, .metrics, .body-grid, etc. */
[hidden] { display: none !important; }
body {
  font-family: 'Geist', 'Inter', system-ui, sans-serif;
  background-color: var(--cream);
  background-image: none;
  color: var(--ink);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* Hanging punctuation on display headings */
.dash-greeting h1, .pg-title, .rich-title, blockquote, .pull-quote {
  hanging-punctuation: first allow-end last;
}

/* Variable font features (Geist stylistic alternates) */
.dash-greeting h1, .pg-title, .rich-title, .display, .metric-value,
.site-info h2, .rec-hero h2 {
  font-feature-settings: "ss01" 1, "ss02" 1, "cv11" 1, "cv01" 1;
  font-optical-sizing: auto;
}

/* Frosted decorative divider utility */
.frosted-rule {
  border: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--hairline-2) 18%, var(--hairline-2) 82%, transparent);
  margin: var(--space-6) 0;
}

/* === Smart focus rings (adaptive) ========================================= */
:focus-visible {
  outline: 2px solid var(--orange);
  outline-offset: 2px;
  border-radius: 6px;
}
.btn:focus-visible { outline-offset: 3px; }
/* Dark surfaces (sidebar active link, primary button) get warm cream halo */
.side-link.is-active:focus-visible,
.btn-primary:focus-visible {
  outline: 0;
  box-shadow: 0 0 0 3px oklch(96.5% 0.018 80 / 0.4), inset 0 1px 0 rgba(255,255,255,0.06);
}

/* === Layout shell ========================================================== */
.shell {
  display: grid;
  grid-template-columns: 264px 1fr;
  min-height: 100vh;
}

/* === Sidebar =============================================================== */
/* Chrome-less: sidebar bg uses --topband so chrome → topbar → sidebar
   reads as one continuous surface (mobile drawer blend) and the desktop
   sidebar reads as a subtly warmer panel next to the neutral page. */
.side {
  background: var(--topband);
  border-right: 1px solid rgba(0, 0, 0, 0.06);
  height: 100vh;
  position: fixed; top: 0; left: 0;
  width: 264px;
  display: flex; flex-direction: column;
  overflow: hidden;
  z-index: 30;
  grid-column: 1;
  grid-row: 1;
}
/* Sidebar is fixed → out of grid flow. Pin .dash + sub-page wrappers
   to column 2 explicitly so they don't fall back into column 1. */
.shell > main.dash,
.shell > .pg,
.shell > .rich-page { grid-column: 2; }
[data-theme="dark"] .side {
  border-right-color: rgba(255, 255, 255, 0.12);
}
.side-link:hover { background: var(--cream-3); }
.side-link.is-active { background: var(--cream-3); }

/* Sidebar collapse toggle (inside sidebar header) */
.side-brand { justify-content: flex-start; }
.side-toggle {
  margin-left: auto;
  background: transparent;
  border: 0;
  padding: 5px;
  border-radius: 6px;
  color: var(--ink-mute);
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background 0.18s cubic-bezier(0.2, 0, 0, 1), color 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.side-toggle:hover { background: var(--cream-3); color: var(--ink); }
.side-toggle svg { display: block; }

/* Floating "open sidebar" button — visible in AI builder first-run state.
   Lovable-style flat disc: soft ambient shadow + faint ring, no convex
   gradient, no rim-light. Lovable's button is nearly flat — the weight
   comes from the size + clean borderless edge, not heavy shadow. */
.side-opener {
  display: none;
  align-items: center;
  justify-content: center;
  position: fixed;
  top: 14px; left: 14px;
  z-index: 60;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--paper);
  color: var(--ink);
  cursor: pointer;
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, 0.04),
    0 1px 2px rgba(0, 0, 0, 0.04),
    0 4px 12px rgba(0, 0, 0, 0.06);
  transition: transform 0.18s var(--ease-out, cubic-bezier(0.2, 0, 0, 1)),
              box-shadow 0.18s var(--ease-out, cubic-bezier(0.2, 0, 0, 1));
}
.side-opener:hover {
  transform: translateY(-1px);
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, 0.06),
    0 2px 4px rgba(0, 0, 0, 0.06),
    0 8px 20px rgba(0, 0, 0, 0.08);
}
.side-opener:active {
  transform: translateY(0);
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, 0.06),
    0 1px 2px rgba(0, 0, 0, 0.04);
}
.side-opener svg { display: block; }
/* Collapsed sidebar = narrow icon rail (Lovable-style). The sidebar stays
   visible at 64px, labels hide, icons center. side-opener stays out of
   the way since the rail is its own affordance. */
body.is-side-collapsed .shell,
html.is-side-collapsed-pre body .shell { grid-template-columns: 64px 1fr; }
body.is-side-collapsed .side,
html.is-side-collapsed-pre body .side { width: 64px; }
body.is-side-collapsed .side-opener,
html.is-side-collapsed-pre body .side-opener { display: none; }

/* Hide text + show icons centered. Brand wordmark, link labels,
   kbd hints, upgrade-card body text, and account name all collapse;
   icons + diamond logo + avatar circle stay visible. */
body.is-side-collapsed .side-brand-name,
body.is-side-collapsed .side-link > span:not(.bs-logo-mark):not([class*="logo"]),
body.is-side-collapsed .side-link-kbd,
body.is-side-collapsed .side-foot-upgrade__title,
body.is-side-collapsed .side-foot-upgrade__cta,
body.is-side-collapsed .side-foot-name { display: none !important; }

body.is-side-collapsed .side-brand {
  flex-direction: column;
  gap: 8px;
  padding: 14px 8px;
  align-items: center;
}
body.is-side-collapsed .side-toggle { margin-left: 0; }
body.is-side-collapsed .side-link {
  justify-content: center;
  padding: 10px 8px;
  gap: 0;
}
body.is-side-collapsed .side-nav-section { margin: 12px 14px; }
body.is-side-collapsed .side-foot-upgrade {
  padding: 8px;
  display: flex;
  justify-content: center;
  /* Show a small icon stand-in instead of the lost text. */
  min-height: 32px;
}
body.is-side-collapsed .side-foot-upgrade::before {
  content: '';
  display: block;
  width: 18px;
  height: 18px;
  border-radius: 5px;
  background: var(--accent-orange, #D57529);
  opacity: 0.85;
}
body.is-side-collapsed .side-foot {
  padding: 12px 8px;
  display: flex;
  justify-content: center;
}
body.is-side-collapsed .side-foot-avatar { margin: 0; }

/* Single small logo top-left. No surrounding card. */
.side-brand {
  display: flex; align-items: center; gap: 8px;
  padding: 18px 18px 14px;
}
.side-brand-name {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-weight: 500;
  font-size: 14px;
  color: var(--ink);
  white-space: nowrap;
}

.side-nav {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 6px 10px 10px;
  scrollbar-width: thin;
  display: flex; flex-direction: column;
}

/* Visual gap between groups (replaces the old mono-caps section heads) */
.side-nav-gap {
  height: 14px;
}
/* Cleaner section break — thin hairline + breathing room. Used in the
   6-item sidebar to separate site / daily-work / settings sections. */
.side-nav-section {
  height: 1px;
  background: var(--hairline);
  margin: 12px 8px;
}

/* Search row — looks like a side-link but acts as a button (opens Cmd+K).
   Reset native button defaults so it sits flush with anchor side-links. */
.side-link--search {
  background: transparent;
  border: 0;
  width: 100%;
  text-align: left;
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.side-link-kbd {
  margin-left: auto;
  font-family: 'Geist', 'Inter', sans-serif;
  font-size: 0.68rem;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
  background: var(--cream-2);
  border: 1px solid var(--hairline);
  border-radius: 5px;
  padding: 2px 6px;
  line-height: 1;
  font-weight: 500;
}
.side-link:hover .side-link-kbd { color: var(--ink-mute); }

.side-link {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  background: transparent;
  border: 0;
  padding: 7px 10px;
  border-radius: 6px;
  font-family: inherit;
  font-size: 14px;
  color: var(--ink-mute);
  cursor: pointer;
  text-align: left;
  text-decoration: none;
  transition: background 0.18s cubic-bezier(0.2, 0, 0, 1), color 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.side-link > svg {
  flex-shrink: 0;
  color: var(--ink-faint);
  transition: color 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.side-link:hover > svg,
.side-link.is-active > svg {
  color: var(--ink);
}
.side-link.is-dim > svg { color: var(--ink-faint); }
a.side-link { color: var(--ink-mute); }
a.side-link:visited { color: var(--ink-mute); }
.side-link:hover { background: var(--cream-2); color: var(--ink); }
.side-link.is-active {
  background: var(--cream-2);
  color: var(--ink);
  font-weight: 500;
}
/* Hub-style sidebar item: chevron pushed to the right edge so the
   item reads as "tap to open this group". Used on Site / Bookings /
   Settings parent items in the consolidated 6-item sidebar. */
.side-link--hub > span:not(.side-link__chev) { flex: 1 1 auto; }
.side-link__chev {
  margin-left: auto;
  color: var(--ink-faint);
  font-size: 14px;
  line-height: 1;
  transition: color 0.18s cubic-bezier(0.2, 0, 0, 1), transform 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.side-link--hub:hover .side-link__chev,
.side-link--hub.is-active .side-link__chev { color: var(--ink); transform: translateX(2px); }
.side-link.is-dim { color: var(--ink-faint); }
.side-link.is-dim:hover { color: var(--ink); }

.side-foot {
  border-top: 0;
  padding: 14px 12px;
  display: flex; align-items: center; gap: 10px;
}
.side-foot-avatar {
  width: 24px; height: 24px;
  border-radius: 50%;
  background: var(--cream-3);
  color: var(--ink);
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: 500; font-size: 12px;
  flex: 0 0 auto;
}
.side-foot-name {
  font-size: 14px;
  color: var(--ink-mute);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Sidebar upgrade nudge — sits above the avatar row, Builder tier only.
   Placeholder href #bump-up per CLAUDE.md (don't wire to /billing yet). */
.side-foot-upgrade {
  display: block;
  margin: 8px 12px 0;
  padding: 12px 14px;
  background: var(--cream-3);
  border-radius: 10px;
  text-decoration: none;
  transition: background 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.side-foot-upgrade:hover { background: var(--cream-2); }
.side-foot-upgrade__title {
  display: block;
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
  line-height: 1.3;
  margin-bottom: 4px;
}
.side-foot-upgrade__cta {
  font-size: 12px;
  color: var(--orange);
  font-weight: 500;
}
@media (max-width: 899px) { .side-foot-upgrade { display: none; } }

/* BS logo mark */
.bs-logo-mark {
  display: inline-block;
  width: 18px; height: 18px;
  flex-shrink: 0;
  line-height: 0;
}
.bs-logo-mark svg { width: 100%; height: 100%; display: block; }
.bs-logo-mark__d {
  fill: var(--ink);
  transform-origin: center;
  transition: opacity 0.25s ease;
}
/* The hero diamond rosette on home page keeps the orange brand mark. */
.home-mark-svg .bs-logo-mark__d,
.home-mark-svg.is-brand .bs-logo-mark__d { fill: var(--orange); }

/* === Main dash ============================================================ */
.dash {
  width: 100%; /* fill the grid cell up to max-width — without this, the inner flex column shrinks to min-content of its centered children */
  max-width: 940px;
  margin: 0 auto;
  /* 2026-05-28: bottom-padding bumped from 80→128 so the fixed mobile
     sidebar drawer footer (.side-foot, ~52px tall) never overlaps the
     last few items on the main scroll. Desktop has the same value;
     overshoot doesn't hurt because the dash is centred in a 940px max
     and the extra whitespace below the last section reads as breathing
     room, not waste. */
  padding: 16px 32px 128px;
  display: flex; flex-direction: column;
  gap: 0;
  box-sizing: border-box;
}
/* Sub-pages (settings, conversations, etc.) share the same narrow editorial column. */
.pg, .rich-page {
  max-width: 940px;
  margin: 0 auto;
  padding: 80px 32px 80px;
  display: flex; flex-direction: column;
  gap: 22px;
}

.dash-greeting {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px;
}
.dash-greeting-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.dash-greeting-actions { display: flex; gap: 8px; flex-shrink: 0; }
.dash-greeting h1 {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: clamp(1.85rem, 3.4vw, 2.4rem);
  font-weight: 500;
  margin: 0;
  letter-spacing: -0.028em;
  line-height: 1.05;
}
.dash-greeting h1 em { color: var(--ink); font-style: normal; }
.dash-greeting .date {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.62rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.dash-new-booking { background: var(--paper); }

/* === Hero: site card (Builder + Recovery) ================================= */
.site-card {
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--card-radius);
  padding: var(--card-pad);
  display: grid;
  grid-template-columns: 144px 1fr auto;
  gap: 22px;
  align-items: center;
  transition: border-color 0.18s cubic-bezier(0.2, 0, 0, 1), background 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.site-card:hover { border-color: var(--hairline-2); }
.site-thumb {
  width: 144px; height: 96px;
  border-radius: 8px;
  background: linear-gradient(135deg, #15212B 0%, #0F1820 100%);
  position: relative;
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
}
.site-thumb::before {
  content: '';
  position: absolute; inset: 0;
  background: radial-gradient(ellipse at top right, rgba(242,176,30,0.16), transparent 60%);
}
.site-thumb-content {
  position: relative; z-index: 1;
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-weight: 700;
  font-size: 0.74rem;
  color: #F5F5F5;
  text-align: center;
  line-height: 1.15;
  padding: 0 12px;
}
.site-thumb-content em { color: #F2B01E; font-style: italic; }
.site-info-eye {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--green);
  font-weight: 600;
}
.site-info-eye::before {
  content: '';
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--green);
  animation: pulse 2.4s ease-in-out infinite;
}
.site-info h2 {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-weight: 600;
  font-size: 1.18rem;
  margin: 6px 0 4px;
  letter-spacing: -0.01em;
}
.site-url {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.84rem;
  color: var(--ink);
  background: var(--cream-2);
  padding: 4px 9px;
  border-radius: 5px;
  text-decoration: none;
}
.site-url:hover { background: var(--cream-3); }
.site-info-meta {
  margin-top: 8px;
  font-size: 0.78rem;
  color: var(--ink-mute);
}
.site-actions { display: flex; gap: 8px; }

/* === Hero: recovery-active (Recovery-only) ================================ */
.rec-hero {
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--card-radius);
  padding: var(--card-pad);
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 20px;
  align-items: center;
  transition: border-color 0.18s cubic-bezier(0.2, 0, 0, 1), background 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.rec-hero:hover { border-color: var(--hairline-2); }
.rec-hero-eye {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--green);
  font-weight: 600;
  margin-bottom: 6px;
}
.rec-hero-eye::before {
  content: '';
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--green);
  animation: pulse 2.4s ease-in-out infinite;
}
.rec-hero h2 {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-weight: 600;
  font-size: 1.35rem;
  margin: 0 0 8px;
  letter-spacing: -0.015em;
}
.rec-hero h2 em { color: var(--ink); font-style: normal; }
.rec-hero-stats { display: flex; gap: 20px; margin-top: 8px; }
.rec-hero-stat-label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.56rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 3px;
}
.rec-hero-stat-value {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 1.2rem;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: -0.01em;
}
.rec-hero-actions { display: flex; flex-direction: column; gap: 8px; }

/* === Hero: connect existing site (Recovery-only) ========================== */
.connect-card {
  background: var(--bone);
  border: 1px dashed var(--hairline-2);
  border-radius: var(--card-radius);
  padding: var(--card-pad);
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 22px;
  align-items: center;
  transition: border-color 0.18s cubic-bezier(0.2, 0, 0, 1), background 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.connect-card:hover { border-color: var(--hairline-3); }
.connect-eye {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-mute);
  font-weight: 600;
  margin-bottom: 8px;
}
.connect-card h3 {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-weight: 600;
  font-size: 1.18rem;
  margin: 0 0 6px;
  letter-spacing: -0.01em;
}
.connect-body {
  font-size: 0.88rem;
  color: var(--ink-mute);
  margin: 0 0 12px;
  max-width: 56ch;
  line-height: 1.55;
}
.connect-options {
  display: flex; gap: 10px;
  flex-wrap: wrap;
}
.connect-options .btn-ghost { background: var(--paper); }

/* === Buttons ============================================================== */
.btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 9px 14px;
  font-family: inherit;
  font-size: 0.84rem;
  font-weight: 500;
  border-radius: 8px;
  cursor: pointer;
  border: 1px solid transparent;
  transition: background 0.18s cubic-bezier(0.2, 0, 0, 1), border-color 0.18s cubic-bezier(0.2, 0, 0, 1), color 0.18s cubic-bezier(0.2, 0, 0, 1);
  text-decoration: none;
  color: var(--ink);
  white-space: nowrap;
}
.btn svg { width: 13px; height: 13px; }
.btn-ghost { background: transparent; border-color: var(--hairline-2); }
.btn-ghost:hover { background: var(--cream-2); border-color: var(--hairline-3); }
.btn-primary {
  background: var(--orange);
  color: #fff;
  border-color: var(--orange);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
.btn-primary:hover {
  background: var(--orange-deep);
  border-color: var(--orange-deep);
}
.btn-primary:active {
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.18);
}

/* === Metrics row ========================================================== */
.metrics {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
}
.metrics.is-five { grid-template-columns: repeat(5, 1fr); }
.metric {
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--card-radius);
  padding: var(--card-pad);
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  transition: border-color 0.18s cubic-bezier(0.2, 0, 0, 1), background 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.metric:hover {
  border-color: var(--hairline-2);
}
.metric.is-empty {
  background: transparent;
  border: 1px dashed var(--hairline-2);
}
.metric-label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.58rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-mute);
  margin-bottom: 8px;
  display: flex; align-items: center; justify-content: space-between;
  gap: 6px;
}
.metric-label .badge {
  background: var(--green-soft);
  color: var(--green);
  padding: 2px 6px;
  border-radius: 4px;
  font-weight: 700;
  letter-spacing: 0.1em;
  font-size: 0.55rem;
}
.metric-label .badge.is-empty {
  background: var(--cream-2);
  color: var(--ink-mute);
}
.metric-value {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: clamp(1.55rem, 2.4vw, 1.85rem);
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.02em;
  line-height: 1;
}
.metrics.is-five .metric-value { font-size: clamp(1.45rem, 2.2vw, 1.7rem); }
.metric.is-empty .metric-value { color: var(--ink-faint); }
.metric-sub {
  margin-top: 6px;
  font-size: 0.76rem;
  color: var(--ink-mute);
}
.metric-sub.is-up { color: var(--green); }
.metric-sub.is-up::before { content: '▲ '; }
.metric.is-empty .metric-sub { color: var(--ink-mute); }

/* === Two-column body ====================================================== */
.body-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: 18px;
  align-items: start;
}
.body-grid > .panel { grid-column: 1; grid-row: 1 / span 2; }
.body-grid > .body-side { grid-column: 2; grid-row: 1; display: flex; flex-direction: column; gap: 14px; }
.body-grid > .upsell { grid-column: 2; grid-row: 2; }

.panel {
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--card-radius);
  padding: var(--card-pad);
  transition: border-color 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.panel:hover { border-color: var(--hairline-2); }
.panel-head {
  display: flex; align-items: baseline; justify-content: space-between;
  margin-bottom: 14px;
  gap: 10px;
}
.panel-title {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 1.1rem;
  font-weight: 600;
  color: var(--ink);
}
.panel-title strong { color: var(--ink); font-weight: 700; }
.panel-link {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-mute);
  text-decoration: none;
}
.panel-link:hover { color: var(--ink); }

/* === Leads (Builder) ====================================================== */
.leads { display: flex; flex-direction: column; }
.lead {
  display: grid;
  grid-template-columns: 36px 1fr auto;
  gap: 12px;
  padding: 11px 0;
  border-bottom: 1px solid var(--hairline);
  align-items: flex-start;
}
.lead:last-child { border-bottom: 0; }
.lead-avatar {
  width: 36px; height: 36px;
  border-radius: 9px;
  background: var(--cream-2);
  color: var(--ink);
  display: flex; align-items: center; justify-content: center;
  font-weight: 600; font-size: 0.86rem;
  flex: 0 0 auto;
}
.lead-text { min-width: 0; }
.lead-name {
  font-weight: 600; color: var(--ink); font-size: 0.92rem;
  display: flex; align-items: center; gap: 6px;
  flex-wrap: wrap;
}
.lead-name .pill {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.54rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  background: var(--cream-2);
  color: var(--ink);
  padding: 2px 5px;
  border-radius: 4px;
  font-weight: 700;
}
.lead-meta {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-top: 2px;
}
.lead-job {
  font-size: 0.82rem;
  color: var(--ink-mute);
  margin-top: 4px;
  line-height: 1.45;
}
.lead-action {
  background: transparent;
  border: 1px solid var(--hairline-2);
  border-radius: 7px;
  padding: 6px 10px;
  font-family: inherit;
  font-size: 0.74rem;
  color: var(--ink);
  cursor: pointer;
  flex: 0 0 auto;
}
.lead-action:hover { background: var(--cream-2); }

/* === Activity feed (Recovery + Recovery-only) ============================= */
.activity { display: flex; flex-direction: column; }
.act {
  display: grid;
  grid-template-columns: 32px 1fr auto;
  gap: 11px;
  padding: 11px 0;
  border-bottom: 1px solid var(--hairline);
  align-items: flex-start;
}
.act:last-child { border-bottom: 0; }
.act-icon {
  width: 32px; height: 32px;
  border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
}
.act-icon svg { width: 14px; height: 14px; }
.act-icon.is-call { background: var(--cream-2); color: var(--ink); }
.act-icon.is-form { background: var(--cream-2); color: var(--ink); }
.act-icon.is-sms  { background: var(--blue-soft); color: var(--blue); }
.act-icon.is-book { background: var(--green-soft); color: var(--green); }
.act-text { min-width: 0; }
.act-name {
  font-weight: 600; color: var(--ink); font-size: 0.9rem;
  display: flex; align-items: center; gap: 6px;
  flex-wrap: wrap;
}
.act-name .pill {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.52rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  background: var(--cream-2);
  color: var(--ink);
  padding: 2px 5px;
  border-radius: 4px;
  font-weight: 700;
}
.act-name .pill.is-booked { background: var(--green-soft); color: var(--green); }
.act-name .pill.is-source { background: var(--cream-2); color: var(--ink-mute); font-weight: 600; }
.act-meta {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.58rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-top: 2px;
}
.act-job {
  font-size: 0.82rem;
  color: var(--ink-mute);
  margin-top: 4px;
  line-height: 1.45;
}
.act-action {
  background: transparent;
  border: 1px solid var(--hairline-2);
  border-radius: 7px;
  padding: 6px 10px;
  font-family: inherit;
  font-size: 0.74rem;
  color: var(--ink);
  cursor: pointer;
  flex: 0 0 auto;
}
.act-action:hover { background: var(--cream-2); }

/* === Upsell card (variants per tier) ====================================== */
.upsell {
  background: var(--ink);
  color: var(--cream);
  border-radius: var(--card-radius);
  padding: var(--card-pad);
  position: relative;
  overflow: hidden;
}
.upsell::before {
  content: '';
  position: absolute; inset: 0;
  background: radial-gradient(ellipse at top right, rgba(217,84,30,0.10), transparent 60%);
  pointer-events: none;
}
.upsell-content { position: relative; z-index: 1; }
.upsell-eye {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--orange-soft);
  font-weight: 600;
  margin-bottom: 10px;
  display: inline-flex; align-items: center; gap: 6px;
}
.upsell-eye::before {
  content: '';
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--orange);
}
.upsell h3 {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 1.18rem;
  font-weight: 600;
  margin: 0 0 6px;
  letter-spacing: -0.01em;
}
.upsell-body {
  font-size: 0.84rem;
  line-height: 1.55;
  color: rgba(255,255,255,0.78);
  margin: 10px 0 14px;
}
.upsell-stat {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin: 14px 0 16px;
}
.upsell-stat-cell {
  background: rgba(255,255,255,0.06);
  border-radius: 9px;
  padding: 10px 12px;
}
.upsell-stat-cell-label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.54rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.55);
  margin-bottom: 4px;
}
.upsell-stat-cell-value {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 1.5rem;
  font-weight: 600;
  letter-spacing: -0.02em;
}
.upsell-stat-cell.is-loss .upsell-stat-cell-value { color: var(--orange-soft); }
.upsell-bullets {
  list-style: none; padding: 0; margin: 14px 0 16px;
  display: flex; flex-direction: column; gap: 8px;
}
.upsell-bullets li {
  display: flex; align-items: flex-start; gap: 9px;
  font-size: 0.84rem;
  color: rgba(255,255,255,0.84);
  line-height: 1.45;
}
.upsell-bullets svg {
  width: 13px; height: 13px;
  flex: 0 0 auto;
  color: var(--orange-soft);
  margin-top: 3px;
}
.upsell-paths {
  display: flex; flex-direction: column; gap: 8px;
  margin: 10px 0 14px;
}
.upsell-path {
  background: rgba(255,255,255,0.06);
  border-radius: 9px;
  padding: 10px 12px;
  display: flex; align-items: center; gap: 10px;
  cursor: pointer;
  transition: background 0.15s;
  border: 1px solid transparent;
  width: 100%;
  font-family: inherit;
  text-align: left;
  color: inherit;
}
.upsell-path:hover {
  background: rgba(255,255,255,0.1);
  border-color: rgba(255,255,255,0.15);
}
.upsell-path-icon {
  width: 28px; height: 28px;
  border-radius: 7px;
  background: rgba(217,84,30,0.18);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--orange-soft);
  flex: 0 0 auto;
}
.upsell-path-icon svg { width: 13px; height: 13px; }
.upsell-path-text { flex: 1 1 auto; }
.upsell-path-name { font-size: 0.86rem; font-weight: 600; color: #fff; }
.upsell-path-desc {
  font-size: 0.72rem;
  color: rgba(255,255,255,0.6);
  margin-top: 2px;
  line-height: 1.4;
}
.upsell-path svg.chev {
  width: 12px; height: 12px;
  color: rgba(255,255,255,0.4);
  flex: 0 0 auto;
}
.upsell-cta {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 10px 14px;
  background: var(--orange);
  color: #fff;
  border: 0;
  border-radius: 9px;
  font-family: inherit;
  font-size: 0.84rem;
  font-weight: 600;
  cursor: pointer;
}
.upsell-cta:hover { background: var(--orange-deep); }
.upsell-cta svg { width: 12px; height: 12px; }
.upsell-foot {
  margin-top: 10px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.45);
}
.upsell-foot strong { color: rgba(255,255,255,0.85); }

/* === Quick actions ======================================================== */
.quick {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
}
.quick-card {
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--card-radius);
  padding: var(--card-pad-tight);
  text-align: left;
  cursor: pointer;
  display: flex; align-items: center; gap: 10px;
  font-family: inherit;
  font-size: 0.86rem;
  color: var(--ink);
  transition: border-color 0.18s cubic-bezier(0.2, 0, 0, 1), background 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.quick-card:hover {
  border-color: var(--hairline-2);
}
.quick-card-icon {
  width: 30px; height: 30px;
  border-radius: 8px;
  background: var(--cream-2);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--ink-mute);
  flex: 0 0 auto;
}
.quick-card-icon svg { width: 14px; height: 14px; }

/* === Site health strip ==================================================== */
.health {
  background: var(--bone);
  border: 1px solid var(--hairline);
  border-radius: var(--card-radius);
  padding: var(--card-pad-tight);
  display: flex; align-items: center; gap: 22px;
  flex-wrap: wrap;
  font-size: 0.82rem;
  color: var(--ink-mute);
}
.health-item {
  display: inline-flex; align-items: center; gap: 6px;
}
.health-item svg { width: 13px; height: 13px; color: var(--green); }
.health-item.is-warn svg { color: var(--orange); }
.health-status {
  margin-left: auto;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--green);
  font-weight: 600;
}

/* === Footer strip ========================================================= */
.foot {
  margin-top: 14px;
  display: flex; align-items: center; justify-content: space-between;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-faint);
  padding-top: 14px;
  border-top: 0;
  background-image: linear-gradient(90deg, transparent, var(--hairline-2) 18%, var(--hairline-2) 82%, transparent);
  background-size: 100% 1px;
  background-repeat: no-repeat;
  background-position: top left;
}
.foot a { color: var(--ink-mute); text-decoration: none; }
.foot a:hover { color: var(--ink); }

/* === Animations =========================================================== */
@keyframes pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.4; }
}

/* === Responsive =========================================================== */
@media (max-width: 1100px) {
  .metrics.is-five { grid-template-columns: repeat(3, 1fr); }
}

@media (max-width: 980px) {
  .metrics { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 880px) {
  .shell { grid-template-columns: 64px 1fr; }
  .side-brand-name,
  .side-foot-name { display: none; }
  .side-link { padding: 8px 10px; font-size: 13px; }
  .body-grid { grid-template-columns: 1fr; }
  .site-card { grid-template-columns: 1fr; }
  .site-actions { justify-content: flex-start; }
  .rec-hero { grid-template-columns: 1fr; }
  .rec-hero-actions { flex-direction: row; flex-wrap: wrap; }
  .connect-card { grid-template-columns: 1fr; }
  /* See base .dash rule for why 128px bottom — fixed mobile drawer
     footer would overlap the last cards otherwise. */
  .dash { padding: 22px 18px 128px; }
}

/* === Mobile shell breakpoint (720px) =====================================
   Below 900px the desktop sidebar becomes a left drawer (overlay shell
   markup + JS lives in dashboard-v2-page.html). The shell itself collapses
   to a single column and the drawer slides in over the content. Layout
   grids drop to 1fr so cards, lists and hero sections stack cleanly.
   Bumped 719.98 -> 899.98 (2026-05-02) to cover narrow desktop windows
   and tablet portrait, not just phones.
   ========================================================================= */
@media (max-width: 899.98px) {
  :root {
    --card-pad: var(--card-pad-tight);
  }

  /* Shell: single column. Drawer is positioned: fixed (set in the page's
     inline style block) so it sits outside the flow. */
  .shell {
    grid-template-columns: 1fr;
    min-height: auto;
  }
  /* Override the desktop `grid-column: 2` on the main content wrappers —
     at <900px shell collapses to one column, but the explicit
     `grid-column: 2` was creating an implicit second track that
     content-sized .dash and shoved everything against the right edge
     (visible at 768px tablet portrait). Pin to column 1 instead. */
  .shell > main.dash,
  .shell > .pg,
  .shell > .rich-page { grid-column: 1; }
  /* Restore side-brand text and foot text inside the drawer at this size
     (the 880px rule above hides them for the icon-rail mode, which we
     don't use on phones). */
  .side-brand-name,
  .side-foot-name { display: inline; }
  .side-link { padding: 9px 12px; font-size: 14px; }

  /* All multi-column layout grids stack on phones. */
  .body-grid { grid-template-columns: 1fr; }
  .site-card { grid-template-columns: 1fr; }
  .rec-hero { grid-template-columns: 1fr; }
  .connect-card { grid-template-columns: 1fr; }
  .metrics,
  .metrics.is-five { grid-template-columns: repeat(2, 1fr); }
  .quick { grid-template-columns: repeat(2, 1fr); }
  .upsell-stat { grid-template-columns: 1fr 1fr; }

  /* Hero/list trim. */
  .rec-hero-stats { flex-wrap: wrap; gap: 14px; }
  .dash-greeting { flex-direction: column; align-items: flex-start; gap: 4px; }
  .foot { flex-direction: column; gap: 6px; align-items: flex-start; }

  /* Padding tightens; the per-page inline style block also handles .dash. */
  .dash { padding: 16px 16px 32px; }
  .pg, .rich-page { padding: 16px 16px 32px; }

  /* Hide the desktop floating opener at this size — the topbar burger
     is the canonical entry point on mobile. */
  .side-opener { display: none !important; }
}

/* ==========================================================================
   Mobile shell — top bar + side drawer (visible only at <900px).
   Moved from dashboard-v2-page.html 2026-05-02 so dashboard-v2.html (the
   shell that serves /dashboard-v2 root) also gets the hamburger.
   ========================================================================== */
.mob-topbar { display: none; }
.mob-drawer-backdrop { display: none; }
.mob-drawer-close { display: none; }

@media (max-width: 899.98px) {
  /* Stable mobile frame.
     - position:fixed (not sticky) so the topbar never detaches when the
       body or any ancestor changes its overflow / scroll container.
     - .shell pushed down by exactly the topbar height so content doesn't
       slip under it.
     - body uses 100dvh so the iOS address-bar shrink doesn't shake the
       layout, plus overscroll-behavior:none kills rubber-band that makes
       the topbar visually "jump" on iOS.
     - Body scroll lock when the drawer is open uses position:fixed
       (the standard iOS pattern) instead of overflow:hidden, which on
       iOS Safari does NOT actually lock body scroll. */
  html, body {
    min-height: 100vh;
    min-height: 100dvh;
    overscroll-behavior: none;
  }
  body { position: relative; }
  /* ──────────────────────────────────────────────────────────────────
     SHARED .mob-topbar — used on BOTH home (dashboard-v2.html) AND
     AI builder (dashboard-v2-page.html). Convention to prevent bleed:
       - Base rule (here) = visual contract shared across both pages.
       - Home-only tweaks → body.is-empty-dash .mob-topbar { ... }
       - Builder-only tweaks → body.is-builder-chat .mob-topbar { ... }
     Never set bg/colour here for a builder-only reason — it bleeds
     to the home topbar. (Has happened: cream vs topband, see commit
     09a26ba for the revert.)
     ────────────────────────────────────────────────────────────── */
  .mob-topbar {
    display: flex;
    align-items: center;
    gap: 10px;
    height: 52px;
    padding: 0 12px;
    /* Topbar bg uses --topband to match the sidebar exactly. Default
       across every page (home, leads, recovery, settings, etc.). The
       AI builder page overrides this to transparent + the glass-blur
       stack — scoped to body.is-builder-chat below so this default
       is preserved on every other surface that shares the stylesheet. */
    background: var(--topband);
    position: fixed;
    top: 0; left: 0; right: 0;
    z-index: 70;
    will-change: transform;
  }
  /* 2026-05-24 — Progressive glass blur (E2 from /_demo-glass) is
     builder-only. body.is-builder-chat lands on the AI builder page;
     other pages keep the solid --topband background above. The three
     .mob-topbar-glass children only exist in dashboard-v2-page.html
     so they wouldn't render elsewhere even if the rules matched, but
     scoping the transparent + overflow:visible bits to .is-builder-
     chat keeps the home/leads/recovery topbars untouched. */
  body.is-builder-chat .mob-topbar {
    background: transparent;
    overflow: visible;
  }
  /* 2026-05-24 — Progressive glass blur, Lovable-style: NO tint
     background on any layer, only backdrop-filter. When nothing
     scrolls under the topbar the layers are completely invisible
     (the cream background shows through unchanged) — when content
     does scroll under, the blur kicks in. The previous version had
     white-tint gradients (0.12 / 0.18 / 0.24 alpha) which painted a
     visible band even over uniform bg, making the topbar look like
     a separate panel. */
  body.is-builder-chat .mob-topbar-glass {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    z-index: -1;
    pointer-events: none;
  }
  /* 2026-05-24 — gentler ramp. Previous values jumped 4 → 14 → 32 px
     blur with masks that stayed solid for the first 55% of each
     layer's height — so the top 55% of the cumulative effect was
     "all layers at full strength", which read as harsh/abrupt. Now:
     blur values are closer (3 → 8 → 16) AND every mask fades from
     y=0, no flat region. The heaviest blur is only fully visible at
     the very top edge and tapers immediately. Cumulative effect at
     the top is softer + the gradient down to crisp content is
     smoother. */
  /* 2026-05-24 — pure blur, no saturate(). Founder report: topbar
     still has a different colour from the background even with no
     content behind it. saturate() was amplifying the cream's faint
     warm tone, leaving a slightly warmer band where each filter
     was active. blur() of a uniform colour returns that same
     colour exactly — invisible over the background — so dropping
     saturate is the only way to make the topbar genuinely
     disappear when nothing's scrolling behind it. */
  /* 2026-05-24 — heavier blur up top + flat hold region before each
     layer fades. Founder: 'more intense blur earlier on, then hold
     it for a bit and let it disappear.' Each mask is solid for the
     first 60% of its height (the 'hold') then tapers to transparent
     by 100% (the 'disappear'). Cumulative result: heaviest glass at
     the top, a steady plateau through the chrome zone, then a
     clean fade-out by ~50px. */
  body.is-builder-chat .mob-topbar-glass[data-layer="1"] {
    height: 70px;
    -webkit-backdrop-filter: blur(1px);
            backdrop-filter: blur(1px);
    -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 60%, transparent 100%);
            mask-image: linear-gradient(180deg, #000 0%, #000 60%, transparent 100%);
  }
  body.is-builder-chat .mob-topbar-glass[data-layer="2"] {
    height: 70px;
    -webkit-backdrop-filter: blur(2px);
            backdrop-filter: blur(2px);
    -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 60%, transparent 100%);
            mask-image: linear-gradient(180deg, #000 0%, #000 60%, transparent 100%);
  }
  body.is-builder-chat .mob-topbar-glass[data-layer="3"] {
    height: 70px;
    -webkit-backdrop-filter: blur(3px);
            backdrop-filter: blur(3px);
    -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 60%, transparent 100%);
            mask-image: linear-gradient(180deg, #000 0%, #000 60%, transparent 100%);
  }
  /* Push everything below the fixed topbar so content doesn't slide under. */
  .shell { padding-top: 52px; }
  /* Lovable-style flat disc — matches .side-opener on desktop.
     Soft ambient shadow + faint ring, no convex gradient. */
  .mob-topbar-burger {
    flex: 0 0 auto;
    width: 48px; height: 48px;
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid rgba(31, 22, 17, 0.13);
    border-bottom-color: rgba(31, 22, 17, 0.19);
    border-radius: 50%;
    padding: 0;
    color: var(--ink);
    cursor: pointer;
    background: linear-gradient(180deg, #FFFFFF 0%, #F8F8F8 100%);
    box-shadow:
      0 1px 3px rgba(31, 22, 17, 0.07),
      0 2px 1px -1px rgba(31, 22, 17, 0.04),
      inset 0 1px 0 rgba(255, 255, 255, 0.9);
    transition: all 0.15s ease;
    -webkit-tap-highlight-color: transparent;
    touch-action: manipulation;
    user-select: none;
    -webkit-user-select: none;
    position: relative;
    z-index: 71;
  }
  .mob-topbar-burger:hover {
    border-color: rgba(31, 22, 17, 0.2);
    border-bottom-color: rgba(31, 22, 17, 0.26);
    box-shadow:
      0 2px 5px rgba(31, 22, 17, 0.09),
      0 2px 1px -1px rgba(31, 22, 17, 0.04),
      inset 0 1px 0 rgba(255, 255, 255, 0.9);
  }
  .mob-topbar-burger:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
  .mob-topbar-burger:active {
    transform: translateY(0.5px);
    box-shadow:
      0 0 2px rgba(31, 22, 17, 0.05),
      inset 0 1px 0 rgba(255, 255, 255, 0.7);
  }

  /* Builder-chat-mode topbar swap. Default brand lockup (diamond + wordmark
     + right spacer) hides; the draft pill (centre) + preview pill (right)
     show. Both use the same flat-disc shadow language as the burger. */
  .builder-project-pill,
  .builder-preview-pill { display: none; }
  body.is-builder-chat [data-mob-default-brand] { display: none !important; }
  /* 2026-05-22 V6 burger styling is now in the base .mob-topbar-burger
     rule at L1481, applied globally across all pages with a sidebar
     button. No builder-specific override needed here. */
  body.is-builder-chat .builder-project-pill {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    height: 44px;
    max-width: min(60vw, 280px);
    padding: 0 18px;
    border: 1px solid rgba(31, 22, 17, 0.13);
    border-bottom-color: rgba(31, 22, 17, 0.19);
    border-radius: 999px;
    background: linear-gradient(180deg, #FFFFFF 0%, #F8F8F8 100%);
    color: var(--ink);
    font-family: inherit;
    font-size: 14px;
    font-weight: 500;
    letter-spacing: -0.01em;
    cursor: pointer;
    box-shadow:
      0 1px 3px rgba(31, 22, 17, 0.07),
      0 2px 1px -1px rgba(31, 22, 17, 0.04),
      inset 0 1px 0 rgba(255, 255, 255, 0.9);
    -webkit-tap-highlight-color: transparent;
    touch-action: manipulation;
    user-select: none;
    -webkit-user-select: none;
    transition: all 0.15s ease;
  }
  .builder-project-pill__name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
  }
  .builder-project-pill__chev {
    flex: 0 0 auto;
    color: var(--ink-mute);
  }
  body.is-builder-chat .builder-project-pill:active {
    transform: translateY(0);
    box-shadow:
      0 0 0 1px rgba(0, 0, 0, 0.06),
      0 1px 2px rgba(0, 0, 0, 0.04);
  }
  /* ── Project pill bottom sheet ──────────────────────────────────── */
  .builder-project-sheet {
    position: fixed;
    inset: 0;
    z-index: 200;
  }
  .builder-project-sheet__backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.25);
    opacity: 0;
    transition: opacity 280ms ease;
  }
  .builder-project-sheet.is-open .builder-project-sheet__backdrop {
    opacity: 1;
  }
  .builder-project-sheet__panel {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    max-width: 460px;
    margin: 0 auto;
    background: #FFFFFF;
    border-top-left-radius: 20px;
    border-top-right-radius: 20px;
    box-shadow:
      0 -1px 0 rgba(0, 0, 0, 0.04),
      0 -16px 48px -12px rgba(20, 14, 8, 0.22);
    padding-bottom: max(20px, env(safe-area-inset-bottom, 20px));
    transform: translateY(100%);
    transition: transform 320ms cubic-bezier(0.16, 1, 0.3, 1);
  }
  .builder-project-sheet.is-open .builder-project-sheet__panel {
    transform: translateY(0);
  }
  .builder-project-sheet__handle {
    width: 36px;
    height: 4px;
    border-radius: 999px;
    background: rgba(31, 22, 17, 0.14);
    margin: 10px auto 0;
  }
  .builder-project-sheet__credits {
    padding: 20px 22px 16px;
  }
  .builder-project-sheet__credits-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: 10px;
  }
  .builder-project-sheet__credits-label {
    font-size: 15px;
    font-weight: 600;
    color: var(--ink, #1F1611);
  }
  .builder-project-sheet__credits-value {
    font-size: 15px;
    font-weight: 500;
    color: var(--ink, #1F1611);
    display: flex;
    align-items: center;
    gap: 4px;
  }
  .builder-project-sheet__credits-value svg {
    opacity: 0.4;
  }
  .builder-project-sheet__bar {
    height: 6px;
    border-radius: 999px;
    background: rgba(31, 22, 17, 0.06);
    overflow: hidden;
    margin-bottom: 8px;
  }
  .builder-project-sheet__bar-fill {
    height: 100%;
    border-radius: 999px;
    transition: width 600ms cubic-bezier(0.16, 1, 0.3, 1);
  }
  .builder-project-sheet__bar-fill.is-healthy {
    background: linear-gradient(90deg, #34A853 0%, #4CAF50 100%);
  }
  .builder-project-sheet__bar-fill.is-warning {
    background: linear-gradient(90deg, #F9A825 0%, #FBC02D 100%);
  }
  .builder-project-sheet__bar-fill.is-critical {
    background: linear-gradient(90deg, #E53935 0%, #EF5350 100%);
  }
  .builder-project-sheet__credits-help {
    font-size: 12px;
    color: var(--ink-mute, #8A7B6D);
    display: flex;
    align-items: center;
    gap: 5px;
  }
  .builder-project-sheet__credits-help::before {
    content: '';
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--ink-mute, #8A7B6D);
    flex-shrink: 0;
  }
  .builder-project-sheet__divider {
    height: 1px;
    background: rgba(31, 22, 17, 0.06);
    margin: 0 22px;
  }
  .builder-project-sheet__nav {
    padding: 8px 10px;
  }
  .builder-project-sheet__item {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px 12px;
    border-radius: 10px;
    font-size: 15px;
    font-weight: 450;
    color: var(--ink, #1F1611);
    text-decoration: none;
    border: 0;
    background: transparent;
    width: 100%;
    cursor: pointer;
    transition: background 0.12s;
    -webkit-tap-highlight-color: transparent;
    font-family: inherit;
  }
  .builder-project-sheet__item:active {
    background: rgba(31, 22, 17, 0.04);
  }
  .builder-project-sheet__item svg {
    color: var(--ink-mute, #6B5A4C);
    flex-shrink: 0;
  }

  body.is-builder-chat .builder-preview-pill {
    display: inline-flex;
    flex: 0 0 auto;
    width: 48px; height: 48px;
    align-items: center;
    justify-content: center;
    border: 1px solid rgba(31, 22, 17, 0.13);
    border-bottom-color: rgba(31, 22, 17, 0.19);
    border-radius: 50%;
    padding: 0;
    color: var(--ink);
    cursor: pointer;
    background: linear-gradient(180deg, #FFFFFF 0%, #F8F8F8 100%);
    box-shadow:
      0 1px 3px rgba(31, 22, 17, 0.07),
      0 2px 1px -1px rgba(31, 22, 17, 0.04),
      inset 0 1px 0 rgba(255, 255, 255, 0.9);
    -webkit-tap-highlight-color: transparent;
    touch-action: manipulation;
    user-select: none;
    -webkit-user-select: none;
    position: relative;
    z-index: 71;
    transition: all 0.15s ease;
  }
  body.is-builder-chat .builder-preview-pill:active {
    transform: translateY(0.5px);
    box-shadow:
      0 0 2px rgba(31, 22, 17, 0.05),
      inset 0 1px 0 rgba(255, 255, 255, 0.7);
  }
  body.is-builder-chat .builder-preview-pill:focus-visible {
    outline: 2px solid var(--orange);
    outline-offset: 2px;
  }
  /* Centre-align the title block now that the spacer is gone: rely on
     the flex:1 mob-topbar-title taking the slack between burger (44) +
     preview pill (44 + 10gap). */
  body.is-builder-chat .mob-topbar-title { justify-content: center; }

  .mob-topbar-title {
    flex: 1 1 auto;
    min-width: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    color: var(--ink);
  }
  /* Diamond brand mark inside the topbar, paired with the BookingSprint
     wordmark to read as a single brand lockup — matches Lovable's
     "heart + Lovable" header treatment. Sized for a 52px-tall bar
     with comfortable optical padding. */
  .mob-topbar-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    line-height: 0;
    flex-shrink: 0;
  }
  .mob-topbar-mark svg { width: 100%; height: 100%; display: block; }
  .mob-topbar-mark .bs-logo-mark__d { fill: var(--ink); }
  .mob-topbar-name {
    font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--ink);
    letter-spacing: -0.01em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  /* Right-side spacer keeps the brand lockup optically centred.
     Width matches the burger button's 48px footprint on the left. */
  .mob-topbar-spacer {
    flex: 0 0 auto;
    width: 48px; height: 48px;
    display: inline-block;
  }

  /* === Card-slide drawer (Lovable-style) =========================
     The page-card slides right to REVEAL the sidebar that's always
     parked at left:0. Sidebar no longer slides — it sits there
     permanently at a lower z-index, hidden by the card until the
     card moves out of the way. Rounded LEFT corners + soft shadow
     on the card give it the "lifted floating card" feel.

     The transform is driven by --drawer-offset (set on body) so the
     topbar, the wave-bands canvas, and the main content all glide
     together as one card. */
  body { --drawer-offset: 0px; }
  body.mob-drawer-open { --drawer-offset: min(360px, 84vw); }

  .side {
    position: fixed !important;
    top: 0; left: 0;
    height: 100vh;
    /* Slightly narrower drawer per user request — leaves a touch more
       peek of the card on the right when the drawer is open. */
    width: min(360px, 84vw);
    /* Stays put — no transform. The card slides over it (closed) or
       off it (open) to reveal / hide. */
    transform: none;
    /* Below the card so the card visually covers it when closed. */
    z-index: 1;
    border-right: 1px solid var(--hairline-2);
    display: flex !important;
    background: var(--topband);
  }

  /* Card surface — every element that should slide as part of the page
     "card" gets the same translate via --drawer-offset. The mob-topbar
     is fixed at top, the wave-bands canvas is fixed inset:0, and the
     shell's main-content children are in flow. All three transform
     in lockstep so the card moves as one unit. */
  .mob-topbar,
  .dash-hero-bg,
  .shell > main.dash,
  .shell > .pg,
  .shell > .rich-page {
    transform: translateX(var(--drawer-offset));
    transition: transform 0.32s cubic-bezier(0.2, 0, 0, 1),
                border-radius 0.32s cubic-bezier(0.2, 0, 0, 1),
                box-shadow 0.32s cubic-bezier(0.2, 0, 0, 1);
  }
  /* Sit the card content above the sidebar when closed (covers it) and
     stay above when open (the sidebar shows through where the card has
     slid away). */
  .mob-topbar { z-index: 70; }
  .shell > main.dash,
  .shell > .pg,
  .shell > .rich-page {
    position: relative;
    z-index: 10;
    /* Opaque card surface — on empty-hero pages the wave-bands sit at
       z-index 2 (covering the sidebar in the closed state) and main.dash
       is transparent so the waves show through. On non-empty / sub-route
       pages no waves are rendered, so main.dash + .pg / .rich-page paint
       linen themselves to cover the sidebar. */
    background: var(--topband);
    /* Extend to fill the viewport even when the page content is
       shorter than the screen. Without this, sub-routes with little
       content (e.g. Drafts on a fresh account) end mid-screen and
       the sidebar's foot (Settings / Need a hand / business name)
       shows through the gap below. 100dvh — 52px topbar so we don't
       overshoot past the visible viewport on iOS Safari. */
    min-height: calc(100dvh - 52px);
  }
  /* Empty-hero exception: main.dash must stay transparent so the waves
     (sitting at z-index 2 underneath) remain visible through the page. */
  body.is-empty-dash .shell > main.dash { background: transparent; }

  /* Rounded LEFT corners + clean outline edge when the card is slid out.
     Two-layer box-shadow on the card elements:
       1. -1px 0 0 0 — a hairline that hugs the rounded curve, giving
          a crisp outlined edge so the corner reads as a defined card.
       2. -3px 0 14px -10px — a very soft drop for "lift", much
          lighter than before so it no longer paints a grey smudge
          into the sidebar area at the corner.
     Topbar also gets padding-left:22px so the burger clears the
     rounded mask. Radius 22px across all card surfaces. */
  body.mob-drawer-open .mob-topbar {
    border-top-left-radius: 22px;
    overflow: hidden;
    padding-left: 22px;
    box-shadow:
      -1px 0 0 0 rgba(31, 22, 17, 0.08),
      -6px 0 22px -8px rgba(31, 22, 17, 0.18);
  }
  body.mob-drawer-open .dash-hero-bg {
    border-top-left-radius: 22px;
    border-bottom-left-radius: 22px;
    overflow: hidden;
  }
  body.mob-drawer-open .shell > main.dash,
  body.mob-drawer-open .shell > .pg,
  body.mob-drawer-open .shell > .rich-page {
    border-bottom-left-radius: 22px;
    box-shadow:
      -1px 0 0 0 rgba(31, 22, 17, 0.08),
      -6px 0 22px -8px rgba(31, 22, 17, 0.18);
  }

  /* The sidebar's hairline border-right was adding a second grey edge
     right next to the card's outline — visible as a thin line stacking
     against the card's hairline at the seam. With the card now carrying
     its own clean edge, the sidebar's border-right is redundant on
     mobile and reads as visual noise. !important needed because the
     [data-theme="light"] .side rule outside this media query wins
     specificity over a plain .side. */
  .side { border-right: 0 !important; }

  /* Backdrop: transparent. Tap-to-close click handler still wired; we
     just don't darken anything because the page card itself is now
     the visible surface to tap.

     CRITICAL: the backdrop must NOT cover the sidebar area. It used
     to be inset:0, which at z-index 80 sat above the sidebar (z:1)
     and the topbar (z:70) — so a tap on a sidebar nav item went to
     the backdrop's click handler (closeDrawer) instead of the link's
     native navigation. Same for touchstart. Net effect: tapping
     sidebar items closed the drawer without navigating, and you
     couldn't scroll the sidebar with touch either. Position the
     backdrop to start where the sidebar ends so the sidebar stays
     fully tappable. */
  .mob-drawer-backdrop {
    display: block;
    position: fixed;
    top: 0;
    bottom: 0;
    left: var(--drawer-offset);
    right: 0;
    background: transparent;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.22s cubic-bezier(0.2, 0, 0, 1),
                left 0.32s cubic-bezier(0.2, 0, 0, 1);
    z-index: 80;
  }
  body.mob-drawer-open .mob-drawer-backdrop {
    opacity: 1;
    pointer-events: auto;
  }

  .mob-drawer-close {
    display: inline-flex;
    align-items: center; justify-content: center;
    position: absolute;
    top: 12px; right: 12px;
    width: 28px; height: 28px;
    background: transparent;
    border: 0;
    border-radius: 6px;
    color: var(--ink-mute);
    cursor: pointer;
    z-index: 1;
    -webkit-tap-highlight-color: rgba(0, 0, 0, 0.1);
    touch-action: manipulation;
    user-select: none;
    -webkit-user-select: none;
  }
  .mob-drawer-close:hover { background: var(--cream-3); color: var(--ink); }
  .mob-drawer-close:active { background: var(--cream-3); color: var(--ink); }
  .mob-drawer-backdrop { touch-action: manipulation; }

  /* iOS-safe body-scroll lock when the drawer is open. overflow:hidden on
     body is silently ignored by iOS Safari; position:fixed actually works.
     We don't bother saving/restoring scroll position since the drawer
     close is fast enough that the user doesn't notice. */
  body.mob-drawer-open {
    overflow: hidden;
    position: fixed;
    width: 100%;
    top: 0; left: 0; right: 0;
  }

  /* NUCLEAR overflow shield. The page kept rendering wider than the
     viewport on iOS Safari ("page slides sideways"). We don't trust any
     individual rule — apply width + max-width + overflow-x at every
     possible level so nothing can escape the viewport.
     - html: hard width-cap
     - body: hard width-cap, position relative for fixed children
     - * direct children of body: max-width: 100vw to catch SPA roots
     - touch-action pan-y prevents horizontal touch-pan from showing
       beyond the viewport edge during scroll-snap glitches */
  html {
    width: 100%;
    max-width: 100vw;
    overflow-x: hidden;
  }
  body {
    width: 100%;
    max-width: 100vw;
    overflow-x: hidden;
    position: relative;
    touch-action: pan-y pinch-zoom;
  }
  /* Every direct child of body must respect viewport width. The 100vw cap
     covers fixed-positioned children (mob-topbar, drawer, backdrop) which
     bypass body's width contraint. */
  body > * {
    max-width: 100vw;
    box-sizing: border-box;
  }
  /* Stop long unbroken strings (URLs, code, phone numbers) from forcing
     viewport-wide overflow. */
  .set-content, .set-card, .set-block, .set-row,
  .dash, .pg, .rich-page, [data-rich-mount] {
    word-break: break-word;
    overflow-wrap: anywhere;
  }
  /* The .usage-row min-width:220px was a desktop assumption that overflows
     when stacked single-column on mobile. */
  .usage-row { min-width: 0 !important; }

  /* Tab strip MUST scroll horizontally even though body overflow-x is
     hidden. Force its own scroll container and ensure no fade mask is
     ever applied. */
  .set-tabs {
    overflow-x: auto !important;
    overflow-y: hidden !important;
    -webkit-overflow-scrolling: touch !important;
    width: 100%;
    max-width: 100%;
  }
  .set-tab { flex: 0 0 auto !important; }

  /* Chevron buttons cause horizontal overflow on mobile (positioned at
     left:-4px / right:-4px outside their wrapper). Touch users swipe the
     strip directly; they don't need the chevrons. Hide them. */
  .set-tabs-chev { display: none !important; }
  .set-tabs-wrap { padding: 0 !important; }

  /* Brute-force defensive overflow shield. Anything inside the page that
     tries to be wider than the viewport gets clipped. The single biggest
     cause of the "page slides sideways" symptom on mobile. */
  .shell, .dash, .pg, .rich-page, .set-page, .set-content, .set-card, .set-block,
  main, [data-rich-mount], [data-main-root] {
    max-width: 100vw !important;
    box-sizing: border-box;
    overflow-x: hidden;
  }
  /* Allow only the tab strips themselves to scroll horizontally. */
  .set-tabs { overflow-x: auto !important; }

  /* Group divider inside the single flat strip (small vertical hairline
     between Account / Bookings / Site & SEO etc.). */
  .set-tab-sep {
    display: inline-block;
    width: 1px;
    height: 18px;
    background: var(--hairline-2);
    margin: 0 8px;
    flex: 0 0 1px;
    align-self: center;
  }

  /* Sticky save bar at bottom of every settings tab — stack on mobile so
     the buttons don't overflow off-screen. Horizontal margin so the bar
     sits inside the page padding rather than spilling under the edge. */
  .set-actions {
    flex-direction: column !important;
    align-items: stretch !important;
    justify-content: flex-start !important;
    gap: 10px !important;
    margin: 4px 12px 0 !important;
    padding: 12px 14px !important;
  }
  .set-actions > div:last-child {
    display: flex !important;
    gap: 8px !important;
    width: 100%;
  }
  .set-actions > div:last-child > .btn {
    flex: 1 1 0 !important;
    min-width: 0 !important;
    text-align: center;
  }

  /* Defensive width caps so no card-internal element pushes the page wide. */
  .set-content, .set-card, .set-block { max-width: 100%; min-width: 0; }
}

/* === Mobile bottom-tab nav ============================================== */
.mobile-nav {
  display: none;
  position: fixed;
  left: 0; right: 0; bottom: 0;
  background: var(--glass-bg);
  backdrop-filter: saturate(140%) blur(14px);
  -webkit-backdrop-filter: saturate(140%) blur(14px);
  border-top: 1px solid var(--hairline-2);
  padding: 6px 4px calc(env(safe-area-inset-bottom, 0px) + 6px);
  z-index: 50;
  grid-template-columns: repeat(5, 1fr);
  gap: 2px;
  align-items: end;
}
/* Bottom-tab nav superseded by the hamburger drawer at <720px (see the
   inline shell rules in dashboard-v2-page.html). Keep the styles around
   in case we want to bring it back, but never show it. */
.mobile-nav { display: none !important; }

.mob-tab {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px;
  padding: 6px 4px 4px;
  text-decoration: none;
  color: var(--ink-mute);
  font-size: 0.62rem; font-weight: 500;
  letter-spacing: 0.01em;
  border-radius: 8px;
  position: relative;
  min-height: 52px;
  transition: color 0.12s, background 0.12s;
}
.mob-tab svg { color: currentColor; transition: color 0.12s; }
.mob-tab:hover, .mob-tab:focus-visible { color: var(--ink); background: var(--cream-2); }
.mob-tab.is-active { color: var(--ink); }
.mob-tab.is-active svg { color: var(--ink); }

.mob-tab-badge {
  position: absolute;
  top: 2px; right: 18%;
  background: var(--ink);
  color: var(--cream);
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.56rem; font-weight: 600;
  letter-spacing: 0.04em;
  padding: 1px 5px;
  border-radius: 999px;
  min-width: 16px; text-align: center;
  line-height: 1.3;
}

/* Center "+ New" CTA tab */
.mob-tab.is-cta { color: var(--ink); }
.mob-tab-cta {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px;
  background: var(--ink);
  color: #fff;
  border-radius: 12px;
  margin-bottom: 1px;
  box-shadow: 0 2px 6px rgba(31, 22, 17, 0.18);
  transition: background 0.12s, transform 0.12s;
}
.mob-tab.is-cta:hover .mob-tab-cta { background: var(--ink-2); transform: translateY(-1px); }

/* === Anchor-as-button text-decoration cleanup ============================= */
a.upsell-cta, a.upsell-path, a.quick-card { text-decoration: none; }
a.upsell-cta { color: #fff; }
a.upsell-cta:visited { color: #fff; }
a.upsell-path { color: inherit; }
a.upsell-path:visited { color: inherit; }
a.quick-card { color: var(--ink); }
a.quick-card:visited { color: var(--ink); }

/* === Empty-state placeholder pages ======================================== */
.empty-card {
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--card-radius);
  padding: var(--card-pad);
  display: flex;
  flex-direction: column;
  gap: 14px;
  transition: border-color 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.empty-card:hover { border-color: var(--hairline-2); }
.empty-card-eye {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-mute);
  font-weight: 600;
}
.empty-card-title {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 1.4rem;
  font-weight: 600;
  margin: 0;
  letter-spacing: -0.012em;
  color: var(--ink);
}
.empty-card-intro {
  font-size: 0.95rem;
  color: var(--ink-mute);
  margin: 0;
  max-width: 60ch;
  line-height: 1.55;
}
.empty-card-body {
  background: var(--cream-2);
  border: 1px dashed var(--hairline-2);
  border-radius: 10px;
  padding: 20px 22px;
  font-size: 0.9rem;
  color: var(--ink-mute);
  line-height: 1.55;
  max-width: 60ch;
}
.empty-card-body strong { color: var(--ink); font-weight: 600; }
.empty-card-back {
  margin-top: 6px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.empty-card-back:hover { color: var(--ink); }

/* Per-user request: drop the 'Back to the dash' link + the plan/billing
   footer strip from every sub-page. Sidebar already provides nav back to
   home, and the plan info lives in Settings → Billing. Hidden site-wide. */
.empty-card-back { display: none !important; }
.foot { display: none !important; }

/* Settings curation for Builder tier (8-section structure).
   - Hide blocks dropped from the rail (Owner & contact, Booking page, Site
     builder, Bookings flow). Markup stays in DOM for the inputs but hidden
     visually so users only see the curated rail items.
   - Inside set-data (renamed 'Danger zone' in the rail), hide the first
     .set-section (Data & privacy) so only the Danger zone section shows.
   Recovery tier gets all blocks back automatically since these rules are
   tier-scoped.
   Note: a prior rule also hid the first two sub-sections of #set-area
   (Service area + Working hours) for Builder tier, on the theory that
   Builder users would configure hours from a "Forms inline availability"
   surface instead. The Booking-rules redesign (2026-05-11) put both
   back as core sub-sections because every tradie running the booking
   widget needs to configure hours + radius, and that rule was dropped. */
body[data-tier="builder"] #set-owner,
body[data-tier="builder"] #set-bookpage,
body[data-tier="builder"] #set-site { display: none !important; }
/* #set-booking was hidden here while it was scaffolding (2026-05-02);
   removed 2026-05-13 when the panel was wired end-to-end (Step 6 of
   Phase 2) — approval mode, time-slot mode, route-opt, pause, default
   reminder all apply to Builder tier too. */
body[data-tier="builder"] #set-data > .set-section:nth-of-type(1) { display: none !important; }

.placeholder-list {
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--card-radius);
  overflow: hidden;
  transition: border-color 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.placeholder-list:hover { border-color: var(--hairline-2); }
.placeholder-row {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid var(--hairline);
  font-size: 0.92rem;
}
.placeholder-row:last-child { border-bottom: 0; }
.placeholder-row-name {
  font-weight: 500;
  color: var(--ink);
}
.placeholder-row-status {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  color: var(--ink-mute);
  background: var(--cream-2);
  padding: 4px 10px;
  border-radius: 6px;
}
.placeholder-row-status.is-on { color: var(--green); background: var(--green-soft); }
.placeholder-row-status.is-soon {
  color: var(--ink-faint);
  background: transparent;
  border: 1px dashed var(--hairline-2);
}

.placeholder-sample-head {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-top: 6px;
}

/* === Toast (share/copy feedback) ========================================== */
.dash-toast {
  position: fixed;
  bottom: 28px; left: 50%;
  transform: translate(-50%, 12px);
  background: var(--ink);
  color: #fff;
  font-family: inherit;
  font-size: 0.84rem;
  font-weight: 500;
  padding: 10px 18px;
  border-radius: 999px;
  box-shadow: 0 10px 30px rgba(31, 22, 17, 0.22);
  opacity: 0;
  transition: opacity 0.18s, transform 0.18s;
  z-index: 9999;
  pointer-events: none;
}
.dash-toast.is-in { opacity: 1; transform: translate(-50%, 0); }

/* === Attention strip ====================================================== */
.attention-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 10px;
}
.attention-card {
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-left: 2px solid var(--ink-mute);
  border-radius: var(--card-radius);
  padding: var(--card-pad-tight);
  display: flex;
  align-items: center;
  gap: 10px;
  transition: border-color 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.attention-card:hover { border-color: var(--hairline-2); border-left-color: var(--ink-mute); }
.attention-card-text { flex: 1; min-width: 0; }
.attention-card-title {
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.3;
}
.attention-card-sub {
  margin-top: 2px;
  font-size: 0.74rem;
  color: var(--ink-mute);
  line-height: 1.3;
}
.attention-card-btn {
  flex-shrink: 0;
  padding: 6px 10px;
  font-size: 0.74rem;
  background: var(--paper);
}

/* === Setup checklist ====================================================== */
.setup-checklist {
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--card-radius);
  overflow: hidden;
  transition: border-color 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.setup-checklist:hover { border-color: var(--hairline-2); }
.setup-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 16px 20px;
  cursor: pointer;
  list-style: none;
}
.setup-summary::-webkit-details-marker { display: none; }
.setup-summary-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.setup-eye {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 13px;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink-mute);
  font-weight: 500;
}
.setup-title {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 1.05rem;
  font-weight: 600;
  margin: 0;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.setup-summary-meta {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-shrink: 0;
}
.setup-progress {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.62rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-mute);
  background: var(--cream-2);
  padding: 5px 10px;
  border-radius: 999px;
}
.setup-dismiss {
  background: transparent;
  border: 0;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  cursor: pointer;
  padding: 4px 6px;
}
.setup-dismiss:hover { color: var(--ink); }
.setup-chev {
  display: inline-flex;
  width: 14px;
  height: 14px;
  color: var(--ink-mute);
  transition: transform 0.18s;
}
.setup-chev svg { width: 100%; height: 100%; }
.setup-checklist[open] .setup-chev { transform: rotate(180deg); }
.setup-steps {
  list-style: none;
  margin: 0;
  padding: 0 0 8px;
  border-top: 1px solid var(--hairline);
}
.setup-step {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 14px 20px;
  border-bottom: 1px solid var(--hairline);
}
.setup-step:last-child { border-bottom: 0; }
.setup-step-num {
  flex-shrink: 0;
  width: 26px; height: 26px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--cream-2);
  color: var(--ink-mute);
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.74rem;
  font-weight: 600;
  margin-top: 1px;
}
.setup-step-num svg { width: 13px; height: 13px; }
.setup-step.is-done .setup-step-num {
  background: var(--green-soft);
  color: var(--green);
}
.setup-step.is-optional .setup-step-num {
  background: transparent;
  border: 1px dashed var(--hairline-2);
  color: var(--ink-faint);
}
.setup-step-text { flex: 1; min-width: 0; }
.setup-step-title {
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.3;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.setup-step.is-done .setup-step-title { color: var(--ink-mute); text-decoration: line-through; text-decoration-color: var(--hairline-2); }
.setup-step-pill {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.54rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-faint);
  background: var(--cream-2);
  padding: 2px 7px;
  border-radius: 999px;
  font-weight: 600;
}
.setup-step-desc {
  margin-top: 2px;
  font-size: 0.78rem;
  color: var(--ink-mute);
  line-height: 1.4;
}
.setup-step-cta {
  flex-shrink: 0;
  align-self: center;
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 13px;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink);
  text-decoration: none;
  padding: 6px 10px;
  border: 1px solid var(--hairline-2);
  border-radius: 7px;
  font-weight: 500;
}
.setup-step-cta:hover { background: var(--cream-2); border-color: var(--hairline-3); }

/* === SMS meter card ======================================================= */
.sms-meter {
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--card-radius);
  padding: var(--card-pad);
  transition: border-color 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.sms-meter:hover { border-color: var(--hairline-2); }
.sms-meter-eye {
  display: block;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-mute);
  font-weight: 600;
  margin-bottom: 8px;
}
.sms-meter-num {
  font-family: 'JetBrains Mono', monospace;
  font-size: 1.6rem;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: -0.01em;
  line-height: 1;
}
.sms-meter-cap {
  font-size: 0.95rem;
  color: var(--ink-faint);
  font-weight: 500;
}
.sms-meter-bar {
  margin-top: 10px;
  height: 6px;
  background: var(--cream-3);
  border-radius: 999px;
  overflow: hidden;
}
.sms-meter-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--orange-soft), var(--orange));
  border-radius: 999px;
}
.sms-meter-foot {
  margin-top: 9px;
  font-size: 0.74rem;
  color: var(--ink-mute);
  line-height: 1.4;
}

/* === Trends chart card ==================================================== */
.trends-chart {
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--card-radius);
  padding: var(--card-pad);
  transition: border-color 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.trends-chart:hover { border-color: var(--hairline-2); }
.trends-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 10px;
}
.trends-head-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.trends-eye {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-mute);
  font-weight: 600;
}
.trends-title {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 1rem;
  font-weight: 600;
  margin: 0;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.trends-toggle {
  display: inline-flex;
  background: var(--cream-2);
  border-radius: 7px;
  padding: 3px;
  gap: 2px;
  flex-shrink: 0;
}
.trends-toggle-btn {
  background: transparent;
  border: 0;
  cursor: pointer;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.66rem;
  letter-spacing: 0.1em;
  font-weight: 600;
  color: var(--ink-mute);
  padding: 4px 9px;
  border-radius: 5px;
}
.trends-toggle-btn:hover { color: var(--ink); }
.trends-toggle-btn.is-active {
  background: var(--paper);
  color: var(--ink);
}
.trends-svg {
  width: 100%;
  height: 100px;
  display: block;
}
.trends-legend {
  margin-top: 8px;
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
}
.trends-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-mute);
}
.trends-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
}
.trends-axis {
  margin-top: 6px;
  display: flex;
  justify-content: space-between;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.58rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* === Responsive: collapse body-grid + greeting on small screens =========== */
@media (max-width: 880px) {
  .body-grid > .panel,
  .body-grid > .body-side,
  .body-grid > .upsell { grid-column: 1; grid-row: auto; }
  .dash-greeting-actions { width: 100%; }
}
@media (max-width: 700px) {
  .attention-strip { grid-template-columns: 1fr; }
  .dash-greeting { flex-direction: column; align-items: flex-start; }
}

/* === Phase 1 polish: hairline-only depth — overrides removed in Anthropic-grade
       restraint pass. Cards now use border + card-pad tokens directly. ======= */

/* === Focus rings on key inputs (theme-aware orange wash) ================= */
.set-input:focus,
.set-textarea:focus,
.set-select:focus {
  box-shadow: 0 0 0 3px var(--orange-wash);
}
.set-input:focus-visible,
.set-textarea:focus-visible,
.set-select:focus-visible {
  outline: 0;
  border-color: var(--ink-mute);
  box-shadow: 0 0 0 3px var(--orange-wash);
}

/* === Phase 1 polish: tabular numerals on number-bearing labels =========== */
.metric-value, .side-link-badge, .side-link-status, .side-plan-cost,
.dash-greeting .date, .pay-amt, .pay-cust-sub, .chip-count,
.rp-thread-time, .ntf-row-cell, .pay-pill, .set-pill,
.rec-hero-stat-value, .site-info-eye, .rec-hero-eye, .connect-eye,
.metric-sub, .pg-sub, .activity-time, .cal-time-cell, .cal-event-time,
.act-meta, .lead-meta, .panel-link, .health-status, .side-plan-meta,
.side-foot-meta, .empty-card-back, .setup-progress, .upsell-foot,
.upsell-stat-cell-value, .sms-meter-num, .sms-meter-cap,
.placeholder-row-status, .trends-axis, .pay-date, .pay-inv {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
}

/* === Pulse-dot animation utility (live status) =========================== */
@keyframes bs-pulse-dot {
  0%, 100% { transform: scale(1); opacity: 1; }
  50%      { transform: scale(1.4); opacity: 0.55; }
}
.pulse-dot {
  display: inline-block;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: currentColor;
  animation: bs-pulse-dot 2.4s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .pulse-dot { animation: none; }
}

/* Numbered section markers — removed in chrome-less restraint pass.
   Class kept only as a no-op so any leftover markup degrades silently. */
.section-marker { display: none; }

/* "What's new" changelog modal — removed in chrome-less restraint pass.
   Markup, trigger and JS handlers all stripped. Cmd+K stays the only surface. */
.bs-changelog-trigger,
.bs-changelog-backdrop,
.bs-changelog-modal,
.bs-changelog-head,
.bs-changelog-body,
.bs-changelog-foot,
.bs-changelog-close,
.bs-changelog-entry,
.bs-changelog-meta,
.bs-changelog-date,
.bs-changelog-tag { display: none !important; }

/* === Print styles ======================================================== */
@media print {
  body { background: #fff !important; }
  .side, .mobile-nav, .upsell, .attention-strip, .empty-card-back, .btn, .pg-actions, .rich-actions, [data-no-print] { display: none !important; }
  .shell { grid-template-columns: 1fr !important; }
  .dash, .pg, .rich-page { padding: 24px !important; max-width: 720px; margin: 0 auto; }
  .metric, .set-card, .pay-card, .activity-feed, .rec-hero, .site-card, .panel, .upsell {
    box-shadow: none !important;
    border: 1px solid #ddd !important;
    page-break-inside: avoid;
    background: #fff !important;
  }
  .pg-head, .rich-head, .dash-greeting { border-bottom: 1px solid #999; padding-bottom: 12px; margin-bottom: 16px; }
  .pg-head::after { content: 'BookingSprint · Printed ' attr(data-print-date); display: block; font-size: 0.7rem; color: #666; margin-top: 6px; font-family: 'JetBrains Mono', monospace; }
  a { color: #1F1611 !important; text-decoration: underline; }
  a[href]::after { content: ' (' attr(href) ')'; font-size: 0.7em; color: #666; }
  /* Tabular alignment for printed lists */
  .pay-row, .activity-feed { font-variant-numeric: tabular-nums; }
}

/* === Editorial typography moments ======================================== */
/* Newsreader serif italic 500 — used as deliberate punctuation, never body copy. */
.serif-moment {
  font-family: 'Newsreader', Georgia, serif;
  font-style: italic;
  font-weight: 500;
  font-feature-settings: "liga", "dlig", "kern";
  letter-spacing: -0.005em;
}

/* Pull-quote treatment for activity feed featured items */
.pull-quote {
  position: relative;
  padding: var(--space-3) var(--space-3) var(--space-3) var(--space-5);
  border-left: 2px solid var(--hairline-3);
  background: transparent;
  border-radius: 0 8px 8px 0;
  margin: var(--space-2) 0;
}
.pull-quote-text {
  font-family: 'Newsreader', Georgia, serif;
  font-style: italic;
  font-weight: 500;
  font-size: 1rem;
  color: var(--ink);
  line-height: 1.5;
}
.pull-quote-attribution {
  display: block;
  margin-top: var(--space-2);
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-style: normal;
  font-weight: 500;
}

/* Hero metric — applied to a single most-prominent metric per tier */
.metric-value.hero-metric,
.hero-metric .metric-value {
  font-size: clamp(2.2rem, 4vw, 3rem);
  font-weight: 500;
  letter-spacing: -0.032em;
  line-height: 1;
  font-feature-settings: "tnum" 1, "lnum" 1, "ss01", "ss02";
}

/* ============================================================================
   Motion choreography — premium feel additions.
   1. Stagger fade-up on first paint
   2. Tabular numerics for animated counters
   3. Cursor-following spotlight on hero/connect cards
   4. Subtle page-in fade
   5. Skeleton shimmer utility
   All respect prefers-reduced-motion.
   ========================================================================== */

/* 1. Stagger fade-up ------------------------------------------------------- */
@keyframes bs-fade-up {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
[data-stagger] {
  opacity: 0;
  /* Was 0.5s — shortened so tab reveal completes inside ~480ms (cap × delay
     + this duration). The eye doesn't register the difference between 300
     and 500ms but the page feels meaningfully snappier on slow phones. */
  animation: bs-fade-up 0.3s var(--ease-out, cubic-bezier(0.2, 0, 0, 1)) forwards;
  animation-delay: calc(var(--bs-stagger-i, 0) * 60ms);
}
@media (prefers-reduced-motion: reduce) {
  [data-stagger] { opacity: 1 !important; transform: none !important; animation: none !important; }
}

/* 2. Animated counter typography ------------------------------------------ */
.metric-value, .pay-amt, .rec-hero-stat-value {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
}

/* 3. Cursor-following spotlight ------------------------------------------- */
.site-card.spotlight, .rec-hero.spotlight, .connect-card.spotlight {
  position: relative;
  isolation: isolate;
}
.site-card.spotlight::after,
.rec-hero.spotlight::after,
.connect-card.spotlight::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(circle at var(--bs-spot-x, 50%) var(--bs-spot-y, 50%), oklch(64.5% 0.16 38 / 0.10), transparent 40%);
  border-radius: inherit;
  opacity: 0;
  transition: opacity 0.3s var(--ease-out, cubic-bezier(0.2, 0, 0, 1));
  z-index: 1;
}
.site-card.spotlight:hover::after,
.rec-hero.spotlight:hover::after,
.connect-card.spotlight:hover::after { opacity: 1; }
.site-card.spotlight > *,
.rec-hero.spotlight > *,
.connect-card.spotlight > * { position: relative; z-index: 2; }
@media (prefers-reduced-motion: reduce) {
  .site-card.spotlight::after,
  .rec-hero.spotlight::after,
  .connect-card.spotlight::after { display: none; }
}

/* 4. Page transition ------------------------------------------------------ */
@keyframes bs-page-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}
main, .dash, .pg, .rich-page {
  animation: bs-page-in 0.32s var(--ease-out, cubic-bezier(0.2, 0, 0, 1));
}
@media (prefers-reduced-motion: reduce) {
  main, .dash, .pg, .rich-page { animation: none; }
}

/* 5. Skeleton shimmer ----------------------------------------------------- */
@keyframes bs-shimmer {
  0%   { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}
.skeleton {
  background-color: var(--cream-2);
  background-image: linear-gradient(90deg, transparent, oklch(96% 0.012 80 / 0.55), transparent);
  background-size: 200% 100%;
  animation: bs-shimmer 1.6s linear infinite;
  border-radius: 6px;
  color: transparent !important;
  pointer-events: none;
}
.skeleton-line { height: 0.9em; display: inline-block; vertical-align: middle; min-width: 4ch; }
.skeleton-block { height: 100px; width: 100%; border-radius: 12px; }
@media (prefers-reduced-motion: reduce) {
  .skeleton { animation: none; background: var(--cream-2); }
}

/* ============================================================================
   Editorial home — restraint over density
   3 elements only: home-hero · home-mark-block · home-activity
   ========================================================================== */
.dash {
  padding-bottom: 80px;
}
.home-hero {
  padding: 6vh 0 24px;
  text-align: center;
  max-width: 720px;
  margin: 0 auto;
}
.home-eye {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 14px;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink-mute);
  font-weight: 500;
  display: inline-block;
  margin-bottom: 28px;
}
.home-greeting {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: clamp(1.9rem, 3.6vw, 2.7rem);
  font-weight: 500;
  letter-spacing: -0.028em;
  line-height: 1.1;
  color: var(--ink);
  margin: 0 0 14px;
  text-wrap: balance;
}
.home-greeting .serif-moment {
  font-family: 'Newsreader', Georgia, serif;
  font-style: italic;
  font-weight: 500;
  font-feature-settings: "liga", "dlig";
  letter-spacing: -0.005em;
}
.home-greeting .home-name {
  /* keep sans, no italic — the name is the anchor */
}
.home-rule {
  width: 48px;
  border: 0;
  border-top: 1px solid var(--hairline-2);
  margin: 0 auto 14px;
}
.home-line {
  font-size: 0.95rem;
  line-height: 1.55;
  color: var(--ink-mute);
  max-width: 520px;
  margin: 0 auto;
  text-wrap: pretty;
}

/* === Diamond-rosette hero =============================================== */
.home-mark-block {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 28px;
  text-align: left;
  padding: 16px 0 40px;
  max-width: 720px;
  margin: 0 auto;
}
.home-mark {
  width: 96px; height: 96px;
  margin: 0;
  position: relative;
  flex-shrink: 0;
}
.home-mark-block > .home-metric,
.home-mark-block > div:not(.home-mark) {
  text-align: left;
  max-width: 380px;
}
.home-mark-svg {
  width: 100%; height: 100%;
  fill: var(--orange);
}
.home-mark-svg .home-mark-d rect {
  transform-origin: center;
  transform-box: fill-box;
  animation: home-mark-breathe 4s ease-in-out infinite;
}
.home-mark-svg .home-mark-d rect:nth-child(1) { animation-delay: 0.0s; }
.home-mark-svg .home-mark-d rect:nth-child(2) { animation-delay: 0.5s; }
.home-mark-svg .home-mark-d rect:nth-child(3) { animation-delay: 1.0s; }
.home-mark-svg .home-mark-d rect:nth-child(4) { animation-delay: 1.5s; }
.home-mark-svg .home-mark-d rect:nth-child(5) { animation-delay: 2.0s; }
.home-mark-svg .home-mark-d rect:nth-child(6) { animation-delay: 2.5s; }
.home-mark-svg .home-mark-d rect:nth-child(7) { animation-delay: 3.0s; }
.home-mark-svg .home-mark-d rect:nth-child(8) { animation-delay: 3.5s; }
@keyframes home-mark-breathe {
  0%, 100% { opacity: 0.85; }
  50%      { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .home-mark-svg .home-mark-d rect { animation: none; opacity: 1; }
}

/* Stack on mobile */
@media (max-width: 700px) {
  .home-mark-block { flex-direction: column; text-align: center; gap: 16px; }
  .home-mark-block > .home-metric,
  .home-mark-block > div:not(.home-mark) { text-align: center; }
}

.home-metric-eye, .home-metric-label {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 13px;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink-mute);
  font-weight: 500;
  margin-bottom: 6px;
}
.home-metric-value {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: clamp(2.2rem, 3.6vw, 2.8rem);
  font-weight: 500;
  letter-spacing: -0.028em;
  line-height: 1;
  color: var(--ink);
  margin-bottom: 8px;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
}
.home-metric-sub {
  font-size: 0.88rem;
  line-height: 1.5;
  color: var(--ink-mute);
}
.home-metric-sub {
  font-size: 0.95rem;
  line-height: 1.55;
  color: var(--ink-mute);
  max-width: 440px;
  margin: 0 auto;
  text-wrap: pretty;
}

/* === Activity strip ===================================================== */
.home-activity {
  max-width: 720px;
  margin: 0 auto;
  padding: 0 0 96px;
}
.home-activity-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  border-bottom: 1px solid var(--hairline);
  padding-bottom: 16px;
  margin-bottom: 24px;
}
.home-activity-head h2 {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 1rem;
  font-weight: 500;
  letter-spacing: -0.005em;
  color: var(--ink);
  margin: 0;
  flex: 1;
  text-transform: none;
}
.home-activity-head .section-marker { display: none; }
.home-activity-more {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 0.85rem;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink-mute);
  text-decoration: none;
  font-weight: 500;
  transition: color 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.home-activity-more:hover { color: var(--ink); }

.home-activity-list {
  list-style: none;
  margin: 0; padding: 0;
}
.home-activity-row {
  display: grid;
  grid-template-columns: 84px 1fr auto;
  gap: 18px;
  padding: 18px 0;
  border-bottom: 1px solid var(--hairline);
  align-items: baseline;
  transition: padding 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.home-activity-row:hover {
  padding-left: 8px;
  padding-right: 8px;
}
.home-activity-row:last-child { border-bottom: 0; }
.home-activity-time {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
.home-activity-body {
  font-size: 0.95rem;
  color: var(--ink);
  line-height: 1.5;
}
.home-activity-meta {
  display: block;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-top: 4px;
}
.home-activity-action {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 0.85rem;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink-mute);
  font-weight: 500;
  text-decoration: none;
  font-variant-numeric: tabular-nums;
  transition: color 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.home-activity-action:hover { color: var(--ink); }

@media (max-width: 700px) {
  .home-hero { padding: 12vh 0 48px; }
  .home-mark-block { padding: 24px 0 64px; }
  .home-mark { width: 180px; height: 180px; }
  .home-activity-row { grid-template-columns: 64px 1fr auto; gap: 12px; padding: 14px 0; }
}

/* === Editorial chapters — generous vertical rhythm ===================== */
.home-chapter {
  max-width: 720px;
  margin: 0 auto;
  padding: 24px 0 32px;
}
.home-chapter:first-of-type { padding-top: 8px; }
.home-chapter-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  border-bottom: 1px solid var(--hairline);
  padding-bottom: 16px;
  margin-bottom: 28px;
}
.home-chapter-head h2 {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 1rem;
  font-weight: 500;
  letter-spacing: -0.005em;
  color: var(--ink);
  margin: 0;
  flex: 1;
  text-transform: none;
}
.home-chapter-head .section-marker { display: none; }
.home-chapter-head .home-activity-more {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 0.85rem;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink-mute);
  text-decoration: none;
  font-weight: 500;
  transition: color 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.home-chapter-head .home-activity-more:hover { color: var(--ink); }

/* === Attention strip ===================================================== */
.home-attention {
  list-style: none; margin: 0; padding: 0;
}
.home-attention-row {
  display: grid;
  grid-template-columns: 14px 1fr auto;
  gap: 14px;
  padding: 14px 0;
  border-bottom: 1px solid var(--hairline);
  align-items: baseline;
  transition: padding 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.home-attention-row:hover {
  padding-left: 8px; padding-right: 8px;
}
.home-attention-row:last-child { border-bottom: 0; }
.home-attention-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--ink-faint);
  margin-top: 8px;
}
.home-attention-row.is-warn .home-attention-dot { background: var(--ink-mute); }
.home-attention-row.is-info .home-attention-dot { background: var(--ink-mute); }
.home-attention-body {
  font-size: 0.92rem;
  color: var(--ink);
  line-height: 1.5;
}
.home-attention-meta {
  display: block;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.58rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-top: 3px;
}
.home-attention-action {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 0.85rem;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink-mute);
  font-weight: 500;
  text-decoration: none;
  transition: color 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.home-attention-action:hover { color: var(--ink); }

/* === Pulse stats strip =================================================== */
.home-pulse {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 24px;
  padding: 0;
}
.home-pulse-item {
  display: flex; flex-direction: column;
  gap: 6px;
}
.home-pulse-eye, .home-pulse-label {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 0.78rem;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-mute);
  font-weight: 500;
}
.home-pulse-value {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 1.35rem;
  font-weight: 500;
  letter-spacing: -0.018em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
}
@media (max-width: 700px) {
  .home-pulse { grid-template-columns: repeat(2, 1fr); }
}

/* === Hero asset card (site preview / recovery hero / connect) ============ */
.home-asset {
  padding: 32px 0;
  border-top: 1px solid var(--hairline);
}
.home-asset-eye, .home-asset-label {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 14px;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink-mute);
  font-weight: 500;
  margin-bottom: 12px;
  display: block;
}
.home-asset-title {
  font-family: 'JetBrains Mono', monospace;
  font-size: 1.15rem;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.005em;
  word-break: break-all;
  line-height: 1.3;
  margin: 0 0 8px;
}
.home-asset-title.is-prose {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  letter-spacing: -0.014em;
  word-break: normal;
  font-size: 1.4rem;
}
.home-asset-sub {
  font-size: 0.95rem;
  color: var(--ink-mute);
  line-height: 1.55;
  margin: 0 0 24px;
  text-wrap: pretty;
}
.home-asset-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 20px;
}
.home-asset-stats {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  padding-top: 16px;
  border-top: 1px solid var(--hairline);
  font-variant-numeric: tabular-nums;
}
.home-asset-stats span + span::before { content: ' · '; color: var(--ink-faint); }
.home-asset-list {
  list-style: none; padding: 0; margin: 12px 0 24px;
  display: flex; flex-wrap: wrap; gap: 10px;
}
.home-asset-list li {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-mute);
  padding: 4px 10px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
}

/* === Tail card (subtle upsell / next-up) ================================ */
.home-tail {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px;
  padding: 32px 0;
  border-top: 1px solid var(--hairline);
}
.home-tail-text {
  font-size: 0.92rem;
  color: var(--ink-mute);
  line-height: 1.5;
  flex: 1;
  text-wrap: pretty;
}
.home-tail-link {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 0.85rem;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink-mute);
  font-weight: 500;
  text-decoration: none;
  flex-shrink: 0;
  transition: color 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.home-tail-link:hover { color: var(--ink); }

@media (max-width: 700px) {
  .home-asset { padding: 24px 0; }
  .home-tail { flex-direction: column; align-items: flex-start; }
}

/* Top "Settings" back-link chrome bar (appears at top of settings route) */
.settings-topbar {
  padding: 18px 32px 0;
  max-width: 1280px;
  margin: 0 auto 40px;
}
.settings-back {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--ink);
  text-decoration: none;
  padding: 6px 10px;
  margin-left: -10px;
  border-radius: 6px;
  transition: background 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.settings-back:hover { background: var(--cream-2); }
.settings-back svg { color: var(--ink-mute); }

/* ===================================================================
   Real-dashboard layout (dashboard-v2 home page)
   Replaces the editorial home-hero / home-mark-block / home-chapter
   stack with a Stripe / Linear-grade KPI + schedule + activity layout.
   Tier-aware via existing data-plan="builder|recovery|recovery-only".
   ================================================================== */

.dash-top {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px;
  padding: 0 0 14px;
  border-bottom: 1px solid var(--hairline);
  margin-bottom: 24px;
}
.dash-top-greeting {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 1.4rem; font-weight: 500;
  letter-spacing: -0.018em; color: var(--ink);
  margin: 0;
}
.dash-top-greeting .serif-moment {
  font-family: 'Newsreader', Georgia, serif;
  font-style: italic; font-weight: 500;
}
.dash-top-date {
  display: block;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.62rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink-faint); font-weight: 600;
  margin-top: 4px;
}
.dash-top-actions { display: flex; gap: 8px; flex-shrink: 0; }

.dash-kpis {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: var(--hairline);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  overflow: hidden;
  margin-bottom: 24px;
}
.kpi {
  background: var(--cream);
  padding: 16px 18px;
  display: flex; flex-direction: column;
}
.kpi-label {
  font-size: 0.85rem;
  color: var(--ink-mute);
  margin-bottom: 8px;
}
.kpi-value-row {
  display: flex; align-items: baseline; gap: 10px;
  margin-bottom: 6px;
}
.kpi-value {
  font-family: 'Geist', sans-serif;
  font-size: 1.85rem; font-weight: 500;
  letter-spacing: -0.022em; color: var(--ink);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.kpi-delta {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.72rem; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.kpi-delta.is-up { color: var(--green); }
.kpi-delta.is-down { color: var(--ink-mute); }
.kpi-spark {
  width: 100%; height: 28px;
  color: var(--ink-faint);
  margin-top: auto;
  display: block;
}
.kpi-spark.is-up { color: var(--green); }
.kpi-spark.is-down { color: var(--ink-mute); }

.dash-grid {
  display: grid;
  grid-template-columns: 1.5fr 1fr;
  gap: 24px;
  margin-bottom: 24px;
}
.dash-bottom-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
  margin-bottom: 24px;
}
@media (max-width: 800px) {
  .dash-kpis { grid-template-columns: repeat(2, 1fr); }
  .dash-grid, .dash-bottom-grid { grid-template-columns: 1fr; }
}

.dash-panel {
  border: 1px solid var(--hairline);
  border-radius: 12px;
  padding: 18px 20px;
  background: var(--cream);
}
.dash-panel-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px;
  padding-bottom: 14px; margin-bottom: 4px;
  border-bottom: 1px solid var(--hairline);
}
.dash-panel-title {
  font-family: 'Geist', sans-serif;
  font-size: 0.95rem; font-weight: 600;
  color: var(--ink);
  margin: 0;
  letter-spacing: -0.012em;
}
.dash-panel-link {
  font-size: 0.82rem;
  color: var(--ink-mute);
  text-decoration: none;
  font-weight: 500;
}
.dash-panel-link:hover { color: var(--ink); }

/* Schedule (recovery / recovery-only) and pipeline (builder) rows.
   Same shape, two class names so each tier reads cleanly in markup. */
.sched-row, .pipe-row {
  display: grid;
  grid-template-columns: 64px 1fr auto;
  gap: 12px;
  padding: 11px 0;
  border-bottom: 1px solid var(--hairline);
  align-items: baseline;
}
.sched-row:last-child, .pipe-row:last-child { border-bottom: 0; }
.sched-time, .pipe-time {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.72rem;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
.sched-body, .pipe-body {
  font-size: 0.88rem;
  color: var(--ink);
  line-height: 1.45;
}
.sched-meta, .pipe-meta {
  display: block;
  font-size: 0.78rem;
  color: var(--ink-mute);
  margin-top: 2px;
}
.sched-status, .pipe-action {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem; letter-spacing: 0.14em; text-transform: uppercase;
  font-weight: 600;
  color: var(--ink-mute);
  text-decoration: none;
}
a.sched-status:hover, a.pipe-action:hover { color: var(--ink); }
.sched-status.is-confirmed { color: var(--green); }
.sched-status.is-tentative { color: var(--ink-faint); }
.sched-status.is-onway { color: var(--orange); }

/* Source attribution chart */
.src-chart-wrap { padding: 8px 0 4px; }
.src-chart {
  width: 100%;
  height: 8px;
  border-radius: 4px;
  overflow: hidden;
  display: block;
}
.src-legend {
  display: flex; flex-direction: column;
  gap: 8px;
  margin-top: 14px;
}
.src-legend-row {
  display: flex; align-items: center; gap: 10px;
  font-size: 0.84rem;
  color: var(--ink-mute);
}
.src-legend-dot {
  width: 8px; height: 8px;
  border-radius: 2px;
  flex-shrink: 0;
}
.src-legend-pct {
  margin-left: auto;
  font-family: 'JetBrains Mono', monospace;
  font-variant-numeric: tabular-nums;
  font-size: 0.78rem;
  color: var(--ink);
  font-weight: 500;
}

/* Recent activity rows */
.act-row {
  display: grid;
  grid-template-columns: 64px 1fr;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--hairline);
  align-items: baseline;
}
.act-row:last-child { border-bottom: 0; }
.act-time {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.7rem;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
.act-body {
  font-size: 0.86rem;
  color: var(--ink);
  line-height: 1.45;
}

/* Footer hero asset card (replaces .home-asset for the new layout) */
.dash-asset {
  border: 1px solid var(--hairline);
  border-radius: 12px;
  padding: 22px 24px;
  background: var(--cream);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  margin-bottom: 32px;
}
.dash-asset-text { flex: 1; min-width: 0; }
.dash-asset-eye {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: 600;
  display: block;
  margin-bottom: 6px;
}
.dash-asset-title {
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--ink);
  margin: 0 0 4px;
  font-family: 'JetBrains Mono', monospace;
  word-break: break-all;
}
.dash-asset-title.is-prose {
  font-family: 'Geist', sans-serif;
  word-break: normal;
  letter-spacing: -0.012em;
}
.dash-asset-sub {
  font-size: 0.85rem;
  color: var(--ink-mute);
  line-height: 1.5;
  margin: 0;
}
.dash-asset-actions {
  display: flex; gap: 8px;
  flex-shrink: 0;
}
.dash-asset-stats {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.62rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-faint);
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--hairline);
  font-variant-numeric: tabular-nums;
}
.dash-asset-stats span + span::before { content: ' · '; color: var(--ink-faint); }

/* Dual-path stat strip — Builder tier shows bookings vs quotes side-by-side */
.dual-path-strip {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
  margin-top: 16px;
  padding: 14px 0 0;
  border-top: 1px solid var(--hairline);
}
.dual-path-tile { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.dual-path-eye {
  font-size: 0.78rem;
  color: var(--ink-mute);
}
.dual-path-num {
  font-family: 'Geist', sans-serif;
  font-size: 1.5rem; font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  line-height: 1.05;
}
.dual-path-trend {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.62rem; letter-spacing: 0.08em;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
@media (max-width: 700px) {
  .dual-path-strip { grid-template-columns: 1fr 1fr; gap: 14px; }
}

@media (max-width: 700px) {
  .dash-asset { flex-direction: column; align-items: flex-start; }
  .dash-asset-actions { width: 100%; }
  .dash-top { flex-direction: column; align-items: flex-start; }
  .dash-top-actions { width: 100%; }
}

/* === My site page ===================================================== */
.site-card-large {
  border: 1px solid var(--hairline);
  border-radius: 12px;
  padding: 32px;
  margin-bottom: 28px;
}
.site-live-pill {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--green); font-weight: 600; margin-bottom: 10px;
}
.site-live-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--green); animation: pulse 2.4s ease-in-out infinite; }
.site-url-large {
  font-family: 'JetBrains Mono', monospace;
  font-size: 1.4rem; font-weight: 500;
  color: var(--ink); letter-spacing: -0.005em;
  word-break: break-all; margin: 0 0 8px;
}
.site-card-sub {
  font-size: 0.95rem; color: var(--ink-mute); line-height: 1.55;
  margin-bottom: 20px;
}
.site-preview {
  height: 360px; width: 100%;
  background: linear-gradient(135deg, oklch(15% 0.018 60) 0%, oklch(22% 0.02 38) 100%);
  border-radius: 10px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; padding: 40px;
  color: oklch(96% 0.02 80);
  position: relative; overflow: hidden;
}
.site-preview-brand {
  font-family: 'Newsreader', Georgia, serif;
  font-style: italic;
  font-size: 0.95rem;
  color: oklch(80% 0.10 38);
  margin-bottom: 12px;
}
.site-preview-headline {
  font-family: 'Geist', sans-serif;
  font-size: clamp(1.4rem, 2.4vw, 2rem);
  font-weight: 500;
  letter-spacing: -0.024em;
  line-height: 1.15;
  margin: 0 0 18px;
  max-width: 600px;
}
.site-preview-cta {
  display: inline-flex; padding: 8px 16px;
  background: oklch(64% 0.16 38);
  color: #fff;
  border-radius: 8px;
  font-size: 0.85rem; font-weight: 500;
  font-family: 'Geist', sans-serif;
}

.quick-actions {
  display: flex; gap: 6px; flex-wrap: wrap;
  padding: 16px 0;
  border-top: 1px solid var(--hairline);
  margin-top: 28px;
}
.quick-action {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px;
  background: transparent;
  border: 1px solid var(--hairline);
  border-radius: 8px;
  font-family: inherit;
  font-size: 0.84rem; color: var(--ink-mute);
  text-decoration: none; cursor: pointer;
  transition: all 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.quick-action:hover { color: var(--ink); border-color: var(--hairline-2); }
.quick-action svg { width: 13px; height: 13px; flex-shrink: 0; }

/* === AI builder page ================================================== */
.builder-shell {
  display: grid;
  grid-template-columns: 360px 1fr;
  gap: 24px;
  height: calc(100vh - 180px);
  min-height: 480px;
}

/* === Claude-style chat surface ========================================
   Empty state: vertically-centered logo + serif greeting + rounded
   input + suggestion chips. No page header. When state="ready",
   layout becomes 2-column (chat left, preview right that slid in). */

/* Plan footer ("Builder · A$39.99/mo · Renews 28 May | Billing · Support")
   doesn't belong on the chat surface. Hide whenever the AI builder is
   mounted, regardless of state. */
main.dash:has(.builder-shell--centered) .foot { display: none !important; }

/* Sidebar collapse for first-run AI Builder users.
   Body class `is-builder-firstrun` is added by JS on AI builder mount
   when there's no site yet, removed when state transitions to ready.
   The sidebar slides out + the content area fills the freed width.
   The existing .side-opener button stays visible as a tiny hamburger
   so the user can still navigate if they want.
   ====================================================================== */
body.is-builder-firstrun .side {
  transform: translateX(-100%);
  pointer-events: none;
}
/* Builder chat surface (Lovable-style): sidebar hidden so the chat
   gets full-bleed width. Body class added by the AI builder shell on
   mount, removed when navigating away. */
body.is-builder-chat .side {
  transform: translateX(-100%);
  pointer-events: none;
}
/* Mobile exception: the drawer pattern relies on the sidebar sitting
   in place at left:0 with the card sliding over/away from it. The
   desktop builder rule above pushes the sidebar fully off-screen
   for the full-bleed preview, which breaks the drawer on mobile
   (tap hamburger → card slides right → reveals empty space because
   the sidebar isn't there). Restore the mobile drawer behaviour
   by reverting the transform + pointer-events at narrow widths. */
@media (max-width: 720px) {
  body.is-builder-chat .side {
    transform: none;
    pointer-events: auto;
  }
  /* The .shell carries padding-top: 52px on mobile to clear the
     topbar. But on this page the topbar is position:sticky (see
     inline rule at dashboard-v2-page.html:5634), so it already takes
     52px of flow — adding shell padding too would leave a 52px gap
     above the chat surface and the sidebar would show through it.
     Zero both the shell padding AND the prior main.dash padding-top
     reset (which was a leftover from when the topbar was assumed to
     be position:fixed). The topbar's own z-index:70 keeps it above
     everything, so no extra offset is needed. */
  body.is-builder-chat .shell { padding-top: 0 !important; }
  body.is-builder-chat .shell > main.dash { padding-top: 0 !important; }
  /* Founder ask 2026-05-29 — chat messages were sitting too far from
     the viewport edges on mobile. Tighten main.dash horizontal padding
     to 10px in builder-chat mode (the base mobile rule is 18px). */
  body.is-builder-chat .shell > main.dash {
    padding-left: 10px !important;
    padding-right: 10px !important;
  }
}
body.is-builder-chat .shell {
  grid-template-columns: 1fr !important;
}
body.is-builder-chat .shell > main.dash {
  grid-column: 1 / -1 !important;
}
/* Builder mode replaces the side-opener corner icon with a Back button. */
body.is-builder-chat .side-opener {
  display: none !important;
}
.builder-back-btn {
  display: none;
  position: fixed;
  top: 14px;
  left: 14px;
  z-index: 60;
  align-items: center;
  gap: 6px;
  padding: 7px 12px 7px 9px;
  border: 1px solid var(--hairline-2);
  border-radius: 8px;
  background: var(--paper, var(--cream-2));
  color: var(--ink-mute);
  text-decoration: none;
  font-family: inherit;
  font-size: 0.82rem;
  font-weight: 500;
  letter-spacing: -0.005em;
  cursor: pointer;
  box-shadow: 0 1px 2px rgba(0,0,0,0.06);
  transition: background 0.18s cubic-bezier(0.22, 1, 0.36, 1), color 0.18s, border-color 0.18s, transform 0.18s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.18s;
}
.builder-back-btn:hover {
  color: var(--ink);
  background: var(--cream-3, #F7F7F7);
  border-color: var(--hairline);
  box-shadow: 0 4px 14px -6px rgba(31, 22, 17, 0.20);
  transform: translateX(-1px);
}
.builder-back-btn:active { transform: translateX(0); }
.builder-back-btn svg { display: block; transition: transform 0.18s cubic-bezier(0.22, 1, 0.36, 1); }
.builder-back-btn:hover svg { transform: translateX(-2px); }
body.is-builder-chat .builder-back-btn { display: inline-flex; }
/* Mobile: tuck the floating Back pill away when the drawer is open so
   it doesn't sit on top of the sidebar header. The sidebar's nav links
   already give the user a way out, so the Back affordance is redundant
   in that state. */
@media (max-width: 720px) {
  /* 2026-05-22 — burger menu covers the same top-left slot on mobile
     and opens the drawer (which contains the same nav as Back); the
     floating Back pill ends up duplicated underneath the burger and
     visible at the edge as "ack". Hide it on all mobile builder
     states; desktop still gets the inline-flex back affordance. */
  body.is-builder-chat .builder-back-btn { display: none !important; }
}
body.is-builder-firstrun .pg,
body.is-builder-firstrun .dashboard-shell {
  grid-template-columns: 1fr !important;
}
body.is-builder-firstrun .side-opener {
  display: inline-flex !important;
}

.builder-shell--centered {
  grid-template-columns: 1fr;
  width: 100% !important;
  max-width: 880px !important;
  margin: 0 auto !important;
  gap: 0;
  /* Was 120px — that pushed the input bar visibly higher than the
     viewport bottom. 32px keeps it near the bottom edge while still
     clearing the optional verify-email banner. */
  height: calc(100vh - 32px);
  min-height: 580px;
  transition: max-width 0.5s cubic-bezier(0.65, 0, 0.35, 1);
}
/* Zero out main.dash bottom padding under the AI builder so the input
   bar isn't lifted off the viewport bottom by page chrome. */
main.dash:has(.builder-shell--centered) { padding-bottom: 0 !important; }

/* Ready state needs the parent .dash cap lifted too. The base .dash
   rule sets max-width: 940px + 32px side padding, so even with
   .builder-shell--centered going `width: 100% !important; max-width:
   none !important`, "100%" only fills 940px of the viewport — leaving
   the preview pane centered with empty space on either side. Drop the
   cap + side padding only while the builder is in ready state. */
main.dash:has(.builder-shell--centered[data-builder-state="ready"]) {
  max-width: none !important;
  padding-left: 0 !important;
  padding-right: 0 !important;
}
/* Ready state: Lovable-style split layout. Narrow chat column on the
   left holds the full conversation history + refine composer. Wide
   preview column on the right holds the iframe, edge-to-edge. The
   shell goes full-bleed (the .builder-shell--centered base rule has
   max-width: 880px !important so we have to !important the override
   too). */
.builder-shell--centered[data-builder-state="ready"] {
  /* 50/50 even split per founder. Mins kick in below ~1000px
     viewport, stacking via the mobile media query at line 4875. */
  grid-template-columns: minmax(420px, 1fr) minmax(520px, 1fr) !important;
  max-width: none !important;
  width: 100% !important;
  margin: 0 !important;
  gap: 16px;
  padding: 0;
}
.builder-shell--centered .builder-preview-pane { display: none; }
.builder-shell--centered[data-builder-state="ready"] .builder-preview-pane {
  display: flex;
  width: 100%;
  min-width: 0;
  animation: builderPreviewSlideIn 0.55s cubic-bezier(0.22, 1, 0.36, 1);
}
@keyframes builderPreviewSlideIn {
  from { opacity: 0; transform: translateX(24px); }
  to   { opacity: 1; transform: translateX(0); }
}

/* Chat pane — drops all card chrome in the centered/empty state. */
.builder-shell--centered .builder-chat-pane {
  border: 0;
  background: transparent;
  display: flex;
  flex-direction: column;
  /* Side padding 0 — AI message contents (brief-form fields) hit
     close to the screen edges. Bottom padding lifts the composer off
     the page edge. */
  padding: 0 0 22px;
  width: 100% !important;
  min-width: 0;
  box-sizing: border-box;
  /* Default (empty): centre everything vertically. */
  justify-content: center;
}
/* 2026-05-16 v4: founder feedback — "the text after I clicked build
   my site is stuck to the bottom text bar". The old rule used
   `justify-content: flex-end` when the chat had content, which pinned
   ALL chat rows to the bottom of the pane and pushed the new AI reply
   right up against the composer. Switch to `flex-start` so the chat
   body fills from the top and scrolls naturally; the composer stays
   at the bottom because chat-body has `flex: 1 1 auto`. Adds breathing
   room between the latest message and the composer. */
.builder-shell--centered .builder-chat-pane:has(.builder-chat-body:not(:empty)) {
  justify-content: flex-start;
}

.builder-chat-body {
  flex: 1 1 auto;
  overflow-y: auto;
  min-height: 0;
  /* Bottom padding inside the scroll viewport so the last message
     never sits flush against the composer when scrolled to the end. */
  padding-bottom: 24px;
  /* Flex-column so the content block can anchor to the bottom of the
     scroll viewport when shorter than it. Without this, a short
     form-card / first AI message sits at the top of the chat-body with
     a yawning gap above the composer (because chat-body fills the
     pane). Pairing display:flex+flex-direction:column with
     margin-top:auto on the first child lifts the entire content block
     to the bottom edge — every "last item" (form's Build button,
     latest AI reply, user message) ends up the same hairline gap above
     the chat bar that shellSyncChatPanePadBot reserves. When content
     overflows, auto resolves to 0 and the scroll behaves normally. */
  display: flex;
  flex-direction: column;
}
/* Specificity-boosted (2 classes + pseudo) to beat
   .builder-msg-timestamp:first-child { margin-top: 8px } at line 3760. */
.builder-shell--centered .builder-chat-body > :first-child { margin-top: auto !important; }
/* 2026-05-22 founder ask — on mobile, stack chat content from the TOP
   of chat-body instead of pinning it to the bottom. Previously the
   auto-margin above pushed short chats down toward the composer so
   the first message ended up well below the topbar fade band — that
   left a visible cream gap above the chat and meant the fade band had
   no content to actually fade. With margin-top: 0 the first message
   sits flush with the top of chat-body (behind the topbar at y=0),
   so messages scroll up through the fade band naturally and the
   topbar's blur always has real content to work on. Desktop keeps
   the auto-margin (chat-pane has its own composer-aligned layout). */
@media (max-width: 720px) {
  .builder-shell--centered:not([data-builder-state="ready"]) .builder-chat-body > :first-child {
    margin-top: 0 !important;
  }
}
.builder-chat-body:empty { display: none; flex: 0; padding-bottom: 0; }

/* Empty hero block — logo + serif italic greeting on a single horizontal row.
   2026-05-12: shrunk for Lovable parity. Previous clamp(2.2,3) was bigger
   than Lovable's headline; 1.65-2.2rem lands closer without going so small
   that the page feels timid. Logo dropped 56→36 to stay paired. */
.builder-empty-hero {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  margin: 0 auto 24px;
}
.builder-empty-mark { display: inline-flex; align-items: center; }
.builder-empty-mark img {
  display: block;
  width: 36px;
  height: 36px;
}
/* Refine mode (returning user with a live site): drop the diamond logo
   above the greeting. It pulls attention away from the "Want to refine
   your website?" headline and from the orange wave illustration. The
   logo stays on the cold-start "Let's build your website." state where
   it gives the empty hero some visual weight. */
body.has-live-site .builder-empty-mark { display: none; }
.builder-empty-greeting {
  font-family: 'Newsreader', Georgia, serif;
  font-style: italic;
  font-weight: 500;
  font-size: clamp(1.65rem, 2.8vw, 2.2rem);
  letter-spacing: -0.02em;
  color: var(--ink);
  margin: 0;
  line-height: 1.15;
  text-align: left;
}
.builder-chat-pane:has(.builder-chat-body:not(:empty)) .builder-empty-hero { display: none; }

/* Instruction line — calm, single-sentence under the hero. Auto-hidden
   once the chat thread starts. */
.builder-instruction {
  text-align: center;
  font-size: 0.95rem;
  color: var(--ink-mute);
  margin: 0 auto 28px;
  max-width: 540px;
  line-height: 1.5;
}
.builder-chat-pane:has(.builder-chat-body:not(:empty)) .builder-instruction { display: none; }

/* Refine-mode (set by dashboard-v2.js when the user already has at least
   one generated site): the trade-name chips beneath the chat input are
   build prompts ("Sparky in Newtown..."), not refine prompts. Hide them
   so the home-page chat reads cleanly as "tell me what to change about
   your existing site" instead of mixing creation and iteration. */
.is-refine-mode .builder-suggestion-row { display: none; }

/* === Avatar pattern for AI messages (Claude / ChatGPT style) ===
   AI message rows are flex with the BookingSprint logo on the left,
   message body to the right. AI messages drop the bubble background
   and read as flowing text. User messages stay right-aligned bubbles. */
.builder-msg-row {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  /* 2026-05-09: bump vertical spacing between message rows. The thread
     was reading as a wall of text — Lovable / ChatGPT both let messages
     breathe with ~16-22px between rows. */
  margin: 16px 0 0;
  /* Premium reveal — every chat row eases in instead of snapping. */
  animation: builderRowEnter 0.4s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.builder-msg-row:first-child { margin-top: 0; }
/* User-after-AI gets a wider gap than AI-after-user, so the rhythm of
   "user asks, AI answers" reads as paired turns rather than a flat list. */
.builder-msg-row.is-user { margin-top: 26px; }
.builder-msg-row.is-ai + .builder-msg-row.is-user { margin-top: 32px; }

/* Centered timestamp divider above user messages — Lovable-style.
   Format: "May 5 at 11:20 AM" — lets the user place the conversation
   in time without having to hover or open a tooltip. */
.builder-msg-timestamp {
  text-align: center;
  font-size: 0.78rem;
  color: var(--ink-mute);
  opacity: 0.7;
  margin: 28px 0 12px;
  letter-spacing: 0.01em;
}
.builder-msg-timestamp:first-child { margin-top: 8px; }

/* Action row under each AI message — retry / thumbs / copy / more.
   Quiet at rest, slightly more visible on hover. Persists in the DOM
   so users don't have to hover the message to find them; the icons
   themselves are muted enough to recede until needed. */
.builder-msg-actions {
  display: flex;
  align-items: center;
  /* 2026-05-24 — minimal icon row, no pill. Tight to the message
     above via small negative margin; -4px left aligns the first
     icon's hit-box with the message's left edge. */
  gap: 0;
  margin: -2px 0 0 -4px;
  padding: 0;
}
.builder-msg-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  background: transparent;
  border: 0;
  border-radius: 5px;
  color: var(--ink-mute);
  cursor: pointer;
  opacity: 0.6;
  transition: opacity 0.15s, background 0.15s, color 0.15s, transform 0.18s;
}
/* Override the inline width/height on the SVGs (set to 14px in JS) so
   the visible glyph shrinks alongside the button. CSS wins over inline
   attrs for SVG sizing in modern browsers. */
.builder-msg-action svg { width: 12px !important; height: 12px !important; }
.builder-msg-action:hover {
  opacity: 1;
  color: var(--ink);
  background: rgba(31, 22, 17, 0.06);
}
.builder-msg-action:active { transform: scale(0.94); }
.builder-msg-action.is-on {
  opacity: 1;
  color: var(--ink);
  background: rgba(31, 22, 17, 0.08);
}
/* Copy success — short tick state. */
.builder-msg-action.is-copied {
  opacity: 1;
  color: var(--ink);
  background: rgba(31, 22, 17, 0.08);
}
.builder-msg-action.is-copied svg { display: none; }
.builder-msg-action.is-copied::after {
  content: '';
  width: 10px;
  height: 10px;
  border: 2px solid currentColor;
  border-top: 0;
  border-right: 0;
  transform: rotate(-50deg) translate(1px, -1px);
}
@keyframes builderRowEnter {
  0%   { opacity: 0; transform: translateY(6px); }
  100% { opacity: 1; transform: translateY(0); }
}
.builder-msg-row.is-user {
  justify-content: flex-end;
  /* 2026-05-24 — scrollIntoView({block:'start'}) on a user row anchors
     it flush with the scroll container's top edge, but the fixed
     topbar (52px) overlays that area so the message ends up tucked
     UNDER the topbar. scroll-margin-top reserves room above the
     anchor target so the row lands just below the topbar with a small
     breathing gap, instead of disappearing behind it. */
  scroll-margin-top: 88px;
}
@media (prefers-reduced-motion: reduce) {
  .builder-msg-row { animation: none; }
}
.builder-msg-avatar {
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  margin-top: 4px;
  border-radius: 4px;
}
/* 2026-05-16 v4: founder asked for no logos in the chat thread.
   Hide the AI anchor entirely as a CSS safety net even if some code
   path still calls shellPlaceAiAnchor — only the empty-state hero
   shows the brand mark now. */
.builder-ai-anchor,
[data-builder-ai-anchor] {
  display: none !important;
}
.builder-ai-anchor--legacy {
  display: flex;
  align-items: center;
  padding: 8px 0 4px;
  margin-top: 2px;
}
.builder-ai-anchor img {
  display: block;
  width: 20px;
  height: 20px;
  opacity: 0.85;
  transform-origin: center;
}
@keyframes builderAnchorBreathe {
  0%, 100% { opacity: 0.45; transform: scale(0.94); }
  50%      { opacity: 1.00; transform: scale(1.04); }
}
@media (prefers-reduced-motion: reduce) {
  .builder-ai-anchor img { animation: none; opacity: 0.7; }
}
/* Override the legacy bubble for AI messages inside the row wrapper —
   plain text reads better with the single anchor logo beneath the
   latest reply (Lovable pattern). */
.builder-msg-row.is-ai .builder-msg {
  background: transparent !important;
  color: var(--ink) !important;
  align-self: auto !important;
  max-width: none;
  padding: 2px 0;
  /* 2026-05-24 founder: bumped to 1.06rem for a touch more readability. */
  font-size: 1.06rem;
  line-height: 1.5;
}
/* Founder 2026-05-29: Haiku replies now render light markdown
   (**bold**, lists, `code`) — see shellFormatAiText in
   public/builder/chat.js. Tradie-facing rendering needs each
   element styled cleanly inside the AI text bubble. */
.builder-msg-row.is-ai .builder-msg strong {
  font-weight: 600;
  color: var(--ink);
}
.builder-msg-row.is-ai .builder-msg em {
  font-style: italic;
}
.builder-msg-row.is-ai .builder-msg ul.builder-msg-list,
.builder-msg-row.is-ai .builder-msg ol.builder-msg-list {
  margin: 8px 0 12px;
  padding-left: 22px;
  display: block;
}
.builder-msg-row.is-ai .builder-msg ul.builder-msg-list { list-style: disc outside; }
.builder-msg-row.is-ai .builder-msg ol.builder-msg-list { list-style: decimal outside; }
.builder-msg-row.is-ai .builder-msg ul.builder-msg-list li,
.builder-msg-row.is-ai .builder-msg ol.builder-msg-list li {
  margin: 4px 0;
  padding-left: 2px;
  line-height: 1.5;
}
.builder-msg-row.is-ai .builder-msg ul.builder-msg-list li::marker,
.builder-msg-row.is-ai .builder-msg ol.builder-msg-list li::marker {
  color: var(--ink-mute);
}
.builder-msg-row.is-ai .builder-msg h4.builder-msg-heading {
  font-size: 1.04rem;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--ink);
  margin: 16px 0 6px;
  line-height: 1.35;
}
.builder-msg-row.is-ai .builder-msg h4.builder-msg-heading:first-child {
  margin-top: 4px;
}
.builder-msg-row.is-ai .builder-msg code.builder-msg-code {
  background: var(--cream-3);
  border: 1px solid var(--hairline);
  border-radius: 4px;
  padding: 1px 6px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.92em;
  color: var(--ink);
}
/* Auto-linkified URLs inside AI messages (e.g. "your site is live at
   smithys.bookingsprint.com"). Warm-stone palette, underline on hover. */
.builder-msg-link {
  color: var(--orange-deep, #c2470a);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  font-weight: 500;
}
.builder-msg-link:hover {
  text-decoration-thickness: 1.5px;
}
/* User bubbles stay as bubbles, max-width 75% so they don't span the
   full row when long. */
.builder-msg-row.is-user .builder-msg {
  /* Widened from 75% so user pills reach closer to the screen edge,
     matching Lovable's pattern. */
  max-width: 92%;
  /* 2026-05-24 founder: bumped to 1rem for a touch more readability.
     Overrides the .builder-msg base size (L8449). */
  font-size: 1rem;
}

/* Submission card — USER-side recap of the brief, rendered after the
   user clicks Build my site. Reads as the user's structured answer to
   the form question. Cream-stone bubble matching standard user-bubble
   styling, with key/value rows for each captured field. Right-aligned
   automatically via `.builder-msg-row.is-user { justify-content: flex-end }`. */
.builder-msg.is-user.builder-submission-card {
  display: flex !important;
  flex-direction: column;
  gap: 8px;
  padding: 14px 18px !important;
  border-radius: 14px !important;
  text-align: left;
  font-size: 0.875rem;
  line-height: 1.45;
  min-width: 260px;
  max-width: 420px;
  background: var(--cream-3, #E5E3DD) !important;
  color: var(--ink, #1F1611) !important;
  border: 1px solid rgba(31, 22, 17, 0.06) !important;
  box-shadow: 0 1px 2px rgba(31, 22, 17, 0.04);
}
.builder-submission-row {
  display: flex;
  justify-content: space-between;
  gap: 18px;
  align-items: baseline;
}
.builder-submission-label {
  color: rgba(31, 22, 17, 0.50);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  flex-shrink: 0;
}
.builder-submission-value {
  font-weight: 500;
  text-align: right;
  color: var(--ink, #1F1611);
  word-break: break-word;
}

/* === Loading animation === pulsing BookingSprint logo where the
   AI avatar would go. Used in place of the "..." typing indicator
   while a chat turn is in flight. */
.builder-loading-msg {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 12px;
  padding: 4px 0;
}
.builder-loading-logo {
  display: inline-block;
  width: 26px;
  height: 26px;
  animation: builderLogoPulse 1.4s ease-in-out infinite;
  transform-origin: center;
}
@keyframes builderLogoPulse {
  0%   { opacity: 0.45; transform: scale(0.92) rotate(0deg); }
  50%  { opacity: 1;    transform: scale(1.06) rotate(180deg); }
  100% { opacity: 0.45; transform: scale(0.92) rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .builder-loading-logo { animation: builderLogoPulseFade 1.4s ease-in-out infinite; }
}
@keyframes builderLogoPulseFade {
  0%, 100% { opacity: 0.4; }
  50%      { opacity: 1; }
}

/* Progress message: rolling stage label that updates while site
   generation is in flight. Three pulsing dots stagger so the message
   visibly animates even if the runner sits at one fraction for a
   while (Sonnet streaming output dominates wall-clock time). */
.builder-msg.is-progress {
  color: var(--ink-mute, #6b6660);
  font-size: 0.94rem;
  background: transparent;
  padding: 4px 0;
}
.builder-progress-dots {
  display: inline-flex;
  gap: 3px;
  align-items: center;
}
.builder-progress-dots > span {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.35;
  animation: builderProgressDots 1.2s ease-in-out infinite;
}
.builder-progress-dots > span:nth-child(2) { animation-delay: 0.15s; }
.builder-progress-dots > span:nth-child(3) { animation-delay: 0.30s; }
@keyframes builderProgressDots {
  0%, 100% { opacity: 0.25; transform: translateY(0); }
  50%      { opacity: 1;    transform: translateY(-2px); }
}
@media (prefers-reduced-motion: reduce) {
  .builder-progress-dots > span { animation: builderLogoPulseFade 1.4s ease-in-out infinite; }
}

/* === Design-choice popover (Phase 14g) ================================
   Floats above the input bar like Claude's command menu. Renders the
   four design questions (scope/palette/typography/layout) one at a
   time with a step counter; on completion, slides back into the
   input bar and a tight one-line summary lands in the chat. */
.builder-input-wrap {
  position: relative;
}
.builder-design-popover {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(100% + 8px);
  background: #fff;
  border: 1px solid rgba(15, 25, 50, 0.08);
  border-radius: 16px;
  box-shadow: 0 12px 32px rgba(20, 14, 8, 0.08), 0 2px 6px rgba(20, 14, 8, 0.04);
  padding: 16px 18px 14px;
  transform: translateY(16px);
  opacity: 0;
  transition: transform 0.26s cubic-bezier(0.2, 0.7, 0.25, 1), opacity 0.22s ease;
  pointer-events: none;
  z-index: 5;
}
.builder-design-popover.is-visible {
  transform: translateY(0);
  opacity: 1;
  pointer-events: auto;
}
.builder-design-popover-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 0 0 12px;
}
.builder-design-popover-step {
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-mute, #6b6660);
}
.builder-design-popover-close {
  font-size: 0.82rem;
  color: var(--ink-mute, #6b6660);
  background: transparent;
  border: 0;
  padding: 4px 8px;
  border-radius: 6px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.builder-design-popover-close:hover {
  background: rgba(15, 25, 50, 0.05);
  color: var(--ink, #1f1611);
}
/* Inside the popover: trim default qcard chrome since the popover
   already provides the card frame. */
.builder-design-popover .builder-qcard {
  background: transparent;
  border: 0;
  padding: 0;
}
.builder-design-popover .builder-qcard-head {
  margin-bottom: 10px;
}
.builder-design-popover .builder-qcard-q {
  font-size: 1rem;
  font-weight: 600;
  margin: 0 0 2px;
  color: var(--ink, #1f1611);
}
.builder-design-popover .builder-qcard-hint {
  font-size: 0.78rem;
  color: var(--ink-mute, #6b6660);
}
.builder-design-popover .builder-qcard-foot {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid rgba(15, 25, 50, 0.06);
  display: flex;
  align-items: center;
  gap: 8px;
}
.builder-design-popover .builder-qcard-spacer { flex: 1 1 auto; }

/* Tight inline summary that replaces the chunky multi-line plan pill
   after the user finishes the four design questions. */
.builder-msg.builder-plan-summary {
  background: transparent;
  color: var(--ink-mute, #6b6660);
  font-size: 0.92rem;
  padding: 4px 0;
}
.builder-msg.builder-plan-summary strong {
  color: var(--ink, #1f1611);
  font-weight: 500;
}

/* === Animated diamond logo (inline SVG) ================================
   Three animation modes for different states:
     .bs-anim-cascade  — clockwise pulse around the ring (typing/thinking)
     .bs-anim-construct — assemble + dissolve loop (actively building)
     .bs-anim-sonar    — pulse from centre outward (calm "I'm listening")

   Each variant sets up the inner <rect> with transform-box: fill-box so
   transform animations preserve the parent <g>'s positioning. */
.bs-anim svg { display: block; }
.bs-anim .bs-d rect {
  fill: var(--ink);
  transform-origin: center;
  transform-box: fill-box;
}

/* Cascade — opacity wave clockwise around the perimeter */
.bs-anim-cascade .bs-d { opacity: 0.22; animation: bs-anim-cascade 1.6s linear infinite; }
.bs-anim-cascade .bs-d:nth-child(1) { animation-delay: 0.0s; }
.bs-anim-cascade .bs-d:nth-child(3) { animation-delay: 0.2s; }
.bs-anim-cascade .bs-d:nth-child(5) { animation-delay: 0.4s; }
.bs-anim-cascade .bs-d:nth-child(7) { animation-delay: 0.6s; }
.bs-anim-cascade .bs-d:nth-child(8) { animation-delay: 0.8s; }
.bs-anim-cascade .bs-d:nth-child(6) { animation-delay: 1.0s; }
.bs-anim-cascade .bs-d:nth-child(4) { animation-delay: 1.2s; }
.bs-anim-cascade .bs-d:nth-child(2) { animation-delay: 1.4s; }
@keyframes bs-anim-cascade {
  0%, 80%, 100% { opacity: 0.22; }
  20%, 35%      { opacity: 1; }
}

/* Construct — diamonds assemble + dissolve in sequence */
.bs-anim-construct .bs-d rect { opacity: 0; animation: bs-anim-construct 2.4s ease-in-out infinite; }
.bs-anim-construct .bs-d:nth-child(1) rect { animation-delay: 0.00s; }
.bs-anim-construct .bs-d:nth-child(2) rect { animation-delay: 0.10s; }
.bs-anim-construct .bs-d:nth-child(3) rect { animation-delay: 0.20s; }
.bs-anim-construct .bs-d:nth-child(4) rect { animation-delay: 0.30s; }
.bs-anim-construct .bs-d:nth-child(5) rect { animation-delay: 0.40s; }
.bs-anim-construct .bs-d:nth-child(6) rect { animation-delay: 0.50s; }
.bs-anim-construct .bs-d:nth-child(7) rect { animation-delay: 0.60s; }
.bs-anim-construct .bs-d:nth-child(8) rect { animation-delay: 0.70s; }
@keyframes bs-anim-construct {
  0%   { opacity: 0; transform: scale(0.4) rotate(-25deg); }
  25%  { opacity: 1; transform: scale(1.0) rotate(0); }
  75%  { opacity: 1; transform: scale(1.0) rotate(0); }
  100% { opacity: 0; transform: scale(0.4) rotate(25deg); }
}

/* Sonar — pulse from centre outward in three rings */
.bs-anim-sonar .bs-d rect { animation: bs-anim-sonar 1.4s ease-in-out infinite; }
.bs-anim-sonar .bs-d:nth-child(2) rect,
.bs-anim-sonar .bs-d:nth-child(3) rect,
.bs-anim-sonar .bs-d:nth-child(6) rect,
.bs-anim-sonar .bs-d:nth-child(7) rect { animation-delay: 0s; }
.bs-anim-sonar .bs-d:nth-child(1) rect,
.bs-anim-sonar .bs-d:nth-child(8) rect { animation-delay: 0.25s; }
.bs-anim-sonar .bs-d:nth-child(4) rect,
.bs-anim-sonar .bs-d:nth-child(5) rect { animation-delay: 0.50s; }
@keyframes bs-anim-sonar {
  0%, 100% { opacity: 0.30; transform: scale(0.75); }
  35%      { opacity: 1.00; transform: scale(1.10); }
}
@media (prefers-reduced-motion: reduce) {
  .bs-anim .bs-d, .bs-anim .bs-d rect { animation: none !important; opacity: 1 !important; }
}

/* === AI builder · in-thread build message ============================
   Rich AI message that fires when the user submits the form-card. Two
   columns: stepped checklist on the left, terminal-style config stream
   on the right. The avatar at the row level uses bs-anim-construct
   while the build is in flight, settling once all steps are done. */
.builder-msg-row.is-build {
  align-items: flex-start;
  width: 100%;
}
.builder-msg-row.is-build .bs-anim {
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  margin-top: 4px;
}
/* Pin the message body to fill the rest of the row so the build grid
   always renders at the wide position regardless of how short any
   given step's log lines happen to be. */
.builder-msg-row.is-build .builder-msg {
  flex: 1 1 0 !important;
  min-width: 0 !important;
  max-width: none !important;
  width: 100%;
}
.builder-build {
  margin-top: 4px;
  width: 100%;
}
.builder-build-head {
  margin-bottom: 14px;
}
.builder-build-eye {
  display: block;
  font-family: 'JetBrains Mono', 'Geist Mono', monospace;
  font-size: 10px;
  letter-spacing: 0.14em;
  color: var(--ink-mute);
  text-transform: uppercase;
  margin: 0 0 4px;
}
.builder-build-title {
  font-family: 'Newsreader', serif;
  font-style: italic;
  font-weight: 500;
  font-size: 18px;
  line-height: 1.2;
  margin: 0;
  color: var(--ink);
}
.builder-build-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
  background: var(--cream-2);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  overflow: hidden;
}
.builder-build-steps {
  list-style: none;
  margin: 0;
  padding: 14px 16px;
  border-right: 1px solid var(--hairline);
}
.builder-build-step {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 0;
  font-size: 13px;
  color: var(--ink-mute);
  transition: color 0.2s ease;
}
.builder-build-step.is-active,
.builder-build-step.is-done { color: var(--ink); }
.builder-build-step-dot {
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.builder-build-step-dot .ring {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  border: 1.4px solid rgba(27, 25, 22, 0.18);
  box-sizing: border-box;
}
.builder-build-step.is-active .builder-build-step-dot .ring {
  border-color: #C2410C;
  border-top-color: transparent;
  animation: builder-build-spin 0.7s linear infinite;
}
.builder-build-step.is-done .builder-build-step-dot .ring { display: none; }
.builder-build-step.is-done .builder-build-step-dot::after {
  content: '';
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--ink);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'><polyline points='2 6 5 9 10 3' fill='none' stroke='%23FAF6EE' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat;
  background-position: center;
}
@keyframes builder-build-spin { to { transform: rotate(360deg); } }

/* Terminal-style code stream on the right */
.builder-build-log {
  padding: 14px 16px;
  font-family: 'JetBrains Mono', 'Geist Mono', monospace;
  font-size: 11.5px;
  color: var(--ink-mute);
  line-height: 1.7;
  overflow: hidden;
  max-height: 180px;
  background: #15110D;
  color: #E8DDC5;
  font-feature-settings: 'liga' 0;
}
.builder-build-log-line {
  opacity: 0;
  animation: builder-build-log-in 0.18s ease-out forwards;
  white-space: pre;
}
.builder-build-log-line .k { color: #F7B061; }
.builder-build-log-line .s { color: #A8D88A; }
.builder-build-log-line .c { color: rgba(232, 221, 197, 0.5); }
@keyframes builder-build-log-in { to { opacity: 1; } }
.builder-build-log .caret {
  display: inline-block;
  width: 6px;
  height: 12px;
  background: #E8DDC5;
  vertical-align: text-bottom;
  margin-left: 2px;
  animation: builder-build-blink 0.9s steps(1) infinite;
}
@keyframes builder-build-blink { 50% { opacity: 0; } }

@media (max-width: 720px) {
  .builder-build-grid { grid-template-columns: 1fr; }
  .builder-build-steps { border-right: 0; border-bottom: 1px solid var(--hairline); }
  .builder-build-log { max-height: 120px; }
}

/* === Lovable-style guided flow ========================================
   Thinking message + question cards + launch-done card. Replaces the
   form-card flow as the primary "tell me what you want" surface. */

/* Thinking message — small "Thought for Ns" header + status card */
.builder-thinking {
  margin-top: 4px;
  width: 100%;
}
.builder-thinking-eye {
  /* Founder 2026-05-29: was 13px — looked notably smaller than the
     AI reply text (~17px) that follows. Bumped to 15px so the
     "Thought for Ns" counter sits in the same weight class as the
     surrounding chat. */
  font-size: 15px;
  color: var(--ink-mute);
  margin: 0 0 10px;
}
.builder-thinking-card {
  border: 1px solid var(--hairline);
  border-radius: 12px;
  padding: 16px 18px;
  background: var(--cream-2);
  display: flex;
  align-items: center;
  gap: 12px;
}
/* Bare variant — no box, just the cascading logo + thinking text inline.
   !important on each property because the base .builder-thinking-card
   rule has accumulated a couple of overrides over time and this needs
   to win deterministically. */
.builder-thinking-card--bare {
  border: 0 !important;
  background: transparent !important;
  padding: 0 !important;
  border-radius: 0 !important;
  box-shadow: none !important;
}
/* Cycling-phrase thinking pulse (Variant 2). Words sit DARK at rest; a
   narrow LIGHT band slides left-to-right across them at 1.3s, the
   "thinking pulse" passing through. Faster + more contrasty than a
   shimmer so the direction reads clearly. .is-fading swaps phrase
   content cleanly; .is-settled freezes the band for the 'Got it.' beat.
   Math: bg-size 220%, bright spot at 50% of gradient = 110% of
   container; P=105% places it just off-left, P=-5% places it just
   off-right. Animating P down sweeps the band rightward. */
.builder-thinking-pulse {
  /* Founder 2026-05-29: was 14px — same readability gap as
     .builder-thinking-eye above. Bumped to 16px so the "Thinking…"
     pulse reads at the AI reply weight. */
  font-size: 16px;
  font-weight: 500;
  background: linear-gradient(
    90deg,
    rgba(31, 22, 17, 1.00)  0%,
    rgba(31, 22, 17, 1.00) 42%,
    rgba(31, 22, 17, 0.18) 50%,
    rgba(31, 22, 17, 1.00) 58%,
    rgba(31, 22, 17, 1.00) 100%
  );
  background-size: 220% 100%;
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  animation: builderThinkSlide 2.4s linear infinite;
  transition: opacity 0.22s ease;
}
.builder-thinking-pulse.is-fading { opacity: 0; }
.builder-thinking-pulse.is-settled {
  animation: none;
  background: none;
  color: var(--ink);
  -webkit-text-fill-color: var(--ink);
  opacity: 1;
}
@keyframes builderThinkSlide {
  0%   { background-position: 105% 0; }
  100% { background-position: -5% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .builder-thinking-pulse { animation: none; }
}

/* ── Build animation (30s ticker → image card) ─────────────────────
   Plays after the review card's "Build my site" tap on the
   Emergency-First (ef) branch. Total runtime 30s:
     0–5s   pulse alone (the .builder-thinking-pulse above keeps
            cycling phrases; this block stays empty)
     5–20s  line phase: trunk drops, globe fades in, tasks stream
            into a domino stack, then the close sequence wipes the
            words right-to-left, fades the globe, retracts the line
     20–30s image-card phase: 240×150 hero-photo card sits anchored
            below the pulse
   At 30s the JS removes the row and shellTransitionToReady fires.
   Sequence + close keyframes were tuned on the /_demo-thinking3
   demo before being lifted here; CSS deltas vs the demo are the
   open delays (5000ms / 5820ms instead of 2000ms / 2820ms) so
   phase 1 holds the pulse alone for the full 5 seconds. */
.bs-build-anim {
  position: relative;
  /* Force a fixed working width — the .builder-msg parent is a flex
     item that shrinks to fit the short pulse text ("Polishing copy"),
     which was clipping the 240px image card to ~half-width and wrapping
     row text mid-word. 320px gives the 240px card + 38px row indent +
     breathing room; max-width:100% protects narrow viewports. */
  width: 320px;
  max-width: 100%;
  height: 240px;
  margin-top: 14px;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 70px, #000 100%);
          mask-image: linear-gradient(180deg, transparent 0, #000 70px, #000 100%);
}
.bs-build-anim__trunk {
  position: absolute;
  left: 22px;
  top: 0;
  bottom: 30px;
  width: 1.5px;
  background: rgba(31, 22, 17, 0.16);
  pointer-events: none;
  transform-origin: top;
  transform: scaleY(0);
  animation: bsBuildAnimTrunkDrop 820ms cubic-bezier(0.65, 0, 0.35, 1) 5000ms forwards;
}
@keyframes bsBuildAnimTrunkDrop {
  0%   { transform: scaleY(0); }
  100% { transform: scaleY(1); }
}
.bs-build-anim__globe {
  position: absolute;
  left: 15px;
  bottom: 14px;
  width: 16px;
  height: 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  z-index: 2;
  pointer-events: none;
  color: rgba(31, 22, 17, 0.32);
  opacity: 0;
  transform: scale(0.55);
  animation: bsBuildAnimGlobeIn 380ms cubic-bezier(0.22, 1, 0.36, 1) 5820ms forwards;
  transform-origin: center;
}
.bs-build-anim__globe svg { width: 16px; height: 16px; display: block; }
@keyframes bsBuildAnimGlobeIn {
  0%   { opacity: 0; transform: scale(0.55); }
  100% { opacity: 1; transform: scale(1); }
}
.bs-build-anim__list {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 38px;
  pointer-events: none;
}
.bs-build-anim__row {
  --row-h: 30px;
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(var(--idx, 0) * var(--row-h));
  padding: 0 14px 0 38px;
  height: var(--row-h);
  display: flex;
  align-items: center;
  font-size: 0.86rem;
  color: #4A3D32;
  letter-spacing: -0.005em;
  line-height: 1.3;
  /* Explicit clip-path: inset(0 0 0 0) starting state so the
     close-rule can animate to inset(0 100% 0 0). Without it,
     browsers refuse to interpolate from the implicit default. */
  clip-path: inset(0 0 0 0);
  -webkit-clip-path: inset(0 0 0 0);
  transition:
    bottom 360ms cubic-bezier(0.22, 1, 0.36, 1),
    opacity 200ms ease-out,
    clip-path 460ms cubic-bezier(0.65, 0, 0.35, 1),
    -webkit-clip-path 460ms cubic-bezier(0.65, 0, 0.35, 1);
  opacity: 1;
}
.bs-build-anim__row::before {
  content: '';
  position: absolute;
  left: 22px;
  top: 50%;
  transform: translateY(-50%);
  width: 10px;
  height: 1.5px;
  background: rgba(31, 22, 17, 0.14);
}
.bs-build-anim__row.is-entering {
  bottom: calc((var(--idx, 0) - 1) * var(--row-h));
  opacity: 0;
  transition: none;
}
.bs-build-anim__row.is-leaving { opacity: 0; }
.bs-build-anim__row-text { display: inline-block; }

/* Close sequence — beats:
     0–460ms    words wipe right-to-left (clip-path on each row)
     460–800ms  globe shrinks + fades (animation-name swap)
     800–1520ms line retracts upward (transform-origin: top)
     +1500ms    image card lands (JS toggles .is-open)
   fill-mode 'both' on the close keyframes holds the open's
   end-state during the delay so trunk/globe don't snap back to
   the base hidden style when .is-closing lands. */
.bs-build-anim.is-closing .bs-build-anim__row {
  clip-path: inset(0 100% 0 0);
  -webkit-clip-path: inset(0 100% 0 0);
}
.bs-build-anim.is-closing .bs-build-anim__globe {
  animation: bsBuildAnimGlobeOut 340ms cubic-bezier(0.65, 0, 0.35, 1) 460ms both;
}
@keyframes bsBuildAnimGlobeOut {
  0%   { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(0.55); }
}
.bs-build-anim.is-closing .bs-build-anim__trunk {
  transform-origin: top;
  animation: bsBuildAnimTrunkRetract 720ms cubic-bezier(0.65, 0, 0.35, 1) 800ms both;
}
@keyframes bsBuildAnimTrunkRetract {
  0%   { transform: scaleY(1); }
  100% { transform: scaleY(0); }
}
.bs-build-anim.is-closing {
  -webkit-mask-image: none;
          mask-image: none;
}

.bs-build-anim__card {
  position: absolute;
  top: 0;
  left: 0;
  transform: translateY(-14px);
  opacity: 0;
  transition:
    opacity 380ms ease-out,
    transform 520ms cubic-bezier(0.16, 1, 0.3, 1);
  pointer-events: none;
  z-index: 3;
}
.bs-build-anim__card.is-open {
  opacity: 1;
  transform: translateY(0);
}
.bs-build-anim__card-inner {
  display: inline-flex;
  flex-direction: column;
  background: #FFF;
  border: 1px solid rgba(31, 22, 17, 0.10);
  border-radius: 12px;
  padding: 8px 8px 10px;
  box-shadow:
    0 1px 2px rgba(31, 22, 17, 0.04),
    0 12px 28px -10px rgba(31, 22, 17, 0.18);
}
.bs-build-anim__card-img {
  width: 240px;
  height: 150px;
  border-radius: 6px;
  background: linear-gradient(135deg, #3B5C7E 0%, #6A7C8A 55%, #C5A47E 100%);
  overflow: hidden;
}
.bs-build-anim__card-cap {
  font-size: 0.76rem;
  color: #6B5A4C;
  margin-top: 8px;
  text-align: center;
  font-weight: 500;
  letter-spacing: 0.005em;
}
@media (prefers-reduced-motion: reduce) {
  .bs-build-anim__trunk,
  .bs-build-anim__globe,
  .bs-build-anim__row,
  .bs-build-anim__card { animation: none !important; transition: none !important; opacity: 1; transform: none; }
}

/* Build thread — cascading-logo bar above a stack of tool cards that
   swap as the build progresses through phases (design → content →
   forms → polish → done). Logo stays put; cards swap. */
.builder-build-thread {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
}
.builder-build-logo-bar {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  padding: 0;
  margin-bottom: 4px;
}
.builder-build-logo-bar .bs-anim {
  width: 24px;
  height: 24px;
  flex-shrink: 0;
}
.builder-build-logo-bar .builder-thinking-pulse {
  font-size: 0.92rem;
  letter-spacing: -0.005em;
  margin-left: 2px;
}
.builder-build-stack {
  position: relative;
  width: 100%;
  max-width: 480px;
}
/* Tool card — sits in the stack, slides in on entry, fades out on
   leaving. New ones replace old ones to give the impression of the
   AI moving through tools. */
.builder-build-tool-card {
  border: 1px solid var(--hairline);
  border-radius: 12px;
  padding: 14px 16px;
  background: var(--paper, #fff);
  box-shadow: 0 1px 2px rgba(31, 22, 17, 0.04);
  animation: builderToolCardIn 0.42s cubic-bezier(0.22, 1, 0.36, 1);
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.builder-build-tool-card.is-leaving {
  opacity: 0;
  transform: translateY(-8px);
}
@keyframes builderToolCardIn {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .builder-build-tool-card { animation: none; }
  .builder-build-tool-card.is-leaving { transition: none; }
}
/* Final "Launched" card — solid ink check + business name, no steps. */
.builder-build-done-card .builder-build-task-head { padding-bottom: 0; border-bottom: 0; }
.builder-build-done-check {
  color: var(--ink);
  flex-shrink: 0;
}
.builder-build-task-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--hairline);
}
.builder-build-task-head .bs-anim {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}
.builder-build-task-title {
  font-size: 0.92rem;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.005em;
  flex: 1;
}
.builder-build-task-eye {
  font-size: 0.72rem;
  font-weight: 400;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}
.builder-build-task-steps {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.builder-build-step {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.84rem;
  line-height: 1.4;
  color: var(--ink-faint);
  transition: color 0.3s ease;
}
.builder-build-step.is-active { color: var(--ink); font-weight: 500; }
.builder-build-step.is-done   { color: var(--ink-mute); }
.builder-build-step-bullet {
  width: 13px;
  height: 13px;
  border-radius: 50%;
  border: 1.5px solid var(--ink-faint);
  background: transparent;
  flex-shrink: 0;
  position: relative;
  transition: border-color 0.3s ease, background 0.3s ease;
}
.builder-build-step.is-active .builder-build-step-bullet {
  border-color: var(--ink);
  animation: builderBuildBulletPulse 1.4s ease-in-out infinite;
}
.builder-build-step.is-active .builder-build-step-bullet::after {
  content: '';
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  background: var(--ink);
}
.builder-build-step.is-done .builder-build-step-bullet {
  border-color: var(--ink);
  background: var(--ink);
}
.builder-build-step.is-done .builder-build-step-bullet::after {
  content: '';
  position: absolute;
  left: 3.5px;
  top: 1.5px;
  width: 4px;
  height: 7px;
  border: solid var(--paper, #fff);
  border-width: 0 1.5px 1.5px 0;
  transform: rotate(45deg);
}
@keyframes builderBuildBulletPulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.18); }
}
@media (prefers-reduced-motion: reduce) {
  .builder-build-step.is-active .builder-build-step-bullet { animation: none; }
}
.builder-thinking-card .bs-anim {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
}
.builder-thinking-card-text {
  font-size: 14px;
  color: var(--ink);
}
.builder-thinking-card-sub {
  font-size: 12px;
  color: var(--ink-mute);
  display: block;
  margin-top: 2px;
}

/* Question card — shared shell for all four question types */
.builder-qcard {
  margin-top: 14px;
  border: 1px solid var(--hairline);
  border-radius: 14px;
  background: var(--cream-2);
  overflow: hidden;
  width: 100%;
}
.builder-qcard-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px 12px;
}
.builder-qcard-q {
  font-size: 16px;
  font-weight: 500;
  color: var(--ink);
  margin: 0;
}
.builder-qcard-hint {
  font-size: 12px;
  color: var(--ink-mute);
}
.builder-qcard-body {
  padding: 0 20px 16px;
}
.builder-qcard-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
}
.builder-qcard-grid.is-list { grid-template-columns: 1fr; gap: 6px; }

/* Each option in the grid */
.builder-qopt {
  border: 1.5px solid transparent;
  border-radius: 12px;
  padding: 14px 16px;
  background: rgba(255, 255, 255, 0.55);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 12px;
  transition: border-color 0.15s ease, background 0.15s ease;
  text-align: left;
  font: inherit;
  color: var(--ink);
}
.builder-qopt:hover { background: #fff; }
.builder-qopt.is-selected {
  border-color: #1B1916;
  background: #fff;
}
.builder-qopt-radio {
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 1.5px solid rgba(0, 0, 0, 0.22);
  box-sizing: border-box;
}
.builder-qopt.is-selected .builder-qopt-radio {
  border-color: #1B1916;
  background: radial-gradient(circle, #1B1916 40%, transparent 45%);
}
.builder-qopt-text { flex: 1; min-width: 0; }
.builder-qopt-title { font-size: 14px; font-weight: 500; display: block; }
.builder-qopt-sub { font-size: 12px; color: var(--ink-mute); display: block; margin-top: 1px; }

/* List variant (for "What should the site include?") — radio + title + sub */
.builder-qcard-grid.is-list .builder-qopt { padding: 12px 14px; border-radius: 10px; }

/* Visual variant — option contains an SVG/preview, title sits below */
.builder-qopt.is-visual {
  flex-direction: column;
  align-items: stretch;
  padding: 14px;
  gap: 10px;
}
.builder-qopt.is-visual .builder-qopt-preview {
  height: 90px;
  border-radius: 8px;
  background: var(--cream);
  border: 1px solid var(--hairline);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.builder-qopt.is-visual .builder-qopt-preview svg { display: block; max-width: 100%; max-height: 100%; }
.builder-qopt.is-visual .builder-qopt-text { text-align: center; }

/* Typography option: large sample text */
.builder-qopt.is-type {
  flex-direction: column;
  align-items: center;
  padding: 22px 14px 18px;
  gap: 4px;
}
.builder-qopt.is-type .builder-qopt-display {
  font-size: 22px;
  font-weight: 500;
  font-family: var(--qopt-display, 'Newsreader', serif);
  font-style: var(--qopt-display-style, italic);
  color: var(--ink);
}
.builder-qopt.is-type .builder-qopt-body {
  font-size: 12px;
  color: var(--ink-mute);
  font-family: var(--qopt-body, 'Geist', sans-serif);
}

/* Palette option: row of 4 colour swatches */
.builder-qopt.is-palette {
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
  padding: 12px;
}
.builder-qopt.is-palette .builder-qopt-swatches {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0;
  width: 100%;
  height: 64px;
  border-radius: 8px;
  overflow: hidden;
}
.builder-qopt.is-palette .builder-qopt-swatches > span {
  display: block;
  width: 100%;
  height: 100%;
}
.builder-qopt.is-palette .builder-qopt-name { font-size: 12px; color: var(--ink-mute); text-align: center; }
/* Same alignment fix for type + visual variants so they fill their cell */
.builder-qopt.is-type, .builder-qopt.is-visual { align-items: stretch !important; }

/* Layout option: wireframe sketch */
.builder-qopt.is-layout .builder-qopt-preview svg rect { fill: rgba(0,0,0,0.18); }

/* "Describe your own" row at the bottom of the card */
.builder-qcard-own {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 6px 0;
  font-size: 13px;
  color: var(--ink-mute);
  cursor: pointer;
  border: 0;
  background: transparent;
  font-family: inherit;
}
.builder-qcard-own .builder-qopt-radio { width: 12px; height: 12px; }

/* Footer nav (prev/next/skip/Next button) */
.builder-qcard-foot {
  display: flex;
  align-items: center;
  padding: 12px 16px;
  border-top: 1px solid var(--hairline);
  background: rgba(255, 255, 255, 0.5);
  gap: 8px;
}
.builder-qcard-nav-arrow {
  width: 28px; height: 28px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0;
  background: transparent;
  color: var(--ink-mute);
  cursor: pointer;
  border-radius: 6px;
}
.builder-qcard-nav-arrow:hover:not([disabled]) { background: var(--cream); color: var(--ink); }
.builder-qcard-nav-arrow[disabled] { opacity: 0.3; cursor: not-allowed; }
.builder-qcard-spacer { flex: 1; }
.builder-qcard-skip {
  background: transparent;
  border: 0;
  font: inherit;
  font-size: 13px;
  color: var(--ink-mute);
  cursor: pointer;
  padding: 6px 10px;
  border-radius: 6px;
}
.builder-qcard-skip:hover { color: var(--ink); }
.builder-qcard-cta {
  background: #1B1916;
  color: var(--cream);
  border: 0;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  padding: 8px 16px;
  border-radius: 8px;
  cursor: pointer;
}
.builder-qcard-cta:hover { background: #000; }
.builder-qcard.is-answered { opacity: 0.6; }
.builder-qcard.is-answered .builder-qcard-foot { display: none; }

/* === Launch-done card ================================================ */
.builder-launch {
  margin-top: 14px;
  border: 1.5px solid #C2A4FF;
  border-radius: 14px;
  background: var(--cream-2);
  overflow: hidden;
  width: 100%;
}
.builder-launch-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px 12px;
}
.builder-launch-title {
  font-size: 16px;
  font-weight: 500;
  margin: 0;
  color: var(--ink);
}
.builder-launch-bookmark {
  background: transparent; border: 0; padding: 4px;
  color: var(--ink-mute); cursor: pointer; border-radius: 6px;
}
.builder-launch-bookmark:hover { color: var(--ink); background: var(--cream); }
.builder-launch-checklist {
  list-style: none;
  margin: 0;
  padding: 6px 20px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.builder-launch-checklist li {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 13.5px;
  color: var(--ink);
}
.builder-launch-checklist .ring {
  width: 14px; height: 14px;
  border-radius: 50%;
  border: 1.5px solid rgba(0,0,0,0.22);
  flex-shrink: 0;
}
.builder-launch-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  border-top: 1px solid var(--hairline);
}
.builder-launch-actions button {
  background: transparent;
  border: 0;
  font: inherit;
  font-size: 13px;
  color: var(--ink);
  padding: 12px 16px;
  cursor: pointer;
}
.builder-launch-actions button + button { border-left: 1px solid var(--hairline); }
.builder-launch-actions button:hover { background: var(--cream); }

/* Summary paragraph below the card */
.builder-launch-summary {
  font-size: 14px;
  color: var(--ink);
  line-height: 1.55;
  margin: 14px 0 8px;
}
.builder-launch-summary-more {
  background: transparent; border: 0; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: 50%;
  margin-top: 4px;
  color: var(--ink-mute);
}
.builder-launch-summary-more:hover { color: var(--ink); background: var(--cream); }

/* Next-action chips */
.builder-launch-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
}
.builder-launch-chip {
  background: transparent;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  padding: 6px 14px;
  font: inherit;
  font-size: 12.5px;
  color: var(--ink);
  cursor: pointer;
}
.builder-launch-chip:hover { background: var(--cream); border-color: rgba(0,0,0,0.22); }

/* Details form card — same shell as the question cards but with text
   inputs + chip pickers for the practical business data the site
   needs (name / phone / suburbs / licence / services). */
.builder-detailcard {
  margin-top: 14px;
  border: 1px solid var(--hairline);
  border-radius: 14px;
  background: var(--cream-2);
  overflow: hidden;
  width: 100%;
}
.builder-detailcard-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px 12px;
}
.builder-detailcard-q {
  font-size: 16px;
  font-weight: 500;
  margin: 0;
}
.builder-detailcard-hint {
  font-size: 12px;
  color: var(--ink-mute);
}
.builder-detailcard-body {
  padding: 0 20px 16px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.builder-detailcard-field { display: flex; flex-direction: column; gap: 6px; }
.builder-detailcard-label {
  font-size: 12px;
  font-weight: 500;
  color: var(--ink-mute);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.builder-detailcard-input {
  background: #fff;
  border: 1px solid var(--hairline);
  border-radius: 8px;
  padding: 10px 12px;
  font: inherit;
  font-size: 14px;
  color: var(--ink);
  outline: 0;
  width: 100%;
  box-sizing: border-box;
}
.builder-detailcard-input:focus { border-color: rgba(0,0,0,0.4); }
.builder-detailcard-help { font-size: 11.5px; color: var(--ink-mute); margin: 2px 0 0; }
.builder-detailcard-chips {
  background: #fff;
  border: 1px solid var(--hairline);
  border-radius: 8px;
  padding: 8px 10px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}
.builder-detailcard-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--cream-2);
  border: 1px solid var(--hairline);
  border-radius: 999px;
  padding: 4px 8px 4px 10px;
  font-size: 12.5px;
  color: var(--ink);
}
.builder-detailcard-chip-remove {
  background: transparent; border: 0;
  width: 14px; height: 14px;
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer;
  color: var(--ink-mute);
  font-size: 14px;
  line-height: 1;
  padding: 0;
  border-radius: 50%;
}
.builder-detailcard-chip-remove:hover { color: var(--ink); background: rgba(0,0,0,0.05); }
.builder-detailcard-chip-input {
  background: transparent;
  border: 0;
  outline: 0;
  font: inherit;
  font-size: 13px;
  color: var(--ink);
  flex: 1;
  min-width: 80px;
  padding: 4px 6px;
}
.builder-detailcard-suggest {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 6px;
}
.builder-detailcard-suggest-chip {
  background: transparent;
  border: 1px dashed var(--hairline);
  border-radius: 999px;
  padding: 4px 10px;
  font: inherit;
  font-size: 12px;
  color: var(--ink-mute);
  cursor: pointer;
}
.builder-detailcard-suggest-chip:hover { color: var(--ink); border-color: var(--ink-mute); border-style: solid; }
.builder-detailcard.has-error .builder-detailcard-input.is-invalid,
.builder-detailcard.has-error .builder-detailcard-chips.is-invalid {
  border-color: #C2410C;
}

/* Plan summary pill (right-aligned, before questions) */
.builder-plan-pill {
  align-self: flex-end;
  max-width: 70%;
  background: var(--cream-2);
  border: 1px solid var(--hairline);
  border-radius: 14px;
  padding: 12px 16px;
  margin: 0 0 14px;
  font-size: 13px;
  color: var(--ink-mute);
  font-style: italic;
}
.builder-plan-pill .k { color: var(--ink); font-style: normal; font-weight: 500; }

/* ============================================================================
   SHARED CHAT-COMPOSER CLASSES — read before editing.

   The classes below (.builder-input-box, .builder-input-textarea,
   .builder-input-bar, .builder-input-action, .builder-input-send,
   .builder-input-mic, .builder-input-more, .builder-input-map,
   .builder-suggestion-row, .builder-suggestion-chip) are used on TWO
   different pages:

     Home  — /dashboard-v2.html, body.is-empty-dash (empty-state hero)
     Builder — /dashboard-v2-page.html, body.is-builder-chat,
                rendered inside .builder-shell--centered

   Convention to prevent home <-> builder bleed:
     - Base rules in this section = HOME PAGE defaults.
     - Builder-only overrides MUST nest under .builder-shell--centered,
       e.g. `.builder-shell--centered .builder-input-box { ... }`.
     - Home-only overrides MUST nest under body.is-empty-dash, e.g.
       `body.is-empty-dash .builder-input-textarea { ... }`.
     - NEVER add an unscoped rule that changes colour, border, or
       padding for builder-specific reasons — it WILL break the home
       composer too (it has happened many times: topbar bleed, chat-box
       white-vs-cream-2 bleed, hairline border drop, etc.).

   When in doubt, check both pages before pushing.
   ============================================================================ */
.builder-input-box {
  /* Base = white (var(--paper)) — that's the home-page chat composer's
     original colour. AI-builder override below (.builder-shell--centered
     .builder-input-box) bumps it to var(--topband) + border: 0 to match
     the sidebar inside the builder context only.
     Founder 2026-05-22: home composer needs its original hairline border
     back — without it the white-on-cream box has no defined edge. */
  background: var(--paper);
  border: 1px solid rgba(31, 22, 17, 0.14);
  border-radius: 24px;
  padding: 18px 16px 12px;
  box-shadow: 0 2px 8px -4px rgba(31, 22, 17, 0.06);
  display: flex;
  flex-direction: column;
  gap: 4px;
  box-sizing: border-box;
}
/* Hard-locked widths — the dashboard's flex column ancestors keep
   collapsing the chat surface to its content otherwise. */
.builder-shell--centered .builder-input-box,
.builder-shell--centered .builder-chat-body {
  width: 100% !important;
  /* Widen the chat surface so messages reach closer to the screen edges
     (Lovable's pattern — founder reference 2026-05-22). Was 820px which
     left a lot of cream gutter on mobile + desktop. */
  max-width: 980px !important;
  margin-left: auto !important;
  margin-right: auto !important;
  box-sizing: border-box !important;
}
/* Input box: hard-fixed width so it doesn't track the chat-body's
   content width (which can swell when a form-card or build message
   lands, making the bar visibly widen and contract as messages
   arrive). align-self: center pins it to the column centre. */
.builder-shell--centered .builder-input-box {
  flex-shrink: 0 !important;
  align-self: center !important;
  width: min(720px, 100%) !important;
  /* Founder 2026-05-30: visual styling (background / border / shadow /
     padding / radius) moved into public/css/builder-mobile.css to
     match the /sandbox .lov-comp recipe exactly. This rule only
     keeps the layout properties (flex-shrink / align-self / width)
     that are dashboard-specific. */
}
.builder-shell--centered .builder-chat-body {
  align-self: stretch !important;
  flex-shrink: 1 !important;
  min-height: 0 !important;
}
.builder-input-textarea {
  border: 0;
  outline: none;
  background: transparent;
  font-family: inherit;
  /* 2026-05-29 founder: was 0.75rem (12px) — too small for the
     placeholder to read as a premium composer (Lovable's sits at ~16px).
     Mobile overrides to 16px @ line 11186; this is the desktop/fallback. */
  font-size: 1rem;
  line-height: 1.45;
  resize: none;
  min-height: 32px;
  max-height: 240px;
  color: var(--ink);
  padding: 4px 4px 4px;
  width: 100%;
  overflow-y: auto;
  -webkit-appearance: none;
  appearance: none;
}
.builder-input-textarea::-webkit-scrollbar { width: 4px; }
.builder-input-textarea::-webkit-scrollbar-thumb { background: var(--hairline); border-radius: 2px; }
.builder-input-textarea::placeholder { color: var(--ink-faint); }
/* 2026-05-22: Lovable-style chat bar — symmetric icon row split into
   left (+ ...) and right (map mic send) clusters. All ghost buttons
   share one 34px squircle treatment so the row reads as one band; the
   send button stays a filled ink disc as the primary action signal. */
.builder-input-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 2px 0 0;
  gap: 8px;
}
.builder-input-bar__left,
.builder-input-bar__right {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
/* 3D-gradient squircle — shared style for +, ..., map, mic.
   Founder ask 2026-05-30: keep current 34px square dims, but apply
   the 3D gradient pattern from the sandbox /sandbox composer
   (white→cream gradient + inset highlight + soft drop shadow) so
   the buttons feel tactile instead of flat ghost outlines. */
.builder-input-action,
.builder-input-more,
.builder-input-map,
.builder-input-mic {
  background: linear-gradient(180deg, #FFFFFF 0%, #F5F5F5 100%);
  border: 1px solid rgba(31, 22, 17, 0.10);
  width: 34px;
  height: 34px;
  border-radius: 10px;
  color: var(--ink);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.9),
    inset 0 -1px 0 rgba(31, 22, 17, 0.04),
    0 1px 2px rgba(31, 22, 17, 0.06),
    0 2px 6px -2px rgba(31, 22, 17, 0.08);
  transition: background 0.15s, color 0.15s, border-color 0.15s, transform 0.15s;
}
.builder-input-action:hover:not(:disabled),
.builder-input-more:hover:not(:disabled),
.builder-input-map:hover:not(:disabled),
.builder-input-mic:hover:not(:disabled) {
  background: linear-gradient(180deg, #FFFFFF 0%, #EAEAEA 100%);
  border-color: rgba(31, 22, 17, 0.18);
  transform: translateY(-1px);
}
.builder-input-action:disabled,
.builder-input-more:disabled,
.builder-input-map:disabled,
.builder-input-mic:disabled { opacity: 0.55; cursor: not-allowed; }
/* Primary send — dark 3D-gradient disc with inset highlight + drop
   shadow, matching the .builder-handoff__btn.is-primary recipe. */
.builder-input-send {
  background: linear-gradient(180deg, #2A211B 0%, #1F1611 100%);
  color: var(--cream, #FAFAFA);
  border: 1px solid rgba(0, 0, 0, 0.55);
  width: 34px;
  height: 34px;
  border-radius: 50%;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  margin-left: 2px;
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.18),
    0 4px 10px -2px rgba(0, 0, 0, 0.22),
    inset 0 1px 0 rgba(255, 255, 255, 0.10);
  transition: background 0.15s, opacity 0.15s, transform 0.15s;
}
.builder-input-send:hover:not(:disabled) {
  background: linear-gradient(180deg, #332923 0%, #241B16 100%);
  transform: translateY(-1px);
}
.builder-input-send:disabled { opacity: 0.4; cursor: not-allowed; }

/* === Suggestion chip row (Claude's "Code / Learn / Strategize" pattern) === */
.builder-suggestion-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
  margin: 18px auto 0;
  max-width: 720px;
}
.builder-suggestion-chip {
  background: var(--paper, #fff);
  border: 1px solid var(--hairline);
  border-radius: 999px;
  padding: 8px 14px;
  font-family: inherit;
  /* 2026-05-29 founder: was 0.85rem (~13.6px); bumped to 0.92rem (~14.7px)
     so the in-chat suggestion chips read as primary affordances and stop
     feeling like footnotes vs the AI's own reply text. */
  font-size: 0.92rem;
  font-weight: 500;
  line-height: 1.25;
  color: var(--ink);
  text-decoration: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  opacity: 1;
  appearance: none;
  -webkit-appearance: none;
  transition: opacity 0.32s ease, background 0.18s, border-color 0.18s, transform 0.15s;
}
.builder-suggestion-chip:hover {
  background: var(--cream-2);
  border-color: var(--ink-mute);
}
.builder-suggestion-chip:active { transform: translateY(0.5px); }
.builder-suggestion-chip:focus-visible {
  outline: 2px solid var(--orange);
  outline-offset: 2px;
}
.builder-suggestion-chip svg { color: var(--ink-mute); flex-shrink: 0; }
.builder-chat-pane:has(.builder-chat-body:not(:empty)) .builder-suggestion-row { display: none; }

/* Refine-mode quick-prompt chips — same visual treatment as the trade
   chips above the chat in build mode, but tapping fills the textarea
   instead of submitting (so the tradie can keep typing specifics).
   Hidden by default; .is-refine-mode on .dash-empty-hero flips them on. */
.builder-refine-suggestions {
  display: none;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
  margin: 18px auto 0;
  max-width: 720px;
}
.is-refine-mode .builder-refine-suggestions { display: flex; }
.builder-refine-suggestions .builder-suggestion-chip { cursor: pointer; }

/* Refinement chips — only visible after a build completes
   (data-builder-state="ready"). Sits above the composer inside
   .builder-input-wrap and reuses .builder-suggestion-chip styling for
   the warm-stone palette. */
.builder-refine-row {
  display: none;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
  padding: 0 4px 10px;
  margin: 0 0 -2px;
}
.builder-shell[data-builder-state="ready"] .builder-refine-row { display: flex; }
.builder-refine-chip { font-size: 0.82rem; padding: 6px 12px; }

/* 2026-05-16 P3: docked picker (template / palette) pinned above the
   composer. Replaces the inline choice card so the picker doesn't push
   the chat thread on scroll and reads more like Lovable's UI. Hidden by
   default; shellPickerDockShow mounts it with a label + chip row. */
/* 2026-05-16 v3: Lovable-style Q&A card. Warm cream surface, soft
   shadow, 2x2 tile grid with distinct layout mockups (or palette
   swatches), "Describe your own" radio row, and a footer pairing
   nav arrows on the left with Skip-all + Next/Review on the right.
   Spec from agent-produced design analysis 2026-05-16. */
.builder-picker-dock {
  display: block;
  padding: 24px 24px 16px;
  margin: 0 0 14px;
  background: #FBFBFB;
  border: 1px solid rgba(31, 22, 17, 0.08);
  border-radius: 16px;
  box-shadow: 0 1px 2px rgba(31, 22, 17, 0.04), 0 8px 24px rgba(31, 22, 17, 0.06);
  /* 2026-05-24 cleaner entrance (founder: "needed a cleaner
     animation when it popped up on the screen"). Was the generic
     builderRowEnter (6px translateY, 320ms) shared with chat
     bubbles. Custom keyframe lifts from 18px below + 0.97 scale +
     opacity over 460ms with a soft overshoot curve, so the dock
     rises gracefully from the composer surface rather than just
     blinking in. GPU-accelerated via will-change. */
  animation: builderPickerDockRise 0.46s cubic-bezier(0.16, 1, 0.3, 1) both;
  will-change: transform, opacity;
  transform-origin: 50% 100%;
}
@keyframes builderPickerDockRise {
  0%   { opacity: 0; transform: translateY(18px) scale(0.97); }
  60%  { opacity: 1; }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}
.builder-picker-dock[hidden] { display: none; }
.builder-picker-dock.is-collapsing {
  animation: builderPickerDockCollapse 0.28s cubic-bezier(0.4, 0, 1, 1) both;
}
@keyframes builderPickerDockCollapse {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(-6px); }
}
.builder-picker-dock__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 20px;
}
.builder-picker-dock__label {
  font-size: 1.125rem;
  font-weight: 600;
  color: #1F1611;
  letter-spacing: -0.005em;
}
.builder-picker-dock__hint {
  font-size: 0.8125rem;
  font-weight: 500;
  color: #6B5A4C;
  flex: 0 0 auto;
}
.builder-picker-dock__chips {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
.builder-picker-dock__chip {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 12px;
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 500;
  text-align: left;
  padding: 18px 16px;
  background: #F1F1F1;
  border: 2px solid transparent;
  border-radius: 12px;
  color: #1F1611;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s;
  min-height: 110px;
}
.builder-picker-dock__chip:hover {
  background: #ECECEC;
}
.builder-picker-dock__chip:disabled { opacity: 0.55; cursor: default; }
/* 2026-05-24 selected affordance — clean white bg flip + ink border
   + a single soft outer halo. Earlier inset-ring approach (founder:
   "looks a bit unfinished with the black") was reading as a double
   border; one ring around the tile is enough now that the white-on-
   cream contrast carries the selection state. */
.builder-picker-dock__chip.is-selected {
  background: #FFFFFF;
  border-color: #1F1611;
  box-shadow: 0 0 0 4px rgba(31, 22, 17, 0.06);
}
.builder-picker-dock__chip.is-selected:hover {
  background: #FFFFFF;
}

/* Palette swatch — 4-stripe horizontal bar, centred in the tile. */
.builder-picker-dock__swatch {
  display: flex;
  width: 100%;
  height: 44px;
  border-radius: 6px;
  overflow: hidden;
  margin: 0 auto;
  border: 1px solid rgba(31, 22, 17, 0.06);
}
.builder-picker-dock__swatch > span { flex: 1 1 0; display: block; height: 100%; }

/* Layout mockup wrapper — 4 distinct patterns picked by index.
   Shape classes match the JS in shellPickerDockShow's renderTemplateLayout
   (dashboard-v2-page.html ~15745): lay-hero, lay-row, lay-cell, lay-bar,
   lay-split, lay-big, lay-col, lay-small, lay-head, lay-grid2, lay-gcell. */
.builder-picker-dock__layout {
  display: flex;
  width: 100%;
  max-width: 140px;
  height: 60px;
  margin: 0 auto;
  padding: 6px;
  box-sizing: border-box;
  background: transparent;
}
/* Pattern A — hero-columns (`data-layout="hero-columns"`):
   one wide hero bar on top, 3-column row underneath. */
.builder-picker-dock__layout[data-layout="hero-columns"] {
  flex-direction: column;
  gap: 6px;
}
.builder-picker-dock__layout .lay-hero {
  display: block;
  height: 18px;
  background: #D8D5CC;
  border-radius: 3px;
}
.builder-picker-dock__layout .lay-row {
  display: flex;
  gap: 6px;
  flex: 1;
  min-height: 0;
}
.builder-picker-dock__layout .lay-cell {
  flex: 1;
  background: #D8D5CC;
  border-radius: 3px;
}
/* Pattern B — stack (`data-layout="stack"`): 4 full-width bars. */
.builder-picker-dock__layout[data-layout="stack"] {
  flex-direction: column;
  gap: 5px;
  padding: 4px 6px;
}
.builder-picker-dock__layout .lay-bar {
  display: block;
  flex: 1;
  min-height: 6px;
  background: #D8D5CC;
  border-radius: 3px;
}
/* Pattern C — split (`data-layout="split"`): big left + 2 small right. */
.builder-picker-dock__layout[data-layout="split"] {
  flex-direction: row;
  gap: 0;
  padding: 6px;
}
.builder-picker-dock__layout .lay-split {
  display: flex;
  gap: 6px;
  flex: 1;
  width: 100%;
}
.builder-picker-dock__layout .lay-big {
  display: block;
  flex: 1.4;
  background: #D8D5CC;
  border-radius: 3px;
}
.builder-picker-dock__layout .lay-col {
  display: flex;
  flex-direction: column;
  gap: 5px;
  flex: 1;
  min-width: 0;
}
.builder-picker-dock__layout .lay-small {
  display: block;
  flex: 1;
  background: #D8D5CC;
  border-radius: 3px;
}
/* Pattern D — grid (`data-layout="grid"`): head bar + 2x2 cells. */
.builder-picker-dock__layout[data-layout="grid"] {
  flex-direction: column;
  gap: 5px;
  padding: 4px 6px;
}
.builder-picker-dock__layout .lay-head {
  display: block;
  height: 10px;
  background: #D8D5CC;
  border-radius: 3px;
}
.builder-picker-dock__layout .lay-grid2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 5px;
  flex: 1;
}
.builder-picker-dock__layout .lay-gcell {
  background: #D8D5CC;
  border-radius: 3px;
}

.builder-picker-dock__chip-label {
  font-size: 0.8125rem;
  font-weight: 500;
  color: #1F1611;
  line-height: 1.3;
  text-align: center;
  margin-top: 4px;
}
.builder-picker-dock__chip-sub {
  display: block;
  font-size: 0.75rem;
  font-weight: 400;
  color: #6B5A4C;
  margin-top: 3px;
  line-height: 1.35;
  text-align: center;
}

/* 2026-05-24 typography tile — self-demonstrating preview. Renders
   a real-feeling tradie title + body sample in the actual fonts,
   then a tiny font-name label at the bottom so the picker is also
   informative ("which fonts am I picking?"). Sizing tuned so the
   personality of each font is legible at the tile width (~140px).
   Title is set in the title font's family, body in the body font,
   both inline-stylesheet via JS in shellPickerDockShow. */
.builder-picker-dock__typography {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  padding: 6px 4px 4px;
  width: 100%;
  text-align: left;
}
.builder-picker-dock__typography-title {
  font-size: 1.4rem;
  font-weight: 500;
  color: #1F1611;
  line-height: 1.1;
  letter-spacing: -0.01em;
}
.builder-picker-dock__typography-body {
  font-size: 0.82rem;
  font-weight: 400;
  color: #4A3D32;
  line-height: 1.35;
}
.builder-picker-dock__typography-pair {
  font-size: 0.68rem;
  font-weight: 500;
  color: #8A7B6D;
  letter-spacing: 0.03em;
  margin-top: 8px;
}

/* 2026-05-24 review card — summary list inside the same dock element
   (chips slot gets repurposed via .builder-picker-dock__chips--review
   modifier). Tile grid styling is overridden back to a vertical
   stack, no hover affordance, no clickability. */
.builder-picker-dock__chips--review {
  display: flex !important;
  flex-direction: column !important;
  gap: 14px !important;
  padding: 4px 0 6px;
}
.builder-picker-dock__review-item {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px 14px;
  background: #FFFFFF;
  border: 1px solid rgba(31, 22, 17, 0.08);
  border-radius: 10px;
}
.builder-picker-dock__review-label {
  font-size: 0.7rem;
  font-weight: 500;
  color: #8A7B6D;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.builder-picker-dock__review-value {
  font-size: 0.95rem;
  font-weight: 500;
  color: #1F1611;
  line-height: 1.35;
}

/* Describe-your-own row — sits below the grid, full-width radio row.
   Matches the HTML emitted at dashboard-v2-page.html:9642
   (`.builder-picker-dock__custom`). */
.builder-picker-dock__custom {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 12px;
  padding: 12px 14px;
  border-radius: 10px;
  border: 1px solid rgba(31, 22, 17, 0.08);
  background: transparent;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s;
}
.builder-picker-dock__custom:hover {
  background: rgba(31, 22, 17, 0.025);
}
.builder-picker-dock__custom input[type="radio"] {
  appearance: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 1.5px solid #6B5A4C;
  flex: 0 0 auto;
  position: relative;
  margin: 0;
  cursor: pointer;
  transition: border-color 0.12s;
}
.builder-picker-dock__custom input[type="radio"]:checked {
  border-color: #1F1611;
}
.builder-picker-dock__custom input[type="radio"]:checked::after {
  content: '';
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  background: #1F1611;
}
.builder-picker-dock__custom span {
  font-size: 0.875rem;
  font-weight: 500;
  color: #1F1611;
}
.builder-picker-dock__custom.is-checked {
  border-color: rgba(31, 22, 17, 0.22);
}

/* 2026-05-24: replaced the "Describe your own" radio with an actual
   text input bar (Lovable parity). Sits between the chips grid and
   the foot; sized to match the composer's quiet inputs — calm border,
   no shadow, focus ring picks up the brand orange via :focus-visible.
   Applies to both the template picker AND the palette picker. */
.builder-picker-dock__custom-input {
  display: block;
  width: 100%;
  margin-top: 18px;
  height: 44px;
  padding: 0 14px;
  font-family: inherit;
  font-size: 0.9rem;
  color: #1F1611;
  background: #FFFFFF;
  border: 1px solid rgba(31, 22, 17, 0.14);
  border-radius: 12px;
  outline: 0;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
  -webkit-appearance: none;
          appearance: none;
}
.builder-picker-dock__custom-input::placeholder {
  color: #8A7B6D;
  opacity: 1;
}
.builder-picker-dock__custom-input:hover {
  border-color: rgba(31, 22, 17, 0.22);
}
.builder-picker-dock__custom-input:focus,
.builder-picker-dock__custom-input:focus-visible {
  border-color: var(--orange, #FF5B00);
  box-shadow: 0 0 0 3px rgba(255, 91, 0, 0.14);
}

/* Footer: nav arrows (left) + Skip-all + CTA (right). */
.builder-picker-dock__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 20px;
  padding-top: 14px;
  border-top: 1px solid rgba(31, 22, 17, 0.08);
}
.builder-picker-dock__nav {
  display: flex;
  gap: 6px;
}
.builder-picker-dock__navbtn {
  appearance: none;
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid rgba(31, 22, 17, 0.08);
  border-radius: 8px;
  color: #6B5A4C;
  cursor: pointer;
  padding: 0;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.builder-picker-dock__navbtn:hover:not(:disabled) {
  background: #F1F1F1;
  color: #1F1611;
  border-color: rgba(31, 22, 17, 0.18);
}
.builder-picker-dock__navbtn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.builder-picker-dock__actions {
  display: flex;
  align-items: center;
  gap: 14px;
}
.builder-picker-dock__skip {
  appearance: none;
  background: transparent;
  border: 0;
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 500;
  color: #6B5A4C;
  cursor: pointer;
  padding: 6px 4px;
  border-radius: 6px;
  transition: color 0.12s;
}
.builder-picker-dock__skip:hover {
  color: #1F1611;
}
.builder-picker-dock__next {
  appearance: none;
  height: 34px;
  padding: 0 16px;
  border-radius: 10px;
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  border: 1px solid rgba(31, 22, 17, 0.08);
  background: transparent;
  color: #1F1611;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.builder-picker-dock__next:hover:not(:disabled) {
  background: #F1F1F1;
}
.builder-picker-dock__next.is-primary {
  /* Brand ink (warm near-black) instead of the generic blue that
     was here before — matches the .builder-input-send circular
     send button so the picker's primary action reads as part of
     the same family. */
  background: var(--ink);
  border-color: var(--ink);
  color: var(--cream, #FAFAFA);
}
.builder-picker-dock__next.is-primary:hover:not(:disabled) {
  background: var(--ink-2, #2A1F17);
  border-color: var(--ink-2, #2A1F17);
}
.builder-picker-dock__next:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Phase 2 (2026-05-17): mobile-tuned picker dock — tighter padding,
   smaller tile minimum, smaller header text, more compact footer so
   the dock fits cleanly above the composer without crowding the
   "Generate your website." headline above it. */
@media (max-width: 720px) {
  .builder-picker-dock {
    padding: 16px 14px 12px;
    border-radius: 14px;
    margin: 0 0 10px;
    /* The dock sits inside .builder-input-wrap (position:fixed at the
       viewport bottom) along with the composer. Without a height cap
       on the chip area, a 3-card layout picker pushes the dock + foot
       past the viewport top and the Skip/Next buttons end up clipped
       under the composer's fade band. Cap the chips area + give it
       internal scroll; head + foot stay visible at all times so the
       Next button is always tappable. */
    display: flex;
    flex-direction: column;
    max-height: calc(100dvh - 52px - 120px - 24px);
  }
  .builder-picker-dock__head {
    margin-bottom: 12px;
    flex: 0 0 auto;
  }
  .builder-picker-dock__label {
    font-size: 0.95rem;
  }
  .builder-picker-dock__hint {
    font-size: 0.7rem;
  }
  .builder-picker-dock__chips {
    gap: 8px;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    /* 2026-05-24 founder report: selected tile "top and left side
       are being cut off". Root cause: overflow-y:auto here (needed
       so a tall 4-tile picker can scroll if vertical room is tight)
       also clips overflow-x by default, which crops the selected
       tile's 4px box-shadow halo where it extends past the chips
       container. Add 6px inner padding so the halo has breathing
       room inside the clip rect, paired with a -6px margin so the
       chips area's outer bounding box stays the same width (tiles
       don't shrink relative to the dock surface). */
    padding: 6px;
    margin: -6px;
  }
  .builder-picker-dock__chip {
    padding: 12px 10px;
    min-height: 86px;
    font-size: 0.78rem;
    gap: 8px;
  }
  .builder-picker-dock__swatch {
    height: 32px;
  }
  .builder-picker-dock__layout {
    max-width: 100px;
    height: 44px;
  }
  .builder-picker-dock__other {
    padding: 8px 4px;
    font-size: 0.78rem;
    flex: 0 0 auto;
  }
  .builder-picker-dock__custom-input {
    /* mobile-tight version below; base styles in the parent block */
    height: 38px;
    padding: 0 12px;
    font-size: 0.85rem;
  }
  .builder-picker-dock__foot {
    margin-top: 10px;
    padding-top: 10px;
    flex: 0 0 auto;
  }
  .builder-picker-dock__nav-btn {
    width: 32px;
    height: 32px;
  }
  .builder-picker-dock__next {
    height: 36px;
    padding: 0 18px;
    font-size: 0.85rem;
  }
}

/* Phase 2 empty-state mobile layout — switch the chat pane from
   vertically-centered to top-stacked when the picker dock is
   visible. The centered layout was clipping the "Generate your
   website." headline once the dock appeared. Always-flex-start on
   mobile is fine because the hero block already has its own bottom
   margin to push it away from the composer.

   Phase 3.7 (2026-05-17): Claude-style spacing. Hero + instruction
   hug the TOP, composer hugs the BOTTOM. The composer-pinning is
   handled by margin-top: auto on .builder-input-wrap further down
   (works in all states, not just empty), so this rule only owns
   the pane padding. */
@media (max-width: 720px) {
  /* 2026-05-22: scoped to genuinely-empty chats (no chat-body content
     yet) so the hero+chip stack keeps its top breathing room. Once the
     user types and chat-body fills, the padding-top here would leave
     a 24px cream gap above the chat content — defeating the Lovable-
     parity "scroll behind the topbar fade" behavior. */
  .builder-shell--centered[data-builder-state="empty"] .builder-chat-pane:not(:has(.builder-chat-body:not(:empty))) {
    justify-content: flex-start !important;
    padding-top: 24px !important;
    padding-bottom: max(16px, env(safe-area-inset-bottom)) !important;
  }
  /* Empty-state suggestion chips: 2-column grid (was a horizontal
     row that cycled one chip at a time). Each chip is a tappable
     trade option that fills the composer with the full example. */
  .builder-shell--centered[data-builder-state="empty"] .builder-suggestion-row {
    display: grid !important;
    grid-template-columns: 1fr 1fr !important;
    gap: 8px !important;
    margin: 14px 0 0 !important;
    max-width: none !important;
  }
  .builder-shell--centered[data-builder-state="empty"] .builder-suggestion-row .builder-suggestion-chip {
    justify-content: flex-start;
    padding: 10px 12px;
    font-size: 0.85rem;
  }
}

/* ────────────────────────────────────────────────────────────
   2026-05-16 v4: Lovable-flavoured polish pass on the picker.
   - Outer dock drops to cream-2; tiles rise to paper → cards-on-card.
   - Tiles get hairline + inset highlight + micro-shadow + 1px hover lift.
   - Selected tile: indigo ring (no harsh ink border); preview shapes
     turn brand-indigo so the choice "lights up".
   - Header: lighter title, "Select one" becomes an uppercase eyebrow pill.
   - Footer: gradient primary CTA with colored drop + press state.
   - Single shared focus ring across all interactive controls.
   Property comments inline below explain each move. */
.builder-picker-dock {
  padding: 22px 22px 14px;
  background: #F1F1F1;
  border: 1px solid rgba(31, 22, 17, 0.06);
  border-radius: 18px;
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.6) inset,
    0 1px 2px rgba(31, 22, 17, 0.03),
    0 12px 32px -12px rgba(31, 22, 17, 0.10);
}
.builder-picker-dock__head {
  align-items: center;
  margin-bottom: 16px;
}
.builder-picker-dock__label {
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.3;
}
.builder-picker-dock__hint {
  font-size: 0.75rem;
  font-weight: 500;
  color: #8A7868;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 3px 8px;
  border-radius: 999px;
  background: rgba(31, 22, 17, 0.04);
}
.builder-picker-dock__chips {
  gap: 10px;
}
.builder-picker-dock__chip {
  gap: 10px;
  padding: 14px 14px 12px;
  background: #FBFBFB;
  border: 1px solid rgba(31, 22, 17, 0.07);
  border-radius: 14px;
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.7) inset,
    0 1px 2px rgba(31, 22, 17, 0.04);
  text-align: left;
  transition:
    background 0.14s ease,
    border-color 0.14s ease,
    box-shadow 0.18s ease,
    transform 0.18s cubic-bezier(0.22, 1, 0.36, 1);
  min-height: 118px;
}
.builder-picker-dock__chip:hover {
  background: #FFFFFF;
  border-color: rgba(31, 22, 17, 0.14);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.8) inset,
    0 2px 4px rgba(31, 22, 17, 0.05),
    0 8px 18px -10px rgba(31, 22, 17, 0.12);
  transform: translateY(-1px);
}
.builder-picker-dock__chip:focus-visible {
  outline: none;
  border-color: rgba(31, 22, 17, 0.18);
  box-shadow:
    0 0 0 2px #FBFBFB,
    0 0 0 4px rgba(31, 22, 17, 0.35);
}
.builder-picker-dock__chip.is-selected {
  /* Phase 2: brand ink ring instead of the indigo-blue that was here
     from the v4 polish pass. The 1px outer ring + warm-ink border
     reads as "selected" without introducing a non-brand colour. */
  border-color: var(--ink, #1F1611);
  background: #FFFFFF;
  box-shadow:
    0 0 0 1px var(--ink, #1F1611),
    0 1px 0 rgba(255, 255, 255, 0.8) inset,
    0 2px 6px rgba(31, 22, 17, 0.08);
}
.builder-picker-dock__chip.is-selected:hover {
  transform: none;
}
.builder-picker-dock__layout {
  max-width: none;
  height: 64px;
  padding: 8px;
  background: #F1F1F1;
  border: 1px solid rgba(31, 22, 17, 0.05);
  border-radius: 8px;
}
.builder-picker-dock__layout .lay-hero,
.builder-picker-dock__layout .lay-cell,
.builder-picker-dock__layout .lay-bar,
.builder-picker-dock__layout .lay-big,
.builder-picker-dock__layout .lay-small,
.builder-picker-dock__layout .lay-head,
.builder-picker-dock__layout .lay-gcell {
  background: linear-gradient(180deg, #DCD8CE 0%, #CFCBC1 100%);
  border-radius: 2px;
}
.builder-picker-dock__chip:hover .lay-hero,
.builder-picker-dock__chip:hover .lay-cell,
.builder-picker-dock__chip:hover .lay-bar,
.builder-picker-dock__chip:hover .lay-big,
.builder-picker-dock__chip:hover .lay-small,
.builder-picker-dock__chip:hover .lay-head,
.builder-picker-dock__chip:hover .lay-gcell {
  background: linear-gradient(180deg, #CFCBC1 0%, #C3BEB3 100%);
}
/* 2026-05-24 founder report: the selected tile "looks unfinished
   with the black". Was: selected state darkened the mockup shapes
   (ink gradient) AND added border + halo — combined visual was a
   heavy dark block. Drop the mockup darken; selection is now
   carried by the white bg flip + ink border alone, mockup keeps
   its calm default colour so the tile reads as a clean "picked
   card" instead of a black slab. */
.builder-picker-dock__swatch {
  height: 40px;
  border-radius: 8px;
  border-color: rgba(31, 22, 17, 0.08);
  box-shadow: 0 1px 2px rgba(31, 22, 17, 0.04);
}
.builder-picker-dock__chip-label {
  font-size: 0.875rem;
  font-weight: 600;
  text-align: left;
  margin-top: 2px;
  letter-spacing: -0.005em;
}
.builder-picker-dock__chip-sub {
  font-size: 0.75rem;
  color: #8A7868;
  text-align: left;
  margin-top: 1px;
}
.builder-picker-dock__custom {
  padding: 11px 14px;
  border-radius: 12px;
  background: #FBFBFB;
  border-color: rgba(31, 22, 17, 0.07);
}
.builder-picker-dock__custom:hover {
  background: #FFFFFF;
  border-color: rgba(31, 22, 17, 0.14);
}
.builder-picker-dock__custom:focus-within {
  border-color: rgba(43, 108, 255, 0.55);
  box-shadow: 0 0 0 3px rgba(43, 108, 255, 0.18);
}
.builder-picker-dock__custom.is-checked {
  border-color: #2B6CFF;
  background: #FFFFFF;
  box-shadow: 0 0 0 1px #2B6CFF;
}
.builder-picker-dock__custom input[type="radio"]:checked {
  border-color: #2B6CFF;
}
.builder-picker-dock__custom input[type="radio"]:checked::after {
  background: #2B6CFF;
}
.builder-picker-dock__foot {
  margin-top: 16px;
  padding-top: 12px;
  border-top: 1px solid rgba(31, 22, 17, 0.06);
}
.builder-picker-dock__navbtn {
  width: 30px;
  height: 30px;
  border-radius: 9px;
  border-color: rgba(31, 22, 17, 0.07);
}
.builder-picker-dock__navbtn:focus-visible {
  outline: none;
  border-color: rgba(43, 108, 255, 0.55);
  box-shadow: 0 0 0 3px rgba(43, 108, 255, 0.18);
}
.builder-picker-dock__skip {
  font-size: 0.8125rem;
  color: #8A7868;
  text-decoration: none;
}
.builder-picker-dock__skip:hover {
  color: #1F1611;
  background: rgba(31, 22, 17, 0.04);
}
.builder-picker-dock__skip:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgba(43, 108, 255, 0.18);
}
.builder-picker-dock__next {
  height: 36px;
  padding: 0 18px;
  border-radius: 10px;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: -0.005em;
}
.builder-picker-dock__next.is-primary {
  /* Phase 2: brand ink replaces the indigo-blue gradient that was
     here from the v4 polish pass. The Send button in the composer
     uses var(--ink) so this primary CTA matches — same family. */
  background: linear-gradient(180deg, var(--ink-2, #2A1F17) 0%, var(--ink, #1F1611) 100%);
  border-color: var(--ink, #1F1611);
  color: var(--cream, #FAFAFA);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.18) inset,
    0 1px 2px rgba(31, 22, 17, 0.18),
    0 6px 14px -6px rgba(31, 22, 17, 0.35);
}
.builder-picker-dock__next.is-primary:hover:not(:disabled) {
  background: linear-gradient(180deg, var(--ink, #1F1611) 0%, #0F0A07 100%);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.18) inset,
    0 2px 4px rgba(31, 22, 17, 0.22),
    0 10px 22px -8px rgba(31, 22, 17, 0.45);
  transform: translateY(-1px);
}
.builder-picker-dock__next.is-primary:active:not(:disabled) {
  transform: translateY(0);
  box-shadow:
    0 1px 2px rgba(31, 22, 17, 0.30) inset;
}
.builder-picker-dock__next:focus-visible {
  outline: none;
  box-shadow:
    0 0 0 2px #FBFBFB,
    0 0 0 4px rgba(31, 22, 17, 0.35);
}

/* 2026-05-16 P3: composer prefix breadcrumbs. Compact chips inside the
   input box, above the textarea, showing the active template + palette.
   Persist across reloads via v15 templateChoice / paletteChoice fields. */
.builder-composer-prefix {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 6px 0 4px;
}
.builder-composer-prefix[hidden] { display: none; }
.builder-composer-prefix__chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.75rem;
  padding: 3px 9px;
  background: var(--cream-2, #F4F4F4);
  border: 1px solid var(--line, #E5E3DD);
  border-radius: 999px;
  color: var(--ink-soft, #5C534A);
}
.builder-composer-prefix__chip-swatch {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  display: inline-block;
  border: 1px solid rgba(0,0,0,0.12);
  flex: 0 0 auto;
}

/* === AI Builder inline form-card chat-message ===
   Sits in .builder-msg-row.is-ai so it gets the BookingSprint-logo avatar
   on the left. The card itself is the right-hand bubble — flush with how
   an AI message would render, but containing a small inline form. */
.builder-form-row { width: 100%; }
.builder-form-card {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 560px;
  background: var(--paper, #fff);
  border: 1px solid var(--hairline);
  border-radius: 16px;
  padding: 18px 20px 16px;
  box-shadow: 0 6px 22px -10px rgba(31, 22, 17, 0.10), 0 1px 2px rgba(31, 22, 17, 0.04);
  display: flex;
  flex-direction: column;
  gap: 12px;
  box-sizing: border-box;
  /* Premium reveal — slide up + fade + tiny scale, then stagger children. */
  animation: builderFormCardEnter 0.55s cubic-bezier(0.22, 1, 0.36, 1) both;
  transform-origin: top center;
  will-change: transform, opacity;
}
@keyframes builderFormCardEnter {
  0%   { opacity: 0; transform: translateY(18px) scale(0.985); }
  60%  { opacity: 1; }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}
/* Stagger every child of the card so the form composes itself in. */
.builder-form-card > .builder-form-header,
.builder-form-card > .builder-form-fields > .builder-form-field,
.builder-form-card > .builder-form-submit {
  animation: builderFormChildEnter 0.5s cubic-bezier(0.22, 1, 0.36, 1) both;
  opacity: 0;
}
@keyframes builderFormChildEnter {
  0%   { opacity: 0; transform: translateY(8px); }
  100% { opacity: 1; transform: translateY(0); }
}
.builder-form-card > .builder-form-header                     { animation-delay: 0.10s; }
.builder-form-card > .builder-form-fields > .builder-form-field:nth-child(1) { animation-delay: 0.18s; }
.builder-form-card > .builder-form-fields > .builder-form-field:nth-child(2) { animation-delay: 0.24s; }
.builder-form-card > .builder-form-fields > .builder-form-field:nth-child(3) { animation-delay: 0.30s; }
.builder-form-card > .builder-form-fields > .builder-form-field:nth-child(4) { animation-delay: 0.36s; }
.builder-form-card > .builder-form-fields > .builder-form-field:nth-child(5) { animation-delay: 0.42s; }
.builder-form-card > .builder-form-fields > .builder-form-field:nth-child(6) { animation-delay: 0.48s; }
.builder-form-card > .builder-form-submit                     { animation-delay: 0.55s; }
@media (prefers-reduced-motion: reduce) {
  .builder-form-card,
  .builder-form-card > .builder-form-header,
  .builder-form-card > .builder-form-fields > .builder-form-field,
  .builder-form-card > .builder-form-submit { animation: none; opacity: 1; transform: none; }
}

/* 2026-05-16 v8: smoother, slower form-card collapse on Build-my-site.
   The previous 380ms was abrupt and read as "clanky" — opacity dropped
   to 0 by 60% while the height was still 1200px, then snapped down.
   New curve: opacity fades over the FULL duration so the card visibly
   recedes rather than blinking out; scale settles to 0.93 with the
   ease-out-quart curve so it feels like the form is folding into the
   message stream. Duration bumped to 620ms. Recap card append is
   delayed by ~280ms in JS so the two motions don't fight. */
.builder-form-row.is-collapsing,
.builder-form-card.is-collapsing {
  animation: builderFormCollapse 0.62s cubic-bezier(0.65, 0, 0.35, 1) forwards;
  transform-origin: top center;
  overflow: hidden;
  pointer-events: none;
}
@keyframes builderFormCollapse {
  0%   { opacity: 1;    transform: scale(1)    translateY(0);    max-height: 1200px; filter: blur(0); }
  35%  { opacity: 0.65; transform: scale(0.985) translateY(-2px); max-height: 1200px; filter: blur(0); }
  70%  { opacity: 0.15; transform: scale(0.95) translateY(-6px); max-height: 380px;  filter: blur(0.3px); }
  100% { opacity: 0;    transform: scale(0.93) translateY(-10px); max-height: 0; margin: 0; padding-top: 0; padding-bottom: 0; filter: blur(0.6px); }
}
@media (prefers-reduced-motion: reduce) {
  .builder-form-row.is-collapsing,
  .builder-form-card.is-collapsing { animation: none; opacity: 0; max-height: 0; }
}

/* Textarea variant of .builder-form-input — keeps the same underline
   treatment but allows multi-line copy. */
.builder-form-textarea {
  resize: vertical;
  min-height: 60px;
  font-family: inherit;
  line-height: 1.5;
}

/* Segmented control — three side-by-side options for the flow-type
   field ("Book a job / Get a quote / Both"). Sits as a single rounded
   pill row with the active option in solid ink, the rest as muted
   ghost buttons. */
.builder-form-segmented {
  display: inline-flex;
  border: 1px solid var(--hairline);
  border-radius: 10px;
  overflow: hidden;
  background: var(--paper, #fff);
  width: 100%;
}
.builder-form-seg {
  flex: 1 1 0;
  padding: 9px 12px;
  font-family: inherit;
  font-size: 0.84rem;
  font-weight: 450;
  color: var(--ink-mute);
  background: transparent;
  border: 0;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
  border-right: 1px solid var(--hairline);
}
.builder-form-seg:last-child { border-right: 0; }
.builder-form-seg:hover { background: var(--cream-2); color: var(--ink); }
.builder-form-seg.is-on {
  background: var(--ink);
  color: var(--paper, #fff);
  font-weight: 500;
}
.builder-form-header {
  font-size: 0.95rem;
  line-height: 1.5;
  color: var(--ink);
  padding-bottom: 6px;
  border-bottom: 1px solid var(--hairline);
}

/* ──────────────────────────────────────────────────────────────────────
   Choice cards (2026-05-15, Option A): the two multi-choice questions
   that follow the form-card — template (3 text tiles) and palette
   (4 swatch tiles). Reuses .builder-form-card as the surface; this block
   adds the grid + tile + selected/disabled treatments. Renderer:
   shellAppendChoiceCard() in dashboard-v2-page.html.
   ────────────────────────────────────────────────────────────────────── */
.builder-choice-card { max-width: 600px; }
.builder-choice-sub {
  margin: -4px 0 4px;
  font-size: 0.82rem;
  color: var(--ink-mute);
  line-height: 1.45;
}
.builder-choice-grid {
  display: grid;
  gap: 10px;
}
.builder-choice-grid--text {
  /* 3 template tiles — single column at narrow widths, side-by-side
     where there's room. Each tile is a small card with label + sub copy. */
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
}
.builder-choice-grid--palette {
  /* 4 palette tiles — 2x2 below 480px, 4-across above. */
  grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 480px) {
  .builder-choice-grid--palette { grid-template-columns: repeat(4, 1fr); }
}
.builder-choice-tile {
  background: var(--paper, #fff);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  padding: 12px 14px;
  font-family: inherit;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 6px;
  transition: background 0.15s, border-color 0.15s, transform 0.15s, box-shadow 0.15s;
}
.builder-choice-tile:hover:not(:disabled):not(.is-selected) {
  background: var(--cream-2);
  border-color: var(--ink-mute);
  transform: translateY(-1px);
  box-shadow: 0 4px 12px -6px rgba(31, 22, 17, 0.16);
}
.builder-choice-tile:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}
.builder-choice-tile.is-selected {
  background: var(--paper, #fff);
  border-color: var(--ink);
  color: var(--ink);
  cursor: default;
  box-shadow: 0 0 0 1px var(--ink);
}
.builder-choice-tile.is-selected .builder-choice-tile-sub { color: var(--ink-mute); }
.builder-choice-tile:disabled:not(.is-selected) {
  opacity: 0.45;
  cursor: default;
}
.builder-choice-label {
  font-size: 0.92rem;
  font-weight: 500;
  letter-spacing: -0.005em;
}
.builder-choice-tile-sub {
  font-size: 0.78rem;
  color: var(--ink-mute);
  line-height: 1.4;
}

/* Palette tile: 3 stacked swatches (bg / fg / accent) above the label.
   The swatch row reads as a colour preview while the label names it. */
.builder-choice-grid--palette .builder-choice-tile {
  padding: 0;
  overflow: hidden;
}
.builder-choice-swatches {
  display: flex;
  height: 56px;
}
.builder-choice-swatch {
  flex: 1 1 0;
  display: block;
}
.builder-choice-grid--palette .builder-choice-label {
  padding: 8px 12px 10px;
  font-size: 0.82rem;
}
.builder-choice-grid--palette .builder-choice-tile.is-selected .builder-choice-label {
  background: var(--ink);
  color: var(--paper, #fff);
}

/* The card stops animating tiles when one's picked — locks the choice
   visually so the conversation thread is unambiguous. */
.builder-choice-card.is-picked .builder-choice-tile:not(.is-selected) {
  pointer-events: none;
}

/* 2026-05-16 v4: OpenAI/Claude-flavoured choice card chrome.
   - Outer dock: cream-2 surround so paper-white tiles read as raised cards.
   - Title left, uppercase eyebrow "SELECT ONE" pill right.
   - Tiles: paper-white with hairline + inset highlight + micro-shadow.
   - Selected: subtle ink ring + paper bg, mockup shapes go ink (NOT indigo —
     stays in the warm-stone/ink palette per OpenAI/Claude monochrome aesthetic).
   - Skeleton page-shape mockup at the top of each template tile so the
     three options read as different homepage structures, not three
     identical text rectangles. */
.builder-choice-card.builder-choice-card--v2 {
  max-width: 720px;
  background: var(--cream-2, #F1F1F1);
  border: 1px solid rgba(31, 22, 17, 0.06);
  border-radius: 18px;
  padding: 22px 22px 14px;
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.6) inset,
    0 1px 2px rgba(31, 22, 17, 0.03),
    0 12px 32px -12px rgba(31, 22, 17, 0.10);
}
.builder-choice-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 0 0 16px;
}
.builder-choice-title {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 1.0625rem;
  font-weight: 600;
  color: var(--ink, #1F1611);
  letter-spacing: -0.01em;
  line-height: 1.3;
}
.builder-choice-select-one {
  font-size: 0.75rem;
  font-weight: 500;
  color: #8A7868;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 3px 8px;
  border-radius: 999px;
  background: rgba(31, 22, 17, 0.04);
  flex: 0 0 auto;
}
.builder-choice-card--v2 .builder-choice-grid {
  gap: 10px;
  margin: 0;
}
.builder-choice-card--v2 .builder-choice-grid--text {
  grid-template-columns: repeat(3, 1fr);
}
@media (max-width: 560px) {
  .builder-choice-card--v2 .builder-choice-grid--text {
    grid-template-columns: 1fr;
  }
}
.builder-choice-card--v2 .builder-choice-tile {
  background: #FBFBFB;
  padding: 14px 14px 12px;
  border: 1px solid rgba(31, 22, 17, 0.07);
  border-radius: 14px;
  min-height: 140px;
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.7) inset,
    0 1px 2px rgba(31, 22, 17, 0.04);
  transition:
    background 0.14s ease,
    border-color 0.14s ease,
    box-shadow 0.18s ease,
    transform 0.18s cubic-bezier(0.22, 1, 0.36, 1);
}
.builder-choice-card--v2 .builder-choice-tile:hover:not(:disabled):not(.is-selected) {
  background: #FFFFFF;
  border-color: rgba(31, 22, 17, 0.14);
  transform: translateY(-1px);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.8) inset,
    0 2px 4px rgba(31, 22, 17, 0.05),
    0 8px 18px -10px rgba(31, 22, 17, 0.12);
}
.builder-choice-card--v2 .builder-choice-tile:focus-visible {
  outline: none;
  border-color: rgba(31, 22, 17, 0.18);
  box-shadow:
    0 0 0 2px #FBFBFB,
    0 0 0 4px rgba(31, 22, 17, 0.28);
}
.builder-choice-card--v2 .builder-choice-tile.is-selected {
  background: #FFFFFF;
  border-color: var(--ink, #1F1611);
  color: var(--ink, #1F1611);
  box-shadow:
    0 0 0 1px var(--ink, #1F1611),
    0 1px 0 rgba(255, 255, 255, 0.8) inset,
    0 2px 6px rgba(31, 22, 17, 0.06);
}
.builder-choice-card--v2 .builder-choice-tile.is-selected .builder-choice-tile-sub {
  color: var(--ink-mute, #6B5A4C);
}
.builder-choice-card--v2 .builder-choice-grid--palette .builder-choice-tile {
  min-height: 0;
  padding: 0;
  overflow: hidden;
}
.builder-choice-card--v2 .builder-choice-grid--palette .builder-choice-swatches {
  height: 64px;
}

/* Skeleton page-shape mockup inside each template tile. Same primitive
   shapes used in the picker dock v3 (lay-*) but with a `cm-` prefix to
   live alongside the choice-tile structure. Shapes use warm-grey fill
   with a subtle gradient for depth; on hover they darken; when the tile
   is selected they go full ink (monochrome — no indigo, OpenAI/Claude
   aesthetic). */
.builder-choice-mockup {
  display: flex !important;
  width: 100%;
  height: 64px;
  padding: 7px;
  margin: 0 0 4px;
  background: #F1F1F1;
  border: 1px solid rgba(31, 22, 17, 0.05);
  border-radius: 8px;
  box-sizing: border-box;
}
/* hero-columns: dominant hero (50%) + a short title strip + a row of 3
   service-card cells below. The title strip + slightly taller cm-row
   together break the "three thin bars" silhouette so this pattern reads
   clearly as "homepage hero + service cards" — visually distinct from
   the stacked editorial pattern next door. */
.builder-choice-mockup[data-mockup="hero-columns"] {
  flex-direction: column !important;
  gap: 4px;
  justify-content: flex-start;
  align-items: stretch;
}
.builder-choice-mockup[data-mockup="hero-columns"] > * {
  flex-shrink: 0;
}
.builder-choice-mockup[data-mockup="hero-columns"] .cm-hero {
  display: block;
  height: 28px;
  width: 100%;
  flex: 0 0 28px;
  border-radius: 3px;
}
.builder-choice-mockup[data-mockup="hero-columns"] .cm-row {
  display: flex !important;
  flex-direction: row !important;
  gap: 4px;
  flex: 1 1 auto;
  min-height: 18px;
  width: 100%;
  align-items: stretch;
}
.builder-choice-mockup .cm-cell {
  display: block;
  flex: 1 1 0;
  min-width: 0;
  min-height: 14px;
  align-self: stretch;
  border-radius: 3px;
}
.builder-choice-mockup[data-mockup="stack"] {
  flex-direction: column;
  gap: 4px;
  padding: 4px 6px;
}
.builder-choice-mockup .cm-bar {
  display: block;
  flex: 1;
  min-height: 5px;
  border-radius: 2px;
}
.builder-choice-mockup[data-mockup="split"] {
  flex-direction: row;
  padding: 6px;
}
.builder-choice-mockup .cm-split {
  display: flex;
  gap: 5px;
  flex: 1;
  width: 100%;
}
.builder-choice-mockup .cm-big {
  display: block;
  flex: 1.4;
  border-radius: 2px;
}
.builder-choice-mockup .cm-col {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1;
  min-width: 0;
}
.builder-choice-mockup .cm-small {
  display: block;
  flex: 1;
  border-radius: 2px;
}
.builder-choice-mockup[data-mockup="grid"] {
  flex-direction: column;
  gap: 4px;
  padding: 4px 6px;
}
.builder-choice-mockup .cm-head {
  display: block;
  height: 8px;
  border-radius: 2px;
}
.builder-choice-mockup .cm-grid2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
  flex: 1;
}
.builder-choice-mockup .cm-gcell {
  border-radius: 2px;
}
/* Default + hover + selected fills — single declaration so the gradient
   stays consistent across all shape primitives. */
.builder-choice-mockup .cm-hero,
.builder-choice-mockup .cm-cell,
.builder-choice-mockup .cm-bar,
.builder-choice-mockup .cm-big,
.builder-choice-mockup .cm-small,
.builder-choice-mockup .cm-head,
.builder-choice-mockup .cm-gcell {
  background: linear-gradient(180deg, #DCD8CE 0%, #CFCBC1 100%);
}
.builder-choice-card--v2 .builder-choice-tile:hover:not(:disabled):not(.is-selected) .cm-hero,
.builder-choice-card--v2 .builder-choice-tile:hover:not(:disabled):not(.is-selected) .cm-cell,
.builder-choice-card--v2 .builder-choice-tile:hover:not(:disabled):not(.is-selected) .cm-bar,
.builder-choice-card--v2 .builder-choice-tile:hover:not(:disabled):not(.is-selected) .cm-big,
.builder-choice-card--v2 .builder-choice-tile:hover:not(:disabled):not(.is-selected) .cm-small,
.builder-choice-card--v2 .builder-choice-tile:hover:not(:disabled):not(.is-selected) .cm-head,
.builder-choice-card--v2 .builder-choice-tile:hover:not(:disabled):not(.is-selected) .cm-gcell {
  background: linear-gradient(180deg, #CFCBC1 0%, #C3BEB3 100%);
}
.builder-choice-card--v2 .builder-choice-tile.is-selected .cm-hero,
.builder-choice-card--v2 .builder-choice-tile.is-selected .cm-cell,
.builder-choice-card--v2 .builder-choice-tile.is-selected .cm-bar,
.builder-choice-card--v2 .builder-choice-tile.is-selected .cm-big,
.builder-choice-card--v2 .builder-choice-tile.is-selected .cm-small,
.builder-choice-card--v2 .builder-choice-tile.is-selected .cm-head,
.builder-choice-card--v2 .builder-choice-tile.is-selected .cm-gcell {
  background: linear-gradient(180deg, #1F1611 0%, #2A1E17 100%);
}

/* "Describe your own" radio row — appears below the grid. */
.builder-choice-custom {
  margin-top: 10px;
  display: flex;
  align-items: center;
  gap: 10px;
  background: transparent;
  border: 0;
  padding: 8px 4px;
  cursor: pointer;
  color: var(--ink-mute);
  font: inherit;
  font-size: 0.92rem;
  border-radius: 8px;
  transition: color 0.14s, background 0.14s;
}
.builder-choice-custom:hover { color: var(--ink); background: rgba(255,255,255,0.5); }
.builder-choice-custom__radio {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 1.5px solid var(--ink-mute);
  flex: 0 0 auto;
  position: relative;
  transition: border-color 0.14s, background 0.14s;
}
.builder-choice-custom.is-selected .builder-choice-custom__radio {
  border-color: var(--ink);
}
.builder-choice-custom.is-selected .builder-choice-custom__radio::after {
  content: '';
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  background: var(--ink);
}
.builder-choice-custom.is-selected { color: var(--ink); }

/* Footer with Skip all (text-link) + primary CTA (filled). */
.builder-choice-foot {
  margin-top: 16px;
  padding-top: 12px;
  border-top: 1px solid rgba(31, 22, 17, 0.06);
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 14px;
}
.builder-choice-skip {
  background: transparent;
  border: 0;
  color: #8A7868;
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 500;
  cursor: pointer;
  padding: 6px 10px;
  border-radius: 8px;
  transition: color 0.14s, background 0.14s;
}
.builder-choice-skip:hover:not(:disabled) {
  color: var(--ink);
  background: rgba(31, 22, 17, 0.04);
}
.builder-choice-skip:disabled { opacity: 0.45; cursor: default; }
.builder-choice-cta {
  background: var(--ink, #1F1611);
  color: var(--paper, #fff);
  border: 1px solid var(--ink, #1F1611);
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: -0.005em;
  height: 36px;
  padding: 0 18px;
  border-radius: 10px;
  cursor: pointer;
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.08) inset,
    0 1px 2px rgba(31, 22, 17, 0.18),
    0 6px 14px -6px rgba(31, 22, 17, 0.30);
  transition: background 0.14s, transform 0.14s, box-shadow 0.18s;
}
.builder-choice-cta:hover:not(:disabled) {
  background: #2A1E17;
  transform: translateY(-1px);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.08) inset,
    0 2px 4px rgba(31, 22, 17, 0.20),
    0 10px 22px -8px rgba(31, 22, 17, 0.36);
}
.builder-choice-cta:active:not(:disabled) {
  transform: translateY(0);
  box-shadow: 0 1px 2px rgba(31, 22, 17, 0.32) inset;
}
.builder-choice-cta:disabled {
  opacity: 0.35;
  cursor: default;
  box-shadow: none;
}

/* User-side "you picked X" pill — right-aligned, ink-inverse, small. */
.builder-msg.is-user.builder-pick-pill {
  font-size: 0.85rem;
  padding: 8px 14px !important;
  border-radius: 14px !important;
  max-width: 75%;
}
.builder-form-fields {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.builder-form-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.builder-form-label {
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--ink-mute);
  letter-spacing: 0.005em;
}
.builder-form-input,
.builder-form-chip-input {
  border: 0;
  border-bottom: 1px solid var(--hairline);
  background: transparent;
  font-family: inherit;
  font-size: 0.98rem;
  line-height: 1.4;
  color: var(--ink);
  padding: 6px 2px 7px;
  outline: none;
  width: 100%;
  box-sizing: border-box;
  transition: border-color 0.15s;
  -webkit-appearance: none;
  appearance: none;
  border-radius: 0;
}
.builder-form-input::placeholder,
.builder-form-chip-input::placeholder {
  color: var(--ink-faint);
}
.builder-form-input:focus,
.builder-form-chip-input:focus {
  border-bottom-color: var(--ink);
}
.builder-form-help {
  margin: 0;
  font-size: 0.76rem;
  color: var(--ink-faint);
  line-height: 1.45;
}
.builder-form-field.has-error .builder-form-label { color: #b3401b; }
.builder-form-field.has-error .builder-form-input,
.builder-form-field.has-error .builder-form-chip-input,
.builder-form-field.has-error .builder-form-chips {
  border-bottom-color: #b3401b;
}
.builder-form-field.has-error .builder-form-chips {
  border-bottom: 1px solid #b3401b;
  padding-bottom: 4px;
}
/* 2026-05-19 — explicit required-state markup on labels (the leading
   "*"), and an error-tinted border on the segmented control when the
   founder-locked flowType pick is missing. */
.builder-form-req {
  color: #b3401b;
  margin-left: 2px;
  font-weight: 600;
}
.builder-form-field.has-error .builder-form-segmented {
  border-color: #b3401b;
  box-shadow: 0 0 0 1px #b3401b inset;
}

/* Suburb chips */
.builder-form-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  min-height: 26px;
  align-items: center;
}
.builder-form-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--cream-2);
  border: 1px solid var(--hairline);
  border-radius: 999px;
  padding: 4px 6px 4px 11px;
  font-size: 0.82rem;
  color: var(--ink);
  line-height: 1.2;
}
.builder-form-chip.is-placeholder {
  background: transparent;
  border-style: dashed;
  color: var(--ink-faint);
  padding: 4px 11px;
}
.builder-form-chip-remove {
  background: transparent;
  border: 0;
  color: var(--ink-mute);
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  padding: 0 4px;
  border-radius: 50%;
  transition: color 0.15s, background 0.15s;
}
.builder-form-chip-remove:hover { color: var(--ink); background: rgba(31, 22, 17, 0.06); }

/* Photo picker */
.builder-form-photo {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
}
.builder-form-photo-pick {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border: 1px dashed var(--hairline);
  border-radius: 10px;
  padding: 9px 14px;
  font-size: 0.85rem;
  color: var(--ink-mute);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.builder-form-photo-pick:hover {
  background: var(--cream-2);
  color: var(--ink);
  border-color: var(--ink-mute);
}
.builder-form-photo-thumbs {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.builder-form-photo-thumb {
  position: relative;
  width: 56px;
  height: 56px;
  border-radius: 8px;
  overflow: hidden;
  background: var(--cream-2);
  border: 1px solid var(--hairline);
}
.builder-form-photo-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.builder-form-photo-thumb.is-uploading::after {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(255, 255, 255, 0.55);
}
.builder-form-photo-remove {
  position: absolute;
  top: 2px;
  right: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 0;
  background: rgba(31, 22, 17, 0.72);
  color: #fff;
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}
.builder-form-photo-remove:hover { background: rgba(31, 22, 17, 0.92); }

/* Submit button — rectangular sibling of .builder-input-send */
.builder-form-submit {
  margin-top: 6px;
  width: 100%;
  background: var(--ink);
  color: var(--cream, #FAFAFA);
  border: 0;
  border-radius: 10px;
  padding: 12px 16px;
  font-family: inherit;
  font-size: 0.95rem;
  font-weight: 500;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  letter-spacing: -0.005em;
  box-shadow: 0 1px 0 rgba(31, 22, 17, 0.06), 0 4px 12px -4px rgba(31, 22, 17, 0.30);
  transition: background 0.18s ease, transform 0.18s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.18s ease, opacity 0.15s;
}
.builder-form-submit:hover:not(:disabled) {
  transform: translateY(-1.5px);
  box-shadow: 0 1px 0 rgba(31, 22, 17, 0.06), 0 8px 18px -6px rgba(31, 22, 17, 0.38);
}
.builder-form-submit:active:not(:disabled) {
  transform: translateY(0);
  box-shadow: 0 1px 0 rgba(31, 22, 17, 0.06), 0 2px 6px -3px rgba(31, 22, 17, 0.30);
}
.builder-form-submit:disabled { opacity: 0.55; cursor: not-allowed; }
.builder-form-submit svg { transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1); }
.builder-form-submit:hover:not(:disabled) svg { transform: translateX(2px); }

.builder-form-card.is-submitting {
  opacity: 0.6;
  pointer-events: none;
}

/* Mobile tab strip — hidden on desktop (both panes show side-by-side). */
.builder-mtabs { display: none; }
@media (max-width: 1000px) {
  .builder-shell { grid-template-columns: 1fr; height: auto; }

  /* Show the mobile tab strip and hide the inactive pane */
  .builder-mtabs {
    display: flex;
    background: var(--cream-2);
    border: 1px solid var(--hairline);
    border-radius: 10px;
    padding: 4px;
    gap: 4px;
    margin-bottom: 14px;
  }
  .builder-mtab {
    flex: 1 1 0;
    min-width: 0;
    padding: 10px 14px;
    font-family: inherit;
    font-size: 0.92rem;
    font-weight: 500;
    color: var(--ink-mute);
    background: transparent;
    border: 0;
    border-radius: 8px;
    cursor: pointer;
    min-height: 44px;
    touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
    transition: background 0.15s, color 0.15s;
  }
  .builder-mtab:hover { color: var(--ink); }
  .builder-mtab.is-on {
    background: var(--paper, #FFFFFF);
    color: var(--ink);
    box-shadow: 0 1px 2px rgba(31, 22, 17, 0.06);
  }
  /* Mobile-only: only the active pane is visible */
  .builder-shell[data-mobile-tab="refine"]  .builder-pane:nth-child(2) { display: none; }
  .builder-shell[data-mobile-tab="preview"] .builder-pane:nth-child(1) { display: none; }
  /* Make whichever pane IS visible take a sensible mobile height */
  /* Each builder pane fills the available height below the sticky
     topbar. Was `calc(100dvh - 240px)` — that 240px gap left ~180px
     of cream void between the form's last field and the fixed
     composer (chat-pane stopped 240px short of the viewport bottom,
     and the composer floats over its own padding-bottom band, not
     against the pane). Shrink the subtractor to just the topbar's
     52px so chat-pane reaches the composer; the pane's own
     padding-bottom (110px) still reserves space for the composer to
     sit above the chat content. */
  .builder-shell .builder-pane { min-height: calc(100dvh - 52px); }
}

/* ──────────────────────────────────────────────────────────────────────
   MOBILE BUILDER — Phase 1: preview-first ready state + draggable
   bottom sheet. Replaces the old tab-style mobile layout (.builder-mtabs
   above) at <=720px when data-builder-state="ready". Empty state stays
   on the existing centered chat (Phase 2 redesigns that).

   CONTAINING-BLOCK GOTCHA: the card-slide drawer (see "Card-slide drawer"
   block earlier in this file) applies `transform: translateX(...)` to
   .shell > main.dash, which makes main.dash the containing block for
   position:fixed descendants — so `bottom:0` ends up relative to
   main.dash's padding box, not the viewport. main.dash extends ~68px
   past the viewport on this page, which pushed the sheet off-screen.
   The card-slide doesn't apply on the builder page (sidebar is hidden
   via body.is-builder-chat), so killing the transform here lets the
   sheet anchor to the viewport again. */
@media (max-width: 720px) {
  /* :not(.mob-drawer-open) — drawer-closed defaults. Animation/opacity
     resets so other effects (page-enter fades, etc.) don't double-play
     during the builder mount. The card-slide drawer animation itself
     is now handled in dashboard-v2-page.css v9b — main.dash uses
     `left: var(--drawer-offset)` instead of `transform: translateX(...)`
     to avoid creating a containing block for position:fixed descendants
     (composer / cards-view shell). The transform override was removed
     from here because v9b's rule supersedes it. */
  body.is-builder-chat:not(.mob-drawer-open) .mob-topbar,
  body.is-builder-chat:not(.mob-drawer-open) .dash-hero-bg {
    animation: none !important;
    opacity: 1 !important;
  }
}

/* Phase 1 mobile sheet layout — diagram, snap points, behaviour notes.
   Visual sketch (peek state):
     ┌──────────────────────┐
     │ [≡] BookingSprint    │ existing mob-topbar
     ├──────────────────────┤
     │   live preview       │ preview-pane, position:fixed inset
     │   (iframe full bleed)│
     ├──────────────────────┤
     │ ━━━━ (drag handle)   │ .builder-sheet-handle
     │ Refine chips         │ visible at peek
     │ [Type to change…]    │ composer (input box)
     ├──────────────────────┤  sheet expands upward when dragged
     │ chat thread          │ chat-body (visible only when sheet > peek)
     └──────────────────────┘

   Snap points (managed by inline JS in dashboard-v2-page.html):
     peek = 200px  (chips + composer; chat-body hidden)
     half = 55dvh  (adds chat-body)
     full = 100dvh - 64px  (covers the page but not the topbar) */

/* Handle is hidden on desktop + empty-state mobile — only renders
   visibly when the sheet is active (mobile + ready state). The
   !important here guards against the JS setting an inline height
   on the chat-pane during empty state (the sheet setup runs on
   mount regardless of state, and any height side-effect must not
   reveal the handle). */
.builder-sheet-handle { display: none !important; }

@media (max-width: 720px) {
  /* Disable the legacy tab-style mobile layout entirely. */
  .builder-mtabs { display: none !important; }
  .builder-shell[data-mobile-tab="refine"] .builder-pane,
  .builder-shell[data-mobile-tab="preview"] .builder-pane { display: flex !important; }

  /* === READY STATE: preview-first with draggable bottom sheet === */
  .builder-shell--centered[data-builder-state="ready"] {
    display: block !important;
    padding: 0 !important;
    margin: 0 !important;
    height: calc(100dvh - 52px);
    max-width: none !important;
    width: 100% !important;
    overflow: hidden;
    position: relative;
  }

  /* Preview pane becomes the full-screen canvas below the topbar.
     NB: main.dash (this pane's ancestor) carries `transform:
     translateX(0)` for the card-slide drawer, which creates a new
     containing block. So position:fixed values here are relative to
     main.dash, not the viewport. main.dash already starts below the
     52px topbar — so top:0 here equals y=52 of the viewport. */
  .builder-shell--centered[data-builder-state="ready"] .builder-preview-pane {
    position: fixed !important;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    width: auto !important;
    height: auto !important;
    min-height: 0 !important;
    z-index: 1;
    border: 0 !important;
    border-radius: 0 !important;
    background: var(--cream);
    display: flex !important;
    flex-direction: column;
    animation: none !important;
    margin: 0 !important;
    padding: 0 !important;
  }

  /* Hide the desktop preview head (Preview/Settings tabs + Domain bar).
     Domain bar surfaces somewhere else in Phase 4 (publish flow). For
     Phase 1 the user can still use the chat to "make it live" via the
     existing refine chips that talk to the same backend. */
  .builder-shell--centered[data-builder-state="ready"] .builder-preview-head {
    display: none;
  }
  .builder-shell--centered[data-builder-state="ready"] .builder-preview-body {
    flex: 1 1 auto;
    border: 0;
    border-radius: 0;
    margin: 0;
    padding: 0;
    width: 100%;
  }
  .builder-shell--centered[data-builder-state="ready"] .builder-preview-body iframe {
    width: 100%;
    height: 100%;
    border: 0;
    display: block;
  }

  /* Chat pane becomes the draggable bottom sheet. JS controls
     --sheet-height + the [data-sheet-state] attribute.
     min-height:0 override — the legacy `@media (max-width: 1000px)`
     rule above sets `.builder-pane { min-height: calc(100dvh - 240px) }`,
     which was clamping the sheet to ~604px on a 844-tall viewport.
     justify-content:flex-start override — the default chat-pane rule
     (line 3642) sets justify-content:center for the empty/centered
     hero state, but in bottom-sheet mode the children (drag handle,
     hero/instruction, composer) MUST stack from the top edge of the
     sheet. Without this override, when chat-body is empty
     (post-generation but no chat history yet) the children get
     vertically centred AND overflow the 200px sheet — the drag
     handle ends up ~330px ABOVE the sheet's top edge, hidden behind
     the preview iframe. Touch events go to the iframe, the user
     can't drag the sheet. */
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane {
    position: fixed !important;
    bottom: 0;
    left: 0;
    right: 0;
    height: var(--sheet-height, 200px);
    max-height: calc(100dvh - 64px);
    min-height: 0 !important;
    z-index: 5;
    background: var(--paper, #ffffff);
    border-top-left-radius: 22px;
    border-top-right-radius: 22px;
    box-shadow:
      0 -1px 0 0 rgba(31, 22, 17, 0.05),
      0 -10px 32px -12px rgba(31, 22, 17, 0.18);
    padding: 0 !important;
    margin: 0 !important;
    width: 100% !important;
    min-width: 0 !important;
    overflow: hidden;
    display: flex !important;
    flex-direction: column !important;
    /* flex-start — was flex-end. With flex-end the children pile to the
       BOTTOM of the sheet, and when total content > sheet height (which
       happens in peek mode because empty-hero + instruction + composer
       all stack here), the OVERFLOW pushes UP past the sheet's top edge.
       The drag handle (first source-order child) ends up at y=401 on
       a 932-tall viewport — hidden behind the preview iframe, untouchable.
       flex-start anchors children to the top; the composer is pinned to
       the bottom via margin-top:auto on .builder-input-wrap below. */
    justify-content: flex-start !important;
    transition: height 0.32s cubic-bezier(0.2, 0, 0, 1);
  }
  /* Pin composer to the sheet's bottom regardless of how much other
     content is above it. Combined with flex-start on the parent, this
     gives us "handle at top, composer at bottom, any in-between content
     flows from the top". */
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane > .builder-input-wrap {
    margin-top: auto !important;
  }
  /* In peek state the empty-hero greeting + instruction line are
     irrelevant — they're for the centered empty/hero layout. Hidden
     in all sheet states by the broader rule further down. */
  /* During an active drag, kill the transition so the sheet tracks
     the finger 1:1 instead of easing toward target heights. JS adds
     .is-sheet-dragging when a pointer is down on the handle. */
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane.is-sheet-dragging {
    transition: none;
  }

  /* Sheet handle — the grab bar at the top of the sheet. */
  .builder-shell--centered[data-builder-state="ready"] .builder-sheet-handle {
    display: flex !important;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 28px;
    flex-shrink: 0;
    cursor: grab;
    touch-action: none;
    background: transparent;
    border: 0;
    /* Big enough hit target without being visually heavy. */
    padding: 0;
    user-select: none;
    -webkit-user-select: none;
  }
  .builder-shell--centered[data-builder-state="ready"] .builder-sheet-handle:active {
    cursor: grabbing;
  }
  .builder-sheet-handle__grip {
    display: block;
    width: 36px;
    height: 4px;
    border-radius: 2px;
    background: var(--ink-mute);
    opacity: 0.32;
    transition: opacity 0.18s;
  }
  .builder-shell--centered[data-builder-state="ready"] .builder-sheet-handle:hover .builder-sheet-handle__grip,
  .builder-shell--centered[data-builder-state="ready"] .builder-sheet-handle:active .builder-sheet-handle__grip {
    opacity: 0.55;
  }

  /* Chat / Sections tabs — surfaced on mobile in half/full sheet states
     so the user can flip the sheet's body between the AI chat thread
     and the per-section editing UI. Hidden in peek (peek is reserved
     for quick chips + composer; the tabs would just steal vertical
     space). The bare .builder-chat-tabs[hidden] rule (line 7297)
     keeps it invisible until shellTransitionToReady flips
     hidden=false. */
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane[data-sheet-state="peek"] .builder-chat-tabs {
    display: none !important;
  }
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane[data-sheet-state="half"] .builder-chat-tabs,
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane[data-sheet-state="full"] .builder-chat-tabs {
    display: flex !important;
    flex-shrink: 0;
    padding: 6px 18px 0;
    border-bottom: 1px solid rgba(31, 22, 17, 0.06);
    background: var(--paper, #ffffff);
    gap: 4px;
  }
  /* Re-style the tab buttons for the mobile sheet — tighter, taller
     hit-area, ink-only active state (no heavy underline) so the chrome
     reads as Claude-clean. */
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane .builder-chat-tab {
    padding: 10px 14px 12px;
    font-size: 0.86rem;
    font-weight: 500;
    color: var(--ink-mute);
    background: transparent;
    border: 0;
    border-bottom: 2px solid transparent;
    cursor: pointer;
    letter-spacing: -0.005em;
    transition: color 0.18s ease, border-color 0.18s ease;
  }
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane .builder-chat-tab.is-on {
    color: var(--ink);
    border-bottom-color: var(--ink);
    font-weight: 600;
  }
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane .builder-chat-tab:hover {
    color: var(--ink);
  }

  /* Chat thread inside the sheet — hidden in peek, visible in
     half/full. Scrolls within its own bounds when content overflows.
     Flex-anchor (margin-top:auto on first child) inherited from the
     base .builder-chat-body rule keeps the latest message pinned to
     the bottom edge so the most relevant content sits right above
     the chips, not the top. */
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    /* 2026-05-24 founder report: text + composer were "shrinking and
       getting wider through the chat". Root cause was state=ready
       stacking another 16px horizontal padding on top of the constant
       16px chat-pane padding (sheet-era leftover) — total inset
       jumped from 16px to 32px the moment the site went live. Keep
       vertical breathing room here; horizontal gutter is owned by
       chat-pane (constant across states).
       --bs-anchor-reserve is set by shellReserveScrollRoomForTopAnchor
       (JS) when a fresh user message lands in ready state so the row
       can be scrolled all the way to the top of the viewport. Default
       8px = pre-fix behaviour. */
    padding: 4px 0 max(8px, var(--bs-anchor-reserve, 8px));
    -webkit-overflow-scrolling: touch;
    /* 2026-05-24 — scroll-padding-top tells scrollIntoView to leave
       this much headroom above the anchored target. Clears the 52px
       fixed topbar + breathing room so follow-up user messages don't
       end up tucked underneath it. Pairs with scroll-margin-top on
       .builder-msg-row.is-user (belt + suspenders for browsers that
       honour one but not the other). */
    scroll-padding-top: 88px;
  }
  /* At peek, hide the chat thread so the sheet shows just chips + composer. */
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane[data-sheet-state="peek"] .builder-chat-body {
    display: none;
  }
  /* At MINIMIZED — only the drag handle is visible. The tradie pulls the
     sheet down to this state to see the full site preview unobstructed.
     A tap on the handle brings it back to peek. */
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane[data-sheet-state="minimized"] .builder-chat-tabs,
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane[data-sheet-state="minimized"] .builder-chat-body,
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane[data-sheet-state="minimized"] .builder-sections-body,
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane[data-sheet-state="minimized"] .builder-input-wrap,
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane[data-sheet-state="minimized"] .builder-suggestion-row,
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane[data-sheet-state="minimized"] .builder-refine-row {
    display: none !important;
  }
  /* Give the handle a slightly more visible grip in minimized state — it's
     the only affordance the user has to bring the sheet back up. */
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane[data-sheet-state="minimized"] .builder-sheet-handle__grip {
    opacity: 0.5;
    width: 44px;
  }
  /* Sections body — visible only in half/full AND when the Sections tab
     is active (toggled via data-active-tab on the chat-pane by the JS at
     dashboard-v2-page-builder.js:~530). */
  .builder-shell--centered[data-builder-state="ready"] .builder-sections-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 14px 18px;
    -webkit-overflow-scrolling: touch;
  }
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane[data-sheet-state="peek"] .builder-sections-body,
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane:not([data-active-tab="sections"]) .builder-sections-body {
    display: none !important;
  }
  /* When Sections or Publish tab is active, hide the chat thread + chips
     + composer so the active body panel has the sheet to itself. The
     composer + refine row are explicitly hidden so the user doesn't try
     to send messages from the Publish settings panel. */
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane[data-active-tab="sections"] .builder-chat-body,
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane[data-active-tab="sections"] .builder-suggestion-row,
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane[data-active-tab="publish"] .builder-chat-body,
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane[data-active-tab="publish"] .builder-suggestion-row,
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane[data-active-tab="publish"] .builder-input-wrap {
    display: none !important;
  }
  /* Publish body — sibling of chat-body / sections-body. Shows in
     half/full when [data-active-tab="publish"]. */
  .builder-shell--centered[data-builder-state="ready"] .builder-publish-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 28px 20px 32px;
    display: flex;
    flex-direction: column;
    gap: 28px;
    -webkit-overflow-scrolling: touch;
  }
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane[data-sheet-state="peek"] .builder-publish-body,
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane[data-sheet-state="minimized"] .builder-publish-body,
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane:not([data-active-tab="publish"]) .builder-publish-body {
    display: none !important;
  }
  /* Publish body — inner sections. Premium minimal styling matching the
     Claude/Anthropic clean-on-cream aesthetic (Phase 14h restyle 2026-05-19).
     Layout: hero URL → collapsed subdomain editor → status+publish action →
     Pro upgrade card. Generous vertical rhythm, URL is the hero, status is
     inline with the primary action. */

  /* === 1. Hero URL card ===
     The published URL is the panel's hero: full-width, large monospace.
     The entire row is tappable — copy-to-clipboard happens on tap. No
     separate "Live at" label. */
  .builder-publish-hero {
    margin: 0;
  }
  .builder-publish-hero__copy {
    appearance: none;
    width: 100%;
    border: 1px solid var(--hairline);
    background: var(--paper);
    border-radius: 16px;
    padding: 22px 20px;
    display: flex;
    align-items: center;
    gap: 14px;
    text-align: left;
    font: inherit;
    cursor: pointer;
    color: var(--ink);
    box-shadow: 0 1px 2px rgba(31, 22, 17, 0.03);
    transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.06s ease;
    -webkit-tap-highlight-color: transparent;
  }
  .builder-publish-hero__copy:active {
    transform: scale(0.99);
  }
  .builder-publish-hero__copy:hover {
    border-color: var(--hairline-2);
    box-shadow: 0 3px 10px rgba(31, 22, 17, 0.05);
  }
  .builder-publish-hero__url {
    flex: 1 1 auto;
    min-width: 0;
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, "Roboto Mono", monospace;
    font-size: 1.02rem;
    font-weight: 500;
    color: var(--ink);
    letter-spacing: -0.01em;
    line-height: 1.35;
    /* break-all so a long subdomain wraps cleanly inside the hero card
       rather than overflowing horizontally. */
    word-break: break-all;
  }
  .builder-publish-hero__copy-icon {
    flex-shrink: 0;
    color: var(--ink-mute);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px; height: 32px;
    border-radius: 8px;
    background: var(--cream-2, transparent);
    transition: color 0.15s ease, background 0.15s ease;
  }
  .builder-publish-hero__copy:hover .builder-publish-hero__copy-icon {
    color: var(--ink);
  }
  .builder-publish-hero__copy-hint {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden; clip: rect(0,0,0,0);
    white-space: nowrap; border: 0;
  }
  /* Copied state — flashes briefly via the same .is-copied class JS uses. */
  .builder-publish-hero__copy.is-copied .builder-publish-hero__copy-icon {
    color: #2e7d32;
    background: rgba(46, 125, 50, 0.08);
  }

  /* === 2. Subdomain editor ===
     Collapsed-by-default ghost row. Tap toggles is-editing to reveal the
     input + status. Keeps the panel quiet pre-edit. */
  .builder-publish-editor {
    display: flex;
    flex-direction: column;
    gap: 12px;
  }
  .builder-publish-editor__toggle {
    appearance: none;
    border: 0;
    background: transparent;
    padding: 0;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    color: var(--ink-mute);
    font: inherit;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .builder-publish-editor__toggle-label {
    font-size: 0.74rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    font-weight: 600;
    color: var(--ink-mute);
  }
  .builder-publish-editor__toggle-action {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 0.86rem;
    font-weight: 500;
    color: var(--ink-mute);
    transition: color 0.15s ease;
  }
  .builder-publish-editor__toggle:hover .builder-publish-editor__toggle-action {
    color: var(--ink);
  }
  .builder-publish-editor__toggle-chev {
    transition: transform 0.2s ease;
  }
  .builder-publish-editor.is-editing .builder-publish-editor__toggle-chev {
    transform: rotate(90deg);
  }
  .builder-publish-editor.is-editing .builder-publish-editor__toggle-action-text::after {
    content: ' done';
  }
  .builder-publish-editor.is-editing .builder-publish-editor__toggle-action-text {
    color: var(--ink);
  }
  .builder-publish-editor__panel {
    display: flex;
    flex-direction: column;
    gap: 8px;
  }
  .builder-publish-editor .builder-domain-input-wrap[data-mobile-publish] {
    display: flex;
    align-items: center;
    border: 1px solid var(--hairline-2);
    border-radius: 12px;
    padding: 12px 14px;
    background: var(--paper);
    gap: 0;
    font-size: 0.98rem;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
  }
  .builder-publish-editor .builder-domain-input-wrap[data-mobile-publish]:focus-within {
    border-color: var(--ink);
    box-shadow: 0 0 0 3px rgba(31, 22, 17, 0.06);
  }
  .builder-publish-editor .builder-domain-input {
    border: 0;
    outline: 0;
    background: transparent;
    flex: 1 1 auto;
    min-width: 0;
    color: var(--ink);
    font: inherit;
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, "Roboto Mono", monospace;
    font-size: 0.96rem;
    padding: 0;
  }
  .builder-publish-editor .builder-domain-suffix {
    color: var(--ink-mute);
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, "Roboto Mono", monospace;
    font-size: 0.94rem;
    flex-shrink: 0;
    padding-left: 4px;
  }
  .builder-publish-editor .builder-domain-status {
    font-size: 0.82rem;
    color: var(--ink-mute);
    padding: 2px 2px;
  }
  .builder-publish-editor .builder-domain-status.is-available { color: #2e7d32; }
  .builder-publish-editor .builder-domain-status.is-taken,
  .builder-publish-editor .builder-domain-status.is-invalid,
  .builder-publish-editor .builder-domain-status.is-reserved { color: #b34740; }

  /* === 3. Status row + Publish action ===
     Live indicator + last-published time sit ABOVE the publish button as
     inline meta — replaces the separate meta-card. */
  .builder-publish-action {
    display: flex;
    flex-direction: column;
    gap: 12px;
  }
  .builder-publish-action__status {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.86rem;
    color: var(--ink-mute);
  }
  .builder-publish-action__dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #2e7d32;
    box-shadow: 0 0 0 4px rgba(46, 125, 50, 0.12);
    flex-shrink: 0;
  }
  .builder-publish-action__status-label {
    font-weight: 600;
    color: var(--ink);
    letter-spacing: -0.005em;
  }
  .builder-publish-action__status-sep {
    color: var(--ink-faint);
  }
  .builder-publish-action__when {
    color: var(--ink-mute);
  }
  /* When the site has never published, hide the status row entirely (JS
     toggles via [data-builder-publish-meta] hidden on the hero card; the
     status row uses the same is-unpublished modifier set by JS).  */
  .builder-publish-action.is-unpublished .builder-publish-action__status {
    display: none;
  }

  .builder-publish-btn--mob {
    appearance: none;
    border: 0;
    background: var(--ink);
    color: var(--paper);
    padding: 16px 18px;
    border-radius: 14px;
    font: inherit;
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    box-shadow: 0 1px 2px rgba(31, 22, 17, 0.08), 0 4px 16px rgba(31, 22, 17, 0.04);
    transition: opacity 0.15s ease, background 0.15s ease, transform 0.06s ease, box-shadow 0.15s ease;
  }
  .builder-publish-btn--mob:hover:not(:disabled) {
    box-shadow: 0 2px 4px rgba(31, 22, 17, 0.12), 0 6px 22px rgba(31, 22, 17, 0.08);
  }
  .builder-publish-btn--mob:active:not(:disabled) {
    transform: scale(0.985);
  }
  .builder-publish-btn--mob:disabled {
    opacity: 0.45;
    cursor: not-allowed;
    box-shadow: none;
  }
  .builder-publish-btn--mob.is-published {
    background: var(--paper);
    color: var(--ink);
    border: 1px solid var(--hairline-2);
    box-shadow: none;
  }
  .builder-publish-btn--mob .builder-publish-dot {
    width: 8px; height: 8px;
    border-radius: 50%;
    background: currentColor;
    opacity: 0.85;
  }

  /* === Generic copy button (used by the hero — but also kept here for
     any future inline copy affordance). === */
  .builder-publish-copy {
    appearance: none;
    border: 0;
    background: transparent;
    color: var(--ink-mute);
    padding: 4px;
    cursor: pointer;
    border-radius: 6px;
    flex-shrink: 0;
    transition: color 0.15s ease, background 0.15s ease;
  }
  .builder-publish-copy:hover { color: var(--ink); background: var(--hairline); }
  .builder-publish-copy.is-copied { color: #2e7d32; }

  /* === 4. Pro upgrade card ===
     Distinct from the rest — accent edge + warm gradient surface so it
     reads as an opportunity, not a row. */
  .builder-publish-custom-row {
    appearance: none;
    position: relative;
    border: 1px solid var(--hairline-2);
    background:
      linear-gradient(135deg,
        rgba(255, 255, 255, 0) 0%,
        rgba(245, 158, 11, 0.04) 100%),
      var(--paper);
    padding: 18px 20px;
    border-radius: 16px;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 14px;
    text-align: left;
    font: inherit;
    transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.06s ease;
    overflow: hidden;
    -webkit-tap-highlight-color: transparent;
  }
  .builder-publish-custom-row::before {
    /* subtle accent edge on the left */
    content: '';
    position: absolute;
    left: 0; top: 18%; bottom: 18%;
    width: 3px;
    border-radius: 0 3px 3px 0;
    background: linear-gradient(180deg, var(--orange, #d97706), color-mix(in srgb, var(--orange, #d97706) 60%, transparent));
    opacity: 0.85;
  }
  .builder-publish-custom-row:hover {
    border-color: var(--hairline-3);
    box-shadow: 0 4px 18px rgba(217, 119, 6, 0.08);
  }
  .builder-publish-custom-row:active {
    transform: scale(0.99);
  }
  .builder-publish-custom-row__icon {
    flex-shrink: 0;
    width: 38px; height: 38px;
    border-radius: 10px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--orange, #d97706);
    background: color-mix(in srgb, var(--orange, #d97706) 12%, transparent);
  }
  .builder-publish-custom-row__main { flex: 1 1 auto; min-width: 0; }
  .builder-publish-custom-row__title {
    font-size: 0.96rem;
    font-weight: 600;
    color: var(--ink);
    letter-spacing: -0.01em;
  }
  .builder-publish-custom-row__sub {
    font-size: 0.8rem;
    color: var(--ink-mute);
    margin-top: 3px;
  }
  .builder-publish-custom-row__chev {
    flex-shrink: 0;
    color: var(--ink-mute);
    transition: transform 0.15s ease, color 0.15s ease;
  }
  .builder-publish-custom-row:hover .builder-publish-custom-row__chev {
    color: var(--ink);
    transform: translateX(2px);
  }

  /* === 5. Variant A — "Settings-page rows" (founder-selected 2026-05-19) ===
     The hero URL card + collapsed subdomain editor + status row stack into a
     unified bordered card with hairline dividers between rows (iOS Settings /
     Stripe Dashboard feel). The Publish button + Pro-upgrade row live
     OUTSIDE the card so they read as separate primary actions, not part of
     the info group.

     These rules override the Phase 14h baseline (sections 1-4 above) by
     source-order specificity. They live in the same @media (max-width:720px)
     block so they only apply to the mobile bottom-sheet Publish tab. */

  /* Tighten the panel padding and zero out the inter-section gap so the
     three info rows visually merge into one unified card. Selector
     specificity-matched to the .builder-shell--centered[data-builder-
     state="ready"] base rule at line ~7110 so source order wins. */
  .builder-shell--centered[data-builder-state="ready"] .builder-publish-body {
    padding: 24px 20px 32px;
    gap: 0;
  }
  /* Strip stray margins on the three wrapper rows. */
  .builder-publish-hero,
  .builder-publish-editor,
  .builder-publish-action {
    margin: 0;
  }
  /* Hero row — flatten the standalone card into the top row of the
     unified card. No shadow, no radius (handled by the unified wrapper),
     calmer hover. */
  .builder-publish-hero__copy {
    border: 0;
    border-radius: 0;
    padding: 14px 16px;
    box-shadow: none;
    background: var(--paper);
    gap: 12px;
  }
  .builder-publish-hero__copy:hover {
    background: var(--cream-2);
    box-shadow: none;
  }
  .builder-publish-hero__copy:active {
    transform: none;
    box-shadow: none;
  }
  .builder-publish-hero__url {
    font-size: 0.92rem;
    font-weight: 500;
    letter-spacing: -0.005em;
  }
  .builder-publish-hero__copy-icon {
    width: 28px;
    height: 28px;
    background: transparent;
  }
  /* Editor row. */
  .builder-publish-editor { gap: 0; }
  .builder-publish-editor__toggle {
    padding: 14px 16px;
    background: var(--paper);
    color: var(--ink);
  }
  .builder-publish-editor__toggle-label {
    color: var(--ink);
    font-size: 0.92rem;
    font-weight: 500;
    text-transform: none;
    letter-spacing: 0;
  }
  .builder-publish-editor__toggle-action { color: var(--ink-mute); }
  .builder-publish-editor__panel {
    padding: 0 16px 14px;
    background: var(--paper);
  }
  /* Status row — last row of the unified card. Hairline borders on all
     sides + bottom radius so the card visually "closes" before the
     publish button. */
  .builder-publish-action {
    gap: 0;
    border: 0;
  }
  .builder-publish-action__status {
    padding: 14px 16px;
    background: var(--paper);
    margin: 0;
    font-size: 0.86rem;
    gap: 8px;
    justify-content: flex-start;
    border: 1px solid var(--hairline);
    border-bottom-left-radius: 14px;
    border-bottom-right-radius: 14px;
  }
  /* Unified card chrome on hero + editor — outer border, top-rounded. */
  .builder-publish-hero,
  .builder-publish-editor {
    border-left: 1px solid var(--hairline);
    border-right: 1px solid var(--hairline);
  }
  .builder-publish-hero {
    border-top: 1px solid var(--hairline);
    border-top-left-radius: 14px;
    border-top-right-radius: 14px;
    overflow: hidden;
  }
  .builder-publish-editor {
    border-top: 1px solid var(--hairline);
  }
  /* Publish button — separate, full-width primary BELOW the unified
     card. Margined off so it doesn't crush the card edge. */
  .builder-publish-btn--mob {
    margin-top: 18px;
    margin-bottom: 18px;
    width: 100%;
    border-radius: 12px;
    padding: 16px 18px;
    background: var(--ink);
    color: var(--paper);
    font-weight: 600;
    font-size: 0.98rem;
    letter-spacing: -0.005em;
    box-shadow:
      0 1px 2px rgba(31, 22, 17, 0.08),
      0 6px 18px rgba(31, 22, 17, 0.06);
  }
  /* Custom-domain row — calm settings-row look. Drops the orange edge
     and warm gradient so it sits as a quiet sibling action, not a
     marketing CTA. */
  .builder-publish-custom-row {
    border-radius: 14px;
    padding: 16px 18px;
    background: var(--paper);
    border: 1px solid var(--hairline);
    box-shadow: none;
  }
  .builder-publish-custom-row::before { display: none; }
  .builder-publish-custom-row__icon {
    background: var(--cream-2);
    color: var(--ink-mute);
    width: 32px;
    height: 32px;
  }
  .builder-publish-custom-row__title { font-size: 0.94rem; }
  .builder-publish-custom-row__sub { font-size: 0.78rem; }

  /* Tab dot — small indicator next to "Publish" tab label when changes
     are pending. JS toggles via [data-builder-publish-tab-dot] visibility. */
  .builder-chat-tab-dot {
    display: inline-block;
    width: 6px; height: 6px;
    border-radius: 50%;
    background: var(--ink);
    margin-left: 6px;
    vertical-align: middle;
    opacity: 0;
    transition: opacity 0.15s ease;
  }
  .builder-chat-tab-dot.is-on { opacity: 1; }
  /* Hide the legacy empty-hero + instruction in EVERY ready state —
     they're chrome for the centred/empty pre-build layout and are
     irrelevant once the sheet exists. Without this they cause
     content overflow that pushes the drag handle past the sheet's
     top edge. */
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane .builder-empty-hero,
  .builder-shell--centered[data-builder-state="ready"] .builder-chat-pane .builder-instruction {
    display: none !important;
  }

  /* Input wrap (refine chips + composer) sits at the bottom of the sheet
     and stays visible at every sheet height. */
  .builder-shell--centered[data-builder-state="ready"] .builder-input-wrap {
    /* 2026-05-24: dropped horizontal 16px padding (sheet-era leftover).
       chat-pane already owns the 16px outer gutter — stacking another
       16px here was shrinking the composer by 32px the moment state
       flipped to ready. */
    padding: 8px 0 max(16px, env(safe-area-inset-bottom)) !important;
    flex-shrink: 0;
    width: 100% !important;
    max-width: none !important;
    box-sizing: border-box;
  }
  .builder-shell--centered[data-builder-state="ready"] .builder-input-box {
    width: 100% !important;
    max-width: none !important;
    margin: 0 !important;
    box-sizing: border-box;
  }

  /* Refine chips: keep horizontally scrollable on narrow widths. */
  .builder-shell--centered[data-builder-state="ready"] .builder-refine-row {
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding: 0 4px 10px;
    margin: 0 -4px 6px;
    /* Subtle fade at the right edge so the user knows there's more to scroll. */
    mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 18px), transparent 100%);
    -webkit-mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 18px), transparent 100%);
  }
  .builder-shell--centered[data-builder-state="ready"] .builder-refine-row::-webkit-scrollbar {
    display: none;
  }
  .builder-shell--centered[data-builder-state="ready"] .builder-refine-chip {
    flex-shrink: 0;
    white-space: nowrap;
  }

  /* Hide the empty-state suggestion row in ready state on mobile. */
  .builder-shell--centered[data-builder-state="ready"] .builder-suggestion-row {
    display: none !important;
  }
}

.builder-pane {
  border: 1px solid var(--hairline);
  border-radius: 12px;
  background: var(--cream);
  display: flex; flex-direction: column;
  overflow: hidden;
}
/* Phase 13: "+ New chat" pane head. Hidden by default; revealed only
   once the chat has started so the empty-hero stays clean on first paint. */
.builder-pane-head {
  display: none;
  padding: 10px 14px 0;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
}
/* Publish button — sits to the right of "New chat" in the pane head.
   Filled with the warm-stone-dark ink so it reads as the primary
   action of the page (the user's end goal: get my site live). */
.builder-publish-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--ink);
  border: 1px solid var(--ink);
  border-radius: 999px;
  padding: 5px 14px;
  font-family: inherit;
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--paper, #fff);
  cursor: pointer;
  transition: opacity 0.15s, transform 0.15s;
}
.builder-publish-btn:hover { opacity: 0.85; transform: translateY(-1px); }
.builder-publish-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

/* ── Editable domain bar (preview-head, next to Publish button) ────
   Wired in dashboard-v2-page.html via shellInitDomainBar(). Input
   debounces against GET /api/builder/check-slug; Publish button
   POSTs to /api/builder/publish with the current slug. */
.builder-domain-bar { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.builder-domain-input-wrap {
  display: inline-flex; align-items: center; gap: 2px;
  padding: 4px 9px; background: var(--paper, #fff);
  border: 1px solid var(--hairline, #E7E2DA);
  border-radius: 999px;
  font-size: 0.74rem; min-width: 0; max-width: 320px;
  transition: border-color 0.15s, background 0.15s;
}
.builder-domain-input-wrap:focus-within { border-color: var(--ink); background: var(--cream, #FBFBFB); }
.builder-domain-prefix, .builder-domain-suffix {
  color: var(--ink-soft, rgba(31,22,17,0.55));
  white-space: nowrap; user-select: none;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.74rem;
}
.builder-domain-input {
  width: 140px; min-width: 60px; border: 0; background: transparent;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.74rem; font-weight: 600; color: var(--ink);
  padding: 2px 0; outline: none;
}
.builder-domain-input::placeholder { color: rgba(31, 22, 17, 0.32); font-weight: 400; }
.builder-domain-input[readonly] { color: var(--ink-mute, #6B5A4C); cursor: default; }
.builder-domain-status {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 0.7rem; font-weight: 500; padding: 2px 8px; border-radius: 999px;
  white-space: nowrap; line-height: 1.4;
}
.builder-domain-status[hidden] { display: none; }
.builder-domain-status.is-checking { background: var(--cream-2, #F4F4F4); color: var(--ink-mute, #6B5A4C); }
.builder-domain-status.is-checking::before {
  content: ''; display: inline-block; width: 8px; height: 8px;
  border-radius: 50%; border: 1.5px solid rgba(31, 22, 17, 0.2);
  border-top-color: var(--ink);
  animation: bdr-spin 0.7s linear infinite;
}
.builder-domain-status.is-available { background: rgba(44, 107, 44, 0.12); color: #2c6b2c; }
.builder-domain-status.is-taken,
.builder-domain-status.is-invalid,
.builder-domain-status.is-reserved,
.builder-domain-status.is-error { background: #f5dcd9; color: #7c2218; }
.builder-domain-suggestion {
  text-decoration: underline; text-underline-offset: 2px;
  cursor: pointer; font-weight: 600; margin-left: 4px;
}
@keyframes bdr-spin { from { transform: rotate(0); } to { transform: rotate(360deg); } }

/* Extended publish-btn states for the domain-bar flow */
.builder-publish-btn .builder-publish-dot {
  width: 6px; height: 6px; border-radius: 50%; background: transparent; display: inline-block;
}
.builder-publish-btn.is-publishing { cursor: wait; opacity: 0.85; }
.builder-publish-btn.is-published { background: #2c6b2c; border-color: #2c6b2c; cursor: default; }
.builder-publish-btn.is-published .builder-publish-dot {
  background: #6ce58a;
  box-shadow: 0 0 0 2px rgba(108, 229, 138, 0.25);
}
.builder-chat-pane:has(.builder-chat-body:not(:empty)) .builder-pane-head {
  display: flex;
}
.builder-newchat-btn {
  display: inline-flex; align-items: center; gap: 6px;
  background: transparent;
  border: 1px solid var(--hairline-2);
  border-radius: 999px;
  padding: 5px 12px;
  font-family: inherit;
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--ink-mute);
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.builder-newchat-btn:hover {
  background: var(--cream-2);
  color: var(--ink);
  border-color: var(--hairline-3);
}
.builder-chat-head {
  padding: 16px 18px;
  border-bottom: 1px solid var(--hairline);
  display: flex; align-items: baseline; justify-content: space-between;
}
.builder-chat-eye {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ink-faint); font-weight: 600;
}
.builder-chat-counter {
  font-size: 0.78rem; color: var(--ink-mute);
}
.builder-chat-body {
  flex: 1; overflow-y: auto;
  /* 2026-05-16 v4: bumped bottom padding so the last message has
     breathing room above the composer. */
  padding: 16px 18px 32px;
  display: flex; flex-direction: column; gap: 14px;
}
.builder-msg {
  /* Widened from 85% -> 96% — AI messages (which contain the brief-form
     fields) need to reach close to the screen edges. User pills keep
     the lower 92% override below since they auto-size to content. */
  max-width: 96%; padding: 10px 12px;
  border-radius: 10px;
  font-size: 0.88rem; line-height: 1.45;
}
.builder-msg.is-user {
  align-self: flex-end;
  /* Warm-stone grey, Claude-style — softer than the near-black --ink the
     bubble used to ship with. Founder 2026-05-16. */
  background: var(--cream-3); color: var(--ink);
}
.builder-msg.is-ai {
  align-self: flex-start;
  background: var(--cream-2); color: var(--ink);
}

/* 2026-05-24 handoff card — polished pass after founder feedback
   ("final your sites ready thing needs fixing as well styling
   wise"). Italic-serif title matches the empty-state Instrument-
   Serif family; tighter overall surface (cream gradient, deeper
   shadow), beefier 44px button row with the primary Preview pill
   filling 60% of the width so it leads the eye. Card width opened
   up to ~400px so the title + 2-button row reads as one unit. */
/* 2026-05-24 (rev 4) — Lovable-parity styling. Subtle brand-orange
   halo (1px outline + 4px soft glow) gives the card a "freshly-
   landed important thing" feel without being loud. Pure white
   surface, 16px border-radius (slightly less than 18 for a more
   utility-card feel), three-layer ink shadow lifts it off the
   chat thread. Specificity anchored via parent row + !important
   on every surface prop to beat the .builder-msg-row.is-ai
   .builder-msg override at L4091 which strips bg + padding. */
.builder-msg-row.is-ai .builder-msg.builder-handoff {
  background: #FFFFFF !important;
  border: 1px solid transparent !important;
  border-radius: 16px !important;
  padding: 20px 20px 18px !important;
  max-width: 400px !important;
  box-shadow:
    0 0 0 1px rgba(255, 91, 0, 0.18),
    0 0 0 4px rgba(255, 91, 0, 0.05),
    0 2px 6px rgba(31, 22, 17, 0.04),
    0 12px 32px -10px rgba(31, 22, 17, 0.14) !important;
  align-self: flex-start !important;
}
/* 2026-05-24 reveal animation — handoff card + site-info land softly
   after the 30s build animation completes instead of snapping in.
   Slower curve than the default builderRowEnter (700ms vs 400ms) so
   the post-build moment feels deliberate; site-info delays 220ms so
   it cascades under the card rather than landing at the same beat.
   builderRowEnter on .builder-msg-row is overridden here. */
.builder-msg-row[data-builder-handoff] {
  animation: bsBuildRevealCard 700ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
.builder-msg-row[data-builder-siteinfo] {
  animation: bsBuildRevealProse 700ms cubic-bezier(0.16, 1, 0.3, 1) 220ms both;
}
@keyframes bsBuildRevealCard {
  0%   { opacity: 0; transform: translateY(14px) scale(0.985); }
  100% { opacity: 1; transform: translateY(0)    scale(1);    }
}
@keyframes bsBuildRevealProse {
  0%   { opacity: 0; transform: translateY(10px); }
  100% { opacity: 1; transform: translateY(0);    }
}
@media (prefers-reduced-motion: reduce) {
  .builder-msg-row[data-builder-handoff],
  .builder-msg-row[data-builder-siteinfo] { animation: none !important; }
}
.builder-handoff__head {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 20px;
}
.builder-handoff__title {
  font-family: inherit;
  font-style: normal;
  font-weight: 600;
  font-size: 1.125rem;
  letter-spacing: -0.012em;
  line-height: 1.2;
  color: var(--ink, #1F1611);
}
.builder-handoff__lede {
  font-size: 0.9rem;
  color: var(--ink-mute, #6B5A4C);
  line-height: 1.5;
}
.builder-handoff__actions {
  display: flex;
  gap: 10px;
}
.builder-handoff__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 44px;
  padding: 0 18px;
  border: 1px solid rgba(31, 22, 17, 0.12);
  background: #FFFFFF;
  color: var(--ink, #1F1611);
  border-radius: 999px;
  font-family: inherit;
  font-size: 0.9rem;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s, transform 0.14s cubic-bezier(0.2, 0, 0, 1);
  -webkit-tap-highlight-color: transparent;
}
/* 2026-05-24: equal-width buttons (founder ask). Was asymmetric
   1.6/1 so Preview leads, but the imbalance read as unfinished.
   Equal flex keeps the same visual weight; the dark vs light fill
   still carries the primary/secondary hierarchy. */
.builder-handoff__btn { flex: 1 1 0; }
.builder-handoff__btn:hover { background: #FAFAFA; }
.builder-handoff__btn:active { transform: translateY(0.5px); }
.builder-handoff__btn.is-primary {
  background: linear-gradient(180deg, #2A211B 0%, #1F1611 100%);
  color: var(--cream, #FAFAF8);
  border-color: rgba(0, 0, 0, 0.55);
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.18),
    0 4px 10px -2px rgba(0, 0, 0, 0.22),
    inset 0 1px 0 rgba(255, 255, 255, 0.10);
}
.builder-handoff__btn.is-primary:hover {
  background: linear-gradient(180deg, #332923 0%, #241B16 100%);
}

/* ===========================================================================
   Brain-chat cards (Phase 9 unification — 2026-05-29)

   Three new card types rendered inline in the refine-mode AI builder chat
   when the server delegates to /api/brain-chat:

     .builder-brain-card--confirm   — confirm-tier staged patch (Apply / Not now)
     .builder-brain-card--applied   — confirm card transitioned after Apply
     .builder-brain-card--locked    — manual-tier locked field message
     .builder-brain-card--declined  — confirm card after Not now
     .builder-brain-card--error     — Apply failed

   Same surface recipe as the rest of the chat: solid white background,
   hairline border, soft neutral drop shadow (no peach — that's reserved
   for .builder-handoff). State signalled only by a small coloured dot.

   Pill buttons use the same .builder-handoff__btn recipe (dark linear-
   gradient primary + flat-white secondary).
   ============================================================================ */
/* Lovable-faithful card (founder 2026-05-30 sandbox port). White
   surface, 1.5px tier-tinted border, 14px radius, 14/16/12 padding.
   Founder 2026-05-30 v2: cards now stretch to a uniform width
   (was shrinking to content, causing "Update status" cards to be
   narrower than "Updated notes" cards in the same turn). */
.builder-msg-row.is-ai .builder-msg.builder-brain-card {
  background: #FFFFFF !important;
  border: 1.5px solid #E0E3F4 !important;  /* default lavender hint */
  border-radius: 14px !important;
  padding: 14px 16px 12px !important;
  width: 100% !important;
  max-width: 440px !important;
  min-width: 0 !important;
  box-shadow: none !important;
  align-self: stretch !important;
  box-sizing: border-box !important;
  /* R6 clip-path sweep reveal — card "wipes in" top to bottom +
     6px translateY for weight. */
  animation: bsBrainCardReveal 580ms cubic-bezier(0.65, 0, 0.35, 1) both;
}
/* Per-tier border tint (replaces the coloured dot signal). */
.builder-brain-card--applied  { border-color: rgba(31, 122, 77, 0.30) !important; }
.builder-brain-card--confirm  { border-color: rgba(183, 129, 4, 0.30) !important; }
.builder-brain-card--locked,
.builder-brain-card--declined { border-color: rgba(31, 22, 17, 0.12) !important; }
.builder-brain-card--error    { border-color: rgba(192, 57, 43, 0.40) !important; }
/* Stagger consecutive brain-cards (auto + confirm + locked landing in
   one turn) so they cascade rather than snapping in together. The
   styled selector targets the 2nd through 4th cards in immediate
   succession; client appends them in apply → confirm → locked order. */
.builder-msg-row.is-ai + .builder-msg-row.is-ai > .builder-brain-card {
  animation-delay: 120ms;
}
.builder-msg-row.is-ai + .builder-msg-row.is-ai + .builder-msg-row.is-ai > .builder-brain-card {
  animation-delay: 240ms;
}
.builder-msg-row.is-ai + .builder-msg-row.is-ai + .builder-msg-row.is-ai + .builder-msg-row.is-ai > .builder-brain-card {
  animation-delay: 360ms;
}
/* R6 — clip-path sweep. Card reveals from top to bottom via
   animated inset rect, paired with a gentle 6px translateY so the
   sweep feels weighted rather than purely geometric. */
@keyframes bsBrainCardReveal {
  0%   { opacity: 0; clip-path: inset(0 0 100% 0); transform: translateY(6px); }
  100% { opacity: 1; clip-path: inset(0 0 0 0);   transform: translateY(0);   }
}
@media (prefers-reduced-motion: reduce) {
  .builder-msg-row.is-ai .builder-msg.builder-brain-card { animation: none !important; }
}
/* ── Lovable card structure: head / title / subtitle / bookmark
   / divider / body / actions. Replaces the dot-label + patch-list
   pattern from the previous V6 cards. */
.builder-brain-card__head {
  display: flex; align-items: flex-start; gap: 10px;
  padding-bottom: 12px;
  border-bottom: 1px solid rgba(31, 22, 17, 0.05);
}
.builder-brain-card__title {
  flex: 1;
  font-size: 15.5px;
  font-weight: 600;
  color: var(--ink, #1F1611);
  letter-spacing: -0.005em;
  line-height: 1.35;
}
.builder-brain-card__subtitle {
  font-size: 12.5px;
  color: var(--ink-mute, #6B5A4C);
  font-weight: 500;
  margin-top: 2px;
}
.builder-brain-card__bookmark {
  background: transparent; border: 0;
  color: var(--ink-faint, #B8AB9D);
  cursor: pointer; padding: 2px;
  flex-shrink: 0;
}
.builder-brain-card__bookmark:hover { color: var(--ink, #1F1611); }
.builder-brain-card__bookmark svg { width: 16px; height: 16px; }

/* LEGACY: kept so any stragglers still using the old dot-label
   structure don't blow up the layout. Both can co-exist. */
.builder-brain-card__label {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink-soft, #8A7866);
  display: none;  /* hidden in favour of __head/__title above */
}
.builder-brain-card__list {
  list-style: none;
  padding: 0;
  margin: 6px 0 14px;
}
.builder-brain-card__list li {
  padding: 8px 0;
  color: var(--ink, #1F1611);
  font-size: 14.5px;
  line-height: 1.5;
  border-bottom: 1px solid rgba(31, 22, 17, 0.06);
}
.builder-brain-card__list li:last-child {
  border-bottom: none;
  padding-bottom: 0;
}
.builder-brain-card__list li b {
  font-weight: 500;
  color: var(--ink, #1F1611);
}
.builder-brain-card__body {
  color: var(--ink, #1F1611);
  font-size: 14px;
  padding: 8px 0 4px;
  margin: 0;
  line-height: 1.5;
}
.builder-brain-card__body b { font-weight: 500; }
.builder-brain-card__meta {
  color: var(--ink-mute, #6B5A4C);
  font-size: 13px;
  margin: -6px 0 12px;
  line-height: 1.5;
}
.builder-brain-card__buttons {
  display: flex;
  gap: 8px;
  padding-top: 12px;
  margin-top: 0;
}
/* Pill buttons mirror .builder-handoff__btn. */
/* Default button — light 3D gradient (Preview / Not now). Founder
   2026-05-30: both buttons in the V6 confirm card now use a 3D
   gradient. Preview is the WHITE→cream gradient with inset highlight
   + soft drop shadow; Apply (below) is the DARK gradient. Reads as a
   matched 3D pair instead of "flat outline + dark fill". */
/* Lovable-style action button — 36px tall, 10px radius (rectangle
   not pill), light 3D gradient. Pairs with .builder-brain-card__btn--primary
   (dark 3D) on confirm cards. Founder 2026-05-30. */
.builder-brain-card__btn {
  flex: 1 1 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 36px;
  padding: 0 16px;
  border: 1px solid rgba(31, 22, 17, 0.10);
  background: linear-gradient(180deg, #FFFFFF 0%, #F5F5F5 100%);
  color: var(--ink, #1F1611);
  border-radius: 10px;
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.9),
    inset 0 -1px 0 rgba(31, 22, 17, 0.04),
    0 1px 2px rgba(31, 22, 17, 0.06),
    0 2px 6px -2px rgba(31, 22, 17, 0.08);
  transition: background 0.12s, border-color 0.12s, transform 0.14s cubic-bezier(0.2, 0, 0, 1);
  -webkit-tap-highlight-color: transparent;
}
.builder-brain-card__btn:hover:not(:disabled) {
  background: linear-gradient(180deg, #FFFFFF 0%, #EAEAEA 100%);
  border-color: rgba(31, 22, 17, 0.18);
}
.builder-brain-card__btn:active:not(:disabled) { transform: translateY(0.5px); }
.builder-brain-card__btn:disabled { opacity: 0.35; cursor: default; }
.builder-brain-card__btn--primary {
  background: linear-gradient(180deg, #2A211B 0%, #1F1611 100%);
  color: var(--cream, #FAFAF8);
  border-color: rgba(0, 0, 0, 0.55);
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.18),
    0 4px 10px -2px rgba(0, 0, 0, 0.22),
    inset 0 1px 0 rgba(255, 255, 255, 0.10);
}
.builder-brain-card__btn--primary:hover:not(:disabled) {
  background: linear-gradient(180deg, #332923 0%, #241B16 100%);
}

/* ===========================================================================
   V6 design — chosen 2026-05-29. Auto edits collapse into a dark-ink toast
   pill at the top of the turn; only confirm-tier gets a full card; locked
   becomes a quiet italic inline note. Shipping rules:
     - auto-tier  → .builder-brain-toast (dark pill, ✓ icon, Undo link)
     - confirm    → .builder-brain-card.builder-brain-card--confirm (existing,
                    lighter shadow override below)
     - locked     → .builder-brain-locked (italic grey inline)
   ============================================================================ */
.builder-msg-row.is-ai .builder-msg.builder-brain-toast {
  display: inline-flex !important;
  flex-wrap: nowrap !important;
  align-items: center !important;
  gap: 10px !important;
  padding: 10px 14px !important;
  background: var(--ink, #1F1611) !important;
  color: var(--cream, #FAFAF8) !important;
  border-radius: 999px !important;
  border: 0 !important;
  font-size: 13px !important;
  font-weight: 500 !important;
  line-height: 1.4 !important;
  box-shadow: 0 8px 24px -8px rgba(0, 0, 0, 0.4) !important;
  width: max-content !important;
  max-width: 100% !important;
  align-self: flex-start !important;
  animation: bsBrainToastIn 460ms cubic-bezier(0.16, 1, 0.3, 1) both !important;
}
@keyframes bsBrainToastIn {
  0%   { opacity: 0; transform: translateY(10px); }
  100% { opacity: 1; transform: translateY(0); }
}
.builder-brain-toast__check {
  width: 16px; height: 16px;
  border-radius: 50%;
  background: #1f7a4d;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--cream, #FAFAF8);
  font-size: 10.5px;
  font-weight: 700;
  flex-shrink: 0;
}
.builder-brain-toast__label {
  flex: 1 1 auto;
  min-width: 0;             /* allow shrinking below content size */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.builder-brain-toast__undo {
  flex-shrink: 0;           /* never shrink — Undo stays one word */
  white-space: nowrap;
  margin-left: 2px;
  background: transparent;
  border: 0;
  color: var(--ink-faint, #B8AB9D);
  font: inherit;
  font-size: 12px;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  cursor: pointer;
  padding: 4px 4px;
  -webkit-tap-highlight-color: transparent;
}
.builder-brain-toast__undo:hover:not(:disabled) { color: var(--cream, #FAFAF8); }
.builder-brain-toast__undo:disabled { opacity: 0.35; cursor: default; text-decoration: none; }
/* After undo: toast dims + check turns grey */
.builder-brain-toast.is-undone {
  opacity: 0.55;
}
.builder-brain-toast.is-undone .builder-brain-toast__check {
  background: var(--ink-faint, #B8AB9D);
}

/* Confirm card — same recipe as before but lighter shadow per V6 */
.builder-msg-row.is-ai .builder-msg.builder-brain-card--confirm {
  box-shadow: 0 4px 14px -6px rgba(31, 22, 17, 0.10) !important;
}
/* V6 confirm card uses ink (full black) for the label, not ink-soft */
.builder-brain-card--confirm .builder-brain-card__label {
  color: var(--ink, #1F1611);
}

/* Locked — italic inline note, no card chrome */
/* Edit-mode pill (composer toolbar). Replaces the square icon button
   — now an icon + word pill that opens a popover with all 3 modes. */
.builder-input-mode-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 32px;
  padding: 0 12px 0 10px;
  border-radius: 999px;
  border: 1px solid rgba(31, 22, 17, 0.10);
  /* 3D gradient (founder 2026-05-30) — matches the composer
     circle buttons recipe so the toolbar reads as a matched set. */
  background: linear-gradient(180deg, #FFFFFF 0%, #F5F5F5 100%);
  color: var(--ink, #1F1611);
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.9),
    inset 0 -1px 0 rgba(31, 22, 17, 0.04),
    0 1px 2px rgba(31, 22, 17, 0.06),
    0 2px 6px -2px rgba(31, 22, 17, 0.08);
  transition: background 0.12s, border-color 0.12s, transform 0.12s;
  -webkit-tap-highlight-color: transparent;
  white-space: nowrap;
}
.builder-input-mode-pill:hover {
  background: linear-gradient(180deg, #FFFFFF 0%, #EAEAEA 100%);
  border-color: rgba(31, 22, 17, 0.18);
}
.builder-input-mode-pill:active { transform: translateY(0.5px); }
.builder-input-mode-pill[aria-expanded="true"] {
  background: var(--ink, #1F1611);
  color: var(--cream, #FAFAF8);
  border-color: var(--ink, #1F1611);
}
.builder-input-mode-pill__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.builder-input-mode-pill__icon svg { width: 14px; height: 14px; }
.builder-input-mode-pill__label {
  font-weight: 500;
  letter-spacing: -0.005em;
  font-size: 13px;
}

/* Mode-pick popover. Lives directly on document.body so its
   stacking context isn't trapped by composer transforms. */
.builder-mode-popover {
  position: fixed;
  z-index: 9999;
  min-width: 240px;
  max-width: 320px;
  background: #FFFFFF;
  border: 1px solid rgba(31, 22, 17, 0.08);
  border-radius: 14px;
  padding: 6px;
  box-shadow:
    0 4px 14px -4px rgba(31, 22, 17, 0.12),
    0 24px 48px -12px rgba(31, 22, 17, 0.18);
  display: flex;
  flex-direction: column;
  gap: 2px;
  transform-origin: bottom right;
  animation: bsModePopoverIn 200ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes bsModePopoverIn {
  0%   { opacity: 0; transform: translateY(6px) scale(0.96); }
  100% { opacity: 1; transform: translateY(0)   scale(1);   }
}
.builder-mode-popover__item {
  display: block;
  text-align: left;
  width: 100%;
  padding: 10px 12px;
  border-radius: 10px;
  border: 0;
  background: transparent;
  color: var(--ink, #1F1611);
  font-family: inherit;
  cursor: pointer;
  transition: background 0.12s;
  -webkit-tap-highlight-color: transparent;
}
.builder-mode-popover__item:hover { background: #FAFAFA; }
.builder-mode-popover__item.is-active {
  background: rgba(31, 22, 17, 0.04);
}
.builder-mode-popover__item.is-active .builder-mode-popover__name::after {
  content: " ✓";
  color: #1f7a4d;
  font-weight: 600;
}
.builder-mode-popover__name {
  display: block;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--ink, #1F1611);
  margin-bottom: 2px;
}
.builder-mode-popover__blurb {
  display: block;
  font-size: 12.5px;
  color: var(--ink-mute, #6B5A4C);
  line-height: 1.4;
}

/* Picker card — swatch grid for ambiguous colour requests.
   Reuses the .builder-brain-card recipe (white surface, hairline,
   soft shadow). 4 tiles in a 2x2 grid (or 1x4 on wider screens). */
.builder-brain-card--picker .builder-picker-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-top: 8px;
}
.builder-picker-tile {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px;
  border-radius: 12px;
  border: 1px solid rgba(31, 22, 17, 0.10);
  background: #FFFFFF;
  color: var(--ink, #1F1611);
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  transition: background 0.12s, border-color 0.12s, transform 0.12s;
  -webkit-tap-highlight-color: transparent;
}
.builder-picker-tile:hover:not(:disabled) {
  background: #FAFAFA;
  border-color: rgba(31, 22, 17, 0.16);
}
.builder-picker-tile:active:not(:disabled) { transform: translateY(0.5px); }
.builder-picker-tile.is-picked {
  background: rgba(31, 122, 77, 0.06);
  border-color: rgba(31, 122, 77, 0.4);
}
.builder-picker-tile.is-picked::after {
  content: "✓";
  position: absolute;
  /* Could float into a corner — keeping inline below name for now */
}
.builder-picker-tile.is-dim {
  opacity: 0.4;
}
.builder-picker-tile:disabled { cursor: default; }
.builder-picker-tile__swatches {
  display: flex;
  gap: 4px;
  height: 36px;
  border-radius: 8px;
  overflow: hidden;
  border: 1px solid rgba(31, 22, 17, 0.06);
}
.builder-picker-tile__swatch {
  flex: 1 1 0;
  height: 100%;
}
.builder-picker-tile__label {
  font-size: 13px;
  font-weight: 500;
  color: var(--ink, #1F1611);
  letter-spacing: -0.005em;
}

.builder-msg-row.is-ai .builder-msg.builder-brain-locked {
  background: transparent !important;
  border: 0 !important;
  padding: 4px 4px !important;
  max-width: 440px !important;
  box-shadow: none !important;
  align-self: flex-start !important;
  color: var(--ink-mute, #6B5A4C) !important;
  font-size: 13.5px !important;
  font-style: italic !important;
  line-height: 1.5 !important;
  animation: bsBrainToastIn 400ms cubic-bezier(0.16, 1, 0.3, 1) 240ms both !important;
}

/* 2026-05-24 site-info follow-up AI msg — appended right after the
   handoff card. Renders as plain prose (no card surface) per
   founder ask: "the second text should not be in a card it should
   just be normal text". The chat's default AI-msg styling at
   L4091 (.builder-msg-row.is-ai .builder-msg → bg:transparent,
   padding: 2px 0) is the desired baseline; we only style the
   prose elements + the inline pill link inside. */
.builder-msg.builder-siteinfo p {
  margin: 0 0 12px;
  /* 2026-05-24 founder: nudged to 1.04rem to match the bumped
     AI/user chat font sizes. */
  font-size: 1.04rem;
  line-height: 1.55;
  color: var(--ink, #1F1611);
}
.builder-msg.builder-siteinfo p strong {
  color: var(--ink, #1F1611);
  font-weight: 600;
}
.builder-msg.builder-siteinfo p:first-child {
  margin-top: 0;
}
.builder-msg.builder-siteinfo p:nth-last-child(2) {
  margin-bottom: 8px;
}
.builder-siteinfo__pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 12px 7px 10px;
  background: #FBFBFB;
  border: 1px solid rgba(31, 22, 17, 0.1);
  border-radius: 999px;
  font-size: 0.83rem;
  font-weight: 500;
  color: var(--ink, #1F1611);
  text-decoration: none;
  transition: background 0.12s, border-color 0.12s, transform 0.14s cubic-bezier(0.2, 0, 0, 1);
  -webkit-tap-highlight-color: transparent;
}
.builder-siteinfo__pill:hover {
  background: #F4F4F4;
  border-color: rgba(31, 22, 17, 0.15);
}
.builder-siteinfo__pill:active { transform: translateY(0.5px); }
.builder-siteinfo__pill svg { flex-shrink: 0; }

.builder-quick-refines {
  display: flex; flex-wrap: wrap; gap: 6px;
  padding: 10px 18px; border-top: 1px solid var(--hairline);
}
.builder-refine-chip {
  background: transparent;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  /* 2026-05-29 founder: was 0.78rem / 4px 11px — chips read as
     footnotes vs the AI reply text. Bumped to 0.88rem and a touch more
     padding so they sit as real affordances. */
  padding: 6px 12px;
  font-family: inherit; font-size: 0.88rem;
  color: var(--ink-mute); cursor: pointer;
  transition: all 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.builder-refine-chip:hover { color: var(--ink); border-color: var(--hairline-2); }
.builder-compose {
  padding: 12px 18px;
  border-top: 1px solid var(--hairline);
  display: flex; gap: 8px; align-items: flex-end;
}
.builder-compose textarea {
  flex: 1;
  border: 0;
  background: transparent;
  color: var(--ink);
  font-family: inherit; font-size: 0.92rem;
  resize: none;
  min-height: 24px; max-height: 120px;
  outline: none;
  padding: 6px 0;
}
.builder-send {
  background: var(--ink); color: var(--cream);
  border: 0; border-radius: 8px;
  padding: 8px 14px; font-family: inherit;
  font-size: 0.85rem; font-weight: 500;
  cursor: pointer;
}
.builder-preview-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 16px;
  border-bottom: 1px solid var(--hairline);
}
.builder-preview-head-right {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}
.builder-tabs { display: inline-flex; background: var(--cream-2); border-radius: 8px; padding: 3px; gap: 2px; }
.builder-tab {
  background: transparent; border: 0; padding: 5px 12px;
  font-family: inherit; font-size: 0.82rem; color: var(--ink-mute);
  border-radius: 6px; cursor: pointer;
  transition: all 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.builder-tab.is-on { background: var(--cream); color: var(--ink); font-weight: 500; }

/* Chat-side tab strip (Chat / Sections) revealed after generation. */
.builder-chat-tabs {
  display: flex; gap: 4px;
  padding: 10px 18px 0;
  border-bottom: 1px solid var(--hairline);
  background: var(--cream);
}
.builder-chat-tabs[hidden] { display: none; }
.builder-chat-tab {
  appearance: none; border: 0; background: transparent;
  padding: 7px 12px; font: inherit; font-size: 0.82rem; font-weight: 500;
  color: var(--ink-mute); cursor: pointer;
  border-radius: 8px 8px 0 0;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  transition: color 0.15s, background 0.15s, border-color 0.15s;
  display: inline-flex; align-items: center;
}
.builder-chat-tab:hover { color: var(--ink); background: var(--cream-2); }
.builder-chat-tab.is-on { color: var(--ink); border-bottom-color: var(--ink); font-weight: 600; }
.builder-chat-tab-count {
  margin-left: 5px; padding: 1px 6px;
  background: var(--cream-3); color: var(--ink-mute);
  border-radius: 999px; font-size: 0.66rem; font-weight: 600;
}
.builder-chat-tab-count:empty { display: none; }

.builder-sections-body { flex: 1; overflow-y: auto; padding: 14px 18px; min-height: 0; }
.builder-sections-body[hidden] { display: none; }
.builder-sections-hint { margin: 0 0 14px; font-size: 0.8rem; color: var(--ink-mute); line-height: 1.45; }
.builder-sections-empty { font-size: 0.85rem; color: var(--ink-mute); font-style: italic; }
.builder-sections-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 6px; }
/* Phase A 2026-05-16: row gained a tradie-language description row between
   the section name (row 1) and the headline (now row 3). 2026-05-17: row 6
   added for the inline rewrite composer (textarea + Send/Cancel) that drops
   under the actions when the tradie clicks Redo with AI. Grid rows are
   now 6: name, desc, headline, variants, actions, composer. */
.builder-sections-row {
  display: grid; grid-template-columns: 1fr auto;
  grid-template-rows: auto auto auto auto auto auto;
  gap: 2px 10px; padding: 12px 14px;
  border-radius: 12px; border: 1px solid transparent; background: transparent;
  transition: background 0.14s, border-color 0.14s;
}
.builder-sections-row[data-hidden="true"] .builder-sections-row__name { text-decoration: line-through; text-decoration-color: var(--ink-faint); text-decoration-thickness: 1px; }
.builder-sections-row[data-hidden="true"] .builder-sections-row__desc,
.builder-sections-row[data-hidden="true"] .builder-sections-row__headline { opacity: 0.55; }
.builder-sections-row:hover { background: var(--cream-2); border-color: var(--hairline); }
.builder-sections-row__name {
  grid-column: 1; grid-row: 1;
  font-size: 0.88rem; font-weight: 600; color: var(--ink);
  display: inline-flex; align-items: center; gap: 8px;
}
.builder-sections-row__hidden-pill {
  display: inline-flex; align-items: center;
  background: var(--cream-3);
  color: var(--ink-mute);
  font-size: 0.66rem; font-weight: 500;
  letter-spacing: 0.04em; text-transform: uppercase;
  padding: 2px 7px; border-radius: 999px;
  text-decoration: none;
}
.builder-sections-row__desc {
  grid-column: 1 / -1; grid-row: 2;
  font-size: 0.74rem; color: var(--ink-mute); line-height: 1.4;
  margin-top: 1px;
}
.builder-sections-row__headline {
  grid-column: 1 / -1; grid-row: 3;
  font-size: 0.76rem; color: var(--ink-faint); line-height: 1.35;
  font-style: italic;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  margin-top: 4px;
}
.builder-sections-row__actions {
  grid-column: 1 / -1; grid-row: 5;
  display: inline-flex; gap: 4px; margin-top: 10px;
  flex-wrap: wrap;
}
.builder-sections-action {
  appearance: none; background: var(--paper); border: 1px solid var(--hairline);
  border-radius: 999px; padding: 5px 12px;
  font: inherit; font-size: 0.74rem; color: var(--ink-mute);
  cursor: pointer; transition: all 0.14s;
}
.builder-sections-action:hover { color: var(--ink); border-color: var(--ink); background: var(--cream); }
.builder-sections-action.is-danger:hover { color: #7c2218; border-color: #c4534a; background: #fdf3f1; }
.builder-sections-action[disabled] { opacity: 0.4; cursor: not-allowed; }
.builder-sections-action.is-secondary {
  color: var(--ink-faint); border-color: transparent; background: transparent;
}
.builder-sections-action.is-secondary:hover {
  color: var(--ink); border-color: var(--hairline); background: var(--cream);
}

/* Inline rewrite composer (2026-05-17). Drops under the actions row when
   the tradie clicks Redo with AI. One optional textarea + Cancel/Rewrite
   buttons. Empty submission is allowed — server treats "no direction"
   as "rewrite with section guidance + brief only". */
.builder-sections-row__composer {
  grid-column: 1 / -1; grid-row: 6;
  display: flex; flex-direction: column; gap: 8px;
  padding: 10px 12px; margin-top: 8px;
  background: var(--cream-1);
  border: 1px solid var(--hairline);
  border-radius: 10px;
}
.builder-sections-composer__label {
  font-size: 0.72rem; color: var(--ink-mute); font-weight: 500;
  line-height: 1.35;
}
.builder-sections-composer__input {
  appearance: none;
  width: 100%; box-sizing: border-box;
  min-height: 56px; resize: vertical;
  padding: 8px 10px;
  font: inherit; font-size: 0.82rem; line-height: 1.4; color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--hairline); border-radius: 8px;
  transition: border-color 0.14s;
}
.builder-sections-composer__input:focus {
  outline: none;
  border-color: var(--ink);
}
.builder-sections-composer__input[disabled] {
  opacity: 0.55; cursor: not-allowed;
}
.builder-sections-composer__actions {
  display: flex; gap: 6px; justify-content: flex-end;
}

/* T1 variant picker chips (2026-05-16). Sits between the headline row
   and the Jump/Rewrite/Hide row. Pill shape matches .builder-sections-
   action but the active chip inverts to the ink/paper duo so the
   tradie can see at a glance which layout is live. Warm-stone only;
   no dark+orange (that lives in the prototype, not the real app).
   Phase A 2026-05-16: chips now carry a one-word variant name alongside
   the letter so the tradie can read "B Photo" instead of just "B". */
.builder-sections-row__variants {
  grid-column: 1 / -1; grid-row: 4;
  display: inline-flex;
  gap: 6px;
  margin-top: 8px;
  flex-wrap: wrap;
}
.builder-section-variant-chip {
  appearance: none;
  background: var(--paper);
  border: 1px solid var(--hairline);
  color: var(--ink-mute);
  font: inherit;
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  padding: 3px 10px 3px 4px;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.14s, border-color 0.14s, color 0.14s;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  line-height: 1.4;
}
.builder-section-variant-chip__letter {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px; height: 18px;
  border-radius: 999px;
  background: var(--cream-3);
  color: var(--ink);
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  flex-shrink: 0;
}
.builder-section-variant-chip__name { padding-right: 2px; }
.builder-section-variant-chip:hover { color: var(--ink); border-color: var(--ink); background: var(--cream); }
.builder-section-variant-chip:hover .builder-section-variant-chip__letter { background: var(--paper); }
.builder-section-variant-chip.is-on {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}
.builder-section-variant-chip.is-on .builder-section-variant-chip__letter {
  background: rgba(255,255,255,0.18);
  color: var(--paper);
}
.builder-section-variant-chip[disabled] { opacity: 0.45; cursor: wait; }

/* Phase A 2026-05-16: footer counter at the bottom of the Sections list,
   "10 sections · 2 hidden · 1 layout changed". Reads as a quiet status
   line, separated from the list by a hairline so it doesn't compete
   with row hover states. */
.builder-sections-footer {
  margin: 14px 0 0;
  padding-top: 12px;
  border-top: 1px solid var(--hairline);
  font-size: 0.72rem;
  color: var(--ink-faint);
  letter-spacing: 0.01em;
}
.builder-sections-footer[hidden] { display: none; }

.builder-device-toggle { display: inline-flex; gap: 4px; }
.builder-device-btn {
  background: transparent; border: 0;
  width: 28px; height: 28px;
  border-radius: 6px;
  color: var(--ink-faint); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.builder-device-btn:hover { background: var(--cream-2); color: var(--ink); }
.builder-device-btn.is-on { background: var(--cream-2); color: var(--ink); }
.builder-preview-body {
  flex: 1; padding: 24px;
  display: flex; align-items: center; justify-content: center;
  background: var(--cream-2);
  overflow: auto;
}


/* ============================================================
   Inbound — unified inbox (Booking + Quote + Phone-call rows)
   Tier-aware blocks revealed by data-plan loop in dashboard-v2.js.
   Chip row reuses the existing .chip / .chip-count primitives.
   ============================================================ */
/* === Inbound empty state — substantive markup ============================== */
/* Welcome card + 3 quick-action cards + "what lands here" preview rows.
   Server-rendered into [data-inbound-list] so the page reads useful even
   before /api/leads resolves. wireRichInteractions replaces this with
   real lead rows when leads exist. */
.inbound-empty {
  display: flex;
  flex-direction: column;
  gap: 24px;
  padding: 16px 0 0;
}
.inbound-empty-card {
  background: var(--paper, #fff);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  padding: 28px 32px;
}
.inbound-empty-title {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-weight: 500;
  font-size: 1.5rem;
  letter-spacing: -0.018em;
  color: var(--ink);
  margin: 0 0 8px;
}
.inbound-empty-sub {
  font-size: 0.95rem;
  color: var(--ink-mute);
  line-height: 1.55;
  margin: 0;
  max-width: 64ch;
  text-wrap: pretty;
}
.inbound-help-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}
.inbound-help {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 18px 18px;
  background: var(--paper, #fff);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  text-align: left;
  text-decoration: none;
  color: var(--ink);
  font-family: inherit;
  font-size: 0.95rem;
  letter-spacing: normal;
  text-transform: none;
  cursor: pointer;
  transition: border-color 0.18s cubic-bezier(0.2, 0, 0, 1), transform 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.inbound-help:hover {
  border-color: var(--ink-mute);
  transform: translateY(-1px);
}
.inbound-help-icon {
  display: inline-flex;
  width: 32px;
  height: 32px;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  background: var(--cream-2);
  color: var(--ink);
  flex-shrink: 0;
}
.inbound-help-text { font-family: inherit; }
.inbound-help-title {
  font-weight: 500;
  font-size: 0.95rem;
  color: var(--ink);
  margin-bottom: 4px;
  line-height: 1.3;
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  letter-spacing: -0.01em;
  text-transform: none;
}
.inbound-help-sub {
  font-size: 0.82rem;
  color: var(--ink-mute);
  line-height: 1.45;
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  letter-spacing: normal;
  text-transform: none;
  font-weight: 400;
}
.inbound-preview {
  margin-top: 4px;
}
.inbound-preview-eyebrow {
  font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace;
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 12px;
  font-weight: 500;
}
.inbound-preview-list {
  display: flex;
  flex-direction: column;
  background: var(--paper, #fff);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  overflow: hidden;
}
.inbound-preview-row {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--hairline);
}
.inbound-preview-row:last-child { border-bottom: 0; }
.inbound-preview-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.78rem;
  font-weight: 500;
  padding: 5px 10px;
  border-radius: 6px;
  flex-shrink: 0;
  min-width: 110px;
}
.inbound-preview-pill.is-booking { background: var(--green-soft, #E8F5E9); color: var(--green, #2E7D32); }
.inbound-preview-pill.is-quote { background: var(--cream-2); color: var(--ink); }
.inbound-preview-pill.is-call { background: rgba(213, 117, 41, 0.1); color: var(--accent-orange, #D57529); }
.inbound-preview-text {
  font-size: 0.88rem;
  color: var(--ink-mute);
  line-height: 1.5;
}

@media (max-width: 720px) {
  .inbound-help-grid { grid-template-columns: 1fr; }
  .inbound-help { padding: 14px 16px; }
  .inbound-empty-card { padding: 22px; }
  .inbound-preview-pill { min-width: auto; }
}

/* ─────────────────────────────────────────────────────────────────────
   Inbound — Style G (day-grouped agenda)
   Rows grouped under date headings (Today / Yesterday / Monday / 18 May).
   Time column on the left, body on the right with status badge inline.
   No bordered list container — section headings + hairlines do the work.
   ───────────────────────────────────────────────────────────────────── */

/* Keep the inbound header in its desktop row layout (title left,
   "+ New" right) even on mobile. The Calendar's mobile rule stacks
   .rich-head to one column + makes .rich-actions full-width — that's
   right for Calendar's multi-control toolbar but wrong for Inbound's
   single corner pill. */
@media (max-width: 720px) {
  .rich-head--inbound { grid-template-columns: 1fr auto !important; align-items: start; gap: 12px; }
  .rich-head--inbound .rich-actions { flex-direction: row !important; align-items: flex-start !important; }
}

/* Compact "+ New" pill in the corner. */
.inbound-new-btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 9px 14px; border-radius: 999px;
  background: var(--ink); color: var(--cream);
  font-family: inherit; font-size: 0.86rem; font-weight: 500;
  text-decoration: none; white-space: nowrap;
  align-self: flex-start;
  transition: background 0.15s ease, transform 0.15s ease;
}
.inbound-new-btn:hover { background: var(--ink-2, #2a1f17); color: var(--cream); }
.inbound-new-btn:active { transform: scale(0.98); }
.inbound-new-btn svg { display: block; }

/* Tab pills — segmented pill group on a cream-2 track. The active
   "indicator" is a SEPARATE absolutely-positioned glass element that
   slides between tab positions via JS-controlled transform + width.
   Sliding the indicator (instead of moving styling from tab to tab)
   gives the iOS-segmented-control feel and keeps the backdrop-filter
   on a single dedicated GPU layer for the whole interaction.
   JS still keys off [data-inbound-filter] + .is-on for the active
   state (the indicator follows the .is-on tab's position). */
.inbound-tabs {
  position: relative; /* indicator is absolute relative to this */
  display: inline-flex; padding: 4px;
  background: var(--cream-2); border-radius: 999px;
  border: 1px solid var(--hairline);
  margin: 4px 0 18px;
  isolation: isolate; /* contains the stacking context for the indicator + tabs */
}
.inbound-tab {
  position: relative;
  z-index: 1; /* stays above the sliding indicator */
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 14px; border-radius: 999px;
  font-family: inherit;
  font-size: 0.86rem; font-weight: 500;
  color: var(--ink-mute);
  background: transparent; border: 0; cursor: pointer;
  transition: color 0.22s ease;
  -webkit-tap-highlight-color: transparent;
}
@media (hover: hover) {
  .inbound-tab:hover { color: var(--ink); }
}
.inbound-tab.is-on { color: var(--ink); }

/* === SLIDING GLASS INDICATOR ===
   One floating glass surface that slides between active tab positions.
   The slide is a transform+width transition (both GPU-cheap properties)
   running for ~320ms with an Apple-style cubic-bezier so the motion
   feels springy but settled.

   Glass recipe (enhanced from the earlier version):
   - 55% white fill so the cream-2 track shows through
   - Heavier blur + saturate (16px / 200%) for a richer refraction look
   - Triple-stack inset box-shadow: a bright top-edge highlight (the
     Apple "Liquid Glass" top-rim signature) + a soft inset bottom for
     refraction depth + a faint outer drop shadow for elevation
   - ::before pseudo-element layers a specular gradient over the top
     half — this is the "lens highlight" that makes glass read as a
     real material rather than just translucency.

   GPU PROMOTION (the user-flagged "must be on GPU" requirement):
   - `will-change: backdrop-filter, transform` is the modern semantic
     hint that tells the browser to keep this element on its own
     dedicated GPU layer.
   - `transform: translateZ(0)` is the legacy WebKit-compatible layer
     promotion that works where will-change is ignored.
   - Both ensure the blur runs in the GPU compositor (not the CPU
     software-blur fallback) AND the slide transition stays at 60fps.

   GOTCHA: backdrop-filter silently breaks if any parent in the chain
   has its own transform / filter / perspective / will-change:transform.
   None of the inbound parents do — verified. */
.inbound-tabs__indicator {
  position: absolute;
  top: 4px;
  left: 0;
  height: calc(100% - 8px);
  width: 0; /* JS sets this on mount */
  pointer-events: none;
  z-index: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.55);
  backdrop-filter: blur(16px) saturate(200%);
  -webkit-backdrop-filter: blur(16px) saturate(200%);
  border: 1px solid rgba(255, 255, 255, 0.75);
  box-shadow:
    0 1px 4px rgba(31, 22, 17, 0.10),
    inset 0 1px 0 rgba(255, 255, 255, 0.95),
    inset 0 -1px 0 rgba(31, 22, 17, 0.05);
  /* GPU compositor promotion — keeps blur on its own GPU layer and
     makes the transform-based slide silky-smooth. */
  will-change: backdrop-filter, transform, width;
  transform: translate3d(0, 0, 0);
  /* Apple-style smooth slide; cubic-bezier is approximately the iOS
     spring decel curve. */
  transition:
    transform 0.32s cubic-bezier(0.32, 0.72, 0, 1),
    width 0.32s cubic-bezier(0.32, 0.72, 0, 1);
}
/* Specular lens highlight — a soft white gradient on the top half
   that makes the surface read as glass, not just a translucent pill. */
.inbound-tabs__indicator::before {
  content: '';
  position: absolute;
  top: 1px; left: 1px; right: 1px;
  height: 50%;
  border-radius: 999px 999px 0 0;
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.45) 0%,
    rgba(255, 255, 255, 0.08) 70%,
    transparent 100%
  );
  pointer-events: none;
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .inbound-tabs__indicator { background: rgba(255, 255, 255, 0.95); }
}

.inbound-tab__count {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.7rem; font-weight: 600;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
.inbound-tab.is-on .inbound-tab__count { color: var(--ink-mute); }

/* Group section: heading row (label + per-state count meta), then a
   container that holds the rows for that bucket. No outer bordered
   card — the heading + hairlines between rows are the only chrome. */
.inbound-group { margin-bottom: 20px; }
.inbound-group:last-child { margin-bottom: 0; }
.inbound-group__head {
  display: flex; align-items: baseline; gap: 10px;
  padding: 0 4px 12px;
  border-bottom: 1px solid var(--hairline);
}
.inbound-group__label {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 0.92rem; font-weight: 600; letter-spacing: -0.01em;
  color: var(--ink);
}
.inbound-group__count {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.68rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-faint); font-weight: 500;
  margin-left: auto;
}
.inbound-group__rows { display: flex; flex-direction: column; }

/* Row layout: time column (left) + body (right). */
.inbound-list { display: flex; flex-direction: column; }
.inbound-row {
  display: grid;
  grid-template-columns: 60px 1fr;
  gap: 14px;
  padding: 12px 4px;
  border-bottom: 1px solid var(--hairline);
  cursor: pointer;
  align-items: flex-start;
  transition: background 0.15s ease;
  -webkit-tap-highlight-color: transparent;
}
.inbound-row:last-child { border-bottom: 0; }
/* Scope :hover to pointing devices ONLY — on touch the :hover state
   sticks after the finger lifts (iOS Safari + Chrome Mobile both do
   this), leaving the row visually "selected" after a scroll-touch.
   :active still gives tap feedback on touch for the moment the
   finger is down. */
@media (hover: hover) {
  .inbound-row:hover { background: var(--paper, #fff); }
}
.inbound-row:focus-visible { outline: 2px solid var(--orange); outline-offset: -2px; border-radius: 6px; }

/* Time column. Currently fmtTime returns "2h ago" for today, day name
   for last week, short date for older. */
.inbound-row__time {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.78rem;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
  padding-top: 2px;
}

.inbound-row__body { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.inbound-row__head {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.inbound-row__name {
  font-size: 1rem; font-weight: 600; letter-spacing: -0.015em;
  color: var(--ink);
}

/* Status badge — coloured pill with a small leading dot. Replaces the
   bare caps text status of the previous design. */
.inbound-row__badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 8px 2px 7px;
  border-radius: 999px;
  font-size: 0.72rem; font-weight: 500;
  letter-spacing: -0.005em;
}
.inbound-row__badge-dot {
  width: 5px; height: 5px; border-radius: 50%;
  background: currentColor;
}
/* Status badge colours:
   - NEW       → blue (#2563EB) — distinct from cancelled, signals action
   - CONFIRMED → green (existing)
   - CANCELLED → red (was muted grey; user wants it to read as alarming)
   - OTHER     → muted grey fallback for unrecognised statuses */
.inbound-row__badge.is-new { background: rgba(37, 99, 235, 0.10); color: #2563EB; }
.inbound-row__badge.is-confirmed { background: rgba(47, 122, 54, 0.10); color: var(--green, #2F7A36); }
.inbound-row__badge.is-cancelled { background: rgba(224, 64, 48, 0.10); color: var(--red, #E04030); }
.inbound-row__badge.is-other { background: rgba(31, 22, 17, 0.06); color: var(--ink-mute); }

.inbound-row__meta {
  font-size: 0.88rem; color: var(--ink-mute); line-height: 1.45;
}
.inbound-row__sub {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.64rem; letter-spacing: 0.13em; text-transform: uppercase;
  color: var(--ink-faint); font-weight: 500;
  margin-top: 2px;
}

/* Out-of-area badge (quotes outside the tradie's service area). */
.inbound-flag.is-ooa {
  display: inline-flex; align-items: center;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.62rem; letter-spacing: 0.1em; text-transform: uppercase;
  padding: 2px 7px; border-radius: 999px;
  background: var(--cream-3, #E5DBC5); color: var(--ink-mute);
}

/* Expanded detail — clean text reveal inside the same row. */
.inbound-detail {
  margin-top: 10px; padding-top: 10px;
  border-top: 1px solid var(--hairline);
  font-size: 0.88rem; color: var(--ink);
  line-height: 1.6;
  white-space: pre-wrap;
}

@media (max-width: 700px) {
  .inbound-row { padding: 12px 2px; gap: 12px; }
  .inbound-row__time { font-size: 0.74rem; }
}

/* ==========================================================================
   Mobile polish sweep (2026-05-02)
   - Kill iOS Safari auto-zoom on input focus (16px floor)
   - 44px tap targets on all primary controls
   - Drawer + topbar respect safe-area insets
   - Tab strip: thin scrollbar + scroll-snap + smaller mask
   - No blue tap highlight on iOS / Android
   - overflow-x: clip beats hidden where supported
   ========================================================================== */

/* Universal tap-highlight kill (Android Chrome blue flash + iOS grey flash). */
* { -webkit-tap-highlight-color: transparent; }

@media (max-width: 899.98px) {
  /* P0: 16px floor on every focusable text-entry control on mobile to stop
     iOS Safari from auto-zooming the page on focus. Specifically excludes
     checkbox / radio / range / file because those don't trigger zoom and
     forcing 16px on them breaks layout. */
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="color"]),
  textarea,
  select {
    font-size: 16px !important;
  }

  /* Topbar: respect safe-area top inset (iPhone notch / Dynamic Island). */
  .mob-topbar {
    padding-top: env(safe-area-inset-top);
    height: calc(52px + env(safe-area-inset-top));
  }

  /* 2026-05-22 Lovable-parity Variant 6 promoted to global — burger
     is 48px (was 44px). The 44px size set here was the WCAG tap-target
     floor override but V6 is already above that. Just remove this rule's
     size override so the base L1481 V6 size (48px) applies. */
  /* Drawer height respects bottom safe-area (iOS home indicator). 100dvh
     also fixes the iOS Safari address-bar shrinking issue. */
  .side {
    height: 100vh;
    height: 100dvh;
    max-height: 100dvh;
    padding-bottom: env(safe-area-inset-bottom);
    overflow-y: auto;
  }

  /* Drawer close X: 28 -> 40. Secondary so doesn't need full 44. */
  .mob-drawer-close {
    width: 40px;
    height: 40px;
  }

  /* Sidebar nav links: ensure 44px tap target. */
  .side-link {
    min-height: 44px;
    display: flex;
    align-items: center;
  }

  /* Settings tabs: 44px tap floor + scroll-snap so swipes feel intentional. */
  .set-tab {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    scroll-snap-align: start;
  }

  /* Settings tab strips: visible thin scrollbar + scroll-snap so swipes
     feel intentional. The fade gradient was confusing (looked like cut-off
     text); chevron buttons in `.set-tabs-wrap` and this scrollbar are the
     only swipe affordances. */
  .set-tabs,
  .set-subtabs {
    scroll-snap-type: x proximity;
    scrollbar-width: thin;
    scrollbar-color: var(--hairline-2) transparent;
    -webkit-mask-image: none !important;
            mask-image: none !important;
  }
  .set-tabs::-webkit-scrollbar,
  .set-subtabs::-webkit-scrollbar {
    display: block !important;
    height: 3px;
    -webkit-appearance: none;
  }
  .set-tabs::-webkit-scrollbar-thumb,
  .set-subtabs::-webkit-scrollbar-thumb {
    background: var(--hairline-2);
    border-radius: 2px;
  }
  .set-tabs::-webkit-scrollbar-track,
  .set-subtabs::-webkit-scrollbar-track { background: transparent; }
  .set-subtab { scroll-snap-align: start; }

  /* Buttons: ensure 44px tap floor on primary CTA-style buttons. */
  .btn,
  .btn-primary,
  .btn-ghost,
  .btn-secondary {
    min-height: 44px;
  }

  /* Replace overflow-x: hidden with the more precise overflow-x: clip where
     supported. clip doesn't establish a scroll container, so it doesn't break
     inertial scrolling on Android keyboards or sticky-positioned children. */
  html, body { overflow-x: clip; max-width: 100%; }
}

/* === Settings tab strip wrapper + scroll chevrons ====================== */
/* The settings JS wraps each `.set-tabs` and `.set-subtabs` strip in a
   `.set-tabs-wrap` containing two chevron buttons. The buttons are the
   primary swipe affordance on touch (a thin scrollbar reinforces it on
   pointer devices). Both buttons are hidden by the JS via `[hidden]` when
   there is nothing to scroll in that direction. */
.set-tabs-wrap {
  position: relative;
}
.set-tabs-chev {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--hairline-2);
  background: var(--paper, #FFFFFF);
  color: var(--ink-mute);
  border-radius: 999px;
  font-size: 1.1rem;
  line-height: 1;
  padding: 0;
  cursor: pointer;
  z-index: 2;
  box-shadow: 0 1px 4px rgba(31, 22, 17, 0.06);
  transition: background 0.15s, color 0.15s, border-color 0.15s;
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0.1);
  -webkit-user-select: none;
  user-select: none;
}
.set-tabs-chev:hover {
  background: var(--cream-2);
  color: var(--ink);
  border-color: var(--hairline);
}
.set-tabs-chev[hidden] { display: none !important; }
.set-tabs-chev[data-scroll-prev] { left: -4px; }
.set-tabs-chev[data-scroll-next] { right: -4px; }

/* Settings sticky save bar — canonical chrome lives in the inline `<style>`
   in dashboard-v2-page.html (`.set-actions`) so it sits next to the related
   glass-bg override. The mobile-stack overrides above are scoped to the
   `(max-width: 720px)` media block so they only fire on narrow viewports. */

/* Outline + box-shadow combo focus ring. On Android Chrome a box-shadow ring
   on an input near the viewport edge gets clipped by the surrounding card —
   adding outline restores a visible ring. Applied at all sizes since it also
   improves keyboard-nav accessibility on desktop. */
.set-input:focus-visible,
.set-textarea:focus-visible,
.set-select:focus-visible {
  outline: 2px solid var(--orange);
  outline-offset: 2px;
}

/* ============================================================================
   Fresh-signup empty state — hero shown when /api/settings/site-status
   returns hasSite=false. Body class `is-empty-dash` flips visibility:
   hides the mock dashboard chrome, shows the centered hero.
   Reuses .builder-input-box / .builder-suggestion-chip styles directly
   (same Claude-style chat input as the AI Builder, just narrower).
   ========================================================================== */
body.is-empty-dash .dash-top,
body.is-empty-dash .dash-kpis,
body.is-empty-dash .dash-grid,
body.is-empty-dash .dash-bottom-grid,
body.is-empty-dash .dash-asset { display: none !important; }

body:not(.is-empty-dash) .dash-empty-hero { display: none !important; }

/* When the empty hero is the only content: switch .dash to a grid layout
   with two rows. Row 1 (auto) holds anything pinned-to-top (e.g. the
   verify-email banner). Row 2 (1fr) holds the hero, vertically centered.
   Without this, the flex `justify-content: center` would drag the banner
   into the middle of the screen alongside the hero. */
body.is-empty-dash .dash {
  display: grid;
  grid-template-rows: auto 1fr;
  /* Full viewport, no vertical padding. The projects strip is
     position: absolute; bottom: 0 of the hero — if .dash retained its
     default padding-bottom (80px), the hero would end 80px above the
     viewport, and the strip's `transform: translateY(calc(100% - 52px))`
     would peek ~130px instead of the intended 52px. Zero padding lets
     the hero reach the actual viewport bottom, which the strip's
     transform math depends on. */
  min-height: 100vh;
  padding-top: 0;
  padding-bottom: 0;
  width: 100%;
}
body.is-empty-dash .dash-empty-hero {
  /* Explicitly land in row 2 (1fr). Without this, when no verify-email
     banner is rendered, the hero auto-flows into row 1 (auto-sized) and
     collapses to content height at the top of .dash, defeating the
     vertical centering. */
  grid-row: 2;
  /* Stretch to fill the grid row so projects can be absolutely-positioned
     at the bottom of the section while hero content stays centered. */
  align-self: stretch;
  justify-self: center;
  /* Lovable parity: input box anchors to viewport vertical center.
     Three-row grid (1fr auto 1fr) with the form in row 2 (auto) and
     the wrapping __above / __below divs in rows 1 / 3. Above-content
     bottom-aligns against the input; below-content top-aligns under it.
     Previously the hero was display:flex with justify-content:center,
     which centered the WHOLE stack — but the input was the last child,
     so it ended up 90-170px below viewport center across viewports. */
  display: grid;
  /* Slight downward bias (1.15fr / auto / 0.85fr instead of symmetric
     1fr / auto / 1fr): the visible content lives ABOVE the input
     (live-pill + logo + heading), with nothing below it once chips are
     hidden in refine mode. Pure symmetric centering pinned the input at
     vp-center, but the stack — measured top-to-bottom of all visible
     content — read as floating above center. Biasing the grid pulls the
     input ~7-9% below vp-center so the whole composition balances out,
     matching Lovable's headline-above / input-below-center rhythm.
     Refine mode (has-live-site) flips the bias — see override below. */
  grid-template-rows: 1.15fr auto 0.85fr;
  position: relative;
  /* No explicit min-height — `align-self: stretch` (grid item default)
     sizes the hero to .dash row 2 (1fr), which is the leftover space
     after the verify-email banner row. An explicit 100vh-based min-height
     here used to overflow past the banner row and shift the centered
     input ~60-120px below viewport center for unverified users. */
}
.dash-empty-hero__above {
  grid-row: 1;
  align-self: end;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
}
body.is-empty-dash .dash-empty-form {
  grid-row: 2;
  align-self: center;
  /* Without explicit justify-self the form inherits `stretch`, which —
     combined with max-width: 720 + width: 100% — pins the box to the
     LEFT edge of the grid cell (the cell is ~876px wide after .dash
     padding, the form caps at 720px, so 156px of slack collects on the
     right). That shifted the input ~78px left of the heading/live-pill
     above it, visible as a clear left-lean on 1200-1500px laptops. */
  justify-self: center;
}
.dash-empty-hero__below {
  grid-row: 3;
  align-self: start;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
}
/* Empty-dash chrome: projects strip stays visible (Lovable-style "My
   website / Templates" tabs + card grid below the hero — the markup
   sits inside .dash-empty-hero so it shows together with the input).
   The upgrade nudge in the sidebar foot still hides until they ship
   their first site so the cold-start surface stays focused. */
body.is-empty-dash .side-foot-upgrade { display: none !important; }
body.is-empty-dash [data-email-verify-banner] {
  grid-row: 1;
  align-self: start;
  justify-self: stretch;
}

/* Empty hero — mirror the AI builder's centered chat surface 1:1.
   Uses .builder-empty-hero / .builder-instruction / .builder-input-box /
   .builder-suggestion-row classes directly so both pages stay in lockstep.
   Section spans full main column (up to .dash 940px max) so the projects
   strip below has room for a 3-card grid. The chat input is constrained
   separately to 640px so it stays in balance with the heading. */
.dash-empty-hero {
  width: 100%;
  margin: 0 auto;
  padding: 6vh 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  position: relative;
  isolation: isolate;
}

/* Warm radial bloom behind the chat input — premium "spotlight" cue that
   reads the prompt as the hero, the way Lovable's gradient halo does.
   Sits inside the hero's isolation context so it stays behind everything
   inside the section but above the page-level wave-bands SVG. The pseudo-
   element's bloom drifts slowly so the page feels alive without animating
   any meaningful chrome. Animation is suppressed under prefers-reduced-
   motion further down. */
/* Empty-hero bloom retired 2026-05-21: founder feedback called the orange
   wash a "red glow" that competed with the wave illustration + the live-
   site pill. The wave-bands SVG underneath already carries the warmth. */
body.is-empty-dash .dash-empty-hero::before { content: none; }

/* Refine mode (has-live-site): with the diamond logo + bloom gone, the
   upper content block (live-pill + headline) is lighter than what the
   1.15/0.85 bias was tuned for. Drop the bias entirely (symmetric
   1fr/auto/1fr) so the input lands at the geometric centre between the
   topbar and the orange wave-bands. */
body.is-empty-dash.has-live-site .dash-empty-hero {
  grid-template-rows: 1fr auto 1fr;
}

/* Warm wave-bands backdrop (ported from landing.html .hero-bands).
   Pinned to the viewport (position: fixed) so the wash spans the full
   page edge-to-edge. The opaque sidebar (.side, cream-2 bg) covers it
   on the left; the wash shows through everywhere else. Mask fades it
   gently at the bottom so footer/scroll area stays calm. */
/* Hidden by default — only revealed when body.is-empty-dash flips on. */
.dash-hero-bg { display: none; }
body.is-empty-dash .dash-hero-bg {
  display: block;
  position: fixed;
  inset: 0;
  /* z-index 2 places the wave-bands BETWEEN the sidebar (z-index 1 on
     mobile, parked at left:0 under the card) and the main content
     (z-index 10). Result: in the closed state, the waves cover the
     sidebar so it stays hidden — the page reads as one warm canvas.
     When the card slides right, the waves slide with it (they share
     the --drawer-offset transform), so the left area where the card
     vacated has no waves above the sidebar, letting it become visible. */
  z-index: 2;
  pointer-events: none;
  overflow: hidden;
}
.dash-hero-bg svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
.dash-hero-bg path { will-change: transform; transform-origin: center; }
.dash-hero-bg path:nth-of-type(1) { animation: dashBandDriftA 14s ease-in-out infinite alternate; }
.dash-hero-bg path:nth-of-type(2) { animation: dashBandDriftB 17s ease-in-out infinite alternate; }
.dash-hero-bg path:nth-of-type(3) { animation: dashBandDriftA 19s ease-in-out infinite alternate; }
.dash-hero-bg path:nth-of-type(4) { animation: dashBandDriftB 22s ease-in-out infinite alternate; }
.dash-hero-bg path:nth-of-type(5) { animation: dashBandDriftA 25s ease-in-out infinite alternate; }
@keyframes dashBandDriftA {
  from { transform: translate(-1.6%, -0.4%); }
  to   { transform: translate( 1.8%,  0.6%); }
}
@keyframes dashBandDriftB {
  from { transform: translate( 1.8%,  0.5%); }
  to   { transform: translate(-1.6%, -0.5%); }
}
@media (prefers-reduced-motion: reduce) {
  .dash-hero-bg path { animation: none !important; }
}
.dash-empty-form {
  width: 100%;
  max-width: 720px;
  align-self: center;
  box-sizing: border-box;
  margin: 0;
  /* Solid white surface (no glass) per user feedback. The .builder-input-box
     base already provides bg + border + shadow — we just constrain width. */
}

/* ============================================================================
   Live site pill — small glassy chip that sits ABOVE the hero (Lovable's
   "Power your app with connectors" promo-pill placement). Only renders
   for users who have published (body.has-live-site, flipped by activateHero
   in dashboard-v2.js when /api/settings/site-status returns a subdomain).
   The pill itself is the link target: opens the live site in a new tab.
   ========================================================================== */
.dash-live-pill {
  /* Lovable parity (close-up reference): partially transparent + heavy
     backdrop blur for the frosted-glass feel, but held together by a
     DARK hairline outline so the shape stays sharp instead of melting
     into the backdrop. White-on-white borders read as soft glass; a
     dark-ink border reads as defined glass. That's the difference. */
  display: none;
  align-items: center;
  gap: 9px;
  margin: 0 auto 22px;
  padding: 7px 14px 7px 12px;
  background: rgba(255, 255, 255, 0.28);
  border: 1px solid rgba(31, 22, 17, 0.14);
  border-radius: 999px;
  box-shadow:
    0 2px 4px rgba(31, 22, 17, 0.04),
    0 10px 28px -10px rgba(31, 22, 17, 0.16),
    inset 0 1px 0 rgba(255, 255, 255, 0.7);
  -webkit-backdrop-filter: blur(24px) saturate(170%);
  backdrop-filter: blur(24px) saturate(170%);
  text-decoration: none;
  color: var(--ink);
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 0.84rem;
  font-weight: 500;
  letter-spacing: 0;
  max-width: max-content;
  transition: border-color 0.18s, transform 0.18s, box-shadow 0.18s, background 0.18s;
}
body.has-live-site .dash-live-pill { display: inline-flex; }
.dash-live-pill:hover {
  background: rgba(255, 255, 255, 0.82);
  transform: translateY(-1px);
  box-shadow:
    0 1px 2px rgba(31, 22, 17, 0.04),
    0 10px 28px -12px rgba(31, 22, 17, 0.18),
    inset 0 1px 0 rgba(255, 255, 255, 0.7);
}
.dash-live-pill__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #2BAB5C;
  box-shadow: 0 0 0 3px rgba(43, 171, 92, 0.18);
  flex-shrink: 0;
}
.dash-live-pill__url {
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 260px;
}
.dash-live-pill__arrow {
  color: var(--ink-mute);
  flex-shrink: 0;
  transition: transform 0.18s;
}
.dash-live-pill:hover .dash-live-pill__arrow { transform: translate(1px, -1px); color: var(--ink); }

/* Refine-mode subtitle is redundant with the headline ("Want to refine your
   website?" already says it). Hide so the page feels less crowded between
   greeting and input — matches Lovable's headline-then-input rhythm. */
.is-refine-mode .builder-instruction { display: none; }

@media (max-width: 720px) {
  .dash-live-pill {
    margin-bottom: 16px;
    padding: 5px 11px 5px 10px;
    font-size: 0.78rem;
    gap: 7px;
  }
  .dash-live-pill__url { max-width: 200px; }
}

/* Build / Plan mode toggle — sits in the chat input bar between the +
   attach button and the send button. Click opens a small popover menu. */
.dash-mode {
  position: relative;
  margin-left: auto;
  margin-right: 8px;
}
.dash-mode-trigger {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: transparent;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  padding: 5px 10px 5px 12px;
  font-family: inherit;
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--ink);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
.dash-mode-trigger:hover {
  background: var(--cream-2);
  border-color: var(--ink-mute);
}
.dash-mode-trigger svg { color: var(--ink-mute); }
.dash-mode-menu {
  position: absolute;
  bottom: calc(100% + 8px);
  right: 0;
  width: 240px;
  background: var(--paper, #fff);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  box-shadow: 0 10px 32px -12px rgba(31, 22, 17, 0.18), 0 1px 2px rgba(31, 22, 17, 0.04);
  padding: 6px;
  z-index: 30;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.dash-mode-opt {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 12px;
  background: transparent;
  border: 0;
  border-radius: 8px;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  width: 100%;
  transition: background 0.12s;
}
.dash-mode-opt:hover { background: var(--cream-2); }
.dash-mode-opt-check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  margin-top: 2px;
  color: var(--ink);
  opacity: 0;
}
.dash-mode-opt.is-on .dash-mode-opt-check { opacity: 1; }
.dash-mode-opt-text { display: flex; flex-direction: column; gap: 2px; }
.dash-mode-opt-name {
  font-size: 0.88rem;
  font-weight: 500;
  color: var(--ink);
}
.dash-mode-opt-sub {
  font-size: 0.78rem;
  color: var(--ink-mute);
  line-height: 1.35;
}

/* Projects strip below the hero — Lovable-style tabs + card grid.
   Glassy translucent surface so it picks up the warm wave backdrop
   instead of sitting on top of it as a solid white slab.
   2026-05-12: absolute-positioned in body.is-empty-dash mode so it
   "peeks" from the bottom of the viewport (Lovable pattern). With
   the strip out of flex flow, the hero content (greeting + input +
   chips) can vertically center in the available space. */
.dash-projects {
  margin-top: 36px;
  width: 100%;
  align-self: stretch;
  background: rgba(255, 255, 255, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.5);
  border-radius: 18px;
  padding: 18px 22px 22px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.6),
    0 1px 2px rgba(31, 22, 17, 0.03),
    0 14px 36px -18px rgba(31, 22, 17, 0.14);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
  backdrop-filter: blur(16px) saturate(140%);
}
body.is-empty-dash .dash-projects {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  margin-top: 0;
  /* Translate down so ONLY the tabs row peeks above viewport bottom —
     scrolling reveals the cards. 72px ≈ tab pill height (52) + a few
     px of breathing room above and below so the pill doesn't look
     guillotined by the viewport edge. */
  transform: translateY(calc(100% - 100px));
  /* Drop the glass on the outer wrapper too in peek mode — at this
     small visible height a translucent surface looks washed-out
     against the warm gradient. Give it a clean solid surface. */
  background: var(--paper, #fff);
  border: 1px solid var(--hairline);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  box-shadow: 0 -10px 32px -16px rgba(31, 22, 17, 0.16), 0 1px 2px rgba(31, 22, 17, 0.04);
}
/* Empty-dash override: pin the strip to the bottom of the .dash-empty-hero
   section (which is min-height: 100vh) so it "peeks" the way Lovable's
   recents strip does. Scrolling the page reveals more of the cards.
   Also breaks out of .dash's 940px max-width using viewport-relative width
   so the strip spans the full content area (viewport minus the 240px sidebar).
   left:50% + transform:translateX(-50%) keeps it centered on the same x-axis
   as the hero column. */
/* 2026-05-12: re-enable the projects strip on home. The 2026-05-09
   pass hid it because the page felt empty for users with no drafts —
   but with the new live-site card + quick actions row above it, the
   strip now reads as "your recent work" rather than "your one site",
   which is what Lovable's recents strip does. Drafts cap at 3 by
   default so the grid stays compact. */

/* Sidebar drafts count badge — quiet, neutral. Sits to the right of
   the "Drafts" label without pulling the eye like a notification dot. */
.side-link-count {
  margin-left: auto;
  min-width: 18px;
  height: 18px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--hairline);
  color: var(--ink-mute);
  font-size: 11px;
  font-weight: 500;
  line-height: 18px;
  text-align: center;
}
.side-link.is-active .side-link-count {
  background: rgba(31, 22, 17, 0.08);
  color: var(--ink);
}

/* Sidebar site-status dot — sits at the right of the "My website"
   row. Green when live, grey when paused, hidden when no site. */
.side-link-status {
  margin-left: auto;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--hairline);
}
.side-link-status[data-status="live"] {
  background: #2f9e44;
  box-shadow: 0 0 0 2px rgba(47, 158, 68, 0.18);
}
.side-link-status[data-status="paused"] {
  background: var(--ink-mute);
  opacity: 0.55;
}

/* Active-draft selector — sits inside the chat input's bottom bar,
   on the right next to the submit arrow. Quiet pill that shows which
   draft the input is routed to, opens a popover above the input on
   click. */
.builder-input-bar__right {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.dash-active-draft {
  position: relative;
  display: inline-flex;
}
.dash-active-draft__button {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 8px;
  background: transparent;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 0.8rem;
  color: var(--ink-mute);
  cursor: pointer;
  transition: color 0.15s, background 0.15s, border-color 0.15s;
  max-width: 240px;
}
.dash-active-draft__button:hover {
  color: var(--ink);
  border-color: var(--ink-mute);
  background: rgba(31, 22, 17, 0.03);
}
.dash-active-draft__label {
  flex-shrink: 0;
}
.dash-active-draft__name {
  color: var(--ink);
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.dash-active-draft__button[aria-expanded="true"] svg { transform: rotate(180deg); }
.dash-active-draft__button svg { transition: transform 0.15s; flex-shrink: 0; }

/* Popover with the list of drafts. Pops up ABOVE the chat input so
   it doesn't shove the page down. Anchored to the right edge of the
   selector since the selector now sits on the right of the bar.
   Max-height with scroll handles users who have a lot of drafts. */
.dash-draft-popover {
  position: absolute;
  bottom: calc(100% + 8px);
  right: 0;
  left: auto;
  min-width: 260px;
  max-width: 360px;
  max-height: 320px;
  overflow-y: auto;
  background: var(--paper, #fff);
  border: 1px solid var(--hairline);
  border-radius: 10px;
  box-shadow: 0 10px 30px rgba(31, 22, 17, 0.08), 0 2px 6px rgba(31, 22, 17, 0.04);
  padding: 6px;
  z-index: 20;
}
.dash-draft-popover__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  padding: 8px 10px;
  background: transparent;
  border: 0;
  border-radius: 6px;
  font-family: inherit;
  font-size: 0.86rem;
  color: var(--ink);
  cursor: pointer;
  text-align: left;
  transition: background 0.12s;
}
.dash-draft-popover__item:hover { background: rgba(31, 22, 17, 0.05); }
.dash-draft-popover__item.is-active { color: var(--ink); }
.dash-draft-popover__item.is-active .dash-draft-popover__name { font-weight: 500; }
.dash-draft-popover__name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1 1 auto;
  min-width: 0;
}
.dash-draft-popover__sep {
  height: 1px;
  background: var(--hairline);
  margin: 4px 6px;
}
.dash-draft-popover__item--new {
  color: var(--ink-mute);
  font-weight: 500;
}
.dash-draft-popover__item--new:hover { color: var(--ink); }
@media (max-width: 720px) {
  body.is-empty-dash .dash-projects {
    width: calc(100vw - 28px);
    bottom: -200px;
  }
}
/* Tabs header — Lovable-style. The .dash-projects-header is the flex row.
   The .dash-projects-tabs is an inline-flex glassy pill container with
   the active tab as a white inner pill. The "+ New chat" link sits to
   the right OUTSIDE the pill. */
.dash-projects-header {
  display: flex;
  align-items: center;
  margin-bottom: 18px;
  gap: 12px;
}
.dash-projects-tabs {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 4px;
  background: var(--cream-2, #F4F4F4);
  border: 1px solid var(--hairline);
  border-radius: 999px;
}
.dash-tab {
  background: transparent;
  border: 0;
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 0.86rem;
  font-weight: 500;
  color: var(--ink-mute);
  padding: 6px 14px;
  border-radius: 999px;
  cursor: pointer;
  transition: color 0.15s, background 0.15s, box-shadow 0.15s;
}
.dash-tab:hover { color: var(--ink); }
.dash-tab.is-on {
  color: var(--ink);
  background: var(--paper, #fff);
  box-shadow: 0 1px 2px rgba(31, 22, 17, 0.08);
}
.dash-projects-all {
  margin-left: auto;
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 0.84rem;
  font-weight: 500;
  color: var(--ink-mute);
  text-decoration: none;
  padding: 6px 4px;
  transition: color 0.15s;
}
.dash-projects-all:hover { color: var(--ink); }
.dash-projects-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
  width: 100%;
}
.dash-project-card {
  display: flex;
  flex-direction: column;
  background: var(--paper, #fff);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  overflow: hidden;
  text-decoration: none;
  color: var(--ink);
  transition: border-color 0.18s cubic-bezier(0.2, 0, 0, 1), transform 0.18s cubic-bezier(0.2, 0, 0, 1);
  position: relative;
}
.dash-project-card:hover {
  border-color: var(--ink-mute);
  transform: translateY(-1px);
}

/* Per-card delete button (founder 2026-05-16). Hidden until the card
   is hovered or the button itself is focused (keyboard). Positioned
   top-right above the thumb. White circular background so it stays
   readable against any thumbnail. Warm-stone palette — no red on
   default state; red appears on hover to signal destructive intent. */
.dash-project-card__delete {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 2;
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--paper, #fff);
  color: var(--ink-mute);
  border: 1px solid var(--hairline);
  border-radius: 8px;
  cursor: pointer;
  opacity: 0;
  transform: scale(0.92);
  transition: opacity 0.14s, transform 0.14s, color 0.14s, border-color 0.14s, background 0.14s;
  appearance: none;
  padding: 0;
}
.dash-project-card:hover .dash-project-card__delete,
.dash-project-card__delete:focus-visible {
  opacity: 1;
  transform: scale(1);
}
.dash-project-card__delete:hover {
  color: #7c2218;
  border-color: #c4534a;
  background: #fdf3f1;
}
.dash-project-card--newchat .dash-project-card__delete { display: none; }

/* 2026-05-16 v2: 3-dot menu button. Lives in the card's meta row
   (NOT absolute-positioned on the thumbnail) so it's always visible —
   matches Lovable's project-card layout where the action is
   discoverable without hovering. */
.dash-project-card__menu {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  color: var(--ink-mute);
  border: 1px solid transparent;
  border-radius: 8px;
  cursor: pointer;
  appearance: none;
  padding: 0;
  margin-left: 10px;
  transition: background 0.14s, color 0.14s, border-color 0.14s;
}
.dash-project-card__menu:hover,
.dash-project-card__menu:focus-visible,
.dash-project-card__menu[aria-expanded="true"] {
  background: var(--cream-2, #F4F4F4);
  color: var(--ink, #1F1611);
  border-color: var(--hairline);
}
.dash-project-card--newchat .dash-project-card__menu { display: none; }

/* 2026-05-16 P4: shared dropdown popover that opens beneath the clicked
   3-dot button. Positioned absolutely via JS — top/left set per-click.
   Single instance mounted near <body>. */
.dash-card-menu-popover {
  position: absolute;
  z-index: 1200;
  min-width: 200px;
  background: var(--paper, #fff);
  border: 1px solid var(--hairline);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(31, 22, 17, 0.12);
  padding: 6px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  animation: dashCardMenuAppear 0.16s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.dash-card-menu-popover[hidden] { display: none; }
@keyframes dashCardMenuAppear {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.dash-card-menu-popover__item {
  appearance: none;
  background: transparent;
  border: 0;
  text-align: left;
  font: inherit;
  font-size: 0.9rem;
  color: var(--ink, #1F1611);
  padding: 7px 10px;
  border-radius: 6px;
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.dash-card-menu-popover__item:hover { background: var(--cream-2, #F4F4F4); }
.dash-card-menu-popover__item:disabled {
  color: var(--ink-mute, #8a8378);
  cursor: not-allowed;
  opacity: 0.55;
}
.dash-card-menu-popover__item:disabled:hover { background: transparent; }
.dash-card-menu-popover__item.is-danger { color: #b53527; }
.dash-card-menu-popover__item.is-danger:hover { background: #fdf3f1; color: #7c2218; }
.dash-card-menu-popover__divider {
  height: 1px;
  background: var(--hairline);
  margin: 4px 0;
}
.dash-project-card__thumb {
  height: 180px;
  background: linear-gradient(135deg, var(--cream-2) 0%, var(--cream) 100%);
  border-bottom: 1px solid var(--hairline);
  position: relative;
  overflow: hidden;
}
.dash-project-card__thumb--builder { background: linear-gradient(135deg, #E8DECF 0%, #C9B89A 100%); }
.dash-project-card__thumb--plumber { background: linear-gradient(135deg, #D9E4E8 0%, #A6BDC4 100%); }
.dash-project-card__thumb--painter { background: linear-gradient(135deg, #E8D9CE 0%, #C9A98F 100%); }

/* Pre-live placeholder thumb — keeps the cream gradient bg, centres
   the BS mark at low opacity so the card reads as "BookingSprint
   draft, not yet generated" instead of "empty block". Covers
   collecting / generating / failed statuses. */
.dash-project-card__thumb--placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
}
.dash-project-card__thumb-mark {
  width: 72px;
  height: 72px;
  fill: var(--ink);
  opacity: 0.22;
}

/* Live preview thumb — scaled-down iframe of the generated site.
   The iframe renders at 1280x800 desktop equivalent then transforms
   to fit the 140px-tall card. pointer-events: none lets clicks fall
   through to the parent card anchor. */
.dash-project-card__thumb--live {
  background: var(--paper, #fff);
}
.dash-project-card__iframe {
  width: 1280px;
  height: 800px;
  border: 0;
  display: block;
  /* Scale 1280 -> ~card width (~360px) -> ratio ~0.28. Use 0.3 so
     small variations in card width still fill the thumb. */
  transform: scale(0.3);
  transform-origin: top left;
  pointer-events: none;
  background: var(--paper, #fff);
}
/* 2026-05-16 v2: meta row is flex so title+sub sit on the left and
   the 3-dot button sits on the right, always visible. */
.dash-project-card__meta {
  padding: 14px 16px;
  display: flex;
  align-items: center;
  gap: 10px;
}
.dash-project-card__meta-text {
  flex: 1 1 auto;
  min-width: 0; /* allow title ellipsis */
}
.dash-project-card__title {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-weight: 500;
  font-size: 0.95rem;
  color: var(--ink);
  line-height: 1.3;
  margin-bottom: 4px;
}
/* Scope the single-line ellipsis to the meta-text context so the
   empty-state title (which uses centered multi-line copy) isn't
   clipped to a single line. */
.dash-project-card__meta-text .dash-project-card__title {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.dash-project-card__sub {
  font-size: 0.82rem;
  color: var(--ink-mute);
  line-height: 1.4;
}
.dash-project-card--empty {
  align-items: center;
  text-align: center;
  padding: 28px 22px;
  grid-column: 1 / -1;
  background: transparent;
  border: 0;
  box-shadow: none;
}
.dash-project-card--empty:hover { transform: none; border-color: transparent; }

/* Drafts page — top pinned section separator. Sits between the
   live-site + new-chat tiles and the rest of the drafts grid. */
.drafts-section-heading {
  grid-column: 1 / -1;
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 0.74rem;
  font-weight: 600;
  color: var(--ink-mute);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin: 36px 0 14px;
  padding-top: 24px;
  border-top: 1px solid var(--hairline);
}

/* Drafts page — "+ New chat" tile. Lives in the top pinned section
   alongside the live-site card. Dashed border + plus glyph signals
   "create new" rather than "open existing". */
.dash-project-card--newchat {
  background: transparent;
  border: 1px dashed var(--hairline);
}
.dash-project-card--newchat:hover {
  border-color: var(--ink-mute);
  background: var(--cream-2, #F4F4F4);
}
.dash-project-card__thumb--newchat {
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border-bottom: 0;
}
.dash-project-card__newchat-plus {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 3.2rem;
  font-weight: 300;
  line-height: 1;
  color: var(--ink-mute);
  transition: color 0.18s;
}
.dash-project-card--newchat:hover .dash-project-card__newchat-plus { color: var(--ink); }

/* Drafts page — live-site card "LIVE" badge in the top-right of the
   thumb. Green dot + caps label. Marks the pinned card visually. */
.dash-project-card__live-badge {
  position: absolute;
  top: 10px;
  right: 10px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px 4px 8px;
  background: rgba(255, 255, 255, 0.92);
  border: 1px solid rgba(31, 22, 17, 0.08);
  border-radius: 999px;
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--ink);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  box-shadow: 0 2px 6px rgba(31, 22, 17, 0.08);
  z-index: 2;
}
.dash-project-card__live-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #2BAB5C;
  box-shadow: 0 0 0 2px rgba(43, 171, 92, 0.18);
}

/* "View all drafts (N)" toggle below the My website grid. Spans the
   full row so it reads as a single discrete control rather than a
   fourth card. Same warm-stone palette as the rest of the surface. */
.dash-projects-toggle {
  grid-column: 1 / -1;
  margin-top: 4px;
  padding: 10px 14px;
  background: transparent;
  border: 1px solid var(--hairline);
  border-radius: 10px;
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--ink-mute);
  cursor: pointer;
  transition: border-color 0.18s, color 0.18s;
}
.dash-projects-toggle:hover {
  border-color: var(--ink-mute);
  color: var(--ink);
}
.dash-project-card--empty .dash-project-card__mark { margin-bottom: 12px; opacity: 0.55; }
.dash-project-card--empty .dash-project-card__mark img { display: block; }

@media (max-width: 720px) {
  .dash-projects-grid { grid-template-columns: 1fr; }
}

/* ---------------------------------------------------------------------
   2026-05-28: Drafts page slim-card + button-row pattern.
   Replaces the previous hero-tile pinned card + dashed "+New" hero tile.
   New layout uses far less vertical space on mobile and gives the
   actual drafts list room to breathe.
--------------------------------------------------------------------- */
.drafts-live-card-slim {
  display: grid;
  grid-template-columns: 72px 1fr;
  gap: 14px;
  align-items: center;
  background: #F5F5F5;
  border: 1px solid rgba(31, 22, 17, 0.06);
  border-radius: 14px;
  padding: 14px;
  margin-bottom: 10px;
  box-shadow: 0 1px 2px rgba(31, 22, 17, 0.04);
}
/* Square thumb — matches the drafts-row thumb visual rhythm so the
   live card sits in the same family as the rows below. Slightly larger
   than the drafts row (72 vs 56) because it's the pinned hero card.
   Renders the site at mobile viewport width and crops to a square. */
.drafts-live-card-slim__thumb {
  position: relative;
  display: block;
  width: 72px;
  height: 72px;
  border-radius: 12px;
  overflow: hidden;
  background: linear-gradient(180deg, #FFFFFF 0%, #F2F2F2 100%);
  border: 1px solid rgba(31, 22, 17, 0.10);
  box-shadow: 0 1px 2px rgba(31, 22, 17, 0.04);
  flex-shrink: 0;
}
.drafts-live-card-slim__iframe {
  width: 414px;
  height: 414px;
  border: 0;
  display: block;
  transform: scale(0.174);
  transform-origin: top left;
  pointer-events: none;
}
.drafts-live-card-slim__live-dot {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #16A34A;
  box-shadow: 0 0 0 2px rgba(255,255,255,0.95);
}
.drafts-live-card-slim__meta {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-rows: auto auto;
  column-gap: 12px;
  row-gap: 4px;
  align-items: center;
  min-width: 0;
}
/* Compact layout: title (top-left) + URL (bottom-left) + action buttons
   (top-right, spanning both rows). Eyebrow dropped — the green live
   dot on the thumb already signals "this is the live site". */
.drafts-live-card-slim__eyebrow { display: none; }
.drafts-live-card-slim__title {
  grid-column: 1;
  grid-row: 1;
  font-family: 'Geist', sans-serif;
  font-size: 0.98rem;
  font-weight: 600;
  letter-spacing: -0.012em;
  color: var(--ink, #2a261f);
  line-height: 1.2;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.drafts-live-card-slim__url {
  grid-column: 1;
  grid-row: 2;
  font-family: 'Geist Mono', 'JetBrains Mono', monospace;
  font-size: 11.5px;
  color: var(--ink-mute, #8a857c);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.drafts-live-card-slim__actions {
  grid-column: 2;
  grid-row: 1 / span 2;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
/* 3D-gradient buttons. Same recipe as the brand .mob-topbar-burger /
   metric dropdown pills: white-to-cream gradient, hairline with a
   darker bottom edge, soft outer shadow, inset top highlight. Primary
   (Edit) is the inverted dark variant — ink gradient + cream-tinted
   inset top stripe so the dimensionality still reads on the dark fill. */
.drafts-live-card-slim__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 32px;
  padding: 0 14px;
  border-radius: 9px;
  background: linear-gradient(180deg, #FFFFFF 0%, #F8F8F8 100%);
  border: 1px solid rgba(31, 22, 17, 0.13);
  border-bottom-color: rgba(31, 22, 17, 0.19);
  color: var(--ink, #1F1611);
  font-family: 'Geist', sans-serif;
  font-size: 0.82rem;
  font-weight: 500;
  letter-spacing: -0.005em;
  text-decoration: none;
  box-shadow:
    0 1px 3px rgba(31, 22, 17, 0.07),
    0 2px 1px -1px rgba(31, 22, 17, 0.04),
    inset 0 1px 0 rgba(255, 255, 255, 0.9);
  transition: all 0.15s ease;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  user-select: none;
}
.drafts-live-card-slim__btn:hover {
  border-color: rgba(31, 22, 17, 0.2);
  border-bottom-color: rgba(31, 22, 17, 0.26);
  box-shadow:
    0 2px 5px rgba(31, 22, 17, 0.09),
    0 2px 1px -1px rgba(31, 22, 17, 0.04),
    inset 0 1px 0 rgba(255, 255, 255, 0.9);
}
.drafts-live-card-slim__btn:active {
  transform: translateY(0.5px);
  box-shadow:
    0 0 2px rgba(31, 22, 17, 0.05),
    inset 0 1px 0 rgba(255, 255, 255, 0.7);
}
.drafts-live-card-slim__btn--primary {
  background: linear-gradient(180deg, #2A1F17 0%, #1F1611 100%);
  color: #FFFFFF;
  border-color: #14100C;
  border-bottom-color: #0A0705;
  box-shadow:
    0 1px 3px rgba(0, 0, 0, 0.18),
    0 2px 1px -1px rgba(0, 0, 0, 0.08),
    inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.drafts-live-card-slim__btn--primary:hover {
  background: linear-gradient(180deg, #332620 0%, #1F1611 100%);
  border-color: #14100C;
  border-bottom-color: #0A0705;
  box-shadow:
    0 2px 6px rgba(0, 0, 0, 0.22),
    0 2px 1px -1px rgba(0, 0, 0, 0.08),
    inset 0 1px 0 rgba(255, 255, 255, 0.10);
}
.drafts-live-card-slim__btn--primary:active {
  transform: translateY(0.5px);
  box-shadow:
    0 0 2px rgba(0, 0, 0, 0.15),
    inset 0 1px 0 rgba(255, 255, 255, 0.05);
}

/* "+ New chat" button-row — pinned full width above the drafts list. */
.drafts-newchat-row {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 52px;
  padding: 0 16px;
  background: linear-gradient(180deg, #FFFFFF 0%, #F8F8F8 100%);
  border: 1px solid rgba(31, 22, 17, 0.13);
  border-bottom-color: rgba(31, 22, 17, 0.19);
  border-radius: 12px;
  color: var(--ink, #1F1611);
  text-decoration: none;
  box-shadow: 0 1px 2px rgba(31, 22, 17, 0.05), inset 0 1px 0 rgba(255,255,255,0.9);
  transition: all 0.14s ease;
  margin-bottom: 18px;
}
/* When there's no live card above (fresh user), the new-chat row is
   the first child of [data-drafts-top] and sits right under the page
   header with no breathing room. Push it down so the spacing matches
   the with-live-card layout. */
[data-drafts-top] > .drafts-newchat-row:first-child {
  margin-top: 14px;
}
.drafts-newchat-row:hover {
  border-color: rgba(31, 22, 17, 0.2);
  border-bottom-color: rgba(31, 22, 17, 0.26);
}
.drafts-newchat-row:active { transform: translateY(0.5px); }
.drafts-newchat-row__plus {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 8px;
  background: rgba(31, 22, 17, 0.06);
  color: var(--ink, #1F1611);
  flex-shrink: 0;
}
.drafts-newchat-row__label {
  font-family: 'Geist', sans-serif;
  font-size: 0.92rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink, #1F1611);
}
.drafts-newchat-row__sub {
  font-family: 'Geist', sans-serif;
  font-size: 0.8rem;
  color: var(--ink-mute, #8a857c);
  margin-left: auto;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
@media (max-width: 480px) {
  .drafts-newchat-row__sub { display: none; }
}

/* Drafts empty state — matches the .leads-tab-empty rhythm from the
   Leads page so the two empty states feel consistent. Circular faded
   icon + bold title + muted sub, centered. */
.drafts-tab-empty {
  text-align: center;
  padding: 48px 24px;
  margin-top: 12px;
  color: #6B5A4C;
}
.drafts-tab-empty__icon {
  width: 48px; height: 48px;
  margin: 0 auto 14px;
  border-radius: 50%;
  background: rgba(31, 22, 17, 0.04);
  display: flex; align-items: center; justify-content: center;
  color: #A39585;
}
.drafts-tab-empty__title {
  font-size: 0.95rem;
  font-weight: 600;
  color: #1F1611;
  margin-bottom: 4px;
}
.drafts-tab-empty__sub {
  font-size: 0.825rem;
  color: #6B5A4C;
  line-height: 1.5;
  max-width: 280px;
  margin: 0 auto;
}

/* Slim draft row — replaces the 3-up tile grid for the Drafts list.
   Each row is ~72px tall: 56px thumb on the left, name + status sub
   in the middle, kebab on the right. Stacks vertically with a thin
   hairline between rows. The parent .dash-projects-grid still wraps
   the rows but the grid template is collapsed to a single column so
   the rows lay out top-to-bottom regardless of viewport. */
.dash-projects-grid:has(.drafts-row) {
  grid-template-columns: 1fr;
  gap: 0;
  border-top: 1px solid var(--hairline, #e7e2da);
}
.drafts-row {
  display: grid;
  grid-template-columns: 56px 1fr auto;
  gap: 14px;
  align-items: center;
  padding: 12px 14px 12px 12px;
  text-decoration: none;
  color: var(--ink, #2a261f);
  border-bottom: 1px solid var(--hairline, #e7e2da);
  transition: background 0.14s ease;
  background: transparent;
}
.drafts-row:hover { background: rgba(31, 22, 17, 0.03); }
.drafts-row__thumb {
  position: relative;
  width: 56px;
  height: 56px;
  border-radius: 10px;
  overflow: hidden;
  background: linear-gradient(180deg, #FFFFFF 0%, #F2F2F2 100%);
  border: 1px solid rgba(31, 22, 17, 0.07);
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.drafts-row__thumb--placeholder { color: rgba(31, 22, 17, 0.28); }
.drafts-row__thumb-mark {
  width: 22px;
  height: 22px;
  stroke: var(--ink, #1F1611);
  opacity: 0.28;
}
.drafts-row__iframe {
  width: 1440px;
  height: 900px;
  border: 0;
  display: block;
  transform: scale(0.039);
  transform-origin: top left;
  pointer-events: none;
}
.drafts-row__meta {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.drafts-row__title {
  font-family: 'Geist', sans-serif;
  font-size: 0.92rem;
  font-weight: 600;
  letter-spacing: -0.012em;
  color: var(--ink, #2a261f);
  line-height: 1.25;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.drafts-row__sub {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: 'Geist', sans-serif;
  font-size: 0.78rem;
  color: var(--ink-mute, #8a857c);
  line-height: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.drafts-row__sep { opacity: 0.5; }
.drafts-row__dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  flex-shrink: 0;
  background: #b3a594;
}
.drafts-row__dot--live { background: #16A34A; }
.drafts-row__dot--gen  { background: #D97706; }
.drafts-row__dot--failed { background: #B91C1C; }
.drafts-row__dot--draft  { background: #B3A594; }
.drafts-row__menu {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px;
  border: 0; background: transparent;
  border-radius: 8px;
  color: var(--ink-mute, #8a857c);
  cursor: pointer;
  flex-shrink: 0;
}
.drafts-row__menu:hover { background: rgba(31, 22, 17, 0.06); color: var(--ink, #2a261f); }
.drafts-row__menu:focus-visible { outline: 2px solid var(--orange, #d97706); outline-offset: 2px; }

@media (max-width: 720px) {
  /* Loose-scroll + off-center fix.
     The mobile mob-topbar is position:fixed at 52px; .shell already adds
     padding-top: 52px for it. .dash + .dash-empty-hero both carried
     min-height: calc(100vh - 32px), which on iOS Safari (where 100vh
     includes the hidden URL bar) overshoots the visible viewport and
     stacks with .dash's own 48px vertical padding to produce a ~70px
     loose scroll. The shared min-height also pushed the flex-centered
     hero into the lower third of the viewport. Switch to 100dvh, drop
     .dash's vertical padding when the empty hero is showing, and let
     align-self: stretch on the grid row size the hero. */
  body.is-empty-dash .dash {
    min-height: calc(100dvh - 52px);
    padding-top: 0;
    padding-bottom: 0;
  }
  .dash-empty-hero { padding: 24px 0; }
  body.is-empty-dash .builder-instruction { display: none; }
  body.is-empty-dash .dash-empty-hero .builder-suggestion-row { display: none; }
  /* Refine chips DO stay visible on mobile — they're the most useful
     surface for a tradie on a jobsite who can't think of what to type. */
  body.is-empty-dash .dash-empty-hero.is-refine-mode .builder-refine-suggestions {
    display: flex;
    padding: 0 8px;
  }
  body.is-empty-dash .dash-empty-hero.is-refine-mode .builder-refine-suggestions .builder-suggestion-chip {
    font-size: 0.8rem;
    padding: 6px 12px;
  }
  .dash-projects { display: none !important; }

  /* Logo stays on the side of the heading at all sizes. Smaller logo
     opens up enough room to keep the heading at a comfortable readable
     size while still fitting on one row at 390px-wide viewports. */
  /* Stack logo above heading on mobile. The desktop side-by-side row works
     for the short "Let's build your website." line but goes goofy when the
     refine-mode line ("Want to refine your website?") wraps to two lines —
     the logo ends up beside a left-aligned text block that visually drifts
     off-centre. Stacking + centring the wrapped text keeps both states
     symmetric on narrow viewports. */
  .builder-empty-hero {
    flex-direction: column;
    align-items: center;
    gap: 12px;
    margin-bottom: 24px;
    justify-content: center;
  }
  .builder-empty-mark img { width: 40px; height: 40px; }
  .builder-empty-greeting {
    font-size: 1.9rem;
    line-height: 1.15;
    text-align: center;
  }
  .builder-instruction {
    font-size: 0.88rem;
    margin-bottom: 20px;
    padding: 0 4px;
  }

  /* Bigger textarea so the placeholder wraps within its visible area
     instead of clipping. */
  .dash-empty-form .builder-input-textarea {
    min-height: 50px;
    font-size: 0.75rem;
  }
  .dash-empty-form .builder-input-textarea::placeholder {
    white-space: normal;
  }

  /* ────────────────────────────────────────────────────────────
     Phase 3.5 (2026-05-17): Claude-style composer polish.

     The user compared BookingSprint mobile against the Claude
     iOS app and called ours "bonangly and kind of compact".
     Claude's chat composer is a substantial 130px+ tray-shaped
     surface on a subtle warm-gray background — it reads as a
     destination, not a control. Our 48px white rectangle by
     comparison felt cramped.

     This pass adopts Claude's composer language while keeping
     the BookingSprint warm palette + the trade chips that help
     a first-time tradie know what to type.

     Key moves:
       - Composer bg: white → var(--cream-2) (subtle warm gray)
       - Min-height: 48 → 72 (composer feels substantial)
       - Radius: 18 → 22 (softer corners)
       - Shadow: drop the heavy desktop drop-shadow; the warm-gray
         surface plus a 1px hairline carries enough definition
       - Hero spacing: more breathing room between greeting +
         instruction + composer (mobile real estate but Claude
         shows generous whitespace works on small screens too)
       - Headline 1.5rem → 1.6rem (slightly bigger; with the
         bigger composer the ratio stays comfortable)
       - Instruction text: smaller + lighter so it doesn't
         compete with the headline
       - Trade chips: were a 2-column grid of full chips with
         icons; now a single horizontal scroll row with smaller,
         lighter chips. Icons kept (they scan-help) but at lower
         opacity so they recede. Demoted, not removed.
     ──────────────────────────────────────────────────────────── */
  .builder-shell--centered .builder-empty-greeting {
    font-size: 1.6rem;
    line-height: 1.2;
    text-align: center;
  }
  .builder-shell--centered .builder-empty-mark img {
    width: 34px;
    height: 34px;
  }
  .builder-shell--centered .builder-empty-hero {
    margin-bottom: 18px;
  }
  .builder-shell--centered .builder-instruction {
    font-size: 0.83rem;
    color: var(--ink-soft);
    margin-bottom: 32px;
    max-width: 300px;
  }

  /* Composer — Claude-balanced chrome (Phase 3.9, 2026-05-17).
     Founder feedback after Phase 3.8: "better but it's a bit too
     small now, same with the text". 3.8 over-shrank — bringing the
     padding, textarea height, and button sizes back up halfway to
     Phase 3.7 dimensions while keeping the white surface, hairline
     border, single-line-placeholder fix, and context-aware
     placeholder swap.

     Phase 3.9 dial-up from 3.8:
       - Composer total ~96px (was ~70px after 3.8, ~125px after 3.7)
       - Padding 12/14/10 (was 6/10) — actual breathing room
       - Border-radius 22 (was 20)
       - Textarea min-height 32 (was 28), line-height 1.5 (was 1.4)
         — text reads larger without bumping font-size off iOS's 16px
         no-zoom floor
       - Send button 36 (was 32), icon 15px (was 14)
       - Attach button 28 (was 24), icon 15px (was 14)
       - Bar min-height 36 (was 32) — button row matches send size */
  .builder-shell--centered .builder-input-box {
    /* Founder 2026-05-30: visuals moved to builder-mobile.css.
       Empty rule kept as a comment-marker so future readers know
       where the original mobile override used to live. */
  }
  .builder-shell--centered .builder-input-textarea {
    font-size: 16px;
    min-height: 32px;
    max-height: 120px;
    line-height: 1.5;
    background: transparent;
    padding: 4px 4px 0;
    field-sizing: content;
  }
  /* When empty (showing the placeholder), constrain to a single line
     and clip any placeholder overflow so a long example doesn't bloat
     the composer height. Without this, "Sparky in Newtown. EV chargers
     and switchboards." wraps to 2-3 lines and the composer ends up
     50-80px taller than it needs to be. */
  .builder-shell--centered .builder-input-textarea:placeholder-shown {
    max-height: 32px;
    overflow: hidden;
  }
  .builder-shell--centered .builder-input-textarea::placeholder {
    color: var(--ink-soft);
  }
  .builder-shell--centered .builder-input-bar {
    margin-top: 0;
    min-height: 36px;
  }
  .builder-shell--centered .builder-input-send {
    width: 36px;
    height: 36px;
  }
  .builder-shell--centered .builder-input-send svg {
    width: 15px;
    height: 15px;
  }
  /* Founder 2026-05-30: keep 28px mobile size BUT inherit the new
     3D gradient from the base .builder-input-action rule. The
     previous transparent + border:0 reset was making the buttons
     invisible on mobile — now they're small but tactile. */
  .builder-shell--centered .builder-input-action,
  .builder-shell--centered .builder-input-more,
  .builder-shell--centered .builder-input-mic,
  .builder-shell--centered .builder-input-map {
    width: 28px;
    height: 28px;
    /* background, border, box-shadow inherited from base rule
       (3D white→cream gradient with inset highlight + drop shadow) */
  }
  .builder-shell--centered .builder-input-action svg,
  .builder-shell--centered .builder-input-more svg,
  .builder-shell--centered .builder-input-mic svg,
  .builder-shell--centered .builder-input-map svg {
    width: 14px;
    height: 14px;
    opacity: 1;
  }
  .builder-shell--centered .builder-input-model {
    display: none;
  }

  /* Layout: composer MUST pin to the bottom of the chat pane in every
     state — empty, mid-chat, ready. Without this, once a chat row lands
     and .builder-instruction (which had margin-bottom:auto) gets hidden,
     the composer floats up into the middle of the page because nothing
     else is consuming the slack. margin-top:auto on .builder-input-wrap
     pushes the composer down regardless of which sibling above it is
     visible. */
  .builder-shell--centered .builder-input-wrap {
    margin-top: auto !important;
  }
  /* When the chat thread has content, kill the cascade that pushed the
     instruction (margin-bottom:auto) so chat-body keeps a normal
     stacking flow and the input stays glued to the bottom edge. */
  .builder-shell--centered:has(.builder-chat-body:not(:empty)) .builder-instruction {
    margin-bottom: 0 !important;
  }
  /* Phase 4.6: chat-body grows to fill the pane above the fixed
     composer. Content flows top-down (default flex-start); for the
     form, the JS scrolls the form's top into view on append so the
     user lands at "Business name". For short chat content (thinking
     state), content sits at the top with empty space below the
     content. That gap reads as "AI is working" rather than a layout
     bug, especially since the composer below has a soft fade scrim
     making it feel like content gently disappears into it. */
  .builder-shell--centered .builder-chat-body {
    flex: 1 1 0% !important;
    overflow-y: auto;
    min-height: 0;
  }
  /* Composer pinned to the VIEWPORT bottom (Claude-exact), not the
     pane bottom. With this the chat bar is always at the bottom of
     the screen whether the chat is empty, mid-thinking, mid-form, or
     a long thread — no whitespace floating above it. Width hugs the
     pane edges (left/right 16) so it doesn't stretch edge-to-edge
     over the safe-area side insets. z-index 5 keeps it above the
     scrolling chat content. The padding-top + linear-gradient
     bottom fade creates a soft scrim so chat content scrolling
     under the composer fades out cleanly instead of cutting
     hard against the white surface. */
  /* Phase 4.8 fix (code-review): gate position:fixed on the empty
     state ONLY. In the ready state, the chat-pane itself is a
     position:fixed bottom sheet (line ~6850) that contains chat-body
     + composer. If the composer is ALSO position:fixed, it escapes
     the sheet and floats at viewport bottom independently — produces
     a duplicate-composer effect (one inside the sheet, one at the
     viewport bottom). Ready state keeps the composer in normal flow
     inside the sheet so the sheet drag still encloses it. */
  .builder-shell--centered:not([data-builder-state="ready"]) .builder-input-wrap {
    position: fixed;
    left: 16px;
    right: 16px;
    bottom: 0;
    z-index: 5;
    /* Zero padding-top: input-box's outer top edge sits exactly at
       composer.outer.top. Combined with shellSyncChatPanePadBot's
       reserve of EXACTLY composer.height (no extra), the chat-body
       content's bottom lands at composer.outer.top = input-box.top.
       The Build button's bottom edge touches the input-box top edge
       with 0 visible cream gap — true "right against the chat bar".
       The 8px fade band still exists below composer.outer.top but
       is fully covered by the input-box's opaque paper bg, so it's
       only visible during iOS rubber-band overscroll when content
       briefly slides past the input-box and into the fade zone. */
    padding-top: 0;
    padding-bottom: max(12px, env(safe-area-inset-bottom));
    /* Wrap bg matches the sidebar / topbar (var(--topband), #FEFEFD) so
       the bottom-of-page chrome reads as one continuous warm panel —
       sidebar, topbar, composer, safe-area-inset band all the same
       colour. Founder direction 2026-05-22. */
    background: linear-gradient(to bottom,
      rgba(253, 253, 253, 0) 0,
      var(--topband, #FEFEFD) 8px,
      var(--topband, #FEFEFD) 100%);
    margin-top: 0 !important;
  }
  /* 2026-05-22 founder ask — Lovable-parity: the chat thread should
     scroll BEHIND the fixed composer (only cut off at the topbar),
     not stop above it. Previously we set padding-bottom on the
     .builder-chat-pane which clipped the chat-body's outer height so
     content couldn't extend past the composer's top edge — that read
     as the form being "cut off early".
     Fix: pane padding-bottom = 0 so chat-body fills the full pane
     height (down to the viewport bottom). The composer-height reserve
     moves INSIDE the chat-body's scroll content (padding-bottom
     below) so the last item still rests above the composer when
     scrolled to the end, while older content can scroll up through
     the composer's fade band. shellSyncChatPanePadBot (JS) sets the
     exact composer height as inline style on .builder-chat-body.
     Scoped to :has(...) so the empty state (no chat yet) keeps its
     small safe-area-inset bottom padding for the hero+chip stack. */
  .builder-shell--centered:not([data-builder-state="ready"]) .builder-chat-pane:has(.builder-chat-body:not(:empty)) {
    padding-bottom: 0 !important;
  }
  /* Default reserve before the JS measures the composer (170px is the
     same upper-bound the JS targeted: ~120 input-box + ~14 fade band +
     up to 34 safe-area). Inline style from shellSyncChatPanePadBot
     overrides this with the precise height once the composer mounts. */
  .builder-shell--centered:not([data-builder-state="ready"]) .builder-chat-body {
    padding-bottom: 170px !important;
  }

  /* PAGE-SCROLL MODE for the mobile chat thread has been moved to
     /public/dashboard-v2-page.css (canonical block at the bottom of
     that file, 2026-05-22 rewrite v5). Keeping the rules here was
     causing cascade fights: every iteration we'd patch this block,
     it would lose to the inline <style> at dashboard-v2-page.html,
     and the layout would land wrong. The new home is a single
     authoritative block in the page-specific stylesheet (loads
     after dashboard-v2.css, beats earlier rules without a
     specificity arms race). */
  /* Suggestion chips MUST hide as soon as the user starts chatting,
     even though they have display:flex !important on the empty-state
     mobile rule below. Same selector specificity + later in the
     cascade = wins. */
  .builder-shell--centered:has(.builder-chat-body:not(:empty)) .builder-suggestion-row {
    display: none !important;
  }

  /* Refined trade chips on empty state — single horizontal row that
     scrolls instead of a 2x2 grid that fights with the composer for
     vertical real estate. Smaller, lighter, icons demoted to 0.55
     opacity so the chip reads as "secondary suggestion". */
  .builder-shell--centered[data-builder-state="empty"] .builder-suggestion-row {
    display: flex !important;
    grid-template-columns: none !important;
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    gap: 8px !important;
    margin: 14px -16px 0 !important;
    padding: 0 16px 4px;
    max-width: none !important;
    /* Soft right-edge fade so the user knows there's more to scroll. */
    mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 22px), transparent 100%);
    -webkit-mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 22px), transparent 100%);
  }
  .builder-shell--centered[data-builder-state="empty"] .builder-suggestion-row::-webkit-scrollbar {
    display: none;
  }
  .builder-shell--centered[data-builder-state="empty"] .builder-suggestion-row .builder-suggestion-chip {
    flex-shrink: 0;
    padding: 7px 12px;
    font-size: 0.8rem;
    background: transparent;
    border-color: rgba(31, 22, 17, 0.10);
    color: var(--ink-mute);
    white-space: nowrap;
  }
  .builder-shell--centered[data-builder-state="empty"] .builder-suggestion-row .builder-suggestion-chip svg {
    opacity: 0.55;
    width: 12px;
    height: 12px;
  }

  /* Phase 3 step 2: Chat message rows on mobile.
     Tighter vertical spacing (mobile real estate is precious), wider
     user bubbles (85% vs desktop's 75% — narrow screens), slightly
     smaller AI text (0.93rem) for density, link wrapping so long
     subdomains don't overflow. */
  .builder-msg-row {
    gap: 10px;
    margin-top: 14px;
  }
  .builder-msg-row.is-user { margin-top: 20px; }
  .builder-msg-row.is-ai + .builder-msg-row.is-user { margin-top: 24px; }
  .builder-msg-timestamp {
    margin: 20px 0 8px;
    font-size: 0.72rem;
  }
  /* 2026-05-29 founder: AI text was 0.93rem (14.88px) on mobile which made
     the refine chat read visibly SMALLER than the user's own messages
     (1rem / 16px) — the "refine chat shrinks vs builder chat" complaint.
     Bumped to 1.06rem to match the desktop AI size and the documented
     intent at line 4296. The "density" gain was real but the readability
     loss + the size-asymmetry-vs-user-bubbles cost more. */
  .builder-msg-row.is-ai .builder-msg {
    font-size: 1.06rem;
    line-height: 1.5;
  }
  .builder-msg-row.is-user .builder-msg {
    max-width: 85%;
  }
  .builder-msg-link { word-break: break-word; }

  /* Phase 3 step 6: message action icons (retry / thumbs / copy / more)
     bumped from 24px to 36px for a more reachable tap target. Still
     short of WCAG 44px but a meaningful improvement, and these icons
     sit BELOW the actual message body so they don't need the full 44.
     Opacity nudged up because mobile has no hover state. */
  .builder-msg-action {
    width: 36px;
    height: 36px;
    opacity: 0.7;
  }
  .builder-msg-actions {
    gap: 0;
    margin: -2px 0 0 -8px;
  }

  /* Phase 3 step 3: AI thinking pulse on mobile.
     The text-gradient sweep itself works fine at any width — but the
     "Thought for Ns" eyebrow + the .builder-thinking-card container
     need tighter padding so they don't feel like big empty cards on
     a narrow screen. The pulse text stays at its base size. */
  .builder-thinking { gap: 6px; }
  .builder-thinking-eye {
    font-size: 0.78rem;
    margin-bottom: 6px;
  }
  .builder-thinking-pulse { font-size: 0.93rem; }

  /* Phase 3 step 4: Form card on mobile.
     The form card is the inline "tell us your business name / phone /
     services" card that appears after /api/builder/extract resolves
     in the first-message hybrid flow. Every new tradie hits it once.
     - Tighter padding so the card uses every pixel of sheet width
     - Border radius 16 → 14 to match the smaller composer
     - Input font 0.98rem → 1rem (16px) so iOS Safari doesn't zoom in
       when the input gets focus (sub-16px inputs trigger zoom-on-tap)
     - Stagger animation kept (it reads as premium even at this size) */
  .builder-form-card {
    max-width: none;
    padding: 14px 16px 14px;
    border-radius: 14px;
    gap: 10px;
  }
  .builder-form-input,
  .builder-form-chip-input {
    font-size: 1rem;
    padding: 8px 2px 9px;
  }
  /* Phase 4.2 (2026-05-17): founder report — "What you do best"
     textarea was rendering ~250px tall on iOS Safari (vs ~85px in
     Chromium dev). rows="3" + min-height:60 was getting expanded by
     iOS Safari to fit the long placeholder ("20+ years on the tools.
     Same-day callouts. No-mess guarantee.") instead of capping at the
     content area. Lock to a fixed height so the field stays compact
     regardless of placeholder length; resize:vertical (inherited) lets
     the tradie expand manually if they have more to say. */
  .builder-form-textarea {
    field-sizing: fixed;
    height: 80px !important;
    min-height: 80px !important;
    max-height: 140px;
  }

  /* Phase 4.3 (2026-05-17): drop the form-card chrome on mobile so the
     form reads as Claude-style flowing content instead of a floating
     modal. The white-bg + border + shadow + radius worked on desktop
     (card sits next to a fixed sidebar, needs visual containment) but
     on mobile the whole pane IS the form's container — the card chrome
     just creates wasted side-margins + a "modal trapped on a page"
     feel. Edge-to-edge transparent surface lets the fields breathe to
     the page edges and the page feels dense like Claude's. */
  .builder-form-card {
    background: transparent;
    border: 0;
    box-shadow: none;
    border-radius: 0;
    padding: 0;
    /* Strip the card-enter animation too — it pulses the slide-up on
       every form mount which compounds with the chat-body's animation
       to read as twitchy. Static appearance is plenty premium. */
    animation: none;
  }
  .builder-form-row { padding: 0; }
  /* Strip the chat-body padding ONLY when the business-info form-card
     is mounted (the edge-to-edge form wants no chrome between it and
     the pane edges). The :not(.builder-choice-card) qualifier is
     critical — the template + palette pickers also use the
     `.builder-form-card` class (with `.builder-choice-card` added),
     and including them would strip the chat-body's 16px gutter for
     every subsequent message rendered while a picker is on screen,
     making "Launched <biz>" / "<biz> is ready" text flush against
     the left edge (user reported "text not fitting in the screen").
     2026-05-22: keep padding-bottom intact (it's the scroll-behind-
     composer reserve from shellSyncChatPanePadBot). Zeroing all four
     edges previously clipped the form's last fields under the fixed
     composer because the chat-body had no bottom buffer at all. */
  .builder-shell--centered .builder-chat-body:has(.builder-form-card:not(.builder-choice-card)) {
    padding-top: 0 !important;
    padding-left: 0 !important;
    padding-right: 0 !important;
  }
  .builder-form-label { font-size: 0.76rem; }
  .builder-form-help { font-size: 0.74rem; }
  .builder-form-submit {
    font-size: 0.95rem;
    padding: 13px 16px;
    border-radius: 12px;
  }

  /* Phase 3 step 5: Action cards (ask / apply / suggestions) on mobile.
     Each of these renders inline in the chat thread. The base styling
     already wraps cleanly; this pass nudges touch targets up + tightens
     spacing for narrow viewports. */

  /* Ask panel — chips above the composer when the AI asks a follow-up
     question. The panel sits inside .builder-input-box at top. Chips
     wrap to multiple lines on narrow screens; bump tap-target padding
     so each chip is finger-friendly. */
  .builder-input-box .builder-action-ask-panel {
    padding: 10px 12px 10px;
  }
  .builder-action-ask-prompt { font-size: 0.8rem; }
  .builder-action-ask-chip {
    padding: 8px 14px;
    font-size: 0.85rem;
  }

  /* Suggestion chip row (post-AI-reply, e.g. "I want to add a testimonials
     section" — Lovable-style follow-up nudge). Same chip pattern as ask,
     same mobile sizing. */
  .builder-action-suggestion-chip {
    padding: 8px 14px;
    font-size: 0.85rem;
  }

  /* "Updating..." applying state — small inline status, mostly fine
     at any width, just shrink the gap. */
  .builder-action-applying { gap: 8px; }

  /* Projects strip: tighter tabs so 'My website' / 'Templates' /
     'Browse all →' fit on one row at narrow widths. */
  .dash-projects {
    margin-top: 28px;
    padding: 6px 14px 18px;
  }
  .dash-projects-tabs {
    gap: 0;
    margin-bottom: 14px;
  }
  .dash-tab {
    padding: 10px 8px;
    font-size: 0.85rem;
    white-space: nowrap;
  }
  .dash-projects-all {
    font-size: 0.78rem;
    padding: 8px 0 8px 6px;
    white-space: nowrap;
  }
}

/* ============================================================================
   My website page — analytics + manage CTAs + quick edits.
   Section eyebrow + grid of KPI cards (empty by default per CLAUDE.md, no
   fabricated metrics) + 2 manage CTAs + chip-style edit list that routes
   to the AI builder with a starter prompt.
   ========================================================================== */
.my-site-section {
  margin-top: 32px;
}
.my-site-eyebrow {
  font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace;
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 14px;
  font-weight: 500;
}
.my-site-section-sub {
  font-size: 0.88rem;
  color: var(--ink-mute);
  margin: -6px 0 14px;
  line-height: 1.5;
}
.my-site-kpis {
  grid-template-columns: repeat(5, 1fr);
  margin-bottom: 0;
}
@media (max-width: 1100px) {
  .my-site-kpis { grid-template-columns: repeat(3, 1fr); }
}
.my-site-kpi-help {
  font-size: 0.78rem;
  color: var(--ink-faint);
  line-height: 1.4;
  margin-top: 4px;
}
.my-site-cta-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
.my-site-cta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 18px 20px;
  background: var(--paper, #fff);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  text-decoration: none;
  color: var(--ink);
  transition: border-color 0.18s cubic-bezier(0.2, 0, 0, 1), transform 0.18s cubic-bezier(0.2, 0, 0, 1);
}
.my-site-cta:hover {
  border-color: var(--ink-mute);
  transform: translateY(-1px);
}
.my-site-cta-title {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-weight: 500;
  font-size: 1.02rem;
  letter-spacing: -0.012em;
  color: var(--ink);
  margin-bottom: 4px;
}
.my-site-cta-sub {
  font-size: 0.85rem;
  color: var(--ink-mute);
  line-height: 1.4;
}
.my-site-cta-arrow {
  display: inline-flex;
  color: var(--ink-mute);
  flex-shrink: 0;
}
.my-site-edit-row {
  text-decoration: none;
  color: var(--ink);
  cursor: pointer;
  transition: background 0.14s cubic-bezier(0.2, 0, 0, 1);
}
.my-site-edit-row:hover {
  background: var(--cream-2);
}
.my-site-edit-hint {
  font-size: 0.78rem;
  color: var(--ink-faint);
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.my-site-edit-arrow {
  font-size: 1.05rem;
  color: var(--ink-faint);
  line-height: 1;
}

@media (max-width: 720px) {
  .my-site-kpis { grid-template-columns: 1fr; }
  .my-site-cta-row { grid-template-columns: 1fr; }
}

/* URL strip — sits between the page header and the iframe preview.
   Two visual states driven by [data-my-site-url-state]:
   "published" (live URL with Copy + Open buttons) and "unpublished"
   (placeholder URL with a Publish CTA). */
.my-site-url {
  margin: 18px 0 0;
}
.my-site-url__inner {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
  background: var(--paper, #fff);
  border: 1px solid var(--hairline);
  border-radius: 12px;
}
.my-site-url__inner[data-my-site-url-state="unpublished"] {
  background: var(--cream-2);
  border-style: dashed;
}
.my-site-url__icon {
  display: inline-flex;
  width: 36px;
  height: 36px;
  align-items: center;
  justify-content: center;
  background: rgba(31, 22, 17, 0.05);
  border-radius: 999px;
  color: var(--ink);
  flex-shrink: 0;
}
.my-site-url__text { flex: 1 1 auto; min-width: 0; }
.my-site-url__label {
  font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace;
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 4px;
}
.my-site-url__value {
  font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace;
  font-size: 0.92rem;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.my-site-url__actions {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}
.my-site-url__copy,
.my-site-url__open {
  font-size: 0.82rem;
  padding: 6px 12px;
}

/* Personal-domain upsell card. Visually identical to .my-site-cta but
   with an icon column so it reads as the upgrade hook, not just
   another option. */
.my-site-domain {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 18px 20px;
  margin-top: 32px;
  background: var(--paper, #fff);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  text-decoration: none;
  color: var(--ink);
  transition: border-color 0.18s, transform 0.18s;
}
.my-site-domain:hover {
  border-color: var(--ink-mute);
  transform: translateY(-1px);
}
.my-site-domain__icon {
  display: inline-flex;
  width: 36px;
  height: 36px;
  align-items: center;
  justify-content: center;
  background: rgba(168, 66, 28, 0.08);
  border-radius: 999px;
  color: var(--orange-deep, #a8421c);
  flex-shrink: 0;
}
.my-site-domain__text { flex: 1 1 auto; min-width: 0; }
.my-site-domain__title {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-weight: 500;
  font-size: 1.02rem;
  letter-spacing: -0.012em;
  color: var(--ink);
  margin-bottom: 4px;
}
.my-site-domain__sub {
  font-size: 0.85rem;
  color: var(--ink-mute);
  line-height: 1.4;
}
.my-site-domain__arrow {
  display: inline-flex;
  color: var(--ink-mute);
  flex-shrink: 0;
}

/* Privacy & analytics row — toggle for PostHog opt-in. */
.my-site-privacy__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 16px 20px;
  background: var(--paper, #fff);
  border: 1px solid var(--hairline);
  border-radius: 12px;
}
.my-site-privacy__title {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-weight: 500;
  font-size: 0.96rem;
  color: var(--ink);
  margin-bottom: 4px;
}
.my-site-privacy__sub {
  font-size: 0.85rem;
  color: var(--ink-mute);
  line-height: 1.5;
  max-width: 60ch;
}
/* Toggle switch — pure CSS, no JS dependency for the visual state. */
.my-site-privacy__switch {
  position: relative;
  display: inline-block;
  width: 40px;
  height: 22px;
  flex-shrink: 0;
}
.my-site-privacy__switch input {
  opacity: 0;
  width: 0;
  height: 0;
}
.my-site-privacy__slider {
  position: absolute;
  inset: 0;
  background: var(--hairline);
  border-radius: 22px;
  cursor: pointer;
  transition: background 0.18s;
}
.my-site-privacy__slider::before {
  content: '';
  position: absolute;
  width: 16px;
  height: 16px;
  left: 3px;
  top: 3px;
  background: var(--paper, #fff);
  border-radius: 50%;
  transition: transform 0.18s;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.my-site-privacy__switch input:checked + .my-site-privacy__slider { background: var(--ink); }
.my-site-privacy__switch input:checked + .my-site-privacy__slider::before { transform: translateX(18px); }

/* Sparkline below the Page views KPI. Tiny SVG bar chart of the last
   7 days. Bars at 18% opacity are zero-days; >0 days bump to 65%. */
.kpi-sparkline {
  display: block;
  width: 70px;
  height: 22px;
  margin: 6px 0 0;
  color: var(--ink-mute);
}

/* Title row + status pill on the My website page. Pill sits next to
   the H1; three states drive the colour — green/live, grey/paused,
   orange/incomplete. Hidden until the site-status fetch resolves. */
.my-site-title-row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.my-site-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 999px;
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 0.78rem;
  font-weight: 500;
  background: rgba(31, 22, 17, 0.05);
  color: var(--ink-mute);
  letter-spacing: 0.005em;
  white-space: nowrap;
}
.my-site-status__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--hairline);
  flex-shrink: 0;
}
.my-site-status[data-state="live"] {
  background: rgba(47, 158, 68, 0.12);
  color: #1d6d2c;
}
.my-site-status[data-state="live"] .my-site-status__dot {
  background: #2f9e44;
  box-shadow: 0 0 0 3px rgba(47, 158, 68, 0.18);
}
.my-site-status[data-state="paused"] {
  background: rgba(31, 22, 17, 0.08);
  color: var(--ink-mute);
}
.my-site-status[data-state="paused"] .my-site-status__dot {
  background: var(--ink-mute);
}
.my-site-status[data-state="incomplete"] {
  background: rgba(168, 66, 28, 0.1);
  color: var(--orange-deep, #a8421c);
}
.my-site-status[data-state="incomplete"] .my-site-status__dot {
  background: var(--orange-deep, #a8421c);
}

/* URL strip extras — alt host (when custom domain is primary) and
   last-published timestamp. Same column as the value, smaller. */
.my-site-url__alt,
.my-site-url__pub {
  font-size: 0.78rem;
  color: var(--ink-mute);
  margin-top: 4px;
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
}
.my-site-url__inner[data-my-site-url-state="paused"] {
  background: rgba(168, 66, 28, 0.06);
  border-color: rgba(168, 66, 28, 0.3);
}
.my-site-url__inner[data-my-site-url-state="paused"] .my-site-url__icon {
  background: rgba(168, 66, 28, 0.12);
  color: var(--orange-deep, #a8421c);
}

/* Preview pane — single iframe at a time. Toggle above switches
   between Desktop (full-width frame) and Mobile (centred phone-
   bezel frame). Both iframes are mounted; CSS hides the inactive
   one based on the wrapper's data-device attribute. */
.my-site-frames {
  position: relative;
  margin: 16px 0;
}
.my-site-frames__toggle {
  display: inline-flex;
  background: var(--cream-2);
  border-radius: 8px;
  padding: 3px;
  gap: 2px;
  margin-bottom: 12px;
}
.my-site-frames__toggle-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: transparent;
  border: 0;
  padding: 6px 12px;
  font-family: inherit;
  font-size: 0.82rem;
  color: var(--ink-mute);
  cursor: pointer;
  border-radius: 5px;
  transition: background 0.15s, color 0.15s;
}
.my-site-frames__toggle-btn:hover { color: var(--ink); }
.my-site-frames__toggle-btn.is-on {
  background: var(--paper, #fff);
  color: var(--ink);
  box-shadow: 0 1px 2px rgba(31, 22, 17, 0.05);
}
/* Visibility — driven by the wrapper's data-device. */
.my-site-frames[data-device="desktop"] .my-site-frame--mobile { display: none; }
.my-site-frames[data-device="mobile"]  .my-site-frame          { display: none; }
.my-site-frame {
  background: var(--paper, #fff);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  overflow: hidden;
  height: min(72vh, 720px);
  min-width: 0;
}
.my-site-frame iframe {
  width: 100%;
  height: 100%;
  border: 0;
  background: #fff;
  display: block;
}
.my-site-frame--mobile {
  display: flex;
  justify-content: center;
  padding: 12px 0;
}
.my-site-frame--mobile__bezel {
  position: relative;
  width: 220px;
  height: 460px;
  border: 8px solid #1f1611;
  border-radius: 28px;
  overflow: hidden;
  box-shadow: 0 8px 24px -10px rgba(31, 22, 17, 0.25);
  background: #1f1611;
}
.my-site-frame--mobile__bezel::before {
  content: '';
  position: absolute;
  top: 6px;
  left: 50%;
  transform: translateX(-50%);
  width: 60px;
  height: 12px;
  background: #1f1611;
  border-radius: 999px;
  z-index: 2;
}
.my-site-frame--mobile__bezel iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 390px;
  height: 815px;
  border: 0;
  background: #fff;
  transform: scale(0.5641); /* 220/390 ≈ 0.564 */
  transform-origin: top left;
}

/* Recent activity rows — 5 rows max, dot + when + ref + path + dur. */
.my-site-recent__list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: var(--paper, #fff);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  overflow: hidden;
}
.my-site-recent__empty {
  padding: 24px 20px;
  text-align: center;
  color: var(--ink-mute);
  font-size: 0.88rem;
}
.my-site-recent__row {
  display: grid;
  grid-template-columns: 12px 90px 1fr 1.5fr 60px;
  gap: 12px;
  align-items: center;
  padding: 12px 16px;
  border-bottom: 1px solid var(--hairline);
  font-size: 0.86rem;
  color: var(--ink);
}
.my-site-recent__row:last-child { border-bottom: 0; }
.my-site-recent__cta {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #2f9e44;
  display: inline-block;
}
.my-site-recent__cta--muted { background: var(--hairline); }
.my-site-recent__when { color: var(--ink-mute); font-size: 0.82rem; }
.my-site-recent__ref { color: var(--ink); font-weight: 500; }
.my-site-recent__path { color: var(--ink-mute); font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace; font-size: 0.8rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.my-site-recent__dur { color: var(--ink-mute); text-align: right; font-size: 0.82rem; }
@media (max-width: 720px) {
  /* Mobile collapse: keep the time-ago label visible — without it both rows
     read as identical "bookingsprint.com — bookingsprint.com" lines. Hide
     the path (debug noise) and the duration (always "—" for sub-second
     preview hits until heartbeat tracking is reliable). */
  .my-site-recent__row {
    grid-template-columns: 12px 1fr auto;
    gap: 10px;
  }
  .my-site-recent__cta { order: 1; }
  .my-site-recent__ref { order: 2; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .my-site-recent__when {
    order: 3;
    color: var(--ink-faint, #b3a594);
    font-size: 0.78rem;
    white-space: nowrap;
    text-align: right;
  }
  .my-site-recent__path { display: none; }
  .my-site-recent__dur { display: none; }
}

/* Share section — three buttons in a row, plus an inline SMS popover. */
.my-site-share__row {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.my-site-share__btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  background: var(--cream-2);
  border: 0;
  border-radius: 999px;
  font-family: inherit;
  font-size: 0.86rem;
  color: var(--ink);
  cursor: pointer;
  text-decoration: none;
  transition: background 0.15s;
}
.my-site-share__btn:hover {
  background: var(--cream-3);
}
.my-site-share__sms {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  flex-wrap: wrap;
}
.my-site-share__sms-input {
  flex: 1 1 200px;
  padding: 9px 12px;
  border: 0;
  border-radius: 8px;
  font-family: inherit;
  font-size: 0.92rem;
  background: var(--cream-2);
  color: var(--ink);
  transition: background 0.12s;
}
.my-site-share__sms-input:focus {
  outline: 0;
  background: var(--cream-3);
}
.my-site-share__sms-send {
  flex: 0 0 auto;
  font-size: 0.86rem;
  padding: 9px 18px;
}
.my-site-share__sms-msg {
  flex: 1 1 100%;
  margin-top: 4px;
  font-size: 0.82rem;
  color: var(--ink-mute);
}
.my-site-share__sms-msg[data-tone="error"]   { color: #c2410c; }
.my-site-share__sms-msg[data-tone="success"] { color: #2f9e44; }

/* Pause / advanced — collapsible details at the bottom of the page. */
.my-site-pause {
  margin-top: 48px;
  border-top: 1px solid var(--hairline);
  padding-top: 24px;
}
.my-site-pause__summary {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.82rem;
  color: var(--ink-mute);
  user-select: none;
  padding: 6px 10px;
  border-radius: 6px;
  transition: background 0.15s, color 0.15s;
}
.my-site-pause__summary::-webkit-details-marker { display: none; }
.my-site-pause__summary:hover {
  background: rgba(31, 22, 17, 0.04);
  color: var(--ink);
}
.my-site-pause[open] .my-site-pause__summary svg { transform: rotate(180deg); }
.my-site-pause__summary svg { transition: transform 0.15s; }
.my-site-pause__body { padding: 16px 0 0; }
.my-site-pause__eyebrow { color: rgba(168, 66, 28, 0.85); }
.my-site-pause__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 16px 20px;
  background: var(--paper, #fff);
  border: 1px solid var(--hairline);
  border-radius: 12px;
}
.my-site-pause__title {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-weight: 500;
  font-size: 0.96rem;
  color: var(--ink);
  margin-bottom: 4px;
}
.my-site-pause__sub {
  font-size: 0.85rem;
  color: var(--ink-mute);
  line-height: 1.5;
  max-width: 60ch;
}

/* ============================================================================
   Examples grid — /dashboard-v2/examples. 3 cards (Marlow Built, Bluestone
   Plumbing, Halse Painting) showing what the AI builder produces. Each card
   iframes the live tradie site at 1440px and scales it down via CSS container
   queries (matches the proof slider on landing.html). The whole iframe is
   click-through to the full site; the "Build something like this" button
   underneath seeds the AI builder with a related starter prompt.
   ============================================================================ */
.examples-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
  gap: 24px;
  margin-top: 24px;
}
.examples-card {
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--card-radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  box-shadow: 0 1px 2px rgba(15, 25, 50, 0.04);
  transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}
.examples-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 12px 28px rgba(15, 25, 50, 0.08), 0 1px 2px rgba(15, 25, 50, 0.04);
  border-color: var(--hairline-2);
}
.examples-card__frame {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: var(--cream);
  container-type: inline-size;
  border-bottom: 1px solid var(--hairline);
  text-decoration: none;
  color: inherit;
}
.examples-card__iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 1440px;
  height: 900px;
  border: 0;
  pointer-events: none;
  transform-origin: top left;
  /* CRITICAL: divide cqw by a length (1440px), not a unitless number.
     Length / length is a valid unitless scale value. */
  transform: scale(calc(100cqw / 1440px));
  will-change: transform;
  backface-visibility: hidden;
}
.examples-card__body {
  padding: 18px 20px 20px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  flex: 1;
}
.examples-card__meta {
  min-width: 0;
}
.examples-card__name {
  font-family: 'Geist', 'Inter Tight', 'Inter', sans-serif;
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: -0.015em;
  margin: 0 0 4px;
  color: var(--ink);
}
.examples-card__trade {
  font-family: 'Geist Mono', monospace;
  font-size: 0.66rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 0;
}
.examples-card__cta {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.examples-card__cta svg {
  transition: transform 0.18s ease;
}
.examples-card__cta:hover svg {
  transform: translateX(2px);
}

@media (max-width: 720px) {
  .examples-grid {
    grid-template-columns: 1fr;
    gap: 16px;
  }
}

/* === Refinement protocol action UI (2026-05-09) ====================
   Backend /api/builder/chat returns an `actions` array. Each action
   type has its own renderer in dashboard-v2-page.html. Styles below
   live in the warm-stone palette and reuse existing tokens
   (--ink, --ink-mute, --paper, --cream-2, --hairline). No new colours.
   ================================================================== */

/* ask: panel docked INSIDE .builder-input-box (as its first child) so it
   shares the composer's border and shadow — reads as part of the input,
   not as a floating row. Bottom hairline separates it from the textarea
   below. Slides up on mount so the surface feels alive. */
.builder-input-box .builder-action-ask-panel {
  position: relative;
  padding: 10px 14px 10px;
  margin: 0;
  border-bottom: 1px solid rgba(31, 22, 17, 0.08);
  background: transparent;
  animation: builderAskSlideUp 0.32s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.builder-action-ask-prompt {
  font-size: 0.82rem;
  color: var(--ink-mute);
  letter-spacing: -0.005em;
  margin: 0 0 8px;
}
.builder-action-ask-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.builder-action-ask-chip {
  font-family: inherit;
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--ink);
  background: var(--cream-2, #F5F5F5);
  border: 1px solid rgba(31, 22, 17, 0.08);
  border-radius: 999px;
  padding: 5px 12px;
  cursor: pointer;
  letter-spacing: -0.005em;
  transition: background 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
}
.builder-action-ask-chip:hover {
  background: var(--cream, #ECECEC);
  border-color: rgba(31, 22, 17, 0.18);
  transform: translateY(-1px);
}
.builder-action-ask-chip:active { transform: translateY(0); }
.builder-action-ask-chip--other {
  background: transparent;
  border-style: dashed;
  color: var(--ink-mute);
}
.builder-action-ask-chip--other:hover {
  background: var(--cream-2, #F5F5F5);
  color: var(--ink);
  border-style: solid;
}

@keyframes builderAskSlideUp {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* While the ask panel is mounted on the composer, hide the regular
   refine-row chips so they don't compete visually. The data-attribute
   is set by shellRenderActionAsk and cleared on dismiss. */
.builder-input-wrap[data-builder-ask-active] .builder-refine-row {
  display: none !important;
}

/* form: AI-side inline card. Cousin of .builder-form-card but lighter
   (no animation cascade — refinement should feel snappy, not theatrical). */
.builder-action-form-row { width: 100%; }
.builder-action-form-card {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 520px;
  background: var(--cream-2);
  border: 1px solid var(--hairline);
  border-radius: 14px;
  padding: 14px 16px 12px;
  box-shadow: 0 4px 14px -8px rgba(31, 22, 17, 0.08), 0 1px 2px rgba(31, 22, 17, 0.03);
  display: flex;
  flex-direction: column;
  gap: 10px;
  box-sizing: border-box;
  animation: builderActionFade 0.4s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.builder-action-form-title {
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--ink);
  line-height: 1.4;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--hairline);
}
.builder-action-form-fields {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.builder-action-form-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.builder-action-form-label {
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--ink-mute);
  letter-spacing: 0.005em;
}
.builder-action-form-input {
  border: 1px solid var(--hairline);
  background: var(--paper, #fff);
  border-radius: 8px;
  padding: 8px 10px;
  font-family: inherit;
  font-size: 0.9rem;
  color: var(--ink);
  transition: border-color 0.18s, box-shadow 0.18s;
  outline: none;
  width: 100%;
  box-sizing: border-box;
}
.builder-action-form-input::placeholder { color: var(--ink-soft, var(--ink-mute)); opacity: 0.7; }
.builder-action-form-input:focus {
  border-color: var(--ink);
  box-shadow: 0 0 0 2px rgba(31, 22, 17, 0.08);
}
.builder-action-form-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 4px;
}
.builder-action-form-submit {
  background: var(--ink);
  color: var(--cream, #FAFAFA);
  border: 0;
  border-radius: 8px;
  padding: 9px 16px;
  font-family: inherit;
  font-size: 0.88rem;
  font-weight: 500;
  cursor: pointer;
  letter-spacing: -0.005em;
  transition: background 0.18s ease, transform 0.18s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.18s ease;
  box-shadow: 0 1px 0 rgba(31, 22, 17, 0.06), 0 3px 8px -3px rgba(31, 22, 17, 0.25);
}
.builder-action-form-submit:hover { transform: translateY(-1px); box-shadow: 0 1px 0 rgba(31, 22, 17, 0.06), 0 6px 14px -5px rgba(31, 22, 17, 0.32); }
.builder-action-form-submit:active { transform: translateY(0); }
.builder-action-form-cancel {
  background: transparent;
  border: 0;
  color: var(--ink-mute);
  font-family: inherit;
  font-size: 0.82rem;
  cursor: pointer;
  padding: 6px 4px;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
  text-decoration-color: rgba(31, 22, 17, 0.25);
}
.builder-action-form-cancel:hover { color: var(--ink); }

/* apply: small "Updating..." pill that lands inline in the AI column. */
.builder-action-applying-row { width: 100%; }
.builder-action-applying {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: transparent;
  color: var(--ink-mute);
  border: 1px solid var(--hairline);
  border-radius: 999px;
  padding: 5px 12px 5px 10px;
  font-size: 0.8rem;
  font-family: inherit;
  font-weight: 450;
  letter-spacing: 0.005em;
  animation: builderActionFade 0.28s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.builder-action-applying-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: transparent;
  border: 1.5px solid var(--ink-mute);
  border-top-color: transparent;
  animation: builderActionSpin 0.9s linear infinite;
  display: inline-block;
}
.builder-action-applying.is-done .builder-action-applying-dot {
  animation: none;
  border-color: var(--ink);
  background: var(--ink);
  position: relative;
}
.builder-action-applying.is-done .builder-action-applying-dot::after {
  content: '';
  position: absolute;
  top: 1px; left: 2.5px;
  width: 2px; height: 4px;
  border: solid var(--cream, #FAFAFA);
  border-width: 0 1.5px 1.5px 0;
  transform: rotate(45deg);
}
.builder-action-applying.is-done { color: var(--ink); }
@keyframes builderActionSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .builder-action-applying-dot { animation: none; border-top-color: var(--ink-mute); }
}

/* suggestions: chip row below an AI reply. Reuses .builder-suggestion-chip
   visual; this just lays them out and adds the rationale-tooltip cursor. */
.builder-action-suggestions-row { width: 100%; }
.builder-action-suggestion-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: -2px;
}
.builder-action-suggestion-chip { font-size: 0.82rem; padding: 6px 12px; cursor: help; }
.builder-action-suggestion-chip:hover { cursor: pointer; }

@keyframes builderActionFade {
  0%   { opacity: 0; transform: translateY(4px); }
  100% { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .builder-action-ask-options,
  .builder-action-form-card,
  .builder-action-applying { animation: none; }
}

/* ============================================================================
   Cards view (mobile only) — 2026-05-22
   Tap the top-right preview pill (.builder-preview-pill) to zoom the builder
   out and reveal three side-by-side cards: Chat / Preview / Sections. Swipe
   horizontally (scroll-snap) to peek; tap a card body to zoom in and exit
   cards-view with that pane active.

   Single mode (default): existing rules unchanged. Only the active pane
   shows; others stay mounted but hidden via existing CSS.

   Cards mode: body.is-builder-cards-view applied. The .builder-shell flips
   from its column layout to a horizontal scroll-snap row, every .builder-pane
   becomes a flex item card, .builder-pane__card-head shows at the top of
   each card, the bottom composer hides, the topbar swaps project pill for
   a Back arrow. Live DOM stays mounted (chat keeps streaming, iframe keeps
   loading) — no screenshots.
   ========================================================================== */

/* Card head + close button — hidden by default (single mode). */
.builder-pane__card-head { display: none; }
.builder-cards-back { display: none; }
/* Card wrapper + label — single mode: wrap is "invisible" (display:contents
   passes children through to the shell grid), label is display:none.
   Cards-view re-enables both via the @media block below. */
.builder-card-wrap { display: contents; }
.builder-card-label { display: none; }
.builder-cards-dock { display: none; }

@media (max-width: 720px) {
  /* Activate cards view --------------------------------------------------- */
  body.is-builder-cards-view {
    background-color: var(--cream-3) !important;
    overflow: hidden;
  }
  /* 2026-05-22 v8d — sidebar must REMAIN renderable in cards-view so
     the burger menu can open it (founder ask: 'make sure the sidebar
     button works cleanly in the cards section as well'). Previous
     rule set `display: none !important` which made the burger a
     no-op. Use visibility-hidden-when-closed + visible-when-open
     instead (same pattern as builder-chat single mode). The original
     concern about the drawer-footer 'leaking under the cards at the
     bottom of the viewport' was visible because the cards bg was
     transparent; with v8c the bg is solid --topband so any leak is
     no longer an issue. */
  body.is-builder-cards-view:not(.mob-drawer-open) .side {
    visibility: hidden !important;
  }
  body.is-builder-cards-view.mob-drawer-open .side {
    visibility: visible !important;
    display: flex !important;
  }
  body.is-builder-cards-view .mob-topbar {
    background-color: var(--cream-3) !important;
    color: var(--ink);
    position: fixed !important;
    top: 0; left: 0; right: 0;
    z-index: 80;
  }
  /* 2026-05-22 Cards-view burger keeps the V6 gradient + shadow from
     the base rule (dashboard-v2.css L1481). This rule used to revert
     to a flat var(--paper) bg + light shadow — overrode V6. Removed. */
  body.is-builder-cards-view .builder-project-pill { display: none !important; }
  body.is-builder-cards-view .builder-preview-pill { display: none !important; }
  /* Back button centred in the topbar via absolute positioning. */
  body.is-builder-cards-view .builder-cards-back {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 44px;
    padding: 0 18px 0 14px;
    border: 1px solid rgba(31, 22, 17, 0.13);
    border-bottom-color: rgba(31, 22, 17, 0.19);
    border-radius: 999px;
    background: linear-gradient(180deg, #FFFFFF 0%, #F8F8F8 100%);
    color: var(--ink);
    font-family: inherit;
    font-size: 0.875rem;
    font-weight: 500;
    cursor: pointer;
    box-shadow:
      0 1px 3px rgba(31, 22, 17, 0.07),
      0 2px 1px -1px rgba(31, 22, 17, 0.04),
      inset 0 1px 0 rgba(255, 255, 255, 0.9);
    -webkit-tap-highlight-color: transparent;
    touch-action: manipulation;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
  }
  body.is-builder-cards-view .mob-topbar { position: fixed !important; }
  body.is-builder-cards-view [data-mob-default-brand] { display: none !important; }
  body.is-builder-cards-view .mob-topbar-title { justify-content: center; }
  body.is-builder-cards-view .mob-topbar-spacer { display: none !important; }

  /* Shell becomes a horizontal scroll-snap row -------------------------- */
  body.is-builder-cards-view .builder-shell {
    position: fixed !important;
    inset: 52px 0 0 0 !important;
    display: flex !important;
    flex-direction: row !important;
    /* center vertically so cards sit with breathing room above + below */
    align-items: center !important;
    /* Tighter gap + side padding so cards are close together, Replit-style. */
    gap: 8px !important;
    padding: 24px 6vw 110px !important;
    overflow-x: auto !important;
    overflow-y: hidden !important;
    scroll-snap-type: x mandatory !important;
    -webkit-overflow-scrolling: touch !important;
    background-color: var(--cream-3) !important;
    z-index: 60 !important;
    grid-template-columns: none !important;
    grid-template-rows: none !important;
    height: auto !important;
    min-height: 0 !important;
  }
  /* Bottom dock — Publish + Share. Floats below the cards, fixed at
     the viewport bottom inside the cards-view backdrop. */
  body.is-builder-cards-view .builder-cards-dock {
    display: flex;
    gap: 12px;
    position: fixed;
    left: 0; right: 0; bottom: 0;
    padding: 14px 16px max(20px, env(safe-area-inset-bottom));
    background: linear-gradient(to bottom, rgba(229, 227, 221, 0) 0%, var(--cream-3) 28%);
    z-index: 70;
    justify-content: center;
    pointer-events: none;
  }
  body.is-builder-cards-view .builder-cards-dock__btn {
    pointer-events: auto;
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: 44px;
    padding: 0 20px;
    border-radius: 999px;
    border: 1px solid rgba(31, 22, 17, 0.13);
    border-bottom-color: rgba(31, 22, 17, 0.19);
    background: linear-gradient(180deg, #FFFFFF 0%, #F8F8F8 100%);
    color: var(--ink);
    font-family: inherit;
    font-size: 0.875rem;
    font-weight: 500;
    cursor: pointer;
    box-shadow:
      0 1px 3px rgba(31, 22, 17, 0.07),
      0 2px 1px -1px rgba(31, 22, 17, 0.04),
      inset 0 1px 0 rgba(255, 255, 255, 0.9);
    -webkit-tap-highlight-color: transparent;
    touch-action: manipulation;
  }
  body.is-builder-cards-view .builder-cards-dock__btn[data-cards-dock="publish"] {
    background: linear-gradient(180deg, #2A2520 0%, #1F1611 100%);
    color: #FAFAFA;
    border-color: #1F1611;
    border-bottom-color: #0F0B08;
    box-shadow:
      0 1px 3px rgba(31, 22, 17, 0.2),
      inset 0 1px 0 rgba(255, 255, 255, 0.08);
  }
  body.is-builder-cards-view .builder-cards-dock__btn:active {
    transform: translateY(1px);
  }
  body.is-builder-cards-view .builder-shell::-webkit-scrollbar { display: none; }

  /* Every pane becomes a card ------------------------------------------- */
  /* In cards-view, the .builder-card-wrap becomes the flex item (NOT the
     .builder-pane). The wrap is a vertical flex column containing the
     floating label + the pane. */
  body.is-builder-cards-view .builder-shell--centered .builder-card-wrap {
    display: flex !important;
    flex-direction: column !important;
    align-items: stretch;
    gap: 10px;
    flex: 0 0 88vw !important;
    width: 88vw !important;
    max-width: 88vw !important;
    scroll-snap-align: center !important;
    scroll-snap-stop: always !important;
    transform: none;
  }
  /* Label sits above the pane — small text + icon, centred. Replit-style. */
  body.is-builder-cards-view .builder-shell--centered .builder-card-label {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    color: var(--ink-mute);
    font-family: inherit;
    font-size: 0.92rem;
    font-weight: 500;
    letter-spacing: -0.005em;
    flex: 0 0 auto;
    padding: 4px 8px;
    user-select: none;
    -webkit-user-select: none;
  }
  body.is-builder-cards-view .builder-shell--centered .builder-pane {
    /* Override the position:fixed bottom-sheet behaviour from
       data-builder-state="ready" on .builder-chat-pane. */
    position: relative !important;
    inset: auto !important;
    top: auto !important;
    bottom: auto !important;
    left: auto !important;
    right: auto !important;
    transform: none;
    pointer-events: auto !important;
    /* Pane fills its wrap (the wrap is the flex item now). */
    flex: 1 1 auto !important;
    width: 100% !important;
    max-width: 100% !important;
    /* Compact card — 42vh so it reads as a thumbnail. min-height: 0
       MUST be set explicitly because .builder-shell .builder-pane has
       min-height: calc(100dvh - 52px) at line 6947 (mobile pane fill),
       and min-height beats max-height in the CSS cascade. Without this
       override, the pane stretches to ~790px on mobile regardless of
       max-height. */
    min-height: 0 !important;
    height: 42vh !important;
    max-height: 380px !important;
    border-radius: 20px !important;
    overflow: hidden !important;
    background: var(--paper) !important;
    box-shadow:
      0 12px 36px rgba(0, 0, 0, 0.50),
      0 2px 8px rgba(0, 0, 0, 0.30) !important;
    scroll-snap-align: center !important;
    scroll-snap-stop: always !important;
    display: flex !important;
    flex-direction: column !important;
    cursor: pointer !important;
    /* Force the preview-pane back to visible — its default display:none
       in non-ready states needs to be overridden so the 3rd card always
       renders in cards view. */
    visibility: visible !important;
  }
  /* Preview pane is hidden in non-ready single mode but must show in cards. */
  body.is-builder-cards-view .builder-shell--centered .builder-preview-pane {
    display: flex !important;
  }
  /* Sections pane shows only in cards-view; hidden in single mode. */
  .builder-shell .builder-sections-pane { display: none; }
  body.is-builder-cards-view .builder-shell .builder-sections-pane {
    display: flex !important;
  }

  /* Card head shown in cards-view --------------------------------------- */
  body.is-builder-cards-view .builder-pane__card-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 14px;
    border-bottom: 1px solid var(--hairline);
    background: var(--paper);
    flex-shrink: 0;
    color: var(--ink);
    font-size: 0.9rem;
    font-weight: 600;
    letter-spacing: -0.005em;
  }
  body.is-builder-cards-view .builder-pane__card-title {
    flex: 1 1 auto;
  }
  body.is-builder-cards-view .builder-pane__card-close {
    flex: 0 0 auto;
    width: 28px; height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--ink-mute);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    touch-action: manipulation;
  }
  body.is-builder-cards-view .builder-pane__card-close:hover {
    background: var(--cream-2);
    color: var(--ink);
  }

  /* 2026-05-23 founder: input-wrap + suggestion-row + refine-row stay
     visible in cards-view as part of the 1:1 chat preview. Pointer
     events are killed in dashboard-v2-page.html so the card-tap-to-
     close gesture still works. sheet-handle (single-mode resize) and
     chat-tabs (those are separate cards in cards-view) stay hidden. */
  body.is-builder-cards-view .builder-sheet-handle { display: none !important; }
  body.is-builder-cards-view .builder-chat-tabs { display: none !important; }
  /* Also override the natural ":has()" hide on .builder-suggestion-row
     (which hides the chips once the chat has any messages) — in cards-
     view we want chips visible inside the card regardless of chat state. */
  body.is-builder-cards-view .builder-chat-pane .builder-suggestion-row { display: flex !important; }

  /* The chat body in cards-view fills the card under the head.
     Content scaling is handled by the parent pane's transform: scale(0.85)
     — that affects all descendants uniformly without zoom quirks. */
  body.is-builder-cards-view .builder-chat-pane .builder-chat-body {
    flex: 1 1 auto !important;
    overflow-y: auto !important;
    padding: 12px !important;
  }

  /* Sections pane body */
  .builder-sections-pane {
    /* base — single mode hidden by default rule above */
  }
  .builder-sections-pane__body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 18px 16px;
    display: flex;
    flex-direction: column;
    gap: 14px;
  }
  .builder-sections-pane__lede {
    margin: 0;
    font-size: 0.92rem;
    line-height: 1.5;
    color: var(--ink-mute);
  }
  .builder-sections-pane__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
  }
  .builder-sections-pane__item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 16px;
    border: 1px solid var(--hairline);
    border-radius: 12px;
    background: var(--cream);
    color: var(--ink);
    font-size: 0.95rem;
    font-weight: 500;
  }
  .builder-sections-pane__item svg {
    color: var(--ink-mute);
    flex-shrink: 0;
  }
  .builder-sections-pane__footnote {
    margin: 6px 0 0;
    font-size: 0.78rem;
    color: var(--ink-soft);
    text-align: center;
    font-style: italic;
  }

  /* Entry animation — zoom OUT feel. Backdrop fades in over 320ms;
     panes scale-in from 1.12 (starts slightly LARGER than card size,
     so the user perceives the previously-fullscreen content shrinking
     into a card) with a 110ms stagger between cards. Total ~750ms,
     deliberately slow so the zoom-out gesture is felt, not snapped. */
  @keyframes builderCardsFadeIn {
    from { opacity: 0; }
    to   { opacity: 1; }
  }
  @keyframes builderCardScaleIn {
    /* Starts slightly LARGER than card (1.08) and shrinks to natural
       (1.0). Perception: the previously full-bleed content shrinks
       into the card's smaller layout box. */
    from { transform: scale(1.08); opacity: 0; }
    to   { transform: scale(1);    opacity: 1; }
  }
  body.is-builder-cards-view:not(.is-builder-cards-closing) .builder-shell {
    animation: builderCardsFadeIn 0.32s cubic-bezier(0.2, 0, 0, 1) both;
  }
  body.is-builder-cards-view:not(.is-builder-cards-closing) .builder-shell--centered .builder-card-wrap {
    animation: builderCardScaleIn 0.52s cubic-bezier(0.16, 0.84, 0.36, 1) both;
    transform-origin: center center;
  }
  body.is-builder-cards-view:not(.is-builder-cards-closing) .builder-card-wrap:nth-child(2) { animation-delay: 0.11s; }
  body.is-builder-cards-view:not(.is-builder-cards-closing) .builder-card-wrap:nth-child(3) { animation-delay: 0.22s; }

  /* Exit animation — zoom IN feel. body.is-builder-cards-closing is
     applied by JS just before removing is-builder-cards-view. The
     panes scale UP to 1.18 + fade out (perception: the chosen card
     grows toward the viewer); the backdrop fades. Total ~380ms; JS
     then strips both classes and single-mode takes over. */
  @keyframes builderCardScaleOut {
    /* From natural (1.0) scales UP to 1.12 + fades. Perception: tapped
       card grows toward viewer as cards-view dissolves. */
    from { transform: scale(1);    opacity: 1; }
    to   { transform: scale(1.12); opacity: 0; }
  }
  @keyframes builderCardsFadeOut {
    from { opacity: 1; }
    to   { opacity: 0; }
  }
  body.is-builder-cards-closing .builder-shell {
    animation: builderCardsFadeOut 0.32s cubic-bezier(0.4, 0, 0.6, 1) forwards;
  }
  body.is-builder-cards-closing .builder-shell--centered .builder-card-wrap {
    animation: builderCardScaleOut 0.38s cubic-bezier(0.4, 0, 0.6, 1) forwards;
    transform-origin: center center;
  }

  @media (prefers-reduced-motion: reduce) {
    body.is-builder-cards-view .builder-shell,
    body.is-builder-cards-view .builder-shell--centered .builder-card-wrap,
    body.is-builder-cards-closing .builder-shell,
    body.is-builder-cards-closing .builder-shell--centered .builder-card-wrap {
      animation: none !important;
    }
  }
}
/* Desktop (>720px) — cards view not wired for v1; preview pill stays
   doing whatever its single-mode click does (currently nothing). */


/* ============================================================================
   Lovable-style topbar fade (2026-05-22)
   ============================================================================
   Founder wants chat content to "fade behind the buttons" like Lovable.
   After several hours iterating on backdrop-filter (which renders as solid
   on the founder's iOS Safari despite the standard hacks), parallel-agent
   research determined:
     - Lovable uses backdrop-filter on a sticky topbar (shadcn default).
     - Their pattern is fragile on iOS Safari when there's a non-trivial
       parent stacking context (which we have: main.dash z:10 +
       transform: translateX from the drawer pattern).
     - A gradient overlay below an opaque topbar gives the visual result
       reliably on every iOS Safari version (verified with iPhone UA
       puppeteer test — see /tmp/alt3.png).

   Architecture:
     - .mob-topbar stays solid var(--topband) (unchanged from base).
     - body.is-builder-chat::after is a fixed 60px gradient strip
       sitting at viewport 52-112, fading from --topband (top, full
       opacity) to transparent (bottom).
     - Scoped to :not(.mob-drawer-open) so the overlay doesn't paint
       over the sidebar drawer when it's open.
     - z-index 65 (above content z:10, below topbar z:70).
     - Content scrolling up through viewport 52-112 visually dissolves
       into the topbar's colour.
     - Content below viewport 112 renders fully clear.

   For the first message NOT to be hidden behind the topbar at rest,
   add padding-top to .builder-chat-body so the first message starts
   at viewport ~112 (clear of the overlay's faded zone).
   ============================================================================ */
@media (max-width: 720px) {
  /* 2026-05-22 founder ask v4 — kill the fade overlay. The topbar is
     now fully transparent (set in dashboard-v2-page.html inline CSS),
     so there's no frosted band to soften into the page; chat content
     scrolls clear behind the floating buttons and clips at the top of
     chat-body's overflow region. */
  body.is-builder-chat::after,
  body.is-builder-chat:not(.mob-drawer-open)::after {
    display: none !important;
  }
  body.is-builder-chat .builder-shell--centered .builder-chat-body {
    padding-top: 0 !important;
  }
}
