// Opaque leaf-widget island. slhx talks to it only with native CustomEvent payloads. // req: interop/001 req: examples/001 (() => { const roots = new WeakMap(); function forEachElement(scope, visit) { for (let node = scope && scope.firstElementChild; node; node = node.nextElementSibling) { visit(node); forEachElement(node, visit); } } function firstElement(scope, predicate) { let found = null; forEachElement(scope, (el) => { if (!found && predicate(el)) found = el; }); return found; } function rootOf(node) { for (let el = node; el; el = el.parentElement) { if (el.hasAttribute && el.hasAttribute("data-slhx-root")) return el; } return document.documentElement; } function parseSnapshot(raw) { const parts = String(raw || "0|0|0|waiting for Rust").split("|"); return { power: Number(parts[0] || 0) || 0, cards: Number(parts[1] || 0) || 0, impact: Number(parts[2] || 0) || 0, label: parts.slice(3).join("|") || "waiting for Rust", }; } function render(canvas, readout, state) { const ctx = canvas && canvas.getContext && canvas.getContext("2d"); if (!ctx) return; const w = canvas.width; const h = canvas.height; const t = state.frame / 48; ctx.clearRect(0, 0, w, h); ctx.fillStyle = "#06121f"; ctx.fillRect(0, 0, w, h); const cx = w / 2; const cy = h / 2; const radius = 38 + Math.min(52, state.snapshot.impact * 2); ctx.strokeStyle = "rgba(68,231,255,.45)"; ctx.lineWidth = 2; ctx.beginPath(); ctx.ellipse(cx, cy, radius * 1.55, radius * 0.72, -0.18, 0, Math.PI * 2); ctx.stroke(); ctx.fillStyle = "rgba(184,255,90,.95)"; for (let i = 0; i < Math.max(1, state.snapshot.cards); i += 1) { const angle = t + (Math.PI * 2 * i / Math.max(1, state.snapshot.cards)); const x = cx + Math.cos(angle) * radius * 1.55; const y = cy + Math.sin(angle) * radius * 0.72; ctx.beginPath(); ctx.arc(x, y, 4 + (state.snapshot.power % 4), 0, Math.PI * 2); ctx.fill(); } ctx.fillStyle = "#fff"; ctx.font = "700 16px system-ui, sans-serif"; ctx.fillText("slhx island", 18, 30); ctx.font = "12px system-ui, sans-serif"; ctx.fillStyle = "rgba(255,255,255,.74)"; ctx.fillText(`cards ${state.snapshot.cards} · impact ${state.snapshot.impact} · boost ${state.snapshot.power}`, 18, 50); if (readout) readout.textContent = state.snapshot.label; } function boot(island) { if (roots.has(island)) return; const canvas = firstElement(island, (el) => el.tagName === "CANVAS"); const readout = firstElement(island, (el) => el.hasAttribute("data-island-readout")); const state = { frame: 0, snapshot: parseSnapshot(island.getAttribute("data-island-snapshot")) }; roots.set(island, state); rootOf(island).addEventListener("slhx:island-orbit", (event) => { state.snapshot = parseSnapshot(event.detail); island.setAttribute("data-island-snapshot", event.detail); }); function tick() { state.frame += 1; render(canvas, readout, state); requestAnimationFrame(tick); } tick(); } function scan() { forEachElement(document, (el) => { if (el.hasAttribute("data-slhx-island")) boot(el); }); } if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", scan); else scan(); })();