/* ═══════════════════════════════════════════════════════════════════════
   CrispBeacon Builder V2 — full-screen, one-question-at-a-time layout.

   PURE LAYOUT / CHROME CSS. This file never redefines a swatch color, a
   card's visual identity, an SVG stroke, a font stack, or any pricing
   copy — all of that continues to come from /styles.css (loaded first)
   exactly as it does on /builder. This file only:
     - makes the shell fill the viewport
     - positions the live preview on the left (~60-65%) and the
       one-question rail on the right (~35-40%)
     - shows/hides via the SAME [hidden] attribute app.js already uses
     - adds a thin top progress/chips bar and a bottom price/CTA bar
     - collapses the rail into a bottom sheet under 900px
   ═══════════════════════════════════════════════════════════════════════ */

:root {
  --v2-topbar-h: 64px;
  /* v4: timeline slimmed 48px → 36px and moved from the bottom to a strip
     directly below the topbar (Owner: "the product timeline... you got a
     little timeline going. We can put that somewhere else too."). */
  --v2-timeline-h: 36px;
  /* v4: footer condensed 96px → 72px, one row only (description + price/
     CTA + Add to Cart + Save Design) — Owner: "We need to condense the
     cost... just the description of the letters and the cost, and then
     add to cart or save design." */
  --v2-bottombar-h: 72px;
  --v2-preview-h: 38vh; /* only used on mobile (<900px); see media query */
  /* v4: rail 32% → 18%, SignMonkey-style (their rail is ~17% of 1920px).
     Owner: "it needs to be smaller... a third of the size that it is
     now... We want the sign to be the highlight." Min-width guards
     against uncomfortably narrow text on smaller-than-1920 desktops. */
  --v2-rail-w: 18%;
  --v2-rail-min-w: 280px;
  --v2-ink: #0A1226;
  --v2-border: rgba(10, 18, 38, 0.12);
  --v2-bg: #F7F9FC;
  --v2-red: #E81820;
}

/* ── Cabinet preview: remove dimension/measurement chrome ───────────
   Owner: "let's get rid of that box that's around the cabinet, the
   dotted line box, and it has the hash marks in the corners... let's get
   rid of the max sign envelope 12 by 6 text, and the top text that's cut
   off. I want to get rid of that just so it's just a cabinet."

   These three pieces of SVG chrome live inside the SHARED /builder.html
   markup (which this PR must not modify) and are positioned/retinted by
   app.js on every render — so we hide them here, in the v2-only
   stylesheet, rather than deleting/editing the source SVG:
     1. #cabBandGuide     — the dashed 12'×6' envelope rect + corner hash
                             marks (#cabEnvelopeRect / #cabEnvelopeTicks)
                             + the "MAX SIGN ENVELOPE · 12′ × 6′" label
                             (#cabEnvelopeLabelBg / #cabEnvelopeLabel).
     2. #cabSizeBadgeGroup — the "48″H × 96″L · 4′ × 8′" dimension pill
                             that floats above the cabinet.
   The cabinet body/face/retainer/returns/shadow rendering itself is
   completely untouched — app.js keeps computing and writing the same
   x/y/width/height/text attributes onto these nodes every render (still
   needed for state/pricing), we simply never paint the result. Scoped to
   the v2 shell only (`body[data-v2-shell]`) so /builder's classic view is
   unaffected. ───────────────────────────────────────────────────── */
body[data-v2-shell] #cabBandGuide,
body[data-v2-shell] #cabSizeBadgeGroup {
  display: none !important;
}

html, body[data-v2-shell] {
  height: 100%;
  margin: 0;
  overflow: hidden; /* the shell manages its own internal scroll regions */
}

/* The v2 shell now hosts its OWN copy of the hamburger/logo/account-cart
   markup inside a real <header> bar (see builder-v2.html), reusing the
   exact classes/ids site-chrome.js, account-control.js, and header-cart.js
   already bind to. We still hide the classic in-page "brand-bar" title
   strip (kicker/subtitle) since the v2 shell doesn't have that slot. */
body[data-v2-shell] > main.page > header.brand-bar {
  display: none !important;
}

.v2-shell {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  background: var(--v2-bg);
  font-family: 'Space Grotesk', 'Inter', system-ui, sans-serif;
  color: var(--v2-ink);
}

/* ── Header bar ──────────────────────────────────────────────────────
   Classic CrispSign chrome (hamburger + logo + cart/account) laid out
   INLINE in a real fixed-height bar instead of site-chrome.css's default
   position:fixed-over-hero treatment (there is no hero here to float
   over -- the v2 shell is 100% viewport-height from the first frame).
   We only override position/display/size for these shared classes,
   scoped under .v2-topbar so /builder and every other page keeps the
   exact same floating behavior it has today. ────────────────────────── */
.v2-topbar {
  height: var(--v2-topbar-h);
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 16px;
  background: #fff;
  border-bottom: 1px solid var(--v2-border);
  z-index: 30;
  position: relative;
}
.v2-topbar .floating-menu-btn {
  position: relative;
  top: auto; left: auto;
  width: 40px;
  height: 40px;
  background: var(--v2-ink);
  flex: 0 0 auto;
}
.v2-topbar .floating-menu-btn span { background: #fff; }
.v2-topbar .legal-logo {
  position: relative;
  top: auto; left: auto;
  height: 34px;
  flex: 0 0 auto;
  /* Override site-chrome.css's <=380px "hide the wordmark entirely"
     rule (written for the floating-over-hero layout, where the logo has
     to compete with the fixed cart/account cluster for the same strip of
     space). Our bar uses normal flex layout with a spacer between the
     logo and the account cluster, so there's always room — keep the
     CrispSign wordmark visible at every width. */
  display: inline-flex !important;
}
.v2-topbar .legal-logo img { height: 34px; }
.v2-topbar-spacer { flex: 1 1 auto; min-width: 0; }
.v2-topbar .account-controls {
  position: relative;
  top: auto; right: auto;
  flex: 0 0 auto;
}
.v2-classic-link {
  flex: 0 0 auto;
  font-size: 12px;
  font-weight: 600;
  color: #5C6273;
  text-decoration: none;
  border: 1px solid var(--v2-border);
  border-radius: 8px;
  padding: 6px 10px;
  margin-left: 4px;
}
.v2-classic-link:hover { color: var(--v2-ink); border-color: var(--v2-ink); }

/* ── Top timeline (horizontal step dots) ─────────────────────────────────
   v4: moved from the bottom of the viewport to a thin strip directly
   below the topbar, ABOVE the preview+rail split. Owner: "The product
   timeline... you got a little timeline going. We can put that somewhere
   else too." Same click-back behavior as before — completed dots are
   red-filled and back-navigable; the current dot gets a red ring;
   upcoming dots are outlined-gray and not clickable. The connecting line
   is red up to the current step, gray beyond. Horizontal scroll with fade
   edges on narrow mobile instead of wrapping to multiple rows. ────────── */
.v2-timeline {
  flex: 0 0 auto;
  height: var(--v2-timeline-h);
  background: #fff;
  border-top: none;
  border-bottom: 1px solid var(--v2-border);
  z-index: 25;
  display: flex;
  align-items: center;
}
.v2-timeline-scroll {
  width: 100%;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 16px, #000 calc(100% - 16px), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 16px, #000 calc(100% - 16px), transparent 100%);
}
.v2-timeline-scroll::-webkit-scrollbar { display: none; }
.v2-timeline-track {
  display: flex;
  align-items: center;
  min-width: max-content;
  padding: 0 20px;
  gap: 0;
}
.v2-tl-step {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
}
.v2-tl-dot-wrap {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 6px;
  background: none;
  border: none;
  cursor: default;
  padding: 2px 8px;
  font-family: inherit;
}
.v2-tl-dot-wrap.is-clickable { cursor: pointer; }
.v2-tl-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  border: 2px solid #D5D9E2;
  background: #fff;
  box-sizing: border-box;
  flex: 0 0 auto;
  transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.v2-tl-step.is-done .v2-tl-dot {
  background: var(--v2-red);
  border-color: var(--v2-red);
}
.v2-tl-step.is-current .v2-tl-dot {
  background: #fff;
  border-color: var(--v2-red);
  box-shadow: 0 0 0 3px rgba(232, 24, 32, 0.22);
}
.v2-tl-label {
  font-size: 11px;
  font-weight: 600;
  color: #9AA0AD;
  white-space: nowrap;
  max-width: 84px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.v2-tl-step.is-done .v2-tl-label { color: var(--v2-ink); }
.v2-tl-step.is-current .v2-tl-label { color: var(--v2-red); font-weight: 800; }
.v2-tl-dot-wrap.is-clickable:hover .v2-tl-label { color: var(--v2-red); }
.v2-tl-line {
  width: 20px;
  height: 2px;
  flex: 0 0 auto;
  background: #D5D9E2;
}
.v2-tl-line.is-done { background: var(--v2-red); }

/* ── Main split ──────────────────────────────────────────────────────── */
.v2-main {
  flex: 1 1 auto;
  display: flex;
  min-height: 0;
}

.v2-preview {
/* v4: preview is the star of the screen -- it takes essentially all
   remaining space once the fixed-width 18% rail is subtracted (rail uses
   flex: 0 0 var(--v2-rail-w), so this flex: 1 1 auto claims the rest).
   Owner: "I want them to be full screen... I want it to take up the
   whole screen. Like I said, screen to screen." */
  flex: 1 1 auto;
  min-width: 0;
  position: relative;
  background: #11151F;
  display: flex;
}
.v2-preview-inner {
  flex: 1 1 auto;
  position: relative;
  min-height: 0;
}
/* The transplanted #canvasWrap fills the whole preview pane instead of
   the small sticky "preview card" box it lives in on /builder. Only
   sizing rules — the SVG content/layers/diagrams are untouched. */
.v2-preview-inner .canvas-wrap {
  position: absolute !important;
  inset: 0 !important;
  width: 100% !important;
  height: 100% !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  margin: 0 !important;
}
.v2-preview-inner .wall-canvas {
  width: 100% !important;
  height: 100% !important;
}

/* ── Day/Night floating toggle host ──────────────────────────────────────
   Owner: "For step 5, let's put that toggle Day and Night inside the
   design area. I want that to just be toggleable all the time."
   Placement decision: "Top-center of the preview area." Sits ABOVE the
   sign so it never overlaps the artwork, and is visible on every rail
   step (it is no longer part of the sequenced flow at all). ───────────── */
.v2-preview-floating {
  position: absolute;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* The classic /builder view injects a floating "quick controls" mini-bar
   (height/color/day-night shortcuts) INSIDE #canvasWrap so those controls
   stay reachable while scrolling past a long, single-page control column
   (see app.js initFloatingControls()). In the v2 one-question-at-a-time
   rail that scroll problem doesn't exist — the same height/color/day-night
   controls are always one click away in the rail or the floating toggle
   above — so this redundant overlay is hidden here only. It is NOT
   removed: the exact same DOM node/logic still exists and still works
   unmodified on /builder. */
#quickControls { display: none !important; }

/* Floating Day/Night pill — same #modeToggle node/listeners as the
   classic builder, restyled here (scoped to .v2-floating-toggle only, no
   change to styles.css's own .mode-toggle/.toggle-pill rules used by
   /builder) as a segmented pill with a subtle backdrop and a RED active
   highlight, per the Owner's placement + style direction. Hidden
   entirely on the Cabinet flow (no lighting concept) via toggleBlock.hidden
   set in builder-v2.js. */
.v2-floating-toggle {
  background: rgba(255, 255, 255, 0.94);
  border-radius: 999px;
  padding: 0;
  box-shadow: 0 6px 20px rgba(10, 18, 38, 0.18);
  border: none;
  margin: 0;
}
.v2-floating-toggle legend { display: none; } /* "Lighting" label redundant on a self-explanatory Day/Night pill floating over the live preview */
.v2-floating-toggle .toggle-pill {
  width: 148px;
  height: 40px;
  padding: 8px 16px;
  background: var(--v2-ink);
  border: none;
  border-radius: 999px;
  font-size: 12px;
  letter-spacing: 0.06em;
}
.v2-floating-toggle .toggle-option {
  color: rgba(255, 255, 255, 0.65);
  font-weight: 700;
}
.v2-floating-toggle .toggle-thumb {
  background: var(--v2-red);
  top: 4px;
  left: 4px;
  width: calc(50% - 4px);
  height: calc(100% - 8px);
}
.v2-floating-toggle .mode-toggle[aria-checked="false"] .toggle-option-day,
.v2-floating-toggle .mode-toggle[aria-checked="true"]  .toggle-option-night {
  color: #fff;
}
.v2-floating-toggle .mode-toggle:focus-visible .toggle-pill {
  box-shadow: 0 0 0 3px rgba(232, 24, 32, 0.35);
}

/* -- Question rail -- v4: shrunk to ~18% wide (SignMonkey-style; their rail
   is ~17% of 1920px). Owner: "it needs to be smaller. It needs to be
   condensed. It needs to be a third of the size that it is now... We
   want the sign to be the highlight so you can see what you're
   building." A hard min-width keeps text legible on sub-1920 desktops
   without the rail ballooning back toward its old ~32% share. -- */
.v2-rail {
  flex: 0 0 var(--v2-rail-w);
  max-width: 420px;
  min-width: var(--v2-rail-min-w);
  background: #fff;
  border-left: 1px solid var(--v2-border);
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.v2-rail-scroll {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 14px 14px 24px;
}

.v2-question-counter {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #E81820;
  margin: 0 0 6px;
}
.v2-question-title {
  font-size: 24px;
  font-weight: 800;
  margin: 0 0 5px;
  line-height: 1.15;
}
.v2-question-hint {
  font-size: 13px;
  color: #5C6273;
  margin: 0 0 16px;
  line-height: 1.45;
}
.v2-question-body {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* Product Type step: make the two cards (Channel Letters / Cabinet Sign)
   occupy the FULL rail width instead of the ~60%-wide row the classic
   .builder-topbar grid produces. Tried both a 50/50 side-by-side row and
   a stacked layout locally (see PR body); stacked wins because the
   product hero art is a wide ~1.8:1 banner image — stacking lets each
   image render notably larger and more legible at full rail width, which
   also directly answers the Owner's "make it bigger" note. Only touches
   the product-card layout that is TRANSPLANTED into the v2 rail; the
   classic /builder topbar strip keeps its original compact row. */
.v2-question-body #productCards.product-cards {
  display: flex !important;
  flex-direction: column !important;
  flex-wrap: nowrap !important;
  gap: 10px !important;
  overflow: visible !important;
  width: 100%;
}
.v2-question-body #productCards.product-cards .product-card {
  flex: 1 1 auto !important;
  width: 100% !important;
  min-width: 0 !important;
  min-height: 96px;
  justify-content: center !important;
  padding: 14px !important;
}
.v2-question-body #productCards.product-cards .product-card-hero {
  max-height: 108px;
}

/* Tighten the option-card rail ~10-15% per Owner feedback ("the steps on
   the right-hand side are just a little bit too big"). Scoped to cards
   INSIDE the transplanted question body only — styles.css / the classic
   /builder page keep their original card sizing untouched. */
.v2-question-body .style-cards,
.v2-question-body .mount-cards,
.v2-question-body .install-cards,
.v2-question-body .cab-config-cards,
.v2-question-body .cab-color-cards,
.v2-question-body .cab-face-cards,
.v2-question-body .cab-design-cards {
  gap: 8px !important;
}
.v2-question-body .style-card,
.v2-question-body .mount-card,
.v2-question-body .install-card,
.v2-question-body .cab-config-card,
.v2-question-body .cab-color-card,
.v2-question-body .cab-face-card,
.v2-question-body .cab-design-card {
  padding: 12px 12px 10px !important;
}
.v2-question-body .style-card-title,
.v2-question-body .mount-card-title,
.v2-question-body .install-card-title,
.v2-question-body .cab-config-title,
.v2-question-body .cab-face-title,
.v2-question-body .cab-color-name,
.v2-question-body .cab-design-card-title {
  font-size: calc(1em - 1.5px);
}
.v2-question-body .style-card-desc,
.v2-question-body .mount-card-desc,
.v2-question-body .install-card-desc,
.v2-question-body .cab-design-card-desc {
  font-size: calc(1em - 1px);
}

/* v4: rail audit at 18% width (~280-420px) -- Mounting's 3 cards, Cabinet
   Body's swatch grid, Cabinet Configuration's 2 cards, and Installation's
   2 cards all previously relied on the classic multi-column grid (3-up /
   2-up), which truncates badly once the rail shrank from ~32% to ~18%.
   Force all of these to a SINGLE column inside the v2 rail specifically
   (the classic /builder page's own multi-column layouts are untouched --
   this only overrides the TRANSPLANTED copies rendered inside
   .v2-question-body). Owner: "I want stuff neatly packaged in little
   boxes like we had before" -- stacking keeps each card at full,
   comfortable width instead of squeezing 2-3 across. */
.v2-question-body .mount-cards,
.v2-question-body .install-cards,
.v2-question-body .cab-config-cards,
.v2-question-body .cab-face-cards-2up {
  grid-template-columns: 1fr !important;
}
/* Mount card icon illustrations were sized for a 3-up grid at ~420px+ per
   card; at full rail width (single column, ~250-380px) they can render
   even larger/clearer -- cap the aspect box so they don't get TOO tall. */
.v2-question-body .mount-card-icon { max-height: 120px; }
/* Cabinet Body color swatches (.cab-color-card) keep a small 2-up grid
   instead of forcing 1-up -- they're compact swatch chips, not full
   illustrated cards, so 2 columns still reads cleanly at 18% width and
   avoids an overly long single-column list of ~6+ color options. */
.v2-question-body .cab-color-grid { grid-template-columns: repeat(2, 1fr) !important; }
/* The transplanted control-block fieldsets carry their own margins/borders
   from styles.css intended for a stacked list; neutralize the outer
   spacing so they sit flush inside a single rail card. */
.v2-question-body > .control-block,
.v2-question-body fieldset.control-block {
  margin: 0 !important;
  border: none !important;
  padding: 0 !important;
}
.v2-question-body .control-header { display: none; } /* title/hint already shown above */

/* ── Color step: teaching photos + re-shown sub-group labels ──────────
   Owner feedback: "You don't have them labeled where the actual colors
   are. I want those labeled, and I want them to be shown with a photo so
   that you can see an actual photo of what we're trying to display."
   Each of the (up to 3) .rail-card sections inside #colorBlock -- Face,
   Return, Trim cap -- gets its own quiet uppercase eyebrow label re-shown
   (the blanket `.control-header { display: none }` above hid these since
   the old sequenced-step title duplicated them; the Color step's single
   shared step title 'Color' does NOT cover 3 distinct sub-groups, so they
   need their own labels back) plus the new v2-color-teach photo block
   builder-v2.js prepends via patchColorTeachingPhotos(). No swatch grid,
   button, id, or click-handler markup is touched by any of this -- purely
   additive chrome above what app.js already renders. */
.v2-question-body #colorBlock .color-face > .control-header,
.v2-question-body #colorBlock .sub-picker > .control-header {
  display: flex !important;
}
.v2-question-body #colorBlock .control-header .control-hint {
  display: inline;
}

.v2-color-teach {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 12px;
}
.v2-color-teach-photo {
  flex: 0 0 auto;
  width: 100px;
  height: 80px;
  border-radius: 8px;
  overflow: hidden;
  background: #E9E4D8; /* neutral placeholder while the photo loads */
  box-shadow: 0 1px 2px rgba(10, 18, 38, 0.12);
}
.v2-color-teach-photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 35%;
}
/* Halo-Lit's single "Body color" group shows a 2-up pair (face + return
   detail) since that one swatch group governs both -- see
   COLOR_TEACHING_PHOTOS.face.halolit in builder-v2.js. */
.v2-color-teach-photo-pair {
  display: flex;
  gap: 2px;
}
.v2-color-teach-photo-pair img {
  width: 50%;
  flex: 1 1 50%;
}
.v2-color-teach-copy {
  flex: 1 1 auto;
  min-width: 0;
}
.v2-color-teach-label {
  margin: 0 0 2px;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: calc(13px * var(--ui-scale, 1));
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--navy, #0A1226);
}
.v2-color-teach-desc {
  margin: 0;
  font-family: var(--font-body);
  font-size: calc(12px * var(--ui-scale, 1));
  line-height: 1.35;
  color: var(--ink-muted, #5C6273);
}
@media (max-width: 560px) {
  .v2-color-teach-photo {
    width: 72px;
    height: 58px;
  }
  .v2-color-teach-label { font-size: 12px; }
  .v2-color-teach-desc { font-size: 11px; }
}

/* ── Letter Type step: real photorealistic thumbnails ─────────────────
   Owner feedback: "these are just placeholders... I want photo realistic
   images of what these are going to actually look like." app.js still
   renders the exact same 4 .style-card buttons (data-value/state untouched
   — see builder-v2.js patchLetterTypeCards()), we only swap what's INSIDE
   the existing .style-card-thumb span for a real photo of the matching
   product shot in /assets/letter-types/ (served absolute, same convention
   as every other asset path in this app), sized into a clean 4:3 frame, and
   restyle the grid into a 2-up (desktop) / 1-up (mobile) card layout so the
   photo, bold label, and small description all read clearly — same "selected"
   red-ring styling as every other option card (.style-card.is-active is
   untouched, from styles.css). No option id, value, or click handler changes. */
.v2-question-body #styleCards.style-cards {
  display: grid !important;
  grid-template-columns: repeat(2, 1fr) !important;
  gap: 12px !important;
}
.v2-question-body #styleCards .style-card {
  flex-direction: column !important;
  align-items: stretch !important;
  padding: 0 0 12px !important;
  overflow: hidden;
  gap: 0;
}
.v2-question-body #styleCards .style-card-thumb {
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: 10px 10px 0 0;
  margin: 0 0 10px;
  background: #E9E4D8; /* neutral placeholder color while the photo loads */
  overflow: hidden;
}
.v2-question-body #styleCards .style-card-thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 30%; /* keeps the "C" centered, crops sky/floor margin */
}
.v2-question-body #styleCards .style-card-title,
.v2-question-body #styleCards .style-card-desc {
  padding-left: 12px;
  padding-right: 12px;
}
.v2-question-body #styleCards .style-card-title {
  font-size: 14px !important;
  font-weight: 700;
  margin-top: 2px;
}
.v2-question-body #styleCards .style-card-desc {
  display: block !important; /* override the classic topbar's "hide desc" rule */
  font-size: 12px !important;
  color: var(--ink-muted, #5C6273);
  margin-top: 2px;
}
@media (max-width: 560px) {
  .v2-question-body #styleCards.style-cards {
    grid-template-columns: 1fr !important;
  }
}

/* ── Sizing step (Letter height + Return depth combined) ─────────────
   Owner request: merge these two into a single rail card, same pattern
   as the Color step's Face/Return/Trim swatch grouping (each of THOSE
   sub-sections already renders its own quiet uppercase .control-label
   eyebrow via the shared styles.css rule). Letter height's containing
   .control-header is caught by the blanket `display: none` rule above
   (its title/hint were redundant with the old standalone step's own
   title) — re-show just its .control-label eyebrow here now that the
   step title ('Sizing') no longer duplicates it. Return depth already
   renders its own <legend class="control-label"> outside .control-header,
   so it needs no extra override — it already reads as "RETURN DEPTH". */
.v2-question-body .v2-sizing-height > .control-header {
  display: flex !important;
}
.v2-question-body .v2-sizing-height > .control-header .control-label {
  display: inline;
}
.v2-question-body .v2-sizing-height > .control-header .control-hint {
  display: none; /* step hint above already covers both sub-controls */
}
/* Thin divider between the Letter height slider and the Return depth
   segmented control, matching the gutter treatment already used between
   the Color step's stacked swatch cards. */
.v2-sizing-height {
  padding-bottom: 18px !important;
  margin-bottom: 18px !important;
  border-bottom: 1px solid var(--v2-border) !important;
}
.v2-sizing-depth {
  margin-top: 0 !important;
}

/* ── Cabinet consolidated steps: "Cabinet size & configuration" and
   "Cabinet body & face" each stack two fieldsets that used to be their
   own standalone sequenced steps. Same treatment as the Sizing merge
   above: re-show each sub-block's own quiet uppercase .control-label
   eyebrow (blanket `.control-header { display: none }` rule above hid it
   since the old title/hint were redundant with the standalone step's own
   title) and paint a thin divider between the two stacked fieldsets. No
   DOM restructuring, no new nodes. */
.v2-question-body .v2-cabpair-block > .control-header {
  display: flex !important;
}
.v2-question-body .v2-cabpair-block > .control-header .control-label {
  display: inline;
}
.v2-question-body .v2-cabpair-block > .control-header .control-hint {
  display: none; /* step hint above already covers both sub-controls */
}
.v2-cabpair-first {
  padding-bottom: 18px !important;
  margin-bottom: 18px !important;
  border-bottom: 1px solid var(--v2-border) !important;
}
.v2-cabpair-second {
  margin-top: 0 !important;
}

.v2-question-nav {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 28px;
  padding-top: 20px;
  border-top: 1px solid var(--v2-border);
}
.v2-nav-back {
  background: none;
  border: 1px solid var(--v2-border);
  border-radius: 10px;
  padding: 12px 16px;
  font-weight: 700;
  font-size: 14px;
  color: var(--v2-ink);
  cursor: pointer;
}
.v2-nav-back:hover { background: #F4F6FA; }
.v2-nav-continue {
  flex: 1 1 auto;
  background: #E81820;
  color: #fff;
  border: none;
  border-radius: 10px;
  padding: 14px 20px;
  font-weight: 800;
  font-size: 15px;
  cursor: pointer;
  transition: opacity 0.15s ease, transform 0.1s ease;
}
.v2-nav-continue:hover:not(:disabled) { transform: translateY(-1px); }
.v2-nav-continue:disabled { opacity: 0.35; cursor: not-allowed; }

/* == Bottom bar == v4: condensed to ONE row -- sign description, price
   (or the state-aware Log in / Create Account CTA), Add to Cart, Save
   Design. Owner: "We need to condense the cost. I want to get rid of the
   free quotes optional design help for $25 built-in proof before
   printing. I want all of that taken out. I want just the description of
   the letters and the cost, and then add to cart or save design. But we
   can condense that down." All filler copy (the "YOUR ESTIMATE" label +
   "Free quote (bullet) Optional design help +$25 (bullet) Built-in proof
   before printing" caveat) is hidden here via CSS -- app.js's own DOM/
   values are never removed, only visually suppressed, so nothing else
   that reads those nodes breaks. builder-v2.js injects the live
   description string + state-aware price/CTA markup described below. */
.v2-bottombar {
  flex: 0 0 auto;
  border-top: 1px solid var(--v2-border);
  background: #fff;
  z-index: 20;
}
.v2-bottombar-inner {
  height: 100%;
  padding: 0 20px;
  display: flex;
  align-items: center;
}
/* The transplanted .checkout-bar was designed as a stacked block at the
   bottom of a long scroll; lay it out as ONE compact horizontal row
   instead. Purely flex/spacing -- no color/price copy changes to the
   underlying nodes. */
.v2-bottombar-inner .checkout-bar {
  display: flex !important;
  align-items: center;
  gap: 14px;
  flex-wrap: nowrap;
  width: 100%;
  margin: 0 !important;
  padding: 10px 0 !important;
  border: none !important;
}
.v2-bottombar-inner .checkout-summary {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex: 1 1 auto;
  min-width: 0;
  order: -1; /* description reads first, leftmost */
}
/* Remove ALL filler text per Owner request: "YOUR ESTIMATE" label and the
   "Free quote / Optional design help +$25 / Built-in proof before
   printing" caveat entirely. The itemized breakdown (#checkoutItemized,
   only shown for priced preset shapes) and the live price/description are
   the only checkout-summary content that remains visible. */
.v2-bottombar-inner .checkout-label,
.v2-bottombar-inner .checkout-fineprint {
  display: none !important;
}
/* Live-computed sign description ("Front-Lit (bullet) 24in tall (bullet)
   Hunter Red (bullet) Direct Mount") injected by builder-v2.js next to the
   real #checkoutSummaryLine node -- reuses the same underlying summary
   line app.js already writes into #checkoutSummaryLine (material +
   dimensions), restyled to read as the footer's leading column instead of
   a small caption under a big price. */
.v2-bottombar-inner .checkout-summary-line {
  display: block !important;
  font-size: 13px;
  font-weight: 600;
  color: var(--v2-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.v2-bottombar-inner .checkout-price {
  font-size: 20px;
  font-weight: 800;
}
.v2-bottombar-inner .save-design-status,
.v2-bottombar-inner .save-design-link { font-size: 12px; }
.v2-bottombar-inner .cta-primary,
.v2-bottombar-inner .cta-secondary {
  flex: 0 0 auto;
  white-space: nowrap;
}

.v2-pulse { animation: v2pulse 0.4s ease 3; }
@keyframes v2pulse { 50% { transform: scale(1.04); } }

/* == Design Essentials == moved to the TOP of the right rail (v4). Owner:
   "the bottom header that you have on this thing where you have 'Design
   Essentials'... that can be in a different spot." Compact, stacked
   controls (Design path toggle / Sign text or Upload / Font picker
   button) sit above the current-question card, inside their own quiet
   card so nothing feels "shoved together" -- Owner: "I want stuff neatly
   packaged in little boxes like we had before." Same underlying nodes/
   ids/listeners as before -- only relocated, never duplicated. == */
.v2-essentials-top {
  padding: 2px 2px 12px;
}
.v2-essentials-eyebrow {
  margin: 0 0 8px;
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #8A8F9C;
}
.v2-essentials-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.v2-rail-divider {
  height: 1px;
  background: var(--v2-border);
  margin: 0 0 16px;
}
/* Neutralize the stacked-list spacing these control-blocks carry from
   styles.css (intended for a full rail card) so they sit flush inside
   the compact Design Essentials strip instead. */
.v2-essentials-body > .control-block,
.v2-essentials-body > fieldset.control-block {
  margin: 0 !important;
  border: none !important;
  padding: 0 !important;
}
.v2-essentials-body .control-header { display: none; } /* eyebrow label already covers this */

/* Design-mode selector — v2 rail. The toggle now holds a single option
   ('Use Text') after the Valet restructure, so a horizontal single-
   button container is fine again. The Valet CTA lives OUTSIDE this
   container as its own .cl-valet-cta block (see styles.css). */
.v2-essentials-body .cl-mode-toggle {
  display: flex;
  gap: 4px;
  width: 100%;
}
.v2-essentials-body .cl-mode-btn {
  flex: 1 1 0;
  font-size: 11.5px;
  padding: 8px 10px;
  white-space: nowrap;
}

/* v2 override for the standalone Valet CTA: tighter typography to match
   the compact v2 rail, but same visual treatment as v1 (outlined white
   card, subtle amber hover). */
.v2-essentials-body .cl-valet-cta {
  margin-top: 8px;
  padding: 10px 12px;
}
.v2-essentials-body .cl-valet-cta-headline { font-size: 12px; }
.v2-essentials-body .cl-valet-cta-sub      { font-size: 11px; }

/* Sign text field -- full rail width now (stacked layout, not inline). */
.v2-essentials-body .signtext-block { width: 100%; }
.v2-essentials-body .signtext-block .control-label,
.v2-essentials-body .signtext-block #textInputLabel {
  display: none; /* eyebrow + placeholder already communicate purpose in this compact context */
}
.v2-essentials-body .text-input {
  width: 100%;
  box-sizing: border-box;
  padding: 9px 10px;
  font-size: 13px;
}

/* Upload Artwork inline button (Channel Letters, Design path = Upload).
   Compact to just the essential controls -- same #clArtworkUpload input /
   label, same listeners, only re-styled smaller for the narrow rail. */
.v2-essentials-body .cl-upload-block { width: 100%; }
.v2-essentials-body .cab-artwork-drop {
  flex-direction: row;
  padding: 8px 10px;
  gap: 6px;
}
.v2-essentials-body .cab-artwork-drop-icon { width: 18px; height: 18px; }
.v2-essentials-body .cab-artwork-drop-primary { font-size: 12px; }
.v2-essentials-body .cab-artwork-drop-secondary,
.v2-essentials-body .cab-artwork-drop-tertiary,
.v2-essentials-body .cab-artwork-drop-gate,
.v2-essentials-body .cl-myfiles,
.v2-essentials-body .cl-artwork-size,
.v2-essentials-body .cl-artwork-count,
.v2-essentials-body .cl-artwork-proof {
  display: none; /* keep the strip to just the drop target + filename; full detail still available once artwork is attached via the meta row below */
}
.v2-essentials-body .cl-artwork-meta { display: flex; font-size: 11px; }

/* Font picker -- now a BUTTON that opens the SignMonkey-style vertical
   preview panel (see .v2-font-panel-* below) instead of an inline
   dropdown. We keep the real #fontDropdown/#fontGrid nodes alive
   (off-screen, display:none) purely as the state/selection source of
   truth that app.js already drives; builder-v2.js reads/writes them
   and renders this trigger + the panel as the actual UI. */
.v2-essentials-body .font-block { width: 100%; }
.v2-essentials-body .font-grid { display: none; }
.v2-essentials-body .font-dropdown { display: none !important; } /* replaced by .v2-font-trigger */
.v2-essentials-body .selection-meta { display: none; } /* redundant with the trigger button's own label */
.v2-font-trigger {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  box-sizing: border-box;
  padding: 9px 10px;
  border: 1px solid var(--v2-border);
  border-radius: 10px;
  background: #fff;
  cursor: pointer;
  font-family: inherit;
  text-align: left;
}
.v2-font-trigger:hover { border-color: var(--v2-red); }
.v2-font-trigger-glyph {
  flex: 0 0 auto;
  font-size: 18px;
  font-weight: 800;
  line-height: 1;
  color: var(--v2-ink);
}
.v2-font-trigger-name {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--v2-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.v2-font-trigger-caret { flex: 0 0 auto; color: #9AA0AD; font-size: 11px; }

/* Cabinet Design path picker -- v2 (this PR): 2 buttons instead of 3.
   Owner's vision: customers choose "Upload artwork" or "AI concept help"
   (which now surfaces the AI Design Studio marketing promo, see
   .v2-ai-promo below) -- "CrispSign designs it" is removed from the rail.
   The underlying builder.html markup still has all 3 .cab-design-card
   nodes (untouched, same ids/listeners/app.js state) -- we CSS-hide the
   removed one and lay the remaining 2 out side-by-side for more visual
   weight, per spec ("make it 2-across so each button gets more visual
   weight"). .v2-cab-design-cards-2up is added by builder-v2.js at init. */
.v2-essentials-body .cab-design-cards {
  display: flex !important;
  flex-direction: column;
  gap: 6px;
}
.v2-essentials-body .cab-design-cards.v2-cab-design-cards-2up {
  flex-direction: row;
  flex-wrap: wrap;
  gap: 8px;
}
.v2-essentials-body .cab-design-card[data-value="crispsign_design"] {
  display: none !important; /* removed from the Cabinet Design Essentials rail per spec -- node/state untouched */
}
.v2-essentials-body .cab-design-card {
  flex: 1 1 auto;
  padding: 8px 10px !important;
  min-width: 0;
}
.v2-essentials-body .cab-design-cards.v2-cab-design-cards-2up .cab-design-card {
  flex: 1 1 0;
  min-height: 44px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  text-align: center;
  padding: 10px 8px !important;
}
.v2-essentials-body .cab-design-card-icon { width: 16px; height: 16px; }
.v2-essentials-body .cab-design-card-title { font-size: 11.5px; }
.v2-essentials-body .cab-design-card-desc,
.v2-essentials-body .cab-design-card-meta { display: none; } /* compact rail context -- title + icon is enough to distinguish the 2 paths */
.v2-essentials-body .cab-design-block {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
}
.v2-essentials-body .cab-artwork-uploader,
.v2-essentials-body .cab-design-notes {
  margin: 0;
  width: 100%;
  min-width: 0;
}
.v2-essentials-body .cab-design-notes-label { font-size: 10.5px; margin-bottom: 2px; }
.v2-essentials-body .cab-design-notes-counter { margin-top: 2px; font-size: 10px; }
.v2-essentials-body .cab-design-notes-input {
  min-height: 48px;
  width: 100%;
  box-sizing: border-box;
  font-size: 12px;
  padding: 6px 8px;
  resize: none;
}
/* "Change design path" modal-trigger fallback for the Cabinet flow, per
   the non-goal note: "If it truly can't fit at 18% rail, make the
   Cabinet Design path panel be a modal-style 'Change design path' button
   in the rail top that opens a full-screen picker." Hidden by default;
   builder-v2.js only reveals it (and hides .cab-design-block in the rail)
   if the measured rail content would overflow badly. */
.v2-cab-design-modal-btn {
  display: none;
  width: 100%;
  box-sizing: border-box;
  padding: 9px 10px;
  border: 1px solid var(--v2-border);
  border-radius: 10px;
  background: #fff;
  font-weight: 700;
  font-size: 12.5px;
  cursor: pointer;
  text-align: left;
}
.v2-cab-design-modal-btn.is-active-mode { display: block; }
.v2-essentials-body.v2-cab-design-collapsed .cab-design-block { display: none; }

/* ═══════════════════════════════════════════════════════════════════════
   AI Design Studio marketing promo panel ("AI concept help")
   PR: feat/beacon-v2-cabinet-ai-perk-promo (Phase 1 of 2)

   Net-new marketing content -- shown in place of the upload dropzone/
   design-notes area when the Cabinet Design Essentials "AI concept help"
   button is active. Navy-to-navy gradient card, red accent numbered
   steps + CTA, feels like a "special offer" card per spec. Does not touch
   pricing, options, or any existing control-block styling.
   ═══════════════════════════════════════════════════════════════════════ */
.v2-ai-promo {
  width: 100%;
  box-sizing: border-box;
  border-radius: 14px;
  padding: 16px 14px 14px;
  background: linear-gradient(160deg, var(--v2-ink) 0%, #1B2740 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 6px 18px rgba(10, 18, 38, 0.22);
  color: #F4F6FB;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.v2-ai-promo-head {
  display: flex;
  align-items: flex-start;
  gap: 8px;
}
.v2-ai-promo-emoji { font-size: 20px; line-height: 1; flex: 0 0 auto; }
.v2-ai-promo-title {
  margin: 0;
  font-size: 14.5px;
  font-weight: 800;
  line-height: 1.3;
  color: #fff;
}
.v2-ai-promo-desc {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: rgba(244, 246, 251, 0.82);
}
.v2-ai-promo-steps-label {
  margin: 0 0 8px;
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(244, 246, 251, 0.55);
}
.v2-ai-promo-steps-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.v2-ai-promo-steps-list li {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 12px;
  line-height: 1.4;
  color: rgba(244, 246, 251, 0.92);
}
.v2-ai-promo-num {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--v2-red);
  color: #fff;
  font-size: 10.5px;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 1px;
}
.v2-ai-promo-live {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 10px 10px;
  border-radius: 10px;
  background: rgba(46, 196, 121, 0.12);
  border: 1px solid rgba(46, 196, 121, 0.35);
  font-size: 12px;
  line-height: 1.45;
  color: rgba(244, 246, 251, 0.92);
}
.v2-ai-promo-live-icon { flex: 0 0 auto; font-size: 14px; line-height: 1.4; }
/* PR 4 (2026-07-26): .v2-ai-promo-cta (the "Open AI Design Studio \u2192"
   button) removed -- Owner directive: "Open AI Design Studio button ...
   should NOT be there. Customer only gets the code AFTER purchase. Remove
   the button, keep the copy." The panel now ends on .v2-ai-promo-live
   (redirect-after-purchase copy) followed directly by .v2-ai-promo-fine. */
.v2-ai-promo-fine {
  margin: 0;
  font-size: 10.5px;
  line-height: 1.5;
  color: rgba(244, 246, 251, 0.5);
}

/* ═══════════════════════════════════════════════════════════════════════
   Cabinet-flow top preview banner promoting the AI perk
   Owner: "get your free AI design, we'll have that banner all over the
   website too." Thin strip above the live preview -- Cabinet flow only.
   ═══════════════════════════════════════════════════════════════════════ */
.v2-preview {
  flex-direction: column; /* banner stacks above .v2-preview-inner when shown */
}
.v2-ai-banner {
  flex: 0 0 auto;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  background: var(--v2-ink);
  color: #F4F0E4;
  font-size: 12px;
  padding: 0 36px 0 12px;
  position: relative;
  z-index: 5;
}
.v2-ai-banner-text {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.v2-ai-banner-link {
  color: #FFC9CC;
  font-weight: 700;
  text-decoration: underline;
  cursor: pointer;
}
.v2-ai-banner-link:hover { color: #fff; }
.v2-ai-banner-close {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  width: 20px;
  height: 20px;
  border: none;
  background: none;
  color: rgba(244, 240, 228, 0.7);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.v2-ai-banner-close:hover { color: #fff; }

/* State-aware price / login CTA -- lives inline in the footer now
   (previously a large dashed-border "lock card"). Owner: "If the
   customer is logged in and we have their info then the price will be
   displayed. If the customer is not logged in... it should read Log in /
   Create a Account. That button should route then to the create your
   account page." */
.v2-price-cta-wrap {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}
.v2-price-cta {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 10px 16px;
  border-radius: 10px;
  font-weight: 800;
  font-size: 13.5px;
  text-decoration: none;
  background: var(--v2-red);
  color: #fff;
  white-space: nowrap;
}
.v2-price-cta:hover { opacity: 0.92; }

/* Animated urgency prompt — sits directly above the Log in / Create
   Account button, ONLY rendered while logged out (see applyGate() /
   buildUrgencyPrompt() in builder-v2.js). Owner: "there needs to be some
   kind of animation or something... We want to create urgency for them
   to create an account and log in so that they can get their instant
   price immediately."

   Chosen animation: a subtle shimmer sweep across the text every ~4s
   (a soft light gradient passing left-to-right) PLUS an independent
   slow twinkle/rotate on the leading sparkle icon -- reads as an active,
   exciting prompt without being distracting or feeling like an error/
   alert state. Pure CSS @keyframes, no JS timers. */
.v2-price-urgency {
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 11.5px;
  font-weight: 700;
  line-height: 1.25;
  color: var(--v2-red);
  white-space: nowrap;
  background: linear-gradient(
    90deg,
    var(--v2-red) 0%,
    var(--v2-red) 40%,
    #ff8f93 50%,
    var(--v2-red) 60%,
    var(--v2-red) 100%
  );
  background-size: 220% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: v2UrgencyShimmer 4s linear infinite;
}
.v2-price-urgency-star {
  flex: 0 0 auto;
  display: inline-block;
  font-size: 12px;
  -webkit-text-fill-color: initial; /* the emoji keeps its own color, only the text clips to the gradient */
  animation: v2UrgencyTwinkle 2.2s ease-in-out infinite;
}
@keyframes v2UrgencyShimmer {
  0%   { background-position: 120% 0; }
  100% { background-position: -120% 0; }
}
@keyframes v2UrgencyTwinkle {
  0%, 100% { transform: rotate(0deg) scale(1); opacity: 0.85; }
  50%      { transform: rotate(14deg) scale(1.18); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .v2-price-urgency {
    animation: none;
    /* Fall back to a plain solid-red label -- gradient text clip with no
       animation would otherwise freeze on a single static gradient
       frame; a flat color keeps it fully legible. */
    background: none;
    -webkit-text-fill-color: currentColor;
  }
  .v2-price-urgency-star { animation: none; }
}
.v2-price-locked .checkout-price,
.v2-price-locked .checkout-summary-line,
.v2-price-locked #checkoutItemized {
  display: none !important;
}

/* ── Product-switch confirm modal ───────────────────────────────────── */
.v2-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(10, 18, 38, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
}
.v2-modal {
  background: #fff;
  border-radius: 16px;
  padding: 24px;
  max-width: 380px;
  box-shadow: 0 20px 60px rgba(10, 18, 38, 0.35);
}
.v2-modal h3 { margin: 0 0 8px; font-size: 18px; }
.v2-modal p { margin: 0 0 20px; font-size: 14px; color: #5C6273; }
.v2-modal-actions { display: flex; gap: 10px; justify-content: flex-end; }
.v2-modal-cancel {
  background: none; border: 1px solid var(--v2-border); border-radius: 10px;
  padding: 10px 16px; font-weight: 700; cursor: pointer;
}
.v2-modal-confirm {
  background: #E81820; color: #fff; border: none; border-radius: 10px;
  padding: 10px 16px; font-weight: 800; cursor: pointer;
}

/* ── SignMonkey-style vertical font preview panel ─────────────────────
   Owner: "For the fonts, the pull-down screen is okay, but you have it at
   the bottom and you can't see the fonts unless you scroll down... it
   needs to be something that either populates up... you can't see any
   other fonts." All 7 fonts render as a large "Aa" glyph + name in one
   column, all visible at once (no scroll on desktop). Slides in from the
   LEFT of the rail, over the preview -- dismissible via the close button
   or a click on the overlay. ─────────────────────────────────────────── */
.v2-font-panel-overlay {
  position: fixed;
  inset: 0;
  background: rgba(10, 18, 38, 0.35);
  z-index: 150;
}
.v2-font-panel {
  position: fixed;
  top: var(--v2-topbar-h);
  bottom: 0;
  /* Anchored to the LEFT edge of the rail -- i.e. it slides in from the
     left of the rail, laid out OVER the right-hand portion of the
     preview, exactly matching the Owner's "populates up" / always-visible
     request without permanently stealing any preview real estate. */
  right: var(--v2-rail-w);
  width: min(420px, 92vw);
  background: #fff;
  box-shadow: -12px 0 40px rgba(10, 18, 38, 0.28);
  z-index: 151;
  display: flex;
  flex-direction: column;
  transform: translateX(0);
  animation: v2FontPanelIn 0.18s ease;
}
@keyframes v2FontPanelIn {
  from { transform: translateX(24px); opacity: 0; }
  to { transform: translateX(0); opacity: 1; }
}
.v2-font-panel-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 20px;
  border-bottom: 1px solid var(--v2-border);
}
.v2-font-panel-head h3 { margin: 0; font-size: 17px; font-weight: 800; color: var(--v2-ink); }
.v2-font-panel-close {
  background: none;
  border: none;
  font-size: 24px;
  line-height: 1;
  color: #8A8FA0;
  cursor: pointer;
  padding: 4px;
}
.v2-font-panel-close:hover { color: var(--v2-ink); }
.v2-font-panel-list {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 8px;
}
/* Each row mirrors a REAL .font-dropdown .font-option node 1:1 (same
   data-font-id, same click->setFont() relay in builder-v2.js) -- just
   rendered bigger, in a single always-visible column. */
.v2-font-row {
  display: flex;
  align-items: center;
  gap: 16px;
  width: 100%;
  padding: 14px 12px;
  border: none;
  border-radius: 12px;
  background: none;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
}
.v2-font-row:hover { background: #F7F9FC; }
.v2-font-row.is-active { background: rgba(232, 24, 32, 0.08); }
.v2-font-row-glyph {
  flex: 0 0 64px;
  height: 64px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 40px;
  line-height: 1;
  color: var(--v2-ink);
  background: #F4F5F8;
  border-radius: 10px;
}
.v2-font-row.is-active .v2-font-row-glyph { background: rgba(232, 24, 32, 0.12); color: var(--v2-red); }
.v2-font-row-name {
  flex: 1 1 auto;
  font-size: 15px;
  font-weight: 700;
  color: var(--v2-ink);
}
.v2-font-row.is-active .v2-font-row-name { color: var(--v2-red); }
.v2-font-row-check {
  flex: 0 0 auto;
  color: var(--v2-red);
  font-weight: 800;
  visibility: hidden;
}
.v2-font-row.is-active .v2-font-row-check { visibility: visible; }

@media (max-width: 900px) {
  /* Mobile: full-viewport bottom sheet instead of a left-side slide-in
     panel (there is no separate "rail edge" to slide in from once the
     rail itself is a bottom sheet). */
  .v2-font-panel {
    top: auto;
    right: 0;
    left: 0;
    bottom: 0;
    width: auto;
    height: min(80vh, 560px);
    border-radius: 20px 20px 0 0;
    animation: v2FontPanelInMobile 0.2s ease;
  }
  @keyframes v2FontPanelInMobile {
    from { transform: translateY(100%); }
    to { transform: translateY(0); }
  }
}

/* ── Mobile bottom-sheet handle ──────────────────────────────────────── */
.v2-sheet-handle {
  display: none;
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(var(--v2-bottombar-h) + 6px);
  z-index: 40;
  background: #fff;
  border: 1px solid var(--v2-border);
  border-radius: 999px;
  width: 56px;
  height: 22px;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 14px rgba(10, 18, 38, 0.2);
}
.v2-sheet-handle span {
  display: block;
  width: 28px;
  height: 4px;
  border-radius: 2px;
  background: #C7CBD6;
}

/* ═══════════════════════════════════════════════════════════════════════
   Mobile (<900px): rail becomes a bottom sheet; preview stays on top and
   remains fully visible above it, per the Owner's explicit requirement.
   ═══════════════════════════════════════════════════════════════════════ */
@media (max-width: 900px) {
  .v2-main {
    flex-direction: column;
    position: relative;
  }
  .v2-preview {
    flex: 0 0 auto;
    height: var(--v2-preview-h);
  }
  /* Floating Day/Night pill: stays top-center on mobile, just a slightly
     smaller footprint so it doesn't crowd the shorter mobile preview
     band. Same node/position strategy as desktop -- only the size
     shrinks. */
  .v2-preview-floating { top: 8px; }
  .v2-floating-toggle .toggle-pill {
    width: 128px;
    height: 34px;
    padding: 6px 12px;
    font-size: 11px;
  }
  .v2-rail {
    position: fixed;
    left: 0;
    right: 0;
    bottom: calc(var(--v2-bottombar-h) + var(--v2-timeline-h));
    /* Explicit height: 100% of the viewport minus every OTHER fixed-height
       band (header + preview + timeline + bottombar). This avoids the
       vh-guessing that let the sheet grow tall enough to cover the
       preview when the price bar/timeline heights changed. --v2-preview-h
       mirrors .v2-preview's own fixed mobile height (see above). */
    height: calc(100vh - var(--v2-topbar-h) - var(--v2-preview-h) - var(--v2-timeline-h) - var(--v2-bottombar-h));
    max-width: none;
    min-width: 0;
    border-left: none;
    border-top: 1px solid var(--v2-border);
    border-radius: 16px 16px 0 0;
    box-shadow: 0 -10px 30px rgba(10, 18, 38, 0.18);
    transition: transform 0.25s ease;
    z-index: 25;
  }
  .v2-rail.v2-rail-collapsed {
    transform: translateY(calc(100% - 64px));
  }
  .v2-sheet-handle { display: flex; z-index: 41; bottom: calc(var(--v2-bottombar-h) + var(--v2-timeline-h) + 6px); }
  .v2-topbar { height: 56px; gap: 8px; padding: 0 10px; }
  .v2-topbar .floating-menu-btn { width: 36px; height: 36px; }
  .v2-topbar .legal-logo { height: 26px; }
  .v2-topbar .legal-logo img { height: 26px; }
  .v2-classic-link { display: none; }
  :root { --v2-timeline-h: 34px; --v2-topbar-h: 56px; --v2-preview-h: 38vh; --v2-bottombar-h: 64px; }
  .v2-timeline-track { padding: 0 16px; }
  .v2-tl-dot-wrap { padding: 2px 6px; gap: 4px; }
  .v2-tl-label { font-size: 9.5px; max-width: 60px; }
  .v2-tl-line { width: 14px; }

  /* Footer: still ONE compact row (description + price/CTA + Add to Cart +
     Save Design) -- just tighter spacing/type scale for the smaller
     viewport. Same content as desktop, no filler text re-added. */
  .v2-bottombar-inner { padding: 6px 10px; }
  .v2-bottombar-inner .checkout-bar { gap: 8px; flex-wrap: nowrap; padding: 6px 0 !important; }
  .v2-bottombar-inner .checkout-summary-line { font-size: 11.5px; }
  .v2-bottombar-inner .checkout-price { font-size: 16px; }
  .v2-bottombar-inner .save-design-link { display: none; } /* saves horizontal space; still reachable from the portal after Save Design */
  .v2-bottombar-inner .cta-primary,
  .v2-bottombar-inner .cta-secondary {
    padding: 8px 12px;
    font-size: 12px;
  }
  .v2-price-cta { padding: 8px 11px; font-size: 11.5px; }
  /* Urgency prompt keeps stacking above the button on mobile too (same
     column layout as desktop) -- just a smaller type size so the two-line
     footer slot doesn't crowd the already-tight mobile row. */
  .v2-price-cta-wrap { gap: 3px; }
  .v2-price-urgency { font-size: 10px; white-space: normal; max-width: 46vw; line-height: 1.15; }
  .v2-price-urgency-star { font-size: 10.5px; }

  /* Design Essentials -- v4: lives at the TOP of the bottom-sheet rail
     (not the footer) on mobile too, per Owner's "that can be in a
     different spot" note applying everywhere, plus the explicit mobile
     testing requirement ("Design Essentials is at top of the bottom
     sheet"). Stacked vertically, same compact controls as desktop, just
     tighter. */
  .v2-essentials-top { padding: 2px 2px 10px; }
  .v2-essentials-eyebrow { font-size: 10px; margin-bottom: 6px; }
  .v2-essentials-body { gap: 8px; }
  .v2-essentials-body .cl-mode-btn { padding: 7px 6px; font-size: 11px; }
  .v2-essentials-body .cl-valet-cta { padding: 9px 10px; }
  .v2-essentials-body .cl-valet-cta-headline { font-size: 11.5px; }
  .v2-essentials-body .cl-valet-cta-sub      { font-size: 10.5px; }
  .v2-essentials-body .text-input { padding: 8px 9px; font-size: 12.5px; }
  .v2-font-trigger { padding: 8px 9px; }
  .v2-font-trigger-name { font-size: 12.5px; }
  .v2-rail-divider { margin-bottom: 12px; }
  /* Design notes (optional, always-available textarea) is dropped from
     the MOBILE rail specifically -- not deleted, not disabled, just not
     rendered in this compact context -- to keep the bottom-sheet's fixed
     header budget small. It remains fully available on desktop and is
     never required to proceed (optional per spec), so nothing the
     customer needs is lost. */
  .v2-essentials-body .cab-design-notes { display: none; }

  /* Cabinet 2-button Design Essentials -- stacks full-width on the
     narrow bottom sheet instead of a cramped side-by-side row (spec:
     "verify 2-button layout stacks or fits at narrow width"). */
  .v2-essentials-body .cab-design-cards.v2-cab-design-cards-2up {
    flex-direction: column;
  }
  .v2-essentials-body .cab-design-cards.v2-cab-design-cards-2up .cab-design-card {
    flex-direction: row;
    justify-content: flex-start;
    min-height: 44px;
  }

  /* AI Design Studio promo panel -- same content, just scrolls naturally
     inside the bottom sheet (spec: "promo panel scrolls properly in the
     bottom sheet"). No fixed height -- .v2-rail-scroll already handles
     overflow-y for the whole sheet. */
  .v2-ai-promo { padding: 14px 12px 12px; gap: 10px; }
  .v2-ai-promo-form { flex-direction: column; }
  .v2-ai-promo-submit { width: 100%; }

  /* Cabinet-flow AI perk banner -- same thin strip, tighter type so the
     "Learn more" link + close (x) never crowd at 375px. */
  .v2-ai-banner { font-size: 11px; padding: 0 30px 0 10px; gap: 6px; }
}


@media (max-width: 480px) {
  .v2-question-title { font-size: 21px; }
  .v2-rail-scroll { padding: 20px 16px 28px; }
}

/* ═══════════════════════════════════════════════════════════════════════
   "Use my building photo" — SignMonkey-style upload + measure feature.
   PR: feat/beacon-v2-building-photo-measurement

   Pure layered-overlay chrome on top of the existing preview. Does not
   redefine any stock-storefront/#canvasWrap/#wallCanvas/#signGroup rules
   above this block — this only adds NEW selectors (.v2-bphoto-*) that sit
   visually above the untouched stock preview and hide it (via the
   `.v2-bphoto-active` class on #v2Preview) while a customer's own photo is
   in use.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Entry point button — floats top-right, offset below the existing
   day/night floating toggle so the two never overlap. ────────────────── */
.v2-bphoto-launch {
  position: absolute;
  top: 16px;
  left: 16px;
  z-index: 11;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgba(255, 255, 255, 0.94);
  color: var(--v2-ink);
  border: none;
  border-radius: 999px;
  padding: 10px 16px 10px 14px;
  font-size: 13px;
  font-weight: 700;
  box-shadow: 0 6px 20px rgba(10, 18, 38, 0.18);
  cursor: pointer;
}
.v2-bphoto-launch:hover { background: #fff; }
.v2-bphoto-launch span { font-size: 15px; }

/* When building-photo mode is active, hide the stock storefront SVG
   entirely (untouched underneath) and show our photo stage instead.

   PR 3 (live-cabinet-on-customer-photo) exception: for CABINETS this is
   the WRONG behavior — hiding/disabling the live #canvasWrap is exactly
   what turned the cabinet into a flat, non-interactive clone (Owner:
   "you can't click on it, you can't do anything with it"). Cabinet mode
   swaps the BACKDROP LAYER INSIDE #canvasWrap instead (see
   #cabinetCustomerBackdrop in builder.html / styles.css) and keeps
   #canvasWrap fully visible + interactive. `.v2-bphoto-active-cabinet`
   (applied instead of `.v2-bphoto-active` when state.product==='cabinet')
   intentionally does NOT hide or disable pointer-events on .canvas-wrap. */
.v2-preview.v2-bphoto-active .v2-preview-inner .canvas-wrap {
  visibility: hidden;
  pointer-events: none;
}
.v2-preview.v2-bphoto-active .v2-bphoto-launch,
.v2-preview.v2-bphoto-active-cabinet .v2-bphoto-launch {
  background: rgba(10, 18, 38, 0.75);
  color: #fff;
}
/* Cabinet photo mode: the legacy clone-based #v2BPhotoStage (channel-letter)
   overlay is never shown — the live canvas itself now displays the
   customer's photo as its backdrop, with the real #cabinetProductGroup
   rendering on top.

   Bug fix (PR 2/3): #v2CabPhotoStage ALSO carries the base .v2-bphoto-stage
   class (see builder-v2.html, class="v2-bphoto-stage v2-cabphoto-stage"), so
   the original selector below matched BOTH stages and hid the cabinet's own
   resize-handle/corner-label overlay any time cabinet photo mode was active.
   :not(.v2-cabphoto-stage) scopes this rule to the channel-letter stage
   only, exactly as intended. */
.v2-preview.v2-bphoto-active-cabinet .v2-bphoto-stage:not(.v2-cabphoto-stage) {
  display: none !important;
}
/* Defense-in-depth (PR 2/3): even if JS ever fails to remove
   'v2-bphoto-active' when switching TO cabinet (the root-cause bug this PR
   fixes), CSS alone must never let the channel-letter photo stage + its
   resize handle render on top of -- or alongside -- an active cabinet.
   :has() is supported in all current evergreen browsers (Chrome/Edge 105+,
   Safari 15.4+, Firefox 121+); this is a backstop, not the primary fix. */
#v2Preview:has(.canvas-wrap[data-product="cabinet"]) .v2-bphoto-stage:not(.v2-cabphoto-stage) {
  display: none !important;
}

/* PR 3 (live-cabinet-on-customer-photo): cabinet-mode overlay. Unlike
   .v2-bphoto-stage (channel letters), this overlay sits ON TOP of the
   still-visible, still-interactive live canvas -- it must never intercept
   clicks meant for the cabinet's own Upload Artwork button / face, so the
   stage itself is pointer-events:none and only its OWN direct chrome
   (resize handle, corner label buttons) opts back into pointer-events. */
.v2-cabphoto-stage {
  background: transparent !important;
  overflow: visible !important;
  pointer-events: none !important;
}
.v2-cabphoto-stage .v2-bphoto-resize-handle,
.v2-cabphoto-stage .v2-bphoto-corner,
.v2-cabphoto-stage .v2-bphoto-corner * {
  pointer-events: auto !important;
}
#v2CabPhotoResizeHandle {
  right: auto;
  bottom: auto;
  z-index: 12;
}

/* == Building-photo stage: the customer's photo + the scaled sign clone,
   layered directly over (visibility-hidden) stock canvas. == */
.v2-bphoto-stage {
  position: absolute;
  inset: 0;
  z-index: 5;
  overflow: hidden;
  background: #000;
}
/* Zoom wrapper: contains BOTH the photo and the sign so scaling this ONE
   node (via CSS transform: scale) zooms them together, keeping the sign
   proportional to the photo at every zoom level. transform-origin is
   updated in JS to the sign's current center before each scale change so
   zooming in feels centered on the sign, not the corner of the photo.
   Owner: "zooming in on the portion of the sign... taking the photo and
   zooming in." The photo itself never pans -- only this wrapper's scale
   changes (SignMonkey pattern: fixed photo, draggable/resizable sign
   only, avoids gesture conflicts). */
.v2-bphoto-zoomwrap {
  position: absolute;
  inset: 0;
  transform-origin: 50% 50%;
  transition: transform 0.12s ease-out;
  will-change: transform;
}
.v2-bphoto-stage-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: #11151F;
  user-select: none;
  -webkit-user-drag: none;
}
.v2-bphoto-sign {
  position: absolute;
  cursor: grab;
  touch-action: none;
  filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.35));
}
.v2-bphoto-sign.is-dragging { cursor: grabbing; }
.v2-bphoto-sign svg { display: block; width: 100%; height: 100%; overflow: visible; pointer-events: none; }
.v2-bphoto-sign.is-snapped::after {
  content: '';
  position: absolute;
  top: -2000px;
  bottom: -2000px;
  left: 50%;
  width: 1px;
  background: rgba(232, 24, 32, 0.55);
  pointer-events: none;
}

/* Corner resize handle: small circular grip, bottom-right of the sign's
   bounding box. Dragging it resizes the sign (snapped to the nearest
   inch), which builder-v2.js mirrors back onto #heightSlider so the
   Sizing step's own state stays in sync.
   Visual matches the demo-canvas #lobbyResizeHandle exactly (Owner request):
   a red circle (fill #E8521B, cream stroke) with a diagonal double-arrow
   glyph, rendered as inline SVG since this layer is HTML/CSS, not the main
   SVG canvas. The outer box is a 22px hit target (same as the demo's
   #lobbyResizeHit r=22 circle); the visible ring inside is 18px (r=9),
   so the box is centered -11px/-11px on the sign's corner (was -9px/-9px
   for the old plain 18px dot -- widened to keep the new 22px hit target
   centered on the same corner point). SVG markup is injected by
   builder-v2.js at handle-creation time (see ensurePhotoResizeHandleGlyph)
   so both #v2BPhotoResizeHandle and #v2CabPhotoResizeHandle share one
   source of truth. */
.v2-bphoto-resize-handle {
  position: absolute;
  right: -11px;
  bottom: -11px;
  width: 22px;
  height: 22px;
  cursor: nwse-resize;
  touch-action: none;
  pointer-events: auto;
  filter: drop-shadow(0 1px 2px rgba(10, 18, 38, 0.45));
  transition: transform var(--dur, 180ms) var(--ease, ease);
}
.v2-bphoto-resize-handle svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}
.v2-bphoto-resize-handle .v2-bphoto-handle-ring {
  fill: var(--signal-orange, #E8521B);
  stroke: var(--cream, #FAF7F2);
  stroke-width: 2;
}
.v2-bphoto-resize-handle .v2-bphoto-handle-glyph {
  fill: none;
  stroke: var(--cream, #FAF7F2);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.v2-bphoto-resize-handle:hover { transform: scale(1.15); }

/* Zoom slider -- bottom-right of the preview, only visible in
   building-photo mode. 100% (default) -> 400%. Owner: "Let's figure out
   what we have to do as far as taking the customer's photos and zooming
   in on it after we measure it to confirm sizing." */
.v2-bphoto-zoom {
  position: absolute;
  right: 16px;
  bottom: 16px;
  z-index: 9;
  display: flex;
  align-items: center;
  gap: 8px;
  background: rgba(10, 18, 38, 0.82);
  color: #fff;
  border-radius: 999px;
  padding: 8px 14px;
}
.v2-bphoto-zoom-btn {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.4);
  background: none;
  color: #fff;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.v2-bphoto-zoom-btn:hover { background: rgba(255, 255, 255, 0.15); }
#v2BPhotoZoomSlider {
  width: 110px;
  accent-color: var(--v2-red);
  cursor: pointer;
}
.v2-bphoto-zoom-pct {
  flex: 0 0 auto;
  font-size: 12px;
  font-weight: 700;
  min-width: 38px;
  text-align: right;
}
@media (max-width: 640px) {
  .v2-bphoto-zoom { right: 8px; bottom: 8px; padding: 6px 10px; gap: 6px; }
  #v2BPhotoZoomSlider { width: 72px; }
}

.v2-bphoto-corner {
  position: absolute;
  left: 12px;
  bottom: 12px;
  z-index: 8;
  background: rgba(10, 18, 38, 0.82);
  color: #fff;
  border-radius: 12px;
  padding: 10px 12px;
  font-size: 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-width: min(320px, 84vw);
}
.v2-bphoto-corner span { font-weight: 600; }
.v2-bphoto-corner button {
  background: none;
  border: none;
  color: #fff;
  font-size: 12px;
  text-align: left;
  padding: 0;
  cursor: pointer;
  opacity: 0.9;
}
.v2-bphoto-corner button:hover { opacity: 1; text-decoration: underline; }
.v2-bphoto-link-btn {
  color: #FF8A4D;
  font-weight: 700;
  text-decoration: none;
  font-size: 12px;
  cursor: pointer;
  background: none;
  border: none;
}
.v2-bphoto-link-btn:hover { text-decoration: underline; }

.v2-bphoto-warning {
  position: absolute;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 9;
  background: #FFF4E5;
  border: 1px solid #F0A94E;
  color: #7A4900;
  border-radius: 10px;
  padding: 10px 14px;
  font-size: 12.5px;
  font-weight: 600;
  max-width: min(460px, 90vw);
  text-align: center;
  box-shadow: 0 6px 18px rgba(10, 18, 38, 0.2);
}

/* ── Resume-saved-photo prompt ──────────────────────────────────────── */
.v2-bphoto-resume {
  position: absolute;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 12;
  background: #fff;
  border-radius: 12px;
  padding: 14px 16px;
  box-shadow: 0 10px 30px rgba(10, 18, 38, 0.25);
  text-align: center;
  max-width: min(360px, 90vw);
}
.v2-bphoto-resume p { margin: 0 0 10px; font-size: 13px; font-weight: 700; color: var(--v2-ink); }
.v2-bphoto-resume-actions { display: flex; gap: 8px; justify-content: center; }
.v2-bphoto-resume-actions button {
  border-radius: 8px;
  border: 1px solid var(--v2-border);
  padding: 8px 12px;
  font-size: 12.5px;
  font-weight: 700;
  cursor: pointer;
  background: #fff;
}
#v2BPhotoResumeYes { background: var(--v2-red); color: #fff; border-color: var(--v2-red); }

/* ── The 4-step modal ───────────────────────────────────────────────── */
.v2-bphoto-modal-overlay { z-index: 200; align-items: flex-start; padding: 40px 16px; overflow-y: auto; }
.v2-bphoto-modal {
  position: relative;
  max-width: 640px;
  width: 100%;
  max-height: calc(100vh - 80px);
  overflow-y: auto;
  padding: 28px;
}
.v2-bphoto-modal-close {
  position: absolute;
  top: 12px;
  right: 14px;
  background: none;
  border: none;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  color: #8A8FA0;
}
.v2-bphoto-modal-close:hover { color: var(--v2-ink); }

.v2-bphoto-steps { display: flex; gap: 6px; justify-content: center; margin-bottom: 18px; }
.v2-bphoto-step-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--v2-border);
}
.v2-bphoto-step-dot.is-active { background: var(--v2-red); width: 22px; border-radius: 5px; }
.v2-bphoto-step-dot.is-done { background: #9FA6B8; }

.v2-bphoto-modal h3 { margin: 0 0 6px; font-size: 20px; font-weight: 800; color: var(--v2-ink); }
.v2-bphoto-panel-hint { margin: 0 0 16px; font-size: 13.5px; color: #5C6273; line-height: 1.5; }

.v2-bphoto-dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: 2px dashed var(--v2-border);
  border-radius: 14px;
  padding: 40px 20px;
  cursor: pointer;
  text-align: center;
  background: #FAFAFC;
}
.v2-bphoto-dropzone:hover, .v2-bphoto-dropzone.is-dragover {
  border-color: var(--v2-red);
  background: #FFF5F5;
}
.v2-bphoto-dropzone-icon { font-size: 32px; }
.v2-bphoto-dropzone-text { font-weight: 700; color: var(--v2-ink); font-size: 14px; }
.v2-bphoto-dropzone-sub { font-size: 12px; color: #8A8FA0; }

.v2-bphoto-error {
  background: #FDEBEC;
  border: 1px solid #F3B7BB;
  color: #B3222B;
  border-radius: 8px;
  padding: 8px 12px;
  font-size: 12.5px;
  font-weight: 600;
  margin-top: 10px;
}

.v2-bphoto-upload-preview { margin-top: 16px; text-align: center; }
.v2-bphoto-upload-preview img {
  max-width: 100%;
  max-height: 320px;
  border-radius: 10px;
  box-shadow: 0 4px 16px rgba(10, 18, 38, 0.15);
}

.v2-bphoto-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 20px;
  gap: 10px;
}
.v2-bphoto-continue {
  background: var(--v2-red);
  color: #fff;
  border: none;
  border-radius: 10px;
  padding: 11px 20px;
  font-weight: 800;
  font-size: 14px;
  cursor: pointer;
}
.v2-bphoto-continue:disabled { background: #D8DAE2; color: #9095A3; cursor: not-allowed; }
.v2-bphoto-back {
  background: none;
  border: 1px solid var(--v2-border);
  border-radius: 10px;
  padding: 11px 16px;
  font-weight: 700;
  font-size: 13.5px;
  cursor: pointer;
  color: var(--v2-ink);
}

/* Step 2: draw canvas */
.v2-bphoto-draw-wrap {
  position: relative;
  display: inline-block;
  max-width: 100%;
  border-radius: 10px;
  overflow: hidden;
  line-height: 0;
  background: #11151F;
}
.v2-bphoto-draw-wrap img { display: block; max-width: 100%; max-height: 60vh; width: auto; height: auto; user-select: none; -webkit-user-drag: none; }
.v2-bphoto-draw-wrap canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  cursor: crosshair;
  touch-action: none;
}
.v2-bphoto-draw-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 10px;
  min-height: 20px;
}
.v2-bphoto-draw-row .v2-bphoto-error { margin-top: 0; flex: 1 1 auto; }

/* Step 3: reference height segmented control */
.v2-bphoto-height-options {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-bottom: 14px;
}
.v2-bphoto-height-opt {
  border: 1.5px solid var(--v2-border);
  background: #fff;
  border-radius: 10px;
  padding: 10px 8px;
  font-weight: 800;
  font-size: 16px;
  color: var(--v2-ink);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}
.v2-bphoto-height-opt span { font-weight: 500; font-size: 10.5px; color: #737A8C; text-align: center; }
.v2-bphoto-height-opt.is-active { border-color: var(--v2-red); background: #FFF5F5; color: var(--v2-red); }
.v2-bphoto-height-opt.is-active span { color: var(--v2-red); }
.v2-bphoto-height-custom { grid-column: span 1; justify-content: center; }

.v2-bphoto-custom-height {
  display: flex;
  gap: 12px;
  margin-bottom: 14px;
}
.v2-bphoto-custom-height label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; font-weight: 700; color: #5C6273; }
.v2-bphoto-custom-height input {
  border: 1px solid var(--v2-border);
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 14px;
  width: 90px;
}

.v2-bphoto-ref-preview {
  background: #F4F5F8;
  border-radius: 8px;
  padding: 10px 12px;
  font-size: 13px;
  font-weight: 700;
  color: var(--v2-ink);
  margin: 0;
}

/* ── Mobile: modal goes full-viewport ───────────────────────────────── */
@media (max-width: 640px) {
  .v2-bphoto-modal-overlay { padding: 0; align-items: stretch; }
  .v2-bphoto-modal {
    max-width: none;
    width: 100%;
    height: 100%;
    max-height: none;
    border-radius: 0;
  }
  .v2-bphoto-height-options { grid-template-columns: repeat(2, 1fr); }
  .v2-bphoto-launch {
    top: 8px;
    left: 8px;
    padding: 8px 12px 8px 10px;
    font-size: 12px;
  }
  .v2-bphoto-corner { left: 8px; bottom: 8px; font-size: 11px; }
}

/* On very narrow phones the "Use my building photo" pill (top-left) and
   the centered Day/Night toggle (top-center) can visually collide since
   both now live at the top of the preview. Collapse the photo button to
   icon-only here so the two floating controls never overlap -- purely a
   label/width tweak, the button's id/click handler/behavior are
   untouched. */
@media (max-width: 400px) {
  .v2-bphoto-launch { padding: 8px; }
  .v2-bphoto-launch-label { display: none; }
}
