Mobile-emulator / colortoss.html
ngiactcp
Add several new playable games to the application
61bdef8
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Color Toss - Premium</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;700;900&display=swap" rel="stylesheet">
<style>
:root {
--primary: #ff0055;
--bg: #0f172a;
}
body {
margin: 0; padding: 0;
background-color: var(--bg);
color: white;
font-family: 'Outfit', sans-serif;
overflow: hidden;
touch-action: none;
user-select: none;
}
#game-container {
position: relative;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: radial-gradient(circle at center, #1e293b 0%, #0f172a 100%);
}
canvas {
display: block;
background: transparent;
}
.ui-screen {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: rgba(15, 23, 42, 0.85);
backdrop-filter: blur(12px);
z-index: 50;
transition: opacity 0.3s ease;
}
.menu-card {
background: linear-gradient(145deg, #1e293b, #0f172a);
padding: 2.5rem 2rem;
border-radius: 2.5rem;
text-align: center;
border: 1px solid rgba(255, 255, 255, 0.1);
width: 85%;
max-width: 360px;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
}
.btn {
width: 100%;
padding: 16px;
border-radius: 1rem;
font-weight: 700;
margin-bottom: 0.75rem;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
text-transform: uppercase;
letter-spacing: 1px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.btn-primary {
background: var(--primary);
color: white;
box-shadow: 0 10px 20px -5px rgba(255, 0, 85, 0.4);
}
.btn-secondary {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
color: #cbd5e1;
}
.btn:active { transform: scale(0.95); opacity: 0.9; }
.diff-btn {
flex: 1;
padding: 10px 5px;
font-size: 0.75rem;
font-weight: 700;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 0.75rem;
margin: 0 4px;
background: rgba(255, 255, 255, 0.03);
color: #94a3b8;
}
.diff-btn.active {
background: white;
color: black;
border-color: white;
}
#hud {
position: absolute;
top: env(safe-area-inset-top, 20px);
left: 0;
width: 100%;
padding: 0 24px;
display: flex;
justify-content: space-between;
align-items: center;
pointer-events: none;
z-index: 10;
}
.hud-btn {
pointer-events: auto;
background: rgba(255,255,255,0.05);
width: 48px;
height: 48px;
border-radius: 14px;
display: flex;
justify-content: center;
align-items: center;
backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.1);
}
.tap-hint {
position: absolute;
bottom: 20%;
width: 100%;
text-align: center;
color: rgba(255,255,255,0.4);
font-weight: 700;
letter-spacing: 2px;
animation: pulse 1.5s infinite;
pointer-events: none;
}
@keyframes pulse {
0%, 100% { opacity: 0.3; transform: scale(1); }
50% { opacity: 0.8; transform: scale(1.05); }
}
</style>
</head>
<body>
<div id="game-container">
<canvas id="gameCanvas"></canvas>
<div id="hud" style="display: none;">
<div id="score-display" class="text-5xl font-black italic tracking-tighter">0</div>
<button class="hud-btn" onclick="togglePause()">
<svg width="24" height="24" viewBox="0 0 24 24" fill="white"><rect x="6" y="4" width="4" height="16" rx="1"/><rect x="14" y="4" width="4" height="16" rx="1"/></svg>
</button>
</div>
<div id="tap-to-start" class="tap-hint" style="display: none;">TAP TO JUMP</div>
<!-- Home Menu -->
<div id="home-menu" class="ui-screen">
<div class="menu-card">
<h1 class="text-6xl font-black italic mb-1 tracking-tighter leading-none">COLOR<br><span style="color: var(--primary)">TOSS</span></h1>
<p class="text-slate-500 mb-10 text-sm font-medium">PREMIUM EDITION</p>
<div class="mb-8">
<p class="text-[10px] uppercase text-slate-400 mb-3 font-black tracking-widest">Difficulty Level</p>
<div class="flex bg-black/20 p-1 rounded-xl">
<button class="diff-btn" onclick="setDifficulty('easy')" id="diff-easy">EASY</button>
<button class="diff-btn active" onclick="setDifficulty('normal')" id="diff-normal">NORMAL</button>
<button class="diff-btn" onclick="setDifficulty('hard')" id="diff-hard">HARD</button>
</div>
</div>
<button class="btn btn-primary" onclick="startNewGame()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
New Game
</button>
<button class="btn btn-secondary" id="resume-btn" onclick="loadSavedGame()" style="display: none;">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 14H9V8h2v8zm4 0h-2V8h2v8z"/></svg>
Continue
</button>
</div>
<p class="absolute bottom-8 text-slate-600 text-[10px] font-bold tracking-widest uppercase">Best Score: <span id="high-score-text">0</span></p>
</div>
<!-- Pause Menu -->
<div id="pause-menu" class="ui-screen" style="display: none;">
<div class="menu-card">
<h2 class="text-3xl font-black mb-8 italic tracking-tight">PAUSED</h2>
<button class="btn btn-primary" onclick="togglePause()">Resume Game</button>
<button class="btn btn-secondary" onclick="saveAndExit()">Save & Exit</button>
</div>
</div>
<!-- Game Over -->
<div id="game-over" class="ui-screen" style="display: none;">
<div class="menu-card">
<div class="text-pink-500 mb-2">
<svg class="mx-auto" width="60" height="60" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>
</div>
<h2 class="text-3xl font-black mb-1">CRASHED!</h2>
<p id="final-score" class="text-slate-400 font-bold mb-8">Score: 0</p>
<button class="btn btn-primary" onclick="startNewGame()">Try Again</button>
<button class="btn btn-secondary" onclick="showHome()">Main Menu</button>
</div>
</div>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const COLORS = ['#FF0055', '#00DDFF', '#FFCC00', '#AA00FF'];
const CANVAS_WIDTH = 400;
const CANVAS_HEIGHT = 700;
let state = {
mode: 'MENU',
waitingForFirstTap: true,
difficulty: 'normal',
score: 0,
highScore: parseInt(localStorage.getItem('ct_best') || 0),
cameraY: 0,
player: { x: 200, y: 550, vy: 0, radius: 12, color: COLORS[0], floatOffset: 0 },
obstacles: [],
switchers: []
};
let audioCtx;
const difficultySettings = {
easy: { speed: 0.02, gravity: 0.22, jump: -5.2 },
normal: { speed: 0.038, gravity: 0.28, jump: -6.0 },
hard: { speed: 0.06, gravity: 0.34, jump: -6.8 }
};
function initAudio() {
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
}
function playSound(f, t, d, v = 0.06) {
if (!audioCtx) return;
const o = audioCtx.createOscillator();
const g = audioCtx.createGain();
o.type = t; o.frequency.value = f;
g.gain.setValueAtTime(v, audioCtx.currentTime);
g.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + d);
o.connect(g); g.connect(audioCtx.destination);
o.start(); o.stop(audioCtx.currentTime + d);
}
class Obstacle {
constructor(y, angle = 0, passed = false) {
this.y = y;
this.radius = 85;
this.angle = angle;
this.thickness = 18;
this.passed = passed;
}
draw() {
const speed = difficultySettings[state.difficulty].speed;
this.angle += speed;
const seg = (Math.PI * 2) / 4;
ctx.lineWidth = this.thickness;
ctx.lineCap = 'round';
for (let i = 0; i < 4; i++) {
ctx.beginPath();
ctx.strokeStyle = COLORS[i];
// Added a small gap for a cleaner look
const start = this.angle + i*seg + 0.05;
const end = this.angle + (i+1)*seg - 0.05;
ctx.arc(CANVAS_WIDTH/2, this.y - state.cameraY, this.radius, start, end);
ctx.stroke();
}
}
check(px, py, pr, pc) {
const dx = px - CANVAS_WIDTH/2, dy = py - this.y;
const d = Math.sqrt(dx*dx + dy*dy);
if (d + pr > this.radius - this.thickness/2 && d - pr < this.radius + this.thickness/2) {
let a = Math.atan2(dy, dx) - this.angle;
while(a < 0) a += Math.PI * 2;
const index = Math.floor((a % (Math.PI*2)) / (Math.PI/2));
if (COLORS[index] !== pc) return true;
}
return false;
}
}
function setDifficulty(d) {
state.difficulty = d;
document.querySelectorAll('.diff-btn').forEach(b => b.classList.remove('active'));
document.getElementById('diff-' + d).classList.add('active');
playSound(400, 'sine', 0.1);
}
function startNewGame() {
initAudio();
state.score = 0;
state.cameraY = 0;
state.waitingForFirstTap = true;
state.player = {
x: 200, y: 550, vy: 0, radius: 12,
color: COLORS[Math.floor(Math.random()*4)],
floatOffset: 0
};
state.obstacles = [];
state.switchers = [];
for(let i=0; i<3; i++) spawn(250 - i*350);
launch();
}
function spawn(y) {
state.obstacles.push(new Obstacle(y));
state.switchers.push({ y: y - 175, radius: 16, active: true, angle: 0 });
}
function launch() {
state.mode = 'PLAYING';
document.querySelectorAll('.ui-screen').forEach(s => s.style.display = 'none');
document.getElementById('hud').style.display = 'flex';
document.getElementById('tap-to-start').style.display = 'block';
document.getElementById('score-display').innerText = state.score;
requestAnimationFrame(gameLoop);
}
function togglePause() {
if (state.mode === 'PLAYING') {
state.mode = 'PAUSED';
document.getElementById('pause-menu').style.display = 'flex';
} else if (state.mode === 'PAUSED') {
state.mode = 'PLAYING';
document.getElementById('pause-menu').style.display = 'none';
requestAnimationFrame(gameLoop);
}
}
function saveAndExit() {
const saveData = {
score: state.score,
cameraY: state.cameraY,
player: state.player,
difficulty: state.difficulty,
waitingForFirstTap: state.waitingForFirstTap,
obstacles: state.obstacles.map(o => ({y: o.y, angle: o.angle, passed: o.passed})),
switchers: state.switchers
};
localStorage.setItem('ct_save', JSON.stringify(saveData));
showHome();
}
function loadSavedGame() {
const raw = localStorage.getItem('ct_save');
if (!raw) return;
const d = JSON.parse(raw);
state = {...state, ...d};
state.obstacles = d.obstacles.map(o => new Obstacle(o.y, o.angle, o.passed));
localStorage.removeItem('ct_save');
launch();
}
function showHome() {
state.mode = 'MENU';
document.querySelectorAll('.ui-screen').forEach(s => s.style.display = 'none');
document.getElementById('home-menu').style.display = 'flex';
document.getElementById('hud').style.display = 'none';
document.getElementById('tap-to-start').style.display = 'none';
document.getElementById('high-score-text').innerText = state.highScore;
const hasSave = localStorage.getItem('ct_save');
document.getElementById('resume-btn').style.display = hasSave ? 'flex' : 'none';
}
function gameLoop() {
if (state.mode !== 'PLAYING') return;
ctx.clearRect(0,0, CANVAS_WIDTH, CANVAS_HEIGHT);
const config = difficultySettings[state.difficulty];
// Physics & Start Logic
if (state.waitingForFirstTap) {
// Floating effect
state.player.floatOffset += 0.05;
const floatY = state.player.y + Math.sin(state.player.floatOffset) * 10;
drawPlayer(state.player.x, floatY);
} else {
document.getElementById('tap-to-start').style.display = 'none';
state.player.vy += config.gravity;
state.player.y += state.player.vy;
if (state.player.y < state.cameraY + 380) state.cameraY = state.player.y - 380;
if (state.player.y - state.cameraY > CANVAS_HEIGHT + 50) { endGame(); return; }
drawPlayer(state.player.x, state.player.y);
}
// Draw Obstacles
state.obstacles.forEach((o, i) => {
o.draw();
if (!state.waitingForFirstTap) {
if (!o.passed && state.player.y < o.y) {
o.passed = true; state.score++;
document.getElementById('score-display').innerText = state.score;
playSound(900, 'sine', 0.1);
}
if (o.check(state.player.x, state.player.y, state.player.radius, state.player.color)) endGame();
}
if (o.y - state.cameraY > CANVAS_HEIGHT + 150) {
state.obstacles.splice(i, 1);
spawn(state.obstacles[state.obstacles.length-1].y - 350);
}
});
// Color Switchers
state.switchers.forEach((s, i) => {
if (!s.active) return;
s.angle += 0.02;
const seg = (Math.PI*2)/4;
ctx.save();
ctx.translate(CANVAS_WIDTH/2, s.y - state.cameraY);
ctx.rotate(s.angle);
for(let j=0; j<4; j++){
ctx.beginPath(); ctx.fillStyle = COLORS[j]; ctx.moveTo(0,0);
ctx.arc(0, 0, s.radius, j*seg, (j+1)*seg); ctx.fill();
}
ctx.restore();
if (!state.waitingForFirstTap) {
const dx = state.player.x - CANVAS_WIDTH/2, dy = state.player.y - s.y;
if (Math.sqrt(dx*dx + dy*dy) < state.player.radius + s.radius) {
s.active = false;
let nc; do { nc = COLORS[Math.floor(Math.random()*4)]; } while(nc === state.player.color);
state.player.color = nc;
playSound(600, 'triangle', 0.2, 0.1);
}
}
});
requestAnimationFrame(gameLoop);
}
function drawPlayer(x, y) {
ctx.save();
ctx.beginPath();
ctx.fillStyle = state.player.color;
ctx.shadowBlur = 25; ctx.shadowColor = state.player.color;
ctx.arc(x, y - state.cameraY, state.player.radius, 0, Math.PI*2);
ctx.fill();
// Inner detail
ctx.beginPath();
ctx.fillStyle = 'rgba(255,255,255,0.3)';
ctx.arc(x - 3, (y - state.cameraY) - 3, 4, 0, Math.PI*2);
ctx.fill();
ctx.restore();
}
function endGame() {
state.mode = 'GAMEOVER';
playSound(150, 'sawtooth', 0.3);
document.getElementById('game-over').style.display = 'flex';
document.getElementById('final-score').innerText = "Score: " + state.score;
if (state.score > state.highScore) {
state.highScore = state.score;
localStorage.setItem('ct_best', state.highScore);
}
}
const handleInput = (e) => {
if (state.mode === 'PLAYING') {
if (state.waitingForFirstTap) {
state.waitingForFirstTap = false;
}
state.player.vy = difficultySettings[state.difficulty].jump;
playSound(500, 'sine', 0.06);
}
};
window.addEventListener('mousedown', e => { if(e.target.closest('button')) return; handleInput(); });
window.addEventListener('touchstart', e => { if(e.target.closest('button')) return; e.preventDefault(); handleInput(); }, {passive:false});
window.addEventListener('keydown', e => { if(e.code === 'Space') handleInput(); });
function resize() {
const s = Math.min(window.innerWidth/CANVAS_WIDTH, window.innerHeight/CANVAS_HEIGHT);
canvas.width = CANVAS_WIDTH; canvas.height = CANVAS_HEIGHT;
canvas.style.width = (CANVAS_WIDTH*s)+'px'; canvas.style.height = (CANVAS_HEIGHT*s)+'px';
}
window.addEventListener('resize', resize);
resize();
showHome();
</script>
</body>
</html>