/* DeeVee widget — Drive Venturous brand: cream, black, burnt copper. */
:root {
  --dv-copper: #A65A29;
  --dv-copper-deep: #964a20;
  --dv-copper-light: #fdf6f2;
  --dv-cream: #f7f6f4;
  --dv-black: #111111;
  --dv-grey: #6b6b6b;
  --dv-success: #61CE70;
  --dv-radius: 6px;
  --dv-panel-shadow: 0 16px 50px rgba(0,0,0,0.18), 0 2px 6px rgba(0,0,0,0.06);
  --dv-glass-bg: rgba(247, 246, 244, 0.92);
  --dv-glass-blur: blur(14px);
  --dv-space-xs: 8px;
  --dv-space-sm: 10px;
  --dv-space-md: 12px;
  --dv-space-lg: 14px;
  --dv-space-xl: 16px;
  --dv-font-head: 'Russo One', 'Helvetica Neue', Arial, sans-serif;
  --dv-font-body: 'Helvetica Neue', Helvetica, Arial, sans-serif;

  /* Premium card language (product cards, compare table) - additive tokens,
     scoped to this pass, so nothing that already reads --dv-radius/
     --dv-panel-shadow elsewhere in this file shifts underneath it. Two-layer
     shadows (a tight contact shadow + a soft ambient one) read as physical
     depth the way a single flat box-shadow never does - same technique
     --dv-panel-shadow already uses for the header card, just tuned lighter
     for a card sitting in the thread rather than a whole panel. */
  --dv-radius-card: 14px;
  --dv-shadow-card: 0 1px 2px rgba(17,17,17,0.05), 0 1px 1px rgba(17,17,17,0.04), 0 10px 24px -8px rgba(17,17,17,0.16);
  --dv-shadow-card-hover: 0 2px 4px rgba(17,17,17,0.06), 0 20px 40px -12px rgba(17,17,17,0.24), 0 0 0 1px rgba(166,90,41,0.14);
  --dv-shadow-chip: 0 1px 2px rgba(17,17,17,0.06), 0 6px 14px -4px rgba(17,17,17,0.14);
  --dv-copper-gradient: linear-gradient(135deg, var(--dv-copper) 0%, var(--dv-copper-deep) 100%);
  --dv-ease-premium: cubic-bezier(0.16, 1, 0.3, 1);

  /* Extended palette - still built from the three brand colors (cream,
     black, burnt copper) above, not new hues. A flat #fff card on a #f7f6f4
     page and pure rgba(0,0,0,x) borders/greys read as generic-template
     default rather than a considered palette - warming every neutral
     toward copper's own hue (even by a few percent) is what makes a
     "black + copper + cream" brand feel like one deliberate palette
     instead of Bootstrap-grey with an accent color bolted on. */
  --dv-surface: #fffdfb;                 /* card/bubble ground - warm, not stark white */
  --dv-black-deep: #0a0908;              /* richer than --dv-black for gradient depth on dark elements */
  --dv-copper-mute: #c99872;             /* desaturated copper - "not yet confirmed" semantic, vs full copper's "confirmed" */
  --dv-copper-mute-bg: #f7efe8;
  --dv-border-soft: rgba(58, 38, 22, 0.08);   /* warm-black at low alpha, not neutral rgba(0,0,0,x) */
  --dv-border-copper: rgba(166, 90, 41, 0.16);
  --dv-black-gradient: linear-gradient(135deg, var(--dv-black) 0%, var(--dv-black-deep) 100%);
}

.dv-root { position: fixed; z-index: 999999; font-family: var(--dv-font-body); }

/* Full-page dim + blur backdrop behind the overlay results panel, matching
   the approved reference exactly - was entirely missing; .dv-panel had its
   own glass effect but nothing dimmed/blurred the rest of the page around
   it. Scoped to .dv-overlay only (not the floating .dv-widget launcher
   surface, which shouldn't darken the whole page) and to .dv-open only, so
   the closed state stays the same zero-footprint non-blocking box it
   always was. .dv-panel is a fixed-position child painted on top, so this
   needs no z-index changes to sit correctly beneath it. */
.dv-overlay.dv-open {
  inset: 0;
  background: rgba(0,0,0,0.55);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  transition: background 300ms ease, backdrop-filter 300ms ease;
}

/* Purely a specificity/reset scope for widget elements the host page's own
   theme might otherwise style (see the .dv-scope .dv-ymm-go rule below) -
   deliberately carries no position/layout so it's safe to use on elements
   that live in normal page flow, unlike .dv-root. */
.dv-scope { font-family: var(--dv-font-body); box-sizing: border-box; }
.dv-scope *, .dv-scope *::before, .dv-scope *::after { box-sizing: border-box; }

/* ── Launcher ─────────────────────────────────────────────────────────── */
@keyframes dv-launcher-in { from { opacity: 0; transform: scale(0.7); } to { opacity: 1; transform: scale(1); } }
@keyframes dv-launcher-glow {
  0%, 100% { box-shadow: 0 6px 24px rgba(0,0,0,0.28), 0 0 0 0 rgba(166,90,41,0.35); }
  50% { box-shadow: 0 6px 28px rgba(0,0,0,0.28), 0 0 0 8px rgba(166,90,41,0); }
}
@keyframes dv-ember-drift {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(3px, -3px) scale(1.08); }
}
.dv-root .dv-launcher {
  position: fixed; right: 24px; bottom: 24px;
  width: 64px; height: 64px; display: flex; align-items: center; justify-content: center;
  background: var(--dv-black); color: var(--dv-copper);
  border: 3px solid var(--dv-copper); border-radius: 50%;
  padding: 0; cursor: pointer;
  font-family: var(--dv-font-head); font-size: 15px; letter-spacing: 0.02em;
  box-shadow: 0 6px 24px rgba(0,0,0,0.28);
  transition: transform 160ms ease, box-shadow 160ms ease;
  opacity: 0; transform: scale(0.7);
  animation: dv-launcher-in 420ms cubic-bezier(0.34,1.56,0.64,1) 300ms forwards,
             dv-launcher-glow 2.6s ease-in-out 1.4s infinite;
}
/* Drifting ember-glow layer behind the bubble. .dv-launcher is already
   position:fixed (and therefore already its own stacking context), so this
   just needs to be absolutely positioned against that existing containing
   block - no position:relative added to the parent, which would otherwise
   overwrite position:fixed and knock the launcher out of its viewport-corner
   placement. */
.dv-root .dv-launcher::before {
  content: '';
  position: absolute; inset: -8px; border-radius: 50%;
  background: radial-gradient(circle at 30% 30%, rgba(166,90,41,0.35), transparent 70%);
  filter: blur(6px);
  animation: dv-ember-drift 4s ease-in-out infinite;
  z-index: -1;
}
/* Persistent online-status dot, bottom-right of the bubble. */
.dv-root .dv-launcher::after {
  content: '';
  position: absolute; bottom: 2px; right: 2px;
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--dv-success); border: 2px solid #000;
  box-shadow: 0 0 6px rgba(97,206,112,0.6);
}
/* !important on both: .dv-launcher already carries a finished
   dv-launcher-in entrance animation (forwards fill-mode, so it permanently
   owns `transform` at the Animations cascade layer) and an infinite
   dv-launcher-glow pulse (owns `box-shadow` the same way) - confirmed via
   computed-style testing that a plain (non-important) :hover rule for either
   property is silently never painted, since normal author rules sit below
   the Animations origin. !important is the standard, narrowly-scoped way to
   let :hover win without touching/restarting either animation. */
.dv-root .dv-launcher:hover { transform: scale(1.08) !important; box-shadow: 0 12px 32px rgba(166,90,41,0.35) !important; }
.dv-open .dv-launcher {
  opacity: 0; transform: scale(0.7); pointer-events: none; animation: none;
  transition: opacity 200ms ease, transform 200ms ease;
}

/* ── Panel: floating widget surface ───────────────────────────────────── */
.dv-widget .dv-panel {
  position: fixed; right: 24px; bottom: 24px;
  width: 391px; height: min(644px, calc(100vh - 48px));
  display: flex; flex-direction: column;
  background: #fff;
  border: 1px solid rgba(0,0,0,0.12); border-radius: 10px;
  box-shadow: 0 24px 60px rgba(0,0,0,0.28), 0 6px 16px rgba(0,0,0,0.08), 0 0 0 1px rgba(166,90,41,0.04);
  backdrop-filter: none; -webkit-backdrop-filter: none;
  overflow: hidden;
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: scale(0.94) translateY(14px); transform-origin: bottom right;
  filter: blur(8px);
  transition: opacity 360ms cubic-bezier(0.16,1,0.3,1),
              transform 520ms cubic-bezier(0.16,1,0.3,1),
              filter 360ms cubic-bezier(0.16,1,0.3,1),
              visibility 0s linear 360ms;
}
.dv-widget.dv-open .dv-panel {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: scale(1) translateY(0);
  filter: blur(0);
  transition: opacity 360ms cubic-bezier(0.16,1,0.3,1),
              transform 520ms cubic-bezier(0.16,1,0.3,1),
              filter 360ms cubic-bezier(0.16,1,0.3,1),
              visibility 0s linear 0s;
}

/* ── Panel: overlay surface (/all-products/) ──────────────────────────── */
/* Centered via fixed+inset:0+margin:auto (not the previous bottom-anchored
   inset:auto 0 0 0), solid off-white #f7f6f4 background (no glass blur -
   both this and the floating widget's panel above are opaque chrome now,
   distinguished by color, not by one being glass), a heavier shadow plus
   a 1px copper hairline, and a blur+scale+translateY "materialize"
   entrance - all matched exactly against deliverables/prototype/style.css's
   .dv-overlay rule (not approximated). */
.dv-overlay .dv-panel {
  position: fixed; inset: 0; margin: auto;
  width: min(1080px, 96vw); height: 85vh; max-height: 780px;
  display: flex; flex-direction: column;
  background: #f7f6f4;
  border: none;
  border-radius: 10px; overflow: hidden;
  box-shadow: 0 50px 120px rgba(0,0,0,0.6), 0 0 0 1px rgba(166,90,41,0.06);
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translateY(24px) scale(0.94);
  filter: blur(10px);
  transition: opacity 360ms cubic-bezier(0.16,1,0.3,1),
              transform 520ms cubic-bezier(0.16,1,0.3,1),
              filter 360ms cubic-bezier(0.16,1,0.3,1),
              visibility 0s linear 360ms;
}
.dv-overlay.dv-open .dv-panel {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: translateY(0) scale(1);
  filter: blur(0);
  transition: opacity 360ms cubic-bezier(0.16,1,0.3,1),
              transform 520ms cubic-bezier(0.16,1,0.3,1),
              filter 360ms cubic-bezier(0.16,1,0.3,1),
              visibility 0s linear 0s;
}

/* ── Dealer rail (overlay only) ───────────────────────────────────────── */
/* .dv-overlay-body is the flex row beneath .dv-head: the chat column
   (.dv-overlay-main - thread + quick-dock + input row, unchanged internally)
   on the left, an empty .dv-rail on the right. Mounted only on the overlay
   surface (see mount() in widget.js) - the compact widget's .dv-panel keeps
   its original flat single-column child list. The rail itself renders
   nothing yet (a later task populates it with dealer results); :not(:empty)
   is what keeps it collapsed until then, so shipping this now causes no
   visible change to the overlay. */
.dv-overlay-body { display: flex; flex: 1; min-height: 0; }
.dv-overlay-main { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.dv-rail {
  width: 280px; flex: 0 0 280px; border-left: 1px solid rgba(0,0,0,0.08);
  padding: var(--dv-space-lg); overflow-y: auto; display: none;
}
.dv-rail:not(:empty) { display: block; }
/* Breakpoint matches deliverables/prototype/style.css's .ov-rail hide rule
   (max-width: 900px) exactly, not approximated. */
@media (max-width: 900px) {
  .dv-rail { display: none !important; }
}

/* ── Header ───────────────────────────────────────────────────────────── */
.dv-head {
  background: var(--dv-black); color: #fff;
  position: relative; flex: 0 0 auto;
  display: flex; align-items: center;
  transition: background 300ms ease;
}
/* Compact bubble: small, tight header. Right padding is intentionally
   larger than the left/top/bottom values - .dv-close is positioned
   absolute (top:10 right:12, not part of this flex row), so .dv-head-info
   needs real clearance on that side or a long admin-set bot name / long
   multi-part status string (state.statusParts.join(' · ')) wraps its
   first line right under the close button at any width above the 480px
   mobile breakpoint (that's the only place title/sub get nowrap+ellipsis
   truncation). Sized empirically against the actual measured close-button
   footprint at this surface's 15px close font (button left edge sits
   ~35px in from the header's right edge at that size/offset). */
.dv-widget .dv-head { padding: 14px 40px 14px 16px; gap: 10px; }
.dv-widget .dv-head-avatar { width: 36px; height: 36px; font-size: 12px; }
.dv-widget .dv-head-title { font-size: 13px; }
.dv-widget .dv-head-sub { font-size: 10px; }
.dv-widget .dv-close { font-size: 15px; }
/* Full-page overlay: bigger, roomier header. Right padding is sized to
   the actual measured close-button footprint at this surface's 22px close
   font (button left edge sits ~41px in from the header's right edge at
   that size/offset) - the original single shared spec's 34px was 7px
   short of that and would let a wrapped line's last word land under the
   button; verified against real rendered rects, not estimated. */
.dv-overlay .dv-head { padding: 18px 44px 18px 26px; gap: 14px; }
.dv-overlay .dv-head-avatar { width: 44px; height: 44px; font-size: 15px; }
.dv-overlay .dv-head-title { font-size: 16px; }
.dv-overlay .dv-head-sub { font-size: 11px; }
.dv-head-avatar {
  width: 32px; height: 32px; border-radius: 50%; flex: 0 0 auto;
  background: var(--dv-copper); color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--dv-font-head); font-size: 12px;
  border: 2px solid rgba(255,255,255,0.15);
  transition: background 300ms ease, color 300ms ease;
}
.dv-head-info { min-width: 0; flex: 1 1 auto; }
.dv-head-title { font-family: var(--dv-font-head); font-size: 16px; letter-spacing: 0.08em; }
.dv-head-sub { font-size: 11px; color: rgba(255,255,255,0.7); margin-top: 2px; }
.dv-status-dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: var(--dv-success); margin-right: 5px; vertical-align: middle;
}
/* Requested-but-not-claimed handoff: the header stays black (rule below)
   but the status dot pulses, so the "Finding you a team member" wait reads
   as active searching rather than a stall. dv-handoff-active stays on the
   header after the claim too, so the pulse is scoped off the claimed class
   instead of keyed to active alone. Ring color is --dv-success (#61CE70 =
   rgb(97,206,112)) so it matches the dot it expands from. */
.dv-handoff-active:not(.dv-handoff-claimed) .dv-status-dot {
  animation: dv-status-pulse 1.4s ease-in-out infinite;
}
@keyframes dv-status-pulse {
  0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(97, 206, 112, 0.45); }
  50% { opacity: 0.6; box-shadow: 0 0 0 5px rgba(97, 206, 112, 0); }
}
/* Header stays black while a handoff is only requested (nobody's there
   yet) and flips black-to-copper - avatar inverts too - only once a real
   staff member has actually claimed the conversation (widget.js's
   markClaimed(), applied on .dv-handoff-claimed). Per direct client
   instruction: a visitor shouldn't get a "you're talking to a person now"
   signal before that's actually true. */
.dv-head.dv-handoff-claimed { background: var(--dv-copper); }
.dv-head.dv-handoff-claimed .dv-head-avatar { background: var(--dv-black); color: var(--dv-copper); }
.dv-head.dv-handoff-claimed .dv-vehicle-chip { background: rgba(0,0,0,0.18); }
.dv-close {
  position: absolute; top: 10px; right: 12px;
  background: none; border: none; color: rgba(255,255,255,0.7);
  font-size: 22px; line-height: 1; cursor: pointer; padding: 4px 8px;
}
.dv-close:hover { color: #fff; }
/* Compact-only expand-to-fullscreen trigger (see mount() in widget.js).
   A normal flex child, unlike .dv-close's absolute positioning - it sits
   in-flow right before the header's right padding, which is already sized
   to clear the close button, so the two never overlap. */

/* ── Quick-reply dock ─────────────────────────────────────────────────── */
/* Persistent container, a sibling of .dv-thread and the input row rather
   than a child of the scrolling thread - lives above the input on both
   surfaces (was a one-shot, compact-only, in-thread render before).
   setQuickReplies() in widget.js clears and repopulates its contents;
   :empty hides the row entirely so it never reserves blank space between
   turns. Wrapping horizontal row of pills, matching the prototype's chip
   strip (deliverables/prototype: setQuick()/.chip). */
.dv-quick-dock {
  display: flex; flex-wrap: wrap; gap: 8px;
  padding: 0 var(--dv-space-xl) var(--dv-space-md);
}
.dv-quick-dock:empty { display: none; }
/* .dv-root prefix on every interactive-element rule below is a deliberate
   specificity boost, not decoration: WordPress themes commonly reset
   `[type="button"], [type="submit"], button` with their own brand color
   (Hello Elementor's default is a bright pink) at (0,1,0) specificity via
   the attribute selector - the exact same specificity as a bare class
   selector, since every button here sets `.type` in JS. A flat class alone
   ties with that theme rule and can lose on source order; scoping under
   `.dv-root` (0,2,0) makes it win unconditionally, on any site. */
.dv-root .dv-quick-reply {
  display: inline-flex; align-items: center; justify-content: center; min-height: 44px;
  background: #fff; color: var(--dv-black); border: 1.5px solid var(--dv-black); border-radius: 7px;
  padding: 10px 18px; font-family: var(--dv-font-head); font-size: 11.5px;
  text-transform: uppercase; letter-spacing: 0.04em; text-align: center;
  box-shadow: 0 1px 2px rgba(17,17,17,0.04);
  cursor: pointer; transition: background 200ms var(--dv-ease-premium), color 200ms var(--dv-ease-premium),
    border-color 200ms var(--dv-ease-premium), transform 200ms var(--dv-ease-premium), box-shadow 200ms var(--dv-ease-premium);
  animation: dv-card-cascade 360ms cubic-bezier(0.16,1,0.3,1) both;
}
.dv-root .dv-quick-reply::before { content: '\203A\0020'; }
.dv-root .dv-quick-reply:hover {
  background: var(--dv-black); color: #fff; border-color: var(--dv-black);
  transform: translateY(-2px);
  box-shadow: var(--dv-shadow-chip);
}
.dv-root .dv-quick-reply:active { transform: translateY(0) scale(0.97); transition-duration: 80ms; }
.dv-root .dv-quick-reply.dv-primary {
  background: var(--dv-copper-gradient); color: #fff; border-color: transparent;
  box-shadow: 0 1px 2px rgba(166,90,41,0.15), 0 6px 16px -4px rgba(166,90,41,0.35);
}
.dv-root .dv-quick-reply.dv-primary:hover {
  background: var(--dv-copper-gradient); border-color: transparent;
  box-shadow: 0 2px 4px rgba(166,90,41,0.18), 0 12px 24px -6px rgba(166,90,41,0.42);
}

/* ── YMM quick-start bar (overlay) ────────────────────────────────────── */
/* Standing element inserted directly into the host page's own content
   flow (see mountInlineYmmBar in widget.js) - block-level, no fixed
   positioning, just enough margin to breathe next to the surrounding
   page content. The results panel it opens is styled separately below.
   mountInlineYmmBar() inserts this as a sibling of an Elementor
   section/article via insertBefore, landing it inside whatever
   flex/grid row that section's own container uses for its children.
   Confirmed live on the real site: the card rendered ~700px wide and
   left-aligned instead of spanning the same width as the hero image
   above it - a plain class selector (0,1,0 specificity) loses to the
   host theme's own layout rules for that row's children, the same
   specificity collision .dv-scope .dv-ymm-go is hardened against
   above. Compound-class selector plus !important on every
   sizing-relevant property (both flex axes, box model, and non-flex
   width) covers row-flex, column-flex, grid, and plain block ancestors
   without knowing which one a given page/theme actually uses. */
.dv-scope.dv-ymm-standalone {
  display: block !important;
  width: 100% !important; max-width: 100% !important; box-sizing: border-box !important;
  margin: 24px 0; flex: 1 1 100% !important; align-self: stretch !important;
  grid-column: 1 / -1 !important;
}
/* Positioning context for the corner tag, which sits above/outside the
   dark card's top-right corner rather than inside it. Unlike the
   prototype's .ymm-wrap (padding: 32px 60px 0 - a full page section with
   nav-aligned side gutters the tag's top:14px/right:60px is inset into),
   this wrapper hugs the card tightly with zero padding, so the prototype's
   literal offsets would land the tag over the card's own header content
   instead of above it. padding-top here recreates that breathing room;
   right stays close-in (matching the card's own edge, not a page gutter)
   since this component has no side gutter to inset from. */
.dv-ymm-outer { position: relative; padding-top: 30px; }
.dv-ymm-note {
  position: absolute; top: 14px; right: 16px; z-index: 1;
  background: var(--dv-copper); color: #fff; font-family: var(--dv-font-head);
  font-size: 10px; padding: 5px 10px; letter-spacing: 0.08em;
  text-transform: uppercase; border-radius: 2px; box-shadow: var(--dv-panel-shadow);
}
.dv-ymm {
  background: var(--dv-black); flex: 0 0 auto; position: relative;
  border-radius: 3px; overflow: hidden;
  box-shadow: var(--dv-panel-shadow);
  /* Scoped near-black override, not a change to the shared --dv-black token
     (which other components still use as-is) - matches the prototype's
     slightly warmer off-black text tone within the YMM bar only.
     .dv-ymm-select's own rule below carries the same #1c1c1c for the
     same reason. */
  color: #1c1c1c;
}
/* Copper "ember strip" left edge, matching the approved reference -
   position:relative above + overflow:hidden here clips it to the
   card's own rounded corners instead of the outer wrapper's. */
.dv-ymm::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 5px;
  background: linear-gradient(180deg,
    var(--dv-copper-deep) 0%, var(--dv-copper) 30%, #d97a3f 50%,
    var(--dv-copper) 70%, var(--dv-copper-deep) 100%);
  background-size: 100% 200%;
  animation: dv-ember-strip 4s linear infinite;
}
@keyframes dv-ember-strip {
  from { background-position: 0% 0%; }
  to   { background-position: 0% -200%; }
}
@media (prefers-reduced-motion: reduce) {
  .dv-ymm::before { animation: none; }
}
.dv-ymm-top {
  padding: 18px 26px 14px;
  display: flex; align-items: center; justify-content: space-between;
  border-bottom: 1px solid rgba(255,255,255,0.08); gap: var(--dv-space-md);
}
.dv-ymm-tl { display: flex; align-items: center; gap: var(--dv-space-lg); min-width: 0; }
.dv-ymm-dv {
  width: 40px; height: 40px; flex: 0 0 auto; border-radius: 50%;
  background: var(--dv-black); border: 2px solid var(--dv-copper); color: var(--dv-copper);
  font-family: var(--dv-font-head); font-size: 14px;
  display: flex; align-items: center; justify-content: center;
}
.dv-ymm-tlt {
  font-family: var(--dv-font-head); color: #fff; font-size: 15px;
  text-transform: uppercase; letter-spacing: 0.04em;
}
.dv-ymm-sub { color: rgba(255,255,255,0.6); font-size: 11px; margin-top: 2px; }
.dv-ymm-badge {
  flex: 0 0 auto; white-space: nowrap;
  background: var(--dv-copper); color: #fff; font-size: 10px;
  padding: 5px 11px; border-radius: 3px; letter-spacing: 0.08em;
  text-transform: uppercase; font-family: var(--dv-font-head);
}
.dv-ymm-fields {
  display: grid; grid-template-columns: 1fr 1fr 1fr 1.3fr auto; gap: 8px;
  padding: var(--dv-space-lg) var(--dv-space-xl);
}
.dv-ymm-field { min-width: 0; }
/* Visually hidden, not display:none - the select already carries an
   identical aria-label (see field() in widget.js), so this exists only
   for assistive tech that prefers a real associated <label> over
   aria-label; sighted users get the approved reference's look, where
   the select's own bold uppercase placeholder option IS the label. */
.dv-ymm-label {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.dv-ymm-field { position: relative; }
.dv-ymm-select {
  width: 100%; padding: 14px; font-size: 13px;
  border: 2px solid transparent; border-radius: 3px;
  background: var(--dv-white, #fff); font-family: var(--dv-font-head);
  color: #1c1c1c; text-transform: uppercase; letter-spacing: 0.02em;
  cursor: pointer;
  transition: border-color 120ms ease, box-shadow 120ms ease, transform 120ms ease;
}
.dv-ymm-select:hover:not(:disabled) { border-color: var(--dv-copper); transform: translateY(-1px); }
.dv-ymm-select:focus {
  border-color: var(--dv-copper); outline: none;
  box-shadow: 0 0 0 4px rgba(166,90,41,0.12);
}
.dv-ymm-select:disabled { opacity: 0.55; cursor: default; }
.dv-ymm-select option { color: var(--dv-black); text-transform: none; }
/* .dv-scope (not .dv-root) here on purpose: the overlay page's YMM bar is
   inserted directly into the host page's own content flow, outside
   #deevee-root entirely, so it needs its own specificity-boosting
   ancestor rather than the fixed-position root wrapper's class. */
.dv-scope .dv-ymm-go {
  flex: 0 0 auto;
  background: var(--dv-copper); color: #fff; border: none;
  border-radius: 3px; padding: 10px 18px; cursor: pointer;
  font-family: var(--dv-font-head); font-size: 14px; letter-spacing: 1.5px;
  min-width: 160px; position: relative; overflow: hidden;
  transition: background 160ms ease, transform 160ms ease, box-shadow 160ms ease;
}
.dv-scope .dv-ymm-go:hover:not(:disabled) {
  background: var(--dv-copper-deep);
  transform: translateY(-2px);
  box-shadow: 0 10px 24px rgba(166,90,41,0.42), 0 0 0 6px rgba(166,90,41,0.1);
}
.dv-scope .dv-ymm-go::after {
  content: ''; position: absolute; top: 0; left: 0; width: 40%; height: 100%;
  background: linear-gradient(120deg, transparent, rgba(255,255,255,0.35), transparent);
  /* translateX() percentages resolve against this element's OWN width
     (40% of the button), not the button's width the way `left` did, so
     the values aren't a 1:1 swap: old rest/hover positions were -60%/130%
     of the BUTTON's width; dividing each by this element's 40% width
     gives the equivalent -150%/325% here, reproducing the identical
     -60%-to-130% (190%-of-button-width) sweep distance, just
     GPU-composited via `transform` instead of the layout-triggering
     `left` property. */
  transform: skewX(-20deg) translateX(-150%);
  transition: transform 720ms ease;
}
.dv-scope .dv-ymm-go:hover:not(:disabled)::after { transform: skewX(-20deg) translateX(325%); }
/* var(--dv-grey) = #6b6b6b, intentionally consolidated onto the shared
   token here (was a hardcoded near-duplicate #797979). The lead-form
   label/footer below (.dv-lf-label/.dv-lf-footer) deliberately keeps the
   literal #797979 instead - a distinct, lighter shade, not a leftover
   inconsistency - so don't collapse the two onto each other. */
.dv-scope .dv-ymm-go:disabled { background: var(--dv-grey); opacity: 1; }
.dv-ymm-tagline {
  padding: 0 var(--dv-space-xl) var(--dv-space-lg);
  font-size: 11px; color: rgba(255,255,255,0.6);
  font-family: var(--dv-font-body);
}
.dv-ymm-tagline-star { color: var(--dv-copper); }
@media (max-width: 900px) {
  .dv-ymm-fields { grid-template-columns: 1fr 1fr; }
  /* .dv-scope-prefixed, not a bare .dv-ymm-go selector: the base rule
     below is .dv-scope .dv-ymm-go (0,2,0) - a bare .dv-ymm-go override
     here (0,1,0) loses on specificity regardless of media-query state,
     so its padding would never actually apply (confirmed via
     getComputedStyle - grid-column applied fine since nothing else sets
     it, but padding silently stayed at the base rule's 10px 18px). */
  .dv-scope .dv-ymm-go { grid-column: span 2; padding: 14px; }
}

/* ── Vehicle-context chip (overlay header, pinned once a vehicle is known) ──
   Copper wash + copper hairline + peach text, matched exactly against the
   approved reference's .ov-ctx (was a neutral white-tinted chip, which
   read as flat/generic against the header next to this copper-accented
   system - not a glow/animation, just the right flat color. */
.dv-vehicle-chip {
  background: rgba(166,90,41,0.25); color: #fcb98d;
  border: 1px solid rgba(166,90,41,0.5);
  font-family: var(--dv-font-head); font-size: 10px; letter-spacing: 0.03em;
  padding: 6px 12px; border-radius: 3px; white-space: nowrap;
  flex: 0 0 auto; margin-right: 22px;
}

/* ── Overlay results: "Best for your build" summary + Top Pick ─────────── */
.dv-results { align-self: stretch; margin: 2px 0; opacity: 0; animation: dv-msg-in 380ms cubic-bezier(0.16,1,0.3,1) both; }
.dv-results-best {
  display: flex; align-items: center; gap: 6px;
  color: var(--dv-copper); font-family: var(--dv-font-head);
  font-size: 12px; letter-spacing: 0.03em; margin-bottom: 8px;
}
.dv-results-cards { display: flex; gap: 10px; flex-wrap: wrap; }
/* Overlay surface: strict 3-equal-column grid (matches the approved
   reference's .ov-grid) instead of flex-wrap - flex-wrap wasn't wrong for
   3 items specifically, but drifted from exact-width parity with the
   reference the moment card count or viewport width changed. */
.dv-overlay .dv-results-cards { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 14px; }
/* Item-count-aware: the base 3-equal-column rule above only fits when
   there really are 3 cards. With fewer, it left the row a fixed 3
   tracks wide with genuinely empty grid cells trailing off to the right
   (a grid's explicit column count doesn't shrink to fit its content) -
   renderOverlayResults() adds one of these classes based on items.length
   so the layout always matches what's actually there. */
.dv-overlay .dv-results-cards.dv-results-solo { grid-template-columns: 1fr; justify-items: center; }
.dv-overlay .dv-results-cards.dv-results-duo { grid-template-columns: 1fr 1fr; }
/* Every card in a row stays equal size, including the top pick - it's
   set apart by a bronze-tinted background (below) and by centered
   position (the `order` styles renderOverlayResults() sets per-card),
   not by taking up more room than its row-mates. .dv-results-trio still
   exists as a marker class for that ordering/tint targeting even though
   it no longer changes grid-template-columns from the 3-equal-column
   base above. */
/* Bronze/copper-tinted background on the pick itself - both surfaces
   (this grid and the single-card compact surface, see the sibling rule
   near .dv-product-card:not(.dv-results-card) below) - a warmer, richer
   ground than the plain --dv-surface every other card gets, so it reads
   as "the one DeeVee is leading with" through color, not size. */
.dv-results-card.dv-top-pick { background: var(--dv-copper-mute-bg); }
/* .dv-card.dv-results-card (not just .dv-results-card) is deliberate:
   the base .dv-card rule (below, in the Cards section) sets its own
   align-items: center and gap: 12px at the SAME specificity as a bare
   .dv-results-card selector (one class each) - a tie that source order
   alone decided, and .dv-card's own rule sits later in this file, so it
   was silently winning. That meant every card here got a stray 12px gap
   above AND below the image (reading as "too much free space") and
   align-items: center instead of stretch, which let .dv-card-body (no
   explicit width of its own) shrink to its content's width and center
   itself instead of spanning the card - the actual cause of text not
   lining up flush with the image's edges. Compounding .dv-card into the
   selector (every element with this class already carries .dv-card too -
   see renderOverlayResults()) makes this rule unambiguously win instead
   of depending on source order. .dv-product-card:not(.dv-results-card)
   below never had this bug: its :not() pseudo-class happens to add enough
   specificity on its own. */
.dv-card.dv-results-card {
  flex: 1 1 182px; max-width: 235px; min-width: 0; position: relative;
  flex-direction: column; align-items: stretch; gap: 0;
  padding: 0; overflow: hidden;
  border-radius: var(--dv-radius-card); border-color: var(--dv-border-soft);
  box-shadow: var(--dv-shadow-card);
  animation: dv-card-cascade 520ms cubic-bezier(0.16,1,0.3,1) both;
  transition: border-color 220ms var(--dv-ease-premium), box-shadow 220ms var(--dv-ease-premium),
    transform 220ms var(--dv-ease-premium);
}
/* min-width: 0 above (overriding the flex/grid item default of auto)
   matters most here: a grid track can otherwise be forced wider than its
   1fr share by its own content's natural minimum width (the TOP PICK
   badge/spec chip text, in particular) instead of wrapping - the classic
   CSS grid overflow trap, and exactly what pushed the 3-column overlay
   grid past the viewport at phone widths. */
.dv-overlay .dv-results-card { max-width: none; }
.dv-results-card:nth-child(1) { animation-delay: 0ms; }
.dv-results-card:nth-child(2) { animation-delay: 80ms; }
.dv-results-card:nth-child(3) { animation-delay: 160ms; }
@keyframes dv-card-cascade {
  from { opacity: 0; transform: translateY(14px) scale(0.94); filter: blur(6px); }
  60% { opacity: 1; filter: blur(0); }
  to { opacity: 1; transform: translateY(0) scale(1); filter: blur(0); }
}
.dv-results-card .dv-card-img { width: 100%; aspect-ratio: 14 / 9; height: auto; object-fit: cover; border-radius: 0; }
/* flex: 1 + min-height: 0 is the actual fix for the row-stretch gap, not
   .dv-card-specs' margin-top: auto alone (below). The overlay's grid
   stretches .dv-results-card to the row's tallest card, but a flex child
   (this body) only grows to fill that extra height if it's told to -
   without flex here, body stayed at its own content height and the
   leftover space sat invisibly below it, where no auto-margin could ever
   reach it. With body actually filling the card, the auto-margin has
   real leftover space inside body to push into, landing the specs strip
   (the last content block in the card, now that the fit banner is gone)
   on the card's true bottom edge instead of floating above a dead gap.
   min-height: 0 stops the flex-column child from refusing to shrink
   below its content size, the other half of the same classic flexbox
   bug. */
.dv-results-card .dv-card-body {
  padding: 10px 10px 12px;
  display: flex; flex-direction: column; gap: 3px;
  flex: 1 1 auto; min-height: 0;
}
.dv-results-card.dv-top-pick { border-color: rgba(166,90,41,0.35); border-width: 1.5px; }
.dv-results-card:hover {
  transform: translateY(-3px); box-shadow: var(--dv-shadow-card-hover);
  border-color: rgba(166,90,41,0.3);
}
.dv-results-card .dv-card-name { font-size: 11px; }
.dv-results-card .dv-card-tagline { font-size: 11px; margin-top: 1px; }
/* No product carries a real product-page url yet, so the card is a
   selection control instead of a link (both surfaces - see
   makeSelectable() in widget.js) - the existing hover lift above already
   reads as "interactive" on the overlay, this just gives it the right
   cursor everywhere and a visible focus state for keyboard users. */
.dv-product-card.dv-selectable { cursor: pointer; }
.dv-product-card.dv-selectable:focus-visible { outline: 2px solid var(--dv-copper); outline-offset: 2px; }
/* Full-width solid bar sitting above the image, inside the card - not a
   floating corner ribbon, matched exactly against the approved
   reference's .topbar (was position:absolute over the image's top-left
   corner, which read as a sticker rather than a deliberate card header). */
.dv-top-pick-badge {
  display: flex; align-items: center; justify-content: center; gap: 5px;
  width: 100%; text-align: center;
  background: var(--dv-copper-gradient); color: #fff;
  font-family: var(--dv-font-head); font-size: 10px; letter-spacing: 0.08em;
  text-transform: uppercase; padding: 5px;
}
.dv-top-pick-badge::before { content: '\2605'; font-size: 11px; }

/* ── Compare card ─────────────────────────────────────────────────────────
   Ported 1:1 from the client-approved prototype's own .compare-card /
   .cmp-* (deliverables/prototype/style.css + script.js's renderCompareTable())
   instead of inventing a generic HTML table - this is the exact design the
   client signed off on: black header row, copper gradient on the Top Pick
   column, label column on the left, no fabricated rating row (this catalog
   has no real review data, unlike the prototype's demo data). */
/* Mobile-first: this widget's own content area (~340-380px) is narrower
   than 3 product columns can read at (the old fixed 100px+3x1fr grid was
   forcing 8.5-10.5px text to fit - below this project's 12px body-text
   floor). Real minimums per column instead, with the whole table scrolling
   horizontally once those minimums exceed the viewport - the standard
   pattern for a table that structurally can't reflow to one column and
   still mean the same thing. */
.dv-compare-scroll {
  overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch;
  border-radius: var(--dv-radius-card); box-shadow: var(--dv-shadow-card);
  margin-bottom: 4px; scrollbar-width: thin;
}
.dv-compare {
  background: var(--dv-surface); border: 1px solid var(--dv-border-soft); border-radius: var(--dv-radius-card);
  overflow: hidden; margin-bottom: 0;
  width: 100%;
}
.dv-cmp-head {
  display: grid; grid-template-columns: 92px repeat(3, minmax(112px, 1fr));
  background: var(--dv-black-gradient); color: #fff;
  position: sticky; top: 0; z-index: 2;
}
.dv-cmp-head .dv-cmp-col {
  padding: 12px 10px; text-align: center; position: relative;
  border-left: 1px solid rgba(255,255,255,0.08); overflow: hidden; text-overflow: ellipsis;
}
.dv-cmp-head .dv-cmp-col:first-child {
  border-left: none; position: sticky; left: 0; z-index: 1; background: var(--dv-black-gradient);
}
/* Top Pick column reads as the hero here too, not just on the results
   cards above - a subtle lift (scale + shadow) rather than a border,
   since the column's own copper fill already carries most of the
   emphasis and a heavier border would compete with the grid's own
   column-divider lines. transform-origin: top keeps the lift reading as
   "this column stands forward," not "this column grew downward." */
.dv-cmp-head .dv-cmp-col.dv-cmp-top {
  background: var(--dv-copper-gradient);
  transform: scale(1.035); transform-origin: top; position: relative; z-index: 1;
  box-shadow: 0 6px 16px -4px rgba(166,90,41,0.45);
}
.dv-cmp-badge {
  display: flex; align-items: center; justify-content: center; gap: 4px;
  font-family: var(--dv-font-head); font-size: 9px;
  letter-spacing: 0.1em; margin-bottom: 4px; color: #fff; opacity: 0.95;
}
.dv-cmp-badge::before { content: '\2605'; font-size: 9px; }
.dv-cmp-name { font-family: var(--dv-font-head); font-size: 12px; text-transform: uppercase; letter-spacing: 0.02em; line-height: 1.3; }
.dv-cmp-row { display: grid; grid-template-columns: 92px repeat(3, minmax(112px, 1fr)); border-top: 1px solid rgba(17,17,17,0.06); }
.dv-cmp-row:hover { background: rgba(166,90,41,0.04); }
.dv-cmp-label {
  padding: 10px 10px; font-family: var(--dv-font-head); font-size: 9.5px;
  text-transform: uppercase; letter-spacing: 0.05em; color: var(--dv-grey); background: var(--dv-cream);
  display: flex; align-items: center; min-width: 0;
  position: sticky; left: 0; z-index: 1;
  border-right: 1px solid rgba(17,17,17,0.06);
}
.dv-cmp-cell {
  padding: 10px 8px; font-size: 12px; color: var(--dv-black); text-align: center;
  border-left: 1px solid rgba(17,17,17,0.05);
  display: flex; align-items: center; justify-content: center; min-width: 0;
  overflow-wrap: break-word; word-break: break-word;
}
.dv-cmp-cell.dv-cmp-top { background: var(--dv-copper-light); font-weight: 600; }
.dv-cmp-foot {
  padding: 10px 12px; background: var(--dv-cream); font-size: 10.5px; color: var(--dv-copper-deep);
  font-family: var(--dv-font-head); letter-spacing: 0.02em; border-top: 1px solid rgba(17,17,17,0.06);
}

/* ── Lead-capture form ────────────────────────────────────────────────── */
/* One field open at a time (renderLeadForm() in widget.js) — matches the
   approved prototype's inline form (deliverables/prototype: .lead-form)
   exactly in structure and voice, restated with this widget's own tokens. */
@keyframes dv-lf-in {
  from { opacity: 0; transform: translateY(10px) scale(0.96); filter: blur(4px); }
  60% { opacity: 1; filter: blur(0); }
  to { opacity: 1; transform: translateY(0) scale(1); filter: blur(0); }
}
.dv-lf-wrap {
  align-self: stretch; margin: 2px 0;
  animation: dv-lf-in 520ms cubic-bezier(0.16,1,0.3,1) both;
}
.dv-lf-card {
  background: #fff; border: 1px solid rgba(0,0,0,0.08);
  border-radius: var(--dv-radius); box-shadow: 0 4px 12px rgba(0,0,0,0.04);
  padding: var(--dv-space-lg);
}
.dv-lf-title {
  font-family: var(--dv-font-head); font-size: 10px; color: var(--dv-copper);
  text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 10px;
}
/* Honeypot decoy field (see renderLeadForm() in widget.js) - same visually-
   hidden technique as .dv-ymm-label above (off-screen, not display:none or
   type="hidden", which a decent bot already knows to skip). A real visitor
   never sees or reaches this; a generic bot form-filler that populates
   every input it finds on the page usually fills it anyway, and a non-empty
   value silently fails the submission server-side
   (BotCheck::honeypot_tripped()). */
.dv-lf-hp {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.dv-lf-row {
  display: flex; justify-content: space-between; align-items: center;
  gap: 10px; padding: 9px 0; border-bottom: 1px solid rgba(0,0,0,0.06);
  transition: background 160ms ease;
}
.dv-lf-row:last-of-type { border-bottom: none; }
/* #fff7f0, not var(--dv-copper-light) (#fdf6f2) - the prototype's peach
   highlight for the active row is a distinct, slightly warmer shade than
   the shared token used elsewhere (e.g. the compare-card top-pick cell
   above), so this overrides locally instead of touching the shared
   variable. */
.dv-lf-row-next {
  background: #fff7f0; margin: 0 calc(-1 * var(--dv-space-lg));
  padding: 9px var(--dv-space-lg); border-radius: 4px;
  border-bottom: 1px solid #ffe9d5;
}
/* #797979, not var(--dv-grey) (#6b6b6b) - a deliberately distinct, lighter
   shade the lead-form uses on purpose; see the cross-reference comment on
   .dv-scope .dv-ymm-go:disabled above for why these two greys differ. */
.dv-lf-label { color: #797979; font-size: 13px; font-weight: 500; flex: 0 0 52px; }
.dv-lf-value { display: flex; align-items: center; gap: 8px; font-size: 13px; min-width: 0; }
.dv-lf-val {
  color: var(--dv-black); font-weight: 600; cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  transition: color 140ms ease;
}
.dv-lf-val:hover, .dv-lf-val:focus-visible { color: var(--dv-copper); }
.dv-lf-val:focus-visible { outline: 2px solid var(--dv-copper); outline-offset: 2px; border-radius: 2px; }
.dv-lf-check { color: var(--dv-copper); font-weight: bold; font-size: 13px; flex: 0 0 auto; }
.dv-lf-pending {
  color: var(--dv-copper); font-family: var(--dv-font-head);
  font-size: 10px; letter-spacing: 0.05em; text-transform: uppercase;
}
.dv-lf-input {
  flex: 1 1 auto; min-width: 0; max-width: 190px;
  background: #fff; border: 1.5px solid var(--dv-copper); border-radius: 4px;
  padding: 7px 10px; font-size: 13px; font-family: var(--dv-font-body); color: var(--dv-black);
  text-align: right; outline: none; box-shadow: 0 0 0 4px rgba(166,90,41,0.12);
}
.dv-lf-input::placeholder { color: #cbcbcb; }
.dv-lf-input:disabled { opacity: 0.6; }
/* Explicit commit buttons (renderLeadForm() in widget.js) - an ADDITION to
   the existing Enter/Tab/blur commit path (see .dv-lf-input's own comment
   history), for visitors on touch or using autofill who have no keyboard
   affordance and no visible cue that a keystroke sends data. Both call the
   exact same commit() the keyboard path calls; only their label/layout
   differ. Wraps the active row's input so the pair reads as one control,
   matching the label/value slot every other row already uses. */
.dv-lf-input-wrap { display: flex; align-items: center; gap: 6px; min-width: 0; }
.dv-lf-input-wrap .dv-lf-input { max-width: 140px; }
/* Small, copper-outlined - a lighter-weight affordance than .dv-lf-send
   below since it only moves to the next field, not to submission. Hidden
   (not just disabled) until the field holds a committable value, so it
   never visually competes with the input for space while empty. */
.dv-root .dv-lf-next {
  flex: 0 0 auto; cursor: pointer; padding: 6px 10px; white-space: nowrap;
  background: #fff; color: var(--dv-copper); border: 1.5px solid var(--dv-copper); border-radius: 4px;
  font-family: var(--dv-font-head); font-size: 10px; letter-spacing: 0.04em; text-transform: uppercase;
  transition: background 140ms ease, color 140ms ease;
}
.dv-root .dv-lf-next:hover { background: var(--dv-copper); color: #fff; }
.dv-root .dv-lf-next[hidden] { display: none; }
/* Full-width, solid copper - the visually heavier "final step" affordance,
   consistent with .dv-lf-retry's full-width black button below but in the
   widget's accent color rather than black, so it doesn't read as an error-
   recovery action. Appended to `card` itself (not the row), same reasoning
   as .dv-lf-retry/.dv-lf-sending sitting outside the row list. */
.dv-root .dv-lf-send {
  display: block; margin-top: 10px; width: 100%; padding: 9px; cursor: pointer;
  background: var(--dv-copper); color: #fff; border: none; border-radius: 4px;
  font-family: var(--dv-font-head); font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase;
  transition: background 140ms ease;
}
.dv-root .dv-lf-send:hover { background: var(--dv-copper-deep); }
.dv-root .dv-lf-send[hidden] { display: none; }
.dv-lf-error {
  margin-top: 8px; padding: 8px 10px; border-radius: 4px;
  background: rgba(166,90,41,0.08); color: var(--dv-copper-deep); font-size: 12px; line-height: 1.4;
}
.dv-lf-sending {
  margin-top: 8px; color: var(--dv-grey); font-size: 12px; font-style: italic;
}
.dv-root .dv-lf-retry {
  margin-top: 8px; width: 100%; padding: 8px; cursor: pointer;
  background: var(--dv-black); color: #fff; border: none; border-radius: 4px;
  font-family: var(--dv-font-head); font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase;
  transition: background 140ms ease;
}
.dv-root .dv-lf-retry:hover { background: var(--dv-copper); }
.dv-lf-footer {
  margin-top: 10px; padding-top: 9px; border-top: 1px solid rgba(0,0,0,0.06);
  font-size: 11px; color: #797979;
}
.dv-lf-footer b { color: var(--dv-black); }
/* Compact floating bubble: smaller card padding/type than the full-width
   overlay, matching the .dv-widget .dv-head/.dv-panel downscale pattern
   above rather than a one-off. */
.dv-widget .dv-lf-card { padding: 12px; font-size: 11px; }
.dv-widget .dv-lf-row { padding: 7px 0; }
.dv-widget .dv-lf-label { font-size: 11px; flex: 0 0 55px; }
.dv-widget .dv-lf-value { font-size: 11px; }
.dv-widget .dv-lf-input { font-size: 11px; padding: 6px 10px; max-width: 180px; }
.dv-widget .dv-lf-input-wrap .dv-lf-input { max-width: 110px; }
.dv-widget .dv-lf-next { font-size: 9px; padding: 5px 8px; }
.dv-widget .dv-lf-send { font-size: 10px; padding: 8px; }

/* ── Lead confirmation card ───────────────────────────────────────────────
   The "hero moment" that appears below the (now-persistent, non-collapsing)
   lead form after a real submission — SENDING pulses briefly, then flips to
   DELIVERED (see renderSalesforceCard() in widget.js). Deliberately styled
   with this widget's own copper brand gradient, not the actual CRM vendor's
   brand colors - which backend the lead posts to is never surfaced to the
   visitor (see the matching comment in widget.js). */
.dv-sf-card {
  background: #fff; border: 1px solid rgba(0,0,0,0.08); border-radius: 8px;
  overflow: hidden; margin-bottom: 12px;
  animation: dv-lf-in 520ms cubic-bezier(0.16,1,0.3,1) both;
  /* .dv-thread is a column flexbox; a flex item with overflow other than
     visible gets an automatic min-height of 0 per the flexbox spec (instead
     of its content height), so once the thread's content exceeds the
     panel's fixed height, this card - being the one item with overflow:
     hidden (needed to clip the gradient header to the rounded corners) -
     absorbs all the shrinkage and collapses to ~2px, clipping its own
     content to invisible, while sibling cards without overflow:hidden stay
     full size. flex-shrink: 0 opts this card out of that shrinking
     entirely, matching how every other card in the thread already behaves
     by virtue of not setting overflow. */
  flex-shrink: 0;
}
.dv-sf-complete { box-shadow: 0 4px 16px rgba(166,90,41,0.15); }
.dv-sf-head {
  display: flex; align-items: center; gap: 8px; padding: 10px 14px;
  background: var(--dv-copper-gradient); color: #fff;
}
.dv-sf-cloud { font-size: 16px; }
.dv-sf-title { font-family: var(--dv-font-head); font-size: 12px; letter-spacing: 0.03em; flex: 1; }
.dv-sf-status {
  font-family: var(--dv-font-head); font-size: 9px; letter-spacing: 0.05em;
  padding: 3px 8px; border-radius: 3px; background: rgba(255,255,255,0.2);
}
.dv-sf-pending { animation: dv-sf-pulse 1.2s ease-in-out infinite; }
@keyframes dv-sf-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
.dv-sf-done { background: var(--dv-success); color: #fff; animation: none; }
/* Distinct from both dv-sf-pending (pulsing, still in flight) and
   dv-sf-done (green, actually confirmed) - amber/copper, not green, so a
   queued-for-retry Salesforce sync never visually reads as a success. */
.dv-sf-queued { background: var(--dv-copper); color: #fff; animation: none; }
.dv-sf-body { padding: 10px 14px; }
.dv-sf-row {
  display: flex; justify-content: space-between; padding: 4px 0;
  font-size: 11.5px; border-bottom: 1px solid rgba(0,0,0,0.04);
}
.dv-sf-row:last-child { border-bottom: none; }
.dv-sf-label { color: var(--dv-grey); }
.dv-sf-foot { padding: 10px 14px; background: var(--dv-cream); }
.dv-sf-fill {
  height: 3px; width: 15%; background: var(--dv-copper); border-radius: 2px;
  transition: width 1.8s ease;
}
.dv-sf-caption { font-size: 10px; color: var(--dv-grey); margin-top: 6px; }
.dv-widget .dv-sf-row, .dv-widget .dv-sf-title { font-size: 10px; }
@media (prefers-reduced-motion: reduce) {
  .dv-sf-card { animation: none; }
  .dv-sf-pending { animation: none; }
  .dv-sf-fill { transition: none; }
}

/* ── Thread ───────────────────────────────────────────────────────────── */
.dv-thread {
  flex: 1 1 auto; overflow-y: auto; padding: 16px;
  display: flex; flex-direction: column; gap: 10px;
}
@keyframes dv-msg-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
.dv-msg {
  max-width: 84%; padding: 11px var(--dv-space-xl);
  border-radius: 18px;
  font-size: 14px; line-height: 1.5; white-space: pre-wrap; word-wrap: break-word;
  animation: dv-msg-in 320ms cubic-bezier(0.16,1,0.3,1) both;
}
.dv-user {
  align-self: flex-end; background: var(--dv-black-gradient); color: #fff;
  border-bottom-right-radius: 4px;
  box-shadow: 0 1px 2px rgba(17,17,17,0.08);
}
.dv-assistant {
  align-self: flex-start; background: var(--dv-surface); color: var(--dv-black);
  border: 1px solid var(--dv-border-soft); border-left: 3px solid var(--dv-copper);
  border-bottom-left-radius: 4px;
  box-shadow: var(--dv-shadow-card);
}
/* Copper, not black - a live staffer's reply used to render visually
   identical to the visitor's own bubble (.dv-user is also solid/white-text,
   just mirrored to the other side), so the only tell was left-vs-right
   alignment. Copper matches the header's own claimed-state color
   (.dv-handoff-claimed) and the banner's "now chatting with X" - the same
   "a real person, branded" signal, everywhere it appears. */
.dv-staff {
  align-self: flex-start; background: var(--dv-copper-gradient); color: #fff;
  border-left: 3px solid #fff; border-bottom-left-radius: 2px;
  box-shadow: 0 1px 3px rgba(166,90,41,0.25);
}
.dv-handoff-banner {
  align-self: center; max-width: 92%;
  background: var(--dv-copper-light); color: var(--dv-copper);
  border: 1px dashed var(--dv-copper); border-radius: 999px;
  padding: 6px 16px; font-size: 12px; text-align: center;
  transition: background 200ms ease, color 200ms ease, border-color 200ms ease;
}
/* Swapped in by markClaimed() once a staffer actually picks up the chat -
   solid border (not dashed) reads as "confirmed", not "still pending",
   matching the copper original's own dashed-while-waiting logic. */
.dv-handoff-banner-claimed {
  background: rgba(97,206,112,0.14); color: #227a35;
  border: 1px solid var(--dv-success);
}

/* Typing indicator */
.dv-typing { display: flex; gap: 4px; padding: 14px 16px; }
.dv-typing span {
  width: 6px; height: 6px; border-radius: 50%; background: var(--dv-copper);
  animation: dv-blink 1.2s infinite ease-in-out;
}
.dv-typing span:nth-child(2) { animation-delay: 0.15s; }
.dv-typing span:nth-child(3) { animation-delay: 0.3s; }
@keyframes dv-blink { 0%, 70%, 100% { opacity: 0.25; } 35% { opacity: 1; } }

/* ── Cards ────────────────────────────────────────────────────────────── */
.dv-card-wrap {
  display: flex; flex-direction: column; gap: 8px;
  align-self: stretch; margin: 2px 0;
  opacity: 0; animation: dv-msg-in 380ms cubic-bezier(0.16,1,0.3,1) 60ms both;
}
.dv-overlay .dv-card-wrap { flex-direction: row; flex-wrap: wrap; }
.dv-card {
  display: flex; gap: 12px; align-items: center;
  background: var(--dv-surface); border: 1px solid var(--dv-border-soft);
  border-radius: var(--dv-radius); padding: var(--dv-space-sm);
  text-decoration: none; color: inherit;
  transition: border-color 160ms ease, box-shadow 160ms ease;
}
.dv-overlay .dv-card { flex: 1 1 240px; max-width: 300px; }
a.dv-card:hover { border-color: var(--dv-copper); box-shadow: 0 4px 16px rgba(166,90,41,0.14); }
.dv-card-img {
  width: 64px; height: 64px; object-fit: cover;
  border-radius: 4px; flex: 0 0 auto; background: var(--dv-cream);
}
.dv-card-body { min-width: 0; }
.dv-card-name {
  font-family: var(--dv-font-head); font-size: 13px;
  letter-spacing: 0.03em; color: var(--dv-black);
}
.dv-card-price { color: var(--dv-copper); font-weight: 700; font-size: 14px; margin-top: 2px; }
.dv-card-note { color: var(--dv-grey); font-size: 12px; margin-top: 2px; }
.dv-card-tagline {
  font-size: 12.5px; color: var(--dv-grey); margin-top: 2px; line-height: 1.4;
}
/* ── Card spec strip - static/dynamic load at a glance, as one compact
   inline row rather than boxed tiles. Real product data (FitmentService::
   row_to_match()'s specs.*) that no card rendered before - the two
   numbers here are the same two the compare table already leads with.
   Now the ONLY product-info element on the card (the confirmed/not-
   confirmed fit banner was removed - direct feedback that it read as
   repeated generic chrome rather than real information), so it carries
   real visual weight instead of reading as a footnote: a solid copper
   border and a bold copper-deep value color, not just a pale background.
   Still squared, not pill-shaped, matching this widget's buttons.
   margin-top: auto (the card body is a flex column) anchors this strip
   to the bottom edge of every card in a row regardless of how much
   tagline sits above it - the fix for cards in the same grid row ending
   at different content heights and leaving a lopsided gap under whichever
   card had less to say. */
.dv-card-specs { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; margin-bottom: 0; margin-left: 0; margin-right: 0; align-self: flex-start; }
.dv-card-body > .dv-card-specs:last-child { margin-top: auto; }
.dv-card-spec-chip {
  display: inline-flex; align-items: baseline; gap: 3px;
  font-size: 10px; color: var(--dv-copper-deep);
  background: var(--dv-copper-light); border: 1.5px solid var(--dv-copper);
  border-radius: 5px; padding: 3px 7px; line-height: 1.3;
}
.dv-card-spec-chip b {
  font-family: var(--dv-font-head); font-weight: 700; font-size: 11px; color: var(--dv-copper-deep);
}
/* Reserves the Top Pick badge's own height on every card in a row even
   when it isn't the pick, so every card's image starts at the same y -
   without this, only the top-pick card carried the badge, so its image
   (and everything under it) sat visibly lower than its row-mates. */
.dv-top-pick-badge--ghost { visibility: hidden; }

/* Compact-surface product card: full-bleed top image and generous body
   padding, echoing this file's own "hero card" precedent (.dv-solo-card /
   .dv-results-card) instead of the old 64px side thumbnail row. Scoped off
   .dv-results-card so the overlay's separate, already client-approved grid
   (renderOverlayResults()) is left completely alone. The old per-card
   "Find nearest dealer" button is gone - removed as noisy repeated chrome
   now that the card itself is the one obvious tap target. */
.dv-product-card:not(.dv-results-card) {
  flex-direction: column; align-items: stretch; gap: 0;
  padding: 0; overflow: hidden;
  border-radius: var(--dv-radius-card);
  border-color: var(--dv-border-soft);
  box-shadow: var(--dv-shadow-card);
  transition: border-color 220ms var(--dv-ease-premium), box-shadow 220ms var(--dv-ease-premium),
    transform 220ms var(--dv-ease-premium);
}
/* Same bronze-tint treatment as the grid surface's top pick (see
   .dv-results-card.dv-top-pick) - :not(.dv-results-card) already beats
   the base .dv-card's specificity on its own (see this rule's own
   :not()-adds-specificity note elsewhere in this file), so no extra
   .dv-card compounding needed here the way the grid surface required. */
.dv-product-card.dv-top-pick:not(.dv-results-card) { background: var(--dv-copper-mute-bg); }
.dv-product-card:not(.dv-results-card) .dv-card-img {
  width: 100%; aspect-ratio: 16 / 8.5; height: auto; object-fit: cover; border-radius: 0;
}
.dv-product-card:not(.dv-results-card) .dv-card-body {
  padding: var(--dv-space-lg);
  display: flex; flex-direction: column; gap: 3px;
}
.dv-product-card:not(.dv-results-card) .dv-card-name {
  font-size: 15px; line-height: 1.2;
}
/* Div-based selectable cards (every seeded product today - see
   makeSelectable()) had no hover feedback of their own on this surface,
   only the anchor variant's a.dv-card:hover above - same border/shadow
   language, just extended to the case that actually ships. Lift is
   deliberately small (a card this size reads as "pressed too hard" past
   2-3px) - the shadow doing most of the work, not the translate. */
.dv-product-card.dv-selectable:not(.dv-results-card):hover {
  border-color: rgba(166,90,41,0.28); box-shadow: var(--dv-shadow-card-hover);
  transform: translateY(-2px);
}
/* No :active rule existed anywhere in this file despite these cards being
   the primary tap target on a touch-first surface - a press-down scale
   reads as "yes, that registered" on a phone the way :hover never can. */
.dv-product-card:not(.dv-results-card):active {
  transform: scale(0.98) translateY(0);
  box-shadow: var(--dv-shadow-card);
  transition-duration: 80ms;
}
/* Used both as an <a> (dv-shopify-card's real Storefront-API result) and
   as a plain <button> (the product-grid card's dormant Buy Direct action,
   see renderOverlayResults() - triggers a chat turn instead of linking
   directly) - border/cursor/font resets needed for the button case, which
   a plain <a> already ignores harmlessly. */
.dv-root .dv-buy-btn {
  display: inline-flex; align-items: center; justify-content: center; min-height: 44px;
  margin-top: 10px; width: 100%; box-sizing: border-box;
  background: var(--dv-copper-gradient); color: #fff !important;
  font-family: var(--dv-font-head); font-size: 12.5px; letter-spacing: 0.08em;
  padding: 10px 16px; border-radius: 7px; text-decoration: none;
  border: none; cursor: pointer; text-align: center;
  box-shadow: 0 1px 2px rgba(166,90,41,0.15), 0 6px 16px -4px rgba(166,90,41,0.32);
  transition: box-shadow 200ms var(--dv-ease-premium), transform 200ms var(--dv-ease-premium);
}
.dv-root .dv-buy-btn:hover {
  box-shadow: 0 2px 4px rgba(166,90,41,0.18), 0 12px 24px -6px rgba(166,90,41,0.4);
  transform: translateY(-1px);
}
.dv-root .dv-buy-btn:active { transform: translateY(0) scale(0.98); }
.dv-shopify-card { background: var(--dv-black); border-color: var(--dv-black); }
.dv-shopify-card .dv-card-name { color: #fff; }
.dv-shopify-card .dv-card-note { color: rgba(255,255,255,0.6); }

/* ── SOLO spotlight card ──────────────────────────────────────────────── */
.dv-solo-card {
  background: var(--dv-black); border-radius: 8px; overflow: hidden;
  margin-bottom: 12px; animation: dv-card-cascade 520ms cubic-bezier(0.16,1,0.3,1) both;
}
.dv-solo-card .dv-card-img { width: 100%; height: 160px; border-radius: 0; }
.dv-solo-body { padding: var(--dv-space-lg); }
.dv-solo-badge {
  display: inline-block; background: var(--dv-copper); color: #fff;
  font-family: var(--dv-font-head); font-size: 9px; letter-spacing: 0.05em;
  text-transform: uppercase; padding: 3px 8px; border-radius: 3px; margin-bottom: 8px;
}
.dv-solo-name {
  font-family: var(--dv-font-head); font-size: 22px; color: #fff;
  text-transform: uppercase; letter-spacing: 0.02em;
}
.dv-solo-specs { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 12px 0; }
.dv-solo-spec { text-align: center; padding: 8px; background: rgba(255,255,255,0.06); border-radius: 4px; }
.dv-solo-spec-val { font-family: var(--dv-font-head); font-size: 13px; color: var(--dv-copper); }
.dv-solo-spec-key { font-size: 9px; color: rgba(255,255,255,0.6); text-transform: uppercase; margin-top: 2px; }
.dv-root .dv-solo-cta {
  display: block; text-align: center; margin-top: 10px;
  background: var(--dv-copper); color: #fff; text-decoration: none;
  font-family: var(--dv-font-head); font-size: 11px; letter-spacing: 0.04em;
  text-transform: uppercase; padding: 10px; border-radius: 3px;
}
.dv-root .dv-solo-cta:hover { background: var(--dv-copper-deep); }

/* ── Dealer results panel ────────────────────────────────────────────────
   "Closest Dealers" header + one row per dealer (name, distance badge,
   checkmark, address), with call/directions actions on the closest match
   only — mirrors the approved prototype's dealer rail structure. */
.dv-dealers {
  background: #fff; border: 1px solid rgba(0,0,0,0.08);
  border-radius: var(--dv-radius); padding: var(--dv-space-md);
}
.dv-dealers-title {
  display: flex; align-items: center; gap: 6px;
  font-family: var(--dv-font-head); font-size: 11px;
  letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--dv-black); margin-bottom: var(--dv-space-sm);
}
.dv-dealer {
  position: relative; background: var(--dv-cream);
  border: 1px solid rgba(0,0,0,0.06); border-left: 3px solid transparent;
  border-radius: 4px; padding: var(--dv-space-sm);
}
.dv-dealer + .dv-dealer { margin-top: var(--dv-space-xs); }
.dv-dealer-top { background: #fff; border-color: rgba(0,0,0,0.08); border-left-color: var(--dv-copper); }
.dv-dealer-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.dv-dealer-name { font-family: var(--dv-font-head); font-size: 12px; letter-spacing: 0.03em; color: var(--dv-black); }
.dv-dealer-meta { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.dv-dealer-distance {
  background: var(--dv-copper); color: #fff; font-family: var(--dv-font-head);
  font-size: 9px; letter-spacing: 0.03em; text-transform: uppercase;
  padding: 2px 7px; border-radius: 3px; white-space: nowrap;
}
.dv-dealer-distance-far { background: var(--dv-grey); }
.dv-dealer-check {
  width: 16px; height: 16px; border-radius: 50%; flex: 0 0 auto;
  border: 1.5px solid rgba(0,0,0,0.18);
  display: flex; align-items: center; justify-content: center;
  font-size: 9px; color: transparent;
}
.dv-dealer-check-on { background: var(--dv-copper); border-color: var(--dv-copper); color: #fff; }
.dv-dealer-address { color: var(--dv-grey); font-size: 11.5px; margin-top: 4px; }
.dv-dealer-actions { display: flex; gap: 6px; margin-top: var(--dv-space-xs); }
.dv-root .dv-dealer-btn {
  flex: 1 1 0; text-align: center; text-decoration: none; cursor: pointer;
  font-family: var(--dv-font-head); font-size: 9.5px; letter-spacing: 0.04em;
  text-transform: uppercase; padding: 6px; border-radius: 3px;
  transition: background 160ms ease, color 160ms ease, border-color 160ms ease;
}
.dv-root .dv-dealer-btn-call { background: var(--dv-copper); color: #fff; }
.dv-root .dv-dealer-btn-call:hover { background: var(--dv-copper-deep); }
.dv-root .dv-dealer-btn-directions { background: #fff; color: var(--dv-black); border: 1px solid var(--dv-black); }
.dv-root .dv-dealer-btn-directions:hover { border-color: var(--dv-copper); color: var(--dv-copper); }

/* Rail-only "Send Lead to X" CTA, pinned beneath the dealer rows by normal
   flow (addCard() appends it to .dv-rail last). Arrow is CSS-only via
   ::before, same glyph/spacing as .dv-quick-reply::before - the button's
   own DOM text stays plain sentence case so the accessible name doesn't
   duplicate or shout the arrow. .dv-root prefix + explicit color: #fff for
   the same reason as every other button here (see the .dv-quick-reply
   comment above): Hello Elementor's global `button { color: #cc3366 }`
   reset silently wins unless both the extra specificity and an explicit
   color declaration are present. */
.dv-root .dv-rail-send {
  display: block; width: 100%; margin-top: var(--dv-space-md);
  /* !important on background/color only: unlike .dv-quick-reply above,
     this button also renders inside the post-lead-form dealer picker,
     which can land in a different Elementor section/column context per
     host page - observed live with a page-specific button background
     reset (a pink ~#cc3366) beating the plain .dv-root specificity bump
     that's sufficient everywhere else. Pin it down rather than chase
     per-page specificity. */
  background: var(--dv-copper) !important; color: #fff !important; border: none; border-radius: 3px;
  padding: 10px; font-family: var(--dv-font-head); font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.03em; text-align: center;
  cursor: pointer; transition: background 160ms ease;
}
.dv-root .dv-rail-send::before { content: '\203A\0020'; }
.dv-root .dv-rail-send:hover { background: var(--dv-copper-deep) !important; }

/* Post-lead-form dealer picker (renderDealerPicker() in widget.js) — reuses
   .dv-dealers/.dv-dealer/.dv-dealer-head/-name/-meta/-distance/-address
   above for the same visual language, checkbox instead of the rail's
   decorative read-only dot, and .dv-rail-send for the submit CTA. */
.dv-dealer-cb { width: 16px; height: 16px; flex: 0 0 auto; accent-color: var(--dv-copper); cursor: pointer; }

/* No-dealers-in-radius state (renderNoDealersCard() in widget.js), shown
   instead of silently auto-submitting when a zip's lookup comes back empty.
   Reuses .dv-dealers' card chrome and .dv-rail-send's button style above for
   the same visual weight as the picker it replaces - only the message line
   is new. */
.dv-no-dealers-msg { color: var(--dv-grey); font-size: 12px; line-height: 1.4; margin-bottom: var(--dv-space-sm); }

.dv-root .dv-dealer-skip {
  display: block; width: 100%; margin-top: 6px; background: none; border: none;
  color: var(--dv-grey); font-size: 11px; text-decoration: underline;
  cursor: pointer; padding: 4px; text-align: center;
}
.dv-root .dv-dealer-skip:hover { color: var(--dv-copper); }

/* ── Composer ─────────────────────────────────────────────────────────── */
.dv-form {
  display: flex; gap: 8px; padding: var(--dv-space-md);
  border-top: 1px solid rgba(0,0,0,0.08); background: #fff; flex: 0 0 auto;
}
.dv-input {
  flex: 1 1 auto; border: 1px solid rgba(0,0,0,0.14);
  border-radius: var(--dv-radius); padding: var(--dv-space-sm) var(--dv-space-md);
  /* 16px minimum: anything smaller triggers iOS Safari auto-zoom on focus */
  font-size: 16px; font-family: var(--dv-font-body); outline: none;
}
.dv-input:focus { border-color: var(--dv-copper); box-shadow: 0 0 0 3px rgba(166,90,41,0.12); }
.dv-root .dv-send {
  flex: 0 0 auto; width: 42px; height: 42px;
  display: flex; align-items: center; justify-content: center;
  background: var(--dv-copper); color: #fff; border: none;
  border-radius: var(--dv-radius); padding: 0; cursor: pointer;
  font-family: var(--dv-font-head); font-size: 16px;
  transition: background 160ms ease;
}
.dv-root .dv-send:hover:not(:disabled) { background: var(--dv-copper-deep); }
.dv-root .dv-send:disabled { opacity: 0.5; cursor: default; }

/* ── Footer brand ─────────────────────────────────────────────────────── */
.dv-brand {
  text-align: center; font-size: 9px; color: rgba(255,255,255,0.35);
  letter-spacing: 0.06em; text-transform: uppercase; font-weight: 700;
  padding: 6px 0; background: var(--dv-black); flex: 0 0 auto;
}
.dv-brand a { color: rgba(255,255,255,0.5); text-decoration: underline; }
.dv-brand a:hover { color: var(--dv-copper); }

/* ── Focus states ─────────────────────────────────────────────────────── */
/* Shared keyboard-focus ring for every interactive control that didn't
   already have one. Copper outline, offset so it reads clearly against
   both the black header/panels and the white cards. */
.dv-root .dv-launcher:focus-visible,
.dv-root .dv-send:focus-visible,
.dv-close:focus-visible,
.dv-root .dv-quick-reply:focus-visible,
.dv-scope .dv-ymm-go:focus-visible,
.dv-root .dv-dealer-btn:focus-visible,
.dv-root .dv-rail-send:focus-visible,
.dv-root .dv-solo-cta:focus-visible,
.dv-root .dv-peek-close:focus-visible,
.dv-root .dv-pick-back:focus-visible,
.dv-root .dv-pick-select:focus-visible,
.dv-root .dv-pick-go:focus-visible,
.dv-root .dv-pick-use-btn:focus-visible,
.dv-root .dv-pick-zip:focus-visible {
  outline: 2px solid var(--dv-copper);
  outline-offset: 2px;
}

/* ── Mobile ───────────────────────────────────────────────────────────── */
@media (max-width: 480px) {
  .dv-widget .dv-panel {
    width: calc(100% - 24px); right: 12px; left: 12px; bottom: 12px;
    height: calc(100dvh - 24px); max-height: none; border-radius: 10px;
  }
  .dv-overlay .dv-panel { width: 100vw; height: 88dvh; }
  .dv-launcher { right: 16px; bottom: 16px; }
  /* .dv-head is a single nowrap flex row (avatar + title/subtitle + vehicle
     chip + close button) with no accommodation for narrow screens - on a
     375px phone the chip's fixed nowrap width (~206px) left only ~45px for
     the title/subtitle block, collapsing "Online · Drive Venturous" into a
     garbled multi-line wrap in that sliver instead of the chip actually
     moving anywhere. Let the row wrap so the chip drops to its own line
     below the title, and truncate title/subtitle with ellipsis instead of
     letting them wrap internally - confirmed against the real live site's
     computed layout, not guessed from source alone. */
  .dv-head { flex-wrap: wrap; row-gap: 6px; }
  .dv-head-title, .dv-head-sub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  /* overflow:hidden above clips the pending pulse's box-shadow ring at the
     dot's edge, so phones fall back to the opacity-only dv-sf-pulse
     (animation-name alone keeps the base shorthand's 1.4s cadence). */
  .dv-handoff-active:not(.dv-handoff-claimed) .dv-status-dot { animation-name: dv-sf-pulse; }
  .dv-vehicle-chip { margin-right: 0; max-width: calc(100% - 42px); overflow: hidden; text-overflow: ellipsis; }
  /* 3 equal columns at phone widths (~326px of usable panel space) leaves
     ~100px per card - survives short names ("XD") but crushes longer ones
     ("Sierra / Sierra Xtra") and the longer "Popular pick for your use
     case" fallback label. A vertical stack fixed the crush but traded it
     for a lot of scrolling to compare 2-3 options; a horizontal snap-
     scroll carousel (the standard "featured item" mobile pattern) reads
     each card at full, comfortable width AND keeps them physically close
     together for comparison. Applies uniformly regardless of item count
     (1/2/3) - .dv-results-solo/-duo/-trio only matter for the desktop
     grid split below.
     Card width 78vw + row padding 0 10vw is the actual centering trick:
     at rest (scrollLeft 0) the first card - always the top pick, DOM
     order is never reshuffled, only `order` on desktop - occupies
     4vw-92vw at rest: a small 4vw gutter on the left (just enough to read
     as intentional spacing, not a layout gap) and an 8vw peek of the next
     card on the right as a "swipe for more" affordance. Asymmetric on
     purpose - an earlier symmetric 10vw/10vw version read as dead space on
     the left on a real phone (flagged live, 2026-10-13), not as
     centering. scroll-padding-inline's two values must mirror the visual
     padding exactly (4vw start / 8vw end) or scroll-snap-align: center
     settles the resting card off by the difference. No JS scroll-to-center
     needed. order: initial !important undoes the desktop hero layout's
     reordering (see the trio rule elsewhere) so the carousel's
     left-to-right sequence matches real item order too - without it, a
     stray order: 2 on the (now non-hero) first card would visually
     reorder the swipe sequence for no reason at this width. */
  .dv-overlay .dv-results-cards {
    display: flex; flex-wrap: nowrap;
    overflow-x: auto; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch;
    gap: 12px; padding: 2px 8vw 2px 4vw; scroll-padding-inline: 4vw 8vw;
    scrollbar-width: none;
  }
  .dv-overlay .dv-results-cards::-webkit-scrollbar { display: none; }
  .dv-overlay .dv-results-cards .dv-results-card {
    flex: 0 0 88vw; max-width: 88vw; width: 88vw;
    scroll-snap-align: center; order: initial !important;
  }
  /* Suspends snapping for the "swipe to see more" nudge (nudgeScrollHint()
     in widget.js) only - a mandatory snap container otherwise yanks
     scrollLeft straight back to the nearest card the instant the nudge's
     own scrollTo() settles, which would just look like nothing happened. */
  .dv-overlay .dv-results-cards.dv-results-nudging { scroll-snap-type: none; }
  /* Compact/floating-widget surface (cfg.surface !== 'overlay' - this is
     the actual embed real visitors get on their phone): addCard()'s own
     products branch renders straight into .dv-card-wrap, a plain
     flex-direction:column stack, so cards were just piling up vertically
     with the top pick sitting at the literal top instead of centered -
     the carousel treatment above was only ever built for the overlay
     surface. Same swipe-carousel technique, scoped to .dv-widget and to
     .dv-product-card (this surface's own card class, not the overlay-only
     .dv-results-card hero card) via the dedicated dv-compact-carousel
     class addCard() adds to that same wrap div - see its own comment in
     widget.js for why that's a new class rather than reusing
     dv-results-cards. No order/`!important` reset needed here: this path
     never assigns an inline order (compact cards are never part of the
     3-card hero layout that's the only thing that sets one), so DOM order
     alone - always items[0..n], top pick first - already puts the pick at
     scrollLeft 0 via the same 88vw-card/4vw-left/8vw-right trick as the
     overlay rule above (see its comment for why this is asymmetric, not
     symmetric 10vw/10vw - flagged live on a real phone, 2026-10-13). */
  .dv-widget .dv-compact-carousel {
    display: flex; flex-direction: row; flex-wrap: nowrap;
    overflow-x: auto; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch;
    gap: 12px; padding: 2px 8vw 2px 4vw; scroll-padding-inline: 4vw 8vw;
    scrollbar-width: none;
    /* Setting overflow-x without overflow-y implicitly computes overflow-y
       to 'auto' too (a CSS legacy-compatibility rule, not a typo) - and a
       flex item whose overflow isn't 'visible' on the cross axis gets its
       automatic minimum size reset to 0 instead of its content height. This
       wrap sits as a shrinkable child of .dv-thread (flex-direction:
       column), so the moment the thread's total content exceeds its own
       visible height (routine - it's a scrolling thread), flexbox crushed
       this row toward 0 instead of just letting the thread scroll more -
       confirmed live, every product card rendered with fully correct data
       and horizontal position but a squashed ~2-4px height. flex-shrink: 0
       (already set on .dv-product-card below) stops this row from ever
       being shrunk at all, matching how a carousel should behave. */
    flex-shrink: 0;
  }
  .dv-widget .dv-compact-carousel::-webkit-scrollbar { display: none; }
  .dv-widget .dv-compact-carousel .dv-product-card {
    flex: 0 0 88vw; max-width: 88vw; width: 88vw;
    scroll-snap-align: center;
  }
  .dv-widget .dv-compact-carousel.dv-results-nudging { scroll-snap-type: none; }
  /* "Card N of M" dots - addScrollDots() in widget.js inserts this right
     after whichever card row it's indicating (overlay or compact carousel,
     same markup either way) and keeps the active one in sync with scroll
     position. Warm-black-at-low-alpha for the inactive dots, matching this
     file's own convention (see --dv-border-soft) rather than a neutral
     rgba(0,0,0,x) grey; copper for the active one, same accent as the rest
     of the brand chrome. */
  .dv-scroll-dots {
    display: flex; justify-content: center; align-items: center;
    gap: 6px; margin: 6px 0 2px;
  }
  .dv-scroll-dot {
    width: 6px; height: 6px; border-radius: 50%;
    background: rgba(58, 38, 22, 0.22);
    transition: background-color 0.2s ease, transform 0.2s ease;
  }
  .dv-scroll-dot.is-active {
    background: var(--dv-copper);
    transform: scale(1.3);
  }
  /* Compare card: .dv-compare-scroll (the wrapper added around .dv-compare)
     already handles horizontal scroll and real per-column minimums
     (minmax(112px, 1fr)) at every width, including this one - no
     phone-specific override needed here any more. The old fixed
     64px-label / 7.5-9px-text version this block used to carry was
     replaced wholesale; keeping it here would have silently undone that
     redesign at exactly the width it matters most, since a later-in-file
     media query wins the cascade over the base rules above. */
}

/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
  .dv-launcher, .dv-card, .dv-send, .dv-buy-btn, .dv-widget .dv-panel, .dv-overlay .dv-panel,
  .dv-quick-reply, .dv-dealer-btn, .dv-rail-send,
  .dv-product-card:not(.dv-results-card) { transition: none; }
  .dv-launcher { animation: none; opacity: 1; transform: none; }
  .dv-launcher::before { animation: none; }
  .dv-open .dv-launcher { animation: none; }
  .dv-widget .dv-panel, .dv-overlay .dv-panel { transform: none; }
  .dv-typing span { animation: none; opacity: 0.6; }
  .dv-peek { animation: none; }
  .dv-msg, .dv-quick-reply, .dv-card-wrap { animation: none; opacity: 1; }
  /* Same selector as the pending-handoff pulse rule so this later block
     wins on source order, not a specificity race. */
  .dv-handoff-active:not(.dv-handoff-claimed) .dv-status-dot { animation: none; }
}

/* ── Smart ping: one proactive nudge above the launcher ────────────────── */
.dv-peek {
  position: fixed; right: 24px; bottom: 96px;
  max-width: 260px; display: flex; align-items: flex-start; gap: 8px;
  background: #fff; color: var(--dv-black);
  border: 1px solid rgba(0,0,0,0.08); border-left: 3px solid var(--dv-copper);
  border-radius: var(--dv-radius);
  padding: var(--dv-space-md) var(--dv-space-lg); box-shadow: 0 8px 28px rgba(0,0,0,0.16);
  cursor: pointer; animation: dv-peek-in 220ms ease both;
}
.dv-peek-text { flex: 1; font-size: 13.5px; line-height: 1.4; }
.dv-root .dv-peek-close {
  background: none; border: none; color: var(--dv-grey);
  font-size: 16px; line-height: 1; cursor: pointer; padding: 0 0 0 4px;
}
.dv-root .dv-peek-close:hover { color: var(--dv-black); }
@keyframes dv-peek-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
@media (max-width: 480px) {
  .dv-peek { right: 16px; bottom: 82px; max-width: calc(100vw - 32px); }
}

/* ── Starter picker cards (compact chat starters) ─────────────────────── */
.dv-pick-wrap { animation: dv-lf-in 300ms cubic-bezier(0.16,1,0.3,1) both; }
.dv-root .dv-pick-back {
  background: none; border: none; color: var(--dv-copper); cursor: pointer;
  font-family: var(--dv-font-head); font-size: 11px; letter-spacing: 0.04em;
  text-transform: uppercase; padding: 0 0 8px;
}
.dv-pick-card {
  background: var(--dv-black); border-left: 2px solid var(--dv-copper);
  border-radius: 8px; padding: 12px 14px 14px;
}
.dv-pick-eyebrow {
  color: var(--dv-copper); font-family: var(--dv-font-head); font-size: 10px;
  letter-spacing: 0.1em; text-transform: uppercase; margin-bottom: 8px;
}
.dv-pick-fields { display: flex; flex-direction: column; gap: 6px; }
.dv-pick-field { position: relative; }
/* Visually hidden - the select's own placeholder option is the label. */
.dv-pick-label {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.dv-pick-select {
  width: 100%; padding: 10px 30px 10px 11px; font-size: 12.5px;
  border: 1.5px solid transparent; border-radius: 5px;
  background: #fff; color: #1c1c1c; font-family: var(--dv-font-head);
  text-transform: uppercase; letter-spacing: 0.02em; cursor: pointer;
  appearance: none; -webkit-appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6'><path d='M0 0l5 6 5-6z' fill='%23A65A29'/></svg>");
  background-repeat: no-repeat; background-position: right 12px center;
  transition: border-color 120ms ease;
}
.dv-pick-select:hover:not(:disabled), .dv-pick-select:focus { border-color: var(--dv-copper); }
/* Suppress the native outline only for mouse/pointer focus - a bare
   `:focus { outline: none }` would win over the shared list's
   `.dv-root .dv-pick-select:focus-visible` rule above regardless of source
   order (both land at the same (0,3,0) specificity), silently eating the
   keyboard focus ring. Scoping to :not(:focus-visible) makes the two rules
   mutually exclusive instead of competing. */
.dv-pick-select:focus:not(:focus-visible) { outline: none; }
.dv-pick-select:disabled { opacity: 0.4; cursor: default; }
.dv-pick-select option { text-transform: none; }
/* Collapsed until Y/M/M chosen (.dv-ready) - a reveal, not a disabled block.
   visibility (delayed on the way out, instant on the way in) keeps the
   button out of tab order while collapsed.
   height/padding/margin-top are fixed at all times (not animated between a
   collapsed and expanded box) - this button only fades in/out via opacity
   now. It used to animate max-height 0 -> 44px, but pointer-events flips to
   `auto` the instant .dv-ready is added (pointer-events isn't animatable,
   so there's no way to delay it), while the box itself was still mid-grow
   from ~0px tall for another 260ms. A visitor who clicked at the
   coordinates the fully-expanded button was ABOUT to occupy could have that
   click fall through to whatever sat beneath it (e.g. a page-nav link)
   for the whole growth animation, because the button's actual hit-test
   rectangle hadn't reached those coordinates yet (Task 7/C9). Keeping the
   box's footprint constant removes that window entirely - only opacity/
   visibility change, so the hit-test rectangle never moves.
   display is ALSO toggled by the same .dv-ready class, alongside
   pointer-events - not a regression back toward the old race. display,
   like pointer-events, is a discrete (non-animatable) property: it is
   either `none` or `block`, with no mid-transition value, so it flips
   atomically in the same style recalculation as pointer-events. There is
   no instant where display has updated but pointer-events hasn't (or vice
   versa), so this cannot reopen the click-through window - it just also
   removes the button from layout entirely while collapsed, instead of
   reserving its ~52px box (height + padding + margin-top) as dead space
   before the picker is ready. Only opacity/visibility remain actually
   transitioned. */
.dv-root .dv-pick-go {
  display: none; width: 100%; background: var(--dv-copper); color: #fff;
  border: none; border-radius: 5px; font-family: var(--dv-font-head);
  font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; cursor: pointer;
  height: 44px; padding: 12px; margin-top: 8px; opacity: 0; pointer-events: none;
  visibility: hidden;
  /* display is delayed the same 200ms as visibility on the way out, so
     removing .dv-ready doesn't cut the opacity fade short with an instant
     snap to display:none - both flip together only once the fade finishes.
     .dv-ready's own transition-delay: 0s makes the way IN instant for all
     three, same as before. */
  transition: opacity 200ms ease, visibility 0s linear 200ms, display 0s linear 200ms;
}
.dv-root .dv-pick-go.dv-ready {
  display: block; opacity: 1; pointer-events: auto; visibility: visible; transition-delay: 0s;
}
.dv-root .dv-pick-go:hover { background: var(--dv-copper-deep); }
.dv-root .dv-pick-use-btn {
  background: #fff; color: #1c1c1c; border: none; border-radius: 5px;
  padding: 10px 11px; font-family: var(--dv-font-head); font-size: 12px;
  letter-spacing: 0.02em; text-transform: uppercase; text-align: left; cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
.dv-root .dv-pick-use-btn:hover { background: var(--dv-copper); color: #fff; }
.dv-pick-zip {
  width: 100%; padding: 10px 11px; font-size: 12.5px;
  border: 1.5px solid transparent; border-radius: 5px;
  background: #fff; color: #1c1c1c; font-family: var(--dv-font-head);
  letter-spacing: 0.06em; transition: border-color 120ms ease;
}
.dv-pick-zip:hover, .dv-pick-zip:focus { border-color: var(--dv-copper); }
/* Same rationale as .dv-pick-select's :not(:focus-visible) rule above -
   a bare `:focus { outline: none }` would tie in specificity with the
   shared `.dv-root .dv-pick-zip:focus-visible` rule and silently eat the
   keyboard focus ring depending on source order. Scoping to
   :not(:focus-visible) makes the two mutually exclusive instead. */
.dv-pick-zip:focus:not(:focus-visible) { outline: none; }
@media (prefers-reduced-motion: reduce) {
  .dv-pick-wrap { animation: none; }
  .dv-root .dv-pick-go { transition: none; }
  .dv-pick-zip { transition: none; }
}
