UNPKG

luminomorphism

Version:

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

54 lines (53 loc) 3.74 kB
class LOrbitalNav extends HTMLElement{static get observedAttributes(){return["radius","speed","size","colour"]}constructor(){super(),this.shadow=this.attachShadow({mode:"open"}),this.radius=120,this.speed=20,this.size=80,this.colour="#00ffff",this.items=[]}connectedCallback(){this._parseAttributes(),this._render()}attributeChangedCallback(){this._parseAttributes(),this._render()}_parseAttributes(){if(this.hasAttribute("radius")){const t=parseInt(this.getAttribute("radius"));!isNaN(t)&&t>0&&(this.radius=t)}if(this.hasAttribute("speed")){const t=parseFloat(this.getAttribute("speed"));!isNaN(t)&&t>=0&&(this.speed=t)}if(this.hasAttribute("size")){const t=parseInt(this.getAttribute("size"));!isNaN(t)&&t>0&&(this.size=t)}if(this.hasAttribute("colour")){const t=this.getAttribute("colour");t&&(this.colour=t)}}_clear(){for(;this.shadow.firstChild;)this.shadow.removeChild(this.shadow.firstChild)}_render(){this._clear();const t=document.createElement("style");t.textContent=` :host { display: block; position: relative; width: 100%; height: ${this.radius*2+this.size}px; } .wrapper { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 0; height: 0; } .rotator { position: relative; width: 0; height: 0; transform-origin: center; animation: spin var(--period) linear infinite; } .bubble { position: absolute; top: 50%; left: 50%; display: flex; align-items: center; justify-content: center; width: ${this.size}px; height: ${this.size}px; border-radius: 50%; cursor: pointer; color: white; font-family: inherit; font-size: 0.9rem; text-align: center; background: radial-gradient(circle at 50% 40%, var(--bubble-colour) 0%, rgba(0,0,0,0.4) 100%); box-shadow: 0 4px 12px rgba(0,0,0,0.4), 0 0 12px 4px var(--bubble-colour)55; transition: transform 0.3s ease, box-shadow 0.3s ease; user-select: none; /* Use CSS variables for translation to allow composition with scale */ transform: translate(var(--x), var(--y)) scale(1); } .bubble:hover { transform: translate(var(--x), var(--y)) scale(1.25); box-shadow: 0 6px 18px rgba(0,0,0,0.6), 0 0 20px 8px var(--bubble-colour)aa; } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } `,this.shadow.appendChild(t);const a=document.createElement("div");a.className="wrapper";const i=document.createElement("div");i.className="rotator",this.speed>0?i.style.setProperty("--period",`${this.speed}s`):i.style.animation="none";const o=Array.from(this.children).filter(e=>e.nodeType===Node.ELEMENT_NODE);if(o.length===0)for(let e=0;e<5;e++){const r=document.createElement("div");r.textContent="Item "+(e+1),o.push(r)}const h=360/o.length;o.forEach((e,r)=>{const l=h*r*Math.PI/180,c=Math.cos(l)*this.radius,d=Math.sin(l)*this.radius,s=document.createElement("div");s.className="bubble";const p=e.getAttribute&&e.getAttribute("data-colour")?e.getAttribute("data-colour"):this.colour;s.style.setProperty("--bubble-colour",p);const n=e.cloneNode(!0);n.style&&(n.style.margin="0"),s.appendChild(n),s.style.setProperty("--x",`${c}px`),s.style.setProperty("--y",`${d}px`),s.addEventListener("click",u=>{u.stopPropagation();const b=new CustomEvent("bubble-click",{detail:{index:r,original:e},bubbles:!0,composed:!0});this.dispatchEvent(b)}),i.appendChild(s)}),a.appendChild(i),this.shadow.appendChild(a)}}customElements.define("l-orbital-nav",LOrbitalNav);