/* ==========================================================================
   Okayam Auto Repair Shop

   Premium automotive, not minimal SaaS. Source system: `bmw` from the
   design-md catalogue. Its central rule is the one this site was missing:
   "page rhythm relies on contrast" and "depth comes entirely from colour-block
   contrast". Wall-to-wall white reads cheap. Dark bands framing light content
   read engineered.

   Signatures taken from the spec:
   - Dark navy #1a2129 bands, light canvas between them
   - Heavy 700 display against Light 300 body (the editorial contrast)
   - Rectangular buttons, near-zero radius (the corporate automotive dialect)
   - Uppercase labels at wide tracking
   - 80px section rhythm
   ========================================================================== */

:root {
  /* Surfaces */
  --canvas: #ffffff;
  --surface-soft: #f7f7f8;
  --surface-card: #fafafa;
  --surface-strong: #ebecee;
  --dark: #1a2129;
  --dark-2: #222b35;
  --dark-3: #2c3742;
  --dark-deep: #12181e;

  /* Ink */
  --ink: #1a1a1a;
  --body: #3c3c3c;
  --muted: #6b6b6b;
  /* Was #9a9a9a: 2.81:1 on white, failing AA at all 18 call sites - the date
     under every recommendation, the calendar's day headers, "Sunday / Closed"
     on the hours card, every input placeholder. #767676 measures 4.54:1 on
     white and 4.51:1 on the closed row's tint. */
  --faint: #767676;
  --on-dark: #ffffff;
  --on-dark-soft: #b9c0c7;
  --on-dark-faint: #7d8891;

  /* Lines */
  --line: #e6e6e6;
  --line-strong: #cccccc;
  --line-dark: rgba(255, 255, 255, 0.14);
  --line-dark-soft: rgba(255, 255, 255, 0.08);

  /* Brand action */
  --primary: #1c69d4;
  --primary-active: #0653b6;
  --primary-soft: #e8f0fc;

  /* Status */
  --ok: #15803d;
  --ok-tint: #e9f6ed;
  --warn: #a16207;
  --warn-tint: #fdf6e3;
  --bad: #c0261b;
  --bad-tint: #fdeded;
  --info: #1c69d4;
  --info-tint: #e8f0fc;

  /* Geometry: sharp. Engineered, not app-rounded. */
  --r-none: 0px;
  --r-xs: 2px;
  --r-sm: 3px;
  --r-md: 4px;
  --r-lg: 6px;

  /* Aliases kept so booking.css / admin.css keep working. */
  --radius: 3px;
  --radius-sm: 2px;
  --pill: 2px;
  --bg: #ffffff;
  --bg-soft: #f7f7f8;
  --ink-soft: #3c3c3c;
  --line-soft: #f0f0f0;
  --navy: #1c69d4;
  --navy-dark: #0653b6;
  --navy-deep: #1a2129;
  --navy-tint: #e8f0fc;
  --amber: #e8a33d;

  --shadow-sm: 0 1px 2px rgba(26, 33, 41, 0.08);
  --shadow: 0 4px 18px -6px rgba(26, 33, 41, 0.18);
  --shadow-lg: 0 24px 60px -24px rgba(26, 33, 41, 0.45);

  --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;

  --header-h: 78px;
  --wrap: 1200px;
  --gutter: 24px;
  --section: 80px;
}

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

/* Icon placeholders.
   Every icon on the site is an <i> that has an <svg> injected into it. An <i>
   is inline by default, so the svg lands on the TEXT BASELINE and leaves
   descender space beneath it, which lifts it about 3px above the optical centre
   of the label beside it. Measured on the hero buttons: icon centre 632.9,
   label centre 635.9. Making the wrapper a flex box removes the baseline from
   the equation entirely. */
i:has(> svg) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}
i > svg { display: block; }

[hidden] { display: none !important; }

html {
  scroll-behavior: smooth;
  color-scheme: light;
  scroll-padding-top: calc(var(--header-h) + 16px);
  -webkit-tap-highlight-color: rgba(28, 105, 212, 0.12);
}

body {
  margin: 0;
  background: var(--canvas);
  color: var(--body);
  font-family: var(--font);
  font-size: 17px;
  font-weight: 300;              /* BMW: Light body is half the signature */
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  font-feature-settings: "ss01";
  touch-action: manipulation;
}

img { max-width: 100%; display: block; }

a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: underline; }

h1, h2, h3, h4 {
  margin: 0;
  color: var(--ink);
  font-weight: 700;              /* heavy display against light body */
  line-height: 1.08;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

h1 { font-size: clamp(2.6rem, 6vw, 4rem); }
h2 { font-size: clamp(1.9rem, 3.6vw, 2.75rem); line-height: 1.12; }
h3 { font-size: 1.2rem; line-height: 1.3; letter-spacing: -0.012em; }
h4 { font-size: 1rem; letter-spacing: -0.008em; }

p { margin: 0 0 1rem; text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

strong, b { font-weight: 600; color: var(--ink); }

:focus-visible { outline: 2px solid var(--primary); outline-offset: 3px; }

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin: 0 auto;
  padding-left: max(var(--gutter), env(safe-area-inset-left));
  padding-right: max(var(--gutter), env(safe-area-inset-right));
}

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  top: -60px; left: 12px;
  z-index: 200;
  background: var(--primary);
  color: #fff;
  padding: 12px 18px;
  font-weight: 600;
  transition: top 0.18s;
}
.skip-link:focus { top: 0; text-decoration: none; }

/* ---------- Section scaffolding ---------- */

/* Uppercase label with a rule: the premium tell, straight from the spec. */
.eyebrow {
  display: block;
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--primary);
  margin-bottom: 18px;
  position: relative;
  padding-left: 36px;
}
.eyebrow::before {
  content: '';
  position: absolute;
  left: 0; top: 0.55em;
  width: 26px; height: 2px;
  background: var(--primary);
}

.section { padding: var(--section) 0; }
.section--tight { padding: calc(var(--section) * 0.6) 0; }
/* The pale band is the backdrop for the before/after gallery, so it carries a
   technical grid rather than being flat paint: 64px squares at 3.5% of the ink
   colour, masked to an ellipse so it fades out at the edges instead of
   stopping at a hard line. Close to a drawing sheet, which is the right
   register for a workshop, and quiet enough that the photographs stay the
   loudest thing on the band.

   `isolation` keeps the pseudo-element from interacting with anything the
   section's own children do with blend modes later. */
.section--soft {
  background: var(--surface-soft);
  position: relative;
  isolation: isolate;
}
.section--soft::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-image:
    linear-gradient(rgba(26, 33, 41, 0.055) 1px, transparent 1px),
    linear-gradient(90deg, rgba(26, 33, 41, 0.055) 1px, transparent 1px);
  background-size: 56px 56px;
  background-position: center;
  /* Wide enough that the grid reads across the whole band, with the fade
     only taking hold near the edges so it never ends on a hard line. */
  -webkit-mask-image: radial-gradient(ellipse 96% 86% at 50% 45%, #000 42%, transparent 92%);
  mask-image: radial-gradient(ellipse 96% 86% at 50% 45%, #000 42%, transparent 92%);
}
/* A single wide sweep of the brand blue, barely there, to stop the band
   reading as grey. */
.section--soft::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background:
    radial-gradient(ellipse 62% 46% at 14% 22%, rgba(28, 105, 212, 0.085), transparent 68%),
    radial-gradient(ellipse 54% 40% at 88% 82%, rgba(28, 105, 212, 0.05), transparent 70%);
}
.section--soft > .wrap { position: relative; z-index: 1; }

/* ---------- The artboard ----------

   One piece of artwork behind a RUN of light bands, rather than one per band.

   Each band used to carry its own background. `cover` crops to the element it
   is painting, so three bands meant three independent crops of the same file:
   the diagonals restarted at every boundary and the page read as stacked
   rectangles. Moving the image up to the wrapper makes it a single crop that
   the bands sit on top of, so the lines run unbroken from the hero all the way
   down to the dark locate band. The seams are still there in the markup; there
   is simply nothing at them to see.

   The run ends where a DELIBERATE band change begins - the dark hero above and
   the dark locate band below. Those edges are meant to be hard.

   WHY THE WASHES ARE WHAT THEY ARE (measured, not guessed - scripts in the
   scratchpad): every pixel of both sources was scanned for relative luminance,
   then a binary search found the smallest blend toward white that lifts the
   DARKEST pixel in the file past what each text colour needs for the 4.5:1
   WCAG AA asks of body text. On the shipped files:

     pick.jpg    60% wash   darkest 0.618   lede 7.03:1   links 4.58:1
     sheen.jpg   33% wash   darkest 0.619   lede 7.04:1   links 4.58:1

   A floor, not an average: it holds wherever the art happens to crop, at any
   viewport, so no arrangement of the page can put text on an illegal patch.
   Where the two overlap in the fade the composite is a blend of the two, which
   cannot be darker than the darker of them, so the floor survives that too. */

.artboard {
  position: relative;
  isolation: isolate;
  background-color: var(--canvas);
  background-image: url('/images/bg/sheen.jpg');
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

/* The artwork's structure is all in its upper half, so the foot of the run -
   the last stretch before the dark locate band - was arriving as flat paint.
   Two marks finish it, both faint enough to stay out of the way: a wash of
   the brand blue coming up from the left, and the chequer from the hours card
   in the opposite corner, larger and skewed so it reads as motion.
   Reusing the chequer rather than inventing a second motif is the point.

   Worst case measured: the chequer squares are the brand blue at 10% over the
   artwork's darkest pixel, which composites to luminance 0.688 - still clear
   of the 0.607 the section headings need. Nothing but the ghost button sits
   down here anyway, and that carries its own white. */
.artboard::before {
  content: '';
  position: absolute;
  left: -8%;
  bottom: 0;
  width: 56%;
  height: 420px;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(ellipse at 28% 86%, rgba(28, 105, 212, 0.1), transparent 68%);
}
.artboard::after {
  content: '';
  position: absolute;
  right: 0;
  bottom: 0;
  width: min(48%, 580px);
  height: 200px;
  z-index: 0;
  pointer-events: none;
  transform: skewX(-18deg);
  transform-origin: 100% 100%;
  background-image:
    repeating-conic-gradient(rgba(28, 105, 212, 0.1) 0% 25%, transparent 0% 50%);
  background-size: 48px 48px;
  background-position: right bottom;
  -webkit-mask-image: linear-gradient(305deg, #000, transparent 68%);
  mask-image: linear-gradient(305deg, #000, transparent 68%);
}

/* The bands are windows onto the artboard, so none of them may paint over it.
   This also undoes .section--reviews' own white fill. */
.artboard > .section {
  position: relative;
  background: none;
}
.artboard > .section > .wrap {
  position: relative;
  z-index: 1;
}

/* The car belongs to the service picker specifically, so it stays a layer of
   its own rather than joining the artboard.

   Its TOP edge is hard, which is correct: it lands against the dark hero,
   where a hard change is the point. Its bottom dissolves into the artboard, so
   by the time the band ends there is nothing left to misalign with the band
   below - which is what the seam between the picker and the work gallery used
   to be. */
.section--pick::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: url('/images/bg/pick.jpg');
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 52%, transparent 96%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 52%, transparent 96%);
}

/* Half-size files for phones: the full ones are cut for a desktop band, and a
   phone would download roughly twice the bytes to paint a third of them. */
@media (max-width: 700px) {
  .artboard { background-image: url('/images/bg/sheen-900.jpg'); }
  .section--pick::before { background-image: url('/images/bg/pick-900.jpg'); }
}

/* The at-risk text, darkened only where artwork is behind it.
   This is what makes a VISIBLE image possible at all: at the site's usual lede
   grey and link blue the calculation above demanded an 85% wash on pick.jpg,
   and the artwork would have been a white rectangle. */
.artboard .lede { color: var(--body); }
.artboard .eyebrow,
.artboard .btn--quiet { color: var(--primary-active); }
.artboard .eyebrow::before { background: var(--primary-active); }

/* Not the recommendations: those sit on white cards of their own, so they keep
   the site's ordinary secondary grey and link blue. Only text standing
   directly on the artwork needs darkening. */

/* ---------- The before/after reveal ----------
   The cards used to just fade up with everything else. These photographs are
   the single most persuasive thing on the site, so they get a reveal that
   actually says something: each pair wipes in left to right, BEFORE first and
   AFTER a beat later, which is the order a person reads them in anyway.

   The photo also "develops" as it arrives - starting flatter and darker and
   settling to full colour. That is done with `filter`, deliberately, because
   `transform` is already spoken for by the hover zoom and an animation with a
   fill mode would win against it and kill the hover for good. */

@keyframes shot-develop {
  from { filter: saturate(0.5) brightness(0.8) contrast(1.04); }
  to   { filter: none; }
}

html.reveal-on .job[data-reveal] .job__shot {
  clip-path: inset(0 100% 0 0);
}
html.reveal-on .job[data-reveal].is-in .job__shot {
  clip-path: inset(0 0 0 0);
  transition: clip-path 0.72s cubic-bezier(0.22, 0.61, 0.36, 1);
}
/* AFTER follows BEFORE, rather than arriving with it. */
html.reveal-on .job[data-reveal].is-in .job__shot:nth-of-type(2) {
  transition-delay: 0.2s;
}
html.reveal-on .job[data-reveal].is-in .job__shot img {
  animation: shot-develop 1.15s cubic-bezier(0.22, 0.61, 0.36, 1) both;
}
html.reveal-on .job[data-reveal].is-in .job__shot:nth-of-type(2) img {
  animation-delay: 0.2s;
}

/* The tags land after their photo, not with it. */
html.reveal-on .job[data-reveal] .job__tag {
  opacity: 0;
  transform: translateY(-6px);
}
html.reveal-on .job[data-reveal].is-in .job__tag {
  opacity: 1;
  transform: none;
  transition: opacity 0.4s ease 0.5s, transform 0.4s cubic-bezier(0.22, 0.61, 0.36, 1) 0.5s;
}
html.reveal-on .job[data-reveal].is-in .job__shot:nth-of-type(2) .job__tag {
  transition-delay: 0.7s;
}

/* The card itself lifts rather than only fading, and keeps the lift on hover. */
.job {
  transition: box-shadow 0.3s ease, transform 0.3s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.job:hover {
  transform: translateY(-3px);
  box-shadow: 0 14px 34px rgba(26, 33, 41, 0.12);
}

@media (prefers-reduced-motion: reduce) {
  html.reveal-on .job[data-reveal] .job__shot,
  html.reveal-on .job[data-reveal].is-in .job__shot {
    clip-path: none !important;
    transition: none !important;
  }
  html.reveal-on .job[data-reveal] .job__tag,
  html.reveal-on .job[data-reveal].is-in .job__tag {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  html.reveal-on .job[data-reveal].is-in .job__shot img { animation: none !important; }
  .job:hover { transform: none; }
}
.section--line { border-top: 1px solid var(--line); }

/* Dark band: the contrast device the whole page rhythm depends on. */
.section--dark {
  background: var(--dark);
  color: var(--on-dark-soft);
  position: relative;
  overflow: hidden;
}
.section--dark h1, .section--dark h2, .section--dark h3,
.section--dark h4, .section--dark strong { color: var(--on-dark); }
.section--dark .lede, .section--dark .muted { color: var(--on-dark-soft); }
.section--dark .eyebrow { color: #6ea8f5; }
.section--dark .eyebrow::before { background: #6ea8f5; }
.section--dark a { color: #8dbcf8; }

.section-head { max-width: 680px; margin-bottom: 52px; }
.section-head--center { margin-left: auto; margin-right: auto; text-align: center; }
.section-head--center .eyebrow { padding-left: 0; }
.section-head--center .eyebrow::before { display: none; }

.lede {
  font-size: clamp(1.05rem, 1.5vw, 1.2rem);
  color: var(--muted);
  margin-top: 18px;
  font-weight: 300;
}
.muted { color: var(--muted); }
.small { font-size: 0.9rem; }

/* ---------- Buttons: rectangular, the automotive dialect ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  font-family: var(--font);
  font-size: 0.86rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 16px 30px;
  border: 1px solid transparent;
  border-radius: var(--r-lg);
  cursor: pointer;
  text-align: center;
  white-space: nowrap;
  position: relative;
  transition: background 0.22s ease, border-color 0.22s ease, color 0.22s ease,
    box-shadow 0.28s ease, transform 0.22s cubic-bezier(0.22, 0.61, 0.36, 1);
}
/* `gap` separates flex children, and a bare text node beside a <span> counts
   as two. Keeping the whole label inside one span stops "Book appointment"
   rendering with a 10px hole in the middle. */
.btn__label { display: inline; }
.btn:hover { text-decoration: none; }
.btn:active { transform: translateY(1px); }
/* Back to 17px: the inline-flex wrapper fix above is what actually corrected
   the alignment (centring error is 0.01px either way), and the larger icon
   carries the button better. */
.btn svg { width: 17px; height: 17px; flex: none; }

/* A primary button is a lit surface rather than a flat swatch: the gradient
   runs top-left to bottom-right so it reads as a single light source, the
   hairline along the top edge is the highlight that source would leave, and
   the shadow beneath is tinted with the button's own blue instead of grey, so
   it looks like colour spilling rather than a drop shadow.

   THE LIGHT END IS CAPPED. The label is 13.8px bold - not "large text" by
   WCAG, so it needs a full 4.5:1 - and white stops clearing that at about
   L 0.183. A first draft of this ran to #4a90f0 at the top-left corner, which
   measures 3.21:1: a button that looks brighter and cannot be read. Every
   stop is checked by `node scripts/contrast.js`; the brightest in use is
   #2b74da at 4.60:1.

   It is also why hover brightens the BOTTOM of the ramp rather than the top.
   The lift, the glow and the arrow carry the response; the corner under the
   first letters of the label stays where the measurement allows. */
.btn--primary {
  background: linear-gradient(135deg, #2a72d8 0%, #1c69d4 48%, #0d4fb0 100%);
  color: #fff;
  box-shadow:
    0 8px 22px -8px rgba(28, 105, 212, 0.65),
    inset 0 1px 0 rgba(255, 255, 255, 0.22);
}
.btn--primary:hover {
  background: linear-gradient(135deg, #2b74da 0%, #1f6edb 48%, #1361c9 100%);
  color: #fff;
  transform: translateY(-2px);
  box-shadow:
    0 16px 32px -10px rgba(28, 105, 212, 0.78),
    inset 0 1px 0 rgba(255, 255, 255, 0.3);
}
/* Pressed, it sits back down and the glow collapses under it. */
.btn--primary:active {
  transform: translateY(0);
  box-shadow:
    0 4px 12px -6px rgba(28, 105, 212, 0.6),
    inset 0 1px 0 rgba(255, 255, 255, 0.14);
}

/* The arrow is the button saying where it goes. It travels on hover, which is
   the whole animation: a mask rather than a glyph, so it takes the button's
   own colour and is never read aloud as "right arrow". */
.btn--arrow::after {
  content: '';
  width: 18px;
  height: 18px;
  flex: none;
  margin-left: 12px;
  background: currentColor;
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'><path d='M4 12h15M13 5l7 7-7 7'/></svg>");
  mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'><path d='M4 12h15M13 5l7 7-7 7'/></svg>");
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
  transition: transform 0.28s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.btn--arrow:hover::after { transform: translateX(5px); }
/* A full-width button has slack to spare, so the arrow takes the far edge. */
.btn--block.btn--arrow::after { margin-left: auto; }

.btn--ghost { background: transparent; color: var(--ink); border-color: var(--line-strong); }
.btn--ghost:hover { border-color: var(--ink); background: var(--ink); color: #fff; }

.btn--quiet {
  background: transparent;
  color: var(--primary);
  padding: 0;
  text-transform: none;
  letter-spacing: 0;
  font-weight: 600;
}
.btn--quiet:hover { text-decoration: underline; }

.btn--danger { background: transparent; color: var(--bad); border-color: #e3bdba; }
.btn--danger:hover { background: var(--bad); border-color: var(--bad); color: #fff; }

.btn--block { width: 100%; }
.btn--lg { padding: 19px 38px; font-size: 0.92rem; }
.btn--sm { padding: 11px 20px; font-size: 0.76rem; }
.btn[disabled] { opacity: 0.45; cursor: not-allowed; }

/* .hero and .page-head are dark surfaces but carry neither helper class, so
   ghost buttons were rendering dark-on-dark and all but disappearing. */
/* These used to flatten the primary button back to one colour on dark bands,
   which is where the biggest one on the site lives. They now carry the same
   lit surface, lifted a shade because it sits against near-black. */
.on-dark .btn--primary, .section--dark .btn--primary,
.hero .btn--primary, .page-head .btn--primary {
  background: linear-gradient(135deg, #2a72d8 0%, #1c69d4 48%, #0f55bb 100%);
  color: #fff;
  box-shadow:
    0 10px 26px -8px rgba(40, 120, 230, 0.6),
    inset 0 1px 0 rgba(255, 255, 255, 0.26);
}
.on-dark .btn--primary:hover, .section--dark .btn--primary:hover,
.hero .btn--primary:hover, .page-head .btn--primary:hover {
  background: linear-gradient(135deg, #2b74da 0%, #1f6edb 48%, #1361c9 100%);
  color: #fff;
  box-shadow:
    0 18px 38px -10px rgba(40, 120, 230, 0.75),
    inset 0 1px 0 rgba(255, 255, 255, 0.34);
}
.on-dark .btn--ghost, .section--dark .btn--ghost,
.hero .btn--ghost, .page-head .btn--ghost {
  background: transparent;
  color: #fff;
  border-color: rgba(255, 255, 255, 0.34);
}
/* The icon keeps the brand blue while the label stays white: on a photograph
   an all-white button reads as a flat plate, and the one spot of colour is
   what stops it. On hover the button inverts, so the icon goes with it. */
.on-dark .btn--ghost svg, .section--dark .btn--ghost svg,
.hero .btn--ghost svg, .page-head .btn--ghost svg { color: #6ea8f5; }
.on-dark .btn--ghost:hover, .section--dark .btn--ghost:hover,
.hero .btn--ghost:hover, .page-head .btn--ghost:hover {
  background: #fff;
  border-color: #fff;
  color: var(--dark);
  transform: translateY(-2px);
  box-shadow: 0 14px 30px -12px rgba(0, 0, 0, 0.55);
}
.on-dark .btn--ghost:hover svg, .section--dark .btn--ghost:hover svg,
.hero .btn--ghost:hover svg, .page-head .btn--ghost:hover svg { color: var(--primary); }

/* ---------- Header: dark, continuous with the hero ---------- */

/* Fixed rather than sticky, so the hero photograph runs the FULL height of the
   viewport behind it instead of starting 78px down with the top of the frame
   cut off.

   Sticky still occupies a row in the flow - that row was the crop. Fixed takes
   the bar out of the flow entirely, so the height it no longer occupies has to
   be given back: every page reserves it on <body>, and the home page, which
   wants the photo to run underneath, reserves it inside the hero's own padding
   instead. A page opts in by putting `has-hero` on its <body>; nothing is
   inferred, so a page that forgets simply keeps the ordinary solid bar. */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
}

/* The dark fill is a layer rather than the element's own background, because
   a background cannot cross-fade from a gradient to a flat colour - it snaps.
   Fading a layer's opacity does what a transition on `background` cannot.
   The hairline rides with it as an inset shadow for the same reason. */
.site-header::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--dark);
  box-shadow: inset 0 -1px 0 var(--line-dark-soft);
  transition: opacity 0.32s ease;
}

body { padding-top: var(--header-h); }
body.has-hero { padding-top: 0; }

/* Over the hero the bar is see-through, carrying only a soft scrim so the white
   nav keeps its contrast wherever the photograph happens to be bright. Once the
   page scrolls past the hero the fill comes back, because the same white type
   would be invisible against the pale bands underneath. */
body.has-hero .site-header::before { opacity: 0; }
body.has-hero .site-header.is-solid::before { opacity: 1; }
body.has-hero .site-header {
  background: linear-gradient(
    to bottom,
    rgba(26, 33, 41, 0.58),
    rgba(26, 33, 41, 0.22) 62%,
    transparent
  );
  transition: background 0.32s ease;
}
body.has-hero .site-header.is-solid { background: none; }

/* An open menu always needs the solid bar under it, scrolled or not. */
body.has-hero .site-header.is-menu-open::before { opacity: 1; }

/* Everything in the bar sits as one centred group rather than being pushed to
   the two edges: logo, links, phone, CTA, read as a single cluster. */
.site-header__bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 18px;
  height: var(--header-h);
}

/* Shrinkable, not `flex: none`: the wordmark has an ellipsis for a reason, and
   a rigid brand block forces the whole header past the viewport on a phone. */
.brand { display: flex; align-items: center; gap: 13px; color: var(--on-dark); flex: none; }
.brand__mark { flex: none; }
.brand:hover { text-decoration: none; color: var(--on-dark); }

/* The emblem is dark-dominant with a near-black interior, so on the navy
   header it needs its own light separation to avoid reading as a smudge.
   A soft radial halo lifts it without putting it on a cheap white tile. */
.brand__mark {
  width: 48px; height: 48px;
  flex: none;
  object-fit: contain;
  display: block;
  background: radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.16) 0%, transparent 62%);
  border-radius: 50%;
}
.brand__text { display: flex; flex-direction: column; line-height: 1.2; min-width: 0; }
.brand__name {
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.brand__sub {
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: var(--on-dark-faint);
}

.site-nav { margin-left: 0; }
.site-nav ul { display: flex; align-items: center; gap: 4px; list-style: none; margin: 0; padding: 0; }
.site-nav a {
  display: block;
  white-space: nowrap;
  padding: 10px 15px;
  color: var(--on-dark-soft);
  font-size: 0.88rem;
  font-weight: 400;
  letter-spacing: 0.02em;
  border-bottom: 2px solid transparent;
}
.site-nav a:hover { color: #fff; text-decoration: none; }
.site-nav a[aria-current='page'] { color: #fff; border-bottom-color: var(--primary); }

.header-actions { display: flex; align-items: center; gap: 12px; flex: none; }

.header-phone {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: var(--on-dark);
  font-weight: 600;
  font-size: 0.92rem;
  padding: 8px 10px;
}
.header-phone:hover { color: #fff; text-decoration: none; }
.header-phone svg { width: 17px; height: 17px; color: var(--primary); }

.nav-toggle {
  display: none;
  width: 46px; height: 46px;
  background: transparent;
  border: 1px solid var(--line-dark);
  border-radius: var(--r-xs);
  color: #fff;
  cursor: pointer;
  padding: 0;
  place-items: center;
}
.nav-toggle svg { width: 22px; height: 22px; }

/* Between the nav breakpoint and the full container width the header phone,
   the full nav and the CTA do not all fit - so the LABEL goes, not the link.
   Hiding it outright meant that on this site's audience, which is almost
   entirely phones, the header number was never visible to anyone at all. An
   icon-only call button keeps the single most useful control on the page. */
@media (max-width: 1180px) {
  .header-actions .header-phone span { display: none; }
  .header-actions .header-phone {
    width: 44px;
    height: 44px;
    justify-content: center;
    gap: 0;
  }
  .header-actions .header-phone svg { width: 19px; height: 19px; }
}

@media (max-width: 1060px) {
  .site-nav a { padding: 10px 11px; font-size: 0.85rem; }
  .site-header__bar { gap: 14px; }
}

@media (max-width: 980px) {
  /* The nav collapses into an overlay here, so the bar goes back to brand on
     the left and the menu button on the right. */
  .site-header__bar { justify-content: space-between; }

  .site-nav {
    position: fixed;
    inset: var(--header-h) 0 auto;
    background: var(--dark-2);
    border-bottom: 1px solid var(--line-dark);
    padding: 8px var(--gutter) 20px;
    display: none;
  }
  .site-nav[data-open='true'] { display: block; }
  .site-nav ul { flex-direction: column; align-items: stretch; gap: 0; }
  .site-nav a { padding: 15px 4px; font-size: 1.02rem; border-bottom: 1px solid var(--line-dark-soft); }
  .site-nav a[aria-current='page'] { border-bottom-color: var(--primary); }
  .nav-toggle { display: grid; order: 3; }
}

@media (max-width: 430px) {
  .header-actions .btn__long { display: none; }
  .btn { white-space: normal; }
  .brand__sub { display: none; }
}

/* ---------- Hero ---------- */

.hero {
  background: var(--dark);
  color: var(--on-dark-soft);
  padding: calc(var(--header-h) + clamp(56px, 8vw, 104px)) 0 clamp(56px, 8vw, 100px);
  position: relative;
  overflow: hidden;
}

/* Depth from light, not from a drop shadow. */
.hero::before {
  content: '';
  position: absolute;
  top: -30%; right: -10%;
  width: 70%; height: 150%;
  background: radial-gradient(ellipse at center, rgba(28, 105, 212, 0.26), transparent 62%);
  pointer-events: none;
}
/* A single clipped rule along the base: the engineered detail. */
.hero::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--primary) 0 18%, transparent 18%);
}

/* The emblem, large and faint. Decorative only, so it is a background rather
   than an <img>, and it is hidden where it would crowd the copy. */
/* ---------- Hero photograph ----------
   The shop floor sits behind the headline. Two washes, both in the hero's own
   colour so the photo reads as part of the band rather than a picture pasted
   into it:

     horizontal - heavy on the left where the text is, opening up towards the
                  right so the car is actually visible rather than a smudge.
     vertical   - darkens the top and bottom edges so the header and the band
                  below meet the photo without a seam.

   Contrast was measured, not guessed: white on the darkest point of the text
   column, and the result is well past the 4.5:1 that body text needs. */

.hero__photo {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
}
.hero__photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Bias downward: the interesting half of the frame is the car and the
     floor, not the ceiling trusses. */
  object-position: center 62%;
  display: block;
}
.hero__photo::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    linear-gradient(
      90deg,
      /* Measured, not eyeballed: at 0.86/34% and 0.52/64% the lede fell to a
         3.90 contrast ratio at 1280px, under the 4.5 that body text needs.
         The text column runs to roughly 50% of the viewport, so the ramp has
         to stay heavy that far across and only open up beyond it. */
      rgba(26, 33, 41, 0.94) 0%,
      rgba(26, 33, 41, 0.91) 40%,
      rgba(26, 33, 41, 0.58) 72%,
      rgba(26, 33, 41, 0.34) 100%
    ),
    linear-gradient(
      to bottom,
      rgba(26, 33, 41, 0.60) 0%,
      rgba(26, 33, 41, 0.14) 40%,
      rgba(26, 33, 41, 0.72) 100%
    );
}

/* The photo already carries the shop's emblem on the workshop wall, so the
   watermark version behind it would be the same mark twice. */
.hero__emblem { display: none; }

.hero__grid { position: relative; z-index: 1; }

@media (max-width: 860px) {
  /* The text runs full width here instead of sitting in a left column, so the
     horizontal wash no longer protects it. Flatten it to an even, heavier
     one. */
  .hero__photo::after {
    background:
      linear-gradient(
        to bottom,
        rgba(26, 33, 41, 0.86) 0%,
        rgba(26, 33, 41, 0.80) 55%,
        rgba(26, 33, 41, 0.92) 100%
      );
  }
  .hero__photo img { object-position: center 58%; }
}

.hero__emblem {
  position: absolute;
  right: -6%;
  top: 50%;
  transform: translateY(-50%);
  width: min(620px, 52vw);
  aspect-ratio: 1 / 1;
  background: url('/images/logo-512.png') center / contain no-repeat;
  opacity: 0.07;
  pointer-events: none;
  z-index: 0;
}

.hero__grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  gap: clamp(36px, 5vw, 72px);
  align-items: center;
}

.hero h1 { color: var(--on-dark); margin-bottom: 24px; max-width: 15ch; }
.hero h1 em { font-style: normal; color: #6ea8f5; }

.hero__lede {
  font-size: clamp(1.08rem, 1.6vw, 1.26rem);
  color: var(--on-dark-soft);
  max-width: 52ch;
  margin-bottom: 38px;
  font-weight: 300;
}
.hero__cta { display: flex; flex-wrap: wrap; gap: 14px; margin-bottom: 44px; }

/* Flex with content-sized items, not equal 1fr columns.
   With equal columns the gap between the TEXT of one item and the ICON of the
   next depends on how long that item's text happens to be: measured 26px after
   the longest descriptor and 61px after the shortest, which reads as a stray
   icon rather than a rhythm. Sizing each item to its content makes every
   gutter identical. */
.trust-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 22px 40px;
  padding-top: 32px;
  border-top: 1px solid var(--line-dark);
}
.trust-strip__item { flex: 0 1 auto; }
.trust-strip__item { display: flex; gap: 13px; align-items: flex-start; }
.trust-strip__item svg { width: 22px; height: 22px; color: var(--primary); flex: none; margin-top: 2px; }
.trust-strip__item strong { display: block; font-size: 1rem; font-weight: 600; color: var(--on-dark); line-height: 1.3; }
.trust-strip__item span {
  display: block;
  font-size: 0.87rem;
  color: var(--on-dark-faint);
  /* If a descriptor ever does wrap, balance it rather than leaving an orphan
     word on its own line: a stray "jobs" or "surprise" is what made the three
     columns read as unevenly spaced even though the grid tracks are identical. */
  text-wrap: balance;
}

/* ---------- Hours card: light plate on the dark band ---------- */

/* ---------- The hours card ----------
   The one piece of the page a returning customer actually comes back for, so
   it is built like a panel rather than a box of text: an icon chip per row, a
   highlighted row for today, and a closed day that looks closed instead of
   just saying so. */

.info-card {
  background: var(--canvas);
  /* The card is a white panel sitting inside the dark hero, which sets a pale
     text colour for the band. Every child here states its own colour, but one
     that forgets would inherit light grey onto white and vanish. */
  color: var(--body);
  border-radius: 12px;
  padding: 30px 28px;
  box-shadow: var(--shadow-lg);
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

/* The corner is the shop's own work now - the Ranger Raptor from the gallery,
   cut to the front three quarter - rather than the generated workshop that
   was there before. A real finished car is a better thing to put beside the
   opening hours than a picture of a workshop that is not this one, and the
   plain studio background behind it means a diagonal crop reads as a car
   instead of a slice of a busy scene.

   It is mirrored. The mask fades down and to the LEFT, so a subject facing
   left loses its grille into the fade and leaves a door panel on show. The
   number plate is cropped out rather than flipped.

   Shorter and narrower than the first attempt, and the subtitle beside it is
   held to 30 characters a line, so the photograph and the text no longer
   overlap - that overlap is what made it look badly cropped. */
.info-card__art {
  position: absolute;
  top: 0;
  right: 0;
  width: 56%;
  height: 150px;
  z-index: -1;
  pointer-events: none;
  background:
    linear-gradient(216deg, rgba(28, 105, 212, 0.44), rgba(28, 105, 212, 0.12) 52%, transparent 70%),
    url('/images/bg/card-car.jpg') right 42% / cover no-repeat;
  -webkit-mask-image: linear-gradient(216deg, #000 6%, rgba(0, 0, 0, 0.5) 38%, transparent 66%);
  mask-image: linear-gradient(216deg, #000 6%, rgba(0, 0, 0, 0.5) 38%, transparent 66%);
}

/* The chequer in the opposite corner, skewed so it reads as motion. Drawn,
   not an asset: it is four gradients and it stays crisp at any size. */
.info-card::after {
  content: '';
  position: absolute;
  right: -14px;
  bottom: -10px;
  width: 150px;
  height: 64px;
  z-index: -1;
  pointer-events: none;
  transform: skewX(-18deg);
  background-image:
    repeating-conic-gradient(rgba(28, 105, 212, 0.14) 0% 25%, transparent 0% 50%);
  background-size: 19px 19px;
  -webkit-mask-image: linear-gradient(300deg, #000, transparent 72%);
  mask-image: linear-gradient(300deg, #000, transparent 72%);
}

.info-card__head {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  margin-bottom: 22px;
}
.info-card__title { min-width: 0; }
.info-card h3 { margin-bottom: 4px; }
/* Same device as the hero headline: the second word carries the colour. */
.info-card h3 em { font-style: normal; color: var(--primary); }
/* Wraps before it reaches the photograph in the corner. */
.info-card__title .small { color: var(--muted); margin: 0; max-width: 30ch; }

/* A soft blue tile behind an icon. Used for the heading, every row and the
   footnote, which is what ties the card together. */
.icon-chip {
  width: 38px;
  height: 38px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 9px;
  background: var(--primary-soft);
  color: var(--primary);
}
.icon-chip--lg { width: 46px; height: 46px; border-radius: 11px; }
.icon-chip--lg svg { width: 22px; height: 22px; }
.icon-chip svg { width: 17px; height: 17px; }

.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 8px 14px;
  border-radius: 999px;
  margin-bottom: 18px;
}
.status-pill::before { content: ''; width: 8px; height: 8px; border-radius: 50%; background: currentColor; }
.status-pill--open { background: var(--ok-tint); color: var(--ok); }
.status-pill--closed { background: var(--bad-tint); color: var(--bad); }

.hours-list { list-style: none; margin: 0 0 24px; padding: 0; }
.hours-list li {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 9px 12px;
  border-radius: 9px;
  border-bottom: 1px solid var(--line);
  font-size: 0.94rem;
}
.hours-list li:last-child { border-bottom: 0; }
.hours-list .day { color: var(--body); flex: 1 1 auto; }
.hours-list .time {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.hours-list .time svg { width: 16px; height: 16px; color: var(--primary); flex: none; }

/* Today is the row anyone opening this card is looking for, so it is tinted
   and carries a blue edge - the same edge the towing callout and the
   recommendations use. */
.hours-list li[data-today='true'] {
  background: linear-gradient(90deg, var(--primary-soft), rgba(232, 240, 252, 0.25));
  box-shadow: inset 3px 0 0 var(--primary);
  border-bottom-color: transparent;
}
.hours-list li[data-today='true'] .day { color: var(--ink); font-weight: 700; }

/* A closed day reads as closed: greyed, struck from the rhythm of the others,
   and the only red on the card. */
.hours-list li[data-closed='true'] {
  background: var(--surface-strong);
  border-bottom-color: transparent;
}
/* --muted, not --faint: on this row's own tint --faint measures 3.84:1 and
   --muted 4.69:1. A closed day should look quiet, not be unreadable - and
   "which day are you shut?" is one of the two questions this card exists to
   answer. */
.hours-list li[data-closed='true'] .icon-chip { background: #f2f3f5; color: var(--muted); }
.hours-list li[data-closed='true'] .day { color: var(--muted); }
.hours-list .time--closed { color: var(--bad); font-weight: 700; }

.info-card__note {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 16px 0 0;
  font-size: 0.9rem;
  color: var(--muted);
}

@media (max-width: 420px) {
  .info-card { padding: 24px 18px; }
  .hours-list li { gap: 10px; padding: 9px 8px; font-size: 0.9rem; }
  .icon-chip { width: 32px; height: 32px; border-radius: 8px; }
  .icon-chip svg { width: 15px; height: 15px; }
}

/* ---------- Cards: hairline-separated plates ---------- */

.grid { display: grid; gap: 1px; background: var(--line); }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(290px, 100%), 1fr)); }

.card {
  background: var(--canvas);
  padding: 32px 28px;
  display: flex;
  flex-direction: column;
  position: relative;
  transition: background 0.2s;
}
.card--hover:hover { background: var(--surface-card); }
.card--hover::after {
  content: '';
  position: absolute;
  left: 0; bottom: 0;
  width: 0; height: 3px;
  background: var(--primary);
  transition: width 0.26s ease;
}
.card--hover:hover::after { width: 100%; }

.card h3 { margin-bottom: 10px; }
.card p { color: var(--muted); font-size: 0.97rem; }

.card__icon {
  width: 46px; height: 46px;
  display: grid;
  place-items: center;
  color: var(--primary);
  margin-bottom: 22px;
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
}
.card__icon svg { width: 23px; height: 23px; }

.card__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 26px;
  margin-top: auto;
  padding-top: 24px;
  font-size: 0.88rem;
}
.card__meta div { display: flex; flex-direction: column; gap: 3px; }
.card__meta .k {
  color: var(--faint);
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.13em;
  font-weight: 700;
}
.card__meta .v { font-weight: 600; color: var(--ink); }

.card__link {
  margin-top: 20px;
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.card__meta + .card__link { margin-top: 18px; }

.tag {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  white-space: nowrap;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  padding: 6px 11px;
  border-radius: var(--r-xs);
  background: var(--surface-strong);
  color: var(--body);
}
.tag svg { width: 13px; height: 13px; flex: none; }
.tag--navy { background: var(--primary-soft); color: var(--primary-active); }

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

.steps {
  display: grid;
  gap: 1px;
  background: var(--line);
  grid-template-columns: repeat(auto-fit, minmax(min(250px, 100%), 1fr));
  list-style: none;
  margin: 0; padding: 0;
}
.step { background: var(--canvas); padding: 36px 30px; }
.step__num {
  font-size: 2.8rem;
  font-weight: 700;
  line-height: 1;
  color: var(--surface-strong);
  letter-spacing: -0.045em;
  margin-bottom: 18px;
  font-variant-numeric: tabular-nums;
}
.step h3 { margin-bottom: 10px; }
.step p { color: var(--muted); font-size: 0.95rem; }

.section--dark .steps { background: var(--line-dark-soft); }
.section--dark .step { background: var(--dark); }
.section--dark .step__num { color: var(--dark-3); }
.section--dark .step p { color: var(--on-dark-soft); }

/* ---------- Check list ---------- */

.check-list { list-style: none; margin: 28px 0 0; padding: 0; display: grid; gap: 18px; }
/* When the list is a column in its own right rather than a footnote under a
   lede, it does not need the top margin and can breathe a little more. */
.check-list--lead { margin-top: 0; gap: 22px; }
.check-list--lead li { font-size: 1.04rem; }
.check-list li { display: flex; gap: 14px; align-items: flex-start; font-size: 1rem; color: var(--muted); }
.check-list svg { width: 20px; height: 20px; color: var(--primary); flex: none; margin-top: 3px; }
.check-list strong { color: var(--ink); font-weight: 600; }
.section--dark .check-list li { color: var(--on-dark-soft); }
.section--dark .check-list strong { color: var(--on-dark); }

/* The real recommendations are styled further down, with the review grid.
   What stood here was the leftover of the invented testimonials that were
   deleted - star rows, avatars, a name block - and none of those elements are
   rendered any more. It was not harmless: `.review` itself still matched, so
   every real recommendation was being painted as a white card with 34px of
   padding, which is the box that appeared behind the quotes once there was
   artwork behind them to show it up. */

/* ---------- Split ---------- */

.split {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  gap: clamp(36px, 5vw, 72px);
  align-items: start;
}
.split--center { align-items: center; }

/* ---------- CTA band ---------- */

.cta-band {
  background: var(--dark);
  color: var(--on-dark-soft);
  padding: clamp(56px, 7vw, 84px) 0;
  position: relative;
  overflow: hidden;
}
.cta-band::before {
  content: '';
  position: absolute;
  top: -40%; left: 10%;
  width: 60%; height: 180%;
  background: radial-gradient(ellipse at center, rgba(28, 105, 212, 0.22), transparent 62%);
  pointer-events: none;
}
.cta-band__inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  gap: 32px;
  align-items: center;
  justify-content: space-between;
}
.cta-band h2 { color: #fff; max-width: 20ch; }
.cta-band p { color: var(--on-dark-soft); margin: 14px 0 0; max-width: 48ch; }
.cta-band__actions { display: flex; flex-wrap: wrap; gap: 14px; }

/* Keeps a phrase whole so a heading wraps at the gap you chose rather than
   wherever the column happens to run out. The hero headline is two sentences;
   without this it broke as "Fixed right. No / surprise bills." */
.keep { white-space: nowrap; }

/* ---------- FAQ accordion ----------
   Was a card grid. Four cards in a three-column auto-fit grid left one cell
   visibly empty, every card in a row stretched to the tallest answer in it,
   and the block spent about 450px of page saying four things that most readers
   only want one of.

   Collapsed rows cost ~62px each, the questions are all readable at a glance,
   and the reader opens the one they actually came with. <details>/<summary> is
   native: keyboard and screen readers work with no script. */

.faq {
  max-width: 860px;
  margin: 0 auto;
  border-top: 1px solid var(--line);
  background: var(--canvas);
}
.faq__item { border-bottom: 1px solid var(--line); }
.faq__item > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  min-height: 62px;
  padding: 18px 22px;
  font-size: 1rem;
  font-weight: 600;
  color: var(--ink);
  cursor: pointer;
  list-style: none;
}
/* Safari and Chrome each draw their own marker; both are removed. */
.faq__item > summary::-webkit-details-marker { display: none; }
.faq__item > summary::marker { content: ''; }
.faq__item > summary:hover { background: var(--surface-soft); }
.faq__item > summary:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }

/* Chevron drawn from two borders rather than an icon: it has to rotate on
   open, and a CSS box rotates without re-rendering an SVG. */
.faq__item > summary::after {
  content: '';
  flex: none;
  width: 9px;
  height: 9px;
  margin-right: 4px;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  transform: translateY(-3px) rotate(45deg);
  transition: transform 0.2s ease;
}
.faq__item[open] > summary::after { transform: translateY(2px) rotate(-135deg); }

.faq__answer { padding: 0 22px 22px; }
.faq__answer p { margin: 0; max-width: 72ch; color: var(--body); }

@media (max-width: 560px) {
  .faq__item > summary { padding: 16px; font-size: 0.95rem; }
  .faq__answer { padding: 0 16px 18px; }
}

/* ==========================================================================
   Motion

   The brief for this site is "simple yet trustworthy". Motion that draws
   attention to itself works against that, so everything here is short, small
   and in one direction: content arrives from slightly below, settles, and
   stops. Nothing bounces, nothing slides sideways, nothing loops.

   Two rules the whole file obeys:

   1. Nothing is hidden unless JavaScript is running. The reveal styles are
      scoped to `html.reveal-on`, which only script adds. If the script fails
      the page renders complete and static rather than blank.

   2. A reader who has asked for less motion gets none of it. There is a
      global reduced-motion block further up that flattens durations, and the
      reveal script checks the same query and marks everything visible
      immediately rather than leaving it to a scroll that may never happen.
   ========================================================================== */

/* ---- Scroll reveal ---- */

html.reveal-on [data-reveal] {
  opacity: 0;
  transform: translate3d(0, 18px, 0);
}
[data-reveal] {
  transition: opacity 0.55s cubic-bezier(0.22, 0.61, 0.36, 1),
              transform 0.55s cubic-bezier(0.22, 0.61, 0.36, 1);
  /* Tells the compositor what is about to change, and takes it back when the
     element has arrived, so a long page is not holding dozens of layers. */
  will-change: opacity, transform;
}
/* Has to out-specify `html.reveal-on [data-reveal]` above, which scores
   0,2,1. `[data-reveal].is-in` alone is 0,2,0 and loses, so elements got the
   class and stayed invisible - measured: 9 marked `is-in`, all 18 still at
   opacity 0. Matching the same prefix makes this 0,3,1. */
html.reveal-on [data-reveal].is-in,
[data-reveal].is-in {
  opacity: 1;
  will-change: auto;
}
/* ...but `transform: none` at 0,3,1 then beat every card hover at 0,2,0, so
   `.job:hover` and `.review:hover` grew their shadow and never lifted.
   Scoping the transform to :not(:hover) keeps the landed state authoritative
   without owning the element for the rest of its life. */
html.reveal-on [data-reveal].is-in:not(:hover),
[data-reveal].is-in:not(:hover) { transform: none; }

/* ---- Hero entrance ---- */

@keyframes hero-rise {
  from { opacity: 0; transform: translate3d(0, 22px, 0); }
  to   { opacity: 1; transform: none; }
}

.hero--entering .enter {
  opacity: 0;
  animation: hero-rise 0.7s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
}

/* The emblem behind the hero drifts in rather than appearing. It is
   decoration, so it comes last and slowest. */
@keyframes emblem-settle {
  from { opacity: 0; transform: scale(1.06); }
  to   { opacity: 1; transform: none; }
}
.hero--entering .hero__emblem {
  animation: emblem-settle 1.4s 0.2s cubic-bezier(0.22, 0.61, 0.36, 1) backwards;
}

/* ---- Buttons ---- */

/* An older, flatter pass at the button system stood here, and being LATER in
   the file it silently won. Three things shipped broken: the new hover glow
   never appeared, the rest-state inset highlight blinked off on hover because
   the later rule did not re-declare it, and `:active` was unreachable because
   it ties with `:hover` on specificity during a press. The gradient also
   stopped cross-fading - `background-color` cannot animate a
   `background-image`, which is what a linear-gradient is. The live rules are
   with the rest of the button system further up. */

/* The arrow on a service row leans toward the tap it is inviting. */
.svc__go svg { transition: transform 0.2s cubic-bezier(0.22, 0.61, 0.36, 1); }
.svc:hover .svc__go svg { transform: translateX(4px); }

/* ---- Service rows and work cards ---- */

.svc {
  transition: background-color 0.18s ease, border-color 0.18s ease,
              box-shadow 0.22s ease;
}
.svc:hover { box-shadow: 0 4px 16px rgba(26, 33, 41, 0.07); }

.job { transition: box-shadow 0.25s ease; }
.job:hover { box-shadow: 0 8px 26px rgba(26, 33, 41, 0.09); }

/* ---- Status pill ---- */

/* A slow pulse on the open/closed dot. It is the one thing on the page whose
   value changes by itself, so a little life on it is honest rather than
   decorative. */
@keyframes dot-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(21, 128, 61, 0.45); }
  70%      { box-shadow: 0 0 0 7px rgba(21, 128, 61, 0); }
}
.status-pill--open::before {
  animation: dot-pulse 2.6s ease-out infinite;
}

/* ---- Reduced motion ----
   Belt and braces. The global block above already flattens durations; this
   removes the transforms outright so nothing moves at all, and makes sure a
   revealed element is never left stuck at opacity 0. */
@media (prefers-reduced-motion: reduce) {
  html.reveal-on [data-reveal],
  [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
  }
  .hero--entering .enter,
  .hero--entering .hero__emblem {
    opacity: 1 !important;
    animation: none !important;
  }
  .btn:active { transform: none; }
  .status-pill--open::before { animation: none; }
  .svc:hover .svc__go svg { transform: none; }
}

/* ---------- Facebook recommendations ----------
   Real customer words, so they are set as quotes and given room rather than
   squeezed into cards with stars and avatars. Facebook recommendations are a
   yes or a no, not a rating, so inventing a star row would be inventing data.

   The band sits on plain canvas between the textured gallery band and the
   dark location band, which keeps the page alternating rather than running
   two textured sections together. */

.section--reviews {
  background: var(--canvas);
  position: relative;
}

/* Separated by whitespace, not by rules.
   The first version drew hairlines by showing a 1px grid gap through a tinted
   grid background. That only looks right when every row is full: a fourth
   review turns the second row into one card beside two empty cells, and an
   empty cell in that technique is a visible pale grey rectangle. Since the
   number of recommendations changes whenever someone pastes a new one in,
   the layout has to survive any count - so the separation is now space plus
   the blue rule each quote already carries, which also suits quotes better
   than boxes do. */
/* ---------- Recommendations ----------

   Pull-quotes, in the vocabulary this site already speaks: a square white card
   with a hairline, lifted off the page, and a 3px blue edge down its left
   side. That edge is not decoration invented for this band - the towing
   callout carries it, and a service row grows one on hover. Reusing it is what
   stops four testimonials looking like a widget bolted onto the page.

   TWO COLUMNS, NOT FOUR. Four was wrong twice over. It cramped the measure to
   about 40 characters so quotes broke mid-phrase, and it made the tallest card
   in a row roughly seven lines against the shortest at two - the height gap
   IS the ugliness, and halving the column count halves it. At two columns the
   measure lands near 65 characters, inside the 45-75 that reads comfortably.

   The quote sits centred in whatever height the row sets, so the slack from
   uneven lengths splits above and below instead of pooling at the bottom. */

.review-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: 22px;
}

/* Counted in JS, because CSS cannot count children. Even counts are pinned
   rather than left to auto-fit, which packs as many columns as fit and strands
   the last quote on a row of its own. Six goes three wide: at two columns six
   cards make a wall, and the extra column also shortens each card. */
.review-grid[data-count='4'],
.review-grid[data-count='6'] { grid-template-columns: 1fr; }
@media (min-width: 760px) {
  .review-grid[data-count='4'],
  .review-grid[data-count='6'] { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1100px) {
  .review-grid[data-count='6'] { grid-template-columns: repeat(3, 1fr); }
}

.review {
  margin: 0;
  display: flex;
  flex-direction: column;
  background: var(--canvas);
  border: 1px solid var(--line);
  border-left: 3px solid var(--primary);
  box-shadow: var(--shadow);
  padding: 26px 28px 22px;
  transition: box-shadow 0.3s ease, transform 0.3s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.review:hover {
  transform: translateY(-3px);
  box-shadow: 0 16px 38px -10px rgba(26, 33, 41, 0.22);
}

.review__body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 14px;
}

.review__text {
  margin: 0;
  font-size: 0.99rem;
  line-height: 1.66;
  color: var(--body);
  font-weight: 300;
}

.review__more {
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--primary);
  align-self: flex-start;
}

/* The attribution is the quiet half: the name carries the weight and the date
   sits under it as a caption, set small and tracked out so it reads as a
   timestamp rather than as a second line of the name. */
.review__by {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 20px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}
.review__name {
  font-size: 1rem;
  font-weight: 700;
  color: var(--ink);
  letter-spacing: -0.01em;
}
.review__meta {
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--faint);
}

@media (max-width: 560px) {
  .review { padding: 26px 22px 22px; }
  .review__text { font-size: 1rem; }
}

/* ---------- Footer ---------- */

.site-footer {
  background: var(--dark-deep);
  color: var(--on-dark-faint);
  padding: 72px 0 28px;
  font-size: 0.94rem;
}
/* Four explicit tracks, deliberately NOT repeat(auto-fit, ...).

   auto-fit may not be combined with a flexible track such as minmax(0, 1.5fr):
   the combination is invalid per the grid spec and browsers disagree about what
   to do with it. Chrome quietly built SIX tracks here, the last two 0px wide.
   iOS Safari built enough 0px tracks on a phone that all four columns collapsed
   to nothing, the logo was crushed to an 8px sliver and the brand paragraph
   printed straight through the Pages list.

   Explicit tracks plus two breakpoints behave identically in every browser. */
.site-footer__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) repeat(3, minmax(0, 1fr));
  gap: 44px;
  padding-bottom: 44px;
  border-bottom: 1px solid var(--line-dark-soft);
}
@media (max-width: 900px) {
  .site-footer__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 38px 28px;
  }
}
@media (max-width: 520px) {
  .site-footer__grid {
    grid-template-columns: minmax(0, 1fr);
    gap: 34px;
  }
}
.site-footer h4 {
  font-size: 0.68rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--on-dark-faint);
  margin-bottom: 20px;
  font-weight: 700;
}
.site-footer ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 13px; }
.site-footer a { color: var(--on-dark-soft); }
.site-footer a:hover { color: #fff; }
.site-footer p { color: var(--on-dark-faint); }
.site-footer .brand { color: #fff; }
.site-footer .brand__mark {
  background: radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.2) 0%, transparent 62%);
}
.site-footer .brand__sub { color: var(--on-dark-faint); }
/* <address> is the right element for the shop's own contact block, but every
   browser italicises it by default, which reads as an aside rather than the
   canonical address. */
.site-footer__bottom address {
  font-style: normal;
  margin: 0;
}

.site-footer__bottom {
  padding-top: 26px;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: space-between;
  color: var(--on-dark-faint);
  font-size: 0.85rem;
}
.footer-brand { margin-bottom: 22px; }

.contact-line { display: flex; gap: 15px; align-items: flex-start; margin-bottom: 24px; min-width: 0; }
.contact-line svg { width: 20px; height: 20px; color: var(--primary); flex: none; margin-top: 4px; }
.contact-line strong {
  display: block;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 5px;
}
.contact-line .contact-line__body {
  font-size: 1.02rem;
  color: var(--ink);
  min-width: 0;
  /* An email address is one unbreakable token; without this it sets a
     min-content width wider than a 320px phone. */
  overflow-wrap: anywhere;
}
.contact-line .contact-line__body a { color: var(--ink); }
.contact-line .contact-line__body a:hover { color: var(--primary); }

.site-footer .contact-line strong { color: var(--on-dark-faint); }
.site-footer .contact-line .contact-line__body,
.site-footer .contact-line .contact-line__body a { color: var(--on-dark-soft); }

/* ---------- Page header ---------- */

.page-head {
  background: var(--dark);
  color: var(--on-dark-soft);
  padding: clamp(48px, 6vw, 84px) 0 clamp(44px, 5vw, 72px);
  position: relative;
  overflow: hidden;
}
.page-head::before {
  content: '';
  position: absolute;
  top: -40%; right: -5%;
  width: 55%; height: 180%;
  background: radial-gradient(ellipse at center, rgba(28, 105, 212, 0.22), transparent 62%);
  pointer-events: none;
}
.page-head > .wrap { position: relative; z-index: 1; }
.page-head h1 { color: var(--on-dark); font-size: clamp(2.1rem, 4.6vw, 3.1rem); }
.page-head .lede { max-width: 62ch; color: var(--on-dark-soft); }
.page-head .eyebrow { color: #6ea8f5; }
.page-head .eyebrow::before { background: #6ea8f5; }
.page-head strong { color: var(--on-dark); }

.crumbs {
  display: flex;
  gap: 9px;
  align-items: center;
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  color: var(--on-dark-faint);
  margin-bottom: 20px;
  text-transform: uppercase;
  font-weight: 600;
}
.crumbs a { color: var(--on-dark-faint); }
.crumbs a:hover { color: #fff; }

/* ---------- Forms ---------- */

.field { margin-bottom: 22px; }
.field--row { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr)); gap: 18px; }

.field__label {
  display: block;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 9px;
}
.field__label .req { color: var(--bad); }
.field__label .opt,
.field__label .muted { font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--faint); }
.field__hint { font-size: 0.86rem; color: var(--muted); margin: 8px 0 0; }

input[type='text'],
input[type='tel'],
input[type='email'],
input[type='date'],
input[type='password'],
input[type='time'],
input[type='search'],
select,
textarea {
  width: 100%;
  background: var(--canvas);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-xs);
  color: var(--ink);
  font-family: var(--font);
  font-size: 1rem;
  font-weight: 400;
  padding: 15px 16px;
  transition: border-color 0.16s, box-shadow 0.16s;
  appearance: none;
}
textarea { min-height: 128px; resize: vertical; line-height: 1.6; }

input::placeholder, textarea::placeholder { color: var(--faint); font-weight: 300; }

input:hover, select:hover, textarea:hover { border-color: var(--muted); }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(28, 105, 212, 0.14);
}

select {
  background-color: var(--canvas);
  color: var(--ink);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b6b6b' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 17px;
  padding-right: 42px;
  cursor: pointer;
}

.field[data-invalid='true'] input,
.field[data-invalid='true'] select,
.field[data-invalid='true'] textarea {
  border-color: var(--bad);
  box-shadow: 0 0 0 3px rgba(192, 38, 27, 0.1);
}
.field__error { display: none; font-size: 0.88rem; color: var(--bad); margin: 8px 0 0; font-weight: 500; }
.field[data-invalid='true'] .field__error { display: block; }

.alert {
  border-radius: var(--r-xs);
  padding: 16px 18px;
  font-size: 0.95rem;
  margin-bottom: 22px;
  border-left: 3px solid transparent;
  display: flex;
  gap: 12px;
  align-items: flex-start;
}
.alert svg { width: 19px; height: 19px; flex: none; margin-top: 2px; }
.alert--error { background: var(--bad-tint); border-left-color: var(--bad); color: #8a1a12; }
.alert--info { background: var(--info-tint); border-left-color: var(--primary); color: var(--primary-active); }
.alert--ok { background: var(--ok-tint); border-left-color: var(--ok); color: #11642f; }
.alert--warn { background: var(--warn-tint); border-left-color: var(--warn); color: #7a4c06; }

/* ---------- Map ---------- */

.map-frame { overflow: hidden; background: var(--surface-strong); min-height: 380px; position: relative; }
.map-frame iframe { width: 100%; height: 100%; min-height: 380px; border: 0; display: block; }

/* ---------- Utilities ---------- */

.row { display: flex; flex-wrap: wrap; gap: 14px; align-items: center; }
.center { text-align: center; }
.mt-2 { margin-top: 20px; }
.mt-3 { margin-top: 30px; }
.tabular, .hours-list .time, .header-phone {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "ss01", "tnum";
}

hr.rule { border: 0; border-top: 1px solid var(--line); margin: 30px 0; }

@media (max-width: 920px) {
  .hero__grid { grid-template-columns: 1fr; }
  :root { --section: 60px; }
}

/* ---------- Thumb ergonomics ---------- */

@media (max-width: 720px), (pointer: coarse) {
  .btn { min-height: 50px; }
  .btn--sm { min-height: 44px; }
  input[type='text'], input[type='tel'], input[type='email'], input[type='date'],
  input[type='password'], input[type='time'], input[type='search'], select, textarea {
    min-height: 50px;
    font-size: 16px;
  }
  textarea { min-height: 130px; }
  .site-nav a { min-height: 50px; display: flex; align-items: center; }
  .nav-toggle { width: 46px; height: 46px; }
  .header-phone { min-height: 44px; }
  .card { padding: 30px 24px; }
  .step { padding: 30px 24px; }
}

@media (prefers-reduced-motion: reduce) {
  .btn--primary:hover, .btn--ghost:hover { transform: none !important; }
  .btn--arrow:hover::after { transform: none !important; }
  html { scroll-behavior: auto; }
  /* animation-iteration-count is the half that is usually left out, and
     without it this is not "no animation" - it is a 0.01ms animation looping
     forever, which on the booking page's loading spinner means a new random
     rotation every frame. That is a flash risk for exactly the reader who
     asked for less motion. */
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}

/* ---------- Landing page: service cards drive straight to booking ---------- */

.card__note {
  font-size: 0.86rem;
  color: var(--primary-active);
  background: var(--primary-soft);
  padding: 10px 12px;
  border-radius: var(--r-xs);
  margin-top: 16px;
}
.card__cta { margin-top: 20px; align-self: flex-start; }
.card--hover:hover .card__cta { border-color: var(--ink); background: var(--ink); color: #fff; }

.after-services {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
  align-items: center;
  margin-top: 36px;
}

/* Towing: an emergency call, visually separated from the bookable grid.
   Given more presence than a plain band: a lit corner, a ringed icon and a
   small label above the heading, so it reads as a service in its own right
   rather than a footnote under the list. */
/* The towing heading is a link now, and the callout is a dark band - the
   default link blue is unreadable on it. */
.callout h3 a { color: inherit; text-decoration: none; }
.callout h3 a:hover { text-decoration: underline; }

.callout {
  display: flex;
  flex-wrap: wrap;
  gap: 24px;
  align-items: center;
  margin-top: 44px;
  padding: 28px 32px;
  background: var(--dark);
  color: var(--on-dark-soft);
  border-left: 3px solid var(--primary);
  position: relative;
  overflow: hidden;
}
.callout::before {
  content: '';
  position: absolute;
  top: -70%; left: -4%;
  width: 46%; height: 240%;
  background: radial-gradient(ellipse at center, rgba(28, 105, 212, 0.28), transparent 62%);
  pointer-events: none;
}
.callout > * { position: relative; z-index: 1; }

.callout__icon {
  width: 56px; height: 56px;
  flex: none;
  display: grid;
  place-items: center;
  border: 1px solid rgba(110, 168, 245, 0.35);
  border-radius: 50%;
  background: rgba(28, 105, 212, 0.16);
  color: #6ea8f5;
}
.callout__icon svg { width: 26px; height: 26px; }

.callout__body { flex: 1 1 260px; min-width: 0; }
.callout__label {
  display: block;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #6ea8f5;
  margin-bottom: 7px;
}
.callout h3 { color: var(--on-dark); margin-bottom: 5px; font-size: 1.25rem; }
.callout p { color: var(--on-dark-soft); margin: 0; font-size: 0.96rem; }


@media (max-width: 720px) {
  .wrap .callout { padding: 24px 20px; gap: 16px; }
  .callout .btn { width: 100%; }
  .after-services { gap: 14px; }
  .after-services .btn { width: 100%; }
}

/* ---------- Before / after gallery ----------
   Side by side rather than a drag-to-compare slider: it is understood
   instantly, needs no JavaScript, works on a phone without fighting page
   scroll, and reads correctly in a screenshot. The whole point is that the
   two states are visible at the same time. */

.work-grid {
  display: grid;
  gap: 1px;
  background: var(--line);
  grid-template-columns: repeat(auto-fit, minmax(min(420px, 100%), 1fr));
}

.job { margin: 0; background: var(--canvas); padding: 0; }

.job__pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px;
  background: var(--line);
}

/* A real button, so a tap and the keyboard both reach it. Everything below
   is undoing the UA button styling; the layout is the same as the div was. */
.job__shot {
  position: relative;
  overflow: hidden;
  background: var(--surface-strong);
  display: block;
  width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: zoom-in;
  -webkit-tap-highlight-color: transparent;
}
.job__shot img {
  width: 100%;
  height: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  display: block;
  transition: transform 0.35s ease;
}
.job__shot:hover img,
.job__shot:focus-visible img { transform: scale(1.03); }
.job__shot:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }

/* The "this gets bigger" affordance. Hidden until pointed at on a mouse,
   always visible on touch, where there is no hover to discover it with. */
.job__zoom {
  position: absolute;
  right: 10px;
  bottom: 10px;
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border-radius: var(--r-xs);
  background: rgba(26, 33, 41, 0.78);
  color: #fff;
  opacity: 0;
  transform: translateY(5px);
  transition: opacity 0.18s ease, transform 0.18s ease;
  pointer-events: none;
}
.job__zoom svg { width: 16px; height: 16px; }
.job__shot:hover .job__zoom,
.job__shot:focus-visible .job__zoom { opacity: 1; transform: none; }
@media (hover: none), (pointer: coarse) {
  .job__zoom { opacity: 1; transform: none; }
  .job__shot:hover img { transform: none; }
}

.job__tag {
  position: absolute;
  top: 12px; left: 12px;
  font-size: 0.64rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 6px 10px;
  border-radius: var(--r-xs);
  backdrop-filter: blur(6px);
}
.job__tag--before { background: rgba(26, 33, 41, 0.82); color: #fff; }
.job__tag--after { background: var(--primary); color: #fff; }

.job__caption {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 20px 22px;
}
.job__caption strong { font-size: 1.05rem; font-weight: 700; color: var(--ink); }
.job__caption span { font-size: 0.92rem; color: var(--muted); }

@media (max-width: 560px) {
  /* Stacked, the pair stops being a comparison, so keep them side by side and
     let the images get shorter instead. */
  .job__shot img { aspect-ratio: 1 / 1; }
  .job__caption { padding: 16px 18px; }
  .job__tag { top: 8px; left: 8px; font-size: 0.6rem; padding: 5px 8px; }
}

/* ---------- Full size photo preview ----------
   The gallery thumbnails are ~280px 4:3 crops. That is fine for recognising
   the vehicle and useless for judging the panel, so pointing at one opens the
   whole photo over the page, uncropped (contain, not cover).

   pointer-events stays off while it is only being hovered: if the overlay
   could take the cursor it would immediately pull it off the thumbnail
   underneath, the thumbnail would fire mouseleave, and the thing would
   flicker. It only becomes clickable once a tap has pinned it open, which is
   the path a phone takes since there is no hover there. */

.photo-view {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  padding: 28px;
  background: rgba(10, 14, 18, 0.9);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.16s ease, visibility 0.16s ease;
  pointer-events: none;
}
.photo-view.is-open { opacity: 1; visibility: visible; }
.photo-view.is-pinned { pointer-events: auto; cursor: zoom-out; }

.photo-view img {
  max-width: min(1180px, 94vw);
  max-height: 76vh;
  width: auto;
  height: auto;
  object-fit: contain;
  display: block;
  box-shadow: 0 32px 90px rgba(0, 0, 0, 0.6);
}

.photo-view__bar {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--on-dark);
}
.photo-view__state {
  font-size: 0.64rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 6px 10px;
  border-radius: var(--r-xs);
  background: var(--primary);
  color: #fff;
}
.photo-view__state--before { background: rgba(255, 255, 255, 0.16); }
.photo-view__name { font-size: 1rem; font-weight: 600; }

.photo-view__close {
  position: absolute;
  top: 16px;
  right: 16px;
  width: 44px;
  height: 44px;
  display: none;
  place-items: center;
  border: 1px solid var(--line-dark);
  border-radius: var(--r-xs);
  background: rgba(255, 255, 255, 0.08);
  color: var(--on-dark);
  cursor: pointer;
}
.photo-view.is-pinned .photo-view__close { display: grid; }
.photo-view__close svg { width: 20px; height: 20px; }

@media (max-width: 560px) {
  /* A landscape photo on a portrait phone is limited by width, so give it
     nearly all of it. The padding is only there to stop the photo touching
     the bezel. */
  .photo-view { padding: 18px 8px; gap: 14px; }
  .photo-view img { max-height: 68vh; max-width: 100%; }
  .photo-view__name { font-size: 0.9rem; }
  .photo-view__close { top: 10px; right: 10px; }
}

/* ---------- Service rows ----------
   Replaces the card grid. Each service was costing ~300px of page for one
   sentence; a row costs ~90px and the whole row is the link, so the separate
   "book this" button goes away too. */

.svc-list {
  display: grid;
  gap: 1px;
  background: var(--line);
  grid-template-columns: repeat(auto-fit, minmax(min(380px, 100%), 1fr));
  border: 1px solid var(--line);
}

.svc {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  padding: 20px 22px;
  background: var(--canvas);
  color: var(--ink);
  text-decoration: none;
  position: relative;
  transition: background 0.16s;
}
.svc:hover { background: var(--surface-card); text-decoration: none; }
.svc::after {
  content: '';
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 0;
  background: var(--primary);
  transition: width 0.18s ease;
}
.svc:hover::after { width: 3px; }

.svc__icon {
  width: 38px; height: 38px;
  flex: none;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  color: var(--primary);
  margin-top: 2px;
}
.svc__icon svg { width: 19px; height: 19px; }

.svc__body { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1; }

.svc__name {
  font-size: 1.02rem;
  font-weight: 700;
  color: var(--ink);
  letter-spacing: -0.012em;
  line-height: 1.25;
}
.svc__blurb {
  font-size: 0.9rem;
  color: var(--muted);
  line-height: 1.45;
  /* Two lines keeps every row the same height without truncating mid-thought. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.svc__meta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--faint);
}
.svc__meta svg { width: 13px; height: 13px; }

.svc__go {
  flex: none;
  align-self: center;
  color: var(--line-strong);
  transition: color 0.16s, transform 0.16s;
}
.svc__go svg { width: 19px; height: 19px; display: block; }
.svc:hover .svc__go { color: var(--primary); transform: translateX(3px); }

@media (max-width: 720px), (pointer: coarse) {
  .svc { padding: 18px 16px; gap: 13px; min-height: 76px; }
  .svc__icon { width: 36px; height: 36px; }
  .svc__blurb { -webkit-line-clamp: 2; }
}

/* ---------- Compact variant: the landing page list ----------
   One line, one fixed height, four fixed parts. Previously the name sat in a
   stacked block and a long one ("PMS (Preventive Maintenance Service)") wrapped
   to two lines, which made its whole grid row taller than the one beside it and
   threw the icons and arrows out of alignment. Short labels plus nowrap means
   every row is identical and the columns line up. */

.svc--compact {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr) 18px;
  align-items: center;
  gap: 14px;
  padding: 0 18px;
  height: 62px;
}
.svc--compact .svc__icon {
  width: 34px; height: 34px;
  margin: 0;
  border: 0;
  background: var(--primary-soft);
  border-radius: var(--r-xs);
}
.svc--compact .svc__icon svg { width: 17px; height: 17px; }

.svc--compact .svc__name {
  font-size: 0.98rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.svc--compact .svc__go { align-self: center; }
.svc--compact .svc__go svg { width: 18px; height: 18px; }

/* ---------- Location: shop photos behind the details ----------
   The three views sit under the address and the map, washed down hard enough
   that white text holds up on top of them. The point is atmosphere, not
   detail: you get a sense of the place while still reading the address. */

.locate .section-head { max-width: 680px; margin-bottom: 36px; }

.locate__panel {
  position: relative;
  padding: clamp(40px, 5vw, 64px) 0;
  overflow: hidden;
}

.locate__bg {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  z-index: 0;
}
.locate__bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  filter: grayscale(0.12) contrast(1.04);
}
/* The wash.

   Two gradients, and both use the band's own colour, rgb(26, 33, 41) =
   var(--dark). It used to wash with rgb(18, 24, 30), a different and colder
   dark, so the photo strip never matched the solid band above and below it no
   matter how heavy the wash went.

   The vertical pass is what removes the seam: the panel fades to exactly the
   band colour at its top and bottom edges, so the section reads as one
   continuous dark block with the shop showing through the middle, rather than
   a photo rectangle dropped into it. */
.locate__bg::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    linear-gradient(
      to bottom,
      rgb(26, 33, 41) 0%,
      rgba(26, 33, 41, 0) 15%,
      rgba(26, 33, 41, 0) 85%,
      rgb(26, 33, 41) 100%
    ),
    /* Heavy where the address sits, lighter on the right so the signboard and
       the bays stay recognisable behind the map. */
    linear-gradient(
      100deg,
      rgba(26, 33, 41, 0.94) 0%,
      rgba(26, 33, 41, 0.86) 42%,
      rgba(26, 33, 41, 0.72) 100%
    );
}

.locate__grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  gap: clamp(32px, 4vw, 56px);
  align-items: center;
}

/* Contact block inverted for the dark band. */
.locate .contact-line strong { color: var(--on-dark-faint); }
.locate .contact-line .contact-line__body,
.locate .contact-line .contact-line__body a { color: var(--on-dark); }
.locate .contact-line .contact-line__body a:hover { color: #8dbcf8; }

.locate .map-frame {
  border: 1px solid var(--line-dark);
  min-height: 330px;
  box-shadow: var(--shadow-lg);
}
.locate .map-frame iframe { min-height: 330px; }

@media (max-width: 720px), (pointer: coarse) {
  /* One photo behind the block rather than three slivers: at phone width each
     of three would be ~120px wide and read as noise under the wash. */
  .locate__bg { grid-template-columns: 1fr; }
  .locate__bg img:not(:nth-child(2)) { display: none; }
  .locate__panel { padding: 36px 0; }
}

/* Tighter section head where a list follows immediately. */
.svc-list { margin-top: 0; }
.section-head:has(+ .svc-list) { margin-bottom: 30px; }

/* Slimmer towing callout: it is one line of copy and a button, so it does not
   need card-sized padding. */
.callout { padding: 20px 24px; gap: 18px; margin-top: 32px; }
.callout__icon { width: 40px; height: 40px; }
.callout__icon svg { width: 20px; height: 20px; }
.callout h3 { font-size: 1.05rem; }
.callout p { font-size: 0.92rem; }
.after-services { margin-top: 26px; }

@media (max-width: 760px) {
  /* Single column on a phone, so rows no longer need to be identical height to
     stay aligned with a neighbour. Let the name wrap rather than truncate:
     "Auto Scan & Diagnostics" clipped to "Auto Scan & Diagn..." helps nobody. */
  .svc--compact {
    grid-template-columns: 34px minmax(0, 1fr) 18px;
    height: auto;
    min-height: 64px;
    padding: 12px 16px;
    gap: 12px;
  }
  .svc--compact .svc__name {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    line-height: 1.3;
  }
}

/* ---------- Towing photographs ----------
   Single frames, not the before/after pairs the work gallery uses: a tow is
   one moment rather than a transformation. Same card language as the gallery
   so the two read as one site. */
.tow-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  gap: 18px;
}
.tow-shot {
  margin: 0;
  background: var(--canvas);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
.tow-shot img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  background: var(--surface-strong);
}
.tow-shot figcaption {
  padding: 14px 16px 16px;
  font-size: 0.92rem;
  color: var(--body);
  line-height: 1.55;
}

/* ---------- One finished car, with its owner's words ----------
   Sits under the before/after grid. The grid proves the repair happened; this
   proves the result was worth writing about, which is a different claim. The
   photograph and the quote point at the same vehicle - the only place on the
   site where they do. */
.feature {
  margin: 24px 0 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
  background: var(--canvas);
  border: 1px solid var(--line);
  border-left: 3px solid var(--primary);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.feature__shot { min-width: 0; }
.feature__shot img {
  display: block;
  width: 100%;
  height: 100%;
  min-height: 240px;
  object-fit: cover;
  background: var(--surface-strong);
}
.feature__body {
  padding: clamp(24px, 3vw, 36px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 10px;
}
.feature__label {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--primary-active);
}
.feature__body h3 { margin: 0; font-size: clamp(1.2rem, 2.4vw, 1.6rem); }
.feature__quote {
  margin: 6px 0 0;
  font-size: 1.02rem;
  line-height: 1.68;
  font-weight: 300;
  color: var(--body);
  position: relative;
}
/* The same drawn rule the recommendations use, rather than a quote glyph a
   screen reader would read aloud. */
.feature__quote::before {
  content: '';
  display: block;
  width: 30px;
  height: 3px;
  background: var(--primary);
  margin-bottom: 16px;
}
.feature__who {
  font-style: normal;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--muted);
}

/* The towing photographs are portrait - a flatbed with a car on it is a tall
   subject - so they keep that shape instead of being cropped to landscape.

   Four of them is the awkward number: auto-fit packs three columns at this
   width and leaves the fourth alone on a row, the same way it stranded the
   fourth recommendation. Pinned instead - one, two, then four across - so no
   width produces a straggler. */
.tow-shot img { aspect-ratio: 3 / 4; }

.tow-grid[data-count='4'] { grid-template-columns: 1fr; }
@media (min-width: 560px) {
  .tow-grid[data-count='4'] { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1100px) {
  .tow-grid[data-count='4'] { grid-template-columns: repeat(4, 1fr); }
}

@media (max-width: 560px) {
  .feature__shot img { min-height: 200px; }
}

/* ---------- Mechanical work ----------
   Grouped by job rather than laid out as one flat grid: four photographs of
   one Innova's intake are a single story, and mixing them with a different
   vehicle's gearbox would read as a pile of parts. Square crops, because the
   set is five portrait frames and one landscape and a row of mixed
   orientations never lines up. */
.mech-list { display: grid; gap: 26px; }

.mech {
  background: var(--canvas);
  border: 1px solid var(--line);
  border-left: 3px solid var(--primary);
  box-shadow: var(--shadow-sm);
  padding: 22px 24px 24px;
}
.mech__head { margin-bottom: 16px; }
.mech__head h3 { margin: 0 0 2px; font-size: 1.1rem; }
.mech__head p { margin: 0; color: var(--muted); font-size: 0.94rem; }

.mech__shots { display: grid; gap: 14px; grid-template-columns: repeat(2, 1fr); }
@media (min-width: 760px) {
  .mech__shots[data-count='4'] { grid-template-columns: repeat(4, 1fr); }
  .mech__shots[data-count='3'] { grid-template-columns: repeat(3, 1fr); }
  .mech__shots[data-count='2'] { grid-template-columns: repeat(2, 1fr); }
}

.mech-shot { margin: 0; }
.mech-shot img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  background: var(--surface-strong);
  border-radius: var(--r-sm);
}
.mech-shot figcaption {
  margin-top: 8px;
  font-size: 0.86rem;
  color: var(--muted);
  line-height: 1.5;
}

@media (max-width: 560px) {
  .mech { padding: 18px 16px 20px; }
  .mech__shots { gap: 10px; }
}
