gsap.registerPlugin(); const q=selector=>document.querySelector(selector),qa=selector=>Array.from(document.querySelectorAll(selector)); const studyAbort=new AbortController(),studyCleanup=[]; const listen=(target,type,handler)=>target.addEventListener(type,handler,{signal:studyAbort.signal}); const preference=matchMedia('(prefers-reduced-motion: reduce)'),staticControl=q('#static-mode'); staticControl.checked=preference.matches;staticControl.disabled=preference.matches; const isStatic=()=>staticControl.checked||preference.matches; let applyMode=()=>{}; listen(staticControl,'change',()=>applyMode()); listen(preference,'change',()=>{staticControl.checked=preference.matches;staticControl.disabled=preference.matches;applyMode();}); listen(window,'pagehide',()=>{studyCleanup.forEach(cleanup=>cleanup());studyAbort.abort();}); const reading=text=>q('#reading').textContent=text; const cells=qa('.spot-cell'),marks=qa('.spot-mark'),spotStage=q('#proximity-stage');let lastPointer=null; function paint(pointer){if(isStatic()){resetSpot();return;}lastPointer=pointer;const radius=Number(q('#radius').value),maximum=Number(q('#maximum').value); cells.forEach((cell,i)=>{const rect=cell.getBoundingClientRect();const distance=Math.hypot(pointer.x-(rect.left+rect.width/2),pointer.y-(rect.top+rect.height/2));const boundedDistance=Math.min(radius,Math.max(0,distance));const intensity=gsap.utils.mapRange(0,radius,1,0,boundedDistance);gsap.to(marks[i],{scale:1+intensity*(maximum-1),duration:.2,ease:'power2.out',overwrite:true});});reading('반경 '+radius+'px · 최대 '+maximum.toFixed(1)+'배');} function resetSpot(){lastPointer=null;gsap.killTweensOf(marks);gsap.set(marks,{scale:1});reading(isStatic()?'정적 보기 · 모든 도형 1배':'포인터를 움직이거나 중앙 비추기를 누르세요.');} listen(spotStage,'pointermove',e=>paint({x:e.clientX,y:e.clientY}));listen(spotStage,'pointerleave',resetSpot);listen(q('#center'),'click',()=>{const rect=spotStage.getBoundingClientRect();paint({x:rect.left+rect.width/2,y:rect.top+rect.height/2});});listen(q('#reset'),'click',resetSpot); for(const control of ['#radius','#maximum'])listen(q(control),'input',()=>{if(lastPointer)paint(lastPointer);});listen(window,'resize',()=>{if(lastPointer)paint(lastPointer);});applyMode=resetSpot;studyCleanup.push(()=>gsap.killTweensOf(marks));resetSpot();