diff --git "a/index.html" "b/index.html"
--- "a/index.html"
+++ "b/index.html"
@@ -317,23 +317,23 @@ this machine.
float grain = 0.88 + 0.24 * hash(vec2(vT * 211.0 + vW * 97.0, floor(uTime * 24.0)));
vec3 col = mix(vCol, vec3(1.0), uHeadWhite * exp(-vT * uHeadK));
gl_FragColor = vec4(col, edge * along * rev * vA * grain * uOpacity);
- }`;function makeSheet(cols,rows,{opacity,headWhite,headK,alongBase,alongK,order=6}){const count=cols*rows;const geo=new THREE.BufferGeometry;const pos=new Float32Array(count*3);const aW=new Float32Array(count);const aT=new Float32Array(count);const aA=new Float32Array(count);const aRev=new Float32Array(count);const aCol=new Float32Array(count*3);for(let k=0;k{const i=(k*cols+c)*3;pos[i]=x;pos[i+1]=y;pos[i+2]=z};const setColumnScalar=(arr,c,v)=>{for(let k=0;ksetColumnScalar(aA,c,v),setRev:(c,v)=>setColumnScalar(aRev,c,v),commit(){posAttr.needsUpdate=aAttr.needsUpdate=revAttr.needsUpdate=true}}}function sampleSheet(sheet,c,u,out){const f=clamp012(u)*(sheet.rows-1);const k=Math.min(sheet.rows-2,Math.floor(f));const m=f-k;const i0=(k*sheet.cols+c)*3,i1=((k+1)*sheet.cols+c)*3;const p=sheet.pos;out.set(p[i0]+(p[i1]-p[i0])*m,p[i0+1]+(p[i1+1]-p[i0+1])*m,p[i0+2]+(p[i1+2]-p[i0+2])*m)}const exitSheet=makeSheet(NC,NK,{opacity:.92,headWhite:.55,headK:5.5,alongBase:.34,alongK:1.5});const innerSheet=makeSheet(NC,NKI,{opacity:.3,headWhite:.65,headK:4,alongBase:.55,alongK:.9});const TRI=[{x:0,y:0},{x:0,y:0},{x:0,y:0}];const TRI_C={x:0,y:0};function updateTri(rotZ,bob){const c=Math.cos(rotZ),s=Math.sin(rotZ);for(let i=0;i<3;i++){const v=LOCAL_V[i];TRI[i].x=v.x*c-v.y*s;TRI[i].y=v.x*s+v.y*c+bob}TRI_C.x=(TRI[0].x+TRI[1].x+TRI[2].x)/3;TRI_C.y=(TRI[0].y+TRI[1].y+TRI[2].y)/3}const cross2=(ax,ay,bx,by)=>ax*by-ay*bx;function castRay(px,py,dx,dy,skip,out){let best=Infinity,be=-1,bx=0,by=0,bnx=0,bny=0;for(let i=0;i<3;i++){if(i===skip)continue;const a=TRI[i],b=TRI[(i+1)%3];const ex=b.x-a.x,ey=b.y-a.y;const den=cross2(dx,dy,ex,ey);if(Math.abs(den)<1e-9)continue;const wx=a.x-px,wy=a.y-py;const t=cross2(wx,wy,ex,ey)/den;const s=cross2(wx,wy,dx,dy)/den;if(t>1e-4&&s>=-1e-4&&s<=1.0001&&t0){nx=-nx;ny=-ny;d=-d}const cosi=-d;const k=1-eta*eta*(1-cosi*cosi);if(k<0)return false;const f=eta*cosi-Math.sqrt(k);out.x=eta*ix+f*nx;out.y=eta*iy+f*ny;return true}function reflect2(ix,iy,nx,ny,out){const d=ix*nx+iy*ny;out.x=ix-2*d*nx;out.y=iy-2*d*ny}const MAX_TRACE_PTS=5;const makeTraceRec=()=>({pts:Array.from({length:MAX_TRACE_PTS},()=>({x:0,y:0})),count:0,ex:0,ey:0,dx:0,dy:0,len:0,valid:false});const TRACES=Array.from({length:NC},makeTraceRec);const CTRACE=makeTraceRec();const ENTRY={valid:false,x:0,y:0,nx:0,ny:0,edge:-1};const HIT_E={t:0,x:0,y:0,nx:0,ny:0,edge:-1};const HIT_I={t:0,x:0,y:0,nx:0,ny:0,edge:-1};const TDIR={x:0,y:0};function trace(n,rec){rec.count=0;rec.valid=false;if(!ENTRY.valid)return;if(!refract2(RAY.dx,RAY.dy,ENTRY.nx,ENTRY.ny,1/n,TDIR))return;rec.pts[0].x=ENTRY.x;rec.pts[0].y=ENTRY.y;rec.count=1;let cx=ENTRY.x,cy=ENTRY.y,dx=TDIR.x,dy=TDIR.y,skip=ENTRY.edge;let len=0;for(let b=0;b<3;b++){if(!castRay(cx,cy,dx,dy,skip,HIT_I))return;len+=HIT_I.t;rec.pts[rec.count].x=HIT_I.x;rec.pts[rec.count].y=HIT_I.y;rec.count++;if(refract2(dx,dy,HIT_I.nx,HIT_I.ny,n,TDIR)){rec.ex=HIT_I.x;rec.ey=HIT_I.y;rec.dx=TDIR.x;rec.dy=TDIR.y;rec.len=len;rec.valid=true;return}reflect2(dx,dy,HIT_I.nx,HIT_I.ny,TDIR);dx=TDIR.x;dy=TDIR.y;cx=HIT_I.x;cy=HIT_I.y;skip=HIT_I.edge}}const SEGL=new Float64Array(MAX_TRACE_PTS);function writeInnerColumn(rec,c,zOff){const rows=innerSheet.rows,cnt=rec.count;if(cnt<2)return;let total=0;for(let i=1;i1e-9?(target-acc)/SEGL[seg]:0;const a=rec.pts[seg-1],b=rec.pts[seg];innerSheet.setPoint(k,c,a.x+(b.x-a.x)*u,a.y+(b.y-a.y)*u,zOff)}}function writeExitColumn(c,w,ex,ey,ang0,tA,zOff){const rows=exitSheet.rows,step=EXIT_LEN/(rows-1);const angT=ang0*(1-.55*clamp012(Math.cos(ang0)));let x=ex,y=ey;for(let k=0;k0){x+=Math.cos(ang)*step;y+=Math.sin(ang)*step}const sway=Math.sin(tA*.8+w*5.4+u*2.4)*.14*u;exitSheet.setPoint(k,c,x-Math.sin(ang)*sway,y+Math.cos(ang)*sway,zOff)}}function buildIncoming(tA,hasEntry){const x0=viewX.left-.5;const y0=RAY.py+(x0-RAY.px)*SLOPE;let x1,y1;if(hasEntry){x1=ENTRY.x;y1=ENTRY.y}else{x1=viewX.right+1;y1=RAY.py+(x1-RAY.px)*SLOPE}for(let k=0;kmakeSprite(16777215,.085,0,9));const pulseHex=w=>new THREE.Color(...specColor(w)).getHex();const NP=5,T_EMIT=2.2,CYCLE=NP*T_EMIT;const whitePulses=Array.from({length:NP},()=>makeSprite(16777215,.085,0,9));const colorPulses=PULSE_W.map(w=>{const hex=pulseHex(w);return Array.from({length:NP},()=>makeSprite(hex,.075,0,9))});const PULSE_COL=PULSE_W.map(w=>Math.round(w*(NC-1)));const washes=PULSE_W.map(w=>makeSprite(pulseHex(w),5.5,0,2));const SAMP=new THREE.Vector3;function samplePts(pts,u,out){const f=clamp012(u)*(pts.length-1);const i=Math.min(pts.length-2,Math.floor(f));out.copy(pts[i]).lerp(pts[i+1],f-i)}const APEX_LOCAL=new THREE.Vector3(0,R,DEPTH/2+.02);const CORNER_LOCAL=LOCAL_V.slice(1).map(v=>new THREE.Vector3(v.x,v.y,DEPTH/2+.02));const APEX_W=new THREE.Vector3;let dragging=false,lastPX=0,lastPY=0;let userX=0,userY=0,userZ=0;let velX=0,velY=0,velZ=0;let velTrail=[];let autoAmp=1,lastInteract=-10;let mouseNX=0,mouseNY=0;let parX=0,parY=0,parTX=0,parTY=0;let tGlobal=0;canvas.addEventListener("pointerdown",e=>{dragging=true;velX=velY=velZ=0;velTrail=[{t:performance.now(),x:userX,y:userY,z:userZ}];lastPX=e.clientX;lastPY=e.clientY;lastInteract=tGlobal;document.body.classList.add("grabbing");try{canvas.setPointerCapture(e.pointerId)}catch(err){}});window.addEventListener("pointermove",e=>{if(!App.landingActive)return;mouseNX=e.clientX/window.innerWidth*2-1;mouseNY=e.clientY/window.innerHeight*2-1;if(!dragging)return;const dx=e.clientX-lastPX,dy=e.clientY-lastPY;lastPX=e.clientX;lastPY=e.clientY;userZ+=dx*.006;userY=clamp(userY+dx*.0028,-.5,.5);userX=clamp(userX+dy*.0035,-.3,.3);const nowMs=performance.now();velTrail.push({t:nowMs,x:userX,y:userY,z:userZ});while(velTrail.length>2&&nowMs-velTrail[0].t>120)velTrail.shift();lastInteract=tGlobal});function endDrag(){if(!dragging)return;dragging=false;if(velTrail.length>1){const a=velTrail[0],b=velTrail[velTrail.length-1];const wdt=Math.max((b.t-a.t)/1e3,1/240);velZ=clamp((b.z-a.z)/wdt,-6,6);velY=clamp((b.y-a.y)/wdt,-2,2);velX=clamp((b.x-a.x)/wdt,-2,2)}velTrail=[];lastInteract=tGlobal;document.body.classList.remove("grabbing")}window.addEventListener("pointerup",endDrag);window.addEventListener("pointercancel",endDrag);function onResize(){const w=window.innerWidth,h=window.innerHeight;renderer.setSize(w,h);const aspect=w/h;camera.aspect=aspect;camZ=Math.min(9.6/Math.min(1,aspect/1.15),15.5);camera.updateProjectionMatrix();const halfH=Math.tan(THREE.MathUtils.degToRad(camera.fov/2))*camZ;viewX.right=halfH*aspect+1.2;viewX.left=-viewX.right}window.addEventListener("resize",onResize);onResize();const colAlpha=new Float32Array(NC);let entryAlpha=0;let trapGlow=0;let lastAC=-.06;const T_OUT=new Float32Array(NC);function castEntry(){const sx=viewX.left-2;const sy=RAY.py+(sx-RAY.px)*SLOPE;const hit=castRay(sx,sy,RAY.dx,RAY.dy,-1,HIT_E)&&HIT_E.nx*RAY.dx+HIT_E.ny*RAY.dy<-.001;ENTRY.valid=hit;if(hit){ENTRY.x=HIT_E.x;ENTRY.y=HIT_E.y;ENTRY.nx=HIT_E.nx;ENTRY.ny=HIT_E.ny;ENTRY.edge=HIT_E.edge}return hit}function centerAngle(){let aC;if(CTRACE.valid){aC=Math.atan2(CTRACE.dy,CTRACE.dx)}else{let sum=0,cnt=0;for(const rec of TRACES){if(rec.valid){sum+=Math.atan2(rec.dy,rec.dx);cnt++}}aC=cnt>0?sum/cnt:lastAC}lastAC=aC;return aC}function updatePulses(tP,dIn,airT,lamp,hasEntry){for(let s=0;s=emit;const age=live?(tP-emit)%CYCLE:0;const dAir=age*CV;const wSpr=whitePulses[s];if(live&&dAir1e-6?clamp012(sinceEntry*(CV/N_COL[c])/rec.len):0;innerSheet.setAlpha(c,colAlpha[c]);innerSheet.setRev(c,glassRev);exitSheet.setAlpha(c,colAlpha[c]);exitSheet.setRev(c,clamp012(Math.max(0,tP-T_OUT[c])*CV/EXIT_LEN));if(rec.valid||colAlpha[c]>.05){gx+=rec.ex*colAlpha[c];gy+=rec.ey*colAlpha[c];ga+=colAlpha[c];if(rec.valid&&T_OUT[c].05)exitGlow.position.set(gx/ga,gy/ga,.05);exitGlow.scale.setScalar(.5*(1+.12*Math.sin(tA*3)));exitGlow.material.opacity=.9*clamp012(ga/(NC*.5))*exitFront;if(hasEntry)entryGlow.position.set(ENTRY.x,ENTRY.y,.05);entryGlow.material.opacity=.7*entryAlpha*clamp012(pastEntry/.7);samplePts(INC_PTS,.03,SAMP);sourceDot.position.copy(SAMP);sourceDot.scale.setScalar(.17+.02*Math.sin(tA*2.1));sourceDot.material.opacity=.95*lamp;incoming.mat.uniforms.uOpacity.value=.95*lamp*(.97+.02*Math.sin(tA*9.1)+.015*Math.sin(tA*3.7));for(const b of allBeams)b.mat.uniforms.uTime.value=tA;exitSheet.mat.uniforms.uTime.value=tA;innerSheet.mat.uniforms.uTime.value=tA;return lamp}const clock=new THREE.Clock;const THIRD=Math.PI*2/3;let ready=false;let spectrumSeen=false;function animate(){if(!App.landingActive)return;requestAnimationFrame(animate);const dt=Math.min(clock.getDelta(),.05);tGlobal+=dt;const tA=tGlobal*SPD;if(!dragging&&(velZ||velY||velX)){userZ+=velZ*dt;userY=clamp(userY+velY*dt,-.5,.5);userX=clamp(userX+velX*dt,-.3,.3);velZ*=Math.exp(-dt*1.5);velY*=Math.exp(-dt*3.5);velX*=Math.exp(-dt*3.5);if(Math.abs(velZ)>.05)lastInteract=tGlobal;else{if(Math.abs(velZ)<.03)velZ=0;if(Math.abs(velY)<.02)velY=0;if(Math.abs(velX)<.02)velX=0}}const idle=tGlobal-lastInteract;const ampTarget=dragging?0:idle>1.6?1:0;autoAmp+=(ampTarget-autoAmp)*(1-Math.exp(-dt*1.4));if(!dragging&&idle>1.6){const dec=Math.exp(-dt*.22);userX*=dec;userY*=dec;const home=Math.round(userZ/THIRD)*THIRD;userZ=home+(userZ-home)*dec}const amp=autoAmp;const rotZ=(Math.sin(tA*.31)*.15+Math.sin(tA*.127)*.06)*amp+userZ;const rotY=Math.sin(tA*.21)*.32*amp+userY;const rotX=(Math.sin(tA*.165)*.09+.02)*amp+userX;const bob=Math.sin(tA*.5)*.055*amp;prism.rotation.set(rotX,rotY,rotZ);prism.position.y=bob;prism.updateMatrixWorld();const lamp=updateOptics(tA,dt,rotZ,bob);if(!spectrumSeen&&exitGlow.material.opacity>.03){spectrumSeen=true;document.body.classList.add("spectrum")}APEX_W.copy(APEX_LOCAL).applyMatrix4(prism.matrixWorld);apexDot.position.copy(APEX_W);apexDot.material.opacity=.9*lamp;for(let i=0;i<2;i++){APEX_W.copy(CORNER_LOCAL[i]).applyMatrix4(prism.matrixWorld);cornerDots[i].position.copy(APEX_W);cornerDots[i].material.opacity=lamp*(.35+.25*Math.sin(tA*2+i*2.1))}glassMat.uniforms.uTime.value=tA;glassMat.uniforms.uCam.value.copy(camera.position);edgeMat.opacity=.45+.08*Math.sin(tA*1.3)+trapGlow*.3;if(!dragging){parTX=mouseNX*.38;parTY=-mouseNY*.22}const pe=1-Math.exp(-dt*3);parX+=(parTX-parX)*pe;parY+=(parTY-parY)*pe;camera.position.set(parX+Math.sin(tA*.13)*.15,.35+parY+Math.cos(tA*.1)*.08,camZ);camera.lookAt(0,.05,0);renderer.render(scene,camera);if(!ready){ready=true;document.body.classList.add("ready");setTimeout(()=>document.body.classList.add("spectrum"),7e3)}}animate();App._disposeLanding=function(){try{renderer.dispose()}catch(err){}}}try{boot()}catch(err){console.error(err)}})()};if(!window.__BONSAI_HOLD_LANDING)App.bootLanding()}if(window.THREE)(function(){var TAU=Math.PI*2;var UP=new THREE.Vector3(0,1,0);var MOT=REDUCED?.35:1;var clamp=THREE.MathUtils.clamp;var lerp=THREE.MathUtils.lerp;function mulberry32(a){return function(){a|=0;a=a+1831565813|0;var t=Math.imul(a^a>>>15,1|a);t=t+Math.imul(t^t>>>7,61|t)^t;return((t^t>>>14)>>>0)/4294967296}}function easeOutCubic(t){return 1-Math.pow(1-t,3)}function easeOutBack(t){var c=1.35;return 1+(c+1)*Math.pow(t-1,3)+c*Math.pow(t-1,2)}function SC(hex){return new THREE.Color(hex).convertSRGBToLinear()}function canvasTex(size,draw){var cv=document.createElement("canvas");cv.width=cv.height=size;draw(cv.getContext("2d"),size);return new THREE.CanvasTexture(cv)}var glowTex=canvasTex(256,function(g,s){var r=g.createRadialGradient(s/2,s/2,0,s/2,s/2,s/2);r.addColorStop(0,"rgba(255,255,255,1)");r.addColorStop(.25,"rgba(255,255,255,0.5)");r.addColorStop(.6,"rgba(255,255,255,0.11)");r.addColorStop(1,"rgba(255,255,255,0)");g.fillStyle=r;g.fillRect(0,0,s,s)});var petalTex=canvasTex(64,function(g,s){g.translate(s/2,s/2);g.scale(1,1.45);var r=g.createRadialGradient(0,0,1,0,0,s*.32);r.addColorStop(0,"rgba(255,240,246,1)");r.addColorStop(.55,"rgba(244,206,221,0.95)");r.addColorStop(1,"rgba(238,190,208,0)");g.fillStyle=r;g.beginPath();g.arc(0,0,s*.32,0,TAU);g.fill()});petalTex.encoding=THREE.sRGBEncoding;var barkTex=canvasTex(256,function(g,s){g.fillStyle="#463327";g.fillRect(0,0,s,s);for(var i=0;i<170;i++){var x=Math.random()*s;g.strokeStyle=Math.random()<.5?"rgba(26,17,11,"+(.1+Math.random()*.26)+")":"rgba(99,77,58,"+(.08+Math.random()*.2)+")";g.lineWidth=1+Math.random()*2.2;g.beginPath();g.moveTo(x,0);var y=0;while(y=.026)addJoint(pts[jI],jr,t0+dur*(jI/n))}for(var k=0;k.55)cands.push(T.pads[i]);if(!cands.length)return;for(var j=0;j.35?lerp(0,3.6,(bloom-.35)/.65):0;if(state.doneAt&&t-doneAtLocal<4)rate*=1.8;spawnAcc+=rate*dt*MOT;while(spawnAcc>1){spawnAcc-=1;spawnPetal()}for(var i=0;i.2)for(var i=0;i<9;i++)spawnPetal()});var gustX=0,gustZ=0,leanX=0,leanZ=0;var lastPX=null;var camAngCur=0;window.addEventListener("pointermove",function(e){if(App.stage!=="loading"){lastPX=null;return}if(lastPX!==null){var dx=clamp((e.clientX-lastPX)/window.innerWidth*3,-.35,.35);gustX+=dx*Math.cos(camAngCur);gustZ+=dx*-Math.sin(camAngCur);var m=Math.sqrt(gustX*gustX+gustZ*gustZ);if(m>1.3){gustX*=1.3/m;gustZ*=1.3/m}}lastPX=e.clientX});window.addEventListener("pointerleave",function(){lastPX=null});function onResize(){var a=window.innerWidth/window.innerHeight;camera.aspect=a;camera.fov=a<1?clamp(39/a*.92,39,60):39;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth,window.innerHeight)}window.addEventListener("resize",onResize);onResize();var clock=new THREE.Clock;var elapsed=0,wind=0,doneAtLocal=-1;function animate(){if(document.body.classList.contains("stage-chat"))return;requestAnimationFrame(animate);var dt=Math.min(clock.getDelta(),.05);elapsed+=dt;var t=elapsed;var nowS=performance.now()/1e3;wind=Math.sin(t*.31)*.5+Math.sin(t*.13+2)*.5;var gust=Math.max(0,wind);var progress=stepProgress(dt,nowS);if(state.doneAt&&doneAtLocal<0)doneAtLocal=t;updateGrowth(progress);var pulse=0;if(doneAtLocal>=0){var cp=t-doneAtLocal;if(cp<3.2)pulse=Math.sin(Math.min(cp/3.2,1)*Math.PI)*.7}canopyLight.intensity=bloom*.7+pulse*.5;for(var gi=0;gi=.999){if(!pd.done){pd.done=true;pd.popT=t}}else{pd.done=false;pd.popT=0}var pop=pd.popT?Math.exp(-(t-pd.popT)*3.5)*.32:0;pd.spr.opacity=(.17*pd.k+pop)*(1+pulse*.8);if(pd.k>0){var lag=Math.min(1,dt*(2.2+pd.phase%1.7));pd.sx+=(leanX-pd.sx)*lag;pd.sz+=(leanZ-pd.sz)*lag;var jig=Math.sin(t*13+pd.phase*3)*shakeAmp;pd.g.position.x=pd.base.x+pd.sx*.16+jig*.06;pd.g.position.z=pd.base.z+pd.sz*.16+jig*.03;pd.g.position.y=pd.base.y+Math.sin(t*.7+pd.phase)*.02*pd.k*MOT+Math.abs(jig)*.05}}var dk=Math.exp(-dt*2);gustX*=dk;gustZ*=dk;leanX+=(gustX-leanX)*Math.min(1,dt*4.5);leanZ+=(gustZ-leanZ)*Math.min(1,dt*4.5);shakeAmp*=Math.exp(-dt*2.6);var wobble=Math.sin(t*13+shakeSeed)*shakeAmp;T.group.rotation.z=Math.sin(t*.6)*.005*(.6+.4*gust)*MOT-leanX*.055+wobble*.02;T.group.rotation.x=Math.sin(t*.43+1)*.004*MOT+leanZ*.055+wobble*.012;updatePetals(dt,t);var introK=easeOutCubic(Math.min(1,elapsed/3.5));var ang,dist,camY;if(AZ_FIX!==null){ang=AZ_FIX;dist=7.9;camY=2.05}else{ang=t*(TAU/80)*MOT+Math.sin(t*.11)*.05;dist=7.9+Math.sin(t*.05+1.3)*.5*MOT+(1-introK)*1.6;camY=2.05+Math.sin(t*.07)*.28*MOT-(1-introK)*.35}camAngCur=ang;camera.position.set(Math.sin(ang)*dist,camY,Math.cos(ang)*dist);camera.lookAt(0,1.62,0);renderer.render(scene,camera);updateDom(nowS)}camera.position.set(0,1.7,9.5);camera.lookAt(0,1.62,0);renderer.render(scene,camera);App.startGarden=function(){clock.getDelta();animate()}})();if(window.THREE&&START_STAGE==="loading"){document.body.classList.remove("stage-landing");document.body.classList.add("stage-loading","ready");if(App.startGarden)App.startGarden();if(FREEZE===null&&QS.has("demo"))setTimeout(()=>{if(!state.external)simulate()},700)}