/* BlockLot console — the "Brief" design (B), which is also the DEFAULT SKIN.
   Light, calm and editorial: a toned paper ground, a serif for headlines, a quiet sans for
   everything read at length, numbers in tabular figures. Adopted 2026-09-29 (owner
   decision) from the three mockups on the design canvas; the other two skins, A — Terminal
   and C — Atlas, are `static/skins/*.css`, which override the tokens below and a few
   layouts. A skin never changes markup (`api/skins.py`).

   ⚠️ The dossier PDF does NOT read this file — `reports/templates/dossier.html` carries its
   own inline stylesheet. A change here is a change to the screen only.

   Chart colours (`--accent`, `--plum`, `--intel-mark`) are the categorical set validated
   with the dataviz palette checker (light surface, CVD-separated, >= 3:1 contrast). */

/* Self-hosted fonts. The demo host serves `font-src 'self'`, so nothing may come from a
   font CDN; the files are the Latin subsets from Fontsource, under the SIL OFL (licences
   beside them in `static/fonts/`). `font-display:swap` so text is never invisible while a
   font loads — the system fallback is close enough in metrics to read. */
@font-face{font-family:"Newsreader";src:url("fonts/newsreader-opsz.woff2") format("woff2");
  font-weight:200 800;font-style:normal;font-display:swap}
@font-face{font-family:"Newsreader";src:url("fonts/newsreader-opsz-italic.woff2") format("woff2");
  font-weight:200 800;font-style:italic;font-display:swap}
@font-face{font-family:"IBM Plex Sans";src:url("fonts/plex-sans-400.woff2") format("woff2");
  font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:"IBM Plex Sans";src:url("fonts/plex-sans-400-italic.woff2") format("woff2");
  font-weight:400;font-style:italic;font-display:swap}
@font-face{font-family:"IBM Plex Sans";src:url("fonts/plex-sans-500.woff2") format("woff2");
  font-weight:500;font-style:normal;font-display:swap}
@font-face{font-family:"IBM Plex Sans";src:url("fonts/plex-sans-600.woff2") format("woff2");
  font-weight:600 700;font-style:normal;font-display:swap}
@font-face{font-family:"IBM Plex Mono";src:url("fonts/plex-mono-400.woff2") format("woff2");
  font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:"IBM Plex Mono";src:url("fonts/plex-mono-500.woff2") format("woff2");
  font-weight:500;font-style:normal;font-display:swap}
@font-face{font-family:"IBM Plex Mono";src:url("fonts/plex-mono-600.woff2") format("woff2");
  font-weight:600 700;font-style:normal;font-display:swap}

:root{
  --surface:#ffffff; --page:#fbfaf7; --sunk:#f3f1ec; --ink:#17191e; --ink-2:#4a4f57;
  --ink-mute:#6b7079;
  --grid:#e5e1d8; --axis:#cfcac0; --rule:#17191e;
  --accent:#2b64b0; --accent-ink:#1e4a86; --accent-soft:#dce6f4;
  --plum:#8a5bb0;
  /* The intelligence voice — BlockLot's own analysis (Future View, the block outlook),
     as distinct from the public record it is derived from. Used for nothing else, so a
     reader learns that brick means "this is our reading, not the city's". */
  --intel:#a8481a; --intel-mark:#d56a2a; --intel-soft:#fbeee5; --intel-line:#f1d5c2;
  --good:#2e7d4f; --warn:#d99a1e; --serious:#c2562f; --crit:#b3261e;
  --font:"IBM Plex Sans",system-ui,-apple-system,"Segoe UI",sans-serif;
  --serif:"Newsreader",Georgia,"Times New Roman",serif;
  --mono:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  --display:var(--serif);
  --pad:28px; --gap:20px; --radius:10px;
  /* The width of the first column in the header AND in the bar under it, so the brand
     and the "Search people" label occupy the same slot and the two inputs line up. One
     variable, because two hand-tuned numbers drift the first time the wordmark changes. */
  --brandw:172px;
  --shadow:0 1px 2px rgba(23,25,30,.04);
  --menu-shadow:0 12px 32px rgba(23,25,30,.14);
}
*{box-sizing:border-box;margin:0;padding:0}
body{background:var(--page);color:var(--ink);font:14.5px/1.55 var(--font);-webkit-font-smoothing:antialiased;
  font-variant-numeric:tabular-nums}
a{color:inherit;text-decoration:none} a:hover{color:var(--accent)}
.num{font-variant-numeric:tabular-nums}

header{display:flex;align-items:center;gap:var(--gap);padding:14px var(--pad);
  background:var(--page);border-bottom:1px solid var(--grid);position:sticky;top:0;z-index:20}
.brand{font:600 24px/1 var(--display);letter-spacing:-.01em;white-space:nowrap;
  display:inline-flex;align-items:center;gap:10px;min-width:var(--brandw)}
.brand span{color:var(--ink-mute);font-weight:400}
/* The mark: four lots on a block, one picked out. `.bl-` is the Brief component prefix
   (tests/test_class_tokens.py). */
.bl-mark{display:inline-grid;grid-template-columns:9px 9px;gap:3px;flex:none}
.bl-mark i{width:9px;height:9px;border:1.5px solid var(--ink)}
.bl-mark i:last-child{background:var(--intel-mark);border-color:var(--intel-mark)}
.omni{flex:1;max-width:560px;padding:11px 16px;border:1px solid var(--axis);
  border-radius:8px;background:var(--surface);font:inherit}
.omni:focus{outline:2px solid var(--accent);outline-offset:-1px;border-color:transparent}
/* The person-lookup bar. It is a sibling of <header>, not a flex item inside it, so it
   is on its own line by default-stylesheet block layout rather than by `flex-wrap` — see
   the comment in base.html for why that mattered. Sticky under the header so it stays
   reachable while a long card scrolls. */
.subbar{position:sticky;top:var(--hdr-h,69px);z-index:19;background:var(--page);
  border-bottom:1px solid var(--grid);padding:0 var(--pad) 14px;
  display:flex;align-items:center;gap:var(--gap)}
/* Same slot as `.brand`, so the input beside it starts exactly where the omni above
   does. Not a hand-tuned margin: both read `--brandw`. */
.subbar-label{min-width:var(--brandw);white-space:nowrap;color:var(--ink-2);
  font-size:14px;flex:0 0 auto}
.omni-person{flex:1;max-width:560px}
nav.top{display:flex;align-items:center;gap:20px;margin-left:auto;color:var(--ink-2);font-size:14px;
  white-space:nowrap}
/* The one filled button in an anonymous header. */
.bl-navcta{background:var(--ink);color:var(--surface);padding:9px 16px;border-radius:6px;
  font-weight:500}
.bl-navcta:hover{color:var(--surface);background:color-mix(in srgb,var(--ink) 85%,var(--accent))}
main{padding:32px var(--pad);max-width:1280px;margin:0 auto}

.card{background:var(--surface);border:1px solid var(--grid);border-radius:var(--radius);
  padding:var(--pad);box-shadow:var(--shadow);margin-bottom:var(--gap)}
/* Section titles are set in the serif: the page reads as a brief, one chapter per card. */
.card>h2{font:500 25px/1.2 var(--display);letter-spacing:-.005em;margin-bottom:10px;display:flex;
  align-items:baseline;gap:10px;flex-wrap:wrap}
.card>h2 .sub{font:400 13px/1.4 var(--font);color:var(--ink-mute);letter-spacing:0}
/* Sub-sections inside a card (Neighborhood's Crime / Mortgage lending / Zoned schools, the
   broker page's record blocks). The reset left them flush against the table above, at
   default h3 size, so a new sub-section read as another row of the previous one. A
   hairline and 28px of air make each one read as its own chapter. */
.card>h3{font-size:16px;font-weight:600;margin:28px 0 8px;padding-top:16px;
  border-top:1px solid var(--grid);display:flex;align-items:baseline;gap:9px;flex-wrap:wrap}
.card>h3:first-child,.card>h2+h3{margin-top:0;padding-top:0;border-top:0}
.card>h3 .sub{font-size:12px;font-weight:400;color:var(--ink-mute)}
/* The same `.sub` inside a table cell or a definition had no rule at all, so "8 of 78"
   and "1 = quietest" ran together as one phrase at full weight. */
.card td .sub,.card dd .sub{font-size:12px;color:var(--ink-mute);margin-left:4px}
.card dd div.sub{margin-left:0}
.row{display:flex;gap:var(--gap);flex-wrap:wrap}.row>*{flex:1;min-width:220px}
.eyebrow{font-size:12px;text-transform:uppercase;letter-spacing:.07em;
  color:var(--ink-mute);font-weight:600;margin-bottom:8px}
/* The headline number or name of a card. Serif, like the section titles: it is what the
   card is ABOUT, and the figures around it are set in the sans. */
.hero{font:500 68px/1.02 var(--display);letter-spacing:-.02em}
.hero-sub{font-size:18px;color:var(--ink-2);margin-top:8px}
/* REQ-204: the header's ZIP and block numbers, and the Owner card's roll owner, are
   links. Links here inherit their colour, so these carry a quiet underline to read as one. */
.hero-link,.owner-link{text-decoration:underline;text-decoration-color:var(--grid);
  text-underline-offset:4px}
.hero-link:hover,.owner-link:hover{text-decoration-color:currentColor}
.hero-link b{color:var(--ink)}

.tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:var(--gap)}
.tile{background:var(--surface);border:1px solid var(--grid);border-radius:var(--radius);padding:18px}
.tile .lab{font-size:12px;letter-spacing:.04em;text-transform:uppercase;color:var(--ink-mute);
  font-weight:600;margin-bottom:6px}
.tile .val{font-size:28px;font-weight:500;line-height:1.15;letter-spacing:-.01em}
.tile .meta{font-size:12px;color:var(--ink-mute);margin-top:4px}

/* The five-value confidence vocabulary. Fixed set — never invent a sixth. */
.chip{display:inline-flex;align-items:center;gap:4px;font:600 11px/1 var(--font);
  letter-spacing:.03em;padding:3px 6px;border-radius:3px;border:1px solid;white-space:nowrap}
.chip::before{font-size:9px}
.c-verified{color:var(--good);border-color:color-mix(in srgb,var(--good) 42%,transparent);
  background:color-mix(in srgb,var(--good) 9%,transparent)}.c-verified::before{content:"✓"}
.c-inferred{color:var(--accent);border-color:color-mix(in srgb,var(--accent) 42%,transparent);
  background:color-mix(in srgb,var(--accent) 9%,transparent)}.c-inferred::before{content:"≈"}
.c-proxy{color:var(--serious);border-color:color-mix(in srgb,var(--serious) 48%,transparent);
  background:color-mix(in srgb,var(--serious) 11%,transparent)}.c-proxy::before{content:"~"}
.c-ambiguous{color:var(--warn);border-color:color-mix(in srgb,var(--warn) 55%,transparent);
  background:color-mix(in srgb,var(--warn) 13%,transparent)}.c-ambiguous::before{content:"?"}
.c-unavail{color:var(--ink-mute);border-color:var(--axis)}.c-unavail::before{content:"—"}

/* Gate (d) — the peer set stated at headline weight when it is not the block. A weaker
   comp set has to announce itself where the number is read, not in the subhead. */
.scope-flag{display:inline-block;font:600 12px/1 var(--font);letter-spacing:.01em;
  color:var(--ink-2);background:color-mix(in srgb,var(--warn) 13%,transparent);
  border:1px solid color-mix(in srgb,var(--warn) 45%,transparent);
  border-radius:3px;padding:5px 8px;margin-bottom:8px}
.scope-flag .muted{font-weight:400}

/* A coded value from a published city vocabulary — building class, zoning district,
   land use. Monospaced because these are identifiers, not words, and neutral in colour
   because a code carries no severity: `R6B` is not better or worse than `M1-4`. REQ-4
   attaches the definition drawer to this class, so there is one hook, not one per field. */
.chip.code{font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,monospace);
  letter-spacing:0;color:var(--ink-2);border-color:var(--axis);
  background:color-mix(in srgb,var(--ink-2) 6%,transparent)}
/* Only a code with a published definition is clickable. The dotted underline is the
   same "there is more behind this" language `.drill` uses, kept quieter because a
   dossier carries a dozen of these and twelve dashed rules would read as noise. */
button.chip.code{cursor:default}
button.chip.code.defined{cursor:pointer;
  border-bottom:1px dashed color-mix(in srgb,var(--accent) 55%,transparent)}
button.chip.code.defined:hover,button.chip.code.defined:focus-visible{
  color:var(--accent);border-color:color-mix(in srgb,var(--accent) 45%,transparent);
  background:color-mix(in srgb,var(--accent) 8%,transparent)}
/* A concept, triggered from its label. Inherits `.drill` and drops its accent colour:
   the definition of "Built FAR" is reference material, not a finding, and colouring
   every <dt> on the page would drown the counts that genuinely do drill into rows. */
.drill.term{color:var(--ink-2);font:inherit;
  border-bottom-color:color-mix(in srgb,var(--ink-2) 34%,transparent)}
.drill.term:hover,.drill.term:focus-visible{color:var(--accent)}

/* Markers — chip *shape*, deliberately NOT a sixth confidence value. `m-` not `c-`.
   A confidence chip answers "how sure are we"; this answers "which row are you on",
   which is navigation. Mixing them would make the five-value set unreadable. */
.m-here{color:var(--ink-2);border-color:var(--axis);
  background:color-mix(in srgb,var(--ink-2) 8%,transparent)}.m-here::before{content:"▸"}
/* The subject's own row in the owner's-portfolio table (BUG-17). Tinted and ruled on the
   left so it is identifiable at a glance without reading the chip. */
tr.pf-subject>td{background:color-mix(in srgb,var(--ink-2) 5%,transparent)}
tr.pf-subject>td:first-child{box-shadow:inset 2px 0 0 var(--ink-2)}

/* BUG-32. A link the gate rejected, in the traversed-links table. It was a grey `.chip`
   in the last column, which on a real subject — 8 rejected against 2 kept — read at a
   glance as ten members of the portfolio. This is a debug surface: the verdict is the
   most important thing in the row, so it is ruled and tinted down the row's whole length
   rather than announced at the end of it. Deliberately NOT a sixth `c-` confidence
   value: "not counted" is what the rule DID, not how sure it is. */
tr.gate-off>td{background:color-mix(in srgb,var(--serious) 6%,transparent)}
tr.gate-off>td:first-child{box-shadow:inset 3px 0 0 var(--serious)}
.siblings table.weak tr.gate-off>td{color:color-mix(in srgb,var(--ink-mute) 70%,var(--ink-2))}
/* The condition the row lost on, under its verdict. */
.gate-why{font:400 10px/1.25 var(--font);color:var(--ink-mute);margin-top:3px;
  max-width:190px;white-space:normal}

/* Status pills — icon + label always, never colour alone. */
.pill{display:inline-flex;align-items:center;gap:4px;font:600 11px/1 var(--font);
  padding:4px 8px;border-radius:99px;white-space:nowrap}
.p-crit{background:color-mix(in srgb,var(--crit) 15%,transparent);color:var(--crit)}
.p-serious{background:color-mix(in srgb,var(--serious) 18%,transparent);color:var(--serious)}
.p-warn{background:color-mix(in srgb,var(--warn) 20%,transparent);color:color-mix(in srgb,var(--warn) 72%,var(--ink))}
.p-good{background:color-mix(in srgb,var(--good) 13%,transparent);color:var(--good)}

/* Two timestamps, never one: the city's publish lag is not our pull cadence. */
.stamp{display:inline-flex;gap:8px;align-items:center;font-size:12px;color:var(--ink-mute)}
.stamp b{font-weight:600;color:var(--ink-2);font-variant-numeric:tabular-nums}
.stamp .sep{width:1px;height:11px;background:var(--axis)}

table{width:100%;border-collapse:collapse;font-size:14px}
th{text-align:left;font-size:11px;text-transform:uppercase;letter-spacing:.06em;
  color:var(--ink-mute);font-weight:600;padding:8px 10px;border-bottom:1px solid var(--rule);white-space:nowrap}
td{padding:0 10px;height:44px;border-bottom:1px solid var(--grid);vertical-align:middle}
/* A ROW header (`<th scope=row>` in a tbody — the account page's label column) is a
   label, not a column heading: it takes the row's light rule, not the ink one. */
tbody th{border-bottom-color:var(--grid)}
tbody tr:hover{background:var(--page)}
td.n,th.n{text-align:right}td.n{font-variant-numeric:tabular-nums}
.muted{color:var(--ink-mute)}
/* Density toggle — Studio rows are generous; list-heavy screens need the option. */
table.compact td{height:30px}table.compact th{padding:6px 8px}

/* The history card (`_history.html`), on `/` and `/account`. TWO groups, because a search
   and a report are two different lines on the bill: reports spend the monthly allowance,
   searches spend only the burst windows. Reading the list should not require reading each
   tag to work out which of the two you are looking at.

   The tag carries the colour, not the row text: the keys are LINKS the customer has
   already paid for, and recolouring a link to signal its category makes it look disabled.
   The search rows get the muted treatment because they are the cheap half — you scan past
   them to find the report you opened. */
.hkind{display:inline-block;min-width:70px;margin-right:9px;padding:2px 7px;
  border:1px solid transparent;border-radius:5px;text-align:center;
  font:600 10px/1.6 var(--font);letter-spacing:.06em;text-transform:uppercase}
.hrow-report .hkind{color:var(--accent);background:var(--accent-soft);
  border-color:color-mix(in srgb,var(--accent) 30%,transparent)}
.hrow-search .hkind{color:var(--ink-mute);background:color-mix(in srgb,var(--axis) 26%,transparent);
  border-color:var(--axis)}
.hrow-search td{color:var(--ink-2)}
/* The BBL kept beside the address on a property row. The address is the label a person
   recognises; the BBL is the identity the link is built on and the one they paste into
   ACRIS, so it stays visible rather than being traded away for readability. */
.hbbl{margin-left:8px;color:var(--ink-mute);font-size:12px;
  font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,monospace)}

.flow{display:grid;grid-template-columns:1fr auto 1fr;align-items:stretch;margin-top:var(--gap)}
.flow-col h3{font-size:11px;text-transform:uppercase;letter-spacing:.07em;
  color:var(--ink-mute);font-weight:600;margin-bottom:10px}
.node{display:flex;justify-content:space-between;gap:8px;align-items:center;padding:7px 11px;
  margin-bottom:4px;border-radius:5px;background:var(--page);border:1px solid var(--grid);font-size:12px}
.pivot{display:flex;flex-direction:column;justify-content:center;align-items:center;
  gap:8px;padding:0 30px;position:relative}
.pivot .box{background:var(--accent);color:#fff;border-radius:var(--radius);
  padding:18px 22px;text-align:center;max-width:210px}
.pivot .box .t{font-size:11px;opacity:.85;text-transform:uppercase;letter-spacing:.06em}
.pivot .box .a{font-weight:650;margin-top:4px;line-height:1.35}
.pivot::before,.pivot::after{content:"";position:absolute;top:50%;width:30px;height:1px;background:var(--axis)}
.pivot::before{left:0}.pivot::after{right:0}

/* Bar: open state filled, closed history greyed behind. Two states of one measure. */
.bars{display:flex;flex-direction:column;gap:2px;margin-top:10px}
.bar-row{display:grid;grid-template-columns:170px 1fr 76px;align-items:center;gap:10px;font-size:12px}
.bar-track{height:16px;background:var(--page);border-radius:2px}
.bar-ghost{height:16px;background:var(--grid);border-radius:0 4px 4px 0;
  outline:1px solid var(--axis);outline-offset:-1px;max-width:100%}
.bar-fill{height:16px;background:var(--accent);border-radius:0 4px 4px 0;max-width:100%}
.bar-row .v{text-align:right;font-variant-numeric:tabular-nums;color:var(--ink-2)}
.bar-row .v em{font-style:normal;color:var(--ink-mute);font-size:.85em}

.note{border-left:2px solid var(--accent);padding:11px 14px;background:var(--page);
  border-radius:0 var(--radius) var(--radius) 0;font-size:12px;color:var(--ink-2);margin-top:var(--gap)}
.note b{color:var(--ink)}.warnbox{border-left-color:var(--warn)}
/* REQ-7. The drawer's required explanation, set apart from the caveat that may follow it:
   one answers "what am I looking at", the other "what is wrong with it". Same box at a
   slightly higher contrast, because it is the first thing to read, not an aside. */
.note.what{color:var(--ink);font-size:13.5px}
/* BUG-22. A break in a year sequence, set apart from the data it interrupts: hatched,
   italic and un-numeric, so it cannot be scanned as a row of values. */
.row-gap>td{background:repeating-linear-gradient(135deg,transparent,transparent 5px,
  var(--page) 5px,var(--page) 10px);color:var(--ink-2);font-style:italic;font-size:12px;
  border-top:1px solid var(--axis);border-bottom:1px solid var(--axis)}
.kv{display:grid;grid-template-columns:auto minmax(0,1fr);gap:5px 18px}
.kv dt{color:var(--ink-mute);white-space:nowrap}
/* DOF sales sits BELOW the Value & tax grid, full card width (2026-09-29). As a `<dd>`
   it was a six-column table in the value column of a half-width card and ran past the
   card's right edge on almost every building. It scrolls inside the card if it is still
   wider than that, never over the edge. */
.dof-sales{margin-top:14px}
.dof-sales-h{color:var(--ink-mute);margin-bottom:4px}
.dof-sales-scroll{max-width:100%;overflow-x:auto}
.dof-sales table{width:100%}
.dof-sales table.compact th,.dof-sales table.compact td{padding-left:6px;padding-right:6px}
.dof-sales td:nth-child(3){white-space:nowrap}
.dof-sales .chip{white-space:normal;line-height:1.25;text-align:left}
/* Sales history (IDEA-13): on a condo unit's page, that unit's own sales. */
.sales-history tr.mine td{font-weight:600}
.sales-history .chip{white-space:normal;line-height:1.25;text-align:left}
.nowrap{white-space:nowrap}
/* Owner card "Last sold" — the second line under the date and price. */
.last-sold-why{font-size:12.5px;margin-top:2px}
.btn{font:500 13px/1 var(--font);padding:10px 15px;border-radius:6px;
  border:1px solid var(--axis);background:var(--surface);color:var(--ink);cursor:pointer;
  display:inline-flex;align-items:center;gap:6px}
.btn:hover{border-color:var(--ink-2);color:var(--ink)}
/* Primary is ink, not blue: blue is the colour of DATA on these pages (links, bars,
   inferred chips), and a blue button reads as one more chart element. */
.btn.pri{background:var(--ink);border-color:var(--ink);color:var(--surface)}
.btn.pri:hover{color:var(--surface);background:color-mix(in srgb,var(--ink) 85%,var(--accent))}
.toolbar{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:var(--gap)}
.htmx-request{opacity:.55;transition:opacity .1s}

/* ── The auth surface (Phase 1 of users-and-access) ──────────────────────────────
   Plain form controls, because these pages carry no JavaScript at all: the demo host
   enforces `script-src 'self'` and refuses an inline handler silently (BUG-113). Every
   affordance below is a <form>, a <button type=submit> or an <a>. */
.card.narrow{max-width:460px;margin-left:auto;margin-right:auto}
.formrow{margin-top:14px;display:block;color:var(--ink-2);font-size:12px;font-weight:600}
.field{width:100%;padding:11px 14px;border:1px solid var(--axis);border-radius:var(--radius);
  background:var(--page);font:inherit;margin-top:5px}
.field:focus{outline:2px solid var(--accent);outline-offset:-1px;border-color:transparent}
.btn.small{padding:6px 10px;font-size:11px}
.linkbtn{background:none;border:0;padding:0;font:inherit;color:inherit;cursor:pointer}
.linkbtn:hover{color:var(--accent)}
nav.top form.inline{display:inline}
.note form.inline{display:inline;margin-left:6px}
.small{font-size:12px}
.critbox{border-left-color:var(--crit);color:var(--ink)}
.goodbox{border-left-color:var(--good)}
/* An error on a form belongs above the form, not below it — and `.note`'s top margin
   would otherwise push it away from the heading it belongs to. */
.card>.note:first-of-type{margin-top:12px}

/* The dossier PDF is this stylesheet plus these rules — one template, two outputs. */
@media print{
  header,nav.top,.toolbar,.btn{display:none}
  body{background:#fff}
  main{max-width:none;padding:0}
  .card{box-shadow:none;border:0;border-bottom:1px solid var(--grid);
    border-radius:0;break-inside:avoid;page-break-inside:avoid}
  .hero{font-size:44px}
}

/* ── Drill-down drawer ───────────────────────────────────────────────────────
   A right-hand drawer rather than a floating popover, and click rather than hover.
   These are tables — 22 fields on a sale, 400 violations on a bad building — and a
   popover either covers the row you clicked or grows its own scrollbar. Hover was
   rejected outright: it fires by accident, does not exist on touch, and you cannot
   select text out of something that disappears when you move toward it. */
.drill{background:none;border:0;padding:0;font:inherit;color:var(--accent);cursor:pointer;
  border-bottom:1px dashed color-mix(in srgb,var(--accent) 45%,transparent)}
.drill:hover{border-bottom-style:solid}
.drill .badge{display:inline-block;min-width:18px;padding:1px 5px;margin-left:5px;
  border-radius:99px;background:var(--accent);color:#fff;font:600 10px/1.5 var(--font);
  font-variant-numeric:tabular-nums}
.drill .badge.zero{background:var(--grid);color:var(--ink-mute)}
.drawer{position:fixed;top:0;right:0;bottom:0;width:min(1080px,88vw);background:var(--surface);
  border-left:1px solid var(--axis);box-shadow:-16px 0 48px rgba(11,11,11,.13);
  z-index:60;display:flex;flex-direction:column;transform:translateX(100%);
  transition:transform .16s ease-out}
.drawer.open{transform:none}
/* HTMX swaps the record fragment into `#drawer-body`, so `.drawer-head` and
   `.drawer-scroll` are grandchildren of `.drawer`, not children. `.drawer > *` therefore
   only ever matched `#drawer-body`, and `.drawer-scroll{flex:1}` had no flex parent to
   act on — the drawer rendered and then would not scroll, on every one of the 25 record
   sets. `#drawer-body` has to be the flex container the CSS was written for.

   `min-height:0` is the load-bearing declaration: a column flex item defaults to
   `min-height:auto` and refuses to shrink below its content, which is precisely why
   `overflow:auto` on a descendant did nothing.

   Rejected: `overflow:auto` on `#drawer-body` itself. Simpler, but the header and its
   close button scroll away with the content — worse on exactly the long tables this
   exists to fix. */
#drawer-body{display:flex;flex-direction:column;flex:1;min-height:0;padding:0}
.drawer-head{display:flex;align-items:center;gap:12px;padding:18px 22px 12px;
  border-bottom:1px solid var(--grid)}
.drawer-head h3{font-size:16px;font-weight:650;flex:1}
.drawer-head .muted{font-weight:400;font-size:12px;margin-left:8px}
.drawer-x{background:none;border:0;font-size:26px;line-height:1;color:var(--ink-mute);
  cursor:pointer;padding:0 4px}
.drawer-x:hover{color:var(--ink)}
/* Horizontal padding lands here, and ONLY here. `.drawer-head` already carries its own
   `18px 22px 12px` and `.note` its own `11px 14px` — they were being indented twice by
   the `.drawer > *` rule (44px and 36px). The scroller is the one element that had no
   padding of its own. */
.drawer-scroll{overflow:auto;flex:1;padding:0 22px 22px}
/* A wide set scrolls sideways (see `.drawer-scroll td` below), and macOS hides scrollbars
   until you scroll — so a clipped column looked like the table's last one. Edge shadows
   say "more this way" with no JS: the `local` covers ride with the content and hide the
   `scroll` shadows pinned to the box, so a shadow shows only where content is cut off. */
@media (min-width:861px){
  .drawer-scroll{background:
    linear-gradient(to right,var(--surface) 30%,transparent) left/28px 100% no-repeat local,
    linear-gradient(to left,var(--surface) 30%,transparent) right/28px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%,rgba(11,11,11,.16),transparent) left/10px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%,rgba(11,11,11,.16),transparent) right/10px 100% no-repeat scroll;
    background-color:var(--surface)}
}
/* Scoped to the drawer rather than changing `.note` globally: that class appears 40 times
   across 8 templates including the PDF, and re-padding all of them to line up one panel
   is not a trade worth making. Inside the drawer it aligns with the head and the
   scroller at 22px; everywhere else it keeps its 14px. */
#drawer-body>.note{padding:11px 22px}
/* Wrapping is decided PER COLUMN, not per cell, and a cell that is not prose never wraps.
   Until 2026-09-26 every cell carried `overflow-wrap:anywhere`, which does more than
   permit a break: it makes a cell's MIN-CONTENT width one character. Auto table layout
   squeezes a table wider than its box down to min-content, so a 10-column mortgage table
   in a 760px drawer broke `2019-03-26` into `2019 / -03- / 26`, a CRFN into four lines
   and a lender name into one syllable per line — on every wide set, at desktop width.

   Now: `nowrap` by default (dates, ids, money, codes — values that are wrong when split),
   and `_records.html` marks a column `.wrap` when its longest value is prose-length.
   A `.wrap` cell gets a floor (`min-width`) so it is never squeezed below a readable
   measure, and `break-word` rather than `anywhere`, which breaks an overlong token only
   when it would otherwise overflow and leaves min-content alone.

   If the floors still add up to more than the drawer, the table scrolls sideways inside
   `.drawer-scroll`. That trade was rejected for PHONES in BUG-14 (below, where rows stack
   instead); at desktop width a 13-column job-filings table has no third option — it
   either scrolls or crushes, and crushing was this bug. */
.drawer-scroll table{font-size:12px}
.drawer-scroll td{height:auto;padding:5px 8px;overflow-wrap:anywhere}
/* Desktop only. Below 861px the rows stack (BUG-14, further down) and a 22em floor on a
   stacked cell pushed the whole drawer, notes included, off a 420px screen. */
@media (min-width:861px){
  .drawer-scroll td{white-space:nowrap;overflow-wrap:normal}
  .drawer-scroll td.wrap{white-space:normal;overflow-wrap:break-word;min-width:13em}
  .drawer-scroll td.wrap.long{min-width:22em}
}
.drawer-scroll td,.drawer-scroll th{vertical-align:top}
/* The column names stay pinned while the rows scroll (owner request, 2026-09-26): a
   61-row permits drawer otherwise leaves you reading `0 · — · 2025-06-22` with no idea
   which column is which. Sticks to the top of `.drawer-scroll`, the element that scrolls;
   the notes above it are outside the scroller and never move.

   Two details that are load-bearing:
   - an opaque background, or the rows paint through the header as they pass under it;
   - the rule is a box-shadow, not the inherited `border-bottom`: under
     `border-collapse:collapse` a border belongs to the table grid, not the cell, and it
     stays behind while the stuck cell moves, so the header lost its underline on scroll.
   Desktop only because below 861px the rows stack and the thead is hidden (BUG-14). */
@media (min-width:861px){
  .drawer-scroll thead th{position:sticky;top:0;z-index:2;background:var(--surface);
    border-bottom:0;box-shadow:inset 0 -1px 0 var(--axis)}
}
.scrim{position:fixed;inset:0;background:rgba(11,11,11,.22);z-index:55;opacity:0;
  pointer-events:none;transition:opacity .16s}
.scrim.open{opacity:1;pointer-events:auto}
@media print{.drawer,.scrim{display:none}}

/* Badge pair: live count, then the historical total when they differ. A bare "0" on a
   building with three closed court cases reads as "nothing here" and the operator never
   clicks — so the second badge keeps the history visible without overstating it. */
.drill .badge.hist{background:transparent;color:var(--ink-mute);
  border:1px solid var(--axis);margin-left:3px}
/* The distress pill as a drill trigger (REQ-6). The base `.drill` dashed underline is
   dropped — running a dashed rule under a pill reads as a rendering fault, not as an
   affordance. The pill carries its own hint instead, and lifts a ring on hover. */
.score-drill{border-bottom:0;display:inline-flex;align-items:center;gap:6px}
.score-drill .pill{transition:box-shadow .12s}
.score-drill:hover .pill,.score-drill:focus-visible .pill{
  box-shadow:0 0 0 2px color-mix(in srgb,var(--accent) 35%,transparent)}
.score-drill .hint{font:600 11px/1 var(--font);color:var(--accent);
  border-bottom:1px dashed color-mix(in srgb,var(--accent) 45%,transparent)}
.score-drill:hover .hint{border-bottom-style:solid}
.drill.empty{color:var(--ink-mute);border-bottom-color:transparent;cursor:default}
.drill.empty .badge{background:var(--grid);color:var(--ink-mute)}

/* ── Merged timeline ─────────────────────────────────────────────────────────
   Five feeds on one axis. Colour is never the only carrier: every row prints its
   feed in the legend above and its own words beside the dot, so the rail reads the
   same in greyscale, in the PDF, and to anyone who cannot separate red from green. */
.tl{position:relative;max-height:620px;overflow:auto;padding-left:4px}
.tl-legend{display:flex;gap:14px;flex-wrap:wrap;margin:2px 0 14px;font-size:12px}
.tl-key{display:inline-flex;align-items:center;gap:6px;color:var(--ink-2)}
.tl-key::before{content:"";width:9px;height:9px;border-radius:99px;background:var(--tone)}
/* The legend doubles as the filter (see property.html). A switched-off key keeps its
   place and its words, struck through and faded, so what is hidden stays named. */
button.tl-key{background:none;border:1px solid transparent;border-radius:999px;
  padding:3px 9px 3px 7px;font:inherit;font-size:12px;cursor:pointer}
button.tl-key:hover{border-color:var(--grid)}
button.tl-key[aria-pressed=false]{opacity:.45;text-decoration:line-through}
.tl-hint{font-size:11.5px;align-self:center}
/* A year's event count; rewritten to "N shown of …" while the legend filters. */
.tl-yn{font-variant-numeric:tabular-nums}
.tl-year[hidden],.tl-row[hidden]{display:none}
.tl-key b{font-weight:650;color:var(--ink)}
.tl-year{display:flex;align-items:baseline;gap:10px;position:sticky;top:0;z-index:1;
  background:var(--surface);padding:9px 0 5px;border-bottom:1px solid var(--grid);
  font-size:12px;color:var(--ink-mute)}
.tl-y{font:600 19px/1.2 var(--display);color:var(--ink);letter-spacing:-.01em}
.tl-row{display:grid;grid-template-columns:96px 1fr auto;gap:12px;align-items:baseline;
  padding:7px 0 7px 20px;position:relative;border-bottom:1px solid var(--grid)}
/* The rail: one continuous line down the gutter, a dot per event. */
.tl-row::before{content:"";position:absolute;left:4px;top:0;bottom:0;width:1px;background:var(--grid)}
.tl-row::after{content:"";position:absolute;left:0;top:13px;width:9px;height:9px;
  border-radius:99px;background:var(--tone);outline:2px solid var(--surface)}
.tl-date{font-size:12px;color:var(--ink-mute);white-space:nowrap}
.tl-what{min-width:0}
.tl-detail{font-size:12px;margin-top:2px;overflow-wrap:anywhere}
.tl-x{font-size:11px;font-weight:650;color:var(--ink-2);background:var(--page);
  border:1px solid var(--grid);border-radius:99px;padding:1px 6px;margin-left:2px}
.tl-amt{font-size:13px;font-weight:650;white-space:nowrap}
/* Tone is a variable, so the dot, the legend swatch and any future mark stay in step. */
.t-deal{--tone:var(--accent)}
.t-debt{--tone:var(--plum)}
.t-work{--tone:var(--good)}
.t-distress{--tone:var(--crit)}
.t-complaint{--tone:var(--warn)}
/* Housing court, evictions and vacate orders. `--serious` sits between the complaint's
   amber and the violation's red, which is the right reading: these are not a worse
   violation, they are a different kind of event — somebody acted. */
.t-legal{--tone:var(--serious)}
/* A row the activity chart jumped to (console.js), briefly marked so the eye lands. */
.tl-row.tl-jump{background:color-mix(in srgb,var(--accent) 9%,transparent)}
/* ── Timeline activity chart (`ac-`, REQ-195) ───────────────────────────────────
   Server-drawn SVG above the event list. Colour comes only from `--tone`, the same
   variable as the legend and the dots, so every skin recolours it with no rule here; a
   split feed (violations by class) varies opacity, never hue. The period switch is three
   radios and the sibling rules below — no script. */
.ac{position:relative;margin:0 0 14px}
.ac-r{position:absolute;opacity:0;width:1px;height:1px;pointer-events:none}
.ac-pick{display:flex;align-items:center;gap:5px;margin:0 0 6px;font-size:12px}
.ac-l{cursor:pointer;padding:2px 10px;border:1px solid var(--grid);border-radius:999px;color:var(--ink-2)}
.ac-l:hover{border-color:var(--axis)}
.ac-r-quarter:checked~.ac-pick .ac-l-quarter,.ac-r-year:checked~.ac-pick .ac-l-year,
.ac-r-month:checked~.ac-pick .ac-l-month{background:var(--accent-soft);border-color:var(--accent);color:var(--ink)}
.ac-r-quarter:focus-visible~.ac-pick .ac-l-quarter,.ac-r-year:focus-visible~.ac-pick .ac-l-year,
.ac-r-month:focus-visible~.ac-pick .ac-l-month{outline:2px solid var(--accent);outline-offset:1px}
.ac-v{display:none;margin:0}
.ac-r-quarter:checked~.ac-v-quarter,.ac-r-year:checked~.ac-v-year,
.ac-r-month:checked~.ac-v-month{display:block}
.ac-svg{display:block;width:100%;height:auto;max-height:230px;overflow:visible}
.ac-grid{stroke:var(--grid);stroke-width:1}
.ac-yl,.ac-xl{fill:var(--ink-mute);font:10px var(--font);font-variant-numeric:tabular-nums}
.ac-hit{fill:transparent}
.ac-bar{cursor:pointer}
.ac-bar:hover .ac-hit{fill:var(--sunk)}
/* The surface-coloured hairline is what separates two segments of one hue. */
.ac-seg{fill:var(--shade,var(--tone));stroke:var(--surface);stroke-width:.6}
/* A split feed's series by their place in `series_by_title` (violations: HPD C, I, B, A,
   ungraded HPD, then DOB, ECB). HPD's classes fade toward the surface; DOB and ECB mix
   toward the ink, so the agencies separate from the classes in light AND dark skins. */
.ac-k0{--shade:var(--tone)}
.ac-k1{--shade:color-mix(in srgb,var(--tone) 80%,var(--surface))}
.ac-k2{--shade:color-mix(in srgb,var(--tone) 62%,var(--surface))}
.ac-k3{--shade:color-mix(in srgb,var(--tone) 42%,var(--surface))}
.ac-k4{--shade:color-mix(in srgb,var(--tone) 30%,var(--surface))}
.ac-k5{--shade:color-mix(in srgb,var(--tone) 55%,var(--ink))}
.ac-k6{--shade:color-mix(in srgb,var(--tone) 30%,var(--ink))}
.ac-k7{--shade:color-mix(in srgb,var(--tone) 50%,var(--ink-mute))}
.ac-seg.ac-off{display:none}
.ac-tip{position:absolute;z-index:3;pointer-events:none;min-width:150px;background:var(--surface);
  border:1px solid var(--axis);border-radius:6px;box-shadow:var(--menu-shadow);
  padding:6px 9px;font-size:12px;line-height:1.5;color:var(--ink)}
.ac-tip-h{font-weight:650;margin-bottom:2px}
.ac-tip-row{display:flex;justify-content:space-between;gap:14px;font-variant-numeric:tabular-nums}
.ac-tip-tot{border-top:1px solid var(--grid);margin-top:3px;padding-top:2px;font-weight:650}
.ac-shades{display:flex;flex-wrap:wrap;align-items:center;gap:4px 12px;margin:4px 0 0}
.ac-shade{display:inline-flex;align-items:center;gap:5px}
.ac-shade i{width:10px;height:10px;border-radius:2px;background:var(--shade,var(--tone))}
.ac-headline{margin:8px 0 2px;font-size:13px;font-weight:600;color:var(--ink)}
.ac-headline[hidden]{display:none}
.ac-note{margin:2px 0 0}
.ac-masked{margin:0 0 12px}
/* The counts as a table, for a screen reader: off-screen, never `display:none`. */
.ac-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);
  clip-path:inset(50%);white-space:nowrap}
@media print{.ac-pick,.ac-tip,.ac-note{display:none}}
/* ── Changes card (`ch-`) ───────────────────────────────────────────────────────
   Its own prefix, per the class-token collision guard: nothing here may reuse a `tl-`
   token, because the two cards sit one above the other and a shared token would make a
   change to one silently restyle the other.

   It borrows the timeline's `--tone` variable and nothing else. Same colour per feed on
   both cards is the point — a violation is the same red whether it is being placed on the
   axis or reported as having moved — and `--tone` is already the one definition of that. */
.ch-list{display:flex;flex-direction:column;gap:2px}
.ch-row{display:grid;grid-template-columns:96px 1fr;gap:12px;align-items:baseline;
  position:relative;padding:7px 0 7px 18px;border-bottom:1px solid var(--grid)}
.ch-row:last-child{border-bottom:0}
/* The tone dot, same geometry as the axis so the eye reads the two cards as one system. */
.ch-row::before{content:"";position:absolute;left:0;top:13px;width:9px;height:9px;
  border-radius:99px;background:var(--tone)}
.ch-when{font-size:12px;color:var(--ink-mute);white-space:nowrap}
/* D2 — the source's own date, when it publishes one, under ours. NULL on every event the
   box can render today; carried so the day a feed starts publishing one costs no change
   here. */
.ch-src{display:block;font-size:11px}
.ch-what{min-width:0}
.ch-feed{font-weight:600}
.ch-verb{font-size:12px;margin-left:4px}
/* The fold count, borrowing the Timeline's `.tl-x` geometry without its token. */
.ch-x{font-size:11px;font-weight:650;color:var(--ink-2);background:var(--page);
  border:1px solid var(--grid);border-radius:99px;padding:1px 6px;margin-left:2px}
.ch-diffs{margin-top:3px;display:flex;flex-direction:column;gap:2px}
/* `overflow-wrap:anywhere` because the values are whatever the agency wrote. 311
   resolution text is a paragraph — cut to 140 chars in `watch.read`, which still wraps. */
.ch-diff{font-size:12px;overflow-wrap:anywhere}
.ch-col{display:inline-block;min-width:104px}
.ch-from{color:var(--ink-mute);text-decoration:line-through;text-decoration-thickness:1px}
.ch-to{font-weight:600}
.ch-arrow{margin:0 3px}

/* Per-row history inside a drawer. The badge toggles a native <details>, so ctrl-F and
   the print stylesheet reach inside it without JavaScript — the same reasoning as
   `.siblings` below. The rows are already fetched: no request, no route. */
/* The history row sits under its data row and must not read as another record: no cell
   border, and the top border of the NEXT data row is what separates them. */
.ch-hist-tr>.ch-hist-cell{border-top:0;padding:0 0 7px}
.ch-hist{margin:2px 0 0}
.ch-hist>summary{cursor:pointer;list-style:none;display:inline-flex;align-items:center;
  gap:5px;font-size:11px;font-weight:650;color:var(--accent);background:var(--page);
  border:1px solid var(--grid);border-radius:99px;padding:1px 7px}
.ch-hist>summary::-webkit-details-marker{display:none}
.ch-hist[open]>summary{color:var(--ink-2)}
.ch-hist-body{margin:6px 0 4px;padding-left:10px;border-left:2px solid var(--grid);
  display:flex;flex-direction:column;gap:5px}
.ch-hist-row{font-size:11px;line-height:1.45}
.ch-hist-when{color:var(--ink-mute);white-space:nowrap;margin-right:6px}

/* On paper the scroll box would silently amputate the axis — print it whole. */
@media print{.tl{max-height:none;overflow:visible}.tl-year{position:static}}

/* Filtered sets spell their states out — "(24 open; 422 closed)" — because two bare
   numbers make the reader guess which is which, and they guess the small one is all
   there is. The live half is emphasised; the retired half stays quiet but present. */
.drill .states{margin-left:5px;font-weight:600;font-size:11px;color:var(--accent)}
.drill.empty .states{color:var(--ink-mute)}

/* Co-registered entities — the LLC-side portfolio, on the property page and the owner
   page. A native <details>: the group is a lead rather than a fact, so it opens because
   the reader chose to follow it, and it collapses back out of the way. Native also means
   the print stylesheet and ctrl-F reach inside it without JavaScript. */
.siblings{margin-top:12px;border:1px solid var(--grid);border-radius:var(--radius);
  background:var(--page)}
.siblings>summary{display:flex;align-items:center;gap:8px;cursor:pointer;padding:9px 12px;
  font-size:12px;font-weight:600;color:var(--accent);list-style:none}
.siblings>summary::-webkit-details-marker{display:none}
/* The disclosure triangle is drawn, not inherited: the platform default sits on a
   different baseline in every browser and the row is only 30px tall. */
.siblings>summary::before{content:"";width:0;height:0;border:4px solid transparent;
  border-left-color:currentColor;transition:transform .12s ease;flex:none}
.siblings[open]>summary::before{transform:rotate(90deg) translateX(-1px)}
.siblings>summary:hover{background:color-mix(in srgb,var(--accent) 6%,transparent)}
.siblings>summary .badge{display:inline-block;min-width:18px;padding:1px 5px;
  border-radius:99px;background:var(--accent-soft);color:var(--accent);
  font-size:11px;text-align:center;font-variant-numeric:tabular-nums}
.siblings>*:not(summary){margin:0 12px 12px}
.siblings .office{font-size:12px;color:var(--ink-mute);margin-top:0;padding-bottom:8px}
/* The humans get the emphasis. They are the reason this section exists — everything
   else here is a company name, and a list of company names is what the reader already
   had. `process_name` is where a single-member LLC leaks its member. */
.siblings .people{border-left:2px solid var(--good);padding:8px 0 8px 11px;
  background:color-mix(in srgb,var(--good) 6%,transparent);border-radius:0 3px 3px 0}
.siblings .people .lab{font-size:11px;text-transform:uppercase;letter-spacing:.06em;
  color:var(--ink-mute);font-weight:600;margin-bottom:5px}
.siblings .person{font-size:13px;display:flex;gap:7px;align-items:baseline;flex-wrap:wrap}
.siblings .person .muted{font-size:12px}
.siblings tr.self{background:color-mix(in srgb,var(--accent) 7%,transparent)}
.siblings table{margin-top:2px}
/* Same suite vs same building. The weak tier is deliberately quieter — it is a
   neighbouring-tenant list, and giving it equal weight is how a shared office floor
   reads as somebody's portfolio. */
.siblings .tier-lab{font-size:11px;text-transform:uppercase;letter-spacing:.06em;
  color:var(--ink-2);font-weight:600;margin-top:12px;display:flex;align-items:center;gap:7px}
.siblings .tier-lab.weak{color:var(--ink-mute)}
.siblings table.weak td{color:var(--ink-mute)}
.siblings table.weak td:first-child{color:var(--ink-2)}
/* A collapsed <details> is hidden by the browser's shadow tree, which no print rule can
   reach — console.js opens them on `beforeprint` and weasyprint gets them open from the
   server. All this can usefully do is keep the group on one page. */
@media print{.siblings{break-inside:avoid}.siblings>summary{color:var(--ink-2)}}

/* "Sold since" reuses the disclosure, in a warning tone: it is adjacent to the portfolio
   but is emphatically not part of it, and the colour has to say so before the label is
   read. */
.siblings.former>summary{color:color-mix(in srgb,var(--warn) 70%,var(--ink))}
.siblings.former>summary .badge{background:color-mix(in srgb,var(--warn) 20%,transparent);
  color:color-mix(in srgb,var(--warn) 72%,var(--ink))}
.siblings.former>summary:hover{background:color-mix(in srgb,var(--warn) 8%,transparent)}

/* The apartment on a condo unit lot. Set apart from the street address rather than
   concatenated into it: they come from different sources (PLUTO vs the assessment roll's
   `aptno`) and 7% of unit lots have no apartment on file at all. */
.apt{display:inline-block;margin-left:7px;padding:1px 6px;border-radius:3px;
  background:var(--accent-soft);color:var(--accent);font:600 11px/1.5 var(--font);
  font-variant-numeric:tabular-nums}

/* Owner-entity column: two numbers per row, and the row is a filter control.
   `here` is emphasised because it is the one that reconciles with the hero; `citywide`
   stays quiet but present, since it is a genuinely useful figure and hiding it was never
   the fix — the fix was labelling both. */
.flow-col h3 .colkey{float:right;text-transform:none;letter-spacing:0;font-weight:400;
  color:var(--ink-mute)}
.node.ent{width:100%;text-align:left;font:inherit;font-size:12px;cursor:pointer}
.node.ent:hover{border-color:var(--accent)}
.node.ent.sel{border-color:var(--accent);background:color-mix(in srgb,var(--accent) 8%,transparent)}
.node.ent .pair{white-space:nowrap;font-variant-numeric:tabular-nums}
.node.ent .pair b{color:var(--ink);font-weight:650}
.node.ent .pair i{font-style:normal;color:var(--axis);margin:0 4px}
.node.ent.all{margin-bottom:8px}
.node.bldg[hidden]{display:none}

/* The arrow that opens an entity from a control that already has a click. On the owner
   page the entity row IS REQ-1's filter, so the link cannot wrap the row — it sits inside
   and stops propagation. */
.ent-go,.cluster-go{color:var(--accent);text-decoration:none;font-size:11px;margin-left:6px;
  opacity:.55}
.ent-go:hover,.cluster-go:hover{opacity:1}

/* The drawer table below ~820px viewport — BUG-14.
   Measured at 420x760 before this: the drawer is 353px wide, the table laid out to 463px
   and overflowed to 507, and single rows were 253-364px tall because the description
   column was squeezed to 100px and wrapped ~20 times. One violation filled a third of the
   screen and the panel scrolled in two directions at once.

   Six columns do not fit in 352px, so below the breakpoint the row stops being a row: it
   becomes stacked label/value pairs, driven by `data-label`. Generic on purpose — the
   drawer serves 25 registry-defined sets and a rule that named particular columns would be
   wrong for most of them. Rejected: hiding columns (which column is expendable differs per
   set) and horizontal scrolling (a panel that already scrolls vertically should not also
   scroll sideways).

   It is also *shorter*: ~150px stacked against ~300px squeezed, because the text finally
   has the full width to wrap into. */
/* (2026-09-26: the drawer is now `min(1080px, 88vw)` and wide sets scroll sideways
   above the breakpoint rather than being squeezed — see `.drawer-scroll td`. The
   arithmetic below is kept as the record of why 860 was chosen; 860 still stacks every
   set on a phone and a narrow window, which is the job.)
   860px, not 820: the drawer is `min(760px, 84vw)` and the table lays out to ~715px,
   so the table only fits once 84vw >= 715 — i.e. a viewport of ~851px. A lower
   breakpoint leaves a band between 820 and 851 that still overflows sideways. */
/* Every selector carries `table.compact`, and that is load-bearing rather than tidy.
   `table.compact td{height:30px}` is specificity (0,1,2) and `.drawer-scroll td` is
   (0,1,1), so the plain selector loses — and under `display:grid` a fixed height stops
   being the minimum that table layout treats it as and becomes a hard box. The
   description then painted straight over the next two rows. It measured clean
   (`scrollWidth == clientWidth`, row heights 201px) because the overflow is the *text*
   escaping the cell, not the scroller growing; only looking at it caught this. */
@media (max-width:860px){
  .drawer-scroll table.compact,.drawer-scroll table.compact tbody,
  .drawer-scroll table.compact tr,.drawer-scroll table.compact td{display:block}
  .drawer-scroll table.compact thead{display:none}
  .drawer-scroll table.compact tr{border-bottom:1px solid var(--axis);padding:10px 0}
  .drawer-scroll table.compact tr:last-child{border-bottom:0}
  .drawer-scroll table.compact td{border:0;padding:2px 0;max-width:none;
    height:auto;min-height:0;
    display:grid;grid-template-columns:88px 1fr;gap:10px;align-items:baseline}
  .drawer-scroll table.compact td::before{content:attr(data-label);color:var(--ink-mute);
    font-size:10px;text-transform:uppercase;letter-spacing:.06em;line-height:1.7}
  /* A money column right-aligned against its own label reads as a stray number. */
  .drawer-scroll table.compact td.n{text-align:left}
  /* The Changes card stacks for the same reason the drawer does: a 96px date gutter
     beside wrapped 311 resolution text leaves the text ~200px on a phone. Below the
     breakpoint the date goes above the event instead of beside it. */
  .ch-row{grid-template-columns:1fr;gap:3px}
  .ch-when{font-size:11px}
  .ch-src{display:inline;margin-left:6px}
  .ch-col{min-width:0;display:inline;margin-right:5px}
  /* The drawer's stacked rows already indent; a second border-left inside one is noise. */
  .ch-hist-body{padding-left:8px}
}

/* ── The meter chip (Phase 2) ────────────────────────────────────────────────
   Server-rendered, no JS. Amber at 80% of the monthly allowance, red at 100% —
   a counter nobody notices until it stops is a support ticket, not a limit.

   ⚠️ `.meterchip`, NOT `.chip`. It was `.chip` for about two minutes in production
   on 2026-08-27 and that was a live visual regression: `.chip` is defined at the top
   of this file and used 65+ times across the templates for PROVENANCE chips
   (`c-inferred`, `c-unavail`, `c-verified`, `c-ambiguous`, `chip code`). Being later
   in the cascade at equal specificity, this block silently restyled every one of them
   — 11px inline-flex became 12px pill-shaped with a border and a background.
   A new component gets a new name; it does not borrow one that is already load-bearing. */
.meterchip{font:600 12px/1 var(--font);padding:7px 11px;border-radius:999px;
  white-space:nowrap;border:1px solid var(--axis);background:var(--page);color:var(--ink-2)}
.meterchip-warn{border-color:var(--warn);color:#7a5600;background:#fff8e8}
.meterchip-crit{border-color:var(--crit);color:#8a1f1f;background:#fdeeee}
.meterchip:hover{color:var(--accent)}
/* The over-the-limit banner sits above the page it is explaining, not below it. */
.limitbox{margin-top:0;margin-bottom:var(--gap)}
.limitbox .btn{margin-left:10px}

/* ── the operator user-admin panel (Session AC, 2026-08-28) ────────────────────
   Every class here carries the `admin-` prefix, and that is the `.chip` lesson written
   down: a generic name added to a shared stylesheet collides with a vocabulary somebody
   else is already using, and the collision shows up as a page that looks subtly wrong on
   a screen nobody was testing. These styles are additive — the panel reuses `.card`,
   `.compact`, `.pill`, `.field`, `.btn`, `.note` and the rest unchanged. */
.admin-nav{display:flex;flex-wrap:wrap;align-items:center;gap:14px;
  margin-bottom:var(--gap);padding:12px var(--pad);background:var(--surface);
  border:1px solid var(--grid);border-radius:var(--radius);box-shadow:var(--shadow)}
.admin-navlink{font-size:13px;font-weight:550;color:var(--ink-2);
  padding:5px 11px;border-radius:999px;border:1px solid transparent}
.admin-navlink:hover{color:var(--accent);border-color:var(--accent-soft)}
.admin-here{background:var(--accent-soft);border-color:var(--accent-soft);color:var(--ink)}
.admin-navnote{margin-left:auto;font-size:12px;color:var(--ink-mute)}
.admin-navnote code{font-size:11px}

.admin-filter{display:flex;flex-wrap:wrap;gap:10px;align-items:center;
  margin:var(--gap) 0}
.admin-filter .field{max-width:320px}
.admin-primary{background:var(--accent);border-color:var(--accent);color:#fff}
.admin-primary:hover{color:#fff;filter:brightness(1.08)}
.admin-check{display:inline-flex;gap:6px;align-items:center;font-size:13px;
  color:var(--ink-2);cursor:pointer}

/* A suspended account has to be legible as suspended at a glance, on a list where every
   other row looks the same. Colour AND a left rule — colour alone is not a signal. */
.admin-suspended{background:#fdf3f3;color:var(--ink-2)}
.admin-suspended td:first-child{box-shadow:inset 3px 0 0 var(--crit)}

.admin-action{padding:16px 0;border-top:1px solid var(--grid)}
.admin-action h3{font-size:14px;font-weight:600;margin-bottom:4px}
.admin-form{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-top:10px}
.admin-stack{display:block}
.admin-stack .field{width:100%;max-width:460px}
.admin-narrow{max-width:120px}
.admin-danger{border-color:var(--crit);color:#8a1f1f}
.admin-danger:hover{background:#fdeeee;color:#8a1f1f}
/* A company ACCOUNT (IDEA-21), as opposed to the free text a person typed as their employer. */
.co-account{font-weight:600;color:var(--accent)}
/* /ops/companies: Detach… opens a confirm form instead of a free-standing tier dropdown. */
.admin-detach>summary{list-style:none;display:inline-block;cursor:pointer}
.admin-detach>summary::-webkit-details-marker{display:none}
.admin-detach[open]>summary{margin-bottom:.4rem}
.admin-form button[disabled],.admin-form .field[disabled]{opacity:.45;cursor:not-allowed}

/* The once-only link. Loud on purpose: the page it appears on is the only place it will
   ever exist, and a reload does not bring it back. */
.admin-once{border:2px solid var(--warn)}
.admin-linkbox{margin:10px 0;padding:12px 14px;background:var(--page);
  border:1px solid var(--axis);border-radius:var(--radius);
  word-break:break-all;font-variant-numeric:tabular-nums}
.admin-linkbox code{font-size:13px}

/* ── the feedback board, /ops/feedback (2026-09-29) ──────────────────────────────
   `admin-fb-` prefix for the reason above. One colour per roadmap status, used by the
   strip, the dots, the step bar and the column headings alike, so a colour means the
   same thing wherever it appears — and every coloured mark also carries its word. */
.admin-fb-s-new{--fb:var(--axis)}
.admin-fb-s-discussing{--fb:var(--warn)}
.admin-fb-s-planned{--fb:#7c5cd6}
.admin-fb-s-in_progress{--fb:var(--accent)}
.admin-fb-s-shipped{--fb:var(--good)}
.admin-fb-s-declined{--fb:var(--crit)}
.admin-fb-s-parked{--fb:var(--ink-mute)}
.admin-fb-strip{display:flex;gap:2px;height:26px;border-radius:6px;overflow:hidden;
  margin:12px 0 6px}
.admin-fb-thin{height:10px;margin:0;min-width:120px}
.admin-fb-seg{background:var(--fb);color:var(--ink);font:600 12px/26px var(--font);
  text-align:center;min-width:18px}
.admin-fb-s-planned.admin-fb-seg,.admin-fb-s-in_progress.admin-fb-seg,.admin-fb-s-shipped.admin-fb-seg,
.admin-fb-s-declined.admin-fb-seg,.admin-fb-s-parked.admin-fb-seg{color:#fff}
.admin-fb-legend{display:flex;flex-wrap:wrap;gap:14px;color:var(--ink-2)}
.admin-fb-dot{display:inline-block;width:10px;height:10px;border-radius:50%;
  background:var(--fb);margin-right:6px;vertical-align:-1px}
.admin-fb-mini{width:60%}
.admin-fb-board{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:12px;
  margin-bottom:var(--gap)}
@media (max-width:1100px){.admin-fb-board{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:600px){.admin-fb-board{grid-template-columns:1fr}}
.admin-fb-col{background:var(--page);border:1px solid var(--grid);border-radius:var(--radius);
  padding:10px;min-height:120px}
.admin-fb-col h3{font-size:13px;font-weight:650;margin:2px 2px 10px}
.admin-fb-card{display:flex;flex-direction:column;gap:5px;padding:10px 12px;margin-bottom:8px;
  background:var(--surface);border:1px solid var(--grid);border-radius:8px;color:var(--ink);
  box-shadow:var(--shadow)}
.admin-fb-card:hover{border-color:var(--accent)}
.admin-fb-ref{font-size:11px;color:var(--ink-mute);font-variant-numeric:tabular-nums}
.admin-fb-title{font-size:13.5px;font-weight:600;line-height:1.3}
.admin-fb-meta{font-size:11.5px;color:var(--ink-2);display:flex;flex-wrap:wrap;gap:6px;
  align-items:center}
.admin-fb-p1{color:var(--crit)}
.admin-fb-p2{color:var(--serious)}
.admin-fb-p3{color:var(--ink-2)}
.admin-fb-two{display:grid;grid-template-columns:3fr 2fr;gap:var(--gap)}
@media (max-width:860px){.admin-fb-two{grid-template-columns:1fr}}
.admin-fb-row{display:flex;flex-wrap:wrap;gap:10px;margin:8px 0}
.admin-fb-row label{display:flex;flex-direction:column;gap:3px;font-size:12px;
  color:var(--ink-2)}
.admin-fb-steps{display:flex;list-style:none;padding:0;margin:12px 0;gap:4px;
  counter-reset:fb}
.admin-fb-steps li{flex:1;text-align:center;padding:7px 4px;font-size:12px;
  background:var(--page);border:1px solid var(--grid);color:var(--ink-mute)}
.admin-fb-steps li:first-child{border-radius:6px 0 0 6px}
.admin-fb-steps li:last-child{border-radius:0 6px 6px 0}
.admin-fb-past{color:var(--ink-2)!important;background:var(--accent-soft)!important}

/* ── the ticket system, /ops/tickets (IDEA-25, 2026-10-03) ──────────────────────
   `admin-tk-` prefix; it reuses the `admin-fb-` board, card and strip above, and adds
   the three statuses tickets have that ideas did not. Every mark carries its word. */
.admin-fb-s-blocked{--fb:var(--serious)}
.admin-fb-s-merged{--fb:#2a9d8f}
.admin-fb-s-duplicate{--fb:var(--ink-mute)}
.admin-fb-s-blocked.admin-fb-seg,.admin-fb-s-merged.admin-fb-seg,
.admin-fb-s-duplicate.admin-fb-seg{color:#fff}
.admin-tk-board{grid-template-columns:repeat(7,minmax(0,1fr))}
@media (max-width:1400px){.admin-tk-board{grid-template-columns:repeat(4,minmax(0,1fr))}}
@media (max-width:860px){.admin-tk-board{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:600px){.admin-tk-board{grid-template-columns:1fr}}
.admin-tk-tabs{display:flex;flex-wrap:wrap;gap:4px;margin:12px 0 4px;
  border-bottom:1px solid var(--grid)}
.admin-tk-tabs a{padding:7px 12px;border:1px solid transparent;border-bottom:none;
  border-radius:6px 6px 0 0;color:var(--ink-2);font-size:13px}
.admin-tk-tabs a.admin-here{background:var(--surface);border-color:var(--grid);
  color:var(--ink);font-weight:650;margin-bottom:-1px}
.admin-tk-filter{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-top:10px}
.admin-tk-filter .field{width:auto;min-width:0;flex:0 1 220px;margin:0}
.admin-tk-syncs{margin:4px 0 0}
.admin-tk-status{white-space:nowrap}
.admin-tk-p{font-weight:650}
.admin-tk-card{min-width:0;overflow-wrap:anywhere}
.admin-tk-p0{color:#fff;background:var(--crit);padding:0 5px;border-radius:4px}
.admin-tk-kind{display:inline-block;font-size:10px;font-weight:650;text-transform:uppercase;
  letter-spacing:.04em;padding:1px 5px;border-radius:4px;background:var(--page);
  border:1px solid var(--grid);color:var(--ink-2)}
.admin-tk-k-bug{color:var(--crit);border-color:var(--crit)}
.admin-tk-k-project{color:var(--accent);border-color:var(--accent)}
.admin-tk-warn{color:var(--crit)}
.admin-tk-branch{font-size:11px;word-break:break-all}
.admin-tk-progress{display:inline-block;width:100%;min-width:80px;height:8px;
  background:var(--page);border:1px solid var(--grid);border-radius:4px;overflow:hidden;
  vertical-align:middle}
.admin-tk-progress span{display:block;height:100%;background:var(--good)}
.admin-tk-progcell{width:25%}
.admin-tk-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));
  gap:8px;margin-top:8px}
.admin-tk-children{display:flex;flex-wrap:wrap;gap:8px;margin-top:4px}
.admin-tk-queue{list-style:none;padding:0;margin:8px 0}
.admin-tk-queue li{display:flex;align-items:center;gap:10px;padding:8px 10px;
  margin-bottom:6px;background:var(--surface);border:1px solid var(--grid);
  border-radius:8px;box-shadow:var(--shadow)}
.admin-tk-queue li.admin-tk-dragging{opacity:.45}
.admin-tk-queue li.admin-tk-over{border-color:var(--accent)}
.admin-tk-grip{cursor:grab;color:var(--ink-mute);user-select:none}
.admin-tk-pos{font:650 13px/1 var(--font);color:var(--ink-mute);min-width:1.6em;
  text-align:right}
.admin-tk-qmain{display:flex;flex-direction:column;gap:3px;flex:1;color:var(--ink)}
.admin-tk-qbtns,.admin-tk-qrow{display:flex;gap:4px;flex-wrap:wrap}
.admin-tk-qbtns form,.admin-tk-qrow form,.admin-tk-dep form{display:inline}
.admin-tk-orderform{margin:0}
.admin-tk-flight td{vertical-align:top}
.admin-tk-desc{margin:10px 0}
.admin-tk-here{font-weight:650;color:var(--ink)!important;
  box-shadow:inset 0 -3px 0 var(--fb)}
.admin-tk-checks{list-style:none;padding:0;margin:0}
.admin-tk-checks li{display:flex;gap:8px;align-items:baseline;padding:3px 0}
.admin-tk-checks form{display:inline}
.admin-tk-checked span{color:var(--ink-mute);text-decoration:line-through}
.admin-tk-box{background:none;border:none;font-size:17px;cursor:pointer;padding:0;
  color:var(--ink)}
.admin-tk-dep{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin:4px 0}
.admin-tk-activity li.admin-tk-n-change .prewrap{color:var(--ink-2)}
.admin-tk-activity li.admin-tk-n-commit .prewrap{font-family:var(--mono,monospace);
  font-size:12.5px}
.admin-tk-activity li.admin-tk-n-session{border-left:3px solid var(--accent);
  padding-left:8px}
.admin-tk-brief h1,.admin-tk-brief h2,.admin-tk-brief h3{font-size:15px;margin:12px 0 4px}

/* ── broker outreach, /ops/outreach (IDEA-7, 2026-09-30) ─────────────────────────
   `admin-or-` prefix. The bars carry their numbers in `title` and the card says what they
   are; a bar is never the only place a figure appears (the tiles hold the totals). */
.admin-or-bars{display:flex;align-items:flex-end;gap:3px;height:120px;margin:10px 0 4px;
  border-bottom:1px solid var(--axis)}
.admin-or-bar{flex:1;background:var(--accent);border-radius:3px 3px 0 0;min-height:2px}
.admin-or-zero{background:var(--grid)}
.admin-or-axis{display:flex;justify-content:space-between}
.admin-or-meter{width:45%}
.admin-or-fill{display:block;height:8px;border-radius:4px;background:var(--accent);min-width:2px}
.admin-or-inline{display:flex;gap:6px}
.admin-or-refused{color:var(--crit)}
.admin-or-ask{border:2px solid var(--accent)}
.admin-or-check{flex-direction:column}
.admin-or-check span:last-child{display:flex;gap:6px;align-items:center;font-size:13px;
  color:var(--ink);padding-top:6px}
.admin-or-mail{white-space:pre-wrap;font:13px/1.5 var(--font);background:var(--page);
  border:1px solid var(--grid);border-radius:var(--radius);padding:12px 14px;margin:8px 0;
  max-height:420px;overflow:auto}
.admin-or-bio{white-space:pre-wrap;max-height:260px;overflow:auto}

/* ── the inbox assistant's open loops, /ops/inbox (Phase 1b, 2026-10-01) ────────────
   `admin-ib-` prefix. Built on the theme tokens only, so all three skins recolour it.
   A kind's pill carries its label as text; the colour only repeats it. */
.admin-ib-search{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-top:10px}
.admin-ib-search .field{flex:1;min-width:220px}
.admin-ib-act{display:flex;gap:6px;margin:0}
.admin-ib-add{margin-top:12px}.admin-ib-add summary{cursor:pointer}
.admin-ib-wrap{max-width:100%;overflow-x:auto}
.admin-ib-late{color:var(--crit);font-weight:600}
.admin-ib-soon{color:var(--serious);font-weight:600}
.admin-ib-k-legal_clock{background:color-mix(in srgb,var(--crit) 15%,transparent);color:var(--crit)}
.admin-ib-k-payment_due{background:color-mix(in srgb,var(--serious) 15%,transparent);color:var(--serious)}
.admin-ib-k-reply_owed{background:color-mix(in srgb,var(--accent) 15%,transparent);color:var(--accent)}
.admin-ib-k-follow_up{background:color-mix(in srgb,var(--good) 15%,transparent);color:var(--good)}
.admin-ib-k-waiting_on_them{background:color-mix(in srgb,var(--ink-2) 12%,transparent);color:var(--ink-2)}
/* On a phone each loop becomes a short stacked card: the summary takes its own line, and
   the due date, Gmail link and buttons stay on screen instead of in a sideways scroll. */
@media (max-width:700px){
  main .card .admin-ib-wrap table,.admin-ib-wrap tbody{display:block;overflow:visible}
  .admin-ib-wrap tr{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;
    padding:10px 0;border-bottom:1px solid var(--grid)}
  .admin-ib-wrap td{display:block;border:0;padding:0}
  .admin-ib-wrap td.admin-ib-what{flex-basis:100%;order:5}
  .admin-ib-wrap td.admin-ib-end{order:6}
}
.admin-fb-now{background:var(--fb)!important;color:#fff!important;font-weight:650}
.admin-fb-now.admin-fb-s-new,.admin-fb-now.admin-fb-s-discussing{color:var(--ink)!important}
.admin-fb-quote{border-top:1px solid var(--grid);padding:12px 0}
.admin-fb-quote blockquote,.admin-fb-body{margin:8px 0;padding:8px 12px;
  border-left:3px solid var(--axis);background:var(--page);color:var(--ink-2)}
.admin-fb-files{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px;align-items:center}
.admin-fb-files img{max-width:240px;max-height:180px;border:1px solid var(--grid);
  border-radius:6px}
.admin-fb-log{list-style:none;padding:0;margin:0 0 12px}
.admin-fb-log li{padding:8px 0;border-top:1px solid var(--grid)}

/* ── Session AD: the legal surfaces ─────────────────────────────────────────── */

/* The site footer. On every page for every audience — for a signed-in user it is a
   reference, for an anonymous visitor and for a scraper it is the browsewrap notice
   (ToS § 2), and a notice nobody can see is a notice nobody has. Quiet, but not so
   quiet that it reads as decoration. */
.sitefoot{display:flex;flex-wrap:wrap;gap:10px;align-items:baseline;
  padding:22px var(--pad) 34px;margin-top:calc(var(--gap)*2);
  border-top:1px solid var(--grid);color:var(--ink-2);font-size:13px}
.sitefoot a{text-decoration:underline;text-underline-offset:2px}
.sitefoot .muted{margin-left:auto}

/* The browsewrap sentence, next to the search box on the pages a stranger lands on.
   `--warn` on the left rule rather than a box: it has to be READ without being an
   alarm, and it sits directly under a search field somebody is about to type into. */
.browsewrap{margin-top:10px;padding-left:12px;border-left:3px solid var(--axis);
  color:var(--ink-2);font-size:13px}
.browsewrap a{text-decoration:underline;text-underline-offset:2px}

/* A rendered legal document. Measure is the whole design here — these are two pages of
   dense prose and a full-width line of 14px text is unreadable at any size. */
.legaldoc{max-width:74ch}
.legaldoc h1{font-size:24px;font-weight:650;margin:0 0 6px}
.legaldoc h2{font-size:17px;font-weight:650;margin:26px 0 8px;
  padding-top:18px;border-top:1px solid var(--grid)}
.legaldoc h2:first-of-type{border-top:0;padding-top:0}
.legaldoc h3{font-size:15px;font-weight:600;margin:18px 0 6px}
.legaldoc p{margin:0 0 12px}
.legaldoc ul,.legaldoc ol{margin:0 0 14px 22px}
.legaldoc li{margin:0 0 6px}
.legaldoc a{color:var(--accent);text-decoration:underline;text-underline-offset:2px}
.legaldoc hr{border:0;border-top:1px solid var(--grid);margin:22px 0}
.legaldoc code{background:var(--page);padding:1px 5px;border-radius:4px;font-size:13px}

/* /sources — data sources & licences (REQ-170 WP-3). The first card is a `.legaldoc`
   (prose at a readable measure); each group is a card of facts per source. A two-column
   definition list on a wide screen, one column on a phone. */
.srcs-page h2{margin-top:22px}
.srcs-disclaimer p{margin:0 0 6px}
.srcs-disclaimer p:last-child{margin:0}
.srcs-licence h3{margin-top:14px}
.srcs-notice{white-space:pre-wrap;overflow-wrap:anywhere;max-width:72ch;margin:8px 0 0;padding:10px 12px;border:1px solid var(--grid);border-radius:6px;font-family:inherit}
.srcs-group{max-width:74ch}
.srcs-group h2{font-size:17px;font-weight:650;margin:0 0 4px}
.srcs-item{padding:14px 0;border-top:1px solid var(--grid);scroll-margin-top:96px}
.srcs-item:first-of-type{border-top:0}
.srcs-item h3{font-size:15px;font-weight:600;margin:0 0 6px}
.srcs-anchor{color:var(--ink);text-decoration:none}
.srcs-anchor:hover{text-decoration:underline}
.srcs-item:target{background:var(--page)}
.srcs-facts{display:grid;grid-template-columns:7.5em 1fr;gap:3px 12px;margin:0;
  font-size:13px}
.srcs-facts dt{color:var(--ink-mute)}
.srcs-facts dd{margin:0;overflow-wrap:anywhere}
.srcs-facts a{color:var(--accent)}

/* Commercial tenants card (REQ-170 WP-11a). Rows are dated records; a source badge links
   to its /sources entry. The table scrolls sideways inside the card on a phone. */
.ct-summary{margin:2px 0 10px;font-size:14px}
.ct-area{margin:-6px 0 10px}
.ct-group{font-size:14px;font-weight:650;margin:14px 0 6px}
.ct-table td{vertical-align:top}
.ct-src{display:inline-block;font:600 10.5px/1 var(--font);letter-spacing:.02em;
  padding:3px 5px;margin:0 4px 2px 0;border:1px solid var(--grid);border-radius:4px;
  color:var(--ink);text-decoration:none}
.ct-src:hover{border-color:var(--accent);color:var(--accent)}
.ct-dates{font-size:12.5px;min-width:11em}
.ct-unnamed{margin-top:6px}
.ct-disclaimer{margin-top:12px}
.ct-leases{margin:12px 0 0}
.ct-disclaimer a{color:var(--accent);text-decoration:underline;text-underline-offset:2px}
.ct-card .drill{margin-top:6px}
/* REQ-170 WP-11b: applications, the gated map list, Storefront history, Lease expirations */
.ct-apps,.ct-map{margin-top:10px}
.ct-history .ct-table td.n,.ct-lx .ct-table td.n{white-space:nowrap}
.ct-app-list{margin:4px 0 6px;padding-left:18px;font-size:13.5px}
.ct-app-list li{margin:3px 0}
.ct-app-list .ct-src{margin-left:6px}
.ct-spark-line{display:flex;align-items:center;gap:8px;margin:0 0 8px}
.ct-spark{color:var(--accent);flex:none}
.ct-lx-periods{max-width:22em}
.ct-lx-sf{max-width:36em}
.ct-lx-sf td:first-child{width:9em}
/* The business page (2026-10-01, `business.html`). The tenants card's classes do the
   tables and badges; these are the header's few extras. Names wrap: a registered name
   can be 80 characters, and a phone is 375px. */
.biz-name{overflow-wrap:anywhere}
.biz-summary{margin:4px 0 8px;font-size:15px;color:var(--ink-2)}
.biz-names,.biz-sources{margin:8px 0 0;font-size:13px;line-height:1.9}
.biz-alias{display:inline-block;margin:0 4px 0 6px;padding:1px 7px;border:1px solid var(--grid);
  border-radius:99px;font-size:12px;overflow-wrap:anywhere}
.biz-sources .ct-src{margin-left:6px}
.biz-src-n{margin-left:3px}
.biz-table tr.mine td{font-weight:600}
.ct-name a,.biz-table td a{color:var(--accent)}
@media (max-width:560px){
  .srcs-facts{grid-template-columns:1fr;gap:0}
  .srcs-facts dd{margin:0 0 6px}
}

/* The acceptance checkbox. Big enough to hit, and the label is the click target — a
   checkbox somebody misses is a checkbox that reads as a broken form. */
.legaldoc + .card,.card label input[type=checkbox]{accent-color:var(--accent)}
label input[type=checkbox]{width:16px;height:16px;margin-right:8px;
  vertical-align:-2px;accent-color:var(--accent)}

/* ── Form errors that a person actually notices ──────────────────────────────
   Measured 2026-08-28: an invalid intended-use answer rendered as `.note critbox` at
   the top of the card — 1,450 characters above the field it was about, with a 2px red
   LEFT BORDER and ordinary black text, on a page already carrying three other `.note`
   blocks whose wording it duplicated. It was not silent; it was indistinguishable from
   help text, which is worse, because the reader concludes the form is broken.

   So an error now differs from a note in every channel at once — colour, weight,
   background, icon and POSITION — and never relies on colour alone. */

.errorbox{border:1px solid var(--crit);border-left-width:4px;border-radius:var(--radius);
  background:#fdf3f3;color:#8a1f1f;padding:12px 15px;margin:14px 0;font-weight:500}
.errorbox b,.errorbox strong{color:#7a1a1a;font-weight:650}
.errorbox ul{margin:6px 0 0 20px}
.errorbox li{margin:3px 0}

/* The same message again, at the field. The top box says what went wrong; this says
   WHERE, which is the half that was missing. */
.fielderror{display:block;margin:6px 0 2px;color:#8a1f1f;font-weight:500;font-size:13px}
.fielderror::before{content:"▲ ";font-size:11px}

/* And the control itself carries the state, so the eye lands on it without reading. */
.field.is-error,.field.is-error:focus{border-color:var(--crit);background:#fffafa}
.field.is-error:focus{outline:2px solid var(--crit);outline-offset:-1px}
.checkrow.is-error{background:#fdf3f3;border-radius:6px;padding:8px 10px;margin-left:-10px}

/* REQ-39 — the /corrections honeypot. Off-screen, not display:none, which some bots skip. */
.hp{position:absolute;left:-10000px;width:1px;height:1px;overflow:hidden}
/* REQ-39 — the corrections queue: requester text keeps its line breaks; overdue is red. */
.prewrap{white-space:pre-wrap;word-break:break-word}
b.crit{color:var(--crit)}
/* The anonymous home card's Sign in / Create an account pair. A flex row, because two
   inline `.btn` anchors in a `.formrow` paragraph overlapped the line under them — the
   paragraph's line box is shorter than a padded button (measured 2026-09-29). */
.btnrow{display:flex;flex-wrap:wrap;gap:8px;margin:14px 0 10px}

/* ── The design-skin switcher (2026-09-29, `api/skins.py`) ────────────────────────
   `skin-` prefix per the class-token guard. Deliberately small: a quiet chip at the end
   of the header, because its job is to collect reactions, not to be a feature. The menu
   is a native <details>, positioned under its summary. */
.skin-pick{position:relative;flex:none}
header .skin-pick{margin-left:4px}
.skin-pick>summary{list-style:none;cursor:pointer;font:500 12px/1 var(--font);color:var(--ink-mute);
  padding:7px 10px;border:1px solid var(--grid);border-radius:999px;white-space:nowrap;
  background:transparent}
.skin-pick>summary::-webkit-details-marker{display:none}
.skin-pick>summary::after{content:"▾";margin-left:5px;font-size:10px}
.skin-pick>summary:hover,.skin-pick[open]>summary{color:var(--ink);border-color:var(--axis)}
.skin-menu{position:absolute;right:0;top:calc(100% + 8px);width:290px;z-index:40;
  background:var(--surface);color:var(--ink);border:1px solid var(--grid);border-radius:12px;
  box-shadow:var(--menu-shadow);padding:10px}
.skin-head{font:600 11px/1 var(--font);letter-spacing:.06em;text-transform:uppercase;
  color:var(--ink-mute);padding:6px 8px 10px}
.skin-opt{display:flex;flex-direction:column;align-items:flex-start;gap:3px;width:100%;
  text-align:left;background:none;border:1px solid transparent;border-radius:8px;
  padding:10px;font:inherit;color:inherit;cursor:pointer}
.skin-opt b{font-weight:600;font-size:14px}
.skin-opt span{font-size:12.5px;color:var(--ink-mute)}
.skin-opt:hover{background:var(--sunk)}
.skin-on{border-color:var(--ink);background:var(--sunk)}
.skin-on b::after{content:" ✓";font-weight:600}
.skin-fb{display:block;margin-top:6px;padding:10px 8px 4px;border-top:1px solid var(--grid);
  font-size:13px;color:var(--accent)}
.skin-fb:hover{text-decoration:underline;text-underline-offset:2px}
@media print{.skin-pick{display:none}}

/* ── The Prospecting menu and hub (REQ-178, `api/prospecting.py`) ───────────────────
   One header item for every list tool. On a desktop a native <details> dropdown; on a
   phone the plain link to the hub instead (the media block below swaps them). Coming-soon
   rows say so in words, never by colour alone. */
.pros-link{display:none}
.pros-pick{position:relative}
.pros-pick>summary{list-style:none;cursor:pointer}
.pros-pick>summary::-webkit-details-marker{display:none}
.pros-pick>summary::after{content:"▾";margin-left:5px;font-size:10px}
.pros-pick>summary:hover,.pros-pick[open]>summary,.pros-on{color:var(--ink)}
.pros-menu{position:absolute;left:-10px;top:calc(100% + 10px);width:350px;z-index:40;
  background:var(--surface);color:var(--ink);border:1px solid var(--grid);border-radius:12px;
  box-shadow:var(--menu-shadow);padding:8px;white-space:normal}
.pros-opt{display:flex;flex-direction:column;gap:3px;padding:8px 10px;border-radius:8px;color:inherit}
.pros-opt b{font-weight:600;font-size:14px}
.pros-opt span{font-size:12.5px;color:var(--ink-mute)}
a.pros-opt:hover{background:var(--sunk)}
.pros-soon b{color:var(--ink-mute)}
.pros-badge{align-self:flex-start;display:inline-block;vertical-align:2px;margin-left:6px;
  font:600 9.5px/1 var(--font);font-style:normal;letter-spacing:.07em;text-transform:uppercase;
  color:var(--ink-2);background:var(--sunk);border:1px solid var(--grid);border-radius:999px;padding:3px 7px}
.pros-tile .pros-badge{margin-left:0;font-size:10.5px;padding:5px 9px}
.pros-all{display:block;margin-top:6px;padding:10px 10px 4px;border-top:1px solid var(--grid);
  font-size:13px;color:var(--accent)}
.pros-all:hover{text-decoration:underline;text-underline-offset:2px}
.pros-title{font:500 30px/1.2 var(--display);margin:6px 0 8px}
.pros-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:var(--gap)}
.pros-tile{display:flex;flex-direction:column;gap:8px;padding:20px 22px;border:1px solid var(--grid);
  border-radius:var(--radius);background:var(--surface);color:inherit}
.pros-tile h3{font:500 21px/1.25 var(--display);margin:0}
.pros-tile p{color:var(--ink-2);margin:0}
a.pros-tile:hover{border-color:var(--axis)}
.pros-go{margin-top:auto;color:var(--accent);font-size:14px}
.pros-tile.pros-soon{border-style:dashed;background:transparent}
.pros-tile.pros-soon h3{color:var(--ink-2)}
@media print{.pros-pick,.pros-link{display:none}}

/* ── "Coming soon" (2026-09-29) ─────────────────────────────────────────────────
   Where the design shows a feature the product does not have yet (Future View, Building
   Watch, the Brief, citywide charts). Owner rule: never the mockups' sample numbers — an
   honest dashed outline that says what is coming, and nothing that could be read as data.
   The badge carries the word, so it is never colour alone. */
.bl-soon{border:1px dashed var(--axis);border-radius:var(--radius);padding:20px 22px;
  background:transparent;margin-bottom:var(--gap);display:flex;flex-direction:column;gap:8px}
.bl-soon h3{font:500 21px/1.25 var(--display);margin:0}
.bl-soon p{color:var(--ink-2);margin:0;max-width:62ch}
.bl-soonbadge{align-self:flex-start;font:600 10.5px/1 var(--font);letter-spacing:.07em;
  text-transform:uppercase;color:var(--ink-2);background:var(--sunk);border:1px solid var(--grid);
  border-radius:999px;padding:5px 9px}
/* The intelligence variant: BlockLot's own analysis, in its own voice (see `--intel`). */
.bl-soon.bl-intel{border-color:var(--intel-line);background:var(--intel-soft)}
.bl-intel .bl-soonbadge{color:var(--intel);background:var(--surface);border-color:var(--intel-line)}
.bl-intel h3{color:var(--ink)}
.bl-kicker{font:600 12px/1 var(--font);letter-spacing:.08em;text-transform:uppercase;color:var(--intel)}

/* ── The public home page ──────────────────────────────────────────────────────── */
.bl-hero{display:grid;grid-template-columns:minmax(0,7fr) minmax(0,5fr);gap:64px;
  align-items:start;padding:40px 0 56px}
.bl-hero h1{font:500 64px/1.03 var(--display);letter-spacing:-.02em;margin:18px 0 20px}
.bl-lede{font-size:19px;line-height:1.55;color:var(--ink-2);max-width:40em;margin-bottom:26px}
.bl-tagline{font:500 14px/1 var(--font);letter-spacing:.03em;color:var(--intel)}
.bl-search{display:flex;gap:10px;margin-bottom:12px}
.bl-search input{flex:1;min-width:0;height:58px;padding:0 20px;font:inherit;font-size:17px;
  border:1px solid var(--axis);border-radius:8px;background:var(--surface);color:var(--ink)}
.bl-search input:focus{outline:2px solid var(--accent);outline-offset:-1px;border-color:transparent}
.bl-search .btn{height:58px;padding:0 26px;font-size:15px}
.bl-aside{background:var(--surface);border:1px solid var(--grid);border-radius:12px;padding:26px}
.bl-aside h2{font:600 24px/1.2 var(--display);padding-bottom:14px;margin-bottom:6px;
  border-bottom:1px solid var(--rule)}
.bl-aside ul{list-style:none;display:flex;flex-direction:column}
.bl-aside li{padding:14px 0;border-bottom:1px solid var(--grid);display:flex;flex-direction:column;gap:4px}
.bl-aside li:last-child{border-bottom:0}
.bl-aside li b{font:500 18px/1.3 var(--display)}
.bl-aside li span{font-size:14px;color:var(--ink-2)}
.bl-pillars{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:40px;padding:8px 0 48px}
.bl-pillar{border-top:2px solid var(--rule);padding-top:20px;display:flex;flex-direction:column;gap:10px}
.bl-pillar .bl-num{font:500 12.5px/1 var(--mono);color:var(--ink-mute)}
.bl-pillar h3{font:500 28px/1.15 var(--display)}
.bl-pillar p{color:var(--ink-2);line-height:1.6}
.bl-pillar .bl-state{font-size:13px;font-weight:600;color:var(--ink)}
.bl-pillar .bl-state.bl-later{color:var(--ink-mute);font-weight:500}
@media (max-width:900px){
  .bl-hero{grid-template-columns:1fr;gap:32px;padding:16px 0 32px}
  .bl-hero h1{font-size:42px}
  .bl-lede{font-size:17px}
  .bl-search{flex-direction:column}
  .bl-search .btn{justify-content:center}
  .bl-pillars{grid-template-columns:1fr;gap:28px}
}

/* The footer's provenance line. */
.bl-sources{flex-basis:100%;font-size:12px;color:var(--ink-mute)}

/* ── Phone layout for the shared chrome ────────────────────────────────────────────
   The header wraps rather than overflowing: brand and switcher on the first line, the
   search and the nav on their own. */
@media (max-width:760px){
  :root{--pad:16px;--brandw:auto}
  header{flex-wrap:wrap;gap:10px 14px;padding:12px var(--pad)}
  /* Three rows: brand and switcher, then the nav (which may itself wrap), then the
     search box. `nav.top` is `nowrap` on a desktop so a name never breaks mid-word; on a
     phone it must be allowed to wrap BETWEEN items, or the header is wider than the
     screen and the whole page scrolls sideways (measured: 682px of layout on a 375px
     phone). */
  header .skin-pick{order:1;margin-left:auto}
  nav.top{order:2;flex-basis:100%;margin-left:0;flex-wrap:wrap;gap:8px 14px;font-size:13px}
  header .omni{order:3;flex-basis:100%;max-width:none}
  .skin-menu{width:min(290px,calc(100vw - 32px))}
  /* REQ-178: a phone gets the Prospecting hub link, not the dropdown. */
  .pros-pick{display:none}
  .pros-link{display:inline}
  main{padding:20px var(--pad)}
  .card{padding:18px}
  .card>h2{font-size:22px}
  .hero{font-size:44px}
  .subbar{top:auto;position:static;flex-wrap:wrap;gap:6px}
  .subbar-label{min-width:0}
  /* A card's table wider than a phone scrolls sideways INSIDE its card, rather than
     widening the page (measured 2026-09-29: the comps table, the portfolio table and the
     sessions table each pushed a 375px phone to 480-690px of layout, so the whole page
     scrolled sideways). `display:block` gives the table a box that can scroll; its rows
     keep table layout through the anonymous table box. The drawer has its own phone rule
     (rows stack, BUG-14) and is left alone. */
  main .card table,main .kv table{display:block;max-width:100%;overflow-x:auto}
  .kv{grid-template-columns:minmax(0,auto) minmax(0,1fr);gap:4px 12px}
  .kv dt{white-space:normal}
  /* A provenance chip carrying a sentence ("no allowance for this use in this
     district") is wider than a phone; it wraps rather than widening the page. */
  .chip{white-space:normal;line-height:1.3}
}

/* ── The property page's map (IDEA-4, 2026-09-29) ─────────────────────────────────
   `.lotmap` is the box MapLibre draws into (`console.js` → drawLotMap). It has a fixed
   height so the page does not jump when the map arrives, and a sunk background so the
   moment before it arrives reads as a place, not as a hole. `maplibre-gl.css` is loaded
   by console.js alongside the library, only on a page that has a map. */
.lotmap{position:relative;height:360px;border-radius:calc(var(--radius) - 4px);
  overflow:hidden;background:var(--sunk);border:1px solid var(--grid)}
.lotmap-msg{padding:16px;font-size:13px}
.lotmap-foot{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;margin-top:10px;
  font-size:13px}
.lotmap-links{margin-left:auto}
@media (max-width:860px){
  .lotmap{height:260px}
  .lotmap-links{margin-left:0}
}

/* ── Links that are the way forward on a form page (IDEA-10, 2026-09-29) ──────────────
   A plain `a` inherits its colour, so inside `.muted` a link is the same grey as the
   sentence around it — "Create one." on the sign-in page read as plain text. `.cta-link`
   gives it the accent colour, weight and an underline in every skin (it uses the tokens). */
.cta-link{color:var(--accent);font-weight:600;text-decoration:underline;
  text-underline-offset:2px}
.cta-link:hover{color:var(--accent-ink)}
.login-forgot{margin:8px 0 0;text-align:right}

/* IDEA-5 (2026-09-29): the apartment in the property page's headline. Smaller than the
   street address and set apart from it, the same idea as `.apt` in the tables. */
.hero-apt{display:inline-block;margin-left:.4em;font-size:.55em;font-weight:500;
  color:var(--accent);vertical-align:.18em}

/* The annual property tax's past years (owner request 2026-09-30): the fiscal year is the
   control, styled as a link with a caret, and opens a small table under the figure. */
.taxhist{display:inline}
.taxhist>summary{display:inline;cursor:pointer;list-style:none;color:var(--accent);
  font-weight:500;margin-left:4px}
.taxhist>summary::-webkit-details-marker{display:none}
.taxhist>summary::before{content:"· "}
.taxhist>summary::after{content:" ▾";font-size:.8em}
.taxhist[open]>summary::after{content:" ▴"}
.taxhist-table{display:table;margin:8px 0 4px;width:auto;font-size:12.5px}
.taxhist-table th,.taxhist-table td{white-space:nowrap;padding:4px 12px 4px 0}
.taxhist-table th:last-child,.taxhist-table td:last-child{padding-right:0}
.taxhist-table th{font-size:10.5px;letter-spacing:.04em}
.taxhist-note{display:block;margin-bottom:6px}

/* ── /leases — the citywide Leases expiring list (REQ-170 WP-17) ─────────────────────
   Tokens only, so the three skins restyle it like everything else. The filters wrap; at
   phone width each takes the full row, and the two count tables stack. */
.lw-title{margin:2px 0 6px}
.lw-tabs{display:flex;gap:4px;margin:6px 0 10px;flex-wrap:wrap}
.lw-tabs a{padding:6px 11px;border:1px solid var(--grid);border-radius:6px;font-size:12.5px;
  color:var(--ink-2);text-decoration:none}
.lw-tabs a.on{background:var(--ink);border-color:var(--ink);color:var(--surface)}
.lw-filters{display:flex;flex-wrap:wrap;gap:10px;margin:10px 0 4px;align-items:flex-end}
.lw-filters label{display:flex;flex-direction:column;gap:3px;font-size:12px;color:var(--ink-2)}
.lw-filters input,.lw-filters select{font:inherit;font-size:13px;padding:6px 8px;
  border:1px solid var(--axis);border-radius:6px;background:var(--surface);color:var(--ink);
  max-width:15rem}
.lw-counts{display:flex;flex-wrap:wrap;gap:18px;align-items:flex-start}
.lw-counts table{width:auto;min-width:14rem}
.lw-table td{vertical-align:top}
.lw-actions{justify-content:flex-end}
.lw-actions form.inline{display:inline}
.toolbar form.inline{display:inline}
/* IDEA-17: the distress search's checkbox groups and the highlighted ticked signal. */
.ds-group{flex:1 1 100%;border:1px solid var(--axis);border-radius:8px;padding:6px 10px 8px;
  margin:0;display:flex;flex-wrap:wrap;gap:4px 16px}
.ds-group legend{font-size:12px;color:var(--ink-2);padding:0 4px}
.ds-group legend select{font-size:12px;padding:2px 4px;margin-left:4px}
.lw-filters label.ds-check{flex-direction:row;align-items:center;gap:6px;font-size:13px;
  color:var(--ink)}
.lw-counts tr.mine td{font-weight:600}
@media (max-width:560px){
  .lw-filters label{flex:1 1 100%}
  .lw-filters input,.lw-filters select{max-width:none;width:100%}
  .lw-counts table{min-width:0;width:100%}
  /* Each row becomes a small card: date, building, lease, our records, source — read top
     to bottom instead of scrolled sideways. */
  /* `table.compact` in every selector: `table.compact td{height:30px}` is (0,1,2) and
     a fixed height on a stacked cell overlaps the next one (the drawer's lesson, above). */
  table.compact.lw-table thead{display:none}
  table.compact.lw-table,table.compact.lw-table tbody,table.compact.lw-table tr,
  table.compact.lw-table td{display:block;width:100%}
  table.compact.lw-table tr{padding:10px 0;border-bottom:1px solid var(--grid)}
  table.compact.lw-table td{border:0;padding:2px 0;height:auto;white-space:normal}
  table.compact.lw-table td.nowrap{font-weight:600}
}

/* ── /commercial — the commercial search (REQ-185) and the ZIP page (REQ-187) ─────────
   Built on the /leases tokens above (`lw-` filters, tabs, counts, table); these add the
   "where" box, the greyed filters, the choice list, the tab counts and the ZIP figures.
   Tokens only, so the three skins restyle it like every other card. */
.cs-where{flex:1 1 22rem}
.cs-filters .cs-where input{max-width:none;width:100%}
.cs-off{opacity:.45}
.cs-off input,.cs-off select{cursor:not-allowed}
.cs-gapf select{max-width:11rem}
.cs-more{flex:1 1 100%}
.cs-more>summary{cursor:pointer;font-size:12.5px;color:var(--accent)}
.cs-hint{margin:2px 0 0}
.cs-pick h2{margin-top:4px}
.cs-choices{margin:6px 0 0;padding-left:1.2em;line-height:1.8}
.cs-head .cs-tabs{margin-top:0}
.cs-n{font-variant-numeric:tabular-nums;opacity:.75;margin-left:3px}
.cs-where-line{margin:4px 0 8px}
.cs-rank td.n{white-space:nowrap}
.zp-figs{display:flex;flex-wrap:wrap;gap:8px 22px;margin:8px 0 6px}
.zp-figs div{min-width:8rem}
.zp-figs dt{font-size:11px;color:var(--ink-2);letter-spacing:.02em}
.zp-figs dd{margin:0;font-size:18px;font-variant-numeric:tabular-nums}
.lotmap.areamap.zp-map{height:520px}
@media (max-width:560px){
  .cs-where{flex:1 1 100%}
  .cs-gapf select{max-width:none}
  .lotmap.areamap.zp-map{height:380px}
}

/* ── The area page (REQ-184): block, street frontage, block face, segment ─────────────
   `area.html`. The switch is two links styled as a segmented control; the figures are a
   responsive grid of label/value pairs; the map reuses `.lotmap`, taller. Tokens only, so
   the three skins restyle it like every other card. */
.area-title{margin:2px 0 4px}
.area-where{margin:0 0 8px;font-size:13px}
.area-switch{display:inline-flex;border:1px solid var(--grid);border-radius:999px;overflow:hidden;
  margin-top:4px}
.area-switch a{padding:5px 14px;font-size:13px;text-decoration:none;color:var(--ink-2)}
.area-switch a.on{background:var(--accent);color:#fff;font-weight:600}
.area-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:10px 16px;
  margin:6px 0 10px}
.area-fig{border-left:3px solid var(--accent-soft);padding:2px 0 2px 10px;margin:0}
.area-fig dt{font-size:12px;color:var(--ink-2);margin:0}
.area-fig dd{margin:2px 0 0;font-size:18px;font-weight:600}
.area-fig dd.small{font-size:12px;font-weight:400}
.lotmap.areamap{height:440px}
/* REQ-191: the owner's-portfolio map. `.lotmap` box; the legend dots read the same tokens
   `drawPortfolioMap` paints with, so each skin's map and legend agree. */
.lotmap.pfmap{height:340px;margin-bottom:4px}
.pfmap-foot{margin:0 0 12px}
.pfmap-key{display:inline-flex;align-items:center;gap:5px;font-size:12.5px;color:var(--ink-2)}
.pfmap-key i{display:inline-block;width:10px;height:10px;border-radius:50%;border:1.5px solid #fff;
  box-shadow:0 0 0 1px var(--axis)}
.pfmap-key .pfk-s{background:var(--accent);width:13px;height:13px}
.pfmap-key .pfk-c{background:var(--plum)}
.pfmap-key .pfk-p{background:var(--intel-mark)}
tr.pf-hot>td{background:var(--accent-soft)}
.area-chips,.area-layers{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:0 0 8px}
.chip-btn{font:600 12px/1 var(--font);padding:5px 10px;border-radius:999px;cursor:pointer;
  border:1px solid var(--grid);background:transparent;color:var(--ink)}
.chip-btn.on{background:var(--accent);border-color:var(--accent);color:#fff}
.chip-btn:disabled{cursor:not-allowed;opacity:.55;font-weight:400}
.area-legend{display:flex;flex-wrap:wrap;gap:4px 14px;margin-top:8px;color:var(--ink-2)}
.area-legend .sw{display:inline-block;width:12px;height:12px;border-radius:2px;margin-right:5px;
  vertical-align:-2px;border:1px solid #5f6670}
.sw-this,.sw-lot{background:color-mix(in srgb,var(--accent) 40%,transparent)}
.sw-across{background:rgba(217,130,43,.45)}
.sw-other{background:rgba(154,160,166,.25);border-color:#b8bcc0}
.sw-subject{background:color-mix(in srgb,var(--accent) 70%,transparent);border-width:2px}
.sw-edge{background:transparent;border:3px solid #1d2733;height:10px}
.sw-node{background:#1d2733;border:2px solid #fff;border-radius:50%;box-shadow:0 0 0 1px #1d2733}
.sw-part{background:rgba(29,39,51,.06);border:2px dashed #6b7480;height:10px}
/* REQ-188: the city blocks a tax block covers, listed under the header. */
.area-parts{margin:6px 0 8px}
.area-parts p{margin:0 0 4px}
.area-part-list ol{margin:4px 0 0;padding-left:22px;columns:2 320px;column-gap:24px}
.area-part-list li{break-inside:avoid;margin:0 0 2px}
.area-part-list li.on{font-weight:600}
.area-dev-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.4fr);gap:12px 20px;align-items:start}
@media (max-width:720px){.area-dev-grid{grid-template-columns:minmax(0,1fr)}}
table[data-sortable] th[data-sort]{cursor:pointer;white-space:nowrap}
table[data-sortable] th[data-dir="asc"]::after{content:" ▲";font-size:9px}
table[data-sortable] th[data-dir="desc"]::after{content:" ▼";font-size:9px}
table[data-sortable] tr.mine td{font-weight:600}
.c-anchor{color:#7a4b00;border-color:#d9a54a;background:#fbf1dc}
.area-pop .maplibregl-popup-content{font:13px/1.35 var(--font);padding:6px 9px;
  background:var(--surface);color:var(--ink)}
/* REQ-191: MapLibre paints the box and its tip white; on the Terminal skin that put light
   ink on white. The skin's own surface instead, tip included, on every anchor. */
.area-pop.maplibregl-popup-anchor-bottom .maplibregl-popup-tip,
.area-pop.maplibregl-popup-anchor-bottom-left .maplibregl-popup-tip,
.area-pop.maplibregl-popup-anchor-bottom-right .maplibregl-popup-tip{border-top-color:var(--surface)}
.area-pop.maplibregl-popup-anchor-top .maplibregl-popup-tip,
.area-pop.maplibregl-popup-anchor-top-left .maplibregl-popup-tip,
.area-pop.maplibregl-popup-anchor-top-right .maplibregl-popup-tip{border-bottom-color:var(--surface)}
.area-pop.maplibregl-popup-anchor-left .maplibregl-popup-tip{border-right-color:var(--surface)}
.area-pop.maplibregl-popup-anchor-right .maplibregl-popup-tip{border-left-color:var(--surface)}
.area-lot-table th[data-sort]{cursor:pointer;white-space:nowrap}
.area-lot-table th[data-dir="asc"]::after{content:" ▲";font-size:9px}
.area-lot-table th[data-dir="desc"]::after{content:" ▼";font-size:9px}
.area-lot-table tr.mine td{font-weight:600}
/* REQ-186: the lots table's header stays put while its rows scroll. The wrapper is the
   scroll box both ways (a sticky cell sticks to its nearest scrolling ancestor, and the
   horizontal wrapper alone would make that a box that never scrolls vertically), capped
   so a 500-lot block is a window onto the table rather than a 25,000-pixel page. */
.area-lots-scroll{max-height:min(78vh,960px);overflow:auto;border-top:1px solid var(--grid)}
.area-lots-scroll .area-lot-table thead th{position:sticky;top:0;z-index:2;background:var(--surface);
  box-shadow:inset 0 -1px 0 var(--grid)}
/* REQ-189: the ZIP / tract / NTA page's ranked blocks keep their header row on screen
   (the lots table's rule above), and the statistic a list is ranked by stands out. */
.cs-rank-scroll{max-height:min(78vh,960px);overflow:auto;border-top:1px solid var(--grid)}
.cs-rank-scroll .cs-rank thead th,.cs-rank-scroll .dl-table thead th{position:sticky;top:0;z-index:2;background:var(--surface);
  box-shadow:inset 0 -1px 0 var(--grid)}
.cs-rank .cs-stat{font-weight:600}
/* REQ-202, owner rule: EVERY large list of buildings, lots or blocks is a window onto the
   table with its header row on screen and sortable headings — the lots table's rule above,
   as one class for any table (`<div class="dof-sales-scroll sheet-scroll">` + `<table
   data-sortable>`). The drawer's rows get the same sticky header. */
.sheet-scroll{max-height:min(78vh,960px);overflow:auto;border-top:1px solid var(--grid)}
.sheet-scroll table thead th,.drawer-scroll table thead th{position:sticky;top:0;z-index:2;
  background:var(--surface);box-shadow:inset 0 -1px 0 var(--grid)}
table[data-sortable] th[data-sort]{cursor:pointer}
.zp-sorts{line-height:1.7}
.zp-stats .area-grid{margin-top:6px}
.zp-trends details{margin:4px 0}
/* REQ-190: the deal search section. */
.dl-presets{display:flex;flex-wrap:wrap;gap:6px;margin:4px 0 10px}
.dl-presets .dl-more{opacity:.85}
.dl-filters summary{cursor:pointer;font-weight:600;margin:4px 0}
.dl-form{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:10px 18px}
.dl-form .toolbar{grid-column:1/-1}
.dl-group{border:1px solid var(--grid);border-radius:6px;padding:6px 10px;margin:0;min-width:0}
.dl-group legend{font-weight:600;font-size:13px;padding:0 4px}
.dl-field{display:block;margin:4px 0;font-size:13px}
.dl-field input[type=number]{width:7.5em}
.dl-field select{max-width:100%}
.dl-opt{display:inline-block;margin-right:8px;white-space:nowrap}
.dl-grey{opacity:.55}
.dl-why{display:block}
.dl-results h3 .sub{font-weight:400}
.dl-map{height:320px;margin:8px 0}
.dl-sorts{line-height:1.7}
.zp-layers{flex-wrap:wrap}
.area-mix{max-width:360px}
.area-card-line{margin:4px 0}
.area-foot{margin:8px 2px 24px}
@media (max-width:560px){
  .lotmap.areamap{height:340px}
  .area-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .area-fig dd{font-size:16px}
}
