/* Datum — the app's design tokens.
   =========================================================================

   Its own file, and not a block at the top of pages/main.css, because three
   documents need these values and only one of them is the app shell:

     * base/main.html, the shell that 48 templates extend;
     * base/advanced_analytics.html and base/ceden_export.html, which are
       standalone documents with their own <head> and their own reset.

   Those last two are the reason this file exists rather than living in the
   shell. Their stylesheets were folded onto the token names along with
   everybody else's, and a `var()` with nothing declaring it falls back to the
   property's initial value — transparent for a background, black for a colour.
   Both pages would have rendered as unpainted markup. Each of the three
   documents links this file for itself.

   Everything below moved here from pages/main.css unchanged. Nothing was
   retuned in the move.
   ========================================================================= */

/* =====================================================================
   Datum — the app's colour system.

   Named for the surveyed baseline every stage height in this product is
   measured from, and written to end an argument the CSS was having with
   itself. Before this there were two systems:

     * the health dashboard, which declares its own WARM near-neutral ramp
       (#1a1a19 / #232322 / #34342f — greige, faintly olive), and
     * everything else, which was Tailwind's COOL slate in light mode
       (#6b7280 / #e5e7eb / #f9fafb) over a bootstrap-ish blue-slate in dark
       (#2c3e50 body, #34495e card, #2d3748 rows, #1a202c fields).

   Those are not one system at two brightnesses, they are two systems, and
   walking from Health into Sensors changed the temperature of the whole
   application. Health won — it is the newer and the more considered of the
   two — so the DARK ramp below is health's values verbatim, and the LIGHT
   ramp is new, rebuilt warm to meet it. That is the change you can see:
   every surface in the app is a shade of paper now rather than a shade of
   slate.

   Every step was measured rather than eyeballed. On white: ink 17.4:1,
   ink-2 7.7:1, ink-3 5.1:1; each still clears 4.5:1 on --surface-2 (ink-3
   is 4.52:1 there). On the BAND, ink-3 does NOT: it measures 4.29:1 in
   light and 4.36:1 in dark, both under AA — so text on the band takes
   --ink-2 (7.7:1 on white, more on the band); see `.page-head p` in
   pages/main.css. --ink-3 exists at this value and not a step lighter
   because the tertiary grey it replaces (#9ca3af, 69 uses) measures 2.54:1
   and had been failing SC 1.4.3 at body size everywhere it was used.

   Page stylesheets should reach for these names and not for hex. The one
   exception is base.html's public shell (static/css/site.css), which is a
   different document with a different palette and is deliberately not
   wired to this file.
   ===================================================================== */
:root {
  /* --warn was the THIRD token today found referenced and never defined
     (after --warn-ink, which was a typo for --warning-ink and rendered at
     1.95:1, and --muted/--ok, which rendered at 4.32:1 on the watershed panel).
     open_map_nws_alerts.css uses `var(--warn, #b45309)` in five places and has
     always taken the fallback — a colour chosen for a light ground, used
     unchanged in dark. Defined here so both themes resolve it. */
  --warn:        #b45309;
  /* --muted and --ok were REFERENCED ten times across the tree and DEFINED
     NOWHERE, so every use silently took its inline fallback — and those
     fallbacks were chosen for a white ground. On the watershed answer's kraft
     --surface-2 that measures 4.32:1 in the LIGHT theme: below AA, on the panel
     whose entire argument is provenance, and invisible to a page-load sweep
     because that panel does not exist until somebody clicks the map.

     Precisely the shape of the --warn-ink bug found hours earlier the same day:
     a token name that never resolved, failing quietly rather than loudly.
     Defined here, in both themes, so this file is the one place that decides. */
  --muted:       #56534C;   /* AA on --paper, --surface and --surface-2 */
  --ok:          #2B7A52;
  /* --- surfaces, lightest to deepest ---------------------------------
     --paper is the page behind the card; --surface is the card; and
     --surface-2 is anything sunk INTO the card (a table header, a well, a
     card's own heading strip). --field is what a text input sits on:
     equal to the card in light mode, one step BELOW it in dark, because a
     field that is lighter than its card reads as raised and a field is a
     hole you type into. */
  --paper:       #FBFAF7;
  --surface:     #FFFFFF;
  --surface-2:   #F4F2EC;
  --field:       #FFFFFF;
  /* Two rules, not one. --line is the hairline between rows, where the
     eye only needs a hint of separation; --line-strong is for the edge of
     a control a reader has to aim at. Splitting them is why inputs stop
     dissolving into the card at the lighter value. */
  --line:        #E3E0D7;
  --line-strong: #CFCABD;

  /* --- ink ---------------------------------------------------------- */
  --ink:         #1A1A19;   /* headings, values, anything read closely */
  --ink-2:       #56534C;   /* body prose, labels                      */
  --ink-3:       #746E62;   /* meta, captions, units — still AA        */

  /* --- accent -------------------------------------------------------
     The one action colour, and it is the logo's blue rather than the
     Tailwind blue-600 (#2563eb) the app had drifted onto in 24 separate
     stylesheets. The nav bar above these pages has always been on brand;
     the pages under it were not, so every Create button sat a few pixels
     below a bar in a visibly colder blue.

     --accent is a FILL and --accent-ink is what is legible on top of it,
     because the two swap roles between themes: light mode fills with the
     deep blue and writes in white (5.77:1), dark mode fills with the light
     blue and writes in near-black (7.34:1). A dark-mode button kept at the
     deep blue would stand only 3.02:1 off the card behind it. */
  --accent:       #1F6D8F;
  --accent-hover: #175571;
  --accent-ink:   #FFFFFF;
  --accent-ring:  #2887b1;  /* focus rings — SC 1.4.11 wants 3:1, this is 3.02 */
  --link:         #1F6D8F;  /* prose links, which are text and not fills   */
  /* The quiet form of the accent: a chip or a callout that belongs to the
     action family but is not itself an action. It replaces an INDIGO
     (#e0e7ff / #3730a3 / #312e81 / #4338ca) that four stylesheets used for
     streak badges and logbook buttons, and which was a fourth hue in an
     app that already had three blues too many. 7.05:1. */
  --accent-tint:     #E6F0F5;
  --accent-tint-ink: #175571;

  /* --- the head band and its gauge rule -------------------------------
     The band was a pale cool blue (#e7f0f7) chosen when the app around it
     was cool. On a warm ramp it read as a foreign object, so it is kraft
     now — a deeper, warmer step of the same paper the rest of the page is
     made of. See the .page-head block far below for the rule along its
     bottom edge and what the two tick colours are for. */
  --band:        #F0ECE2;
  --band-line:   #E0D9C8;
  --tick:        #C9C0AC;
  --tick-major:  #B5AA93;

  /* --- provisional --------------------------------------------------
     The logo's gold, which until now was decoration with no job. It has
     one meaning in this app and only one: THIS NUMBER IS REAL BUT NOT YET
     BLESSED — a rule that is paused, a rating curve not yet approved, a
     reading that has not been through QA/QC. That distinction is one the
     people using this product already carry in their heads and the
     interface has never shown them.

     Note --warning below resolves to the same value. That is not a
     collision, it is the system agreeing with itself: a provisional
     reading and a warning are the same kind of "look at this before you
     rely on it", and health's warning hue was already in the gold family. */
  --provisional:      #8A5A0D;
  --provisional-tint: #FDF3E3;

  /* --- status -------------------------------------------------------
     health's five, unchanged — they are already validated and every one of
     them is paired with an icon and a word in the markup, so meaning never
     rides on colour alone.

     The -ink variants are the same five stepped dark enough to CARRY TEXT.
     The base values are for fills, dots, bars and borders, where SC 1.4.11
     asks 3:1; at body size on white --warning measures 1.83:1, so a page
     that writes a status word in the base colour is unreadable. Reach for
     -ink whenever the colour is the text itself. */
  --good:     #0ca30c;  --good-ink:     #137333;  --good-tint:     #EAF4EA;
  --warning:  #fab219;  --warning-ink:  #8A5A0D;  --warning-tint:  #FDF3E3;
  --serious:  #ec835a;  --serious-ink:  #A8481B;  --serious-tint:  #FBEEE7;
  --critical: #d03b3b;  --critical-ink: #A82323;  --critical-tint: #FAEBEB;
  --unknown:  #9aa0a6;  --unknown-ink:  #746E62;  --unknown-tint:  #F4F2EC;

  /* A sixth, and the only one that is not health's: "no judgement was
     possible". It exists because the alert lists had nowhere to put that
     answer and were putting it on --warning, next to "Paused", "Off" and
     "Not checked yet" — so a dead sensor and a deliberately disabled rule
     were the same colour, and the reader could not tell "look at this"
     from "we cannot say". Violet is the nearest hue that is not already
     spoken for by the five above and is not confusable with any of them
     at a 7px dot.

     Measured: --stale-ink on --stale-tint is 7.77:1 (light) and 8.79:1
     (dark); the base carries the dot and the border at 4.23:1 on paper,
     3.66:1 on its own tint and 3.83:1 on the dark tint — all over the
     3:1 SC 1.4.11 asks of a non-text mark. As with the five, the base
     does not flip in dark mode; only the ink and the tint do. */
  --stale:    #8B5CF6;  --stale-ink:    #5B21B6;  --stale-tint:    #F1ECFD;
}

/* The dark half. Every surface and ink value here is the health
   dashboard's own, so the page you arrive from and the page you arrive at
   are finally made of the same material. Only the things health has no
   opinion about — fields, the accent, the band, provisional — are new. */
.dark-mode {
  --warn:        #dfa64b;
  --muted:       #B9BCBE;
  --ok:          #6CC294;
  --paper:       #141413;
  --surface:     #1A1A19;
  --surface-2:   #232322;
  --field:       #141413;
  --line:        #34342F;
  --line-strong: #45453E;

  --ink:         #F2F2EF;
  --ink-2:       #B9BCBE;
  --ink-3:       #8B8F92;

  /* The fill does NOT flip, and that is deliberate. An earlier pass had
     dark mode fill with the light blue and write in near-black, which
     measures better in isolation (7.34:1 against 5.77:1) — but the fill
     and its label are set in different places. Fifty-four rules across the
     page stylesheets say `background: <the accent>; color: #fff`, having
     each been written when the accent was one fixed colour, and a fill
     that inverts under them turns every one of those labels into white on
     pale blue at 1.9:1. Holding the fill still is worth more than the
     extra ratio, and it keeps a Create button the same colour in both
     themes, which is what a brand colour is for.

     #1F6D8F stands 3.02:1 off the card behind it — over the 3:1 SC 1.4.11
     asks of a control's boundary — and carries white at 5.77:1. The hover
     lightens rather than darkens, because on a dark page "pressed" reads
     as brighter, and stops where white would drop under 4.5:1. */
  --accent:       #1F6D8F;
  --accent-hover: #25789C;
  --accent-ink:   #FFFFFF;
  --accent-ring:  #5FB3D9;
  /* A link is text, not a fill, so this one does flip. */
  --link:         #5FB3D9;
  --accent-tint:     #17313D;
  --accent-tint-ink: #8ACBE6;

  --band:        #2B2B28;
  --band-line:   #3A3A34;
  --tick:        #4A4A43;
  --tick-major:  #605F55;

  --provisional:      #E0A446;
  --provisional-tint: #2A2318;

  --good-ink:     #4BD14B;  --good-tint:     #16241A;
  --warning-ink:  #E0A446;  --warning-tint:  #2A2318;
  --serious-ink:  #F2996F;  --serious-tint:  #2E2019;
  --critical-ink: #F06A6A;  --critical-tint: #2E1B1B;
  --unknown-ink:  #8B8F92;  --unknown-tint:  #232322;
  --stale-ink:    #C4B5FD;  --stale-tint:    #241C36;
}

/* --- type ------------------------------------------------------------
   Three roles, and the third one is the reason this block exists.

   Sen is the brand face and now the only one: this file set the signed-in
   app in Nunito while static/css/site.css set the public site in Sen, so
   the product had two typefaces and the one people actually work in all
   day was not the one on the logo.

   --font-data is the role that is new. Every MEASURED value in this
   application — a stage height, a discharge, a turbidity, a timestamp, a
   storage key, a QARTOD flag code — gets a machine face with tabular
   figures, because those are numbers that get read down a column and
   compared, and a proportional font makes a column of them ragged. The
   health dashboard already does this for one field (.nh-status); the
   .measure utility further down is that idea generalised. It is a system
   stack on purpose: no webfont to download for it. */
:root {
  --font-ui:   'Sen', sans-serif;
  --font-data: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* --- corners ---------------------------------------------------------------
   Four steps, where there were fifteen. The app was carrying 1, 2, 3, 4, 5, 6,
   7, 8, 9, 10, 11, 12, 14, 20 and 999px — not as a scale anybody had chosen but
   as the accumulated first guess of whoever wrote each rule, which is why two
   cards side by side on the same page could round differently.

   The card step is health's 10px, because that is the radius on the page this
   refresh was measured against. --r-xs is for things too small to round
   properly: a code chip, a progress bar, a status dot. --r-pill is a shape
   rather than a radius — it means "capsule", and the number only has to be
   larger than half the element's height.
   -------------------------------------------------------------------------- */
:root {
  --r-xs:   4px;
  --r-sm:   8px;
  --r-md:  10px;
  --r-pill: 999px;
}

/* --- space ------------------------------------------------------------
   Seven steps on a 4px grid, and the honest report is that the six declared
   here before this had ZERO uses across 91 stylesheets. Padding, gap and
   margin were raw values everywhere, and the measurement is worse than the
   font sizes: 111 distinct spacing lengths in the CSS, and reading back the
   computed `gap` of every flex and grid container on nine signed-in pages
   gives, in order of how often they are painted:

     6px ×432   7px ×319   8px ×297   3px ×191   10px ×168   5px ×131

   Four of the top six are 5, 6, 7 and 8 — 1,179 gaps set to four values
   nobody can tell apart, sitting next to each other on the same page. That
   is not density tuning, it is a thousand independent guesses at "a small
   gap", and the visible result is that no two blocks in this application
   breathe alike.

   The grid is 4px because every step then lands on a whole device pixel at
   1x and a whole half-pixel at 2x, and because the two values the app leans
   on hardest — 6 and 10 — are each exactly one step off a grid value. They
   round to 8 and 12 and nothing is lost; a reader who could see the
   difference between a 6px gap and an 8px gap is a reader who has been
   handed a ruler.

   --space-0 is new at the bottom, because 141 declarations set 2px and there
   was no token under 4px for them: the inside of a chip, the gap between a
   status dot and its word, the offset on a focus ring. --space-7 is new at
   the top for the air BETWEEN sections of a page, which is the one distance
   that should be visibly larger than any distance inside a section — see the
   note on grouping in the elevation block below.
   -------------------------------------------------------------------------- */
:root {
  --space-0:  2px;
  --space-1:  4px;
  --space-2:  8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
}

/* --- type scale -------------------------------------------------------
   Six steps, and the number that matters is not six but the distance
   between them.

   What this replaces was five steps with a floor, which was right as far as
   it went and was almost entirely unused: --fs-0 had 37 uses and --fs-1
   through --fs-4 had none at all. The stylesheets carried 132 distinct
   font-size values in 1,160 declarations instead. Rendering nine signed-in
   pages and reading back the computed size of every element that owns text
   turns those 132 into 28 sizes actually painted, led by:

     14.4px ×1385   12.48 ×643   13.6 ×517   13.12 ×391   16 ×289
     11.2   ×246    12.8  ×240   11.52×186   15.2 ×153   14.08×140

   Nine of them inside four pixels. 0.72, 0.74, 0.76, 0.78, 0.80, 0.82, 0.85
   and 0.90rem are not eight sizes. They are one size that eight people
   guessed at, and nobody has ever seen the difference between 12.48px and
   12.8px. Each guess was somebody trying to say "this matters less than
   that" with the only dial they thought they had.

   The scale takes that dial away, and it can afford to because this file
   already ships the two dials that do the job better at small sizes: the ink
   ramp — three measured, contrast-safe steps of emphasis — and weight. Under
   about 16px a 5% size change is invisible and a colour change is not. So
   hierarchy INSIDE a dense row, table or card is made of ink and weight;
   SIZE is spent only on jumps a reader can actually see.

   No step is within 12% of the one below it, and the ratios widen going up
   (1.17, 1.14, 1.20, 1.25, 1.33), because a 2px difference reads as a
   decision at 12px and as a mistake at 24px. The two steps that moved from
   the old five moved onto evidence: --fs-3 was 1.25rem, but the app's real
   subheading cluster is 1.05/1.10/1.15rem (54 uses), and --fs-4 was 1.6rem,
   used twice, where the real page-title cluster is 1.35/1.40/1.50 (30 uses).
   --fs-5 is new, because the display numbers on the health tiles and the
   stat cards were reaching 1.9 and 2.2rem with no step to land on.
   ------------------------------------------------------------------------ */
:root {
  --fs-0: 0.75rem;   /* 12px — the floor. Caps micro-labels, chips, badges,
                        units, table meta. Nothing in this product is set
                        smaller; anything that wants to be is decoration
                        pretending to be text.                              */
  --fs-1: 0.875rem;  /* 14px — the workhorse. Table cells, form labels, row
                        metadata, nav, button text, everything in a dense
                        surface. This one step absorbs 0.72 through 0.85rem. */
  --fs-2: 1rem;      /* 16px — body. Running prose, form inputs, the name of
                        a thing in a list.                                  */
  --fs-3: 1.2rem;    /* 19px — the heading over a section or a card.        */
  --fs-4: 1.5rem;    /* 24px — the page title. One per page.                */
  --fs-5: 2rem;      /* 32px — display. The single number a stat tile
                        exists to show, and nothing else.                   */

  /* Four line-heights, because leading is the other half of density and the
     app had one setting for prose doing duty for everything. --lh-ui is the
     lever that matters: the sensor list sets six metadata lines at body
     leading, which is most of why one row is 190px tall to carry four short
     facts. Dense UI text is scanned, not read, and wants 1.4. */
  --lh-display: 1.1;   /* --fs-5 and --fs-4                                 */
  --lh-tight:   1.25;  /* headings, badges, chips                           */
  --lh-ui:      1.4;   /* table cells, metadata, labels, controls           */
  --lh-body:    1.5;   /* running text                                      */

  /* Two weights, named because they are already the app's answer — 224
     declarations at 600 and 142 at 700 against 23 at 500 — and naming them
     stops a third from appearing. With size collapsed onto six steps these
     carry more of the hierarchy than they used to, so they need to be the
     same two everywhere. */
  --w-med:    600;
  --w-strong: 700;

  /* The caps micro-label — SITE, LAST DATA, GROUP, RULES ALERTING — is the
     most repeated typographic object in this product, and it was tracked at
     0.03, 0.04, 0.05, 0.06 and 0.08em depending on who wrote the rule. Caps
     need tracking; they need one amount of it. */
  --track-caps: 0.05em;
}

/* --- controls -----------------------------------------------------------
   The reason the same control looks different on different pages.

   Measured on nine signed-in pages: 16 distinct rendered button heights and
   13 distinct input/select heights. The buttons cluster at 31, 32, 33, 34,
   35 and 36px — six heights inside five pixels — and the inputs cluster
   LOWER, at 22, 25, 27 and 29. That gap is the thing you can see without
   measuring anything: on every filter row in the app a search box, two
   selects and a Filter button sit on one line at four different heights,
   and the row reads as assembled rather than designed.

   One height for a control, then, and one for its compact form. 34px is the
   app's own most common button height and it clears the 24px minimum target
   size WCAG 2.2 asks for at AA. Chips get 24px, collapsing the 24/25/26/28
   the badges render at today.

   --cell-pad-y is the table-row lever. Three densities are in use — 10px,
   6px and 4px of vertical cell padding — and 1,185 of the cells are on the
   loosest. A table is a dense surface; 8px with --lh-ui is a comfortable row
   that is not a paragraph.

   --card-pad replaces five card paddings, two of which (16px 18px and
   18px 20px) are asymmetric by 2px in a way no reader can perceive as
   intentional and every reader receives as slightly-off. Cards are square:
   the same air on all four sides. */
:root {
  --ctl-h:      34px;
  --ctl-h-sm:   28px;
  --ctl-pad-x:  var(--space-3);
  --chip-h:     24px;
  --cell-pad-y: var(--space-2);
  --cell-pad-x: var(--space-3);
  --card-pad:   var(--space-4);
}

/* --- elevation ----------------------------------------------------------
   Still two shadows, and the count they are up against has been measured:
   76 distinct box-shadow values across the stylesheets, against ONE use of
   --shadow-1 and none of --shadow-2. Twenty-nine of the 76 are one-offs
   written once and never reused, several of them tinted with a brand blue
   or a Tailwind slate that no longer exists in this palette.

   The app is flat on purpose — a card is a bordered surface, not a floating
   object — so level 1 is barely-there lift and level 2 is for things
   genuinely above the page (menus, popovers, modals, the dock). Dark mode
   deepens both: on a dark ground a pale shadow does nothing, and depth has
   to come from darkness.

   The point that the 76 shadows were missing, and that matters more than
   their number: BORDER, FILL, RADIUS and SHADOW each independently say
   "this is a separate object", and spending all four on every block spends
   the whole vocabulary on the first thing a reader looks at. A page whose
   sections are all cards has no sections — it has a stack of equals. The
   grouping ladder in this product, weakest to strongest, is:

     1. space alone            — related rows, the default
     2. a hairline (--line)    — a divider inside one object
     3. --surface-2 fill       — something sunk into a card
     4. border + --surface     — a card: a genuinely separate object
     5. + --shadow-2           — a layer floating over the page

   Rung 1 is free and underused; rungs 4 and 5 are expensive and everywhere.
   Most blocks that are cards today want rung 1 with --space-7 between them
   and their heading doing the work.
   ------------------------------------------------------------------------ */
:root {
  --shadow-1: 0 1px 2px rgba(26, 26, 25, 0.06);
  --shadow-2: 0 10px 26px rgba(26, 26, 25, 0.14);
}
.dark-mode {
  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.35);
  --shadow-2: 0 10px 26px rgba(0, 0, 0, 0.5);
}

/* --- brand gold ---------------------------------------------------------
   The logo's gold as a MARK colour — the assistant's heron, the public
   nav's focus rings, chart accents. It is not a text colour (#cb8616 is
   1.83:1-adjacent territory on white; --provisional / --warning-ink are
   the gold that carries words) and not a second action colour. Named here
   because 21 rules across map.css, assistant.css and the analytics sheets
   spell the hex out, and a brand hue that lives in twenty-one places is
   twenty-one chances to drift. */
:root {
  --brand-gold: #cb8616;
}

/* --- measure -------------------------------------------------------------
   Two page widths for the whole product, and only two.

   Every document here is one of two things. An ORDINARY page — a form, a
   list, a settings panel, a docs page, a thread — where the content is
   read, and a WIDE page — analytics, the maps, the QA/QC and rating
   workbenches, the health dashboard, the import screens, a dashboard grid —
   where the content is a plot or a table that gets better the more screen
   you give it. The difference between the two tiers is meant to be obvious
   at a glance: a reader should be able to say "this one is a workbench"
   from the shape of the page alone.

   Before this there was effectively one tier. The public shell had 1300 and
   min(2100px, 95vw) and that pair worked, which is why analytics and the
   maps are the pages people like. The app shell had 1100 for BOTH tiers,
   because the wide one had been collapsed onto the ordinary one to stop the
   two workbenches and the health dashboard sitting a hair — 100px — wider
   than everything else for no reason a reader could name. Collapsing it was
   right: 100px is noise, not a decision. The answer is not to reinstate the
   noise but to make the wide tier wide enough to read as a decision, and to
   give the app the same honest pair the public shell already had.

   --nh-page-measure is 1400px. On a 1280 laptop that is the screen minus a
   gutter; on 1440 it is very nearly the whole screen; on 1920 it is 73% of
   the width where 1100 was 57%; on a 2560 monitor it deliberately stops,
   because a settings form 2000px wide is worse to use, not better. It is
   also what the public home page's sections are set to, so the marketing
   page and the application it advertises are the same width.

   --nh-page-measure-wide is min(2100px, 95vw) — the public shell's existing
   wide value, unchanged, because that is the one the operator already
   likes. A percentage rather than a pixel gutter: a wide page's whole job
   is to fill the screen it is handed.

   --nh-read-measure is NOT a third page width. It is the length of a LINE
   of running text, and it exists so that widening a shell can never widen
   prose. 78ch is about 80 characters; it is the cap the community board's
   post bodies and comment forms were already written to, named here so the
   1400px shells cannot outrun it.

   --nh-page-gutter is side air for a shell whose children do NOT pad
   themselves. The app shell's cards sit flush against .main-layout's edge,
   so without it a card would touch the viewport on a 1280 laptop; the
   public shell needs none, because every block inside .page-content brings
   its own padding.
   ------------------------------------------------------------------------ */
:root {
  --nh-page-measure:      1400px;
  --nh-page-measure-wide: min(2100px, 95vw);
  --nh-read-measure:      78ch;
  --nh-page-gutter:       24px;
}

/* --- charts -------------------------------------------------------------
   The plots are this product's actual content and they are the least
   designed thing in it. Read back off a rendered analytics page, the Plotly
   layout in dark mode is:

     paper_bgcolor  #1e1e1e      (--surface in dark is #1A1A19)
     font.color     #e2e8f0      (--ink in dark is #F2F2EF)
     font.family    "Open Sans", verdana, arial
     line.color     #58a6ff
     gridcolor      rgba(255,255,255,0.15)  on BOTH axes

   Every one of those is off-system. #1e1e1e is a cool grey inside a warm
   near-black card, which is the exact two-temperatures fault the colour
   block at the top of this file was written to end — it just moved from the
   page into the plot. #e2e8f0 is Tailwind slate-200, a palette this app no
   longer uses. Open Sans is a typeface that appears nowhere else in the
   product, so every axis label in every chart is set in a face the reader
   sees only there. #58a6ff is GitHub's blue, not the brand's.

   Charts are typography and they deserve the same care: the values below
   exist so the plotting code can read them off the document with
   getComputedStyle rather than carrying a second palette. They flip with
   the theme like everything else here.

   --chart-grid is deliberately much fainter than --line, and there is one
   grid rather than two: a time series is read horizontally against its
   value axis, so the horizontal rules earn their ink and the vertical ones
   are usually just noise laid over the data. --chart-fill is the area under
   a single series at the opacity where it reads as weight rather than as a
   second colour, and --chart-point is for the emphasised LAST value, which
   is the one number on a monitoring chart anybody came to see.
   ------------------------------------------------------------------------ */
:root {
  --chart-paper: var(--surface);
  --chart-ink:   var(--ink-2);
  --chart-title: var(--ink);
  --chart-grid:  rgba(26, 26, 25, 0.10);
  --chart-axis:  var(--line-strong);
  --chart-line:  #1F6D8F;
  --chart-fill:  rgba(31, 109, 143, 0.12);
  --chart-point: #175571;
  --chart-font:  'Sen', sans-serif;
}
.dark-mode {
  --chart-grid:  rgba(242, 242, 239, 0.10);
  --chart-line:  #5FB3D9;
  --chart-fill:  rgba(95, 179, 217, 0.14);
  --chart-point: #8ACBE6;
}

/* --- utilities ----------------------------------------------------------
   Two classes, and the first one is a debt this file has been carrying: the
   type block above has promised ".measure utility further down" since it
   was written, and there was no such utility. Anything that names a
   measured quantity — a stage height, a discharge, a timestamp, a storage
   key, a QARTOD flag code — takes .measure and gets the machine face.

   .tnum is the same idea without the face change, for the many places where
   a number is set in Sen and only needs its digits to stop dancing:
   tabular figures give every digit the width of a zero, so a column of them
   aligns and a value that ticks up in place stops shifting sideways.

   They are separate on purpose, and the reason is written on
   `.nh-tile .v` in health_dashboard.css: at display size tabular figures
   make "7 of 47" read loose and gappy, because the zero is the widest digit
   and every 1 then carries a zero's worth of air. Tabular figures are for
   COLUMNS that must align vertically and for values that update in place.
   A single large number wants proportional digits. Do not put .tnum on a
   headline number.
   ------------------------------------------------------------------------ */
.measure {
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  font-size: 0.94em;   /* the mono stack runs large next to Sen at the same
                          nominal size; this is the optical match already in
                          use at .stats-num and .last-checked */
  letter-spacing: 0;
}
.tnum { font-variant-numeric: tabular-nums; }
