(function () { var c = document.getElementById('c'), ctx = c.getContext('2d'), W, H, ground, cat, cacti, clouds, particles, spd, score, best, dead, frame, shake, nightCycle; best = parseInt(localStorage.getItem('catBest') || '0', 10); document.getElementById('hi').textContent = best; /* ── размеры ── */ function resize() { W = Math.min(window.innerWidth, 640); H = 280; c.width = W; c.height = H; ground = H - 36; if (cat) { var floor = ground - cat.h; if (cat.y > floor) cat.y = floor; } } /* ── инициализация ── */ function init() { resize(); score = 0; dead = false; frame = 0; spd = 3.5; shake = 0; nightCycle = 0; cat = { x: 70, y: ground - 36, w: 36, h: 36, vy: 0, jumps: 0, maxJumps: 2, legFrame: 0, tailAngle: 0, eyeBlink: 0 }; cacti = []; clouds = []; particles = []; for (var i = 0; i < 4; i++) { clouds.push({ x: Math.random() * W, y: 20 + Math.random() * 60, r: 18 + Math.random() * 22, s: 0.3 + Math.random() * 0.5 }); } document.getElementById('sc').textContent = '0'; } /* ── прыжок ── */ function jump() { if (dead) { init(); return; } if (cat.jumps < cat.maxJumps) { cat.vy = cat.jumps === 0 ? -11 : -9; cat.jumps++; for (var i = 0; i < 6; i++) { particles.push({ x: cat.x + cat.w / 2, y: cat.y + cat.h, vx: (Math.random() - 0.5) * 3, vy: Math.random() * 2 + 1, life: 20 + Math.random() * 10, r: 2 + Math.random() * 3, col: cat.jumps === 2 ? '#f6e58d' : '#dfe6e9' }); } } } /* ── спавн кактусов ── */ function spawnCactus() { var gap = cacti.length ? cacti[cacti.length - 1].x : 0; if (gap > W - 80) return; var h = 26 + Math.random() * 24; var type = Math.random() < 0.3 ? 'double' : 'single'; cacti.push({ x: W + 10, w: type === 'double' ? 26 : 14, h: h, type: type }); } /* ── обновление ── */ function update() { if (dead) return; frame++; nightCycle = (Math.sin(frame * 0.002) + 1) / 2; /* спавн */ var interval = Math.max(35, 75 - score); if (frame % interval === 0) spawnCactus(); /* физика кота */ cat.vy += 0.55; cat.y += cat.vy; var floor = ground - cat.h; if (cat.y >= floor) { cat.y = floor; cat.vy = 0; cat.jumps = 0; } /* анимация лапок */ cat.legFrame += spd * 0.15; cat.tailAngle = Math.sin(frame * 0.08) * 0.4; cat.eyeBlink = (frame % 180 < 6) ? 1 : 0; /* кактусы */ for (var i = cacti.length - 1; i >= 0; i--) { cacti[i].x -= spd; if (cacti[i].x + cacti[i].w < 0) { cacti.splice(i, 1); score++; document.getElementById('sc').textContent = score; continue; } var cx = cacti[i]; if ( cat.x + cat.w - 8 > cx.x + 2 && cat.x + 8 < cx.x + cx.w - 2 && cat.y + cat.h > ground - cx.h + 4 ) { dead = true; shake = 12; if (score > best) { best = score; localStorage.setItem('catBest', best); document.getElementById('hi').textContent = best; } for (var j = 0; j < 15; j++) { particles.push({ x: cat.x + cat.w / 2, y: cat.y + cat.h / 2, vx: (Math.random() - 0.5) * 6, vy: (Math.random() - 0.5) * 6, life: 30 + Math.random() * 20, r: 2 + Math.random() * 4, col: '#e94560' }); } } } /* облака */ for (var k = 0; k < clouds.length; k++) { clouds[k].x -= clouds[k].s; if (clouds[k].x + clouds[k].r < 0) { clouds[k].x = W + clouds[k].r; clouds[k].y = 20 + Math.random() * 60; } } /* частицы */ for (var p = particles.length - 1; p >= 0; p--) { var pt = particles[p]; pt.x += pt.vx; pt.y += pt.vy; pt.life--; if (pt.life <= 0) particles.splice(p, 1); } /* ускорение */ if (frame % 180 === 0 && spd < 9) spd += 0.25; } /* ── рисование кота ── */ function drawCat(cx, cy, w, h) { var onGround = cat.jumps === 0 && cat.vy === 0; var legOff1 = onGround ? Math.sin(cat.legFrame) * 5 : -3; var legOff2 = onGround ? Math.sin(cat.legFrame + Math.PI) * 5 : 3; ctx.save(); ctx.translate(cx, cy); /* хвост */ ctx.strokeStyle = '#f39c12'; ctx.lineWidth = 3; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(2, h * 0.5); ctx.quadraticCurveTo(-10, h * 0.2 + Math.sin(cat.tailAngle) * 10, -8, h * 0.05); ctx.stroke(); /* тело */ ctx.fillStyle = '#f39c12'; ctx.beginPath(); ctx.ellipse(w / 2, h * 0.55, w / 2, h * 0.35, 0, 0, Math.PI * 2); ctx.fill(); /* голова */ ctx.beginPath(); ctx.arc(w * 0.72, h * 0.28, 10, 0, Math.PI * 2); ctx.fill(); /* уши */ ctx.beginPath(); ctx.moveTo(w * 0.6, h * 0.15); ctx.lineTo(w * 0.55, h * -0.05); ctx.lineTo(w * 0.7, h * 0.12); ctx.fill(); ctx.beginPath(); ctx.moveTo(w * 0.78, h * 0.12); ctx.lineTo(w * 0.82, h * -0.08); ctx.lineTo(w * 0.9, h * 0.15); ctx.fill(); /* внутренние уши */ ctx.fillStyle = '#e17055'; ctx.beginPath(); ctx.moveTo(w * 0.62, h * 0.14); ctx.lineTo(w * 0.58, h * 0.0); ctx.lineTo(w * 0.68, h * 0.13); ctx.fill(); ctx.beginPath(); ctx.moveTo(w * 0.8, h * 0.12); ctx.lineTo(w * 0.83, h * -0.03); ctx.lineTo(w * 0.88, h * 0.14); ctx.fill(); /* глаза */ ctx.fillStyle = '#2d3436'; if (cat.eyeBlink) { ctx.fillRect(w * 0.65, h * 0.25, 4, 2); ctx.fillRect(w * 0.76, h * 0.25, 4, 2); } else { ctx.beginPath(); ctx.arc(w * 0.67, h * 0.26, 2.5, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(w * 0.78, h * 0.26, 2.5, 0, Math.PI * 2); ctx.fill(); /* блики */ ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(w * 0.68, h * 0.24, 1, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(w * 0.79, h * 0.24, 1, 0, Math.PI * 2); ctx.fill(); } /* нос */ ctx.fillStyle = '#e17055'; ctx.beginPath(); ctx.moveTo(w * 0.73, h * 0.32); ctx.lineTo(w * 0.71, h * 0.36); ctx.lineTo(w * 0.75, h * 0.36); ctx.fill(); /* усы */ ctx.strokeStyle = '#636e72'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(w * 0.6, h * 0.33); ctx.lineTo(w * 0.45, h * 0.28); ctx.stroke(); ctx.beginPath(); ctx.moveTo(w * 0.6, h * 0.36); ctx.lineTo(w * 0.44, h * 0.38); ctx.stroke(); ctx.beginPath(); ctx.moveTo(w * 0.86, h * 0.33); ctx.lineTo(w, h * 0.28); ctx.stroke(); ctx.beginPath(); ctx.moveTo(w * 0.86, h * 0.36); ctx.lineTo(w * 1.01, h * 0.38); ctx.stroke(); /* лапы */ ctx.fillStyle = '#e17055'; ctx.lineWidth = 3; ctx.strokeStyle = '#f39c12'; /* передние */ ctx.beginPath(); ctx.moveTo(w * 0.65, h * 0.7); ctx.lineTo(w * 0.68, h * 0.95 + legOff1); ctx.stroke(); ctx.beginPath(); ctx.arc(w * 0.68, h * 0.95 + legOff1, 2.5, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.moveTo(w * 0.55, h * 0.72); ctx.lineTo(w * 0.57, h * 0.95 + legOff2); ctx.stroke(); ctx.beginPath(); ctx.arc(w * 0.57, h * 0.95 + legOff2, 2.5, 0, Math.PI * 2); ctx.fill(); /* задние */ ctx.beginPath(); ctx.moveTo(w * 0.25, h * 0.7); ctx.lineTo(w * 0.22, h * 0.95 + legOff2); ctx.stroke(); ctx.beginPath(); ctx.arc(w * 0.22, h * 0.95 + legOff2, 2.5, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.moveTo(w * 0.35, h * 0.72); ctx.lineTo(w * 0.33, h * 0.95 + legOff1); ctx.stroke(); ctx.beginPath(); ctx.arc(w * 0.33, h * 0.95 + legOff1, 2.5, 0, Math.PI * 2); ctx.fill(); /* полоски */ ctx.strokeStyle = '#e17055'; ctx.lineWidth = 1.5; ctx.globalAlpha = 0.5; for (var s = 0; s < 3; s++) { var sx = w * 0.3 + s * 8; ctx.beginPath(); ctx.moveTo(sx, h * 0.38); ctx.lineTo(sx + 2, h * 0.58); ctx.stroke(); } ctx.globalAlpha = 1; ctx.restore(); } /* ── рисование кактуса ── */ function drawCactus(cx) { var baseX = cx.x, baseH = cx.h, baseW = cx.w; var topY = ground - baseH; /* градиент */ var grd = ctx.createLinearGradient(baseX, topY, baseX + baseW, ground); grd.addColorStop(0, '#00b894'); grd.addColorStop(1, '#00cec9'); /* ствол */ ctx.fillStyle = grd; roundRect(baseX, topY, baseW, baseH, 4); /* шипы */ ctx.fillStyle = '#55efc4'; for (var s = 0; s < baseH; s += 8) { ctx.beginPath(); ctx.moveTo(baseX, topY + s); ctx.lineTo(baseX - 4, topY + s + 2); ctx.lineTo(baseX, topY + s + 4); ctx.fill(); ctx.beginPath(); ctx.moveTo(baseX + baseW, topY + s); ctx.lineTo(baseX + baseW + 4, topY + s + 2); ctx.lineTo(baseX + baseW, topY + s + 4); ctx.fill(); } if (cx.type === 'double') { /* боковая ветка */ ctx.fillStyle = grd; roundRect(baseX + baseW - 2, topY + baseH * 0.3, 10, baseH * 0.5, 3); } /* цветок сверху */ ctx.fillStyle = '#fd79a8'; ctx.beginPath(); ctx.arc(baseX + baseW / 2, topY - 2, 4, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#ffeaa7'; ctx.beginPath(); ctx.arc(baseX + baseW / 2, topY - 2, 2, 0, Math.PI * 2); ctx.fill(); } function roundRect(x, y, w, h, r) { ctx.beginPath(); ctx.moveTo(x + r, y); ctx.lineTo(x + w - r, y); ctx.quadraticCurveTo(x + w, y, x + w, y + r); ctx.lineTo(x + w, y + h - r); ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); ctx.lineTo(x + r, y + h); ctx.quadraticCurveTo(x, y + h, x, y + h - r); ctx.lineTo(x, y + r); ctx.quadraticCurveTo(x, y, x + r, y); ctx.closePath(); ctx.fill(); } /* ── основная отрисовка ── */ function draw() { /* тряска камеры */ var sx = 0, sy = 0; if (shake > 0) { sx = (Math.random() - 0.5) * shake; sy = (Math.random() - 0.5) * shake; shake *= 0.85; if (shake < 0.5) shake = 0; } ctx.save(); ctx.translate(sx, sy); /* небо — градиент день/ночь */ var skyTop = lerpColor('#0f3460', '#0a0a23', nightCycle); var skyBot = lerpColor('#1a1a5e', '#0f0f2e', nightCycle); var sky = ctx.createLinearGradient(0, 0, 0, ground); sky.addColorStop(0, skyTop); sky.addColorStop(1, skyBot); ctx.fillStyle = sky; ctx.fillRect(0, 0, W, ground); /* звёзды ночью */ if (nightCycle > 0.4) { ctx.fillStyle = 'rgba(255,255,255,' + ((nightCycle - 0.4) * 1.2) + ')'; for (var s = 0; s < 30; s++) { var sx2 = (s * 137.5 + frame * 0.02) % W; var sy2 = (s * 97.3) % (ground - 20) + 10; ctx.beginPath(); ctx.arc(sx2, sy2, 1, 0, Math.PI * 2); ctx.fill(); } } /* облака */ ctx.fillStyle = 'rgba(255,255,255,' + (0.12 + nightCycle * 0.05) + ')'; for (var k = 0; k < clouds.length; k++) { var cl = clouds[k]; ctx.beginPath(); ctx.arc(cl.x, cl.y, cl.r, 0, Math.PI * 2); ctx.arc(cl.x + cl.r * 0.7, cl.y - cl.r * 0.3, cl.r * 0.7, 0, Math.PI * 2); ctx.arc(cl.x - cl.r * 0.5, cl.y + cl.r * 0.1, cl.r * 0.6, 0, Math.PI * 2); ctx.fill(); } /* земля */ var gndGrd = ctx.createLinearGradient(0, ground, 0, H); gndGrd.addColorStop(0, '#2d3436'); gndGrd.addColorStop(1, '#1a1a2e'); ctx.fillStyle = gndGrd; ctx.fillRect(0, ground, W, H - ground); /* трава */ ctx.strokeStyle = '#00b894'; ctx.lineWidth = 1.5; for (var g = 0; g < W; g += 12) { var gh = 4 + Math.sin(g * 0.5 + frame * 0.05) * 2; ctx.beginPath(); ctx.moveTo(g, ground); ctx.lineTo(g + 2, ground - gh); ctx.stroke(); } /* линия земли */ ctx.strokeStyle = '#00b894'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(0, ground); ctx.lineTo(W, ground); ctx.stroke(); /* пыль при беге */ if (!dead && cat.jumps === 0) { if (frame % 4 === 0) { particles.push({ x: cat.x + 5, y: ground - 2, vx: -Math.random() * 2, vy: -Math.random() * 1.5, life: 12 + Math.random() * 8, r: 1.5 + Math.random() * 2, col: '#b2bec3' }); } } /* кактусы */ for (var i = 0; i < cacti.length; i++) { drawCactus(cacti[i]); } /* тень кота */ ctx.fillStyle = 'rgba(0,0,0,0.2)'; var shadowScale = 1 - (ground - cat.h - cat.y) / 120; if (shadowScale < 0.3) shadowScale = 0.3; ctx.beginPath(); ctx.ellipse(cat.x + cat.w / 2, ground + 2, cat.w / 2 * shadowScale, 3 * shadowScale, 0, 0, Math.PI * 2); ctx.fill(); /* кот */ if (!dead) { drawCat(cat.x, cat.y, cat.w, cat.h); } else { /* мёртвый кот — перевёрнутый */ ctx.save(); ctx.translate(cat.x + cat.w / 2, cat.y + cat.h / 2); ctx.rotate(Math.PI); ctx.translate(-cat.w / 2, -cat.h / 2); drawCat(0, 0, cat.w, cat.h); ctx.restore(); /* текст */ ctx.fillStyle = 'rgba(0,0,0,0.5)'; ctx.fillRect(0, 0, W, H); ctx.fillStyle = '#e94560'; ctx.font = 'bold 22px system-ui'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('😿 Врезался!', W / 2, H / 2 - 16); ctx.fillStyle = '#dfe6e9'; ctx.font = '14px system-ui'; ctx.fillText('Счёт: ' + score + ' | Рекорд: ' + best, W / 2, H / 2 + 14); ctx.font = '12px system-ui'; ctx.fillStyle = '#888'; ctx.fillText('Тап или пробел — заново', W / 2, H / 2 + 38); ctx.textAlign = 'start'; ctx.textBaseline = 'alphabetic'; } /* частицы */ for (var p = 0; p < particles.length; p++) { var pt = particles[p]; ctx.globalAlpha = pt.life / 30; ctx.fillStyle = pt.col; ctx.beginPath(); ctx.arc(pt.x, pt.y, pt.r, 0, Math.PI * 2); ctx.fill(); } ctx.globalAlpha = 1; /* двойной прыжок индикатор */ if (!dead) { for (var d = 0; d < cat.maxJumps; d++) { ctx.beginPath(); ctx.arc(cat.x + cat.w / 2 - 6 + d * 12, cat.y - 8, 4, 0, Math.PI * 2); ctx.fillStyle = d < (cat.maxJumps - cat.jumps) ? '#f6e58d' : '#636e72'; ctx.fill(); ctx.strokeStyle = '#2d3436'; ctx.lineWidth = 1; ctx.stroke(); } } /* скорость */ if (!dead) { ctx.fillStyle = 'rgba(255,255,255,0.3)'; ctx.font = '10px system-ui'; ctx.fillText('×' + spd.toFixed(1), W - 40, 16); } ctx.restore(); } /* ── утилиты цвета ── */ function lerpColor(a, b, t) { var ar = parseInt(a.slice(1, 3), 16), ag = parseInt(a.slice(3, 5), 16), ab = parseInt(a.slice(5, 7), 16); var br = parseInt(b.slice(1, 3), 16), bg = parseInt(b.slice(3, 5), 16), bb = parseInt(b.slice(5, 7), 16); var rr = Math.round(ar + (br - ar) * t); var rg = Math.round(ag + (bg - ag) * t); var rb = Math.round(ab + (bb - ab) * t); return '#' + ((1 << 24) + (rr << 16) + (rg << 8) + rb).toString(16).slice(1); } /* ── цикл ── */ function loop() { update(); draw(); requestAnimationFrame(loop); } /* ── управление ── */ window.addEventListener('keydown', function (e) { if (e.code === 'Space' || e.code === 'ArrowUp') { e.preventDefault(); jump(); } }); c.addEventListener('touchstart', function (e) { e.preventDefault(); jump(); }, { passive: false }); c.addEventListener('click', function () { jump(); }); window.addEventListener('resize', resize); init(); loop(); })();