luminomorphism
Version:
A UI design system built around light, blur, ambient motion and perceptual feedback.
113 lines (100 loc) • 7.04 kB
JavaScript
class f extends HTMLElement{static get observedAttributes(){return["value","max","state","nodes","layers","speed","color"]}constructor(){super(),this.attachShadow({mode:"open"}),this.config={value:0,max:100,state:"loading",nodes:6,layers:3,speed:1,color:"#00ffff"},this.neural={network:[],connections:[],pulses:[]},this.animationId=null,this.frameCount=0}connectedCallback(){this.parseAttributes(),this.render(),setTimeout(()=>{this.initNetwork(),this.startAnimation()},100)}disconnectedCallback(){this.animationId&&cancelAnimationFrame(this.animationId)}attributeChangedCallback(t,s,o){s!==o&&(this.parseAttributes(),t==="value"&&this.updateProgress())}parseAttributes(){this.config.value=Math.max(0,Math.min(parseFloat(this.getAttribute("value"))||0,100)),this.config.max=parseFloat(this.getAttribute("max"))||100,this.config.state=this.getAttribute("state")||"loading",this.config.nodes=parseInt(this.getAttribute("nodes"))||6,this.config.layers=Math.max(2,parseInt(this.getAttribute("layers"))||3),this.config.speed=parseFloat(this.getAttribute("speed"))||1,this.config.color=this.getAttribute("color")||this.getStateColor()}getStateColor(){return{loading:"#00ffff",success:"#00ff80",error:"#ff4444",idle:"#666666"}[this.config.state]||"#00ffff"}render(){const t=this.config.color||this.getStateColor();this.shadowRoot.innerHTML=`
<style>
:host {
display: block;
width: 100%;
height: 120px;
position: relative;
}
.container {
width: 100%;
height: 100%;
background: linear-gradient(90deg,
rgba(255,255,255,0.02) 0%,
rgba(255,255,255,0.01) 50%,
rgba(255,255,255,0.02) 100%);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 12px;
position: relative;
overflow: hidden;
}
.info {
position: absolute;
top: 10px;
left: 15px;
font-size: 0.8rem;
color: ${t};
font-weight: 600;
z-index: 10;
}
.state {
position: absolute;
top: 10px;
right: 15px;
font-size: 0.7rem;
color: ${t};
text-transform: uppercase;
padding: 4px 8px;
background: ${t}20;
border: 1px solid ${t}40;
border-radius: 12px;
z-index: 10;
}
.network {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.node {
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
background: ${t}40;
border: 1px solid ${t}60;
transition: all 0.3s ease;
}
.node.active {
background: ${t};
border-color: ${t};
box-shadow: 0 0 15px ${t}80;
animation: pulse 2s infinite;
}
.connection {
position: absolute;
height: 1px;
background: ${t}30;
transform-origin: left center;
}
.connection.active {
background: ${t}80;
height: 2px;
box-shadow: 0 0 5px ${t}60;
}
.pulse-dot {
position: absolute;
width: 4px;
height: 4px;
background: ${t};
border-radius: 50%;
box-shadow: 0 0 8px ${t};
animation: travel linear;
}
pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.3); }
}
travel {
0% { opacity: 1; }
100% { opacity: 0; }
}
</style>
<div class="container">
<div class="info">${Math.round(this.config.value)}%</div>
<div class="state">${this.config.state}</div>
<div class="network" id="network"></div>
</div>
`}initNetwork(){const t=this.shadowRoot.getElementById("network");if(!t)return;const s=t.offsetWidth-40,o=t.offsetHeight-40;if(s<=0||o<=0){setTimeout(()=>this.initNetwork(),100);return}this.neural.network=[],t.innerHTML="";for(let e=0;e<this.config.layers;e++){const h=[],r=e===0||e===this.config.layers-1?Math.max(1,Math.floor(this.config.nodes*.7)):this.config.nodes,a=20+e*s/(this.config.layers-1);for(let i=0;i<r;i++){const l=20+o/(r+1)*(i+1),n=document.createElement("div");n.className="node",n.style.left=`${a-5}px`,n.style.top=`${l-5}px`,t.appendChild(n);const d={id:`${e}-${i}`,layer:e,index:i,x:a,y:l,element:n,active:!1};h.push(d)}this.neural.network.push(h)}this.neural.connections=[];for(let e=0;e<this.config.layers-1;e++){const h=this.neural.network[e],r=this.neural.network[e+1];h.forEach(a=>{r.forEach(i=>{if(Math.random()>.2){const l=i.x-a.x,n=i.y-a.y,d=Math.sqrt(l*l+n*n),u=Math.atan2(n,l)*(180/Math.PI),c=document.createElement("div");c.className="connection",c.style.left=`${a.x}px`,c.style.top=`${a.y}px`,c.style.width=`${d}px`,c.style.transform=`rotate(${u}deg)`,t.appendChild(c),this.neural.connections.push({from:a,to:i,element:c,active:!1})}})})}this.updateProgress()}updateProgress(){const t=this.config.value/this.config.max;this.neural.network.forEach((o,e)=>{const h=Math.max(0,t*this.neural.network.length-e);o.forEach((r,a)=>{const i=a<Math.floor(h*o.length);i!==r.active&&(r.active=i,i?r.element.classList.add("active"):r.element.classList.remove("active"))})}),this.neural.connections.forEach(o=>{const e=o.from.active&&o.to.active;e!==o.active&&(o.active=e,e?o.element.classList.add("active"):o.element.classList.remove("active"))});const s=this.shadowRoot.querySelector(".info");s&&(s.textContent=`${Math.round(this.config.value)}%`),this.dispatchEvent(new CustomEvent("progress-change",{detail:{value:this.config.value,percentage:this.config.value/this.config.max*100}}))}createPulse(){const t=this.neural.connections.filter(n=>n.active);if(t.length===0)return;const s=t[Math.floor(Math.random()*t.length)],o=this.shadowRoot.getElementById("network"),e=document.createElement("div");e.className="pulse-dot",e.style.left=`${s.from.x-2}px`,e.style.top=`${s.from.y-2}px`,e.style.animationDuration=`${1/this.config.speed}s`,o.appendChild(e);const h=s.to.x-s.from.x,r=s.to.y-s.from.y,a=Date.now(),i=1e3/this.config.speed,l=()=>{const n=Date.now()-a,d=Math.min(n/i,1),u=s.from.x+h*d-2,c=s.from.y+r*d-2;e.style.left=`${u}px`,e.style.top=`${c}px`,d<1?requestAnimationFrame(l):e.remove()};l()}startAnimation(){const t=()=>{this.frameCount++,this.frameCount%30===0&&Math.random()<.3&&this.createPulse(),this.animationId=requestAnimationFrame(t)};t()}setValue(t){this.setAttribute("value",t)}setState(t){this.setAttribute("state",t),this.render(),setTimeout(()=>this.initNetwork(),50)}pulse(){for(let t=0;t<5;t++)setTimeout(()=>this.createPulse(),t*200)}reset(){this.setValue(0)}}customElements.define("l-neural-progress",f);
//# sourceMappingURL=l-neural-progress.min.js.map