Pokemon TCCNY GYM - Complete Implementation

# Pokemon-Style Gym Entrance Animation - Complete Implementation ## Overview A Pokemon-style animated intro scene where a trainer walks from a dock to a gym building, then the scene fades out with a classic iris wipe transition to reveal a hidden page behind it. ## Required Assets 1. **background.png** (1536x1024) - Top-down Pokemon-style town scene 2. **trainer.png** - Sprite sheet (4x4 grid, each frame 96x96 pixels) 3. **trees.png** - Animated tree sprite sheet (4 frames horizontal, each 120x200 pixels) --- ## COMPLETE HTML ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Pokemon TCCNY GYM</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } html, body { width: 100%; height: 100%; overflow: hidden; background: #1a1a2e; } .game-container { position: relative; width: 100vw; height: 100vh; overflow: hidden; clip-path: circle(150% at 50% 50%); } .game-container.iris-close { animation: irisClose 2s ease-in forwards; } @keyframes irisClose { 0% { clip-path: circle(100% at 50% 50%); } 100% { clip-path: circle(0% at 50% 50%); } } .background { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: fill; image-rendering: pixelated; } .sprite { position: absolute; background-image: url('trainer.png'); image-rendering: pixelated; z-index: 100; left: 0; top: 0; transition: opacity 0.6s ease-out; } .sprite.fade-out { opacity: 0; } .tree { position: absolute; left: 0; top: 0; background-image: url('trees.png'); image-rendering: pixelated; z-index: 50; } .shadow { position: absolute; z-index: 99; left: 0; top: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0; transition: opacity 0.6s ease-out; } .shadow.fade-out { opacity: 0; } .shadow-row { display: flex; gap: 0; } .shadow-pixel { background: transparent; } .shadow-pixel.filled { background: #1a1a1a; } .hidden-page { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 500; opacity: 0; pointer-events: none; transition: opacity 0.5s ease-in; } .hidden-page.visible { opacity: 1; pointer-events: auto; } .hidden-page h1 { font-family: 'Press Start 2P', monospace, sans-serif; font-size: 2.5rem; color: #e94560; text-shadow: 4px 4px 0 #0f3460; margin-bottom: 1rem; text-align: center; } .hidden-page p { font-family: 'Press Start 2P', monospace, sans-serif; font-size: 1rem; color: #eee; text-align: center; line-height: 2; } </style> </head> <body> <div class="hidden-page" id="hiddenPage"> <h1>TCCNY GYM</h1> <p>Welcome, Trainer!</p> </div> <div class="game-container" id="gameContainer"> <img src="background.png" class="background" id="background"> <div class="shadow" id="shadow"></div> <div class="sprite" id="trainer"></div> </div> <script> // ============================================ // CONSTANTS // ============================================ const ORIGINAL_WIDTH = 1536; const ORIGINAL_HEIGHT = 1024; const BASE_FRAME_WIDTH = 96; const BASE_FRAME_HEIGHT = 96; const BASE_TREE_WIDTH = 120; const BASE_TREE_HEIGHT = 200; const FRAME_SEQUENCE = [0, 1, 2, 3]; const ANIMATION_SPEED = 100; const DIRECTIONS = { down: 0, right: 1, left: 2, up: 3 }; // ============================================ // DOM ELEMENTS // ============================================ const gameContainer = document.getElementById('gameContainer'); const trainer = document.getElementById('trainer'); const shadow = document.getElementById('shadow'); const hiddenPage = document.getElementById('hiddenPage'); // ============================================ // TRAINER PATH (percentage coordinates) // ============================================ const PATH_PERCENT = [ { x: 24.5, y: 82 }, // Start on dock { x: 24.5, y: 75 }, // Walk up from dock { x: 24.5, y: 65 }, // Continue up path { x: 24.5, y: 55 }, // Approaching junction { x: 24.5, y: 47 }, // At junction { x: 35, y: 47 }, // Turn right { x: 50, y: 47 }, // At center junction { x: 50, y: 35 }, // Turn up toward gym { x: 50, y: 22 }, // Near gym entrance (TRIGGER FADE) { x: 50, y: 15 }, // At gym entrance ]; // ============================================ // TREE POSITIONS (percentage coordinates) // ============================================ const BORDER_TREES = [ // TOP LEFT { x: -15, y: -30 }, { x: -8, y: -28 }, { x: -1, y: -30 }, { x: 6, y: -28 }, { x: 13, y: -30 }, { x: 20, y: -28 }, { x: -12, y: -20 }, { x: -5, y: -18 }, { x: 2, y: -20 }, { x: 9, y: -18 }, { x: 16, y: -20 }, // TOP RIGHT { x: 68, y: -30 }, { x: 75, y: -28 }, { x: 82, y: -30 }, { x: 89, y: -28 }, { x: 96, y: -30 }, { x: 103, y: -28 }, { x: 71, y: -20 }, { x: 78, y: -18 }, { x: 85, y: -20 }, { x: 92, y: -18 }, { x: 99, y: -20 }, // LEFT BORDER { x: -20, y: -25 }, { x: -20, y: -15 }, { x: -20, y: -5 }, { x: -20, y: 5 }, { x: -20, y: 15 }, { x: -20, y: 25 }, { x: -13, y: -20 }, { x: -13, y: -10 }, { x: -13, y: 0 }, { x: -13, y: 10 }, { x: -13, y: 20 }, { x: -13, y: 30 }, // RIGHT BORDER { x: 96, y: -25 }, { x: 96, y: -15 }, { x: 96, y: -5 }, { x: 96, y: 5 }, { x: 96, y: 15 }, { x: 96, y: 25 }, { x: 103, y: -20 }, { x: 103, y: -10 }, { x: 103, y: 0 }, { x: 103, y: 10 }, { x: 103, y: 20 }, { x: 103, y: 30 }, ]; const PROMINENT_TREES = [ // Waterfront { x: 35, y: 34 }, { x: 43, y: 32 }, { x: 51, y: 34 }, { x: 59, y: 32 }, { x: 67, y: 34 }, { x: 75, y: 32 }, // Left side { x: -2, y: 5 }, // Right side { x: 97, y: 5 }, { x: 95, y: 18 }, // Other grass { x: 5, y: 25 }, ]; // ============================================ // SCALING SYSTEM (CRITICAL FOR TREES) // ============================================ // MUST store initial dimensions at page load BEFORE any resize const initialWidth = window.innerWidth; const initialHeight = window.innerHeight; const initialScale = Math.min(initialWidth / 800, initialHeight / 533); // Current dimensions (updated on resize) let scale = 1; let containerWidth = 0; let containerHeight = 0; function calculateDimensions() { containerWidth = window.innerWidth; containerHeight = window.innerHeight; scale = Math.min(containerWidth / 800, containerHeight / 533); } function percentToPixel(xPercent, yPercent) { return { x: (xPercent / 100) * containerWidth, y: (yPercent / 100) * containerHeight }; } // Trainer uses uniform scale function getScaledSpriteDimensions() { return { width: BASE_FRAME_WIDTH * scale, height: BASE_FRAME_HEIGHT * scale }; } // TREES use proportional scale (CRITICAL - do not change this formula) function getScaledTreeDimensions() { const width = BASE_TREE_WIDTH * initialScale * (containerWidth / initialWidth); const height = BASE_TREE_HEIGHT * initialScale * (containerHeight / initialHeight); return { width, height }; } // ============================================ // SHADOW // ============================================ const SHADOW_PATTERN = [ [0, 1, 1, 1, 1, 1, 1, 0], [1, 1, 1, 1, 1, 1, 1, 1], [0, 1, 1, 1, 1, 1, 1, 0], ]; function createPixelShadow() { shadow.innerHTML = ''; SHADOW_PATTERN.forEach(row => { const rowDiv = document.createElement('div'); rowDiv.className = 'shadow-row'; row.forEach(pixel => { const pixelDiv = document.createElement('div'); pixelDiv.className = 'shadow-pixel' + (pixel ? ' filled' : ''); rowDiv.appendChild(pixelDiv); }); shadow.appendChild(rowDiv); }); } function updateShadowSize() { const dims = getScaledSpriteDimensions(); const pixelSize = Math.max(2, Math.floor(dims.width / 16)); shadow.querySelectorAll('.shadow-pixel').forEach(p => { p.style.width = pixelSize + 'px'; p.style.height = pixelSize + 'px'; }); } // ============================================ // TREES // ============================================ const borderTreeElements = []; const prominentTreeElements = []; function createTrees() { BORDER_TREES.forEach((pos, index) => { const tree = document.createElement('div'); tree.className = 'tree'; tree.dataset.type = 'border'; tree.dataset.frame = Math.floor(Math.random() * 4); gameContainer.appendChild(tree); borderTreeElements.push(tree); }); PROMINENT_TREES.forEach((pos, index) => { const tree = document.createElement('div'); tree.className = 'tree'; tree.dataset.type = 'prominent'; gameContainer.appendChild(tree); prominentTreeElements.push(tree); }); updateTreePositions(); } function updateTreePositions() { const treeDims = getScaledTreeDimensions(); borderTreeElements.forEach((tree, index) => { const pos = BORDER_TREES[index]; const pixelPos = percentToPixel(pos.x, pos.y); tree.style.width = treeDims.width + 'px'; tree.style.height = treeDims.height + 'px'; tree.style.transform = `translate(${pixelPos.x}px, ${pixelPos.y}px)`; tree.style.backgroundSize = `${treeDims.width * 4}px ${treeDims.height}px`; const staticFrame = parseInt(tree.dataset.frame); tree.style.backgroundPosition = `${-staticFrame * treeDims.width}px 0`; }); prominentTreeElements.forEach((tree, index) => { const pos = PROMINENT_TREES[index]; const pixelPos = percentToPixel(pos.x, pos.y); tree.style.width = treeDims.width + 'px'; tree.style.height = treeDims.height + 'px'; tree.style.transform = `translate(${pixelPos.x}px, ${pixelPos.y}px)`; tree.style.backgroundSize = `${treeDims.width * 4}px ${treeDims.height}px`; }); } // Tree animation (prominent only) let treeFrame = 0; setInterval(() => { treeFrame = (treeFrame + 1) % 4; const treeDims = getScaledTreeDimensions(); prominentTreeElements.forEach(tree => { tree.style.backgroundPosition = `${-treeFrame * treeDims.width}px 0`; }); }, 350); // ============================================ // TRAINER // ============================================ let frame = 0; let pathIndex = 1; let xPercent = PATH_PERCENT[0].x; let yPercent = PATH_PERCENT[0].y; const SPEED_PERCENT = 0.22; let direction = 'down'; let startDelay = 60; let isMoving = false; function updateSpriteSize() { const dims = getScaledSpriteDimensions(); trainer.style.width = dims.width + 'px'; trainer.style.height = dims.height + 'px'; trainer.style.backgroundSize = `${dims.width * 4}px ${dims.height * 4}px`; } function updateSprite() { const dims = getScaledSpriteDimensions(); const row = DIRECTIONS[direction]; const col = FRAME_SEQUENCE[frame]; trainer.style.backgroundPosition = `${-col * dims.width}px ${-row * dims.height}px`; } function updateTrainerPosition() { const pixelPos = percentToPixel(xPercent, yPercent); const dims = getScaledSpriteDimensions(); trainer.style.transform = `translate(${pixelPos.x - dims.width / 2}px, ${pixelPos.y - dims.height}px)`; const pixelSize = Math.max(2, Math.floor(dims.width / 16)); const shadowWidth = pixelSize * 8; const shadowHeight = pixelSize * 3; shadow.style.transform = `translate(${pixelPos.x - shadowWidth / 2}px, ${pixelPos.y - shadowHeight / 2}px)`; } function getSegmentDirection(fromIdx, toIdx) { const dx = PATH_PERCENT[toIdx].x - PATH_PERCENT[fromIdx].x; const dy = PATH_PERCENT[toIdx].y - PATH_PERCENT[fromIdx].y; if (Math.abs(dy) >= 1) return dy > 0 ? 'down' : 'up'; return dx > 0 ? 'right' : 'left'; } // Walk animation setInterval(() => { if (isMoving) { frame = (frame + 1) % 4; updateSprite(); } }, ANIMATION_SPEED); // ============================================ // FADE SEQUENCE // ============================================ let fadeTriggered = false; function triggerGymEntrance() { if (fadeTriggered) return; fadeTriggered = true; trainer.classList.add('fade-out'); shadow.classList.add('fade-out'); setTimeout(() => { gameContainer.classList.add('iris-close'); }, 400); setTimeout(() => { gameContainer.style.visibility = 'hidden'; hiddenPage.classList.add('visible'); }, 2400); } // ============================================ // MAIN LOOP // ============================================ let lastWidth = 0; let lastHeight = 0; function move() { // Resize detection (CRITICAL for trees) if (lastWidth !== window.innerWidth || lastHeight !== window.innerHeight) { lastWidth = window.innerWidth; lastHeight = window.innerHeight; calculateDimensions(); updateTreePositions(); updateSpriteSize(); updateShadowSize(); } // Start delay if (startDelay > 0) { startDelay--; if (startDelay === 0) { direction = getSegmentDirection(0, 1); isMoving = true; } return; } if (pathIndex >= PATH_PERCENT.length) { isMoving = false; return; } // Trigger fade at waypoint 8 if (pathIndex === 8 && !fadeTriggered) { triggerGymEntrance(); } const target = PATH_PERCENT[pathIndex]; const dx = target.x - xPercent; const dy = target.y - yPercent; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < SPEED_PERCENT) { xPercent = target.x; yPercent = target.y; pathIndex++; if (pathIndex < PATH_PERCENT.length) { direction = getSegmentDirection(pathIndex - 1, pathIndex); } return; } xPercent += (dx / dist) * SPEED_PERCENT; yPercent += (dy / dist) * SPEED_PERCENT; updateTrainerPosition(); updateSprite(); } setInterval(move, 16); // ============================================ // RESIZE HANDLER // ============================================ function onResize() { requestAnimationFrame(() => { calculateDimensions(); updateSpriteSize(); updateShadowSize(); updateTreePositions(); updateTrainerPosition(); updateSprite(); }); } window.addEventListener('resize', onResize); // ============================================ // INITIALIZE // ============================================ calculateDimensions(); createTrees(); createPixelShadow(); updateSpriteSize(); updateShadowSize(); updateTrainerPosition(); updateSprite(); </script> </body> </html> ``` --- ## Key Technical Notes 1. **Tree Scaling Formula** (DO NOT MODIFY): ```javascript width = BASE_TREE_WIDTH * initialScale * (containerWidth / initialWidth); height = BASE_TREE_HEIGHT * initialScale * (containerHeight / initialHeight); ``` 2. **Background must use `object-fit: fill`** to stretch with viewport 3. **Trees positioned with `transform: translate()`** not left/top 4. **Resize detection in animation loop** keeps trees synced 5. **Shadow is pixel-art oval** (8x3 pattern) positioned under trainer's feet

Pokemon TCCNY GYM - Complete Implementation previewUse this prompt