JustPaste.it

tetet

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Pokémon Battle 3D Demo - Fixed</title>
    <style>
/* --- Global Styles & Material Inspired UI --- */
        @import u rl('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap'); /* Roboto is standard Material font */
        @import u rl('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); /* Keep Inter as fallback/option */

        :root {
            --bg-color: #f5f5f5; /* Material Light Background */
            --card-bg: #ffffff;
            --text-color: rgba(0, 0, 0, 0.87); /* Material Primary Text */
            --subtle-text: rgba(0, 0, 0, 0.6); /* Material Secondary Text */
            --very-subtle-text: rgba(0, 0, 0, 0.38); /* Material Hint/Disabled Text */
            --divider-color: rgba(0, 0, 0, 0.12); /* Material Divider */
            --primary-color: #6200EE; /* Material Purple (Example) */
            --primary-variant: #3700B3;
            --secondary-color: #03DAC6; /* Material Teal (Example) */
            --primary-blue: #2196F3; /* Material Blue */
            --hover-blue: #1976D2;
            --hp-high: #4CAF50; /* Material Green */
            --hp-medium: #FFC107; /* Material Amber */
            --hp-low: #F44336; /* Material Red */
            --info-box-bg: rgba(255, 255, 255, 0.85); /* Keep refined info box bg */
            --info-box-border: rgba(0, 0, 0, 0.06); /* Even subtler border */
            --health-bar-track: rgba(0, 0, 0, 0.1);
            -- 'Roboto', 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; /* Prioritize Roboto */

            /* Card Specific */
            --card-border-radius: 12px; /* Softer radius */
            --card-shadow: 0 2px 4px rgba(0,0,0,0.1), 0 1px 6px rgba(0,0,0,0.08); /* Base shadow */
            --card-shadow-hover: 0 6px 12px rgba(0,0,0,0.15), 0 4px 8px rgba(0,0,0,0.12); /* Hover shadow */

             /* Type Chip Colors (Material Style) - ADD MORE AS NEEDED */
            --type-color-electric: #FFC107; --type-text-electric: rgba(0,0,0,0.87);
            --type-color-fire: #FF5722; --type-text-fire: white;
            --type-color-water: #03A9F4; --type-text-water: white;
            --type-color-grass: #8BC34A; --type-text-grass: rgba(0,0,0,0.87);
            --type-color-ghost: #7E57C2; --type-text-ghost: white;
            --type-color-psychic: #EC407A; --type-text-psychic: white;
            --type-color-fighting: #A1887F; --type-text-fighting: white;
            --type-color-normal: #BDBDBD; --type-text-normal: rgba(0,0,0,0.87);
            --type-color-default: #9E9E9E; --type-text-default: white;
        }

        * { box-sizing: border-box; 0; padding: 0; }
        html, body { height: 100%; overflow: hidden; }

        body {
            var(--font-family); background-color: var(--bg-color); color: var(--text-color);
            display: flex; justify-content: center; align-items: center; padding: 20px; perspective: 1200px;
        }

        .game-container {
            width: 100%; max-width: 850px; /* Slightly wider */ height: 90vh; max-height: 700px; /* Slightly taller */
            background-color: var(--card-bg); border-radius: 18px;
            box-shadow: 0 4px 15px rgba(0,0,0,0.1); /* Softer container shadow */
            overflow: hidden; position: relative; display: flex; flex-direction: column;
        }

        /* --- Screens --- */
        .screen { position: absolute; top: 0; left: 0; width: 100%; height: 100%; padding: 0; transition: transform 0.6s ease-in-out, opacity 0.5s ease-in-out; backface-visibility: hidden; display: flex; flex-direction: column; z-index: 5; }
        .selection-screen { z-index: 10; opacity: 1; transform: rotateY(0deg); align-items: center; padding: 24px; background-color: var(--bg-color); /* Match body bg */ }
        .battle-screen { z-index: 5; opacity: 0; transform: rotateY(180deg); justify-content: space-between; }
        .game-container.battle-active .selection-screen { opacity: 0; transform: rotateY(-180deg); pointer-events: none; }
        .game-container.battle-active .battle-screen { opacity: 1; transform: rotateY(0deg); z-index: 10; }

        /* --- Selection Screen --- */
        .selection-screen h1 {
            font-size: 24px; font-weight: 500; /* Material Title */ 24px;
            color: var(--text-color); text-align: center; width: 100%;
        }

        .pokemon-cards {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); /* Adjusted size */
            gap: 24px; /* Material Grid Spacing */
            width: 100%;
            overflow-y: auto;
            padding: 0 8px 16px 8px; /* Padding around grid */
            max-height: calc(100% - 60px); /* Adjust calc based on h1 */
        }

        /* --- === POKEMON CARD - MATERIAL DESIGN === --- */
        .pokemon-card {
            background-color: var(--card-bg);
            border-radius: var(--card-border-radius);
            box-shadow: var(--card-shadow);
            transition: transform 0.2s ease-out, box-shadow 0.2s ease-out;
            cursor: pointer;
            overflow: hidden; /* Clip image and ripple */
            display: flex;
            flex-direction: column;
            position: relative; /* For ripple */
        }

        .pokemon-card:hover {
            transform: translateY(-4px);
            box-shadow: var(--card-shadow-hover);
        }

        .card-image-container {
            height: 140px; /* Fixed height for image area */
            background-color: #e0e0e0; /* Placeholder background */
            display: flex;
            align-items: center;
            justify-content: center;
            overflow: hidden;
            padding: 10px; /* Padding around the image */
            position: relative; /* For potential type-color backgrounds */
        }
        /* Add type-specific backgrounds to image container */
        .pokemon-card.type-fire .card-image-container { background-color: #FFCCBC; }
        .pokemon-card.type-water .card-image-container { background-color: #B3E5FC; }
        .pokemon-card.type-grass .card-image-container { background-color: #C8E6C9; }
        .pokemon-card.type-electric .card-image-container { background-color: #FFF9C4; }
        .pokemon-card.type-ghost .card-image-container { background-color: #D1C4E9; }
        /* Add more type backgrounds */


        .card-image {
            max-width: 100%;
            max-height: 100%;
            object-fit: contain;
            filter: drop-shadow(0 2px 3px rgba(0,0,0,0.2));
        }

        .card-content {
            padding: 16px;
            flex-grow: 1; /* Allows description to push footer down if needed */
            display: flex;
            flex-direction: column;
        }

        .card-name {
            font-size: 18px; /* Material Subtitle 1 */
            font-weight: 500;
            color: var(--text-color);
            8px;
            1.4;
        }

        .card-info {
            display: flex;
            align-items: center;
            justify-content: space-between; /* Pushes type and HP apart */
            12px;
        }

        .card-type-chip {
            display: inline-block;
            padding: 4px 10px;
            border-radius: 16px; /* Pill shape */
            font-size: 11px;
            font-weight: 500;
            text-transform: uppercase;
            letter-spacing: 0.5px;
             /* Default colors - overridden by specific type classes below */
             background-color: var(--type-color-default);
             color: var(--type-text-default);
        }
        /* Apply type-specific colors */
        .type-electric .card-type-chip { background-color: var(--type-color-electric); color: var(--type-text-electric); }
        .type-fire .card-type-chip { background-color: var(--type-color-fire); color: var(--type-text-fire); }
        .type-water .card-type-chip { background-color: var(--type-color-water); color: var(--type-text-water); }
        .type-grass .card-type-chip { background-color: var(--type-color-grass); color: var(--type-text-grass); }
        .type-ghost .card-type-chip { background-color: var(--type-color-ghost); color: var(--type-text-ghost); }
        .type-psychic .card-type-chip { background-color: var(--type-color-psychic); color: var(--type-text-psychic); }
        .type-fighting .card-type-chip { background-color: var(--type-color-fighting); color: var(--type-text-fighting); }
        .type-normal .card-type-chip { background-color: var(--type-color-normal); color: var(--type-text-normal); }
        /* Add more type rules here */


        .card-hp {
            font-size: 14px;
            font-weight: 500;
            color: var(--subtle-text);
        }

        .card-description {
            font-size: 13px; /* Material Body 2 */
            font-weight: 400;
            color: var(--subtle-text);
            1.5;
            auto; /* Pushes description down if content above is short */
            padding-top: 8px; /* Space above description if needed */
             /* Limit description lines */
             display: -webkit-box;
            -webkit-line-clamp: 2; /* Show max 2 lines */
            -webkit-box-orient: vertical;
            overflow: hidden;
            text-overflow: ellipsis;
        }

        /* Material Ripple Effect */
        .card-ripple {
            position: absolute;
            border-radius: 50%;
            background-color: rgba(0, 0, 0, 0.1); /* Ripple color */
            transform: scale(0);
            opacity: 1;
            pointer-events: none; /* Important */
        }
        .card-ripple.active {
            transform: scale(2);
            opacity: 0;
            transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.5s cubic-bezier(0.4, 0, 0.2, 1);
        }
        /* --- === END MATERIAL CARD === --- */


        /* --- Battle Screen --- */
        .battle-arena { flex-grow: 1; position: relative; overflow: hidden; display: flex; flex-direction: column; justify-content: space-between; background: linear-gradient(135deg, #e3f2fd 0%, #f5f5f5 100%); /* Adjusted gradient to match MD bg */ }
        #three-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 2; pointer-events: none; display: none; }
        .game-container.battle-active #three-canvas { display: block; }

        /* --- Info Boxes Positioning (Keep refined Apple style - it still looks good) --- */
        .opponent-side, .player-side { position: absolute; width: 240px; z-index: 4; }
        .opponent-side { top: 30px; left: 30px; }
        .player-side { bottom: 30px; right: 30px; }
        .pokemon-info-box { background-color: var(--info-box-bg); backdrop-filter: blur(16px) saturate(180%); -webkit-backdrop-filter: blur(16px) saturate(180%); border: 1px solid var(--info-box-border); border-radius: 16px; padding: 14px 20px; width: 100%; box-shadow: 0 5px 20px rgba(0, 0, 0, 0.07); transition: background-color 0.3s ease, box-shadow 0.3s ease; }
        .opponent-side .pokemon-info-box { text-align: left; }
        .player-side .pokemon-info-box { text-align: right; }
        .pokemon-name { font-size: 19px; font-weight: 600; color: var(--text-color); 4px; letter-spacing: 0.2px; }
        .hp-text { font-size: 12px; font-weight: 500; color: var(--very-subtle-text); 8px; 1.3; }
        .health-bar-container { width: 100%; height: 12px; background-color: var(--health-bar-track); border-radius: 7px; overflow: hidden; position: relative; }
        .health-bar { height: 100%; width: 100%; border-radius: 7px; background-color: var(--hp-high); transition: width 0.6s cubic-bezier(0.25, 0.1, 0.25, 1), background-color 0.5s ease; }
        .health-bar.medium { background-color: var(--hp-medium); }
        .health-bar.low { background-color: var(--hp-low); }

        /* --- Sprite Positioning --- */
        .pokemon-sprite { position: absolute; width: 145px; height: 145px; object-fit: contain; transition: transform 0.3s ease; image-rendering: pixelated; image-rendering: -moz-crisp-edges; image-rendering: crisp-edges; z-index: 3; filter: drop-shadow(0 5px 8px rgba(0,0,0,0.28)); transform-origin: bottom center; }
        #opponent-sprite { top: 12%; right: 15%; }
        #player-sprite { bottom: 8%; left: 15%; }

        /* --- Battle Controls --- */
        .battle-controls { display: flex; border-top: 1px solid var(--divider-color); height: 130px; z-index: 11; background-color: var(--card-bg); position: relative; }
        .battle-message { flex-grow: 1; padding: 20px; font-size: 16px; 1.5; display: flex; align-items: center; overflow-y: auto; height: 100%; color: var(--text-color); }
        .attack-options { width: 40%; display: grid; grid-template-columns: 1fr 1fr; gap: 10px; padding: 15px; border-left: 1px solid var(--divider-color); height: 100%; }
        .attack-button { /* Using Material Filled Button Style */
            background-color: var(--primary-blue); color: white; border: none; border-radius: 4px; /* Standard MD radius */
            padding: 8px 16px; /* MD Button Padding */ font-size: 14px; font-weight: 500; /* MD Button Font */
             text-transform: uppercase; letter-spacing: 0.8px; /* MD Button Text Style */
             cursor: pointer; transition: background-color 0.2s ease, box-shadow 0.2s ease, transform 0.1s ease; text-align: center;
             box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); /* Subtle button shadow */
             overflow: hidden; position: relative; /* For potential ripple */
        }
        .attack-button:hover:not(:disabled) { background-color: var(--hover-blue); box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); }
        .attack-button:active:not(:disabled) { transform: scale(0.98); box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); }
        .attack-button:disabled { background-color: rgba(0,0,0,0.12); color: var(--very-subtle-text); cursor: not-allowed; box-shadow: none; }

        /* --- Animations --- */
        .sprite-hit-flash { animation: flash 0.4s ease-out; }
        .sprite-faint { animation: faint 1s ease-out forwards; }
        .sprite-shake-short { animation: shake-short 0.3s ease-in-out; }
        @keyframes flash { 0%, 100% { opacity: 1; filter: drop-shadow(0 5px 8px rgba(0,0,0,0.28)) brightness(1); } 50% { opacity: 0.6; filter: drop-shadow(0 5px 8px rgba(0,0,0,0.28)) brightness(1.8); } }
        @keyframes faint { 0% { transform: translateY(0) scale(1); opacity: 1; } 50% { transform: translateY(-15px) rotateZ(-5deg) scale(1.05); opacity: 0.8; } 100% { transform: translateY(40px) scale(0.7); opacity: 0; } }
        @keyframes shake-short { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-3px); } 75% { transform: translateX(3px); } }

        /* --- Game Over Screen --- */
        .game-over-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(33, 33, 33, 0.7); /* Darker overlay */ backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); color: white; display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 20; opacity: 0; pointer-events: none; transition: opacity 0.5s ease; text-align: center; }
        .game-over-overlay.visible { opacity: 1; pointer-events: auto; }
        .game-over-overlay h2 { font-size: 34px; 20px; font-weight: 500; }
        .game-over-overlay button { /* Re-style button to match MD */
            background-color: var(--secondary-color); /* Use secondary color */ color: rgba(0,0,0,0.87); border: none; border-radius: 4px;
            padding: 10px 24px; font-size: 14px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.8px;
             cursor: pointer; transition: background-color 0.2s ease, box-shadow 0.2s ease, transform 0.1s ease; 24px;
             box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
        }
         .game-over-overlay button:hover { background-color: #00bfa5; /* Darker secondary */ box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); transform: translateY(-1px); }
         .game-over-overlay button:active { transform: scale(0.99); box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); }   
    </style>
    <!-- ADD THREE.JS LIBRARY -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
</head>
<body>

    <div class="game-container" id="game-container">

        <!-- ===== Selection Screen ===== -->
        <div class="screen selection-screen" id="selection-screen">
            <h1>Choose your Pokémon</h1>
            <div class="pokemon-cards" id="pokemon-cards"></div>
        </div>

        <!-- ===== Battle Screen ===== -->
        <div class="screen battle-screen" id="battle-screen">
            <div class="battle-arena" id="battle-arena">
                <!-- 3D Canvas -->
                <canvas id="three-canvas"></canvas>

                <!-- Sprites (Now direct children of Arena for easier absolute positioning) -->
                 <img src="" alt="Opponent Pokémon" id="opponent-sprite" class="pokemon-sprite">
                 <img src="" alt="Player Pokémon" id="player-sprite" class="pokemon-sprite">

                <!-- Info Boxes (Absolutely positioned containers) -->
                <div class="opponent-side">
                    <div class="pokemon-info-box">
                        <div class="pokemon-name" id="opponent-name">Opponent</div>
                        <div class="hp-text" id="opponent-hp-text">HP: ??? / ???</div>
                        <div class="health-bar-container">
                            <div class="health-bar" id="opponent-health-bar"></div>
                        </div>
                    </div>
                </div>
                <div class="player-side">
                    <div class="pokemon-info-box">
                        <div class="pokemon-name" id="player-name">Player</div>
                        <div class="hp-text" id="player-hp-text">HP: ??? / ???</div>
                        <div class="health-bar-container">
                            <div class="health-bar" id="player-health-bar"></div>
                        </div>
                    </div>
                </div>

            </div>

            <div class="battle-controls">
                <div class="battle-message" id="battle-message">Select a Pokémon!</div>
                <div class="attack-options" id="attack-options"></div>
            </div>
        </div>

        <!-- ===== Game Over Overlay ===== -->
         <div class="game-over-overlay" id="game-over-overlay">
            <h2 id="game-over-message">Game Over</h2>
            <button id="play-again-button">Play Again</button>
        </div>

    </div>

    <script>
        // --- DOM Elements ---
        const gameContainer = document.getElementById('game-container');
        const selectionScreen = document.getElementById('selection-screen');
        const battleScreen = document.getElementById('battle-screen');
        const pokemonCardsContainer = document.getElementById('pokemon-cards');
        const battleArena = document.getElementById('battle-arena');
        const threeCanvas = document.getElementById('three-canvas');
        const battleMessage = document.getElementById('battle-message');
        const attackOptionsContainer = document.getElementById('attack-options');
        // Opponent UI
        const opponentNameEl = document.getElementById('opponent-name');
        const opponentHpTextEl = document.getElementById('opponent-hp-text');
        const opponentHealthBarEl = document.getElementById('opponent-health-bar');
        const opponentSpriteEl = document.getElementById('opponent-sprite');
        // Player UI
        const playerNameEl = document.getElementById('player-name');
        const playerHpTextEl = document.getElementById('player-hp-text');
        const playerHealthBarEl = document.getElementById('player-health-bar');
        const playerSpriteEl = document.getElementById('player-sprite');
        // Game Over UI
        const gameOverOverlay = document.getElementById('game-over-overlay');
        const gameOverMessage = document.getElementById('game-over-message');
        const playAgainButton = document.getElementById('play-again-button');

        // --- Game Data ---
        // Ensure all attacks have a valid effectType
        const pokemonData = [
             {
                id: 1, name: 'Pikachu', type: 'Electric', hp: 90, maxHp: 90,
                description: "Its electric pouches store energy. It discharges electricity when threatened.", // Added description
                spriteUrl: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/25.png',
                attacks: [ { name: 'Thunder Shock', damage: 20, effectType: 'electric' }, { name: 'Quick Attack', damage: 15, effectType: 'physical_dash' }, { name: 'Iron Tail', damage: 25, effectType: 'physical_impact' }, { name: 'Thunderbolt', damage: 35, effectType: 'electric_strong' } ]
            },
             {
                id: 2, name: 'Charizard', type: 'Fire', hp: 120, maxHp: 120,
                description: "Spits fire hot enough to melt boulders. Known to cause forest fires unintentionally.", // Added description
                spriteUrl: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/6.png',
                attacks: [ { name: 'Flamethrower', damage: 30, effectType: 'fire' }, { name: 'Slash', damage: 22, effectType: 'physical_impact' }, { name: 'Dragon Claw', damage: 28, effectType: 'physical_impact' }, { name: 'Air Slash', damage: 25, effectType: 'physical_dash' } ]
            },
            {
                id: 3, name: 'Bulbasaur', type: 'Grass', hp: 100, maxHp: 100,
                description: "A strange seed was planted on its back at birth. The plant sprouts and grows with this Pokémon.", // Added description
                spriteUrl: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/1.png',
                attacks: [ { name: 'Vine Whip', damage: 20, effectType: 'physical_impact' }, { name: 'Tackle', damage: 15, effectType: 'physical_dash' }, { name: 'Seed Bomb', damage: 28, effectType: 'grass_bomb' }, { name: 'Razor Leaf', damage: 25, effectType: 'grass_leaf' } ]
            },
             {
                id: 4, name: 'Squirtle', type: 'Water', hp: 100, maxHp: 100,
                description: "After birth, its back swells and hardens into a shell. Powerfully sprays foam from its mouth.", // Added description
                spriteUrl: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/7.png',
                attacks: [ { name: 'Water Gun', damage: 20, effectType: 'water' }, { name: 'Tackle', damage: 15, effectType: 'physical_dash' }, { name: 'Bubble Beam', damage: 26, effectType: 'water_bubble' }, { name: 'Aqua Tail', damage: 30, effectType: 'physical_impact' } ]
            },
             {
                id: 5, name: 'Gengar', type: 'Ghost', hp: 110, maxHp: 110,
                description: "It hides in shadows. It is said that if Gengar is hiding, it cools the area by nearly 10°F.", // Added description
                spriteUrl: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/94.png',
                attacks: [ { name: 'Shadow Ball', damage: 32, effectType: 'dark_ball' }, { name: 'Lick', damage: 18, effectType: 'physical_impact' }, { name: 'Dark Pulse', damage: 28, effectType: 'dark_pulse' }, { name: 'Sludge Bomb', damage: 30, effectType: 'poison_bomb' } ]
            },
        ];
        // --- Game State ---
        let playerPokemon = null;
        let opponentPokemon = null;
        let isPlayerTurn = true;
        let isBattleOver = false;
        let attackInProgress = false; // Crucial flag

        // --- Three.js Setup ---
        let scene, camera, renderer, clock;
        let activeParticles = [];
        let animationFrameId = null;

        function initThree() {
            scene = new THREE.Scene();
            clock = new THREE.Clock();

            const arenaRect = battleArena.getBoundingClientRect();
            const aspect = arenaRect.width / arenaRect.height;

            camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000);
            camera.position.set(0, 1.5, 6); // Adjusted camera slightly
            camera.lookAt(0, 0, 0);

            renderer = new THREE.WebGLRenderer({ canvas: threeCanvas, alpha: true, antialias: true });
            renderer.setSize(arenaRect.width, arenaRect.height);
            renderer.setPixelRatio(window.devicePixelRatio);

            const ambientLight = new THREE.AmbientLight(0xffffff, 0.7);
            scene.add(ambientLight);
            const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
            directionalLight.position.set(5, 10, 7);
            scene.add(directionalLight);

            window.addEventListener('resize', onWindowResize, false);
        }

// --- Helper Functions for Card Generation ---
        function mapHpToStars(hp) {
            // Simple mapping: 1 star per 15 HP, max 10 stars
            const stars = Math.min(10, Math.max(1, Math.ceil(hp / 15)));
            return '★'.repeat(stars); // Repeat the star character
        }

        function getTypeAttribute(type) {
            // Basic mapping of Pokemon types to Yu-Gi-Oh style attributes/colors
            // This is simplified - Yu-Gi-Oh attributes are LIGHT, DARK, EARTH, WATER, FIRE, WIND, DIVINE
            // We'll map Pokemon types conceptually for visual flair
            switch (type.toLowerCase()) {
                case 'fire': return { symbol: '🔥', color: '#FF4500' }; // FIRE - OrangeRed
                case 'water': return { symbol: '💧', color: '#1E90FF' }; // WATER - DodgerBlue
                case 'grass': return { symbol: '🌿', color: '#228B22' }; // EARTH (conceptually) - ForestGreen
                case 'electric': return { symbol: '⚡', color: '#FFD700' }; // LIGHT (conceptually) - Gold
                case 'ghost': return { symbol: '👻', color: '#4B0082' }; // DARK (conceptually) - Indigo
                case 'psychic': return { symbol: '👁️', color: '#FF1493' }; // LIGHT (conceptually) - DeepPink
                case 'fighting': return { symbol: '🥊', color: '#A0522D' }; // EARTH - Sienna
                case 'normal': return { symbol: '⚪', color: '#A9A9A9' }; // Generic - DarkGray
                default: return { symbol: '❓', color: '#696969' }; // Default - DimGray
            }
        }
        
        function onWindowResize() {
            const arenaRect = battleArena.getBoundingClientRect();
            if (arenaRect.width > 0 && arenaRect.height > 0 && renderer && camera) {
                 camera.aspect = arenaRect.width / arenaRect.height;
                 camera.updateProjectionMatrix();
                 renderer.setSize(arenaRect.width, arenaRect.height);
            }
        }

        function get3DPositionFromSprite(spriteElement) {
            // Ensure calculation runs only when element is visible and sized
            if (!spriteElement || spriteElement.offsetParent === null || !camera) {
                // console.warn("Sprite not ready for 3D positioning", spriteElement);
                 return new THREE.Vector3(0, 0, 0); // Return default if not ready
            }
            const spriteRect = spriteElement.getBoundingClientRect();
            const arenaRect = battleArena.getBoundingClientRect();

            const spriteCenterX = spriteRect.left - arenaRect.left + spriteRect.width / 2;
            const spriteCenterY = spriteRect.top - arenaRect.top + spriteRect.height / 2; // Use bottom for origin maybe? Let's stick to center

            const ndcX = (spriteCenterX / arenaRect.width) * 2 - 1;
            const ndcY = -(spriteCenterY / arenaRect.height) * 2 + 1;

            const vector = new THREE.Vector3(ndcX, ndcY, 0.5);
            vector.unproject(camera);
            const dir = vector.sub(camera.position).normalize();
            const distance = -camera.position.z / dir.z;
            const pos = camera.position.clone().add(dir.multiplyScalar(distance));

            // Adjust Y slightly to match perceived sprite base
            pos.y += (spriteElement === opponentSpriteEl) ? 0.2 : -0.3;

            return pos;
        }


        // --- Particle Animation Logic ---
        function createParticleEffect(config) {
            const { count = 100, color = 0xffffff, size = 0.1, duration = 1.0, startPos, endPos, speed = 5, type = 'burst', stagger = 0.2 } = config;

            if (!scene || !clock) return; // Safety check

            const geometry = new THREE.BufferGeometry();
            const positions = new Float32Array(count * 3);
            const velocities = new Float32Array(count * 3);
            const startTimes = new Float32Array(count);
            const initialSize = new Float32Array(count); // For size variation

            const baseTime = clock.getElapsedTime();

            for (let i = 0; i < count; i++) {
                positions[i * 3] = startPos.x + (Math.random() - 0.5) * 0.1; // Slight start variance
                positions[i * 3 + 1] = startPos.y + (Math.random() - 0.5) * 0.1;
                positions[i * 3 + 2] = startPos.z;
                initialSize[i] = size * (0.8 + Math.random() * 0.4); // Size variation

                if (type === 'burst') {
                    const phi = Math.random() * Math.PI * 2;
                    const costheta = Math.random() * 2 - 1;
                    const sintheta = Math.sqrt(1 - costheta * costheta);
                    velocities[i * 3] = sintheta * Math.cos(phi) * speed * (0.5 + Math.random());
                    velocities[i * 3 + 1] = sintheta * Math.sin(phi) * speed * (0.5 + Math.random());
                    velocities[i * 3 + 2] = costheta * speed * (0.5 + Math.random());
                } else if (type === 'stream' && endPos) {
                    const dir = endPos.clone().sub(startPos).normalize();
                    const angleVariance = 0.2; // Radians spread
                    const randomAngleX = (Math.random() - 0.5) * angleVariance;
                    const randomAngleY = (Math.random() - 0.5) * angleVariance;
                     dir.applyAxisAngle(new THREE.Vector3(0, 1, 0), randomAngleX); // Rotate slightly
                    dir.applyAxisAngle(new THREE.Vector3(1, 0, 0), randomAngleY);

                    velocities[i * 3] = dir.x * speed * (0.8 + Math.random() * 0.4);
                    velocities[i * 3 + 1] = dir.y * speed * (0.8 + Math.random() * 0.4);
                    velocities[i * 3 + 2] = dir.z * speed * (0.8 + Math.random() * 0.4);
                } else { // Default burst
                     const phi = Math.random() * Math.PI * 2;
                    const costheta = Math.random() * 2 - 1;
                    const sintheta = Math.sqrt(1 - costheta * costheta);
                    velocities[i * 3] = sintheta * Math.cos(phi) * speed;
                    velocities[i * 3 + 1] = sintheta * Math.sin(phi) * speed;
                    velocities[i * 3 + 2] = costheta * speed;
                }
                startTimes[i] = baseTime + (Math.random() * stagger); // Stagger start
            }

            geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
            geometry.setAttribute('velocity', new THREE.BufferAttribute(velocities, 3));
            geometry.setAttribute('startTime', new THREE.BufferAttribute(startTimes, 1));
            geometry.setAttribute('initialSize', new THREE.BufferAttribute(initialSize, 1)); // Store initial size

            const material = new THREE.PointsMaterial({
                color: color,
                size: size, // Base size, might be overridden if shader used
                transparent: true,
                opacity: 0.9,
                blending: THREE.AdditiveBlending,
                sizeAttenuation: true,
                depthWrite: false
            });

            const points = new THREE.Points(geometry, material);
            points.userData = {
                startTime: baseTime, // Overall system start time
                duration: duration,
                isParticleSystem: true,
                 needsUpdate: true, // Flag for the update loop
                 baseSize: size
            };

            scene.add(points);
            activeParticles.push(points);
            // console.log("Created particle effect:", type, "Color:", color); // Debug log
        }

        function updateParticles(deltaTime) {
            if (!clock || !scene) return;
            const currentTime = clock.getElapsedTime();
            const particlesToRemove = [];

            for (let i = activeParticles.length - 1; i >= 0; i--) {
                const system = activeParticles[i];

                 // Handle Non-particle tracked objects (like bombs/rings)
                if (!system.userData.isParticleSystem) {
                     if (system.update) { // Does it have a custom update function?
                         system.update(deltaTime);
                     }
                     // Check if its duration has passed
                     if (currentTime > system.userData.startTime + system.userData.duration) {
                         particlesToRemove.push(i);
                         scene.remove(system);
                         if(system.geometry) system.geometry.dispose();
                         if(system.material) system.material.dispose();
                     }
                     continue; // Move to next active particle/mesh
                 }


                // Handle standard particle systems
                const systemElapsedTime = currentTime - system.userData.startTime;
                if (systemElapsedTime > system.userData.duration + 1.0) { // Add buffer for staggered particles
                    particlesToRemove.push(i);
                    scene.remove(system);
                    system.geometry.dispose();
                    system.material.dispose();
                    continue;
                }

                // Update individual particles within the system
                const positions = system.geometry.attributes.position.array;
                const velocities = system.geometry.attributes.velocity.array;
                const startTimes = system.geometry.attributes.startTime.array;
                const initialSizes = system.geometry.attributes.initialSize.array; // Get sizes

                let aliveParticles = 0;
                for (let j = 0; j < positions.length / 3; j++) {
                    const particleStartTime = startTimes[j];
                    if (currentTime >= particleStartTime) {
                        const particleElapsedTime = currentTime - particleStartTime;
                        // Check if individual particle life is over (relative to system duration)
                         if (particleElapsedTime < system.userData.duration) {
                             positions[j * 3] += velocities[j * 3] * deltaTime;
                             positions[j * 3 + 1] += velocities[j * 3 + 1] * deltaTime;
                             positions[j * 3 + 2] += velocities[j * 3 + 2] * deltaTime;
                             // Optional: Add gravity or forces here
                             // velocities[j*3+1] -= 9.8 * deltaTime * 0.1; // Simple weak gravity

                            aliveParticles++;
                         }
                    } else {
                        // Particle hasn't started yet, but is technically alive
                        aliveParticles++;
                    }
                }

                system.geometry.attributes.position.needsUpdate = true;

                 // Fade out the entire system based on its overall lifetime
                 const lifeLeft = 1.0 - Math.min(1.0, systemElapsedTime / system.userData.duration);
                 system.material.opacity = Math.max(0, lifeLeft * 0.9);
                 // system.material.size = system.userData.baseSize * lifeLeft; // Optional shrink

                // If no particles will ever be alive again, mark for removal early
                if (aliveParticles === 0 && systemElapsedTime > system.userData.duration) {
                     if (!particlesToRemove.includes(i)) { // Avoid duplicates
                         particlesToRemove.push(i);
                         scene.remove(system);
                         system.geometry.dispose();
                         system.material.dispose();
                     }
                }
            }

             // Remove marked systems from the active list
            particlesToRemove.sort((a, b) => b - a); // Sort descending to avoid index issues
            particlesToRemove.forEach(index => activeParticles.splice(index, 1));
        }


        function animate() {
            animationFrameId = requestAnimationFrame(animate);
            if (!renderer || !scene || !camera || !clock) return; // Safety checks

            const deltaTime = clock.getDelta();
            updateParticles(deltaTime);

            if (threeCanvas.offsetParent !== null) { // Render only if visible
                renderer.render(scene, camera);
            }
        }

        function startAnimationLoop() {
            if (!animationFrameId) {
                if(!clock) clock = new THREE.Clock(); // Ensure clock exists
                clock.start();
                animate();
                // console.log("Animation loop started");
            }
        }

        function stopAnimationLoop() {
            if (animationFrameId) {
                cancelAnimationFrame(animationFrameId);
                animationFrameId = null;
                if(clock) clock.stop();
                // console.log("Animation loop stopped");
                // Clean up particles immediately
                activeParticles.forEach(system => {
                    scene.remove(system);
                    if(system.geometry) system.geometry.dispose();
                    if(system.material) system.material.dispose();
                });
                activeParticles = [];
            }
        }

         // --- Core Game Logic (Modified) ---

        function getRandomPokemon(excludeId = null) {
            const availablePokemon = pokemonData.filter(p => p.id !== excludeId);
            const randomIndex = Math.floor(Math.random() * availablePokemon.length);
            return JSON.parse(JSON.stringify(availablePokemon[randomIndex]));
        }

        function updateHealthBar(pokemon, healthBarEl, hpTextEl) {
            const hpPercent = Math.max(0, (pokemon.hp / pokemon.maxHp) * 100);
            healthBarEl.style.width = `${hpPercent}%`;
            hpTextEl.textContent = `HP: ${Math.max(0, pokemon.hp)} / ${pokemon.maxHp}`;
            healthBarEl.className = 'health-bar'; // Reset classes first
            if (hpPercent > 50) healthBarEl.classList.add('high');
            else if (hpPercent > 20) healthBarEl.classList.add('medium');
            else healthBarEl.classList.add('low');
        }

        function displayMessage(message, speed = 40) {
            battleMessage.textContent = ''; // Clear first
            let i = 0;
            function typeWriter() {
                if (i < message.length) {
                    battleMessage.textContent += message.charAt(i);
                    i++;
                    setTimeout(typeWriter, speed);
                } else {
                    battleMessage.scrollTop = battleMessage.scrollHeight;
                }
            }
            typeWriter();
        }

        function playCssAnimation(targetSprite, animationClass, duration = 400) {
            targetSprite.classList.add(animationClass);
            setTimeout(() => {
                targetSprite.classList.remove(animationClass);
            }, duration);
        }

        // Modified: Now just sets the flag, button state handled separately
        function setAttackInProgress(inProgress) {
            attackInProgress = inProgress;
            // console.log("Attack In Progress:", attackInProgress); // Debug log
             const buttons = attackOptionsContainer.querySelectorAll('.attack-button');
             buttons.forEach(button => button.disabled = inProgress || !isPlayerTurn); // Also disable if not player turn
        }

        // --- Play 3D Attack Animations (REVISED Cases) ---
        function play3DAttackAnimation(attack, attackerSprite, defenderSprite) {
             if (!scene || !clock) return;
             console.log(`Attempting 3D effect: ${attack.effectType} from ${attackerSprite.id} to ${defenderSprite.id}`);

             // Get positions *just before* creating effect, ensuring elements are placed
             const startPos = get3DPositionFromSprite(attackerSprite);
             const endPos = get3DPositionFromSprite(defenderSprite);

             // Ensure positions are valid (not default 0,0,0 if element wasn't ready)
             if (startPos.lengthSq() === 0 || endPos.lengthSq() === 0) {
                 console.warn("Could not get valid 3D positions for animation.", startPos, endPos);
                 return; // Don't try to animate if positions are bad
             }

             switch (attack.effectType) {
                case 'electric':
                case 'electric_strong':
                    createParticleEffect({
                        startPos: startPos, endPos: endPos, type: 'stream',
                        color: 0xFFFF00, size: attack.effectType === 'electric_strong' ? 0.12 : 0.09,
                        count: attack.effectType === 'electric_strong' ? 150 : 100,
                        speed: 12, duration: 0.7, stagger: 0.1
                    });
                     if (attack.effectType === 'electric_strong') {
                        setTimeout(() => createParticleEffect({
                            startPos: endPos, type: 'burst', color: 0xFFFF99, size: 0.25, count: 60, speed: 5, duration: 0.5
                        }), 450); // Impact burst delay
                     }
                    break;
                case 'fire':
                    createParticleEffect({
                        startPos: startPos, endPos: endPos, type: 'stream', color: 0xFF4500,
                        size: 0.18, count: 120, speed: 9, duration: 0.9, stagger: 0.2
                    });
                     createParticleEffect({
                        startPos: startPos, endPos: endPos, type: 'stream', color: 0xFFD700,
                        size: 0.08, count: 70, speed: 9.5, duration: 0.9, stagger: 0.25
                    });
                    break;
                case 'water':
                     createParticleEffect({
                        startPos: startPos, endPos: endPos, type: 'stream', color: 0x1E90FF,
                        size: 0.15, count: 120, speed: 10, duration: 0.8, stagger: 0.15
                    });
                     break;
                case 'water_bubble':
                     createParticleEffect({
                         startPos: startPos, endPos: endPos, type: 'stream', color: 0xADD8E6,
                         size: 0.2, count: 50, speed: 7, duration: 1.1, stagger: 0.3
                     });
                     createParticleEffect({
                         startPos: startPos, endPos: endPos, type: 'stream', color: 0xFFFFFF,
                         size: 0.08, count: 70, speed: 7.5, duration: 1.1, stagger: 0.35
                     });
                    break;
                 case 'grass_bomb':
                 case 'poison_bomb':
                    const bombColor = attack.effectType === 'grass_bomb' ? 0x00FF00 : 0x8A2BE2; // Green or BlueViolet
                    const bombGeo = new THREE.SphereGeometry(0.3, 16, 16);
                    const bombMat = new THREE.MeshBasicMaterial({ color: bombColor, transparent: true, opacity: 0.9 });
                    const bombMesh = new THREE.Mesh(bombGeo, bombMat);
                    bombMesh.position.copy(startPos);
                    bombMesh.userData = { isParticleSystem: false, startTime: clock.getElapsedTime(), duration: 0.6, start: startPos.clone(), end: endPos.clone() };
                    scene.add(bombMesh);
                    activeParticles.push(bombMesh);
                    bombMesh.update = function(deltaTime) { // Assign update directly
                         const progress = Math.min(1.0, (clock.getElapsedTime() - this.userData.startTime) / this.userData.duration);
                         this.position.lerpVectors(this.userData.start, this.userData.end, progress);
                         if (progress >= 1.0 && !this.userData.exploded) { // Explode only once
                             this.userData.exploded = true; // Prevent re-explosion
                             createParticleEffect({
                                startPos: this.position, type: 'burst', color: bombColor,
                                size: 0.2, count: 100, speed: 6, duration: 0.6
                             });
                             // Mesh removal is handled by the main update loop based on duration
                         }
                      };
                    break;
                 case 'grass_leaf':
                     for (let i=0; i<15; i++) {
                         setTimeout(() => {
                             createParticleEffect({
                                startPos: startPos, endPos: endPos, type: 'stream', color: 0x228B22,
                                size: 0.12, count: 5, speed: 8 + (Math.random() * 2),
                                duration: 0.7 + (Math.random() * 0.2), stagger: 0.05
                             });
                         }, i * 35); // Stagger leaves
                     }
                     break;
                 case 'dark_ball':
                     createParticleEffect({
                         startPos: startPos, endPos: endPos, type: 'stream', color: 0x483D8B,
                         size: 0.25, count: 60, speed: 8, duration: 0.9, stagger: 0.1
                     });
                      createParticleEffect({
                         startPos: startPos, endPos: endPos, type: 'stream', color: 0x8A2BE2, // BlueViolet glow
                         size: 0.1, count: 60, speed: 8.5, duration: 0.9, stagger: 0.15
                     });
                     break;
                  case 'dark_pulse':
                      const pulseColor = 0x4B0082; // Indigo
                      for(let i = 0; i < 4; i++) { // More rings
                          setTimeout(() => {
                              const ringGeo = new THREE.RingGeometry(0.1 + i*0.6, 0.3 + i*0.6, 32);
                              const ringMat = new THREE.MeshBasicMaterial({ color: pulseColor, side: THREE.DoubleSide, transparent: true, opacity: 0.7 });
                              const ringMesh = new THREE.Mesh(ringGeo, ringMat);
                              ringMesh.position.copy(endPos);
                              ringMesh.position.y += 0.1; // Slightly lift pulse center
                              ringMesh.rotation.x = Math.PI / 1.8; // Angle slightly towards camera
                              ringMesh.userData = { isParticleSystem: false, startTime: clock.getElapsedTime(), duration: 0.6, startScale: 0.1, endScale: 1.0 + i*0.6 };
                              scene.add(ringMesh);
                              activeParticles.push(ringMesh);
                              ringMesh.update = function(deltaTime) {
                                const progress = Math.min(1.0, (clock.getElapsedTime() - this.userData.startTime) / this.userData.duration);
                                const easeOutQuad = t => t * (2 - t); // Easing function
                                const scale = THREE.MathUtils.lerp(this.userData.startScale, this.userData.endScale, easeOutQuad(progress));
                                this.scale.set(scale, scale, scale);
                                this.material.opacity = Math.max(0, 0.7 * (1.0 - progress));
                              };
                          }, i * 120); // Stagger rings
                      }
                      break;
                case 'physical_dash':
                    // Attacker lunge (CSS/simple movement could be added) + quick particle trail
                    createParticleEffect({
                        startPos: startPos, endPos: endPos, type: 'stream', color: 0xFFFFFF,
                        size: 0.06, count: 40, speed: 18, duration: 0.35, stagger: 0.05
                    });
                    break;
                case 'physical_impact':
                    // Impact burst at defender
                    createParticleEffect({
                        startPos: endPos, type: 'burst', color: 0xFFA500, // Orange sparks
                        size: 0.15, count: 60, speed: 5, duration: 0.5, stagger: 0.1
                    });
                     createParticleEffect({
                        startPos: endPos, type: 'burst', color: 0xFFFFFF, // White inner flash
                        size: 0.1, count: 40, speed: 6, duration: 0.4, stagger: 0.05
                    });
                    break;
                default:
                     console.warn(`Unknown effectType: ${attack.effectType}. Playing default effect.`);
                    createParticleEffect({ // Default effect if type unknown
                        startPos: endPos, type: 'burst', color: 0xCCCCCC,
                        size: 0.1, count: 50, speed: 4, duration: 0.6
                    });
            }
        }

        // --- handleAttack (REVISED Timing & State) ---
        function handleAttack(attack, attacker, defender, attackerSprite, defenderSprite, defenderHealthBar, defenderHpText) {
            // Check guards: Battle over, attack already in progress, or not the correct turn
            if (isBattleOver || attackInProgress || (isPlayerTurn && attacker !== playerPokemon) || (!isPlayerTurn && attacker !== opponentPokemon)) {
                // console.log("Attack prevented:", {isBattleOver, attackInProgress, isPlayerTurn, attacker: attacker.name});
                return;
            }

            setAttackInProgress(true); // Set flag and disable buttons *immediately*

            const message = `${attacker.name} used ${attack.name}!`;
            displayMessage(message, 30);

            // --- Define Timeouts ---
            const PRE_ANIM_DELAY = 200; // Small pause before visual action
            const POST_3D_START_DELAY = 100; // Time after 3D anim starts before attacker shake
            const IMPACT_DELAY = 650; // Time from start until damage/hit flash (adjust based on typical 3D anim travel time)
            const POST_IMPACT_MSG_DELAY = IMPACT_DELAY + 300; // Time until damage message update
            const FAINT_CHECK_DELAY = POST_IMPACT_MSG_DELAY + 100; // Time until faint check
            const TURN_SWITCH_DELAY = FAINT_CHECK_DELAY + 1200; // Total time before switching turn (allowing messages/faint anim)

            // 1. Initial delay
            setTimeout(() => {
                // 2. Start 3D Animation
                play3DAttackAnimation(attack, attackerSprite, defenderSprite);

                // 3. Attacker animation (optional small shake) shortly after 3D starts
                setTimeout(() => {
                     playCssAnimation(attackerSprite, 'sprite-shake-short', 300);
                }, POST_3D_START_DELAY);


                // 4. Damage application and defender hit animation at IMPACT_DELAY
                setTimeout(() => {
                    defender.hp -= attack.damage;
                    updateHealthBar(defender, defenderHealthBar, defenderHpText);
                    playCssAnimation(defenderSprite, 'sprite-hit-flash', 400); // CSS flash on hit

                    // 5. Update battle message with damage info after impact
                    setTimeout(() => {
                        const damageMessage = ` It dealt ${attack.damage} damage.`;
                        displayMessage(message + damageMessage, 30);

                        // 6. Check for faint after damage message is displayed
                        setTimeout(() => {
                             if (defender.hp <= 0) {
                                 // Faint sequence handles its own delays and game over state
                                 handleFaint(defender, defenderSprite);
                                 // Faint handles setting battle over and showing overlay, no turn switch needed here.
                             } else {
                                 // 7. If no faint, schedule the turn switch
                                 setTimeout(switchTurn, TURN_SWITCH_DELAY - FAINT_CHECK_DELAY); // Remaining time until turn switch
                             }
                        }, FAINT_CHECK_DELAY - POST_IMPACT_MSG_DELAY); // Delay relative to message update

                    }, POST_IMPACT_MSG_DELAY - IMPACT_DELAY); // Delay relative to impact

                }, IMPACT_DELAY - PRE_ANIM_DELAY); // Adjust delay relative to outer timeout

            }, PRE_ANIM_DELAY);
        }


         function handleFaint(faintedPokemon, faintedSprite) {
             // No need to check attackInProgress here, as it's part of the attack sequence
             isBattleOver = true; // Set battle over flag *immediately*
             playCssAnimation(faintedSprite, 'sprite-faint', 1000);
             const winner = (faintedPokemon === opponentPokemon) ? playerPokemon : opponentPokemon;
             const loser = faintedPokemon;

             const message = `${loser.name} fainted!`;
             displayMessage(message); // Show faint message first

             setTimeout(() => { // Delay win message and game over screen
                 displayMessage(`${message} ${winner.name} wins!`);
                 showGameOver(winner === playerPokemon);
                 // Don't set attackInProgress = false here, resetGame handles it
             }, 1200); // Allow faint animation to play out mostly
         }


        function switchTurn() {
            if(isBattleOver) return; // Don't switch turns if game has ended

            isPlayerTurn = !isPlayerTurn;
             // console.log("Switching turn. Player turn:", isPlayerTurn);

            if (!isPlayerTurn) {
                // Opponent's turn
                displayMessage(`Opponent ${opponentPokemon.name}'s turn...`);
                 // Buttons remain disabled (set by setAttackInProgress(true) earlier)
                setTimeout(opponentAttack, 1500 + Math.random() * 500);
            } else {
                // Player's turn - *Crucially*, re-enable controls and reset flag
                displayMessage(`Your turn! Choose an attack.`);
                setAttackInProgress(false); // Reset flag and enable buttons
            }
        }

        function opponentAttack() {
            if(isBattleOver) return; // Check again before opponent attacks

            const randomAttackIndex = Math.floor(Math.random() * opponentPokemon.attacks.length);
            const attack = opponentPokemon.attacks[randomAttackIndex];
            // Opponent's handleAttack call will set attackInProgress back to true
            handleAttack(attack, opponentPokemon, playerPokemon, opponentSpriteEl, playerSpriteEl, playerHealthBarEl, playerHpTextEl);
        }


        function setupBattle(selectedPlayerPokemon) {
            playerPokemon = JSON.parse(JSON.stringify(selectedPlayerPokemon));
            opponentPokemon = getRandomPokemon(playerPokemon.id);
            isBattleOver = false;
            isPlayerTurn = true; // Player always starts
            attackInProgress = false; // Ensure reset at start

            stopAnimationLoop(); // Clear any old effects
            if (!scene) { // Initialize Three.js only if needed
                initThree();
            } else { // Clear previous non-light objects if scene exists
                activeParticles.forEach(system => { // Clear array first
                    scene.remove(system);
                    if(system.geometry) system.geometry.dispose();
                    if(system.material) system.material.dispose();
                });
                activeParticles = [];
                // Also remove any other temporary meshes if needed
                 const objectsToRemove = scene.children.filter(child =>
                    !(child instanceof THREE.Light) && !(child instanceof THREE.Camera)
                 );
                 objectsToRemove.forEach(obj => scene.remove(obj));
            }


            // Update Player UI
            playerNameEl.textContent = playerPokemon.name;
            playerSpriteEl.src = playerPokemon.spriteUrl;
            playerSpriteEl.alt = playerPokemon.name;
            playerSpriteEl.className = 'pokemon-sprite'; // Reset classes
            playerSpriteEl.style.opacity = 1;
            updateHealthBar(playerPokemon, playerHealthBarEl, playerHpTextEl);

            // Update Opponent UI
            opponentNameEl.textContent = opponentPokemon.name;
            opponentSpriteEl.src = opponentPokemon.spriteUrl;
            opponentSpriteEl.alt = opponentPokemon.name;
            opponentSpriteEl.className = 'pokemon-sprite'; // Reset classes
            opponentSpriteEl.style.opacity = 1;
            updateHealthBar(opponentPokemon, opponentHealthBarEl, opponentHpTextEl);

            // Create Attack Buttons
            attackOptionsContainer.innerHTML = '';
            playerPokemon.attacks.forEach(attack => {
                const button = document.createElement('button');
                button.classList.add('attack-button');
                button.textContent = attack.name;
                button.onclick = () => handleAttack(attack, playerPokemon, opponentPokemon, playerSpriteEl, opponentSpriteEl, opponentHealthBarEl, opponentHpTextEl);
                attackOptionsContainer.appendChild(button);
            });

            displayMessage(`Battle start! ${playerPokemon.name} vs ${opponentPokemon.name}. Your turn!`);
            setAttackInProgress(false); // Explicitly enable buttons for player start

            gameContainer.classList.add('battle-active');
            setTimeout(() => {
                 onWindowResize(); // Ensure canvas size is correct after transition
                 startAnimationLoop();
                 // Force redraw/reflow to help positioning? (Usually not needed)
                 // battleArena.offsetHeight;
            }, 650); // Slightly longer delay to ensure layout settled
        }

        function showGameOver(playerWon) {
            gameOverMessage.textContent = playerWon ? "You Won!" : "You Lost!";
            gameOverOverlay.classList.add('visible');
            stopAnimationLoop();
        }


        function resetGame() {
             playerPokemon = null;
             opponentPokemon = null;
             isPlayerTurn = true;
             isBattleOver = false;
             attackInProgress = false;

             stopAnimationLoop(); // Stop 3D loop

             // Reset UI elements
             playerNameEl.textContent = "Player";
             playerHpTextEl.textContent = "HP: ??? / ???";
             playerHealthBarEl.style.width = '100%';
             playerHealthBarEl.className = 'health-bar high';
             playerSpriteEl.src = "";
             playerSpriteEl.style.opacity = 0;

             opponentNameEl.textContent = "Opponent";
             opponentHpTextEl.textContent = "HP: ??? / ???";
             opponentHealthBarEl.style.width = '100%';
             opponentHealthBarEl.className = 'health-bar high';
             opponentSpriteEl.src = "";
             opponentSpriteEl.style.opacity = 0;

             attackOptionsContainer.innerHTML = '';
             battleMessage.textContent = 'Select a Pokémon to start the battle!';
             gameOverOverlay.classList.remove('visible');

            gameContainer.classList.remove('battle-active');
            setTimeout(initializeSelection, 600);
        }

        function initializeSelection() {
            pokemonCardsContainer.innerHTML = ''; // Clear existing cards
            pokemonData.forEach(pokemon => {
                const card = document.createElement('div');
                // Add base class and type-specific class for styling hooks
                const typeClass = `type-${pokemon.type.toLowerCase()}`;
                card.classList.add('pokemon-card', typeClass);

                card.innerHTML = `
                    <div class="card-image-container">
                        <img src="${pokemon.spriteUrl}" alt="${pokemon.name}" class="card-image">
                    </div>
                    <div class="card-content">
                        <h3 class="card-name">${pokemon.name}</h3>
                        <div class="card-info">
                            <span class="card-type-chip">${pokemon.type}</span>
                            <span class="card-hp">HP: ${pokemon.maxHp}</span>
                        </div>
                        ${pokemon.description ? `<p class="card-description">${pokemon.description}</p>` : ''}
                    </div>
                    <div class="card-ripple"></div>
                `; // Added ripple element

                card.onclick = (e) => {
                     if (!gameContainer.classList.contains('battle-active') && !attackInProgress) {
                        // --- Ripple Effect ---
                        const ripple = card.querySelector('.card-ripple');
                        const rect = card.getBoundingClientRect();
                        const size = Math.max(rect.width, rect.height);
                        const x = e.clientX - rect.left - size / 2;
                        const y = e.clientY - rect.top - size / 2;
                        ripple.style.width = ripple.style.height = `${size}px`;
                        ripple.style.left = `${x}px`;
                        ripple.style.top = `${y}px`;
                        ripple.classList.add('active');
                        // Remove ripple effect after animation
                        setTimeout(() => ripple.classList.remove('active'), 600);
                        // --- Proceed to setup battle after short delay ---
                        setTimeout(() => setupBattle(pokemon), 150); // Slight delay for ripple visibility
                     }
                };
                pokemonCardsContainer.appendChild(card);
            });
        }
        
        // --- Event Listeners ---
        playAgainButton.addEventListener('click', resetGame);

        // --- Initial Load ---
        document.addEventListener('DOMContentLoaded', () => {
            initializeSelection();
            opponentSpriteEl.style.opacity = 0; // Hide sprites initially
            playerSpriteEl.style.opacity = 0;
        });

    </script>

</body>
</html>