@liveloop · 5/15/2026, 2:13:06 PM
Initial version — all lines are new.
+<style>+ /* Modern "cover" look — deep background with a warm + purple glow,+ serif title, a glowing board and rounded snake segments. */+ html, body { background-color: #0a0a0a; background-image: radial-gradient(120% 70% at 82% -8%, rgba(255,90,31,0.20), rgba(255,255,255,0) 46%), radial-gradient(120% 90% at 18% 112%, rgba(124,58,237,0.26), rgba(255,255,255,0) 52%); color: #F4EEE3; font-family: "Geist", system-ui, -apple-system, Segoe UI, Roboto, sans-serif; overflow: hidden; }+ .wrap { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; gap: 0.55rem; padding: 0.9rem; }+ h1 { margin: 0; font-family: "Instrument Serif", Georgia, serif; font-weight: 400; font-size: 1.7rem; line-height: 1.05; letter-spacing: -0.01em; }+ .score { font-size: 0.8rem; color: #B7ACE6; }+ .score b { color: #ef4444; font-weight: 600; }+ canvas { background: linear-gradient(180deg, rgba(255,255,255,0.07), rgba(0,0,0,0.16)); border-radius: 18px; box-shadow: 0 0 0 1px rgba(255,255,255,0.06), 0 24px 60px -22px rgba(124,58,237,0.55); max-width: 88%; max-height: 52%; touch-action: none; }+ .dpad { display: flex; flex-direction: column; align-items: center; gap: 0.4rem; }+ .drow { display: flex; gap: 2.9rem; }+ .db { width: 3rem; height: 3rem; padding: 0; border-radius: 0.85rem; border: 0; background: rgba(255,255,255,0.09); color: #F4EEE3; font-size: 1.05rem; line-height: 1; cursor: pointer; touch-action: manipulation; -webkit-tap-highlight-color: transparent; transition: background 0.12s; }+ .db:active { background: #22c55e; color: #0E0A1F; }+ .over { position: absolute; inset: 0; display: none; align-items: center; justify-content: center; flex-direction: column; gap: 0.6rem; background: rgba(14,10,31,0.72); backdrop-filter: blur(12px); }+ .over.visible { display: flex; }+ .over h2 { margin: 0; font-family: "Instrument Serif", Georgia, serif; font-weight: 400; font-size: 2rem; }+ #restart { padding: 0.6rem 1.4rem; border-radius: 9999px; border: 0; background: #ef4444; color: #fff; font-weight: 500; cursor: pointer; }+</style>+<div class="wrap">+ <h1>Classic snake</h1>+ <canvas id="board" width="360" height="360" aria-label="Snake board"></canvas>+ <div class="score">Score <b id="score">0</b></div>+ <div class="dpad">+ <button class="db" type="button" data-dx="0" data-dy="-1" aria-label="Up">▲</button>+ <div class="drow">+ <button class="db" type="button" data-dx="-1" data-dy="0" aria-label="Left">◀</button>+ <button class="db" type="button" data-dx="1" data-dy="0" aria-label="Right">▶</button>+ </div>+ <button class="db" type="button" data-dx="0" data-dy="1" aria-label="Down">▼</button>+ </div>+</div>+<div class="over" id="over">+ <h2>Game over</h2>+ <button id="restart">Play again</button>+</div>+<script>+(function(){+ var GRID = 18;+ var TICK = 180;+ // Ease the player in: start ~1.7× slower than the chosen speed, then+ // accelerate toward TICK as the score grows (6ms faster per food).+ var START_TICK = Math.round(TICK * 1.7);+ function currentTick() { return Math.max(TICK, START_TICK - score * 6); }+ var canvas = document.getElementById('board');+ var ctx = canvas.getContext('2d');+ var scoreEl = document.getElementById('score');+ var overEl = document.getElementById('over');+ var restartBtn = document.getElementById('restart');+ var cell, snake, dir, food, score, timer, running, started;+ var foodSrc = "";+ var foodImg = null;+ if (foodSrc) { foodImg = new Image(); foodImg.src = foodSrc; }++ function resize() {+ var size = Math.min(canvas.parentElement.clientWidth * 0.88, canvas.parentElement.clientHeight * 0.52, 420);+ canvas.style.width = size + 'px';+ canvas.style.height = size + 'px';+ canvas.width = size;+ canvas.height = size;+ cell = canvas.width / GRID;+ draw();+ }++ function reset() {+ snake = [{ x: Math.floor(GRID/2), y: Math.floor(GRID/2) }];+ dir = { x: 1, y: 0 };+ placeFood();+ score = 0; scoreEl.textContent = '0';+ overEl.classList.remove('visible');+ running = true;+ // Wait for the first input before moving: the board shows the snake+ // and food, and a still preview (e.g. the editor's template thumb)+ // no longer auto-plays itself into a game over.+ started = false;+ clearTimeout(timer);+ draw();+ }++ // Self-scheduling loop so the interval can shrink as the score climbs+ // (a plain setInterval can't change its delay). Re-reads currentTick()+ // after every step.+ function loop() {+ if (!running || !started) return;+ tick();+ timer = setTimeout(loop, currentTick());+ }++ function startGame() {+ if (started || !running) return;+ started = true;+ clearTimeout(timer);+ timer = setTimeout(loop, currentTick());+ }++ function placeFood() {+ while (true) {+ var x = Math.floor(Math.random() * GRID);+ var y = Math.floor(Math.random() * GRID);+ if (!snake.some(function(s){ return s.x === x && s.y === y; })) {+ food = { x: x, y: y };+ return;+ }+ }+ }++ function tick() {+ if (!running) return;+ var head = { x: snake[0].x + dir.x, y: snake[0].y + dir.y };+ if (head.x < 0 || head.x >= GRID || head.y < 0 || head.y >= GRID || snake.some(function(s){ return s.x === head.x && s.y === head.y; })) {+ running = false;+ overEl.classList.add('visible');+ return;+ }+ snake.unshift(head);+ if (head.x === food.x && head.y === food.y) {+ score++; scoreEl.textContent = String(score);+ placeFood();+ } else {+ snake.pop();+ }+ draw();+ }++ function draw() {+ // resize() runs before reset() on first load, so guard against the+ // snake/food not being placed yet — otherwise the IIFE throws and+ // the game never starts.+ if (!snake || !food) return;+ ctx.clearRect(0, 0, canvas.width, canvas.height);+ // Food — the author's image, or a glowing dot (soft halo + core).+ if (foodImg && foodImg.complete && foodImg.naturalWidth) {+ ctx.drawImage(foodImg, food.x * cell, food.y * cell, cell, cell);+ } else {+ var fx = (food.x + 0.5) * cell, fy = (food.y + 0.5) * cell;+ ctx.fillStyle = '#ef4444';+ ctx.globalAlpha = 0.22;+ ctx.beginPath(); ctx.arc(fx, fy, cell * 0.72, 0, Math.PI * 2); ctx.fill();+ ctx.globalAlpha = 1;+ ctx.beginPath(); ctx.arc(fx, fy, cell * 0.34, 0, Math.PI * 2); ctx.fill();+ }+ // Snake — rounded squares fading head → tail for a modern gradient feel.+ var pad = cell * 0.13, rr = Math.min(8, cell * 0.34);+ ctx.fillStyle = '#22c55e';+ for (var i = 0; i < snake.length; i++) {+ var s = snake[i];+ ctx.globalAlpha = 1 - (i / snake.length) * 0.5;+ roundRectPath(ctx, s.x * cell + pad, s.y * cell + pad, cell - pad * 2, cell - pad * 2, rr);+ ctx.fill();+ }+ ctx.globalAlpha = 1;+ }++ function roundRectPath(c, x, y, w, h, r) {+ if (c.roundRect) { c.beginPath(); c.roundRect(x, y, w, h, r); return; }+ c.beginPath();+ c.moveTo(x + r, y);+ c.arcTo(x + w, y, x + w, y + h, r);+ c.arcTo(x + w, y + h, x, y + h, r);+ c.arcTo(x, y + h, x, y, r);+ c.arcTo(x, y, x + w, y, r);+ c.closePath();+ }++ function setDir(nx, ny) {+ if (snake.length > 1 && nx === -dir.x && ny === -dir.y) return;+ dir = { x: nx, y: ny };+ startGame(); // first direction press kicks off the loop+ }++ window.addEventListener('keydown', function(e){+ var k = e.key;+ if (k === 'ArrowUp' || k === 'w') { setDir(0, -1); e.preventDefault(); }+ else if (k === 'ArrowDown' || k === 's') { setDir(0, 1); e.preventDefault(); }+ else if (k === 'ArrowLeft' || k === 'a') { setDir(-1, 0); e.preventDefault(); }+ else if (k === 'ArrowRight' || k === 'd') { setDir(1, 0); e.preventDefault(); }+ });++ // On-screen D-pad — the primary control on mobile, and a no-keyboard+ // option on desktop (arrow keys only reach the game when the iframe+ // has focus).+ Array.prototype.forEach.call(document.querySelectorAll('.db'), function(b){+ b.addEventListener('click', function(){+ setDir(+ parseInt(b.getAttribute('data-dx'), 10),+ parseInt(b.getAttribute('data-dy'), 10),+ );+ });+ });++ var touchStart = null;+ canvas.addEventListener('touchstart', function(e){ touchStart = e.touches[0]; }, { passive: true });+ canvas.addEventListener('touchend', function(e){+ if (!touchStart) return;+ var t = e.changedTouches[0];+ var dx = t.clientX - touchStart.clientX;+ var dy = t.clientY - touchStart.clientY;+ if (Math.abs(dx) > Math.abs(dy)) setDir(dx > 0 ? 1 : -1, 0);+ else setDir(0, dy > 0 ? 1 : -1);+ touchStart = null;+ }, { passive: true });++ restartBtn.addEventListener('click', reset);+ window.addEventListener('resize', resize);++ // Visibility-aware pause via the liveloop SDK.+ if (window.liveloop) {+ window.liveloop.onVisibility(function(v){+ if (!v) { clearTimeout(timer); }+ else if (running && started) { clearTimeout(timer); timer = setTimeout(loop, currentTick()); }+ });+ }++ resize();+ reset();+})();+</script>+<script>/*ll-media-controls*/+(function(){+ function wire(){+ var ll = window.liveloop; if(!ll || !ll.declareMedia) return;+ var nodes = [].slice.call(document.querySelectorAll('video,audio')).filter(function(el){+ return !(el.id && el.id.indexOf('ll-')===0) && !el.hasAttribute('data-ll-unmanaged');+ });+ if(!nodes.length) return;+ var hasVideo = nodes.some(function(el){ return el.tagName==='VIDEO'; });+ ll.declareMedia({ sound:true, playback:hasVideo });+ var paused=false;+ function play(el){ try{ var p=el.play&&el.play(); if(p&&p.catch)p.catch(function(){}); }catch(e){} }+ function applyMuted(m){ nodes.forEach(function(el){ try{ el.muted=m; }catch(e){} if(!m&&!paused) play(el); }); }+ function applyPaused(p){ paused=p; nodes.forEach(function(el){ try{ if(p){ el.pause&&el.pause(); } else { play(el); } }catch(e){} }); }+ applyMuted(!!ll.muted);+ if(ll.onMute) ll.onMute(applyMuted);+ if(ll.onPause) ll.onPause(applyPaused);+ }+ if(document.readyState==='loading'){ document.addEventListener('DOMContentLoaded', wire); } else { wire(); }+})();+</script>