Files
bubblechambersimart/fable/Proof1/simulator.html
2026-07-19 21:28:14 -04:00

90 lines
5.7 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html><html><head><meta charset="utf-8">
<title>Proof1 · lit chamber — backlit parallax</title>
<style>
html,body{margin:0;height:100%;background:#0a0a0b;overflow:hidden;
font:12px/1.5 ui-monospace,monospace;color:#9aa;}
#wrap{position:fixed;inset:0;display:grid;place-items:center;}
.scene{perspective:1400px;perspective-origin:50% 45%;}
.stack{position:relative;transform-style:preserve-3d;transition:transform .06s linear;
width:500.5px;height:810px;}
.panel{position:absolute;inset:-70%;
background:radial-gradient(120% 90% at 50% 42%,#fff6ec, #ffe7c8 42%, #f0c48c 78%, #caa06a 100%);
filter:blur(2px);}
.plane{position:absolute;inset:0;width:100%;height:100%;background-size:cover;
background-repeat:no-repeat;will-change:transform,filter;}
#hud{position:fixed;left:14px;top:12px;z-index:9;background:#141416cc;padding:12px 14px;
border-left:3px solid #d9b48a;max-width:300px;}
#hud h1{font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:#d9b48a;font-weight:400;margin:0 0 8px;}
#hud label{display:block;margin:7px 0 2px;color:#aab;}
#hud input[type=range]{width:100%;}
#legend{position:fixed;right:14px;top:12px;z-index:9;background:#141416cc;padding:10px 12px;color:#889;}
#legend b{color:#d9b48a;font-weight:400;}
.pill{display:inline-block;margin:2px 4px 2px 0;padding:1px 6px;border:1px solid #333;border-radius:9px;cursor:pointer;color:#aab;}
.pill.off{opacity:.35;text-decoration:line-through;}
</style></head><body>
<div id="hud">
<h1>Proof1 · lit chamber</h1>
<div>move mouse = parallax · drag sliders</div>
<label>air gap ×<span id="gv"></span></label>
<input id="gap" type="range" min="1" max="28" step="0.5" value="12">
<label>parallax ×<span id="pv"></span></label>
<input id="par" type="range" min="0" max="220" step="5" value="110">
<label>backlight<span id="bv"></span></label>
<input id="bright" type="range" min="0.3" max="1.8" step="0.05" value="1">
<div id="pills"></div>
</div>
<div id="legend"></div>
<div id="wrap"><div class="scene"><div class="stack" id="stack">
<div class="panel" id="panel"></div>
</div></div></div>
<script>
const PLANES=[{"id":"sun","file":"plane-1-sun.svg","z":0,"blur":1.4,"dim":1,"white":"none","glow":1},{"id":"event-glow","file":"plane-2-event-glow.svg","z":8,"blur":2.6,"dim":0.85,"white":"none","glow":0.9},{"id":"sea-far","file":"plane-3-sea-far.svg","z":14,"blur":0.9,"dim":0.7,"white":"none","glow":0.8},{"id":"sea-mid","file":"plane-4-sea-mid.svg","z":18,"blur":0.5,"dim":0.7,"white":"trace","glow":0.7},{"id":"sea-near","file":"plane-5-sea-near.svg","z":22,"blur":0.2,"dim":0.7,"white":"trace","glow":0.7},{"id":"event-birth","file":"plane-6-event-birth.svg","z":28,"blur":0.7,"dim":1,"white":"none","glow":0.8},{"id":"event-life","file":"plane-7-event-life.svg","z":32,"blur":0.3,"dim":1,"white":"trace","glow":0.5},{"id":"event-death","file":"plane-8-event-death.svg","z":36,"blur":0,"dim":1,"white":"under","glow":0.3},{"id":"front-glass","file":"plane-9-front-glass.svg","z":36,"blur":0,"dim":1,"white":"none","glow":0,"blend":"multiply"}];
const stack=document.getElementById('stack');
const legend=document.getElementById('legend');
const pills=document.getElementById('pills');
const maxZ=Math.max(...PLANES.map(p=>p.z));
const els={};
legend.innerHTML='<b>planes</b> (front → back)<br>'+[...PLANES].reverse().map(p=>p.id+' · '+p.z+'mm').join('<br>');
PLANES.forEach(p=>{
const d=document.createElement('div');
d.className='plane'; d.style.backgroundImage='url('+p.file+')';
d.dataset.id=p.id; stack.appendChild(d); els[p.id]=d;
const pill=document.createElement('span'); pill.className='pill'; pill.textContent=p.id;
pill.onclick=()=>{d.dataset.off=d.dataset.off?'':'1'; pill.classList.toggle('off'); layout();};
pills.appendChild(pill);
});
let gap=12, par=110, bright=1, mx=0, my=0;
const panel=document.getElementById('panel');
// One transform per plane = translateZ (real depth) + a depth-scaled screen
// shift (EXAGGERATED parallax) + the stack's 3D tilt. Deeper planes shift more,
// so the event's birth→death timeline slides apart as you move.
function update(){
stack.style.transform='rotateX('+(my*11).toFixed(2)+'deg) rotateY('+(mx*15).toFixed(2)+'deg)';
for(const p of PLANES){
const e=els[p.id];
const z=(p.z-maxZ)*gap; // front plane at 0, others recede
const depth=(maxZ-p.z)/(maxZ||1); // 0 = front, 1 = deepest
const ox=(-mx*depth*par).toFixed(1), oy=(my*depth*par).toFixed(1);
e.style.transform='translate3d('+ox+'px,'+oy+'px,'+z.toFixed(1)+'px)';
e.style.filter='blur('+((p.blur||0)*(gap/8)).toFixed(2)+'px)';
e.style.opacity=e.dataset.off?0:(p.dim??1);
e.style.mixBlendMode=p.blend||'normal';
}
// keep the light panel ALWAYS behind the deepest plane, scaled up so it still
// fills the backdrop as it recedes (else deep planes pop behind it at high gap).
const pz=(0-maxZ)*gap-90;
panel.style.transform='translateZ('+pz.toFixed(0)+'px) scale('+(1+(-pz)/1400).toFixed(2)+')';
panel.style.filter='blur(2px) brightness('+bright+')';
}
addEventListener('mousemove',e=>{
mx=(e.clientX/innerWidth-0.5)*2; my=-(e.clientY/innerHeight-0.5)*2; update();
});
const gapI=document.getElementById('gap'), parI=document.getElementById('par'), brI=document.getElementById('bright');
gapI.oninput=()=>{gap=+gapI.value; document.getElementById('gv').textContent=' '+gap; update();};
parI.oninput=()=>{par=+parI.value; document.getElementById('pv').textContent=' '+par; update();};
brI.oninput=()=>{bright=+brI.value; document.getElementById('bv').textContent=' '+bright.toFixed(2); update();};
document.getElementById('gv').textContent=' '+gap;
document.getElementById('pv').textContent=' '+par;
document.getElementById('bv').textContent=' '+bright.toFixed(2);
update();
</script></body></html>