/* Loader screens (loader.js): boot card, run loading card, load error, WebGL missing, graphics reset, and the
   menu-time / multiplayer join pill. JETS design system (UI loop iteration 4, operator 2026-11-01 "all uis like this
   [JETS panel] on web"): the card is a .jt-panel with the JETS header (.gm-softkeys tile │ › TITLE │ tape), the
   progress readouts sit in a .jt-data block around the JETS segmented gauge (.gm-hangar-bar), run items carry kit chips
   and the action keys are kit keys (ui/kit.js button). The markup of #loading / #bgload is static in index.html (it
   paints before any module loads); loader.js only fills it in. Colours, radii and fonts come from ui/tokens.css.
   Sizes are panel design px (--hu) pinned to 1 px here: the card paints before ui/kit.js writes --u, and must not
   jump in size when the menu installs the window scale mid-boot. */
.loading, .bgload { --hu: 1px; }
/* z-index 39: the loading, error and context-lost cards cover the game layers and the pause card (35); the .errors box (40) stays above. */
.loading { display: grid; place-items: center; z-index: 39; padding: 16px;
  background: radial-gradient(120% 90% at 50% 0%, var(--glass) 0%, var(--night) 70%); }
.loader-card { width: min(560px, 92vw); display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; font-family: var(--font-ui); }
.loader-logo { display: block; width: min(520px, 82vw); height: auto; aspect-ratio: 1400 / 136; object-fit: contain; margin: 0 auto;
  filter: drop-shadow(0 2px 6px var(--black-a55)); } /* validated wordmark, 1400 x 136 */
.loader-panel { padding: 18px; display: grid; gap: 12px; min-width: 0; }
/* Header: the JETS header at a card size; the title is the live state name (upper case like every JETS title). */
.loader-head .gm-softkey { width: 44px; height: 44px; }
/* Title shrinks with an ellipsis (it sits directly in the header since UI-LATENCY #5, the slot wrapper is gone). */
.loader-head .gm-softkey-label { flex: 0 1 auto; min-width: 0; }
.loader-head .gm-softkey-label { font-size: 22px; text-transform: uppercase; overflow: hidden; text-overflow: ellipsis; display: block; }
.loader-head .gm-softkey-label::before { display: inline-block; vertical-align: -1px; margin-inline-end: 8px; }
.loader-phase { margin: 0; font-size: 15px; line-height: 1.35; color: var(--h-text); min-height: 1.35em; }
/* Gauge block: step + % above, MB + ETA below the JETS segmented gauge (operator #10975 "a proper progress bar").
   loader.js eases the fill every frame and writes it as --v; the gauge reads it as its --fill (no CSS transition).
   Before the first byte a band sweeps the empty track; on a network stall the gauge freezes amber; on error it freezes
   red at the value it reached. --hg-lit tints the gauge, the header tile, chevron and tape pointer per state. */
.loader-gauge { padding: 12px 14px; display: grid; gap: 8px; }
.loader-bar { --fill: var(--v, 0); --bar-dim: var(--cyan-a55); --bar-lit: var(--hg-lit);
  --seg-pitch: 10px; --seg-gap: 3px; height: 14px; overflow: hidden; }
.loader-bar::before { transition: none; }
/* Indeterminate sweep before the first byte (segmented like the gauge: the mask covers the band too). */
.loader-bar--indet::after { content: ''; position: absolute; inset: 0; width: 35%;
  background: linear-gradient(90deg, transparent, var(--cyan-a55), transparent);
  animation: loader-indet 1.2s ease-in-out infinite; }
@keyframes loader-indet { from { transform: translateX(-100%); } to { transform: translateX(290%); } }
.loader-meta { display: flex; justify-content: space-between; gap: 12px; font-size: 13px; color: var(--h-muted); letter-spacing: .06em;
  font-variant-numeric: tabular-nums; }
.loader-meta--step { color: var(--h-text); font-size: 15px; letter-spacing: .02em; min-height: 1.2em; }
.loader-meta--step > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.loader-pct { color: var(--hg-lit); text-shadow: 0 0 10px var(--cyan-a45); }
/* Run item list: one line per item in a data block, a state glyph and a kit chip with READY / bytes / QUEUED. */
.loader-items { list-style: none; margin: 0; padding: 10px 14px; display: grid; gap: 6px; }
.loader-items li { display: flex; align-items: center; gap: 10px; font-size: 15px; color: var(--h-muted); min-width: 0; }
.loader-items li::before { content: '○'; flex: none; width: 1.2em; text-align: center; color: var(--hint); }
.loader-items li[data-state="load"], .loader-items li[data-state="build"] { color: var(--h-text); }
.loader-items li[data-state="load"]::before { content: '◐'; color: var(--cyan); animation: loader-spin 1s steps(4) infinite; }
.loader-items li[data-state="build"]::before { content: '⚙'; color: var(--cyan); }
.loader-items li[data-state="done"]::before { content: '✓'; color: var(--green); }
.loader-items li[data-state="fail"] { color: var(--red); }
.loader-items li[data-state="fail"]::before { content: '!'; color: var(--red); }
.loader-item-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.loader-item-size { flex: none; font-size: 12px; letter-spacing: .06em; padding: 3px 8px; font-variant-numeric: tabular-nums; }
.loader-items li[data-state="load"] .loader-item-size, .loader-items li[data-state="build"] .loader-item-size { color: var(--cyan); }
.loader-items li[data-state="fail"] .loader-item-size { color: var(--red); }
@keyframes loader-spin { to { transform: rotate(360deg); } }
.loader-hint { margin: 0; font-size: 14px; color: var(--amber); }
/* Error state: the hint line carries the real error message (file names, URLs): smaller, wraps anywhere. */
.loading[data-state="error"] .loader-hint { font-size: 12px; overflow-wrap: anywhere; }
/* Browser button chrome off inside the loader (the .gm reset of menu.css does not reach it); zero specificity so the
   kit key recipes keep their colour and padding. */
:where(.loading) button { font: inherit; color: inherit; background: none; border: 0; margin: 0; padding: 0; cursor: pointer; }
:where(.loading) button:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }
/* Action keys (loader.js setActions: kit primary for the first action, secondary for the rest). */
.loader-actions { display: flex; gap: 12px; justify-content: center; }
.loader-key { min-width: 150px; min-height: 46px; padding: 6px 20px; display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  font: 17px/1 var(--font-ui); letter-spacing: .1em; }
.loader-key-glyph { font-size: 1.2em; line-height: 1; }
.loader-actions[hidden], .loader-items[hidden], .loader-hint[hidden] { display: none; }
/* Per-state tint: network stall amber; error and missing WebGL red; graphics reset amber, full gauge. */
.loading[data-stall="net"] .loader-panel, .loading[data-state="lost"] .loader-panel { --hg-lit: var(--amber); }
.loading[data-state="error"] .loader-panel, .loading[data-state="nowebgl"] .loader-panel { --hg-lit: var(--red); }
/* Dim end of the lit ramp per state: the state's alpha step (tokens.css), not color-mix() of --hg-lit (Chrome < 111,
   the A51's Chrome 93, rejects color-mix and the whole gauge fill then vanished). */
.loading[data-stall="net"] .loader-bar, .loading[data-state="lost"] .loader-bar { --bar-dim: var(--amber-a55); }
.loading[data-state="error"] .loader-bar, .loading[data-state="nowebgl"] .loader-bar { --bar-dim: var(--red-a55); }
.loading[data-state="error"] .gm-softkey-label, .loading[data-state="nowebgl"] .gm-softkey-label { color: var(--red); }
.loading[data-state="nowebgl"] .loader-bar, .loading[data-state="lost"] .loader-bar { --fill: 1; }
.loading:is([data-state="error"], [data-state="nowebgl"], [data-state="lost"]) .loader-bar::after { display: none; }
.loading:is([data-state="error"], [data-state="nowebgl"], [data-state="lost"]) .loader-meta--step { display: none; }
.loading:is([data-state="error"], [data-state="nowebgl"], [data-state="lost"]) .loader-meta { color: var(--hint); font-size: 12px; }
.loading[data-stall="net"] .loader-bar::after { animation: none; }
.loading:is([data-stall="net"], [data-state="lost"]) .loader-pct { text-shadow: none; }
/* WHAT: under the OS reduced-motion setting the sweep stops; the band rests mid-track.
   WHY: endless sweeping gradients are decorative motion (WCAG 2.3.3); the percentage, step list and gauge fill still show progress. */
@media (prefers-reduced-motion: reduce) {
  .loader-bar--indet::after { animation: none; transform: translateX(95%); }
  .loader-items li[data-state="load"]::before { animation: none; }
}

/* Menu-time background pill: bottom centre (clear of the menu column, settings and news), a small JETS panel with the
   current item and a thin segmented gauge. */
.bgload { position: fixed; left: 50%; bottom: 16px; transform: translateX(-50%); z-index: 25; display: grid; gap: 6px; min-width: 220px;
  padding: 9px 14px; font: 12px/1.2 var(--font-ui); color: var(--h-text); letter-spacing: .08em; pointer-events: none; }
.bgload-bar { --fill: var(--v, 0); --seg-pitch: 6px; --seg-gap: 2px; height: 6px; }
/* In flight the pill carries the multiplayer join bar: top centre, clear of the bottom HUD gauges. */
.bgload--join { top: 14%; bottom: auto; }

/* Phones: landscape is short (logo and list shrink), portrait is narrow (card full width). */
@media (max-height: 480px) {
  .loader-logo { width: min(260px, 50vw); }
  .loader-card { gap: 7px; }
  .loader-panel { padding: 10px 14px; gap: 7px; }
  .loader-head .gm-hangar-tape { display: none; }
  .loader-items { padding: 6px 10px; gap: 3px; }
  .loader-items li { font-size: 13px; }
}
@media (max-width: 480px) {
  .loader-card { width: 94vw; }
  .loader-panel { padding: 14px; }
  .loader-head .gm-softkey { width: 38px; height: 38px; }
  .loader-head .gm-softkey-label { font-size: 18px; letter-spacing: .1em; white-space: normal; line-height: 1.15; }
  .loader-meta { font-size: 12px; }
  .loader-meta--step { font-size: 13px; }
  .loader-key { min-width: 0; flex: 1 1 0; }
}

/* Run -> flying reveal (loader.js setState): the card fades to the scene; the timer in loader.js removes it. */
.loading.loader-reveal { opacity: 0; transition: opacity var(--reveal-ms, 450ms) ease-out; pointer-events: none; }
