<script setup lang="ts">
import {
  Bomb,
  ChevronLeft,
  Flag,
  RotateCcw,
  Volume2,
  VolumeX,
} from 'lucide-vue-next'
import {
  computed,
  onBeforeUnmount,
  onMounted,
  ref,
  type CSSProperties,
} from 'vue'

import { playMinesweeperSound } from '@/features/games/minesweeper/audio'
import { MINESWEEPER_DIFFICULTIES } from '@/features/games/minesweeper/engine'
import { useMinesweeperStore } from '@/features/games/minesweeper/store'
import type {
  MinesweeperCell,
  MinesweeperDifficulty,
} from '@/features/games/minesweeper/types'
import { usePhoneStore } from '@/stores/phone'

const phone = usePhoneStore()
const minesweeper = useMinesweeperStore()
const difficulties: MinesweeperDifficulty[] = ['quick', 'classic', 'expert']
const game = computed(() => minesweeper.game)
const flagsPlaced = computed(
  () => minesweeper.game?.cells.filter((cell) => cell.isFlagged).length ?? 0,
)
const minesRemaining = computed(() =>
  Math.max(0, (minesweeper.game?.mineCount ?? 0) - flagsPlaced.value),
)
const roundEffect = ref<'explosion' | 'victory' | null>(null)
const resultVisible = ref(true)
const markerEffectCellId = ref<number | null>(null)
const markerEffectKey = ref(0)
const blastStyle = computed<CSSProperties>(() => {
  const currentGame = minesweeper.game
  const explodedCell = currentGame?.cells.find(
    (cell) => cell.id === currentGame.explodedCellId,
  )
  if (!currentGame || !explodedCell) return {}

  return {
    '--blast-left': `${((explodedCell.column + 0.5) / currentGame.width) * 100}%`,
    '--blast-top': `${((explodedCell.row + 0.5) / currentGame.height) * 100}%`,
  } as CSSProperties
})
const markerStyle = computed<CSSProperties>(() => {
  const currentGame = minesweeper.game
  const markedCell = currentGame?.cells.find(
    (cell) => cell.id === markerEffectCellId.value,
  )
  if (!currentGame || !markedCell) return {}

  return {
    '--marker-left': `${((markedCell.column + 0.5) / currentGame.width) * 100}%`,
    '--marker-top': `${((markedCell.row + 0.5) / currentGame.height) * 100}%`,
  } as CSSProperties
})
let clockTimer: ReturnType<typeof setInterval> | undefined
let longPressTimer: ReturnType<typeof setTimeout> | undefined
let suppressNextClick = false
let suppressTimer: ReturnType<typeof setTimeout> | undefined
let effectTimer: ReturnType<typeof setTimeout> | undefined
let markerEffectTimer: ReturnType<typeof setTimeout> | undefined

function formatTime(milliseconds: number): string {
  const seconds = Math.floor(milliseconds / 1000)
  return `${Math.floor(seconds / 60)}:${(seconds % 60).toString().padStart(2, '0')}`
}

function bestLabel(difficulty: MinesweeperDifficulty): string {
  const best = minesweeper.best[difficulty]
  return best ? formatTime(best.timeMs) : phone.t('Apps.minesweeper.noBest')
}

function cellLabel(cell: MinesweeperCell): string {
  if (cell.isFlagged) return phone.t('Apps.minesweeper.flaggedCell')
  if (!cell.isRevealed) return phone.t('Apps.minesweeper.hiddenCell')
  if (cell.isMine) return phone.t('Apps.minesweeper.mineCell')
  return cell.adjacentMines > 0
    ? phone.t('Apps.minesweeper.numberCell', {
        count: String(cell.adjacentMines),
      })
    : phone.t('Apps.minesweeper.emptyCell')
}

function reveal(cellId: number): void {
  if (suppressNextClick) {
    suppressNextClick = false
    return
  }

  const result = minesweeper.reveal(cellId)
  if (!result?.changed) return
  if (result.state.status === 'lost') {
    playMinesweeperSound('mine', minesweeper.soundEnabled)
    startRoundEffect('explosion', 1050)
  } else if (result.state.status === 'won') {
    playMinesweeperSound('win', minesweeper.soundEnabled)
    startRoundEffect('victory', 900)
  } else {
    playMinesweeperSound(
      result.revealedCount >= 5 ? 'clear' : 'reveal',
      minesweeper.soundEnabled,
    )
  }
}

function startRoundEffect(
  effect: 'explosion' | 'victory',
  duration: number,
): void {
  if (effectTimer) clearTimeout(effectTimer)
  roundEffect.value = effect
  resultVisible.value = false
  effectTimer = setTimeout(() => {
    roundEffect.value = null
    resultVisible.value = true
    effectTimer = undefined
  }, duration)
}

function toggleFlag(cellId: number): void {
  const result = minesweeper.toggleFlag(cellId)
  if (!result?.changed) return

  const markedCell = result.state.cells.find((cell) => cell.id === cellId)
  if (markedCell?.isFlagged) {
    if (markerEffectTimer) clearTimeout(markerEffectTimer)
    markerEffectCellId.value = cellId
    markerEffectKey.value += 1
    playMinesweeperSound('place', minesweeper.soundEnabled)
    markerEffectTimer = setTimeout(() => {
      markerEffectCellId.value = null
      markerEffectTimer = undefined
    }, 720)
  } else {
    playMinesweeperSound('flag', minesweeper.soundEnabled)
  }
}

function beginLongPress(cellId: number): void {
  if (longPressTimer) clearTimeout(longPressTimer)
  longPressTimer = setTimeout(() => {
    suppressNextClick = true
    toggleFlag(cellId)
    if (suppressTimer) clearTimeout(suppressTimer)
    suppressTimer = setTimeout(() => {
      suppressNextClick = false
    }, 650)
  }, 470)
}

function cancelLongPress(): void {
  if (longPressTimer) clearTimeout(longPressTimer)
  longPressTimer = undefined
}

function toggleSound(): void {
  const enabled = !minesweeper.soundEnabled
  minesweeper.setSoundEnabled(enabled)
  if (enabled) playMinesweeperSound('flag', true)
}

function restart(): void {
  if (!minesweeper.game) return

  if (effectTimer) clearTimeout(effectTimer)
  if (markerEffectTimer) clearTimeout(markerEffectTimer)
  effectTimer = undefined
  markerEffectTimer = undefined
  roundEffect.value = null
  markerEffectCellId.value = null
  resultVisible.value = true
  minesweeper.start(minesweeper.game.difficulty)
}

minesweeper.hydrate()
onMounted(() => {
  if (!minesweeper.menuOpen) minesweeper.resumeGame()
  clockTimer = setInterval(() => minesweeper.updateElapsed(), 100)
})
onBeforeUnmount(() => {
  if (clockTimer) clearInterval(clockTimer)
  if (longPressTimer) clearTimeout(longPressTimer)
  if (suppressTimer) clearTimeout(suppressTimer)
  if (effectTimer) clearTimeout(effectTimer)
  if (markerEffectTimer) clearTimeout(markerEffectTimer)
  minesweeper.pause()
  minesweeper.persist()
})
</script>

<template>
  <main
    class="minesweeper-app"
    :class="{ 'minesweeper-app--playing': !minesweeper.menuOpen && game }"
    :aria-label="phone.t('Apps.minesweeper.name')"
  >
    <header v-if="minesweeper.menuOpen" class="minesweeper-header">
      <div>
        <span>{{ phone.t('Apps.minesweeper.eyebrow') }}</span>
        <h1>{{ phone.t('Apps.minesweeper.name') }}</h1>
      </div>
      <button
        type="button"
        :aria-label="
          phone.t(
            minesweeper.soundEnabled
              ? 'Apps.minesweeper.mute'
              : 'Apps.minesweeper.unmute',
          )
        "
        @click="toggleSound"
      >
        <Volume2 v-if="minesweeper.soundEnabled" :size="18" aria-hidden="true" />
        <VolumeX v-else :size="18" aria-hidden="true" />
      </button>
    </header>

    <section v-if="minesweeper.menuOpen" class="minesweeper-menu">
      <div class="minesweeper-hero" aria-hidden="true">
        <span></span><span></span><span class="minesweeper-hero__one">1</span>
        <span></span><span class="minesweeper-hero__mine"><Bomb :size="27" /></span><span></span>
        <span class="minesweeper-hero__two">2</span><span></span><span class="minesweeper-hero__flag"><Flag :size="24" fill="currentColor" /></span>
      </div>

      <div class="minesweeper-intro">
        <h2>{{ phone.t('Apps.minesweeper.chooseTitle') }}</h2>
        <p>{{ phone.t('Apps.minesweeper.chooseBody') }}</p>
      </div>

      <button
        v-if="game && game.status !== 'lost' && game.status !== 'won'"
        type="button"
        class="minesweeper-resume"
        @click="minesweeper.resumeGame"
      >
        {{ phone.t('Apps.minesweeper.resume') }}
        <small>{{ phone.t(`Apps.minesweeper.difficulties.${game.difficulty}`) }} · {{ formatTime(minesweeper.elapsedMs) }}</small>
      </button>

      <div class="minesweeper-difficulties">
        <button
          v-for="difficulty in difficulties"
          :key="difficulty"
          type="button"
          @click="minesweeper.start(difficulty)"
        >
          <span class="minesweeper-difficulty__size">
            {{ MINESWEEPER_DIFFICULTIES[difficulty].width }}×{{ MINESWEEPER_DIFFICULTIES[difficulty].height }}
          </span>
          <strong>{{ phone.t(`Apps.minesweeper.difficulties.${difficulty}`) }}</strong>
          <small>
            {{ MINESWEEPER_DIFFICULTIES[difficulty].mines }} {{ phone.t('Apps.minesweeper.mines') }} · {{ bestLabel(difficulty) }}
          </small>
        </button>
      </div>

      <p class="minesweeper-long-press">{{ phone.t('Apps.minesweeper.longPressHint') }}</p>
    </section>

    <section
      v-else-if="game"
      class="minesweeper-game"
      :class="{
        'minesweeper-game--exploding': roundEffect === 'explosion',
        'minesweeper-game--victory': roundEffect === 'victory',
      }"
    >
      <div class="minesweeper-toolbar">
        <button
          type="button"
          class="minesweeper-toolbar__icon"
          :aria-label="phone.t('Apps.minesweeper.backToMenu')"
          @pointerup.stop="minesweeper.showMenu()"
          @click.stop="minesweeper.showMenu()"
        >
          <ChevronLeft :size="19" :stroke-width="2.7" aria-hidden="true" />
        </button>
        <div>
          <span>{{ phone.t('Apps.minesweeper.mines') }}</span>
          <strong>{{ minesRemaining }}</strong>
        </div>
        <div>
          <span>{{ phone.t('Apps.minesweeper.time') }}</span>
          <strong>{{ formatTime(minesweeper.elapsedMs) }}</strong>
        </div>
        <button
          type="button"
          class="minesweeper-toolbar__icon"
          :aria-label="phone.t('Apps.minesweeper.restart')"
          @click="restart"
        >
          <RotateCcw :size="17" :stroke-width="2.5" aria-hidden="true" />
        </button>
      </div>

      <div
        class="minesweeper-board"
        :class="`minesweeper-board--${game.difficulty}`"
        :style="{ '--minesweeper-columns': game.width }"
        :aria-label="phone.t('Apps.minesweeper.board')"
      >
        <button
          v-for="cell in game.cells"
          :key="cell.id"
          type="button"
          class="minesweeper-cell"
          :class="{
            'minesweeper-cell--revealed': cell.isRevealed,
            'minesweeper-cell--flagged': cell.isFlagged,
            'minesweeper-cell--mine': cell.isMine && cell.isRevealed,
            'minesweeper-cell--exploded': game.explodedCellId === cell.id,
            'minesweeper-cell--marking': markerEffectCellId === cell.id,
            [`minesweeper-cell--number-${cell.adjacentMines}`]:
              cell.isRevealed && !cell.isMine && cell.adjacentMines > 0,
          }"
          :aria-label="cellLabel(cell)"
          @click="reveal(cell.id)"
          @contextmenu.prevent="toggleFlag(cell.id)"
          @pointerdown="beginLongPress(cell.id)"
          @pointerup="cancelLongPress"
          @pointerleave="cancelLongPress"
          @pointercancel="cancelLongPress"
        >
          <Flag v-if="cell.isFlagged" :size="15" fill="currentColor" aria-hidden="true" />
          <Bomb v-else-if="cell.isMine && cell.isRevealed" :size="16" aria-hidden="true" />
          <strong v-else-if="cell.isRevealed && cell.adjacentMines > 0">{{ cell.adjacentMines }}</strong>
        </button>

        <div
          v-if="markerEffectCellId !== null"
          :key="markerEffectKey"
          class="minesweeper-marker-drop"
          :style="markerStyle"
          aria-hidden="true"
        >
          <span class="minesweeper-marker-drop__trail"></span>
          <span class="minesweeper-marker-drop__bomb">
            <Bomb :size="24" fill="currentColor" />
          </span>
          <span class="minesweeper-marker-drop__impact"></span>
        </div>

        <div
          v-if="roundEffect === 'explosion'"
          class="minesweeper-blast"
          :style="blastStyle"
          aria-hidden="true"
        >
          <span class="minesweeper-blast__flash"></span>
          <span class="minesweeper-blast__ring"></span>
          <span class="minesweeper-blast__core"><Bomb :size="25" fill="currentColor" /></span>
          <i v-for="particle in 12" :key="particle"></i>
        </div>

        <div
          v-if="roundEffect === 'victory'"
          class="minesweeper-celebration"
          aria-hidden="true"
        >
          <i v-for="particle in 18" :key="particle"></i>
        </div>

        <div
          v-if="
            resultVisible && (game.status === 'won' || game.status === 'lost')
          "
          class="minesweeper-overlay"
        >
          <span class="minesweeper-overlay__icon">
            <Flag v-if="game.status === 'won'" :size="30" fill="currentColor" />
            <Bomb v-else :size="30" />
          </span>
          <h2>{{ phone.t(game.status === 'won' ? 'Apps.minesweeper.wonTitle' : 'Apps.minesweeper.lostTitle') }}</h2>
          <p>
            {{
              phone.t(
                game.status === 'won'
                  ? 'Apps.minesweeper.wonBody'
                  : 'Apps.minesweeper.lostBody',
                { time: formatTime(minesweeper.elapsedMs) },
              )
            }}
          </p>
          <button type="button" class="minesweeper-primary" @click="restart">
            {{ phone.t('Apps.minesweeper.playAgain') }}
          </button>
          <button
            type="button"
            class="minesweeper-secondary"
            @pointerup.stop="minesweeper.showMenu()"
            @click.stop="minesweeper.showMenu()"
          >
            {{ phone.t('Apps.minesweeper.mainMenu') }}
          </button>
        </div>
      </div>

      <p class="minesweeper-game__hint">{{ phone.t('Apps.minesweeper.gameHint') }}</p>
    </section>
  </main>
</template>

<style scoped>
.minesweeper-app {
  position: absolute;
  inset: 0;
  overflow: hidden;
  padding: 52px 16px 27px;
  color: #153b42;
  background:
    radial-gradient(circle at 88% 7%, rgb(108 231 218 / 32%), transparent 30%),
    linear-gradient(155deg, #effcf7 0%, #cfeee5 52%, #abdcd7 100%);
  font-family: var(--sky-font-family);
  touch-action: manipulation;
  user-select: none;
}

.minesweeper-app--playing {
  padding: 0;
}

.minesweeper-header {
  height: 55px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.minesweeper-header span { display: block; color: #59878a; font-size: 9px; font-weight: 800; letter-spacing: 1.1px; text-transform: uppercase; }
.minesweeper-header h1 { margin: 0; font-size: 24px; line-height: 1; letter-spacing: -0.7px; }

.minesweeper-header button,
.minesweeper-toolbar__icon {
  width: 36px;
  height: 36px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid rgb(23 83 87 / 9%);
  border-radius: 12px;
  color: #246871;
  background: rgb(255 255 255 / 48%);
  box-shadow: 0 4px 10px rgb(23 73 75 / 8%);
}

.minesweeper-menu {
  height: calc(100% - 55px);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  text-align: center;
}

.minesweeper-hero {
  width: 136px;
  height: 136px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 5px;
  padding: 7px;
  border-radius: 25px;
  background: #167e82;
  box-shadow: 0 15px 28px rgb(18 87 90 / 20%);
  transform: rotate(-2deg);
}

.minesweeper-hero > span {
  display: grid;
  place-items: center;
  border-radius: 10px;
  color: #eafff8;
  background: linear-gradient(145deg, #46c2bc, #279c9d);
  box-shadow: inset 0 2px 0 rgb(255 255 255 / 18%), 0 3px 5px rgb(10 68 71 / 18%);
  font-size: 20px;
  font-weight: 900;
}

.minesweeper-hero .minesweeper-hero__one,
.minesweeper-hero .minesweeper-hero__two { background: #fff8e9; }
.minesweeper-hero__one { color: #367bd3 !important; }
.minesweeper-hero__two { color: #198768 !important; }
.minesweeper-hero__mine { color: #203b54 !important; background: #46b7b2 !important; }
.minesweeper-hero__flag { color: #f15f57 !important; }

.minesweeper-intro h2 { margin: 0; font-size: 21px; }
.minesweeper-intro p { max-width: 270px; margin: 5px 0 0; color: #5f8584; font-size: 11px; line-height: 1.4; }

.minesweeper-resume {
  width: 100%;
  min-height: 48px;
  display: grid;
  place-items: center;
  gap: 1px;
  border: 0;
  border-radius: 14px;
  color: #effff9;
  background: linear-gradient(135deg, #249b96, #147a81);
  box-shadow: 0 7px 15px rgb(20 111 113 / 18%);
  font-size: 12px;
  font-weight: 850;
}
.minesweeper-resume small { color: #bfece3; font-size: 8px; }

.minesweeper-difficulties { width: 100%; display: grid; gap: 6px; }
.minesweeper-difficulties button {
  min-height: 50px;
  display: grid;
  grid-template-columns: 49px 1fr;
  grid-template-rows: 1fr 1fr;
  align-items: center;
  column-gap: 10px;
  padding: 6px 10px;
  border: 1px solid rgb(23 91 93 / 8%);
  border-radius: 14px;
  color: #204b50;
  background: rgb(255 255 255 / 45%);
  text-align: left;
}
.minesweeper-difficulty__size { grid-row: 1 / 3; display: grid; place-items: center; height: 37px; border-radius: 10px; color: #f0fff9; background: #238f8e; font-size: 11px; font-weight: 850; }
.minesweeper-difficulties strong { align-self: end; font-size: 12px; }
.minesweeper-difficulties small { align-self: start; color: #658a89; font-size: 8px; }
.minesweeper-long-press { margin: 0; color: #628887; font-size: 9px; }

.minesweeper-game {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle at 12% 88%, rgb(26 129 130 / 14%), transparent 32%),
    radial-gradient(circle at 88% 16%, rgb(238 255 249 / 45%), transparent 29%);
}

.minesweeper-toolbar {
  position: absolute;
  z-index: 10;
  top: 66px;
  right: 18px;
  left: 18px;
  height: 42px;
  display: grid;
  grid-template-columns: 32px 1fr 1fr 32px;
  align-items: center;
  gap: 4px;
  padding: 4px;
  border: 1px solid rgb(23 83 87 / 10%);
  border-radius: 21px;
  background: rgb(226 249 242 / 65%);
  box-shadow: 0 8px 24px rgb(23 73 75 / 13%);
  backdrop-filter: blur(14px);
  box-sizing: border-box;
}
.minesweeper-toolbar div { height: 32px; display: grid; grid-template-rows: 10px 20px; align-content: center; justify-items: center; }
.minesweeper-toolbar span { color: #5a8484; font-size: 10px; font-weight: 800; line-height: 10px; text-transform: uppercase; }
.minesweeper-toolbar strong { display: block; font-size: 18px; line-height: 20px; }

.minesweeper-toolbar .minesweeper-toolbar__icon {
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: 50%;
  box-shadow: none;
}

.minesweeper-board {
  position: absolute;
  top: 50%;
  right: 14px;
  left: 14px;
  display: grid;
  grid-template-columns: repeat(var(--minesweeper-columns), minmax(0, 1fr));
  gap: 3px;
  padding: 7px;
  border: 0;
  border-radius: 18px;
  background: #187a7e;
  box-shadow: inset 0 2px 2px rgb(255 255 255 / 10%), 0 15px 27px rgb(20 79 81 / 18%);
  transform: translateY(-45%);
}

.minesweeper-cell {
  aspect-ratio: 1;
  min-width: 0;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 7px;
  color: #effff8;
  background: linear-gradient(145deg, #4bc4bd, #279d9e);
  box-shadow: inset 0 2px 0 rgb(255 255 255 / 15%), 0 2px 3px rgb(9 61 64 / 20%);
  font-size: 13px;
  transition: transform 100ms ease, background 130ms ease;
}

.minesweeper-board--expert .minesweeper-cell { border-radius: 6px; font-size: 11px; }
.minesweeper-cell--revealed { color: #477078; background: #eef5e9; box-shadow: inset 0 1px 3px rgb(39 83 80 / 12%); animation: minesweeper-reveal 170ms ease-out; }
.minesweeper-cell--flagged { color: #f35f58; background: linear-gradient(145deg, #5bcec5, #2ca5a4); }
.minesweeper-cell--marking > svg { animation: minesweeper-marker-flag 720ms ease-out; }
.minesweeper-cell--mine { color: #263c51; background: #dce5dd; }
.minesweeper-cell--exploded { color: #fff; background: radial-gradient(circle, #263238 0 31%, #743e37 34% 55%, #df584e 58%); box-shadow: inset 0 0 9px #050b0c, 0 0 12px #ff5a3c; animation: minesweeper-explode 720ms ease-out; }
.minesweeper-cell--number-1 { color: #3275ce; }
.minesweeper-cell--number-2 { color: #188569; }
.minesweeper-cell--number-3 { color: #dd574b; }
.minesweeper-cell--number-4 { color: #7354ad; }
.minesweeper-cell--number-5,
.minesweeper-cell--number-6,
.minesweeper-cell--number-7,
.minesweeper-cell--number-8 { color: #9b4e34; }

@keyframes minesweeper-reveal { from { opacity: 0.45; transform: scale(0.84); } to { opacity: 1; transform: scale(1); } }
@keyframes minesweeper-explode { 0% { filter: brightness(4); transform: scale(0.7); } 28% { filter: brightness(2); transform: scale(1.32); } 62% { transform: scale(0.9); } 100% { filter: brightness(0.8); transform: scale(1); } }

.minesweeper-game--exploding .minesweeper-board {
  animation: minesweeper-board-shake 680ms cubic-bezier(0.36, 0.07, 0.19, 0.97);
}

.minesweeper-game--exploding::after {
  position: absolute;
  z-index: 9;
  inset: 0;
  background: #ffb341;
  content: "";
  pointer-events: none;
  animation: minesweeper-screen-flash 520ms ease-out forwards;
}

.minesweeper-blast {
  position: absolute;
  z-index: 12;
  top: var(--blast-top);
  left: var(--blast-left);
  width: 0;
  height: 0;
  pointer-events: none;
}

.minesweeper-marker-drop {
  position: absolute;
  z-index: 11;
  top: var(--marker-top);
  left: var(--marker-left);
  width: 0;
  height: 0;
  pointer-events: none;
}

.minesweeper-marker-drop__bomb {
  position: absolute;
  z-index: 2;
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  margin: -19px;
  border: 2px solid #667985;
  border-radius: 50%;
  color: #172c36;
  background: radial-gradient(circle at 35% 28%, #687b83, #263c45 48%, #12272f 75%);
  box-shadow: inset -4px -5px 8px rgb(0 0 0 / 35%), 0 5px 9px rgb(11 42 45 / 42%);
  animation: minesweeper-bomb-drop 720ms cubic-bezier(0.2, 0.8, 0.35, 1) forwards;
}

.minesweeper-marker-drop__trail {
  position: absolute;
  z-index: 1;
  width: 7px;
  height: 95px;
  margin: -87px 0 0 27px;
  border-radius: 50%;
  background: linear-gradient(to bottom, transparent, #ffe38a 45%, #ff6c42 83%, transparent);
  filter: blur(2px);
  transform: rotate(35deg);
  transform-origin: bottom;
  animation: minesweeper-bomb-trail 570ms ease-out forwards;
}

.minesweeper-marker-drop__impact {
  position: absolute;
  width: 34px;
  height: 34px;
  margin: -17px;
  border: 4px solid #8df3df;
  border-radius: 50%;
  box-shadow: 0 0 12px #4bc4bd;
  opacity: 0;
  animation: minesweeper-marker-impact 720ms ease-out forwards;
}

.minesweeper-blast__flash,
.minesweeper-blast__ring,
.minesweeper-blast__core,
.minesweeper-blast i {
  position: absolute;
  top: 0;
  left: 0;
}

.minesweeper-blast__flash {
  width: 112px;
  height: 112px;
  margin: -56px;
  border-radius: 50%;
  background: radial-gradient(circle, #fff 0 7%, #ffdf68 12%, #ff713d 32%, transparent 68%);
  animation: minesweeper-blast-flash 560ms ease-out forwards;
}

.minesweeper-blast__ring {
  width: 32px;
  height: 32px;
  margin: -16px;
  border: 6px solid #ffd166;
  border-radius: 50%;
  box-shadow: 0 0 18px #ff713d;
  animation: minesweeper-blast-ring 720ms ease-out forwards;
}

.minesweeper-blast__core {
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  margin: -22px;
  border-radius: 50%;
  color: #202a32;
  background: #f6b846;
  filter: drop-shadow(0 0 8px #fff0a6);
  animation: minesweeper-blast-core 620ms ease-out forwards;
}

.minesweeper-blast i {
  --particle-x: 58px;
  --particle-y: 0;
  width: 7px;
  height: 7px;
  margin: -3px;
  border-radius: 2px;
  background: #ffcf55;
  box-shadow: 0 0 7px #ff743d;
  animation: minesweeper-debris 760ms ease-out forwards;
}

.minesweeper-blast i:nth-of-type(2) { --particle-x: 51px; --particle-y: 30px; }
.minesweeper-blast i:nth-of-type(3) { --particle-x: 29px; --particle-y: 54px; }
.minesweeper-blast i:nth-of-type(4) { --particle-x: 0; --particle-y: 66px; }
.minesweeper-blast i:nth-of-type(5) { --particle-x: -34px; --particle-y: 58px; }
.minesweeper-blast i:nth-of-type(6) { --particle-x: -61px; --particle-y: 28px; }
.minesweeper-blast i:nth-of-type(7) { --particle-x: -68px; --particle-y: -5px; }
.minesweeper-blast i:nth-of-type(8) { --particle-x: -49px; --particle-y: -43px; }
.minesweeper-blast i:nth-of-type(9) { --particle-x: -12px; --particle-y: -65px; }
.minesweeper-blast i:nth-of-type(10) { --particle-x: 28px; --particle-y: -60px; }
.minesweeper-blast i:nth-of-type(11) { --particle-x: 61px; --particle-y: -36px; }
.minesweeper-blast i:nth-of-type(12) { --particle-x: 72px; --particle-y: 10px; }

.minesweeper-celebration {
  position: absolute;
  z-index: 12;
  inset: 0;
  overflow: hidden;
  border-radius: 17px;
  pointer-events: none;
}

.minesweeper-celebration i {
  --confetti-x: 10%;
  --confetti-delay: 0ms;
  --confetti-color: #ffcf55;
  position: absolute;
  top: -14px;
  left: var(--confetti-x);
  width: 7px;
  height: 13px;
  border-radius: 2px;
  background: var(--confetti-color);
  animation: minesweeper-confetti 850ms var(--confetti-delay) ease-in forwards;
}

.minesweeper-celebration i:nth-child(3n + 1) { --confetti-color: #ff7065; transform: rotate(22deg); }
.minesweeper-celebration i:nth-child(3n + 2) { --confetti-color: #9cf0d9; transform: rotate(-31deg); }
.minesweeper-celebration i:nth-child(2n) { --confetti-delay: 90ms; }
.minesweeper-celebration i:nth-child(1) { --confetti-x: 4%; }
.minesweeper-celebration i:nth-child(2) { --confetti-x: 10%; }
.minesweeper-celebration i:nth-child(3) { --confetti-x: 17%; }
.minesweeper-celebration i:nth-child(4) { --confetti-x: 24%; }
.minesweeper-celebration i:nth-child(5) { --confetti-x: 31%; }
.minesweeper-celebration i:nth-child(6) { --confetti-x: 38%; }
.minesweeper-celebration i:nth-child(7) { --confetti-x: 45%; }
.minesweeper-celebration i:nth-child(8) { --confetti-x: 52%; }
.minesweeper-celebration i:nth-child(9) { --confetti-x: 59%; }
.minesweeper-celebration i:nth-child(10) { --confetti-x: 66%; }
.minesweeper-celebration i:nth-child(11) { --confetti-x: 73%; }
.minesweeper-celebration i:nth-child(12) { --confetti-x: 80%; }
.minesweeper-celebration i:nth-child(13) { --confetti-x: 87%; }
.minesweeper-celebration i:nth-child(14) { --confetti-x: 94%; }
.minesweeper-celebration i:nth-child(15) { --confetti-x: 14%; }
.minesweeper-celebration i:nth-child(16) { --confetti-x: 42%; }
.minesweeper-celebration i:nth-child(17) { --confetti-x: 70%; }
.minesweeper-celebration i:nth-child(18) { --confetti-x: 90%; }

.minesweeper-game--victory .minesweeper-cell--flagged {
  animation: minesweeper-flag-celebrate 520ms ease-out alternate 2;
}

@keyframes minesweeper-board-shake {
  0%, 100% { transform: translate(0, -45%); }
  12% { transform: translate(-7px, calc(-45% + 3px)) rotate(-1deg); }
  24% { transform: translate(6px, calc(-45% - 4px)) rotate(1deg); }
  38% { transform: translate(-5px, calc(-45% - 2px)); }
  52% { transform: translate(4px, calc(-45% + 3px)); }
  68% { transform: translate(-2px, calc(-45% - 2px)); }
  82% { transform: translate(2px, calc(-45% + 1px)); }
}

@keyframes minesweeper-bomb-drop {
  0% { opacity: 0; transform: translate(105px, -185px) scale(0.55) rotate(160deg); }
  12% { opacity: 1; }
  66% { transform: translate(0, 0) scale(1) rotate(355deg); }
  78% { transform: translate(0, -13px) scale(0.9) rotate(370deg); }
  90% { opacity: 1; transform: translate(0, 0) scale(0.78) rotate(380deg); }
  100% { opacity: 0; transform: translate(0, 0) scale(0.35) rotate(390deg); }
}

@keyframes minesweeper-bomb-trail {
  0% { opacity: 0; scale: 0.4; }
  18% { opacity: 0.9; }
  72%, 100% { opacity: 0; scale: 1; }
}

@keyframes minesweeper-marker-impact {
  0%, 62% { opacity: 0; transform: scale(0.2); }
  68% { opacity: 1; }
  100% { opacity: 0; transform: scale(2.2); }
}

@keyframes minesweeper-marker-flag {
  0%, 68% { opacity: 0; transform: scale(0.35) rotate(-18deg); }
  82% { opacity: 1; transform: scale(1.28) rotate(5deg); }
  100% { opacity: 1; transform: scale(1) rotate(0); }
}

@keyframes minesweeper-screen-flash {
  0% { opacity: 0.82; }
  18% { opacity: 0.22; }
  100% { opacity: 0; }
}

@keyframes minesweeper-blast-flash {
  0% { opacity: 1; transform: scale(0.18); }
  45% { opacity: 0.88; transform: scale(1.15); }
  100% { opacity: 0; transform: scale(1.7); }
}

@keyframes minesweeper-blast-ring {
  0% { opacity: 1; transform: scale(0.15); }
  100% { opacity: 0; transform: scale(4.2); }
}

@keyframes minesweeper-blast-core {
  0% { opacity: 1; transform: scale(0.35) rotate(-18deg); }
  48% { opacity: 1; transform: scale(1.2) rotate(12deg); }
  100% { opacity: 0; transform: scale(0.6) rotate(55deg); }
}

@keyframes minesweeper-debris {
  0% { opacity: 1; transform: translate(0) rotate(0); }
  75% { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--particle-x), var(--particle-y)) rotate(260deg); }
}

@keyframes minesweeper-confetti {
  0% { opacity: 0; translate: 0 0; rotate: 0deg; }
  10% { opacity: 1; }
  100% { opacity: 0; translate: 16px 380px; rotate: 520deg; }
}

@keyframes minesweeper-flag-celebrate {
  from { filter: brightness(1); transform: scale(1); }
  to { filter: brightness(1.3); transform: scale(1.12) rotate(3deg); }
}

.minesweeper-overlay {
  position: absolute;
  z-index: 5;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 22px;
  border-radius: 17px;
  color: #effff9;
  background: rgb(13 66 70 / 86%);
  backdrop-filter: blur(5px);
  text-align: center;
}
.minesweeper-overlay__icon { width: 58px; height: 58px; display: grid; place-items: center; border-radius: 50%; color: #ff756a; background: rgb(255 255 255 / 10%); }
.minesweeper-overlay h2 { margin: 0; font-size: 24px; }
.minesweeper-overlay p { max-width: 220px; margin: -2px 0 4px; color: #bee1dc; font-size: 10px; line-height: 1.35; }
.minesweeper-primary,
.minesweeper-secondary { position: relative; z-index: 1; min-width: 155px; min-height: 40px; border-radius: 13px; font-size: 11px; font-weight: 850; pointer-events: auto; }
.minesweeper-primary { border: 0; color: #104d51; background: #8ce3d2; }
.minesweeper-secondary { border: 1px solid rgb(255 255 255 / 13%); color: #e6faf5; background: rgb(255 255 255 / 7%); }
.minesweeper-game__hint {
  position: absolute;
  z-index: 6;
  right: 45px;
  bottom: 27px;
  left: 45px;
  margin: 0;
  padding: 7px 10px;
  border-radius: 999px;
  color: #668c8c;
  background: rgb(226 249 242 / 52%);
  backdrop-filter: blur(10px);
  font-size: 9px;
  text-align: center;
  pointer-events: none;
}

button:active { transform: scale(0.96); }

@media (prefers-reduced-motion: reduce) {
  .minesweeper-game--exploding .minesweeper-board,
  .minesweeper-game--exploding::after,
  .minesweeper-blast > *,
  .minesweeper-marker-drop > *,
  .minesweeper-cell--marking > svg,
  .minesweeper-celebration i,
  .minesweeper-game--victory .minesweeper-cell--flagged,
  .minesweeper-cell--revealed,
  .minesweeper-cell--exploded { animation: none; }
}
</style>
