/* South Coast Roof Restoration
   Charcoal ground, off white type, one terracotta accent reserved for the call
   to action. Self hosted webfonts in fonts/, four switchable options.
   Mobile first: every media query steps up, never down. */

/* ---------------------------------------------------------------- tokens */
:root {
  /* THREE GROUNDS, not two.
       deep   #17191B  charcoal, the hero and the footer
       stone  #E3DED6  the middle value step, section.mid
       cream  #F7F4EF  the lightest band, section.light
     Stone is the charcoal lightened to L* 88.7 and rotated warm (HSL 37, 19%,
     86.5%) so it sits with the cream instead of reading as a cold blue grey.
     The page never puts deep next to cream: every seam is one rung on that
     ladder. See HANDOVER.md, "Three grounds". */
  --deep:      #17191B;
  --stone:     #E3DED6;
  --cream:     #F7F4EF;

  --bg:        var(--deep);
  --bg-alt:    #1C1F22;   /* alternating section ground */
  --surface:   #212427;   /* cards */
  --surface-2: #272B2F;   /* card hover, inputs */
  --line:      #33383D;
  --line-soft: #2A2E32;
  --line-strong: #6A7178; /* 3.6:1 on --bg, for a border that carries meaning */

  --ink:       #EFEDEA;   /* 15.1:1 on --bg */
  --ink-muted: #A9AFB6;   /* 8.0:1 on --bg  */
  --ink-dim:   #8B9198;   /* 5.5:1 on --bg, labels only, never body copy */

  --err-text:  #FF9C8E;   /* 8.7:1 on --bg */
  --err-line:  #FF8A7A;

  --star:        #F5B301; /* 9.5:1 on --bg. Gold, and gold whatever the accent */

  /* The accent is a complete set, switched by a class on <html>. The rust
     values are repeated on :root so the page is still right if the class is
     missing. See the ACCENT block below. */
  --accent:         #A9502A;
  --accent-hi:      #8C3F20;
  --accent-on-dark: #E08A5F;
  --accent-on-light:#8E3F1F;
  --accent-wash:    rgba(169, 80, 42, .14);
  --accent-text:    var(--accent-on-dark);

  /* the roof pitch motif: one geometry, three scales. 1:4 rise to half span,
     14 degrees, apex on the centre line. The large divider stretches that
     across the whole band, which flattens it to a few degrees on purpose. */
  --pitch-h: clamp(22px, 3.6vw, 66px);
  --pitch-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 96 24'%3E%3Cpath d='M3 21L48 9l45 12' fill='none' stroke='%23000' stroke-width='6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

  /* fine grain, so a flat cream band is not dead flat. Drawn once as an
     inline SVG data URI; img-src in the CSP allows data:. */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g)'/%3E%3C/svg%3E");

  --radius: 4px;
  --wrap: 1140px;
  --gutter: 20px;
  --tap: 44px;            /* minimum tap target */

  --font: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
          "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-head: var(--font);

  /* Every type decision runs through these, so a typeface option is a token
     block and not a hunt through the file. Option D leaves them as they are. */
  --body-size:   17px;
  --body-lh:     1.6;
  --body-track:  normal;
  --head-weight: 800;
  --head-lh:     1.15;
  --head-track:  -0.02em;
  --h1:          clamp(2rem, 6.4vw, 3.4rem);
  --h2:          clamp(1.5rem, 4.4vw, 2.25rem);
  --h3:          1.14rem;
  --h3-track:    -0.01em;
  --card-h2:     1.16rem;

}

/* ================================================================ FONTS */
/* Self hosted, latin subset, in fonts/. No font CDN: the CSP is
   style-src 'self' and self hosting is the house rule. Four options are wired
   up so the owner can compare them on the real page. Once one is picked,
   delete the other three token blocks, their @font-face rules and the woff2
   files they name, then move the winner's tokens onto :root. See HANDOVER.md,
   "Remove before go live". */

/* variable, weight axis 100 to 900 */
@font-face {
  font-family: "Archivo var";
  src: url("../fonts/archivo-var-latin.woff2") format("woff2");
  font-weight: 100 900; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Inter var";
  src: url("../fonts/inter-var-latin.woff2") format("woff2");
  font-weight: 100 900; font-style: normal; font-display: swap;
}
/* variable, optical size axis 9 to 144 as well as weight */
@font-face {
  font-family: "Fraunces var";
  src: url("../fonts/fraunces-var-latin.woff2") format("woff2");
  font-weight: 100 900; font-style: normal; font-display: swap;
}
/* static weights: Barlow has no variable build in this subset */
@font-face {
  font-family: "Barlow web";
  src: url("../fonts/barlow-400-latin.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Barlow web";
  src: url("../fonts/barlow-600-latin.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Barlow web";
  src: url("../fonts/barlow-700-latin.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Barlow Condensed web";
  src: url("../fonts/barlowcondensed-700-latin.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}

/* A, workwear grotesque. Archivo is squarish and site sign flavoured, so the
   headings get tighter tracking and a shorter line than the system stack
   wants. Inter underneath, tracked in a hair because Inter runs wide. */
html.font-a {
  --font: "Inter var", ui-sans-serif, system-ui, -apple-system,
          BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-head: "Archivo var", "Arial Narrow", Impact, sans-serif;
  --body-size:   17px;
  --body-lh:     1.62;
  --body-track:  -0.003em;
  --head-weight: 800;
  --head-lh:     1.06;
  --head-track:  -0.028em;
  --h1:          clamp(2.1rem, 6.8vw, 3.6rem);
  --h2:          clamp(1.55rem, 4.6vw, 2.35rem);
  --h3:          1.12rem;
  --h3-track:    -0.018em;
  --card-h2:     1.18rem;
}
/* the eyebrow and the small caps labels want the 700, not the 800 */
html.font-a .eyebrow,
html.font-a .area-col h3,
html.font-a .strip-label { font-weight: 700; letter-spacing: .13em; }

/* B, condensed confidence. Barlow Condensed buys about a third more
   characters per line, so the hero can go a long way up without wrapping.
   Condensed faces do not want negative tracking, they are tight already. */
html.font-b {
  --font: "Barlow web", ui-sans-serif, system-ui, -apple-system,
          BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-head: "Barlow Condensed web", "Barlow web", "Arial Narrow", sans-serif;
  --body-size:   17.5px;   /* Barlow sets small, this lands on the same read */
  --body-lh:     1.58;
  --body-track:  normal;
  --head-weight: 700;
  --head-lh:     1.0;
  --head-track:  -0.005em;
  --h1:          clamp(2.6rem, 9.4vw, 4.6rem);
  --h2:          clamp(1.9rem, 5.8vw, 3rem);
  --h3:          1.3rem;
  --h3-track:    0;
  --card-h2:     1.34rem;
}
/* ch is narrower in a condensed face, so the 20ch default would squeeze the
   hero line onto three rows and give the size back. 24ch holds it at two. */
html.font-b .hero h1 { max-width: 24ch; }
html.font-b .eyebrow, html.font-b .area-col h3, html.font-b .strip-label {
  letter-spacing: .16em;
}

/* C, quiet premium. Fraunces at 600 with the optical size axis live, so the
   big headings pick up the high contrast cut and the small ones do not fall
   apart. Serif caps read larger than a grotesque, so the sizes come down. */
html.font-c {
  --font: "Inter var", ui-sans-serif, system-ui, -apple-system,
          BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-head: "Fraunces var", Georgia, "Times New Roman", serif;
  --body-size:   17px;
  --body-lh:     1.65;
  --body-track:  -0.003em;
  --head-weight: 600;
  --head-lh:     1.12;
  --head-track:  -0.014em;
  --h1:          clamp(1.95rem, 5.9vw, 3.15rem);
  --h2:          clamp(1.45rem, 4.1vw, 2.1rem);
  --h3:          1.1rem;
  --h3-track:    -0.008em;
  --card-h2:     1.1rem;
}
/* the whole point of Fraunces: opsz follows the rendered size */
html.font-c h1, html.font-c h2, html.font-c h3 {
  font-optical-sizing: auto;
  font-synthesis-weight: none;
}
/* the display quote is set in the serif too, it is the one place it earns it */
html.font-c.rev-2 .review-quote {
  font-family: var(--font-head);
  font-weight: 500;
  letter-spacing: -0.008em;
  font-optical-sizing: auto;
}

/* D, current. The system stack, untouched, so there is something to
   compare against. Named for the switcher, deliberately empty. */
html.font-d { }

/* ============================================================== ACCENT */
/* Five complete accent sets, switched by a class on <html>, so the owner can
   flip between them on the live page. A set is five values, not one hue:

     --accent           the solid, for buttons and rules. White sits on it.
     --accent-hi        the hover, always darker so white still passes.
     --accent-on-dark   accent coloured type on the charcoal.
     --accent-on-light  accent coloured type on the stone and the cream.
     --accent-wash      the tint behind the form's success panel.

   Every pair is measured, not eyeballed. The numbers are in HANDOVER.md.
   The gold on the review stars is deliberately not part of this: stars stay
   gold whatever the accent is.

   Muted rust is the default and is repeated on :root, so the page is still
   right if the class is missing. Once one is picked, move it onto :root and
   delete the other four blocks. See HANDOVER.md, "Remove before go live". */

/* muted rust, close to the reference site's own */
html.accent-rust {
  --accent:          #A9502A;   /* white 5.4:1 */
  --accent-hi:       #8C3F20;   /* white 7.4:1 */
  --accent-on-dark:  #E08A5F;   /* 6.7:1 on charcoal */
  --accent-on-light: #8E3F1F;   /* 6.6:1 on cream, 5.4:1 on stone */
  --accent-wash:     rgba(169, 80, 42, .14);
}
/* heritage green. No other roofer in the Illawarra is using it, and it is the
   one accent that makes both the red and the charcoal roofs in the gallery
   sit forward instead of arguing with the accent. */
html.accent-green {
  --accent:          #2F6B4F;   /* white 6.3:1 */
  --accent-hi:       #24543E;   /* white 8.7:1 */
  --accent-on-dark:  #6FBF95;   /* 8.0:1 on charcoal */
  --accent-on-light: #245A41;   /* 7.3:1 on cream, 6.0:1 on stone */
  --accent-wash:     rgba(47, 107, 79, .14);
}
/* antique brass, which ties into the gold already on the review stars */
html.accent-brass {
  --accent:          #8A6A1E;   /* white 5.1:1 */
  --accent-hi:       #6E5416;   /* white 7.1:1 */
  --accent-on-dark:  #E0AE3C;   /* 8.6:1 on charcoal */
  --accent-on-light: #6E5416;   /* 6.5:1 on cream, 5.3:1 on stone */
  --accent-wash:     rgba(138, 106, 30, .14);
}
/* ink blue, the conventional trade choice. Here so it can be ruled out. */
html.accent-ink {
  --accent:          #2C5B8F;   /* white 7.0:1 */
  --accent-hi:       #22476F;   /* white 9.6:1 */
  --accent-on-dark:  #7FB4E8;   /* 8.1:1 on charcoal */
  --accent-on-light: #275181;   /* 7.4:1 on cream, 6.1:1 on stone */
  --accent-wash:     rgba(44, 91, 143, .14);
}
/* what the page had before, for the comparison */
html.accent-current {
  --accent:          #C24A16;   /* white 4.9:1 */
  --accent-hi:       #A83F12;   /* white 6.2:1 */
  --accent-on-dark:  #F2793B;   /* 6.4:1 on charcoal */
  --accent-on-light: #A83F12;   /* 5.7:1 on cream, 4.6:1 on stone */
  --accent-wash:     rgba(194, 74, 22, .14);
}

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

html {
  scroll-behavior: smooth;
  /* sticky header height plus a little air, so #anchors do not land under it */
  scroll-padding-top: 76px;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--body-size);
  line-height: var(--body-lh);
  letter-spacing: var(--body-track);
  overflow-x: hidden;             /* belt and braces against 375px overflow */
}

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

/* the inline <svg> sprite in the markup: present, referenced by <use>,
   never drawn */
.svg-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

h1, h2, h3 {
  font-family: var(--font-head);
  font-weight: var(--head-weight);
  line-height: var(--head-lh);
  letter-spacing: var(--head-track);
  margin: 0 0 .6em;
  text-wrap: balance;
}
h1 { font-size: var(--h1); }
h2 { font-size: var(--h2); }
h3 { font-size: var(--h3); letter-spacing: var(--h3-track); }
p  { margin: 0 0 1em; }
p:last-child { margin-bottom: 0; }

a { color: var(--accent-text); }
a:hover { color: var(--ink); }

/* one visible focus style for everything, never removed */
:focus-visible {
  outline: 3px solid var(--accent-text);
  outline-offset: 2px;
  border-radius: 2px;
}

/* visible to a screen reader, never drawn */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--accent); color: #fff; padding: 12px 18px;
  font-weight: 700; border-radius: 0 0 var(--radius) 0;
}
.skip:focus { left: 0; }

.wrap { width: 100%; max-width: var(--wrap); margin: 0 auto; padding: 0 var(--gutter); }

section { padding: 56px 0; position: relative; }
section.alt { background: var(--bg-alt); }

/* ................................................. the three grounds */
/* Every component reads its colour from these tokens, so redefining them on a
   section flips the whole band without touching a single component.

   The band order on the page is deep, stone, cream, stone, cream, stone,
   cream, stone, deep. No seam ever puts charcoal straight against cream. */

/* cream, the lightest band */
section.light {
  --bg:        var(--cream);
  --bg-alt:    #EFEBE3;
  --surface:   #FCFAF5;   /* ivory cards, never pure white */
  --surface-2: #F1EDE5;
  --line:      #C3C7CA;   /* charcoal tints, not warm tan */
  --line-soft: #DCDFE1;
  --line-strong: #81858A; /* 3.2:1 on cream */

  --ink:       #1A1C1E;   /* 15.6:1 on cream */
  --ink-muted: #55595E;   /*  6.4:1 on cream */
  --ink-dim:   #63686D;   /*  5.1:1 on cream */

  --err-text:  #B3261E;   /*  6.0:1 on cream */
  --err-line:  #B3261E;

  --accent-text: var(--accent-on-light);
  --star:        #9A6B08; /*  4.3:1 on cream, still gold */

  background-color: var(--bg);
  color: var(--ink);
}

/* stone, the middle value step between the charcoal and the cream */
section.mid {
  --bg:        var(--stone);
  --bg-alt:    #D9D3C9;
  --surface:   #F6F3ED;   /* cards lift off the stone */
  --surface-2: #EDE8E0;
  --line:      #C0B9AE;
  --line-soft: #D2CCC2;
  --line-strong: #7A746A; /* 3.4:1 on stone */

  --ink:       #1A1C1E;   /* 12.8:1 on stone */
  --ink-muted: #4E5257;   /*  5.9:1 on stone */
  --ink-dim:   #565B60;   /*  5.1:1 on stone */

  --err-text:  #B3261E;   /*  4.9:1 on stone */
  --err-line:  #B3261E;

  --accent-text: var(--accent-on-light);
  --star:        #9A6B08; /*  3.5:1 on stone */

  background-color: var(--bg);
  color: var(--ink);
}

/* ................................ a band is never one flat fill */
/* A shallow vertical gradient, a few percent of luminance over a long
   distance, so the band leans toward whatever sits above and below it rather
   than sitting there as a slab. Deliberately under the threshold where a ramp
   becomes visible as banding. */
section.light, section.mid {
  background-image:
    linear-gradient(180deg,
      rgba(26, 28, 30, .055) 0%,
      rgba(26, 28, 30, 0) 24%,
      rgba(26, 28, 30, 0) 74%,
      rgba(26, 28, 30, .05) 100%);
}
section.alt, .site-footer {
  background-image:
    linear-gradient(180deg,
      rgba(255, 255, 255, .035) 0%,
      rgba(255, 255, 255, 0) 38%,
      rgba(255, 255, 255, 0) 100%);
}

/* .................................................. the grain overlay */
/* Cream reads as dead flat on a good screen. This is fractal noise at 3.5%,
   drawn as an inline SVG data URI, sitting behind the content and taking no
   pointer events. */
section.light::after, section.mid::after {
  content: "";
  position: absolute; inset: 0;
  background-image: var(--grain);
  background-repeat: repeat;
  opacity: .035;
  pointer-events: none;
  z-index: 0;
}
section.light > .wrap, section.mid > .wrap { position: relative; z-index: 1; }

/* the few places a colour is written literally rather than through a token */
section.light .btn-ghost, section.mid .btn-ghost {
  background-image: linear-gradient(rgba(0, 0, 0, .05), rgba(0, 0, 0, .05));
}
section.light .btn-ghost:hover, section.mid .btn-ghost:hover,
section.light .btn-ghost:focus-visible,
section.mid .btn-ghost:focus-visible { background-color: transparent; }
section.light .coast, section.mid .coast { opacity: .18; }
@media (min-width: 760px) { section { padding: 84px 0; } }

.eyebrow {
  font-size: .78rem; font-weight: 700; letter-spacing: .14em;
  text-transform: uppercase; color: var(--accent-text);
  margin: 0 0 .7em;
}
.lead { color: var(--ink-muted); font-size: 1.06rem; max-width: 62ch; }

/* ============================================= the roof pitch motif */
/* One gable, three scales. The geometry is 1:4, rise to half span, apex on
   the centre line, straight slopes: 14 degrees.

   LARGE, the seam between two bands. It is a clip-path on a pseudo element
   that belongs to the LOWER band and rises into the band above it, so there
   are not two edges to line up. The element is two pixels taller than the
   rise and overlaps its own band by those two pixels, which is why a
   fractional --pitch-h can never open a hairline along the seam. Stretching
   one gable across a whole band flattens it: 7.8 degrees at 320px, 3.9 at
   1280, 2.9 at 2560. That is the shallow pitch the reference uses, and the
   reason the large scale is the same construction rather than the same angle.

   SMALL, the mark closing every section heading block, below.
   And the same gable in the wordmark, see tools/build-brand.py. */
.pitch-up { position: relative; }
.pitch-up::before {
  content: "";
  position: absolute;
  left: 0; right: 0;
  top: calc(var(--pitch-h) * -1);
  height: calc(var(--pitch-h) + 2px);
  background-color: var(--pitch-fill, var(--bg));
  /* carries the band's own top of band shading up the slope, so the gable
     does not sit on the seam as a flat cut out */
  background-image: linear-gradient(180deg,
    rgba(26, 28, 30, .10), rgba(26, 28, 30, .055));
  -webkit-clip-path: polygon(0 100%, 50% 0, 100% 100%);
          clip-path: polygon(0 100%, 50% 0, 100% 100%);
  pointer-events: none;
  z-index: 1;
}
/* the footer paints on --bg-alt, not --bg */
.site-footer { --pitch-fill: var(--bg-alt); }

/* The small mark. Same gable, drawn as a mask so it takes the accent token
   instead of a baked in colour, closing the heading block of every section.
   #services is the one section with no h2, so its eyebrow carries it. */
main section > .wrap > h2::after,
#services > .wrap > .eyebrow::after {
  content: "";
  display: block;
  width: 48px; height: 12px;
  margin: 14px 0 0;
  background-color: var(--accent-text);
  -webkit-mask-image: var(--pitch-mark);
          mask-image: var(--pitch-mark);
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-size: 100% 100%;
          mask-size: 100% 100%;
}

/* ------------------------------------------------------------- buttons */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: .5em;
  min-height: var(--tap); padding: 12px 22px;
  border: 2px solid transparent; border-radius: var(--radius);
  font: inherit; font-weight: 700; font-size: 1rem;
  text-decoration: none; cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}
.btn-call { background: var(--accent); color: #fff; border-color: var(--accent); }
.btn-call:hover, .btn-call:focus-visible { background: var(--accent-hi); border-color: var(--accent-hi); color: #fff; }
.btn-ghost { background: transparent; color: var(--ink); border-color: var(--line-strong); }
.btn-ghost:hover, .btn-ghost:focus-visible { border-color: var(--ink); color: var(--ink); background: rgba(255,255,255,.04); }
.btn-block { width: 100%; }

/* -------------------------------------------------------------- header */
.site-header {
  position: sticky; top: 0; z-index: 60;
  background: var(--bg);
  border-bottom: 1px solid var(--line-soft);
}

.header-inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; min-height: 64px;
}
.brand { display: flex; align-items: center; flex: 0 1 auto; min-width: 0; min-height: var(--tap); }
.brand img { width: 232px; max-width: 58vw; height: auto; }
@media (min-width: 760px) { .brand img { width: 290px; max-width: none; } }

.nav-links { display: none; }
.nav-links a {
  color: var(--ink); text-decoration: none; font-weight: 600; font-size: .96rem;
  padding: 10px 2px; border-bottom: 2px solid transparent;
}
.nav-links a:hover, .nav-links a:focus-visible { color: var(--accent-text); }

.burger {
  display: inline-flex; flex-direction: column; justify-content: center; gap: 5px;
  width: var(--tap); height: var(--tap); padding: 10px;
  background: transparent; border: 1px solid var(--line); border-radius: var(--radius);
  cursor: pointer;
}
.burger span { display: block; height: 2px; background: var(--ink); border-radius: 2px; }
/* compact on mobile, full label from 940px up */
.header-call { font-size: .88rem; padding: 10px 14px; white-space: nowrap; }
.header-call .header-call-long { display: none; }
@media (min-width: 420px) { .header-call { font-size: .92rem; } }

@media (min-width: 940px) {
  .burger { display: none; }
  .nav-links { display: flex; align-items: center; gap: 22px; }
  .header-call { font-size: 1rem; padding: 12px 22px; }
  .header-call .header-call-long { display: inline; }
  .header-call .header-call-short { display: none; }
}

/* mobile panel */
.nav-panel {
  display: none;
  border-top: 1px solid var(--line-soft);
  background: var(--bg);
  padding: 8px var(--gutter) 20px;
}
.nav-panel.open { display: block; }
@media (min-width: 940px) { .nav-panel, .nav-panel.open { display: none; } }
.nav-panel a {
  display: flex; align-items: center; min-height: var(--tap);
  color: var(--ink); text-decoration: none; font-weight: 600; font-size: 1.05rem;
  padding: 6px 4px; border-bottom: 1px solid var(--line-soft);
}
.nav-panel a:last-of-type { border-bottom: 0; }
.nav-panel .btn { margin-top: 14px; }

/* ---------------------------------------------------------------- hero */
/* Image and copy share one grid cell, so the photo covers the whole hero and
   there is no seam where the image stops and the charcoal starts. */
.hero {
  position: relative; padding: 0; background: var(--bg);
  display: grid; min-height: clamp(500px, 82vh, 700px);
}
.hero > * { grid-area: 1 / 1; min-width: 0; }
.hero-media { position: relative; height: 100%; overflow: hidden; }
.hero-media img {
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 40%;
}
.hero-media::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg,
    rgba(23,25,27,.72) 0%, rgba(23,25,27,.42) 34%,
    rgba(23,25,27,.82) 72%, rgba(23,25,27,.97) 100%);
}
/* keeps the .wrap gutter: a bare `padding: 40px 0` here would zero it out */
/* the bottom padding clears the pitch that the trust strip below rises into,
   so the apex can never reach the last line of hero copy at any width */
.hero-body {
  position: relative; z-index: 2; align-self: end;
  padding: 40px var(--gutter) calc(44px + var(--pitch-h));
}
@media (min-width: 760px) {
  .hero-body { padding: 60px var(--gutter) calc(64px + var(--pitch-h)); }
}
.hero h1 { margin-bottom: .35em; max-width: 20ch; }
.hero-tagline { color: var(--ink); font-size: 1.08rem; max-width: 54ch; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin: 24px 0 18px; }
.hero-actions .btn { flex: 1 1 240px; }
/* the outline button sits on a photo, so it needs its own ground to stay legible */
.hero-actions .btn-ghost { background: rgba(23,25,27,.62); border-color: rgba(239,237,234,.42); }
.hero-actions .btn-ghost:hover, .hero-actions .btn-ghost:focus-visible { background: rgba(23,25,27,.86); border-color: var(--ink); }
@media (min-width: 560px) { .hero-actions .btn { flex: 0 0 auto; } }
.hero-meta {
  color: var(--ink-muted); font-size: .92rem; margin: 0;
  display: flex; flex-wrap: wrap; gap: 6px 10px; align-items: center;
}
.hero-meta a { color: var(--ink-muted); text-decoration: none; border-bottom: 1px solid var(--line); }
.hero-meta a:hover { color: var(--ink); }
.dot { color: var(--line); }

/* -------------------------------------------------------- trust strip */
/* The Google rating badge is the anchor. The two facts sit beside it, smaller
   and quieter, so this reads as one badge with two notes rather than three
   equal items. Two switchable treatments, picked by a class on <html>:

     badge-inline  the badge sits inside the dark strip
     badge-card    the badge is a raised white card lapping over the hero

   The rating is typed into the markup. See HANDOVER.md for the note on
   pulling it live from the Places API instead. */

.trust {
  position: relative;
  background-color: var(--bg);
  border-bottom: 1px solid var(--line-soft);
  padding: 0;
}
.trust-inner {
  display: flex; flex-direction: column; align-items: center;
  gap: 12px; padding-top: 16px; padding-bottom: 16px;
}
.trust-rest { display: flex; flex-direction: column; align-items: center; }
.trust-item {
  margin: 0; padding: 6px 8px; text-align: center;
  font-weight: 600; font-size: .9rem; letter-spacing: .01em;
  color: var(--ink-muted);
}
.trust-sep { display: block; width: 44px; height: 1px; background: var(--line); }
@media (min-width: 620px) {
  .trust-inner { flex-direction: row; justify-content: center; gap: 24px; }
  .trust-rest { flex-direction: row; align-items: center; }
  .trust-item { padding: 6px 18px; }
  .trust-sep { width: 1px; height: 20px; }
}
@media (min-width: 860px) {
  .trust-inner { gap: 34px; }
  .trust-item { padding: 6px 26px; }
}

/* the badge itself, shared by both treatments */
.gbadge {
  position: relative; z-index: 3;
  display: inline-flex; align-items: center; gap: 13px;
  min-height: var(--tap);
  text-decoration: none;
  transition: border-color .18s ease, background-color .18s ease,
              box-shadow .18s ease, transform .18s ease;
}
.gbadge-mark { display: flex; flex: 0 0 auto; }
.gbadge-text { display: flex; flex-direction: column; gap: 1px; }
.gbadge-line { display: flex; align-items: baseline; gap: 8px; }
.gbadge-score { font-weight: 800; font-size: 1.3rem; line-height: 1; }
.gbadge-stars { font-size: 1rem; letter-spacing: 1.5px; line-height: 1; }
.gbadge-src { font-size: .78rem; font-weight: 600; letter-spacing: .02em; }

/* .................................. badge-inline, sitting in the strip */
html.badge-inline .gbadge {
  padding: 9px 20px 9px 14px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--ink);
}
html.badge-inline .gbadge-stars { color: var(--star); }
html.badge-inline .gbadge-src { color: var(--ink-dim); }
html.badge-inline .gbadge:hover,
html.badge-inline .gbadge:focus-visible {
  background: var(--surface-2); border-color: var(--ink-muted);
}

/* ................... badge-card, a raised white card over the hero seam */
/* The card is pulled up out of the strip by a negative margin, so it laps
   over the bottom of the hero photo and the strip's top rule runs behind it.
   The strip keeps its own padding underneath, so nothing collides. */
html.badge-card .trust-inner {
  flex-direction: column;
  gap: 14px;
  padding-top: 0;
  padding-bottom: 18px;
}
/* Three facts need about 520px on one line. Below that a wrapped row with a
   rule floating in the middle of it reads as broken, so they stack instead.
   This matches the base .trust-rest breakpoint. Raise it again if a fourth
   fact is ever added. */
@media (min-width: 620px) {
  html.badge-card .trust-rest { flex-direction: row; align-items: center; }
}
html.badge-card .gbadge {
  margin-top: -42px;
  padding: 13px 22px;
  background: #FFFFFF; color: #202124;
  border: 1px solid #DADCE0;
  border-radius: 12px;
  box-shadow: 0 14px 34px rgba(0, 0, 0, .46), 0 2px 6px rgba(0, 0, 0, .22);
}
html.badge-card .gbadge-stars { color: #E8A400; }
html.badge-card .gbadge-src { color: #5F6368; }
html.badge-card .gbadge:hover,
html.badge-card .gbadge:focus-visible { border-color: #B9BDC2; }
html.motion.badge-card .gbadge:hover { transform: translateY(-2px); }
@media (min-width: 620px) {
  html.badge-card .gbadge { margin-top: -50px; padding: 15px 26px; gap: 15px; }
  html.badge-card .gbadge-score { font-size: 1.45rem; }
  html.badge-card .gbadge-stars { font-size: 1.1rem; }
}

/* ------------------------------------------------------------ services */
.cards { display: grid; gap: 18px; grid-template-columns: 1fr; margin-top: 32px; }
@media (min-width: 620px) { .cards { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 960px) { .cards { grid-template-columns: repeat(4, 1fr); gap: 20px; } }
.card {
  background: var(--surface); border: 1px solid var(--line-soft);
  border-radius: var(--radius); overflow: hidden;
  display: flex; flex-direction: column;
}
.card-media { background: var(--surface-2); }
.card-media img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; }
.card-body { padding: 18px 18px 22px; }
.card h2 { font-size: var(--card-h2); margin-bottom: .5em; letter-spacing: var(--h3-track); }
.card p { color: var(--ink-muted); font-size: .97rem; margin: 0; }
/* the driveway card carries no photo on purpose (no album shot exists), so it
   gets an accent rule where the others have an image, and the row stays even */
.card.no-photo { border-top: 3px solid var(--accent); }
.card.no-photo .card-body { padding-top: 22px; }

/* ------------------------------------------------------------ our work */
.pairs { display: grid; gap: 26px; margin-top: 32px; }
@media (min-width: 900px) { .pairs { grid-template-columns: repeat(2, 1fr); gap: 32px; } }
.pair {
  background: var(--surface); border: 1px solid var(--line-soft);
  border-radius: var(--radius); padding: 14px;
}
.pair-imgs { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.shot { margin: 0; position: relative; }
.shot img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: 2px; background: var(--surface-2); }
.shot figcaption {
  position: absolute; top: 8px; left: 8px;
  /* The chip carries its own fixed dark background, so it cannot use --ink:
     section.light and section.mid redefine --ink to near black, which made the
     BEFORE label black on black. Pinned to the dark theme's off white. */
  background: rgba(23,25,27,.86); color: #EFEDEA;
  font-size: .72rem; font-weight: 800; letter-spacing: .12em; text-transform: uppercase;
  padding: 5px 9px; border-radius: 2px;
}
.shot.after figcaption { background: var(--accent); color: #fff; }
.pair-label { margin: 12px 2px 2px; font-size: .92rem; color: var(--ink-muted); }

/* Service copy runs as a few short paragraphs, not one block. */
.card p + p { margin-top: .7em; }

/* A strip of finished roofs that drifts on its own. The track holds the same
   run twice and travels exactly minus 50 percent, so the loop has no seam.
   The outer element is a real scroll container, so a visitor can still drag it,
   and the drift stops on hover, on focus, while a finger is down, and under
   prefers-reduced-motion.

   The edges used to fade out under a mask, which washed a roof out to nothing
   on both sides. The strip now sits outside the content wrap and runs the full
   width of the page, so the only thing cutting an image is the edge of the
   screen. The first tile still lines up with the copy above it. */
.strip-label {
  margin: 38px 0 12px; font-size: .78rem; font-weight: 700; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-dim);
}
.marquee {
  overflow-x: auto; overflow-y: hidden;
  scrollbar-width: none; -ms-overflow-style: none;
  padding-left: max(var(--gutter), calc((100% - var(--wrap)) / 2));
}
.marquee::-webkit-scrollbar { display: none; }
.marquee-track { display: flex; width: max-content; }
.marquee-run { display: flex; gap: 10px; list-style: none; margin: 0; padding: 0 10px 0 0; }
.marquee-run li { flex: 0 0 auto; }
.marquee-run img {
  width: clamp(250px, 46vw, 360px); aspect-ratio: 4 / 3; object-fit: cover;
  border-radius: var(--radius); background: var(--surface);
  border: 1px solid var(--line-soft);
}
/* The drift used to be a CSS animation on the track. It is now driven by the
   container's own scrollLeft in js/main.js, because that is the only way the
   same strip can be dragged with a mouse, flicked with a thumb, and still wrap
   round for ever without the two mechanisms fighting each other. */
.marquee { cursor: grab; touch-action: pan-x; }
.marquee.dragging { cursor: grabbing; user-select: none; }
.marquee.dragging img { pointer-events: none; }

/* ------------------------------------------------------------- why us */
.why { display: grid; gap: 18px; margin-top: 32px; grid-template-columns: 1fr; }
@media (min-width: 640px) { .why { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 980px) { .why { grid-template-columns: repeat(4, 1fr); } }
.why div {
  border-top: 3px solid var(--accent); padding-top: 16px;
}
.why h3 { margin-bottom: .4em; }
.why p { color: var(--ink-muted); font-size: .97rem; margin: 0; }

/* ------------------------------------------------------------ reviews */
/* Three switchable layouts over one set of markup, picked by a class on
   <html>. Names sit under the quote and never in a heading, there are no
   dates, and the Read all reviews on Google button is common to all three.
   Once one is picked, delete the other two blocks. See HANDOVER.md.

     rev-1  Google style card wall, even grid, quote clamped with a reveal
     rev-2  one large rotating quote, dots and arrows, slow auto advance
     rev-3  horizontal scroll snap rail with a card peeking at the edge  */

/* ....................................................... shared basics */
.review-stage { margin-top: 30px; }
.review-list { list-style: none; margin: 0; padding: 0; }
.review-item { min-width: 0; }
.review { margin: 0; display: flex; flex-direction: column; }
.stars { color: var(--star); letter-spacing: 1px; }
.review .stars { font-size: 1.05rem; margin: 0 0 10px; }
.review-quote { margin: 0 0 16px; color: var(--ink); font-size: .98rem; }
.review-by { margin-top: auto; display: flex; align-items: center; gap: 12px; }
.review-mark { display: none; }
.review-more { display: none; }
.review-controls { display: none; }

.avatar {
  flex: 0 0 auto; width: 38px; height: 38px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--surface-2); border: 1px solid var(--line);
  color: var(--ink); font-weight: 800; font-size: 1rem; line-height: 1;
}
.who { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.who-name { font-weight: 700; font-size: .96rem; color: var(--ink); }
.who-src {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: .8rem; color: var(--ink-dim);
}
.gmark { flex: 0 0 auto; display: block; }
.reviews-cta { margin-top: 30px; }

/* the arrows and dots are shared, which layout shows them is not */
.review-arrow {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto; width: 44px; height: 44px; padding: 0;
  background: var(--surface-2); color: var(--ink);
  border: 1px solid var(--line); border-radius: 50%;
  cursor: pointer;
  transition: background-color .18s ease, border-color .18s ease, opacity .18s ease;
}
.review-arrow:hover { background: var(--surface); border-color: var(--ink-muted); }
.review-arrow[disabled] { opacity: .35; cursor: default; }
.review-dots { display: none; list-style: none; margin: 0; padding: 0; gap: 2px; }
.review-dots li { display: block; }
.review-dot {
  display: grid; place-items: center;
  width: 30px; height: 44px; padding: 0;
  background: none; border: 0; cursor: pointer;
}
.review-dot::before {
  content: ""; display: block;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--line-soft); border: 1px solid var(--line);
  transition: background-color .2s ease, transform .2s ease;
}
.review-dot:hover::before { background: var(--ink-dim); }
.review-dot[aria-current="true"]::before {
  background: var(--accent); border-color: var(--accent); transform: scale(1.3);
}

/* ................................................ 1, Google card wall */
/* White cards, so the section reads as a wall of Google reviews whichever
   ground it sits on. The colours here are written literally rather than
   through the tokens for exactly that reason. */
html.rev-1 .review-list {
  display: grid; gap: 16px; grid-template-columns: 1fr; align-items: stretch;
}
@media (min-width: 620px) { html.rev-1 .review-list { grid-template-columns: repeat(2, 1fr); gap: 18px; } }
@media (min-width: 1000px) { html.rev-1 .review-list { grid-template-columns: repeat(3, 1fr); } }
html.rev-1 .review {
  position: relative; height: 100%;
  background: #FFFFFF; color: #3C4043;
  border: 1px solid #DADCE0; border-radius: 8px;
  padding: 20px 20px 18px;
  box-shadow: 0 1px 3px rgba(60, 64, 67, .18);
}
html.rev-1 .review .stars { color: #E8A400; }
html.rev-1 .review-quote { color: #3C4043; }
html.rev-1 .review-quote.clamped {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 5;
  line-clamp: 5; overflow: hidden;
}
html.rev-1 .avatar { background: #F1F3F4; border-color: #DADCE0; color: #3C4043; }
html.rev-1 .who-name { color: #202124; }
html.rev-1 .who-src { color: #5F6368; }
html.rev-1 .review-mark { display: block; position: absolute; top: 16px; right: 16px; }
html.rev-1 .review-more {
  display: inline-flex; align-items: center;
  align-self: flex-start; min-height: 32px;
  margin: -6px 0 14px; padding: 0;
  background: none; border: 0;
  color: #1A6FD4; font: inherit; font-size: .92rem; font-weight: 700;
  cursor: pointer;
}
html.rev-1 .review-more:hover { text-decoration: underline; }
/* the page focus ring is an orange that goes quiet on white */
html.rev-1 .review :focus-visible { outline-color: #1A6FD4; }

/* ............................................ 2, one rotating quote */
/* Every slide sits in the same grid cell, so the section does not jump as the
   quotes change length. The ones that are not showing are visibility: hidden,
   which takes them out of the tab order and out of the accessibility tree
   without collapsing the cell. */
html.rev-2 .review-viewport { padding: 4px 0; }
html.rev-2 .review-list { display: grid; }
html.rev-2 .review-item {
  grid-area: 1 / 1;
  opacity: 0; visibility: hidden;
  transition: opacity .5s ease;
}
html.rev-2 .review-item.is-current { opacity: 1; visibility: visible; }
html.rev-2 .review { align-items: center; text-align: center; }
html.rev-2 .review .stars {
  font-size: clamp(1.4rem, 3.4vw, 1.9rem); letter-spacing: 4px; margin-bottom: 18px;
}
html.rev-2 .review-quote {
  font-size: clamp(1.18rem, 2.9vw, 1.85rem);
  line-height: 1.4; letter-spacing: -0.012em;
  color: var(--ink);
  max-width: 44ch; margin: 0 auto 26px;
  text-wrap: pretty;
}
html.rev-2 .review-by { margin-top: 0; justify-content: center; }
html.rev-2 .who { align-items: center; }
html.rev-2 .avatar { width: 44px; height: 44px; font-size: 1.15rem; }
html.rev-2 .review-controls {
  display: flex; align-items: center; justify-content: center;
  gap: 8px; margin-top: 26px;
}
html.rev-2 .review-dots { display: flex; }

/* ...................................................... 3, a rail */
/* The list bleeds past the wrap gutter and the cards are narrower than the
   track, so the next card is always half in view: that, not a scrollbar, is
   what says the row moves. Snap points land the card on the gutter. */
html.rev-3 .review-viewport {
  margin-left: calc(var(--gutter) * -1);
  margin-right: calc(var(--gutter) * -1);
}
html.rev-3 .review-list {
  display: flex; gap: 16px;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-padding-left: var(--gutter);
  padding: 4px var(--gutter) 8px;
  scrollbar-width: none; -ms-overflow-style: none;
  overscroll-behavior-x: contain;
}
html.rev-3 .review-list::-webkit-scrollbar { display: none; }
html.rev-3 .review-list:focus-visible { outline: 3px solid var(--accent-text); outline-offset: 2px; }
html.rev-3 .review-item {
  flex: 0 0 min(78%, 320px);
  scroll-snap-align: start;
}
html.rev-3 .review {
  height: 100%;
  background: var(--surface); border: 1px solid var(--line-soft);
  border-radius: var(--radius); padding: 20px;
}
/* Left unclamped, one long quote makes every card in the rail as tall as it
   is, which on a phone is most of the screen. Same clamp and same reveal as
   the card wall, a few lines longer because the card is narrower. */
html.rev-3 .review-quote.clamped {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 8;
  line-clamp: 8; overflow: hidden;
}
html.rev-3 .review-more {
  display: inline-flex; align-items: center;
  align-self: flex-start; min-height: 32px;
  margin: -4px 0 14px; padding: 0;
  background: none; border: 0;
  color: var(--accent-text); font: inherit; font-size: .92rem; font-weight: 700;
  cursor: pointer;
}
html.rev-3 .review-more:hover { text-decoration: underline; }
html.rev-3 .review-controls { display: none; }
@media (min-width: 760px) {
  html.rev-3 .review-controls {
    display: flex; align-items: center; justify-content: flex-end;
    gap: 10px; margin-top: 14px;
  }
  html.rev-3 .review-dots { display: none; }
}

/* -------------------------------------------------------------- areas */
#areas { position: relative; overflow: hidden; }
/* decorative only, sits behind the copy and carries no place names */
.coast {
  position: absolute; left: 0; right: 0; bottom: 0; width: 100%; height: 170px;
  color: var(--accent); pointer-events: none; opacity: .30;
}
#areas .wrap { position: relative; z-index: 1; }
.area-cols { display: grid; gap: 26px; margin: 30px 0 26px; grid-template-columns: 1fr; }
@media (min-width: 720px) { .area-cols { grid-template-columns: repeat(3, 1fr); gap: 30px; } }
.area-col h3 {
  margin: 0 0 14px; padding-bottom: 10px;
  border-bottom: 1px solid var(--line);
  font-size: .82rem; font-weight: 800; letter-spacing: .12em;
  text-transform: uppercase; color: var(--accent-text);
}
.suburbs { list-style: none; display: flex; flex-wrap: wrap; gap: 9px; padding: 0; margin: 0; }
.suburbs li {
  background: var(--surface); border: 1px solid var(--line-soft);
  border-radius: var(--radius); padding: 8px 13px; font-weight: 600; font-size: .94rem;
  transition: background-color .18s ease, border-color .18s ease, transform .18s ease;
}
.motion .suburbs li:hover { background: var(--surface-2); border-color: var(--line); transform: translateY(-2px); }
.areas-close { color: var(--ink-muted); font-size: 1.02rem; margin: 0; }
#areas { padding-bottom: 96px; }
@media (min-width: 760px) { #areas { padding-bottom: 124px; } }
/* same clearance for the pitch the footer rises into */
#contact { padding-bottom: calc(56px + var(--pitch-h)); }
@media (min-width: 760px) { #contact { padding-bottom: calc(84px + var(--pitch-h)); } }

/* ------------------------------------------------------------ contact */
.contact-grid { display: grid; gap: 34px; margin-top: 30px; grid-template-columns: 1fr; }
@media (min-width: 900px) { .contact-grid { grid-template-columns: 1.1fr .9fr; gap: 48px; } }

.field { margin-bottom: 18px; }
.field label { display: block; font-weight: 700; font-size: .93rem; margin-bottom: 7px; }
.field .req { color: var(--accent-text); }
.field input, .field textarea {
  width: 100%; min-height: var(--tap); padding: 12px 14px;
  background: var(--surface-2); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius);
  font: inherit; font-size: 1rem;
}
.field textarea { min-height: 128px; resize: vertical; }
.field input:focus, .field textarea:focus { border-color: var(--accent-text); }
.field input[aria-invalid="true"], .field textarea[aria-invalid="true"] { border-color: var(--err-line); }
.field .err { display: none; color: var(--err-text); font-size: .88rem; margin-top: 6px; font-weight: 600; }
.field .err.show { display: block; }

/* the honeypot. This used to be a style attribute, which the page's own CSP
   (style-src 'self') blocks, so the field was rendering in plain sight. */
.hp { position: absolute; left: -9999px; top: auto; width: 1px; height: 1px; overflow: hidden; }

.form-note { color: var(--ink-muted); font-size: .88rem; margin-top: 14px; }
.form-status { margin-top: 18px; padding: 16px 18px; border-radius: var(--radius); display: none; font-weight: 600; }
.form-status.show { display: block; }
.form-status.ok  { background: var(--accent-wash); border: 1px solid var(--accent); color: var(--ink); }
.form-status.bad { background: rgba(179, 38, 30, .09); border: 1px solid var(--err-line); color: var(--ink); }

.details dl { margin: 0; }
.details dt { font-size: .78rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-dim); margin-top: 20px; }
.details dt:first-child { margin-top: 0; }
.details dd { margin: 6px 0 0; font-size: 1.02rem; }
.details dd a { color: var(--ink); text-decoration: none; border-bottom: 1px solid var(--accent); display: inline-flex; align-items: center; min-height: 32px; }
.details dd a:hover { color: var(--accent-text); }
.details .hours { color: var(--ink-muted); font-size: .98rem; }

/* ------------------------------------------------------------- footer */
.site-footer { background-color: var(--bg-alt); border-top: 1px solid var(--line-soft); padding: 44px 0; position: relative; }
.site-footer img { width: 260px; max-width: 70vw; margin-bottom: 22px; }
.footer-credit { margin-top: 14px; font-size: .82rem; color: var(--ink-dim); }
.footer-credit a { color: inherit; }
.footer-credit a:hover { color: var(--ink-muted); }
.site-footer p { color: var(--ink-muted); font-size: .94rem; margin: 0 0 .5em; }
.site-footer a { color: var(--ink-muted); }
.site-footer a:hover { color: var(--ink); }
.footer-legal { margin-top: 20px; font-size: .86rem; color: var(--ink-dim); }

/* The sticky bottom call bar is gone: a fixed header and a fixed bar together
   held about a fifth of a phone screen permanently. The header stays, and
   carries a compact call button on mobile so tap to call is still one tap
   away from anywhere on the page. */

/* ------------------------------------------------------------- motion */
/* Everything here is gated on html.motion, which js/main.js adds only when
   matchMedia says the visitor has no reduced motion preference, and removes
   again if they change it mid visit. The media query at the bottom of this
   file is the belt and braces for a no JS visitor. */

/* reveal on scroll: a short fade and a 14px rise, once, staggered inside a
   grid by an inline delay the script sets */
.motion .rv {
  opacity: 0; transform: translateY(14px);
  transition: opacity .55s ease, transform .55s cubic-bezier(.22,.61,.36,1);
}
/* the stagger inside a grid. Classes rather than an inline style, because the
   CSP is style-src 'self' and a style attribute would be blocked. */
.motion .rv-d1 { transition-delay:  70ms; }
.motion .rv-d2 { transition-delay: 140ms; }
.motion .rv-d3 { transition-delay: 210ms; }
.motion .rv-d4 { transition-delay: 280ms; }
.motion .rv-d5 { transition-delay: 350ms; }
.motion .rv.rv-in { opacity: 1; transform: none; }

/* slow drift across the hero photo */
@keyframes ken-burns {
  from { transform: scale(1) translate3d(0, 0, 0); }
  to   { transform: scale(1.07) translate3d(0, -1.2%, 0); }
}
.motion .hero-media img {
  animation: ken-burns 9s ease-in-out infinite alternate;
  will-change: transform;
}

/* hover lift on the cards */
.card, .pair, .review {
  transition: transform .22s ease, box-shadow .22s ease, border-color .22s ease;
}
.motion .card:hover, .motion .pair:hover, .motion.rev-1 .review:hover,
.motion.rev-3 .review:hover {
  transform: translateY(-3px);
  border-color: var(--line);
  box-shadow: 0 10px 24px rgba(0, 0, 0, .34);
}
.motion section.light .card:hover {
  box-shadow: 0 10px 22px rgba(26, 28, 30, .12);
}

/* buttons fill from the left instead of snapping colour */
.btn-ghost {
  background-image: linear-gradient(rgba(255, 255, 255, .07), rgba(255, 255, 255, .07));
  background-repeat: no-repeat; background-size: 0% 100%;
  transition: background-size .28s ease, border-color .2s ease, color .2s ease;
}
.btn-ghost:hover, .btn-ghost:focus-visible { background-size: 100% 100%; }
.btn-call { background-image: none; }

/* nav underline grows out of the middle rather than appearing */
.nav-links a { transition: color .2s ease; position: relative; }
.nav-links a::after {
  content: ""; position: absolute; left: 50%; right: 50%; bottom: 0; height: 2px;
  background: var(--accent-text); transition: left .22s ease, right .22s ease;
}
.nav-links a:hover::after, .nav-links a:focus-visible::after { left: 0; right: 0; }

/* -------------------------------------------------------- reduced motion */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  /* the hero drift and the reveals stop dead. The strip keeps its second run
     so it still wraps when dragged, it just never moves on its own. */
  .rv { opacity: 1 !important; transform: none !important; }
  .hero-media img { animation: none !important; transform: none !important; }
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}
