Liveloop

An interactive timeline for social media. Every post is a tiny app you can play, save, and remix.

Product

  • Feed
  • Create
  • Claude Code plugin
  • Blog

Legal

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • DMCA

Project

  • Templates
© 2026 Liveloop. All rights reserved.
LiveloopVersion history

v1Current

@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">&#9650;</button>
+ <div class="drow">
+ <button class="db" type="button" data-dx="-1" data-dy="0" aria-label="Left">&#9664;</button>
+ <button class="db" type="button" data-dx="1" data-dy="0" aria-label="Right">&#9654;</button>
+ </div>
+ <button class="db" type="button" data-dx="0" data-dy="1" aria-label="Down">&#9660;</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>

v1Current

@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">&#9650;</button>
+ <div class="drow">
+ <button class="db" type="button" data-dx="-1" data-dy="0" aria-label="Left">&#9664;</button>
+ <button class="db" type="button" data-dx="1" data-dy="0" aria-label="Right">&#9654;</button>
+ </div>
+ <button class="db" type="button" data-dx="0" data-dy="1" aria-label="Down">&#9660;</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>
← Version history