@liveloop · 5/18/2026, 7:49:56 AM
Initial version — all lines are new.
+<style>+ html,body{height:100%;margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;background:#0f172a;color:#f1f5f9;overflow:hidden;}+ #app{position:relative;height:100%;}+ .screen{position:absolute;inset:0;display:none;flex-direction:column;align-items:center;justify-content:center;gap:.85rem;padding:1.5rem;box-sizing:border-box;text-align:center;}+ h1{margin:0;font-size:1.5rem;}+ .sub{margin:0;color:#94a3b8;font-size:.84rem;max-width:26ch;line-height:1.5;}+ button{font:inherit;font-weight:700;border:0;border-radius:9999px;padding:.7rem 1.5rem;cursor:pointer;touch-action:manipulation;background:#38bdf8;color:#08131f;font-size:1rem;}+ button.alt{background:#1e293b;color:#e2e8f0;}+ input{font:inherit;font-size:1rem;border-radius:.6rem;border:2px solid #334155;background:#0b1220;color:#fff;padding:.55rem .7rem;}+ .name-field{width:11rem;text-align:center;}+ .err{color:#f87171;font-size:.85rem;}+ #chat{position:absolute;inset:0;display:none;flex-direction:column;}+ #chead{display:flex;align-items:center;gap:.4rem;padding:.55rem .65rem;background:#1e293b;border-bottom:1px solid #334155;}+ #cwho{flex:1;font-weight:700;font-size:.88rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}+ #chead button{padding:.4rem .85rem;font-size:.76rem;font-weight:700;}+ #clog{flex:1;overflow-y:auto;padding:.7rem;display:flex;flex-direction:column;gap:.4rem;}+ .msg{max-width:78%;padding:.45rem .7rem;border-radius:.9rem;font-size:.92rem;line-height:1.35;word-wrap:break-word;white-space:pre-wrap;}+ .msg.me{align-self:flex-end;background:#38bdf8;color:#08131f;border-bottom-right-radius:.2rem;}+ .msg.them{align-self:flex-start;background:#1e293b;color:#f1f5f9;border-bottom-left-radius:.2rem;}+ .sys{align-self:center;color:#64748b;font-size:.78rem;text-align:center;}+ #cbar{display:flex;gap:.45rem;padding:.55rem;background:#1e293b;border-top:1px solid #334155;}+ #cinput{flex:1;}+ #cinput:disabled{opacity:.5;}+</style>+<div id="app">+ <div class="screen" id="screen"></div>+ <div id="chat">+ <div id="chead">+ <span id="cwho">Stranger</span>+ <button class="alt" id="nextBtn" type="button">Next ⏭</button>+ <button class="alt" id="stopBtn" type="button">Stop</button>+ </div>+ <div id="clog"></div>+ <div id="cbar">+ <input id="cinput" type="text" maxlength="500" placeholder="Say something…" autocomplete="off">+ <button id="sendBtn" type="button">Send</button>+ </div>+ </div>+</div>+<script>+(function(){+ var L=window.liveloop, RT=L&&L.realtime;+ var screen=document.getElementById('screen'), chat=document.getElementById('chat');+ var clog=document.getElementById('clog'), cinput=document.getElementById('cinput'), cwho=document.getElementById('cwho');+ var phase='menu';+ var lobbyId=null, lobbyMates=[], lobbyCand=null, queueTimer=null, shownCount=-1, joinStamp=0;+ var myName='', partnerName='', players=[], hiTimer=null;++ function onTap(el,fn){+ var sx=0,sy=0,armed=false;+ el.addEventListener('pointerdown',function(e){ armed=true;sx=e.clientX;sy=e.clientY; });+ el.addEventListener('pointercancel',function(){ armed=false; });+ el.addEventListener('pointerup',function(e){+ if(!armed) return; armed=false;+ if(Math.abs(e.clientX-sx)<16&&Math.abs(e.clientY-sy)<16) fn();+ });+ }+ function el(tag,cls,txt){ var e=document.createElement(tag); if(cls)e.className=cls; if(txt!=null)e.textContent=txt; return e; }+ function show(){ chat.style.display='none'; screen.style.display='flex'; screen.innerHTML=''; for(var i=0;i<arguments.length;i++) screen.appendChild(arguments[i]); }+ function showChat(){ screen.style.display='none'; chat.style.display='flex'; }++ if(!RT){ show(el('h1',null,'💬 Chat Roulette'), el('p','err','Realtime is not available here — open this from the Liveloop feed.')); return; }++ function commitName(inp){+ var n=(((inp&&inp.value)||'').trim()).slice(0,16) || 'Someone';+ myName=n;+ if(L&&L.storage){ try{ L.storage.set({name:n}); }catch(e){} }+ }+ function loadName(){+ if(!L||!L.storage) return;+ L.storage.get().then(function(s){+ if(s&&typeof s.name==='string'&&s.name) myName=s.name;+ if(phase==='menu') menu();+ },function(){});+ }+ function menu(){+ phase='menu';+ var nameInp=el('input','name-field'); nameInp.maxLength=16; nameInp.placeholder='Your name'; nameInp.value=myName;+ var start=el('button',null,'Start chatting');+ onTap(start,function(){ commitName(nameInp); startQueue(); });+ show(el('h1',null,'💬 Chat Roulette'),+ el('p','sub','Type a name and we’ll match you with a random person to chat with. Be kind — and use the 🚩 flag on the card to report anything off.'),+ nameInp, start);+ }++ function stopQueue(){ if(queueTimer){ clearInterval(queueTimer); queueTimer=null; } }+ function clearHi(){ if(hiTimer){ clearInterval(hiTimer); hiTimer=null; } }+ function backToMenu(){ try{ RT.leave(); }catch(e){} stopQueue(); clearHi(); phase='menu'; players=[]; menu(); }+ function errScreen(msg){+ stopQueue(); clearHi(); phase='menu';+ var back=el('button','alt','Back'); onTap(back,menu);+ show(el('h1',null,'Something went wrong'), el('p','err',msg), back);+ }+ function startQueue(){+ try{ RT.leave(); }catch(e){}+ stopQueue(); clearHi();+ phase='searching'; lobbyCand=null; lobbyMates=[]; shownCount=-1;+ searching();+ RT.join('_lobby').then(function(res){+ if(phase!=='searching') return;+ if(!res||!res.ok){ errScreen((res&&res.error)||'Could not reach matchmaking.'); return; }+ lobbyId=res.playerId; lobbyMates=res.players||[];+ queueTimer=setInterval(queueTick,1000);+ queueTick();+ });+ }+ function searching(){+ var n=lobbyMates.length;+ var cancel=el('button','alt','Cancel'); onTap(cancel,backToMenu);+ show(el('h1',null,'Finding someone…'),+ el('p','sub', n>1 ? (n+' people waiting — pairing you up.') : 'Waiting for someone else to start chatting.'),+ cancel);+ }+ function queueTick(){+ if(phase!=='searching') return;+ var sorted=lobbyMates.slice().sort(), cand=null;+ if(sorted.length>=2){+ if(lobbyId===sorted[0]) cand=sorted[1];+ else if(lobbyId===sorted[1]) cand=sorted[0];+ }+ lobbyCand=cand;+ if(cand) RT.send({t:'match',with:cand});+ if(lobbyMates.length!==shownCount){ shownCount=lobbyMates.length; searching(); }+ }+ function roomFor(a,b){+ var s=[a,b].slice().sort().join('|'), h=5381;+ for(var i=0;i<s.length;i++){ h=(((h<<5)+h)+s.charCodeAt(i))>>>0; }+ return 'c'+h.toString(36);+ }+ function beginMatch(partner){+ if(phase!=='searching') return;+ stopQueue();+ phase='connecting';+ show(el('h1',null,'Found someone!'), el('p','sub','Connecting you…'));+ RT.join(roomFor(lobbyId,partner)).then(function(res){+ if(phase!=='connecting') return;+ if(!res||!res.ok){ errScreen((res&&res.error)||'Could not connect.'); return; }+ players=res.players||[];+ phase='lobby';+ var stamp=++joinStamp;+ show(el('h1',null,'Almost there…'), el('p','sub','Waiting for the other person to load.'));+ checkStart();+ setTimeout(function(){ if(phase==='lobby' && joinStamp===stamp) startQueue(); }, 9000);+ });+ }+ function checkStart(){+ if((phase==='lobby'||phase==='connecting') && players.length>=2) startChat();+ }++ function startChat(){+ phase='chatting'; partnerName=''; cwho.textContent='Stranger';+ clog.innerHTML=''; sysMsg('You’re connected. Say hi! 👋');+ cinput.value=''; cinput.disabled=false;+ showChat();+ var n=0; clearHi(); sendHi();+ hiTimer=setInterval(function(){+ if(++n>=3 || phase!=='chatting'){ clearHi(); return; }+ sendHi();+ },900);+ }+ function sendHi(){ RT.send({t:'hi',nm:myName}); }+ function sysMsg(t){ clog.appendChild(el('div','sys',t)); clog.scrollTop=clog.scrollHeight; }+ function addMsg(mine,text){+ clog.appendChild(el('div','msg '+(mine?'me':'them'),text));+ clog.scrollTop=clog.scrollHeight;+ }+ function setPartner(nm){+ if(nm && nm!==partnerName){ partnerName=nm; cwho.textContent='Chatting with '+nm; }+ }+ function sendMsg(){+ if(phase!=='chatting' || cinput.disabled) return;+ var t=(cinput.value||'').trim().slice(0,500);+ if(!t) return;+ cinput.value='';+ RT.send({t:'msg',text:t,nm:myName});+ addMsg(true,t);+ }+ onTap(document.getElementById('sendBtn'),sendMsg);+ onTap(document.getElementById('nextBtn'),function(){ clearHi(); startQueue(); });+ onTap(document.getElementById('stopBtn'),backToMenu);+ cinput.addEventListener('keydown',function(e){ if(e.key==='Enter'){ e.preventDefault(); sendMsg(); } });++ RT.onPlayers(function(list){+ list=list||[];+ if(phase==='searching'){ lobbyMates=list; queueTick(); return; }+ players=list;+ if(phase==='lobby'||phase==='connecting'){ checkStart(); }+ else if(phase==='chatting' && players.length<2){+ cinput.disabled=true;+ cwho.textContent='They left — tap Next ⏭';+ sysMsg((partnerName||'They')+' left the chat.');+ }+ });+ RT.onMessage(function(m){+ var d=m&&m.data; if(!d) return;+ if(phase==='searching'){+ if(d.t==='match' && d.with===lobbyId){+ queueTick();+ if(m.from===lobbyCand) beginMatch(m.from);+ }+ return;+ }+ if(phase!=='chatting') return;+ if(typeof d.nm==='string' && d.nm) setPartner(d.nm);+ if(d.t==='msg' && typeof d.text==='string'){ addMsg(false,d.text.slice(0,500)); }+ });++ menu();+ loadName();+})();+</script>