/* public/css/home.css — Phase 2 of the refactor (REFACTOR_PLAN.md §7).
   Copied verbatim from public/dashboard-v2.css ranges:
     614..3451  (home/recovery/my-site chrome + animations)
     9149..11588 (inbound, settings tabs, rich page chrome)
*/

/* === 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); }
/* ──── home.css range 2: source lines 9149..11588 ──── */
/* ============================================================
   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;
}
/* GPU reveal — each band springs up on its OWN <svg> layer (translate3d),
   different delay/travel so the stack arrives organically (copied from the
   landing hero). The spring lives on the band <svg>; the slow drift stays on
   the <path> inside, so the two transforms compose with no settle-jump. */
.dash-hero-bg .band {
  will-change: transform;
  transform: translateZ(0);
  animation: dashBandSpring var(--dur, 1.6s) cubic-bezier(0.19, 0.92, 0.27, 1) var(--delay, 0s) both;
}
@keyframes dashBandSpring {
  0%   { transform: translate3d(0, var(--d), 0); }
  66%  { transform: translate3d(0, var(--o), 0); }
  100% { transform: translate3d(0, 0, 0); }
}
.dash-hero-bg .band > path { will-change: transform; transform-origin: center; }
.dash-hero-bg .band.driftA > path { animation: dashBandDriftA 19s ease-in-out infinite alternate; }
.dash-hero-bg .band.driftB > path { animation: dashBandDriftB 23s 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 .band { animation: none !important; transform: none !important; }
  .dash-hero-bg .band > 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 {
    /* AI builder composer (mobile) — --cream-2 panel, hairline outline,
       layered drop shadow. */
    background: var(--cream-2);
    padding: 12px 14px 10px;
    border-radius: 22px;
    border: 1px solid var(--hairline);
    box-shadow:
      0 12px 28px -8px rgba(31, 22, 17, 0.14),
      0 4px 10px -4px rgba(31, 22, 17, 0.08),
      0 1px 2px rgba(31, 22, 17, 0.05);
    gap: 4px;
  }
  .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;
  }
  .builder-shell--centered .builder-input-action {
    width: 28px;
    height: 28px;
    background: transparent;
    border: 0;
  }
  .builder-shell--centered .builder-input-action svg {
    opacity: 0.45;
    width: 15px;
    height: 15px;
  }
  .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), #FDFDFD) 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, #FDFDFD) 8px,
      var(--topband, #FDFDFD) 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;
  }
}

