lightDOORwsMs / index.html
acer
Claude Opus 5 (1M context)
open the door - the corridor goes in
4444df5
Raw History Blame Contribute Delete
43 kB
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
<title>lightDOORs β€” a place made of light, with doors instead of windows</title>
<style>
:root{--bg:#08060f;--fg:#efeaf2;--dim:#8d8496;--line:#241e30;--gold:#e8c07a;
--ok:#7fe0a0;--no:#ff8b96;--pane:#0e0a18}
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{height:100%;margin:0;overflow:hidden}
body{background:radial-gradient(120% 90% at 50% -10%,#1a1030 0%,#08060f 60%);
color:var(--fg);font:13px/1.55 ui-monospace,"SF Mono",Menlo,Consolas,monospace}
/* the field the doors stand in */
#field{position:fixed;inset:0}
#stars{position:fixed;inset:0;pointer-events:none}
/* a pane of light */
.pane{position:absolute;min-width:230px;min-height:150px;background:rgba(14,10,24,.93);
border:1px solid var(--line);border-radius:13px;box-shadow:0 18px 60px rgba(0,0,0,.6);
display:flex;flex-direction:column;overflow:hidden;backdrop-filter:blur(9px)}
.pane.on{border-color:rgba(232,192,122,.42)}
.bar{display:flex;align-items:center;gap:8px;padding:7px 9px;cursor:grab;
background:linear-gradient(180deg,rgba(255,255,255,.05),rgba(255,255,255,0));
border-bottom:1px solid var(--line);flex:0 0 auto;touch-action:none}
.bar b{font-weight:600;font-size:12px;flex:1;white-space:nowrap;overflow:hidden;
text-overflow:ellipsis}
.dot{width:11px;height:11px;border-radius:50%;border:0;cursor:pointer;flex:0 0 auto}
.dot.x{background:#ff6b78}.dot.m{background:#e8c07a}
.body{flex:1;overflow:auto;padding:10px;font-size:12.5px}
.grip{position:absolute;right:0;bottom:0;width:18px;height:18px;cursor:nwse-resize;
background:linear-gradient(135deg,transparent 50%,rgba(255,255,255,.22) 50%);
touch-action:none}
.key{width:14px;height:14px;border-radius:4px;flex:0 0 auto;border:1px solid rgba(0,0,0,.4)}
/* the sill β€” where the doors are */
#sill{position:fixed;left:0;right:0;bottom:0;display:flex;gap:6px;padding:8px 10px;
background:rgba(8,6,15,.9);border-top:1px solid var(--line);overflow-x:auto;
align-items:center;z-index:9000;backdrop-filter:blur(12px)}
#sill button{background:transparent;color:var(--fg);border:1px solid var(--line);
border-radius:9px;padding:8px 12px;font:600 12px ui-monospace,monospace;
cursor:pointer;white-space:nowrap;flex:0 0 auto}
#sill button.lit{border-color:var(--gold);color:var(--gold)}
#sill .me{margin-left:auto;display:flex;align-items:center;gap:7px;flex:0 0 auto;
color:var(--dim);font-size:11px;padding-left:10px}
/* bits */
input,textarea,select{width:100%;background:rgba(255,255,255,.04);color:var(--fg);
border:1px solid var(--line);border-radius:9px;padding:9px;
font:12.5px ui-monospace,monospace;margin:4px 0}
textarea{min-height:62px;font-size:11px;line-height:1.45}
.btn{background:var(--gold);color:#120d18;border:0;border-radius:9px;padding:9px 15px;
font:700 12px ui-monospace,monospace;cursor:pointer;margin:3px 5px 3px 0}
.btn.g{background:transparent;color:var(--fg);border:1px solid var(--line)}
.row{display:flex;justify-content:space-between;gap:10px;padding:1px 0}
.k{color:var(--dim)}.ok{color:var(--ok)}.no{color:var(--no)}.acc{color:var(--gold)}
.stream{height:100%;display:flex;flex-direction:column}
.said{flex:1;overflow:auto;white-space:pre-wrap;word-break:break-word}
.said div{padding:2px 0}
.said i{font-style:normal;color:var(--dim)}
.chip{height:17px;width:44px;border-radius:5px;border:1px solid var(--line);
display:inline-block;vertical-align:middle;margin:2px 4px 2px 0;cursor:pointer}
h4{margin:9px 0 5px;font-size:10.5px;letter-spacing:1.4px;text-transform:uppercase;
color:var(--dim);font-weight:400;border-top:1px solid var(--line);padding-top:8px}
h4:first-child{border-top:0;padding-top:0;margin-top:0}
p{color:#c9c0d4;margin:6px 0}b{color:#fff}small{color:var(--dim)}
canvas.paint{width:100%;height:100%;display:block;background:#050309;
border-radius:8px;touch-action:none}
#toast{position:fixed;left:50%;top:14px;transform:translateX(-50%);z-index:9999;
background:rgba(20,14,34,.95);border:1px solid var(--line);border-radius:10px;
padding:9px 15px;font-size:12px;opacity:0;transition:opacity .3s;pointer-events:none}
</style>
<canvas id="stars"></canvas>
<div id="field"></div>
<div id="sill">
<button id="d-open" class="lit">β—‰ open</button>
<button data-door="doors">doors</button>
<button data-door="people">people</button>
<button data-door="say">say</button>
<button data-door="paint">paint</button>
<button data-door="carry">carry</button>
<button data-door="play">play</button>
<button data-door="rooms">rooms</button>
<button data-door="all">β—‡ all</button>
<button data-door="what">what is this</button>
<span class="me"><span class="key" id="mykey"></span><span id="myid">β€”</span>
<span id="mypeers" class="ok">0</span></span>
</div>
<div id="toast"></div>
<script>
/* =========================================================================
lightDOORs. A door is a label, not a socket β€” you can open as many as you
like and it costs nothing, because there was never a socket per door.
Nothing here is stored on anyone's machine but yours.
========================================================================= */
const ICE={iceServers:[{urls:["stun:stun.l.google.com:19302",
"stun:stun1.l.google.com:19302"]}]};
const $=s=>document.querySelector(s);
const peers=new Map(); /* colour -> {pc, ctl, doors:Map} */
const pending=new Map();
const seen=new Set();
const doors=new Map(); /* door name -> [{from,body,t}] */
const strokes=new Map(); /* door name -> [[x,y,px,py,colour]] */
const carried=[]; /* things that arrived */
let MYID=null, bootPc=null, isHost=false, z=10;
let held=new Set();
try{held=new Set(JSON.parse(localStorage.getItem("ld.colours")||"[]"))}catch(e){}
let myDoors=[];
try{myDoors=JSON.parse(localStorage.getItem("ld.doors")||'["light"]')}catch(e){myDoors=["light"]}
if(!myDoors.length) myDoors=["light"];
const saveDoors=()=>{try{localStorage.setItem("ld.doors",JSON.stringify(myDoors))}catch(e){}};
/* ---- colour is the address; the address is the content ---- */
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("");
}
async function shaBytes(u){
const d=await crypto.subtle.digest("SHA-256",u);
return [...new Uint8Array(d)].map(x=>x.toString(16).padStart(2,"0")).join("");
}
/* THE QUANT. Same law as the shipped engine, verified byte-identical on 40
cases: JL count-sketch into 1024 buckets, then the fixed 3,200-byte tail of
turbo(1024) + signs(128) + zeta(1024) + hist(1024). O(N) in, O(1) out. */
function quant(bytes){
const D=1024, pad=(8-(bytes.length%8))%8;
const b=new Uint8Array(bytes.length+pad); b.set(bytes);
const f=new Float64Array(b.buffer), proj=new Float64Array(D);
for(let i=0;i<f.length;i++){
const h=(Math.imul(i,2654435761))>>>0, v=f[i];
if(!Number.isFinite(v)) continue;
proj[h&(D-1)] += (h&0x80000000)? -v : v;
}
let max=1e-12;
for(let j=0;j<D;j++){const a=Math.abs(proj[j]); if(a>max)max=a;}
const turbo=new Int8Array(D), signs=new Uint8Array(D>>3),
zeta=new Uint8Array(D), hist=new Uint32Array(256);
for(let j=0;j<D;j++){
const v=proj[j]/max, q=Math.round(v*127), a=Math.abs(v);
turbo[j]=q;
if(v<0) signs[j>>3] |= (1<<(j&7));
zeta[j]= a<1e-9 ? 15 : Math.min(15, -Math.log2(a)|0);
hist[(q+128)&255]+=1;
}
const out=new Uint8Array(3200);
out.set(new Uint8Array(turbo.buffer),0); out.set(signs,1024);
out.set(zeta,1152); out.set(new Uint8Array(hist.buffer),2176);
return out;
}
function playIn(ch){
ch.binaryType="arraybuffer";
ch.onmessage=async e=>{
if(typeof e.data==="string") return;
const t=new Uint8Array(e.data);
if(t.length!==3200){ toast("not a tuple ("+t.length+" B)"); return; }
const h=await shaBytes(t); absorb(h.slice(0,16));
const p=pane("play","play",DOORS.play,420,380);
if(p._show){ p._show(t); p._t=t;
p.querySelector("#pin").textContent="played to you";
p.querySelector("#pout").textContent="3,200 B";
p.querySelector("#psha").textContent=h.slice(0,16); }
toast("played Β· "+h.slice(0,10));
};
}
const cl=(v,a,b)=>Math.max(a,Math.min(b,v));
function stops(h){
h=(h||"")+"000000000000";
const p=i=>cl(parseInt(h.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)])];
}
const grad=h=>{const[a,b]=stops(h);
return "linear-gradient(90deg,rgb("+a+"),rgb("+b+"))"};
const ink=h=>"rgb("+stops(h)[1]+")";
const absorb=c=>{ if(!c||c===MYID||held.has(c)) return;
held.add(c); try{localStorage.setItem("ld.colours",JSON.stringify([...held]))}catch(e){}
refresh("people"); };
const toast=t=>{const e=$("#toast");e.textContent=t;e.style.opacity=1;
clearTimeout(e._t);e._t=setTimeout(()=>e.style.opacity=0,2300)};
/* =========================== panes ====================================== */
const open=new Map();
function pane(id,title,build,w,h){
if(open.has(id)){ const p=open.get(id); p.style.zIndex=++z; lit(); return p; }
const p=document.createElement("div"); p.className="pane"; p.style.zIndex=++z;
const n=open.size, vw=innerWidth, vh=innerHeight;
w=Math.min(w||330,vw-24); h=Math.min(h||300,vh-110);
p.style.left=Math.min(vw-w-12,18+n*26)+"px";
p.style.top =Math.min(vh-h-70,18+n*24)+"px";
p.style.width=w+"px"; p.style.height=h+"px";
p.innerHTML='<div class=bar><b></b>'+
'<button class="dot m"></button><button class="dot x"></button></div>'+
'<div class=body></div><div class=grip></div>';
p.querySelector("b").textContent=title;
build(p.querySelector(".body"),p);
p.onpointerdown=()=>{p.style.zIndex=++z;lit()};
drag(p.querySelector(".bar"),p); resize(p.querySelector(".grip"),p);
p.querySelector(".dot.x").onclick=e=>{e.stopPropagation();p.remove();open.delete(id);lit()};
p.querySelector(".dot.m").onclick=e=>{e.stopPropagation();
const b=p.querySelector(".body");
const min=b.style.display!=="none";
b.style.display=min?"none":"";p.querySelector(".grip").style.display=min?"none":"";
p._h=p._h||p.style.height; p.style.height=min?"auto":p._h};
$("#field").appendChild(p); open.set(id,p); lit();
return p;
}
function lit(){
document.querySelectorAll("#sill button[data-door]").forEach(b=>
b.classList.toggle("lit",open.has(b.dataset.door)));
}
function drag(h,p){
h.onpointerdown=e=>{ if(e.target.classList.contains("dot"))return;
h.setPointerCapture(e.pointerId); p.style.zIndex=++z;
const sx=e.clientX-p.offsetLeft, sy=e.clientY-p.offsetTop;
h.onpointermove=m=>{
p.style.left=cl(m.clientX-sx,-40,innerWidth-70)+"px";
p.style.top =cl(m.clientY-sy,0,innerHeight-70)+"px"; };
h.onpointerup=()=>{h.onpointermove=null;h.onpointerup=null}; };
}
function resize(g,p){
g.onpointerdown=e=>{ e.stopPropagation(); g.setPointerCapture(e.pointerId);
const sw=p.offsetWidth-e.clientX, sh=p.offsetHeight-e.clientY;
g.onpointermove=m=>{ p.style.width=Math.max(230,sw+m.clientX)+"px";
p.style.height=Math.max(150,sh+m.clientY)+"px"; p._h=p.style.height;
if(p._fit)p._fit(); };
g.onpointerup=()=>{g.onpointermove=null;g.onpointerup=null}; };
}
const refresh=id=>{ const p=open.get(id); if(p&&p._draw) p._draw(); };
/* =========================== the doors ================================== */
function doorPane(name){
pane("door:"+name,"door Β· "+name,(b,p)=>{
b.innerHTML='<div class=stream><div class=said></div>'+
'<input placeholder="say it into this door"></div>';
b.style.padding="8px";
const said=b.querySelector(".said"), inp=b.querySelector("input");
p._draw=()=>{
said.innerHTML=(doors.get(name)||[]).slice(-300).map(m=>
'<div><span class=key style="background:'+grad(m.from)+
'"></span> <i>'+(m.from===MYID?"you":m.from.slice(0,6))+'</i> '+
esc(m.body)+'</div>').join("");
said.scrollTop=said.scrollHeight; };
inp.onkeydown=e=>{ if(e.key!=="Enter")return;
const v=inp.value.trim(); if(!v)return; inp.value="";
speak(name,v); };
p._draw();
},380,300);
}
const esc=s=>s.replace(/[&<>]/g,c=>({"&":"&amp;","<":"&lt;",">":"&gt;"}[c]));
const DOORS={
doors:(b,p)=>{
p._draw=()=>{
b.innerHTML="<h4>your doors β€” a door is a label, not a socket</h4>"+
myDoors.map(d=>'<button class="btn g" data-d="'+d+'">'+d+'</button>').join("")+
'<h4>cut a new one</h4><input id=nd placeholder="name it β€” anything">'+
'<button class="btn" id=cut>cut the door</button>'+
'<p><small>Opening one costs nothing. There was never a socket per door β€” '+
'the name <b>is</b> the route, so the count has no ceiling.</small></p>';
b.querySelectorAll("[data-d]").forEach(x=>x.onclick=()=>doorPane(x.dataset.d));
b.querySelector("#cut").onclick=()=>{
const v=b.querySelector("#nd").value.trim().toLowerCase().replace(/\s+/g,"-");
if(!v)return; if(!myDoors.includes(v)){myDoors.push(v);saveDoors()}
p._draw(); doorPane(v); openDoorEverywhere(v); };
};
p._draw();
},
people:(b,p)=>{
p._draw=()=>{
const live=[...peers.keys()];
b.innerHTML="<h4>here now</h4>"+
(live.length?live.map(c=>'<span class=chip title="'+c+'" style="background:'+
grad(c)+'"></span>').join(""):"<small>nobody yet</small>")+
"<h4>colours you carry β€” "+held.size+"</h4>"+
(held.size?[...held].map(c=>'<span class=chip title="'+c+'" style="background:'+
grad(c)+'"></span>').join(""):"<small>none yet</small>")+
"<p><small>The colour outlives the link. Nothing here is a contact list β€” "+
"there is no entry to delete, because there was never an entry.</small></p>";
};
p._draw();
},
say:(b,p)=>{ b.innerHTML="";
const sel=document.createElement("select");
const redraw=()=>{ sel.innerHTML=myDoors.map(d=>"<option>"+d+"</option>").join("") };
redraw(); b.appendChild(sel);
const s=document.createElement("div"); s.className="said";
s.style.cssText="height:calc(100% - 92px);overflow:auto"; b.appendChild(s);
const i=document.createElement("input"); i.placeholder="say it"; b.appendChild(i);
p._draw=()=>{ redraw();
s.innerHTML=(doors.get(sel.value)||[]).slice(-300).map(m=>
'<div><span class=key style="background:'+grad(m.from)+'"></span> <i>'+
(m.from===MYID?"you":m.from.slice(0,6))+'</i> '+esc(m.body)+'</div>').join("");
s.scrollTop=s.scrollHeight; };
sel.onchange=p._draw;
i.onkeydown=e=>{ if(e.key!=="Enter")return; const v=i.value.trim();
if(!v)return; i.value=""; speak(sel.value,v); };
p._draw();
},
paint:(b,p)=>{
b.style.padding="6px"; b.innerHTML='<canvas class=paint></canvas>';
const c=b.querySelector("canvas"), g=c.getContext("2d");
let last=null;
p._fit=()=>{ const r=c.getBoundingClientRect();
c.width=Math.max(2,r.width*2); c.height=Math.max(2,r.height*2); p._draw(); };
p._draw=()=>{ g.fillStyle="#050309"; g.fillRect(0,0,c.width,c.height);
g.lineCap="round";
for(const s of (strokes.get("light")||[])){
g.strokeStyle=ink(s[4]); g.lineWidth=5;
g.beginPath(); g.moveTo(s[2]*c.width,s[3]*c.height);
g.lineTo(s[0]*c.width,s[1]*c.height); g.stroke(); } };
const at=e=>{const r=c.getBoundingClientRect();
return [(e.clientX-r.left)/r.width,(e.clientY-r.top)/r.height]};
c.onpointerdown=e=>{ c.setPointerCapture(e.pointerId); last=at(e); };
c.onpointermove=e=>{ if(!last)return; const n=at(e);
addStroke("light",[n[0],n[1],last[0],last[1],MYID]);
gossip("light","STROKE|from="+MYID+"|s="+
[n[0],n[1],last[0],last[1]].map(v=>v.toFixed(4)).join(",")+"|json=0");
last=n; };
c.onpointerup=()=>last=null;
setTimeout(p._fit,60);
},
carry:(b,p)=>{
p._draw=()=>{
b.innerHTML="<h4>hand something over</h4>"+
'<input type=file id=f multiple>'+
"<h4>what arrived β€” "+carried.length+"</h4>"+
(carried.length?carried.map((c,i)=>
'<div class=row><span>'+esc(c.name)+'</span>'+
'<a class=acc download="'+esc(c.name)+'" href="'+c.url+'">keep it</a></div>'
).join(""):"<small>nothing yet</small>")+
"<p><small>It goes down the same line as the words. Nobody hosts it, "+
"nobody stores it, and it never leaves the two machines.</small></p>";
b.querySelector("#f").onchange=e=>{ for(const f of e.target.files) handOver(f); };
};
p._draw();
},
rooms:(b,p)=>{
b.style.padding="8px";
b.innerHTML="<h4>a private room only people who know the word can enter</h4>"+
'<input id=rn placeholder="a word &mdash; or a whole phrase" value="light">'+
'<canvas id=rc style="width:100%;height:120px;display:block;'+
'border:1px solid var(--line);border-radius:8px;margin:6px 0"></canvas>'+
'<div class=row><span class=k>this room&#39;s address</span><span id=ra class=acc>&mdash;</span></div>'+
'<button class="btn" id=renter>enter this room</button>'+
'<button class="btn g" id=rgo>just look at it</button>'+
"<p><small>The room&#39;s address is <b>sha256 of your word</b>. Type the same "+
"word as someone else and you land in the <b>same room</b> &mdash; talk, draw, "+
"hand files over. Type a different word and you are somewhere else entirely. "+
"<b>Nobody who doesn&#39;t know the word can find it</b>: there is no list of "+
"rooms to browse, and the address gives the word away no more than a lock gives "+
"away its key. No server, no account, no directory.</small></p>"+
"<p><small>Same law as the registration office &mdash; a room 10^30 places along "+
"is reached as fast as the first, because there is no table to walk.</small></p>";
const CUBE="b40b3e1726b0e81d";
const c=b.querySelector("#rc"), g=c.getContext("2d");
p._fit=()=>{const r=c.getBoundingClientRect();
c.width=Math.max(2,r.width*2); c.height=Math.max(2,r.height*2); if(p._t)show(p._t);};
function show(t){ p._t=t;
const W=c.width,H=c.height,N=32,cw=W/N,ch=H/N;
g.fillStyle="#050309"; g.fillRect(0,0,W,H);
for(let i=0;i<1024;i++){
const R=Math.min(255,Math.abs(t[i]<<24>>24)*2), G=(15-t[1152+i])*17, B=t[2176+i];
const neg=(t[1024+(i>>3)]>>(i&7))&1;
g.fillStyle="rgb("+(neg?B:R)+","+G+","+(neg?R:B)+")";
g.fillRect((i%N)*cw,((i/N)|0)*ch,cw+1,ch+1);
}
}
async function addrOf(word){ return await sha(CUBE+"|"+word); }
async function look(word){
const full=await addrOf(word);
b.querySelector("#ra").textContent=full.slice(0,16);
show(quant(new TextEncoder().encode(full)));
return full;
}
async function enter(word){
if(!word){ toast("type a word first"); return; }
const full=await look(word);
const room="r."+full.slice(0,12); /* the door IS the address */
if(!myDoors.includes(room)){ myDoors.push(room); saveDoors(); }
if(!doors.has(room)) doors.set(room,[]);
doorPane(room); /* live, synced via the mesh */
openDoorEverywhere(room);
absorb(full.slice(0,16));
toast(peers.size ? "entered &mdash; "+peers.size+" boat(s) can meet you here"
: "entered &mdash; give this word to who you want here");
}
b.querySelector("#renter").onclick=()=>enter((b.querySelector("#rn").value||"").trim());
b.querySelector("#rgo").onclick=()=>look((b.querySelector("#rn").value||"light").trim());
b.querySelector("#rn").onkeydown=e=>{ if(e.key==="Enter") enter((e.target.value||"").trim()); };
setTimeout(()=>{p._fit(); look("light");},60);
},
play:(b,p)=>{
b.style.padding="8px";
b.innerHTML="<h4>hand anything in β€” it leaves as 3.1 KB</h4>"+
'<input type=file id=pf><input id=pt placeholder="or type something">'+
'<canvas id=pc style="width:100%;height:150px;display:block;'+
'border:1px solid var(--line);border-radius:8px;margin:6px 0"></canvas>'+
'<div class=row><span class=k>in</span><span id=pin>β€”</span></div>'+
'<div class=row><span class=k>out</span><span class=acc id=pout>β€”</span></div>'+
'<div class=row><span class=k>colour</span><span id=psha class=acc>β€”</span></div>'+
'<button class="btn" id=psend>play it to everyone</button>'+
"<p><small>The tuple is <b>3,200 bytes whatever you hand it</b> β€” the "+
"same four bands the engine writes. What crosses is the light, "+
"<b>not the file</b>. Nothing is saved on either side.</small></p>";
const c=b.querySelector("#pc"), g=c.getContext("2d");
p._fit=()=>{const r=c.getBoundingClientRect();
c.width=Math.max(2,r.width*2); c.height=Math.max(2,r.height*2); if(p._last)show(p._last)};
function show(t){
p._last=t;
const W=c.width,H=c.height,N=32,cw=W/N,ch=H/N;
g.fillStyle="#050309"; g.fillRect(0,0,W,H);
for(let i=0;i<1024;i++){
const R=Math.min(255,Math.abs(t[i]<<24>>24)*2); /* turbo, signed */
const G=(15-t[1152+i])*17; /* zeta */
const B=t[2176+i]; /* hist bytes */
const neg=(t[1024+(i>>3)]>>(i&7))&1; /* signs = the anti */
g.fillStyle="rgb("+(neg?B:R)+","+G+","+(neg?R:B)+")";
g.fillRect((i%N)*cw,((i/N)|0)*ch,cw+1,ch+1);
}
}
p._show=show;
async function take(bytes,label){
const t=quant(bytes);
const h=await shaBytes(t);
b.querySelector("#pin").textContent=label+" Β· "+bytes.length.toLocaleString()+" B";
b.querySelector("#pout").textContent=t.length.toLocaleString()+" B";
b.querySelector("#psha").textContent=h.slice(0,16);
show(t); p._t=t; absorb(h.slice(0,16));
}
b.querySelector("#pf").onchange=async e=>{ const f=e.target.files[0]; if(!f)return;
take(new Uint8Array(await f.arrayBuffer()), f.name); };
b.querySelector("#pt").onkeydown=e=>{ if(e.key!=="Enter")return;
take(new TextEncoder().encode(e.target.value), "typed"); };
b.querySelector("#psend").onclick=()=>{
if(!p._t){ toast("hand something in first"); return; }
if(!peers.size){ toast("nobody to play it to yet"); return; }
for(const [,rec] of peers){
const ch=rec.pc.createDataChannel("play:"+Date.now());
ch.binaryType="arraybuffer";
ch.onopen=()=>{ ch.send(p._t.buffer.slice(0)); setTimeout(()=>ch.close(),300); };
}
toast("played Β· 3,200 bytes to "+peers.size);
};
setTimeout(p._fit,60);
},
all:(b,p)=>{
const go=u=>"onclick=\"window.open('"+u+"','_blank','noopener')\"";
b.innerHTML=
"<h4>every light, one door</h4>"+
"<p>This hub is one room. <b>The bridge is the door to all of them</b> β€” "+
"every public light on the account in one place, so none of it gets lost "+
"like Tesla.</p>"+
'<button class="btn" '+go("https://jessebrown1980.github.io/the-bridge-to-all-of-it/")+
'>open the bridge &rarr; all 182</button>'+
"<h4>the nearest few</h4>"+
'<button class="btn g" '+go("https://jessebrown1980.github.io/lightDOORwsMs/")+'>this hub</button>'+
'<button class="btn g" '+go("https://jessebrown1980.github.io/the-light-boat/")+'>the light boat</button>'+
'<button class="btn g" '+go("https://jessebrown1980.github.io/Be-infinitly-express-spherically-free/")+'>the sphere</button>'+
'<button class="btn g" '+go("https://jessebrown1980.github.io/the-null-space-or-NO-jo0O/")+'>the null space</button>'+
'<button class="btn g" '+go("https://jessebrown1980.github.io/the-zebras-z-is-the-squared-wave/")+'>the zebra</button>'+
"<p><small>Photons are the same photon with different colours and oils; "+
"these are the same light with different doors. The bridge holds them all, "+
"and the bridge points back here &mdash; so from any one you can reach every "+
"other.</small></p>";
},
what:(b)=>{ b.innerHTML=
"<h4>a door is a label, not a socket</h4>"+
"<p>Cut as many as you like. The name <b>is</b> the route, so there is no "+
"count to run out of β€” that ceiling only exists where somebody hands out "+
"numbered slots.</p>"+
"<h4>the colour is the address</h4>"+
"<p>Your colour is the sha-256 of a secret this browser made for itself the "+
"first time you opened the page β€” two brown stops, banded 16…240, "+
"luminance-clamped 32…224. <b>The secret never leaves; only the colour does.</b> "+
"Nobody handed you the name, so nobody can take it away, and it does not "+
"change when a link does.</p>"+
"<h4>the trace is spent, the colour is kept</h4>"+
"<p>The block you pass at the first meeting is a <b>one-time introduction</b> "+
"and this page throws it away on contact. What you keep is the colour. Break "+
"every link at once and it reassembles, because what survived was never the "+
"wiring.</p>"+
"<h4>what is free, and the one thing that isn't</h4>"+
"<p>No server of ours touches any of it β€” words, drawings, files, all of it "+
"goes browser to browser with nothing in the middle to store or relay. The one "+
"thing that isn't free, stated rather than hidden: to find a route across the "+
"internet, browsers ask a public <b>STUN</b> server for their own outside "+
"address. Free ones. <b>They see that two addresses asked; they never see a "+
"word of what you say.</b> On the same wifi β€” or down a USB cable β€” it doesn't "+
"need them at all.</p>"+
"<h4>the first meeting is still by hand</h4>"+
"<p>Everything after it travels the links that already exist: you are "+
"introduced by somebody who already knows you both. That is the only "+
"directory there is, and it is not a list anyone can seize.</p>" }
};
/* =========================== traffic ==================================== */
function addStroke(door,s){
if(!strokes.has(door)) strokes.set(door,[]);
const a=strokes.get(door); a.push(s); if(a.length>4000)a.shift();
refresh("paint");
}
function record(door,from,body){
if(!doors.has(door)) doors.set(door,[]);
doors.get(door).push({from,body});
refresh("door:"+door); refresh("say");
}
async function mid(){ return (await sha(MYID+Math.random()+performance.now())).slice(0,10) }
async function speak(door,text){
const id=await mid(); seen.add(id);
record(door,MYID,text);
fan(door,"SAY|from="+MYID+"|mid="+id+"|body="+text+"|json=0");
}
async function gossip(door,row){
const id=await mid(); seen.add(id);
fan(door,row.replace("|json=0","|mid="+id+"|json=0"));
}
function fan(door,row,except){
for(const [pid,rec] of peers){ if(pid===except) continue;
let ch=rec.doors.get(door);
if(!ch){ ch=rec.pc.createDataChannel("door:"+door);
ch.onmessage=e=>onDoor(pid,door,e.data); rec.doors.set(door,ch); }
try{ if(ch.readyState==="open") ch.send(row) }catch(e){}
}
}
function openDoorEverywhere(door){ fan(door,"OPEN|from="+MYID+"|json=0") }
function onDoor(from,door,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){ if(seen.has(f.mid)) return; seen.add(f.mid); }
absorb(f.from);
const tag=row.split("|")[0];
if(tag==="SAY"){ record(door,f.from,f.body);
if(!open.has("door:"+door)&&!open.has("say")) toast(door+" Β· "+f.body.slice(0,44)); }
else if(tag==="STROKE"){ const n=f.s.split(",").map(Number);
addStroke(door,[n[0],n[1],n[2],n[3],f.from]); }
if(!myDoors.includes(door)){ myDoors.push(door); saveDoors(); refresh("doors"); }
fan(door,row,from);
}
/* ---- handing a thing over: its own channel, closed when done ---- */
async function handOver(file){
if(!peers.size){ toast("nobody to hand it to yet"); return }
const fid=(await sha(file.name+file.size+Math.random())).slice(0,10);
for(const [pid,rec] of peers){
const ch=rec.pc.createDataChannel("give:"+fid);
ch.binaryType="arraybuffer";
ch.onopen=async()=>{
ch.send("GIVE|name="+file.name+"|size="+file.size+"|json=0");
const buf=await file.arrayBuffer(); const CH=16384;
for(let o=0;o<buf.byteLength;o+=CH){
while(ch.bufferedAmount>1<<20) await new Promise(r=>setTimeout(r,25));
ch.send(buf.slice(o,o+CH));
}
ch.send("DONE"); setTimeout(()=>ch.close(),400);
};
}
toast("handed over Β· "+file.name);
}
function takeGift(ch){
ch.binaryType="arraybuffer";
let name="a thing", parts=[];
ch.onmessage=e=>{
if(typeof e.data==="string"){
if(e.data.startsWith("GIVE|")){
name=(e.data.match(/name=([^|]*)/)||[])[1]||name; return; }
if(e.data==="DONE"){
const url=URL.createObjectURL(new Blob(parts));
carried.push({name,url}); refresh("carry");
toast("arrived Β· "+name); parts=[]; }
return;
}
parts.push(e.data);
};
}
/* =========================== the mesh =================================== */
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){}};
function greet(pc,ctl){
const hi=()=>send(ctl,"IAM|id="+MYID+"|json=0");
ctl.onopen=hi; if(ctl.readyState==="open") hi();
ctl.onmessage=e=>{ const m=e.data.match(/^IAM\|id=([0-9a-f]+)/);
if(!m) return;
/* the same colour on both ends is not two boats β€” it is one machine
talking to itself (a second tab of the same browser). Say so. */
if(m[1]===MYID){ toast("that is this same browser β€” same colour, same boat");
try{pc.close()}catch(e){} return; }
if(!peers.has(m[1])) attach(m[1],pc,ctl); };
pc.ondatachannel=e=>{ const L=e.channel.label;
if(L.startsWith("give:")) takeGift(e.channel);
else if(L.startsWith("play:")) playIn(e.channel); };
}
function attach(id,pc,ctl){
const rec={pc,ctl,doors:new Map()}; peers.set(id,rec);
absorb(id); spend();
ctl.onmessage=e=>onCtl(id,e.data);
ctl.onclose=()=>{peers.delete(id);count();refresh("people");
toast(id.slice(0,6)+" unlinked Β· colour kept")};
pc.ondatachannel=e=>{
const L=e.channel.label;
if(L.startsWith("door:")){ const d=L.slice(5);
e.channel.onmessage=ev=>onDoor(id,d,ev.data); rec.doors.set(d,e.channel);
if(!myDoors.includes(d)){myDoors.push(d);saveDoors();refresh("doors")} }
else if(L.startsWith("give:")) takeGift(e.channel);
else if(L.startsWith("play:")) playIn(e.channel);
};
for(const d of myDoors) fan(d,"OPEN|from="+MYID+"|json=0");
count(); refresh("people"); toast(id.slice(0,6)+" Β· colour absorbed");
const others=[...peers.keys()].filter(x=>x!==id);
if(others.length) send(ctl,"HELLO|id="+MYID+"|peers="+others.join(",")+"|json=0");
}
function spend(){ const p=open.get("open");
if(p){ const a=p.querySelector("#mine"),b=p.querySelector("#theirs");
if(a)a.value=""; if(b)b.value=""; if(p._spent)p._spent(); } }
const count=()=>{ $("#mypeers").textContent=peers.size };
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);
greet(pc,pc.createDataChannel("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");
}
} 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");
} 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)){
await pending.get(f.from).setRemoteDescription(expand(f.sdp));
pending.delete(f.from);
} else if(peers.has(f.to)) send(peers.get(f.to).ctl,row);
}
}
/* =========================== the opening ================================ */
function openPane(){
pane("open","the first meeting β€” by hand, once",(b,p)=>{
b.innerHTML=
"<p>Give <b>one</b> person your block. After that, everyone else arrives "+
"through the links that already exist.</p>"+
'<h4 id=lbl>yours β€” hand it over</h4><textarea id=mine readonly></textarea>'+
'<button class="btn g" id=copy>copy</button>'+
'<button class="btn g" id=mint>mint a fresh one</button>'+
'<h4>theirs β€” paste it</h4><textarea id=theirs></textarea>'+
'<button class="btn" id=take>take it</button>'+
'<div class=row style="margin-top:8px"><span class=k>traces kept</span>'+
'<span class=ok id=nt>0</span></div>';
p._spent=()=>{ b.querySelector("#nt").textContent="0";
b.querySelector("#lbl").textContent="spent β€” mint a fresh one for the next person"; };
b.querySelector("#copy").onclick=()=>{
b.querySelector("#mine").select(); document.execCommand("copy"); toast("copied"); };
b.querySelector("#mint").onclick=()=>mint(b);
b.querySelector("#take").onclick=()=>take(b);
mint(b);
},370,430);
}
async function mint(b){
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);
b.querySelector("#mine").value=small;
b.querySelector("#nt").textContent="1";
b.querySelector("#lbl").textContent="yours β€” one use, for one person";
if(!MYID) await name(small);
}
async function take(b){
const raw=b.querySelector("#theirs").value.trim(); if(!raw) return;
let d; try{ d=expand(raw) }catch(e){ toast("that isn't a block"); return }
if(d.type==="answer"&&isHost&&bootPc){
try{ await bootPc.setRemoteDescription(d); toast("taken Β· opening") }
catch(e){ toast("that block was for a different opening") }
return;
}
const pc=new RTCPeerConnection(ICE);
pc.ondatachannel=e=>{ if(e.channel.label==="ctl") greet(pc,e.channel) };
await pc.setRemoteDescription(d);
await pc.setLocalDescription(await pc.createAnswer()); await gather(pc);
const small=reduce(pc.localDescription);
bootPc=pc; isHost=false;
if(!MYID) await name(small);
b.querySelector("#mine").value=small;
b.querySelector("#nt").textContent="1";
b.querySelector("#lbl").textContent="your answer β€” send it back to them";
toast("answer ready β€” send it back");
}
async function name(seed){
MYID=(await sha(seed)).slice(0,16);
$("#myid").textContent=MYID.slice(0,10);
$("#mykey").style.background=grad(MYID);
}
/* =========================== the field ================================== */
const sc=$("#stars"), sg=sc.getContext("2d");
let SW=0,SH=0,T=0;
function fitStars(){ SW=sc.width=innerWidth*2; SH=sc.height=innerHeight*2 }
addEventListener("resize",()=>{fitStars();for(const p of open.values())p._fit&&p._fit()});
fitStars();
function field(){
T+=0.006;
sg.clearRect(0,0,SW,SH);
const ids=[MYID,...peers.keys(),...held].filter(Boolean);
const uniq=[...new Set(ids)];
const cx=SW/2, cy=SH*0.42, R=Math.min(SW,SH)*0.34;
uniq.forEach((id,i)=>{
const a=T+i*(2*Math.PI/Math.max(1,uniq.length));
const live=id===MYID||peers.has(id);
const x=cx+Math.cos(a)*R*(live?1:1.35);
const y=cy+Math.sin(a)*R*0.5*(live?1:1.35);
const [ca,cb]=stops(id), r=id===MYID?26:live?18:11;
const g=sg.createRadialGradient(x,y,0,x,y,r*3);
g.addColorStop(0,"rgba("+ca+","+(live?.5:.2)+")");
g.addColorStop(1,"rgba(0,0,0,0)");
sg.fillStyle=g; sg.beginPath(); sg.arc(x,y,r*3,0,6.2832); sg.fill();
sg.globalAlpha=live?.85:.34; sg.fillStyle="rgb("+cb+")";
sg.beginPath(); sg.arc(x,y,r,0,6.2832); sg.fill(); sg.globalAlpha=1;
if(live&&id!==MYID){ sg.strokeStyle="rgba("+cb+",.22)"; sg.lineWidth=2;
sg.beginPath(); sg.moveTo(cx,cy); sg.lineTo(x,y); sg.stroke(); }
});
requestAnimationFrame(field);
}
requestAnimationFrame(field);
/* =========================== wiring ===================================== */
$("#d-open").onclick=openPane;
document.querySelectorAll("#sill button[data-door]").forEach(btn=>{
btn.onclick=()=>{ const d=btn.dataset.door;
if(open.has(d)){ open.get(d).remove(); open.delete(d); lit(); return; }
pane(d,d==="what"?"what is this":d,DOORS[d],
d==="paint"||d==="play"||d==="rooms"?420:340,
d==="what"?420:d==="play"||d==="rooms"?400:320); };
});
(async()=>{ /* a colour before anyone arrives, from this browser alone */
let s=null; try{s=localStorage.getItem("ld.seed")}catch(e){}
if(!s){ s=[...crypto.getRandomValues(new Uint8Array(24))]
.map(x=>x.toString(16).padStart(2,"0")).join("");
try{localStorage.setItem("ld.seed",s)}catch(e){} }
await name(s);
for(const d of myDoors) if(!doors.has(d)) doors.set(d,[]);
pane("what","what is this",DOORS.what,360,420);
toast("your colour is yours β€” nobody handed it to you");
})();
</script>
<!-- ─────────────────────────────────────────────────────────────────────
THE CORRIDOR β€” every door reaches every other door.
Generated 2026-08-07 from a live measurement of all 27 doors.
Pure anchors. No src=, no <link href>, no script, no network at runtime:
this block renders with the network unplugged, and every line in it is
a way out. Paste it directly before </body>.
───────────────────────────────────────────────────────────────────── -->
<nav id="corridor" style="margin:3rem auto 2rem;max-width:60rem;padding:1.25rem 1.5rem;border-top:1px solid currentColor;font:14px/1.9 ui-monospace,SFMono-Regular,Menlo,monospace;opacity:.85">
<div style="letter-spacing:.14em;text-transform:uppercase;opacity:.6;margin-bottom:.75rem">the corridor &mdash; 27 doors</div>
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(17rem,1fr));gap:.15rem .75rem">
<a href="https://jessebrown1980.github.io/all-the-doors/">all the doors</a>
<a href="https://jessebrown1980.github.io/asolaria-multiverse/">asolaria multiverse</a>
<a href="https://jessebrown1980.github.io/Be-infinitly-express-spherically-free/">Be infinitly express spherically free</a>
<a href="https://jessebrown1980.github.io/Browns-infinite-play-and-zoom/">Browns infinite play and zoom</a>
<a href="https://jessebrown1980.github.io/does-2-to-the-n-always-contain-a-digit-2-in-base-3/">does 2 to the n always contain a digit 2 in base 3</a>
<a href="https://jessebrown1980.github.io/does-the-closure-survive-81-levels/">does the closure survive 81 levels</a>
<a href="https://jessebrown1980.github.io/free-at-all-levels/">free at all levels</a>
<a href="https://jessebrown1980.github.io/Higgs-Bell-Hilbert-and-Brown-at-the-zero/">Higgs Bell Hilbert and Brown at the zero</a>
<a href="https://jessebrown1980.github.io/identity-kernel-registry/">identity kernel registry</a>
<a href="https://jessebrown1980.github.io/light-boat-engine-ships-with-oils/">light boat engine ships with oils</a>
<a href="https://jessebrown1980.github.io/lightDOORwsMs/">lightDOORwsMs</a>
<a href="https://jessebrown1980.github.io/one-click/">one click</a>
<a href="https://jessebrown1980.github.io/see-her-anywhere-so-not-lost-to-time/">see her anywhere so not lost to time</a>
<a href="https://jessebrown1980.github.io/the-bridge-you-need-three-languages/">the bridge you need three languages</a>
<a href="https://jessebrown1980.github.io/The-Brown-Light-erdos-Engine-block-powered-by-light/">The Brown Light erdos Engine block powered by light</a>
<a href="https://jessebrown1980.github.io/the-browns-solution-to-erods-o0O-nx3-6-for-1-with--1-3/">the browns solution to erods o0O nx3 6 for 1 with 1 3</a>
<a href="https://jessebrown1980.github.io/the-colour-qr/">the colour qr</a>
<a href="https://jessebrown1980.github.io/the-family-of-verses/">the family of verses</a>
<a href="https://jessebrown1980.github.io/the-human-based-understanding/">the human based understanding</a>
<a href="https://jessebrown1980.github.io/the-law-of-space-and-light-and-color-and-travel-at-the-speed-of-one/">the law of space and light and color and travel at the speed of one</a>
<a href="https://jessebrown1980.github.io/the-light-boat/">the light boat</a>
<a href="https://jessebrown1980.github.io/the-light-hub-http-0/">the light hub http 0</a>
<a href="https://jessebrown1980.github.io/the-null-space-or-NO-jo0O/">the null space or NO jo0O</a>
<a href="https://jessebrown1980.github.io/the-zebras-z-is-the-squared-wave/">the zebras z is the squared wave</a>
<a href="https://jessebrown1980.github.io/tribute-three-around-a-free-centre/">tribute three around a free centre</a>
<a href="https://jessebrown1980.github.io/two-fixed-points-have-an-interval/">two fixed points have an interval</a>
<a href="https://jessebrown1980.github.io/why-mycelium-grounded-and-the-ases/">why mycelium grounded and the ases</a>
</div>
<div style="margin-top:1rem;opacity:.5;font-size:12px">the light is the code. every door open, every door reachable.</div>
</nav>