/* Play Mode - Fullscreen Game Player Styles */

/* ============================================
   CSS Custom Properties for Play Mode
   ============================================ */
:root {
  /* Button size and glow tokens shared with the end screen live in
     common/scene/EndScreen.css. */
  --shadow-success-glow: 0 6px 20px color-mix(in srgb, var(--color-success) 60%, transparent);
}

/* ============================================
   Like Button State
   ============================================ */
#like-button.liked svg,
#mobile-like-button.liked svg {
  fill: currentColor;
}

/* ============================================
   Auth Buttons
   ============================================ */
#auth-buttons {
  display: flex;
  gap: var(--space-2);
}

/* ============================================
   Body Layout
   ============================================ */
body {
  /* Where the play area starts: under the header, which stays on screen
     during play like every other page (its play/pause and mute controls are
     the in-game controls). Every layer that begins at that edge reads this
     one value; capture mode sets it to 0 when it hides the header. */
  --play-top: var(--clearance-header);
  width: 100%;
  height: 100%;
  overflow: hidden;
}

/* ============================================
   Canvas Container
   Pinned from the header to the bottom edge at every size.
   ============================================ */
.play-container {
  position: fixed;
  top: var(--play-top);
  left: 0;
  right: 0;
  bottom: 0;
  overflow: hidden;
  background: var(--color-background);
}

/* !important: the renderer's setSize writes the canvas an inline pixel size,
   and between a viewport change (rotation, iOS toolbar) and the resize that
   follows it, that stale size would leave a strip of the container showing.
   Filling by CSS keeps the canvas on the container through the gap. */
#three-canvas {
  width: 100% !important;
  height: 100% !important;
  display: block;
  background: var(--color-background);
  filter: blur(8px);
  transition: filter 0.25s ease;
}

body.game-active #three-canvas {
  filter: none;
}

/* ============================================
   Overlay Screen
   ============================================ */
.overlay-screen {
  position: fixed;
  top: var(--play-top);
  left: 0;
  right: 0;
  bottom: 0;
  min-width: var(--width-page-min);
  background: color-mix(in srgb, var(--color-background) 75%, transparent);
  display: flex;
  justify-content: center;
  align-items: center;
  transition:
    opacity 0.25s ease-out,
    background 0.25s ease;
  opacity: 0;
  pointer-events: none;
  z-index: 5002;
}

/* Blackout layer to hide canvas behind screenshot */
#play-blackout {
  position: fixed;
  top: var(--play-top);
  left: 0;
  width: 100%;
  height: calc(100% - var(--play-top));
  background: var(--color-background);
  z-index: 5000;
}

/* Background image element for blurred screenshot */
#play-background {
  position: fixed;
  top: var(--play-top);
  left: 0;
  width: 100%;
  height: calc(100% - var(--play-top));
  object-fit: cover;
  filter: blur(8px);
  z-index: 5001;
  opacity: 0;
  transition: opacity 0.25s ease-out;
}

#play-background.loaded {
  opacity: 1;
}

body.game-active #play-blackout,
body.game-paused #play-blackout,
body.game-active #play-background,
body.game-paused #play-background {
  display: none;
}

.overlay-screen.fade-in {
  opacity: 1;
  pointer-events: auto;
}

.overlay-screen.fade-out {
  opacity: 0;
  pointer-events: none;
}

.overlay-screen.hidden {
  display: none;
}

/* #play-content lives in an inner scroll wrapper below the header, so the
   scrollbar stays in the visible region (like .main-content on home)
   instead of running up behind the panel. The overlay itself stays the
   full-screen tinted backdrop, and the confetti/rain canvases remain
   pinned to it. */
#play-scroll {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Below #play-content's min width, plain center overflows BOTH edges
     and flexbox makes the left one unscrollable; safe center falls back
     to start-aligned so everything stays reachable. (Progressive
     enhancement: browsers without safe keep the plain center above.) */
  align-items: safe center;
  /* Pin to the top (not center): #play-content's auto margins still
       center the card when there is room, but on overflow the top of
       the card stays anchored instead of being pushed out of view.
       Without this, justify-content: center centers the overflowing
       column and the top becomes unreachable. */
  justify-content: flex-start;
  /* Scroll if the card exceeds the available height, so its controls
       stay reachable on short viewports. */
  overflow-y: auto;
}

/* The footer follows the site rule: fixed at the bottom on a tall screen,
   in the menu's scroll flow on a short one. Either way it lives in the
   overlay, so it leaves with the start and pause screens when the game
   takes the screen, and play needs no footer rule of its own. On a tall
   screen the wrapper keeps running to the bottom edge and carries the
   footer band in its padding, like .landing: Safari clips a fixed
   descendant to its scrolling ancestor's box, so a wrapper that stopped
   above the bar would lose the footer there. */
@media (min-height: 769px) {
  #play-scroll {
    padding-bottom: var(--clearance-footer);
    scroll-padding-bottom: var(--clearance-footer);
  }
}

/* In flow it is the wrapper's last item, so #play-content's auto margins
   leave it at the bottom edge when the card is short and at the end of the
   scroll when it is tall. */
@media (max-height: 768px) {
  #play-scroll .site-footer {
    position: static;
    flex-shrink: 0;
    width: 100%;
  }
}

/* ============================================
   Play Content
   ============================================ */
#play-content {
  position: relative;
  z-index: 1;
  text-align: center;
  color: var(--color-text);
  padding: var(--space-2);
  width: 100%;
  /* Same floor as the header/footer panels, so the preview tile (and the
     input-guide table inside it) never has to scroll horizontally. */
  min-width: var(--width-page-min);
  max-width: 640px;
  /* Auto margins center the card in the scroll wrapper when there is
     room; on overflow they collapse so the top stays reachable. */
  margin-top: auto;
  margin-bottom: auto;
}

#play-title {
  display: block;
  margin: 0;
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-bold);
  color: var(--color-text);
}

/* Outcome banner shown in end-state ("YOU WIN!" / "GAME OVER").
   Takes the thumbnail's place in the tile; text follows an SVG path so
   it arcs upward. Hidden by default; built/toggled by the shared EndScreen.
   Pointer-events disabled so clicks pass through. Its look (color, letters,
   ripple) is .outcome-banner in common/scene/EndScreen.css. */
#play-outcome {
  display: none;
  position: relative;
  width: 100%;
  pointer-events: none;
  z-index: 2;
}

#play-outcome.visible {
  display: block;
}

/* A framed end-state (a camera move ran) fades in over the second half
   of the move (B1D_System.END_SCREEN_AT_MS through END_CAMERA_MS), so it
   runs 500ms instead of the pause menu's 250ms and completes as the
   camera lands. Unframed end-states (nothing to frame, e.g. no player
   asset) keep the base 250ms fade. */
#play-overlay.end-state-framed {
  transition:
    opacity 0.5s ease-out,
    background 0.25s ease;
}

/* In end-state (win/lose), the input guide is irrelevant -- the player
   isn't going to look up controls after the game is over. The accent
   ring rides the tile chrome, so it goes too. */
#play-overlay.end-state #play-controls {
  display: none !important;
}

#play-overlay.end-state #play-thumbnail::after {
  content: none;
}

/* End-state buttons are their own DOM nodes (not the relabeled Start
   button) so a queued Space-keyup or touchend can't synthesize a click
   on a still-focused play-mode button when the end screen pops up. */
#play-end-primary-button,
#play-end-secondary-button {
  display: none;
}
#play-overlay.end-state #play-start-button,
#play-overlay.end-state #play-restart-button {
  display: none;
}
#play-overlay.end-state #play-end-primary-button,
#play-overlay.end-state #play-end-secondary-button {
  display: flex;
}

/* Confetti and rain canvases: fill the overlay, sit behind #play-content.
   Only one is visible at a time, gated by the end-state-win/lose class.
   Palette tokens live on :root in style.css so Confetti.js / Rain.js can
   read them via getComputedStyle and stay in sync with the editor preview. */
#play-confetti,
#play-rain {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  display: none;
}

#play-overlay.end-state-win #play-confetti {
  display: block;
}

#play-overlay.end-state-lose #play-rain {
  display: block;
}

#play-author {
  display: block;
  font-size: var(--font-size);
  color: var(--color-text-dark);
  margin-bottom: var(--space-2);
}

/* The leaderboard block (Leaderboard.css): the tile's second view on the
   start screen and the pause menu, behind the Controls / Leaderboard
   switch; the run's board between the thumbnail and the buttons on the
   end screen (Player.#openEndSurface moves it out of the tile). */
#play-overlay.end-state #play-leaderboard.visible {
  margin-bottom: var(--space-2);
}

/* The tile's view tabs (the shared .tabs, with the tile as their
   .tab-panel). Shown only once the game's board exists
   (Player.#showLeaderboard); the end screen has no tile view to switch.
   The tile's radius is set by its id above, which outranks the shared
   .tab-panel corner rule, so the corner is squared here by id -- and the
   screenshot's own rounder corner with it, or it would notch the square
   under the first tab. */
#play-overlay.end-state #play-tile-tabs {
  display: none;
}

#play-tile-tabs:not([hidden]) + #play-thumbnail,
#play-tile-tabs:not([hidden])
  + #play-thumbnail
  :is(#play-screenshot, #play-controls, #play-leaderboard) {
  border-top-left-radius: 0;
}

/* One view at a time: the guide by default, the board behind its tab. */
#play-thumbnail.view-leaderboard #play-controls,
#play-thumbnail:not(.view-leaderboard) #play-leaderboard {
  display: none;
}

/* The board view fills the tile like the guide does: same panel, centered,
   scrolling when the tile is short. */
#play-thumbnail #play-leaderboard.visible {
  position: absolute;
  /* Same inset, stacking and rounding as #play-controls (below), for the
     same reason: the glow ring keeps the outer pixel and stays above the
     board only where nothing else paints. */
  inset: var(--border-width);
  z-index: 3;
  border-radius: calc(var(--radius-lg) - var(--border-width));
  justify-content: center;
  padding: var(--space-2);
  background: color-mix(in srgb, var(--color-surface) 95%, transparent);
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
}

#play-tagline {
  margin: var(--space-2) 0 0 0;
  font-size: var(--font-size-lg);
  color: var(--color-text-dark);
}

#play-thumbnail {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  margin-bottom: var(--space-2);
  border-radius: var(--radius-lg);
  border: var(--border-width) var(--border-style) var(--color-border);
  /* One pixel of the tile's own surface inside the border: the glow ring
     runs on that pixel (the ring is the outer pixel of the padding box,
     style.css), so at rest it is a dark seam and not a sliver of screenshot
     peeking out around the guide. The guide and board are inset by the same
     pixel and stacked above the ring (see #play-controls). */
  padding: var(--border-width);
  overflow: hidden;
  background: var(--color-surface);
}

#play-screenshot {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Rounder than the tile's clip on purpose: when the translucent guide
     covers this image, their edges must not coincide at the corners --
     seam pixels there let bright artwork glow through as a 1px arc.
     Receding past the clip curve leaves only the tile's dark surface
     under the seams. */
  border-radius: calc(var(--radius-lg) + 5px);
  opacity: 0;
  transition: opacity 0.25s ease-out;
}

#play-screenshot.loaded {
  opacity: 1;
}

/* In end-state, hide the entire thumbnail chrome (image, border,
   background) and let the tile shrink to what it holds: the outcome
   banner, then the title, author, and leaderboard block flowing under
   it. The overlay backdrop shows through to the blurred game canvas. */
#play-overlay.end-state #play-thumbnail {
  aspect-ratio: auto;
  background: transparent;
  border: none;
  overflow: visible;
}

#play-overlay.end-state #play-screenshot {
  display: none;
}

/* Title/author are moved into #play-thumbnail by Player.#openEndSurface so
   they flow under the outcome arc. The banner's box ends at its
   lettering (OutcomeBanner.fit), so the title tucks up into the arc by
   a plain margin, and the author follows on the grid. */
#play-overlay.end-state #play-thumbnail #play-title,
#play-overlay.end-state #play-thumbnail #play-author {
  position: relative;
  text-align: center;
  pointer-events: none;
  z-index: 3;
}

#play-overlay.end-state #play-thumbnail #play-title {
  margin-top: calc(-1 * var(--space-10));
}

#play-overlay.end-state #play-thumbnail #play-author {
  margin: var(--space-1) 0 0;
}

/* ============================================
   Button Layout
   ============================================ */
.button-layout {
  display: flex;
  gap: var(--space-2);
  align-items: stretch;
  width: 100%;
}

/* ============================================
   Action Buttons
   ============================================ */

/* The row's text buttons share its width. Their look is .play-button in
   common/scene/EndScreen.css. */
.play-button {
  flex: 1;
}

/* The square icon buttons (Share / Remix / Like) are .play-button too, in
   the success color and at their own size. */
.action-button {
  flex: initial;
  width: var(--height-button-large);
  padding: 0;
  background: var(--color-success);
  border-color: var(--color-success);
}

.action-button:hover:not(:disabled) {
  background: color-mix(in srgb, var(--color-success) 85%, black);
  border-color: color-mix(in srgb, var(--color-success) 85%, black);
  box-shadow: var(--shadow-success-glow);
}

/* Progress bar for loading state */
#play-start-button .button-text {
  position: relative;
  z-index: 2;
}

#play-start-button .button-progress {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: 0%;
  background: var(--color-accent-light);
  transition:
    width 0.25s ease,
    background 0.5s ease;
  z-index: 1;
}

#play-start-button:not(:disabled) .button-progress {
  background: var(--color-accent);
}

#play-start-button:hover:not(:disabled) .button-progress {
  background: color-mix(in srgb, var(--color-accent) 85%, black);
}

/* Action button icons */
.action-button span {
  display: flex;
  align-items: center;
  justify-content: center;
}

@media (max-width: 450px) {
  .action-button {
    width: var(--height-button-small);
  }
}

/* ============================================
   Controls Table
   ============================================ */

/* Input guide -- once the control scheme is known it fills the thumbnail
   tile and stays up (there is no toggle; the guide IS the tile's view).
   Scrolls vertically (only) when the scheme has more controls than fit;
   the tile's min width (via #play-content) keeps rows from ever needing
   a horizontal scroll. */
#play-controls {
  display: flex;
  position: absolute;
  /* Pulled in by the glow ring's width and stacked above it (the ring is
     z-index 2, style.css): the ring keeps the tile's outer pixel, and if
     Safari drops the ring's mask the bare wedge lands behind this near-solid
     guide instead of on top of it. The corner radius follows the ring's
     inner edge so the arcs stay uncovered too. */
  inset: var(--border-width);
  z-index: 3;
  border-radius: calc(var(--radius-lg) - var(--border-width));
  padding: var(--space-2);
  font-size: var(--font-size);
  color: var(--color-text);
  /* No backdrop-filter here: Chromium clips a backdrop-filter layer's
     rounded corners without antialiasing, so the bright screenshot under
     this guide bleeds ~1px outside the tile's corner arcs. Behind a 95%
     opaque fill the blur only softened a 5% ghost, so it isn't missed. */
  background: color-mix(in srgb, var(--color-surface) 95%, transparent);
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  opacity: 0;
  /* visibility (not just opacity) so the not-yet-loaded guide isn't an
     invisible tab stop -- it is keyboard-focusable (see player.html). */
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.25s ease-out;
}

#play-controls.visible {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

/* The guide and the board fill a tile that clips, so the focus ring is
   drawn inside them: around them, none of it would show. The board has no
   tab stop of its own, but a browser gives one to whatever scrolls. */
#play-controls:focus-visible,
#play-leaderboard:focus-visible {
  outline-offset: calc(-1 * var(--focus-outline-offset));
}

/* margin: auto (not flex centering) so a table taller than the tile
   scrolls from its top edge instead of clipping both ends. */
#play-controls table {
  margin: auto;
  border-collapse: collapse;
}

#play-controls th,
#play-controls td {
  padding: var(--space-1) var(--space-2);
  text-align: left;
  white-space: nowrap;
}

/* Input columns stay nowrap (key names read as units), but a long action
   label wraps rather than pushing the row past the tile's hidden
   horizontal overflow. */
#play-controls td:first-child {
  white-space: normal;
}

#play-controls th {
  font-weight: var(--font-weight);
  color: var(--color-text-dark);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

#play-controls .control-input {
  font-family: var(--font-family-mono);
  font-size: var(--font-size-sm);
  background: var(--overlay-black-50);
  padding: 2px 6px;
  border-radius: var(--radius-sm);
}

/* ============================================
   Responsive: Mobile (768px)
   ============================================ */
@media (max-width: 768px) {
  #play-title {
    font-size: var(--font-size-lg);
  }

  /* The arc is smaller here, so the title tucks under it less. */
  #play-overlay.end-state #play-thumbnail #play-title {
    margin-top: calc(-1 * var(--space-5));
  }

  #play-tagline {
    font-size: var(--font-size);
  }
}

/* ============================================
   Responsive: Mini (480px)
   ============================================ */
@media (max-width: 480px) {
  #play-controls {
    font-size: var(--font-size-sm);
  }

  #play-controls .control-input {
    font-size: var(--font-size-xs);
  }
}

/* ============================================
   Play Overlay Container (record button + its timer)
   ============================================ */
.play-overlay-container {
  position: fixed;
  top: calc(var(--play-top) + var(--space-2));
  right: calc(var(--space-2) + var(--safe-area-right));
  display: none;
  z-index: 2001;
}

/* ============================================
   Pre-record Countdown
   Shown over the play view while the capture pipeline warms up, then removed
   before MediaRecorder starts (so it is never captured). See PlayerRecorder.
   ============================================ */
.play-countdown {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2002;
  pointer-events: none;
  font-weight: var(--font-weight-bold);
  font-size: clamp(72px, 20vw, 220px);
  line-height: 1;
  color: var(--color-text);
  text-shadow: var(--shadow-accent-glow);
}

.play-countdown-pulse {
  animation: play-countdown-pulse 0.25s ease-out;
}

@keyframes play-countdown-pulse {
  from {
    opacity: 0;
    transform: scale(1.5);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

/* ============================================
   Statistics Panel
   Page chrome at body level (PlayerRenderer mounts it there): it must sit
   above the pause/start/end overlay stack so a ?stats=1 panel stays
   readable on those screens, and inside the play container -- a fixed
   layer with its own stacking context -- no z-index could reach past them.
   ============================================ */
.play-statistics {
  position: fixed;
  top: calc(var(--play-top) + var(--space-2));
  left: var(--space-2);
  background: var(--overlay-black-50);
  color: var(--color-text);
  font-family: var(--font-family-mono);
  font-size: var(--font-size-sm);
  line-height: 1.4;
  padding: var(--space-2);
  border-radius: var(--radius-sm);
  /* The play area, less its own gap top and bottom: the open sections are
     taller than the screen on a phone and on a 1080p desktop alike, and
     what does not fit scrolls (StatsPanel.css) instead of running off the
     bottom edge. The home indicator comes out of the height too. */
  max-height: calc(100% - var(--play-top) - var(--space-2) * 2 - var(--safe-area-bottom));
  /* Above the overlay stack (5000-5002), below the fixed header (9999). */
  z-index: 5005;
  display: none;
}

.play-statistics.visible {
  /* A column: the title keeps its place while the body under it scrolls. */
  display: flex;
}

/* With touch controls on screen the panel stops a gap above the joystick,
   so its sections never open behind a control: the joystick keeps its own
   space and the panel scrolls within what is left. */
body:has(.play-container > .touch-controls) .play-statistics {
  max-height: calc(
    100% - var(--play-top) - var(--space-2) * 3 - var(--joystick-size) - var(--safe-area-bottom)
  );
}

/* Keep the statistics overlay out of recorded frames while a capture runs. */
body.recorder-capturing .play-statistics {
  display: none;
}

.play-statistics-close {
  position: absolute;
  top: var(--space-1);
  right: var(--space-2);
  background: none;
  border: none;
  color: var(--color-text);
  font-size: var(--font-size-lg);
  line-height: 1;
  padding: 0;
  cursor: pointer;
}

/* ============================================
   Floating Record Button (gameplay capture / clip share)
   Shown for everyone via body.recorder-enabled during active gameplay.
   The live timer is appended after the button in the DOM; row-reverse
   puts it on the button's left while the stack stays right-anchored.
   ============================================ */
body.recorder-enabled.game-active .play-overlay-container {
  display: flex;
  flex-direction: row-reverse;
  gap: var(--space-2);
}

.floating-record-button {
  display: none;
  width: 44px;
  height: 44px;
  padding: 0;
  background: var(--overlay-black-50);
  border: none;
  border-radius: var(--radius-sm);
  color: var(--color-text);
  cursor: pointer;
  align-items: center;
  justify-content: center;
}

body.recorder-enabled.game-active .floating-record-button {
  display: flex;
}

.floating-record-button svg {
  width: 24px;
  height: 24px;
}

/* Active capture: red glyph with a soft pulse so it reads as "live". */
.floating-record-button.recording {
  color: var(--color-error);
  animation: record-pulse 1.5s ease-in-out infinite;
}

@keyframes record-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.45;
  }
}

/* Live "elapsed / cap" timer beside the record button during a player capture.
   Turns red in the final seconds before the auto-stop cap. */
.play-record-timer {
  display: flex;
  align-items: center;
  padding: 0 var(--space-2);
  height: 44px;
  background: var(--overlay-black-50);
  border-radius: var(--radius-sm);
  color: var(--color-text);
  font-family: var(--font-family-mono);
  font-size: var(--font-size-sm);
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}

.play-record-timer.ending {
  color: var(--color-error);
}

/* ============================================
   Play Chrome
   No footer during play (see --notice-bottom, common/style.css).
   ============================================ */
body.game-active {
  --notice-bottom: var(--notice-bottom-bare);
}

/* ============================================
   Capture Immersive Mode
   For the duration of a capture, hide the header and let the canvas fill the
   frame so the bar never lands in the recorded clip (whatever the crop
   aspect). Instant (no transition) -- we don't animate the capture. This is
   the one place the header leaves the screen. The renderer is reallocated to
   match (PlayerRecorder calls onWindowResize), so the canvas is not merely
   CSS-stretched.
   ============================================ */
body.recorder-immersive {
  --play-top: 0px;
}

body.recorder-immersive .panel-menu {
  transform: translateY(calc(-100% - var(--safe-area-top)));
}

/* ============================================
   Kiosk Mode (?kiosk=1, see Player.#kiosk)
   The game is the whole page: no header, the canvas from the top edge, and
   no pointer over it. Until the game is first revealed the start screen is
   hidden as well -- its backdrop, title and buttons are for a person
   choosing to play, and there is nobody there. The end screens still show:
   they come later, on the same overlay.
   ============================================ */
body.kiosk {
  --play-top: 0px;
}

body.kiosk .panel-menu {
  display: none;
}

body.kiosk .play-container {
  cursor: none;
}

body.kiosk-loading #play-scroll,
body.kiosk-loading #play-background {
  visibility: hidden;
}

/* ============================================
   Next / back game (PlayerFeed)
   In the menu's own layout, around the card: #play-stage is a row in
   landscape (strips down both sides) and a column in portrait (bands
   across the top and bottom), so they extend the layout rather than sit
   over it, and the scrollbar keeps its edge. On the start, pause and end
   screens only: they live in the overlay, so they leave with it and are
   never over live play. A chevron pointing the way with its label on its
   open side (the label reads along the strip, turned upright), in the
   icon-button colors (.button-icon). A side with no game keeps its room
   (disabled: visibility, not display) so the card stays centered.
   ============================================ */
#play-stage {
  display: flex;
  width: 100%;
  /* Fill the wrapper when the card is short, so the zones reach the
     edges and the card's auto margins center it between them. */
  flex: 1 0 auto;
}

.play-feed-zone {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--color-link);
  cursor: pointer;
  transition: color 0.25s ease;
}

.play-feed-zone:disabled {
  visibility: hidden;
}

@media (hover: hover) {
  .play-feed-zone:hover {
    color: var(--color-link-light);
  }
}

.play-feed-zone:active,
.play-feed-zone:focus-visible {
  color: var(--color-link-light);
}

/* The strips run to the edges of the screen, so the ring is drawn inside
   them, for a keyboard's focus and a gamepad's highlight alike. */
.play-feed-zone:is(:focus-visible, .menu-nav-focus) {
  outline-offset: calc(-1 * var(--focus-outline-offset));
}

.play-feed-label {
  font-size: var(--font-size-sm);
  line-height: 1;
  white-space: nowrap;
}

/* The glyph sits in the middle of its box; take the box in to the glyph
   so the label lands right beside it. */
.play-feed-zone svg {
  display: block;
  margin: -5px;
}

@media (orientation: landscape) {
  #play-stage {
    flex-direction: row;
    /* Strips at the screen's edges; the two are the same width, so the
       card between them stays centered. */
    justify-content: space-between;
  }

  .play-feed-zone {
    width: var(--space-10);
    /* Label, then the chevron at the outer edge. */
    flex-direction: row;
  }

  #play-prev {
    flex-direction: row-reverse;
  }

  /* The label reads down the strip beside the next chevron, and up it
     beside the previous one, so each reads away from the card. */
  .play-feed-label {
    writing-mode: vertical-rl;
  }

  #play-prev .play-feed-label {
    transform: rotate(180deg);
  }
}

@media (orientation: portrait) {
  #play-stage {
    flex-direction: column;
    align-items: center;
  }

  .play-feed-zone {
    width: 100%;
    height: var(--space-10);
    /* Label, then the chevron at the outer edge. */
    flex-direction: column;
  }

  #play-prev {
    flex-direction: column-reverse;
  }

  /* The side chevrons turned upright: left becomes up, right becomes down. */
  .play-feed-zone svg {
    transform: rotate(90deg);
  }
}

/* ============================================
   Stepping between games
   A step from the arrows loads the neighbor's page; player.html adds a
   view-transition type for the direction at pagereveal, and in place of
   the site-wide fade (common/style.css) the outgoing page slides out one
   way while the incoming one slides in behind it -- along the width in
   landscape, the height in portrait. Reduced motion keeps the fade rule
   there.

   The outgoing snapshot is shown here, against the site rule that hides
   it: that rule guards a cold navigation, and a step leaves a player page
   that is fully drawn for another copy of itself, warm in the cache. A
   slow destination can still hand the snapshot over blank, in which case
   a blank pane slides out ahead of the new page.

   The header and footer sit out of the slide. For a step only (the type
   is set on both pages, so both captures see it) each is named, which
   snapshots it as its own layer painted above the root: the play area
   slides beneath it. The layer shows the outgoing page's bar, held still
   -- the new page's is captured before the scripts that fill it have run
   -- and the live one takes its place when the slide ends. Every other
   arrival and departure keeps the site-wide fade, bars included.
   ============================================ */
html:active-view-transition-type(play-next, play-prev) .panel-menu {
  view-transition-name: site-header;
}

html:active-view-transition-type(play-next, play-prev) .site-footer {
  view-transition-name: site-footer;
}

::view-transition-old(site-header),
::view-transition-old(site-footer) {
  animation: none;
}

::view-transition-new(site-header),
::view-transition-new(site-footer) {
  visibility: hidden;
}

@media (orientation: landscape) {
  :root {
    --play-slide-next: translateX(100%);
    --play-slide-prev: translateX(-100%);
  }
}

@media (orientation: portrait) {
  :root {
    --play-slide-next: translateY(100%);
    --play-slide-prev: translateY(-100%);
  }
}

/* In from the side stepped toward; out toward the side stepped from. */
@keyframes play-slide-in-next {
  from {
    transform: var(--play-slide-next);
  }
}

@keyframes play-slide-in-prev {
  from {
    transform: var(--play-slide-prev);
  }
}

@keyframes play-slide-out-next {
  to {
    transform: var(--play-slide-prev);
  }
}

@keyframes play-slide-out-prev {
  to {
    transform: var(--play-slide-next);
  }
}

@media (prefers-reduced-motion: no-preference) {
  html:active-view-transition-type(play-next)::view-transition-old(root),
  html:active-view-transition-type(play-prev)::view-transition-old(root) {
    visibility: visible;
  }

  html:active-view-transition-type(play-next)::view-transition-new(root) {
    animation-name: play-slide-in-next;
    animation-timing-function: ease-out;
  }

  html:active-view-transition-type(play-next)::view-transition-old(root) {
    animation-name: play-slide-out-next;
    animation-timing-function: ease-out;
  }

  html:active-view-transition-type(play-prev)::view-transition-new(root) {
    animation-name: play-slide-in-prev;
    animation-timing-function: ease-out;
  }

  html:active-view-transition-type(play-prev)::view-transition-old(root) {
    animation-name: play-slide-out-prev;
    animation-timing-function: ease-out;
  }
}
