/* ---- homepage/src/css/tokens.css ---- */
/* generated by tools/build.py from tokens.json: edit tokens.json, not this file */
:root {
  --navy: #162230;
  --sky: #7CB5E0;
  --ink: #162230;
  --ink-2: #44505D;
  --paper: #FBFAF6;
  --stone: #F1EBE0;
  --deep: #00323B;
  --tan: #F0EADF;
  --spring: #2BB8AF;
  --spring-ink: #0A7672;
  --sun: #F2B640;
  --on-dark: #F4F1EA;
  --on-dark-2: #B9C6CC;
  --hair: rgb(22 34 48 / 0.13);
  --hair-dark: rgb(244 241 234 / 0.16);
  --glass: rgb(255 255 255 / 0.84);
  --focus: #0A7672;
  --font-display: 'Switzer', 'Segoe UI', system-ui, sans-serif;
  --font-body: 'Switzer', 'Segoe UI', system-ui, sans-serif;
  --font-quote: 'Domine', Georgia, serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, 'Cascadia Mono', monospace;
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --ease-calm: cubic-bezier(0.4, 0, 0.2, 1);
  --dur-quick: 350ms;
  --dur-std: 520ms;
  --dur-slow: 820ms;
  --gut: clamp(16px, 4.4vw, 64px);
  --maxw: 1360px;
  --radius: 14px;
  --radius-lg: 22px;
}

/* ---- homepage/src/css/base.css ---- */
/* Design Taste · route: custom (bespoke) · structure: drawn Florida water world, solid grid, island menu
 * vibe: "Florida water from the aquifer to the tap, clean and solid, drawn by hand"
 * grounds: white + limestone + navy (logo) + deep spring teal · accent: spring aqua · display/body: Switzer 800/400,
 * quotes: Domine, readings and labels: IBM Plex Mono · motion: premium calm (ease-calm, 350/520/820 ms)
 * pre-emit critique is stamped at handoff */

*, *::before, *::after { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scrollbar-gutter: stable;           /* a menu or dialog that locks scroll never moves the page */
  overflow-x: clip;
  background: var(--paper);
  color: var(--ink);
  scrollbar-width: thin;
  scrollbar-color: rgb(22 34 48 / 0.28) transparent;
}
html, * { -webkit-tap-highlight-color: transparent; }   /* standing rule: no blue tap highlight, ever */
body {
  margin: 0;
  overflow-x: clip;
  font: 400 18px/1.62 var(--font-body);
  font-feature-settings: 'ss01' 0;
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-thumb { background: rgb(22 34 48 / 0.24); border-radius: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::selection { background: rgb(43 184 175 / 0.32); color: var(--ink); }

img, svg, video { display: block; max-width: 100%; }
img { height: auto; }
figure, blockquote { margin: 0; }
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }
a { color: inherit; text-decoration-thickness: 1px; text-underline-offset: 0.22em; }

:focus { outline: none; }
/* The keyboard ring follows each control's own corners: this rule sets the ring and its offset, never a shape (a
   radius here turned the island's pills and every text link into boxes when focused, round 3, E9). Its colour is the
   section's own spring ink, which the dark tone turns light, so the ring reads on every ground. Text links and the
   questions carry a small radius at rest, so their ring is soft without changing anything a visitor sees. */
:focus-visible { outline: 2px solid var(--spring-ink); outline-offset: 3px; }
a, summary { border-radius: 4px; }
/* over a film no single colour reads on every frame: a white ring framed in deep ink on both sides */
.film__toggle:focus-visible { outline-color: #FFFFFF; outline-offset: 2px; box-shadow: 0 0 0 6px rgb(14 42 53 / 0.92); }

.skip { position: fixed; left: 16px; top: -60px; z-index: 100; padding: 10px 16px; border-radius: 10px;
  background: var(--navy); color: #fff; text-decoration: none; font-weight: 600; transition: top var(--dur-quick) var(--ease-out); }
.skip:focus-visible { top: 14px; }
.sr { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }

/* ---------- type ---------- */
h1, h2, h3, h4 { margin: 0; font-family: var(--font-display); font-style: normal; color: inherit; }
.h-hero { font-weight: 800; font-size: clamp(44px, 6vw, 92px); line-height: 0.97; letter-spacing: -0.038em; text-wrap: balance; }
.h-xl { font-weight: 800; font-size: clamp(38px, 4.9vw, 74px); line-height: 1; letter-spacing: -0.034em; text-wrap: balance; }
.h-xl .ln { display: block; }   /* round 3 polish: a two-sentence heading breaks between its sentences, never inside one */
.nw { white-space: nowrap; }      /* a hyphenated pair that must not split at its hyphen ("high- / pressure") */
.h-l  { font-weight: 800; font-size: clamp(32px, 3.7vw, 54px); line-height: 1.04; letter-spacing: -0.03em; text-wrap: balance; }
.h-m  { font-weight: 700; font-size: clamp(22px, 1.9vw, 28px); line-height: 1.16; letter-spacing: -0.016em; text-wrap: balance; }
.lead { font-size: clamp(18px, 1.45vw, 21px); line-height: 1.6; color: var(--ink-2); text-wrap: pretty; }
p { margin: 0; text-wrap: pretty; }
.label { font: 500 12.5px/1.2 var(--font-mono); letter-spacing: 0.14em; text-transform: uppercase; }
.num { font-variant-numeric: tabular-nums; }

/* ---------- layout ---------- */
.wrap { width: min(100% - 2 * var(--gut), var(--maxw)); margin-inline: auto; }
.section { position: relative; padding-block: clamp(80px, 11vw, 158px); }   /* 158 desktop (Bay Balcony's measured rhythm), 80 phone
   (Medscapes'): on one shared ground two paddings meet as one band, and 96 + 96 read as a gap on a phone */

/* ---------- buttons (8 states: default, hover, focus, active, disabled, loading, error, success) ---------- */
.btn { --b-bg: var(--btn); --b-ink: var(--btn-ink); --b-line: transparent;
  display: inline-flex; align-items: center; gap: 10px; min-height: 52px; padding: 0 24px; border-radius: 999px;
  border: 1px solid var(--b-line); background: var(--b-bg); color: var(--b-ink); font: 600 16.5px/1 var(--font-body);
  letter-spacing: -0.005em; text-decoration: none; white-space: nowrap;
  transition: background-color var(--dur-quick) var(--ease-calm), color var(--dur-quick) var(--ease-calm),
    border-color var(--dur-quick) var(--ease-calm), transform var(--dur-quick) var(--ease-out); }
.btn svg { width: 18px; height: 18px; flex: none; transition: transform var(--dur-quick) var(--ease-out); }
.btn--spring { --b-bg: var(--spring); --b-ink: var(--navy); }
.btn--line { --b-bg: transparent; --b-ink: var(--ink); --b-line: var(--hair); }
.hero__cta .btn--line { --b-line: rgb(22 34 48 / 0.5); }   /* round 3 polish: on the pale sky the hairline reads (3:1), still the quiet one */
@media (hover: hover) and (pointer: fine) {
  .btn:hover { --b-bg: var(--btn-hover); }
  .btn--spring:hover { --b-bg: #48C9C0; }
  .btn--line:hover { --b-line: var(--ink); --b-bg: transparent; }
  .btn:hover svg { transform: translateX(3px); }
}
.btn:active { transform: translateY(1px) scale(0.99); }
.btn[disabled], .btn[aria-disabled="true"] { opacity: 0.45; pointer-events: none; }
.btn[data-state="loading"] { pointer-events: none; }
.btn[data-state="error"] { --b-bg: #9E2F24; --b-ink: #fff; }
.btn[data-state="success"] { --b-bg: var(--spring-ink); --b-ink: #fff; }

/* ---------- reduced motion: calm, never broken ---------- */
@media (prefers-reduced-motion: reduce) {
  .btn, .btn svg { transition-duration: 1ms; }
}

/* ---- homepage/src/css/ground.css ---- */
/* ---------- grounds and tones: every section keeps its own ----------
   Every section names its ground (data-theme) and tone (data-tone) and paints them itself. Round 6 (5 Nov 2026, Ali:
   "i want to get rid of the color switching effect on scroll everywhere across the site. i want the sections to keep
   their colors [the ones they switch to as we scroll]"): the ground switch is retired (js/ground.js is no longer
   bundled; checkpoint/round6-before-2026-11-05 holds it with the switching rules this file carried). What was its
   static fallback is now the only path, so the selectors keep their html:not(.gm) prefix: same specificity, same
   cascade as the pages the gates passed. Where the ground changes, the lower section rises over the one above along a
   drawn edge (css/edges.css). Cards, the map, the header, the island, the form and the footer are "light islands":
   they keep light tokens in every tone. */

/* round 4 (E7, Ali: "i really like all the colors in both screenshots too ... make subtle adjustments on the page - maybe
   to the section backgrounds that are superwhite"): the Three.js draft's grounds. Paper #FFFFFF > #FBFAF6, stone
   #F3EFE7 > #F1EBE0 (the review cards' fill, E4), deep #0E2A35 > #00323B; cream and navy stay. Kept in step with
   tokens.json (ui and art.deep_ground). */
:root {
  --g-paper: #FBFAF6; --g-stone: #F1EBE0; --g-cream: #FAF7F1; --g-navy: #162230; --g-deep: #00323B; --g-tan: #F0EADF;
  --ground: #FBFAF6;
  --hair-2: rgb(22 34 48 / 0.2); --hair-3: rgb(22 34 48 / 0.32);
  --wash: rgb(22 34 48 / 0.028); --track: rgb(22 34 48 / 0.14); --tick: rgb(22 34 48 / 0.55); --thumb: #162230;
  --btn: #162230; --btn-ink: #FFFFFF; --btn-hover: #0D1824; --star: #B7801A;
}
/* the dark tone */
html:not(.gm) [data-tone="dark"] {
  --ink: #F4F1EA; --ink-2: #B9C6CC; --hair: rgb(244 241 234 / 0.16); --hair-2: rgb(244 241 234 / 0.26); --hair-3: rgb(244 241 234 / 0.42);
  --spring-ink: #2BB8AF; --wash: rgb(255 255 255 / 0.04); --track: rgb(244 241 234 / 0.18); --tick: rgb(244 241 234 / 0.62);
  --thumb: #F4F1EA; --btn: #2BB8AF; --btn-ink: #162230; --btn-hover: #48C9C0; --star: #E9B44C;
}
/* round 4 (E36, Ali: "the #6E3823 feels a little too strong actually. lets replace it with a tan #F0E4CB and adjust all
   the colors on everything accordingly there for proper visibility", then "thats too tan, turn it down a notch", then
   "just use this tan #F0EADF"; E33's clay is gone): tan #F0EADF (tried first: #F0E4CB, then #F3EBD8). A light ground,
   so the two sections take the light tone with one change: the spring accent drops a step, as the hero's eyebrow does
   (#086A66, 5.4:1), since #0A7672 clears 4.5 by a hair here (4.56:1). Ink 13.4:1, ink-2 6.9:1; the hairlines are the
   light tone's own. */
html:not(.gm) [data-theme="tan"] { --spring-ink: #086A66; }
html:not(.gm) [data-tone="dark"] { color: var(--ink); }
/* light islands: their own light tokens in every tone */
.hero, .site-head, .isl, .rev, .post, .book__panel, [data-book-sheet], .cqm, .foot, .film__toggle, .film__cap,
.press__card {
  --ink: #162230; --ink-2: #44505D; --hair: rgb(22 34 48 / 0.13); --hair-2: rgb(22 34 48 / 0.2); --hair-3: rgb(22 34 48 / 0.32);
  --spring-ink: #0A7672; --wash: rgb(22 34 48 / 0.028); --track: rgb(22 34 48 / 0.14); --tick: rgb(22 34 48 / 0.55);
  --thumb: #162230; --btn: #162230; --btn-ink: #FFFFFF; --btn-hover: #0D1824; --star: #B7801A;
}
.rev { box-shadow: inset 0 0 0 1px var(--hair); }   /* the card keeps an edge on light grounds too */

/* the grounds */
html:not(.gm) [data-theme="paper"] { background: var(--g-paper); --ground: var(--g-paper); }
html:not(.gm) [data-theme="stone"] { background: var(--g-stone); --ground: var(--g-stone); }
html:not(.gm) [data-theme="cream"] { background: var(--g-cream); --ground: var(--g-cream); }
html:not(.gm) [data-theme="navy"] { background: var(--g-navy); --ground: var(--g-navy); }
html:not(.gm) [data-theme="deep"] { background: var(--g-deep); --ground: var(--g-deep); }
html:not(.gm) [data-theme="tan"] { background: var(--g-tan); --ground: var(--g-tan); }
html:not(.gm) .hero[data-theme] { background: #CFE6EF; }
/* the header keeps its own light glass on every ground (css/chrome.css); round 4's E32 had it take each section's
   colour with the switch */

/* ---- homepage/src/css/edges.css ---- */
/* ---------- section edges (round 6, 5 Nov 2026): written by tools/make_edges.py, never by hand ----------
   The ground switch is retired: every section keeps its own ground, and where the ground changes the lower
   section rises over the one above along a drawn water line, with a hairline on its crest (teal where water
   meets dark, ink elsewhere). A section that draws its own edge (data-self, .edge--top, the spring) is left
   alone. ::before is the hairline's colour cut to the shape, ::after the ground cut to the same shape a hair
   lower, so the line is the sliver between them. Static masks: nothing here runs while the page scrolls. */
:root { --edge-h: clamp(30px, 3.8vw, 56px); --edge-lift: 1.25px; }
body:not(.ip) { --edge-h: clamp(32px, 4vw, 60px); }   /* the homepage */
body.nt--city { --edge-h: clamp(34px, 4.8vw, 72px); }   /* city pages: tidal */
body.nt--about { --edge-h: clamp(24px, 2.8vw, 40px); }   /* About: calm */
body:is(.ip--post, .nt--list, .ip--stub) { --edge-h: clamp(22px, 2.4vw, 36px); }   /* the blog: gentle */
body:is(.nt--services, .nt--locations, .nt--contact, .nt--booking, .nt--legal) { --edge-h: clamp(28px, 3.4vw, 48px); }   /* hubs */
body.nt--landing { --edge-h: clamp(26px, 3vw, 44px); }   /* landings */
main > [data-theme="navy"], main > [data-theme="deep"] {
  --edge-a: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 64' preserveAspectRatio='none'%3E%3Cpath d='M0 48.1 C9 48.2 34 48.8 51 48.3 C69 47.8 86 46.3 103 45.4 C120 44.5 137 43.3 154 43.0 C171 42.6 189 42.8 206 43.1 C223 43.4 240 44.5 257 44.8 C274 45.1 291 45.5 309 44.7 C326 43.9 343 42.4 360 40.1 C377 37.8 394 34.3 411 31.2 C429 28.0 446 24.0 463 21.3 C480 18.6 497 16.1 514 15.0 C531 14.0 549 14.1 566 14.9 C583 15.7 600 18.0 617 19.9 C634 21.8 651 24.5 669 26.3 C686 28.1 703 29.6 720 30.5 C737 31.4 754 31.4 771 31.6 C789 31.8 806 31.3 823 31.8 C840 32.2 857 32.7 874 34.2 C891 35.6 909 37.9 926 40.3 C943 42.6 960 46.0 977 48.2 C994 50.5 1011 53.0 1029 54.0 C1046 55.0 1063 55.1 1080 54.1 C1097 53.1 1114 50.5 1131 48.0 C1149 45.5 1166 41.7 1183 38.9 C1200 36.1 1217 33.0 1234 31.1 C1251 29.2 1269 28.1 1286 27.5 C1303 26.8 1320 27.3 1337 27.3 C1354 27.3 1371 27.9 1389 27.6 C1406 27.3 1431 25.9 1440 25.5 V64H0Z'/%3E%3C/svg%3E");
  --edge-b: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 64' preserveAspectRatio='none'%3E%3Cpath d='M0 48.0 C9 47.6 34 47.3 51 45.6 C69 43.9 86 41.0 103 38.0 C120 35.0 137 30.7 154 27.6 C171 24.4 189 20.9 206 18.9 C223 16.9 240 15.8 257 15.6 C274 15.5 291 16.8 309 18.0 C326 19.3 343 21.6 360 23.2 C377 24.7 394 26.4 411 27.3 C429 28.2 446 28.4 463 28.5 C480 28.7 497 28.0 514 28.2 C531 28.3 549 28.4 566 29.5 C583 30.6 600 32.5 617 34.9 C634 37.2 651 40.6 669 43.4 C686 46.2 703 49.6 720 51.5 C737 53.4 754 54.9 771 55.0 C789 55.0 806 53.7 823 52.0 C840 50.2 857 47.0 874 44.5 C891 41.9 909 38.6 926 36.5 C943 34.4 960 32.7 977 31.7 C994 30.7 1011 30.7 1029 30.5 C1046 30.3 1063 30.8 1080 30.5 C1097 30.1 1114 29.7 1131 28.5 C1149 27.3 1166 25.3 1183 23.5 C1200 21.6 1217 19.0 1234 17.5 C1251 16.0 1269 14.6 1286 14.6 C1303 14.6 1320 15.7 1337 17.6 C1354 19.5 1371 22.9 1389 26.0 C1406 29.1 1431 34.5 1440 36.2 V64H0Z'/%3E%3C/svg%3E");
  --edge-c: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 64' preserveAspectRatio='none'%3E%3Cpath d='M0 20.8 C9 19.7 34 15.5 51 14.3 C69 13.0 86 12.7 103 13.5 C120 14.3 137 16.6 154 18.9 C171 21.2 189 24.7 206 27.2 C223 29.8 240 32.6 257 34.3 C274 36.0 291 37.0 309 37.6 C326 38.2 343 37.8 360 37.9 C377 38.0 394 37.7 411 38.3 C429 38.9 446 40.0 463 41.4 C480 42.9 497 45.3 514 47.1 C531 48.9 549 51.2 566 52.1 C583 53.1 600 53.6 617 52.7 C634 51.9 651 49.8 669 47.2 C686 44.7 703 40.7 720 37.5 C737 34.2 754 30.4 771 27.9 C789 25.3 806 23.3 823 22.3 C840 21.3 857 21.5 874 21.7 C891 22.0 909 23.3 926 23.8 C943 24.4 960 25.1 977 25.0 C994 25.0 1011 24.3 1029 23.6 C1046 23.0 1063 21.5 1080 21.1 C1097 20.6 1114 20.0 1131 20.7 C1149 21.4 1166 23.0 1183 25.3 C1200 27.6 1217 31.3 1234 34.5 C1251 37.8 1269 41.9 1286 44.7 C1303 47.6 1320 50.2 1337 51.4 C1354 52.7 1371 52.7 1389 52.2 C1406 51.6 1431 48.7 1440 48.1 V64H0Z'/%3E%3C/svg%3E");
}
main > [data-theme="tan"] {
  --edge-a: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 64' preserveAspectRatio='none'%3E%3Cpath d='M0 46.7 C9 46.9 34 48.4 51 47.9 C69 47.4 86 44.6 103 43.8 C120 43.0 137 43.0 154 43.2 C171 43.3 189 44.7 206 44.6 C223 44.5 240 42.8 257 42.5 C274 42.3 291 42.3 309 43.1 C326 43.9 343 46.7 360 47.2 C377 47.7 394 46.8 411 45.9 C429 45.0 446 42.6 463 41.6 C480 40.6 497 41.1 514 40.0 C531 38.9 549 37.2 566 35.0 C583 32.8 600 28.6 617 26.9 C634 25.2 651 24.9 669 24.6 C686 24.4 703 25.5 720 25.3 C737 25.1 754 23.4 771 23.4 C789 23.4 806 24.2 823 25.3 C840 26.5 857 29.6 874 30.3 C891 31.1 909 30.3 926 29.9 C943 29.6 960 28.1 977 28.1 C994 28.1 1011 29.8 1029 30.1 C1046 30.4 1063 30.0 1080 29.7 C1097 29.3 1114 27.4 1131 27.9 C1149 28.4 1166 30.8 1183 32.6 C1200 34.5 1217 37.5 1234 38.9 C1251 40.4 1269 40.2 1286 41.4 C1303 42.6 1320 44.5 1337 46.2 C1354 47.8 1371 50.8 1389 51.4 C1406 51.9 1431 49.8 1440 49.4 V64H0Z'/%3E%3C/svg%3E");
  --edge-b: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 64' preserveAspectRatio='none'%3E%3Cpath d='M0 47.9 C9 47.4 34 45.9 51 44.7 C69 43.5 86 41.9 103 41.0 C120 40.0 137 40.5 154 39.0 C171 37.6 189 34.6 206 32.4 C223 30.1 240 26.6 257 25.4 C274 24.2 291 25.1 309 25.1 C326 25.0 343 25.4 360 25.1 C377 24.9 394 23.3 411 23.6 C429 24.0 446 25.9 463 27.2 C480 28.4 497 30.7 514 31.0 C531 31.3 549 29.4 566 28.9 C583 28.5 600 27.9 617 28.1 C634 28.3 651 30.0 669 30.1 C686 30.2 703 28.8 720 28.5 C737 28.2 754 27.3 771 28.4 C789 29.4 806 32.9 823 34.8 C840 36.8 857 38.8 874 40.0 C891 41.3 909 41.1 926 42.5 C943 43.8 960 46.6 977 48.1 C994 49.6 1011 51.5 1029 51.4 C1046 51.4 1063 48.8 1080 47.8 C1097 46.9 1114 46.0 1131 45.5 C1149 45.1 1166 46.1 1183 45.3 C1200 44.6 1217 42.3 1234 41.2 C1251 40.2 1269 38.8 1286 38.9 C1303 39.0 1320 41.5 1337 42.0 C1354 42.5 1371 42.5 1389 41.9 C1406 41.4 1431 39.2 1440 38.7 V64H0Z'/%3E%3C/svg%3E");
  --edge-c: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 64' preserveAspectRatio='none'%3E%3Cpath d='M0 26.9 C9 25.9 34 21.7 51 20.8 C69 19.9 86 21.0 103 21.4 C120 21.8 137 22.9 154 23.2 C171 23.5 189 22.4 206 23.3 C223 24.2 240 26.6 257 28.5 C274 30.4 291 33.6 309 34.6 C326 35.6 343 34.6 360 34.6 C377 34.7 394 34.4 411 35.0 C429 35.5 446 37.6 463 37.9 C480 38.2 497 37.1 514 36.7 C531 36.4 549 35.1 566 35.8 C583 36.5 600 39.5 617 41.1 C634 42.7 651 44.5 669 45.4 C686 46.3 703 45.6 720 46.4 C737 47.3 754 49.4 771 50.4 C789 51.5 806 53.1 823 52.6 C840 52.2 857 49.2 874 47.6 C891 46.1 909 44.3 926 43.3 C943 42.2 960 42.6 977 41.4 C994 40.1 1011 37.4 1029 35.8 C1046 34.1 1063 32.0 1080 31.7 C1097 31.3 1114 33.4 1131 33.8 C1149 34.1 1166 34.4 1183 33.9 C1200 33.5 1217 31.5 1234 31.1 C1251 30.7 1269 31.8 1286 31.7 C1303 31.7 1320 31.9 1337 30.7 C1354 29.5 1371 26.0 1389 24.5 C1406 23.1 1431 22.3 1440 21.9 V64H0Z'/%3E%3C/svg%3E");
}
main > [data-theme="stone"] {
  --edge-a: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 64' preserveAspectRatio='none'%3E%3Cpath d='M0 46.2 C9 45.7 34 44.4 51 43.5 C69 42.6 86 40.8 103 40.9 C120 41.0 137 43.8 154 44.2 C171 44.6 189 45.3 206 43.5 C223 41.7 240 36.3 257 33.4 C274 30.4 291 26.4 309 25.7 C326 25.0 343 27.6 360 29.0 C377 30.4 394 33.5 411 34.1 C429 34.8 446 33.0 463 32.8 C480 32.6 497 31.6 514 33.1 C531 34.7 549 39.4 566 42.1 C583 44.9 600 49.0 617 49.6 C634 50.2 651 47.5 669 45.6 C686 43.7 703 39.5 720 38.2 C737 36.9 754 37.7 771 37.7 C789 37.7 806 39.3 823 38.2 C840 37.0 857 33.3 874 30.9 C891 28.6 909 24.4 926 24.0 C943 23.7 960 26.3 977 28.6 C994 30.9 1011 36.0 1029 37.9 C1046 39.7 1063 39.7 1080 39.8 C1097 39.9 1114 37.8 1131 38.4 C1149 39.0 1166 41.6 1183 43.4 C1200 45.2 1217 49.1 1234 49.3 C1251 49.4 1269 47.0 1286 44.5 C1303 41.9 1320 36.1 1337 33.9 C1354 31.6 1371 30.8 1389 30.8 C1406 30.7 1431 33.1 1440 33.5 V64H0Z'/%3E%3C/svg%3E");
  --edge-b: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 64' preserveAspectRatio='none'%3E%3Cpath d='M0 45.7 C9 45.1 34 44.5 51 42.0 C69 39.5 86 33.5 103 30.9 C120 28.2 137 26.1 154 26.1 C171 26.1 189 29.7 206 31.0 C223 32.3 240 33.8 257 33.8 C274 33.9 291 31.2 309 31.3 C326 31.4 343 32.0 360 34.2 C377 36.5 394 42.1 411 44.7 C429 47.2 446 49.6 463 49.4 C480 49.2 497 45.3 514 43.4 C531 41.5 549 38.7 566 38.0 C583 37.3 600 39.4 617 39.3 C634 39.2 651 39.3 669 37.5 C686 35.7 703 30.7 720 28.5 C737 26.2 754 23.6 771 24.0 C789 24.5 806 28.6 823 31.0 C840 33.4 857 37.4 874 38.6 C891 39.8 909 38.3 926 38.3 C943 38.3 960 37.3 977 38.5 C994 39.7 1011 43.8 1029 45.6 C1046 47.4 1063 50.1 1080 49.5 C1097 48.9 1114 44.7 1131 41.9 C1149 39.1 1166 34.3 1183 32.7 C1200 31.1 1217 32.1 1234 32.3 C1251 32.5 1269 34.5 1286 33.9 C1303 33.4 1320 30.5 1337 29.2 C1354 27.8 1371 25.2 1389 26.0 C1406 26.7 1431 32.4 1440 33.6 V64H0Z'/%3E%3C/svg%3E");
  --edge-c: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 64' preserveAspectRatio='none'%3E%3Cpath d='M0 29.3 C9 28.4 34 23.8 51 23.9 C69 24.1 86 27.8 103 30.1 C120 32.5 137 36.6 154 37.9 C171 39.2 189 37.9 206 37.9 C223 37.9 240 36.7 257 37.9 C274 39.1 291 43.0 309 45.0 C326 47.0 343 50.1 360 49.7 C377 49.4 394 45.7 411 43.0 C429 40.3 446 35.3 463 33.6 C480 31.8 497 32.5 514 32.6 C531 32.7 549 34.8 566 34.3 C583 33.7 600 31.0 617 29.5 C634 28.1 651 25.1 669 25.6 C686 26.1 703 29.6 720 32.5 C737 35.4 754 41.0 771 43.0 C789 45.0 806 44.8 823 44.5 C840 44.2 857 41.2 874 41.0 C891 40.8 909 42.2 926 43.1 C943 44.0 960 46.8 977 46.2 C994 45.7 1011 42.6 1029 39.7 C1046 36.8 1063 31.0 1080 28.8 C1097 26.7 1114 26.5 1131 27.1 C1149 27.7 1166 31.4 1183 32.4 C1200 33.4 1217 33.3 1234 33.0 C1251 32.7 1269 30.1 1286 30.6 C1303 31.2 1320 33.6 1337 36.3 C1354 38.9 1371 44.7 1389 46.8 C1406 48.8 1431 48.2 1440 48.5 V64H0Z'/%3E%3C/svg%3E");
}
main > [data-theme="paper"], main > [data-theme="cream"] {
  --edge-a: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 64' preserveAspectRatio='none'%3E%3Cpath d='M0 47.1 C9 47.0 34 47.3 51 46.3 C69 45.3 86 41.6 103 41.0 C120 40.4 137 42.3 154 42.8 C171 43.3 189 43.8 206 44.1 C223 44.5 240 45.0 257 45.0 C274 44.9 291 45.7 309 43.6 C326 41.6 343 35.7 360 32.7 C377 29.7 394 26.9 411 25.4 C429 24.0 446 24.2 463 24.1 C480 24.0 497 23.6 514 24.8 C531 26.0 549 30.0 566 31.1 C583 32.3 600 32.2 617 31.7 C634 31.2 651 28.3 669 28.2 C686 28.1 703 29.8 720 30.8 C737 31.8 754 32.3 771 34.3 C789 36.3 806 40.1 823 42.9 C840 45.7 857 50.2 874 51.2 C891 52.1 909 49.5 926 48.4 C943 47.3 960 46.0 977 44.6 C994 43.3 1011 41.6 1029 40.4 C1046 39.2 1063 37.1 1080 37.2 C1097 37.3 1114 40.7 1131 40.9 C1149 41.1 1166 40.2 1183 38.5 C1200 36.9 1217 33.4 1234 31.2 C1251 29.1 1269 27.7 1286 25.8 C1303 23.9 1320 20.2 1337 19.8 C1354 19.4 1371 21.4 1389 23.4 C1406 25.4 1431 30.4 1440 31.8 V64H0Z'/%3E%3C/svg%3E");
  --edge-b: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 64' preserveAspectRatio='none'%3E%3Cpath d='M0 47.7 C9 47.0 34 45.3 51 43.5 C69 41.7 86 39.2 103 36.8 C120 34.4 137 31.5 154 29.1 C171 26.6 189 22.6 206 22.1 C223 21.5 240 24.6 257 25.9 C274 27.2 291 29.1 309 29.7 C326 30.4 343 29.7 360 29.9 C377 30.1 394 31.2 411 30.8 C429 30.5 446 27.8 463 27.9 C480 28.0 497 29.2 514 31.5 C531 33.9 549 39.3 566 41.8 C583 44.4 600 45.6 617 47.0 C634 48.5 651 50.4 669 50.4 C686 50.4 703 48.8 720 47.0 C737 45.1 754 40.6 771 39.5 C789 38.3 806 39.9 823 40.0 C840 40.1 857 40.3 874 40.2 C891 40.0 909 39.6 926 39.0 C943 38.3 960 38.5 977 36.4 C994 34.2 1011 28.6 1029 26.0 C1046 23.4 1063 21.3 1080 20.9 C1097 20.5 1114 22.4 1131 23.4 C1149 24.4 1166 25.1 1183 27.0 C1200 28.8 1217 33.2 1234 34.7 C1251 36.3 1269 36.5 1286 36.3 C1303 36.1 1320 33.3 1337 33.4 C1354 33.6 1371 35.8 1389 37.0 C1406 38.3 1431 40.4 1440 41.0 V64H0Z'/%3E%3C/svg%3E");
  --edge-c: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 64' preserveAspectRatio='none'%3E%3Cpath d='M0 27.1 C9 25.9 34 20.4 51 19.7 C69 19.1 86 21.9 103 23.2 C120 24.6 137 26.2 154 28.0 C171 29.8 189 32.4 206 34.1 C223 35.9 240 38.4 257 38.5 C274 38.7 291 35.4 309 35.1 C326 34.8 343 35.7 360 36.9 C377 38.2 394 40.9 411 42.6 C429 44.2 446 45.3 463 46.9 C480 48.6 497 52.2 514 52.3 C531 52.4 549 49.9 566 47.6 C583 45.2 600 40.5 617 38.3 C634 36.1 651 35.7 669 34.5 C686 33.3 703 31.3 720 31.0 C737 30.7 754 32.2 771 32.7 C789 33.1 806 34.6 823 33.5 C840 32.4 857 28.0 874 26.3 C891 24.5 909 23.5 926 23.0 C943 22.5 960 22.3 977 23.0 C994 23.8 1011 24.8 1029 27.4 C1046 30.0 1063 36.2 1080 38.7 C1097 41.3 1114 42.1 1131 42.7 C1149 43.2 1166 42.1 1183 42.0 C1200 42.0 1217 42.6 1234 42.3 C1251 42.1 1269 40.1 1286 40.7 C1303 41.2 1320 44.2 1337 45.7 C1354 47.2 1371 50.0 1389 49.8 C1406 49.7 1431 45.6 1440 44.8 V64H0Z'/%3E%3C/svg%3E");
}
main > :nth-child(3n+1) { --edge-m: var(--edge-a); }
main > :nth-child(3n+2) { --edge-m: var(--edge-b); }
main > :nth-child(3n) { --edge-m: var(--edge-c); }
main > [data-theme] { --edge-line: rgb(22 34 48 / 0.15); }
main > [data-tone="dark"], main > [data-tone="dark"] + [data-tone="light"] { --edge-line: rgb(43 184 175 / 0.5); }
:is(main > [data-theme="navy"] + [data-theme]:not([data-theme="navy"]), main > [data-theme="deep"] + [data-theme]:not([data-theme="deep"]), main > [data-theme="tan"] + [data-theme]:not([data-theme="tan"]), main > [data-theme="stone"] + [data-theme]:not([data-theme="stone"]), main > [data-theme="paper"] + [data-theme]:not([data-theme="paper"]), main > [data-theme="cream"] + [data-theme]:not([data-theme="cream"])):not([data-self]):not(:has(> .edge--top, > .sky__edge))::before,
:is(main > [data-theme="navy"] + [data-theme]:not([data-theme="navy"]), main > [data-theme="deep"] + [data-theme]:not([data-theme="deep"]), main > [data-theme="tan"] + [data-theme]:not([data-theme="tan"]), main > [data-theme="stone"] + [data-theme]:not([data-theme="stone"]), main > [data-theme="paper"] + [data-theme]:not([data-theme="paper"]), main > [data-theme="cream"] + [data-theme]:not([data-theme="cream"])):not([data-self]):not(:has(> .edge--top, > .sky__edge))::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: calc(100% - 1px); height: var(--edge-h);
  pointer-events: none;
  -webkit-mask: var(--edge-m) center top / max(100%, 900px) 100% no-repeat;
  mask: var(--edge-m) center top / max(100%, 900px) 100% no-repeat; }
:is(main > [data-theme="navy"] + [data-theme]:not([data-theme="navy"]), main > [data-theme="deep"] + [data-theme]:not([data-theme="deep"]), main > [data-theme="tan"] + [data-theme]:not([data-theme="tan"]), main > [data-theme="stone"] + [data-theme]:not([data-theme="stone"]), main > [data-theme="paper"] + [data-theme]:not([data-theme="paper"]), main > [data-theme="cream"] + [data-theme]:not([data-theme="cream"])):not([data-self]):not(:has(> .edge--top, > .sky__edge))::before { background: var(--edge-line); }
:is(main > [data-theme="navy"] + [data-theme]:not([data-theme="navy"]), main > [data-theme="deep"] + [data-theme]:not([data-theme="deep"]), main > [data-theme="tan"] + [data-theme]:not([data-theme="tan"]), main > [data-theme="stone"] + [data-theme]:not([data-theme="stone"]), main > [data-theme="paper"] + [data-theme]:not([data-theme="paper"]), main > [data-theme="cream"] + [data-theme]:not([data-theme="cream"])):not([data-self]):not(:has(> .edge--top, > .sky__edge))::after { background: var(--ground); translate: 0 var(--edge-lift); }
main > [data-theme]:nth-child(even)::before, main > [data-theme]:nth-child(even)::after { scale: -1 1; }
/* the aerial: the paper above hangs over the photo's top in a wave (no ground band, no straight edge) */
main > .vista { isolation: isolate; }
:is(main > [data-theme="navy"] + [data-theme]:not([data-theme="navy"]), main > [data-theme="deep"] + [data-theme]:not([data-theme="deep"]), main > [data-theme="tan"] + [data-theme]:not([data-theme="tan"]), main > [data-theme="stone"] + [data-theme]:not([data-theme="stone"]), main > [data-theme="paper"] + [data-theme]:not([data-theme="paper"]), main > [data-theme="cream"] + [data-theme]:not([data-theme="cream"])).vista:not([data-self]):not(:has(> .edge--top, > .sky__edge))::before { content: none; }
:is(main > [data-theme="navy"] + [data-theme]:not([data-theme="navy"]), main > [data-theme="deep"] + [data-theme]:not([data-theme="deep"]), main > [data-theme="tan"] + [data-theme]:not([data-theme="tan"]), main > [data-theme="stone"] + [data-theme]:not([data-theme="stone"]), main > [data-theme="paper"] + [data-theme]:not([data-theme="paper"]), main > [data-theme="cream"] + [data-theme]:not([data-theme="cream"])).vista:not([data-self]):not(:has(> .edge--top, > .sky__edge))::after {
  top: -1px; bottom: auto; background: var(--g-paper); translate: none; scale: 1 -1; z-index: 2; }

/* ---- homepage/src/css/chrome.css ---- */
/* ---------- header: the full-colour logo, centred, always on a light ground ---------- */
.site-head { position: fixed; inset: 0 0 auto; z-index: 40; pointer-events: none;
  transition: transform var(--dur-std) var(--ease-calm); }
.site-head__bar { pointer-events: auto; display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center; gap: 20px; height: 84px; }
/* the light ground appears only once the page has scrolled: glass at 84% with one 12 px blur (x.ai's recipe) */
.site-head::before { content: ''; position: absolute; inset: 0; background: var(--glass); backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px); border-bottom: 1px solid var(--hair); opacity: 0;
  transition: opacity var(--dur-std) var(--ease-calm); }
.site-head.is-solid::before { opacity: 1; }
.site-head.is-tucked { transform: translateY(-100%); }
.site-head__brand { --bw: clamp(150px, 13vw, 196px); position: relative; justify-self: center; display: block; width: var(--bw); line-height: 0; }
.site-head__brand .cq-logo { width: 100%; height: auto; overflow: visible; }
.site-head__test, .site-head__tel { position: relative; font: 600 15px/1 var(--font-body); color: var(--ink); text-decoration: none; }
.site-head__test { justify-self: start; }
.site-head__tel { justify-self: end; letter-spacing: 0.01em; }
@media (hover: hover) and (pointer: fine) {
  .site-head__test:hover, .site-head__tel:hover { text-decoration: underline; }
}
@media (max-width: 760px) {
  .site-head__bar { grid-template-columns: 1fr; height: 72px; }
  .site-head__test, .site-head__tel { display: none; }   /* one header action on a phone: the number lives in the menu */
}

/* a long in-page jump dips through a veil instead of racing down the page */
.jump-veil { position: fixed; inset: 0; z-index: 45; pointer-events: none; opacity: 0; background: var(--paper); }

/* ---------- the logo's life: a glint across the drop and a slow wave shift, every few seconds; waves flow on hover ---------- */
.cq-logo .m-glint { transform: translateX(-70px); }
.cq-logo .m-wave, .cq-logo .m-drop { transform-box: fill-box; transform-origin: 50% 50%; }
.cq-logo.is-breathing .m-glint { animation: cq-glint 1.6s var(--ease-calm) both; }
.cq-logo.is-breathing .m-wave { animation: cq-wave 2.4s var(--ease-in-out) both; }
.cq-logo.is-breathing .m-drop { animation: cq-drop 2.4s var(--ease-in-out) both; }
@keyframes cq-glint { from { transform: translateX(-70px); } to { transform: translateX(90px); } }
@keyframes cq-wave { 0%, 100% { transform: translateX(0); } 45% { transform: translateX(-4px); } }
@keyframes cq-drop { 0%, 100% { transform: translateY(0); } 40% { transform: translateY(2.2px); } 70% { transform: translateY(-0.8px); } }
@media (hover: hover) and (pointer: fine) {
  .site-head__brand:hover .m-wave { animation: cq-flow 1.8s var(--ease-in-out) infinite; }
  .site-head__brand:hover .m-drop { animation: cq-bob 1.8s var(--ease-in-out) infinite; }
  .site-head__brand:hover .m-glint { animation: cq-glint 1.1s var(--ease-calm) both; }
}
@keyframes cq-flow { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(-6px); } }
@keyframes cq-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-2.5px); } }
@media (prefers-reduced-motion: reduce) {
  .cq-logo .m-glint, .cq-logo .m-wave, .cq-logo .m-drop { animation: none !important; }
}

/* ---- homepage/src/css/loader.css ---- */
/* ---------- loader: the round mark draws itself, a drop falls in, the water rises, the word writes in, and the
   whole lockup flies up into the header's own slot (it IS the header logo, so the landing is exact).
   Mechanics borrowed and tailored: Medscapes (CSS-only first part, first paint under the opaque ground, a 180 ms
   hold then a 1.1 s opacity dissolve, every load and back-forward restore); Bay Balcony (the mark drawn in, then
   handed to the page). Classes come from the head script before first paint: html.ld (ground on), html.ld-centre
   (the logo sits large in the middle). Reduced motion never sets them. ---------- */
.loader { display: none; }
html.ld-on .loader { display: block; position: fixed; inset: 0; z-index: 39; pointer-events: none;
  background: linear-gradient(180deg, #CFE6EF 0%, #E3EFF0 55%, #EEF3EE 100%);
  animation: ld-failsafe 1ms 6.5s forwards; }        /* if the script never runs, the page still opens */
@keyframes ld-failsafe { to { opacity: 0; visibility: hidden; } }

html.ld-on .site-head__test, html.ld-on .site-head__tel { opacity: 0; }
.site-head__test, .site-head__tel { transition: opacity 700ms var(--ease-calm) 300ms; }

/* The logo is moved to the middle by a transform alone: its layout box never leaves the header slot, so the handoff
   is not a layout shift (as position: fixed it scored CLS 0.037 on a phone, the page's only shift). The slot's centre
   sits half a header below the top: 42 px, 36 px on a phone. */
/* Only the wordmark plays the loader (Ali, 2026-10-29: "just the clearquest wordmark and cut out the circle and water
   drop logo from it, thats whats making it broken"). The word, not the whole lockup, sits in the middle: the lockup is
   shifted left by the word's own offset (the word's centre is 14.02% of the lockup's width right of the lockup's
   centre, 1.29% low), so the move stays a transform and the layout box never leaves the slot (CLS 0). */
html.ld-centre .site-head__brand { z-index: 41;
  transform: translate(calc(var(--bw) * -0.1402 * var(--ld-s, 2.5)), calc(47svh - var(--ld-mid, 42px) - var(--bw) * 0.0129 * var(--ld-s, 2.5)))
    scale(var(--ld-s, 2.5));
  animation: ld-brand-failsafe 1ms 6.5s forwards; }
@keyframes ld-brand-failsafe { to { transform: none; } }
@media (max-width: 760px) { html.ld-centre .site-head__brand { --ld-s: 1.95; --ld-mid: 36px; } }

/* The word writes itself in: a soft-edged mask sweeps left to right over a faint ghost of the word. The ghost (14%) is
   on screen from the first frame: Chrome holds a page with nothing contentful to paint (measured once: 3.6 s blank). */
.cq-logo .m-draw, .cq-logo .m-ripple { opacity: 0; }
html.ld-centre .cq-logo :is(.m-ring, .m-water, .m-drop, .m-glint) { opacity: 0; }
html.ld-centre .cq-logo .w-tag { animation: ld-fade-in 620ms var(--ease-calm) 1000ms both; }
html.ld-centre .site-head__brand {
  -webkit-mask-image: linear-gradient(90deg, #000 40%, rgb(0 0 0 / 0.14) 52%); mask-image: linear-gradient(90deg, #000 40%, rgb(0 0 0 / 0.14) 52%);
  -webkit-mask-size: 240% 100%; mask-size: 240% 100%; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  animation: ld-sweep 1150ms cubic-bezier(0.33, 0, 0.2, 1) 120ms both, ld-brand-failsafe 1ms 6.5s forwards; }
/* at 100% the solid stop has passed the word's right edge; at 0% only the mark (hidden) sits under the solid part */
@keyframes ld-sweep { from { -webkit-mask-position: 82% 0; mask-position: 82% 0; } to { -webkit-mask-position: -6% 0; mask-position: -6% 0; } }   /* -6%: the soft edge ends past the last letter, so the word is fully solid before the mask lifts */
/* the ring and the drop arrive as the logo lands */
html.ld-reveal .cq-logo :is(.m-ring, .m-water, .m-drop) { animation: ld-fade-in 700ms var(--ease-calm) 260ms both; }
@keyframes ld-fade-in { from { opacity: 0; } to { opacity: 1; } }

/* the page under it settles in as the ground dissolves (compositor-only: opacity and transform). The words wait until
   the flying logo has climbed past them: on a phone its path runs straight up through the headline (crossing it 430
   to 620 ms into the 1,000 ms flight, which starts 180 ms after this class), so they hold 640 ms; on wider screens
   it passes over the drawing, so 300 ms keeps the same order without the wait */
html { --ld-copy: 300ms; }
@media (max-width: 760px) { html { --ld-copy: 640ms; } }
html.ld-reveal .hero__copy > * { animation: ld-up 900ms var(--ease-out) var(--ld-copy) both; }
html.ld-reveal .hero__copy > :nth-child(2) { animation-delay: calc(var(--ld-copy) + 70ms); }
html.ld-reveal .hero__copy > :nth-child(3) { animation-delay: calc(var(--ld-copy) + 140ms); }
html.ld-reveal .hero__copy > :nth-child(4) { animation-delay: calc(var(--ld-copy) + 210ms); }
html.ld-reveal .hero__copy > :nth-child(5) { animation-delay: calc(var(--ld-copy) + 280ms); }
@keyframes ld-up { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }

/* round 4 (E11, Ali: "i really like the form submission animation, i want something smooth like that built and added
   to the loader"; picked A of three rendered options): the booking thank-you's glass sits under the word and fills as
   it writes in: a drop falls (gravity's ease-in, a stretch, a squash), beads leap, three rings spread, the water rises
   on the release curve, the light on the coaster blooms and a glint crosses the glass, all done by 1.6 s, before the
   flight. It belongs to the loader's ground, so it dissolves with it and never flies with the word; the logo's own ring
   and drop stay out (Ali, 29 Oct). Compositor only (transform and opacity); reduced motion never shows the loader. */
.loader .ld-glass { position: absolute; left: 50%; top: calc(47svh + var(--ld-lw) * 0.13); width: calc(var(--ld-lw) * 0.25);
  transform: translateX(-50%); overflow: visible; }
.loader { --ld-lw: calc(clamp(150px, 13vw, 196px) * 2.5); }
@media (max-width: 760px) { .loader { --ld-lw: calc(clamp(150px, 13vw, 196px) * 1.95); } }
.ld-glass * { transform-box: fill-box; transform-origin: 50% 50%; }
.ld-g { animation: ld-g-in 520ms cubic-bezier(0.16, 1, 0.3, 1) both; }
.ld-drop { opacity: 0; animation: ld-g-fall 600ms cubic-bezier(0.55, 0.05, 0.85, 0.35) 140ms both; }
.ld-water { transform-origin: 50% 100%; animation: ld-g-rise-w 900ms cubic-bezier(0.22, 1, 0.36, 1) 700ms both; }
.ld-surface { animation: ld-g-rise-s 900ms cubic-bezier(0.22, 1, 0.36, 1) 700ms both; }
.ld-ring { opacity: 0; animation: ld-g-ring 1300ms cubic-bezier(0.16, 1, 0.3, 1) both; }
.ld-r1 { animation-delay: 720ms; } .ld-r2 { animation-delay: 880ms; } .ld-r3 { animation-delay: 1060ms; }
.ld-bead { opacity: 0; animation: 540ms cubic-bezier(0.2, 0.6, 0.4, 1) 720ms both; }
.ld-b1 { animation-name: ld-g-b1; } .ld-b2 { animation-name: ld-g-b2; animation-delay: 740ms; } .ld-b3 { animation-name: ld-g-b3; animation-delay: 730ms; }
.ld-caustic { animation: ld-g-caus 1100ms cubic-bezier(0.16, 1, 0.3, 1) 820ms both; }
.ld-glint { opacity: 0; animation: ld-g-glint 1000ms cubic-bezier(0.65, 0, 0.35, 1) 1300ms both; }
@keyframes ld-g-in { from { opacity: 0; transform: translateY(8px) scale(0.97); } }
@keyframes ld-g-fall { 0% { opacity: 0; transform: translateY(-104px) scale(0.92, 1); } 14% { opacity: 1; } 86% { opacity: 1; transform: translateY(-5px) scale(0.9, 1.16); } 100% { opacity: 0; transform: translateY(3px) scale(1.3, 0.42); } }
@keyframes ld-g-ring { 0% { opacity: 0.95; transform: scale(0.06); } 65% { opacity: 0.45; } 100% { opacity: 0; transform: scale(1); } }
@keyframes ld-g-rise-w { from { transform: scaleY(0.626); } }
@keyframes ld-g-rise-s { from { transform: translateY(25.8px); } }
@keyframes ld-g-caus { from { opacity: 0.3; transform: scale(0.7, 0.8); } }
@keyframes ld-g-glint { 0% { opacity: 0; transform: translateX(-24px); } 40% { opacity: 0.5; } 100% { opacity: 0; transform: translateX(24px); } }
@keyframes ld-g-b1 { 0% { opacity: 1; transform: none; } 45% { transform: translate(-9px, -17px); } 100% { opacity: 0; transform: translate(-14px, -2px); } }
@keyframes ld-g-b2 { 0% { opacity: 1; transform: none; } 45% { transform: translate(8px, -20px); } 100% { opacity: 0; transform: translate(13px, -4px); } }
@keyframes ld-g-b3 { 0% { opacity: 1; transform: none; } 45% { transform: translate(-3px, -24px); } 100% { opacity: 0; transform: translate(-5px, -8px); } }

/* the island arrives with the page, never over the loader */
html.ld-on .isl { visibility: hidden; transform: translateY(110px); }   /* the island rises from below once the loader hands over: opaque the whole way, so its text never sits half-faded */
.isl { transition: transform 900ms var(--ease-out) 250ms, translate 520ms var(--ease-out); }   /* translate: stepping aside for the form (menu.css) */

/* ---- homepage/src/css/menu.css ---- */
/* ---------- the island and its mega menu ----------
   Closed: a light glass pill at the bottom centre (Bay Balcony's island: a drop that fills as you read, the section's
   name, Menu, Free test). Open: the same shape grows up into the mega menu, a clip-path on one full-size panel
   (Medscapes' wipe and stagger); on phones and tablets the panel is a full-screen sheet that scrolls itself. Closing is
   faster and leaves the way the shape goes: every item sinks out ahead of the edge, the shape lands back in the bar.
   - State lives on .isl (.is-open), a ~150-element subtree. html.menu-open carries only the scroll lock (no rule keys
     a descendant on it), so opening and closing never restyle the page (measured before: 2,844 elements per toggle).
   - Motion is compositor-only: clip-path, transform, opacity. The glass blur is the bar's own small layer (.isl__glass);
     the open ground is a plain layer that fades in, so no backdrop blur ever spans the panel or the screen.
   - Colours are the island's own (--isl-*): a page theme that flips the ground never reaches it. */
.isl {
  --isl-ink: #162230; --isl-ink-2: #44505D; --isl-acc: #0A7672; --isl-spring: #2BB8AF; --isl-spring-hi: #48C9C0;
  --isl-ground: #F8F9F6; --isl-glass: rgb(250 251 249 / 0.88); --isl-hair: rgb(22 34 48 / 0.12); --isl-card: #FFFFFF;
  --isl-frame: #EEF1EC; --isl-scrim: rgb(14 42 53 / 0.32); --isl-star: #C98A12;
  --isl-calm: cubic-bezier(0.4, 0, 0.2, 1); --isl-io: cubic-bezier(0.65, 0, 0.35, 1);
  --isl-out: cubic-bezier(0.16, 1, 0.3, 1); --isl-in: cubic-bezier(0.4, 0, 1, 1);
  --isl-wipe: 600ms var(--isl-calm);                 /* the open; phones re-set it */
  --isl-c0: 330ms;                                   /* content starts once the widening edge has passed it */
  /* values the clip-path reads stay plain calc(), no min()/max(): the desktop wipe is verified composited (it kept
     moving through a 450 ms main-thread stall; no compositeFailed in a trace). Phones use a transform (see below). */
  --bar-w: 452px; --bar-h: 62px; --isl-b: calc(16px + env(safe-area-inset-bottom, 0px));
  /* round 3 (E8): on short windows the panel takes the height the header gave up (the scrim dims it anyway), and the
     menu's type, arch and lists scale with the window's height below, so the rating card's foot is never cut */
  --panel-w: min(1180px, calc(100% - 32px)); --panel-h: min(740px, calc(100% - 40px));
  position: fixed; inset: 0; z-index: 50; pointer-events: none; color: var(--isl-ink);
}
.isl .label { color: var(--isl-ink-2); }
.isl :focus-visible { outline: 2px solid var(--isl-acc); outline-offset: 3px; }   /* ring only: the pills keep their round ends (E9) */
html.menu-open { overflow: hidden; }                 /* the scroll lock; scrollbar-gutter: stable keeps the page still */

.isl__scrim { position: absolute; inset: 0; background: var(--isl-scrim); opacity: 0;
  transition: opacity 420ms var(--isl-calm) 60ms; }
.isl.is-open .isl__scrim { opacity: 1; pointer-events: auto; transition: opacity 560ms var(--isl-calm); }

/* the bar's glass: its own layer under the panel, exactly the closed bar's box, so the blur reads the page and never
   spans more than the bar. Covered by the open ground; hidden once covered so it does no work while the menu is open. */
.isl__glass { position: absolute; left: 0; right: 0; bottom: var(--isl-b); margin-inline: auto; width: var(--bar-w); height: var(--bar-h);
  border-radius: calc(var(--bar-h) / 2); background: var(--isl-glass);
  -webkit-backdrop-filter: blur(14px) saturate(1.15); backdrop-filter: blur(14px) saturate(1.15);
  box-shadow: inset 0 0 0 1px var(--isl-hair); }   /* no outer shadow: it would show below the panel's edge mid-wipe */
.isl.is-open .isl__glass { visibility: hidden; transition: visibility 0s linear 200ms; }

/* ---- the panel: one shape, clipped to the bar while closed (clip-path clips hit-testing too) ---- */
.isl__panel { position: absolute; left: 0; right: 0; bottom: var(--isl-b); margin-inline: auto; width: var(--panel-w); height: var(--panel-h);
  pointer-events: auto; contain: layout style;
  clip-path: inset(calc(100% - var(--bar-h)) calc((100% - var(--bar-w)) / 2) 0px round calc(var(--bar-h) / 2));
  transition: clip-path 470ms var(--isl-io) 110ms; }
.isl.is-open .isl__panel { clip-path: inset(0px 0px 0px round 26px); transition: clip-path var(--isl-wipe); }
.isl__sheet { position: absolute; inset: 0; }        /* ground + content; on phones it is the part that moves */
.isl__ground { position: absolute; inset: 0; border-radius: 26px; background: var(--isl-ground); box-shadow: inset 0 0 0 1px var(--isl-hair);
  opacity: 0; transition: opacity 130ms linear 450ms; }      /* close: gone exactly as the shape lands, the glass beneath */
.isl.is-open .isl__ground { opacity: 1; transition: opacity 160ms var(--isl-calm); }

/* ---- the bar (always at the bottom centre of the panel) ---- */
.isl__bar { position: absolute; left: 0; right: 0; bottom: 0; z-index: 1; margin-inline: auto; width: var(--bar-w); height: var(--bar-h);
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto; align-items: center; gap: 10px; padding: 0 7px 0 16px; }
.isl__drop { width: 22px; height: 28px; display: block; }
.isl__drop svg { width: 100%; height: 100%; overflow: visible; }
.isl__fill { transform: translateY(calc((1 - var(--read, 0)) * 30px)); }
.isl__label { position: relative; height: 18px; overflow: hidden; font-size: 12px; }   /* round 3 polish: the 12 px floor */
.isl__label > span { position: absolute; inset: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.isl__label > span:last-child { transform: translateY(110%); opacity: 0; }
.isl__label.is-swapping > span:first-child { transform: translateY(-110%); opacity: 0; transition: transform 420ms var(--isl-calm), opacity 420ms var(--isl-calm); }
.isl__label.is-swapping > span:last-child { transform: none; opacity: 1; transition: transform 420ms var(--isl-calm), opacity 420ms var(--isl-calm); }

/* Menu <-> Close: two lines turn into a cross (composited transforms on two spans) while the word slides, both 380 ms
   on one curve; the two words share one grid cell, so the button keeps one width in both states */
.isl__menu { position: relative; display: inline-flex; align-items: center; gap: 10px; height: 46px; padding: 0 16px 0 15px; border: 0; border-radius: 999px;
  background: transparent; font: 600 15px/1 var(--font-body); color: var(--isl-ink);
  transition: background-color 350ms var(--isl-calm), transform 160ms var(--isl-calm); }
.isl__menu::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 0 0 1px var(--isl-hair); opacity: 0;
  transition: opacity 300ms var(--isl-calm); }
.isl__burger { position: relative; flex: none; width: 18px; height: 12px; }
.isl__burger i { position: absolute; left: 0; width: 100%; height: 1.8px; border-radius: 2px; background: currentColor;
  transition: transform 380ms var(--isl-io); }
.isl__burger i:first-child { top: 2px; }
.isl__burger i:last-child { bottom: 2px; }
.isl.is-open .isl__burger i:first-child { transform: translateY(3.1px) rotate(45deg); }
.isl.is-open .isl__burger i:last-child { transform: translateY(-3.1px) rotate(-45deg); }
.isl__say { display: grid; overflow: hidden; line-height: 1.25; }
.isl__say > span { grid-area: 1 / 1; transition: transform 380ms var(--isl-io), opacity 300ms var(--isl-calm); }
.isl__say > span:last-child { transform: translateY(105%); opacity: 0; }
.isl.is-open .isl__say > span:first-child { transform: translateY(-105%); opacity: 0; }
.isl.is-open .isl__say > span:last-child { transform: none; opacity: 1; }
.isl__book { display: inline-flex; align-items: center; height: 48px; padding: 0 20px; border-radius: 999px; background: var(--isl-spring);
  color: var(--isl-ink); font: 650 15.5px/1 var(--font-body); text-decoration: none; white-space: nowrap;
  transition: background-color 350ms var(--isl-calm), transform 160ms var(--isl-calm); }
@media (hover: hover) and (pointer: fine) {
  .isl__menu:hover { background: rgb(22 34 48 / 0.06); }
  .isl__book:hover { background: var(--isl-spring-hi); }
}
.isl__menu:active, .isl__book:active { transform: scale(0.97); }

/* ---- the mega menu (above the bar) ---- */
.isl__mega { position: absolute; inset: 0 0 var(--bar-h); padding: clamp(18px, min(2.6vw, 4.2vh), 38px) clamp(24px, 3vw, 46px) 14px;
  display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.8fr) minmax(0, 1.25fr); grid-template-rows: auto 1fr;
  column-gap: clamp(20px, 3vw, 48px); overflow: hidden auto; scrollbar-width: thin; scrollbar-color: rgb(22 34 48 / 0.22) transparent;
  contain: layout style; }   /* sized to fit at every height; the inner scroll is only the last net for a very short window */
.isl__top { grid-column: 1 / -1; display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
.isl__big { list-style: none; margin: 0; padding: 0; align-self: center; display: grid; gap: 2px; }
.isl__big a { display: flex; align-items: baseline; gap: 14px; padding: clamp(2px, 0.7vh, 6px) 0; font: 700 clamp(24px, min(2.5vw, 5.2vh), 40px)/1.08 var(--font-display);
  letter-spacing: -0.028em; color: var(--isl-ink); text-decoration: none; transition: color 350ms var(--isl-calm); }
.isl__big .n { display: inline-grid; place-items: center; min-width: 2.7em; padding: 4px 0; border-radius: 999px;
  font: 500 12px/1 var(--font-mono); letter-spacing: 0.06em; color: var(--isl-ink-2); transition: color 350ms var(--isl-calm); }
.isl__big a[data-here] .n { color: var(--isl-acc); box-shadow: inset 0 0 0 1px currentColor; }   /* where you opened it */
@media (hover: hover) and (pointer: fine) {
  .isl__big a:hover { color: var(--isl-acc); }   /* planted: colour and the arch's drawing answer the pointer (round 3; it slid 6 px) */
  .isl__big a:hover .n { color: var(--isl-acc); }
}
.isl__big a:active { color: var(--isl-acc); }
.isl__big li.is-chosen a { color: var(--isl-acc); }

/* the arch: a Florida water specimen per menu line; the three drawings are layers that cross-fade (no markup swap on hover) */
.isl__arch { align-self: center; display: grid; justify-items: center; gap: clamp(8px, 1.6vh, 14px); text-align: center; }
.isl__arch-frame { position: relative; width: min(250px, 100%, 36vh); aspect-ratio: 5 / 6.4; border-radius: 999px 999px 18px 18px; overflow: hidden;
  background: linear-gradient(180deg, #DDEFF2, #F3F1E8); box-shadow: inset 0 0 0 1px rgb(22 34 48 / 0.08); }
.isl__spec { position: absolute; inset: 0; }
.isl__layer { position: absolute; inset: 0; display: grid; place-items: end center; opacity: 0; transform: translateY(12px) scale(0.96);
  transform-origin: 50% 100%; transition: opacity 180ms var(--isl-in), transform 300ms var(--isl-in); }
.isl__layer.is-on { opacity: 1; transform: none; transition: opacity 420ms var(--isl-out) 90ms, transform 620ms var(--isl-out) 90ms; }
.isl__layer svg { display: block; width: 100%; height: 100%; }
.isl__arch figcaption { display: grid; gap: 3px; max-width: 240px; }
.isl__arch b { font: 700 18px/1.2 var(--font-display); letter-spacing: -0.01em; }
.isl__arch i { font: italic 400 14px/1.3 var(--font-quote); color: var(--isl-ink-2); }
.isl__arch span { min-height: 2.9em; font-size: 14px; line-height: 1.45; color: var(--isl-ink-2); }   /* two lines held: no reflow on swap */

.isl__cols { align-self: center; display: grid; grid-template-columns: 1fr 0.8fr; gap: 26px 28px; }
.isl__cols ul { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 4px; }
.isl__cols ul a { font-size: 16px; line-height: 1.35; color: var(--isl-ink); text-decoration: none; display: inline-block; padding: 3px 0;
  transition: color 350ms var(--isl-calm); }
@media (hover: hover) and (pointer: fine) { .isl__cols ul a:hover { color: var(--isl-acc); text-decoration: underline; } }
.isl__th { display: none; }                          /* drawn equipment tiles: phones only (loaded on intent, see menu.js) */
.isl__card { grid-column: 1 / -1; display: grid; grid-template-columns: auto 1fr; align-items: center; gap: 12px 20px;
  padding: 18px 20px; border-radius: 18px; background: var(--isl-card); box-shadow: inset 0 0 0 1px var(--isl-hair); }
.isl__rate { grid-column: 1 / -1; font-size: 15px; }
.isl__rate .stars { color: var(--isl-star); letter-spacing: 0.08em; }
.isl__tel { font: 700 22px/1 var(--font-display); color: var(--isl-ink); text-decoration: none; white-space: nowrap; letter-spacing: -0.01em; }
.isl__where { grid-column: 1 / -1; font-size: 14px; line-height: 1.45; color: var(--isl-ink-2); }
/* short desktop windows (a laptop with its browser's bars, about 590 to 720 px tall): the lists and the card tighten
   so the whole menu stands inside the panel; measured at 1280 x 590, 1366 x 640 and 1536 x 690 (E8) */
@media (min-width: 961px) and (max-height: 760px) {
  .isl__cols { gap: 16px 24px; }
  .isl__cols ul { margin-top: 8px; gap: 1px; }
  .isl__cols ul a { font-size: 15px; padding: 2px 0; }
  .isl__card { gap: 6px 16px; padding: 12px 16px; border-radius: 14px; }
  .isl__rate { font-size: 14px; }
  .isl__tel { font-size: 19px; }
  .isl__where { font-size: 13px; line-height: 1.4; }
  .isl__arch figcaption { gap: 1px; }
  .isl__arch b { font-size: 16px; }
  .isl__arch i, .isl__arch span { font-size: 13px; }
  .isl__arch span { min-height: 2.8em; }
}
@media (min-width: 961px) and (max-height: 640px) {
  .isl__group--co ul { grid-template-columns: 1fr 1fr; column-gap: 14px; }
  .isl__arch span { display: none; }      /* the specimen's name and subtitle stay; the note goes on very short windows */
}

/* ---- the beats ----
   Open, two movements: the shape grows out of the bar (600 ms, calm curve: 89% of its width and height by ~330 ms),
   then the content rises in once the edge has passed it, so no line is ever cut by a moving edge: the kicker, the seven
   lines 36 ms apart, the arch, the columns. Opacity rides the calm curve (it starts slow, so nothing is half there before
   its turn), travel rides the long ease-out. Close (580 ms against ~1.2 s): everything sinks out top first within 210 ms,
   ahead of the edge that sweeps down into the bar from 110 ms; a chosen line holds until the edge takes it. */
.isl__top, .isl__arch, .isl__cols, .isl__big li { opacity: 0; transform: translateY(14px); }
.isl__top { transition: opacity 140ms var(--isl-in), transform 200ms var(--isl-in); }
.isl__big li { transition: opacity 140ms var(--isl-in) calc(var(--i) * 12ms), transform 200ms var(--isl-in) calc(var(--i) * 12ms); }
.isl__arch { transition: opacity 140ms var(--isl-in) 30ms, transform 200ms var(--isl-in) 30ms; }
.isl__cols { transition: opacity 140ms var(--isl-in) 50ms, transform 200ms var(--isl-in) 50ms; }
.isl__big li.is-chosen { opacity: 1; transform: none; transition: none; }
.isl.is-open .isl__top, .isl.is-open .isl__arch, .isl.is-open .isl__cols, .isl.is-open .isl__big li { opacity: 1; transform: none; }
.isl.is-open .isl__top { transition: opacity 460ms var(--isl-calm) calc(var(--isl-c0) - 30ms), transform 640ms var(--isl-out) calc(var(--isl-c0) - 30ms); }
.isl.is-open .isl__big li { transition: opacity 460ms var(--isl-calm) calc(var(--isl-c0) + var(--i) * 36ms), transform 640ms var(--isl-out) calc(var(--isl-c0) + var(--i) * 36ms); }
.isl.is-open .isl__arch { transition: opacity 520ms var(--isl-calm) calc(var(--isl-c0) + 50ms), transform 700ms var(--isl-out) calc(var(--isl-c0) + 50ms); }
.isl.is-open .isl__cols { transition: opacity 520ms var(--isl-calm) calc(var(--isl-c0) + 90ms), transform 700ms var(--isl-out) calc(var(--isl-c0) + 90ms); }

/* ---------- tablets and phones: a full-screen sheet that rises out of the bar ----------
   The sheet moves by transform, not by clip-path: Chromium declined to composite the full-screen clip wipe here
   (trace: compositeFailed 8192, unsupported clip-path; the sheet froze through a 300 ms main-thread stall) and Safari
   never composites clip-path, so on the phones this site is for a transform is the only wipe that cannot stutter. The
   sheet is 28 px taller than the screen: its rounded leading edge rises from just above the bar and ends above the top
   edge, so the open sheet is full-bleed. It is transparent until it starts to rise, and fades out as it lands. */
/* Round 6 (5 Nov 2026, Ali: "check why the island menu feels shifty and stutters on mobile interface while scrolling up
   and down, make sure its movement is built properly and firmly locked and intentional"). Three causes, three fixes:
   1. The island hung from a full-screen frame (inset: 0). A phone's toolbar sliding away resizes that frame only once
      the slide ends, so the bar sat still and then jumped to the new bottom on every scroll. Anchored to the bottom edge
      (top: auto) with the screen's live height, the browser carries the island with the toolbar instead.
   2. The glass's backdrop blur re-read and re-blurred the page beneath it on every scrolling frame. On phones the glass
      is near-opaque instead, with no blur (desktop keeps it).
   3. iOS changes the bottom safe-area inset as its toolbar collapses; the bar's offset now eases there instead of
      snapping. */
@media (max-width: 960px) {
  .isl { --bar-h: 60px; --isl-b: calc(10px + env(safe-area-inset-bottom, 0px));
    --isl-c0: 360ms;
    top: auto; height: 100vh; height: 100dvh; }
  .isl__panel { inset: 0; width: auto; height: auto; margin: 0; clip-path: none; transition: none; pointer-events: none; }
  .isl.is-open .isl__panel { clip-path: none; transition: none; }
  .isl__bar { bottom: var(--isl-b); pointer-events: auto; }
  .isl__bar, .isl__glass { transition: bottom 320ms var(--isl-calm); }
  .isl__glass { -webkit-backdrop-filter: none; backdrop-filter: none; background: rgb(250 251 249 / 0.96); }
  .isl__sheet { inset: -28px 0 0; pointer-events: none;
    transform: translateY(calc(100% - var(--bar-h) - var(--isl-b) - 6px)); transition: transform 470ms var(--isl-io) 110ms; }
  .isl.is-open .isl__sheet { transform: none; pointer-events: auto; transition: transform 600ms cubic-bezier(0.3, 0.6, 0.15, 1); }
  .isl__ground { border-radius: 28px 28px 0 0; box-shadow: none; }
  /* the sheet scrolls itself, above the bar; the page behind never moves */
  .isl__mega { inset: 28px 0 calc(var(--bar-h) + var(--isl-b) + 10px); display: block; overflow-x: clip; overflow-y: auto; overscroll-behavior: contain;
    padding: max(30px, calc(env(safe-area-inset-top) + 20px)) max(24px, calc(env(safe-area-inset-right) + 16px)) 30px max(24px, calc(env(safe-area-inset-left) + 16px));
    box-shadow: inset 0 -1px 0 var(--isl-hair); scrollbar-width: thin; scrollbar-color: rgb(22 34 48 / 0.22) transparent; -webkit-overflow-scrolling: touch; }
  .isl__top { margin-bottom: 12px; }
  .isl__big { gap: 0; }
  .isl__big a { align-items: center; gap: 12px; min-height: 54px; padding: 4px 0; font-size: clamp(31px, 8.6vw, 42px); line-height: 1.1; }
  .isl__big .n { min-width: 2.9em; font-size: 12px; }
  /* the arch, re-composed: the drawing beside its caption, showing the specimen of the section you opened from */
  .isl__arch { grid-template-columns: 116px minmax(0, 1fr); align-items: center; justify-items: start; gap: 20px; text-align: left;
    margin-top: 24px; padding-top: 24px; border-top: 1px solid var(--isl-hair); }
  .isl__arch-frame { width: 116px; }
  .isl__arch figcaption { max-width: 30ch; }
  .isl__arch span { min-height: 0; }
  .isl__cols { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0; margin-top: 26px; }
  .isl__group { padding-top: 22px; border-top: 1px solid var(--isl-hair); }
  .isl__group + .isl__group { margin-top: 26px; }
  .isl__cols ul { gap: 0; margin-top: 12px; }
  .isl__group li { border-top: 1px solid var(--isl-hair); }
  .isl__cols .isl__group--svc a { display: grid; grid-template-columns: 72px minmax(0, 1fr); align-items: center; gap: 16px; width: 100%;
    min-height: 72px; padding: 9px 0; font-size: 16.5px; font-weight: 500; line-height: 1.3; }
  .isl__th { display: block; width: 72px; height: 54px; border-radius: 10px; background: var(--isl-frame); object-fit: cover;
    outline: 1px solid var(--isl-hair); outline-offset: -1px; }
  .isl__group--co ul { grid-template-columns: 1fr 1fr; column-gap: 20px; }
  .isl__cols .isl__group--co a { display: flex; align-items: center; width: 100%; min-height: 50px; padding: 0; font-size: 16.5px; }
  .isl__cols ul a:active { color: var(--isl-acc); }
  .isl__card { grid-template-columns: minmax(0, 1fr); gap: 8px; margin-top: 30px; padding: 20px 20px 18px; }
  .isl__tel { display: inline-flex; align-items: center; min-height: 44px; font-size: 26px; }
  .isl__where { justify-self: start; text-align: left; }
  .isl.is-open .isl__menu::after { opacity: 1; }      /* the close control reads as a control: a hairline ring */
}
@media (min-width: 600px) and (max-width: 960px) {
  /* tablets: the service rows take the full width in two columns; company and the card sit side by side below */
  .isl__cols { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); column-gap: 32px; }
  .isl__group--svc { grid-column: 1 / -1; }
  .isl__group--svc ul { grid-template-columns: 1fr 1fr; column-gap: 28px; }
  .isl__group--co ul { grid-template-columns: 1fr; }
  .isl__card { grid-column: auto; align-self: start; margin-top: 26px; }
}
@media (max-width: 476px) {
  .isl { --bar-w: calc(100% - 24px); }               /* 12 px each side; 452 px above this width */
}
@media (max-width: 520px) {
  .isl__bar { padding-left: 14px; gap: 6px; }
  .isl__say { display: none; }                         /* the button keeps its name (aria-label); the cross says the rest */
  .isl__menu { justify-content: center; width: 48px; height: 48px; padding: 0; }
}
/* round 3 (fix 1): the island steps aside while it would sit on the booking form's buttons (menu.js sets .is-away). It
   leaves faster than it comes back (loader.css carries the return); focus inside it brings it back, so a keyboard
   never lands on a bar that is off the screen */
.isl.is-away:not(.is-open):not(:focus-within) { translate: 0 calc(var(--bar-h) + var(--isl-b) + 40px);
  transition: transform 900ms var(--ease-out) 250ms, translate 340ms var(--isl-in); }
@media (prefers-reduced-motion: reduce) {
  .isl, .isl.is-away:not(.is-open):not(:focus-within) { transition: translate 1ms linear; }   /* steps aside without travel */
  /* no travel: the panel appears and leaves by a short fade in place; the shape snaps */
  .isl__panel, .isl.is-open .isl__panel { transition: clip-path 1ms linear 170ms; }
  .isl.is-open .isl__panel { transition-delay: 0ms; }
  .isl__sheet { transition: transform 1ms linear 170ms; }
  .isl.is-open .isl__sheet { transition: transform 1ms linear; }
  .isl__ground, .isl.is-open .isl__ground { transition: opacity 160ms linear; }
  .isl__top, .isl__arch, .isl__cols, .isl__big li,
  .isl.is-open .isl__top, .isl.is-open .isl__arch, .isl.is-open .isl__cols, .isl.is-open .isl__big li {
    transform: none; transition: opacity 160ms linear; }
  .isl__layer, .isl__layer.is-on { transform: none; transition: opacity 160ms linear; }
  .isl__burger i, .isl__say > span { transition-duration: 1ms; }
  .isl__big a, .isl__big a:hover { transform: none; }
}

/* ---- homepage/src/css/sections.css ---- */
/* ---------- shared section furniture ---------- */
.eyebrow { color: var(--spring-ink); margin: 0 0 18px; }
.h-xl + .lead { margin-top: 22px; }
.ph { display: block; min-height: 40px; border-radius: 8px;
  background: repeating-linear-gradient(45deg, rgb(22 34 48 / 0.05) 0 8px, transparent 8px 16px); }
picture { display: contents; }

/* the footer's wave edge: drawn in the footer's colour, hung over the edge above it. It is a 96 px band that paints
   both grounds itself; --cut is where the two meet. (The section edges inside the page left when the ground began
   to change as a whole, 2026-10-29.) */
.edge { position: absolute; left: 0; right: 0; z-index: 2; height: 96px; line-height: 0; pointer-events: none; }
.edge--top { top: calc(-1 * var(--cut, 48px)); }
.foot > .edge--top { --cut: 66px; }
/* round 4 (E7, Ali: "i like ... how the section edges are waves instead of flat lines. lets use this in some places"):
   the how and reviews bands rise on decor.py's water edges again (round 2 drew them; they left when the ground switch
   made sections see-through). A wave-topped section paints its own ground (data-self, ground.css), and its edge is
   see-through above the crest, so before the switch the wave rises in the band's colour and then the screen floods to
   meet it. The crest sits on the boundary; the strip's upper part shows whatever ground the page has. */
.how > .edge--top { --cut: 48px; }
.reviews > .edge--top { --cut: 51px; }
.how > .edge--top .dcr, .reviews > .edge--top .dcr { --dcr-up: transparent; --dcr-dn: var(--g-navy); }

/* ---------- 2 · hero: the drawn yard and the water under it ----------
   Every layer of the drawing shares one box, so the layers line up at any size. The drawing always spans the width;
   on a short screen the land box crops the top of the sky, never the install. --k pans the phone's crop. */
.hero { position: relative; isolation: isolate; overflow: clip; background: #CFE6EF; container-type: inline-size;
  display: flex; flex-direction: column;            /* stacked: words first, then the drawing, then the proof strip */
  --land-h: calc(100cqw * 0.6275); --dw: max(100cqw, var(--land-h) * 1.5936); --k: 0; }
.hero__copy { order: -1; }
/* stacked: the whole drawing shows, lifted so its empty sky runs under the words (the sky's top is the hero's own
   colour, so there is no seam). The crown's tip (y 132 of 1004) lands 24px under the buttons at every width. */
.hero__art { position: relative; margin-top: calc(var(--land-h) * -0.1315 - clamp(26px, 4vw, 44px) + 24px); }
.hero__land { position: relative; height: var(--land-h); overflow: clip; view-timeline: --land block; }
.hero__l, .hero__u { position: absolute; right: calc((100cqw - var(--dw)) * var(--k)); width: var(--dw); height: auto; max-width: none; }
.hero__l { bottom: 0; aspect-ratio: 1600 / 1004; }
.hero__u { top: 0; aspect-ratio: 1600 / 320; }
/* the cutaway starts four drawing units above the land box's foot, and paints over it, exactly as the scene was drawn.
   Round 4 (E6, Ali: "i dont like how the underground layer has so much height, lets reduce that"): 320 units deep
   (was 500), ending just under the well's screen, which still sits in the water-bearing limestone */
.hero__under { position: relative; z-index: 1; height: calc(var(--dw) * 0.2); margin-top: calc(var(--dw) * -0.0025); overflow: clip; }
.hero__palm { transform-origin: 59.375% 100%; }
.hero__copy { position: relative; z-index: 3; padding-top: clamp(104px, 13vw, 150px); padding-bottom: clamp(26px, 4vw, 44px); }
.hero__copy > * { max-width: 640px; }
.hero__eyebrow { color: #086A66; margin-bottom: 20px; }   /* spring-ink darkened a step: 4.97:1 on the sky, where #0A7672 gave 4.21 */
.hero__lead { margin-top: 22px; color: var(--ink-2); }
.hero__cta { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 30px; }
/* overlay: the words stand in the open sky left of the palm; they stop short of the crown (its left tip sits at 44.4%
   of the drawing's width) and above the treeline */
@media (min-width: 1180px) and (min-height: 640px) {
  .hero { --land-h: max(min(clamp(600px, 100svh, 1440px), 100cqw * 0.6275), 100cqw * 0.55); }   /* never crop more sky than the crown can spare (its top sits at y 132 of 1004) */
  .hero__copy { position: absolute; inset: 0 0 auto; padding-top: clamp(104px, 15svh, 170px); padding-bottom: 0; }
  .hero__art { margin-top: 0; }
  .hero__copy > * { max-width: min(560px, 42.4cqw - 34px - max(var(--gut), (100cqw - var(--maxw)) / 2)); }
  .hero__lead { max-width: min(500px, 36cqw); }
  .hero .h-hero { font-size: clamp(48px, min(6vw, 9.6svh), 92px); }
}
@media (max-width: 599px) {
  .hero { --land-h: 112cqw; --k: 0.2; }
  /* (round 3 fix 6 cut the phone's cutaway at y 320 of 500, just under the well's screen; since round 4's E6 the whole
     band is that depth everywhere) */
}
/* the proof strip: on paper, under the cutaway, full bleed. Round 3 (Ali, E14: "centered properly in middle of the
   page. and not be so widely spread out or left aligned"): one compact group in the middle of the page, each figure
   centred over its label, hairlines between them, the same air on both sides of every hairline */
.hero__stats { position: relative; z-index: 3; display: flex; justify-content: center; align-items: stretch; margin: 0 auto;
  padding: clamp(34px, 3.8vw, 56px) 0; background: #FFFFFF; box-shadow: 0 0 0 100vmax #FFFFFF; clip-path: inset(0 -100vmax); }
.hero__stats > div { flex: 0 1 auto; min-width: 0; display: flex; flex-direction: column-reverse; justify-content: flex-end; align-items: center;
  gap: 10px; padding-inline: clamp(18px, 4.4vw, 76px); text-align: center; }
.hero__stats > div + div { border-left: 1px solid var(--hair); }
.hero__stats dt { font: 500 12px/1.35 var(--font-mono); letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-2); }
.hero__stats dd { margin: 0; display: flex; align-items: center; justify-content: center; gap: 8px; font: 800 clamp(34px, 3.6vw, 56px)/1 var(--font-display); letter-spacing: -0.035em; }
.hero__stats .star { flex: none; width: 0.5em; height: 0.5em; fill: #E0A32E; }   /* flex: none, or a narrow column squeezes it to a speck */

/* ---------- 3 · film: an inset frame that grows to full bleed as it comes in (Bay Balcony's intro) ---------- */
.film { position: relative; padding: 0 0 clamp(20px, 2vw, 32px); view-timeline: --film block; }
.film__frame { position: relative; aspect-ratio: 2.4 / 1; min-height: 300px; max-height: 88svh; width: 100%; overflow: hidden;
  background: #9DB9C4; border-radius: 26px; transform: scale(0.88); }
@supports (animation-timeline: view()) {
  .film__frame { animation: film-grow linear both; animation-timeline: --film; animation-range: entry 0% cover 48%; }
}
@keyframes film-grow { from { transform: scale(0.86); border-radius: 26px; } to { transform: scale(1); border-radius: 0; } }
.film__video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* round 4 (E16, Ali: "the smooth scroll lags and shivers especially near the hero"): a film that a scroll animation
   scales or moves must never become a hardware video overlay. Windows promoted the suburbs film to a DirectComposition
   overlay as it finished growing and as the island stopped covering it, and set up the video processor three times in a
   row (347 ms of the GPU's main thread, frames of 117 to 130 ms) at about 835 and 1,500 px; with no island over it, 15
   to 16 such stalls. The near-identity filter keeps both films on the ordinary compositing path: 0 stalls in two warm
   runs, the island removed included (checkpoint/round4/e16/ablate5*) */
.film__video, .vista__video { filter: saturate(1.001); }
.no-js .film__frame { background: #9DB9C4 url("/wp-content/themes/clearquest/assets/media/suburbs-poster-1280.webp") center / cover; }   /* the script picks the poster by screen size; without it, this one */
/* the caption (round 3, E15, Ali: "larger ... more visible on the video ... an intentional spot where it can indicate
   that sense of familiarity"): a map's label laid on the aerial view, a pin and the words on a solid light card in the
   frame's lower left, the twin of the Pause film pill in the lower right; read at 14:1 on any frame.
   round 4 (E20, Ali: "too bold and stands out oddly"): a quiet label, one step up from the Pause pill rather than a
   heading: 500 weight at 16 to 20 px with a touch of tracking (was 700 at 19 to 30 px, -0.018em), a softer shadow */
.film__cap { position: absolute; left: var(--gut); bottom: clamp(16px, 2vw, 26px); display: inline-flex; align-items: center; gap: 9px;
  padding: 10px 18px 10px 12px; border-radius: 999px; background: rgb(255 255 255 / 0.94); color: var(--ink);
  font: 500 clamp(16px, 1.2vw, 20px)/1.2 var(--font-body); letter-spacing: 0.005em; text-transform: none;
  box-shadow: 0 10px 28px -18px rgb(10 26 34 / 0.45); }
.film__pin { flex: none; width: 0.95em; height: 1.15em; }
.film__pin path { fill: #0A7672; }
.film__pin circle { fill: #FFFFFF; }
/* round 4 (E22, Ali: "take out the pause film and pause water buttons. why do we even have them"): the "Pause film" and
   "Pause water" pills became a small icon on the same white disc. The control stays because moving content that starts
   on its own and runs past 5 s needs a pause (WCAG 2.2.2, level A); Ali picked the quiet form. With a mouse it wakes
   while the pointer moves over its film or the water and sleeps 2 s after (motion.js, .is-awake), and it shows on
   keyboard focus and while paused, so Play is always there to find; on touch screens it is a small disc that stays.
   The words stay for screen readers (.sr); the hidden one of the pair leaves the accessible name (visibility). */
.film__toggle { position: absolute; right: var(--gut); bottom: 14px; width: 40px; height: 40px; padding: 0; border: 0; border-radius: 50%;
  background: rgb(255 255 255 / 0.88); color: var(--ink); display: grid; place-items: center; cursor: pointer; }
.film__icon { width: 14px; height: 14px; fill: currentColor; }
.film__i-play { stroke: currentColor; stroke-width: 1.4; stroke-linejoin: round; }   /* the round join softens its corners */
.film__toggle .film__i-play, .film__toggle[data-paused="true"] .film__i-pause { display: none; }
.film__toggle[data-paused="true"] .film__i-play { display: inline; }
.film__toggle .film__play, .film__toggle[data-paused="true"] .film__pause { visibility: hidden; }
.film__toggle[data-paused="true"] .film__play { visibility: visible; }
@media (hover: hover) and (pointer: fine) {
  .film__toggle { opacity: 0; transition: opacity 220ms var(--ease-out); }
  .is-awake > .film__toggle, .film__toggle:hover, .film__toggle:focus-visible, .film__toggle[data-paused="true"] { opacity: 1; }
}
@media (hover: none), (pointer: coarse) {
  .film__toggle { width: 34px; height: 34px; opacity: 0.82; }
  .film__toggle::after { content: ''; position: absolute; inset: -5px; border-radius: 50%; }   /* a 44 px target round the small disc */
  .film__toggle[data-paused="true"] { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .film__toggle { transition: none; } }

/* ---------- 13 · islands: a breath of open water, full bleed, the footage drifting a little slower than the page ----------
   The frame keeps the footage's own 2.4 : 1 (4 : 5 on a phone, its portrait cut); the video stands 14% taller than the
   frame so the drift never shows an edge. Without scroll timelines it simply sits still. */
.vista { position: relative; view-timeline: --vista block; }
.vista__frame { position: relative; margin: 0; height: clamp(360px, calc(100vw / 2.4), 86svh); overflow: hidden; background: #3F6E86; }
.vista__video { position: absolute; left: 0; top: -7%; width: 100%; height: 114%; object-fit: cover; }
@supports (animation-timeline: view()) {
  .vista__video { animation: vista-drift linear both; animation-timeline: --vista; animation-range: cover 0% cover 100%; }
}
@keyframes vista-drift { from { transform: translateY(-5%); } to { transform: translateY(5%); } }
.no-js .vista__frame { background: #3F6E86 url("/wp-content/themes/clearquest/assets/media/islands-poster-1280.webp") center / cover; }
@media (max-width: 599px) { .vista__frame { height: auto; aspect-ratio: 4 / 5; max-height: 86svh; } }

/* ---------- 15 · the spring: life comes from water (round 4, E8; it was the clouds film) ----------
   A tall section whose water stays on screen (sticky) while the words arrive in two beats, set by motion.js from how
   far the section has scrolled; then it lets go into the booking form. The Three.js draft's Florida spring paints
   behind the words (js/water.js): its picture first, the live scene over it once its shaders are ready. The camera
   keeps the shore just past the words (just under them on a tall screen), so they sit on the sunlit sand bank in navy
   ink, and "& clearer water." takes the spring's own teal. Reduced motion: no hold, every word shown, the picture as a
   still. */
.sky { position: relative; height: 250svh; }
.sky__pin { position: sticky; top: 0; height: 100svh; overflow: clip; display: grid; align-items: center; }
.sky__water { position: absolute; inset: 0; background: #D9D1BD; }   /* the sand, until the picture lands */
.sky__water picture, .sky__poster, .sky__canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.sky__poster { object-fit: cover; }
.sky__canvas { opacity: 0; transition: opacity 600ms var(--ease-calm); touch-action: pan-y; }   /* E19: live off screen now; 600 ms (was 900) for a late arrival */
.sky__canvas.is-live { opacity: 1; }
.sky__water .film__toggle { z-index: 2; bottom: max(18px, env(safe-area-inset-bottom)); }
.sky__water .film__toggle[hidden] { display: none; }
.sky__words { position: relative; z-index: 1; color: #162230; padding-block: 96px; }
/* round 4 (E23, Ali: "Clear answers, clear pricing & clearer water."): three phrases, each whole on its own line, "Clear
   answers, / clear pricing" in the first beat and "& clearer water." in the second. Set a fifth smaller than "Life comes
   from water" (7.4vw), since its longest line is 1.39 times as long, so the block stays about the old width and the
   shore stays just past the words; a phone sizes it so "& clearer water." fits the screen */
.sky__h { font: 800 clamp(46px, 5.9vw, 108px)/0.97 var(--font-display); letter-spacing: -0.04em; max-width: 14ch; margin: 0; }
.sky__h .sky__l { display: block; }
.sky__p, .sky__l--2 { white-space: nowrap; }
.sky__l--2 { color: #075E5B; }   /* on the sand behind the glyphs: 4.55:1 on average (navy 9.63:1), measured from the frame */
/* the spring's top edge (E7 with E8): the page's ground (this section's live palette, so it fades with the veil) above
   a water line, its crest in the spring's teal, a thinner line drifting under it; it scrolls away as the water pins */
.sky__edge { position: absolute; left: 0; top: -1px; z-index: 2; width: 100%; height: 64px; pointer-events: none; overflow: visible; }
.sky__edge-fill { fill: var(--ground); }
html:not(.gm) .sky__edge-fill { fill: var(--g-paper); }   /* no switching (reduced motion, no script): the posts' own paper above */
.sky__edge-crest { fill: none; stroke: color-mix(in oklch, var(--spring) 55%, transparent); stroke-width: 1.4; vector-effect: non-scaling-stroke; }
.sky__edge-line { fill: none; stroke: color-mix(in oklch, var(--spring) 45%, transparent); stroke-width: 1; vector-effect: non-scaling-stroke;
  opacity: 0.55; animation: sky-edge-drift 9s var(--ease-in-out) infinite alternate; }
.sky.is-off .sky__edge-line { animation-play-state: paused; }
@keyframes sky-edge-drift { to { transform: translateX(-18px); } }
@media (max-width: 760px) { .sky__edge { height: 40px; } }
@media (prefers-reduced-motion: reduce) { .sky__edge-line { animation: none; } }
/* round 4 (E35, Ali: "make the 'book your free test' section background color #F1EBE0 and i also do not like the straight
   edge cutoff boundary between that section and the three.js clear water section above it. give it more style and
   natural flow"): the spring ends on a shore, not a cut. The booking's ground rises over the scene's last strip along a
   long lazy line with small cusps: foam on its water side, a band of wet sand under it, a lace of foam a little out, and
   two layers of swells that drift on the compositor, paused off screen (art: checkpoint/round4/e32/shore.py). It runs
   low at the right, under the pause button, at every width; it arrives as the water lets go. */
.sky__foot { position: absolute; left: 0; right: 0; bottom: -1px; z-index: 2; height: 112px; overflow: hidden; pointer-events: none; }
.sky__foot > svg { position: absolute; top: 0; left: -24px; width: calc(100% + 48px); max-width: none; height: 100%; }   /* base.css caps svgs at 100%: it left the water's straight foot showing 24 px short of the right edge */
.sky__foot-fill { fill: var(--ground); stroke: rgb(255 255 255 / 0.9); stroke-width: 3.2px; }   /* the stroke's inner half sits under the wet sand */
.sky__foot-wet { fill: color-mix(in srgb, var(--ground) 76%, #B39762); }
.sky__foot-lace { fill: none; stroke: rgb(255 255 255 / 0.55); stroke-width: 1.2px; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.sky__foot-swell path { fill: #FFFFFF; }
.sky__foot-swell--1 { opacity: 0.4; animation: shore-lap-1 7.5s var(--ease-in-out) infinite alternate; }
.sky__foot-swell--2 { opacity: 0.24; animation: shore-lap-2 10s var(--ease-in-out) infinite alternate; }
.sky.is-off .sky__foot-swell { animation-play-state: paused; }
@keyframes shore-lap-1 { to { transform: translate(-16px, 3px); } }
@keyframes shore-lap-2 { to { transform: translate(14px, 2px); } }
@media (max-width: 760px) { .sky__foot { height: 72px; } }
@media (prefers-reduced-motion: reduce) { .sky__foot-swell { animation: none; } }
/* purely the two lines and the water: no button here (E19: "lets not use this section as a cta"); round 4 (E1) took
   out the third line, "Let’s take care of it." */
.js .sky__l { opacity: 0; transform: translateY(26px); transition: opacity 900ms var(--ease-out), transform 1100ms var(--ease-out); }
.js .sky.is-1 .sky__l--1, .js .sky.is-2 .sky__l--2 { opacity: 1; transform: none; }
@media (max-width: 760px) {
  /* while the water is held, the island bar covers its foot: the pause button sits above the bar */
  .sky__water .film__toggle { bottom: calc(96px + env(safe-area-inset-bottom, 0px)); }
}
/* a tall screen: the camera lays the shore just under the words, so they sit near the top with the water below */
@media (max-aspect-ratio: 9/10) {
  .sky__pin { align-items: start; }
  .sky__words { padding-block: max(88px, 14svh) 0; }
}
@media (max-width: 599px) {
  .sky { height: 230svh; }
  .sky__h { font-size: clamp(32px, 11.4vw, 50px); }
}
@media (prefers-reduced-motion: reduce) {
  .sky { height: auto; }
  .sky__pin { position: relative; min-height: 100svh; height: auto; }
  .js .sky__l { opacity: 1; transform: none; transition: none; }
}

/* ---------- 4 · story ---------- */
.story__grid { display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr); gap: clamp(32px, 6vw, 104px); align-items: center; }
.story__copy > p:not(.lead):not(.eyebrow):not(.story__fb) { margin-top: 18px; color: var(--ink-2); max-width: 60ch; }
.story__facts { list-style: none; margin: 36px 0 0; padding: 20px 0 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); border-top: 1px solid var(--hair); }
.story__facts li { display: grid; align-content: start; gap: 6px; padding-right: 16px; }
.story__facts li + li { padding-left: 18px; border-left: 1px solid var(--hair); }
.story__facts b { font: 800 26px/1 var(--font-display); letter-spacing: -0.02em; }
.story__facts span { font-size: 15px; line-height: 1.45; color: var(--ink-2); }
.story__fb { margin-top: 26px; font-size: 15.5px; color: var(--ink-2); }
.story__fb a { color: var(--spring-ink); font-weight: 600; }
.story__photo { margin: 0; }
.story__img { display: block; width: 100%; aspect-ratio: 4 / 5; object-fit: cover; border-radius: var(--radius-lg); background: var(--stone); }
/* round 4 (E21, Ali: "make zack and abby's photo frame a bit smaller and maybe add some complimenting decoration around
   it"; picked A of three rendered options): the photo at 84% of its column, centred, and behind its lower left corner
   the hairline rings of a spring in the brand's teal, one ring spreading out now and then (7 s; paused off screen, still
   under reduced motion). Hairline only, no fill, clear of the words and the caption */
.story__photo { position: relative; width: 84%; justify-self: center; isolation: isolate; }
.story__rings { position: absolute; z-index: -1; left: -26%; bottom: 5%; width: 74%; aspect-ratio: 1; pointer-events: none; }
.story__rings svg { display: block; width: 100%; height: 100%; overflow: visible; }
.story__rings circle { fill: none; stroke: #0A7672; stroke-width: 1px; vector-effect: non-scaling-stroke; }
.story__ring { position: absolute; inset: 0; border: 1px solid rgb(10 118 114 / 0.5); border-radius: 50%; opacity: 0; transform: scale(0.3); }
@media (prefers-reduced-motion: no-preference) {
  .story__ring { animation: story-ripple 7s cubic-bezier(0.16, 1, 0.3, 1) 1.5s infinite; }
  .story.is-off .story__ring { animation-play-state: paused; }
}
@keyframes story-ripple { 0% { opacity: 0; transform: scale(0.3); } 10% { opacity: 0.55; } 55%, 100% { opacity: 0; transform: scale(1.03); } }
.story__photo figcaption { margin-top: 12px; font-size: 14.5px; color: var(--ink-2); }
/* round 5 (E39, Saif: "Need small or minimized Facebook content under them. Zach is trying to make more informational
   content too"; Ali: "a beautiful widget that scrolls through their facebook content"): the latest posts from their
   Facebook page under the photo, small white cards on the page's own rail (widgets.js: swipe, drag and arrows, and one
   card every 7 s while it is on screen until the reader takes over; a Pause button; still under reduced motion).
   tools/make_fb.py writes the cards from the page's public feed. */
/* E48 (Ali: "expand the facebook posts widget to show more posts"): the rail leaves the photo's column for the full width
   under the story, four cards side by side on a wide screen, three on a tablet, one and a peek on a phone */
.story__fb-rail { margin-top: clamp(48px, 6vw, 88px); }
.fbr { min-width: 0; }
.fbr__t { margin: 0 0 16px; font-size: 15.5px; font-weight: 600; line-height: 1.3; }
.fbr__t a { display: inline-flex; align-items: center; gap: 10px; color: var(--ink); text-decoration: none; }
.fbr__t a span { text-decoration: underline; text-decoration-color: var(--hair-2); text-decoration-thickness: 1px; text-underline-offset: 0.28em; }
@media (hover: hover) and (pointer: fine) { .fbr__t a:hover span { text-decoration-color: currentColor; } }
.fbr__mark { flex: none; width: 24px; height: 24px; }
.fbr__track { list-style: none; margin: 0; padding: 2px; display: flex; gap: 12px; overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory; scroll-padding-inline: 2px; scrollbar-width: none; }
.fbr__track::-webkit-scrollbar { display: none; }
/* E49 (Ali: "dont like the fades on the sides of the cards"): no edge fades on this rail, and cards that fit the row
   exactly (one on a phone, three on a tablet, four wide), so no card is ever shown in part; the arrows and the glide
   say there are more */
.fbr__item { flex: 0 0 100%; display: flex; scroll-snap-align: start; }
@media (min-width: 700px) { .fbr__item { flex-basis: calc((100% - 2 * 12px) / 3); } }
@media (min-width: 1100px) { .fbr__item { flex-basis: calc((100% - 3 * 12px) / 4); } }
/* the page's shared rail rules (widgets.css, loaded later) set an 18 px gap and the edge fades; this rail keeps its own
   12 px, which its card widths count on (measured: at 18 px the fourth card ran 16 px past the row), and no fades */
.story .fbr [data-rail-track].fbr__track { gap: 12px; -webkit-mask-image: none; mask-image: none; }
.fbr__card { flex: 1; display: grid; grid-template-columns: 88px minmax(0, 1fr); column-gap: 16px; align-items: start; padding: 14px;
  border-radius: 16px; background: #FFFFFF; box-shadow: inset 0 0 0 1px var(--hair); color: var(--ink); text-decoration: none;
  transition: box-shadow var(--dur-quick) var(--ease-calm); }
@media (hover: hover) and (pointer: fine) { .fbr__card:hover { box-shadow: inset 0 0 0 1px var(--hair-3), 0 12px 26px -18px rgb(22 34 48 / 0.4); } }
.fbr__card:active { box-shadow: inset 0 0 0 1px var(--hair-3); }
.fbr__pic { position: relative; width: 88px; height: 88px; border-radius: 10px; overflow: hidden; background: var(--stone); }
.fbr__pic img { display: block; width: 100%; height: 100%; object-fit: cover; }
.fbr__badge { position: absolute; left: 6px; bottom: 6px; display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px 2px 6px;
  border-radius: 999px; background: rgb(22 34 48 / 0.82); color: #FFFFFF; font: 600 12px/1.4 var(--font-body); font-variant-numeric: tabular-nums; }
.fbr__badge svg { width: 10px; height: 10px; }
.fbr__body { display: grid; gap: 8px; min-width: 0; padding-top: 2px; }
.fbr__date { font-size: 13px; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.fbr__text { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; line-clamp: 3; overflow: hidden;
  font-size: 14.5px; line-height: 1.5; overflow-wrap: anywhere; }
.fbr .rail__nav { margin-top: 16px; gap: 8px; }
.fbr .rail__nav button { width: 44px; height: 44px; }

/* ---------- 5 · who we serve ---------- */
.serve__head { max-width: 900px; }
.serve__cards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(20px, 2.6vw, 40px); margin-top: clamp(40px, 5vw, 64px); }
/* each card opens on its own drawn home (round 3, E16, art/serve_cards.py): the city lot with its meter at the curb,
   the well lot with its casing cut away to the limestone, the new build with its garage loop. The hairline moves under
   the drawing. (Round 4, E26: the city or well switch that lit one card's hairline came out with the cutaway.) */
.serve__art { aspect-ratio: 480 / 330; border-radius: 16px; overflow: hidden; background: #DCEBF1; }
.serve__art img { display: block; width: 100%; height: 100%; }
.serve__card h3 { margin-top: 22px; padding-top: 20px; border-top: 1px solid var(--hair-2);   /* round 4 (E33): the tone's hairline, so it follows the section's ground */
  transition: color var(--dur-std) var(--ease-calm), border-color var(--dur-std) var(--ease-calm); }
.serve__card p { margin-top: 12px; color: var(--ink-2); font-size: 17px; }
@media (min-width: 961px) {   /* three across: drawing, title and words share rows, so a two-line title never drops its neighbours' words */
  .serve__cards { grid-template-rows: auto auto 1fr; row-gap: 0; }
  .serve__card { display: grid; grid-row: span 3; grid-template-rows: subgrid; }
}
@media (min-width: 600px) and (max-width: 960px) {   /* one card per row: the drawing beside its words, not a tall stack */
  .serve__card { display: grid; grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr); grid-template-rows: auto 1fr; column-gap: clamp(20px, 3.4vw, 32px); }
  .serve__art { grid-row: 1 / span 2; align-self: start; }
  .serve__card h3 { margin-top: 0; }
}
/* round 4 (E37, Ali: "more space between each of these cards and a little bit larger headings and also center aligned
   please", over the stacked cards on a narrow screen): the titles a step up at every width (22 to 28 px was .h-m's,
   now 25 to 31); the words centred wherever a card is one column (three across, and stacked under 600 px), and kept
   to their edge where they sit beside the drawing (600 to 960 px), as two-column heads keep theirs; cards that follow
   one another down the page stand 52 to 72 px apart (the shared 20 to 40 px gap before) */
.serve__card h3 { font-size: clamp(25px, 2.15vw, 31px); }
@media (min-width: 961px), (max-width: 599px) { .serve__card { text-align: center; } }
@media (max-width: 960px) { .serve__cards { row-gap: clamp(52px, 9vw, 72px); } }

/* ---------- 6 · systems ---------- */
.sys__head { display: flex; justify-content: space-between; align-items: end; gap: 28px 40px; flex-wrap: wrap; }
.sys__head > div { max-width: 800px; }
.sys__body { display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(0, 0.9fr); gap: clamp(28px, 4.4vw, 72px); margin-top: clamp(40px, 5vw, 64px); align-items: start; }
.sys__list { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--hair); }
.sys__list li { border-bottom: 1px solid var(--hair); }
.sys__list a { display: grid; grid-template-columns: 96px minmax(0, 0.95fr) minmax(0, 1.05fr); gap: 22px; align-items: center; padding: 14px 0;
  color: var(--ink); text-decoration: none; }
.sys__art { display: block; width: 96px; aspect-ratio: 4 / 3; }
.sys__art img { width: 100%; height: 100%; }
.sys__list b { font: 700 clamp(19px, 1.6vw, 24px)/1.15 var(--font-display); letter-spacing: -0.018em; transition: color var(--dur-quick) var(--ease-calm); }
.sys__list a > span:last-child { font-size: 15.5px; line-height: 1.5; color: var(--ink-2); }
/* hover answers in colour and in the photo beside the list; the row stays planted (round 3, after E10's "planted
   firmly": it slid 6 px) */
@media (hover: hover) and (pointer: fine) { .sys__list a:hover b { color: var(--spring-ink); } }
.sys__list a.is-on b { color: var(--spring-ink); }
/* sticky beside the list; never taller than the screen under the header line and over the island bar (round 3, an E8
   sibling: at 1536 x 752 its foot ran 20 px off the screen and under the bar) */
.sys__peek { position: sticky; top: 112px; margin: 0; aspect-ratio: 4 / 5; max-height: calc(100svh - 216px); border-radius: var(--radius-lg);
  overflow: hidden; background: var(--stone); }
.sys__peek-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0; transform: scale(1.03);
  transition: opacity 520ms var(--ease-calm), transform 900ms var(--ease-out); }
.sys__peek-img.is-on { opacity: 1; transform: none; }
/* every row shows a real photo (round 3, E24); the lab row's is stock, and says so in a small credit on the photo */
.sys__peek-stock .sys__peek-photo { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.sys__peek-credit { position: absolute; left: 14px; bottom: 14px; padding: 5px 10px; border-radius: 999px;
  background: rgb(255 255 255 / 0.9); color: #44505D; font: 500 12px/1.2 var(--font-mono); letter-spacing: 0.04em; }   /* 12 px floor */

/* ---------- 7 · how it works ---------- */
.how__head { max-width: 900px; }
.steps { --sg: clamp(22px, 2.8vw, 44px); position: relative; list-style: none; margin: clamp(52px, 6vw, 84px) 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sg); }
/* the line joins the four photos and ends inside the last one (E20: "this line should not extent like that into
   nothingness"): from photo 1's centre (60 px) to photo 4's, three column steps on, so 3/4 of (width + gap) */
.steps__line { position: absolute; left: 60px; top: 40px; width: calc((100% + var(--sg)) * 0.75); height: 40px; z-index: 0; overflow: visible; }
.steps__line path { fill: none; stroke: var(--spring-ink); stroke-width: 2; stroke-linecap: round; vector-effect: non-scaling-stroke; opacity: 0.8; }
.js .steps__line { clip-path: inset(0 100% 0 0); transition: clip-path 1800ms var(--ease-calm) 240ms; }
.js .steps.is-in .steps__line { clip-path: inset(0 0 0 0); }
.step { position: relative; z-index: 1; }
.step__photo { width: 120px; height: 120px; margin: 0 0 24px; border-radius: 50%; overflow: hidden; background: #23384D;
  box-shadow: 0 0 0 7px var(--ground), 0 0 0 8px var(--hair-2); }
.step__img { width: 100%; height: 100%; object-fit: cover; }
.step .label { color: var(--spring-ink); margin-bottom: 10px; }
.step p:last-child { color: var(--ink-2); }
.step p:last-child { margin-top: 10px; font-size: 16px; }
.js .step { opacity: 0; transform: translateY(18px); transition: opacity 700ms var(--ease-out), transform 820ms var(--ease-out); }
.js .steps.is-in .step { opacity: 1; transform: none; }
.js .steps.is-in .step:nth-child(2) { transition-delay: 160ms; }
.js .steps.is-in .step:nth-child(3) { transition-delay: 320ms; }
.js .steps.is-in .step:nth-child(4) { transition-delay: 480ms; }
/* round 4 (E10: "these circles are leaning left on the page, i want them looking centered overall"): from 761 px, where
   the head above is centred (E17), each step centres on its column (circle, label, title and text), so the row shares
   the head's axis; the line still runs from the first circle's centre to the last one's, now half a column in */
@media (min-width: 761px) {
  .step { text-align: center; }
  .step__photo { margin-inline: auto; }
  .step :is(h3, p) { text-wrap: balance; }
  .steps__line { left: calc((100% - 3 * var(--sg)) / 8); }
}
.promises { list-style: none; margin: clamp(52px, 6vw, 84px) 0 0; padding: 28px 0 0; border-top: 1px solid var(--hair);
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px 48px; }
.promises li { position: relative; padding-left: 28px; color: var(--ink); font-size: 17px; }
.promises li::before { content: ''; position: absolute; left: 0; top: 0.36em; width: 12px; height: 15px; background: var(--spring-ink);
  -webkit-mask: var(--drop-mask) center / contain no-repeat; mask: var(--drop-mask) center / contain no-repeat; }
:root { --drop-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 15'%3E%3Cpath d='M6 0C4.5 2.6 1 6.3 1 9.4a5 5 0 0010 0C11 6.3 7.5 2.6 6 0z'/%3E%3C/svg%3E"); }

/* ---------- 8 · what the test reads ---------- */
.reads__grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(32px, 5vw, 88px); align-items: start; }
.reads__proof { margin: 26px 0 0; display: grid; grid-template-columns: 148px minmax(0, 1fr); gap: 18px; align-items: center; }
.reads__meter { width: 148px; aspect-ratio: 4 / 3; object-fit: cover; border-radius: 12px; }
.reads__proof figcaption { font-size: 14.5px; color: var(--ink-2); }

/* ---------- 9 · installs ---------- */
.installs__head { max-width: 900px; }

/* ---------- 10 · news ---------- */
.news__grid { display: grid; grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr); gap: 32px clamp(32px, 5vw, 88px); align-items: start; }
.news__body p:not(.lead) { margin-top: 16px; color: var(--ink-2); }
.news__body .btn { margin-top: 28px; }
/* round 5 (E42): Tampa Bay 28's mark under the heading, the station that ran the story. E52 (Ali: "use a dark themed
   tampa bay 28 logo for contrast and also make it larger so it sits beneath that heading width symmitrically"): heading
   and plate share one shrink-wrapped box. The heading's two lines are set (.ln), so the box is as wide as its longer
   line, and the plate, whose own content counts for nothing in that width (contain: inline-size), stretches to it:
   plate and heading share both edges at every width. The plate is the page's navy, a ground chosen for the station's
   dark-ground mark (tools/make_press.py cuts the abc letters in this same #162230), with a light hairline that keeps
   its edge when the ground turns navy for the reviews. Padding in percent keeps the mark's margins in proportion.
   Two classes each, since the press row's .press__card and .press__logo come later in this file (the round-5 card's
   own padding lost that tie and never applied). */
.news__mast { display: grid; width: fit-content; max-width: 100%; }
.news__src { display: block; contain: inline-size; margin-top: clamp(24px, 3vw, 40px); border-radius: 16px; color: var(--ink); text-decoration: none; }
.press__card.news__card { height: auto; min-height: 0; padding: 7% 10%; background: #162230;
  --hair: rgb(244 241 234 / 0.14); --hair-3: rgb(244 241 234 / 0.34); }
.press__logo.news__logo { width: 100%; height: auto; aspect-ratio: 264.583 / 88.107; }
@media (hover: hover) and (pointer: fine) {
  .news__src:hover .press__card { box-shadow: inset 0 0 0 1px var(--hair-3), 0 12px 26px -18px rgb(22 34 48 / 0.4); }
}
.news__src:active .press__card { box-shadow: inset 0 0 0 1px var(--hair-3); }
.press { grid-column: 1 / -1; margin-top: clamp(12px, 2vw, 28px); padding-top: 28px; border-top: 1px solid var(--hair-2);
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 18px; }
.press .label { color: var(--ink-2); }
/* each publication's own mark in its own colours (tools/make_press.py, E22) on a white card of its own, so black type
   never lands on the navy the ground turns to for the reviews. Phones: 2 + 2 + 1, tablets: 3 + 2, both centred; wide: one
   row of five beside the label. Logo heights are set by eye so the five weigh the same. */
.press__row { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
.press__row li { grid-column: span 2; }
.press__row li:last-child { grid-column: 2 / span 2; }
.press__row a { display: block; height: 100%; border-radius: 16px; color: var(--ink); text-decoration: none; }
.press__card { display: grid; place-items: center; height: 100%; min-height: 72px; padding: 14px 16px; border-radius: 16px; background: #FFFFFF;
  box-shadow: inset 0 0 0 1px var(--hair); transition: box-shadow var(--dur-quick) var(--ease-calm); }
@media (hover: hover) and (pointer: fine) {
  .press__row a:hover .press__card { box-shadow: inset 0 0 0 1px var(--hair-3), 0 12px 26px -18px rgb(22 34 48 / 0.4); }
}
.press__logo { display: block; width: auto; max-width: 100%; object-fit: contain; }
.press__logo--ap { height: 36px; }
.press__logo--ein { height: 26px; }
.press__logo--wfla { height: 40px; }
.press__logo--bloomberg { height: 22px; }
/* no logo file yet: its name, set in two lines like a masthead */
.press__type { display: grid; justify-items: center; font: 800 17px/1.02 var(--font-display); letter-spacing: -0.015em; color: var(--ink); }
@media (min-width: 600px) {
  .press__row { grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 12px; }
  .press__row li:nth-child(4) { grid-column: 2 / span 2; }
  .press__row li:last-child { grid-column: span 2; }
  .press__card { min-height: 78px; padding: 14px 20px; }
  .press__logo--ein { height: 28px; }
  .press__logo--bloomberg { height: 23px; }
}
@media (min-width: 1024px) {
  .press__row { grid-template-columns: repeat(5, minmax(0, 1fr)); }
  .press__row li, .press__row li:nth-child(4), .press__row li:last-child { grid-column: auto; }
}
@media (min-width: 1200px) {
  .press { grid-template-columns: auto minmax(0, 1fr); align-items: center; column-gap: clamp(28px, 3vw, 44px); }
}
@media (min-width: 1440px) {
  .press__card { min-height: 88px; }
  .press__logo--ap { height: 40px; }
  .press__logo--ein { height: 32px; }
  .press__logo--wfla { height: 46px; }
  .press__logo--bloomberg { height: 26px; }
  .press__type { font-size: 19px; }
}

/* ---------- 11 · reviews ---------- */
.reviews { overflow-x: clip; }
.reviews__grid { display: grid; grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr); gap: clamp(32px, 5vw, 88px); align-items: start; }
.reviews__rating { display: flex; align-items: center; gap: 14px; margin-top: 28px; }
.reviews__rating b { font: 800 48px/1 var(--font-display); letter-spacing: -0.03em; color: var(--ink); }
.reviews__rating > span:last-child { display: grid; gap: 3px; font-size: 14px; color: var(--ink-2); }
.reviews__rating .stars { color: var(--star); letter-spacing: 0.1em; }
.g-mark { width: 30px; height: 30px; border-radius: 50%; background: conic-gradient(#EA4335 0 25%, #FBBC05 0 50%, #34A853 0 75%, #4285F4 0);
  -webkit-mask: radial-gradient(circle, transparent 7.5px, #000 8px); mask: radial-gradient(circle, transparent 7.5px, #000 8px); }
.reviews__google { display: inline-block; margin-top: 18px; color: var(--spring-ink); font-weight: 600; }

/* batch 33: "Google rating" links the Google place, as the live widget's rating did */
.reviews__place { color: inherit; text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px;
  text-decoration-color: var(--hair-3); }
@media (hover: hover) and (pointer: fine) { .reviews__place:hover { text-decoration-color: currentColor; } }
/* a page's own live line under the band's heading (static pages, batch 33: "Your community chooses us!") */
.reviews__line { margin-top: 16px; }

/* ---------- 12 · service area ---------- */
.area__grid { display: grid; grid-template-columns: minmax(0, 0.82fr) minmax(0, 1.18fr); gap: clamp(32px, 5vw, 80px); align-items: center; }
/* the towns as plain links in the copy; resting on one flies the map there (js/map.js), a click opens its page */
/* every town carries its own dot, so a wrapped line never starts with a separator */
.towns { list-style: none; margin: 26px 0 0; padding: 20px 0 0; border-top: 1px solid var(--hair);
  display: grid; grid-template-columns: repeat(3, max-content); gap: 10px 30px; font: 600 16.5px/1.5 var(--font-body); }
.towns li { display: flex; align-items: center; gap: 10px; white-space: nowrap; }
.towns li::before { content: ''; flex: none; width: 5px; height: 5px; border-radius: 50%; background: var(--spring-ink); }
@media (max-width: 520px) { .towns { grid-template-columns: repeat(2, max-content); } }
.towns a { color: var(--ink); text-decoration: underline; text-decoration-color: var(--hair); text-decoration-thickness: 1px; text-underline-offset: 0.28em;
  transition: color var(--dur-quick) var(--ease-calm), text-decoration-color var(--dur-quick) var(--ease-calm); }
.towns span { color: var(--ink-2); font-weight: 500; }
@media (hover: hover) and (pointer: fine) { .towns a:hover { color: var(--spring-ink); text-decoration-color: currentColor; } }
.area__cta { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 30px; }
.area__map { min-width: 0; }

/* ---------- 13 · questions ---------- */
.faq__grid { display: grid; grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr); gap: clamp(32px, 5vw, 88px); align-items: start; }
.faq__intro { position: sticky; top: 112px; }
.faq__intro .lead a { color: var(--ink); }
/* the carafe and tumbler on a windowsill (round 3, E18, art/faq_art.py: 760:400, every edge drawn, no fade). The
   intro sticks under the header, so the art takes the column's width but never so much that the column outgrows the
   screen, and never so far that it reaches under the island bar: it either ends left of the bar (whose left edge is
   50vw - 226 px; 248 keeps a margin) or stays above it. --t estimates the words' height above the art (342 px at 1024
   wide, 418 px from 1536; measured), 176 px is the header line plus margins, 242 px the same plus the bar's band; the
   art is 1.9:1. Under 726 px tall there is no room: the words stand alone. */
.faq__art { --t: min(418px, 190px + 15vw); margin-top: 40px;
  width: min(100%, calc((100svh - 176px - var(--t)) * 1.9),
    max(calc(50vw - 248px - max(var(--gut), (100vw - var(--maxw)) / 2)), calc((100svh - 242px - var(--t)) * 1.9))); }
@media (max-height: 725px) { .faq__art { display: none; } }
.faq__art > svg { width: 100%; height: auto; display: block; }   /* direct children only: a drawn partial (.dcr) sizes its own layers */
.faq__list { border-top: 1px solid var(--hair); }
.faq__item { border-bottom: 1px solid var(--hair); }
.faq__item summary { list-style: none; display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 20px; align-items: center; padding: 22px 0; cursor: pointer; }
.faq__item summary::-webkit-details-marker { display: none; }
.faq__q { font: 650 clamp(18px, 1.5vw, 22px)/1.32 var(--font-display); letter-spacing: -0.01em; color: var(--ink); }
.faq__icon { position: relative; flex: none; width: 38px; height: 38px; border-radius: 50%; border: 1px solid var(--hair);
  transition: border-color var(--dur-quick) var(--ease-calm); }
.faq__icon::before, .faq__icon::after { content: ''; position: absolute; left: 50%; top: 50%; width: 12px; height: 1.6px; border-radius: 2px;
  background: var(--spring-ink); transform: translate(-50%, -50%); transition: transform var(--dur-std) var(--ease-calm); }
.faq__icon::after { transform: translate(-50%, -50%) rotate(90deg); }
.faq__item[open] .faq__icon::after { transform: translate(-50%, -50%) rotate(0deg); }
/* with the script, the mark turns the moment the answer starts to move either way */
.js .faq__item:not(.is-open) .faq__icon::after { transform: translate(-50%, -50%) rotate(90deg); }
.js .faq__item.is-open .faq__icon::after { transform: translate(-50%, -50%) rotate(0deg); }
@media (hover: hover) and (pointer: fine) { .faq__item summary:hover .faq__icon { border-color: var(--spring-ink); } }
.faq__a p { color: var(--ink-2); padding: 0 0 26px; max-width: 64ch; font-size: 16.5px; line-height: 1.66; }

/* ---------- 14 · posts ---------- */
/* round 3 polish: no strata band under the cards (it read as a stray stripe between the posts and the night sky); the
   section keeps its full padding and the clouds film begins right after it, as the islands film does after the area */
.posts__head { display: flex; justify-content: space-between; align-items: end; gap: 28px 40px; flex-wrap: wrap; }
.posts__head > div { max-width: 780px; }
.posts__rail { position: relative; z-index: 1; margin-top: clamp(36px, 4vw, 56px); }
.post { flex: 0 0 clamp(272px, 24vw, 344px); scroll-snap-align: start; display: grid; align-content: start; gap: 12px; padding: 26px;
  border-radius: 18px; background: #fff; box-shadow: inset 0 0 0 1px var(--hair); }
.post__tag { color: var(--spring-ink); }
.post a { color: var(--ink); text-decoration: none; }
.post__t { font: 700 20px/1.26 var(--font-display); letter-spacing: -0.012em; transition: color var(--dur-quick) var(--ease-calm); }
@media (hover: hover) and (pointer: fine) { .post a:hover .post__t { color: var(--spring-ink); } }
.post p { font-size: 15px; line-height: 1.56; color: var(--ink-2); }

/* ---------- 15 · book ---------- */
.book { padding-bottom: 0; }
.book__grid { position: relative; z-index: 1; display: grid; grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr); gap: clamp(32px, 5vw, 80px); align-items: start; }
.book__drop { width: 76px; height: 96px; margin-top: 36px; }
.book__drop svg { width: 100%; height: 100%; overflow: visible; }
/* the other way in, beside the form (round 3: the column under the count stood empty): call, and the rating */
.book__help { display: grid; justify-items: start; gap: 8px; max-width: 340px; margin-top: clamp(36px, 4vw, 56px); padding-top: 22px; border-top: 1px solid var(--hair); }
.book__help .label { color: var(--ink-2); }
.book__tel { font: 800 clamp(24px, 2.2vw, 30px)/1.1 var(--font-display); letter-spacing: -0.02em; color: var(--ink); text-decoration: none; }
@media (hover: hover) and (pointer: fine) { .book__tel:hover { color: var(--spring-ink); } }
.book__rate { font-size: 15px; color: var(--ink-2); }
.book__rate .stars { color: var(--star); letter-spacing: 0.08em; }
@media (max-width: 960px) { .book__help { display: none; } }   /* phones and tablets: the form follows at once, and the island carries the call */
.book__step { margin-top: 12px; color: var(--ink-2); }
.book__ground { position: relative; margin-top: -80px; line-height: 0; --dcr-ground: var(--ground); }   /* the form card overlaps the bank's top 80 px; round 4 (E35): the bank's sand runs down into the live ground (stone), not the cream baked in as its fallback */
.book__ground > svg { width: 100%; height: auto; display: block; }   /* a descendant rule here squashed the bank to half height and left its eelgrass on the sand (round 3) */

/* ---------- 16 · footer: limestone, so the full-colour logo always sits on a light ground ---------- */
.foot { position: relative; background: #F1EBE0; color: var(--ink); padding: clamp(80px, 8vw, 120px) 0 0; }   /* round 4 (E7): stone follows the draft, #F3EFE7 > #F1EBE0 */
/* round 4 (E9): the closing spring under the legal row (round 3, E21) came out at Ali's ask, so the legal row is the
   page's last line again; the footer keeps room under it for the island (a 62 px bar resting 16 px off the foot), so
   the bar never sits over the links at the end of the page */
.foot { padding-bottom: calc(112px + env(safe-area-inset-bottom, 0px)); }
.foot .label { color: var(--ink-2); }
/* the giant wordmark: the logo's own lettering across the full measure, rising out of its baseline as it comes in */
.foot__wm { display: block; line-height: 0; color: var(--navy); }
.foot__word { display: block; width: 100%; height: auto; overflow: visible; }
.js .foot__wm[data-reveal] { opacity: 1; transform: none; }
.js .foot__wm[data-reveal] .foot__word { clip-path: inset(0 0 100% 0); transform: translateY(18%);
  transition: clip-path 1200ms var(--ease-out), transform 1200ms var(--ease-out); }
.js .foot__wm.is-in .foot__word { clip-path: inset(-2% 0 -2% 0); transform: none; }
@media (hover: hover) and (pointer: fine) {
  .foot__wm .foot__word path { transition: fill 600ms var(--ease-calm); }
  .foot__wm:hover .foot__word path { fill: var(--spring-ink); }
}
.foot__grid { display: grid; grid-template-columns: minmax(0, 1.5fr) repeat(3, minmax(0, 0.8fr)); gap: 44px clamp(24px, 3vw, 48px);
  margin-top: clamp(40px, 5vw, 72px); padding-top: clamp(32px, 3.6vw, 48px); border-top: 1px solid rgb(22 34 48 / 0.16); }
.foot__brand > p { max-width: 42ch; color: var(--ink-2); font-size: 16px; }
.foot__col ul { list-style: none; margin: 16px 0 0; padding: 0; display: grid; gap: 9px; }
.foot__col a { color: var(--ink); text-decoration: none; font-size: 15.5px; }
@media (hover: hover) and (pointer: fine) { .foot__col a:hover { text-decoration: underline; } }
.foot__reach p:not(.label) { margin-top: 12px; font-size: 15px; color: var(--ink-2); }
.foot__tel { display: inline-block; margin-top: 16px; font: 800 24px/1 var(--font-display); color: var(--ink); text-decoration: none; }
.news-form { margin-top: 32px; max-width: 470px; }
.news-form__t { margin-top: 10px; font: 700 20px/1.26 var(--font-display); color: var(--ink); }
.news-form__row { display: flex; gap: 8px; margin-top: 16px; }
.news-form input { flex: 1; min-width: 0; min-height: 52px; padding: 0 18px; border-radius: 999px; border: 1px solid rgb(22 34 48 / 0.22);
  background: #fff; font-size: 16px; transition: border-color var(--dur-quick) var(--ease-calm); }
.news-form input:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.news-form input[aria-invalid="true"] { border-color: #9E2F24; }
.news-form__say { margin-top: 10px; font-size: 14px; color: var(--ink-2); }
.news-form__say.is-error { color: #9E2F24; }
.news-form__say.is-ok { color: var(--spring-ink); }
/* subscribed: a small designed moment, the booking close in miniature (round 3, an E13 sibling): a drop lands beside
   the words as they rise in. Nothing is sent from the prototype; the WordPress build wires the list. */
.news-form__say.is-ok { display: flex; align-items: center; gap: 10px; animation: news-ok 520ms var(--ease-out) both; }
.news-form__say.is-ok::before { content: ''; flex: none; width: 13px; height: 17px; background: var(--spring-ink);
  -webkit-mask: var(--drop-mask) center / contain no-repeat; mask: var(--drop-mask) center / contain no-repeat;
  transform-origin: 50% 100%; animation: news-drop 820ms var(--ease-out) both; }
@keyframes news-ok { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes news-drop { 0% { opacity: 0; transform: translateY(-16px) scale(0.7); } 55% { opacity: 1; transform: translateY(0) scale(1.06, 0.86); }
  78% { transform: scale(0.97, 1.04); } 100% { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .news-form__say.is-ok, .news-form__say.is-ok::before { animation: none; } }
.foot__base { display: flex; flex-wrap: wrap; gap: 12px 28px; align-items: center; margin-top: 60px; padding-top: 22px;
  border-top: 1px solid rgb(22 34 48 / 0.14); font-size: 14px; color: var(--ink-2); }
.foot__base ul { list-style: none; margin: 0 0 0 auto; padding: 0; display: flex; flex-wrap: wrap; gap: 8px 20px; }
.foot__base a { color: var(--ink-2); }

/* ---------- reveals: one class from one observer; opacity and transform only ---------- */
.js [data-reveal] { opacity: 0; transform: translateY(16px); transition: opacity 800ms var(--ease-out), transform 900ms var(--ease-out); }
.js [data-reveal].is-in { opacity: 1; transform: none; }

/* ---------- round 4 (E25, Ali: "right now all the sections and elements and headers are just fixed on the page and
   are just there as we scroll. id like more movement so that the page scroll feels more responsive, at least on the
   first scroll experience ... maybe add some gorgeous parallax and movement wherever possible") ----------
   Entrances. A [data-rv] element waits, from the first paint, at the first keyframe of a paused animation (scripts on,
   motion allowed, not the lite tier); motion.js runs it (.is-in) as it arrives from below, in reading order, and drops
   it once it lands (.rv-done), so nothing animates at rest. Never a transition: while a colour switch runs, ground.css
   gives text holders transition: var(--sl) !important, which would cut the stagger and take over the rise. Entrances
   write opacity, translate and scale on leaves; drift writes translate on the containers around them, never on the
   same element. A heading marked data-rv="lines" rises line by line in its own slots (.rl), clipped with a margin so
   no ascender or descender is cut and nothing moves. Values from Ali's approved builds: Zariia Wraps (24 px, 900 and
   1300 ms, lines 70 ms apart) and Bay Balcony home-2 (28 px, 1000 and 1100 ms). */
:root { --rv-dur: 1000ms; --rv-rise: 1100ms; --rv-y: 22px; }
@media (max-width: 760px) { :root { --rv-y: 14px; } }
.rl { display: block; overflow: clip; overflow-clip-margin: 0.14em; }
.rl__i { display: block; }
@media (prefers-reduced-motion: no-preference) {
  html.js:not(.lite):not(.rv-off) [data-rv]:not(.rv-done) { animation: rv-up var(--rv-dur) var(--ease-out) var(--rv-d, 0ms) both paused; overflow-anchor: none; }
  html.js:not(.lite):not(.rv-off) [data-rv="photo"]:not(.rv-done) { animation-name: rv-photo; animation-duration: 1400ms; }
  html.js:not(.lite):not(.rv-off) [data-rv].is-in { animation-play-state: running; }
  html.js:not(.lite):not(.rv-off) [data-rv].rl-on { animation: none; }
  html.js:not(.lite):not(.rv-off) .rl__i { animation: rv-line var(--rv-rise) var(--ease-out) var(--rv-d, 0ms) both paused; overflow-anchor: none; }
  html.js:not(.lite):not(.rv-off) .is-in > .rl > .rl__i { animation-play-state: running; }
}
@keyframes rv-up { from { opacity: 0; translate: 0 var(--rv-y); } }
@keyframes rv-photo { from { opacity: 0; scale: 1.025; } }   /* a photo settles: 1.025 keeps it clear of its caption */
@keyframes rv-line { from { translate: 0 125%; } }
@media print { [data-rv], .rl__i { animation: none !important; } }

/* Drift, the planes (Zariia Wraps' approved recipe, 27 Oct: "The parallax feels right. Keep it as is."). A [data-drift]
   container moves against the page at speed k: photos and drawings deep (-0.05), words near (+0.03), at rest when
   centred in the window, the shift growing with the distance from the centre. Phones half, and words do not drift on
   phones. Under reduced motion, on the lite tier and without scroll timelines nothing drifts; an effect that is off is
   animation: none, never a zero speed (obs 1659). The hero's words, the one plane in the first view, ride the page's
   own scroll from the top and start at rest (obs 1660). The suburbs film's footage sinks inside its frame on the
   frame's named timeline (an anonymous view() inside a clipping frame never moves: obs 1287, 1656), over a 6% overhang. */
[data-drift] { --k: -0.05; --km: 1; }
[data-drift="near"] { --k: 0.03; }
[data-drift="soft"] { --k: -0.03; }   /* a gentle deep plane: the map as one picture (round 5, E41: the drawn map again; its marks never move inside it unless asked), card and step rows */
@keyframes plane { from { translate: 0 calc(var(--k) * var(--km) * (50vh + 50%)); } to { translate: 0 calc(var(--k) * var(--km) * -1 * (50vh + 50%)); } }
@keyframes hero-words { to { translate: 0 calc(-10vh * var(--hw, 1)); } }
@keyframes film-sink { from { translate: 0 -5%; } to { translate: 0 5%; } }
.film__video { inset: -6% 0 auto; height: 112%; }
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    html:not(.lite) [data-drift] { animation: plane linear both; animation-timeline: view(); animation-range: cover 0% cover 100%; overflow-anchor: none; }
    html:not(.lite) .hero__copy { animation: hero-words linear both; animation-timeline: scroll(root); animation-range: 0 100vh; overflow-anchor: none; }
    html:not(.lite) .film__video { animation: film-sink linear both; animation-timeline: --film; animation-range: entry 0% exit 100%; }
    /* the check-in's three characters (?motion=a|b|c): Ali kept b, the default, on 2 Nov ("Keep B"); a and c stay a
       query away until the deploy pass. a keeps the words at the page's speed; c drifts half again as far and lets the
       hero's words lift further and ease back (its third plane, the serve cutaway, came out in E26) */
    html[data-motion="c"] [data-drift] { --km: 1.5; }
    html[data-motion="c"] .hero__copy { --hw: 1.6; animation-name: hero-words-c; }
    html[data-motion="a"] [data-drift="near"], html[data-motion="a"] .hero__copy { animation: none; }
    /* phones last, so they win: half the drift, no word drift */
    @media (max-width: 820px) {
      html [data-drift] { --km: 0.5; }
      html[data-motion="c"] [data-drift] { --km: 0.75; }
      html:not(.lite) [data-drift="near"], html:not(.lite) .hero__copy { animation: none; }
    }
  }
}
@keyframes hero-words-c { to { translate: 0 calc(-10vh * var(--hw, 1)); opacity: 0.55; } }
@media (prefers-reduced-motion: reduce) { .film__video { inset: 0; height: 100%; } }
html.nodrift :is([data-drift], .hero__copy, .film__video) { animation: none !important; }   /* ?nodrift, probes only */

/* ---------- hero motion: parallax on the scroll timeline, the palm and clouds on their own clocks ---------- */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .hero__sky { animation: hero-sky linear both; animation-timeline: --land; animation-range: exit 0% exit 100%; }
    .hero__clouds { animation: hero-clouds linear both, cloud-drift 36s var(--ease-in-out) infinite alternate;
      animation-timeline: --land, auto; animation-range: exit 0% exit 100%, normal; }
  }
  .hero__clouds { will-change: transform; }
  .hero__palm { animation: palm-sway 9s var(--ease-in-out) infinite; will-change: transform; }
  .hero.is-off .hero__clouds, .hero.is-off .hero__palm { animation-play-state: paused; }
}
@keyframes hero-sky { to { transform: translateY(14%); } }
@keyframes hero-clouds { to { transform: translateY(9%); } }
@keyframes cloud-drift { from { translate: -1.2% 0; } to { translate: 1.4% 0; } }
@keyframes palm-sway { 0%, 100% { rotate: -0.45deg; } 50% { rotate: 0.62deg; } }   /* Ali: a tiny bit more sway */

/* ---------- section heads centre where they have the room (round 3, E17: "center align the section headers wherever
   there is space for it"), and the controls under them follow the same axis (E23: "bring these buttons to the center
   as well"). Single-column heads only: serve, systems, how, installs, posts. Two-column heads (story, reads, news,
   reviews, area, faq, book) keep their left edge for the column beside them, and phones keep left: no empty side there. */
@media (min-width: 761px) {
  .serve__head, .how__head, .installs__head { margin-inline: auto; text-align: center; }
  .sys__head, .posts .posts__head { justify-content: center; text-align: center; }
  .sys__head > div:first-child, .posts__head > div:first-child { flex: 0 0 100%; max-width: none; }   /* the button (and the posts' arrows) wrap to one centred row under the text */
  :is(.serve__head, .sys__head, .how__head, .installs__head, .posts__head) .lead { max-width: 760px; margin-inline: auto; text-wrap: balance; }   /* centred lines even out; pretty left a 4-word stub */
  .posts .posts__head > .btn { margin-left: 0; }
  .installs .gal__chips, .installs .rail__nav { justify-content: center; }
}

/* ---------- tablet and phone: re-composed, never squeezed ---------- */
@media (max-width: 1080px) {
  .sys__list a { grid-template-columns: 84px minmax(0, 1fr); row-gap: 4px; }
  .sys__list a > span:last-child { grid-column: 2; }
  .sys__art { width: 84px; grid-row: span 2; }
}
@media (max-width: 960px) {
  .story__grid, .sys__body, .reads__grid, .reviews__grid, .area__grid, .faq__grid, .book__grid, .news__grid { grid-template-columns: minmax(0, 1fr); }
  .sys__peek { display: none; }
  .faq__intro { position: static; }
  .faq__art { display: none; }
  .steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .steps__line { display: none; }
  .foot__grid { grid-template-columns: 1fr 1fr; }
  .foot__brand { grid-column: 1 / -1; }
  .serve__cards { grid-template-columns: minmax(0, 1fr); }
  .story__photo { max-width: 520px; width: 86%; }
  .story__rings { left: -9%; bottom: 16%; width: 46%; }   /* E21: the rings come in from the screen's edge and keep off the caption */
}
@media (max-width: 760px) {
  .hero__stats dt { font-size: 12px; letter-spacing: 0.06em; }   /* round 3 polish: the 12 px floor (was 10.5) */
  .film__frame { aspect-ratio: 4 / 5; max-height: 72svh; min-height: 0; }
  .film__cap { top: 16px; bottom: auto; left: 16px; padding: 8px 14px 8px 10px; gap: 8px; }   /* the Pause pill keeps the foot */
  /* round 4 (E12): "Serving neighborhoods like yours" keeps one line down to 320 px (it wrapped "yours" and met the
     frame's edge at 19 px): the size follows the width there; E20 quieted it to 15 px up to 360, 16 px from about 384 */
  .film__cap { font-size: clamp(15px, calc((100vw - 80px) / 19), 16px); white-space: nowrap; }
  .story__facts { grid-template-columns: minmax(0, 1fr); gap: 14px; }
  .story__facts li + li { padding-left: 0; border-left: 0; border-top: 1px solid var(--hair); padding-top: 14px; }
  .steps { grid-template-columns: minmax(0, 1fr); }
  .step { display: grid; grid-template-columns: 88px minmax(0, 1fr); column-gap: 20px; }
  .step__photo { width: 88px; height: 88px; grid-row: span 3; margin: 0; }
  .promises { grid-template-columns: minmax(0, 1fr); }
  .reads__proof { grid-template-columns: 112px minmax(0, 1fr); }
  .reads__meter { width: 112px; }
  /* round 3 polish: Explore and Services side by side on phones (they ran two screens long, one under the other); the
     brand block and Reach us keep the full width. Links 14.5 px so the longest stays on one line at 360 px */
  .foot__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 20px; }
  .foot__brand, .foot__reach { grid-column: 1 / -1; }
  .foot__col a { font-size: 14.5px; }
  .foot__base ul { margin-left: 0; }
  .news-form__row { flex-direction: column; }
  .news-form__row .btn { justify-content: center; }
}
@media (prefers-reduced-motion: reduce) {
  .film__frame { animation: none !important; transform: none; border-radius: 0; }
  .vista__video { animation: none !important; transform: none; top: 0; height: 100%; }
  .js [data-reveal], .js .step { opacity: 1 !important; transform: none !important; transition: none !important; }
  .js .foot__wm[data-reveal] .foot__word { clip-path: none; transform: none; transition: none; }
  .js .steps__line { clip-path: none; transition: none; }
}
.post__art { width: 100%; height: auto; aspect-ratio: 4 / 3; margin-bottom: 6px; border-radius: 12px; background: linear-gradient(180deg, #F5F8F6, var(--stone)); }
/* round 6 (5 Nov 2026): the cards show each post's own featured image (tools/make_post_covers.py), in the plate's box */
.post__img { display: block; width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover; margin-bottom: 6px; border-radius: 12px;
  background: var(--stone); }

/* ---- homepage/src/css/widgets.css ---- */
/* ---------- interactive pieces: each one is a real control first, an animation second ---------- */

/* what the test reads: four real readings as toggle buttons */
/* round 5 (E47, Ali: "pills arent the same size consistent"): one size for all four. Rows share the tallest tile's height
   (a two-line name, "Combined chlorine" on a phone, made its tile the tallest), each button fills its cell, and its name
   holds the top while the number sits at the foot, so the numbers line up across a row */
.readings { list-style: none; margin: 30px 0 0; padding: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-rows: 1fr; gap: 10px; }
.readings li { display: flex; min-width: 0; }
.readings button { width: 100%; height: 100%; display: grid; align-content: space-between; gap: 6px; padding: 15px 18px; border-radius: 14px; border: 1px solid var(--hair);
  background: var(--wash); color: var(--ink); text-align: left;
  transition: border-color var(--dur-quick) var(--ease-calm), background-color var(--dur-quick) var(--ease-calm); }
.readings button[aria-pressed="true"] { border-color: var(--spring-ink); background: rgb(43 184 175 / 0.1); }
@media (hover: hover) and (pointer: fine) { .readings button[aria-pressed="false"]:hover { border-color: var(--hair-3); } }
.readings__k { font: 500 12px/1.2 var(--font-mono); letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-2); }   /* 12 px floor */
.readings__v { font: 800 30px/1 var(--font-display); letter-spacing: -0.02em; }
.readings__v small { margin-left: 3px; font: 500 13px/1 var(--font-mono); letter-spacing: 0.04em; color: var(--ink-2); }
.readings__say { color: var(--ink-2); margin-top: 20px; min-height: 5.4em; font-size: 16.5px; line-height: 1.62; transition: opacity 260ms var(--ease-calm); }
.readings__say.is-swap { opacity: 0; }

/* the hardness scale: the art, a styled range, and a ruler whose 0 to 25 sits under the thumb's centre */
.hard { padding: clamp(18px, 2.4vw, 30px); border-radius: var(--radius-lg); background: var(--wash); box-shadow: inset 0 0 0 1px var(--hair); }
.hard__art { aspect-ratio: 4 / 3; border-radius: var(--radius); overflow: hidden; background: #E9EEEC; }
.hard__art > svg { width: 100%; height: 100%; display: block; }
.hard__label { display: grid; gap: 4px; margin-top: 22px;   /* the reading has its own line, so the slider never moves as the words change */
  font: 600 15.5px/1.3 var(--font-body); color: var(--ink); }
.hard__label output { display: block; min-height: 1.3em; font: 500 14px/1.3 var(--font-mono); letter-spacing: 0.02em; color: var(--spring-ink); }
@media (max-width: 520px) { .hard__label output { min-height: 2.6em; } }
.hard__range { --v: 0.492; -webkit-appearance: none; appearance: none; display: block; width: 100%; height: 32px; margin: 12px 0 0; background: transparent; cursor: pointer; }
.hard__range:focus-visible { outline: none; }
.hard__range::-webkit-slider-runnable-track { height: 6px; border-radius: 6px;
  background: linear-gradient(90deg, var(--spring-ink) calc(12px + (100% - 24px) * var(--v)), var(--track) 0); }
.hard__range::-webkit-slider-thumb { -webkit-appearance: none; width: 24px; height: 24px; margin-top: -9px; border: 0; border-radius: 50%;
  background: var(--thumb); box-shadow: 0 0 0 6px rgb(43 184 175 / 0.28); transition: box-shadow var(--dur-quick) var(--ease-calm); }
.hard__range:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 3px var(--ground), 0 0 0 5px var(--spring-ink); }
.hard__range::-moz-range-track { height: 6px; border-radius: 6px; background: var(--track); }
.hard__range::-moz-range-progress { height: 6px; border-radius: 6px; background: var(--spring-ink); }
.hard__range::-moz-range-thumb { width: 24px; height: 24px; border: 0; border-radius: 50%; background: var(--thumb); box-shadow: 0 0 0 6px rgb(43 184 175 / 0.28); }
.hard__range:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 3px var(--ground), 0 0 0 5px var(--spring-ink); }
.hard__scale { position: relative; height: 72px; margin: 6px 12px 0; --max: 430; }   /* parts per million: 25 gpg at 17.2 */
.hard__scale i { position: absolute; font: 500 12px/1 var(--font-mono); font-style: normal; letter-spacing: 0.04em; text-transform: uppercase;   /* 12 px floor (was 10.5) */
  color: var(--ink-2); white-space: nowrap; }
.hard__band { position: absolute; left: calc(var(--a) / var(--max) * 100%); width: calc((var(--b) - var(--a)) / var(--max) * 100%); height: 6px; border-radius: 6px; }
.hard__band--tampa { top: 28px; background: rgb(124 181 224 / 0.82); }
.hard__band--cq { top: 39px; background: rgb(43 184 175 / 0.86); }
.hard__tick { position: absolute; left: calc(var(--v) / var(--max) * 100%); width: 0; border-left: 1px solid var(--tick); }
.hard__tick--up { top: 16px; height: 34px; }
.hard__tick--up i { bottom: calc(100% + 5px); left: 0; transform: translateX(-50%); }
.hard__tick--down { top: 22px; height: 34px; }
.hard__tick--down i { top: calc(100% + 6px); left: 0; transform: translateX(-50%); }
.hard__ends i { bottom: 0; }
.hard__ends i:first-child { left: 0; transform: translateX(-50%); }
.hard__ends i:last-child { right: 0; transform: translateX(50%); }
.hard__legend { list-style: none; margin: 16px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px 22px; font-size: 13.5px; color: var(--ink-2); }
.hard__legend li { display: inline-flex; align-items: center; gap: 9px; }
.hard__sw { display: inline-block; width: 20px; height: 6px; border-radius: 6px; }
.hard__sw--tampa { background: rgb(124 181 224 / 0.82); }
.hard__sw--cq { background: rgb(43 184 175 / 0.86); }
.hard__note { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--hair); font-size: 14px; line-height: 1.6; color: var(--ink-2); }

/* rails: native horizontal scroll with snap; a mouse can drag, keys and trackpads work as they always do. overflow-y
   hidden (perf pass, 2 Nov): with overflow-x auto the other axis computes to auto too, and the cards still waiting for
   their entrance (14 px low) gave both rails 4 to 8 px of vertical scroll, so a vertical flick or swipe over a rail went
   to the rail and the page lost most of it (checkpoint/round4/perf/runs/rail-*; obs 1916). Hidden clips the same box. */
[data-rail-track] { display: flex; gap: 18px; overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain; scroll-snap-type: x mandatory;
  scrollbar-width: none; cursor: grab; }
[data-rail-track]::-webkit-scrollbar { display: none; }
[data-rail-track].is-drag { cursor: grabbing; scroll-snap-type: none; user-select: none; }
[data-rail-track].is-drag a, [data-rail-track].is-drag img { pointer-events: none; }
[data-rail-track]:focus-visible { outline-offset: 6px; }   /* ring only: the track keeps its own shape (E9; it took an 18 px radius on focus until round 3) */
.posts__track { padding: 4px max(var(--gut), (100% - var(--maxw)) / 2) 10px; scroll-padding-inline: max(var(--gut), (100% - var(--maxw)) / 2); }
.rail__nav { display: flex; justify-content: flex-end; gap: 10px; margin-top: 22px; }
.posts .rail__nav:not(.rail__nav--head) { width: min(100% - 2 * var(--gut), var(--maxw)); margin-inline: auto; }
.rail__nav--head { margin-top: 0; width: auto; margin-inline: 0; }   /* in a section head, beside its button */
.posts__head > .btn { margin-left: auto; }
.rail__nav button { width: 48px; height: 48px; display: grid; place-items: center; border-radius: 50%; border: 1px solid var(--hair);
  background: transparent; color: var(--ink); transition: border-color var(--dur-quick) var(--ease-calm), opacity var(--dur-quick) var(--ease-calm); }
.rail__nav button svg { width: 20px; height: 20px; }
.rail__nav button:disabled { opacity: 0.35; cursor: default; }
@media (hover: hover) and (pointer: fine) { .rail__nav button:not(:disabled):hover { border-color: var(--ink); } }
/* round 3 (fix 5): a card cut by the rail's edge fades into it instead of ending mid-word ("A r"), and only on a side
   where more cards wait (widgets.js sets the classes with the arrows' own state) */
[data-rail-track].is-more-r { -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 72px), transparent); mask-image: linear-gradient(90deg, #000 calc(100% - 72px), transparent); }
[data-rail-track].is-more-l { -webkit-mask-image: linear-gradient(90deg, transparent, #000 48px); mask-image: linear-gradient(90deg, transparent, #000 48px); }
[data-rail-track].is-more-l.is-more-r { -webkit-mask-image: linear-gradient(90deg, transparent, #000 48px, #000 calc(100% - 72px), transparent);
  mask-image: linear-gradient(90deg, transparent, #000 48px, #000 calc(100% - 72px), transparent); }

/* installs gallery: filter chips over a dense grid of real photos. Round 4 (E34): the before-and-after rail came out and
   the gallery took its room: every photo shows, three to a row on wide screens (the first 2 x 2, the portraits 1 x 2, a
   band of the three landscapes after the fourth row, as build.py orders them: nine whole rows), rows sized so a
   portrait fills its cell uncropped (a third of the content less the gaps, x 4/3, over two rows) */
.gal { margin-top: clamp(56px, 6vw, 84px); }
.gal__chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 22px; }
.gal__chips button { min-height: 42px; padding: 0 18px; border-radius: 999px; border: 1px solid var(--hair); background: transparent; color: var(--ink);
  font: 600 15px/1 var(--font-body); transition: background-color var(--dur-quick) var(--ease-calm), color var(--dur-quick) var(--ease-calm), border-color var(--dur-quick) var(--ease-calm); }
.gal__chips button[aria-pressed="true"] { background: var(--btn); border-color: var(--btn); color: var(--btn-ink); }
@media (hover: hover) and (pointer: fine) { .gal__chips button[aria-pressed="false"]:hover { border-color: var(--ink); } }
.gal__grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); grid-auto-rows: clamp(170px, 19.3vw, 289px); grid-auto-flow: dense; gap: 14px; }
.gal__cell { position: relative; display: block; margin: 0; padding: 0; border: 0; border-radius: var(--radius); overflow: hidden; background: var(--stone);
  cursor: zoom-in; transition: opacity 300ms var(--ease-calm), transform 420ms var(--ease-out); }
.gal__cell--tall { grid-row: span 2; }
.gal__cell:first-child { grid-column: span 2; grid-row: span 2; }
.gal__img { width: 100%; height: 100%; object-fit: cover; transition: transform 900ms var(--ease-out); }
@media (hover: hover) and (pointer: fine) { button.gal__cell:hover .gal__img { transform: scale(1.035); } }
.gal__cell.is-out { opacity: 0; transform: scale(0.98); }
.gal__cell[hidden] { display: none; }
.gal__more { display: flex; width: fit-content; margin: 26px auto 0; }   /* round 3 (fix 4): "Show all 14 photos", under the grid */
.gal__more[hidden] { display: none; }

/* lightbox: the photo full size on a dark veil; swipe, arrows, Escape */
.lb { position: fixed; inset: 0; width: 100%; height: 100%; max-width: none; max-height: none; margin: 0; padding: 0; border: 0;
  background: rgb(12 20 28 / 0.95); color: #fff; opacity: 0; transition: opacity var(--dur-quick) var(--ease-calm); overscroll-behavior: contain; }
.lb[open] { display: grid; grid-template-rows: auto minmax(0, 1fr) auto; }
.lb.is-in { opacity: 1; }
.lb::backdrop { background: transparent; }
.lb__top { display: flex; justify-content: space-between; align-items: center; padding: 16px var(--gut); }
.lb__count { font: 500 13px/1 var(--font-mono); letter-spacing: 0.08em; color: var(--on-dark-2); }
.lb__stage { position: relative; display: grid; place-items: center; min-height: 0; padding: 0 clamp(16px, 7vw, 96px); touch-action: pan-y; }
/* round 3 (fix 2): the height is capped by the window, not by 100%: a percentage resolved against the stage's auto row,
   which the photo itself sized, so a tall photo opened 1779 px tall in an 864 px window and showed only its middle */
.lb__img { max-width: 100%; max-height: calc(100svh - 160px); width: auto; height: auto; object-fit: contain; border-radius: 12px;
  transition: opacity 260ms var(--ease-calm), transform 420ms var(--ease-out); }
.lb__img.is-swap { opacity: 0; transform: scale(0.985); }
.lb__cap { padding: 14px var(--gut) 22px; text-align: center; font-size: 15px; color: var(--on-dark-2); }
.lb__btn { width: 48px; height: 48px; display: grid; place-items: center; border-radius: 50%; border: 1px solid rgb(244 241 234 / 0.24);
  background: rgb(12 20 28 / 0.4); color: #fff; }
.lb__btn svg { width: 20px; height: 20px; }
.lb__prev, .lb__next { position: absolute; top: 50%; transform: translateY(-50%); }
.lb__prev { left: max(10px, calc(var(--gut) - 24px)); }
.lb__next { right: max(10px, calc(var(--gut) - 24px)); }
@media (hover: hover) and (pointer: fine) { .lb__btn:hover { border-color: #fff; } }
html.lb-open { overflow: hidden; }

/* the big quote: every slide shares one grid cell, so the stage is as tall as the longest and never jumps */
.quotes__stage { display: grid; }
.quote { grid-area: 1 / 1; margin: 0; transition: opacity 520ms var(--ease-calm), transform 640ms var(--ease-out), visibility 0s; }
.quote[hidden] { display: block; visibility: hidden; opacity: 0; transform: translateY(10px);
  transition: opacity 300ms var(--ease-calm), transform 300ms var(--ease-calm), visibility 0s linear 300ms; }
.quote blockquote p { font: 400 clamp(24px, 2.5vw, 36px)/1.34 var(--font-quote); color: var(--ink); text-indent: -0.42em; }
.quote figcaption { display: flex; align-items: center; gap: 10px; margin-top: 24px; font-size: 15px; color: var(--ink-2); }
.quote figcaption b { color: var(--ink); font-weight: 650; }
.g-dot { width: 16px; height: 16px; flex: none; border-radius: 50%; background: conic-gradient(#EA4335 0 25%, #FBBC05 0 50%, #34A853 0 75%, #4285F4 0); }
.quotes__nav { display: flex; align-items: center; gap: 10px; margin-top: 30px; }
.quotes__nav button { width: 48px; height: 48px; display: grid; place-items: center; border-radius: 50%; border: 1px solid var(--hair);
  background: transparent; color: var(--ink); transition: border-color var(--dur-quick) var(--ease-calm); }
.quotes__nav button svg { width: 20px; height: 20px; }
@media (hover: hover) and (pointer: fine) { .quotes__nav button:hover { border-color: var(--spring-ink); } }
.quotes__count { margin-left: 8px; font: 500 13px/1 var(--font-mono); letter-spacing: 0.06em; color: var(--ink-2); }

/* the review belt: two rows drifting in opposite directions at 30 px/s; drag to throw, Pause to stop */
.belt { position: relative; margin-top: clamp(64px, 7vw, 104px); }
.belt__bar { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 22px; }
.belt__bar .label { color: var(--ink-2); }
.belt__pause { display: inline-grid; grid-template-columns: auto auto; grid-template-areas: "icon say"; align-items: center; column-gap: 8px;
  min-height: 40px; padding: 0 16px 0 12px; border-radius: 999px;
  border: 1px solid var(--hair); background: transparent; color: var(--ink); font: 600 14px/1 var(--font-body);
  transition: border-color var(--dur-quick) var(--ease-calm); }
.belt__pause svg { grid-area: icon; width: 18px; height: 18px; }
.belt__pause svg path { fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.belt__i2, .belt__pause[data-paused="true"] .belt__i1 { display: none; }
.belt__pause[data-paused="true"] .belt__i2 { display: inline; }
/* round 3: "Pause" and "Play" share one cell, the hidden word keeping its room, so the button holds one width */
.belt__p1, .belt__p2 { grid-area: say; }
.belt__p2, .belt__pause[data-paused="true"] .belt__p1 { visibility: hidden; }
.belt__pause[data-paused="true"] .belt__p2 { visibility: visible; }
@media (hover: hover) and (pointer: fine) { .belt__pause:hover { border-color: var(--ink); } }
.belt__row { overflow-x: auto; scrollbar-width: none; }
.belt__row::-webkit-scrollbar { display: none; }
.js .belt__row { overflow: hidden; touch-action: pan-y; cursor: grab; }
.js .belt__row.is-drag { cursor: grabbing; user-select: none; }
.belt__row + .belt__row { margin-top: 18px; }
.belt__track { display: flex; gap: 18px; width: max-content; padding-inline: 9px; }
.js .belt__track { will-change: transform; }
.rev { flex: none; width: clamp(290px, 26vw, 380px); margin: 0; padding: 24px; border-radius: 18px; background: #F1EBE0; color: var(--ink);
  display: grid; grid-template-rows: minmax(0, 1fr) auto; gap: 20px; }
.rev p { font-size: 15.5px; line-height: 1.58; color: var(--ink); }
.rev figcaption { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 12px; align-items: center; }
.rev__av { width: 40px; height: 40px; border-radius: 50%; display: grid; place-items: center; background: #DDEFEC; color: var(--spring-ink); font: 700 14px/1 var(--font-body); }
.rev__who { display: grid; gap: 2px; font-size: 13.5px; color: var(--ink-2); }
.rev__who b { color: var(--ink); font-size: 15px; font-weight: 650; }
/* batch 33: the live widget's six lead the rows; each name links its reviewer on Google, and a review over 420
   characters takes a wider card so its row keeps its height */
.rev--long { width: clamp(340px, 38vw, 560px); }
.rev__name { justify-self: start; color: inherit; text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px;
  text-decoration-color: rgb(22 34 48 / 0.32); -webkit-user-drag: none; }
@media (hover: hover) and (pointer: fine) { .rev__name:hover { text-decoration-color: currentColor; } }
.rev__stars { width: 76px; height: 14px; background: #D99A1E; -webkit-mask: var(--stars) left center / 76px 14px no-repeat; mask: var(--stars) left center / 76px 14px no-repeat; }
:root { --stars: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 76 14'%3E%3Cpath id='s' d='M7 .6l1.9 4 4.4.5-3.3 3 .9 4.3L7 10.2l-3.9 2.2.9-4.3-3.3-3 4.4-.5z'/%3E%3Cuse href='%23s' x='15.5'/%3E%3Cuse href='%23s' x='31'/%3E%3Cuse href='%23s' x='46.5'/%3E%3Cuse href='%23s' x='62'/%3E%3C/svg%3E"); }

/* anything that loops stops when its section is off screen */
.is-off [data-book-drop] *, .is-off .belt__track { animation-play-state: paused !important; }

@media (max-width: 960px) {
  .gal__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-rows: clamp(140px, 34vw, 260px); }
  .gal__cell--span { grid-column: span 2; grid-row: span 2; }   /* round 4 (E34): two columns hold 27 cells badly; the third landscape spans 2 x 2 and every row closes */
}
@media (max-width: 760px) {
  .readings__v { font-size: 26px; }
  .gal__chips button { min-height: 40px; padding: 0 15px; }   /* four chips on two short rows: all in view, none cut at the edge */
  .lb__prev, .lb__next { top: auto; bottom: 12px; transform: none; }
  .lb__cap { padding-bottom: 76px; }
}
@media (prefers-reduced-motion: reduce) {
  .quote, .quote[hidden], .gal__cell, .gal__img, .lb, .lb__img, .readings__say { transition-duration: 1ms !important; transition-delay: 0s !important; }
}

/* ---- homepage/src/css/art-live.css ---- */
/* Live drawing: the hardness close-up (art/hard_art.py -> partials/hard-art.html). Only selectors under .hard /
   .hard__art. Everything that loops is opacity or transform, and stops when its section is off screen (.reads.is-off)
   or the visitor prefers reduced motion. (The hero's spring and its live layer came out in round 4, E6; the city or
   well cutaway and its switch came out in round 4, E26, and art/source_art.py still writes partials/source-art.html for
   later use; the hero's meter and butterflies carry their own rules in partials/hero-art.html.) */

/* ---------- 8 · hardness close-up ---------- */
.hard__art { position: relative; aspect-ratio: 4 / 3; border-radius: var(--radius); overflow: hidden; background: #EEF1EF; contain: layout paint; }
.hard__art svg { display: block; width: 100%; height: 100%; }
/* the stream's glints loop in their own small root stacked on the drawing, so they never repaint the deposits */
.hard__art > .hard__glints { position: absolute; inset: 0; pointer-events: none; }
/* every deposit comes in over its own stretch of the scale: --a where it starts to show, --s one over its span;
   --hard (0 to 25, set by the page's script on .hard) falls back to the Tampa 2025 average when no script runs */
.hard__art .k-grow { opacity: clamp(0, calc((var(--hard, 12.3) - var(--a)) * var(--s)), 1); }
/* the stream's glints run down it, gently */
.hard__art .k-glint { opacity: 0; }
@keyframes k-glint {
  0% { transform: translateY(-36px); opacity: 0; }
  18% { opacity: 0.9; }
  78% { opacity: 0.9; }
  100% { transform: translateY(250px); opacity: 0; }
}
/* the water, moving (round 4, E28): js/stream.js paints it on a WebGL canvas the art places over the drawing. The
   canvas fades in over the drawn stream, which then steps aside with its glints; without WebGL, before the first frame
   or after a lost context, the drawn stream stays */
.hard__art > .hard__water { position: absolute; pointer-events: none; opacity: 0; }
.hard__art.is-wet > .hard__water { opacity: 1; }
.hard__art.is-water .k-stream, .hard__art.is-water > .hard__glints { display: none; }
@media (prefers-reduced-motion: no-preference) {
  .hard__art > .hard__water { transition: opacity 450ms ease-out; }
  .hard__art .k-grow { transition: opacity 220ms linear; }
  .hard__art .k-glint { animation: k-glint 2.8s cubic-bezier(0.4, 0, 0.2, 1) infinite; }
  .hard__art .k-glint--b { animation-duration: 3.4s; animation-delay: -1.5s; }
  .reads.is-off .hard__art .k-glint { animation-play-state: paused; }
}

/* ---- homepage/src/css/form.css ---- */
/* Design Taste · component: progressive booking form + book sheet · genre: editorial (the page's drawn Florida water)
 * theme: page tokens (tokens.json) · states: default · hover · focus · active · disabled · loading · error · success
 * contrast (computed, WCAG): ink on white 16.1:1 · ink-2 on white 8.2:1 · error #9E2F24 on white 7.3:1 ·
 *   spring-ink on the chosen tint 5.1:1 · white tick on spring-ink 5.5:1 · field edge (navy 50%) on white 3.2:1 ·
 *   focus ring #0A7672 on #FAF7F1 5.1:1 · navy on the spring Send button 6.6:1
 * After Medscapes' progressive estimate form (left: title, promise, drawn progress, 1 OF 5; right: the question, a
 * hint, drawn tiles, a hairline and Next) and Bay Balcony's BookSheet (grows out of the pressed button; on a phone a
 * full-height sheet that drags down to close). */

:root {
  --bf-err: #9E2F24;                                               /* the page's error colour (base.css .btn error) */
  --bf-edge: color-mix(in srgb, var(--navy) 50%, transparent);     /* field and check edges: 3:1 on white */
  --bf-line: color-mix(in srgb, var(--navy) 26%, transparent);     /* option pills */
  --bf-tint: color-mix(in srgb, var(--spring) 7%, #fff);           /* a chosen tile: the faintest spring */
}

/* ---------- the drop: fills a step at a time (data-step on [data-book-drop]); levels by filled area ---------- */
.bdrop { position: relative; width: 76px; height: 96px; --bd-y: 65.8px; }
[data-step="2"] > .bdrop { --bd-y: 55.1px; }
[data-step="3"] > .bdrop { --bd-y: 44.5px; }
[data-step="4"] > .bdrop { --bd-y: 31.9px; }
[data-step="5"] > .bdrop { --bd-y: 17.8px; }
[data-step="done"] > .bdrop { --bd-y: -7px; }
.book__drop .bdrop svg { position: absolute; left: 0; top: 0; width: 76px; height: 96px; overflow: visible; }
.book__drop .bdrop .bdrop__wave { width: 152px; height: 104px; overflow: hidden; }
.bdrop__glass, .bdrop__water { position: absolute; inset: 0;
  clip-path: path('M38 10.75C28.5 25.63 12.67 44.63 12.67 63A25.33 25.33 0 0 0 63.33 63C63.33 44.63 47.5 25.63 38 10.75Z'); }
.bdrop__glass { background: #EAF4FA; }              /* art pane_l: clear water, so the white shine reads on it */
.bdrop__rise { position: absolute; left: 0; top: 0; width: 152px; height: 104px;
  transform: translateY(var(--bd-y)); transition: transform var(--dur-slow) var(--ease-out); }
.bdrop.is-live .bdrop__wave--front { animation: bd-wave 5.6s linear infinite; }
.bdrop.is-live .bdrop__wave--back { animation: bd-wave 8.8s linear infinite reverse; }
@keyframes bd-wave { to { transform: translateX(-76px); } }

/* the small drop that rides with the form into the sheet (the island's drop, same outline) */
.bf__mdrop { position: relative; flex: none; width: 24px; height: 30px; --md-y: 21.1px; }
.bf__mdrop[data-step="2"] { --md-y: 17.7px; }
.bf__mdrop[data-step="3"] { --md-y: 14.4px; }
.bf__mdrop[data-step="4"] { --md-y: 10.4px; }
.bf__mdrop[data-step="5"] { --md-y: 5.9px; }
.bf__mdrop[data-step="done"] { --md-y: -2px; }
.bf__mglass, .bf__mwater { position: absolute; inset: 0;
  clip-path: path('M12 1.5C9 6.2 4 12.2 4 18a8 8 0 0 0 16 0c0-5.8-5-11.8-8-16.5z'); }
.bf__mglass { background: #EAF4FA; }
.bf__mwater::before { content: ''; position: absolute; left: 0; right: 0; top: 0; height: 34px; background: #2DB8AE;
  transform: translateY(var(--md-y)); transition: transform var(--dur-slow) var(--ease-out); }
.bf__mdrop svg { position: absolute; inset: 0; width: 24px; height: 30px; overflow: visible; }

/* ---------- the form's home in the section: one white card beside the intro; the card stays when the form travels to
   the sheet, holding its height, so the page never moves ---------- */
[data-book-home] { position: relative; min-width: 0; padding: clamp(22px, 3vw, 40px); background: var(--paper);
  border-radius: 22px; box-shadow: 0 0 0 1px var(--hair), 0 22px 34px -30px rgb(14 42 53 / 0.3); }
/* round 5, 3 Nov (Ali: "so much unnecessary blank space between the selections and the next and back buttons on the
   form"): beside the intro the card fits the step it shows, Back and Next a hairline under the last answer, and its
   height glides when the step changes (form.js). Round 3 stretched the card down to stand on the bank drawing with its
   foot pinned to the floor; once the answers-so-far list made the intro taller than the form, that left Back and Next
   340 to 570 px under the answers. The intro keeps the bank's 80 px tuck plus air under its last line instead, so the
   drawing never runs under text. */
@media (min-width: 961px) {
  [data-book-home] { align-self: start; }
  .book__intro { padding-bottom: 120px; }
}

/* ---------- the form ---------- */
.bf { overflow-anchor: none; }                     /* a step changing height never drags the page by its anchor */
.bf [hidden] { display: none !important; }
.bf__prog { display: none; align-items: center; gap: 10px; min-height: 44px; padding-right: 60px; pointer-events: none; }
.bf__pcount { color: var(--ink-2); }

.bf__steps { position: relative; container: bf / inline-size; }
.bf__step { min-width: 0; margin: 0; padding: 0; border: 0; }
.js .bf__step:not(.is-current):not(.is-leaving) { display: none; }
.bf__step.is-leaving { position: absolute; margin: 0; pointer-events: none; }
.bf__steps.is-moving { overflow: clip; overflow-clip-margin: 14px; }

/* the question (legend), floated so it lays out like a heading */
.bf__q { float: left; width: 100%; margin: 0; padding: 0; font: 800 clamp(26px, 2.6vw, 38px)/1.08 var(--font-display);
  letter-spacing: -0.028em; color: var(--ink); text-wrap: balance; }
.bf__q:focus { outline: none; }                    /* a focus target for the step change, not a control */
.bf__q + * { clear: both; }
.bf__hint { padding-top: 12px; font-size: 16.5px; line-height: 1.5; color: var(--ink-2); }
.bf__sub { min-width: 0; margin: 30px 0 0; padding: 0; border: 0; }
.bf__subq { float: left; width: 100%; margin: 0; padding: 0; font: 600 15.5px/1.35 var(--font-body); color: var(--ink); }
.bf__subq + * { clear: both; }
/* the pills sit a clear step under their question (a float's clearance swallowed the old 12 px margin, so the words
   pressed onto the pills, round 3, E11): padding, which clearance cannot eat */
.bf__sub > .bf-seg { margin-top: 0; padding-top: 14px; }
.bf__opt { font-weight: 400; color: var(--ink-2); }

/* errors: words and a drawn mark beside the field, never colour alone. Nothing moves when one appears (E10): a step's
   hint and its error share one cell that always holds the taller of the two, and every checked field keeps a line
   for its message, so tiles, fields and buttons stay planted whatever is said near them. */
.bf__err { display: flex; align-items: flex-start; gap: 8px; margin-top: 12px; font: 500 15px/1.45 var(--font-body);
  color: var(--bf-err); }
.bf__err svg { flex: none; width: 16px; height: 16px; margin-top: 2.5px; }
.bf__field .bf__err { margin-top: 0; }
/* round 3 (fix 1): an error or field that form.js scrolls into view stops clear of the header above and the island
   below; it landed at the very top, under the header that slides back in on the way up */
.bf__err, .bf__field { scroll-margin: 96px 0 112px; }
.js .bf__say { display: grid; }
.js .bf__say > * { grid-area: 1 / 1; transition: opacity 240ms var(--ease-calm); }
.js .bf__say > .bf__err { margin-top: 0; padding-top: 12px; }
.js .bf__say > .bf__err[hidden] { display: flex !important; visibility: hidden; opacity: 0; }
.js .bf__say:has(> .bf__err:not([hidden])) > .bf__hint { opacity: 0; }
.js .bf__field .bf__err[hidden] { display: flex !important; visibility: hidden; opacity: 0; }
.js .bf__field .bf__err { min-height: 1.45em; transition: opacity 240ms var(--ease-calm); }
.js .bf__fields { row-gap: 12px; }                 /* the message line under a checked field carries part of the gap */

/* ---------- drawn choice tiles: a real checkbox or radio over the whole card ---------- */
.bf__tiles { --cols: 4; --tg: 12px; display: flex; flex-wrap: wrap; justify-content: flex-start; gap: var(--tg); margin-top: 22px; }
/* the last row starts at the left edge with the rest of the grid: centred, seven tiles in four columns stood half a
   tile off their columns (E11) */
.bf__tiles--three { --cols: 3; }
.bf-tile { position: relative; flex: 0 0 calc((100% - (var(--cols) - 1) * var(--tg)) / var(--cols)); min-width: 0;
  display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 18px 11px 16px;
  background: #fff; border: 1px solid var(--hair); border-radius: 16px; text-align: center; cursor: pointer;
  transition: transform var(--dur-quick) var(--ease-out), border-color var(--dur-quick) var(--ease-calm),
    background-color var(--dur-quick) var(--ease-calm), box-shadow var(--dur-quick) var(--ease-calm); }
.bf-tile input { position: absolute; inset: 0; z-index: 2; width: 100%; height: 100%; margin: 0; border-radius: inherit;
  opacity: 0; cursor: pointer; -webkit-appearance: none; appearance: none; }
.bf-tile__art { display: block; width: min(132px, 100%); aspect-ratio: 4 / 3; }
.bf-tile__art img, .bf-tile__art svg { display: block; width: 100%; height: 100%; }
.bf-tile__t { font: 600 15.5px/1.25 var(--font-body); color: var(--ink); text-wrap: balance; }
.bf-tile__s { margin-top: -2px; font-size: 13.5px; line-height: 1.35; color: var(--ink-2); }   /* round 5: a service tile's second line, 4 px under its title */
.bf-tile__tick { position: absolute; top: 12px; right: 12px; width: 22px; height: 22px; display: grid; place-items: center;
  border: 1px solid var(--bf-edge); border-radius: 50%; background: #fff;
  transition: background-color 240ms var(--ease-calm), border-color 240ms var(--ease-calm); }
.bf-tile__tick svg { width: 20px; height: 20px; fill: none; stroke: #fff; stroke-width: 2; stroke-linecap: round;
  stroke-linejoin: round; stroke-dasharray: 1 2; stroke-dashoffset: 1; transition: stroke-dashoffset 300ms var(--ease-out) 60ms; }
.bf-tile::after { content: ''; position: absolute; inset: -1px; border: 1.5px solid var(--spring-ink); border-radius: 16px;
  opacity: 0; pointer-events: none; transition: opacity 240ms var(--ease-calm); }
.bf-tile:has(input:checked) { background: var(--bf-tint); }
.bf-tile:has(input:checked)::after { opacity: 1; }
.bf-tile:has(input:checked) .bf-tile__tick { background: var(--spring-ink); border-color: var(--spring-ink); }
.bf-tile:has(input:checked) .bf-tile__tick svg { stroke-dashoffset: 0; }
.bf-tile:has(input:focus-visible) { outline: 2px solid var(--focus); outline-offset: 3px; }
/* planted (Ali, round 3, E10: "no fidgeting"): a tile never leaves its place. Hover answers with its edge and a soft
   shadow under it; a press answers with the faint wash it will keep once chosen; the tick draws itself on choosing. */
@media (hover: hover) and (pointer: fine) {
  .bf-tile:hover { border-color: color-mix(in srgb, var(--spring-ink) 45%, transparent);
    box-shadow: 0 10px 22px -16px rgb(14 42 53 / 0.3); }
  .bf-tile:hover .bf-tile__tick { border-color: var(--spring-ink); }
}
.bf-tile:active { background: color-mix(in srgb, var(--spring) 5%, #fff); }

/* ---------- option pills (household, system, best time) ---------- */
.bf-seg { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.bf-seg__o { position: relative; display: inline-flex; align-items: center; justify-content: center; min-height: 48px;
  padding: 0 20px; border: 1px solid var(--bf-line); border-radius: 999px; background: #fff;
  font: 500 16px/1.2 var(--font-body); color: var(--ink); cursor: pointer; white-space: nowrap;
  transition: border-color var(--dur-quick) var(--ease-calm), background-color var(--dur-quick) var(--ease-calm),
    color var(--dur-quick) var(--ease-calm), transform var(--dur-quick) var(--ease-out); }
.bf-seg__o input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; border-radius: inherit; opacity: 0;
  cursor: pointer; -webkit-appearance: none; appearance: none; }
.bf-seg__o::after { content: ''; position: absolute; inset: -1px; border: 1.5px solid var(--spring-ink); border-radius: inherit;
  opacity: 0; pointer-events: none; transition: opacity 240ms var(--ease-calm); }
.bf-seg__o:has(input:checked) { background: var(--bf-tint); color: var(--spring-ink); }
.bf-seg__o:has(input:checked)::after { opacity: 1; }
.bf-seg__o:has(input:focus-visible) { outline: 2px solid var(--focus); outline-offset: 3px; }
@media (hover: hover) and (pointer: fine) {
  .bf-seg__o:hover { border-color: color-mix(in srgb, var(--spring-ink) 55%, transparent); }
}
.bf-seg__o:active { background: color-mix(in srgb, var(--spring) 5%, #fff); }   /* planted: no shrink (E10) */

/* ---------- fields ---------- */
.bf__fields { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 20px 16px; margin-top: 24px; }
.bf__field { display: grid; gap: 10px; align-content: start; min-width: 0; }
.bf__field--full { grid-column: 1 / -1; }
.bf__fields > .bf__sub { margin-top: 0; }
.bf__lab { font: 600 15.5px/1.3 var(--font-body); color: var(--ink); }
.bf__in { display: block; width: 100%; min-height: 54px; margin: 0; padding: 0 16px; border: 1px solid var(--bf-edge);
  border-radius: 12px; background: #fff; font: 400 17px/1.4 var(--font-body); color: var(--ink); caret-color: var(--spring-ink);
  transition: border-color var(--dur-quick) var(--ease-calm); }
textarea.bf__in { min-height: 82px; padding: 14px 16px; line-height: 1.5; resize: vertical; }   /* two lines (round 3: step 5 set the card's height) */
.bf__in:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-color: var(--spring-ink); border-radius: 12px; }
.bf__in[aria-invalid="true"] { border-color: var(--bf-err); }
.bf__in:-webkit-autofill { -webkit-box-shadow: 0 0 0 40px #fff inset; -webkit-text-fill-color: var(--ink); }
.bf__in:autofill { box-shadow: 0 0 0 40px #fff inset; }
@media (hover: hover) and (pointer: fine) { .bf__in:hover:not(:focus) { border-color: var(--ink-2); } }
.bf__sel { position: relative; display: block; }
.bf__sel select { -webkit-appearance: none; appearance: none; padding-right: 46px; cursor: pointer; }
.bf__chev { position: absolute; top: 50%; right: 17px; width: 14px; height: 14px; margin-top: -7px; color: var(--ink); pointer-events: none;
  transition: transform 300ms var(--ease-out); }
.bf__note { font-size: 15.5px; line-height: 1.5; color: var(--spring-ink); }
.js .bf__note { min-height: 1.5em; }               /* the town note's line is kept, so the street field never moves (E10) */

/* the town list, styled (round 3, E12): where the browser supports a customisable select, the list that opens is the
   form's own card (Switzer, the hairline, a soft shadow, a spring wash on the option under the pointer or keys, a
   drawn tick on the chosen town; never the system's blue). It is still the native select underneath: arrow keys,
   typing a town's first letters, Escape, and screen readers all work as they always have. The list fades and settles
   in over 220 ms and leaves faster (160 ms); the chevron turns with it. Older browsers keep the native list. */
@supports (appearance: base-select) {
  .bf__sel select, .bf__sel ::picker(select) { appearance: base-select; }
  .bf__sel select { display: flex; align-items: center; padding: 0 46px 0 16px; }
  .bf__sel select::picker-icon { display: none; }
  .bf__sel:has(select:open) .bf__chev { transform: rotate(180deg); }
  .bf__sel select:open { border-color: var(--spring-ink); }
  .bf__sel ::picker(select) {
    margin-top: 8px; padding: 6px; border: 1px solid var(--hair); border-radius: 14px; background: #fff;
    box-shadow: 0 18px 40px -18px rgb(14 42 53 / 0.38), 0 2px 6px rgb(14 42 53 / 0.06);
    max-height: min(360px, 52vh); overflow-y: auto; scrollbar-width: thin; scrollbar-color: rgb(22 34 48 / 0.24) transparent;
    opacity: 0; transform: translateY(-6px);
    transition: opacity 160ms var(--ease-calm), transform 160ms var(--ease-calm), display 160ms allow-discrete, overlay 160ms allow-discrete;
  }
  .bf__sel ::picker(select):popover-open { opacity: 1; transform: none;
    transition: opacity 220ms var(--ease-out), transform 260ms var(--ease-out), display 220ms allow-discrete, overlay 220ms allow-discrete; }
  @starting-style { .bf__sel ::picker(select):popover-open { opacity: 0; transform: translateY(-6px); } }
  .bf__sel option { display: flex; align-items: center; gap: 10px; min-height: 44px; padding: 0 14px 0 12px; border-radius: 9px;
    font: 400 16.5px/1.3 var(--font-body); color: var(--ink); background: transparent; cursor: pointer;
    transition: background-color 160ms var(--ease-calm), color 160ms var(--ease-calm); }
  .bf__sel option::checkmark { order: 1; margin-left: auto; content: ''; width: 14px; height: 14px; flex: none;
    background: currentColor; -webkit-mask: var(--bf-tick) center / contain no-repeat; mask: var(--bf-tick) center / contain no-repeat; }
  .bf__sel option:hover, .bf__sel option:focus-visible { background: color-mix(in srgb, var(--spring) 10%, #fff); outline: none; }
  .bf__sel option:checked { color: var(--spring-ink); font-weight: 600; }
  .bf__sel option[value=""] { color: var(--ink-2); }
  .bf__sel option[value=""]::checkmark { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  @supports (appearance: base-select) {
    .bf__sel ::picker(select), .bf__sel ::picker(select):popover-open { transform: none; transition-duration: 1ms; }
    .bf__chev { transition-duration: 1ms; }
  }
}
:root { --bf-tick: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M2.6 7.4l2.9 2.9 5.9-6.4' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }

/* ---------- the foot: a hairline, Back and Next; on the last step Send and the consent line ---------- */
.bf__foot { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 32px; padding-top: 22px;
  border-top: 1px solid var(--hair); container: bff / inline-size; }
.bf__next, .bf__send { margin-left: auto; }
.bf__back, .bf__next { display: none; }
.js .bf__back, .js .bf__next { display: inline-flex; }
/* round 5 (E43): the review (step 6) is the last step and holds Send; the five before it hold Next */
.js .bf[data-step="1"] .bf__back, .js .bf[data-step="6"] .bf__next, .js .bf[data-step="done"] .bf__foot { display: none; }
.js .bf:not([data-step="6"]) .bf__send, .js .bf:not([data-step="6"]) .bf__consent { display: none; }
@media (hover: hover) and (pointer: fine) { .bf__back:hover svg { transform: translateX(-3px); } }
.bf__consent { flex: 1 0 100%; font-size: 13.5px; line-height: 1.45; color: var(--ink-2); text-align: right; }
.bf__send-l { display: grid; }
.bf__send-l > span { grid-area: 1 / 1; transition: opacity 200ms var(--ease-calm); }
.bf__send-l > span + span { opacity: 0; }
.bf__send[data-state="loading"] .bf__send-l > span:first-child { opacity: 0; }
.bf__send[data-state="loading"] .bf__send-l > span + span { opacity: 1; }
.bf__send-ring { opacity: 0; stroke-dasharray: 0.7 0.3; transform-origin: 10px 10px; }
.bf__send[data-state="loading"] .bf__send-arrow { opacity: 0; }
.bf__send[data-state="loading"] .bf__send-ring { opacity: 1; animation: bf-spin 0.9s linear infinite; }
@keyframes bf-spin { to { transform: rotate(1turn); } }
@container bff (max-width: 460px) {
  .bf__foot > .btn { flex: 1 1 0; min-width: 0; justify-content: center; }
  .bf__consent { text-align: left; }
}

/* ---------- round 5 (E43): the answers so far, the review, the street's suggestions, the opt-ins ----------
   Saif: "a form of progressive that also lets you see the previous info"; Ali: "previous selections can be seen side
   by side and a full review of all selections becomes the final step before submission". Beside the step (the intro
   column, wide screens): every step in play, the passed ones with their answer and a Change, the current one marked by
   a spring dot. Narrow (the intro is above the form, or hidden in the narrow sheet): the passed answers as thin chips
   over the question, each one a Change. Hairlines, never fills; the Change words in spring ink, underlined. */
.bf-sum { list-style: none; max-width: 380px; margin: 24px 0 0; padding: 0; border-top: 1px solid var(--hair); }
.bf-sum[hidden] { display: none; }
/* round 5 spacing pass (Ali: "make sure to adjust spacing correctly everywhere. that text feels too cramped"): a row
   breathes 12 px above and below, and a step's name stands 3 px clear of its answer (they touched) */
.bf-sum__i { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; column-gap: 12px; row-gap: 3px;
  min-height: 48px; padding: 12px 0; border-bottom: 1px solid var(--hair); }
.bf-sum__k { grid-column: 1; font: 500 15px/1.35 var(--font-body); color: var(--ink-2); }
.bf-sum__i.is-now .bf-sum__k { font-weight: 700; color: var(--ink); }
.bf-sum__i.is-now .bf-sum__k::after { content: ''; display: inline-block; width: 7px; height: 7px; margin-left: 9px;
  vertical-align: 1px; border-radius: 50%; background: var(--spring-ink); }
.bf-sum__i.is-done .bf-sum__k { font-size: 13.5px; }
.bf-sum__v { grid-column: 1; font: 600 15.5px/1.4 var(--font-body); color: var(--ink); overflow-wrap: anywhere; }
.bf-sum__x { grid-column: 2; grid-row: 1 / span 2; }
.bf-sum__x, .bf-rev__edit { display: inline-flex; align-items: center; min-height: 40px; padding: 0 2px; border: 0; background: none;
  font: 600 15px/1 var(--font-body); color: var(--spring-ink); text-decoration: underline; text-decoration-color: currentColor;
  text-decoration-thickness: 1px; text-underline-offset: 0.3em; cursor: pointer; }
.bf-sum__x:focus-visible, .bf-rev__edit:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: 6px; }
@media (hover: hover) and (pointer: fine) { .bf-sum__x:hover, .bf-rev__edit:hover { text-decoration-thickness: 2px; } }
.bf-sum__x:active, .bf-rev__edit:active { color: var(--ink); }

/* E45 (Ali, over the phone sheet: "those pills on top of form are getting cut off"): the chips wrap onto lines, so every
   answer shows whole (a sideways scroller sliced the first and last at the card's edges), and the row never shrinks: in
   the sheet's fixed-height column a scroller may shrink to nothing, and the column squeezed the chips' bottoms off */
.bf-chips { flex: none; display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 20px; padding: 0; }
.bf-chip { flex: none; display: inline-flex; align-items: center; gap: 7px; max-width: 100%; min-height: 40px; padding: 0 14px 0 15px;
  border: 1px solid var(--bf-line); border-radius: 999px; background: #fff; font: 500 14.5px/1.2 var(--font-body); color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer; }
.bf-chip::after { content: ''; flex: none; width: 12px; height: 12px; background: var(--spring-ink);
  -webkit-mask: var(--bf-pen) center / contain no-repeat; mask: var(--bf-pen) center / contain no-repeat; }
.bf-chip:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
@media (hover: hover) and (pointer: fine) { .bf-chip:hover { border-color: var(--spring-ink); } }
.bf-chip:active { background: var(--bf-tint); }
:root { --bf-pen: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2 10l.6-2.4L8.4 1.8a1.1 1.1 0 0 1 1.6 0l.2.2a1.1 1.1 0 0 1 0 1.6L4.4 9.4z' fill='none' stroke='%23000' stroke-width='1.3' stroke-linejoin='round'/%3E%3C/svg%3E"); }
/* round 5, 3 Nov: on wide screens the drop and its count stand beside the answers so far, not above them, so the intro
   stays near the form's height (the list had made it 265 px taller than the form beside it, in the page and in the
   sheet). Narrow screens keep the drop and count stacked, and the chips carry the answers. */
.book__prog { margin-top: 36px; }
.book__prog .book__drop { margin-top: 0; }
@media (min-width: 961px) {
  .book__prog { display: grid; grid-template-columns: 76px minmax(0, 1fr); align-items: start; column-gap: 28px; }
  .book__prog > .bf-sum { margin-top: 0; }
}
/* one place for the answers at a time: beside the step when the intro stands beside the form, as chips when it does not */
@media (min-width: 961px) { .bf-chips { display: none !important; } }
@media (max-width: 960px) { .bf-sum { display: none !important; } }

/* the review: a hairline table, the step's name, its answers line by line, and its Change */
.bf-rev { margin-top: 22px; }
.bf-rev__list { margin: 0; border-top: 1px solid var(--hair); }
.bf-rev__row { display: grid; grid-template-columns: minmax(96px, 0.3fr) minmax(0, 1fr) auto; column-gap: 18px; align-items: start;
  padding: 16px 0; border-bottom: 1px solid var(--hair); }
.bf-rev__row dt { padding-top: 2px; font: 600 14.5px/1.5 var(--font-body); color: var(--ink-2); }
.bf-rev__row dd { margin: 0; display: grid; gap: 4px; font-size: 16.5px; line-height: 1.5; color: var(--ink); overflow-wrap: anywhere; }
.bf-rev__row .bf-rev__x { align-self: center; }
.bf-rev__none { color: var(--ink-2); }
.bf-rev__nojs { color: var(--ink-2); }
@container bf (max-width: 539px) {
  .bf-rev__row { grid-template-columns: minmax(0, 1fr) auto; row-gap: 4px; }
  .bf-rev__row dt, .bf-rev__row dd:not(.bf-rev__x) { grid-column: 1; }
  .bf-rev__row .bf-rev__x { grid-column: 2; grid-row: 1 / span 2; }
}

/* the street's suggestions: a card under the field, over the fields below it, so nothing moves when it opens */
.bf-addr__box { position: relative; }
.bf-addr__pop { position: absolute; left: 0; right: 0; top: calc(100% + 6px); z-index: 6; padding: 6px; border: 1px solid var(--hair);
  border-radius: 14px; background: #fff; box-shadow: 0 18px 40px -18px rgb(14 42 53 / 0.38), 0 2px 6px rgb(14 42 53 / 0.06); }
.bf-addr__list { list-style: none; margin: 0; padding: 0; }
.bf-addr__o { display: grid; gap: 2px; min-height: 48px; padding: 10px 12px; border-radius: 9px; cursor: pointer; }
.bf-addr__o b { font: 600 16px/1.3 var(--font-body); color: var(--ink); }
.bf-addr__o span { font-size: 14px; line-height: 1.35; color: var(--ink-2); }
.bf-addr__o[aria-selected="true"] { background: color-mix(in srgb, var(--spring) 10%, #fff); }
.bf-addr__by { margin: 4px 10px 2px; font-size: 12px; line-height: 1.4; color: var(--ink-2); text-align: right; }

/* the review's opt-ins and Jobber's SMS notice, word for word */
/* spacing pass: the legend is floated (.bf__subq), and a float's clearance swallowed the first box's 12 px margin, so
   it sat 0 px under "Updates" (measured; round 3's E11 hit the same with the pills). Each box now stands on padding,
   which clearance cannot eat: 14 px under the legend and between the two, as the pills stand under their questions.
   The text gets a size step and air between its lines; the block has more room above it than inside it. */
.bf-opts { margin-top: 34px; }
.bf-check { display: grid; grid-template-columns: 22px minmax(0, 1fr); column-gap: 14px; align-items: start; margin-top: 0;
  padding-top: 14px; font-size: 15.5px; line-height: 1.6; color: var(--ink); cursor: pointer; }
.bf-check input { width: 20px; height: 20px; margin: 3px 0 0; accent-color: var(--spring-ink); cursor: pointer; }
.bf-check input:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.bf__fine { margin-top: 24px; font-size: 13.5px; line-height: 1.65; color: var(--ink-2); }

/* ---------- sent: the close (round 3, E13, Ali: "a gorgeous form submission animation on the final step") ----------
   One calm sequence, about two seconds, compositor only: a clear glass on its stone coaster settles in half full; a
   drop falls into it (gravity's ease-in, a stretch as it falls, a squash as it meets the water), beads leap from the
   splash, the water rises to the brim while three rings spread across the surface on the release curve, the light the
   glass throws on the coaster brightens, a glint crosses the glass, and the thanks rises in as the rings settle; the
   drop in the intro has filled to its brim on the same beat. Played once (form.js adds is-play). Without motion, or
   before it plays, the scene rests: the full glass, the words in place. Nothing is sent by the prototype. */
.bf__done { text-align: center; }
.bf__splash { width: min(230px, 64%); margin: 10px auto 6px; }
.bf__splash svg { display: block; width: 100%; height: auto; overflow: visible; }
.bf__done .bf__q { text-align: center; }
.bf__done-p { padding-top: 16px; max-width: 44ch; margin-inline: auto; font-size: 18px; line-height: 1.6; color: var(--ink-2); }   /* padding: clearance under the floated heading swallows a margin */
.bf__done-row { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-top: 28px; }
.bf__done-x { display: none; }
.bs .bf__done-x { display: inline-flex; }
.bf__drop, .bf__ring, .bf__bead { opacity: 0; }
.bf__drop, .bf__ring, .bf__bead, .bf__glint, .bf__splash, .bf__caustic { transform-box: fill-box; transform-origin: 50% 50%; }
.bf__water { transform-box: fill-box; transform-origin: 50% 100%; }
.bf__ring { vector-effect: non-scaling-stroke; }
.bf__done.is-play .bf__splash { animation: bf-pool 520ms var(--ease-out) both; }
.bf__done.is-play .bf__drop { animation: bf-fall 640ms cubic-bezier(0.55, 0.05, 0.85, 0.35) 160ms both; }
.bf__done.is-play .bf__water { animation: bf-rise-water 980ms cubic-bezier(0.22, 1, 0.36, 1) 760ms both; }
.bf__done.is-play .bf__surface { animation: bf-rise-surface 980ms cubic-bezier(0.22, 1, 0.36, 1) 760ms both; }
.bf__done.is-play .bf__caustic { animation: bf-caustic 1200ms var(--ease-out) 900ms both; }
.bf__done.is-play .bf__ring { animation: bf-ring 1400ms cubic-bezier(0.16, 1, 0.3, 1) both; }
.bf__done.is-play .bf__ring--1 { animation-delay: 780ms; }
.bf__done.is-play .bf__ring--2 { animation-delay: 950ms; }
.bf__done.is-play .bf__ring--3 { animation-delay: 1140ms; }
.bf__done.is-play .bf__bead { animation: 560ms cubic-bezier(0.2, 0.6, 0.4, 1) 780ms both; }
.bf__done.is-play .bf__bead--1 { animation-name: bf-bead1; }
.bf__done.is-play .bf__bead--2 { animation-name: bf-bead2; animation-delay: 800ms; }
.bf__done.is-play .bf__bead--3 { animation-name: bf-bead3; animation-delay: 790ms; }
.bf__done.is-play .bf__bead--4 { animation-name: bf-bead4; animation-delay: 820ms; }
.bf__done.is-play .bf__glint { animation: bf-glint 1100ms var(--ease-in-out) 1420ms both; }
@keyframes bf-rise-water { from { transform: scaleY(0.626); } }
@keyframes bf-rise-surface { from { transform: translateY(25.8px); } }
@keyframes bf-caustic { from { opacity: 0.3; transform: scale(0.7, 0.8); } }
.bf__done.is-play :is(.bf__q, .bf__done-p, .bf__done-row) { animation: bf-rise 760ms var(--ease-out) both; }
.bf__done.is-play .bf__q { animation-delay: 980ms; }
.bf__done.is-play .bf__done-p { animation-delay: 1100ms; }
.bf__done.is-play .bf__done-row { animation-delay: 1220ms; }
@keyframes bf-pool { from { opacity: 0; transform: translateY(8px) scale(0.97); } }
@keyframes bf-fall {
  0% { opacity: 0; transform: translateY(-104px) scale(0.92, 1); }
  14% { opacity: 1; }
  86% { opacity: 1; transform: translateY(-5px) scale(0.9, 1.16); }
  100% { opacity: 0; transform: translateY(3px) scale(1.3, 0.42); }
}
@keyframes bf-ring { 0% { opacity: 0.95; transform: scale(0.06); } 65% { opacity: 0.45; } 100% { opacity: 0; transform: scale(1); } }
@keyframes bf-bead1 { 0% { opacity: 1; transform: none; } 45% { transform: translate(-9px, -17px); } 100% { opacity: 0; transform: translate(-14px, -2px); } }
@keyframes bf-bead2 { 0% { opacity: 1; transform: none; } 45% { transform: translate(8px, -20px); } 100% { opacity: 0; transform: translate(13px, -4px); } }
@keyframes bf-bead3 { 0% { opacity: 1; transform: none; } 45% { transform: translate(-3px, -24px); } 100% { opacity: 0; transform: translate(-5px, -8px); } }
@keyframes bf-bead4 { 0% { opacity: 1; transform: none; } 45% { transform: translate(12px, -11px); } 100% { opacity: 0; transform: translate(18px, 2px); } }
@keyframes bf-glint { 0% { opacity: 0; transform: translateX(-24px); } 40% { opacity: 0.5; } 100% { opacity: 0; transform: translateX(24px); } }
@keyframes bf-rise { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }

/* the glass of the close is a little larger on desktop, in the page card and in the sheet alike (round 3). The planted
   height that stood here (E10: the card held its tallest step so Back and Next stayed put for all five) gave way on
   3 Nov to Back and Next following each step: it had left them up to 570 px under a short step's answers. Within a step
   nothing moves still (errors and hints share one reserved line); between steps the height glides (form.js). */
@media (min-width: 961px) {
  [data-book-home] .bf__splash, .bs .bf__splash { width: min(270px, 64%); }
}
.bf__step.is-measure { display: block !important; position: absolute; left: 0; right: 0; top: 0; visibility: hidden; }

/* ---------- without JavaScript: every step, stacked, read top to bottom ---------- */
.no-js .bf__step + .bf__step:not(.bf__done) { margin-top: 48px; padding-top: 40px; border-top: 1px solid var(--hair); }

/* ---------- narrow forms (the phone, the sheet on a tablet): re-composed, never squeezed ---------- */
@container bf (max-width: 619px) { .bf__tiles { --cols: 3; } }
@container bf (max-width: 419px) { .bf__tiles { --cols: 2; --tg: 10px; } .bf-tile { padding: 16px 10px 14px; } }
@container bf (max-width: 539px) { .bf__fields { grid-template-columns: minmax(0, 1fr); } }

/* ---------- the book sheet ---------- */
html.bs-open { overflow: hidden; }
.bs { position: fixed; inset: 0; width: 100%; height: 100%; max-width: none; max-height: none; margin: 0; padding: 0;
  border: 0; background: transparent; color: var(--ink); overflow: clip; overscroll-behavior: contain; }
.bs:not([open]) { display: none; }
.bs::backdrop { background: transparent; }
.bs__scrim { position: absolute; inset: 0; background: rgb(14 42 53 / 0.4); }
.bs__card, .bs__shade { position: absolute; left: 0; right: 0; bottom: max(16px, env(safe-area-inset-bottom)); margin-inline: auto;
  width: min(1180px, calc(100% - 32px)); height: min(820px, calc(100% - 32px)); border-radius: 26px; }
.bs__shade { box-shadow: 0 30px 70px -24px rgb(14 42 53 / 0.5), 0 2px 8px rgb(14 42 53 / 0.1); pointer-events: none; }
.bs__card { display: flex; flex-direction: column; overflow: clip; background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--hair); }
.bs__x { position: absolute; top: 18px; right: 18px; z-index: 3; width: 44px; height: 44px; display: grid; place-items: center;
  padding: 0; border: 1px solid var(--bf-line); border-radius: 50%; background: rgb(255 255 255 / 0.75); color: var(--ink);
  transition: border-color var(--dur-quick) var(--ease-calm), transform var(--dur-std) var(--ease-out); }
.bs__x svg { width: 18px; height: 18px; }
.bs__x:focus-visible { border-radius: 50%; }
@media (hover: hover) and (pointer: fine) { .bs__x:hover { border-color: var(--ink); transform: rotate(90deg); } }
.bs__x:active { transform: scale(0.95); }
.bs__grip { display: none; }
/* the row is the card's height and no more (Ali, 3 Nov, over the sheet on a laptop: "getting cut off at the bottom, no
   scroll"): an auto row grew with the intro's answers-so-far list past the card, which clipped the intro's last rows
   and pushed Next under the card's edge. On a short screen the intro scrolls inside its column, keeping the step being
   answered in view (form.js), with room inside the scroller for focus rings. */
.bs__grid { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: minmax(0, 0.72fr) minmax(0, 1.28fr);
  grid-template-rows: minmax(0, 1fr);
  column-gap: clamp(36px, 4.4vw, 72px); padding: clamp(56px, 5vw, 68px) clamp(32px, 3.6vw, 56px) clamp(24px, 2.6vw, 36px); }
.bs__intro { min-width: 0; min-height: 0; overflow-y: auto; overscroll-behavior: contain; margin: -8px -10px -8px -8px;
  padding: 8px 10px 8px 8px; scrollbar-width: thin; scrollbar-color: rgb(22 34 48 / 0.24) transparent; }
.bs__title { font: 800 clamp(32px, 3.1vw, 48px)/1.02 var(--font-display); letter-spacing: -0.032em; text-wrap: balance; }
.bs__lead { margin-top: 18px; max-width: 34ch; font-size: 17.5px; line-height: 1.6; color: var(--ink-2); }
/* a short laptop screen (the sheet's card under 730 px): the title and lead a size down, so the first steps' list fits
   without the column scrolling (3 Nov) */
@media (min-width: 961px) and (max-height: 760px) {
  .bs__title { font-size: clamp(30px, 2.8vw, 40px); }
  .bs__lead { font-size: 16px; line-height: 1.55; }
}
.bs__panel { min-width: 0; min-height: 0; display: flex; flex-direction: column; }
.bs .bf { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
/* the steps take their own height with Back and Next right under them (3 Nov, as in the page); a step taller than the
   room left shrinks and scrolls, so the foot never leaves the card */
.bs .bf__steps { flex: 0 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; margin: -10px -12px 0;
  padding: 10px 12px 16px; scrollbar-width: thin; scrollbar-color: rgb(22 34 48 / 0.24) transparent; }
.bs .bf__steps.is-moving { overflow: clip; }
.bs .bf__foot { flex: none; margin-top: 10px; }
/* the close stands in the middle of the sheet's form area as it does in the page card (round 3, parent review: it sat
   at the top over an empty lower half). Auto margins, not justify-content: they give way to scrolling when the area is
   shorter than the scene, so its top is never cut off. */
.bs .bf[data-step="done"] .bf__steps { flex: 1 1 auto; display: flex; flex-direction: column; }
.bs .bf[data-step="done"] .bf__done { margin-block: auto; }

@media (max-width: 960px) {
  .bs__card, .bs__shade { width: min(760px, calc(100% - 32px)); height: min(900px, calc(100% - 32px)); }
  .bs__grid { grid-template-columns: minmax(0, 1fr); padding: 14px clamp(20px, 4vw, 36px) clamp(18px, 3vw, 28px); }
  .bs__intro { display: none; }
  .bs .bf__prog { display: flex; }
  .bs__x { top: 14px; right: 14px; }
}
@media (max-width: 760px) {
  .bs { --bs-gap: max(10px, env(safe-area-inset-top)); }
  .bs__card { top: calc(var(--bs-vt, 0px) + var(--bs-gap)); bottom: auto; width: 100%;
    height: calc(var(--bs-vh, 100%) - var(--bs-gap)); border-radius: 22px 22px 0 0; touch-action: pinch-zoom; }
  .bs__shade { display: none; }
  .bs__grid { padding: 16px 16px 0; }
  .bs .bf__steps { flex: 1 1 auto; touch-action: pan-y pinch-zoom; }   /* a phone keeps Back and Next at the thumb's foot */
  .bs .bf__foot { padding-bottom: max(14px, env(safe-area-inset-bottom)); }
  .bs__grip { display: block; position: absolute; top: 0; left: 50%; z-index: 2; width: 140px; height: 28px; margin-left: -70px;
    cursor: grab; }
  .bs__grip::before { content: ''; position: absolute; top: 8px; left: 50%; width: 40px; height: 5px; margin-left: -20px;
    border-radius: 3px; background: rgb(22 34 48 / 0.22); }
  .bs__x { top: 12px; right: 12px; }
}

/* ---------- reduced motion: instant swaps, nothing grows or slides, the water holds still ---------- */
@media (prefers-reduced-motion: reduce) {
  .bdrop__rise, .bf__mwater::before { transition-duration: 1ms; }
  .bdrop.is-live .bdrop__wave { animation: none; }
  .bf-tile, .bf-tile::after, .bf-tile__tick, .bf-tile__tick svg, .bf-seg__o, .bf-seg__o::after, .bf__in, .bs__x,
  .bf__send-l > span { transition-duration: 1ms; transition-delay: 0ms; }
  .js .bf__err:not([hidden]) { animation: none; }
  .bf__send[data-state="loading"] .bf__send-ring { animation-duration: 2.4s; }
  .bf__done.is-play *, .bf__done.is-play { animation: none !important; }
  .js .bf__say > *, .js .bf__field .bf__err { transition-duration: 1ms; }
}
