From 4e44533cdd773bf2a30cea26a92d8d196a9f8dc3 Mon Sep 17 00:00:00 2001 From: type-two Date: Fri, 10 Jul 2026 23:33:43 +1000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8C=B1=20Z1.5=20=E2=80=94=20stage=20nodes?= =?UTF-8?q?=20are=20spinning=20orbs=20(John's=20vision)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each built group becomes a spinning orb on the stage, not a flat chip — a glowing core in the group's hue with its picked feeds as petals orbiting around it (CSS zspin/zpulse). They accumulate as you build: pick from fire → one orb; add cosmos → two; and on. Orbs breathe live — the core glows by the group's average value, each petal brightens with its own feed. More feeds widen the orbit ring. Verified live: fire (3 petals) + cosmos (2 petals) render as two spinning glowing orbs; console clean. Co-Authored-By: Claude Opus 4.8 --- viz/index.html | 52 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/viz/index.html b/viz/index.html index 8ad5b28..65fce5b 100644 --- a/viz/index.html +++ b/viz/index.html @@ -2697,10 +2697,16 @@ + ".zdot{width:40px;height:40px;border-radius:50%;flex:0 0 auto;position:relative}" + ".zlbl{font-size:12.5px;color:#c6d4ec;letter-spacing:.3px;line-height:1.2}" + ".zlbl small{display:block;color:rgba(150,170,205,.6);font-size:10px}" - + ".zstage{position:absolute;left:210px;right:210px;top:84px;bottom:150px;pointer-events:auto;display:flex;flex-wrap:wrap;align-content:flex-start;gap:12px;padding:14px;overflow:auto}" - + ".zstage .zhint{margin:auto;color:rgba(150,170,205,.5);font-size:13px;font-style:italic;text-align:center}" - + ".zchip{border-radius:13px;padding:11px 13px;min-width:130px;background:rgba(20,28,44,.72);border:1px solid;font-size:12px;color:#dbe6f8}" - + ".zchip b{color:#eaf1ff}.zchip .mem{color:rgba(180,195,225,.8);font-size:11px;margin-top:5px;line-height:1.5}" + + ".zstage{position:absolute;left:210px;right:210px;top:84px;bottom:150px;pointer-events:auto;display:flex;flex-wrap:wrap;align-content:center;justify-content:center;gap:22px;padding:14px;overflow:auto}" + + ".zstage .zhint{margin:auto;color:rgba(150,170,205,.5);font-size:13px;font-style:italic;text-align:center;max-width:280px}" + + ".zorb{position:relative;width:132px;height:150px;flex:0 0 auto;cursor:grab}" + + ".zorb .core{position:absolute;left:50%;top:44%;width:44px;height:44px;margin:-22px;border-radius:50%;animation:zpulse 3.2s ease-in-out infinite}" + + ".zorb .orbit{position:absolute;left:0;right:0;top:0;height:132px;animation:zspin 15s linear infinite}" + + ".zorb .pet{position:absolute;left:50%;top:50%;width:13px;height:13px;margin:-6.5px;border-radius:50%;box-shadow:0 0 6px currentColor}" + + ".zorb .cap{position:absolute;left:0;right:0;bottom:0;text-align:center;font-size:11px;color:#dbe6f8}" + + ".zorb .cap small{color:rgba(170,190,220,.6)}" + + "@keyframes zspin{to{transform:rotate(360deg)}}" + + "@keyframes zpulse{0%,100%{transform:scale(1);opacity:.9}50%{transform:scale(1.12);opacity:1}}" + ".zrack{position:absolute;left:210px;right:210px;bottom:20px;height:120px;pointer-events:auto;overflow:auto}" + ".zrack .grp{font-size:10px;letter-spacing:1px;color:rgba(150,170,205,.55);text-transform:uppercase;margin:2px 0 4px}" + ".zsock{display:inline-block;margin:0 7px 7px 0;padding:6px 10px;border-radius:9px;background:rgba(18,26,42,.7);border:1px solid rgba(120,150,200,.2);font-size:11.5px;color:#aab8d4}" @@ -2791,21 +2797,31 @@ }, renderStage() { if (!this.stage) return; - this.stage.innerHTML = ""; + this.stage.innerHTML = ""; this.orbs = []; const keys = Object.keys(this.nodes); if (!keys.length) { const hint = document.createElement("div"); hint.className = "zhint"; - hint.textContent = "click a ring on either side → pick a feed → it lands here. then drag it to a voice (soon)."; + hint.textContent = "click a ring on either side → pick a feed → it becomes a spinning orb here. gather more and the orbs multiply. (drag to a voice — soon.)"; this.stage.appendChild(hint); return; } - for (const gk of keys) { - const meta = GROUP_META[gk], node = this.nodes[gk]; - const chip = document.createElement("div"); chip.className = "zchip"; chip.style.borderColor = meta.hue; - const names = [...node.members].map(k => { const n = sources.get(k); return n ? n.label : k; }); - chip.innerHTML = '' + meta.emoji + " " + gk + " · " + node.members.size - + '
' + names.map(x => x.replace(/"; - chip._group = gk; - this.stage.appendChild(chip); + for (const gk of keys) { // each built group → a spinning orb; they accumulate + const meta = GROUP_META[gk], mem = [...this.nodes[gk].members]; + const orb = document.createElement("div"); orb.className = "zorb"; orb.title = mem.map(k => (sources.get(k) || {}).label || k).join(" · "); + const orbit = document.createElement("div"); orbit.className = "orbit"; + const R = 44 + Math.min(mem.length, 8) * 1.5; // more feeds → a slightly wider ring + mem.forEach((k, i) => { + const a = (i / mem.length) * Math.PI * 2; + const pet = document.createElement("div"); pet.className = "pet"; + pet.style.color = meta.hue; pet.style.background = meta.hue; + pet.style.transform = "translate(" + (Math.cos(a) * R).toFixed(1) + "px," + (Math.sin(a) * R).toFixed(1) + "px)"; + pet._src = k; orbit.appendChild(pet); + }); + const core = document.createElement("div"); core.className = "core"; core.style.background = meta.hue; + const cap = document.createElement("div"); cap.className = "cap"; + cap.innerHTML = meta.emoji + " " + gk + " · " + mem.length + ""; + orb.appendChild(orbit); orb.appendChild(core); orb.appendChild(cap); + orb._group = gk; orb._core = core; + this.stage.appendChild(orb); this.orbs.push(orb); } }, update() { @@ -2823,6 +2839,14 @@ row._fill.style.width = Math.round((n ? (n.normDisp || n.norm || 0) : 0) * 100) + "%"; } } + if (this.orbs) for (const orb of this.orbs) { // orbs breathe: core glows by group avg, petals by feed + const v = _grpAvg(orb._group); + orb._core.style.boxShadow = "0 0 " + (8 + v * 24).toFixed(0) + "px " + (1 + v * 3).toFixed(1) + "px " + GROUP_META[orb._group].hue; + for (const pet of orb.querySelectorAll(".pet")) { + const n = sources.get(pet._src); + pet.style.opacity = (0.32 + (n ? (n.normDisp || n.norm || 0) : 0) * 0.68).toFixed(2); + } + } }, };