/* ==========================================================================
   vtq-components.css — Van Thats Quick shared component layer
   --------------------------------------------------------------------------
   Owner: rebuild Agent 1 (design system). Consumed by the injection scripts
   the other agents own. Link it as:

       <link rel="stylesheet" href="/css/vtq-components.css?v=1">

   ...after /css/output.css so these rules win over Tailwind utilities of the
   same specificity, and alongside /css/lead-form.css (they do not collide:
   this file never touches a .vtq-lf-* selector).

   WHY PLAIN CSS, NOT TAILWIND
   `npm run build:css` purges against ./**\/*.html. Components injected by a
   JS script are invisible to that purge until the script has run, so a
   Tailwind-only component silently loses its classes on the next rebuild.
   The same reasoning is written at the top of lead-form.css. Follow it.

   TOKENS
   The --vtq-* layer below takes its STRUCTURE from lead-form.css (Soft Product
   8/16/pill radii, named motion curves, the same lime) but its INK AND SURFACE
   VALUES from the Tailwind grey ladder the 253 live pages actually render:
   gray-900 headings, gray-500 body, gray-400 faint, gray-200 borders,
   gray-800 dark surfaces. lead-form.css uses a warmer neutral ramp
   (#292929/#5d5d5d) scoped to .vtq-lf. Matching Tailwind rather than the form
   is deliberate: every component in this file gets injected INTO a Tailwind
   page and sits inches from that page's own headings. A band with its own
   grey is exactly the bolt-on look this rebuild exists to remove. The
   remaining delta between .vtq-lf and this file is logged as a follow-up in
   docs/rebuild/DESIGN-LANGUAGE.md — it is not something to fix mid-rebuild.

   Do not invent new raw hex in a component — add a token here.

   DARK MODE
   Class-based (.dark on <html>), per tailwind.config.js darkMode:"class".
   As shipped, 253 of 259 pages have no element that applies it — see
   docs/rebuild/DESIGN-LANGUAGE.md. Every component here is authored
   light-first and carries a .dark override so it stays correct if and when
   a real toggle lands.

   RESPONSIVE
   Verified at 390 / 834 / 1024 / 1440. Components size off their CONTAINER
   (container queries) not the viewport, because the same component renders
   full-bleed in a band and ~380px inside a hero column. No component uses a
   flex-basis for column sizing: `flex: 1 1 380px` turns that basis into a
   minimum HEIGHT the moment a media query flips the container to `column`.
   Grid + minmax() has no such failure mode, so everything here uses grid.
   ========================================================================== */

:root {
  /* --- ink ladder: Tailwind gray-900 / 500 / 400, as the live pages use ---
     RULE: --vtq-ink-faint is #9ca3af, which measures 2.54:1 on white and so
     FAILS WCAG AA (4.5:1) for body text. It is therefore reserved for
     non-informational pixels only — icons, inactive step dots, placeholders,
     decorative rules. Any text a customer needs to READ uses --vtq-ink-muted
     (#6b7280, 4.83:1 on white). The live pages use text-gray-400 for real
     fine print; this file deliberately does not. */
  --vtq-ink: #111827;
  --vtq-ink-muted: #6b7280;
  --vtq-ink-faint: #9ca3af;

  /* --- surfaces: white card on gray-50 tint, gray-200/300 rules --- */
  --vtq-surface: #ffffff;
  --vtq-surface-subtle: #f9fafb;
  --vtq-surface-hover: #f3f4f6;
  --vtq-border: #e5e7eb;
  --vtq-border-strong: #d1d5db;

  /* --- VTQ lime --- */
  --vtq-accent: #a3e635;
  --vtq-accent-strong: #84cc16;
  --vtq-accent-deep: #4d7c0f;
  --vtq-accent-ink: #1a2e05;
  --vtq-accent-soft: #f2fbe3;
  --vtq-accent-ring: rgba(163, 230, 53, 0.45);

  --vtq-success: #22c55e;
  --vtq-danger: #dc2626;
  --vtq-star: #fbbc04;

  /* --- radii: 8 control / 16 card / 24 shell / pill --- */
  --vtq-r-ctl: 8px;
  --vtq-r-card: 16px;
  --vtq-r-shell: 24px;
  --vtq-r-pill: 9999px;

  /* --- elevation ---
     card / card-hover are copied EXACTLY from tailwind.config.js so a
     component sits at the same height as the Tailwind `shadow-card` cards it
     is placed beside. float/lift are for objects that are meant to sit above
     the page (the quote widget), not among it. */
  --vtq-shadow-card: 0 1px 3px rgba(0, 0, 0, 0.04), 0 4px 12px rgba(0, 0, 0, 0.06);
  --vtq-shadow-card-hover: 0 4px 12px rgba(0, 0, 0, 0.08), 0 12px 40px rgba(0, 0, 0, 0.12);
  --vtq-shadow-soft: 0 20px 40px -10px rgba(0, 0, 0, 0.08);
  --vtq-shadow-float: 0 30px 60px -15px rgba(0, 0, 0, 0.12);
  --vtq-shadow-glow: 0 0 30px rgba(163, 230, 53, 0.4);

  /* --- motion: named curves only, never invented per component --- */
  --vtq-ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --vtq-ease-in: cubic-bezier(0.4, 0, 1, 1);
  --vtq-dur-instant: 150ms;
  --vtq-dur-quick: 200ms;
  --vtq-dur-enter: 340ms;
  --vtq-stagger: 26ms;

  /* --- the one hard accessibility number in this file --- */
  --vtq-tap: 44px;
}

/* Dark surfaces are gray-800/gray-700, NOT the surface-dark #171717 token.
   #171717 is used on exactly one element sitewide (the <main> shell); every
   dark CARD on every page is dark:bg-gray-800. Matching the cards is what
   makes these components disappear into the page. */
.dark {
  --vtq-ink: #ffffff;
  --vtq-ink-muted: #9ca3af;
  --vtq-ink-faint: #6b7280;
  --vtq-surface: #1f2937;
  --vtq-surface-subtle: #111827;
  --vtq-surface-hover: #374151;
  --vtq-border: #374151;
  --vtq-border-strong: #4b5563;
  --vtq-accent-deep: #bef264;
  --vtq-accent-soft: rgba(163, 230, 53, 0.10);
  --vtq-shadow-card: 0 1px 3px rgba(0, 0, 0, 0.3), 0 4px 12px rgba(0, 0, 0, 0.4);
  --vtq-shadow-card-hover: 0 4px 12px rgba(0, 0, 0, 0.4), 0 12px 40px rgba(0, 0, 0, 0.5);
  --vtq-shadow-soft: 0 20px 40px -10px rgba(0, 0, 0, 0.5);
  --vtq-shadow-float: 0 30px 60px -15px rgba(0, 0, 0, 0.6);
}

/* --------------------------------------------------------------------------
   Shared primitives
   -------------------------------------------------------------------------- */

/* Every component root opts into border-box for its subtree so a Tailwind
   page and an injected component agree on what `padding` means. */
.vtq-ladder *,
.vtq-trustbar *,
.vtq-rating *,
.vtq-q *,
.vtq-process * {
  box-sizing: border-box;
}

.vtq-ladder,
.vtq-trustbar,
.vtq-rating,
.vtq-q,
.vtq-process {
  font-family: Inter, "SF Pro Text", -apple-system, BlinkMacSystemFont,
    "Helvetica Neue", Helvetica, Arial, sans-serif;
  letter-spacing: -0.15px;
  color: var(--vtq-ink);
  -webkit-font-smoothing: antialiased;
}

/* Section eyebrow + heading, so every band this file introduces shares one
   rhythm rather than each agent picking their own Tailwind heading string. */
.vtq-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--vtq-accent-deep);
  margin: 0 0 10px;
}

.vtq-band-title {
  font-size: clamp(1.5rem, 1.15rem + 1.6vw, 2.25rem);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: var(--vtq-ink);
  margin: 0 0 8px;
}

.vtq-band-sub {
  font-size: clamp(0.9375rem, 0.9rem + 0.2vw, 1.0625rem);
  line-height: 1.55;
  color: var(--vtq-ink-muted);
  margin: 0 0 24px;
  max-width: 62ch;
}

/* --------------------------------------------------------------------------
   Band wrappers — the canonical section rhythm, so five agents do not each
   pick their own Tailwind padding string. These are exact equivalents of the
   dominant live pattern:
     <section class="py-12 px-6 md:px-12">  ->  <section class="vtq-band">
     <div class="max-w-6xl mx-auto">        ->  <div class="vtq-band__inner">
   Either spelling is fine; do not mix them within one band.
   -------------------------------------------------------------------------- */

.vtq-band {
  padding: 3rem 1.5rem;
}

@media (min-width: 768px) {
  .vtq-band {
    padding-left: 3rem;
    padding-right: 3rem;
  }
}

/* The alternating tint, == bg-gray-50 dark:bg-gray-900/50 */
.vtq-band--tint {
  background: var(--vtq-surface-subtle);
}

.vtq-band__inner {
  max-width: 72rem; /* == max-w-6xl */
  margin-inline: auto;
  min-width: 0;
}

.vtq-band__inner--prose {
  max-width: 48rem; /* == max-w-3xl */
}

/* --------------------------------------------------------------------------
   .fade-up SAFETY — do not remove.

   Every page ships `.fade-up { opacity: 0; transform: translateY(12px) }` in
   an inline <style>, and relies on an IntersectionObserver at the BOTTOM of
   the page to set opacity back to 1. Two ways that fails for us:
     1. JS is blocked/errors -> the element is invisible forever.
     2. A component injected into the DOM AFTER that observer has already run
        is never observed, so it is never revealed. Every component in this
        file is injected by a script, so this is the likely case, not the
        edge case.
   Rather than ask four agents to remember it, any .fade-up that lands inside
   one of our component roots is forced visible here. Entrance animation for
   these components comes from their own keyframes instead.
   -------------------------------------------------------------------------- */
.vtq-ladder .fade-up,
.vtq-trustbar .fade-up,
.vtq-rating .fade-up,
.vtq-q .fade-up,
.vtq-process .fade-up,
.vtq-ladder.fade-up,
.vtq-trustbar.fade-up,
.vtq-rating.fade-up,
.vtq-q.fade-up,
.vtq-process.fade-up {
  opacity: 1 !important;
  transform: none !important;
}

/* Visually hidden but read by screen readers and by Google. */
.vtq-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

@media (prefers-reduced-motion: reduce) {
  .vtq-ladder *,
  .vtq-trustbar *,
  .vtq-rating *,
  .vtq-q *,
  .vtq-process * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ==========================================================================
   1. PRICE LADDER  —  .vtq-ladder
   --------------------------------------------------------------------------
   A real <table> with a real <caption>, <thead> and <th scope>. One markup,
   one source of truth for the numbers. The brief records that contradictory
   price ladders have already shipped on this site twice; a card layout for
   mobile plus a table for desktop would mean writing every price twice, so
   this component deliberately refuses that and solves mobile with scrolling
   instead.

   Beating JunkBunk's table specifically:
     - their first column scrolls away on a phone, so by the time you reach
       the price you no longer know which row you are on. Ours is sticky.
     - they give no scroll affordance. Ours has a CSS-only right-edge fade
       that vanishes when you reach the end (background-attachment: local).
     - they have no recommended row and no way to act from the table. Ours
       has --featured and a >=44px per-row CTA.

   MARKUP CONTRACT
     <div class="vtq-ladder">
       <div class="vtq-ladder__scroll" tabindex="0" role="region"
            aria-label="Rubbish removal prices">
         <table class="vtq-ladder__table">
           <caption class="vtq-sr">...</caption>
           <thead><tr>
             <th scope="col" class="vtq-ladder__th">Load size</th>
             <th scope="col" class="vtq-ladder__th vtq-ladder__th--num">Cubic yards</th>
             ... Bin bags / Max weight / Time on site ...
             <th scope="col" class="vtq-ladder__th vtq-ladder__th--num">Price</th>
             <th scope="col" class="vtq-ladder__th"><span class="vtq-sr">Book</span></th>
           </tr></thead>
           <tbody>
             <tr class="vtq-ladder__row vtq-ladder__row--featured">
               <th scope="row" class="vtq-ladder__rowhead">
                 <span class="vtq-ladder__name">Minimum load</span>
                 <span class="vtq-ladder__note">Most booked</span>
               </th>
               <td class="vtq-ladder__td vtq-ladder__td--num">1</td>
               ...
               <td class="vtq-ladder__td vtq-ladder__td--num">
                 <span class="vtq-ladder__price">£69</span>
               </td>
               <td class="vtq-ladder__td vtq-ladder__td--act">
                 <a class="vtq-ladder__pick" href="#quote">Book</a>
               </td>
             </tr>
           </tbody>
         </table>
       </div>
       <p class="vtq-ladder__foot">...</p>
     </div>

   `tabindex="0"` on the scroller is required, not decorative: a keyboard-only
   user cannot reach an overflowing region otherwise. role/aria-label stop
   that tabstop being announced as an unlabelled group.
   -------------------------------------------------------------------------- */

.vtq-ladder {
  --vtq-ladder-fade: 28px;
  width: 100%;
  max-width: 100%;
  min-width: 0; /* lets it shrink inside a grid/flex parent instead of pushing the body wide */
}

.vtq-ladder__scroll {
  width: 100%;
  max-width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  /* Keeps a fast horizontal swipe inside the table instead of chaining out to
     the document and triggering back-navigation on iOS. */
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  border: 1px solid var(--vtq-border);
  border-radius: var(--vtq-r-card);
  background: var(--vtq-surface);
  box-shadow: var(--vtq-shadow-card);

  /* CSS-only scroll affordance. Two layers pinned to the scrollport
     (background-attachment: scroll -> stays put) and two pinned to the
     content (local -> slides away). Where the content layer has slid past,
     the scrollport layer shows through as a fade. Reaching either end covers
     that end's fade with the matching cover layer. No JS, no scroll listener. */
  background-image:
    /* right fade, fixed to the viewport of the scroller */
    linear-gradient(to left, rgba(0, 0, 0, 0.10), rgba(0, 0, 0, 0)),
    /* right cover, travels with the content */
    linear-gradient(to left, var(--vtq-surface), var(--vtq-surface)),
    /* left fade */
    linear-gradient(to right, rgba(0, 0, 0, 0.10), rgba(0, 0, 0, 0)),
    /* left cover */
    linear-gradient(to right, var(--vtq-surface), var(--vtq-surface));
  background-position: right center, right center, left center, left center;
  background-repeat: no-repeat;
  background-size:
    var(--vtq-ladder-fade) 100%,
    var(--vtq-ladder-fade) 100%,
    var(--vtq-ladder-fade) 100%,
    var(--vtq-ladder-fade) 100%;
  background-attachment: scroll, local, scroll, local;
}

.vtq-ladder__scroll:focus-visible {
  outline: none;
  border-color: var(--vtq-accent-strong);
  box-shadow: 0 0 0 3px var(--vtq-accent-ring);
}

.vtq-ladder__table {
  width: 100%;
  /* The min-width is what makes the thing scroll rather than crush six
     columns into 390px. Below this the table is unreadable; above it, the
     table simply fills the container and the fades never appear. */
  min-width: 640px;
  border-collapse: separate; /* separate, not collapse: `collapse` drops the
                                borders on position:sticky cells in Safari */
  border-spacing: 0;
  background: transparent;
  font-size: 14px;
}

.vtq-ladder__th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--vtq-surface-subtle);
  color: var(--vtq-ink-muted);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-align: left;
  white-space: nowrap;
  padding: 12px 14px;
  border-bottom: 1px solid var(--vtq-border);
}

.vtq-ladder__th--num,
.vtq-ladder__td--num {
  text-align: right;
}

/* The row header is the sticky column. It has to repaint its own background
   and its own right-hand edge, because it is painted over the cells it
   scrolls above. */
.vtq-ladder__rowhead,
.vtq-ladder__th:first-child {
  position: sticky;
  left: 0;
  z-index: 3;
  background: var(--vtq-surface);
  text-align: left;
  padding: 14px;
  font-weight: 600;
  color: var(--vtq-ink);
  white-space: nowrap;
  box-shadow: 1px 0 0 var(--vtq-border);
}

.vtq-ladder__th:first-child {
  z-index: 4; /* corner cell: sticky in both axes, must sit above both */
  background: var(--vtq-surface-subtle);
}

.vtq-ladder__name {
  display: block;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.25;
}

.vtq-ladder__note {
  display: block;
  margin-top: 2px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--vtq-accent-deep);
}

.vtq-ladder__td {
  padding: 14px;
  color: var(--vtq-ink-muted);
  border-bottom: 1px solid var(--vtq-border);
  white-space: nowrap;
  vertical-align: middle;
}

.vtq-ladder__rowhead {
  border-bottom: 1px solid var(--vtq-border);
}

.vtq-ladder__row:last-child .vtq-ladder__td,
.vtq-ladder__row:last-child .vtq-ladder__rowhead {
  border-bottom: 0;
}

/* DELIBERATE DIVERGENCE from the live price cards, which render the figure as
   `text-2xl font-extrabold gradient-text` — the lime #a3e635 -> #84cc16
   gradient clipped to the text. On white that lands at roughly 1.9:1 contrast,
   which fails WCAG AA (4.5:1) and even the 3:1 large-text allowance. The price
   is the single most important number on the page and it is currently the
   least legible thing on it. Solid ink here; lime is kept for the chrome of
   the featured row, where it is decoration rather than information. */
.vtq-ladder__price {
  font-size: 18px;
  font-weight: 700;
  color: var(--vtq-ink);
  /* Tabular figures so £69 / £185 / £315 / £495 align on the decimal column
     down the ladder instead of drifting. */
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

.vtq-ladder__row:hover .vtq-ladder__td,
.vtq-ladder__row:hover .vtq-ladder__rowhead {
  background: var(--vtq-surface-hover);
}

/* Featured row: the tier we want chosen. Marked with a lime left edge drawn
   as an inset shadow on the sticky cell, so it survives horizontal scroll. */
.vtq-ladder__row--featured .vtq-ladder__rowhead {
  background: var(--vtq-accent-soft);
  box-shadow: inset 3px 0 0 var(--vtq-accent-strong), 1px 0 0 var(--vtq-border);
}

.vtq-ladder__row--featured .vtq-ladder__td {
  background: var(--vtq-accent-soft);
}

.vtq-ladder__row--featured:hover .vtq-ladder__td,
.vtq-ladder__row--featured:hover .vtq-ladder__rowhead {
  background: var(--vtq-accent-soft);
}

.vtq-ladder__td--act {
  text-align: right;
  padding: 8px 14px;
}

.vtq-ladder__pick {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Tap target. 44px is the floor, not the target — see --vtq-tap. */
  min-height: var(--vtq-tap);
  min-width: 88px;
  padding: 0 18px;
  border-radius: var(--vtq-r-pill);
  border: 1px solid var(--vtq-border-strong);
  background: var(--vtq-surface);
  color: var(--vtq-ink);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--vtq-dur-instant) var(--vtq-ease-out),
    border-color var(--vtq-dur-instant) var(--vtq-ease-out),
    color var(--vtq-dur-instant) var(--vtq-ease-out);
}

.vtq-ladder__pick:hover {
  background: var(--vtq-surface-hover);
  border-color: var(--vtq-ink-faint);
}

.vtq-ladder__pick:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--vtq-accent-ring);
}

.vtq-ladder__row--featured .vtq-ladder__pick {
  background: var(--vtq-accent);
  border-color: var(--vtq-accent-strong);
  color: var(--vtq-accent-ink);
}

.vtq-ladder__row--featured .vtq-ladder__pick:hover {
  background: var(--vtq-accent-strong);
}

.vtq-ladder__foot {
  margin: 12px 2px 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--vtq-ink-muted);
}

/* Scroll hint, shown only while the table can actually scroll. Hidden from
   assistive tech: the region already announces itself. */
.vtq-ladder__hint {
  display: none;
  align-items: center;
  gap: 5px;
  margin: 0 2px 10px;
  font-size: 12px;
  font-weight: 500;
  color: var(--vtq-ink-muted);
}

/* The hint is viewport-gated rather than container-gated on purpose: CSS
   cannot ask "is this element currently overflowing". Below 720px the table
   always overflows, at and above it never does (see the padding rule below),
   so the viewport is an exact proxy here. */
@media (max-width: 719.98px) {
  .vtq-ladder__hint {
    display: flex;
  }
  .vtq-ladder__table {
    font-size: 13px;
  }
}

/* Tighter cells up to 1024px. Measured: with 14px side padding the 7-column
   table wants 728px, but the band's inner container at an 834px viewport is
   720px — an 8px overflow, which buys a scrollbar, a scroll fade and a
   sticky column for nothing. Dropping to 12px saves 4px per cell (28px
   total) and the table fits exactly. Do not "simplify" this back to 14px. */
@media (max-width: 1023.98px) {
  .vtq-ladder__th,
  .vtq-ladder__td,
  .vtq-ladder__rowhead {
    padding-left: 12px;
    padding-right: 12px;
  }
}

/* ==========================================================================
   2. TRUST BAR  —  .vtq-trustbar
   --------------------------------------------------------------------------
   A compact proof row that sits directly under the hero. Sizes off its
   container, so the same markup works full-bleed or in a 380px column.

   ⛔ COPY CONSTRAINT, non-negotiable: VTQ holds no Environment Agency waste
   carrier registration. Nothing placed in this component may say or imply
   "licensed waste carrier", "registered waste carrier" or "EA registered".
   scripts/check-site.mjs fails the build on those strings and it is right to.

   MARKUP CONTRACT
     <ul class="vtq-trustbar">
       <li class="vtq-trustbar__item">
         <span class="vtq-trustbar__icon material-icons-round" aria-hidden="true">schedule</span>
         <span class="vtq-trustbar__text">
           <strong class="vtq-trustbar__lead">Same day</strong>
           <span class="vtq-trustbar__sub">Booked before 11am</span>
         </span>
       </li>
       ...
     </ul>
   Add .vtq-trustbar--boxed for a bordered card treatment on a plain section.
   -------------------------------------------------------------------------- */

.vtq-trustbar {
  container-type: inline-size;
  container-name: vtqtrust;
  display: grid;
  /* auto-fit + minmax, never flex-basis. A `flex: 1 1 220px` here would
     become a 220px MIN-HEIGHT per item the moment the bar wrapped to a
     column, which is the trap called out in the brief. */
  /* 170px, measured not guessed: at 200px a 722px container (an 834px iPad)
     fits only 3 tracks, leaving the fourth proof point orphaned on its own
     row. 170px fits 4 there, still collapses to 1 at 326px (a 390px phone),
     and auto-fit collapses the spare tracks above 900px. Tuned for the
     four-item bar this component is specified for. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 170px), 1fr));
  gap: 2px;
  list-style: none;
  margin: 0;
  padding: 0;
  width: 100%;
  min-width: 0;
  border-radius: var(--vtq-r-card);
  overflow: hidden;
  background: var(--vtq-border);
}

.vtq-trustbar--boxed {
  border: 1px solid var(--vtq-border);
  box-shadow: var(--vtq-shadow-card);
}

.vtq-trustbar__item {
  display: flex;
  align-items: center;
  gap: 10px;
  /* min-height, not height: the tap floor must not become a clamp that
     truncates a two-line proof point at 390px. */
  min-height: calc(var(--vtq-tap) + 12px);
  padding: 12px 14px;
  background: var(--vtq-surface);
  margin: 0;
}

/* Interactive proof points (a link to reviews, a tel: link) inherit the row
   and must keep the tap floor themselves, not rely on the parent. */
a.vtq-trustbar__item,
.vtq-trustbar__item > a {
  min-height: var(--vtq-tap);
  color: inherit;
  text-decoration: none;
  transition: background var(--vtq-dur-instant) var(--vtq-ease-out);
}

/* Linked proof point, first-class.

   The bar is a <ul> grid whose children are the <li>s, so making the whole row
   tappable means the ANCHOR has to be the .vtq-trustbar__item and the <li> has
   to stop being the visual cell. Put this on the <li>:

     <li class="vtq-trustbar__item--link">
       <a class="vtq-trustbar__item" href="…">…</a>
     </li>

   Added at the trust-layer agent's request; it replaces the local
   `.vtq-tl-bar-link` workaround in css/trust-layer.css, which can now be
   deleted. Keeping the seam here means one definition of what a trust-bar row
   is, which is the entire point of this file. */
.vtq-trustbar__item--link {
  display: grid;
  margin: 0;
  padding: 0;
  min-width: 0;
  background: none;
  min-height: 0;
}

a.vtq-trustbar__item:hover {
  background: var(--vtq-surface-hover);
}

a.vtq-trustbar__item:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--vtq-accent-strong);
}

.vtq-trustbar__icon {
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--vtq-accent-deep);
  font-size: 22px !important; /* beats the Material Icons Round base rule */
  line-height: 1;
}

.vtq-trustbar__text {
  display: flex;
  flex-direction: column;
  min-width: 0; /* lets long proof text ellipsise instead of widening the grid track */
}

.vtq-trustbar__lead {
  font-size: 13px;
  font-weight: 600;
  color: var(--vtq-ink);
  line-height: 1.25;
}

.vtq-trustbar__sub {
  font-size: 12px;
  color: var(--vtq-ink-muted);
  line-height: 1.3;
  margin-top: 1px;
}

/* Once the bar has room for a real row, tighten it into a single strip. */
@container vtqtrust (min-width: 700px) {
  .vtq-trustbar__item {
    padding: 14px 18px;
  }
}

/* ==========================================================================
   3. RATING DISPLAY  —  .vtq-rating
   --------------------------------------------------------------------------
   Star rating + count + source attribution, for use inline in copy, under a
   headline or in a trust bar. This is NOT a replacement for .rev-badge in
   review-embed.css — that is the full Google/Yell card and stays. This is the
   compact inline form the card has no variant for.

   ⛔ Numbers must be real. review-embed.css already carries the rule
   "never seed fakes" and it applies here identically.

   MARKUP CONTRACT
     <p class="vtq-rating">
       <span class="vtq-rating__stars" style="--rating:4.9" role="img"
             aria-label="4.9 out of 5 stars"></span>
       <span class="vtq-rating__score">4.9</span>
       <span class="vtq-rating__count">from 41 reviews</span>
       <span class="vtq-rating__src">on <strong>Google</strong></span>
     </p>
   --rating is a NUMBER 0-5 and drives the partial fill. Set it inline; it is
   data, not styling.
   -------------------------------------------------------------------------- */

.vtq-rating {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 8px;
  margin: 0;
  font-size: 13px;
  color: var(--vtq-ink-muted);
  line-height: 1.3;
}

.vtq-rating__stars {
  --rating: 5;
  --vtq-star-pct: calc(var(--rating) / 5 * 100%);
  display: inline-block;
  font-size: 16px;
  line-height: 1;
  letter-spacing: 2px;
  flex: 0 0 auto;
}

/* Same gradient-clip technique as .rev-stars in review-embed.css, kept
   identical on purpose so a badge and an inline rating never show two
   different shades of star on one page. */
.vtq-rating__stars::before {
  content: "\2605\2605\2605\2605\2605";
  background: linear-gradient(
    90deg,
    var(--vtq-star) var(--vtq-star-pct),
    var(--vtq-border-strong) var(--vtq-star-pct)
  );
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}

.vtq-rating__score {
  font-size: 15px;
  font-weight: 700;
  color: var(--vtq-ink);
  font-variant-numeric: tabular-nums;
}

.vtq-rating__count {
  color: var(--vtq-ink-muted);
}

.vtq-rating__src {
  color: var(--vtq-ink-muted);
}

.vtq-rating__src strong {
  font-weight: 600;
  color: var(--vtq-ink-muted);
}

/* Larger variant for a hero or a section heading. */
.vtq-rating--lg {
  font-size: 14px;
  gap: 6px 10px;
}
.vtq-rating--lg .vtq-rating__stars {
  font-size: 20px;
}
.vtq-rating--lg .vtq-rating__score {
  font-size: 20px;
}

/* Link form keeps the tap floor. */
a.vtq-rating {
  text-decoration: none;
  min-height: var(--vtq-tap);
  align-content: center;
}
a.vtq-rating:hover .vtq-rating__src strong {
  color: var(--vtq-accent-deep);
}
a.vtq-rating:focus-visible {
  outline: none;
  border-radius: var(--vtq-r-ctl);
  box-shadow: 0 0 0 3px var(--vtq-accent-ring);
}

/* ==========================================================================
   4. INSTANT-QUOTE WIDGET SHELL  —  .vtq-q
   --------------------------------------------------------------------------
   ⚠️ STATUS: SUPERSEDED, PENDING ORCHESTRATOR DECISION — DO NOT BUILD ON THIS.

   This shell was written for Agent 2 to consume. Agent 2 instead shipped a
   complete parallel implementation — css/instant-quote.css (.vtq-iq-*, 71
   classes, its own token layer), js/instant-quote.js, functions/api/slots.js
   and add-instant-quote.js — which references neither .vtq-q nor this
   stylesheet. Theirs is wired end to end; this one is a shell with no JS.

   So this section is almost certainly dead weight that should be DELETED
   before deploy. It is left in place only because css/vtq-components.css is
   untracked, so a deletion here is not recoverable from git, and the call is
   the orchestrator's rather than mine.

   If .vtq-iq ships: delete everything from this banner to the start of
   section 5 (~330 lines, ~9KB). Nothing else in this file references it.

   The contract below is retained as documentation of the intended state
   machine, which is worth reading even if the CSS goes: states are driven by
   ATTRIBUTES, so the JS sets attributes and never juggles class names.
   --------------------------------------------------------------------------
   Shell only. Every visual state is driven by an ATTRIBUTE on the root or on
   the element itself — Agent 2 sets attributes, never juggles class names.
   That keeps the state machine in one place and means CSS and JS cannot drift.

   ROOT ATTRIBUTES
     data-step   = "1" | "2" | "3"     which panel is visible
     data-state  = "idle" | "loading" | "quoted" | "error" | "booked"
     data-busy   (presence)            in-flight; disables actions, spins CTA

   PANELS
     .vtq-q__panel[data-panel="1|2|3"] — the panel whose data-panel equals the
     root's data-step is shown. All others are display:none. Nothing else is
     required; do not also set hidden.

   PRICE REVEAL
     .vtq-q__reveal is display:none until data-state is "quoted" or "booked".
     Put the number in .vtq-q__price-num. Do NOT animate a count-up: an
     advertised price is a binding offer under the Consumer Rights Act 2015
     and a number that visibly changes reads as negotiable.

   FULL CLASS LIST (this is the contract — nothing here is optional naming)
     .vtq-q                      root
     .vtq-q__shell               the card
     .vtq-q__head / __title / __sub
     .vtq-q__rail                step indicator
       .vtq-q__rail-step[data-state="todo|active|done"]
         .vtq-q__dot             the numbered circle
         .vtq-q__rail-label
     .vtq-q__body
     .vtq-q__panel[data-panel]
     .vtq-q__fields              stacked field grid
     .vtq-q__row2                two-up above 470px of CONTAINER width
     .vtq-q__field / __label / __opt / __hint
     .vtq-q__input / __select / __textarea
     .vtq-q__err[data-show]
     .vtq-q__options             the load-size radiogroup
       .vtq-q__option[aria-checked]
         .vtq-q__option-icon / __option-name / __option-desc / __option-price
     .vtq-q__reveal
       .vtq-q__reveal-label
       .vtq-q__price / __price-num / __price-note
       .vtq-q__reveal-list / .vtq-q__reveal-item
     .vtq-q__actions
       .vtq-q__btn .vtq-q__btn--primary | .vtq-q__btn--ghost
     .vtq-q__trust
     .vtq-q__status              aria-live="polite" region
   JS HOOKS (attributes, so styling never depends on them)
     [data-q-next] [data-q-back] [data-q-submit] [data-q-price] [data-q-size]
   -------------------------------------------------------------------------- */

.vtq-q {
  /* Container, not viewport: this widget renders ~380px wide in a hero
     column and ~640px in a section band on the very same 1440px screen. */
  container-type: inline-size;
  container-name: vtqq;
  display: block;
  width: 100%;
  min-width: 0;
  font-size: 14px;
  line-height: 1.5;
}

.vtq-q__shell {
  background: var(--vtq-surface);
  border: 1px solid var(--vtq-border);
  border-radius: var(--vtq-r-shell);
  box-shadow: var(--vtq-shadow-float);
  overflow: hidden;
  max-width: 640px;
  margin: 0 auto;
}

.vtq-q__head {
  padding: 24px 22px 0;
}

.vtq-q__title {
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.25;
  color: var(--vtq-ink);
  margin: 0 0 6px;
}

.vtq-q__sub {
  font-size: 13px;
  color: var(--vtq-ink-muted);
  margin: 0;
}

/* ---- step rail ---- */

.vtq-q__rail {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 18px 22px 0;
  margin: 0;
  list-style: none;
}

.vtq-q__rail-step {
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
  color: var(--vtq-ink-faint);
}

/* The connector between steps grows; the steps themselves do not. */
.vtq-q__rail-step:not(:last-child)::after {
  content: "";
  flex: 1 1 auto;
  height: 2px;
  min-width: 12px;
  margin: 0 4px;
  border-radius: 2px;
  background: var(--vtq-border);
  transition: background var(--vtq-dur-quick) var(--vtq-ease-out);
}

.vtq-q__rail-step:not(:last-child) {
  flex: 1 1 auto;
}

.vtq-q__dot {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--vtq-r-pill);
  border: 1px solid var(--vtq-border-strong);
  background: var(--vtq-surface);
  font-size: 11px;
  font-weight: 600;
  color: var(--vtq-ink-faint);
  transition: background var(--vtq-dur-quick) var(--vtq-ease-out),
    border-color var(--vtq-dur-quick) var(--vtq-ease-out),
    color var(--vtq-dur-quick) var(--vtq-ease-out);
}

.vtq-q__rail-label {
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
}

.vtq-q__rail-step[data-state="active"] .vtq-q__dot {
  background: var(--vtq-accent);
  border-color: var(--vtq-accent-strong);
  color: var(--vtq-accent-ink);
}
.vtq-q__rail-step[data-state="active"] {
  color: var(--vtq-ink);
}

.vtq-q__rail-step[data-state="done"] .vtq-q__dot {
  background: var(--vtq-accent-strong);
  border-color: var(--vtq-accent-strong);
  color: #ffffff;
}
.vtq-q__rail-step[data-state="done"]::after {
  background: var(--vtq-accent-strong);
}

/* Labels are noise at hero-column width; the dots still carry the position. */
@container vtqq (max-width: 419px) {
  .vtq-q__rail-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* ---- body / panels ---- */

.vtq-q__body {
  padding: 20px 22px 22px;
}

.vtq-q__panel {
  display: none;
}

.vtq-q[data-step="1"] .vtq-q__panel[data-panel="1"],
.vtq-q[data-step="2"] .vtq-q__panel[data-panel="2"],
.vtq-q[data-step="3"] .vtq-q__panel[data-panel="3"] {
  display: block;
  animation: vtq-q-rise var(--vtq-dur-enter) var(--vtq-ease-out) both;
}

/* No data-step yet (progressive enhancement / JS not booted): show step 1 so
   the widget is never a blank card. */
.vtq-q:not([data-step]) .vtq-q__panel[data-panel="1"] {
  display: block;
}

@keyframes vtq-q-rise {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* ---- fields ---- */

.vtq-q__fields {
  display: grid;
  gap: 14px;
}

/* Two-up only when the CONTAINER can take it. */
@container vtqq (min-width: 470px) {
  .vtq-q__row2 {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 14px;
  }
}

.vtq-q__field {
  display: block;
  min-width: 0;
}

.vtq-q__label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  font-weight: 500;
  color: var(--vtq-ink);
  margin-bottom: 6px;
  user-select: none;
}

.vtq-q__opt {
  font-size: 12px;
  font-weight: 400;
  color: var(--vtq-ink-muted);
  margin-left: auto;
}

.vtq-q__input,
.vtq-q__select,
.vtq-q__textarea {
  width: 100%;
  min-width: 0;
  font-family: inherit;
  letter-spacing: -0.15px;
  /* 16px, not 14px. Anything under 16px makes iOS Safari zoom the viewport on
     focus, which on a 390px ad landing page throws the user out of the form. */
  font-size: 16px;
  min-height: var(--vtq-tap);
  color: var(--vtq-ink);
  background: var(--vtq-surface);
  border: 1px solid var(--vtq-border);
  border-radius: var(--vtq-r-ctl);
  padding: 10px 12px;
  outline: none;
  -webkit-appearance: none;
  appearance: none;
  transition: border-color var(--vtq-dur-instant) var(--vtq-ease-out),
    box-shadow var(--vtq-dur-instant) var(--vtq-ease-out);
}

.vtq-q__textarea {
  min-height: 84px;
  resize: vertical;
}

.vtq-q__select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239e9e9e' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 16px;
  padding-right: 34px;
}

.vtq-q__input::placeholder,
.vtq-q__textarea::placeholder {
  color: var(--vtq-ink-faint);
}

.vtq-q__input:focus-visible,
.vtq-q__select:focus-visible,
.vtq-q__textarea:focus-visible {
  border-color: var(--vtq-accent-strong);
  box-shadow: 0 0 0 3px var(--vtq-accent-ring);
}

.vtq-q__input[aria-invalid="true"],
.vtq-q__select[aria-invalid="true"],
.vtq-q__textarea[aria-invalid="true"] {
  border-color: var(--vtq-danger);
  box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.15);
}

.vtq-q__hint {
  display: block;
  margin-top: 5px;
  font-size: 12px;
  color: var(--vtq-ink-muted);
}

.vtq-q__err {
  display: none;
  align-items: center;
  gap: 5px;
  margin-top: 5px;
  font-size: 12px;
  color: var(--vtq-danger);
}

.vtq-q__err[data-show] {
  display: flex;
}

/* ---- load-size options (role="radiogroup") ---- */

.vtq-q__options {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  align-items: stretch;
}

@container vtqq (min-width: 470px) {
  .vtq-q__options {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.vtq-q__option {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  /* height:100% + align-items:stretch on the grid keeps all four card prices
     on one line even when one description wraps. */
  height: 100%;
  min-height: calc(var(--vtq-tap) + 44px);
  padding: 14px 8px 12px;
  border: 1px solid var(--vtq-border);
  border-radius: var(--vtq-r-card);
  background: var(--vtq-surface);
  cursor: pointer;
  text-align: center;
  transition: border-color var(--vtq-dur-instant) var(--vtq-ease-out),
    background var(--vtq-dur-instant) var(--vtq-ease-out),
    box-shadow var(--vtq-dur-instant) var(--vtq-ease-out);
}

.vtq-q__option:hover {
  border-color: var(--vtq-ink-faint);
  background: var(--vtq-surface-hover);
}

.vtq-q__option:focus-visible {
  outline: none;
  border-color: var(--vtq-accent-strong);
  box-shadow: 0 0 0 3px var(--vtq-accent-ring);
}

.vtq-q__option[aria-checked="true"] {
  border-color: var(--vtq-accent-strong);
  background: var(--vtq-accent-soft);
  box-shadow: 0 0 0 1px var(--vtq-accent-strong);
}

.vtq-q__option-icon {
  width: 40px;
  height: 40px;
  color: var(--vtq-ink-faint);
  transition: color var(--vtq-dur-instant) var(--vtq-ease-out);
}

.vtq-q__option[aria-checked="true"] .vtq-q__option-icon {
  color: var(--vtq-accent-strong);
}

.vtq-q__option-name {
  display: block;
  font-size: 13px;
  font-weight: 600;
  color: var(--vtq-ink);
  line-height: 1.25;
}

.vtq-q__option-desc {
  display: block;
  margin-top: 2px;
  font-size: 12px;
  line-height: 1.3;
  color: var(--vtq-ink-muted);
}

.vtq-q__option-price {
  display: block;
  margin-top: auto;
  padding-top: 8px;
  font-size: 13px;
  font-weight: 600;
  color: var(--vtq-ink);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* ---- price reveal ---- */

.vtq-q__reveal {
  display: none;
  margin-top: 16px;
  padding: 16px 18px;
  border: 1px solid var(--vtq-accent-strong);
  border-radius: var(--vtq-r-card);
  background: var(--vtq-accent-soft);
}

.vtq-q[data-state="quoted"] .vtq-q__reveal,
.vtq-q[data-state="booked"] .vtq-q__reveal {
  display: block;
  animation: vtq-q-rise var(--vtq-dur-enter) var(--vtq-ease-out) both;
}

.vtq-q__reveal-label {
  display: block;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--vtq-accent-deep);
  margin-bottom: 4px;
}

.vtq-q__price {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 10px;
}

.vtq-q__price-num {
  font-size: clamp(2rem, 1.6rem + 1.6cqi, 2.5rem);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.05;
  color: var(--vtq-ink);
  font-variant-numeric: tabular-nums;
}

.vtq-q__price-note {
  font-size: 12px;
  color: var(--vtq-ink-muted);
}

.vtq-q__reveal-list {
  display: grid;
  gap: 5px;
  margin: 12px 0 0;
  padding: 0;
  list-style: none;
}

.vtq-q__reveal-item {
  display: flex;
  align-items: flex-start;
  gap: 7px;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--vtq-ink-muted);
}

.vtq-q__reveal-item::before {
  content: "\2713";
  flex: 0 0 auto;
  font-weight: 700;
  color: var(--vtq-accent-strong);
  line-height: 1.45;
}

/* ---- actions ---- */

.vtq-q__actions {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 18px;
}

.vtq-q__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  /* The live .vtq-lf-btn computes to ~43px. This is the corrected floor. */
  min-height: var(--vtq-tap);
  padding: 0 22px;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.15px;
  border: 1px solid transparent;
  border-radius: var(--vtq-r-pill);
  cursor: pointer;
  white-space: nowrap;
  text-decoration: none;
  transition: background var(--vtq-dur-instant) var(--vtq-ease-out),
    border-color var(--vtq-dur-instant) var(--vtq-ease-out),
    box-shadow var(--vtq-dur-instant) var(--vtq-ease-out),
    opacity var(--vtq-dur-instant) var(--vtq-ease-out);
}

.vtq-q__btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--vtq-accent-ring);
}

.vtq-q__btn:disabled,
.vtq-q__btn[aria-disabled="true"] {
  pointer-events: none;
  opacity: 0.5;
}

.vtq-q__btn--primary {
  flex: 1 1 auto; /* auto basis, never a px basis — see the header note */
  background: var(--vtq-accent);
  color: var(--vtq-accent-ink);
}

.vtq-q__btn--primary:hover {
  background: var(--vtq-accent-strong);
}

.vtq-q__btn--ghost {
  flex: 0 0 auto;
  background: transparent;
  color: var(--vtq-ink-muted);
  border-color: var(--vtq-border);
}

.vtq-q__btn--ghost:hover {
  background: var(--vtq-surface-hover);
  color: var(--vtq-ink);
}

/* Busy state: the spinner is inside the primary button and hidden until the
   root carries data-busy. Agent 2 sets/clears one attribute, nothing else. */
.vtq-q__spin {
  display: none;
  width: 15px;
  height: 15px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: var(--vtq-r-pill);
  animation: vtq-q-spin 0.6s linear infinite;
}

.vtq-q[data-busy] .vtq-q__spin {
  display: inline-block;
}
.vtq-q[data-busy] .vtq-q__btn {
  pointer-events: none;
}
.vtq-q[data-busy] .vtq-q__btn-label {
  display: none;
}

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

.vtq-q__trust {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 14px;
  margin-top: 16px;
  font-size: 12px;
  color: var(--vtq-ink-muted);
}

.vtq-q__status {
  margin-top: 10px;
  font-size: 12.5px;
  color: var(--vtq-ink-muted);
}

.vtq-q__status:empty {
  display: none;
}

.vtq-q[data-state="error"] .vtq-q__status {
  color: var(--vtq-danger);
}

/* At hero-column width the actions must stack, and the primary must lead. */
@container vtqq (max-width: 359px) {
  .vtq-q__actions {
    flex-direction: column-reverse;
    align-items: stretch;
  }
  .vtq-q__btn {
    width: 100%;
  }
}

/* ==========================================================================
   5. PROCESS BAND  —  .vtq-process
   --------------------------------------------------------------------------
   "What happens to your waste" / "How it works". Numbered steps with a rule
   running through them on desktop, a vertical spine on mobile.

   ⛔ COPY CONSTRAINT: this band is where a licence claim is most tempting
   ("taken to a licensed transfer station..."). Describe what VTQ DOES —
   sorting, recycling rates, transfer-station handover — never what VTQ is
   REGISTERED as. scripts/check-site.mjs enforces this.

   MARKUP CONTRACT
     <ol class="vtq-process">
       <li class="vtq-process__step">
         <span class="vtq-process__num" aria-hidden="true">1</span>
         <h3 class="vtq-process__title">We load it</h3>
         <p class="vtq-process__body">...</p>
       </li>
       ...
     </ol>
   Add .vtq-process--4 when there are exactly four steps.
   -------------------------------------------------------------------------- */

/* NOT a container query component, unlike .vtq-q — and the reason is a trap
   worth writing down. An element that declares `container-type` becomes the
   container for its DESCENDANTS; it cannot be targeted by a @container rule
   against its own name. So `.vtq-process { container-name: vtqproc }` plus
   `@container vtqproc { .vtq-process--4 { ... } }` silently never matches,
   and the band collapses to one column at every width. Measured that exact
   failure at 1440px before switching to media queries.
   Media queries are also the honest choice here: the process band is always
   a full-width section band, so the viewport is an accurate proxy for its
   width. The quote widget is not, which is why that one keeps @container. */
.vtq-process {
  display: grid;
  /* auto-fit/minmax again. The vertical spine below is drawn with a
     pseudo-element on each step, never with a flex-basis. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
  gap: 28px 24px;
  list-style: none;
  margin: 0;
  padding: 0;
  width: 100%;
  min-width: 0;
  counter-reset: vtq-process;
}

.vtq-process__step {
  position: relative;
  display: block;
  min-width: 0;
  padding-left: 46px;
  margin: 0;
}

.vtq-process__num {
  position: absolute;
  left: 0;
  top: 0;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--vtq-r-pill);
  background: var(--vtq-accent);
  color: var(--vtq-accent-ink);
  font-size: 14px;
  font-weight: 700;
  line-height: 1;
}

/* --4: use when there are EXACTLY four steps, which is what every service
   page has. Explicit 1 -> 2 -> 4 columns, deliberately skipping 3.
   auto-fit alone produced a 3+1 orphan at 1024px (a 912px container fits
   exactly 3.5 tracks of 240px), which is the single ugliest thing a
   four-step band can do. Measured, not guessed. */
/* Viewport breakpoints chosen from the measured inner width of a .vtq-band
   (body p-2 = 16px, band px-6/px-12, max-w-6xl):
     390px viewport -> 326px inner -> 1 column
     834px viewport -> 722px inner -> 2 columns
    1024px viewport -> 910px inner -> 4 columns
   Deliberately no 3-column stop: 4 steps in 3 columns is the orphan row. */
.vtq-process--4 {
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 640px) {
  .vtq-process--4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 1024px) {
  .vtq-process--4 {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* The spine: a rule from under this step's number down to the next step.
   Only drawn while the band is a single stacked column. The moment the steps
   sit side by side a vertical rule points at nothing, and a horizontal one
   can only be correct if every step shares a row — which auto-fit cannot
   promise. So: vertical when stacked, horizontal only in the one arrangement
   where a single row IS guaranteed (--4 at >=840px), and nothing in between.
   Side by side, the numbers carry the sequence on their own. */
.vtq-process__step:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 15px;
  top: 38px;
  bottom: -28px; /* == the grid row-gap, so the rule meets the next number */
  width: 2px;
  border-radius: 2px;
  background: linear-gradient(
    to bottom,
    var(--vtq-accent-strong),
    var(--vtq-border)
  );
}

/* Side by side, there is no connector at all — the numbers carry the sequence.

   A horizontal connector was tried and removed. In this layout the number is
   absolutely positioned to the LEFT of the step (the step has padding-left:46px)
   and the title is the first in-flow element, so the title's text sits at the
   same vertical centre as the number. A rule drawn between the number circles
   therefore runs straight through the title and renders as a strikethrough.
   Caught on a 1440px screenshot, not by any measurement. Do not reinstate it
   without first moving the number above the title. */
@media (min-width: 640px) {
  .vtq-process__step:not(:last-child)::before {
    display: none;
  }
}

.vtq-process__title {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.3;
  color: var(--vtq-ink);
  margin: 6px 0 5px;
}

.vtq-process__body {
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--vtq-ink-muted);
  margin: 0;
}

/* A stat the band can carry, e.g. a recycling rate. */
.vtq-process__stat {
  display: block;
  margin-top: 8px;
  font-size: 20px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--vtq-accent-deep);
  font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   Fallback: container queries are supported everywhere this site's traffic
   comes from (Safari 16+/Chrome 105+), but a browser without them would show
   every container-query rule as never-matching, i.e. the narrow layout. That
   degrades correctly for every component above EXCEPT the two grids that
   need a wider layout to look deliberate, so give them a viewport-based
   safety net that only applies when @container is unsupported.
   -------------------------------------------------------------------------- */
@supports not (container-type: inline-size) {
  @media (min-width: 768px) {
    .vtq-q__options {
      grid-template-columns: repeat(4, minmax(0, 1fr));
    }
    .vtq-q__row2 {
      display: grid;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: 14px;
    }
  }
}
