{"project":{"id":"BFPA45f","userId":"davidyarham@gmail.com","username":null,"userPicture":null,"name":"Jessica's Gymnastics","visible":true,"contributors":"","githubRepo":null,"forkedFrom":null,"isTemplate":false,"tags":"","files":{"folder":"","files":[{"name":"index.html","content":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Untitled</title>\n  <link rel=\"stylesheet\" href=\"style.css\">\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n<link href=\"https://fonts.googleapis.com/css2?family=Lilita+One&family=Quicksand:wght@500;700&display=swap\" rel=\"stylesheet\">\n<script src=\"https://unpkg.com/lucide@latest/dist/umd/lucide.js\"></script>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n</head>\n<body>\n<div class=\"game\">\n\t<div class=\"game__bg\">\n\t\t<div class=\"game__bg-stripe game__bg-stripe--1\"></div>\n\t\t<div class=\"game__bg-stripe game__bg-stripe--2\"></div>\n\t\t<div class=\"game__bg-stripe game__bg-stripe--3\"></div>\n\t</div>\n\n\t<div class=\"game__title-screen\" id=\"titleScreen\">\n\t\t<div class=\"game__logo\">\n\t\t\t<div class=\"game__logo-star\">★</div>\n\t\t\t<h1 class=\"game__title\">Jessica's<br>Gymnastics</h1>\n\t\t\t<p class=\"game__subtitle\">Tap & flip your way to gold!</p>\n\t\t</div>\n\t\t<button class=\"game__btn game__btn--start\" id=\"startBtn\">\n      <span>Start Routine</span>\n    </button>\n\t\t<div class=\"game__instructions\">\n\t\t\t<div class=\"game__instruction\">\n\t\t\t\t<kbd>←</kbd><kbd>→</kbd> or Swipe to move\n\t\t\t</div>\n\t\t\t<div class=\"game__instruction\">\n\t\t\t\t<kbd>Space</kbd> or Tap to flip\n\t\t\t</div>\n\t\t\t<div class=\"game__instruction\">\n\t\t\t\tLand on platforms to score!\n\t\t\t</div>\n\t\t</div>\n\t</div>\n\n\t<div class=\"game__hud\" id=\"hud\">\n\t\t<div class=\"game__hud-item\">\n\t\t\t<span class=\"game__hud-label\">Score</span>\n\t\t\t<span class=\"game__hud-value\" id=\"scoreDisplay\">0</span>\n\t\t</div>\n\t\t<div class=\"game__hud-item\">\n\t\t\t<span class=\"game__hud-label\">Combo</span>\n\t\t\t<span class=\"game__hud-value game__hud-value--combo\" id=\"comboDisplay\">x1</span>\n\t\t</div>\n\t\t<div class=\"game__hud-item\">\n\t\t\t<span class=\"game__hud-label\">Best</span>\n\t\t\t<span class=\"game__hud-value\" id=\"bestDisplay\">0</span>\n\t\t</div>\n\t</div>\n\n\t<canvas id=\"gameCanvas\"></canvas>\n\n\t<div class=\"game__controls\" id=\"mobileControls\">\n\t\t<button class=\"game__ctrl game__ctrl--left\" id=\"btnLeft\">\n      <i data-lucide=\"chevron-left\"></i>\n    </button>\n\t\t<button class=\"game__ctrl game__ctrl--jump\" id=\"btnSuperJump\">\n      <i data-lucide=\"arrow-up\"></i>\n      <span>SUPER</span>\n    </button>\n\t\t<button class=\"game__ctrl game__ctrl--flip\" id=\"btnFlip\">\n      <i data-lucide=\"rotate-cw\"></i>\n      <span>FLIP!</span>\n    </button>\n\t\t<button class=\"game__ctrl game__ctrl--right\" id=\"btnRight\">\n      <i data-lucide=\"chevron-right\"></i>\n    </button>\n\t</div>\n\n\t<div class=\"game__over\" id=\"gameOver\">\n\t\t<div class=\"game__over-card\">\n\t\t\t<div class=\"game__over-stars\" id=\"overStars\">★★★</div>\n\t\t\t<h2 class=\"game__over-title\">Routine Complete!</h2>\n\t\t\t<div class=\"game__over-score\">\n\t\t\t\t<span>Final Score</span>\n\t\t\t\t<strong id=\"finalScore\">0</strong>\n\t\t\t</div>\n\t\t\t<div class=\"game__over-msg\" id=\"overMsg\">Great performance!</div>\n\t\t\t<button class=\"game__btn game__btn--retry\" id=\"retryBtn\">\n        <span>Try Again</span>\n      </button>\n\t\t</div>\n\t</div>\n\n\t<div class=\"game__particles\" id=\"particles\"></div>\n</div>\n  <script type=\"module\" src=\"main.js\"></script>\n</body>\n</html>"},{"name":"main.js","content":"// ===== SOUND ENGINE (Web Audio API) =====\nconst AudioCtx = window.AudioContext || window.webkitAudioContext;\nlet audioCtx;\n\nfunction ensureAudio() {\n\tif (!audioCtx) audioCtx = new AudioCtx();\n\tif (audioCtx.state === 'suspended') audioCtx.resume();\n}\n\nfunction playTone(freq, duration, type = 'square', volume = 0.15) {\n\tif (!audioCtx) return;\n\tconst osc = audioCtx.createOscillator();\n\tconst gain = audioCtx.createGain();\n\tosc.type = type;\n\tosc.frequency.setValueAtTime(freq, audioCtx.currentTime);\n\tgain.gain.setValueAtTime(volume, audioCtx.currentTime);\n\tgain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + duration);\n\tosc.connect(gain);\n\tgain.connect(audioCtx.destination);\n\tosc.start();\n\tosc.stop(audioCtx.currentTime + duration);\n}\n\nfunction sfxJump() {\n\tplayTone(400, 0.12, 'square', 0.1);\n\tsetTimeout(() => playTone(600, 0.1, 'square', 0.08), 50);\n}\n\nfunction sfxSuperJump() {\n\tplayTone(300, 0.15, 'sine', 0.12);\n\tsetTimeout(() => playTone(500, 0.12, 'sine', 0.1), 50);\n\tsetTimeout(() => playTone(800, 0.15, 'sine', 0.1), 100);\n\tsetTimeout(() => playTone(1000, 0.2, 'sine', 0.08), 150);\n}\n\nfunction sfxFlip() {\n\tplayTone(300, 0.15, 'sawtooth', 0.08);\n\tsetTimeout(() => playTone(500, 0.12, 'sawtooth', 0.06), 60);\n\tsetTimeout(() => playTone(700, 0.1, 'sawtooth', 0.05), 120);\n}\n\nfunction sfxLand(combo) {\n\tconst base = 500 + combo * 80;\n\tplayTone(base, 0.2, 'sine', 0.12);\n\tsetTimeout(() => playTone(base * 1.25, 0.15, 'sine', 0.1), 80);\n\tsetTimeout(() => playTone(base * 1.5, 0.12, 'sine', 0.08), 160);\n}\n\nfunction sfxPerfect() {\n\tconst notes = [523, 659, 784, 1047];\n\tnotes.forEach((n, i) => {\n\t\tsetTimeout(() => playTone(n, 0.25, 'sine', 0.1), i * 80);\n\t});\n}\n\nfunction sfxFall() {\n\tplayTone(300, 0.3, 'sawtooth', 0.1);\n\tsetTimeout(() => playTone(200, 0.3, 'sawtooth', 0.08), 100);\n\tsetTimeout(() => playTone(100, 0.4, 'sawtooth', 0.06), 200);\n}\n\nfunction sfxGameOver() {\n\tconst notes = [400, 350, 300, 200];\n\tnotes.forEach((n, i) => {\n\t\tsetTimeout(() => playTone(n, 0.35, 'triangle', 0.1), i * 150);\n\t});\n}\n\n// ===== GAME ENGINE =====\nconst canvas = document.getElementById('gameCanvas');\nconst ctx = canvas.getContext('2d');\nconst particles = document.getElementById('particles');\n\nlet W, H;\n\nfunction resize() {\n\tW = canvas.width = window.innerWidth;\n\tH = canvas.height = window.innerHeight;\n}\nwindow.addEventListener('resize', resize);\nresize();\n\n// Game State\nconst STATE = {\n\tTITLE: 0,\n\tPLAYING: 1,\n\tOVER: 2\n};\nlet state = STATE.TITLE;\nlet score = 0;\nlet bestScore = parseInt(localStorage.getItem('jessicaBest') || '0');\nlet combo = 0;\nlet flipping = false;\nlet flipAngle = 0;\nlet flipCount = 0;\n\n// Jessica (the gymnast)\nconst jessica = {\n\tx: 0,\n\ty: 0,\n\tvx: 0,\n\tvy: 0,\n\tw: 30,\n\th: 40,\n\tonGround: false,\n\trotation: 0,\n\ttargetRotation: 0,\n\tlives: 3,\n\ttrail: []\n};\n\n// Platforms\nlet platforms = [];\nlet platformSpeed = 1.5;\nlet spawnTimer = 0;\nlet difficulty = 0;\n\n// Stars (collectibles)\nlet stars = [];\n\n// Camera\nlet cameraY = 0;\n\n// Input\nconst keys = {};\nlet moveLeft = false;\nlet moveRight = false;\nlet doFlip = false;\nlet superJumpReady = true;\nlet superJumpActive = false;\nlet superJumpCooldown = 0;\nconst SUPER_JUMP_COOLDOWN = 3000;\n\nfunction initGame() {\n\tscore = 0;\n\tcombo = 0;\n\tflipping = false;\n\tflipAngle = 0;\n\tflipCount = 0;\n\tdifficulty = 0;\n\tplatformSpeed = 1.5;\n\tsuperJumpReady = true;\n\tsuperJumpActive = false;\n\tsuperJumpCooldown = 0;\n\tconst sjBtn = document.getElementById('btnSuperJump');\n\tif (sjBtn) sjBtn.classList.remove('cooldown');\n\n\tjessica.x = W / 2;\n\tjessica.y = H - 120;\n\tjessica.vx = 0;\n\tjessica.vy = 0;\n\tjessica.onGround = false;\n\tjessica.rotation = 0;\n\tjessica.trail = [];\n\n\tplatforms = [];\n\tstars = [];\n\tcameraY = 0;\n\n\t// Ground platform\n\tplatforms.push({\n\t\tx: 0,\n\t\ty: H - 60,\n\t\tw: W,\n\t\th: 20,\n\t\ttype: 'ground',\n\t\tcolor: '#a855f7'\n\t});\n\n\t// Initial platforms\n\tfor (let i = 0; i < 8; i++) {\n\t\tspawnPlatform(H - 140 - i * 100);\n\t}\n\n\tupdateHUD();\n}\n\nfunction spawnPlatform(y) {\n\tconst minW = Math.max(60, 120 - difficulty * 2);\n\tconst maxW = Math.max(80, 180 - difficulty * 3);\n\tconst w = minW + Math.random() * (maxW - minW);\n\tconst x = 30 + Math.random() * (W - w - 60);\n\n\tconst types = ['normal', 'normal', 'normal', 'bouncy', 'moving'];\n\tconst type = difficulty > 3 ? types[Math.floor(Math.random() * types.length)] : 'normal';\n\n\tconst colors = ['#ff2d78', '#06b6d4', '#a855f7', '#fbbf24', '#22c55e'];\n\n\tconst p = {\n\t\tx,\n\t\ty,\n\t\tw,\n\t\th: 14,\n\t\ttype,\n\t\tcolor: colors[Math.floor(Math.random() * colors.length)],\n\t\tmoveDir: Math.random() > 0.5 ? 1 : -1,\n\t\tmoveSpeed: 1 + Math.random() * 1.5,\n\t\torigX: x\n\t};\n\n\tplatforms.push(p);\n\n\t// Maybe spawn a star\n\tif (Math.random() < 0.3) {\n\t\tstars.push({\n\t\t\tx: x + w / 2,\n\t\t\ty: y - 30,\n\t\t\tcollected: false,\n\t\t\tbobOffset: Math.random() * Math.PI * 2\n\t\t});\n\t}\n}\n\nfunction updateHUD() {\n\tdocument.getElementById('scoreDisplay').textContent = score;\n\tdocument.getElementById('comboDisplay').textContent = 'x' + Math.max(1, combo);\n\tdocument.getElementById('bestDisplay').textContent = bestScore;\n}\n\nfunction spawnParticles(x, y, color, count = 8) {\n\tfor (let i = 0; i < count; i++) {\n\t\tconst el = document.createElement('div');\n\t\tel.className = 'game__particle';\n\t\tconst angle = (Math.PI * 2 / count) * i + Math.random() * 0.5;\n\t\tconst dist = 30 + Math.random() * 50;\n\t\tconst tx = Math.cos(angle) * dist;\n\t\tconst ty = Math.sin(angle) * dist;\n\t\tel.style.cssText = `\n      left: ${x}px;\n      top: ${y - cameraY}px;\n      width: ${4 + Math.random() * 6}px;\n      height: ${4 + Math.random() * 6}px;\n      background: ${color};\n      --tx: ${tx}px;\n      --ty: ${ty}px;\n    `;\n\t\tparticles.appendChild(el);\n\t\tsetTimeout(() => el.remove(), 800);\n\t}\n}\n\nfunction showFloatText(x, y, text, color = '#fbbf24') {\n\tconst el = document.createElement('div');\n\tel.className = 'game__float-text';\n\tel.textContent = text;\n\tel.style.left = x + 'px';\n\tel.style.top = (y - cameraY) + 'px';\n\tel.style.color = color;\n\tparticles.appendChild(el);\n\tsetTimeout(() => el.remove(), 1000);\n}\n\n// ===== GAME LOOP =====\nlet lastTime = 0;\nconst GRAVITY = 0.45;\nconst MOVE_SPEED = 5;\nconst JUMP_FORCE = -11;\nconst FLIP_SPEED = 12;\n\nfunction update(dt) {\n\tif (state !== STATE.PLAYING) return;\n\n\t// Input\n\tconst left = keys['ArrowLeft'] || keys['KeyA'] || moveLeft;\n\tconst right = keys['ArrowRight'] || keys['KeyD'] || moveRight;\n\n\tif (left) jessica.vx = -MOVE_SPEED;\n\telse if (right) jessica.vx = MOVE_SPEED;\n\telse jessica.vx *= 0.85;\n\n\t// Flip\n\tif ((keys['Space'] || doFlip) && !flipping && !jessica.onGround) {\n\t\tflipping = true;\n\t\tflipAngle = 0;\n\t\tflipCount++;\n\t\tsfxFlip();\n\t\tdoFlip = false;\n\t}\n\tif (doFlip) doFlip = false;\n\n\t// Super jump cooldown\n\tif (!superJumpReady) {\n\t\tsuperJumpCooldown -= dt;\n\t\tif (superJumpCooldown <= 0) {\n\t\t\tsuperJumpReady = true;\n\t\t\tconst sjBtn = document.getElementById('btnSuperJump');\n\t\t\tif (sjBtn) sjBtn.classList.remove('cooldown');\n\t\t}\n\t}\n\n\t// Apply gravity\n\tjessica.vy += GRAVITY;\n\tjessica.x += jessica.vx;\n\tjessica.y += jessica.vy;\n\n\t// Flip animation\n\tif (flipping) {\n\t\tflipAngle += FLIP_SPEED;\n\t\tjessica.rotation += FLIP_SPEED * (Math.PI / 180);\n\t\tif (flipAngle >= 360) {\n\t\t\tflipping = false;\n\t\t\tflipAngle = 0;\n\t\t\tjessica.rotation = 0;\n\t\t}\n\t} else if (jessica.onGround) {\n\t\tjessica.rotation *= 0.8;\n\t}\n\n\t// Trail\n\tjessica.trail.push({\n\t\tx: jessica.x,\n\t\ty: jessica.y,\n\t\tage: 0\n\t});\n\tif (jessica.trail.length > 12) jessica.trail.shift();\n\tjessica.trail.forEach(t => t.age++);\n\n\t// Wrap horizontal\n\tif (jessica.x < -jessica.w) jessica.x = W + jessica.w;\n\tif (jessica.x > W + jessica.w) jessica.x = -jessica.w;\n\n\t// Camera follows jessica upward\n\tconst targetCamY = Math.min(0, jessica.y - H * 0.4);\n\tcameraY += (targetCamY - cameraY) * 0.08;\n\n\t// Platform collision\n\tjessica.onGround = false;\n\tfor (const p of platforms) {\n\t\t// Moving platforms\n\t\tif (p.type === 'moving') {\n\t\t\tp.x += p.moveDir * p.moveSpeed;\n\t\t\tif (p.x < 20 || p.x + p.w > W - 20) p.moveDir *= -1;\n\t\t}\n\n\t\t// Collision from above\n\t\tif (\n\t\t\tjessica.vy >= 0 &&\n\t\t\tjessica.x + jessica.w / 2 > p.x &&\n\t\t\tjessica.x - jessica.w / 2 < p.x + p.w &&\n\t\t\tjessica.y + jessica.h / 2 > p.y &&\n\t\t\tjessica.y + jessica.h / 2 < p.y + p.h + jessica.vy + 5\n\t\t) {\n\t\t\tjessica.y = p.y - jessica.h / 2;\n\t\t\tjessica.onGround = true;\n\n\t\t\tconst wasFlipping = flipCount > 0;\n\n\t\t\tconst jumpMultiplier = superJumpActive ? 2.0 : 1.0;\n\t\t\tif (p.type === 'bouncy') {\n\t\t\t\tjessica.vy = JUMP_FORCE * 1.4 * jumpMultiplier;\n\t\t\t\tjessica.onGround = false;\n\t\t\t\tif (superJumpActive) {\n\t\t\t\t\tsfxSuperJump();\n\t\t\t\t} else {\n\t\t\t\t\tsfxJump();\n\t\t\t\t}\n\t\t\t\tspawnParticles(jessica.x, jessica.y + jessica.h / 2, superJumpActive ? '#06b6d4' : '#fbbf24', superJumpActive ? 16 : 10);\n\t\t\t} else {\n\t\t\t\tjessica.vy = JUMP_FORCE * jumpMultiplier;\n\t\t\t\tjessica.onGround = false;\n\t\t\t\tif (superJumpActive) {\n\t\t\t\t\tsfxSuperJump();\n\t\t\t\t} else {\n\t\t\t\t\tsfxJump();\n\t\t\t\t}\n\t\t\t\tif (superJumpActive) spawnParticles(jessica.x, jessica.y + jessica.h / 2, '#06b6d4', 14);\n\t\t\t}\n\t\t\tif (superJumpActive) {\n\t\t\t\tshowFloatText(jessica.x, jessica.y, 'SUPER JUMP!', '#06b6d4');\n\t\t\t\tsuperJumpActive = false;\n\t\t\t}\n\n\t\t\t// Score flips\n\t\t\tif (wasFlipping) {\n\t\t\t\tconst points = flipCount * 100 * Math.max(1, combo);\n\t\t\t\tscore += points;\n\t\t\t\tcombo++;\n\t\t\t\tsfxLand(combo);\n\n\t\t\t\tconst screenY = jessica.y - cameraY;\n\t\t\t\tif (flipCount >= 2) {\n\t\t\t\t\tsfxPerfect();\n\t\t\t\t\tshowFloatText(jessica.x, jessica.y, `AMAZING! +${points}`, '#ff2d78');\n\t\t\t\t\tspawnParticles(jessica.x, jessica.y, '#ff2d78', 16);\n\t\t\t\t} else {\n\t\t\t\t\tshowFloatText(jessica.x, jessica.y, `+${points}`);\n\t\t\t\t\tspawnParticles(jessica.x, jessica.y + jessica.h / 2, p.color, 8);\n\t\t\t\t}\n\n\t\t\t\t// Combo bump animation\n\t\t\t\tconst comboEl = document.getElementById('comboDisplay');\n\t\t\t\tcomboEl.classList.remove('bump');\n\t\t\t\tvoid comboEl.offsetWidth;\n\t\t\t\tcomboEl.classList.add('bump');\n\t\t\t} else {\n\t\t\t\tcombo = 0;\n\t\t\t\tscore += 10;\n\t\t\t\tspawnParticles(jessica.x, jessica.y + jessica.h / 2, p.color, 4);\n\t\t\t}\n\n\t\t\tflipCount = 0;\n\t\t\tflipping = false;\n\t\t\tflipAngle = 0;\n\t\t\tjessica.rotation = 0;\n\n\t\t\tupdateHUD();\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Star collection\n\tfor (const s of stars) {\n\t\tif (s.collected) continue;\n\t\tconst dx = jessica.x - s.x;\n\t\tconst dy = jessica.y - s.y;\n\t\tif (Math.sqrt(dx * dx + dy * dy) < 30) {\n\t\t\ts.collected = true;\n\t\t\tscore += 50;\n\t\t\tsfxPerfect();\n\t\t\tspawnParticles(s.x, s.y, '#fbbf24', 12);\n\t\t\tshowFloatText(s.x, s.y, '★ +50', '#fbbf24');\n\t\t\tupdateHUD();\n\t\t}\n\t}\n\n\t// Spawn new platforms as we go up\n\tconst highestPlatform = Math.min(...platforms.map(p => p.y));\n\tif (highestPlatform > cameraY - 200) {\n\t\tconst count = 3 + Math.floor(Math.random() * 2);\n\t\tfor (let i = 0; i < count; i++) {\n\t\t\tspawnPlatform(highestPlatform - 80 - i * (90 + Math.random() * 40));\n\t\t}\n\t\tdifficulty += 0.3;\n\t}\n\n\t// Remove far below platforms\n\tplatforms = platforms.filter(p => p.y < cameraY + H + 200);\n\tstars = stars.filter(s => s.y < cameraY + H + 200);\n\n\t// Fall death\n\tif (jessica.y > cameraY + H + 100) {\n\t\tgameOver();\n\t}\n}\n\nfunction drawJessica() {\n\tctx.save();\n\tctx.translate(jessica.x, jessica.y);\n\tctx.rotate(jessica.rotation);\n\n\t// Body\n\tconst gradient = ctx.createLinearGradient(-12, -20, 12, 20);\n\tgradient.addColorStop(0, '#ff6ba6');\n\tgradient.addColorStop(1, '#ff2d78');\n\n\t// Leotard body\n\tctx.fillStyle = gradient;\n\tctx.beginPath();\n\tctx.ellipse(0, 0, 12, 18, 0, 0, Math.PI * 2);\n\tctx.fill();\n\n\t// Head\n\tctx.fillStyle = '#fdd9b5';\n\tctx.beginPath();\n\tctx.arc(0, -22, 10, 0, Math.PI * 2);\n\tctx.fill();\n\n\t// Hair\n\tctx.fillStyle = '#5c3317';\n\tctx.beginPath();\n\tctx.arc(0, -25, 10, Math.PI, 0);\n\tctx.fill();\n\n\t// Hair ponytail\n\tctx.beginPath();\n\tctx.moveTo(5, -28);\n\tctx.quadraticCurveTo(18, -35, 14, -20);\n\tctx.lineWidth = 3;\n\tctx.strokeStyle = '#5c3317';\n\tctx.stroke();\n\n\t// Eyes\n\tctx.fillStyle = '#333';\n\tctx.beginPath();\n\tctx.arc(-4, -22, 2, 0, Math.PI * 2);\n\tctx.arc(4, -22, 2, 0, Math.PI * 2);\n\tctx.fill();\n\n\t// Smile\n\tctx.strokeStyle = '#e74c6f';\n\tctx.lineWidth = 1.5;\n\tctx.beginPath();\n\tctx.arc(0, -19, 4, 0.2, Math.PI - 0.2);\n\tctx.stroke();\n\n\t// Arms\n\tctx.strokeStyle = '#fdd9b5';\n\tctx.lineWidth = 4;\n\tctx.lineCap = 'round';\n\n\tconst armWave = Math.sin(Date.now() / 150) * 0.3;\n\t// Left arm\n\tctx.beginPath();\n\tctx.moveTo(-10, -8);\n\tctx.lineTo(-22, -15 + Math.sin(armWave) * 5);\n\tctx.stroke();\n\t// Right arm\n\tctx.beginPath();\n\tctx.moveTo(10, -8);\n\tctx.lineTo(22, -15 + Math.cos(armWave) * 5);\n\tctx.stroke();\n\n\t// Legs\n\tctx.strokeStyle = '#fdd9b5';\n\tctx.lineWidth = 4;\n\tconst legAngle = jessica.onGround ? 0 : Math.sin(Date.now() / 100) * 0.4;\n\t// Left leg\n\tctx.beginPath();\n\tctx.moveTo(-6, 14);\n\tctx.lineTo(-10 - Math.sin(legAngle) * 5, 30);\n\tctx.stroke();\n\t// Right leg\n\tctx.beginPath();\n\tctx.moveTo(6, 14);\n\tctx.lineTo(10 + Math.sin(legAngle) * 5, 30);\n\tctx.stroke();\n\n\t// Sparkle effect when flipping\n\tif (flipping) {\n\t\tctx.globalAlpha = 0.6;\n\t\tfor (let i = 0; i < 3; i++) {\n\t\t\tconst angle = (Date.now() / 50 + i * 2.1);\n\t\t\tconst sx = Math.cos(angle) * 25;\n\t\t\tconst sy = Math.sin(angle) * 25;\n\t\t\tctx.fillStyle = i % 2 === 0 ? '#fbbf24' : '#ff2d78';\n\t\t\tctx.beginPath();\n\t\t\tctx.arc(sx, sy, 3, 0, Math.PI * 2);\n\t\t\tctx.fill();\n\t\t}\n\t\tctx.globalAlpha = 1;\n\t}\n\n\tctx.restore();\n}\n\nfunction draw() {\n\tctx.clearRect(0, 0, W, H);\n\n\tctx.save();\n\tctx.translate(0, -cameraY);\n\n\t// Draw trail\n\tfor (let i = 0; i < jessica.trail.length; i++) {\n\t\tconst t = jessica.trail[i];\n\t\tconst alpha = (1 - t.age / 15) * 0.3;\n\t\tif (alpha <= 0) continue;\n\t\tctx.globalAlpha = alpha;\n\t\tctx.fillStyle = '#ff2d78';\n\t\tctx.beginPath();\n\t\tctx.arc(t.x, t.y, 6 - (t.age / 15) * 4, 0, Math.PI * 2);\n\t\tctx.fill();\n\t}\n\tctx.globalAlpha = 1;\n\n\t// Draw platforms\n\tfor (const p of platforms) {\n\t\tctx.save();\n\n\t\tif (p.type === 'ground') {\n\t\t\t// Ground\n\t\t\tconst gGrad = ctx.createLinearGradient(0, p.y, 0, p.y + p.h);\n\t\t\tgGrad.addColorStop(0, '#a855f7');\n\t\t\tgGrad.addColorStop(1, '#7c3aed');\n\t\t\tctx.fillStyle = gGrad;\n\t\t\tctx.fillRect(p.x, p.y, p.w, p.h + 200);\n\n\t\t\t// Mat pattern\n\t\t\tctx.fillStyle = 'rgba(255,255,255,0.05)';\n\t\t\tfor (let i = 0; i < p.w; i += 40) {\n\t\t\t\tctx.fillRect(p.x + i, p.y, 20, p.h);\n\t\t\t}\n\t\t} else {\n\t\t\t// Platform shadow\n\t\t\tctx.fillStyle = 'rgba(0,0,0,0.2)';\n\t\t\troundRect(ctx, p.x + 2, p.y + 3, p.w, p.h, 7);\n\t\t\tctx.fill();\n\n\t\t\t// Platform body\n\t\t\tconst pGrad = ctx.createLinearGradient(p.x, p.y, p.x, p.y + p.h);\n\t\t\tpGrad.addColorStop(0, p.color);\n\t\t\tpGrad.addColorStop(1, shadeColor(p.color, -30));\n\t\t\tctx.fillStyle = pGrad;\n\t\t\troundRect(ctx, p.x, p.y, p.w, p.h, 7);\n\t\t\tctx.fill();\n\n\t\t\t// Shine\n\t\t\tctx.fillStyle = 'rgba(255,255,255,0.25)';\n\t\t\troundRect(ctx, p.x + 3, p.y + 2, p.w - 6, p.h / 2 - 1, 4);\n\t\t\tctx.fill();\n\n\t\t\t// Bouncy indicator\n\t\t\tif (p.type === 'bouncy') {\n\t\t\t\tctx.fillStyle = 'rgba(255,255,255,0.5)';\n\t\t\t\tconst bounce = Math.sin(Date.now() / 200) * 2;\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.moveTo(p.x + p.w / 2 - 8, p.y - 4 + bounce);\n\t\t\t\tctx.lineTo(p.x + p.w / 2, p.y - 10 + bounce);\n\t\t\t\tctx.lineTo(p.x + p.w / 2 + 8, p.y - 4 + bounce);\n\t\t\t\tctx.fill();\n\t\t\t}\n\n\t\t\t// Moving indicator\n\t\t\tif (p.type === 'moving') {\n\t\t\t\tctx.fillStyle = 'rgba(255,255,255,0.4)';\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.arc(p.x + 8, p.y + p.h / 2, 2, 0, Math.PI * 2);\n\t\t\t\tctx.arc(p.x + p.w - 8, p.y + p.h / 2, 2, 0, Math.PI * 2);\n\t\t\t\tctx.fill();\n\t\t\t}\n\t\t}\n\n\t\tctx.restore();\n\t}\n\n\t// Draw stars\n\tfor (const s of stars) {\n\t\tif (s.collected) continue;\n\t\tconst bob = Math.sin(Date.now() / 300 + s.bobOffset) * 4;\n\t\tctx.save();\n\t\tctx.translate(s.x, s.y + bob);\n\t\tctx.rotate(Date.now() / 500);\n\n\t\t// Glow\n\t\tctx.shadowColor = '#fbbf24';\n\t\tctx.shadowBlur = 15;\n\n\t\tctx.fillStyle = '#fbbf24';\n\t\tdrawStar(ctx, 0, 0, 5, 10, 5);\n\t\tctx.fill();\n\n\t\tctx.shadowBlur = 0;\n\t\tctx.restore();\n\t}\n\n\t// Draw Jessica\n\tdrawJessica();\n\n\tctx.restore();\n}\n\nfunction drawStar(ctx, cx, cy, spikes, outerR, innerR) {\n\tlet rot = Math.PI / 2 * 3;\n\tlet x = cx;\n\tlet y = cy;\n\tconst step = Math.PI / spikes;\n\n\tctx.beginPath();\n\tctx.moveTo(cx, cy - outerR);\n\tfor (let i = 0; i < spikes; i++) {\n\t\tx = cx + Math.cos(rot) * outerR;\n\t\ty = cy + Math.sin(rot) * outerR;\n\t\tctx.lineTo(x, y);\n\t\trot += step;\n\t\tx = cx + Math.cos(rot) * innerR;\n\t\ty = cy + Math.sin(rot) * innerR;\n\t\tctx.lineTo(x, y);\n\t\trot += step;\n\t}\n\tctx.lineTo(cx, cy - outerR);\n\tctx.closePath();\n}\n\nfunction roundRect(ctx, x, y, w, h, r) {\n\tctx.beginPath();\n\tctx.moveTo(x + r, y);\n\tctx.lineTo(x + w - r, y);\n\tctx.quadraticCurveTo(x + w, y, x + w, y + r);\n\tctx.lineTo(x + w, y + h - r);\n\tctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);\n\tctx.lineTo(x + r, y + h);\n\tctx.quadraticCurveTo(x, y + h, x, y + h - r);\n\tctx.lineTo(x, y + r);\n\tctx.quadraticCurveTo(x, y, x + r, y);\n\tctx.closePath();\n}\n\nfunction shadeColor(color, percent) {\n\tlet R = parseInt(color.substring(1, 3), 16);\n\tlet G = parseInt(color.substring(3, 5), 16);\n\tlet B = parseInt(color.substring(5, 7), 16);\n\tR = Math.min(255, Math.max(0, R + percent));\n\tG = Math.min(255, Math.max(0, G + percent));\n\tB = Math.min(255, Math.max(0, B + percent));\n\treturn '#' + ((1 << 24) + (R << 16) + (G << 8) + B).toString(16).slice(1);\n}\n\nfunction gameOver() {\n\tstate = STATE.OVER;\n\tsfxGameOver();\n\n\tif (score > bestScore) {\n\t\tbestScore = score;\n\t\tlocalStorage.setItem('jessicaBest', bestScore);\n\t}\n\n\tdocument.getElementById('finalScore').textContent = score;\n\tdocument.getElementById('bestDisplay').textContent = bestScore;\n\n\t// Stars rating\n\tlet starCount = 1;\n\tif (score >= 500) starCount = 2;\n\tif (score >= 1500) starCount = 3;\n\tdocument.getElementById('overStars').textContent = '★'.repeat(starCount) + '☆'.repeat(3 - starCount);\n\n\t// Message\n\tconst messages = [{\n\t\t\tmin: 0,\n\t\t\tmsg: 'Keep practicing! 💪'\n\t\t},\n\t\t{\n\t\t\tmin: 200,\n\t\t\tmsg: 'Nice moves, Jessica!'\n\t\t},\n\t\t{\n\t\t\tmin: 500,\n\t\t\tmsg: 'Great routine!'\n\t\t},\n\t\t{\n\t\t\tmin: 1000,\n\t\t\tmsg: 'Incredible performance!'\n\t\t},\n\t\t{\n\t\t\tmin: 2000,\n\t\t\tmsg: 'Olympic champion! 🥇'\n\t\t},\n\t\t{\n\t\t\tmin: 5000,\n\t\t\tmsg: 'LEGENDARY! 🏆'\n\t\t}\n\t];\n\tlet msg = messages[0].msg;\n\tfor (const m of messages) {\n\t\tif (score >= m.min) msg = m.msg;\n\t}\n\tdocument.getElementById('overMsg').textContent = msg;\n\n\tdocument.getElementById('gameOver').classList.add('visible');\n\tdocument.getElementById('mobileControls').classList.remove('visible');\n}\n\nfunction loop(time) {\n\tconst dt = Math.min(time - lastTime, 32);\n\tlastTime = time;\n\n\tupdate(dt);\n\tdraw();\n\n\trequestAnimationFrame(loop);\n}\n\n// ===== CONTROLS =====\nwindow.addEventListener('keydown', e => {\n\tkeys[e.code] = true;\n\tif (e.code === 'Space') {\n\t\te.preventDefault();\n\t\tif (state === STATE.PLAYING && !jessica.onGround) {\n\t\t\tdoFlip = true;\n\t\t}\n\t}\n\tif (e.code === 'ShiftLeft' || e.code === 'ShiftRight') {\n\t\te.preventDefault();\n\t\tactivateSuperJump();\n\t}\n});\n\nwindow.addEventListener('keyup', e => {\n\tkeys[e.code] = false;\n});\n\n// Mobile buttons\nconst btnLeft = document.getElementById('btnLeft');\nconst btnRight = document.getElementById('btnRight');\nconst btnFlip = document.getElementById('btnFlip');\nconst btnSuperJump = document.getElementById('btnSuperJump');\n\nfunction activateSuperJump() {\n\tif (!superJumpReady || state !== STATE.PLAYING) return;\n\tsuperJumpActive = true;\n\tsuperJumpReady = false;\n\tsuperJumpCooldown = SUPER_JUMP_COOLDOWN;\n\tbtnSuperJump.classList.add('cooldown');\n\tsfxSuperJump();\n\tspawnParticles(jessica.x, jessica.y, '#06b6d4', 8);\n}\n\nbtnSuperJump.addEventListener('touchstart', e => {\n\te.preventDefault();\n\tactivateSuperJump();\n});\nbtnSuperJump.addEventListener('mousedown', () => activateSuperJump());\n\nbtnLeft.addEventListener('touchstart', e => {\n\te.preventDefault();\n\tmoveLeft = true;\n});\nbtnLeft.addEventListener('touchend', e => {\n\te.preventDefault();\n\tmoveLeft = false;\n});\nbtnRight.addEventListener('touchstart', e => {\n\te.preventDefault();\n\tmoveRight = true;\n});\nbtnRight.addEventListener('touchend', e => {\n\te.preventDefault();\n\tmoveRight = false;\n});\nbtnFlip.addEventListener('touchstart', e => {\n\te.preventDefault();\n\tif (state === STATE.PLAYING && !jessica.onGround) doFlip = true;\n});\n\n// Also mouse for testing\nbtnLeft.addEventListener('mousedown', () => moveLeft = true);\nbtnLeft.addEventListener('mouseup', () => moveLeft = false);\nbtnLeft.addEventListener('mouseleave', () => moveLeft = false);\nbtnRight.addEventListener('mousedown', () => moveRight = true);\nbtnRight.addEventListener('mouseup', () => moveRight = false);\nbtnRight.addEventListener('mouseleave', () => moveRight = false);\nbtnFlip.addEventListener('mousedown', () => {\n\tif (state === STATE.PLAYING && !jessica.onGround) doFlip = true;\n});\n\n// Touch swipe on canvas\nlet touchStartX = 0;\nlet touchStartY = 0;\ncanvas.addEventListener('touchstart', e => {\n\tconst t = e.touches[0];\n\ttouchStartX = t.clientX;\n\ttouchStartY = t.clientY;\n});\n\ncanvas.addEventListener('touchend', e => {\n\t// Quick tap = flip\n\tif (state === STATE.PLAYING && !jessica.onGround) {\n\t\tdoFlip = true;\n\t}\n});\n\n// ===== START / RETRY =====\ndocument.getElementById('startBtn').addEventListener('click', () => {\n\tensureAudio();\n\tstartGame();\n});\n\ndocument.getElementById('retryBtn').addEventListener('click', () => {\n\tensureAudio();\n\tdocument.getElementById('gameOver').classList.remove('visible');\n\tstartGame();\n});\n\nfunction startGame() {\n\tstate = STATE.PLAYING;\n\tinitGame();\n\tdocument.getElementById('titleScreen').classList.add('hidden');\n\tdocument.getElementById('hud').classList.add('visible');\n\tdocument.getElementById('mobileControls').classList.add('visible');\n\n\t// Play start jingle\n\tconst notes = [523, 659, 784];\n\tnotes.forEach((n, i) => {\n\t\tsetTimeout(() => playTone(n, 0.2, 'sine', 0.1), i * 100);\n\t});\n}\n\n// Init Lucide icons\nif (typeof lucide !== 'undefined') lucide.createIcons();\n\n// Start loop\nrequestAnimationFrame(loop);"},{"name":"style.css","content":"@import url('https://fonts.googleapis.com/css2?family=Lilita+One&family=Quicksand:wght@500;700&display=swap');\n\n:root {\n\tcolor-scheme: light;\n\t--pink-hot: #ff2d78;\n\t--pink-light: #ffb3d0;\n\t--purple-deep: #6b21a8;\n\t--purple-mid: #a855f7;\n\t--purple-light: #e9d5ff;\n\t--gold: #fbbf24;\n\t--gold-dark: #d97706;\n\t--cyan: #06b6d4;\n\t--white: #ffffff;\n\t--cream: #fef7f0;\n\t--floor: #2d1b4e;\n\tfont-family: 'Quicksand', sans-serif;\n}\n\n* {\n\tbox-sizing: border-box;\n\tmargin: 0;\n\tpadding: 0;\n}\n\nhtml, body {\n\theight: 100%;\n\toverflow: hidden;\n\ttouch-action: none;\n\tuser-select: none;\n\t-webkit-user-select: none;\n}\n\n.game {\n\tposition: relative;\n\twidth: 100vw;\n\theight: 100vh;\n\toverflow: hidden;\n\tbackground: linear-gradient(180deg, #1e0533 0%, #3b0764 40%, #581c87 70%, #7c3aed 100%);\n}\n\n.game__bg {\n\tposition: absolute;\n\tinset: 0;\n\tpointer-events: none;\n\toverflow: hidden;\n}\n\n.game__bg-stripe {\n\tposition: absolute;\n\tborder-radius: 50%;\n\topacity: 0.08;\n\tfilter: blur(60px);\n}\n\n.game__bg-stripe--1 {\n\twidth: 600px;\n\theight: 600px;\n\tbackground: var(--pink-hot);\n\ttop: -200px;\n\tright: -100px;\n}\n\n.game__bg-stripe--2 {\n\twidth: 500px;\n\theight: 500px;\n\tbackground: var(--cyan);\n\tbottom: -150px;\n\tleft: -100px;\n}\n\n.game__bg-stripe--3 {\n\twidth: 400px;\n\theight: 400px;\n\tbackground: var(--gold);\n\ttop: 40%;\n\tleft: 30%;\n}\n\n/* Title Screen */\n.game__title-screen {\n\tposition: absolute;\n\tinset: 0;\n\tz-index: 20;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\tgap: 30px;\n\tbackground: linear-gradient(180deg, #1e0533ee 0%, #3b0764ee 50%, #581c87ee 100%);\n\tbackdrop-filter: blur(10px);\n\ttransition: opacity 0.5s, visibility 0.5s;\n}\n\n.game__title-screen.hidden {\n\topacity: 0;\n\tvisibility: hidden;\n\tpointer-events: none;\n}\n\n.game__logo {\n\ttext-align: center;\n\tposition: relative;\n}\n\n.game__logo-star {\n\tfont-size: 48px;\n\tcolor: var(--gold);\n\ttext-shadow: 0 0 30px rgba(251, 191, 36, 0.6);\n\tanimation: starPulse 2s ease-in-out infinite;\n}\n\n@keyframes starPulse {\n\t0%, 100% {\n\t\ttransform: scale(1) rotate(0deg);\n\t}\n\n\t50% {\n\t\ttransform: scale(1.15) rotate(10deg);\n\t}\n}\n\n.game__title {\n\tfont-family: 'Lilita One', cursive;\n\tfont-size: clamp(42px, 10vw, 72px);\n\tcolor: var(--white);\n\tline-height: 1;\n\ttext-shadow:\n\t\t3px 3px 0 var(--pink-hot),\n\t\t6px 6px 0 rgba(255, 45, 120, 0.3);\n\tletter-spacing: -1px;\n}\n\n.game__subtitle {\n\tfont-size: clamp(14px, 3vw, 20px);\n\tcolor: var(--pink-light);\n\tfont-weight: 700;\n\tmargin-top: 8px;\n\tletter-spacing: 2px;\n\ttext-transform: uppercase;\n}\n\n.game__btn {\n\tborder: none;\n\tcursor: pointer;\n\tfont-family: 'Lilita One', cursive;\n\tfont-size: 22px;\n\tletter-spacing: 1px;\n\tborder-radius: 50px;\n\tpadding: 16px 48px;\n\ttransition: transform 0.15s, box-shadow 0.15s;\n\tposition: relative;\n\toverflow: hidden;\n}\n\n.game__btn:active {\n\ttransform: scale(0.95);\n}\n\n.game__btn--start, .game__btn--retry {\n\tbackground: linear-gradient(135deg, var(--pink-hot), #ff6ba6);\n\tcolor: var(--white);\n\tbox-shadow:\n\t\t0 4px 20px rgba(255, 45, 120, 0.5),\n\t\t0 0 0 3px rgba(255, 255, 255, 0.15),\n\t\tinset 0 1px 0 rgba(255, 255, 255, 0.3);\n}\n\n.game__btn--start:hover, .game__btn--retry:hover {\n\ttransform: translateY(-2px);\n\tbox-shadow:\n\t\t0 8px 30px rgba(255, 45, 120, 0.6),\n\t\t0 0 0 3px rgba(255, 255, 255, 0.2),\n\t\tinset 0 1px 0 rgba(255, 255, 255, 0.3);\n}\n\n.game__instructions {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 10px;\n\ttext-align: center;\n}\n\n.game__instruction {\n\tcolor: var(--purple-light);\n\tfont-size: 14px;\n\tfont-weight: 700;\n}\n\n.game__instruction kbd {\n\tdisplay: inline-block;\n\tbackground: rgba(255, 255, 255, 0.12);\n\tborder: 1px solid rgba(255, 255, 255, 0.2);\n\tborder-radius: 6px;\n\tpadding: 2px 8px;\n\tfont-family: 'Quicksand', sans-serif;\n\tfont-size: 13px;\n\tmargin: 0 2px;\n}\n\n/* HUD */\n.game__hud {\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n\tz-index: 10;\n\tdisplay: flex;\n\tjustify-content: space-around;\n\tpadding: 12px 16px;\n\tbackground: linear-gradient(180deg, rgba(30, 5, 51, 0.8) 0%, transparent 100%);\n\topacity: 0;\n\ttransition: opacity 0.4s;\n}\n\n.game__hud.visible {\n\topacity: 1;\n}\n\n.game__hud-item {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 2px;\n}\n\n.game__hud-label {\n\tfont-size: 11px;\n\ttext-transform: uppercase;\n\tletter-spacing: 2px;\n\tcolor: var(--pink-light);\n\tfont-weight: 700;\n}\n\n.game__hud-value {\n\tfont-family: 'Lilita One', cursive;\n\tfont-size: 28px;\n\tcolor: var(--white);\n\ttext-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);\n}\n\n.game__hud-value--combo {\n\tcolor: var(--gold);\n\ttransition: transform 0.2s;\n}\n\n.game__hud-value--combo.bump {\n\tanimation: comboBump 0.3s ease;\n}\n\n@keyframes comboBump {\n\t0% {\n\t\ttransform: scale(1);\n\t}\n\n\t50% {\n\t\ttransform: scale(1.4);\n\t}\n\n\t100% {\n\t\ttransform: scale(1);\n\t}\n}\n\n/* Canvas */\n#gameCanvas {\n\tposition: absolute;\n\tinset: 0;\n\twidth: 100%;\n\theight: 100%;\n\tz-index: 5;\n}\n\n/* Mobile Controls */\n.game__controls {\n\tposition: absolute;\n\tbottom: 20px;\n\tleft: 0;\n\tright: 0;\n\tz-index: 15;\n\tdisplay: flex;\n\tjustify-content: center;\n\talign-items: center;\n\tgap: 16px;\n\tpadding: 0 20px;\n\topacity: 0;\n\ttransition: opacity 0.4s;\n\tpointer-events: none;\n}\n\n.game__controls.visible {\n\topacity: 1;\n\tpointer-events: auto;\n}\n\n.game__ctrl {\n\tborder: none;\n\tcursor: pointer;\n\tborder-radius: 50%;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\ttransition: transform 0.1s;\n\t-webkit-tap-highlight-color: transparent;\n}\n\n.game__ctrl:active {\n\ttransform: scale(0.9);\n}\n\n.game__ctrl--left, .game__ctrl--right {\n\twidth: 64px;\n\theight: 64px;\n\tbackground: rgba(255, 255, 255, 0.12);\n\tborder: 2px solid rgba(255, 255, 255, 0.25);\n\tcolor: var(--white);\n\tbackdrop-filter: blur(8px);\n}\n\n.game__ctrl--jump {\n\twidth: 72px;\n\theight: 72px;\n\tbackground: linear-gradient(135deg, #06b6d4, #0891b2);\n\tcolor: var(--white);\n\tflex-direction: column;\n\tgap: 2px;\n\tbox-shadow: 0 4px 25px rgba(6, 182, 212, 0.5);\n\tborder: 2px solid rgba(255, 255, 255, 0.3);\n\ttransition: transform 0.1s, opacity 0.3s, box-shadow 0.3s;\n}\n\n.game__ctrl--jump span {\n\tfont-family: 'Lilita One', cursive;\n\tfont-size: 10px;\n\tletter-spacing: 1px;\n}\n\n.game__ctrl--jump.cooldown {\n\topacity: 0.4;\n\tbox-shadow: 0 2px 10px rgba(6, 182, 212, 0.2);\n\tpointer-events: none;\n}\n\n.game__ctrl--jump svg {\n\twidth: 22px;\n\theight: 22px;\n}\n\n.game__ctrl--flip {\n\twidth: 88px;\n\theight: 88px;\n\tbackground: linear-gradient(135deg, var(--pink-hot), var(--purple-mid));\n\tcolor: var(--white);\n\tflex-direction: column;\n\tgap: 2px;\n\tbox-shadow: 0 4px 25px rgba(255, 45, 120, 0.5);\n\tborder: 2px solid rgba(255, 255, 255, 0.3);\n}\n\n.game__ctrl--flip span {\n\tfont-family: 'Lilita One', cursive;\n\tfont-size: 11px;\n\tletter-spacing: 1px;\n}\n\n.game__ctrl svg {\n\twidth: 28px;\n\theight: 28px;\n}\n\n.game__ctrl--flip svg {\n\twidth: 24px;\n\theight: 24px;\n}\n\n/* Game Over */\n.game__over {\n\tposition: absolute;\n\tinset: 0;\n\tz-index: 25;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tbackground: rgba(30, 5, 51, 0.85);\n\tbackdrop-filter: blur(12px);\n\topacity: 0;\n\tvisibility: hidden;\n\tpointer-events: none;\n\ttransition: opacity 0.5s, visibility 0.5s;\n}\n\n.game__over.visible {\n\topacity: 1;\n\tvisibility: visible;\n\tpointer-events: auto;\n}\n\n.game__over-card {\n\tbackground: linear-gradient(160deg, #2d1b4e, #1e0533);\n\tborder: 2px solid rgba(168, 85, 247, 0.3);\n\tborder-radius: 24px;\n\tpadding: 40px;\n\ttext-align: center;\n\tmax-width: 340px;\n\twidth: 90%;\n\tbox-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n}\n\n.game__over-stars {\n\tfont-size: 40px;\n\tcolor: var(--gold);\n\ttext-shadow: 0 0 20px rgba(251, 191, 36, 0.5);\n\tmargin-bottom: 12px;\n}\n\n.game__over-title {\n\tfont-family: 'Lilita One', cursive;\n\tfont-size: 28px;\n\tcolor: var(--white);\n\tmargin-bottom: 20px;\n}\n\n.game__over-score {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tmargin-bottom: 16px;\n}\n\n.game__over-score span {\n\tfont-size: 13px;\n\tcolor: var(--pink-light);\n\ttext-transform: uppercase;\n\tletter-spacing: 2px;\n\tfont-weight: 700;\n}\n\n.game__over-score strong {\n\tfont-family: 'Lilita One', cursive;\n\tfont-size: 52px;\n\tcolor: var(--gold);\n\ttext-shadow: 0 2px 20px rgba(251, 191, 36, 0.4);\n}\n\n.game__over-msg {\n\tcolor: var(--purple-light);\n\tfont-size: 15px;\n\tfont-weight: 700;\n\tmargin-bottom: 24px;\n}\n\n/* Particles */\n.game__particles {\n\tposition: absolute;\n\tinset: 0;\n\tz-index: 12;\n\tpointer-events: none;\n\toverflow: hidden;\n}\n\n.game__particle {\n\tposition: absolute;\n\tborder-radius: 50%;\n\tpointer-events: none;\n\tanimation: particleFly 0.8s ease-out forwards;\n}\n\n@keyframes particleFly {\n\t0% {\n\t\topacity: 1;\n\t\ttransform: translate(0, 0) scale(1);\n\t}\n\n\t100% {\n\t\topacity: 0;\n\t\ttransform: translate(var(--tx), var(--ty)) scale(0);\n\t}\n}\n\n.game__float-text {\n\tposition: absolute;\n\tz-index: 13;\n\tpointer-events: none;\n\tfont-family: 'Lilita One', cursive;\n\tfont-size: 24px;\n\tcolor: var(--gold);\n\ttext-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);\n\tanimation: floatUp 1s ease-out forwards;\n}\n\n@keyframes floatUp {\n\t0% {\n\t\topacity: 1;\n\t\ttransform: translateY(0) scale(1);\n\t}\n\n\t100% {\n\t\topacity: 0;\n\t\ttransform: translateY(-80px) scale(1.3);\n\t}\n}\n\n@media (min-width: 768px) {\n\t.game__controls {\n\t\tdisplay: none !important;\n\t}\n}\n\n@media (max-width: 767px) {\n\t.game__instruction kbd {\n\t\tdisplay: none;\n\t}\n\n\t.game__instructions {\n\t\tgap: 6px;\n\t}\n\n\t.game__instruction:nth-child(1)::before {\n\t\tcontent: 'Swipe or buttons to move';\n\t}\n\n\t.game__instruction:nth-child(1) {\n\t\tfont-size: 0;\n\t}\n\n\t.game__instruction:nth-child(1)::before {\n\t\tfont-size: 14px;\n\t}\n}"}],"folders":[]},"variants":null,"createdAt":"2026-02-28T13:50:23.140Z","updatedAt":"2026-03-02T00:31:00.473Z","hasThumbnail":true}}