/* The expandable stage container — two layouts keyed off [data-layout] (devify-me-site#83).
 *
 * Generic: no character in here. Colours come from the page's tokens through --stage-* so the
 * container can move to another page (or into a Layout, studio#374) without edits.
 *
 *   compact   the stage floats in the page; the way in is an icon in the frame's corner
 *   expanded  the frame fills the viewport; the HUD holds every control, the corner the way out
 *   [data-motion]  the growth / shrink between them (expandable.js animates it; see its header)
 */
.stage{
  --stage-ground: var(--ground, #14142B);
  --stage-paper: var(--paper, #F4F3DF);
  --stage-accent: var(--accent, #0AA0BE);
  --stage-panel: rgba(20, 20, 43, .9);
  --stage-flap: color-mix(in srgb, var(--stage-paper) 9%, var(--stage-ground));
  position: relative;
  width: min(420px, 100%);
  margin-inline: auto;
}

/* ---------- compact ---------- */
.stage__frame{
  position: relative;
  aspect-ratio: 4 / 5;
  border-radius: 24px;
  overflow: hidden;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .45);
  background: radial-gradient(120% 90% at 50% 34%, #2a2d52 0%, #1b1b38 52%, var(--stage-ground) 100%);
  animation: stage-float 7s ease-in-out infinite;
  container-type: inline-size;     /* the loader's lockup sizes itself from the frame (cqi) */
}
@keyframes stage-float{ 0%, 100%{ transform: translateY(0) } 50%{ transform: translateY(-14px) } }

.stage__glass{ position: absolute; inset: 0; cursor: pointer }
.stage__poster, .stage__canvas, .stage__loader{
  position: absolute; inset: 0; width: 100%; height: 100%; display: block;
  transition: opacity .6s ease;
  /* Round the layers themselves, not only the frame. Jorge saw the corners flicker between
     rounded and square (2026-09-23, real GPU; SwiftShader does not show it). The likely cause is
     the WebGL canvas being its own compositor layer, redrawn every frame inside a transform-
     animated parent (stage-float), so the parent's border-radius + overflow clip is not applied
     to it consistently. A clip-path on the layer itself travels with its content. It is not on
     the frame because there it would also clip the frame's box-shadow. */
  clip-path: inset(0 round 24px);
}
.stage__poster{ object-fit: cover }
/* A snapshot stand-in for the canvas during the growth / after the shrink (expandable.js). Its size
   is set inline; it is centred in the frame and never takes a click. */
.stage__ghost{
  position: absolute; inset: 0; margin: auto; z-index: 1; display: block;
  pointer-events: none; transform-origin: 50% 50%;
}
.stage__canvas{ opacity: 0 }
.stage[data-state="ready"] .stage__canvas{ opacity: 1 }
/* The canvas takes keys (compact: turn him; game mode: walk him), so it takes focus. Its own outline
   would be cut off by its clip-path, so the frame draws the ring: inset, over the scene and under the
   corner icon, paper with a ground halo so it holds contrast over any part of the picture. */
.stage__canvas:focus{ outline: none }
.stage__frame::after{
  content: ""; position: absolute; inset: 0; z-index: 3; pointer-events: none;
  border-radius: inherit; visibility: hidden;
  box-shadow: inset 0 0 0 3px var(--stage-paper), inset 0 0 0 6px var(--stage-ground);
}
.stage__frame:has(.stage__canvas:focus-visible)::after{ visibility: visible }
.stage[data-motion] .stage__frame::after{ visibility: hidden }
.stage[data-state="ready"] .stage__poster{ opacity: 0; pointer-events: none }

.stage__status{
  position: absolute; left: 50%; bottom: 16px; transform: translateX(-50%); z-index: 3;
  margin: 0; padding: 6px 14px; border-radius: 999px; max-width: calc(100% - 32px);
  background: var(--stage-panel); color: var(--stage-paper);
  font-size: .85rem; line-height: 1.4; text-align: center; white-space: normal;
}
.stage__status:empty{ display: none }
/* It announces "Loading" and "Ready" (the loader draws the progress). Only an error is SHOWN. */
.stage:not([data-state="error"]) .stage__status{
  clip-path: inset(50%); width: 1px; height: 1px; overflow: hidden; white-space: nowrap; padding: 0;
}

.stage button{
  font: inherit; font-weight: 600; font-size: .95rem; letter-spacing: .02em;
  color: var(--stage-paper); background: var(--stage-panel);
  border: 1px solid var(--stage-accent); border-radius: 999px;
  min-height: 44px; padding: 8px 20px; cursor: pointer;
  transition: background .15s ease, color .15s ease;
}
.stage button:hover{ background: #23234a }
.stage button:focus-visible{
  /* paper ring with a ground halo, so it holds contrast over any part of the scene behind it */
  outline: 2px solid var(--stage-paper); outline-offset: 2px;
  box-shadow: 0 0 0 5px var(--stage-ground);
}
.stage button[aria-pressed="true"]{ background: var(--stage-accent); color: var(--stage-ground) }

/* ---------- the loader: a lockup, a split-flap word, a hairline bar that reports real bytes ----------
   Shown while the 3D loads (main.js sets data-state="loading"), and from first paint when the page
   will autoload, so the still never flashes first. <noscript> in the page hides it, because without
   script nothing will ever load. The bar's width is set from GLTFLoader's progress events; while no
   total is known (the three.js modules, or a response with no Content-Length) it is indeterminate,
   never a made-up number (design/the-wall/BUILD-NOTES.md, "Do not port"). */
.stage__loader{
  z-index: 1; pointer-events: none;
  display: grid; place-items: center;
  background: var(--stage-ground); color: var(--stage-paper);
  opacity: 0; visibility: hidden; transition: opacity .6s ease, visibility 0s linear .6s;
}
.stage[data-state="loading"] .stage__loader,
.stage[data-autoload="true"]:not([data-state]) .stage__loader{ opacity: 1; visibility: visible; transition: opacity .6s ease }
/* Nothing may sit under the loader: two layers with the same rounded clip each anti-alias the same
   edge pixels, so a bright still underneath shows as a 1px light line along the frame's edge. */
.stage[data-state="loading"] .stage__poster,
.stage[data-autoload="true"]:not([data-state]) .stage__poster{ opacity: 0 }
.stage__lockup{
  --lockup: clamp(150px, 56cqi, 300px); width: var(--lockup); display: grid;
  animation: stage-appear .4s ease both;
}
.stage__wordmark{ display: block; width: 100%; height: auto }
@keyframes stage-appear{ from{ opacity: 0 } }
/* The tagline: a fixed lead and a split-flap board, right-aligned under the wordmark as in the
   logo. The board's cells are fixed boxes (flap.js), so the line never changes width. */
.stage__tagline{
  display: flex; justify-content: flex-end; align-items: center; gap: .32em;
  margin: calc(var(--lockup) * .04) 0 0; max-width: none;
  font-weight: 400; font-size: calc(var(--lockup) * .066); line-height: 1; white-space: nowrap;
}
.stage__flap{ display: flex; gap: 2px }
.stage__flap > span{
  box-sizing: border-box; width: .84em; height: 1.4em; display: grid; place-items: center;
  border-radius: 2px; color: var(--stage-paper);
  /* two flaps and the hairline between them */
  background: linear-gradient(to bottom,
    var(--stage-flap) 0 calc(50% - .5px), var(--stage-ground) calc(50% - .5px) calc(50% + .5px),
    var(--stage-flap) calc(50% + .5px) 100%);
}
.stage__flap > span[data-tumbling]{ color: color-mix(in srgb, var(--stage-paper) 50%, transparent) }
.stage__vh, .stage__keys{
  position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.stage__bar{
  position: relative; overflow: hidden; height: 1px; margin-top: calc(var(--lockup) * .13);
  background: color-mix(in srgb, var(--stage-paper) 16%, transparent);
}
.stage__bar > span{
  position: absolute; inset: 0 auto 0 0; width: 0; background: var(--stage-accent);
  transition: width .18s linear;
}
.stage__bar[data-indeterminate] > span{ width: 28%; transition: none; animation: stage-sweep 1.3s ease-in-out infinite }
@keyframes stage-sweep{ from{ transform: translateX(-100%) } to{ transform: translateX(360%) } }
.stage__loadline{
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
  margin: 10px 0 0; max-width: none;
  font-size: .66rem; line-height: 1.4; letter-spacing: .3em; text-transform: uppercase;
  color: color-mix(in srgb, var(--stage-paper) 60%, transparent);
}
.stage__loadline > .stage__pct{ color: var(--stage-paper); letter-spacing: .12em; font-variant-numeric: tabular-nums }

/* The corner toggle: the way in when compact, the way out when expanded (one element, so focus
   returns to exactly where it left). 44px hit target; the glyph is inline SVG in the markup. */
.stage .stage__toggle{
  position: absolute; z-index: 4; top: 10px; right: 10px;
  width: 44px; height: 44px; min-height: 44px; padding: 0;
  display: grid; place-items: center;
  border-radius: 12px; border-color: color-mix(in srgb, var(--stage-paper) 28%, transparent);
  transition: background .15s ease, opacity .2s ease;
}
.stage .stage__toggle:hover{ border-color: var(--stage-accent) }
.stage .stage__toggle[hidden]{ display: none }
.stage__toggle svg{ width: 22px; height: 22px; display: block; pointer-events: none }
.stage__toggle .stage__icon--collapse,
.stage[data-layout="expanded"] .stage__toggle .stage__icon--expand{ display: none }
.stage[data-layout="expanded"] .stage__toggle .stage__icon--collapse{ display: block }
/* expanded: the corner cell the HUD keeps empty, clear of a notch */
.stage[data-layout="expanded"] .stage__toggle{
  top: max(14px, env(safe-area-inset-top)); right: max(14px, env(safe-area-inset-right));
}

/* ---------- expanded ---------- */
html.stage-open{ overflow: hidden }
/* The section keeps its compact box in the page (so nothing behind it reflows, and the shrink has a
   place to land); the FRAME is what leaves the flow and fills the viewport. */
.stage[data-layout="expanded"]{ aspect-ratio: 4 / 5 }
.stage[data-layout="expanded"] .stage__frame{
  position: fixed; inset: 0; z-index: 50; aspect-ratio: auto;
  border-radius: 0; box-shadow: none; animation: none;
}
.stage[data-layout="expanded"] .stage__poster,
.stage[data-layout="expanded"] .stage__canvas,
.stage[data-layout="expanded"] .stage__loader{ clip-path: none }
/* A browser-fullscreen root: the UA sizes it to the screen; the frame inside is fixed anyway. */
.stage:fullscreen{ background: var(--stage-ground) }

/* The page's ground, rising behind the frame as it grows, and the compact frame's shadow following
   the visible rectangle. Both are driven by expandable.js; these are their resting states. */
.stage__ground{
  position: fixed; inset: 0; z-index: 48; pointer-events: none;
  background: var(--stage-ground); opacity: 0; visibility: hidden;
}
.stage[data-layout="expanded"] .stage__ground{ opacity: 1; visibility: visible }
.stage__halo{
  position: fixed; z-index: 49; pointer-events: none; opacity: 0; visibility: hidden;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .45);
}
.stage[data-motion] .stage__halo{ visibility: visible }

/* While the frame moves: nothing in it is clickable (a stray click must not poke or orbit), its own
   background steps aside (each layer carries its own clip), and the chrome waits for it to land. */
.stage[data-motion] .stage__frame{ pointer-events: none; background: transparent }
.stage[data-motion] .stage__hud,
.stage[data-motion] .stage__toggle,
.stage[data-motion] .stage__status{ opacity: 0; transition: none }
.stage__hud{ transition: opacity .2s ease }
.stage[data-layout="expanded"] .stage__glass{ cursor: grab }
.stage[data-layout="expanded"] .stage__glass:active{ cursor: grabbing }

/* The HUD only exists while expanded (game.js mounts into it). It lets pointer events fall through
   to the canvas everywhere except on its own controls, so dragging anywhere orbits.
   The controls sit along the TOP (#64 Phase 3): in game mode the bottom of the frame is the floor the
   visitor clicks or taps to walk the character, and on a phone in landscape the floor is a band only
   ~60 px tall, which a control row at the bottom covered almost entirely. The top of a stage set is
   ceiling and wall. Wide: one row, title left, controls centred, the corner right. Narrow: the
   controls wrap onto their own rows under the title. */
.stage__hud{
  position: absolute; inset: 0; pointer-events: none;
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-rows: auto 1fr auto;
  grid-template-areas: "title controls corner" ". . ." "hint hint hint";
  align-items: start;
  gap: 10px;
  padding: max(14px, env(safe-area-inset-top)) max(14px, env(safe-area-inset-right))
           max(14px, env(safe-area-inset-bottom)) max(14px, env(safe-area-inset-left));
}
.stage__hud[hidden]{ display: none }
.stage__hud > *{ pointer-events: auto }
.stage__title{
  grid-area: title; justify-self: start; align-self: center; margin: 0;
  padding: 6px 16px; border-radius: 999px; background: var(--stage-panel);
  font-weight: 600; letter-spacing: .34em; text-transform: uppercase; font-size: .78rem;
  color: var(--stage-accent);
}
/* the corner toggle sits over the HUD's top-right cell; keep that cell empty */
.stage__hud::after{ content: ""; grid-area: corner; width: 44px; height: 44px }
.stage__controls{
  grid-area: controls; justify-self: center; align-self: start;
  display: flex; flex-wrap: wrap; justify-content: center; gap: 10px;
  max-width: 100%;
}
.stage__hint{
  grid-area: hint; justify-self: center; margin: 0; max-width: 60ch;
  padding: 4px 14px; border-radius: 999px; background: var(--stage-panel);
  font-size: .8rem; line-height: 1.5; text-align: center; color: var(--stage-paper);
}
@media (max-width: 760px){
  .stage__hud{
    grid-template-columns: 1fr auto;
    grid-template-rows: auto auto 1fr auto;
    grid-template-areas: "title corner" "controls controls" ". ." "hint hint";
  }
}
/* short landscape phones: drop the hint before the controls get crowded */
@media (max-height: 420px){ .stage__hint{ display: none } }

@media (prefers-reduced-motion: reduce){
  .stage__frame{ animation: none }
  .stage__poster, .stage__canvas, .stage__loader, .stage button, .stage__hud{ transition: none }
  /* the flap swaps without tumbling (flap.js), and the bar only ever changes width */
  .stage__lockup{ animation: none }
  .stage__bar > span{ transition: none }
  .stage__bar[data-indeterminate] > span{ animation: none; width: 0 }
}
