/* ============================================================================
 * theme/gm/hellenic-gm.css — the GREEK-MYTHOLOGY (gm) BRIDGE LAYER.
 *
 * Load LAST, after theme/anim.css. Removing the single <link> that loads this
 * file removes every effect it has; nothing else in the console references it,
 * and no page markup depends on it.
 *
 *   styles.css → theme/hellenic.css → theme/odyssey-login.css → theme/anim.css
 *                                                             → THIS FILE
 *
 * 🔴 IT EDITS NOTHING. It declares new `--gm-*` variables, re-points ONE
 * existing variable (--hel-display), and adds opt-in `.gm-*` classes. Every hex,
 * every contrast figure and every status token in theme/hellenic.css is
 * bit-identical with this file loaded or absent.
 *
 * 🔴 PROVENANCE. §1–§5 are the token bridge produced and measured by the
 * preceding step of this workflow, moved here from theme/hellenic-gm.css. The
 * colour analysis is unchanged and still holds — no hex moved. What DID change
 * on the move is stated where it occurs: the font path (§1) and the request
 * budget (below). §6 (the sprite tiers) and §7 (the nav webfont) are additions.
 *
 * 🔴 §7 IS GENERATED, §1–§6 ARE NOT. Everything between the two GM-BUILD
 * sentinels at the end of this file is build/gm-build.py's output and is rewritten
 * in place on every build; everything above them is authored prose the builder
 * preserves byte for byte. Edit the builder, not the block.
 *
 * ---------------------------------------------------------------------------
 * 🔴 WHY THE OBVIOUS DIRECTION IS A DEFECT.
 * The supplied sheet (Documents/Codex/2026-07-28/ge/outputs/
 * greek-mythology-web-theme/theme.css) is a SINGLE DARK PALETTE with no light
 * counterpart. Binding the console's tokens to the --gm-* literals (--fg:
 * #f3efe4, --panel: #2a2620, …) reproduces exactly the failure fixed on
 * 2026-07-28, when the firmware syntax ramp measured 1.44:1 in the marble theme.
 * Worst cell over the console's five real surfaces (--bg, --bg-elev, --panel,
 * --panel-2, --panel-3):
 *
 *                             NIGHT worst      MARBLE worst
 *   --gm-marble    #f3efe4      11.81  ok        1.02  FAIL
 *   --gm-limestone #d6c6a8       8.08  ok        1.24  FAIL
 *   --gm-bronze    #a46f2a       3.16  FAIL      3.17  FAIL
 *   --gm-gold      #d7a63c       6.08  ok        1.64  FAIL
 *   --gm-aegean    #174f74       1.55  FAIL      6.43  ok
 *   --gm-olive     #526d35       2.32  FAIL      4.30  FAIL
 *   --gm-crimson   #8f2f24       1.68  FAIL      5.95  ok
 *   --gm-charcoal  #1c1a17  as a ground vs marble --fg #1c1712 = 1.02 FAIL
 *   --gm-panel     #2a2620  as a ground vs marble --fg #1c1712 = 1.18 FAIL
 *   --gm-line rgba(243,239,228,.22) composited on marble --panel = 1.02 FAIL
 *
 * 13 of the 20 theme×token cells fail. --gm-bronze fails in BOTH themes. The
 * supplied sheet's own pairings fail on their own terms too: its .gm-button sets
 * #fff7df on --gm-bronze #a46f2a = 4.02:1, and its :hover drops that to 3.40:1.
 *
 * So the arrow is REVERSED. `--gm-*` becomes an ALIAS ONTO the console token
 * that already holds that role and already has a measured value in both themes.
 *
 * ---------------------------------------------------------------------------
 * 🔴 FILL vs INK. theme/hellenic.css:258 and :289 record, with pixel
 * measurements, that a gilt pigment cannot serve both roles: marble darkens gold
 * for its INK role (#d9a441 → #9a7420) and a plate built from the darkened value
 * put dark ink at 4.09:1. The gm set has one --gm-gold and one --gm-bronze, so
 * this bridge SPLITS each into `-ink` and `-plate`:
 *   --gm-gold-ink   → --hel-label        night 6.03   marble 6.73
 *   --gm-gold-plate → --hel-plate-gold   carries --hel-ink at 5.56 / 5.24 worst
 *                     pixel (hellenic.css:1066)
 *
 * ---------------------------------------------------------------------------
 * 🔴 REQUEST + BYTE BUDGET — the constraint that shaped this file.
 * nginx conf.d/01-ddos-global.conf counts each HTTP/2 STREAM against
 * limit_conn 30, so a page issuing ~100 subresources gets a random subset 503'd
 * and the failure presents as "the theme is broken" rather than as a rate limit.
 * That is what removed 23 of 48 pages from the menu on 2026-07-28.
 *
 * WHAT THIS LAYER COSTS, on a console page:
 *
 *     stylesheet ..................... 1 request  (this file)
 *     display face ................... 1 request  (§1)
 *     nav webfont .................... 1 request  (§7 — 51 glyphs in one file;
 *                                      the alternative is 51 requests)
 *     sprite sheets .................. 2 requests (faces + marks)
 *     login seal ..................... 1 request  (login page only, not eager)
 *     ------------------------------------------------------------------
 *     added on a console page ........ 5 requests
 *     added per page AFTER the first . 0 requests, 0 B   (all five are static
 *                                      and cached; a mark or a face is a
 *                                      background-position offset into a sheet
 *                                      that is already in memory, and a glyph is
 *                                      a codepoint in a font that already is)
 *     data: URIs 0 · @import 0 · external hosts 0 · individual image files 0
 *
 * 🔴 NO BYTE FIGURE IS WRITTEN IN THIS PARAGRAPH, and that is deliberate: every
 * one of them would be stale the moment anyone edited a comment, including this
 * one — which is exactly what happened to the previous version of this list. The
 * live numbers are in the GENERATED BLOCK at the end of this file, written by the
 * packer that produced the bytes. `python build/gm-budget.py` prints the totals
 * against the caps, and deploy/deploy-gm-theme.py refuses to deploy when they are
 * over. Prose here is not free: this file's own size is inside the eager cap it
 * is competing for, which is what sets Q_FACE and Q_MARK in build/gm-build.py.
 *
 * 🔴 THE SHEETS ARE NOT ON THE BOOT PATH — but they ARE on the eager chain,
 * because three surfaces have opted in: pages/agents.js:432, products.js:144 and
 * auth.js:476. img/gm-seal.avif is NOT eager: `.gm-seal` exists on one element on
 * one page, and a CSS background image is fetched only when an element that needs
 * it is actually laid out.
 * This matters more than it sounds: webconsole/app.js:801-806 limits page-module
 * import() to CONCURRENCY = 8 with MAX_ATTEMPTS = 4, and that governor does NOT
 * cover CSS url() or <img src> — the browser's own loader fetches those,
 * unbounded and unretried. An asset that loses the 503 race is simply never
 * painted. So raster assets are MORE dangerous than page modules, not less, and
 * the mitigation is to have very few of them.
 *
 * ---------------------------------------------------------------------------
 * 🔴 VERIFICATION STATE, 2026-07-29.
 * This file previously carried a Chrome read-back proving 16 .gm-fig cells on a
 * 4x4 sheet, `.gm-hero` gone, and the seal swap. That measurement was true of the
 * build it described and is FALSE of this one: the sheets were repacked from the
 * HD drop (18 faces on a 6x3 grid, 17 marks, new art), .gm-fig gained a ring, and
 * §7 is new. It has been deleted rather than edited, because a browser
 * measurement that was not taken cannot be paraphrased into one that was.
 *
 * WHAT HAS been measured for this build, all of it at build time and reproducible
 * with `python build/gm-build.py`: every glyph codepoint cross-checked three ways
 * (the drop's CSS, its JSON, and Set A's filenames), every mark's four corner
 * alphas re-read before packing, every tile confirmed a downscale rather than an
 * upscale, and the hero's left-38% luminance re-measured on the file the builder
 * wrote. NOTHING here has been read back out of a browser yet, and this layer is
 * not deployed.
 * ==========================================================================*/


/* ============================================================================
 * §1  THE DISPLAY FACE — "Aegean Stone", same-origin, woff2 only.
 *
 * MEASURED FROM THE FILES:
 *   woff2 .... 1,752 bytes   totalSfntSize 43,456   10 tables   flavor 0x00010000
 *   ttf ...... 43,396 bytes  120 glyphs, 119 non-empty outlines
 *   The woff2 is the SAME font, not a subset. 1,752 / 43,396 = 4.0%.
 *
 * 🔴 THE TTF IS NOT SHIPPED. Its only purpose is engines without woff2, and this
 * console's real floor is far newer: theme/hellenic.css uses color-mix() at 15
 * call sites and :has() at 6, and index.html loads 4 `type="module"` scripts.
 * Any engine that renders this console has had woff2 for years.
 * build/gm-budget.py lists the .ttf as a forbidden name so it cannot creep back.
 *
 * COVERAGE, measured from the cmap (3,1 format 4), 119 codepoints:
 *   A–Z 26/26 · a–z 26/26 · 0–9 10/10 · Greek Α–Ω α–ω 48
 *   space ! & , - . / : ?
 *   MISSING from ASCII (24): " # $ % ' ( ) * + ; < = > @ [ \ ] ^ _ ` { | } ~
 *   MISSING entirely: Latin-1 supplement 0/96 — so no ·, no —, no é.
 * Of the 51 pageHeader() title literals in webconsole/pages/*.js, 50 are fully
 * covered; "Export everything (JSON)" falls back for its two parentheses.
 *
 * 🔴 DISPLAY ONLY — a metric fact, not taste. Every glyph is 0.650 em (the face
 * is uniform-width) and cap height equals x-height (H and x both 0.776 em), so
 * lowercase carries no ascender/descender silhouette and running text would lose
 * word shape entirely. The §5 re-assertions in theme/hellenic.css already forbid
 * data surfaces from inheriting --hel-display, and this file changes only that
 * variable's value, so every one of those guards still applies unchanged.
 *
 * FIRST PAINT: `font-display: swap`, so a 404 or a slow font costs nothing. The
 * swap IS visible — measured in Chrome at the live 27px/600/.022em of
 * hellenic.css:947-951 against the same fallback stack (resolved to Candara):
 * "Credentials" +62.3px (+47.7%), "AGV Material Science" +86.2px (+35.0%),
 * "Queued Tasks" +41.9px (+26.3%). size-adjust is the wrong fix (width-matching
 * needs 74%, which drops cap height 21.0px → 15.5px). Preload instead — see the
 * deploy note; at 1,752 B same-origin it lands before the SPA renders a title.
 *
 * 🔴 PATH — CHANGED BY THE MOVE. This file now lives at theme/gm/, so the
 * relative url() below resolves to theme/gm/fonts/aegean-stone-regular.woff2,
 * i.e. /opt/finitectl-console/theme/gm/fonts/aegean-stone-regular.woff2 on the
 * host. It is relative, so it resolves from either hostname that includes the
 * console snippet. No CDN, no @import, no absolute origin.
 * sha256 of the staged file:
 *   d747d354e5d48aa44b35a495cf7df1a49100bbcb0c9cdbd1aa4ff70e482b5675
 * (byte-identical to the source drop; verified by the generator.)
 * ==========================================================================*/

@font-face {
  font-family: "Aegean Stone";
  src: url("./fonts/aegean-stone-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  /* generated from the font's own cmap — see the coverage note above */
  unicode-range: U+0020-0021, U+0026, U+002C-003A, U+003F,
                 U+0041-005A, U+0061-007A,
                 U+0391-03A1, U+03A3-03A9, U+03B1-03C1, U+03C3-03C9;
}


/* ============================================================================
 * §2  THE TOKEN BRIDGE — --gm-* aliases onto console tokens.
 *
 * Each alias carries a FALLBACK to the styles.css value, so if theme/hellenic.css
 * is ever absent the gm names still resolve to something real. Nothing here is
 * theme-specific: every token on the right is already re-bound by hellenic.css's
 * night / marble / auto blocks — one declaration, three themes.
 *
 * MEASURED, worst cell over --bg / --bg-elev / --panel / --panel-2 / --panel-3:
 *
 *   INK ROLES (render as words — need >= 4.5:1)      NIGHT   MARBLE
 *     --gm-ink        → --fg                         11.24    13.10   ok
 *     --gm-ink-dim    → --fg-dim                      6.12     7.36   ok
 *     --gm-limestone  → --fg-dim                      6.12     7.36   ok
 *     --gm-gold-ink   → --hel-label                   6.03     6.73   ok
 *     --gm-bronze-ink → --hel-label                   6.03     6.73   ok
 *     --gm-aegean-ink → --hel-info-text               8.71     7.74   ok
 *     --gm-olive-ink  → --hel-ok-text                 8.95     6.13   ok
 *     --gm-crimson-ink→ --hel-danger-text             6.99     7.65   ok
 *   worst ink figure anywhere in this bridge: 5.34 (--hel-warn-text, marble).
 *
 *   FILL / GRAPHIC ROLES (need >= 3:1)               NIGHT   MARBLE
 *     --gm-bronze     → --hel-bronze                  4.19     4.68   ok
 *     --gm-gold       → --hel-plate-gold              6.03     1.66   plate*
 *     --gm-aegean     → --accent                      6.04     6.03   ok
 *     --gm-olive      → --hel-laurel                  6.03     4.48   ok
 *     --gm-crimson    → --danger                      4.06     6.87   ok
 *   *the plate is not a boundary; what must clear AA is the INK ON it, measured
 *    pixel-by-pixel at 5.24 marble / 5.56 night in hellenic.css:1066.
 *
 *   GROUNDS
 *     --gm-charcoal   → --attic-black   15.50 / 16.65
 *     --gm-panel      → --panel         13.95 / 16.65
 *     --gm-line       → --line           1.41 /  1.72
 *       --line is a DECORATIVE hairline, not a control edge. It is the live
 *       console value, unchanged, reported for visibility only. Do NOT use
 *       --gm-line as the identifying boundary of a control — use --gm-aegean
 *       (--accent), which measures 6.04 / 6.03.
 *
 * 🔴 --gm-limestone has NO console counterpart and, measured, no consumer: it is
 * declared in the supplied theme.css and referenced by zero rules there
 * (`grep -c "var(--gm-limestone)" theme.css` → 0; likewise --gm-aegean,
 * --gm-olive and --gm-crimson — 4 of its 10 colour tokens are declared and never
 * used). It aliases to --fg-dim. No new pigment is invented.
 * ==========================================================================*/

:root {
  /* --- type ------------------------------------------------------------- */
  --gm-font-display: var(--hel-display);
  --gm-font-body:    var(--sans);

  /* --- ink roles: these render as words ---------------------------------- */
  --gm-ink:          var(--fg);
  --gm-ink-dim:      var(--fg-dim);
  --gm-ink-faint:    var(--fg-faint);
  --gm-limestone:    var(--fg-dim);
  --gm-marble:       var(--fg);
  --gm-gold-ink:     var(--hel-label, var(--warn));
  --gm-bronze-ink:   var(--hel-label, var(--warn));
  --gm-aegean-ink:   var(--hel-info-text, var(--info));
  --gm-olive-ink:    var(--hel-ok-text, var(--ok));
  --gm-crimson-ink:  var(--hel-danger-text, var(--danger));

  /* --- fill / graphic roles ---------------------------------------------- */
  --gm-gold:         var(--hel-plate-gold, var(--warn));
  --gm-bronze:       var(--hel-bronze, var(--warn));
  --gm-aegean:       var(--accent);
  --gm-olive:        var(--hel-laurel, var(--ok));
  --gm-crimson:      var(--danger);

  /* --- grounds ------------------------------------------------------------ */
  --gm-charcoal:     var(--hel-ink, var(--fg-invert));
  --gm-panel:        var(--panel);
  --gm-panel-2:      var(--panel-2);
  --gm-line:         var(--line);
  --gm-line-soft:    var(--line-soft);
}


/* ============================================================================
 * §3  THE DISPLAY FACE, WIRED — one variable, and one exception.
 *
 * theme/hellenic.css:937 declares --hel-display and :942 binds it to exactly
 * three live selectors plus the .hel-display opt-in. Re-pointing the variable
 * upgrades all four with no new selector and no cascade fight.
 *
 * 🔴 THE EXCEPTION: the topbar wordmark keeps the system stack. styles.css:134
 * records a measured "order of sacrifice" for the topbar — a nowrap flex row
 * inside #fm-app{overflow:hidden} — and styles.css:142 gives .fm-brand
 * `overflow:hidden`, so a wordmark that grows is CLIPPED, not pushed. Measured
 * at the topbar's 14px/700: "FiniteManager " is 122.5px in Aegean vs 90.6px
 * system, +31.9px onto a row budgeted at ~578px. styles.css:541 drops the
 * wordmark below 560px, leaving a 561–600px window where that is the difference
 * between fitting and being silently cut. The login card has no such pressure
 * (177px inside a 380px card) and keeps the face.
 * ==========================================================================*/

:root {
  --hel-display-sys: Optima, Candara, "Palatino Linotype", "Book Antiqua",
                     Palatino, "URW Palladio L", Georgia, serif;
  --hel-display: "Aegean Stone", var(--hel-display-sys);
}

/* measured exception — see above */
.fm-topbar .fm-brand { font-family: var(--hel-display-sys); }


/* ============================================================================
 * §4  THE SUPPLIED .gm-* CLASSES, RE-EXPRESSED.
 *
 * The source sheet ships four classes. They are reproduced here so markup
 * authored against the gm sample renders correctly inside the console — but
 * every colour is a console token, so each one is theme-aware.
 *
 * MEASURED as re-expressed:
 *   .gm-theme   --fg on --bg ................ night 15.49   marble 15.15
 *   .gm-panel   --fg on the gradient, worst .. night 12.85   marble 14.99
 *   .gm-button  ink on the plate, worst ...... night  5.78   marble  6.32
 *   .gm-title   --fg, worst of 5 surfaces .... night 11.24   marble 13.10
 * The source sheet's own figures for .gm-button were 4.02 flat and 3.40 hover,
 * both below AA, in its single theme.
 * ==========================================================================*/

.gm-theme {
  background: var(--bg);
  color: var(--gm-ink);
  font-family: var(--gm-font-body);
}

.gm-title,
.gm-rune,
.gm-stat-number {
  font-family: var(--gm-font-display);
  letter-spacing: .022em;
}
/* the face is uniform-width (0.650 em, measured), so this costs nothing */
.gm-stat-number { font-variant-numeric: tabular-nums; }

.gm-panel {
  background: linear-gradient(180deg, var(--gm-panel-2) 0%, var(--gm-panel) 100%);
  border: 1px solid var(--gm-line);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-sm);
}

/* Deliberately the LIVE .fm-btn.primary treatment (hellenic.css:424 and :430)
   rather than the source sheet's bronze plate, which measured 4.02:1 — below AA
   — and 3.40:1 on hover. A console button is a control; it does not get a
   decorative exemption. */
.gm-button {
  background: linear-gradient(180deg, var(--gm-gold), var(--gm-bronze));
  border: 1px solid color-mix(in srgb, var(--gm-bronze) 70%, black);
  border-radius: var(--radius-sm);
  color: var(--gm-charcoal);
  font-family: var(--gm-font-display);
  letter-spacing: .022em;
  text-shadow: none;
}
:root[data-theme="marble"] .gm-button {
  background: linear-gradient(180deg, #2a5c9c, var(--accent));
  border-color: color-mix(in srgb, var(--accent) 80%, black);
  color: var(--marble);
}
/* Same gate hellenic.css:439 documents: unscoped, this also fires in auto+dark,
   where --accent is the LIGHT aegean and marble-on-light measures 1.86:1. */
@media (prefers-color-scheme: light) {
  :root[data-theme="auto"] .gm-button {
    background: linear-gradient(180deg, #2a5c9c, var(--accent));
    border-color: color-mix(in srgb, var(--accent) 80%, black);
    color: var(--marble);
  }
}
/* brightness() lifts plate AND ink together, so it cannot quietly close the gap.
   Worst hover figure anywhere: 6.17. */
.gm-button:hover { filter: brightness(1.06); }


/* ============================================================================
 * §6  THE SPRITE SHEETS.  (NEW — not part of the token bridge.)
 *
 * Three files carry 36 tiles between them. The exact pixel dimensions, tile
 * counts and byte sizes are NOT written here — they are in the generated block
 * at the end of this file, because a hand-written inventory goes stale the first
 * time a sheet is repacked and this one already had. gm-assets.js is the only
 * thing that knows which cell is which; a page asks for a ROLE and never for a
 * filename or an offset.
 *
 * Declaring the sheets here costs ZERO requests until a surface opts in — a CSS
 * background image is fetched only when an element that needs it is laid out.
 *
 * WHY AVIF AND NOT PNG. Measured on this drop at these display sizes, AVIF is
 * roughly 1.5–2x smaller than WebP and ~7x smaller than optimised PNG. No
 * <picture> fallback pair is shipped: it would add no requests but would double
 * the on-disk surface the orphan check has to police, and every engine that
 * runs this console (color-mix, :has, ES modules — see §1) has AVIF.
 * 🔴 The deploy script's health check is what confirms the sheets actually
 * decode in the operator's browser. If AVIF ever needs a fallback, regenerate
 * with a .webp twin and add one image-set() — do not hand-add a second <img>.
 *
 * WHERE THE ART COMES FROM (2026-07-29). All three sheets are packed from the
 * owner's greek-console-hd-asset-system, which supersedes the 204 photographic
 * crops the first version of this layer had to segment:
 *   FACES  Set C, 52 named portraits at 192px, opaque RGB and already centred.
 *     build/gm-build.py applies a supersampled circle and nothing else — no
 *     grabCut, no head locator, no per-source overrides. The cv2 machinery and
 *     its three hand-picked head boxes are gone from the builder with the drop
 *     that needed them.
 *   MARKS  Set B, 52 designed medallions at 136px, transparent corners on all
 *     52 (re-checked on every tile the builder packs; a non-zero corner alpha
 *     fails the build). A medallion is a navy disc with a bronze ring, so it
 *     reads as an inset on marble and as a badge on night without a cutout.
 *   SEAL   Set D, the Odysseus seal at 336px.
 * Every tile is a DOWNSCALE of its source — 192→96, 136→64, 336→168 — so unlike
 * the previous drop the cell is not the ceiling on detail; the encoder is.
 *
 * 🔴 .gm-fig CARRIES A RING AND DID NOT BEFORE. The old faces were cut-out
 * silhouettes on transparent ground, so they needed no edge. Set C portraits are
 * full circular tiles with their own dark navy backdrop, which is a visible
 * tondo on the marble ground and dissolves into the night one. The 1px inset
 * ring is what gives it an edge on both, and it is the same --gk-ring token
 * .gm-seal already uses, so the two tiers match.
 *
 * GEOMETRY. background-size is the sheet scaled so one cell equals the rendered
 * box; background-position steps by whole cells. --gm-col/--gm-row and
 * --gm-cols/--gm-rows are set inline by gm-assets.js from the packer's output,
 * so repacking at a different grid does not touch this file.
 * 🔴 That claim was already written here while the rules below hardcoded `* 5`
 * and `* 3`. Growing the sheet from 14 tiles to 16 would have silently
 * mis-registered every tile on it. The literals are gone.
 * ==========================================================================*/

.gm-fig,
.gm-mark {
  display: inline-block;
  flex: none;
  background-repeat: no-repeat;
  background-position:
    calc(var(--gm-col, 0) * var(--gm-size) * -1)
    calc(var(--gm-row, 0) * var(--gm-size) * -1);
  /* a tile is decoration beside a text label; it never carries meaning alone */
  vertical-align: middle;
}

.gm-fig {
  --gm-size: 48px;
  --gm-cols: 6;                 /* fallback only; gm-assets.js sets both inline */
  --gm-rows: 3;
  width: var(--gm-size);
  height: var(--gm-size);
  background-image: url("./img/gm-faces.avif");
  background-size: calc(var(--gm-size) * var(--gm-cols)) calc(var(--gm-size) * var(--gm-rows));
  border-radius: 50%;
  /* see §6: Set C portraits are full discs, not cut-outs, so the tier needs its
     own edge to stay a tondo on night and to stay separate on marble. */
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--gk-ring, #c9923a) 55%, transparent);
}

.gm-mark {
  --gm-size: 32px;
  --gm-cols: 6;
  --gm-rows: 3;
  width: var(--gm-size);
  height: var(--gm-size);
  background-image: url("./img/gm-marks.avif");
  background-size: calc(var(--gm-size) * var(--gm-cols)) calc(var(--gm-size) * var(--gm-rows));
}

/* THE LOGIN SEAL. Its own 168px file: that is odyssey-login.css's 84px `.ody-seal`
   at DPR 2, and a 168px cell on the shared faces sheet would make that sheet
   1008px wide, tens of KB against an eager cap measured in tens. One page renders
   it, so only that page fetches it. (It replaced img/gm-hero.avif on 2026-07-28 —
   a `login:hero` surface with an empty SURFACES entry and no call site anywhere,
   as webconsole/auth.js:329-342 records.)

   THE ART IS Set D, the one canonical figure Set A does not cover, and it arrives
   with its own ground: a navy disc inside a spoked bronze ring. The glaze and ring
   below therefore sit BEHIND an opaque tile rather than under a cut-out bust —
   they are kept because they are what shows through the `contain` letterbox at
   non-square boxes and because --gk-glaze/--gk-ring are inherited from
   `.ody-seal` (odyssey-login.css:681), so the fallbacks keep this class usable
   elsewhere. */
.gm-seal {
  --gm-size: 84px;
  display: inline-block;
  flex: none;
  width: var(--gm-size);
  height: var(--gm-size);
  border-radius: 50%;
  background:
    url("./img/gm-seal.avif") center / contain no-repeat,
    radial-gradient(circle at 50% 38%,
      color-mix(in srgb, var(--gk-glaze, #17110d) 55%, transparent) 0 58%,
      var(--gk-glaze, #17110d) 78%);
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--gk-ring, #c9923a) 70%, transparent),
    inset 0 0 10px color-mix(in srgb, #000 55%, transparent);
  vertical-align: middle;
}

/* 🔴 KILL-SWITCH FOR THE RASTER LAYER SPECIFICALLY. Distinct from the ornament
   switch in §5 because it is worth being able to drop every sprite byte WITHOUT
   losing the type and the tokens — e.g. while diagnosing a 503 storm. The exact
   figure it drops is the three image lines of the generated inventory at the end
   of this file. With `<html data-gm-img="off">` the elements collapse to zero and
   no sheet is ever fetched, because an element with no box is not laid out.
   It does NOT cover the nav webfont: that is §7's own rule set, and dropping the
   sheets while keeping an 18px glyph is the useful combination. */
:root[data-gm-img="off"] .gm-fig,
:root[data-gm-img="off"] .gm-mark,
:root[data-gm-img="off"] .gm-seal {
  display: none !important;
}


/* ============================================================================
 * §5  KILL-SWITCH, PRINT, FORCED COLOURS.
 *
 * A decorative type change is ornament, so it answers to the SAME four spellings
 * theme/hellenic.css:1177 already honours, plus one of its own:
 *     FMTheme.setOrnament('none')                    ← canonical (theme/boot.js)
 *     document.documentElement.dataset.ornament = 'off'
 *     document.documentElement.classList.add('hel-no-ornament')
 *     <html data-fm-hellenic="none">
 *     <html data-gm="off">                           ← this layer only
 *
 * Each restores --hel-display to the verbatim system stack. The --gm-* aliases
 * are NOT cleared: they are colour tokens bound to console tokens, they carry
 * meaning rather than decoration, and clearing them would leave any surface
 * using them with no colour at all.
 *
 * The raster layer follows ornament too — the sheets are decoration by
 * definition, and an operator who has turned ornament off should not be paying
 * for them. §7 repeats these four selectors for the nav glyphs rather than
 * extending the list below, because that block is generated and this one is not:
 * a generated rule cannot be kept in sync with a hand-edited selector list.
 * ==========================================================================*/

:root[data-ornament="off"],
:root.hel-no-ornament,
:root[data-fm-hellenic="none"],
:root[data-gm="off"] {
  --hel-display: var(--hel-display-sys);
}

:root[data-ornament="off"] .gm-fig,
:root[data-ornament="off"] .gm-mark,
:root[data-ornament="off"] .gm-seal,
:root.hel-no-ornament .gm-fig,
:root.hel-no-ornament .gm-mark,
:root.hel-no-ornament .gm-seal,
:root[data-fm-hellenic="none"] .gm-fig,
:root[data-fm-hellenic="none"] .gm-mark,
:root[data-fm-hellenic="none"] .gm-seal,
:root[data-gm="off"] .gm-fig,
:root[data-gm="off"] .gm-mark,
:root[data-gm="off"] .gm-seal {
  display: none !important;
}

@media (forced-colors: active) {
  :root { --hel-display: var(--hel-display-sys); }
  /* a photographic tile carries no forced-colours semantics — drop it */
  .gm-fig, .gm-mark, .gm-seal { display: none !important; }
}

/* A uniform-width face at +36% costs paper and gains nothing on a printout, and
   a 43 KB raster set costs ink. */
@media print {
  :root { --hel-display: var(--hel-display-sys); }
  .gm-panel { background: none; box-shadow: none; }
  .gm-fig, .gm-mark, .gm-seal { display: none !important; }
}


/* >>> GM-BUILD GENERATED BLOCK -- do not edit; rerun build/gm-build.py <<< */
/* ============================================================================
 * §7  THE NAV MARKS, AND THE MEASURED ASSET INVENTORY.
 *
 * 🔴 GENERATED. Everything between the sentinels is build/gm-build.py's output;
 * everything outside them is authored prose the builder preserves byte for byte.
 * The inventory lives here rather than in a hand-written comment because the
 * numbers in a hand-written comment went stale the first time a sheet was
 * repacked, and this file's §6 is where that happened.
 *
 * ONE FILE, 51 SYMBOLS. The other delivery for Set A is one request per mark --
 * 51 against a cold-load cap of 152 with 151 already spent. This is +1. Every
 * mark is drawn with `stroke: currentColor` and no background, so ONE file is
 * correct on the night ground (#0a0e14) and on the marble one (#fbf7ef); that is
 * the property the previous photographic tiles could not have.
 *
 * 🔴 IT IS NOT THE SUPPLIED WEBFONT. That font is a broken stroke-to-outline
 * conversion: 51 glyphs at mean IoU 0.715 against the same drop's own PNG
 * exports of the same icons, 14 below 0.60, every closed contour filled solid.
 * build/gm-build.py carries the full measurement and the reversal instructions.
 *
 * SIZE. `.gci` is a square box of --gci-size, default 1em, so inside the rail's
 * `.fm-nav-item .fm-nav-icon` (styles.css:220 -- font-size 15px in an 18px cell)
 * it is 15px in an 18px box, exactly where a glyph would have been. gm-assets.js
 * sets --gci-size inline when a call site asks for a specific px.
 *
 * THERE IS NO PER-SYMBOL RULE BELOW, and that is a real difference from a font:
 * a font needs one `content: "\fXXX"` rule per glyph, a sprite puts the id in the
 * markup instead (`<use href="...#gci-athena-owl-and-spear">`). All 51 marks are
 * served by the single `.gci` rule below.
 *
 * ASSET INVENTORY, written by the builder that wrote the files:
 *   img/gm-faces.avif      576x288    18 tiles, 96px cell, 6x3 grid       19180 B
 *   img/gm-marks.avif      384x192    17 tiles, 64px cell, 6x3 grid       13413 B
 *   img/gm-seal.avif       168x168    1 tile, 168px cell, login page only   5225 B
 *   img/gci-sprite.svg     72 viewBox  51 symbols, currentColor            21876 B
 * ==========================================================================*/

.gci {
  --gci-size: 1em;
  display: inline-block;
  flex: none;
  width: var(--gci-size);
  height: var(--gci-size);
  vertical-align: middle;
  color: inherit;
  overflow: visible;
}

/* 🔴 THE STROKE COLOUR IS NOT SET HERE. Each <symbol> carries
   stroke="currentColor" as a presentation attribute, and a declaration in this
   stylesheet would OUTRANK it -- including for the 39 elements that deliberately
   override to fill="currentColor" stroke="none" (pupils, rivets, seeds). The
   only thing this layer has to do is make sure `color` reaches the element,
   which `color: inherit` above does. */

/* A mark is decoration beside the row's own title; it never carries meaning
   alone. Same treatment the sprite tiers get in §5, repeated rather than folded
   into that selector list because this block is generated and that one is not. */
@media (forced-colors: active) { .gci { display: none !important; } }
@media print { .gci { display: none !important; } }
:root[data-ornament="off"] .gci,
:root.hel-no-ornament .gci,
:root[data-fm-hellenic="none"] .gci,
:root[data-gm="off"] .gci,
:root[data-gm-img="off"] .gci {
  display: none !important;
}
/* <<< GM-BUILD GENERATED BLOCK END >>> */
