/* Page frame: reset, root canvas, route states (portal / game / dashboard / no WebGPU), the hop
   overlay, the skip link, a11y utilities.
   Owner: integrator (design/CONTRACTS.md). Loaded by site/index.html; portal/build.sh versions it.
   Colours are kit.css tokens (design/tokens.json) with today's values as fallbacks.

   <html> states (set by portal-entry.js before the first paint, then by core/*):
     portal-active | dashboard-active   the route family
     deep-link          a game URL at boot: the portal layer waits hidden (no flash of the hub)
     gpu-ready          a GPU frame is on screen: the canvas fades in (300 ms)
     gpu-paint          the GPU paints GPU-bodied twins (kit.css switch, UIKIT §0)
     gpu-unavailable    no WebGPU: the CSS theme; the DOM still routes and plays games
     hop-active         a route transition runs
     portal-away        the portal DOM layer is faded out and inert (hops, game shown)
     game-active        a game is shown (after its reveal)
     data-motion        "reduce" | "full" - effective Reduce motion (aio.motion over the OS)
     data-route         "notfound" on an unknown game URL */

:root { color-scheme: dark; }
* { box-sizing: border-box; margin: 0; padding: 0; }
/* The page's own text (anything a component does not style) is the kit's body face and text colour. */
html, body { height: 100%; overflow: hidden; background: var(--void-0, #0C0716); color: var(--text, #E8DDF7);
  font-family: var(--f-body, "Fredoka", system-ui, sans-serif); }
html.portal-active { height: auto; min-height: 100%; overflow-x: hidden; overflow-y: auto; }
html.portal-active body { height: auto; min-height: 100vh; overflow: visible; }
html.portal-active.game-active, html.portal-active.game-active body { height: 100%; overflow: hidden; }

/* The WebGPU canvas sits under every DOM layer and fades in after its first good frame (§16.3).
   [data-k-fade] (index.html): kit.css's reduced-motion freeze leaves this opacity fade alone. */
#gpu { position: fixed; inset: 0; width: 100%; height: 100%; display: block; opacity: 0;
  transition: opacity 300ms linear; }
html.gpu-ready #gpu { opacity: 1; }

/* The portal DOM layer. It fades out together with the canvas' hub on every hop (120 ms,
   150 ms under Reduce motion: the hop is then this fade, canvas and DOM together, DESIGN §10) and
   is inert while away; a deep link keeps it hidden from the first paint. Portal-layer DOM outside
   #portal-page opts in with [data-portal-layer]; under Reduce motion its only transition is this
   opacity fade (it wins over kit.css's freeze), #portal-page carries [data-k-fade]. */
#portal-page { position: relative; min-height: 100vh; pointer-events: none; transition: opacity 120ms linear; }
[data-portal-layer] { transition: opacity 120ms linear; }
html[data-motion="reduce"] #portal-page { transition-duration: 150ms; }
html[data-motion="reduce"] [data-portal-layer] { transition: opacity 150ms linear !important; }
html.portal-away #portal-page, html.portal-away [data-portal-layer],
html.deep-link #portal-page, html.deep-link [data-portal-layer] { opacity: 0; }
html.deep-link #portal-page { transition: none; }

.sr-only { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
html.game-active #portal-page { visibility: hidden; pointer-events: none; }
html.game-active #account-shell, html.game-active #portal-hero { display: none; }
html.dashboard-active, html.dashboard-active body { height: auto; min-height: 100%; overflow: auto; }
html.dashboard-active #portal-page,
html.dashboard-active #portal-hero,
html.dashboard-active #account-shell,
html.dashboard-active #game-controls,
html.dashboard-active #nogpu,
html.dashboard-active #hop-overlay,
html.dashboard-active #portal-toasts,
html.dashboard-active #game { display: none !important; }
/* The game's DOM controls exist only while a game is shown (html.game-active, set at the reveal by
   core/transition.js): never during a hop, whatever a script does to their [hidden]. */
html:not(.game-active) #game-controls { display: none !important; }
/* The account app stays above the thin renderer's canvas (entry/thin.js). */
html.dashboard-active #dashboard-shell { display: block !important; min-height: 100vh; position: relative; z-index: 1; }
html:fullscreen #gpu { display: none; }
/* Forced colors: the renderer never starts; system colours only (DESIGN §14). */
@media (forced-colors: active) {
  #gpu, #companion-overlay { display: none !important; }
}

/* Skip link "Skip to games" (UIKIT §31): kit.css .k-skip draws the cream key, hidden until focused, at
   (max(16, inset-left + 8), max(16, inset-top + 8)); main.js moves focus to h2#all-games. */
html.dashboard-active .skip-link, html.game-active .skip-link { display: none; }

/* The hop overlay (DESIGN §11.16): the only live DOM during a hop. z 11: above #game (2), the pad and
   account keys (5-7), under birb. Click-through except its own controls. Components: kit.css
   (.k-overlay safe areas, .k-loader, .k-key, .k-inkpanel); this file places them. */
#hop-overlay { position: fixed; inset: 0; z-index: 11; pointer-events: none; }
#hop-overlay .hop-poster { position: absolute; inset: 0; opacity: 0; transition: opacity 150ms linear;
  background: var(--void-0, #0C0716) center / cover no-repeat; }
#hop-overlay .hop-poster[data-fit="contain"] { background-size: contain; }
/* Portrait: the still sits in a 16:9 band, never a cover-fit crop (DESIGN §11.14). */
@media (orientation: portrait) { #hop-overlay .hop-poster { background-size: contain; } }
#hop-overlay.is-covered .hop-poster { opacity: 1; }
#hop-overlay.is-instant .hop-poster { transition: none; }
#hop-overlay.is-active #hop-back, #hop-overlay.is-active #hop-error { pointer-events: auto; }
/* "Opening portal..." after 0.3 s of waiting, on an ink pill: text never sits on the game or on goo. */
.hop-loader { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); padding: 22px 28px 20px;
  width: max-content; max-width: calc(100% - 32px); /* left: 50% would cap its shrink-to-fit width at half the screen */
  border: 2.5px solid var(--ink, #07030D); border-radius: 26px; background: var(--pill, #0B0712);
  box-shadow: 0 3px 0 var(--ink, #07030D); }
/* A cream Back key after 2 s of waiting, top right, clear of the game bar's own keys. */
.hop-back { position: absolute; top: max(16px, calc(env(safe-area-inset-top) + 12px));
  right: max(16px, calc(env(safe-area-inset-right) + 12px)); }
/* The error panel (gameview fills and styles it; this places the plain default). */
.hop-error { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: min(460px, calc(100% - 32px)); }
.hop-error__title { color: var(--text-hi, #FFF4E0); font: 800 var(--fs-display-state-l, 28px)/1.02 var(--f-display, "Gluten", system-ui, sans-serif); }
.hop-error__title:focus { outline: none; }
.hop-error__text { color: var(--text, #E8DDF7); font: 400 16px/1.5 var(--f-body, "Fredoka", system-ui, sans-serif); }
.hop-error__keys { display: flex; flex-wrap: wrap; justify-content: center; gap: 14px; margin-top: 10px; }

/* The single account action remains keyboard and
   screen-reader accessible above the WebGPU-rendered game catalog. */
[hidden] { display: none !important; }
