/* mwcurated.ca: one stylesheet, no framework.
 *
 * Brand (Billy, 2026-10-19): spruce green is the one accent; the monogram's lighter faces are
 * the only other green, and never for text. Cream ground, warm grey lines, near-black ink.
 * Type: Lexend Giga (the wordmark's face) for the wordmark and small spaced labels only;
 * Cera Pro for headings and body. Only Regular (400) is in fonts/ so far (Billy, 2026-11-01), so
 * the 300 headings show in Regular and <strong> is a synthesized bold; drop CeraPro-Light.woff2
 * and CeraPro-Bold.woff2 into public/fonts/ and rebuild to get them (build.js writes an
 * @font-face rule for each file that's there). Hanken Grotesk is the fallback.
 *
 * The rules of the page: big honest photos, short sentences, room, one accent, almost no motion.
 */

@font-face { font-family: "Lexend Giga"; src: url("/fonts/lexend-giga.woff2") format("woff2"); font-weight: 100 900; font-display: swap; }
@font-face { font-family: "Hanken Grotesk"; src: url("/fonts/hanken-grotesk.woff2") format("woff2"); font-weight: 100 900; font-display: swap; }
@font-face { font-family: "Cera Pro"; src: url("/fonts/CeraPro-Regular.woff2") format("woff2"); font-weight: 400; font-display: swap; }

:root {
  --green: #4D6F39;
  --green-deep: #3F5C2F;
  --green-soft: #879A77;
  --green-pale: #DCE5D3;   /* a pale spruce tint, for the mark (Billy, 2026-11-05) */
  --cream: #F9F8F3;
  --cream-2: #FFFFFA;
  --grey: #DEDEDD;
  --ink: #262324;
  --muted: #5F5E5A;
  --hair: rgba(38, 35, 36, .12);

  --display: "Lexend Giga", "Hanken Grotesk", system-ui, sans-serif;
  --body: "Cera Pro", "Hanken Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* viewport-fit=cover (spec 49): a phone turned sideways keeps the sides clear of the notch. */
  --gutter: max(clamp(16px, 4vw, 48px), env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px));
  --measure: 60ch;
  --wide: 1200px;
  --space-section: clamp(56px, 9vw, 120px);
  --radius: 6px;
  --tap: 44px;
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; scroll-padding-top: 88px; /* clear the sticky header when a link jumps to a section */ }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--cream);
  color: var(--ink);
  font-family: var(--body);
  font-size: 17px;
  line-height: 1.6;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
}

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

h1, h2, h3 { font-family: var(--body); font-weight: 300; line-height: 1.15; margin: 0 0 .5em; letter-spacing: -.01em; }
h1 { font-size: clamp(34px, 5.2vw, 58px); }
h2 { font-size: clamp(27px, 3.4vw, 40px); }
h3 { font-size: clamp(20px, 2vw, 24px); font-weight: 400; }
p { margin: 0 0 1em; max-width: var(--measure); }
p:last-child { margin-bottom: 0; }
strong { font-weight: 700; }

a { color: var(--green); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: .18em; }
a:hover { color: var(--green-deep); }
a:focus-visible, button:focus-visible, summary:focus-visible { outline: 3px solid var(--green); outline-offset: 3px; border-radius: 2px; }

.label {
  font-family: var(--display);
  /* 13px on a phone and up to about 1080px, then 14px, so a one- or two-word label (Cost-plus,
     Questions) holds its own (Billy, 2026-11-04; was 11px on a phone). */
  font-size: clamp(13px, calc(7px + .55vw), 14px);
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--green);
  margin: 0 0 16px;
}
/* Phone: the label sits close on its heading, so the two read as one stack (Billy, 2026-11-04;
   was 16px, about 18px between the letters). */
@media (max-width: 600px) { .label { margin-bottom: 6px; } }
/* Home's hero label keeps its old size (11px on a phone, up to 14px): two lines over the big
   heading, and Billy liked it as it was. */
.hero .label { font-size: clamp(11px, calc(7px + .55vw), 14px); }
/* "Proudly serving" over the place (Billy, 2026-11-05): the same face, smaller, wider spaced and
   grey, close on the green line so the two read as one. Phones: 10px, and the green line spaced a
   touch tighter so it stays on one line from 360 wide up (two lines, as on a computer). */
.hero .label-pre { display: block; font-size: 10.5px; line-height: 1.3; letter-spacing: .34em;
  color: var(--muted); margin-bottom: 3px; }
@media (max-width: 600px) {
  .hero .label { letter-spacing: .15em; }
  .hero .label-pre { font-size: 10px; letter-spacing: .3em; margin-bottom: 2px; }
}
/* Laptop: a smaller headline (Billy, 2026-11-05; was up to 58px), 50px at 1280 and up, scaling to
   38 at 860 where the column is narrow. Phones keep 34px. */
@media (min-width: 860px) { .hero h1 { font-size: clamp(38px, 3.9vw, 50px); } }

.wrap { width: min(100% - 2 * var(--gutter), var(--wide)); margin-inline: auto; }
.section { padding-block: var(--space-section); }
.section + .section { padding-top: 0; }
.hr { border: 0; border-top: 1px solid var(--hair); margin: 0; }

/* Buttons: one filled, one quiet. Both tall enough to tap. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: var(--tap); padding: 10px 22px;
  border-radius: 999px; border: 1px solid var(--green);
  font: inherit; font-weight: 400; text-decoration: none;
  background: var(--green); color: var(--cream-2);
}
.btn:hover { background: var(--green-deep); border-color: var(--green-deep); color: var(--cream-2); }
.btn--quiet { background: transparent; color: var(--green); }
.btn--quiet:hover { background: rgba(77, 111, 57, .08); color: var(--green-deep); }
.btn--light { background: var(--cream-2); color: var(--green); border-color: var(--cream-2); }
.btn--light:hover { background: var(--cream); border-color: var(--cream); color: var(--green-deep); }

/* The site's one emphasis mark: a word in a tight box (Billy, 2026-11-05: "cost-plus" on Home, and
   only there since the tiles' verbs came off). Ink on a pale spruce tint (Billy, 2026-11-05, was
   cream on the spruce green): filled green stays for the actions, Get in touch and Call, so the
   mark reads as a tag, not a button. 12:1 contrast. For spaced capitals (.2em): the right padding is
   less by the letter-spacing, so the word sits centred in its box. */
.mark { display: inline-block; padding: .3em calc(.5em - .2em) .3em .5em; background: var(--green-pale); color: var(--ink); border-radius: 3px; line-height: 1; }
.label .mark { margin: -.3em .1em; }

/* ---- header ----------------------------------------------------------------------------- */
.site-header { position: sticky; top: 0; z-index: 10; background: rgba(249, 248, 243, .94); backdrop-filter: blur(8px); border-bottom: 1px solid var(--hair); }
.site-header .wrap { display: flex; align-items: center; justify-content: space-between; min-height: 72px; gap: 16px; }
.brand { display: inline-flex; align-items: center; gap: 12px; text-decoration: none; color: var(--green); min-height: var(--tap); }
.brand svg { width: 40px; height: 40px; flex: none; }
.brand .wordmark { font-family: var(--display); font-weight: 500; font-size: 13px; letter-spacing: .22em; text-transform: uppercase; white-space: nowrap; }
.nav { display: flex; align-items: center; gap: 4px; }
.nav a { display: inline-flex; align-items: center; min-height: var(--tap); padding: 0 14px; text-decoration: none; color: var(--ink); font-size: 16px; }
.nav a:hover { color: var(--green); }
.nav a[aria-current="page"] { color: var(--green); text-decoration: underline; text-underline-offset: .35em; }
/* Spec 51: the nav, then the call icon, then Get in touch. */
.head-end { display: flex; align-items: center; gap: 4px; }
.head-touch { margin-left: 4px; padding: 0 22px; }
/* The call icon (spec 51 §3.4): a 44px disc with a handset, at every width; the number beside it
   from 1000px, where the nav has room (it would wrap the header at 761). Muted to match the
   cost-plus mark (Billy, 2026-11-05: it was a filled green disc): an ink handset on the pale tint,
   a shade deeper on hover, so the filled green is Get in touch and the Call buttons alone. */
.call { display: inline-flex; align-items: center; gap: 10px; min-height: var(--tap); padding: 0; border-radius: 999px; color: var(--ink); text-decoration: none; white-space: nowrap; font-size: 16px; }
.call svg { flex: none; width: var(--tap); height: var(--tap); padding: 12px; border-radius: 50%; background: var(--green-pale); color: var(--ink); }
.call:hover { color: var(--green); }
.call:hover svg { background: #CBD8BE; }
.call:focus-visible { outline-offset: 2px; border-radius: 999px; }
.call-n { display: none; }
/* The phone's speech bubble (Billy, 2026-11-05): the call icon's circle, to the contact band; phones only. */
.head-contact { display: none; }
@media (max-width: 760px) { .head-end .head-contact { display: inline-flex; } }
@media (min-width: 1000px) { .call { padding-right: 10px; margin-left: 6px; } .call-n { display: inline; } }
/* On a phone (spec 49 §3.3, spec 51 §3.7) the header is the brand and the call icon, and scrolls
   away with the page: nothing floats. Links in the page and the footer carry the reader. */
@media (max-width: 760px) {
  .site-header { position: relative; }
  .head-end .nav, .head-touch, .head-end .call:not(.head-contact) { display: none; }
  .brand .wordmark { font-size: 11px; letter-spacing: .18em; }
  html { scroll-padding-top: 16px; }
}

@media (max-width: 380px) { .brand .wordmark { display: none; } }

/* ---- hero ------------------------------------------------------------------------------- */
.hero { padding-top: clamp(40px, 7vw, 96px); }
/* Laptop: closer under the header (Billy, 2026-11-05): up to 52px (was up to 96), a little less than
   the gap between Home's sections (68), so the page starts right away. Phones keep 40px. */
@media (min-width: 601px) { .hero { padding-top: clamp(36px, 3.8vw, 52px); } }
.hero .wrap { display: grid; gap: clamp(24px, 5vw, 64px); align-items: center; }
/* Wide screens: the hero sits on the tiles' grid (Billy, 2026-11-05): two equal columns with the
   tiles' 12px gap, so the photo starts exactly at the third tile's left edge and ends at the
   fourth's right edge (was 5fr 6fr with a 64px gap, which landed in neither). The text column's
   room before the photo is padding, so the photo's edge stays on the grid. */
@media (min-width: 860px) {
  .hero .wrap { grid-template-columns: 1fr 1fr; gap: 12px; --hero-pad: clamp(24px, 3.5vw, 56px); }
  .hero .wrap > div { padding-right: var(--hero-pad); }
  /* The job site runs through that padding (Billy, 2026-11-05): it starts on the first tile's
     left edge and ends on the second's right edge, 12px from the photo (it stopped 24 to 56px
     short, inside the padding, and was capped at 620). The headline and lede keep the room. */
  .hero .wrap > div > .build { width: calc(100% + var(--hero-pad)); max-width: none;
    margin-right: calc(-1 * var(--hero-pad)); }
}
/* Wide screens: the photo fills the row's height, so when the text runs taller (Billy's longer
   line, 2026-11-04) the photo's foot still meets the tiles 12px below, and so do the buttons. It
   keeps 3:2 as its least height. */
@media (min-width: 860px) {
  .hero .wrap { align-items: stretch; }
  .hero-photo, .hero picture { display: block; height: 100%; }
  .hero picture img { height: 100%; }
}
.hero p.lede { font-size: clamp(18px, 1.6vw, 21px); color: var(--muted); max-width: 44ch; }
.hero .actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 28px; }
.hero picture img { border-radius: var(--radius); aspect-ratio: 3 / 2; object-fit: cover; }
.hero-photo { position: relative; margin: 0; }
.page-hero { padding-top: clamp(40px, 7vw, 96px); }
.page-hero .lede { font-size: clamp(18px, 1.6vw, 21px); color: var(--muted); }

/* Home, phone: 96px between sections (48 + 48) instead of 56, so a break reads as a break.
   Scoped to Home by its photo breaks (only Home has them); desktop keeps --space-section. */
@media (max-width: 600px) {
  main:has(> .section--break) > .section { padding-block: 48px; }
}


/* ---- The hero's job site (Billy, 2026-11-05) -------------------------------------------------
   site/src/partials/build-scene.html. Every part rests in its finished state; the animations only
   play from the start up to it, so reduced motion (and anything that skips animation) gets the
   finished site. Colours are the plans' (pine, glass, wall) plus spruce and a hard-hat yellow. */
/* The palette is shared with the site's other scenes (spec 53's kit: the footer's end of the day,
   the thanks page), so they all draw in the same colours; the hero's other rules stay on .build.
   --b-truck is the M/W pickup's body (white, with the slate line). --b-hat is the sun, star and moon.
   Spec 57 (Billy, 2026-11-05): the house and crew look like our work, weathered cedar, a dark
   charcoal metal roof (Billy: the grey galvalume looked wrong), black frames and big lit glass; the
   crew in charcoal, navy and heather with white hard hats; trees in sage. Brand green is on the sign, the pickup's monogram and the barrow's tub. */
.build,.scene{--b-wall:#3A3637;--b-line:#8C877F;--b-pine:#EAD9B6;--b-pine-ln:#C2A574;--b-glass:#DCE8EE;--b-glass-ln:#8FA9B5;--b-stone:#C9C5BC;--b-hat:#F2C230;--b-hat-ln:#C9960E;--b-lamp:#F8DC86;--b-truck:#FFFFFA;
  --b-cedar-1:#8E6C4E;--b-cedar-2:#9A7657;--b-cedar-3:#84644A;--b-cedar-4:#A07D5D;--b-cedar-ln:#6E523B;--b-metal:#3A3F42;--b-metal-hl:#646B6F;--b-frame:#262324;--b-glass-lit:#F2DCA0;--b-steel:#9AA1A4;
  --b-helmet:#F4F2EC;--b-helmet-ln:#8C877F;--b-shirt-1:#3D3A3B;--b-shirt-2:#33405A;--b-shirt-3:#8A8781;--b-leaf:#E1E6D8;--b-leaf-ln:#6F8263;--b-bark:#7A6A58;--b-grass:#E4E8DC}
.build{display:block;width:100%;max-width:620px;height:auto;overflow:hidden;margin-top:32px}
.build *{transform-box:fill-box}
/* reveal.js makes it a button to watch the build again (motion allowed, with a script). */
.build.is-replayable{cursor:pointer;-webkit-tap-highlight-color:transparent;border-radius:4px}
.build.is-replayable:focus-visible{outline:2px solid var(--green);outline-offset:4px}
.build .place{transform-box:view-box}
.build .ln{fill:none;stroke:var(--ink);stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.build .leg{stroke-width:2.4}
/* the far leg a shade lighter, so you can follow each leg as they pass (Billy, 2026-11-05) */
.build .w1b,.build .w3b,.build .leg.b{stroke:#504B4A}
.build .ground{stroke:var(--b-line);stroke-width:1.2}
.build .grass{fill:var(--b-grass)}
.build .treeline{fill:#E7EDE0}
.build .shadow{fill:rgba(38,35,36,.09)}
.build .slab{fill:var(--b-wall)}
.build .stud{stroke:var(--b-pine-ln);stroke-width:2.2;stroke-linecap:round}
.build .plate{fill:none;stroke:var(--b-pine-ln);stroke-width:2.4;stroke-linecap:round}
/* The finished house (spec 57): weathered cedar (the gable and wall take the drawing's own cedar
   pattern by their fill attribute, so the url can't be read against site.css's address), a galvalume
   roof with square ends and a thin highlight along its top, black frames, a black steel door. */
.build .cb1,.scene .cb1{fill:var(--b-cedar-1)}.build .cb2,.scene .cb2{fill:var(--b-cedar-2)}.build .cb3,.scene .cb3{fill:var(--b-cedar-3)}.build .cb4,.scene .cb4{fill:var(--b-cedar-4)}.build .cj,.scene .cj{fill:var(--b-cedar-ln)}
.build .clad{stroke:var(--b-cedar-ln);stroke-width:.8}
.build .roof{fill:none;stroke:var(--b-metal);stroke-width:4.4;stroke-linecap:butt;stroke-linejoin:miter}
.build .roof-hl{fill:none;stroke:var(--b-metal-hl);stroke-width:.8;stroke-linejoin:miter}
.build .ridge{fill:none;stroke:var(--b-metal);stroke-width:3;stroke-linecap:square;stroke-linejoin:miter}
.build .truss{fill:none;stroke:var(--b-pine-ln);stroke-width:2;stroke-linecap:round}
.build .web{stroke-width:1.6}
.build .gable{stroke:none}
.build .chimney{fill:var(--b-frame)}
/* Lights on at the end (Billy, 2026-11-05): at rest the panes glow, so reduced motion gets the lit
   house; spec 57's softer lit glass, since there's so much more of it. */
.build .pane{fill:var(--b-glass-lit)}
.build .frame{fill:none;stroke:var(--b-frame);stroke-width:1.8}
.build .mull{fill:none;stroke:var(--b-frame);stroke-width:1.3}
.build .door{fill:var(--b-frame)}
.build .pull{stroke:var(--b-steel);stroke-width:1.2;stroke-linecap:round}
.build .head{fill:var(--cream-2);stroke:var(--ink);stroke-width:1.5}
.build .hat{fill:var(--b-helmet);stroke:var(--b-helmet-ln);stroke-width:.9;stroke-linejoin:round}
.build .brim{stroke:var(--b-helmet-ln);stroke-width:2.2;stroke-linecap:round}
.build .hat-rib{fill:none;stroke:var(--b-helmet-ln);stroke-width:1;stroke-linecap:round}
/* The crew (spec 57): each man's shirt through --shirt, set by .crew-2 and .crew-3 on his group;
   charcoal by default. White hard hats, outlined so they read on the cream. */
.build .shirt{fill:var(--shirt,var(--b-shirt-1))}
.build .crew-2,.scene .crew-2{--shirt:var(--b-shirt-2)}.build .crew-3,.scene .crew-3{--shirt:var(--b-shirt-3)}
.build .mw{font-family:"Lexend Giga",system-ui,sans-serif;font-size:4.6px;font-weight:500;letter-spacing:.1px;fill:var(--cream-2);text-anchor:middle}
/* The ladder over his shoulder shows only while he walks; at rest it leans on the wall. */
.build .carried{opacity:0}
/* Also hidden at rest (Billy, 2026-11-05): the stone in the barrow (it's in the path now), the chimney's
   smoke, and the two arms that only come up in the crew's loops (a wave, a brow wipe). */
.build .load,.build .smoke,.build .wave,.build .wipe{opacity:0}
.build .puff{fill:#CCC7BD}
.build .hand{fill:var(--cream-2);stroke:var(--ink);stroke-width:1.2}
.build .thumb{stroke:var(--ink);stroke-width:1.5;stroke-linecap:round}
.build .stone{fill:#BEB9AF;stroke:#8F8A80;stroke-width:.9}
.build .lad{fill:none;stroke:var(--b-steel);stroke-width:1.6;stroke-linecap:round}
.build .tree{fill:var(--b-leaf);stroke:var(--b-leaf-ln);stroke-width:1.4}
.build .trunk{stroke:var(--b-bark);stroke-width:1.6;stroke-linecap:round}
.build .tick{stroke:var(--b-line);stroke-width:1.4;stroke-linecap:round}
.build .plank{fill:var(--b-pine);stroke:var(--b-pine-ln);stroke-width:.8}
.build .nail-ln{stroke:var(--ink);stroke-width:1.25;stroke-linecap:round}
/* The barrow's tub in the brand green (Billy, 2026-11-05, after spec 57): the one piece of the
   job site, beside the sign and the pickup's monogram, that keeps it. */
.build .tub{fill:var(--green);stroke:var(--ink);stroke-width:1.2;stroke-linejoin:round}
.build .rock{fill:var(--b-stone);stroke:#9E998F;stroke-width:.8}
.build .wheel{fill:var(--cream-2);stroke:var(--ink);stroke-width:1.6}
.build .spoke{stroke:var(--ink);stroke-width:1}
.build .sign{fill:var(--cream-2);stroke:var(--b-line);stroke-width:1}
.build .post{stroke:var(--b-wall);stroke-width:2.4;stroke-linecap:round}
.build .sign-t{font-family:"Lexend Giga",system-ui,sans-serif;font-size:11.2px;font-weight:500;letter-spacing:.9px;fill:var(--green)}
.build .sign-s{font-family:"Lexend Giga",system-ui,sans-serif;font-size:5.15px;letter-spacing:.8px;fill:var(--green)}

@media (prefers-reduced-motion: no-preference){
  .build .slab{animation:b-grow-x .5s .2s cubic-bezier(.3,.7,.3,1) both;transform-origin:left}
  .build .stud{animation:b-grow-y .45s cubic-bezier(.3,.8,.3,1.2) both;transform-origin:bottom;animation-delay:calc(.7s + var(--i) * .09s)}
  .build .plate{stroke-dasharray:1;animation:b-draw .5s 1.6s ease-out both}
  /* The roof (Billy, 2026-11-05): the truss frames up, the gable closes over it, then the panels
     go on from the eaves to the ridge (both sides at once), then the ridge cap and the chimney. */
  .build .chord{stroke-dasharray:1;animation:b-draw .45s 2.05s ease-out both}
  .build .web{stroke-dasharray:1;animation:b-draw .3s 2.5s ease-out both}
  .build .web.w2{animation-delay:2.65s}
  .build .gable{animation:b-fade .45s 2.95s both}
  .build .roof{animation:b-lay .28s cubic-bezier(.3,.8,.3,1.2) both;animation-delay:calc(3.2s + var(--i) * .1s)}
  .build .ridge{animation:b-lay .3s 3.75s cubic-bezier(.3,.8,.3,1.3) both}
  .build .roof-hl{animation:b-fade .3s 3.75s both}
  .build .chimney{animation:b-fade .4s 3.85s both}
  .build .cladding{animation:b-fade .6s 4s both}
  .build .win,.build .door-g{animation:b-pop .4s cubic-bezier(.3,.8,.3,1.4) both;transform-origin:center}
  .build .win{animation-delay:4.4s}.build .door-g{animation-delay:4.6s}
  /* The two walkers ease into place instead of stopping dead, and the barrow sets off later so he
     and the ladder never knot together; both come out from behind the sign (Billy, 2026-11-05). */
  .build .walker{animation:b-walk 3s .1s cubic-bezier(.25,.1,.3,1) both}
  .build .w1a,.build .w3a{transform-origin:top right}
  .build .w1b,.build .w3b{transform-origin:top left}
  /* A real stride (Billy, 2026-11-05: they hopped like scissors): each leg swings from behind him
     to in front, out of step with the other, so they pass each other mid-stride; he rises a touch
     as they pass (a vertical leg reaches lower than a spread one), so his feet stay on the ground.
     Each leg's angle at rest is 15° off vertical (behind for a, in front for b). */
  .build .w1a,.build .w3a{animation:b-stride-a .66s ease-in-out backwards}
  .build .w1b,.build .w3b{animation:b-stride-b .66s ease-in-out backwards}
  .build .bob{animation:b-bob .33s ease-in-out 8 .1s backwards}
  .build .bob.b3{animation-duration:.35s;animation-delay:1.1s}
  .build .w1a,.build .w1b{animation-iteration-count:4;animation-delay:.1s}
  .build .barrow{animation:b-walk 3s 1.1s cubic-bezier(.2,0,.3,1) both}
  .build .w3a,.build .w3b{animation-duration:.7s;animation-iteration-count:4;animation-delay:1.1s}
  .build .spokes{transform-origin:center;animation:b-roll 3s 1.1s cubic-bezier(.2,0,.3,1) both}
  /* The stone path (Billy, 2026-11-05): the barrow tips forward on its wheel (its fill-box corner
     is 188,90.2, so the axle is 28px 27.8px in), his arm follows the handles, the load goes, and the
     path's stones are laid from the front of the lot up to the door. */
  .build .tip{transform-origin:28px 27.8px;animation:b-tip 1s 4.45s ease-in-out both}
  .build .b-arm{transform-origin:top left;animation:b-arm 1s 4.45s ease-in-out both}
  .build .load{animation:b-unload .25s 4.75s both}
  .build .stone{animation:b-lay .3s cubic-bezier(.3,.8,.3,1.2) both;animation-delay:calc(4.95s + var(--i) * .16s)}
  /* Smoke once the flue's on, a puff a second for good. */
  .build .smoke{opacity:1}
  .build .puff{transform-origin:center;animation:b-smoke 3s ease-out infinite both;animation-delay:calc(4.3s + var(--i) * 1s)}
  .build .pane{animation:b-light 1.4s 7.9s ease-in-out both}
  /* The crew's loops once the work's done, each on its own period: the ladder man waves a
     thumbs-up (first about 6s), the barrow man wipes his brow (first about 7.2s). */
  .build .wave{opacity:1;transform-origin:left bottom;animation:b-wave 5.5s 2.2s infinite both}
  .build .wave-off{animation:b-wave-off 5.5s 2.2s infinite both}
  .build .wipe{opacity:1;transform-origin:left bottom;animation:b-wipe 6.6s 2.45s infinite both}
  .build .carried{animation:b-carry 3.15s both}
  .build .leaning{transform-origin:bottom left;animation:b-lean .55s 3.15s cubic-bezier(.4,.1,.3,1) both}
  .build .arm{transform-origin:top left;animation:b-hammer .7s 1s infinite ease-in both}
  .build .tick{animation:b-tick .7s 1s infinite both}
  /* The nail goes down a step on each strike (76% into each .7s swing), three swings a nail. */
  .build .nail{animation:b-nail 2.1s 1s infinite linear both}
  .build .tree-g{animation:b-pop .5s 4.8s cubic-bezier(.3,.8,.3,1.3) both;transform-origin:bottom}
  /* More trees after the house, one by one, then every tree sways for good (Billy, 2026-11-05). */
  .build .sprout{animation:b-pop .5s cubic-bezier(.3,.8,.3,1.4) both;transform-origin:bottom}
  .build .t1{animation-delay:5.3s}.build .s1{animation-delay:5.7s}.build .s2{animation-delay:6s}.build .s3{animation-delay:6.3s}.build .t2{animation-delay:6.8s}.build .s4{animation-delay:7.3s}.build .s5{animation-delay:7.6s}
  .build .sway{transform-origin:bottom;animation:b-sway 3.6s ease-in-out infinite alternate}
  .build .t1 .sway{animation-delay:-1.8s;animation-duration:4.2s}.build .t2 .sway{animation-delay:-1.1s;animation-duration:3.2s}
  .build .sign-g{animation:b-pop .45s .15s cubic-bezier(.3,.8,.3,1.3) both;transform-origin:bottom}
}
@keyframes b-grow-x{from{transform:scaleX(0)}}
@keyframes b-grow-y{from{transform:scaleY(0)}}
@keyframes b-draw{from{stroke-dashoffset:1}to{stroke-dashoffset:0}}
@keyframes b-fade{from{opacity:0}}
@keyframes b-sway{from{transform:rotate(-2.5deg)}to{transform:rotate(2.5deg)}}
@keyframes b-lay{from{opacity:0;transform:translateY(-6px)}}
@keyframes b-pop{from{transform:scale(0);opacity:0}}
@keyframes b-walk{from{transform:translateX(-300px)}}
@keyframes b-stride-a{0%,100%{transform:rotate(7deg)}25%{transform:rotate(-15deg) scaleY(.8)}50%{transform:rotate(-37deg)}}
@keyframes b-stride-b{0%,100%{transform:rotate(-7deg)}50%{transform:rotate(37deg)}75%{transform:rotate(15deg) scaleY(.8)}}
@keyframes b-bob{50%{transform:translateY(-1px)}}
@keyframes b-roll{from{transform:rotate(-1400deg)}}
@keyframes b-carry{0%,99.9%{opacity:1}}
@keyframes b-lean{from{transform:rotate(-74deg);opacity:0}8%{opacity:1}}
@keyframes b-hammer{0%,50%{transform:rotate(-75deg)}76%,82%{transform:rotate(0deg)}100%{transform:rotate(-75deg)}}
@keyframes b-tick{0%,75%{opacity:0}78%{opacity:1}94%,100%{opacity:0}}
@keyframes b-nail{0%{opacity:0;transform:translateY(-2px)}6%,25.3%{opacity:1;transform:translateY(0)}26.5%,58.7%{transform:translateY(.6px)}59.9%,92%{transform:translateY(1.2px)}93.2%,99%{opacity:1;transform:translateY(1.75px)}100%{opacity:0;transform:translateY(1.75px)}}
@keyframes b-tip{35%,60%{transform:rotate(12deg)}}
@keyframes b-arm{35%,60%{transform:rotate(-29deg)}}
@keyframes b-unload{from{opacity:1}to{opacity:0}}
@keyframes b-smoke{0%{opacity:0;transform:translate(0,0) scale(.6)}20%{opacity:.9}100%{opacity:0;transform:translate(6px,-12px) scale(2)}}
@keyframes b-light{from{fill:var(--b-glass)}}
@keyframes b-wave{0%,70%{opacity:0;transform:rotate(150deg)}74%{opacity:1;transform:rotate(0deg)}78%{transform:rotate(-14deg)}82%{transform:rotate(6deg)}86%{transform:rotate(-14deg)}90%{opacity:1;transform:rotate(0deg)}95%,100%{opacity:0;transform:rotate(150deg)}}
@keyframes b-wave-off{0%,70%{opacity:1}73%,92%{opacity:0}95%,100%{opacity:1}}
@keyframes b-wipe{0%,72%{opacity:0;transform:rotate(110deg)}76%{opacity:1;transform:rotate(0deg)}80%{transform:rotate(-9deg)}84%{transform:rotate(3deg)}88%{transform:rotate(-9deg)}91%{opacity:1;transform:rotate(0deg)}95%,100%{opacity:0;transform:rotate(110deg)}}

/* ---- The scenes: the same job site elsewhere on the site (spec 53, Billy, 2026-11-05) ---------
   The footer's end of the day (partials/end-of-day.html) and the thanks page's thumbs-up
   (thanks-scene.html), drawn from the kit's partials (scene-crew, scene-house, scene-conifer,
   scene-round-tree, scene-treeline) in the hero's palette, its line weights and its keyframes
   (b-stride-a/-b, b-bob, b-pop, b-sway, b-smoke, b-light, b-wave, b-fade, b-carry); new ones are
   b- too, so reveal.js's replay covers them. As in the hero, every part rests in its finished state
   and the animations only play up to it; reduced motion gets the rest, still. */
.scene{display:block;width:100%;overflow:hidden}
.scene *{transform-box:fill-box}
.scene .place{transform-box:view-box}
.scene.is-replayable{cursor:pointer;-webkit-tap-highlight-color:transparent;border-radius:4px}
.scene.is-replayable:focus-visible{outline:2px solid var(--green);outline-offset:4px}
.scene .ln{fill:none;stroke:var(--ink);stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.scene .leg{stroke-width:2.4}
.scene .lb{stroke:#504B4A}
.scene .ground{stroke:var(--b-line);stroke-width:1.2}
.scene .grass{fill:var(--b-grass)}
.scene .treeline{fill:#E7EDE0}
.scene .shadow{fill:rgba(38,35,36,.09)}
.scene .slab{fill:var(--b-wall)}
/* The house as the hero's (spec 57): its cedar pattern's colours are with the hero's rules. */
.scene .clad{stroke:var(--b-cedar-ln);stroke-width:.8}
.scene .roof{fill:none;stroke:var(--b-metal);stroke-width:4.4;stroke-linecap:butt;stroke-linejoin:miter}
.scene .roof-hl{fill:none;stroke:var(--b-metal-hl);stroke-width:.8;stroke-linejoin:miter}
.scene .ridge{fill:none;stroke:var(--b-metal);stroke-width:3;stroke-linecap:square;stroke-linejoin:miter}
.scene .gable{stroke:none}
.scene .chimney{fill:var(--b-frame)}
.scene .pane{fill:var(--b-glass-lit)}
.scene .frame{fill:none;stroke:var(--b-frame);stroke-width:1.8}
.scene .mull{fill:none;stroke:var(--b-frame);stroke-width:1.3}
.scene .door{fill:var(--b-frame)}
.scene .pull{stroke:var(--b-steel);stroke-width:1.2;stroke-linecap:round}
.scene .head{fill:var(--cream-2);stroke:var(--ink);stroke-width:1.5}
.scene .hat{fill:var(--b-helmet);stroke:var(--b-helmet-ln);stroke-width:.9;stroke-linejoin:round}
.scene .brim{stroke:var(--b-helmet-ln);stroke-width:2.2;stroke-linecap:round}
.scene .hat-rib{fill:none;stroke:var(--b-helmet-ln);stroke-width:1;stroke-linecap:round}
.scene .shirt{fill:var(--shirt,var(--b-shirt-1))}
.scene .hand{fill:var(--cream-2);stroke:var(--ink);stroke-width:1.2}
.scene .thumb{stroke:var(--ink);stroke-width:1.5;stroke-linecap:round}
.scene .lad{fill:none;stroke:var(--b-steel);stroke-width:1.6;stroke-linecap:round}
.scene .tree{fill:var(--b-leaf);stroke:var(--b-leaf-ln);stroke-width:1.4}
.scene .trunk{stroke:var(--b-bark);stroke-width:1.6;stroke-linecap:round}
.scene .puff{fill:#CCC7BD}
.scene .sign{fill:var(--cream-2);stroke:var(--b-line);stroke-width:1}
.scene .post{stroke:var(--b-wall);stroke-width:2.4;stroke-linecap:round}
.scene .sign-t{font-family:"Lexend Giga",system-ui,sans-serif;font-size:11.2px;font-weight:500;letter-spacing:.9px;fill:var(--green)}
.scene .sign-s{font-family:"Lexend Giga",system-ui,sans-serif;font-size:5.15px;letter-spacing:.8px;fill:var(--green)}
/* the evening sky, and the M/W pickup (white, the slate line, M/W on the door) */
.scene .sun{fill:var(--b-lamp);stroke:var(--b-hat);stroke-width:1}
.scene .star{fill:var(--b-lamp);stroke:var(--b-hat-ln);stroke-width:.6;stroke-linejoin:round}
.scene .moon{fill:var(--b-lamp);stroke:var(--b-hat-ln);stroke-width:.7}
.scene .body,.scene .gate{fill:var(--b-truck);stroke:var(--b-wall);stroke-width:1.4;stroke-linejoin:round}
.scene .cab-glass{fill:var(--b-glass);stroke:var(--b-glass-ln);stroke-width:1;stroke-linejoin:round}
.scene .seam{stroke:var(--b-line);stroke-width:.8}
.scene .headlamp{fill:var(--b-lamp);stroke:var(--b-hat-ln);stroke-width:.6}
.scene .rack{fill:none;stroke:var(--b-wall);stroke-width:1.6;stroke-linecap:round}
.scene .tyre{fill:var(--b-wall)}
.scene .hub{fill:var(--b-stone)}
.scene .spoke{stroke:var(--b-wall);stroke-width:1.1;stroke-linecap:round}
.scene .box{fill:var(--b-shirt-1);stroke:var(--ink);stroke-width:1.1}
.scene .box-h{fill:none;stroke:var(--ink);stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round}
/* At rest: the sun has set, the crew and the truck have gone, no smoke or dust. The arms each man
   only uses for a moment are hidden too (the men are, anyway). */
.scene .sun,.scene .truck,.scene .man,.scene .smoke,.scene .dust{opacity:0}
.scene .arm-carry,.scene .arm-lift,.scene .arm-hold,.scene .arm-up,.scene .carry-box,.scene .carry-lad,.scene .wave{opacity:0}

/* The footer's strip: a fixed height so nothing shifts as it draws, sliced from the middle. */
.site-footer .end-of-day{height:96px;margin-bottom:16px}
@media (min-width: 761px){.site-footer .end-of-day{height:128px}}
/* reveal.js (spec 53 §3.4): a scene doesn't fade in like a section (its own motion is its
   arrival), but waits until it's on screen to start, and rests while it's off screen. */
.reveal-armed svg.scene[data-reveal]{opacity:1;transform:none}
.reveal-armed .scene:not(.is-in) *,.is-off *{animation-play-state:paused}
/* reveal.js's replay: everything inside a drawing off for one style pass, so it all starts over. */
.is-replaying *{animation:none !important}
/* The thanks page's sign and thumbs-up, left-aligned with the text, between the lede and the buttons. */
.thanks-scene{width:160px;height:64px;margin-top:24px}

@media (prefers-reduced-motion: no-preference){
  .scene .sway{transform-origin:bottom;animation:b-sway 3.6s ease-in-out infinite alternate}
  .scene .t1 .sway{animation-delay:-1.8s;animation-duration:4.2s}.scene .t2 .sway{animation-delay:-.6s;animation-duration:3.4s}
  .scene .t3 .sway{animation-delay:-1.1s;animation-duration:3.2s}.scene .t4 .sway{animation-delay:-1.4s;animation-duration:4s}
  .scene .t5 .sway{animation-delay:-2.4s;animation-duration:3.9s}.scene .t6 .sway{animation-delay:-2s;animation-duration:3.3s}
  .scene .t7 .sway{animation-delay:-.9s;animation-duration:4.4s}
  /* 0 to 2.5s: the sun sinks into the treeline (fading as it reaches it, so it never shows in
     shards between the trees' points) while the two walk to the pickup, the hero's
     stride and bob (four strides each), easing to a stop. */
  .end-of-day .sun{animation:b-set 2.5s ease-in-out both}
  .end-of-day .m-lad{animation:b-ed-lad 3.5s linear both}
  .end-of-day .m-box{animation:b-ed-box 3.5s linear both}
  .end-of-day .la{transform-origin:top right;animation:b-stride-a .625s ease-in-out 4 backwards}
  .end-of-day .lb{transform-origin:top left;animation:b-stride-b .625s ease-in-out 4 backwards}
  .end-of-day .bob{animation:b-bob .3125s ease-in-out 8 backwards}
  /* 2.5 to 3.5s: the ladder up onto the rack and the toolbox into the bed (each handed over to
     the truck's own copy as it lands), the tailgate swings shut, and the two go in. */
  .end-of-day .arm-hold,.end-of-day .m-box .arm-carry{animation:b-carry 2.5s both}
  .end-of-day .m-box .arm-lift{animation:b-carry .4s 2.5s forwards}
  .end-of-day .m-box .arm-rest{animation:b-hide 2.9s}
  .end-of-day .m-lad .arm-rest{animation:b-hide 3s}
  .end-of-day .arm-up{transform-origin:bottom;animation:b-ed-arm-up 3s both}
  .end-of-day .carry-lad{animation:b-ed-lad-up 2.9s both}
  .end-of-day .carry-box{animation:b-ed-box-up 2.9s both}
  .end-of-day .lad-in,.end-of-day .box-in{animation:b-hide 2.88s}
  .end-of-day .gate{transform-origin:right bottom;animation:b-ed-gate 3.2s ease-in both}
  /* 3.8 to 5.8s: it pulls away and out of the frame, easing in; the wheels turn, a little dust. */
  .end-of-day .truck{animation:b-ed-drive 5.8s linear both}
  .end-of-day .spin{transform-origin:center;animation:b-ed-spin 2s 3.8s cubic-bezier(.55,0,.9,.55) both}
  .end-of-day .dust{opacity:1}
  .end-of-day .dust .puff{transform-origin:center;animation:b-dust .9s ease-out both;animation-delay:calc(3.8s + var(--i) * .15s)}
  /* 6 to 7.5s: the lights come on one after the other (left, right, the door's sidelight, then the
     gable's big window last; spec 57), a star and a thin moon come out; then the flue smokes, a
     puff a second, for good. */
  .end-of-day .pane{animation:b-light .4s ease-in-out both}
  .end-of-day .w1 .pane{animation-delay:6s}.end-of-day .w2 .pane{animation-delay:6.4s}
  .end-of-day .side{animation-delay:6.8s}.end-of-day .w3 .pane{animation-delay:7.1s}
  .end-of-day .star{animation:b-fade .9s 6.4s both}
  .end-of-day .moon{animation:b-fade 1.1s 6.7s both}
  .end-of-day .smoke{opacity:1}
  /* The thanks page: the sign pops up (as in the hero), then the thumbs-up at .6s and every 5.5s. */
  .thanks-scene .sign-g{animation:b-pop .45s .15s cubic-bezier(.3,.8,.3,1.3) both;transform-origin:bottom}
  .thanks-scene .wave{opacity:1;transform-origin:left bottom;animation:b-wave 5.5s -3.25s infinite both}
  .thanks-scene .wave-off{animation:b-wave-off 5.5s -3.25s infinite both}
  .end-of-day .smoke .puff{transform-origin:center;animation:b-smoke 3s ease-out infinite both;animation-delay:calc(7.5s + var(--i) * 1s)}
}
@keyframes b-hide{from,to{opacity:0}}
@keyframes b-set{0%{opacity:1;transform:none}45%{opacity:1}100%{opacity:0;transform:translateY(30px)}}
@keyframes b-ed-lad{0%{opacity:1;transform:translateX(-169px);animation-timing-function:cubic-bezier(.25,.1,.3,1)}71.4%{opacity:1;transform:none}91.4%{opacity:1}100%{opacity:0;transform:none}}
@keyframes b-ed-box{0%{opacity:1;transform:translateX(-167px);animation-timing-function:cubic-bezier(.25,.1,.3,1)}71.4%{opacity:1;transform:none}90%{opacity:1;transform:none}100%{opacity:0;transform:translateX(30px)}}
@keyframes b-ed-arm-up{0%,83.1%{opacity:0;transform:scaleY(.4)}83.2%{opacity:1;transform:scaleY(.4)}95%,99.9%{opacity:1;transform:none}100%{opacity:0;transform:none}}
@keyframes b-ed-lad-up{0%,86.2%{opacity:1;transform:none}98.3%,99.9%{opacity:1;transform:translateY(-15px)}100%{opacity:0;transform:translateY(-15px)}}
@keyframes b-ed-box-up{0%,86.2%{opacity:1;transform:none}98.3%,99.9%{opacity:1;transform:translate(10px,-20px)}100%{opacity:0;transform:translate(10px,-20px)}}
@keyframes b-ed-gate{0%,90.6%{transform:rotate(-90deg)}100%{transform:none}}
@keyframes b-ed-drive{0%,65.5%{opacity:1;transform:none;animation-timing-function:cubic-bezier(.55,0,.9,.55)}100%{opacity:1;transform:translateX(720px)}}
@keyframes b-ed-spin{to{transform:rotate(1440deg)}}
@keyframes b-dust{0%{opacity:0;transform:scale(.5)}25%{opacity:.8}100%{opacity:0;transform:translate(-16px,-5px) scale(2.2)}}

/* ---- Between sections (spec 54, Billy, 2026-11-05) -------------------------------------------
   Four small patches of the job site's ground, each the first thing in its chapter, right above
   its label: the keys handed over before Custom homes (partials/divider-homes.html, spec 58), a
   pendant hung and switched on before Renovations (divider-pendant.html), a sapling planted before
   Landscapes (divider-sapling.html), a modern sauna's flue steaming before Custom saunas
   (divider-sauna.html, redrawn by spec 58). Every divider's frame is 0 -80 W 84 (the ground at y 0, the
   sky to -80, the grass to 4), in a fixed box 52px tall (60 from 761px), as wide as its frame, so
   nothing shifts as it draws. Each rests on its last frame; reveal.js holds it until it's on
   screen, rests it off screen and makes it its own replay button, as every scene since spec 53. */
.divider{--dv-h:52px;height:var(--dv-h);margin:0 0 calc(6px - var(--dv-h) * 4 / 84)}
@media (min-width: 761px){.divider{--dv-h:60px;margin-bottom:calc(8px - var(--dv-h) * 4 / 84)}}
/* From the block above to the divider's top: 24px (28 from 761); from its ground to the label,
   6px (8), so the label stands on the drawing's ground (Billy, 2026-11-05; spec 54 had 16 and 20). Only the three chapters with a divider; on a phone the green block's own margin is
   the gap, wider up the chapter's top padding. */
@media (max-width: 600px){main:has(> .section--break) > .inverse:has(+ .chapter > .divider){margin-bottom:24px}}
@media (min-width: 601px){main:has(> .section--break) > .section.chapter:has(> .divider){padding-top:24px}}
@media (min-width: 761px){main:has(> .section--break) > .section.chapter:has(> .divider){padding-top:28px}}
/* Custom homes (spec 58 §3.1) follows the four tiles, not a green block: on a phone the tiles' own
   bottom padding is the 24px gap; from 601px the chapter's padding above is, so the tiles' goes. */
@media (max-width: 600px){#services:has(+ .chapter > .divider){padding-bottom:24px}}
@media (min-width: 601px){#services:has(+ .chapter > .divider){padding-bottom:0}}
/* The chapter's label has a top margin (.chapter .label) that used to fold into the gap above;
   under a divider it would add to the 16px, so it goes in these three. */
.chapter:has(> .divider) > .chapter-head .label{margin-top:0}
.divider .ground{stroke-linecap:round}
.dv-pendant{width:calc(var(--dv-h) * 132 / 84)}
/* the kitchen: a slate ceiling strip and counter, a pine island, the stepladder, two pendants */
.scene .ceiling,.scene .counter,.scene .shade,.scene .canopy{fill:var(--b-wall)}
.scene .island{fill:var(--b-pine);stroke:var(--b-pine-ln);stroke-width:1}
.scene .board{stroke:var(--b-pine-ln);stroke-width:.8}
.scene .cord{stroke:var(--ink);stroke-width:.7}
.scene .bulb{fill:var(--b-lamp);stroke:var(--b-hat-ln);stroke-width:.6}
.scene .glow,.scene .beam{fill:var(--b-lamp);stroke:none}
.scene .glow{fill-opacity:.5}
.scene .beam{fill-opacity:.32}
.scene .lad-cap{fill:none;stroke:var(--b-steel);stroke-width:2.6;stroke-linecap:round}
@media (prefers-reduced-motion: no-preference){
  /* 0 to .7s: he lifts it the last few units and it clicks in (his arm goes with it) */
  .dv-pendant .pd-lift{animation:b-dv-lift .7s ease-out both}
  .dv-pendant .arm-hold{animation:b-carry .7s both}
  .dv-pendant .dv-arm-l{animation:b-hide .7s}
  /* .7 to 1.5s: he lets go, the cord pays out and it swings about the ceiling, 8°, 4°, still */
  .dv-pendant .pd-drop{animation:b-dv-drop .16s .7s ease-in both}
  .dv-pendant .pd-swing{transform-origin:50% 9px;animation:b-dv-swing .8s .7s ease-out both}
  /* 1.8 to 2.2s: the lights come on, with one flicker */
  .dv-pendant .bulb{animation:b-light .4s 1.8s ease-in both}
  .dv-pendant .lit{animation:b-dv-flick .4s 1.8s linear both}
  /* 2.4 to 3.2s: he steps down off the ladder, in a small arc */
  .dv-pendant .dv-step{animation:b-dv-step .8s 2.4s ease-in-out both}
  .dv-pendant .dv-shadow{animation:b-hide 3.2s}
}
@keyframes b-dv-lift{0%{transform:translateY(4px)}75%{transform:translateY(-1px)}100%{transform:none}}
@keyframes b-dv-drop{from{transform:translateY(-8px)}}
@keyframes b-dv-swing{0%{transform:none}25%{transform:rotate(8deg)}55%{transform:rotate(-4deg)}80%{transform:rotate(1.2deg)}100%{transform:none}}
@keyframes b-dv-flick{0%{opacity:0}35%{opacity:1}50%{opacity:.3}65%,100%{opacity:1}}
@keyframes b-dv-step{0%{transform:translate(-22px,-20px)}45%{transform:translate(-12px,-24px)}100%{transform:none}}
.dv-sapling{width:calc(var(--dv-h) * 104 / 84)}
/* the planting: a mound of earth (the palette's nearest to soil), the spade */
.scene .mound,.scene .earth{fill:var(--b-pine-ln)}
.scene .spade{fill:none;stroke:var(--b-pine-ln);stroke-width:1.6;stroke-linecap:round}
.scene .blade{fill:var(--b-wall)}
/* At rest he leans on the planted spade: the specks and the slanted spade are only for digging. */
.dv-sapling .earth,.dv-sapling .dv-dig{opacity:0}
@media (prefers-reduced-motion: no-preference){
  /* 0 to 1.2s: two spadefuls, rocking about his far shoulder, earth thrown out at each */
  .dv-sapling .dv-rock{transform-origin:100% 0;animation:b-dv-dig .6s ease-in-out 2}
  .dv-sapling .earth{transform-origin:center;animation:b-dv-soil .6s linear 2 both}
  /* 1.2 to 1.5s: a sprout pops up in the hole; 1.5 to 3s it grows in two steps, swaying from the start */
  .dv-sapling .dv-sprout{transform-origin:bottom;animation:b-pop .3s 1.2s cubic-bezier(.3,.8,.3,1.3) both}
  .dv-sapling .dv-grow{transform-origin:bottom;animation:b-dv-grow 1.5s 1.5s ease-out both}
  .dv-sapling .sway{animation-delay:1.2s}
  /* 1.5 to 2.2s: he steps back half a pace, then plants the spade and leans on it */
  .dv-sapling .dv-back{animation:b-dv-back .7s 1.5s ease-in-out both}
  .dv-sapling .la{transform-origin:top right;animation:b-stride-a .7s 1.5s ease-in-out}
  .dv-sapling .lb{transform-origin:top left;animation:b-stride-b .7s 1.5s ease-in-out}
  .dv-sapling .dv-dig{animation:b-carry 2.3s both}
  .dv-sapling .dv-lean{animation:b-hide 2.3s}
  .dv-sapling .dv-planted{animation:b-lay .25s 2.1s ease-out both}
}
@keyframes b-dv-dig{0%{transform:none}30%{transform:rotate(-4deg)}70%{transform:rotate(14deg)}100%{transform:none}}
@keyframes b-dv-soil{0%,55%{opacity:0;transform:none}62%{opacity:1}82%{opacity:1;transform:translate(var(--dx),-7px)}100%{opacity:0;transform:translate(calc(var(--dx) * 1.5),-1px)}}
@keyframes b-dv-grow{0%{transform:scale(.25)}30%{transform:scale(.66)}40%,52%{transform:scale(.6)}85%{transform:scale(1.05)}100%{transform:none}}
@keyframes b-dv-back{from{transform:translateX(-8px)}}
/* ---- Before Custom homes: the keys (spec 58, Billy, 2026-11-05) ----------------------------
   partials/divider-homes.html. Every time is from --t0: 0 with a script (reveal.js holds the
   drawing until the hero is quiet, data-after="hero"), 9.5s with none, so it waits out the hero's
   build (9.3s); until then it shows its first frame, the door shut. At rest: the door open on the
   lit hall, the owner holding the key up by her shoulder, his arms down. */
.dv-keys{width:calc(var(--dv-h) * 144 / 84)}
.dv-homes{--t0:0s}
html:not(.js) .dv-homes{--t0:9.5s}
.scene .fascia{fill:var(--b-metal)}
.scene .hall{fill:var(--b-glass-lit)}
.scene .hall-lamp{fill:var(--b-lamp);stroke:var(--b-hat-ln);stroke-width:.4}
.scene .leaf{fill:var(--b-frame)}
.scene .ky-pull{stroke:var(--cream-2);stroke-width:1;stroke-linecap:round}
.scene .dv-frame{fill:none;stroke:var(--b-frame);stroke-width:1.6}
.scene .ky-glass{fill:var(--b-glass)}
/* the owner: a tan sweater and hair, no hard hat (she isn't crew); the key in brass */
.scene .owner{fill:var(--b-pine-ln)}
.scene .hair{fill:var(--b-wall)}
.scene .ky-ring{fill:none;stroke:var(--b-hat-ln);stroke-width:1}
.scene .ky-bit{fill:none;stroke:var(--b-hat-ln);stroke-width:1;stroke-linecap:round}
.dv-keys .ky-door{transform:scaleX(.16);transform-origin:left}
.dv-keys .ky-give,.dv-keys .ky-take{transform-box:view-box;transform-origin:3px -29px}
.dv-keys .ky-give{transform:rotate(70deg)}
.dv-keys .ky-take{transform:rotate(-45deg)}
.dv-keys .ky-key-c{opacity:0}
@media (prefers-reduced-motion: no-preference){
  /* .1 to .8s: the door swings open on its hinge; .3 to .9s the hall behind it lights */
  .dv-keys .ky-door{animation:b-dv-from .7s calc(var(--t0) + .1s) ease-in-out both}
  .dv-keys .hall,.dv-keys .hall-lamp{animation:b-light .6s calc(var(--t0) + .3s) ease-in both}
  /* .8 to 1.2s he brings the key up; 1.5 to 2s his arm drops */
  .dv-keys .ky-give{animation:b-dv-give 1.2s calc(var(--t0) + .8s) ease-in-out both}
  /* 1 to 1.4s she reaches for it; 1.5 to 2.1s she lifts it up by her shoulder */
  .dv-keys .ky-take{animation:b-dv-take 1.1s calc(var(--t0) + 1s) ease-in-out both}
  /* 1.45s: the key changes hands */
  .dv-keys .ky-key-c{animation:b-carry calc(var(--t0) + 1.45s) both}
  .dv-keys .ky-key-o{animation:b-hide calc(var(--t0) + 1.45s)}
}
@keyframes b-dv-from{from{transform:none}}
@keyframes b-dv-give{0%{transform:rotate(70deg)}33%,58%{transform:none}100%{transform:rotate(70deg)}}
@keyframes b-dv-take{0%{transform:rotate(70deg)}36%,45%{transform:none}100%{transform:rotate(-45deg)}}
/* ---- end of the keys ---- */
/* ---- Before Custom saunas: the modern sauna (spec 58 §3.5, replacing spec 54's cabin) -------
   A mono-pitch cedar box under a thin dark metal roof, one big window down to the floor, and
   through it the lit room: three tiers of pale pine benches, the heater and its stones, a bucket
   and ladle, a warm glow. The room and the door's slit light at .6s (from dark glass) and the
   room's inside fades in with them, since you can't see in through dark glass; from 1.8s steam.
   Lit glass here is .sn-room and .sn-lite, never .pane (spec 57's, lit at rest everywhere). */
.dv-sauna{width:calc(var(--dv-h) * 136 / 84)}
.scene .sn-roof{fill:none;stroke:var(--b-metal);stroke-width:3}
.scene .sn-room{fill:var(--b-glass-lit)}
.scene .sn-glow{fill:var(--b-lamp);fill-opacity:.9}
.scene .sn-wall-ln{stroke:var(--b-pine-ln);stroke-width:.6}
.scene .sn-tier{fill:var(--b-pine);stroke:var(--b-pine-ln);stroke-width:.9;stroke-linejoin:round}
.scene .sn-seat{stroke:var(--b-pine-ln);stroke-width:1.5}
.scene .sn-heater{fill:var(--b-wall)}
.scene .sn-heater-ln{stroke:var(--b-line);stroke-width:.6}
.scene .sn-stone{fill:var(--b-stone);stroke:var(--b-line);stroke-width:.5}
.scene .bucket{fill:var(--b-pine-ln);stroke:var(--b-wall);stroke-width:.6}
.scene .ladle{stroke:var(--b-wall);stroke-width:.8;stroke-linecap:round}
.scene .sn-shine{stroke:#fff;stroke-opacity:.5;stroke-width:1.2;stroke-linecap:round}
.scene .sn-frame{fill:none;stroke:var(--b-frame);stroke-width:1.8}
.scene .sn-door{fill:var(--b-frame)}
.scene .sn-lite{fill:var(--b-glass-lit)}
.scene .sn-pull{stroke:var(--cream-2);stroke-width:1;stroke-linecap:round}
/* the flue and the split logs (round pine ends), as spec 54's */
.scene .pipe{fill:var(--b-frame)}
.scene .log{fill:var(--b-pine);stroke:var(--b-pine-ln);stroke-width:1}
.scene .log-ring{fill:none;stroke:var(--b-pine-ln);stroke-width:.7}
/* At rest the room is lit and nothing steams. */
.dv-sauna .steam{opacity:0}
@media (prefers-reduced-motion: no-preference){
  /* .6 to 1.6s: the fire's lit; from 1.8s steam, higher, slower and wider than the hero's smoke */
  .dv-sauna .sn-room,.dv-sauna .sn-lite{animation:b-light 1s .6s ease-in-out both}
  .dv-sauna .sn-in{animation:b-fade 1s .6s both}
  .dv-sauna .steam{opacity:1}
  .dv-sauna .steam .puff{transform-origin:center;animation:b-steam 3.6s ease-out infinite both;animation-delay:calc(1.8s + var(--i) * 1.2s)}
}
@keyframes b-steam{0%{opacity:0;transform:translate(0,0) scale(.6)}18%{opacity:.85}100%{opacity:0;transform:translate(7px,-15px) scale(2.8)}}
@keyframes b-rg-settle{0%{rotate:0deg}20%{rotate:2deg}45%{rotate:-1deg}70%{rotate:.4deg}100%{rotate:0deg}}
/* ---- end of spec 54 ---- */

/* ---- photo breaks: a triad between Home's sections (Billy, 2026-11-04) -------------------
   In place of the old projects mosaic: one big
   photo (the first, a landscape original) and two small ones. Wide screen: the big one takes two
   thirds and the two small ones stack beside it, every frame about 4:3. Phone: the big one full
   width at 4:3, the two small ones square side by side under it. Same space above and below as
   between sections. */
.section--break { padding-bottom: var(--space-section); }
/* Triads stacked one after another: closer than a section break, so they read as one run. */
.section--break:has(+ .section--break) { padding-bottom: clamp(32px, 4vw, 56px); }
.photo-break { display: grid; gap: 12px; grid-template-columns: 2fr 1fr; grid-template-rows: 1fr 1fr; aspect-ratio: 2 / 1; }
.photo-break > :first-child { grid-row: 1 / 3; }
.photo-break picture { display: block; height: 100%; min-height: 0; }
.photo-break picture img { display: block; width: 100%; height: 100%; object-fit: cover; border-radius: var(--radius); }
/* The sauna shower, cropped from near the top so the beam, rain head and sauna door show (Billy,
   2026-11-05); its centre was mostly plaster wall. */
.photo-break .pos-shower img { object-position: 50% 22%; }
/* The walnut vanity is a tall photo in a small frame: cropped low so the cabinet and its brass
   pulls show, not the mirrors (Billy, 2026-11-05). */
.photo-break .pos-vanity img { object-position: 50% 72%; }
/* The ring-light nook as Dining rooms' big photo (Billy, 2026-11-05): tall, so cropped just below
   the middle: the light and the tables both show. */
.photo-break .pos-ring img { object-position: 50% 55%; }
/* The basement lounge in Custom homes (Billy, 2026-11-05): a tall photo cropped a little high, so the
   whole timber beam shows with the room under it. */
.photo-break .pos-beam img { object-position: 50% 35%; }
/* A drawing as a grid's big frame (Home's saunas, Billy, 2026-11-05): the plan centred on its paper,
   filling the frame like a photo. It stays wide on every screen: the big frame is wide on a laptop
   and 4:3 across the top on a phone. */
.photo-break .plate--cell { height: 100%; min-height: 0; }
.photo-break .plate--cell .plate-paper { height: 100%; box-sizing: border-box; display: flex; align-items: center; justify-content: center; padding: clamp(14px, 3vw, 40px); }
.photo-break .plate--cell .dw { max-height: 100%; }
@media (max-width: 600px) {
  .photo-break { grid-template-columns: 1fr 1fr; grid-template-rows: auto; aspect-ratio: auto; }
  .photo-break > :first-child { grid-column: 1 / 3; grid-row: auto; }
  .photo-break picture img { height: auto; aspect-ratio: 1 / 1; }
  .photo-break > :first-child img { aspect-ratio: 4 / 3; }
  /* The plan's frame matches a first photo's 4:3; its room names would be too small to read here. */
  .photo-break .plate--cell { height: auto; aspect-ratio: 4 / 3; }
  .photo-break .plate--cell .plate-paper { padding: 12px; }
  .photo-break .plate--cell .tx { display: none; }
  /* The sections either side already give 48px each way on a phone, so a break adds none. */
  main:has(> .section--break) > .section.section--break { padding-block: 0; }
  /* Triads stacked one after another (Billy, 2026-11-04): 32px between them. */
  main:has(> .section--break) > .section.section--break:has(+ .section--break) { padding-bottom: 32px; }
}

/* The four chapters (spec 51): an h2 with the service name, one line, the triads, a link to the
   project on Our work. Sections in a row, like the triads they grew from, with a little more room
   between them than between two triads, since each ends with its link and the next opens with a
   heading. A chapter's second triad (Renovations) has its own label. */
.chapter-head { margin-bottom: 20px; }
.chapter-head h2 { margin-bottom: 6px; }
/* What we build and the chapters (Billy, 2026-11-05): a label over a big headline, both in the h2,
   in the crew band's style (its size and measure); no line under it. */
.chapter-head h2 { margin: 0; max-width: 34ch; font-size: clamp(27px, 3.3vw, 42px); }
/* A phrase kept on one line when it fits, wrapping inside itself only when it can't. */
.hl-keep { display: inline-block; }
/* On a phone the kept phrase dropped whole to line 2 and left the headline in short lines, half
   the screen wide (Billy, 2026-11-05): there it wraps like any other text. */
@media (max-width: 859px) { .hl-keep { display: inline; } }
.chapter-head h2 .label { display: block; margin-bottom: 10px; font-weight: 400; line-height: 1.4; letter-spacing: .2em; }
.chapter-head p { color: var(--muted); }
.chapter .label { margin-top: 32px; }
.chapter-head + .label { margin-top: 0; }
/* A group's title inside a chapter (Renovations' four; Billy, 2026-11-05, option B): smaller grey
   capitals with a hairline running across to the edge, so it reads as nested under the chapter's
   green label rather than as its equal. A long title wraps and the line takes what's left; a title
   with markup inside goes in one <span>, or each piece becomes its own flex item. */
.label.sub { display: flex; align-items: center; gap: 14px; max-width: none; font-size: clamp(11px, calc(6.5px + .45vw), 12px); color: var(--muted); }
.label.sub::after { content: ""; flex: 1 1 24px; height: 1px; background: rgba(38, 35, 36, .18); }
.chapter-go { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 20px; }
.section--break.chapter:has(+ .section--break) { padding-bottom: clamp(56px, 6vw, 80px); }
@media (max-width: 600px) {
  main:has(> .section--break) > .section.section--break.chapter:has(+ .section--break) { padding-bottom: 56px; }
  .chapter .label { margin-top: 24px; }
  .chapter-head { margin-bottom: 16px; }
  /* A chapter has no top padding on a phone (it follows the one above), so a jump to it would
     put its h2 at the screen's edge; this lands it where every other section's label lands. */
  .chapter, #trades { scroll-margin-top: 48px; }
}
/* The staircase clip on its own, last on Our work (Billy, 2026-11-05): one tall frame,
   centred, nothing beside it. About a screen high on a wide screen, shorter on a phone. */
.clip-solo { display: flex; justify-content: center; }
.clip-label { text-align: center; margin-inline: auto; }
.clip-solo video { display: block; height: min(calc(100svh - 160px), 720px); width: auto; max-width: 100%; aspect-ratio: 9 / 16; object-fit: cover; border-radius: var(--radius); }
/* Phone (Billy, 2026-11-05): the column's full width, the whole 9:16 frame (about 636px tall at 390). */
@media (max-width: 600px) { .clip-solo video { width: 100%; height: auto; } }

/* ---- why ------------------------------------------------------------------------------- */
.why { display: grid; gap: 32px; }
@media (min-width: 860px) { .why { grid-template-columns: 1fr 1fr; gap: clamp(32px, 6vw, 96px); } }
/* Home's Made by us (was Custom pieces; Billy, 2026-11-04): the finished light wide on top, the two making-of
   photos side by side under it. All three are 9:16 phone shots, cropped to fit. */
.made { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; align-content: start; }
.made figure { margin: 0; }
.made-final { grid-column: 1 / -1; }
.made picture img { display: block; width: 100%; aspect-ratio: 4 / 5; object-fit: cover; border-radius: var(--radius); }
.made-final picture img { aspect-ratio: 16 / 10; object-position: 50% 46%; }
.made figcaption { font-size: 14px; color: var(--muted); margin-top: 6px; }
/* Desktop (Billy, 2026-11-05): the heading with the paragraph beside it, then the three photos in
   one row (the light 2 parts, each making-of shot 1), all the same height, so the short text column
   no longer leaves a tall gap beside the photos. Phones keep the stack. */
@media (min-width: 860px) {
  #made-by-us .why { align-items: end; row-gap: 0; }
  #made-by-us .made-text { display: contents; }
  #made-by-us .made-text > .label { grid-column: 1; grid-row: 1; }
  #made-by-us .made-text > h2 { grid-column: 1; grid-row: 2; margin-bottom: 0; }
  #made-by-us .made-text > p:not(.label) { grid-column: 2; grid-row: 2; margin: 0; }
  #made-by-us .made { grid-column: 1 / -1; grid-row: 3; grid-template-columns: 2fr 1fr 1fr; margin-top: clamp(28px, 3vw, 40px); }
  #made-by-us .made-final { grid-column: auto; }
}
/* ---- Home: shared bits -------------------------------------------------------------- */
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
/* The small green label on About's crew and trades cards. */
.sum-k { font-family: var(--display); font-size: clamp(11px, calc(7px + .55vw), 14px); letter-spacing: .2em; text-transform: uppercase; color: var(--green) !important; font-weight: 500; }
.points { list-style: none; margin: 0; padding: 0; display: grid; gap: 20px; }
.points li { padding-top: 16px; border-top: 1px solid var(--hair); }
.points h3 { margin-bottom: 4px; }
.points p { color: var(--muted); max-width: 40ch; }

/* ---- Home: how we work, two points side by side, each with a frame ----------------------- */
/* Billy, 2026-11-04. Each frame is a soft 4:3 stage (4:5 on phones, two across) with a sample on it, all
   demo data (no "Sample" tag since 2026-11-04, Billy): 1 a real Clock card in a phone
   that runs off the bottom of the stage, 2 the top of a milestone summary as a page that runs off
   the bottom too. Both images come from site/originals/ through tools/images.js (the summary is
   drawn by tools/frames.js). The receipts step was dropped, and the "Step 1 / Step 2" labels with
   it: the reports aren't a step, they're what the client gets (Billy, 2026-11-04). The class
   names stay .steps / .step. */
.how-head h2 { max-width: 22ch; }
.how-head .lede { font-size: clamp(18px, 1.6vw, 21px); color: var(--muted); max-width: 52ch; }
.steps { list-style: none; margin: clamp(28px, 5vw, 56px) 0 0; padding: 0; display: grid; grid-template-columns: repeat(2, 1fr); gap: clamp(12px, 3vw, 40px); }
/* Phones: the two sit side by side too (Billy, 2026-11-04), so each stage is taller than wide and
   the phone and page fill more of it; the captions come down a size. */
@media (max-width: 639px) {
  .steps .step-frame { aspect-ratio: 4 / 5; }
  /* Cost-plus: square (Billy, 2026-11-05: at 4:5 the last third of both frames was blank). */
  #cost-plus .steps .step-frame { aspect-ratio: 1 / 1; }
  .steps .phone { width: 84%; top: 14px; padding: 12px 4px 0; border-radius: 20px; }
  .steps .phone .phone-screen img, .steps .phone picture { border-radius: 10px 10px 0 0; }
  .steps .page { width: 92%; top: 14px; }
  .step h3 { font-size: 17px; margin: 14px 0 4px; }
  .step p { font-size: 15px; line-height: 1.45; }
}
.step h3 { margin: 20px 0 6px; }
.step p { color: var(--muted); max-width: 36ch; }
.step-frame { position: relative; aspect-ratio: 4 / 3; overflow: hidden; background: rgba(38, 35, 36, .06); border-radius: var(--radius); }
.step-frame picture, .step-frame img { display: block; width: 100%; height: auto; }
.phone { position: absolute; left: 50%; top: 7%; width: 58%; height: 120%; transform: translateX(-50%); padding: 16px 6px 0; background: var(--cream-2); border: 2px solid rgba(38, 35, 36, .22); border-radius: 26px; box-shadow: 0 10px 30px rgba(38, 35, 36, .10); }
.phone::before { content: ""; position: absolute; top: 6px; left: 50%; width: 22%; height: 4px; transform: translateX(-50%); border-radius: 2px; background: rgba(38, 35, 36, .18); }
.phone .phone-screen img { border-radius: 14px 14px 0 0; }
.page { position: absolute; left: 50%; top: 7%; width: 82%; height: 120%; transform: translateX(-50%); background: #FFFFFA; border: 1px solid rgba(38, 35, 36, .14); border-radius: 3px; box-shadow: 0 10px 30px rgba(38, 35, 36, .10); }

/* Cost-plus as an inverse block (Billy, 2026-11-05): deep green edge to edge, a step darker than
   the contact band, cream type, the frames lit like the band's form. Not a .section, so the
   sections either side keep their own padding against it. */
/* The contact band's green since 2026-11-05 (Billy: softer; was --green-deep, a step darker). */
.inverse { background: var(--green); color: var(--cream-2); }
.inverse > .wrap { padding-block: clamp(56px, 8vw, 104px); }
/* Phone: the chapters either side have no padding of their own there, so the block brings its gap. */
@media (max-width: 600px) { .inverse { margin-block: 48px; } }
/* Wider: the chapter after starts with its label's line height, so it comes up to match the gap above. */
@media (min-width: 601px) { .inverse + .section { padding-top: calc(var(--space-section) - 32px); } }
/* Home on a laptop, tightened twice (Billy, 2026-11-05): the gap between sections is up to 68px
   (the site's is 120) and the green blocks' padding up to 64px (was 104), so section to section
   around a green block is about 131px at 1280 (was 218). Scoped to Home by its photo breaks; the
   contact band follows, since it uses the same gap. Phones keep their own spacing. */
@media (min-width: 601px) {
  main:has(> .section--break) { --space-section: clamp(48px, 5.2vw, 68px); }
  main:has(> .section--break) .inverse > .wrap { padding-block: clamp(44px, 5vw, 64px); }
}
.inverse .label, .inverse h2, .inverse h3 { color: var(--cream-2); }
.inverse .how-head .lede, .inverse .step p { color: rgba(255, 255, 250, .82); }
.inverse .step-frame { background: rgba(255, 255, 250, .08); }
.inverse .phone, .inverse .page { border-color: rgba(255, 255, 250, .35); box-shadow: 0 12px 32px rgba(0, 0, 0, .25); }
/* Cost-plus: the green ends halfway down the two frames (Billy, 2026-11-05, option D of four: it was
   green on green). The block itself is cream; the green is a full-width layer behind the heading that
   stops 42% of the way down the frames, worked out from the frames' width (container units on
   .steps) and their shape, 4:3, or 1:1 on a phone. The frames are cream cards with a soft shadow,
   straddling the edge, and the captions sit on the cream, in the page's own colours. */
#cost-plus { background: var(--cream); overflow: clip; }
/* The heading stays above the green layer, which comes later in the page. */
#cost-plus .how-head { position: relative; z-index: 1; }
#cost-plus .steps { --gap: clamp(12px, 3vw, 40px); --shape: .75; position: relative; isolation: isolate; container-type: inline-size; }
#cost-plus .steps::before { content: ""; position: absolute; z-index: -1; left: 50%; width: 100vw; transform: translateX(-50%);
  bottom: calc(100% - (100cqw - var(--gap)) / 2 * var(--shape) * .42); height: 3000px; background: var(--green); }
@media (max-width: 639px) { #cost-plus .steps { --shape: 1; } }
#cost-plus .step-frame { background: var(--cream-2); border: 1px solid var(--hair); box-shadow: 0 18px 40px rgba(38, 35, 36, .14); }
#cost-plus .phone, #cost-plus .page { border-color: var(--hair); box-shadow: 0 10px 28px rgba(38, 35, 36, .14); }
#cost-plus .step h3 { color: var(--ink); }
#cost-plus .step p { color: var(--muted); }
/* Design, together on the same deep green (Billy, 2026-11-05): its text, the steps' rules and
   numbers, the cabin's caption and the button in cream; the plan stays on its sheet of paper. */
.inverse .why p, .inverse .points p, .inverse .item-line, .inverse .fine-print, .inverse .hint { color: rgba(255, 255, 250, .82); }
.inverse .points li { border-top-color: rgba(255, 255, 250, .22); }
.inverse .points--numbered li::before { background: var(--cream-2); color: var(--green); }
.inverse .plate-paper { border-color: transparent; }
.inverse .btn--quiet { color: var(--cream-2); border-color: rgba(255, 255, 250, .7); }
.inverse .btn--quiet:hover { background: rgba(255, 255, 250, .1); color: var(--cream-2); border-color: var(--cream-2); }
.inverse a:focus-visible { outline-color: var(--cream-2); }
/* Between two plain sections (Design sits between Made by us and About), the sections' own padding
   is the gap on a phone, so the block adds none of its own there. */
@media (max-width: 600px) {
  .section:not(.section--break) + .inverse { margin-top: 0; }
  .inverse:has(+ .section:not(.section--break)) { margin-bottom: 0; }
}

/* ---- services: four photo tiles under the hero photo, links to the chapters --------------- */
/* The four tiles right under the hero photo, 12px down (the tiles' own gap), so the five photos read
   as one header (Billy, 2026-11-05: the line over them is gone). The id beats Home's phone rule for
   section padding; a section's gap after them. */
#services { padding: 12px 0 var(--space-section); }
@media (max-width: 600px) { #services { padding-bottom: 48px; } }
/* Narrow phones: the tile names a little tighter, so "Renovations" fits its tile at 320. */
@media (max-width: 380px) { #services .tile-h { letter-spacing: .13em; } }
.tiles { display: grid; gap: 12px; grid-template-columns: repeat(4, 1fr); }
@media (max-width: 859px) { .tiles { grid-template-columns: 1fr 1fr; } }
.tile { position: relative; display: block; border-radius: var(--radius); overflow: hidden; isolation: isolate; color: var(--cream-2); text-decoration: none; }
.tile picture img { display: block; width: 100%; aspect-ratio: 3 / 4; object-fit: cover; }
/* A dark wash at the foot of the photo so the white text reads on any picture. */
.tile::after { content: ""; position: absolute; inset: 0; z-index: 1; background: linear-gradient(to top, rgba(38, 35, 36, .72), rgba(38, 35, 36, 0) 45%); }
.tile-txt { position: absolute; left: 16px; right: 16px; bottom: 16px; z-index: 2; display: grid; gap: 4px; }
/* The service name in the site's label style: small spaced capitals (Billy, 2026-11-03). Each tile
   is a link to its chapter (spec 51): a green ring on focus, a slight lift on hover, a press. */
.tile-h { font-family: var(--display); font-size: clamp(10px, calc(7px + .5vw), 13px); letter-spacing: .2em; text-transform: uppercase; font-weight: 500; line-height: 1.5; }
.tile-s { font-size: 14px; opacity: .85; }
.tile:focus-visible { outline: 3px solid var(--green); outline-offset: 3px; }
.tile:hover { color: var(--cream-2); }
@media (prefers-reduced-motion: no-preference) {
  .tile { transition: transform 150ms ease-out, box-shadow 150ms ease-out; -webkit-tap-highlight-color: transparent; }
  .tile:hover { transform: translateY(-2px); box-shadow: 0 8px 20px -10px rgba(38, 35, 36, .45); }
  .tile:active { transform: scale(.985); transition-duration: 120ms; }
}
/* On a phone the tiles are narrow: the service name alone. */
@media (max-width: 599px) {
  .tile-s { display: none; }
  .tile-txt { left: 14px; right: 14px; bottom: 14px; }
}

/* ---- projects: photo, name, type · place · season ---------------------------------------- */
.grid { display: grid; gap: clamp(24px, 4vw, 40px) 24px; }
@media (min-width: 640px) { .grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1000px) { .grid--3 { grid-template-columns: 1fr 1fr 1fr; } }
.card { display: block; text-decoration: none; color: inherit; }
.card picture { margin-bottom: 12px; }
.card picture img { aspect-ratio: 4 / 3; object-fit: cover; border-radius: var(--radius); }
.card h3 { margin: 0 0 2px; font-weight: 400; }
.card:hover h3 { color: var(--green); }
.card .meta { color: var(--muted); font-size: 15px; margin: 0; }
.more { margin-top: 32px; }
/* The Projects page's single photos of our work, mixed in with the project cards (Billy,
   2026-11-03; site/src/projects-grid.json). No page behind them: a click opens the photo in the
   modal (gallery.js). A tall one spans two rows, its photo filling the height the cards beside
   it set (absolute, so it doesn't stretch the rows itself); on a phone it's a 4:5 crop. */
.mix-wrap { container-type: inline-size; }
.grid--mix { grid-auto-flow: row dense; }
/* Every row at least one card tall (a 4:3 photo plus its one line, 32px), so a row holding only
   tall photos doesn't collapse. 24px is the column gap. */
@media (min-width: 640px) { .grid--mix { grid-auto-rows: minmax(calc((100cqw - 24px) / 2 * .75 + 32px), auto); } }
@media (min-width: 1000px) { .grid--mix { grid-auto-rows: minmax(calc((100cqw - 48px) / 3 * .75 + 32px), auto); } }
.work { display: flex; flex-direction: column; text-decoration: none; color: inherit; cursor: zoom-in; }
.work picture img, .work video { display: block; width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover; border-radius: var(--radius); }
.work-media { display: block; position: relative; }
/* Every group, single photo and drawing on Our work has the same head (Billy, 2026-11-04): a title,
   and an optional line in grey under it. */
.item-head { display: block; margin-top: 12px; }
.item-title { display: block; margin: 0; color: var(--ink); font: inherit; font-size: 16px; font-weight: 400; line-height: 1.35; letter-spacing: 0; }
.item-line { display: block; max-width: 62ch; margin: 4px 0 0; color: var(--muted); font-size: 15px; line-height: 1.45; }
.work:hover .item-title { color: var(--green); }

/* A clip shows a small play mark until it's playing (always, with no JavaScript). */
.work--video .work-media::after { content: ""; position: absolute; left: 14px; bottom: 14px; width: 40px; height: 40px; border-radius: 999px;
  background: rgba(20, 18, 19, .55) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 6.5v11l9-5.5z' fill='%23fff'/%3E%3C/svg%3E") center / 22px no-repeat; pointer-events: none; transition: opacity 200ms ease-out; }
.work--video.is-playing .work-media::after { opacity: 0; }
.work:focus-visible, .card:focus-visible { outline: 3px solid var(--green); outline-offset: 4px; border-radius: var(--radius); }
.work--tall picture img, .work--tall video { aspect-ratio: 4 / 5; }
@media (min-width: 640px) {
  .work--tall { grid-row: span 2; }
  .work--tall picture, .work--tall .work-media { position: relative; flex: 1 1 auto; min-height: 240px; }
  .work--tall picture img, .work--tall video { position: absolute; inset: 0; width: 100%; height: 100%; aspect-ratio: auto; }
}

/* Our work is one page (Billy, 2026-11-04): groups, runs of single photos, proof points and
   drawings, one under the other (site/src/our-work.json; site/tools/build.js says which is which). */
.work-flow { display: flex; flex-direction: column; gap: clamp(56px, 8vw, 104px); padding-top: 40px; padding-bottom: var(--space-section); }
.work-block { scroll-margin-top: 96px; }
.work-flow > .mix-wrap .grid { row-gap: clamp(32px, 4vw, 40px); }

/* A group: one project as a mosaic of its photos, its head under it. Every cell is 4:3 (two
   columns, three from 1000px); a big tile spans two by two, a tall one two rows, at every width.
   The photos fill their cells; their own captions are for the modal and screen readers only. */
.group { container-type: inline-size; }
.spread-grid { display: grid; gap: 12px; grid-template-columns: 1fr 1fr; grid-auto-flow: row dense;
  grid-auto-rows: calc((100cqw - 12px) / 2 * .75); }
@media (min-width: 1000px) { .spread-grid { grid-template-columns: repeat(3, 1fr); grid-auto-rows: calc((100cqw - 24px) / 3 * .75); } }
.spread-grid .work { position: relative; min-height: 0; }
.spread-grid .work--tall { grid-row: span 2; }
.spread-grid .work--big { grid-column: span 2; grid-row: span 2; }
.spread-grid .work picture, .spread-grid .work .work-media { position: absolute; inset: 0; min-height: 0; }
.spread-grid .work picture img, .spread-grid .work video { position: absolute; inset: 0; width: 100%; height: 100%; aspect-ratio: auto; }
.spread-grid .item-head { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.group > .item-head { margin-top: 16px; }

/* A proof point: a break in the photos, a line from the rest of the site. */
.proof { display: grid; gap: 14px 56px; padding: clamp(36px, 5vw, 64px) 0; border-top: 1px solid var(--hair); border-bottom: 1px solid var(--hair); }
@media (min-width: 860px) { .proof { grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); align-items: end; } }
.proof .label { margin-bottom: 10px; }
.proof-title { margin: 0; max-width: 22ch; font-size: clamp(27px, 3.3vw, 42px); font-weight: 300; line-height: 1.15; }
/* The smallest phones: a touch smaller, so the crew band's two short lines stay two at 320. */
@media (max-width: 359px) { .proof-title { font-size: 25px; } }
.proof-body p { margin: 0; color: var(--muted); }
.proof-body .proof-go { margin-top: 18px; }

/* A drawing (Billy, 2026-11-04): a plan or elevation on a sheet of paper, in the style of our
   client proposals, drawn by site/tools/drawings.js. No real job and no figures. */
.plate { margin: 0; container-type: inline-size; }
.plate-paper { padding: clamp(18px, 3.4vw, 44px) clamp(14px, 3.4vw, 44px) clamp(16px, 2.6vw, 32px); background: var(--cream-2);
  border: 1px solid var(--hair); border-radius: var(--radius); }
.dw { display: block; width: 100%; height: auto; max-height: 540px; margin: 0 auto; overflow: visible; }
.plate-legend { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px 20px; margin: clamp(14px, 2.4vw, 28px) 0 0; padding: 0; list-style: none; color: var(--muted); font-size: 13px; line-height: 1.4; }
.plate-legend li { display: flex; align-items: center; gap: 8px; }
.key { flex: none; width: 18px; height: 12px; border: 1px solid; border-radius: 2px; }
.plate figcaption .item-head { margin-top: 16px; }

/* The drawing's palette: the proposals' colours, warmed to the site's. */
.dw, .plate-legend { --dw-wall: #3A3637; --dw-line: #8C877F; --dw-wet: #E2EBDB; --dw-wet-ln: #6F8F5C; --dw-cab: #ECE1CD; --dw-cab-ln: #B09C7C;
  --dw-stone: #EFEEE9; --dw-stone-ln: #A8A49C; --dw-glass: #D8E7EF; --dw-glass-ln: #84A7BB; --dw-pine: #E9D5B0; --dw-pine-ln: #C9AD7F;
  --dw-furn: #F4F2EC; --dw-furn-ln: #A39E95; }
.dw path { fill: none; stroke: var(--dw-line); stroke-width: 1.2; stroke-linejoin: round; stroke-linecap: round; }
.dw .wall { fill: var(--dw-wall); stroke: var(--dw-wall); }
.dw .wall-ln { stroke: var(--dw-wall); stroke-width: 2.4; }
.dw .wet, .dw .wet-fx { fill: var(--dw-wet); stroke: var(--dw-wet-ln); }
.dw .wet-fx { fill: #EEF4EA; }
.dw .wet-ln { stroke: var(--dw-wet-ln); }
.dw .cab { fill: var(--dw-cab); stroke: var(--dw-cab-ln); }
.dw .cab-ln { stroke: var(--dw-cab-ln); }
.dw .stone { fill: var(--dw-stone); stroke: var(--dw-stone-ln); }
.dw .stone-ln { stroke: var(--dw-stone-ln); }
.dw .glass { fill: var(--dw-glass); stroke: var(--dw-glass-ln); }
.dw .glass-ln { stroke: var(--dw-glass-ln); }
.dw .pine { fill: var(--dw-pine); stroke: var(--dw-pine-ln); }
.dw .pine-soft { fill: #F3E7D1; stroke: var(--dw-pine-ln); }
.dw .pine-ln { stroke: var(--dw-pine-ln); stroke-width: .9; }
.dw .heater { fill: #5C5856; stroke: var(--dw-wall); }
.dw .heater + path, .dw .heater ~ .stone-ln { stroke: #CFCBC4; }
.dw .trim { fill: var(--cream-2); stroke: var(--dw-line); }
.dw .furn { fill: var(--dw-furn); stroke: var(--dw-furn-ln); }
.dw .furn-ln { stroke: var(--dw-furn-ln); }
.dw .door { stroke: var(--dw-wall); stroke-width: 1.6; }
.dw .swing { stroke: var(--dw-furn-ln); stroke-dasharray: 5 5; }
.dw .dim { stroke: var(--dw-line); stroke-width: 1; }
.dw .tx { fill: var(--muted); font-family: var(--display); font-size: 12px; font-weight: 400; letter-spacing: .16em; text-transform: uppercase;
  text-anchor: middle; dominant-baseline: central; }
.dw .tx-sm { font-size: 10px; }
.dw .tx-on { fill: #6B5A40; }
.dw .tx-muted { fill: var(--dw-line); }
/* On narrower screens the drawing is smaller, so its labels and lines are heavier in its own
   units, to stay readable (about 10px type on a phone). */
@media (max-width: 999px) {
  .dw .tx { font-size: 17px; } .dw .tx-sm { font-size: 14px; }
  .dw path { stroke-width: 1.7; } .dw .pine-ln { stroke-width: 1.2; } .dw .door { stroke-width: 2.2; } .dw .wall-ln { stroke-width: 3; }
}
@media (max-width: 639px) {
  .dw .tx { font-size: 25px; letter-spacing: .12em; } .dw .tx-sm { font-size: 20px; }
  .dw path { stroke-width: 2.4; } .dw .pine-ln { stroke-width: 1.6; } .dw .door { stroke-width: 3; } .dw .dim { stroke-width: 2; } .dw .wall-ln { stroke-width: 4; }
  .dw .swing { stroke-dasharray: 8 8; }
}
.key--cabinetry { background: var(--dw-cab); border-color: var(--dw-cab-ln); }
.key--wet { background: var(--dw-wet); border-color: var(--dw-wet-ln); }
.key--glass { background: var(--dw-glass); border-color: var(--dw-glass-ln); }
.key--pine { background: var(--dw-pine); border-color: var(--dw-pine-ln);
  background-image: repeating-linear-gradient(90deg, transparent 0 4px, var(--dw-pine-ln) 4px 5px); }

/* ---- testimonial ------------------------------------------------------------------------- */
.quote { margin: 0; display: grid; gap: clamp(20px, 4vw, 48px); align-items: center; }
@media (min-width: 760px) { .quote { grid-template-columns: minmax(240px, 360px) 1fr; } }
.quote-photo { aspect-ratio: 4 / 5; border-radius: var(--radius); overflow: hidden; background: var(--grey); }
.quote-photo picture, .quote-photo img { display: block; width: 100%; height: 100%; object-fit: cover; }
@media (max-width: 759px) { .quote-photo { aspect-ratio: 4 / 3; } }
.quote blockquote { margin: 0; }
.quote blockquote p { margin: 0; font-size: clamp(22px, 2.6vw, 32px); font-weight: 300; line-height: 1.3; }
.quote blockquote p::before { content: "“"; } .quote blockquote p::after { content: "”"; }
.quote figcaption { margin-top: 20px; color: var(--muted); font-size: 15px; }
.quote figcaption strong { color: var(--ink); font-weight: 600; }

/* ---- faq: plain <details> ---------------------------------------------------------------- */
.faq { max-width: 72ch; }
.faq details { border-top: 1px solid var(--hair); }
.faq details:last-of-type { border-bottom: 1px solid var(--hair); }
.faq summary { position: relative; cursor: pointer; list-style: none; display: flex; align-items: center; min-height: 56px; padding: 12px 32px 12px 0; font-size: 19px; font-weight: 400; }
.faq summary::-webkit-details-marker { display: none; }
/* The + is two 1.5px bars in a 16px box; the upright one lies down on open, so it reads − (spec 46). */
.faq summary::before, .faq summary::after { content: ""; position: absolute; right: 0; top: 50%; width: 16px; height: 1.5px; margin-top: -1px; background: var(--green); }
.faq summary::after { transform: rotate(90deg); }
.faq details[open] summary::after { transform: rotate(0deg); }
/* The answer opens instead of jumping (spec 46): a grid row from 0fr to 1fr, eased only where
   @starting-style lets a transition start from the closed state; elsewhere, and under reduced
   motion, it opens at once as before. */
.faq .a-wrap { display: grid; grid-template-rows: 1fr; }
.faq .a { min-height: 0; overflow: hidden; padding: 0 0 20px; color: var(--muted); max-width: 60ch; }

/* ---- contact band ------------------------------------------------------------------------ */
.band { background: var(--green); color: var(--cream-2); }
.band .wrap { display: grid; gap: 24px; padding-block: var(--space-section); align-items: center; }
@media (min-width: 860px) { .band .wrap { grid-template-columns: 3fr 2fr; } }
.band .label { color: var(--cream-2); }
.band p { color: rgba(255, 255, 250, .85); }
.band a:not(.btn) { color: var(--cream-2); }
.band .ways { display: grid; gap: 10px; font-size: clamp(18px, 2vw, 24px); }
.band .ways a { display: inline-flex; align-items: center; min-height: var(--tap); text-decoration-color: rgba(255, 255, 250, .5); }
.band .wrap > div .ways { margin-top: 20px; font-size: clamp(17px, 1.6vw, 20px); gap: 0; }
/* The Call button leads (Billy, 2026-11-05): filled cream on the green, the email a line under it. */
.band .ways .band-call { justify-self: start; min-height: 52px; padding: 12px 28px; margin-bottom: 8px; font-size: inherit; }
/* The band's short form sits on a cream card, so it takes the page's ink colours back. */
@media (min-width: 860px) { .band .wrap { align-items: start; } }
.band-form { position: relative; max-width: none; padding: clamp(20px, 3vw, 32px); background: var(--cream-2); color: var(--ink); border-radius: 12px; }
.band .band-form p { color: var(--muted); }
.band-form .field label { color: var(--ink); }
.band-form .opt { color: var(--muted); }
.band-form textarea { min-height: 112px; }

/* ---- about ------------------------------------------------------------------------------- */
.people { display: grid; gap: 32px; }
@media (min-width: 640px) { .people { grid-template-columns: 1fr 1fr; } }
.person picture { margin-bottom: 12px; max-width: 420px; }
.person picture img { aspect-ratio: 4 / 5; object-fit: cover; border-radius: var(--radius); }
/* A portrait still to come: same shape as a photo, with its TODO (Billy, 2026-11-03). */
/* Home's short version (spec 50): two side by side at every width, smaller photos. */
.people--short { grid-template-columns: 1fr 1fr; gap: 16px; max-width: 640px; margin-top: 20px; }
.people--short .person p:last-child { margin-bottom: 0; }
.people--short + p { margin-top: 24px; }
/* The portraits' stand-in (Billy, 2026-11-05): one line sketch for both, spruce lines on the pale
   tint, in the photos' 4:5 frame, until the new photos come. */
.person-sketch { width: 100%; max-width: 420px; height: auto; aspect-ratio: 4 / 5; margin-bottom: 12px; background: var(--green-pale); color: var(--green-deep); border-radius: var(--radius); }
.person h3 { margin-bottom: 2px; }
.person .role { color: var(--muted); margin-bottom: 10px; }
.people--short .person picture { max-width: none; }
/* The trades (spec 51 §6.13), inside About: two short plain lists side by side, not cards. */
/* Dave on site, between the people and the trades' heading (Billy, 2026-11-05): as wide as the
   trades block, so the column's left edge and width hold. */
.on-site { margin: clamp(40px, 5vw, 64px) 0 0; max-width: 720px; }
.on-site picture img { display: block; width: 100%; height: auto; border-radius: var(--radius); }
.on-site figcaption { font-size: 14px; color: var(--muted); margin-top: 8px; }
/* About on desktop (Billy, 2026-11-05): the heading across the top, then the two people on the
   left and Dave on site on the right, as tall as the cards (the photo fills its frame, close to its
   own 3:2), instead of the photo alone under them. The call button follows. Phones keep the stack. */
@media (min-width: 860px) {
  #about { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); column-gap: clamp(24px, 3vw, 40px); }
  #about > * { grid-column: 1 / -1; }
  #about > .people--short { grid-column: 1; grid-row: 2; max-width: none; align-self: start; }
  #about > .on-site { grid-column: 2; grid-row: 2; margin: 20px 0 0; max-width: none; display: flex; flex-direction: column; }
  #about > .on-site picture { flex: 1; display: block; min-height: 0; }
  #about > .on-site picture img { height: 100%; object-fit: cover; }
  #about > .chapter-go { margin-top: clamp(28px, 3vw, 40px); }
}
/* Home's crew band (Billy, 2026-11-05, late): a placeholder until client quotes replace it. Its own
   deep green block, #crew, between Renovations and Landscapes: label, headline and one line from
   our-work.json's crew entry, with no rules around it. The trades' stages, chips and switch went. */
#crew .proof { display: block; border: 0; padding: 0; }
#crew .proof-title { max-width: none; }
#crew .proof-body { margin-top: 16px; max-width: 46ch; }
.inverse .proof-body p { color: rgba(255, 255, 250, .82); }
/* Its planted row of Ontario trees (partials/band-plantings.html) along the foot, edge to edge, under
   the text: the computer row, or from 700px down the phone's own row of five. */
#crew { position: relative; }
#crew > .wrap { padding-bottom: clamp(116px, 12.5vw, 168px); }
#crew .plantings { position: absolute; left: 0; bottom: -1px; width: 100%; height: clamp(84px, 9.5vw, 138px); display: block; pointer-events: none; }
#crew .plantings--narrow { display: none; }
@media (max-width: 700px) { #crew .plantings--wide { display: none; } #crew .plantings--narrow { display: block; } }
#crew .pt-trunk { fill: none; stroke: var(--green-deep); stroke-linecap: round; stroke-linejoin: round; }
#crew .pt-crown { fill: var(--green-deep); }
#crew .pt-crown2 { fill: #46683A; }   /* between the band's green and the deep green */
#crew .pt-bark { fill: none; stroke: var(--green-pale); stroke-linecap: round; }
#crew .pt-mark { stroke: var(--green-deep); stroke-width: 1.4; stroke-linecap: round; }
#crew .pt-ground { stroke: var(--green-deep); stroke-width: 1.5; }
/* As the band arrives (reveal.js holds it until then, as for every .scene): the ground line draws out
   from the middle, then each tree grows up out of it, the bur oak first and then outward in pairs
   (--d in the partial), sways once and stands still. Nothing loops. Reduced motion: just there. */
@media (prefers-reduced-motion: no-preference) {
  #crew .plantings .pt-ground { transform-box: fill-box; transform-origin: center; animation: b-grow-x .6s cubic-bezier(.3,.7,.2,1) both; }
  #crew .plantings .pt-t { transform-box: fill-box; transform-origin: 50% 100%;
    animation: b-pt-grow .7s calc(.45s + var(--d)) cubic-bezier(.3,.8,.3,1.25) both, b-rg-settle 2.4s calc(1.15s + var(--d)) ease-in-out; }
}
@keyframes b-pt-grow { from { transform: scale(.2, 0); opacity: 0; } 40% { opacity: 1; } }
.inverse .made figcaption { color: rgba(255, 255, 250, .72); }

/* ---- a drag-to-compare handle (Home's Design, and Lakefield on Our work) ---------------- */
/* The handle needs compare.js, which adds .js to <html>; without it the photo sits above the
   option instead of over it. */
.compare { --pos: 50%; margin-top: 24px; }
.compare .frame { position: relative; border-radius: var(--radius); overflow: hidden; background: var(--grey); }
.compare .after, .compare .before { position: relative; }
.compare .after picture { display: none; }
.compare .after picture:first-child, .compare--single .after picture { display: block; }
.compare img { aspect-ratio: 16 / 9; object-fit: cover; width: 100%; }
.compare .tag { position: absolute; top: 12px; font-family: var(--display); font-size: 10px; letter-spacing: .18em; text-transform: uppercase; color: var(--cream-2); background: rgba(38, 35, 36, .62); padding: 6px 10px; border-radius: 999px; }
.compare .tag--before { left: 12px; }
.compare .tag--after { right: 12px; }
.compare .divider, .compare .handle { display: none; }
/* With the script: the photo lies over the option, clipped to --pos. compare.js moves it from the
   pointer on the frame (horizontal drags; vertical swipes still scroll the page); the hidden range
   stays on top for the keyboard and screen readers but takes no touches, because on an iPhone a
   native slider only moves when the touch lands on its hidden thumb (Billy, 2026-11-03). */
.js .compare .frame { touch-action: pan-y; cursor: ew-resize; -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
.js .compare .frame img { -webkit-user-drag: none; pointer-events: none; }
.js .compare .before { position: absolute; inset: 0; clip-path: inset(0 calc(100% - var(--pos)) 0 0); }
.js .compare .divider { display: block; position: absolute; top: 0; bottom: 0; left: var(--pos); width: 2px; margin-left: -1px; background: var(--cream-2); box-shadow: 0 0 0 1px rgba(38, 35, 36, .25); pointer-events: none; }
.js .compare .knob { position: absolute; top: 50%; left: 50%; width: 44px; height: 44px; margin: -22px 0 0 -22px; border-radius: 50%; background: var(--cream-2); box-shadow: 0 2px 10px rgba(38, 35, 36, .35); display: grid; place-items: center; }
.js .compare .knob::before { content: "◀ ▶"; font-size: 11px; letter-spacing: 2px; color: var(--green); }
.js .compare .handle { display: block; position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; pointer-events: none; -webkit-appearance: none; appearance: none; background: transparent; }
.js .compare .handle::-webkit-slider-thumb { -webkit-appearance: none; width: 44px; height: 100%; }
.js .compare .handle::-moz-range-thumb { width: 44px; height: 100%; border: 0; }
.js .compare .handle:focus-visible ~ .divider .knob, .js .compare .frame:has(.handle:focus-visible) .knob { outline: 3px solid var(--green); outline-offset: 2px; }
.js .compare .tag--after { opacity: 1; }
/* Without the script: label each half, stacked. */
html:not(.js) .compare .before { margin-bottom: 8px; border-radius: var(--radius); overflow: hidden; }
html:not(.js) .compare .frame { background: none; overflow: visible; }
html:not(.js) .compare .after { border-radius: var(--radius); overflow: hidden; }
/* The drawings behind a rendering (Our work, spec 51): two across, a swipeable row on a phone; each opens full size. */
.plan-row { list-style: none; margin: 16px 0 0; padding: 0; display: grid; gap: 12px; grid-template-columns: repeat(2, 1fr); }
.plan-row li { margin: 0; }
.plan-row a { display: block; height: 100%; padding: 12px; background: #fff; border: 1px solid var(--hair); border-radius: var(--radius); text-decoration: none; color: var(--ink); }
.plan-row a:hover { border-color: var(--green); }
.plan-row a:focus-visible { outline: 3px solid var(--green); outline-offset: 2px; }
.plan-row img { display: block; width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: contain; }
.plan-row span { display: block; margin-top: 10px; font-size: 14px; color: #5e5a55; }
@media (max-width: 600px) {
  .plan-row { grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: 82%; overflow-x: auto; scroll-snap-type: x mandatory; margin-inline: calc(-1 * var(--gutter)); padding: 0 var(--gutter) 8px; scroll-padding-inline: var(--gutter); }
  .plan-row li { scroll-snap-align: start; }
}
/* The drawings' modal (site/public/gallery.js). Without the script the tiles open the image. */
html.lb-open { overflow: hidden; }
.lightbox { position: fixed; inset: 0; width: 100vw; height: 100vh; height: 100dvh; max-width: none; max-height: none; margin: 0; padding: 72px 76px; border: 0; background: transparent; box-sizing: border-box; }
.lightbox[open] { display: grid; place-items: center; }
.lightbox::backdrop { background: rgba(20, 18, 19, .88); }
.lb-figure { margin: 0; padding: 12px; background: #fff; border-radius: var(--radius); max-width: 100%; max-height: 100%; display: flex; flex-direction: column; box-sizing: border-box; }
.lb-img, .lb-video { display: block; min-height: 0; max-width: 100%; max-height: calc(100dvh - 144px - 56px); width: auto; height: auto; object-fit: contain; margin: 0 auto; }
.lb-caption { display: flex; justify-content: space-between; gap: 16px; margin-top: 10px; font-size: 14px; color: #5e5a55; }
.lb-count { white-space: nowrap; }
.lb-figure [hidden] { display: none; }
.lb-btn { position: fixed; display: grid; place-items: center; width: 48px; height: 48px; padding: 0; border: 0; border-radius: 999px; background: var(--cream-2); color: var(--green); cursor: pointer; }
.lb-btn svg { display: block; }
.lb-btn:hover { background: #fff; color: var(--green-deep); }
.lb-btn:focus-visible { outline: 3px solid var(--green-soft); outline-offset: 3px; }
.lb-close { top: 14px; right: 14px; }
.lb-prev, .lb-next { top: 50%; transform: translateY(-50%); }
.lb-prev { left: 14px; }
.lb-next { right: 14px; }
@media (max-width: 600px) {
  .lightbox { padding: 72px 12px 84px; }
  .lb-img, .lb-video { max-height: calc(100dvh - 156px - 56px); }
  .lb-prev, .lb-next { top: auto; bottom: 18px; transform: none; }
  .lb-prev { left: calc(50% - 60px); }
  .lb-next { right: calc(50% - 60px); }
}
@media (prefers-reduced-motion: no-preference) {
  .lightbox[open] .lb-figure { animation: lb-in .18s ease-out; }
  @keyframes lb-in { from { opacity: 0; transform: scale(.98); } to { opacity: 1; transform: none; } }
}
.hint { font-size: 15px; color: var(--muted); margin-top: 12px; }

/* Home teaser: the photo and one option, side by side. */
/* The home page's compare sits in a half-width column: no top gap, a squarer photo. */
.compare--home { margin-top: 0; }
/* No script, no handle: the photos stack, so there is nothing to drag. */
html:not(.js) .compare-hint { display: none; }
.compare--home img { aspect-ratio: 4 / 3; }
@media (min-width: 860px) { .compare--home img { aspect-ratio: 16 / 10; } }
.teaser { display: block; text-decoration: none; color: inherit; }
.teaser-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.teaser-pair img { aspect-ratio: 4 / 3; object-fit: cover; border-radius: var(--radius); }
.teaser:hover .hint { color: var(--green); }

.points--numbered { counter-reset: step; padding: 0; }
.points--numbered li { position: relative; padding-left: 44px; }
.points--numbered li::before { counter-increment: step; content: counter(step); position: absolute; left: 0; top: 18px; width: 30px; height: 30px; border-radius: 50%; background: var(--green); color: var(--cream-2); font-family: var(--display); font-size: 11px; display: grid; place-items: center; }
.fine-print { margin-top: 40px; font-size: 14px; color: var(--muted); max-width: 70ch; }
/* Home's Design (spec 51): the three steps under the slider, side by side from 860px. */
.design-how { margin-top: clamp(40px, 5vw, 64px); }
@media (min-width: 860px) { .design-steps { grid-template-columns: repeat(3, 1fr); gap: 32px; } }
.design-steps + .fine-print { margin-top: 24px; }
/* The lakeside cabin plan under the steps (Billy, 2026-11-05): part of Design, so a step's gap above it,
   not a section break; the fine print follows it as it followed the steps. */
.design-plan { margin-top: clamp(32px, 4vw, 48px); }
.design-plan + .fine-print { margin-top: 24px; }

/* ---- the form: plain fields, tall enough to tap ------------------------------------------- */
.form { display: grid; gap: 16px; max-width: 560px; }
.field { display: grid; gap: 6px; }
.field-row { display: grid; gap: 16px; }
@media (min-width: 540px) { .field-row { grid-template-columns: 1fr 1fr; } }
.field label { font-size: 15px; color: var(--ink); }
.field input, .field select, .field textarea {
  font: inherit; color: var(--ink); background: var(--cream-2);
  border: 1px solid rgba(38, 35, 36, .28); border-radius: 8px;
  min-height: var(--tap); padding: 10px 14px; width: 100%;
}
.field textarea { resize: vertical; min-height: 140px; line-height: 1.5; }
.field input:focus, .field select:focus, .field textarea:focus { outline: 3px solid var(--green); outline-offset: 1px; border-color: var(--green); }
.field select { appearance: none; -webkit-appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%234D6F39' stroke-width='1.5'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 14px center; padding-right: 36px; }
.form .hint { font-size: 14px; color: var(--muted); margin: -6px 0 0; }
.form .form-reply { margin: 0; }
/* "What's it about?" (spec 51 §3.5): optional radio chips, 44px, two by two on a narrow phone.
   The radio stays in the page (keyboard, screen readers) but out of sight; its chip shows it. */
.chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: 0; border: 0; min-width: 0; }
.chips legend { width: 100%; padding: 0; margin-bottom: 6px; font-size: 15px; color: var(--ink); }
.chips label { position: relative; flex: 1 1 calc(50% - 4px); }
@media (min-width: 420px) { .chips label { flex: 0 1 auto; } }
.chips input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
.chips label span { display: flex; align-items: center; justify-content: center; min-height: var(--tap); padding: 8px 16px; border: 1px solid rgba(38, 35, 36, .28); border-radius: 999px; background: var(--cream-2); font-size: 15px; line-height: 1.2; text-align: center; white-space: nowrap; }
.chips input:hover + span { border-color: var(--green); }
.chips input:checked + span { background: var(--green); border-color: var(--green); color: var(--cream-2); }
.chips input:focus-visible + span { outline: 3px solid var(--green); outline-offset: 2px; }
.form .btn { justify-self: start; min-width: 140px; }
/* The honeypot: off the page, out of the tab order, invisible to screen readers. */
.trap { position: absolute; left: -10000px; top: auto; width: 1px; height: 1px; overflow: hidden; }
.result .actions { margin-top: 28px; display: flex; flex-wrap: wrap; gap: 12px; }
/* Our work's "Back to the story" (spec 51 §3.6), above its label and at its foot. */
.back { margin: 0 0 20px; font-size: 16px; }
.back a { display: inline-flex; align-items: center; min-height: var(--tap); }
.back--foot { margin: 0; }
/* Lakefield's slider and the boathouse plans on Our work: the compare and plan row's own looks,
   with Our work's head under them. */
.work-compare, .work-plans { margin: 0; }
.work-compare .compare { margin-top: 0; }
.work-plans .plan-row { margin-top: 0; }
.work-compare figcaption .item-head, .work-plans figcaption .item-head { margin-top: 16px; }
.result { padding-bottom: var(--space-section); }

/* ---- footer ------------------------------------------------------------------------------ */
/* Two rows: the logo and the pages, a hairline, then contact and the fine print in small type. */
/* No top border since spec 53 (Billy, 2026-11-05): the end-of-day strip's ground line is the divide. */
.site-footer { padding-block: 28px 20px; font-size: 15px; color: var(--muted); }
.site-footer .foot-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 4px 32px; }
.site-footer .foot-main { padding-bottom: 16px; border-bottom: 1px solid var(--hair); }
.site-footer .foot-fine { padding-top: 8px; font-size: 13px; }
.site-footer p { margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 0 24px; }
.site-footer .foot-nav { display: flex; flex-wrap: wrap; gap: 0 28px; }
.site-footer a { color: var(--ink); text-decoration: none; display: inline-flex; align-items: center; min-height: var(--tap); }
.site-footer a:hover { color: var(--green); text-decoration: underline; }
.site-footer .brand { color: var(--green); }
.site-footer .foot-ig { min-width: var(--tap); justify-content: center; margin-left: -12px; }
.site-footer .foot-ig svg { display: block; }
.site-footer .foot-top { color: var(--muted); }
/* On a laptop the fine print and "Top" join the contact line's row directly (display: contents):
   sized as their own flex row, Chrome came up short and sent "Top" past the edge or onto its own line. */
@media (min-width: 760px) {
  .site-footer .foot-fine { gap: 4px 24px; }
  .site-footer .foot-legal { display: contents; }
  .site-footer .foot-legal > span { margin-left: auto; }
}
/* On a phone: everything stacks to the left, the pages in one line under the logo. */
@media (max-width: 759px) {
  .site-footer .foot-row { flex-direction: column; align-items: flex-start; }
  .site-footer .foot-nav { gap: 0 24px; }
  .site-footer .foot-legal { flex-direction: column; align-items: flex-start; }
  .site-footer .foot-legal > span { padding-block: 4px; }
  .site-footer .foot-sep { display: none; }
  .site-footer .foot-where { display: block; }
}
@media (max-width: 380px) { .site-footer .foot-nav { gap: 0 18px; } }
/* The foot of the page clears an iPhone's home bar (viewport-fit=cover). */
@media (max-width: 760px) { .site-footer { padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px)); } }

/* ---- the TODO marker: visible on purpose, so nothing half-done ships ---------------------- */
.todo { display: inline-block; font-family: var(--display); font-size: 10px; letter-spacing: .18em; text-transform: uppercase; color: #8A3B1F; background: #F6E3DA; border: 1px dashed #C98A6E; padding: 6px 10px; border-radius: 4px; }

/* ---- motion: one hover, nothing else; none at all when the person asks ------------------- */
@media (prefers-reduced-motion: no-preference) {
  .btn, .nav a, .card h3, a, .field input, .field select, .field textarea { transition: color 150ms ease-out, background-color 150ms ease-out, border-color 150ms ease-out; }
}

/* ---- motion: spec 46 (Billy, 2026-11-04), the foundation. Everything below is off with no
   JavaScript and under reduced motion: reveal.js arms the page first (html.reveal-armed), and
   every rule here sits under no-preference. ---------------------- */
/* Sections settle into view as they reach the screen: a 12px rise and a fade, 500ms. A grid ([data-reveal-stagger]) stays put and its children come
   one after another, 70ms apart, up to the eighth. */
@media (prefers-reduced-motion: no-preference) {
  .reveal-armed [data-reveal] { opacity: 0; transform: translateY(12px); }
  .reveal-armed [data-reveal].is-in { opacity: 1; transform: none; transition: opacity 500ms ease-out, transform 500ms ease-out; }
  .reveal-armed [data-reveal-stagger] > * { opacity: 0; transform: translateY(12px); }
  .reveal-armed [data-reveal-stagger].is-in > * { opacity: 1; transform: none; transition: opacity 500ms ease-out, transform 500ms ease-out; }
  .reveal-armed [data-reveal-stagger].is-in > :nth-child(2) { transition-delay: 70ms; }
  .reveal-armed [data-reveal-stagger].is-in > :nth-child(3) { transition-delay: 140ms; }
  .reveal-armed [data-reveal-stagger].is-in > :nth-child(4) { transition-delay: 210ms; }
  .reveal-armed [data-reveal-stagger].is-in > :nth-child(5) { transition-delay: 280ms; }
  .reveal-armed [data-reveal-stagger].is-in > :nth-child(6) { transition-delay: 350ms; }
  /* Our work's drawings draw themselves in as they reach the screen (Billy, 2026-11-04): each
     outline traces in (pathLength="1", so a dash of 1 is the whole line), then its fill fades up,
     walls first, then built-ins and glass, furniture, dimensions, labels. With no JavaScript or
     under reduced motion they're simply drawn. */
  .reveal-armed .plate .dw path:not(.swing) { stroke-dasharray: 1 1; }
  /* A drawing in a grid (.plate--cell) has no reveal of its own: it draws in with its grid. */
  .reveal-armed .plate:not(.is-in, .is-in > .plate) .dw path:not(.swing) { stroke-dashoffset: 1; fill-opacity: 0; }
  .reveal-armed .plate:not(.is-in, .is-in > .plate) .dw .swing, .reveal-armed .plate:not(.is-in, .is-in > .plate) .dw .tx { opacity: 0; }
  .reveal-armed :is(.plate.is-in, .is-in > .plate) .dw path:not(.swing) { transition: stroke-dashoffset 1100ms cubic-bezier(.45, 0, .2, 1) var(--d, 0ms), fill-opacity 700ms ease-out calc(var(--d, 0ms) + 600ms); }
  .reveal-armed :is(.plate.is-in, .is-in > .plate) .dw .swing, .reveal-armed :is(.plate.is-in, .is-in > .plate) .dw .tx { transition: opacity 600ms ease-out calc(var(--d, 0ms) + 500ms); }
  .dw .p2 { --d: 250ms; } .dw .p3 { --d: 550ms; } .dw .p4 { --d: 850ms; } .dw .p5 { --d: 1100ms; }
  .reveal-armed [data-reveal-stagger].is-in > :nth-child(7) { transition-delay: 420ms; }
  .reveal-armed [data-reveal-stagger].is-in > :nth-child(n + 8) { transition-delay: 490ms; }
  /* Revealed by focus (a keyboard user tabbing ahead of the scroll): at once. */
  .reveal-armed .reveal-now, .reveal-armed .reveal-now > * { transition: none !important; }
}
/* Press states (§3.2): phones have no hover, so a tap gets its own answer, 120ms. The press sits
   on the link, not the picture, so a press anywhere on a card counts. (Home's service tiles have
   their own, with their hover, since spec 51.) The native tap highlight goes only on the
   things that have a press state of their own. */
@media (prefers-reduced-motion: no-preference) {
  .btn { transition: color 150ms ease-out, background-color 150ms ease-out, border-color 150ms ease-out, transform 120ms ease-out; }
  .btn:active { transform: scale(.97); }
  .card picture img, .work picture img, .work video, .teaser img { transition: transform 120ms ease-out, filter 120ms ease-out; }
  .card:active picture img, .work:active picture img, .work:active video, .teaser:active img {
    transform: scale(.985); filter: brightness(1.04); transition-duration: 120ms;
  }
  .faq summary { transition: background-color 120ms ease-out; }
  .faq summary:active { background: rgba(77, 111, 57, .05); }
  .nav a:not(.btn), .site-footer a { transition: color 150ms ease-out, background-color 150ms ease-out, border-color 150ms ease-out, opacity 120ms ease-out; }
  .site-footer a:active, .nav a:not(.btn):active { opacity: .7; }
  .btn, .card, .work, .teaser, .faq summary, .nav a, .site-footer a { -webkit-tap-highlight-color: transparent; }
}
/* Photos fade in as they load (§3.5) instead of popping in mid-scroll. Every picture has the
   site's cream-grey ground, rounded where its photo is, so there's never a hole; once armed, a
   lazy photo is clear until reveal.js marks it loaded (at once if it came from the cache), then
   fades up in 350ms. Not the hero (eager, the LCP), and never the compare's layers: one at
   opacity 0 would show the other half. Clips are a <video poster>, not a lazy image. */
picture { background: rgba(38, 35, 36, .06); }
.hero picture, .card picture, .gallery picture, .person picture, .work picture, .cover picture, .teaser-pair picture, .made picture { border-radius: var(--radius); }
.phone picture { border-radius: 14px 14px 0 0; }
@media (prefers-reduced-motion: no-preference) {
  .reveal-armed picture img[loading="lazy"]:not(.is-loaded) { opacity: 0; }
  .reveal-armed picture img[loading="lazy"].is-loaded { opacity: 1; transition: opacity 350ms ease-out; }
  .reveal-armed .compare picture img { opacity: 1; transition: none; }
}
/* The FAQ's easing (§3.6), 250ms open, the bar 200ms. */
@media (prefers-reduced-motion: no-preference) {
  .faq summary::after { transition: transform 200ms ease-out; }
  .faq details[open] .a-wrap { transition: grid-template-rows 250ms ease-out; }
  @starting-style { .faq details[open] .a-wrap { grid-template-rows: 0fr; } }
}
/* The header on scroll (§3.7, reveal.js): a hairline shadow once the page has scrolled. Desktop
   only in effect: on a phone the header isn't sticky (spec 49), so it scrolls away. */
.site-header.is-scrolled { box-shadow: 0 1px 0 var(--hair), 0 8px 24px -16px rgba(38, 35, 36, .25); }
@media (prefers-reduced-motion: no-preference) {
  .site-header { transition: box-shadow 200ms ease-out; }
}
/* Page to page (§3.3): a short crossfade between pages of the site where the browser can (Safari
   18.2+, Chrome 126+); elsewhere pages change as they always did. The header and footer are
   named so they stay put rather than crossfade with themselves. The rule itself can't sit in a
   media query, so under reduced motion the animations are turned off instead. */
@view-transition { navigation: auto; }
.site-header { view-transition-name: site-header; }
.site-footer { view-transition-name: site-footer; }
::view-transition-old(root), ::view-transition-new(root) { animation-duration: 200ms; }
/* The header and footer are the same on every page, so the new page's simply stands: no fade of
   their own and no resize. (Left to the default, a header caught with the phone menu open is a
   taller snapshot squeezed down to the new one, and the wordmark doubles for a moment.) */
::view-transition-group(site-header), ::view-transition-group(site-footer),
::view-transition-new(site-header), ::view-transition-new(site-footer) { animation: none; }
::view-transition-old(site-header), ::view-transition-old(site-footer) { display: none; }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
}

/* ---- motion: spec 47 (Billy, 2026-11-04), four moments. Like spec 46, nothing here does
   anything with no JavaScript or under reduced motion. The compare sweep is in compare.js. ---- */
/* Home's hero settles in as the page opens (§3.3): the text line by line with spec 46's 12px and
   500ms, and the photo from 4% close. The photo is the LCP, so it never fades, it only scales,
   clipped by its own rounded picture. This hangs off html.js, which compare.js sets from the
   head BEFORE the body is drawn (it's the site's one script without defer); that's what lets the
   hero start from its first paint with no flash of the finished hero. reveal.js is deferred and
   runs too late. Moving compare.js to defer would break this. `backwards`, not `both`: a filled
   animation would keep holding transform after it ends and beat the buttons' press scale. */
@media (prefers-reduced-motion: no-preference) {
  .js .hero picture { overflow: hidden; }
  .js .hero .wrap > div > * { animation: hero-in 500ms ease-out backwards; }
  .js .hero .wrap > div > :nth-child(2) { animation-delay: 80ms; }
  .js .hero .wrap > div > :nth-child(3) { animation-delay: 160ms; }
  .js .hero .wrap > div > :nth-child(4) { animation-delay: 240ms; }
  .js .hero picture img { animation: hero-photo 1200ms cubic-bezier(.2, .7, .3, 1) backwards; }
  @keyframes hero-in { from { opacity: 0; transform: translateY(12px); } }
  @keyframes hero-photo { from { transform: scale(1.04); } }
}
/* "How we work" (§3.4): as each stage crosses the screen, its phone or page rises into place,
   tied to the scroll (scroll back up and it sinks again), and ends exactly where it sits without
   this. A CSS scroll-driven animation, so no script: Chrome and Edge 115+, Safari 26+; elsewhere
   the frames stand still. `translate`, not `transform`, because both are centred with
   transform: translateX(-50%), which the separate property adds to. The stage's overflow clips
   them. Gated on .reveal-armed (reveal.js), so nothing new
   with no JavaScript; spec 46's stagger still fades each step in above it. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .reveal-armed .step-frame { view-timeline-name: --frame; }
    .reveal-armed .step-frame .phone,
    .reveal-armed .step-frame .page {
      animation: frame-rise linear backwards;
      animation-timeline: --frame;          /* after the shorthand, which resets it */
      animation-range: entry 0% cover 50%;  /* from the stage's top entering to its middle at mid-screen */
    }
    .reveal-armed .step-frame .phone { --rise: clamp(16px, 2.4vw, 28px); }
    .reveal-armed .step-frame .page { --rise: clamp(24px, 3.4vw, 40px); }
    @keyframes frame-rise { from { translate: 0 var(--rise); } to { translate: 0 0; } }
  }
}

@media print {
  [data-reveal], [data-reveal-stagger] > * { opacity: 1 !important; transform: none !important; }
}
