/*
 * Published contractor site styles.
 *
 * The single source of visual truth. SectionRenderer emits the class names
 * below, and the builder previews pages through an iframe of that same
 * renderer — so there is exactly one place where a section's look is defined.
 *
 * Extracted from the Claude Design prototypes in design/*.dc.html.
 */

:root {
  --bg: #faf8f3;
  --bg-alt: #f2ede2;
  --dark: #262019;
  --dark-2: #2f2820;
  --text: #2b2521;
  --muted: #6f6558;
  /* Darker muted, for lead paragraphs that sit at larger sizes and carry real
     content. 7.3:1 on --bg-alt against --muted's 4.9:1. */
  --muted-strong: #554b40;
  --accent: #8a6540;
  --accent-dark: #6d4e2f;
  --gold: #c9a877;
  --gold-bright: #e8b34b;
  --border: #e8e0d2;
  --border-2: #d8cebc;
  --on-dark: #faf8f3;
  --on-dark-muted: #ddd3c3;
  --radius: 12px;
  /* Overridden per site from the builder's Theme panel (SiteTheme.java). */
  --font-heading: 'Marcellus', Georgia, serif;
  --font-body: 'Karla', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-nav: var(--font-body);
  --btn-bg: var(--accent);
  --btn-bg-hover: var(--accent-dark);
  --btn-text: var(--on-dark);
  --btn-hero-bg: var(--gold);
  --btn-hero-bg-hover: var(--gold-bright);
  --btn-hero-text: var(--dark);
  --btn-text-hover: var(--btn-text);
  --btn-hero-text-hover: var(--btn-hero-text);
  --btn-hover-ring: none;
  /* One device pixel, expressed in CSS pixels.

     A 1px rule is 1.5 device pixels on a 150% Windows display, and the browser
     has to snap that to a whole number: at an even position it lands on two
     rows, at an odd one on a single row. Identical rules then look like two
     different designs down the same page -- one bar thin, the next twice as
     heavy. Sizing the rule to exactly one device pixel removes the choice, so
     every bar on the site renders the same whatever it lands on. */
  --hair: 1px;
}
@media (min-resolution: 1.1dppx) and (max-resolution: 1.3dppx) { :root { --hair: 0.8px; } }
@media (min-resolution: 1.3dppx) and (max-resolution: 1.6dppx) { :root { --hair: 0.667px; } }
@media (min-resolution: 1.6dppx) and (max-resolution: 1.9dppx) { :root { --hair: 0.572px; } }
@media (min-resolution: 1.9dppx) { :root { --hair: 0.5px; } }

* { box-sizing: border-box; }

/* hidden always hides. The browser's own rule for it is the weakest there is,
   so any class that sets display -- the lightbox arrows' flex, say -- would
   otherwise quietly show an element the script has hidden. */
[hidden] { display: none !important; }

body {
  margin: 0;
  /* One background for the whole site. Sections no longer alternate between
     two creams; the divider rule below is what separates them, and the only
     band that breaks the field is the dark one behind the reviews. */
  background: var(--bg-alt);
  color: var(--text);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
  /* Sticky footer: on a short page — a service with one subservice, say — the
     footer would otherwise float mid-screen with background below it. */
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

main { flex: 1 0 auto; }

a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-dark); }
img { max-width: 100%; display: block; }

h1, h2, h3, .serif {
  font-family: var(--font-heading);
  font-weight: 400;
  margin: 0;
}

.container { max-width: 1180px; margin: 0 auto; padding: 0 24px; }
/* Even and tight, top and bottom. The divider at the top of each band is what
   separates one section from the next, so the padding only has to keep the
   content off the line rather than do the separating itself. */
.section { padding: 25px 0; }
.section--alt { background: var(--bg-alt); }

/* One divider for the whole site: a hairline the width of the container,
   drawn at the top of every band that follows another. Drawing it on the
   following band rather than the preceding one means there is never a line
   left hanging after the last section on a page.
   .band marks a full-width section wrapper; article blocks inside a blog post
   do not carry it, so paragraphs are not sliced apart by rules. */
.band + .band::before {
  content: ""; display: block; height: var(--hair); background: var(--dark);
  width: min(100% - 48px, 1132px); margin: 0 auto;
}
/* A band with nothing in it has no height, so its rule lands straight on the
   next band's and the two read as one double-thick bar. The renderer no longer
   emits such a band at all; this covers any page still served by an older
   build, and the two rules under it keep an empty band from breaking the
   chain that holds a rule away from the cover and off the top of a page. */
.band:empty::before { display: none; }
.band:empty:first-child + .band::before { display: none; }
.band:has(> .hero) + .band:empty + .band::before { display: none; }
/* Two places where a line would be noise rather than structure: under the
   cover, which already ends in a large dark field, and against the reviews
   band, which is a field of colour whose own edges do the separating. */
.band:has(> .hero) + .band::before,
.band:has(> .section--reviews) + .band::before,
.band:has(> .section--reviews)::before { display: none; }
/* And the blog, where every band is a card with its own edges: a rule
   between two of them draws a line around nothing. */
.page-blog .band::before { display: none; }

.eyebrow {
  font-size: 13px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--accent); font-weight: 700; margin-bottom: 10px;
}
/* Marcellus has a single 400 cut, so asking it for bold gives a smeared faux
   bold. Section titles use Karla 800 instead: a real heavy weight, already
   loaded. The serif is kept for the hero and card titles, so the page reads as
   display serif + strong sans section headers rather than one flat voice. */
.section-title { font-family: var(--font-body); font-weight: 800;
                 font-size: clamp(32px, 4vw, 46px); letter-spacing: -.015em; }
/* Centred: the hero copy and the cards below are centred, so a left-aligned
   head was the only thing breaking the column. */
.section-head { margin-bottom: 40px; text-align: center; }
.section-lead { font-size: 17px; line-height: 1.6; color: var(--muted-strong); max-width: 62ch; margin: 12px auto 0; }

/* Text the contractor typed is shown as they typed it.
 *
 * HTML collapses runs of spaces and ignores newlines, so an indent or a blank
 * line between paragraphs in the builder simply vanished on the page -- the
 * box said one thing and the site said another, with nothing to explain the
 * difference. pre-wrap keeps both and still wraps normally at the edge.
 *
 * Review text is the one case handled separately, further down: it comes from
 * Google rather than from the builder, so its line breaks are kept but its runs
 * of spaces are not. See .review-text. */
.section-lead,
.card-text,
.detail-text,
.founder-bio,
.process-step-text,
.cta-sub,
.verdict-item p,
/* The hero subheadline has no class of its own -- it is the paragraph that
   follows the headline inside the copy block. */
.hero-copy p { white-space: pre-wrap; }

/* The band that opens a page. Its heading is the page's title, so it runs a
   little larger than the section heads below it, and its intro is given the
   width of the page rather than the 62ch reading measure -- a deliberate
   choice here, where the paragraph is the content, rather than a caption
   sitting above a grid. */
.section--intro .section-title { font-size: clamp(35px, 4.4vw, 51px); }
/* The intro's lead is written on the section rather than inside .section-head,
   so it misses the centring that head gives everything in it. */
.section--intro .section-lead { width: fit-content; max-width: 90%; text-align: center; }

/* With a background photo the band behaves like the cover: the image fills it,
   a scrim holds the contrast, and the container is lifted above both. The
   band keeps a little more air than a plain one, because type sitting tight
   against the edge of a photograph looks like a mistake. */
.section--intro-photo { position: relative; background: var(--dark); padding: 40px 0; }
/* Tighter under the heading too: on a photo the pair reads as one block, and
   the wide gap the plain band uses pulls them apart. */
.section--intro-photo .section-head { margin-bottom: 22px; }
.section--intro-photo .intro-bg { position: absolute; inset: 0; }
.section--intro-photo .intro-scrim { position: absolute; inset: 0; pointer-events: none;
                                     background: rgba(38, 32, 25, .58); }
.section--intro-photo > .container { position: relative; }
/* Larger than a plain intro's heading: a photographic band is a bigger field
   and the same size reads as small against it. The lead has to move with it --
   the dark text it wears on cream disappears on a photo. */
.section--intro-photo .section-title { font-size: clamp(42px, 5.3vw, 61px);
                                       color: var(--on-dark); }
/* Full strength, not the muted cream: over a photograph the muted tone loses
   against the bright parts of the image, and this is the one paragraph on the
   page that has to be read. */
/* Centred under a centred heading. The intro's lead is rendered outside the
   .section-head block -- that is what makes it range left here while every
   other band's does not -- so the alignment has to be stated. */
.section--intro-photo .section-lead { color: var(--on-dark); font-size: 18.5px;
                                      text-align: center; max-width: 70ch; }

/* One button for the whole site, the brown from the contact prompt. The cover
   is the single exception below: it sits on a dark photograph, where brown on
   near-black would barely read as a button at all. */
.btn {
  /* 10% up on the original 15/28 padding and 16px label. */
  display: inline-block; padding: 16.5px 30.8px; border-radius: 7px;
  font-weight: 600; font-size: 17.6px; border: none; cursor: pointer;
  /* A <button> doesn't inherit the page's font as a link does: without this
     the contact form's Send button was in the browser's default face. */
  font-family: inherit;
  background: var(--btn-bg); color: var(--btn-text);
}
.btn:hover { background: var(--btn-bg-hover); color: var(--btn-text-hover); box-shadow: var(--btn-hover-ring); }
.btn--hero { background: var(--btn-hero-bg); color: var(--btn-hero-text); font-weight: 700; }
.btn--hero:hover { background: var(--btn-hero-bg-hover); color: var(--btn-hero-text-hover); }
.btn--ghost { background: transparent; border: 1.5px solid rgba(243,237,226,0.55); color: var(--on-dark); font-weight: 600; }
.btn--ghost:hover { border-color: var(--on-dark); color: var(--on-dark); }

/* An image that hasn't been uploaded yet — a quiet placeholder, never a broken icon. */
.media { position: relative; overflow: hidden; background: var(--bg-alt); }
.media img { width: 100%; height: 100%; object-fit: cover; }
.media--empty {
  background: repeating-linear-gradient(45deg, #ece5d7 0 10px, #e4dccb 10px 20px);
}
.media--contain img { object-fit: contain; }

/* ---- site chrome ---- */

.site-header {
  position: sticky; top: 0; z-index: 50;
  background: color-mix(in srgb, var(--bg-alt) 96%, transparent); backdrop-filter: blur(8px);
  /* The same rule that divides every section: one bar design site-wide. */
  border-bottom: var(--hair) solid var(--dark);
}
.site-header .container {
  height: 68px; display: flex; align-items: center; justify-content: space-between; gap: 24px;
}
.brand { font-family: var(--font-heading); font-size: 21px; letter-spacing: .04em; color: var(--text); white-space: nowrap; }
/* Header lockup: a square-ish mark beside the business name. Sizing the mark
   alone keeps it legible — a wide logo with a tagline baked in has to shrink
   to the bar height and its small type becomes unreadable. */
.brand { display: inline-flex; align-items: center; gap: 12px; }
.brand-mark { display: block; height: 38px; width: auto; max-width: 56px; object-fit: contain; }
.brand-name { font-family: var(--font-heading); font-size: 21px; letter-spacing: .04em;
              color: var(--text); white-space: nowrap; }
.site-nav { display: flex; align-items: center; gap: 28px; font-size: 15px; font-weight: 500;
            font-family: var(--font-nav); }
.site-nav a { color: var(--text); }
.site-nav a.is-active { color: var(--accent); font-weight: 700; }
.nav-toggle { display: none; background: none; border: none; cursor: pointer; flex-direction: column; gap: 5px; padding: 8px; }
.nav-toggle span { display: block; width: 24px; height: 2px; background: var(--text); }

/* The footer's small type matches the menu, so header and footer read as a
   pair; --font-nav is the body font unless the theme names a menu font. */
.site-footer { background: var(--dark); color: var(--on-dark-muted); padding: 56px 0 32px;
               font-family: var(--font-nav); }
.site-footer a { color: var(--on-dark-muted); }
.site-footer a:hover { color: var(--on-dark); }
.footer-grid { display: grid; grid-template-columns: 1.6fr 1fr; gap: 40px; }
/* The mark sits beside the name, as it does in the header. */
/* The name carries the footer; the blurb under it stays at its old size so it
   reads as a caption rather than competing with the name, and the contact
   details sit between the two. */
.footer-brand { font-family: var(--font-heading); font-size: 30px; color: var(--on-dark);
                margin-bottom: 16px; display: flex; align-items: center; gap: 16px; }
.footer-mark { height: 51px; width: auto; max-width: 78px; object-fit: contain; }
.footer-blurb { font-size: 14.5px; line-height: 1.6; max-width: 46ch; }
.footer-head { font-size: 15px; letter-spacing: .1em; text-transform: uppercase; color: var(--gold); font-weight: 700; margin-bottom: 14px; }
.footer-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 9px; font-size: 16.5px; }
/* Icon and text on a fixed two-column rail, so a wrapping address lines up
   under itself rather than running back beneath its own icon. */
.footer-contact li { display: grid; grid-template-columns: 20px 1fr; gap: 11px; align-items: start; }
.footer-icon { width: 20px; height: 20px; margin-top: 1px; fill: none; stroke: var(--gold);
               stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }

.footer-sep { margin: 0 10px; opacity: .45; }
.footer-bottom { border-top: 1px solid color-mix(in srgb, var(--on-dark) 14%, var(--dark)); margin-top: 40px; padding-top: 20px; font-size: 14.5px; display: flex; justify-content: space-between; flex-wrap: wrap; gap: 12px; }

/* ---- hero ---- */

.hero { position: relative; min-height: 560px; display: flex; align-items: center; background: var(--dark); }
.hero .media { position: absolute; inset: 0; }
/* Even overlay rather than a left-weighted gradient: hero copy is centred, and
   a one-sided scrim reads lopsided behind it. */
.hero-scrim { position: absolute; inset: 0; pointer-events: none; background: rgba(38, 32, 25, .58); }
.hero-inner { position: relative; width: 100%; padding-top: 96px; padding-bottom: 96px; }
.hero-copy { max-width: 620px; }
/* Split motto: text, mark, text on one line. The halves share the leftover
   space equally and lean towards the mark, so it reads as one phrase with the
   logo set into the middle rather than three separate things. */
.hero-copy--wide { max-width: 1120px; }
.hero-split { display: flex; align-items: center; justify-content: center;
              gap: clamp(14px, 3vw, 44px); flex-wrap: nowrap; }
.hero-split-text { flex: 1 1 0; min-width: 0; }
.hero-split-text:first-child { text-align: right; }
.hero-split-text:last-child  { text-align: left; }
.hero-split-logo { flex: 0 0 auto; height: clamp(120px, 19vw, 260px); width: auto;
                   object-fit: contain; }
.hero h1 { font-size: clamp(36px, 5vw, 56px); line-height: 1.1; margin: 0 0 20px; color: var(--on-dark); }
.hero p { font-size: 18px; line-height: 1.6; color: var(--on-dark-muted); margin: 0 0 32px; max-width: 52ch; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 14px; }
/* Years-of-experience badge: a house silhouette over a ribbon, pinned to the
   bottom-left of the cover. The shape is a clip-path rather than an image so
   it stays sharp at any size and the numbers remain selectable text. */
.hero-badge { position: absolute; left: clamp(14px, 4vw, 56px); bottom: 5px; z-index: 2;
              width: clamp(75px, 7.6vw, 105px); text-align: center; }
/* Steeper roof than a plain pentagon, matching the reference badge. */
.hero-badge-shape { background: var(--on-dark); color: var(--text);
                    clip-path: polygon(50% 0, 100% 32%, 100% 100%, 0 100%, 0 32%);
                    padding: clamp(19px, 2.3vw, 27px) 7px clamp(8px, .85vw, 11px); }
/* Heavy sans, not the page serif: the number is a figure to read at a glance,
   not a piece of prose. Karla is already loaded for body text. */
.hero-badge-num { display: block; font-family: var(--font-body);
                  font-weight: 700; font-size: clamp(27px, 3.2vw, 41px); line-height: .95;
                  letter-spacing: -.02em; }
.hero-badge-label { display: block; margin-top: 2px;
                    font-family: var(--font-body); font-weight: 800;
                    font-size: clamp(11px, 1.05vw, 13px);
                    letter-spacing: .18em; text-transform: uppercase; }
/* Wider than the shape and overhanging it, so it reads as a ribbon wrapped
   around the badge rather than a third stacked block. */
.hero-badge-ribbon { background: var(--gold); color: var(--on-dark);
                     margin: 0 clamp(-9px, -.72vw, -6px);
                     padding: clamp(4px, .6vw, 7px) 4px;
                     font-family: var(--font-body);
                     font-size: clamp(9.5px, .92vw, 11.5px); font-weight: 800;
                     letter-spacing: .16em; text-transform: uppercase; }
/* Optional hero logo. Sized by height so a wide mark and a square one both land
   at a sensible size, capped by width so a very wide logo cannot overflow on a
   phone. object-fit keeps the aspect ratio of whatever was uploaded. */
.hero-logo { display: block; margin: 0 auto 28px; height: clamp(72px, 12vw, 160px);
             max-width: min(420px, 80%); width: auto; object-fit: contain; }

/* ---- card grids ---- */

.grid { display: grid; gap: 24px; }
/* Column count comes from the renderer so rows stay even. minmax(0,1fr) rather
   than 1fr, or a long word in a card would push the track wider than its share. */
/* Cards in a row stretch to the tallest, so a row stays even when one title
   runs to three lines. */
.grid--services { grid-template-columns: repeat(var(--cols, 3), minmax(0, 1fr)); align-items: stretch; }
/* Landscape cards, 60% photo and 40% copy at their usual size (a 5:4 card):
   the photo has a fixed shape and the copy a minimum one. A fixed shape for
   the whole card used to clip a long title, or push it up over the photo,
   since the copy is centred; now the copy grows instead, and its card with it. */
/* On the page background, like the band they sit in, rather than a lighter
   card colour: the border and the photo set each card off. */
.grid--services .card { display: flex; flex-direction: column; overflow: hidden; background: var(--bg-alt); }
.grid--services .card .media { height: auto; aspect-ratio: 25 / 12; flex: none; }
.grid--services .card-body { flex: 1 0 auto; aspect-ratio: 25 / 8; justify-content: center;
                             padding: 12px 18px 16px; gap: 6px; }
.grid--services .card-title { font-size: 22px; line-height: 1.2; }
/* Balanced rows like the service grid. The gaps are half what they were --
   the photos keep a tenth of the row's width between them rather than a fifth
   -- and the photos take back everything the gaps gave up, so the row still
   begins and ends flush with the section above it. */
.grid--materials { grid-template-columns: repeat(var(--cols, 4), minmax(0, 1fr));
                   column-gap: calc(10% / (var(--cols, 4) - 1) + 8px); row-gap: 28px; }
@media (max-width: 820px) {
  /* Two across, and the photos take their whole track again. Narrowing them
     to leave air is a luxury of a wide screen; on a tablet or a phone the
     photo is the thing worth the space. */
  .grid--materials { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 28px; }
}
@media (max-width: 520px) {
  /* One per row: there is no neighbour left to make room for. */
  .grid--materials { grid-template-columns: minmax(0, 1fr); column-gap: 0; }
}
.grid--reviews { grid-template-columns: repeat(auto-fit, minmax(290px, 1fr)); align-items: start; }
.grid--projects { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.grid--founders { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.grid--posts { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }

.card {
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius);
  overflow: hidden; color: var(--text); display: flex; flex-direction: column;
}
.card:hover { border-color: var(--border-2); color: var(--text); }
.card .media { height: 170px; }
/* Centred: the cards are short and sit in a symmetric grid, so centring the
   title and blurb reads as deliberate rather than ragged. align-items does the
   work because .card-body is a flex column — text-align alone would not move
   the block itself. */
.card-body { padding: 20px 20px 24px; display: flex; flex-direction: column; gap: 8px;
             align-items: center; text-align: center; }
.card-title { font-family: var(--font-heading); font-size: 21px; }
.card-text { font-size: 14.5px; line-height: 1.55; color: var(--muted); }
.grid--services .card-text { font-size: 15.5px; }

.tile { display: flex; flex-direction: column; gap: 18px; color: var(--text); }
/* Landscape rather than square: the photos are as wide as the row allows,
   and a 4:3 crop keeps the band from growing tall as they widen. */
.tile .media { aspect-ratio: 4 / 3; border-radius: 8px; }
/* Same face and size as a service card's title, so the two bands read as one
   system rather than two designs that happen to share a page. */
.tile-label { font-family: var(--font-heading); font-size: 22px; line-height: 1.2;
              text-align: center; }

/* ---- reviews ---- */

/* The band is its own field of colour rather than the flat dark it used to
   be: blue held in the top-left corner, green in the bottom-right, and a
   white core showing through the gap they leave in the middle.
   Three things matter about how this is built. Every bloom steps down through
   five stops instead of one, so it dissolves rather than ending on a visible
   ring. The colours are painted ON TOP of the white core, not under it --
   under it they come out grey, because a white veil desaturates whatever it
   covers. And each is pulled back past its own corner, which is what leaves
   the centre clear for the cards to sit on. */
.section--reviews {
  /* A shallow band. The colour field is doing the work of separating this
     section from the ones around it, so the usual 72px of air above and below
     only pushed the gradient's edges away from the content it frames. The
     heading sits 20px from the top edge and the Google button 15px from the
     bottom -- the heading is large type and wants the extra air above it. */
  padding: 20px 0 15px;
  background:
    radial-gradient(64% 120% at -4% 8%,
      rgba(48,126,212,.96) 0%, rgba(48,126,212,.67) 28%, rgba(48,126,212,.40) 50%,
      rgba(48,126,212,.21) 68%, rgba(48,126,212,.08) 84%, rgba(48,126,212,0) 100%),
    radial-gradient(64% 120% at 104% 92%,
      rgba(54,170,118,.96) 0%, rgba(54,170,118,.67) 28%, rgba(54,170,118,.40) 50%,
      rgba(54,170,118,.21) 68%, rgba(54,170,118,.08) 84%, rgba(54,170,118,0) 100%),
    radial-gradient(95% 165% at 50% 50%,
      rgba(255,254,251,1) 0%, rgba(255,254,251,.70) 28%, rgba(255,254,251,.42) 50%,
      rgba(255,254,251,.22) 68%, rgba(255,254,251,.08) 84%, rgba(255,254,251,0) 100%),
    #0f0d0b;
}
/* The band is light where the type sits, so the heading comes back to the
   page's own colour instead of the cream it wore on the dark version. */
.section--reviews .section-title { color: var(--text); }
.section--reviews .reviews-empty { color: var(--muted-strong); }

/* The aggregate line under the heading: score, stars, count, read as one row.
   Score and count sit in the page's own colour; only the stars are gold. */
.reviews-summary {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  margin: 4px 0 28px; font-size: 18px;
}
.reviews-summary-score { font-weight: 700; color: var(--text); }
.reviews-summary-stars { color: var(--gold-bright); font-size: 19px; letter-spacing: 2px; }
.reviews-summary-count { color: var(--muted-strong); }


/* The slider. The track is an ordinary scroll container with snap points, so
   touch, trackpad, keyboard and the arrow buttons all drive the same thing and
   it still works if the script never loads. The buttons sit in their own grid
   columns rather than over the cards, so nothing is ever covered. */
.reviews-slider { display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
                  align-items: center; gap: 14px; }
.reviews-track {
  display: flex; gap: 24px; overflow-x: auto; scroll-snap-type: x mandatory;
  scroll-behavior: smooth; scrollbar-width: none; padding-bottom: 2px;
}
.reviews-track::-webkit-scrollbar { display: none; }
/* Three across on a wide screen, and the basis is what the arrows step by. */
.reviews-track > .review { flex: 0 0 calc((100% - 48px) / 3); scroll-snap-align: start; }
@media (max-width: 980px) { .reviews-track > .review { flex-basis: calc((100% - 24px) / 2); } }

.slider-nav {
  width: 42px; height: 42px; border-radius: 50%; flex: none;
  background: transparent; color: var(--on-dark);
  border: 1px solid #5a4c39; cursor: pointer;
  font-size: 22px; line-height: 1; display: flex; align-items: center;
  justify-content: center; padding: 0 0 2px;
  transition: border-color .15s ease, color .15s ease, opacity .15s ease;
}
.slider-nav:hover { border-color: var(--gold); color: var(--gold); }
/* Dimmed rather than removed at the ends: a control that disappears makes the
   row jump sideways and the cards resize. */
.slider-nav[disabled] { opacity: .3; cursor: default; }
.slider-nav[disabled]:hover { border-color: #5a4c39; color: var(--on-dark); }

/* Landscape cards of one fixed shape. Reviews run from one line to twenty,
   and letting each card size itself gives a ragged wall; a fixed 4:3 makes the
   band read as a set. Grid rather than flex so the meta line is pinned to the
   last row whatever the text does above it. */
/* The card's own colours, set here rather than inherited, so a card looks the
   same wherever it is rendered. On the reviews band they are overridden just
   below: against a light field the warm off-white stops reading as a card, so
   there they go pure white and lean on a deeper shadow instead. */
.review {
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 20px; overflow: hidden;
  /* So the text below can ask how wide its own card is. */
  container-type: inline-size;
  display: grid; grid-template-rows: auto 1fr auto; gap: 10px;
  aspect-ratio: 4 / 3;
}
/* Pinned to the corner rather than placed in the grid, so it cannot push the
   stars along or take a row from the text. */
.review { position: relative; }
.review-source { position: absolute; top: 14px; right: 14px; width: 18px; height: 18px;
                 line-height: 0; opacity: .9; pointer-events: none; }
.review-source svg { width: 100%; height: 100%; display: block; }
/* Room for the mark, so a five-star row never runs under it. */
.review-stars { color: var(--gold-bright); font-size: 17.5px; letter-spacing: 2px;
                padding-right: 26px; }
/* Cut to whole lines with an ellipsis, not clipped mid-letter by the card's
   overflow. Seven lines is what fits the 4:3 box from a phone width upwards. */
/* How many lines fit is decided by the card, not by a guess. A 4:3 box gets
   shorter as it gets narrower, so one fixed number either overflows at small
   sizes or wastes space at large ones -- and the arrows alone took enough
   width off the cards to break a count that had fitted a moment earlier.
   Container queries let each card answer for itself.

   The arithmetic, for anyone changing these: a container query measures the
   CONTENT box, so the width it reports is already 40px less than the card.
   Height is 0.75 * (that + 40), less the same 40px of padding, less about 59px
   for the stars, the meta line and two gaps -- so roughly 0.75w - 69 is left
   for text, divided by the 25.6px line height. Change the font size and these
   thresholds have to move with it. */
.review-text {
  font-size: 16px; line-height: 1.6; color: var(--text); min-height: 0;
  /* A review that is only answers to Google's questionnaire arrives as one
     answer per line, and a review someone typed in paragraphs keeps them.
     pre-line rather than pre-wrap: this text was scraped from a web page, so
     runs of spaces in it are an artefact and still want collapsing. The clamp
     below counts the lines this produces, so a review with breaks in it is cut
     off sooner -- which is right: the card is a taste of the review and the
     link on the name goes to the whole of it. */
  white-space: pre-line;
  display: -webkit-box; -webkit-box-orient: vertical;
  overflow: hidden;
  /* The clamp puts an ellipsis on the last line but does not stop later lines
     being laid out when the box is taller -- which it is, the row being 1fr.
     The matching max-height is what actually hides them. */
  -webkit-line-clamp: 3; max-height: calc(1.6em * 3);
}
@container (min-width: 235px) { .review-text { -webkit-line-clamp: 4; max-height: calc(1.6em * 4); } }
@container (min-width: 269px) { .review-text { -webkit-line-clamp: 5; max-height: calc(1.6em * 5); } }
@container (min-width: 303px) { .review-text { -webkit-line-clamp: 6; max-height: calc(1.6em * 6); } }
@container (min-width: 337px) { .review-text { -webkit-line-clamp: 7; max-height: calc(1.6em * 7); } }
/* A fixed line-height: a name in another script falls back to a font with
   taller metrics, which otherwise sat that card's name and date a few pixels
   off its neighbours'. */
/* Always the last row: a review with no text would otherwise take the text's
   row and sit a gap higher than its neighbours. */
.review-meta { grid-row: 3; display: flex; justify-content: space-between; align-items: end; gap: 8px; font-size: 14.5px; line-height: 1.4; }
.review-name { font-weight: 700; color: var(--text); }
/* Grey rather than the old near-black-background tint, which all but vanished
   once the card went white. */
.review-date { color: var(--muted); }
/* Where we have the source link, the author's name carries it -- Google's
   terms want the review reachable at its origin. The global link colour is a
   mid brown that all but vanishes on this dark band, so inherit instead. */
.review-name a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.review-name a:hover { color: var(--accent); }
.reviews-empty { font-size: 15px; text-align: center; }

/* Against the pale centre of the band, a warm off-white card on warm off-white
   has nothing to separate it, so the card goes white and the shadow does the
   lifting. The arrows need the same treatment: they were drawn as pale outlines
   for a dark background. */
.section--reviews .review { background: #fff; box-shadow: 0 16px 40px rgba(20,30,40,.18); }
.section--reviews .slider-nav { background: rgba(255,255,255,.92); color: var(--text);
                                border-color: rgba(43,37,33,.14); }
.section--reviews .slider-nav:hover { border-color: var(--accent); color: var(--accent); }
/* White cards and white arrows (the glow and the dark looks) keep their own
   dark ink: a theme with light text -- a dark site -- would otherwise put
   pale grey words on white. */
.section--reviews:not(.section--reviews-plain) .review,
.section--reviews:not(.section--reviews-plain) .slider-nav { --text: #1f2328; --muted: #6b7280; }
.section--reviews:not(.section--reviews-plain) .slider-nav { color: var(--text); font-weight: 700; }
.section--reviews:not(.section--reviews-plain) .slider-nav[disabled] { opacity: .45; }
.section--reviews:not(.section--reviews-plain) .slider-nav[disabled]:hover { color: var(--text); border-color: rgba(43,37,33,.14); }

/* The two looks a contractor can pick instead of the glow, both drawn from
   the theme so the band matches the rest of the site. Page color drops the
   field altogether; Dark fills it with the theme's dark colour, the same as
   the footer, the way a monochrome site sets one band apart. */
/* Page color: everything from the theme. The band is the page background,
   the cards a shade of it with a hairline, the words the theme's text and
   muted colours, the arrows outlined in the text colour -- so it reads on a
   light theme and a dark one alike. */
.section--reviews-plain { background: transparent; }
.section--reviews-plain .review { background: color-mix(in srgb, var(--text) 6%, var(--bg-alt));
  border-color: color-mix(in srgb, var(--text) 18%, var(--bg-alt)); color: var(--text);
  box-shadow: 0 6px 20px rgba(0,0,0,.10); }
.section--reviews-plain .section-lead { color: var(--muted); }
.section--reviews-plain .slider-nav { background: transparent; color: var(--text); font-weight: 700;
  border: 1.5px solid color-mix(in srgb, var(--text) 45%, transparent); }
.section--reviews-plain .slider-nav:hover { border-color: var(--accent); color: var(--accent); }
.section--reviews-plain .slider-nav[disabled] { opacity: .35; }
.section--reviews-plain .slider-nav[disabled]:hover { border-color: color-mix(in srgb, var(--text) 45%, transparent);
  color: var(--text); }

.section--reviews-dark { background: var(--dark); }
.section--reviews-dark .section-title,
.section--reviews-dark .section-lead,
.section--reviews-dark .eyebrow { color: var(--on-dark); }
.section--reviews-dark .reviews-empty { color: var(--on-dark-muted); }
.section--reviews-dark .reviews-summary-score { color: var(--on-dark); }
.section--reviews-dark .reviews-summary-count { color: var(--on-dark-muted); }
.section--reviews-dark .review { box-shadow: none; border-color: transparent; }
/* The site's button is often the same dark as this band, so here it turns
   over: light fill, dark label. */
.section--reviews-dark .btn { background: var(--on-dark); color: var(--dark); }
.section--reviews-dark .btn:hover { background: transparent; color: var(--on-dark);
                                    box-shadow: inset 0 0 0 1.5px var(--on-dark); }

/* ---- pricing (the platform's own site only; SectionRenderer.pricing) ----
   Cards side by side, as many across as there are plans up to four, each a
   shade of the page background with the theme's text, muted and accent
   colours, so it follows the Theme panel like every other band. */
.pricing { display: flex; flex-direction: column; gap: 24px; max-width: 1000px; margin: 8px auto 0; }
/* Two parts side by side: the plan on the left, a little lighter than its
   features on the right. Both are mixed from the theme's page background,
   so they follow the Theme panel on a light site and a dark one. */
.plan { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); overflow: hidden;
  color: var(--text); text-align: left; border-radius: 22px;
  border: 1px solid color-mix(in srgb, var(--text) 16%, var(--bg-alt));
  background: color-mix(in srgb, var(--text) 4%, var(--bg-alt)); }
.plan-main { padding: 40px 36px 36px; background: color-mix(in srgb, #fff 9%, var(--bg-alt));
  border-right: 1px solid color-mix(in srgb, var(--text) 12%, var(--bg-alt));
  /* Its words and button centred, across and down. */
  display: flex; flex-direction: column; justify-content: center; text-align: center; }
.plan-side { padding: 40px 40px 36px; }
/* A plan with no features listed is the left part alone, full width. */
.plan:not(:has(.plan-side)) { grid-template-columns: minmax(0, 1fr); }
.plan:not(:has(.plan-side)) .plan-main { border-right: 0; }
.plan-name { font-family: var(--font-heading); font-size: 34px; line-height: 1.15; color: var(--text); margin: 0; }
.plan-price { margin-top: 26px; font-size: 34px; font-weight: 700; line-height: 1.1; color: var(--text); }
.plan-period { font-weight: 700; }
.plan-note { margin: 10px 0 0; font-size: 13.5px; line-height: 1.5; color: var(--muted); white-space: pre-wrap; }
.plan-btn { display: block; width: 100%; text-align: center; margin-top: 26px; border-radius: 12px; }
.plan-group { font-family: var(--font-body); font-size: 15px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--text); margin: 24px 0 10px; }
.plan-group:first-child { margin-top: 0; }
.plan-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.plan-list li { display: grid; grid-template-columns: 22px 1fr; gap: 14px; align-items: center;
  font-size: 16px; line-height: 1.5; }
.plan-list svg { width: 22px; height: 22px; fill: none; stroke: var(--accent); stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round; }
/* On a phone the two parts stack: the plan on top, its features below. */
@media (max-width: 760px) {
  .plan { grid-template-columns: minmax(0, 1fr); }
  .plan-main { padding: 28px 22px 24px; border-right: 0;
    border-bottom: 1px solid color-mix(in srgb, var(--text) 12%, var(--bg-alt)); }
  .plan-side { padding: 24px 22px 26px; }
}

/* ---- partners ---- */

/* A strip that advances one logo at a time and rests between moves. The
   motion is driven by a script rather than a CSS animation, because a
   keyframe set that holds between steps would need as many stops as there are
   logos -- a different set per contractor. */
.partners { overflow: hidden;
            -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
            mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent); }
.partners-track { display: flex; width: max-content; gap: 48px; align-items: center;
                  will-change: transform; }
/* Movement someone did not ask for is the kind that causes trouble, so for
   anyone who has asked their system for less of it the strip simply sits
   still, wrapped as it was before. */
@media (prefers-reduced-motion: reduce) {
  .partners-track { transform: none !important; width: auto; flex-wrap: wrap;
                    justify-content: center; gap: 20px; }
}
.partner { width: 280px; height: 120px; flex: none; }
/* A linked logo still has to be a 280x120 box, which an inline anchor is not. */
a.partner--link { display: block; border-radius: 6px; }
a.partner--link:focus-visible { outline: 2px solid var(--gold); outline-offset: 4px; }
a.partner--link:focus-visible .media img { filter: none; opacity: 1; }
/* Grey at rest so a row of competing brand colours does not shout over the
   page, full colour under the cursor -- the strip keeps stepping either way,
   so this is a highlight rather than a way to hold a logo still. */
.partner .media img { filter: grayscale(1); opacity: .72;
                      transition: filter .25s ease, opacity .25s ease; }
.partner:hover .media img { filter: none; opacity: 1; }
.partner .media { width: 100%; height: 100%; border-radius: 6px; background: transparent; }

/* The three switches. Each does one thing, so they combine: a row of social
   icons is usually all three at once, a supplier strip none of them. */

/* Still: a centred row that wraps where it runs out of width. */
.partners--still { overflow: visible; -webkit-mask-image: none; mask-image: none; }
.partners--still .partners-track { width: auto; flex-wrap: wrap; justify-content: center;
                                   transform: none !important; gap: 40px; }

/* Colour: as the brand draws it, for marks a visitor is looking for rather
   than logos that would otherwise shout over the page. */
.partners--color .partner .media img { filter: none; opacity: 1; }

/* Below about 660px the strip is too narrow for two logos and their gap, so
   it used to show one logo pinned left with a slice of the next beside it.
   Here each logo takes the whole strip instead: one at a time, centred, and
   the step the script measures becomes exactly one screen. The edge fade goes
   too -- with a logo spanning the full width it would dim its own ends. */
@media (max-width: 660px) {
  .partners { -webkit-mask-image: none; mask-image: none; }
  .partners-track { gap: 0; }
  .partner { width: calc(100vw - 48px); height: 110px; }
  /* A still row is already wrapping, so it has no reason to give a whole
     screen to each mark. Even keeps its own sizing, smaller to fit more of
     them across a phone -- it comes after so it wins on equal weight. */
  .partners--still .partners-track { gap: 24px; }
  .partners--still .partner { width: 200px; height: 96px; }
}

/* ---- follow us ---- */

/* FOLLOW US, in capitals: the band reads as a sign-off under the page
   rather than one more section of it. Set in CSS, so the words stay
   "Follow us" for a screen reader, which would otherwise spell capitals out. */
.section--follow .section-title { text-transform: uppercase; letter-spacing: .04em; }

/* Small, even, and in colour: the opposite of the partners strip on all three
   counts, because these are the marks a visitor is looking for. */
.social { display: flex; flex-wrap: wrap; justify-content: center; align-items: flex-start;
          gap: 28px 44px; }
/* Each mark is its platform's own artwork (img/platforms), so every icon
   comes out the same size and in its brand colour, with the site's name
   under it for anyone who doesn't know the mark. */
.social-item { display: flex; flex-direction: column; align-items: center; gap: 10px;
               flex: none; min-width: 64px; padding: 6px; border-radius: 8px;
               color: var(--muted-strong); text-decoration: none; }
.social-item img { display: block; width: 40px; height: 40px; transition: transform .2s ease; }
.social-name { font-size: 14px; font-weight: 600; line-height: 1.2; }
.social-item:hover img { transform: scale(1.08); }
.social-item:hover .social-name { color: var(--accent); }
.social-item:focus-visible { outline: 2px solid var(--gold); outline-offset: 4px; }
@media (prefers-reduced-motion: reduce) { .social-item img { transition: none; } }

@media (max-width: 520px) {
  /* One width for every item, so eight profiles wrap as two even rows of
     four instead of leaving one stranded on a row of its own. */
  .social { gap: 20px 8px; }
  .social-item { width: 76px; min-width: 0; padding: 6px 0; gap: 8px; }
  .social-item img { width: 34px; height: 34px; }
  .social-name { font-size: 13px; }
}

/* ---- awards and licences ---- */

/* A centred row of badges rather than a grid: there are usually only a few,
   and a grid would strand the last one alone on the left of a second row. */
/* A shallower band than the rest: badges are small, self-contained marks and
   do not need a section's worth of air around them. The cut is split between
   the outer padding and the gap under the heading -- taking it all from the
   padding would leave the heading almost touching the divider above it. */
.section--awards { padding: 24px 0; }
.section--awards .section-head { margin-bottom: 23px; }
.awards { display: flex; flex-wrap: wrap; justify-content: center; align-items: center;
          gap: 40px 64px; }
.award { width: 187px; height: 187px; flex: none; }
.award .media { width: 100%; height: 100%; background: transparent; }

/* ---- cta ---- */

.cta-band {
  background: var(--bg-alt);
  padding: 48px 0;
}
.cta-band .container { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 20px; }
/* When the heading cannot share a line with the button, the band centres and
   the button goes under it. The class is put on by the page's own script,
   which measures it; without that script the band stays a row. */
.cta-band .container.is-stacked { flex-direction: column; align-items: center; text-align: center; }
.cta-title { font-family: var(--font-heading); font-size: 26px; margin-bottom: 6px; }
/* As an item inside a service page this band has no band before it to
   draw the site-wide divider, so it carries its own -- same 1px rule,
   same width, in place of the band's top padding. */
.cta-band--service { padding-top: 0; }
/* Dragged to the top of a service page it follows the cover, which ends in a
   large dark field of its own -- a rule there would be the second line in a
   row, exactly what the site avoids everywhere else. */
.hero--service + .cta-band--service { padding-top: 48px; }
.hero--service + .cta-band--service::before { display: none; }
.cta-band--service::before { content: ""; display: block; height: var(--hair);
  background: var(--dark); width: min(100% - 48px, 1132px); margin: 0 auto 48px; }

/* The question over its button, both centred in the article: the button is
   what the box is for, so it sits in the middle of the page, not off at an edge. */
.cta-box {
  background: var(--bg); border: 1px solid var(--border-2); border-radius: var(--radius);
  padding: 28px; margin: 32px 0; display: flex; flex-direction: column; gap: 16px;
  align-items: center; text-align: center;
}
.cta-box .cta-title { margin-bottom: 0; }

/* ---- detail blocks (service / material / project / story) ---- */

.detail { display: grid; grid-template-columns: 1fr 1fr; gap: 48px; align-items: center; padding: 56px 0; }
.detail .media { aspect-ratio: 4 / 3; border-radius: var(--radius); }
/* The story with no photo takes the whole page and centres on it, rather
   than sitting in the half a photo would have left. The measure is capped
   so the lines stay readable across a wide screen. */
.detail--wordsonly { grid-template-columns: 1fr; text-align: center; }
.detail--wordsonly .detail-text { max-width: 68ch; margin-left: auto; margin-right: auto; }
/* ---- pros and cons ---- */

/* Spans both columns, so each side gets half the page rather than half the
   text column. A coloured bar rather than a filled panel: the page is already
   a field of cream blocks, and two more tinted rectangles made the entry look
   busier than it reads. Green and terracotta are opposite each other on the
   wheel but held well down in saturation, so they mark a verdict rather than
   bolting a traffic light onto a cream page.
   The colour is never the only signal -- the word Pros or Cons is set above
   every one of them, for readers who cannot tell the two hues apart. */
.verdict { grid-column: 1 / -1; display: flex; gap: 40px; margin-top: 4px; }
.verdict-item { flex: 1 1 0; padding: 2px 0 2px 16px; }
.verdict-head { font-weight: 700; font-size: 13px; letter-spacing: .08em;
                text-transform: uppercase; }
.verdict-item p { margin: 8px 0 0; font-size: 15px; line-height: 1.6; color: var(--text); }
.verdict-item--pro { border-left: 3px solid #4a7a54; }
.verdict-item--pro .verdict-head { color: #2f5137; }
.verdict-item--con { border-left: 3px solid #aa4a34; }
.verdict-item--con .verdict-head { color: #7a3522; }
.detail h2, .detail h3 { font-size: clamp(24px, 3vw, 32px); margin-bottom: 12px; }
.detail-text { font-size: 16px; line-height: 1.65; color: var(--muted); }
.detail-meta { font-size: 13.5px; color: var(--muted); margin-top: 10px; }
.detail-list { list-style: none; margin: 20px 0 0; padding: 0; display: flex; flex-direction: column; gap: 14px; }
.detail-list li { border-left: 2px solid var(--gold); padding-left: 14px; }
.detail-list b { font-family: var(--font-heading); font-weight: 400; font-size: 17px; display: block; margin-bottom: 3px; }
.detail-list span { font-size: 14.5px; line-height: 1.55; color: var(--muted); }
.traits { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 18px; list-style: none; padding: 0; }
.traits li { border: 1px solid var(--border-2); border-radius: 999px; padding: 6px 14px; font-size: 13.5px; }

.tag { display: inline-block; background: var(--bg-alt); border: 1px solid var(--border); border-radius: 999px; padding: 4px 12px; font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--accent); margin-bottom: 10px; }

/* ---- portfolio: cover grid + lightbox ---- */

/* One cover per project, the title underneath. The whole card is the button,
   so there is no small target to hunt for. */
.pcard { position: relative; }
.pcard-btn { display: block; width: 100%; padding: 0; border: 0; background: none;
             cursor: pointer; text-align: center; color: inherit; font: inherit; }
/* No background of its own, so a project with no cover yet still shows the
   standard placeholder hatching rather than a blank rectangle. */
.pcard-cover { display: block; aspect-ratio: 4 / 3; border-radius: var(--radius);
               overflow: hidden; }
.pcard-cover img { width: 100%; height: 100%; object-fit: cover; display: block;
                   transition: transform .35s ease; }
.pcard-btn:hover .pcard-cover img { transform: scale(1.04); }
.pcard-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: var(--radius); }
.pcard-label { display: block; margin-top: 12px; font-family: var(--font-heading);
               font-size: 21px; color: var(--text); }

/* The lightbox. Built by the script on first open and reused after that, so a
   page with no portfolio on it never carries one. */
.lightbox { position: fixed; inset: 0; z-index: 90; display: flex; flex-direction: column;
            justify-content: center; background: rgba(16, 14, 12, .94); }
/* Photo and caption are centred together as one block, so the words stay with
   the picture instead of drifting to the bottom of the screen. */
.lightbox-stage { flex: 0 1 auto; display: flex; align-items: center; justify-content: center;
                  padding: 56px 72px 0; min-height: 0; }
.lightbox-stage img { max-width: 100%; max-height: 76vh; object-fit: contain;
                      border-radius: 6px; }
.lightbox-foot { flex: 0 0 auto; padding: 18px 24px 8px; text-align: center; color: var(--on-dark); }
.lightbox-title { font-family: var(--font-heading); font-size: 22px; }
.lightbox-text { font-size: 14.5px; line-height: 1.6; color: var(--on-dark-muted);
                 max-width: 68ch; margin: 6px auto 0; }
.lightbox-count { font-size: 13px; letter-spacing: .08em; color: var(--on-dark-muted); margin-top: 10px; }
.lightbox-btn { position: absolute; display: flex; align-items: center; justify-content: center;
                width: 52px; height: 52px; border-radius: 999px; border: 0; cursor: pointer;
                background: rgba(250, 248, 243, .92); color: #1b1714; }
.lightbox-btn:hover { background: #fff; }
.lightbox-btn svg { width: 22px; height: 22px; fill: none; stroke: currentColor;
                    stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.lightbox-prev { left: 18px; top: 50%; transform: translateY(-50%); }
.lightbox-next { right: 18px; top: 50%; transform: translateY(-50%); }
.lightbox-close { right: 18px; top: 18px; width: 44px; height: 44px; }
.lightbox-btn[disabled] { opacity: .35; cursor: default; }
/* Nothing behind the overlay should scroll while it is open. */
body.is-lightbox { overflow: hidden; }

@media (max-width: 720px) {
  .lightbox-stage { padding: 56px 14px 8px; }
  .lightbox-prev { left: 8px; }
  .lightbox-next { right: 8px; }
  .lightbox-btn { width: 44px; height: 44px; }
  .lightbox-text { display: none; }
}

/* ---- founders ---- */

.founder { text-align: center; }
.founder .media { width: 132px; height: 132px; border-radius: 50%; margin: 0 auto 14px; }
.founder-name { font-family: var(--font-heading); font-size: 20px; }
.founder-role { font-size: 13px; letter-spacing: .08em; text-transform: uppercase; color: var(--accent); font-weight: 700; margin: 4px 0 10px; }
.founder-bio { font-size: 14.5px; line-height: 1.6; color: var(--muted); }

/* ---- an embedded app ---- */

/* Somebody else's tool, sitting in the page like a photograph would: the same
   corner radius and hairline as a card, so a visualiser from another company
   does not read as a hole cut in the site.
   Height is set in pixels (SectionRenderer.embedHeight) rather than as an
   aspect ratio: these tools lay themselves out to whatever box they are
   given, and the frame stands as tall as the tool, so it shows whole and
   the page scrolls rather than a box inside it. */
.embed { position: relative; width: 100%; overflow: hidden;
         border: var(--hair) solid var(--border); border-radius: var(--radius);
         background: var(--bg-alt); }
.embed iframe { display: block; width: 100%; height: 100%; border: 0; }

/* ---- questions and answers ---- */

/* Narrower than the band it sits in. A question and its answer are reading
   matter, and a line the full width of the page is hard to track back from. */
.faq { max-width: 820px; margin: 0 auto; }
.faq-item { border-bottom: var(--hair) solid var(--border-2); }
.faq-item:first-child { border-top: var(--hair) solid var(--border-2); }

/* The row. Both rules that hide the marker are needed: Safari draws the
   disclosure triangle through its own pseudo-element even where list-style
   has already taken it away. */
.faq-q {
  display: flex; align-items: center; justify-content: space-between; gap: 24px;
  padding: 18px 2px; cursor: pointer; list-style: none;
  font-family: var(--font-body); font-weight: 700; font-size: 18px;
  line-height: 1.4; color: var(--text);
}
.faq-q::-webkit-details-marker { display: none; }
.faq-q:hover { color: var(--accent); }
.faq-q:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* A plus built from two bars rather than a character: nothing to come out in
   the wrong font, and opening it is a quarter turn into a cross rather than a
   swap of one glyph for another. currentColor, so it follows the question
   into the accent colour on hover. */
.faq-sign { position: relative; flex: none; width: 15px; height: 15px;
            transition: transform .18s ease; }
.faq-sign::before, .faq-sign::after {
  content: ''; position: absolute; background: currentColor; border-radius: 1px;
}
.faq-sign::before { inset: calc(50% - 1px) 0; }
.faq-sign::after  { inset: 0 calc(50% - 1px); }
.faq-item[open] .faq-sign { transform: rotate(45deg); }
@media (prefers-reduced-motion: reduce) { .faq-sign { transition: none; } }

/* Pulled up a touch: the question's own bottom padding already separates the
   two, and the full gap read as a stray line of space. */
.faq-a { margin-top: -4px; padding: 0 2px 20px; max-width: 72ch;
         font-size: 16px; line-height: 1.65; color: var(--muted-strong); }

/* ---- blog ---- */

/* Narrower than the band it sits in, and centred in it: a card the full
   width of the page reads as a section rather than as one post among
   several. */
.post-card { width: 76.5%; margin-left: auto; margin-right: auto; }
.post-card .media { height: 230px; }
.post-title { font-family: var(--font-heading); font-size: 22px; line-height: 1.25; }
.post-excerpt { font-size: 14.5px; line-height: 1.6; color: var(--muted); }
.byline { display: flex; align-items: center; gap: 10px; font-size: 13px; color: var(--muted); margin-top: 4px; }
.byline .media { width: 32px; height: 32px; border-radius: 50%; flex: 0 0 32px; }
/* The dots between author, date and read time sit closer than the photo does. */
.byline > [aria-hidden] { margin: 0 -4px; }

.article { max-width: 720px; margin: 0 auto; padding: 56px 0; }
.article h1 { font-size: clamp(30px, 4vw, 42px); line-height: 1.15; margin: 10px 0 24px; }
/* The post's cover, under its title. It is as wide as the card it was
   clicked from rather than as wide as the words -- the same 76.5% of the
   page the blog page gives it -- so it breaks out of the article's measure
   and centres on the page. No set shape: the height follows the picture, so
   nothing is cropped off whatever the contractor uploaded. */
.post-hero {
  /* 15% in from the width of the card it was clicked from, and never
     narrower than the words under it: on a phone even the card's width is
     less than the article's own measure, and a cover set in from the text it
     belongs to reads as a mistake. That measure is written out rather than
     left as 100% so the height cap below works from the same number. */
  --cover: max(calc(0.650 * min(1132px, 100vw - 48px)), min(720px, 100vw - 48px));
  width: var(--cover);
  /* A tall photograph would otherwise push the writing off the screen before
     it began. Anything deeper than half its width is cropped to it; a wider
     one keeps its own height and is not touched. */
  max-height: calc(0.5 * var(--cover));
  overflow: hidden;
  margin-left: 50%;
  transform: translateX(-50%);
  border-radius: var(--radius);
  margin-bottom: 32px;
}
.post-hero img { height: auto; display: block; }
/* Bold, unlike the headings elsewhere: a heading inside an article has no
   band or rule around it to mark where one part ends and the next begins,
   so the weight has to do that work on its own. */
.article h2 { font-size: 26px; font-weight: 700; margin: 36px 0 12px; }
/* A smaller heading, written ### in a paragraph. */
.article h3 { font-size: 21px; font-weight: 700; margin: 28px 0 10px; }
.article p { font-size: 17.5px; line-height: 1.75; color: #3a332c; margin: 0 0 20px; }
/* Lists and links in an article's paragraphs, typed or pasted (ArticleText).
   The list reads at the paragraph's size, its markers in the accent. */
.article ul,
.article ol { font-size: 17.5px; line-height: 1.75; color: #3a332c; margin: 0 0 20px; padding-left: 1.5em; }
.article li { margin: 0 0 6px; padding-left: 0.25em; }
.article li::marker { color: var(--accent); }
.article p strong,
.article li strong { font-weight: 700; color: var(--text); }
.article p a,
.article li a { text-decoration: underline; text-underline-offset: 2px; }
.article figure { margin: 28px 0; }
.article figure .media { aspect-ratio: 16 / 9; border-radius: var(--radius); }
.article figcaption { font-size: 13.5px; color: var(--muted); margin-top: 8px; text-align: center; }

/* What to read next, at the foot of an article. Three covers with their
   titles, narrow enough that they read as a footnote to the piece just
   finished rather than as a second blog page. */
.more-posts { margin-top: 56px; padding-top: 28px; border-top: var(--hair) solid var(--dark); }
.more-title { font-family: var(--font-heading); font-size: 22px; margin-bottom: 18px; }
.more-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
.more-card { display: flex; flex-direction: column; gap: 9px; color: inherit; text-decoration: none; }
.more-cover { aspect-ratio: 3 / 2; border-radius: 8px; }
.more-name { font-family: var(--font-heading); font-size: 17px; line-height: 1.3; }
.more-meta { font-size: 12.5px; color: var(--muted); }
.more-card:hover .more-name { text-decoration: underline; text-underline-offset: 3px; }

/* ---- contact ---- */

/* A little air under the rule above it: the first label sat almost on the
   line, which read as the form belonging to the band before it. */
.contact-form { max-width: 560px; margin: 20px auto 0; }
/* 26px, not 10: the field above carries a 16px bottom margin and the two
   collapse to the larger of them, so 10 here would have moved nothing. */
.contact-form .btn { display: block; width: fit-content; margin: 26px auto 0; }
.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
.field label { font-size: 13px; font-weight: 700; letter-spacing: .04em; }
.field input, .field textarea, .field select {
  font-family: inherit; font-size: 15px; padding: 13px 14px;
  border: 1px solid var(--border-2); border-radius: 8px; background: var(--bg); color: var(--text);
}
.field input:focus, .field textarea:focus, .field select:focus { outline: 2px solid var(--gold); outline-offset: 1px; }
.form-msg { margin-top: 12px; font-weight: 600; text-align: center; }

/* ---- responsive ---- */

@media (max-width: 1000px) {
  .site-nav { display: none; }
  .site-nav.is-open {
    display: flex; flex-direction: column; align-items: stretch; gap: 0;
    position: absolute; left: 0; right: 0; top: 68px;
    background: var(--bg-alt); border-bottom: var(--hair) solid var(--dark); padding: 6px 24px 14px; font-size: 17px;
  }
  /* Every row of the open menu, a page, a dropdown or an entry under one,
     is the same comfortable height to tap. */
  .site-nav.is-open > a { padding: 10px 0; }
  .nav-toggle { display: flex; }
  .site-header { position: relative; }
  .brand { gap: 9px; }
  .brand-mark { height: 30px; max-width: 44px; }
  .brand-name { font-size: 17px; }
}

@media (max-width: 820px) {
  .section { padding: 25px 0; }
  /* The renderer's column count assumes a desktop width; below that, override. */
  .grid--services { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .detail, .footer-grid { grid-template-columns: 1fr; gap: 28px; }
  /* Three thumbnails across a phone are postage stamps. */
  .more-grid { grid-template-columns: 1fr; gap: 24px; }
  /* Side by side needs two columns' worth of width; stacked below that. */
  .verdict { flex-direction: column; }
  .detail { padding: 36px 0; }
  .hero { min-height: 440px; }
  .hero-inner { padding-top: 64px; padding-bottom: 64px; }
  .hero-logo { height: clamp(56px, 18vw, 96px); margin-bottom: 20px; }
  /* Out of the way of the stacked copy on a phone: smaller, and the copy gets
     enough bottom padding that the button can never sit behind it. */
  /* Centred along the bottom edge rather than tucked into the corner: at this
     width the copy above is centred too, so the column reads straight down. */
  .hero-badge { width: 65px; left: 50%; transform: translateX(-50%); }
  .hero-badge-shape { padding: 13px 5px 7px; }
  .hero-badge-num { font-size: 22px; }
  .hero-badge-label { font-size: 9.5px; letter-spacing: .12em; }
  .hero-badge-ribbon { font-size: 8.5px; padding: 4px 3px; margin: 0 -5px; }
  .hero:has(.hero-badge) .hero-inner { padding-bottom: 110px; }
  /* Too narrow for three across: stack them, mark first. */
  .hero-split { flex-direction: column; gap: 14px; }
  .hero-split-text { flex: 0 0 auto; text-align: center; }
  .hero-split-text:first-child, .hero-split-text:last-child { text-align: center; }
  .hero-split-logo { order: -1; height: clamp(96px, 30vw, 170px); }
  /* Only until the page's script has measured the band for itself: below
     this width a heading and a button almost never share a line, so a page
     whose script never ran should stack them rather than crush them. Once
     the script has run it decides, at every width. */
  html:not(.cta-measured) .cta-band .container { flex-direction: column; align-items: center; text-align: center; }
}

/* Hero copy is centred for every site. These sit after the base .hero rules
   above so they win on equal specificity. */
.hero-copy { margin-left: auto; margin-right: auto; text-align: center; }
.hero-copy p { margin-left: auto; margin-right: auto; }
.hero-copy .hero-actions { justify-content: center; }

/* One description serves both the Services page and the home card. Clamp it on
   the card so heights stay uniform however much the customer wrote. */
.grid--services .card-text {
  display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: 2; overflow: hidden;
}

/* ---- services dropdown ---- */

.nav-item.has-menu { position: relative; display: flex; align-items: center; }
.nav-parent {
  background: none; border: none; cursor: pointer; padding: 0;
  font: inherit; color: var(--text); display: inline-flex; align-items: center; gap: 6px;
}
.nav-parent.is-active { color: var(--accent); font-weight: 700; }
.nav-parent .caret {
  border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-top: 5px solid currentColor; transition: transform .15s;
}
.nav-item.has-menu.is-open .nav-parent .caret { transform: rotate(180deg); }

.nav-menu {
  display: none; position: absolute; top: 100%; left: 0; min-width: 230px;
  /* The page's own colours from the theme, as the header bar above it, so
     a dark theme gets a dark menu with its own light words. */
  background: var(--bg-alt); color: var(--text);
  border: 1px solid color-mix(in srgb, var(--text) 16%, var(--bg-alt)); border-radius: 10px;
  padding: 8px; box-shadow: 0 18px 40px rgba(0, 0, 0, .22); z-index: 60;
  /* The header stays pinned to the top while the page scrolls, and the menu
     with it, so a long list (twenty towns served) would run off the bottom of
     the window out of reach. It ends above the bottom and scrolls instead;
     reaching its end doesn't scroll the page behind it. */
  max-height: calc(100vh - 68px - 16px); max-height: calc(100dvh - 68px - 16px);
  overflow-y: auto; overscroll-behavior: contain;
}
/* Hover for pointers, is-open for taps and keyboards.
   NOT :focus-within — a tap leaves focus on the button, so that rule kept the
   menu open and made the second tap look like it did nothing. The button is a
   real <button>, so Enter/Space fire click and toggle is-open anyway. */
.nav-item.has-menu:hover .nav-menu,
.nav-item.has-menu.is-open .nav-menu { display: block; }
.nav-menu a { display: block; padding: 9px 12px; border-radius: 7px; white-space: nowrap; }
.nav-menu a { color: var(--text); }
.nav-menu a:hover { background: color-mix(in srgb, var(--text) 8%, transparent); color: var(--accent); }

/* The same width the menu folds into its panel at (see responsive). */
@media (max-width: 1000px) {
  /* Inside the open mobile panel the menu expands inline rather than floating. */
  .nav-item.has-menu { display: block; }
  .nav-parent { padding: 10px 0; width: 100%; }
  .nav-menu {
    position: static; min-width: 0; border: none; box-shadow: none;
    padding: 0 0 0 4px; background: none;
    /* Here the header scrolls away with the page, menu and all, so the page's
       own scroll reaches every entry: no second scroll inside it. */
    max-height: none; overflow: visible;
  }
  .nav-menu a { padding: 10px 12px; }
  .nav-item.has-menu:hover .nav-menu { display: none; }
  .nav-item.has-menu.is-open .nav-menu { display: block; }
}

/* ---- service page ---- */

.card--link { display: block; color: inherit; text-decoration: none; }
.card--link:hover { box-shadow: 0 14px 34px rgba(38, 32, 25, .12); transform: translateY(-2px); }

/* 30% shorter than it was, and the copy sits in the middle of it: title,
   description and button as one block on the cover's centre line, which is
   what a short cover wants -- there is no longer enough empty space below the
   words to be worth spreading anything into. */
.hero--service { min-height: 266px; align-items: stretch; }
/* The copy is centred by the band rather than by auto margins of its own: as
   a flex item told to grow, its max-width froze it at 620px and the leftover
   width stayed on the right, which left every word sitting against the left
   edge. Centring it from the parent cannot be defeated that way. */
.hero--service .hero-inner { display: flex; align-items: center; justify-content: center;
                             padding-top: 36px; padding-bottom: 36px; }
.hero--service .hero-copy { flex: 0 1 auto; width: 100%; }
/* The gaps between the three are set here rather than left to each element's
   own margin, so the block stays evenly spaced as it centres. */
.hero--service .hero-copy h1 { margin: 0; }
/* auto either side, not 0: the lead is capped at 52ch, so without them its
   box sits at the left of the copy and its centred text lands to the left of
   the heading's. */
.hero--service .hero-copy p { margin: 18px auto 0; }
.hero--service .hero-actions { margin-top: 26px; }
/* A service cover is often a close-up of pale tile or stone, which the
   standard scrim is too light to hold the type against. Darker here only, so
   the home cover is untouched, and the line under the title goes up to the
   full light cream rather than the muted one. */
.hero--service .hero-scrim { background: rgba(38, 32, 25, .72); }
.hero--service p { color: var(--on-dark); }
/* Full container width -- the same measure as the home page's sections.
   Only the paragraph keeps a reading cap (below); the heading, photos and
   buttons use the whole band. */
.subservice { margin: 0 auto 48px; }
.subservice:last-child { margin-bottom: 0; }
/* The site-wide divider, one between every pair of subservices: same 1px rule
   in the same colour, the width of the container it already sits in. Drawn on
   the one below so the last block never trails a rule into the footer -- and
   a contact band in the list brings its own. */
.subservice + .subservice::before,
.cta-band--service + .section .subservice:first-child::before {
  content: ""; display: block; height: var(--hair); background: var(--dark);
  margin: 0 0 48px;
}
/* Centred over the photos below them, and over the buttons that can close
   the block -- everything else in a subservice is centred, so ranged-left
   copy was the only thing breaking the column. */
.subservice h2 { font-size: clamp(26px, calc(3vw + 2px), 34px); margin: 0 0 12px; text-align: center; }
.subservice p { font-size: 17px; line-height: 1.7; color: var(--muted); margin: 0 auto;
                text-align: center; max-width: 70ch; }

/* Words and photos in one two-column grid, the narrower column for the words.
   The copy is placed by hand; the photos flow into whatever is left, so one
   photo lands beside the words and three fill the block: a large one next to
   them and two across underneath. Any other count still lands somewhere sane,
   but one or three is what the shape is for. */
/* Equal columns, so every photo on the page is the same size whatever row it
   lands in. Rows are sized by the photos' own shape rather than stretched to
   match each other -- a tall portrait photo used to drag a whole row out with
   it and leave the one beside it stranded at the top of a tall empty cell. */
.sub-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; }
/* Centred down the column, so the words sit against the middle of the photo
   beside them. */
.sub-copy { grid-area: 1 / 1; display: flex; flex-direction: column; justify-content: center; }
/* Alternating sides down the page: the words swap columns and the first photo
   takes the cell they left, so the rest flow on unchanged. */
.sub-grid--flip .sub-copy { grid-area: 1 / 2; }
.sub-grid--flip .sub-photo:nth-child(2) { grid-area: 1 / 1; }
/* With no photo at all there is no grid, just the centred column of words. */
.sub-copy--solo { max-width: 70ch; margin: 0 auto; }
/* One shape for every photo, so what the contractor uploads -- portrait,
   landscape, a phone snap -- never changes the layout: the box is 4:3 and the
   picture is cropped to fill it. */
.sub-photo { border-radius: 10px; overflow: hidden; aspect-ratio: 4 / 3; }

/* One column on a narrow screen: the hand-placed cells are released first,
   or the words and the first photo would land on top of each other. */
@media (max-width: 860px) {
  .sub-grid, .sub-grid--flip { grid-template-columns: 1fr; gap: 20px; }
  .sub-copy, .sub-grid--flip .sub-copy,
  .sub-grid--flip .sub-photo:nth-child(2) { grid-area: auto; }
  .sub-copy { margin-bottom: 4px; }
}

/* Material tiles on the home page link to their entry on the Materials page. */
.tile--link { display: flex; color: inherit; text-decoration: none; }
.tile--link:hover { transform: translateY(-2px); }
.tile--link:hover .tile-label { color: var(--accent); }
.tile--link .media, .tile--link .tile-label { transition: none; }
.tile--link { transition: transform .15s ease; }

/* The header is sticky, so an anchored material would land underneath it. */
[id^="material-"] { scroll-margin-top: 84px; }

/* Centred action under a home-page band (e.g. "See all our materials"). */
.band-action { margin-top: 36px; text-align: center; }

/* ---- process band ---- */

/* Heading left, steps right, a rule between them -- and a dashed line joining
   each step to the next so the band reads as a sequence rather than a grid of
   unrelated tiles. */
/* --pgap is both the grid gap and the length of every connector, so the dashes
   always exactly span the space between two steps. --pline is the colour of
   every line in the band, kept in one place so the divider and the connectors
   can never drift apart. */
/* --picon is the icon's footprint and --pclear the air left between an icon
   and any line that reaches it. Every connector is positioned from these two,
   so the gap is identical whichever direction the line runs. */
/* --plane is the clear strip either side of the columns that the row-change
   loops swing out into. It is padding on this container, not margin on the
   cells, so a loop passes beside the captions instead of across them -- a
   caption can use its whole column and still never meet a line. */
/* No rule across the top: the band already has the site's divider above its
   heading, and a second line under it was one too many. --pline stays, since
   it is the colour of the dashed route between the steps. */
.process-steps { --pgap: 48px; --picon: 72px; --pclear: 12px; --plane: 64px;
                 --pline: var(--gold);
                 display: flex; flex-direction: column; gap: var(--pgap);
                 padding: 25px calc(var(--plane) + 12px) 0; }
/* This band sets its own distance from the head, in the padding above. The
   head's usual margin would be added to it rather than replaced by it, which
   is how the first row of icons ended up most of a screen below the intro. */
.section-head:has(+ .process-steps) { margin-bottom: 0; }

/* ---- service steps ---- */

/* The steps on a service page are the Process band's route, so they share all
   of its styling. Two differences only: no rule across the top, because there
   is no heading above it to separate from, and a number in place of the empty
   placeholder where a step has no photo. */
/* Clear of whatever follows it -- the captions on the last row run right to
   the bottom of the band, so without this a subservice heading starts almost
   on top of them. */
.process-steps--bare { border-top: 0; padding-top: 0; margin-bottom: 30px; }
.process-steps--bare:last-child { margin-bottom: 0; }
/* A row centres its own steps, which is the whole reason rows are separate
   containers: a last row of one or two sits in the middle of the band instead
   of hanging off the left. row-reverse rather than re-ordering in the markup,
   so the DOM still reads 1, 2, 3 for a screen reader. */
.process-row { display: flex; gap: var(--pgap); justify-content: center; }
.process-row--rev { flex-direction: row-reverse; }

/* Every step is one slot wide whether or not its row is full, so a short row
   lines up under the one above it instead of spreading out. */
.process-step { position: relative; display: flex; flex-direction: column;
                align-items: center; text-align: center; gap: 12px;
                flex: 0 0 calc((100% - (var(--cols, 3) - 1) * var(--pgap)) / var(--cols, 3)); }
/* A fixed box rather than width:auto. The connectors are placed from the
   icon's half-width, so an icon that sized itself to its own artwork would move
   the line's starting point and the clearance would differ icon to icon.
   object-fit keeps each image's proportions inside the box. */
.process-icon { display: block; width: var(--picon); height: 56px; object-fit: contain; }
/* A step with no icon shows its number, in the icon's box and sized to fill it,
   so a number and an icon carry the same weight in a row that mixes them. After
   .process-icon so its flex wins over display:block. */
.process-icon--num { display: flex; align-items: center; justify-content: center; }
.process-icon--num span { font-family: var(--font-body); font-weight: 800;
                          font-size: 44px; line-height: 1; color: var(--gold); }
.process-step-title { font-family: var(--font-heading); font-size: 19px; line-height: 1.25; }
.process-step-text { font-size: 14.5px; line-height: 1.6; color: var(--muted); }

/* Connectors. Each step draws the single line that reaches the next step in
   order. Inline SVG rather than borders, because a border cannot curve into a
   route; preserveAspectRatio="none" lets one path stretch to whatever width a
   column happens to be, and non-scaling-stroke keeps the dashes an even 2px
   whatever that stretch turns out to be. */
.process-link { position: absolute; overflow: visible; pointer-events: none;
                fill: none; stroke: var(--pline); stroke-width: 2;
                stroke-linecap: round; stroke-dasharray: 7 8; }
.process-link path { vector-effect: non-scaling-stroke; }

/* Every variant states its width outright. An <svg> is a replaced element, so
   pinning both left and right does not stretch it the way it would a div --
   the browser keeps the intrinsic size and quietly drops one of the offsets,
   which left these lines ending in mid-air. */

/* Along a row: starts and ends --pclear clear of each icon, centred on the
   icons' own centre line (28px down, half the icon's 56px height). The width
   is this cell's remainder, plus the gap, plus the next cell's lead-in. */
.process-link--right, .process-link--left {
  top: 8px; height: 40px;
  width: calc(100% + var(--pgap) - var(--picon) - var(--pclear) * 2);
}
.process-link--right { left: calc(50% + var(--picon) / 2 + var(--pclear)); }
.process-link--left  { right: calc(50% + var(--picon) / 2 + var(--pclear)); }

/* Row change: from this icon's edge, out into the lane, and back to the icon
   directly below. The width carries it past the column edge into the lane;
   100% + the gap is the drop from one icon's centre line to the next. */
.process-link--loop-right, .process-link--loop-left {
  top: 28px; height: calc(100% + var(--pgap));
  width: calc(50% - var(--picon) / 2 - var(--pclear) + var(--plane));
}
.process-link--loop-right { left: calc(50% + var(--picon) / 2 + var(--pclear)); }
.process-link--loop-left  { right: calc(50% + var(--picon) / 2 + var(--pclear)); }

/* The sideways reach into a short row below, picking up where the loop lands.
   --dx is how many slots across the next step sits, in halves. */
.process-link--tail-left, .process-link--tail-right {
  top: calc(100% + var(--pgap) + 8px); height: 40px;
  width: calc(var(--dx, 0) * (100% + var(--pgap)));
}
.process-link--tail-left  { right: calc(50% - var(--picon) / 2 - var(--pclear)); }
.process-link--tail-right { left: calc(50% - var(--picon) / 2 - var(--pclear)); }

.process-link--stack-right, .process-link--stack-left { display: none; }

@media (max-width: 820px) {
  /* The lanes stay: the loops still need somewhere to swing. Narrower than on
     desktop, since they are taking width away from the captions now rather
     than from empty margin. */
  .process-steps { --pgap: 56px; --plane: 36px;
                   padding: 32px calc(var(--plane) + 6px) 0; }
  /* One column, so every row collapses to a stack and the sideways routes
     have nowhere to go. */
  .process-row, .process-row--rev { flex-direction: column; gap: var(--pgap); }
  .process-step { flex: 0 0 auto; }
  .process-link--right, .process-link--left,
  .process-link--loop-right, .process-link--loop-left,
  .process-link--tail-left, .process-link--tail-right { display: none; }
  /* Same loop as a desktop row change, from one icon's edge out into the lane
     and back to the next icon's edge, alternating sides down the column. */
  .process-link--stack-right, .process-link--stack-left {
    display: block; top: 28px; height: calc(100% + var(--pgap));
    width: calc(50% - var(--picon) / 2 - var(--pclear) + var(--plane));
  }
  .process-link--stack-right { left: calc(50% + var(--picon) / 2 + var(--pclear)); }
  .process-link--stack-left  { right: calc(50% + var(--picon) / 2 + var(--pclear)); }
}

@media (max-width: 620px) {
  .awards { gap: 28px 36px; }
  .award { width: 143px; height: 143px; }
}

@media (max-width: 620px) {
  /* On a phone the buttons would eat a quarter of the card's width, and a
     swipe is the natural gesture anyway. */
  .reviews-slider { grid-template-columns: minmax(0, 1fr); }
  .slider-nav { display: none; }
  .reviews-track > .review { flex-basis: 100%; }
  .grid--services { grid-template-columns: 1fr; }
  /* One per row: the card is full width, so a wider shape (a 3:2 card, split
     the same 60:40) keeps it from becoming a tall block that pushes the next
     card off screen. */
  .grid--services .card .media { aspect-ratio: 5 / 2; }
  .grid--services .card-body { aspect-ratio: 15 / 4; }
}
/* ---- whole pictures, and framed ones ---- */

/* One picture on this site stands alone: the story beside the words about
   the business. It is shown entire, because there is nothing for it to match
   and a crop takes the edges off whatever was uploaded -- off a logo it takes
   whatever will not fit, which is how a mark ends up with its bottom row
   missing.

   Every other picture belongs to a set seen together: the gallery's covers,
   the materials, the cards and tiles, the photographs down a service page,
   the ones set into an article. A set whose pictures are each a different
   shape reads as a mistake, so each fills its frame and is cropped to it.
   That is what .media does unasked, so only the exception is written here.

   Last in the file on purpose: several of these boxes set object-fit on
   their own img at the same weight, so order is what decides.

   Round frames are in neither list. A circle has no letterbox to give back,
   and those crops are of a face, which is the one subject a centre crop
   always gets right. */
.detail .media img { object-fit: contain; }

/* No tint behind it: the fill was never seen while a picture covered it, and
   a band of it around a contained one reads as a box drawn on the page. The
   hatching an empty box wears is left alone. */
.detail .media:not(.media--empty) { background: transparent; }

/* The exception to the exception: a material belongs to a set, and its block
   is built from the same shape the story is. */
.detail--material .media img { object-fit: cover; }
.detail--material .media { background: var(--bg-alt); }

/* ---- text as it was typed ---- */

/* Every one of these comes out of a box in the builder that the contractor
   can press Enter in. HTML folds a run of spaces and every newline into one
   space, so a numbered list typed down the panel arrived on the page as a
   single block of prose with the numbers buried in it. The breaks and the
   indents they typed are kept.

   Review text keeps its line breaks too, but under pre-line rather than
   pre-wrap -- it was scraped from a page, so its stray spacing is an artefact.
   See .review-text. */
.article p,
.hero-copy p,
.sub-copy p,
.section-lead,
.detail-text,
.card-text,
.founder-bio,
.post-excerpt,
.process-step-text,
.pcard-text,
.faq-a,
.footer-blurb { white-space: pre-wrap; }

/* ---------- the platform's home cover: start a site (SectionRenderer.start) ----------
   Only on the site HOME_SITE names. The motto in large type, a box to
   describe the business in, and the rest of the home page below. The band
   is the page background (--bg-alt, the Theme panel's "Page background"),
   as every other band is, with the theme's text, muted and accent colours.
   The box is white with its own dark ink, so it reads on any theme. */
.start { position: relative; background: var(--bg-alt); color: var(--text);
  /* The whole window under the menu: the next band starts below the fold. */
  min-height: calc(100vh - 68px); min-height: calc(100dvh - 68px); display: flex; }
/* Three rows: the box in the middle row, centred in the band because the
   rows above and below it share the rest equally; the motto centred in the
   row above, halfway between the menu and the box. At least 40px between
   the motto and the box, and on a short window the rows simply grow. */
.start-inner { position: relative; width: 100%; text-align: center; padding-top: 32px; padding-bottom: 32px;
  display: grid; grid-template-rows: 1fr auto 1fr; row-gap: 40px; }
.start-head { align-self: center; }
.start-title:last-child { margin-bottom: 0; }
.start-title { font-family: var(--font-heading); font-size: clamp(30px, 5.17vw, 62px); line-height: 1.08;
  letter-spacing: -.01em; margin: 0 auto 22px; max-width: 16ch; color: var(--text); }
.start-title span { white-space: pre-wrap; }
.start-accent { color: var(--accent); }
.start-sub { font-size: clamp(16px, 1.7vw, 20px); color: var(--muted); margin: 0 auto;
  max-width: 56ch; white-space: pre-wrap; }

.start-card { --ink: #1f2328; --ink-muted: #6b7280; --start-line: #e5e7eb; --start-tint: #f3f4f6;
  /* width: 100%, as a grid item with auto margins would shrink to its content. */
  width: 100%; max-width: 696px; margin: 0 auto; text-align: left;
  background: #fff; color: var(--ink); font-family: var(--font-body);
  border: 1px solid var(--start-line); border-radius: 15px; padding: 15px 19px 14px;
  /* A glow around the edges: a pale ring and a tight halo of the theme's
     accent mixed with white, then a wide one of the accent itself.
     Stronger while typing in it. */
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 35%, #fff),
              0 0 18px color-mix(in srgb, var(--accent) 50%, #fff),
              0 0 56px color-mix(in srgb, var(--accent) 60%, transparent),
              0 12px 32px rgba(0, 0, 0, .12);
  transition: box-shadow .2s; }
.start-card:focus-within {
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 30%, #fff),
              0 0 26px color-mix(in srgb, var(--accent) 45%, #fff),
              0 0 80px color-mix(in srgb, var(--accent) 75%, transparent),
              0 12px 32px rgba(0, 0, 0, .12); }
.start-tabs { display: flex; justify-content: center; flex-wrap: wrap; gap: 10px;
  padding-bottom: 12px; border-bottom: 1px solid var(--start-line); }
.start-tab { display: inline-flex; align-items: center; gap: 10px; font: inherit; font-size: 17px; font-weight: 600;
  color: var(--ink); background: transparent; border: 1px solid var(--start-line); border-radius: 999px;
  padding: 10px 20px; cursor: pointer; transition: border-color .15s; }
.start-tab svg, .start-go svg, .start-mic svg { width: 15px; height: 15px; fill: none;
  stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; flex: none; }
.start-tab svg, .start-go svg, .start-mic svg { width: 18px; height: 18px; }
.start-tab:hover { border-color: var(--ink-muted); }
.start-tab.is-on { border: 2px solid var(--accent); padding: 9px 19px; color: var(--accent); }
.start-fields { display: flex; flex-direction: column; gap: 3px; padding-top: 8px; }
.start-input, .start-text { width: 100%; font: inherit; font-size: 16.5px; color: var(--ink); background: transparent;
  border: 0; border-bottom: 1px solid var(--start-line); padding: 9px 2px; outline: none; }
.start-text { border-bottom: 0; resize: none; min-height: 76px; line-height: 1.5; }
.start-input::placeholder, .start-text::placeholder { color: var(--ink-muted); opacity: .85; }
.start-input:focus { border-bottom-color: var(--accent); }
.start-foot { display: flex; align-items: center; justify-content: flex-end; gap: 10px; }
.start-err { flex: 1 1 auto; margin: 0; font-size: 13px; color: #c0362c; min-height: 1em; }
.start-mic { display: inline-flex; align-items: center; justify-content: center; width: 43px; height: 43px;
  border: 0; border-radius: 50%; background: transparent; color: var(--ink-muted); cursor: pointer; }
.start-mic:hover { background: var(--start-tint); color: var(--ink); }
.start-mic.is-on { background: color-mix(in srgb, #c0362c 15%, transparent); color: #c0362c; }
.start-go { display: inline-flex; align-items: center; gap: 10px; font: inherit; font-size: 17px; font-weight: 700;
  color: var(--btn-text); background: var(--btn-bg); border: 0; border-radius: 11px; padding: 12px 23px; cursor: pointer;
  transition: background .15s, opacity .15s; }
.start-go:hover { background: var(--btn-bg-hover); color: var(--btn-text-hover); }
.start-go:disabled { opacity: .55; cursor: default; }
.start-tab:focus-visible, .start-go:focus-visible, .start-mic:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* The account circle at the right of the platform site's menu
   (public-page.jsp): a person icon signed out, the initial signed in. */
/* The circle sits at the far right of the window, not of the content
   column; the bar's content keeps clear of it however wide the window is. */
.site-account { position: absolute; right: 24px; top: 50%; transform: translateY(-50%); }
.site-header .container:has(.site-account) {
  padding-right: max(24px, min(78px, calc(78px - (100vw - 1180px) / 2))); }
.site-header .container:has(.site-account) .site-nav { margin-left: auto; }
.account-btn { display: inline-flex; align-items: center; justify-content: center; width: 38px; height: 38px;
  border-radius: 50%; border: 1px solid var(--border); background: transparent; color: var(--text);
  font: 700 16px/1 var(--font-nav); cursor: pointer; transition: border-color .15s, color .15s; }
.account-btn svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round; }
.account-btn:hover { border-color: var(--accent); color: var(--accent); }
.account-btn.is-in { background: var(--accent); border-color: var(--accent); color: var(--btn-text, #fff); }
.account-btn.is-in:hover { color: var(--btn-text, #fff); filter: brightness(1.08); }
.account-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.account-menu { position: absolute; right: 0; top: calc(100% + 10px); z-index: 60; min-width: 220px;
  background: var(--bg-alt); color: var(--text); border: 1px solid var(--border); border-radius: 12px;
  padding: 6px; box-shadow: 0 12px 32px color-mix(in srgb, var(--text) 16%, transparent); font-family: var(--font-nav); }
.account-menu[hidden] { display: none; }
.account-who { display: flex; flex-direction: column; gap: 2px; padding: 10px 12px 12px;
  border-bottom: 1px solid var(--border); margin-bottom: 4px; }
.account-name { font-size: 15px; }
.account-email { font-size: 13px; color: var(--muted); overflow-wrap: anywhere; }
.account-item { display: block; width: 100%; text-align: left; font: inherit; font-size: 15px; color: var(--text);
  background: none; border: 0; border-radius: 8px; padding: 10px 12px; cursor: pointer; }
.account-item:hover { background: color-mix(in srgb, var(--text) 6%, transparent); color: var(--accent); }

/* The platform home's header stays pinned to the top, on phones too. */
body.is-platform-home .site-header { position: sticky; }

@media (max-width: 600px) {
  .start-inner { padding-top: 24px; padding-bottom: 24px; }
  .start-card { padding: 12px 12px 10px; border-radius: 12px; }
  .start-tab { font-size: 15.5px; padding: 8px 14px; }
  .start-tab.is-on { padding: 7px 13px; }
}
