/* ==========================================================================
   Tag Custom Products — site styles
   Every value comes from tokens.css. No raw hex, no magic numbers.

   Revised after client review. The first pass read DIY because photography was
   quarantined in the gallery, every section was text-on-white separated by
   hairline rules, and the type scale had no real jump between levels. This pass
   puts the work on screen, alternates dark against light, and gives the type
   room to actually build hierarchy.
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

/* The hidden attribute only carries display:none from the UA stylesheet, which
   any author `display` rule beats. .btn{display:inline-flex} and
   .steps-bar{display:flex} were both silently overriding it, leaving the submit
   button and the progress bar visible when they were marked hidden. */
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--surface-0);
  color: var(--on-dark-muted);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-body);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img, video { max-width: 100%; height: auto; display: block; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  color: var(--on-dark);
  font-weight: var(--weight-bold);
  margin: 0 0 var(--space-4);
  text-wrap: balance;
}

/* A real scale. The first pass topped out at 2.5rem for H2, which is why
   nothing on the page ever read as a headline. */
h1 { font-size: clamp(2.75rem, 7vw, 5.5rem); line-height: 0.98; letter-spacing: -0.035em; }
h2 { font-size: clamp(2rem, 4.4vw, 3.5rem);  line-height: 1.04; letter-spacing: -0.03em; }
h3 { font-size: var(--text-xl);  line-height: 1.15; letter-spacing: -0.015em; }
h4 { font-size: var(--text-lg);  line-height: 1.2; }

p { margin: 0 0 var(--space-4); }
p, li { max-width: var(--measure); }

a { color: var(--on-dark); text-decoration-thickness: 1px; text-underline-offset: 4px; }
a:hover { color: var(--color-accent); }

.skip-link {
  position: absolute; left: var(--space-2); top: calc(-1 * var(--space-16));
  z-index: 100; background: var(--color-ink-strong); color: var(--color-ground);
  padding: var(--space-3) var(--space-4); text-decoration: none;
  transition: top var(--duration-quick) var(--ease-out);
}
.skip-link:focus { top: var(--space-2); color: var(--color-ground); }

.shell { width: min(100% - (2 * var(--gutter)), var(--container-max)); margin-inline: auto; }
.shell--text { width: min(100% - (2 * var(--gutter)), var(--container-text)); margin-inline: auto; }

/* ---- Buttons ------------------------------------------------------------ */
/* Label is 1.25rem/700 — deliberately above the 18.66px-bold "large text"
   threshold. That is what lets WHITE sit on red at 4.00:1 and still meet AA,
   which is the client's requirement. Do not shrink below 1.1667rem without
   changing the label colour, or the bar moves back to 4.5:1 and this fails. */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  font-size: var(--text-md);
  letter-spacing: 0.01em;
  text-decoration: none;
  padding: var(--space-4) var(--space-8);
  min-height: 56px;
  border: var(--border-rule) solid var(--on-dark);
  border-radius: var(--radius-none);
  background: transparent;
  color: var(--on-dark);
  cursor: pointer;
  transition: background var(--duration-quick) var(--ease-out),
              color var(--duration-quick) var(--ease-out),
              transform var(--duration-instant) var(--ease-out);
}
.btn:hover { background: var(--on-dark); color: var(--surface-0); }
.btn:active { transform: translateY(1px); }

/* WHITE on red. Never black — client requirement. */
.btn--accent { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-ground); }
.btn--accent:hover { background: var(--on-dark); border-color: var(--on-dark); color: var(--surface-0); }

.btn--ghost { background: transparent; border-color: var(--color-ground); color: var(--color-ground); }
.btn--ghost:hover { background: var(--on-dark); border-color: var(--on-dark); color: var(--surface-0); }

.btn--on-accent { background: var(--on-dark); border-color: var(--on-dark); color: var(--surface-0); }
.btn--on-accent:hover { background: var(--color-accent); border-color: var(--color-accent); color: var(--on-dark); }

/* ---- Header ------------------------------------------------------------- */
/* Light header on purpose. The logo is black + red on transparent — a
   brightness(0) invert(1) knockout flattened it to solid white and destroyed
   the red entirely. Rather than commission a knockout mark, the nav goes light
   and the logo renders as drawn. */
.site-header { border-bottom: var(--border-hairline) solid var(--color-neutral-200); background: var(--color-ground); position: relative; z-index: 20; }
.site-header__inner { display: flex; align-items: center; gap: var(--space-6); padding-block: var(--space-4); }
.brand { margin-right: auto; display: block; }
.brand img { width: 92px; height: auto; }

.nav { display: flex; align-items: center; gap: var(--space-6); }
.nav__list { display: flex; gap: var(--space-5); list-style: none; margin: 0; padding: 0; }
.nav__list a {
  font-family: var(--font-display); font-size: var(--text-sm);
  font-weight: var(--weight-medium); text-decoration: none;
  color: var(--color-ink-strong);
  padding-block: var(--space-2); position: relative;
}
/* Same fix as the drawer dash below: `right` is a layout property and animating
   it reflows on every frame. scaleX from a full-width bar composites instead,
   and wipes in from the left exactly as before. */
.nav__list a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: var(--color-accent);
  transform: scaleX(0); transform-origin: left center;
  transition: transform var(--duration-quick) var(--ease-out);
}
.nav__list a:hover::after { transform: scaleX(1); }
/* 1.1875rem (19px) bold, NOT --text-sm. White on red is 4.00:1, which only
   clears the 3:1 large-text bar; below 18.66px bold the bar becomes 4.5:1 and
   this button fails. The size is load-bearing for accessibility, not styling.
   scripts/verify-red-buttons.mjs enforces it in the build. */
.nav__cta { font-size: 1.1875rem; padding: var(--space-3) var(--space-5); min-height: 44px; }
.nav-toggle { display: none; }
/* Drawer-only furniture. These exist in the markup on every breakpoint so the
   mobile tray can be grouped and can carry a call link, but above 767px the
   nav is a single horizontal row and none of it applies. .nav__foot keeps the
   CTA behaving as a plain flex child of .nav, exactly as it did before it was
   wrapped. */
.nav__group { display: none; }
.nav__tel { display: none; }
.nav__foot { display: flex; align-items: center; gap: var(--space-4); }
.nav-scrim { display: none; }
.site-header .brand img { width: 104px; }

/* ---- Hero --------------------------------------------------------------- */
.hero {
  position: relative; isolation: isolate; color: var(--color-ground);
  background-color: #2B2B2B;              /* white on this = 14.16:1 */
  min-height: min(86vh, 780px);
  display: flex; align-items: flex-end;
}
.hero__media { position: absolute; inset: 0; z-index: -2; overflow: hidden; }
.hero__media img, .hero__media video { width: 100%; height: 100%; object-fit: cover; }
.hero::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(100deg, rgba(0,0,0,0.82) 0%, rgba(0,0,0,0.66) 46%, rgba(0,0,0,0.24) 100%),
    linear-gradient(to top, rgba(0,0,0,0.55) 0%, rgba(0,0,0,0) 42%);
}
/* No width here. .shell already sets the container width, and an equal-specificity
   width:100% declared later in the file silently cancels it — which pushed the
   hero copy flush to the viewport edge. This is the element-vs-type selector
   collision the build phase warns about. margin-inline:auto on .shell does the
   centring inside the flex parent. */
.hero__inner { padding-block: clamp(4rem, 10vw, 8rem); }
.hero h1 { color: var(--color-ground); max-width: 15ch; margin-bottom: var(--space-6); }
.hero__lead { font-size: clamp(1.125rem, 1.6vw, 1.375rem); max-width: 46ch; color: rgba(255,255,255,0.92); }
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--space-4); margin-top: var(--space-10); }
.hero__eyebrow {
  font-family: var(--font-display); font-size: var(--text-xs); font-weight: var(--weight-bold);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--color-ground);
  display: flex; align-items: center; gap: var(--space-3); margin-bottom: var(--space-5);
}
.hero__eyebrow::before { content: ""; width: 44px; height: 2px; background: var(--color-accent); flex: none; }

/* Small icon control, not a call to action — client requirement. */
.hero__pause {
  position: absolute; right: var(--gutter); bottom: var(--space-5); z-index: 3;
  width: 40px; height: 40px; min-height: 0; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: var(--border-hairline) solid rgba(255,255,255,0.55);
  background: rgba(0,0,0,0.42); color: var(--color-ground);
  border-radius: var(--radius-none); cursor: pointer;
  transition: background var(--duration-quick) var(--ease-out);
}
.hero__pause:hover { background: rgba(0,0,0,0.78); }
.hero__pause svg { width: 13px; height: 13px; fill: currentColor; display: block; }
.hero__pause[hidden] { display: none; }
.hero-poster { display: none; }

/* ---- Sections ----------------------------------------------------------- */
/* Padding cut roughly 40%. Measured ink coverage was 16-36% per section — the
   content was fine, the rooms were too big. */
.section { padding-block: clamp(3rem, 5.5vw, 5rem); }
.section--tight { padding-block: clamp(2rem, 3.5vw, 3rem); }
/* ---- Light rows -----------------------------------------------------------
   Dark = our work: the hero, the services, the shop, what we decorate. That is
   where the photography lives and where near-black makes it read.
   Light = working with us: track record, process, customer voices.
   The switch encodes which side of the transaction you are reading about; it is
   not decoration. Everything colour-role flips, and red flips with it — #FF0000
   is 4.86:1 on #0D0D0D but only 4.00:1 on white, so a 14px red eyebrow is legal
   on dark and illegal on light. On light rows the eyebrow is black. */

.section--light {
  background: var(--color-ground);
  color: var(--color-ink);
}
/* Two light rows sit next to each other (process, then voices). A slightly
   cooler ground on the second keeps them distinct without breaking the
   dark/light encoding by forcing one back to dark. */
.section--light-alt { background: var(--color-neutral-50); }
.section--light h2,
.section--light h3 { color: var(--color-ink-strong); }
.section--light p { color: var(--color-neutral-800); }
.section--light a { color: var(--color-ink); }
.section--light a:hover { color: var(--color-accent); }
/* The chip inverts on light rows: a white chip on a white section is invisible.
   White on ink is 21:1, the same as ink on white the other way round. */
.section--light .eyebrow { background: var(--color-ink-strong); color: var(--color-ground); }
/* .section__head--row p:not(.eyebrow) is (0,3,0) and beats .section--light p at
   (0,1,1), which left dark-theme #B9B9B9 body text on a white row at 1.96:1.
   This needs (0,4,0) to win. Found by verify-rendered-contrast.mjs, not by
   reading the stylesheet. */
.section--light .section__head--row p:not(.eyebrow) { color: var(--color-neutral-800); }

.section--light .track::before { background-image: repeating-linear-gradient(to right, var(--color-neutral-200) 0 8px, transparent 8px 15px); }
.section--light .track p { color: var(--color-neutral-800); }
.section--light .stats > * + * { border-left-color: var(--color-neutral-200); }
.section--light .stat p { color: var(--color-neutral-800); }
.section--light .voices__lead blockquote { color: var(--color-ink-strong); }
.section--light .voices__rest figure { background: var(--color-neutral-50); }
.section--light .voices__rest blockquote { color: var(--color-ink-strong); }
.section--light figcaption { color: var(--color-neutral-800); }
.section--light .svc__link { color: var(--color-ink-strong); }
.section--light .btn { border-color: var(--color-ink-strong); color: var(--color-ink-strong); }
.section--light .btn:hover { background: var(--color-ink-strong); color: var(--color-ground); }
.section--light .btn--accent { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-ground); }
.section--light .btn--accent:hover { background: var(--color-ink-strong); border-color: var(--color-ink-strong); color: var(--color-ground); }

/* The ch unit resolves against the element's OWN font-size. Putting a ch
   max-width on the container measured it against the inherited 18px body size —
   24ch came out at 215px — so a 56px headline inside wrapped to six lines. The
   constraint belongs on the heading, where ch means what it looks like it means. */
.section__head { margin-bottom: clamp(1.5rem, 2.5vw, 2.5rem); }
.section__head > h2, .section__head > div > h2 { max-width: 15ch; }
/* Sections whose head sits above a full-width grid read better with a shorter,
   wider headline than a four-line column beside dead space. */
.section__head--row { max-width: none; display: grid; gap: var(--space-6); align-items: end; }
@media (min-width: 900px) { .section__head--row { grid-template-columns: 1.4fr 1fr; } }
.section__head--row h2 { max-width: 18ch; margin: 0; }
.section__head--row p:not(.eyebrow) { color: var(--on-dark-muted); margin: 0; }
/* A filled chip, not coloured text. Client: red on black is hard to read — the
   eyebrow gets a white background while the section stays black.

   #FF0000 on #0D0D0D is 4.86:1, which technically passed, but "passes AA" and
   "comfortable at 14px with 0.14em tracking" are different claims and the
   client is the one reading it. Ink on ground is 21:1.

   Inverted rather than white-everywhere: a white chip on a white section is
   invisible, so light rows get the same chip filled dark. One component, two
   fills, readable on both — instead of a chip on dark and bare text on light,
   which reads as two unrelated things.

   inline-flex so the chip hugs its label. As a <p> it still occupies its own
   line, so no layout moves. The red survives as the dash inside, which is pure
   decoration and exempt from the text-contrast bar. */
.eyebrow {
  font-family: var(--font-display); font-size: var(--text-xs);
  font-weight: var(--weight-bold); letter-spacing: 0.14em;
  text-transform: uppercase;
  background: var(--color-ground); color: var(--color-ink-strong);
  display: inline-flex; align-items: center; gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  margin: 0 0 var(--space-5);
  /* .bleed__copy and .phero__copy are column flex containers, which blockify
     inline-flex back to flex and then stretch the item to full width — a
     548px white bar behind three words. align-self keeps the chip the width of
     its own label wherever it lands. */
  align-self: flex-start;
}
.eyebrow::before { content: ""; width: 22px; height: 2px; background: var(--color-accent); flex: none; }

.lead { font-size: var(--text-md); max-width: 52ch; }

/* ==========================================================================
   SECTION SHAPES — each section has a different structure on purpose.
   The previous pass put a bordered card around services, stats, steps, quotes
   AND tiles. When every block is a card, none of them read as important. Cards
   now appear exactly ONCE on the page (the quote pair), and every other section
   earns its structure from its own content.
   ========================================================================== */

/* ---- Services: bare alternating image/text, no card, no fake numbering ---- */
/* The old 01/02/03/04 markers implied a sequence. These are four parallel
   offerings — you can start at any of them — so numbering encoded something
   untrue. Removed per "structure is information". */
@media (min-width: 860px) {
}
.svc__link {
  font-family: var(--font-display); font-weight: var(--weight-bold); font-size: var(--text-sm);
  letter-spacing: 0.06em; text-transform: uppercase; text-decoration: none;
  color: var(--on-dark); display: inline-flex; align-items: center; gap: var(--space-2);
  align-self: flex-start; border-bottom: 2px solid var(--color-accent); padding-bottom: 2px;
}
.svc__link::after { content: "\2192"; transition: transform var(--duration-quick) var(--ease-out); }
.svc__link:hover { color: var(--color-accent); }
.svc__link:hover::after { transform: translateX(4px); }

/* ---- Stats: bare numerals divided by stitch rules, no boxes -------------- */
.stats { display: grid; gap: var(--space-8); }
@media (min-width: 760px) {
  .stats { grid-template-columns: repeat(3, 1fr); gap: 0; }
  .stats > * + * { border-left: 2px dashed var(--surface-3); padding-left: clamp(1.5rem, 3vw, 3rem); }
  .stats > * { padding-right: clamp(1.5rem, 3vw, 3rem); }
}
.stat p.stat__num {
  font-family: var(--font-display); font-weight: var(--weight-bold);
  font-size: clamp(4rem, 11vw, 8.5rem); line-height: 0.8;
  letter-spacing: -0.055em; color: var(--color-accent);
  margin: 0 0 var(--space-4); max-width: none;
}
.stat h3 { font-size: var(--text-lg); margin-bottom: var(--space-2); }
.stat p { font-size: var(--text-sm); color: var(--on-dark-muted); margin: 0; max-width: 28ch; }

/* ---- Process: a real sequence, so numbering IS information here ---------- */
/* This is the one place a numbered marker is honest — the steps happen in
   order and the reader needs that order. Rendered as a stitched timeline
   rather than three boxes. */
.track { position: relative; display: grid; gap: var(--space-8); counter-reset: step; }
@media (min-width: 860px) {
  .track { grid-template-columns: repeat(3, 1fr); gap: clamp(2rem, 4vw, 3.5rem); }
  .track::before {
    content: ""; position: absolute; left: 0; right: 0; top: 21px; height: 2px;
    background-image: repeating-linear-gradient(to right, var(--surface-3) 0 8px, transparent 8px 15px);
  }
}
/* padding-top was --space-10 (40px) while the marker is 44px tall at top:0, so
   the heading collided with the number. Needs the marker height plus real air. */
.track > li { counter-increment: step; list-style: none; max-width: none; position: relative; padding-top: calc(44px + var(--space-6)); }
.track > li::before {
  content: counter(step); position: absolute; top: 0; left: 0;
  width: 44px; height: 44px; display: grid; place-items: center;
  background: var(--color-accent); color: var(--on-dark);
  font-family: var(--font-display); font-weight: var(--weight-bold); font-size: 1.25rem;
}
.track h3 { font-size: var(--text-lg); margin-bottom: var(--space-2); }
.track p { margin: 0; color: var(--on-dark-muted); max-width: 34ch; }

/* ---- Testimonials: ONE lead quote, the rest supporting ------------------- */
/* The only card treatment left on the page, and it is deliberately asymmetric
   so it does not read as a row of equals. */
.voices { display: grid; gap: clamp(2rem, 4vw, 3.5rem); align-items: start; }
/* 2.3fr / 1fr, not 1.5 / 1. The right column is supporting material, and at
   near-parity it competed with the lead quote instead of framing it. */
@media (min-width: 900px) { .voices { grid-template-columns: 2.3fr 1fr; } }
.voices__lead blockquote {
  margin: 0 0 var(--space-6); font-family: var(--font-display); font-weight: var(--weight-medium);
  font-size: clamp(1.75rem, 3.4vw, 3rem); line-height: 1.18; letter-spacing: -0.025em;
  color: var(--on-dark);
}
.voices__lead blockquote p { max-width: 26ch; margin: 0; }
.voices__rest { display: grid; gap: var(--space-4); }
/* border-TOP, not border-left. Every other card here (.panel, .svc-card) marks
   itself with a 3px accent along the top edge; this was the last block still
   wearing it down the side, which is both inconsistent with the system and the
   single most recognisable tell of a generated UI. Same accent, same weight,
   moved to the same edge as everything else. */
.voices__rest figure { margin: 0; padding: var(--space-5); background: var(--surface-1); border-top: 3px solid var(--color-accent); }
.voices__rest blockquote { margin: 0 0 var(--space-3); font-size: var(--text-sm); color: var(--on-dark); }
.voices__rest blockquote p { margin: 0; max-width: none; }
figcaption {
  font-family: var(--font-display); font-weight: var(--weight-bold); font-size: var(--text-xs);
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--on-dark-faint);
  display: flex; align-items: center; gap: var(--space-3);
}
.voices__lead figcaption::before { content: ""; width: 32px; height: 2px; background: var(--color-accent); flex: none; }

/* ---- What we decorate: a running list, not a grid of boxes --------------- */

/* ---- Full-bleed image band ---------------------------------------------- */
/* Split, not overlay. Three attempts at text-over-photo failed here for the
   same reason: these are product snapshots with no negative space, so copy
   always lands on the busiest part of the frame. Giving each its own half fixes
   the cause rather than the symptom. */
.bleed { background: var(--surface-1); overflow: hidden; }
.bleed__grid { display: grid; align-items: stretch; }
@media (min-width: 860px) { .bleed__grid { grid-template-columns: 1fr 1fr; } }
/* NO aspect-ratio here. The grid is align-items:stretch, so the figure's HEIGHT
   is set by the taller copy column — and an aspect-ratio then derives WIDTH
   from that height. A 695px-tall row made this 927px wide inside a 720px
   column, and the photo ran 207px into the copy and sat on top of every line.
   The cell already gives it both dimensions; it only needs a floor for the
   one-column case. scripts/verify-overflow.mjs now catches this class. */
.bleed__figure { position: relative; overflow: hidden; min-height: 320px; }
@media (max-width: 859px) { .bleed__figure { min-height: clamp(300px, 62vw, 460px); } }
.bleed__figure img { width: 100%; height: 100%; object-fit: cover; }
.bleed__copy { display: flex; flex-direction: column; justify-content: center; padding: clamp(2rem, 5vw, 4.5rem); }
/* 13ch broke the headline into five lines and forced the row to 695px, which is
   what dragged the figure out of shape in the first place. */
.bleed__copy h2 { color: var(--on-dark); max-width: 20ch; }
/* :not(.eyebrow) for the same reason as .section__head--row above — a bare `p`
   rule here is (0,1,1) and sits after .eyebrow (0,1,0), so it repainted the
   chip's label #B9B9B9 on its own white background: 1.96:1. Seventh time this
   exact collision has appeared in this stylesheet. */
.bleed__copy p:not(.eyebrow) { color: var(--on-dark-muted); max-width: 42ch; font-size: var(--text-md); }
/* Was clamp(5rem,12vw,10rem) — 771px tall with a single left-aligned column and
   an empty right half. Shorter, and the copy now occupies a defined column so
   the photograph reads as the other half rather than as leftover space. */

/* ---- Product tiles ------------------------------------------------------ */

/* ---- Steps -------------------------------------------------------------- */

@media (min-width: 768px) {  }

/* ---- Testimonials ------------------------------------------------------- */

@media (min-width: 700px) {  }

/* ---- CTA band ----------------------------------------------------------- */
.band { background: var(--color-accent); color: var(--on-dark); padding-block: clamp(2.5rem, 4.5vw, 4rem); }
.band__inner { display: flex; flex-wrap: wrap; gap: var(--space-8); align-items: center; justify-content: space-between; }
.band__title { color: var(--color-ground); margin: 0 0 var(--space-3); max-width: 16ch; }
/* 1.25rem, not the 18px default. This is white on red; below 18.66px bold or
   24px regular the bar becomes 4.5:1 and 4.00 fails. */
.band__text { color: var(--on-dark); margin: 0; max-width: 40ch; font-size: 1.5rem; }

/* ---- Gallery ------------------------------------------------------------ */
.gallery { display: grid; gap: var(--space-3); grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
.gallery picture { overflow: hidden; display: block; background: var(--surface-2); }
.gallery img { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; transition: transform 600ms var(--ease-out); }
.gallery picture:hover img { transform: scale(1.04); }

/* ---- Forms -------------------------------------------------------------- */
.field { margin-bottom: var(--space-6); }
.field label { display: block; font-family: var(--font-display); font-weight: var(--weight-bold); font-size: var(--text-sm); margin-bottom: var(--space-2); }
.field input, .field select, .field textarea {
  width: 100%; font: inherit; font-size: var(--text-base); color: var(--on-dark);
  padding: var(--space-4);
  background: var(--surface-2);
  border: var(--border-hairline) solid var(--surface-3);
  border-radius: var(--radius-sm);
  transition: border-color var(--duration-quick) var(--ease-out);
}
.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--color-accent); }
.field__hint { font-size: var(--text-xs); color: var(--on-dark-faint); margin: var(--space-2) 0 0; }
.field__error { font-size: var(--text-xs); color: #FF6B6B; margin: var(--space-2) 0 0; font-weight: var(--weight-bold); }
.required-mark { color: var(--color-accent); }

.check-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.check-list label { display: flex; align-items: center; gap: var(--space-3); min-height: 44px; font-family: inherit; font-weight: var(--weight-regular); }
.check-list input { width: 20px; height: 20px; accent-color: var(--color-accent); }
fieldset.field { border: 0; padding: 0; margin: 0 0 var(--space-6); }
fieldset.field legend { font-family: var(--font-display); font-weight: var(--weight-bold); font-size: var(--text-sm); margin-bottom: var(--space-3); padding: 0; }
textarea { resize: vertical; }

/* Honeypot: off-screen rather than display:none — some bots skip hidden fields
   but will happily fill one that is merely positioned away. */
.hp-field { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.form-status { padding: var(--space-5); border: var(--border-rule) solid; margin-bottom: var(--space-6); }
.form-status--error { border-color: #FF6B6B; color: #FF6B6B; }
.form-status--success { border-color: #4ADE80; color: #4ADE80; }
.form-status h2 { margin-top: 0; font-size: var(--text-lg); }
.form-status[hidden] { display: none; }
#quote-form[data-submitting="true"] { opacity: 0.6; pointer-events: none; }
#q-submit[disabled] { opacity: 0.6; cursor: not-allowed; }
.h-captcha { margin-bottom: var(--space-3); }

/* ---- Footer ------------------------------------------------------------- */
.site-footer { background: var(--surface-1); border-top: var(--border-hairline) solid var(--surface-3); color: var(--on-dark-muted); padding-top: clamp(3.5rem, 7vw, 5.5rem); margin-top: 0; }
.site-footer a { color: var(--color-ground); }
.site-footer a:hover { color: var(--color-accent); }
.site-footer__grid { display: grid; gap: var(--space-8); grid-template-columns: 1fr; }
@media (min-width: 768px) { .site-footer__grid { grid-template-columns: 1.5fr 1fr 1fr 1fr; gap: var(--space-10); } }
.site-footer__head { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.14em; margin-bottom: var(--space-4); color: var(--color-ground); }
.site-footer__blurb { font-size: var(--text-sm); color: var(--on-dark-faint); margin-top: var(--space-5); max-width: 34ch; }
.site-footer__addr { font-style: normal; font-size: var(--text-sm); line-height: 1.9; }
.site-footer__hours { font-size: var(--text-sm); margin: 0; line-height: 1.9; }
.site-footer .stitch-rule { margin: clamp(3rem, 6vw, 4.5rem) 0 var(--space-6); opacity: 0.9; }
.site-footer__legal { display: flex; flex-wrap: wrap; gap: var(--space-4); justify-content: space-between; padding-bottom: var(--space-10); }
.site-footer__legal p { font-size: var(--text-xs); color: var(--on-dark-faint); margin: 0; }
.site-footer img { background: var(--color-ground); padding: var(--space-3); }

/* ---- Breadcrumbs -------------------------------------------------------- */
.crumbs { font-size: var(--text-xs); padding-block: var(--space-5); border-bottom: var(--border-hairline) solid var(--surface-3); }
.crumbs ol { display: flex; flex-wrap: wrap; gap: var(--space-2); list-style: none; margin: 0; padding: 0; }
.crumbs li + li::before { content: "/"; margin-right: var(--space-2); color: var(--on-dark-faint); }
.crumbs a { text-decoration: none; }

/* ---- Interior page header ----------------------------------------------- */

/* ---- Utilities ---------------------------------------------------------- */
.u-mt-8 { margin-top: var(--space-8); }

/* ---- Mobile ------------------------------------------------------------- */
/* The nav switches to the drawer at 1023px, not 767px, while everything else
   below still breaks at 767px. Eight links plus a 19px CTA need about 932px of
   header shell, and there is only 754px at 820px wide — so the horizontal nav
   ran 49px past its own container on every page at every tablet width, and had
   done since it was built. verify-overflow.mjs found it; nothing before that
   ever measured a width between 768 and 1439. */
@media (max-width: 1023px) {
  /* The toggle was INVISIBLE: white text and a white border on a white header.
     It took --on-dark because the page body is dark, but this header is
     deliberately light so the black-and-red logo can render as drawn. The pair
     was #FFFFFF on #FFFFFF — 1:1, not a near miss.

     verify-rendered-contrast.mjs never caught it because it runs at 1440x950,
     where this button is display:none and the walker skips it. Every contrast
     number this project has reported was a desktop number.
     scripts/verify-mobile-nav.mjs closes that hole. */
  .nav-toggle {
    display: inline-flex; align-items: center; gap: var(--space-3);
    min-height: 44px; min-width: 44px;
    font-family: var(--font-display); font-size: var(--text-sm); font-weight: var(--weight-bold);
    letter-spacing: var(--tracking-wide); text-transform: uppercase;
    background: var(--color-ground);
    border: var(--border-rule) solid var(--color-ink-strong);
    color: var(--color-ink-strong);           /* 21:1 on the light header */
    border-radius: var(--radius-none); padding: var(--space-2) var(--space-4);
    cursor: pointer; position: relative; z-index: 2;
    transition: background var(--duration-quick) var(--ease-out),
                color var(--duration-quick) var(--ease-out);
  }
  .nav-toggle:hover { background: var(--color-ink-strong); color: var(--color-ground); }
  .nav-toggle__bars {
    width: 20px; height: 2px; background: currentColor; position: relative; display: block;
    transition: background var(--duration-quick) var(--ease-out);
  }
  .nav-toggle__bars::before, .nav-toggle__bars::after {
    content: ""; position: absolute; left: 0; width: 20px; height: 2px; background: currentColor;
    transition: transform var(--duration-settle) var(--ease-out),
                top var(--duration-settle) var(--ease-out);
  }
  .nav-toggle__bars::before { top: -6px; }
  .nav-toggle__bars::after  { top: 6px; }
  /* Open: the middle bar drops out and the outer two cross into an X. Driven
     off aria-expanded, which JS already maintains, so the visual state cannot
     drift from the state assistive tech is told about. */
  .nav-toggle[aria-expanded="true"] .nav-toggle__bars { background: transparent; }
  .nav-toggle[aria-expanded="true"] .nav-toggle__bars::before { top: 0; transform: rotate(45deg); }
  .nav-toggle[aria-expanded="true"] .nav-toggle__bars::after  { top: 0; transform: rotate(-45deg); }

  /* Base mobile nav — no JS. A plain stacked list, always visible. A panel that
     can only be opened by script is a panel that can never be opened, so the
     drawer below is scoped to .js and this is what remains without it. */
  .nav { flex-direction: column; align-items: stretch; gap: var(--space-4); padding-block: var(--space-5); flex-basis: 100%; }
  .nav__list { flex-direction: column; gap: 0; }
  .nav__list a { display: block; padding: var(--space-3) 0; min-height: 44px; }
  .nav__list a::after { display: none; }
  .nav__foot { flex-direction: column; align-items: stretch; }
  .site-header__inner { flex-wrap: wrap; }

  /* ---- The drawer ------------------------------------------------------ */
  /* Dark panel dropping from under the header, against the light header it
     hangs off. That inversion is the whole idea: the tray reads as a different
     surface rather than more of the same page. It starts BELOW the header
     rather than covering it, so the logo stays put and the X is always on
     white — no z-index fight to keep the close control legible. */
  .js .nav {
    position: fixed; left: 0; right: 0; top: var(--header-h, 84px);
    max-height: calc(100dvh - var(--header-h, 84px));
    overflow-y: auto; overscroll-behavior: contain;
    background: var(--surface-0); color: var(--on-dark);
    padding: var(--space-4) var(--gutter) var(--space-10);
    border-top: var(--border-heavy) solid var(--color-accent);
    box-shadow: var(--shadow-overlay);
    z-index: 1;
    transform: translateY(-12px); opacity: 0; visibility: hidden;
    transition: opacity var(--duration-settle) var(--ease-out),
                transform var(--duration-settle) var(--ease-out),
                visibility 0s linear var(--duration-settle);
  }
  /* visibility rather than display:none: it can be transitioned, and it still
     takes the links out of the tab order while closed. */
  .js .nav[data-open="true"] {
    transform: none; opacity: 1; visibility: visible;
    transition: opacity var(--duration-settle) var(--ease-out),
                transform var(--duration-settle) var(--ease-out);
  }

  .js .nav__group {
    display: list-item; list-style: none;
    font-family: var(--font-display); font-size: var(--text-xs);
    font-weight: var(--weight-bold); letter-spacing: var(--tracking-wide);
    text-transform: uppercase; color: var(--on-dark-faint);   /* 5.3:1 on surface-0 */
    padding: var(--space-6) 0 var(--space-2);
  }
  .js .nav__group:first-child { padding-top: 0; }

  .js .nav__list a {
    display: flex; align-items: center; justify-content: space-between;
    color: var(--on-dark);                                    /* 19.4:1 */
    font-family: var(--font-display); font-size: var(--text-lg);
    font-weight: var(--weight-medium); line-height: var(--leading-snug);
    letter-spacing: var(--tracking-tight); text-decoration: none;
    padding: var(--space-3) 0; min-height: 48px;
    border-bottom: var(--border-hairline) solid var(--surface-3);
  }
  /* Overrides the desktop hover underline, which is absolutely positioned and
     display:none here. Everything it set has to be unset, not just re-coloured. */
  /* scaleX from a fixed 26px rather than an animated width. Identical on screen
     — 10px at rest, 26px on interaction — but width is a layout property, so
     animating it reflows the row every frame. transform composites. */
  .js .nav__list a::after {
    content: ""; display: block; position: static;
    left: auto; right: auto; bottom: auto;
    width: 26px; height: 2px; background: var(--color-accent);
    transform: scaleX(0.385); transform-origin: right center;
    transition: transform var(--duration-quick) var(--ease-out);
  }
  .js .nav__list a:hover::after,
  .js .nav__list a:focus-visible::after { transform: scaleX(1); }

  .js .nav__foot { display: flex; flex-direction: column; align-items: stretch; gap: var(--space-4); margin-top: var(--space-8); }
  .js .nav__cta { width: 100%; justify-content: center; }
  /* A phone number on a phone is a tap target, not a caption. It was 29px tall
     until verify-mobile-nav caught it on its first run. */
  .js .nav__tel {
    display: flex; align-items: center; justify-content: center;
    min-height: 44px; text-decoration: none;
    color: var(--on-dark); font-family: var(--font-display);
    font-size: var(--text-base); font-weight: var(--weight-bold);
  }

  /* Pointer-events rather than [hidden], so it can fade. */
  .js .nav-scrim {
    display: block; position: fixed; left: 0; right: 0; bottom: 0;
    top: var(--header-h, 84px);
    background: var(--overlay-scrim); z-index: 0;
    opacity: 0; pointer-events: none;
    transition: opacity var(--duration-settle) var(--ease-out);
  }
  .js .nav-scrim[data-open="true"] { opacity: 1; pointer-events: auto; }
}

/* Phone layout. Everything below is unrelated to the nav and keeps its own
   breakpoint — a tablet gets the drawer but not the stacked hero. */
@media (max-width: 767px) {
  .hero { min-height: 0; }
  .band__inner { flex-direction: column; align-items: flex-start; }
  .hero__actions .btn { flex: 1 1 auto; }

  /* Phones get the background video too, at client request. The poster img
     stays displayed underneath it: if autoplay is refused — iOS Low Power
     Mode, Data Saver, reduced motion — there is already an image in place
     rather than a black rectangle. */
  .hero-poster { display: block; }
}

/* ==========================================================================
   MOTION
   frontend-design: "An orchestrated moment usually lands harder than scattered
   effects." So this is one system, not a pile of separate tricks:

     1. a staged hero entrance on load
     2. one reveal gesture reused for every section as it enters
     3. a slow parallax drift on the two full-bleed photographs
     4. restrained hover feedback

   Everything is opt-IN via .is-in, added by JS. With JS off, or under
   prefers-reduced-motion, elements are simply visible — nothing is hidden
   behind an animation that might never run.
   ========================================================================== */

@media (prefers-reduced-motion: no-preference) {
  /* --- one reveal gesture, reused ------------------------------------- */
  .js [data-reveal] {
    opacity: 0;
    transform: translateY(18px);
    transition: opacity 700ms var(--ease-out), transform 700ms var(--ease-out);
  }
  .js [data-reveal].is-in { opacity: 1; transform: none; }
  /* Children stagger so a section resolves as a sequence, not a single block. */
  .js [data-reveal-group] > * {
    opacity: 0;
    transform: translateY(14px);
    transition: opacity 620ms var(--ease-out), transform 620ms var(--ease-out);
  }
  .js [data-reveal-group].is-in > * { opacity: 1; transform: none; }
  .js [data-reveal-group].is-in > *:nth-child(2) { transition-delay: 90ms; }
  .js [data-reveal-group].is-in > *:nth-child(3) { transition-delay: 180ms; }
  .js [data-reveal-group].is-in > *:nth-child(4) { transition-delay: 270ms; }

  /* --- staged hero entrance ------------------------------------------- */
  .js .hero__eyebrow,
  .js .hero h1,
  .js .hero__lead,
  .js .hero__actions {
    opacity: 0; transform: translateY(22px);
    transition: opacity 800ms var(--ease-out), transform 800ms var(--ease-out);
  }
  .js .hero.is-ready .hero__eyebrow { opacity: 1; transform: none; transition-delay: 80ms; }
  .js .hero.is-ready h1            { opacity: 1; transform: none; transition-delay: 200ms; }
  .js .hero.is-ready .hero__lead   { opacity: 1; transform: none; transition-delay: 360ms; }
  .js .hero.is-ready .hero__actions{ opacity: 1; transform: none; transition-delay: 500ms; }

  /* The hero media settles rather than snapping in — it is the first thing
     seen, so it gets the longest, slowest gesture on the page. */
  .js .hero__media { transform: scale(1.06); transition: transform 2600ms cubic-bezier(0.16, 1, 0.3, 1); }
  .js .hero.is-ready .hero__media { transform: scale(1); }

  /* --- parallax on the full-bleed photographs -------------------------- */
  .js 
  /* --- restrained hover feedback --------------------------------------- */
  .stat p.stat__num { transition: transform 400ms var(--ease-out); }
  .stat:hover p.stat__num { transform: translateY(-4px); }
}

/* No JS, or reduced motion: everything is visible and static. */
[data-reveal], [data-reveal-group] > * { opacity: 1; transform: none; }

/* ==========================================================================
   PICKER — "What we decorate"
   Was a static running list. Now it does a job: choosing items here carries
   them into the quote form, so the section is a step in the conversion path
   rather than a paragraph of nouns. Works without JS as plain links.
   ========================================================================== */
.picker { display: grid; gap: clamp(1.5rem, 3vw, 3rem); }
@media (min-width: 900px) { .picker { grid-template-columns: 1.6fr 1fr; align-items: start; } }

.picker__grid { display: flex; flex-wrap: wrap; gap: var(--space-3); list-style: none; margin: 0; padding: 0; }
.picker__grid li { max-width: none; }
.picker__btn {
  font-family: var(--font-display); font-size: clamp(0.95rem, 1.3vw, 1.125rem);
  font-weight: var(--weight-medium); color: var(--on-dark);
  background: transparent; cursor: pointer; text-decoration: none;
  border: var(--border-hairline) solid var(--surface-3);
  padding: var(--space-3) var(--space-5); min-height: 48px;
  display: inline-flex; align-items: center; gap: var(--space-3);
  transition: border-color var(--duration-quick) var(--ease-out),
              color var(--duration-quick) var(--ease-out),
              background var(--duration-quick) var(--ease-out);
}
.picker__btn:hover { border-color: var(--color-accent); color: var(--color-accent); }
.picker__btn[aria-pressed="true"] {
  background: var(--color-accent); border-color: var(--color-accent); color: var(--on-dark);
}
/* The tick is drawn, not an emoji — emoji-as-icon is an explicit tell. */
.picker__btn::before {
  content: ""; width: 9px; height: 9px; flex: none;
  border: 2px solid currentColor; opacity: 0.45;
  transition: opacity var(--duration-quick) var(--ease-out), background var(--duration-quick) var(--ease-out);
}
.picker__btn[aria-pressed="true"]::before { opacity: 1; background: currentColor; }

.picker__panel { border: var(--border-hairline) solid var(--surface-3); padding: clamp(1.5rem, 2.5vw, 2rem); position: sticky; top: var(--space-6); }
.picker__count {
  font-family: var(--font-display); font-weight: var(--weight-bold);
  font-size: clamp(2.5rem, 5vw, 4rem); line-height: 0.9; color: var(--color-accent);
  margin: 0 0 var(--space-2); max-width: none;
}
.picker__label { font-size: var(--text-sm); color: var(--on-dark-muted); margin: 0 0 var(--space-5); }
.picker__chosen { font-size: var(--text-sm); color: var(--on-dark); margin: 0 0 var(--space-5); min-height: 3em; }
.picker__panel .btn { width: 100%; }

.section--light .picker__btn { color: var(--color-ink-strong); border-color: var(--color-neutral-200); }
.section--light .picker__btn[aria-pressed="true"] { color: var(--color-ground); border-color: var(--color-accent); }
.section--light .picker__panel { border-color: var(--color-neutral-200); }
.section--light .picker__label,
.section--light .picker__chosen { color: var(--color-neutral-800); }

/* ==========================================================================
   QUOTE BUILDER — three steps instead of eight fields at once
   Order escalates commitment: what you need, then scale, then who you are.
   Contact details come last, once someone has invested a little effort.

   Without JS every step is visible and the form is one ordinary scroll — the
   steps are a progressive enhancement, never a gate.
   ========================================================================== */
.steps-bar { display: flex; gap: var(--space-2); margin-bottom: var(--space-8); list-style: none; padding: 0; }
.steps-bar li {
  flex: 1; max-width: none; font-family: var(--font-display); font-size: var(--text-xs);
  font-weight: var(--weight-bold); letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--on-dark-faint); padding-top: var(--space-3);
  border-top: 3px solid var(--surface-3);
  transition: color var(--duration-quick) var(--ease-out), border-color var(--duration-quick) var(--ease-out);
}
.steps-bar li[data-state="current"] { color: var(--on-dark); border-top-color: var(--color-accent); }
.steps-bar li[data-state="done"] { color: var(--on-dark-muted); border-top-color: var(--color-accent); }

.qstep { border: 0; padding: 0; margin: 0 0 var(--space-6); }
.qstep > legend {
  font-family: var(--font-display); font-weight: var(--weight-bold);
  font-size: clamp(1.5rem, 2.6vw, 2rem); letter-spacing: -0.02em;
  color: var(--on-dark); padding: 0; margin-bottom: var(--space-2);
}
.qstep__hint { color: var(--on-dark-muted); margin-bottom: var(--space-6); }
.js .qstep[hidden] { display: none; }

.qnav { display: flex; flex-wrap: wrap; gap: var(--space-4); align-items: center; margin-top: var(--space-8); }
.qnav .btn { min-width: 160px; }
.qback {
  background: none; border: 0; color: var(--on-dark-muted); cursor: pointer;
  font-family: var(--font-display); font-size: var(--text-sm); font-weight: var(--weight-bold);
  letter-spacing: 0.06em; text-transform: uppercase; padding: var(--space-3);
  min-height: 48px;
}
.qback:hover { color: var(--on-dark); }
.qback[hidden] { display: none; }

/* ==========================================================================
   INTERIOR PAGES
   These were shipping on the bare generic template: an H1 on black, one-line
   sections, and a bulleted list — on pages about a visual craft, with zero
   photography. The homepage got every design pass and these got none.

   Same rules as the homepage: photography does the work, each block has its own
   shape, and cards stay rare.
   ========================================================================== */

/* ---- Interior hero: photo + copy, never copy over photo ------------------ */
/* Same lesson as the in-the-shop band — these snapshots have no negative space,
   so text goes beside the image, not on it. */
.phero { background: var(--surface-1); border-bottom: 1px solid var(--surface-3); }
.phero__grid { display: grid; align-items: stretch; }
@media (min-width: 900px) { .phero__grid { grid-template-columns: 1.1fr 1fr; } }
.phero__copy { display: flex; flex-direction: column; justify-content: center; padding: clamp(2rem, 5vw, 4.5rem) clamp(1.5rem, 4vw, 4rem); }
.phero__copy h1 { max-width: 13ch; margin-bottom: var(--space-5); }
.phero__copy p:not(.eyebrow) { color: var(--on-dark-muted); max-width: 44ch; font-size: var(--text-md); }
/* Identical trap to .bleed__figure, and it was live on three service pages:
   aspect-ratio inside an align-items:stretch grid derives WIDTH from the row
   height set by the copy column. On /service-area/cleburne-tx at 1024px the
   figure came out 1218px wide in a 1024px grid — 194px over. The cell supplies
   both dimensions; only the one-column case needs a floor. */
.phero__figure { position: relative; overflow: hidden; min-height: 280px; background: var(--surface-2); }
@media (max-width: 859px) { .phero__figure { min-height: clamp(280px, 58vw, 420px); } }
.phero__figure img { width: 100%; height: 100%; object-fit: cover; }
.phero__actions { display: flex; flex-wrap: wrap; gap: var(--space-4); margin-top: var(--space-8); }

/* ---- Spec rows: a real definition list, not a stranded sentence ---------- */
.spec { display: grid; gap: 0; margin: 0; }
.spec__row { display: grid; gap: var(--space-3) clamp(1.5rem, 4vw, 3rem); padding-block: var(--space-6); border-top: 1px solid var(--surface-3); }
@media (min-width: 760px) { .spec__row { grid-template-columns: 1fr 2fr; } }
.spec__row:last-child { border-bottom: 1px solid var(--surface-3); }
.spec dt {
  font-family: var(--font-display); font-weight: var(--weight-bold); font-size: var(--text-xs);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--color-accent);
}
.spec dd { margin: 0; color: var(--on-dark-muted); max-width: 52ch; }
.section--light .spec__row { border-color: var(--color-neutral-200); }
.section--light .spec dd { color: var(--color-neutral-800); }
/* Red at 14px bold on white is 4.00:1 and fails. On light rows the spec label
   goes black; the accent stays as the rule above the row, which is decorative.
   This is the documented rule and I broke it again building the spec block. */
.section--light .spec dt { color: var(--color-ink-strong); }

/* The fit list and the sibling cross-links both became .panel. Their old
   components are gone rather than left in the sheet as a second way to build
   the same thing. */

/* ---- Catalog cards: the partners as real destinations ------------------- */
.cats { display: grid; gap: var(--space-4); }
@media (min-width: 800px) { .cats { grid-template-columns: repeat(2, 1fr); } }
.cat {
  position: relative; overflow: hidden; display: flex; flex-direction: column;
  justify-content: flex-end; min-height: 340px; padding: clamp(1.5rem, 3vw, 2.5rem);
  border: 1px solid var(--surface-3); text-decoration: none; isolation: isolate;
}
.cat__media { position: absolute; inset: 0; z-index: -2; }
.cat__media img { width: 100%; height: 100%; object-fit: cover; transition: transform 800ms var(--ease-out); }
.cat::after { content: ""; position: absolute; inset: 0; z-index: -1; background: linear-gradient(to top, rgba(0,0,0,0.88) 12%, rgba(0,0,0,0.35) 100%); }
.cat:hover .cat__media img { transform: scale(1.05); }
.cat__name { font-family: var(--font-display); font-size: clamp(1.5rem, 2.4vw, 2rem); font-weight: var(--weight-bold); color: var(--on-dark); margin-bottom: var(--space-2); }
.cat p { color: rgba(255,255,255,0.86); margin: 0 0 var(--space-5); max-width: 34ch; }
.cat__go { font-family: var(--font-display); font-size: var(--text-sm); font-weight: var(--weight-bold); letter-spacing: 0.06em; text-transform: uppercase; color: var(--on-dark); display: inline-flex; gap: var(--space-2); }
.cat__go::after { content: "\2197"; }

/* ---- Gallery: vary the tiles so it reads as a wall, not a spreadsheet ---- */
.gallery { display: grid; gap: var(--space-3); grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); grid-auto-flow: dense; }
.gallery picture:nth-child(6n + 1) { grid-column: span 2; grid-row: span 2; }
.gallery picture:nth-child(6n + 4) { grid-row: span 2; }
@media (max-width: 620px) { .gallery picture:nth-child(n) { grid-column: auto; grid-row: auto; } }

/* ==========================================================================
   ACCENT ON LIGHT ROWS — one block, not scattered patches
   #FF0000 is 4.86:1 on the dark ground and clears the normal-text bar there.
   On white it is 4.00:1 and on the neutral-50 wash 3.73:1, so it is legal ONLY
   for large text (>=24px, or >=18.66px bold) and for non-text decoration.

   Every SMALL accent-coloured text on a light row is corrected here. I broke
   this rule three separate times building the interior pages after writing it
   down, so it lives in one place now instead of being remembered per component.
   Large accent text and pure decoration (rules, borders, dots, focus) stay red.
   ========================================================================== */
.section--light .required-mark,
.section--light .picker__count { color: var(--color-ink-strong); }

/* Hover states are fine — a 4:1 hover is still legible and is not the resting
   state — but link hover on light goes to the darker red used for errors so it
   clears the bar in every state. */
.section--light a:hover,
.section--light .svc__link:hover,
.section--light .picker__btn:hover { color: var(--color-error); }

/* Large accent text keeps the brand red: these clear the 3:1 large-text bar. */
.section--light .stat p.stat__num { color: var(--color-accent); }

/* ==========================================================================
   INTERACTIVE SUBPAGE COMPONENTS
   Each of these does a job. frontend-design: motion and interaction should
   serve the subject, and an orchestrated moment beats scattered effects.
   ========================================================================== */

/* ---- Gallery: filter + lightbox ----------------------------------------- */
.gal-filter { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-8); }
.gal-filter button {
  /* 1.1875rem, not --text-sm. The active state is WHITE on red at 4.00:1, which
     only clears the 3:1 large-text bar — below 18.66px bold it needs 4.5:1 and
     fails. Same load-bearing size rule as the nav CTA. */
  font-family: var(--font-display); font-size: 1.1875rem; font-weight: var(--weight-bold);
  letter-spacing: 0.06em; text-transform: uppercase; cursor: pointer;
  background: transparent; color: var(--on-dark);
  border: 1px solid var(--surface-3); padding: var(--space-3) var(--space-5); min-height: 48px;
  transition: border-color var(--duration-quick) var(--ease-out), background var(--duration-quick) var(--ease-out);
}
.gal-filter button:hover { border-color: var(--color-accent); }
.gal-filter button[aria-pressed="true"] { background: var(--color-accent); border-color: var(--color-accent); color: var(--on-dark); }

.gallery picture { cursor: zoom-in; position: relative; }
.gallery picture[hidden] { display: none !important; }

.lightbox {
  position: fixed; inset: 0; z-index: 200; display: grid; place-items: center;
  background: rgba(5, 5, 5, 0.94); padding: clamp(1rem, 4vw, 3rem);
}
.lightbox[hidden] { display: none !important; }
.lightbox img { max-width: min(92vw, 1200px); max-height: 82vh; width: auto; height: auto; }
.lightbox__cap {
  color: var(--on-dark-muted); font-size: var(--text-sm); text-align: center;
  margin-top: var(--space-4); max-width: 60ch;
}
.lightbox__close, .lightbox__prev, .lightbox__next {
  position: absolute; background: rgba(255,255,255,0.08); border: 1px solid var(--surface-3);
  color: var(--on-dark); width: 52px; height: 52px; cursor: pointer;
  display: grid; place-items: center; font-size: 1.5rem; line-height: 1;
  transition: background var(--duration-quick) var(--ease-out);
}
.lightbox__close:hover, .lightbox__prev:hover, .lightbox__next:hover { background: var(--color-accent); border-color: var(--color-accent); }
.lightbox__close { top: var(--space-5); right: var(--space-5); }
.lightbox__prev { left: var(--space-5); top: 50%; transform: translateY(-50%); }
.lightbox__next { right: var(--space-5); top: 50%; transform: translateY(-50%); }
@media (max-width: 620px) {
  .lightbox__prev { left: var(--space-3); }
  .lightbox__next { right: var(--space-3); }
}

/* ---- Method comparison -------------------------------------------------- */
.compare { border: 1px solid var(--surface-3); }
.compare__tabs { display: grid; grid-template-columns: repeat(2, 1fr); }
.compare__tabs button {
  font-family: var(--font-display); font-size: clamp(1rem, 1.6vw, 1.25rem); font-weight: var(--weight-bold);
  background: transparent; color: var(--on-dark-muted); cursor: pointer;
  border: 0; border-bottom: 3px solid var(--surface-3);
  padding: var(--space-5) var(--space-4); min-height: 64px;
  transition: color var(--duration-quick) var(--ease-out), border-color var(--duration-quick) var(--ease-out);
}
.compare__tabs button[aria-selected="true"] { color: var(--on-dark); border-bottom-color: var(--color-accent); }
.compare__panel { padding: clamp(1.5rem, 3vw, 2.5rem); }
.compare__panel[hidden] { display: none !important; }
.compare__verdict {
  font-family: var(--font-display); font-size: clamp(1.25rem, 2.2vw, 1.75rem); font-weight: var(--weight-bold);
  color: var(--on-dark); margin: 0 0 var(--space-5); max-width: 32ch; line-height: 1.2;
}
.compare__list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0; }
.compare__list li {
  display: grid; gap: var(--space-3) var(--space-5); padding-block: var(--space-4);
  border-top: 1px solid var(--surface-3); max-width: none; color: var(--on-dark-muted);
}
@media (min-width: 620px) { .compare__list li { grid-template-columns: 12ch 1fr; } }
.compare__list b {
  font-family: var(--font-display); font-size: var(--text-xs); font-weight: var(--weight-bold);
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--color-accent);
}
.section--light .compare { border-color: var(--color-neutral-200); }
.section--light .compare__tabs button { color: var(--color-neutral-800); border-bottom-color: var(--color-neutral-200); }
.section--light .compare__tabs button[aria-selected="true"] { color: var(--color-ink-strong); border-bottom-color: var(--color-accent); }
.section--light .compare__verdict { color: var(--color-ink-strong); }
.section--light .compare__list li { border-color: var(--color-neutral-200); color: var(--color-neutral-800); }
.section--light .compare__list b { color: var(--color-ink-strong); }

/* ==========================================================================
   PANEL CARDS — one component, used by the Q&A, the fit list, the sibling
   links and the location method cards. Client asked for the same treatment in
   four places, so it is one class rather than four near-copies that drift.
   Light ground on purpose: these blocks were reading as heavy black slabs.
   ========================================================================== */
.panels { display: grid; gap: var(--space-4); }
@media (min-width: 640px)  { .panels--2 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 640px)  { .panels--3 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .panels--3 { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 640px)  { .panels--4 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1100px) { .panels--4 { grid-template-columns: repeat(4, 1fr); } }

.panel {
  --panel-pad: clamp(1.25rem, 2.2vw, 1.75rem);
  background: var(--color-ground);
  border: 1px solid var(--color-neutral-200);
  border-top: 3px solid var(--color-accent);
  padding: var(--panel-pad);
  display: flex; flex-direction: column; gap: var(--space-3);
  text-decoration: none;
  transition: transform var(--duration-quick) var(--ease-out),
              box-shadow var(--duration-quick) var(--ease-out);
}
a.panel:hover { transform: translateY(-3px); box-shadow: 0 10px 28px rgba(0,0,0,0.28); }
.panel__kicker {
  font-family: var(--font-display); font-size: var(--text-xs); font-weight: var(--weight-bold);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--color-ink-strong);
  margin: 0;
}
.panel__title {
  font-family: var(--font-display); font-size: var(--text-lg); font-weight: var(--weight-bold);
  color: var(--color-ink-strong); margin: 0; line-height: 1.2; max-width: none;
}
.panel p { color: var(--color-neutral-800); margin: 0; max-width: none; }
a.panel .panel__title { transition: color var(--duration-quick) var(--ease-out); }
a.panel:hover .panel__title { color: var(--color-error); }
.panel__go {
  font-family: var(--font-display); font-size: var(--text-xs); font-weight: var(--weight-bold);
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-ink-strong);
  margin-top: auto; display: inline-flex; align-items: center; gap: var(--space-2);
}
.panel__go::after { content: "\2192"; transition: transform var(--duration-quick) var(--ease-out); }
a.panel:hover .panel__go::after { transform: translateX(4px); }

/* Compact variant for a plain list of things a service suits. */

/* ---- Service cards: the same panel, carrying a photograph ---------------
   The homepage "What we do" was four full-width alternating photo rows — a
   different component from everything the interior pages now use, and about
   1800px of scrolling for four links. Same .panel as the interlink and suits
   cards, with the image bleeding to the card edge under the red rule.

   aspect-ratio is safe HERE and was not safe on .bleed__figure: this sits in a
   flex column whose width comes from the grid track, so the ratio derives
   height from width. The bleed figure was stretched to a row height and
   derived width from it, which is how it grew past its column.
   ------------------------------------------------------------------------ */
.svc-card { padding-top: 0; overflow: hidden; }
.svc-card__media {
  display: block; margin: 0 calc(-1 * var(--panel-pad)) var(--space-1);
  aspect-ratio: 4 / 3; overflow: hidden; background: var(--surface-1);
}
.svc-card__media picture { display: block; width: 100%; height: 100%; }
.svc-card__media img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform 700ms var(--ease-out);
}
/* Focus gets the same treatment as hover — a keyboard user should see the card
   respond, not just get a ring. */
.svc-card:hover .svc-card__media img,
.svc-card:focus-visible .svc-card__media img { transform: scale(1.06); }
.svc-card:focus-visible { transform: translateY(-3px); box-shadow: 0 10px 28px rgba(0, 0, 0, 0.28); }
/* A <span>, not a <p>: the whole card is one <a>, and spans keep the markup
   unambiguous inside a link. .panel p never applies, so it needs its own rule. */
.panel__blurb { color: var(--color-neutral-800); font-size: var(--text-sm); line-height: var(--leading-normal); }
.svc-card .panel__list { margin-top: auto; padding-top: var(--space-2); }
.svc-card .panel__go { margin-top: var(--space-3); }

/* The comparison sits on the dark homepage section as well as the light service
   pages, so it cannot assume a ground.

   :not() is load-bearing, not tidiness. .section--light IS a .section, so a
   plain `.section .compare__tabs button` ties the existing light override at
   (0,2,1) and beats it on source order alone — which put white tab labels on
   the white service-page sections at 1:1 and 1.96:1. Same specificity trap
   that has bitten .stat p, .hero__inner and .section__head--row p here before.
   Excluding the light variant raises this to (0,3,1) and stops the collision. */
.section:not(.section--light) .compare__tabs button { color: var(--on-dark-muted); }
.section:not(.section--light) .compare__tabs button[aria-selected="true"] { color: var(--on-dark); }

/* ---- Picker panel: light, per client feedback --------------------------- */
.picker__panel { background: var(--color-ground); border-color: var(--color-neutral-200); }
.picker__panel .picker__label,
.picker__panel .picker__chosen { color: var(--color-neutral-800); }
.picker__panel .picker__count { color: var(--color-accent); }
.picker__panel .btn { border-color: var(--color-accent); }

/* ---- Contact: details beside a map ------------------------------------- */
.contactgrid { display: grid; gap: var(--space-6); align-items: stretch; }
@media (min-width: 900px) { .contactgrid { grid-template-columns: 1fr 38%; } }
.contactgrid__map { min-height: 380px; position: relative; border: 1px solid var(--color-neutral-200); }
.contactgrid__map iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; }

.contactgrid__map { background: var(--color-neutral-50); display: grid; place-items: center; }
.contactgrid__fallback { padding: var(--space-6); text-align: center; margin: 0; }

/* Item list inside a panel card — the things a service actually suits. */
.panel__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.panel__list li {
  max-width: none; font-size: var(--text-sm); color: var(--color-neutral-800);
  display: flex; align-items: baseline; gap: var(--space-3);
}
.panel__list li::before { content: ""; width: 5px; height: 5px; background: var(--color-accent); flex: none; transform: translateY(-2px); }
