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

v2Current

@joaobarrosfranca · 6/7/2026, 11:20:22 PM

Diff against v1.

-{"scenes":[{"id":"s1","text":"","media":{"type":"link","value":"https://www.tiktok.com/@liveloop.space/video/7647562856971504918?_r=1&_t=ZN-971Y15Sbsne"},"title":"Stop crying Mark!"}]}
+{"scenes": [{"id": "s1", "title": "", "text": "", "media": {"type": "link", "value": "https://www.tiktok.com/@liveloop.space/video/7647562856971504918?_r=1&_t=ZN-971Y15Sbsne"}, "overlay": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\"><style>\nhtml,body{margin:0;padding:0;width:100%;height:100%;background:transparent;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;overflow:hidden}\n*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}\n.wrap{position:absolute;left:50%;bottom:26px;transform:translateX(-50%);width:92%;max-width:360px;z-index:1000;pointer-events:auto}\n.card{background:rgba(0,0,0,.72);border-radius:18px;padding:14px 14px 12px;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);box-shadow:0 8px 26px rgba(0,0,0,.4)}\n.title{color:#fff;font-size:14px;font-weight:800;text-align:center;margin:0 0 11px;letter-spacing:.2px;text-shadow:0 1px 3px rgba(0,0,0,.5)}\n.btns{display:flex;gap:10px}\n.btn{flex:1;border:0;border-radius:14px;padding:13px 6px;color:#fff;cursor:pointer;font-weight:800;text-shadow:0 1px 2px rgba(0,0,0,.35);transition:transform .08s ease,box-shadow .15s ease;box-shadow:0 4px 10px rgba(0,0,0,.25);position:relative}\n.btn:active{transform:scale(.95)}\n.btn .name{font-size:15px;display:block}\n.btn .pts{font-size:21px;font-weight:900;display:block;margin-top:2px}\n.mark{background:linear-gradient(135deg,#4a90e2,#2e5cb8)}\n.sophie{background:linear-gradient(135deg,#e74c3c,#c0392b)}\n.btn.pick{box-shadow:0 0 0 2px #ffd54a,0 6px 16px rgba(0,0,0,.35)}\n.btn.pick::after{content:'✓';position:absolute;top:7px;right:9px;font-size:12px}\n.bar{display:flex;height:12px;border-radius:999px;overflow:hidden;margin:13px 0 7px;background:rgba(255,255,255,.16)}\n.bar .m{background:#4a90e2;transition:width .4s cubic-bezier(.4,1.2,.5,1)}\n.bar .s{background:#e74c3c;transition:width .4s cubic-bezier(.4,1.2,.5,1)}\n.lead{text-align:center;color:#ffd54a;font-size:14px;font-weight:900;min-height:18px;text-shadow:0 1px 3px rgba(0,0,0,.55)}\n.hint{text-align:center;color:#cfcfcf;font-size:11px;margin-top:5px;min-height:13px}\n</style></head><body>\n<div class=\"wrap\"><div class=\"card\">\n <div class=\"title\">Who's your pick? 🔥</div>\n <div class=\"btns\">\n <button class=\"btn mark\" id=\"bMark\"><span class=\"name\">Mark 🔵</span><span class=\"pts\" id=\"pMark\">0</span></button>\n <button class=\"btn sophie\" id=\"bSophie\"><span class=\"name\">Sophie 🔴</span><span class=\"pts\" id=\"pSophie\">0</span></button>\n </div>\n <div class=\"bar\"><div class=\"m\" id=\"barM\" style=\"width:50%\"></div><div class=\"s\" id=\"barS\" style=\"width:50%\"></div></div>\n <div class=\"lead\" id=\"lead\">Tap to vote — live results!</div>\n <div class=\"hint\" id=\"hint\">Everyone's votes count. You can change your pick.</div>\n</div></div>\n<script>\n(function(){\n var AC=window.AudioContext||window.webkitAudioContext,ctx=null;\n function beep(f){try{if(!ctx){ctx=new AC();ctx.resume&&ctx.resume();}var n=ctx.currentTime,o=ctx.createOscillator(),g=ctx.createGain();o.connect(g);g.connect(ctx.destination);o.type='sine';o.frequency.value=f;g.gain.setValueAtTime(.18,n);g.gain.exponentialRampToValueAtTime(.01,n+.16);o.start(n);o.stop(n+.16);}catch(e){}}\n var ll=window.liveloop, COLL='mark_sophie';\n var pick=null, mark=0, sophie=0;\n var pM=document.getElementById('pMark'),pS=document.getElementById('pSophie'),barM=document.getElementById('barM'),barS=document.getElementById('barS'),lead=document.getElementById('lead'),hint=document.getElementById('hint'),bMark=document.getElementById('bMark'),bSophie=document.getElementById('bSophie');\n function hasData(){return !!(ll&&ll.data);}\n function hasStore(){return !!(ll&&ll.storage);}\n function render(){\n pM.textContent=mark;pS.textContent=sophie;\n var t=mark+sophie,pm=t?Math.round(mark/t*100):50;\n barM.style.width=pm+'%';barS.style.width=(100-pm)+'%';\n bMark.classList.toggle('pick',pick==='mark');\n bSophie.classList.toggle('pick',pick==='sophie');\n if(t===0)lead.textContent='Tap to vote — live results!';\n else if(mark>sophie)lead.textContent='👑 Mark is winning! '+mark+'–'+sophie;\n else if(sophie>mark)lead.textContent='👑 Sophie is winning! '+sophie+'–'+mark;\n else lead.textContent=\"🤝 It's a tie! \"+mark+'–'+sophie;\n hint.textContent=pick?('Your pick: '+(pick==='mark'?'Mark 🔵':'Sophie 🔴')+' — tap to switch'):\"Everyone's votes count. You can change your pick.\";\n }\n function loadShared(){\n if(!hasData()){render();return;}\n try{\n ll.data.list(COLL,{limit:5000}).then(function(r){\n if(r&&r.ok&&r.records){\n var m=0,s=0;\n r.records.forEach(function(rec){var v=rec.data&&rec.data.vote;if(v==='mark')m++;else if(v==='sophie')s++;});\n mark=m;sophie=s;render();\n }\n }).catch(function(){});\n }catch(e){}\n }\n function vote(v){\n if(pick===v)return;\n // optimistic local update so it feels instant, then reconcile with shared totals\n if(pick==='mark')mark=Math.max(0,mark-1);else if(pick==='sophie')sophie=Math.max(0,sophie-1);\n if(v==='mark')mark++;else sophie++;\n pick=v;beep(v==='mark'?520:660);render();\n if(hasStore()){try{ll.storage.set({vote:v});}catch(e){}}\n if(hasData()){try{ll.data.submit(COLL,{vote:v},{mode:'keep_last'}).then(loadShared).catch(function(){});}catch(e){}}\n }\n bMark.addEventListener('click',function(){vote('mark');});\n bSophie.addEventListener('click',function(){vote('sophie');});\n // restore this viewer's earlier pick, then load the shared tally\n if(hasStore()){try{ll.storage.get().then(function(d){if(d&&d.vote)pick=d.vote;loadShared();}).catch(loadShared);}catch(e){loadShared();}}else{loadShared();}\n setInterval(loadShared,3000);\n render();\n})();\n</script>\n</body></html>\n"}]}

v2Current

@joaobarrosfranca · 6/7/2026, 11:20:22 PM

Diff against v1.

-{"scenes":[{"id":"s1","text":"","media":{"type":"link","value":"https://www.tiktok.com/@liveloop.space/video/7647562856971504918?_r=1&_t=ZN-971Y15Sbsne"},"title":"Stop crying Mark!"}]}
+{"scenes": [{"id": "s1", "title": "", "text": "", "media": {"type": "link", "value": "https://www.tiktok.com/@liveloop.space/video/7647562856971504918?_r=1&_t=ZN-971Y15Sbsne"}, "overlay": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\"><style>\nhtml,body{margin:0;padding:0;width:100%;height:100%;background:transparent;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;overflow:hidden}\n*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}\n.wrap{position:absolute;left:50%;bottom:26px;transform:translateX(-50%);width:92%;max-width:360px;z-index:1000;pointer-events:auto}\n.card{background:rgba(0,0,0,.72);border-radius:18px;padding:14px 14px 12px;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);box-shadow:0 8px 26px rgba(0,0,0,.4)}\n.title{color:#fff;font-size:14px;font-weight:800;text-align:center;margin:0 0 11px;letter-spacing:.2px;text-shadow:0 1px 3px rgba(0,0,0,.5)}\n.btns{display:flex;gap:10px}\n.btn{flex:1;border:0;border-radius:14px;padding:13px 6px;color:#fff;cursor:pointer;font-weight:800;text-shadow:0 1px 2px rgba(0,0,0,.35);transition:transform .08s ease,box-shadow .15s ease;box-shadow:0 4px 10px rgba(0,0,0,.25);position:relative}\n.btn:active{transform:scale(.95)}\n.btn .name{font-size:15px;display:block}\n.btn .pts{font-size:21px;font-weight:900;display:block;margin-top:2px}\n.mark{background:linear-gradient(135deg,#4a90e2,#2e5cb8)}\n.sophie{background:linear-gradient(135deg,#e74c3c,#c0392b)}\n.btn.pick{box-shadow:0 0 0 2px #ffd54a,0 6px 16px rgba(0,0,0,.35)}\n.btn.pick::after{content:'✓';position:absolute;top:7px;right:9px;font-size:12px}\n.bar{display:flex;height:12px;border-radius:999px;overflow:hidden;margin:13px 0 7px;background:rgba(255,255,255,.16)}\n.bar .m{background:#4a90e2;transition:width .4s cubic-bezier(.4,1.2,.5,1)}\n.bar .s{background:#e74c3c;transition:width .4s cubic-bezier(.4,1.2,.5,1)}\n.lead{text-align:center;color:#ffd54a;font-size:14px;font-weight:900;min-height:18px;text-shadow:0 1px 3px rgba(0,0,0,.55)}\n.hint{text-align:center;color:#cfcfcf;font-size:11px;margin-top:5px;min-height:13px}\n</style></head><body>\n<div class=\"wrap\"><div class=\"card\">\n <div class=\"title\">Who's your pick? 🔥</div>\n <div class=\"btns\">\n <button class=\"btn mark\" id=\"bMark\"><span class=\"name\">Mark 🔵</span><span class=\"pts\" id=\"pMark\">0</span></button>\n <button class=\"btn sophie\" id=\"bSophie\"><span class=\"name\">Sophie 🔴</span><span class=\"pts\" id=\"pSophie\">0</span></button>\n </div>\n <div class=\"bar\"><div class=\"m\" id=\"barM\" style=\"width:50%\"></div><div class=\"s\" id=\"barS\" style=\"width:50%\"></div></div>\n <div class=\"lead\" id=\"lead\">Tap to vote — live results!</div>\n <div class=\"hint\" id=\"hint\">Everyone's votes count. You can change your pick.</div>\n</div></div>\n<script>\n(function(){\n var AC=window.AudioContext||window.webkitAudioContext,ctx=null;\n function beep(f){try{if(!ctx){ctx=new AC();ctx.resume&&ctx.resume();}var n=ctx.currentTime,o=ctx.createOscillator(),g=ctx.createGain();o.connect(g);g.connect(ctx.destination);o.type='sine';o.frequency.value=f;g.gain.setValueAtTime(.18,n);g.gain.exponentialRampToValueAtTime(.01,n+.16);o.start(n);o.stop(n+.16);}catch(e){}}\n var ll=window.liveloop, COLL='mark_sophie';\n var pick=null, mark=0, sophie=0;\n var pM=document.getElementById('pMark'),pS=document.getElementById('pSophie'),barM=document.getElementById('barM'),barS=document.getElementById('barS'),lead=document.getElementById('lead'),hint=document.getElementById('hint'),bMark=document.getElementById('bMark'),bSophie=document.getElementById('bSophie');\n function hasData(){return !!(ll&&ll.data);}\n function hasStore(){return !!(ll&&ll.storage);}\n function render(){\n pM.textContent=mark;pS.textContent=sophie;\n var t=mark+sophie,pm=t?Math.round(mark/t*100):50;\n barM.style.width=pm+'%';barS.style.width=(100-pm)+'%';\n bMark.classList.toggle('pick',pick==='mark');\n bSophie.classList.toggle('pick',pick==='sophie');\n if(t===0)lead.textContent='Tap to vote — live results!';\n else if(mark>sophie)lead.textContent='👑 Mark is winning! '+mark+'–'+sophie;\n else if(sophie>mark)lead.textContent='👑 Sophie is winning! '+sophie+'–'+mark;\n else lead.textContent=\"🤝 It's a tie! \"+mark+'–'+sophie;\n hint.textContent=pick?('Your pick: '+(pick==='mark'?'Mark 🔵':'Sophie 🔴')+' — tap to switch'):\"Everyone's votes count. You can change your pick.\";\n }\n function loadShared(){\n if(!hasData()){render();return;}\n try{\n ll.data.list(COLL,{limit:5000}).then(function(r){\n if(r&&r.ok&&r.records){\n var m=0,s=0;\n r.records.forEach(function(rec){var v=rec.data&&rec.data.vote;if(v==='mark')m++;else if(v==='sophie')s++;});\n mark=m;sophie=s;render();\n }\n }).catch(function(){});\n }catch(e){}\n }\n function vote(v){\n if(pick===v)return;\n // optimistic local update so it feels instant, then reconcile with shared totals\n if(pick==='mark')mark=Math.max(0,mark-1);else if(pick==='sophie')sophie=Math.max(0,sophie-1);\n if(v==='mark')mark++;else sophie++;\n pick=v;beep(v==='mark'?520:660);render();\n if(hasStore()){try{ll.storage.set({vote:v});}catch(e){}}\n if(hasData()){try{ll.data.submit(COLL,{vote:v},{mode:'keep_last'}).then(loadShared).catch(function(){});}catch(e){}}\n }\n bMark.addEventListener('click',function(){vote('mark');});\n bSophie.addEventListener('click',function(){vote('sophie');});\n // restore this viewer's earlier pick, then load the shared tally\n if(hasStore()){try{ll.storage.get().then(function(d){if(d&&d.vote)pick=d.vote;loadShared();}).catch(loadShared);}catch(e){loadShared();}}else{loadShared();}\n setInterval(loadShared,3000);\n render();\n})();\n</script>\n</body></html>\n"}]}
← Version history