UNPKG

luminomorphism

Version:

A UI design system built around light, blur, ambient motion and perceptual feedback.

16 lines (15 loc) 9.4 kB
var x=Object.defineProperty;var m=(u,t)=>x(u,"name",{value:t,configurable:!0});class v extends HTMLElement{static{m(this,"LLuminousField")}static get observedAttributes(){return["field-type","intensity","frequency","particle-count","color-primary","color-secondary","interaction-mode","field-decay","resonance-enabled","memory-enabled"]}constructor(){super(),this.attachShadow({mode:"open"}),this.canvas=document.createElement("canvas"),this.ctx=this.canvas.getContext("2d"),this.shadowRoot.appendChild(this.canvas),this.dpr=Math.max(1,window.devicePixelRatio||1),this.field={nodes:[],resonancePoints:[],memoryTraces:[],fieldLines:[]},this.physics={fieldType:"electromagnetic",intensity:1,frequency:.05,decay:.95,resonanceEnabled:!1,memoryEnabled:!1},this.interaction={mouse:{x:null,y:null},activeZones:new Set,fieldDisruption:0},this.animation={frameCount:0,lastTime:0,animationId:null,activeEffects:[]},this.cleanupTasks=[],this._onResize=null}connectedCallback(){this.parseAttributes(),setTimeout(()=>{this.setupCanvas(),this.initializeField(),this.attachEventListeners(),this.startAnimation()},200)}disconnectedCallback(){this.cleanup()}attributeChangedCallback(t,e,i){e!==i&&(this.parseAttributes(),this.reinitialize())}parseAttributes(){this.physics.fieldType=this.getAttribute("field-type")||"electromagnetic",this.physics.intensity=parseFloat(this.getAttribute("intensity"))||1,this.physics.frequency=parseFloat(this.getAttribute("frequency"))||.05,this.physics.decay=parseFloat(this.getAttribute("field-decay"))||.95,this.physics.resonanceEnabled=this.getAttribute("resonance-enabled")==="true",this.physics.memoryEnabled=this.getAttribute("memory-enabled")==="true",this.config={particleCount:parseInt(this.getAttribute("particle-count"))||50,colorPrimary:this.getAttribute("color-primary")||"#00ffff",colorSecondary:this.getAttribute("color-secondary")||"#ff00ff",interactionMode:this.getAttribute("interaction-mode")||"attract"}}setupCanvas(){const t=document.createElement("style");t.textContent=` :host { display: block; width: 100%; height: 100%; position: relative; overflow: hidden; } canvas { width: 100%; height: 100%; background: transparent; } `,this.shadowRoot.appendChild(t),this.handleResize(),this._onResize=()=>{const e=Math.max(1,window.devicePixelRatio||1);e!==this.dpr&&(this.dpr=e),this.handleResize()},window.addEventListener("resize",this._onResize),this.cleanupTasks.push(()=>{this._onResize&&window.removeEventListener("resize",this._onResize),this._onResize=null})}initializeField(){const{particleCount:t,colorPrimary:e,colorSecondary:i}=this.config;this.field.nodes=[];for(let s=0;s<t;s++){const a={id:s,x:Math.random()*(this.canvas.width/this.dpr),y:Math.random()*(this.canvas.height/this.dpr),vx:(Math.random()-.5)*2,vy:(Math.random()-.5)*2,charge:Math.random()>.5?1:-1,energy:Math.random(),resonancePhase:Math.random()*Math.PI*2,positionHistory:this.physics.memoryEnabled?[]:null,energyHistory:[],color:Math.random()>.5?e:i,radius:2+Math.random()*3,opacity:.7+Math.random()*.3,fieldInfluence:0,resonanceAmplitude:0};this.field.nodes.push(a)}}attachEventListeners(){const t=m(i=>{const s=this.canvas.getBoundingClientRect();this.interaction.mouse.x=i.clientX-s.left,this.interaction.mouse.y=i.clientY-s.top,this.calculateFieldDisruption()},"handlePointerMove"),e=m(()=>{this.interaction.mouse.x=null,this.interaction.mouse.y=null,this.interaction.fieldDisruption*=.9},"handlePointerLeave");this.shadowRoot.addEventListener("pointermove",t),this.shadowRoot.addEventListener("pointerleave",e),this.cleanupTasks.push(()=>{this.shadowRoot.removeEventListener("pointermove",t),this.shadowRoot.removeEventListener("pointerleave",e)})}calculateFieldDisruption(){if(this.interaction.mouse.x==null||this.interaction.mouse.y==null)return;const t=this.canvas.width/this.dpr/2,e=this.canvas.height/this.dpr/2,i=this.interaction.mouse.x-t,s=this.interaction.mouse.y-e,a=Math.sqrt(i*i+s*s),o=Math.sqrt(t*t+e*e);this.interaction.fieldDisruption=(1-a/o)*this.physics.intensity}updateField(){const{nodes:t}=this.field,{fieldType:e,intensity:i,frequency:s,decay:a}=this.physics,{mouse:o,fieldDisruption:c}=this.interaction;for(let r=0;r<t.length;r++){const n=t[r];let h=0,l=0;switch(e){case"electromagnetic":[h,l]=this.calculateElectromagneticForces(n,r);break;case"gravitational":[h,l]=this.calculateGravitationalForces(n,r);break;case"quantum":[h,l]=this.calculateQuantumForces(n,r);break}if(o.x!==null){const d=o.x-n.x,f=o.y-n.y,y=Math.sqrt(d*d+f*f);if(y<150){const p=c*(1-y/150),g=this.config.interactionMode==="attract"?1:-1;h+=d/y*p*g*.5,l+=f/y*p*g*.5}}if(this.physics.resonanceEnabled){const d=Math.sin(this.animation.frameCount*s+n.resonancePhase);n.resonanceAmplitude=d*i*.3,h+=d*.1,l+=Math.cos(this.animation.frameCount*s+n.resonancePhase)*.1}n.vx=(n.vx+h)*a,n.vy=(n.vy+l)*a,n.x+=n.vx,n.y+=n.vy,this.handleBoundaries(n),this.physics.memoryEnabled&&n.positionHistory&&(n.positionHistory.push({x:n.x,y:n.y,frame:this.animation.frameCount}),n.positionHistory.length>20&&n.positionHistory.shift()),n.energy=Math.min(1,n.energy+Math.abs(h+l)*.01),n.energyHistory.push(n.energy),n.energyHistory.length>10&&n.energyHistory.shift()}}calculateElectromagneticForces(t,e){let i=0,s=0;const{nodes:a}=this.field;for(let o=0;o<a.length;o++){if(o===e)continue;const c=a[o],r=c.x-t.x,n=c.y-t.y,h=Math.sqrt(r*r+n*n);if(h>0&&h<100){const l=t.charge*c.charge/(h*h),d=l>0?-1:1;i+=d*(r/h)*Math.abs(l)*.1,s+=d*(n/h)*Math.abs(l)*.1}}return[i,s]}calculateGravitationalForces(t,e){let i=0,s=0;const a=this.canvas.width/this.dpr/2,o=this.canvas.height/this.dpr/2,c=a-t.x,r=o-t.y,n=Math.sqrt(c*c+r*r);if(n>0){const h=this.physics.intensity/(n*.01);i=c/n*h*.001,s=r/n*h*.001}return[i,s]}calculateQuantumForces(t,e){let i=0,s=0;const a=this.physics.intensity*.1;return i=(Math.random()-.5)*a,s=(Math.random()-.5)*a,Math.random()<.001&&(t.x+=(Math.random()-.5)*50,t.y+=(Math.random()-.5)*50),[i,s]}handleBoundaries(t){const e=t.radius;t.x<e&&(t.x=e,t.vx*=-.8),t.x>this.canvas.width/this.dpr-e&&(t.x=this.canvas.width/this.dpr-e,t.vx*=-.8),t.y<e&&(t.y=e,t.vy*=-.8),t.y>this.canvas.height/this.dpr-e&&(t.y=this.canvas.height/this.dpr-e,t.vy*=-.8)}render(){const{ctx:t,canvas:e}=this;t.clearRect(0,0,e.width,e.height),this.renderFieldLines(),this.physics.memoryEnabled&&this.renderMemoryTraces(),this.renderConnections(),this.renderParticles(),this.physics.resonanceEnabled&&this.renderResonanceEffects()}renderFieldLines(){const{ctx:t}=this,{fieldType:e}=this.physics;if(e==="electromagnetic"){t.strokeStyle="rgba(0, 255, 255, 0.1)",t.lineWidth=.5;for(const i of this.field.nodes)if(i.charge>0){t.beginPath();for(let s=0;s<Math.PI*2;s+=Math.PI/8){const a=i.x+Math.cos(s)*10,o=i.y+Math.sin(s)*10,c=i.x+Math.cos(s)*30,r=i.y+Math.sin(s)*30;t.moveTo(a,o),t.lineTo(c,r)}t.stroke()}}}renderMemoryTraces(){const{ctx:t}=this;for(const e of this.field.nodes)if(e.positionHistory){t.strokeStyle=`${e.color}20`,t.lineWidth=1,t.beginPath();for(let i=1;i<e.positionHistory.length;i++){const s=e.positionHistory[i-1],a=e.positionHistory[i];i===1&&t.moveTo(s.x,s.y),t.lineTo(a.x,a.y)}t.stroke()}}renderConnections(){const{ctx:t}=this,{nodes:e}=this.field;for(let i=0;i<e.length;i++)for(let s=i+1;s<e.length;s++){const a=e[i],o=e[s],c=a.x-o.x,r=a.y-o.y,n=Math.sqrt(c*c+r*r);if(n<80){const h=(1-n/80)*.3;t.strokeStyle=`rgba(0, 255, 255, ${h})`,t.lineWidth=.5,t.beginPath(),t.moveTo(a.x,a.y),t.lineTo(o.x,o.y),t.stroke()}}}renderParticles(){const{ctx:t}=this;for(const e of this.field.nodes){const i=e.radius+e.resonanceAmplitude,s=t.createRadialGradient(e.x,e.y,0,e.x,e.y,i*3),a=e.opacity,o=Math.min(.8,a+e.energy*.3),c=Math.floor(o*255).toString(16).padStart(2,"0");s.addColorStop(0,`${e.color}${c}`),s.addColorStop(.7,`${e.color}40`),s.addColorStop(1,`${e.color}00`),t.fillStyle=s,t.beginPath(),t.arc(e.x,e.y,i,0,Math.PI*2),t.fill(),t.fillStyle=e.color,t.beginPath(),t.arc(e.x,e.y,Math.max(1,i*.3),0,Math.PI*2),t.fill()}}renderResonanceEffects(){const{ctx:t}=this;for(const e of this.field.nodes)if(Math.abs(e.resonanceAmplitude)>.1){const i=e.radius*2+Math.abs(e.resonanceAmplitude)*10,s=Math.abs(e.resonanceAmplitude)*.3,a=Math.floor(s*255).toString(16).padStart(2,"0");t.strokeStyle=`${e.color}${a}`,t.lineWidth=2,t.beginPath(),t.arc(e.x,e.y,i,0,Math.PI*2),t.stroke()}}animate(){this.animation.frameCount++,this.updateField(),this.render(),this.animation.animationId=requestAnimationFrame(()=>this.animate())}startAnimation(){this.animation.animationId&&cancelAnimationFrame(this.animation.animationId),this.animate()}handleResize(){const t=this.getBoundingClientRect(),e=Math.max(1,Math.floor(t.width)),i=Math.max(1,Math.floor(t.height));(this.canvas.width!==Math.floor(e*this.dpr)||this.canvas.height!==Math.floor(i*this.dpr))&&(this.canvas.width=Math.floor(e*this.dpr),this.canvas.height=Math.floor(i*this.dpr),this.canvas.style.width=e+"px",this.canvas.style.height=i+"px",this.ctx.setTransform(this.dpr,0,0,this.dpr,0,0))}reinitialize(){this.cleanup(),this.initializeField(),this.attachEventListeners(),this.startAnimation()}cleanup(){this.animation.animationId&&(cancelAnimationFrame(this.animation.animationId),this.animation.animationId=null),this.cleanupTasks.forEach(t=>{try{t()}catch{}}),this.cleanupTasks=[]}}customElements.define("l-luminous-field",v); //# sourceMappingURL=l-luminous-field.js.min.js.map