/* ============================================================================
   QuoteFleet — Customize panel (Wave 2)
   The rebuilt /app/brand surface: a single-purpose "Customize your calculator"
   page (theme presets, accent, font, logo, name/tagline) beside a live widget
   preview. Dark dashboard brand: #161616 page / #1C1C1C surface, cobalt
   #0D3CFC / periwinkle #6E8BFF accent, cream #E6E3E0, hairline borders. No teal.
   ========================================================================== */

/* ── De-clutter: suppress legacy injected noise on THIS page only ──────────
   The dashboard enhancement scripts inject readiness checklists, "answer a few
   setup questions" chips, preview mocks, share panels and the accessorial
   scanner. The JS injectors are guarded to skip the brand route; these scoped
   rules are a belt-and-suspenders net so nothing leaks above the controls even
   if an injector is loaded another way. Scoped to the customize page via :has()
   so other routes are untouched. */
#page-content:has(.qf-customize) .qf-brand-editor,
#page-content:has(.qf-customize) .qf-brand-publish-tip,
#page-content:has(.qf-customize) .qf-brand-studio-preview,
#page-content:has(.qf-customize) .qf-onboarding-panel,
#page-content:has(.qf-customize) .qf-setup-coach,
#page-content:has(.qf-customize) .qf-setup-kicker-inline,
#page-content:has(.qf-customize) .qf-share-readiness,
#page-content:has(.qf-customize) .qf-share-tip,
#page-content:has(.qf-customize) .qf-preview-card,
#page-content:has(.qf-customize) .qf-dashboard-preview,
#page-content:has(.qf-customize) .qf-acc-tools {
  display: none !important;
}

/* ── Page shell ───────────────────────────────────────────────────────── */
.qf-customize h1 { margin: 0 0 4px; }
.qf-customize .page-sub { margin: 0 0 18px; color: var(--muted); }

.qf-cz-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.92fr);
  gap: 18px;
  align-items: start;
}
.qf-cz-controls { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.qf-cz-preview-col { position: sticky; top: 18px; min-width: 0; }

/* ── Sections (cards) ─────────────────────────────────────────────────── */
.qf-cz-section { padding: 16px 16px 18px; }
.qf-cz-section-title {
  font-size: 14px; font-weight: 600; color: var(--ink);
  letter-spacing: -0.01em; margin-bottom: 4px;
}
.qf-cz-hint { font-size: 12.5px; color: var(--muted); line-height: 1.45; }
.qf-cz-section > .qf-cz-hint { margin-bottom: 12px; }

.qf-cz-field { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; }
.qf-cz-field:first-of-type { margin-top: 10px; }
.qf-cz-label { font-size: 12.5px; font-weight: 600; color: var(--ink-soft); }
.qf-cz-field .qf-cz-hint { margin-top: 2px; }

/* ── Theme preset cards ───────────────────────────────────────────────── */
.qf-cz-preset-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
}
.qf-cz-preset {
  display: flex; flex-direction: column; gap: 8px;
  padding: 8px; border: 1px solid var(--border);
  border-radius: var(--radius); background: var(--surface-2);
  cursor: pointer; text-align: left; transition: all .2s ease;
}
.qf-cz-preset:hover { border-color: var(--border-strong); }
.qf-cz-preset:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qf-cz-preset.is-selected { border-color: var(--accent-fill); box-shadow: 0 0 0 1px var(--accent-fill); }
.qf-cz-preset-swatch {
  position: relative; height: 46px; border-radius: 8px;
  border: 1px solid var(--border); overflow: hidden;
}
.qf-cz-preset-surface {
  position: absolute; left: 8px; right: 8px; bottom: 8px; top: 14px;
  border-radius: 5px;
}
.qf-cz-preset-accent {
  position: absolute; right: 9px; bottom: 9px; width: 16px; height: 16px;
  border-radius: 5px; box-shadow: var(--qf-shadow-sm);
}
.qf-cz-preset-name { font-size: 12.5px; font-weight: 600; color: var(--ink); }

/* ── Accent swatches ──────────────────────────────────────────────────── */
.qf-cz-accent-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.qf-cz-swatch {
  width: 30px; height: 30px; border-radius: 8px; padding: 0;
  border: 1px solid var(--border-strong); cursor: pointer;
  position: relative; transition: all .2s ease;
}.qf-cz-swatch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qf-cz-swatch.is-selected { box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent-fill); }
.qf-cz-swatch-default,
.qf-cz-swatch-custom {
  width: auto; height: 30px; padding: 0 12px; gap: 7px;
  display: inline-flex; align-items: center; font-size: 12px; font-weight: 600;
  color: var(--ink-soft); background: var(--surface-2);
}
.qf-cz-swatch-custom input[type="color"] {
  width: 18px; height: 18px; padding: 0; border: none; background: none;
  border-radius: 5px; cursor: pointer;
}
.qf-cz-swatch-custom input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
.qf-cz-swatch-custom input[type="color"]::-webkit-color-swatch { border: none; border-radius: 4px; }

/* ── Font select ──────────────────────────────────────────────────────── */
.qf-cz-select { max-width: 320px; }

/* ── Button-hover chips ───────────────────────────────────────────────── */
.qf-cz-hover-row { display: flex; flex-wrap: wrap; gap: 8px; }
.qf-cz-hover-chip {
  display: inline-flex; align-items: center; padding: 8px 13px;
  border-radius: var(--radius-btn); border: 1px solid var(--border-strong);
  background: var(--surface-2); color: var(--ink-soft);
  font-size: 12px; font-weight: 650; cursor: pointer;
  transition: all .2s ease;
}.qf-cz-hover-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qf-cz-hover-chip.is-selected {
  border-color: var(--accent-fill); color: var(--ink);
  box-shadow: 0 0 0 1px var(--accent-fill);
}

/* ── Map-style chips (per-tenant map look) ────────────────────────────── */
.qf-cz-mapstyle-row { display: flex; flex-wrap: wrap; gap: 8px; }
.qf-cz-mapstyle {
  display: inline-flex; align-items: center; gap: 8px; padding: 8px 12px 8px 8px;
  border-radius: var(--radius-btn); border: 1px solid var(--border-strong);
  background: var(--surface-2); color: var(--ink-soft);
  font-size: 12px; font-weight: 600; cursor: pointer;
  transition: all .2s ease;
}.qf-cz-mapstyle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qf-cz-mapstyle.is-selected {
  border-color: var(--accent-fill); color: var(--ink);
  box-shadow: 0 0 0 1px var(--accent-fill);
}
/* Tiny map-preview swatch: a representative base + a diagonal route line so
   each option reads at a glance. Self-contained (no Static Maps call). */
.qf-cz-mapstyle-swatch {
  position: relative; width: 28px; height: 20px; border-radius: 5px;
  border: 1px solid var(--border-strong); overflow: hidden; flex: 0 0 auto;
}
.qf-cz-mapstyle-swatch::after {
  content: ''; position: absolute; left: -4px; right: -4px; top: 50%;
  height: 3px; transform: rotate(-24deg); transform-origin: center;
  border-radius: 2px;
}
.qf-ms-branded    { background: #16204a; }
.qf-ms-branded::after    { background: #3f5cc0; }
.qf-ms-grayscale  { background: #f2f3f5; }
.qf-ms-grayscale::after  { background: #3356EE; }
.qf-ms-standard   { background: #e8efe4; }
.qf-ms-standard::after   { background: #3356EE; }
.qf-ms-dark-routes,
.qf-ms-dark_routes { background: #2c2c2c; }
.qf-ms-dark_routes::after { background: #f4f6f8; height: 4px; }
.qf-ms-soft       { background: #f4f1ea; }
.qf-ms-soft::after       { background: #3356EE; }
.qf-ms-satellite  { background: #4f6b3f; }
.qf-ms-satellite::after  { background: #f4f6f8; height: 4px; }

/* ── Text-colour swatches (background-aware) ──────────────────────────── */
.qf-cz-textcolor-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.qf-cz-textcolor {
  display: inline-flex; align-items: center; gap: 7px; padding: 6px 12px 6px 8px;
  border-radius: var(--radius-btn); border: 1px solid var(--border-strong);
  background: var(--surface-2); color: var(--ink-soft);
  font-size: 12px; font-weight: 600; cursor: pointer;
  transition: all .2s ease;
}.qf-cz-textcolor:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qf-cz-textcolor.is-selected {
  border-color: var(--accent-fill); color: var(--ink);
  box-shadow: 0 0 0 1px var(--accent-fill);
}
.qf-cz-textcolor-dot {
  width: 16px; height: 16px; border-radius: 50%;
  border: 1px solid var(--border-strong); flex: 0 0 auto;
}

/* ── Logo dropzone ────────────────────────────────────────────────────── */
.qf-cz-dropzone {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 22px 16px; text-align: center;
  border: 1px dashed var(--border-strong); border-radius: var(--radius);
  background: var(--surface-2); transition: all .2s ease;
}
.qf-cz-dropzone.is-drag { border-color: var(--accent-fill); background: var(--accent-soft); }
.qf-cz-dropzone-title { font-size: 13px; font-weight: 600; color: var(--ink); }
.qf-cz-file { display: none; }
.qf-cz-pick { margin-top: 6px; }
.qf-cz-logo-current { display: flex; align-items: center; gap: 12px; margin-top: 12px; flex-wrap: wrap; }
.qf-cz-logo-img {
  max-height: 56px; max-width: 180px; border-radius: 8px;
  background: var(--surface-3); padding: 6px; border: 1px solid var(--border);
}

/* ── Live preview ─────────────────────────────────────────────────────── */
.qf-cz-preview { padding: 14px; }
.qf-cz-preview-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.qf-cz-preview-title { font-size: 14px; font-weight: 600; color: var(--ink); }
.qf-cz-preview-open { font-size: 12.5px; font-weight: 600; color: var(--accent); text-decoration: none; }
.qf-cz-preview-open:hover { color: var(--accent-strong); text-decoration: underline; }
.qf-cz-frame-wrap {
  border: 0; border-radius: var(--radius-lg);
  overflow: hidden; background: transparent;
}
.qf-cz-frame { display: block; width: 100%; height: 620px; border: 0; }
.qf-cz-preview-note { margin-top: 10px; font-size: 12px; color: var(--muted); line-height: 1.45; }

/* ── Responsive: stack + un-stick preview ─────────────────────────────── */
@media (max-width: 900px) {
  .qf-cz-layout { grid-template-columns: minmax(0, 1fr); }
  .qf-cz-preview-col { position: static; order: -1; }
  .qf-cz-frame { height: 520px; }
  .qf-cz-preset-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 480px) {
  .qf-cz-preset-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ── Drag-scroll carousel (theme presets + map styles) ────────────────────
   A reusable horizontal strip with subtle chevron arrows, used by BOTH the
   theme-preset and map-style selectors (see makeCarousel in app.js). The track
   scrolls on pointer drag (grab/grabbing cursor) for mouse, touch and pen alike,
   with momentum on release; items proximity-snap for tidy alignment. touch-action
   is pan-y so a vertical swipe still scrolls the page while JS owns the horizontal
   drag. Tokens/relative units only — colour + spacing guards stay green. */
.qf-cz-carousel { display: flex; align-items: center; gap: 4px; }
.qf-cz-carousel-track {
  display: flex; flex-wrap: nowrap; gap: 4px;
  flex: 1 1 auto; min-width: 0;
  overflow-x: auto; overflow-y: hidden;
  /* No scroll-snap: free drag-to-scroll; cropped edge chips are fine (snap jumped). */
  -webkit-overflow-scrolling: touch;
  touch-action: pan-y;
  cursor: grab;
  padding: 4px;
  scrollbar-width: none; /* Firefox — hide the native bar, still scrollable */
}
.qf-cz-carousel-track::-webkit-scrollbar { width: 0; height: 0; display: none; }
.qf-cz-carousel-track.is-grabbing { cursor: grabbing; }
.qf-cz-carousel-track > * { flex: 0 0 auto; }
/* In the carousel the preset cards take a fixed, comfortably-tappable width. */
.qf-cz-carousel-track .qf-cz-preset { width: 120px; }

.qf-cz-carousel-arrow {
  flex: 0 0 auto; width: 24px; height: 24px;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0; border-radius: 999px;
  border: 1px solid var(--border-strong); background: var(--surface-2);
  color: var(--ink-soft); cursor: pointer;
  transition: all .2s ease;
}
.qf-cz-carousel-arrow:hover { color: var(--ink);}
.qf-cz-carousel-arrow:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Hide at the ends. Scoped through .qf-cz-carousel so it out-specifies the
   global `.app-main button[disabled]{opacity:.62}` in premium-saas-polish.css;
   visibility:hidden is a belt-and-suspenders so the arrow fully disappears. */
.qf-cz-carousel .qf-cz-carousel-arrow.is-hidden,
.qf-cz-carousel .qf-cz-carousel-arrow:disabled { opacity: 0; visibility: hidden; pointer-events: none; }
.qf-cz-carousel-arrow svg { display: block; }
@media (prefers-reduced-motion: reduce) {
  .qf-cz-carousel-arrow { transition: none; }
}

/* ── Map-blend opacity slider ─────────────────────────────────────────────
   Replaces the old on/off toggle. 0 = crisp map; 1–100 = feather intensity.
   The live preview tracks the drag; the save is debounced. */
.qf-cz-blend-row { display: flex; align-items: center; gap: 12px; }
.qf-cz-blend-slider {
  flex: 1 1 auto; min-width: 0; max-width: 320px;
  accent-color: var(--accent);
  cursor: pointer;
}
.qf-cz-blend-slider:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qf-cz-blend-value {
  flex: 0 0 auto; min-width: 44px; text-align: right;
  font-size: 12.5px; font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums;
}

/* ── Tabbed workspace (Design | Behavior) ─────────────────────────────────
   The left controls column is split into two tab panels; BOTH drive the one
   live preview on the right. Appended at EOF so the line-based spacing/colour
   baselines above don't renumber. */
.qf-cz-tabs {
  display: flex; gap: 8px; margin-bottom: 4px;
  border-bottom: 1px solid var(--border);
}
.qf-cz-tab {
  appearance: none; background: none; border: 0;
  padding: 8px 12px;
  font-size: 13px; font-weight: 650; color: var(--muted);
  border-bottom: 2px solid transparent; cursor: pointer;
  transition: all .2s ease;
}
.qf-cz-tab:hover { color: var(--ink); }
.qf-cz-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qf-cz-tab.is-active { color: var(--ink); border-bottom-color: var(--accent-fill); }
.qf-cz-tabpanel { display: flex; flex-direction: column; gap: 16px; }
.qf-cz-tabpanel.is-hidden { display: none; }

/* ── Preview header tools (device + host toggles) ─────────────────────────*/
.qf-cz-preview-head { flex-wrap: wrap; gap: 8px; }
.qf-cz-preview-tools { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.qf-cz-seg {
  display: inline-flex; align-items: center;
  border: 1px solid var(--border-strong); border-radius: var(--radius-btn);
  background: var(--surface-2); overflow: hidden;
}
.qf-cz-seg-btn {
  appearance: none; background: none; border: 0;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 32px; min-height: 28px; padding: 4px 8px;
  color: var(--ink-soft); cursor: pointer;
  font-size: 12px; font-weight: 650;
  transition: all .2s ease;
}
.qf-cz-seg-btn.qf-cz-seg-text { padding: 4px 12px; }
.qf-cz-seg-btn:hover { color: var(--ink); }
.qf-cz-seg-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qf-cz-seg-btn.is-active { background: var(--accent-soft); color: var(--ink); }
.qf-cz-seg-btn svg { display: block; }

/* ── Auto-height + device framing of the preview ──────────────────────────
   Height is driven from JS (the widget posts QF_WIDGET_HEIGHT), so there is no
   fixed-box inner scrollbar. data-device clamps the width. The widget-theme
   toggle (Auto/Light/Dark) ONLY re-skins the calculator via postMessage — it no
   longer swaps a host backdrop/frame, so the preview container stays a constant
   size across all three states (no apparent resize/reframe on toggle). */
.qf-cz-frame-wrap {
  transition: all .2s ease;
}
.qf-cz-frame-wrap[data-device="mobile"] { max-width: 390px; margin-left: auto; margin-right: auto; }

/* ── Grab-scroll carousel cursor (Fix: premium draggable selector) ─────────
   The track reads grabbable (cursor:grab set above; .is-grabbing → grabbing
   during a drag). The option cards are the actual click target, so they show
   the pointer cursor. Appended at EOF so the line-based baselines above don't
   renumber. */
.qf-cz-carousel-track .qf-cz-preset,
.qf-cz-carousel-track .qf-cz-mapstyle { cursor: pointer; }

/* ── Fix B: mobile foldable control sheet (≤640px) ─────────────────────────
   The live preview stays in the main viewport; the customization controls dock
   into a foldable bottom sheet with a sticky shortcut row + drag handle.
   Mirrors the WeFixTrades QuoteQuick #467 sticky widget shell — opaque surface
   + hairline + rounded floating bar (no glass), premium eased fold on the iOS
   sheet curve, honors prefers-reduced-motion. State lives on .qf-cz-sheet /
   .is-open (no body classes), so a route swap that wipes #page-content
   auto-resets the :has()-scoped rules below. Desktop is untouched. Appended at
   EOF so the line-based baselines above don't renumber. Tokens/ramp/calc only. */
.qf-cz-sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1150;
  display: flex;
  flex-direction: column;
  /* Height is driven inline by the drag handle (setSheetHeight in app.js) — drag
     up = taller, down = shorter, stays where released. This is only the collapsed
     peek fallback before JS measures. Bottom-docked + full-width; resizing via
     HEIGHT (not transform) so the page never jumps and body scroll stays contained. */
  height: var(--qf-cz-peek, 96px);
  max-height: 92vh;
  background: var(--surface);
  border-top: 1px solid var(--border-strong);
  border-top-left-radius: var(--radius-lg);
  border-top-right-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  /* Property-specific on purpose: the sheet resizes by HEIGHT, never by
     transform, or the page jumps under it. `all` would animate transform too,
     so this one keeps an explicit property (customizePanel.test.ts RULE). */
  transition: height 0.3s ease;
  overflow: hidden;
  padding-bottom: env(safe-area-inset-bottom);
}
.qf-cz-sheet.is-dragging { transition: none; }
@media (prefers-reduced-motion: reduce) {
  .qf-cz-sheet { transition: none; }
}

.qf-cz-sheet-handle {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 24px;
  padding: 0;
  border: 0;
  background: none;
  cursor: grab;
  touch-action: none;
}
.qf-cz-sheet-handle:active { cursor: grabbing; }
.qf-cz-sheet-handle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qf-cz-sheet-grip {
  width: 40px;
  height: 4px;
  border-radius: var(--radius-pill);
  background: var(--border-strong);
  transition: all .2s ease;
}
.qf-cz-sheet-handle:hover .qf-cz-sheet-grip { background: var(--muted); }

.qf-cz-sheet-shortcuts {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 16px 12px;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.qf-cz-sheet-shortcuts::-webkit-scrollbar { width: 0; height: 0; display: none; }
.qf-cz-sheet-chip {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 44px;
  padding: 0 16px;
  border-radius: var(--radius-btn);
  border: 1px solid var(--border-strong);
  background: var(--surface-2);
  color: var(--ink-soft);
  font-size: 12.5px;
  font-weight: 650;
  white-space: nowrap;
  cursor: pointer;
  transition: all .2s ease;
}
.qf-cz-sheet-chip:hover { color: var(--ink); border-color: var(--accent-fill); }
.qf-cz-sheet-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qf-cz-sheet-chip.is-active {
  background: var(--accent-soft);
  color: var(--ink);
  border-color: var(--accent-fill);
}
.qf-cz-sheet-chip--tab { font-weight: 700; }

.qf-cz-sheet-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding: 4px 16px 24px;
}
/* The moved controls stack fills the sheet body; its own top tab bar is
   redundant with the sticky shortcut chips, so hide it inside the sheet. */
.qf-cz-sheet-body .qf-cz-tabs { display: none; }
.qf-cz-sheet-body .qf-cz-controls { gap: 6px; }

/* ≤640: preview owns the viewport; reserve room for the collapsed sheet peek so
   the shortcut row never covers the preview's bottom. */
@media (max-width: 640px) {
  #page-content:has(.qf-cz-sheet) { padding-bottom: calc(var(--qf-cz-peek, 96px) + 24px); }
}

/* The floating copilot launcher must clear the collapsed sheet, and step aside
   while the sheet is open (both descendants of #app-shell). Auto-resets when the
   sheet is removed (route swap) because the :has() no longer matches. */
#app-shell:has(.qf-cz-sheet) .qf-copilot-launcher {
  bottom: calc(var(--qf-cz-peek, 96px) + 16px);
  transition: all .3s ease;
}
#app-shell:has(.qf-cz-sheet.is-open) .qf-copilot-launcher {
  opacity: 0;
  transform: translateY(8px) scale(0.9);
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  #app-shell:has(.qf-cz-sheet) .qf-copilot-launcher { transition: all .2s ease; }
}

/* ── Guided-editing pointer: active container + arrow + preview alignment ───
   Clicking / focusing a config container marks it active (.is-cz-active),
   reveals a floating brand-accent arrow at the container's RIGHT edge pointing
   toward the live preview, and scrolls the preview viewport so the mapped
   widget section lines up level with it (see buildLivePreview.alignTo in
   app.js). To scroll WITHIN the preview, the frame becomes a fixed-height
   viewport (overflow-y:auto) while the iframe keeps its natural full-content
   height (QF_WIDGET_HEIGHT), so any section can be scrolled to. Appended at EOF
   so the line-based spacing/colour baselines above don't renumber. Tokens + 8px
   ramp only; theme-aware (light + dark); honours prefers-reduced-motion. */
.qf-cz-frame-wrap {
  height: 620px;
  max-height: calc(100vh - 120px);
  overflow-y: auto;
  overflow-x: hidden;
  /* Edit 7: keep wheel/touch scroll INSIDE the preview — never chain to the
     portal page (the app-shell is overflow:hidden, but this makes it explicit
     and robust for both desktop borderless + mobile framed modes). */
  overscroll-behavior: contain;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
}
@media (max-width: 900px) {
  .qf-cz-frame-wrap { height: 520px; }
}
@media (prefers-reduced-motion: reduce) {
  .qf-cz-frame-wrap { scroll-behavior: auto; }
}

/* The active container is marked with a subtle accent outline so it reads as the
   one being edited; on activation the mapped preview section also pulses and the
   preview scroll-aligns to it (see buildLivePreview.alignTo in app.js). There is
   NO visible guide arrow — it was too easily confused with the horizontal
   carousel chevrons — the outline + pulse + scroll-align carry the link.
   position:relative is kept as a harmless positioning context for any
   absolutely-placed children of a section. */
.qf-cz-section { position: relative; }
.qf-cz-section.is-cz-active {
  border-color: var(--accent-fill);
  box-shadow: 0 0 0 1px var(--accent-fill);
}

/* ══════════════════════════════════════════════════════════════════════════
   MOBILE CUSTOMIZE PASS (≤640px) — floating/resizable control panel, near-
   full-bleed preview, header inline with the hamburger, a quiet "Live preview"
   caption, and slightly-rounded (square-ish) chips. Appended at EOF so the
   line-based spacing/colour baselines above never renumber. Tokens + 8px ramp
   only; theme-aware (light + dark); honours prefers-reduced-motion.
   ══════════════════════════════════════════════════════════════════════════ */

/* #6 — Slightly-rounded (not pill) config buttons + bottom-menu chips, applied
   across the customize surface's tappable chips for one consistent shape. */
.qf-cz-sheet-chip,
.qf-cz-hover-chip,
.qf-cz-mapstyle,
.qf-cz-textcolor,
.qf-cz-seg { border-radius: var(--radius-btn); }

/* #6 — The bottom-menu shortcut chips scroll horizontally with two subtle,
   auto-hiding arrows (they never wrap to a second line). The row is wrapped by
   makeCarousel → .qf-cz-carousel + .qf-cz-carousel-arrow. Neutralise the double
   padding from stacking .qf-cz-sheet-shortcuts + .qf-cz-carousel-track and keep
   the wrapper from flexing inside the sheet column. */
.qf-cz-sheet > .qf-cz-carousel { flex: 0 0 auto; padding: 0 8px 8px; }
.qf-cz-sheet-shortcuts.qf-cz-carousel-track { padding: 4px; }

/* #5 — "Live preview" reads as a quiet caption on mobile (smaller, muted,
   lighter weight), not a heading. */
@media (max-width: 640px) {
  .qf-cz-preview-title {
    font-size: 11px;
    font-weight: 500;
    color: var(--muted);
    letter-spacing: 0.04em;
    text-transform: uppercase;
  }
}

/* #2 — Preview takes nearly the full viewport width on mobile (small gutters):
   drop the 390px cap + centring, trim the card padding, and pull the preview
   column out past most of the .app-main gutter. */
@media (max-width: 640px) {
  .qf-cz-preview { padding: 8px; }
  .qf-cz-preview-col { margin-left: -16px; margin-right: -16px; }
  .qf-cz-frame-wrap[data-device="mobile"] {
    max-width: none;
    margin-left: 0;
    margin-right: 0;
  }
}

/* #3 — Header sits inline with the fixed hamburger: no subtitle, smaller title,
   padded left so it shares the hamburger's column (a 40px button at top:12px
   left:12px, see app.html).
   The ~64px dead gap above the title was dashboard-polish.css padding .app-main
   72px at the top (leftover clearance for the removed top app-bar). On the
   Customize page that band is pure dead space, so trim it to 12px — the title
   then sits tight right under the sticky trial banner. Scoped via :has() so
   every OTHER dashboard page keeps its top clearance (desktop untouched). */
@media (max-width: 640px) {
  .app-main:has(.qf-customize) { padding-top: 60px; }
  .qf-customize .page-sub { display: none; }
  .qf-customize h1 {
    font-size: 18px;
    line-height: 40px;
    min-height: 40px;
    padding-left: 48px;
    margin-top: 0;
    margin-bottom: 8px;
  }
  /* #4 finish — nothing but the widget in the preview area on mobile: drop the
     "This is exactly what your customers see…" note (the subtle "Live preview"
     caption is enough). Desktop keeps the note. */
  .qf-cz-preview-note { display: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   CUSTOMIZE ROUND 2 — carousel arrows pushed to the section's side borders, and
   the mobile-sheet nav gets subtle borderless arrows. Appended at EOF so the
   line-based spacing/colour guard baselines above never renumber. 8px ramp only.
   ══════════════════════════════════════════════════════════════════════════ */
/* Item 2 — the option carousels inside a section card pull their arrows out to
   the card's inner edge (section side padding is 16px) so the left/right arrows
   sit at the very border and the track spans the full width. */
.qf-cz-section .qf-cz-carousel { margin-left: -12px; margin-right: -12px; }

/* Item 3 — the bottom-sheet main nav (Design/Page/Behavior + jumps) is a
   carousel; its arrows are subtle and borderless (no circle), unlike the section
   arrows. The nav wrapper carries .qf-cz-carousel--nav (added in app.js). */
.qf-cz-carousel--nav .qf-cz-carousel-arrow {
  width: 24px;
  border: 0;
  background: transparent;
  color: var(--muted);
}
.qf-cz-carousel--nav .qf-cz-carousel-arrow:hover {
  background: transparent;
  color: var(--ink);
}
/* .qf-cz-sheet-shortcuts sets flex:0 0 auto, which overrode the carousel track's
   flex:1 — so the track grew to its full content width and overflowed the
   container with NO internal scroll (chips clipped, undraggable, arrows hidden).
   Restore the track flex so it stays within the carousel and scrolls. */
.qf-cz-carousel--nav .qf-cz-carousel-track { flex: 1 1 auto; min-width: 0; }

/* Item 1 — the map-style swatch is now a mini-map SVG (land / water / road grid
   / route line); fill the swatch with it and drop the old abstract diagonal. */
.qf-cz-mapstyle-swatch svg { display: block; width: 100%; height: 100%; }
.qf-cz-mapstyle-swatch::after { display: none; }

/* The Design / Page / Behavior tabs stretch to fill the bar so it reads as one
   complete menu (was left-aligned with dead space on the right). */
.qf-cz-tabs .qf-cz-tab { flex: 1 1 0; text-align: center; }

/* ══════════════════════════════════════════════════════════════════════════
   CUSTOMIZE PREVIEW POLISH — full-height preview + aligned split header.
   (1) The preview column fills the available viewport height (flex, no fixed
       620px box, no wasted gap below the widget) — robust to the portal top-bar
       height (viewport-relative, no hardcoded chrome offset). (2) The preview
       toolbar (head) is lifted OUT of the card in app.js to sit as the RIGHT
       half of a split header, level with the left column's Design/Page/Behavior
       tab bar and with a matching bottom-border treatment; the preview card top
       then aligns with the left column's first card. Desktop-only (min-width
       901px) so the ≤900 stacked layout + ≤640 bottom-sheet stay untouched.
       Appended at EOF so the line-based spacing/colour baselines never renumber.
       Tokens + 8px ramp only; theme-aware (light + dark). ═══════════════════ */
@media (min-width: 901px) {
  /* Split-header treatment: the tab bar (left) and the preview toolbar (right)
     share the same row height + a hairline bottom border, separated by the grid
     column gap — so they read as one split header with a clear left/right
     divide. min-height matches so the two cards below them start level. */
  .qf-cz-tabs {
    min-height: 40px;
    margin-bottom: 4px;
  }
  .qf-cz-preview-head {
    min-height: 40px;
    align-items: center;
    margin-bottom: 4px;
    padding-bottom: 0;
    border-bottom: 1px solid var(--border);
  }

  /* Both columns share a 16px header→card rhythm (on the 8px ramp) so the tab
     bar's bottom and the preview toolbar's bottom sit level and the two cards
     below them start at the EXACT same vertical position: tab/head margin-bottom
     4px + column gap 16px = 20px on each side. */
  .qf-cz-controls { gap: 16px; }

  /* Full-height preview: the column fills the viewport and flexes so the widget
     frame grows into the space (no short floating card, no dead gap). It must
     account for the STATIC PORTAL TOP BAR (added in #264): --qf-bar-h is the
     bar's real measured height (published by syncBarHeight in app.js; includes
     the in-bar trial/announcement pill), with a 56px fallback for first paint.
     · top: pin the sticky column just BELOW the bar (bar height + 18px breath),
       so it no longer slides under the higher-z-index bar on scroll.
     · height: fill the viewport UNDER the bar — 36px = 18px below the bar
       (matching `top`) + 18px bottom breathing room. */
  .qf-cz-preview-col {
    display: flex;
    flex-direction: column;
    gap: 16px;
    top: calc(var(--qf-bar-h, 56px) + 18px);
    height: calc(100vh - var(--qf-bar-h, 56px) - 36px);
    height: calc(100dvh - var(--qf-bar-h, 56px) - 36px);
  }
  .qf-cz-preview {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
  }
  /* Fill the card; the frame becomes the flexible viewport (the guided-editing
     scroll-align in app.js still works — it stays a bounded overflow-y:auto box
     shorter than the widget's auto-reported content height). Overrides the fixed
     620px + calc(100vh-120px) cap from the guided-editing block above. */
  .qf-cz-frame-wrap {
    flex: 1 1 auto;
    min-height: 0;
    height: auto;
    max-height: none;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   REALISTIC BORDERLESS DESKTOP PREVIEW (≥901px) — make the live preview look
   TRUE-TO-LIFE, exactly as customers see it on the hosted page (/w/<slug>).
   The real hosted page (hostedPage.ts) centers the calculator in a plain shell
   — `.qf-hp-shell { max-width: 1180px; margin: 0 auto }` — with only the
   calculator's OWN soft-shadow card around it (`.qf-hp-frame-card`: rounded +
   shadow, transparent bg), NOT a heavy outer frame. So on desktop we:
     · strip the artificial preview-card chrome AROUND the widget (border / bg /
       box-shadow on the preview pane) so the widget floats CENTERED on the
       preview canvas, mirroring the shell's margin:auto centering; and
     · give the widget frame the real page's minimal surface — borderless, a
       soft elevation shadow + rounded corners (the widget's own card) — capped
       at the real bare-page calc width (560px, matching `.qf-hp-grid` bare) and
       centered, so it never over-stretches on wide viewports.
   The Desktop|Mobile device toggle (buildLivePreview → data-device) keeps
   driving width: desktop = natural 560px centered; mobile = 390px phone column
   (from the base rule), both borderless + softly elevated. Desktop-only so the
   ≤900 stacked layout and ≤640 bottom-sheet stay untouched. Appended at EOF so
   the line-based spacing/colour baselines never renumber. Tokens only;
   theme-aware (light + dark via style.css --radius-lg / --shadow-lg).
   ══════════════════════════════════════════════════════════════════════════ */
@media (min-width: 901px) {
  /* The preview PANE becomes the plain canvas the widget centers on (like the
     hosted page's shell/background) — no boxy card border around the widget. */
  .qf-cz-preview {
    background: transparent;
    border: 0;
    box-shadow: none;
  }
  /* The widget frame = the real page's own calculator card: borderless, softly
     elevated + rounded, centered. No artificial preview border. width:100% gives
     the flex item a definite cross-size so max-width + auto margins can cap and
     centre it (without it, the auto cross-margins collapse the frame to the
     iframe's 300px intrinsic width). */
  .qf-cz-frame-wrap {
    width: 100%;
    border: 0;
    background: transparent;
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    margin-left: auto;
    margin-right: auto;
  }
  /* Desktop device view: cap to the real bare-page calc width (560px) and
     centre — mirrors `body.qf-hp--bare .qf-hp-grid { grid-template-columns:
     minmax(0, 560px); justify-content: center }`. Mobile keeps its 390px cap
     from the base rule. */
  .qf-cz-frame-wrap[data-device="desktop"] { max-width: 560px; }
}

/* ── Text-colour custom picker + contrast warning ─────────────────────────
   Appended at EOF so the line-based guard baselines above don't renumber.
   Mirrors the accent picker's custom-swatch look; theme-token colours only. */
.qf-cz-textcolor-custom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}
.qf-cz-textcolor-customwrap {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 32px;
  padding: 0 12px;
  border-radius: 8px;
  border: 1px solid var(--border-strong);
  background: var(--surface-2);
  color: var(--ink-soft);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.qf-cz-textcolor-customwrap input[type="color"] {
  width: 18px;
  height: 18px;
  padding: 0;
  border: none;
  background: none;
  border-radius: 5px;
  cursor: pointer;
}
.qf-cz-textcolor-customwrap input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
.qf-cz-textcolor-customwrap input[type="color"]::-webkit-color-swatch { border: none; border-radius: 4px; }
.qf-cz-textcolor-hex {
  width: 96px;
  max-width: 96px;
  height: 32px;
  text-transform: lowercase;
}
.qf-cz-textcolor-warn {
  margin-top: 8px;
  padding: 8px 12px;
  border-radius: 8px;
  font-size: 12px;
  font-weight: 600;
  color: var(--warn);
  background: var(--warn-bg);
  border: 1px solid var(--warn);
}
.qf-cz-textcolor-warn[hidden] { display: none; }

/* ══════════════════════════════════════════════════════════════════════════
   DESKTOP HOSTED-PAGE PREVIEW — real width, scaled to fit (Page/Behavior tabs)
   The bare calculator (Design tab, data-preview="widget") stays at its natural
   ~560px calc width (rule above). But the HOSTED page (data-preview="hosted")
   is a full landing layout — hero + aside BESIDE the widget — that only reads
   correctly at its real desktop width (hostedPage.ts `.qf-hp-shell { max-width:
   1180px }`). Cramming it into ~500px forces its ≤640 MOBILE layout, which is
   wrong for a desktop preview. So on desktop we render the iframe at the real
   1180px and CSS-transform it DOWN to fit the pane (scale factor + heights set
   by applyHostedScale() in app.js): the sizer takes the SCALED layout box, the
   iframe is positioned + transformed inside it, and the wrap scrolls it with no
   overflow. Mobile keeps the real ~390px phone column (base rule). Appended at
   EOF so the line-based spacing/colour guard baselines never renumber. Tokens
   only; theme-aware via the shared style.css variables.
   ══════════════════════════════════════════════════════════════════════════ */
.qf-cz-frame-sizer { display: block; width: 100%; }
@media (min-width: 901px) {
  /* Let the hosted desktop preview use the FULL pane width (not the 560px calc
     cap) and clip the oversized-then-scaled iframe cleanly. Higher specificity
     (two attrs) beats the data-device="desktop" 560px cap above. */
  .qf-cz-frame-wrap[data-preview="hosted"][data-device="desktop"] {
    max-width: none;
    overflow: hidden;
  }
  /* The sizer is the scaled layout box (height set by JS); center it in the pane. */
  .qf-cz-frame-wrap[data-preview="hosted"][data-device="desktop"] .qf-cz-frame-sizer {
    position: relative;
    overflow: hidden;
    margin-left: auto;
    margin-right: auto;
  }
  /* The iframe renders at the real desktop width (set by JS) and is pulled out
     of flow so only the transformed (scaled) sizer contributes to layout. */
  .qf-cz-frame-wrap[data-preview="hosted"][data-device="desktop"] .qf-cz-frame {
    position: absolute;
    top: 0;
    left: 0;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   MOBILE PREVIEW SCROLL FIX — scroll INSIDE the iframe on touch.
   At ≤900px .qf-cz-frame-wrap is a FIXED-height pane (520px, set above). The
   desktop auto-height model sizes the iframe to the widget's full content and
   scrolls the OUTER wrap via JS — but on a phone the content-tall iframe ate the
   touch and had nothing to scroll (its doc == its own height), while the outer
   wrap was unreachable through the iframe, so the whole preview was frozen.
   Filling the pane (sizer + iframe at 100%; iframe height forced to 100% inline
   by app.js plainFrameHeight()) hands scrolling to the widget document inside
   the iframe — a touch-drag now scrolls it naturally. The sizer needs an
   explicit height so the iframe's height:100% resolves against the fixed pane.
   Desktop (≥901px) keeps the auto-height + guided-scroll model untouched.
   Appended at EOF so the line-based spacing/colour baselines never renumber. */
@media (max-width: 900px) {
  .qf-cz-frame-sizer { height: 100%; }
  .qf-cz-frame { height: 100%; }
}

/* ══════════════════════════════════════════════════════════════════════════
   MOBILE SHEET GRAB ZONE — bigger drag surface, tinted zone, dashed divider.
   The collapsed sheet's 24px grip was too small to grab with a thumb, so the
   whole TOP region — the grip handle AND the Design/Page/Behavior nav row — is
   the drag surface (wireNavDrag in app.js resizes the sheet on a VERTICAL drag,
   while a TAP still switches the tab and a HORIZONTAL drag still scrolls the nav
   carousel). These rules ONLY exist inside the ≤640 bottom sheet — the
   .qf-cz-sheet node is created by JS on mobile only — so the desktop split-
   header Design/Page/Behavior tab bar is completely untouched. Tokens only;
   theme-aware. Appended at EOF so the line-based baselines never renumber.
   ══════════════════════════════════════════════════════════════════════════ */
/* Grab-zone TINT: a subtle --ink overlay reads BRIGHTER than the sheet surface
   in dark mode (--ink is near-white) and DARKER in light mode (--ink is near-
   black) — one rule, correct in both themes, no hardcoded colours. */
.qf-cz-sheet-handle,
.qf-cz-sheet > .qf-cz-carousel--nav {
  background: color-mix(in srgb, var(--ink) 5%, transparent);
}
/* Round the grip's top to sit flush inside the sheet's rounded top edge. */
.qf-cz-sheet-handle {
  border-top-left-radius: var(--radius-lg);
  border-top-right-radius: var(--radius-lg);
}
/* Both drag axes are JS-owned in this zone (vertical = resize, horizontal =
   carousel), so take the gestures away from the browser's native pan/scroll —
   otherwise a native vertical scroll would fight the resize drag. */
.qf-cz-sheet > .qf-cz-carousel--nav { cursor: grab; touch-action: none; }
.qf-cz-sheet .qf-cz-carousel--nav .qf-cz-carousel-track { touch-action: none; }
.qf-cz-sheet.is-dragging > .qf-cz-carousel--nav { cursor: grabbing; }
/* Subtle DASHED divider beneath the three tab buttons (the nav row), marking the
   bottom of the grab zone and separating it from the scrolling controls. */
.qf-cz-sheet > .qf-cz-carousel--nav {
  border-bottom: 1px dashed var(--border);
}

/* ══════════════════════════════════════════════════════════════════════════
   INTENTIONAL MOBILE-DEVICE PREVIEW (≥901px) — the 390px phone-width widget
   previously floated in a wide, empty pane ("a straw in empty space"): the
   desktop preview PANE is transparent/borderless (block above), so the centred
   390px column sat surrounded by dead gutter that read as a bug. Present it as
   a DELIBERATE, centred phone preview instead:
     · the pane gains a SUBTLE device backdrop (soft inset surface tone) only
       while the mobile device is showing, so the surrounding space reads as
       intentional framing, not empty; and
     · the mobile frame gets a LIGHT phone chrome — a thin rounded device edge
       + a touch more elevation (NOT a chunky skeuomorphic bezel) — so it
       obviously reads as a phone sitting on that backdrop.
   Scoped with :has() so it applies ONLY in mobile-device mode; the desktop
   device view (borderless scaled hosted, #272) is untouched. The base
   data-device="mobile" 390px cap + centring still holds. Appended at EOF so
   the line-based spacing/colour baselines never renumber. Tokens + 8px ramp
   only; theme-aware (light + dark via style.css variables).
   ══════════════════════════════════════════════════════════════════════════ */
@media (min-width: 901px) {
  /* Dimmed, neutral backdrop shown ONLY while the mobile phone preview is up, so
     the gutter around the 390px column reads as an intentional stage — not a
     narrow card floating in an empty pane. Edit 8: the pane HUGS the phone
     (max-width ≈ frame + a thin margin) and centres in the column, so the frame
     occupies most of the pane width instead of sitting in a wide empty band. The
     phone itself stays centred via the base rule's margin:auto. */
  .qf-cz-preview:has(.qf-cz-frame-wrap[data-device="mobile"]) {
    max-width: 430px;
    margin-left: auto;
    margin-right: auto;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: inset 0 1px 0 var(--border);
    padding: 32px 16px;
  }
  /* Centre the toolbar above the narrowed phone card so the whole preview reads
     as one tidy centred module (no toolbar hanging wider than the phone). */
  .qf-cz-preview-col:has(.qf-cz-frame-wrap[data-device="mobile"]) .qf-cz-preview-head {
    max-width: 430px;
    margin-left: auto;
    margin-right: auto;
  }
  .qf-cz-preview:has(.qf-cz-frame-wrap[data-device="mobile"]) .qf-cz-preview-note { align-self: stretch; }
  /* Light, honest phone frame: a thin uniform bezel with a phone-like corner
     radius + soft elevation, on the dimmed backdrop above. NO notch / buttons /
     gloss / hand — CSS only, no image. The top bezel is a touch taller to hold a
     faint status strip. Edit 8: bound the frame to a REALISTIC phone viewport
     (~390×780 ≈ 2:1) so it reads like a phone, NOT a long strip — the tall
     calculator SCROLLS inside it (Edit 7 wheel-forward + the wrap's overflow).
     This is border + backdrop ONLY: the iframe inside stays fully interactive. */
  .qf-cz-preview:has(.qf-cz-frame-wrap[data-device="mobile"]) .qf-cz-frame-wrap[data-device="mobile"] {
    position: relative;
    height: 780px;
    border: 1px solid var(--border-strong);
    border-radius: 30px;
    box-shadow: var(--shadow-lg);
    background: var(--surface);
    padding: 16px 8px 12px;
  }
  /* Faint status strip in the top bezel (honest — reads as a phone's status bar,
     not a speaker notch). Anchored to the PANE (which does NOT scroll), not the
     frame-wrap (which is now the scroll container, Edit 7/8) — so it stays put
     while the calculator scrolls behind it. The pane hugs the centred phone, so
     left:50% lands over the frame; the on-grid top + a transform nudge drops it
     into the 16px top bezel. Pointer-events off so it never blocks. */
  .qf-cz-preview:has(.qf-cz-frame-wrap[data-device="mobile"])::after {
    content: '';
    position: absolute;
    top: 32px;
    left: 50%;
    transform: translate(-50%, 8px);
    width: 340px;
    max-width: 66%;
    height: 4px;
    border-radius: 999px;
    background: var(--border-strong);
    opacity: .6;
    pointer-events: none;
    z-index: 1;
  }
  /* Round the live content to follow the phone's corners inside the bezel. The
     frame-wrap already clips (overflow:hidden), so this just softens the inner
     edge; interactivity is unaffected. */
  .qf-cz-preview:has(.qf-cz-frame-wrap[data-device="mobile"]) .qf-cz-frame-wrap[data-device="mobile"] .qf-cz-frame-sizer,
  .qf-cz-preview:has(.qf-cz-frame-wrap[data-device="mobile"]) .qf-cz-frame-wrap[data-device="mobile"] .qf-cz-frame {
    border-radius: 18px;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   REAL MAP-STYLE THUMBNAILS + "VIEW ALL" GALLERIES (map + theme).
   (1) The inline map-style chip becomes a compact CARD — a REAL base-map render
       on top, label below — matching the theme-preset card (was a pill with a
       tiny SVG mini-map). Selected = accent ring (outline), never a bright fill.
   (2) A subtle right-aligned "View all" text link under each carousel opens a
       responsive GRID gallery (reuses .qf-modal-backdrop/.qf-modal-card) that
       shows every option at once. Mobile: grid → 2 cols, modal fits viewport.
   Appended at EOF so the line-based spacing/colour baselines never renumber.
   Tokens + 8px ramp only; theme-aware (light + dark via style.css variables).
   ══════════════════════════════════════════════════════════════════════════ */
/* Inline map-style chip → compact card (overrides the earlier pill rule; this
   file loads after premium-saas-polish.css so it wins the cascade). */
.qf-cz-mapstyle {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  padding: 8px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--ink);
}
.qf-cz-mapstyle:hover {border-color: var(--border-strong); }
.qf-cz-mapstyle.is-selected {
  border-color: var(--accent-fill);
  color: var(--ink);
  box-shadow: 0 0 0 1px var(--accent-fill);
}
.qf-cz-carousel-track .qf-cz-mapstyle { width: 120px; }
/* Real map render — slightly bigger than the old 28×20 SVG swatch, matched to
   the theme-preset swatch height. object-fit:cover + rounded + subtle border,
   with a placeholder surface so it never flashes blank while lazy-loading. */
.qf-cz-mapstyle-thumb {
  display: block;
  width: 100%;
  height: 46px;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface-3);
  box-shadow: var(--qf-shadow-sm);
}
.qf-cz-mapstyle-name { font-size: 12.5px; font-weight: 600; color: var(--ink); text-align: left; }

/* Subtle "View all" text link, right-aligned under the carousel. Understated —
   muted until hover, not a loud button. */
.qf-cz-viewall-row { display: flex; justify-content: flex-end; margin-top: 8px; }
.qf-cz-viewall {
  padding: 4px 8px;
  border: 0;
  background: none;
  border-radius: 8px;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
}
.qf-cz-viewall:hover { color: var(--accent); text-decoration: underline; }
.qf-cz-viewall:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Gallery modal — a wider card with a titled header + close (×), a responsive
   grid, and a footer hint. Combined selector beats the base .qf-modal-card. */
.qf-modal-card.qf-gallery-card {
  width: min(760px, 100%);
  max-height: 88vh;
  display: flex;
  flex-direction: column;
  padding: 16px;
  text-align: left;
}
.qf-gallery-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 16px;
}
.qf-gallery-head h3 { margin: 0; }
.qf-gallery-x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--ink-soft);
  cursor: pointer;
}
.qf-gallery-x:hover { border-color: var(--border-strong); color: var(--ink); }
.qf-gallery-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qf-gallery-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
  overflow-y: auto;
  padding: 4px;
}
.qf-gallery-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--ink);
  cursor: pointer;
  text-align: left;
  transition: all .2s ease;
}
.qf-gallery-tile:hover {border-color: var(--border-strong); }
.qf-gallery-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qf-gallery-tile.is-selected {
  border-color: var(--accent-fill);
  color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent-fill);
}
.qf-gallery-map-thumb {
  display: block;
  width: 100%;
  height: 96px;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface-3);
}
.qf-gallery-tile .qf-cz-preset-swatch { height: 96px; }
.qf-gallery-tile-name { font-size: 12.5px; font-weight: 600; color: var(--ink); }
/* Selected marker — a small accent check, only on the selected tile. */
.qf-gallery-check {
  position: absolute;
  top: 8px;
  right: 8px;
  display: none;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 999px;
  background: var(--accent-fill);
  color: var(--bg);
}
.qf-gallery-tile.is-selected .qf-gallery-check { display: inline-flex; }
.qf-gallery-hint { margin: 16px 0 0; font-size: 12px; color: var(--muted); }
@media (max-width: 560px) {
  .qf-gallery-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .qf-modal-card.qf-gallery-card { width: 100%; padding: 16px; }
  .qf-gallery-map-thumb { height: 80px; }
  .qf-gallery-tile .qf-cz-preset-swatch { height: 80px; }
}

/* ══════════════════════════════════════════════════════════════════════════
   Customize carousel refinements + inline unfold + header-logo grid.
   Appended at EOF so the line-based spacing/colour guard baselines never
   renumber. Tokens + 8px ramp only; theme-aware (light + dark via style.css
   CSS variables — no hardcoded colours).
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Edit 1 — Bigger map-style thumbnails ──────────────────────────────────
   The real base-map render was only ~46px tall, filling ~2/3 of its tile.
   Fill most of the tile so it reads as clearly as the theme swatch. object-fit
   /radius/border/shadow are inherited from the earlier .qf-cz-mapstyle-thumb
   rule — only the height grows here. Applies to the inline carousel chips AND
   the unfold grid (which reuses the same class). */
.qf-cz-mapstyle-thumb { height: 70px; }

/* ── Edit 2 — Soft dim/blur edge fades on every carousel ───────────────────
   Gradient overlay pseudo-elements at the track's inner edges so items appear
   to slide BEHIND a shade instead of hard-clipping. Blends to the section
   surface in BOTH themes via the token (no hardcoded colour). Toggled by
   has-fade-left / has-fade-right (set in makeCarousel.update() using the same
   atStart/atEnd logic as the arrows). Offset ~24px inboard so each fade sits
   over the track edge, just past the arrow slot; a faint backdrop blur softens
   the sliding items. Because makeCarousel is shared this covers ALL customize
   carousels (theme / map / accent / button-hover / text-colour / sheet nav). */
.qf-cz-carousel { position: relative; --qf-cz-fade-bg: var(--surface); }
.qf-cz-carousel::before,
.qf-cz-carousel::after {
  content: '';
  position: absolute;
  top: 4px;
  bottom: 4px;
  width: 40px;
  pointer-events: none;
  opacity: 0;
  z-index: 1;
  transition: all .2s ease;
}
/* The mask matches each side's colour gradient so the faint backdrop blur fades
   out inward with the shade (no hard blur seam), leaving only a soft edge. */
.qf-cz-carousel::before {
  left: 24px;
  background: var(--qf-cz-fade-bg);
  -webkit-mask-image: linear-gradient(to right, #000, transparent);
  mask-image: linear-gradient(to right, #000, transparent);
}
.qf-cz-carousel::after {
  right: 24px;
  background: var(--qf-cz-fade-bg);
  -webkit-mask-image: linear-gradient(to left, #000, transparent);
  mask-image: linear-gradient(to left, #000, transparent);
}
.qf-cz-carousel.has-fade-left::before { opacity: 1; }
.qf-cz-carousel.has-fade-right::after { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .qf-cz-carousel::before,
  .qf-cz-carousel::after { transition: none; }
}

/* ── Edit 3 — Minimal borderless arrows ────────────────────────────────────
   Drop the circle (border / surface fill / shadow) — show only the chevron
   glyph with a comfortable hit area. Hover = a subtle colour shift, no lift.
   Sit above the edge-fade so the glyph stays crisp and clickable. The
   .is-hidden / :disabled end-of-track behaviour (earlier rule) is untouched. */
.qf-cz-carousel-arrow {
  position: relative;
  z-index: 2;
  width: 26px;
  height: 26px;
  border: 0;
  background: transparent;
  box-shadow: none;
  color: var(--muted);
}
.qf-cz-carousel-arrow:hover {
  background: transparent;
  color: var(--ink);
  transform: none;
}

/* ── Edit 4 — Inline unfold (replaces the gallery modal) ───────────────────
   A "View all" link expands the section in place. Smooth height via the
   grid-template-rows 0fr↔1fr trick (no max-height guessing); the inner wrapper
   clips while collapsed. Tiles reuse the swatch / map-thumb + the SAME select
   handlers; selected = accent ring (never a bright fill). Mobile ≤560px = 2
   columns. */
.qf-cz-unfold {
  display: grid;
  grid-template-rows: 0fr;
  transition: all .3s ease;
}
.qf-cz-unfold.is-open { grid-template-rows: 1fr; }
.qf-cz-unfold-inner { overflow: hidden; min-height: 0; }
.qf-cz-unfold-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
  padding: 12px 4px 4px;
}
.qf-cz-unfold-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--ink);
  cursor: pointer;
  text-align: left;
  transition: all .2s ease;
}
.qf-cz-unfold-tile:hover {border-color: var(--border-strong); }
.qf-cz-unfold-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qf-cz-unfold-tile.is-selected {
  border-color: var(--accent-fill);
  box-shadow: 0 0 0 1px var(--accent-fill);
}
.qf-cz-unfold-tile.is-selected .qf-gallery-check { display: inline-flex; }
.qf-cz-unfold-tile-name { font-size: 12.5px; font-weight: 600; color: var(--ink); }
.qf-cz-unfold-tile .qf-cz-mapstyle-thumb { height: 92px; }
.qf-cz-unfold-tile .qf-cz-preset-swatch { height: 92px; }
/* "View all" toggle → label + rotating chevron. */
.qf-cz-unfold-toggle { display: inline-flex; align-items: center; gap: 4px; }
.qf-cz-unfold-chev { display: inline-flex; transition: all .2s ease; }
.qf-cz-unfold-toggle.is-open .qf-cz-unfold-chev { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
  .qf-cz-unfold { transition: none; }
  .qf-cz-unfold-chev { transition: none; }
}
@media (max-width: 560px) {
  .qf-cz-unfold-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ── Edit 5/9 — Header-logo section: full-width grid, every option visible ──
   The control groups (Logo size / Layout / Alignment / Show-name) were stacked
   left-aligned, leaving the right ~40% of the card empty. Lay them in a 2-col
   grid that uses the full width. Edit 9: chips WRAP (no carousel), so no option
   is ever hidden behind a scroll arrow. "Logo size" (4 opts) + the show-name
   toggle span the full row; Layout + Alignment pair on the middle row. Collapse
   to one column on mobile — the 4 size chips wrap 2×2 (never 1-alone). */
.qf-cz-header-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
  margin-top: 8px;
  align-items: start;
}
.qf-cz-header-group { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.qf-cz-header-group > .qf-cz-label { margin: 0; }
/* Full-width groups span both columns so all their options fit on one row. */
.qf-cz-header-group--full { grid-column: 1 / -1; }
/* Chips wrap freely within their group (no horizontal scroll / arrows). */
.qf-cz-header-group .qf-cz-hover-row { flex-wrap: wrap; }
.qf-cz-header-toggle { margin-top: 0; }
@media (max-width: 560px) {
  .qf-cz-header-grid { grid-template-columns: 1fr; }
}

/* ── Map-style thumbnails fill their tile edge-to-edge ─────────────────────
   The real base-map render sat inside the tile's 8px padding, framed by its
   own border/radius — leaving an empty gap all around the map image. Drop the
   tile padding and let the map go full-bleed to the top/left/right edges; the
   tile's own overflow:clip + radius supplies the rounded corners, and the
   label keeps its breathing room via its own padding. Applies to BOTH the
   inline carousel chips (.qf-cz-mapstyle) and the unfold-grid MAP tiles
   (.qf-cz-unfold-map) — not the theme tiles. Selected ring + check preserved. */
.qf-cz-mapstyle,
.qf-cz-unfold-tile.qf-cz-unfold-map {
  padding: 0;
  gap: 0;
  overflow: clip;
}
.qf-cz-mapstyle .qf-cz-mapstyle-thumb,
.qf-cz-unfold-tile.qf-cz-unfold-map .qf-cz-mapstyle-thumb {
  width: 100%;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
.qf-cz-mapstyle .qf-cz-mapstyle-name {
  padding: 8px;
}
.qf-cz-unfold-tile.qf-cz-unfold-map .qf-cz-unfold-tile-name {
  padding: 8px;
}
