/* ===========================================================================
   Willamette Landscaping & Tree Services.
   One stylesheet. No framework, no reset library, no external request.

   Written to DESIGN.md. Components appear once each, in the order of that
   file's section 8 inventory, under their own comment header.

   THE PALETTE IS MEASURED, NOT CHOSEN. Every pair below was resolved from
   OKLCH to sRGB and scored with the WCAG formula before it was written down
   (tools/build.py prints nothing about this; the numbers came out of the
   build report's contrast solve). Three of DESIGN.md's starting values did
   not survive that and are recorded here as the final ones:

   1. `--gold-deep` was asked to be BOTH the pressed state of the gold button
      AND gold text on paper. Those two jobs pull in opposite directions and
      one token cannot do them: at oklch(0.72 0.14 85) it measures 2.33 to 1
      on paper, which fails AA by a long way, and taking it dark enough to
      pass (about L 0.50) would make the button's pressed state read as brown.
      Split into `--gold-press` (the pressed ground, 8.70 to 1 under
      `--on-gold`) and `--gold-ink` (gold as text on paper, 5.63 to 1 on
      `--paper` and 5.08 on `--paper-deep`).
   2. `--line` at oklch(0.86 0.015 120) is 1.42 to 1 on paper. That is fine
      for a decorative rule and NOT fine for the edge of a control, so
      `--line-strong` exists at 3.36 to 1 for anything a visitor can press.
   3. The form field's own border needed the same treatment against the card
      it sits on: `--field-line` clears 3.21 to 1 against `--forest-soft` and
      4.67 to 1 against the field's own fill.

   Measured ratios that matter, all AA or better:
     ink on paper            16.77      ink-soft on paper          7.76
     ink on paper deep       15.12      ink-soft on paper deep     7.00
     on-forest on forest     13.55      on-forest-dim on forest    8.50
     on-forest on forest deep 16.43     on-forest-dim on f deep   10.31
     on-forest on forest soft 10.52     on-forest-dim on f soft    6.60
     on-gold on gold         11.50      on-gold on gold press      8.70
     gold on forest           9.02      gold on forest soft        7.01
     gold-ink on paper        5.63      gold-ink on paper deep     5.08
     on-forest on field bg   15.29      field-dim on field bg      8.37

   `--on-forest` over this gold measures 1.50 to 1. DESIGN.md bans that pair
   by name and it appears nowhere on this site.
   =========================================================================== */

/* ------------------------------------------------------------------ tokens */
:root {
  /* --- the two grounds, both the client's own --- */
  --forest:        oklch(0.27 0.06 150);
  --forest-deep:   oklch(0.20 0.05 150);
  --forest-soft:   oklch(0.34 0.06 150);
  --on-forest:     oklch(0.97 0.01 110);
  --on-forest-dim: oklch(0.82 0.02 130);

  --paper:         oklch(0.975 0.008 95);
  --paper-deep:    oklch(0.94 0.012 95);
  --ink:           oklch(0.20 0.02 150);
  --ink-soft:      oklch(0.42 0.03 150);

  --line:          oklch(0.86 0.015 120);
  --line-strong:   oklch(0.62 0.03 120);
  --line-forest:   oklch(0.36 0.04 150);

  /* --- the action colour. Nothing else is gold. --- */
  --gold:          oklch(0.84 0.15 92);
  --gold-press:    oklch(0.76 0.15 88);
  --gold-ink:      oklch(0.50 0.10 85);
  --on-gold:       oklch(0.18 0.03 95);
  --focus:         var(--gold);

  /* --- the form field, always on a forest card --- */
  --field-bg:      oklch(0.23 0.05 150);
  --field-line:    oklch(0.62 0.045 150);
  --field-dim:     oklch(0.78 0.02 130);
  --bad:           oklch(0.76 0.15 35);

  /* --- the ground and ink the current section paints with. Every section
         sets these two and the components inside read them, so one component
         works on both grounds without a variant class per ground. --- */
  --ground:        var(--paper);
  --on-ground:     var(--ink);
  --on-ground-dim: var(--ink-soft);
  --rule:          var(--line);

  /* --- type --- */
  --serif: 'DM Serif Display', 'DM Serif Display fallback', Georgia, 'Times New Roman', serif;
  --sans:  'DM Sans', 'DM Sans fallback', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --mono:  'DM Mono', 'DM Mono fallback', ui-monospace, Menlo, monospace;

  --fs-0: 0.8rem;
  --fs-1: 1rem;
  --fs-2: 1.25rem;
  --fs-3: 1.5625rem;
  --fs-4: 2rem;
  --fs-5: 2.75rem;
  --fs-6: clamp(2.5rem, 2rem + 3vw, 4.5rem);
  --fs-7: clamp(3.5rem, 2rem + 6vw, 7rem);

  /* --- space. 1.5 ratio from 24px, as DESIGN.md section 6. --- */
  --sp-0: 4px;
  --sp-1: 7px;
  --sp-2: 11px;
  --sp-3: 16px;
  --sp-4: 24px;
  --sp-5: 36px;
  --sp-6: 54px;
  --sp-7: 81px;
  --sp-8: 122px;
  --sp-9: 182px;

  --w: 1240px;
  --w-prose: 64ch;
  --gut: clamp(20px, 4vw, 48px);

  --r-sm: 4px;
  --r-md: 10px;

  /* The ONE shadow on this site. DESIGN.md section 6. Nothing else casts. */
  --shadow: 0 1px 0 rgb(8 18 10 / .06), 0 12px 32px -12px rgb(8 18 10 / .35);

  --dock-h: 56px;
  --hd-h: 72px;
}

@media (min-width: 1024px) {
  :root { --hd-h: 84px; }
}

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

html {
  font-size: 17px;
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--hd-h) + 16px);
}
@media (min-width: 1024px) { html { font-size: 18px; } }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--fs-1);
  line-height: 1.55;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  overflow-wrap: break-word;
}

body.is-locked { overflow: hidden; }

img, picture, video, svg { max-width: 100%; }
img, video { height: auto; display: block; }

h1, h2, h3, h4, p, figure, ul, ol, dl, dd, blockquote { margin: 0; }
ul, ol { padding: 0; list-style: none; }

a { color: inherit; }
button { font: inherit; color: inherit; }

:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 3px;
  border-radius: 2px;
}

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 90;
  background: var(--gold); color: var(--on-gold);
  padding: var(--sp-2) var(--sp-4); font-weight: 600;
}
.skip:focus { left: var(--sp-3); top: var(--sp-3); }

.vh {
  position: absolute !important; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ------------------------------------------------------------- typesetting */
h1, h2, .display {
  font-family: var(--serif);
  font-weight: 400;
  letter-spacing: -0.01em;
  text-wrap: balance;
}
h1 { font-size: var(--fs-6); line-height: 1.05; }
h2 { font-size: var(--fs-4); line-height: 1.15; }
@media (min-width: 900px) { h2 { font-size: var(--fs-5); } }

h3 {
  font-family: var(--sans);
  font-size: var(--fs-3);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.005em;
}
h4 { font-family: var(--sans); font-size: var(--fs-2); font-weight: 600; line-height: 1.3; }

.lead {
  font-size: var(--fs-2);
  line-height: 1.45;
  color: var(--on-ground-dim);
  text-wrap: pretty;
}

.mono {
  font-family: var(--mono);
  font-size: var(--fs-0);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1.4;
}

.nb { white-space: nowrap; }

/* The 64ch measure applies to EVERY prose block, not only to one that also
   carries .wrap. A service page's body sits in a 7fr grid column, which is
   667px at 1440, and 667px at 18px runs about 75 characters a line. */
.prose { max-width: var(--w-prose); }
.prose p + p { margin-top: 1em; }
.prose p { text-wrap: pretty; }

/* -------------------------------------------------------------- containers */
.wrap {
  width: 100%;
  max-width: var(--w);
  margin-inline: auto;
  padding-inline: var(--gut);
}

/* DESIGN.md section 5 records that on the Warrick site `.prose` lost to
   `.wrap` by source order, so its 68ch measure never applied anywhere on
   twenty routes. Two classes beat one, whatever the order, so this cannot
   happen here. The checker reads the computed max-width on /about at 1440
   and asserts it is the ch value. */
.wrap.prose { max-width: var(--w-prose); }

/* --- escaping the container -------------------------------------------
   DESIGN.md wants at least four media blocks on the home page touching a
   viewport edge. That is done here with GRID RAILS rather than the usual
   negative margin, because the negative margin version is written
   `calc(50% - 50vw)` and `100vw` INCLUDES the scrollbar in Chrome: on a real
   desktop with a classic scrollbar every bleeding block overshoots by half
   the scrollbar width and the page scrolls sideways. A headless run has
   overlay scrollbars and never sees it, which is exactly the kind of defect
   that ships.

   A rails section lays out three tracks: a flexible rail, the container's own
   measure, a flexible rail. A child sits in `main` by default and can be told
   to run out to one edge or to both. No viewport unit is involved, so the
   arithmetic cannot disagree with the document's own width. */
.rails {
  display: grid;
  grid-template-columns:
    [full-start] minmax(var(--gut), 1fr)
    [main-start] minmax(0, calc(var(--w) - var(--gut) * 2))
    [main-end] minmax(var(--gut), 1fr)
    [full-end];
}
.rails > * { grid-column: main; }
.rails > .to-right { grid-column: main-start / full-end; }
.rails > .to-left  { grid-column: full-start / main-end; }
.rails > .to-both  { grid-column: full; }
@media (max-width: 899.98px) {
  /* On a phone every rails child runs the full width and carries its own
     gutter, so a photograph still reaches both edges and copy never does. */
  .rails > * { grid-column: full; padding-inline: var(--gut); }
  .rails > .to-right, .rails > .to-left, .rails > .to-both { padding-inline: 0; }
  .rails > .to-right > .pair__say,
  .rails > .to-left  > .pair__say { padding-inline: var(--gut); }
}

/* A two column pair inside a rails section. The ratios are the ones DESIGN.md
   names; no two consecutive two column sections on a page share one. */
.pair { display: grid; gap: var(--sp-5); align-items: center; }
@media (min-width: 900px) {
  .pair { gap: var(--sp-7); }
  .pair--7-5 { grid-template-columns: 7fr 5fr; }
  .pair--5-7 { grid-template-columns: 5fr 7fr; }
  .pair--2-1 { grid-template-columns: 2fr 1fr; }
  .pair--1-2 { grid-template-columns: 1fr 2fr; }
  .pair--4-8 { grid-template-columns: 4fr 8fr; }
}

/* --- section rhythm. Six levels, each one meaning something, so the home
       page resolves six distinct vertical paddings rather than repeating one.
       A section that FOLLOWS a full bleed photograph takes less top padding
       than one that follows text: the photograph has already separated them. */
.sec { padding-block: var(--sp-7); }
@media (min-width: 900px) { .sec { padding-block: var(--sp-8); } }

.sec--band  { padding-block: var(--sp-5); }
.sec--tight { padding-block: var(--sp-6); }
.sec--mid   { padding-block: var(--sp-6); }
@media (min-width: 900px) { .sec--mid { padding-block: var(--sp-7); } }
.sec--tall  { padding-block: var(--sp-7); }
@media (min-width: 900px) { .sec--tall { padding-block: var(--sp-9); } }
.sec--hug   { padding-top: var(--sp-4); }
.sec--after-media { padding-top: var(--sp-5); }
@media (min-width: 900px) { .sec--after-media { padding-top: var(--sp-6); } }

/* --- grounds. A section paints its ground and hands the components inside
       the right ink through the four --on-ground tokens. --- */
.gr-paper      { background: var(--paper); color: var(--ink);
                 --ground: var(--paper); --on-ground: var(--ink);
                 --on-ground-dim: var(--ink-soft); --rule: var(--line); }
.gr-paper-deep { background: var(--paper-deep); color: var(--ink);
                 --ground: var(--paper-deep); --on-ground: var(--ink);
                 --on-ground-dim: var(--ink-soft); --rule: var(--line); }
.gr-forest     { background: var(--forest); color: var(--on-forest);
                 --ground: var(--forest); --on-ground: var(--on-forest);
                 --on-ground-dim: var(--on-forest-dim); --rule: var(--line-forest); }
.gr-forest-deep{ background: var(--forest-deep); color: var(--on-forest);
                 --ground: var(--forest-deep); --on-ground: var(--on-forest);
                 --on-ground-dim: var(--on-forest-dim); --rule: var(--line-forest); }

.rule-top { border-top: 1px solid var(--rule); }

/* --- the twelve column grid, with the five ratios DESIGN.md names. No two
       consecutive two column sections on a page share one. --- */
.grid { display: grid; gap: var(--sp-6); align-items: start; }
@media (min-width: 900px) {
  .grid { gap: var(--sp-7); }
  .g-5-7 { grid-template-columns: 5fr 7fr; }
  .g-7-5 { grid-template-columns: 7fr 5fr; }
  .g-1-2 { grid-template-columns: 1fr 2fr; }
  .g-2-1 { grid-template-columns: 2fr 1fr; }
  .g-4-8 { grid-template-columns: 4fr 8fr; }
  .g-6-5 { grid-template-columns: 6fr 5fr; }
  .g-gap-tight { gap: var(--sp-5); }
}

/* No radius on a photograph, ever. DESIGN.md section 9. */
.ph { display: block; width: 100%; height: auto; border-radius: 0; }
.ph--cover { height: 100%; object-fit: cover; }

figure.fig { margin: 0; }
figure.fig figcaption {
  margin-top: var(--sp-2);
  font-family: var(--mono); font-size: var(--fs-0);
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--on-ground-dim);
}

/* ------------------------------------------------------------------ header */
.hd {
  position: sticky; top: 0; z-index: 40;
  background: var(--forest);
  color: var(--on-forest);
  border-bottom: 1px solid transparent;
  transition: border-color 160ms linear, background-color 160ms linear;
}
.hd--scrolled { border-bottom-color: var(--line-forest); }

.hd__in {
  display: flex; align-items: center; gap: var(--sp-4);
  min-height: var(--hd-h);
}
@media (max-width: 639.98px) { .hd__in { gap: var(--sp-2); } }
@media (max-width: 399.98px) { .hd__in { gap: 7px; } }

.brand {
  display: inline-flex; align-items: center; gap: var(--sp-3);
  text-decoration: none; color: var(--on-forest);
  margin-right: auto;
}
/* The mark is painted with a CSS mask rather than shipped as an <img>.
   An <img> renders the SVG in its own document, where `currentColor`
   resolves to that document's own default ink, so the pale warm tone the
   brand needs on forest is not reachable that way. A mask takes the alpha of the same one
   file and paints it in whatever colour the element inherits, which is how
   one 86KB artwork serves the header, the footer and the drawer in two
   different colours with one request. Verified rendered on forest, on paper
   and on gold before this was committed. */
.mark {
  display: block;
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-size: contain; mask-size: contain;
  flex: none;
}
/* 255 by 205.375 user units, the measured ink box. */
.brand__mark { width: 50px; height: 40px; }
@media (min-width: 1024px) { .brand__mark { width: 55px; height: 44px; } }

/* The trading name is thirty seven characters, which is far too long to set
   as one line beside a 50px mark. Written as a plain string in an 11ch box it
   broke mid word into "Landscapin / g", because body carries
   overflow-wrap: break-word for long copy. So it is set as a real two part
   lockup: the place name in the serif over the trade in tracked mono, both
   taken from the same string in content/site.json rather than retyped. */
.brand__name { display: none; }
@media (min-width: 1200px) {
  .brand__name { display: block; overflow-wrap: normal; word-break: normal; }
}
.brand__first {
  display: block;
  font-family: var(--serif);
  font-size: 1.18rem;
  line-height: 1;
  letter-spacing: -0.01em;
}
.brand__rest {
  display: block;
  margin-top: 3px;
  font-family: var(--mono);
  font-size: 0.6rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--on-forest-dim);
  white-space: nowrap;
}

.nav { display: none; gap: clamp(14px, 1.5vw, 26px); align-items: center; }
@media (min-width: 1024px) { .nav { display: flex; } }
.nav a {
  text-decoration: none;
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--on-forest-dim);
  padding-block: 6px;
  position: relative;
  transition: color 120ms linear;
}
.nav a:hover { color: var(--on-forest); }
.nav a[aria-current="page"] { color: var(--on-forest); }
.nav a[aria-current="page"]::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: 2px; background: var(--gold);
}

.hd__acts { display: flex; align-items: center; gap: var(--sp-3); }

/* The phone number is in the bar at EVERY width, which is what DESIGN.md
   section 6.1 asks for and what the first build got wrong: below 640px it was
   display:none, and on the home page the dock is suppressed while the hero
   form is on screen, so the first screen of the site that sells "call or text
   us" carried no way to call. The number shrinks and the glyph appears rather
   than the number disappearing. Measured at 360 and at 390 the mark, the
   number and the Menu button all fit on one row with clearance and no
   overflow. */
.hd__tel {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--mono);
  font-size: var(--fs-0);
  letter-spacing: 0.06em;
  color: var(--gold);
  text-decoration: none;
  white-space: nowrap;
  min-height: 44px;
}
.hd__tel:hover { text-decoration: underline; text-underline-offset: 3px; }
.hd__tel svg { display: none; width: 15px; height: 15px; flex: none; }
@media (max-width: 639.98px) {
  .hd__tel { font-size: 0.74rem; letter-spacing: 0.01em; }
  .hd__tel svg { display: block; }
}
@media (max-width: 399.98px) {
  .hd__tel { font-size: 0.68rem; letter-spacing: 0; gap: 4px; }
  .hd__tel svg { width: 14px; height: 14px; }
}

.burger {
  display: inline-flex; align-items: center; gap: 8px;
  background: none; border: 1px solid var(--line-forest);
  color: var(--on-forest);
  padding: 9px 13px; border-radius: var(--r-sm);
  font-family: var(--mono); font-size: 0.7rem;
  letter-spacing: 0.08em; text-transform: uppercase;
  cursor: pointer; min-height: 44px;
}
@media (max-width: 399.98px) {
  .burger { padding: 9px 10px; gap: 6px; font-size: 0.64rem; letter-spacing: 0.04em; }
}
.burger svg { width: 16px; height: 16px; }
@media (min-width: 1024px) { .burger { display: none; } }

/* Above 900 the header carries the estimate button; below it the dock does,
   so the header never stacks two actions on a narrow bar. */
.hd .btn { display: none; }
@media (min-width: 900px) { .hd .btn { display: inline-flex; } }

/* ------------------------------------------------------------------ drawer */
.drawer { position: fixed; inset: 0; z-index: 60; visibility: hidden; }
.drawer[data-open="true"] { visibility: visible; }
.drawer__scrim {
  position: absolute; inset: 0;
  background: oklch(0.14 0.03 150 / .62);
  opacity: 0; transition: opacity 220ms ease;
}
.drawer[data-open="true"] .drawer__scrim { opacity: 1; }
.drawer__panel {
  position: absolute; top: 0; right: 0; bottom: 0;
  width: min(380px, 88vw);
  background: var(--forest-deep); color: var(--on-forest);
  /* The panel is a forest ground and has to SAY so, because .btn--ghost draws
     itself from --on-ground. Without these four lines the ghost button
     inherited the root default, which is --ink, so the drawer's "call us"
     button rendered dark ink on a dark ground: present, focusable, and
     invisible. The Warrick build shipped the same class of defect in an
     About CTA. Any surface that changes the ground sets these four. */
  --ground: var(--forest-deep);
  --on-ground: var(--on-forest);
  --on-ground-dim: var(--on-forest-dim);
  --rule: var(--line-forest);
  padding: var(--sp-4) var(--sp-4) calc(var(--sp-6) + env(safe-area-inset-bottom));
  padding-top: calc(var(--sp-4) + env(safe-area-inset-top));
  display: flex; flex-direction: column; gap: var(--sp-5);
  overflow-y: auto;
  transform: translateX(100%);
  transition: transform 260ms cubic-bezier(.22,.61,.36,1);
}
.drawer[data-open="true"] .drawer__panel { transform: translateX(0); }
.drawer__top { display: flex; justify-content: flex-end; }
.drawer__top .burger { display: inline-flex; }
.drawer__nav { display: flex; flex-direction: column; }
.drawer__nav a {
  text-decoration: none;
  font-family: var(--serif);
  font-size: var(--fs-3);
  padding-block: var(--sp-3);
  border-bottom: 1px solid var(--line-forest);
}
.drawer__nav a[aria-current="page"] { color: var(--gold); }
.drawer__acts { display: grid; gap: var(--sp-2); }
.drawer__foot { color: var(--on-forest-dim); }

@media (prefers-reduced-motion: reduce) {
  .drawer__panel, .drawer__scrim { transition: none; }
}

/* =========================================================================
   COMPONENT INVENTORY, in DESIGN.md section 8 order.
   ========================================================================= */

/* ---------------------------------------------------- THE GOLD REGISTER ---
   Every selector in this stylesheet that paints with --gold, --gold-press or
   --gold-ink, listed so the rule can be reviewed by name rather than trusted.
   DESIGN.md section 4 allows: the primary button, the mobile dock's estimate
   button, the header phone link, the heading cut mark, the focus ring, the
   star glyphs, the active nav underline.

     .btn, .btn:hover                the primary button, and its pressed state
     .dock__a--go                    the dock's estimate button
     .hd__tel, .vhero__tel           a phone link. The list names the header
                                     one; the hero's is the same object and
                                     the same action
     .form-state a                   the phone link in the error state, same
     .skip                           the skip link, a button in all but name
     .cut__mark                      the heading cut mark
     :root --focus                   the focus ring
     .review__stars                  the star glyphs
     .nav a[aria-current]::after     the active nav underline
     .drawer__nav a[aria-current]    the same indicator on a phone
     .cut__eyebrow, .chip--gold      the eyebrow label above a heading, in
     .ft__h                          --gold on forest and --gold-ink on paper
     .sections__name svg,            the arrow inside a link, which is part of
     .related__n svg                 a pressable row
     .field__lab .req                the mark on a field that must be filled
                                     before the gold button will do anything
     .field--check input             accent-color on the consent checkbox

   Four uses were REMOVED rather than argued for, because they were
   decoration: the 2px gold rule over each step of the facts strip, the gold
   diamond on every bullet, the gold tick glyph on the trust list, and the 3px
   gold stripe down the side of the quote block, which also broke the side
   stripe ban in section 9.

   THE EYEBROW IS THE ONE EXTENSION AND DESIGN.md CONTRADICTS ITSELF ON IT.
   Section 4's "gold fills exactly these" list does not include an eyebrow,
   and the paragraph directly above it says to use the deep gold "for any gold
   text on paper (eyebrows, ...)". The eyebrow is kept, and the contradiction
   is flagged rather than resolved silently.
   ------------------------------------------------------------------------ */

/* ------------------------------------------------------------------- .btn */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  background: var(--gold); color: var(--on-gold);
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  padding: 15px 24px;
  font-weight: 600; font-size: 0.97rem; line-height: 1.1;
  text-decoration: none; cursor: pointer;
  transition: background-color 120ms linear;
  min-height: 48px;
}
.btn:hover, .btn:focus-visible { background: var(--gold-press); }
.btn svg { width: 15px; height: 15px; flex: none; }

.btn--sm { padding: 10px 16px; min-height: 42px; font-size: 0.9rem; }
.btn--block { width: 100%; }

/* Ghost on paper: a forest outline. Ghost on forest: a paper outline. Never
   a gold ground on anything but the primary. */
.btn--ghost {
  background: transparent; color: var(--on-ground);
  border-color: var(--on-ground);
}
.btn--ghost:hover, .btn--ghost:focus-visible {
  background: var(--on-ground); color: var(--ground);
}

/* ------------------------------------------------------------------ .chip */
/* The mono label. Used for the eyebrow above a heading, the licence line,
   the section numbers on the services device and the form field labels. */
.chip {
  display: inline-block;
  font-family: var(--mono);
  font-size: var(--fs-0);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--on-ground-dim);
}
/* Gold as text on paper needs --gold-ink; see the header of this file. */
.chip--gold { color: var(--gold-ink); }
.gr-forest .chip--gold, .gr-forest-deep .chip--gold { color: var(--gold); }

.chip--rule {
  display: flex; align-items: center; gap: var(--sp-3);
}
.chip--rule::after {
  content: ""; flex: 1 1 auto; height: 1px; background: var(--rule);
}

/* ---------------------------------------------------------------- .plinth */
/* The credential band. Four facts in mono over serif figures, divided by
   hairlines rather than made into four cards: DESIGN.md bans an identical
   card grid by name. */
.plinth { display: grid; gap: 0; }
@media (min-width: 640px) { .plinth { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 980px) { .plinth { grid-template-columns: repeat(4, 1fr); } }

.plinth__item {
  padding: var(--sp-4) 0;
  border-top: 1px solid var(--line-forest);
}
@media (min-width: 640px) {
  .plinth__item { padding: var(--sp-4) var(--sp-5); }
  .plinth__item:nth-child(odd) { padding-left: 0; }
  .plinth__item:nth-child(even) { padding-right: 0; }
  .plinth__item + .plinth__item { border-left: 1px solid var(--line-forest); }
  .plinth__item:nth-child(odd) { border-left: 0; }
}
@media (min-width: 980px) {
  .plinth__item { padding: var(--sp-3) var(--sp-5); border-top: 0; }
  .plinth__item:first-child { padding-left: 0; }
  .plinth__item:last-child { padding-right: 0; }
  .plinth__item + .plinth__item { border-left: 1px solid var(--line-forest); }
}

.plinth__fig {
  font-family: var(--serif);
  font-size: var(--fs-3);
  line-height: 1.1;
  display: block;
}
@media (min-width: 900px) { .plinth__fig { font-size: var(--fs-4); } }
.plinth__lab {
  display: block; margin-top: var(--sp-1);
  font-family: var(--mono); font-size: var(--fs-0);
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--on-forest-dim);
}

/* ------------------------------------------------------------------- .cut */
/* The section heading with its mark. They take a tree down in sections, so a
   heading carries a small gold cut mark: two short strokes with a gap, the
   shape of a felling cut. It is a SHAPE, so it is exempt from the gold on
   paper contrast rule, and it is the one decorative motion on the site. */
.cut { display: block; }
.cut__mark {
  display: block; width: 46px; height: 10px; margin-bottom: var(--sp-3);
  background: var(--gold);
  clip-path: polygon(0 4px, 18px 4px, 24px 0, 30px 4px, 46px 4px,
                     46px 6px, 30px 6px, 24px 10px, 18px 6px, 0 6px);
  transform-origin: left center;
  transform: scaleX(0);
  transition: transform 300ms cubic-bezier(.22,.61,.36,1) 120ms;
}
/* Drawn in on reveal, and ONLY where motion is welcome. With JS off, or
   under reduced motion, the mark is simply there from the start. */
.cut__mark { transform: scaleX(1); }
@media (prefers-reduced-motion: no-preference) {
  html.js .rv:not(.in) .cut__mark { transform: scaleX(0); }
}

.cut__eyebrow {
  display: block; margin-bottom: var(--sp-2);
  font-family: var(--mono); font-size: var(--fs-0);
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--gold-ink);
}
.gr-forest .cut__eyebrow, .gr-forest-deep .cut__eyebrow { color: var(--gold); }

/* -------------------------------------------------------------- .sections */
/* The device. A trunk cut into lengths: one vertical rule down the left with
   a cut mark at every join, each length a service. Not a card grid, and the
   lengths are deliberately unequal in height because their copy is. */
.sections { display: block; }
.sections__group + .sections__group { margin-top: var(--sp-6); }
.sections__glab {
  font-family: var(--mono); font-size: var(--fs-0);
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--on-ground-dim);
  padding-bottom: var(--sp-2);
  border-bottom: 1px solid var(--rule);
}
.sections__list { display: block; }

.sections__item {
  position: relative;
  display: grid;
  grid-template-columns: 2.6rem 1fr;
  gap: 0 var(--sp-3);
  align-items: start;
  padding: var(--sp-4) 0;
  border-bottom: 1px solid var(--rule);
  text-decoration: none;
  color: inherit;
  transition: background-color 140ms linear;
}
@media (min-width: 760px) {
  .sections__item {
    grid-template-columns: 3.4rem minmax(0, 1fr) 132px;
    gap: 0 var(--sp-4);
    align-items: center;
    padding: var(--sp-4) var(--sp-3);
    margin-inline: calc(var(--sp-3) * -1);
  }
}
.sections__item:hover { background: color-mix(in oklab, var(--on-ground) 5%, transparent); }

.sections__no {
  font-family: var(--mono); font-size: var(--fs-0);
  letter-spacing: 0.06em;
  color: var(--on-ground-dim);
  padding-top: 5px;
}
.sections__body { min-width: 0; }
.sections__name {
  font-family: var(--serif); font-weight: 400;
  font-size: var(--fs-3); line-height: 1.15;
  display: flex; align-items: baseline; gap: 10px;
}
.sections__name svg {
  width: 13px; height: 13px; flex: none;
  color: var(--gold-ink);
  transform: translateX(0);
  transition: transform 140ms ease;
}
.gr-forest .sections__name svg, .gr-forest-deep .sections__name svg { color: var(--gold); }
.sections__item:hover .sections__name svg { transform: translateX(4px); }
.sections__say { display: block; margin-top: 5px;
                 color: var(--on-ground-dim); font-size: 0.97rem; }

.sections__thumb { display: none; }
@media (min-width: 760px) {
  .sections__thumb { display: block; width: 132px; height: 88px; overflow: hidden; }
  .sections__thumb img { width: 100%; height: 100%; object-fit: cover; border-radius: 0; }
}

/* ------------------------------------------------------------- .form-card */
/* One card, one solve. It sits on --forest-soft everywhere it appears: in
   the hero, in the closing band on every page, and on /contact. That is why
   the field colours only had to clear contrast against one ground. */
.form-card {
  background: var(--forest-soft);
  color: var(--on-forest);
  border-radius: var(--r-md);
  padding: var(--sp-4);
  box-shadow: var(--shadow);
  --ground: var(--forest-soft);
  --on-ground: var(--on-forest);
  --on-ground-dim: var(--on-forest-dim);
  --rule: var(--line-forest);
}
@media (min-width: 640px) { .form-card { padding: var(--sp-5); } }

.form-card__h {
  font-family: var(--serif); font-weight: 400;
  font-size: var(--fs-3); line-height: 1.15;
}
.form-card__sub { margin-top: var(--sp-2); color: var(--on-forest-dim); font-size: 0.95rem; }
.form-card__body { margin-top: var(--sp-4); display: grid; gap: var(--sp-3); }
@media (min-width: 560px) {
  .form-card__row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); }
}
.form-card__foot {
  margin-top: var(--sp-3);
  color: var(--on-forest-dim);
  font-size: 0.82rem; line-height: 1.5;
}

.form-note {
  margin-top: var(--sp-3);
  padding: var(--sp-3);
  border: 1px solid var(--line-forest);
  border-radius: var(--r-sm);
  background: color-mix(in oklab, var(--field-bg) 55%, transparent);
  color: var(--on-forest-dim);
  font-size: 0.88rem;
}

.form-state {
  padding: var(--sp-4);
  border: 1px solid var(--line-forest);
  border-radius: var(--r-sm);
  background: var(--field-bg);
}
.form-state h3 { font-size: var(--fs-2); }
.form-state p { margin-top: var(--sp-2); color: var(--on-forest-dim); }
.form-state a { color: var(--gold); }

/* ----------------------------------------------------------------- .field */
.field { display: block; }
.field__lab {
  display: block; margin-bottom: 6px;
  font-family: var(--mono); font-size: 0.72rem;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--field-dim);
}
.field__lab .req { color: var(--gold); }

.field input,
.field select,
.field textarea {
  width: 100%;
  font: inherit;
  font-size: 1rem;              /* 16px floor: iOS zooms a smaller field */
  color: var(--on-forest);
  background: var(--field-bg);
  border: 1px solid var(--field-line);
  border-radius: var(--r-sm);
  padding: 12px 13px;
  min-height: 48px;
  appearance: none;
  transition: border-color 120ms linear;
}
.field textarea { min-height: 104px; resize: vertical; line-height: 1.5; }
.field input::placeholder, .field textarea::placeholder { color: var(--field-dim); }
.field input:hover, .field select:hover, .field textarea:hover { border-color: var(--on-forest-dim); }

.field select {
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 18px center, right 13px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 34px;
}
.field select option { color: var(--ink); background: var(--paper); }

.field__err { display: block; margin-top: 5px; font-size: 0.82rem; color: var(--bad); }
.field[data-bad="true"] input,
.field[data-bad="true"] select,
.field[data-bad="true"] textarea { border-color: var(--bad); }

.field--check { display: block; }
.field--check label {
  display: grid; grid-template-columns: 22px 1fr; gap: var(--sp-2);
  align-items: start;
  font-size: 0.85rem; line-height: 1.5;
  color: var(--on-forest-dim);
  cursor: pointer;
}
.field--check input {
  width: 22px; height: 22px; min-height: 0; margin: 1px 0 0; padding: 0;
  accent-color: var(--gold);
  appearance: auto;
  background: none; border: 0;
}

/* Honeypot. Off screen rather than display:none, because some autofillers
   skip a hidden input and the point is that a bot fills it. */
.hp {
  position: absolute !important; left: -9999px; top: 0;
  width: 1px; height: 1px; opacity: 0; pointer-events: none;
}

/* ---------------------------------------------------------------- .review */
.review { }
.review__stars { display: inline-flex; gap: 3px; color: var(--gold); line-height: 1; }
.review__stars svg { width: 15px; height: 15px; }
.review__text { margin-top: var(--sp-3); text-wrap: pretty; }
.review__who {
  margin-top: var(--sp-3);
  font-family: var(--mono); font-size: var(--fs-0);
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--on-ground-dim);
}

/* The display pull quote. Serif italic, the one long review on the home
   page and the one on every service and city page. */
.review--pull .review__text {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-3);
  line-height: 1.3;
  letter-spacing: -0.005em;
}
@media (min-width: 900px) {
  .review--pull .review__text { font-size: var(--fs-4); line-height: 1.28; }
}
.review--pull .review__stars svg { width: 19px; height: 19px; }

.review-row { display: grid; gap: var(--sp-5); }
@media (min-width: 760px) { .review-row { grid-template-columns: repeat(3, 1fr); } }
.review-row .review { font-size: 0.95rem; }
.review-row .review + .review { }
@media (max-width: 759.98px) {
  .review-row .review + .review { border-top: 1px solid var(--rule); padding-top: var(--sp-5); }
}
@media (min-width: 760px) {
  .review-row .review + .review { border-left: 1px solid var(--rule); padding-left: var(--sp-5); }
}

.review-list { display: grid; gap: var(--sp-6); }
.review-list .review { padding-bottom: var(--sp-6); border-bottom: 1px solid var(--rule); }
.review-list .review:last-child { border-bottom: 0; padding-bottom: 0; }

.score {
  display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap;
}
.score__n { font-family: var(--serif); font-size: var(--fs-7); line-height: .9; }

/* ---------------------------------------------------------------- .mosaic */
/* The home page's eight frames. NOT the masonry: with eight photographs of
   very different heights, CSS columns balanced two of the four columns about
   250px short and left a hole of paper under them, which reads as a broken
   section rather than as a mixed aspect grid. This is a deliberate packing
   instead: one frame at two by two, one tall, six square, which is exactly
   twelve cells and therefore exactly four columns by three rows with no
   ragged edge, and exactly two columns by six rows on a phone. Mixed aspect
   is kept, and it is now a decision rather than an accident. */
.mosaic {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  grid-auto-flow: dense;
  gap: 3px;
}
@media (min-width: 760px) { .mosaic { grid-template-columns: repeat(4, 1fr); } }
.mosaic__tile { position: relative; overflow: hidden; aspect-ratio: 1; display: block;
                background: var(--forest-deep); }
.mosaic__tile--wide { grid-column: span 2; grid-row: span 2; }
.mosaic__tile--tall { grid-row: span 2; aspect-ratio: 1 / 2; }
.mosaic__tile img,
.mosaic__tile picture { width: 100%; height: 100%; }
.mosaic__tile img {
  object-fit: cover; border-radius: 0;
  transition: transform 400ms cubic-bezier(.22,.61,.36,1);
}
.mosaic__tile:hover img { transform: scale(1.03); }
@media (prefers-reduced-motion: reduce) {
  .mosaic__tile img, .mosaic__tile:hover img { transition: none; transform: none; }
}

/* --------------------------------------------------------------- .masonry */
/* Mixed aspect, no captions on the tiles, no radius, and the tile heights
   differ on purpose. Four columns on a desktop, two on a phone. */
.masonry { column-count: 2; column-gap: 3px; }
@media (min-width: 700px) { .masonry { column-count: 3; } }
@media (min-width: 1100px) { .masonry { column-count: 4; } }
.masonry__tile {
  display: block; break-inside: avoid; margin-bottom: 3px;
  position: relative; overflow: hidden;
  background: var(--forest-deep);
}
.masonry__tile img {
  width: 100%; height: auto; border-radius: 0;
  transition: transform 400ms cubic-bezier(.22,.61,.36,1);
}
.masonry__tile:hover img { transform: scale(1.03); }
@media (prefers-reduced-motion: reduce) {
  .masonry__tile img, .masonry__tile:hover img { transition: none; transform: none; }
}

/* ----------------------------------------------------------------- .plate */
/* The credential plate on /about and in the hero ticks. ONE plate divided by
   hairlines, not a grid of cards. */
.plate {
  display: grid;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
}
@media (min-width: 640px) { .plate { grid-template-columns: 1fr 1fr; } }
.plate__item { padding: var(--sp-4); border-top: 1px solid var(--rule); }
.plate__item:first-child { border-top: 0; }
@media (min-width: 640px) {
  .plate__item:nth-child(2) { border-top: 0; }
  .plate__item:nth-child(even) { border-left: 1px solid var(--rule); }
}
.plate__fig { font-family: var(--serif); font-size: var(--fs-3); line-height: 1.1; }
.plate__lab {
  display: block; margin-top: 5px;
  font-family: var(--mono); font-size: var(--fs-0);
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--on-ground-dim);
}

/* The guarantee and the owner's own words, quoted verbatim.
   It used to carry a 3px gold rule down its left edge, which broke TWO rules
   at once: gold outside the list in DESIGN.md section 4, and a side stripe
   border as an accent, banned by name in section 9. A hairline above it and
   the display serif in italic say quotation just as clearly and say it in the
   site's own vocabulary. */
.quote-block {
  border-top: 1px solid var(--rule);
  padding-top: var(--sp-4);
  max-width: 56ch;
}
.quote-block p {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-2);
  line-height: 1.4;
  text-wrap: pretty;
}

.ticks { display: grid; gap: var(--sp-2); }
@media (min-width: 420px) { .ticks { grid-template-columns: 1fr 1fr; } }
.ticks li {
  display: grid; grid-template-columns: 17px 1fr; gap: 9px;
  align-items: start;
  font-size: 0.92rem; line-height: 1.4;
}
/* The tick glyph inherits rather than going gold. It confirms a fact, it is
   not something a visitor can press, and gold on this site means exactly one
   thing. */
.ticks svg { width: 17px; height: 17px; color: var(--on-ground-dim); margin-top: 2px; }

/* ------------------------------------------------------------------- .faq */
.faq { border-top: 1px solid var(--rule); }
.faq details { border-bottom: 1px solid var(--rule); }
.faq summary {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  padding: var(--sp-4) 0;
  cursor: pointer; list-style: none;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary h3 { font-size: var(--fs-2); font-weight: 600; }
.faq__chev { flex: none; color: var(--on-ground-dim); transition: transform 180ms ease; }
.faq__chev svg { width: 15px; height: 15px; display: block; }
.faq details[open] .faq__chev { transform: rotate(180deg); }
.faq__body { padding-bottom: var(--sp-4); }
.faq__body p { color: var(--on-ground-dim); max-width: 62ch; }
@media (prefers-reduced-motion: reduce) { .faq__chev { transition: none; } }

/* ------------------------------------------------------------------ .dock */
/* Phones only. Three equal actions. `main` and the footer carry bottom
   padding so the end of the page always clears it, and site.js hides it
   while a form is on screen so it can never cover a submit button. */
.dock {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
  display: grid; grid-template-columns: repeat(3, 1fr);
  background: var(--forest-deep);
  border-top: 1px solid var(--line-forest);
  box-shadow: var(--shadow);
  padding-bottom: env(safe-area-inset-bottom);
  transform: translateY(0);
  transition: transform 220ms cubic-bezier(.22,.61,.36,1);
}
.dock[data-hidden="true"] { transform: translateY(105%); }
@media (min-width: 900px) { .dock { display: none; } }
@media (prefers-reduced-motion: reduce) { .dock { transition: none; } }

.dock__a {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px;
  min-height: var(--dock-h);
  text-decoration: none;
  color: var(--on-forest);
  font-family: var(--mono); font-size: 0.66rem;
  letter-spacing: 0.08em; text-transform: uppercase;
}
.dock__a svg { width: 17px; height: 17px; }
.dock__a + .dock__a { border-left: 1px solid var(--line-forest); }
.dock__a--go { background: var(--gold); color: var(--on-gold); }
.dock__a--go:hover { background: var(--gold-press); }

@media (max-width: 899.98px) {
  main { padding-bottom: calc(var(--dock-h) + env(safe-area-inset-bottom)); }
}

/* ------------------------------------------------------------------- .map */
.map { position: relative; background: var(--paper-deep); }
.map iframe {
  display: block; width: 100%; height: 100%; border: 0;
  min-height: 300px;
}
.map--tall iframe { min-height: 420px; }
@media (min-width: 900px) { .map--tall iframe { min-height: 520px; } }

.city-links { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.city-links a {
  display: inline-block;
  padding: 8px 13px;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  text-decoration: none;
  font-size: 0.9rem;
  transition: border-color 120ms linear, background-color 120ms linear;
}
.city-links a:hover { border-color: var(--on-ground); background: var(--ground); }

/* ---------------------------------------------------------------- .crumbs */
.crumbs { padding-top: var(--sp-4); }
.crumbs ol { display: flex; flex-wrap: wrap; gap: 6px 10px; }
.crumbs li {
  font-family: var(--mono); font-size: 0.7rem;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--on-ground-dim);
  display: flex; align-items: center; gap: 10px;
}
.crumbs li + li::before { content: "/"; color: var(--line-strong); }
.crumbs a { text-decoration: none; }
.crumbs a:hover { text-decoration: underline; text-underline-offset: 3px; }

/* -------------------------------------------------------------- .lightbox */
.lb {
  position: fixed; inset: 0; z-index: 80;
  background: oklch(0.14 0.02 150 / .93);
  display: grid; grid-template-columns: 56px 1fr 56px; align-items: center;
  padding: var(--sp-4);
}
.lb__fig { margin: 0; display: flex; align-items: center; justify-content: center; min-width: 0; }
.lb__fig img { max-width: 100%; max-height: 82vh; width: auto; border-radius: 0; }
.lb__btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 46px; height: 46px;
  background: transparent; color: var(--on-forest);
  border: 1px solid var(--line-forest); border-radius: var(--r-sm);
  cursor: pointer;
}
.lb__btn:hover { background: var(--forest-soft); }
.lb__btn svg { width: 20px; height: 20px; }
.lb__prev svg { transform: rotate(180deg); }
.lb__close { position: absolute; top: var(--sp-4); right: var(--sp-4); }
.lb__count {
  position: absolute; left: 0; right: 0; bottom: var(--sp-4);
  text-align: center;
  font-family: var(--mono); font-size: var(--fs-0);
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--on-forest-dim);
}

/* =========================================================================
   PAGE PIECES
   ========================================================================= */

/* -------------------------------------------------------------- the hero */
.vhero {
  position: relative;
  background: var(--forest-deep);
  color: var(--on-forest);
  isolation: isolate;
  --ground: var(--forest-deep);
  --on-ground: var(--on-forest);
  --on-ground-dim: var(--on-forest-dim);
  --rule: var(--line-forest);
}
.vhero__media { position: absolute; inset: 0; overflow: hidden; z-index: -1; }
.vhero__poster, .vhero__poster img, .vhero__vid {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
}
.vhero__vid { opacity: 0; transition: opacity 600ms linear; }
.vhero__vid.is-playing { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .vhero__vid { transition: none; } }

/* THE SCRIM ALPHA IS 0.80 AND IT WAS SOLVED, NOT CHOSEN.
   Every one of the 23 frames of both clips plus both posters was sampled,
   mapped through object-fit: cover onto the exact box each piece of hero copy
   occupies at 1440 and at 390, composited under this scrim colour at alphas
   from 0.62 to 0.84, and scored with the WCAG formula. The binding element is
   the GOLD eyebrow, which is the lowest luminance text on the hero:

     desktop, worst frame  alpha 0.72 -> 3.85   0.76 -> 4.40   0.80 -> 5.04
     phone,   worst frame  alpha 0.72 -> 3.86   0.76 -> 4.42   0.80 -> 5.07

   So 0.76 fails AA on the worst frame of the clip and 0.80 clears it with
   margin. The same measurement taken off the RENDERED page, by pausing the
   video, hiding the copy and reading the lightest pixel under each block,
   gives 7.29 at 1440 and 5.82 at 390: better than the model, because one
   screenshot catches one frame and the model covers the brightest one in the
   clip. The model is the number that binds. */
.vhero__scrim {
  position: absolute; inset: 0;
  background:
    linear-gradient(to right,
      oklch(0.17 0.04 150 / var(--scrim, .80)) 0%,
      oklch(0.17 0.04 150 / var(--scrim, .80)) 46%,
      oklch(0.17 0.04 150 / calc(var(--scrim, .80) * 0.72)) 100%);
}
@media (max-width: 899.98px) {
  .vhero__scrim {
    background: linear-gradient(to bottom,
      oklch(0.17 0.04 150 / calc(var(--scrim, .80) * 0.92)) 0%,
      oklch(0.17 0.04 150 / var(--scrim, .80)) 42%,
      oklch(0.17 0.04 150 / var(--scrim, .80)) 100%);
  }
}

/* The phone order is say, FORM, then the ticks and the actions, because the
   form card has to break the fold on a 390 by 844 screen. On a desktop the
   card moves into its own column and the ticks rejoin the copy underneath. */
.vhero__in {
  position: relative;
  display: grid;
  gap: var(--sp-4);
  padding-block: var(--sp-5) var(--sp-6);
}
@media (min-width: 900px) {
  .vhero__in {
    grid-template-columns: 6fr 5fr;
    grid-template-areas: "say form" "more form";
    grid-template-rows: auto auto;
    align-content: center;
    column-gap: var(--sp-7);
    row-gap: var(--sp-5);
    min-height: min(100svh, 880px);
    padding-block: var(--sp-7);
  }
  .vhero__say  { grid-area: say; }
  .vhero .form-card { grid-area: form; align-self: center; }
  .vhero__more { grid-area: more; }
}
.vhero__say { max-width: 46ch; }

/* One lead is rendered per breakpoint, never both. display:none keeps the
   other out of the accessibility tree as well as off the screen. */
.vhero__lead[data-short="false"] { display: none; }
@media (min-width: 900px) {
  .vhero__lead[data-short="true"] { display: none; }
  .vhero__lead[data-short="false"] { display: block; }
}
.vhero__eyebrow {
  display: block; margin-bottom: var(--sp-3);
  font-family: var(--mono); font-size: var(--fs-0);
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--gold);
}
.vhero__lead { margin-top: var(--sp-3); font-size: var(--fs-2); line-height: 1.45;
               color: var(--on-forest); }
@media (min-width: 900px) { .vhero__lead { margin-top: var(--sp-4); } }
.vhero__ticks { margin-top: 0; }
.vhero__acts { margin-top: var(--sp-4); display: flex; flex-wrap: wrap;
               gap: var(--sp-3); align-items: center; }
.vhero__tel {
  font-family: var(--mono); font-size: var(--fs-0);
  letter-spacing: 0.06em; color: var(--gold); text-decoration: none;
}
.vhero__tel:hover { text-decoration: underline; text-underline-offset: 3px; }

/* -------------------------------------------------- the photo hero (inner) */
.phero {
  position: relative;
  background: var(--forest-deep); color: var(--on-forest);
  isolation: isolate;
  --ground: var(--forest-deep);
  --on-ground: var(--on-forest);
  --on-ground-dim: var(--on-forest-dim);
  --rule: var(--line-forest);
}
.phero__media { position: absolute; inset: 0; overflow: hidden; z-index: -1; }
.phero__media img { width: 100%; height: 100%; object-fit: cover; }
/* Measured the same way as the video hero's, against every service, city and
   about hero photograph at 390 and at 1440: the worst ratio any piece of copy
   scores on any of those 25 frames is 6.02 to 1, on the gold eyebrow at
   /tree-service-tangent at 390. The first run of that measurement reported
   1.10 to 1 and was wrong: a position:fixed element is composited into an
   element screenshot at its VIEWPORT position, so on a hero taller than the
   screen the mobile dock's gold button landed in the middle of the capture
   and was sampled as if it were the ground. */
.phero__scrim {
  position: absolute; inset: 0;
  background: linear-gradient(to right,
    oklch(0.17 0.04 150 / .84) 0%,
    oklch(0.17 0.04 150 / .84) 50%,
    oklch(0.17 0.04 150 / .64) 100%);
}
@media (max-width: 899.98px) {
  .phero__scrim {
    background: linear-gradient(to bottom,
      oklch(0.17 0.04 150 / .78) 0%, oklch(0.17 0.04 150 / .86) 100%);
  }
}
.phero__in {
  position: relative;
  display: grid; gap: var(--sp-5);
  padding-block: var(--sp-6);
}
@media (min-width: 900px) {
  .phero__in { grid-template-columns: 7fr 5fr; gap: var(--sp-7); align-items: center;
               padding-block: var(--sp-7); }
}
.phero__say { max-width: 44ch; }
.phero h1 { font-size: clamp(2.1rem, 1.5rem + 2.6vw, 3.6rem); }
.phero__lead { margin-top: var(--sp-4); font-size: var(--fs-2); line-height: 1.45; }

/* ---------------------------------------------------------- the plain hero */
.hero-plain { padding-block: var(--sp-6) var(--sp-5); }
@media (min-width: 900px) { .hero-plain { padding-block: var(--sp-7) var(--sp-6); } }
.hero-plain h1 { max-width: 20ch; }
.hero-plain .lead { margin-top: var(--sp-4); max-width: 60ch; }

/* ----------------------------------------------------------------- facts */
/* Three steps, cut from one rule rather than boxed. The gold top rule this
   used to carry was decoration, which section 4 does not allow, and three
   equal boxes would have been the identical card grid section 9 bans. */
.facts { display: grid; gap: var(--sp-5); counter-reset: step; }
@media (min-width: 720px) {
  .facts { grid-template-columns: repeat(3, 1fr); gap: 0; }
  .facts > li { padding-inline: var(--sp-5); }
  .facts > li:first-child { padding-left: 0; }
  .facts > li:last-child { padding-right: 0; }
  .facts > li + li { border-left: 1px solid var(--rule); }
}
.facts > li { border-top: 1px solid var(--rule); padding-top: var(--sp-4); counter-increment: step; }
.facts > li::before {
  content: counter(step, decimal-leading-zero);
  display: block; margin-bottom: var(--sp-2);
  font-family: var(--mono); font-size: var(--fs-0);
  letter-spacing: 0.08em; color: var(--on-ground-dim);
}
.facts h3 { font-size: var(--fs-2); }
.facts p { margin-top: 8px; color: var(--on-ground-dim); font-size: 0.95rem; }

/* ------------------------------------------------------------ list marks */
.bullets { display: grid; gap: var(--sp-2); }
.bullets li {
  display: grid; grid-template-columns: 15px 1fr; gap: 11px; align-items: start;
}
.bullets li::before {
  content: ""; width: 9px; height: 9px; margin-top: .55em;
  background: var(--on-ground-dim);
  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
}

/* ------------------------------------------------------------ related row */
/* Three columns cut from one rule, not three identical bordered cards, which
   section 9 bans by name. Same object as the credential plinth and the facts
   strip: one surface divided by hairlines. */
.related { display: grid; gap: var(--sp-5); }
@media (min-width: 720px) {
  .related { grid-template-columns: repeat(3, 1fr); gap: 0; }
  .related a { padding-inline: var(--sp-5); }
  .related a:first-child { padding-left: 0; }
  .related a:last-child { padding-right: 0; }
  .related a + a { border-left: 1px solid var(--rule); }
}
.related a {
  display: block; padding-top: var(--sp-4);
  border-top: 1px solid var(--rule);
  text-decoration: none;
}
.related__n {
  display: flex; align-items: baseline; gap: 9px;
  font-family: var(--serif); font-weight: 400;
  font-size: var(--fs-3); line-height: 1.15;
}
.related__n svg { width: 13px; height: 13px; flex: none; transition: transform 140ms ease; }
.related a:hover .related__n { text-decoration: underline; text-underline-offset: 4px; }
.related a:hover .related__n svg { transform: translateX(4px); }
.related__s { margin-top: 8px; color: var(--on-ground-dim); font-size: 0.92rem; }

/* --------------------------------------------------------------- big link */
.big-link {
  font-family: var(--serif); font-size: var(--fs-4);
  line-height: 1.1; text-decoration: none;
  display: inline-block;
}
@media (min-width: 900px) { .big-link { font-size: var(--fs-5); } }
.big-link:hover { text-decoration: underline; text-underline-offset: 5px; }

.contact-rows { display: grid; gap: var(--sp-5); }
@media (min-width: 720px) { .contact-rows { grid-template-columns: 1fr 1fr; } }
.contact-row { border-top: 1px solid var(--rule); padding-top: var(--sp-3); }
.contact-row a { text-decoration: none; font-size: var(--fs-2); font-weight: 600; }
.contact-row a:hover { text-decoration: underline; text-underline-offset: 4px; }
.contact-row p { margin-top: 4px; color: var(--on-ground-dim); font-size: 0.92rem; }

/* ----------------------------------------------------------------- footer */
.ft {
  background: var(--forest-deep); color: var(--on-forest);
  padding-block: var(--sp-7) var(--sp-5);
  --ground: var(--forest-deep);
  --on-ground: var(--on-forest);
  --on-ground-dim: var(--on-forest-dim);
  --rule: var(--line-forest);
}
@media (max-width: 899.98px) {
  .ft { padding-bottom: calc(var(--sp-5) + var(--dock-h) + env(safe-area-inset-bottom)); }
}
.ft__grid { display: grid; gap: var(--sp-6); }
@media (min-width: 760px) { .ft__grid { grid-template-columns: 1.4fr 1fr 1fr; } }
@media (min-width: 1040px) { .ft__grid { grid-template-columns: 1.5fr 1fr 1fr 1.1fr; } }
.ft__lock { width: 132px; height: 134px; }
.ft__name { margin-top: var(--sp-3); font-family: var(--serif); font-size: var(--fs-2); }
.ft__line { margin-top: var(--sp-2); color: var(--on-forest-dim); font-size: 0.92rem; }
.ft__line a { text-decoration: none; }
.ft__line a:hover { text-decoration: underline; text-underline-offset: 3px; }
.ft__h {
  font-family: var(--mono); font-size: var(--fs-0);
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--gold);
  padding-bottom: var(--sp-2); border-bottom: 1px solid var(--line-forest);
}
.ft__list { margin-top: var(--sp-3); display: grid; gap: 7px; }
.ft__list a { text-decoration: none; font-size: 0.92rem; color: var(--on-forest-dim); }
.ft__list a:hover { color: var(--on-forest); text-decoration: underline; text-underline-offset: 3px; }
.ft__legal {
  margin-top: var(--sp-6); padding-top: var(--sp-4);
  border-top: 1px solid var(--line-forest);
  display: grid; gap: var(--sp-2);
  color: var(--on-forest-dim);
}
@media (min-width: 760px) { .ft__legal { grid-template-columns: 1fr auto; align-items: center; } }
.ft__social { display: flex; gap: var(--sp-3); margin-top: var(--sp-3); }
.ft__social a {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px;
  border: 1px solid var(--line-forest); border-radius: var(--r-sm);
  color: var(--on-forest-dim);
}
.ft__social a:hover { color: var(--on-forest); border-color: var(--on-forest-dim); }
.ft__social svg { width: 18px; height: 18px; }

/* ---------------------------------------------------------------- reveals */
/* Transform and opacity only. Nothing here animates layout. Under reduced
   motion nothing is ever at opacity 0: the rule that hides .rv is scoped to
   html.js AND to the no preference query, so a visitor who asks for less
   motion sees a finished page even before the observer runs. */
@media (prefers-reduced-motion: no-preference) {
  html.js .rv {
    opacity: 0;
    transform: translateY(12px);
    transition: opacity 500ms cubic-bezier(.22,.61,.36,1) calc(var(--d, 0) * 40ms),
                transform 500ms cubic-bezier(.22,.61,.36,1) calc(var(--d, 0) * 40ms);
  }
  html.js .rv.in { opacity: 1; transform: none; }
}

/* -------------------------------------------------------------- utilities */
.mt-2 { margin-top: var(--sp-2); }
.mt-3 { margin-top: var(--sp-3); }
.mt-4 { margin-top: var(--sp-4); }
.mt-5 { margin-top: var(--sp-5); }
.mt-6 { margin-top: var(--sp-6); }
.mt-7 { margin-top: var(--sp-7); }
.dim { color: var(--on-ground-dim); }
.stack { display: grid; gap: var(--sp-4); align-content: start; }
.stack--tight { gap: var(--sp-3); }
.center { text-align: center; }
.max-56 { max-width: 56ch; }
.max-62 { max-width: 62ch; }
