/* ==========================================================================
   Appliance Kings — Design System
   "Dispatch Board": dark, cyan-accented, over a seamless PCB-trace texture.
   Author: Fisher.digital
   ========================================================================== */

/* ---------- Fonts (self-hosted, variable, latin subset) ----------
   Saira            — squared, engineered grotesque for headlines. Its flat
                      terminals suit the circuit-board and control-panel
                      language the rest of the site is built on.
   Public Sans      — sober, sturdy body face. Chosen for legibility over
                      personality: it carries 2,400-word appliance pages and
                      never draws attention to itself.
   Spline Sans Mono — technical monospace for labels, prices and error codes.
                      Normal-width, unlike the previous mono, so labels no
                      longer need a width override to sit correctly.
   ------------------------------------------------------------------ */
@font-face {
  font-family: 'Saira';
  src: url('../fonts/saira-var.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-display: swap;
  font-style: normal;
}
@font-face {
  font-family: 'Public Sans';
  src: url('../fonts/public-sans-var.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-display: swap;
  font-style: normal;
}
@font-face {
  font-family: 'Spline Sans Mono';
  src: url('../fonts/spline-mono-var.woff2') format('woff2-variations');
  font-weight: 300 700;
  font-display: swap;
  font-style: normal;
}

/* ---------- Tokens ---------- */
:root {
  /* brand */
  --cy: #0aacba;
  --cy-bright: #22d3e4;
  --cy-deep: #067f8a;
  --cy-glow: rgba(10, 172, 186, 0.16);
  --cy-line: rgba(10, 172, 186, 0.38);

  /* surfaces */
  --ink: #07090a;
  --ink-2: #0b0e0f;
  --ink-3: #101416;
  --ink-4: #161b1d;

  /* lines */
  --line: rgba(255, 255, 255, 0.09);
  --line-2: rgba(255, 255, 255, 0.16);
  --line-3: rgba(255, 255, 255, 0.28);

  /* text */
  --paper: #f2f5f6;
  --muted: #9aa7aa;
  --muted-2: #828f93;   /* >=4.8:1 on every surface, incl. the WhatsApp tile */

  /* status */
  --ok: #46d39a;
  --warn: #f2b544;

  /* type */
  --display: 'Saira', 'Helvetica Neue', Helvetica, system-ui, sans-serif;
  --body: 'Public Sans', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --mono: 'Spline Sans Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  /* metrics */
  --gut: clamp(1rem, 3.2vw, 2rem);
  --maxw: 1400px;
  --rail: 1px;
  --r: 2px;
  --r-lg: 4px;
  --x-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 6l12 12M18 6L6 18' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round'/%3E%3C/svg%3E");
  --ar-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 12h15M13 6l6 6-6 6' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

  /* fluid type scale */
  --t-xs: 0.7rem;
  --t-sm: 0.8125rem;
  --t-base: clamp(0.975rem, 0.94rem + 0.18vw, 1.075rem);
  --t-lg: clamp(1.1rem, 1.03rem + 0.34vw, 1.3rem);
  --t-xl: clamp(1.35rem, 1.18rem + 0.8vw, 1.85rem);
  --t-2xl: clamp(1.55rem, 1.3rem + 1.15vw, 2.15rem);
  --t-3xl: clamp(1.9rem, 1.4rem + 2.15vw, 3.05rem);
  --t-4xl: clamp(1.95rem, 1.15rem + 3.3vw, 3.9rem);
  --t-5xl: clamp(3.2rem, 0.5rem + 10.4vw, 11rem);
}

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 96px;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}
[class*='mono'], .eyebrow, .slab, .btn, .chip, .tile__lbl, .card__n, .card__ft,
.idx__no, .idx__meta, .crumbs, .rail, .nav__link, .toc__t, .ftr__h, .ftr__bot,
.form label, .arw, .stat span, .mast__kick, .mast__facts dt, .revs .rev__by,
.acc__btn + *, .dock a, .ptab thead th, .prose th, .note__t, .burger, .drawer__close {
}

body {
  background-color: var(--ink);
  background-image: url('../img/pcb.svg');
  background-size: 360px 360px;
  background-repeat: repeat;
  color: var(--paper);
  font-family: var(--body);
  font-size: var(--t-base);
  line-height: 1.65;
  font-feature-settings: 'ss01', 'cv01';
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
img, svg, video { display: block; max-width: 100%; height: auto; }
button, input, select, textarea { font: inherit; color: inherit; }
a { color: var(--cy); text-decoration-color: var(--cy-line); text-underline-offset: 0.22em; }
a:hover { color: var(--cy-bright); }
:focus-visible { outline: 2px solid var(--cy-bright); outline-offset: 3px; border-radius: 2px; }
::selection { background: var(--cy); color: var(--ink); }
hr { border: 0; border-top: 1px solid var(--line); }

/* ---------- Layout primitives ---------- */
.wrap { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: var(--gut); }
.wrap-tight { max-width: 980px; }
.wrap-read { max-width: 76ch; }

/* The board: a seamless PCB trace texture, tiled behind everything.
   Appliance faults are overwhelmingly board, element and pump failures, so the
   texture is on-topic rather than decorative wallpaper. Kept very faint so it
   never competes with body copy, and it is a single 4kB SVG. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(135% 95% at 50% 0%, rgba(7, 9, 10, 0.65), rgba(7, 9, 10, 0.82) 45%, rgba(7, 9, 10, 0.94) 78%, var(--ink) 100%);
}

/* Animated glow layer: sits above the static board texture and its veil,
   below every piece of content. */
.board {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  overflow: hidden;
  contain: strict;
  transition: opacity 0.25s linear;
}
.board svg { width: 100%; height: 100%; display: block; }

/* Phones get the static board texture but not the moving streaks — the effect
   is barely legible at that size and the frame budget is better spent
   elsewhere. */
@media (max-width: 700px) {
  .board { display: none; }
}

main, header, footer { position: relative; z-index: 2; }

/* Section rhythm */
.sec { padding-block: clamp(3.5rem, 7vw, 7rem); border-top: 1px solid var(--line); }
.sec--flush { border-top: 0; }
.sec--tint { background: linear-gradient(180deg, rgba(11, 14, 15, 0.96), rgba(7, 9, 10, 0.96)); }

/* Blueprint section label: 01 / TITLE ------------------ */
.slab {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  font-family: var(--mono);
  font-size: var(--t-xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted-2);
  margin-bottom: 1.6rem;
}
.slab::before {
  content: '';
  width: 18px;
  height: 1px;
  background: var(--cy);
  flex: none;
}
.slab::after {
  content: '';
  flex: 1;
  height: 1px;
  background: repeating-linear-gradient(90deg, var(--line-2) 0 4px, transparent 4px 9px);
}

/* ---------- Type ---------- */
h1, h2, h3, h4, h5 {
  font-family: var(--display);
  font-weight: 800;
  line-height: 1.04;
  letter-spacing: -0.022em;
  text-wrap: balance;
}
h1 { font-size: var(--t-4xl); }
h2 { font-size: var(--t-3xl); }
/* Saira ships here without its width axis — carrying it costs 60 kB and the
   only thing it bought was condensing long words on small screens. The type
   scale below plus break-word does that job instead; responsive-check.py
   verifies at 320px, where "HERTFORDSHIRE" is the worst case. */
h1, h2, h3 { overflow-wrap: break-word; }
h3 { font-size: var(--t-xl); letter-spacing: -0.02em; line-height: 1.15; }
h4 { font-size: var(--t-lg); letter-spacing: -0.015em; line-height: 1.2; }
h5 { font-size: var(--t-base); letter-spacing: -0.01em; }
p { text-wrap: pretty; }
.lede { font-size: var(--t-lg); color: #cfd8da; line-height: 1.55; max-width: 62ch; }
.eyebrow {
  font-family: var(--mono);
  font-size: var(--t-xs);
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--cy);
}
.cy { color: var(--cy); }
.muted { color: var(--muted); }
.mono { font-family: var(--mono); }
.small { font-size: var(--t-sm); }
.stroke {
  -webkit-text-stroke: 1.2px var(--line-3);
  color: transparent;
}

/* Prose blocks for content pages */
.prose > * + * { margin-top: 1.05em; }
.prose h2 { margin-top: 2.4em; font-size: var(--t-2xl); }
.prose h3 { margin-top: 1.9em; }
.prose h4 { margin-top: 1.6em; color: var(--paper); }
.prose ul, .prose ol { padding-left: 1.15rem; }
.prose li + li { margin-top: 0.45em; }
.prose li::marker { color: var(--cy); }
.prose strong { color: #fff; font-weight: 650; }
/* Tables carry no rules. Rows are separated by a barely-there fill on the odd
   ones and by generous vertical padding, which is enough to track a line
   across without drawing a grid over the content. The header is set in the
   mono face instead of being boxed off by a border. */
.prose table { width: 100%; border-collapse: collapse; font-size: var(--t-sm); }
.prose th, .prose td { text-align: left; padding: 0.75rem 0.9rem; border: 0; }
.prose th { font-family: var(--mono); font-size: var(--t-xs); letter-spacing: 0.07em; text-transform: uppercase; color: var(--cy); font-weight: 500; padding-bottom: 0.5rem; }
.prose tbody tr:nth-child(odd) { background: rgba(255, 255, 255, 0.022); }
.prose tbody tr:hover { background: rgba(10, 172, 186, 0.06); }
.prose td:first-child { color: var(--paper); }
.prose blockquote {
  border-left: 2px solid var(--cy);
  padding: 0.2rem 0 0.2rem 1.1rem;
  color: #cfd8da;
  font-style: normal;
}

/* Glyphs not present in the self-hosted subsets are drawn, never typed,
   so nothing ever falls back to a system font or renders as tofu. */
.ar {
  display: inline-block;
  width: 0.85em; height: 0.85em;
  vertical-align: -0.06em;
  background: currentColor;
  -webkit-mask: var(--ar-svg) no-repeat center / contain;
  mask: var(--ar-svg) no-repeat center / contain;
  transition: transform 0.18s ease;
}
a:hover > .ar, a:hover .ar { transform: translateX(3px); }

/* ---------- Buttons ---------- */
/* ---------- Buttons ----------
   Hard-edged, with the corner cut away on the diagonal and a solid offset
   shadow instead of a border — the shape does the work a border used to.
   Adapted from the Uiverse "glitch" button by namecho, recoloured to the
   brand: the original's red/cyan/yellow becomes cyan, deep cyan and white, and
   the shadow colour is a variable so the WhatsApp button keeps its green.

   The glitch itself is on primary buttons only. Ghost buttons are secondary by
   definition and do not need to shout, and the WhatsApp button belongs to
   somebody else's brand. */
.btn {
  --btn-bg: var(--cy);
  --btn-fg: #04191b;
  --btn-shadow: var(--cy-deep);
  --btn-cut: 12px;
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  padding: 0.9rem 1.5rem;
  background: linear-gradient(45deg, transparent var(--btn-cut), var(--btn-bg) var(--btn-cut));
  color: var(--btn-fg);
  border: 0;
  border-radius: 0;
  box-shadow: 5px 0 0 var(--btn-shadow);
  font-family: var(--mono);
  font-size: var(--t-sm);
  font-weight: 600;
  letter-spacing: 0.1em;
  line-height: 1.1;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.16s ease, box-shadow 0.16s ease, background 0.16s ease;
}
.btn > * { position: relative; z-index: 1; }
.btn:hover { color: var(--btn-fg); transform: translate(-2px, -2px); box-shadow: 7px 2px 0 var(--btn-shadow); }
.btn:active { transform: translate(0, 0); box-shadow: 3px 0 0 var(--btn-shadow); }
.btn:focus-visible { outline: 2px solid var(--cy-bright); outline-offset: 3px; }

/* Ghost: a quiet filled surface rather than an outline, so it keeps the same
   cut corner and does not reintroduce a border. */
.btn--ghost { --btn-bg: rgba(255, 255, 255, 0.07); --btn-fg: var(--paper); --btn-shadow: var(--line-2); }
.btn--ghost:hover { --btn-bg: rgba(10, 172, 186, 0.2); --btn-fg: var(--cy); }
.btn--wa { --btn-bg: #1faa53; --btn-fg: #041a0c; --btn-shadow: #0d5c2c; }

/* The glitch overlay. data-txt is set by site.js from the button's own label,
   so the rule only ever fires on a button that has one — without JS there is
   no overlay at all and the button is simply a button. */
.btn:not(.btn--ghost):not(.btn--wa)[data-txt]::after {
  --slice-0: inset(50% 50% 50% 50%);
  --slice-1: inset(80% -6px 0 0);
  --slice-2: inset(50% -6px 30% 0);
  --slice-3: inset(10% -6px 85% 0);
  --slice-4: inset(40% -6px 43% 0);
  --slice-5: inset(80% -6px 5% 0);
  content: attr(data-txt);
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: inherit;
  background: linear-gradient(45deg, transparent 8px, var(--cy-deep) 8px, var(--cy-deep) 13px, var(--btn-bg) 13px);
  color: var(--btn-fg);
  text-shadow: -3px -3px 0 var(--cy-bright), 3px 3px 0 rgba(255, 255, 255, 0.75);
  clip-path: var(--slice-0);
  pointer-events: none;
}
.btn:not(.btn--ghost):not(.btn--wa)[data-txt]:hover::after {
  animation: glitch 1s steps(2, end);
}
@keyframes glitch {
  0%   { clip-path: var(--slice-1); transform: translate(-20px, -10px); }
  10%  { clip-path: var(--slice-3); transform: translate(10px, 10px); }
  20%  { clip-path: var(--slice-1); transform: translate(-10px, 10px); }
  30%  { clip-path: var(--slice-3); transform: translate(0, 5px); }
  40%  { clip-path: var(--slice-2); transform: translate(-5px, 0); }
  50%  { clip-path: var(--slice-3); transform: translate(5px, 0); }
  60%  { clip-path: var(--slice-4); transform: translate(5px, 10px); }
  70%  { clip-path: var(--slice-2); transform: translate(-10px, 10px); }
  80%  { clip-path: var(--slice-5); transform: translate(20px, -10px); }
  90%  { clip-path: var(--slice-1); transform: translate(-10px, 0); }
  100% { clip-path: var(--slice-1); transform: translate(0); }
}
@media (prefers-reduced-motion: reduce) {
  .btn:hover { transform: none; }
  .btn[data-txt]:hover::after { animation: none; }
}
.btn--wa:hover { box-shadow: 0 8px 24px -10px #1faa53; }
.btn--lg { padding: 1.05rem 1.7rem; font-size: var(--t-base); }
.btn--block { display: flex; width: 100%; box-shadow: 4px 0 0 var(--btn-shadow); }
.btn--block:hover { box-shadow: 6px 2px 0 var(--btn-shadow); }
.btn svg { width: 1.05em; height: 1.05em; flex: none; }

/* Text link with arrow */
.arw {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--mono);
  font-size: var(--t-sm);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  text-decoration: none;
  border-bottom: 1px solid var(--cy-line);
  padding-bottom: 2px;
}
.arw::after {
  content: '';
  width: 0.9em; height: 0.9em;
  background: currentColor;
  -webkit-mask: var(--ar-svg) no-repeat center / contain;
  mask: var(--ar-svg) no-repeat center / contain;
  transition: transform 0.18s ease;
}
.arw:hover::after { transform: translateX(4px); }

/* ---------- Utility rail (top) ---------- */
.rail {
  background: var(--ink-2);
  border-bottom: 1px solid var(--line);
  font-family: var(--mono);
  font-size: var(--t-xs);
  letter-spacing: 0.04em;
}
.rail__in {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  min-height: 34px;
  padding-block: 0.3rem;
}
.rail__status { display: flex; align-items: center; gap: 0.55rem; color: var(--muted); }
.rail__links { display: flex; align-items: center; gap: 1.2rem; }
.rail a { text-decoration: none; color: var(--muted); display: inline-block; padding: 0.35rem 0; }
.rail a:hover { color: var(--cy); }
.dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--ok); flex: none;
  box-shadow: 0 0 0 0 rgba(70, 211, 154, 0.55);
  animation: ping 2.4s infinite;
}
.dot--closed { background: var(--warn); animation: none; }
@keyframes ping {
  0% { box-shadow: 0 0 0 0 rgba(70, 211, 154, 0.5); }
  70% { box-shadow: 0 0 0 7px rgba(70, 211, 154, 0); }
  100% { box-shadow: 0 0 0 0 rgba(70, 211, 154, 0); }
}
@media (max-width: 900px) { .hide-s { display: none !important; } }
@media (max-width: 560px) { .rail__in { min-height: 30px; } .rail__status { font-size: 0.66rem; } }

/* ---------- Header / nav ---------- */
.hdr-wrap { position: sticky; top: 0; z-index: 60; }
.hdr {
  position: relative;
  background: rgba(7, 9, 10, 0.82);
  backdrop-filter: saturate(150%) blur(14px);
  -webkit-backdrop-filter: saturate(150%) blur(14px);
  border-bottom: 1px solid var(--line);
}
.hdr__in { display: flex; align-items: center; gap: 1.2rem; min-height: 68px; }
.brand { display: flex; align-items: center; gap: 0.6rem; text-decoration: none; color: var(--paper); flex: none; }
.brand svg { width: 34px; color: var(--cy); }
.brand__txt { font-family: var(--display); font-weight: 800; letter-spacing: -0.04em; line-height: 0.92; font-size: 1.02rem; text-transform: uppercase; }
.brand__txt span { display: block; color: var(--cy); }

.nav { margin-left: auto; display: flex; align-items: center; gap: 0.15rem; }
.nav__item { position: relative; }
.nav__link {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.75rem;
  font-family: var(--mono);
  font-size: var(--t-xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--paper);
  text-decoration: none;
  border-radius: var(--r);
  background: none;
  border: 0;
  cursor: pointer;
}
.nav__link:hover, .nav__item:hover .nav__link { color: var(--cy); background: rgba(255, 255, 255, 0.04); }
.nav__link[aria-current='page'] { color: var(--cy); }
.nav__link::after { content: ''; }
.nav__item--has > .nav__link::after {
  content: '';
  width: 5px; height: 5px;
  border-right: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  transform: rotate(45deg) translateY(-2px);
}
.mega {
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  /* The visual gap is padding, not an offset. An offset leaves a dead strip
     between the trigger and the panel, and the menu closes as the pointer
     crosses it. */
  margin-top: 0;
  padding-top: calc(1.1rem + 8px);
  min-width: 560px;
  background: var(--ink-2);
  border: 1px solid var(--line-2);
  border-radius: var(--r-lg);
  box-shadow: 0 30px 70px -20px #000;
  padding: 1.1rem;
  background-clip: padding-box;
  display: none;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.15rem 1.4rem;
}
.mega--wide { min-width: 720px; grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* An invisible bridge across the gap keeps :hover alive between the trigger
   and the panel. */
.mega::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: -10px;
  height: 14px;
}
/* With JS running, open/close is driven entirely by [data-open] so that
   Escape can close a panel while focus legitimately stays on its trigger.
   The :hover / :focus-within rules are the no-JS fallback only. */
html:not(.js) .nav__item:hover .mega,
html:not(.js) .nav__item:focus-within .mega,
.nav__item[data-open] .mega { display: grid; }
/* The right-hand menus are wide; centring them would push them off screen. */
.nav__item:nth-last-child(-n + 2) .mega { left: auto; right: 0; transform: none; }
.nav__item:nth-last-child(-n + 2) .mega::before { right: 0; left: 0; }
.mega a {
  display: block;
  padding: 0.5rem 0.6rem;
  font-size: var(--t-sm);
  color: var(--paper);
  text-decoration: none;
  border-radius: var(--r);
  border-left: 2px solid transparent;
}
.mega a:hover { background: rgba(255, 255, 255, 0.05); border-left-color: var(--cy); color: var(--cy); }
.mega a i { display: block; font-style: normal; font-size: var(--t-xs); color: var(--muted-2); font-family: var(--mono); }
.mega__hd {
  grid-column: 1 / -1;
  font-family: var(--mono);
  font-size: var(--t-xs);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted-2);
  padding: 0.2rem 0.6rem 0.5rem;
  border-bottom: 1px solid var(--line);
  margin-bottom: 0.35rem;
}
.hdr__cta { display: flex; gap: 0.85rem; flex: none; }
@media (max-width: 1100px) { .nav, .hdr__cta { display: none; } }

.burger {
  display: none;
  margin-left: auto;
  background: none;
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  padding: 0.55rem 0.7rem;
  cursor: pointer;
  font-family: var(--mono);
  font-size: var(--t-xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--paper);
}
@media (max-width: 1100px) { .burger { display: inline-flex; align-items: center; gap: 0.5rem; } }
.burger span { display: block; width: 16px; height: 9px; border-top: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; }

/* Mobile drawer */
.drawer {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: var(--ink);
  transform: translateY(-100%);
  transition: transform 0.34s cubic-bezier(0.4, 0, 0.2, 1);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-bottom: 6rem;
}
.drawer[data-open='true'] { transform: translateY(0); }
.drawer__top { display: flex; align-items: center; justify-content: space-between; min-height: 68px; border-bottom: 1px solid var(--line); }
.drawer__close { background: none; border: 1px solid var(--line-2); border-radius: var(--r); padding: 0.5rem 0.8rem; cursor: pointer; font-family: var(--mono); font-size: var(--t-xs); text-transform: uppercase; letter-spacing: 0.04em; }
.acc { border-bottom: 1px solid var(--line); }
.acc__btn {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 0;
  background: none;
  border: 0;
  cursor: pointer;
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.25rem;
  letter-spacing: -0.02em;
  text-align: left;
  color: var(--paper);
}
.acc__btn::after { content: '+'; font-family: var(--mono); color: var(--cy); font-size: 1.3rem; }
.acc[data-open='true'] .acc__btn::after { content: '–'; }
.acc__panel { display: none; padding-bottom: 1rem; }
.acc[data-open='true'] .acc__panel { display: grid; gap: 0.1rem; }
.acc__panel a { display: block; padding: 0.65rem 0; font-size: var(--t-sm); text-decoration: none; color: var(--muted); border-bottom: 1px dotted rgba(255,255,255,.06); }
.acc__panel a:hover { color: var(--cy); }
.drawer__link { display: block; padding: 1rem 0; border-bottom: 1px solid var(--line); font-family: var(--display); font-weight: 700; font-size: 1.25rem; letter-spacing: -0.02em; color: var(--paper); text-decoration: none; }

/* ---------- Masthead (home hero — NOT a standard hero) ---------- */
.mast { padding-top: clamp(1.4rem, 2.6vw, 2.2rem); padding-bottom: clamp(1.4rem, 2.6vw, 2.2rem); overflow: hidden; }

/* ---------- Photographic masthead ----------
   The veil is a contrast decision, not a taste one — but most of the work is
   now done before the CSS ever runs. tools/make_photos.py tone-maps the hero
   variant so no pixel exceeds 0.46 luminance, which means the veil no longer
   has to be set by a blown-out window. It dropped from 0.92 to 0.56 and the
   photograph is legible as a result. Text over it is also held at the brighter
   tokens rather than --muted-2, which buys another step of headroom.

   Every figure below is measured, not estimated: tools/check_hero_contrast.py
   screenshots what is actually behind each text element at six widths and
   computes the real ratio. Change either number and run it. */
.mast--photo { position: relative; isolation: isolate; padding-block: clamp(2rem, 4.5vw, 4rem); }
.mast--photo .mast__in { position: relative; z-index: 2; }
.mast__bg { position: absolute; inset: 0; z-index: 0; display: block; overflow: hidden; }
/* `.mast__bg .ph img`, not `.mast__bg img`: the still is a <picture class="ph">
   and the generic `.ph img { height: auto }` further down the file has the same
   specificity, so it won last on source order alone. The photograph collapsed
   to its intrinsic 16:9 — a 225px strip across the top of a 1045px masthead,
   with bare background under the rest of the text. It looked like a broken
   image on a phone and like nothing at all on a desktop, where the masthead
   happens to be close to 16:9. */
.mast__bg .ph img,
.mast__bg img,
.mast__vid { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.mast__bg .ph,
.mast__bg picture { position: absolute; inset: 0; display: block; }
.mast__bg .ph img,
.mast__bg img { object-position: 62% 46%; }

/* The still swaps between the kitchen photograph and a frame of the clip at
   the same 900px breakpoint the video itself uses, so the poster is never a
   preview of something that will not arrive. The swap is done with `media` on
   the <source> elements, not here — CSS would hide one after both had already
   been downloaded. */

/* The clip fades over its own poster frame once it is genuinely playing —
   opacity, so the compositor handles it and nothing reflows. */
.mast__vid {
  opacity: 0;
  transition: opacity 0.7s ease;
  object-position: 50% 50%;
}
.mast__vid[data-playing] { opacity: 1; }

.mast__vidbtn {
  position: absolute;
  /* Clear of the fixed WhatsApp button (56px + 22px inset, over 1100px) and of
     the sticky mobile dock below that. It is the control WCAG 2.2.2 requires
     for the looping video, so it cannot sit under something else — the same
     collision the reviews rail had. */
  right: max(1rem, env(safe-area-inset-right));
  bottom: calc(1rem + env(safe-area-inset-bottom));
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  min-height: 32px;
  padding: 0.3rem 0.75rem 0.3rem 0.6rem;
  border: 1px solid var(--line-2);
  border-radius: 100px;
  background: rgba(7, 9, 10, 0.72);
  color: var(--fg);
  font-family: var(--mono);
  font-size: var(--t-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
  backdrop-filter: blur(6px);
}
.mast__vidbtn:hover { border-color: var(--cy); color: var(--cy); }
.mast__vidbtn svg { width: 13px; height: 13px; fill: currentColor; }
.mast__vidbtn .i-play { display: none; }
.mast__vidbtn[data-paused] .i-pause { display: none; }
.mast__vidbtn[data-paused] .i-play { display: block; }
.mast--photo::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  /* Decorative only — without this it swallows clicks meant for the pause
     control sitting above it. */
  pointer-events: none;
  background:
    linear-gradient(97deg,
      rgba(7, 9, 10, 0.48) 0%,
      rgba(7, 9, 10, 0.44) 58%,
      rgba(7, 9, 10, 0.30) 78%,
      rgba(7, 9, 10, 0.16) 100%),
    linear-gradient(to bottom, rgba(7, 9, 10, 0.26) 0%, rgba(7, 9, 10, 0) 38%, rgba(7, 9, 10, 0.24) 100%);
}
/* Below 900px the columns stack, so text crosses the whole frame and a
   horizontal gradient is no use. This one runs vertically instead, matched to
   where the type actually lands: the headline occupies roughly the top 15% and
   is 35px bold white, which needs only 3:1, so the veil stays light enough
   there to show the kitchen. Everything below is body-sized and includes the
   weakest muted token, so the veil goes to 0.90 before the lede starts.
   Measured against the visible slice of the photograph, whose brightest pixels
   are effectively white: 0.58 gives the headline 4.7:1, and 0.90 gives the
   small muted line 6.5:1. */
@media (max-width: 899px) {
  .mast--photo::after {
    background: linear-gradient(to bottom,
      rgba(7, 9, 10, 0.29) 0%,
      rgba(7, 9, 10, 0.31) 14%,
      rgba(7, 9, 10, 0.48) 19%,
      rgba(7, 9, 10, 0.52) 100%);
  }
  .mast__bg .ph img,
  .mast__bg img { object-position: 58% 42%; }
}
.mast--photo h1 { max-width: 17ch; }
/* Over a photograph the weakest muted token has no headroom left to give, so
   the masthead promotes its small print a step. Everything here is a large
   surface of body colour on a controlled background, not a subtle hierarchy
   cue, and legibility outranks the tonal step-down. */
.mast--photo .lede { color: var(--fg); }
.mast--photo .small.muted { color: #cfd8da; }

/* ---------- Photographs ---------- */
.ph { display: block; }
.ph img { display: block; max-width: 100%; height: auto; }
.mast__kick {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 1.4rem;
  font-family: var(--mono);
  font-size: var(--t-xs);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted-2);
  padding-bottom: 0.85rem;
  margin-bottom: clamp(1rem, 2vw, 1.5rem);
  border-bottom: 1px solid var(--line);
}
.mast__kick b { color: var(--cy); font-weight: 500; }
.mast h1 {
  font-size: clamp(2.2rem, 1.05rem + 4.1vw, 4.4rem);
  line-height: 0.9;
  letter-spacing: -0.045em;
  text-transform: uppercase;
  text-wrap: balance;
  max-width: 20ch;
}
.mast h1 em { font-style: normal; color: var(--cy); }
.mast h1 .thin { font-weight: 300; letter-spacing: -0.03em; }
.mast__crown { display: inline-block; width: 0.92em; color: var(--cy); vertical-align: -0.06em; margin-right: 0.12em; }
.mast__sub {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1.2rem;
  margin-top: clamp(1.1rem, 2.1vw, 1.7rem);
  padding-top: 1.1rem;
  border-top: 1px solid var(--line);
}
@media (min-width: 900px) {
  .mast__sub { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); gap: 2.4rem; align-items: start; }
}
/* ---------- Surfaces, not grids ----------
   These blocks used to be built the same way a table is: a 1px gap over a
   line-coloured background, plus an outer border, so every card sat in a ruled
   cell. Six components doing that gave the whole site a drawn-grid look. They
   are now separated by real space over the page background, with each card
   carrying its own quiet surface. Same structure, no ruling. */
/* Auto-columns rather than a hard repeat(3): the strip used to be three
   figures and is now two, and a fixed three-column grid left the removed one
   as a third of empty masthead. */
.mast__facts { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 0.5rem; }
.mast__facts div { background: var(--ink-2); padding: 0.85rem 0.9rem; border-radius: var(--r-lg); }
.mast__facts dt { font-family: var(--mono); font-size: var(--t-xs); letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted-2); }
.mast__facts dd { font-family: var(--display); font-weight: 700; font-size: clamp(1.05rem, 2.4vw, 1.5rem); letter-spacing: -0.03em; margin: 0.15rem 0 0; }
.mast__facts dd small { font-size: 0.62em; color: var(--muted); font-weight: 500; letter-spacing: 0; }

/* ---------- Bento dispatch grid ---------- */
.bento {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5rem;
}
@media (min-width: 620px) { .bento { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.tile {
  background: var(--ink-2);
  border-radius: var(--r-lg);
  padding: 1.2rem 1.15rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  min-height: 128px;
  position: relative;
  text-decoration: none;
  color: inherit;
  transition: background 0.2s ease;
}
a.tile:hover { background: var(--ink-4); }
.tile__lbl { font-family: var(--mono); font-size: var(--t-xs); letter-spacing: 0.05em; text-transform: uppercase; color: var(--muted-2); }
.tile__big { font-family: var(--display); font-weight: 800; font-size: clamp(1.45rem, 2.3vw, 2rem); line-height: 0.95; letter-spacing: -0.04em; }
.tile__big small { font-size: 0.55em; letter-spacing: 0; color: var(--muted); font-weight: 500; display: block; margin-top: .25rem; }
.tile p { font-size: var(--t-sm); color: var(--muted); margin-top: auto; }
.tile--span2 { grid-column: span 2; }
.tile--full { grid-column: 1 / -1; }
.tile--cy { background: var(--cy); color: #04191b; }
.tile--cy .tile__lbl { color: rgba(4, 25, 27, 0.85); }
.tile--cy p { color: rgba(4, 25, 27, 0.85); }
.tile--cy .tile__big,
.tile--cy .tile__big small { color: #04191b; }
.tile--wa { background: #16241c; }
.tile--wa .tile__lbl { color: var(--muted); }
.tile--book { background: linear-gradient(155deg, var(--ink-3), var(--ink-2)); }

/* Booking widget inside bento. No longer a form — booking is ServiceM8's
   hosted diary, which cannot be pre-filled from a URL, so the two dropdowns
   that used to sit here were asking questions for nothing. */
.dispatch { display: grid; gap: 0.55rem; }
.dispatch label { font-family: var(--mono); font-size: var(--t-xs); letter-spacing: 0.07em; text-transform: uppercase; color: var(--muted-2); }
.dispatch__lede { font-size: var(--t-sm); color: var(--muted); line-height: 1.55; margin-bottom: 0.2rem; }
.dispatch__or {
  font-family: var(--mono); font-size: var(--t-xs); letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--muted-2); text-align: center; margin: 0;
}
.field {
  width: 100%;
  background: var(--ink);
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  padding: 0.7rem 0.75rem;
  font-size: var(--t-sm);
  color: var(--paper);
  appearance: none;
}
select.field {
  background-image: linear-gradient(45deg, transparent 50%, var(--cy) 50%), linear-gradient(135deg, var(--cy) 50%, transparent 50%);
  background-position: calc(100% - 17px) calc(50% + 1px), calc(100% - 12px) calc(50% + 1px);
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 2rem;
}
.field:focus { border-color: var(--cy); outline: none; box-shadow: 0 0 0 3px var(--cy-glow); }
.field::placeholder { color: var(--muted-2); }
textarea.field { min-height: 120px; resize: vertical; line-height: 1.5; }

/* ---------- Marquee ticker ---------- */
.ticker {
  border-block: 1px solid var(--line);
  background: var(--ink-2);
  overflow: hidden;
  padding-block: 0.75rem;
  --dur: 46s;
}
.ticker__track { display: flex; width: max-content; animation: slide var(--dur) linear infinite; }
.ticker:hover .ticker__track { animation-play-state: paused; }
.ticker__grp { display: flex; align-items: center; gap: 2.2rem; padding-right: 2.2rem; }
.ticker span {
  font-family: var(--mono);
  font-size: var(--t-sm);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
}
.ticker span::before { content: ''; width: 5px; height: 5px; background: var(--cy); transform: rotate(45deg); flex: none; }
@keyframes slide { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* ---------- Specialist badge ---------- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.35rem 0.85rem 0.35rem 0.6rem;
  border: 1px solid rgba(10, 172, 186, 0.45);
  border-radius: 100px;
  background: rgba(10, 172, 186, 0.1);
  font-family: var(--mono);
  font-size: var(--t-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--cy);
}
.badge svg { width: 15px; flex: none; }

/* ---------- Full-bleed picture band ----------
   Edge to edge on purpose. The text column is padded to line up with .wrap so
   the type still sits on the page grid even though the photo does not. */
.pband {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: center;
  border-block: 1px solid var(--line);
  background: var(--ink-2);
}
.pband__ph { min-width: 0; }
.pband__ph img { width: 100%; height: 100%; object-fit: cover; aspect-ratio: 16 / 10; }
.pband__txt { padding: clamp(1.6rem, 4vw, 3.6rem) var(--gut); min-width: 0; }
.pband__txt h2 { text-wrap: balance; }
@media (min-width: 900px) {
  .pband { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .pband__ph img { aspect-ratio: 4 / 3; max-height: 30rem; }
  .pband--flip .pband__ph { order: 2; }
  .pband--flip .pband__txt { order: 1; }
  .pband__txt { padding-inline: clamp(2rem, 5vw, 4.5rem); }
}

/* ---------- Job diary ----------
   Column count follows the number of cards rather than the viewport alone. A
   new site has one write-up in it, and a lone card in a fixed three-column
   grid leaves two empty cells that read as a loading error. */
.jobs { display: grid; gap: 0.6rem; }
@media (min-width: 700px) {
  .jobs:has(.job:nth-child(2)) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1000px) {
  .jobs:has(.job:nth-child(3)) { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.job {
  background: var(--ink-2);
  border-radius: var(--r-lg);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
  min-width: 0;
  transition: background 0.18s ease;
}
.job:hover { background: var(--ink-3); }
.job__thumb { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block; }
.job__in { padding: 1.15rem 1.1rem 1.25rem; display: flex; flex-direction: column; gap: 0.32rem; flex: 1; }
.job__date { font-family: var(--mono); font-size: var(--t-xs); letter-spacing: 0.04em; text-transform: uppercase; color: var(--cy); }
.job__t { font-family: var(--display); font-weight: 700; font-size: var(--t-lg); letter-spacing: -0.025em; line-height: 1.16; }
.job__d { font-size: var(--t-sm); color: var(--muted); }
.job__tag { margin-top: auto; padding-top: 0.7rem; font-family: var(--mono); font-size: var(--t-xs); text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted-2); }

/* Photographs the owner uploads are whatever came off his phone. The cap is
   what stops a portrait shot filling three screens on mobile — contain rather
   than cover, because cropping someone's evidence photo is worse than
   letterboxing it. */
.jobimg { margin: 1.6rem 0 0; }
.jobimg img {
  width: 100%;
  height: auto;
  max-height: 62vh;
  object-fit: contain;
  background: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: var(--r);
}
.jobimg figcaption { font-size: var(--t-sm); color: var(--muted-2); margin-top: 0.5rem; }

/* ---------- Local appliance blocks (town pages) ----------
   These replaced 300 thin area-x-appliance pages. Two columns on desktop so
   six paragraphs read as a scannable set rather than a wall; one column below
   900px, where stacking is the only honest option. */
.lcls {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 26rem), 1fr));
  gap: 1.6rem 2.4rem;
}
.lcl {
  border-top: 2px solid var(--cy);
  padding-top: 1.1rem;
  min-width: 0;
}
.lcl__t {
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--t-xl);
  letter-spacing: -0.025em;
  line-height: 1.15;
  margin-bottom: 0.55rem;
}
.lcl p { color: var(--muted); max-width: 62ch; }
.lcl__ft {
  margin-top: 0.9rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.4rem;
  font-size: var(--t-sm);
}

/* ---------- Index table (appliance / catalogue listing) ---------- */
.idx { border-top: 1px solid var(--line-2); }
.idx__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.6rem 1.2rem;
  align-items: center;
  padding: 1.05rem 0.6rem;
  border-bottom: 1px solid var(--line);
  text-decoration: none;
  color: inherit;
  transition: background 0.18s ease, padding 0.18s ease;
}
.idx__row:hover { background: rgba(10, 172, 186, 0.06); padding-inline: 1rem 0.4rem; }
.idx__no { font-family: var(--mono); font-size: var(--t-xs); color: var(--cy); letter-spacing: 0.04em; }
.idx__ttl { display: block; font-family: var(--display); font-weight: 700; font-size: var(--t-lg); letter-spacing: -0.025em; line-height: 1.2; }
.idx__desc { display: block; font-size: var(--t-sm); color: var(--muted); margin-top: 0.2rem; max-width: 78ch; }
.idx__meta { font-family: var(--mono); font-size: var(--t-xs); color: var(--muted-2); text-transform: uppercase; letter-spacing: 0.04em; white-space: nowrap; }
/* Rows that still carry a real marker — an error code or a postcode, never a
   sequence number — get the extra leading column. */
.idx__row:has(.idx__no) { grid-template-columns: auto minmax(0, 1fr) auto; }
@media (max-width: 620px) {
  .idx__row { grid-template-columns: minmax(0, 1fr); }
  .idx__row:has(.idx__no) { grid-template-columns: auto minmax(0, 1fr); }
  .idx__meta { grid-column: 1 / -1; white-space: normal; color: var(--cy); }
  .idx__row:hover { padding-inline: 0.6rem; }
}
.idx__row:hover .idx__meta { color: var(--cy); }

/* ---------- Card grids ---------- */
.grid { display: grid; gap: 0.6rem; }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 270px), 1fr)); }
.grid--4 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 210px), 1fr)); }
.grid--3f { grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); }
@media (min-width: 1000px) { .grid--3f { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.card {
  background: var(--ink-2);
  border-radius: var(--r-lg);
  padding: 1.35rem 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  text-decoration: none;
  color: inherit;
  transition: background 0.18s ease;
}
a.card:hover { background: var(--ink-4); }
a.card:hover .card__t { color: var(--cy); }
.card__n { font-family: var(--mono); font-size: var(--t-xs); color: var(--cy); letter-spacing: 0.07em; }
.card__t { font-family: var(--display); font-weight: 700; font-size: var(--t-lg); letter-spacing: -0.025em; line-height: 1.15; }
.card p { font-size: var(--t-sm); color: var(--muted); }
.card__ft { margin-top: auto; padding-top: 0.7rem; font-family: var(--mono); font-size: var(--t-xs); text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted-2); }

/* Chip list (towns, brands, tags) */
.chips { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.48rem 0.8rem;
  min-height: 30px;
  border: 1px solid var(--line-2);
  border-radius: 100px;
  font-family: var(--mono);
  font-size: var(--t-xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  text-decoration: none;
  background: var(--ink-2);
}
.chip:hover { border-color: var(--cy); color: var(--cy); background: rgba(10, 172, 186, 0.07); }
.chip--on { border-color: var(--cy); color: var(--cy); }

/* ---------- Reviews carousel ---------- */
/* ---------- Reviews ----------
   One component, two jobs. The rail under the masthead revolves; the grid on
   /reviews/ does not. Both are built from the same card, so a change to the
   card is a change in both places and there is nothing to keep in step.

   On a phone the rail is a scroll-snapped swipe rather than a stack: the
   reviews are the first thing under the hero and they must not push the
   actual services off the screen, so one card shows at a time and the whole
   band costs about a third of a viewport. */
.revs { position: relative; }
.revs__vp { overflow: hidden; }
.revs__track { display: flex; gap: 0.6rem; transition: transform 0.55s cubic-bezier(0.4, 0, 0.2, 1); }
/* Cards sat on --ink-2 inside a band that was also --ink-2, so there was no
   card — just floating text in a dark strip. They now have their own lighter
   surface, a hairline, and a 3px edge in the colour of the platform the
   review came from, which is what makes a row of them read as reviews from
   somewhere rather than as three paragraphs. */
.rev {
  --rev-accent: var(--cy);
  flex: 0 0 100%;
  position: relative;
  overflow: hidden;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.045), rgba(255, 255, 255, 0) 42%),
    var(--ink-3);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 1.5rem 1.35rem 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  min-width: 0;
  transition: border-color 0.25s ease, transform 0.25s ease;
}
.rev::before {
  content: '';
  position: absolute;
  inset: 0 0 auto;
  height: 3px;
  background: var(--rev-accent);
}
/* The quotation mark is the cheapest way to make a block of text look like a
   quotation. Decorative, so it is behind everything and out of the tree. */
.rev::after {
  content: '\201C';
  position: absolute;
  right: 0.5rem;
  top: 0.1rem;
  font-family: var(--display);
  font-size: 6rem;
  line-height: 1;
  color: var(--rev-accent);
  opacity: 0.09;
  pointer-events: none;
}
.rev:hover { border-color: var(--rev-accent); }
.rev--tp { --rev-accent: #00b67a; }
.rev--g { --rev-accent: #4285f4; }
.rev--fb { --rev-accent: #4267b2; }
@media (prefers-reduced-motion: no-preference) {
  .revgrid .rev:hover { transform: translateY(-2px); }
}
@media (min-width: 760px) { .rev { flex-basis: calc(50% - 0.3rem); } }
@media (min-width: 1120px) { .rev { flex-basis: calc(33.3333% - 0.4rem); } }

/* Gold, not brand cyan. Five cyan stars read as a UI element; five gold ones
   read as a rating, because every review platform on earth uses gold. */
.rev__stars { display: flex; gap: 2px; color: #ffc53d; }
.rev__stars svg { width: 16px; height: 16px; filter: drop-shadow(0 1px 3px rgba(255, 197, 61, 0.28)); }
.rev__stars .off { opacity: 0.22; filter: none; }
.rev__q { font-size: var(--t-base); line-height: 1.6; color: #dfe6e7; }
.rev__q::before { content: '\201C'; }
.rev__q::after { content: '\201D'; }
.rev__by { margin-top: auto; display: flex; flex-wrap: wrap; align-items: center; gap: 0.2rem 0.6rem; }
.rev__who { font-family: var(--display); font-weight: 700; letter-spacing: -0.015em; color: var(--paper); }
.rev__meta { font-family: var(--mono); font-size: var(--t-xs); text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted-2); }
.rev__ft { display: flex; align-items: center; justify-content: space-between; gap: 0.8rem; }
.rev__src {
  display: inline-flex; align-items: center; gap: 0.35rem;
  font-family: var(--mono); font-size: var(--t-xs);
  text-transform: uppercase; letter-spacing: 0.06em;
  padding: 0.26rem 0.6rem;
  border-radius: 100px;
  color: var(--cy);
  background: rgba(10, 172, 186, 0.12);
  border: 1px solid transparent;
}
.rev__src svg { width: 13px; height: 13px; flex: none; }
.rev__src--tp { color: #2fd39b; background: rgba(0, 182, 122, 0.14); border-color: rgba(0, 182, 122, 0.3); }
.rev__src--tp svg { fill: #00b67a; }
.rev__src--g { color: #a8c7ff; background: rgba(66, 133, 244, 0.14); border-color: rgba(66, 133, 244, 0.32); }
.rev__src--fb { color: #9db4e8; background: rgba(66, 103, 178, 0.16); border-color: rgba(66, 103, 178, 0.34); }
.rev__srcl { text-decoration: none; }
.rev__srcl:hover .rev__src { filter: brightness(1.18); }
.rev__when { font-family: var(--mono); font-size: var(--t-xs); color: var(--muted-2); text-transform: uppercase; letter-spacing: 0.04em; }

/* An initial in a coloured disc. Six tones, picked from the name so the same
   person keeps the same colour and a rail of twelve is a spread rather than
   twelve of one. Cheaper and more honest than a stock avatar, and Google's
   own profile photos cannot be cached anyway. */
.rev__av {
  width: 32px; height: 32px; border-radius: 50%; flex: none;
  display: grid; place-items: center;
  font-family: var(--display); font-weight: 700; font-size: 0.9rem;
  letter-spacing: -0.02em;
  color: #07090a;
  margin-right: 0.15rem;
}
.rev__av--0 { background: linear-gradient(140deg, #22d3e4, #0aacba); }
.rev__av--1 { background: linear-gradient(140deg, #ffd166, #f0a92e); }
.rev__av--2 { background: linear-gradient(140deg, #6ee7a8, #22b573); }
.rev__av--3 { background: linear-gradient(140deg, #a5b4fc, #6f7ff0); }
.rev__av--4 { background: linear-gradient(140deg, #fca5a5, #ef6f6f); }
.rev__av--5 { background: linear-gradient(140deg, #f0abfc, #c46ee0); }

/* The band that replaced the scrolling town names under the masthead.
   A flat --ink-2 strip directly under a photographic masthead read as a gap in
   the page. It now carries a cyan hairline at the top, a soft brand wash from
   the left and a warm one from the right, so the section has a colour of its
   own without competing with the hero above it. */
.revband {
  position: relative;
  border-block: 1px solid var(--line);
  padding-block: clamp(1.5rem, 3vw, 2.4rem);
  background:
    radial-gradient(120% 130% at 4% 0%, rgba(10, 172, 186, 0.16), transparent 58%),
    radial-gradient(90% 120% at 98% 100%, rgba(255, 197, 61, 0.09), transparent 62%),
    var(--ink-2);
}
.revband::before {
  content: '';
  position: absolute;
  inset: 0 0 auto;
  height: 2px;
  background: linear-gradient(90deg, var(--cy) 0%, rgba(10, 172, 186, 0.35) 34%, transparent 72%);
}
.revband .eyebrow { color: var(--cy); }
.revband__hd { display: flex; align-items: baseline; gap: 0.6rem 1.2rem; flex-wrap: wrap; margin-bottom: 1rem; }
.revband__lead { font-family: var(--display); font-weight: 700; font-size: var(--t-lg); letter-spacing: -0.02em; flex: 1; min-width: 12rem; }
.revband .revs__ctl { margin-top: 0.8rem; }

/* Fixed-height cards in the rail so the band does not jump as it advances
   through reviews of different lengths. Long ones clamp; the full text is on
   /reviews/, which is what the "All reviews" link is for. */
.revs--rail .rev { min-height: 0; }
.revs--rail .rev__q {
  display: -webkit-box;
  -webkit-line-clamp: 5;
  -webkit-box-orient: vertical;
  line-clamp: 5;
  overflow: hidden;
  font-size: var(--t-sm);
}

/* Below 760px the track becomes a real swipeable carousel. Native scrolling
   with snap points beats a JS transform on touch: it is momentum-correct, it
   works before the script loads, and it is what a thumb expects. */
@media (max-width: 759px) {
  .revs--rail .revs__vp { overflow-x: auto; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch; scrollbar-width: none; margin-inline: calc(var(--gut) * -1); padding-inline: var(--gut); }
  .revs--rail .revs__vp::-webkit-scrollbar { display: none; }
  .revs--rail .revs__track { transform: none !important; transition: none; }
  .revs--rail .rev { flex-basis: 84%; scroll-snap-align: start; }
  .revs--rail .rev__q { -webkit-line-clamp: 4; line-clamp: 4; }
  .revband .revs__ctl { display: none; }
}

/* The /reviews/ grid: every review in full, no clamping, no rotation. */
.revgrid { display: grid; gap: 0.6rem; }
@media (min-width: 760px) { .revgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1120px) { .revgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.revs__tp { margin-top: 1rem; font-size: var(--t-sm); }
.revs__tp a { color: var(--muted); text-decoration: none; }
.revs__tp a:hover { color: var(--cy); }
.revs__tp strong { color: var(--paper); }
/* Controls left, progress bar filling the rest.
   They used to sit bottom-right, where the fixed WhatsApp button covered the
   next-review control — and, once the rail gained a pause button, covered the
   one control WCAG 2.2.2 requires to be reachable. Nothing on the right-hand
   end of a section is safe while that float exists. */
.revs__ctl { display: flex; align-items: center; gap: 1rem; margin-top: 1.1rem; }
.revs__btns { display: flex; gap: 0.4rem; flex: none; }
.revs__btn {
  width: 42px; height: 42px;
  display: grid; place-items: center;
  background: var(--ink-2);
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  cursor: pointer;
  color: var(--paper);
}
.revs__btn:hover { border-color: var(--cy); color: var(--cy); }
/* Pause/play. Hidden until the script un-hides it, so a no-JS reader is not
   offered a control over motion that is not happening. The glyph is drawn in
   CSS rather than shipped as two icons: one box becomes two bars, or one
   triangle, with nothing to load. */
.revs__btn--pp .pp { display: block; width: 11px; height: 12px; background: currentColor;
  clip-path: polygon(0 0, 32% 0, 32% 100%, 0 100%, 0 0, 68% 0, 100% 0, 100% 100%, 68% 100%, 68% 0); }
.revs__btn--pp.is-paused .pp { clip-path: polygon(8% 0, 100% 50%, 8% 100%); }
.revs__prog { flex: 1; height: 2px; background: var(--line); position: relative; }
.revs__prog i { position: absolute; inset-block: 0; left: 0; background: var(--cy); transition: width 0.4s ease, left 0.4s ease; }

/* ---------- FAQ / accordions ---------- */
.faq { border-top: 1px solid var(--line-2); }
.faq details { border-bottom: 1px solid var(--line); }
.faq summary {
  list-style: none;
  cursor: pointer;
  padding: 1.15rem 2.4rem 1.15rem 0;
  position: relative;
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--t-lg);
  letter-spacing: -0.02em;
  line-height: 1.3;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: '+';
  position: absolute;
  right: 0.35rem;
  top: 1rem;
  font-family: var(--mono);
  color: var(--cy);
  font-size: 1.4rem;
  line-height: 1;
}
.faq details[open] summary::after { content: '–'; }
.faq details[open] summary { color: var(--cy); }
.faq__a { padding: 0 2.4rem 1.35rem 0; color: var(--muted); }
.faq__a > * + * { margin-top: 0.8rem; }
.faq__a strong { color: var(--paper); }

/* ---------- Callout / notice ---------- */
.note {
  min-width: 0;
  border: 1px solid var(--line-2);
  border-left: 3px solid var(--cy);
  background: var(--ink-2);
  padding: 1.1rem 1.2rem;
  border-radius: var(--r);
}
.note--warn { border-left-color: var(--warn); }
.note--soon { border-left-color: var(--cy); background: rgba(10, 172, 186, 0.07); }
.note__t { font-family: var(--mono); font-size: var(--t-xs); letter-spacing: 0.05em; text-transform: uppercase; color: var(--cy); margin-bottom: 0.4rem; }
.note--warn .note__t { color: var(--warn); }

/* Stat strip */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%,140px), 1fr)); gap: 0.5rem; }
.stat { background: var(--ink-2); padding: 1.3rem 1.1rem; border-radius: var(--r-lg); }
.stat b { display: block; font-family: var(--display); font-weight: 800; font-size: clamp(1.7rem, 3.6vw, 2.6rem); letter-spacing: -0.04em; line-height: 1; color: var(--cy); }
.stat span { display: block; margin-top: 0.4rem; font-family: var(--mono); font-size: var(--t-xs); letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); }

/* Split feature.
   The explicit minmax(0, 1fr) matters: an implicit `auto` track is sized by its
   widest child's min-content, so a wide table would drag the whole page sideways
   on a narrow screen instead of scrolling inside its own wrapper. */
.split { display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(1.6rem, 4vw, 3.5rem); align-items: start; }
.split > * { min-width: 0; }
.prose { min-width: 0; }
@media (min-width: 900px) { .split { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
@media (min-width: 900px) { .split--wide { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); } }
/* /book/ — the embedded diary is a real form with an address lookup and a
   calendar in it, so it gets the room. The sidebar only holds a phone number
   and four short lines and does not need half the page. */
@media (min-width: 900px) { .split--book { grid-template-columns: minmax(0, 1fr) 20rem; } }

/* Steps */
.steps { display: grid; gap: 0.6rem; }
@media (min-width: 760px) { .steps { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.step { background: var(--ink-2); padding: 1.4rem 1.2rem; border-radius: var(--r-lg); }
.step h3 { font-size: var(--t-base); margin-bottom: 0.35rem; }
.step p { font-size: var(--t-sm); color: var(--muted); }

/* Tables scroll horizontally on narrow screens instead of stretching the page.
   The wrapper is focusable so a keyboard user can reach the scroll (WCAG 2.1.1). */
.tbl {
  max-width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  scrollbar-color: var(--line-3) transparent;
}
.tbl:focus-visible { outline: 2px solid var(--cy-bright); outline-offset: 3px; }
.tbl::-webkit-scrollbar { height: 8px; }
.tbl::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 8px; }
.tbl table { margin: 0; }
/* On phones, give the columns room and let the user swipe, rather than
   squeezing "£75–£280" onto two lines. The classic scroll-shadow pair below
   signals there is more to the right and fades out once you reach the end. */
@media (max-width: 700px) {
  .tbl--wide table { min-width: 30rem; }
  .tbl {
    background:
      linear-gradient(to right, var(--ink) 40%, rgba(7, 9, 10, 0)) left center / 28px 100% no-repeat local,
      linear-gradient(to left, var(--ink) 40%, rgba(7, 9, 10, 0)) right center / 28px 100% no-repeat local,
      linear-gradient(to right, rgba(10, 172, 186, 0.35), rgba(10, 172, 186, 0)) left center / 14px 100% no-repeat scroll,
      linear-gradient(to left, rgba(10, 172, 186, 0.35), rgba(10, 172, 186, 0)) right center / 14px 100% no-repeat scroll;
  }
}
.prose > .tbl + * { margin-top: 1.05em; }

/* Price table */
.ptab { width: 100%; border-collapse: collapse; }
.ptab th, .ptab td { padding: 0.9rem 0.9rem; text-align: left; border: 0; font-size: var(--t-sm); }
.ptab thead th { font-family: var(--mono); font-size: var(--t-xs); letter-spacing: 0.07em; text-transform: uppercase; color: var(--cy); font-weight: 500; padding-bottom: 0.5rem; }
.ptab td:last-child, .ptab th:last-child { text-align: right; font-family: var(--mono); white-space: nowrap; }
.ptab tbody tr:nth-child(odd) { background: rgba(255, 255, 255, 0.022); }
.ptab tbody tr:hover { background: rgba(10, 172, 186, 0.06); }
.ptab .p { color: var(--cy); font-weight: 600; }

/* Plans */
/* ---------- Per-appliance price box ----------
   Every price this appliance can be charged, stated. "From £90" on its own is
   a gap: it tells someone with an integrated dishwasher the price starts at
   £90 and lets them find out on the day that it is £100. */
.pricebox {
  margin: 1.6rem 0;
  background:
    radial-gradient(120% 130% at 0% 0%, rgba(10, 172, 186, 0.13), transparent 60%),
    var(--ink-2);
  border: 1px solid var(--line);
  border-left: 3px solid var(--cy);
  border-radius: var(--r-lg);
  padding: 1.3rem 1.3rem 1.1rem;
}
.pricebox__hd {
  font-family: var(--mono); font-size: var(--t-xs); font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--cy);
  margin-bottom: 0.7rem;
}
.pricebox .ptab { margin-top: 0; }
.pricebox .ptab td { padding: 0.5rem 0; }
.pricebox .ptab td.p { font-family: var(--display); font-weight: 700; letter-spacing: -0.02em; color: var(--paper); white-space: nowrap; }
.pricebox p { max-width: none; }

/* ---------- Home page price bands ----------
   Four rows rather than a paragraph. The oven exception was a subordinate
   clause in the middle of a sentence and nobody with a built-in oven was going
   to find it there; it now has its own row and its own accent. */
.bands { display: grid; gap: 0; border-top: 1px solid var(--line); }
.bands > div {
  display: grid; grid-template-columns: 4.2rem minmax(0, 1fr);
  gap: 0.9rem; align-items: baseline;
  padding: 0.7rem 0; border-bottom: 1px solid var(--line);
}
.bands dt {
  font-family: var(--display); font-weight: 800; font-size: var(--t-lg);
  letter-spacing: -0.035em; color: var(--paper);
}
.bands dd { margin: 0; font-size: var(--t-sm); color: var(--muted); line-height: 1.5; }
.bands dd strong { color: var(--paper); }
/* The oven row is the exception people miss, so it is emphasised — but the
   price itself stays white like every other price. A £90 in a different
   colour to the other £90 above it reads as a different kind of number. */
.bands--flag { position: relative; }
.bands--flag dt { color: var(--paper); }
.bands--flag dd { color: #cfd8da; }

/* ---------- Instagram strip ----------
   Square crops in a scrolling row on a phone, a grid above that. Photos only,
   so every tile is a real image rather than a video placeholder. */
.ig__hd { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.1rem; }
.ig__lead { font-family: var(--display); font-weight: 700; font-size: var(--t-lg); letter-spacing: -0.02em; }
.ig { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.5rem; }
@media (min-width: 620px) { .ig { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 900px) { .ig { grid-template-columns: repeat(6, minmax(0, 1fr)); } }
.ig__it {
  display: block; position: relative; aspect-ratio: 1 / 1; overflow: hidden;
  border-radius: var(--r-lg); background: var(--ink-3); border: 1px solid var(--line);
}
.ig__it img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 0.4s ease, opacity 0.3s ease; }
.ig__it:hover img { transform: scale(1.05); }
@media (prefers-reduced-motion: reduce) { .ig__it img { transition: none; } .ig__it:hover img { transform: none; } }

/* ---------- The single plan ----------
   Three tiers were three cards in a row and the price was the thing being
   compared. One plan has nothing to compare against, so the layout stops being
   a price table and becomes what it actually is: a price, and what you get.
   Price block left where the eye lands first, the list beside it. */
.plan1 { display: grid; gap: 0.7rem; align-items: start; }
@media (min-width: 860px) { .plan1 { grid-template-columns: minmax(0, 20rem) minmax(0, 1fr); gap: 1rem; } }
.plan1__buy {
  position: relative;
  background:
    radial-gradient(120% 120% at 0% 0%, rgba(10, 172, 186, 0.16), transparent 62%),
    var(--ink-3);
  border: 1px solid var(--line);
  border-top: 3px solid var(--cy);
  border-radius: var(--r-lg);
  padding: 1.5rem 1.35rem;
}
.plan1__amt {
  font-family: var(--display); font-weight: 800;
  font-size: clamp(2.4rem, 5.4vw, 3.4rem);
  letter-spacing: -0.05em; line-height: 1; color: var(--paper);
}
.plan1__amt small { font-size: 0.3em; font-weight: 500; color: var(--muted); letter-spacing: 0; }
.plan1__alt { margin-top: 0.55rem; font-size: var(--t-sm); color: var(--muted); }
.plan1__save {
  margin-top: 0.35rem; font-family: var(--mono); font-size: var(--t-xs);
  text-transform: uppercase; letter-spacing: 0.06em; color: #6ee7a8;
}
.plan1__buy .btn { margin-top: 1.1rem; }
.plan1__what {
  background: var(--ink-2); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 1.5rem 1.35rem;
}
.plan1__what h3 { font-family: var(--display); font-size: var(--t-xl); letter-spacing: -0.03em; }
.plan1__what .lede { margin-top: 0.3rem; }
.plan1__hero {
  margin-top: 1rem; padding: 0.85rem 1rem;
  background: rgba(10, 172, 186, 0.1);
  border-left: 2px solid var(--cy); border-radius: 2px;
  font-size: var(--t-sm);
}
.plan1 .plan__soon { position: static; display: inline-block; margin-bottom: 0.8rem; }

/* Shared tick list — also used by the multi-property block. */
.ticks { list-style: none; padding: 0; display: grid; gap: 0.5rem; font-size: var(--t-sm); margin-top: 1rem; }
.ticks li { display: grid; grid-template-columns: 16px 1fr; gap: 0.6rem; align-items: start; color: #cfd8da; }
.ticks li::before {
  content: ''; width: 11px; height: 6px; margin-top: 0.55em;
  border-left: 2px solid var(--cy); border-bottom: 2px solid var(--cy);
  transform: rotate(-45deg);
}
.ticks li.no { color: var(--muted-2); }
.ticks li.no::before {
  width: 10px; height: 10px; margin-top: 0.45em;
  border: 0; transform: none; background: currentColor;
  clip-path: polygon(20% 0, 50% 30%, 80% 0, 100% 20%, 70% 50%, 100% 80%, 80% 100%, 50% 70%, 20% 100%, 0 80%, 30% 50%, 0 20%);
  opacity: 0.6;
}

/* Add-on and multi-property blocks under the plan. */
.pextras { display: grid; gap: 0.7rem; }
@media (min-width: 760px) { .pextras { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.pextra {
  background: var(--ink-2); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 1.3rem 1.2rem;
}
.pextra h3 { font-family: var(--display); font-size: var(--t-lg); letter-spacing: -0.02em; }
.pextra__p { color: var(--cy); white-space: nowrap; }
.pextra p { margin-top: 0.5rem; font-size: var(--t-sm); color: var(--muted); }
.pextra .ticks { margin-top: 0.7rem; }

.plans { display: grid; gap: 0.6rem; }
@media (min-width: 860px) { .plans { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.plan { background: var(--ink-2); padding: 1.6rem 1.4rem; display: flex; flex-direction: column; gap: 0.9rem; border-radius: var(--r-lg); position: relative; }
/* Marks a plan that has not launched. Deliberately sits above the price, so
   nobody reads the figure before they read that it is not yet buyable. */
.plan__soon {
  align-self: start;
  font-family: var(--mono);
  font-size: var(--t-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--cy);
  background: rgba(10, 172, 186, 0.12);
  padding: 0.24rem 0.6rem;
}
.plan--hero { background: var(--ink-3); box-shadow: inset 0 2px 0 var(--cy); }
.plan__price { font-family: var(--display); font-weight: 800; font-size: clamp(2rem, 4.4vw, 3rem); letter-spacing: -0.045em; line-height: 1; }
.plan__price small { font-size: 0.34em; font-weight: 500; color: var(--muted); letter-spacing: 0; }
.plan ul { list-style: none; padding: 0; display: grid; gap: 0.55rem; font-size: var(--t-sm); }
.plan li { display: grid; grid-template-columns: 16px 1fr; gap: 0.6rem; align-items: start; color: #cfd8da; }
.plan li::before {
  content: '';
  width: 11px; height: 6px;
  margin-top: 0.55em;
  border-left: 2px solid var(--cy);
  border-bottom: 2px solid var(--cy);
  transform: rotate(-45deg);
}
.plan li.no { color: var(--muted-2); }
.plan li.no::before {
  width: 10px; height: 10px;
  margin-top: 0.45em;
  border: 0;
  transform: none;
  background: currentColor;
  color: var(--muted-2);
  -webkit-mask: var(--x-svg) no-repeat center / contain;
  mask: var(--x-svg) no-repeat center / contain;
}

/* Breadcrumbs */
.crumbs { font-family: var(--mono); font-size: var(--t-xs); letter-spacing: 0.05em; text-transform: uppercase; color: var(--muted); padding-block: 1rem; border-bottom: 1px solid var(--line); }
.crumbs ol { list-style: none; padding: 0; display: flex; flex-wrap: wrap; gap: 0.5rem; }
.crumbs li + li::before { content: '/'; margin-right: 0.5rem; color: var(--line-3); }
.crumbs a { color: var(--muted); text-decoration: none; display: inline-block; padding: 0.2rem 0; }
.crumbs a:hover { color: var(--cy); }
.crumbs a:hover { color: var(--cy); }

/* Page header for interior pages */
.phd { padding-block: clamp(2.2rem, 5vw, 4rem) clamp(1.6rem, 3vw, 2.4rem); }
.phd h1 { font-size: var(--t-4xl); text-transform: uppercase; line-height: 0.94; max-width: 26ch; }
.phd .lede { margin-top: 1.1rem; }
.phd__act { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 1.6rem; }

/* Table of contents */
.toc { border: 1px solid var(--line); background: var(--ink-2); padding: 1.1rem 1.2rem; border-radius: var(--r); }
.toc__t { font-family: var(--mono); font-size: var(--t-xs); letter-spacing: 0.05em; text-transform: uppercase; color: var(--muted-2); margin-bottom: 0.7rem; }
.toc ol { list-style: none; padding: 0; display: grid; gap: 0.35rem; }
.toc a { display: flex; gap: 0.7rem; font-size: var(--t-sm); text-decoration: none; color: var(--paper); padding: 0.25rem 0; }
.toc a::before { content: ''; width: 5px; height: 5px; background: var(--cy); transform: rotate(45deg); flex: none; margin-top: 0.55em; }
.toc a:hover { color: var(--cy); }

/* Sticky aside */
/* ---------- Booking iframe (/book/) ----------
   ServiceM8's form is responsive and scrolls internally, so it is given a
   fixed tall box rather than being resized to its content: it is cross-origin,
   which means its height is not readable from here and never will be. The
   height is generous enough that the "Select a time" button is reachable
   without the customer hunting for an inner scrollbar. White ground because
   the form is a light-themed page and a dark frame around it looks broken. */
.bookframe {
  display: block; width: 100%; min-height: 900px; border: 1px solid var(--line);
  border-radius: var(--r); background: #fff;
}
@media (max-width: 640px) { .bookframe { min-height: 720px; } }

.aside-sticky { position: sticky; top: 96px; display: grid; gap: 1rem; }

/* CTA band */
.band { border-block: 1px solid var(--line); background: linear-gradient(120deg, var(--ink-3), var(--ink-2) 60%); }
.band__in { display: grid; gap: 1.4rem; padding-block: clamp(2.4rem, 5vw, 4rem); align-items: center; }
@media (min-width: 900px) { .band__in { grid-template-columns: minmax(0, 1fr) auto; gap: 2.5rem; } }
.band h2 { font-size: var(--t-2xl); text-transform: uppercase; }
.band__act { display: flex; flex-wrap: wrap; gap: 0.6rem; }

/* Chevron divider derived from the crown mark */
.chev { height: 12px; background-repeat: repeat-x; background-size: 22px 12px; opacity: 0.5;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='12' viewBox='0 0 22 12'%3E%3Cpath d='M0 12 11 0l11 12z' fill='none' stroke='%230aacba' stroke-width='1'/%3E%3C/svg%3E"); }

/* ---------- Forms ---------- */
.form { display: grid; gap: 1rem; }
.form__row { display: grid; gap: 1rem; }
@media (min-width: 640px) { .form__row--2 { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
.form label { display: grid; gap: 0.35rem; font-family: var(--mono); font-size: var(--t-xs); letter-spacing: 0.07em; text-transform: uppercase; color: var(--muted); }
.form .req { color: var(--cy); font-weight: 400; }
.form .lbl { display: block; }
.consent { display: grid; grid-template-columns: 20px 1fr; gap: 0.7rem; align-items: start; font-family: var(--body); font-size: var(--t-sm); text-transform: none; letter-spacing: 0; color: var(--muted); line-height: 1.5; }
.consent input { width: 18px; height: 18px; accent-color: var(--cy); margin-top: 0.15rem; }
.form__msg { display: none; padding: 0.9rem 1rem; border-radius: var(--r); font-size: var(--t-sm); }
.form__msg[data-state='ok'] { display: block; background: rgba(70, 211, 154, 0.1); border: 1px solid rgba(70, 211, 154, 0.4); color: #9ef0cd; }
.form__msg[data-state='err'] { display: block; background: rgba(242, 181, 68, 0.1); border: 1px solid rgba(242, 181, 68, 0.4); color: #ffdca0; }
.hp { position: absolute; left: -9999px; opacity: 0; height: 0; overflow: hidden; }

/* ---------- Footer ---------- */
.ftr { border-top: 1px solid var(--line-2); background: var(--ink-2); margin-top: 0; }
.ftr__top { display: grid; gap: 2.2rem; padding-block: clamp(2.6rem, 5vw, 4.2rem) clamp(1.8rem, 3.5vw, 2.6rem); }
/* Three columns since the coverage list moved out to its own full-width
   band below — a fourth track here would just be an empty gap. */
@media (min-width: 900px) { .ftr__top { grid-template-columns: minmax(0, 1.25fr) repeat(2, minmax(0, 1fr)); gap: 3rem; } }
.ftr__h { font-family: var(--mono); font-weight: 500; font-size: var(--t-xs); letter-spacing: 0.09em; text-transform: uppercase; color: var(--muted-2); margin-bottom: 0.9rem; line-height: 1.4; }
.ftr ul { list-style: none; padding: 0; display: grid; gap: 0.1rem; }
.ftr li a { display: block; padding: 0.32rem 0; }
/* :not(.btn) matters — .ftr a (0,2,0) outranks .btn (0,1,0), which was painting
   the footer's Call and WhatsApp buttons muted grey on their coloured fills. */
.ftr a:not(.btn) { font-size: var(--t-sm); color: var(--muted); text-decoration: none; }
.ftr a:not(.btn):hover { color: var(--cy); }
.ftr__brand svg { width: 46px; color: var(--cy); margin-bottom: 0.8rem; }
.ftr__bot { border-top: 1px solid var(--line); padding-block: 1.2rem; display: flex; flex-wrap: wrap; gap: 0.7rem 1.6rem; justify-content: space-between; font-family: var(--mono); font-size: var(--t-xs); letter-spacing: 0.04em; color: var(--muted-2); }
.ftr__bot a { font-size: inherit; display: inline-block; padding: 0.25rem 0; }
/* ---------- Footer coverage list ----------
   Fifty towns across the full footer width, grouped into the three regions the
   business works in. Chips rather than a list because the eye can scan a
   wrapped row of short labels far faster than a five-column list, and grouping
   means a reader in Hertfordshire never reads the Cambridgeshire names at all.

   Deliberately smaller than the equivalent block used on page bodies: this is
   a footer, so the type steps down and the padding tightens — but not below
   24px of height, which is the WCAG 2.2 target-size floor. */
.ftr__areas { padding-bottom: clamp(2rem, 4vw, 3rem); }
.fa { display: grid; gap: 1.6rem 2.4rem; }
@media (min-width: 760px) { .fa { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.fa__grp { min-width: 0; }
.fa__h {
  font-family: var(--mono);
  font-weight: 500;
  font-size: var(--t-xs);
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--cy);
  margin-bottom: 0.7rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--line);
}
.fa__h span { color: var(--muted-2); }
.fa__chips { display: flex; flex-wrap: wrap; gap: 0.34rem; }
.ftr .fa__chips a:not(.btn) {
  display: inline-flex;
  align-items: center;
  min-height: 26px;
  padding: 0.24rem 0.62rem;
  border: 1px solid var(--line);
  border-radius: 100px;
  font-size: 0.78rem;
  line-height: 1.25;
  color: var(--muted);
  background: var(--ink-3);
  transition: color 0.15s ease, border-color 0.15s ease;
}
.ftr .fa__chips a:not(.btn):hover,
.ftr .fa__chips a:not(.btn):focus-visible {
  color: var(--cy);
  border-color: var(--cy);
}

/* ---------- Sticky mobile action bar ---------- */
.dock {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 70;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  background: var(--line-2);
  border-top: 1px solid var(--line-2);
  padding-bottom: env(safe-area-inset-bottom);
  transform: translateY(110%);
  transition: transform 0.3s ease;
}
.dock[data-show='true'] { transform: translateY(0); }
.dock a {
  background: var(--ink-2);
  padding: 0.75rem 0.4rem;
  display: grid;
  justify-items: center;
  gap: 0.25rem;
  text-decoration: none;
  color: var(--paper);
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.dock a svg { width: 19px; height: 19px; color: var(--cy); }
.dock a.dock--wa svg { color: #38d16f; }
.dock a.dock--book { background: var(--cy); color: #04191b; }
.dock a.dock--book svg { color: #04191b; }
@media (min-width: 1101px) { .dock { display: none; } }
@media (max-width: 1100px) { body { padding-bottom: 4.5rem; } }

/* Floating WhatsApp (desktop) */
.wa-float {
  position: fixed;
  right: 22px; bottom: 22px;
  z-index: 65;
  width: 56px; height: 56px;
  border-radius: 50%;
  background: #1faa53;
  display: grid; place-items: center;
  box-shadow: 0 14px 34px -12px rgba(31, 170, 83, 0.9);
  color: #fff;
}
.wa-float svg { width: 28px; height: 28px; }
.wa-float:hover { transform: scale(1.06); color: #fff; }
@media (max-width: 1100px) { .wa-float { display: none; } }
/* Over 1100px the float exists, so the video control steps left of it. */
@media (min-width: 1101px) { .mast__vidbtn { right: calc(56px + 22px + 1.25rem); } }
/* At or under 1100px the sticky dock takes the bottom of the screen instead. */
@media (max-width: 1100px) { .mast__vidbtn { bottom: calc(4.6rem + env(safe-area-inset-bottom)); } }

/* ---------- Misc helpers ---------- */
.vh { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.skip { position: absolute; left: -9999px; top: 0; background: var(--cy); color: #04191b; padding: 0.7rem 1rem; z-index: 200; font-family: var(--mono); font-size: var(--t-sm); }
.skip:focus { left: 0; }
.mt0 { margin-top: 0 !important; }
.mt1 { margin-top: 1rem; }
.mt2 { margin-top: 2rem; }
.mt3 { margin-top: 3rem; }
.center { text-align: center; }
.flexr { display: flex; flex-wrap: wrap; gap: 0.85rem 0.9rem; align-items: center; }
.rule { height: 1px; background: repeating-linear-gradient(90deg, var(--line-2) 0 6px, transparent 6px 12px); }

/* Reveal on scroll — only ever hidden when JS is confirmed running, so the
   content is always visible to crawlers and to anyone with JS disabled. */
.js [data-rv] { opacity: 0; transform: translateY(14px); transition: opacity 0.6s ease, transform 0.6s cubic-bezier(0.2, 0.7, 0.3, 1); }
.js [data-rv].in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .js [data-rv] { opacity: 1; transform: none; } }

/* Booking embed slot */
.embed-slot { border: 1px dashed var(--line-2); border-radius: var(--r-lg); padding: 1.4rem; background: var(--ink-2); }
.embed-slot iframe { width: 100%; border: 0; min-height: 720px; border-radius: var(--r); }


/* ==========================================================================
   Mobile refinements
   ========================================================================== */

/* iOS Safari zooms the whole page when a focused input's text is under 16px.
   Everything below that threshold gets bumped on small screens. */
@media (max-width: 900px) {
  .field,
  input[type="text"], input[type="tel"], input[type="email"],
  input[type="search"], select, textarea {
    font-size: 16px;
    padding: 0.8rem 0.8rem;
  }
  select.field { background-position: calc(100% - 18px) calc(50% + 1px), calc(100% - 13px) calc(50% + 1px); }
}

/* Comfortable, thumb-sized controls on touch devices. */
@media (hover: none) and (pointer: coarse) {
  .btn { padding: 0.95rem 1.35rem; min-height: 48px; }
  .btn--lg { min-height: 54px; }
  .revs__btn { width: 48px; height: 48px; }
  .faq summary { padding-block: 1.15rem; }
  .acc__btn { padding-block: 1.15rem; }
  .idx__row { padding-block: 1.15rem; }
  .drawer__link { padding-block: 1.15rem; }
  a, button, summary { -webkit-tap-highlight-color: rgba(10, 172, 186, 0.18); }
  /* Hover-only affordances never stick on touch. */
  .idx__row:hover { background: none; padding-inline: 0.6rem; }
  a.card:hover, a.tile:hover { background: var(--ink-2); }
}

@media (max-width: 700px) {
  /* The kicker's last two items repeat the facts strip immediately below it,
     so on phones they only cost vertical space. */
  .mast__kick span:nth-child(n + 3) { display: none; }
  .phd { padding-block: 1.6rem 1.2rem; }
  .phd__act { gap: 0.5rem; }
  .phd__act .btn { flex: 1 1 100%; }
  .band__act .btn { flex: 1 1 100%; }
  .sec { padding-block: 3rem; }
  .slab { margin-bottom: 1.1rem; }
  /* Section labels lose the trailing rule when space is tight. */
  .slab::after { min-width: 24px; }
  .toc { position: static; }
  .aside-sticky { position: static; }
  /* Three separate cards down a phone screen cost about 375px to say three
     short things. One surface with hairline dividers says the same in 150,
     and 225px is most of the reason the masthead ran past the viewport. */
  .mast__facts {
    grid-auto-flow: row;
    grid-auto-columns: auto;
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
    background: var(--ink-2);
    border-radius: var(--r-lg);
  }
  .mast__facts div {
    background: none;
    border-radius: 0;
    padding: 0.62rem 0.9rem;
    display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
  }
  .mast__facts div + div { border-top: 1px solid var(--line); }
  .mast__facts dd { margin-top: 0; }
  /* The masthead is the tallest thing on the site and every rem here is a rem
     before the visitor sees what we actually do. */
  .mast--photo { padding-block: 1.5rem 1.8rem; }
  .mast__sub { gap: 0.9rem; margin-top: 1rem; padding-top: 0.9rem; }
  .mast--photo .lede { font-size: var(--t-base); }
  .mast .flexr { gap: 0.6rem; }
  .mast .mt2 { margin-top: 1rem; }
  .rev { min-height: 0; padding: 1.3rem 1.15rem; }
  .plan { padding: 1.35rem 1.15rem; }
  .ftr__bot { flex-direction: column; gap: 0.4rem; }
}

/* Short landscape phones: the sticky chrome must not eat the screen. */
@media (max-height: 460px) and (orientation: landscape) {
  .rail { display: none; }
  .hdr__in { min-height: 54px; }
  .dock { display: none; }
  body { padding-bottom: 0; }
  html { scroll-padding-top: 62px; }
}

/* Scale the board texture down on phones so the traces stay in proportion
   with the smaller type, and veil it a little more behind dense copy. */
@media (max-width: 560px) {
  body { background-size: 260px 260px; }
  body::before { background: linear-gradient(180deg, rgba(7,9,10,0.55), rgba(7,9,10,0.9) 60%, var(--ink)); }
}

/* Print */
@media print {
  .hdr-wrap, .dock, .wa-float, .ftr__top { display: none !important; }
  body { background-image: none; }
  body::before { display: none; }
  body { background: #fff; color: #000; }
}
