/* ═══════════════════════════════════════════════════════════════════════════
   THE WALL: five balls of points on one ring.

     .wall          the room. No transform of its own: it is a `.reveal`, and
                    the reveal choreography owns its transform.
     .wall__field   the orbit. Carries the perspective and no transform.
     .wall__slot    where a ball is on the orbit this frame: one keyframe track
                    carries it round and turns it back to face the reader.
     .ball          the canvas (scripts/studio/balls.js) and the name under it.
     .wall__back    behind the ring: Omry at his window, watching the balls
                    go round, the run's first half.
     .wall__omry    the middle of the ring: Omry, and the run that leaves him
                    standing there (scripts/studio/omry.js).
   ═════════════════════════════════════════════════════════════════════════ */

.wall {
  position: relative;
  z-index: 1;
  margin-top: clamp(54px, 6vw, 92px);
  max-width: min(100%, 1320px);
}

.wall::after {
  content: "";
  position: absolute;
  z-index: -1;
  inset: auto -6% -14% -6%;
  height: 54%;
  background: radial-gradient(50% 100% at 50% 100%, rgba(0, 170, 210, 0.1), transparent 70%);
  opacity: 0.6;
  pointer-events: none;
}

/* ── the orbit ──────────────────────────────────────────────────────────
   Five slots on one keyframe track, a fifth of the period apart. The ring's
   centre is set back by its radius, so the ball in front stands at z = 0.
   The room above the ring was the back balls' (they ride up to two radii
   behind, seen from above); it is Omry's now, who stands head and shoulders
   over them, and the ring is px all the way, so the room is too. */
.wall__field {
  --orbit-n: 5;
  --orbit-w: 280px;
  --orbit-r: 560px;
  --orbit-t: 60s;
  --orbit-tilt: 16deg;
  position: relative;
  display: grid;
  margin: 0;
  padding: 330px 0 0;
  list-style: none;
  perspective: 2400px;
  perspective-origin: 50% 20%;
  transform-style: preserve-3d;
  pointer-events: none;
}

.wall__slot {
  --i: 0;
  grid-area: 1 / 1;
  justify-self: center;
  width: min(var(--orbit-w), 100%);
  pointer-events: auto;
  transform-style: preserve-3d;
  animation: wall-orbit var(--orbit-t) linear infinite;
  animation-delay: calc(var(--i) * var(--orbit-t) / var(--orbit-n) * -1);
}
.wall__slot:nth-child(1) { --i: 0; }
.wall__slot:nth-child(2) { --i: 1; }
.wall__slot:nth-child(3) { --i: 2; }
.wall__slot:nth-child(4) { --i: 3; }
.wall__slot:nth-child(5) { --i: 4; }

@keyframes wall-orbit {
  0%   { transform: rotateX(calc(-1 * var(--orbit-tilt))) translateZ(calc(-1 * var(--orbit-r))) rotateY(0deg)   translateZ(var(--orbit-r)) rotateY(0deg)    rotateX(var(--orbit-tilt)); opacity: 1;    filter: brightness(1); }
  50%  { transform: rotateX(calc(-1 * var(--orbit-tilt))) translateZ(calc(-1 * var(--orbit-r))) rotateY(180deg) translateZ(var(--orbit-r)) rotateY(-180deg) rotateX(var(--orbit-tilt)); opacity: 0.62; filter: brightness(0.62); }
  100% { transform: rotateX(calc(-1 * var(--orbit-tilt))) translateZ(calc(-1 * var(--orbit-r))) rotateY(360deg) translateZ(var(--orbit-r)) rotateY(-360deg) rotateX(var(--orbit-tilt)); opacity: 1;    filter: brightness(1); }
}

/* ── Omry, at the middle of the ring ────────────────────────────────────
   The item takes no room: a point at the middle of the cell the slots share,
   which is where each slot turns about, carried by a slot's own moves at no
   angle (tilt, back by the radius, tilt off), which is the ring's centre,
   facing the reader. Two pixels further back than that, so a ball passing
   his side at the same depth is in front of him, never fighting him. Being
   in the field's 3D context is what puts the balls behind him round the back
   and in front of him round the front.

   He is points on one canvas (scripts/studio/omry.js) that hangs off that
   point, the ring passing `--run-lift` of the way down it. The canvas is
   transparent wherever he is not, so it may be as wide as the run needs: the
   script fits the atlas's rows to its height and centres its columns, and
   `--run-w` / `--run-h` is the atlas's own proportion (366 x 200 cells,
   which tests/page.mjs reads off index.html), so every place he goes in the
   run is on it. A mask sinks the cut at his thighs into the dark, and takes
   the edge off a frame where he walks in from the side. The sides are plain
   numbers (px at the ring's centre) so the frames below can take the
   proportion. */
.wall__omry {
  --run-w: 1244;
  --run-h: 680;
  --run-lift: 72%;
  grid-area: 1 / 1;
  justify-self: center;
  align-self: center;
  position: relative;
  width: 0;
  height: 0;
  /* Its canvas joins the ring's 3D context itself. Flattened into a point,
     it has no plane to be sorted by, and is drawn behind every ball. */
  transform-style: preserve-3d;
  transform: rotateX(calc(-1 * var(--orbit-tilt))) translateZ(calc(-1 * var(--orbit-r) - 2px)) rotateX(var(--orbit-tilt));
  pointer-events: none;
}
.wall__run {
  position: absolute;
  left: 0;
  top: 0;
  display: block;
  width: calc(var(--run-w) * 1px);
  /* The reset caps a canvas at its parent's width, and the parent is a point. */
  max-width: none;
  height: calc(var(--run-h) * 1px);
  translate: -50% calc(-1 * var(--run-lift));
  -webkit-mask-image: linear-gradient(to right, transparent, #000 5%, #000 95%, transparent), linear-gradient(to bottom, #000 64%, transparent 96%);
  -webkit-mask-composite: source-in;
  mask-image: linear-gradient(to right, transparent, #000 5%, #000 95%, transparent), linear-gradient(to bottom, #000 64%, transparent 96%);
  mask-composite: intersect;
}

/* ── Omry, behind the ring ──────────────────────────────────────────────
   The run's first half: him at a window, hands on the glass, looking out at
   the ring. A point like `.wall__omry`'s, carried the same way, but pushed
   far back past the far side of the orbit (`--look-back` beyond two radii),
   so the whole ring goes round in front of him and his hands on the glass
   face it. He is drawn large, so he looms over the ring from behind it, and
   a little dimmer, as the back of the ring is. Going back along the ring's
   tilt carries him up the window, and `--look-lift` sets how far: low
   enough that his hands on the glass are level with the balls at the back
   of the ring, and his face under the top of the window. The atlas is the
   same one, so it takes the same proportion; the mask fades him into the
   dark on every side, since the window cuts him off at the waist and the
   frame's edges. */
.wall__back {
  --look-w: 2200;
  --look-h: 1202;
  --look-lift: 10%;
  --look-back: 2400px;
  grid-area: 1 / 1;
  justify-self: center;
  align-self: center;
  position: relative;
  width: 0;
  height: 0;
  transform-style: preserve-3d;
  transform: rotateX(calc(-1 * var(--orbit-tilt))) translateZ(calc(-2 * var(--orbit-r) - var(--look-back))) rotateX(var(--orbit-tilt));
  pointer-events: none;
}
.wall__look {
  position: absolute;
  left: 0;
  top: 0;
  display: block;
  width: calc(var(--look-w) * 1px);
  max-width: none;
  height: calc(var(--look-h) * 1px);
  translate: -50% calc(-1 * var(--look-lift));
  opacity: 0.85;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 14%, #000 86%, transparent), linear-gradient(to bottom, transparent, #000 8%, #000 62%, transparent 92%);
  -webkit-mask-composite: source-in;
  mask-image: linear-gradient(to right, transparent, #000 14%, #000 86%, transparent), linear-gradient(to bottom, transparent, #000 8%, #000 62%, transparent 92%);
  mask-composite: intersect;
}

/* ── the ball ───────────────────────────────────────────────────────────── */
.ball {
  display: grid;
  justify-items: center;
  gap: clamp(10px, 1vw, 16px);
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.ball:focus-visible { outline: 1px solid var(--cyan-l2-strong, #63d8ff); outline-offset: 6px; }
.ball__dots { transition: transform 420ms var(--ease-out, ease-out), filter 420ms var(--ease-out, ease-out); }
.ball:hover .ball__dots,
.ball:focus-visible .ball__dots { transform: scale(1.05); }
/* Pressed, and waiting on Omry's snap (scripts/studio/inspect.js). */
.ball.is-called .ball__dots { transform: scale(1.1); filter: brightness(1.45); }
.ball.is-called .ball__name { color: var(--signal-hot); }
/* While it is being inspected, the ball is in the middle of the screen. */
.ball.is-away .ball__dots { visibility: hidden; }

/* The ring holds still while a ball is inspected. */
.wall.is-inspecting .wall__slot { animation-play-state: paused; }
.ball__dots {
  display: block;
  width: 100%;
  aspect-ratio: 1;
}
.ball__name {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 2.3vw, 2.5rem);
  line-height: 0.92;
  letter-spacing: 0.012em;
  text-transform: uppercase;
  color: var(--bone);
}

/* ── the inspect view ───────────────────────────────────────────────────
   One layer over the window (scripts/studio/inspect.js): the page dims a
   little behind it, and the ball is rebuilt on its canvas. Above the header,
   under the drawn cursor. */
.inspect {
  position: fixed;
  inset: 0;
  z-index: 110;
}
.inspect[hidden] { display: none; }
.inspect::before {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(1, 4, 10, 0.5);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  opacity: 0;
  transition: opacity 520ms var(--ease-ui, ease);
}
.inspect.is-open::before { opacity: 1; }
.inspect__dots {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  cursor: grab;
  touch-action: none;
}
.inspect.is-turning .inspect__dots { cursor: grabbing; }
.inspect__foot {
  position: absolute;
  left: 50%;
  bottom: clamp(24px, 6vh, 64px);
  display: grid;
  justify-items: center;
  gap: 16px;
  transform: translate(-50%, 12px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 420ms var(--ease-ui), transform 520ms var(--ease-out);
}
.inspect.is-built .inspect__foot { opacity: 1; transform: translate(-50%, 0); pointer-events: auto; }
.inspect__name {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(2rem, 4vw, 3.4rem);
  line-height: 1;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--bone);
}
.inspect__store {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 9px 18px 9px 14px;
  border: 1px solid var(--edge-metal-lit);
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.72);
  color: #fff;
  text-decoration: none;
  white-space: nowrap;
  transition: border-color 300ms var(--ease-ui), background 300ms var(--ease-ui);
}
.inspect__store[hidden] { display: none; }
.inspect__store svg { flex: none; margin-top: -2px; }
.inspect__store span { display: grid; font-family: var(--font-ui); font-size: 17px; font-weight: 600; line-height: 1.05; letter-spacing: -0.01em; }
.inspect__store small { font-size: 10px; font-weight: 500; letter-spacing: 0.02em; }
.inspect__store[href]:hover,
.inspect__store[href]:focus-visible { border-color: #fff; background: #000; }
/* Coming soon: the same badge, quieter, and not a link. */
.inspect__store:not([href]) { opacity: 0.72; border-color: var(--edge-metal); cursor: default; }
.inspect__close {
  position: absolute;
  top: clamp(16px, 3vh, 32px);
  right: clamp(16px, 3vw, 40px);
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--edge-metal);
  border-radius: 50%;
  background: rgba(4, 10, 18, 0.6);
  color: var(--text-2);
  cursor: pointer;
  opacity: 0;
  transition: opacity 420ms var(--ease-ui, ease), color 300ms var(--ease-ui, ease), border-color 300ms var(--ease-ui, ease);
}
.inspect.is-open .inspect__close { opacity: 1; }
.inspect__close:hover,
.inspect__close:focus-visible { color: #fff; border-color: var(--edge-metal-lit); }

@media (prefers-reduced-motion: reduce) {
  .inspect::before, .inspect__foot, .inspect__close, .ball__dots { transition: none; }
  .wall__field { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(26px, 5vw, 40px); padding-top: 0; }
  .wall__slot { grid-area: auto; width: 100%; animation: none; transform: none; opacity: 1; filter: none; }
}

@media (forced-colors: active) {
  .wall::after { display: none; }
  .wall__field { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(26px, 5vw, 40px); padding-top: 0; }
  .wall__slot { grid-area: auto; width: 100%; animation: none; transform: none; opacity: 1; filter: none; }
}

@media print {
  .wall::after { display: none; }
  .wall__field { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(26px, 5vw, 40px); padding-top: 0; }
  .wall__slot { grid-area: auto; width: 100%; animation: none; transform: none; opacity: 1; filter: none; }
  .wall__omry, .wall__back { display: none; }
}

/* ── the frames ─────────────────────────────────────────────────────────
   Under 900px the ring is a grid of two, standing still. */
@media (max-width: 900px) {
  .wall__field { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(26px, 5vw, 40px); padding-top: 0; }
  .wall__slot { grid-area: auto; width: 100%; animation: none; transform: none; opacity: 1; filter: none; }
}

/* With no ring, Omry stands over the balls instead of among them: the first
   row, all of it, which the auto-placed slots then flow under. The box is
   narrower than the atlas, and that is all it takes: the script fits him to
   its height and centres him in it, and the moments of the run where he is
   off to one side run off its edge. One block for every frame that lays the
   ring out flat; print, above, does not draw him at all. */
@media (prefers-reduced-motion: reduce), (forced-colors: active), (max-width: 900px) {
  .wall__omry { grid-area: 1 / 1 / 2 / -1; width: min(100%, 560px); height: auto; aspect-ratio: 6 / 5; transform: none; }
  .wall__run { width: 100%; height: 100%; translate: none; }
  .wall__back { display: none; }
}
