From f4c36c48ac7254a62c887e09a2f1593d9df220ef Mon Sep 17 00:00:00 2001 From: Gemini AI Date: Fri, 26 Dec 2025 10:57:43 +0400 Subject: [PATCH] Fix all 5 new games with proper logic flow and Tetris 120 FPS - Neon Puzzle: Fixed angle normalization, visual feedback for aligned connections - Rhythm Beat: Complete 4-lane system with key indicators and timing-based scoring - Cosmic Arena: Added particle explosion effects - Crystal Tetris: Fixed duplicate shape, proper game over, drop speed scaling, 120 FPS with requestAnimationFrame - Aurora Jumper: Reachable platform placement based on jump physics, proper respawn system --- app.js | 1818 +++++++++++++++++++++++++++++++++++++++++++++++++++- index.html | 262 +++++++- style.css | 650 +++++++++++++++++++ 3 files changed, 2717 insertions(+), 13 deletions(-) diff --git a/app.js b/app.js index 0dd736a..9f3e0a5 100644 --- a/app.js +++ b/app.js @@ -8,6 +8,12 @@ document.addEventListener('DOMContentLoaded', () => { initCountdown(); initMessageForge(); initGame(); + initTraoom(); + initNeonPuzzle(); + initRhythmBeat(); + initCosmicArena(); + initTetris(); + initPlatformer(); }); // --- Utility: Scroll to Section --- @@ -98,7 +104,10 @@ function initBackground() { requestAnimationFrame(animate); }; - window.addEventListener('resize', resize); + window.addEventListener('resize', () => { + isMobile = window.innerWidth < 768; + resize(); + }); resize(); initParticles(); animate(); @@ -138,17 +147,30 @@ function initMessageForge() { const btn = document.getElementById('forgeBtn'); const output = document.getElementById('outputMessage'); const canvas = document.getElementById('snowflakeCanvas'); + const cloudContainer = document.getElementById('cloudContainer'); const ctx = canvas.getContext('2d'); canvas.width = 300; canvas.height = 300; + // Load persistent messages + let communityMessages = JSON.parse(localStorage.getItem('trae_messages') || '[]'); + + const updateCloud = () => { + cloudContainer.innerHTML = ''; + communityMessages.slice(-15).reverse().forEach(msg => { + const span = document.createElement('span'); + span.className = 'cloud-msg'; + span.innerText = msg; + cloudContainer.appendChild(span); + }); + }; + const drawCrystal = (text) => { ctx.clearRect(0, 0, canvas.width, canvas.height); const centerX = canvas.width / 2; const centerY = canvas.height / 2; - // Draw decorative snowflake based on text length ctx.strokeStyle = '#00ff66'; ctx.lineWidth = 2; const branches = 6 + (text.length % 6); @@ -161,7 +183,6 @@ function initMessageForge() { ctx.lineTo(centerX + Math.cos(angle) * radius, centerY + Math.sin(angle) * radius); ctx.stroke(); - // Sub-branches for (let j = 1; j < 4; j++) { const subAngle = angle + 0.5; const subX = centerX + Math.cos(angle) * (radius * j / 4); @@ -179,11 +200,18 @@ function initMessageForge() { }; btn.addEventListener('click', () => { - if (input.value.trim()) { - drawCrystal(input.value); + const text = input.value.trim(); + if (text) { + drawCrystal(text); + communityMessages.push(text); + if (communityMessages.length > 50) communityMessages.shift(); + localStorage.setItem('trae_messages', JSON.stringify(communityMessages)); + updateCloud(); input.value = ''; } }); + + updateCloud(); } // --- 4. Gift Catcher Mini Game --- @@ -192,15 +220,100 @@ function initGame() { const ctx = canvas.getContext('2d'); const startBtn = document.getElementById('startGameBtn'); const scoreEl = document.getElementById('scoreVal'); + const overlay = document.getElementById('gameOverlay'); + const submitBtn = document.getElementById('submitScoreBtn'); + const nameInput = document.getElementById('playerName'); + const leaderboardList = document.getElementById('leaderboardList'); + const timerDisplay = document.getElementById('timerDisplay'); + const timeBtns = document.querySelectorAll('.time-btn'); let score = 0; let gameActive = false; let playerX = 0; + let playerWidth = 90; + let playerHeight = 25; + let playerBottomOffset = 35; let gifts = []; let particles = []; let difficultyMultiplier = 1; let combo = 0; let lastCatchTime = 0; + let timeLeft = 15; + let timerInterval = null; + let selectedDuration = 15; + let isMobile = window.innerWidth < 768; + + timeBtns.forEach(btn => { + btn.addEventListener('click', () => { + timeBtns.forEach(b => b.classList.remove('active')); + btn.classList.add('active'); + selectedDuration = parseInt(btn.dataset.time); + timeLeft = selectedDuration; + timerDisplay.innerText = timeLeft + 's'; + }); + }); + + // Leaderboard logic + let leaderboard = JSON.parse(localStorage.getItem('trae_leaderboard') || '[]'); + + const updateLeaderboardUI = () => { + leaderboardList.innerHTML = ''; + leaderboard.sort((a, b) => b.score - a.score).slice(0, 10).forEach((entry, idx) => { + const dateStr = new Date(entry.date).toLocaleDateString(); + const durationStr = entry.duration ? `${entry.duration}s` : 'N/A'; + const div = document.createElement('div'); + div.className = 'leader-item'; + div.innerHTML = ` + #${idx + 1} + ${entry.name} + ${entry.score} + ${durationStr} + ${dateStr} + `; + leaderboardList.appendChild(div); + }); + }; + + const startTimer = () => { + timeLeft = selectedDuration; + timerDisplay.innerText = timeLeft + 's'; + timerDisplay.classList.remove('warning'); + + if (timerInterval) clearInterval(timerInterval); + + timerInterval = setInterval(() => { + timeLeft--; + timerDisplay.innerText = timeLeft + 's'; + + const warningThreshold = Math.max(5, Math.floor(selectedDuration * 0.2)); + if (timeLeft <= warningThreshold) { + timerDisplay.classList.add('warning'); + } + + if (timeLeft <= 0) { + clearInterval(timerInterval); + gameOver(); + } + }, 1000); + }; + + const gameOver = () => { + gameActive = false; + if (timerInterval) clearInterval(timerInterval); + document.body.style.overflow = ''; + overlay.classList.remove('hidden'); + document.getElementById('overlayTitle').innerText = `GAME OVER - SCORE: ${score}`; + }; + + submitBtn.addEventListener('click', () => { + const name = nameInput.value.trim() || 'Anonymous'; + leaderboard.push({ name, score, date: new Date().toISOString(), duration: selectedDuration }); + localStorage.setItem('trae_leaderboard', JSON.stringify(leaderboard)); + updateLeaderboardUI(); + overlay.classList.add('hidden'); + startBtn.style.display = 'block'; + document.body.style.overflow = ''; + }); const resize = () => { canvas.width = canvas.parentElement.clientWidth; @@ -465,7 +578,7 @@ function initGame() { ctx.shadowBlur = 20; ctx.shadowColor = '#00ff66'; ctx.beginPath(); - ctx.roundRect(playerX - 45, canvas.height - 35, 90, 25, 8); + ctx.roundRect(playerX - playerWidth / 2, canvas.height - playerBottomOffset, playerWidth, playerHeight, 8); ctx.fill(); ctx.stroke(); ctx.restore(); @@ -516,8 +629,8 @@ function initGame() { gift.draw(); // Collision detection - if (gift.y + gift.size > canvas.height - 35 && - gift.x > playerX - 55 && gift.x < playerX + 55) { + if (gift.y + gift.size > canvas.height - playerBottomOffset - playerHeight / 2 && + gift.x > playerX - playerWidth / 2 - 10 && gift.x < playerX + playerWidth / 2 + 10) { // Combo system const now = Date.now(); @@ -560,11 +673,38 @@ function initGame() { requestAnimationFrame(updateGame); }; + updateLeaderboardUI(); + canvas.addEventListener('mousemove', (e) => { const rect = canvas.getBoundingClientRect(); playerX = e.clientX - rect.left; + playerX = Math.max(playerWidth / 2, Math.min(canvas.width - playerWidth / 2, playerX)); }); + canvas.addEventListener('touchstart', (e) => { + e.preventDefault(); + const rect = canvas.getBoundingClientRect(); + const touch = e.touches[0]; + playerX = touch.clientX - rect.left; + playerX = Math.max(playerWidth / 2, Math.min(canvas.width - playerWidth / 2, playerX)); + }, { passive: false }); + + canvas.addEventListener('touchmove', (e) => { + e.preventDefault(); + const rect = canvas.getBoundingClientRect(); + const touch = e.touches[0]; + playerX = touch.clientX - rect.left; + playerX = Math.max(playerWidth / 2, Math.min(canvas.width - playerWidth / 2, playerX)); + }, { passive: false }); + + canvas.addEventListener('touchend', (e) => { + e.preventDefault(); + }, { passive: false }); + + canvas.addEventListener('touchcancel', (e) => { + e.preventDefault(); + }, { passive: false }); + startBtn.addEventListener('click', () => { gameActive = true; score = 0; @@ -577,9 +717,1671 @@ function initGame() { startBtn.style.display = 'none'; document.getElementById('gameUI').style.top = '40px'; document.getElementById('gameUI').style.transform = 'translateX(-50%)'; + document.body.style.overflow = 'hidden'; + startTimer(); updateGame(); }); + document.addEventListener('touchmove', (e) => { + if (gameActive) { + const gameCanvas = document.getElementById('gameCanvas'); + if (!gameCanvas.contains(e.target)) { + e.preventDefault(); + } + } + }, { passive: false }); + window.addEventListener('resize', resize); resize(); } + +// Export/Import Leaderboard +window.exportLeaderboard = () => { + const leaderboard = JSON.parse(localStorage.getItem('trae_leaderboard') || '[]'); + const dataStr = JSON.stringify(leaderboard, null, 2); + const blob = new Blob([dataStr], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `trae_leaderboard_${new Date().toISOString().split('T')[0]}.json`; + a.click(); + URL.revokeObjectURL(url); +}; + +window.importLeaderboard = (event) => { + const file = event.target.files[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = (e) => { + try { + const data = JSON.parse(e.target.result); + if (Array.isArray(data)) { + localStorage.setItem('trae_leaderboard', JSON.stringify(data)); + location.reload(); + } + } catch (err) { + alert('Invalid file format'); + } + }; + reader.readAsText(file); +}; + +// --- 5. Traoom - Bug Hunter Game --- +function initTraoom() { + const canvas = document.getElementById('traoomCanvas'); + const ctx = canvas.getContext('2d'); + const startBtn = document.getElementById('startTraoomBtn'); + const restartBtn = document.getElementById('restartTraoomBtn'); + const killEl = document.getElementById('killVal'); + const timeEl = document.getElementById('traoomTime'); + const waveEl = document.getElementById('waveVal'); + const overlay = document.getElementById('traoomOverlay'); + const finalKillsEl = document.getElementById('finalKills'); + const finalTimeEl = document.getElementById('finalTime'); + + let gameActive = false; + let kills = 0; + let wave = 1; + let gameTime = 0; + let timerInterval = null; + let lastShot = 0; + const SHOOT_COOLDOWN = 200; + + const keys = {}; + const mouse = { x: 0, y: 0 }; + + const resize = () => { + canvas.width = canvas.parentElement.clientWidth; + canvas.height = canvas.parentElement.clientHeight; + }; + + const drawRobloxBug = (x, y, size, color, health, maxHealth) => { + ctx.save(); + + const scale = size / 40; + + ctx.translate(x, y); + + ctx.fillStyle = color; + ctx.strokeStyle = '#ffffff'; + ctx.lineWidth = 2; + + const pulse = Math.sin(Date.now() * 0.01) * 0.1 + 1; + ctx.scale(pulse, pulse); + + ctx.beginPath(); + ctx.roundRect(-20 * scale, -20 * scale, 40 * scale, 40 * scale, 5 * scale); + ctx.fill(); + ctx.stroke(); + + ctx.fillStyle = '#ffffff'; + ctx.beginPath(); + ctx.arc(-8 * scale, -5 * scale, 4 * scale, 0, Math.PI * 2); + ctx.arc(8 * scale, -5 * scale, 4 * scale, 0, Math.PI * 2); + ctx.fill(); + + ctx.fillStyle = '#ff0000'; + ctx.beginPath(); + ctx.arc(-8 * scale, -5 * scale, 2 * scale, 0, Math.PI * 2); + ctx.arc(8 * scale, -5 * scale, 2 * scale, 0, Math.PI * 2); + ctx.fill(); + + ctx.strokeStyle = '#ffffff'; + ctx.lineWidth = 3; + ctx.beginPath(); + ctx.moveTo(-10 * scale, 8 * scale); + ctx.lineTo(10 * scale, 8 * scale); + ctx.stroke(); + + const healthPercent = health / maxHealth; + ctx.fillStyle = 'rgba(255, 0, 0, 0.7)'; + ctx.fillRect(-15 * scale, -30 * scale, 30 * scale, 5 * scale); + ctx.fillStyle = '#00ff66'; + ctx.fillRect(-15 * scale, -30 * scale, 30 * scale * healthPercent, 5 * scale); + + ctx.restore(); + }; + + const drawPlayer = (x, y, size) => { + ctx.save(); + ctx.translate(x, y); + + const angle = Math.atan2(mouse.y - y, mouse.x - x); + ctx.rotate(angle); + + ctx.fillStyle = '#00ff66'; + ctx.strokeStyle = '#ffffff'; + ctx.lineWidth = 2; + + ctx.beginPath(); + ctx.roundRect(-size / 2, -size / 2, size, size, 5); + ctx.fill(); + ctx.stroke(); + + ctx.fillStyle = '#ffffff'; + ctx.beginPath(); + ctx.arc(3, -3, 4, 0, Math.PI * 2); + ctx.fill(); + + ctx.fillStyle = '#00f2ff'; + ctx.fillRect(size / 2 - 5, -3, 15, 6); + + ctx.restore(); + }; + + const drawBullet = (x, y, size) => { + ctx.save(); + ctx.fillStyle = '#00f2ff'; + ctx.shadowColor = '#00f2ff'; + ctx.shadowBlur = 10; + ctx.beginPath(); + ctx.arc(x, y, size, 0, Math.PI * 2); + ctx.fill(); + ctx.restore(); + }; + + const drawParticle = (p) => { + ctx.save(); + ctx.globalAlpha = p.life / p.maxLife; + ctx.fillStyle = p.color; + ctx.beginPath(); + ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); + ctx.fill(); + ctx.restore(); + }; + + class Bug { + constructor() { + const side = Math.floor(Math.random() * 4); + switch(side) { + case 0: + this.x = -30; + this.y = Math.random() * canvas.height; + break; + case 1: + this.x = canvas.width + 30; + this.y = Math.random() * canvas.height; + break; + case 2: + this.x = Math.random() * canvas.width; + this.y = -30; + break; + case 3: + this.x = Math.random() * canvas.width; + this.y = canvas.height + 30; + break; + } + this.size = 30 + Math.random() * 20; + this.speed = 1 + Math.random() * (1 + wave * 0.3); + this.health = 2 + Math.floor(wave * 0.5); + this.maxHealth = this.health; + + const bugTypes = [ + { color: '#ff0000', name: 'SyntaxError' }, + { color: '#ff6600', name: 'TypeError' }, + { color: '#ff00ff', name: 'LogicBug' }, + { color: '#ffff00', name: 'MemoryLeak' }, + { color: '#00ffff', name: 'NullPointer' } + ]; + this.type = bugTypes[Math.floor(Math.random() * bugTypes.length)]; + } + + update(playerX, playerY) { + const dx = playerX - this.x; + const dy = playerY - this.y; + const dist = Math.sqrt(dx * dx + dy * dy); + + if (dist > 0) { + this.x += (dx / dist) * this.speed; + this.y += (dy / dist) * this.speed; + } + } + + draw() { + drawRobloxBug(this.x, this.y, this.size, this.type.color, this.health, this.maxHealth); + } + + takeDamage(amount) { + this.health -= amount; + return this.health <= 0; + } + } + + class Bullet { + constructor(x, y, targetX, targetY) { + this.x = x; + this.y = y; + const dx = targetX - x; + const dy = targetY - y; + const dist = Math.sqrt(dx * dx + dy * dy); + this.vx = (dx / dist) * 12; + this.vy = (dy / dist) * 12; + this.size = 5; + this.life = 60; + } + + update() { + this.x += this.vx; + this.y += this.vy; + this.life--; + return this.life > 0 && + this.x > 0 && this.x < canvas.width && + this.y > 0 && this.y < canvas.height; + } + + draw() { + drawBullet(this.x, this.y, this.size); + } + } + + class Particle { + constructor(x, y, color) { + this.x = x; + this.y = y; + this.vx = (Math.random() - 0.5) * 8; + this.vy = (Math.random() - 0.5) * 8; + this.size = 3 + Math.random() * 4; + this.color = color; + this.life = 30; + this.maxLife = 30; + } + + update() { + this.x += this.vx; + this.y += this.vy; + this.vy += 0.2; + this.life--; + return this.life > 0; + } + + draw() { + drawParticle(this); + } + } + + let player = { x: 0, y: 0, size: 30, speed: 5, health: 100 }; + let bugs = []; + let bullets = []; + let particles = []; + let spawnTimer = 0; + let spawnRate = 120; + + const spawnBug = () => { + bugs.push(new Bug()); + }; + + const createExplosion = (x, y, color) => { + for (let i = 0; i < 10; i++) { + particles.push(new Particle(x, y, color)); + } + }; + + const checkCollisions = () => { + bullets.forEach((bullet, bi) => { + bugs.forEach((bug, gi) => { + const dx = bullet.x - bug.x; + const dy = bullet.y - bug.y; + const dist = Math.sqrt(dx * dx + dy * dy); + + if (dist < bug.size / 2 + bullet.size) { + bullets.splice(bi, 1); + + if (bug.takeDamage(1)) { + bugs.splice(gi, 1); + kills++; + killEl.innerText = kills; + createExplosion(bug.x, bug.y, bug.type.color); + + if (kills % 10 === 0) { + wave++; + waveEl.innerText = wave; + spawnRate = Math.max(30, 120 - wave * 10); + } + } else { + createExplosion(bullet.x, bullet.y, '#ffffff'); + } + } + }); + }); + + bugs.forEach((bug) => { + const dx = player.x - bug.x; + const dy = player.y - bug.y; + const dist = Math.sqrt(dx * dx + dy * dy); + + if (dist < player.size / 2 + bug.size / 2) { + player.health -= 10; + createExplosion(player.x, player.y, '#ff0000'); + + if (player.health <= 0) { + gameOver(); + } + } + }); + }; + + const updateGame = () => { + if (!gameActive) return; + + ctx.fillStyle = 'rgba(5, 11, 20, 0.3)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + if (keys['KeyW'] || keys['ArrowUp']) player.y = Math.max(player.size / 2, player.y - player.speed); + if (keys['KeyS'] || keys['ArrowDown']) player.y = Math.min(canvas.height - player.size / 2, player.y + player.speed); + if (keys['KeyA'] || keys['ArrowLeft']) player.x = Math.max(player.size / 2, player.x - player.speed); + if (keys['KeyD'] || keys['ArrowRight']) player.x = Math.min(canvas.width - player.size / 2, player.x + player.speed); + + drawPlayer(player.x, player.y, player.size); + + spawnTimer++; + if (spawnTimer >= spawnRate) { + spawnBug(); + spawnTimer = 0; + } + + bullets = bullets.filter(b => b.update()); + bullets.forEach(b => b.draw()); + + bugs.forEach(bug => { + bug.update(player.x, player.y); + bug.draw(); + }); + + particles = particles.filter(p => p.update()); + particles.forEach(p => p.draw()); + + checkCollisions(); + + requestAnimationFrame(updateGame); + }; + + const formatTime = (seconds) => { + const mins = Math.floor(seconds / 60); + const secs = seconds % 60; + return `${mins}:${secs.toString().padStart(2, '0')}`; + }; + + const startTimer = () => { + gameTime = 0; + timerInterval = setInterval(() => { + gameTime++; + timeEl.innerText = formatTime(gameTime); + }, 1000); + }; + + const gameOver = () => { + gameActive = false; + clearInterval(timerInterval); + + finalKillsEl.innerText = kills; + finalTimeEl.innerText = formatTime(gameTime); + overlay.classList.remove('hidden'); + }; + + const startGame = () => { + gameActive = true; + kills = 0; + wave = 1; + gameTime = 0; + player = { x: canvas.width / 2, y: canvas.height / 2, size: 30, speed: 5, health: 100 }; + bugs = []; + bullets = []; + particles = []; + spawnTimer = 0; + spawnRate = 120; + + killEl.innerText = '0'; + waveEl.innerText = '1'; + timeEl.innerText = '0:00'; + overlay.classList.add('hidden'); + + startTimer(); + updateGame(); + }; + + window.addEventListener('keydown', (e) => { + keys[e.code] = true; + if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Space'].includes(e.code)) { + e.preventDefault(); + } + }); + + window.addEventListener('keyup', (e) => { + keys[e.code] = false; + }); + + canvas.addEventListener('mousemove', (e) => { + const rect = canvas.getBoundingClientRect(); + mouse.x = e.clientX - rect.left; + mouse.y = e.clientY - rect.top; + }); + + canvas.addEventListener('click', (e) => { + if (!gameActive) return; + + const now = Date.now(); + if (now - lastShot >= SHOOT_COOLDOWN) { + bullets.push(new Bullet(player.x, player.y, mouse.x, mouse.y)); + lastShot = now; + } + }); + + canvas.addEventListener('touchstart', (e) => { + e.preventDefault(); + const rect = canvas.getBoundingClientRect(); + const touch = e.touches[0]; + mouse.x = touch.clientX - rect.left; + mouse.y = touch.clientY - rect.top; + + const now = Date.now(); + if (now - lastShot >= SHOOT_COOLDOWN) { + bullets.push(new Bullet(player.x, player.y, mouse.x, mouse.y)); + lastShot = now; + } + }, { passive: false }); + + canvas.addEventListener('touchmove', (e) => { + e.preventDefault(); + const rect = canvas.getBoundingClientRect(); + const touch = e.touches[0]; + mouse.x = touch.clientX - rect.left; + mouse.y = touch.clientY - rect.top; + }, { passive: false }); + + startBtn.addEventListener('click', startGame); + restartBtn.addEventListener('click', startGame); + + window.addEventListener('resize', resize); + resize(); +} + +// --- 6. Neon Puzzle Game --- +function initNeonPuzzle() { + const canvas = document.getElementById('neonpuzzleCanvas'); + const ctx = canvas.getContext('2d'); + const startBtn = document.getElementById('startPuzzleBtn'); + const nextBtn = document.getElementById('nextPuzzleBtn'); + const levelEl = document.getElementById('puzzleLevel'); + const movesEl = document.getElementById('puzzleMoves'); + const overlay = document.getElementById('neonpuzzleOverlay'); + const finalMovesEl = document.getElementById('finalMoves'); + + let gameActive = false; + let level = 1; + let moves = 0; + let nodes = []; + let connections = []; + + const resize = () => { + canvas.width = canvas.parentElement.clientWidth; + canvas.height = canvas.parentElement.clientHeight; + }; + + class Node { + constructor(x, y) { + this.x = x; + this.y = y; + this.connections = []; + this.angle = 0; + } + + draw() { + ctx.save(); + ctx.translate(this.x, this.y); + ctx.rotate(this.angle); + + ctx.fillStyle = '#ff0066'; + ctx.shadowColor = '#ff0066'; + ctx.shadowBlur = 15; + ctx.beginPath(); + ctx.arc(0, 0, 20, 0, Math.PI * 2); + ctx.fill(); + + ctx.fillStyle = '#ffffff'; + ctx.shadowBlur = 0; + ctx.beginPath(); + ctx.arc(0, 0, 8, 0, Math.PI * 2); + ctx.fill(); + + ctx.restore(); + } + + rotate() { + this.angle += Math.PI / 4; + moves++; + movesEl.innerText = moves; + } + } + + const generateLevel = () => { + nodes = []; + connections = []; + moves = 0; + movesEl.innerText = '0'; + + const nodeCount = 4 + level; + const positions = []; + + for (let i = 0; i < nodeCount; i++) { + let x, y; + let valid = false; + let attempts = 0; + + while (!valid && attempts < 100) { + x = 100 + Math.random() * (canvas.width - 200); + y = 100 + Math.random() * (canvas.height - 200); + valid = true; + + for (const pos of positions) { + const dist = Math.sqrt((x - pos.x) ** 2 + (y - pos.y) ** 2); + if (dist < 80) valid = false; + } + attempts++; + } + + if (valid) { + positions.push({ x, y }); + nodes.push(new Node(x, y)); + } + } + + for (let i = 0; i < nodes.length - 1; i++) { + connections.push([i, i + 1]); + nodes[i].connections.push(i + 1); + nodes[i + 1].connections.push(i); + } + + connections.push([nodes.length - 1, 0]); + nodes[0].connections.push(nodes.length - 1); + nodes[nodes.length - 1].connections.push(0); + }; + + const checkWin = () => { + for (const [from, to] of connections) { + const nodeA = nodes[from]; + const nodeB = nodes[to]; + const targetAngle = Math.atan2(nodeB.y - nodeA.y, nodeB.x - nodeA.x); + let currentAngle = nodeA.angle % (Math.PI * 2); + if (currentAngle < 0) currentAngle += Math.PI * 2; + let diff = Math.abs(targetAngle - currentAngle); + if (diff > Math.PI) diff = Math.PI * 2 - diff; + + if (diff > 0.3) return false; + } + + return true; + }; + + const updateGame = () => { + if (!gameActive) return; + + ctx.fillStyle = 'rgba(5, 11, 20, 0.3)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + connections.forEach(([from, to]) => { + const nodeA = nodes[from]; + const nodeB = nodes[to]; + const aligned = Math.abs(nodeA.angle - Math.atan2(nodeB.y - nodeA.y, nodeB.x - nodeA.x)) < 0.3; + + ctx.strokeStyle = aligned ? '#00ff66' : '#ff6600'; + ctx.lineWidth = 3; + ctx.shadowColor = aligned ? '#00ff66' : '#ff6600'; + ctx.shadowBlur = aligned ? 20 : 10; + ctx.beginPath(); + ctx.moveTo(nodeA.x, nodeA.y); + ctx.lineTo(nodeB.x, nodeB.y); + ctx.stroke(); + }); + + nodes.forEach(node => node.draw()); + + requestAnimationFrame(updateGame); + }; + + const startGame = () => { + gameActive = true; + level = 1; + levelEl.innerText = level; + generateLevel(); + overlay.classList.add('hidden'); + updateGame(); + }; + + const nextLevel = () => { + level++; + levelEl.innerText = level; + generateLevel(); + overlay.classList.add('hidden'); + }; + + canvas.addEventListener('click', (e) => { + if (!gameActive) return; + + const rect = canvas.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + for (const node of nodes) { + const dist = Math.sqrt((x - node.x) ** 2 + (y - node.y) ** 2); + if (dist < 25) { + node.rotate(); + break; + } + } + + if (checkWin()) { + gameActive = false; + finalMovesEl.innerText = moves; + overlay.classList.remove('hidden'); + } + }); + + startBtn.addEventListener('click', startGame); + nextBtn.addEventListener('click', nextLevel); + + window.addEventListener('resize', resize); + resize(); +} + +// --- 7. Rhythm Beat Game --- +function initRhythmBeat() { + const canvas = document.getElementById('rhythmCanvas'); + const ctx = canvas.getContext('2d'); + const startBtn = document.getElementById('startRhythmBtn'); + const restartBtn = document.getElementById('restartRhythmBtn'); + const scoreEl = document.getElementById('rhythmScore'); + const comboEl = document.getElementById('rhythmCombo'); + const streakEl = document.getElementById('rhythmStreak'); + const overlay = document.getElementById('rhythmOverlay'); + const finalScoreEl = document.getElementById('finalRhythmScore'); + + let gameActive = false; + let score = 0; + let combo = 0; + let streak = 0; + let beats = []; + let beatInterval = 0; + let totalBeats = 0; + let maxBeats = 50; + let keyFeedback = { D: 0, F: 0, J: 0, K: 0 }; + const keyLabels = ['D', 'F', 'J', 'K']; + const keyColors = ['#ff0066', '#ff6600', '#00ff66', '#00f2ff']; + const laneX = []; + + const resize = () => { + canvas.width = canvas.parentElement.clientWidth; + canvas.height = canvas.parentElement.clientHeight; + const laneWidth = canvas.width / 4; + for (let i = 0; i < 4; i++) { + laneX[i] = laneWidth * i + laneWidth / 2; + } + }; + + const drawKeyIndicators = () => { + keyLabels.forEach((label, i) => { + const x = laneX[i]; + const y = canvas.height - 50; + const feedback = keyFeedback[label]; + + ctx.save(); + ctx.strokeStyle = keyColors[i]; + ctx.shadowColor = keyColors[i]; + ctx.shadowBlur = 15 + feedback * 20; + ctx.lineWidth = 4; + ctx.beginPath(); + ctx.arc(x, y, 30, 0, Math.PI * 2); + ctx.stroke(); + + ctx.fillStyle = feedback > 0 ? keyColors[i] : 'rgba(255,255,255,0.3)'; + ctx.shadowBlur = feedback * 20; + ctx.beginPath(); + ctx.arc(x, y, 25, 0, Math.PI * 2); + ctx.fill(); + + ctx.fillStyle = '#ffffff'; + ctx.shadowBlur = 0; + ctx.font = 'bold 20px Arial'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(label, x, y); + ctx.restore(); + + if (feedback > 0) keyFeedback[label] -= 0.1; + }); + }; + + const drawBeat = (beat) => { + const x = laneX[beat.lane]; + const y = canvas.height - 50 - beat.y; + const progress = beat.y / (canvas.height - 100); + + ctx.save(); + ctx.strokeStyle = keyColors[beat.lane]; + ctx.shadowColor = keyColors[beat.lane]; + ctx.shadowBlur = 20; + ctx.lineWidth = 4; + ctx.beginPath(); + ctx.arc(x, y, 20, 0, Math.PI * 2); + ctx.stroke(); + + ctx.fillStyle = keyColors[beat.lane]; + ctx.beginPath(); + ctx.arc(x, y, 12, 0, Math.PI * 2); + ctx.fill(); + ctx.restore(); + }; + + const spawnBeat = () => { + if (totalBeats >= maxBeats) return; + const lane = Math.floor(Math.random() * 4); + beats.push({ y: 0, lane: lane, hit: false }); + totalBeats++; + }; + + const updateGame = () => { + if (!gameActive) return; + + ctx.fillStyle = 'rgba(5, 11, 20, 0.3)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + for (let i = 0; i < 4; i++) { + ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)'; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(laneX[i], 0); + ctx.lineTo(laneX[i], canvas.height); + ctx.stroke(); + } + + beatInterval++; + if (beatInterval % 40 === 0) { + spawnBeat(); + } + + beats = beats.filter(beat => { + if (beat.hit) return false; + beat.y += 4; + drawBeat(beat); + + if (beat.y > canvas.height - 50) { + combo = 0; + streak = 0; + comboEl.innerText = '0'; + streakEl.innerText = '0'; + return false; + } + return true; + }); + + drawKeyIndicators(); + + if (totalBeats >= maxBeats && beats.length === 0) { + gameActive = false; + finalScoreEl.innerText = score; + overlay.classList.remove('hidden'); + } + + requestAnimationFrame(updateGame); + }; + + const startGame = () => { + gameActive = true; + score = 0; + combo = 0; + streak = 0; + beats = []; + beatInterval = 0; + totalBeats = 0; + keyFeedback = { D: 0, F: 0, J: 0, K: 0 }; + scoreEl.innerText = '0'; + comboEl.innerText = '0'; + streakEl.innerText = '0'; + overlay.classList.add('hidden'); + updateGame(); + }; + + window.addEventListener('keydown', (e) => { + if (!gameActive) return; + const keyIdx = keyLabels.indexOf(e.code.replace('Key', '')); + if (keyIdx === -1) return; + + keyFeedback[keyLabels[keyIdx]] = 1; + + for (const beat of beats) { + if (beat.lane === keyIdx && !beat.hit) { + const distance = Math.abs(beat.y - (canvas.height - 100)); + if (distance < 40) { + beat.hit = true; + if (distance < 15) { + score += 100 + combo * 20; + combo++; + streak++; + } else if (distance < 25) { + score += 50 + combo * 10; + combo++; + streak++; + } else { + score += 20; + combo = 0; + } + scoreEl.innerText = score; + comboEl.innerText = combo; + streakEl.innerText = streak; + break; + } + } + } + }); + + startBtn.addEventListener('click', startGame); + restartBtn.addEventListener('click', startGame); + + window.addEventListener('resize', resize); + resize(); +} + +// --- 8. Cosmic Arena Game --- +function initCosmicArena() { + const canvas = document.getElementById('arenaCanvas'); + const ctx = canvas.getContext('2d'); + const startBtn = document.getElementById('startArenaBtn'); + const restartBtn = document.getElementById('restartArenaBtn'); + const hpEl = document.getElementById('arenaHP'); + const killsEl = document.getElementById('arenaKills'); + const waveEl = document.getElementById('arenaWave'); + const overlay = document.getElementById('arenaOverlay'); + const finalKillsEl = document.getElementById('finalArenaKills'); + + let gameActive = false; + let player = { x: 0, y: 0, hp: 100, speed: 4, size: 30 }; + let enemies = []; + let bullets = []; + let particles = []; + let kills = 0; + let wave = 1; + let enemySpawnTimer = 0; + let spawnRate = 90; + + const keys = {}; + const mouse = { x: 0, y: 0 }; + + const resize = () => { + canvas.width = canvas.parentElement.clientWidth; + canvas.height = canvas.parentElement.clientHeight; + }; + + const drawPlayer = () => { + ctx.save(); + ctx.translate(player.x, player.y); + + const angle = Math.atan2(mouse.y - player.y, mouse.x - player.x); + ctx.rotate(angle); + + ctx.fillStyle = '#00ffcc'; + ctx.shadowColor = '#00ffcc'; + ctx.shadowBlur = 15; + ctx.beginPath(); + ctx.moveTo(20, 0); + ctx.lineTo(-15, -10); + ctx.lineTo(-15, 10); + ctx.closePath(); + ctx.fill(); + + ctx.restore(); + }; + + const drawEnemy = (enemy) => { + ctx.save(); + ctx.translate(enemy.x, enemy.y); + + ctx.fillStyle = enemy.type; + ctx.shadowColor = enemy.type; + ctx.shadowBlur = 10; + + const pulse = Math.sin(Date.now() * 0.01) * 0.2 + 1; + ctx.scale(pulse, pulse); + + ctx.beginPath(); + for (let i = 0; i < 6; i++) { + const angle = (i / 6) * Math.PI * 2; + const x = Math.cos(angle) * enemy.size; + const y = Math.sin(angle) * enemy.size; + if (i === 0) ctx.moveTo(x, y); + else ctx.lineTo(x, y); + } + ctx.closePath(); + ctx.fill(); + + ctx.restore(); + }; + + const drawBullet = (bullet) => { + ctx.save(); + ctx.fillStyle = '#ffffff'; + ctx.shadowColor = '#ffffff'; + ctx.shadowBlur = 10; + ctx.beginPath(); + ctx.arc(bullet.x, bullet.y, 4, 0, Math.PI * 2); + ctx.fill(); + ctx.restore(); + }; + + class Particle { + constructor(x, y, color) { + this.x = x; + this.y = y; + this.color = color; + this.vx = (Math.random() - 0.5) * 6; + this.vy = (Math.random() - 0.5) * 6; + this.life = 1; + this.decay = 0.03 + Math.random() * 0.02; + } + update() { + this.x += this.vx; + this.y += this.vy; + this.life -= this.decay; + } + draw() { + ctx.save(); + ctx.globalAlpha = this.life; + ctx.fillStyle = this.color; + ctx.shadowColor = this.color; + ctx.shadowBlur = 10; + ctx.beginPath(); + ctx.arc(this.x, this.y, 3, 0, Math.PI * 2); + ctx.fill(); + ctx.restore(); + } + } + + const createExplosion = (x, y, color) => { + for (let i = 0; i < 15; i++) { + particles.push(new Particle(x, y, color)); + } + }; + + const spawnEnemy = () => { + const side = Math.floor(Math.random() * 4); + let x, y; + switch(side) { + case 0: x = -30; y = Math.random() * canvas.height; break; + case 1: x = canvas.width + 30; y = Math.random() * canvas.height; break; + case 2: x = Math.random() * canvas.width; y = -30; break; + case 3: x = Math.random() * canvas.width; y = canvas.height + 30; break; + } + + const types = ['#ff0066', '#ff6600', '#ffcc00']; + enemies.push({ + x, y, + size: 15 + Math.random() * 10, + speed: 1 + wave * 0.2, + hp: 2 + Math.floor(wave * 0.5), + type: types[Math.floor(Math.random() * types.length)] + }); + }; + + const updateGame = () => { + if (!gameActive) return; + + ctx.fillStyle = 'rgba(5, 11, 20, 0.2)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + if (keys['KeyW'] || keys['ArrowUp']) player.y = Math.max(player.size, player.y - player.speed); + if (keys['KeyS'] || keys['ArrowDown']) player.y = Math.min(canvas.height - player.size, player.y + player.speed); + if (keys['KeyA'] || keys['ArrowLeft']) player.x = Math.max(player.size, player.x - player.speed); + if (keys['KeyD'] || keys['ArrowRight']) player.x = Math.min(canvas.width - player.size, player.x + player.speed); + + drawPlayer(); + + enemySpawnTimer++; + if (enemySpawnTimer >= spawnRate) { + spawnEnemy(); + enemySpawnTimer = 0; + } + + enemies = enemies.filter(enemy => { + const dx = player.x - enemy.x; + const dy = player.y - enemy.y; + const dist = Math.sqrt(dx * dx + dy * dy); + + if (dist > 0) { + enemy.x += (dx / dist) * enemy.speed; + enemy.y += (dy / dist) * enemy.speed; + } + + if (dist < player.size + enemy.size) { + player.hp -= 10; + hpEl.innerText = Math.max(0, player.hp); + + if (player.hp <= 0) { + gameActive = false; + finalKillsEl.innerText = kills; + overlay.classList.remove('hidden'); + } + } + + drawEnemy(enemy); + return true; + }); + + bullets = bullets.filter(bullet => { + bullet.x += bullet.vx; + bullet.y += bullet.vy; + + for (let i = enemies.length - 1; i >= 0; i--) { + const enemy = enemies[i]; + const dx = bullet.x - enemy.x; + const dy = bullet.y - enemy.y; + const dist = Math.sqrt(dx * dx + dy * dy); + + if (dist < enemy.size) { + enemy.hp--; + createExplosion(bullet.x, bullet.y, '#ffffff'); + if (enemy.hp <= 0) { + createExplosion(enemy.x, enemy.y, enemy.type); + enemies.splice(i, 1); + kills++; + killsEl.innerText = kills; + + if (kills % 5 === 0) { + wave++; + waveEl.innerText = wave; + spawnRate = Math.max(30, 90 - wave * 5); + } + } + return false; + } + } + + if (bullet.x < 0 || bullet.x > canvas.width || bullet.y < 0 || bullet.y > canvas.height) { + return false; + } + + drawBullet(bullet); + return true; + }); + + particles = particles.filter(p => { + p.update(); + p.draw(); + return p.life > 0; + }); + + requestAnimationFrame(updateGame); + }; + + const startGame = () => { + gameActive = true; + player = { x: canvas.width / 2, y: canvas.height / 2, hp: 100, speed: 4, size: 30 }; + enemies = []; + bullets = []; + particles = []; + kills = 0; + wave = 1; + enemySpawnTimer = 0; + spawnRate = 90; + hpEl.innerText = '100'; + killsEl.innerText = '0'; + waveEl.innerText = '1'; + overlay.classList.add('hidden'); + updateGame(); + }; + + window.addEventListener('keydown', (e) => keys[e.code] = true); + window.addEventListener('keyup', (e) => keys[e.code] = false); + + canvas.addEventListener('mousemove', (e) => { + const rect = canvas.getBoundingClientRect(); + mouse.x = e.clientX - rect.left; + mouse.y = e.clientY - rect.top; + }); + + canvas.addEventListener('click', (e) => { + if (!gameActive) return; + const dx = mouse.x - player.x; + const dy = mouse.y - player.y; + const dist = Math.sqrt(dx * dx + dy * dy); + bullets.push({ + x: player.x, y: player.y, + vx: (dx / dist) * 10, + vy: (dy / dist) * 10 + }); + }); + + startBtn.addEventListener('click', startGame); + restartBtn.addEventListener('click', startGame); + + window.addEventListener('resize', resize); + resize(); +} + +// --- 9. Crystal Tetris Game --- +function initTetris() { + const canvas = document.getElementById('tetrisCanvas'); + const ctx = canvas.getContext('2d'); + const startBtn = document.getElementById('startTetrisBtn'); + const restartBtn = document.getElementById('restartTetrisBtn'); + const scoreEl = document.getElementById('tetrisScore'); + const linesEl = document.getElementById('tetrisLines'); + const levelEl = document.getElementById('tetrisLevel'); + const overlay = document.getElementById('tetrisOverlay'); + const finalScoreEl = document.getElementById('finalTetrisScore'); + + let gameActive = false; + let score = 0; + let lines = 0; + let level = 1; + let grid = []; + let currentPiece = null; + let frameCount = 0; + let animationId = null; + const COLS = 10; + const ROWS = 20; + const BLOCK_SIZE = 25; + const TARGET_FPS = 120; + const FRAME_INTERVAL = 1000 / TARGET_FPS; + let lastTime = 0; + + const COLORS = ['#ff0066', '#ff6600', '#ffcc00', '#00ff66', '#00f2ff', '#7000ff', '#ff00ff']; + const SHAPES = [ + [[1, 1, 1, 1]], + [[1, 1, 1], [1]], + [[1, 1], [1, 1]], + [[0, 1, 0], [1, 1, 1]], + [[1, 0, 0], [1, 1, 1]], + [[0, 1, 1], [1, 1, 0]], + [[0, 1, 1, 1], [1, 0, 0, 0]] + ]; + + const getDropSpeed = () => { + const framesPerDrop = Math.max(2, 120 - (level - 1) * 10); + return framesPerDrop; + }; + + const resize = () => { + canvas.width = canvas.parentElement.clientWidth; + canvas.height = canvas.parentElement.clientHeight; + }; + + const createGrid = () => { + grid = []; + for (let r = 0; r < ROWS; r++) { + grid[r] = []; + for (let c = 0; c < COLS; c++) { + grid[r][c] = 0; + } + } + }; + + const createPiece = () => { + const shapeIdx = Math.floor(Math.random() * SHAPES.length); + const color = COLORS[shapeIdx]; + currentPiece = { + shape: SHAPES[shapeIdx], + color: color, + x: Math.floor(COLS / 2) - 1, + y: 0 + }; + }; + + const drawBlock = (x, y, color) => { + ctx.fillStyle = color; + ctx.shadowColor = color; + ctx.shadowBlur = 5; + ctx.fillRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE - 1, BLOCK_SIZE - 1); + + ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)'; + ctx.lineWidth = 2; + ctx.strokeRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE - 1, BLOCK_SIZE - 1); + }; + + const drawGrid = () => { + ctx.fillStyle = 'rgba(5, 11, 20, 0.3)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + for (let r = 0; r < ROWS; r++) { + for (let c = 0; c < COLS; c++) { + if (grid[r][c]) { + drawBlock(c, r, grid[r][c]); + } + } + } + }; + + const drawPiece = () => { + if (!currentPiece) return; + currentPiece.shape.forEach((row, r) => { + row.forEach((value, c) => { + if (value) { + drawBlock(currentPiece.x + c, currentPiece.y + r, currentPiece.color); + } + }); + }); + }; + + const isValidMove = (piece, offsetX, offsetY) => { + return piece.shape.every((row, r) => { + return row.every((value, c) => { + if (!value) return true; + const newX = piece.x + c + offsetX; + const newY = piece.y + r + offsetY; + return newX >= 0 && newX < COLS && newY < ROWS && newY >= 0 && !grid[newY][newX]; + }); + }); + }; + + const lockPiece = () => { + currentPiece.shape.forEach((row, r) => { + row.forEach((value, c) => { + if (value) { + grid[currentPiece.y + r][currentPiece.x + c] = currentPiece.color; + } + }); + }); + + clearLines(); + createPiece(); + + if (!isValidMove(currentPiece, 0, 0)) { + gameOver(); + } + }; + + const clearLines = () => { + let linesCleared = 0; + for (let r = ROWS - 1; r >= 0; r--) { + if (grid[r].every(cell => cell !== 0)) { + grid.splice(r, 1); + grid.unshift(Array(COLS).fill(0)); + linesCleared++; + } + } + + if (linesCleared > 0) { + lines += linesCleared; + score += linesCleared * 100 * linesCleared; + level = Math.floor(lines / 10) + 1; + scoreEl.innerText = score; + linesEl.innerText = lines; + levelEl.innerText = level; + } + }; + + const updateGame = (timestamp) => { + if (!gameActive) return; + + const deltaTime = timestamp - lastTime; + + if (deltaTime >= FRAME_INTERVAL) { + lastTime = timestamp; + frameCount++; + + const dropSpeed = getDropSpeed(); + if (frameCount % dropSpeed === 0) { + if (isValidMove(currentPiece, 0, 1)) { + currentPiece.y++; + } else { + lockPiece(); + } + } + } + + draw(); + animationId = requestAnimationFrame(updateGame); + }; + + const draw = () => { + drawGrid(); + drawPiece(); + }; + + const startGame = () => { + gameActive = true; + score = 0; + lines = 0; + level = 1; + frameCount = 0; + lastTime = performance.now(); + createGrid(); + createPiece(); + scoreEl.innerText = '0'; + linesEl.innerText = '0'; + levelEl.innerText = '1'; + overlay.classList.add('hidden'); + + if (animationId) cancelAnimationFrame(animationId); + animationId = requestAnimationFrame(updateGame); + }; + + const gameOver = () => { + gameActive = false; + if (animationId) cancelAnimationFrame(animationId); + finalScoreEl.innerText = score; + overlay.classList.remove('hidden'); + }; + + window.addEventListener('keydown', (e) => { + if (!gameActive || !currentPiece) return; + + switch(e.code) { + case 'ArrowLeft': + if (isValidMove(currentPiece, -1, 0)) currentPiece.x--; + break; + case 'ArrowRight': + if (isValidMove(currentPiece, 1, 0)) currentPiece.x++; + break; + case 'ArrowDown': + if (isValidMove(currentPiece, 0, 1)) { + currentPiece.y++; + score += 1; + scoreEl.innerText = score; + if (!isValidMove(currentPiece, 0, 1)) { + lockPiece(); + } + } + break; + case 'ArrowUp': + const rotated = currentPiece.shape[0].map((_, i) => currentPiece.shape.map(row => row[i]).reverse()); + if (isValidMove({ ...currentPiece, shape: rotated }, 0, 0)) { + currentPiece.shape = rotated; + } + break; + case 'Space': + while (isValidMove(currentPiece, 0, 1)) { + currentPiece.y++; + score += 2; + } + scoreEl.innerText = score; + lockPiece(); + break; + } + + draw(); + }); + + startBtn.addEventListener('click', startGame); + restartBtn.addEventListener('click', startGame); + + window.addEventListener('resize', resize); + resize(); +} + +// --- 10. Aurora Jumper Platformer Game --- +function initPlatformer() { + const canvas = document.getElementById('platformerCanvas'); + const ctx = canvas.getContext('2d'); + const startBtn = document.getElementById('startPlatformerBtn'); + const restartBtn = document.getElementById('restartPlatformerBtn'); + const coinsEl = document.getElementById('coinsVal'); + const timeEl = document.getElementById('platformerTime'); + const overlay = document.getElementById('platformerOverlay'); + const finalCoinsEl = document.getElementById('finalCoins'); + const finalTimeEl = document.getElementById('finalPlatformerTime'); + + let gameActive = false; + let player = { x: 50, y: 300, vx: 0, vy: 0, onGround: false, size: 25 }; + let platforms = []; + let coins = []; + let collected = 0; + let gameTime = 0; + let timerInterval = null; + let gravity = 0.5; + let jumpForce = -12; + const keys = {}; + + const resize = () => { + canvas.width = canvas.parentElement.clientWidth; + canvas.height = canvas.parentElement.clientHeight; + }; + + const generateLevel = () => { + platforms = []; + coins = []; + + const platformCount = 8 + Math.floor(Math.random() * 5); + const jumpHeight = Math.abs(jumpForce * jumpForce / (2 * gravity)); + const maxReachableY = jumpHeight * 0.9; + + let prevX = 50; + let prevY = canvas.height - 80; + + for (let i = 0; i < platformCount; i++) { + let x, y; + + if (i === 0) { + x = 30; + y = canvas.height - 80; + } else if (i === platformCount - 1) { + x = canvas.width - 130; + y = prevY - maxReachableY + Math.random() * 20; + } else { + const minDeltaX = 40; + const maxDeltaX = canvas.width / platformCount - 20; + x = prevX + minDeltaX + Math.random() * maxDeltaX; + x = Math.max(50, Math.min(canvas.width - 130, x)); + + const direction = Math.random() > 0.5 ? 1 : -1; + y = prevY - (maxReachableY * 0.4 + Math.random() * (maxReachableY * 0.5)) * direction; + y = Math.max(80, Math.min(canvas.height - 80, y)); + } + + const width = 100 + Math.random() * 40; + platforms.push({ x, y, width: Math.floor(width), height: 15, type: i === platformCount - 1 ? 'goal' : 'normal' }); + + if (i < platformCount - 1 && Math.random() > 0.2) { + coins.push({ + x: x + width / 2, + y: y - 30, + size: 12, + collected: false + }); + } + + prevX = x; + prevY = y; + } + + player.x = platforms[0].x + platforms[0].width / 2; + player.y = platforms[0].y - player.size; + player.vx = 0; + player.vy = 0; + }; + + const drawPlayer = () => { + ctx.save(); + + ctx.fillStyle = '#00ff00'; + ctx.shadowColor = '#00ff00'; + ctx.shadowBlur = 15; + + ctx.beginPath(); + ctx.roundRect(player.x - player.size / 2, player.y - player.size, player.size, player.size, 5); + ctx.fill(); + + ctx.fillStyle = '#ffffff'; + ctx.beginPath(); + ctx.arc(player.x + 3, player.y - player.size + 8, 4, 0, Math.PI * 2); + ctx.fill(); + + ctx.restore(); + }; + + const drawPlatform = (platform) => { + ctx.fillStyle = platform.type === 'goal' ? '#00ff66' : '#0099cc'; + ctx.shadowColor = platform.type === 'goal' ? '#00ff00' : '#00f2ff'; + ctx.shadowBlur = 10; + ctx.fillRect(platform.x, platform.y, platform.width, platform.height); + + if (platform.type === 'goal') { + ctx.fillStyle = '#ffffff'; + ctx.font = '12px Orbitron'; + ctx.fillText('GOAL', platform.x + platform.width / 2 - 20, platform.y + 12); + } + }; + + const drawCoin = (coin) => { + if (coin.collected) return; + + ctx.save(); + const pulse = Math.sin(Date.now() * 0.01) * 0.2 + 1; + ctx.translate(coin.x, coin.y); + ctx.scale(pulse, pulse); + + ctx.fillStyle = '#ffcc00'; + ctx.shadowColor = '#ffcc00'; + ctx.shadowBlur = 15; + ctx.beginPath(); + ctx.arc(0, 0, coin.size, 0, Math.PI * 2); + ctx.fill(); + + ctx.fillStyle = '#ffffff'; + ctx.beginPath(); + ctx.arc(0, 0, coin.size / 2, 0, Math.PI * 2); + ctx.fill(); + + ctx.restore(); + }; + + const updateGame = () => { + if (!gameActive) return; + + ctx.fillStyle = 'rgba(5, 11, 20, 0.2)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + if (keys['KeyA'] || keys['ArrowLeft']) player.vx = -5; + else if (keys['KeyD'] || keys['ArrowRight']) player.vx = 5; + else player.vx *= 0.8; + + if ((keys['Space'] || keys['ArrowUp']) && player.onGround) { + player.vy = jumpForce; + player.onGround = false; + } + + player.vy += gravity; + player.x += player.vx; + player.y += player.vy; + + player.onGround = false; + platforms.forEach(platform => { + if (player.x + player.size / 2 > platform.x && + player.x - player.size / 2 < platform.x + platform.width && + player.y >= platform.y && + player.y <= platform.y + platform.height + player.vy + 5) { + if (player.vy > 0) { + player.y = platform.y; + player.vy = 0; + player.onGround = true; + } + + if (platform.type === 'goal') { + gameActive = false; + clearInterval(timerInterval); + finalCoinsEl.innerText = collected; + finalTimeEl.innerText = timeEl.innerText; + overlay.classList.remove('hidden'); + } + } + }); + + player.x = Math.max(player.size / 2, Math.min(canvas.width - player.size / 2, player.x)); + player.y = Math.min(canvas.height, player.y); + + coins.forEach(coin => { + if (coin.collected) return; + + const dx = player.x - coin.x; + const dy = player.y - coin.y; + const dist = Math.sqrt(dx * dx + dy * dy); + + if (dist < player.size + coin.size) { + coin.collected = true; + collected++; + coinsEl.innerText = collected; + } + + drawCoin(coin); + }); + + platforms.forEach(drawPlatform); + drawPlayer(); + + if (player.y > canvas.height + 50) { + player.x = platforms[0].x + platforms[0].width / 2; + player.y = platforms[0].y - player.size; + player.vx = 0; + player.vy = 0; + collected = Math.max(0, collected - 1); + coinsEl.innerText = collected; + } + + requestAnimationFrame(updateGame); + }; + + const formatTime = (seconds) => { + const mins = Math.floor(seconds / 60); + const secs = seconds % 60; + return `${mins}:${secs.toString().padStart(2, '0')}`; + }; + + const startTimer = () => { + gameTime = 0; + timerInterval = setInterval(() => { + gameTime++; + timeEl.innerText = formatTime(gameTime); + }, 1000); + }; + + const startGame = () => { + gameActive = true; + collected = 0; + if (timerInterval) clearInterval(timerInterval); + generateLevel(); + coinsEl.innerText = '0'; + timeEl.innerText = '0:00'; + overlay.classList.add('hidden'); + startTimer(); + updateGame(); + }; + + window.addEventListener('keydown', (e) => keys[e.code] = true); + window.addEventListener('keyup', (e) => keys[e.code] = false); + + startBtn.addEventListener('click', startGame); + restartBtn.addEventListener('click', startGame); + + window.addEventListener('resize', resize); + resize(); +} + +// Export/Import Messages +window.exportMessages = () => { + const messages = JSON.parse(localStorage.getItem('trae_messages') || '[]'); + const dataStr = JSON.stringify(messages, null, 2); + const blob = new Blob([dataStr], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `trae_messages_${new Date().toISOString().split('T')[0]}.json`; + a.click(); + URL.revokeObjectURL(url); +}; + +window.importMessages = (event) => { + const file = event.target.files[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = (e) => { + try { + const data = JSON.parse(e.target.result); + if (Array.isArray(data)) { + localStorage.setItem('trae_messages', JSON.stringify(data)); + location.reload(); + } + } catch (err) { + alert('Invalid file format'); + } + }; + reader.readAsText(file); +}; diff --git a/index.html b/index.html index f4f9491..aaf0743 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + TRAE Aurora | Crystalline Christmas @@ -19,8 +19,14 @@ @@ -30,11 +36,11 @@

TRAE CHRISTMAS

Built using TRAE.AI IDE, GLM 4.7, and Google Gemini 3 Flash.

- + @@ -58,9 +64,59 @@ +
+
+
+ 🎁 +
+ Gift Catcher +
+
+
+ 🐛 +
+ Traoom +
+
+
+ 🧩 +
+ Neon Puzzle +
+
+
+ 🎵 +
+ Rhythm Beat +
+
+
+ ⚔️ +
+ Cosmic Arena +
+
+
+ 💎 +
+ Crystal Tetris +
+
+
+ 🏃 +
+ Aurora Jumper +
+
+
+ +
+ Message Forge +
+
+
-
@@ -78,6 +134,15 @@

+
+

Community Cloud

+
+
+ + + +
+
@@ -89,10 +154,197 @@
Score: 0
+
+ Challenge: + + + +
+
+
+ 60s +
+
+

Leaderboard

+
+
+ + + +
+
+ + + + +
+
+

Traoom

+

Fight against code bugs! Use WASD/Arrows to move, Click to shoot.

+
+
+
+
Kills: 0
+
Time: 0:00
+
Wave: 1
+
+ + +
+ +
+
+

Controls: WASD/Arrows to move | Click to shoot | Space to dash

+
+
+
+ + +
+
+

Neon Puzzle

+

Connect neon paths to complete circuits! Click nodes to rotate.

+
+
+
+
Level: 1
+
Moves: 0
+
+ + +
+ +
+
+

Controls: Click nodes to rotate paths | Complete all connections

+
+
+
+ + +
+
+

Rhythm Beat

+

Hit the beats in sync with the aurora rhythm!

+
+
+
+
Score: 0
+
Combo: 0
+
Streak: 0
+
+ + +
+ +
+
+

Controls: Press keys when beats reach center | Perfect timing = more points

+
+
+
+ + +
+
+

Cosmic Arena

+

Duel in the cosmic void! Defeat opponents to survive.

+
+
+
+
HP: 100
+
Kills: 0
+
Wave: 1
+
+ + +
+ +
+
+

Controls: WASD/Arrows move | Click to attack | Dodge and counter

+
+
+
+ + +
+
+

Crystal Tetris

+

Stack crystals in TRAE aurora style!

+
+
+
+
Score: 0
+
Lines: 0
+
Level: 1
+
+ + +
+ +
+
+

Controls: Arrow keys to move/rotate | Space to drop | Complete lines

+
+
+
+ + +
+
+

Aurora Jumper

+

Jump through crystalline platforms to reach the aurora!

+
+
+
+
Coins: 0
+
Time: 0:00
+
+ + +
+ +
+
+

Controls: WASD/Arrows move | Space to jump | Collect coins

+
diff --git a/style.css b/style.css index 831125e..2ca21e0 100644 --- a/style.css +++ b/style.css @@ -38,6 +38,116 @@ body { max-width: 1200px; margin: 0 auto; padding: 0 20px; + width: 100%; +} + +@media (max-width: 768px) { + .app-container { + padding: 0 5px; + max-width: 100%; + min-height: 100vh; + } + + .glass-nav { + padding: 10px 15px; + margin-top: 5px; + border-radius: 20px; + } + + .logo { + font-size: 1.2rem; + } + + .nav-links { + gap: 12px; + } + + .nav-links a { + font-size: 0.85rem; + } + + .game-container { + height: 60vh; + min-height: 400px; + margin-top: 20px; + border-radius: 15px; + } + + .game-timer { + width: 60px; + height: 60px; + top: 10px; + right: 10px; + } + + #timerDisplay { + font-size: 1.2rem; + } + + .time-btn { + padding: 6px 15px; + font-size: 0.8rem; + } + + .time-selector { + flex-wrap: wrap; + gap: 8px; + } + + .selector-label { + width: 100%; + text-align: center; + margin-bottom: 8px; + } + + .selector-label { + font-size: 0.8rem; + } + + .leader-item { + font-size: 0.85rem; + padding: 8px 10px; + } + + .leader-date, .leader-duration { + font-size: 0.7rem; + } + + .section-card { + margin: 50px 0; + padding: 30px 15px; + } + + .section-card h2 { + font-size: 1.8rem; + } + + .forge-input-group { + flex-direction: column; + padding: 0 10px; + } + + .forge-input-group input { + width: 100%; + } + + .message-cloud { + max-height: 200px; + padding: 20px; + } + + .cloud-msg { + font-size: 0.8rem; + padding: 6px 12px; + } + + .game-container { + min-height: 400px; + } + + .export-buttons { + flex-direction: column; + } } /* Glassmorphism Utility */ @@ -182,6 +292,8 @@ body { justify-content: center; align-items: center; text-align: center; + padding: 80px 20px 20px; + min-height: 600px; } .glitch-text { @@ -260,6 +372,260 @@ body { color: var(--text-dim); } +/* iPhone-style App Icons */ +.app-icons { + display: flex; + gap: 25px; + margin: 40px 0; + flex-wrap: wrap; + justify-content: center; +} + +.app-icon { + display: flex; + flex-direction: column; + align-items: center; + cursor: pointer; + transition: transform 0.3s ease, opacity 0.3s ease; +} + +.app-icon:hover { + transform: scale(1.1); +} + +.app-icon:hover .app-icon-inner { + box-shadow: 0 20px 40px rgba(0, 242, 255, 0.3), + inset 0 0 30px rgba(0, 242, 255, 0.2); +} + +.app-icon-inner { + width: 80px; + height: 80px; + border-radius: 18px; + display: flex; + align-items: center; + justify-content: center; + font-size: 40px; + margin-bottom: 10px; + transition: all 0.3s ease; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); +} + +.gift-icon { + background: linear-gradient(135deg, #00ff66, #00cc44); +} + +.traoom-icon { + background: linear-gradient(135deg, #7000ff, #4a00e0); +} + +.forge-icon { + background: linear-gradient(135deg, #00f2ff, #0099cc); +} + +.app-icon-emoji { + filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3)); +} + +/* Neon Puzzle Game Styles */ +.neonpuzzle-icon { + background: linear-gradient(135deg, #ff0066, #ff6600); +} + +.neonpuzzle-container { + position: relative; + width: 100%; + height: 500px; + background: rgba(0, 0, 0, 0.5); + border-radius: 20px; + overflow: hidden; + margin-top: 40px; + max-width: 100%; + border: 2px solid #ff0066; +} + +#neonpuzzleCanvas { + width: 100%; + height: 100%; + display: block; + touch-action: none; +} + +.neonpuzzle-stats { + position: absolute; + top: 20px; + left: 20px; + display: flex; + gap: 20px; + z-index: 10; +} + +.neonpuzzle-stat { + background: var(--glass-bg); + border: 1px solid var(--glass-border); + padding: 10px 20px; + border-radius: 20px; + font-family: var(--font-accent); + font-size: 1rem; + color: #ff6600; +} + +.neonpuzzle-stat span { + color: #ff0066; + font-weight: bold; +} + +/* Rhythm Beat Game Styles */ +.rhythm-icon { + background: linear-gradient(135deg, #ffcc00, #ff9900); +} + +.rhythm-container { + position: relative; + width: 100%; + height: 500px; + background: rgba(0, 0, 0, 0.5); + border-radius: 20px; + overflow: hidden; + margin-top: 40px; + max-width: 100%; + border: 2px solid #ffcc00; +} + +#rhythmCanvas { + width: 100%; + height: 100%; + display: block; + touch-action: none; +} + +.rhythm-stats { + position: absolute; + top: 20px; + left: 20px; + display: flex; + gap: 20px; + z-index: 10; +} + +.rhythm-stat { + background: var(--glass-bg); + border: 1px solid var(--glass-border); + padding: 10px 20px; + border-radius: 20px; + font-family: var(--font-accent); + font-size: 1rem; + color: #ff9900; +} + +.rhythm-stat span { + color: #ffcc00; + font-weight: bold; +} + +/* Cosmic Arena Game Styles */ +.arena-icon { + background: linear-gradient(135deg, #00ffcc, #0066ff); +} + +.tetris-icon { + background: linear-gradient(135deg, #ff00ff, #ff0066); +} + +.platformer-icon { + background: linear-gradient(135deg, #00ff00, #00cc00); +} + +.arena-container { + position: relative; + width: 100%; + height: 500px; + background: rgba(0, 0, 0, 0.5); + border-radius: 20px; + overflow: hidden; + margin-top: 40px; + max-width: 100%; + border: 2px solid #00ffcc; +} + +#arenaCanvas { + width: 100%; + height: 100%; + display: block; + touch-action: none; +} + +.arena-stats { + position: absolute; + top: 20px; + left: 20px; + display: flex; + gap: 20px; + z-index: 10; +} + +.arena-stat { + background: var(--glass-bg); + border: 1px solid var(--glass-border); + padding: 10px 20px; + border-radius: 20px; + font-family: var(--font-accent); + font-size: 1rem; + color: #0066ff; +} + +.arena-stat span { + color: #00ffcc; + font-weight: bold; +} + +/* Tetris Game Styles */ +.tetris-container { + position: relative; + width: 100%; + height: 500px; + background: rgba(0, 0, 0, 0.5); + border-radius: 20px; + overflow: hidden; + margin-top: 40px; + max-width: 100%; + border: 2px solid #ff00ff; +} + +#tetrisCanvas { + width: 100%; + height: 100%; + display: block; + touch-action: none; +} + +/* Platformer Game Styles */ +.platformer-container { + position: relative; + width: 100%; + height: 500px; + background: rgba(0, 0, 0, 0.5); + border-radius: 20px; + overflow: hidden; + margin-top: 40px; + max-width: 100%; + border: 2px solid #00ff00; +} + +#platformerCanvas { + width: 100%; + height: 100%; + display: block; + touch-action: none; +} + +.app-label { + font-size: 0.85rem; + color: var(--text-primary); + font-weight: 500; + letter-spacing: 0.5px; +} + /* Buttons */ .btn-primary, .btn-secondary { padding: 15px 35px; @@ -350,11 +716,46 @@ body { border-radius: 20px; overflow: hidden; margin-top: 40px; + max-width: 100%; } #gameCanvas { width: 100%; height: 100%; + touch-action: none; + -webkit-touch-callout: none; + -webkit-user-select: none; + user-select: none; + display: block; +} + +.game-timer { + position: absolute; + top: 20px; + right: 20px; + background: var(--glass-bg); + border: 1px solid var(--glass-border); + border-radius: 50%; + width: 80px; + height: 80px; + display: flex; + align-items: center; + justify-content: center; + z-index: 10; + box-shadow: 0 0 30px rgba(0, 255, 102, 0.3); + animation: pulse 1s infinite; +} + +#timerDisplay { + font-family: var(--font-accent); + font-size: 1.5rem; + font-weight: 700; + color: var(--trae-green); +} + +#timerDisplay.warning { + color: #ff3366; + animation: pulse 0.5s infinite; } #gameUI { @@ -372,6 +773,177 @@ body { margin-bottom: 20px; } +/* Message Cloud */ +.message-cloud { + margin-top: 40px; + padding: 30px; + max-height: 250px; + overflow-y: auto; +} + +.message-cloud h3 { + font-family: var(--font-accent); + font-size: 1.2rem; + margin-bottom: 20px; + color: var(--trae-green); +} + +#cloudContainer { + display: flex; + flex-wrap: wrap; + gap: 10px; + justify-content: center; +} + +.cloud-msg { + background: rgba(0, 255, 102, 0.1); + padding: 8px 16px; + border-radius: 20px; + font-size: 0.9rem; + animation: fadeIn 0.5s ease; + border: 1px solid rgba(0, 255, 102, 0.2); +} + +/* Game Over Overlay */ +.game-overlay { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(5, 11, 20, 0.9); + padding: 40px; + border-radius: 20px; + border: 2px solid var(--trae-green); + z-index: 100; + text-align: center; + backdrop-filter: blur(10px); +} + +.game-overlay.hidden { + display: none; +} + +.game-overlay h3 { + font-family: var(--font-accent); + font-size: 2rem; + margin-bottom: 20px; + color: #ff3366; +} + +.input-group { + display: flex; + flex-direction: column; + gap: 15px; +} + +.input-group input { + background: rgba(255, 255, 255, 0.1); + border: 1px solid var(--glass-border); + padding: 12px 20px; + border-radius: 10px; + color: white; + text-align: center; +} + +.time-selector { + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + margin-bottom: 20px; +} + +.selector-label { + font-family: var(--font-accent); + font-size: 0.9rem; + color: var(--trae-green); +} + +.time-btn { + background: rgba(0, 255, 102, 0.1); + border: 1px solid var(--trae-green); + color: var(--trae-green); + padding: 8px 20px; + border-radius: 20px; + font-family: var(--font-accent); + font-size: 0.9rem; + cursor: pointer; + transition: all 0.3s ease; +} + +.time-btn:hover { + background: rgba(0, 255, 102, 0.2); + box-shadow: 0 0 15px rgba(0, 255, 102, 0.3); +} + +.time-btn.active { + background: var(--trae-green); + color: black; + box-shadow: 0 0 20px rgba(0, 255, 102, 0.5); +} + +.export-buttons { + display: flex; + gap: 10px; + margin-top: 20px; + justify-content: center; +} + +.export-btn { + background: rgba(0, 242, 255, 0.1); + border: 1px solid var(--aurora-cyan); + color: var(--aurora-cyan); + padding: 10px 20px; + border-radius: 10px; + font-family: var(--font-accent); + font-size: 0.85rem; + cursor: pointer; + transition: all 0.3s ease; +} + +.export-btn:hover { + background: rgba(0, 242, 255, 0.2); + box-shadow: 0 0 15px rgba(0, 242, 255, 0.3); +} + +/* Leaderboard */ +.leaderboard { + margin-top: 40px; + padding: 30px; + text-align: left; +} + +.leaderboard h3 { + font-family: var(--font-accent); + text-align: center; + margin-bottom: 20px; + color: var(--aurora-cyan); +} + +.leader-item { + display: flex; + justify-content: space-between; + padding: 10px 20px; + border-bottom: 1px solid var(--glass-border); + font-family: var(--font-accent); +} + +.leader-item:last-child { + border: none; +} + +.leader-rank { color: var(--trae-green); } +.leader-name { flex: 1; margin-left: 20px; } +.leader-score { color: var(--aurora-cyan); } +.leader-duration { color: #ff9900; font-size: 0.85rem; margin-left: 10px; } +.leader-date { color: #888; font-size: 0.8rem; margin-left: 10px; } + +@keyframes fadeIn { + from { opacity: 0; transform: scale(0.9); } + to { opacity: 1; transform: scale(1); } +} + +/* Footer Links */ footer { text-align: center; padding: 60px 40px; @@ -404,9 +976,87 @@ footer span, .glm-link, .author-link, .gemini-link { font-family: var(--font-main); } +/* Traoom Game Styles */ +.traoom-container { + position: relative; + width: 100%; + height: 500px; + background: rgba(0, 0, 0, 0.5); + border-radius: 20px; + overflow: hidden; + margin-top: 40px; + max-width: 100%; + border: 2px solid var(--aurora-purple); +} + +#traoomCanvas { + width: 100%; + height: 100%; + display: block; + touch-action: none; +} + +.traoom-stats { + position: absolute; + top: 20px; + left: 20px; + display: flex; + gap: 20px; + z-index: 10; +} + +.traoom-stat { + background: var(--glass-bg); + border: 1px solid var(--glass-border); + padding: 10px 20px; + border-radius: 20px; + font-family: var(--font-accent); + font-size: 1rem; + color: var(--aurora-cyan); +} + +.traoom-stat span { + color: var(--trae-green); + font-weight: bold; +} + +.traoom-controls { + margin-top: 20px; + padding: 15px; + background: var(--glass-bg); + border-radius: 15px; + border: 1px solid var(--glass-border); +} + +.traoom-controls p { + font-size: 0.9rem; + color: var(--text-dim); +} + @media (max-width: 768px) { .glitch-text { font-size: 3rem; } .countdown-container { flex-wrap: wrap; justify-content: center; } .forge-input-group { flex-direction: column; } .btn-secondary { margin-left: 0; margin-top: 15px; } + + .traoom-container { + height: 60vh; + min-height: 400px; + } + + .traoom-stats { + flex-direction: column; + gap: 8px; + left: 10px; + top: 10px; + } + + .traoom-stat { + padding: 6px 12px; + font-size: 0.85rem; + } + + .traoom-controls p { + font-size: 0.8rem; + } }