luminomorphism
Version:
A UI design system built around light, blur, ambient motion and perceptual feedback.
22 lines (21 loc) • 3.12 kB
JavaScript
(function(){class E extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this.nodes=[],this.mouse={x:0,y:0}}connectedCallback(){setTimeout(()=>this.init(),50)}reinit(){this.shadowRoot.innerHTML="",this.nodes=[],requestAnimationFrame(()=>this.init())}init(){const L=parseInt(this.getAttribute("count"))||8,M=parseFloat(this.getAttribute("size"))||50,R=parseFloat(this.getAttribute("opacity"))||.5,g=this.offsetWidth>0?this.offsetWidth:800,f=this.offsetHeight>0?this.offsetHeight:600,m=this.getAttribute("verlet")!=="false",b=this.getAttribute("boids")!=="false",P=this.getAttribute("magnetic")!=="false",T={0:"0 0 10px rgba(255,255,255,0.05)",1:"0 0 40px rgba(0,255,255,0.6)",2:"0 0 20px rgba(0,128,255,0.4)",3:"0 0 28px rgba(0,194,255,0.45)",4:"0 0 14px rgba(200,150,255,0.3)",5:"0 0 32px rgba(0,224,209,0.5)",6:"0 0 32px rgba(139,92,246,0.5)",7:"0 0 40px rgba(170,255,255,0.6)"},A=document.createElement("style");A.textContent=`
:host {
position: relative;
display: block;
width: 100%;
height: 100%;
min-width: 400px;
min-height: 300px;
overflow: hidden;
}
.node {
position: absolute;
width: ${M}px;
height: ${M}px;
border-radius: 50%;
background: rgba(0, 255, 255, ${R});
backdrop-filter: blur(4px);
pointer-events: none;
transition: box-shadow 0.3s ease, background 0.3s ease;
}
`,this.shadowRoot.appendChild(A);const h=document.createElement("div");h.style.position="absolute",h.style.width="100%",h.style.height="100%",this.shadowRoot.appendChild(h);for(let o=0;o<L;o++){const e=document.createElement("div");e.classList.add("node"),e.x=Math.random()*g,e.y=Math.random()*f,e.xPrev=e.x,e.yPrev=e.y,e.ax=0,e.ay=0,h.appendChild(e),this.nodes.push(e)}this.shadowRoot.addEventListener("pointermove",o=>{const e=this.getBoundingClientRect();this.mouse.x=o.clientX-e.left,this.mouse.y=o.clientY-e.top});const C=()=>{const o=parseFloat(this.getAttribute("speed"))||1,e=60,$=100,F=.08,k=.0015,q=2,H=.8,S=(m?1:0)<<2|(b?1:0)<<1|(P?1:0),X=T[S];for(let t=0;t<this.nodes.length;t++){const s=this.nodes[t];let i=0,d=0;if(s.style.boxShadow=X,P&&(m||b)){const n=this.mouse.x-s.x,a=this.mouse.y-s.y,r=Math.sqrt(n*n+a*a),c=r<150?-q:H;i+=c*n/(r||1)*.01*o,d+=c*a/(r||1)*.01*o}if(b){let n=0,a=0,r=0,c=0,x=0,p=0;for(let y=0;y<this.nodes.length;y++){if(t===y)continue;const u=this.nodes[y],v=s.x-u.x,w=s.y-u.y,l=Math.sqrt(v*v+w*w);l<e&&l>0&&(n+=v/l,a+=w/l,p++),l<$&&(r+=u.x,c+=u.y,x++)}p>0&&(n/=p,a/=p,i+=n*F,d+=a*F),x>0&&(r/=x,c/=x,i+=(r-s.x)*k,d+=(c-s.y)*k)}i+=(Math.random()-.5)*.1,d+=(Math.random()-.5)*.1,s.ax=i,s.ay=d}for(let t of this.nodes){if(m){const s=t.x,i=t.y;t.x+=(t.x-t.xPrev)*.95+t.ax,t.y+=(t.y-t.yPrev)*.95+t.ay,t.xPrev=s,t.yPrev=i}else t.x+=t.ax,t.y+=t.ay;t.x<0&&(t.x=0,t.xPrev=t.x+1),t.x>g&&(t.x=g,t.xPrev=t.x-1),t.y<0&&(t.y=0,t.yPrev=t.y+1),t.y>f&&(t.y=f,t.yPrev=t.y-1),t.style.transform=`translate(${t.x}px, ${t.y}px)`}requestAnimationFrame(C)};C()}}window.customElements.define("l-magnetic-cluster",E)})();