UNPKG

luminomorphism

Version:

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

274 lines (245 loc) 14.6 kB
class LQuantumToggle extends HTMLElement{static get observedAttributes(){return["checked","disabled","size","color-on","color-off","quantum-mode","transition-speed","particle-count","superposition-enabled","label","glow-intensity"]}constructor(){super(),this.attachShadow({mode:"open"}),this.config={checked:!1,disabled:!1,size:"medium",colorOn:"#00ff80",colorOff:"#ff4444",quantumMode:!0,transitionSpeed:1,particleCount:8,superpositionEnabled:!0,label:"",glowIntensity:1},this.state={isTransitioning:!1,superpositionPhase:0,particles:[],quantumField:0,waveFunctions:[]},this.elements={},this.animationId=null,this.transitionTimeout=null,this.cleanup=[]}connectedCallback(){this.parseAttributes(),this.render(),this.initializeElements(),this.attachEventListeners(),this.initializeQuantumField(),this.startAnimation()}disconnectedCallback(){this.stopAnimation(),this.cleanup.forEach(t=>t())}attributeChangedCallback(t,e,s){e!==s&&(this.parseAttributes(),t==="checked"?this.updateToggleState():this.updateStyling())}parseAttributes(){this.config.checked=this.hasAttribute("checked"),this.config.disabled=this.hasAttribute("disabled"),this.config.size=this.getAttribute("size")||"medium",this.config.colorOn=this.getAttribute("color-on")||"#00ff80",this.config.colorOff=this.getAttribute("color-off")||"#ff4444",this.config.quantumMode=this.getAttribute("quantum-mode")!=="false",this.config.transitionSpeed=parseFloat(this.getAttribute("transition-speed"))||1,this.config.particleCount=parseInt(this.getAttribute("particle-count"))||8,this.config.superpositionEnabled=this.getAttribute("superposition-enabled")!=="false",this.config.label=this.getAttribute("label")||"",this.config.glowIntensity=parseFloat(this.getAttribute("glow-intensity"))||1}render(){const{size:t,colorOn:e,colorOff:s,label:i,disabled:n}=this.config,o={small:{width:"40px",height:"20px",thumb:"16px"},medium:{width:"60px",height:"30px",thumb:"24px"},large:{width:"80px",height:"40px",thumb:"32px"}},a=o[t]||o.medium;this.shadowRoot.innerHTML=` <style> :host { display: inline-flex; align-items: center; gap: 12px; cursor: ${n?"not-allowed":"pointer"}; opacity: ${n?"0.6":"1"}; user-select: none; } .toggle-label { font-size: 0.9rem; color: #ffffff; opacity: 0.9; font-weight: 500; } .toggle-container { position: relative; width: ${a.width}; height: ${a.height}; cursor: ${n?"not-allowed":"pointer"}; } .toggle-track { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(90deg, rgba(255,68,68,0.3) 0%, rgba(0,255,128,0.3) 100%); border: 2px solid rgba(255,255,255,0.2); border-radius: ${parseInt(a.height)/2}px; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); overflow: hidden; } .toggle-track.on { background: linear-gradient(90deg, ${e}40 0%, ${e}20 100%); border-color: ${e}60; box-shadow: 0 0 20px ${e}30, inset 0 2px 4px rgba(0,0,0,0.2); } .toggle-track.off { background: linear-gradient(90deg, ${s}40 0%, ${s}20 100%); border-color: ${s}60; box-shadow: 0 0 20px ${s}30, inset 0 2px 4px rgba(0,0,0,0.2); } .quantum-field { position: absolute; top: -10px; left: -10px; right: -10px; bottom: -10px; background: radial-gradient(circle, transparent 40%, rgba(255,255,255,0.05) 100%); border-radius: ${parseInt(a.height)/2+10}px; opacity: 0; transition: opacity 0.3s ease; pointer-events: none; } .quantum-field.active { opacity: 1; animation: quantumPulse 2s ease-in-out infinite; } .toggle-thumb { position: absolute; top: 50%; left: 3px; width: ${a.thumb}; height: ${a.thumb}; background: linear-gradient(135deg, #ffffff, #f0f0f0); border: 2px solid rgba(0,0,0,0.1); border-radius: 50%; transform: translateY(-50%); transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); box-shadow: 0 4px 12px rgba(0,0,0,0.15), 0 2px 4px rgba(0,0,0,0.1); z-index: 10; } .toggle-thumb.on { left: calc(100% - ${a.thumb} - 3px); background: linear-gradient(135deg, ${e}, ${e}cc); border-color: ${e}; box-shadow: 0 4px 16px rgba(0,0,0,0.2), 0 0 24px ${e}60, inset 0 1px 2px rgba(255,255,255,0.3); } .toggle-thumb.off { left: 3px; background: linear-gradient(135deg, ${s}, ${s}cc); border-color: ${s}; box-shadow: 0 4px 16px rgba(0,0,0,0.2), 0 0 24px ${s}60, inset 0 1px 2px rgba(255,255,255,0.3); } .toggle-thumb.superposition { animation: superpositionFlicker 0.8s ease-in-out; background: linear-gradient(135deg, ${s} 0%, #ffffff 30%, ${e} 60%, #ffffff 100%); box-shadow: 0 4px 20px rgba(0,0,0,0.25), 0 0 30px rgba(255,255,255,0.6), 0 0 40px ${e}40, 0 0 40px ${s}40; } .quantum-particle { position: absolute; width: 3px; height: 3px; background: #ffffff; border-radius: 50%; pointer-events: none; opacity: 0; z-index: 5; } .wave-function { position: absolute; top: 50%; left: 0; width: 100%; height: 2px; background: linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.6) 50%, transparent 100%); transform: translateY(-50%); opacity: 0; pointer-events: none; z-index: 3; } .wave-function.active { opacity: 1; animation: waveMotion 1s ease-in-out; } .transition-effect { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle, rgba(255,255,255,0.8) 0%, transparent 70%); border-radius: ${parseInt(a.height)/2}px; opacity: 0; pointer-events: none; z-index: 8; } .transition-effect.active { opacity: 1; animation: transitionFlash 0.6s ease-out; } @keyframes quantumPulse { 0%, 100% { transform: scale(1) rotate(0deg); opacity: 0.3; } 50% { transform: scale(1.05) rotate(180deg); opacity: 0.6; } } @keyframes superpositionFlicker { 0% { background-position: 0% 50%; } 25% { background-position: 100% 50%; } 50% { background-position: 50% 0%; } 75% { background-position: 50% 100%; } 100% { background-position: 0% 50%; } } @keyframes waveMotion { 0% { transform: translateY(-50%) scaleX(0); opacity: 0; } 50% { transform: translateY(-50%) scaleX(1); opacity: 1; } 100% { transform: translateY(-50%) scaleX(0); opacity: 0; } } @keyframes transitionFlash { 0% { opacity: 0; transform: scale(0.5); } 50% { opacity: 1; transform: scale(1.2); } 100% { opacity: 0; transform: scale(2); } } @keyframes particleFloat { 0% { opacity: 0; transform: translate(0, 0) scale(0.5); } 50% { opacity: 1; transform: translate(var(--dx), var(--dy)) scale(1); } 100% { opacity: 0; transform: translate(var(--dx2), var(--dy2)) scale(0.3); } } /* Hover effects */ .toggle-container:hover .toggle-thumb { transform: translateY(-50%) scale(1.05); } .toggle-container:hover .quantum-field { opacity: 0.8; } /* Focus styles */ :host(:focus-within) .toggle-track { outline: 2px solid rgba(255,255,255,0.5); outline-offset: 2px; } /* Disabled state */ :host([disabled]) .toggle-track { filter: grayscale(1); opacity: 0.6; } :host([disabled]) .toggle-thumb { filter: grayscale(1); } </style> <div class="toggle-container" id="toggleContainer"> <div class="quantum-field" id="quantumField"></div> <div class="toggle-track ${this.config.checked?"on":"off"}" id="toggleTrack"> <div class="wave-function" id="waveFunction"></div> <div class="transition-effect" id="transitionEffect"></div> </div> <div class="toggle-thumb ${this.config.checked?"on":"off"}" id="toggleThumb"></div> </div> ${i?`<span class="toggle-label">${i}</span>`:""} `}initializeElements(){this.elements={container:this.shadowRoot.getElementById("toggleContainer"),track:this.shadowRoot.getElementById("toggleTrack"),thumb:this.shadowRoot.getElementById("toggleThumb"),quantumField:this.shadowRoot.getElementById("quantumField"),waveFunction:this.shadowRoot.getElementById("waveFunction"),transitionEffect:this.shadowRoot.getElementById("transitionEffect")},this.setAttribute("tabindex","0")}attachEventListeners(){const t=n=>this.handleClick(n),e=n=>this.handleKeydown(n),s=n=>this.handleFocus(n),i=n=>this.handleBlur(n);this.addEventListener("click",t),this.addEventListener("keydown",e),this.addEventListener("focus",s),this.addEventListener("blur",i),this.cleanup.push(()=>{this.removeEventListener("click",t),this.removeEventListener("keydown",e),this.removeEventListener("focus",s),this.removeEventListener("blur",i)})}handleClick(t){this.config.disabled||(t.preventDefault(),this.toggle())}handleKeydown(t){this.config.disabled||(t.key===" "||t.key==="Enter")&&(t.preventDefault(),this.toggle())}handleFocus(t){this.config.quantumMode&&this.elements.quantumField.classList.add("active")}handleBlur(t){this.elements.quantumField.classList.remove("active")}toggle(){this.state.isTransitioning||(this.config.checked=!this.config.checked,this.config.checked?this.setAttribute("checked",""):this.removeAttribute("checked"),this.performQuantumTransition(),this.dispatchEvent(new CustomEvent("change",{detail:{checked:this.config.checked},bubbles:!0})))}performQuantumTransition(){if(!this.config.quantumMode){this.updateToggleState();return}this.state.isTransitioning=!0,this.config.superpositionEnabled&&this.enterSuperposition(),this.triggerWaveCollapse(),this.createQuantumParticles(),this.elements.transitionEffect.classList.add("active"),setTimeout(()=>{this.elements.transitionEffect.classList.remove("active")},600);const t=800/this.config.transitionSpeed;this.transitionTimeout=setTimeout(()=>{this.completeTransition()},t)}enterSuperposition(){this.elements.thumb.classList.add("superposition"),this.elements.quantumField.classList.add("active"),setTimeout(()=>{this.elements.thumb.classList.remove("superposition"),this.elements.quantumField.classList.remove("active")},800/this.config.transitionSpeed)}triggerWaveCollapse(){this.elements.waveFunction.classList.add("active"),setTimeout(()=>{this.elements.waveFunction.classList.remove("active")},1e3/this.config.transitionSpeed)}createQuantumParticles(){const t=this.elements.container,e=this.config.particleCount;for(let s=0;s<e;s++){const i=document.createElement("div");i.className="quantum-particle";const n=Math.random()*t.offsetWidth,o=Math.random()*t.offsetHeight,a=(Math.random()-.5)*40,r=(Math.random()-.5)*40,l=a*2,c=r*2;i.style.left=`${n}px`,i.style.top=`${o}px`,i.style.setProperty("--dx",`${a}px`),i.style.setProperty("--dy",`${r}px`),i.style.setProperty("--dx2",`${l}px`),i.style.setProperty("--dy2",`${c}px`),i.style.background=this.config.checked?this.config.colorOn:this.config.colorOff,i.style.animation=`particleFloat ${1.5/this.config.transitionSpeed}s ease-out`,t.appendChild(i),setTimeout(()=>{i.parentNode&&i.parentNode.removeChild(i)},1.5*1e3/this.config.transitionSpeed)}}completeTransition(){this.updateToggleState(),this.state.isTransitioning=!1}updateToggleState(){const{thumb:t,track:e}=this.elements;this.config.checked?(t.classList.remove("off"),t.classList.add("on"),e.classList.remove("off"),e.classList.add("on")):(t.classList.remove("on"),t.classList.add("off"),e.classList.remove("on"),e.classList.add("off"))}updateStyling(){this.render(),this.initializeElements(),this.attachEventListeners(),this.updateToggleState()}initializeQuantumField(){this.config.quantumMode&&(this.state.waveFunctions=Array.from({length:3},(t,e)=>({amplitude:Math.random()*.5+.3,frequency:(e+1)*.1,phase:Math.random()*Math.PI*2})))}startAnimation(){if(!this.config.quantumMode)return;const t=()=>{if(this.state.quantumField+=.02,this.elements.quantumField.classList.contains("active")){const e=Math.sin(this.state.quantumField)*.3+.7;this.elements.quantumField.style.opacity=e*this.config.glowIntensity}this.animationId=requestAnimationFrame(t)};t()}stopAnimation(){this.animationId&&(cancelAnimationFrame(this.animationId),this.animationId=null),this.transitionTimeout&&(clearTimeout(this.transitionTimeout),this.transitionTimeout=null)}check(){this.config.checked||this.toggle()}uncheck(){this.config.checked&&this.toggle()}isChecked(){return this.config.checked}enable(){this.config.disabled=!1,this.removeAttribute("disabled"),this.style.opacity="1",this.style.cursor="pointer"}disable(){this.config.disabled=!0,this.setAttribute("disabled",""),this.style.opacity="0.6",this.style.cursor="not-allowed"}}customElements.define("l-quantum-toggle",LQuantumToggle);