/* The page without WebGPU (DESIGN §15.9, UIKIT §30): same page, same DOM, the CSS theme of kit.css.
   This file holds what only this mode (and the boot frame) needs:
     1. the one-line banner #nogpu (kit.css's .k-banner; markup site/index.html#nogpu from portal/assets-src/regions/nogpu.html)
     2. the void behind the page while html.gpu-unavailable (no canvas paints it), with its junk (#nogpu-junk)
     3. .hub-still: the hub portals as baked pictures (boot frame, no WebGPU, the hub3d kill switch)
     4. forced colors
   The numbers and URLs of 2 and 3 are generated by the art bake (node portal/assets-src/bake.mjs) from site/art/art.json:
   edit the generator (bake.mjs nogpuCss()), never the block. portal/src/nogpu/nogpu.js switches the mode on.
   Owner: assets (design/CONTRACTS.md). Loaded by site/index.html; portal/build.sh versions it. */

/* ---- 1. The banner: a strip in the page flow, first thing after the skip link ------------------------------------------ */

/* Shown only when WebGPU is unavailable: an author display rule would defeat the UA's [hidden] { display: none }. */
#nogpu[hidden] { display: none; }

/* The well and the close key sit on the strip's first 33 px row (48 minus the padding) instead of being centred, so opening
   "Why?" grows the strip downwards without moving them. .k-banner__text is one run of text: the message, then "Why?" right
   after it (on phones at the end of the message's second line: two rows, not three); an open "Why?" drops below as a block. */
#nogpu { z-index: var(--z-nogpu, 3); align-items: flex-start; }
#nogpu > .k-banner__well { margin-top: 1.5px; }
#nogpu > .k-banner__text { flex: 1 1 auto; min-width: 0; margin-top: 6px; }
#nogpu .k-banner__msg { display: inline; }
#nogpu .k-banner__why { display: inline-block; margin: -2px 0 -2px 6px; vertical-align: baseline; }
#nogpu .k-banner__why[open] { display: block; margin: 0 0 4px; }
#nogpu .k-banner__why pre { max-width: 76ch; }
#nogpu > .k-banner__close { margin-top: -3.5px; margin-bottom: -3.5px; }
/* coarse: 44 px targets whose extra height hangs over the neighbouring rows instead of growing the strip */
@media (pointer: coarse) {
  #nogpu .k-banner__why:not([open]) { margin-top: -10px; margin-bottom: -10px; }
  #nogpu .k-banner__why > summary { min-width: var(--target-coarse, 44px); justify-content: flex-start; }
  #nogpu > .k-banner__close { margin-top: -5.5px; margin-bottom: -5.5px; }
}

/* desktop: the page gutter (DESIGN §6: 36 px), safe areas included */
@media (min-width: 760px) {
  #nogpu {
    padding-left: max(36px, calc(env(safe-area-inset-left, 0px) + 16px));
    padding-right: max(26px, calc(env(safe-area-inset-right, 0px) + 6px));
  }
}
/* phones: the notch side stays clear */
@media (max-width: 759.98px) {
  #nogpu { column-gap: 12px; padding-top: max(6px, env(safe-area-inset-top, 0px)); }
}

/* ---- 2. The void without a canvas (DESIGN §15.4): void-0, the grape glow where the featured portal sits, cream stars (a
   baked tile from the shape source's K.voidBg). Still: no rings, no twinkle. The hero is clamp(640px, 100svh, 1000px) tall. */
html.gpu-unavailable body { background-color: var(--void-0); background-repeat: no-repeat, repeat, repeat; }
/* the void's junk: portal/src/nogpu/junk.js places baked silhouettes (art/css/junk-*.webp) in the hero's free void, never on
   text, pictures or controls, once per layout (static). The layer sits under the whole portal page and fades with it. */
.nogpu-junk { position: absolute; left: 0; top: 0; width: 0; height: 0; overflow: visible; z-index: 0; pointer-events: none; }
.nogpu-junk[hidden], html:not(.gpu-unavailable) .nogpu-junk { display: none; }
.nogpu-junk__piece { position: absolute; display: block; max-width: none; margin: 0; opacity: .92; }

/* ---- 3. The hub stills (DESIGN §15.9, §16.3; portal/assets-src/README.md "Hub stills") -------------------------------------
   One baked picture per layout holds the featured rim, Stalks and the drips; the hole is see-through, so the featured game's
   own picture goes behind it and a rotation step only swaps two srcs. It is the boot frame's largest picture (its LCP), so
   #head-assets preloads it at high priority with the poster's hole mask, for the layout and density this file picks (wide:
   art/hub/still.webp, or still-1x.webp up to 1.3 dppx; portrait: still-m.webp). Markup (hero region; the element IS the fitted
   rest-pose opening, i.e. the slot the GPU hub fits its featured portal into):
     <div id="hero-portal" class="hub-featured-window hub-still" aria-hidden="true" tabindex="-1">
       <img class="hub-still__media" src="/videos/satsuma.jpg" alt="" fetchpriority="high" data-clip="satsuma">
       <span class="k-boxart hub-still__art"><img src="/boxart/satsuma.jpg" alt=""></span>          (optional)
     </div>
   - Size the element like the opening (about 1.68 : 1 wide, 1.62 : 1 portrait); the rim and the hole are % of it, so they
     scale. The picture reaches outside the element (the rim, Stalks, drips): leave room, and no overflow clip on it.
   - .hub-still__media: an <img> (or a muted <video>). data-clip="<slug>" lets the CSS theme play that game's clip in the hole
     (portal/src/nogpu/clips.js; only without WebGPU).
   - .hub-still__art: any .k-boxart; this file gives it the kit size of the layout (150 x 225, md 110 x 165, phones 76 x 114),
     the frame, tilt and glob, centred where the still's composition has it. It is a layout box (no transform), so the GPU hub
     can paint the same twin from its rect.
   - When the GPU hub paints the slot, mark the element data-gpu: kit.css's switch then hides the still's paint (::after, the
     media, the box art) under html.gpu-paint; without data-gpu (no WebGPU, the boot frame, the hub3d kill switch) it stays.
   Small hub portals (DESIGN §11.2, 144 x 100 openings; phones 140 x 96): the same idea, one rim per slot,
     <a class="k-hublink" href="/knight" aria-label="Play Knight. Fantasy RPG, online.">
       <span class="hub-still hub-still--small" data-still="0|1|2"><img class="hub-still__media" src="/videos/knight.jpg" alt=""></span> …
   dormant at rest, awake while its link is hovered (fine pointers) or focus-visible, or while it has .is-awake.
   No game in the featured portal (404, a failed or empty catalog): add .hub-still--empty to it: the dormant picture, an empty hole.
   SOON portals (DESIGN §11.2: two small closed membranes in the void, no labels): <span class="hub-still hub-still--soon"
   data-still="0|1" aria-hidden="true"></span> sized as the opening (60 x 34 and 52 x 28 at 1440 x 900); 0 has Peeper peeking. */
/* zero specificity: the hero's own layout rules (position, size, z-index) always win */
:where(.hub-still) { position: relative; isolation: isolate; }
.hub-still::after {
  content: ""; position: absolute; z-index: 1; pointer-events: none;
  background-position: 0 0; background-size: 100% 100%; background-repeat: no-repeat;
}
.hub-still > .hub-still__media {
  position: absolute; z-index: 0; display: block; box-sizing: border-box; max-width: none; max-height: none; margin: 0;
  object-fit: cover; object-position: 50% 50%; background: var(--void-0);
}
.hub-still > .hub-still__art { position: absolute; z-index: 2; margin: 0; }
:where(.hub-still--small, .hub-still--soon) { display: block; }
.hub-still--small::after { background-image: var(--hub-still-rest); }
/* hover-capable devices fetch the awake frame ahead (a 1 px layer parked outside the box), so the first hover never blinks; only
   after the page has loaded (html.mono-ready: load, then idle or 1 s; portal-entry.js), so the boot frame's own pictures go
   first, and not while the GPU paints the slot (kit.css hides the still then: html.gpu-paint + its [data-gpu]) */
@media (hover: hover) {
  :where(html.mono-ready:not(.gpu-paint)) .hub-still--small::after, :where(html.mono-ready) .hub-still--small:not([data-gpu])::after {
    background-image: var(--hub-still-rest), var(--hub-still-awake); background-size: 100% 100%, 1px 1px; background-position: 0 0, -9px -9px; }
  a:hover .hub-still--small::after { background-image: var(--hub-still-awake); background-size: 100% 100%; background-position: 0 0; }
}
a:focus-visible .hub-still--small::after, .hub-still--small.is-awake::after { background-image: var(--hub-still-awake); background-size: 100% 100%; background-position: 0 0; }
/* .hub-still--empty: no game in the featured portal (the 404 "This dimension is empty.", a failed or empty catalog): the dormant
   picture, an empty hole (the void and its stars show through), no media, no box art */
.hub-still--empty > .hub-still__media, .hub-still--empty > .hub-still__art, .hub-still--empty > .nogpu-clip { display: none; }
/* a deep link keeps the portal layer hidden from the first paint until its hop starts (html.deep-link), and a shown game hides
   it (html.game-active): the small and SOON stills wait meanwhile, so they do not compete with the game's first requests; they
   load (or come from the cache) on Back. The featured still and its hole mask are preloaded with every page (#head-assets: the
   boot frame's LCP picture), so a deep link keeps using them: no second fetch, no unused preload, and Back finds them ready. */
html.deep-link .hub-still:is(.hub-still--small, .hub-still--soon)::after, html.game-active .hub-still[class]::after { background-image: none; }

/* bake:nogpu (generated by portal/assets-src/bake.mjs nogpuCss() from site/art/art.json: do not edit this block) */
/* wide: the 1440x900 composition */
@media (min-width: 760px) and (min-aspect-ratio: 11/10) {
  html.gpu-unavailable body { background-image: radial-gradient(ellipse 62% 56% at 71.7% 38%, var(--glow-core), var(--glow-mid) 45%, transparent), url(/art/css/void-stars.webp?v=2ab0a7132a), url(/art/css/void-stars-2.webp?v=af0c7d573a);
    background-size: 100% clamp(640px, 100svh, 1000px), 480px 480px, 704px 704px; }
  .hub-still:not(.hub-still--small, .hub-still--soon)::after { background-image: url(/art/hub/still.webp?v=ec313dbb12); }
  .hub-still:not(.hub-still--small, .hub-still--soon).hub-still--empty::after { background-image: url(/art/hub/still-dormant.webp?v=33cd5a08af); }
  .hub-still:not(.hub-still--small, .hub-still--soon)::after { left: -7.2581%; top: -24.2944%; width: 109.6774%; height: 145.1795%; }
  .hub-still:not(.hub-still--small, .hub-still--soon) > .hub-still__media { left: -2.7554%; top: -1.3773%; width: 100.9409%; height: 102.3933%;
    -webkit-mask: url(/art/hub/hole.svg?v=644c4ad3c2) 51.5385% 53.562% / 108.6551% 141.7861% no-repeat; mask: url(/art/hub/hole.svg?v=644c4ad3c2) 51.5385% 53.562% / 108.6551% 141.7861% no-repeat; }
  .hub-still:not(.hub-still--small, .hub-still--soon) > .hub-still__art { left: calc(12.2312% - 75px); top: calc(78.0989% - 112.5px); width: 150px; height: 225px;
    --k-rot: -9deg; --k-frame: 6px; --k-glob: var(--k-art-glob-hero, none); --k-glob-w: var(--k-art-glob-hero-w, 0px); --k-glob-h: var(--k-art-glob-hero-h, 0px); --k-glob-x: var(--k-art-glob-hero-x, 0px); --k-glob-y: var(--k-art-glob-hero-y, 0px); }
}
/* screens up to 1.3 dppx: the same still at about half the bytes (one image px per screen px up to the largest slot) */
@media (min-width: 760px) and (min-aspect-ratio: 11/10) and (max-resolution: 1.3dppx) { .hub-still:not(.hub-still--small, .hub-still--soon)::after { background-image: url(/art/hub/still-1x.webp?v=3c98d786fd); } }
@media (min-width: 760px) and (max-width: 1199.98px) and (min-aspect-ratio: 11/10) {
  .hub-still:not(.hub-still--small, .hub-still--soon) > .hub-still__art { left: calc(12.2312% - 55px); top: calc(78.0989% - 82.5px); width: 110px; height: 165px;
    --k-rot: -9deg; --k-frame: 5px; --k-glob: var(--k-art-glob-card, none); --k-glob-w: var(--k-art-glob-card-w, 0px); --k-glob-h: var(--k-art-glob-card-h, 0px); --k-glob-x: var(--k-art-glob-card-x, 0px); --k-glob-y: var(--k-art-glob-card-y, 0px); }
}
/* portrait: the 390x844 composition (phones, tablets in portrait) */
@media (max-width: 759.98px), (max-aspect-ratio: 1099/1000) {
  html.gpu-unavailable body { background-image: radial-gradient(ellipse 90% 45% at 50% 300px, var(--glow-core), var(--glow-mid) 45%, transparent), url(/art/css/void-stars.webp?v=2ab0a7132a), url(/art/css/void-stars-2.webp?v=af0c7d573a);
    background-size: 100% clamp(640px, 100svh, 1000px), 480px 480px, 704px 704px; }
  .hub-still:not(.hub-still--small, .hub-still--soon)::after { background-image: url(/art/hub/still-m.webp?v=effaba8740); }
  .hub-still:not(.hub-still--small, .hub-still--soon).hub-still--empty::after { background-image: url(/art/hub/still-m-dormant.webp?v=cdc89ce9eb); }
  .hub-still:not(.hub-still--small, .hub-still--soon)::after { left: -9.7143%; top: -13.4259%; width: 123.4286%; height: 138.8889%; }
  .hub-still:not(.hub-still--small, .hub-still--soon) > .hub-still__media { left: -2.2857%; top: -1.6204%; width: 101%; height: 101.1574%;
    -webkit-mask: url(/art/hub/hole-m.svg?v=b70942e070) 33.121% 31.2883% / 122.2065% 137.2998% no-repeat; mask: url(/art/hub/hole-m.svg?v=b70942e070) 33.121% 31.2883% / 122.2065% 137.2998% no-repeat; }
  .hub-still:not(.hub-still--small, .hub-still--soon) > .hub-still__art { left: calc(9.1429% - 38px); top: calc(88.4259% - 57px); width: 76px; height: 114px;
    --k-rot: -8deg; --k-frame: 4px; --k-glob: var(--k-art-glob-phone, none); --k-glob-w: var(--k-art-glob-phone-w, 0px); --k-glob-h: var(--k-art-glob-phone-h, 0px); --k-glob-x: var(--k-art-glob-phone-x, 0px); --k-glob-y: var(--k-art-glob-phone-y, 0px); }
}
/* the account routes (same page, no hub): the glow sits at the page centre (DESIGN §15.4) */
html.dashboard-active.gpu-unavailable body { background-image: radial-gradient(ellipse 70% 50% at 50% 34%, var(--glow-core), var(--glow-mid) 45%, transparent), url(/art/css/void-stars.webp?v=2ab0a7132a), url(/art/css/void-stars-2.webp?v=af0c7d573a);
  background-size: 100% clamp(640px, 100svh, 1000px), 480px 480px, 704px 704px; }
/* short landscape (coarse, under 500 px tall): no box art */
@media (pointer: coarse) and (max-height: 499.98px) and (min-aspect-ratio: 11/10) { .hub-still:not(.hub-still--small, .hub-still--soon) > .hub-still__art { display: none; } }
/* the small hub portals: one rim per slot, the same in every layout */
.hub-still--small[data-still="0"] { --hub-still-rest: url(/art/hub/small-0.webp?v=290eece24a); --hub-still-awake: url(/art/hub/small-0-awake.webp?v=241dbc235f); }
.hub-still--small[data-still="0"]::after { left: -9.0278%; top: -16%; width: 123.6111%; height: 146%; }
.hub-still--small[data-still="0"] > .hub-still__media { left: 1.3889%; top: 0.5%; width: 101.3889%; height: 101.5%;
  -webkit-mask: url(/art/hub/small-hole-0.svg?v=087ff6d816) 46.875% 37.0787% / 121.9178% 143.8424% no-repeat; mask: url(/art/hub/small-hole-0.svg?v=087ff6d816) 46.875% 37.0787% / 121.9178% 143.8424% no-repeat; }
.hub-still--small[data-still="1"] { --hub-still-rest: url(/art/hub/small-1.webp?v=5751941a22); --hub-still-awake: url(/art/hub/small-1-awake.webp?v=a14cb6a25f); }
.hub-still--small[data-still="1"]::after { left: -11.8056%; top: -19%; width: 122.2222%; height: 149%; }
.hub-still--small[data-still="1"] > .hub-still__media { left: -1.0417%; top: -3%; width: 101.0417%; height: 103.5%;
  -webkit-mask: url(/art/hub/small-hole-1.svg?v=286699e6db) 50.8197% 35.1648% / 120.9622% 143.9614% no-repeat; mask: url(/art/hub/small-hole-1.svg?v=286699e6db) 50.8197% 35.1648% / 120.9622% 143.9614% no-repeat; }
.hub-still--small[data-still="2"] { --hub-still-rest: url(/art/hub/small-2.webp?v=15cb214f87); --hub-still-awake: url(/art/hub/small-2-awake.webp?v=3fbaaa30f1); }
.hub-still--small[data-still="2"]::after { left: -14.5833%; top: -17%; width: 125%; height: 147%; }
.hub-still--small[data-still="2"] > .hub-still__media { left: -3.4722%; top: -0.5%; width: 102.4306%; height: 100.5%;
  -webkit-mask: url(/art/hub/small-hole-2.svg?v=0d244c7ff5) 49.2308% 35.4839% / 122.0339% 146.2687% no-repeat; mask: url(/art/hub/small-hole-2.svg?v=0d244c7ff5) 49.2308% 35.4839% / 122.0339% 146.2687% no-repeat; }
/* the SOON portals: closed membranes (no media), the element = the opening */
.hub-still--soon[data-still="0"]::after { left: -43.3333%; top: -76.4706%; width: 186.6667%; height: 252.9412%; background-image: url(/art/hub/soon-0.webp?v=e28f75811b); }
.hub-still--soon[data-still="1"]::after { left: -50%; top: -92.8571%; width: 200%; height: 285.7143%; background-image: url(/art/hub/soon-1.webp?v=1d38e17df1); }
/* /bake:nogpu */

/* ---- 4. Forced colors: the renderer never starts (main.js), so there is no banner and no bird; decoration hides, content
   images stay (DESIGN §14, §15.9). */
@media (forced-colors: active) {
  #nogpu { box-shadow: none; border-bottom: 3px solid CanvasText; }
  .nogpu-junk { display: none; }
  .hub-still::after { display: none; }
  :root .hub-still[class] > .hub-still__media {
    inset: 0; width: 100%; height: 100%; border: 2px solid CanvasText; border-radius: 18px;
    -webkit-mask: none; mask: none;
  }
  html.gpu-unavailable body { background: Canvas; }
}
