:root{
  --ink:#1F1F1F; --muted:#5E6468; --bg:#F7F9FC; --surface:#FFFFFF;
  --border:#E4E9F0;
  /* Drip accent — deviation from Brand System v1.0, approved by Chris 2026-08-02.
     Volta Green is retired on this page: the volta o in the footer is blue too.
     Rebased on the Mira Pro palette 2026-08-04. Mira and the old Drip ramp are the
     SAME hue family (214–218°) — Mira is simply less saturated — so this is a
     desaturation, not a hue change, and every rule below keeps working.
     #376FD0 = 4.58:1 on bg and 4.83:1 under white text: passes AA both ways. */
  --accent:#376FD0; --accent-dark:#295CAE; --accent-press:#224887;

  /* Drip blue ramp. Contrast measured, not estimated. 600 and 900 are pinned
     exactly to Mira (primary, sidebar navy); the rest follow its saturation curve.
     on #F7F9FC  — 600 = 4.58:1 (AA normal), 500 = 3.68:1 (large only), 700 = 6.79:1
     on #161F2C  — 200 = 11.40:1, 300 = 7.34:1, 400 = 5.15:1, 600 = 3.43:1 (NEVER body text on dark)
     white on 600 = 4.83:1, white on 950 = 16.58:1 */
  --blue-50:#F8F9FC;  --blue-100:#E1E8F4; --blue-200:#C9D7EE; --blue-300:#8EAEE1;
  --blue-400:#6290DA; --blue-500:#4D81D5; --blue-600:#376FD0; --blue-700:#295CAE;
  --blue-800:#224887; --blue-900:#233044; --blue-950:#161F2C; --blue-1000:#0D131C;
  --w-page:1120px; --w-headline:620px; --w-measure:680px; --w-narrow:440px; --w-statement:720px;
}
*{box-sizing:border-box;margin:0;padding:0}
html{-webkit-text-size-adjust:100%}
body{
  font-family:'Poppins',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
  background:var(--bg); color:var(--ink);
  font-size:16px; font-weight:400; line-height:1.65;
  -webkit-font-smoothing:antialiased;
}
.wrap{max-width:var(--w-page);margin:0 auto;padding:0 24px}
@media(min-width:600px){.wrap{padding:0 40px}}
@media(min-width:1024px){.wrap{padding:0 64px}}

h1,h2,h3{font-weight:500;letter-spacing:-.015em;text-wrap:balance}
h1{font-size:clamp(2.25rem,1.5rem + 3.2vw,3.75rem);line-height:1.06;letter-spacing:-.025em;max-width:var(--w-headline)}
h2{font-size:clamp(1.625rem,1.2rem + 1.2vw,2rem);line-height:1.20;max-width:var(--w-headline)}
h3{font-size:1.375rem;line-height:1.35;letter-spacing:-.01em}
p{text-wrap:pretty}
.lead{font-size:1.125rem;line-height:1.6;color:var(--muted);max-width:var(--w-narrow)}
.body{font-size:1rem;color:var(--muted);max-width:var(--w-measure)}
.eyebrow{font-size:.8125rem;font-weight:500;letter-spacing:.06em;color:var(--muted);line-height:1.4}
.cap{font-size:.8125rem;line-height:1.5;color:var(--muted)}

/* ---------- nav ---------- */
.nav{height:64px;display:flex;align-items:center}
@media(min-width:600px){.nav{height:72px}}
.nav-in{max-width:var(--w-page);margin:0 auto;padding:0 24px;width:100%;display:flex;align-items:center}
@media(min-width:600px){.nav-in{padding:0 40px}}
@media(min-width:1024px){.nav-in{padding:0 64px}}
.lockup{display:flex;align-items:center;text-decoration:none;color:var(--ink)}
.site-header .lockup{grid-column:1;justify-self:start}
.wordmark{font-weight:700;font-size:1.125rem;letter-spacing:-.01em;line-height:1}
@media(min-width:600px){.wordmark{font-size:1.25rem}}
.logo-o{color:var(--accent)}
/* Drip wordmark — Poppins Bold, the p in accent. Its bowl is the o-pattern,
   carried over from the volta mark. */
.dripmark{font-weight:700;font-size:1.375rem;letter-spacing:-.015em;line-height:1}
@media(max-width:599px){.dripmark{font-size:1.25rem}}
.drip-p{color:var(--accent)}
.nav-right{margin-left:auto}
a.tlink{font-size:.9375rem;font-weight:500;color:var(--ink);text-decoration:underline;text-underline-offset:4px;text-decoration-thickness:1px;transition:color .15s ease}
a.tlink:hover{color:var(--muted)}

/* ---------- sections ---------- */
section{padding-block:80px}
@media(min-width:600px){section{padding-block:112px}}
@media(min-width:1024px){section{padding-block:160px}}
/* top padding clears the fixed pill on every page that does not use the shot */
.hero{padding-block:132px 80px}
.hero.after-bleed{padding-top:56px}
@media(min-width:600px){.hero{padding-block:152px 112px}.hero.after-bleed{padding-top:72px}}
@media(min-width:1024px){.hero{padding-block:168px 160px}.hero.after-bleed{padding-top:88px}}
/* ---------- .tight: compressed page rhythm ----------
   Wraps one page's bands (currently /pricing). The shared `section` padding is
   160px top AND bottom at ≥1024, so consecutive bands sit 320px apart and the
   page reads as a stack of near-empty screens. ⚠ `.tight section` (0,1,1) also
   beats `.statement-sec` (0,1,0), so the statement gets its own rule below at
   (0,2,0) rather than inheriting the body figure. */
.tight section{padding-block:72px}
@media(min-width:600px){.tight section{padding-block:88px}}
@media(min-width:1024px){.tight section{padding-block:88px}}
.tight .statement-sec{padding-block:96px}
@media(min-width:1024px){.tight .statement-sec{padding-block:112px}}
/* the hero photo carries its own height — leave it out of the compression */
.tight .hero-shot{padding-block:160px}

/* a section landing straight under a `.hero-shot` — the photo already carries
   the top of the page, so the full 160px section padding reads as dead space */
/* ⚠ `.after-shot` is (0,1,0) and loses to `.tight section` (0,1,1), so inside a
   .tight page it needs the two-class form or the gap comes straight back */
.after-shot,.tight .after-shot{padding-top:48px}
@media(min-width:600px){.after-shot,.tight .after-shot{padding-top:56px}}
@media(min-width:1024px){.after-shot,.tight .after-shot{padding-top:72px}}
/* ⚠ the other half of that gap is `.tiers{margin-top:64px}`, which exists to
   clear an h2 — there is no h2 here, so it doubles up with the padding above */
.after-shot .tiers{margin-top:0}
.statement-sec{padding-block:112px}
@media(min-width:600px){.statement-sec{padding-block:144px}}
@media(min-width:1024px){.statement-sec{padding-block:200px}}

/* ---------- buttons ---------- */
.btn{
  display:inline-flex;align-items:center;justify-content:center;
  height:52px;padding-inline:28px;border-radius:8px;border:0;
  background:var(--accent);color:#FFFFFF;
  font-family:inherit;font-size:.9375rem;font-weight:500;line-height:1;
  text-decoration:none;cursor:pointer;transition:background .15s ease;
  width:100%;max-width:320px;
}
@media(min-width:600px){.btn{height:48px;width:auto;max-width:none}}
.btn:hover{background:var(--accent-dark)}
.btn:active{background:var(--accent-press)}
.cta-row{display:flex;flex-wrap:wrap;align-items:center;gap:24px}
a:focus-visible,button:focus-visible{outline:2px solid var(--ink);outline-offset:3px;box-shadow:0 0 0 6px rgba(23,92,211,.28);border-radius:8px}

/* ---------- full-bleed photo band ----------
   🚨 THE PHOTOGRAPHY RULE (Chris, 2026-08-02): "the image should be a full
   section, never a card", and "the section text should be over the image."
   A photograph on this site is one of exactly two things — a `.hero-shot`
   hero, or a `section(bg=)` band with the headline and copy sitting on it.
   It is never a rounded, shadowed rectangle floating inside `.wrap`; that
   reads as a card, and cards are for text. `.photo` below is kept flush and
   shadowless for the same reason. `.bleed` is the third, unused form — a bare
   edge-to-edge strip with no type on it — kept for a photo that has nothing
   to say over it. */
.bleed{margin:0;width:100%;line-height:0;background:var(--blue-950);overflow:hidden}
.bleed img{width:100%;height:clamp(300px,54vh,620px);object-fit:cover;object-position:center 62%;display:block}
@media(max-width:599px){.bleed img{height:300px;object-position:70% 60%}}
/* the band is decorative: if the file is absent the figure removes itself
   rather than leaving a broken-image box in the hero */
.bleed[hidden]{display:none}

/* ---------- hero ---------- */
.hero-grid{display:grid;gap:64px}
@media(min-width:900px){.hero-grid{grid-template-columns:repeat(12,1fr);gap:48px;align-items:center}
  .hero-type{grid-column:1/7}.hero-art{grid-column:8/13}}
.hero-type h1{margin-bottom:24px}
.hero-type .lead{margin-bottom:40px}
.status{display:flex;align-items:center;margin-top:64px;font-size:.8125rem;color:var(--ink)}
.dot{width:8px;height:8px;border-radius:50%;background:var(--accent);margin-right:8px;flex:none}
.hero-art svg{width:100%;height:auto;display:block}
@media(max-width:899px){
  .hero-art{max-height:360px;overflow:hidden}
  .hero-art svg{height:360px}
}

/* ---------- stats ---------- */
.stat-row{display:grid;gap:40px;margin-top:64px}
@media(min-width:900px){.stat-row{grid-template-columns:repeat(3,1fr);gap:48px}}
.stat .n{font-size:clamp(2.25rem,1.6rem + 1.8vw,3.25rem);font-weight:500;line-height:1.05;letter-spacing:-.02em;font-variant-numeric:tabular-nums}
.stat .l{font-size:.9375rem;color:var(--muted);margin-top:12px;max-width:26ch;line-height:1.6}
.sources{margin-top:40px;max-width:var(--w-measure)}
.pull{color:var(--ink)}
.dark .pull{color:#FFFFFF}

/* ---------- icons ---------- */
.ico{width:22px;height:22px;flex:none;fill:none;stroke:currentColor;stroke-width:1.5;
     stroke-linecap:round;stroke-linejoin:round}
.ico-sm{width:16px;height:16px;stroke-width:1.7}
.ico-lg{width:26px;height:26px;stroke-width:1.4}
.eyebrow{display:flex;align-items:center;gap:9px}
.eyebrow .ico{color:var(--accent)}
.catch .ico{color:var(--muted);margin-bottom:14px;display:block}
/* check lists replace the plain dot bullets */
.checks{list-style:none;display:grid;gap:14px}
.checks li{display:grid;grid-template-columns:20px 1fr;gap:11px;align-items:start;
           padding-left:0;font-size:.9375rem;color:var(--muted)}
.checks li::before{content:none}
.checks .ico{color:var(--accent);width:18px;height:18px;margin-top:3px;stroke-width:2}

/* ---------- dark bands ----------
   On #161F2C the accent itself is only 2.90:1, so it is never text here —
   type steps up to blue-200/300 and the button inverts to white. */
.dark{background:var(--blue-950)}
.dark h2,.dark h3,.dark .big{color:#FFFFFF}
.dark p,.dark .body,.dark li,.dark .lead{color:var(--blue-200)}
.dark .eyebrow{color:var(--blue-300)}
.dark .eyebrow .ico{color:var(--blue-400)}
.dark .checks .ico{color:var(--blue-400)}
.dark .checks li::before,.dark li::before{background:var(--blue-400)}
.dark figcaption,.dark .cap{color:var(--blue-300)}
.dark .stat .n{color:#FFFFFF}
.dark .stat .l{color:var(--blue-200)}
.dark .dl dt{color:#FFFFFF}
.dark .dl dd{color:var(--blue-200)}
.dark .btn{background:#FFFFFF;color:var(--blue-950)}
.dark .btn:hover{background:var(--blue-100)}
.dark .btn:active{background:var(--blue-200)}
.dark a.tlink{color:#FFFFFF}
.dark a.tlink:hover{color:var(--blue-300)}
.dark a:focus-visible,.dark button:focus-visible{outline-color:#FFFFFF;box-shadow:0 0 0 6px rgba(143,184,243,.32)}

/* ---------- product surfaces (flat UI) ---------- */
.ui{width:100%;height:auto;display:block}
.ui-portal{max-width:1000px;margin-inline:auto;border-radius:14px;
  box-shadow:0 2px 4px rgba(5,13,26,.28),0 24px 60px rgba(5,13,26,.42)}
.ui-app{max-width:320px;margin-inline:auto;
  filter:drop-shadow(0 14px 30px rgba(31,31,31,.13))}
/* ⚠ The phone is #0D131C on #161F2C — on a dark photo band it is dark-on-dark
   and the light-background shadow above does nothing. A 1px blue-300 rim gives
   it an edge against the scrim; the deeper shadow does the lifting. (0,2,0)
   beats `.ui-app` and sits after it, so it wins on both counts. */
.dark .ui-app{filter:drop-shadow(0 0 1px rgba(143,184,243,.45))
  drop-shadow(0 22px 44px rgba(5,13,26,.6))}

/* ---------- isometric figures ---------- */
figure{margin:0}
.iso{width:100%;height:auto;display:block}
.iso-wide{max-width:640px;margin-inline:auto}
figcaption{font-size:.8125rem;color:var(--muted);margin-top:20px;line-height:1.5;max-width:44ch}
/* captions are short: balance the lines so the last one is never a stray word,
   and never let the trailing link split mid-phrase */
figcaption{text-wrap:balance}
figcaption a.tlink{white-space:nowrap}
.split{display:grid;gap:56px;margin-top:64px;align-items:center}
@media(min-width:900px){.split{grid-template-columns:repeat(12,1fr);gap:56px}
  .split .art{grid-column:1/7}.split .txt{grid-column:7/13}
  .split.flip .art{grid-column:7/13;order:2}.split.flip .txt{grid-column:1/7;order:1}}
/* ⚠ `.photo` is GONE, and nothing may bring it back. It was the in-wrap photo
   treatment — width:100%, aspect-ratio:3/2, border-radius:16px and a drop
   shadow on dark — and that rounded, lifted rectangle is precisely the card
   look the photography rule bans. Both call sites (the homepage install shot
   and the /pricing apartment) are `section(bg=)` bands now. A photo goes
   full-bleed with the type over it, or it does not go on the page. */
.split ul{list-style:none;display:grid;gap:16px;margin-top:28px}
.split li{position:relative;padding-left:18px;font-size:1rem;color:var(--muted)}
.split li::before{content:"";position:absolute;left:0;top:11px;width:3px;height:3px;border-radius:50%;background:var(--ink)}
/* ⚠ Undoes `.split li`, which sits LATER in this file than `.dark li` and
   matches at the same specificity, so it silently won: the muted grey landed
   at 2.90:1 on blue-950. Keep this below `.split`. (The dot-plus-tick half of
   the same trap is handled by the guard at the foot of this file.) */
.dark .split li{color:var(--blue-200)}

/* ---------- how it works ---------- */
.hiw{display:grid;gap:64px;margin-top:64px}
@media(min-width:900px){.hiw{grid-template-columns:repeat(12,1fr);gap:48px;align-items:center}
  .hiw-art{grid-column:1/8}.hiw-steps{grid-column:8/13}}
.steps{display:grid;gap:40px}
@media(min-width:900px){.steps{gap:48px}}
.step .i{font-size:.8125rem;font-weight:500;color:var(--muted);font-variant-numeric:tabular-nums;margin-bottom:8px}
.step h3{margin-bottom:8px}
.step p{font-size:1rem;color:var(--muted);max-width:var(--w-narrow)}

/* ---------- statement ---------- */
.statement{max-width:var(--w-statement);margin:0 auto;text-align:center}
.statement .big{
  font-size:clamp(1.75rem,1.1rem + 2.4vw,2.75rem);
  font-weight:400;line-height:1.20;letter-spacing:-.02em;text-wrap:balance;
}
.consq{display:grid;gap:24px;margin-top:64px;text-align:left}
@media(min-width:600px){.consq{grid-template-columns:repeat(3,1fr);gap:48px;text-align:center}}
.consq p{font-size:.9375rem;color:var(--muted)}

/* ---------- definition list ---------- */
.dl{display:grid;gap:32px;margin-top:64px}
@media(min-width:600px){.dl{grid-template-columns:260px 1fr;gap:40px 48px}}
.dl dt{font-size:1.0625rem;font-weight:500;color:var(--ink)}
.dl dd{font-size:1rem;color:var(--muted);max-width:560px}

/* ---------- tiers ---------- */
.tiers{display:grid;gap:24px;margin-top:64px}
@media(min-width:900px){.tiers{grid-template-columns:1fr 1fr}}
/* the single package: one full-width card, its list in two columns rather than
   a lone half-width tier stranded beside empty space */
.tiers-one{grid-template-columns:1fr}
@media(min-width:900px){.tiers-one{grid-template-columns:1fr}}
/* three across on one row (the add-ons). ⚠ It has to beat the 900px `1fr 1fr`
   above, so the breakpoint is higher AND it comes later in the file. Below
   1024px it falls back to that two-up, then to one. */
@media(min-width:1024px){.tiers-3{grid-template-columns:repeat(3,1fr);gap:20px}
  /* the three bodies run to different lengths, so the CTA is pushed to the
     bottom of the card instead of floating wherever the copy happens to end */
  .tiers-3 .tier{padding:32px 28px;display:flex;flex-direction:column}
  .tiers-3 .tier > a.tlink{margin-top:auto;align-self:flex-start}}
@media(min-width:760px){.tier-wide .checks{grid-template-columns:repeat(2,1fr);gap:16px 48px}}
@media(min-width:760px){.tier-wide{padding:48px}}
/* two-up inside the wide tier: price on the left, what you get on the right.
   ⚠ Must come AFTER the 760px rule above — same specificity, later wins — or
   the checks stay two-across inside a half-width column and wrap to shreds. */
@media(min-width:900px){
  .tier-wide{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
    gap:56px;align-items:start}
  .tier-wide .checks{grid-template-columns:1fr;gap:14px;margin-bottom:0}
  /* the rule under the term chips divided price from checks when they stacked;
     side by side there is nothing left to divide */
  .tw-l .terms{border-bottom:0;padding-bottom:0;margin-bottom:0}
  .tw-r{display:flex;flex-direction:column}
  .tw-r > a.tlink{margin-top:28px;align-self:flex-start}
}
.tier{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:32px}
@media(min-width:600px){.tier{padding:40px}}
.avail{display:flex;align-items:center;font-size:.8125rem;font-weight:500;color:var(--muted);margin-bottom:20px}
.avail .d{width:8px;height:8px;border-radius:50%;margin-right:8px;flex:none}
.d.on{background:var(--accent)}
.d.off{background:var(--border)}
.tier h3{margin-bottom:12px}
.tier .desc{font-size:1rem;color:var(--muted);margin-bottom:32px}
.tier ul{list-style:none;margin-bottom:32px;display:grid;gap:12px}
.tier li{position:relative;padding-left:18px;font-size:.9375rem;color:var(--muted)}
.tier li::before{content:"";position:absolute;left:0;top:10px;width:3px;height:3px;border-radius:50%;background:var(--ink)}

/* ---------- strata ---------- */
.strata{max-width:var(--w-measure)}
.strata p{margin-top:24px;font-size:1rem;color:var(--muted)}
.strata p.ink{color:var(--ink)}
.strata ul{list-style:none;margin-top:40px;display:grid;gap:16px}
.strata li{position:relative;padding-left:18px;font-size:1rem;color:var(--muted)}
.strata li::before{content:"";position:absolute;left:0;top:11px;width:3px;height:3px;border-radius:50%;background:var(--ink)}
.strata .cta-row{margin-top:48px}

/* ---------- catches ---------- */
.catches{display:grid;gap:40px;margin-top:64px}
@media(min-width:600px){.catches{grid-template-columns:repeat(2,1fr);gap:40px 48px}}
@media(min-width:900px){.catches{grid-template-columns:repeat(3,1fr)}}
.catch h3{font-size:1.0625rem;margin-bottom:8px}
.catch p{font-size:.9375rem;color:var(--muted)}

/* ---------- questions (accordion, /faq only) ---------- */
/* Native details/summary: keyboard, screen readers and in-page find all work
   without JS, and the answers still render if drip.js never loads. */
.qs{margin:64px auto 0;max-width:var(--w-measure);border-top:1px solid var(--border)}
.q{border-bottom:1px solid var(--border)}
.q>summary{
  display:flex;align-items:baseline;justify-content:space-between;gap:24px;
  padding:24px 0;cursor:pointer;list-style:none;
}
/* the h3 stays inside the summary so the page keeps its heading outline */
.q>summary h3{font-size:1.0625rem;font-weight:500;line-height:1.35;text-wrap:balance}
.q>summary::-webkit-details-marker{display:none}
.q>summary::marker{content:''}
.q>summary:hover,.q[open]>summary{color:var(--accent)}
.q>summary:focus-visible{outline:2px solid var(--ink);outline-offset:3px;
  box-shadow:0 0 0 6px rgba(23,92,211,.28);border-radius:8px}
/* the + rotates 45deg into an x — no icon file, no shadow, matches the art direction */
.q-mark{position:relative;flex:0 0 auto;width:14px;height:14px;align-self:center;
  transition:transform .16s ease,color .16s ease;color:var(--muted)}
.q-mark::before,.q-mark::after{content:'';position:absolute;inset:50% 0 auto 0;
  height:1.5px;background:currentColor;border-radius:1px}
.q-mark::after{transform:rotate(90deg)}
.q>summary:hover .q-mark,.q[open] .q-mark{color:var(--accent)}
.q[open] .q-mark{transform:rotate(45deg)}
.q-body{padding-bottom:28px}
.q-body p{font-size:1rem;color:var(--muted)}
.q[open] .q-body{animation:q-in .16s ease-out}
@keyframes q-in{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
@media(prefers-reduced-motion:reduce){
  .q-mark{transition:none}
  .q[open] .q-body{animation:none}
}

/* centred page header — /faq only, every other hero stays left */
.hero-centered{text-align:center}
.hero-centered h1,.hero-centered .lead{margin-inline:auto}
.hero-centered .eyebrow{justify-content:center}

/* ---------- footer ---------- */
footer{border-top:1px solid var(--border);padding-block:48px 40px}
@media(min-width:600px){footer{padding-block:64px 48px}}
.foot{display:grid;gap:40px}
@media(min-width:760px){.foot{grid-template-columns:minmax(220px,1fr) auto;gap:56px;align-items:start}}
.foot .legal{margin-top:16px;max-width:420px}
.foot-blurb{font-size:.9375rem;color:var(--muted);margin-top:14px;max-width:34ch}
/* grouped columns, not one long list */
.foot-cols{display:grid;gap:32px 40px;grid-template-columns:repeat(2,minmax(0,1fr))}
@media(min-width:760px){.foot-cols{grid-template-columns:repeat(3,auto);gap:0 clamp(40px,6vw,88px)}}
.foot-col h5{font-size:.75rem;font-weight:500;letter-spacing:.06em;color:var(--ink);margin-bottom:14px}
.foot-col{display:grid;gap:11px;align-content:start}
.foot-col a{font-size:.8125rem;color:var(--muted);text-decoration:none;transition:color .15s ease}
.foot-col a:hover{color:var(--accent)}

/* ---------- drawings ---------- */
.slab,.trunk,.conn,.bracket{fill:none;stroke:var(--border);stroke-width:1}
.node{fill:var(--surface);stroke:var(--muted);stroke-width:1}
.hub-ring{fill:var(--bg);stroke:var(--ink);stroke-width:1.5}
.hub-core{fill:var(--ink)}
.hub-base{stroke:var(--border);stroke-width:1;fill:none}
.pulse{fill:none;stroke:var(--accent);stroke-width:1.5;stroke-linecap:round;stroke-dasharray:24 1000}

@keyframes pulse{0%{stroke-dashoffset:24}12.5%{stroke-dashoffset:-1000}100%{stroke-dashoffset:-1000}}
@keyframes lit{
  0%{fill:var(--surface);stroke:var(--muted);stroke-width:1}
  2%{fill:var(--accent);stroke:var(--accent);stroke-width:2.5}
  10%{fill:var(--accent);stroke:var(--accent);stroke-width:2.5}
  14%{fill:var(--surface);stroke:var(--muted);stroke-width:1}
  100%{fill:var(--surface);stroke:var(--muted);stroke-width:1}
}
@keyframes hubflash{
  0%,11.5%{stroke:var(--ink)} 12.5%{stroke:var(--accent)}
  13.6%,28.2%{stroke:var(--ink)} 29.2%{stroke:var(--accent)}
  30.3%,44.8%{stroke:var(--ink)} 45.8%{stroke:var(--accent)}
  46.9%,61.5%{stroke:var(--ink)} 62.5%{stroke:var(--accent)}
  63.6%,78.2%{stroke:var(--ink)} 79.2%{stroke:var(--accent)}
  80.3%,94.8%{stroke:var(--ink)} 95.8%{stroke:var(--accent)}
  96.9%,100%{stroke:var(--ink)}
}
#stack .pulse{animation:pulse 14.4s linear infinite backwards;animation-delay:calc(var(--i) * 2.4s)}
#stack .node.anim{animation:lit 14.4s linear infinite backwards;animation-delay:calc(var(--i) * 2.4s)}
#stack .hub-ring{animation:hubflash 14.4s linear infinite}


@media(prefers-reduced-motion:reduce){
  #stack .pulse,#stack .node.anim,#stack .hub-ring{animation:none}
  #stack .pulse{stroke-dasharray:none;stroke-dashoffset:0;opacity:.55}
  .rm-lit{fill:var(--accent);stroke:var(--accent);stroke-width:2.5}
}

/* ================= multi-page shell: volta-home-site style nav ================= */
/* Floating pill, ported from volta-home-site's SiteHeader: clear over a dark
   full-bleed hero, glass pill once scrolled, and ALWAYS ON SCREEN.
   ⚠ The port's hide-on-scroll-down was removed (Chris, 2026-08-03: "the nav
   should always stay fixed") along with `.is-hidden`, its transform transition
   and the JS that set it. Nothing here should reintroduce a transform on the
   header — the pill is a fixed bar, not a revealing one. */
.site-header{position:fixed;top:0;left:0;right:0;z-index:60;padding-top:14px}
.nav-shell{max-width:var(--w-page);margin:0 auto;padding:0 16px}
@media(min-width:600px){.nav-shell{padding:0 24px}}
/* three columns, not flex: the 1fr rails split the leftover space evenly so the
   links sit dead centre whatever the wordmark and CTA measure. Columns are
   assigned explicitly — .header-nav is display:none below 940px, and with auto
   placement the actions would slide into column 2 instead of the right edge. */
.site-header .nav-in{width:max-content;min-width:min(820px,100%);max-width:100%;margin:0 auto;
  height:62px;padding:0 19px;display:grid;grid-template-columns:1fr auto 1fr;
  align-items:center;gap:clamp(20px,4vw,48px);
  background:rgba(255,255,255,.88);backdrop-filter:saturate(180%) blur(16px);
  -webkit-backdrop-filter:saturate(180%) blur(16px);
  border:1px solid var(--border);border-radius:14px;
  box-shadow:0 1px 2px rgba(31,31,31,.04),0 10px 26px rgba(31,31,31,.07);
  transition:background-color .3s ease,border-color .3s ease,box-shadow .3s ease}
/* ⚠ The pill's padding must stay SYMMETRIC — the 1fr rails centre the links on
   the content box, so any left/right imbalance shows up as an off-centre nav.
   The wordmark and CTA get their different insets from margins inside the
   rails instead, which do not move the middle column. */
@media(min-width:600px){.site-header .nav-in{height:66px;padding:0 20px}}
.site-header .lockup{margin-left:7px}
.site-header .header-actions{margin-right:-7px}
@media(min-width:600px){.site-header .lockup{margin-left:12px}
  .site-header .header-actions{margin-right:-6px}}

/* Clear over the hero photo — links go white, so only pass is-transparent on a
   page whose first screen is the dark full-bleed shot. */
.site-header.is-transparent::before{content:"";position:absolute;inset:0 0 auto;height:150px;
  z-index:-1;pointer-events:none;transition:opacity .3s ease;
  background:linear-gradient(180deg,rgba(6,14,26,.42) 0%,rgba(6,14,26,.26) 55%,rgba(6,14,26,0) 100%)}
.site-header.is-transparent.is-scrolled::before{opacity:0}
.site-header.is-transparent .nav-in{background:transparent;border-color:transparent;box-shadow:none;
  backdrop-filter:none;-webkit-backdrop-filter:none}
.site-header.is-transparent .lockup,
.site-header.is-transparent .header-nav a,
.site-header.is-transparent .nav-login,
.site-header.is-transparent .nav-dropdown-toggle{color:#fff}
.site-header.is-transparent .header-nav a:hover,
.site-header.is-transparent .nav-login:hover,
.site-header.is-transparent .nav-dropdown-toggle:hover{color:var(--blue-200)}
/* the accent fails as type on dark (2.90:1) — the mark steps up to blue-400 */
.site-header.is-transparent .drip-p{color:var(--blue-400)}
.site-header.is-transparent .nav-toggle span{background:#fff}
.site-header.is-transparent.is-scrolled .nav-in{background:rgba(255,255,255,.88);
  border-color:var(--border);box-shadow:0 1px 2px rgba(31,31,31,.04),0 10px 26px rgba(31,31,31,.07);
  backdrop-filter:saturate(180%) blur(16px);-webkit-backdrop-filter:saturate(180%) blur(16px)}
.site-header.is-transparent.is-scrolled .lockup,
.site-header.is-transparent.is-scrolled .header-nav a,
.site-header.is-transparent.is-scrolled .nav-login,
.site-header.is-transparent.is-scrolled .nav-dropdown-toggle{color:var(--ink)}
.site-header.is-transparent.is-scrolled .drip-p{color:var(--accent)}
.site-header.is-transparent.is-scrolled .nav-toggle span{background:var(--ink)}
.header-nav{display:none;align-items:center;gap:26px;grid-column:2;justify-self:center}
@media(min-width:940px){.header-nav{display:flex}}
.header-nav a,.nav-dropdown-toggle{font-size:.9375rem;font-weight:500;color:var(--ink);
  text-decoration:none;background:none;border:0;font-family:inherit;cursor:pointer;
  padding:0;display:inline-flex;align-items:center;gap:6px;transition:color .15s ease}
.header-nav a:hover,.nav-dropdown-toggle:hover{color:var(--muted)}
.nav-chev{width:6px;height:6px;border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;
  transform:rotate(45deg) translate(-1px,-1px);transition:transform .15s ease}
.nav-dropdown{position:relative}
.nav-dropdown-menu{position:absolute;top:calc(100% + 14px);left:-16px;min-width:216px;
  background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:8px;
  display:grid;gap:2px;opacity:0;visibility:hidden;transform:translateY(-4px);
  transition:opacity .15s ease,transform .15s ease,visibility .15s;
  box-shadow:0 1px 2px rgba(31,31,31,.05),0 12px 28px rgba(31,31,31,.08)}
.nav-dropdown:hover .nav-dropdown-menu,.nav-dropdown.open .nav-dropdown-menu{opacity:1;visibility:visible;transform:none}
.nav-dropdown:hover .nav-chev,.nav-dropdown.open .nav-chev{transform:rotate(225deg) translate(-2px,-2px)}
.nav-dropdown-menu a{display:block;padding:9px 12px;border-radius:8px;font-weight:400}
.nav-dropdown-menu a:hover{background:var(--blue-50);color:var(--ink)}
.header-actions{grid-column:3;justify-self:end;display:flex;align-items:center;gap:14px}
/* Sign-in is a secondary text link, never a second filled button — the quote
   button has to stay the one primary action in the bar. Hidden below 940px
   alongside .btn-sm, because the hamburger carries it there instead. */
.nav-login{display:none;font-size:.9375rem;font-weight:500;color:var(--ink);text-decoration:none;
  transition:color .15s ease;white-space:nowrap}
@media(min-width:940px){.nav-login{display:inline-flex;align-items:center}}
.nav-login:hover{color:var(--muted)}
.btn-sm{height:40px;padding-inline:18px;font-size:.875rem;width:auto;max-width:none;display:none}
@media(min-width:940px){.btn-sm{display:inline-flex}}
.nav-toggle{width:40px;height:40px;border:0;background:none;cursor:pointer;display:grid;
  align-content:center;gap:5px;justify-items:center;padding:0}
@media(min-width:940px){.nav-toggle{display:none}}
.nav-toggle span{width:20px;height:1.5px;background:var(--ink);border-radius:2px;transition:transform .2s ease,opacity .2s ease}
.nav-toggle[aria-expanded="true"] span:first-child{transform:translateY(3.25px) rotate(45deg)}
.nav-toggle[aria-expanded="true"] span:last-child{transform:translateY(-3.25px) rotate(-45deg)}
.nav-menu{display:none;flex-direction:column;gap:2px;margin-top:10px;padding:12px 20px 18px;
  background:var(--surface);border:1px solid var(--border);border-radius:14px;
  box-shadow:0 10px 30px rgba(31,31,31,.10)}
.nav-menu.open{display:flex}
@media(min-width:940px){.nav-menu,.nav-menu.open{display:none}}
.nav-menu a{padding:13px 0;font-size:1.0625rem;font-weight:500;color:var(--ink);text-decoration:none;
  border-bottom:1px solid var(--border)}
/* the mobile CTA is a <button> — it opens the address modal, it is not a link */
.nav-menu a.btn,.nav-menu button.btn{margin-top:18px;border-bottom:0;color:#fff;justify-content:center}
.nav-menu button.btn{width:100%;max-width:none}

/* ================= hero with the photo behind the type ================= */
.hero-shot{position:relative;isolation:isolate;min-height:clamp(520px,76vh,760px);
  display:flex;align-items:flex-end;background:var(--blue-950);overflow:hidden}
.hero-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center 58%;z-index:-2}
.hero-scrim{position:absolute;inset:0;z-index:-1;
  background:linear-gradient(to top,rgba(6,14,26,.90) 0%,rgba(6,14,26,.74) 34%,rgba(6,14,26,.34) 64%,rgba(6,14,26,.14) 100%)}
.hero-copy{padding-block:64px 72px;width:100%}
@media(min-width:900px){.hero-copy{padding-block:96px 88px}}
.hero-shot h1{color:#fff;max-width:18ch;text-wrap:balance}
.hero-shot .lead{color:#E6EDF6;max-width:56ch;margin-top:22px}
.hero-shot .eyebrow{color:var(--blue-300)}
.hero-shot .cta-row{margin-top:36px}
a.tlink.onshot{color:#fff}
a.tlink.onshot:hover{color:var(--blue-300)}
/* ⚠ Below 600px `.hero-shot` collapses to min-height:0, so the copy starts at
   its own padding — and the fixed pill is 76px tall (14 offset + 62 bar), which
   ate the first line of every h1 on a shot hero. 108 clears it with air. */
@media(max-width:599px){.hero-shot{min-height:0;padding-top:0}.hero-img{object-position:68% 58%}
  .hero-copy{padding-block:108px 72px}}

/* ================= a whole band on a photo =================
   Same recipe as the hero, one level down: the photo is full-bleed behind the
   band and a scrim carries it. ⚠ Only ever used with `.dark` — the scrim is
   dark, so light type is the only readable option on top of it. The scrim is
   lighter than the hero's on purpose: a hero has to hold an h1, a band only
   has to hold body copy and cards that bring their own backing. */
.has-bg{position:relative;isolation:isolate;overflow:hidden;background:var(--blue-950)}
.sec-bg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  object-position:center 58%;z-index:-2}
/* Heavy at the top where the headline and body copy sit straight on the photo,
   then it opens up over the card zone — the cards carry their own 80% backing,
   so the picture is allowed to show through there. */
.sec-scrim{position:absolute;inset:0;z-index:-1;
  background:linear-gradient(to bottom,rgba(6,14,26,.88) 0%,rgba(6,14,26,.80) 30%,
    rgba(6,14,26,.56) 52%,rgba(6,14,26,.64) 100%)}
@media(max-width:659px){.sec-scrim{background:rgba(6,14,26,.86)}}
/* ⚠ The gradient above opens to .56 halfway down because cards bring their own
   backing there. A band whose TYPE runs straight on the photo top to bottom
   needs a floor instead: measured against a white blowout in the photo, .56
   puts blue-200 body copy at 3.68:1 — under AA. At .78 it is 5.9:1 and white
   headlines are 8.2:1, so this variant never drops below .78. */
.scrim-copy .sec-scrim{background:linear-gradient(to bottom,rgba(6,14,26,.90) 0%,
  rgba(6,14,26,.84) 40%,rgba(6,14,26,.78) 100%)}
/* ⚠ Same specificity trap as everywhere else in this file: `.scrim-copy
   .sec-scrim` is (0,2,0), so it beats the (0,1,0) mobile rule above no matter
   that the media query comes first. The small screen needs its own two-class
   form or it silently keeps the desktop gradient. */
@media(max-width:659px){.scrim-copy .sec-scrim{background:rgba(6,14,26,.88)}}

/* ================= page bits ================= */
.card .tag{font-size:.75rem;font-weight:500;color:var(--accent);background:var(--blue-50);
  padding:4px 10px;border-radius:99px;align-self:flex-start;margin-bottom:14px}
.price{margin-top:4px;margin-bottom:26px;font-size:1.0625rem;font-weight:500;color:var(--ink)}
.price span{display:block;font-size:.875rem;font-weight:400;color:var(--muted);margin-top:4px}
.dark .price{color:#fff}
.dark .price span{color:var(--blue-200)}
.tier .checks{margin-bottom:28px}
.dark .nav-dropdown-menu a:hover{background:var(--blue-900)}

/* ============ image placeholders — asymmetric radius, deliberately not Moen ============ */
.ph{position:relative;aspect-ratio:4/5;background:var(--blue-50);
  border:1px solid var(--blue-100);border-radius:14px;
  display:flex;flex-direction:column;justify-content:flex-end;padding:24px;overflow:hidden}
.ph::before{content:"";position:absolute;inset:0;
  background:repeating-linear-gradient(-45deg,transparent 0 13px,rgba(23,92,211,.055) 13px 26px)}
.ph .ph-tag{position:relative;font-size:.6875rem;font-weight:500;color:var(--accent);
  letter-spacing:.06em;margin-bottom:8px}
.ph .ph-cap{position:relative;font-size:.9375rem;font-weight:500;color:var(--ink);line-height:1.35}
.dark .ph{background:var(--blue-900);border-color:var(--blue-800)}
.dark .ph::before{background:repeating-linear-gradient(-45deg,transparent 0 13px,rgba(143,184,243,.07) 13px 26px)}
.dark .ph .ph-cap{color:#fff}
.dark .ph .ph-tag{color:var(--blue-300)}
.ph-wide{aspect-ratio:16/10;border-radius:16px}

.phgrid{display:grid;gap:20px;margin-top:56px}
@media(min-width:640px){.phgrid{grid-template-columns:repeat(2,1fr)}}
@media(min-width:1000px){.phgrid{grid-template-columns:repeat(3,1fr)}}
.phgrid .item p{font-size:.9375rem;color:var(--muted);margin-top:16px}
.dark .phgrid .item p{color:var(--blue-200)}
.phgrid .item h4{font-size:1.0625rem;font-weight:500;margin-top:20px}
.dark .phgrid .item h4{color:#fff}

/* ============ feature grid ============ */
.feats{display:grid;gap:40px 48px;margin-top:56px}
@media(min-width:640px){.feats{grid-template-columns:repeat(2,1fr)}}
@media(min-width:1000px){.feats{grid-template-columns:repeat(3,1fr)}}
/* four items: 2x2, then 1x4 — never 3 + a widow */
@media(min-width:1000px){.feats-4{grid-template-columns:repeat(4,1fr);gap:40px 36px}}
.feat h4{font-size:1.0625rem;font-weight:500;margin-bottom:8px}
.feat p{font-size:.9375rem;color:var(--muted)}
.dark .feat h4{color:#fff}
.dark .feat p{color:var(--blue-200)}
.feat .ico{color:var(--accent);margin-bottom:14px;display:block}
.dark .feat .ico{color:var(--blue-400)}

/* ============ comparison table ============ */
.cmp-scroll{overflow-x:auto;margin-top:56px;border:1px solid var(--border);border-radius:14px;background:var(--surface)}
.cmp{width:100%;border-collapse:collapse;font-size:.9375rem;min-width:620px}
.cmp th{text-align:left;font-weight:500;font-size:.8125rem;color:var(--muted);padding:18px 20px;
  border-bottom:1px solid var(--border);white-space:nowrap}
.cmp th.ours{color:var(--accent)}
.cmp td{padding:16px 20px;border-bottom:1px solid var(--border);color:var(--muted)}
.cmp tr:last-child td{border-bottom:0}
.cmp td:first-child{color:var(--ink)}
.cmp .yes{color:var(--accent);font-weight:500}
.cmp .no{color:#B9C0C6}
.cmp col.ours,.cmp td.ours{background:var(--blue-50)}

/* ============ industries ============ */
/* 2x2 from tablet up, single stack on mobile. ⚠ Deliberately no 4-across rule:
   Chris set these to two columns 2026-08-02 so the copy keeps a real measure. */
.ind-grid{display:grid;gap:20px;margin-top:56px}
@media(min-width:660px){.ind-grid{grid-template-columns:repeat(2,1fr)}}
@media(min-width:1080px){.ind-grid{gap:24px}}
.ind{display:flex;flex-direction:column;text-decoration:none;color:inherit;
  background:var(--surface);border:1px solid var(--border);
  border-radius:14px;overflow:hidden}
/* 🚨 No hover state on these (Chris, 2026-08-02). The lift, the accent border
   and the arrow nudge are all gone — the card sits still. They are still
   links, so `:focus-visible` (the shared rule up top) is the only state left,
   and it must stay: removing hover cannot take keyboard focus with it. */
.ind-body{padding:26px 24px 28px;display:flex;flex-direction:column;flex:1}
.ind-name{font-size:.75rem;font-weight:500;letter-spacing:.06em;color:var(--accent);margin-bottom:12px}
.ind h4{font-size:1.1875rem;font-weight:500;line-height:1.3;letter-spacing:-.01em;margin-bottom:12px;text-wrap:balance}
.ind p{font-size:.9375rem;color:var(--muted);flex:1}
.ind .go{margin-top:22px;font-size:.9375rem;font-weight:500;color:var(--ink);
  display:inline-flex;align-items:center;gap:7px}
.ind .go::after{content:"";width:5px;height:5px;border-right:1.5px solid currentColor;
  border-bottom:1.5px solid currentColor;transform:rotate(-45deg)}
/* text-only cards sitting on a `.has-bg` photo band. ⚠ The 80% tint is not
   cosmetic: measured against the worst case (a white blowout in the photo
   directly behind a card) it holds blue-300 at 4.9:1 and blue-200 at 7.2:1.
   Thin it out and the 12px label drops below AA. */
.dark .ind{background:rgba(8,20,38,.80);border-color:rgba(143,184,243,.26);
  backdrop-filter:blur(10px) saturate(115%);-webkit-backdrop-filter:blur(10px) saturate(115%)}
.dark .ind-name{color:var(--blue-300)}
.dark .ind h4{color:#FFFFFF}
.dark .ind p{color:var(--blue-200)}
.dark .ind .go{color:#FFFFFF}
/* the parts-grid glyph, at card scale. The rule over there is "line art at
   roughly 5x the heading" — the heading here is 19px, so 88/100px, not the
   132/156px a full parts row takes. */
.ind .pg-icon{--pg-w:88px;margin-bottom:20px}
@media(min-width:1080px){.ind .pg-icon{--pg-w:100px}}
/* 🚨 HALO OFF, BOTH SURFACES. On .parts the halo is a disc of the BAND colour
   that punches a hole through any stroke running behind the badge. These cards
   are their own surface — a translucent glass tint over a photo on `/`, and
   --surface on /industries — so a disc of the band colour prints as a patch
   instead of disappearing. The four industry glyphs are drawn clear of the
   badge corner instead, which is the only thing the halo was cutting.
   ⚠ The second selector is not redundant: `.dark .pg-halo` down in the parts
   block is also (0,2,0) and sits LATER in the file, so a lone `.ind .pg-halo`
   loses on the dark band. Same trap as the dot-plus-tick one. */
.ind .pg-halo,.dark .ind .pg-halo{fill:none}
/* Measured against the worst case behind the card (a white blowout in the
   photo, so the 80% tint composites to #394351): blue-400 line art lands at
   3.52:1 and blue-300 at 4.92:1. Both clear the 3:1 floor for a graphic, but
   the glyph is the same weight of information as the label above it, so it
   takes the label's colour. The mark inside the badge is blue-950 on blue-300
   at 8.5:1. */
.dark .ind .pg-icon{color:var(--blue-300)}
.dark .ind .pg-badge{fill:var(--blue-300)}

.ind-home{margin-top:20px;background:var(--blue-50);border:1px solid var(--blue-100);
  border-radius:14px;padding:32px 28px;display:flex;flex-wrap:wrap;
  align-items:center;gap:20px 32px;text-decoration:none;color:inherit;transition:border-color .18s ease}
.ind-home:hover{border-color:var(--accent)}
.ind-home .lbl{font-size:.75rem;font-weight:500;letter-spacing:.06em;color:var(--accent)}
.ind-home h4{font-size:1.1875rem;font-weight:500;letter-spacing:-.01em;margin-top:6px}
.ind-home p{font-size:.9375rem;color:var(--muted);max-width:52ch;margin-top:6px}
.ind-home .go{margin-left:auto;font-size:.9375rem;font-weight:500;color:var(--accent);white-space:nowrap}
@media(max-width:659px){.ind-home .go{margin-left:0}}

/* ============ scoping: three steps side by side on desktop ============ */
.steps-row{display:grid;gap:32px;margin-top:56px}
@media(min-width:820px){.steps-row{grid-template-columns:repeat(3,1fr);gap:40px}}
.steps-row .step{background:transparent;padding:0}
.steps-row .step .i{display:inline-flex;align-items:center;justify-content:center;
  width:38px;height:38px;border-radius:50%;border:1.5px solid var(--border);
  font-size:.8125rem;font-weight:500;color:var(--muted);margin-bottom:18px}
.dark .steps-row .step .i{border-color:var(--blue-800);color:var(--blue-300)}
.steps-row .step h3{font-size:1.1875rem;margin-bottom:10px}
.dark .steps-row .step h3{color:#fff}
.steps-row .step p{font-size:.9375rem;color:var(--muted);max-width:34ch}
.dark .steps-row .step p{color:var(--blue-200)}

/* ============ partners ============ */
.ptypes{display:grid;gap:20px;margin-top:56px}
@media(min-width:700px){.ptypes{grid-template-columns:repeat(2,1fr)}}
@media(min-width:1080px){.ptypes{grid-template-columns:repeat(3,1fr)}}
.ptype{background:var(--surface);border:1px solid var(--border);
  border-radius:14px;padding:30px 28px;display:flex;flex-direction:column}
.dark .ptype{background:var(--blue-900);border-color:var(--blue-800)}
.ptype .ico{color:var(--accent);margin-bottom:16px;display:block}
.dark .ptype .ico{color:var(--blue-400)}
.ptype h4{font-size:1.125rem;font-weight:500;letter-spacing:-.01em;margin-bottom:10px}
.dark .ptype h4{color:#fff}
.ptype p{font-size:.9375rem;color:var(--muted);margin-bottom:20px}
.dark .ptype p{color:var(--blue-200)}
.ptype ul{list-style:none;display:grid;gap:10px;margin-top:auto}
.ptype li{position:relative;padding-left:16px;font-size:.875rem;color:var(--muted);line-height:1.5}
.dark .ptype li{color:var(--blue-200)}
.ptype li::before{content:"";position:absolute;left:0;top:9px;width:3px;height:3px;
  border-radius:50%;background:var(--accent)}
.dark .ptype li::before{background:var(--blue-400)}
.applybox{background:var(--surface);border:1px solid var(--border);
  border-radius:16px;padding:44px 36px;margin-top:56px;max-width:var(--w-measure)}
.applybox h3{margin-bottom:12px}
.applybox p{font-size:1rem;color:var(--muted);margin-bottom:14px}
.applybox .fields{display:grid;gap:10px;margin:26px 0 30px}
.applybox .fields span{font-size:.875rem;color:var(--muted);padding-left:16px;position:relative}
.applybox .fields span::before{content:"";position:absolute;left:0;top:9px;width:3px;height:3px;
  border-radius:50%;background:var(--border)}

/* ================= headline price =================
   The big number on /packages. ⚠ It is accent blue on the light surface only —
   measured 5.99:1 on #FFFFFF and 5.79:1 on #F7F9FC. On the navy canvas the
   accent collapses to 2.90:1, so `.dark` steps the figure to blue-400 (6.12:1).
   Weight stays 500: Poppins 700 is reserved for the wordmark. */
.price-hero{margin:8px 0 22px}
.price-hero .from{display:block;font-size:.9375rem;color:var(--muted);margin-bottom:4px}
.price-hero .amt{display:inline-flex;align-items:flex-start;color:var(--accent);
  font-weight:500;line-height:.92;letter-spacing:-.035em;
  font-size:clamp(3.25rem,2rem + 5.5vw,5rem)}
.price-hero .cur{font-size:.36em;line-height:1;margin-top:.22em;margin-right:.03em;letter-spacing:-.01em}
.price-hero .cents{font-size:.42em;line-height:1;margin-top:.17em;letter-spacing:-.01em}
.price-hero .per{display:block;font-size:1rem;color:var(--muted);margin-top:14px}
.dark .price-hero .amt{color:var(--blue-400)}
.dark .price-hero .from,.dark .price-hero .per{color:var(--blue-200)}
.price-note{font-size:.9375rem;color:var(--muted);max-width:58ch;margin-bottom:26px}
.dark .price-note{color:var(--blue-200)}

/* term chips — 12 / 24 / 36 months. Accent on blue-50 is 5.52:1, the same
   pairing `.card .tag` already uses. */
.terms{display:flex;flex-wrap:wrap;align-items:center;gap:12px 18px;
  margin-bottom:32px;padding-bottom:32px;border-bottom:1px solid var(--border)}
.terms-l{font-size:.8125rem;font-weight:500;letter-spacing:.06em;color:var(--muted)}
.term-pills{display:flex;flex-wrap:wrap;gap:8px}
.term{font-size:.875rem;font-weight:500;color:var(--accent);background:var(--blue-50);
  border:1px solid var(--blue-100);border-radius:99px;padding:6px 14px}
.dark .terms{border-bottom-color:rgba(143,184,243,.26)}
.dark .terms-l{color:var(--blue-300)}
.dark .term{color:var(--blue-200);background:rgba(143,184,243,.12);
  border-color:rgba(143,184,243,.26)}

/* simple / transparent / trusted */
.trio{display:grid;gap:24px;margin-top:56px}
@media(min-width:760px){.trio{grid-template-columns:repeat(3,1fr)}}
.tcard{background:var(--surface);border:1px solid var(--border);
  border-radius:12px;padding:32px 28px}
.tcard h4{font-size:1.1875rem;font-weight:500;letter-spacing:-.01em;margin-bottom:12px}
.tcard p{font-size:.9375rem;color:var(--muted)}
.dark .tcard{background:rgba(8,20,38,.80);border-color:rgba(143,184,243,.26)}
.dark .tcard h4{color:#FFFFFF}
.dark .tcard p{color:var(--blue-200)}

/* ================= last word: no dot on a tick =================
   ⚠ Five separate blocks (.split, .tier, .strata, .ptype, …) each add their own
   dot bullet with `<scope> li::before`, all at the same specificity as
   `.checks li::before{content:none}` up at line 131 and all BELOW it, so every
   `checks()` list nested inside one of them rendered a dot AND a tick. This
   guard has to stay the last rule in the file — anything appended after it that
   sets `li::before{content}` will bring the dots back. */
.checks li::before{content:none}

/* ============================================================
   Hero + transparent-nav corrections
   ============================================================ */

/* The wordmark p keeps the brand accent in every header state.
   The transparent state previously stepped it up to blue-400, which
   reads as washed-out light blue against the bright top of the photo. */
.site-header.is-transparent .drip-p{color:var(--accent)}

/* Give the floating nav a firmer bed so white "Dri" and the accent p
   both hold against a light ceiling in the photo. */
.site-header.is-transparent::before{
  height:190px;
  background:linear-gradient(to bottom,rgba(6,14,26,.62) 0%,rgba(6,14,26,.30) 55%,rgba(6,14,26,0) 100%);
}

/* ---- mobile hero ---- */
@media(max-width:599px){
  /* a deliberate, viewport-proportioned hero instead of a content-height box */
  /* Sized to the copy plus breathing room rather than the viewport, and centred.
     Bottom-aligning a tall hero on a phone leaves a dead slab of ceiling
     between the nav and the headline. */
  .hero-shot{min-height:min(72vh,560px);display:flex;align-items:center}
  /* bias the crop to the flooded floor so the copy sits on darker pixels */
  .hero-img{object-position:72% 68%}
  /* the photo is bright behind the subhead, so the scrim has to carry it */
  .hero-scrim{background:linear-gradient(to top,
      rgba(6,14,26,.95) 0%,rgba(6,14,26,.90) 38%,rgba(6,14,26,.74) 68%,rgba(6,14,26,.46) 100%)}
  .hero-copy{padding-block:104px 40px}
  .hero-shot h1{max-width:14ch}
  .hero-shot .lead{margin-top:16px;font-size:1rem;max-width:34ch}
  .hero-shot .cta-row{margin-top:28px}
}

/* ============================================================================
   THE INTAKE FLOW — hero address bar, the "Have questions" modal, /quote, /advisor
   ============================================================================
   Three surfaces share this block because two of them appear on every page:
   the address bar opens the flow from the homepage hero, and the modal is in
   the nav sitewide. Only the `.flow-*` layout is exclusive to /quote.

   ⚠ The palette rule holds throughout: the accent is 5.79:1 on light and
   2.90:1 on the navy canvas, so on anything dark (the hero, the glass modal,
   the flow background) the accent is never type and the button inverts to
   white with blue-950 on it.
   ⚠ Nothing below may set `li::before{content:…}` — that would step over the
   no-dot-on-a-tick guard above, which has to keep winning. */

/* ---------- glass ----------
   Two stacked layers, the same recipe as volta's: a white tint to lift the
   panel off the photo, then a dark wash to pull it back down. Either alone
   fails — white alone goes milky over a bright crop, dark alone goes muddy. */
:root{
  --glass-tint:rgba(255,255,255,.13);
  --glass-wash:rgba(6,14,26,.42);
  --glass-line:rgba(255,255,255,.22);
  --glass-blur:20px;
}
.glass{
  background:linear-gradient(var(--glass-tint),var(--glass-tint)),var(--glass-wash);
  border:1px solid var(--glass-line);
  backdrop-filter:blur(var(--glass-blur)) saturate(140%);
  -webkit-backdrop-filter:blur(var(--glass-blur)) saturate(140%);
}
/* Without backdrop-filter the panel would be near-transparent with white type
   sitting straight on the photo. Fall back to solid navy. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .glass{background:rgba(10,26,51,.92)}
}

/* ---------- hero address bar ---------- */
.addrbar{margin-top:36px;max-width:560px}
.addrbar-row{display:flex;gap:10px;padding:10px;border-radius:16px;position:relative}
@media(max-width:599px){.addrbar-row{flex-direction:column;padding:12px}}
.addrbar .fieldwrap{flex:1;min-width:0;position:relative;display:flex;align-items:center}
.addrbar .pin{position:absolute;left:14px;color:var(--blue-200);pointer-events:none}
.addrbar input{
  width:100%;height:52px;padding:0 16px 0 44px;border:0;border-radius:10px;
  background:rgba(255,255,255,.10);color:#fff;
  font-family:inherit;font-size:1rem;font-weight:400;line-height:1;
}
.addrbar input::placeholder{color:rgba(230,237,246,.72)}
.addrbar input:focus{outline:none;background:rgba(255,255,255,.17)}
.addrbar input:focus-visible{box-shadow:0 0 0 2px #fff}
.addrbar .btn{height:52px;flex:none;max-width:none;width:auto;padding-inline:24px}
@media(max-width:599px){.addrbar .btn{width:100%}}
.addrbar .hint{font-size:.8125rem;color:var(--blue-200);margin-top:12px;padding-left:2px}
.addrbar .hint.err{color:#F87171}

/* ⚠⚠ The suggestion menu is LIGHT in both places, and that is a correction.
   It first took the glass material of the bar it drops out of, on the argument
   that a white menu under a glass bar reads as a different application. That
   was wrong, and measurably so:

     dark glass over the scrimmed hero   white text 15.46:1, panel/backdrop 1.21:1
     dark glass over a white blowout     white text  2.83:1

   Two separate failures. Against a bright frame of the photo the menu's own
   text collapsed to 2.83:1; and even where the text was fine, the panel was
   within 1.21:1 of the hero behind it, so it never read as a surface at all —
   the hint line underneath showed straight through the rows. A menu is not
   decoration, it is the thing being read.

   Solid white: ink 16.48:1, the secondary line 6.00:1 (AA at any size), and
   17.9:1 of separation from the hero. It is also now ONE material instead of
   two, so the same component looks the same on the hero and inside the flow
   card.

   ⚠ OPAQUE, and not by accident — a first pass kept 3% translucency plus the
   backdrop blur "for a hint of glass", and the `.hint` line sitting behind the
   panel smeared up through the first row. A menu cannot have anything showing
   through it. No backdrop-filter here either: with an opaque background it
   blurs nothing, and it would needlessly make this a stacking context. */
.ac-menu{
  position:absolute;top:calc(100% + 8px);left:0;right:0;z-index:20;
  border-radius:14px;padding:6px;display:none;max-height:302px;overflow-y:auto;
  background:var(--surface);border:1px solid var(--border);
  box-shadow:0 2px 6px rgba(5,13,26,.20),0 22px 54px rgba(5,13,26,.42);
}
.ac-menu.open{display:block}
.ac-menu button{
  display:block;width:100%;text-align:left;border:0;background:none;cursor:pointer;
  font-family:inherit;font-size:.9375rem;line-height:1.45;color:var(--ink);
  padding:11px 13px;border-radius:9px;
}
.ac-menu button:hover,.ac-menu button[aria-selected="true"]{background:var(--blue-50)}
.ac-menu .ac-sub{display:block;font-size:.8125rem;color:var(--muted);margin-top:2px}
.ac-menu .ac-note{padding:11px 13px;font-size:.875rem;color:var(--muted)}

/* ---------- "Have questions" — nav button + modal ---------- */
.hq-btn{
  display:inline-flex;align-items:center;gap:7px;height:40px;padding-inline:14px;
  border:1px solid var(--border);border-radius:99px;background:var(--surface);
  font-family:inherit;font-size:.875rem;font-weight:500;color:var(--ink);
  cursor:pointer;white-space:nowrap;transition:border-color .15s ease,background .15s ease;
}
.hq-btn:hover{border-color:var(--accent);background:var(--blue-50)}
.hq-btn .ico{color:var(--accent)}
@media(max-width:939px){.hq-btn .hq-label{display:none}
  .hq-btn{padding-inline:0;width:40px;justify-content:center}}
/* over the photo hero the pill is glass, like everything else up there */
.site-header.is-transparent:not(.is-scrolled) .hq-btn{
  background:rgba(255,255,255,.12);border-color:rgba(255,255,255,.34);color:#fff;
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)}
.site-header.is-transparent:not(.is-scrolled) .hq-btn .ico{color:#fff}
.site-header.is-transparent:not(.is-scrolled) .hq-btn:hover{background:rgba(255,255,255,.22)}

.hq-modal{border:0;padding:0;background:none;max-width:none;max-height:none;
  width:100%;height:100%;overflow:hidden}
.hq-modal::backdrop{background:rgba(6,14,26,.62);
  backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}
.hq-modal:not([open]){display:none}
.hq-shell{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;padding:24px}
.hq-panel{
  width:100%;max-width:420px;border-radius:20px;padding:32px 30px 30px;position:relative;
  box-shadow:0 30px 70px rgba(5,13,26,.55);text-align:left;
}
.hq-panel .hq-brand{font-weight:700;font-size:1.25rem;letter-spacing:-.015em;line-height:1;color:#fff}
.hq-panel .hq-brand span{color:var(--blue-400)}
.hq-panel h3{color:#fff;margin-top:18px;font-size:1.375rem;line-height:1.3}
.hq-panel p{color:var(--blue-200);font-size:.9375rem;margin-top:10px;max-width:34ch}
.hq-panel .btn{background:#fff;color:var(--blue-950);margin-top:26px;width:100%;max-width:none}
.hq-panel .btn:hover{background:var(--blue-100)}
.hq-panel .btn:active{background:var(--blue-200)}
.hq-panel .hq-alt{display:block;margin-top:16px;font-size:.875rem;color:var(--blue-200);
  text-decoration:underline;text-underline-offset:4px}
.hq-panel .hq-alt:hover{color:#fff}
.hq-close{position:absolute;top:14px;right:14px;width:36px;height:36px;border:0;border-radius:50%;
  background:rgba(255,255,255,.12);color:#fff;cursor:pointer;display:grid;place-items:center;
  font-family:inherit;font-size:1.125rem;line-height:1}
.hq-close:hover{background:rgba(255,255,255,.22)}

/* ---------- the address modal — the nav CTA's target ----------
   Same dialog as "Have questions", one size up, with the hero's address bar
   dropped in. The bar arrives carrying hero spacing and a 560px cap, and the
   panel's own `.btn` rule would stretch its submit to full width, so both are
   unwound here rather than forked in the markup. */
.addr-panel{max-width:520px}
.addr-panel .addrbar{margin-top:24px;max-width:none}
.addr-panel .addrbar-row{padding:8px}
.addr-panel .addrbar .btn{width:auto;max-width:none;margin-top:0;height:52px;
  background:#fff;color:var(--blue-950)}
@media(max-width:599px){.addr-panel .addrbar .btn{width:100%}}
.addr-panel .addrbar .btn:hover{background:var(--blue-100)}
.addr-panel .addrbar .hint{color:var(--blue-200)}
.hq-panel .btn:focus-visible,.hq-close:focus-visible,.hq-panel .hq-alt:focus-visible{
  outline-color:#fff;box-shadow:0 0 0 6px rgba(143,184,243,.32)}

/* ============================================================================
   /quote — the four-step flow
   ============================================================================ */
/* The fixed `.flow-head` is out of flow; this is the height `.flow-main` has
   to clear. 22/24/26 padding top and bottom around a 40px button. */
.flow{--flow-head-h:84px;min-height:100vh;min-height:100dvh;position:relative;isolation:isolate;
  display:flex;flex-direction:column;background:var(--blue-950)}
/* fixed, so the photo holds still while a long step scrolls over it */
.flow-bg{position:fixed;inset:0;width:100%;height:100%;object-fit:cover;
  object-position:center 58%;z-index:-2}
.flow-scrim{position:fixed;inset:0;z-index:-1;
  background:linear-gradient(to right,rgba(6,14,26,.90) 0%,rgba(6,14,26,.78) 46%,rgba(6,14,26,.62) 100%)}
@media(max-width:899px){.flow-scrim{background:rgba(6,14,26,.84)}}

/* the flow's own header — wordmark and one button, no site nav */
/* Fixed, like the marketing pill — the nav stays put at every scroll position
   (Chris, 2026-08-03). ⚠ It is out of flow, so `.flow-main` carries a top
   padding of `--flow-head-h` to sit under it; change one and change the other.
   The ::before is not decoration: the white card scrolls UNDER this bar on a
   phone, and without a scrim the wordmark lands on white. */
.flow-head{position:fixed;top:0;left:0;right:0;z-index:40;
  display:flex;align-items:center;justify-content:space-between;
  gap:16px;padding:22px 24px;max-width:1240px;margin:0 auto;width:100%}
@media(min-width:900px){.flow-head{padding:26px 40px}}
.flow-head .lockup{text-decoration:none}
.flow-head .dripmark{color:#fff}
.flow-head .drip-p{color:var(--blue-400)}
.flow-head .hq-btn{background:rgba(255,255,255,.12);border-color:rgba(255,255,255,.30);color:#fff;
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)}
.flow-head .hq-btn .ico{color:#fff}
.flow-head .hq-btn:hover{background:rgba(255,255,255,.22);border-color:rgba(255,255,255,.5)}
.flow-head::before{content:"";position:fixed;top:0;left:0;right:0;height:calc(var(--flow-head-h) + 28px);
  z-index:-1;pointer-events:none;
  background:linear-gradient(to bottom,rgba(6,14,26,.94) 0%,rgba(6,14,26,.74) 52%,rgba(6,14,26,0) 100%)}

.flow-main{flex:1;width:100%;max-width:1240px;margin:0 auto;padding:16px 24px 72px}
@media(min-width:900px){.flow-main{padding:40px 40px 80px}}
.flow-grid{display:grid;gap:36px;align-items:start}
@media(min-width:960px){.flow-grid{grid-template-columns:1fr 1fr;gap:64px;align-items:center;
  min-height:calc(100dvh - 220px)}}

/* --- left column: the rail and the headline --- */
.flow-copy{display:flex;gap:24px;align-items:flex-start}
.flow-copy h1{color:#fff;max-width:13ch}
.flow-copy .lead{color:var(--blue-200);margin-top:24px;max-width:42ch;font-size:1.0625rem}
@media(max-width:959px){
  .flow-copy{flex-direction:column;gap:0}
  .flow-copy h1{font-size:clamp(2rem,1.3rem + 3vw,2.75rem);max-width:16ch}
  .flow-copy .lead{margin-top:16px;font-size:1rem}
}

.rail{display:flex;flex-direction:column;align-items:center;padding-top:10px;flex:none}
@media(max-width:959px){.rail{flex-direction:row;padding-top:0;margin-bottom:22px}}
.rail .node{width:15px;height:15px;border-radius:50%;background:rgba(255,255,255,.24);
  flex:none;transition:background .25s ease,transform .25s ease}
.rail .node.done{background:var(--blue-400)}
.rail .node.now{background:#fff;transform:scale(1.15)}
.rail .link{width:2px;height:56px;background:rgba(255,255,255,.24)}
@media(max-width:959px){.rail .link{width:40px;height:2px}}
.rail .link.done{background:var(--blue-400)}
.rail-count{display:none}
@media(max-width:959px){.rail-count{display:block;font-size:.8125rem;color:var(--blue-300);
  margin-bottom:14px;letter-spacing:.02em}}

/* --- the address chip, from step 2 on --- */
.flow-chip{display:flex;align-items:center;gap:16px;border-radius:14px;padding:14px 18px;
  margin-bottom:16px;font-size:.9375rem;color:#fff;line-height:1.45}
.flow-chip .lbl{color:var(--blue-200)}
.flow-chip button{margin-left:auto;flex:none;background:none;border:0;cursor:pointer;
  font-family:inherit;font-size:.9375rem;color:#fff;text-decoration:underline;
  text-underline-offset:4px;padding:0}
.flow-chip button:hover{color:var(--blue-300)}
.flow-chip[hidden]{display:none}

/* --- right column: the card --- */
.flow-card{background:var(--surface);border-radius:24px;padding:28px 24px;
  box-shadow:0 2px 6px rgba(5,13,26,.18),0 28px 64px rgba(5,13,26,.34)}
@media(min-width:600px){.flow-card{padding:36px 34px}}
.flow-card .step-pane{display:grid;gap:14px}
.flow-card h2{font-size:1.25rem;margin-bottom:2px}
.flow-card .sub{font-size:.9375rem;color:var(--muted);margin-top:-4px;margin-bottom:6px}

/* --- fields: the label floats up out of the way once there is content --- */
.fld{position:relative}
.fld input{
  width:100%;height:64px;padding:22px 18px 6px;border:1px solid var(--border);border-radius:12px;
  background:var(--surface);color:var(--ink);font-family:inherit;font-size:1rem;line-height:1.2;
  transition:border-color .15s ease,background .15s ease;
}
.fld input:hover{border-color:#D3DAD7}
.fld input:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(23,92,211,.16)}
.fld label{
  position:absolute;left:19px;top:50%;transform:translateY(-50%);pointer-events:none;
  font-size:1rem;color:var(--muted);transform-origin:left top;
  transition:transform .15s ease,font-size .15s ease,color .15s ease;
}
.fld input:focus + label,
.fld input:not(:placeholder-shown) + label{transform:translateY(-22px);font-size:.75rem}
.fld input:focus + label{color:var(--accent)}
/* auto-filled from the address lookup — tinted so it reads as "we did this" */
.fld.prefilled input{background:var(--blue-50);border-color:var(--blue-100)}
.fld-note{display:flex;align-items:flex-start;gap:8px;font-size:.8125rem;color:var(--muted);
  margin-top:-4px;line-height:1.45}
.fld-note .ico{color:var(--accent);width:15px;height:15px;margin-top:2px}
.fld-row{display:grid;gap:14px}
@media(min-width:520px){.fld-row{grid-template-columns:1fr 1fr}}
.fld input[aria-invalid="true"]{border-color:#C81E1E}
.form-err{font-size:.8125rem;color:#C81E1E;line-height:1.45}
.form-err[hidden]{display:none}

/* --- condensed address: the read-back line and its disclosure --- */
.addr-brief{font-size:.9375rem;color:var(--ink);line-height:1.5;margin-top:-2px;
  padding:12px 16px;background:var(--blue-50);border:1px solid var(--blue-100);border-radius:12px}
.addr-brief[hidden]{display:none}
.addr-toggle{display:inline-flex;align-items:center;gap:8px;align-self:flex-start;
  background:none;border:0;padding:2px 0;cursor:pointer;font-family:inherit;
  font-size:.875rem;font-weight:500;color:var(--accent);text-align:left}
.addr-toggle:hover{color:var(--accent-dark)}
.addr-toggle .chev{width:7px;height:7px;flex:none;
  border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;
  transform:rotate(45deg) translate(-1px,-1px);transition:transform .15s ease}
.addr-toggle[aria-expanded="true"] .chev{transform:rotate(225deg) translate(-2px,-2px)}
.addr-more{display:grid;gap:14px}
.addr-more[hidden]{display:none}

/* --- choice cards: property type, shut-off --- */
.picks{display:grid;gap:12px}
@media(min-width:560px){.picks.two-up{grid-template-columns:1fr 1fr}}
.pick-card{position:relative;display:flex;gap:13px;align-items:flex-start;cursor:pointer;
  border:1px solid var(--border);border-radius:14px;padding:16px 18px;background:var(--surface);
  transition:border-color .15s ease,background .15s ease}
.pick-card:hover{border-color:var(--blue-300);background:var(--blue-50)}
.pick-card input{position:absolute;opacity:0;width:1px;height:1px;margin:0}
.pick-card .mark{width:20px;height:20px;border-radius:50%;border:1.5px solid #C6CEDA;flex:none;
  margin-top:2px;display:grid;place-items:center;transition:border-color .15s ease}
.pick-card .mark::after{content:"";width:10px;height:10px;border-radius:50%;background:var(--accent);
  transform:scale(0);transition:transform .15s ease}
.pick-card .t{font-size:.9375rem;font-weight:500;color:var(--ink);line-height:1.4}
.pick-card .d{font-size:.8125rem;color:var(--muted);margin-top:3px;line-height:1.5}
/* `.is-on` is set by quote.js and does the real work; `:has()` is the
   no-JS-repaint path. Both, so neither browser nor script order can strand a
   radio with no visible selected state. */
.pick-card:has(input:checked),.pick-card.is-on{border-color:var(--accent);background:var(--blue-50)}
.pick-card:has(input:checked) .mark,.pick-card.is-on .mark{border-color:var(--accent)}
.pick-card:has(input:checked) .mark::after,.pick-card.is-on .mark::after{transform:scale(1)}
.pick-card:has(input:focus-visible){outline:2px solid var(--ink);outline-offset:3px;
  box-shadow:0 0 0 6px rgba(23,92,211,.28)}

/* the unit count, revealed under the picks for a multi-unit property */
.units{display:grid;gap:10px;margin-top:4px}
.units[hidden]{display:none}

/* --- card footer --- */
.flow-actions{display:flex;align-items:center;gap:14px;margin-top:22px}
.flow-actions .btn{flex:1;max-width:none;width:auto;height:56px;border-radius:12px;
  justify-content:space-between;padding-inline:22px;font-size:1rem}
.flow-actions .btn .arrow{width:18px;height:18px;flex:none}
.flow-actions .btn:disabled{background:var(--blue-100);color:#7C8AA0;cursor:not-allowed}
.flow-back{flex:none;height:56px;padding-inline:18px;border:1px solid var(--border);border-radius:12px;
  background:var(--surface);font-family:inherit;font-size:.9375rem;font-weight:500;color:var(--ink);
  cursor:pointer;transition:border-color .15s ease,background .15s ease}
.flow-back:hover{border-color:var(--accent);background:var(--blue-50)}
.flow-legal{font-size:.75rem;color:var(--muted);margin-top:16px;line-height:1.5}

/* ============================================================================
   /advisor — the end of the flow
   ============================================================================ */
.sumcard{background:var(--surface);border:1px solid var(--border);border-radius:18px;
  padding:28px 26px;max-width:var(--w-measure);margin-top:48px}
.sumcard h3{font-size:1.125rem;margin-bottom:4px}
.sumcard > p{font-size:.9375rem;color:var(--muted)}
.sumlist{display:grid;gap:0;margin-top:18px}
.sumlist > div{display:flex;flex-wrap:wrap;gap:8px 20px;padding:13px 0;
  border-bottom:1px solid var(--border);font-size:.9375rem}
.sumlist > div:last-child{border-bottom:0;padding-bottom:0}
.sumlist dt{color:var(--muted);min-width:150px}
.sumlist dd{color:var(--ink);font-weight:500;flex:1}
.sumcard .edit{margin-top:22px;display:inline-flex}
.sumcard.is-empty .sumlist,.sumcard.is-empty .edit{display:none}

/* ⚠ The nav pill is `width:max-content` over `1fr auto 1fr`, and the two rails
   are forced to the same width. Adding the "Have questions" pill pushed the
   actions past the rail the grid had sized for them, and the CTA — a flex item
   with the default `flex-shrink:1` — absorbed it by wrapping to two lines.
   Buttons never wrap and never shrink; the label goes instead. */
.btn,.hq-btn{white-space:nowrap}
.header-actions > *{flex:none}
/* icon-only until there is genuinely room for both labels beside the links */
@media(max-width:1119px){.site-header .hq-btn .hq-label{display:none}
  .site-header .hq-btn{padding-inline:0;width:40px;justify-content:center}}

/* ⚠⚠ `[hidden]` is a UA rule at zero-ish specificity, so ANY class that sets
   `display` beats it. `.step-pane{display:grid}` did exactly that and every
   one of the four steps rendered at once, stacked, on first load. Every
   display-setting class in the flow needs its own hidden guard. */
.step-pane[hidden],[data-copy][hidden],.flow-chip[hidden],.units[hidden],
.flow-back[hidden],.form-err[hidden]{display:none}

/* the two lines of a choice card are spans inside a span — without this they
   run together as one sentence: "HouseA detached home, townhouse or duplex." */
.pick-card .t,.pick-card .d{display:block}

/* The scrim was tuned for type sitting straight on the photo; here the type
   has a card beside it and the photo is doing the work of telling you this is
   still Drip. Opened up so the skyline actually reads. */
.flow-scrim{background:linear-gradient(to right,
  rgba(6,14,26,.86) 0%,rgba(6,14,26,.72) 38%,rgba(6,14,26,.46) 100%)}
@media(max-width:899px){.flow-scrim{background:rgba(6,14,26,.80)}}

/* ⚠ Chris, 2026-08-03: the intake header must sit on the same measure as the
   rest of the site. It was on a 1240 container with its own padding, so the
   wordmark landed further left than anything else on the site. `--w-page` and
   the `.wrap` padding scale are the site's measure — use them, not a number. */
.flow-head,.flow-main{max-width:var(--w-page)}
.flow-head{padding:22px 24px}
.flow-main{padding:calc(var(--flow-head-h) + 16px) 24px 72px}
@media(min-width:600px){.flow{--flow-head-h:88px}
  .flow-head{padding:24px 40px}.flow-main{padding:calc(var(--flow-head-h) + 24px) 40px 80px}}
@media(min-width:1024px){.flow{--flow-head-h:92px}
  .flow-head{padding:26px 64px}.flow-main{padding:calc(var(--flow-head-h) + 32px) 64px 80px}}

/* ============================================================================
   HOMEPAGE — the display scale
   Chris, 2026-08-03: bold headings, crisp subtitles. Everything below is
   scoped to a `.display` wrapper the same way `.tight` is scoped to /pricing,
   so the homepage gets the bigger scale and no other page moves. Promoting it
   sitewide is deleting the `.display ` prefix from the four type rules;
   nothing else here is page-specific.
   ========================================================================= */

/* ---------- type ----------
   ⚠ 600 has to stay in the Poppins request in head(). Without it the browser
   synthesises a fake semibold by smearing the 500, and every headline on the
   page goes soft at exactly the size that is meant to carry it. */
.display h1,.display h2{font-weight:600;letter-spacing:-.028em}
.display h2{font-size:clamp(1.875rem,1.15rem + 2.4vw,2.875rem);line-height:1.10;max-width:19ch}
.display h3,.display h4{font-weight:600}
/* the deck under a headline: one step up from `.body` and on a shorter measure,
   so it reads as a subtitle rather than as the first paragraph. Colour is left
   to `.body` / `.dark .body` — setting it here would beat the dark override. */
.display section > .wrap > .body{font-size:1.125rem;line-height:1.55;max-width:52ch}

/* ---------- the price card ----------
   A comparison table in Drip's material: a glass panel with the usual
   way struck out beside what the number actually covers.
   🚨 Flat navy band, never a photo one. Composited on #161F2C the glass
   surface lands at #283344 — white 12.74:1, blue-200 9.15:1, blue-400 (the
   price) 4.49:1. Run the same panel over a photo band and a white blowout in
   the crop takes those to 6.07 / 4.36 / 2.14, and the price number fails even
   the 3:1 large-text floor. That is why this section takes no `bg`. */
.pricecard{border-radius:20px;padding:26px 20px;margin-top:48px;max-width:840px}
@media(min-width:700px){.pricecard{padding:40px;margin-top:56px}}
.pc-row{display:grid;gap:10px;padding:18px 0;border-top:1px solid rgba(255,255,255,.15)}
.pc-row:first-child{border-top:0;padding-top:0}
@media(min-width:760px){.pc-row{grid-template-columns:1.05fr 1fr 1fr;gap:28px;align-items:baseline;padding:15px 0}}
.pc-l{font-size:.9375rem;font-weight:500;color:#fff}
.pc-o,.pc-n{font-size:.9375rem;line-height:1.5}
.pc-o{color:var(--blue-300)}
.pc-n{color:#fff;font-weight:500}
/* the strike is the whole point of the column — it has to be visible on navy
   without turning the words into a blur, so it is thin and one step lighter
   than the text it crosses. */
.pc-o s{text-decoration:line-through;text-decoration-thickness:1px;
  text-decoration-color:rgba(143,184,243,.75)}
/* small-screen row keys. The header row carries these from 760px up, so they
   are only ever shown when it is gone. */
.pc-k{display:block;font-size:.6875rem;font-weight:500;letter-spacing:.07em;
  text-transform:uppercase;color:var(--blue-300);margin-bottom:3px}
@media(min-width:760px){.pc-k{display:none}}
.pc-head{display:none}
@media(min-width:760px){.pc-head{display:grid;padding-bottom:14px}
  .pc-head div{font-size:.6875rem;font-weight:500;letter-spacing:.07em;
    text-transform:uppercase;color:var(--blue-300)}
  .pc-head .pc-hn{color:#fff}}
.pc-total{display:grid;gap:6px;margin-top:26px;padding-top:26px;
  border-top:1px solid rgba(255,255,255,.28)}
@media(min-width:760px){.pc-total{grid-template-columns:1.05fr 1fr 1fr;gap:28px;align-items:center;margin-top:8px}}
.pc-tl{font-size:1.0625rem;font-weight:600;color:#fff}
.pc-tv{grid-column:auto;font-size:1.75rem;font-weight:600;line-height:1.1;color:#fff;
  letter-spacing:-.02em;font-variant-numeric:tabular-nums}
@media(min-width:760px){.pc-tv{grid-column:3}}
.pc-tv span{display:block;font-size:.875rem;font-weight:400;letter-spacing:0;
  color:var(--blue-200);margin-top:5px}
.pricecard .cta-row{margin-top:28px}
.pc-fine{font-size:.8125rem;line-height:1.5;color:var(--blue-300);margin-top:18px;max-width:56ch}

/* ---------- status colours ----------
   Semantic, not brand. Volta Green is retired from Drip's palette (2026-08-02)
   and none of these is it — they are the alert states the product actually
   reports, and they only ever appear inside a mock or an alert card.
   Re-measured against the Mira rebase 2026-08-04 (band is now --blue-950 #161F2C).
   The green moved into Mira's own hue (123°); Mira ships it at #388E3C, which is
   only 4.12:1 on white and fails AA as text, so it is darkened to 5.60:1.
   Measured: ok on ok-50 = 5.13:1, lift on #161F2C = 9.52:1 (green);
   warn on warn-50 = 5.05:1, lift on #161F2C = 9.93:1;
   alert on alert-50 = 5.02:1, red lift on #161F2C = 5.99:1.
   ⚠ The solid -lift tiles still take #1F1F1F ink, never white: red 5.96:1. */
:root{
  --ok:#2E7631; --ok-50:#EDF8ED; --ok-line:#B7E3B9; --ok-lift:#4ADE80;
  --warn:#9A5B08; --warn-50:#FEF6E7; --warn-lift:#FBBF24;
  --alert:#C81E1E; --alert-50:#FDECEC; --alert-lift:#F87171;
}

/* ---------- the app, as bold tiles ----------
   🚨 Chris, 2026-08-03: "smarter with drip, something bolder like this" — with a
   reference showing big filled tiles carrying a bold label and a large value.
   The first pass was a 292px dark phone mock; it was too small and too quiet to
   be the section's whole visual. The phone is GONE — do not put it back without
   asking, and if a phone frame ever returns it goes beside these, not instead.
   ⚠ Same material rule as `.alert-tile`: a solid fill with near-black type over
   it. Fill is blue-300 because BLUE = ALL SYSTEMS NORMAL is locked on this site
   and this is the everything-is-fine state. Measured #8EAEE1 under #1F1F1F ink:
   8.10:1. Never fade the ink for hierarchy — size and weight only. */
.apptiles{display:grid;gap:16px}
.atile{background:var(--blue-300);border-radius:22px;padding:24px 26px 26px;
  display:grid;grid-template-columns:1fr auto;align-items:end;gap:14px;color:var(--ink)}
@media(min-width:600px){.atile{border-radius:26px;padding:26px 30px 30px}}
.atl{grid-column:1/-1;font-size:1.0625rem;font-weight:600;letter-spacing:-.01em;
  margin-bottom:44px}
.atv{font-size:clamp(2.5rem,1.7rem + 2.6vw,3.75rem);font-weight:500;line-height:.96;
  letter-spacing:-.03em;font-variant-numeric:tabular-nums;display:flex;align-items:baseline;gap:10px}
.atv em{font-size:1rem;font-style:normal;font-weight:500;letter-spacing:0}
.atile .arw{width:38px;height:38px;flex:none;fill:none;stroke:var(--ink);stroke-width:1.6;
  stroke-linecap:round;stroke-linejoin:round;align-self:end;margin-bottom:4px}
/* the pale companion card — the reference pairs the filled tiles with one white
   card carrying a meter, which is what stops the stack reading as a colour block */
.acard{background:var(--surface);border:1px solid var(--border);border-radius:22px;
  padding:24px 26px 26px}
@media(min-width:600px){.acard{border-radius:26px;padding:26px 30px}}
.ac-top{display:flex;align-items:center;justify-content:space-between;gap:14px}
.ac-top b{font-size:1.0625rem;font-weight:600;letter-spacing:-.01em}
.ac-pill{font-size:.8125rem;font-weight:500;color:var(--muted);border:1px solid var(--border);
  border-radius:99px;padding:6px 13px;white-space:nowrap}
.ac-bar{height:26px;border-radius:8px;background:var(--blue-50);margin-top:22px;overflow:hidden}
.ac-bar i{display:block;height:100%;border-radius:8px;background:var(--accent)}
.ac-scale{display:flex;justify-content:space-between;font-size:.8125rem;color:var(--muted);
  margin-top:10px;font-variant-numeric:tabular-nums}
.appsplit{display:grid;gap:48px;margin-top:56px}
@media(min-width:900px){.appsplit{grid-template-columns:minmax(0,1fr) minmax(0,1.05fr);gap:64px;align-items:start}}

/* ---------- alert tiles ----------
   🚨 Chris, 2026-08-03: "never use these color edges." The first pass signalled
   state with a 3px coloured rail down the left of a white card; that is gone,
   and no rule below may reintroduce a one-sided coloured border anywhere.
   State is carried by the WHOLE tile now — a solid fill with near-black type
   over it, after the reference he sent ("make the notifications bold and easy
   to read"). Big type, one fill, no rails, no thin outlines.
   ⚠ The fills are the `-lift` tokens, which exist because they are the steps
   that survive on dark. Measured against #1F1F1F ink: red #F87171 5.94:1,
   amber #FBBF24 9.87:1 — so ink, never white, is the type colour on a tile. */
.alerts{display:grid;gap:20px;margin-top:56px}
@media(min-width:820px){.alerts{grid-template-columns:repeat(2,1fr);gap:24px}}
.alert-tile{border:0;border-radius:20px;padding:26px 24px;color:var(--ink)}
@media(min-width:600px){.alert-tile{border-radius:24px;padding:30px 28px}}
.alert-tile.is-water{background:var(--alert-lift)}
.alert-tile.is-batt{background:var(--warn-lift)}
/* ⚠ The kicker stays SOLID ink. Fading it to rgba(31,31,31,.78) for hierarchy
   measured 4.17:1 on the red tile — under AA at this size. Hierarchy here is
   size and weight only, never opacity. */
.at-top{display:flex;align-items:baseline;justify-content:space-between;gap:14px;
  font-size:.8125rem;font-weight:500;color:var(--ink)}
.at-top .at-when{font-variant-numeric:tabular-nums;white-space:nowrap}
.at-room{font-size:1.25rem;font-weight:600;letter-spacing:-.01em;margin-top:22px;color:var(--ink)}
.at-state{font-size:clamp(1.875rem,1.3rem + 1.7vw,2.625rem);font-weight:600;letter-spacing:-.03em;
  line-height:1.04;margin-top:4px;color:var(--ink);text-wrap:balance}
.at-note{font-size:.9375rem;line-height:1.5;color:var(--ink);margin-top:14px;max-width:34ch}
.at-pills{display:flex;flex-wrap:wrap;gap:8px;margin-top:24px}
/* ⚠ .70 is the floor for this outline, not a taste call: at .42 the pill edge
   is 2.07:1 against the red tile and at .55 it is 2.67:1, both under the 3:1
   a UI boundary needs. .70 measures 3.58:1 on red and 4.75:1 on amber. */
.at-pills span{display:inline-flex;align-items:center;font-size:.875rem;font-weight:500;
  color:var(--ink);border:1.5px solid rgba(31,31,31,.70);border-radius:99px;padding:7px 14px}
/* The tile brings its own fill, so `.dark` must not restyle it — these two
   guards stop the generic dark-band text rules from bleaching the type. */
.dark .alert-tile,.dark .alert-tile p,.dark .alert-tile div{color:var(--ink)}


/* ---------- the admin portal, drawn in CSS ----------
   Three addresses and one status box. Same reason as the phone: it is the
   product's own type and palette, and it stays sharp. Decorative. */
.portalmock{background:var(--surface);border:1px solid var(--border);border-radius:18px;
  padding:22px 20px;margin-top:56px;max-width:760px;
  box-shadow:0 24px 50px -28px rgba(6,14,26,.30)}
@media(min-width:700px){.portalmock{padding:28px}}
.pm-top{display:flex;align-items:center;justify-content:space-between;gap:14px;
  padding-bottom:18px;border-bottom:1px solid var(--border)}
.pm-title{font-size:1rem;font-weight:600;color:var(--ink)}
.pm-live{display:inline-flex;align-items:center;gap:7px;font-size:.75rem;font-weight:500;
  color:var(--muted)}
.pm-live i{width:7px;height:7px;border-radius:50%;background:var(--ok);flex:none}
.pm-pills{display:flex;flex-wrap:wrap;gap:9px;padding:18px 0}
.pm-pill{display:inline-flex;align-items:center;gap:8px;font-size:.875rem;font-weight:500;
  color:var(--ink);background:var(--blue-50);border:1px solid var(--blue-100);
  border-radius:99px;padding:8px 15px}
.pm-pill .ico{width:15px;height:15px;color:var(--accent);stroke-width:1.8}
.pm-status{display:flex;align-items:flex-start;gap:13px;border-radius:14px;padding:18px;
  background:var(--ok-50);border:1px solid var(--ok-line)}
.pm-dot{width:10px;height:10px;border-radius:50%;background:var(--ok);flex:none;margin-top:6px}
.pm-status strong{display:block;font-size:1rem;font-weight:600;color:var(--ok);line-height:1.4}
.pm-status span{display:block;font-size:.875rem;color:var(--muted);margin-top:2px}
.dark .portalmock{background:rgba(8,20,38,.80);border-color:rgba(143,184,243,.26);box-shadow:none}
.dark .pm-top{border-bottom-color:rgba(143,184,243,.20)}
.dark .pm-title{color:#fff}
.dark .pm-live{color:var(--blue-300)}
.dark .pm-live i{background:var(--ok-lift)}
.dark .pm-pill{color:#fff;background:rgba(143,184,243,.12);border-color:rgba(143,184,243,.26)}
.dark .pm-pill .ico{color:var(--blue-400)}
.dark .pm-status{background:rgba(74,222,128,.10);border-color:rgba(74,222,128,.30)}
.dark .pm-dot{background:var(--ok-lift)}
.dark .pm-status strong{color:var(--ok-lift)}
.dark .pm-status span{color:var(--blue-200)}

/* ---------- the glass needs something to refract ----------
   Over a flat colour `backdrop-filter` has nothing to blur, so the price card
   read as a plain lighter panel rather than as glass. This lays a soft blue
   field behind it for the blur to pick up. It is dark-on-dark and does not
   move the numbers in the .pricecard note: composited under the same glass,
   white goes 12.74 -> 10.26:1, blue-200 9.15 -> 7.37:1, blue-400 4.49 -> 3.61:1.
   All still clear, and the price stays above the 3:1 large-text floor. */
.glow{position:relative;isolation:isolate;overflow:hidden}
.glow::before{content:"";position:absolute;z-index:-1;left:50%;top:46%;
  width:min(1180px,124%);aspect-ratio:1.55;transform:translate(-50%,-50%);
  pointer-events:none;
  background:radial-gradient(ellipse at center,rgba(46,118,222,.30),rgba(46,118,222,0) 68%)}

/* ---------- fixes found in review ----------
   The line-up frames were 4:5 — three of them stacked 400px of empty stripe
   under the headline and dominated the section. 16:10 halves that, and the
   caption inside the frame is dropped because the h4 underneath already says
   the same words. ⚠ These are placeholders on purpose: there are no product
   photographs yet. Swapping in a real <img> is a change at the call site in
   build_site.py, not here. */
.phgrid .ph{aspect-ratio:16/10;border-radius:14px;padding:20px}

/* ============================================================================
   BORROWED FROM THE NEW VOLTA HOME PAGE — 2026-08-04
   ==========================================================================
   Three shapes came over from volta-home-site: the depth wash on a solid
   band, the numbered step card, and the /base parts grid with its badged line
   art. Everything below is additive — no rule here redefines a selector that
   already exists further up this file.

   🚨 THE COLOUR IS TRANSLATED, NOT COPIED. The volta versions are lime on
   --green-deep. Volta Green is retired on this site (Chris, 2026-08-02), so
   every one of these runs on the blue ramp, and the ramp's own measurements
   at the top of this file are what pick the values.
   ⚑ RE-MEASURED against the Mira Pro rebase (2026-08-04) — the ramp
   desaturated under this block and every figure below moved:
     light band — --accent on --bg 4.58:1, white on --accent 4.83:1
     navy band  — --accent on --blue-950 is 3.43:1 and is NEVER body type
                  there; --blue-400 is 5.15:1 and is what the dark half of
                  every rule below switches to (--blue-950 back on it is the
                  same 5.15:1, which is what makes the badge mark legible)
   All of it still clears AA. Both halves are written out so a band can be
   flipped light/dark without anyone having to re-derive which blue survives.
   ========================================================================= */

/* ---------- .depth — a solid band that is not one flat rectangle ----------
   The volta homepage's green band gets three radial layers so it reads as a
   field rather than a fill. ⚠⚠ EVERY LAYER IS DARKER THAN OR EQUAL TO THE
   BASE COLOUR, and that is a constraint, not a preference: the obvious way to
   make a dark band less flat is to bloom the brighter accent into it, and
   --blue-600 at 3.43:1 against white type would fail the band for body copy.
   --blue-1000 can only improve it, so the dimension comes from the sink and
   the vignette, never from a lift.
   Applied with `cls='depth'` alongside `dark=True`. */
.dark.depth{
  background:
    radial-gradient(78% 58% at 50% 2%,var(--blue-950) 0%,transparent 68%),
    radial-gradient(88% 72% at 6% 104%,var(--blue-1000) 0%,transparent 62%),
    radial-gradient(120% 100% at 92% 98%,var(--blue-1000) 0%,transparent 58%),
    var(--blue-950);
}

/* ---------- .center-head — the volta band's centred head ----------
   `section()` sets the headline left with the deck under it. On the borrowed
   bands the head is centred and the deck is a single short line at display
   size, which is what makes the cards below read as a set rather than as a
   continuation of the paragraph. */
.center-head > .wrap > h2{margin-inline:auto;text-align:center;max-width:16ch}
.center-head > .wrap > .body{
  margin-inline:auto;text-align:center;max-width:34ch;
  font-size:1.25rem;line-height:1.45;
}
@media(min-width:900px){.center-head > .wrap > .body{font-size:1.4375rem}}
/* the deck is the size of a lead here, so on the navy it takes white rather
   than the blue-200 body colour — 17.38:1, and it is one line, not a block */
.dark.center-head > .wrap > .body{color:#fff}

/* ---------- step cards ----------
   The homepage card: a numbered ring and a watermark glyph on ONE row, then
   the heading and the line.
   ⚠ Cards are near-white and belong on a dark band only — on --bg they are a
   white rectangle on an almost-white page.
   ⚠ Three columns, and five cards on purpose fill 3 + 2. Five across puts
   each card under 200px at --w-page and the headings break to three lines. */
.step-cards{display:grid;gap:20px;margin-top:56px}
@media(min-width:640px){.step-cards{grid-template-columns:repeat(2,1fr);gap:22px}}
@media(min-width:980px){.step-cards{grid-template-columns:repeat(3,1fr);gap:24px}}
.step-card{
  background:rgba(255,255,255,.94);
  border-radius:18px;padding:28px 26px 30px;
}
.sc-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:20px}
/* ⚠ .40, not .26. On the card fill (#F1F2F2 composited) .26 measures 1.72:1 —
   the ring all but vanished. .40 is 2.39:1. It stays under 3:1 deliberately:
   the ring is decoration around a NUMBER that is itself 14.70:1, so the
   content is carried by the digits, and a ring dark enough to clear 3:1 starts
   competing with them. */
.sc-ring{
  display:inline-flex;align-items:center;justify-content:center;
  width:44px;height:44px;flex:none;
  border:2px solid rgba(31,31,31,.40);border-radius:50%;
  font-size:1rem;font-weight:500;color:var(--ink);
  font-variant-numeric:tabular-nums;line-height:1;
}
/* ⚠ Ink at .20, not accent. The glyph is a watermark behind the number, not a
   second focal point — the moment it takes the accent it competes with the
   ring and the card has two things to look at. */
.sc-icon{width:36px;height:36px;flex:none;fill:none;stroke:var(--ink);
  stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;opacity:.20}
/* ⚠ Both of these have to out-specify `.dark h3` and `.dark p` (0,1,1 each),
   or the card's own text goes white on white. */
.dark .step-card h3{font-size:1.1875rem;color:var(--ink);margin-bottom:8px}
.dark .step-card p{font-size:.9375rem;color:var(--muted);max-width:none}

/* ---------- parts grid — the /base "inside" shape ----------
   Two columns, an oversized heading, and line art at roughly 5x it. The size
   is what makes a glyph read as a component rather than as a bullet. */
.parts{display:grid;gap:56px;margin-top:64px}
@media(min-width:820px){.parts{grid-template-columns:repeat(2,minmax(0,1fr));
  column-gap:88px;row-gap:80px}}
/* Each glyph starts at a different x inside its own viewBox, so one fixed
   negative margin would leave half the set hanging off the heading's left
   rule. --pg-inset carries the glyph's leftmost x and the margin derives from
   it, which keeps the alignment through the width change below. */
.pg-icon{display:block;--pg-inset:4;--pg-w:132px;
  width:var(--pg-w);height:var(--pg-w);
  margin:0 0 24px calc(var(--pg-inset) * var(--pg-w) / -100);
  color:var(--accent)}
@media(min-width:820px){.pg-icon{--pg-w:156px;margin-bottom:28px}}
.pg-item h3{font-size:1.5rem;font-weight:500;letter-spacing:-.018em;margin-bottom:12px}
@media(min-width:820px){.pg-item h3{font-size:1.75rem}}
.pg-item p{font-size:1rem;line-height:1.6;color:var(--muted);max-width:44ch}
/* ---- badge parts. The halo is a disc of the BAND colour under the badge; it
   punches a clean gap through whatever stroke passes behind it, which is the
   only reason the corner reads at 24px as well as at 156px. */
.pg-halo{fill:var(--bg)}
.pg-badge{fill:var(--accent)}
.pg-mark{stroke:#FFFFFF;fill:none}
/* the wide row: the service the parts hang off, with a hairline above it.
   Five items in a two-column grid orphans the fifth, and this one is not a
   part anyway. */
.pg-wide{padding-top:48px;border-top:1px solid var(--border)}
@media(min-width:820px){.pg-wide{grid-column:1/-1;display:grid;
  grid-template-columns:auto minmax(0,1fr);column-gap:48px;align-items:start;
  padding-top:60px}
  .pg-wide .pg-icon{margin-bottom:0}
  .pg-wide p{max-width:58ch}}
/* the dark half of the swap — kept written out even though /how-it-works runs
   this band light, because the band rhythm is the only reason it does */
.dark .parts{color:#fff}
.dark .pg-icon{color:var(--blue-400)}
.dark .pg-item h3{color:#fff}
.dark .pg-item p{color:var(--blue-200)}
.dark .pg-halo{fill:var(--blue-950)}
.dark .pg-badge{fill:var(--blue-400)}
.dark .pg-mark{stroke:var(--blue-950)}
.dark .pg-wide{border-top-color:rgba(143,184,243,.22)}

/* ============================================================================
   /products — the Base product page's shape, on the blue ramp
   ========================================================================= */

/* ---- the headline band. Navy, straight under the hero photo, so the two run
   together as one dark movement before the page opens out to --bg. */
.prod-intro{background:var(--blue-950);padding-block:96px 88px}
@media(min-width:1024px){.prod-intro{padding-block:128px 112px}}
.prod-intro h2{
  font-size:clamp(1.875rem,1.15rem + 2.4vw,2.875rem);
  font-weight:600;line-height:1.10;letter-spacing:-.028em;
  color:#fff;max-width:18ch;
}
.prod-intro p{margin-top:22px;font-size:1.125rem;line-height:1.55;
  color:var(--blue-200);max-width:var(--w-measure)}

/* ---- the quiet "every install includes" banner ----
   ⚠ SEPARATOR DOTS ARE ROW-SCOPED, and that is the whole trick. One wrapping
   flex with `span + span::before` looks right on a single line and breaks the
   moment it wraps: the rule fires on every span after the first, including
   whichever one starts line two, so the second row opens with a stranded "·".
   CSS cannot detect a line start and moving to ::after only relocates the
   orphan. So the rows are explicit in the markup, dots are drawn only BETWEEN
   SIBLINGS OF A ROW, and below the breakpoint the rows become columns and the
   dots switch off entirely. */
.inc-band{padding-block:64px 72px;background:var(--surface)}
.inc-title{font-size:1.375rem;font-weight:500;text-align:center;
  margin:0 auto 18px;max-width:none}
.inc-list{display:flex;flex-direction:column;align-items:center;gap:8px;
  margin:0 auto;max-width:900px;font-size:1.0625rem;color:var(--muted);text-align:center}
.inc-row{display:flex;flex-wrap:wrap;justify-content:center}
.inc-row > span + span::before{content:"\00b7";margin:0 12px;opacity:.5;font-weight:700}
.inc-ctas{display:flex;gap:24px;justify-content:center;align-items:center;
  flex-wrap:wrap;margin-top:32px}
@media(max-width:599px){
  .inc-title{font-size:1.1875rem}
  .inc-list{font-size:1rem}
  .inc-row{flex-direction:column;gap:8px}
  .inc-row > span + span::before{content:none}
}

/* ---- one section per product ----
   Copy left, frame plus a hairline-separated list right. `.psp-flip` puts the
   panel on the left instead; only the grid columns move, so the DOM order
   stays copy-then-list and a screen reader hears the heading first either way.
   ⚠ The frames are `ph()` placeholders — there is no product photography yet.
   Swapping one for a real <img> is a change at the call site in
   build_site.py, not here. */
.prod-split{padding-block:80px}
@media(min-width:600px){.prod-split{padding-block:96px}}
@media(min-width:1024px){.prod-split{padding-block:120px}}
/* ⚠ The alternating ground rides `.psp-flip`, NOT `:nth-of-type(even)`. The
   splits are `<section>`s among other `<section>`s, so nth-of-type counts the
   hero and the two bands above them too — inserting one band anywhere on the
   page would silently invert the whole stripe. The flip and the ground change
   together on the same product either way, which is what makes the middle one
   read as the odd row. */
.psp-flip{background:var(--surface)}
.psp-grid{display:grid;gap:40px}
@media(min-width:900px){.psp-grid{grid-template-columns:minmax(0,.88fr) minmax(0,1.12fr);
  gap:72px;align-items:start}
  .psp-flip .psp-copy{order:2}
  .psp-flip .psp-panel{order:1}}
.psp-copy h2{margin-bottom:18px}
.psp-copy > p{font-size:1.125rem;line-height:1.55;color:var(--muted);max-width:42ch}
.psp-panel .ph{margin-bottom:10px}
.psp-list{list-style:none;margin:0;padding:0}
.psp-list li{padding:19px 0;border-bottom:1px solid var(--border)}
.psp-list li:last-child{border-bottom:0;padding-bottom:0}
.psp-list h3{font-size:1.0625rem;font-weight:500;letter-spacing:-.005em;margin-bottom:5px}
.psp-list p{font-size:1rem;line-height:1.5;color:var(--muted)}

/* ---------- mobile menu: the flattened dropdown's label ----------
   The desktop `Products` dropdown becomes a labelled group in the mobile
   panel rather than a second collapse inside an already-collapsed one.
   ⚠ `.nav-menu a` sets a bottom hairline on every child; the label is a
   <span>, so it takes its own inset instead — and the first item under it
   loses the rule it would otherwise share with the label above. */
.nav-menu-label{padding:16px 0 6px;font-size:.8125rem;font-weight:500;
  letter-spacing:.04em;color:var(--muted);line-height:1.4}
.nav-menu a.nav-menu-sub{padding-left:14px}

/* ---------- in-page anchors clear the fixed pill ----------
   /products#sensor, #hub and #valve are linked straight from the nav. The
   header is fixed at 14px + a 62/66px pill, so an un-offset jump lands the
   band's top edge underneath it. 96px puts the boundary just below the bar and
   the band's own top padding then carries the heading down as usual. */
.prod-split{scroll-margin-top:96px}

/* ---------- a real product shot in the split panel ----------
   Same 16:10 frame as `.ph`, so a placeholder and a photograph are
   interchangeable without the section reflowing.
   ⚠ The sensor is a WHITE object cut out on transparency, so the frame cannot
   be white — it would vanish into its own background. A soft wash from
   --surface at the top to --blue-50 at the edge gives the disc something to
   sit against, and the drop-shadow (not a box-shadow: the PNG is a circle in a
   square canvas) puts it on a surface rather than floating in the frame. */
.psp-shot{position:relative;aspect-ratio:16/10;border-radius:16px;overflow:hidden;
  --shot-inset:22px;
  background:radial-gradient(120% 110% at 50% 22%,var(--surface) 0%,var(--blue-50) 76%)}
/* ⚠⚠ THE IMAGE IS ABSOLUTELY POSITIONED, AND THAT IS THE FIX, NOT A STYLE.
   In flow — as a grid child with `place-items:center` — neither `height:100%`
   nor `max-height:100%` resolves: the frame's height comes from
   `aspect-ratio`, which does not count as a definite height for a percentage
   on an in-flow child. The 834x834 PNG kept its intrinsic ratio at the frame's
   full width and `overflow:hidden` cropped it just below the disc's shoulder.
   Percentages on an ABSOLUTELY positioned child resolve against the containing
   block's padding box, which is definite here, so the calc() below works.
   object-fit then letterboxes the square inside the 16:10 frame and
   --shot-inset alone controls how large the product sits. */
.psp-shot img{position:absolute;left:var(--shot-inset);top:var(--shot-inset);
  width:calc(100% - var(--shot-inset) * 2);height:calc(100% - var(--shot-inset) * 2);
  object-fit:contain;display:block;
  filter:drop-shadow(0 14px 26px rgba(18,41,74,.16))}
@media(max-width:599px){.psp-shot{--shot-inset:16px}}
/* ---- the resident app in a product frame.
   ⚠ The phone is an INLINE <svg>, not an <img> — an SVG loaded through <img>
   cannot fetch the webfont and would lose Poppins, which is why it is inlined
   everywhere on this site. The consequence here is that `.psp-shot img` above
   cannot reach it and this frame has to size the phone itself.
   ⚠ The frame is SQUARE, not 16:10. A 760x1558 phone letterboxed into 16:10
   resolves against the frame's HEIGHT, so it lands about 170px wide inside a
   560px frame and reads as a thumbnail. Square gives the same phone ~90% of
   the frame height, which is a product shot. `.psp-shot-app .ui-app` is (0,2,0)
   and beats both `.ui` and `.ui-app`, so it wins wherever it sits in the file. */
.psp-shot-app{aspect-ratio:1/1}
.psp-shot-app .ui-app{position:absolute;left:50%;top:var(--shot-inset);
  transform:translateX(-50%);width:auto;max-width:none;
  height:calc(100% - var(--shot-inset) * 2);
  filter:drop-shadow(0 14px 26px rgba(18,41,74,.16))}

/* ---------- the line-up: three products across the band seam ----------
   Navy headline band, the three products straddling the seam where the navy
   hands off to the page, then what the install includes. One movement, not
   three stacked bands.

   ⚠⚠ THE SEAM IS A HARD-STOPPED GRADIENT ON ONE SECTION, NOT TWO STACKED
   SECTIONS. The products have to cross the boundary, and with two elements the
   lower one's stacking context clips whatever overhangs it.
   ⚠ `.prod-intro` above this carries a deliberately SMALL bottom pad — it and
   the top third here are the same navy and must read as one block. Give it a
   full pad and you get a visible seam between two identical colours. */
.prod-lineup{padding:0 0 8px;
  background:linear-gradient(to bottom,var(--blue-950) 0 30%,var(--bg) 30% 100%)}
.pl-row{display:grid;gap:36px;padding-top:40px}
@media(min-width:760px){.pl-row{grid-template-columns:repeat(3,1fr);gap:28px;
  align-items:end;padding-top:48px}}
@media(min-width:1024px){.pl-row{gap:40px}}
.pl-item{margin:0}
/* one square footprint for all three, so a photograph and a placeholder sit at
   the same size and the row does not jump when one is swapped in */
.pl-shot{aspect-ratio:1/1;display:grid;place-items:stretch}
.pl-shot .ph{aspect-ratio:1/1;height:100%;border-radius:16px}
/* ⚠ Absolutely positioned for the same reason as `.psp-shot img` — a
   percentage height on an in-flow child does not resolve against a parent
   sized only by `aspect-ratio`. See the note there. */
.pl-shot-img{position:relative;width:100%;height:100%}
.pl-shot-img img{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;
  display:block;filter:drop-shadow(0 18px 30px rgba(5,13,26,.28))}
.pl-item figcaption{margin-top:20px;text-align:center;
  font-size:1.0625rem;font-weight:500;line-height:1.4}
.pl-item figcaption a{color:var(--ink);text-decoration:none}
.pl-item figcaption a:hover{color:var(--accent)}

/* ---------- ghost button ----------
   The quieter half of a two-button row: same metrics as `.btn` so the pair
   sits on one baseline, an outline instead of a fill.
   ⚠ It must come AFTER `.btn` in this file — it overrides background and
   colour at equal specificity (0,1,0) and would otherwise lose. */
/* ⚠ The outline is `rgba(31,31,31,.46)`, NOT `--border`. --border is #E4E9F0
   and measures 1.16:1 on --bg — a ghost button whose outline you cannot see is
   a text link wearing padding. At .46 it is 2.82:1, which is where a 1px
   hairline stops disappearing on a bright screen. It is still under the 3:1
   WCAG 1.4.11 asks of a control boundary; what carries the button here is its
   LABEL at 15.63:1, and the outline is the affordance, not the identifier. Go
   darker and it stops reading as the quiet half of the pair. */
.btn-ghost{background:transparent;color:var(--ink);
  box-shadow:inset 0 0 0 1px rgba(31,31,31,.46)}
.btn-ghost:hover{background:var(--blue-50);box-shadow:inset 0 0 0 1px var(--blue-300)}
.btn-ghost:active{background:var(--blue-100)}
.dark .btn-ghost{background:transparent;color:#fff;
  box-shadow:inset 0 0 0 1px rgba(143,184,243,.36)}
.dark .btn-ghost:hover{background:rgba(143,184,243,.12)}
