Download mesh.html from Jessedbrown/lightDOORwsMs: direct link, hf CLI and curl.
- Browser
- Download file 17.5 kB
-
https://huggingface.co/Jessedbrown/lightDOORwsMs/resolve/main/mesh.html
- Command line
-
hf download hf://Jessedbrown/lightDOORwsMs/mesh.html
-
curl -L -o mesh.html https://huggingface.co/Jessedbrown/lightDOORwsMs/resolve/main/mesh.html
17.5 kB
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width,initial-scale=1"> | |
| <title>The light mesh — you keep the colour, not the trace</title> | |
| <style> | |
| :root{--bg:#0a0812;--fg:#efeaf2;--dim:#8d8496;--line:#221c2c;--gold:#e8c07a; | |
| --ok:#7fe0a0;--no:#ff8b96} | |
| *{box-sizing:border-box}html,body{height:100%} | |
| body{margin:0;background:radial-gradient(70% 55% at 50% 0%,#171029 0%,#0a0812 62%); | |
| color:var(--fg);font:14px/1.6 ui-monospace,"SF Mono",Menlo,Consolas,monospace} | |
| main{max-width:740px;margin:0 auto;padding:24px 18px 80px} | |
| h1{font:600 22px/1.3 ui-serif,Georgia,serif;margin:0 0 4px} | |
| .sub{color:var(--dim);margin:0 0 18px;font-size:13px} | |
| button{background:var(--gold);color:#120d18;border:0;border-radius:11px; | |
| padding:14px 24px;font:700 14px ui-monospace,monospace;cursor:pointer; | |
| margin:4px 6px 4px 0} | |
| button.ghost{background:transparent;color:var(--fg);border:1px solid var(--line)} | |
| button:disabled{opacity:.4;cursor:default} | |
| h2{font-size:11px;letter-spacing:1.5px;text-transform:uppercase;color:var(--dim); | |
| border-top:1px solid var(--line);padding-top:13px;margin:22px 0 9px;font-weight:400} | |
| .row{display:flex;justify-content:space-between;gap:12px;padding:2px 0;font-size:13px} | |
| .k{color:var(--dim)} .ok{color:var(--ok)} .no{color:var(--no)} .acc{color:var(--gold)} | |
| textarea{width:100%;min-height:68px;background:rgba(255,255,255,.03);color:var(--fg); | |
| border:1px solid var(--line);border-radius:10px;padding:10px; | |
| font:11px/1.45 ui-monospace,monospace} | |
| canvas{width:100%;height:230px;display:block;border:1px solid var(--line); | |
| border-radius:11px;background:#07050e;margin:10px 0} | |
| #log{background:rgba(255,255,255,.02);border:1px solid var(--line);border-radius:10px; | |
| padding:10px;height:170px;overflow:auto;white-space:pre-wrap;font-size:11px; | |
| color:#c6bdd0} | |
| input{width:100%;background:rgba(255,255,255,.03);color:var(--fg); | |
| border:1px solid var(--line);border-radius:10px;padding:10px; | |
| font:13px ui-monospace,monospace;margin-top:8px} | |
| #held{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0} | |
| .chip{height:20px;width:52px;border-radius:5px;border:1px solid var(--line)} | |
| p{color:#cdc4d6} b{color:#fff} small{color:var(--dim)} | |
| </style> | |
| <main> | |
| <h1>The light mesh</h1> | |
| <p class="sub">Join by knowing <b>one</b>. Keep the <b>colour</b>, drop the trace. | |
| No server, no directory, no list of who exists.</p> | |
| <div> | |
| <button id="open">◉ OPEN</button> | |
| <button id="join" class="ghost">join someone's</button> | |
| </div> | |
| <h2>you</h2> | |
| <div class="row"><span class="k">your colour</span><span id="me" class="acc">—</span></div> | |
| <div class="row"><span class="k">boats connected</span><span id="np" class="ok">0</span></div> | |
| <div class="row"><span class="k">colours held</span><span id="nh" class="acc">0</span></div> | |
| <div class="row"><span class="k">rooms open</span><span id="nr">0</span></div> | |
| <div class="row"><span class="k">introductions</span><span id="ni">0</span></div> | |
| <div class="row"><span class="k">traces kept</span><span id="nt" class="ok">0</span></div> | |
| <div class="row"><span class="k">server in the path</span><span class="ok">none</span></div> | |
| <div id="held"></div> | |
| <canvas id="cv"></canvas> | |
| <div id="stage" style="display:none"> | |
| <h2 id="lbl">your trace — give it to one person</h2> | |
| <textarea id="mine" readonly></textarea> | |
| <button id="copy" class="ghost">copy</button> | |
| <h2>theirs — paste here</h2> | |
| <textarea id="theirs" placeholder="paste their trace"></textarea> | |
| <button id="take" class="ghost">take it</button> | |
| <button id="mint" class="ghost">new trace for somebody else</button> | |
| </div> | |
| <h2>the rooms</h2> | |
| <input id="room" value="light" placeholder="room name — a label, not a port"> | |
| <div id="log">nothing yet.</div> | |
| <input id="msg" placeholder="say something in this room" disabled> | |
| <h2>tracking and addressing are different things</h2> | |
| <p><b>Tracking</b> is keeping a record of where something is. <b>Addressing</b> is | |
| knowing what it is. You only need the first when the name was handed out by | |
| somebody — when the name <i>is</i> the thing, there is nothing to look up.</p> | |
| <h2>the trace, and the colour</h2> | |
| <p>The <b>trace</b> is the block that passes at the meeting. It is a <b>one-time | |
| introduction</b> and it expires the moment it is used. <b>This page throws it away | |
| on contact</b> — the counter above stays at zero on purpose.</p> | |
| <p>The <b>colour</b> is what you keep. Absorb it and it is yours: no contact list, | |
| no directory, no entry to delete. <b>Your network is not a list of who you know — | |
| it is the set of colours you carry.</b></p> | |
| <p><b>And the colour outlives the connection.</b> If a link drops nobody is lost; | |
| you still hold their colour, and any boat that also holds it can put you back | |
| together. Break every link at once and it reassembles, because what survived was | |
| never the wiring.</p> | |
| <h2>all the boats are the same boat</h2> | |
| <p>Every one runs this identical page. There is no host version and no client | |
| version. The only thing that differs is the colour, and the colour is the sha-256 | |
| of its own block — <b>two brown stops, banded 16…240, luminance-clamped | |
| 32…224.</b></p> | |
| <p>Photons are the same way, and it is measured rather than assumed: two identical | |
| photons meeting a beamsplitter always leave together, never one each way. <b>If you | |
| could tell them apart by anything at all, the effect disappears.</b> No serial | |
| number, no registry, nowhere to put one. If two boats ever produced the same block | |
| they would <i>be</i> the same boat.</p> | |
| <p><small>The first link is still made by hand. Everything after it travels the | |
| links that already exist.</small></p> | |
| </main> | |
| <script> | |
| /* --------------------------------------------------------------------------- | |
| Keep the colour, drop the trace. A boat names itself; nobody assigns. | |
| --------------------------------------------------------------------------- */ | |
| const ICE={iceServers:[{urls:["stun:stun.l.google.com:19302", | |
| "stun:stun1.l.google.com:19302"]}]}; | |
| const $=id=>document.getElementById(id); | |
| const peers=new Map(); /* colour -> {pc, ctl, rooms} */ | |
| const seen=new Set(); /* message ids already passed on */ | |
| const pending=new Map(); /* colour -> pc awaiting an answer */ | |
| let MYID=null, intros=0, traces=0, isHost=false, bootPc=null; | |
| /* the colours you carry, and they survive a reload */ | |
| let held=new Set(); | |
| try{ held=new Set(JSON.parse(localStorage.getItem("mesh.colours")||"[]")); }catch(e){} | |
| const absorb=c=>{ if(!c||c===MYID) return; held.add(c); | |
| try{ localStorage.setItem("mesh.colours",JSON.stringify([...held])); }catch(e){} | |
| paintHeld(); }; | |
| const log=t=>{const l=$("log"); | |
| if(l.textContent==="nothing yet.")l.textContent=""; | |
| l.textContent+=t+"\n"; l.scrollTop=l.scrollHeight;}; | |
| async function sha(s){ | |
| const d=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(s)); | |
| return [...new Uint8Array(d)].map(x=>x.toString(16).padStart(2,"0")).join(""); | |
| } | |
| const cl=(v,a,b)=>Math.max(a,Math.min(b,v)); | |
| function stops(h){ | |
| const p=i=>cl(parseInt((h+"000000000000").substr(i,2),16)||16,16,240); | |
| const lum=c=>0.2126*c[0]+0.7152*c[1]+0.0722*c[2]; | |
| const fix=c=>{const L=lum(c);return (L>=32&&L<=224)?c: | |
| c.map(x=>Math.round(cl(x*128/Math.max(1,L),0,255)));}; | |
| return [fix([p(0),p(2),p(4)]),fix([p(6),p(8),p(10)])]; | |
| } | |
| function paintHeld(){ | |
| $("nh").textContent=held.size; | |
| $("held").innerHTML=[...held].map(c=>{const [a,b]=stops(c); | |
| return "<div class=chip title='"+c+"' style=\"background:linear-gradient("+ | |
| "90deg,rgb("+a+"),rgb("+b+"))\"></div>";}).join(""); | |
| } | |
| const stat=()=>{ $("np").textContent=peers.size; $("ni").textContent=intros; | |
| $("nt").textContent=traces; | |
| const rs=new Set(); for(const p of peers.values()) for(const k of p.rooms.keys()) rs.add(k); | |
| $("nr").textContent=rs.size; }; | |
| /* ---- reduction ---- */ | |
| function reduce(d){ | |
| const s=d.sdp, g=re=>{const m=s.match(re);return m?m[1]:"";}; | |
| const c=[...s.matchAll(/a=candidate:(\S+ \d+ \S+ \d+ \S+ \d+ typ \S+)/g)] | |
| .map(m=>m[1].replace(/ generation.*$/,"")); | |
| return [d.type==="offer"?"O":"A",g(/a=ice-ufrag:(\S+)/),g(/a=ice-pwd:(\S+)/), | |
| g(/a=fingerprint:sha-256 (\S+)/).replace(/:/g,""),g(/a=setup:(\S+)/), | |
| c.join("~")].join("|"); | |
| } | |
| function expand(str){ | |
| const [t,uf,pw,fp,su,c]=str.trim().split("|"); | |
| const cands=(c||"").split("~").filter(Boolean) | |
| .map(x=>"a=candidate:"+x+" generation 0").join("\r\n"); | |
| return {type:t==="O"?"offer":"answer", sdp: | |
| "v=0\r\no=- 1 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0\r\n"+ | |
| "a=extmap-allow-mixed\r\na=msid-semantic: WMS\r\n"+ | |
| "m=application 9 UDP/DTLS/SCTP webrtc-datachannel\r\nc=IN IP4 0.0.0.0\r\n"+ | |
| (cands?cands+"\r\n":"")+"a=ice-ufrag:"+uf+"\r\na=ice-pwd:"+pw+"\r\n"+ | |
| "a=ice-options:trickle\r\na=fingerprint:sha-256 "+fp.match(/.{2}/g).join(":")+ | |
| "\r\na=setup:"+su+"\r\na=mid:0\r\na=sctp-port:5000\r\n"+ | |
| "a=max-message-size:262144\r\n"}; | |
| } | |
| const gather=pc=>new Promise(r=>{ | |
| if(pc.iceGatheringState==="complete") return r(); | |
| pc.onicecandidate=e=>{ if(!e.candidate) r(); }; | |
| setTimeout(r,3000); | |
| }); | |
| const send=(c,t)=>{ try{ if(c&&c.readyState==="open") c.send(t); }catch(e){} }; | |
| /* ---- a boat names itself; nothing is registered until it does ---- */ | |
| function greet(pc,ctl){ | |
| const hello=()=>send(ctl,"IAM|id="+MYID+"|json=0"); | |
| ctl.onopen=hello; if(ctl.readyState==="open") hello(); | |
| ctl.onmessage=e=>{ | |
| const m=e.data.match(/^IAM\|id=([0-9a-f]+)/); | |
| if(m && !peers.has(m[1])) attach(m[1],pc,ctl); | |
| }; | |
| } | |
| function attach(id,pc,ctl){ | |
| const rec={pc,ctl,rooms:new Map()}; | |
| peers.set(id,rec); | |
| absorb(id); /* the colour is kept */ | |
| dropTraces(); /* the trace is not */ | |
| ctl.onmessage=e=>onCtl(id,e.data); | |
| ctl.onclose=()=>{ peers.delete(id); stat(); | |
| log("— "+id.slice(0,8)+" unlinked (colour kept) —"); }; | |
| pc.ondatachannel=e=>{ | |
| if(e.channel.label.startsWith("room:")){ | |
| const nm=e.channel.label.slice(5); | |
| e.channel.onmessage=ev=>onRoom(id,nm,ev.data); | |
| rec.rooms.set(nm,e.channel); stat(); | |
| } | |
| }; | |
| openRoom(id,$("room").value.trim()||"light"); | |
| stat(); log("+ "+id.slice(0,8)+" — colour absorbed"); | |
| const others=[...peers.keys()].filter(x=>x!==id); | |
| if(others.length) send(ctl,"HELLO|id="+MYID+"|peers="+others.join(",")+"|json=0"); | |
| } | |
| function dropTraces(){ | |
| $("mine").value=""; $("theirs").value=""; traces=0; stat(); | |
| } | |
| function openRoom(pid,name){ | |
| const rec=peers.get(pid); if(!rec||rec.rooms.has(name)) return; | |
| const c=rec.pc.createDataChannel("room:"+name); | |
| c.onmessage=e=>onRoom(pid,name,e.data); | |
| rec.rooms.set(name,c); stat(); | |
| } | |
| /* ---- introductions travel the links that exist ---- */ | |
| async function onCtl(from,row){ | |
| const f={}; row.split("|").forEach(kv=>{const i=kv.indexOf("="); | |
| if(i>0) f[kv.slice(0,i)]=kv.slice(i+1);}); | |
| const tag=row.split("|")[0]; | |
| if(tag==="HELLO"){ | |
| for(const p of (f.peers||"").split(",").filter(Boolean)){ | |
| if(p===MYID||peers.has(p)||pending.has(p)) continue; | |
| const pc=new RTCPeerConnection(ICE); | |
| const ctl=pc.createDataChannel("ctl"); | |
| greet(pc,ctl); | |
| await pc.setLocalDescription(await pc.createOffer()); | |
| await gather(pc); | |
| pending.set(p,pc); | |
| send(peers.get(from).ctl, | |
| "INTRO|to="+p+"|from="+MYID+"|sdp="+reduce(pc.localDescription)+"|json=0"); | |
| intros++; stat(); log("→ asking for an introduction to "+p.slice(0,8)); | |
| } | |
| } | |
| else if(tag==="INTRO"){ | |
| if(f.to===MYID){ | |
| const pc=new RTCPeerConnection(ICE); | |
| pc.ondatachannel=e=>{ if(e.channel.label==="ctl") greet(pc,e.channel); }; | |
| await pc.setRemoteDescription(expand(f.sdp)); | |
| await pc.setLocalDescription(await pc.createAnswer()); | |
| await gather(pc); | |
| send(peers.get(from).ctl, | |
| "BACK|to="+f.from+"|from="+MYID+"|sdp="+reduce(pc.localDescription)+"|json=0"); | |
| intros++; stat(); log("← introduced to "+f.from.slice(0,8)); | |
| } else if(peers.has(f.to)) send(peers.get(f.to).ctl,row); | |
| } | |
| else if(tag==="BACK"){ | |
| if(f.to===MYID && pending.has(f.from)){ | |
| const pc=pending.get(f.from); | |
| await pc.setRemoteDescription(expand(f.sdp)); | |
| pending.delete(f.from); | |
| } else if(peers.has(f.to)) send(peers.get(f.to).ctl,row); | |
| } | |
| } | |
| /* ---- rooms ---- */ | |
| function onRoom(from,name,row){ | |
| const f={}; row.split("|").forEach(kv=>{const i=kv.indexOf("="); | |
| if(i>0) f[kv.slice(0,i)]=kv.slice(i+1);}); | |
| if(!f.mid || seen.has(f.mid)) return; | |
| seen.add(f.mid); absorb(f.from); | |
| if(name===($("room").value.trim()||"light")) | |
| log(f.from.slice(0,8)+" · "+name+" · "+f.body); | |
| for(const [pid,rec] of peers){ if(pid===from) continue; | |
| const c=rec.rooms.get(name); if(c) send(c,row); } | |
| pulse(from); | |
| } | |
| async function say(text){ | |
| const name=$("room").value.trim()||"light"; | |
| const mid=(await sha(MYID+name+text+Object.keys(seen).length+text.length)).slice(0,8); | |
| seen.add(mid); | |
| const row="MSG|room="+name+"|from="+MYID+"|mid="+mid+"|body="+text+"|json=0"; | |
| for(const [pid,rec] of peers){ openRoom(pid,name); send(rec.rooms.get(name),row); } | |
| log("you · "+name+" · "+text); | |
| } | |
| /* ---- drawing ---- */ | |
| const cv=$("cv"), g=cv.getContext("2d"); let W=0,H=0,t=0,pulses=[]; | |
| function fit(){ const r=cv.getBoundingClientRect(); | |
| W=cv.width=Math.max(2,r.width*2); H=cv.height=Math.max(2,r.height*2); } | |
| addEventListener("resize",fit); setTimeout(fit,80); | |
| const pulse=id=>pulses.push({id,t:0}); | |
| function frame(){ | |
| t+=0.016; | |
| g.fillStyle="#07050e"; g.fillRect(0,0,W,H); | |
| const ids=[MYID,...peers.keys()].filter(Boolean); | |
| const n=Math.max(1,ids.length), R=Math.min(W,H)*0.32, cx=W/2, cy=H/2, pos={}; | |
| ids.forEach((id,i)=>{ | |
| if(i===0){ pos[id]=[cx,cy]; return; } | |
| const a=t*0.15+(i-1)*(2*Math.PI/Math.max(1,n-1)); | |
| pos[id]=[cx+Math.cos(a)*R, cy+Math.sin(a)*R]; | |
| }); | |
| g.lineWidth=2; | |
| for(const id of ids.slice(1)){ | |
| const [,b]=stops(id); | |
| const gr=g.createLinearGradient(cx,cy,pos[id][0],pos[id][1]); | |
| gr.addColorStop(0,"rgba(255,255,255,.10)"); gr.addColorStop(1,"rgb("+b+")"); | |
| g.strokeStyle=gr; g.globalAlpha=.55; | |
| g.beginPath(); g.moveTo(cx,cy); g.lineTo(...pos[id]); g.stroke(); | |
| } | |
| g.globalAlpha=1; | |
| for(const id of ids){ | |
| const [a,b]=stops(id), p=pos[id], r=id===MYID?16:12; | |
| const gr=g.createRadialGradient(p[0],p[1],0,p[0],p[1],r*2.6); | |
| gr.addColorStop(0,"rgb("+a+")"); gr.addColorStop(1,"rgba(0,0,0,0)"); | |
| g.globalAlpha=.6; g.fillStyle=gr; | |
| g.beginPath(); g.arc(p[0],p[1],r*2.6,0,6.2832); g.fill(); | |
| g.globalAlpha=1; g.fillStyle="rgb("+b+")"; | |
| g.beginPath(); g.arc(p[0],p[1],r,0,6.2832); g.fill(); | |
| g.fillStyle="rgba(240,235,245,.8)"; g.font="18px ui-monospace,monospace"; | |
| g.textAlign="center"; g.fillText(id.slice(0,6), p[0], p[1]+r+22); | |
| } | |
| pulses=pulses.filter(p=>{ p.t+=0.03; return p.t<1; }); | |
| for(const p of pulses){ const q=pos[p.id]; if(!q) continue; | |
| g.strokeStyle="rgba(232,192,122,"+(1-p.t)+")"; g.lineWidth=2; | |
| g.beginPath(); g.arc(q[0],q[1],12+p.t*40,0,6.2832); g.stroke(); } | |
| if(ids.length<=1){ | |
| g.fillStyle="rgba(141,132,150,.8)"; g.font="20px ui-monospace,monospace"; | |
| g.textAlign="center"; g.fillText("one boat. give your trace to somebody.",cx,cy+90); } | |
| requestAnimationFrame(frame); | |
| } | |
| requestAnimationFrame(frame); | |
| /* ---- the one link made by hand ---- */ | |
| async function emit(desc){ | |
| const small=reduce(desc); | |
| $("mine").value=small; traces=1; stat(); | |
| const h=await sha(small); | |
| if(!MYID){ MYID=h.slice(0,16); $("me").textContent=MYID; } | |
| } | |
| $("open").onclick=async()=>{ | |
| isHost=true; $("open").disabled=true; $("join").disabled=true; | |
| $("stage").style.display="block"; | |
| bootPc=new RTCPeerConnection(ICE); | |
| greet(bootPc,bootPc.createDataChannel("ctl")); | |
| await bootPc.setLocalDescription(await bootPc.createOffer()); | |
| await gather(bootPc); | |
| await emit(bootPc.localDescription); | |
| log("give that trace to one person."); | |
| }; | |
| $("join").onclick=()=>{ | |
| isHost=false; $("open").disabled=true; $("join").disabled=true; | |
| $("stage").style.display="block"; | |
| $("lbl").textContent="your trace — appears after you take theirs"; | |
| log("paste their trace, then take it."); | |
| }; | |
| $("take").onclick=async()=>{ | |
| const raw=$("theirs").value.trim(); if(!raw) return; | |
| let d; try{ d=expand(raw); }catch(e){ log("not a trace."); return; } | |
| if(isHost){ await bootPc.setRemoteDescription(d); log("taken. opening…"); } | |
| else{ | |
| bootPc=new RTCPeerConnection(ICE); | |
| bootPc.ondatachannel=e=>{ if(e.channel.label==="ctl") greet(bootPc,e.channel); }; | |
| await bootPc.setRemoteDescription(d); | |
| await bootPc.setLocalDescription(await bootPc.createAnswer()); | |
| await gather(bootPc); | |
| await emit(bootPc.localDescription); | |
| log("your trace is above. send it back."); | |
| } | |
| }; | |
| $("mint").onclick=async()=>{ | |
| isHost=true; | |
| bootPc=new RTCPeerConnection(ICE); | |
| greet(bootPc,bootPc.createDataChannel("ctl")); | |
| await bootPc.setLocalDescription(await bootPc.createOffer()); | |
| await gather(bootPc); | |
| const small=reduce(bootPc.localDescription); | |
| $("mine").value=small; traces=1; stat(); | |
| $("lbl").textContent="a fresh trace — one use, for one person"; | |
| log("new trace minted. the old one is spent."); | |
| }; | |
| $("copy").onclick=()=>{ $("mine").select(); document.execCommand("copy"); }; | |
| $("msg").onkeydown=e=>{ if(e.key!=="Enter") return; | |
| const v=e.target.value.trim(); if(!v||!peers.size) return; | |
| say(v); e.target.value=""; }; | |
| setInterval(()=>{ $("msg").disabled = peers.size===0; },600); | |
| paintHeld(); stat(); | |
| </script> | |