From 4bf193d0974e3b7991ec9368dbc7e617e7fa1123 Mon Sep 17 00:00:00 2001 From: type-two Date: Mon, 6 Jul 2026 12:59:56 +1000 Subject: [PATCH] =?UTF-8?q?moods=20persist=20across=20reloads=20=E2=80=94?= =?UTF-8?q?=20no=20more=20losing=20the=20gentle=20preset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit applyMood now saves gs_mood; on page load the last-chosen mood re-applies (default "first light"), guarded to run once per load (moodAppliedThisLoad), never on a mid-session reconnect. A reload no longer drops you into the raw factory "wall" — your vibe survives. Nothing mix-related survives a reload anyway (routes/tweaks/grooves reset from the hub), so re-applying the mood is strictly a friendlier landing. Plus: pressing the synth button into a groove-less mix now tickers a hint pointing to mood -> first light. Verified: first visit stores gs_mood=first light; reload re-applies it (dial shows 92 LOCKED, gnarly voices muted), zero console errors. Co-Authored-By: Claude Fable 5 --- viz/index.html | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/viz/index.html b/viz/index.html index 424d5f0..13fc5ab 100644 --- a/viz/index.html +++ b/viz/index.html @@ -1808,9 +1808,11 @@ const MOOD_KEYS = ["glitch", "granular.density", "saturation", "tempo.nudge", "wavetable.morph", "lfo.rate", "reverb.size", "delay.feedback", "drone.voices", "pad.brightness", "perc.density", "master.space", "lead.note", "bass.note"]; + let moodAppliedThisLoad = false; // guard: apply a mood once per page load, not per reconnect function applyMood(name) { const m = MOODS[name]; if (!m) return; + try { localStorage.setItem("gs_mood", name); } catch (e) {} // your vibe survives a reload for (const k of MOOD_KEYS) { const t2 = tw(k); t2.mute = 0; t2.gain = 1; t2.offset = 0; } if (m.storm) { for (const k in seqs) seqs[k].on = false; @@ -2244,13 +2246,20 @@ if (Array.isArray(msg.routes)) { preLayoutRoutes(msg.routes); clientMatrix = true; // from here YOUR patch is computed locally - // first visit: greet gently — a song-shaped mood, not the full flood - let seen = false; try { seen = !!localStorage.getItem("gs_seen"); } catch (e) {} - if (!seen) { - try { localStorage.setItem("gs_seen", "1"); } catch (e) {} + // greet with your last mood (default: the gentle "first light"). Applied + // once per page load — never on a mid-session reconnect, so it can't + // clobber what you're playing. A reload no longer drops you into the wall. + if (!moodAppliedThisLoad) { + moodAppliedThisLoad = true; + let firstEver = false, saved = "first light"; + try { firstEver = !localStorage.getItem("gs_seen"); } catch (e) {} + try { saved = localStorage.getItem("gs_mood") || "first light"; } catch (e) {} setTimeout(() => { - applyMood("first light"); - eventTicker.unshift({ text: "🌅 first light — a gentle arrangement to start you off. right-click the sky → mood → the full storm when you're ready", tAdded: now(), src: "sys" }); + applyMood(saved); + if (firstEver) { + try { localStorage.setItem("gs_seen", "1"); } catch (e) {} + eventTicker.unshift({ text: "🌅 first light — a gentle arrangement to start you off. right-click the sky → 🌗 mood for more (or the full storm when you're ready)", tAdded: now(), src: "sys" }); + } }, 900); } } @@ -5694,7 +5703,15 @@ function paint() { btn.style.color = on ? "#7cffb2" : "#9fb4d8"; btn.style.borderColor = on ? "rgba(124,255,178,0.5)" : "rgba(120,150,200,0.25)"; } if (!AC) { btn.style.opacity = "0.3"; btn.title = "Web Audio not supported"; } - else btn.addEventListener("click", () => { on ? stop() : start(); paint(); }); + else btn.addEventListener("click", () => { + const wasOff = !on; + on ? stop() : start(); paint(); + if (wasOff) { // starting into the raw factory wall? point the way out + const anyGroove = Object.keys(seqs).some(k => seqs[k] && seqs[k].on); + if (!anyGroove) + eventTicker.unshift({ text: "♪ this is the full world at once — for a gentle song, right-click the sky → 🌗 mood → first light", tAdded: now(), src: "sys" }); + } + }); return { update(dests) { if (dests) Object.assign(D, dests); }, pluck }; })();