/* Inter, as the design law requires — SERVED FROM THIS ORIGIN (UX-2).

   The history, because both previous shapes were wrong for reasons worth not
   repeating. It began as an `@import` on this line, and an @import is a
   request the browser cannot discover until the sheet containing it has
   arrived: measured cold at 45 ms of round-trip, tokens.css finished at 182 ms
   and fonts.googleapis.com was requested at 182.4, a chain first paint waited
   at the end of. UX-1 moved it to a `<link>` in base.html, which fixed the
   chain and left the DEPENDENCY: a render-blocking stylesheet on a third-party
   origin means an unreachable fonts.googleapis.com is a product that shows
   nothing until the connection gives up.

   The seven files below are that stylesheet's own woff2 subsets, vendored to
   web/static/fonts/ (Inter is OFL-1.1; LICENSE.txt ships beside them). One
   request path, no DNS to a stranger, no third-party origin on any page.

   SEVEN FILES AND NOT TWENTY-EIGHT. The four weights the product uses —
   400/500/600/700 — resolve to the SAME file per subset: Inter v20 is served
   as a VARIABLE font with a weight axis, which is why `font-weight` below is
   the range `100 900` rather than four separate faces. Google's own CSS emits
   28 @font-face blocks over 7 unique URLs; this is those 7.

   ALL SEVEN SUBSETS, and `unicode-range` is why that costs nothing. A browser
   downloads only the subsets a rendered glyph needs, so an English page fetches
   `latin` (48 KB) and nothing else. Cyrillic and Greek are here because this is
   a CS2 product and player names are not all ASCII — UX-1 named that as the
   reason the vendoring decision was a real one.

   `font-display: swap` is deliberate: the system stack below paints
   immediately and Inter replaces it when it arrives. No invisible text, ever.

   `?v=20` on each URL is the version this file's NAME already carries (Google's
   `v20` directory), so the bytes behind that URL cannot change without the URL
   changing — which is exactly the condition web/assets.py's immutable tier
   requires. The preload in base.html spells the SAME url, version and all;
   spelling either one differently would fetch the file twice, and
   tests/test_nav_feel.py asserts they agree. */

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/inter-v20-latin.woff2?v=20") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/inter-v20-latin-ext.woff2?v=20") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/inter-v20-cyrillic.woff2?v=20") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/inter-v20-cyrillic-ext.woff2?v=20") format("woff2");
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F,
    U+FE2E-FE2F;
}

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/inter-v20-greek.woff2?v=20") format("woff2");
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1,
    U+03A3-03FF;
}

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/inter-v20-greek-ext.woff2?v=20") format("woff2");
  unicode-range: U+1F00-1FFF;
}

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/inter-v20-vietnamese.woff2?v=20") format("woff2");
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
    U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
    U+1EA0-1EF9, U+20AB;
}

/* =====================================================================
   CS Anchor design tokens — Nocturne base + League Ledger overrides.

   THIS IS THE ONLY FILE IN THE REPO ALLOWED TO CONTAIN A COLOR LITERAL.
   `components.css` is token-only, and tests/test_styleguide_render.py
   enforces that structurally (with a synthetic-violation probe), because
   the fastest way to lose a design system is one hard-coded hex in a
   component that nobody notices until the theme changes.

   Order matters and is the design law (docs/PHASE3-DESIGN-PLAN.md):

     1. NOCTURNE BASE  — the grammar. OKLCH tonal ramps on one shared
        lightness scale, Inter, 0.7x density spacing, 8px radii, shadows
        tuned to a dark ground.  Source: the nocturne design kit's styles.css.
        (Written without the kit's globbed directory name on purpose: that
        path contains a comment-close sequence, and pasting it here silently
        ended this comment and swallowed the whole token block. Rule G exists
        because the same accident once removed a stylesheet in production.)
     2. LEAGUE LEDGER  — the product's `:root` overrides. **LL WINS** on
        every conflict: the accent is the teal #35c9b1, not Nocturne's
        blurple, and the ground drops to #0d0f14 with the panel family
        above it.  Source: "CS Anchor League Ledger.PARTIAL.dc.html"
     3. DERIVED        — tokens this codebase adds because the reference
        expressed them as inline literals (accent washes, row rules,
        hover tints). Named for their ROLE, valued from the reference.

   Nocturne's own rules, kept because they are what makes it Nocturne:
   no pure black or pure white (shadow ink is the documented exception),
   chroma stays low outside the accent, headings never bolder than 500,
   freestanding rules fade to transparent over 48px an end, and elevation
   on a dark ground is an edge plus ambient darkness rather than a stack
   of shadows.
   ===================================================================== */

:root {
  /* The theme is single, and it is dark (UX-1). Declared here for the CSSOM
     and in base.html as a `<meta name="color-scheme">` for the frames that
     land BEFORE this file does — the meta is what darkens the canvas, the
     scrollbars and the inter-document flash; this is what a
     `getComputedStyle(document.documentElement).colorScheme` read reports, and
     it read `normal` (i.e. light) until UX-1. Not a token: it is a declaration
     on the root element that happens to live in the file that owns the root. */
  color-scheme: dark;

  /* ------------------------------------------------------------------
     1. NOCTURNE BASE
     ------------------------------------------------------------------ */

  --color-text: #e9e9ed;
  --color-divider: color-mix(in srgb, #e9e9ed 16%, transparent);

  /* Neutral ramp — surfaces, borders, muted text. Same perceptual step
     as every other ramp, so 800 of one matches 800 of another. */
  --color-neutral-100: #f3f5fe;
  --color-neutral-200: #e4e7f5;
  --color-neutral-300: #cfd3e5;
  --color-neutral-400: #b2b6ca;
  --color-neutral-500: #9397ab;
  --color-neutral-600: #75798c;
  --color-neutral-700: #595d6c;
  --color-neutral-800: #3f424d;
  --color-neutral-900: #292b31;

  /* Nocturne's second-accent stand-in. Kept so both sets resolve; the
     system treats accent and accent-2 as ONE role (it is a mono scheme). */
  --color-accent-2-100: #f5f4ff;
  --color-accent-2-200: #e7e5fe;
  --color-accent-2-300: #d2cefd;
  --color-accent-2-400: #b5afe8;
  --color-accent-2-500: #9690c9;
  --color-accent-2-600: #7972a9;
  --color-accent-2-700: #5c5783;
  --color-accent-2-800: #423e5d;
  --color-accent-2-900: #2b293a;

  --font-heading: "Inter", system-ui, sans-serif;
  --font-heading-weight: 500;
  --font-body: "Inter", system-ui, sans-serif;

  /* Density 0.70x — this system is dense on purpose. */
  --space-1: 2.8px;
  --space-2: 5.6px;
  --space-3: 8.4px;
  --space-4: 11.2px;
  --space-6: 16.8px;
  --space-8: 22.4px;

  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 14px;

  /* Elevation: a hairline edge plus ambient darkness, never a stack. */
  --shadow-sm: 0 0 0 1px #3f424d;
  --shadow-md: 0 0 0 1px #595d6c, 0 6px 18px rgba(0, 0, 0, 0.55);
  --shadow-lg: 0 0 0 1px #9397ab, 0 16px 40px rgba(0, 0, 0, 0.65);

  /* ------------------------------------------------------------------
     2. LEAGUE LEDGER OVERRIDES — these WIN over the Nocturne values above
     ------------------------------------------------------------------ */

  --color-bg: #0d0f14;
  --color-surface: #15181f;

  /* The accent moves from Nocturne's blurple to the product's teal. */
  --color-accent: #35c9b1;
  --color-accent-100: #eafaf7;
  --color-accent-200: #cdf2eb;
  --color-accent-300: #a4e8dc;
  --color-accent-400: #6ddcc9;
  --color-accent-500: #35c9b1;
  --color-accent-600: #23a894;
  --color-accent-700: #1a8375;
  --color-accent-800: #16443f;
  --color-accent-900: #122b29;

  /* Panel family — the three surfaces the product actually paints on. */
  --panel: #14171e;
  --panel-2: #191d26;
  --panel-3: #1f2430;

  --line: rgba(233, 233, 237, 0.1);
  --line-2: rgba(233, 233, 237, 0.16);
  /* The two muted TEXT steps. Raised at R6 (2026-08-12 review §5): the note
     that used to stand here argued --dim-2 could stay at 2.8:1 because it was
     "interface chrome". The review measured what it actually colours — 330-odd
     rules, and the list is meta lines, SAMPLE COUNTS, section captions and
     absent-dashes. A sample count is the number that tells the operator
     whether to believe the number beside it; that is body copy by function
     whatever the layout calls it. So the role argument lost and both steps
     moved up.

     MEASURED, foreground #e9e9ed composited on each ground the product paints:

       token     alpha   page #0d0f14   panel #14171e   -2 #191d26   -3 #1f2430
       --dim-2   0.53        5.03            4.95          4.83         4.64
       --dim     0.68        7.64            7.36          7.08         6.69
       (text)    1.00       15.83           14.81         13.93        12.82

     Every step clears AA (4.5:1) on every ground, including --panel-3, which
     is the lightest thing anything is written on. The old pair was 5.18/2.74
     on the page ground.

     --dim MOVED TOO, and that is the point rather than collateral: --dim-2 at
     AA sits 0.19 alpha from where --dim was, and two steps that close are one
     step. The hierarchy is kept in COMPOSITED LUMINANCE, which is what an eye
     reads: 0.240 -> 0.381 -> 0.817, so the quiet-to-mid gap (0.141) is very
     slightly WIDER than it was before (0.135) and the mid-to-ink gap is still
     the largest of the three. Three tones, still ordered, all readable.

     Marks are NOT text and did not move — see --mark / --mark-quiet below. */
  --dim: rgba(233, 233, 237, 0.68);
  --dim-2: rgba(233, 233, 237, 0.53);

  /* The two muted MARK steps: the old --dim / --dim-2 values, verbatim, for
     the ~30 places that spent a text token on something with no glyph in it —
     a 2px bar fill, a 5px state dot, a legend swatch, a chart's baseline
     series, a dashed rule through an axis.

     They are separate tokens rather than a shared one because the two roles
     have different FLOORS and were only ever equal by accident. Text has to
     clear 4.5:1; a non-text mark has to clear 3:1, and several of these marks
     are deliberately BELOW their neighbours because that is what they mean —
     `[data-enabled="0"]`, `[data-synced="0"]`, `.state-dot-no-output`,
     `.pp-seg-other`. Dragging an off-state up with the text sweep would have
     made "nothing is reporting here" the loudest dot in the row.

     MEASURED as non-text marks on --panel: --mark 5.09:1, --mark-quiet
     2.78:1. The first clears the 3:1 bar for a graphical object outright. The
     second is a FILL under a labelled key rather than an object carrying
     meaning alone — every one of its sites is a bar whose value is also
     printed, a swatch whose word is beside it, or an off-state whose row says
     so in text — so 2.78 is the reference's own weight for a shape the reader
     never has to decode, not a contrast failure hiding behind a rename. */
  --mark: rgba(233, 233, 237, 0.54);
  --mark-quiet: rgba(233, 233, 237, 0.34);

  /* Semantic stat colors. T/CT are sides, win/loss are outcomes; they are
     four distinct roles and must never be swapped for "a warm one". */
  --t: #f0a13a;
  --ct: #4f9df7;
  --win: #35c9b1;
  --loss: #e0574f;

  /* The GRENADE vocabulary (UT-1, 2026-08-09, from the operator's Utility
     export). Four items, four marks, four hues — an ADDITIVE token group:
     nothing above changes, and no existing surface reads these.

     They are their own roles rather than aliases of the four above, even
     where a value repeats. `--nade-he` is the loss red and `--nade-fire` is
     the T amber, and both would be WRONG as `var(--loss)` / `var(--t)`: a
     molotov is not a T-side thing and an HE grenade is not a defeat. The
     Utility page draws all four in one stacked column, so the day either
     semantic hue is retuned, the grenade stack must not move with it.

     Smoke and flash have no counterpart above at all: smoke is a cool grey
     that reads as vapour and flash is the export's warm bone. MEASURED
     against --panel (#14171e), as glyph strokes and bar fills: smoke 6.41:1,
     flash 14.48:1, HE 4.82:1, fire 8.42:1. All four clear the 3:1 bar for
     non-text marks and three of the four clear AA for text as well. HE at
     4.82 is the floor, and it is the same figure --loss carries (the note
     further down records why THAT one is spent on a border rather than a
     fill). Here the hue is only ever a mark — a glyph stroke or a filled
     segment — and never a numeral, so 4.82 against a 3:1 requirement is
     headroom rather than a near miss. It matters at all because on the spend
     curve the hue IS the label. */
  --nade-smoke: #8f9bb3;
  --nade-flash: #ece7d5;
  --nade-he: #e0574f;
  --nade-fire: #f0a13a;

  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;

  /* ------------------------------------------------------------------
     3. DERIVED — role tokens for values the reference inlined
     ------------------------------------------------------------------ */

  /* The page ground: two soft radial blooms over the base, fixed so the
     light does not scroll with the content. */
  --page-bloom-1: #1a1f2c;
  --page-bloom-2: rgba(18, 36, 35, 0.5);
  --page-bg-fade: rgba(13, 15, 20, 0);

  /* Accent washes — the accent as a tint, never as a flood (Nocturne's
     "Don't": no saturated field larger than a mark or a line). */
  --accent-wash: rgba(53, 201, 177, 0.07);
  --accent-wash-2: rgba(53, 201, 177, 0.1);
  --accent-wash-3: rgba(53, 201, 177, 0.13);
  --accent-wash-4: rgba(53, 201, 177, 0.16);
  --accent-ring: rgba(53, 201, 177, 0.34);
  --accent-ring-soft: rgba(53, 201, 177, 0.28);
  --accent-chip-btn: rgba(53, 201, 177, 0.14);

  /* T-colored warning wash — the "your filter moved but these numbers
     did not" pill. Amber because it is a caution, not a loss. */
  --t-wash: rgba(240, 161, 58, 0.12);
  --t-ring: rgba(240, 161, 58, 0.35);
  /* The CT side's mirror of the pair above. The side filter is a segmented
     control whose selected segment is a FILLED tint (the reference's own
     active-mode treatment); T already had its tint and CT did not, so the two
     halves of one control could not be given the same weight. Same alpha as
     `--t-wash` on purpose — T and CT are peers, and an asymmetric tint would
     read as "one of these is the default". */
  --ct-wash: rgba(79, 157, 247, 0.12);
  --loss-bar: rgba(224, 87, 79, 0.66);
  /* The loss hue as a 1px edge, the mirror of --accent-ring. Phase 4's series
     result badge is outlined rather than tinted: MEASURED, `--loss` reads
     4.8:1 on the panel ground and drops below AA the moment anything lightens
     what is behind it, so the badge keeps the panel and spends the hue on its
     border. */
  --loss-ring: rgba(224, 87, 79, 0.35);
  /* The loss hue as INK, for the case the note above predicted: text in the
     loss tone sitting on a ground lighter than the panel. `--loss` is 4.82:1 on
     `--panel` and 4.17:1 on `--panel-3` — under AA — so the entry module's
     avatar tile, which IS a `--panel-3` chip inside a panel, painted its 10px
     initials at 4.17.

     Same hue (3.5° against 3.3°), lifted in lightness alone, and MEASURED on
     every ground: 4.83:1 on `--panel-3`, 5.25 on `--panel-2`, 5.58 on
     `--panel`, 5.97 on the page. It is deliberately NOT a replacement for
     `--loss`: a fill, a border and a 34px figure all read fine at the shipped
     hue, and swapping the whole tone would move ~40 sites to fix one. This is
     the ink for small text on a lightened ground, and the rule that uses it
     names that ground. */
  --loss-ink: #e8675f;

  /* Neutral tints keyed off the text color, per Nocturne's guidance to
     tint from the foreground rather than reach for a gray. */
  --row-rule: rgba(233, 233, 237, 0.08);
  --hover-tint: rgba(233, 233, 237, 0.05);
  --hover-tint-2: rgba(233, 233, 237, 0.07);
  --track: rgba(233, 233, 237, 0.07);
  --track-2: rgba(233, 233, 237, 0.06);
  /* A FILLED neutral, not a track: the part of a two-sided bar that both
     columns have in common. It has to read as a drawn segment beside a
     coloured one, so it sits well above --track, which is the empty ground a
     coloured fill runs over. The value is the game-page export's own. */
  --fill-neutral: rgba(233, 233, 237, 0.22);
  /* The median mark on a bar: a hairline the eye reads as a reference, not
     as a second fill. Brighter than --fill-neutral because it is 1.5px wide
     and has to survive being that thin. */
  --fill-tick: rgba(233, 233, 237, 0.55);
  --scrollbar-thumb: #2b303c;

  /* Comparison tones — GOOD / BAD / LEVEL against a comparison set.
     Introduced by the Team stats export (2026-08-07), which is the scoped
     win for that page and defines its own trio: it separates "we are ahead
     of the comparison" from "we won this match", and the product already
     spends --win/--loss on the second. They are not interchangeable: --win
     is the accent teal, which on a page whose accent already means "you are
     here" would read as chrome rather than as a verdict.

     MEASURED on --panel (#14171e): --stat-good 9.4:1, --stat-bad 6.6:1,
     --stat-neutral 14.9:1 — all clear of AA for body text, which matters
     because these colour NUMBERS, and a number is the smallest thing on the
     page that must not be ambiguous. (--loss, by contrast, reads 4.8:1 and
     is why the series badge outlines instead of tinting.) */
  --stat-good: #34d399;
  --stat-bad: #f87171;
  --stat-neutral: #e5e7eb;
  --stat-good-wash: rgba(52, 211, 153, 0.13);
  --stat-good-ring: rgba(52, 211, 153, 0.34);
  --stat-bad-wash: rgba(248, 113, 113, 0.13);
  --stat-bad-ring: rgba(248, 113, 113, 0.34);

  /* Type scale — the reference's real sizes, named. Half-pixel steps are
     deliberate: at 0.7x density a whole pixel is a big jump. */
  --fs-micro: 9px;
  --fs-micro-2: 9.5px;
  --fs-label: 10px;
  --fs-label-2: 10.5px;
  --fs-xs: 11px;
  --fs-xs-2: 11.5px;
  --fs-sm: 12px;
  --fs-sm-2: 12.5px;
  --fs-md: 13px;
  --fs-md-2: 13.5px;
  --fs-base: 14px;
  --fs-body: 15px;
  --fs-lg: 17px;
  --fs-xl: 19px;
  --fs-2xl: 22px;
  --fs-kpi: 29px;
  /* An ALIAS, not a second 29px (R6). Two tokens holding the same literal is a
     coincidence a stylesheet cannot tell from an intention: the next person to
     retune the KPI step has no way to know whether the page titles were meant
     to follow it, so they either move both and hope or move one and leave a
     silent drift. Spelling the dependency makes the answer readable — page
     titles ride the KPI step today. To break them apart, give this its own
     literal back; that edit is then a decision on the record rather than a
     divergence discovered later in a screenshot. */
  --fs-h1: var(--fs-kpi);
  --fs-display: 34px;

  /* Spacing — the product rhythm, alongside Nocturne's density scale. */
  --sp-05: 2px;
  --sp-1: 4px;
  --sp-1-5: 6px;
  --sp-2: 8px;
  --sp-2-5: 10px;
  --sp-3: 12px;
  --sp-3-5: 14px;
  --sp-4: 16px;
  --sp-4-5: 18px;
  --sp-5: 20px;
  --sp-6: 26px;
  --sp-8: 34px;

  /* Layout */
  --side-width: 216px;
  --main-max: 1320px;
  --rail-width: 178px;
  --rule-fade: 48px; /* Nocturne: rules fade over 48px an end */

  /* Motion — one short, functional duration. This is a data surface;
     movement here is feedback, not choreography. */
  --t-fast: 0.13s;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);

  /* ENTRANCE MOTION (UX-2). The operator asked for "natural, not annoying",
     and the difference between those two is entirely in these five numbers,
     so they are tokens rather than literals scattered across three page
     sheets — a page that animates 60ms slower than its neighbour reads as a
     slower page, not as a different one.

     `--t-enter` is 340ms because a bar that fills faster than ~300ms is not
     perceived as filling at all (it reads as a paint), and one slower than
     ~400ms is something you WAIT for. `--ease-out` decelerates hard and never
     overshoots: a bar whose length is a NUMBER must not bounce past the number
     and come back, which is what a spring curve does and why none is used
     here.

     `--stagger` is the gap between one row's start and the next. 32ms over a
     ten-row panel is 320ms of sweep — the eye reads it as one gesture rather
     than as ten events. `--stagger-max` caps the accumulated delay so a
     forty-row table's last row is not still waiting to start a second and a
     half after the page arrived; past that cap everything remaining moves
     together.

     `--t-reveal` is for text and panels, which fade and lift rather than
     fill, and is deliberately SHORTER than the bars: the numbers are the
     content and they must be readable before the decoration finishes. */
  --t-enter: 340ms;
  --t-reveal: 220ms;
  --ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);
  --stagger: 32ms;
  --stagger-max: 420ms;
}

/* ══ WIDE VIEWPORTS — the width system above the design width ══════════
   The reference is authoritative AT its own width and says nothing above
   it. Its `.main` carries `max-width:1320px` as an inline style and the
   only media query in the whole 1.1MB export is `(max-width:900px)`, so
   1320 is a design width, never a ceiling somebody chose for a 2560px
   monitor. With the 216px rail beside it the pinned composition is
   COMPLETE at 216 + 1320 = 1536px of viewport, and every pixel past that
   was landing in a void: measured on this corpus at 2560px, `.main` sat
   at x=216 and ended at 1536, leaving 1024px — 40% of the screen — of
   nothing, all of it on the right, because a capped grid item with no
   auto margins is start-aligned.

   1600px is therefore the first breakpoint that can exist without
   touching the pinned design: it is above the 1536 at which the design
   is fully drawn, and 160px clear of 1440 so the desk width nothing here
   may change is nowhere near the boundary.

   The token becomes a bigger LENGTH rather than a viewport expression on
   purpose. `--main-max` has four consumers (`.main`, `.sharedbar-in`,
   `.sharedbar-note`, `.adm`) whose containing blocks are three different
   boxes; a `calc(100% - …)` would resolve against each of them and pull
   the share view's bar out of line with its own page, and would inset
   `.adm` a second time inside the `.main` that already caps it. One
   length moves all four together, which is what a width SYSTEM means.

   1800 is measured, not chosen. Between 1536 and 2016 the column is
   fully fluid (it fills the track, and `.main`'s own 34px padding keeps
   the content off the viewport edge exactly as it does at 1000px). Past
   2016 it holds and the leftover becomes a symmetric gutter — 272px a
   side at 2560, from `.main { margin-inline: auto }` in components.css.
   Why the ceiling is here and not higher, from renders at 2560 of the
   same page: at 1800 `/numbers` draws its filter bar in 3 rows instead
   of 4 and its cost grid in 537px cells; at 2200 the same cells reach
   680px around values of four or five characters, and the endgame
   cross-tab spaces a five-character number across 270px. That is where
   more width stops buying information and starts buying distance. */
@media (min-width: 1600px) {
  :root {
    --main-max: 1800px;
  }
}
