/* KnockPortal — per-contractor canvassing page
   ============================================================================
   One page, two jobs. The mode buttons flip the whole composition:

     WALK      list is the work surface — wide pane, big type, sticky street
               headers, walking order. Map demoted to a locator.
     POSTCARDS satellite is the work surface — map takes the width, the pane
               narrows to a running tally of picked roofs.

   Mode state lives in aria-selected on #tab-walk / #tab-mail (set by page.js);
   the layout reads it with :has().

   Above the modes sits the screen. body[data-screen] is written by page.js:

     city      the whole of San Francisco. Map takes the full width, the panel
               and the group controls are gone — there is no group picked yet.
     cluster   the composition above: map + panel, walk or postcards.
     area      the same composition as postcards, with the group select and the
               mode tabs replaced by the two controls the manual area has: the
               reach, and the reset. No group, no permits, no download.

   Under all three screens stands the strip: the mailing is collected group by
   group and belongs to none of them, so nothing that hides the panel may touch
   it.

   Hooks page.js depends on (ids + emitted classes):
   #map #back #pick #tab-walk #tab-mail #pane #count #clear #dl #tomail
   #strip #strip-sum #strip-say #mail-clear #mail-send
   #dateline #lede-city #lede-cluster #notice #provenance #staleness #zoomnote
   #area-go #lede-area #area-tools #area-r #area-r-say #area-reset
   #legend-area #provenance-area
   .yrs .win .rsince   (runtime text slots)
   .grp .st .row .row.pick .row.on .row.inmail .z .hint
   ========================================================================= */

/* ------------------------------------------------------------------ fonts */
@font-face{font-family:'Barlow';font-weight:400;font-display:swap;
  src:url('fonts/Barlow-Regular.woff2') format('woff2')}
@font-face{font-family:'Barlow';font-weight:500;font-display:swap;
  src:url('fonts/Barlow-Medium.woff2') format('woff2')}
@font-face{font-family:'Barlow';font-weight:600;font-display:swap;
  src:url('fonts/Barlow-SemiBold.woff2') format('woff2')}
@font-face{font-family:'Barlow Condensed';font-weight:500;font-display:swap;
  src:url('fonts/BarlowCondensed-Medium.woff2') format('woff2')}
@font-face{font-family:'Barlow Condensed';font-weight:600;font-display:swap;
  src:url('fonts/BarlowCondensed-SemiBold.woff2') format('woff2')}
@font-face{font-family:'Barlow Condensed';font-weight:700;font-display:swap;
  src:url('fonts/BarlowCondensed-Bold.woff2') format('woff2')}

/* ----------------------------------------------------------------- tokens */
:root{
  --ink:#0F1822;        /* page ground            */
  --well:#0B131C;       /* map well, deepest      */
  --slate:#1B2733;      /* panel                  */
  --slate-2:#233140;    /* raised: active tab, hover */
  --line:#33424F;       /* hairline               */
  --mute:#93A3B2;       /* secondary text         */
  --lede:#C6D1DB;       /* readable body on ink   */
  --paper:#F2F5F7;      /* primary text           */
  --orange:#FF6B1A;     /* fresh permit · primary action */
  --orange-press:#E85D10;
  --blue:#5FB0E8;       /* no permit on record    */
  --green:#4ED08A;      /* selected for postcards (matches map) */
  --grey:#6B7C8C;       /* already reroofed       */
  --cond:'Barlow Condensed', Barlow, sans-serif;
}
*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0; color:var(--paper);
  background:var(--ink)
    radial-gradient(rgba(242,245,247,.035) 1px, transparent 1.2px);
  background-size:auto, 24px 24px;
  font:16px/1.5 Barlow, system-ui, sans-serif;
}
.shell{max-width:1760px;margin:0 auto}
a{color:var(--blue)}
button{font-family:inherit}
:focus-visible{outline:2px solid var(--orange);outline-offset:2px;border-radius:4px}

/* ------------------------------------------------------------- app frame */
.screen{display:flex;flex-direction:column;height:100svh;min-height:600px}
.top,.controls{flex:none}

/* ----------------------------------------------------------------- header */
.top{padding:20px 28px 14px}
.topline{display:flex;justify-content:space-between;align-items:baseline;gap:16px}
/* margin-right:auto and not a changed source order: the nav and the wordmark are
   pushed to the right edge while the "Built for" line keeps the left, so the mark
   is still the last thing read on the line, exactly as it was before the nav. */
.built{font-size:12px;color:var(--mute);text-transform:uppercase;letter-spacing:.2em;margin-right:auto}
/* The way out of the surface. Quieter than the body text on purpose: these are
   doors out of the page, not the work on it. */
.topnav{display:flex;gap:16px;white-space:nowrap}
.topnav a{font-size:13px;color:var(--mute);text-decoration:none;transition:color .12s ease}
.topnav a:hover{color:var(--paper)}
.wordmark{font:700 19px/1 var(--cond);letter-spacing:.01em;color:var(--paper);white-space:nowrap}
.wordmark .wm-o{color:var(--orange)}
h1{margin:5px 0 3px;font:700 clamp(30px,3.6vw,46px)/1.04 var(--cond);letter-spacing:.01em}
h1 em{font-style:normal;color:var(--orange)}
.dateline{font:600 13px/1.4 var(--cond);letter-spacing:.12em;text-transform:uppercase;color:var(--mute)}
.lede{margin:10px 0 0;color:var(--lede);font-size:15px;line-height:1.5;max-width:68ch}
.lede b{color:var(--paper);font-weight:600}
/* the standing caveat under the city lede: same paragraph, quieter voice —
   it is not news about this snapshot, it is what the snapshot is not */
.lede-2{display:block;margin-top:9px;color:var(--mute)}
.lede-2 b{color:var(--lede)}
/* a forwarded #c<id> that no longer exists lands here */
.notice{
  margin:10px 0 0;max-width:68ch;padding:9px 13px;
  border-left:3px solid var(--orange);background:rgba(255,107,26,.09);
  color:var(--lede);font-size:13.5px;line-height:1.45;
}

/* --------------------------------------------------------------- controls */
.controls{
  position:sticky;top:0;z-index:30;
  display:grid;grid-template-columns:auto minmax(240px,1fr) auto;gap:14px;align-items:end;
  padding:14px 28px;background:rgba(15,24,34,.92);
  border-top:1px solid var(--line);border-bottom:1px solid var(--line);
  backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
}
.back,.areago{
  background:none;border:1px solid var(--line);color:var(--paper);border-radius:8px;
  padding:12px 15px;font:600 13px/1 var(--cond);letter-spacing:.08em;text-transform:uppercase;
  cursor:pointer;white-space:nowrap;
  transition:background .15s ease,border-color .15s ease;
}
.back:hover,.areago:hover{border-color:#4A5D70;background:rgba(35,49,64,.6)}
/* The door to the manual area stands in the header, under the lede, and only on
   the screen where the offer makes sense: the city one, where the alternative —
   the group select — is the next thing he sees. */
.areago{margin-top:14px}
body:not([data-screen="city"]) .areago{display:none}
.field{display:flex;flex-direction:column;gap:5px;min-width:0}
.field-label{font:600 11px/1 var(--cond);letter-spacing:.18em;text-transform:uppercase;color:var(--mute)}
select#pick{
  appearance:none;-webkit-appearance:none;width:100%;
  background:var(--slate) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23FF6B1A' stroke-width='2'/%3E%3C/svg%3E") no-repeat right 14px center;
  color:var(--paper);border:1px solid var(--line);border-radius:8px;
  padding:11px 38px 11px 14px;
  font:600 17px/1.2 var(--cond);letter-spacing:.02em;
  text-overflow:ellipsis;white-space:nowrap;overflow:hidden;cursor:pointer;
}
select#pick:hover{border-color:#46586A}
select#pick option{font:15px Barlow;background:var(--slate);color:var(--paper)}

.tabs{display:flex;gap:8px}
.tab{
  display:flex;flex-direction:column;align-items:flex-start;gap:2px;
  min-width:190px;padding:9px 16px 10px;
  background:transparent;border:1px solid var(--line);border-radius:8px;
  color:var(--mute);cursor:pointer;text-align:left;
  transition:background .15s ease,border-color .15s ease,color .15s ease;
}
.tab:hover{background:rgba(35,49,64,.6)}
.tab-t{font:600 16px/1.2 var(--cond);letter-spacing:.06em;text-transform:uppercase}
.tab-s{font:400 12.5px/1.3 Barlow;letter-spacing:0}
.tab[aria-selected=true]{
  background:var(--slate-2);border-color:#4A5D70;color:var(--paper);
  box-shadow:inset 0 -2px 0 var(--orange);
}
.tab[aria-selected=true] .tab-s{color:var(--mute)}

/* ------------------------------------------------------------ area tools */
/* The reach and the reset, standing where the select and the tabs stand on the
   other screens. Hidden until the body says which screen this is — page.js
   writes that attribute, and before it has, nothing about the area is true. */
.area-tools{display:flex;gap:12px;align-items:end}
body:not([data-screen="area"]) #area-tools{display:none}
.area-field{display:flex;flex-direction:column;gap:5px;min-width:0}
#area-r{accent-color:var(--orange);width:min(320px, 100%)}
/* the same quiet gloss the postcard count carries beside its own field */
.area-r-say{font-size:12.5px;color:var(--mute)}
#area-r:disabled,#area-reset:disabled{opacity:.5;cursor:default}

/* ------------------------------------------------------------------ stage */
.stage{
  display:grid;grid-template-columns:minmax(0,1fr) var(--panel-w,620px);
  flex:1;min-height:0;
  border-bottom:1px solid var(--line);
}
/* walk (default): the list is the work surface */
.stage{--panel-w:620px}
/* postcards: the satellite is the work surface */
body:has(#tab-mail[aria-selected=true]) .stage{--panel-w:360px}

/* ---------------------------------------------------------------- screens */
/* City: nothing is picked yet, so there is no list to show and no group to
   switch. The panel is not emptied, it is removed from the layout. */
body[data-screen="city"] .stage{grid-template-columns:minmax(0,1fr)}
body[data-screen="city"] #panel{display:none}
body[data-screen="city"] .controls{display:none}
body[data-screen="city"] #lede-cluster{display:none}
body[data-screen="city"] #legend-cluster{display:none}
/* "a blue house is…" describes the cluster map; there are no blue houses on
   the city map, so the caveat waits until it is true */
body[data-screen="city"] #note-cluster{display:none}
body[data-screen="cluster"] #lede-city{display:none}
body[data-screen="cluster"] #legend-city{display:none}

/* Area: the postcards composition without a group behind it. The panel and the
   two columns are the default, so only what differs is written here — the group
   select and the mode tabs go, the area's own controls come, and the download
   and the save go with the group they belonged to.
   The two ledes and the two keys of the other screens are hidden as a family
   rather than one by one: this screen has one of each of its own, and it is the
   only thing that stays. */
body[data-screen="area"] .lede:not(#lede-area){display:none}
body:not([data-screen="area"]) #lede-area{display:none}
body[data-screen="area"] .legend:not(#legend-area){display:none}
body:not([data-screen="area"]) #legend-area{display:none}
body[data-screen="area"] .field{display:none}
body[data-screen="area"] .tabs{display:none}
body[data-screen="area"] .controls{grid-template-columns:auto 1fr}
/* the satellite is the work surface here, as it is for postcards */
body[data-screen="area"] .stage{--panel-w:360px}
/* Download is bound to a group and a snapshot, and Save selection stores the
   centre of a group so it can be found again — neither has anything to say
   about an area, so neither is offered. */
body[data-screen="area"] #save,
body[data-screen="area"] #dl{display:none}
/* "a blue house is…" describes a group's map, and this screen draws no permits
   at all; its own word about what it does and does not know is in the lede */
body[data-screen="area"] #note-cluster{display:none}
/* the address layer is published on its own clock and answers for its own age */
body:not([data-screen="area"]) #provenance-area{display:none}
body[data-screen="area"] #provenance{display:none}

.maparea{position:relative;min-width:0;background:var(--well)}
#map{position:absolute;inset:0}
/* demote the map a notch while walking — it is a locator, not the job. Only on
   the cluster screen: on the city screen the map IS the job. */
body[data-screen="cluster"]:has(#tab-walk[aria-selected=true]) .maparea::after{
  content:'';position:absolute;inset:0;pointer-events:none;
  background:rgba(15,24,34,.18);
}

/* the map area also carries its own failure, so it must read on both screens */
.mapmsg{
  height:100%;display:flex;align-items:center;justify-content:center;
  padding:32px;text-align:center;font:15px/1.5 Barlow, system-ui, sans-serif;color:var(--mute);
}
.mapmsg > div{max-width:46ch}
.mapmsg b{
  display:block;color:var(--paper);font:600 21px/1.2 var(--cond);
  letter-spacing:.02em;margin-bottom:8px;
}
.mapmsg-t{margin-top:14px}

/* The neighbour layer's own message — a plate laid over a map that is still
   running, unlike .mapmsg above, which stands in place of one that is not.
   Takes no pointer events at all: everything under it is still the map, and a
   drag or a pinch that happens to start here belongs there. Held clear of the
   zoom control in the opposite corner by its own width. */
.zoomnote{
  position:absolute;top:16px;left:50%;transform:translateX(-50%);z-index:6;
  margin:0;max-width:min(40ch, calc(100% - 100px));
  padding:9px 14px;text-align:center;
  background:rgba(15,24,34,.88);border:1px solid rgba(51,66,79,.9);border-radius:8px;
  color:var(--lede);font-size:13px;line-height:1.4;
  backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);
  pointer-events:none;
}

/* Mapbox draws its logo into .mapboxgl-ctrl-bottom-left — the same corner the
   key stands in, and the key, sitting higher in the stack, was covering it. The
   logo is a condition of the tile licence, so the key is what moves: the bottom
   strip of the map belongs to the logo and the key stands on top of it. The
   text attribution keeps its own corner on the right, untouched.
   CSS cannot measure one element against another, so the strip is a number.
   It is not a guess at mapbox-gl.css: the strip and the logo box are set from
   the same --logo-h here, and the -4px pullouts the library gives the mark are
   zeroed, so the mark is exactly --logo-h tall at --logo-inset off both edges.
   --logo-h is the mark's own height (the 88x23 wordmark); its width is left
   alone, so the library's own narrow-map compact form still works. */
.maparea{--logo-h:23px;--logo-inset:10px}
.mapboxgl-ctrl-bottom-left .mapboxgl-ctrl{margin:0 0 var(--logo-inset) var(--logo-inset)}
a.mapboxgl-ctrl-logo{margin:0;height:var(--logo-h)}

/* The corner anchor, standing clear of the strip below it. pointer-events stays
   off here and is handed back to the toggle alone: an expanded key must not eat
   the drag and the pinch under it. */
.legendbox{
  position:absolute;left:14px;z-index:5;
  bottom:calc(var(--logo-inset) + var(--logo-h) + 8px);
  display:flex;flex-direction:column;align-items:flex-start;gap:6px;
  pointer-events:none;
}
.legend{display:flex;flex-direction:column;gap:6px}
/* folded state on phones; the desktop never renders this button */
.legend-toggle{display:none}
/* direct child only — the runtime text slots are spans too, and they must not
   each grow a plate of their own */
.legend > span{
  display:inline-flex;align-items:center;gap:8px;align-self:flex-start;
  background:rgba(15,24,34,.82);border:1px solid rgba(51,66,79,.8);border-radius:6px;
  padding:5px 10px;font-size:12.5px;color:var(--lede);
  backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);
}
.legend i{width:10px;height:10px;border-radius:50%;flex:none}
.lg-permit{background:var(--orange);box-shadow:0 0 0 2px #fff}
.lg-open{background:var(--blue)}
.lg-sel{background:var(--green);box-shadow:0 0 0 2px #fff}
/* the same grey and the same fading the map gives a roof already behind him */
.legend i.lg-hist{background:#8A97A6;opacity:.6}
/* city screen: one permit family, two weights, plus the box around a group */
.lg-solo{background:var(--orange);opacity:.5;width:7px;height:7px}
.lg-box{
  width:15px;height:10px;border-radius:2px;
  background:rgba(255,107,26,.16);border:1px solid #FF9B57;
}
/* the area screen: the same orange outline as a group's box, drawn round —
   because what he drew is a reach from a point, not a rectangle over a block */
.legend i.lg-area{
  width:12px;height:12px;border-radius:50%;
  background:rgba(255,107,26,.16);border:1px solid #FF9B57;box-shadow:none;
}
/* the two history steps are drawn as crosses on the map, so the legend shows a
   cross too — a dot here would promise a marker the map never draws */
.legend i.lg-x{
  width:12px;height:12px;border-radius:0;background:none;box-shadow:none;
  display:inline-flex;align-items:center;justify-content:center;
  font:700 15px/1 system-ui,sans-serif;
}
/* the map gives both crosses a white halo; the chip sits on a dark plate, so
   stacked white shadows stand in for it — otherwise the dark step vanishes */
.lg-x.lg-recent{color:#FF8C42}
.lg-x.lg-done{color:#243240}
.legend i.lg-x{text-shadow:0 0 2px #fff,0 0 2px #fff,0 0 3px #fff}
.legend i.lg-x::before{content:'\2715'}

/* ------------------------------------------------------------------ panel */
#panel{
  display:flex;flex-direction:column;min-height:0;overflow:hidden;
  background:var(--slate);border-left:1px solid var(--line);
}
.pane{flex:1;min-height:0;overflow:auto;overscroll-behavior:contain;padding:0 0 8px}

.hint{
  margin:0;padding:14px 22px;color:var(--mute);font-size:13.5px;line-height:1.5;
  border-bottom:1px solid rgba(51,66,79,.5);
}

/* group bands: fresh-permit zone first, marked with the permit orange */
.grp{
  padding:16px 22px 6px;color:var(--mute);
  font:600 12px/1 var(--cond);letter-spacing:.18em;text-transform:uppercase;
}
.pane .grp:first-of-type{color:var(--orange)}
.pane .grp:first-of-type::before{
  content:'';display:inline-block;width:8px;height:8px;border-radius:50%;
  background:var(--orange);margin-right:9px;vertical-align:1px;
}

/* street headers: the walking unit — sticky while the street scrolls */
.st{
  position:sticky;top:0;z-index:3;
  display:flex;justify-content:space-between;align-items:baseline;gap:10px;
  padding:10px 22px 8px;background:var(--slate);
  border-bottom:1px solid var(--line);
  font:600 16px/1.1 var(--cond);letter-spacing:.1em;text-transform:uppercase;color:var(--paper);
}
.st span{
  font:600 12px/1 var(--cond);letter-spacing:.06em;color:var(--mute);
  border:1px solid var(--line);border-radius:999px;padding:3px 9px 4px;
}

/* address rows */
/* The row wraps, and the address is the one thing on it that cannot.
   At five elements — checkbox, address, index, the word of the history and two
   buttons — the line runs out of width, and the only stretchable item is the
   address, so the whole shortfall used to land on it: 448 NOE ST came apart over
   three lines. The address is what the row is about, so it is made unbreakable
   (white-space:nowrap) and unshrinkable (flex-shrink 0 on a content-sized basis),
   and the row is allowed to wrap instead. What goes down to the second line is
   then the controls at the end of it — the word and the buttons — which is the
   right thing to move. Row gap is smaller than the column gap: a row that broke
   in two is still one row.
   flex-grow stays 1, so the index and the buttons keep the right edge exactly as
   they held it before. */
.row{
  display:flex;align-items:center;flex-wrap:wrap;gap:6px 12px;
  padding:10px 22px;border-bottom:1px solid rgba(51,66,79,.45);
  font-size:16.5px;line-height:1.25;
}
.row b{font-weight:600;flex:1 0 auto;white-space:nowrap;letter-spacing:.01em}
.row .z{color:var(--mute);font-size:12.5px;white-space:nowrap}

/* postcards mode: rows become pickable — give them a real checkbox affordance */
.row.pick{cursor:pointer;transition:background .12s ease}
.row.pick:hover{background:rgba(35,49,64,.55)}
.row.pick::before{
  content:'';flex:none;width:19px;height:19px;border-radius:5px;
  border:1.5px solid var(--line);background:transparent;
  transition:background .12s ease,border-color .12s ease;
}
.row.on{background:rgba(78,208,138,.12)}
.row.on:hover{background:rgba(78,208,138,.18)}
.row.on::before{
  border-color:var(--green);background:var(--green)
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='9'%3E%3Cpath d='M1 4.5l3 3 6-7' fill='none' stroke='%230F1822' stroke-width='2.2'/%3E%3C/svg%3E") no-repeat center;
}
/* Already in the mailing. It has to be legible at the same time as .on, which
   is a different fact about the same row — what he is picking here, against
   what the mailing already holds — so this is a mark and an edge rather than a
   second colour of the row: a row cannot be two colours, but it can carry a
   stamp. */
.row.pick.inmail{box-shadow:inset 3px 0 0 var(--line)}
.row.pick.inmail::after{
  content:'\2709';flex:none;
  font:15px/1 system-ui,sans-serif;color:var(--mute);
}
/* Behind him already: mailed, walked, or struck off by hand. The address goes
   quiet and says which, and the button beside it is the only way out or back —
   the row itself still picks, because the history advises and does not forbid. */
.row.pick.hist b{color:var(--mute)}
.row .hx{
  font-size:11px;letter-spacing:.06em;text-transform:uppercase;
  color:var(--mute);white-space:nowrap;
}
.row .hxact{
  margin-left:8px;font:12px/1.2 inherit;padding:3px 8px;
  border:1px solid var(--line);border-radius:5px;
  background:transparent;color:var(--mute);cursor:pointer;flex:none;
}
.row .hxact:hover{color:var(--paper)}
.row .hxact:disabled{opacity:.5;cursor:default}

/* walk mode rows are compact: no checkbox column, tighter rhythm */
body:has(#tab-walk[aria-selected=true]) .row{padding:9px 22px}

/* ------------------------------------------------------------------- foot */
.foot{
  display:flex;gap:12px;align-items:center;flex-wrap:wrap;
  padding:14px 22px;border-top:1px solid var(--line);background:var(--slate);
}
.count{flex:1;font-size:14px;color:var(--mute)}
.count b{font:700 24px/1 var(--cond);color:var(--paper);margin-right:2px;vertical-align:-1px}
button.go{
  background:var(--orange);color:#12100E;border:0;border-radius:8px;
  padding:12px 18px;font:600 15px/1 var(--cond);letter-spacing:.08em;text-transform:uppercase;
  cursor:pointer;transition:background .15s ease;
}
button.go:hover{background:var(--orange-press)}
button.go[disabled]{background:var(--line);color:var(--mute);cursor:default}
button.ghost{
  background:none;border:1px solid var(--line);color:var(--paper);border-radius:8px;
  padding:11px 14px;font:600 13px/1 var(--cond);letter-spacing:.08em;text-transform:uppercase;cursor:pointer;
}
button.ghost:hover{border-color:#4A5D70}
/* the whole width of the wrapped foot row: a sentence squeezed in beside the
   counter and three buttons is a sentence nobody reads */
.savenote{flex-basis:100%;margin:0;font-size:13px;line-height:1.45;color:var(--mute)}

/* ------------------------------------------------------------------ strip */
/* The mailing, under both screens. Nothing keyed on body[data-screen] may hide
   it: on the city screen the panel is out of the layout altogether, and that is
   the screen he crosses on the way to the next group — the mailing has to be
   readable while he does. In the flex column of .screen it is one line that
   does not stretch; the stage keeps every pixel it had. */
.strip{
  flex:none;
  display:flex;gap:12px;align-items:center;flex-wrap:wrap;
  padding:12px 28px;
  border-top:1px solid var(--line);background:var(--slate);
}
/* what is in it, at the weight of the panel's own counter */
.strip-sum{flex:none;font-size:14px;color:var(--mute)}
.strip-sum b{font:700 24px/1 var(--cond);color:var(--paper);margin-right:2px;vertical-align:-1px}
/* the standing sentence, in the voice of .savenote: it is not news about this
   mailing, it is the terms every mailing is collected under */
.strip-note{flex:1;min-width:200px;margin:0;font-size:13px;line-height:1.45;color:var(--mute)}
/* the answer to the last press, on a line of its own for the same reason
   .savenote takes one */
.strip-say{flex-basis:100%;margin:0;font-size:13px;line-height:1.45;color:var(--lede)}

/* ---------------------------------------------------------------- endnote */
.endnote{padding:22px 28px 40px;max-width:92ch}
.note{margin:0 0 12px;color:var(--mute);font-size:13.5px;line-height:1.6}
.atcost{margin:0 0 10px;color:var(--lede);font-size:14.5px;line-height:1.55;font-weight:500}
.provenance{margin:0;font:500 12px/1 var(--cond);letter-spacing:.12em;text-transform:uppercase;color:var(--mute)}
/* only appears when the snapshot has aged past STALE_DAYS in page.js */
.staleness{margin:8px 0 0;color:var(--orange);font-size:13px;line-height:1.45}

/* -------------------------------------------------------------- map extras */
.pop{font:14px Barlow;color:var(--paper)}
.pop b{display:block;font-size:15px;margin-bottom:3px}
.pop .m{color:var(--mute);font-size:13px;margin-bottom:6px}
.pop .m.solo{margin:7px 0 0}
.mapboxgl-popup-content{background:var(--slate);border:1px solid var(--line);border-radius:8px}
.mapboxgl-popup-tip{border-top-color:var(--slate);border-bottom-color:var(--slate)}
.mapboxgl-popup-close-button{color:var(--mute);font-size:18px}

/* ----------------------------------------------------------------- mobile */
@media(max-width:920px){
  .top{padding:18px 16px 12px}
  .dateline{font-size:11.5px;letter-spacing:.1em}
  .lede{font-size:14.5px}
  .controls{position:static;grid-template-columns:1fr;gap:10px;padding:12px 18px;align-items:stretch}
  /* the one-column stack is the phone's answer on every screen, and the area's
     two-column desktop rule is more specific than the line above */
  body[data-screen="area"] .controls{grid-template-columns:1fr}
  .screen{display:block;height:auto;min-height:0}
  .tabs{width:100%}
  .tab{flex:1;min-width:0}
  .tab-s{font-size:12px}
  /* a slider is worked with a thumb here: it takes the whole width, and the
     reset drops under it rather than beside it */
  .area-tools{flex-direction:column;align-items:stretch;width:100%}
  #area-r{width:100%}
  .areago{width:100%;margin-top:12px}

  .stage{display:block;height:auto;border-bottom:0}
  /* the cluster screen keeps 46vh: the address list starts right under the map
     and has to be on screen without a scroll */
  .maparea{position:relative;height:46vh;min-height:300px}
  /* the city screen has no list under the map — the height that leaves room for
     one would just leave a hole instead. svh so the Safari toolbars sliding in
     cannot push the bottom of the map past the fold. */
  body[data-screen="city"] .maparea{height:70vh;height:70svh;min-height:420px}
  /* walking happens on foot, not on a map — give the phone the list */
  body[data-screen="cluster"]:has(#tab-walk[aria-selected=true]) .maparea{display:none}
  .legend > span{font-size:11.5px;padding:4px 8px}
  .zoomnote{top:12px;padding:8px 11px;font-size:12px;max-width:calc(100% - 80px)}

  /* ------------------------------------------------------------- map key */
  /* Folded by default: five rows of key cover half the map on a phone, and the
     map is the thing he came to look at. */
  /* the logo strip clears the home indicator, and the key, standing on the
     strip, rides up with it */
  .maparea{--logo-inset:calc(12px + env(safe-area-inset-bottom))}
  .legend{display:none}
  body[data-legend="open"][data-screen="city"] #legend-city{display:flex}
  body[data-legend="open"][data-screen="cluster"] #legend-cluster{display:flex}
  body[data-legend="open"][data-screen="area"] #legend-area{display:flex}
  .legend-toggle{
    display:inline-flex;align-items:center;gap:7px;
    pointer-events:auto;                       /* the one thing that takes taps */
    background:rgba(15,24,34,.86);border:1px solid rgba(51,66,79,.9);border-radius:6px;
    padding:7px 11px;color:var(--lede);cursor:pointer;
    font:600 12px/1 var(--cond);letter-spacing:.14em;text-transform:uppercase;
    backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);
  }
  .legend-toggle::after{content:'\25B4';font-size:11px;color:var(--mute);margin-left:1px}
  body[data-legend="open"] .legend-toggle::after{content:'\25BE'}

  #panel{display:block;overflow:visible;border-left:0;border-top:1px solid var(--line)}
  .pane{display:block;overflow:visible}  /* the page scrolls, not a nested box */
  .st{top:0}
  .row{padding:12px 18px;font-size:18px}
  .row .z{font-size:12px}
  .grp,.hint{padding-left:18px;padding-right:18px}
  .st{padding-left:18px;padding-right:18px}
  .foot{position:sticky;bottom:0;z-index:20;box-shadow:0 -8px 24px rgba(0,0,0,.35);
        padding-bottom:calc(14px + env(safe-area-inset-bottom))}
  .foot .go{flex:1 1 100%;padding:14px 18px;font-size:16px}   /* thumb bar */
  .endnote{padding:20px 18px 120px}

  /* -------------------------------------------------------------- strip */
  /* The page's own bottom bar: it holds the floor for the whole of .screen, so
     the mailing does not scroll away while he works a list — and it clears the
     home indicator, like the logo strip above it does.
     The panel's foot is sticky too, and two bars cannot both stand on the
     floor. CSS cannot measure one box against another, so the height the foot
     stands off is a number and the strip is held open to exactly that number.
     The answer line is lifted out of the flow for the same reason: a sentence
     appearing under the buttons would otherwise push the bar past it. */
  :root{--strip-h:calc(92px + env(safe-area-inset-bottom))}
  .foot{bottom:var(--strip-h)}
  .strip{
    position:sticky;bottom:0;z-index:21;
    min-height:var(--strip-h);align-content:center;gap:8px 12px;
    padding:12px 18px calc(12px + env(safe-area-inset-bottom));
    box-shadow:0 -8px 24px rgba(0,0,0,.35);
  }
  .strip-sum{flex:1 1 auto;min-width:0}
  /* the two buttons keep the first line; the sentence takes the second */
  .strip-note{flex-basis:100%;order:3;min-width:0;font-size:12.5px}
  .strip-say{
    position:absolute;left:18px;right:18px;bottom:100%;
    margin:0 0 8px;padding:8px 11px;
    background:var(--slate);border:1px solid var(--line);border-radius:8px;
    box-shadow:0 -6px 18px rgba(0,0,0,.3);
  }
}

/* ------------------------------------------------------------------ print */
@media print{
  body{background:#fff;color:#000;font-size:12.5px}
  /* #area-tools sits inside .controls and #area-go in the header; neither is a
     thing that can be pressed on paper */
  .controls,#area-tools,#area-go,.maparea,.foot,.wordmark,.topnav,.lede,.notice{display:none}
  .top{padding:0 0 10px}
  .built,.dateline{color:#000}
  h1{font-size:26pt}
  h1 em{color:#000}
  .stage{display:block;height:auto;border:0}
  #panel{display:block;background:#fff;border:0;overflow:visible}
  .pane{overflow:visible;padding:0}
  .hint{color:#333;border-color:#ccc;padding:8px 0}
  .grp{color:#000;padding:12px 0 4px}
  .pane .grp:first-of-type{color:#000}
  .pane .grp:first-of-type::before{background:#000}
  .st{position:static;background:#fff;color:#000;border-color:#000;padding:8px 0 4px}
  .st span{color:#333}
  .row{padding:5px 0;border-color:#ddd;color:#000;break-inside:avoid;font-size:12.5px}
  .row .z{color:#333}
  .row.pick::before{border-color:#999}
  .row.on::before{background:#000;border-color:#000}
  /* the word stays on paper, the button does not: nothing is pressed on a
     printed list */
  .row .hxact{display:none}
  /* a printed list is carried down a street; the mailing is not on it, and two
     buttons that cannot be pressed on paper are noise */
  .strip{display:none}
  .endnote{padding:14px 0 0;max-width:none}
  .note,.atcost,.provenance,.staleness{color:#000}
}

/* --------------------------------------------------------------- fallback */
/* If :has() is unavailable, the default (walk) layout applies to both modes —
   the page stays fully usable, only the mode-dependent emphasis is lost. */
@media(prefers-reduced-motion:reduce){
  *{transition:none !important}
}

/* ------------------------------------------------- postcard count control */
.mailn{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  padding:14px 18px 12px;border-bottom:1px solid var(--line);
}
.mailn label{
  font:600 12px/1 var(--cond);letter-spacing:.16em;text-transform:uppercase;color:var(--mute);
}
.mailn input{
  width:84px;padding:7px 9px;font:600 17px/1 var(--cond);letter-spacing:.02em;
  color:var(--paper);background:var(--slate-2);
  border:1px solid var(--line);border-radius:6px;
}
.mailn input:focus{outline:2px solid var(--orange);outline-offset:1px}
.mailn-note{font-size:12.5px;color:var(--mute);flex:1;min-width:150px}
@media(max-width:920px){ .mailn{padding:12px 18px 10px} }
@media print{ .mailn{display:none} }
