/* strmrx-core UI — shared, brand-neutral CSS for every StrmrX app.
   STRMRX_UI_VERSION 0.3.0  (keep in lockstep with core.js SX.uiVersion)

   0.3.0 — the StrmrX Design System lands in core (canonical home). Adds the Motion
   System tokens (--sx-ease-*, --sx-spring*, --sx-dur-*, --sx-stagger), the six
   data-motion personalities, the family aurora-glow + faint-grid backdrop
   (--sx-glow-1/2), the premium primitives (ripple, stacked toast, skeleton,
   [data-reveal] stagger), the pill component set (.sx-btn/.sx-card/.sx-chip/
   .sx-meter/.sx-topbar), the mobile bar, and prefers-reduced-motion collapse.
   Reference implementation this was merged from: collectx core.css.

   Distribution: canonical here → tools/sync.ps1 copies it into each app's docroot
   (same "no Composer on SiteGround" model as strmrx-core.php). Every app loads this
   BEFORE its own style.css, on EVERY shell (landing, dashboard, admin, overlay…),
   and overrides only the token VALUES in its :root skin.

   How an app skins it (in its own style.css :root): map the app's brand palette onto
   the --sx-* contract, e.g.  :root{ --sx-accent:var(--blood); --sx-bg:var(--bg); … }
   The app keeps its brand vars (--accent, --bg, …) for app-specific CSS; the --sx-*
   mapping is the bridge that drives all the shared chrome below. New in 0.3.0: also
   set --sx-glow-1/2 to steer the backdrop's flavour, and declare ONE motion
   personality on <html data-motion="…"> (default = friendly).

   @layer order means everything here is OVERRIDABLE by an app's own unlayered rules —
   so promoting chrome into core can never regress an app, and an app drops its now-
   duplicate rule to let core own it. The app's font @import stays in its style.css. */
@layer sx-tokens, sx-base, sx-components, sx-overlay;

/* ── Tokens ──────────────────────────────────────────────────────────────────
   Neutral defaults. Each app re-declares these in its own :root skin — usually by
   mapping its existing brand vars onto them, e.g.  :root{ --sx-accent:var(--blood) } */
@layer sx-tokens{
  :root{
    /* surfaces */
    --sx-bg:#0a0a0c; --sx-surface:#15151b; --sx-surface2:#1c1c24;
    --sx-border:#2a2230; --sx-border2:#3a3040;
    /* text */
    --sx-text:#e9e3e6; --sx-muted:#8b8390; --sx-muted2:#a89faa; --sx-faint:#626c7d;
    /* accent + status */
    --sx-accent:#c41e3a; --sx-accent2:#ff6b35;
    --sx-accent-ink:#ffffff; --sx-accent-soft:#241016; --sx-accent-hover:#d6263f;
    --sx-ok:#3ddc84; --sx-danger:#f87171;
    /* shape + type + depth */
    --sx-radius:16px; --sx-radius-sm:10px;
    --sx-shadow:0 8px 30px rgba(0,0,0,.35);
    --sx-page-max:1120px;
    --sx-font:'Space Grotesk',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
    --sx-mono:'JetBrains Mono',ui-monospace,monospace;
    /* twitch brand (constant across apps) */
    --sx-twitch:#9146FF;

    /* backdrop — the family "world": aurora glows + faint grid, tinted per app.
       An app skin re-points the glow hues; hi/lo default to derived neutrals. */
    --sx-glow-1:var(--sx-accent);
    --sx-glow-2:var(--sx-accent2,var(--sx-accent));
    --sx-bg-hi:color-mix(in srgb,var(--sx-bg) 90%,#aab6ff);
    --sx-bg-lo:color-mix(in srgb,var(--sx-bg) 78%,#000);

    /* StrmrX Motion System — premium motion = feathered easing + a HAIR of
       overshoot-and-settle + asymmetric enter/exit. Every interactive element uses
       these tokens, never ad-hoc timings. Full rules in the sx-design skill. */
    --sx-ease:          cubic-bezier(.2,0,0,1);        /* standard default (decelerating) */
    --sx-ease-out:      cubic-bezier(.05,.7,.1,1);     /* emphasized decelerate — enters / settle */
    --sx-ease-in:       cubic-bezier(.3,0,.8,.15);     /* accelerate — exits (get out of the way) */
    --sx-ease-in-out:   cubic-bezier(.65,.05,.36,1);   /* feathered both ends — on-screen reflow */
    --sx-spring:        cubic-bezier(.34,1.3,.64,1);   /* premium pop: overshoot ~a hair, settle */
    --sx-spring-strong: cubic-bezier(.34,1.56,.64,1);  /* livelier bounce — rare delight moments only */
    --sx-dur-fast: .14s;   /* taps, toggles, hovers */
    --sx-dur-base: .24s;   /* popovers, menus, small enters */
    --sx-dur-slow: .34s;   /* modals, sheets, cards */
    --sx-dur-in:   .24s;   /* enter — springy */
    --sx-dur-out:  .42s;   /* exit / settle-back — slower + smooth (asymmetry reads premium) */
    --sx-stagger:  40ms;   /* per-item cascade delay (cap ~8 items) */
    --sx-press-scale: .96; /* how much a control compresses on :active */
    --sx-dur: .16s; --sx-dur-lg: .3s;  /* legacy aliases */

    /* back-compat aliases for already-shipped components / app skins */
    --sx-panel:var(--sx-surface); --sx-panel2:var(--sx-surface2); --sx-line:var(--sx-border);
    --sx-surface-2:var(--sx-surface2); /* collectx-era spelling */
  }

  /* ── Motion Personality — the app's *voice*. The mechanical tokens above are the
     grammar; a personality retunes the same dials to match the app's feeling. Set it
     once on <html data-motion="…"> and every build feels distinct but still premium.
     (Full guidance — per-moment energy, rules — in the sx-design skill.) */
  [data-motion="precise"]{   /* dashboards, finance, dev tools — exact, trustworthy */
    --sx-spring:cubic-bezier(.2,0,0,1); --sx-ease-out:cubic-bezier(.2,0,0,1); --sx-ease-in:cubic-bezier(.4,0,1,1);
    --sx-dur-in:.16s; --sx-dur-out:.28s; --sx-stagger:0ms; --sx-press-scale:.985;
  }
  [data-motion="calm"]{      /* wellness, luxury, reading — unhurried, elegant */
    --sx-spring:cubic-bezier(.34,1.1,.6,1); --sx-ease-out:cubic-bezier(.33,0,.2,1); --sx-ease-in:cubic-bezier(.4,0,.4,1);
    --sx-dur-in:.34s; --sx-dur-out:.52s; --sx-stagger:60ms; --sx-press-scale:.985;
  }
  [data-motion="friendly"]{  /* consumer / community / streamer tools — StrmrX default */
    --sx-spring:cubic-bezier(.34,1.3,.5,1); --sx-ease-out:cubic-bezier(.2,0,0,1); --sx-ease-in:cubic-bezier(.4,0,.6,1);
    --sx-dur-in:.24s; --sx-dur-out:.42s; --sx-stagger:40ms; --sx-press-scale:.96;
  }
  [data-motion="playful"]{   /* games, kids, delight-forward — bouncy, toy-like */
    --sx-spring:cubic-bezier(.34,1.56,.64,1); --sx-ease-out:cubic-bezier(.2,0,0,1); --sx-ease-in:cubic-bezier(.5,0,.75,0);
    --sx-dur-in:.26s; --sx-dur-out:.44s; --sx-stagger:55ms; --sx-press-scale:.93;
  }
  [data-motion="energetic"]{ /* live overlays, hype, sports — fast, punchy, bold */
    --sx-spring:cubic-bezier(.22,1.4,.36,1); --sx-ease-out:cubic-bezier(.15,.9,.2,1); --sx-ease-in:cubic-bezier(.55,0,1,.45);
    --sx-dur-in:.18s; --sx-dur-out:.32s; --sx-stagger:25ms; --sx-press-scale:.95;
  }
  [data-motion="confident"]{ /* bold brand, marketing hero — assertive, stable, no wobble */
    --sx-spring:cubic-bezier(.16,1.1,.3,1); --sx-ease-out:cubic-bezier(.16,1,.3,1); --sx-ease-in:cubic-bezier(.7,0,.84,0);
    --sx-dur-in:.26s; --sx-dur-out:.4s; --sx-stagger:45ms; --sx-press-scale:.97;
  }
}

/* ── Base ─────────────────────────────────────────────────────────────────────
   The reset + page defaults every app repeated verbatim. Overlay pages override
   body via their own unlayered `body.sx-overlay` / `body.overlay` rule. */
@layer sx-base{
  *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
  html{scroll-behavior:smooth}
  body{font-family:var(--sx-font);background:var(--sx-bg);color:var(--sx-text);
    line-height:1.6;overflow-x:hidden;position:relative;min-height:100vh;
    -webkit-font-smoothing:antialiased}
  a{color:var(--sx-accent);text-decoration:none}
  .mono{font-family:var(--sx-mono)}
  button:disabled{opacity:.6;cursor:not-allowed}
  ::-webkit-scrollbar{width:7px}
  ::-webkit-scrollbar-track{background:var(--sx-surface)}
  ::-webkit-scrollbar-thumb{background:var(--sx-accent);border-radius:4px}

  /* The StrmrX "world" backdrop: same structure family-wide — aurora glows + a
     faint grid — but tinted by each app's accent so builds feel related without
     being identical. An app skin sets --sx-glow-1/2 to steer its own flavour.
     Suppressed on OBS overlay shells (transparent sources must stay transparent). */
  body::before{
    content:'';position:fixed;inset:0;z-index:-2;pointer-events:none;
    background:
      radial-gradient(52% 46% at 6% -6%, color-mix(in srgb,var(--sx-glow-1) 30%,transparent), transparent 62%),
      radial-gradient(46% 42% at 101% 6%, color-mix(in srgb,var(--sx-glow-2) 24%,transparent), transparent 60%),
      radial-gradient(62% 58% at 50% 118%, color-mix(in srgb,var(--sx-glow-1) 18%,transparent), transparent 60%),
      linear-gradient(180deg, var(--sx-bg-hi), var(--sx-bg) 42%, var(--sx-bg-lo));
  }
  body::after{
    content:'';position:fixed;inset:0;z-index:-1;pointer-events:none;
    background-image:radial-gradient(circle, color-mix(in srgb,var(--sx-text) 8%,transparent) 1px, transparent 1.5px);
    background-size:30px 30px;
    -webkit-mask-image:radial-gradient(92% 72% at 50% 22%, #000 0%, transparent 80%);
            mask-image:radial-gradient(92% 72% at 50% 22%, #000 0%, transparent 80%);
    opacity:.5;
  }
  body.sx-overlay::before,body.sx-overlay::after,
  body.overlay::before,body.overlay::after{content:none}
}

/* ── Components ───────────────────────────────────────────────────────────────*/
@layer sx-components{
  /* buttons — rectangular .btn family (PHP-era chrome, motion upgraded 0.3.0) */
  .btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;
    padding:11px 20px;border-radius:var(--sx-radius-sm);border:1px solid var(--sx-border2);
    background:var(--sx-surface);color:var(--sx-text);font-weight:600;font-size:14px;
    cursor:pointer;font-family:inherit;white-space:nowrap;position:relative;overflow:hidden;
    transition:transform var(--sx-dur-fast) var(--sx-spring),
      border-color var(--sx-dur-fast) var(--sx-ease),background var(--sx-dur-fast) var(--sx-ease),
      box-shadow var(--sx-dur-fast) var(--sx-ease),filter var(--sx-dur-fast) var(--sx-ease)}
  .btn:hover{border-color:var(--sx-accent);transform:translateY(-1px)}
  .btn:active{transform:scale(var(--sx-press-scale))}
  .btn-primary{background:var(--sx-accent);border-color:var(--sx-accent);color:var(--sx-accent-ink)}
  .btn-primary:hover{background:var(--sx-accent-hover);border-color:var(--sx-accent-hover)}
  .btn-block{width:100%}
  .btn-twitch{background:var(--sx-twitch);border-color:var(--sx-twitch);color:#fff}
  .btn-twitch:hover{background:#7d2ff7;border-color:#7d2ff7;box-shadow:0 0 22px rgba(145,70,255,.45)}
  .btn-danger{background:transparent;border-color:rgba(248,113,113,.4);color:#fca5a5}
  .btn-danger:hover{background:rgba(248,113,113,.12);border-color:var(--sx-danger)}
  .btn-ghost{background:transparent}
  .btn-sm{padding:7px 13px;font-size:13px;border-radius:8px}

  /* buttons — pill .sx-btn family (design-system chrome; new builds default here) */
  .sx-btn{display:inline-flex;align-items:center;gap:8px;
    padding:9px 16px;border-radius:999px;border:1px solid var(--sx-border);
    background:var(--sx-surface2);color:var(--sx-text);font:inherit;font-weight:600;
    cursor:pointer;white-space:nowrap;position:relative;overflow:hidden;
    transition:transform var(--sx-dur) var(--sx-spring),border-color var(--sx-dur) var(--sx-ease),
      background var(--sx-dur) var(--sx-ease),filter var(--sx-dur) var(--sx-ease)}
  .sx-btn:hover{border-color:color-mix(in srgb,var(--sx-accent) 60%,var(--sx-border));transform:translateY(-1px)}
  .sx-btn:active{transform:scale(var(--sx-press-scale))}
  .sx-btn-primary{background:var(--sx-accent);border-color:transparent;color:var(--sx-accent-ink)}
  .sx-btn-primary:hover{filter:brightness(1.08)}
  .sx-btn-ghost{background:transparent}
  .sx-btn-sm{padding:6px 12px;font-size:13px}

  /* top nav / wordmark — shared chrome on landing + app pages */
  .nav{position:sticky;top:0;z-index:50;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
    background:color-mix(in srgb,var(--sx-bg) 78%,transparent);border-bottom:1px solid var(--sx-border)}
  .nav-inner{max-width:var(--sx-page-max);margin:0 auto;padding:14px 24px;display:flex;align-items:center;gap:20px}
  .brand{font-size:22px;font-weight:700;letter-spacing:-.3px;display:flex;align-items:center;gap:9px}
  .brand .x,.brand b,.brand .b2{color:var(--sx-accent)}
  .brand .b1{color:var(--sx-text)}
  .brand svg{flex-shrink:0}
  .brand-mark{width:1.15em;height:1.15em;display:inline-block;vertical-align:-.2em;color:var(--sx-accent);flex-shrink:0}
  .nav-links{margin-left:auto;display:flex;gap:22px;align-items:center;font-size:14px}
  .nav-links a{color:var(--sx-muted2);transition:color var(--sx-dur-fast) var(--sx-ease)}
  .nav-links a:hover{color:var(--sx-text)}
  .nav-burger{display:none;margin-left:auto;background:none;border:none;color:var(--sx-text);font-size:22px;cursor:pointer}

  .app-nav{position:sticky;top:0;z-index:50;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
    background:color-mix(in srgb,var(--sx-bg) 82%,transparent);border-bottom:1px solid var(--sx-border)}
  .app-nav-inner{max-width:980px;margin:0 auto;padding:12px 24px;display:flex;align-items:center;gap:18px}
  .app-nav .spacer{margin-left:auto}

  /* topbar — design-system chrome variant (brand dot + accent X wordmark) */
  .sx-wrap{max-width:var(--sx-page-max);margin:0 auto;padding:0 20px}
  .sx-topbar{display:flex;align-items:center;gap:16px;padding:14px 0;
    border-bottom:1px solid var(--sx-border);position:sticky;top:0;z-index:20;
    background:color-mix(in srgb,var(--sx-bg) 82%,transparent);
    backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)}
  .sx-brand{display:flex;align-items:center;gap:10px;font-weight:800;letter-spacing:.2px;font-size:18px}
  .sx-brand .dot{width:20px;height:20px;border-radius:6px;
    background:linear-gradient(135deg,var(--sx-accent),var(--sx-accent2,var(--sx-accent)));
    box-shadow:0 0 14px color-mix(in srgb,var(--sx-accent) 55%,transparent)}
  .sx-brand b{color:var(--sx-accent)}
  .sx-spacer{flex:1}

  /* cards + chips + bits */
  .sx-card{background:var(--sx-surface);border:1px solid var(--sx-border);
    border-radius:var(--sx-radius);box-shadow:var(--sx-shadow)}
  .sx-chip{display:inline-flex;align-items:center;gap:6px;
    padding:3px 10px;border-radius:999px;font-size:12px;font-weight:700;
    background:var(--sx-surface2);border:1px solid var(--sx-border);color:var(--sx-muted)}
  .sx-mono{font-family:var(--sx-mono)}
  .sx-avatar{width:30px;height:30px;border-radius:50%;object-fit:cover;border:1px solid var(--sx-border)}
  .sx-meter{height:8px;border-radius:999px;background:var(--sx-surface2);overflow:hidden}
  .sx-meter > span{display:block;height:100%;border-radius:999px;transition:width var(--sx-dur-lg) var(--sx-ease-out)}

  /* form controls */
  label.field{display:block}
  .field span{display:block;font-size:12px;color:var(--sx-muted2);text-transform:uppercase;letter-spacing:.4px;margin:0 0 5px}
  input,select,textarea{width:100%;padding:11px 13px;border:1px solid var(--sx-border);border-radius:8px;
    background:var(--sx-surface2);color:var(--sx-text);font-size:15px;outline:none;font-family:inherit;color-scheme:dark;
    transition:border-color var(--sx-dur-fast) var(--sx-ease)}
  input:focus,select:focus,textarea:focus{border-color:var(--sx-accent)}
  select{cursor:pointer}

  /* base panel (apps layer their own decoration on top, e.g. HUD corners) */
  .panel{background:var(--sx-surface);border:1px solid var(--sx-border);border-radius:var(--sx-radius);padding:24px;margin-bottom:20px}

  /* tables (admin) */
  table{width:100%;border-collapse:collapse}
  thead th{text-align:left;font-size:12px;color:var(--sx-muted);text-transform:uppercase;letter-spacing:.5px;padding:0 12px 10px;border-bottom:1px solid var(--sx-border)}
  tbody td{padding:14px 12px;border-bottom:1px solid var(--sx-border);vertical-align:top;font-size:14px}
  tbody tr:last-child td{border-bottom:none}
  .tbl-wrap{overflow-x:auto}

  /* helpers */
  .muted{color:var(--sx-muted)}
  .small{font-size:12px}
  .hidden{display:none}

  /* ── toggle: replaces raw checkboxes everywhere. Markup is minimal — JS
     (SX.toggles) builds the switch + label wrapper; if JS never runs, the bare
     checkbox still works.
       <label class="sx-toggle" data-tip="Longer explanation on hover">
         <input type="checkbox" id="x" checked> Short label
       </label> */
  .sx-toggle{display:inline-flex;align-items:center;gap:10px;cursor:pointer;user-select:none;
    position:relative;font-weight:600;color:var(--sx-text);font-size:14px;line-height:1.3}
  .sx-toggle input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;margin:0}
  .sx-track{flex:none;width:40px;height:23px;border-radius:999px;background:#3a323f;position:relative;
    transition:background var(--sx-dur-fast) var(--sx-ease);box-shadow:inset 0 0 0 1px rgba(255,255,255,.06)}
  .sx-knob{position:absolute;top:2px;left:2px;width:19px;height:19px;border-radius:50%;background:#cfc7d2;
    transition:transform var(--sx-dur-base) var(--sx-spring),background var(--sx-dur-fast) var(--sx-ease);
    box-shadow:0 1px 3px rgba(0,0,0,.5)}
  .sx-toggle input:checked + .sx-track{background:var(--sx-accent)}
  .sx-toggle input:checked + .sx-track .sx-knob{transform:translateX(17px);background:#fff}
  .sx-toggle input:focus-visible + .sx-track{outline:2px solid var(--sx-accent);outline-offset:2px}
  .sx-toggle input:disabled + .sx-track{opacity:.4}
  .sx-label{font-weight:600}
  /* a small info affordance the app can drop in if a label needs a visible hint cue */
  .sx-info{flex:none;display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;
    border-radius:50%;font-size:10px;font-weight:800;color:var(--sx-muted);
    box-shadow:inset 0 0 0 1.4px currentColor;cursor:help}

  /* ── Hover/focus tooltip: any element with [data-tip] ── */
  [data-tip]{position:relative}
  [data-tip]::after{content:attr(data-tip);position:absolute;left:0;bottom:calc(100% + 9px);z-index:60;
    width:max-content;min-width:150px;max-width:280px;padding:8px 11px;border-radius:9px;
    background:#14121a;color:#ece6ea;font-family:var(--sx-font);font-size:12px;font-weight:500;line-height:1.45;
    white-space:normal;text-align:left;letter-spacing:0;text-transform:none;
    box-shadow:0 12px 34px -10px #000,inset 0 0 0 1px rgba(255,255,255,.09);
    opacity:0;transform:translateY(4px);pointer-events:none;
    transition:opacity var(--sx-dur-fast) var(--sx-ease),transform var(--sx-dur-fast) var(--sx-ease-out)}
  [data-tip]::before{content:"";position:absolute;left:16px;bottom:calc(100% + 4px);z-index:60;
    border:5px solid transparent;border-top-color:#14121a;opacity:0;transition:opacity var(--sx-dur-fast) var(--sx-ease)}
  [data-tip]:hover::after,[data-tip]:focus-within::after,
  [data-tip]:hover::before,[data-tip]:focus-within::before{opacity:1;transform:translateY(0)}

  /* ── Toast (SX.toast) — stacked pills, spring in / ease out ── */
  .sx-toasts{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);
    display:flex;flex-direction:column;gap:8px;z-index:200;align-items:center;pointer-events:none}
  .sx-toast{pointer-events:auto;background:var(--sx-surface2);border:1px solid var(--sx-border);
    color:var(--sx-text);padding:10px 16px;border-radius:999px;box-shadow:var(--sx-shadow);
    font-family:var(--sx-font);font-size:14px;font-weight:600;display:flex;align-items:center;gap:8px;
    animation:sx-toast-in var(--sx-dur-in) var(--sx-spring)}
  .sx-toast.out{animation:sx-toast-out var(--sx-dur-base) var(--sx-ease-in) forwards}
  .sx-toast .accent{color:var(--sx-accent)}
  @keyframes sx-toast-in{from{opacity:0;transform:translateY(12px) scale(.96)}}
  @keyframes sx-toast-out{to{opacity:0;transform:translateY(8px)}}

  /* ── premium primitives (used family-wide via core.js) ── */

  /* click ripple — core.js attaches to .btn / .sx-btn / [data-ripple] */
  .sx-ripple{position:absolute;border-radius:50%;transform:scale(0);background:rgba(255,255,255,.35);
    pointer-events:none;animation:sx-ripple .55s var(--sx-ease)}
  @keyframes sx-ripple{to{transform:scale(2.4);opacity:0}}

  /* skeleton shimmer — use for loading placeholders instead of blank space */
  .sx-skeleton{background:linear-gradient(100deg,var(--sx-surface) 30%,var(--sx-surface2) 50%,var(--sx-surface) 70%);
    background-size:200% 100%;animation:sx-shimmer 1.3s infinite;border-radius:var(--sx-radius-sm)}
  @keyframes sx-shimmer{to{background-position:-200% 0}}

  /* reveal-on-load — core.js (SX.reveal) adds .in after mount for a soft staggered entrance */
  [data-reveal]{opacity:0;transform:translateY(10px)}
  [data-reveal].in{opacity:1;transform:none;
    transition:opacity var(--sx-dur-lg) var(--sx-ease),transform var(--sx-dur-lg) var(--sx-spring)}

  /* ── mobile bar: sticky bars stay on one line at phone widths ── */
  @media (max-width:640px){
    .sx-wrap{padding:0 14px}
    .sx-brand{font-size:16px;min-width:0}
    .sx-topbar{gap:8px}
    .sx-topbar .sx-btn{padding:7px 12px}
    .sx-topbar .sx-btn-sm{padding:6px 10px}
    .nav-inner,.app-nav-inner{padding:12px 14px;gap:12px}
    .brand{font-size:18px}
  }
}

/* ── Overlay shell (games only) ───────────────────────────────────────────────
   The shared OBS browser-source frame — ONE shell for every StrmrX overlay, so a
   streamer's experience (and any future "change the overlay" request) is uniform
   across apps. Contract: a full-viewport root that fades in on .ready (via
   SX.revealOverlay), holding the app's own game UI in one of two stage modes:
     • .sx-stage-wrap  — BOXED: a fixed-size design (app sets width) that
       SX.fitStage() scales-to-fit + centres in any source size. Streamers size
       the OBS source (e.g. left half, chat on the right) and it scales to fit.
     • .sx-stage-fill  — FULL-BLEED: a canvas/WebGL app (e.g. PixiJS with
       resizeTo:window) that fills the source and self-fits its own world.
   The app renders its OWN game inside — this layer only frames + reveals it. */
@layer sx-overlay{
  body.sx-overlay{margin:0;overflow:hidden;background:transparent}
  .sx-overlay-root{position:fixed;inset:0;overflow:hidden;opacity:0;transition:opacity .5s var(--sx-ease-out)}
  .sx-overlay-root.ready{opacity:1}
  /* BOXED: absolutely centred; SX.fitStage applies translateX(-50%) scale(). */
  .sx-stage-wrap{position:absolute;left:50%;top:16px;transform:translateX(-50%)}
  /* FULL-BLEED: fills the root; the app's canvas/world self-fits inside. */
  .sx-stage-fill{position:absolute;inset:0}
}

/* Accessibility: anyone who asks for less motion gets cross-fades, everywhere.
   Unlayered on purpose — this must win over app CSS too. */
@media (prefers-reduced-motion: reduce){
  *{animation-duration:.001ms !important;transition-duration:.001ms !important}
}
