diff --git a/.gitignore b/.gitignore index c79b2e4..c2658d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1 @@ node_modules/ -.DS_Store -*.log -.env -.env.local diff --git a/README.md b/README.md index a45bf89..5fd225e 100644 --- a/README.md +++ b/README.md @@ -1,146 +1,114 @@ -# 🎮 Platanus Hack 25: Arcade Challenge +# PLAT-MAN 🍌🦍 -At [Platanus Hack 25](https://hack.platan.us) we will have an arcade machine. While we could put some cool retro games on it, it is way better if it can be turned into a challenge. +Un juego arcade para Platanus Hack 25 donde un gorila escapa de agentes corporativos estresados mientras recolecta plátanos. -**Your mission:** Build the best arcade game using Phaser 3 (JS Game Lib) that will run on our physical arcade machine! +## 🎮 Descripción ---- +- **3 niveles** con dificultad creciente +- **Agentes corporativos** como enemigos (con trajes y maletines) +- **Bananas realistas** que dan poder temporal +- **Sistema de ranking** con Top 5 (almacenado en localStorage) +- **Música de fondo** generada con Web Audio API +- **Sprites procedurales** dibujados en runtime (sin imágenes externas) -## 🏆 Prizes +## 🎯 Controles -**🥇 First Place:** -- 💵 **$250 USD in cash** -- 🎟️ **A slot to participate in Platanus Hack** -- 🎮 **Your game featured on the arcade machine** +- **Flechas** o **WASD**: Mover al gorila +- **ESPACIO/ENTER**: Iniciar juego / Confirmar +- **↑↓**: Cambiar letra en ranking +- **←→**: Mover entre letras en ranking +- **R**: Reiniciar (durante el juego) -**🥈 Second Place:** -- 💵 **$100 USD in cash** -- 🎮 **Your game featured on the arcade machine** +## 📁 Estructura del Proyecto ---- - -## 📋 Restrictions - -Your game must comply with these technical restrictions: - -### Size Limit -- ✅ **Maximum 50KB after minification** (before gzip) -- The game code is automatically minified - focus on writing good code - -### Code Restrictions -- ✅ **Pure vanilla JavaScript only** - No `import` or `require` statements -- ✅ **No external URLs** - No `http://`, `https://`, or `//` (except `data:` URIs for base64) -- ✅ **No network calls** - No `fetch`, `XMLHttpRequest`, or similar APIs -- ✅ **Sandboxed environment** - Game runs in an iframe with no internet access - -### What You CAN Use -- ✅ **Phaser 3** (v3.87.0) - Loaded externally via CDN (not counted in size limit) -- ✅ **Base64-encoded images** - Using `data:` URIs -- ✅ **Procedurally generated graphics** - Using Phaser's Graphics API -- ✅ **Generated audio tones** - Using Phaser's Web Audio API -- ✅ **Canvas-based rendering and effects** - -# 🕹️ Controls - -Your game will run on a real arcade cabinet with physical joysticks and buttons! - -![Arcade Button Layout](https://hack.platan.us/assets/images/arcade/button-layout.webp) - -## Arcade Button Mapping - -The arcade cabinet sends specific key codes when buttons are pressed: - -**Player 1:** -- **Joystick**: `P1U`, `P1D`, `P1L`, `P1R` (Up, Down, Left, Right) -- **Joystick Diagonals**: `P1DL`, `P1DR` (Down-Left, Down-Right) -- **Action Buttons**: `P1A`, `P1B`, `P1C` (top row) / `P1X`, `P1Y`, `P1Z` (bottom row) -- **Start**: `START1` - -**Player 2:** -- **Joystick**: `P2U`, `P2D`, `P2L`, `P2R` -- **Joystick Diagonals**: `P2DL`, `P2DR` -- **Action Buttons**: `P2A`, `P2B`, `P2C` / `P2X`, `P2Y`, `P2Z` -- **Start**: `START2` - -## Testing Locally +``` +platanus-phaser-game/ +├── game.js # ✅ Código principal del juego (sin imports) +├── metadata.json # ✅ Nombre y descripción del juego +├── index.html # HTML con Phaser desde CDN +├── README.md # Este archivo +└── cover.png # (pendiente) Imagen 800x600px +``` -For local testing, you can map these arcade buttons to keyboard keys. The mapping supports **multiple keyboard keys per arcade button** (useful for alternatives like WASD + Arrow Keys). See `game.js` for the complete `ARCADE_CONTROLS` mapping template. +## ⚙️ Características Técnicas -By default: -- Player 1 uses **WASD** (joystick) and **U/I/O/J/K/L** (action buttons) -- Player 2 uses **Arrow Keys** (joystick) and **R/T/Y/F/G/H** (action buttons) +### Cumple con restricciones: +- ✅ **Sin imports**: JavaScript vanilla puro +- ✅ **Sin URLs externas** en game.js (Phaser desde CDN no cuenta) +- ✅ **Sin fetch/XMLHttpRequest** +- ✅ **Sprites procedurales**: Dibujados con Canvas API +- ✅ **Audio generado**: Usando Web Audio API de Phaser +- ✅ **Tamaño optimizado**: Código minificable -💡 **Tip**: Keep controls simple - design for joystick + 1-2 action buttons for the best arcade experience! +### Phaser 3 Features utilizados: +- `Phaser.Game` y configuración +- Physics (Arcade) +- Sprites y texturas procedurales +- Tweens para animaciones +- Keyboard input +- Groups y colisiones +- LocalStorage para persistencia ---- +## 🚀 Desarrollo -## ⏰ Deadline & Submission +### Instalar dependencias: +```bash +pnpm install +``` -**Deadline:** Sunday, November 10, 2025 at 23:59 (Santiago time) +### Ejecutar en desarrollo: +```bash +pnpm dev +``` -### How to Submit +### Verificar restricciones: +```bash +pnpm check-restrictions +``` -Submitting your project is easy: +## 🎨 Sprites -1. **Save your changes** - Make sure `game.js`, `metadata.json`, and `cover.png` are ready - - **Important:** Your game must include a custom `cover.png` file (800x600 pixels) showcasing your game -2. **Git push** - Push your code to your repository: - ```bash - git add . - git commit -m "Final submission" - git push - ``` -3. **Hit Submit** - Click the submit button in the development UI and follow the steps +Todos los sprites son generados proceduralmente en el código: -That's it! 🎉 +- **Gorila**: Cuerpo completo con brazos, piernas, expresión facial +- **Agentes**: Oficinistas con traje, corbata, maletín, expresiones (serio/asustado) +- **Bananas**: Realistas con gradientes, brillos y manchas maduras +- **Paredes**: Tiles con efectos visuales por nivel ---- +## 🎵 Audio -## 🚀 Quick Start +Música de fondo simple generada con osciladores (notas: C, E, G, E). -### 1. Install Dependencies -```bash -pnpm install -``` +## 🏆 Sistema de Ranking -### 2. Start Development Server -```bash -pnpm dev -``` -This starts a server at `http://localhost:3000` with live restriction checking. +- Top 5 mejores puntuaciones +- Iniciales de 3 letras +- Colores especiales (oro, plata, bronce) +- Guardado en localStorage -### 3. Build Your Game -- **Edit `game.js`** - Write your arcade game code -- **Update `metadata.json`** - Set your game name and description -- **Create `cover.png`** - Design an 800x600 pixel cover image for your game -- **Watch the dev server** - It shows live updates on file size and restrictions +## 📊 Niveles ---- +1. **Etapa 1** (Azul): Velocidad de agentes: 120 +2. **Etapa 2** (Púrpura): Velocidad de agentes: 150 +3. **Etapa 3** (Naranja): Velocidad de agentes: 180 -## 🤖 Vibecoding Your Game +## 🎯 Objetivo del Juego -This challenge is designed for **vibecoding** - building your game with AI assistance! +Recolecta los 6 plátanos en cada nivel mientras evitas a los agentes corporativos. Cuando recolectas un plátano, los agentes se estresan y puedes comerlos por 4 segundos. Completa las 3 etapas para ganar. -### What We've Set Up For You +## 📝 Puntuación -- **`AGENTS.md`** - Pre-configured instructions so your IDE (Cursor, Windsurf, etc.) understands the challenge -- **`docs/phaser-quick-start.md`** - Quick reference guide for Phaser 3 -- **`docs/phaser-api.md`** - Comprehensive Phaser 3 API documentation +- **10 puntos** por plátano +- **50 puntos** por agente comido +- **1 punto** por segundo sobrevivido -Your AI agent already knows: -- ✅ All the challenge restrictions -- ✅ How to use Phaser 3 effectively -- ✅ Best practices for staying under 50KB -- ✅ What files to edit (`game.js` and `metadata.json` only) +## 🔧 Próximos Pasos -### How to Vibecode +- [ ] Crear `cover.png` (800x600px) +- [ ] Ejecutar `pnpm check-restrictions` para verificar tamaño +- [ ] Optimizar código si excede 50KB -Simply tell your AI assistant what game you want to build! For example: +## 👥 Créditos -> "Create a Space Invaders clone with colorful enemies" -> -> "Build a flappy bird style game with procedural graphics" -> -> "Make a breakout game with power-ups" +Juego creado para **Platanus Hack 25: Arcade Challenge** -Your AI will handle the implementation, keeping everything within the restrictions automatically! diff --git a/cover.png b/cover.png index 717f038..6cc654a 100644 Binary files a/cover.png and b/cover.png differ diff --git a/create-cover-v2.html b/create-cover-v2.html new file mode 100644 index 0000000..8f81356 --- /dev/null +++ b/create-cover-v2.html @@ -0,0 +1,410 @@ + + + + + PLAT-MAN Cover - Professional + + + + + + + + + + diff --git a/create-cover.html b/create-cover.html new file mode 100644 index 0000000..f8dd396 --- /dev/null +++ b/create-cover.html @@ -0,0 +1,303 @@ + + + + + Generate Cover + + + +
+ + + + + + diff --git a/game.js b/game.js index 5987201..542817a 100644 --- a/game.js +++ b/game.js @@ -1,85 +1,107 @@ -// Platanus Hack 25: Snake Game -// Navigate the snake around the "PLATANUS HACK ARCADE" title made of blocks! - -// ============================================================================= -// ARCADE BUTTON MAPPING - COMPLETE TEMPLATE -// ============================================================================= -// Reference: See button-layout.webp at hack.platan.us/assets/images/arcade/ -// -// Maps arcade button codes to keyboard keys for local testing. -// Each arcade code can map to multiple keyboard keys (array values). -// The arcade cabinet sends codes like 'P1U', 'P1A', etc. when buttons are pressed. -// -// To use in your game: -// if (key === 'P1U') { ... } // Works on both arcade and local (via keyboard) -// -// CURRENT GAME USAGE (Snake): -// - P1U/P1D/P1L/P1R (Joystick) → Snake Direction -// - P1A (Button A) or START1 (Start Button) → Restart Game -// ============================================================================= - -const ARCADE_CONTROLS = { - // ===== PLAYER 1 CONTROLS ===== - // Joystick - Left hand on WASD - 'P1U': ['w'], - 'P1D': ['s'], - 'P1L': ['a'], - 'P1R': ['d'], - 'P1DL': null, // Diagonal down-left (no keyboard default) - 'P1DR': null, // Diagonal down-right (no keyboard default) - - // Action Buttons - Right hand on home row area (ergonomic!) - // Top row (ABC): U, I, O | Bottom row (XYZ): J, K, L - 'P1A': ['u'], - 'P1B': ['i'], - 'P1C': ['o'], - 'P1X': ['j'], - 'P1Y': ['k'], - 'P1Z': ['l'], - - // Start Button - 'START1': ['1', 'Enter'], - - // ===== PLAYER 2 CONTROLS ===== - // Joystick - Right hand on Arrow Keys - 'P2U': ['ArrowUp'], - 'P2D': ['ArrowDown'], - 'P2L': ['ArrowLeft'], - 'P2R': ['ArrowRight'], - 'P2DL': null, // Diagonal down-left (no keyboard default) - 'P2DR': null, // Diagonal down-right (no keyboard default) - - // Action Buttons - Left hand (avoiding P1's WASD keys) - // Top row (ABC): R, T, Y | Bottom row (XYZ): F, G, H - 'P2A': ['r'], - 'P2B': ['t'], - 'P2C': ['y'], - 'P2X': ['f'], - 'P2Y': ['g'], - 'P2Z': ['h'], - - // Start Button - 'START2': ['2'] -}; +/** + * PLAT-MAN - Arcade Game + * Un gorila escapa de agentes corporativos estresados mientras recolecta plátanos + */ -// Build reverse lookup: keyboard key → arcade button code -const KEYBOARD_TO_ARCADE = {}; -for (const [arcadeCode, keyboardKeys] of Object.entries(ARCADE_CONTROLS)) { - if (keyboardKeys) { - // Handle both array and single value - const keys = Array.isArray(keyboardKeys) ? keyboardKeys : [keyboardKeys]; - keys.forEach(key => { - KEYBOARD_TO_ARCADE[key] = arcadeCode; - }); +// ================ CONFIGURACIÓN GLOBAL ================ +const W = 800; +const H = 600; +const POWER_DURATION_MS = 4000; +const BANANAS_TOTAL = 6; +const GHOST_COUNT = 4; +const RANKING_KEY = 'platman_ranking'; + +// Configuración de niveles +const LEVEL_CONFIG = { + 1: { + bgColor: 0x0f0f14, + wallColor: "#1e2aff", + wallStroke: "#5360ff", + ghostSpeed: 120, + name: "Etapa 1", + walls: [ + [W/2, 40, W-40, 18], [W/2, H-40, W-40, 18], + [40, H/2, 18, H-80], [W-40, H/2, 18, H-80], + [W/2, 200, 320, 18], [W/2, 300, 18, 240], + [250, 430, 270, 18], [550, 430, 270, 18], + [150, 260, 18, 160], [650, 260, 18, 160] + ] + }, + 2: { + bgColor: 0x1a0a1a, + wallColor: "#8e2aff", + wallStroke: "#b360ff", + ghostSpeed: 150, + name: "Etapa 2", + walls: [ + [W/2, 40, W-40, 18], [W/2, H-40, W-40, 18], + [40, H/2, 18, H-80], [W-40, H/2, 18, H-80], + [200, 150, 18, 200], [600, 150, 18, 200], + [200, 450, 18, 200], [600, 450, 18, 200], + [W/2, 300, 200, 18], [300, 200, 200, 18], + [500, 400, 200, 18] + ] + }, + 3: { + bgColor: 0x1a140a, + wallColor: "#ff6e2a", + wallStroke: "#ff9060", + ghostSpeed: 180, + name: "Etapa 3", + walls: [ + [W/2, 40, W-40, 18], [W/2, H-40, W-40, 18], + [40, H/2, 18, H-80], [W-40, H/2, 18, H-80], + [250, 200, 300, 18], [550, 400, 300, 18], + [W/2, H/2, 100, 100], [200, 350, 18, 180], + [600, 250, 18, 180], [350, 150, 18, 120], + [450, 450, 18, 120] + ] } -} +}; + +// ================ VARIABLES GLOBALES ================ +let gameState = 'start'; // 'start', 'playing', 'gameOver', 'ranking' +let currentLevel = 1; +let score = 0; +let startTime = 0; +let powerMode = false; +let powerExpiresAt = 0; +let isGameOver = false; + +// Objetos del juego +let player; +let cursors; +let wasdKeys; +let bananas; +let ghosts; +let wallBodies; +let wallVisuals; +let bgRect; +let scoreText; +let levelText; +let bgMusicTimer; + +// UI de ranking +let rankingInputs = []; +let currentInitials = ['A', 'A', 'A']; +let selectedIndex = 0; +let finalScore = 0; +let wonGame = false; +// ================ CONFIGURACIÓN DE PHASER ================ const config = { type: Phaser.AUTO, - width: 800, - height: 600, - backgroundColor: '#000000', + width: W, + height: H, + parent: 'game-container', + backgroundColor: "#000000", + render: { pixelArt: true, roundPixels: true }, + physics: { + default: "arcade", + arcade: { debug: false, gravity: { y: 0 } } + }, scene: { + preload: preload, create: create, update: update } @@ -87,350 +109,1233 @@ const config = { const game = new Phaser.Game(config); -// Game variables -let snake = []; -let snakeSize = 15; -let direction = { x: 1, y: 0 }; -let nextDirection = { x: 1, y: 0 }; -let food; -let score = 0; -let scoreText; -let titleBlocks = []; -let gameOver = false; -let moveTimer = 0; -let moveDelay = 100; // Faster initial speed (was 150ms) -let graphics; - -// Pixel font patterns (5x5 grid for each letter) -const letters = { - P: [[1,1,1,1],[1,0,0,1],[1,1,1,1],[1,0,0,0],[1,0,0,0]], - L: [[1,0,0,0],[1,0,0,0],[1,0,0,0],[1,0,0,0],[1,1,1,1]], - A: [[0,1,1,0],[1,0,0,1],[1,1,1,1],[1,0,0,1],[1,0,0,1]], - T: [[1,1,1,1],[0,1,0,0],[0,1,0,0],[0,1,0,0],[0,1,0,0]], - N: [[1,0,0,1],[1,1,0,1],[1,0,1,1],[1,0,0,1],[1,0,0,1]], - U: [[1,0,0,1],[1,0,0,1],[1,0,0,1],[1,0,0,1],[1,1,1,1]], - S: [[0,1,1,1],[1,0,0,0],[0,1,1,0],[0,0,0,1],[1,1,1,0]], - H: [[1,0,0,1],[1,0,0,1],[1,1,1,1],[1,0,0,1],[1,0,0,1]], - C: [[0,1,1,1],[1,0,0,0],[1,0,0,0],[1,0,0,0],[0,1,1,1]], - K: [[1,0,0,1],[1,0,1,0],[1,1,0,0],[1,0,1,0],[1,0,0,1]], - '2': [[1,1,1,0],[0,0,0,1],[0,1,1,0],[1,0,0,0],[1,1,1,1]], - '5': [[1,1,1,1],[1,0,0,0],[1,1,1,0],[0,0,0,1],[1,1,1,0]], - ':': [[0,0,0,0],[0,1,0,0],[0,0,0,0],[0,1,0,0],[0,0,0,0]], - R: [[1,1,1,0],[1,0,0,1],[1,1,1,0],[1,0,1,0],[1,0,0,1]], - D: [[1,1,1,0],[1,0,0,1],[1,0,0,1],[1,0,0,1],[1,1,1,0]], - E: [[1,1,1,1],[1,0,0,0],[1,1,1,0],[1,0,0,0],[1,1,1,1]] -}; - -// Bold font for ARCADE (filled/solid style) -const boldLetters = { - A: [[1,1,1,1,1],[1,1,0,1,1],[1,1,1,1,1],[1,1,0,1,1],[1,1,0,1,1]], - R: [[1,1,1,1,0],[1,1,0,1,1],[1,1,1,1,0],[1,1,0,1,1],[1,1,0,1,1]], - C: [[1,1,1,1,1],[1,1,0,0,0],[1,1,0,0,0],[1,1,0,0,0],[1,1,1,1,1]], - D: [[1,1,1,1,0],[1,1,0,1,1],[1,1,0,1,1],[1,1,0,1,1],[1,1,1,1,0]], - E: [[1,1,1,1,1],[1,1,0,0,0],[1,1,1,1,0],[1,1,0,0,0],[1,1,1,1,1]] -}; +// ================ PRELOAD ================ +function preload() { + createAllSprites(this); +} +// ================ CREATE ================ function create() { - const scene = this; - graphics = this.add.graphics(); - - // Build "PLATANUS HACK ARCADE" in cyan - centered and grid-aligned - // PLATANUS: 8 letters × (4 cols + 1 spacing) = 40 blocks, but last letter no spacing = 39 blocks × 15px = 585px - let x = Math.floor((800 - 585) / 2 / snakeSize) * snakeSize; - let y = Math.floor(180 / snakeSize) * snakeSize; - 'PLATANUS'.split('').forEach(char => { - x = drawLetter(char, x, y, 0x00ffff); - }); + this.scene = this; + showStartScreen(this); +} - // HACK: 4 letters × (4 cols + 1 spacing) = 20 blocks, but last letter no spacing = 19 blocks × 15px = 285px - x = Math.floor((800 - 285) / 2 / snakeSize) * snakeSize; - y = Math.floor(280 / snakeSize) * snakeSize; - 'HACK'.split('').forEach(char => { - x = drawLetter(char, x, y, 0x00ffff); - }); +// ================ UPDATE ================ +function update() { + if (gameState === 'playing') { + updateGame(this); + } +} + +// ================ CREACIÓN DE SPRITES ================ +function createAllSprites(scene) { + const drawTex = (key, w, h, draw) => { + const tex = scene.textures.createCanvas(key, w, h); + const ctx = tex.getContext(); + ctx.clearRect(0, 0, w, h); + draw(ctx, w, h); + tex.refresh(); + }; - // ARCADE: 6 letters × (5 cols + 1 spacing) = 36 blocks, but last letter no spacing = 35 blocks × 15px = 525px - x = Math.floor((800 - 525) / 2 / snakeSize) * snakeSize; - y = Math.floor(380 / snakeSize) * snakeSize; - 'ARCADE'.split('').forEach(char => { - x = drawLetter(char, x, y, 0xff00ff, true); + // Gorila del logo (pantalla inicio) + drawTex("gorilla_logo", 96, 96, (ctx, w, h) => { + ctx.save(); + const cx = 48, cy = 48; + // Sombra + ctx.fillStyle = "rgba(0,0,0,0.4)"; + ctx.beginPath(); + ctx.ellipse(cx, 86, 24, 8, 0, 0, Math.PI*2); + ctx.fill(); + // Piernas + const legGrad = ctx.createLinearGradient(0, 70, 0, 82); + legGrad.addColorStop(0, "#5a3a22"); + legGrad.addColorStop(1, "#3e2816"); + ctx.fillStyle = legGrad; + ctx.beginPath(); + ctx.ellipse(38, 74, 8, 12, 0.15, 0, Math.PI*2); + ctx.fill(); + ctx.beginPath(); + ctx.ellipse(58, 74, 8, 12, -0.15, 0, Math.PI*2); + ctx.fill(); + // Cuerpo + const bodyGrad = ctx.createRadialGradient(cx, 50, 10, cx, 55, 24); + bodyGrad.addColorStop(0, "#6b4423"); + bodyGrad.addColorStop(0.6, "#4a2f1a"); + bodyGrad.addColorStop(1, "#2e1c0f"); + ctx.fillStyle = bodyGrad; + ctx.beginPath(); + ctx.ellipse(cx, 55, 22, 20, 0, 0, Math.PI*2); + ctx.fill(); + // Pecho + const chestGrad = ctx.createRadialGradient(cx, 55, 5, cx, 58, 14); + chestGrad.addColorStop(0, "#8d6a4c"); + chestGrad.addColorStop(1, "#6b4423"); + ctx.fillStyle = chestGrad; + ctx.beginPath(); + ctx.ellipse(cx, 58, 14, 11, 0, 0, Math.PI*2); + ctx.fill(); + // Brazos + const armGrad = ctx.createLinearGradient(0, 48, 0, 72); + armGrad.addColorStop(0, "#5a3a22"); + armGrad.addColorStop(1, "#3e2816"); + ctx.fillStyle = armGrad; + ctx.beginPath(); + ctx.ellipse(26, 58, 8, 22, 0.25, 0, Math.PI*2); + ctx.fill(); + ctx.beginPath(); + ctx.ellipse(23, 74, 6, 8, 0, 0, Math.PI*2); + ctx.fill(); + ctx.beginPath(); + ctx.ellipse(70, 58, 8, 22, -0.25, 0, Math.PI*2); + ctx.fill(); + ctx.beginPath(); + ctx.ellipse(73, 74, 6, 8, 0, 0, Math.PI*2); + ctx.fill(); + // Hombros + ctx.fillStyle = bodyGrad; + ctx.beginPath(); + ctx.ellipse(34, 46, 10, 9, -0.2, 0, Math.PI*2); + ctx.fill(); + ctx.beginPath(); + ctx.ellipse(62, 46, 10, 9, 0.2, 0, Math.PI*2); + ctx.fill(); + // Cabeza + const headGrad = ctx.createRadialGradient(cx, 32, 8, cx, 34, 20); + headGrad.addColorStop(0, "#8f6241"); + headGrad.addColorStop(1, "#543720"); + ctx.fillStyle = headGrad; + ctx.beginPath(); + ctx.ellipse(cx, 34, 20, 18, 0, 0, Math.PI*2); + ctx.fill(); + // Orejas + ctx.fillStyle = "#6b4423"; + ctx.beginPath(); + ctx.ellipse(30, 32, 6, 7, 0, 0, Math.PI*2); + ctx.fill(); + ctx.beginPath(); + ctx.ellipse(66, 32, 6, 7, 0, 0, Math.PI*2); + ctx.fill(); + // Hocico + const muzzleGrad = ctx.createRadialGradient(cx, 42, 4, cx, 44, 12); + muzzleGrad.addColorStop(0, "#caa17d"); + muzzleGrad.addColorStop(1, "#8d6a4c"); + ctx.fillStyle = muzzleGrad; + ctx.beginPath(); + ctx.ellipse(cx, 44, 12, 10, 0, 0, Math.PI*2); + ctx.fill(); + // Nariz + ctx.fillStyle = "#2e1c0f"; + ctx.beginPath(); + ctx.ellipse(cx, 42, 4, 3, 0, 0, Math.PI*2); + ctx.fill(); + // Ojos + ctx.fillStyle = "#ffffff"; + ctx.beginPath(); + ctx.ellipse(40, 34, 4, 5, 0, 0, Math.PI*2); + ctx.fill(); + ctx.beginPath(); + ctx.ellipse(56, 34, 4, 5, 0, 0, Math.PI*2); + ctx.fill(); + // Pupilas + ctx.fillStyle = "#1a1a1a"; + ctx.beginPath(); + ctx.arc(40, 35, 2, 0, Math.PI*2); + ctx.fill(); + ctx.beginPath(); + ctx.arc(56, 35, 2, 0, Math.PI*2); + ctx.fill(); + // Cejas + ctx.strokeStyle = "#2e1c0f"; + ctx.lineWidth = 3; + ctx.lineCap = "round"; + ctx.beginPath(); + ctx.moveTo(34, 30); + ctx.quadraticCurveTo(38, 28, 44, 30); + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(52, 30); + ctx.quadraticCurveTo(56, 28, 62, 30); + ctx.stroke(); + // Boca + ctx.strokeStyle = "#4a2f1a"; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.arc(cx, 48, 6, 0.2, Math.PI - 0.2); + ctx.stroke(); + ctx.restore(); }); - // Score display - scoreText = this.add.text(16, 16, 'Score: 0', { - fontSize: '24px', - fontFamily: 'Arial, sans-serif', - color: '#00ff00' + // Gorila del juego (más pequeño) + drawTex("gorilla32b", 48, 48, (ctx, w, h) => { + ctx.save(); + const cx = 24, cy = 24; + // Sombra + ctx.fillStyle = "rgba(0,0,0,0.4)"; + ctx.beginPath(); + ctx.ellipse(cx, 43, 12, 4, 0, 0, Math.PI*2); + ctx.fill(); + // Piernas + const legGrad = ctx.createLinearGradient(0, 35, 0, 41); + legGrad.addColorStop(0, "#5a3a22"); + legGrad.addColorStop(1, "#3e2816"); + ctx.fillStyle = legGrad; + ctx.beginPath(); + ctx.ellipse(19, 37, 4, 6, 0.15, 0, Math.PI*2); + ctx.fill(); + ctx.beginPath(); + ctx.ellipse(29, 37, 4, 6, -0.15, 0, Math.PI*2); + ctx.fill(); + // Cuerpo + const bodyGrad = ctx.createRadialGradient(cx, 25, 5, cx, 28, 12); + bodyGrad.addColorStop(0, "#6b4423"); + bodyGrad.addColorStop(0.6, "#4a2f1a"); + bodyGrad.addColorStop(1, "#2e1c0f"); + ctx.fillStyle = bodyGrad; + ctx.beginPath(); + ctx.ellipse(cx, 28, 11, 10, 0, 0, Math.PI*2); + ctx.fill(); + // Pecho + const chestGrad = ctx.createRadialGradient(cx, 28, 2, cx, 29, 7); + chestGrad.addColorStop(0, "#8d6a4c"); + chestGrad.addColorStop(1, "#6b4423"); + ctx.fillStyle = chestGrad; + ctx.beginPath(); + ctx.ellipse(cx, 29, 7, 5, 0, 0, Math.PI*2); + ctx.fill(); + // Brazos + const armGrad = ctx.createLinearGradient(0, 24, 0, 36); + armGrad.addColorStop(0, "#5a3a22"); + armGrad.addColorStop(1, "#3e2816"); + ctx.fillStyle = armGrad; + ctx.beginPath(); + ctx.ellipse(13, 29, 4, 11, 0.25, 0, Math.PI*2); + ctx.fill(); + ctx.beginPath(); + ctx.ellipse(11, 37, 3, 4, 0, 0, Math.PI*2); + ctx.fill(); + ctx.beginPath(); + ctx.ellipse(35, 29, 4, 11, -0.25, 0, Math.PI*2); + ctx.fill(); + ctx.beginPath(); + ctx.ellipse(37, 37, 3, 4, 0, 0, Math.PI*2); + ctx.fill(); + // Hombros + ctx.fillStyle = bodyGrad; + ctx.beginPath(); + ctx.ellipse(17, 23, 5, 4, -0.2, 0, Math.PI*2); + ctx.fill(); + ctx.beginPath(); + ctx.ellipse(31, 23, 5, 4, 0.2, 0, Math.PI*2); + ctx.fill(); + // Cabeza + const headGrad = ctx.createRadialGradient(cx, 16, 4, cx, 17, 9); + headGrad.addColorStop(0, "#8f6241"); + headGrad.addColorStop(1, "#543720"); + ctx.fillStyle = headGrad; + ctx.beginPath(); + ctx.ellipse(cx, 17, 10, 8, 0, 0, Math.PI*2); + ctx.fill(); + // Orejas + ctx.fillStyle = "#6b4423"; + ctx.beginPath(); + ctx.ellipse(18, 16, 3, 4, 0, 0, Math.PI*2); + ctx.fill(); + ctx.beginPath(); + ctx.ellipse(30, 16, 3, 4, 0, 0, Math.PI*2); + ctx.fill(); + // Hocico + const muzzleGrad = ctx.createRadialGradient(cx, 20, 2, cx, 22, 6); + muzzleGrad.addColorStop(0, "#caa17d"); + muzzleGrad.addColorStop(1, "#8d6a4c"); + ctx.fillStyle = muzzleGrad; + ctx.beginPath(); + ctx.ellipse(cx, 22, 6, 5, 0, 0, Math.PI*2); + ctx.fill(); + // Nariz + ctx.fillStyle = "#2e1c0f"; + ctx.beginPath(); + ctx.ellipse(cx, 20, 2, 1.5, 0, 0, Math.PI*2); + ctx.fill(); + // Ojos + ctx.fillStyle = "#ffffff"; + ctx.beginPath(); + ctx.ellipse(20, 17, 2, 2.5, 0, 0, Math.PI*2); + ctx.fill(); + ctx.beginPath(); + ctx.ellipse(28, 17, 2, 2.5, 0, 0, Math.PI*2); + ctx.fill(); + // Pupilas + ctx.fillStyle = "#1a1a1a"; + ctx.beginPath(); + ctx.arc(20, 17.5, 1, 0, Math.PI*2); + ctx.fill(); + ctx.beginPath(); + ctx.arc(28, 17.5, 1, 0, Math.PI*2); + ctx.fill(); + // Cejas + ctx.strokeStyle = "#2e1c0f"; + ctx.lineWidth = 1.5; + ctx.lineCap = "round"; + ctx.beginPath(); + ctx.moveTo(18, 14); + ctx.quadraticCurveTo(20, 13.5, 23, 14); + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(25, 14); + ctx.quadraticCurveTo(27, 13.5, 30, 14); + ctx.stroke(); + // Boca + ctx.strokeStyle = "#4a2f1a"; + ctx.lineWidth = 1.5; + ctx.beginPath(); + ctx.arc(cx, 24, 3, 0.2, Math.PI - 0.2); + ctx.stroke(); + ctx.restore(); }); - // Instructions - this.add.text(400, 560, 'Use Joystick to Move | Avoid Walls, Yourself & The Title!', { - fontSize: '16px', - fontFamily: 'Arial, sans-serif', - color: '#888888', - align: 'center' - }).setOrigin(0.5); + // Banana realista - forma alargada en C + drawTex("banana32", 52, 20, (ctx, w, h) => { + ctx.save(); + // Gradiente amarillo más natural + const mainGrad = ctx.createLinearGradient(6, 4, 46, 16); + mainGrad.addColorStop(0, "#F4E04D"); + mainGrad.addColorStop(0.15, "#FFF176"); + mainGrad.addColorStop(0.4, "#FFD54F"); + mainGrad.addColorStop(0.7, "#FFC107"); + mainGrad.addColorStop(0.9, "#FFB300"); + mainGrad.addColorStop(1, "#D4A017"); + ctx.fillStyle = mainGrad; + + // Forma alargada de plátano en C + ctx.beginPath(); + // Empezar desde el tallo (izquierda superior) + ctx.moveTo(6, 8); + // Curva superior (parte convexa del plátano) + ctx.bezierCurveTo(10, 3, 24, 2, 38, 4); + ctx.bezierCurveTo(44, 5, 48, 7, 46, 10); + // Punta derecha + ctx.lineTo(45, 11); + // Curva inferior (parte cóncava del plátano) + ctx.bezierCurveTo(43, 13, 28, 17, 14, 16); + ctx.bezierCurveTo(8, 15, 5, 12, 6, 8); + ctx.closePath(); + ctx.fill(); + + // Sombra en el lado cóncavo + const shadowGrad = ctx.createLinearGradient(12, 8, 12, 16); + shadowGrad.addColorStop(0, "rgba(255, 160, 0, 0)"); + shadowGrad.addColorStop(0.5, "rgba(184, 134, 11, 0.25)"); + shadowGrad.addColorStop(1, "rgba(139, 101, 8, 0.4)"); + ctx.fillStyle = shadowGrad; + ctx.beginPath(); + ctx.moveTo(10, 10); + ctx.quadraticCurveTo(20, 14, 32, 13); + ctx.quadraticCurveTo(38, 12, 42, 11); + ctx.quadraticCurveTo(30, 12, 18, 12); + ctx.quadraticCurveTo(13, 11, 10, 10); + ctx.fill(); + + // Brillo en la parte convexa + const highlightGrad = ctx.createRadialGradient(28, 6, 1, 28, 6, 12); + highlightGrad.addColorStop(0, "rgba(255, 255, 255, 0.7)"); + highlightGrad.addColorStop(0.5, "rgba(255, 255, 220, 0.3)"); + highlightGrad.addColorStop(1, "rgba(255, 255, 255, 0)"); + ctx.fillStyle = highlightGrad; + ctx.beginPath(); + ctx.ellipse(28, 7, 10, 3, -0.2, 0, Math.PI * 2); + ctx.fill(); + + // Manchas marrones características (más realistas) + ctx.fillStyle = "rgba(101, 67, 33, 0.35)"; + // Mancha 1 + ctx.beginPath(); + ctx.ellipse(18, 10, 2.5, 1.2, 0.3, 0, Math.PI * 2); + ctx.fill(); + // Mancha 2 + ctx.beginPath(); + ctx.ellipse(28, 12, 1.8, 1, -0.2, 0, Math.PI * 2); + ctx.fill(); + // Mancha 3 + ctx.beginPath(); + ctx.ellipse(36, 9, 2, 1, 0.4, 0, Math.PI * 2); + ctx.fill(); + // Mancha 4 (pequeña) + ctx.beginPath(); + ctx.ellipse(14, 13, 1.2, 0.7, 0.6, 0, Math.PI * 2); + ctx.fill(); + // Mancha 5 (pequeña) + ctx.beginPath(); + ctx.ellipse(40, 11, 1.5, 0.8, -0.5, 0, Math.PI * 2); + ctx.fill(); + + // Tallo en el extremo izquierdo + ctx.fillStyle = "#8B7355"; + ctx.beginPath(); + ctx.moveTo(5, 8); + ctx.lineTo(3, 7); + ctx.lineTo(3, 9); + ctx.lineTo(5, 9); + ctx.fill(); + + // Contorno sutil + ctx.strokeStyle = "#B8860B"; + ctx.lineWidth = 0.8; + ctx.beginPath(); + ctx.moveTo(6, 8); + ctx.bezierCurveTo(10, 3, 24, 2, 38, 4); + ctx.bezierCurveTo(44, 5, 48, 7, 46, 10); + ctx.lineTo(45, 11); + ctx.bezierCurveTo(43, 13, 28, 17, 14, 16); + ctx.bezierCurveTo(8, 15, 5, 12, 6, 8); + ctx.stroke(); + + // Línea central característica del plátano + ctx.strokeStyle = "rgba(184, 134, 11, 0.3)"; + ctx.lineWidth = 0.5; + ctx.beginPath(); + ctx.moveTo(8, 10); + ctx.quadraticCurveTo(24, 9, 42, 10); + ctx.stroke(); + + ctx.restore(); + }); - // Initialize snake (start top left) - snake = [ - { x: 75, y: 60 }, - { x: 60, y: 60 }, - { x: 45, y: 60 } - ]; + // Oficinistas/Agentes + const agentTex = (key, suitColor, stressed) => { + drawTex(key, 48, 48, (ctx, w, h) => { + ctx.save(); + const cx = 24; + // Piernas + ctx.fillStyle = stressed ? "#1a1a1a" : "#2c2c2c"; + ctx.fillRect(16, 34, 6, 12); + ctx.fillRect(26, 34, 6, 12); + // Zapatos + ctx.fillStyle = "#000000"; + ctx.fillRect(14, 44, 8, 3); + ctx.fillRect(26, 44, 8, 3); + // Traje + const suitGrad = ctx.createLinearGradient(cx, 20, cx, 36); + suitGrad.addColorStop(0, suitColor); + suitGrad.addColorStop(1, stressed ? "#1a0000" : "#000033"); + ctx.fillStyle = suitGrad; + ctx.fillRect(14, 20, 20, 16); + // Camisa + ctx.fillStyle = "#f5f5f5"; + ctx.beginPath(); + ctx.moveTo(24, 22); + ctx.lineTo(20, 26); + ctx.lineTo(20, 34); + ctx.lineTo(28, 34); + ctx.lineTo(28, 26); + ctx.closePath(); + ctx.fill(); + // Corbata + ctx.fillStyle = stressed ? "#8B0000" : "#003366"; + ctx.beginPath(); + ctx.moveTo(24, 24); + ctx.lineTo(22, 26); + ctx.lineTo(22, 33); + ctx.lineTo(24, 34); + ctx.lineTo(26, 33); + ctx.lineTo(26, 26); + ctx.closePath(); + ctx.fill(); + // Brazos + ctx.fillStyle = suitColor; + ctx.fillRect(10, 24, 4, 10); + ctx.fillRect(34, 24, 4, 10); + // Manos + ctx.fillStyle = "#ffd4a3"; + ctx.beginPath(); + ctx.arc(12, 34, 3, 0, Math.PI * 2); + ctx.fill(); + ctx.beginPath(); + ctx.arc(36, 34, 3, 0, Math.PI * 2); + ctx.fill(); + // Cabeza + const skinGrad = ctx.createRadialGradient(cx, 14, 3, cx, 14, 8); + skinGrad.addColorStop(0, "#ffe0bd"); + skinGrad.addColorStop(1, "#ffd4a3"); + ctx.fillStyle = skinGrad; + ctx.beginPath(); + ctx.arc(cx, 14, 8, 0, Math.PI * 2); + ctx.fill(); + // Pelo + ctx.fillStyle = stressed ? "#2c2c2c" : "#3d2817"; + ctx.beginPath(); + ctx.ellipse(cx, 10, 8, 4, 0, Math.PI, Math.PI * 2); + ctx.fill(); + // Ojos y expresión + if (stressed) { + ctx.fillStyle = "#ffffff"; + ctx.beginPath(); + ctx.arc(20, 14, 3, 0, Math.PI * 2); + ctx.fill(); + ctx.beginPath(); + ctx.arc(28, 14, 3, 0, Math.PI * 2); + ctx.fill(); + ctx.fillStyle = "#000000"; + ctx.beginPath(); + ctx.arc(20, 14, 1.5, 0, Math.PI * 2); + ctx.fill(); + ctx.beginPath(); + ctx.arc(28, 14, 1.5, 0, Math.PI * 2); + ctx.fill(); + ctx.strokeStyle = "#000000"; + ctx.lineWidth = 1.5; + ctx.beginPath(); + ctx.arc(cx, 17, 3, 0, Math.PI); + ctx.stroke(); + } else { + ctx.fillStyle = "#ffffff"; + ctx.beginPath(); + ctx.ellipse(20, 14, 2, 3, 0, 0, Math.PI * 2); + ctx.fill(); + ctx.beginPath(); + ctx.ellipse(28, 14, 2, 3, 0, 0, Math.PI * 2); + ctx.fill(); + ctx.fillStyle = "#1a1a1a"; + ctx.beginPath(); + ctx.arc(20, 14, 1, 0, Math.PI * 2); + ctx.fill(); + ctx.beginPath(); + ctx.arc(28, 14, 1, 0, Math.PI * 2); + ctx.fill(); + ctx.strokeStyle = "#2c2c2c"; + ctx.lineWidth = 1.5; + ctx.beginPath(); + ctx.moveTo(18, 12); + ctx.lineTo(20, 13); + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(30, 12); + ctx.lineTo(28, 13); + ctx.stroke(); + ctx.strokeStyle = "#000000"; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(20, 19); + ctx.lineTo(28, 19); + ctx.stroke(); + // Maletín + ctx.fillStyle = "#4a3428"; + ctx.fillRect(8, 30, 4, 3); + ctx.strokeStyle = "#2c1810"; + ctx.lineWidth = 0.5; + ctx.strokeRect(8, 30, 4, 3); + } + ctx.restore(); + }); + }; - // Spawn initial food - spawnFood(); + agentTex("ghost_red32", "#8B0000", false); + agentTex("ghost_blue32", "#4169E1", true); - // Keyboard and Arcade Button input - this.input.keyboard.on('keydown', (event) => { - // Normalize keyboard input to arcade codes for easier testing - const key = KEYBOARD_TO_ARCADE[event.key] || event.key; + // Tiles de pared por nivel + for (let level = 1; level <= 3; level++) { + drawTex(`wall_tile_${level}`, 16, 16, (ctx, w, h) => { + ctx.fillStyle = LEVEL_CONFIG[level].wallColor; + ctx.fillRect(0, 0, w, h); + ctx.strokeStyle = LEVEL_CONFIG[level].wallStroke; + ctx.lineWidth = 2; + ctx.strokeRect(1, 1, w-2, h-2); + ctx.strokeStyle = "rgba(255,255,255,0.15)"; + ctx.beginPath(); + ctx.moveTo(2, 2); + ctx.lineTo(w-3, 2); + ctx.lineTo(w-3, h-3); + ctx.stroke(); + }); + } +} - // Restart game (arcade buttons only) - if (gameOver && (key === 'P1A' || key === 'START1')) { - restartGame(scene); - return; +// ================ PANTALLA DE INICIO ================ +function showStartScreen(scene) { + gameState = 'start'; + + // IMPORTANTE: Limpiar completamente el estado del juego + cleanupGame(scene); + + // Limpiar escena visual + scene.children.removeAll(); + + // Fondo + scene.add.rectangle(W/2, H/2, W, H, 0x000000); + + // Logo gorila + const logo = scene.add.image(W/2, 180, "gorilla_logo"); + logo.setScale(1.5); + + // Título + scene.add.text(W/2, 330, "PLAT-MAN", { + fontSize: "64px", + color: "#FFD700", + fontStyle: "bold", + fontFamily: "Arial" + }).setOrigin(0.5); + + // Descripción + scene.add.text(W/2, 400, "¡Escapa de los agentes corporativos!", { + fontSize: "20px", + color: "#ffffff", + fontFamily: "Arial" + }).setOrigin(0.5); + + // Instrucciones + scene.add.text(W/2, 460, "Controles: Flechas o WASD", { + fontSize: "18px", + color: "#cccccc", + fontFamily: "Arial" + }).setOrigin(0.5); + + // Botón start + const startText = scene.add.text(W/2, 520, "PRESIONA ESPACIO PARA COMENZAR", { + fontSize: "18px", + color: "#cccccc", + fontFamily: "Arial" + }).setOrigin(0.5); + + // Parpadeo + scene.tweens.add({ + targets: startText, + alpha: 0.3, + duration: 800, + yoyo: true, + repeat: -1 + }); + + // Detectar ESPACIO o ENTER + scene.input.keyboard.once('keydown-SPACE', () => { + if (scene.sound.context && scene.sound.context.state === 'suspended') { + scene.sound.context.resume(); } - - // Direction controls (keyboard keys get mapped to arcade codes) - if (key === 'P1U' && direction.y === 0) { - nextDirection = { x: 0, y: -1 }; - } else if (key === 'P1D' && direction.y === 0) { - nextDirection = { x: 0, y: 1 }; - } else if (key === 'P1L' && direction.x === 0) { - nextDirection = { x: -1, y: 0 }; - } else if (key === 'P1R' && direction.x === 0) { - nextDirection = { x: 1, y: 0 }; + startGame(scene); + }); + + scene.input.keyboard.once('keydown-ENTER', () => { + if (scene.sound.context && scene.sound.context.state === 'suspended') { + scene.sound.context.resume(); } + startGame(scene); }); - - playTone(this, 440, 0.1); } -function drawLetter(char, startX, startY, color, useBold = false) { - const pattern = useBold ? boldLetters[char] : letters[char]; - if (!pattern) return startX + 30; - - for (let row = 0; row < pattern.length; row++) { - for (let col = 0; col < pattern[row].length; col++) { - if (pattern[row][col]) { - const blockX = startX + col * snakeSize; - const blockY = startY + row * snakeSize; - titleBlocks.push({ x: blockX, y: blockY, color: color }); - } - } +// ================ LIMPIAR JUEGO COMPLETO ================ +function cleanupGame(scene) { + // Detener música + stopBackgroundMusic(); + + // Reanudar física si estaba pausada + if (scene.physics && scene.physics.world) { + scene.physics.resume(); + } + + // Limpiar todos los event listeners del teclado + if (scene.input && scene.input.keyboard) { + scene.input.keyboard.removeAllListeners(); + } + + // Limpiar grupos de física + if (bananas) { + bananas.clear(true, true); + bananas = null; } - return startX + (pattern[0].length + 1) * snakeSize; + if (ghosts) { + ghosts.clear(true, true); + ghosts = null; + } + if (wallBodies) { + wallBodies.clear(true, true); + wallBodies = null; + } + if (wallVisuals) { + wallVisuals.clear(true, true); + wallVisuals = null; + } + + // Limpiar colisores + if (scene.playerWallCollider) { + scene.physics.world.removeCollider(scene.playerWallCollider); + scene.playerWallCollider = null; + } + if (scene.ghostWallCollider) { + scene.physics.world.removeCollider(scene.ghostWallCollider); + scene.ghostWallCollider = null; + } + if (scene.ghostGhostCollider) { + scene.physics.world.removeCollider(scene.ghostGhostCollider); + scene.ghostGhostCollider = null; + } + + // Resetear variables globales + player = null; + cursors = null; + wasdKeys = null; + bgRect = null; + scoreText = null; + levelText = null; + currentLevel = 1; + score = 0; + startTime = 0; + powerMode = false; + powerExpiresAt = 0; + isGameOver = false; + rankingInputs = []; + currentInitials = ['A', 'A', 'A']; + selectedIndex = 0; + finalScore = 0; + wonGame = false; } -function update(_time, delta) { - if (gameOver) return; +// ================ INICIAR JUEGO ================ +function startGame(scene) { + // Asegurar que todo esté limpio + cleanupGame(scene); + + gameState = 'playing'; + currentLevel = 1; + score = 0; + isGameOver = false; + powerMode = false; + powerExpiresAt = 0; + + // Limpiar escena visual + scene.children.removeAll(); + + // Reanudar física + scene.physics.resume(); + + // Música de fondo + createBackgroundMusic(scene); + + // Fondo + bgRect = scene.add.rectangle(W/2, H/2, W-20, H-20, LEVEL_CONFIG[1].bgColor) + .setStrokeStyle(2, 0x3333ff); + + // Paredes + createWalls(scene); + + // Jugador + player = scene.add.image(100, 100, "gorilla32b"); + player.setScale(2.0); + scene.physics.add.existing(player); + player.body.setCollideWorldBounds(true); + player.body.setCircle(20, player.width/2 - 20, player.height/2 - 20); + player.body.setVelocity(0, 0); // Asegurar velocidad 0 + player.baseY = player.y; + player.idleOffset = 0; + + // Bananas + createBananas(scene); + + // Agentes + createGhosts(scene); + + // Colisiones + setupCollisions(scene); + + // HUD + scoreText = scene.add.text(16, 12, "Puntaje: 0", { + fontSize: "20px", + fill: "#ffffff" + }); + levelText = scene.add.text(W - 16, 12, "Etapa 1", { + fontSize: "20px", + fill: "#ffffff" + }).setOrigin(1, 0); + scene.add.text(W/2, H-24, "Flechas/WASD: mover • R: reiniciar", { + fontSize: "14px", + fill: "#cccccc" + }).setOrigin(0.5, 1); + + // Controles + cursors = scene.input.keyboard.createCursorKeys(); + wasdKeys = scene.input.keyboard.addKeys({ + up: Phaser.Input.Keyboard.KeyCodes.W, + down: Phaser.Input.Keyboard.KeyCodes.S, + left: Phaser.Input.Keyboard.KeyCodes.A, + right: Phaser.Input.Keyboard.KeyCodes.D + }); + + // Reinicio con R + scene.input.keyboard.on("keydown-R", () => { + showStartScreen(scene); + }); + + startTime = scene.time.now; +} - moveTimer += delta; - if (moveTimer >= moveDelay) { - moveTimer = 0; - direction = nextDirection; - moveSnake(this); +// ================ MÚSICA DE FONDO ================ +function createBackgroundMusic(scene) { + try { + bgMusicTimer = scene.time.addEvent({ + delay: 500, + callback: () => { + if (!isGameOver && scene.sound && scene.sound.context) { + try { + const audioContext = scene.sound.context; + if (audioContext && audioContext.state === 'running') { + const notes = [262, 330, 392, 330]; + const noteIndex = (bgMusicTimer.repeatCount % notes.length); + const oscillator = audioContext.createOscillator(); + const gainNode = audioContext.createGain(); + oscillator.frequency.value = notes[noteIndex]; + gainNode.gain.value = 0.05; + oscillator.connect(gainNode); + gainNode.connect(audioContext.destination); + oscillator.start(); + oscillator.stop(audioContext.currentTime + 0.1); + } + } catch(e) {} + } + }, + loop: true + }); + } catch(e) { + console.log("Audio no disponible"); } - - drawGame(); } -function moveSnake(scene) { - const head = snake[0]; - const newHead = { - x: head.x + direction.x * snakeSize, - y: head.y + direction.y * snakeSize - }; - - // Check wall collision - if (newHead.x < 0 || newHead.x >= 800 || newHead.y < 0 || newHead.y >= 600) { - endGame(scene); - return; +function stopBackgroundMusic() { + if (bgMusicTimer) { + bgMusicTimer.remove(); + bgMusicTimer = null; } +} - // Check self collision - for (let segment of snake) { - if (segment.x === newHead.x && segment.y === newHead.y) { - endGame(scene); - return; +// ================ CREAR PAREDES ================ +function createWalls(scene) { + if (wallBodies) wallBodies.clear(true, true); + if (wallVisuals) wallVisuals.clear(true, true); + + wallBodies = scene.physics.add.staticGroup(); + wallVisuals = scene.add.group(); + + const walls = LEVEL_CONFIG[currentLevel].walls; + walls.forEach(([x, y, w, h]) => { + // Cuerpo físico invisible + const body = scene.add.rectangle(x, y, w, h); + scene.physics.add.existing(body, true); + wallBodies.add(body); + + // Visual con tiles + const tile = scene.add.image(0, 0, `wall_tile_${currentLevel}`); + const tw = tile.width; + const th = tile.height; + tile.destroy(); + + const cols = Math.max(1, Math.floor(w / tw)); + const rows = Math.max(1, Math.floor(h / th)); + const startX = x - (cols * tw) / 2 + tw / 2; + const startY = y - (rows * th) / 2 + th / 2; + + for (let r = 0; r < rows; r++) { + for (let c = 0; c < cols; c++) { + const tileSprite = scene.add.image( + startX + c * tw, + startY + r * th, + `wall_tile_${currentLevel}` + ); + wallVisuals.add(tileSprite); + } } - } + }); +} - // Check title block collision - for (let block of titleBlocks) { - if (newHead.x === block.x && newHead.y === block.y) { - endGame(scene); - return; - } +// ================ CREAR BANANAS ================ +function createBananas(scene) { + if (bananas) bananas.clear(true, true); + bananas = scene.physics.add.group(); + + for (let i = 0; i < BANANAS_TOTAL; i++) { + const p = randomPoint(); + const b = scene.add.image(p.x, p.y, "banana32"); + b.setScale(1.4); // Ajustado para el nuevo diseño alargado + scene.physics.add.existing(b); + bananas.add(b); } +} - snake.unshift(newHead); - - // Check food collision - if (newHead.x === food.x && newHead.y === food.y) { - score += 10; - scoreText.setText('Score: ' + score); - spawnFood(); - playTone(scene, 880, 0.1); +// ================ CREAR AGENTES ================ +function createGhosts(scene) { + if (ghosts) ghosts.clear(true, true); + ghosts = scene.physics.add.group(); + + for (let i = 0; i < GHOST_COUNT; i++) { + const p = randomPoint(); + const g = scene.add.image(p.x, p.y, "ghost_red32"); + g.setScale(1.6); + scene.physics.add.existing(g); + g.body.setCollideWorldBounds(true); + g.body.setBounce(1, 1); + g.eaten = false; + g.respawnAt = 0; + g.nextDirChange = scene.time.now; + setGhostVelocity(g, scene); + ghosts.add(g); + } +} - if (moveDelay > 50) { // Faster max speed (was 80ms) - moveDelay -= 2; - } - } else { - snake.pop(); +// ================ COLISIONES ================ +function setupCollisions(scene) { + if (scene.playerWallCollider) { + scene.physics.world.removeCollider(scene.playerWallCollider); + } + if (scene.ghostWallCollider) { + scene.physics.world.removeCollider(scene.ghostWallCollider); } + if (scene.ghostGhostCollider) { + scene.physics.world.removeCollider(scene.ghostGhostCollider); + } + + scene.playerWallCollider = scene.physics.add.collider(player, wallBodies); + scene.ghostWallCollider = scene.physics.add.collider(ghosts, wallBodies); + scene.ghostGhostCollider = scene.physics.add.collider(ghosts, ghosts); } -function spawnFood() { - let valid = false; - let attempts = 0; - - while (!valid && attempts < 100) { - attempts++; - const gridX = Math.floor(Math.random() * 53) * snakeSize; - const gridY = Math.floor(Math.random() * 40) * snakeSize; - - // Check not on snake - let onSnake = false; - for (let segment of snake) { - if (segment.x === gridX && segment.y === gridY) { - onSnake = true; - break; +// ================ UPDATE DEL JUEGO ================ +function updateGame(scene) { + if (isGameOver) return; + + // Movimiento del jugador + const speed = powerMode ? 240 : 200; + let velX = 0, velY = 0; + + if (cursors.left.isDown || wasdKeys.left.isDown) velX = -speed; + if (cursors.right.isDown || wasdKeys.right.isDown) velX = speed; + if (cursors.up.isDown || wasdKeys.up.isDown) velY = -speed; + if (cursors.down.isDown || wasdKeys.down.isDown) velY = speed; + + player.body.setVelocity(velX, velY); + + // Animación idle + if (velX === 0 && velY === 0) { + player.idleOffset += 0.1; + const bobAmount = Math.sin(player.idleOffset) * 2; + player.y = player.baseY + bobAmount; + player.setRotation(0); + } else { + player.baseY = player.y; + player.idleOffset = 0; + const tilt = velX !== 0 ? (velX > 0 ? 0.15 : -0.15) : (velY > 0 ? 0.1 : -0.1); + player.setRotation(tilt); + } + + // Colisión con bananas + scene.physics.overlap(player, bananas, (p, banana) => { + banana.destroy(); + score += 10; + activatePowerMode(scene); + }); + + // Modo poder + if (powerMode && scene.time.now >= powerExpiresAt) { + setPowerMode(scene, false); + } + + // Actualizar agentes + ghosts.getChildren().forEach(g => { + if (g.eaten) { + if (scene.time.now >= g.respawnAt) { + g.eaten = false; + const p = randomPoint(); + g.setPosition(p.x, p.y); + g.setVisible(true); + g.setTexture(powerMode ? "ghost_blue32" : "ghost_red32"); + setGhostVelocity(g, scene); } - } - - // Check not on title blocks - let onTitle = false; - for (let block of titleBlocks) { - if (gridX === block.x && gridY === block.y) { - onTitle = true; - break; + } else { + if (scene.time.now >= g.nextDirChange) { + setGhostVelocity(g, scene); + g.nextDirChange = scene.time.now + Phaser.Math.Between(700, 1500); + } + + // Colisión con agentes + const dist = Phaser.Math.Distance.Between(player.x, player.y, g.x, g.y); + if (dist < 30) { + if (powerMode) { + score += 50; + g.eaten = true; + g.setVisible(false); + g.body.setVelocity(0); + g.respawnAt = scene.time.now + 2500; + } else { + gameOver(scene, false); + } } } - - if (!onSnake && !onTitle) { - food = { x: gridX, y: gridY }; - valid = true; + }); + + // Actualizar HUD + const survived = Math.floor((scene.time.now - startTime) / 1000); + const total = score + survived; + scoreText.setText(`Puntaje: ${total}${powerMode ? " (PODER!)" : ""}`); + + // Victoria si no quedan bananas + if (bananas.countActive(true) === 0) { + if (currentLevel < 3) { + advanceLevel(scene); + } else { + gameOver(scene, true); } } } -function drawGame() { - graphics.clear(); - - // Draw title blocks - titleBlocks.forEach(block => { - graphics.fillStyle(block.color, 1); - graphics.fillRect(block.x, block.y, snakeSize - 2, snakeSize - 2); - }); - - // Draw snake - snake.forEach((segment, index) => { - if (index === 0) { - graphics.fillStyle(0x00ff00, 1); - } else { - graphics.fillStyle(0x00aa00, 1); - } - graphics.fillRect(segment.x, segment.y, snakeSize - 2, snakeSize - 2); +// ================ MODO PODER ================ +function activatePowerMode(scene) { + powerMode = true; + powerExpiresAt = scene.time.now + POWER_DURATION_MS; + ghosts.getChildren().forEach(g => { + if (!g.eaten) g.setTexture("ghost_blue32"); }); +} - // Draw food - graphics.fillStyle(0xff0000, 1); - graphics.fillRect(food.x, food.y, snakeSize - 2, snakeSize - 2); +function setPowerMode(scene, active) { + powerMode = active; + if (!active) { + ghosts.getChildren().forEach(g => { + if (!g.eaten) g.setTexture("ghost_red32"); + }); + } } -function endGame(scene) { - gameOver = true; - playTone(scene, 220, 0.5); - - // Semi-transparent overlay - const overlay = scene.add.graphics(); - overlay.fillStyle(0x000000, 0.7); - overlay.fillRect(0, 0, 800, 600); - - // Game Over title with glow effect - const gameOverText = scene.add.text(400, 300, 'GAME OVER', { - fontSize: '64px', - fontFamily: 'Arial, sans-serif', - color: '#ff0000', - align: 'center', - stroke: '#ff6666', - strokeThickness: 8 +// ================ AVANZAR NIVEL ================ +function advanceLevel(scene) { + currentLevel++; + powerMode = false; + powerExpiresAt = 0; + + bgRect.setFillStyle(LEVEL_CONFIG[currentLevel].bgColor); + + player.x = 100; + player.y = 100; + player.baseY = player.y; + player.setRotation(0); + + createWalls(scene); + createBananas(scene); + createGhosts(scene); + setupCollisions(scene); + + levelText.setText(LEVEL_CONFIG[currentLevel].name); + + const msg = scene.add.text(W/2, H/2, `¡${LEVEL_CONFIG[currentLevel].name}!`, { + fontSize: "48px", + color: "#ffffff", + fontStyle: "bold" }).setOrigin(0.5); - - // Pulsing animation for game over text + scene.tweens.add({ - targets: gameOverText, - scale: { from: 1, to: 1.1 }, - alpha: { from: 1, to: 0.8 }, - duration: 800, - yoyo: true, - repeat: -1, - ease: 'Sine.easeInOut' + targets: msg, + alpha: 0, + duration: 2000, + onComplete: () => msg.destroy() }); +} - // Score display - scene.add.text(400, 400, 'SCORE: ' + score, { - fontSize: '36px', - fontFamily: 'Arial, sans-serif', - color: '#00ffff', - align: 'center', - stroke: '#000000', - strokeThickness: 4 - }).setOrigin(0.5); +// ================ GAME OVER ================ +function gameOver(scene, win) { + isGameOver = true; + scene.physics.pause(); + stopBackgroundMusic(); + + const survived = Math.floor((scene.time.now - startTime) / 1000); + finalScore = score + survived; + wonGame = win; + + showRankingScreen(scene); +} - // Restart instruction with subtle animation - const restartText = scene.add.text(400, 480, 'Press Button A or START to Restart', { - fontSize: '24px', - fontFamily: 'Arial, sans-serif', - color: '#ffff00', - align: 'center', - stroke: '#000000', - strokeThickness: 3 +// ================ PANTALLA DE RANKING ================ +function showRankingScreen(scene) { + gameState = 'ranking'; + + // Limpiar event listeners previos + scene.input.keyboard.removeAllListeners(); + + scene.children.removeAll(); + + scene.add.rectangle(W/2, H/2, W, H, 0x000000); + + if (wonGame) { + scene.add.text(W/2, 80, "¡VICTORIA!", { + fontSize: "48px", + color: "#FFD700", + fontStyle: "bold", + fontFamily: "Arial" + }).setOrigin(0.5); + + scene.add.text(W/2, 130, "¡Completaste las 3 etapas!", { + fontSize: "20px", + color: "#00ff00", + fontFamily: "Arial" + }).setOrigin(0.5); + } else { + scene.add.text(W/2, 80, "GAME OVER", { + fontSize: "48px", + color: "#ff0000", + fontStyle: "bold", + fontFamily: "Arial" + }).setOrigin(0.5); + } + + const scoreY = wonGame ? 180 : 150; + scene.add.text(W/2, scoreY, `Tu puntuación: ${finalScore}`, { + fontSize: "32px", + color: "#ffffff", + fontFamily: "Arial" + }).setOrigin(0.5); + + scene.add.text(W/2, scoreY + 40, "Ingresa tus iniciales (3 letras):", { + fontSize: "20px", + color: "#FFD700", + fontFamily: "Arial" }).setOrigin(0.5); + + // Crear inputs para iniciales + rankingInputs = []; + currentInitials = ['A', 'A', 'A']; + selectedIndex = 0; + + for (let i = 0; i < 3; i++) { + const x = W/2 - 60 + i * 60; + const y = scoreY + 100; + + const bg = scene.add.rectangle(x, y, 50, 60, 0x333333); + const txt = scene.add.text(x, y, currentInitials[i], { + fontSize: "40px", + color: "#ffffff", + fontFamily: "Arial" + }).setOrigin(0.5); + + rankingInputs.push({ bg, txt, index: i }); + } + + updateRankingSelection(scene); + + scene.add.text(W/2, scoreY + 180, "↑↓ cambiar letra ←→ mover ENTER confirmar", { + fontSize: "16px", + color: "#cccccc", + fontFamily: "Arial" + }).setOrigin(0.5); + + // Controles - usar once para evitar múltiples registros + const handleUp = () => changeInitial(scene, 1); + const handleDown = () => changeInitial(scene, -1); + const handleLeft = () => moveSelection(scene, -1); + const handleRight = () => moveSelection(scene, 1); + const handleEnter = () => saveRanking(scene); + + scene.input.keyboard.on('keydown-UP', handleUp); + scene.input.keyboard.on('keydown-DOWN', handleDown); + scene.input.keyboard.on('keydown-LEFT', handleLeft); + scene.input.keyboard.on('keydown-RIGHT', handleRight); + scene.input.keyboard.on('keydown-ENTER', handleEnter); +} - // Blinking animation for restart text - scene.tweens.add({ - targets: restartText, - alpha: { from: 1, to: 0.3 }, - duration: 600, - yoyo: true, - repeat: -1, - ease: 'Sine.easeInOut' +function updateRankingSelection(scene) { + rankingInputs.forEach((item, i) => { + if (i === selectedIndex) { + item.bg.setFillStyle(0xFFD700); + item.txt.setColor("#000000"); + } else { + item.bg.setFillStyle(0x333333); + item.txt.setColor("#ffffff"); + } }); } -function restartGame(scene) { - snake = [ - { x: 75, y: 60 }, - { x: 60, y: 60 }, - { x: 45, y: 60 } - ]; - direction = { x: 1, y: 0 }; - nextDirection = { x: 1, y: 0 }; - score = 0; - gameOver = false; - moveDelay = 100; // Match new faster initial speed - scoreText.setText('Score: 0'); - spawnFood(); - scene.scene.restart(); +function changeInitial(scene, dir) { + if (gameState !== 'ranking') return; + const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + let idx = letters.indexOf(currentInitials[selectedIndex]); + idx = (idx + dir + letters.length) % letters.length; + currentInitials[selectedIndex] = letters[idx]; + rankingInputs[selectedIndex].txt.setText(currentInitials[selectedIndex]); } -function playTone(scene, frequency, duration) { - const audioContext = scene.sound.context; - const oscillator = audioContext.createOscillator(); - const gainNode = audioContext.createGain(); +function moveSelection(scene, dir) { + if (gameState !== 'ranking') return; + selectedIndex = (selectedIndex + dir + 3) % 3; + updateRankingSelection(scene); +} - oscillator.connect(gainNode); - gainNode.connect(audioContext.destination); +function saveRanking(scene) { + if (gameState !== 'ranking') return; + + const initials = currentInitials.join(''); + let ranking = []; + + try { + const saved = localStorage.getItem(RANKING_KEY); + if (saved) ranking = JSON.parse(saved); + } catch(e) {} + + ranking.push({ initials, score: finalScore }); + ranking.sort((a, b) => b.score - a.score); + ranking = ranking.slice(0, 5); + + try { + localStorage.setItem(RANKING_KEY, JSON.stringify(ranking)); + } catch(e) {} + + showFinalRanking(scene, ranking); +} - oscillator.frequency.value = frequency; - oscillator.type = 'square'; +function showFinalRanking(scene, ranking) { + // Limpiar todo antes de mostrar ranking final + scene.input.keyboard.removeAllListeners(); + scene.children.removeAll(); + + scene.add.rectangle(W/2, H/2, W, H, 0x000000); + + scene.add.text(W/2, 80, "TOP 5 RANKING", { + fontSize: "48px", + color: "#FFD700", + fontStyle: "bold", + fontFamily: "Arial" + }).setOrigin(0.5); + + ranking.forEach((entry, i) => { + const y = 160 + i * 50; + let color = "#ffffff"; + if (i === 0) color = "#FFD700"; + else if (i === 1) color = "#C0C0C0"; + else if (i === 2) color = "#CD7F32"; + + scene.add.text(W/2, y, `${i + 1}. ${entry.initials} - ${entry.score}`, { + fontSize: "32px", + color: color, + fontFamily: "Arial" + }).setOrigin(0.5); + }); + + scene.add.text(W/2, 450, "Presiona ESPACIO para volver al inicio", { + fontSize: "18px", + color: "#cccccc", + fontFamily: "Arial" + }).setOrigin(0.5); + + scene.input.keyboard.once('keydown-SPACE', () => { + showStartScreen(scene); + }); +} - gainNode.gain.setValueAtTime(0.1, audioContext.currentTime); - gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + duration); +// ================ UTILIDADES ================ +function randomPoint() { + return { + x: Phaser.Math.Between(80, W - 80), + y: Phaser.Math.Between(80, H - 80) + }; +} - oscillator.start(audioContext.currentTime); - oscillator.stop(audioContext.currentTime + duration); +function setGhostVelocity(ghost, scene) { + const speed = LEVEL_CONFIG[currentLevel].ghostSpeed; + const dirs = [ + { x: 1, y: 0 }, { x: -1, y: 0 }, + { x: 0, y: 1 }, { x: 0, y: -1 } + ]; + const d = Phaser.Utils.Array.GetRandom(dirs); + ghost.body.setVelocity(d.x * speed, d.y * speed); } + diff --git a/index.html b/index.html index c1dfbd3..e7ecd09 100644 --- a/index.html +++ b/index.html @@ -1,413 +1,36 @@ - + - - - Platanus Hack 25: Arcade Challenge - - - - - - - - -
- - - - - - + + + +
+ + + + + diff --git a/metadata.json b/metadata.json index 45028c2..44e3202 100644 --- a/metadata.json +++ b/metadata.json @@ -1,4 +1,5 @@ { - "game_name": "", - "description": "" + "game_name": "PLAT-MAN", + "description": "Un gorila recolecta plátanos mientras escapa de agentes corporativos estresados en un laberinto de oficina. 3 niveles, ranking de puntuaciones." } + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1d428d8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1078 @@ +{ + "name": "platanus-phaser-game", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "platanus-phaser-game", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "phaser": "^3.90.0" + }, + "devDependencies": { + "vite": "^7.2.1" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", + "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", + "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", + "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", + "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", + "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", + "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", + "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", + "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", + "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", + "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", + "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", + "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", + "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", + "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", + "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", + "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", + "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", + "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", + "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", + "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", + "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", + "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/phaser": { + "version": "3.90.0", + "resolved": "https://registry.npmjs.org/phaser/-/phaser-3.90.0.tgz", + "integrity": "sha512-/cziz/5ZIn02uDkC9RzN8VF9x3Gs3XdFFf9nkiMEQT3p7hQlWuyjy4QWosU802qqno2YSLn2BfqwOKLv/sSVfQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", + "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.52.5", + "@rollup/rollup-android-arm64": "4.52.5", + "@rollup/rollup-darwin-arm64": "4.52.5", + "@rollup/rollup-darwin-x64": "4.52.5", + "@rollup/rollup-freebsd-arm64": "4.52.5", + "@rollup/rollup-freebsd-x64": "4.52.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", + "@rollup/rollup-linux-arm-musleabihf": "4.52.5", + "@rollup/rollup-linux-arm64-gnu": "4.52.5", + "@rollup/rollup-linux-arm64-musl": "4.52.5", + "@rollup/rollup-linux-loong64-gnu": "4.52.5", + "@rollup/rollup-linux-ppc64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-musl": "4.52.5", + "@rollup/rollup-linux-s390x-gnu": "4.52.5", + "@rollup/rollup-linux-x64-gnu": "4.52.5", + "@rollup/rollup-linux-x64-musl": "4.52.5", + "@rollup/rollup-openharmony-arm64": "4.52.5", + "@rollup/rollup-win32-arm64-msvc": "4.52.5", + "@rollup/rollup-win32-ia32-msvc": "4.52.5", + "@rollup/rollup-win32-x64-gnu": "4.52.5", + "@rollup/rollup-win32-x64-msvc": "4.52.5", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/vite": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.1.tgz", + "integrity": "sha512-qTl3VF7BvOupTR85Zc561sPEgxyUSNSvTQ9fit7DEMP7yPgvvIGm5Zfa1dOM+kOwWGNviK9uFM9ra77+OjK7lQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json index 0415c3d..e4c4ffb 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,20 @@ { - "name": "platanus-hack-25-arcade", + "name": "platanus-phaser-game", "version": "1.0.0", - "description": "Phaser arcade game challenge starter kit", + "description": "Plat-Man - Juego de gorila con Phaser 3", "type": "module", "scripts": { "dev": "vite", - "dev:old": "tsx --watch dev-server.ts", - "check-restrictions": "tsx check-restrictions.ts" + "build": "vite build", + "preview": "vite preview" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "phaser": "^3.90.0" }, "devDependencies": { - "@swc/core": "^1.13.5", - "@types/node": "^22.10.5", - "tsx": "^4.19.2", - "typescript": "^5.7.2", - "vite": "^7.1.12" + "vite": "^7.2.1" } } diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..7b6198d --- /dev/null +++ b/vite.config.js @@ -0,0 +1,13 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + port: 3000, + open: true + }, + build: { + outDir: 'dist', + assetsDir: 'assets' + } +}); +