/**
 * DealerPro Elite — Reusable Components
 *
 * Buttons, cards, forms, badges, chips, breadcrumbs, trust strip, animations.
 * Loaded after layout.css.
 *
 * @package DealerPro_Elite
 * @since 2.0.0
 */

/* ─── ICON SPRITE — Lucide symbols don't ship with stroke/fill attrs,
       so force currentColor here. Without this, icons render as invisible
       black-on-black squares inside their tiles. */
.dpelite-icon,
svg.dpelite-icon {
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  flex-shrink: 0;
}
.dpelite-icon * {
  stroke: currentColor;
  fill: none;
}

/* ─── PROCESS-GRID / PROCESS-CARD ───────────────────────
 * Canonical 3-step (or 6 with `--6` modifier) flow used by shipping,
 * trade-in, sell-with-us, buy-now. Was previously redefined in shipping.css
 * with subtle drift in num size and connector line. Consolidated here. */
.process-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-5);
  position: relative;
}
.process-grid::before {
  /* Decorative connector line behind the num bubbles. Suppressed on the
     6-step variant (multi-row, the line wouldn't make sense). */
  content: '';
  position: absolute;
  top: 32px;
  left: 16.66%;
  right: 16.66%;
  height: 2px;
  background: var(--border);
  z-index: 0;
}
.process-grid--4 {
  grid-template-columns: repeat(4, 1fr);
}
.process-grid--4::before {
  /* Connector line spans 4 columns instead of 3 — adjust insets so the line
     sits between the first and last bubble centers. */
  left: 12.5%;
  right: 12.5%;
}
.process-grid--6 {
  grid-template-columns: repeat(3, 1fr);
  row-gap: var(--s-5);
}
.process-grid--6::before { display: none; }
.process-card {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--s-7) var(--s-5);
  text-align: center;
  position: relative;
  z-index: 1;
  transition: var(--transition);
}
.process-card:hover {
  border-color: var(--accent-border);
  transform: translateY(-2px);
  /* Match the shadow-on-hover treatment used by sibling display cards
     (.value-card / .why-card / .path-card) — without the elevation cue
     the lift was a silent 2px shift with no depth. */
  box-shadow: var(--shadow-md);
}
.process-card__num {
  width: 64px;
  height: 64px;
  border-radius: var(--r-full);
  background: var(--accent);
  color: var(--accent-fg);
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto var(--s-5);
  font-family: var(--font-mono);
  font-size: var(--t-xl);
  font-weight: 800;
  border: 4px solid var(--bg-1);
  box-shadow: 0 0 0 4px var(--accent-soft);
}
.process-card__title {
  font-size: var(--t-md);
  font-weight: 800;
  color: var(--text);
  margin: 0 0 var(--s-2);
}
.process-card__desc {
  font-size: var(--t-sm);
  color: var(--text-soft);
  line-height: 1.55;
  margin: 0;
}
@media (max-width: 1024px) {
  .process-grid { grid-template-columns: repeat(2, 1fr); }
  .process-grid::before { display: none; }
}
@media (max-width: 540px) {
  .process-grid { grid-template-columns: 1fr; }
}

/* ─── BOTTOM-CTA ──────────────────────────────────────
 * Canonical "final CTA" strip used by homepage, inventory archive, single
 * listing, faq, contact, about. Was previously redefined in 4
 * stylesheets with different padding/background/grid — homepage had the
 * radial-gradient look, inventory was flat, single-listing targeted raw
 * h2/p elements instead of BEM children. Consolidated here. */
.bottom-cta {
  background:
    radial-gradient(ellipse 60% 100% at 100% 50%, var(--accent-soft), transparent 70%),
    var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  padding: var(--s-10);
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--s-8);
  align-items: center;
}
.bottom-cta__title {
  font-size: var(--t-section-title);
  font-weight: 800;
  letter-spacing: var(--ls-display);
  color: var(--text);
  margin: var(--s-2) 0 var(--s-3);
}
.bottom-cta__sub {
  font-size: var(--t-sm);
  color: var(--text-soft);
  line-height: 1.55;
  max-width: 560px;
}
.bottom-cta__buttons {
  display: flex;
  gap: var(--s-3);
  flex-wrap: wrap;
  flex-shrink: 0;
}
@media (max-width: 900px) {
  .bottom-cta {
    grid-template-columns: 1fr;
    padding: var(--s-7);
    text-align: center;
  }
  .bottom-cta__sub { margin-left: auto; margin-right: auto; }
  .bottom-cta__buttons { justify-content: center; }
}
@media (max-width: 720px) {
  .bottom-cta {
    padding: var(--s-6);
  }
  .bottom-cta__buttons { width: 100%; }
  .bottom-cta__buttons .btn { flex: 1; min-width: 0; }
}

/* ─── DPELITE FORM PRIMITIVES (shared across all forms — trade-in,
       contact, consignment, sell-with-us, buy-now, shipping).
       These were previously undefined which made forms render as plain
       stacked inputs with no spacing, no row layout, and broken submit. */
.dpelite-form {
  display: grid;
  gap: var(--s-4);
}
.dpelite-form__row {
  display: grid;
  gap: var(--s-4);
}
.dpelite-form__row--2col {
  grid-template-columns: 1fr 1fr;
}
.dpelite-form__row--3col {
  grid-template-columns: repeat(3, 1fr);
}
@media (max-width: 720px) {
  .dpelite-form__row--2col,
  .dpelite-form__row--3col { grid-template-columns: 1fr; }
}
/* Wrapper-style labels (the <label> contains its <input>/<textarea>/<select>
   inside it) get a grid layout so label-text + control stack with a small gap.
   Standalone labels (used with `for=` and a sibling input — like the contact
   form) MUST NOT inherit display: grid because the ::after asterisk would
   become a grid item and drop to a new line under the label text. We
   restrict the grid layout to labels that actually wrap a control. */
/* `:where()` zeroes out the specificity of the inner :has() argument so this
   rule sits at single-class specificity (0,1,0). Without this wrapper, the
   `:has()` pseudo's argument inherits its specificity from the most-specific
   element selector inside (an element selector adds +0,0,1 — small, but
   enough that the rule unexpectedly outranked single-class modifiers like
   `.form-field--consent`. Recently fixed by bumping that modifier with
   compound `.form-field.form-field--consent`; this is the cleaner upstream
   fix that prevents future modifiers from running into the same trap. */
.dpelite-form :where(label:has(> input, > textarea, > select)) {
  display: grid;
  gap: var(--s-2);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-muted);
}
.dpelite-form label > span:first-child {
  display: block;
}
/* Belt-and-suspenders: even on browsers that don't support :has, force the
   required-asterisk pseudo to render inline so it never stacks below the
   label text. */
.form-label--required::after {
  display: inline;
}
.dpelite-form label em {
  color: var(--accent);
  font-style: normal;
  font-weight: 700;
}
.dpelite-form__submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  margin-top: var(--s-3);
  position: relative;
  min-height: 56px;
}
/* Compound selector outranks `.btn { font-weight: 600 }` (declared later in
   this file at the same specificity, so plain `.dpelite-form__submit` would
   silently lose). Bumping to (0,2,0) wins cleanly. */
.btn.dpelite-form__submit {
  font-weight: 700;
}
/* Full-width submit on mobile so thumb-tap targets the whole bottom of the
   form rather than a centered 200px button. Desktop keeps the natural width. */
@media (max-width: 720px) {
  .dpelite-form__submit { width: 100%; }
}
.dpelite-form__submit-spinner {
  display: none;
  width: 16px;
  height: 16px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: btn-spin 700ms linear infinite;
}
.dpelite-form.is-loading .dpelite-form__submit-spinner { display: inline-block; }
.dpelite-form.is-loading .dpelite-form__submit-label { opacity: 0.5; }
.dpelite-form__trust {
  font-size: var(--t-xs);
  color: var(--text-muted, var(--text-3));
  text-align: center;
  margin: var(--s-3) 0 0;
}

/* "What happens next" 3-step journey timeline — rendered above the
   submit button on contact / listing-inquiry / trade-in / consignment
   forms. Answers the user's "is this going to ghost me?" friction.
   Reusable component — same selectors across all 4 form templates. */
.dpelite-form__journey {
  list-style: none;
  margin: var(--s-6) 0 var(--s-5);
  padding: var(--s-5) var(--s-5) var(--s-4);
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  display: grid;
  gap: var(--s-3);
  /* Subtle accent bar on the left edge — frames the timeline as a
     "next steps" container, not just a list of bullets. */
  border-left: 3px solid var(--accent);
}
.dpelite-form__journey-item {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  padding: 0;
}
.dpelite-form__journey-num {
  /* Step circle — same scale as buy-now stepper for visual continuity
     across the site. Accent color so the eye reads "this is step N". */
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border-radius: var(--r-full);
  background: var(--accent-soft);
  border: 1px solid var(--accent-border);
  color: var(--accent);
  font-size: var(--t-sm);
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-variant-numeric: tabular-nums;
}
.dpelite-form__journey-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  font-size: var(--t-sm);
  line-height: 1.5;
}
.dpelite-form__journey-text strong {
  color: var(--text);
  font-weight: 600;
}
.dpelite-form__journey-meta {
  font-size: var(--t-xs);
  color: var(--text-muted);
  font-weight: 400;
}
.dpelite-form__commission-preview {
  background: var(--accent-soft);
  border: 1px solid var(--accent-border);
  border-radius: var(--r-sm);
  padding: var(--s-4);
  font-size: var(--t-sm);
  color: var(--text);
  margin-bottom: var(--s-3);
}
.dpelite-form__turnstile {
  margin: var(--s-3) 0;
  display: flex;
  justify-content: center;
}

/* ─── COMMISSION-INFO (sell-with-us "How the X% commission works" card)
   Redesigned May 2026 — the previous version stretched the table to
   100% of a 1200px+ wrap-card, leaving 60% empty middle space between
   labels and values + a stray yellow underline floating mid-row
   (border-bottom on td:last-child only). New treatment: centered
   max-width column, denser rows, and a subtle separator stack that
   reads like a vertical math equation rather than a wide spreadsheet. */
.commission-info {
  background: var(--bg-2);
  border: 1px solid var(--accent-border);
  border-radius: var(--r-lg);
  padding: var(--s-8) var(--s-7);
  margin-top: var(--s-6);
  /* Centered inner layout — see __example block. Card itself stays
     full-width so the surrounding `.wrap-card` chrome wraps the
     centered example uniformly. */
}
.commission-info__example {
  /* Cap the visual width so labels + values sit close — no awkward
     mid-row gap on wide screens. 460px is wide enough for a $999,999
     price + a multi-word label without truncation. */
  max-width: 460px;
  margin: 0 auto;
  border-collapse: collapse;
}
.commission-info__example th,
.commission-info__example td {
  padding: var(--s-4) var(--s-2);
  font-size: var(--t-sm);
  color: var(--text);
  /* Move the divider to the ROW level so it spans label + value
     cleanly. Per-td borders left a stub line under the value column
     and visually fragmented the row. */
  border-bottom: 1px solid var(--border);
}
.commission-info__example th {
  text-align: left;
  font-size: var(--t-xs);
  text-transform: uppercase;
  letter-spacing: 1.5px;
  color: var(--text-muted, var(--text-3));
  font-weight: 700;
  width: 60%;
}
.commission-info__example td {
  text-align: right;
  font-family: var(--font-mono);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.commission-info__total th,
.commission-info__total td {
  /* Final-row emphasis — drop the border, lift to a heavier total line
     above, accent the payout amount. Reads like a checkout receipt's
     "Total" row. */
  border-bottom: 0;
  border-top: 2px solid var(--accent);
  padding-top: var(--s-5);
}
.commission-info__total th {
  color: var(--text);
  font-size: var(--t-sm);
  letter-spacing: 1.5px;
  font-weight: 800;
}
.commission-info__total td {
  color: var(--accent);
  font-size: var(--t-xl);
  font-weight: 800;
}
.commission-info__note {
  font-size: var(--t-xs);
  color: var(--text-muted, var(--text-3));
  margin: var(--s-5) auto 0;
  max-width: 460px;
  text-align: center;
  font-style: italic;
}

/* ─── .sr-only / .screen-reader-text — visually hidden but reachable by
       screen readers + Tab. Used for status announcements, descriptive
       labels for icon buttons, and step indicators that should remain in
       the accessibility tree. The .screen-reader-text alias matches the
       WordPress core convention so theme code can use either. */
.sr-only,
.screen-reader-text {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ─── SKIP-TO-CONTENT (a11y) — visually hidden until focused. */
.skip-to-content,
.skip-link {
  position: absolute;
  top: -100px;
  left: var(--s-4);
  z-index: 100000;
  padding: var(--s-3) var(--s-5);
  background: var(--accent);
  color: var(--accent-fg);
  font-weight: 700;
  border-radius: var(--r-sm);
  text-decoration: none;
  transition: top var(--transition);
}
.skip-to-content:focus,
.skip-link:focus {
  top: var(--s-3);
  outline: 2px solid var(--text);
  outline-offset: 2px;
}

/* ─── WHY-GRID / WHY-CARD ──────────────────────────────
 * Canonical 4-feature trust grid used by homepage, about,
 * shipping, contact, sell-with-us, trade-in. Was previously redefined in
 * 4 stylesheets with different padding/icon-size/radius — visible drift
 * across pages. Consolidated here. */
.why-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--s-5);
}
.why-card {
  background: var(--bg-2);
  border: 1px solid var(--border);
  /* Harmonized to --r-md (14px) to match value-card / industry-card /
     process-card / check-card display-card family. Was --r-lg (20px) which
     made why-card visually heavier than its siblings on the same surface. */
  border-radius: var(--r-md);
  padding: var(--s-7) var(--s-6);
  text-align: center;
  position: relative;
  overflow: hidden;
  transition: var(--transition);
}
.why-card:hover {
  border-color: var(--accent-border);
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}
.why-card__icon {
  /* Bumped from 52px to 64px to match .value-card and .process-card display
     icon weight. 52px read as compact secondary, but why-card carries hero
     trust-signal weight on the homepage and About page. */
  width: 64px;
  height: 64px;
  border-radius: var(--r-md);
  background: var(--accent-soft);
  border: 1px solid var(--accent-border);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto var(--s-5);
}
.why-card__icon svg {
  width: 28px;
  height: 28px;
}
.why-card__title {
  font-size: var(--t-md);
  margin-bottom: var(--s-3);
}
.why-card__desc {
  font-size: var(--t-sm);
  color: var(--text-soft);
  line-height: 1.6;
}
@media (max-width: 1024px) {
  .why-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 540px) {
  .why-grid { grid-template-columns: 1fr; }
}

/* ─── PAGE HERO (shared across all static pages) ──────── */
.page-hero {
  position: relative;
  padding: var(--s-12) 0 var(--s-10);
  text-align: center;
  isolation: isolate;
}
/* Tighten the hero on phones — was 96/80px above & below, eating most
   of the first viewport before the actual content. 56/48 leaves room
   for kicker + h1 + sub above the fold. */
@media (max-width: 720px) {
  .page-hero { padding: var(--s-7) 0 var(--s-6); }
}
.page-hero::before {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse 900px 500px at 50% 0%, var(--accent-glow, rgba(251, 191, 36, 0.08)), transparent 70%);
  z-index: -1;
}
.page-hero__title {
  font-size: var(--t-page-hero);
  font-weight: 800;
  letter-spacing: var(--ls-display);
  line-height: var(--lh-tight);
  margin: var(--s-4) 0 var(--s-5);
  /* Avoid orphan words on narrow viewports ("Inventory" alone on line 2).
     Modern engines redistribute words for visual balance — graceful
     degradation: older browsers render the same wrap as before. */
  text-wrap: balance;
}
.page-hero__title em {
  color: var(--accent);
  font-style: normal;
}
.page-hero__sub {
  font-size: var(--t-md);
  color: var(--text-soft, var(--text-2));
  max-width: 640px;
  margin: var(--s-5) auto var(--s-7);
  line-height: 1.6;
}
/* Mobile-specific reading width — caps to ~32ch so the subhead doesn't
   spread to 4 dense lines edge-to-edge on a 360-393px viewport. The
   `pretty` text-wrap improves the line-break logic on supporting
   browsers (Chrome 117+, Firefox 121+) so the last line isn't a stub. */
@media (max-width: 720px) {
  .page-hero__sub {
    max-width: 32ch;
    text-wrap: pretty;
  }
}
.page-hero__cta {
  display: flex;
  gap: var(--s-3);
  justify-content: center;
  flex-wrap: wrap;
  margin-bottom: var(--s-7);
}
.page-hero__pills {
  display: flex;
  gap: var(--s-3);
  justify-content: center;
  flex-wrap: wrap;
}

/* ─── ANIMATIONS (KEYFRAMES) ─────────────────────────────── */
@keyframes pulse-dot {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.6; transform: scale(0.85); }
}

@keyframes heart-pulse {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.25); }
  70%  { transform: scale(0.95); }
  100% { transform: scale(1); }
}

@keyframes fade-in-up {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes glow-pulse {
  0%, 100% { box-shadow: 0 0 0 var(--accent-glow); }
  50%      { box-shadow: 0 0 24px var(--accent-glow); }
}

@keyframes btn-spin {
  to { transform: rotate(360deg); }
}

/* Respect reduced-motion preference globally for animations defined here */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* ─── ACCESSIBILITY UTILITIES ────────────────────────────── */
/* Visually hidden but available to assistive tech */
.sr-only {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Universal focus ring for all interactive elements */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--r-xs);
}

/* ─── BUTTONS ────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  padding: 14px 26px;
  border-radius: var(--r-sm);
  font-size: var(--t-sm);
  font-weight: 600;
  letter-spacing: 0.3px;
  line-height: 1.2;
  transition: var(--transition);
  white-space: nowrap;
  position: relative;
  cursor: pointer;
  border: 1px solid transparent;
  text-decoration: none;
  font-family: inherit;
  /* Eliminate the 300ms double-tap-zoom delay on iOS Safari. Without this,
     buttons feel sluggish on first tap. `manipulation` allows pinch-zoom
     on the page itself but disables tap-zoom on this element. */
  touch-action: manipulation;
  /* Defensive overflow: when the button content (icon + label + phone)
     is wider than its flex container, clip + ellipsis instead of pushing
     past the button edge. Dealer report v2.0.35: text was visibly
     escaping the button on narrow viewports. */
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Phone number span inside .btn--call — hidden on small phones so the
   button content stays "📞 Call Us" (fits) rather than
   "📞 Call Us (909) 403-4890" (overflows). On desktop both render. */
.btn__phone {
  font-weight: 700;
  opacity: 0.95;
  white-space: nowrap;
}
@media (max-width: 480px) {
  .btn__phone { display: none; }
}

/* Apply touch-action: manipulation broadly to tappable controls. Same rationale
   as .btn — kills the legacy 300ms tap delay on iOS WebKit. */
.icon-btn,
.nav-toggle,
.mobile-drawer__close,
.mobile-drawer__link,
.gallery__nav,
.gallery__share,
.card__save,
.card__qv-trigger,
.page-btn,
.filters__clear,
.faq-item__q,
.lightbox__nav,
.lightbox__close,
.tab-btn { touch-action: manipulation; }

/* Gallery scroll axis hints — let the browser commit to the native scroll
   direction without waiting to disambiguate, so swipe handlers don't fight
   the scroller. */
.gallery__main { touch-action: pan-y; }
.gallery__thumbs { touch-action: pan-x; }

/* Multi-step buy-now form: prevent Android Chrome pull-to-refresh from
   accidentally reloading and wiping mid-form input on Step 2/3/4 scroll. */
[data-dpelite-form="buynow"] { overscroll-behavior-y: contain; }

.btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
/* Yellow accent fill needs a contrasting focus ring (yellow-on-yellow is
   invisible). Override on solid-yellow surfaces. */
.btn--primary:focus-visible {
  outline-color: var(--text);
}

/* Tactile press feedback for touch devices — `tap-highlight-color: transparent`
   removes the system feedback, so without this `:active` rule taps look dead.
   The transform: none in reduced-motion blocks this — tapping still works,
   just without the press animation. */
.btn:active {
  transform: translateY(0) !important;
  filter: brightness(0.92);
}

.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.45;
  pointer-events: none;
  cursor: not-allowed;
}

/* Shared :active + :focus-visible regime for the non-`.btn` button family
   (icon buttons, gallery nav, save heart, pagination, FAQ trigger,
   filter clear, review-item edit). Without this, only `.btn` got the
   tactile press feedback and a visible focus ring; the rest looked dead
   on tap and were unreachable for keyboard users with no visible
   indicator. */
.icon-btn:active,
.gallery__nav:active,
.gallery__share:active,
.card__save:active,
.card__qv-trigger:active,
.page-btn:active,
.review-item__edit:active,
.filters__clear:active {
  transform: scale(0.97);
  filter: brightness(0.92);
}
.icon-btn:focus-visible,
.gallery__nav:focus-visible,
.gallery__share:focus-visible,
.card__save:focus-visible,
.card__qv-trigger:focus-visible,
.page-btn:focus-visible,
.review-item__edit:focus-visible,
.filters__clear:focus-visible,
.dpelite-form__submit:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
/* Disabled-state parity for non-.btn buttons. */
.icon-btn:disabled,
.icon-btn[aria-disabled="true"],
.page-btn:disabled,
.page-btn[aria-disabled="true"],
.dpelite-form__submit:disabled {
  opacity: 0.45;
  pointer-events: none;
  cursor: not-allowed;
}

/* Primary — accent fill, with shine on hover */
.btn--primary {
  background: var(--accent);
  color: var(--accent-fg);
  overflow: hidden;
}
.btn--primary:hover {
  background: var(--accent-dark);
  transform: translateY(-2px);
  box-shadow: var(--shadow-glow);
}
.btn--primary::after {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
  transition: left 600ms ease;
  pointer-events: none;
}
.btn--primary:hover::after { left: 100%; }

/* Call — high-attention CTA (red), reserved for `tel:` actions only.
   `.btn--secondary` was previously aliased here so non-call actions on
   404/500 pages rendered as red phone-style buttons. Now `.btn--secondary`
   renders as a calmer ghost-style button (use `.btn--call` only when the
   href is a tel: link with a phone icon). */
.btn--call {
  background: var(--cta);
  color: var(--cta-fg);
  box-shadow: var(--shadow-cta);
}
.btn--call:hover {
  background: var(--cta-dark);
  transform: translateY(-2px);
}
.btn--secondary {
  background: rgba(255, 255, 255, 0.04);
  color: var(--text);
  border: 1px solid var(--border-strong);
}
.btn--secondary:hover {
  background: rgba(255, 255, 255, 0.08);
  border-color: var(--accent-border);
}

/* Ghost — subtle outlined button */
.btn--ghost {
  background: rgba(255, 255, 255, 0.04);
  color: var(--text);
  border: 1px solid var(--border-strong);
}
.btn--ghost:hover {
  background: rgba(255, 255, 255, 0.08);
  border-color: var(--accent-border);
}

/* Sizes */
.btn--lg        { padding: 18px 32px; font-size: var(--t-base); }
/* WCAG 2.5.5 Target Size — mobile tap targets ≥ 44×44. Was 40px (auditor flagged). */
.btn--sm        { padding: 12px 18px; font-size: var(--t-xs); min-height: 44px; }

/* Loading state — spinner appended to right of label */
.btn.is-loading {
  pointer-events: none;
  opacity: 0.75;
  cursor: wait;
}
.btn.is-loading::after {
  content: '';
  display: inline-block;
  width: 14px;
  height: 14px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: btn-spin 700ms linear infinite;
  margin-left: 8px;
  vertical-align: middle;
}

/* ─── KICKER (eyebrow heading) ──────────────────────────── */
.kicker {
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: 2.5px;
  text-transform: uppercase;
  color: var(--accent);
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
}
.kicker::before {
  content: '';
  width: 28px;
  height: 1px;
  background: var(--accent);
}
.kicker--centered::after {
  content: '';
  width: 28px;
  height: 1px;
  background: var(--accent);
}
@media (max-width: 1024px) {
  .kicker::after {
    content: '';
    width: 28px;
    height: 1px;
    background: var(--accent);
  }
}

/* ─── BADGES / PILLS / CHIPS / TAGS ─────────────────────── */
/* .badge — small, square-ish indicator (e.g. on cards) */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  background: var(--accent);
  color: var(--accent-fg);
  border-radius: var(--r-xs);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 1.5px;
  text-transform: uppercase;
}
.badge--cta     { background: var(--cta); color: var(--cta-fg); }
.badge--success { background: var(--success); color: var(--accent-fg); }
/* .pill — fully rounded version of badge, used for trust items */
.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: 8px 14px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--border);
  border-radius: var(--r-full);
  font-size: var(--t-sm);
  color: var(--text-soft);
}
.pill svg {
  width: 14px;
  height: 14px;
  color: var(--success);
}
/* Mockup parity: every trust pill in a hero gets a green check icon. We add
   it via ::before so templates that don't render their own <svg> still pick
   up the icon automatically. The :has() check skips ::before when the
   template already inlined an SVG, preventing duplicate icons.
   Wrapped in @supports because Safari < 15.4 / Firefox < 121 don't support
   :has() and would render the entire selector as invalid (good — falls back
   to no auto-icon, better than duplicate icons). */
@supports selector(:has(*)) {
  .pill:not(:has(> svg))::before {
    content: "";
    width: 14px;
    height: 14px;
    flex-shrink: 0;
    background-color: var(--success);
    -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'><polyline points='20 6 9 17 4 12'/></svg>");
            mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'><polyline points='20 6 9 17 4 12'/></svg>");
    -webkit-mask-size: contain;
            mask-size: contain;
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
    -webkit-mask-position: center;
            mask-position: center;
  }
}

/* .chip — removable filter token (used in inventory active-filters) */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: 6px 12px;
  background: var(--accent-soft);
  border: 1px solid var(--accent-border);
  border-radius: var(--r-full);
  font-size: var(--t-xs);
  color: var(--accent);
  font-weight: 600;
  transition: var(--transition);
  cursor: pointer;
}
.chip:hover { background: var(--accent-glow); }
.chip:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.chip__close {
  /* WCAG 2.5.5 Target Size — was 14×14px (audit flagged). Visual icon
     stays small but the touch target expands via padding so the whole
     button reaches the 24×24 minimum recommended for inline UI (the
     full 44×44 enforced spec is only for primary CTAs; in-list pill
     close-buttons get the 24×24 relaxation when separated from other
     targets by 24px+). The .chip itself provides additional surrounding
     spacing. */
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--accent-border);
  color: var(--accent);
  font-size: 14px;
  line-height: 1;
  transition: var(--transition);
  /* Ensure no surrounding clickable swallows the press. */
  flex-shrink: 0;
}
.chip:hover .chip__close {
  background: var(--accent);
  color: var(--accent-fg);
}

/* ─── EMPTY STATE ──────────────────────────────────────
 * Canonical no-results / not-found pattern used by inventory archive,
 * search results, and (future) any other "no content" view. Was previously
 * defined only in inventory.css → search.php had unstyled blow-up SVG. */
.empty-state {
  /* Required so the AJAX in-flight `.is-loading::after` spinner overlay
     positions against this container (it uses position: absolute + 50%/50%).
     Without this, the spinner anchors to body and shows off-center on
     filter refinement from a 0-results state. */
  position: relative;
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  padding: var(--s-16) var(--s-8);
  text-align: center;
  max-width: 720px;
  margin: var(--s-7) auto;
}
.empty-state__icon {
  width: 96px;
  height: 96px;
  border-radius: var(--r-full);
  background: var(--accent-soft);
  border: 1px solid var(--accent-border);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto var(--s-6);
}
.empty-state__icon svg {
  width: 44px;
  height: 44px;
}
.empty-state__title {
  font-size: var(--t-section-title-sm);
  margin-bottom: var(--s-3);
  letter-spacing: var(--ls-display);
  line-height: var(--lh-snug);
}
.empty-state__sub {
  font-size: var(--t-base);
  color: var(--text-soft);
  line-height: var(--lh-relaxed);
  max-width: 480px;
  margin: 0 auto var(--s-7);
}
.empty-state__actions {
  display: flex;
  gap: var(--s-3);
  justify-content: center;
  flex-wrap: wrap;
}
/* Per-filter "remove this" chips on the filtered-empty branch (no results
   match active filters). Renders between the description and the action
   buttons. The chips reuse the existing `.chip` style from the inventory
   filter rail so visual language matches what the user already saw at
   the top of the page when applying filters. */
.empty-state__chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: var(--s-2);
  max-width: 720px;
  margin: 0 auto var(--s-7);
}
.empty-state__chips-lbl {
  font-size: var(--t-xs);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 1.5px;
  font-weight: 600;
  margin-right: var(--s-2);
}
@media (max-width: 540px) {
  .empty-state { padding: var(--s-10) var(--s-5); }
  .empty-state__actions {
    flex-direction: column;
    align-items: stretch;
  }
  .empty-state__chips-lbl {
    width: 100%;
    text-align: center;
    margin-right: 0;
    margin-bottom: var(--s-2);
  }
}

/* .tag — alias for badge--ghost, neutral inline label */
.tag {
  display: inline-flex;
  align-items: center;
  padding: 3px 8px;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--r-xs);
  font-size: var(--t-xs);
  color: var(--text-soft);
  font-weight: 600;
}

/* The generic `.card` block (with __header/__body/__footer) was removed
   — it collided with the listing-card `.card` defined further below at
   line ~1440. No template uses `.card__header` or `.card__footer`. */

/* ─── FORMS ──────────────────────────────────────────────── */
.form-grid  { display: grid; gap: var(--s-4); }
.form-row   { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-4); }
.form-group { display: grid; gap: var(--s-2); }
.form-field { display: grid; gap: var(--s-2); }

/* Consent-row modifier: GDPR/Turnstile checkbox rows that sit inline with
   their label. Replaces 5 identical inline-style blocks across the form parts
   (contact / trade-in / consignment / listing-inquiry).
   Compound selector boosts specificity over the more specific
   `.dpelite-form label:has(> input)` rule that would otherwise force grid. */
.form-field.form-field--consent {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  margin-top: var(--s-3);
}

.form-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1.5px;
  color: var(--text-muted);
  font-weight: 600;
}
/* Visual required marker — pairs with the HTML `required` attribute so
   sighted users can scan a form for required fields without focusing each one
   first. SR users get the "required" announcement from `aria-required`.

   Switched from `*` (May 2026) to inline "(required)" text. The asterisk
   relied on convention many users either don't know or have to mentally
   translate ("does that mean must-fill or footnote?"). Plain English
   removes the question. Muted color + small size keeps it from competing
   with the label itself. */
.form-label--required::after {
  content: ' (required)';
  color: var(--text-muted);
  font-size: 0.78em;
  font-weight: 400;
  font-style: italic;
  margin-left: 4px;
  letter-spacing: 0.02em;
  /* Keep on the same baseline as the label — `text-bottom` looked OK
     in Chrome but hung below the label in Safari without explicit align. */
  vertical-align: baseline;
}

/* Inline "(optional)" tag — mirror of the required hint above, used on
   fields that are deliberately blank-friendly (e.g. message textarea on
   contact form). Same visual treatment so the label stays readable. */
.form-label__optional {
  color: var(--text-muted);
  font-size: 0.78em;
  font-weight: 400;
  font-style: italic;
  letter-spacing: 0.02em;
  vertical-align: baseline;
  margin-left: 2px;
}

.form-help {
  font-size: var(--t-xs);
  color: var(--text-muted);
}

.form-input,
.form-textarea,
.form-select,
input[type="text"],
input[type="email"],
input[type="tel"],
input[type="url"],
input[type="number"],
input[type="search"],
input[type="password"],
textarea,
select {
  background: var(--bg-3);
  /* v2.0.81 — switched from --border-strong (12% white = 1.43:1 vs
     bg-3) to --form-input-border (40% white = 3.84:1) so low-vision
     users can perceive field boundaries on dark forms. WCAG 1.4.11
     non-text contrast. */
  border: 1px solid var(--form-input-border);
  border-radius: var(--r-sm);
  padding: 14px 16px;
  font-size: var(--t-sm);
  line-height: 1.2;
  color: var(--text);
  transition: var(--transition);
  font-family: inherit;
  width: 100%;
}

/* Reset native fieldset/legend styling — used by trade-in / consignment forms.
   Without this, the browser draws a 1px gray border + offset legend that
   looks completely unstyled vs. the rest of the theme. */
.dpelite-form__group,
fieldset.dpelite-form__group {
  border: 0;
  margin: 0 0 var(--s-6);
  padding: 0;
  display: grid;
  gap: var(--s-4);
}
.dpelite-form__group > legend,
fieldset.dpelite-form__group > legend {
  padding: 0;
  margin-bottom: var(--s-3);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--accent);
  width: 100%;
  border-bottom: 1px solid var(--border);
  padding-bottom: var(--s-3);
}
/* Required asterisks on form labels — match brand voice. */
.dpelite-form label > em,
.dpelite-form-required {
  color: var(--accent);
  font-style: normal;
  font-weight: 700;
  margin-left: 2px;
}

/* Invalid state for required fields that fail validation. */
.form-input.is-invalid,
.form-textarea.is-invalid,
.form-select.is-invalid,
input.is-invalid,
textarea.is-invalid,
select.is-invalid {
  border-color: var(--cta);
  box-shadow: 0 0 0 3px var(--cta-soft);
}

/* Per-field error message — injected by form-validation.js after each
   invalid input. Used as the target of aria-describedby so screen readers
   announce the specific reason instead of a single mashed-up banner. */
.field-error {
  display: block;
  margin-top: var(--s-2);
  /* v2.1.3 — lighten the error TEXT for contrast (was --cta #dc2626 ≈ 4.0:1 on
     dark, just under AA). A lighter tint of the brand red clears 4.5:1 while
     staying rebrandable; old browsers fall back to the solid --cta. */
  color: var(--cta);
  color: color-mix(in srgb, var(--cta) 55%, white);
  font-size: var(--t-xs);
  line-height: 1.4;
}
.field-error[hidden] { display: none; }

/* Reveal-on-scroll feature removed (May 2026) — dealer chose simplicity
   over the staggered-fade animation. The matching JS (initScrollReveal in
   main.js) was deleted at the same time. If a future iteration needs it
   back, restore the [data-reveal] / .is-revealed pair plus the JS observer. */

/* Field-error gets a leading "⚠ " glyph so red-blind users still see
   the warn signal (WCAG 1.4.1 Use of Color). */
.field-error::before {
  content: "⚠ ";
  margin-right: 0.25em;
}

/* ─── FORCED-COLORS / High-Contrast Mode (Windows) ───────
   Most of our visual hierarchy uses `box-shadow` for focus rings and
   `background-color` for state — both are stripped or remapped by the
   forced-colors backstop. This block ensures interactive elements stay
   perceivable: explicit `outline` for focus, explicit `border` for
   buttons / inputs / cards, and `forced-color-adjust:none` only where
   we need to preserve the system colors WP/Windows substitutes. */
@media (forced-colors: active) {
  *:focus-visible {
    outline: 2px solid Highlight !important;
    outline-offset: 2px !important;
  }
  .btn,
  .btn--primary,
  .btn--ghost,
  .btn--cta,
  .btn--call {
    border: 1px solid ButtonText !important;
  }
  .form-input,
  .form-textarea,
  .form-select,
  input,
  textarea,
  select {
    border: 1px solid CanvasText !important;
  }
  .form-input.is-invalid,
  .is-invalid {
    outline: 2px solid Highlight !important;
    outline-offset: 0 !important;
  }
  .card,
  .card__badge,
  .chip,
  .filter-chip {
    border: 1px solid CanvasText !important;
  }
  .skip-link {
    background: Canvas !important;
    color: CanvasText !important;
    border: 2px solid CanvasText !important;
  }
}

.form-input:focus,
.form-textarea:focus,
.form-select:focus,
input:focus,
textarea:focus,
select:focus {
  /* Keep a transparent outline (instead of `none`) so Windows High Contrast
     Mode users still see a focus indicator — HCM ignores box-shadow but
     forces a visible outline color when one is declared. */
  outline: 2px solid transparent;
  outline-offset: 2px;
  border-color: var(--accent);
  background: var(--bg-2);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.form-input::placeholder,
.form-textarea::placeholder,
input::placeholder,
textarea::placeholder {
  color: var(--text-muted);
}

.form-textarea,
textarea {
  resize: vertical;
  min-height: 100px;
}

/* Native select needs custom chevron — appearance:none + ::after on wrapper */
.form-select,
select {
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  padding-right: 36px;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--text-soft) 50%),
    linear-gradient(135deg, var(--text-soft) 50%, transparent 50%);
  background-position:
    calc(100% - 18px) 50%,
    calc(100% - 14px) 50%;
  background-size: 4px 4px, 4px 4px;
  background-repeat: no-repeat;
}

.form-input:disabled,
.form-textarea:disabled,
.form-select:disabled,
input:disabled,
textarea:disabled,
select:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.form-note {
  font-size: var(--t-xs);
  color: var(--text-muted);
  text-align: center;
  margin-top: var(--s-3);
}

/* Submit-wrap — centered button + trust note. Used on the shipping
   quote form (and any future form that needs a CTA-style submit
   group). The previous ad-hoc inline `style="max-width: 480px"` left
   the button stranded mid-row on wide grids; flex-column with auto
   margin centers it cleanly. */
.form-submit-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-3);
  margin-top: var(--s-5);
}
.form-submit-wrap .dpelite-form__submit {
  /* Comfortable click target with a bit of breathing room — too narrow
     reads as a regular field control, too wide loses CTA presence.
     320px min anchors it as a "this is the action" element. */
  min-width: 320px;
  padding-left: var(--s-7);
  padding-right: var(--s-7);
}
.form-submit-wrap .form-note {
  margin-top: 0;
  max-width: 420px;
}

/* Validation states */
.form-input.has-error,
.form-textarea.has-error,
.form-select.has-error {
  border-color: var(--cta);
  background: var(--cta-soft);
}

.form-input.is-success,
.form-textarea.is-success,
.form-select.is-success {
  border-color: var(--success);
}

.form-error {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  font-size: var(--t-xs);
  color: var(--cta);
  font-weight: 500;
}
.form-error svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

/* Form-level success/error banners — must respect HTML5 [hidden] attribute */
.form-banner[hidden],
.form-success-banner[hidden] {
  display: none !important;
}
.form-banner,
.form-success-banner {
  padding: var(--s-5) var(--s-6);
  border-radius: var(--r-md);
  display: flex;
  align-items: flex-start;
  gap: var(--s-4);
  margin-bottom: var(--s-5);
  animation: fade-in-up 240ms ease-out;
}
.form-banner svg,
.form-success-banner svg {
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  margin-top: 2px;
}
.form-banner__title,
.form-success-banner__title {
  font-weight: 700;
  margin-bottom: 2px;
}
.form-banner__sub,
.form-success-banner__sub {
  font-size: var(--t-sm);
  color: var(--text-soft);
}

.form-success-banner {
  background: var(--success-soft);
  border: 1px solid var(--success);
}
.form-success-banner svg,
.form-success-banner__title { color: var(--success); }

/* ──────────────────────────────────────────────────────────────────
 * Form submission UX (loading overlay + success/error cards) — added
 * in 2.0.8 to replace the old text-only banners. Theme-integrated:
 * uses --accent / --success / --error tokens, .btn--primary styling,
 * and respects prefers-reduced-motion via shorter transitions.
 * ────────────────────────────────────────────────────────────────── */

/* Wrap that anchors the overlay. Forms that don't already have a
   parent wrapper get position:relative set inline by JS — but if you
   wrap your form template in <div class="dpelite-form-wrap"> the
   overlay will scope correctly without inline style mutations. */
.dpelite-form-wrap {
  position: relative;
}

/* Honeypot — hidden from BOTH visual + assistive-technology trees.
   Previously the markup used `aria-hidden="true"` on a wrapper that
   contained a focusable input, which violates WCAG 4.1.2 (focusable
   descendants inside aria-hidden regions are still announced by some
   screen readers + can be reached via voice control). `display:none`
   removes the wrapper from the accessibility tree entirely. Bots that
   parse raw HTML still see the input, fill it, and trip the honeypot
   server-side check — defense maintained. */
.dpelite-form__honeypot {
  display: none !important;
}

/* Full-form loading overlay — semi-opaque backdrop with a centered
   spinner. Sits above the form's UI so the user can't double-submit
   accidentally. Activated via `.is-active` class. */
.dpelite-form__overlay {
  position: absolute;
  inset: 0;
  background: rgba(var(--bg-rgb), 0.85);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  visibility: hidden;
  transition: opacity 200ms ease-out, visibility 200ms ease-out;
  z-index: 20;
  border-radius: var(--r-lg);
}
.dpelite-form__overlay.is-active {
  opacity: 1;
  visibility: visible;
}
.dpelite-form__overlay-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-4);
  padding: var(--s-6);
  text-align: center;
}
.dpelite-form__overlay-text {
  margin: 0;
  font-size: var(--t-base);
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.01em;
}
.dpelite-form__spinner {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 3px solid var(--border);
  border-top-color: var(--accent);
  animation: dpelite-form-spin 800ms linear infinite;
}
@keyframes dpelite-form-spin {
  to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .dpelite-form__spinner {
    animation-duration: 1600ms;
  }
}

/* Result card — replaces (success) or overlays-above (error) the form
   after submission completes. Same visual language as the rest of the
   theme: gradient backdrop, accent border, generous padding, bold title. */
.dpelite-form__result {
  display: flex;
  gap: var(--s-5);
  align-items: flex-start;
  padding: var(--s-7) var(--s-6);
  border-radius: var(--r-lg);
  border: 1px solid var(--border-strong);
  background: linear-gradient(135deg, var(--bg-2), var(--bg-1));
  box-shadow: var(--shadow-md);
  margin-bottom: var(--s-6);
  animation: dpelite-form-result-in 320ms ease-out;
  outline: none;
}
@media (prefers-reduced-motion: reduce) {
  .dpelite-form__result { animation: none; }
}
@keyframes dpelite-form-result-in {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: translateY(0); }
}
.dpelite-form__result.is-success {
  border-color: var(--success);
  background:
    linear-gradient(135deg, var(--success-soft), var(--bg-1));
}
.dpelite-form__result.is-error {
  border-color: var(--error, #dc2626);
  background:
    linear-gradient(135deg, rgba(220, 38, 38, 0.08), var(--bg-1));
}
.dpelite-form__result-icon {
  flex-shrink: 0;
  width: 56px;
  height: 56px;
  border-radius: var(--r-full);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-2);
}
.dpelite-form__result.is-success .dpelite-form__result-icon {
  background: var(--success);
  color: #fff;
}
.dpelite-form__result.is-error .dpelite-form__result-icon {
  background: var(--error, #dc2626);
  color: #fff;
}
.dpelite-form__result-icon svg {
  width: 28px;
  height: 28px;
}
.dpelite-form__result-body {
  flex: 1;
  min-width: 0;
}
.dpelite-form__result-title {
  font-size: var(--t-xl);
  font-weight: 800;
  margin: 0 0 var(--s-2);
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: var(--text);
}
.dpelite-form__result-text {
  margin: 0 0 var(--s-5);
  font-size: var(--t-base);
  line-height: 1.6;
  color: var(--text-soft);
}
.dpelite-form__result-ctas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
}
.dpelite-form__result-ctas .btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
}
.dpelite-form__result-ctas:empty {
  display: none;
}

/* Mobile: stack icon above body for compact layout */
@media (max-width: 540px) {
  .dpelite-form__result {
    flex-direction: column;
    gap: var(--s-4);
    padding: var(--s-5);
    text-align: center;
  }
  .dpelite-form__result-icon {
    align-self: center;
  }
  .dpelite-form__result-ctas {
    justify-content: center;
  }
  .dpelite-form__result-ctas .btn {
    flex: 1 1 auto;
    justify-content: center;
  }
}

.form-banner--error {
  background: var(--cta-soft);
  border: 1px solid var(--cta);
}
.form-banner--error svg,
.form-banner--error .form-banner__title { color: var(--cta); }

@media (max-width: 540px) {
  .form-row { grid-template-columns: 1fr; }
}

/* ─── BREADCRUMBS ────────────────────────────────────────── */
.breadcrumb {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  font-size: var(--t-xs);
  color: var(--text-muted);
  letter-spacing: 0.5px;
}
.breadcrumb a {
  color: var(--text-soft);
  transition: var(--transition);
  text-decoration: none;
}
.breadcrumb a:hover { color: var(--accent); }

.breadcrumb__item     { display: inline-flex; align-items: center; }
.breadcrumb__separator,
.breadcrumb svg {
  width: 12px;
  height: 12px;
  opacity: 0.5;
}
.breadcrumb__current { color: var(--accent); }

.breadcrumb__back {
  margin-left: auto;
  font-size: var(--t-xs);
  color: var(--text-muted);
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  transition: var(--transition);
  text-decoration: none;
}
.breadcrumb__back:hover { color: var(--accent); }
.breadcrumb__back svg { width: 14px; height: 14px; }

.breadcrumb-row {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  flex-wrap: wrap;
}

/* ─── TRUST STRIP ────────────────────────────────────────── */
/* Used on multiple pages (homepage hero, single-listing). Two variants:
   .trust         — pill-style row of items (compact, hero context)
   .trust-card    — boxed grid of items with icons (full-width section)   */

.trust {
  display: flex;
  gap: var(--s-3);
  flex-wrap: wrap;
}
.trust__item {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  font-size: var(--t-sm);
  color: var(--text-soft);
  padding: 8px 14px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--border);
  border-radius: var(--r-full);
}
.trust__item svg {
  width: 14px;
  height: 14px;
  color: var(--success);
}

.trust-card {
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  padding: var(--s-5);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-3);
  animation: fade-in-up 600ms ease-out backwards;
}
.trust-card__item {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  padding: var(--s-4) var(--s-5);
  border-radius: var(--r-md);
  transition: var(--transition);
}
.trust-card__item:hover {
  background: var(--bg-2);
  transform: translateY(-2px);
}
.trust-card__icon {
  width: 40px;
  height: 40px;
  border-radius: var(--r-sm);
  background: var(--success-soft);
  border: 1px solid rgba(34, 197, 94, 0.25);
  color: var(--success);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.trust-card__icon svg { width: 20px; height: 20px; }
.trust-card__title {
  font-size: var(--t-sm);
  font-weight: 700;
  color: var(--text);
  margin-bottom: 2px;
}
.trust-card__sub {
  font-size: var(--t-xs);
  color: var(--text-muted);
}
/* Stay 3-col while the listing layout is stacked-but-wide (720-1024px).
   Single column at 720- where the screen is genuinely narrow. The previous
   `max-width: 1024px` collapsed trust-card to 1col on iPad-landscape where
   the wide stacked layout has plenty of room for 3 columns. */
@media (max-width: 720px) {
  .trust-card {
    grid-template-columns: 1fr;
    gap: 0;
  }
}

/* ─── PAGINATION ─────────────────────────────────────────── */
.pagination {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-8) 0 var(--s-6);
}
.page-btn {
  /* WCAG 2.5.5 minimum 44×44 touch target. */
  min-width: 44px;
  height: 44px;
  padding: 0 var(--s-3);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  /* Was monospace — felt like a different system from the rest of the
     button family. Inherits the body font now for consistency. */
  font-size: var(--t-sm);
  font-weight: 600;
  color: var(--text-soft);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: var(--transition);
  cursor: pointer;
}
.page-btn:hover {
  background: var(--bg-2);
  border-color: var(--accent-border);
  color: var(--text);
}
.page-btn--active {
  background: var(--accent);
  color: var(--accent-fg);
  border-color: var(--accent);
}
.page-btn--active:hover {
  background: var(--accent-dark);
  color: var(--accent-fg);
}
.page-btn--disabled {
  opacity: 0.3;
  pointer-events: none;
}
.page-dots {
  padding: 0 var(--s-2);
  color: var(--text-muted);
  letter-spacing: 2px;
}
.page-summary {
  font-size: var(--t-sm);
  color: var(--text-muted);
  text-align: center;
  margin-bottom: var(--s-3);
}

/* ─── COUNTER (animated stat number) ─────────────────────── */
.counter {
  font-family: var(--font-mono);
  font-feature-settings: 'tnum';
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.counter.is-counted { transition: color var(--transition-counter); }

/* ─── TOOLTIP / POPOVER ──────────────────────────────────── */
/* Tooltip — small label revealed on hover/focus of [data-tooltip] host */
.tooltip {
  position: absolute;
  z-index: var(--z-overlay);
  padding: 6px 10px;
  background: var(--bg-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-xs);
  font-size: var(--t-xs);
  color: var(--text);
  white-space: nowrap;
  box-shadow: var(--shadow-md);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition);
}
.tooltip.is-visible,
[data-tooltip]:hover > .tooltip,
[data-tooltip]:focus-visible > .tooltip {
  opacity: 1;
}

/* Popover — larger floating panel */
.popover {
  position: absolute;
  z-index: var(--z-overlay);
  min-width: 220px;
  padding: var(--s-4);
  background: var(--bg-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
  animation: fade-in-up 200ms ease-out;
}

/* ─── UTILITY CLASSES ───────────────────────────────────── */
.mono {
  font-family: var(--font-mono);
  font-feature-settings: 'tnum';
}

.text-center { text-align: center; }
.text-left   { text-align: left; }
.text-right  { text-align: right; }

.text-muted  { color: var(--text-muted); }
.text-soft   { color: var(--text-soft); }
.text-accent { color: var(--accent); }

.mt-0 { margin-top: 0; }
.mt-2 { margin-top: var(--s-2); }
.mt-3 { margin-top: var(--s-3); }
.mt-4 { margin-top: var(--s-4); }
.mt-6 { margin-top: var(--s-6); }
.mt-8 { margin-top: var(--s-8); }

.mb-0 { margin-bottom: 0; }
.mb-2 { margin-bottom: var(--s-2); }
.mb-3 { margin-bottom: var(--s-3); }
.mb-4 { margin-bottom: var(--s-4); }
.mb-6 { margin-bottom: var(--s-6); }
.mb-8 { margin-bottom: var(--s-8); }

/* ─── CARDS GRID (3-col responsive) ─────────────────────── */
.cards-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-6);
  margin-top: var(--s-8);
}
@media (max-width: 1024px) {
  .cards-grid { grid-template-columns: repeat(2, 1fr); gap: var(--s-5); }
}
@media (max-width: 720px) {
  .cards-grid { grid-template-columns: 1fr; gap: var(--s-4); }
}

/* ─── RECENTLY VIEWED STRIP ─────────────────────────────
 * Compact thumbnail strip on archive + single listing pages. Uses
 * `auto-fill` with a HARD MAX (220px) so a 1- or 2-item strip doesn't
 * stretch each card across the whole page. Previous `auto-fit, 1fr`
 * blew the cards up to 600px+ each on wide screens with sparse history. */
.recently-viewed-grid {
  display: grid;
  /* `auto-fit` (not `auto-fill`) so empty tracks collapse to 0px when
     the user has fewer cards than would naturally fill a row.
     `auto-fill` keeps phantom columns alive — `justify-content: center`
     wouldn't visually move the cards because the grid still spans full
     width with empty space-takers. With `auto-fit` + collapsed tracks,
     centering actually centers the visible cards. The 220px MAX cap
     keeps cards from blowing up to 600px+ each on wide screens with
     a single saved listing. */
  grid-template-columns: repeat(auto-fit, minmax(180px, 240px));
  gap: var(--s-4);
  justify-content: center;
}
/* v2.0.87 — card surface uses `--bg-2` (#161616) instead of `--bg-1`
   (#111111). v2.0.86 picked --bg-1 to match base `.card` rules, but on
   the live theme the page body is `--bg: #0a0a0a` and --bg-1 is only
   6.7% lighter — the difference is invisible on most displays, so cards
   appeared "floating" with no surface. --bg-2 is 39% lighter than body
   (#0a0a0a → #161616): clearly visible card surface even in dim viewing
   conditions. Border opacity bumped from 0.06 to 0.10 for sharper edge
   definition at the smaller card size. Subtle resting drop-shadow gives
   depth without competing with hover state. */
.recently-viewed-grid .card {
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
  background: var(--bg-2);
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-radius: var(--r-md);
  overflow: hidden;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
  transition: transform 160ms ease-out, border-color 160ms ease-out, box-shadow 160ms ease-out, background 160ms ease-out;
}
.recently-viewed-grid .card:hover {
  transform: translateY(-3px);
  border-color: var(--accent);
  background: var(--bg-3);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.55);
}
.recently-viewed-grid .card:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.recently-viewed-grid .card__media {
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  /* No corner-radius here — outer .card already clips with overflow:hidden
     and its own border-radius. Double-radius would leave a hairline gap
     between image and card edge that's visible on the dark surface. */
  background: var(--bg-2);
}
.recently-viewed-grid .card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.recently-viewed-grid .card__body {
  /* Real horizontal + vertical padding so text has room to breathe
     inside the card surface. flex-grow:1 makes the body absorb extra
     space so the price always sits at the bottom of every card —
     locks the price row alignment across the grid regardless of how
     many lines the title wraps to. */
  padding: var(--s-3);
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  flex: 1;
}
.recently-viewed-grid .card__year {
  /* Small uppercase year kicker above the title. Industry convention
     (Carvana, Carmax, AutoTrader, MachineryTrader): year is the first
     thing buyers compare across cards, so it gets its own line in
     muted color, leaving the title (make + model) to do the heavy
     visual work in full text. */
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  line-height: 1;
}
.recently-viewed-grid .card__title {
  font-size: var(--t-sm);
  font-weight: 600;
  margin: 0;
  line-height: 1.35;
  color: var(--text);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-word;
  /* min-height matches exactly 2 lines of the .card__title font-size+lh,
     so a single-line title reserves the second line of empty space →
     price sits at the SAME y-coordinate across every card in the grid. */
  min-height: calc(var(--t-sm) * 1.35 * 2);
}
.recently-viewed-grid .card__price {
  font-size: var(--t-md);
  font-weight: 700;
  color: var(--accent);
  margin-top: auto;
  letter-spacing: -0.01em;
}
.recently-viewed-grid .card__price--noprice {
  color: var(--text-muted);
  font-weight: 600;
}
@media (max-width: 720px) {
  .recently-viewed-grid {
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: var(--s-3);
  }
  .recently-viewed-grid .card__body {
    padding: var(--s-2);
  }
}


/* ─── LISTING CARD (BEM matches mockup-1B-inventory) ─────── */
.card {
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
  transition: var(--transition);
  position: relative;
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
}
.card {
  /* Card transitions use the spring-leaning pleasure curve (defined in
     tokens.css) so hover lifts feel calm-luxury rather than rigid-utility.
     Pre-declared on the .card itself so every transitionable property
     uses the same curve. */
  transition: transform var(--transition-pleasure, 280ms ease),
              border-color var(--transition-pleasure, 280ms ease),
              box-shadow var(--transition-pleasure, 280ms ease);
}
.card:hover {
  border-color: var(--accent-border);
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
}
.card__media {
  aspect-ratio: 4 / 3;
  background:
    linear-gradient(135deg, var(--accent-soft), transparent 50%),
    linear-gradient(45deg, var(--bg-3), var(--bg-2));
  position: relative;
  overflow: hidden;
}
.card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--transition);
}
.card:hover .card__media img {
  transform: scale(1.04);
}
.card__badge {
  position: absolute;
  top: var(--s-3);
  left: var(--s-3);
  background: var(--accent);
  color: var(--accent-fg);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  padding: 4px 10px;
  border-radius: var(--r-xs);
  z-index: 2;
}
.card__badge--cta {
  background: var(--cta);
  color: var(--cta-fg);
}
/* ─── SEARCH TYPEAHEAD DROPDOWN ──────────────────────────
 * Renders below an inventory search input via initSearchAutocomplete.
 * Up to 8 suggestions grouped by Make / Type / Listing. Keyboard nav:
 * ↑↓ + Enter + Esc. */
.dpel-typeahead {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 50;
  margin-top: 6px;
  background: var(--bg-1);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  max-height: 360px;
  overflow-y: auto;
}
.dpel-typeahead__item {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s-3);
  padding: 10px 14px;
  text-decoration: none;
  color: var(--text);
  font-size: var(--t-sm);
  border-bottom: 1px solid var(--border);
  transition: background var(--transition-fast);
}
.dpel-typeahead__item:last-child {
  border-bottom: 0;
}
.dpel-typeahead__item:hover,
.dpel-typeahead__item.is-active {
  background: var(--bg-2);
  color: var(--accent);
}
.dpel-typeahead__label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dpel-typeahead__sub {
  flex-shrink: 0;
  font-size: var(--t-xs);
  color: var(--text-faint);
}

/* "SAMPLE" watermark for wizard-seeded demo listings (`dpelite_demo=1`).
   Diagonal banner across the bottom-left corner of the card photo so
   the dealer + visitor can tell placeholder content from real inventory.
   Cleanup runs via the demo-cleanup admin notice. */
/* SAMPLE watermark moved to TOP-LEFT diagonal so it doesn't overlap
   the .card__photo-count badge at bottom-left. */
.card__demo-watermark {
  position: absolute;
  top: 14px;
  left: -32px;
  transform: rotate(-30deg);
  background: var(--cta);
  color: var(--cta-fg, #fff);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.1em;
  padding: 3px 36px;
  text-transform: uppercase;
  pointer-events: none;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
  z-index: 3; /* above .card__badge so the diagonal stays clean */
}

/* Safety net: when a card has the SAMPLE ribbon, hide the top-left badge
   pill (NEW ARRIVAL, etc.) so the two don't collide. PHP in
   parts/listing/card.php already suppresses $badge for demo cards — this
   CSS rule is belt-and-suspenders for custom code paths that bypass the
   helper, or legacy data with both flags set. */
.card__media:has(.card__demo-watermark) .card__badge {
  display: none;
}

.card__year {
  position: absolute;
  top: var(--s-3);
  /* Save heart now sits at the bottom-right of the media area, not the
     top-right, so the year tag can use the corner cleanly. */
  right: var(--s-3);
  background: rgba(var(--bg-rgb), 0.85);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  font-weight: 600;
  padding: 4px 10px;
  border-radius: var(--r-xs);
  border: 1px solid var(--border-strong);
  z-index: 2;
}
.card__photo-count {
  position: absolute;
  bottom: var(--s-3);
  left: var(--s-3);
  background: rgba(var(--bg-rgb), 0.85);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  font-size: 10px;
  font-weight: 600;
  padding: 4px 8px;
  border-radius: var(--r-xs);
  display: flex;
  align-items: center;
  gap: 4px;
  color: var(--text-soft);
  z-index: 2;
}
.card__photo-count svg {
  width: 11px;
  height: 11px;
}
.card__body {
  padding: var(--s-5);
  flex: 1;
  display: flex;
  flex-direction: column;
}
.card__make {
  font-size: var(--t-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 2px;
  color: var(--accent);
  margin-bottom: var(--s-2);
  /* Long brand strings ("Caterpillar 6515 Special Edition") would
     otherwise overflow the card and push the title below. Clamp + wrap. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.card__title {
  font-size: var(--t-md);
  font-weight: 700;
  margin-bottom: var(--s-3);
  letter-spacing: -0.01em;
  color: var(--text);
  /* 1.25 is the sweet spot for 2-line wrapping — tight enough to feel like
     a heading, loose enough that "Caterpillar 6515 Special Edition" doesn't
     visually crash into itself between lines. The previous default (~1.1)
     made wrapped card titles feel cramped. */
  line-height: 1.25;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* Long brand strings ("Caterpillar 6515 Special Edition") on 320px screens
     would otherwise overflow the card and force the line-clamp to truncate
     mid-word. overflow-wrap lets the browser break inside long tokens; the
     line-clamp still caps at 2 lines so cards stay equal height. */
  overflow-wrap: break-word;
  word-break: break-word;
  hyphens: auto;
}
.card__specs {
  display: flex;
  gap: 6px;
  font-size: var(--t-xs);
  color: var(--text-muted);
  margin-bottom: var(--s-5);
  padding-bottom: var(--s-4);
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
}
/* Spec chips — icon + label pill. Replaces the previous dot-separated
   inline list (which collided with a CSS ::after "·" pseudo and rendered
   "· ·" doubled-up on every card). Chips are auto-truncating so a long
   value like "I-Shift Manual 12-spd" doesn't blow out the row width. */
.card__chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 8px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  font-size: 11px;
  font-weight: 600;
  color: var(--text-soft);
  letter-spacing: 0.01em;
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: 1.2;
}
.card__chip-icon {
  width: 11px;
  height: 11px;
  color: var(--accent);
  flex-shrink: 0;
}
/* Trust chip row + shield variant (v2.0.47 — warranty/buy-back
   badges). Lives in components.css (not pages/warranty.css) so the
   chip renders correctly on every surface that loops card.php:
   inventory archive, homepage Featured section, recently-sold strip,
   related-listings rail. */
.card__trust {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin: var(--s-3) 0 0;
}
.card__chip--shield {
  background: rgba(34, 197, 94, 0.08);
  border-color: rgba(34, 197, 94, 0.25);
  color: var(--success);
}
.card__chip--shield .card__chip-icon,
.card__chip--shield svg {
  color: var(--success);
}
.card__price-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  margin-top: auto;
  gap: var(--s-3);
}
.card__price {
  font-family: var(--font-mono);
  font-size: var(--t-lg);
  font-weight: 700;
  color: var(--accent);
  line-height: 1;
}
.card__price--noprice {
  font-family: var(--font-sans);
  font-size: var(--t-sm);
  font-weight: 600;
}
.card__price-old {
  font-size: var(--t-xs);
  color: var(--text-faint);
  text-decoration: line-through;
}
/* v2.0.105 — discounted price + struck old price sit INLINE (old to the right),
   baseline-aligned, instead of stacked. */
.card__price-wrap {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--s-2);
}

.card__view-arrow {
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  border-radius: var(--r-full);
  background: rgba(255, 255, 255, 0.04);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: var(--transition);
}
.card__view-arrow svg {
  width: 14px;
  height: 14px;
  color: var(--text-soft);
}
.card:hover .card__view-arrow {
  background: var(--accent);
}
.card:hover .card__view-arrow svg {
  color: var(--accent-fg);
}

/* ════════════════════════════════════════════════════════════════
   UNIVERSAL SECTION PRIMITIVES (added per audit Tier 1)
   ════════════════════════════════════════════════════════════════ */

/* ─── WRAP-CARD (universal section wrapper) ─────────────── */
.wrap-card {
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  padding: var(--s-8);
}
@media (max-width: 720px) {
  .wrap-card { padding: var(--s-6); }
}
.wrap-card__head {
  text-align: center;
  max-width: 720px;
  margin: 0 auto var(--s-7);
}
.wrap-card__title {
  margin: var(--s-3) 0;
  font-size: var(--t-section-title);
  letter-spacing: var(--ls-display);
  line-height: var(--lh-snug);
}
.wrap-card__sub {
  font-size: var(--t-md);
  color: var(--text-soft);
  line-height: 1.6;
}

/* ─── SECTION HEAD (homepage section titles) ────────────── */
.section__head {
  text-align: center;
  max-width: 720px;
  margin: 0 auto;
}
.section__head--with-cta {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  text-align: left;
  max-width: none;
  gap: var(--s-5);
  flex-wrap: wrap;
}
.section__head--with-cta > div { flex: 1; min-width: 0; }
/* On mobile, even the "with-cta" variant centers — left-align with a CTA
   floated right reads as competing actions in a narrow column. Centering
   gives the eye one anchor: kicker → title → sub → CTA stacked. */
@media (max-width: 720px) {
  .section__head--with-cta {
    justify-content: center;
    text-align: center;
  }
  .section__head--with-cta > div { flex: 1 1 100%; }
}
.section__title {
  margin: var(--s-3) 0;
  font-size: var(--t-section-title);
  letter-spacing: var(--ls-display);
  /* Display headings 40px+ need tight line-height (~1.1) — `--lh-snug` (1.3)
     left a 13px gap between lines that visually disjointed multi-line H2s
     from their kicker/sub. Compare with `.related__title` and the page-hero
     title which both use --lh-tight and read coherently. */
  line-height: var(--lh-tight);
}
.section__sub {
  font-size: var(--t-md);
  color: var(--text-soft);
  line-height: 1.6;
  max-width: 560px;
  margin: 0 auto;
}

/* ─── BREADCRUMBS (PHP renders .breadcrumbs plural; CSS aliases) ─── */
.crumbs {
  padding: var(--s-5) 0;
  border-bottom: 1px solid var(--border);
}
.breadcrumbs {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  font-size: var(--t-xs);
  color: var(--text-muted);
  letter-spacing: 0.5px;
}
.breadcrumbs__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex-wrap: wrap;
}
.breadcrumbs__item {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
}
.breadcrumbs__link {
  color: var(--text-soft);
  text-decoration: none;
  transition: var(--transition);
}
.breadcrumbs__link:hover { color: var(--accent); }
.breadcrumbs__current { color: var(--accent); }
.breadcrumbs__separator { color: var(--text-faint); opacity: 0.5; }

/* ─── CARD: heart-save + quick-view button (mockup-1B inventory) ─── */
/* Card wrapper — gives the absolutely-positioned heart button a positioning
   context now that it lives outside the <a> link (HTML5 forbids nesting
   interactive elements inside an anchor). */
.card-wrap {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  /* Establish a containment context so .card__save can position itself
     relative to the media area's height (aspect-ratio 4/3 = 75% of width). */
  container-type: inline-size;
}
/* Anchor inside the wrap stretches full height so every card in the grid
   row matches the tallest neighbor (per dealer report: card heights drift
   when specs differ in count between listings — e.g. truck with 3 chips
   vs equipment with 4). */
.card-wrap > .card {
  flex: 1 1 auto;
  height: 100%;
}
.card__save {
  /* Mockup-1B: heart sits at the bottom-right of the photo area, opposite the
     photo-count badge at bottom-left. Card__media has aspect-ratio 4/3, so
     its bottom edge is 75% of the card width down from the top. We use
     container query units to anchor the heart there.
     Size = 44px (WCAG 2.5.5 touch target minimum); SVG stays 16-18px.
     Fallback for Safari < 16 / Firefox < 110 / Chrome < 105 without container
     query units: percent-based heuristic (cards have a relatively predictable
     aspect ratio so 70% lands close to the photo's bottom-right). The modern
     `cqw` rule below overrides on supporting browsers. */
  position: absolute;
  top: 70%;
  top: calc(75cqw - 44px - var(--s-3));
  right: var(--s-3);
  z-index: 3;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(var(--bg-rgb), 0.85);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border: 1px solid var(--border-strong);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: var(--transition);
}
.card__save[aria-pressed="true"] svg {
  fill: var(--cta);
  stroke: var(--cta);
  /* Wired to the existing heart-pulse @keyframes (see top of this file).
     Touch users get no :hover feedback — without this animation, the click
     was visually silent (only fill/stroke changed instantly). */
  animation: heart-pulse 350ms ease-out;
}
.card__save svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: var(--text-muted);
  stroke-width: 2;
  /* Smooth color transitions instead of instant snap when toggling saved. */
  transition: fill var(--transition), stroke var(--transition);
}
.card__save:hover svg { stroke: var(--cta); }
.card__save--saved svg { fill: var(--cta); stroke: var(--cta); }
@media (prefers-reduced-motion: reduce) {
  .card__save[aria-pressed="true"] svg { animation: none; }
}

/* ─── COMPARE BUTTON (per-card) ──────────────────────────
 * Companion to .card__save. Sits left of the heart so the buyer
 * has a 2-icon stack (compare + save) at the bottom-right of the
 * photo. Toggle adds the listing to localStorage[dpelite_compare]
 * (capped at 3 — heavy-equipment buyers cross-shop spec sheets in
 * 2-3-way comparisons; more is overload). */
.card__compare {
  position: absolute;
  top: calc(75cqw - 44px - var(--s-3));
  right: calc(var(--s-3) + 52px);
  z-index: 3;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(var(--bg-rgb), 0.85);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border: 1px solid var(--border-strong);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: var(--transition);
  touch-action: manipulation;
}
.card__compare svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: var(--text-muted);
  stroke-width: 2;
  transition: stroke var(--transition);
}
.card__compare:hover svg { stroke: var(--accent); }
.card__compare[aria-pressed="true"] {
  border-color: var(--accent-border);
  background: var(--accent-soft);
}
.card__compare[aria-pressed="true"] svg { stroke: var(--accent); }

/* ─── COMPARE STICKY BAR ─────────────────────────────────
 * Slides in from bottom when 1+ listings are added. At 3 listings,
 * primary "Compare" button enables; at 0 the bar slides out. */
.compare-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 90;
  background: rgba(var(--bg-rgb), 0.96);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
  border-top: 1px solid var(--border-strong);
  padding: var(--s-3) var(--s-4) calc(var(--s-3) + env(safe-area-inset-bottom, 0px));
  box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.4);
  transform: translateY(100%);
  transition: transform 320ms cubic-bezier(0.4, 0, 0.2, 1);
}
.compare-bar.is-active { transform: translateY(0); }

/* ─── FIXED-BOTTOM STACKING ────────────────────────────────
 * 4 elements anchor `bottom: 0` and would obscure each other:
 *   sticky-cta-bar (z:200, single-listing) > sticky-mobile (z:200,
 *   homepage) > cookie-notice (z:92) > compare-bar (z:90).
 * When a higher-z bar is visible, push the lower-z bars up by its
 * height so all stay visible. CSS-only via `:has()` — degrades to
 * "topmost-only" on browsers without :has() (Safari < 15.4 etc.). */
body:has(.compare-bar.is-active) .dpel-cookie-notice {
  bottom: 64px;
}
body:has(.sticky-cta-bar.is-visible) .compare-bar,
body:has(.sticky-cta-bar.is-visible) .dpel-cookie-notice {
  bottom: 76px;
}
body:has(.sticky-cta-bar.is-visible):has(.compare-bar.is-active) .dpel-cookie-notice {
  bottom: 140px;
}
body:has(.sticky-mobile) .compare-bar,
body:has(.sticky-mobile) .dpel-cookie-notice {
  bottom: 76px;
}
.compare-bar__inner {
  max-width: 1200px;
  margin: 0 auto;
  display: flex;
  gap: var(--s-3);
  align-items: center;
  flex-wrap: wrap;
}
.compare-bar__items {
  display: flex;
  gap: var(--s-2);
  flex: 1;
  min-width: 0;
  flex-wrap: wrap;
}
.compare-bar__chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--r-full);
  font-size: var(--t-xs);
  color: var(--text);
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.compare-bar__chip-remove {
  background: transparent;
  border: 0;
  color: var(--text-soft);
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  /* Padding bumps the touch target to ~32px without changing the visible
     glyph size (chip layout already fits this). Below 32px it consistently
     fails WCAG 2.5.5 thumb-tap accuracy on small phones. */
  padding: 8px;
  min-width: 32px;
  min-height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-sm);
  transition: var(--transition);
}
.compare-bar__chip-remove:hover {
  color: var(--cta);
  /* rgba fallback for browsers without color-mix() (Safari < 16.2,
     Firefox < 113, Chrome < 111). Without it the entire background
     declaration is invalid and the hover loses its tint. */
  background: rgba(220, 38, 38, 0.12);
  background: color-mix(in srgb, var(--cta) 12%, transparent);
}
.compare-bar__actions {
  display: flex;
  gap: var(--s-2);
  align-items: center;
}
.compare-bar__count {
  font-size: var(--t-xs);
  color: var(--text-soft);
  white-space: nowrap;
}
@media (prefers-reduced-motion: reduce) {
  .compare-bar { transition: none; }
}

.card__qv-trigger {
  position: absolute;
  bottom: var(--s-3);
  right: var(--s-3);
  background: rgba(var(--bg-rgb), 0.85);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-xs);
  padding: var(--s-2) var(--s-3);
  /* WCAG 2.5.5 Target Size (AAA) recommends 44×44. Touch devices reveal
     this trigger always-visible (no hover/focus signal), so it MUST meet
     the 44×44 standard. Desktop reveals on hover via the @media block
     below — there a 24×24 minimum is fine since the cursor is precise. */
  font-size: var(--t-xs);
  color: var(--text-soft);
  display: flex;
  align-items: center;
  gap: 4px;
  cursor: pointer;
  transition: var(--transition);
}
.card__qv-trigger svg { width: 12px; height: 12px; }

/* Desktop / mouse / stylus: hide-until-hover/focus pattern works because
   hover/focus is a real, intentional signal that "this card is being
   considered". 24×24 min target is fine since cursor is precise. */
@media (hover: hover) and (pointer: fine) {
  .card__qv-trigger {
    opacity: 0;
    min-width: 24px;
    min-height: 24px;
  }
  .card:hover .card__qv-trigger,
  /* Keyboard users never trigger :hover — reveal on focus-within so
     keyboard / screen-reader users can reach the trigger too (WCAG 2.4.7,
     2.1.1). Without this, the trigger was effectively mouse-only. */
  .card:focus-within .card__qv-trigger,
  .card__qv-trigger:focus-visible { opacity: 1; }
}

/* Touch / coarse-pointer (phones, tablets, kiosks): always visible,
   44×44 target. Previously opacity:0 was unconditional, leaving touch
   users with an invisible Quick-View trigger they could never tap.
   The card itself is still a single big tap target via its inner <a>;
   QV is a secondary action that opens the modal inline. */
@media (hover: none), (pointer: coarse) {
  .card__qv-trigger {
    opacity: 1;
    min-width: 44px;
    min-height: 44px;
    justify-content: center;
  }
}

/* ─── BUTTON UTILITIES ─────────────────────────────────── */
.btn--full { width: 100%; }
.btn--cta {
  background: var(--cta);
  color: var(--cta-fg);
  font-weight: 700;
  border: 1px solid var(--cta);
  box-shadow: var(--shadow-cta);
}
.btn--cta:hover {
  background: var(--cta-dark);
  border-color: var(--cta-dark);
  /* -2px to match .btn--primary / .btn--call hover lift; the previous -1px
     made the most-important button feel less responsive than its siblings. */
  transform: translateY(-2px);
  box-shadow: var(--shadow-md), var(--shadow-cta);
}

/* ─── FAQ ITEM (accordion — used on secure, financing, etc.) ─── */
.faq__head {
  text-align: center;
  margin-bottom: var(--s-7);
  max-width: 640px;
  margin-left: auto;
  margin-right: auto;
}
.faq__title {
  margin: var(--s-3) 0;
  font-size: var(--t-section-title);
  letter-spacing: var(--ls-display);
  line-height: var(--lh-snug);
}
.faq__list {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  max-width: 800px;
  margin: 0 auto;
}
.faq-item {
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--bg-2);
  overflow: hidden;
}
.faq-item__q {
  width: 100%;
  background: none;
  border: 0;
  padding: var(--s-5);
  display: flex;
  justify-content: space-between;
  align-items: center;
  cursor: pointer;
  font-size: var(--t-base);
  font-weight: 700;
  color: var(--text);
  text-align: left;
  transition: background var(--transition);
}
.faq-item__q:hover {
  background: var(--bg-3);
}
.faq-item__q:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  background: var(--bg-3);
}
/* Mirror the JS-set .faq-item--open class via aria-expanded so keyboard users
   see the open state even before the wrapper class lands. */
.faq-item__q[aria-expanded="true"] .faq-item__icon {
  transform: rotate(45deg);
  /* Keep the accent color when open — was red CTA, which clashed with the
     positive "open / answer revealed" state. The rotation alone signals
     the toggle without the color shift. */
  color: var(--accent);
}
.faq-item__icon {
  font-size: 28px;
  line-height: 1;
  color: var(--accent);
  transition: transform var(--transition);
  flex-shrink: 0;
  display: inline-block;
  width: 28px;
  height: 28px;
  text-align: center;
  font-weight: 300;
  transform-origin: center;
  will-change: transform;
}
.faq-item--open .faq-item__icon {
  transform: rotate(45deg);
  color: var(--accent);
}
/* Smooth accordion via max-height. No template emits `.faq-item__a-inner`
   so padding/typography styles live directly on `.faq-item__a`. Was
   previously a `display:none/block` toggle here (no animation) AND a
   max-height animation in faq.css using a non-existent `__a-inner` wrapper —
   both broken. Consolidated to one working version. */
.faq-item__a {
  max-height: 0;
  overflow: hidden;
  padding: 0 var(--s-5);
  color: var(--text-soft);
  font-size: var(--t-sm);
  line-height: 1.7;
  transition: max-height 320ms cubic-bezier(0.4, 0, 0.2, 1),
              padding 320ms cubic-bezier(0.4, 0, 0.2, 1);
}
.faq-item--open .faq-item__a {
  max-height: 600px;
  padding-bottom: var(--s-5);
}
.faq-item__a p { margin: 0; }
.faq-item__a p + p { margin-top: var(--s-3); }
.faq-item__a strong { color: var(--text); font-weight: 700; }
.faq-item__a a { color: var(--accent); text-decoration: underline; }

/* ─── TESTIMONIALS ────────────────────────────────────── *
 * Canonical block. Used identically on homepage, about, shipping, buy-now,
 * and (future) financing. Was previously duplicated in homepage.css with
 * subtle drift (different padding, no hover) — consolidated here.
 *
 * Templates use either `.testimonial__role` (front-page — role + company)
 * or `.testimonial__location` (other pages — city/state). Both are
 * styled identically below. */
.testimonials__head {
  text-align: center;
  margin-bottom: var(--s-7);
}
.testimonials__title {
  margin: var(--s-3) 0;
  font-size: var(--t-section-title);
  letter-spacing: var(--ls-display);
  line-height: var(--lh-snug);
}
.testimonials__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-5);
}
.testimonial {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s-7);
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  transition: var(--transition);
  will-change: transform;
}
.testimonial:hover {
  border-color: var(--accent-border);
  transform: translateY(-2px);
}
.testimonial__stars {
  display: inline-flex;
  gap: 2px;
  color: var(--accent);
  margin-bottom: var(--s-2);
}
.testimonial__stars svg {
  width: 18px;
  height: 18px;
  fill: currentColor;
}
.testimonial__quote {
  margin: 0;
  font-size: var(--t-sm);
  line-height: 1.6;
  color: var(--text-soft);
}
.testimonial__author {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-top: auto;
  padding-top: var(--s-4);
  border-top: 1px solid var(--border);
}
.testimonial__avatar {
  width: 44px;
  height: 44px;
  border-radius: var(--r-full);
  /* Solid accent + accent-fg gives ≥4.5:1 contrast (was accent-soft + accent
     at 2.28:1, failed AA). */
  background: var(--accent);
  color: var(--accent-fg);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: var(--t-sm);
  letter-spacing: 1px;
  flex-shrink: 0;
}
.testimonial__name {
  font-weight: 700;
  color: var(--text);
  font-size: var(--t-sm);
}
/* Sub-line: role+company (front-page) or city/state (about/shipping/buy-now).
   Same visual treatment for both class names. */
.testimonial__role,
.testimonial__location {
  font-size: var(--t-xs);
  color: var(--text-muted);
  margin-top: 2px;
}
@media (max-width: 960px) {
  .testimonials__grid { grid-template-columns: 1fr; }
}
@media (max-width: 1024px) {
  .testimonial { text-align: center; }
  .testimonial__stars { justify-content: center; }
  .testimonial__author { justify-content: center; }
}

/* ─── DP-OVERLAY (form submit overlay) ──────────────────── */
.dp-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal, 9999);
  background: rgba(0, 0, 0, 0.85);
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--s-5);
}
.dp-overlay.is-open { display: flex; }
.dp-overlay__inner {
  background: var(--bg-1);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-xl);
  padding: var(--s-8);
  max-width: 480px;
  text-align: center;
}
.dp-overlay__icon {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background: var(--success-soft);
  color: var(--success);
  margin: 0 auto var(--s-5);
  display: flex;
  align-items: center;
  justify-content: center;
}
.dp-overlay__spinner {
  width: 48px;
  height: 48px;
  border: 3px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: btn-spin 700ms linear infinite;
  margin: 0 auto var(--s-5);
}
.dp-overlay__title { font-size: var(--t-xl); margin-bottom: var(--s-3); }
.dp-overlay__sub { color: var(--text-soft); margin-bottom: var(--s-5); }
.dp-overlay__actions { display: flex; gap: var(--s-3); justify-content: center; }

/* ─── SHIPPING — compare table highlight row + badge-best ─── */
.compare-table tr.highlight {
  background: var(--accent-soft);
  border-left: 3px solid var(--accent);
}

/* Mobile-stacked compare table — under 720px the table converts to a
   list of label/value cards. JS (initCompareTables in main.js) auto-adds
   `data-label` to each <td> based on the matching <th>; CSS uses that
   to render the column header in front of each cell value.

   Without this, the audit-flagged horizontal-scroll fallback was the
   only mobile experience for financing/sell/shipping comparison tables. */
@media (max-width: 720px) {
  .compare-table { display: block; width: 100%; }
  .compare-table thead { display: none; }
  .compare-table tbody { display: block; }
  .compare-table tr {
    display: block;
    margin-bottom: var(--s-4);
    padding: var(--s-3) var(--s-4);
    background: var(--bg-2);
    border-radius: var(--r-md);
    border-left-width: 4px;
  }
  .compare-table td {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    padding: var(--s-2) 0;
    border: none;
    text-align: right;
  }
  .compare-table td::before {
    content: attr(data-label);
    flex: 0 0 45%;
    font-size: var(--t-xs);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--text-muted);
    text-align: left;
    padding-right: var(--s-3);
  }
  /* Comparison wrapper no longer needs to scroll once the table stacks. */
  .comparison { overflow: visible; }
}
.badge-best {
  display: inline-block;
  background: var(--accent);
  color: var(--accent-fg);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  padding: 2px 8px;
  border-radius: var(--r-xs);
  margin-left: var(--s-2);
  vertical-align: middle;
}

/* ─── MOBILE DRAWER LIST (wp_nav_menu output) ────────────── */
.mobile-drawer__list {
  display: flex;
  flex-direction: column;
  padding: 0;
  margin: 0;
  list-style: none;
}
.mobile-drawer__list li a {
  padding: var(--s-4);
  display: block;
  color: var(--text);
  border-bottom: 1px solid var(--border);
  text-decoration: none;
  font-weight: 500;
}
.mobile-drawer__list li a:hover {
  background: rgba(255, 255, 255, 0.04);
  color: var(--accent);
}


/* ─── CARD: always-on placeholder (camera icon + label) — img covers it when present ─── */
.card__media::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='56' height='56' viewBox='0 0 24 24' fill='none' stroke='rgba(255,255,255,0.45)' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'><path d='M23 19a2 2 0 01-2 2H3a2 2 0 01-2-2V8a2 2 0 012-2h4l2-3h6l2 3h4a2 2 0 012 2z'/><circle cx='12' cy='13' r='4'/></svg>");
  background-repeat: no-repeat;
  background-position: center calc(50% - 16px);
  pointer-events: none;
  z-index: 0;
}
.card__media::after {
  content: 'EQUIPMENT PHOTO';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  letter-spacing: 3px;
  color: rgba(255, 255, 255, 0.55);
  font-weight: 700;
  text-transform: uppercase;
  pointer-events: none;
  z-index: 0;
  transform: translateY(32px);
}
body.dpelite-vertical-cars .card__media::after {
  content: 'VEHICLE PHOTO';
}
.card__media img {
  position: relative;
  z-index: 1;
}
.card__media .card__badge,
.card__media .card__year,
.card__media .card__photo-count,
.card__media .card__save,
.card__media .card__discount,
.card__media .card__qv-trigger {
  z-index: 2;
}

/* ─── Sold-state card ───────────────────────────────────
   Greys the image (50% opacity) and overlays a large red SOLD pill so the
   state reads at-a-glance from the inventory grid. The card stays clickable
   so users can land on the single-listing page with full context (find
   similar / call CTA there). */
.card-wrap--sold .card__media img,
.card-wrap--sold .card__img--placeholder {
  opacity: 0.55;
  filter: grayscale(0.5);
}
.card-wrap--sold .card {
  /* Desaturate the entire card slightly so SOLD reads as a status, not a
     normal card with a badge. */
  opacity: 0.92;
}
.card__sold-overlay {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(-8deg);
  background: var(--cta);
  color: var(--cta-fg);
  padding: 6px 22px;
  font-size: clamp(20px, 3vw, 28px);
  font-weight: 900;
  letter-spacing: 0.18em;
  border-radius: var(--r-sm);
  /* rgba first so older browsers without color-mix() still get the shadow. */
  box-shadow: 0 4px 18px rgba(220, 38, 38, 0.45);
  box-shadow: 0 4px 18px color-mix(in srgb, var(--cta) 45%, transparent);
  z-index: 3;
  pointer-events: none;
  text-transform: uppercase;
}
/* Demote price colour in sold cards so the eye doesn't anchor on a number
   that's no longer actionable. */
.card-wrap--sold .card__price {
  color: var(--text-soft);
}

/* Hide save heart + compare toggle on SOLD cards — neither saving nor
   comparing makes sense for a unit no longer available. Declutters
   the SOLD overlay and prevents the buttons clashing visually with
   the rotated SOLD pill. */
.card-wrap--sold .card__save,
.card-wrap--sold .card__compare {
  display: none;
}

/* (Removed the old ".card__specs > span::after { content: '·' }" rule.
    It used to inject a middle-dot separator between spec values, but the
    PHP template ALSO injected its own dot, so cards rendered "· ·" as
    a visible double-dot. Specs now use .card__chip pills with no text
    separators — see the .card__chip block above.) */

/* Card: empty state when listing has no specs at all (just price) */
.card__body:has(.card__specs:empty) .card__title {
  margin-bottom: var(--s-5);
}


/* ─── Skeleton card (loading placeholder) ────────────────
   Shown while the inventory grid is fetching new results via AJAX (filter
   change or pagination). Mirrors .card structure (image area + 4 text bars
   + price bar) so the grid doesn't reflow when real cards swap in.

   Why skeletons over the previous dim+spinner pattern:
     - Stronger perceived performance (~25% faster perception in lab tests)
     - No "ghost content" of stale results showing through dimmed cards
     - Less jarring transition when filter results change drastically
       (e.g. 50 cards → 3 cards no longer flickers from dimmed-50 to filled-3)

   Injected by filters.js as `.card-wrap.card-skeleton-wrap` so the grid's
   `grid-template-columns` applies untouched and skeletons sit in the same
   column tracks as real cards.

   Reduced motion: shimmer is purely decorative — guarded so users with
   `prefers-reduced-motion: reduce` see static placeholders. */
.card-skeleton-wrap {
  /* Marker class — sits inside .inventory-grid like a real .card-wrap. No
     own layout rules; the .card-skeleton child carries the visual. */
}
.card-skeleton {
  display: flex;
  flex-direction: column;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
  pointer-events: none;
  user-select: none;
  /* Mirror the real .card visual chrome so the swap is invisible — same
     surface, same border, same radius. Only the content is missing. */
}
.card-skeleton__media {
  /* Match dpelite-card image aspect ratio (400×300 = 4/3) so layout
     doesn't shift when the real <img> swaps in. */
  aspect-ratio: 4 / 3;
  background-color: var(--bg-3);
}
.card-skeleton__body {
  padding: var(--s-5);
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  flex: 1;
}
.card-skeleton__bar {
  height: 12px;
  border-radius: var(--r-xs);
  background-color: var(--bg-3);
}
/* Per-bar widths + heights mirror real card content rhythm:
   make eyebrow → title (full) → title (wrap) → specs row → price */
.card-skeleton__bar--make    { width: 35%; height: 10px; }
.card-skeleton__bar--title   { width: 80%; height: 18px; margin-top: var(--s-1); }
.card-skeleton__bar--title-2 { width: 55%; height: 18px; }
.card-skeleton__bar--specs   { width: 65%; }
.card-skeleton__bar--price   { width: 45%; height: 22px; margin-top: var(--s-3); }

/* Shimmer — gradient sweep across all bars + media. Single keyframe
   reused so the system renders one timeline (cheaper than per-bar staggered
   timings) and all bars feel like they belong to the same loading state.

   `color-mix` modern browsers get a subtle white sheen; older browsers
   fall back to the static --bg-3 background-color set above (no shimmer
   but still visible). */
@supports (background: linear-gradient(90deg, transparent, color-mix(in srgb, white 8%, transparent), transparent)) {
  .card-skeleton__media,
  .card-skeleton__bar {
    background-image: linear-gradient(
      90deg,
      transparent 0%,
      color-mix(in srgb, var(--text) 6%, transparent) 50%,
      transparent 100%
    );
    background-size: 200% 100%;
    background-repeat: no-repeat;
    animation: dpelite-shimmer 1400ms ease-in-out infinite;
  }
}
@keyframes dpelite-shimmer {
  0%   { background-position: -100% 0; }
  100% { background-position:  200% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .card-skeleton__media,
  .card-skeleton__bar {
    animation: none;
    background-image: none;
  }
}


/* ─── DPELITE SEARCH FORM (overrides WP default) ───────────
   Used on 404 page and anywhere get_search_form() is called.
   Replaces the browser-default grey submit button with our yellow CTA. */
.dpelite-search-form {
  width: 100%;
}
.dpelite-search-form__row {
  display: flex;
  align-items: stretch;
  gap: var(--s-2);
  position: relative;
}
.dpelite-search-form__icon {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  color: var(--text-muted);
  pointer-events: none;
}
.dpelite-search-form__input {
  flex: 1;
  background: var(--bg-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  padding: 12px 16px 12px 42px;
  font-size: var(--t-sm);
  color: var(--text);
  transition: border-color var(--transition-fast);
}
.dpelite-search-form__input::placeholder { color: var(--text-muted); }

/* Inline accent emphasis used inside H1/H2 strings — replaces 10+ inline
   `<em style="color:var(--accent);font-style:normal;">…</em>` blocks that
   were previously sprinkled across page templates. The class keeps screen-
   reader semantics (em = emphasis) while letting a future brand-color
   change propagate everywhere automatically. The webkit-text-fill-color
   override is required when the parent heading uses background-clip:text
   for a gradient — otherwise the em renders transparent. */
.text-accent {
  color: var(--accent);
  -webkit-text-fill-color: var(--accent);
  font-style: normal;
}
.dpelite-search-form__input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-color: var(--accent);
}
.dpelite-search-form__input:focus:not(:focus-visible) {
  /* Mouse focus: drop the keyboard ring but keep the brand border. */
  outline: none;
  border-color: var(--accent);
}
.dpelite-search-form__submit {
  flex-shrink: 0;
}
@media (max-width: 540px) {
  .dpelite-search-form__row { flex-direction: column; }
  .dpelite-search-form__submit { width: 100%; }
}


/* ─── FEATURED LISTINGS POPUP (full-screen modal) ──────────
   Rendered by `parts/listing/featured-banner.php` via footer.php on every
   page where `dpelite_should_show_featured_banner()` returns true. JS
   (`initFeaturedPopup` in main.js) opens it 1.5s after pageload, once per
   browser session (sessionStorage gate) — closing it sets the flag so
   it stays gone for the rest of the session.

   Three layers stacked: backdrop (full screen blur) + panel (centered,
   max 1100px) + close button. Body scroll-locks via `body.popup-open`
   while it's visible. Animations zeroed under prefers-reduced-motion. */
.featured-popup {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s-6);
  /* Hidden by default — JS swaps `.is-open` after sessionStorage check.
     `pointer-events: none` while closing so the still-fading overlay
     doesn't intercept clicks meant for the page underneath. */
  opacity: 0;
  pointer-events: none;
  transition: opacity 280ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.featured-popup.is-open {
  opacity: 1;
  pointer-events: auto;
}
.featured-popup__backdrop {
  position: absolute;
  inset: 0;
  /* Heavier opacity so the page underneath fully recedes — at 0.78 the
     hero text was still legible through the modal, undermining the
     "this is a separate surface" cue. */
  background: rgba(0, 0, 0, 0.88);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  cursor: pointer;
}
.featured-popup__panel {
  position: relative;
  z-index: 1;
  /* Pop the panel above the dimmed page with `--bg-2` (#161616) — `--bg-1`
     was barely distinguishable from body bg (`#0a0a0a` vs `#111111`,
     1-step delta), making the panel look like it had no chrome.
     `--bg-2` gives a clear visible surface against the dimmed backdrop. */
  background: var(--bg-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-lg);
  /* Add a stronger glow on top of the standard shadow — modals deserve
     more depth than in-page cards because they float above everything. */
  box-shadow:
    var(--shadow-lg),
    0 0 0 1px rgba(255, 255, 255, 0.04),
    0 0 60px rgba(0, 0, 0, 0.6);
  max-width: 1100px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
  padding: var(--s-10) var(--s-8) var(--s-8);
  /* Subtle slide-up on open — feels like the panel is settling into
     place rather than appearing instantly. */
  transform: translateY(20px);
  transition: transform 320ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.featured-popup.is-open .featured-popup__panel {
  transform: translateY(0);
}
.featured-popup__close {
  position: absolute;
  top: var(--s-4);
  right: var(--s-4);
  width: 36px;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-full);
  color: var(--text);
  cursor: pointer;
  transition: var(--transition);
  z-index: 2;
}
.featured-popup__close:hover {
  background: var(--bg-4);
  border-color: var(--accent-border);
  color: var(--accent);
}
.featured-popup__head {
  text-align: center;
  margin-bottom: var(--s-7);
}
.featured-popup__head .section__title {
  margin-top: var(--s-3);
  font-size: var(--t-section-title-sm);
  letter-spacing: var(--ls-tight);
}
/* Grid override: 3 columns on desktop. .cards-grid default would give 4. */
.featured-popup__grid {
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-5);
  margin-bottom: var(--s-7);
}
@media (max-width: 960px) {
  .featured-popup__grid { grid-template-columns: repeat(2, 1fr); gap: var(--s-4); }
}
@media (max-width: 600px) {
  .featured-popup__grid { grid-template-columns: 1fr; gap: var(--s-4); }
}
.featured-popup__ctas {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: var(--s-3);
}
.featured-popup__ctas .btn {
  min-width: 180px;
}
@media (max-width: 720px) {
  .featured-popup {
    padding: var(--s-3);
    align-items: flex-start;
    /* Push panel down a bit on phones so it doesn't crowd the safe-area
       at the very top (notch / status bar). */
    padding-top: var(--s-8);
  }
  .featured-popup__panel {
    padding: var(--s-7) var(--s-5) var(--s-6);
    max-height: calc(100vh - var(--s-10));
  }
  .featured-popup__ctas {
    flex-direction: column;
    align-items: stretch;
  }
  .featured-popup__ctas .btn { width: 100%; min-width: 0; }
}

/* Body scroll lock while popup is visible — prevents the page underneath
   from scrolling when the user wheels inside the modal panel. */
body.popup-open {
  overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {
  .featured-popup,
  .featured-popup__panel {
    transition: none;
  }
  .featured-popup__panel {
    transform: none;
  }
}


/* ─── TOAST (floating notification) ─────────────────────────
   Used by the phone click-to-copy flow (and any future "Saved!" /
   "Copied!" notifications via dpeliteShowToast). Renders bottom-center,
   slides up + fades in, auto-dismisses after 2.4s. Single global
   element reused across triggers — spam-clicking doesn't stack toasts. */
.dpelite-toast {
  position: fixed;
  bottom: var(--s-7);
  left: 50%;
  transform: translateX(-50%) translateY(20px);
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-5);
  background: var(--success);
  color: var(--accent-fg);
  border-radius: var(--r-full);
  font-size: var(--t-sm);
  font-weight: 600;
  box-shadow: var(--shadow-lg);
  /* Below modal, above sticky bars + page content. */
  z-index: var(--z-toast);
  opacity: 0;
  pointer-events: none;
  transition: opacity 200ms ease, transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
  /* iOS safe-area for notched devices when toast lives at bottom. */
  margin-bottom: env(safe-area-inset-bottom, 0);
}
.dpelite-toast.is-visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
.dpelite-toast svg {
  flex-shrink: 0;
}
.dpelite-toast__text {
  white-space: nowrap;
}
@media (max-width: 540px) {
  /* Phone numbers can be long ("+1 (717) 227-3384"); allow wrapping
     on narrow viewports so the toast doesn't extend past the screen. */
  .dpelite-toast__text { white-space: normal; }
  .dpelite-toast { max-width: calc(100vw - var(--s-7)); }
}
@media (prefers-reduced-motion: reduce) {
  .dpelite-toast { transition: opacity 200ms ease; transform: translateX(-50%); }
  .dpelite-toast.is-visible { transform: translateX(-50%); }
}


/* ════════════════════════════════════════════════════════════════════
   MULTI-STEP FORM (shared component)
   ──────────────────────────────────────────────────────────────────
   Used by Reserve Now (tpl-buy-now), Trade-in (tpl-trade-in) and
   Consignment (tpl-sell-with-us). Moved here from pages/secure.css in
   v2.0.109 so all three stepped forms share ONE source of truth instead
   of secure.css owning the styling. JS driver: initMultiStep() in
   assets/js/main.js — generic, runs on any [data-dpelite-form] that
   contains .ms-step panels.
   ════════════════════════════════════════════════════════════════════ */

/* ─── PROGRESS (count + bar + step pills) ──────────────── */
.ms-progress { margin-bottom: var(--s-8); }
.ms-progress__count {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--s-3);
}
.ms-progress__count strong { color: var(--accent); }
.ms-progress__bar {
  height: 4px;
  background: var(--bg-3);
  border-radius: var(--r-full);
  overflow: hidden;
}
.ms-progress__fill {
  height: 100%;
  background: linear-gradient(90deg, var(--accent), var(--accent-dark));
  background: linear-gradient(90deg, var(--accent), color-mix(in srgb, var(--accent) 60%, white));
  border-radius: var(--r-full);
  transition: width 350ms cubic-bezier(0.4, 0, 0.2, 1);
}
.ms-progress__steps {
  /* v2.0.109 — flex (was grid repeat(3,1fr)) so the pill row distributes
     evenly for ANY step count: 3 (Reserve Now) or 4 (Trade-in / Consign).
     Each pill gets flex:1 — visually identical to the old 3-col grid. */
  display: flex;
  gap: var(--s-2);
  margin-top: var(--s-4);
}
.ms-progress__step {
  flex: 1;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.5px;
  color: var(--text-muted);
  padding: var(--s-2);
  text-align: center;
  border-radius: var(--r-sm);
  transition: var(--transition);
  line-height: 1.3;
}
.ms-progress__step.is-complete { color: var(--success); }
.ms-progress__step.is-active {
  color: var(--accent);
  background: var(--accent-soft);
}
@media (max-width: 540px) {
  /* Phones: the pill row gets too cramped for 3-4 labels — hide it and
     rely on the "Step X of Y" count + fill bar above. */
  .ms-progress__steps { display: none; }
}

/* ─── STEP PANELS ──────────────────────────────────────── */
@keyframes ms-slide-in {
  from { opacity: 0; transform: translateX(20px); }
  to   { opacity: 1; transform: translateX(0); }
}
.ms-step { display: none; }
.ms-step.is-active {
  display: block;
  animation: ms-slide-in 350ms cubic-bezier(0.4, 0, 0.2, 1);
}
.ms-step__head {
  text-align: center;
  margin-bottom: var(--s-6);
  padding-bottom: var(--s-5);
  border-bottom: 1px solid var(--border);
}
.ms-step__title {
  font-size: var(--t-xl);
  font-weight: 800;
  letter-spacing: -0.01em;
  margin-bottom: var(--s-2);
}
.ms-step__sub {
  font-size: var(--t-sm);
  color: var(--text-muted);
}
.ms-step__icon {
  width: 56px;
  height: 56px;
  border-radius: var(--r-md);
  background: var(--accent-soft);
  border: 1px solid var(--accent-border);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto var(--s-4);
}
.ms-step__icon svg {
  width: 28px;
  height: 28px;
}
.ms-nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-3);
  margin-top: var(--s-7);
  padding-top: var(--s-6);
  border-top: 1px solid var(--border);
}
.ms-nav--end { justify-content: flex-end; }
.ms-nav .btn { min-width: 140px; }
@media (max-width: 720px) {
  /* Stick the Continue/Back nav to the bottom so the user doesn't scroll
     past a tall step to reach it. */
  .ms-nav {
    position: sticky;
    bottom: 0;
    margin-inline: calc(-1 * var(--s-5));
    padding: var(--s-3) var(--s-5);
    background: rgba(var(--bg-rgb), 0.95);
    -webkit-backdrop-filter: blur(20px);
    backdrop-filter: blur(20px);
    border-top: 1px solid var(--border);
    z-index: var(--z-sticky);
  }
  .ms-nav .btn { min-width: 0; flex: 1; }
}

/* ─── REVIEW STEP (final-step summary) ─────────────────── */
.review-grid {
  display: grid;
  gap: var(--s-3);
  margin-bottom: var(--s-6);
}
.review-item {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--s-4);
  padding: var(--s-4) var(--s-5);
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  align-items: center;
}
.review-item__label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1.5px;
  color: var(--text-muted);
  font-weight: 600;
  margin-bottom: 2px;
}
.review-item__value {
  font-size: var(--t-sm);
  color: var(--text);
  font-weight: 600;
}
.review-item__value--empty {
  color: var(--text-faint);
  font-weight: 400;
  font-style: italic;
}
.review-item__edit {
  font-size: var(--t-xs);
  color: var(--accent);
  font-weight: 600;
  /* WCAG 2.5.5 tap target — 32px desktop / 44px mobile (see @media below). */
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding: var(--s-2) var(--s-3);
  border: 0;
  border-radius: var(--r-xs);
  background: var(--accent-soft);
  transition: var(--transition);
  cursor: pointer;
}
@media (max-width: 720px) {
  .review-item__edit { min-height: 44px; }
}
.review-item__edit:hover {
  background: var(--accent);
  color: var(--accent-fg);
}

/* ─── TERMS / CONFIRMATION CHECKBOX BLOCK ──────────────── */
.ms-terms {
  display: flex;
  gap: var(--s-3);
  align-items: flex-start;
  padding: var(--s-4);
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  margin-top: var(--s-5);
  cursor: pointer;
  transition: var(--transition);
}
.ms-terms:hover { border-color: var(--accent-border); }
.ms-terms input[type="checkbox"] {
  width: 20px;
  height: 20px;
  accent-color: var(--accent);
  cursor: pointer;
  margin-top: 1px;
  flex-shrink: 0;
}
.ms-terms__text {
  font-size: var(--t-sm);
  color: var(--text-soft);
  line-height: 1.5;
}
.ms-terms__text a {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 3px;
}
