/* ==========================================================================
   game.css — chrome for the two Kaplay games, plus the incident-report modal.

   Loaded only by index.html and games/skyfire-squadron/index.html; /recipes/
   has no use for any of it. MUST be linked AFTER site.css: .enemy-grid and
   .enemy-card here extend .portfolio-grid / .portfolio-card, which live there,
   and every colour below is a custom property defined on :root in site.css.
   ========================================================================== */

/* Hero + Game
   ========================================================================== */

#hero {
  margin-bottom: 48px;
}

.hero-intro h1 {
  margin-bottom: 0.2em;
}

.hero-tagline {
  color: var(--text-muted);
}

.game-wrap {
  position: relative;
  max-width: 480px;
  margin: 24px auto 0;
}

.game-hud {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1em;
  justify-content: space-between;
  margin-bottom: 8px;
  font-size: 0.9rem;
}

.hud-item {
  display: flex;
  align-items: center;
  gap: 0.5em;
}

/* The [hidden] attribute's UA-stylesheet display:none loses to the
   .hud-item rule above (author CSS always beats UA CSS at equal
   specificity) — without this, a hidden .hud-item (e.g. ROOT ACCESS
   between power-ups) stays visibly flexed. */
.hud-item[hidden] {
  display: none;
}

.hud-item strong {
  color: var(--red-accent);
}

.hud-item--power strong {
  color: var(--goal-green);
}

/* Root Access active/almost-out glow — text-shadow rather than box-shadow
   since this badge is plain inline text with no background/padding box.
   text-shadow inherits by default, so this also reaches the nested
   <strong> countdown. Class presence is toggled every frame in main.js's
   HUD sync block (same ".is-active" convention as .game-critical-pulse). */
.hud-item--power.is-active {
  animation: power-glow 1.6s ease-in-out infinite;
}

.hud-item--power.is-low {
  /* Faster, more urgent cycle overrides the steady glow above once the
     buff is about to expire — same class-swap idea as .game-critical-pulse,
     just scoped to this one badge instead of a full-canvas overlay. */
  animation: power-glow-low 0.5s ease-in-out infinite;
}

@keyframes power-glow {
  0%, 100% { text-shadow: 0 0 4px rgba(255, 209, 102, 0.35); }
  50% { text-shadow: 0 0 10px rgba(255, 209, 102, 0.85); }
}

@keyframes power-glow-low {
  0%, 100% { text-shadow: 0 0 3px rgba(255, 209, 102, 0.25); }
  50% { text-shadow: 0 0 12px rgba(255, 209, 102, 0.95); }
}

.redundancy-bar {
  display: inline-flex;
  gap: 4px;
}

.redundancy-node {
  width: 20px;
  height: 10px;
  border-radius: 2px;
  background: var(--crimson-deep);
  border: 1px solid var(--red-accent-dim);
  transition: background-color 0.2s ease, box-shadow 0.2s ease;
}

.redundancy-node.is-up {
  background: var(--goal-green);
  border-color: var(--goal-green);
  box-shadow: 0 0 6px rgba(53, 208, 127, 0.6);
}

.redundancy-node.is-down {
  background: transparent;
  border-color: var(--red-accent-dim);
  box-shadow: none;
}

.game-pause-btn,
.game-mute-btn,
.game-achievements-btn {
  background: none;
  border: 1px solid var(--red-accent-dim);
  border-radius: 4px;
  color: var(--text-primary);
  font-size: 1rem;
  line-height: 1;
  padding: 4px 8px;
  cursor: pointer;
}

.game-pause-btn:hover,
.game-mute-btn:hover,
.game-achievements-btn:hover {
  border-color: var(--red-accent);
}

.game-pause-btn[aria-pressed="true"] {
  border-color: var(--red-accent);
  color: var(--red-accent);
}

.game-canvas-frame {
  position: relative;
  border: 1px solid var(--crimson-deep);
  border-radius: 6px;
  background: var(--bg-panel);
  overflow: hidden;
  aspect-ratio: 480 / 240;
}

#platformer-canvas {
  display: block;
  width: 100%;
  height: 100%;
  image-rendering: pixelated;
}

.game-hit-flash {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(ellipse at center, rgba(255, 45, 58, 0.55) 0%, rgba(255, 45, 58, 0) 70%);
  opacity: 0;
}

.game-hit-flash.is-active {
  animation: hit-flash 420ms ease-out;
}

@keyframes hit-flash {
  0% { opacity: 1; }
  100% { opacity: 0; }
}

.game-perfect-flash {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(ellipse at center, rgba(255, 209, 102, 0.65) 0%, rgba(255, 209, 102, 0) 70%);
  opacity: 0;
}

.game-perfect-flash.is-active {
  animation: hit-flash 500ms ease-out;
}

/* Looping low-REDUNDANCY warning vignette (Mega Man/Contra-style "one hit
   left" urgency cue) — unlike the one-shot hit/perfect flashes above, this
   is toggled by plain class presence (main.js's onUpdate), not a
   restart-the-animation reflow trick, since it just needs to stay on/off
   for as long as the player is down to their last REDUNDANCY node. */
.game-critical-pulse {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(ellipse at center, rgba(255, 45, 58, 0.5) 0%, rgba(255, 45, 58, 0) 65%);
  opacity: 0;
}

.game-critical-pulse.is-active {
  animation: critical-pulse 1.6s ease-in-out infinite;
}

@keyframes critical-pulse {
  0%, 100% { opacity: 0.15; }
  50% { opacity: 0.45; }
}

.game-combo {
  position: absolute;
  top: 30%;
  left: 50%;
  transform: translate(-50%, -50%) scale(0.8);
  pointer-events: none;
  font-family: var(--font-mono);
  font-weight: bold;
  font-size: 1.1rem;
  color: var(--goal-green);
  text-shadow: 0 0 8px rgba(53, 208, 127, 0.7);
  opacity: 0;
  white-space: nowrap;
}

.game-combo.is-active {
  animation: combo-pop 700ms ease-out;
}

@keyframes combo-pop {
  0% { opacity: 0; transform: translate(-50%, -50%) scale(0.6); }
  20% { opacity: 1; transform: translate(-50%, -50%) scale(1.1); }
  35% { transform: translate(-50%, -50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -60%) scale(1); }
}

.game-power-toast {
  position: absolute;
  top: 38%;
  left: 50%;
  transform: translate(-50%, -50%) scale(0.8);
  pointer-events: none;
  font-family: var(--font-mono);
  font-weight: bold;
  font-size: 1.15rem;
  color: var(--gold-accent);
  text-shadow: 0 0 8px rgba(255, 209, 102, 0.7);
  opacity: 0;
  white-space: nowrap;
}

.game-power-toast.is-active {
  animation: combo-pop 900ms ease-out;
}

.game-achievement-toast {
  position: absolute;
  top: 10px;
  left: 50%;
  transform: translate(-50%, -140%);
  pointer-events: none;
  background: rgba(18, 16, 19, 0.9);
  border: 1px solid var(--gold-accent);
  border-radius: 4px;
  padding: 6px 12px;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--gold-accent);
  opacity: 0;
  white-space: nowrap;
  max-width: 90%;
  overflow: hidden;
  text-overflow: ellipsis;
}

.game-achievement-toast.is-active {
  animation: achievement-toast 3200ms ease-out;
}

@keyframes achievement-toast {
  0% { opacity: 0; transform: translate(-50%, -140%); }
  8% { opacity: 1; transform: translate(-50%, 0); }
  85% { opacity: 1; transform: translate(-50%, 0); }
  100% { opacity: 0; transform: translate(-50%, -140%); }
}

.game-pause-overlay {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  background: rgba(18, 16, 19, 0.85);
  text-align: center;
}

.game-pause-overlay[hidden] {
  display: none;
}

.game-pause-overlay__msg {
  margin: 0;
  color: var(--text-primary);
  font-size: 1.1rem;
  font-weight: bold;
}

.game-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Plain "center" centers overflow symmetrically when the overlay's
     content (message + stats + bonuses + highscores) is taller than the
     canvas frame — common on short mobile viewports — which pushes the
     Start/Try Again button (the first child) above the box's own visible
     top edge with no way to scroll to it, since scrollTop can't go
     negative. "safe center" falls back to start-alignment instead of
     letting that happen; the plain "center" above it is a fallback for
     browsers that don't understand "safe" (an unsupported value in the
     second declaration is ignored, leaving the first in effect). */
  justify-content: center;
  justify-content: safe center;
  gap: 12px;
  background: rgba(18, 16, 19, 0.85);
  text-align: center;
  padding: 16px;
  overflow-y: auto;
}

.game-overlay[hidden] {
  display: none;
}

.game-overlay__msg {
  margin: 0;
  color: var(--text-muted);
  max-width: 32ch;
}

.game-overlay__stats {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--text-muted);
}

.game-overlay__stats[hidden] {
  display: none;
}

.game-overlay--perfect {
  background: rgba(38, 28, 10, 0.92);
  animation: perfect-glow 1.8s ease-in-out infinite;
}

.game-overlay--perfect .game-overlay__msg {
  color: var(--gold-accent);
  font-weight: bold;
}

@keyframes perfect-glow {
  0%, 100% { box-shadow: inset 0 0 40px rgba(255, 209, 102, 0.25); }
  50% { box-shadow: inset 0 0 70px rgba(255, 209, 102, 0.55); }
}

.game-bonuses {
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 0.85rem;
  color: var(--text-muted);
  text-align: center;
}

.game-bonuses[hidden] {
  display: none;
}

.game-bonuses li::before {
  content: "+ ";
  color: var(--goal-green);
}

.game-bonuses li.is-penalty {
  color: var(--focus-ring);
}

.game-bonuses li.is-penalty::before {
  content: "\2212 ";
  color: var(--red-accent);
}

.game-highscores {
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: highscore;
  font-size: 0.85rem;
  color: var(--text-muted);
}

.game-highscores[hidden] {
  display: none;
}

.game-highscores li {
  counter-increment: highscore;
}

.game-highscores li::before {
  content: counter(highscore) ". ";
  color: var(--red-accent-dim);
}

/* Endless-mode high scores also show the stage reached (see
   javascripts/skyfire-squadron/main.js's renderHighScores()) — de-emphasized
   relative to the score itself, same spirit as .game-bonuses's muted entries. */
.skyfire-highscore-stage {
  opacity: 0.7;
  font-size: 0.9em;
}

.game-achievements-panel {
  position: absolute;
  inset: 0;
  z-index: 10;
  margin: 0;
  padding: 16px;
  list-style: none;
  background: rgba(18, 16, 19, 0.96);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-size: 0.8rem;
}

.game-achievements-panel[hidden] {
  display: none;
}

.game-achievements-panel li {
  border: 1px solid var(--crimson-deep);
  border-radius: 4px;
  padding: 6px 10px;
  text-align: left;
}

.game-achievements-panel li.is-unlocked {
  border-color: var(--gold-accent);
}

.game-achievements-panel li.is-locked {
  opacity: 0.5;
}

.game-achievements-panel li strong {
  display: block;
  color: var(--text-primary);
}

.game-achievements-panel li.is-unlocked strong {
  color: var(--gold-accent);
}

.game-achievements-panel li span {
  color: var(--text-muted);
}

.game-touch-controls {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  margin-top: 12px;
}

.game-touch-controls {
  display: none;
}

@media (pointer: coarse) {
  .game-touch-controls {
    display: flex;
  }
}

.touch-row {
  display: flex;
  gap: 6px;
}

.touch-btn {
  width: 64px;
  height: 56px;
  border-radius: 6px;
  border: 1px solid var(--crimson-deep);
  background: var(--bg-panel-raised);
  color: var(--red-accent);
  font-size: 1.2rem;
  cursor: pointer;
}

.touch-btn:active {
  background: var(--crimson-deep);
}

.touch-btn--jump {
  width: 100%;
  max-width: 220px;
  color: var(--goal-green);
  border-color: var(--goal-green);
  font-family: var(--font-mono);
  font-weight: bold;
  letter-spacing: 0.05em;
}

.touch-btn--shoot {
  width: auto;
  padding: 0 14px;
  color: #ffb14e;
  border-color: #ffb14e;
  font-family: var(--font-mono);
  font-weight: bold;
  font-size: 0.85rem;
  letter-spacing: 0.05em;
}

.game-hint {
  text-align: center;
  font-size: 0.8rem;
  color: var(--text-muted);
  margin-top: 10px;
}

/* Skyfire Squadron
   ========================================================================== */

/* Same spacing as #hero's margin-bottom below — without this, the Games
   section (inserted directly above Projects in _layouts/home.html) sat
   flush against it, since neither #games nor #portfolio carries its own
   margin normally (portfolio-links.html was previously always the last
   section on the page, with nothing below it needing separation). */
#games {
  margin-bottom: 48px;
}

/* Portrait viewport (see VIEW_W/VIEW_H in javascripts/skyfire-squadron/main.js)
   — a dedicated modifier rather than editing .game-canvas-frame directly, since
   the platformer's landscape aspect-ratio is still read by that base rule. */
.game-canvas-frame--portrait {
  aspect-ratio: 360 / 560;
}

#skyfire-canvas {
  display: block;
  width: 100%;
  height: 100%;
  image-rendering: pixelated;
}

/* Plus-shaped 4-directional pad (free 2D movement, unlike the platformer's
   left/right + jump) laid out on a 3x3 grid with the center cell empty. */
.touch-dpad {
  display: grid;
  grid-template-columns: 52px 52px 52px;
  grid-template-rows: 52px 52px;
  gap: 4px;
  justify-content: center;
}

/* .touch-btn's base size (64x56, tuned for the platformer's wide jump/shoot
   buttons) is larger than this grid's own tracks — grid items don't shrink
   to fit their track just because the track is smaller, so without this
   override every dpad button overflowed its cell and visually overlapped
   its neighbors on narrow/mobile viewports. Pinning width/height to exactly
   the track size (and dropping the inherited padding, which isn't needed
   for a single centered arrow glyph) fixes that. */
.touch-dpad .touch-btn {
  width: 52px;
  height: 52px;
  padding: 0;
}

.touch-dpad__up {
  grid-column: 2;
  grid-row: 1;
}

.touch-dpad__left {
  grid-column: 1;
  grid-row: 2;
}

.touch-dpad__right {
  grid-column: 3;
  grid-row: 2;
}

.touch-dpad__down {
  grid-column: 2;
  grid-row: 2;
}

.touch-btn--fire {
  width: auto;
  padding: 0 14px;
  color: #ffb14e;
  border-color: #ffb14e;
  font-family: var(--font-mono);
  font-weight: bold;
  font-size: 0.85rem;
  letter-spacing: 0.05em;
}

.touch-btn--bomb {
  width: auto;
  padding: 0 14px;
  color: var(--gold-accent);
  border-color: var(--gold-accent);
  font-family: var(--font-mono);
  font-weight: bold;
  font-size: 0.85rem;
  letter-spacing: 0.05em;
}

/* Story
   ========================================================================== */

.story-trigger {
  text-align: center;
  margin: 24px 0;
}

.story-dialog {
  display: none;
}

.story-dialog:target {
  display: flex;
  position: fixed;
  inset: 0;
  z-index: 50;
  align-items: center;
  justify-content: center;
  padding: 16px;
}

.story-dialog__panel {
  position: relative;
  width: min(90vw, 720px);
  max-height: 85vh;
  overflow-y: auto;
  border: 1px solid var(--red-accent);
  border-radius: 8px;
  background: var(--bg-panel);
  color: var(--text-primary);
  padding: 24px;
  text-align: left;
}

/* Deliberately in normal flow, not position:absolute — the panel scrolls, and
   an absolutely-positioned close would scroll out of reach on a long recipe. */
.story-dialog__close {
  display: block;
  text-align: right;
  margin: -8px -8px 0 0;
  color: var(--text-muted);
  font-size: 1.4rem;
  line-height: 1;
  text-decoration: none;
}

.story-dialog__close:hover {
  color: var(--red-accent);
}

.story-intro {
  max-width: 60ch;
  color: var(--text-muted);
}

/* .enemy-grid reuses .portfolio-grid's layout/breakpoints as-is; these
   cards reuse .portfolio-card's chrome too, but as plain divs (not links
   like the portfolio cards), so the hover lift/border-brighten transition
   is dropped — nothing to link to. */
.enemy-card {
  transition: none;
}

.enemy-card:hover {
  border-color: var(--crimson-deep);
  transform: none;
}

.enemy-card h4 {
  margin-bottom: 0.3em;
}

.enemy-card p {
  color: var(--text-muted);
  margin-bottom: 0;
}

.enemy-card__sprite {
  display: block;
  height: 64px;
  width: auto;
  margin-bottom: 12px;
  image-rendering: pixelated;
}

.field-kit {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.field-kit li {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* A fixed width+height box with object-fit: contain, not height-only
   scaling — the three collectible sprites have very different native
   aspect ratios (powerup-root-access.png is a tiny, elongated 13x7 next
   to collectible-cash.png's 28x20 and collectible-redundancy.png's
   32x32), so scaling by height alone left the key looking like a thin
   sliver next to the other two. This keeps all three in the same visual
   footprint instead. */
.field-kit__sprite {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  object-fit: contain;
  image-rendering: pixelated;
}

/* Reduced motion
   ========================================================================== */
/* Screen shake itself is gated in JS (main.js's REDUCE_MOTION check around
   its shake() calls). This covers the CSS-driven effects: the full-canvas
   hit/perfect flashes get a capped opacity instead of a bright full flash
   (a photosensitivity concern, not just vestibular motion), the toast
   popups keep their original hold/fade timing so the information they
   convey stays legible but lose their scale/translate "pop", and the two
   infinite looping glows (Perfect Run overlay, critical-REDUNDANCY pulse)
   become static instead of pulsing. */
@media (prefers-reduced-motion: reduce) {
  .game-hit-flash.is-active {
    animation: hit-flash-reduced 420ms ease-out;
  }

  .game-perfect-flash.is-active {
    animation: hit-flash-reduced 500ms ease-out;
  }

  @keyframes hit-flash-reduced {
    0% { opacity: 0.35; }
    100% { opacity: 0; }
  }

  .game-combo.is-active {
    animation: combo-pop-reduced 700ms ease-out;
  }

  .game-power-toast.is-active {
    animation: combo-pop-reduced 900ms ease-out;
  }

  @keyframes combo-pop-reduced {
    0% { opacity: 0; transform: translate(-50%, -50%); }
    20% { opacity: 1; transform: translate(-50%, -50%); }
    80% { opacity: 1; transform: translate(-50%, -50%); }
    100% { opacity: 0; transform: translate(-50%, -50%); }
  }

  .game-achievement-toast.is-active {
    animation: achievement-toast-reduced 3200ms ease-out;
  }

  @keyframes achievement-toast-reduced {
    0% { opacity: 0; transform: translate(-50%, 0); }
    8% { opacity: 1; transform: translate(-50%, 0); }
    85% { opacity: 1; transform: translate(-50%, 0); }
    100% { opacity: 0; transform: translate(-50%, 0); }
  }

  .game-overlay--perfect {
    animation: none;
    box-shadow: inset 0 0 40px rgba(255, 209, 102, 0.25);
  }

  .game-critical-pulse.is-active {
    animation: none;
    opacity: 0.25;
  }

  .hud-item--power.is-active {
    animation: none;
    text-shadow: 0 0 6px rgba(255, 209, 102, 0.6);
  }

  .hud-item--power.is-low {
    animation: none;
    text-shadow: 0 0 8px rgba(255, 209, 102, 0.85);
  }
}

/* Responsive breakpoints
   ========================================================================== */

@media (min-width: 900px) {
  .game-wrap {
    max-width: 720px;
  }
}

@media (min-width: 1200px) {
  .game-wrap {
    max-width: 860px;
  }
}
