/* ============================================================================
   MAVEN RO — SYSTEM UI KIT
   A diegetic, in-world "System" overlay in the Solo Leveling idiom.
   Neon blue. Deliberately NOT the site's crimson/gold chrome.

   Self-contained: no @import, no CDN, no webfont. System font stack only.
   Everything is driven by the custom properties in the :root block below;
   other lanes consume those tokens. See CONTRACT.md.

   Colour values were sampled off refs/bar-system/solo-leveling-ref.jpg
   (736x554). Sampled numbers are quoted next to the token that carries them.
   ========================================================================= */

/* ---------------------------------------------------------------------------
   1. DESIGN TOKENS
   ------------------------------------------------------------------------ */

:root {
  /* -- 1.1 FIELD ----------------------------------------------------------
     The deep near-black navy the panel floats on. In the reference the far
     field reads rgb(8,8,16)..rgb(18,17,28) — black with a blue cast, never
     neutral grey. Used for demo backdrops and for the scrim behind a modal
     alert so the panel stays legible over a busy rendered map.              */
  --sys-field:            #08080f;   /* sampled rgb(8,8,16)   */
  --sys-field-hi:         #12111c;   /* sampled rgb(18,17,28) */

  /* -- 1.2 THE BLUES ------------------------------------------------------
     Ramp from the panel's dark core out to the near-white hairline.
     The panel interior is NOT flat: it is darkest at the centre and blooms
     brighter toward its own edge, because the neon rim bleeds inward.       */
  --sys-blue-core:        #1437a8;   /* sampled rgb(20,55,168)  panel centre */
  --sys-blue-mid:         #1541b8;   /* interpolated, 2/3 out               */
  --sys-blue-edge:        #1275dc;   /* sampled rgb(18,117,220) panel rim   */
  --sys-blue-lift:        #3e76da;   /* sampled rgb(62,118,218) header pill */
  --sys-blue-bright:      #72a5f8;   /* sampled rgb(114,165,248) pill edge  */
  --sys-hairline:         #cfe2ff;   /* the inner border. Near-white, not a
                                        pale blue: in the reference this line
                                        reads R=120 against an R=30 surround,
                                        so it is the WHITEST thing on the panel
                                        after the type. A bluer value sinks
                                        into the fill and the frame disappears. */

  /* Raw channels, so consumers can build their own rgba() at any alpha
     without hard-coding our numbers. */
  --sys-rgb-glow:         30 128 245;   /* the light the panel emits. Very
                                           low red — the reference's glow
                                           reads rgb(9,43,151) at 12px out   */
  --sys-rgb-hairline:     207 226 255;
  --sys-rgb-glow-inner:   22 162 255;   /* the light thrown INWARD. Brighter
                                           and cyaner than --sys-rgb-glow:
                                           the reference's interior rim hits
                                           G=110, which the outward glow's
                                           G=128 cannot reach at any alpha
                                           you would actually want to use.  */
  --sys-rgb-lift:         135 200 255;  /* the white-blue that lifts a pill */
  --sys-rgb-accent:       82 219 88;

  /* -- 1.3 THE GREEN ------------------------------------------------------
     The defining detail: the *variable* part of a system message is green
     while the fixed copy stays white. Sampled glyph core rgb(88,204,33);
     the photograph desaturates, so the token is pushed back toward source.  */
  --sys-accent:           #52db58;
  --sys-accent-dim:       #329a37;

  /* Secondary semantic accents. Same idiom, different meaning. Use sparingly
     — green is the system's voice; these are for quests, damage, warnings.  */
  --sys-accent-gold:      #ffcc4d;
  --sys-accent-red:       #ff5a5a;
  --sys-accent-violet:    #b98cff;
  --sys-accent-cyan:      #55e2ff;

  /* -- 1.4 TEXT -----------------------------------------------------------*/
  --sys-ink:              #f2f6ff;   /* white caps; sampled (203,204,237)
                                        through the camera = pure white     */
  --sys-ink-dim:          rgb(226 238 255 / 0.72);
  --sys-ink-faint:        rgb(226 238 255 / 0.46);

  /* -- 1.5 GLOW LAYERS ----------------------------------------------------
     THE SIGNATURE. Built from stacked shadows, never one flat drop.

     Measured falloff outward from the hairline in the reference (blue
     channel):  0px:254  4:193  8:178  12:150  16:116  20:83  26:41  32:24
     40:18  50:16 (field). Two things fall out of that curve and they are the
     whole trick:

       (a) the bloom is INTENSE, not WIDE. It is essentially gone ~18px past
           the panel edge. A single wide soft shadow — the obvious first
           instinct — reads as a drop shadow, not neon.
       (b) most of the apparent "glow" is actually INSIDE the panel: the fill
           climbs from rgb(21,56,168) at the centre to rgb(15,110,217) at the
           rim. The neon bleeds inward harder than it bleeds outward.

     And the structural consequence, which is the thing worth stealing: the
     brightest pixel is the HAIRLINE, and the fill falls off in BOTH directions
     from it. So the hairline is modelled as the light source — it is the neon
     tube and the panel is the diffuser — rather than putting all the glow on
     the panel's own edge. That is why --sys-frame-glow exists separately.

     Alphas scale with --sys-glow-strength so a consumer can dim the lot with
     one number.                                                             */
  --sys-glow-strength:    1;

  /* Panel-edge bloom: the light that reaches the scene behind the overlay. */
  /* No spread ring. A `0 0 0 1px` rim is the reflex move and it is wrong here:
     it puts a hard bright edge on the panel, and the reference has no visible
     panel edge at all — just one smooth ramp from the field up to the
     hairline. Any hard step reads as a sticker. */
  --sys-glow-1:      0 0 12px     rgb(var(--sys-rgb-glow) / calc(0.26 * var(--sys-glow-strength)));
  --sys-glow-2:      0 0 26px     rgb(var(--sys-rgb-glow) / calc(0.11 * var(--sys-glow-strength)));
  --sys-glow-3:      0 0 56px     rgb(var(--sys-rgb-glow) / calc(0.07 * var(--sys-glow-strength)));
  --sys-glow-4:      0 0 110px    rgb(var(--sys-rgb-glow) / calc(0.045 * var(--sys-glow-strength)));
  --sys-glow-5:      0 0 220px    rgb(var(--sys-rgb-glow) / calc(0.028 * var(--sys-glow-strength)));

  /* The panel's own outer band is DARKER than its centre — the reference dips
     to ~rgb(8,32,130) at the panel edge before the hairline's light takes
     over. This is what lets the outward glow meet the fill at the same value,
     so the two blend into one continuous ramp instead of stepping. */
  /* REMOVED: a 1px inset hairline used to sit here, on .sys-panel — the GLOW
     box, 15.7px outside .sys-frame. Because the panel is rounded (r4) and the
     frame is square (r2), it rendered as a faint SECOND rounded rectangle
     tracing the halo. Measured as a non-monotonic bump in the outward falloff
     on all four sides (left 46.4 -> 51.5 -> 43.5; top 45.8 -> 51.0 -> 37.3),
     where the reference's falloff is perfectly monotonic everywhere. Shrinking
     the radius 16 -> 4 made it tighter but did not remove it; only removing the
     line removes the artifact. The frame's own light already defines the edge.
     NB: this must be a NO-OP SHADOW, not `none` — the token is composed into a
     comma-separated box-shadow list, and `none` is only legal as the sole
     value. `box-shadow: none, inset ...` is invalid and would drop the entire
     panel bloom on the floor. */
  --sys-glow-inner-1: inset 0 0 0 0 transparent;
  --sys-glow-inner-2: inset 0 0 12px   rgb(2 8 40 / 0.88);

  /* THE LIGHT SOURCE. Applied to .sys-frame, so it spills outward across the
     panel's outer band and past its edge, and inward across the interior —
     which is exactly the two-sided falloff the reference shows. */
  --sys-frame-glow:
    0 0 3px    rgb(var(--sys-rgb-hairline) / calc(0.90 * var(--sys-glow-strength))),
    0 0 8px    rgb(var(--sys-rgb-hairline) / calc(0.45 * var(--sys-glow-strength))),
    0 0 14px   rgb(var(--sys-rgb-glow) / calc(0.60 * var(--sys-glow-strength))),
    0 0 30px   rgb(var(--sys-rgb-glow) / calc(0.34 * var(--sys-glow-strength))),
    0 0 62px   rgb(var(--sys-rgb-glow) / calc(0.13 * var(--sys-glow-strength))),
    /* Inward: three stacked inset layers rather than one. They have to be
       inset SHADOWS, not a radial-gradient background — a radial gradient is
       an ellipse, and an ellipse inside a rounded rectangle leaves a visible
       dark oval in the middle of the panel. That oval was the single most
       obvious tell in the side-by-side. Inset shadows follow the rounded
       rect, so the falloff is parallel to the frame on all four sides. */
    inset 0 0 12px  rgb(var(--sys-rgb-hairline) / calc(0.16 * var(--sys-glow-strength))),
    inset 0 0 20px  rgb(var(--sys-rgb-glow-inner) / calc(1.00 * var(--sys-glow-strength))),
    inset 0 0 55px  rgb(var(--sys-rgb-glow-inner) / calc(0.62 * var(--sys-glow-strength))),
    inset 0 0 130px rgb(var(--sys-rgb-glow-inner) / calc(0.22 * var(--sys-glow-strength)));

  /* The whole panel stack, ready to drop into box-shadow. */
  --sys-bloom:
    var(--sys-glow-inner-1), var(--sys-glow-inner-2),
    var(--sys-glow-1), var(--sys-glow-2), var(--sys-glow-3),
    var(--sys-glow-4), var(--sys-glow-5);

  /* Text glow. Applied to the caps so they read as emitted, not printed. */
  --sys-text-glow:        0 0 12px rgb(var(--sys-rgb-hairline) / 0.55),
                          0 0 30px rgb(var(--sys-rgb-glow) / 0.35);
  --sys-accent-glow:      0 0 10px rgb(var(--sys-rgb-accent) / 0.60),
                          0 0 26px rgb(var(--sys-rgb-accent) / 0.35);

  /* -- 1.6 RADII ----------------------------------------------------------
     Measured off the reference corners: the panel is modestly rounded, the
     inner frame slightly tighter, the header pill much rounder.            */
  /* The panel is the GLOW box that wraps the frame. At 16px its silhouette was
     a visibly round-cornered blue halo around a hard square frame — measured as
     a tell one layer out from the frame corner itself. In the reference the
     halo hugs the square corner, so this has to track --sys-r-frame closely
     rather than being "modestly rounded" on its own terms. */
  --sys-r-panel:          4px;
  /* The reference's hairline meets at a hard right angle. Measured at 11x zoom
     on an aligned crop, it shows essentially no arc — camera blur is only ~3px
     and cannot conceal a radius. At 9px ours turned a visible ~14px
     quarter-circle, and corner radius was the single tell that let a blind
     critic pick our panel out of the pair. Near-square is correct here. */
  --sys-r-frame:          2px;
  /* Likewise the header: the reference is a soft, near-rectangular lighter
     BAND whose ends fade into the fill, not a rounded pill. 18px read as a
     UI-kit pill and was the second tell, in the same direction as the first. */
  --sys-r-pill:           5px;
  --sys-r-chip:           12px;
  --sys-r-btn:            8px;

  /* -- 1.7 GEOMETRY -------------------------------------------------------
     Ratios carried over from the reference, expressed against panel width:
     NOTE: 1cqi is the panel's CONTENT box, not its border box — so these are
     ratios of 367px, not of the 394px panel. Getting that wrong makes every
     vertical measurement come out ~8% short, which is exactly the bug the
     first pass shipped.
       hairline inset from panel edge   13 / 367 =  3.6%
       frame padding, sides             17 / 367 =  4.6%   -> pill = 327px
       frame padding, top               40 / 367 = 10.9%
       header pill height               48 / 367 = 13.1%
       body region below the pill      155 / 367 = 42.2%
       body type size                   29.4/367 =  8.0%
       header type size                 24.6/367 =  6.7%
     Glyph-ink measurements the sizes were solved against:
       "[WELCOME, PLAYER.]" ink span 296px, ink rows 336..362
       "ALERT" ink span 79px, ink rows 241..258, ring icon 22px
     Panels declare `container-type: inline-size`, so these are authored in
     cqi and the whole composition scales with the panel at any viewport.   */
  --sys-alert-w:          440px;  /* default width of a centred alert       */
  --sys-toast-w:          360px;
  --sys-panel-w:          520px;
  --sys-edge:            clamp(12px, 3vw, 28px); /* viewport safe margin    */
  --sys-stack-gap:        12px;

  /* -- 1.8 TYPE -----------------------------------------------------------
     No webfont is permitted (strict origin), so this is a system stack
     chosen for a bold humanist-grotesque cap silhouette close to the
     reference. Segoe UI leads on Windows (the platform this server's players
     are on); Trebuchet is the closest cross-platform fallback.             */
  --sys-font: "Segoe UI", "Trebuchet MS", "Helvetica Neue", Helvetica, Arial,
              system-ui, -apple-system, "Noto Sans", sans-serif;
  --sys-font-mono: ui-monospace, "Cascadia Mono", "Consolas", "SF Mono",
              "Menlo", monospace;

  --sys-weight:           700;
  /* The reference's body caps are NOT tracked out — solving its 296px ink span
     against its 20.4px cap height lands on ~0 tracking. Only the header is
     opened up, and only slightly more than the reference, because "ALERT"
     reads as a label and wants the air. */
  --sys-track-body:       0.008em;
  --sys-track-title:      0.05em;
  --sys-track-meta:       0.16em;   /* small caps labels                      */

  /* -- 1.9 MOTION ---------------------------------------------------------
     The anime arrival: a fast scale-in with the bloom overshooting, then a
     short settle. Nothing eases linearly.                                  */
  --sys-dur-in:           420ms;
  --sys-dur-out:          220ms;
  --sys-dur-bloom:        720ms;
  --sys-dur-flash:        520ms;
  --sys-ease-arrive:      cubic-bezier(0.16, 1, 0.30, 1);
  --sys-ease-settle:      cubic-bezier(0.33, 1, 0.68, 1);
  --sys-ease-exit:        cubic-bezier(0.55, 0, 0.85, 0.35);

  /* -- 1.10 LAYERING ------------------------------------------------------
     One base, and a documented band per layer so lanes stacking on top of
     this kit never have to guess. See CONTRACT.md §z-index.                */
  --sys-z:                9500;   /* base. root overlay sits here.          */
  --sys-z-dock:           calc(var(--sys-z) + 10);
  --sys-z-toast:          calc(var(--sys-z) + 20);
  --sys-z-panel:          calc(var(--sys-z) + 30);
  --sys-z-alert:          calc(var(--sys-z) + 40);

  /* -- 1.11 BACKDROP ------------------------------------------------------
     The kit floats over a rendered map, so it must buy its own separation. */
  --sys-scrim:            radial-gradient(
                            120% 90% at 50% 50%,
                            rgb(4 6 16 / 0.62) 0%,
                            rgb(4 6 16 / 0.42) 45%,
                            rgb(4 6 16 / 0.00) 100%);
  --sys-backdrop-blur:    blur(3px) saturate(1.05);
  --sys-panel-alpha:      0.965;  /* panels are *almost* opaque, so the
                                     backdrop-filter still reads at the rim */
}

/* ---------------------------------------------------------------------------
   2. ROOT OVERLAY
   Fixed, click-through by default. It must not assume it owns the page: no
   styles leak outward, nothing is applied to html/body, and every rule below
   is scoped under .sys-root or .sys-* .
   ------------------------------------------------------------------------ */

.sys-root {
  position: fixed;
  inset: 0;
  z-index: var(--sys-z);
  pointer-events: none;
  font-family: var(--sys-font);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  color: var(--sys-ink);
  /* Isolate so our stacking contexts can never be reordered by the host. */
  isolation: isolate;
}

.sys-root *,
.sys-root *::before,
.sys-root *::after { box-sizing: border-box; }

/* Anything genuinely interactive re-enables pointer events for itself. */
.sys-root [data-sys-interactive] { pointer-events: auto; }

/* -- Layers ---------------------------------------------------------------*/
.sys-layer { position: absolute; display: flex; }

.sys-layer--alert {
  inset: 0;
  z-index: var(--sys-z-alert);
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: var(--sys-stack-gap);
  padding: var(--sys-edge);
}

.sys-layer--toast {
  top: var(--sys-edge);
  right: var(--sys-edge);
  z-index: var(--sys-z-toast);
  flex-direction: column;
  align-items: flex-end;
  gap: var(--sys-stack-gap);
  max-height: calc(100% - var(--sys-edge) * 2);
  width: min(var(--sys-toast-w), calc(100vw - var(--sys-edge) * 2));
}

.sys-layer--panel {
  inset: 0;
  z-index: var(--sys-z-panel);
  pointer-events: none;
}

/* Minimised windows dock BOTTOM-RIGHT by default (Ramil, 2026-08-23 — moved
   from bottom-left). Left is still reachable via
   `<div class="sys-root" data-dock-side="left">` rather than being deleted,
   because the choice is a preference, not a fact about the kit. */
.sys-layer--dock {
  right: var(--sys-edge);
  bottom: var(--sys-edge);
  z-index: var(--sys-z-dock);
  /* `column`, not `column-reverse`: the box is pinned at its bottom, so the
     last child lands nearest the corner and older chips push upward. */
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  max-width: calc(100vw - var(--sys-edge) * 2);
}

.sys-root[data-dock-side="left"] .sys-layer--dock {
  right: auto;
  left: var(--sys-edge);
  align-items: flex-start;
}

/* Modal scrim — only mounted while a modal alert/panel is open. It takes
   pointer events unconditionally: that is what makes it modal. Without this it
   inherits pointer-events:none from the root and the player can keep clicking
   the game through a window that is supposed to be blocking. */
.sys-scrim {
  position: absolute;
  inset: 0;
  pointer-events: auto;
  /* Must sit below EVERY surface it can scrim, not just below alerts.
     At `--sys-z-alert - 1` (base+39) it cleared alerts (base+40) but buried the
     PANEL layer (base+30) underneath itself, so `System.panel({scrim:true})`
     rendered and then swallowed its own clicks — the modal was unusable while
     looking correct. Sitting one below the panel band covers the dock and toast
     layers (as a modal should) while leaving both panels and alerts clickable. */
  z-index: calc(var(--sys-z-panel) - 1);
  background: var(--sys-scrim);
  backdrop-filter: var(--sys-backdrop-blur);
  -webkit-backdrop-filter: var(--sys-backdrop-blur);
  opacity: 0;
  animation: sys-scrim-in var(--sys-dur-in) var(--sys-ease-arrive) forwards;
}
.sys-scrim[data-sys-closing] {
  animation: sys-scrim-out var(--sys-dur-out) var(--sys-ease-exit) forwards;
}

/* ---------------------------------------------------------------------------
   3. THE PANEL — the core visual. Everything else is a variant of this.
   ------------------------------------------------------------------------ */

.sys-panel {
  position: relative;
  container-type: inline-size;   /* all interior sizing is in cqi */
  width: 100%;
  /* 14/392 = 3.57% — the hairline inset.
     MUST NOT be cqi. This element declares `container-type: inline-size`, so
     it is its OWN container, and an element's padding cannot resolve against a
     size its padding helps determine. Rather than erroring, the value silently
     falls back to the VIEWPORT: 3.6cqi measured 51.84px @1440 / 36.86 @1024 /
     26.50 @736 / 14.04 @390 — exactly 3.6% of viewport width. That inflated
     every desktop toast into a ~360x144 flat blue slab wrapped around a 256x40
     frame, so alert/notify/panel stopped reading as one system at the width
     this actually ships at.
     A percentage resolves against the CONTAINING BLOCK's width, and since this
     is `box-sizing: border-box` + `width: 100%`, the containing block's width
     IS this panel's border-box width — which is what 3.6cqi was reaching for.
     Interior cqi units are unaffected: they resolve against this panel's
     content box, which is correct and was never the bug. */
  padding: 3.57%;
  border-radius: var(--sys-r-panel);
  isolation: isolate;

  /* Panel fill. Darkest at the centre, brightening to the rim — the inward
     bleed of the neon edge. Gradient does the broad shape; the inset shadows
     in --sys-bloom sharpen the last ~40px against the edge. */
  /* Near-flat on purpose. All of the modelling comes from the frame's light
     and the edge vignette above; a strong gradient here fights both. */
  background:
    radial-gradient(ellipse closest-side at 50% 50%,
      rgb(21 57 172 / var(--sys-panel-alpha)) 0%,
      rgb(21 58 174 / var(--sys-panel-alpha)) 62%,
      rgb(20 62 180 / var(--sys-panel-alpha)) 100%);

  box-shadow: var(--sys-bloom);

  backdrop-filter: var(--sys-backdrop-blur);
  -webkit-backdrop-filter: var(--sys-backdrop-blur);

  pointer-events: auto;
  will-change: transform, opacity;
}

/* The outward bloom lives on its own layer so the arrival animation can
   overshoot it independently of the panel's own scale, and so we never
   animate box-shadow (which cannot be composited). */
/* The animatable half of the bloom. It exists so the arrival can overshoot the
   glow independently of the panel's own scale, and so we never animate
   box-shadow (which cannot be composited).

   It is a *transparent* box carrying an outer box-shadow, not a filled and
   blurred rectangle. That matters: an outer box-shadow is clipped to outside
   its own border box, so this can never wash the panel's fill. A filled div —
   the obvious implementation — sits above the parent's background even at
   z-index:-1, and quietly lifts the whole interior. */
.sys-panel__bloom {
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  box-shadow:
    0 0 22px rgb(var(--sys-rgb-glow) / calc(0.15 * var(--sys-glow-strength))),
    0 0 60px rgb(var(--sys-rgb-glow) / calc(0.05 * var(--sys-glow-strength)));
  opacity: 1;
}

/* A single fast white wash on arrival — the anime's "materialise" beat. */
.sys-panel__flash {
  position: absolute;
  inset: 0;
  z-index: 3;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(160deg,
    rgb(255 255 255 / 0.55) 0%,
    rgb(190 225 255 / 0.30) 38%,
    rgb(255 255 255 / 0.00) 72%);
  mix-blend-mode: screen;
}

/* -- 3.1 The hairline frame ----------------------------------------------
   A thin light rule inset from the panel edge. It is the element that reads
   as "a window", and it carries the layout for everything inside.          */
.sys-frame {
  position: relative;
  z-index: 2;
  border: 1px solid rgb(var(--sys-rgb-hairline) / 1);
  border-radius: var(--sys-r-frame);
  /* Top padding is measured; there is no bottom padding because in the
     reference the message block runs to the frame's inner edge and is centred
     inside it. .sys-body carries symmetric padding of its own so long copy
     still breathes without moving the optical centre. */
  padding: 10.9cqi 4.6cqi 0;
  box-shadow: var(--sys-frame-glow);
  /* The frame is the light source. Its inset layers (see --sys-frame-glow)
     light the interior and produce the *step* the reference shows across the
     hairline: the fill immediately inside it (rgb 15,110,217) is far brighter
     than the band immediately outside it (rgb ~17,60,178). An inset shadow on
     the PANEL cannot make that step, because it is measured from the panel
     edge, not the frame. Putting the light on the frame can.
     No background here on purpose — see the note in --sys-frame-glow. */
  display: flex;
  flex-direction: column;
}

/* -- 3.2 The header pill --------------------------------------------------
   Spans the frame's full content width (327 of 328 in the reference), lighter
   than the panel, brightest along its top and bottom edges.                */
.sys-head {
  position: relative;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 2.7cqi;
  min-height: 13.1cqi;             /* 48px at the reference scale */
  padding: 1.4cqi 3cqi;
  border-radius: var(--sys-r-pill);
  /* The pill is a white-blue wash over the panel, not a separate blue. In the
     reference it reads rgb(60,117,213) against a rgb(21,56,168) centre —
     a ~40% lift in R, which no amount of blue tint reproduces. */
  background: linear-gradient(180deg,
    rgb(var(--sys-rgb-lift) / 0.66) 0%,
    rgb(var(--sys-rgb-lift) / 0.42) 15%,
    rgb(var(--sys-rgb-lift) / 0.33) 50%,
    rgb(var(--sys-rgb-lift) / 0.42) 86%,
    rgb(var(--sys-rgb-lift) / 0.62) 100%);
  /* The band is a CLOSED PLATE, bright on all four sides — not a wash that
     fades out.
     This was got backwards once. A reviewer described the reference's ends as
     "fading into the fill", that description was implemented as a horizontal
     mask, and measurement then showed the exact opposite: in the reference the
     band ENDS are the brightest pixels in it — luminance 159 at the left end
     and 168 at the right, against a band interior of ~110. The masked version
     ended at 75, a local minimum, darker than both the band interior (104) and
     the panel fill behind it (83). It read as an unfinished gradient.
     So: a continuous rim closes all four sides, with the top and bottom edges
     carrying extra lift, and no mask. */
  box-shadow:
    /* OUTWARD SPILL — the band is a light source, so it must brighten the panel
       fill AROUND it. Measured against the reference: 4-7px below the band the
       reference's fill sits at 48-50 L while ours sat at 32-34 — a 16 L deficit,
       and approaching the band from above the reference CLIMBS (33.7 -> 42.8)
       where ours FELL (38.4 -> 33.9). A sign reversal: the band was carving a
       dark moat instead of glowing into the fill.
       The cause was a previous fix moving all the band's light to INSET
       shadows, and an inset shadow cannot paint outside its own border box. So
       the outward layers below are the ones that matter, and they are not
       decoration. */
    0 4px 34px 8px rgb(var(--sys-rgb-lift) / 0.34),
    0 0 14px 2px  rgb(var(--sys-rgb-lift) / 0.30),
    /* Light POOLS at the two ends and bleeds inward — it is not an outline.
       Measured on the reference: the ends peak ~164/169 against a ~123 interior
       and decay over ~30px. A continuous 1.5px rim (the previous attempt)
       overshot the other way: +79 L at the top edge against the reference's
       +29, i.e. 1.9x the light energy, and the ends collapsed to interior
       within 8px. That reads as an outlined form control rather than a lit bar.
       Large blur with a negative spread gives the broad inward bloom without
       ever drawing an edge. */
    inset  22px 0 30px -16px rgb(255 255 255 / 0.38),   /* left end  */
    inset -22px 0 30px -16px rgb(255 255 255 / 0.38),   /* right end */
    /* Top rim measured 1.75x too hot against the reference (+24.4 L over band
       interior vs its +13.9). Pulled back — the light it was carrying belongs
       in the outward spill above, not stacked on the inner edge. */
    inset 0  8px 14px -6px  rgb(255 255 255 / 0.20),    /* top bloom */
    inset 0 -8px 14px -6px  rgb(255 255 255 / 0.26),    /* bottom bloom */
    inset 0 0 26px rgb(var(--sys-rgb-lift) / 0.18);
}

.sys-head__icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 6cqi;
  height: 6cqi;
  border-radius: 50%;
  border: 0.56cqi solid var(--sys-ink);
  color: var(--sys-ink);
  font-size: 3.6cqi;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0;
  filter: drop-shadow(0 0 6px rgb(var(--sys-rgb-hairline) / 0.7));
}
/* The bang, drawn rather than typed, so it optically centres in the ring at
   every size regardless of the resolved font's metrics. */
.sys-head__icon::before {
  content: "";
  width: 0.66cqi;
  height: 2.05cqi;
  margin-bottom: 0.36cqi;
  border-radius: 1px;
  background: currentColor;
  box-shadow: 0 1.12cqi 0 0 currentColor;   /* the dot */
}

.sys-head__title {
  font-size: 6.7cqi;               /* 24.6px -> 18px cap, matching the ref */
  font-weight: var(--sys-weight);
  letter-spacing: var(--sys-track-title);
  text-transform: uppercase;
  line-height: 1;
  color: var(--sys-ink);
  text-shadow: var(--sys-text-glow);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* -- 3.3 The body ---------------------------------------------------------
   In the reference the message sits dead-centre of the space below the pill.
   flex:1 + centring reproduces that at any content length.                 */
.sys-body {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 42.2cqi;             /* 155px at the reference scale */
  padding: 2cqi 0;                 /* symmetric: preserves the optical centre */
  text-align: center;
}

.sys-message {
  margin: 0;
  font-size: 8cqi;                 /* solved against the 296px reference ink span */
  font-weight: var(--sys-weight);
  letter-spacing: var(--sys-track-body);
  text-transform: uppercase;
  line-height: 1.22;
  color: var(--sys-ink);
  text-shadow: var(--sys-text-glow);
  text-wrap: balance;
}

/* THE detail: the variable part of a system message is green. */
.sys-accent {
  color: var(--sys-accent);
  text-shadow: var(--sys-accent-glow);
}
.sys-accent[data-accent="gold"]   { color: var(--sys-accent-gold);
  text-shadow: 0 0 10px rgb(255 204 77 / .55), 0 0 26px rgb(255 204 77 / .3); }
.sys-accent[data-accent="red"]    { color: var(--sys-accent-red);
  text-shadow: 0 0 10px rgb(255 90 90 / .55), 0 0 26px rgb(255 90 90 / .3); }
.sys-accent[data-accent="violet"] { color: var(--sys-accent-violet);
  text-shadow: 0 0 10px rgb(185 140 255 / .55), 0 0 26px rgb(185 140 255 / .3); }
.sys-accent[data-accent="cyan"]   { color: var(--sys-accent-cyan);
  text-shadow: 0 0 10px rgb(85 226 255 / .55), 0 0 26px rgb(85 226 255 / .3); }

/* ---------------------------------------------------------------------------
   4. ALERT — the classic centred [ ... ] notification. Reference-exact.
   ------------------------------------------------------------------------ */

.sys-alert {
  container-type: inline-size;
  width: min(var(--sys-alert-w), calc(100vw - var(--sys-edge) * 2));
  pointer-events: auto;
}

/* A dismissible alert shows an affordance rather than leaving the user stuck.
   It sits OUTSIDE the panel on purpose — the panel's interior composition is
   matched to the reference and nothing is allowed to disturb it. */
.sys-alert__hint {
  margin: 0;
  padding-top: 2.6cqi;
  text-align: center;
  font-size: 3cqi;
  font-weight: 600;
  letter-spacing: var(--sys-track-meta);
  text-transform: uppercase;
  color: rgb(var(--sys-rgb-hairline) / 0.55);
  text-shadow: 0 0 14px rgb(var(--sys-rgb-glow) / 0.45);
}

/* ---------------------------------------------------------------------------
   5. TOAST — the same language, quieter, stacked, self-dismissing.
   ------------------------------------------------------------------------ */

.sys-toast {
  width: 100%;
  pointer-events: auto;
}

/* Toasts drop the tall reference proportions — they are a strip, not a window. */
.sys-toast .sys-frame {
  padding: 3.4cqi 4cqi;
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: 3.4cqi;
}
/* A toast body stacks an optional eyebrow ABOVE the message, so it is a column.
   It inherited `display:flex; align-items:center` from .sys-body, which made
   the eyebrow a horizontal SIBLING of the message at a measured 0.00px gap —
   rendering "WARNINGHP IS LOW." and "ITEMRED POTION X5 ACQUIRED.". */
.sys-toast .sys-body {
  min-height: 0;
  padding: 0;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  text-align: left;
}
.sys-toast .sys-message {
  font-size: 5.4cqi;
  line-height: 1.3;
}
.sys-toast__mark {
  flex: none;
  display: grid;
  place-items: center;
  width: 8cqi;
  height: 8cqi;
  border-radius: 50%;
  color: var(--sys-ink);
  font-size: 4.4cqi;
  font-weight: 700;
  line-height: 1;
  background: rgb(var(--sys-rgb-lift) / 0.26);
  box-shadow:
    inset 0 0 0 1px rgb(var(--sys-rgb-hairline) / 0.55),
    0 0 14px rgb(var(--sys-rgb-lift) / 0.30);
  text-shadow: var(--sys-text-glow);
}
/* The eyebrow above a toast's message.
   `display:block` is not enough: inside a toast the parent `.sys-body` computes
   `display:flex; align-items:center`, which makes this a flex SIBLING of the
   message rather than a block above it. The measured horizontal gap was
   0.00px, rendering "WARNINGHP IS LOW." and "ITEMRED POTION X5 ACQUIRED."
   `flex-basis:100%` forces the wrap so it occupies its own line again, and the
   margin then does what it was always meant to do. */
.sys-toast__label {
  display: block;
  flex: 0 0 100%;
  max-width: 100%;
  margin-bottom: 0.6cqi;
  font-size: 3.2cqi;
  font-weight: 700;
  letter-spacing: var(--sys-track-meta);
  text-transform: uppercase;
  color: rgb(var(--sys-rgb-hairline) / 0.78);
}

/* Auto-dismiss progress. Purely decorative; the timer is authoritative. */
.sys-toast__life {
  position: absolute;
  left: 3.6cqi;
  right: 3.6cqi;
  bottom: 1.6cqi;
  height: 1.5px;
  border-radius: 2px;
  overflow: hidden;
  background: rgb(var(--sys-rgb-hairline) / 0.18);
}
.sys-toast__life > i {
  display: block;
  height: 100%;
  width: 100%;
  transform-origin: left center;
  background: linear-gradient(90deg,
    rgb(var(--sys-rgb-hairline) / 0.95),
    rgb(var(--sys-rgb-glow) / 0.75));
  box-shadow: 0 0 8px rgb(var(--sys-rgb-glow) / 0.7);
  animation: sys-life linear forwards;
  animation-duration: var(--sys-life, 4000ms);
}

/* Semantic toast variants recolour only the mark + accent, never the panel —
   the panel is the system's identity and stays blue. */
.sys-toast[data-variant="success"] .sys-toast__mark { color: var(--sys-accent); }
.sys-toast[data-variant="warn"]    .sys-toast__mark { color: var(--sys-accent-gold); }
.sys-toast[data-variant="danger"]  .sys-toast__mark { color: var(--sys-accent-red); }
.sys-toast[data-variant="quest"]   .sys-toast__mark { color: var(--sys-accent-violet); }

/* ---------------------------------------------------------------------------
   6. WINDOW PANEL — persistent, draggable-by-header, minimisable.
   This is the surface the AI-companion lane builds on. See CONTRACT.md.
   ------------------------------------------------------------------------ */

.sys-window {
  position: absolute;
  width: min(var(--sys-panel-w), calc(100vw - var(--sys-edge) * 2));
  max-height: calc(100vh - var(--sys-edge) * 2);
  pointer-events: auto;
  display: flex;
}
.sys-window > .sys-panel { display: flex; flex-direction: column; min-height: 0; }
.sys-window .sys-frame  { min-height: 0; padding: 3.4cqi; gap: 3.2cqi; }

/* Dock positions. Consumers pass dock:'bottom-left' etc. */
.sys-window[data-dock="center"]       { top: 50%; left: 50%; transform: translate(-50%, -50%); }
.sys-window[data-dock="top-left"]     { top: var(--sys-edge);    left: var(--sys-edge); }
.sys-window[data-dock="top-right"]    { top: var(--sys-edge);    right: var(--sys-edge); }
.sys-window[data-dock="bottom-left"]  { bottom: var(--sys-edge); left: var(--sys-edge); }
.sys-window[data-dock="bottom-right"] { bottom: var(--sys-edge); right: var(--sys-edge); }

.sys-window .sys-head { justify-content: flex-start; padding-right: 1.4cqi; }
.sys-window .sys-head__title { flex: 1; text-align: left; font-size: 4.6cqi; }
.sys-window .sys-head__icon  { width: 5cqi; height: 5cqi; border-width: 0.46cqi; }

.sys-head__tools { display: flex; align-items: center; gap: 1cqi; flex: none; }

.sys-iconbtn {
  appearance: none;
  display: grid;
  place-items: center;
  width: 6.2cqi;
  height: 6.2cqi;
  min-width: 26px;
  min-height: 26px;
  padding: 0;
  border: 1px solid rgb(var(--sys-rgb-hairline) / 0.45);
  border-radius: 6px;
  background: rgb(var(--sys-rgb-lift) / 0.16);
  color: var(--sys-ink);
  font: inherit;
  font-size: 3.4cqi;
  line-height: 1;
  cursor: pointer;
  transition: background 140ms var(--sys-ease-settle),
              border-color 140ms var(--sys-ease-settle),
              box-shadow 140ms var(--sys-ease-settle);
}
.sys-iconbtn:hover {
  background: rgb(var(--sys-rgb-lift) / 0.34);
  border-color: rgb(var(--sys-rgb-hairline) / 0.85);
  box-shadow: 0 0 14px rgb(var(--sys-rgb-glow) / 0.55);
}

/* -- 6.1 Window body ------------------------------------------------------*/
.sys-window .sys-body {
  min-height: 0;
  padding: 0;
  display: block;
  text-align: left;
  overflow: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: rgb(var(--sys-rgb-hairline) / 0.45) transparent;
}
.sys-window .sys-body::-webkit-scrollbar { width: 8px; height: 8px; }
.sys-window .sys-body::-webkit-scrollbar-thumb {
  background: rgb(var(--sys-rgb-hairline) / 0.40);
  border-radius: 8px;
}
.sys-window .sys-body::-webkit-scrollbar-track { background: transparent; }

/* Prose inside a window is sentence case and lighter — caps are for the
   system's own voice, not for arbitrary content. */
.sys-prose {
  margin: 0;
  font-size: 3.5cqi;
  font-weight: 400;
  line-height: 1.6;
  letter-spacing: 0.005em;
  color: var(--sys-ink-dim);
  text-transform: none;
  text-shadow: none;
}
.sys-prose > * + * { margin-top: 0.8em; }
.sys-prose strong  { color: var(--sys-ink); font-weight: 700; }
.sys-prose code {
  font-family: var(--sys-font-mono);
  font-size: 0.92em;
  padding: 0.1em 0.4em;
  border-radius: 4px;
  background: rgb(4 12 40 / 0.45);
  box-shadow: inset 0 0 0 1px rgb(var(--sys-rgb-hairline) / 0.22);
}

/* -- 6.2 Stat rows — a common in-game panel need --------------------------*/
.sys-stats { display: grid; gap: 1.6cqi; margin: 0; }
.sys-stat  { display: flex; align-items: baseline; justify-content: space-between; gap: 2cqi; }
.sys-stat__k {
  font-size: 3.1cqi; font-weight: 700; letter-spacing: var(--sys-track-meta);
  text-transform: uppercase; color: rgb(var(--sys-rgb-hairline) / 0.72);
}
.sys-stat__dots { flex: 1; height: 1px; background: repeating-linear-gradient(
  90deg, rgb(var(--sys-rgb-hairline) / 0.35) 0 2px, transparent 2px 6px); }
.sys-stat__v {
  font-size: 3.6cqi; font-weight: 700; letter-spacing: 0.02em;
  color: var(--sys-ink); text-shadow: var(--sys-text-glow);
}

/* -- 6.3 Actions ----------------------------------------------------------*/
.sys-actions {
  flex: none;
  display: flex;
  flex-wrap: wrap;
  gap: 2.2cqi;
  justify-content: flex-end;
}
.sys-actions[data-align="center"]  { justify-content: center; }
.sys-actions[data-align="stretch"] > .sys-btn { flex: 1; }

.sys-btn {
  appearance: none;
  position: relative;
  padding: 2.2cqi 4.4cqi;
  min-height: 34px;
  border-radius: var(--sys-r-btn);
  border: 1px solid rgb(var(--sys-rgb-hairline) / 0.55);
  background: linear-gradient(180deg,
    rgb(var(--sys-rgb-lift) / 0.30) 0%,
    rgb(var(--sys-rgb-lift) / 0.14) 100%);
  color: var(--sys-ink);
  font-family: inherit;
  font-size: 3.4cqi;
  font-weight: 700;
  letter-spacing: var(--sys-track-meta);
  text-transform: uppercase;
  text-shadow: 0 0 10px rgb(var(--sys-rgb-hairline) / 0.45);
  cursor: pointer;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.22),
              0 0 0 rgb(var(--sys-rgb-glow) / 0);
  transition: background 160ms var(--sys-ease-settle),
              box-shadow 160ms var(--sys-ease-settle),
              border-color 160ms var(--sys-ease-settle),
              transform 160ms var(--sys-ease-settle);
}
.sys-btn:hover {
  background: linear-gradient(180deg,
    rgb(var(--sys-rgb-lift) / 0.48) 0%,
    rgb(var(--sys-rgb-lift) / 0.24) 100%);
  border-color: rgb(var(--sys-rgb-hairline) / 0.95);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.3),
              0 0 20px rgb(var(--sys-rgb-glow) / 0.6);
}
.sys-btn:active { transform: translateY(1px); }

.sys-btn[data-variant="primary"] {
  border-color: rgb(var(--sys-rgb-accent) / 0.75);
  background: linear-gradient(180deg,
    rgb(var(--sys-rgb-accent) / 0.30) 0%,
    rgb(var(--sys-rgb-accent) / 0.12) 100%);
  color: #ecffe8;
  text-shadow: var(--sys-accent-glow);
}
.sys-btn[data-variant="primary"]:hover {
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.28),
              0 0 22px rgb(var(--sys-rgb-accent) / 0.55);
}
.sys-btn[data-variant="ghost"] {
  background: transparent;
  border-color: rgb(var(--sys-rgb-hairline) / 0.32);
  color: rgb(var(--sys-rgb-hairline) / 0.82);
}

/* -- 6.4 Minimised dock chip (bottom-left) --------------------------------
   The state the AI-companion lane docks into. Fixed pixel type here on
   purpose: a chip must stay legible and tappable, not scale with a container. */
.sys-chip {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 9px 14px 9px 10px;
  min-height: 44px;
  border-radius: var(--sys-r-chip);
  border: 1px solid rgb(var(--sys-rgb-hairline) / 0.55);
  background:
    linear-gradient(180deg, rgb(21 66 190 / 0.94), rgb(16 45 150 / 0.94));
  color: var(--sys-ink);
  font-family: var(--sys-font);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: var(--sys-track-meta);
  text-transform: uppercase;
  cursor: pointer;
  pointer-events: auto;
  backdrop-filter: var(--sys-backdrop-blur);
  -webkit-backdrop-filter: var(--sys-backdrop-blur);
  box-shadow:
    inset 0 0 0 1px rgb(var(--sys-rgb-hairline) / 0.20),
    inset 0 0 18px rgb(var(--sys-rgb-glow) / 0.35),
    var(--sys-glow-1), var(--sys-glow-2), var(--sys-glow-3);
  transition: box-shadow 160ms var(--sys-ease-settle),
              transform 160ms var(--sys-ease-settle);
}
.sys-chip:hover { transform: translateY(-2px); }
.sys-chip:hover,
.sys-chip[data-unread]:not([data-unread="0"]) {
  box-shadow:
    inset 0 0 0 1px rgb(var(--sys-rgb-hairline) / 0.28),
    inset 0 0 20px rgb(var(--sys-rgb-glow) / 0.45),
    var(--sys-glow-1), var(--sys-glow-2), var(--sys-glow-3), var(--sys-glow-4);
}
.sys-chip__dot {
  flex: none;
  width: 24px; height: 24px;
  display: grid; place-items: center;
  border-radius: 50%;
  border: 2px solid currentColor;
  font-size: 12px;
  filter: drop-shadow(0 0 6px rgb(var(--sys-rgb-hairline) / 0.7));
}
.sys-chip__badge {
  flex: none;
  min-width: 20px; height: 20px; padding: 0 6px;
  display: grid; place-items: center;
  border-radius: 10px;
  background: var(--sys-accent);
  color: #04240a;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0;
  box-shadow: 0 0 12px rgb(var(--sys-rgb-accent) / 0.7);
}
.sys-chip[data-unread="0"] .sys-chip__badge,
.sys-chip:not([data-unread]) .sys-chip__badge { display: none; }

/* ---------------------------------------------------------------------------
   7. TRANSCRIPT — chat rendering inside a .sys-panel. Contract surface for
   the AI-companion lane.
   ------------------------------------------------------------------------ */

.sys-transcript {
  display: flex;
  flex-direction: column;
  gap: 2.4cqi;
  padding: 0.5cqi 0;
}
.sys-msg { display: flex; flex-direction: column; gap: 0.8cqi; max-width: 88%; }
.sys-msg[data-from="you"] { align-self: flex-end; align-items: flex-end; }

.sys-msg__who {
  font-size: 2.8cqi;
  font-weight: 700;
  letter-spacing: var(--sys-track-meta);
  text-transform: uppercase;
  color: rgb(var(--sys-rgb-hairline) / 0.68);
}
.sys-msg[data-from="system"] .sys-msg__who { color: var(--sys-accent); }

.sys-msg__text {
  padding: 2.2cqi 2.8cqi;
  border-radius: 10px;
  font-size: 3.4cqi;
  font-weight: 400;
  line-height: 1.55;
  text-transform: none;
  color: var(--sys-ink-dim);
  background: rgb(6 18 58 / 0.42);
  box-shadow: inset 0 0 0 1px rgb(var(--sys-rgb-hairline) / 0.20);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.sys-msg[data-from="you"] .sys-msg__text {
  background: rgb(var(--sys-rgb-lift) / 0.22);
  color: var(--sys-ink);
  box-shadow: inset 0 0 0 1px rgb(var(--sys-rgb-hairline) / 0.34);
}
.sys-msg[data-from="system"] .sys-msg__text {
  box-shadow: inset 0 0 0 1px rgb(var(--sys-rgb-accent) / 0.28);
}

/* Typing indicator the companion lane can toggle. */
.sys-typing { display: inline-flex; gap: 5px; align-items: center; padding: 3px 0; }
.sys-typing i {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--sys-accent);
  box-shadow: 0 0 8px rgb(var(--sys-rgb-accent) / 0.8);
  animation: sys-typing 1.1s ease-in-out infinite;
}
.sys-typing i:nth-child(2) { animation-delay: 0.15s; }
.sys-typing i:nth-child(3) { animation-delay: 0.30s; }

/* ---------------------------------------------------------------------------
   8. FOCUS — visible, on-brand, and never removed.
   ------------------------------------------------------------------------ */

/* Scoped to the overlay, but also bound directly to the controls so a lane can
   reuse .sys-btn / .sys-iconbtn / .sys-chip outside our root and still get the
   focus treatment. */
.sys-root :focus-visible,
.sys-btn:focus-visible,
.sys-iconbtn:focus-visible,
.sys-chip:focus-visible {
  outline: 2px solid var(--sys-hairline);
  outline-offset: 3px;
  border-radius: inherit;
  box-shadow: 0 0 0 5px rgb(var(--sys-rgb-glow) / 0.35),
              0 0 22px rgb(var(--sys-rgb-glow) / 0.55);
}
/* Panels are focused programmatically on open so Esc and Tab have somewhere to
   land. That must not draw a ring — and it must clear the box-shadow too, not
   just the outline: the shadow above has a 5px spread, which on a .sys-alert
   wrapper paints a hard bright band 5px outside the panel and destroys the
   glow ramp. Clearing only `outline` leaves that band behind. */
.sys-root [tabindex="-1"]:focus,
.sys-root [tabindex="-1"]:focus-visible { outline: none; box-shadow: none; }

.sys-sr {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ---------------------------------------------------------------------------
   9. MOTION
   ------------------------------------------------------------------------ */

@keyframes sys-arrive {
  0%   { opacity: 0; transform: scale(0.82); }
  46%  { opacity: 1; transform: scale(1.035); }
  74%  { transform: scale(0.993); }
  100% { opacity: 1; transform: scale(1); }
}
@keyframes sys-bloom {
  0%   { opacity: 0;    transform: scale(0.55); }
  16%  { opacity: 1;    transform: scale(1.28); }
  46%  { opacity: 0.78; transform: scale(1.06); }
  100% { opacity: 0.9;  transform: scale(1); }
}
@keyframes sys-flash {
  0%   { opacity: 0; }
  10%  { opacity: 0.95; }
  100% { opacity: 0; }
}
@keyframes sys-depart {
  0%   { opacity: 1; transform: scale(1); filter: blur(0); }
  100% { opacity: 0; transform: scale(0.94); filter: blur(3px); }
}
@keyframes sys-slide-in {
  0%   { opacity: 0; transform: translateX(26px) scale(0.94); }
  60%  { opacity: 1; transform: translateX(-3px) scale(1.005); }
  100% { opacity: 1; transform: translateX(0) scale(1); }
}
@keyframes sys-slide-out {
  0%   { opacity: 1; transform: translateX(0) scale(1); max-height: 40vh; }
  100% { opacity: 0; transform: translateX(30px) scale(0.94); max-height: 0; }
}
@keyframes sys-chip-in {
  0%   { opacity: 0; transform: translateY(14px) scale(0.9); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes sys-scrim-in  { to { opacity: 1; } }
@keyframes sys-scrim-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes sys-life      { from { transform: scaleX(1); } to { transform: scaleX(0); } }
@keyframes sys-typing {
  0%, 60%, 100% { opacity: 0.25; transform: translateY(0); }
  30%           { opacity: 1;    transform: translateY(-3px); }
}

/* Applied by JS on mount. */
.sys-anim-arrive               { animation: sys-arrive var(--sys-dur-in) var(--sys-ease-arrive) both; }
.sys-anim-arrive .sys-panel__bloom { animation: sys-bloom var(--sys-dur-bloom) var(--sys-ease-settle) both; }
.sys-anim-arrive .sys-panel__flash { animation: sys-flash var(--sys-dur-flash) var(--sys-ease-settle) both; }
.sys-anim-slide                { animation: sys-slide-in var(--sys-dur-in) var(--sys-ease-arrive) both; }
.sys-anim-chip                 { animation: sys-chip-in var(--sys-dur-in) var(--sys-ease-arrive) both; }

[data-sys-closing].sys-anim-arrive,
[data-sys-closing]:not(.sys-anim-slide) { animation: sys-depart var(--sys-dur-out) var(--sys-ease-exit) both; }
[data-sys-closing].sys-anim-slide       { animation: sys-slide-out var(--sys-dur-out) var(--sys-ease-exit) both; }

/* A centred window must keep its centring transform while animating. */
.sys-window[data-dock="center"].sys-anim-arrive { animation-name: sys-arrive-centered; }
.sys-window[data-dock="center"][data-sys-closing] { animation-name: sys-depart-centered; }
@keyframes sys-arrive-centered {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.82); }
  46%  { opacity: 1; transform: translate(-50%, -50%) scale(1.035); }
  74%  { transform: translate(-50%, -50%) scale(0.993); }
  100% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
@keyframes sys-depart-centered {
  0%   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(0.94); }
}

/* -- 9.1 Reduced motion ---------------------------------------------------
   Honour the OS setting. Everything still appears and disappears, and the
   auto-dismiss timers still run — only the movement is removed.            */
@media (prefers-reduced-motion: reduce) {
  :root {
    --sys-dur-in: 130ms;
    --sys-dur-out: 110ms;
    --sys-dur-bloom: 130ms;
    --sys-dur-flash: 1ms;
  }
  .sys-anim-arrive,
  .sys-anim-slide,
  .sys-anim-chip,
  .sys-window[data-dock="center"].sys-anim-arrive { animation: sys-fade var(--sys-dur-in) linear both; }
  .sys-anim-arrive .sys-panel__bloom { animation: none; opacity: 0.9; }
  .sys-anim-arrive .sys-panel__flash { animation: none; opacity: 0; }
  [data-sys-closing],
  [data-sys-closing].sys-anim-arrive,
  [data-sys-closing].sys-anim-slide,
  .sys-window[data-dock="center"][data-sys-closing] {
    animation: sys-fade-out var(--sys-dur-out) linear both;
  }
  .sys-typing i { animation: none; opacity: 0.7; }
  @keyframes sys-fade     { from { opacity: 0; } to { opacity: 1; } }
  @keyframes sys-fade-out { from { opacity: 1; } to { opacity: 0; } }
  .sys-root * { transition-duration: 1ms !important; }
}

/* ---------------------------------------------------------------------------
   10. RESPONSIVE
   The kit is container-scaled, so it mostly takes care of itself. These are
   the handful of decisions that are about the *viewport*, not the panel.
   ------------------------------------------------------------------------ */

@media (max-width: 560px) {
  :root {
    --sys-alert-w: 100%;
    --sys-toast-w: 100%;
    --sys-panel-w: 100%;
    --sys-edge: 12px;
  }
  /* Toasts move to the top edge full-width; a right-hand stack is unusable
     on a 390px screen held one-handed. */
  .sys-layer--toast { left: var(--sys-edge); right: var(--sys-edge); }
  /* A window at phone width is effectively a sheet — pin it to the bottom. */
  .sys-window[data-dock="center"] {
    top: auto; bottom: var(--sys-edge); left: var(--sys-edge); right: var(--sys-edge);
    transform: none; width: auto;
  }
  .sys-window[data-dock="center"].sys-anim-arrive { animation-name: sys-arrive; }
  .sys-window[data-dock="center"][data-sys-closing] { animation-name: sys-depart; }
  .sys-window[data-dock="top-left"], .sys-window[data-dock="top-right"],
  .sys-window[data-dock="bottom-left"], .sys-window[data-dock="bottom-right"] {
    left: var(--sys-edge); right: var(--sys-edge); width: auto;
  }
  .sys-chip { font-size: 11px; }
}

/* Coarse pointers get bigger hit targets without changing the look. */
@media (pointer: coarse) {
  .sys-iconbtn { min-width: 34px; min-height: 34px; }
  .sys-btn     { min-height: 42px; }
}

/* Very short viewports (landscape phone over the game): shrink the alert's
   generous reference proportions so it never exceeds the screen. */
@media (max-height: 520px) {
  .sys-frame { padding: 6cqi 4.6cqi 0; }
  .sys-body  { min-height: 0; padding: 5cqi 0; }
}

/* ---------------------------------------------------------------------------
   11. HOST-PAGE HARDENING
   The kit lives in the parent page of /play, above <iframe id="ro-engine-frame">.
   It must not be affected by, and must not affect, the host.
   ------------------------------------------------------------------------ */

.sys-root img,
.sys-root svg { max-width: 100%; }
.sys-root button,
.sys-root input,
.sys-root textarea { font-family: inherit; }

/* ---- HOST DEFENCE -------------------------------------------------------
   The kit is documented as drop-in safe, and on `/play` it was not. The host
   page's own stylesheet carries
       button:not(.rag-bare):not(…)x6      specificity (0,8,1)
   because every `:not()` argument contributes its own specificity. `.sys-btn`
   is (0,1,0) and even `.sys-root button` is only (0,1,1), so every kit button
   rendered in the host's oxblood/Cinzel with clipped labels.

   Nesting cannot win this — (0,8,1) is out of reach for readable selectors —
   and `@layer` would make it WORSE, since unlayered host rules beat layered
   ones. So these few properties are defended explicitly. They are deliberately
   the minimum set a host is likely to clobber on a generic `button`/`input`,
   and they are scoped inside `.sys-root`, so nothing here can escape into the
   host's own controls.

   This defends against ANY host, not just this one — the kit must not have to
   know its host's class names to work. */
.sys-root .sys-btn,
.sys-root .sys-chip,
.sys-root button.sys-btn,
.sys-root input,
.sys-root textarea {
  font-family: var(--sys-font) !important;
  text-transform: none !important;
  letter-spacing: normal !important;
  box-sizing: border-box !important;
}
.sys-root .sys-btn,
.sys-root button.sys-btn {
  background-image: none !important;
  text-shadow: none !important;
  width: auto !important;
  min-width: 0 !important;
}
/* Never inherit a host `text-transform`/`direction` surprise into prose. */
.sys-prose, .sys-msg__text { direction: ltr; }

/* Print: the overlay is a live game surface; do not print it. */
@media print { .sys-root { display: none !important; } }

/* Forced-colours (Windows high contrast): keep structure, drop the glow. */
@media (forced-colors: active) {
  .sys-panel { background: Canvas; box-shadow: none; border: 1px solid CanvasText; }
  .sys-panel__bloom, .sys-panel__flash { display: none; }
  .sys-frame { border-color: CanvasText; box-shadow: none; }
  .sys-message, .sys-head__title { text-shadow: none; }
  .sys-accent { color: LinkText; text-shadow: none; }
}
