  :root{
    --ink:#0F1A1C; --soft:#46565A; --mute:#5b6d71; --line:#E7ECEA; --paper:#EEF2F1; --card:#fff;
    --green:#1f5130; --green-bright:#16a34a; --orange:#E2772F; --violation:#C0392B;
    --font:"Inter",system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
    /* Aliases so the shared nav/auth markup (identical across pages) drops in unchanged. */
    --text:var(--ink); --text-secondary:var(--soft); --text-tertiary:var(--mute);
    --border:var(--line); --bg:var(--card); --bg-secondary:var(--paper);
    --green-dark:#174026; --radius:12px; --radius-sm:8px;
  }
  *{box-sizing:border-box}
  html,body{margin:0}
  body{font-family:var(--font);color:var(--ink);background:var(--paper);line-height:1.5}
  /* .wrap is also .hs-container: the width and side gutters come from layout.css (this page is the full-bleed tier). */
  a{color:var(--green)}

  /* NOTE: the page's old top-bar + auth-modal CSS was removed in the shell conversion — the shared
     left-sidebar shell (app.css + partials/shell.html) now owns nav, auth and share. Only map/page
     content CSS remains below, and it deliberately overrides app.css for this page's content. */

  .head{padding:34px 0 8px}
  .eyebrow{font-size:12px;letter-spacing:.02em;color:var(--mute);text-transform:none}
  h1{font-weight:700;font-size:30px;letter-spacing:-.02em;margin:6px 0 4px}
  .sub{color:var(--soft);font-size:15px;max-width:640px}

  /* The address field carries a VISIBLE label, not just a placeholder: a placeholder disappears
     the moment the resident types, so it cannot name the field while it is being filled in. The
     label sits above the field rather than inside it: the placeholder is now an EXAMPLE
     address, which is guidance, not the field's name. */
  .search-label{display:block;font-size:12.5px;font-weight:700;color:var(--ink);margin:18px 0 6px}
  /* The search control is ONE field: the submit button lives INSIDE the input as a
     magnifying-glass icon pinned to the far right. The full-width green "See development
     nearby" button that used to sit beside the field was removed — it read as the page's
     primary action when the primary action is typing an address, and on a phone it wrapped
     below and pushed the field narrow. Nothing about the SEARCH changed: the button kept
     its id (#go) and type="submit", so the click still goes through the form's own submit
     handler, Enter still submits, and the disabled-while-searching state still applies. */
  form.search{position:relative;display:block;margin:0 0 4px;max-width:640px}
  .search input{width:100%;box-sizing:border-box;font-family:var(--font);font-size:15px;
    padding:13px 52px 13px 15px;border:1px solid var(--line);border-radius:11px;background:#fff}
  .search input:focus{outline:none;border-color:var(--green-bright);box-shadow:0 0 0 3px #16a34a22}
  .search input[aria-invalid="true"]{border-color:var(--violation)}
  /* 44x44 is the TAP TARGET, not the artwork: the button is a transparent 44px square centred
     on the field's right edge and only the ~19px glyph inside it is drawn, so the minimum
     touch size is met without the field itself growing — the input's own height is unchanged
     (13px padding + 15px text ≈ 46px), which is why a 44px control fits inside it. */
  .search button{position:absolute;top:50%;right:3px;transform:translateY(-50%);
    width:44px;height:44px;display:flex;align-items:center;justify-content:center;padding:0;
    border:0;border-radius:9px;background:none;color:var(--green);cursor:pointer}
  .search button svg{width:19px;height:19px;display:block}
  .search button:hover{background:#16a34a14;color:#174026}
  .search button:focus-visible{outline:2px solid var(--green-bright);outline-offset:-2px}
  .search button:disabled{opacity:.45;cursor:default;background:none}
  /* Inline validation covers the ONE thing this page can judge client-side: an empty box.
     Anything typed is handed to the geocoder, which reports its own honest no-match — the
     field takes free-form text, so no address SHAPE is demanded here (the same reason the
     no-match copy suggests a city or ZIP rather than prescribing a triple). */
  .search-err{font-size:12.5px;color:var(--violation);margin-top:6px}

  /* "What is changing in my zip code?" — the email sign-up for this ZIP's Bluesky MAPS
     posts (founder, 2026-09-25). ZIP mode only: an address search is about a home, not a
     ZIP, and zipmode is the class the page already uses to say which one it is showing.
     It lives inside .head, so the Place-page embed hides it with the rest of the hero. */
  /* No width cap: the consent line (~660px at 12.5px) fits on ONE line on desktop (founder,
     2026-09-26). The old 640px cap wrapped "anytime." onto a second line. It still wraps
     where the screen is narrower than the sentence, so a phone never scrolls sideways. */
  .zip-email{display:none;margin:16px 0 4px}
  body.zipmode .zip-email{display:block}
  .zip-email-btn{min-height:44px;padding:11px 18px;border:0;border-radius:11px;
    background:var(--green);color:#fff;font-family:var(--font);font-size:15px;font-weight:700;
    cursor:pointer}
  .zip-email-btn:hover{background:var(--green-dark)}
  .zip-email-btn:focus-visible{outline:2px solid var(--green-bright);outline-offset:2px}
  .zip-email-btn:disabled{cursor:default;opacity:.7}
  .zip-email-btn.on{background:#fff;color:var(--green);border:1px solid var(--line);opacity:1}
  .zip-email-note{font-size:12.5px;color:var(--soft);margin-top:7px}
  .zip-email-msg{font-size:12.5px;color:var(--violation);margin-top:4px}
  .zip-email-msg:empty{display:none}
  /* Save the searched ADDRESS to My Places. Offered only once an address search has resolved
     (revealed from that success path) and never in ZIP mode. */
  .save-addr{display:none;margin:14px 0 4px}
  .save-addr:not([hidden]){display:block}
  body.zipmode .save-addr{display:none}
  .save-addr-btn{min-height:44px;padding:11px 18px;border:1px solid var(--green);border-radius:11px;
    background:#fff;color:var(--green);font:700 14px var(--font);cursor:pointer}
  .save-addr-btn:hover{background:#f1f8f4}
  .save-addr-btn:focus-visible{outline:2px solid var(--green-bright);outline-offset:2px}

  .status{margin:18px 0;font-size:14px;color:var(--soft)}
  .status.err{color:var(--violation)}

  .results{display:none;margin:8px 0 40px}
  .grid{display:grid;grid-template-columns:1.6fr 1fr;gap:18px;align-items:start}
  .grid.band{grid-template-columns:1fr 1fr 1fr}
  /* Development summary/map grid: stack so the counts summary sits ABOVE a full-width map. */
  .grid.stacked{grid-template-columns:1fr}
  .grid.stacked .read{order:-1}                          /* counts summary above the map */
  .grid.stacked .counts{grid-template-columns:repeat(4,1fr)}  /* wide count strip on top */
  @media(max-width:820px){.grid,.grid.band,.grid.stacked .counts{grid-template-columns:1fr}}
  @media(max-width:640px){.grid.stacked .counts{grid-template-columns:1fr 1fr}}
  .card{background:var(--card);border:1px solid var(--line);border-radius:16px;
    box-shadow:0 14px 34px -26px rgba(8,30,28,.5)}
  .mapcard{padding:14px}
  .map-bar{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;margin-bottom:11px;gap:10px}  /* wraps: at 390px the caption + 2D/3D switch were 27px wider than the card */
  .map-cap{font-weight:600;font-size:13px;color:var(--soft)}
  /* ZIP mode hides #radSel (body.zipmode .radsel). A ZIP is not a radius around a point,
     so there is no "widen the search" control. 2D / 3D aerial / 3D satellite all frame the
     records' own extent (ZIP_FRAME). Address mode keeps the radius chips. */
  .viewseg{display:inline-flex;background:#eef2f1;border:1px solid var(--line);border-radius:9px;overflow:hidden;flex:0 0 auto}
  .viewseg button{border:0;background:transparent;font:600 12px var(--font);color:var(--soft);padding:6px 13px;cursor:pointer}
  .viewseg button.on{background:var(--green);color:#fff}
  .map-frame{position:relative;height:600px;border-radius:14px;overflow:hidden}
  #map{position:absolute;inset:0;z-index:0}
  #mapInner{position:absolute;inset:0}
  .mapgl{position:absolute;inset:0;display:none;background:#0a1012;z-index:1}
  .mapgl.on{display:block}
  .mapgl.leaflet-container,.mapgl .leaflet-container{width:100%;height:100%;background:#0a1012}
  .map3d{position:absolute;inset:0;display:none;background:#0a1012;z-index:2}
  .map3d.on{display:block}
  .map3d canvas{display:block;width:100%;height:100%}
  /* In-frame loading / failure notice for the two 3D views. Sits ABOVE both 3D panels
     (z-index 5, under .mk3tip's 6) and is the reason a 3D view can never again present
     as a silent black rectangle. Hidden entirely when nothing is wrong, so a working
     map is pixel-identical to before. */
  .map-msg{position:absolute;inset:0;z-index:5;display:flex;align-items:center;justify-content:center;
    padding:24px;background:rgba(10,18,20,.92);
    -webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}
  .map-msg[hidden]{display:none}
  /* FALLBACK NOTICE — a strip, never a cover. When a 3D view fails the resident is put back
     on the 2D map, so the explanation must sit ABOVE that map without hiding it; covering it
     would trade a black rectangle for a dark one. Loading keeps the full cover, because the
     panel underneath it genuinely has nothing to show yet. */
  /* MEASURED GAP, fixed here: the strip sat across the top of the frame and the 2D map's
     zoom control sits top-left, so after a fallback elementFromPoint over the zoom button
     returned the NOTICE — the control was covered and unclickable, which fails "2D remains
     usable". Two changes, both minimal: start the strip clear of the control (left:56px,
     the control is ~34px wide at a 10px inset), and let clicks through the bar itself so
     nothing underneath is ever captured. The dismiss button re-enables pointer events for
     itself, because it is the one thing in the bar that must be clickable. */
  .map-msg.mm-bar{inset:auto 0 auto 56px;top:0;padding:10px 34px 10px 14px;background:rgba(10,18,20,.94);
    align-items:flex-start;justify-content:flex-start;pointer-events:none}
  .map-msg.mm-bar .mm-x{pointer-events:auto}
  .map-msg.mm-bar .mm-in{max-width:none;text-align:left}
  .map-msg .mm-in{max-width:430px;text-align:center;color:#eaf5f1;font-size:13px;line-height:1.55}
  .map-msg .mm-t{font-weight:600;font-size:14px;margin-bottom:6px}
  .map-msg .mm-x{position:absolute;top:8px;right:10px;border:0;background:transparent;cursor:pointer;
    color:#eaf5f1;font:600 15px var(--font);line-height:1;padding:4px 6px;opacity:.75}
  .map-msg .mm-x:hover{opacity:1}
  .map-msg .mm-sub{margin-top:3px;opacity:.85}
  /* The secondary line is optional by design — on a narrow screen the primary message and
     the map matter more than the requirement note, so it is dropped rather than allowed to
     grow the strip over the 2D map underneath it. */
  @media (max-width: 560px){ .map-msg .mm-sub{display:none} }
  .map-msg .mm-spin{width:22px;height:22px;margin:0 auto 10px;border-radius:50%;
    border:2px solid rgba(255,255,255,.25);border-top-color:#2dd4bf;animation:mmspin .9s linear infinite}
  @keyframes mmspin{to{transform:rotate(360deg)}}
  @media (prefers-reduced-motion: reduce){ .map-msg .mm-spin{animation:none} }
  .maplibregl-popup-content{font-family:var(--font);font-size:13px;border-radius:10px;padding:9px 12px}
  .maplibregl-popup-content .pt{font-weight:600;margin-bottom:2px}
  .maplibregl-popup-content .pk{font-size:11.5px;color:var(--mute)}
  .homepin{width:30px;height:30px;border-radius:9px;background:var(--green);border:2px solid #fff;
    display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 2px 7px rgba(0,0,0,.4)}
  .leaflet-marker-icon.homepin{margin-left:-15px;margin-top:-15px}
  .mk3tip{position:absolute;z-index:6;display:none;pointer-events:none;max-width:230px;
    background:rgba(10,18,20,.93);color:#eaf5f1;border:1px solid rgba(255,255,255,.15);
    border-radius:10px;padding:8px 11px;font-size:12px;line-height:1.45;box-shadow:0 6px 18px rgba(0,0,0,.45)}
  .mk3tip .n{font-weight:700;font-size:12.5px}
  .mk3tip .ty{opacity:.85}
  .mk3info{position:absolute;right:12px;top:12px;z-index:7;display:none;width:238px;
    background:rgba(10,18,20,.96);color:#eaf5f1;border:1px solid rgba(255,255,255,.16);
    border-radius:12px;padding:12px 13px 12px;box-shadow:0 10px 28px rgba(0,0,0,.55);font-size:12.5px}
  .mk3info .cl{position:absolute;right:8px;top:6px;background:none;border:0;color:#9fb6b0;
    font-size:17px;cursor:pointer;line-height:1;padding:2px}
  .mk3info .ci-n{font-weight:700;font-size:13.5px;padding-right:16px;line-height:1.3}
  .mk3info .ci-t{opacity:.85;margin-top:2px}
  .mk3info .ci-v{margin-top:7px;font-weight:600}
  .mk3info .ci-l{margin-top:9px;display:flex;flex-wrap:wrap;gap:4px 10px}
  .mk3info .ci-l a{color:#8fd8c8;text-decoration:none;font-weight:600}
  .hud3d{position:absolute;inset:0;pointer-events:none}
  .h3-hint{position:absolute;left:12px;top:11px;font-size:10.5px;color:#9fb6b0;letter-spacing:.02em}
  .ctrl3d{position:absolute;right:12px;bottom:11px;display:flex;align-items:center;gap:8px;pointer-events:auto}
  .c3-btn,.c3-time{background:rgba(12,20,22,.74);border:1px solid rgba(255,255,255,.16);border-radius:8px;
    color:#cfe8e2;font:600 11px var(--font);padding:5px 9px;cursor:pointer;display:flex;align-items:center;gap:6px}
  .c3-btn.on{background:var(--green);border-color:var(--green);color:#fff}
  .c3-time input{width:70px}
  /* +/- zoom buttons: a paired square-ish group so trackpad/touch users can zoom the
     3D scene without a scroll wheel (scroll still works). Font-size bumped, fixed width. */
  .zoom3d{display:flex;align-items:center;gap:4px;pointer-events:auto}
  .c3-z{padding:4px 0;width:26px;justify-content:center;font-size:15px;line-height:1}

  .read{padding:18px}
  .read h2{font-size:12px;letter-spacing:.02em;color:var(--mute);margin:0 0 4px;font-weight:600}
  .addr{font-weight:600;font-size:16px;margin-bottom:4px}
  /* BACK TO THE WHOLE ZIP. Address mode is entered by JS (run() flips ZIP_MODE) and the URL
     never changes, so before this existed a resident who searched an address had no control,
     no history entry and no visible route back to the whole-ZIP view. It is a real anchor, not
     a JS handler, so browser back/forward, middle-click and keyboard all behave normally. */
  /* One small clarifier under the heading pair. The heading pair itself is eyebrow + PLACE
     (".read h2" is a 12px muted label, ".addr" the 16px subject), so the explanatory sentence
     cannot live in either without inverting that hierarchy - it gets its own quiet line. */
  .scopenote{display:none;font-size:12px;color:var(--mute);margin:0 0 8px}
  .backzip{display:none;margin:2px 0 10px;font-size:12.5px}
  .backzip a{color:var(--ink);text-decoration:none;border-bottom:1px solid var(--line)}
  .backzip a:hover,.backzip a:focus{border-bottom-color:var(--ink)}
  .freshline{font-size:11.5px;color:var(--mute);margin-bottom:12px;min-height:14px}
  .freshline:empty{margin-bottom:0;min-height:0}
  .counts{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin-bottom:8px}
  .cc{background:#f6f9f7;border:1px solid var(--line);border-radius:11px;padding:11px 12px}
  .covnote{margin-top:12px;font-size:12.5px;line-height:1.5;color:var(--soft);
    background:#f0f6f3;border:1px solid var(--line);border-radius:10px;padding:9px 12px}
  .cc .n{font-weight:700;font-size:22px}
  .cc .k{font-size:11.5px;color:var(--soft);line-height:1.3}
  .cc.flag .n{color:var(--orange)}
  /* Open-for-comment banner — shown above the counts only when there's a live comment window. */
  .commentbar{display:flex;align-items:flex-start;gap:9px;margin:0 0 12px;padding:11px 13px;
    border:1px solid var(--orange);border-left-width:4px;border-radius:10px;
    background:#fdf1e8;color:#8a4113}
  .commentbar .cb-ic{flex:0 0 auto;font-size:15px;line-height:1.35}
  .commentbar .cb-tx{font-size:13px;line-height:1.4}
  .commentbar strong{font-weight:700}
  .commentbar .cb-when{font-weight:700;color:var(--orange)}

  .band{margin-top:22px}
  .band-h{font-weight:700;font-size:16px;color:#fff;
    margin:-18px -18px 0;padding:12px 18px;border-radius:16px 16px 0 0}
  /* The three band headers ARE stage swatches — same three colours as the legend, same
     stage names — so this tracks HS.LIFECYCLE_HEX. Left dark, an "Operating now" header
     in brand green would sit above "Operating now" pins in a different green. */
  .band-h.t-built{background:#00893a}
  .band-h.t-approved{background:#2563EB}
  .band-h.t-proposed{background:#E2772F}
  /* Lifecycle unknown — the legend's own neutral (HS.LIFECYCLE_HEX.unknown / statusHex.onfile). */
  .band-h.t-unknown{background:#706468}
  .band-s{font-size:12.5px;color:var(--mute);margin:10px 0}
  .rec{display:flex;gap:11px;align-items:flex-start;padding:11px 0;border-top:1px solid var(--line)}
  .rec .dot{width:10px;height:10px;border-radius:50%;margin-top:5px;flex:0 0 auto}
  .rec .t{font-weight:600;font-size:14px}
  .rec .s{font-size:12.5px;color:var(--soft)}
  .rec .decidedtag{font-weight:700;color:var(--mute)}
  /* DECISION NOTATION (2026-09-20 contract). A denied application stays in the Proposed
     rail, so its ruling has to be readable at a glance or the rail implies the ruling
     never happened. It is a block on its own line, not an inline muted chip, and it is
     NOT communicated by colour alone — the outcome word is always present in the text. */
  .rec .decnote{margin-top:3px;font-weight:700;color:#9a2f2f}
  .rec .decnote .decsrc{font-weight:600}
  .rec .decunv{margin-top:2px;font-size:11px;color:#5d6f72}
  .popdec{margin-top:4px;font-weight:700;color:#9a2f2f}
  .popdec .decsrc{font-weight:600}
  .popunv{margin-top:2px;font-size:11px;color:#5d6f72}
  .band-s .histnote{display:block;margin-top:2px;color:#5d6f72}
  .rec .vflag{display:inline-block;margin-top:4px;font-size:11px;color:var(--violation);
    background:#fdecec;border:1px solid #f5c9c9;border-radius:6px;padding:2px 7px}
  .empty{color:var(--mute);font-size:13px;padding:12px 0}
  .listmore{color:var(--mute);font-size:12.5px;padding:8px 0 2px}

  .foot{color:var(--mute);font-size:12px;margin:8px 0 40px;line-height:1.6}

  /* ── Property page (address dossier — case-study §4.3) ──────────────────────────
     Same visual language as the rest of the page: Inter, var(--green) #1f5130,
     var(--line) #E7ECEA borders, white cards, green/blue/orange lifecycle colors,
     and the existing "record ▸" link pattern. */
  #property{display:none;margin:8px 0 40px}
  .prop-head{padding:26px 0 6px}
  .prop-crumb{font-size:12.5px;color:var(--mute);margin-bottom:6px}
  .prop-crumb a{color:var(--green);text-decoration:none}
  .prop-crumb a:hover{text-decoration:underline}
  .prop-titlerow{display:flex;align-items:flex-start;justify-content:space-between;gap:14px;flex-wrap:wrap}
  .prop-h1{font-weight:700;font-size:26px;letter-spacing:-.02em;margin:0}
  .prop-actions{display:flex;gap:8px;flex:0 0 auto}
  .prop-sub{color:var(--soft);font-size:13.5px;margin:6px 0 14px}
  .prop-sub a{color:var(--green)}
  .statgrid{display:grid;grid-template-columns:repeat(4,1fr);gap:9px;margin-bottom:16px}
  @media(max-width:820px){.statgrid{grid-template-columns:1fr 1fr}}
  .prop-card{background:var(--card);border:1px solid var(--line);border-radius:16px;
    box-shadow:0 14px 34px -26px rgba(8,30,28,.5);padding:18px;margin-bottom:16px}
  .prop-card h3{font-size:12px;letter-spacing:.02em;color:var(--mute);margin:0 0 10px;font-weight:600;text-transform:uppercase}
  .entgrid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:14px}
  @media(max-width:820px){.entgrid{grid-template-columns:1fr}}
  .entcol .ec-h{font-size:11.5px;color:var(--mute);font-weight:600;margin-bottom:6px}
  .ent{padding:6px 0;border-top:1px solid var(--line);font-size:13.5px}
  .ent .en{font-weight:600;display:flex;align-items:center;gap:6px}
  .ent .em{font-size:12px;color:var(--mute)}
  /* Verified-negative note: "we checked and found no shared attribute" — same principle as
     the Also-checked line, so a non-connection is as legible as a connection. */
  .ent .en-absent{font-size:11.5px;color:var(--mute);font-style:italic;margin-top:2px;line-height:1.4}
  .linkdot{width:8px;height:8px;border-radius:50%;background:var(--orange);flex:0 0 auto;display:inline-block}
  /* Connection map (SVG, computed from entity_links evidence — never hand-arranged) */
  .entmap{margin:2px 0 6px;overflow-x:auto}
  .entmap svg{display:block;margin:0 auto;max-width:100%}
  .entlinks-h{font-size:11.5px;color:var(--mute);font-weight:600;margin:10px 0 2px;border-top:1px solid var(--line);padding-top:12px}
  .entlinks{margin-top:2px}
  .entlink{font-size:13px;color:var(--ink);padding:5px 0;line-height:1.55}
  .entlink a{color:var(--green)}
  .entlink .elbl{background:#FEF8F3;border:1px solid #F0D9C3;color:#8a5a2e;border-radius:6px;
    padding:1.5px 8px;font-size:11.5px;font-weight:600;white-space:nowrap}
  .conn-note{margin-top:10px;font-size:12px;color:var(--mute);border-top:1px solid var(--line);padding-top:9px}
  .roster-note{margin-top:10px;font-size:12px;color:var(--mute);border-top:1px solid var(--line);padding-top:9px}
  .pill{display:inline-block;font:700 10.5px var(--font);letter-spacing:.04em;color:#fff;
    border-radius:6px;padding:2.5px 8px;vertical-align:middle}
  .pill.built{background:var(--green)}
  .pill.approved{background:#2563EB}
  .pill.proposed{background:var(--orange)}
  .filing{display:flex;gap:14px;padding:12px 0;border-top:1px solid var(--line)}
  .filing .fdate{flex:0 0 92px;font-size:12px;color:var(--mute);padding-top:2px}
  .filing .ft{font-weight:600;font-size:14px;display:flex;align-items:center;gap:7px;flex-wrap:wrap}
  .filing .fs{font-size:12.5px;color:var(--soft);margin-top:3px;line-height:1.5}
  .filing .fmath{color:var(--mute)}
  .alsochecked{margin-top:10px;font-size:12px;color:var(--mute);border-top:1px solid var(--line);padding-top:10px}
  .actgrp{margin-top:4px}
  .actgrp .ag-h{font-size:11.5px;color:var(--mute);font-weight:600;margin:8px 0 2px}
  .actitem{padding:7px 0;border-top:1px solid var(--line);font-size:13px}
  .actitem .am{font-size:11.5px;color:var(--mute)}
  .tier2{margin-top:12px;border-top:1px solid var(--line);padding-top:10px;font-size:13px;color:var(--soft)}
  .leaflet-popup-content{font-family:var(--font);font-size:13px}
  .pt{font-weight:600;margin-bottom:2px}
  .pk{font-size:11.5px;color:var(--mute)}
  .maprow{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin:0 0 11px}
  .mapkey{display:flex;flex-wrap:wrap;gap:4px}
  .radsel{display:flex;align-items:center;gap:5px;flex-wrap:wrap}
  /* A radius needs an address-derived HOME centre. ZIP mode has none, so the control is not
     offered there - showing it would invite a circle around a ZIP centre. */
  body.zipmode .radsel{display:none}
  /* "From this place" re-frames the 3D view around the searched ADDRESS. In ZIP mode there
     is no address pin - a ZIP centroid is a page anchor, not a saved place - so the control
     promises a place the page cannot show. Written fail-closed (hidden unless `hashome` is
     set by a real address search) rather than "hidden in zipmode", so any future no-address
     state inherits the correct behaviour instead of leaking the control. Same standard the
     radius control already applies: "A RADIUS REQUIRES AN ADDRESS-DERIVED CENTRE." */
  body:not(.hashome) #homeViewBtn{display:none}
  .radsel .rlbl{font-size:11.5px;color:var(--mute);font-weight:600;margin-right:2px}
  .radsel button{border:1px solid var(--line);background:#f7faf8;font:600 12px var(--font);color:var(--soft);
    padding:4px 10px;border-radius:7px;cursor:pointer}
  .radsel button.on{background:var(--green);border-color:var(--green);color:#fff}
  /* LEGEND PILL — deliberately NEUTRAL, never a coloured block. A strongly coloured pill
     reads as a selected filter or a button, which is exactly the confusion this row must
     not create: the only thing carrying stage meaning here is the DOT. So the pill stays
     white/very-light-grey with a subtle border and dark charcoal label text. */
  .mapkey span{display:flex;align-items:center;gap:7px;white-space:nowrap;
    padding:4px 10px;border-radius:7px;cursor:pointer;user-select:none;font-size:12px;
    color:var(--ink);border:1px solid var(--line);background:#fff}
  .mapkey span:hover{background:#fafbfa;border-color:#d6ddda}
  .mapkey span:focus-visible{outline:2px solid var(--green-bright);outline-offset:2px}
  /* The `[aria-pressed="true"]` and `.off` rules that used to sit here are GONE. They dressed
     the toggle-button legend: Stage left it in #1098 and Type/Regulatory in #1101, so all three
     rows are now <label>-wrapped checkboxes and nothing on the page carries either hook —
     measured in the rendered DOM, not inferred: `[aria-pressed]` and `.off` each match 0
     elements. The live selected/unselected treatment is the shared chip block below
     (`.chipbox:checked~.ck` and `.typechip.on, .regchip.on, .stagechip.on`).
     ⚠️ `.mapkey span` above is NOT part of that legacy set — do not delete it with them. It
     matches 33 live elements (every chip's inner .ck/.dot/.ic/.t span) and is the only source
     of their `display:flex`/`gap`/`font-size`. Its pill background/border/padding are overridden
     by the id-scoped inner reset further down, which is why only these rules were removable. */
  /* THE STAGE DOT. A small solid circle and nothing else — no triangle, no marker
     silhouette, no warning glyph. Map-marker SHAPES are reserved for the Type row below,
     so nothing in this row can be mistaken for a pin or a project type. */
  .mapkey .dot{width:11px;height:11px;border-radius:50%;flex:0 0 auto;display:block;
    box-shadow:inset 0 0 0 1px rgba(0,0,0,.18)}
  /* ── STATUS CHIPS — a multi-select CHECKBOX group, not toggle buttons ───────────
     A checked chip is shown on the map. The chip is a <label> wrapping a REAL
     <input type=checkbox>, so focus order, Space-to-toggle and every screen reader's
     checkbox semantics come from the platform instead of from re-implemented ARIA.
     The input is visually hidden but NEVER display:none — a display:none input is
     unfocusable, which would quietly remove the whole row from keyboard use. */
  #mapkeyShapes .typechip, #mapkeyReg .regchip, #mapkey .stagechip{display:inline-flex;align-items:center;gap:7px;white-space:nowrap;
    padding:5px 11px;border-radius:7px;cursor:pointer;user-select:none;font-size:12px;
    color:var(--ink);border:1px solid var(--line);background:#fff;min-height:32px;position:relative}
  /* The pill styling belongs to the CHIP and to nothing inside it. `.mapkey span` is a
     DESCENDANT rule, so without this reset the dot and the label would each draw their
     own bordered, padded box inside the chip. */
  #mapkeyShapes .typechip>span, #mapkeyReg .regchip>span, #mapkey .stagechip>span{padding:0;border:0;background:none;min-height:0}
  #mapkeyShapes .typechip:hover, #mapkeyReg .regchip:hover, #mapkey .stagechip:hover{background:#fafbfa;border-color:#d6ddda}
  /* Focus rides on the CHIP: the input is clipped to 1px, and a focus ring drawn on a
     1px box is a focus ring nobody can see. */
  #mapkeyShapes .typechip:focus-within, #mapkeyReg .regchip:focus-within, #mapkey .stagechip:focus-within{outline:2px solid var(--green-bright);outline-offset:2px}
  #mapkeyShapes .chipbox, #mapkeyReg .chipbox, #mapkey .stagebox{position:absolute;width:1px;height:1px;margin:0;padding:0;border:0;
    opacity:0;pointer-events:none}
  /* THE CHECK MARK — the inclusion signal, and it is a SHAPE plus a GLYPH, never a hue.
     Unchecked is an empty outlined square; checked is a filled square carrying a ✓. It
     keeps its size in both states, so toggling never reflows the row. This is the same
     "never colour alone" rule the dot already obeys, applied to the second channel:
     the DOT says which stage, the BOX says whether it is on the map. */
  #mapkeyShapes .typechip .ck, #mapkeyReg .regchip .ck, #mapkey .stagechip .ck{flex:0 0 auto;width:14px;height:14px;border-radius:4px;
    border:1.5px solid #7d8c86;background:#fff;display:inline-flex;align-items:center;
    justify-content:center;font-size:10px;line-height:1;color:transparent}
  #mapkeyShapes .typechip .ck::after, #mapkeyReg .regchip .ck::after, #mapkey .stagechip .ck::after{content:"\2713";font-weight:700}
  #mapkeyShapes .chipbox:checked~.ck, #mapkeyReg .chipbox:checked~.ck, #mapkey .stagebox:checked~.ck{background:var(--green);border-color:var(--green);color:#fff}
  /* Selected chip: a darker outline plus a very subtle tint — the same channel the Type
     row uses, and, as there, never the stage hue. */
  #mapkeyShapes .typechip.on, #mapkeyReg .regchip.on, #mapkey .stagechip.on{border-color:#9aa8a3;background:#f4f6f5}
  #mapkeyShapes .typechip.on:hover, #mapkeyReg .regchip.on:hover, #mapkey .stagechip.on:hover{background:#eef1ef}
  /* UNSELECTED IS NEUTRAL, NOT DISABLED. No strike-through and no heavy dimming: the
     resident is choosing what to see, not looking at something broken or deleted. The
     label stays full-contrast charcoal and the DOT stays at full strength, because the
     dot is still the pin-colour legend whether or not that stage is on the map. */
  #mapkeyShapes .typechip .t, #mapkeyReg .regchip .t, #mapkey .stagechip .t{color:var(--ink)}
  #mapkey .stagechip .dot{opacity:1}
  /* Touch targets. 44x44 on coarse pointers, where the WCAG target-size rule applies;
     the desktop chip stays compact. */
  @media (pointer:coarse){
    #mapkey .stagechip{min-height:44px;padding:9px 13px}
    #mapkey .stagechip .ck{width:18px;height:18px;font-size:12px}
    #mapkeyShapes .typechip, #mapkeyReg .regchip{min-height:44px;padding:9px 13px}
    #mapkeyShapes .typechip .ck, #mapkeyReg .regchip .ck{width:18px;height:18px;font-size:12px}
  }
  /* The empty state's way out. A real <button> styled as a link: an <a href="#"> would
     announce as a link to nowhere and would push a history entry on every click. */
  .mk-linkbtn{font:inherit;font-size:11px;font-weight:700;color:#8a4b12;background:none;
    border:0;padding:2px 0;text-decoration:underline;cursor:pointer}
  .mk-linkbtn:hover{color:#6d3a0d}
  .mk-linkbtn:focus-visible{outline:2px solid var(--green-bright);outline-offset:2px}
  .maplegend-wrap{display:flex;flex-direction:column;gap:6px;flex:1;min-width:0}
  /* SECTION HEADING — the three filter dimensions are the panel's major rank, so they
     read as sections rather than as another line of helper text. This reuses the app's
     existing section-label idiom (app.css `.groupHead` / `.kicker` / this page's own
     `.dt-lbl`): uppercase, tracked, bold, ink — not a new typographic invention. It has
     to outrank BOTH neighbours it sits between, which is why all four channels move at
     once: 13.5px/700 ink uppercase against 12px/400 ink sentence-case chip labels and
     11px mute helper copy. Size alone at this scale would not carry it. */
  .mapkey-hd{font-size:13.5px;font-weight:700;color:var(--ink);letter-spacing:.05em;
    text-transform:uppercase;margin-top:12px}
  /* The separation IS the grouping. `.maplegend-wrap` gaps 6px uniformly, which spaces a
     heading from its own controls exactly as far as it spaces it from the previous
     section's — so the three groups never read as three. The extra top margin belongs to
     the heading (it opens a section) and is cancelled on the first, which opens nothing. */
  .mapkey-hd:first-child{margin-top:0}
  .mapkey-shapes .ic{display:flex;align-items:center;line-height:0}
  /* REGULATORY ROW. One chip, same pattern as the other two rows — the ON/OFF switch it
     replaces is gone entirely, so there is no second control over the same layer. The
     purple R keeps its own icon box and is never dimmed or struck: it explains what the
     RECORD is, while the checkbox says whether the overlay is currently drawn. */
  .mapkey-reg{display:flex;align-items:center;gap:9px;flex-wrap:wrap}
  #mapkeyShapes .ic, #mapkeyReg .ic{display:flex;align-items:center;line-height:0}
  #mapkeyShapes .typechip .ic, #mapkeyReg .regchip .ic{opacity:1}
  /* THE ONE MAP KEY. It reads the map's three encodings and nothing else — the three
     "checked X are shown on the map" rules and the long legend paragraph that used to
     restate them are gone, so this is the only explanation in the panel. It is
     SUBORDINATE by construction: 11px mute against 13.5px ink headings, and it sits
     under a hairline rule so it is plainly the panel's footer rather than a fourth
     section. Left-aligned, because it now aligns with the controls it explains instead
     of centring one short line under a left-aligned column. */
  .mapkey-note{font-size:11px;color:var(--mute);line-height:1.45;width:100%;
    max-width:none;margin:10px 0 0;padding-top:8px;border-top:1px solid var(--line);
    text-wrap:balance}
  .mapkey-note strong{font-weight:700;color:var(--soft)}
  /* Each `X = Y ·` is one unbreakable unit, so a narrow viewport wraps BETWEEN the
     three clauses rather than stranding a lone separator at the head of a line. The
     spans carry no text of their own, so the accessible description these groups are
     aria-describedby is byte-identical to the sentence on screen. */
  .mapkey-note .mk-clause{white-space:nowrap}
  .mapkey-empty{font-size:11px;line-height:1.35;max-width:42em;color:#8a4b12;
    background:#fff6ec;border:1px solid #f0d3b4;border-radius:7px;padding:5px 9px}
  .mapkey-empty[hidden]{display:none}
  .tfall{display:block;width:100%;margin-top:4px;padding:6px;font:inherit;font-size:11px;font-weight:600;
    color:var(--green);background:none;border:0;border-top:1px solid var(--line);cursor:pointer;text-align:left}
  .tfall:hover{background:#f2f5f3}

  /* ===== Development-tracker: resolved-project anchor (a) + ZIP/county notices (b/c) =====
     The resolved-project data is a verified public-record anchor. Every honesty label the
     store carries (zip_basis, off_target_zip straddle, unresolved status tracks, unconfirmed
     bridges) renders here verbatim — never flattened, never faked. */
  .devtracker{margin-top:22px}
  .dt-anchor{margin-bottom:16px}
  .dt-hd{font-weight:700;font-size:16px;color:#fff;background:#4b3f8f;
    margin:-18px -18px 0;padding:12px 18px;border-radius:16px 16px 0 0}
  .dt-sub{font-size:12.5px;color:var(--mute);margin:10px 0 2px}
  .dt-title{font-weight:700;font-size:17px;color:var(--ink);margin-top:12px}
  .dt-straddle{font-size:13px;color:var(--soft);margin:5px 0 4px;line-height:1.5}
  .dt-lbl{font-weight:700;font-size:11.5px;color:var(--soft);text-transform:uppercase;
    letter-spacing:.04em;margin:18px 0 4px}
  .dt-lbl-note{font-weight:400;text-transform:none;letter-spacing:0;color:var(--mute)}
  .dt-track{display:flex;justify-content:space-between;gap:12px;align-items:baseline;
    padding:9px 0;border-top:1px solid var(--line);font-size:13px}
  .dt-track .k{font-weight:600;color:var(--ink)}
  .dt-track .v{text-align:right}
  .dt-unresolved{color:var(--mute);font-style:italic}
  .dt-alias{padding:8px 0;border-top:1px solid var(--line);font-size:13px}
  .dt-alias .nm{font-weight:600;color:var(--ink)}
  .dt-alias .src{color:var(--mute);font-size:11.5px;margin-top:2px}
  .dt-alias .src a{color:var(--green)}
  .dt-chip{display:inline-block;font-size:10.5px;font-weight:600;padding:1px 7px;border-radius:6px;
    background:#e6f4ec;color:#1f5130;margin-left:7px;vertical-align:middle}
  .dt-chip.warn{background:#fdf0e6;color:#a85a1f}
  .dt-roster{max-height:360px;overflow:auto;border:1px solid var(--line);border-radius:10px;
    padding:2px 12px;margin-top:6px}
  .dt-parcel{padding:9px 0;border-top:1px solid var(--line);font-size:13px}
  .dt-parcel:first-child{border-top:none}
  .dt-parcel .own{font-weight:600;color:var(--ink)}
  .dt-parcel .meta{color:var(--soft);font-size:12px;margin-top:1px}
  .dt-parcel .meta a{color:var(--green)}
  .dt-flag{display:block;color:#a85a1f;font-size:11.5px;margin-top:3px;line-height:1.45}
  .dt-badge{display:inline-block;font-size:10.5px;font-weight:700;padding:1px 8px;border-radius:6px;
    background:#eef2f7;color:#3b5566;margin-right:8px;letter-spacing:.02em;flex:0 0 auto}
  .dt-count{font-size:12px;color:var(--mute);margin:2px 0 8px}
  .mapcaption{font-size:12px;color:var(--soft);margin-top:8px;line-height:1.5}
  .mapcaption .sw{display:inline-block;width:11px;height:11px;border-radius:3px;vertical-align:middle;
    margin:0 3px 0 9px;border:1px solid rgba(0,0,0,.15)}

  /* ===================== EMBED MODE — the hide list ===================== */
  /* WHAT SURVIVES, and it is the whole point: the map canvas and the three FILTER ROWS,
     with their existing map key. Those rows are the same DOM ids the full page builds
     (#mapkey, #mapkeyShapes, #mapkeyReg) driven by the same writers (HS.setCategoryFilter,
     the stage writer, HS.regulatoryVisible) — CSS hides chrome, it does not re-create a
     control. Nothing here is a decorative chip.

     CSS ALONE IS SUFFICIENT — shell.js is NOT forked. The one thing display:none cannot
     do by itself is collapse the sidebar's grid COLUMN, because .app is
     `grid-template-columns: var(--sidebar) 1fr`, so a hidden #hs-side would still reserve
     its track and the map would sit in a narrow right-hand column. One extra rule fixes
     that, which is why no shell change is needed. */
  .hs-embed .app{grid-template-columns:1fr}
  .hs-embed #hs-side,
  .hs-embed #hs-top,
  .hs-embed .sidebackdrop{display:none !important}
  /* The page's own chrome: hero, the ADDRESS SEARCH FORM (an embed is anchored to the
     Place that hosts it — a search box inside it would navigate the iframe away from the
     Place), and the status line. */
  .hs-embed .wrap>.head,
  .hs-embed .wrap>.status{display:none !important}
  /* Everything below the map: the counters card, the built/approved/proposed bands, the
     civic band, the Development tracker and its notices, the footer, and the #property
     dossier. The Place page carries its own copy of this material; the iframe is a MAP. */
  .hs-embed .grid.stacked>.card.read,
  .hs-embed .results>.grid.band,
  .hs-embed .results>.card.read.band,
  .hs-embed .results>.devtracker,
  .hs-embed .results>.foot,
  .hs-embed #property{display:none !important}
  /* 3D is out of scope for a Place embed, so its controls AND its two canvases go: with
     #viewSeg hidden there is no way to reach them, and hiding the canvases means a stale
     one can never sit over the 2D map. */
  .hs-embed #viewSeg,
  .hs-embed #map3d,
  .hs-embed #mapgl{display:none !important}
  /* THE RADIUS PICKER IS HIDDEN BECAUSE THE EMBED'S RADIUS IS FIXED AT 2 BY THE HOST URL.
     A visible picker would offer to change a geography the Place page has already stated
     in its own copy. */
  .hs-embed #radSel{display:none !important}
  /* The iframe supplies the frame, so the page inside it drops its own page padding and
     card chrome — otherwise a Place shows a card inside a card inside a card. */
  .hs-embed .wrap{padding:0;max-width:none}
  .hs-embed .card.mapcard{border:0;border-radius:0;box-shadow:none;margin:0}
  .hs-embed .results{margin:0}
  .hs-embed body{background:var(--panel)}
  /* ── THE EMBED IS A FIXED FRAME, SO THE CARD FITS THE FRAME INSTEAD OF OVERFLOWING IT ──
     MEASURED DEFECT (2026-09-15, reported from a live ZIP page): `.map-frame` is a hard
     600px and the filter panel is 288px tall at desktop widths (412px at 390px wide), so
     the embed document stood 957px tall inside a 520px iframe — 437px of overflow, on
     EVERY embed, at every width. The panel sits ABOVE the map, so the panel was the half
     that left: Leaflet focuses its own container on any interaction (a zoom click, a
     marker click), the browser scrolls that 600px container into view, and the document
     scrolled from 0 to 343 — exactly `.map-frame`'s offset — taking STATUS / PROJECT TYPE
     / REGULATORY out of the frame. Zoom-out did NOT bring it back, and the next click
     re-hid it, so the controls read as gone.

     The fix is geometric, and it is scoped to the embed: the card becomes a flex column
     exactly one frame tall, and the map takes whatever the panel does not. With no
     document overflow there is nothing to scroll, so no focus-scroll can move the panel
     out of view. `overflow:hidden` is the belt to that braces — at a width where the
     panel genuinely cannot fit, the frame clips rather than letting the panel slide away.

     THE PANEL IS NEVER HIDDEN HERE — test/place-context-map.test.mjs pins that the embed
     hides no filter row, and that pin is a founder decision (the rows are why the iframe
     replaced HS.buildLive). Where the panel does not fit it scrolls INSIDE ITS OWN BOX,
     bounded by max-height, so the thing that gives is a scrollbar on the panel and never
     the panel's presence. Full-page Map 1 is untouched: `.map-frame` keeps its 600px
     everywhere outside `.hs-embed`, measured unchanged at 1280x900. */
  .hs-embed,
  .hs-embed body{height:100%;overflow:hidden}
  .hs-embed .card.mapcard{display:flex;flex-direction:column;height:100dvh;
    box-sizing:border-box;min-height:0}
  /* 45dvh is a ceiling, not a height: at desktop widths the 288px panel is under it and
     lays out exactly as it always did, with no scrollbar. It binds only where the panel
     wraps taller than the frame can spare. */
  .hs-embed .maprow{flex:0 1 auto;min-height:0;max-height:45dvh;overflow-y:auto}
  .hs-embed .map-frame{flex:1 1 auto;height:auto;min-height:160px}

  /* ===================== PREVIEW (?embed=1&preview=1) ===================== */
  /* The homepage's sample map (founder, Revised Index Design, 2026-09-30). The fixed-frame
     rules above fit the card INTO the host's iframe height; the preview turns that around:
     the document is as tall as its content, so STATUS / PROJECT TYPE / REGULATORY RECORDS and
     the map key all show with no internal scrollbar, and the host sizes the iframe to this
     document's scrollHeight. The map canvas has a fixed height: --hs-preview-map-h, which the
     host sets by its own breakpoint (430px / 400px / 340px), default 430px.
     LAYOUT ONLY. No control is hidden, renamed, restyled or re-created; no data, marker or
     filter rule changes. Every rule is scoped to .hs-embed.hs-preview, so full-page Map 1 and
     every other embed are unchanged. */
  .hs-embed.hs-preview,
  .hs-embed.hs-preview body{height:auto;min-height:0;overflow:hidden}
  .hs-embed.hs-preview .app{min-height:0}
  .hs-embed.hs-preview .card.mapcard{display:block;height:auto}
  .hs-embed.hs-preview .maprow{max-height:none;overflow:visible}
  .hs-embed.hs-preview .map-frame{height:var(--hs-preview-map-h,430px);min-height:0}

