/* ==================================================================
   Generated by tools/fetch-fonts.js — do not edit by hand.
   Self-hosted so the site makes no third-party requests at all.
   Open Font Licence; see fonts/LICENCE.md.
   ================================================================== */

@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/assets/fonts/geist-var-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/assets/fonts/geist-var-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Playfair Display';
  font-style: italic;
  font-weight: 400 700;
  font-display: swap;
  src: url('/assets/fonts/playfair-var-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ==================================================================
    GADCET FIX — VISUAL SYSTEM
    ------------------------------------------------------------------
    A ground-up redesign built for one register: premium modern retail
    — Apple / Nothing / Back Market / Monzo / Linear / Stripe in feel —
    without becoming a SaaS dashboard, a startup template, or losing
    the fact that this is a physical repair company with real stores
    and real technicians.

    One design system, used identically on every page: one type family
    (Geist), one colour system where blue is an accent rather than a
    ground, a formal spacing scale, four named container widths, three
    card styles, and section composition that varies by proportion and
    background temperature rather than by inventing new rules per page.

    Dark navy is spent deliberately (hero, one trust band, the
    business hero, the closing CTA, the footer) — never as the default
    ground and never alternated mechanically with white. The previous
    iteration of this file, "The Workbench", is documented for context
    in docs/DESIGN.md; this block replaces its tokens outright.
    ================================================================== */

/* ---------- 1. TOKENS --------------------------------------------- */

:root {
  /* ---- Colour ------------------------------------------------------
     Green is the shopfront, not the ground: it is the button, the
     link, the active state and a handful of deliberate dark moments.
     Everywhere else the page is white or a warm/cool off-white, so a
     background change reads as grouping, not decoration. */
  /* Two values were measured and darkened, the same way the previous
     pass darkened a brief-specified colour that reached only 4.42:1:
     #078456 (brand) clears AA on white and canvas but falls to 4.2-4.4
     on the paler sage/mint/cool grounds, and #7A847E (the muted text)
     falls to 3.5 on the same two. Both are darkened here until every
     light ground clears 4.5:1 — see the measured table below. */
  --brand:        #067A4F;  /* buttons, links, active states, CTAs     */
  --brand-dark:   #045C3D;  /* hover/pressed states, dark accents      */
  --brand-deep:   #103026;  /* the dark ground. Hero, trust, CTA, footer */
  --brand-soft:   #EAF5EF;  /* a tinted plate or band on white         */
  --brand-bright: #139C69;  /* a fill accent. Never text               */
  --brand-lift:   #3ED6A0;  /* focus rings and highlights on dark      */

  --ink:          #101713;  /* body text, and the darkest text on light */
  --text-soft:    #5F6964;  /* secondary copy                          */
  --text-mute:    #656E69;  /* meta, captions, table labels            */
  --line:         #DEE5E0;  /* hairlines and borders                   */
  --line-soft:    #EAEFEA;  /* dividers inside a card                  */

  --canvas:       #FAFBF9;  /* default light band, warm off-white      */
  --sage:         #F3F6F2;  /* a quiet, transitional ground            */
  --mint:         #EDF4F0;  /* a second, cooler transitional ground    */
  --cool:         #F4F8F5;  /* data-dense grounds (tables, filters)    */
  --surface:      #FFFFFF;  /* cards, tables, forms                    */

  --text:         var(--ink);
  --on-dark:      #FFFFFF;
  --on-dark-soft: rgba(255, 255, 255, 0.74);
  --on-dark-line: rgba(255, 255, 255, 0.16);

  --star: #F0A62B;

  /* A price reads as its own colour, not the button colour — so a
     page is never making three different claims (link, action, price)
     in one hue at one value. */
  --price:      var(--brand-dark);  /* on light grounds */
  --price-dark: #6FE9B6;            /* on brand-deep or ink */

  --ok:    #0E7C4A;
  --warn:  #8A5A00;
  --error: #A32219;

  /* ---- Radius -------------------------------------------------- */
  --r-btn:     12px;
  --r-dense:   10px;
  --r-card:    16px;  /* standard card */
  --r-media:   20px;  /* media card, device tile, booking summary */
  --r-feature: 24px;  /* feature/editorial card */
  --r-frame:   24px;  /* hero and large media frames */
  --r-pill:    999px;

  /* ---- Space — a formal scale, not an 8px grid extended ad hoc --- */
  --s1: 4px;   --s2: 8px;   --s3: 12px;  --s4: 16px;
  --s5: 20px;  --s6: 24px;  --s7: 32px;  --s8: 40px;
  --s9: 48px;  --s10: 64px; --s11: 80px; --s12: 96px; --s13: 120px;

  /* ---- Containers — four named widths, not one .container for
     everything. Standard content, wide media, a reading measure,
     and a form width all behave differently at the same viewport. */
  --container:      1240px;
  --container-wide: 1380px;
  --container-read: 720px;
  --container-form: 640px;
  --hero-maxw:      1200px;
  --maxw: var(--container); /* transitional alias while pages migrate */
  --measure: 68ch;

  --gutter: 18px;

  /* Vertical rhythm between sections. Two steps, so a page has a loud
     gap and a quiet one rather than one uniform interval. */
  --rhythm: 88px;
  --rhythm-lg: 112px;

  /* ---- Type -------------------------------------------------------- */
  --font: var(--type-family);
  --font-display: var(--font);
  --font-ui: var(--font);

  --font-accent: 'Playfair Display', Georgia, 'Times New Roman', serif;

  --fluid: calc((100vw - 23.4375rem) / 66.5625);

  --t-xl:        clamp(4rem, calc(4rem + 3 * var(--fluid)), 7rem);
  --t-hero:      clamp(2rem, calc(2rem + 2 * var(--fluid)), 4rem);
  --t-hero-home: var(--t-hero);
  --t-hero-page: clamp(2rem, calc(2rem + 1 * var(--fluid)), 3rem);
  --t-hero-lede: var(--t-lede);

  --t-statement: clamp(2rem, calc(2rem + 1 * var(--fluid)), 3rem);
  --t-title-l:   clamp(2rem, calc(2rem + 1 * var(--fluid)), 3rem);
  --t-title:     clamp(1.5rem, calc(1.5rem + 0.88 * var(--fluid)), 2.38rem);
  --t-title-s:   clamp(1.5rem, calc(1.5rem + 0.375 * var(--fluid)), 1.875rem);
  --t-card:      clamp(1.25rem, calc(1.25rem + 0.125 * var(--fluid)), 1.375rem);
  --t-lede:      clamp(1.25rem, calc(1.25rem + 0.125 * var(--fluid)), 1.375rem);
  --t-body:      1.0625rem;
  --t-small:     0.875rem;
  --t-micro:     0.8125rem;

  --lh-display: 1.2;
  --lh-title:   1.32;
  --lh-card:    1.26;
  --lh-body:    1.647;
  --lh-small:   1.64;

  --track-xl: -0.03em;
  --track-l:  -0.03em;
  --track-m:  -0.02em;
  --track-h:  -0.01em;
  --track-0:  0em;

  --w-body:    400;
  --w-heading: 500;
  --w-ui:      500;
  --w-strong:  600;

  /* ---- Depth ------------------------------------------------------- */
  --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.04), 0 2px 8px rgba(15, 23, 42, 0.04);
  --shadow-md: 0 2px 4px rgba(15, 23, 42, 0.05), 0 12px 28px rgba(15, 23, 42, 0.08);
  --shadow-lg: 0 8px 20px rgba(15, 23, 42, 0.08), 0 28px 64px rgba(15, 23, 42, 0.14);
  --lift: 0 -8px 24px rgba(10, 26, 58, 0.10);

  --header-h: 74px;
  --actionbar-h: 0px;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --dur: 200ms;
}

@media (min-width: 640px)  { :root { --gutter: 24px; } }
@media (min-width: 1024px) { :root { --gutter: 32px; --header-h: 76px; } }
@media (min-width: 1440px) { :root { --gutter: 40px; } }

/* The mobile action bar is fixed over the page on small screens, so
   the page needs to end above it rather than behind it. */
@media (max-width: 899px) {
  :root { --actionbar-h: 68px; }
}

@media (prefers-reduced-motion: reduce) {
  :root { --dur: 0ms; }
}

/* The mobile action bar is fixed over the page on small screens, so
   the page needs to end above it rather than behind it. */
@media (max-width: 899px) {
  :root { --actionbar-h: 68px; }
}

/* ---------- 2. RESET ---------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  /* Anchor targets clear the fixed header instead of hiding under it.
     This one line is most of the old sticky-header bug. */
  scroll-padding-top: calc(var(--header-h) + 24px);
  /* Every in-page jump glides instead of teleporting, so a customer
     can see that the page moved and roughly how far. The reduced
     motion query further down turns this back off for anyone who has
     asked their system for less movement. */
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--surface);
  color: var(--text);
  /* Tabular figures site-wide. Prices, phone numbers, opening hours,
     distances, review counts and dates all line up in a column
     without a single component asking for it. */
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  padding-bottom: var(--actionbar-h);
}

h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
ul, ol { margin: 0; padding: 0; }
li { list-style: none; }

img, svg, video { display: block; max-width: 100%; }
img, video { height: auto; }

button, input, select, textarea { color: inherit; }
button { background: none; border: 0; padding: 0; cursor: pointer; }

a { color: inherit; }

table { border-collapse: collapse; width: 100%; }

/* ---------- 3. FOCUS ---------------------------------------------- */

/* One ring, everywhere, visible on every ground. --brand-lift is
   5.56:1 on forest and 9.06:1 on ink, so it survives being placed on
   the darkest surfaces in the system, and it is dark enough to be
   seen against white when the second ring below it is drawn. */
:focus-visible {
  outline: 2px solid var(--brand-lift);
  outline-offset: 2px;
  border-radius: 2px;
}
:focus:not(:focus-visible) { outline: none; }

/* On white the bright green ring is only 1.9:1, so light grounds get
   a second darker ring underneath it. */
.bench :focus-visible,
.surface :focus-visible,
body > main :focus-visible {
  box-shadow: 0 0 0 4px rgba(14, 124, 74, 0.35);
}
.flood :focus-visible, .footer :focus-visible, .statband :focus-visible {
  box-shadow: none;
}

.skip-link {
  position: absolute;
  left: var(--s6);
  top: -100px;
  z-index: 200;
  background: var(--ink);
  color: var(--on-dark);
  padding: var(--s3) var(--s6);
  border-radius: var(--r-pill);
  transition: top 160ms var(--ease);
}
.skip-link:focus { top: var(--s3); }

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------- 4. TYPE ----------------------------------------------- */

/* Sentence case is the rule, so nothing here uppercases anything
   except the one eyebrow class, which is tracked out and small
   enough that it reads as a label rather than as shouting.

   The rule that governs every heading on this site: A HEADING IS
   NEVER GIVEN A MAX-WIDTH IN `ch`. The previous system set
   `.opener__text { max-width: 26ch }`, which resolves against the
   BODY font on the wrapper, not the display font on the heading —
   about 210px — and every section title on the site broke into a
   column of single words. Headings here are constrained by their
   grid column and balanced by `text-wrap: balance`, and that is the
   only width control they get. */

.display-xl, .display-l, .display-m, .heading, .title-l, .title, .title-s {
  text-wrap: balance;
  color: var(--ink);
}

/* Hero. 64-72px on a desktop, 700, and a line-height under 1 so two
   lines lock together as a block. */

/* Large section title — the opening statement of a major band. */

/* Standard section title. */

/* A sub-section, or a section title on a page whose main title is
   already doing the work. */

/* Card title. 20-24px, semibold — a card title set at 700 in a grid
   of twelve cards is twelve competing headlines. */
.heading--sm, .card-title {
  text-wrap: balance;
}

/* The one eyebrow. 12-13px, medium weight, brand green, sentence
   case — it names the section rather than repeating the heading, and
   it is never the tracked all-caps kicker that reads as a template. */
.eyebrow, .section-label {
  display: block;
  margin-bottom: var(--s3);
  color: var(--brand);
  text-transform: none;
}
.on-dark .eyebrow, .flood .eyebrow, .on-dark .section-label, .flood .section-label {
  color: var(--brand-lift);
}

/* A large introduction under a heading. 18-20px, and it is the only
   place a measure is enforced, because a 20px line running 1200px
   wide is genuinely unreadable. */
.subhead, .lede {
  color: var(--text-soft);
  max-width: 60ch;
}
.lede { max-width: var(--measure); }

.prose { max-width: var(--measure); }
.prose p { margin-bottom: var(--s6); }
.prose p:last-child { margin-bottom: 0; }
.prose h2 {
  margin: var(--s10) 0 var(--s4);
}
.prose h3 {
  margin: var(--s7) 0 var(--s3);
}
.prose h2:first-child, .prose h3:first-child { margin-top: 0; }
.prose ul, .prose ol { margin: 0 0 var(--s6); padding-left: var(--s6); }
.prose li { list-style: disc; margin-bottom: var(--s2); }
.prose ol li { list-style: decimal; }
/* Excludes .btn: a button dropped into a .prose block (the About page's
   "Book a repair" under "Know where to get help") is still a button, not
   inline prose — .prose's extra tag-selector specificity (0,1,1) was
   beating .btn's own colour (0,1,0), painting white button text as the
   brand green and making it invisible on the primary button's matching
   green background. */
.prose a:not(.btn) { color: var(--brand); text-underline-offset: 3px; }

.small {
  color: var(--text-mute);
}

/* Meta lines are laid out, not punctuated. No middle dots. */
.meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s6);
  color: var(--text-mute);
}

/* ---------- 5. LAYOUT --------------------------------------------- */

.container {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
/* A narrower rail for running prose, inside the normal container so
   the gutters still line up with everything above and below it. */
.container--narrow { max-width: 880px; }

.section { padding-block: var(--rhythm); }
.section--lg { padding-block: var(--rhythm-lg); }
.section--tight { padding-block: var(--s10); }

@media (max-width: 767px) {
  .section { padding-block: var(--s10); }
  .section--lg { padding-block: var(--s10); }
  .section--tight { padding-block: var(--s9); }
}

/* ---- Grounds ------------------------------------------------------
   Six, and a page moves between them by temperature rather than by
   flipping light to dark and back. The order that reads well is
   white → canvas → white → mint → white → forest, and no ground
   appears twice in a row. */
.surface { background: var(--surface); color: var(--text); }
.canvas, .bench { background: var(--canvas); color: var(--text); }
.cool { background: var(--cool); color: var(--text); }
.sage { background: var(--sage); color: var(--text); }
.mint { background: var(--mint); color: var(--text); }

/* The dark grounds. `.flood` is retained as the name every component
   already tests for; it is forest, and it is now rationed. */
.flood, .on-dark {
  background: var(--brand-deep);
  color: var(--on-dark);
}
.flood--ink, .on-dark--ink { background: var(--ink); }

.flood h1, .flood h2, .flood h3, .flood h4,
.on-dark h1, .on-dark h2, .on-dark h3, .on-dark h4 { color: #fff; }
.flood .subhead, .flood .small, .flood .meta, .flood .lede,
.on-dark .subhead, .on-dark .small, .on-dark .meta { color: var(--on-dark-soft); }

/* A hairline between two same-coloured bands, so they read as two
   sections rather than as one very long one. */
.section--ruled { border-top: 1px solid var(--line); }

/* ---- Splits -------------------------------------------------------
   Asymmetric by default. The variants exist so a page can change its
   proportion from section to section instead of running 7/5 eight
   times down the page. */
.split {
  display: grid;
  gap: var(--s9);
  align-items: start;
}
.split > * { min-width: 0; }
@media (min-width: 900px) {
  .split        { grid-template-columns: 7fr 5fr; gap: clamp(40px, 5vw, 80px); }
  .split--5-7   { grid-template-columns: 5fr 7fr; }
  .split--6-6   { grid-template-columns: 1fr 1fr; }
  .split--8-4   { grid-template-columns: 8fr 4fr; }
  .split--4-8   { grid-template-columns: 4fr 8fr; }
  .split--35-65 { grid-template-columns: 35fr 65fr; }
  .split--centre { align-items: center; }
}

/* ---- Section opener -----------------------------------------------
   A heading, an optional lead, and an optional action on the right.
   The heading column is wide — 62% of the container at desktop — so a
   title of four or five words sets on one or two lines. It is NEVER
   given a max-width in ch. */
.opener {
  display: grid;
  gap: var(--s6);
  margin-bottom: var(--s9);
}
.opener__text { min-width: 0; }
.opener__text > * + * { margin-top: var(--s4); }
.opener .subhead { max-width: 52ch; }
.opener__action { align-self: end; }

@media (min-width: 900px) {
  .opener {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
    gap: var(--s7) var(--s9);
  }
  .opener__text { max-width: 62ch; }
  .opener--wide .opener__text { max-width: none; }
  /* A lead that sits beside the heading rather than under it, for a
     section whose title is short and whose introduction is not. */
  .opener--beside { grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); align-items: end; }
  .opener--beside .opener__text { max-width: none; }
}

/* Centred opener. Used once per page at most. */
.opener--centre { justify-items: center; text-align: center; }
.opener--centre .opener__text { max-width: 34ch; }
.opener--centre .subhead { margin-inline: auto; }

/* ---- Grids --------------------------------------------------------
   `auto-fit` with a real minimum, so a three-up grid becomes two-up
   and then one-up on its own rather than at a breakpoint someone
   has to remember to write. */
.grid { display: grid; gap: var(--s6); }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 1000px) { .grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 599px)  { .grid--4 { grid-template-columns: 1fr; } }
.grid--gap-lg { gap: var(--s7); }

.stack { display: grid; gap: var(--s6); align-content: start; }
.stack--sm { gap: var(--s3); }
.stack--lg { gap: var(--s7); }

.row { display: flex; flex-wrap: wrap; gap: var(--s3); align-items: center; }

/* An action row under a heading or a hero. Buttons never stretch to
   the width of their container here. */
.actions { display: flex; flex-wrap: wrap; gap: var(--s3); align-items: center; }
@media (max-width: 479px) {
  .actions { display: grid; gap: var(--s2); }
  .actions > .btn { width: 100%; }
}

/* ---- Media --------------------------------------------------------
   A framed photograph. Every meaningful image on the site goes in one
   of these, so the corner radius and the hairline are consistent and
   a photograph never sits raw on a coloured ground. */
.frame {
  position: relative;
  border-radius: var(--r-frame);
  overflow: hidden;
  background: var(--cool);
}
.frame > .picture, .frame > .picture img { width: 100%; height: 100%; }
.frame img { width: 100%; height: 100%; object-fit: cover; display: block; }
.frame--3-2  { aspect-ratio: 3 / 2; }
.frame--4-3  { aspect-ratio: 4 / 3; }
.frame--16-10 { aspect-ratio: 16 / 10; }
.frame--16-9 { aspect-ratio: 16 / 9; }
.frame--1-1  { aspect-ratio: 1 / 1; }
.frame--5-6  { aspect-ratio: 5 / 6; }
.on-dark .frame, .flood .frame { border: 1px solid var(--on-dark-line); }

/* A full-bleed photographic band. One per page at most: it is the
   thing a customer remembers, and two of them is neither. */
.band {
  position: relative;
  min-height: clamp(280px, 38vw, 460px);
  display: grid;
  align-items: end;
  overflow: hidden;
  background: var(--ink);
}
.band__media { position: absolute; inset: 0; }
.band__media img { width: 100%; height: 100%; object-fit: cover; }
.band__media::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(20, 32, 25, 0.88) 0%, rgba(20, 32, 25, 0.35) 45%, rgba(20, 32, 25, 0.12) 100%);
}
.band__inner { position: relative; padding-block: var(--s10); color: #fff; }
.band__inner h2, .band__inner h3 { color: #fff; }
.band__inner p { color: rgba(255, 255, 255, 0.8); max-width: 52ch; margin-top: var(--s4); }

/* ---------- 6. BUTTONS -------------------------------------------- */

/* Three sizes — 40 / 48 / 52 — an 11px radius, and a label that
   states the outcome. Not a pill: a pill on every control is one of
   the things that made the old interface read as a template.

   `color` is set with an `!important`-free but deliberately specific
   selector. The previous system set it on `.btn` alone, and `.flood a
   { color: #fff }` — one class plus one element, so more specific —
   won on every button inside a green band. A white button with a
   white label is exactly the "blank white rectangle" that appeared in
   every CTA on the site. */
.btn,
.flood .btn, .on-dark .btn, .footer .btn {
  --btn-bg: var(--brand);
  --btn-fg: #FFFFFF;
  --btn-line: transparent;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  min-height: 48px;
  padding: 0 var(--s6);
  background: var(--btn-bg);
  color: var(--btn-fg);
  border: 1px solid var(--btn-line);
  border-radius: var(--r-btn);
  text-decoration: none;
  text-align: center;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color 140ms var(--ease), color 140ms var(--ease),
              border-color 140ms var(--ease), box-shadow 140ms var(--ease),
              transform 140ms var(--ease);
}
.btn > svg { flex: none; }
.btn:hover { background: var(--btn-bg-hover, var(--ink)); color: var(--btn-fg-hover, #fff); border-color: transparent; }
.btn:active { transform: translateY(1px); }

/* Primary — brand green, white label. */
.btn--primary { --btn-bg: var(--brand); --btn-fg: #FFFFFF; --btn-bg-hover: #066647; }

/* Secondary on a light ground — white, dark label, soft border. */
.btn--secondary {
  --btn-bg: var(--surface);
  --btn-fg: var(--ink);
  --btn-line: var(--line);
  --btn-bg-hover: var(--ink);
  --btn-fg-hover: #fff;
}

/* Quiet tertiary: no fill, no border until hover. */
.btn--ghost {
  --btn-bg: transparent;
  --btn-fg: var(--ink);
  --btn-line: transparent;
  --btn-bg-hover: var(--sage);
  --btn-fg-hover: var(--ink);
}

/* ---- On a dark ground ---------------------------------------------
   The primary inverts to white with a forest label; the secondary
   becomes a transparent button with a white border. Both selectors
   carry two classes so they beat `.flood a { color }`. */
.flood .btn--primary, .on-dark .btn--primary, .footer .btn--primary, .btn--onflood {
  --btn-bg: #FFFFFF;
  --btn-fg: var(--brand-deep);
  --btn-line: transparent;
  --btn-bg-hover: var(--brand-lift);
  --btn-fg-hover: var(--ink);
}
.flood .btn--secondary, .on-dark .btn--secondary, .footer .btn--secondary {
  --btn-bg: transparent;
  --btn-fg: #FFFFFF;
  --btn-line: rgba(255, 255, 255, 0.45);
  --btn-bg-hover: rgba(255, 255, 255, 0.12);
  --btn-fg-hover: #FFFFFF;
}
.flood .btn--secondary:hover, .on-dark .btn--secondary:hover, .footer .btn--secondary:hover {
  border-color: #fff;
}
/* The inner <span> a button label sits in inherits from the anchor,
   which is what `.flood a` was actually recolouring. Pin it. */
.btn > span { color: inherit; }

.btn--sm { min-height: 44px; padding: 0 var(--s4); }
.btn--lg { min-height: 52px; padding: 0 var(--s7); }
.btn--block { display: flex; width: 100%; }

.btn[aria-disabled='true'], .btn:disabled {
  opacity: 0.45;
  pointer-events: none;
}

/* A text action: brand colour, an underline that reveals on hover, and
   an arrow that shifts forward — the lighter-weight sibling of a
   button, used inline rather than as a boxed control. */
.tlink {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  width: fit-content;
  color: var(--brand);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  padding-bottom: 1px;
  transition: border-color 140ms var(--ease);
}
.tlink:hover { border-bottom-color: currentColor; }
.flood .tlink, .on-dark .tlink, .footer .tlink { color: var(--brand-lift); }

/* The arrow that moves forward on hover — shared by a primary button
   and a text action, never appended to a plain link. */
.btn__arrow { display: inline-flex; transition: transform var(--dur) var(--ease); }
.btn__arrow svg { display: block; }
.btn:hover .btn__arrow, .tlink:hover .btn__arrow { transform: translateX(2px); }

/* An icon-only control: one glyph, a square hit target, a
   visually-hidden label for assistive technology. */
.btn--icon {
  --btn-bg: var(--surface);
  --btn-fg: var(--ink);
  --btn-line: var(--line);
  --btn-bg-hover: var(--sage);
  --btn-fg-hover: var(--ink);
  padding: 0;
  gap: 0;
}
.btn--icon-md { width: 48px; min-height: 48px; }
.btn--icon-sm { width: 44px; min-height: 44px; }

/* The phone number as a tertiary action under the buttons. */
.phone-link {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  width: fit-content;
  text-decoration: none;
  min-height: 44px;
}
.phone-link svg { flex: none; opacity: 0.8; }
.phone-link:hover { text-decoration: underline; text-underline-offset: 4px; }

/* ---------- 7. CHIPS, BADGES, DOTS -------------------------------- */

/* A chip is `inline-flex`, which is fine until it becomes a grid or
   flex item — then it stretches to the full width of its track and
   renders as an empty bordered rectangle with a word in the corner.
   That is what the category label on every article card was, and it
   is why the page appeared to contain stray empty inputs.
   `justify-self`/`align-self: start` and `width: fit-content` pin it
   at its own size in every container it can land in. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  width: fit-content;
  max-width: 100%;
  justify-self: start;
  align-self: start;
  min-height: 28px;
  padding: var(--s1) var(--s3);
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text-soft);
  white-space: nowrap;
}
.chip--soft { background: var(--sage); border-color: transparent; color: #0A5C3F; }
.chip--turnaround {
  border-color: rgba(9, 137, 95, 0.28);
  background: rgba(9, 137, 95, 0.08);
  color: #066647;
}
/* "Ready today" is genuine urgency and gets the one high-contrast
   treatment in the set. */
.chip--today {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--brand-lift);
}
.flood .chip, .on-dark .chip {
  background: rgba(255, 255, 255, 0.1);
  border-color: var(--on-dark-line);
  color: rgba(255, 255, 255, 0.88);
}

/* A pill used as a filter or a shortcut. Interactive, so it is at
   least 36px tall and has a real hover. */
.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  width: fit-content;
  min-height: 44px;
  padding: 0 var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r-btn);
  background: var(--surface);
  color: var(--text-soft);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: border-color 140ms var(--ease), background-color 140ms var(--ease), color 140ms var(--ease);
}
.pill:hover { border-color: var(--brand); background: var(--sage); color: #066647; }
.pill[aria-pressed='true'], .pill.is-on {
  background: var(--ink);
  border-color: var(--ink);
  color: #fff;
}

.dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--text-mute);
  flex: none;
}
.dot--open { background: var(--brand); box-shadow: 0 0 0 3px rgba(9, 137, 95, 0.18); }
.on-dark .dot--open, .flood .dot--open { background: var(--brand-lift); box-shadow: 0 0 0 3px rgba(62, 214, 160, 0.22); }
.dot--closed { background: var(--text-mute); }

.stars { display: inline-flex; gap: 2px; color: var(--star); }
.stars svg { width: 16px; height: 16px; }
.stars--lg svg { width: 20px; height: 20px; }

/* ---------- 8. HEADER --------------------------------------------- */

/* ONE header, one treatment, on every page.

   It used to have two: transparent white-on-forest while a page was
   scrolled to the top of a dark hero, and solid ink-on-white
   everywhere else, switched by two body classes and a scroll
   listener. That is three states to keep in sync for a bar that is
   72px tall, and it is why the logo was white on one page and green
   on the next. Now the heroes carry the colour and the header is
   always the same white bar.

   It is sticky rather than fixed, so it occupies its own space in the
   flow and the hero begins directly under it with no offset to keep
   in step. Nothing above it is transformed -- a transformed ancestor
   would become its containing block and it would scroll away
   mid-page -- it owns its own stacking context, and
   scroll-padding-top on <html> keeps anchor targets clear of it.

   If it appears to repeat down a page, that is a full-page screenshot
   stitching a stuck element into every viewport-height tile it
   captures, not a duplicated component. tools/shoot.js asserts the
   element count in the DOM as well as taking the picture. */
.header {
  position: sticky;
  top: 0;
  z-index: 100;
  height: var(--header-h);
  display: flex;
  align-items: center;
  isolation: isolate;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  transition: box-shadow 180ms var(--ease);
}
/* app.js adds .is-solid once the page has scrolled. The only thing it
   changes now is the shadow, so the bar lifts off the content under
   it instead of changing colour. */
.header.is-solid { box-shadow: 0 1px 3px rgba(20, 32, 25, 0.05); }

.header .nav__link,
.header .header__phone { color: var(--text); }
.header .logo { color: var(--ink); }

.page { padding-top: 0; }

.header__inner {
  display: flex;
  align-items: center;
  gap: var(--s6);
  width: 100%;
}

/* ---- The lockup ---------------------------------------------------
   One size, everywhere: 28px mark, a word space, 19px wordmark, which
   measures about 130px in the header and about 120 in the footer. It
   is the same component in both, and it is never rendered as a bare
   icon or as bare text. */
.logo {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  color: var(--ink);
  text-decoration: none;
  flex: none;
  margin-right: auto;
  min-height: 44px;
}
/* The G, painted rather than pictured.
   -------------------------------------------------------------------
   The glyph is a transparent PNG used as a mask, so its colour is a
   CSS decision on every ground instead of being baked into the file
   at #007F5F -- a green that belongs to neither this palette nor the
   grounds it was being placed on. --logo-mark is the one value each
   context sets. The same pattern the service icons already use.

   The React front end at /repair/ was rendering the same file as an
   <img>: a white-on-transparent glyph on a white bar, which is to say
   nothing at all. It uses the mask now too. */
.logo__mark {
  --logo-mark: currentColor;
  width: 28px;
  height: 28px;
  flex: none;
  background-color: var(--logo-mark);
  -webkit-mask: url('/assets/brand/mark-glyph.png') center / contain no-repeat;
  mask: url('/assets/brand/mark-glyph.png') center / contain no-repeat;
}
@media (min-resolution: 1.5dppx) {
  .logo__mark {
    -webkit-mask-image: url('/assets/brand/mark-glyph@2x.png');
    mask-image: url('/assets/brand/mark-glyph@2x.png');
  }
}

.logo__text {
/* -0.03em closed the counters of the two round letters at 21px and
      pulled "Gadcet Fix" into the wordmark. A wordmark is one word set once; it
      gets the tracking that suits it, not the display scale's. */
  display: inline-flex;
  /* A real word space between the two halves of the name, not a
     decorative gap. */
  gap: 0.26em;
}
.logo__name { color: inherit; }

/* Two colours in the lockup and no more: the wordmark's, and green.
   On the footer's dark ground both halves go white so the G does not
   become a third value floating in a green band. */
.logo__sub { color: var(--brand); }
.header .logo__mark { --logo-mark: var(--brand); }
.footer .logo { color: #fff; }
.footer .logo__sub { color: var(--brand-lift); }
.footer .logo__mark { --logo-mark: #fff; }
.footer .logo__mark { width: 26px; height: 26px; }

.nav { display: none; }
@media (min-width: 1080px) {
  .nav { display: flex; flex: 1; justify-content: center; }
  .logo { margin-right: 0; }
  .nav__list { display: flex; gap: clamp(20px, 2vw, 32px); align-items: center; }
  .header__inner { justify-content: space-between; }
}
.nav__link {
  display: inline-block;
  color: var(--text);
  text-decoration: none;
  padding: var(--s3) 0;
  border-bottom: 2px solid transparent;
  white-space: nowrap;
  transition: border-color 140ms var(--ease);
}
.nav__link:hover { border-bottom-color: currentColor; }
.nav__link.is-current { border-bottom-color: var(--brand); }

.header__actions {
  display: flex;
  align-items: center;
  gap: var(--s3);
  flex: none;
}
.header__phone {
  display: none;
  color: var(--text);
  text-decoration: none;
  white-space: nowrap;
  min-height: 44px;
  align-items: center;
}
.header__phone:hover { text-decoration: underline; text-underline-offset: 4px; }
@media (min-width: 1080px) { .header__phone { display: inline-flex; } }
@media (max-width: 899px) { .header__actions .btn { display: none; } }

.header .header__actions .btn {
  --btn-bg: var(--brand);
  --btn-fg: #fff;
  --btn-line: var(--brand);
  --btn-bg-hover: #066647;
  --btn-fg-hover: #fff;
}

.menu-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px; height: 44px;
  border-radius: var(--r-dense);
  color: var(--ink);
}
@media (min-width: 1080px) { .menu-btn { display: none; } }

/* Mobile menu — a full sheet under the bar, never over it. */
.menu {
  position: fixed;
  inset: var(--header-h) 0 0 0;
  z-index: 99;
  background: var(--surface);
  border-top: 1px solid var(--line);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--s7) 0 calc(var(--actionbar-h) + var(--s10));
}
.menu[hidden] { display: none; }
.menu__list { border-top: 1px solid var(--line); margin-top: var(--s6); }
.menu__list a {
  display: flex;
  align-items: center;
  min-height: 56px;
  padding: var(--s3) 0;
  border-bottom: 1px solid var(--line);
  text-decoration: none;
}
.menu__meta { margin-top: var(--s7); display: grid; gap: var(--s3); justify-items: start; }
.menu__meta .btn { width: 100%; }

/* ---------- 9. MOBILE ACTION BAR ---------------------------------- */

/* Two actions, on every page, at the bottom of every phone screen:
   call the shop, or book. This is the one component in the system
   allowed a shadow, because it genuinely floats over the page. */
.actionbar {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 90;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 1px;
  background: var(--line);
  border-top: 1px solid var(--line);
  box-shadow: var(--lift);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
@media (min-width: 900px) { .actionbar { display: none; } }

.actionbar__btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  min-height: 60px;
  background: var(--surface);
  color: var(--ink);
  text-decoration: none;
  min-width: 0;
  white-space: nowrap;
}
@media (max-width: 420px) { }
.actionbar__btn--book { background: var(--brand); color: #fff; }
.actionbar__btn svg { flex: none; }

/* ---------- 10. HERO ---------------------------------------------- */

/* Five heroes and one box.

   The system this replaces opened every page on a 600px forest slab
   with a 72px headline on it, and put a second dark strip of five
   promises directly underneath. Two screens of chrome before a
   customer with a cracked screen saw anything they could act on.

   A hero here is a band about as tall as a paragraph and a
   photograph. It names the page, offers at most two actions and up
   to three facts, and hands the viewport to the content. The
   measured targets at 1440 are:

       .hero--home        430-500     the one hero allowed to be big
       .hero--split       280-360     the default inner page
       .hero--compact     240-300     a page that is itself a tool
       .hero--editorial   280-320     text only, no photograph
       .hero--banner      300-420     one contained image in a page

   and at 390 the whole thing, image included, stays under about 540
   on the homepage and about 420 everywhere else.

   The hero rail is deliberately narrower than the page container --
   1200 against 1360 -- so a hero reads as contained rather than as
   the widest thing on the page. See docs/DESIGN.md section 10. */

.hero {
  --hero-pad: 64px;
  --hero-min: 280px;
  --hero-media-h: 320px;
  --hero-media-col: minmax(400px, 480px);
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding-block: var(--hero-pad);
}

.hero__inner {
  display: grid;
  gap: var(--s7);
  align-content: center;
  align-items: center;
  width: min(calc(100% - var(--gutter) * 2), var(--hero-maxw));
  min-height: var(--hero-min);
  margin-inline: auto;
}
.hero__inner > * { min-width: 0; }

@media (min-width: 1000px) {
  .hero--has-media .hero__inner {
    grid-template-columns: minmax(0, 620px) var(--hero-media-col);
    justify-content: space-between;
    gap: clamp(56px, 5vw, 72px);
  }
}

/* ---- Copy ---------------------------------------------------------
   A measure, not a column: 560 for the block, 520 for the lede, so a
   hero paragraph is three lines rather than seven. */
.hero__copy { max-width: 620px; }

.hero__eyebrow {
  margin: 0 0 var(--s3);
  color: var(--brand);
}

.hero__title {
  max-width: 620px;
  margin: 0;
  color: var(--ink);
  text-wrap: balance;
}
/* The full stop after "Fixed properly". Manrope sets a square period,
   so at hero size it takes the weight of the text around it and only
   the colour changes. */
.hero__title .accent { color: var(--brand); }

.hero__lede {
  max-width: 580px;
  margin: var(--s4) 0 0;
  color: var(--text-soft);
  text-wrap: pretty;
}
.hero__lede:empty { display: none; }

/* Two actions. A third is a link inside the page, not a button here. */
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
  margin-top: 22px;
}

/* Three facts on one rule, in small type. This is what replaced the
   five-item promise strip that used to sit under every hero. */
.hero__trust {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 22px;
  margin: 20px 0 0;
  padding-top: 15px;
  border-top: 1px solid var(--line);
  list-style: none;
}
.hero__fact {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  color: var(--text-soft);
}
.hero__fact svg { flex: none; color: var(--brand); }

/* ---- Media --------------------------------------------------------
   Two kinds. A photograph is a fixed-height rounded frame, because a
   hero whose height depends on an image aspect ratio is a hero whose
   height nobody controls. A card -- a search field, the store
   locator -- keeps its own height and is capped instead. */
.hero__media { width: 100%; }

.hero--media-image .hero__media {
  height: var(--hero-media-h);
  overflow: hidden;
  border-radius: 20px;
  background: var(--cool);
}
.hero--media-image .hero__media > *,
.hero--media-image .hero__media picture,
.hero--media-image .hero__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.hero--media-card .hero__media { max-width: 440px; justify-self: end; }
.hero--media-card .hero__media > * { margin: 0; }

/* ---- Variants ----------------------------------------------------- */

/* Oversized by design: the brief's target is a hero that reads as a
   deliberate, premium-retail opening statement rather than a band
   that gets out of the way as fast as possible. --hero-pad steps
   through the formal spacing scale rather than one-off pixel values. */
.hero--home {
  --hero-pad: var(--s12); /* 96px */
  --hero-min: 440px;
  --hero-media-h: 420px;
  --hero-media-col: minmax(460px, 520px);
}
.hero--split {
  --hero-pad: var(--s10); /* 64px */
  --hero-min: 320px;
}
.hero--compact {
  --hero-pad: var(--s9); /* 48px */
  --hero-min: 220px;
  --hero-media-h: 220px;
  --hero-media-col: minmax(340px, 420px);
}
.hero--editorial {
  --hero-pad: var(--s10); /* 64px */
  --hero-min: 260px;
}
/* A text-only hero gets the wider measure, because there is no image
   holding the other half of the line. */
.hero--text-only .hero__copy,
.hero--text-only .hero__title { max-width: 680px; }
.hero--editorial .hero__copy,
.hero--editorial .hero__title { max-width: 700px; }

/* ---- Grounds ------------------------------------------------------
   A hero is not automatically forest. Nine pages, six grounds: the
   dark ones are spent on the homepage, about, business and a store
   page, and every working page opens light. */
.hero--white  { background: var(--surface); border-bottom: 1px solid var(--line); }
.hero--canvas { background: var(--canvas); }
.hero--cool   { background: var(--cool); }
.hero--sage   { background: var(--sage); }
.hero--mint   { background: var(--mint); }
.hero--forest { background: var(--brand-deep); }
.hero--ink    { background: var(--ink); }

.hero--forest,
.hero--ink { color: var(--on-dark); }
.hero--forest .hero__title,
.hero--ink .hero__title { color: #fff; }
.hero--forest .hero__title .accent,
.hero--ink .hero__title .accent { color: var(--brand-lift); }
.hero--forest .hero__eyebrow,
.hero--ink .hero__eyebrow { color: var(--brand-lift); }
.hero--forest .hero__lede,
.hero--ink .hero__lede { color: var(--on-dark-soft); }
.hero--forest .hero__trust,
.hero--ink .hero__trust { border-top-color: rgba(255, 255, 255, 0.16); }
.hero--forest .hero__fact,
.hero--ink .hero__fact { color: rgba(255, 255, 255, 0.92); }
.hero--forest .hero__fact svg,
.hero--ink .hero__fact svg { color: var(--brand-lift); }
.hero--forest .hero--media-image .hero__media,
.hero--forest.hero--media-image .hero__media,
.hero--ink.hero--media-image .hero__media { border: 1px solid rgba(255, 255, 255, 0.14); }

/* One wash, on the dark grounds only, low enough to read as light
   rather than as a gradient. Contained by overflow: hidden. */
.hero--forest::before,
.hero--ink::before {
  content: "";
  position: absolute;
  z-index: -1;
  top: -30%;
  right: -8%;
  width: 62%;
  height: 130%;
  background: radial-gradient(closest-side, rgba(19, 169, 118, 0.28), rgba(19, 169, 118, 0) 72%);
  pointer-events: none;
}

/* On a dark ground the primary action inverts to white and the
   secondary becomes an outline. Without this the primary was brand
   green on forest -- barely separated from its own background --
   while the secondary was solid white, so the less important action
   was the loudest thing in the composition. */
.hero--forest .btn--primary,
.hero--ink .btn--primary {
  --btn-bg: #FFFFFF;
  --btn-fg: var(--brand-deep);
  --btn-line: transparent;
  --btn-bg-hover: var(--brand-lift);
  --btn-fg-hover: var(--ink);
}
.hero--forest .btn--secondary,
.hero--ink .btn--secondary {
  --btn-bg: transparent;
  --btn-fg: #FFFFFF;
  --btn-line: rgba(255, 255, 255, 0.45);
  --btn-bg-hover: rgba(255, 255, 255, 0.12);
  --btn-fg-hover: #FFFFFF;
}
.hero--forest .btn--secondary:hover,
.hero--ink .btn--secondary:hover { border-color: #fff; }

/* ...but a white card sitting on a dark hero is a light ground of its
   own, so the buttons inside it stay on the light palette. */
.hero--forest .hero__card .btn--primary,
.hero--ink .hero__card .btn--primary {
  --btn-bg: var(--brand);
  --btn-fg: #FFFFFF;
  --btn-line: transparent;
  --btn-bg-hover: #066647;
  --btn-fg-hover: #FFFFFF;
}
.hero--forest .hero__card .btn--secondary,
.hero--ink .hero__card .btn--secondary {
  --btn-bg: var(--surface);
  --btn-fg: var(--ink);
  --btn-line: var(--line);
  --btn-bg-hover: var(--ink);
  --btn-fg-hover: #FFFFFF;
}

/* ---- The card that sits in a hero ---------------------------------
   The price search, the store locator, the FAQ filter. White, one
   hairline, a soft shadow and 24px of padding -- not a floating panel
   with a 40px radius and three nested rows. */
.hero__card {
  padding: 24px;
  border: 1px solid var(--line);
  border-radius: 16px;
  background: var(--surface);
  color: var(--text);
  box-shadow: 0 10px 30px rgba(20, 32, 25, 0.06);
}
.hero__card > * + * { margin-top: var(--s4); }
.hero__card-title {
  margin: 0;
}
.hero__card-hint {
  margin: 0;
  color: var(--text-mute);
}
.hero__card .actions { display: flex; flex-wrap: wrap; gap: var(--s2); }

/* ---- Image banner -------------------------------------------------
   One strong photograph inside a page, once, contained rather than
   run to the browser edge, with a short line over its lower-left
   corner. The gradient exists only to make that line legible. */
.hero--banner {
  padding-block: 0;
  overflow: visible;
  background: none;
}
.hero--banner::before { content: none; }
.hero__banner {
  position: relative;
  width: min(calc(100% - var(--gutter) * 2), 1100px);
  height: 380px;
  margin-inline: auto;
  overflow: hidden;
  border-radius: 20px;
  isolation: isolate;
}
.hero__banner-media,
.hero__banner-media picture,
.hero__banner-media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.hero__banner::after {
  content: "";
  position: absolute;
  inset: 40% 0 0;
  background: linear-gradient(rgba(9, 20, 15, 0), rgba(9, 20, 15, 0.76));
}
.hero__banner-copy {
  position: absolute;
  z-index: 1;
  inset: auto 0 0 0;
  max-width: 640px;
  padding: 32px;
  color: #fff;
}
.hero__banner-copy .hero__title { color: #fff; }
.hero__banner-copy .hero__lede { color: rgba(255, 255, 255, 0.86); }

/* ---- Tablet -------------------------------------------------------
   The split stops holding at 1000. Below it the media goes under the
   copy at full width and the hero is as tall as its parts. */
@media (max-width: 999px) {
  .hero {
    --hero-min: 0px;
    --hero-pad: var(--s9); /* 48px */
    --hero-media-h: 260px;
  }
  .hero--home { --hero-pad: var(--s10); --hero-media-h: 300px; } /* 64px */
  .hero__inner { gap: var(--s6); }
  .hero__copy,
  .hero__title,
  .hero__lede { max-width: 620px; }
  .hero--media-card .hero__media { max-width: 560px; justify-self: start; }
}

/* ---- Phone --------------------------------------------------------
   390 wide. Header, hero text, buttons, image below -- and the whole
   band under about 540 on the homepage and 420 on an inner page. The
   trust row is what goes: it is repeated further down every page. */
@media (max-width: 599px) {
  .hero {
    --hero-pad: var(--s7); /* 32px */
    --hero-media-h: 160px;
  }
  .hero--home { --hero-pad: var(--s9); --hero-media-h: 220px; } /* 48px, the brief's mobile-hero floor */
  .hero--compact,
  .hero--editorial { --hero-pad: var(--s6); } /* 24px */
  .hero__inner { gap: 14px; }
  /* The eyebrow is the first thing to go on a phone. It repeats the
     breadcrumb directly above it and costs 31px of the 420 the whole
     band is allowed. */
  .hero__eyebrow { display: none; }
  /* Three lines. A store's own description runs to six on a phone and
     pushed the hero past 430px on its own — and every page that sets
     a long lede here prints the same text again in its body, so the
     clamp costs the reader nothing. */
  .hero__lede {
    display: -webkit-box;
    overflow: hidden;
    margin-top: 12px;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
  }
  .hero__actions { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 14px; }
  /* Two actions side by side, at the touch-target floor rather than
     at the 52px desktop size. A third goes full width underneath. */
  .hero__actions .btn { width: auto; min-width: 0; padding-inline: 12px; }
  .hero__actions .btn--lg { min-height: 48px; }
  .hero__actions .btn--block { grid-column: 1 / -1; }
  .hero:not(.hero--home) .hero__trust { display: none; }
  .hero--home .hero__trust { gap: 8px 16px; margin-top: 16px; padding-top: 12px; }
  .hero--home .hero__fact svg { display: none; }
  /* A card in a hero is the page's own tool -- the price search, the
     store locator -- so the control itself is never clipped. What
     goes is everything around it: the card's own title, which only
     restates the h1 two lines above it, its hint, and the buttons
     that repeat the page's CTA further down. The field stays. */
  .hero__card { padding: 14px; }
  .hero__card > * + * { margin-top: 12px; }
  .hero__card-title,
  .hero__card-hint,
  .hero__card .actions,
  .hero__card .locator__meta .small,
  .hero__card .locator__title { display: none; }
  .hero__banner { height: 300px; }
  .hero__banner-copy { padding: 20px; }
}

/* ---------- 11. REPAIR FINDER ------------------------------------- */

/* The first thing under the hero, and the component that has to feel
   like a product rather than a list of links: one elevated surface
   carrying a search field, four large device selectors and a row of
   the faults people actually arrive with.

   It overlaps the hero by 48px on a desktop so the page opens on one
   composition rather than on two stacked bands. */
.finder-wrap {
  position: relative;
  padding-block: clamp(64px, 7vw, 88px);
  background: #F7F9F7;
}
.finder {
  position: relative;
  max-width: 1240px;
  margin-inline: auto;
  padding: 0;
  background: transparent;
}

.finder__head {
  display: grid;
  max-width: 720px;
  gap: var(--s3);
  margin-bottom: var(--s7);
}
.finder__head .eyebrow { margin: 0; }
.finder__head .subhead { max-width: 620px; }

.finder .search { margin-bottom: 28px; }
.finder .search svg { left: 20px; }
.finder .search input {
  min-height: 64px;
  padding-left: 56px;
  border-radius: 16px;
  border-color: #DDE4DF;
  box-shadow: 0 8px 24px rgba(13, 30, 22, 0.04);
}
.finder .search input:focus { outline: 0; box-shadow: 0 0 0 4px rgba(7, 132, 87, 0.12), 0 8px 24px rgba(13, 30, 22, 0.04); }

/* The four selectors. Each is a real tile with an illustration of the
   device family, a label and a line saying what it covers. */
.finder__devices {
  display: grid;
  gap: var(--s3);
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (min-width: 900px) { .finder__devices { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s4); } }

.finder__device {
  display: grid;
  grid-template-rows: 156px 1fr;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 18px;
  background: var(--surface);
  color: var(--ink);
  text-decoration: none;
  box-shadow: 0 8px 24px rgba(13, 30, 22, 0.035);
  transition: border-color 200ms var(--ease), transform 200ms var(--ease), box-shadow 200ms var(--ease);
}
.finder__device:hover {
  border-color: #CAD8D0;
  transform: translateY(-3px);
  box-shadow: 0 12px 30px rgba(13, 30, 22, 0.07);
}
.finder__device-shot {
  display: grid;
  place-items: center;
  overflow: hidden;
  padding: 8px;
  background: #FFFFFF;
  border-bottom: 1px solid var(--line-soft);
}
.finder__device-shot picture,
.finder__device-shot img { width: 100%; height: 100%; }
.finder__device-shot img { object-fit: contain; transition: transform 200ms var(--ease); }
.finder__device:hover .finder__device-shot img { transform: scale(1.025); }
.finder__device-body {
  display: flex;
  min-height: 210px;
  padding: 20px;
  flex-direction: column;
  gap: 7px;
}
.service-icon {
  display: block;
  width: 28px;
  height: 28px;
  background: currentColor;
  -webkit-mask: var(--service-icon) center / contain no-repeat;
  mask: var(--service-icon) center / contain no-repeat;
}
.finder__device-note { color: var(--text-mute); }
.finder__device-price { margin-top: auto; padding-top: var(--s3); color: var(--price); }
.finder__device-go { display: inline-flex; align-items: center; gap: 5px; color: var(--brand); }
.finder__device-go svg { transition: transform 200ms var(--ease); }
.finder__device:hover .finder__device-go svg { transform: translateX(2px); }

/* Homepage price story: real repair photography plus three live catalogue
   examples, never a miniature spreadsheet. */
.pricepromo {
  display: grid;
  overflow: hidden;
  min-height: 480px;
  border: 1px solid var(--line);
  border-radius: var(--r-frame);
  background: var(--sage);
}
.pricepromo__copy { display: grid; align-content: center; gap: var(--s6); padding: clamp(32px, 5vw, 72px); }
.pricepromo__context { color: var(--brand); }
.pricepromo__copy .subhead { max-width: 42ch; }
.pricepromo__search .search { margin: 0; max-width: 580px; }
.pricepromo__search .search input { min-height: 56px; background: var(--surface); }
.pricepromo__popular { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; color: var(--text-mute); }
.pricepromo__popular a { display: inline-flex; min-height: 44px; padding: 0 4px; align-items: center; color: var(--brand-deep); text-underline-offset: 3px; }
.pricepromo__copy > .btn { justify-self: start; }
.pricepromo__visual { position: relative; min-height: 380px; background: var(--ink); }
.pricepromo__visual > img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 45%; }
.pricepromo__rows { position: absolute; left: var(--s6); right: var(--s6); bottom: var(--s6); display: grid; overflow: hidden; border-radius: var(--r-card); background: rgba(255,255,255,.96); box-shadow: var(--shadow-md); }
.pricepromo__rows li { display: flex; min-width: 0; padding: 12px 16px; align-items: center; justify-content: space-between; gap: 16px; border-bottom: 1px solid var(--line-soft); }
.pricepromo__rows li:last-child { border-bottom: 0; }
.pricepromo__rows li > span { display: grid; min-width: 0; }
.pricepromo__rows b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pricepromo__rows small { color: var(--text-mute); }
.pricepromo__rows strong { display: grid; flex: none; color: var(--price); text-align: right; }
.home-faults > .grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (min-width: 900px) { .pricepromo { grid-template-columns: 1fr 1fr; } }
@media (max-width: 899px) {
  .pricepromo { min-height: 0; }
  .pricepromo__visual { min-height: min(470px, 100vw); }
}
@media (max-width: 479px) {
  .pricepromo__copy { padding: 28px 20px 32px; }
  .pricepromo__popular { gap: 8px; }
  .pricepromo__visual { min-height: 390px; }
  .pricepromo__rows { left: 12px; right: 12px; bottom: 12px; }
}
@media (max-width: 999px) { .home-faults > .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 419px) { .home-faults > .grid { grid-template-columns: 1fr; } }

/* The fault pills. */
.finder__faults {
  display: grid;
  gap: var(--s3);
  margin-top: 36px;
}
.finder__faults-label {
  color: var(--ink);
}
.finder__fault-pills { display: flex; flex-wrap: wrap; gap: 8px; }
.finder__fault-pills .pill { min-height: 44px; border-radius: var(--r-pill); background: #F0F3F1; }
.finder__trust {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 28px;
  margin: 32px 0 0;
  padding: 22px 0 0;
  border-top: 1px solid var(--line);
  color: var(--text-soft);
  list-style: none;
}
.finder__trust li { display: inline-flex; align-items: center; gap: 7px; }
.finder__trust svg { color: var(--brand); }
.finder__stores { margin: 14px 0 0; color: var(--text-mute); }

@media (max-width: 899px) {
  .finder__device { grid-template-rows: 145px 1fr; }
}
@media (max-width: 519px) {
  .finder-wrap { padding-block: 56px; }
  .finder .search input { min-height: 56px; padding-left: 48px; }
  .finder .search svg { left: 16px; }
  .finder__devices { gap: 10px; }
  .finder__device { grid-template-rows: 116px 1fr; border-radius: 16px; }
  .finder__device-shot { padding: 4px; }
  .finder__device-body { min-height: 190px; padding: 16px; }
  .finder__fault-pills { flex-wrap: nowrap; overflow-x: auto; margin-right: calc(var(--gutter) * -1); padding-right: var(--gutter); scrollbar-width: none; }
  .finder__fault-pills::-webkit-scrollbar { display: none; }
  .finder__trust { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
}

/* Legacy names from the previous markup, mapped onto the new
   component so a page that has not been recomposed still renders. */
.repair-start__faults { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); margin-top: var(--s6); }
.repair-start__faults > span { margin-right: var(--s2); color: var(--text-mute); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .lift:hover, .devcard:hover, .storecard:hover, .artcard:hover,
  .faultcard:hover, .finder__device:hover { transform: none; }
}

/* ---------- 12. PROMISE STRIP ------------------------------------- */

/* Five promises, directly under the hero, on ink so it reads as a
   continuation of the flood rather than as the first white section.
   Scrolls horizontally on a phone instead of wrapping to three rows. */
.promise {
  background: var(--ink);
  color: #fff;
  border-block: 1px solid rgba(255, 255, 255, 0.1);
}
.promise__list {
  display: flex;
  gap: var(--s7);
  align-items: center;
  padding-block: var(--s4);
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.promise__list::-webkit-scrollbar { display: none; }
.promise__item {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  white-space: nowrap;
  color: rgba(255, 255, 255, 0.92);
}
.promise__item svg { color: var(--brand-lift); flex: none; }
@media (min-width: 1100px) {
  .promise__list { justify-content: space-between; gap: var(--s6); }
}

/* ---------- 13. PRICES -------------------------------------------- */

/* Everyone in this category hides price behind an enquiry form, and
    Gadcet Fix publishes several hundred. That is the strongest thing the
    business has, and the previous page spent it on a spreadsheet:
   583 visually identical rows in one column, every price accessible
   and none of them findable.

   Two components live here now. `.pgroups` is the price page — the
   same records grouped by the device a customer actually owns. The
   `.ptable` below it is the small teaser table, eight rows on the
   homepage, where a table genuinely is the right shape. */

/* ---- Search field, shared -------------------------------------- */
.search {
  position: relative;
  display: block;
  width: 100%;
}
.search svg {
  position: absolute;
  left: var(--s4);
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-mute);
  pointer-events: none;
}
.search input {
  width: 100%;
  min-height: 52px;
  padding: var(--s3) var(--s4) var(--s3) 48px;
  border: 1px solid var(--line);
  border-radius: var(--r-btn);
  background: var(--surface);
  color: var(--ink);
}
.search input:focus {
  outline: 2px solid var(--brand-lift);
  outline-offset: 1px;
  border-color: var(--brand);
}
.search input::placeholder { color: var(--text-mute); }

/* ---- The grouped price list ------------------------------------ */
.pgroups { display: grid; gap: var(--s6); }

/* The toolbar sticks under the header once the list starts scrolling,
   so the filters stay reachable through several hundred devices. */
.pgroups__toolbar {
  position: sticky;
  top: var(--header-h);
  z-index: 20;
  display: grid;
  gap: var(--s4);
  padding-block: var(--s4);
  margin-bottom: var(--s2);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}
.canvas .pgroups__toolbar, .bench .pgroups__toolbar { background: var(--canvas); }
.pgroups__toolbar .search { max-width: 560px; }

.pfilters { display: grid; gap: var(--s3); }
@media (min-width: 1100px) {
  .pfilters { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s3) var(--s7); }
  .pfilter--sort { grid-column: 2; justify-self: end; }
}
.pfilter { display: flex; align-items: center; gap: var(--s3); min-width: 0; }
.pfilter__label {
  flex: none;
  width: 60px;
  text-transform: uppercase;
  color: var(--text-mute);
}
.pfilter__set {
  display: flex;
  gap: var(--s2);
  overflow-x: auto;
  scrollbar-width: none;
  padding-bottom: 2px;
  min-width: 0;
}
.pfilter__set::-webkit-scrollbar { display: none; }
/* Fade only the edge that still has pills past it — set by JS
   (app.js, initScrollFades) once on load and again on scroll, so a
   strip that already fits everything gets no mask at all. */
.pfilter__set.is-fade-start { -webkit-mask-image: linear-gradient(to right, transparent, #000 24px); mask-image: linear-gradient(to right, transparent, #000 24px); }
.pfilter__set.is-fade-end { -webkit-mask-image: linear-gradient(to left, transparent, #000 24px); mask-image: linear-gradient(to left, transparent, #000 24px); }
.pfilter__set.is-fade-start.is-fade-end {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 24px, #000 calc(100% - 24px), transparent);
  mask-image: linear-gradient(to right, transparent, #000 24px, #000 calc(100% - 24px), transparent);
}
.pfilter .pill { min-height: 36px; padding: 0 var(--s3); }

.pselect {
  min-height: 40px;
  padding: 0 var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r-btn);
  background: var(--surface);
  color: var(--ink);
}

.pgroups__count { color: var(--text-mute); }

.pgroups__list { display: grid; gap: var(--s4); }
@media (min-width: 760px)  { .pgroups__list { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* One device, its repairs and their prices. */
.pgroup {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  overflow: hidden;
}
.pgroup[hidden] { display: none; }
.pgroup__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  padding: var(--s6) var(--s6) var(--s4);
  border-bottom: 1px solid var(--line-soft);
  background: var(--sage);
}
.pgroup__brand {
  display: block;
  text-transform: uppercase;
  color: #2C6D57;
}
.pgroup__model {
  margin-top: 3px;
  color: var(--ink);
}
.pgroup__from { flex: none; color: var(--text-mute); white-space: nowrap; }
.pgroup__from strong { color: var(--price); }

.pgroup__rows { display: grid; }
.prow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3) var(--s6);
  border-bottom: 1px solid var(--line-soft);
}
.pgroup__rows > .prow:last-child { border-bottom: 0; }

/* The price. Prominent but not enormous: 19px, bold, brand green.
   The previous 32px amber figure made every row shout equally. */
.prow__price {
  color: var(--text-mute);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.prow__price strong {
  margin-left: 4px;
  color: var(--price);
}
.prow__book {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 var(--s3);
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--brand);
  text-decoration: none;
  white-space: nowrap;
  transition: background-color 140ms var(--ease), border-color 140ms var(--ease), color 140ms var(--ease);
}
.prow__book:hover { background: var(--brand); border-color: var(--brand); color: #fff; }

.pgroup__rest { border-top: 1px solid var(--line-soft); }
.pgroup__rest summary {
  list-style: none;
  cursor: pointer;
  padding: var(--s3) var(--s6);
  color: var(--brand);
  min-height: 44px;
  display: flex;
  align-items: center;
}
.pgroup__rest summary::-webkit-details-marker { display: none; }
.pgroup__rest summary:hover { background: var(--sage); }
.pgroup__rest .pgroup__rest-close { display: none; }
.pgroup__rest[open] .pgroup__rest-open { display: none; }
.pgroup__rest[open] .pgroup__rest-close { display: inline; }

.pgroups__more { display: grid; justify-items: center; gap: var(--s3); padding-top: var(--s6); }
.pgroups__more[hidden] { display: none; }
.pgroups__note { margin-top: var(--s7); }

/* ---- The teaser table ------------------------------------------ */
.prices__head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s6);
  align-items: end;
  justify-content: space-between;
  margin-bottom: var(--s7);
}
.prices__controls {
  position: sticky;
  top: var(--header-h);
  z-index: 20;
  background: var(--canvas);
  padding-block: var(--s4);
  margin-bottom: var(--s2);
  border-bottom: 1px solid var(--line);
}
.surface .prices__controls { background: var(--surface); }
.prices__controls .search { max-width: 520px; }

.filters {
  display: flex;
  gap: var(--s2);
  margin-top: var(--s3);
  overflow-x: auto;
  scrollbar-width: none;
  padding-bottom: 2px;
}
.filters::-webkit-scrollbar { display: none; }
.filter {
  min-height: 40px;
  padding: 0 var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r-btn);
  background: var(--surface);
  color: var(--text-soft);
  white-space: nowrap;
  transition: background-color 140ms var(--ease), color 140ms var(--ease), border-color 140ms var(--ease);
}
.filter:hover { border-color: var(--brand); color: var(--brand); }
.filter[aria-pressed='true'] { background: var(--ink); border-color: var(--ink); color: #fff; }

.ptable-wrap {
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  background: var(--surface);
  overflow-x: auto;
}
.ptable { min-width: 620px; }
.ptable caption { text-align: left; padding: var(--s4) var(--s6); color: var(--text-mute); }

.ptable th {
  text-align: left;
  text-transform: uppercase;
  color: var(--text-mute);
  padding: var(--s3) var(--s6);
  border-bottom: 1px solid var(--line);
  background: var(--surface);
  white-space: nowrap;
}
.ptable th button {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  min-height: 32px;
  color: inherit;
  text-transform: inherit;
}
.ptable th button:hover { color: var(--ink); }
.ptable th[aria-sort] button { color: var(--ink); }
.ptable__caret { opacity: 0; transition: opacity 120ms var(--ease); }
.ptable th[aria-sort] .ptable__caret { opacity: 1; }
.ptable th[aria-sort='descending'] .ptable__caret { transform: rotate(180deg); }

.ptable td {
  padding: var(--s4) var(--s6);
  border-bottom: 1px solid var(--line-soft);
  vertical-align: middle;
}
.ptable tbody tr:last-child td { border-bottom: 0; }
.ptable tbody tr:hover { background: var(--sage); }
.ptable__device { color: var(--text-soft); }
.ptable__action { text-align: right; white-space: nowrap; }

/* The price figure. Tabular, brand green, and sized so it reads as
   the answer without shouting over the device it belongs to. */
.price {
  color: var(--price);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  display: inline-block;
}
.price__from {
  display: block;
  color: var(--text-mute);
  margin-bottom: 2px;
}
/* On a dark ground the lifted value is both legal and correct. */
.flood .price, .on-dark .price, .flood--ink .price, .statband .price { color: var(--price-dark); }
.flood .price__from, .on-dark .price__from, .statband .price__from { color: var(--on-dark-soft); }

/* Empty state is an invitation, with a way out. */
.empty {
  padding: var(--s10) var(--s6);
  text-align: center;
  border: 1px dashed var(--line);
  border-radius: var(--r-card);
  background: var(--surface);
}
.empty[hidden] { display: none; }
.empty p { color: var(--text-soft); margin-top: var(--s2); }
.empty .heading--sm { margin-bottom: var(--s2); }

/* ---------- 14. QUOTE WIDGET -------------------------------------- */

/* Three chained selects landing on a price and a button. A bench card
   sitting inside the street register — the one place the two registers
   touch, and they touch as two distinct objects, not as a blend. */
.quote {
  background: var(--surface);
  color: var(--text);
  border-radius: var(--r-card);
  padding: var(--s7);
  border: 1px solid var(--line);
}
.quote__title { margin-bottom: var(--s6); }
.quote__fields { display: grid; gap: var(--s4); }
.quote__result {
  margin-top: var(--s6);
  padding-top: var(--s6);
  border-top: 1px solid var(--line);
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: var(--s4);
  min-height: 72px;
}
.quote__hint { color: var(--text-mute); max-width: 26ch; }
.quote .btn { margin-top: var(--s4); }

/* ---------- 15. FORM FIELDS --------------------------------------- */

.field { display: grid; gap: var(--s2); }
.field__hint { color: var(--text-mute); }

.field input,
.field select,
.field textarea {
  width: 100%;
  min-height: 52px;
  padding: var(--s3) var(--s4);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-dense);
  color: var(--text);
  transition: border-color 140ms var(--ease);
}
.field textarea { min-height: 132px; padding-top: var(--s3); resize: vertical; }

.field select {
  appearance: none;
  padding-right: var(--s10);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%234A5A52' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--s4) center;
  cursor: pointer;
}
.field select:disabled { background-color: var(--canvas); color: var(--text-mute); cursor: not-allowed; }

.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: 2px solid var(--brand-lift);
  outline-offset: 1px;
  border-color: var(--brand);
}

.field.is-invalid input,
.field.is-invalid select,
.field.is-invalid textarea { border-color: var(--error); }

/* Error text says what happened and how to fix it. It does not
   apologise, and it is never the only signal — the border moves too. */
.field__error {
  display: flex;
  align-items: start;
  gap: var(--s2);
  color: var(--error);
}
.field__error svg { flex: none; margin-top: 2px; }

.form__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s4);
  align-items: center;
  margin-top: var(--s7);
}

fieldset { border: 0; margin: 0; padding: 0; }
legend { padding: 0; }

/* Radio and checkbox cards — a real input, styled as a target. */
.optcard {
  position: relative;
  display: flex;
  gap: var(--s3);
  padding: var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r-dense);
  background: var(--surface);
  cursor: pointer;
  min-height: 60px;
  align-items: center;
  transition: border-color 140ms var(--ease), background-color 140ms var(--ease);
}
.optcard:hover { border-color: var(--ink); }
.optcard input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.optcard input:focus-visible + .optcard__body { outline: 2px solid var(--brand-lift); outline-offset: 4px; }
.optcard:has(input:checked) {
  border-color: var(--brand);
  background: rgba(14, 124, 74, 0.06);
  box-shadow: inset 0 0 0 1px var(--brand);
}
.optcard__body { flex: 1; }
.optcard__meta { color: var(--text-mute); }

/* ---------- 16. CARDS --------------------------------------------- */

/* One resting state for every card in the system: white, a 1px
   #DAE2DD border, an 18px radius and 24-28px of padding. Depth is
   restrained but it exists — a card at rest may carry the faintest
   shadow, and an interactive card lifts 2px on hover.

   The rule the old system broke: do not put a border round every
   individual fact. A card groups related content; a bordered box
   round a single line is chrome, and it is what made the pages read
   as a component library rather than as a website. */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  padding: var(--s6);
  display: flex;
  flex-direction: column;
  gap: var(--s3);
}
@media (min-width: 600px) { .card { padding: 28px; } }
.card--flat { border-radius: var(--r-dense); padding: var(--s6); box-shadow: none; }
.card--raised { box-shadow: var(--shadow-sm); }
.card--plain { border: 0; padding: 0; background: none; }

/* A phone number set larger inside a card — the contact page's
   "Calling is quickest" panel and anywhere else that wants the same
   emphasis. This used to borrow `.footer__phone`, which is right for
   the footer's dark ground: white at rest, lime on hover. On a white
   card that meant an inline `style="color:var(--ink)"` to make it
   readable at all, with the hover colour left unfixed underneath it —
   still the footer's lime, uncalibrated for a light background. */
.card__phone {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  color: var(--ink);
  text-decoration: none;
}
.card__phone:hover { color: var(--brand); }

/* Anything a customer can click. The lift is 2px and the shadow is
   the small one — the point is that the card acknowledges the cursor,
   not that it leaves the page. */
.lift {
  transition: transform 160ms var(--ease), box-shadow 160ms var(--ease), border-color 160ms var(--ease);
}
.lift:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); border-color: #C9D6CE; }

/* ---- Device category tile ------------------------------------------
   Four empty green rectangles previously. It is now a photographic
   tile: a real device or bench photograph fills the top 58%, and the
   label with the lowest published price sits beneath it. */
.devcard {
  position: relative;
  display: grid;
  grid-template-rows: auto 1fr;
  height: 100%;
  border-radius: var(--r-media);
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--line);
  text-decoration: none;
  color: var(--ink);
  transition: transform 180ms var(--ease), box-shadow 180ms var(--ease), border-color 180ms var(--ease);
}
.devcard:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); border-color: #C4D5CB; }
/* White, not the old grey, because the tiles now carry product
   cutouts that were shot on white: any other ground puts a visible
   rectangle behind each device. The band still reads as separate from
   the card because the body below it carries the hairline. */
.devcard__shot {
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  padding: 8px;
  background: #FFFFFF;
  display: grid;
  place-items: center;
}
.devcard__shot img, .devcard__shot .picture, .devcard__shot .picture img {
  width: 100%; height: 100%; object-fit: contain;
}
.devcard__shot img { transition: transform 400ms var(--ease); }
.devcard:hover .devcard__shot img { transform: scale(1.03); }
.devcard__shot img { object-position: center; }
/* The placeholder path: an SVG glyph on mint, centred, never cropped. */
.devcard__cut {
  max-width: 62%;
  max-height: 62%;
  width: auto;
  object-fit: contain;
}
.devcard__body {
  display: grid;
  align-content: start;
  gap: var(--s2);
  padding: var(--s6);
  border-top: 1px solid var(--line-soft);
}
.devcard__scope {
  display: block;
  color: var(--text-mute);
}
.devcard__price {
  display: block;
  margin-top: var(--s2);
  color: var(--price);
}
.devcard__price small { color: var(--text-mute); }
.devcard__go {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: var(--s3);
  color: var(--brand);
}
.devcard:hover .devcard__go { text-decoration: underline; text-underline-offset: 3px; }

/* ---- Fault card ----------------------------------------------------
   Plain language first, the technical name underneath. */
.faultcard {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  padding: var(--s6);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-dense);
  text-decoration: none;
  color: inherit;
  min-height: 100%;
  transition: border-color 140ms var(--ease), background-color 140ms var(--ease), transform 140ms var(--ease);
}
.faultcard:hover { border-color: var(--brand); transform: translateY(-2px); }
.faultcard__icon {
  width: 44px; height: 44px;
  display: grid;
  place-items: center;
  border-radius: var(--r-dense);
  background: var(--sage);
  color: var(--brand);
  margin-bottom: var(--s2);
}
.faultcard__plain { display: block; }
.faultcard__tech { display: block; color: var(--text-mute); }
.faultcard__tags { display: flex; flex-wrap: wrap; gap: var(--s1); margin-top: auto; padding-top: var(--s3); }
.faultcard__tag {
  padding: 2px var(--s2);
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--text-mute);
  white-space: nowrap;
}

/* ---- Store card ----------------------------------------------------
   A compact retail-location card: name and live status first, useful
   address and services next, then one primary route with two quiet
   contact actions. */
.storecard {
  display: grid;
  min-height: 100%;
  padding: 28px;
  align-content: start;
  gap: 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 20px;
  box-shadow: 0 8px 30px rgba(13, 30, 22, 0.035);
  transition: transform 200ms var(--ease), box-shadow 200ms var(--ease), border-color 200ms var(--ease);
}
.storecard:hover { transform: translateY(-2px); box-shadow: 0 14px 36px rgba(13, 30, 22, 0.07); border-color: #CAD7CF; }
.storecard--nearest { border-color: rgba(7, 124, 85, .55); background: #fff; }
.storecard__head { display: flex; min-height: 24px; align-items: center; gap: 8px; }
.storecard__index {
  margin-right: auto;
  color: #8B958F;
}
.storecard__nearest {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 8px;
  border-radius: var(--r-pill);
  color: var(--brand);
  background: var(--sage);
}
.storecard__nearest[hidden], .storecard__distance[hidden] { display: none; }
.storecard__distance { color: var(--brand); white-space: nowrap; }
.storecard__name { margin: 0; }
.storecard__name a { min-height: 0; }
.storecard__rating {
  display: flex;
  flex-wrap: wrap;
  margin: -4px 0 0;
  align-items: center;
  gap: 5px 8px;
  color: var(--text-soft);
}
.storecard__rating .stars { color: var(--star); }
.storecard__rating-count { color: var(--text-mute); }
.storecard__status {
  display: inline-flex;
  width: fit-content;
  margin: 0;
  padding: 5px 9px;
  align-items: center;
  gap: 7px;
  border-radius: var(--r-pill);
  color: #0B6946;
  background: #EAF6F0;
}
.storecard__status:has(.dot--closed) { color: #775D56; background: #F5EFED; }
.storecard__addr { display: flex; gap: 9px; color: var(--text-soft); }
.storecard__addr svg { flex: none; margin-top: 4px; color: var(--brand); }
.storecard__services { display: flex; flex-wrap: wrap; margin: 0; gap: 3px 7px; color: var(--text-mute); }
.storecard__services-label { flex-basis: 100%; color: var(--ink); }
.storecard__services > span:not(.storecard__services-label):not(:last-child)::after { margin-left: 7px; color: #B2BAB5; content: '·'; }
.storecard__actions {
  display: grid;
  gap: 12px;
  margin-top: auto;
  padding-top: 6px;
}
.storecard__cta {
  display: inline-flex;
  width: 100%;
  min-height: 46px;
  padding: 0 16px;
  align-items: center;
  justify-content: center;
  gap: 7px;
  border-radius: 12px;
  color: #fff;
  background: var(--brand);
  text-decoration: none;
  transition: background-color 180ms var(--ease);
}
.storecard__cta:hover { background: #066647; }
.storecard__cta svg { transition: transform 180ms var(--ease); }
.storecard__cta:hover svg { transform: translateX(2px); }
.storecard__links { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.storecard__links a {
  display: inline-flex;
  min-height: 44px;
  align-items: center;
  justify-content: center;
  gap: 7px;
  border: 1px solid var(--line);
  border-radius: 11px;
  color: var(--text-soft);
  text-decoration: none;
}
.storecard__links a:hover { border-color: #B9CBC1; color: var(--brand); background: var(--sage); }

/* ---- Article card --------------------------------------------------
   One image ratio for every card in a grid, so a row of three does
   not arrive with three different crops and three different heights. */
.artcard {
  display: grid;
  grid-template-rows: auto 1fr;
  text-decoration: none;
  color: inherit;
  border-radius: var(--r-card);
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--surface);
  height: 100%;
  transition: transform 160ms var(--ease), box-shadow 160ms var(--ease), border-color 160ms var(--ease);
}
.artcard:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); border-color: #C9D6CE; }
.artcard:hover .artcard__title { text-decoration: underline; text-underline-offset: 3px; }
.artcard__shot { aspect-ratio: 16 / 10; background: var(--cool); overflow: hidden; }
.artcard__shot img, .artcard__shot .picture, .artcard__shot .picture img {
  width: 100%; height: 100%; object-fit: cover;
}
.artcard__body { padding: var(--s6); display: grid; gap: var(--s3); align-content: start; }
.artcard__title {
  text-wrap: balance;
}
.artcard__dek {
  color: var(--text-soft);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.artcard .meta { margin-top: var(--s1); }

/* The feature article: one large card at the head of the advice
   grid, image beside the text rather than above it. */
.artcard--feature { grid-template-rows: none; border-radius: var(--r-frame); }
@media (min-width: 860px) {
  .artcard--feature { grid-template-columns: 7fr 5fr; align-items: stretch; }
  .artcard--feature .artcard__shot { aspect-ratio: auto; height: 100%; min-height: 360px; }
  .artcard--feature .artcard__body { padding: clamp(28px, 3.4vw, 48px); align-content: center; gap: var(--s4); }
  .artcard--feature .artcard__dek { -webkit-line-clamp: 4; }
}

/* ---- Review card ---------------------------------------------------
   Verbatim, never trimmed. The grid these sit in aligns to `start`,
   so a short review stays a short card rather than becoming a tall
   card mostly full of white space. */
.review {
  display: flex;
  flex-direction: column;
  gap: var(--s4);
  padding: 28px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
}
.review__text { color: var(--text); }
.review__foot {
  margin-top: auto;
  padding-top: var(--s4);
  border-top: 1px solid var(--line-soft);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
}
.review__who { display: block; }
.review__when { display: block; margin-top: 2px; color: var(--text-mute); }
.review__source {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--text-mute);
}
.review__source svg { color: var(--star); }

/* ---------- 17. STAT BAND ----------------------------------------- */

/* The figures were previously compressed into a thin strip, and the
   number ran straight into its own label because both were <span>
   elements with no `display: block` — `margin-top` on an inline box
   does nothing, so "18" and "Years repairing" set as one word.

   This band is 200-260px tall, five columns, the figure at 46-60px
   and the label at 15px underneath it. It is the ONE ink band on a
   page, and it earns that by being the page's proof. */
.statband { background: var(--ink); color: #fff; padding-block: clamp(56px, 6vw, 84px); }
.statband--light { background: var(--sage); color: var(--text); }
.stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s9) var(--s6);
}
@media (min-width: 700px)  { .stats { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .stats { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--s7) var(--s6); } }

.stat { display: grid; align-content: start; gap: var(--s3); }
.stat__num {
  display: block;   /* 40 → 56 */
  color: #fff;
  font-variant-numeric: tabular-nums;
}
.statband--light .stat__num { color: var(--brand-deep); }
.stat__label {
  display: block;
  color: var(--on-dark-soft);
  max-width: 22ch;
}
.statband--light .stat__label { color: var(--text-soft); }
.stat--rating .stat__num { color: var(--brand-lift); }
.statband--light .stat--rating .stat__num { color: var(--brand); }

/* ---------- 18. NUMBERED PROCESS RAIL ----------------------------- */

/* Three steps across a desktop, connected by one hairline that runs
   the width of the row and passes behind each numeral. Vertical on a
   phone, with the line running down the left instead.

   Numbers are correct here and only here: a repair genuinely happens
   in this order, so this is an ordered list, not decoration. */
.rail {
  counter-reset: rail;
  display: grid;
  gap: var(--s7);
  position: relative;
}
.rail__step { position: relative; display: grid; gap: var(--s3); align-content: start; }

/* The numeral. Large, green, and set on its own line above a rule. */
.rail__step::before {
  counter-increment: rail;
  content: '0' counter(rail);
  display: block;
  color: var(--brand);
  font-variant-numeric: tabular-nums;
}
.rail__step::after {
  content: '';
  display: block;
  height: 1px;
  background: var(--line);
  margin-top: var(--s1);
}
.rail__title { display: block; }
.rail__body { display: block; color: var(--text-soft); }

@media (min-width: 900px) {
  .rail { grid-auto-flow: column; grid-auto-columns: 1fr; gap: var(--s9); }
  /* The connecting line: it runs across the whole row at the level of
     the rules under each numeral, so the three steps read as one
     sequence rather than as three unrelated columns. */
  .rail::before {
    content: '';
    position: absolute;
    left: 0; right: 0;
    top: 44px;
    height: 1px;
    background: var(--line);
  }
  .rail__step::after { position: relative; z-index: 1; background: var(--line); }
}

.flood .rail::before, .on-dark .rail::before,
.flood .rail__step::after, .on-dark .rail__step::after { background: var(--on-dark-line); }
.flood .rail__step::before, .on-dark .rail__step::before { color: var(--brand-lift); }
.flood .rail__body, .on-dark .rail__body { color: var(--on-dark-soft); }
.mint .rail::before, .sage .rail::before,
.mint .rail__step::after, .sage .rail__step::after { background: rgba(6, 74, 53, 0.16); }

/* ---------- 19. DIAGNOSTIC SECTION -------------------------------- */

/* "We find the problem before we fix it." A large photograph of an
   open device taking 55% of the section, the argument beside it, and
   three proof points set as icon + label + one line each.

   The previous version was four paragraphs of 14px text hanging off
   CSS connector lines, and the bold lead-in ran straight into the
   body because both were <span> elements with no `display: block`. */
.annot { position: relative; }
.annot__figure {
  position: relative;
  border-radius: var(--r-frame);
  overflow: hidden;
  aspect-ratio: 4 / 3.2;
  background: var(--cool);
  box-shadow: var(--shadow-md);
}
.annot__figure img, .annot__figure .picture, .annot__figure .picture img {
  width: 100%; height: 100%; object-fit: cover;
}
@media (min-width: 900px) {
  .annot__figure { aspect-ratio: 4 / 3.6; }
}

.annot__lede { margin-bottom: var(--s7); }

/* The proof points. No border round each one — they are grouped by a
   single hairline between them, which is what stops the section
   reading as three more cards. */
.annot__list {
  display: grid;
  gap: var(--s6);
  padding-top: var(--s6);
  border-top: 1px solid var(--line);
}
.annot__item {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr);
  gap: var(--s4);
  align-items: start;
}
.annot__node {
  width: 44px; height: 44px;
  display: grid;
  place-items: center;
  border-radius: 12px;
  background: var(--mint);
  color: var(--brand-deep);
}
.annot__node svg { width: 20px; height: 20px; }
.annot__title {
  display: block;
}
.annot__body {
  display: block;
  margin-top: 4px;
  color: var(--text-soft);
}

.sage .annot__list, .mint .annot__list { border-top-color: rgba(6, 74, 53, 0.14); }
.sage .annot__node { background: #fff; }
.flood .annot__list, .on-dark .annot__list { border-top-color: var(--on-dark-line); }
.flood .annot__node, .on-dark .annot__node { background: rgba(255, 255, 255, 0.1); color: var(--brand-lift); }
.flood .annot__body, .on-dark .annot__body { color: var(--on-dark-soft); }

/* ---------- 20. ACCORDION ----------------------------------------- */

/* Full width, hairline dividers, no card chrome. Built on <details>,
   so it opens with no script and reads correctly to a screen reader.
   One open at a time is enforced by app.js, not by markup. */
.acc { border-top: 1px solid var(--line); }
.acc__item { border-bottom: 1px solid var(--line); }
.acc__item > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s6);
  min-height: 64px;
  padding: var(--s4) 0;
  cursor: pointer;
  list-style: none;
}
.acc__item > summary::-webkit-details-marker { display: none; }
.acc__item > summary:hover { color: var(--brand); }
.acc__sign {
  position: relative;
  width: 20px; height: 20px;
  flex: none;
  color: var(--brand);
}
.acc__sign::before, .acc__sign::after {
  content: '';
  position: absolute;
  left: 50%; top: 50%;
  background: currentColor;
  transform: translate(-50%, -50%);
  transition: transform 180ms var(--ease), opacity 180ms var(--ease);
}
.acc__sign::before { width: 16px; height: 2px; }
.acc__sign::after { width: 2px; height: 16px; }
.acc__item[open] .acc__sign::after { transform: translate(-50%, -50%) rotate(90deg); opacity: 0; }
.acc__body { padding: 0 0 var(--s6); max-width: var(--measure); color: var(--text-soft); }
.acc__body p + p { margin-top: var(--s4); }

/* ---------- 21. CTA BAND ------------------------------------------ */

/* Forest ground, one headline, two buttons and the phone number.
   Centre alignment exists in this system only inside this component.

   The blank white rectangle that appeared here on every page was the
   primary button: `.flood .btn--primary` set `--btn-fg` to forest,
   but `color` came from `.flood a { color: #fff }`, which is one
   class plus one element and therefore beat the plain `.btn`
   selector that read the variable. A white button with a white
   label. Section 6 now sets `color` from a selector of equal
   specificity, and pins the inner span. */
.cta {
  background: var(--brand-deep);
  color: #fff;
  text-align: center;
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.cta::before {
  content: '';
  position: absolute;
  z-index: -1;
  inset: -40% -10% auto -10%;
  height: 160%;
  background: radial-gradient(closest-side, rgba(19, 169, 118, 0.28), rgba(19, 169, 118, 0) 70%);
  pointer-events: none;
}
.cta__inner { display: grid; justify-items: center; gap: var(--s6); max-width: 46ch; margin-inline: auto; }
.cta__title { color: #fff; text-wrap: balance; }
.cta__sub { color: var(--on-dark-soft); max-width: 46ch; }
.cta__actions { display: flex; flex-wrap: wrap; gap: var(--s3); justify-content: center; }
.cta .phone-link { color: #fff; }
@media (max-width: 479px) {
  .cta__actions { display: grid; width: 100%; gap: var(--s2); }
  .cta__actions .btn { width: 100%; }
}

/* The lighter CTA, for a page that has already spent its forest. */
.cta--mint { background: var(--mint); color: var(--ink); }
.cta--mint::before { background: none; }
.cta--mint .cta__title { color: var(--ink); }
.cta--mint .cta__sub { color: var(--text-soft); }
.cta--mint .phone-link { color: var(--ink); }

/* ---------- 22. BREADCRUMBS --------------------------------------- */

.crumbs { background: var(--canvas); border-bottom: 1px solid var(--line); }
.crumbs ol { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2); padding-block: var(--s3); }
.crumbs li { display: flex; align-items: center; gap: var(--s2); color: var(--text-mute); }
.crumbs a { color: var(--text-soft); text-decoration: none; }
.crumbs a:hover { color: var(--brand); text-decoration: underline; text-underline-offset: 3px; }
.crumbs svg { color: var(--line); }
.crumbs [aria-current] { color: var(--text); }

/* ---------- 23. (was PAGE HEADER) ------------------------------- */

/* .pagehead is gone. Every page opens on one of the five hero
   variants in section 10, so the second, near-identical opening-band
   component -- with its own type scale, its own padding and its own
   three grounds -- has no call sites left. That duplication is why
   the About title was 46px and the FAQ title 34px on the same site. */

/* ---------- 24. TABS ---------------------------------------------- */

.tabs {
  display: flex;
  gap: var(--s1);
  overflow-x: auto;
  scrollbar-width: none;
  border-bottom: 1px solid var(--line);
  margin-bottom: var(--s9);
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  min-height: 56px;
  padding: 0 var(--s6);
  border-bottom: 3px solid transparent;
  color: var(--text-soft);
  text-decoration: none;
  white-space: nowrap;
  transition: color 140ms var(--ease), border-color 140ms var(--ease);
}
.tab:hover { color: var(--ink); }
.tab[aria-selected='true'], .tab.is-current {
  color: var(--ink);
  border-bottom-color: var(--brand);
}

/* ---------- 25. COMPARISON TABLE ---------------------------------- */

/* What's included against what is not. Two columns, hairlines, no
   card chrome — a bench-register component through and through. */
.compare { border: 1px solid var(--line); border-radius: var(--r-dense); overflow: hidden; background: var(--surface); }
.compare th {
  text-align: left;
  padding: var(--s4);
  background: var(--canvas);
  border-bottom: 1px solid var(--line);
}
.compare td { padding: var(--s4); border-bottom: 1px solid var(--line); vertical-align: top; }
.compare tr:last-child td { border-bottom: 0; }
.compare td:first-child { border-right: 1px solid var(--line); }
.compare__yes { color: var(--brand); }
.compare__no { color: var(--text-mute); }
.compare__item { display: flex; gap: var(--s2); align-items: start; margin-bottom: var(--s3); }
.compare__item:last-child { margin-bottom: 0; }
.compare__item svg { flex: none; margin-top: 3px; }

/* ---------- 26. TIER CARDS ---------------------------------------- */

/* Business turnaround tiers, one flagged. The flag is a real
   distinction, so it gets the ink ground rather than a coloured
   sticker on an otherwise identical card. */
.tiers { display: grid; gap: var(--s6); }
@media (min-width: 900px) { .tiers { grid-template-columns: repeat(3, 1fr); } }

.tier {
  display: flex;
  flex-direction: column;
  gap: var(--s4);
  padding: var(--s7);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
}
.tier--flagged {
  background: var(--ink);
  border-color: var(--ink);
  color: #fff;
}
.tier--flagged .tier__note, .tier--flagged .tier__list { color: var(--on-dark-soft); }
.tier--flagged .tier__list li::before { color: var(--brand-lift); }
.tier__flag {
  align-self: start;
  padding: var(--s1) var(--s3);
  border-radius: var(--r-dense);
  background: var(--brand-bright);
  color: var(--ink);
}
.tier--flagged .tier__time { color: var(--price-dark); }
.tier__note { color: var(--text-soft); }
.tier__list { display: grid; gap: var(--s2); margin-top: auto; padding-top: var(--s4); color: var(--text-soft); }
.tier__list li { display: flex; gap: var(--s2); align-items: start; }
.tier__list li::before { content: '+'; color: var(--brand); flex: none; }

/* ---------- 27. OPENING HOURS TABLE ------------------------------- */

.hours { width: 100%; }
.hours th, .hours td { padding: var(--s3) 0; border-bottom: 1px solid var(--line); text-align: left; }
.hours th { color: var(--text-soft); }
.hours td { text-align: right; font-variant-numeric: tabular-nums; }
.hours tr:last-child th, .hours tr:last-child td { border-bottom: 0; }
.hours tr.is-today th, .hours tr.is-today td { color: var(--ink); }

/* ---------- 28. FOOTER -------------------------------------------- */

/* Five columns, 88px of top padding and 64px at the bottom, and the
   store column carries town names only — the four full postal
   addresses made the footer twice as tall as it needed to be and
   buried the links that people actually use. The addresses live on
   the store pages, which is where somebody looking for one is going
   anyway. */
.footer {
  background: linear-gradient(180deg, var(--brand-deep) 0%, var(--ink) 62%);
  color: #fff;
  padding-top: clamp(64px, 7vw, 96px);
  padding-bottom: clamp(48px, 5vw, 72px);
}
.footer__grid { display: grid; gap: var(--s9) var(--s7); }
@media (min-width: 640px)  { .footer__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .footer__grid { grid-template-columns: 1.5fr 1fr 1fr 1fr 1fr; gap: var(--s7); } }

.footer__blurb {
  color: var(--on-dark-soft);
  max-width: 30ch;
  margin-top: var(--s4);
}
.footer__heading {
  color: #fff;
  margin-bottom: var(--s4);
}
.footer__links { display: grid; gap: 2px; }
.footer__links a {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  color: var(--on-dark-soft);
  text-decoration: none;
}
.footer__links a:hover { color: #fff; text-decoration: underline; text-underline-offset: 3px; }

/* Town names, linked to the store pages. No addresses here. */
.footer__stores { display: grid; gap: 2px; }
.footer__store a {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  color: var(--on-dark-soft);
  text-decoration: none;
}
.footer__store a:hover { color: #fff; text-decoration: underline; text-underline-offset: 3px; }

.footer__phone {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  margin-top: var(--s6);
  color: #fff;
  text-decoration: none;
}
.footer__phone:hover { color: var(--brand-lift); }

/* gadcetfix.co.uk is a distinct outbound block, not a link in a list: it
    goes to the sister business and should not read as navigation.
    A slim strip, and the two lines are blocks so they do not run
    together the way they did before. */
.footer__outbound {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s6);
  margin-top: var(--s9);
  padding: var(--s6);
  border: 1px solid var(--on-dark-line);
  border-radius: var(--r-card);
  background: rgba(255, 255, 255, 0.05);
  text-decoration: none;
  color: #fff;
  transition: background-color 140ms var(--ease), border-color 140ms var(--ease);
}
.footer__outbound:hover { background: rgba(255, 255, 255, 0.1); border-color: rgba(255, 255, 255, 0.28); }
.footer__outbound > svg { flex: none; color: var(--brand-lift); }
.footer__outbound-title {
  display: block;
}
.footer__outbound-sub {
  display: block;
  margin-top: 3px;
  color: var(--on-dark-soft);
}

.footer__bar {
  margin-top: var(--s9);
  padding-top: var(--s6);
  border-top: 1px solid var(--on-dark-line);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3) var(--s7);
  justify-content: space-between;
  align-items: center;
  color: var(--on-dark-soft);
}
.footer__bar a { color: var(--on-dark-soft); text-decoration: none; }
.footer__bar a:hover { color: #fff; text-decoration: underline; text-underline-offset: 3px; }

/* ---------- 29. CONSENT BANNER ------------------------------------ */

.consent {
  position: fixed;
  inset: auto 0 var(--actionbar-h) 0;
  z-index: 95;
  background: var(--ink);
  color: #fff;
  border-top: 1px solid rgba(255, 255, 255, 0.14);
}
.consent[hidden] { display: none; }
.consent__inner {
  max-width: var(--maxw);
  margin-inline: auto;
  padding: var(--s6) var(--gutter);
  display: grid;
  gap: var(--s4);
}
@media (min-width: 900px) {
  .consent__inner { grid-template-columns: 1fr auto; align-items: center; }
}
.consent__copy h2 { margin: 0 0 var(--s2); color: #fff; font-size: 1.125rem; line-height: 1.35; }
.consent__text { color: var(--on-dark-soft); max-width: 72ch; }
.consent__actions { display: flex; flex-wrap: wrap; gap: var(--s3); align-items: center; }
.consent__actions .btn--secondary {
  --btn-fg: #fff;
  --btn-line: rgba(255,255,255,.5);
  border-color: rgba(255,255,255,.55);
  background: transparent;
  color: #fff;
}
.consent__link { color: var(--brand-lift); }
.footer__legal button {
  padding: 0;
  border: 0;
  background: none;
  color: var(--on-dark-soft);
  font: inherit;
  cursor: pointer;
}
.footer__legal button:hover { color: #fff; text-decoration: underline; text-underline-offset: 3px; }
.footer__legal button:focus-visible { outline: 2px solid var(--brand-lift); outline-offset: 4px; }

/* ---------- 30. NOTES AND UTILITIES ------------------------------- */

.note {
  display: flex;
  gap: var(--s3);
  padding: var(--s6);
  border: 1px solid var(--line);
  border-left: 3px solid var(--brand);
  border-radius: var(--r-dense);
  background: var(--surface);
  color: var(--text-soft);
}
.note svg { flex: none; color: var(--brand); margin-top: 2px; }
.note--warn { border-left-color: var(--warn); }
.note--warn svg { color: var(--warn); }

.divider { height: 1px; background: var(--line); border: 0; margin-block: var(--s9); }

.figure { border-radius: var(--r-card); overflow: hidden; background: var(--canvas); }
.figure img { width: 100%; height: auto; }
.figure figcaption { padding: var(--s3) 0 0; color: var(--text-mute); }

/* A full-bleed photograph used as a section break. No tint, no scrim. */
.photoband { position: relative; overflow: hidden; background: var(--ink); }
.photoband img { width: 100%; height: 100%; object-fit: cover; aspect-ratio: 12 / 5; }
@media (max-width: 767px) { .photoband img { aspect-ratio: 4 / 3; } }

.js-only { display: none; }
.js .js-only { display: revert; }
.js .no-js-only { display: none; }

@media print {
  .header, .actionbar, .consent, .cta, .menu { display: none !important; }
  body { padding-bottom: 0; }
}

/* ---------- 31. BREADCRUMBS ABOVE THE HERO ---------------------- */

/* The crumb bar is emitted above <main>, directly under a header
   that now takes its own space in the flow, so it needs no header
   offset. It does need to take the colour of the hero underneath it,
   or it puts a stripe of the wrong ground between the two. */
.crumbs { padding-block: 2px; }
body[data-hero='forest'] .crumbs,
body[data-hero='ink'] .crumbs {
  background: var(--brand-deep);
  border-bottom: 0;
}
body[data-hero='ink'] .crumbs { background: var(--ink); }
body[data-hero='forest'] .crumbs li,
body[data-hero='forest'] .crumbs a,
body[data-hero='ink'] .crumbs li,
body[data-hero='ink'] .crumbs a { color: var(--on-dark-soft); }
body[data-hero='forest'] .crumbs a:hover,
body[data-hero='ink'] .crumbs a:hover { color: #fff; }
body[data-hero='forest'] .crumbs svg,
body[data-hero='ink'] .crumbs svg { color: rgba(255, 255, 255, 0.35); }
body[data-hero='forest'] .crumbs [aria-current],
body[data-hero='ink'] .crumbs [aria-current] { color: #fff; }

body[data-hero='sage'] .crumbs   { background: var(--sage); border-bottom: 0; }
body[data-hero='canvas'] .crumbs { background: var(--canvas); border-bottom: 0; }
body[data-hero='cool'] .crumbs   { background: var(--cool); border-bottom: 0; }
body[data-hero='mint'] .crumbs   { background: var(--mint); border-bottom: 0; }
body[data-hero='white'] .crumbs  { background: var(--surface); border-bottom: 0; }

/* ---------- 32. REVIEWS, RATING AND LOCATOR ----------------------- */

/* The rating block is the claim, the review cards are the evidence,
   and they sit on one horizontal line rather than stacked. */
.reviews__head { display: grid; gap: var(--s6); align-content: start; }
.rating { display: grid; gap: var(--s2); justify-items: start; }
.rating__score {
  color: var(--brand);
  font-variant-numeric: tabular-nums;
}
.rating__source { color: var(--text-mute); }

/* Cards align to the top of the row, so a one-line review stays a
   short card instead of becoming a tall box full of white space. */
.reviews__list {
  display: grid;
  gap: var(--s4);
  align-items: start;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
@media (max-width: 899px) {
  .reviews__list {
    grid-auto-flow: column;
    grid-auto-columns: minmax(280px, 84%);
    grid-template-columns: none;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    padding-bottom: var(--s2);
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
  }
  .reviews__list::-webkit-scrollbar { display: none; }
  .reviews__list > li { scroll-snap-align: start; }
}

/* ---- Store locator -------------------------------------------------
   A real search module, not a form dropped on a dark band. */
.locator { display: grid; gap: 14px; }
.locator__title { margin: 0; }
.locator__form { display: grid; gap: var(--s3); }
@media (min-width: 520px) {
  .locator__form { grid-template-columns: minmax(0, 1fr) auto; }
}
.locator__field {
  display: flex;
  min-width: 0;
  min-height: 54px;
  padding: 0 16px;
  align-items: center;
  gap: 10px;
  border: 1px solid #DDE4DF;
  border-radius: 14px;
  background: #fff;
  transition: border-color 180ms var(--ease), box-shadow 180ms var(--ease);
}
.locator__field:focus-within { border-color: var(--brand); box-shadow: 0 0 0 4px rgba(7, 124, 85, .11); }
.locator__field svg { flex: none; color: var(--text-mute); }
.locator__field:focus-within svg { color: var(--brand); }
.locator__field input { width: 100%; min-width: 0; height: 100%; border: 0; outline: 0; color: var(--ink); background: transparent; }
.locator__field input::placeholder { color: #818B85; }
.locator__submit {
  display: inline-flex;
  min-height: 54px;
  padding: 0 18px;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: 0;
  border-radius: 13px;
  color: #fff;
  background: var(--brand);
  cursor: pointer;
  transition: background-color 180ms var(--ease);
}
.locator__submit:hover { background: #066647; }
.locator__submit svg { transition: transform 180ms var(--ease); }
.locator__submit:hover svg { transform: translateX(2px); }
.locator__meta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2) var(--s4); }
.locator__geo { display: inline-flex; min-height: 44px; padding: 0; align-items: center; gap: 7px; border: 0; color: var(--brand); background: transparent; cursor: pointer; }
.locator__geo:hover { text-decoration: underline; text-underline-offset: 3px; }
.locator__geo:disabled { opacity: .65; cursor: wait; }
.locator__geo.is-loading .locator__geo-icon { animation: locator-spin .8s linear infinite; }
.locator__privacy { margin: -8px 0 0; color: var(--text-mute); }
.locator__status { min-height: 1.4em; margin: 0; color: var(--text-soft); }
@keyframes locator-spin { to { transform: rotate(360deg); } }
.locator--dark .locator__title, .flood .locator__title, .on-dark .locator__title { color: #fff; }
.flood .locator__meta .small, .on-dark .locator__meta .small { color: var(--on-dark-soft); }

/* The store section on the homepage keeps the locator inside a card
   so it reads as a control rather than as loose form fields. */
.split .locator {
  padding: var(--s6);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
}
.surface .split .locator { background: var(--canvas); }

/* ---------- 33. PICTURE PLACEHOLDERS ------------------------------ */

/* Every <img> carries a tiny blurred copy of itself as a background
   so the box is never an empty hole while the real file arrives. It
   was set without `background-size`, so the 16px placeholder TILED —
   which is why the advice cards rendered as a repeating mosaic in
   any screenshot taken before the images had loaded. */
.picture { display: block; }
.picture img {
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

/* ---------- 34. FEATURE AND BAND UTILITIES ------------------------ */

.artcard__go {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: var(--s2);
  color: var(--brand);
}

/* A photo/text split where the photograph runs to the edge of the
   container and the text sits in a panel beside it. */
.showcase { display: grid; gap: var(--s7); align-items: center; }
@media (min-width: 900px) {
  .showcase { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(40px, 5vw, 72px); }
  .showcase--flip > .showcase__media { order: 2; }
}
.showcase__media { border-radius: var(--r-frame); overflow: hidden; box-shadow: var(--shadow-md); }
.showcase__media img, .showcase__media .picture, .showcase__media .picture img { width: 100%; display: block; }
.showcase__text { display: grid; gap: var(--s4); align-content: start; }


/* ---------- 35. CORRECTIONS FROM THE SCREENSHOT REVIEW ------------ */

/* Each of these came out of reading the rendered pages rather than
   the markup, which is the only way several of them were ever going
   to be found. */

/* 1. The "beside" opener lost its action column.
      `.opener` is `1fr auto` at desktop; `--beside` redefined the
      template to two content columns, which pushed the button onto a
      second row in the middle of the section. It needs three. */
@media (min-width: 900px) {
  .opener--beside {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.75fr) auto;
    align-items: end;
  }
  .opener--beside .opener__text { max-width: none; }
  .opener--beside .opener__lede { align-self: end; }
}

/* A lead that sits in the second column of a "beside" opener. */
.opener__lede { color: var(--text-soft); max-width: 44ch; }
.flood .opener__lede, .on-dark .opener__lede { color: var(--on-dark-soft); }

/* 2. The process rail's connecting line never rendered: a `::before`
      on a grid container becomes a grid item, so it was being laid
      out as a fourth column rather than drawn across the row. The
      line is drawn by each step instead, extended by the gap. */
.rail::before { content: none; }
@media (min-width: 900px) {
  .rail__step:not(:last-child)::after { width: calc(100% + var(--s9)); }
}

/* 3. Statistics wrapped "12 months" onto two lines, which threw the
      five labels onto five different baselines. Subgrid puts every
      figure on one row and every label on the next. */
.stat__num { white-space: nowrap; }
@supports (grid-template-rows: subgrid) {
  @media (min-width: 700px) {
    .stats { grid-template-rows: auto auto; }
    .stat { grid-row: span 2; grid-template-rows: subgrid; }
  }
}

/* 4. Reviews: three 300px cards do not fit in a 65% column, so the
      third wrapped and left a hole. The rating and the cards share
      one grid across the full container instead. */
.reviews { display: grid; gap: var(--s7); }
@media (min-width: 1100px) {
  .reviews {
    grid-template-columns: minmax(240px, 300px) repeat(3, minmax(0, 1fr));
    gap: var(--s7) var(--s4);
    align-items: start;
  }
  .reviews__list { display: contents; }
  .reviews__list > li { min-width: 0; }
}

/* 5. The locator sits in a 400px column on the homepage, where a
      side-by-side field and button squeezed the field to nothing.
      It stacks by default and only splits where there is room. */
.locator__form { grid-template-columns: 1fr; }
/* Inside a hero card the postcode field and its button sit on one row
   from 380px up. Stacked, they cost 56px of a band that is only
   allowed 400 on a phone, and the pair fits comfortably. */
@media (min-width: 380px) {
  .hero__card .locator__form,
  .hero__media .locator__form { grid-template-columns: minmax(0, 1fr) auto; }
}

/* 6. The price filters were laid out two-up; section 41 replaces
      that with one full-width row per filter group. */

/* 7. Device tiles ended at different heights because their summary
      lines are different lengths. The link pins to the bottom. */
.devcard__body { align-content: space-between; }
.devcard__go { margin-top: auto; padding-top: var(--s3); }

/* 8. An article with no summary rendered an empty element that still
      took a grid gap, so a row of cards had ragged internal spacing. */
.artcard__dek:empty { display: none; }
.hero__lede:empty, .subhead:empty { display: none; }


/* ---------- 36. SERVICE COMPARISON, TIMELINE ---------------------- */

/* The turnaround options, as a comparison rather than as three
   pricing cards.
   -------------------------------------------------------------------
   Three cards with a highlighted middle one is the SaaS pricing table,
   and it is the wrong shape here: the differences between these
   options are four specific attributes, and a customer wants to read
   ACROSS them. A table does that; three parallel bullet lists do not.
   It is a real <table> because it is genuinely tabular. */
.svc-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--r-card); background: var(--surface); }
.svc { min-width: 720px; }
.svc th, .svc td {
  padding: var(--s4) var(--s6);
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--line-soft);
}
.svc tbody tr:last-child th, .svc tbody tr:last-child td { border-bottom: 0; }
.svc thead th { border-bottom: 1px solid var(--line); padding-block: var(--s6); }
.svc tbody th {
  width: 150px;
  text-transform: uppercase;
  color: var(--text-mute);
  white-space: nowrap;
}
.svc td { color: var(--text-soft); }
.svc__name { display: block; color: var(--ink); }
.svc__flag {
  display: inline-block;
  margin-top: 6px;
  padding: 3px 9px;
  border-radius: 6px;
  background: var(--mint);
  color: #066647;
}
.svc__time { display: block; color: var(--price); }
.svc__list { display: grid; gap: var(--s2); }
.svc__list li { display: grid; grid-template-columns: 18px 1fr; gap: var(--s2); align-items: start; }
.svc__list svg { color: var(--brand); margin-top: 3px; }
/* The chosen column is tinted, not boxed and lifted. */
.svc__col--flagged { background: var(--sage); }

/* ---- Timeline ------------------------------------------------------
   The company history. It was previously borrowing the numbered
   process rail, which counts 01/02/03 — so a timeline of years was
   rendered with the counter suppressed and the year jammed into the
   heading. It gets its own component. */
.timeline { display: grid; gap: var(--s7); }
@media (min-width: 900px) {
  .timeline { grid-auto-flow: column; grid-auto-columns: 1fr; gap: var(--s9); }
}
.timeline__step { display: grid; gap: var(--s3); align-content: start; }
.timeline__year {
  color: var(--brand);
  font-variant-numeric: tabular-nums;
  padding-bottom: var(--s3);
  border-bottom: 1px solid var(--line);
}
.timeline__body { color: var(--text-soft); }


/* ---------- 37. OVERFLOW CONTAINMENT ------------------------------ */

/* Every horizontally scrolling strip on the site establishes a
   containing block.
   -------------------------------------------------------------------
   Without `position: relative` here, an absolutely positioned
   descendant resolves against the initial containing block instead of
   against the scroller — so it ESCAPES the clip. The visually hidden
   label inside the price table's last column header is one of those,
   and it was sitting 553px into a 360px viewport, pushing
   `documentElement.scrollWidth` to 554 on a phone.
   Nothing was visible, and the page still scrolled sideways. */
.ptable-wrap,
.svc-wrap,
.promise__list,
.filters,
.pfilter__set,
.reviews__list,
.tabs { position: relative; }

/* The statistics band overflowed at 360px: "12 months" set at 40px
   and forbidden to wrap is wider than a half-width column on a
   phone. The figure scales with the viewport rather than starting at
   a size that cannot fit. */


/* ---------- 38. STORE CARD REFINEMENTS ---------------------------- */

/* The plate carries the heading, so the card names its town once. */
.storecard__name a {
  display: inline-flex;
  min-height: 44px;
  align-items: center;
  text-decoration: none;
  color: inherit;
}
.storecard:hover .storecard__name a { text-decoration: underline; text-underline-offset: 3px; }

/* Two up on the locator page, so a store card is wide enough for its
   address, its service chips and its three actions without any of
   them wrapping. `auto-fit` was giving three narrow columns. */
.grid--stores { display: grid; gap: var(--s6); grid-template-columns: 1fr; }
@media (min-width: 760px) { .grid--stores { grid-template-columns: repeat(2, minmax(0, 1fr)); } }


/* ---------- 39. STAT AND BAND CORRECTIONS ------------------------- */

/* The figures are allowed to wrap again. Forbidding it made
   "12 months" 270px wide in a 232px column and it ran into "7 days"
   next to it; subgrid is what keeps the five labels on one line, so
   the figure does not also have to be a single line to stay aligned.
   Capped at 52px, which is inside the 46-60px the design calls for. */
.stat__num {
  white-space: normal;
}

/* The photographic band sits under a scrim heavy enough for body
   copy, not just for a heading. Measured against the lightest of the
   three photographs used in a band. */
.band__media::after {
  background: linear-gradient(to top,
    rgba(20, 32, 25, 0.92) 0%,
    rgba(20, 32, 25, 0.7) 38%,
    rgba(20, 32, 25, 0.3) 72%,
    rgba(20, 32, 25, 0.15) 100%);
}
.band__inner p { color: rgba(255, 255, 255, 0.88); }


/* ---------- 40. FEATURE ARTICLE CORRECTION ------------------------ */

/* The feature card was as tall as its photograph's intrinsic height,
   because the image column had `height: 100%` inside a row whose
   height the image itself was setting. Pinning the ratio makes the
   photograph decide the row and the text centre inside it. */
@media (min-width: 860px) {
  .artcard--feature .artcard__shot {
    aspect-ratio: 16 / 10;
    height: auto;
    min-height: 0;
  }
  .artcard--feature { align-items: stretch; }
  .artcard--feature .artcard__body { align-content: center; }
}


/* ---------- 41. PRICE TOOLBAR CORRECTION -------------------------- */

/* Four filter groups in a two-by-two grid left each pill strip about
   240px wide, so "Charging port repair" was clipped mid-word and the
   repair row ran under the sort control. A grid item defaults to
   `min-width: auto`, which is what let the strip push past its
   track in the first place.

   Each filter group gets a full-width row instead, and the sort
   control moves up beside the search field where there is room for
   it. Three rows of pills, each with the whole toolbar to scroll in. */
.pfilters { display: grid; gap: var(--s3); grid-template-columns: 1fr; }
.pfilter { min-width: 0; }
.pfilter__set { flex: 1 1 auto; min-width: 0; }

@media (min-width: 1100px) {
  .pfilters { grid-template-columns: 1fr; }
  .pfilters > .pfilter { grid-area: auto; }
  .pfilters > .pfilter--sort { grid-area: auto; justify-self: start; }
}

/* Search and sort share the top line of the toolbar. */
.pgroups__row {
  display: grid;
  gap: var(--s3);
  align-items: center;
}
@media (min-width: 760px) {
  .pgroups__row { grid-template-columns: minmax(0, 1fr) auto; }
}
.pgroups__row .search { max-width: 560px; }


/* ---------- 42. (was HERO BUTTON HIERARCHY) --------------------- */

/* Declared with the hero in section 10, against the ground classes
   rather than against a negation of one variant. */

/* ---------- 43. REVIEW COLUMN WIDTH ------------------------------- */

/* Review cards were coming out around 290px because the rating
   column was taking its 300px maximum. Capping it at 240 puts the
   cards back inside the 300-380px the design calls for. */
@media (min-width: 1100px) {
  .reviews { grid-template-columns: minmax(200px, 240px) repeat(3, minmax(0, 1fr)); }
}

/* ---------- 44. TOUCH TARGETS ------------------------------------- */

/* Everything a finger lands on clears 44px, which is the floor
   `tools/shoot.js` enforces at all six breakpoints. These are the
   controls this design pass introduced or resized below it. */

/* Filter pills in the price toolbar. They were shrunk to 36 to fit
   four groups in a two-up grid; the toolbar is one group per row now,
   so there is room for the real size. */
.pfilter .pill { min-height: 44px; padding: 0 var(--s4); }
.pill { min-height: 44px; }

/* The sort control beside the search field. */
.pselect { min-height: 44px; }

/* Column-sort buttons in the price table header. */
.ptable th button { min-height: 44px; }

/* "Use my location" is a button, not a sentence, so it gets a
   control's hit area rather than a text link's. */
#locator-geo { min-height: 44px; display: inline-flex; align-items: center; }

/* The footer phone number. */
.footer__phone { min-height: 44px; }

/* Store links in the footer are a link list, the same as every other
   footer column, and are exempted in the audit alongside them —
   but they are given a comfortable row height anyway. */
.footer__store a { min-height: 36px; }


/* A standalone text action gets a control's hit area. `.tlink` is only
   ever used as an action on its own line, never inside running prose —
   `.prose a` is the inline treatment. */
.tlink { min-height: 44px; display: inline-flex; align-items: center; }

/* The Book control on a price row. 36px was chosen for density in a
   three-across grid of device cards; 44 is the floor. */
.prow__book { min-height: 44px; }

/* ---------- 45. TYPOGRAPHY CORRECTIONS ---------------------------- */

/* Measured with tools/type-audit.mjs, which renders every heading on
   every page at 390 and 1440 and reports what the CSS alone cannot:
   how many lines a heading actually takes, how wide it actually sets,
   and how many different sizes one semantic level ends up at. */

/* ---- 1. text-wrap: the right algorithm for the job ---------------
   `balance` equalises line lengths. On a two- or three-line display
   heading that is exactly right. On a card title in a 345px column it
   is actively wrong: "Why Is My PS5 HDMI Port Not Working?" balanced
   into 180px lines inside a 345px box, so half the card was empty and
   the title read as a narrow stack.

   `pretty` leaves the measure alone and only prevents a short last
   line, which is what a card title and an accordion question want. */
.artcard__title,
.acc__q,
.faultcard__plain,
.devcard__name,
.storecard__name,
.pgroup__model,
.rail__title,
.annot__title,
.review__text,
.finder__device-name,
.prose h2,
.prose h3,
.subhead,
.lede,
.opener__lede { text-wrap: pretty; }

/* The FAQ question is an <h3> with `display: contents` inside its
   <summary>, so it generates no box and text-wrap on it does nothing.
   The summary is what actually wraps. */
.acc__item > summary { text-wrap: pretty; }

/* Balance stays where it earns its place: the hero, page headers,
   section openers and the CTA — headings of two or three long lines
   whose ragged edge is visible. */
.hero__title,
.cta__title,
.opener__text > .display-l,
.opener__text > .display-m,
.opener__text > .display-xl { text-wrap: balance; }

/* ---- 2. One size per tier ----------------------------------------
   The audit found eight distinct sizes across h2/h3 at 1440, several
   of them a pixel or two apart at two different weights — 23px/600
   and 23px/700 for the same tier, and a store card title that was
   23px on one page and 24px on another depending on whether it sat
   on a photograph or a plate. Three tiers, and every component maps
   onto one of them. */

/* Block heading. 23px, 700. */

/* Item title. 19px, 600 — a row label, deliberately below a card
   title so a grid of twelve does not read as twelve headlines. */
/* The feature article keeps its own size; it is the one headline in
   the advice grid, not one of the row of cards. */
@media (min-width: 860px) {
}

/* ---- 3. Reviews -------------------------------------------------
   "What customers say" was inside the 240px rating column, where a
   42px heading set three words on three lines — the column-of-words
   failure, reintroduced by the fix that gave the review cards their
   width back. The heading is a normal section opener now and the
   column holds only the score. */
.reviews { display: grid; gap: var(--s7); align-items: start; }
@media (min-width: 1000px) {
  .reviews {
    grid-template-columns: minmax(180px, 220px) repeat(3, minmax(0, 1fr));
    gap: var(--s6) var(--s4);
  }
  .reviews__list { display: contents; }
  .reviews__list > li { min-width: 0; }
}
.reviews__head { display: grid; gap: var(--s4); align-content: start; }

/* ---------- 46. STORE LOCATOR EXPERIENCE ------------------------- */

.store-locator-page { padding-block: clamp(64px, 7vw, 96px); }
.store-locator-layout {
  display: grid;
  gap: 56px;
  align-items: start;
}
.store-locator-intro { display: grid; max-width: 560px; gap: 18px; }
.store-locator-intro > :is(.eyebrow, h1, .lede, .store-locator-open) { margin: 0; }
.store-locator-intro .lede { max-width: 480px; color: var(--text-soft); }
.store-locator-open { display: inline-flex; align-items: center; gap: 8px; color: var(--text-soft); }
.store-locator-open svg { color: var(--brand); }
.store-locator-page .locator { margin-top: 18px; }
.store-locator-trust { display: grid; margin: 6px 0 0; padding: 20px 0 0; gap: 9px; border-top: 1px solid var(--line); color: var(--text-soft); list-style: none; }
.store-locator-trust li { display: flex; align-items: center; gap: 8px; }
.store-locator-trust svg { color: var(--brand); }
.store-locator-results { min-width: 0; }
.store-locator-results__head { display: flex; margin-bottom: 20px; align-items: baseline; justify-content: space-between; gap: 16px; }
.store-locator-results__head h2 { margin: 0; }
.store-locator-results__head span { color: var(--text-mute); }

@media (min-width: 1100px) {
  .store-locator-layout { grid-template-columns: minmax(340px, .36fr) minmax(0, .64fr); gap: clamp(48px, 5vw, 72px); }
  .store-locator-intro { position: sticky; top: calc(var(--header-h) + 28px); }
  .store-locator-page .grid--stores { gap: 18px; }
}
@media (max-width: 1099px) {
  .store-locator-intro { max-width: 760px; }
}
@media (max-width: 759px) {
  .store-locator-layout { gap: 40px; }
  .store-locator-results__head { margin-bottom: 16px; }
  .store-locator-page .grid--stores { grid-template-columns: 1fr; gap: 14px; }
  .storecard { padding: 20px; border-radius: 18px; }
}
@media (max-width: 519px) {
  .store-locator-page .locator__form { grid-template-columns: 1fr; }
  .store-locator-page .locator__submit { width: 100%; }
}

/* ---------- 47. ACTION-FIRST JOURNEYS ---------------------------- */

/* Search entry points share one behaviour and one visual contract.
   Suggestions sit on top of the following content, so results never
   push a hero or selector around while somebody is typing. */
.repair-searchbox { position: relative; z-index: 8; width: 100%; }
.repair-searchbox > form { position: relative; }
.repair-searchbox .search { margin: 0; }
.repair-searchbox .search input { padding-right: 58px; }
.repair-searchbox__go {
  position: absolute;
  top: 50%;
  right: 8px;
  display: grid;
  width: 44px;
  height: 44px;
  padding: 0;
  place-items: center;
  border: 0;
  border-radius: 11px;
  background: var(--brand);
  color: #fff;
  cursor: pointer;
  transform: translateY(-50%);
  transition: background-color 180ms var(--ease), transform 180ms var(--ease);
}
.repair-searchbox__go:hover { background: var(--brand-deep); transform: translateY(-50%) translateX(2px); }
.repair-searchbox__go:focus-visible { outline: 3px solid rgba(7,132,87,.24); outline-offset: 2px; }
.repair-suggestions {
  position: absolute;
  z-index: 20;
  top: calc(100% + 8px);
  left: 0;
  right: 0;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--surface);
  color: var(--ink);
  box-shadow: 0 18px 44px rgba(13,30,22,.11);
}
.repair-suggestions[hidden] { display: none; }
.repair-suggestions__group + .repair-suggestions__group { border-top: 1px solid var(--line-soft); }
.repair-suggestions__group > p {
  margin: 0;
  padding: 10px 16px 5px;
  color: var(--text-mute);
  text-transform: uppercase;
}
.repair-suggestions__group > a {
  display: grid;
  min-height: 50px;
  padding: 8px 16px;
  align-content: center;
  color: inherit;
  text-decoration: none;
}
.repair-suggestions__group > a:hover,
.repair-suggestions__group > a:focus-visible { background: var(--sage); outline: 0; }
.repair-suggestions__group > a small { color: var(--text-mute); }
.repair-suggestions__loading { margin: 0; padding: 18px; color: var(--text-mute); }
.repair-suggestions__empty { display: grid; gap: 5px; padding: 18px; }
.repair-suggestions__empty > span { color: var(--text-mute); }
.repair-suggestions__empty > div { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 8px; }
.repair-suggestions__empty > div > a:not(.btn) { color: var(--brand); }

/* The homepage hero begins with a task instead of two generic calls
   to action. The image remains physical proof, while this column is
   the fastest path to a price or repair. */
.hero__actions:has(.hero-start) { display: block; width: 100%; max-width: 560px; }
.hero-start { display: grid; width: 100%; gap: 11px; }
.hero-start .search input { min-height: 58px; border: 1px solid rgba(255,255,255,.28); background: #fff; box-shadow: 0 10px 28px rgba(5,26,18,.14); }
.hero-start__popular { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 13px; margin: 0; color: var(--on-dark-soft); }
.hero-start__popular a { min-height: 44px; display: inline-flex; align-items: center; color: #fff; text-underline-offset: 3px; }
.hero-start__links { display: flex; flex-wrap: wrap; gap: 9px; }
.hero-start__links .btn { min-height: 44px; }

.finder .repair-searchbox { margin-bottom: 28px; }
.finder .repair-searchbox .search { margin: 0; }
.finder__device { position: relative; }
.finder__device.is-selected { border-color: var(--brand); background: var(--sage); box-shadow: 0 0 0 3px rgba(7,132,87,.09); }
.finder__device.is-selected::after {
  content: '✓';
  position: absolute;
  top: 12px;
  right: 12px;
  display: grid;
  width: 26px;
  height: 26px;
  place-items: center;
  border-radius: 50%;
  background: var(--brand);
  color: #fff;
}

/* Progressive disclosure uses one open stage separated by rules,
   rather than stacking four dashboard cards. */
.assistant {
  margin-top: 32px;
  padding: 24px 0 4px;
  border-top: 1px solid var(--line);
}
.assistant[hidden], .assistant__step[hidden], .assistant__result[hidden] { display: none; }
.assistant__feedback { min-height: 24px; margin: 0 0 16px; color: var(--brand-deep); }
.assistant__progress { display: grid; grid-template-columns: repeat(4, 1fr); margin: 0 0 28px; padding: 0; list-style: none; }
.assistant__progress li { position: relative; display: flex; align-items: center; gap: 8px; color: var(--text-mute); }
.assistant__progress li:not(:last-child)::after { content: ''; position: absolute; z-index: -1; top: 14px; left: 29px; right: 8px; height: 1px; background: var(--line); }
.assistant__progress span { display: grid; width: 28px; height: 28px; place-items: center; border: 1px solid var(--line); border-radius: 50%; background: var(--surface); }
.assistant__progress .is-current { color: var(--ink); }
.assistant__progress .is-current span { border-color: var(--brand); background: var(--brand); color: #fff; }
.assistant__progress .is-complete { color: var(--brand-deep); }
.assistant__progress .is-complete span { border-color: #BBDCCF; background: var(--mint); color: var(--brand-deep); }
.assistant__progress .is-complete span::before { content: '✓'; }
.assistant__step { animation: assistant-in 200ms var(--ease) both; }
@keyframes assistant-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.assistant__step-head { display: flex; margin-bottom: 17px; align-items: end; justify-content: space-between; gap: 20px; }
.assistant__kicker { margin: 0 0 3px; color: var(--brand); text-transform: uppercase; }
.assistant__step h3, .assistant__result h3 { margin: 0; color: var(--ink); }
.assistant__step-head > button,
.assistant__more,
.assistant__escape :is(a,button) { min-height: 44px; padding: 0; border: 0; background: none; color: var(--brand); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
.assistant__options { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 10px; }
.assistant__option {
  display: grid;
  min-height: 66px;
  padding: 11px 14px;
  align-content: center;
  gap: 2px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
  color: var(--ink);
  text-align: left;
  cursor: pointer;
  transition: border-color 180ms var(--ease), background-color 180ms var(--ease), transform 180ms var(--ease);
}
.assistant__option:hover { border-color: #B7CEC2; transform: translateY(-2px); }
.assistant__option.is-selected { border-color: var(--brand); background: var(--mint); }
.assistant__option small { color: var(--text-mute); }
.assistant__model-search { position: relative; display: block; max-width: 480px; margin-bottom: 12px; }
.assistant__model-search svg { position: absolute; top: 50%; left: 14px; color: var(--text-mute); transform: translateY(-50%); }
.assistant__model-search input { width: 100%; min-height: 48px; padding: 0 14px 0 42px; border: 1px solid var(--line); border-radius: 12px; background: var(--surface); }
.assistant__model-search input:focus { border-color: var(--brand); outline: 3px solid rgba(7,132,87,.1); }
.assistant__more { margin-top: 8px; }
.assistant__escape { margin: 10px 0 0; }
.assistant__no-options { grid-column: 1/-1; margin: 0; padding: 16px 0; color: var(--text-mute); }
.assistant__result {
  display: grid;
  grid-template-columns: minmax(0,1.25fr) minmax(320px,.75fr);
  gap: 22px 38px;
  padding: 26px;
  border: 1px solid #C6DED3;
  border-radius: 18px;
  background: var(--mint);
  animation: assistant-in 220ms var(--ease) both;
}
.assistant__result > div:first-child > p:last-child { max-width: 600px; margin: 8px 0 0; color: var(--text-soft); }
.assistant__result dl { display: grid; margin: 0; }
.assistant__result dl div { display: flex; padding: 9px 0; justify-content: space-between; gap: 18px; border-bottom: 1px solid rgba(6,74,53,.12); }
.assistant__result dt { color: var(--text-mute); }
.assistant__result dd { margin: 0; color: var(--ink); text-align: right; }
.assistant__warranty { display: flex; margin: 0; align-items: center; gap: 8px; color: var(--brand-deep); }
.assistant__actions { display: flex; justify-content: flex-end; gap: 9px; }

.prow__actions { display: flex; align-items: center; gap: 8px; }
.prow__detail { min-height: 44px; display: inline-flex; align-items: center; color: var(--text-soft); }
.rail__action { margin-top: auto; }
/* Three actions do not fit one row in a card this width — the phone
   number alone needs about 115px, Directions another 100px with its
   icon, and Store details a further 90px, against roughly 290px on
   offer. Forcing all three across either wrapped the phone number
   onto two lines or, sized the other way, squeezed Store details
   until its label overflowed its own button. Store details takes the
   row to itself; Directions and the phone number, which are the pair
   someone reaches for together, share the row below. */
.storecard__links { grid-template-columns: 1fr; gap: 8px; }
.storecard__links a:first-child { border-color: transparent; background: transparent; justify-content: flex-start; padding-inline: 0; }
.storecard__links-row { display: grid; grid-template-columns: 1fr auto; gap: 8px; }
.storecard__links-row a { white-space: nowrap; padding-inline: 12px; }

@media (max-width: 899px) {
  .assistant__options { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .assistant__result { grid-template-columns: 1fr; }
  .assistant__actions { justify-content: flex-start; }
}
@media (max-width: 599px) {
  .hero--home .hero__media { display: none; }
  .hero__actions:has(.hero-start) { grid-column: 1 / -1; }
  .hero-start__links { display: grid; grid-template-columns: 1fr 1fr; }
  .hero-start__links .btn { width: 100%; padding-inline: 8px; }
  .repair-suggestions { position: fixed; top: auto; left: var(--gutter); right: var(--gutter); max-height: min(70vh,540px); overflow-y: auto; }
  .finder .repair-suggestions { position: absolute; left: 0; right: 0; }
  .assistant { margin-top: 24px; padding-top: 20px; }
  .assistant__progress li { display: grid; justify-items: start; gap: 4px; }
  .assistant__progress li:not(:last-child)::after { top: 14px; }
  .assistant__step-head { align-items: start; }
  .assistant__step-head > button { flex: none; }
  .assistant__options { gap: 8px; }
  .assistant__option { min-height: 70px; padding: 10px; }
  .assistant__result { padding: 20px; }
  .assistant__actions { display: grid; }
  .assistant__actions .btn { width: 100%; }
  .prow { grid-template-columns: minmax(0,1fr) auto; }
  .prow__actions { grid-column: 1 / -1; justify-content: flex-end; }
  .storecard__links a:first-child { justify-content: center; }
  .storecard__links-row { grid-template-columns: 1fr 1fr; }
}
