🌱 Z0 — zero mode: blank dimension + first-visit choice card

The calm front door. Zero is not a muted world (moods do that) — it's an EMPTY
one you build by hand, the same matrix started from nothing.

- enterZero(): snapshots the current patch → gs_prezero, then loadPatch({}) empties
  the field; sets clientMatrix so the hub can't re-populate the cables; gs_mood="0 zero".
- exitZeroToMood(name): restores gs_prezero (the full factory) then applies the mood —
  leaving zero brings the whole world back.
- First-visit choice card ("one world, three doors"): 🌱 start from zero · 🌅 first
  light · ⛈ the full storm. Replaces the silent default-into-first-light; backdrop/esc =
  first light. Returning users keep their last mood; a returning zero-user re-enters zero.
- Mood submenu gains "0 zero — build from nothing"; F11 toggles zero (KEY_ACTIONS).

Verified live (logged-in preview): card renders with 3 doors; "start from zero" empties
the field (56 factory cables cleared, snapshotted); F11 restores all 56 + first light
(92 BPM locked); returning-zero reload re-enters zero with no card; console clean.

The 7+7 calm slot UI is Z1. Not deployed yet (deploy is Z6, post-review).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-10 22:53:14 +10:00
parent 35c094f8d8
commit 2a705e3a35

View File

@ -2566,6 +2566,83 @@
if (typeof p.bpm === "number") godtime.bpm = clamp(p.bpm, 40, 180);
layoutDirty = true;
}
// ---- zero mode — dimension zero -------------------------------------------
// The calm front door: not a muted world (moods do that) but an EMPTY one — no
// cables, nothing sounding — that you build by hand. It is the SAME matrix
// (routesArr/tweaks/synth), just started from nothing; leaving zero restores
// the world you had. Client-local, like moods — the hub/other users untouched.
let zeroMode = false;
function enterZero() {
if (!zeroMode) { // snapshot what you had, so exiting can restore it
try { localStorage.setItem("gs_prezero", JSON.stringify(serializePatch())); } catch (e) {}
}
clientMatrix = true; // own the matrix so the hub can't re-populate the cables
loadPatch({ routes: [], tweaks: {}, seqs: {} }); // empty the field
zeroMode = true;
try { localStorage.setItem("gs_mood", "0 zero"); } catch (e) {}
eventTicker.unshift({ text: "🌱 zero — the field is empty. build your own instrument: right-click a source → patch to → a voice, then press ♪", tAdded: now(), src: "sys" });
layoutDirty = true;
}
function exitZeroToMood(name) { // leave zero back into the full world, then a mood
if (zeroMode) {
zeroMode = false;
try { const pz = localStorage.getItem("gs_prezero"); if (pz) loadPatch(JSON.parse(pz)); } catch (e) {}
}
applyMood(name);
}
// The three doors, shown once on a first-ever visit (replaces the silent drop
// into first light). Backdrop/esc = first light, the gentle default.
function showFirstVisitCard() {
if (document.getElementById("firstcard")) return;
const wrap = document.createElement("div");
wrap.id = "firstcard";
Object.assign(wrap.style, { position: "fixed", inset: "0", zIndex: "70", display: "flex",
alignItems: "center", justifyContent: "center", background: "rgba(6,10,20,0.72)",
backdropFilter: "blur(8px)", WebkitBackdropFilter: "blur(8px)" });
const card = document.createElement("div");
Object.assign(card.style, { maxWidth: "640px", width: "88%", textAlign: "center",
color: "#d6e2f4", fontFamily: "inherit" });
const h = document.createElement("div");
h.textContent = "one world, three doors";
Object.assign(h.style, { fontWeight: "800", fontSize: "clamp(22px,4vw,32px)", letterSpacing: "2px", marginBottom: "6px" });
const sub = document.createElement("div");
sub.textContent = "the planet is already playing. choose how you meet it.";
Object.assign(sub.style, { color: "rgba(165,185,218,0.75)", fontStyle: "italic", marginBottom: "26px", fontSize: "14px" });
card.appendChild(h); card.appendChild(sub);
const row = document.createElement("div");
Object.assign(row.style, { display: "flex", gap: "14px", flexWrap: "wrap", justifyContent: "center" });
const doors = [
["🌱", "start from zero", "an empty field. build the instrument yourself, one cable at a time.", () => enterZero()],
["🌅", "first light", "a gentle arrangement — a bass line, soft hats, a small melody. ease in.", () => applyMood("first light")],
["⛈", "the full storm", "every one of the world's hands on it at once. the factory instrument.", () => applyMood("the full storm")],
];
for (const [emoji, title, desc, act] of doors) {
const b = document.createElement("div");
Object.assign(b.style, { flex: "1 1 170px", minWidth: "160px", maxWidth: "200px", cursor: "pointer",
padding: "20px 16px", borderRadius: "16px", background: "rgba(20,28,44,0.7)",
border: "1px solid rgba(120,150,200,0.28)", transition: "transform .12s, border-color .12s" });
b.onmouseenter = () => { b.style.transform = "translateY(-3px)"; b.style.borderColor = "rgba(150,200,255,0.7)"; };
b.onmouseleave = () => { b.style.transform = ""; b.style.borderColor = "rgba(120,150,200,0.28)"; };
b.innerHTML = '<div style="font-size:34px;margin-bottom:8px">' + emoji + '</div>'
+ '<div style="font-weight:700;color:#eaf1ff;margin-bottom:6px">' + title + '</div>'
+ '<div style="font-size:12px;color:rgba(180,195,225,0.75);line-height:1.5">' + desc + '</div>';
b.onclick = () => { wrap.remove(); document.removeEventListener("keydown", onEsc, true); act(); };
row.appendChild(b);
}
card.appendChild(row);
const foot = document.createElement("div");
foot.textContent = "you can switch any time — right-click the sky → 🌗 mood";
Object.assign(foot.style, { marginTop: "22px", fontSize: "11px", color: "rgba(150,170,205,0.55)" });
card.appendChild(foot);
wrap.appendChild(card);
const onEsc = (e) => { if (e.key === "Escape") { wrap.remove(); document.removeEventListener("keydown", onEsc, true); applyMood("first light"); } };
wrap.onclick = (e) => { if (e.target === wrap) { wrap.remove(); document.removeEventListener("keydown", onEsc, true); applyMood("first light"); } };
document.addEventListener("keydown", onEsc, true);
document.body.appendChild(wrap);
}
function makeGroupLocal(name, members) {
if (!name || !members || !members.length) return;
groupsInfo[name] = { label: name, members: members.slice() };
@ -2841,10 +2918,13 @@
try { firstEver = !localStorage.getItem("gs_seen"); } catch (e) {}
try { saved = localStorage.getItem("gs_mood") || "first light"; } catch (e) {}
setTimeout(() => {
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" });
showFirstVisitCard(); // three doors: zero / first light / storm
} else if (saved === "0 zero") {
enterZero(); // a returning zero-builder comes back to zero
} else {
applyMood(saved);
}
}, 900);
}
@ -5580,9 +5660,13 @@
ctxItem(arr.open ? "close tracks" : "🎛 tracks — arrange clips",
() => { arr.open ? closeArranger() : openArranger(); }, "T");
const moodSub = ctxSub("🌗 mood — how much world");
const z0 = document.createElement("div"); z0.className = "it";
z0.textContent = zeroMode ? "0 zero — you're building it (a mood leaves)" : "0 zero — build from nothing";
z0.onclick = () => { enterZero(); closeCtx(); };
moodSub.appendChild(z0);
for (const mn of Object.keys(MOODS)) {
const it2 = document.createElement("div"); it2.className = "it"; it2.textContent = mn;
it2.onclick = () => { applyMood(mn); closeCtx(); };
it2.onclick = () => { zeroMode ? exitZeroToMood(mn) : applyMood(mn); closeCtx(); };
moodSub.appendChild(it2);
}
if (mindChakra) ctxItem("step out of " + mindChakra.label, () => exitMind(), "esc");
@ -6174,9 +6258,10 @@
nudgeDown: { label: "lower the level of…", targeted: true, run: (k) => { const t = tw(k); t.gain = clamp((t.gain != null ? t.gain : 1) - 0.08, 0, 2); } },
muteKey: { label: "mute / unmute…", targeted: true, run: (k) => toggleMuteLocal(k) },
soloKeyA: { label: "solo…", targeted: true, run: (k) => { soloKey = soloKey === k ? null : k; } },
zero: { label: "zero — build from nothing", run: () => { zeroMode ? exitZeroToMood("first light") : enterZero(); } },
};
const DEFAULT_KEYS = { F1: { a: "grimoire" }, F2: { a: "play" }, F3: { a: "chakraView" }, F4: { a: "skinNext" },
F5: { a: "moodCycle" }, F6: { a: "tracks" }, F7: { a: "perform" }, F8: { a: "godLock" }, F9: { a: "earthEcho" }, F10: { a: "stage" } };
F5: { a: "moodCycle" }, F6: { a: "tracks" }, F7: { a: "perform" }, F8: { a: "godLock" }, F9: { a: "earthEcho" }, F10: { a: "stage" }, F11: { a: "zero" } };
let keymap = {};
try { keymap = JSON.parse(localStorage.getItem("gs_keys")) || {}; } catch (e) {}
if (!Object.keys(keymap).length) keymap = JSON.parse(JSON.stringify(DEFAULT_KEYS));