diff --git a/hub.py b/hub.py index b110ca1..f6502d0 100644 --- a/hub.py +++ b/hub.py @@ -115,7 +115,7 @@ class Hub: # (the ws control channel has no auth — safe to expose publicly this way). # Set via config "readonly": true or env GODSTRUMENT_READONLY=1. self.readonly = bool(cfg.get("readonly")) or \ - os.environ.get("GODSTRUMENT_READONLY", "") not in ("", "0") + os.environ.get("GODSTRUMENT_READONLY", "").strip().lower() in ("1", "true", "yes", "on") self.signals: dict[str, Signal] = {} self._raw_lock = threading.Lock() diff --git a/transform.py b/transform.py index 572759e..92ada12 100644 --- a/transform.py +++ b/transform.py @@ -37,7 +37,7 @@ class Tweak: if spec: for k, v in spec.items(): if k in self.p: - self.p[k] = float(v) if not isinstance(v, bool) else float(v) + self.p[k] = float(v) self._held: dict[str, float] = {} # freeze holds, keyed by signal name self._lag: dict[str, float] = {} # smoothing state, keyed by signal name self.orbit_lfo = 1.0 # external planetary-orbit gain (hub-set) diff --git a/viz/index.html b/viz/index.html index fd97594..3291371 100644 --- a/viz/index.html +++ b/viz/index.html @@ -1800,7 +1800,7 @@ pick.appendChild(lb); } const hint2 = document.createElement("span"); hint2.className = "albl"; - const nSel = dests.get(arrSelKey); + const nSel = dests.get(arrSelKey) || { isNote: false }; // dests can be empty in the cold-connect window (pre-hello) hint2.textContent = nSel.isNote ? "piano roll · " + NOTE_NAMES[gScale.root] + " " + gScale.name.replace("_", " ") + " · click to place · stack 2+ in a column → plays one at random" : "16-step gate · lit steps let the world's signal through"; @@ -2993,7 +2993,7 @@ unp.onclick = () => { for (const src of node.members) this._dropSource(src); this.refreshSockets(); this.persist(); m.remove(); }; m.appendChild(unp); const vibe = document.createElement("div"); vibe.textContent = "💾 save as vibe"; vibe.style.cssText = "color:#9fd0ff;cursor:pointer;margin:5px 0"; - vibe.onclick = () => { m.remove(); if (this.saveVibe) this.saveVibe(gk); else eventTicker.unshift({ text: "💾 vibes land in the next update — build a feeling, save it, hand it to a friend", tAdded: now(), src: "sys" }); }; + vibe.onclick = () => { m.remove(); this.saveVibe(gk); }; m.appendChild(vibe); document.body.appendChild(m); const off = (ev) => { if (!m.contains(ev.target)) { m.remove(); document.removeEventListener("mousedown", off, true); } }; @@ -6580,7 +6580,7 @@ for (const sk of Object.keys(SKINS)) { const it2 = document.createElement("div"); it2.className = "it"; it2.textContent = (SKINS[sk].key === activeSkin.key ? "✓ " : "") + SKINS[sk].label; - it2.onclick = () => { setSkin(sk); if (!chakraMode) { chakraMode = true; clearTrails(); } closeCtx(); }; + it2.onclick = () => { setSkin(sk); if (!chakraMode && !zeroMode) { chakraMode = true; clearTrails(); } closeCtx(); }; skinSub.appendChild(it2); } ctxItem(performMode ? "leave performance mode" : "performance mode", @@ -7170,8 +7170,8 @@ grimoire: { label: "open the grimoire (help)", run: () => { const g = document.getElementById("grimoire"); if (g) g.classList.add("open"); } }, play: { label: "play / stop the synth", run: () => { if (typeof Synth !== "undefined") Synth.toggle(); } }, chakraView:{ label: "chakra / skin view", run: () => { if (zeroMode) return; chakraMode = !chakraMode; if (!chakraMode) { layoutDirty = true; exitMind(); } else clearTrails(); } }, - skinNext: { label: "next skin", run: () => { setSkin(nextSkinKey()); if (!chakraMode) { chakraMode = true; clearTrails(); } } }, - skinPrev: { label: "previous skin", run: () => { setSkin(prevSkinKey()); if (!chakraMode) { chakraMode = true; clearTrails(); } } }, + skinNext: { label: "next skin", run: () => { setSkin(nextSkinKey()); if (!chakraMode && !zeroMode) { chakraMode = true; clearTrails(); } } }, + skinPrev: { label: "previous skin", run: () => { setSkin(prevSkinKey()); if (!chakraMode && !zeroMode) { chakraMode = true; clearTrails(); } } }, moodCycle: { label: "cycle mood (first light / drift / storm)", run: () => cycleMood() }, tracks: { label: "tracks / arranger", run: () => { arr.open ? closeArranger() : openArranger(); } }, perform: { label: "performance mode", run: () => window.dispatchEvent(new KeyboardEvent("keydown", { key: "p" })) }, diff --git a/viz/manual.html b/viz/manual.html index 42f397a..39fa180 100644 --- a/viz/manual.html +++ b/viz/manual.html @@ -351,6 +351,25 @@ padding: 4px 9px; user-select: text; -webkit-user-select: text; transition: background 0.15s; } #account .codechip:hover { background: rgba(124,255,178,0.18); } #account .codechip.copied { color: #fff; border-color: #7cffb2; background: rgba(124,255,178,0.3); } + /* admin — user management */ + #account.wide .box { width: 520px; } + #account .userlist { margin-top: 6px; } + #account .urow { padding: 8px 0; border-bottom: 1px solid rgba(120,150,200,0.12); } + #account .urow .uhead { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; } + #account .urow .uname { color: #eaf1ff; font-size: 13px; font-weight: 600; } + #account .urow .umeta { color: rgba(160,180,210,0.6); font-size: 10.5px; } + #account .urow .ubadge { font-size: 9.5px; letter-spacing: .5px; text-transform: uppercase; padding: 1px 6px; border-radius: 20px; + background: rgba(255,214,92,0.14); color: #ffd65c; border: 1px solid rgba(255,214,92,0.35); } + #account .urow .uact { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 6px; } + #account .urow .ubtn { background: rgba(255,255,255,0.05); color: #b9c8e2; border: 1px solid rgba(120,150,200,0.22); + border-radius: 7px; padding: 4px 9px; cursor: pointer; font-size: 10.5px; } + #account .urow .ubtn:hover { border-color: rgba(150,180,255,0.55); color: #eaf1ff; } + #account .urow .ubtn.danger:hover { border-color: rgba(255,120,120,0.6); color: #ffb3b3; } + #account .urow .uedit { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 6px; } + #account .urow .uedit input { flex: 1 1 130px; min-width: 0; padding: 5px 8px; border-radius: 7px; font: inherit; font-size: 11px; + background: rgba(8,12,24,0.7); color: #eaf1ff; border: 1px solid rgba(120,150,200,0.25); } + #account .urow .umsg { font-size: 10.5px; margin-top: 4px; min-height: 12px; } + #account .urow .umsg .tmp { font-family: monospace; color: #7cffb2; user-select: text; } /* ---- gated landing page (shown until you sign in) ---- */ #landing {