/* ==========================================================================
   Studio Beta — editorial client template
   Hand-built static site. No framework, no build step, no npm.

   TO RE-SKIN FOR A NEW CLIENT: change the colour tokens and the two font
   families in the block below, and nothing else in this file. Everything
   downstream is expressed in terms of those variables, so one palette
   re-skins the whole site.

   The skeleton this file dresses is documented in design-directive.md at the
   repo root. The hero is first and the enquiry last; what sits between them
   moves per site under the rules in section-library.md.
   ========================================================================== */

/* --- Palette -------------------------------------------------------------
   Light, neutral, one restrained accent. The photographs carry all the
   colour; the page around them stays close to paper.
   ------------------------------------------------------------------------ */
:root {
  /* The two grounds, written as channels because the rest of the file needs
     them at partial opacity — the header plate, the hero scrim, the plate
     wash, the enquiry section, the footer and every hairline. Those are all
     written rgb(var(--paper-rgb) / alpha) rather than as literal colours,
     which is the whole reason a palette is a token change and not a
     find-and-replace through 1100 lines.

     --paper and --ink below are derived from these, so there is exactly one
     place to type each colour and the two forms cannot drift apart.

     FOR A DARK PALETTE: make --paper-rgb the near-black and --ink-rgb the
     near-white, then swap --todo and --todo-alt. Nothing else moves. */
  /* GREY GROUND, from 2026-09-12. This was warm white #F7F6F3 and every demo
     built before this date runs that. It is a neutral light grey now, and the
     change is deliberate rather than a drift.

     Why: the ground is a surface for photographs, and a warm white one makes
     every picture on the page read as a bright rectangle pinned to a brighter
     sheet. Grey steps down from a photograph instead of competing with it, and
     it is the first ground on which the white review cards actually lift.

     THE WHOLE NEUTRAL RAMP MOVES TOGETHER. A grey ground under a warm chalk
     fill reads as a printing error. --wash keeps a hue because it is the one
     surface allowed one.

     NOT BACKPORTED. demos/tonys-plumbing, pasha-electrical, direct-autocare,
     wash-n-wag and bracken-and-bay all carry their own copy of this file and
     all five still run warm white. That is on purpose — each finished demo
     gets hand-tuned afterwards so it does not look like the last one — so do
     not "fix" them to match. */
  --paper-rgb: 239 239 241;
  --ink-rgb:    20  24  28;

  --paper:      rgb(var(--paper-rgb));  /* the page ground — light grey     */
  --paper-2:    #FAFAFB;   /* near-white — plates that must lift off paper  */
  --chalk:      #E4E4E7;   /* the alternate fill, one step down from paper  */
  --wash:       #E2E8EC;   /* palest blue — the one surface with a hue      */
  --stone:      #C9C9CE;   /* mid neutral — borders that need to be seen    */

  --ink:        rgb(var(--ink-rgb));    /* body text, never pure black      */
  --ink-2:      #39424A;   /* secondary text                    8.06:1      */
  /* Darkened from #646A71 when the ground went grey. --chalk is darker than
     it was, and palette-check.php measures the WORST surface rather than the
     ground: captions sat at 4.31:1 on the alternate fill while looking
     perfectly fine on the page itself. MOVE THE GROUND, RE-MEASURE THIS. */
  --muted:      #5F656B;   /* captions, meta, indices           4.65:1      */

  --water:      #1F5C8B;   /* THE accent. One colour, used hard. 6.06:1     */
  --water-deep: #16456A;   /* hover / pressed                    8.57:1     */
  --water-soft: #9FC0D6;   /* rules and marks, never small text on paper    */

  /* --- The accent's other two jobs, split out for a dark palette ---------
     --water-soft was doing three jobs: a quiet mark on the ground, the accent
     on the photo scrim (hero em, stars, scroll cue, wordmark dot over the
     hero), and the hover on the inverted bands (enquiry, footer, form focus).
     On a light palette all three want a pale tint, so one token worked. On a
     dark palette they pull apart: the scrim never flips and stays pale, the
     inverted band becomes the LIGHT band and needs a dark tint. Setting one
     token for both put the hero accent at 1.05:1 on demos/minos.

     Promoted out of demos/minos on 2026-09-13 for the second dark build, as
     demos.md said it should be. Both default to --water-soft, so every light
     site renders exactly as before. A dark palette sets all three. Same shape
     as --todo / --todo-scrim / --todo-alt. */
  --water-scrim: var(--water-soft);   /* on the photo scrim — never flips  */
  --water-alt:   var(--water-soft);   /* on the inverted bands — flips     */

  /* --muted and --water both carry SMALL TEXT, so both need 4.5:1 and
     neither is free to be chosen on looks alone. --muted sets eyebrows at
     11px, form labels, hours, prices and service descriptions. --water sets
     the nav hover, the star rating and the accented clause in a heading. Both were below
     the floor until 2026-09-11 — muted at 3.32:1 — which is why the ratio
     is written next to each one. Re-skin, then re-check.

     MEASURE AGAINST --chalk, NOT --paper. Every one of those small-text
     roles also appears inside an alternate-fill section, and --chalk is the
     darkest surface a light palette puts text on. A value tuned against
     --paper passes on half the page and fails on the other half; that is
     exactly how the 3.32:1 shipped. The ratios noted here are the worst
     case across --paper, --paper-2, --chalk and --wash.

     One more constraint, and it decides accents: the bracket marker below is
     orange on every Studio Beta site, so THE ACCENT MAY NOT BE ORANGE OR
     BROWN. If a client's colour lands in that family, move the accent. The
     marker's whole job is being impossible to miss. */

  /* The unverified-content marker. Two colours, because the same marker has
     to stay legible on the page ground and on the inverted sections. On a
     dark palette these two swap. The tint they share does not move — the
     bracket must look the same on every Studio Beta site. */
  --todo:      #9B4318;       /* marker text on the page ground   */
  --todo-alt:  #F0A97A;       /* marker text on inverted surfaces */
  --todo-scrim: #F0A97A;      /* marker text on a photo scrim     */
  --todo-rgb:  212 122 60;    /* the shared fill and border tint  */

  /* --- Photo scrims: FIXED, and deliberately not part of the palette ------
     A scrim exists to make text readable over a photograph nobody has seen
     yet. Its job has nothing to do with whether the page around it is cream
     or black, so it does not flip. Deriving it from --ink looked tidy and
     was wrong: on a dark palette --ink becomes near-white, which turned the
     full-bleed quote plate into a bright wash and pushed the bracket marker
     inside the about-page note down to 3.26:1.

     Change these per world only to change how heavy the scrim is, never to
     follow the ground. Used by .plate::after, .plate blockquote, .plate cite
     and .about__note. */
  --scrim-rgb:     20  24  28;   /* always dark: the wash over a photo   */
  --on-scrim-rgb: 247 246 243;   /* always light: text on that wash      */

  /* --- The hero wash, and why it is a dial -------------------------------
     The hero fades the page ground up over the photograph so the headline
     has something to sit on. The defaults below were set against the
     reference build, whose hero is a plain white tiled wall — about the
     easiest photograph a headline can sit on.

     They are not enough for a busy one. Found 2026-09-11 building the
     mobile-mechanic demo: over an engine bay, a tyre tread and a car in a
     driveway, the lede was unreadable at these values and the eyebrow had
     effectively vanished. That is a hero failing its own non-negotiable,
     not a bad photograph.

     So the strength is a per-client dial. Raise 2, 3 and x together until
     the lede is comfortable; leave 1 alone unless the bottom edge shows a
     seam. Values are alpha, 0 to 1.

       0.95 / 0.58 / 0.04 / 0.55   a plain, pale photograph
       0.97 / 0.86 / 0.45 / 0.86   a busy or mid-tone one

     Check it at 390px as well as on a desktop: the phone crop uses the
     portrait frame, which is a different part of the picture. */
  --hero-wash-1: 0.97;   /* bottom edge, where the page takes over    */
  --hero-wash-2: 0.72;   /* 26% up — this is the one that matters     */
  --hero-wash-3: 0.16;   /* 58% up, where the photograph wins         */
  --hero-wash-x: 0.70;   /* left to right, behind the type            */

  /* --- The DARK hero scrim, and why it is now a dial too -----------------
     This block said, for two months, that the dark scrim "is fixed rather
     than dialled" because "white on a dark wash clears 4.5:1 over anything".
     That is false and it was measured false on 2026-09-12 building
     demos/minos: four candidate photographs were composited against these
     exact stops and the headline came out at 2.11:1, 2.57:1, 3.5:1 and
     4.87:1 — three of the four failing, one of them by half.

     The sentence is true of a genuinely dark photograph and false of a
     mid-toned one. An engine bay with sky in the top third is mid-toned, and
     the vertical gradient is at its WEAKEST exactly where the headline sits.
     The light hero was given four tokens for this same reason on 2026-09-11;
     the dark one should have had them at the same time.

     Defaults below are the old hardcoded values to the digit, so every site
     built before today renders byte-identical. Raise them per client the way
     the light wash is raised, and MEASURE — compositing the photograph under
     these stops and reading the brightest pixel behind each glyph is the only
     check that catches this. Eyeballing does not.

     The horizontal one is usually the cheapest to move: the hero type is all
     in the left column, so raising --hero-dark-x darkens where the words are
     and leaves the subject of the photograph alone. */
  --hero-dark-1: 0.80;   /* bottom edge                               */
  --hero-dark-2: 0.48;   /* 38% up                                    */
  --hero-dark-3: 0.18;   /* the top — weakest, and where the headline is */
  --hero-dark-x: 0.42;   /* left to right, behind the type            */

  --hair:        rgb(var(--ink-rgb) / 0.10);
  --hair-strong: rgb(var(--ink-rgb) / 0.22);

  /* --- Type, and the register it sets ------------------------------------
     ONE grotesque, set heavy for headings and plain for text. That is the
     default for a trade, and it is a deliberate reversal: this template ran
     a high-contrast display serif with an italic accent clause until
     2026-09-11, which looked expensive and looked like a design studio. A
     plumber's customer has a leak. They need to believe he is real, local and
     licensed, and none of that is carried by a beautiful typeface.

     Two families are still supported — point the tokens at different faces.
     Earn it first. A trade site almost never needs it. */
  --font-display: "Instrument Serif", Georgia, "Times New Roman", serif;   /* studiobeta-site: the brand face; css/site.css sets it too */
  --font-body:    "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;

  /* A third family, for ONE line. World 6 inverts the rule above: the sans
     carries the site and a quiet serif appears exactly once, on the statement
     lead. It defaults to --font-display, so a World 1 site that never sets it
     renders identically to before. Promoted out of demos/wash-n-wag on
     2026-09-12 because the second World 6 build needed it and demos.md said
     the next one would drift without it. */
  --font-accent:  var(--font-display);

  /* Heading weight. 700 is the trade default. Drop to 600 for anything that
     wants to feel calmer, never below 500 or the headings stop leading. */
  --weight-display: 700;

  /* Generous radius and EXACTLY ONE soft shadow is World 6, Soft Coat. No
     other world spends either — World 1 says no boxes and means it — so
     nothing in this file reads these two. A World 6 build points them at the
     blocks it wants lifted, and picks different blocks from the last one.
     Tokens live here so two World 6 builds cannot drift apart on the values;
     which elements get them is the per-build decision. */
  --radius:     14px;
  --shadow:     0 18px 40px -24px rgb(var(--ink-rgb) / 0.28);

  /* --- Rhythm ------------------------------------------------------------
     --pad was clamp(5rem, 12vw, 11rem) and that was magazine pacing: 176px
     of air between sections reads as a gallery with time on its hands. A
     working business is denser. Raise it only for a client whose whole offer
     is calm. */
  --measure:  60ch;
  --gutter:   clamp(1.25rem, 5vw, 5rem);
  --max:      1340px;
  --max-text: 940px;
  --pad:      clamp(3.5rem, 7vw, 6.5rem);

  --ease:     cubic-bezier(0.22, 1, 0.36, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
}

/* --- Reset ---------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Belt and braces with the body rule below: overflow does not always
     propagate from body to the viewport, and a stray pixel of horizontal
     scroll is the most common way a page like this feels broken on a phone. */
  overflow-x: clip;
}

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: clamp(1rem, 0.95rem + 0.22vw, 1.0938rem);
  font-weight: 400;
  line-height: 1.68;
  color: var(--ink-2);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* clip, not hidden: `hidden` turns the body into a scroll container, which
     is what quietly breaks position: sticky and pinned sections. */
  overflow-x: hidden;
  overflow-x: clip;
}

img, svg, video, canvas { display: block; max-width: 100%; }
img { height: auto; }
a { color: inherit; }
p { margin: 0; }
ul, ol, dl, dd { margin: 0; padding: 0; }
li { list-style: none; }

::selection { background: var(--water); color: var(--paper-2); }

/* --- Typography ----------------------------------------------------------- */
h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  color: var(--ink);
  line-height: 1.04;
  letter-spacing: -0.025em;
  margin: 0;
  text-wrap: balance;
}

/* The hero line. It came down from 8rem: at that size a headline stops being
   a sentence and becomes a poster, and a plumber is not selling a poster. */
.display {
  font-size: clamp(2.5rem, 6.2vw, 5.25rem);
  line-height: 1.0;
  letter-spacing: -0.035em;
}

/* --- World 3 display: past 8vw -------------------------------------------
   World 3 is a photograph and a piece of type meeting with nothing between
   them, and the type has to hold its half. The category is a condensed
   high-contrast serif or a tight grotesque, so the width is set here too; a
   face with no width axis ignores it. Scoped to .world-3 so no other world
   moves. Added 2026-09-13 for demos/king-of-cuts. */
.world-3 .display {
  font-size: clamp(3rem, 8.6vw, 8.75rem);
  line-height: 0.92;
  letter-spacing: -0.02em;
}
.world-3 .display,
.world-3 h2 { font-stretch: 72%; }
.world-3 .hero h1 { max-width: min(13ch, 100%); }
@media (max-width: 720px) { .world-3 .hero h1 { max-width: 100%; } }

h2 { font-size: clamp(1.75rem, 3.6vw, 3rem); }
h3 { font-size: clamp(1.15rem, 1.7vw, 1.45rem); letter-spacing: -0.015em; }

/* A clause inside a heading takes the accent and nothing else. It used to go
   italic in a display serif, which was the single most expensive-looking
   thing on the page. Colour alone is enough emphasis. */
h1 em, h2 em, .display em {
  font-style: normal;
  color: var(--water);
}

.eyebrow {
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 1.5rem;
  font-variant-numeric: tabular-nums;
}

.lede {
  font-size: clamp(1.0625rem, 1.15vw, 1.25rem);
  line-height: 1.6;
  color: var(--ink-2);
  max-width: min(46ch, 100%);
}

.muted   { color: var(--muted); }
.caption {
  font-size: 0.75rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* --- Layout --------------------------------------------------------------- */
.wrap {
  width: 100%;
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.wrap--text { max-width: calc(var(--max-text) + 2 * var(--gutter)); }

section { position: relative; }
.section-pad { padding-block: var(--pad); }
.section-pad--tight { padding-block: clamp(3.5rem, 8vw, 7rem); }

.rule { height: 1px; border: 0; margin: 0; background: var(--hair); }

/* Section head: an index numeral, a hairline, a title. The one masthead
   device the whole page repeats. */
.head {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: clamp(1.5rem, 4vw, 4rem);
  align-items: end;
  padding-bottom: clamp(2.5rem, 5vw, 4.5rem);
}
.head__index {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  margin-bottom: 1.25rem;
}
.head__index::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--hair);
  transform: translateY(-0.35em);
}

/* --- Edge language: how a block declares where it ends --------------------
   Axis 6 in design-worlds.md, and measured across two builds it is the
   strongest fingerprint on the page. .head repeats six times a page and was
   identical on a plumber and a dog groomer, which is most of the reason those
   two sites read as one template wearing two palettes.

   Three treatments. THE DEFAULT IS RULED, and it is everything written above,
   unscoped — so a site that names no world renders exactly as it did before
   this block existed. Opt into another with 'world' in config/site.php; the
   world-to-treatment map lives in partials/head.php.

     ruled   index numeral, full hairline, ruled lists, hairline grids.
             World 1, and the fallback for any world without its own.
     bare    no numerals, no rules, no grid lines. A photograph and a piece of
             type meeting with nothing between them. Worlds 3 and 4.
     soft    the numeral traded for a short accent dash, a generous radius, and
             exactly ONE soft shadow on the page. World 6.

   Do not add a fourth treatment until a world actually builds a page that
   needs it. A treatment invented ahead of a build is a mood board.
   ------------------------------------------------------------------------ */

/* bare ------------------------------------------------------------------- */
.edge-bare .head__index { display: none; }
.edge-bare .rule { background: transparent; }
.edge-bare .proof__grid { gap: 0; background: transparent; }
.edge-bare .service { border-top: 0; }
.edge-bare .service:last-child { border-bottom: 0; }
/* The review cards lose their panel too, and the statement its rule. A
   bordered card is a box, and World 3 is the fewest boxes of the six — the
   quote stands on the ground. Found 2026-09-13 on demos/king-of-cuts, the
   first bare page to carry reviews. */
.edge-bare .quote { background: transparent; border: 0; padding-inline: 0; }
.edge-bare .quote footer { border-top: 0; }
.edge-bare .statement__foot { border-top: 0; }
.edge-bare .faq__item { border-top: 0; }
.edge-bare .faq__item:last-child { border-bottom: 0; }

/* soft --------------------------------------------------------------------
   The shadow below is THE shadow. World 6 allows exactly one, and a second is
   the thing that turns this vertical into slop — so if another block needs
   lifting, take this one away first rather than adding to it. */
.edge-soft { --radius: 14px; }
.edge-soft .head__index span { display: none; }
.edge-soft .head__index::after {
  flex: 0 0 3rem;
  height: 2px;
  background: var(--water);
  transform: none;
}
.edge-soft .frame__media,
.edge-soft .about__media { border-radius: var(--radius); }
.edge-soft .proof__grid {
  gap: 0;
  background: transparent;
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: 0 18px 40px -28px rgb(var(--ink-rgb) / 0.45);
}

/* --- Controls ------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.7em;
  font-family: var(--font-body);
  font-size: 0.9375rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  padding: 1.05rem 1.9rem;
  border: 1px solid transparent;
  border-radius: 999px;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.4s var(--ease), color 0.4s var(--ease),
              border-color 0.4s var(--ease), transform 0.4s var(--ease);
}
.btn--primary { background: var(--ink); color: var(--paper); }
.btn--primary:hover { background: var(--water-deep); }
.btn--ghost { border-color: var(--hair-strong); color: var(--ink); }
.btn--ghost:hover { border-color: var(--ink); background: var(--ink); color: var(--paper); }
.btn:hover { transform: translateY(-2px); }
.btn:focus-visible { outline: 2px solid var(--water); outline-offset: 4px; }
.btn__arrow { transition: transform 0.45s var(--ease); }
.btn:hover .btn__arrow { transform: translateX(4px); }

.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 0.55em;
  text-decoration: none;
  color: var(--ink);
  padding-bottom: 0.25rem;
  border-bottom: 1px solid var(--hair-strong);
  transition: color 0.35s var(--ease), border-color 0.35s var(--ease);
}
.link-arrow:hover { color: var(--water); border-color: var(--water); }
.link-arrow .btn__arrow { transition: transform 0.45s var(--ease); }
.link-arrow:hover .btn__arrow { transform: translateX(4px); }

/* --- Scroll progress hairline --------------------------------------------- */
.progress {
  position: fixed;
  inset: 0 auto auto 0;
  height: 2px;
  width: 100%;
  transform: scaleX(0);
  transform-origin: 0 50%;
  background: var(--water);
  z-index: 90;
  pointer-events: none;
}

/* --- Header --------------------------------------------------------------- */
/* The header height is constant, and the stuck state is paint only: a plate
   fades in behind it and a hairline appears under it. It used to animate
   padding as well, which is a layout property changing on every scroll that
   crosses the threshold — the one place on the page where jank would be most
   obvious. The condense reads fine without it. */
.site-header {
  position: fixed;
  inset: 0 0 auto;
  z-index: 80;
  padding-block: clamp(0.95rem, 1.8vw, 1.45rem);
  transition: background 0.5s var(--ease), box-shadow 0.5s var(--ease);
}
.site-header.is-stuck {
  background: rgb(var(--paper-rgb) / 0.86);
  backdrop-filter: saturate(150%) blur(14px);
  -webkit-backdrop-filter: saturate(150%) blur(14px);
  box-shadow: 0 1px 0 var(--hair);
}
.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
}
.wordmark {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.05rem, 1.5vw, 1.3rem);
  letter-spacing: -0.02em;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}
.wordmark span { color: var(--water); }

.nav { display: flex; align-items: center; gap: clamp(1.25rem, 2.4vw, 2.25rem); }
.nav a {
  font-size: 0.875rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--ink);
  text-decoration: none;
  transition: color 0.35s var(--ease);
}
.nav a:hover { color: var(--water); }
.nav .btn { padding: 0.7rem 1.35rem; font-size: 0.8438rem; }
.nav-toggle { display: none; }

/* --- Hero -----------------------------------------------------------------
   Renders at rest. No reveal, no fade-in, no wait — the top of the page is
   the first thing a visitor, a crawler and a link preview all see.
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  min-height: 84svh;
  display: flex;
  align-items: flex-end;
  /* The header floats over this, so the top padding is what stops the eyebrow
     colliding with the wordmark on a short laptop screen. */
  padding-top: clamp(7rem, 14vh, 10rem);
  padding-bottom: clamp(3rem, 7vw, 6rem);
  overflow: hidden;
  isolation: isolate;
}

.hero__media { position: absolute; inset: 0; z-index: -2; overflow: hidden; }
.hero__media img,
.hero__media canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Frames past the first exist for the WebGL sequence and are preloaded by it;
   one image at a time is visible in the plain-HTML fallback. */
.hero__media img + img { opacity: 0; }
.hero__media canvas { opacity: 0; transition: opacity 1.2s var(--ease); }
/* `webgl-hero`, NOT `webgl-on`. The hero sequence only exists when the client
   has more than one hero frame; the gallery ripple runs regardless. These were
   both keyed to `webgl-on`, so a single-frame hero on a desktop hid its own
   photograph and put nothing in its place — a flat grey rectangle where the
   picture should be. Found 2026-09-11, the first time a client had one frame. */
.webgl-hero .hero__media canvas { opacity: 1; }
.webgl-hero .hero__media img { opacity: 0; }

/* A soft paper scrim so type stays legible over any photograph without
   dimming the picture into mud. */
.hero__media::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  background:
    linear-gradient(to top,
      rgb(var(--paper-rgb) / var(--hero-wash-1)) 0%,
      rgb(var(--paper-rgb) / var(--hero-wash-2)) 26%,
      rgb(var(--paper-rgb) / var(--hero-wash-3)) 58%),
    linear-gradient(to right,
      rgb(var(--paper-rgb) / var(--hero-wash-x)),
      rgb(var(--paper-rgb) / 0) 62%);
}

/* --- The dark hero -------------------------------------------------------
   A second hero treatment, chosen per client by `hero_style` in
   config/site.php, which puts `hero-dark` on the body. It is not a fork: the
   light hero above is untouched and every other section on the page is
   identical under either.

   WHICH ONE TO USE. It follows the photograph, not taste. A pale, calm,
   finished-room picture takes the light wash. A photograph of the work
   actually happening — hands, tools, pipework, a van, an engine bay — is
   almost always mid-toned and busy, and fighting that with a heavier and
   heavier paper wash ends up erasing the picture to keep the type readable.
   Going dark stops the fight: the scrim only has to darken, the type goes
   white, and the photograph keeps its contrast.

   It is also the bolder of the two, which is usually what a trade wants.

   THE SCRIM IS A DIAL, from 2026-09-12. This said it was fixed, and that it
   needed no per-client knob "because white on a dark wash clears 4.5:1 over
   anything". Measured against four real photographs it was wrong three times
   out of four — the full reasoning and the numbers are on the
   --hero-dark-* tokens at the top of this file. Dark is still the right
   treatment for a work photograph; it just is not self-adjusting.
   -------------------------------------------------------------------------- */
.hero-dark .hero__media::after {
  background:
    linear-gradient(to top,
      rgb(var(--scrim-rgb) / var(--hero-dark-1)) 0%,
      rgb(var(--scrim-rgb) / var(--hero-dark-2)) 38%,
      rgb(var(--scrim-rgb) / var(--hero-dark-3)) 100%),
    linear-gradient(to right,
      rgb(var(--scrim-rgb) / var(--hero-dark-x)),
      rgb(var(--scrim-rgb) / 0) 66%);
}

.hero-dark .hero h1,
.hero-dark .hero__meta strong { color: rgb(var(--on-scrim-rgb)); }
.hero-dark .hero h1 em { color: var(--water-scrim); }
.hero-dark .hero__lede { color: rgb(var(--on-scrim-rgb) / 0.88); }
.hero-dark .hero__eyebrow,
.hero-dark .hero__meta { color: rgb(var(--on-scrim-rgb) / 0.72); }
.hero-dark .hero__meta { border-top-color: rgb(var(--on-scrim-rgb) / 0.22); }
.hero-dark .hero .stars { color: var(--water-scrim); }
.hero-dark .hero__eyebrow .dot { background: var(--water-scrim); }

/* White button on a dark picture. The ink button disappears into it. */
/* BOTH halves come from the FIXED scrim pair, not from the palette. This said
   `color: var(--ink)`, which is correct only while --ink is the dark end of
   the palette: invert it for a dark ground and --ink becomes near-white, so
   the label goes white-on-white and the primary call to action disappears.
   Found 2026-09-12 on the first dark build. The scrim tokens deliberately do
   not follow the palette, so this pair is right under either ground.

   Hover cannot use --water-soft for the same reason — it means "pale" on a
   light palette and "dark" on a dark one, so it cannot safely sit behind a
   fixed label colour. It dims the pill instead. */
.hero-dark .hero .btn--primary { background: rgb(var(--on-scrim-rgb)); color: rgb(var(--scrim-rgb)); }
.hero-dark .hero .btn--primary:hover { background: rgb(var(--on-scrim-rgb) / 0.88); color: rgb(var(--scrim-rgb)); }
.hero-dark .hero .link-arrow {
  color: rgb(var(--on-scrim-rgb));
  border-bottom-color: rgb(var(--on-scrim-rgb) / 0.45);
}
.hero-dark .hero .link-arrow:hover { color: var(--water-scrim); border-bottom-color: var(--water-scrim); }

.hero-dark .scroll-cue { color: rgb(var(--on-scrim-rgb) / 0.72); }
.hero-dark .scroll-cue__line { background: rgb(var(--on-scrim-rgb) / 0.3); }
.hero-dark .scroll-cue__line::after { background: var(--water-scrim); }

/* The header floats over the hero, so while it is transparent it has to be
   light too. It flips back the moment it picks up its plate on scroll. */
.hero-dark .site-header:not(.is-stuck) .wordmark,
.hero-dark .site-header:not(.is-stuck) .nav a { color: rgb(var(--on-scrim-rgb)); }
.hero-dark .site-header:not(.is-stuck) .wordmark span { color: var(--water-scrim); }
.hero-dark .site-header:not(.is-stuck) .nav .btn--ghost {
  border-color: rgb(var(--on-scrim-rgb) / 0.45);
  color: rgb(var(--on-scrim-rgb));
}
.hero-dark .site-header:not(.is-stuck) .nav .btn--ghost:hover {
  background: rgb(var(--on-scrim-rgb));
  border-color: rgb(var(--on-scrim-rgb));
  color: var(--ink);
}
.hero-dark .site-header:not(.is-stuck) .nav-toggle span { background: rgb(var(--on-scrim-rgb)); }

/* The bracket marker has to survive on the scrim as well. */
.hero-dark .hero .is-todo { color: var(--todo-scrim); background: rgb(var(--todo-rgb) / 0.2); }

.hero__inner { position: relative; width: 100%; min-width: 0; z-index: 1; }
.hero__eyebrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.9rem;
  margin-bottom: clamp(1.5rem, 3vw, 2.5rem);
}
.hero__eyebrow .dot { width: 4px; height: 4px; border-radius: 50%; background: var(--water); }

.hero h1 { max-width: 18ch; }
.hero__lede { margin-top: clamp(1.5rem, 3vw, 2.25rem); max-width: min(42ch, 100%); }

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem 1.5rem;
  margin-top: clamp(2rem, 4vw, 3rem);
}

.hero__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 2.25rem;
  margin-top: clamp(2.5rem, 5vw, 4rem);
  padding-top: 1.5rem;
  border-top: 1px solid var(--hair);
  font-size: 0.875rem;
  color: var(--muted);
}
.hero__meta strong { color: var(--ink); font-weight: 500; }
.stars { color: var(--water); letter-spacing: 0.12em; }

.scroll-cue {
  position: absolute;
  right: var(--gutter);
  bottom: clamp(3rem, 7vw, 6rem);
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
.scroll-cue__line {
  width: 1px;
  height: 56px;
  background: var(--hair-strong);
  position: relative;
  overflow: hidden;
}
.scroll-cue__line::after {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 40%;
  background: var(--water);
  animation: cue 2.6s var(--ease) infinite;
}
@keyframes cue {
  0%   { transform: translateY(-100%); }
  55%  { transform: translateY(250%); }
  100% { transform: translateY(250%); }
}

/* --- Proof band -----------------------------------------------------------
   Directly under the hero, and it replaced a scrolling strip of trade words.
   The strip was a fashion device with no job. This is the most persuasive
   thing on a trade page — licence, insurance, years, rating — and burying it
   two thirds down the page, which is where it used to live, was the single
   biggest layout mistake in the first version.

   Four cells on one hairline grid. No boxes, no icons, no badges.
   -------------------------------------------------------------------------- */
.proof {
  border-block: 1px solid var(--hair);
  background: var(--chalk);
}
.proof__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: var(--hair);
}
.proof__item {
  background: var(--chalk);
  padding: clamp(1.25rem, 2.4vw, 2rem) clamp(1rem, 2vw, 1.75rem);
}
.proof__item dt {
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.55rem;
}
.proof__item dd {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.15rem, 2vw, 1.6rem);
  letter-spacing: -0.02em;
  color: var(--ink);
  line-height: 1.15;
}
.proof__item dd .stars { font-size: 0.8em; }

/* --- Statement ------------------------------------------------------------
   The lead line only. This was `.statement p`, which also caught the two
   paragraphs in the footnote below it and set body copy at 44px. It went
   unnoticed while the lead line was the only paragraph anyone looked at.
   -------------------------------------------------------------------------- */
.statement__lead {
  font-family: var(--font-accent);
  font-weight: 600;
  font-size: clamp(1.4rem, 2.7vw, 2.35rem);
  line-height: 1.26;
  letter-spacing: -0.02em;
  color: var(--ink);
  max-width: min(26ch, 100%);
}
.statement__foot {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: clamp(1.5rem, 4vw, 4rem);
  margin-top: clamp(3rem, 6vw, 5rem);
  padding-top: 2rem;
  border-top: 1px solid var(--hair);
}

/* --- Gallery -------------------------------------------------------------- */
/* Even rows, all six frames the same size. This was a staggered, offset,
   twelve-column set with frames pushed up and down against each other —
   a portfolio device that says the pictures are the point. Here the pictures
   are evidence, and evidence is easier to read in a grid. */
.gallery__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1.5rem, 3vw, 2.75rem) clamp(1rem, 2vw, 2rem);
}

.frame__media {
  position: relative;
  overflow: hidden;
  background: var(--chalk);
  aspect-ratio: 4 / 5;
}
/* The picture is oversized inside its window so it can be parallaxed within
   the frame without ever exposing an edge. */
.frame__img {
  position: absolute;
  inset: -12% 0;
  width: 100%;
  height: 124%;
  object-fit: cover;
  will-change: transform;
}

/* The WebGL hover canvas. One canvas for the whole gallery: it parks itself
   over whichever frame the cursor is on and is invisible otherwise, so the
   grid stays plain <img> elements until someone reaches for one. */
.gl-thumbs {
  position: fixed;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.4s var(--ease);
}
.gl-thumbs.is-live { opacity: 1; }
.frame__media { cursor: crosshair; }

/* Stacked, not a two-column row: a narrow frame's title would otherwise wrap
   into three words a line while the meta wrapped beside it. */
.frame__cap {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin-top: 1.1rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--hair);
}
.frame__cap h3 { font-size: clamp(1.05rem, 1.4vw, 1.3rem); }

/* --- The round photo mask ------------------------------------------------
   World 6's signature, opted into with 'gallery_shape' => 'round'. Every
   other world leaves the 4:5 window alone.

   A circle crops harder than a rectangle and it crops from the corners in,
   so the subject has to sit in the middle of the shot or the mask eats it.
   The shot list is where that gets said to whoever takes the photographs;
   this rule cannot rescue a badly framed one.

   The caption loses its rule here. A hairline running flat under a circle
   reads as a mistake, and centring the caption under the mask is the whole
   reason the device looks made rather than defaulted. */
.gallery__grid--round .frame__media {
  aspect-ratio: 1;
  border-radius: 50%;
}
.gallery__grid--round .frame__cap {
  align-items: center;
  text-align: center;
  border-top: 0;
  padding-top: 0;
}

/* --- Services ------------------------------------------------------------- */
.services__layout {
  display: grid;
  grid-template-columns: minmax(0, 4fr) minmax(0, 7fr);
  gap: clamp(2rem, 6vw, 6rem);
  align-items: start;
}
.services__aside { position: sticky; top: clamp(6rem, 14vh, 9rem); }
.services__count {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(4rem, 9vw, 8rem);
  line-height: 0.85;
  letter-spacing: -0.045em;
  color: var(--water-soft);
  font-variant-numeric: tabular-nums;
  display: block;
  margin-top: clamp(2rem, 4vw, 3rem);
}

.service {
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 1fr);
  gap: clamp(1rem, 2.5vw, 2.5rem);
  padding-block: clamp(2rem, 3.5vw, 3rem);
  border-top: 1px solid var(--hair);
}
.service:last-child { border-bottom: 1px solid var(--hair); }
.service__no {
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  padding-top: 0.5rem;
}
.service h3 { margin-bottom: 0.7rem; }
.service p { max-width: min(52ch, 100%); }
.service__tags { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 1.1rem; }
.service__tags li {
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  color: var(--ink-2);
  padding: 0.3rem 0.75rem;
  border: 1px solid var(--hair);
  border-radius: 999px;
  background: var(--paper-2);
}

/* --- Priced by size, not by service --------------------------------------
   The second World 6 signature. Renders inside the services section, after
   the service list if there is one and on its own if there is not, so the
   skeleton keeps its fixed order either way.

   A real table, because this is tabular data and a screen reader should be
   told so. Prices are tabular-nums and right-aligned: a column of numbers
   that does not line up is the thing that makes a small site look homemade.

   Bracketed prices come through as markers, which is what stops an
   unconfirmed number reading as a quote. */
.sizes { margin-top: clamp(2rem, 4vw, 3.5rem); }
.sizes__lede {
  max-width: min(52ch, 100%);
  margin-bottom: clamp(1.25rem, 2.5vw, 2rem);
}
.sizes__table {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}
.sizes__table thead th {
  font-family: var(--font-body);
  font-size: 0.75rem;
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  padding-bottom: 0.9rem;
}
.sizes__table th,
.sizes__table td { text-align: right; }
.sizes__table th:first-child,
.sizes__table td:first-child { text-align: left; }
.sizes__table tbody th,
.sizes__table tbody td {
  border-top: 1px solid var(--hair);
  padding-block: clamp(1rem, 2vw, 1.5rem);
  font-weight: 400;
  /* Baseline, so the price sits on the same line as the size it belongs to
     rather than floating in the middle of a two-line cell. */
  vertical-align: baseline;
}
.sizes__table tbody tr:last-child th,
.sizes__table tbody tr:last-child td { border-bottom: 1px solid var(--hair); }
/* The size cell stays a table cell. Making it a flex container takes it out
   of the row's layout and its hairline stops lining up with the ones beside
   it, which is visible as a broken rule at every breakpoint. */
.sizes__size { padding-right: 1rem; }
.sizes__detail {
  display: block;
  margin-top: 0.25rem;
  font-size: 0.8rem;
  color: var(--muted);
}
.sizes__note {
  margin-top: clamp(1rem, 2vw, 1.5rem);
  font-size: 0.85rem;
  color: var(--muted);
  max-width: min(52ch, 100%);
}

/* --- World 3: the gallery run edge to edge ---------------------------------
   'gallery_layout' => 'bleed'. The grid leaves the text column (index.php
   closes the wrap early) and the six photographs meet on hairline gutters
   against the ground, with no caption rule and no box. The caption sits ON
   the photograph under its own scrim, so it reads against the fixed scrim
   pair rather than the palette, exactly as the plate does.

   Column counts are inherited from .gallery__grid, including the 860px and
   720px steps, so this block only changes gutters and the caption. */
.gallery__grid--bleed { gap: 2px; }
/* margin: 0 because .frame is a <figure>, and the browser's default figure
   margin is 40px either side. The template never reset it, so every frame in
   every gallery sits 40px in from its column. On the bleed grid that shows as
   an 80px gutter instead of 2px. Not reset on the base .frame yet: that would
   widen the gallery on every demo already built. */
.gallery__grid--bleed .frame { position: relative; margin: 0; }
.gallery__grid--bleed .frame__cap {
  position: absolute;
  inset: auto 0 0 0;
  z-index: 1;
  margin: 0;
  padding: clamp(3rem, 6vw, 4.5rem) clamp(1rem, 2vw, 1.5rem) clamp(1rem, 2vw, 1.4rem);
  border-top: 0;
  background: linear-gradient(to top, rgb(var(--scrim-rgb) / 0.88), rgb(var(--scrim-rgb) / 0));
  pointer-events: none;
}
.gallery__grid--bleed .frame__cap h3 { color: rgb(var(--on-scrim-rgb)); }
.gallery__grid--bleed .frame__cap .caption { color: rgb(var(--on-scrim-rgb) / 0.8); }
.gallery__grid--bleed .is-todo { color: var(--todo-scrim); background: rgb(var(--todo-rgb) / 0.2); }

/* --- World 3: the price column, pinned beside a portrait -------------------
   'price_list' in config/site.php. One tall photograph holds the left half of
   the screen while the priced list scrolls past on the right — a photograph
   and a piece of type with nothing between them.

   The pin is position: sticky, deliberately not a ScrollTrigger pin. It needs
   no script, so it holds with JavaScript off; it moves nothing on its own, so
   it needs no reduced-motion exception; and it cannot leave a gap if the
   motion layer dies half way. body uses overflow-x: clip rather than hidden
   for exactly this reason — hidden would break the stick.

   No rules between rows. The list is separated by air and by type size, which
   is the bare edge treatment World 3 asks for. Prices are tabular-nums and
   never wrap, so a column of them lines up. */
.pricecol {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: start;
}
.pricecol__media {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
  background: var(--chalk);
}
.pricecol__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* On the photograph, so it carries its own plate — same as .about__note. */
.pricecol__note {
  position: absolute;
  left: 0.9rem;
  right: 0.9rem;
  bottom: 0.9rem;
  z-index: 2;
  background: rgb(var(--scrim-rgb) / 0.82);
  padding: 0.7rem 0.9rem;
  color: rgb(var(--on-scrim-rgb) / 0.78);
}
.pricecol__note .is-todo { color: var(--todo-scrim); background: rgb(var(--todo-rgb) / 0.2); }

.pricecol__body {
  min-width: 0;
  min-height: 100svh;
  padding: var(--pad) var(--gutter);
  max-width: 46rem;
}
.pricecol__group { margin-top: clamp(2.5rem, 5vw, 4rem); }
.pricecol__group h3 {
  font-family: var(--font-body);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.75rem;
}
.pricecol__list li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  padding-block: clamp(0.85rem, 1.6vw, 1.25rem);
}
.pricecol__name,
.pricecol__price {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  font-size: clamp(1.3rem, 2.3vw, 2rem);
  line-height: 1.12;
  letter-spacing: -0.015em;
  color: var(--ink);
}
.pricecol__name { min-width: 0; }
.pricecol__price {
  color: var(--water);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.pricecol__detail {
  display: block;
  margin-top: 0.35rem;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 0.9375rem;
  letter-spacing: 0;
  color: var(--muted);
}
.pricecol__foot {
  margin-top: clamp(2rem, 4vw, 3rem);
  font-size: 0.9375rem;
  color: var(--muted);
  max-width: min(52ch, 100%);
}

@media (max-width: 1024px) {
  .pricecol { grid-template-columns: minmax(0, 1fr); }
  .pricecol__media { position: relative; height: auto; aspect-ratio: 4 / 5; max-height: 78svh; }
  .pricecol__body { min-height: 0; }
}

/* --- Full-bleed interstitial ---------------------------------------------- */
.plate {
  position: relative;
  min-height: clamp(60vh, 78vh, 90vh);
  display: grid;
  place-items: center;
  overflow: hidden;
  isolation: isolate;
  padding-inline: var(--gutter);
}
.plate__media { position: absolute; inset: -14% 0; z-index: -1; }
.plate__media img { width: 100%; height: 100%; object-fit: cover; will-change: transform; }
.plate::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(180deg, rgb(var(--scrim-rgb) / 0.40), rgb(var(--scrim-rgb) / 0.60));
}
.plate blockquote {
  margin: 0;
  text-align: center;
  font-weight: 600;
  color: rgb(var(--on-scrim-rgb));
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 3.4vw, 3.1rem);
  line-height: 1.22;
  letter-spacing: -0.02em;
  max-width: min(20ch, 100%);
}
.plate cite {
  display: block;
  margin-top: 2rem;
  font-family: var(--font-body);
  font-style: normal;
  font-size: 0.75rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgb(var(--on-scrim-rgb) / 0.72);
}

/* --- About ---------------------------------------------------------------- */
.about { background: var(--chalk); }
.about__layout {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: clamp(2rem, 6vw, 6rem);
  align-items: center;
}
.about__media {
  position: relative;
  overflow: hidden;
  aspect-ratio: 4 / 5;
  background: var(--stone);
}
.about__media img {
  position: absolute;
  inset: -10% 0;
  width: 100%;
  height: 120%;
  object-fit: cover;
  will-change: transform;
}
/* Sits on top of a photograph, so it carries its own plate rather than
   trusting whatever happens to be behind it. */
.about__note {
  position: absolute;
  left: 0.9rem;
  right: 0.9rem;
  bottom: 0.9rem;
  z-index: 2;
  background: rgb(var(--scrim-rgb) / 0.82);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  padding: 0.7rem 0.9rem;
}
.about h2 { margin-bottom: 1.75rem; }
.stack > * + * { margin-top: 1.25rem; }

.creds {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1px;
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
  background: var(--hair);
  border: 1px solid var(--hair);
}
.cred { background: var(--chalk); padding: 1.35rem 1.25rem; }
.cred dt {
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.6rem;
}
.cred dd {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.15rem, 1.8vw, 1.5rem);
  color: var(--ink);
  letter-spacing: -0.02em;
}

/* --- Testimonials --------------------------------------------------------- */
/* Three across, all visible at once. This was a pinned rail that dragged
   sideways while the page held still — good fun, and the wrong call: reviews
   are the proof, and proof should be readable at a glance rather than ridden
   through. The pin also cost about 1,700px of scrolling to show five cards. */
/* minmax(0, 1fr), NOT 1fr. A grid item is implicitly min-width: auto, which
   resolves to min-content, so a plain 1fr track refuses to shrink below the
   widest thing inside the card and the row grows past its container. At 375px
   the tracks resolved to 263px + 239px inside a 335px wrap, which put the
   middle review at x=339 — off the screen entirely, and invisible rather than
   scrollable, because body carries overflow-x: clip.

   The phone check everyone runs does not catch this. document.scrollWidth
   still equals innerWidth, because the clip is doing its job; the card is
   simply gone. Found 2026-09-12 building demos/minos, and it was true of
   every demo built before it. */
.quotes__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1rem, 2vw, 1.75rem);
}
.quote {
  background: var(--paper-2);
  border: 1px solid var(--hair);
  padding: clamp(1.5rem, 2.4vw, 2.25rem);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 1.75rem;
}
.quote__mark {
  font-family: var(--font-display);
  font-size: 3.5rem;
  line-height: 0.6;
  color: var(--water-soft);
  display: block;
  margin-bottom: 1.5rem;
}
.quote p {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.1rem, 1.55vw, 1.4rem);
  line-height: 1.42;
  letter-spacing: -0.015em;
  color: var(--ink);
}
.quote footer {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--hair);
}
.quote cite { font-style: normal; font-weight: 500; color: var(--ink); font-size: 0.9375rem; }

/* --- Areas ---------------------------------------------------------------- */
.areas { display: flex; flex-wrap: wrap; gap: 0.5rem 0; margin-top: 2rem; }
.areas li {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.35rem, 3vw, 2.4rem);
  letter-spacing: -0.02em;
  color: var(--ink);
  padding-right: 1.1rem;
  margin-right: 1.1rem;
  border-right: 1px solid var(--hair);
  line-height: 1.25;
}
.areas li:last-child { border-right: 0; }
.areas__note { margin-top: 2rem; }

/* --- FAQ -------------------------------------------------------------------
   'faq' in config/site.php, off by default. Added 2026-09-15.

   A ruled list of <details>, set in the right-hand column of the section head
   so the questions line up under the lede instead of running the full width.
   It stacks to one column with the head, at 1024px.

   No script. The marker is a + drawn in CSS that turns to a cross when open,
   so there is no icon font and no SVG request. The answer is deliberately NOT
   animated open: <details> cannot transition its height without a script, and
   a faked one that clips its text is worse than one that simply appears.
   Reduced motion already stills the marker, through the block at the foot of
   this file. */
.faq {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  column-gap: clamp(1.5rem, 4vw, 4rem);
}
.faq__item { grid-column: 2; border-top: 1px solid var(--hair); }
.faq__item:last-child { border-bottom: 1px solid var(--hair); }
.faq__item summary {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  padding-block: clamp(1.25rem, 2.4vw, 1.75rem);
  cursor: pointer;
  list-style: none;
}
.faq__item summary::-webkit-details-marker { display: none; }
.faq__item summary h3 { font-size: clamp(1.1rem, 1.6vw, 1.35rem); line-height: 1.25; }
.faq__item summary::after {
  content: "+";
  flex: none;
  font-family: var(--font-body);
  font-weight: 300;
  font-size: 1.9rem;
  line-height: 0.6;
  color: var(--water);
  transition: transform 0.35s var(--ease);
}
.faq__item[open] summary::after { transform: rotate(45deg); }
.faq__item summary:focus-visible { outline: 2px solid var(--water); outline-offset: 4px; }
.faq__a {
  padding-bottom: clamp(1.25rem, 2.4vw, 1.75rem);
  max-width: min(56ch, 100%);
}

/* --- Enquiry -------------------------------------------------------------- */
.enquiry { background: var(--ink); color: rgb(var(--paper-rgb) / 0.72); }
.enquiry h2, .enquiry .display { color: var(--paper); }
/* Small text on a flipped surface.
   --muted is tuned against the page ground, so it is the wrong colour the
   moment a section inverts: on the light skeleton a caption inside .enquiry
   sat at 3.26:1, and on a dark palette the same caption lands at 2.4:1 on
   the now-light enquiry plate. Every small-text role that can appear inside
   an inverted section has to be restated against the ground it is actually
   on. Found by auditing every text node on the rendered page, 2026-09-11. */
.enquiry .eyebrow,
.enquiry .caption,
.enquiry .muted { color: rgb(var(--paper-rgb) / 0.62); }

/* The about-page note is not an inverted section, it is a scrim over a
   photograph, so it reads against --on-scrim rather than against the page. */
.about__note,
.about__note .caption { color: rgb(var(--on-scrim-rgb) / 0.78); }
.enquiry .lede { color: rgb(var(--paper-rgb) / 0.78); }
.enquiry .head__index::after { background: rgb(var(--paper-rgb) / 0.18); }
.enquiry__layout {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: clamp(2.5rem, 6vw, 6rem);
  align-items: start;
}
.enquiry__direct {
  margin-top: clamp(2rem, 4vw, 3rem);
  padding-top: 2rem;
  border-top: 1px solid rgb(var(--paper-rgb) / 0.16);
}
.enquiry__phone {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2rem, 4.4vw, 3.4rem);
  letter-spacing: -0.03em;
  color: var(--paper);
  text-decoration: none;
  display: inline-block;
  transition: color 0.4s var(--ease);
}
.enquiry__phone:hover { color: var(--water-alt); }

/* --- Forms ----------------------------------------------------------------
   The form appears twice in this system: on the dark enquiry band of the home
   page, and on the light contact page. It is one set of rules driven by four
   variables that the dark band overrides, so neither copy is a fork.
   -------------------------------------------------------------------------- */
.form {
  --f-label: var(--muted);
  --f-text:  var(--ink);
  --f-line:  var(--hair-strong);
  --f-focus: var(--water);
  --f-ghost: rgb(var(--ink-rgb) / 0.34);
}
.enquiry .form {
  --f-label: rgb(var(--paper-rgb) / 0.6);
  --f-text:  var(--paper);
  --f-line:  rgb(var(--paper-rgb) / 0.22);
  --f-focus: var(--water-alt);
  --f-ghost: rgb(var(--paper-rgb) / 0.32);
}

.field { margin-bottom: 1.5rem; }
.field label {
  display: block;
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--f-label);
  margin-bottom: 0.6rem;
}
.field .req { color: var(--f-focus); }
.field input, .field textarea, .field select {
  width: 100%;
  font: inherit;
  color: var(--f-text);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--f-line);
  padding: 0.85rem 0;
  border-radius: 0;
  transition: border-color 0.4s var(--ease);
}
.field textarea { min-height: 120px; resize: vertical; line-height: 1.6; }
.field input:focus, .field textarea:focus, .field select:focus {
  outline: none;
  border-bottom-color: var(--f-focus);
}
.field input::placeholder, .field textarea::placeholder { color: var(--f-ghost); }
.field select {
  appearance: none;
  -webkit-appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 12px center, right 7px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 2rem;
}
.field select option { background: var(--paper); color: var(--ink); }
.enquiry .field select option { background: var(--ink); color: var(--paper); }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 0 1.5rem; }

.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.form__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem 1.5rem;
  margin-top: 2rem;
}
.enquiry .form__actions .btn--primary { background: var(--paper); color: var(--ink); }
.enquiry .form__actions .btn--primary:hover { background: var(--water-alt); }
.form__note { font-size: 0.8125rem; color: var(--f-label); max-width: min(34ch, 100%); }
.form__status {
  margin-top: 1.5rem;
  padding: 1rem 1.25rem;
  border: 1px solid var(--f-line);
  font-size: 0.9375rem;
  color: var(--f-text);
}
.form__status[hidden] { display: none; }
.form__status.is-ok    { border-color: var(--f-focus); }
.form__status.is-error { color: var(--f-focus); }
.btn[aria-busy="true"] { opacity: 0.6; pointer-events: none; }

/* --- Footer --------------------------------------------------------------- */
.site-footer {
  background: var(--ink);
  color: rgb(var(--paper-rgb) / 0.62);
  padding-block: clamp(2.5rem, 5vw, 4rem);
  font-size: 0.875rem;
  border-top: 1px solid rgb(var(--paper-rgb) / 0.12);
}
.site-footer__inner {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 2rem;
  justify-content: space-between;
  align-items: baseline;
}
.site-footer strong { color: var(--paper); font-weight: 500; }
.site-footer a { text-decoration: none; transition: color 0.35s var(--ease); }
.site-footer a:hover { color: var(--water-alt); }

/* --- The call bar ---------------------------------------------------------
   Phone only, and it is the single highest-value element on a trade site.
   Someone standing over a leaking pipe is not scrolling back up to find a
   number. It is fixed to the bottom of the screen from the moment the hero
   leaves, and .js hides it until then so it never covers the hero's own
   buttons. With JavaScript off it is simply always there, which is fine.
   -------------------------------------------------------------------------- */
.callbar { display: none; }

@media (max-width: 720px) {
  .callbar {
    position: fixed;
    inset: auto 0 0 0;
    z-index: 85;
    display: grid;
    grid-template-columns: 1.6fr 1fr;
    background: var(--ink);
    border-top: 1px solid rgb(var(--paper-rgb) / 0.14);
    padding-bottom: env(safe-area-inset-bottom, 0);
  }
  .callbar a {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.55em;
    padding: 1.05rem 1rem;
    font-size: 0.9688rem;
    font-weight: 600;
    letter-spacing: 0.01em;
    text-decoration: none;
    color: var(--paper);
  }
  .callbar a:first-child { background: var(--water); }
  .callbar a + a { border-left: 1px solid rgb(var(--paper-rgb) / 0.14); }

  /* So the last thing on the page is not permanently under the bar. */
  body { padding-bottom: 4.25rem; }

  .js .callbar { transform: translateY(101%); transition: transform 0.4s var(--ease); }
  .js .callbar.is-up { transform: none; }
}

/* --- Skip link ------------------------------------------------------------ */
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--ink);
  color: var(--paper);
  padding: 0.9rem 1.4rem;
  text-decoration: none;
}
.skip:focus { left: 0; }


/* --- Inner pages ----------------------------------------------------------
   The home page carries the editorial skeleton. About, contact and the menu
   page are quieter documents built from these, and they are deliberately
   plainer: a visitor who has reached page two is reading, not being sold to.
   -------------------------------------------------------------------------- */
.page-head {
  padding-top: clamp(9rem, 18vh, 13rem);
  padding-bottom: clamp(2.5rem, 6vw, 5rem);
  border-bottom: 1px solid var(--hair);
}
.page-head h1 {
  font-size: clamp(2.5rem, 6.4vw, 5.5rem);
  line-height: 0.98;
  letter-spacing: -0.03em;
}
.page-head__lede { margin-top: 1.75rem; max-width: min(52ch, 100%); }

.split {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: clamp(1.5rem, 4vw, 4rem);
  align-items: start;
}
.mt { margin-top: 1.75rem; }

/* Contact facts. A ruled list, not a card. */
.details { margin-top: clamp(2rem, 4vw, 3rem); }
.detail {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  gap: 1rem 2rem;
  padding-block: 1.15rem;
  border-top: 1px solid var(--hair);
}
.detail:last-child { border-bottom: 1px solid var(--hair); }
.detail dt {
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
.detail dd { color: var(--ink); }
.detail dd a {
  text-decoration: none;
  border-bottom: 1px solid var(--hair-strong);
  transition: color 0.35s var(--ease), border-color 0.35s var(--ease);
}
.detail dd a:hover { color: var(--water); border-bottom-color: var(--water); }

.hours { margin-top: 1rem; }
.hours li {
  display: flex;
  justify-content: space-between;
  gap: 2rem;
  padding-block: 0.7rem;
  border-bottom: 1px solid var(--hair);
}
.hours li:last-child { border-bottom: 0; }
.hours span:last-child { color: var(--muted); font-variant-numeric: tabular-nums; }

/* A single quotation, given a page of its own to breathe in. */
.band {
  padding-block: clamp(4rem, 9vw, 7rem);
  background: var(--chalk);
  text-align: center;
}
.band blockquote {
  margin: 0 auto;
  font-weight: 600;
  max-width: min(24ch, 100%);
  font-family: var(--font-display);
  font-size: clamp(1.4rem, 3vw, 2.6rem);
  line-height: 1.24;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.band cite {
  display: block;
  margin-top: 1.75rem;
  font-family: var(--font-body);
  font-style: normal;
  font-size: 0.75rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Three short propositions on hairlines. The inner-page cousin of .service. */
.offers {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1px;
  margin-top: clamp(3rem, 6vw, 5rem);
  background: var(--hair);
  border-block: 1px solid var(--hair);
}
.offer { background: var(--paper); padding: clamp(1.75rem, 3vw, 2.5rem); }
.offer__mark {
  display: block;
  width: 22px;
  height: 1px;
  background: var(--water);
  margin-bottom: 1.5rem;
}
.offer h3 { margin-bottom: 0.6rem; }
.offer p { color: var(--muted); font-size: 0.9688rem; }

/* Menu / price list. Same rules as everything else: no boxes. */
.menu-group + .menu-group { margin-top: clamp(3rem, 6vw, 5rem); }
.menu-group h2 { margin-bottom: 1.75rem; }
.menu-list li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  padding-block: 1rem;
  border-bottom: 1px solid var(--hair);
}
.menu-list li:last-child { border-bottom: 0; }
.menu-list li span:last-child { color: var(--muted); font-variant-numeric: tabular-nums; }
.menu-list__note { display: block; color: var(--muted); font-size: 0.875rem; }
.menu-note { margin-top: 2rem; max-width: min(60ch, 100%); font-size: 0.9375rem; }

.form-section .split { align-items: start; }

@media (max-width: 1024px) {
  .split, .detail { grid-template-columns: 1fr; }
}

/* --- Motion primitives ----------------------------------------------------
   All of them are scoped to .js and all of them fail open: with JavaScript
   off, or if the motion script never runs, everything below is visible.
   -------------------------------------------------------------------------- */
.js .reveal { opacity: 0; transform: translateY(26px); }
.js .reveal.is-in {
  opacity: 1;
  transform: none;
  transition: opacity 0.9s var(--ease-out), transform 0.9s var(--ease-out);
}

/* Image mask: the frame opens from its bottom edge while the picture inside
   settles back from a slight scale. Together they read as a curtain rather
   than a fade. */
.js .mask { clip-path: inset(0 0 100% 0); }
.js .mask.is-in { clip-path: inset(0 0 0 0); transition: clip-path 1.15s var(--ease-out); }
.js .mask img { transform: scale(1.12); }
.js .mask.is-in img { transform: scale(1); transition: transform 1.4s var(--ease-out); }

/* The curtain has to keep the circle's edge while it sweeps. A plain inset
   clip cuts a flat chord across a round mask, which reads as a rendering
   fault rather than a reveal. */
.js .gallery__grid--round .mask { clip-path: inset(0 0 100% 0 round 50%); }
.js .gallery__grid--round .mask.is-in { clip-path: inset(0 0 0 0 round 50%); }

/* --- Unverified content ---------------------------------------------------
   Anything the client has not confirmed is wrapped in [square brackets] in
   config/site.php and renders like this: impossible to miss, impossible to
   ship by accident, and never a live link.
   -------------------------------------------------------------------------- */
.is-todo {
  color: var(--todo);
  background: rgb(var(--todo-rgb) / 0.12);
  border: 1px dashed rgb(var(--todo-rgb) / 0.6);
  border-radius: 3px;
  padding: 0.05em 0.4em;
  font-family: var(--font-body);
  font-size: 0.9em;
  font-style: normal;
  letter-spacing: 0;
  text-transform: none;
  /* A marker that wraps keeps its box on both lines instead of losing the
     border down one side. */
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

/* At display sizes the filled box shouts louder than the heading it sits in.
   Same signal, drawn as a rule instead of a chip. */
.areas li.is-todo {
  background: none;
  border: 0;
  border-right: 1px solid var(--hair);
  border-bottom: 1px dashed rgb(var(--todo-rgb) / 0.75);
  border-radius: 0;
  padding: 0 1.1rem 0.1rem 0;
  /* Restated rather than inherited: the size lives on the li itself, so
     `inherit` here would reach past it to the list. */
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 3vw, 2.4rem);
}
.areas li.is-todo:last-child { border-right: 0; }
/* The marker on a surface that is not the page ground. Two cases, and they
   are not the same case — conflating them is what put a dark-orange bracket
   on a near-white scrim at 3.26:1 once the palette went dark.

   Inverted SECTIONS flip with the palette, so they take --todo-alt. The
   footer belongs here and was missing until 2026-09-11; on a light palette
   nobody could see the gap, because the footer and the enquiry section were
   both dark anyway. Any new inverted section has to be added to this list.

   Photo SCRIMS do not flip, so they take --todo-scrim, which is always the
   light orange. */
.enquiry .is-todo, .site-footer .is-todo {
  color: var(--todo-alt);
  background: rgb(var(--todo-rgb) / 0.2);
}
.plate .is-todo, .about__note .is-todo {
  color: var(--todo-scrim);
  background: rgb(var(--todo-rgb) / 0.2);
}
.btn.is-todo, a.is-todo { pointer-events: none; }

/* A placeholder BUTTON keeps the shape of a button while it carries the
   marker. .is-todo on its own sets chip padding and a smaller size, which
   collapsed the pill to a tag. Needed once booking_button() started rendering
   an unconfirmed booking link as a disabled Book button in four places.
   Added 2026-09-13 on demos/king-of-cuts. */
.btn.is-todo {
  padding: 1.05rem 1.9rem;
  border-radius: 999px;
  font-size: 0.9375rem;
  transform: none;
}
.nav .btn.is-todo { padding: 0.7rem 1.35rem; font-size: 0.8438rem; }
/* The dark hero and the transparent header over it both paint their buttons
   white. Without these the marker lost its orange and read as a live button. */
.hero-dark .hero .btn.is-todo,
.hero-dark .site-header:not(.is-stuck) .nav .btn.is-todo {
  color: var(--todo-scrim);
  background: rgb(var(--todo-rgb) / 0.2);
  border-color: rgb(var(--todo-rgb) / 0.6);
}
.pricecol__cta { margin-top: clamp(1.5rem, 3vw, 2.25rem); }
.enquiry__book { margin-bottom: clamp(2rem, 4vw, 3rem); }
/* A live Book button on the enquiry band. That band is the inverted surface,
   so the ordinary primary pill would vanish into it. */
.enquiry__book .btn--primary:not(.is-todo) { background: var(--paper); color: var(--ink); }

/* --- Reduced motion -------------------------------------------------------
   Everything still arrives. It just arrives immediately.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .js .reveal { opacity: 1; transform: none; }
  .js .mask,
  .js .gallery__grid--round .mask { clip-path: none; }
  .js .mask img { transform: none; }
  .scroll-cue { display: none; }
}

/* --- Responsive ----------------------------------------------------------- */
@media (max-width: 1024px) {
  .head,
  .statement__foot,
  .services__layout,
  .about__layout,
  .enquiry__layout,
  .faq { grid-template-columns: 1fr; }
  .faq__item { grid-column: 1; }
  .services__aside { position: static; }
  .services__count { display: none; }
  .about__media { order: -1; aspect-ratio: 3 / 2; }
}

@media (max-width: 860px) {
  .gallery__grid  { grid-template-columns: 1fr 1fr; }
  /* The size table is the one block that cannot reflow into a column:
     a price belongs beside the size it applies to. Tighten it instead. */
  .sizes__table thead th { font-size: 0.68rem; letter-spacing: 0.1em; }
  .sizes__table tbody th,
  .sizes__table tbody td { padding-block: 0.9rem; }
  .sizes__size { padding-right: 0.5rem; }
  .quotes__grid   { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .proof__grid    { grid-template-columns: 1fr 1fr; }
}

/* Two review cards side by side stop being readable somewhere around here — a
   verbatim Google review is three or four sentences, and at 160px of measure
   it sets as a column of one-word lines. One card per row from 620px down,
   which also removes any remaining chance of the row overrunning. */
@media (max-width: 620px) {
  .quotes__grid { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 720px) {
  .nav {
    position: fixed;
    inset: 0 0 auto;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
    background: var(--paper);
    padding: 5.5rem var(--gutter) 2.5rem;
    border-bottom: 1px solid var(--hair);
    transform: translateY(-101%);
    transition: transform 0.6s var(--ease);
  }
  .nav[data-open="true"] { transform: none; }
  .nav a { font-family: var(--font-display); font-size: 1.6rem; }
  .nav .btn { margin-top: 1rem; }

  .nav-toggle {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    width: 40px;
    height: 40px;
    padding: 0;
    background: none;
    border: 0;
    cursor: pointer;
    z-index: 2;
  }
  .nav-toggle span {
    display: block;
    height: 1px;
    width: 22px;
    background: var(--ink);
    transition: transform 0.4s var(--ease), opacity 0.3s var(--ease);
  }
  .nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
  .nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
  .nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

  .hero { min-height: 92svh; }
  .hero h1 { max-width: 100%; }
  .scroll-cue { display: none; }
  .gallery__grid { grid-template-columns: 1fr; }
  .frame:nth-child(1) { grid-column: span 1; }
  .service { grid-template-columns: 1fr; gap: 0.75rem; }
  .service__no { padding-top: 0; }
  .field-row { grid-template-columns: 1fr; }

  .areas li { font-size: 1.35rem; }
}

/* --- The dark band's borrowed pieces ---------------------------------------
   .hours and .detail are inner-page components that the home page's enquiry
   band reuses. Their hairlines and muted text are drawn for paper, so they
   are restated here for ink rather than duplicated as a second component.
   -------------------------------------------------------------------------- */
.enquiry .hours li { border-bottom-color: rgb(var(--paper-rgb) / 0.14); }
.enquiry .hours span:last-child { color: rgb(var(--paper-rgb) / 0.62); }
.enquiry .detail { border-top-color: rgb(var(--paper-rgb) / 0.14); }
.enquiry .detail:last-child { border-bottom-color: rgb(var(--paper-rgb) / 0.14); }
.enquiry .detail dt { color: rgb(var(--paper-rgb) / 0.62); }
.enquiry .detail dd { color: var(--paper); }
