@font-face {
  font-family: 'AliPixel';
  src: url('../fonts/pixel.ttf') format('truetype');
  font-display: swap;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: #1b1920;
  overflow: hidden;
  font-family: 'AliPixel', monospace;
}

/* Mobile Chrome/Safari draw a default translucent tap-highlight rectangle
   over whatever element receives a touch, sized to that element's own box -
   with no visible art of its own, a <button class="hotspot"> (e.g. the
   champion poke hitbox) shows this as a plain dark box briefly flashing
   over the character on tap. Not related to network/server speed at all -
   this is a per-tap client-side rendering default and would happen on the
   final hosted site too. Disabled everywhere rather than just on
   .hotspot, since every tappable element in the game (carousel arrows,
   difficulty picks, play/info buttons, board squares, etc.) is equally
   exposed to it. */
* {
  -webkit-tap-highlight-color: transparent;
}

#stage-wrapper {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* See index.html's trailing <script> for when/why this comes down. */
#loading-screen {
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: #1b1920;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  transition: opacity 0.4s ease;
}
#loading-screen.loading-done {
  opacity: 0;
  pointer-events: none;
}
#loading-title {
  color: #e8e6e0;
  font-size: 28px;
  letter-spacing: 2px;
}
/* The actual authored "..." dots pixel art, lifted from the AI thinking-
   bubble sprites (assets/anim/thinking_bubble/c78-83.png) rather than a CSS
   approximation: that source art is a single flattened bubble+dots image,
   so assets/anim/loading_dots/sheet.png is a new asset built by isolating
   just the 3 dot shapes (dropping the speech-bubble body/outline) and
   recoloring them from the bubble's navy to this screen's own light text
   color, keeping the exact authored pixel shapes and motion.
   The real authored motion is a 5-step traveling wave, held one extra
   frame at rest to breathe before it repeats - confirmed by comparing each
   dot's vertical position across all 6 raw source cels:
     step 1 (c78, held 2 frames): left down,  mid down, right down
     step 2 (c80):                left  up,   mid down, right down
     step 3 (c81):                left  up,   mid  up,  right down
     step 4 (c82):                left down,  mid  up,  right  up
     step 5 (c83):                left down,  mid down, right  up
     (then wraps back to step 1/c78 - NOT a raw c80->c83 wraparound, which
     would skip the rest state and double-flip left+right in the same tick)
   so the sheet's 6 frames are laid out left-to-right as c78,c78,c80,c81,c82,c83.
   Pure CSS steps() sprite animation (no JS) so it's already animating from
   the very first paint, same as this div's previous CSS-keyframe circles -
   it doesn't need to wait for main.js to load and run before it can start.
   100ms/frame matches FRAME_MS, the same speed the live thinking bubble
   itself animates at.
*/
#loading-dots {
  width: 102px;
  height: 33px;
  background-image: url('assets/anim/loading_dots/sheet.png');
  background-repeat: no-repeat;
  background-size: 612px 33px;
  image-rendering: pixelated;
  animation: loading-dots-step 0.6s steps(6) infinite;
}
@keyframes loading-dots-step {
  from { background-position: 0 0; }
  to { background-position: -612px 0; }
}

#stage {
  position: relative;
  width: 725px;
  height: 1024px;
  background: #1b1920;
  overflow: hidden;
  image-rendering: pixelated;
  transform-origin: center center;
  transform: scale(var(--scale, 1));
  flex-shrink: 0;
}

.screen {
  position: absolute;
  inset: 0;
  width: 725px;
  height: 1024px;
}

.hidden { display: none !important; }

.layer {
  position: absolute;
  left: 0;
  top: 0;
  width: 725px;
  height: 1024px;
  pointer-events: none;
  image-rendering: pixelated;
  user-select: none;
}

/* Each champion reaction sprite is a full 725x1024 canvas that bakes in
   BOTH a real window+character in its native slot AND an opaque (non-
   transparent) empty-window placeholder in the OTHER slot, since every
   champion's art was authored assuming a fixed native slot (see
   CHAMP_WINDOW_SHIFT_Y in data.js). #play-champ-top/#play-champ-bottom are
   translateY-shifted by JS to move whichever content belongs in that slot
   into view - but without clipping, the *unshifted* placeholder half of
   whichever image happens to stack on top (bottom always paints over top,
   being later in DOM order at the same z-index) would sit right on top of
   the other slot's real content whenever a champion is viewed in its own
   native slot (shift = 0), fully hiding it. These wrapper clips are the
   fix: each pins a fixed on-screen viewport around just its own slot's
   window art (bounds verified empirically against every reaction pose's
   real alpha-channel extent, with margin) so only whichever content the
   translateY shift has moved into that window can ever be visible, no
   matter which champion/slot pairing or shift is active. */
.champ-window-clip {
  /* Same box as the old bare .layer (0,0,725,1024) - deliberately NOT
     repositioned/resized to the slot itself, because the child img's
     translateY shift is computed in ITS OWN layout box (which still spans
     the full 0-1024 canvas, unshifted, inside this wrapper). clip-path
     (unlike overflow+height/top on the wrapper) clips a window fixed in
     THIS static box's own coordinate space, independent of the child's
     transform, so cropping happens post-shift, wherever the shifted
     content actually lands on screen - not pre-shift against the wrong
     portion of the child's box. */
  position: absolute;
  left: 0;
  top: 0;
  width: 725px;
  height: 1024px;
  overflow: hidden;
}
.champ-window-clip-top { clip-path: inset(0 0 874px 0); }
.champ-window-clip-bottom { clip-path: inset(874px 0 0 0); }

.icon-img {
  position: absolute;
  pointer-events: none;
  image-rendering: pixelated;
  user-select: none;
}

.hotspot {
  position: absolute;
  background: transparent;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  z-index: 50;
  outline: none;
}
.hotspot:focus { outline: none; }

/* PLAY is caged/non-interactive while an under-construction roster entry
   (see UNDER_CONSTRUCTION_CHAMPIONS in data.js) is showing - the cage art
   fully covers the button, so this just needs to stop the cursor reacting
   and stop clicks from doing anything, not hide/move anything. */
.hotspot-disabled {
  cursor: default;
  pointer-events: none;
}

/* ------------------------------------------------------------------ splash */
#difficulty-bar { position: absolute; inset: 0; }

/* ------------------------------------------------------------------- play */
/* Bubbles render at native art size (no upscale), tucked in tight against
   the near edge of their champion's window (speech bubbles poke out to
   the window's right with a small tail overlapping it; the thinking
   cloud sits just above-right of the character's head, closer in). */
.bubble-slot {
  position: absolute;
  z-index: 40;
  pointer-events: none;
  display: none;
}
.bubble-slot img { display: block; width: 100%; height: 100%; image-rendering: pixelated; }
/* Ground-truth pixel-measured directly from real recorded frames (source
   frame 176 for speech: exact outline bbox x408-521,y6-82 top / the
   equivalent bottom slot at the same x with the constant +872px vertical
   slot offset measured between the two windows; source frame 79 for the
   thinking cloud: exact bbox x400-468,y8-61). Both bubbles sit tucked in
   tight against their window's near edge, NOT floating far off to the
   side as previously placed. */
#bubble-top.speech      { left: 408px; top: 6px;   width: 114px; height: 77px; }
#bubble-top.thinking    { left: 400px; top: 8px;   width: 69px;  height: 54px; }
#bubble-bottom.speech   { left: 408px; top: 878px; width: 114px; height: 77px; }
#bubble-bottom.thinking { left: 400px; top: 880px; width: 69px;  height: 54px; }

#square-highlights { position: absolute; inset: 0; z-index: 12; pointer-events: none; }
/* Square selection highlight - a real 84x84 sprite (see HIGHLIGHT_SPRITES
   in data.js), swapped between the click-flash and sustain-stroke frame by
   setHighlight() in main.js. No computed color/border - the art itself
   already carries both colorways (white/black) and both states. */
.sq-highlight {
  position: absolute;
  width: 84px; height: 84px;
  pointer-events: none;
}

#piece-layer { position: absolute; inset: 0; z-index: 20; }
.piece {
  position: absolute;
  width: 84px; height: 84px;
  image-rendering: pixelated;
  cursor: pointer;
  z-index: 21;
  /* Suppress the browser's own native image-drag (ghost thumbnail + native
     dragstart) and touch scroll/zoom gestures so pointerdown/move/up in
     main.js's drag-to-move handling gets a clean, uninterrupted gesture
     instead of fighting the browser's built-in drag-an-<img> behavior. */
  touch-action: none;
  user-select: none;
  -webkit-user-drag: none;
}
/* A piece actively being dragged is still the exact same plain <img> at its
   native size/art - just raised above every other piece (and the
   king-cross-fx burst, z-index 45) so it visibly rides on top of whatever
   square it's currently over. No scale/shadow/recolor - it looks identical
   to a resting piece, only its position and stacking change. */
.piece.dragging {
  z-index: 55;
  cursor: grabbing;
}

/* Illegal-destination-click feedback: a real 3-frame animated X (see
   ILLEGAL_SQUARE_ANIM_FRAMES in data.js, showIllegalX in main.js) - sits
   above every piece so it's never partially hidden by the piece occupying
   that square. */
.illegal-x {
  width: 84px; height: 84px;
  z-index: 46;
  display: none;
}

/* A won game's king-crossed sparkle burst is real art that intentionally
   overflows its own tile on every side - this is the exact same king
   <img>, just temporarily grown/repositioned and raised above everything
   else while its src cycles through the burst frames (see applyKingAnim in
   main.js). Never a separate element. */
.piece.king-cross-fx {
  width: 110px; height: 120px;
  z-index: 45;
}

/* --------------------------------------------------------------- captured trays */
.tray {
  position: absolute;
  width: 242px; height: 26px;
  z-index: 30;
  image-rendering: pixelated;
}
#tray-top { left: 20px; top: 91px; }
#tray-bottom { left: 20px; top: 119px; }
.tray-slot {
  position: absolute;
  top: 0;
  width: 16px; height: 26px;
  image-rendering: pixelated;
  pointer-events: none;
}

/* Occupies the exact same 16x26 footprint as one captured-piece slot and
   sits flush against the tray's ground line, immediately to the left of
   the first slot. Real authored glyphs (extracted from mockup_14's
   "material + white/black" and "material white/black numbers" layers),
   positioned as absolutely-placed children rather than CSS text - the
   pixel-measured source layout has the plus sign flush with the box's
   bottom-right corner group and the digit immediately to its right with a
   2px gap, both glyphs' bottom edges flush with the box's bottom edge
   (the tray's own ground line). Multi-digit values grow leftward off the
   box's right edge so the ones-digit always lands in the source-authored
   spot. */
.tray-advantage {
  position: absolute;
  width: 16px;
  height: 26px;
  z-index: 30;
}
.tray-advantage img {
  position: absolute;
  bottom: 0;
  image-rendering: pixelated;
}
.tray-advantage .adv-plus { width: 6px; height: 6px; }
.tray-advantage .adv-digit { width: 8px; height: 12px; }
#tray-adv-top { left: 4px; top: 91px; }
#tray-adv-bottom { left: 4px; top: 119px; }

#diff-readout-img {
  image-rendering: pixelated;
  pointer-events: none;
  z-index: 30;
}

/* --------------------------------------------------------------------- clock */
/* clock_top.png / clock_bottom.png are the real housing art (re-extracted
   from mockup_14's clean "timer bg" layer - a blank plate with no baked
   placeholder digits, unlike the old art), full-stage size, used directly
   as `.layer`s in the HTML. clock_light_* is the real active-turn glow
   border overlay, swapped in/out with a hard opacity cut (no fade) to match
   the source gif's one-frame turn-swap cut. The digits/colon are now
   also real authored art (SegClock/segdisplay.js swaps <img> src per
   digit/color instead of drawing SVG segments), overlaid at the display's
   exact position within that full-canvas art. */
.clock-light { opacity: 0; z-index: 29; }
.clock-light.active { opacity: 1; }
.clock-frame {
  position: absolute;
  left: 569px; width: 134px; height: 51px;
  z-index: 30;
}
#clock-top { top: 78px; }
#clock-bottom { top: 895px; }
.seg-digit {
  position: absolute;
  top: 0; width: 26px; height: 51px;
  image-rendering: pixelated;
}
.seg-colon {
  position: absolute;
  top: 11px; width: 5px; height: 25px;
  image-rendering: pixelated;
}

/* --------------------------------------------------------------------- modals */
.modal {
  position: absolute;
  inset: 0;
  z-index: 100;
}

/* The info window's background art sits at a razor-thin margin from both
   the difficulty bar above it (~3px clearance from the "easy" pill) and
   the play button below it (~4px clearance) - moving the whole window
   either direction just trades one clipping bug for the other. Since
   nothing behind an open modal should be visible anyway, simply hide the
   specific splash elements that risk peeking past those thin edges while
   the info modal is open. */
#splash-screen.info-open #difficulty-bar,
#splash-screen.info-open #play-btn-art,
#splash-screen.info-open #play-btn-shine,
#splash-screen.info-open #play-btn-cage,
#splash-screen.info-open #info-arrow-img {
  visibility: hidden;
}

