Files
bubblechambersimart/fable/Proof1/simulator.html

81 lines
4.7 KiB
HTML
Raw Normal View History

2026-07-19 13:37:10 -04:00
<!DOCTYPE html><html><head><meta charset="utf-8">
<title>Proof1 · lit chamber — backlit parallax</title>
<style>
:root{--gap:6;--bright:1;}
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:2200px;perspective-origin:50% 45%;}
.stack{position:relative;transform-style:preserve-3d;transition:transform .06s linear;
width:500.5px;height:810px;}
.panel{position:absolute;inset:-14%;transform:translateZ(-120px);
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="18" step="0.5" value="6">
<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":0.95,"white":"none","glow":1},{"id":"sea-far","file":"plane-2-sea-far.svg","z":9,"blur":0.9,"dim":1,"white":"none","glow":0.7},{"id":"sea-mid","file":"plane-3-sea-mid.svg","z":15,"blur":0.5,"dim":1,"white":"trace","glow":0.7},{"id":"sea-near","file":"plane-4-sea-near.svg","z":18,"blur":0.2,"dim":1,"white":"trace","glow":0.7},{"id":"event-halo","file":"plane-5-event-halo.svg","z":27,"blur":2.4,"dim":0.85,"white":"none","glow":0.9},{"id":"event-core","file":"plane-6-event-core.svg","z":30,"blur":0,"dim":1,"white":"under","glow":0.5},{"id":"front-glass","file":"plane-7-front-glass.svg","z":30,"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=6, bright=1, mx=0, my=0;
function layout(){
for(const p of PLANES){
const e=els[p.id];
const z=(p.z-maxZ)*gap; // front plane at 0, others recede
const blur=(p.blur||0)*(gap/6);
const off=e.dataset.off?' opacity:0;':'';
e.style.transform='translateZ('+z+'px)';
e.style.filter='blur('+blur.toFixed(2)+'px)';
e.style.opacity=e.dataset.off?0:(p.dim??1);
e.style.mixBlendMode=p.blend||'normal';
}
document.getElementById('panel').style.filter='blur(2px) brightness('+bright+')';
}
function parallax(){
stack.style.transform='rotateX('+(my*7)+'deg) rotateY('+(mx*9)+'deg)';
}
addEventListener('mousemove',e=>{
mx=(e.clientX/innerWidth-0.5)*2; my=-(e.clientY/innerHeight-0.5)*2; parallax();
});
const gapI=document.getElementById('gap'), brI=document.getElementById('bright');
gapI.oninput=()=>{gap=+gapI.value; document.getElementById('gv').textContent=' '+gap; layout();};
brI.oninput=()=>{bright=+brI.value; document.getElementById('bv').textContent=' '+bright.toFixed(2); layout();};
document.getElementById('gv').textContent=' '+gap;
document.getElementById('bv').textContent=' '+bright.toFixed(2);
layout(); parallax();
</script></body></html>