/* ============================================================
   CREATIVE TILER & BATHROOMS — DESIGN SYSTEM
   ============================================================ */

/* 1. TOKENS */
:root {
  color-scheme: light dark;
  --stone: #F5F2EE;
  --stone-dark: #EAE6E0;
  --slate: #1E2122;
  --slate-mid: #2A2D2E;
  --slate-light: #3D4244;
  --brass: #C49A6C;
  --brass-dark: #A07848;
  --brass-light: #D4B48C;
  /* Q12 "would you ship 2.3:1 body text?" -- no.
     --brass is a FILL colour and stays exactly as it is (53 uses: buttons,
     avatars, rules, icons). As TEXT on a light ground it measured 2.57:1 on
     white and 2.30:1 on stone, against the 4.5 AA needs. These two are the
     same hue walked down its own ramp until it clears:
       --brass-text     5.10:1 on white, 4.57:1 on stone  (body, kickers, links)
       --brass-text-lg  3.42:1 on white, 3.06:1 on stone  (>=24px only, needs 3:1)
     Use --brass for anything you FILL, --brass-text for anything you READ. */
  --brass-text: #856949;
  --brass-text-lg: #A9845D;
  --mist: #8A9B99;
  --mist-light: #B5C4C2;
  --white: #FFFFFF;
  --ink: #3A3A3A;
  /* Was #6B7372: 4.36:1 on --stone, just under the 4.5 needed for body
     text. #656D6C measures 4.76 on stone and 5.31 on white. */
  --text-light: #656D6C;

  /* Semantic pair, split out so dark mode has something safe to flip.
     --white and --slate stay LITERAL: --white is text on dark sections in 38
     places, --slate is a dark section background in 18. Inverting either
     would collapse text into its own background. */
  --surface: #FFFFFF;   /* page and card grounds that are white in light mode */
  --heading: #1E2122;   /* text that is slate-coloured in light mode */
  --on-brass: #16191A;  /* foreground on a brass fill. Was #FFFFFF, which
                           measured 2.57:1 -- the same failure dark mode had
                           already fixed below by flipping to ink. Slate on
                           brass measures 6.32:1, so light mode flips too. */

  --font-display: 'Cormorant Garamond', Georgia, serif;
  --font-body: 'DM Sans', system-ui, sans-serif;
  --font-mono: 'DM Mono', monospace;

  --text-display: clamp(3rem, 6vw, 5.5rem);
  --text-h1: clamp(2.2rem, 4vw, 3.5rem);
  --text-h2: clamp(1.8rem, 3vw, 2.8rem);
  --text-h3: clamp(1.3rem, 2vw, 1.8rem);
  --text-body-lg: 1.125rem;
  --text-body: 1rem;
  --text-sm: 0.875rem;
  --text-xs: 0.75rem;

  --space-xs: 0.5rem;
  --space-sm: 1rem;
  --space-md: 1.5rem;
  --space-lg: 2.5rem;
  --space-xl: 4rem;
  --space-2xl: 7rem;

  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 16px;
  --radius-full: 9999px;

  --shadow-sm: 0 2px 8px rgba(0,0,0,0.08);
  --shadow-md: 0 8px 32px rgba(0,0,0,0.12);
  --shadow-lg: 0 24px 64px rgba(0,0,0,0.18);

  --transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  --transition-slow: 0.6s cubic-bezier(0.4, 0, 0.2, 1);

  --container: 1280px;
  --container-narrow: 800px;
}

/* 2. RESET */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; font-size: 16px; }
body {
  font-family: var(--font-body);
  font-size: var(--text-body);
  color: var(--ink);
  background: var(--surface);
  line-height: 1.6;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  /* Suppress the grey flash iOS paints over every tapped link and button.
     The :active states in section 31 give better feedback than it did. */
  -webkit-tap-highlight-color: transparent;
}
img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { cursor: pointer; border: none; background: none; font-family: inherit; }
ul { list-style: none; }
input, textarea, select { font-family: inherit; }

/* 3. TYPOGRAPHY */
.display { font-family: var(--font-display); font-size: var(--text-display); font-weight: 400; line-height: 1.05; letter-spacing: -0.02em; }
h1, .h1 { font-family: var(--font-display); font-size: var(--text-h1); font-weight: 400; line-height: 1.1; letter-spacing: -0.015em; }
h2, .h2 { font-family: var(--font-display); font-size: var(--text-h2); font-weight: 400; line-height: 1.15; letter-spacing: -0.01em; }

/* Q8 "why are your headlines broken with a manual <br>?" -- because the line
   break was drawn at one width and then shipped to every width. 20 of them
   across five pages, each one a guess about a viewport. text-wrap:balance
   lets the browser even out the lines at whatever width it actually gets,
   and a ch-based max-width keeps display type off the long measure that
   made the <br> feel necessary in the first place. Browsers without
   text-wrap:balance just wrap normally, which is the old behaviour minus
   the frozen break point. */
h1, .h1, h2, .h2 {
  text-wrap: balance;
  /* Removing the <br> let some section headings run to a single 1200px line,
     which is a worse measure than the two lines they replaced. A ch-based cap
     keeps display type to a readable measure and lets balance choose where
     the break falls -- so the headings still land on two lines at desktop,
     but at a width the browser picked rather than one baked into the markup. */
  max-width: 22ch;
}
/* Centred headers need the cap centred with them, or it pins them left. */
.section-header--center h2,
.section-header--center h1,
.testimonials-header h2,
.process-header h2,
.suppliers-header h2,
.footer-cta h2,
.ba-header h2 { margin-inline: auto; }
/* Two-column blocks already sit in a narrow column; the cap would double up. */
.service-block__content h2,
.about-founder h2 { max-width: none; }
h3, .h3 { font-family: var(--font-display); font-size: var(--text-h3); font-weight: 400; line-height: 1.2; }
.kicker {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--brass-text);
}

/* The kicker is the one label that appears on BOTH grounds. --brass-text is
   walked down for a light ground, so on the dark sections it inverts: it
   measured 3.17:1 there against the 4.5 needed. On dark, plain --brass is
   already right (5.8:1), so these six containers hand it back. This list was
   read off the DOM rather than guessed -- it is every dark background on the
   site that contains a .kicker. */
.hero .kicker,
.page-hero .kicker,
.service-card .kicker,
.service-block--dark .kicker,
.ba-section .kicker,
.footer-cta .kicker,
/* .btn--ghost's only instance sits in the dark .ba-section too. */
.ba-section .btn--ghost { color: var(--brass); }
.mono { font-family: var(--font-mono); }
.body-lg { font-size: var(--text-body-lg); line-height: 1.7; }
.text-mist { color: var(--mist); }
.text-brass { color: var(--brass-text); }
.text-white { color: var(--white); }
.text-slate { color: var(--heading); }
.text-light { color: var(--text-light); }

/* 4. LAYOUT */
.container { width: 100%; max-width: var(--container); margin: 0 auto; padding: 0 var(--space-lg); }
.container--narrow { max-width: var(--container-narrow); }
.section { padding: var(--space-2xl) 0; }
.section--sm { padding: var(--space-xl) 0; }
.section--stone { background: var(--stone); }
.section--slate { background: var(--slate); color: var(--white); }
.section--slate-mid { background: var(--slate-mid); color: var(--white); }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-lg); }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-lg); }
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-lg); }
.flex { display: flex; }
.flex-center { display: flex; align-items: center; justify-content: center; }
.flex-between { display: flex; align-items: center; justify-content: space-between; }
.gap-sm { gap: var(--space-sm); }
.gap-md { gap: var(--space-md); }
.gap-lg { gap: var(--space-lg); }
.col-span-2 { grid-column: span 2; }

/* 5. BUTTONS */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.875rem 2rem;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  border-radius: var(--radius-sm);
  transition: var(--transition);
  white-space: nowrap;
}
.btn--brass {
  background: var(--brass);
  color: var(--on-brass);
}
.btn--brass:hover { background: var(--brass-dark); transform: translateY(-1px); box-shadow: 0 4px 20px rgba(196,154,108,0.4); }
.btn--outline-white {
  border: 1.5px solid rgba(255,255,255,0.6);
  color: var(--white);
}
.btn--outline-white:hover { border-color: var(--white); background: rgba(255,255,255,0.1); }
.btn--outline-slate {
  border: 1.5px solid var(--heading);
  color: var(--heading);
}
.btn--outline-slate:hover { background: var(--slate); color: var(--white); }
.btn--ghost {
  color: var(--brass-text);
  padding: 0;
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
.btn--ghost:hover { color: var(--brass-dark); }
.btn--ghost::after { content: ' →'; transition: var(--transition); }
.btn--ghost:hover::after { margin-left: 4px; }

/* 6. NAV */
.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  transition: var(--transition);
}
/* Over the hero photo the bar is transparent, so the logo had nothing to sit
   on. A soft scrim separates it without reading as a solid bar. */
.nav::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, rgba(20, 22, 23, 0.55), rgba(20, 22, 23, 0));
  pointer-events: none;
  transition: var(--transition);
}
.nav--scrolled::before { opacity: 0; }
.nav__inner { position: relative; z-index: 1; }
.nav--scrolled {
  background: rgba(30, 33, 34, 0.97);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  box-shadow: 0 1px 0 rgba(255,255,255,0.08);
}
.nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 72px;
  padding: 0 var(--space-lg);
  max-width: var(--container);
  margin: 0 auto;
}
.nav__logo {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
.nav__logo-mark {
  width: 36px; height: 36px;
  background: var(--brass);
  border-radius: var(--radius-sm);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display);
  font-size: 1.1rem;
  color: var(--white);
  font-weight: 600;
}
.nav__logo-text {
  display: flex;
  flex-direction: column;
  line-height: 1.2;
}
.nav__logo-name {
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 500;
  color: var(--white);
  letter-spacing: 0.02em;
}
.nav__logo-sub {
  font-size: 0.65rem;
  color: var(--mist);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.nav__links {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
}
.nav__link {
  font-size: var(--text-sm);
  font-weight: 500;
  color: rgba(255,255,255,0.75);
  letter-spacing: 0.03em;
  transition: var(--transition);
  position: relative;
}
.nav__link::after {
  content: '';
  position: absolute;
  bottom: -4px; left: 0; right: 0;
  height: 1px;
  background: var(--brass);
  transform: scaleX(0);
  transition: var(--transition);
}
.nav__link:hover { color: var(--white); }
.nav__link:hover::after, .nav__link.active::after { transform: scaleX(1); }
.nav__link.active { color: var(--white); }
.nav__cta { margin-left: var(--space-md); }
.nav__phone {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--brass-light);
  letter-spacing: 0.03em;
}
.nav__hamburger {
  display: none;
  flex-direction: column;
  gap: 5px;
  padding: 8px;
}
.nav__hamburger span {
  display: block;
  width: 24px; height: 1.5px;
  background: var(--white);
  transition: var(--transition);
}

/* 7. HERO */
.hero {
  position: relative;
  /* svh is the viewport as it stands with Safari's URL bar showing. Plain vh
     on iOS measures the space behind that collapsing bar, so the hero ran
     taller than the screen and the whole page shifted every time the bar
     hid or reappeared. vh stays first as the fallback for old browsers. */
  height: 100vh;
  height: 100svh;
  min-height: 600px;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
}
.hero__bg {
  position: absolute;
  inset: 0;
  /* Islay: petrol-blue glazed metro brick, tin slipper bath, exposed roof
     beams. One file, so no image-set -- that only earns its place with more
     than one density to choose between.

     CEILING, and it is lower than the shot it replaced: the source is
     600x600. A 1440px hero scales it 2.4x, and 4.8x on a 2x display, so it
     will be visibly soft on a wide desktop. The 828px photograph before it
     had the same problem one stop less badly. Nothing in CSS fixes this --
     it needs a larger export of this same frame, ideally landscape.

     The frame is square and the composition is vertical (beams and lights up
     top, bath across the middle, planting at the foot), while the hero is a
     wide bottom-aligned box, so cover crops it hard. 60% was picked by
     rendering 42/60/75/90 and looking: 42 lost the bath into the gradient
     entirely, 75 pushed it up under the headline. 60 keeps the tiling
     filling the clear band -- which is the thing being sold -- lets the tub
     rim sweep in beneath the type, and holds the star pendant and the ferns
     in frame for the character they carry. */
  background-image: url('../images/projects/isles-islay-skinny-metro-brick-bath-portrait-roof.webp');
  background-size: cover;
  background-position: center 60%;
  transform-origin: center;
}
.hero__bg::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    rgba(20, 22, 23, 0.92) 0%,
    rgba(20, 22, 23, 0.5) 45%,
    rgba(20, 22, 23, 0.15) 100%
  );
}
.hero__content {
  position: relative;
  z-index: 2;
  padding: var(--space-2xl) var(--space-lg);
  max-width: var(--container);
  margin: 0 auto;
  width: 100%;
  padding-bottom: 120px;
}
.hero__kicker {
  margin-bottom: var(--space-md);
}
.hero__headline {
  color: var(--white);
  max-width: 780px;
  margin-bottom: var(--space-md);
}
.hero__sub {
  color: rgba(255,255,255,0.7);
  max-width: 560px;
  font-size: var(--text-body-lg);
  line-height: 1.7;
  margin-bottom: var(--space-lg);
}
.hero__actions {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  flex-wrap: wrap;
}
.hero__strip {
  position: absolute;
  bottom: 0; left: 0; right: 0;
  z-index: 3;
  background: rgba(196,154,108,0.12);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border-top: 1px solid rgba(196,154,108,0.2);
}
/* Q9 "what is decoration pretending to be content?" -- this strip was, and
   the gap was why. --space-2xl is 7rem, and six of them between four items
   and three dividers ate 672px of a 1280px container. That left the four
   claims about 100px each, so every one of them wrapped: measured 2/2/3/3
   lines at 1920px, 4/2/3/3 at 1100px, with "Free no-obligation quotes"
   breaking across a hyphen. Four claims nobody can read at a glance are
   decoration. A gap that flexes with the room lets each sit on one line,
   which is the only way a trust strip does its job. */
.hero__strip-inner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(1.25rem, 3.2vw, 3.25rem);
  padding: 1.25rem var(--space-lg);
  max-width: var(--container);
  margin: 0 auto;
}
.hero__strip-item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  /* Say it on one line or not at all. */
  white-space: nowrap;
}
.hero__strip-icon {
  font-size: 1.1rem;
  color: var(--brass);
}
.hero__strip-text {
  font-size: var(--text-sm);
  color: rgba(255,255,255,0.85);
  font-weight: 500;
}
.hero__strip-text strong { color: var(--white); }
.hero__strip-divider {
  width: 1px; height: 28px;
  background: rgba(196,154,108,0.3);
}
/* Inline SVG, brass, matching every other icon on the site. Was #F5C842
   -- a yellow that appears nowhere else in the palette -- set on literal
   U+2605 glyphs, which iOS is free to render through its own font. */
.stars-row { display: inline-flex; align-items: center; gap: 3px; color: var(--brass); }

/* 8. PROOF BAR */
.proof-bar {
  background: var(--stone);
  padding: var(--space-lg) 0;
  border-bottom: 1px solid var(--stone-dark);
}
.proof-bar__inner {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  justify-content: center;
  flex-wrap: wrap;
}
.proof-bar__avatar {
  width: 48px; height: 48px;
  border-radius: 50%;
  object-fit: cover;
  border: 2px solid var(--brass);
  background: linear-gradient(135deg, var(--mist), var(--slate));
  display: flex; align-items: center; justify-content: center;
  color: white; font-size: 1.2rem; font-family: var(--font-display);
}
.proof-bar__text { font-size: var(--text-sm); color: var(--ink); }
.proof-bar__text strong { color: var(--heading); }
.proof-bar__link { color: var(--brass-text); font-weight: 600; font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.1em; }

/* 9. BEFORE/AFTER SLIDER */
.ba-section { padding: var(--space-2xl) 0; background: var(--slate); }
.ba-container { max-width: var(--container); margin: 0 auto; padding: 0 var(--space-lg); }
.ba-header { text-align: center; margin-bottom: var(--space-xl); }
.ba-header h2 { color: var(--white); margin-bottom: var(--space-sm); }
.ba-header p { color: var(--mist); max-width: 560px; margin: 0 auto; font-size: var(--text-body-lg); }

.ba-wrapper {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-lg);
  align-items: start;
}
.ba-slider {
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  aspect-ratio: 4/3;
  cursor: ew-resize;
  user-select: none;
  box-shadow: var(--shadow-lg);
}
.ba-slider__before,
.ba-slider__after {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
}
.ba-slider__before {
  filter: grayscale(30%) brightness(0.85);
}
.ba-slider__after {
  clip-path: inset(0 50% 0 0);
}
.ba-slider__divider {
  position: absolute;
  top: 0; bottom: 0;
  left: 50%;
  width: 3px;
  background: var(--brass);
  transform: translateX(-50%);
  z-index: 3;
}
.ba-slider__handle {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 44px; height: 44px;
  background: var(--brass);
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  z-index: 4;
  box-shadow: 0 0 0 4px rgba(196,154,108,0.3);
  font-size: 1rem;
  color: var(--white);
}
.ba-label {
  position: absolute;
  bottom: var(--space-sm);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: white;
  background: rgba(0,0,0,0.5);
  padding: 4px 10px;
  border-radius: var(--radius-full);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}
.ba-label--before { left: var(--space-sm); }
.ba-label--after { right: var(--space-sm); }

.ba-details { display: flex; flex-direction: column; justify-content: center; gap: var(--space-md); padding-left: var(--space-md); }
.ba-details__quote {
  font-family: var(--font-display);
  font-size: clamp(1.4rem, 2.5vw, 2rem);
  color: var(--white);
  font-style: italic;
  line-height: 1.35;
  border-left: 3px solid var(--brass);
  padding-left: var(--space-md);
}
.ba-details__meta { color: var(--mist); font-size: var(--text-sm); }
.ba-details__meta span { color: var(--brass-light); font-weight: 600; }
.ba-details__specs {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}
.ba-spec {
  background: rgba(255,255,255,0.05);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: var(--radius-md);
  padding: var(--space-sm);
}
.ba-spec__label { font-size: var(--text-xs); color: var(--mist); text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 4px; }
.ba-spec__value { font-size: var(--text-sm); font-weight: 600; color: var(--white); }

/* 10. SERVICE CARDS */
.services-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5px;
  background: var(--stone-dark);
}
.service-card {
  position: relative;
  aspect-ratio: 3/4;
  overflow: hidden;
  background: var(--slate);
}
.service-card__bg {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  transition: transform 0.7s cubic-bezier(0.4, 0, 0.2, 1);
}
.service-card:hover .service-card__bg { transform: scale(1.06); }
.service-card__overlay {
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(20,22,23,0.9) 0%, rgba(20,22,23,0.2) 60%, transparent 100%);
  transition: var(--transition);
}
.service-card:hover .service-card__overlay { background: linear-gradient(to top, rgba(20,22,23,0.95) 0%, rgba(20,22,23,0.4) 70%, rgba(20,22,23,0.1) 100%); }
.service-card__content {
  position: absolute;
  bottom: 0; left: 0; right: 0;
  padding: var(--space-lg);
  z-index: 2;
}
.service-card__kicker { margin-bottom: 0.5rem; }
/* Q2's answer, on the card. Brass on the card's dark overlay, so it reads as
   the one number on an otherwise photographic tile. The qualifier sits at
   label size beside it so the figure is never quoted without its unit. */
.service-card__price {
  font-family: var(--font-mono);
  font-size: 1.05rem;
  color: var(--white);
  margin-bottom: 0.75rem;
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.5rem;
}
.service-card__price strong { color: var(--brass); font-weight: 500; }
.service-card__price span {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  color: var(--mist-light);
  letter-spacing: 0.04em;
}
.service-card__title {
  font-family: var(--font-display);
  font-size: clamp(1.4rem, 2vw, 1.9rem);
  font-weight: 400;
  color: var(--white);
  margin-bottom: var(--space-sm);
  line-height: 1.15;
}
.service-card__body {
  font-size: var(--text-sm);
  color: rgba(255,255,255,0.65);
  line-height: 1.6;
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.5s ease, opacity 0.4s ease;
  opacity: 0;
  margin-bottom: 0;
}
.service-card:hover .service-card__body { max-height: 120px; opacity: 1; margin-bottom: var(--space-sm); }
/* Always sits on the card's dark photo overlay, so it takes the light
   ramp. --brass-text measured 3.17:1 here; --brass measures 5.8:1.
   axe could not see this one -- it reports "incomplete" rather than a
   violation wherever an ancestor carries a background-image. */
.service-card__link { display: inline-flex; align-items: center; gap: 6px; font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: var(--brass); transition: var(--transition); }
.service-card__link:hover { gap: 10px; }

/* 11. PROCESS */
.process-section { background: var(--stone); padding: var(--space-2xl) 0; }
.process-header { text-align: center; margin-bottom: var(--space-xl); }
.process-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0; position: relative; }
.process-grid::before {
  content: '';
  position: absolute;
  top: 28px; left: 12.5%; right: 12.5%;
  height: 1px;
  background: linear-gradient(to right, transparent, var(--brass), var(--brass), transparent);
  z-index: 0;
}
.process-step { padding: 0 var(--space-md); text-align: center; position: relative; z-index: 1; }
.process-step__number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px; height: 56px;
  border-radius: 50%;
  background: var(--surface);
  border: 2px solid var(--brass);
  font-family: var(--font-mono);
  font-size: 1.1rem;
  color: var(--brass-text);
  margin: 0 auto var(--space-md);
  font-weight: 600;
}
.process-step__title { font-family: var(--font-display); font-size: 1.25rem; color: var(--heading); margin-bottom: 0.5rem; }
.process-step__body { font-size: var(--text-sm); color: var(--text-light); line-height: 1.6; }

/* 12. STATS */
.stats-section { background: var(--slate); padding: var(--space-xl) 0; }
.stats-grid { display: grid; grid-template-columns: repeat(3, 1fr); }
.stat-item { text-align: center; padding: var(--space-xl) var(--space-lg); position: relative; }
.stat-item:not(:last-child)::after { content: ''; position: absolute; top: 20%; right: 0; bottom: 20%; width: 1px; background: rgba(255,255,255,0.08); }
.stat-number {
  font-family: var(--font-mono);
  font-size: clamp(3rem, 6vw, 5rem);
  font-weight: 400;
  color: var(--brass-text-lg);
  line-height: 1;
  margin-bottom: 0.5rem;
  display: block;
}
.stat-label { font-size: var(--text-sm); color: var(--mist); text-transform: uppercase; letter-spacing: 0.12em; font-weight: 500; }

/* 13. TESTIMONIALS */
.testimonials-section { background: var(--surface); padding: var(--space-2xl) 0; }
.testimonials-header { text-align: center; margin-bottom: var(--space-xl); }
.testimonials-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-md);
  align-items: start;
}
.testimonial-card {
  background: var(--stone);
  border-radius: var(--radius-lg);
  padding: var(--space-lg);
  border: 1px solid var(--stone-dark);
  transition: var(--transition);
}
.testimonial-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); }
.testimonial-card--featured {
  background: var(--slate);
  border-color: var(--slate-light);
}
.testimonial-card__stars { margin-bottom: var(--space-sm); }
.testimonial-card__quote {
  font-family: var(--font-display);
  font-size: 1.2rem;
  font-style: italic;
  line-height: 1.4;
  margin-bottom: var(--space-sm);
  color: var(--heading);
}
.testimonial-card--featured .testimonial-card__quote { color: var(--white); }
.testimonial-card__body { font-size: var(--text-sm); color: var(--text-light); line-height: 1.65; margin-bottom: var(--space-md); }
.testimonial-card--featured .testimonial-card__body { color: var(--mist); }
.testimonial-card__author { display: flex; align-items: center; gap: 0.75rem; }
.testimonial-card__avatar {
  width: 40px; height: 40px;
  border-radius: 50%;
  background: var(--brass);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display);
  font-size: 1rem;
  color: var(--on-brass);
  flex-shrink: 0;
}
.testimonial-card__name { font-weight: 600; font-size: var(--text-sm); color: var(--heading); }
.testimonial-card--featured .testimonial-card__name { color: var(--white); }
.testimonial-card__location { font-size: var(--text-xs); color: var(--text-light); }
.testimonial-card--featured .testimonial-card__location { color: var(--mist); }

/* 14. PORTFOLIO GRID */
.portfolio-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 3px;
}
.portfolio-item {
  position: relative;
  overflow: hidden;
  background: var(--stone-dark);
  aspect-ratio: 4/3;
}
.portfolio-item--tall { aspect-ratio: 4/5; }
.portfolio-item--wide { grid-column: span 2; aspect-ratio: 16/9; }
.portfolio-item__img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
}
.portfolio-item:hover .portfolio-item__img { transform: scale(1.05); }
.portfolio-item__overlay {
  position: absolute;
  inset: 0;
  background: rgba(20,22,23,0);
  display: flex; align-items: flex-end;
  padding: var(--space-md);
  transition: var(--transition);
}
.portfolio-item:hover .portfolio-item__overlay { background: rgba(20,22,23,0.6); }
.portfolio-item__info { opacity: 0; transform: translateY(8px); transition: var(--transition); }
.portfolio-item:hover .portfolio-item__info { opacity: 1; transform: translateY(0); }
.portfolio-item__tag {
  display: inline-block;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--brass-text);
  margin-bottom: 4px;
}
.portfolio-item__title { font-family: var(--font-display); font-size: 1.2rem; color: var(--white); }

/* 15. FILTER BAR */
.filter-bar { display: flex; gap: var(--space-sm); flex-wrap: wrap; }
.filter-btn {
  display: inline-flex;
  align-items: center;
  /* 44px is Apple's minimum comfortable target. The type stays put; only
     the box grows, so nothing about the design changes. */
  min-height: 44px;
  padding: 0.5rem 1.25rem;
  border-radius: var(--radius-full);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  transition: var(--transition);
  border: 1.5px solid var(--stone-dark);
  color: var(--text-light);
  background: transparent;
}
.filter-btn:hover, .filter-btn.active {
  background: var(--slate);
  border-color: var(--heading);
  color: var(--white);
}

/* 16. SUPPLIERS */
.suppliers-section { background: var(--stone); padding: var(--space-xl) 0; }
.suppliers-header { text-align: center; margin-bottom: var(--space-xl); }
.suppliers-grid { display: flex; align-items: center; justify-content: center; gap: var(--space-md); flex-wrap: wrap; }
.supplier-logo {
  /* Real supplier logos are opaque JPEGs with their own backgrounds, so each
     sits on a uniform white card rather than floating on the stone. */
  height: 42px;
  width: auto;
  box-sizing: content-box;
  background: var(--white);
  padding: 10px 16px;
  border: 1px solid var(--stone-dark);
  border-radius: var(--radius-sm);
  opacity: 0.88;
  transition: var(--transition);
}
.supplier-logo:hover { opacity: 1; border-color: var(--brass); }

/* 17. FOOTER CTA */
.footer-cta {
  background: var(--slate);
  padding: var(--space-2xl) 0;
  text-align: center;
  position: relative;
  overflow: hidden;
}
.footer-cta::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: url('../images/projects/bathroom-03-brass-shower.jpg');
  background-size: cover;
  background-position: center;
  opacity: 0.08;
}
.footer-cta__content { position: relative; z-index: 1; max-width: 640px; margin: 0 auto; padding: 0 var(--space-lg); }
.footer-cta h2 { color: var(--white); margin-bottom: var(--space-sm); }
.footer-cta p { color: var(--mist); font-size: var(--text-body-lg); margin-bottom: var(--space-lg); }

/* 18. FOOTER */
.footer { background: #141618; padding: var(--space-xl) 0 var(--space-lg); }
.footer__grid { display: grid; grid-template-columns: 1.5fr 1fr 1fr 1.2fr; gap: var(--space-xl); margin-bottom: var(--space-xl); }
.footer__brand-logo { display: flex; align-items: center; gap: 0.75rem; margin-bottom: var(--space-sm); }
.footer__brand-desc { font-size: var(--text-sm); color: var(--mist); line-height: 1.7; margin-bottom: var(--space-md); max-width: 280px; }
.footer__socials { display: flex; gap: var(--space-sm); }
.footer__social {
  width: 36px; height: 36px;
  border-radius: 50%;
  border: 1px solid rgba(255,255,255,0.1);
  display: flex; align-items: center; justify-content: center;
  color: var(--mist);
  font-size: 0.9rem;
  transition: var(--transition);
  position: relative;
}
/* The circle stays 36px; the tappable area becomes 44px. Invisible, and it
   costs the layout nothing because the pseudo-element is absolute. */
.footer__social::after {
  content: '';
  position: absolute;
  inset: -4px;
  border-radius: 50%;
}
.footer__social:hover { border-color: var(--brass); color: var(--brass); }
.footer__col-title { font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.15em; text-transform: uppercase; color: var(--white); margin-bottom: var(--space-md); }
.footer__links { display: flex; flex-direction: column; gap: 0.75rem; }
.footer__link { font-size: var(--text-sm); color: var(--mist); transition: var(--transition); }
.footer__link:hover { color: var(--brass); }
.footer__contact-item { display: flex; align-items: flex-start; gap: 0.75rem; margin-bottom: 1rem; }
.footer__contact-icon { color: var(--brass); font-size: 0.9rem; margin-top: 2px; flex-shrink: 0; }
.footer__contact-text { font-size: var(--text-sm); color: var(--mist); line-height: 1.5; }
.footer__contact-text a { color: var(--brass-light); }
.footer__contact-text a:hover { color: var(--brass); }
/* The sub-line under the phone number: who answers, and when. Sits a size
   down from the number itself but stays above the 4.5:1 line -- the old
   inline rgba(255,255,255,0.3) it replaces measured 2.7:1. */
.footer__contact-sub {
  display: inline-block;
  font-size: var(--text-xs);
  color: rgba(255,255,255,0.62);
  line-height: 1.5;
}
.footer__bottom { border-top: 1px solid rgba(255,255,255,0.06); padding-top: var(--space-md); display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--space-sm); }
.footer__copy { font-size: var(--text-xs); color: rgba(255,255,255,0.55); }
.footer__privacy { margin: 0; }
.footer__privacy a { color: var(--brass-light); text-decoration: underline; text-underline-offset: 2px; }
.footer__privacy a:hover { color: var(--brass); }
.footer__bottom-links { display: flex; gap: var(--space-md); }
.footer__bottom-link { font-size: var(--text-xs); color: rgba(255,255,255,0.55); }
.footer__bottom-link:hover { color: var(--mist); }

/* 19. CONTACT FORM */
.contact-layout { display: grid; grid-template-columns: 1fr 1.4fr; gap: var(--space-2xl); align-items: start; }
.contact-info { position: sticky; top: 100px; }
.contact-info__headline { margin-bottom: var(--space-sm); }
.contact-info__body { color: var(--text-light); font-size: var(--text-body-lg); margin-bottom: var(--space-xl); line-height: 1.7; }
.contact-detail { display: flex; gap: var(--space-md); margin-bottom: var(--space-lg); align-items: flex-start; }
.contact-detail__icon {
  width: 48px; height: 48px;
  border-radius: var(--radius-md);
  background: var(--stone);
  display: flex; align-items: center; justify-content: center;
  color: var(--brass);
  font-size: 1.1rem;
  flex-shrink: 0;
}
.contact-detail__label { font-size: var(--text-xs); color: var(--text-light); text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 4px; }
.contact-detail__value { font-size: var(--text-body-lg); font-weight: 600; color: var(--heading); }
.contact-detail__sub { font-size: var(--text-sm); color: var(--text-light); margin-top: 2px; }
.contact-info__next { margin-top: var(--space-xl); padding: var(--space-lg); background: var(--stone); border-radius: var(--radius-lg); }
.contact-info__next-title { font-family: var(--font-display); font-size: 1.1rem; color: var(--heading); margin-bottom: var(--space-md); }
.next-step { display: flex; align-items: flex-start; gap: 1rem; margin-bottom: var(--space-md); }
.next-step:last-child { margin-bottom: 0; }
.next-step__num {
  width: 28px; height: 28px;
  border-radius: 50%;
  background: var(--brass);
  color: var(--on-brass);
  font-size: var(--text-xs);
  font-weight: 700;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  margin-top: 1px;
}
.next-step__text { font-size: var(--text-sm); color: var(--ink); line-height: 1.5; }
.next-step__text strong { color: var(--heading); display: block; margin-bottom: 2px; }

/* MULTI-STEP FORM */
.msf {
  background: var(--surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  border: 1px solid var(--stone-dark);
}
.msf__progress {
  display: flex;
  height: 4px;
  background: var(--stone-dark);
}
.msf__progress-fill {
  background: var(--brass);
  transition: width 0.4s ease;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.msf__header {
  padding: var(--space-lg) var(--space-xl) var(--space-md);
  border-bottom: 1px solid var(--stone-dark);
}
.msf__step-label { font-size: var(--text-xs); color: var(--text-light); margin-bottom: 4px; }
.msf__step-title { font-family: var(--font-display); font-size: 1.5rem; color: var(--heading); }
.msf__body { padding: var(--space-xl); }
.msf__step { display: none; }
.msf__step.active { display: block; }
.service-options { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-sm); margin-bottom: var(--space-lg); }
.service-option {
  border: 1.5px solid var(--stone-dark);
  border-radius: var(--radius-md);
  padding: var(--space-md);
  cursor: pointer;
  transition: var(--transition);
  text-align: center;
}
.service-option:hover { border-color: var(--brass); background: rgba(196,154,108,0.05); }
.service-option.selected { border-color: var(--brass); background: rgba(196,154,108,0.08); }
.service-option__icon { font-size: 1.5rem; margin-bottom: 0.5rem; }
.service-option__label { font-size: var(--text-sm); font-weight: 600; color: var(--heading); }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-md); }
.form-group { margin-bottom: var(--space-md); }
.form-label { display: block; font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-light); margin-bottom: 8px; }
.form-input, .form-select, .form-textarea {
  width: 100%;
  padding: 0.875rem 1rem;
  border: 1.5px solid var(--stone-dark);
  border-radius: var(--radius-md);
  font-family: var(--font-body);
  font-size: var(--text-body);
  color: var(--heading);
  background: var(--surface);
  transition: var(--transition);
  outline: none;
}
.form-input:focus, .form-select:focus, .form-textarea:focus { border-color: var(--brass); box-shadow: 0 0 0 3px rgba(196,154,108,0.12); }
.form-textarea { resize: vertical; min-height: 100px; }
.budget-options { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-sm); }
.budget-option {
  border: 1.5px solid var(--stone-dark);
  border-radius: var(--radius-md);
  padding: 0.75rem var(--space-sm);
  cursor: pointer;
  transition: var(--transition);
  text-align: center;
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--ink);
}
.budget-option:hover, .budget-option.selected { border-color: var(--brass); background: rgba(196,154,108,0.06); color: var(--heading); font-weight: 600; }
.msf__footer {
  padding: var(--space-md) var(--space-xl) var(--space-xl);
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.msf__back-btn { font-size: var(--text-sm); color: var(--text-light); font-weight: 500; padding: 0; cursor: pointer; display: flex; align-items: center; gap: 4px; }
.msf__back-btn:hover { color: var(--heading); }
.msf__submit-wrap { text-align: center; padding: var(--space-xl); display: none; }
.msf__submit-wrap.active { display: block; }
.msf__success-icon { font-size: 3rem; margin-bottom: var(--space-md); }
.msf__success-title { font-family: var(--font-display); font-size: 1.8rem; color: var(--heading); margin-bottom: var(--space-sm); }
.msf__success-body { color: var(--text-light); max-width: 360px; margin: 0 auto; }

/* 20. PAGE HERO (inner pages) */
.page-hero {
  padding: 160px 0 80px;
  background: var(--slate);
  position: relative;
  overflow: hidden;
}
.page-hero::before {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at 70% 50%, rgba(196,154,108,0.12) 0%, transparent 60%);
}
.page-hero__content { position: relative; z-index: 1; }
.page-hero h1 { color: var(--white); margin-bottom: var(--space-sm); }
.page-hero p { color: var(--mist); font-size: var(--text-body-lg); max-width: 560px; }
.breadcrumb { display: flex; align-items: center; gap: 0.5rem; margin-bottom: var(--space-md); }
.breadcrumb__item { font-size: var(--text-xs); color: var(--mist); text-transform: uppercase; letter-spacing: 0.1em; }
.breadcrumb__item--link:hover { color: var(--brass); }
.breadcrumb__sep { color: rgba(255,255,255,0.2); font-size: 0.7rem; }

/* 21. SERVICES PAGE */
.service-block { display: grid; grid-template-columns: 1fr 1fr; gap: 0; min-height: 560px; }
.service-block--reverse { direction: rtl; }
.service-block--reverse > * { direction: ltr; }
.service-block__image { position: relative; overflow: hidden; min-height: 460px; }
.service-block__image img { width: 100%; height: 100%; object-fit: cover; }
.service-block__content { display: flex; flex-direction: column; justify-content: center; padding: var(--space-2xl); background: var(--surface); }
.service-block--dark .service-block__content { background: var(--slate); }
.service-block__list { list-style: none; margin: var(--space-md) 0; }
.service-block__list li {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.5rem 0;
  font-size: var(--text-sm);
  color: var(--ink);
  border-bottom: 1px solid var(--stone-dark);
}
.service-block--dark .service-block__list li { color: var(--mist); border-bottom-color: rgba(255,255,255,0.06); }
.service-block--dark .service-block__price-from,
.service-block--dark .service-block__price-note { color: var(--mist-light); }
/* A horizontally-scrolling region needs to be reachable, and to say so when
   it is: it takes focus, and shows it. */
.testimonials-grid:focus-visible,
.services-grid:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 4px;
}
.service-block__list li::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: var(--brass); flex-shrink: 0; }
.service-block__price { display: inline-flex; align-items: baseline; gap: 0.5rem; margin-bottom: var(--space-sm); }
.service-block__price-from { font-size: var(--text-xs); color: var(--text-light); text-transform: uppercase; letter-spacing: 0.1em; }
.service-block__price-val { font-family: var(--font-mono); font-size: 2rem; font-weight: 400; color: var(--brass-text-lg); }
/* "Per job" is a word where the others are a figure, so it sits a size down
   and stops pretending to be a number. */
.service-block__price--text .service-block__price-val { font-size: 1.5rem; }
.service-block__price-note {
  font-size: var(--text-xs);
  color: var(--text-light);
  margin-bottom: var(--space-md);
  max-width: 46ch;
}

/* Q1/Q4 fallout. A single nowrap CTA ("Discuss Your Project", 244px) plus
   2 x 68px of padding made this column's min-content 380px, and because grid
   items default to min-width:auto the whole PAGE scrolled sideways below
   414px -- measured +60px at 320px. The label is shorter now, but the shape
   of the bug was the padding, so the padding is what gets fixed: below 480px
   the column stops spending 136px of a 320px screen on its own margins. */
@media (max-width: 480px) {
  .service-block__content { padding: var(--space-xl) var(--space-md); }
  .service-block__content .btn { white-space: normal; }
}

/* 22. FAQ ACCORDION */
.faq-section { padding: var(--space-2xl) 0; background: var(--stone); }
.faq-list { max-width: 780px; margin: 0 auto; }
.faq-item { border-bottom: 1px solid var(--stone-dark); }
.faq-item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-md) 0;
  cursor: pointer;
  font-size: var(--text-body-lg);
  font-weight: 500;
  color: var(--heading);
  list-style: none;
  user-select: none;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after {
  content: '+';
  font-size: 1.5rem;
  color: var(--brass-text-lg);
  transition: var(--transition);
  flex-shrink: 0;
  margin-left: var(--space-md);
}
.faq-item[open] summary::after { content: '−'; }
.faq-item__body { padding: 0 0 var(--space-md); color: var(--text-light); line-height: 1.7; }

/* 23. ABOUT PAGE */
.about-founder { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2xl); align-items: center; }
.about-founder__image {
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  aspect-ratio: 4/5;
}
.about-founder__image img { width: 100%; height: 100%; object-fit: cover; }
.about-founder__badge {
  position: absolute;
  bottom: var(--space-lg); right: var(--space-lg);
  background: var(--brass);
  color: var(--on-brass);
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-md);
  font-family: var(--font-display);
  font-size: 1.1rem;
  text-align: center;
}
.about-founder__badge-num { font-size: 2rem; display: block; line-height: 1; margin-bottom: 2px; }
.about-founder__kicker { margin-bottom: var(--space-sm); }
.about-founder__headline { margin-bottom: var(--space-md); }
.about-founder__quote {
  font-family: var(--font-display);
  font-size: 1.3rem;
  font-style: italic;
  color: var(--heading);
  border-left: 3px solid var(--brass);
  padding-left: var(--space-md);
  margin: var(--space-lg) 0;
  line-height: 1.5;
}
.values-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 3px; background: var(--stone-dark); margin: var(--space-2xl) 0 0; }
.value-card {
  background: var(--slate);
  padding: var(--space-xl) var(--space-lg);
  text-align: center;
  transition: var(--transition);
}
.value-card:hover { background: var(--slate-light); }
.value-card__icon { font-size: 2rem; margin-bottom: var(--space-md); display: block; }
.value-card__title { font-family: var(--font-display); font-size: 1.5rem; color: var(--white); margin-bottom: 0.5rem; }
.value-card__body { font-size: var(--text-sm); color: var(--mist); line-height: 1.6; font-style: italic; }

.trust-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-md); margin-top: var(--space-xl); }
.trust-card {
  border: 1px solid var(--stone-dark);
  border-radius: var(--radius-lg);
  padding: var(--space-lg);
  display: flex; gap: var(--space-md); align-items: flex-start;
}
.trust-card__icon { font-size: 1.5rem; color: var(--brass); flex-shrink: 0; margin-top: 2px; }
.trust-card__title { font-weight: 600; font-size: var(--text-sm); color: var(--heading); margin-bottom: 4px; }
.trust-card__body { font-size: var(--text-sm); color: var(--text-light); line-height: 1.5; }

/* 24. MOBILE NAV OVERLAY */
.mobile-nav {
  position: fixed;
  inset: 0;
  background: var(--slate);
  z-index: 200;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-lg);
  padding: calc(var(--space-lg) + env(safe-area-inset-top, 0px)) var(--space-lg)
           calc(var(--space-lg) + env(safe-area-inset-bottom, 0px));
  opacity: 0;
  pointer-events: none;
  /* Q10 "what control ships that a person can't see?" -- this one. Hiding
     with opacity alone left all seven controls in the tab order on every
     page and every width: a keyboard user met 7 invisible stops, and on
     desktop they were invisible DUPLICATES of the visible nav sitting right
     after it. visibility:hidden takes them out of the tab order and off the
     accessibility tree; delaying it until the fade finishes keeps the
     transition. The .open rule restores it with no delay. */
  visibility: hidden;
  transition: opacity 0.3s ease, visibility 0s linear 0.3s;
}
.mobile-nav.open {
  opacity: 1;
  pointer-events: all;
  visibility: visible;
  transition: opacity 0.3s ease, visibility 0s linear 0s;
}
.mobile-nav__link {
  font-family: var(--font-display);
  font-size: clamp(2rem, 5vw, 3.5rem);
  color: var(--white);
  transition: var(--transition);
}
.mobile-nav__link:hover { color: var(--brass); }
.mobile-nav__close {
  position: absolute;
  top: var(--space-lg); right: var(--space-lg);
  font-size: 2rem;
  line-height: 1;
  color: var(--white);
  cursor: pointer;
  padding: 8px;
  /* The glyph is about 20px wide, so the button was ~36px across. */
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.mobile-nav__phone { font-family: var(--font-mono); font-size: 1.2rem; color: var(--brass); margin-top: var(--space-lg); }
.mobile-phone-bar {
  display: none;
  position: fixed;
  bottom: 0; left: 0; right: 0;
  background: var(--brass);
  z-index: 90;
  padding: 1rem var(--space-lg);
  /* Clear the home indicator on notched iPhones; 0px on everything else. */
  padding-bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
}
/* Two actions share the pill: call on the left, enquiry on the right. Both
   are >= 44px tall. The enquiry side is the filled one because it is the
   action the site is built around; the number stays visible because on a
   trade site it does real work. */
.mobile-phone-bar__call {
  display: inline-flex; align-items: center; gap: 0.4rem;
  flex-shrink: 0;
  min-height: 44px;
  padding: 0 0.15rem;
  color: var(--on-brass);
  font-family: var(--font-mono);
  font-weight: 500; font-size: 0.92rem; letter-spacing: 0.01em;
  white-space: nowrap;
}
.mobile-phone-bar__cta {
  flex: 1 1 auto;
  min-width: 0;
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px;
  padding: 0.35rem 0.7rem;
  border-radius: var(--radius-full);
  background: var(--slate);
  color: var(--white);
  font-weight: 700; font-size: 0.76rem;
  letter-spacing: 0.02em; line-height: 1.25;
  text-align: center;
}

/* 25. UTILS */
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; }
.text-center { text-align: center; }
.mb-sm { margin-bottom: var(--space-sm); }
.mb-md { margin-bottom: var(--space-md); }
.mb-lg { margin-bottom: var(--space-lg); }
.mb-xl { margin-bottom: var(--space-xl); }
.mt-sm { margin-top: var(--space-sm); }
.mt-md { margin-top: var(--space-md); }
.mt-lg { margin-top: var(--space-lg); }
.section-header { margin-bottom: var(--space-xl); }
.section-header--center { text-align: center; }
.section-header .kicker { margin-bottom: 0.75rem; }
/* Q3, the visible half. Off-screen until focused, then it lands as a real
   brass pill in the top-left -- above the fixed nav (z 200) so it is never
   covered by it. */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 300;
  background: var(--brass);
  color: var(--on-brass);
  font-size: var(--text-sm);
  font-weight: 600;
  padding: 0.75rem 1.25rem;
  border-radius: 0 0 var(--radius-md) 0;
  text-decoration: none;
}
.skip-link:focus {
  left: 0;
}
/* The nav is fixed, so an in-page jump would otherwise land under it. */
#main { scroll-margin-top: 90px; }

.divider { width: 48px; height: 2px; background: var(--brass); display: block; }
.divider--center { margin: 1rem auto; }

/* 26. ANIMATIONS */
@keyframes fadeInUp {
  from { opacity: 0; transform: translateY(24px); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes slideInLeft { from { opacity: 0; transform: translateX(-32px); } to { opacity: 1; transform: translateX(0); } }
.animate-up { animation: fadeInUp 0.7s cubic-bezier(0.4, 0, 0.2, 1) forwards; opacity: 0; }
.animate-up-delay-1 { animation-delay: 0.1s; }
.animate-up-delay-2 { animation-delay: 0.2s; }
.animate-up-delay-3 { animation-delay: 0.3s; }
.animate-up-delay-4 { animation-delay: 0.4s; }
/* Scroll reveal is an enhancement, not a gate. Without the inline script in
   <head> adding .js, .reveal is never hidden, so content shows unanimated
   rather than not at all -- 61 blocks across the site depend on this. */
/* Q11 "what does a fade-up on 40 elements communicate?" -- nothing, and at
   0.7s it was content arriving late 40 times a page. Motion earns its place
   by being brief enough to read as the page settling, not as a sequence the
   reader has to wait out. 0.32s and a 10px rise do that; the reveal is still
   there, it just stops being the thing you notice. */
.js .reveal { opacity: 0; transform: translateY(10px); transition: opacity 0.32s ease-out, transform 0.32s ease-out; }
.js .reveal.visible { opacity: 1; transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
  .js .reveal { opacity: 1; transform: none; transition: none; }
  .animate-up { animation: none; opacity: 1; }
}

/* 27. RESPONSIVE */
@media (max-width: 1024px) {
  .services-grid { grid-template-columns: 1fr 1fr; }
  .footer__grid { grid-template-columns: 1fr 1fr; gap: var(--space-lg); }
  .about-founder { gap: var(--space-lg); }
  .values-grid { grid-template-columns: repeat(2, 1fr); }
  .ba-wrapper { grid-template-columns: 1fr; }
  .ba-details { padding-left: 0; padding-top: var(--space-md); }
  .contact-layout { grid-template-columns: 1fr; }
  .contact-info { position: static; }
}
@media (max-width: 768px) {
  .nav__links, .nav__cta, .nav__phone { display: none; }
  .nav__hamburger { display: flex; }
  .services-grid { grid-template-columns: 1fr; }
  .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }
  .process-grid { grid-template-columns: 1fr 1fr; }
  .process-grid::before { display: none; }
  .stats-grid { grid-template-columns: 1fr; }
  .stat-item:not(:last-child)::after { width: 60px; height: 1px; top: auto; right: auto; bottom: 0; left: 50%; transform: translateX(-50%); }
  .testimonials-grid { grid-template-columns: 1fr; }
  .portfolio-grid { grid-template-columns: 1fr 1fr; }
  .portfolio-item--wide { grid-column: span 1; aspect-ratio: 4/3; }
  .trust-row { grid-template-columns: 1fr; }
  .service-block, .about-founder { grid-template-columns: 1fr; }
  .service-block--reverse { direction: ltr; }
  .service-block__image { min-height: 300px; }
  .mobile-phone-bar { display: flex; }
  .footer { padding-bottom: calc(70px + env(safe-area-inset-bottom, 0px)); }
  /* Four items wrapping into a single flex column ran to 226px -- a third of
     the hero on an SE -- and the fixed call bar covered the bottom 60px, so
     the fourth item ("Free no-obligation quotes") was never visible on any
     phone. A 2x2 grid halves the height; the bottom padding clears the bar. */
  .hero__strip-inner {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-sm) var(--space-md);
    padding: 1rem var(--space-md);
    padding-bottom: 1rem;
    align-items: start;
  }
  /* The one-line rule above is a desktop rule. Here the strip is a 2x2 grid
     with roughly half the width per cell, so nowrap would push the page
     sideways -- these items are meant to wrap at this size. */
  .hero__strip-item { align-items: flex-start; white-space: normal; }
  .hero__strip-icon { margin-top: 2px; }
  .hero__strip-divider { display: none; }
  .msf__body { padding: var(--space-md); }
  .msf__footer { padding: var(--space-sm) var(--space-md) var(--space-md); }
  .form-row { grid-template-columns: 1fr; }
  .service-options, .budget-options { grid-template-columns: 1fr; }
  .values-grid { grid-template-columns: 1fr 1fr; }
  /* The trust strip is absolutely positioned at the hero's bottom edge and
     measures 226px once its four items stack (measured 375-430px wide).
     160px of clearance left the sub-heading clipped and BOTH hero buttons
     completely underneath it on every iPhone size. 226 + a 24px gap.
     Tied to the strip's height: if its contents change, re-measure. */
  /* Two classes deliberately. The element is class="hero__content container",
     and the 480px block below resets .container to `padding: 0 var(--space-sm)`
     -- a shorthand, so it zeroes padding-bottom, and it wins on source order.
     That is why the hero had NO bottom clearance on any phone <= 480px and both
     buttons sat underneath the strip. (0,2,0) beats (0,1,0) whatever the order. */
  /* Clearance tracks the measured strip: 139px on a tall phone (no pill
     overlap) and 213px on a short one (pill overlaps, clearance kept),
     plus a 24px gap in both cases. Over-padding here does not just waste
     space -- it pushes the hero text up and reopens the empty band at the
     top that section 34 exists to close. */
  .hero__content.container { padding-top: var(--space-lg); padding-bottom: 163px; }
  /* The sub-heading's 40px bottom margin is desktop rhythm. On a 667px SE the
     hero is over-constrained and this is the cheapest 16px available. */
  .hero__sub { margin-bottom: var(--space-md); }
}
@media (max-width: 480px) {
  .container { padding: 0 var(--space-sm); }
  .footer__grid { grid-template-columns: 1fr; gap: var(--space-lg); }
  .portfolio-grid { grid-template-columns: 1fr; }
  .portfolio-item--wide { grid-column: span 1; }
  .process-grid { grid-template-columns: 1fr; }
  .values-grid { grid-template-columns: 1fr; }
  .ba-details__specs { grid-template-columns: 1fr; }
  :root { --space-2xl: 4rem; }
}


/* ============================================================
   26. ICONS
   Inline SVG, 24x24 grid, 1.5px stroke, inherits colour via
   currentColor. Sizes come from the width/height attributes in
   the markup; these rules only handle flow and alignment.
   ============================================================ */
.ico { display: inline-block; vertical-align: middle; flex-shrink: 0; }
.value-card__icon .ico { display: block; margin: 0 auto; }
.trust-card__icon,
.footer__contact-icon { line-height: 0; }
.footer__social .ico { display: block; }


/* ============================================================
   27. EXPLORABLE STEP 2
   Pickers that answer back. Chips are real <button>s, so they
   are keyboard-operable and announced -- the old <div>s were
   neither.
   ============================================================ */
.msf__step-intro { font-size: var(--text-sm); color: var(--text-light); margin-bottom: var(--space-md); }
.form-label__hint { text-transform: none; letter-spacing: 0; font-weight: 500; opacity: 0.7; margin-left: 6px; }

.chip-row { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.chip {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--ink);
  background: var(--surface);
  border: 1.5px solid var(--stone-dark);
  border-radius: var(--radius-full);
  padding: 0.5rem 0.9rem;
  cursor: pointer;
  transition: var(--transition);
}
.chip:hover { border-color: var(--brass); background: rgba(196,154,108,0.06); }
.chip.selected { border-color: var(--brass); background: rgba(196,154,108,0.10); color: var(--heading); font-weight: 600; }

/* <button> needs the reset the old <div>s got for free */
.service-option, .budget-option { font-family: var(--font-body); background: var(--surface); width: 100%; display: block; }
.service-option { font-size: inherit; }

.chip:focus-visible,
.budget-option:focus-visible,
.service-option:focus-visible {
  outline: none;
  border-color: var(--brass);
  box-shadow: 0 0 0 3px rgba(196,154,108,0.22);
}

.field-echo {
  font-size: var(--text-xs);
  line-height: 1.6;
  color: var(--text-light);
  margin-top: 0.65rem;
  padding: 0.65rem 0.8rem;
  background: var(--stone);
  border-left: 2px solid var(--brass);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  animation: echo-in 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}
.field-echo[hidden] { display: none; }
.field-echo strong { color: var(--heading); font-weight: 600; }
.field-echo--check { border-left-color: var(--mist); }
.field-echo--miss { border-left-color: var(--slate-light); background: var(--stone-dark); }
@keyframes echo-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}

.msf__recap {
  margin-top: var(--space-lg);
  padding: var(--space-md);
  background: var(--slate);
  border-radius: var(--radius-md);
}
.msf__recap[hidden] { display: none; }
.msf__recap-label {
  font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--brass-text); font-weight: 700; margin-bottom: 6px;
}
.msf__recap-text { font-family: var(--font-display); font-size: 1.15rem; line-height: 1.5; color: var(--white); }
.msf__recap-text strong { color: var(--brass-light); font-weight: 600; }


/* ============================================================
   28. ROOM-FILTERED SCOPE
   Scope chips are grouped under the rooms chosen in step 1, so
   nobody is offered a wet room conversion for a splashback.
   ============================================================ */
.scope-group__title {
  font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--brass-text);
  margin: var(--space-md) 0 0.5rem;
}
.scope-group__title:first-child { margin-top: 0; }
.scope-empty {
  font-size: var(--text-sm); color: var(--text-light);
  padding: 0.85rem 1rem; background: var(--stone); border-radius: var(--radius-md);
}


/* ============================================================
   29. OPTIONAL-BUT-PERSUADED DETAIL FIELD
   The free-text box stays optional. It explains its own value,
   and nudges once if skipped -- it never blocks.
   ============================================================ */
.form-help {
  font-size: var(--text-xs); line-height: 1.65;
  color: var(--text-light); margin: -2px 0 0.65rem;
}
.form-help strong { color: var(--heading); font-weight: 600; }
.field-echo--nudge {
  border-left-color: var(--brass-dark);
  background: rgba(196, 154, 108, 0.10);
  color: var(--heading);
}


/* ============================================================
   30. REAL IMAGERY
   ============================================================ */
/* --- Responsive logo system -------------------------------------------
   The supplied file is the full lockup: script (46% of its height),
   "& Bathrooms Ltd" (9%), a rule (1.4%) and the tagline (6.8%), with
   77px of gaps. Shown whole in a 72px nav the script gets ~21px and the
   rest is unreadable, which is why it read as "small". So the nav uses a
   crop of the first two bands only -- same box height, script nearly
   twice the size. The footer, which has the room, keeps the full lockup
   including the tagline.
   Replace with proper exported variants when a designer cuts them.
   ---------------------------------------------------------------------- */
.brand-logo {
  display: block;
  position: relative;
  overflow: hidden;
  height: 50px;
  width: 131.6px;
  flex-shrink: 0;
}
.brand-logo img {
  position: absolute;
  max-width: none;
  width: 149.3px;
  height: auto;
  left: -8.8px;
  top: -1.8px;
}

/* footer has the vertical room for the whole thing, tagline and all */
.footer__brand-logo .brand-logo {
  height: 108px;
  width: 200px;
  margin-bottom: var(--space-sm);
}
.footer__brand-logo .brand-logo img { width: 200px; left: 0; top: 0; }

@media (max-width: 640px) {
  .brand-logo { height: 40px; width: 105.3px; }
  .brand-logo img { width: 119.5px; left: -7.0px; top: -1.4px; }
  /* the full footer lockup at 200px will not fit a half-width column on a phone */
  .footer__brand-logo .brand-logo { height: 76px; width: 141px; }
  .footer__brand-logo .brand-logo img { width: 141px; }
}

.ba-figure { margin: 0; border-radius: var(--radius-lg); overflow: hidden; box-shadow: var(--shadow-lg); }
.ba-figure img { display: block; width: 100%; height: 100%; object-fit: cover; aspect-ratio: 4 / 3; }


/* ============================================================
   31. ENQUIRY CHAT
   The layout is the one this customer already reads fluently
   from Messenger -- avatar, bubbles, typing dots, quick
   replies, composer. Brand colours, not Messenger's, so it
   reads as ours rather than as a copy.
   ============================================================ */
.chat {
  background: var(--surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.chat__header {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.9rem var(--space-md);
  border-bottom: 1px solid var(--stone-dark);
  background: var(--surface);
}
.chat__avatar {
  width: 42px; height: 42px; border-radius: 50%;
  background: var(--slate);
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0; overflow: hidden;
}
.chat__avatar img { width: 30px; height: auto; display: block; }
.chat__who { display: flex; flex-direction: column; min-width: 0; }
.chat__name { font-weight: 700; font-size: var(--text-sm); color: var(--heading); }
.chat__status {
  font-size: var(--text-xs); color: var(--text-light);
  display: flex; align-items: center; gap: 0.4rem;
}
.chat__status::before {
  content: ''; width: 7px; height: 7px; border-radius: 50%;
  background: var(--mist); flex-shrink: 0;
}

.chat__log {
  /* grows with the conversation, then scrolls -- a fixed height left a big
     blank area above the first few messages */
  min-height: 240px;
  max-height: min(50vh, 430px);
  justify-content: flex-end;
  overflow-y: auto;
  /* Stop the page behind dragging once the conversation hits its end. */
  overscroll-behavior: contain;
  padding: var(--space-md);
  display: flex; flex-direction: column; gap: 0.35rem;
  background: var(--surface);
}

.msg { display: flex; align-items: flex-end; gap: 0.5rem; max-width: 88%; }
.msg--in { align-self: flex-start; }
.msg--out { align-self: flex-end; flex-direction: row-reverse; }
.msg--stacked .msg__avatar { visibility: hidden; }
.msg__avatar {
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--slate); padding: 4px;
  object-fit: contain; flex-shrink: 0;
}
.msg__bubble {
  padding: 0.65rem 0.9rem;
  border-radius: 18px;
  font-size: var(--text-sm);
  line-height: 1.55;
  animation: msg-in 0.26s cubic-bezier(0.4, 0, 0.2, 1);
}
.msg--in .msg__bubble {
  background: var(--stone); color: var(--ink);
  border-bottom-left-radius: 5px;
}
.msg--out .msg__bubble {
  background: var(--brass); color: var(--white);
  border-bottom-right-radius: 5px; font-weight: 500;
}
.msg__bubble strong { color: var(--heading); font-weight: 700; }
.msg--out .msg__bubble strong { color: var(--white); }
.msg__bubble a { color: var(--brass-text); font-weight: 600; text-decoration: underline; }
@keyframes msg-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

.typing { display: inline-flex; gap: 4px; padding: 2px 0; }
.typing i {
  width: 7px; height: 7px; border-radius: 50%; background: var(--mist);
  animation: typing 1.1s infinite ease-in-out;
}
.typing i:nth-child(2) { animation-delay: 0.16s; }
.typing i:nth-child(3) { animation-delay: 0.32s; }
@keyframes typing {
  0%, 60%, 100% { transform: translateY(0); opacity: 0.45; }
  30%           { transform: translateY(-4px); opacity: 1; }
}

.chat__composer {
  border-top: 1px solid var(--stone-dark);
  padding: 0.85rem var(--space-md);
  background: var(--surface);
  min-height: 68px;
  max-height: 210px;
  overflow-y: auto;
  display: flex; flex-direction: column; gap: 0.6rem;
}

.quickreplies { display: flex; flex-wrap: wrap; gap: 0.45rem; }
.qr {
  font-family: var(--font-body);
  font-size: var(--text-sm); font-weight: 500;
  color: var(--brass-text);
  background: var(--surface);
  border: 1.5px solid var(--brass);
  border-radius: var(--radius-full);
  padding: 0.5rem 0.95rem;
  cursor: pointer;
  transition: var(--transition);
}
.qr:hover { background: rgba(196, 154, 108, 0.10); }
.qr--on { background: var(--brass); border-color: var(--brass); color: var(--on-brass); font-weight: 600; }
.qr--go { position: sticky; bottom: -0.85rem; align-self: flex-start; background: var(--slate); border-color: var(--slate); color: var(--white); font-weight: 600; }
.qr--go { box-shadow: 0 -6px 10px -6px rgba(0,0,0,0.10); }
.qr--go:hover:not(:disabled) { background: var(--slate-mid); }
.qr--go:disabled { opacity: 0.4; cursor: default; border-color: var(--stone-dark); background: var(--stone); color: var(--text-light); }
.qr--skip { align-self: flex-start; border-color: var(--stone-dark); color: var(--text-light); }
.qr:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(196, 154, 108, 0.25); }

.composer { display: flex; gap: 0.5rem; align-items: center; }
.composer__input {
  flex: 1; min-width: 0;
  font-family: var(--font-body); font-size: var(--text-body);
  color: var(--heading);
  background: var(--stone);
  border: 1.5px solid transparent;
  border-radius: var(--radius-full);
  padding: 0.7rem 1.1rem;
  transition: var(--transition);
}
.composer__input:focus { outline: none; background: var(--surface); border-color: var(--brass); }
.composer__send {
  width: 42px; height: 42px; flex-shrink: 0;
  border-radius: 50%; border: none;
  background: var(--brass); color: var(--white);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; transition: var(--transition);
}
.composer__send:hover { background: var(--brass-dark); }
.composer__send:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(196, 154, 108, 0.3); }

.nowrap { white-space: nowrap; }
.chat__done { font-size: var(--text-xs); color: var(--text-light); text-align: center; margin: 0; }

@media (max-width: 640px) {
  .chat__log { height: 62vh; }
  .msg { max-width: 94%; }
}


/* ============================================================
   32. SOCIAL FEEDS
   Click-to-load: the placeholder is ours, the third-party
   iframe only appears once the visitor asks for it.
   ============================================================ */
.feeds__intro { color: var(--text-light); max-width: 460px; margin: var(--space-sm) auto 0; }
.feed-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-md);
  margin-top: var(--space-xl);
}
.feed {
  border: 1px solid var(--stone-dark);
  border-radius: var(--radius-lg);
  background: var(--surface);
  overflow: hidden;
  min-height: 340px;
  display: flex;
}
.feed__cover {
  flex: 1;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center;
  padding: var(--space-lg) var(--space-md);
  gap: 0.5rem;
}
.feed__icon {
  width: 48px; height: 48px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--stone); color: var(--heading);
  margin-bottom: 0.25rem;
}
.feed__icon--facebook { background: #1877F2; color: #fff; }
.feed__icon--instagram { background: linear-gradient(45deg, #F58529, #DD2A7B, #8134AF); color: #fff; }
.feed__icon--tiktok { background: var(--slate); color: #fff; }
.feed__label { font-family: var(--font-display); font-size: 1.35rem; color: var(--heading); }
.feed__blurb { font-size: var(--text-sm); color: var(--text-light); margin-bottom: 0.6rem; }
.feed__consent { font-size: var(--text-xs); color: var(--text-light); max-width: 230px; line-height: 1.5; }
.feed__direct { font-size: var(--text-xs); color: var(--brass-text); font-weight: 600; }
/* Loaded state: break out of the grid and out of .container, to 80vw. */
.feed--loaded {
  grid-column: 1 / -1;
  min-height: 0;
  border-color: transparent;
  background: transparent;
  padding: 0;
  width: 80vw;
  max-width: 1180px;
  position: relative;
  left: 50%;
  transform: translateX(-50%);
}
.feed__stage {
  display: flex;
  gap: var(--space-lg);
  align-items: stretch;
  background: var(--stone);
  border-radius: var(--radius-lg);
  padding: var(--space-lg);
  width: 100%;
}
.feed__aside {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column; justify-content: center;
  gap: 0.7rem;
  padding: var(--space-sm) var(--space-md);
}
.feed__aside-title { font-family: var(--font-display); font-size: 1.9rem; color: var(--heading); line-height: 1.15; }
.feed__aside-body { color: var(--text-light); line-height: 1.7; max-width: 42ch; }
.feed__aside .btn { align-self: flex-start; margin-top: 0.4rem; }
/* 500px is Meta's ceiling for the Page Plugin -- it will not render wider. */
.feed__frame {
  width: 500px; max-width: 100%; height: 640px;
  border: 0; display: block; flex-shrink: 0;
  border-radius: var(--radius-md);
  background: var(--white);
  box-shadow: var(--shadow-sm);
}
@media (max-width: 900px) {
  .feed--loaded { width: 92vw; }
  .feed__stage { flex-direction: column; padding: var(--space-md); }
  .feed__aside { padding: 0; text-align: center; }
  .feed__aside .btn { align-self: center; }
  .feed__frame { width: 100%; margin: 0 auto; }
}


/* ============================================================
   33. TOUCH DEVICES
   Everything above this point was written for a mouse. A finger
   cannot hover, so a :hover rule on a phone either never fires or
   -- worse -- fires on tap and then sticks until you touch
   something else.

   The portfolio was the bad case: .portfolio-item__info sat at
   opacity 0 and only appeared on hover, so every phone showed
   twelve unlabelled photographs.

   (hover: none) is the honest test for "this pointer cannot
   hover". Overriding here rather than rewriting the 31 rules
   above keeps the desktop design exactly as it was.
   ============================================================ */
@media (hover: none) {

  /* Captions are content, not a reward for hovering. Always visible,
     over a gradient scrim so the text holds up against a pale tile. */
  .portfolio-item__overlay {
    background: linear-gradient(to top,
                rgba(20, 22, 23, 0.74) 0%,
                rgba(20, 22, 23, 0.30) 44%,
                rgba(20, 22, 23, 0) 74%);
  }
  .portfolio-item__info { opacity: 1; transform: none; }

  /* Same bug, second place: the three home-page service cards collapse their
     description to max-height:0 and only open it on hover, so on a phone the
     cards read as a title and a link with the selling copy missing entirely.
     max-height stays generous rather than `none` so the desktop transition
     still has something to animate towards. */
  .service-card__body {
    max-height: 200px;
    opacity: 1;
    margin-bottom: var(--space-sm);
  }
  .service-card__overlay {
    background: linear-gradient(to top,
                rgba(20, 22, 23, 0.94) 0%,
                rgba(20, 22, 23, 0.55) 45%,
                rgba(20, 22, 23, 0.12) 100%);
  }

  /* Lift-and-scale effects leave the element stranded in its raised
     state after a tap. Neutralise the movement only -- the colour
     changes are harmless and the shadows still read as a press. */
  .btn--brass:hover { transform: none; }
  .testimonial-card:hover { transform: none; }
  .portfolio-item:hover .portfolio-item__img { transform: none; }
  .service-card:hover .service-card__bg { transform: none; }

  /* Give a finger the feedback a cursor was getting: a short press.
     0.3s (the shared --transition) is far too slow to read as touch,
     so transform gets its own faster curve while colour keeps the
     original timing. */
  .btn,
  .filter-btn,
  .qr,
  .chip,
  .composer__send {
    transition: transform 0.12s cubic-bezier(0.4, 0, 0.2, 1),
                background var(--transition),
                border-color var(--transition),
                color var(--transition);
  }
  .btn:active,
  .filter-btn:active,
  .qr:active,
  .chip:active,
  .composer__send:active { transform: scale(0.97); }

  .portfolio-item:active .portfolio-item__img { transform: scale(1.03); }
}


/* ============================================================
   34. MOBILE REFINEMENT
   Everything in this section is phone-only polish. It sits after
   the responsive block deliberately, so it wins on source order
   without needing raised specificity, and the whole section can
   be deleted to return the site to where it was.

   Measured before this section, at 393px wide:
     index    10.7 screens      testimonials  1541px (1.8 screens)
     projects 11.4 screens      service cards 1575px (1.8 screens)
     about    10.0 screens      footer        1158px (1.4 screens)
   ============================================================ */
@media (max-width: 768px) {

  /* --- 34.1 spacing and radius --------------------------------------- */
  /* --space-2xl only dropped to 4rem below 480px, so phones in the
     481-768 band carried full desktop 112px gaps between sections. */
  :root { --space-2xl: 4.25rem; }
  /* 16px corners read timid at arm's length. */
  :root { --radius-lg: 22px; }

  /* --- 34.2 carousels ------------------------------------------------ */
  /* Three cards stacked vertically is a desktop grid apologising for a
     phone. As snap carousels they take one screen instead of nearly two,
     and 86% columns leave the next card peeking -- which is the thing
     that actually tells a thumb there is more to the side. */
  .testimonials-grid,
  .services-grid {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: 86%;
    align-items: stretch;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .testimonials-grid { padding-bottom: var(--space-sm); }
  .testimonials-grid::-webkit-scrollbar,
  .services-grid::-webkit-scrollbar { display: none; }
  .testimonial-card,
  .service-card { scroll-snap-align: start; }
  /* The cards set their own height in a row; let the tallest lead. */
  .testimonial-card { height: auto; }

  /* Q10, second half. The 86% column above exists so the next card peeks --
     that peek is the only thing telling a thumb there is more to the side.
     But .reveal held cards 2 and 3 at opacity 0 until they were scrolled to,
     so the peek was blank: measured card 2 sitting 40px inside a 375px
     viewport, invisible. The affordance and the animation wanted the same
     pixels. The affordance wins -- inside a carousel the reveal is switched
     off, and the cards are simply there. (.services-grid never had .reveal
     on its children, which is why that carousel already peeked correctly.) */
  .js .testimonials-grid > .reveal,
  .js .services-grid > .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }

  /* --- 34.3 portfolio ------------------------------------------------ */
  /* Twelve photographs at one per row was 11.4 screens. Two columns
     brings the grid itself to roughly 1.5, and the tiles are still
     large enough to read at 180px wide. */
  .portfolio-grid { grid-template-columns: 1fr 1fr; }
  .portfolio-item__overlay { padding: var(--space-sm); }
  .portfolio-item__title { font-size: 0.95rem; line-height: 1.25; }
  .portfolio-item__tag { font-size: 0.56rem; letter-spacing: 0.07em; }

  /* --- 34.4 hero ----------------------------------------------------- */
  /* 0.15em tracking pushed the kicker onto a second line at every phone
     width. 0.08em fits one line at 375px and shouts less at 12px. */
  .hero__kicker { letter-spacing: 0.08em; }

  /* The buttons are inline-flex, so stacked they were two different
     widths, ragged against the left margin. Full width reads deliberate. */
  .hero__actions {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-sm);
  }
  .hero__actions .btn { width: 100%; justify-content: center; }

  /* --- 34.5 the call bar becomes a floating pill --------------------- */
  /* A full-bleed square-cornered slab is the most dated element on the
     page. Inset, rounded and blurred, it reads as an app affordance --
     and it stops visually welding itself to the bottom of the screen. */
  .mobile-phone-bar {
    left: var(--space-sm);
    right: var(--space-sm);
    bottom: calc(var(--space-sm) + env(safe-area-inset-bottom, 0px));
    padding: 0.5rem 0.6rem;
    padding-bottom: 0.5rem;
    gap: 0.5rem;
    justify-content: space-between;
    border-radius: var(--radius-full);
    background: rgba(196, 154, 108, 0.94);
    -webkit-backdrop-filter: blur(14px) saturate(1.2);
    backdrop-filter: blur(14px) saturate(1.2);
    box-shadow: 0 10px 30px rgba(20, 22, 23, 0.32);
  }
  /* Clearance for the pill: ~54px tall + 16px inset + breathing room. */
  .footer { padding-bottom: calc(88px + env(safe-area-inset-bottom, 0px)); }

  /* --- 34.6 chat ----------------------------------------------------- */
  /* The log is bottom-aligned inside its box, so a 240px floor left a
     large empty field above the opening message. */
  .chat__log { min-height: 150px; }

  /* --- 34.7 footer --------------------------------------------------- */
  /* 1.4 screens of desktop link rhythm. The two short link columns sit
     side by side; brand and contact keep the full width. */
  .footer__grid {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-lg) var(--space-md);
  }
  .footer__grid > :first-child,
  .footer__grid > :last-child { grid-column: 1 / -1; }
  .footer__links { gap: 0.6rem; }

  /* --- 34.8 enquiry quick replies ------------------------------------ */
  /* The composer caps at 210px and scrolls its own contents. At desktop width
     the first question's eleven chips wrap to 195px and fit inside that cap;
     in a phone-width column the same chips wrap to 368px, so four of them --
     "Outside", "Business premises", "Rental property", "Somewhere else" --
     sat below a fold, inside a box with no visible scrollbar to advertise
     that there was more. Nobody scrolls a container they cannot tell is
     scrollable, so those were choices the customer simply never had.
     Let the composer take the height its content needs and let the PAGE
     scroll, which is the scroll people already know is there. */
  .chat__composer {
    max-height: none;
    overflow-y: visible;
    /* The floating call pill owns the bottom ~70px of the viewport. Without
       clearance the confirm button can come to rest underneath it, and
       elementFromPoint at the button's centre returned .mobile-phone-bar on a
       667px SE -- meaning a tap on what looks like "confirm" would have dialled
       Scott instead. Same clearance the footer already carries. */
    padding-bottom: calc(0.85rem + 84px + env(safe-area-inset-bottom, 0px));
  }

  /* The confirm button was sticky so it stayed reachable above the composer's
     own scroll, and carried a shadow hinting at content below it. With no
     inner scroll there is nothing to stick to and the hint would be a lie. */
  .qr--go {
    position: static;
    box-shadow: none;
    /* Padding below the button does not lift it: scroll-into-view aligns the
       element's own box to the viewport edge, which is where the pill sits.
       scroll-margin is the mechanism that reserves that space for scrolling. */
    scroll-margin-bottom: 90px;
  }
  .quickreplies, .composer { scroll-margin-bottom: 90px; }

  /* These chips are the primary tap targets of the entire enquiry flow and
     every one measured under 44px. Same treatment the filter buttons got:
     the box grows, the type does not move. */
  .qr {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
}

/* The hero keeps the full viewport on a 667px SE, which has nothing to
   spare, but trims on taller phones where ~450px of empty photograph sat
   above the kicker. The peek of the next section also signals the scroll. */
@media (max-width: 768px) and (min-height: 760px) {
  .hero { height: 88svh; }
}

/* On a tall phone the trimmed hero ends above the floating pill, so the strip
   needs no clearance and 78px of empty translucent band was showing under it.
   On a short phone the hero still runs to the bottom edge and the pill does
   overlap, so the clearance is kept exactly where it is earned. */
@media (max-width: 768px) and (max-height: 759px) {
  .hero__strip-inner {
    padding-bottom: calc(1rem + 84px + env(safe-area-inset-bottom, 0px));
  }
  .hero__content.container { padding-bottom: 237px; }
}


/* ============================================================
   35. DARK MODE
   Only tokens flip. --white and --slate stay literal on purpose:
   --white is text on dark sections in 38 places, --slate is a
   dark section's background in 18. Inverting either would put
   text and its own background at the same value.

   What flips is the semantic pair added in section 1:
     --surface   page and card grounds
     --heading   text that is slate in light mode
   plus the stone / ink / mist scales, which are unambiguous.

   Three things deliberately keep real white, because they sit on
   dark ground in BOTH schemes:
     .nav__hamburger span   the bars on the dark nav
     .supplier-logo         opaque logo JPEGs need a light card
     .feed__frame           Meta's embed is a light document
   ============================================================ */
@media (prefers-color-scheme: dark) {
  :root {
    --surface:     #14181A;   /* page ground */
    --heading:     #F0EDE8;   /* was slate text */
    --ink:         #DBD7D2;
    --text-light:  #97A1A0;

    /* Cards lift OFF the page in dark mode, so stone goes lighter than
       surface -- the inverse of its light-mode relationship. */
    --stone:       #1C2123;
    --stone-dark:  #2B3134;

    /* The deliberately-dark sections stay the darkest band so the page
       keeps the same rhythm it has in light mode. */
    --slate:       #0D1011;
    --slate-mid:   #14181A;
    --slate-light: #232829;

    /* Brass lifted slightly: #C49A6C is under 4.5:1 on this ground. */
    --brass:       #D3AA7D;
    --brass-dark:  #B98F63;
    --brass-light: #E3C6A3;

    /* The light-mode text ramp is walked DOWN for a light ground, so it
       inverts here: #856949 measures 2.58-3.74 on these dark grounds. The
       lifted --brass already measures 6.17-8.93 as text, so both text
       tokens simply become it -- no third value to keep in sync. */
    --brass-text:    #D3AA7D;
    --brass-text-lg: #D3AA7D;
    --mist:        #94A5A3;
    --mist-light:  #B7C4C2;

    /* Brass is lightened above so it works as TEXT on a dark ground, which
       makes white-on-brass worse rather than better -- measured 2.14:1 on
       the primary button. A dark foreground on a light accent is both the
       accessible and the conventional choice for a dark UI. */
    --on-brass:    #16191A;
  }

  /* The footer hardcodes #141618, which lands within a shade of the dark
     page ground and loses its edge. Push it to the darkest band instead. */
  .footer {
    background: var(--slate);
    border-top: 1px solid var(--stone-dark);
  }

  /* Photographs carry their own light. Taking a little out stops them
     glaring against a dark page without washing the colour out. */
  .portfolio-item__img,
  .service-card__bg,
  .service-block__image img,
  .ba-figure img { filter: brightness(0.92); }

  /* Shadows tuned for a white page vanish on a dark one; a hairline
     does the separating instead. */
  .chat { border: 1px solid var(--stone-dark); }
}


/* ============================================================
   36. CRAFT MOTION
   Brand-level polish, chosen so nothing here depends on content
   that does not exist yet. Four pieces, one idea: make the craft
   visible in how the site behaves.

     36.1  the logo writes itself on               (it is a script)
     36.2  a slow specular sweep across the CTA    (it is brass)
     36.3  portfolio tiles settle into place       (they are tiles)
     36.4  pages change like a tile sliding home   (view transitions)

   Every part is transform/opacity only, so it composites on the
   GPU and costs no layout. Every part is switched off under
   prefers-reduced-motion at the end of this section.
   ============================================================ */

/* --- 36.1 the logo writes itself -------------------------------------- */
/* The mark is a script signature, so it reveals left to right the way it
   would have been written rather than fading in like a stock image.
   Nav only: the footer logo would perform this below the fold, unseen. */
@keyframes logoInk {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}
.nav__logo .brand-logo img {
  animation: logoInk 1.05s cubic-bezier(0.22, 0.61, 0.36, 1) 0.12s both;
}

/* --- 36.2 brass catches the light ------------------------------------- */
/* A specular sweep, not a glow. It runs 1.1s in every 7s and sits still
   the rest of the time -- long enough to read as light moving across
   metal, rare enough that it never nags. Only the primary CTA. */
@keyframes brassSheen {
  0%, 68%   { transform: translateX(-140%) skewX(-18deg); }
  84%, 100% { transform: translateX(240%)  skewX(-18deg); }
}
.btn--brass {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.btn--brass::after {
  content: '';
  position: absolute;
  top: -20%;
  bottom: -20%;
  left: 0;
  width: 38%;
  pointer-events: none;
  background: linear-gradient(100deg,
              rgba(255, 255, 255, 0) 0%,
              rgba(255, 255, 255, 0.40) 50%,
              rgba(255, 255, 255, 0) 100%);
  transform: translateX(-140%) skewX(-18deg);
  animation: brassSheen 7s ease-in-out infinite;
}

/* --- 36.3 tiles settle into place ------------------------------------- */
/* Each tile drops the last few pixels and seats itself, staggered across
   the row, the way a tiler works left to right. --i is set per tile in
   main.js and repeats every third item, which reads correctly at both
   the two-column phone grid and the three-column desktop one.
   Gated on .js exactly like .reveal: with no JavaScript the tiles are
   simply visible rather than permanently hidden. */
@keyframes tileSet {
  from { opacity: 0; transform: translateY(15px) scale(0.965); }
  to   { opacity: 1; transform: none; }
}
.js .portfolio-grid .portfolio-item { opacity: 0; }
.js .portfolio-grid .portfolio-item.tile-set {
  animation: tileSet 0.62s cubic-bezier(0.22, 0.61, 0.36, 1) both;
  animation-delay: calc(var(--i, 0) * 85ms);
}

/* --- 36.4 pages change like a tile going home ------------------------- */
/* Cross-document view transitions. Progressive enhancement in the truest
   sense: browsers without support navigate exactly as they did before,
   and nothing needs a JavaScript router. Kept short -- a page change that
   announces itself for half a second reads as slow, not smooth. */
@view-transition { navigation: auto; }

@keyframes pageOut { to   { opacity: 0; transform: scale(0.994); } }
@keyframes pageIn  { from { opacity: 0; transform: translateY(10px); } }

::view-transition-old(root) {
  animation: pageOut 190ms cubic-bezier(0.4, 0, 1, 1) both;
}
::view-transition-new(root) {
  animation: pageIn 280ms cubic-bezier(0.22, 0.61, 0.36, 1) both;
}

/* --- 36.5 all of it, off, when the system asks ------------------------ */
@media (prefers-reduced-motion: reduce) {
  .nav__logo .brand-logo img { animation: none; clip-path: none; }
  .btn--brass::after { animation: none; opacity: 0; }
  .js .portfolio-grid .portfolio-item { opacity: 1; }
  .js .portfolio-grid .portfolio-item.tile-set { animation: none; }
  ::view-transition-old(root),
  ::view-transition-new(root) { animation: none; }
  @view-transition { navigation: none; }
}
