/* ============================================================
   NoiseMap NYC — landing page styles
   Pure CSS, no framework. Soft / light / playful / rounded.
   ============================================================ */

/* ===== GENERATED BLOCK — DO NOT EDIT BY HAND ==============================
   frontend/build.js writes the :root below from the ACTIVE style in
   design/styles.json. Editing it here is silently overwritten the next time
   serve.js gets a request or deploy.sh runs — change design/styles.json (or
   use frontend/design.html) instead.
   Everything else in this file is hand-written and is NOT touched by build.js:
   only these token VALUES change between styles, never the rules that read
   them, because every style has to render the same page structure.
   ========================================================================= */
/* @generated-tokens:start */
/* style: slate — slate. Generated from design/styles.json. */
:root {
  --style-name:      "slate";
  --bg:              #F2F4F6;
  --bg-soft:         #E7EBEF;
  --surface:         #FFFFFF;
  --ink:             #151A1F;
  --ink-soft:        #5F6B76;
  --brand:           #7A2E1E;
  --brand-dk:        #5C2115;
  --brand-lt:        #B4634C;
  --on-brand:        #FFFDF8;
  --hairline:        #DFE4E9;
  --hairline-strong: #C2CAD3;
  --accent:          #1F5C4A;
  --wash-1:          #EAEEF2;
  --wash-2:          #F5F7F9;
  --shadow-rgb:      30, 45, 60;
  --radius:          3px;
  --radius-sm:       2px;
  --radius-pill:     3px;
  --border-w:        1px;
  --shadow:          none;
  --shadow-sm:       none;
  --wt-bold:         700;
  --type-scale:      1;
  --font:            "Source Serif 4", "Charter", Georgia, "Times New Roman", serif;
  --font-display:    "Fraunces", "Charter", Georgia, "Times New Roman", serif;
}
/* @generated-tokens:end */

/* Hand-written, and deliberately NOT part of the style system: the noise ramp
   is a DATA scale. Green/amber/red has to mean the same loudness on the page,
   on the map plate and in the PDF, so it cannot be a design choice. --maxw is
   layout, and layout is held constant so the styles stay comparable. */
:root {
  --quiet:     #2BC48A;   /* green  */
  --mid:       #FFC24B;   /* amber  */
  --loud:      #FF6B6B;   /* coral  */
  --maxw:      1120px;
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; font-size: calc(100% * var(--type-scale)); }
/* hkz-37: `overflow-x: hidden` used to live here. It hid a real 11px header
   overflow at 390px instead of fixing it, and it makes any future overflow
   invisible to both us and the viewport tests. The page must FIT. */
html, body { max-width: 100%; }

body {
  margin: 0;
  font-family: var(--font);
  color: var(--ink);
  background:
    radial-gradient(1100px 600px at 85% -8%, var(--wash-1) 0%, transparent 60%),
    radial-gradient(900px 600px at -5% 5%, var(--wash-2) 0%, transparent 55%),
    var(--bg);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 { font-family: var(--font-display); line-height: 1.12; letter-spacing: -0.02em; margin: 0; }
h1 { font-size: clamp(2.1rem, 5.2vw, 3.5rem); font-weight: var(--wt-bold); }
h2 { font-size: clamp(1.6rem, 3.6vw, 2.4rem); font-weight: var(--wt-bold); }
h3 { font-size: 1.18rem; font-weight: var(--wt-bold); }
em { font-style: normal; color: var(--brand); }
p  { margin: 0; }
a  { color: inherit; }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* ---------------- header ---------------- */
.site-header {
  position: sticky; top: 0; z-index: 50;
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px;
  max-width: var(--maxw); margin: 0 auto;
  padding: 16px clamp(18px, 4vw, 36px);
  backdrop-filter: saturate(140%) blur(8px);
}
.brand {
  display: inline-flex; align-items: center; gap: 9px;
  text-decoration: none; font-weight: var(--wt-bold); font-size: 1.18rem;
  color: var(--ink); font-family: var(--font-display);
}
.brand-mark {
  display: grid; place-items: center;
  width: 36px; height: 36px; border-radius: calc(var(--radius) * 0.5);
  color: #fff; background: linear-gradient(135deg, var(--brand), var(--brand-lt));
  box-shadow: var(--shadow-sm);
}
.brand-city { color: var(--brand); }
.site-nav { display: flex; align-items: center; gap: 22px; }
.site-nav a { text-decoration: none; color: var(--ink-soft); font-weight: 700; font-size: 0.95rem; }
.site-nav a:hover { color: var(--ink); }
.nav-cta {
  background: var(--brand); color: #fff !important;
  padding: 9px 16px; border-radius: var(--radius-pill);
  box-shadow: var(--shadow-sm);
}
.nav-cta:hover { background: var(--brand-dk); }
/* the free map pill reads as secondary next to the money CTA */
.nav-cta-map { background: var(--bg-soft); color: var(--brand) !important; box-shadow: none; }
/* hkz-32: "For business" is a THIRD pill because the <=880px rule below hides
   every non-.nav-cta link, and Pro has to be reachable on a phone. Quietest of
   the three on purpose — the hero belongs to the free map and the walk. */
.nav-cta-pro {
  background: transparent; color: var(--ink-soft) !important;
  box-shadow: none; border: var(--border-w) solid var(--hairline-strong);
  padding: 6px 12px;
}
.nav-cta-pro:hover { color: var(--brand) !important; border-color: var(--brand); }
.nav-pro-short { display: none; }
.nav-cta-map:hover { background: var(--bg-soft); }

/* ---------------- hero ---------------- */
.hero {
  max-width: var(--maxw); margin: 0 auto;
  padding: clamp(28px, 5vw, 64px) clamp(18px, 4vw, 36px) 40px;
  display: grid; grid-template-columns: 1.05fr 0.95fr;
  gap: clamp(28px, 5vw, 60px); align-items: center;
}
.eyebrow {
  display: inline-block;
  background: #fff; border: 1px solid var(--hairline);
  color: var(--ink-soft); font-weight: 700; font-size: 0.86rem;
  padding: 6px 13px; border-radius: var(--radius-pill); box-shadow: var(--shadow-sm);
}
.hero-copy h1 { margin: 18px 0 0; }
.hero-copy .sub {
  margin-top: 16px; font-size: clamp(1.02rem, 1.6vw, 1.18rem);
  color: var(--ink-soft); max-width: 30em;
}

/* ---- the hero's ONE door (hkz-33, narrowed) ----
   It was two cards side by side, consumer and business. The business card
   moved to the end of #measure (.measure-pro): on a phone the pair made a
   demo request compete with the $100 walk on the first screen, and the walk
   is what the paid traffic arrives for. One column now — a lone card in a
   1fr 1fr grid would sit in the left half and read as a broken pair. */
.hero-ctas {
  display: grid; grid-template-columns: 1fr;
  gap: 12px; margin-top: 26px; max-width: 420px;
}

/* ---- Pro, at the end of "what kind of loud" ---- */
.measure-pro { margin-top: 34px; }
.measure-pro h3 {
  margin: 0 0 12px; font-size: 1.12rem; color: var(--ink);
}
.measure-pro .hero-cta { max-width: 420px; }
.hero-cta {
  display: flex; flex-direction: column; gap: 3px;
  padding: 15px 17px; border-radius: calc(var(--radius) * 0.9);
  text-decoration: none; box-shadow: var(--shadow-sm);
  transition: transform .12s, background .15s, border-color .15s;
}
.hero-cta:hover { transform: translateY(-2px); }
.hc-kicker { font-size: 0.72rem; font-weight: var(--wt-bold); letter-spacing: .04em; text-transform: uppercase; opacity: .8; }
.hc-title { font-size: 1.02rem; font-weight: var(--wt-bold); line-height: 1.25; }
.hc-sub { font-size: 0.82rem; line-height: 1.4; opacity: .85; }

/* consumer door — the loud one */
.hero-cta-you { background: var(--brand); color: #fff; }
.hero-cta-you:hover { background: var(--brand-dk); }

/* business door — quiet, bordered, clearly a different kind of thing */
.hero-cta-biz {
  background: #fff; color: var(--ink);
  border: var(--border-w) solid var(--hairline-strong); box-shadow: none;
}
.hero-cta-biz .hc-kicker { color: var(--brand); opacity: 1; }
.hero-cta-biz .hc-sub { color: var(--ink-soft); opacity: 1; }
.hero-cta-biz:hover { border-color: var(--brand); }

.trust-row {
  display: flex; flex-wrap: wrap; gap: 18px;
  margin-top: 26px; color: var(--ink-soft);
  font-size: 0.9rem; font-weight: 700;
}

/* ---------------- hero map mock ---------------- */
.hero-map { position: relative; }
.map-card {
  background: var(--surface); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 14px;
  border: 1px solid var(--hairline);
}
.map-card-head {
  display: flex; align-items: center; gap: 9px;
  padding: 4px 6px 12px; font-weight: 700; color: var(--ink-soft);
  font-size: 0.9rem;
}
.map-title { flex: 1; }
.map-badge {
  font-size: 0.66rem; font-weight: var(--wt-bold); letter-spacing: .08em;
  color: var(--brand); background: var(--bg-soft);
  padding: 3px 8px; border-radius: var(--radius-pill);
}
.dot { width: 9px; height: 9px; border-radius: 50%; }
.dot-quiet { background: var(--quiet); box-shadow: 0 0 0 4px rgba(43,196,138,.18); }
.map-frame {
  position: relative; aspect-ratio: 4 / 3.4;
  border-radius: var(--radius-sm); overflow: hidden;
  background: #EEF2F6;
}
.map-frame svg { display: block; width: 100%; height: 100%; }
.map-legend {
  display: flex; gap: 16px; justify-content: center;
  padding: 12px 4px 4px; font-size: 0.82rem; font-weight: 700;
  color: var(--ink-soft);
}
.lg { display: inline-flex; align-items: center; gap: 6px; }
.sw { width: 12px; height: 12px; border-radius: 4px; display: inline-block; }
.sw-quiet { background: var(--quiet); }
.sw-mid   { background: var(--mid); }
.sw-loud  { background: var(--loud); }

.map-float {
  position: absolute; background: #fff;
  font-size: 0.82rem; font-weight: 700; color: var(--ink);
  padding: 8px 13px; border-radius: var(--radius-pill);
  box-shadow: var(--shadow); border: 1px solid var(--hairline);
  white-space: nowrap;
}
.map-float-a { top: 8%; right: -6%; }
.map-float-b { bottom: 14%; left: -8%; }

/* ---------------- generic section ---------------- */
.section {
  max-width: var(--maxw); margin: 0 auto;
  padding: clamp(48px, 7vw, 88px) clamp(18px, 4vw, 36px);
}
.section-soft {
  max-width: none;
  background: linear-gradient(180deg, transparent, var(--bg-soft) 12%, var(--bg-soft) 88%, transparent);
}
.section-soft > * { max-width: var(--maxw); margin-left: auto; margin-right: auto; }
.section-head { max-width: 40rem; margin: 0 auto 40px; text-align: center; }
.section-head p { margin-top: 14px; color: var(--ink-soft); font-size: 1.05rem; }

/* feature cards */
/* TWO cards, two columns, centred. The count is written here rather than
   auto-fitted so adding a source is a deliberate edit in both places, not a
   silent reflow — and `max-width` keeps two cards from stretching to the full
   1120px, which made them read as panels rather than as a pair. */
.card-grid {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 18px;
  max-width: 780px; margin: 0 auto;
}
.feature-card {
  background: var(--surface); border: 1px solid var(--hairline);
  border-radius: var(--radius); padding: 24px 22px;
  box-shadow: var(--shadow-sm);
  transition: transform .15s, box-shadow .15s;
}
.feature-card:hover { transform: translateY(-4px); box-shadow: var(--shadow); }
.fc-head {
  display: flex; align-items: center; gap: 12px; margin-bottom: 12px;
}
.fc-icon {
  flex: none; width: 42px; height: 42px; display: grid; place-items: center;
  font-size: 1.3rem; border-radius: calc(var(--radius) * 0.55);
  background: var(--bg-soft);
}
.feature-card h3 { margin: 0; font-size: 1.08rem; }
.feature-card p { color: var(--ink-soft); font-size: 0.96rem; }

/* ---------------- steps ---------------- */
.step-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
.step {
  background: var(--surface); border: 1px solid var(--hairline);
  border-radius: var(--radius); padding: 28px 24px; box-shadow: var(--shadow-sm);
}
.step-head {
  display: flex; align-items: center; gap: 12px; margin-bottom: 12px;
}
.step-num {
  flex: none; display: grid; place-items: center;
  width: 38px; height: 38px; border-radius: calc(var(--radius) * 0.55);
  font-weight: var(--wt-bold); color: #fff;
  background: linear-gradient(135deg, var(--brand), var(--brand-lt));
}
.step h3 { margin: 0; font-size: 1.08rem; }
/* The uncalibrated-dB caveat under the steps — quiet, but on the page. */
.how-fine {
  max-width: 44rem; margin: 26px auto 0; text-align: center;
  color: var(--ink-soft); font-size: 0.86rem; line-height: 1.6;
}
.step p { color: var(--ink-soft); font-size: 0.96rem; }

/* ---------------- book CTA (sch-4) ----------------
   The paid CTA, directly under the sample report. Deliberately a
   light band rather than a second indigo one: two full-bleed
   gradient bands back to back read as one, and this is the CTA
   that has to win. */
.book-band {
  text-align: center;
  max-width: var(--maxw);
  margin: 0 auto;
  padding-top: clamp(28px, 4vw, 40px);
  padding-bottom: clamp(28px, 4vw, 40px);
}
.book-band p {
  margin: 14px auto 0;
  max-width: 34em;
  color: var(--ink-soft);
}
.book-cta {
  display: inline-block;
  margin-top: 24px;
  padding: 16px 30px;
  min-height: 54px;
  border-radius: var(--radius-pill);
  background: var(--brand);
  color: #fff;
  font-weight: var(--wt-bold);
  font-size: 1.08rem;
  text-decoration: none;
  box-shadow: var(--shadow-sm);
  transition: background .15s ease, transform .12s ease;
}
.book-cta:hover { background: var(--brand-dk); transform: translateY(-1px); }
.book-cta:active { transform: translateY(0); }
.book-fine { margin-top: 12px !important; font-size: 0.86rem; }

/* ---------------- "For business" band (hkz-32) ---------------- */
/* ---------------- the band on the landing page ---------------- */
/* Still modest and low on the page, but no longer the only business
   door: hkz-33 put a Pro CTA in the hero beside the walk CTA. This band
   is the second chance, for a reader who scrolled past the hero. */
.pro-band {
  max-width: var(--maxw);
  margin: 0 auto clamp(40px, 6vw, 72px);
  padding: 22px clamp(18px, 4vw, 30px);
  border: var(--border-w) dashed var(--hairline-strong);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, .55);
  display: flex;
  flex-direction: column;
  gap: 12px;
  align-items: flex-start;
}
.pro-band-copy { color: var(--ink-soft); font-size: 0.97rem; max-width: 46em; }
.pro-band-copy strong { color: var(--ink); }
.pro-band-link {
  flex: none;
  text-decoration: none;
  font-weight: var(--wt-bold);
  font-size: 0.95rem;
  color: var(--brand);
  background: #fff;
  border: var(--border-w) solid var(--hairline-strong);
  border-radius: var(--radius-pill);
  padding: 10px 18px;
  white-space: nowrap;
}
.pro-band-link:hover { border-color: var(--brand); }

@media (min-width: 720px) {
  .pro-band { flex-direction: row; align-items: center; justify-content: space-between; gap: 24px; }
}

/* ---------------- footer ---------------- */
.site-footer {
  max-width: var(--maxw); margin: 0 auto;
  padding: 26px clamp(18px, 4vw, 36px) 40px;
  display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px;
  color: var(--ink-soft); font-size: 0.9rem; font-weight: 700;
}
.foot-by a { color: var(--brand); text-decoration: none; }
.foot-by a:hover { text-decoration: underline; }

/* ---------------- responsive ---------------- */
@media (max-width: 880px) {
  /* PHONE READING ORDER: say what this is, then show it, then sell it.
     The map used to come first (order:-1) and the H1 landed under it, so a
     visitor arriving cold from an ad met an unlabelled heat map and only
     afterwards read what it was — and the sub's promise ("Search any NYC
     address") appeared below the very thing it describes.

     The map and the copy live in different containers, so ordering alone
     cannot interleave them: .hero-copy becomes `display: contents` and its
     children become grid items of .hero, which is what lets the map sit
     between the sub and the two doors. `gap: 0` because the children carry
     their own margins — the hero's gap would otherwise apply between every
     one of them and push the $100 CTA further off the fold, which is the
     opposite of the point. */
  .hero { grid-template-columns: 1fr; gap: 0; }
  .hero-copy { display: contents; }
  .hero-copy h1 { order: 1; }
  .hero-copy .sub { order: 2; }
  /* hkz-50: promise -> input -> map. The search is a grid item like the rest
     (.hero-copy is display:contents), so without an `order` it would land in
     DOM position — above the map, but the ordering below would then be off by
     one and the $100 CTA would move. Everything after it shifted up a number. */
  .hero-search { order: 3; }
  /* The eyebrow sits with the pins, not above the headline: "Built only for
     New York City" is a CREDENTIAL, and it reads as one next to "90+ hours on
     foot" — above the H1 it was just a chip the eye skipped on the way in.
     justify-self because grid would otherwise stretch the inline-block pill. */
  .eyebrow { order: 5; justify-self: start; margin-top: 22px; }
  /* The pins earn the CTA: "90+ hours on foot" reads as the caption to the
     map above it, and the $100 ask lands after the proof rather than before. */
  .trust-row { order: 6; }
  .hero-ctas { order: 7; }
  .hero-map { order: 4; max-width: 440px; margin: 22px auto 0; width: 100%; }
  .card-grid { grid-template-columns: repeat(2, 1fr); }
  .step-grid { grid-template-columns: 1fr; }
  /* DIRECT children only. Without the `>` this also hid the links inside
     .nav-menu-panel — they are <a> inside .site-nav too — and it outranks the
     panel's own rule on specificity, so the menu opened to an empty sliver.
     The rule always meant "the nav's own links", it just never had to say so. */
  .site-nav > a:not(.nav-cta) { display: none; }
  .map-float-a { right: 0; }
  .map-float-b { left: 0; }
}
@media (max-width: 480px) {
  .card-grid { grid-template-columns: 1fr; }
  .hero-ctas { grid-template-columns: 1fr; }
  .trust-row { gap: 12px; }
  /* two nav pills now (free map + book a walk): shrink them so the
     wordmark still fits on one line at 390px */
  .site-header { gap: 8px; }
  .brand { white-space: nowrap; }
  .site-header { padding-left: 10px; padding-right: 10px; gap: 8px; }
  .site-nav { gap: 5px; }
  .nav-cta { padding: 7px 8px; font-size: 0.76rem; white-space: nowrap; }
  /* hkz-32: a third pill needs ~70px that this header does not have. It comes
     from the brand's " NYC" (43px) and a couple of px off each pill — measured
     at 390px, where the two original pills already ended exactly on the edge.
     The wordmark still reads "Honkz"; nothing is pushed off-screen. */
  .brand-city { display: none; }
  .nav-pro-long { display: none; }
  .nav-pro-short { display: inline; }

  /* hkz-37 --------------------------------------------------------------
     Everything below used to live in the `max-width: 389px` block, which
     meant a 390pt iPhone -- the single most common visitor -- got the DESKTOP
     gutters and the desktop map padding. These are phone rules, so they
     belong at the phone breakpoint.

     The header also has to fit at 390px, which it did not: with hkz-35's
     serif wordmark the three pills ended at x=401 on a 390px screen. The
     pills lose a few px each, and `flex-wrap` on the header is the backstop
     -- under any style whose type runs wider, the nav drops to a second row
     rather than hanging off the edge. */
  .site-header { flex-wrap: wrap; row-gap: 6px; }
  .site-nav { gap: 4px; flex-wrap: wrap; justify-content: flex-end; }
  .nav-cta { padding: 6px 7px; font-size: 0.72rem; }
  .nav-cta-pro { padding: 5px 7px; }
  /* the wordmark gives up the last few px so all three pills stay on ROW ONE
     at 390px; the backstop above only has to catch styles with wider type. */
  .brand { font-size: 1.05rem; gap: 7px; }
  .brand-mark { width: 30px; height: 30px; }

  /* smaller side gutters on phones so the content uses the width it has */
  .hero,
  .section,
  .site-header,
  .site-footer { padding-left: 10px; padding-right: 10px; }

  /* THE MAP IS THE SHOP WINDOW: on a phone the teaser runs nearly edge to
     edge (>=92% of the viewport) instead of floating inside a wide frame. */
  .hero { padding-top: 18px; }
  .hero-map { max-width: none; }
  .map-card { padding: 3px; }
  .map-card-head { padding: 2px 4px 6px; font-size: 0.82rem; }
  .hz-mount { aspect-ratio: 4 / 4.6; }
  .map-hint { margin-top: 6px; }
  .map-open { margin-left: 0; margin-right: 0; }
}

/* hkz-32: below 390px (iPhone SE and older Androids) the header is genuinely
   full — three pills overflow even at the sizes above, and the two free CTAs
   outrank Pro. Under that width the landing page's "For business" band is the
   door instead.
   hkz-37: this block used to carry the phone gutters and the tight map card
   too; those now live in the 480px block above, where every phone gets them.
   What is left is the one rule that is genuinely about being NARROWER than an
   iPhone 12. NOTE this is a `max-width: 389px` block, not part of the 480px one. */
@media (max-width: 389px) {
  .nav-cta-pro { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  * { scroll-behavior: auto !important; }
}

/* ============================================================
   Real map component (honkz_map.js) + gated city overlay
   ============================================================ */
.hz-mount {
  position: relative; width: 100%; aspect-ratio: 4 / 3.4;
  border-radius: var(--radius-sm); overflow: hidden; background: #eef2f4;
}
.hz-map { position: relative; width: 100%; height: 100%; }
.hz-canvas { position: absolute; inset: 0; }
.hz-toggles {
  position: absolute; top: 10px; left: 10px; z-index: 3;
  display: flex; gap: 8px; align-items: flex-start; flex-wrap: wrap;
  max-width: calc(100% - 160px); /* leave the top-right clear for the legend */
  /* THE CONTAINER MUST NOT EAT DRAGS. It is a wrapping flex box that grows to
     max-width once a panel opens, so its own box covered a wide strip of the
     map and swallowed every drag that began there — opening a panel made the
     map unpannable. Only the controls themselves should take the pointer; the
     gaps between them, and the empty area the flex box reserves, belong to the
     map. Same pattern as .city-bar above. */
  pointer-events: none;
}
.hz-toggles > * { pointer-events: auto; }
.hz-break { flex-basis: 100%; height: 0; }
.hz-seg {
  display: inline-flex; background: rgba(255, 255, 255, 0.92);
  border-radius: var(--radius-pill); padding: 3px; box-shadow: 0 4px 14px -6px rgba(var(--shadow-rgb), 0.4);
}
.hz-seg button {
  border: none; background: transparent; font: inherit; font-size: 0.8rem;
  font-weight: 700; color: var(--ink-soft); padding: 5px 13px; border-radius: var(--radius-pill); cursor: pointer;
}
.hz-seg button.is-active { background: var(--brand); color: #fff; }
.hz-pill {
  border: none; background: rgba(255, 255, 255, 0.92); font: inherit; font-size: 0.8rem;
  font-weight: 700; color: var(--ink-soft); padding: 7px 14px; border-radius: var(--radius-pill); cursor: pointer;
  box-shadow: 0 4px 14px -6px rgba(var(--shadow-rgb), 0.4);
}
.hz-pill.is-active { background: var(--brand); color: #fff; }
/* dev/VIP model-settings row: [A] [weekday|weekend] [——hour slider——] 19:00 */
.hz-mrow {
  display: inline-flex; align-items: center; gap: 9px;
  background: rgba(255, 255, 255, 0.92); border-radius: var(--radius-pill); padding: 3px 12px 3px 3px;
  box-shadow: 0 4px 14px -6px rgba(var(--shadow-rgb), 0.4);
}
.hz-mrow .hz-seg { background: transparent; box-shadow: none; padding: 0; }
.hz-mtag {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: var(--radius-pill); margin-left: 3px;
  background: var(--brand); color: #fff; font-size: 0.7rem; font-weight: var(--wt-bold);
}
.hz-hour { width: 132px; accent-color: var(--brand); cursor: pointer; }
.hz-hlab {
  font-size: 0.75rem; font-weight: 700; color: var(--ink-soft);
  font-variant-numeric: tabular-nums; min-width: 46px;
}
/* an hour nobody walked: the label shows "03:00 → 05:00" and is visibly derived */
.hz-hlab.is-snapped { color: #9a92ad; font-style: italic; min-width: 92px; }
.hz-lnote { color: #9a92ad; font-weight: 600; }
/* model line-width slider — sits inline with the Compare / fade pills */
.hz-wrow { padding: 3px 12px; gap: 7px; }
.hz-wlab { font-size: 0.75rem; font-weight: 700; color: #9a92ad; }
.hz-wslider { width: 84px; }
.hz-hlab.is-auto { color: #9a92ad; } /* width is still on its per-mode default */

/* ---- grouped controls (2026-08-19) ------------------------------------
   Two groups now exist and they mean different things, so they must not look
   alike. "Walks" wraps the metric segment, because the metric options ARE the
   walk layer (None = walks off). "Prediction settings" wraps compare / fade /
   width / the A-B rows, so a reader can see at a glance that those belong to
   the model rather than to the map. */
.hz-group {
  display: inline-flex; align-items: center; gap: 7px; flex-wrap: wrap;
  background: rgba(255, 255, 255, 0.92); border-radius: calc(var(--radius-sm) * 1.3); padding: 5px 10px;
  box-shadow: 0 4px 14px -6px rgba(var(--shadow-rgb), 0.4);
}
.hz-group .hz-seg,
.hz-group .hz-mrow { background: transparent; box-shadow: none; padding: 0; }
.hz-group .hz-pill { box-shadow: none; background: rgba(120, 100, 200, 0.10); }
.hz-group .hz-pill.is-active { background: var(--brand); color: #fff; }
.hz-glab {
  font-size: 0.66rem; font-weight: var(--wt-bold); letter-spacing: 0.06em;
  text-transform: uppercase; color: #9a92ad;
}
.hz-pill-model.is-active { box-shadow: 0 4px 14px -6px rgba(var(--shadow-rgb), 0.6); }

/* ---- Prediction settings: a disclosure, not a sprawl ------------------
   Six advanced controls laid out inline ran the whole width of the map and
   were unusable on a phone. They now collapse to one pill that opens a NARROW
   column: every control stacks under a small caps label, so the panel needs no
   horizontal room and the map stays readable beside it. */
.hz-panel {
  display: inline-flex; flex-direction: column; width: 246px; max-width: calc(100vw - 32px);
  background: rgba(255, 255, 255, 0.95); border-radius: var(--radius-sm);
  box-shadow: 0 6px 20px -8px rgba(var(--shadow-rgb), 0.5); overflow: hidden;
}
/* the Prediction pill IS the header when the panel exists: square off its
   bottom corners so pill and panel read as one object */
.hz-panel-head {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  width: 100%; border-radius: var(--radius-sm) var(--radius-sm) 0 0 !important; box-shadow: none !important;
  padding: 9px 12px;
}
.hz-panel .hz-pill.hz-panel-head.is-active { background: var(--brand); color: #fff; }
.hz-panel.is-open .hz-panel-head { border-bottom: 1px solid rgba(120, 100, 200, 0.25); }
.hz-panel:not(.is-open) .hz-panel-head { border-radius: var(--radius-pill) !important; }
.hz-panel:not(.is-open) { width: auto; background: transparent; box-shadow: none; }
.hz-caret { font-size: 0.7rem; opacity: 0.75; }

.hz-panel-body { display: flex; flex-direction: column; gap: 10px; padding: 10px 12px 12px; }
.hz-field { display: flex; flex-direction: column; gap: 5px; }
.hz-flab {
  font-size: 0.62rem; font-weight: var(--wt-bold); letter-spacing: 0.07em;
  text-transform: uppercase; color: #9a92ad;
}
/* inside the panel every control is full width and shadowless: the panel is the
   surface, the controls are not floating on the map any more */
.hz-panel .hz-pill { box-shadow: none; background: rgba(120, 100, 200, 0.10); text-align: center; }
.hz-panel .hz-pill.is-active { background: var(--brand); color: #fff; }
.hz-panel .hz-mrow {
  background: transparent; box-shadow: none; padding: 0; gap: 7px; flex-wrap: wrap;
  min-height: 0;
}
.hz-panel .hz-seg { background: rgba(120, 100, 200, 0.10); box-shadow: none; padding: 2px; }
.hz-panel .hz-seg button { padding: 4px 10px; font-size: 0.75rem; }
/* slider and its value share one line, value right-aligned like the reference
   panel; the day segment takes a full row of its own above them */
/* the basemap segment has five options and the panel is narrow: let it wrap
   rather than run off the edge, and let each button take its share of the row */
.hz-panel .hz-seg { flex: 0 0 100%; flex-wrap: wrap; }
.hz-panel .hz-seg button { flex: 1 1 0; }
.hz-panel .hz-hour,
.hz-panel .hz-wslider { flex: 1 1 auto; width: auto; min-width: 96px; }
.hz-panel .hz-wrow { padding: 0; flex-wrap: nowrap; }
.hz-panel .hz-hlab {
  min-width: 44px; text-align: right; font-variant-numeric: tabular-nums;
}
.hz-panel .hz-hlab.is-snapped { min-width: 84px; }
/* The snapped label ("09:00 → 12:00") is wider than the plain one, and on the
   narrow panel that extra width wrapped it under the slider. Weekend still has
   untrained hours inside the slider's range (6, 10, 11), so the label must hold
   its line rather than relying on the range never producing one.
   The ROW still wraps, deliberately: the day segment is flex-basis 100% and takes
   a line of its own, so `nowrap` here pushes the slider out of the panel entirely.
   The fix is to let the SLIDER shrink instead, so the label always fits beside it. */
.hz-panel .hz-hlab { white-space: nowrap; flex: none; }
.hz-panel .hz-hour { flex: 1 1 60px; min-width: 0; }
/* the A/B chip is redundant inside the panel: the field label already says
   "Setting A" / "Setting B", and dropping it saves a whole row */
.hz-panel .hz-mtag { display: none; }
/* one dense row of controls, no labels: Hide · − · + / Compare · Measured.
   Every child takes an equal share of the panel width (`flex: 1 1 0`), so a row
   of three reads as thirds and a row of two as halves. */
.hz-prow { display: flex; align-items: center; gap: 5px; flex-wrap: nowrap; }
.hz-prow .hz-pill {
  flex: 1 1 0; min-width: 0; padding: 7px 8px; font-size: 0.74rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* a stepper at the end of its range says so rather than silently doing nothing */
.hz-prow .hz-pill:disabled { opacity: 0.38; cursor: default; }
.hz-prow .hz-icon {
  flex: 1 1 0; width: auto; padding: 7px 0; text-align: center;
  font-size: 0.85rem; line-height: 1;
}

/* The width slider sat shorter than the pills beside it. Match their box so the
   row has one height instead of two. */
.hz-wrow {
  padding: 0 12px; gap: 7px; min-height: 32px; align-items: center;
}
.hz-wslider { width: 84px; height: 32px; }
.hz-group .hz-wrow { min-height: 30px; }

/* compare-mode legend: BOTH ends are inputs; they drive the same symmetric span */
.hz-ldiff { width: 46px; }
.hz-legend {
  /* bottom-LEFT: the toggles own the whole top-left corner and the key used to
     collide with them; expanding upward keeps the controls untouched. */
  position: absolute; bottom: 10px; left: 10px; z-index: 3;
  display: flex; gap: 12px; flex-wrap: wrap; background: rgba(255, 255, 255, 0.92);
  border-radius: var(--radius-pill); padding: 5px 12px; font-size: 0.72rem; font-weight: 700;
  color: var(--ink-soft); box-shadow: 0 4px 14px -6px rgba(var(--shadow-rgb), 0.4);
}
.hz-legend i {
  display: inline-block; width: 11px; height: 11px; border-radius: 3px;
  margin-right: 5px; vertical-align: -1px;
}
/* TWO LEGENDS, STACKED, ONE RAMP (hkz-41). Colour means PERCENTILE in both rows,
   so the two gradient bars are deliberately identical: p90 on one is p90 on the
   other. The rows are told apart by their UNIT and by the shape each layer is
   actually drawn in — a soft blob for the walked raster, a hairline for the
   model — not by hue any more. */
.hz-legend { flex-direction: column; align-items: flex-start; gap: 4px; }
.hz-lrow { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.hz-lwhat {
  display: flex; align-items: center; gap: 6px;
  font-size: 0.62rem; font-weight: var(--wt-bold); text-transform: uppercase;
  letter-spacing: 0.05em; color: #9a92ad; min-width: 74px;
}
/* The shape cue is the ONLY thing left telling the two layers apart at a glance,
   now that they share a ramp — so the legend's generic 11x11 swatch must not
   flatten the blob and the hairline into the same square. */
.hz-lwhat i { margin: 0; flex: none; }
.hz-lwhat .hz-cline { width: 17px; height: 4px; border-radius: 2px; }
.hz-lwhat .hz-cblob { width: 17px; height: 12px; border-radius: 6px; }
/* the shared ramp itself, so an edit to either end is visible as a ramp */
.hz-lbar {
  width: 62px; height: 8px; border-radius: 4px; flex: none;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.06);
}
.hz-lend { display: flex; align-items: center; gap: 4px; }
.hz-lval { color: var(--ink); font-weight: var(--wt-bold); }
.hz-lunit { color: #9a92ad; font-weight: 600; }
/* full width of the panel, not a 246px column beside nothing: the note was
   wrapping to four lines and making the box taller than its own contents */
.hz-lnote2 {
  width: 100%; color: #9a92ad; font-weight: 600; font-size: 0.66rem;
  line-height: 1.35; padding-left: 2px;
}

/* ONE PANEL, TWO STATES. Collapsed it is just the key: what the shapes mean,
   in three words each. Open it adds the scales (dev-editable) and one short
   note. The coverage key used to be a SECOND box stacked above this one, and
   with both open the map showed two overlapping panels saying related things. */
.hz-lkey {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  font-weight: 600; color: var(--ink-soft);
}
.hz-lkey span { display: inline-flex; align-items: center; }
.hz-lkey .hz-cline { width: 17px; height: 4px; border-radius: 2px; }
.hz-lkey .hz-cblob { width: 17px; height: 12px; border-radius: 6px; }
.hz-legend.is-open .hz-lkey {
  padding-bottom: 5px; margin-bottom: 2px;
  border-bottom: 1px solid var(--hairline);
}
.hz-lscales { display: flex; flex-direction: column; gap: 4px; width: 100%; }
.hz-legend .hz-legkey { margin-left: auto; align-self: center; }

/* COVERAGE KEY — says out loud what the model surface's two strengths mean.
   Opened by the legend's caret and stacked directly ABOVE it, bottom-left. */
.hz-cov {
  position: absolute; bottom: 44px; left: 10px; z-index: 3;
  display: flex; flex-direction: column;
  background: rgba(255, 255, 255, 0.93); border-radius: var(--radius-sm); padding: 0;
  font-size: 0.7rem; font-weight: 600; color: var(--ink-soft); line-height: 1.3;
  box-shadow: 0 4px 14px -6px rgba(var(--shadow-rgb), 0.4);
  max-width: calc(100vw - 20px);
}
.hz-cov[hidden] { display: none; }
/* collapsed by default: on a phone the open key was taller than the controls and
   covered the city. The header stays, so the map still offers to explain itself. */
.hz-covbody {
  display: flex; flex-direction: column; gap: 6px; width: 214px;
  padding: 9px 12px 10px;
}
/* the caret that opens it, living in the legend */
.hz-legkey {
  border: none; background: transparent; cursor: pointer; padding: 0 0 0 2px;
  font-size: 0.7rem; color: #9a92ad; line-height: 1;
}
.hz-cov b { font-size: 0.66rem; font-weight: var(--wt-bold); text-transform: uppercase;
  letter-spacing: 0.05em; color: #9a92ad; }
.hz-cov span { display: flex; align-items: center; gap: 7px; }
.hz-cov em { font-style: normal; font-weight: 600; color: #9a92ad; font-size: 0.66rem; }
.hz-cov i { flex: none; }
/* the model surface: a line at the walked strength, and the same line faint */
.hz-cline {
  width: 17px; height: 4px; border-radius: 2px;
  /* the SHARED green→red ramp (hkz-41): both layers are coloured by percentile,
     so the model no longer has a hue of its own. What tells it apart is that it
     draws as a thin line while the walks draw as the blurred blob below.
     The mid stop is not decoration: the map interpolates in HSL and routes
     through yellow, and a plain two-stop CSS gradient would land on brown. */
  background: linear-gradient(90deg, #00c853, #d8e000 50%, #ff1e1e);
}
.hz-cline.is-far { opacity: 0.3; }
/* the measured walks: a soft heat blob, which is how they actually draw */
.hz-cblob {
  width: 17px; height: 12px; border-radius: 6px;
  background: radial-gradient(circle at 50% 50%, #ff5a2b 0%, #f7b500 32%, #00c853 62%, rgba(0, 200, 83, 0) 85%);
}
.hz-popover {
  position: fixed; z-index: 1200; max-width: 240px; background: #fff;
  border: 1px solid #e6dff4; border-radius: var(--radius-sm); padding: 13px 16px;
  box-shadow: 0 16px 40px -16px rgba(var(--shadow-rgb), 0.45);
}
.hz-popover h4 { margin: 0 0 6px; padding-right: 14px; font-size: 0.95rem; color: var(--ink); }
.hz-popover p { margin: 0; font-size: 0.82rem; line-height: 1.45; color: var(--ink-soft); }
.hz-pop-close {
  position: absolute; top: 6px; right: 9px; border: none; background: none;
  font-size: 1.15rem; line-height: 1; color: #b0aac0; cursor: pointer;
}
.map-badge-live { background: #e3f7ee; color: #178f63; }
.map-hint { margin: 8px 0 0; font-size: 0.78rem; color: #8a849c; text-align: center; }

/* the free door into the citywide map, sitting under the hero teaser */
.map-open {
  display: block; margin: 12px 4px 2px; padding: 12px 16px;
  border-radius: var(--radius-pill); background: var(--brand); color: #fff;
  text-align: center; text-decoration: none;
  font-weight: var(--wt-bold); font-size: 0.95rem;
  box-shadow: var(--shadow-sm);
}
.map-open:hover { background: var(--brand-dk); }

/* full-screen citywide map — free, opened from any [data-open-map] or #map */
body.city-open { overflow: hidden; }
.city-overlay { position: fixed; inset: 0; z-index: 1000; display: flex; flex-direction: column; background: #e9eef2; }
/* The bar used to be a full-width white strip that cost a whole row of screen
   on a phone. It is now transparent chrome: the title is a pill only as wide as
   its text, the close button floats, and the map owns the rest of the height. */
.city-bar {
  position: absolute; top: 0; left: 0; right: 0; z-index: 6;
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px; background: transparent; border-bottom: none;
  pointer-events: none; /* the map keeps the space between the pills */
}
.city-bar > * { pointer-events: auto; }
.city-bar .blk-search { margin: 0 auto 0 0; flex: 0 1 190px; }
/* the map's own chrome starts below the floating bar */
.city-overlay .hz-toggles { top: 52px; }
.city-title {
  font-weight: var(--wt-bold); color: var(--ink); font-size: 0.9rem;
  background: rgba(255, 255, 255, 0.82); padding: 5px 12px; border-radius: var(--radius-pill);
  backdrop-filter: blur(3px);
}
.city-close {
  border: none; background: #f0ecf9; width: 34px; height: 34px; border-radius: 50%;
  font-size: 1.3rem; line-height: 1; color: var(--ink-soft); cursor: pointer;
}
.city-overlay .hz-mount { flex: 1; width: auto; aspect-ratio: auto; border-radius: 0; }
/* map + card share the space below the bar; min-height:0 so the flex child is
   allowed to shrink instead of pushing the bar off-screen on short viewports */
.city-body { position: relative; flex: 1; min-height: 0; display: flex; }

.city-overlay[hidden] { display: none; }

/* ============================================================
   Address search + the shareable block card (hkz-21)
   ============================================================ */
.blk-search {
  position: relative; display: flex; gap: 6px; align-items: center;
  flex: 1 1 420px; max-width: 460px; margin: 0 14px;
}
.blk-search input {
  flex: 1; min-width: 0; font: inherit; font-size: 0.92rem;
  padding: 9px 13px; border: 1.5px solid #e3dcf2; border-radius: var(--radius-pill);
  background: #faf8ff; color: var(--ink); -webkit-appearance: none;
}
.blk-search input:focus { outline: none; border-color: var(--brand); background: #fff; }
.blk-search button {
  font: inherit; font-weight: var(--wt-bold); font-size: 0.85rem; white-space: nowrap;
  padding: 9px 15px; border: none; border-radius: var(--radius-pill);
  background: var(--brand); color: #fff; cursor: pointer;
}
.blk-search button:hover { background: var(--brand-dk); }
/* suggestion list — GeoSearch autocomplete, keyboard navigable */
.blk-sug {
  position: absolute; top: calc(100% + 6px); left: 0; right: 0; z-index: 20;
  margin: 0; padding: 6px; list-style: none; background: #fff;
  border: 1px solid #e6dff4; border-radius: var(--radius-sm); box-shadow: var(--shadow-sm);
  max-height: 46vh; overflow-y: auto;
}
.blk-sug[hidden] { display: none; }
.blk-sug li {
  padding: 8px 11px; border-radius: 10px; cursor: pointer;
  font-size: 0.88rem; color: var(--ink); line-height: 1.25;
}
.blk-sug li small { display: block; color: var(--ink-soft); font-size: 0.76rem; }
.blk-sug li[aria-selected="true"], .blk-sug li:hover { background: var(--bg-soft); }
.blk-sug li.is-msg { cursor: default; color: var(--ink-soft); }
.blk-sug li.is-msg:hover { background: none; }

/* ---- the same field, as the hero's second door (hkz-50) ------------------
   Deliberately the SAME .blk-search component as the overlay's — one look,
   one stylesheet, one behaviour, and app.js drives both from one wireSearch.
   Only what the hero context needs is overridden: it is a block rather than a
   bar item, so the base rule's `flex` and its `margin: 0 14px` (which would
   push it 14px past the hero's own gutter and overflow a 360px phone) both
   have to go. It lives HERE, after the component, because these are the rules
   that have to win the cascade — up in the hero section they lost to it.
   `max-width` matches .hero-ctas so the two doors share a left edge. */
.hero-search {
  flex: none; margin: 18px 0 0; max-width: 420px; width: 100%;
}
.hero-search input { padding: 11px 15px; font-size: 0.98rem; }
.hero-search button { padding: 11px 17px; }
@media (max-width: 880px) {
  /* the phone ORDER is in the responsive block with the rest of the hero's;
     only the spacing is here, where it can outrank .blk-search's margin. */
  .hero-search { margin-top: 14px; }
}
@media (max-width: 389px) {
  /* "Find my block" + a usable field do not both fit on the smallest phone */
  .hero-search input { padding: 10px 13px; font-size: 0.94rem; }
  .hero-search button { padding: 10px 12px; font-size: 0.8rem; }
}

/* ---- the address search rides in the floating bar, beside the title ------
   Its field is sized to the title pill so the two read as one row of chrome. */
.city-bar .blk-search input {
  padding: 5px 13px; font-size: 0.85rem; background: rgba(255, 255, 255, 0.95);
  box-shadow: 0 4px 14px -6px rgba(var(--shadow-rgb), 0.4);
}
.city-bar .blk-search button { padding: 5px 12px; font-size: 0.78rem; }
@media (max-width: 640px) {
  .hz-toggles { gap: 6px; max-width: calc(100% - 20px); }
  .hz-seg button { padding: 5px 10px; }
  /* The DEV display adds PM2.5 / VOC / Temp / Humidity to the metric segment,
     which puts seven buttons in a pill that cannot fit 390px and pushed the
     whole map toolbar off the right edge (mobile_layout_tests.py caught it,
     pre-existing since the air metrics landed). Prod only ever shows three, so
     this never affected a visitor. Wrapping keeps every control reachable
     without a horizontal scroll nobody would discover. */
  .hz-toggles .hz-seg { flex-wrap: wrap; max-width: 100%; }
  .city-bar { padding: 8px 12px; }
}

/* The card sits over the map's top-left corner, which is where the map keeps
   its own toggles — push them clear rather than letting the card bury them. */
.city-body.has-card .hz-toggles { left: 376px; max-width: calc(100% - 536px); }

.blk-card {
  position: absolute; z-index: 6; top: 14px; left: 14px; width: 348px;
  max-height: calc(100% - 28px); overflow-y: auto;
  background: var(--surface); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 18px 18px 16px;
}
.blk-card[hidden] { display: none; }
.blk-x {
  position: absolute; top: 10px; right: 10px; border: none; background: #f0ecf9;
  width: 28px; height: 28px; border-radius: 50%; font-size: 1.1rem; line-height: 1;
  color: var(--ink-soft); cursor: pointer;
}
.blk-card h3 { margin: 0 26px 2px 0; font-size: 1.12rem; line-height: 1.25; }
.blk-where { margin: 0 0 12px; color: var(--ink-soft); font-size: 0.82rem; }

/* THE credibility line: measured or modelled, never blurred together. */
.blk-badge {
  display: inline-flex; align-items: center; gap: 6px; margin-bottom: 12px;
  padding: 5px 11px; border-radius: var(--radius-pill); font-size: 0.75rem; font-weight: var(--wt-bold);
}
.blk-badge.is-walked { background: #e4f8ef; color: #12805a; }
.blk-badge.is-model  { background: #fff2d9; color: #8a5b00; }

.blk-big { display: flex; align-items: baseline; gap: 7px; }
.blk-db { font-size: 2.5rem; font-weight: 900; line-height: 1; color: var(--ink); }
.blk-unit { font-size: 1rem; font-weight: var(--wt-bold); color: var(--ink-soft); }
.blk-src { margin: 5px 0 14px; font-size: 0.78rem; color: var(--ink-soft); }

.blk-h4 { margin: 0 0 7px; font-size: 0.78rem; font-weight: var(--wt-bold); color: var(--ink-soft);
  text-transform: uppercase; letter-spacing: 0.04em; }
/* SCORE BY HOUR (hkz-51): the chart, and the all-day honk rank beside it.
   The row is a chart that gives way and a figure that does not — the number is
   two digits and must never wrap, so the SVG takes whatever is left. At 360 the
   card is a full-width sheet (330px of content), which leaves the chart ~240px:
   the same as its viewBox, so nothing is scaled down to unreadable. */
/* THE TWO MODELLED SCORES, AS EQUALS. Same box, same type, same weight, side by
   side on one row — the point of the row is that neither is the headline. They
   are both 0-100 ranks against modelled NYC streets, which is what makes putting
   them on one scale honest; the measured dB above is a different quantity and
   deliberately keeps its own, larger treatment.
   1fr 1fr rather than flex: equal columns regardless of whether one reads "8"
   and the other "100". */
.blk-pair {
  display: grid; grid-template-columns: 1fr 1fr; gap: 9px;
  margin: 12px 0 14px;
}
.blk-score {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: 0; padding: 9px 8px; border-radius: var(--radius-sm);
  background: #f6f2ff; border: 1px solid #ece5fb;
}
.blk-score-lab {
  font-size: 0.66rem; font-weight: var(--wt-bold); color: var(--ink-soft);
  text-transform: uppercase; letter-spacing: 0.04em;
}
.blk-score-val { font-size: 1.9rem; font-weight: 900; line-height: 1.1; color: var(--ink); }
.blk-score-sub { font-size: 0.64rem; color: var(--ink-soft); white-space: nowrap; }
/* the chart is now the full width of the card — the honk rank moved up */
.blk-chart { min-width: 0; }
.blk-svg { display: block; width: 100%; height: auto; overflow: visible; }
.bc-grid { stroke: #ece7f6; stroke-width: 1; }
.bc-dot { stroke: var(--surface, #fff); stroke-width: 1.2; }
.bc-line { fill: none; stroke: #b9aee0; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.bc-ax { font-size: 8.5px; fill: var(--ink-soft); }
.bc-val { font-size: 10px; font-weight: 800; fill: var(--ink); }
.bc-lab { font-size: 10px; fill: var(--ink-soft); }
/* the heading and the loudest-hour caption share one line (hkz-51) */
.blk-h4row { display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; margin-bottom: 5px; }
.blk-h4row .blk-h4 { margin: 0; }
.blk-loudest { margin: 0; font-size: 0.8rem; font-weight: 700; text-align: right; }
.blk-fine { margin: 0 0 14px; font-size: 0.72rem; color: var(--ink-soft); line-height: 1.4; }

/* the modelled-block ask, immediately above the Book a walk button */
.blk-ask {
  margin: 0 0 12px; padding: 11px 13px; border-radius: var(--radius-sm);
  background: #fff7e8; border: 1px solid #ffe4b8;
  font-size: 0.82rem; line-height: 1.45; color: #6b4f14;
}
.blk-ask strong { color: #8a5b00; }

.blk-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; }
.blk-btn {
  font: inherit; font-size: 0.82rem; font-weight: var(--wt-bold); padding: 9px 13px;
  border-radius: var(--radius-pill); border: 1.5px solid #e3dcf2; background: #faf8ff;
  color: var(--ink); cursor: pointer; text-decoration: none; display: inline-block;
}
.blk-btn:hover { border-color: var(--brand); }
.blk-btn.is-primary { background: var(--brand); border-color: var(--brand); color: #fff; }
.blk-btn.is-primary:hover { background: var(--brand-dk); }

.blk-cap { border-top: 1px solid #efeaf8; padding-top: 13px; }
.blk-cap p { margin: 0 0 9px; font-size: 0.82rem; color: var(--ink-soft); line-height: 1.45; }
.blk-cap form { display: flex; gap: 7px; }
.blk-cap input {
  flex: 1; min-width: 0; font: inherit; font-size: 0.85rem; padding: 9px 12px;
  border: 1.5px solid #e3dcf2; border-radius: var(--radius-pill); background: #faf8ff; color: var(--ink);
}
.blk-cap input:focus { outline: none; border-color: var(--brand); background: #fff; }
.blk-note { margin: 8px 0 0; font-size: 0.76rem; color: #b3261e; }
.blk-note.is-success { color: #12805a; }

/* phone: bar wraps, card becomes a bottom sheet */
@media (max-width: 720px) {
  /* hkz-37: the bar is allowed to wrap, but at 360px the title + address field
     + close button came to 362px and the CLOSE BUTTON was the item that wrapped
     — onto the map's own toggle strip, where it sat on top of the metric
     segment. Keep the three on one line and let the address field shrink; it
     still takes the whole bar on focus (the title and close hide there). */
  .city-bar { flex-wrap: nowrap; }
  .city-bar .blk-search { min-width: 0; }
  .city-bar .blk-search input { min-width: 0; }
  .city-bar:focus-within { flex-wrap: wrap; }
  /* On a phone the card is a sheet over two thirds of the map, which leaves
     the toggles a strip they cannot be used in — and that strip is the only
     map the visitor can still see. Give it to the map; closing the card brings
     the toggles back. */
  .city-body.has-card .hz-toggles { display: none; }
  .hz-cov { width: 158px; font-size: 0.64rem; }
  /* At 390px two legend rows plus a note is the whole bottom of the map. Shrink
     the type and the bar rather than dropping a row: the second row is the
     point of the change, and the note is what stops the two units being read
     against each other. */
  /* the bottom padding is the basemap attribution's room: the legend now spans
     the width and the attribution sits in its bottom-right corner. */
  .hz-legend {
    font-size: 0.66rem; padding: 5px 9px 15px; gap: 3px; max-width: calc(100vw - 20px);
  }
  .hz-lrow { gap: 5px; }
  .hz-lwhat { min-width: 0; font-size: 0.58rem; }
  .hz-lbar { width: 42px; }
  .hz-lnote2 { max-width: none; font-size: 0.6rem; }
  .city-bar { flex-wrap: wrap; gap: 8px; padding: 9px 10px; }
  .city-title { font-size: 0.85rem; }
  /* compact next to the title; while it is in use it takes the entire bar and
     the title + close button give up their space rather than wrapping it. */
  .city-bar .blk-search { flex: 0 1 128px; transition: flex-basis 0.18s ease; }
  .city-bar:focus-within .blk-search { flex: 1 1 100%; }
  /* THE CLOSE BUTTON NEVER HIDES. It used to be in this rule with the title,
     and on a phone that made the overlay a trap: focus stays in the field while
     the keyboard is up, so the one exit from a full-screen map disappeared —
     and the "Find my block" button appeared roughly where it had been, so a tap
     aimed at × hit a CTA instead, blurred the field, and snapped the bar back.
     That is the flicker. The field does not need this space anyway: on
     focus-within the bar wraps and .blk-search takes a row of its own, so the
     title and × keep the first row between them. */
  .city-bar:focus-within .city-title { display: none; }
  /* ...but it must not wrap DOWN either. The field is `flex: 1 1 100%` on
     focus, so it claims the first row and the × lands on the second — on top
     of the map's metric strip, which is hkz-37's original bug. `order: -1`
     puts the × ahead of the field so it keeps the first row to itself, and
     `margin-left: auto` holds it in the top-right corner it has always been
     in; the field then wraps to a full-width row underneath. */
  .city-bar:focus-within .city-close { order: -1; margin-left: auto; }
  .city-bar .blk-search button { display: none; padding: 5px 11px; font-size: 0.78rem; }
  .city-bar:focus-within .blk-search button { display: inline-block; }
  .blk-card {
    top: auto; bottom: 0; left: 0; right: 0; width: auto;
    max-height: 66%; border-radius: var(--radius) var(--radius) 0 0;
    padding: 15px 15px 18px;
  }
  .blk-actions .blk-btn { flex: 1; text-align: center; }
}

/* basemap attribution (bottom-right of the map) */
.hz-attr {
  position: absolute; bottom: 6px; right: 8px; z-index: 3;
  background: rgba(255, 255, 255, 0.82); border-radius: 6px;
  padding: 2px 7px; font-size: 0.62rem; color: var(--ink-soft);
}
.hz-attr a { color: var(--ink-soft); }

/* dev-only time-range filter panel (localhost / ?vip) */
.hz-dev {
  position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%); z-index: 3;
  display: flex; gap: 6px; align-items: center; flex-wrap: wrap; justify-content: center;
  background: rgba(255, 255, 255, 0.94); border-radius: 10px; padding: 6px 10px;
  box-shadow: 0 4px 14px -6px rgba(var(--shadow-rgb), 0.4);
  font-size: 0.72rem; font-weight: 700; color: var(--ink-soft); max-width: calc(100% - 20px);
}
.hz-dev:empty { display: none; }
.hz-dev input {
  font: inherit; font-weight: 500; padding: 3px 6px;
  border: 1px solid #d8d2ea; border-radius: 7px; color: var(--ink);
}
.hz-dev .hz-treset {
  cursor: pointer; border: 1px solid #d8d2ea; background: #f4f1fb;
  border-radius: 7px; padding: 3px 9px; font: inherit; font-weight: 700; color: var(--brand);
}

/* dev: editable endpoint values and colours inside the legend (hkz-41).
   Session-only — there is nothing behind these but the running page. */
.hz-legend input {
  width: 3.6em; font: inherit; font-weight: 700; padding: 1px 4px;
  border: 1px solid #d8d2ea; border-radius: 6px; color: var(--ink);
  -moz-appearance: textfield;
}
.hz-legend input.hz-lnum { width: 4.1em; }
.hz-legend input.hz-lcol {
  width: 18px; height: 18px; padding: 0; border-radius: 4px;
  border: 1px solid #d8d2ea; background: none; cursor: pointer;
}
.hz-legend input.hz-lcol::-webkit-color-swatch-wrapper { padding: 1px; }
.hz-legend input.hz-lcol::-webkit-color-swatch { border: none; border-radius: 3px; }
.hz-legend input::-webkit-outer-spin-button,
.hz-legend input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* dev env: dev/prod display-format toggle (bottom-right, ~90% height) */
.hz-format {
  position: absolute; top: 90%; right: 12px; transform: translateY(-50%); z-index: 4;
  cursor: pointer; font: inherit; font-size: 0.72rem; font-weight: var(--wt-bold);
  padding: 5px 11px; border-radius: var(--radius-pill); border: 1px dashed #b0aac0;
  background: rgba(255, 255, 255, 0.92); color: #6c5ce7;
  box-shadow: 0 4px 14px -6px rgba(var(--shadow-rgb), 0.4);
}
.hz-format:hover { border-color: var(--brand); }

/* ============================================================
   Sample report — the proof of what a booked walk buys
   ============================================================ */
.sample-wrap { max-width: 900px; margin: 0 auto; text-align: center; }

.sample-pages {
  display: grid; grid-template-columns: 1fr 1fr; gap: 22px;
  align-items: start;
}
.sample-page {
  display: block; width: 100%; padding: 0; margin: 0;
  border: 0; background: none; font: inherit; color: inherit;
  cursor: zoom-in; text-align: center;
}
.sample-page img {
  display: block; width: 100%; height: auto;
  border-radius: var(--radius-sm);
  border: 1px solid #E7E0F5;
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  transition: transform .15s, box-shadow .15s;
}
.sample-page:hover img { transform: translateY(-4px); box-shadow: var(--shadow); }
.sample-cap {
  display: block; margin-top: 10px;
  font-size: 0.88rem; font-weight: 700; color: var(--ink-soft);
}


.sample-open {
  display: inline-flex; align-items: baseline; gap: 10px;
  margin-top: 22px; padding: 13px 24px;
  border-radius: var(--radius-pill); background: var(--brand); color: #fff;
  font-weight: var(--wt-bold); text-decoration: none;
  box-shadow: var(--shadow-sm);
  transition: background .15s, transform .15s;
}
.sample-open:hover { background: var(--brand-dk); transform: translateY(-2px); }
.sample-size { font-weight: 600; font-size: 0.85rem; opacity: .8; }

/* page viewer overlay */
.sample-overlay {
  position: fixed; inset: 0; z-index: 60;
  background: #211B2E; /* solid: this is a document viewer, not a peek */
  display: flex; flex-direction: column;
}
.sample-overlay[hidden] { display: none; }
.sample-bar {
  flex: none; display: flex; align-items: center; gap: 14px;
  padding: 10px clamp(12px, 3vw, 22px); color: #fff;
}
.sample-bar-title {
  font-weight: var(--wt-bold); font-size: 0.95rem;
  min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sample-bar-pdf {
  flex: none; white-space: nowrap; margin-left: auto; color: #fff; font-weight: 700; font-size: 0.9rem;
  text-decoration: underline; text-underline-offset: 3px;
}
.sample-close {
  flex: none; width: 36px; height: 36px; border: 0; border-radius: 50%;
  background: rgba(255,255,255,.14); color: #fff;
  font-size: 1.4rem; line-height: 1; cursor: pointer;
}
.sample-close:hover { background: rgba(255,255,255,.26); }
.sample-stage {
  flex: 1; min-height: 0; overflow: auto;
  padding: 0 clamp(8px, 3vw, 22px) clamp(12px, 3vw, 22px);
}
.sample-stage img {
  display: block; margin: 0 auto; width: auto; max-width: 100%;
  border-radius: 10px; background: #fff;
}

@media (max-width: 640px) {
  /* one page at a time on a phone — two 170px-wide pages read as nothing */
  .sample-pages { grid-template-columns: 1fr; gap: 26px; max-width: 380px; margin: 0 auto; }
  .sample-open { width: 100%; justify-content: center; }
  /* full-bleed page in the viewer, scrolled vertically */
  .sample-stage img { width: 100%; }
  /* the neighbourhood name is on the page itself — drop it from the bar so
     the title cannot wrap into the PDF link and the close button */
  .sample-bar-where { display: none; }
}

/* ============================================================
   PRODUCT NEWS + WHAT IS NEXT (the two plain captures)
   Both are ordinary email boxes, deliberately quieter than the
   block card's capture: that one is the page's conversion, and
   these must not compete with it for the eye.
   ============================================================ */
/* `var(--wrap)` does not exist — the site's width variable is `--maxw` — so this
   resolved to nothing and the band ran the full window while every section
   beside it stopped at 1120px. Same width, same horizontal padding as
   `.section`, so it lines up with the cards above and below it. */
.news-band {
  max-width: var(--maxw); margin: 0 auto;
  padding: 0 clamp(18px, 4vw, 36px) 6px;
}
.news-form {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px 22px;
  background: var(--surface); border: 1px solid var(--hairline);
  border-radius: var(--radius); padding: 16px 20px; box-shadow: var(--shadow-sm);
}
.news-copy h3 { margin: 0; font-size: 1rem; }
.news-copy p { margin: 2px 0 0; color: var(--ink-soft); font-size: 0.86rem; }
.news-row { display: flex; gap: 8px; margin-left: auto; }
.news-row input {
  border: 1px solid var(--hairline-strong); border-radius: var(--radius-pill);
  padding: 9px 14px; font: inherit; font-size: 0.9rem; min-width: 0; width: 15rem;
  background: var(--bg);
}
.news-row input:focus { outline: none; border-color: var(--brand); background: var(--surface); }
.news-row button, .idea-row button {
  border: none; border-radius: var(--radius-pill); background: var(--brand);
  color: var(--on-brand); font: inherit; font-weight: var(--wt-bold);
  font-size: 0.88rem; padding: 9px 18px; cursor: pointer; white-space: nowrap;
}
.news-row button:hover, .idea-row button:hover { background: var(--brand-dk); }
.news-note { flex-basis: 100%; margin: 0; font-size: 0.82rem; color: var(--accent); }
.news-note.is-success { color: var(--ink-soft); }
.news-form.is-done .news-row button, .idea-form.is-done .idea-row button {
  background: var(--hairline-strong); cursor: default;
}

.next-strip { margin-top: 30px; }
.next-strip h3 { margin: 0; font-size: 1.15rem; }
.next-lede { margin: 4px 0 14px; color: var(--ink-soft); font-size: 0.92rem; }
.next-list {
  list-style: none; margin: 0 0 18px; padding: 0;
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px;
}
.next-list li {
  background: var(--bg-soft); border: 1px solid var(--hairline);
  border-radius: var(--radius-sm); padding: 12px 14px;
  font-size: 0.86rem; color: var(--ink-soft); line-height: 1.45;
}
.next-list b { display: block; color: var(--ink); font-size: 0.92rem; }
.next-icon { font-size: 1.1rem; }

.idea-form {
  background: var(--surface); border: 1px solid var(--hairline);
  border-radius: var(--radius); padding: 16px 20px; box-shadow: var(--shadow-sm);
}
.idea-form h4 { margin: 0 0 10px; font-size: 1rem; }
.idea-form textarea {
  width: 100%; resize: vertical; font: inherit; font-size: 0.9rem;
  border: 1px solid var(--hairline-strong); border-radius: var(--radius-sm);
  padding: 10px 12px; background: var(--bg);
}
.idea-form textarea:focus { outline: none; border-color: var(--brand); background: var(--surface); }
.idea-row { display: flex; gap: 8px; margin-top: 10px; }
.idea-row input {
  flex: 1 1 auto; min-width: 0; border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-pill); padding: 9px 14px; font: inherit;
  font-size: 0.9rem; background: var(--bg);
}
.idea-row input:focus { outline: none; border-color: var(--brand); background: var(--surface); }

@media (max-width: 860px) {
  .next-list { grid-template-columns: repeat(2, 1fr); }
  .news-row { margin-left: 0; flex-basis: 100%; }
  /* `flex: 1 1 0`, not `1 1 auto`: with `auto` the basis is the 15rem width and
     the row refuses to shrink below input + button, which is what pushed the
     page 5px wider than a 390px phone (caught by mobile_layout_tests.py). */
  .news-row input { width: auto; flex: 1 1 0; min-width: 0; }
}
@media (max-width: 480px) {
  .next-list { grid-template-columns: 1fr; }
  /* the gutters are the last thing to give: a phone has 390px and the band was
     spending 84 of them on padding before the field got any */
  .news-band { padding-left: 14px; padding-right: 14px; }
  .news-form, .idea-form { padding: 14px; }
  /* stack rather than squeeze: a 143px button beside a field leaves the field
     unusable at this width */
  .news-row, .idea-row { flex-wrap: wrap; }
  .news-row input, .idea-row input { flex: 1 1 100%; }
  .news-row button, .idea-row button { flex: 1 1 100%; }
}

/* ============================================================
   /contact — one form, nothing else. The page exists so a customer
   whose confirmation email never arrived has somewhere to go, and
   so the booking code has somewhere to be typed.
   ============================================================ */
.ct-wrap {
  max-width: 620px; margin: 0 auto;
  padding: clamp(28px, 5vw, 56px) clamp(18px, 4vw, 36px) 40px;
}
.ct-wrap h1 { margin: 0 0 8px; font-size: clamp(1.6rem, 4vw, 2.2rem); }
.ct-lede { margin: 0 0 22px; color: var(--ink-soft); line-height: 1.55; }
.ct-form {
  background: var(--surface); border: 1px solid var(--hairline);
  border-radius: var(--radius); padding: 20px; box-shadow: var(--shadow-sm);
  display: flex; flex-direction: column;
}
.ct-form label {
  font-size: 0.72rem; font-weight: var(--wt-bold); text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--ink-soft); margin-bottom: 5px;
}
.ct-form label + * { margin-bottom: 16px; }
.ct-form textarea, .ct-form input {
  font: inherit; font-size: 0.95rem; width: 100%;
  border: 1px solid var(--hairline-strong); border-radius: var(--radius-sm);
  padding: 10px 12px; background: var(--bg);
}
.ct-form textarea { resize: vertical; }
.ct-form textarea:focus, .ct-form input:focus {
  outline: none; border-color: var(--brand); background: var(--surface);
}
.ct-form button {
  align-self: flex-start; border: none; border-radius: var(--radius-pill);
  background: var(--brand); color: var(--on-brand); font: inherit;
  font-weight: var(--wt-bold); padding: 11px 26px; cursor: pointer;
}
.ct-form button:hover { background: var(--brand-dk); }
.ct-form.is-done button { background: var(--hairline-strong); cursor: default; }
.ct-note { margin: 12px 0 0; font-size: 0.86rem; color: var(--accent); }
.ct-note.is-success { color: var(--ink-soft); }

/* the same form, in a band at the foot of the landing page. Centred like the
   sections around it: left-aligned it read as a different site. */
.ct-band {
  max-width: var(--maxw); margin: 0 auto;
  padding: clamp(24px, 4vw, 40px) clamp(18px, 4vw, 36px) 10px;
  text-align: center;
}
.ct-band h2 { margin: 0 0 4px; font-size: clamp(1.5rem, 3vw, 2rem); }
.ct-band .ct-lede { margin: 0 auto 16px; font-size: 0.98rem; max-width: 46ch; }
/* the form itself stays left-aligned inside its card: a label above a field is
   read down the left edge, not from the middle. */
.ct-band .ct-form { max-width: 620px; margin: 0 auto; text-align: left; }

/* the booked page's reference: the one identifier that survives a typo */
.bk-refbox { margin-top: 12px; line-height: 1.6; }
.bk-refbox strong { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.9rem; }
.bk-refsave { color: var(--ink-soft); font-size: 0.88rem; }
.bk-refbox a { margin-left: 6px; font-size: 0.88rem; font-weight: var(--wt-bold); }

/* ============================================================
   THE PHONE MENU (and the two page links above the Pro band)

   Below 880px the four plain nav links are display:none, which used to make
   /analysis, and the three in-page anchors, unreachable on a phone. This is the
   way back to them. It is a <details>, so it needs no JS: it toggles, takes the
   keyboard, and closes on Escape on its own.
   ============================================================ */
.nav-menu { position: relative; display: none; }
.nav-menu > summary {
  list-style: none;
  cursor: pointer;
  width: 34px;
  height: 30px;
  display: grid;
  place-items: center;
  border-radius: var(--radius-sm);
  border: var(--border-w) solid var(--hairline-strong);
  background: var(--bg-soft);
}
.nav-menu > summary::-webkit-details-marker { display: none; }
/* three bars, drawn rather than typed: a glyph would inherit the font's own
   metrics and sit off-centre at this size */
.nav-menu > summary > span {
  width: 15px;
  height: 2px;
  background: var(--ink);
  position: relative;
}
.nav-menu > summary > span::before,
.nav-menu > summary > span::after {
  content: "";
  position: absolute;
  left: 0;
  width: 15px;
  height: 2px;
  background: var(--ink);
}
.nav-menu > summary > span::before { top: -5px; }
.nav-menu > summary > span::after { top: 5px; }
.nav-menu[open] > summary { background: var(--brand); border-color: var(--brand); }
.nav-menu[open] > summary > span,
.nav-menu[open] > summary > span::before,
.nav-menu[open] > summary > span::after { background: var(--on-brand); }
.nav-menu-panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 60;
  min-width: 190px;
  display: flex;
  flex-direction: column;
  padding: 6px;
  background: var(--bg);
  border: var(--border-w) solid var(--hairline-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-panel, 0 8px 24px rgba(0, 0, 0, 0.12));
}
/* the panel's links are full-width rows, not the inline nav links they mirror */
.nav-menu-panel a {
  display: block;
  padding: 9px 12px;
  border-radius: var(--radius-sm);
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--ink);
  text-decoration: none;
}
.nav-menu-panel a:hover { background: var(--bg-soft); }

/* Two buttons, centred, no heading — see the template comment. */
.page-links {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
  margin: 48px auto 8px;
  max-width: var(--maxw);
  padding: 0 var(--pad);
}
.page-link {
  display: inline-block;
  padding: 11px 20px;
  border-radius: var(--radius-pill);
  border: var(--border-w) solid var(--hairline-strong);
  background: var(--bg);
  color: var(--ink);
  font-weight: 700;
  font-size: 0.95rem;
  text-decoration: none;
}
.page-link:hover { border-color: var(--brand); color: var(--brand); }

@media (max-width: 880px) {
  /* the links are hidden at this width, so the way to them appears */
  .nav-menu { display: block; }
}

/* ---- block card: share as a corner icon, and the walked card's offer ----
   hkz-49 follow-up. Share moved out of .blk-actions (it was competing with the
   only paid ask for that row) into the corner beside the ×; the book button
   moved UP under the badge on a walked card, where it clears the fold. */
.blk-icon {
  position: absolute; top: 10px; right: 44px; border: none; background: #f0ecf9;
  width: 28px; height: 28px; border-radius: 50%; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--ink-soft); cursor: pointer;
  transition: background .15s, color .15s;
}
.blk-icon:hover { background: var(--brand); color: #fff; }
.blk-icon.is-done { background: #d8f3e5; color: #10794f; }
.blk-offer { margin: 12px 0 4px; }
.blk-offer .blk-btn { display: inline-block; }
.blk-why {
  margin: 7px 0 0; font-size: 0.83rem; color: var(--ink-soft); max-width: 34em;
}
