@import url("tokens.css?v=13");

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0;
  font-family: var(--font);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--ink);
  background: var(--paper);
  padding-bottom: 4.25rem; /* clears the sticky mobile bar */
}
img { max-width: 100%; display: block; }
a { color: inherit; }
h1, h2, h3 { line-height: var(--lh-tight); letter-spacing: -0.015em; margin: 0; text-wrap: balance; }
p { margin: 0; max-width: var(--measure); }
ul { margin: 0; padding: 0; list-style: none; }

/* ---------- layout ---------- */
.wrap { max-width: var(--max); margin-inline: auto; padding-inline: var(--gutter); }
section { padding-block: var(--s7); }
section.tight { padding-block: var(--s6); }
.band { background: var(--paper-2); }
.band-dark { background: var(--dark); color: var(--dark-ink); }
.band-dark p, .band-dark li { color: var(--dark-ink-2); }
.stack > * + * { margin-top: var(--s3); }
.stack-lg > * + * { margin-top: var(--s5); }
.eyebrow {
  font-size: var(--fs-xs); font-weight: 650; text-transform: uppercase;
  letter-spacing: 0.09em; color: var(--ink-3); margin: 0;
}
.band-dark .eyebrow { color: var(--dark-ink-2); }
.lead { font-size: 1.1875rem; color: var(--ink-2); }
.band-dark .lead { color: var(--dark-ink-2); }

/* ---------- wireframe annotations ---------- */
.wf {
  display: var(--wf-note-display);
  font-size: var(--fs-xs); line-height: 1.45; color: #8a5a00;
  background: #fff8e6; border-left: 3px solid #e0a800;
  padding: var(--s2) var(--s3); border-radius: 0 var(--radius) var(--radius) 0;
  max-width: var(--measure); margin-top: var(--s2);
}
.wf b { color: #6b4600; }
.wf-banner {
  display: var(--wf-note-display);
  background: var(--ink); color: #fff; font-size: var(--fs-xs);
  padding: var(--s2) var(--gutter); text-align: center; letter-spacing: 0.02em;
}
.wf-banner strong { font-weight: 650; }

/* placeholder blocks stand in for photography we don't have yet */
.ph {
  background: var(--paper-3);
  background-image:
    linear-gradient(135deg, transparent 47%, var(--line) 47%, var(--line) 53%, transparent 53%);
  background-size: 22px 22px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  display: grid; place-items: center;
  color: var(--ink-3); font-size: var(--fs-xs);
  letter-spacing: 0.06em; text-transform: uppercase;
  min-height: 200px; text-align: center; padding: var(--s3);
}
.ph-tall { min-height: 380px; }

/* ---------- service icons ----------
   One set, one 24-grid, one stroke weight. Each icon is two layers: a static
   ink outline that carries the meaning, and a lime "live" element that moves.
   The movement is the title, not decoration — the maintenance tick draws, the
   emergency bolt flickers, the installation plug seats into the socket, the
   repair pulse travels the fault trace. Lime as a stroke on white is 1.4:1, so
   the live layer uses --accent-text and sits on a lime tint tile.

   Each icon plays once when its card scrolls in (motion.js already puts .is-in
   on the grid) and replays on hover or keyboard focus. Nothing loops: four
   icons cycling forever beside body copy is a distraction, not polish. */
.ico {
  display: grid; place-items: center;
  width: 44px; height: 44px;
  background: var(--accent-quiet);
  border-radius: var(--radius);
}
.ico svg { display: block; width: 26px; height: 26px; }
.ico-frame { stroke: var(--ink-2); }
.ico-live  { stroke: var(--accent-text); }

/* Resting state = fully drawn and seated, so a no-JS or reduced-motion visitor
   sees the finished icon rather than a half-drawn one. */
.ico-live { stroke-dashoffset: 0; opacity: 1; }

@media (prefers-reduced-motion: no-preference) {
  /* pre-roll hangs off .rv, which only exists once motion.js has run */
  .rv .ico-draw,  .rv-stagger .ico-draw   { stroke-dasharray: 100; stroke-dashoffset: 100; }
  .rv .ico-slide, .rv-stagger .ico-slide  { transform: translateY(-4px); opacity: 0; }
  .rv .ico-flick, .rv-stagger .ico-flick  { opacity: 0; }
  .rv .ico-pulse, .rv-stagger .ico-pulse  { stroke-dasharray: 16 84; stroke-dashoffset: 116; }

  .rv.is-in .ico-draw, .rv-stagger.is-in .ico-draw, .card:hover .ico-draw, .card:focus-within .ico-draw,
  .srv-item:hover .ico-draw, .srv-item:focus-visible .ico-draw
    { animation: ico-draw 620ms var(--ease, cubic-bezier(.2,.7,.3,1)) 120ms forwards; }
  .rv.is-in .ico-slide, .rv-stagger.is-in .ico-slide, .card:hover .ico-slide, .card:focus-within .ico-slide,
  .srv-item:hover .ico-slide, .srv-item:focus-visible .ico-slide
    { animation: ico-slide 520ms cubic-bezier(.3,1.4,.5,1) 120ms forwards; }
  .rv.is-in .ico-flick, .rv-stagger.is-in .ico-flick, .card:hover .ico-flick, .card:focus-within .ico-flick,
  .srv-item:hover .ico-flick, .srv-item:focus-visible .ico-flick
    { animation: ico-flick 900ms steps(1, end) 120ms forwards; }
  .rv.is-in .ico-pulse, .rv-stagger.is-in .ico-pulse, .card:hover .ico-pulse, .card:focus-within .ico-pulse,
  .srv-item:hover .ico-pulse, .srv-item:focus-visible .ico-pulse
    { animation: ico-pulse 900ms cubic-bezier(.4,0,.5,1) 120ms forwards; }
}

@keyframes ico-draw  { to { stroke-dashoffset: 0; } }
@keyframes ico-slide { from { transform: translateY(-4px); opacity: 0; }
                       to   { transform: translateY(0);    opacity: 1; } }
@keyframes ico-flick { 0%,20% { opacity: 0 } 25% { opacity: 1 } 35% { opacity: .25 }
                       45%,100% { opacity: 1 } }
@keyframes ico-pulse { from { stroke-dashoffset: 116 } to { stroke-dashoffset: -16 } }

/* ---------- header ---------- */
.hdr {
  position: sticky; top: 0; z-index: 40;
  background: var(--paper); border-bottom: 1px solid var(--line);
}
.hdr-in { display: flex; align-items: center; gap: var(--s4); min-height: 68px; }
.nav { display: none; gap: var(--s4); margin-left: auto; font-size: var(--fs-sm); }
.nav a { text-decoration: none; color: var(--ink-2); padding-block: var(--s1); }
.nav a:hover, .nav a:focus-visible { color: var(--ink); }
.nav [aria-current="page"] { color: var(--ink); font-weight: 600; }
.hdr-cta { display: none; margin-left: var(--s2); }
@media (min-width: 900px) {
  .nav { display: flex; }
  .hdr-cta { display: inline-flex; }
}

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  font: inherit; font-size: var(--fs-sm); font-weight: 600;
  padding: 0.8125rem var(--s4); border-radius: var(--radius);
  text-decoration: none; border: 1px solid transparent; cursor: pointer;
  transition: background-color 140ms ease, color 140ms ease, border-color 140ms ease, transform 90ms ease;
}
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--accent); color: var(--accent-ink); }
.btn-primary:hover { filter: brightness(0.92); }
.btn-ghost { border-color: var(--line); color: var(--ink); background: var(--paper); }
.btn-ghost:hover { border-color: var(--ink-3); }
.band-dark .btn-ghost { background: transparent; color: var(--dark-ink); border-color: var(--dark-line); }
.band-dark .btn-ghost:hover { border-color: var(--dark-ink-2); }
.btn-urgent { background: var(--urgent); color: var(--urgent-ink); }
.btn-urgent:hover { filter: brightness(0.92); }
.btn-row { display: flex; flex-wrap: wrap; gap: var(--s2); }
:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; border-radius: 3px; }
.band-dark :focus-visible { outline-color: #fff; }

/* ---------- hero ---------- */
.hero { padding-block: var(--s7) var(--s6); }
.hero h1 { font-size: var(--fs-hero); font-weight: 680; }
.hero .lead { margin-top: var(--s3); }
.hero-grid { display: grid; gap: var(--s5); align-items: center; }
/* Contact pairs a short CTA column with a tall form. align-items:center strands
   the short one in the middle of a column of white space — top-align those. */
.hero-grid:has(.form) { align-items: start; }
@media (min-width: 900px) { .hero-grid { grid-template-columns: 1.05fr 0.95fr; gap: var(--s6); } }

/* ---------- hero photo carries the page ----------
   The hero shot was narrower and shorter than the headline beside it, so the
   page led with text and the photo read as an afterthought. Three changes:
   the image column is now the wider one, the crop is portrait so it gains
   height, and past 1100px it bleeds to the right edge of the viewport.
   Everything is inside media queries — mobile stays a single stacked column
   at the 4:5 base ratio with no bleed. */
@media (min-width: 900px) {
  .hero .hero-grid { grid-template-columns: 1fr 1.15fr; align-items: stretch; }
  /* fill the row rather than hold a fixed ratio — the photo then matches the
     full height of the headline block instead of floating short beside it.
     min-height covers the case where the copy is unusually brief. */
  .hero .shot-hero { aspect-ratio: auto; height: 100%; min-height: 26rem; }
}
@media (min-width: 1100px) {
  /* distance from the wrap's content edge out to the viewport edge */
  .hero { --bleed: calc(max(0px, (100vw - var(--max)) / 2) + var(--gutter)); overflow-x: clip; }
  .hero .shot-hero {
    margin-right: calc(var(--bleed) * -1);
    border-start-end-radius: 0; border-end-end-radius: 0;
  }
}

/* trust strip */
.trust { border-block: 1px solid var(--line); background: var(--paper); }
.band-dark + .trust { border-top: 0; }
.trust ul {
  display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5);
  padding-block: var(--s3); font-size: var(--fs-sm); color: var(--ink-2);
}
.trust li { display: flex; align-items: center; gap: var(--s2); }
.trust li::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent-text); flex: none;
}

/* ---------- cards ---------- */
.grid { display: grid; gap: var(--s3); }
@media (min-width: 640px) { .grid-2 { grid-template-columns: repeat(2, 1fr); } }
/* grid-3: the three trust blocks on About, mirroring the reference layout.
   Stacks at mobile, goes three-up at the same breakpoint grid-4 does. */
@media (min-width: 980px) { .grid-4 { grid-template-columns: repeat(4, 1fr); } .grid-3 { grid-template-columns: repeat(3, 1fr); } .grid-2 { grid-template-columns: repeat(2, 1fr); } }
.card {
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: var(--s4); background: var(--paper);
  display: flex; flex-direction: column; gap: var(--s2);
  transition: border-color 140ms ease, box-shadow 140ms ease;
}
.card h3 { font-size: var(--fs-h3); font-weight: 640; }
.card p { font-size: var(--fs-sm); color: var(--ink-2); }
.card .more {
  margin-top: auto; font-size: var(--fs-sm); font-weight: 600;
  text-decoration: none; color: var(--ink);
}
.card .more::after { content: " →"; }
a.card:hover, .card:has(a:hover) { border-color: var(--ink-3); box-shadow: 0 2px 12px rgb(0 0 0 / 0.05); }
a.card { text-decoration: none; }

/* ---------- process ---------- */
.steps { counter-reset: step; display: grid; gap: var(--s4); }
@media (min-width: 900px) { .steps { grid-template-columns: repeat(4, 1fr); gap: var(--s5); } }
.step { position: relative; padding-top: var(--s4); border-top: 2px solid var(--accent); }
.step::before {
  counter-increment: step; content: "0" counter(step);
  position: absolute; top: var(--s3); left: 0;
  font-size: var(--fs-xs); font-weight: 700; letter-spacing: 0.08em; color: var(--accent-text);
}
.step h3 { font-size: var(--fs-h3); margin-top: var(--s3); font-weight: 640; }
.step p { font-size: var(--fs-sm); color: var(--ink-2); margin-top: var(--s2); }

/* ---------- ticks ---------- */
.ticks { display: grid; gap: var(--s2); }
@media (min-width: 700px) { .ticks-2 { grid-template-columns: repeat(2, 1fr); } }
.ticks li {
  display: flex; gap: var(--s2); align-items: flex-start;
  font-size: var(--fs-sm); color: var(--ink-2);
}
.ticks li::before { content: "✓"; color: var(--accent-text); font-weight: 700; flex: none; width: 0.875rem; }
.band-dark .ticks li::before { color: var(--accent); }
.band-dark .step::before { color: var(--accent); }
.band-dark .trust li::before { background: var(--accent); }

/* ---------- suburbs ---------- */
.chips { display: flex; flex-wrap: wrap; gap: var(--s1) var(--s2); }
.chips li {
  font-size: var(--fs-sm); color: var(--ink-2);
  border: 1px solid var(--line); border-radius: 100px; padding: 0.3125rem 0.75rem;
}

/* ---------- reviews / testimonials ----------
   The stars carry meaning, so they get a dark rim: brand lime is 1.4:1 on
   white and a bare lime star reads as decoration rather than a rating. */
.stars { display: inline-flex; gap: 2px; line-height: 0; }
.star {
  width: 1.125rem; height: 1.125rem; flex: none;
  fill: var(--accent); stroke: var(--accent-text); stroke-width: 1;
  stroke-linejoin: round;
}

.tm-score {
  display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
  padding-bottom: var(--s4); border-bottom: 1px solid var(--line);
}
.tm-score-n { font-size: 1.75rem; font-weight: 700; letter-spacing: -0.02em; }
.tm-score .stars { transform: translateY(1px); }
.tm-score-meta { font-size: var(--fs-sm); color: var(--ink-2); flex: 1 1 14rem; }
.tm-score-meta a { text-underline-offset: 2px; }

.tm-grid {
  display: grid; gap: var(--s4);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr));
}
.tm {
  margin: 0; display: flex; flex-direction: column; gap: var(--s3);
  background: var(--paper); border: 1px solid var(--line);
  border-top: 3px solid var(--accent);
  border-radius: var(--radius-lg);
  padding: var(--s4);
  box-shadow: 0 1px 2px rgb(13 15 18 / 0.04);
}
.tm-q {
  margin: 0; max-width: none; color: var(--ink);
  font-size: var(--fs-body); quotes: "\201C" "\201D";
}
.tm-q::before { content: open-quote; }
.tm-q::after  { content: close-quote; }
.tm-by {
  display: flex; align-items: center; gap: var(--s2);
  margin-top: auto; padding-top: var(--s3); border-top: 1px solid var(--line);
}
.tm-av {
  width: 2.25rem; height: 2.25rem; flex: none; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--accent-quiet); border: 1px solid var(--line);
  color: var(--accent-text); font-size: var(--fs-xs); font-weight: 700;
  letter-spacing: 0.02em;
}
.tm-name { display: block; font-size: var(--fs-sm); font-weight: 650; }
.tm-src  { display: block; font-size: var(--fs-xs); color: var(--ink-3); }

/* the single review high on the home page — same component, one column */
/* the single review high on the home page sits on white, so it takes the
   tinted fill to separate from the page; inside a grey band it flips back. */
.tm-pull { max-width: 34rem; background: var(--paper-2); }
.band .tm-pull { background: var(--paper); }

/* ---------- FAQ accordion ---------- */
.faq { border-top: 1px solid var(--line); max-width: 46rem; }
.faq details { border-bottom: 1px solid var(--line); }
.faq summary {
  cursor: pointer; list-style: none; padding-block: var(--s4);
  font-weight: 600; display: flex; gap: var(--s3); align-items: flex-start;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: "+"; margin-left: auto; font-weight: 400; font-size: 1.4rem;
  line-height: 1; color: var(--ink-3); flex: none;
}
.faq details[open] summary::after { content: "−"; }
.faq summary:hover { color: var(--ink); }
.faq .ans { padding-bottom: var(--s4); font-size: var(--fs-sm); color: var(--ink-2); }

/* ---------- emergency alert ---------- */
.alert {
  background: #fdf3f2;
  border: 1px solid #f0cfcc;
  border-left: 4px solid var(--urgent);
  border-radius: var(--radius);
  padding: var(--s4);
}
.alert h3 { font-size: var(--fs-h3); color: var(--urgent); font-weight: 650; }
.alert p { font-size: var(--fs-sm); color: var(--ink-2); margin-top: var(--s2); }

/* ---------- form ---------- */
.form { display: grid; gap: var(--s3); max-width: 34rem; }
.field { display: grid; gap: var(--s1); }
.field label { font-size: var(--fs-sm); font-weight: 600; }
.field input, .field select, .field textarea {
  font: inherit; font-size: var(--fs-sm); padding: 0.6875rem var(--s2);
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--paper); color: var(--ink);
}
.field textarea { min-height: 7rem; resize: vertical; }

/* ---------- CTA block ---------- */
.cta-block { display: grid; gap: var(--s4); justify-items: start; }
.cta-block h2 { font-size: var(--fs-h2); font-weight: 660; }

/* ---------- footer ---------- */
.ftr { background: var(--dark); color: var(--dark-ink); padding-block: var(--s6) var(--s5); }
.ftr a { color: var(--dark-ink-2); text-decoration: none; font-size: var(--fs-sm); }
.ftr a:hover { color: var(--dark-ink); text-decoration: underline; text-underline-offset: 3px; }
.ftr-grid { display: grid; gap: var(--s5); }
@media (min-width: 780px) { .ftr-grid { grid-template-columns: 1.4fr 1fr 1fr 1fr; } }
.ftr h4 { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.09em; color: var(--dark-ink-2); margin: 0 0 var(--s2); font-weight: 650; }
.ftr ul { display: grid; gap: var(--s1); }
.ftr-legal {
  margin-top: var(--s6); padding-top: var(--s4); border-top: 1px solid var(--dark-line);
  font-size: var(--fs-xs); color: var(--dark-ink-2);
  display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4);
}
.tagline { font-size: var(--fs-sm); color: var(--dark-ink-2); margin-top: var(--s3); font-style: italic; }

/* ---------- sticky mobile call bar (from Holliday) ---------- */
.callbar {
  position: fixed; inset: auto 0 0 0; z-index: 50;
  display: grid; grid-template-columns: 1fr auto;
  gap: 1px; background: var(--line);
  border-top: 1px solid var(--line);
  box-shadow: 0 -2px 14px rgb(0 0 0 / 0.08);
}
.callbar a {
  padding: 0.9375rem var(--s3); text-decoration: none; font-weight: 650;
  font-size: var(--fs-sm); text-align: center;
}
.callbar .call { background: var(--accent); color: var(--accent-ink); }
.callbar .emg  { background: var(--urgent); color: var(--urgent-ink); padding-inline: var(--s5); }
@media (min-width: 900px) { .callbar { display: none; } body { padding-bottom: 0; } }

/* ---------- breadcrumb ---------- */
.crumb { font-size: var(--fs-xs); color: var(--ink-3); padding-top: var(--s4); }
.crumb a { text-decoration: none; }
.crumb a:hover { text-decoration: underline; }

/* ---------- logo ----------
   The mark is white type + a lime bolt, drawn to sit on black. On the light
   header that means giving it its own dark plate rather than recolouring the
   type — recolouring costs the bolt its contrast and the wordmark its weight.
   In the footer the band is already dark, so the mark sits bare on it.
   Source is a 262x98 cutout; capped at 148px wide so it renders at ~1.8x
   density and the compressed edges never show. */
.logo {
  display: inline-flex; align-items: center;
  background: var(--dark); border-radius: var(--radius);
  padding: 0.5rem 0.75rem;
}
.logo img { display: block; width: 148px; height: auto; }
.ftr-logo { display: block; width: 168px; height: auto; }

/* ---------- mobile nav ---------- */
.menu { margin-left: auto; }
.menu > summary {
  list-style: none; cursor: pointer;
  display: inline-flex; align-items: center; gap: var(--s2);
  font-size: var(--fs-sm); font-weight: 600;
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 0.5rem var(--s3);
}
.menu > summary::-webkit-details-marker { display: none; }
.menu > summary::before { content: "☰"; font-size: 1rem; line-height: 1; }
.menu[open] > summary::before { content: "✕"; }
.menu-panel {
  position: absolute; left: 0; right: 0; top: 100%;
  background: var(--paper); border-bottom: 1px solid var(--line);
  box-shadow: 0 8px 20px rgb(0 0 0 / 0.07);
  padding: var(--s3) var(--gutter) var(--s4);
  display: grid; gap: var(--s1);
}
.menu-panel a {
  text-decoration: none; color: var(--ink); font-weight: 600;
  padding: 0.6875rem 0; border-bottom: 1px solid var(--line);
}
.menu-panel a:last-child { border-bottom: 0; }
.menu-panel .emg-link { color: var(--urgent); }
@media (min-width: 900px) { .menu { display: none; } }

/* ---------- mid-page CTA strip ---------- */
.cta-strip {
  background: var(--accent); color: var(--accent-ink);
  padding-block: var(--s5);
}
.cta-strip .in {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--s3) var(--s5); justify-content: space-between;
}
.cta-strip p { font-weight: 640; font-size: 1.125rem; color: var(--accent-ink); max-width: 46ch; }
.cta-strip .btn-dark { background: var(--dark); color: #fff; }
.cta-strip .btn-dark:hover { filter: brightness(1.25); }

/* ---------- inclusions table (maintenance program) ---------- */
.incl { border-top: 1px solid var(--line); }
.incl .row {
  display: grid; gap: var(--s1); padding-block: var(--s4);
  border-bottom: 1px solid var(--line);
}
@media (min-width: 760px) { .incl .row { grid-template-columns: 15rem 1fr; gap: var(--s5); align-items: start; } }
.incl h3 { font-size: var(--fs-h3); font-weight: 640; }
.incl p { font-size: var(--fs-sm); color: var(--ink-2); }

/* ---------- tier cards ---------- */
.tier { border: 1px solid var(--line); border-radius: var(--radius-lg); padding: var(--s4); display: grid; gap: var(--s2); align-content: start; }
.tier.is-lead { border-color: var(--ink); border-top: 4px solid var(--accent); }
.tier .price { font-size: 1.5rem; font-weight: 680; }
.tier .unit { font-size: var(--fs-xs); color: var(--ink-3); }

/* ---------- contact detail list ---------- */
.deets { display: grid; gap: var(--s4); }
.deets .d h4 { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.09em; color: var(--ink-3); margin: 0 0 var(--s1); font-weight: 650; }
.deets .d p, .deets .d a { font-size: var(--fs-body); color: var(--ink); text-decoration: none; font-weight: 600; }
.deets .d a:hover { text-decoration: underline; text-underline-offset: 3px; }

/* header CTA must stay hidden on mobile — .btn's display would otherwise win on source order */
.hdr .hdr-cta { display: none; }
@media (min-width: 900px) { .hdr .hdr-cta { display: inline-flex; } }

/* ============================================================
   MOTION LAYER — the "extra juice"
   Rules: nothing moves more than ~14px, nothing lasts longer than
   500ms, nothing animates that the user didn't scroll to. All of it
   turns off under prefers-reduced-motion. Motion here is used to
   direct attention, never to decorate.
   ============================================================ */

/* --- 1. scroll reveal (JS adds .is-in) --- */
.rv { opacity: 0; transform: translateY(14px); }
.rv.is-in { opacity: 1; transform: none; transition: opacity .55s cubic-bezier(.22,.61,.36,1), transform .55s cubic-bezier(.22,.61,.36,1); }
/* stagger children of a revealed grid */
.rv-stagger > * { opacity: 0; transform: translateY(14px); }
.rv-stagger.is-in > * { opacity: 1; transform: none; transition: opacity .5s cubic-bezier(.22,.61,.36,1), transform .5s cubic-bezier(.22,.61,.36,1); }
.rv-stagger.is-in > *:nth-child(2) { transition-delay: .07s; }
.rv-stagger.is-in > *:nth-child(3) { transition-delay: .14s; }
.rv-stagger.is-in > *:nth-child(4) { transition-delay: .21s; }
.rv-stagger.is-in > *:nth-child(5) { transition-delay: .28s; }
.rv-stagger.is-in > *:nth-child(6) { transition-delay: .35s; }
.rv-stagger.is-in > *:nth-child(7) { transition-delay: .42s; }
.rv-stagger.is-in > *:nth-child(8) { transition-delay: .49s; }

/* --- 2. header condenses once you leave the hero --- */
.hdr { transition: box-shadow .3s ease, background-color .3s ease; }
.hdr.is-stuck { box-shadow: 0 6px 24px rgb(13 15 18 / 0.06); }
.hdr.is-stuck .hdr-in { padding-block: 0.55rem; transition: padding .3s ease; }

/* --- 3. cards lift, and the accent rule draws in on hover --- */
.card { transition: transform .28s cubic-bezier(.22,.61,.36,1), box-shadow .28s ease, border-color .28s ease; position: relative; overflow: hidden; }
.card::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 3px;
  background: var(--accent); transform: scaleX(0); transform-origin: left;
  transition: transform .38s cubic-bezier(.22,.61,.36,1);
}
.card:hover, .card:focus-visible { transform: translateY(-3px); box-shadow: 0 10px 30px rgb(13 15 18 / 0.09); border-color: var(--ink-3); }
.card:hover::after, .card:focus-visible::after { transform: scaleX(1); }

/* --- 4. buttons: press feedback, not just colour --- */
.btn { transition: transform .12s ease, filter .18s ease, background-color .18s ease, color .18s ease; }
.btn:active { transform: translateY(1px); }

/* --- 5. nav underline --- */
.nav a { position: relative; }
.nav a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -4px; height: 2px;
  background: var(--accent); transform: scaleX(0); transform-origin: right;
  transition: transform .26s cubic-bezier(.22,.61,.36,1);
}
.nav a:hover::after, .nav a[aria-current="page"]::after { transform: scaleX(1); transform-origin: left; }

/* --- 6. process steps: the connecting line draws as you scroll to it --- */
/* No connector line here. Each .step already carries a 2px lime border-top;
   adding a grey rule 1rem below it rendered as two parallel lines. The steps
   still read as a sequence via the 01-04 counters and the stagger reveal. */
.steps { position: relative; }

/* --- 7. CTA strip: light sweeps across the lime on hover --- */
.cta-strip { position: relative; overflow: hidden; }
.cta-strip::before {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(105deg, transparent 40%, rgb(255 255 255 / 0.45) 50%, transparent 60%);
  transform: translateX(-100%); transition: transform .8s ease;
}
.cta-strip:hover::before { transform: translateX(100%); }

/* --- 8. FAQ: answers slide open instead of snapping --- */
.faq details > div, .faq details > p { animation: faq-open .28s cubic-bezier(.22,.61,.36,1); }
@keyframes faq-open { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
.faq summary { transition: color .2s ease; }

/* --- 9. mobile call bar slides up once you've scrolled past the hero CTA --- */
.callbar { transform: translateY(100%); transition: transform .32s cubic-bezier(.22,.61,.36,1); }
.callbar.is-in { transform: none; }

/* --- respect the setting --- */
@media (prefers-reduced-motion: reduce) {
  .rv, .rv-stagger > *, .rv.is-in, .rv-stagger.is-in > * { opacity: 1 !important; transform: none !important; transition: none !important; }
  .callbar { transform: none !important; transition: none !important; }
  .cta-strip::before { display: none; }
  .card, .btn, .card::after, .nav a::after { transition: none !important; }
  .faq details > div, .faq details > p { animation: none !important; }
}

/* ---------- header colour discipline (28 Jul) ----------
   The logo plate is already a solid black block. A lime button beside it
   makes two loud elements fighting inside one 68px bar. Lime stays as the
   accent — the bolt, the active underline — and the header CTA goes dark. */
.hdr .hdr-cta { background: var(--ink); color: #fff; border-color: var(--ink); }
.hdr .hdr-cta:hover { filter: none; background: #23272e; border-color: #23272e; }

/* ---------- photography ----------
   Real-estate style shots. Fixed aspect + object-fit so a swapped-in photo of
   any dimension can never change the layout. */
.shot { margin: 0; border-radius: var(--radius-lg); overflow: hidden; background: var(--paper-3); }
.shot img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* Matthew's photos are all 2:3 portraits, so the slots are 4:5. A 16/9 hero
   would crop a portrait down to a band of his shoulders — the room, the tool
   and the branded polo all disappear, which is the whole point of the shot. */
.shot-hero { aspect-ratio: 4 / 5; }
.shot-portrait { aspect-ratio: 4 / 5; }
/* The Services hero is the one landscape slot on the site: that page's brief was
   always "wide shot", so it gets 3:2 rather than the portrait 4:5. It also skips
   `.hero .shot-hero` full-height override — stretching a 3:2 frame to the
   height of the copy column would crop it back to a letterbox. */
.shot-wide { aspect-ratio: 3 / 2; }

.card-photo { padding: 0; overflow: hidden; display: flex; flex-direction: column; }
.card-photo .shot-thumb {
  display: block; width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover;
  background: var(--paper-3); border-bottom: 1px solid var(--line);
}
.card-photo h3, .card-photo p, .card-photo .more { margin-inline: var(--s3); }
.card-photo h3 { margin-top: var(--s3); }
.card-photo .more { margin-bottom: var(--s3); margin-top: auto; padding-top: var(--s3); }
/* the hover rule is drawn under the photo, not over it */
.card-photo::after { z-index: 2; }

/* ---------- vetting panel ----------
   Sits where the Who-we-serve hero photo slot was. That slot was briefed as a
   generic Brisbane apartment exterior, which tells a property manager nothing;
   this is what someone adding a trade to their list actually checks. Every line
   is a confirmed fact, so it ships final rather than waiting on Matthew.
   Insurance cover is deliberately absent — the amount is still unconfirmed. */
.creds {
  align-self: start;
  border: 1px solid var(--line); border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  background: var(--paper-2); padding: var(--s4);
}
.creds dl { display: grid; gap: var(--s3); margin-top: var(--s3); }
.creds dt {
  font-size: var(--fs-xs); text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--ink-3);
}
/* the browser's default 40px dd indent reads as a mistake in a boxed panel */
.creds dd { font-size: var(--fs-sm); font-weight: 560; margin: 2px 0 0; }

/* ---------- service-area map ----------
   Drawn, not an image: every colour is a token, and redrawing it to Matthew's
   real radius is a coordinate change rather than a new asset. Deliberately
   schematic — it shows reach, it is not a survey.
   The boundary uses the brand accent, not red: red is reserved sitewide for
   genuine emergency, and spending it on a map would blunt that. */
.map { margin: 0; display: grid; gap: var(--s3); }
.map svg { width: 100%; height: auto; display: block; border: 1px solid var(--line); border-radius: var(--radius-lg); }
.map .land  { fill: var(--paper-2); }
.map .water { fill: #e3e9ea; }
.map .sea   { fill: var(--ink-3); font-size: 12px; font-weight: 600; letter-spacing: .04em; }
.map .river { fill: none; stroke: #cfd9db; stroke-width: 9; stroke-linecap: round; }
.map .zone {
  fill: var(--accent); fill-opacity: 0.13;
  stroke: var(--accent-text); stroke-width: 2; stroke-dasharray: 7 6;
}
.map .pin { fill: var(--accent-text); stroke: var(--paper); stroke-width: 2; }
.map .lbl { fill: var(--ink-2); font-size: 13px; font-weight: 560; }
/* Two emphasised markers, 18 Aug: Redcliffe is where the business is BASED,
   the CBD is the far EDGE of the area. Previously one marker (.pin-cbd) sat on
   the CBD as the centre of the map, which is no longer what the area is. */
.map .pin-base { fill: var(--ink); }
.map .lbl-base { fill: var(--ink); font-weight: 700; }
.map .pin-edge { fill: var(--paper); stroke: var(--ink); stroke-width: 2.5; }
.map .lbl-edge { fill: var(--ink); font-weight: 660; }

.map figcaption { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4); font-size: var(--fs-xs); color: var(--ink-3); }
.map .key { display: inline-flex; align-items: center; gap: 0.5ch; }
.map .sw { width: 14px; height: 14px; border-radius: 3px; display: inline-block; }
.map .sw-zone { background: color-mix(in srgb, var(--accent) 20%, transparent); border: 2px dashed var(--accent-text); }
.map .sw-pin { width: 9px; height: 9px; border-radius: 50%; background: var(--accent-text); }
.map .sw-base { width: 12px; height: 12px; border-radius: 50%; background: var(--ink); }

/* Build-notes toggle (review.js). Hiding them makes the same URL client-safe. */
html.notes-off .wf, html.notes-off .wf-banner { display: none !important; }

/* ============================================================
   HOME — DE-STOCK PASS (18 Aug 2026)
   The home page ran three 4-up card grids back to back: services,
   audiences, process. Same box, same spacing, three times — which is
   what makes a page read as a template rather than as this business.
   Nothing below changes a word of copy. Each of the three sections
   now carries its own shape:
     services  -> a hairline row index (no boxes, no tinted tiles)
     audiences -> asymmetric photo tiles, label on the image
     process   -> a rail with nodes, not four bordered columns
   Plus: the service-area section goes to black so the page has a
   second dark anchor before the footer, one statement line breaks
   the section-after-section rhythm, and hover states get finished.
   ============================================================ */

/* ---------- 1. services as a row index ----------
   A trades service list is a list. Four bordered boxes was the
   template answer; rows separated by hairlines read faster, hold
   longer descriptions, and stop the section rhyming with the two
   below it. The icons survive — it is the tinted square behind them
   that was the stock tell, so that goes and the drawing gets bigger. */
.srv { border-top: 1px solid var(--line); }
.srv-item {
  position: relative; isolation: isolate;
  display: grid; gap: var(--s1) var(--s4);
  grid-template-columns: auto 1fr;
  align-items: start;
  padding-block: var(--s4);
  border-bottom: 1px solid var(--line);
  text-decoration: none; color: inherit;
}
@media (min-width: 900px) {
  .srv-item {
    grid-template-columns: 34px minmax(0, 17rem) minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--s5);
    padding-block: var(--s5);
  }
}
/* the hover wash bleeds past the text so the whole row is the target */
.srv-item::before {
  content: ""; position: absolute; z-index: -1;
  inset: 0 calc(var(--s3) * -1);
  background: var(--accent-quiet); border-radius: var(--radius);
  opacity: 0; transition: opacity .22s ease;
}
.srv-item::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px;
  background: var(--accent); transform: scaleX(0); transform-origin: left;
  transition: transform .38s cubic-bezier(.22,.61,.36,1);
}
.srv-item:hover::before, .srv-item:focus-visible::before { opacity: 1; }
.srv-item:hover::after,  .srv-item:focus-visible::after  { transform: scaleX(1); }

.srv-item .ico { background: none; width: 34px; height: 34px; place-items: start center; }
.srv-item .ico svg { width: 32px; height: 32px; }
.srv-h { font-size: var(--fs-h3); font-weight: 640; }
/* underline drawn on hover rather than a static one — the row already
   announces itself as a link by moving */
.srv-h span {
  background-image: linear-gradient(var(--ink), var(--ink));
  background-size: 0% 1.5px; background-repeat: no-repeat;
  background-position: 0 100%;
  transition: background-size .32s cubic-bezier(.22,.61,.36,1);
}
.srv-item:hover .srv-h span, .srv-item:focus-visible .srv-h span { background-size: 100% 1.5px; }
.srv-item p { font-size: var(--fs-sm); color: var(--ink-2); grid-column: 2 / -1; }
@media (min-width: 900px) { .srv-item p { grid-column: auto; } }
.srv-go {
  display: none; font-size: 1.125rem; color: var(--ink-3);
  transition: transform .24s cubic-bezier(.22,.61,.36,1), color .24s ease;
}
@media (min-width: 900px) { .srv-go { display: block; } }
.srv-item:hover .srv-go, .srv-item:focus-visible .srv-go { transform: translateX(5px); color: var(--ink); }

/* ---------- 2. audiences as photo tiles ----------
   Same four photos, but the label sits on the image instead of in a
   bordered box under it, and the columns are 7/5 then 5/7 so the row
   below does not mirror the row above. */
.tiles { display: grid; gap: var(--s3); }
@media (min-width: 700px) {
  .tiles { grid-template-columns: repeat(12, 1fr); gap: var(--s3); }
  .tiles > *:nth-child(1) { grid-column: span 7; }
  .tiles > *:nth-child(2) { grid-column: span 5; }
  .tiles > *:nth-child(3) { grid-column: span 5; }
  .tiles > *:nth-child(4) { grid-column: span 7; }
}
.tile {
  position: relative; isolation: isolate; display: block; overflow: hidden;
  border-radius: var(--radius-lg); text-decoration: none; background: var(--ink);
}
.tile img {
  width: 100%; height: 100%; object-fit: cover; aspect-ratio: 16 / 10;
  transition: transform .5s cubic-bezier(.22,.61,.36,1);
}
.tile::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to top, rgb(13 15 18 / .94) 0%, rgb(13 15 18 / .72) 30%, rgb(13 15 18 / .08) 76%);
}
.tile-in { position: absolute; inset: auto 0 0 0; z-index: 1; padding: var(--s4); display: grid; gap: 0.35rem; }
.tile-in h3 { font-size: var(--fs-h3); font-weight: 650; color: #fff; }
.tile-in h3 span {
  background-image: linear-gradient(var(--accent), var(--accent));
  background-size: 0% 2px; background-repeat: no-repeat; background-position: 0 100%;
  transition: background-size .32s cubic-bezier(.22,.61,.36,1);
}
.tile-in p { font-size: var(--fs-sm); color: rgb(255 255 255 / .82); max-width: 36ch; }
.tile:hover img, .tile:focus-visible img { transform: scale(1.045); }
.tile:hover .tile-in h3 span, .tile:focus-visible .tile-in h3 span { background-size: 100% 2px; }

/* ---------- 3. process as a rail ----------
   Four columns with a lime bar on top of each read as four cards. One
   continuous rail with a node per step reads as a sequence, which is
   what the section is actually saying. */
@media (min-width: 900px) {
  .steps::before {
    content: ""; position: absolute; top: 5px; left: 0; right: 0;
    height: 1px; background: var(--line);
  }
}
.step { border-top: 0; padding-top: var(--s5); }
.step::after {
  content: ""; position: absolute; top: 0; left: 0;
  width: 11px; height: 11px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 0 4px var(--paper-2);
}
.step::before { top: 1px; left: 22px; }
.step h3 { margin-top: 0; }

/* ---------- 4. one statement line ----------
   Section, section, section is its own kind of flat. This is his own
   tagline — the line already on his logo and in the footer — given the
   room to be read once. */
.statement { padding-block: var(--s8); }
.statement .rule { display: block; width: 56px; height: 3px; background: var(--accent); }
.statement blockquote {
  margin: var(--s4) 0 0;
  font-size: clamp(1.65rem, 3.6vw, 2.65rem); line-height: 1.16;
  font-weight: 640; letter-spacing: -0.022em; max-width: 20ch; text-wrap: balance;
}
.statement .src {
  margin-top: var(--s4); font-size: var(--fs-xs);
  text-transform: uppercase; letter-spacing: 0.09em; color: var(--ink-3);
}

/* ---------- 5. the map section goes to black ----------
   The page was white, grey, white, grey the whole way down. The service
   area is the one section that is pure information, so it can carry the
   contrast without competing with the hero. */
.band-dark .map svg { border-color: var(--dark-line); }
.band-dark .map .land  { fill: #1e232b; }
.band-dark .map .water { fill: #0a1115; }
.band-dark .map .river { stroke: #2b4753; }
.band-dark .map .sea   { fill: #6e7982; }
.band-dark .map .zone  { fill: var(--accent); fill-opacity: 0.06; stroke: var(--accent); stroke-opacity: 0.85; }
.band-dark .map .pin   { fill: var(--accent); stroke: var(--dark); }
.band-dark .map .lbl   { fill: var(--dark-ink-2); }
.band-dark .map .pin-base { fill: #fff; }
.band-dark .map .lbl-base { fill: #fff; }
.band-dark .map .pin-edge { fill: var(--dark); stroke: #fff; }
.band-dark .map .lbl-edge { fill: #fff; }
.band-dark .map figcaption { color: var(--dark-ink-2); }
.band-dark .map .sw-zone { border-color: var(--accent); }
.band-dark .map .sw-pin  { background: var(--accent); }
.band-dark .map .sw-base { background: #fff; }
.band-dark .chips li { border-color: var(--dark-line); color: var(--dark-ink-2); }
.band-dark .wf { background: #1b1a10; border-color: #3d3a12; color: #d9d5a8; }
.band-dark .wf b { color: var(--accent); }

/* ---------- 6. spacing is no longer one number ----------
   Every section was padded --s7 top and bottom, which is why the page
   scrolled at one flat pace. The sections that carry the most weight
   now get more air than the ones that don't. */
.pad-xl { padding-block: var(--s8); }

/* ---------- 7. hover craft on the cards that stayed ----------
   The underline is drawn on hover and the arrow moves with it. The
   arrow is inline-block so the underline stops at the word. */
.card .more {
  text-decoration: underline; text-decoration-color: transparent;
  text-decoration-thickness: 1.5px; text-underline-offset: 3px;
  transition: text-decoration-color .24s ease;
}
.card .more::after { display: inline-block; transition: transform .24s cubic-bezier(.22,.61,.36,1); }
a.card:hover .more, .card:has(a:hover) .more { text-decoration-color: var(--accent-text); }
a.card:hover .more::after, .card:has(a:hover) .more::after { transform: translateX(4px); }

@media (prefers-reduced-motion: reduce) {
  .srv-item::before, .srv-item::after, .tile img, .tile-in h3 span,
  .srv-h span, .srv-go, .card .more, .card .more::after { transition: none !important; }
  .tile:hover img { transform: none !important; }
}

/* ---------- 8. "why people switch" is a two-column section ----------
   It used to be one ragged left column on a full-width page: an h2 running the
   whole 1180px over paragraphs capped at 68ch over a 34rem review card, with
   the entire right half of the section empty. The headline was set wider than
   its own body copy, and the review was jammed 16px under the last paragraph
   so it read as more paragraph rather than as proof.
   Now the copy holds a column it actually fills, and the review sits beside it
   with its own room — the same two-column structure the hero above and the
   contact block below already use. */
.switch { display: grid; gap: var(--s5); align-items: start; }
@media (min-width: 900px) {
  .switch { grid-template-columns: 1.1fr 0.9fr; gap: var(--s7); align-items: center; }
}
.switch-side { display: grid; gap: var(--s3); }
/* in a column of its own the review fills the column rather than a fixed 34rem */
.switch .tm-pull { max-width: none; }

/* ---------- 9. the CTA strip line breaks cleanly or not at all ----------
   At 46ch the line wrapped mid-clause ("...and a free / quote."), which reads
   as a mistake rather than a line break. Wide enough to sit on one line at
   desktop, and balanced into two even lines when it has to wrap. */
.cta-strip p { max-width: 64ch; text-wrap: balance; }

/* ---------- 10. hero: the photo was running the page off the bottom ----------
   Two problems, one cause. With `aspect-ratio: auto` the figure took the
   photo's intrinsic 4:5 height, and the bleed let the column grow to the full
   viewport edge — ~1010px wide at 2000px. So the row went ~900px tall while
   the copy beside it needed 430, leaving a half-empty left column; and simply
   capping the height turned a portrait photo into a wide letterbox that cut
   Matthew's face off.
   Fix both: stop the bleed short of the edge so the column stays around
   800px, and cap the height at 40rem. That lands the photo near square, which
   is what a portrait of a person wants, and puts the whole hero at ~760px —
   inside the 600-800 band where the next section still peeks below the fold
   on a laptop. */
@media (min-width: 1100px) {
  /* was: the full distance out to the viewport edge. Now at most 220px of it,
     and never closer than 40px to the wrap's own edge on mid-size screens. */
  .hero { --bleed: calc(max(0px, min(220px, (100vw - var(--max)) / 2 - 40px)) + var(--gutter)); }
}
@media (min-width: 900px) {
  .hero .shot-hero { max-height: 40rem; }
  .hero .shot-hero img { height: 100%; }
  /* the copy column no longer stretches to the photo's full height, so it sits
     in the optical middle instead of pinned to the top with air underneath */
  .hero .hero-grid > div:first-child { align-self: center; }
}

/* ---------- 11. process: four steps, four columns, no separation ----------
   The rail's 1px top line plus a node per step was too quiet to divide four
   columns of running text — the section read as one flat block of paragraphs.
   Vertical hairlines between the steps do the dividing; the top rail is
   strengthened to the same weight so the two read as one drawn frame. */
@media (min-width: 900px) {
  .steps { grid-template-columns: repeat(4, 1fr); gap: 0; }
  .steps::before { background: var(--line); height: 1px; }
  .step { padding-inline: var(--s5); }
  .step:first-child { padding-inline-start: 0; }
  .step:last-child { padding-inline-end: 0; }
  .step + .step { border-inline-start: 1px solid var(--line); }
  /* the node has to sit on the rail, which is now inset by the step's padding */
  .step::after { left: var(--s5); }
  .step::before { left: calc(var(--s5) + 22px); }
  .step:first-child::after { left: 0; }
  .step:first-child::before { left: 22px; }
}

/* ---------- 12. inner-page hero portraits get the same cap as the home hero ----------
   `.shot-portrait` holds a fixed 4:5, so in a ~554px column it renders ~692px
   tall while the copy beside it needs about 470. Every Who-we-serve and
   service page was left with a band of empty space under its buttons. Cap the
   photo the way the home hero is capped; the copy is already centred by the
   rule in block 10, which applies to every .hero on the site. */
@media (min-width: 900px) {
  .hero .shot-portrait { max-height: 34rem; }
  .hero .shot-portrait img { height: 100%; }
}
