1import{r as i,j as g}from"./index-_Sg_1KT6.js";import{u as gt}from"./useCanvas-DRXw8nFR.js";import{u as vt}from"./useMouseInteraction-DqIzV1q4.js";import{E as yt}from"./ExperimentControls-DmwC3FZh.js";import{E as Mt}from"./ExperimentMetrics-D-l2z47K.js";import{E as bt}from"./ExperimentNav-CPfQyQAJ.js";const s=32,ot=3e3,H=[[2,3],[3,4],[3,5],[4,5],[5,6],[4,7],[5,8],[6,7],[7,8],[2,5],[3,7],[1,4],[5,9],[6,11],[1,2]],Pt=({category:at,experiment:L})=>{const{canvasRef:J,ctx:r,dimensions:e}=gt(),P=vt(J),[D,ct]=i.useState("excite"),[l,Q]=i.useState(3),[d,X]=i.useState(5),[it,O]=i.useState("sand finds the silence between vibrations"),[Y,rt]=i.useState(0),S=i.useRef([]),Z=i.useRef(0),A=i.useRef(null),tt=i.useRef({n:0,m:0,w:0,h:0}),T=i.useCallback(()=>{if(e.width===0)return;const n=e.width-s*2,h=e.height-s*2,m=[];for(let a=0;a<ot;a++)m.push({x:s+Math.random()*n,y:s+Math.random()*h,vx:0,vy:0,settled:0});S.current=m},[e.width,e.height]);i.useEffect(()=>{e.width>0&&S.current.length===0&&T()},[e.width,T]);const et=i.useCallback(()=>{const n=e.width,h=e.height;if(n===0)return;const m=.35,a=Math.ceil(n*m),u=Math.ceil(h*m);A.current||(A.current=document.createElement("canvas"));const v=A.current;v.width=a,v.height=u;const p=v.getContext("2d"),G=p.createImageData(a,u),y=G.data,f=s*m,o=s*m,E=Math.max(a-f*2,1),I=Math.max(u-o*2,1),t=l*Math.PI,k=d*Math.PI,C=new Float32Array(a),F=new Float32Array(a);for(let c=0;c<a;c++){const x=(c-f)/E;C[c]=Math.cos(t*x),F[c]=Math.cos(k*x)}const B=new Float32Array(u),$=new Float32Array(u);for(let c=0;c<u;c++){const x=(c-o)/I;B[c]=Math.cos(t*x),$[c]=Math.cos(k*x)}for(let c=0;c<u;c++)for(let x=0;x<a;x++){const M=(c*a+x)*4,q=(x-f)/E,_=(c-o)/I;if(q<0||q>1||_<0||_>1){y[M]=0,y[M+1]=1,y[M+2]=6,y[M+3]=255;continue}
1const b=C[x]*$[c]-F[x]*B[c],K=Math.abs(b);let z=0,N=1,j=6;if(K<.08){const w=1-K/.08,R=w*w;z+=22*R|0,N+=50*R|0,j+=35*R|0}else if(b>0){const w=b>1?1:b;z+=w*10|0,N+=w*4|0}else{const w=-b>1?1:-b;j+=w*16|0,N+=w*3|0}y[M]=z,y[M+1]=N,y[M+2]=j,y[M+3]=255}p.putImageData(G,0,0),tt.current={n:l,m:d,w:n,h}},[e.width,e.height,l,d]),st=i.useCallback(()=>{if(!r||e.width===0)return;Z.current++;const n=tt.current;(n.n!==l||n.m!==d||n.w!==e.width||n.h!==e.height)&&et(),A.current?r.drawImage(A.current,0,0,e.width,e.height):(r.fillStyle="#000106",r.fillRect(0,0,e.width,e.height));const h=e.width-s*2,m=e.height-s*2;r.strokeStyle="rgba(102, 255, 204, 0.12)",r.lineWidth=1,r.strokeRect(s,s,h,m);const a=S.current;let u=0;const v=l*Math.PI,p=d*Math.PI,G=1/h,y=1/m;if(D==="excite"&&l!==d){const f=P.positionRef.current,o=P.isInBounds,E=P.isDown;for(let I=0;I<a.length;I++){const t=a[I],k=(t.x-s)*G,C=(t.y-s)*y,F=Math.cos(v*k),B=Math.sin(v*k),$=Math.cos(p*k),c=Math.sin(p*k),x=Math.cos(v*C),M=Math.sin(v*C),q=Math.cos(p*C),_=Math.sin(p*C),b=F*q-$*x,K=-v*B*q+p*c*x,z=-p*F*_+v*$*M,N=.03;t.vx+=-b*K*N,t.vy+=-b*z*N;const j=.2*Math.max(0,1-t.settled*.01);if(t.vx+=(Math.random()-.5)*j,t.vy+=(Math.random()-.5)*j,o){const R=t.x-f.x,U=t.y-f.y,V=R*R+U*U;if(V<6400&&V>16){const W=Math.sqrt(V),nt=(E?2:.5)*(1-W/80);t.vx+=R/W*nt,t.vy+=U/W*nt,t.settled=0}}t.vx*=.92,t.vy*=.92,t.x+=t.vx,t.y+=t.vy,t.x<s+1&&(t.x=s+1,t.vx*=-.5),t.x>e.width-s-1&&(t.x=e.width-s-1,t.vx*=-.5),t.y<s+1&&(t.y=s+1,t.vy*=-.5),t.y>e.height-s-1&&(t.y=e.height-s-1,t.vy*=-.5),t.vx*t.vx+t.vy*t.vy<.01&&Math.abs(b)<.08?(t.settled=Math.min(200,t.settled+1),u++):t.settled=Math.max(0,t.settled-1)}}else if(D==="excite"&&l===d)for(let f=0;f<a.length;f++){const o=a[f];o.vx+=(Math.random()-.5)*.3,o.vy+=(Math.random()-.5)*.3,o.vx*=.95,o.vy*=.95,o.x+=o.vx,o.y+=o.vy,o.x<s+1&&(o.x=s+1),o.x>e.width-s-1&&(o.x=e.width-s-1),o.y<s+1&&(o.y=s+1),o.y>e.height-s-1&&(o.y=e.height-s-1)}else u=a.length;Z.current%20===0&&rt(a.length?Math.round(u/a.length*100):0);for(let f=0;f<a.length;f++){const o=a[f];if(o.settled>30)r.fillStyle="rgba(255, 221, 136, 0.85)";else{const E=.35+Math.min(o.settled,30)*.015;r.fillStyle=`rgba(200, 170, 100, ${E})`}r.fillRect(o.x-.5,o.y-.5,1.5,1.5)}r.font='10px "Space Mono", SFMono-Regular, Menlo, monospace',r.fillStyle="rgba(102, 255, 204, 0.25)",r.textAlign="left",r.fillText(`chladni(${l}, ${d})`,s+6,s+16)},[r,e,D,l,d,et,P.positionRef,P.isInBounds,P.isDown]);i.useEffect(()=>{if(!r||e.width===0)return;let n;const h=()=>{st(),n=requestAnimationFrame(h)};return n=requestAnimationFrame(h),()=>cancelAnimationFrame(n)},[r,e.width,st]);const lt=i.useCallback(n=>{ct(n),O(n==="excite"?"plate resonating â grains seek the silent lines":"vibration arrested â pattern frozen in time")},[]),dt=i.useCallback(()=>{const n=H.findIndex(([a,u])=>a===l&&u===d),[h,m]=H[(n+1)%H.length];Q(h),X(m),S.current.forEach(a=>{a.settled=0}),O(`mode (${h},${m}) â new resonance forming`)},[l,d]),ht=i.useCallback(()=>{Q(n=>{const h=n>=12?1:n+1;return S.current.forEach(m=>{m.settled=0}),h})},[]),mt=i.useCallback(()=>{X(n=>{const h=n>=12?1:n+1;return S.current.forEach(m=>{m.settled=0}),h})},[]),xt=i.useCallback(()=>{T(),O("grains scattered â seeking new equilibrium")},[T]),ft=[{id:"pattern",label:"next.pattern()",onClick:dt},{id:"n",label:`n++ [${l}]`,onClick:ht},{id:"m",label:`m++ [${d}]`,onClick:mt},{id:"scatter",label:"scatter()",onClick:xt,variant:"reset"}],ut=i.useMemo(()=>{const n=l===d?"degenerate":l+d>10?"intricate":l+d>6?"complex":"simple";return[{label:"grains",value:ot},{label:"settled",value:`${Y}%`},{label:"harmonics",value:`(${l},${d})`},{label:"form",value:n}]},[l,d,Y]);return g.jsxs("div",{className:"fixed inset-0 flex flex-col",children:[g.jsxs("header",{className:"relative z-50 flex items-center justify-between p-2 sm:p-4 border-b border-void-green/20 bg-void-dark/80 backdrop-blur-sm",children:[g.jsxs("div",{className:"flex items-center gap-2 sm:gap-4",children:[g.jsx(bt,{currentCategory:at.slug,currentExperiment:L.slug}),g.jsx("h1",{className:"text-xl text-glow hidden sm:block",style:{color:L.color},children:L.name})]}),g.jsx(Mt,{metrics:ut})]}),g.jsxs("div",{className:"flex items-center justify-between p-2 sm:p-4 border-b border-void-green/10 bg-void-dark/60 backdrop-blur-sm",children:[g.jsx(yt,{modes:[{id:"excite",label:"excite()"},{id:"freeze",label:"freeze()"}],currentMode:D,onModeChange:lt,controls:ft}),g.jsx("p",{className:"text-void-green/50 text-xs hidden md:block max-w-md text-right font-mono",children:it})]}),g.jsx("div",{className:"flex-1 min-h-0 relative bg-void-dark",children:g.jsx("canvas",{ref:J,className:"absolute inset-0 w-full h-full cursor-crosshair","data-testid":"chladni-plate-canvas"})})]})};export{Pt as default};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.