/* Dashboards: the rendered grid (base/templates/base/dashboard_page.html,
   an inline `dashboard` section on a group site) and the builder's canvas —
   one renderer (static/js/dashboard.js), so one stylesheet. */

.nhd-head { display:flex; flex-wrap:wrap; align-items:center; gap:10px 16px; margin-bottom:14px; }
.nhd-head h1 { font-size:1.5rem; margin:0; }
.nhd-head .nhd-meta { color:var(--ink-3); font-size:0.85rem; }
.nhd-head .spacer { flex:1; }
.nhd-head a { color:var(--link); font-weight:600; font-size:0.85rem; text-decoration:none; }
.nhd-switch { font-size:0.85rem; padding:5px 8px; border:1px solid var(--line-strong); border-radius:var(--r-sm); background:var(--surface); color:var(--ink); }

/* The grid. Twelve columns; a row is --nhd-row tall; a widget spans (x, y, w, h)
   in cells. Same numbers as base/dashboard_layout.py (COLS, ROW_PX). */
.nhd-grid { --nhd-cols:12; --nhd-row:60px; --nhd-gap:10px;
  display:grid; grid-template-columns:repeat(var(--nhd-cols), minmax(0, 1fr));
  grid-auto-rows:var(--nhd-row); gap:var(--nhd-gap); position:relative; }
.nhd-w { position:relative; display:flex; flex-direction:column; min-width:0; min-height:0;
  border:1px solid var(--line); border-radius:var(--r-md); background:var(--surface); overflow:hidden; }
body.dark-mode .nhd-w { background:var(--surface-2); }
.nhd-w__head { display:flex; align-items:center; gap:8px; padding:6px 10px; font-size:0.82rem; font-weight:600;
  color:var(--ink-2); border-bottom:1px solid var(--line); min-height:30px; flex:0 0 auto; }
/* The head is a query container so the sensor name shrinks in a narrow tile
   instead of ellipsing to a single letter ("B…" for "Botanical Garden",
   2026-08-29). It stays one line — wrapping here would steal height from the
   body — but it gives up size before it gives up characters. */
.nhd-w__head { container-type:inline-size; }
.nhd-w__head .nhd-w__title { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  font-size:clamp(0.62rem, 7cqw, 0.82rem); }
/* …except where the head IS the label. On a value tile and a rule chart the
   title carries the sensor or the rule name, and an ellipsis there ("Nort…",
   "North Fork 0 Cond ML …") costs the reader the only identification the
   widget has. Those two wrap onto as many lines as the name needs and shrink
   further before they do, rather than truncating (operator, 2026-08-29). The
   one-line rule above still holds everywhere else, where the head sits over a
   chart and every line it takes is a line off the plot.
   The alert-status widget joined them 2026-09-07: its head sits over chips,
   not a plot, and on a phone-width column the one-line rule cut its label to
   "Ale…" (audit L1-6). */
.nhd-w--stat .nhd-w__head, .nhd-w--rule_viz .nhd-w__head,
.nhd-w--alert_status .nhd-w__head { align-items:flex-start; }
.nhd-w--stat .nhd-w__head .nhd-w__title,
.nhd-w--rule_viz .nhd-w__head .nhd-w__title,
.nhd-w--alert_status .nhd-w__head .nhd-w__title {
  /* break-word, not anywhere: `anywhere` fed min-content sizing and split
     "Alert/statu/s" mid-word in a 34px column (sweep P1 F1, 2026-09-07) —
     the same lesson the hero title learned this morning. */
  white-space:normal; overflow:visible; text-overflow:clip; overflow-wrap:break-word;
  font-size:clamp(0.55rem, 7cqw, 0.82rem); line-height:1.25; }
.nhd-w--stat .nhd-w__head .nhd-w__sub,
.nhd-w--rule_viz .nhd-w__head .nhd-w__sub,
.nhd-w--alert_status .nhd-w__head .nhd-w__sub { white-space:normal; overflow-wrap:anywhere; }
/* The title is an <h2> for navigation, styled as the header text it always was. */
h2.nhd-w__title { margin:0; font-size:inherit; font-weight:inherit; line-height:inherit; color:inherit; }
/* Text present for assistive technology only (chip details, history bars). */
.nhd-sr { position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; }
.nhd-w:focus-visible { outline:2px solid var(--accent-ring, #2887b1); outline-offset:2px; }
.nhd-w__head .nhd-w__sub { font-weight:400; color:var(--ink-3); white-space:nowrap;
  font-size:clamp(0.58rem, 6cqw, 0.82rem); }
.nhd-w__body { flex:1 1 auto; min-height:0; position:relative; overflow:auto; padding:8px 10px; font-size:0.85rem; color:var(--ink-2); }
.nhd-w__body--flush { padding:0; overflow:hidden; }
.nhd-w__body .nhd-plot { position:absolute; inset:0; }
.nhd-msg { color:var(--ink-3); font-size:0.82rem; margin:0; }
.nhd-err { color:var(--critical-ink); font-size:0.82rem; margin:0; }

/* Latest value tile. The number is the widget; the label and the time are
   captions, one line each. The tile is a query container so the number scales
   with the cell it landed in (a 3-column cell at a 721 px viewport is ~150px
   wide; on a phone the grid is one column and every widget is full width) and
   nothing is ever clipped or scrolled. */
.nhd-w__body--stat { overflow:hidden; padding:6px 8px; }
.nhd-stat { display:flex; flex-direction:column; justify-content:center; align-items:center; height:100%;
  min-width:0; text-align:center; container-type:inline-size; }
/* `min-width:0` on the flex child and a floor low enough that a four- or
   five-digit reading with its unit still fits a ~70 px tile without being
   clipped to "463." (2026-08-29). */
/* The floor is what decides whether a reading is READ or merely displayed.
   At 0.95rem "524.51 µS/cm" did not fit an ~85 px tile and ellipsed to
   "524…." — a number with its digits cut off is worse than no number, because
   it still looks like one. The floor comes down far enough that the full
   value and its unit fit the narrowest tile the grid produces; the ceiling and
   the desktop rendering are untouched, so a roomy tile still shows a big
   number (operator, 2026-08-29). */
.nhd-stat__v { font-size:clamp(0.58rem, 16cqw, 2.4rem); font-weight:700; color:var(--ink); line-height:1.05;
  letter-spacing:-0.02em; font-variant-numeric:tabular-nums; white-space:nowrap; max-width:100%;
  min-width:0; overflow:hidden; text-overflow:ellipsis; }
.nhd-stat__v small { font-size:0.42em; font-weight:500; color:var(--ink-3); margin-left:0.15em; letter-spacing:0; }
/* The captions SHRINK rather than truncate: in a narrow tile "Conductivity"
   ellipsed to "Co…" and "Aug 28, 06:45" to "Aug…" told the reader nothing,
   which defeats the tile. They scale with the container like the number, wrap
   onto a second line when they still do not fit, and only then ellipse
   (2026-08-29). The variable name and the value are the two things that must
   stay legible at any width. */
.nhd-stat__l, .nhd-stat__t { max-width:100%; overflow:hidden; overflow-wrap:anywhere;
  display:-webkit-box; -webkit-box-orient:vertical; }
.nhd-stat__l { font-size:clamp(0.6rem, 7cqw, 0.8rem); font-weight:600; color:var(--ink-2);
  margin-top:5px; line-height:1.2; -webkit-line-clamp:2; }
.nhd-stat__t { font-size:clamp(0.55rem, 6cqw, 0.72rem); color:var(--ink-3);
  margin-top:2px; line-height:1.2; -webkit-line-clamp:2; }
.nhd-stat--low .nhd-stat__v, .nhd-stat--high .nhd-stat__v { color:var(--critical-ink); }

/* Sensor info + weather: definition rows. */
.nhd-kv { display:grid; grid-template-columns:auto 1fr; gap:3px 10px; font-size:0.82rem; margin:0; }
.nhd-kv dt { color:var(--ink-3); }
.nhd-kv dd { margin:0; color:var(--ink); overflow-wrap:anywhere; }
.nhd-notes { white-space:pre-wrap; font-size:0.8rem; color:var(--ink-2); margin:8px 0 0; }

/* Weather. One "now" line — the temperature large, like the stat tile, and
   humidity / wind / 24 h rain as a quiet strip that wraps — then the plot,
   then a one-line HTML legend that stands in for the axis titles. The widget
   is a query container so the temperature scales with the cell. */
.nhd-wx { display:flex; flex-direction:column; height:100%; min-width:0; container-type:inline-size; overflow:hidden; }
.nhd-wx__now { display:flex; flex-wrap:wrap; gap:2px 14px; align-items:baseline; margin-bottom:4px; min-width:0; flex:0 0 auto; }
.nhd-wx__t { font-size:clamp(1.3rem, 9cqw, 2rem); font-weight:700; color:var(--ink); line-height:1.05;
  letter-spacing:-0.02em; font-variant-numeric:tabular-nums; white-space:nowrap; }
.nhd-wx__t small { font-size:0.5em; font-weight:500; color:var(--ink-3); margin-left:0.15em; letter-spacing:0; }
/* The sky: the glyph from static/css/nh_wx_icons.css with its WMO label beside
   it. The SVG is aria-hidden, so the label is the icon's text alternative and
   not decoration — it stays visible at every width and is the first thing a
   screen reader is given about the weather.
   --wx-icon-size is turned down from the map card's 2.1em because these cells
   go to 90 px; it is still in `em`, so it follows the widget's own type. */
.nhd-wx__sky { display:flex; align-items:center; gap:var(--space-1); min-width:0; flex:0 0 auto;
  margin-bottom:var(--space-0); --wx-icon-size:1.45em; }
.nhd-wx__sky-l { font-size:var(--fs-0); color:var(--ink-2); line-height:1; min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* Inside the "now" line the sky sits ahead of the big temperature, which is a
   baseline-aligned flex row — centre the glyph against it rather than letting
   the box hang off the text baseline, and drop its own bottom margin, since
   the row it is in already has one. */
.nhd-wx__now .nhd-wx__sky { align-self:center; margin-bottom:0; }
.nhd-wx__strip { display:flex; flex-wrap:wrap; gap:2px 12px; min-width:0; font-size:0.78rem; color:var(--ink-3); }
.nhd-wx__k { white-space:nowrap; }
.nhd-wx__plot { flex:1 1 auto; min-height:80px; min-width:0; position:relative; overflow:hidden; }
.nhd-wx__legend { display:flex; flex-wrap:wrap; gap:2px 12px; flex:0 0 auto; margin-top:2px; font-size:0.72rem; color:var(--ink-3); }
.nhd-wx__key { white-space:nowrap; display:inline-flex; align-items:center; gap:5px; }
.nhd-wx__swatch { display:inline-block; width:10px; height:10px; border-radius:var(--r-xs); flex:0 0 auto; }
.nhd-wx__key--rain .nhd-wx__swatch { background:#2887b1; }
.nhd-wx__key--temp .nhd-wx__swatch { background:#cb8616; height:3px; border-radius:2px; }
/* "modelled · 3-25 km grid, 1.5 km away". Deliberately not styled as a warning:
   it is a fact about the series, and a yellow banner on every weather widget
   would be tuned out by the second dashboard. It wraps rather than truncating —
   the distance is the half most likely to fall off a narrow card, and it is the
   half that decides whether the number is worth anything here. */
.nhd-wx__src { font-size:0.7rem; color:var(--ink-3); margin-top:4px; white-space:normal; }
/* Figures only: a two-column grid of label-above-value cells, each one line
   with an ellipsis, that still reads in 90 px cells. The widget SCROLLS in
   this mode (2026-09-25): `.nhd-wx` is `overflow:hidden` for the chart, whose
   plot flexes to fill the cell, but six figures plus the sky and source lines
   are taller than a default cell and "Rain, next 24 h" was clipped off the
   bottom with nothing to say it was there. */
.nhd-wx--figs { overflow-y:auto; overflow-x:hidden; overscroll-behavior:contain; scrollbar-gutter:stable; }
.nhd-wx__figs { display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:6px 8px; }
.nhd-wx__fig { padding:4px 6px; border-radius:var(--r-sm); background:var(--surface-2); min-width:0; }
.nhd-wx__fig-l { font-size:0.7rem; color:var(--ink-3); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.nhd-wx__fig-v { font-size:0.98rem; font-weight:700; color:var(--ink); line-height:1.25; font-variant-numeric:tabular-nums;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* Alert status chips: the same levels as the alert cards. */
.nhd-chips { display:flex; flex-direction:column; gap:6px; }
.nhd-chip { display:flex; align-items:center; gap:8px; font-size:0.82rem; }
.nhd-chip__dot { width:10px; height:10px; border-radius:50%; flex:0 0 auto; background:var(--unknown); }
.nhd-chip--good .nhd-chip__dot { background:var(--good); }
.nhd-chip--warning .nhd-chip__dot { background:var(--warning); }
.nhd-chip--serious .nhd-chip__dot { background:var(--serious); }
.nhd-chip--critical .nhd-chip__dot { background:var(--critical); }
.nhd-chip { padding:3px 8px; border-radius:var(--r-sm); background:var(--unknown-tint); }
.nhd-chip--good { background:var(--good-tint); }
.nhd-chip--warning { background:var(--warning-tint); }
.nhd-chip--critical { background:var(--critical-tint); }
.nhd-chip--critical .nhd-chip__s, .nhd-chip--critical .nhd-chip__t { color:var(--critical-ink); font-weight:600; }
.nhd-chip--good .nhd-chip__s { color:var(--good-ink); }
.nhd-chip--warning .nhd-chip__s { color:var(--warning-ink); }
.nhd-chip__t { flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--ink); }
.nhd-chip__s { color:var(--ink-3); white-space:nowrap; }

.nhd-events { width:100%; border-collapse:collapse; font-size:0.78rem; }
.nhd-events td { padding:3px 6px 3px 0; border-bottom:1px solid var(--line); vertical-align:top; }
.nhd-events .t { color:var(--ink-3); white-space:nowrap; }

.nhd-text { white-space:pre-wrap; margin:0; line-height:1.5; color:var(--ink); }
.nhd-img { width:100%; height:100%; object-fit:cover; display:block; }
.nhd-img--contain { object-fit:contain; }
.nhd-mapbox { position:absolute; inset:0; }

/* Rule chart: the visualizer's own SVG, fitted to the widget — never scaled
   up past the box (it is drawn with `meet` in dashboard.js). */
.nhd-viz { display:flex; flex-direction:column; overflow:hidden; }
/* The window buttons as a compact segmented control: one row, never wrapped,
   scrolling sideways in a cell too narrow for four (wave 23 Q1). */
.nhd-viz .viz-controls { margin:0 0 6px; flex:0 0 auto; display:inline-flex; flex-wrap:nowrap; gap:0; align-self:flex-start; max-width:100%;
  overflow-x:auto; overflow-y:hidden; scrollbar-width:none; border:1px solid var(--line); border-radius:var(--r-pill); background:var(--surface-2); }
.nhd-viz .viz-controls::-webkit-scrollbar { display:none; }
.nhd-viz .viz-win { border:none; border-radius:0; background:transparent; padding:2px 9px; font-size:0.72rem; white-space:nowrap; flex:0 0 auto; }
.nhd-viz .viz-win + .viz-win { border-left:1px solid var(--line); }
.nhd-viz .viz-win.on { background:var(--accent-hover); color:#fff; }
body.dark-mode .nhd-viz .viz-win { background:transparent; }
body.dark-mode .nhd-viz .viz-win.on { background:var(--accent-hover); color:#fff; }
.nhd-viz .viz-chart { width:100%; height:auto; flex:1 1 auto; min-height:0; max-height:100%; }
.nhd-viz .viz-plotly { flex:1 1 auto; min-height:0; }
.nhd-viz .viz-legend, .nhd-viz .viz-stats, .nhd-viz .viz-warn { flex:0 0 auto; font-size:0.74rem; margin:4px 0 0; }
/* The rule's own line — sensor · variable (unit) · model. It was one line with
   an ellipsis, which on a dashboard-width card cut it at "HistGradientBoost…"
   and dropped the model name, the unit, or both. It wraps now, taking as many
   rows as it needs: this is three facts, not a heading, and a card that hides
   two of them to stay one line tall has kept the wrong thing (operator,
   2026-08-29). */
.nhd-viz__meta { font-size:0.72rem; margin:0 0 4px; flex:0 0 auto; max-width:100%;
  white-space:normal; overflow-wrap:anywhere; line-height:1.35; }

/* Tablets, 721–1024px: the grid is still the desktop one — a phone stacks it,
   a tablet does not — so four value tiles across a 768px iPad land in cells
   about 60px wide, where the container-query floors below were still too tall
   and "4.06" clipped to "4…". The floors come down here and only here; the
   ceilings and the desktop rendering are untouched (2026-08-29). */
@media (min-width: 721px) and (max-width: 1024px) {
  .nhd-stat__v { font-size:clamp(0.54rem, 16cqw, 2.4rem); }
  .nhd-stat__l { font-size:clamp(0.5rem, 7cqw, 0.8rem); }
  .nhd-stat__t { font-size:clamp(0.46rem, 6cqw, 0.72rem); }
  .nhd-stat__v small { font-size:0.4em; }
  .nhd-w__body--stat { padding:4px 5px; }
  .nhd-w__head { padding:5px 7px; gap:5px; }
  .nhd-w__head .nhd-w__title { font-size:clamp(0.52rem, 7cqw, 0.82rem); }
  .nhd-w__head .nhd-w__sub { font-size:clamp(0.48rem, 6cqw, 0.82rem); }
  /* The two heads that carry a NAME keep wrapping here too — this band is the
     iPad the truncation was reported on, so its floors must not put the
     ellipsis back. */
  .nhd-w--stat .nhd-w__head .nhd-w__title,
  .nhd-w--rule_viz .nhd-w__head .nhd-w__title { font-size:clamp(0.5rem, 7cqw, 0.82rem); }
  .nhd-wx__figs { gap:4px 6px; }
}

/* Narrow screens: one column, natural heights per widget type — a value tile
   short, anything with a chart tall enough that axis labels never sit under a
   legend. The layout is a desktop arrangement; a phone gets the widgets in
   order. No widget body may scroll sideways. */
@media (max-width: 720px) {
  .nhd-grid { display:flex; flex-direction:column; }
  .nhd-w { grid-column:auto !important; grid-row:auto !important; min-height:220px; }
  .nhd-w--short { min-height:110px; }
  .nhd-w--stat, .nhd-w--alert_status, .nhd-w--sensor_info { min-height:96px; }
  /* Chart widgets get a FIXED height on a phone, not a minimum: their plots
     size themselves from the body (ResizeObserver → Plotly.relayout), and a
     body that grows with its content feeds that height straight back —
     the rule chart ran away to thousands of pixels (2026-08-28). */
  .nhd-w--timeseries, .nhd-w--histogram, .nhd-w--weather, .nhd-w--history, .nhd-w--map { height:300px; min-height:0; }
  .nhd-w--rule_viz { height:360px; min-height:0; }
  .nhd-head { flex-wrap:wrap; gap:6px 10px; }
  .nhd-head h1 { flex:1 1 100%; font-size:1.25rem; }
  .nhd-head .spacer { display:none; }
  .nhd-w__body { overflow-x:hidden; }
  .nhd-stats { grid-template-columns:repeat(2, minmax(0, 1fr)); }
  .nhd-viz .viz-plotly { min-height:200px; }

  /* …unless this dashboard has a MOBILE LAYOUT of its own. Then the author
     arranged the phone themselves — same twelve columns, drawn at phone width
     — and everything above (the stacking, the per-type heights) would fight
     what they laid out. The renderer marks the grid; the widget's own cell
     comes back through custom properties it sets on each widget, because the
     rules above win over an inline `grid-column` with `!important`.
     The heights still come from the grid, never from the content, so the
     runaway-plot loop those fixed heights closed cannot come back. */
  .nhd-grid--mobile { display:grid; }
  .nhd-grid--mobile > .nhd-w { grid-column:var(--nhd-gc) !important; grid-row:var(--nhd-gr) !important; height:auto !important; min-height:0 !important; }
}

/* Alert & downtime history: one row per sensor (outages) and per rule (alerts). */
.nhd-hist { display:flex; flex-direction:column; gap:4px; font-size:0.76rem; }
.nhd-hist__row { display:grid; grid-template-columns:minmax(90px, 30%) 1fr; gap:8px; align-items:center; }
.nhd-hist__label { color:var(--ink-2); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.nhd-hist__track { position:relative; height:14px; background:var(--surface-2); border-radius:var(--r-pill); border:1px solid var(--line); overflow:hidden; }
.nhd-hist__bar { position:absolute; top:0; bottom:0; border-radius:2px; }
.nhd-hist__bar--alert { background:var(--critical); }
.nhd-hist__bar--down { background:var(--unknown); }
.nhd-hist__bar.is-open { box-shadow:inset 0 0 0 1px rgba(0,0,0,0.25); }
.nhd-hist__none { position:absolute; left:6px; top:-1px; color:var(--ink-3); font-size:0.68rem; }
.nhd-hist__axis { position:relative; height:14px; margin-left:calc(30% + 8px); color:var(--ink-3); font-size:0.66rem; }
.nhd-hist__axis span { position:absolute; transform:translateX(-50%); white-space:nowrap; }

/* Several latest values from one sensor, as a compact grid. */
.nhd-stats { display:grid; grid-template-columns:repeat(auto-fit, minmax(84px, 1fr)); gap:6px 8px; }
.nhd-stats__cell { padding:4px 6px; border-radius:var(--r-sm); background:var(--surface-2); min-width:0; }
.nhd-stats__l { font-size:0.7rem; color:var(--ink-3); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.nhd-stats__v { font-size:1.15rem; font-weight:700; color:var(--ink); line-height:1.2; font-variant-numeric:tabular-nums;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.nhd-stats__v small { font-size:0.7rem; font-weight:500; color:var(--ink-3); margin-left:3px; }

/* Photo gallery widget. */
.nhd-gallery { position:absolute; inset:0; }
.nhd-gallery .nhd-img { width:100%; height:100%; }
.nhd-gallery__btn { position:absolute; top:50%; transform:translateY(-50%); width:32px; height:44px; border:none; border-radius:var(--r-sm);
  background:rgba(0,0,0,0.35); color:#fff; font-size:1.6rem; line-height:1; cursor:pointer; opacity:0.7; }
.nhd-gallery__btn:hover { opacity:1; }
.nhd-gallery__btn--prev { left:6px; } .nhd-gallery__btn--next { right:6px; }
.nhd-gallery__pause { position:absolute; top:6px; right:6px; width:30px; height:26px; border:none; border-radius:var(--r-sm);
  background:rgba(0,0,0,0.45); color:#fff; font-size:0.72rem; cursor:pointer; opacity:0.8; }
.nhd-gallery__pause:hover, .nhd-gallery__pause:focus-visible { opacity:1; }
.nhd-gallery__dots { position:absolute; left:0; right:0; bottom:26px; display:flex; justify-content:center; gap:5px; }
.nhd-gallery__dot { width:8px; height:8px; border-radius:50%; border:none; background:rgba(255,255,255,0.55); cursor:pointer; padding:0; }
.nhd-gallery__dot.is-on { background:#fff; }
.nhd-gallery__cap { position:absolute; left:0; right:0; bottom:0; padding:3px 8px; font-size:0.72rem; color:#fff; background:linear-gradient(transparent, rgba(0,0,0,0.55)); }
.nhd-crumb { color:var(--ink-3); font-weight:500; font-size:0.85em; }
.db-folder-in { max-width:180px; }

/* Map widget labels — the maps pages' frosted pill — and the highlight halo. */
.leaflet-tooltip.nhd-label { background:rgba(255,255,255,0.6); -webkit-backdrop-filter:blur(4px); backdrop-filter:blur(4px); border:none; box-shadow:0 1px 3px rgba(0,0,0,0.18); border-radius:var(--r-xs); padding:1px 6px; color:var(--ink); font-weight:600; font-size:12px; }
.leaflet-tooltip.nhd-label::before { display:none !important; }
.leaflet-tooltip.nhd-label--hl { background:rgba(203,134,22,0.92); color:#fff; font-size:13px; }
body.dark-mode .leaflet-tooltip.nhd-label { background:rgba(20,22,24,0.7); color:#e8e6df; }
body.dark-mode .leaflet-tooltip.nhd-label--hl { background:rgba(203,134,22,0.92); color:#fff; }
@keyframes nhd-halo { 0% { stroke-opacity:0.9; } 50% { stroke-opacity:0.35; } 100% { stroke-opacity:0.9; } }
.nhd-map-halo { animation: nhd-halo 2s ease-in-out infinite; }

/* The unit, on the LABEL line rather than hung off the reading (2026-08-31).

   It used to be a <small> tucked against the big number, where it was both
   illegible and the thing that made the card too wide on a phone. Moving it
   down to the label line was half the fix; the other half is that it must be
   READABLE there — the operator asked for it clearer, and a unit is not
   decoration, it is what makes "4.28" mean anything.

   So it inherits the label's size rather than shrinking, keeps the label's
   weight, and is NOT dimmed. Only the colour separates it from the name. */
.nhd-stat__u {
  margin-left: .35em;
  font-weight: inherit;
  color: var(--ink-2);
  white-space: nowrap;
}
/* Nothing may shrink it back: `small` inside these labels was the old shape. */
.nhd-stat__l small, .nhd-stats__l small { font-size: inherit; }

/* The picture-size slider in the builder's inspector (2026-09-01). The live
   percentage sits in the label, so a drag reads as a number rather than a
   guess. */
.db-field--range input[type=range] { width: 100%; }
.db-range__out {
  float: right;
  font-variant-numeric: tabular-nums;
  opacity: .8;
}
