";
+ }
+
+ function toggleSymbol(symbol) {
+ selected = selected.includes(symbol) ? selected.filter((item) => item !== symbol) : [...selected, symbol].slice(-3);
+ const phrase = dreamPhrase(selected);
+ sentence.textContent = phrase.title;
+ reading.textContent = phrase.reading;
+ if (selected.length >= 2) {
+ const discoveredSymbols = { ...(state.discoveredSymbols || {}) };
+ selected.forEach((item) => {
+ discoveredSymbols[item] = { meaning: phrase.meaning, discoveredAt: new Date().toISOString(), path: selected };
+ });
+ state = { ...state, discoveredSymbols, lastDreamPath: selected, crossGameFlags: { ...(state.crossGameFlags || {}), "symbol:dream-sentence": true } };
+ writeObservatoryState(state);
+ status.textContent = selected.length === 3 ? "The website answers in objects, then pretends it was only navigation." : "A symbolic route has begun to breathe.";
+ }
+ render();
+ }
+
+ $("[data-subconscious-clear]", root).addEventListener("click", () => {
+ selected = [];
+ sentence.textContent = "Nothing has combined yet.";
+ reading.textContent = "young z has drawn a door in the margin and refuses to explain why that helps.";
+ status.textContent = "The current dream closed without being lost.";
+ render();
+ });
+ render();
+ }
+
+ function dreamPhrase(symbols) {
+ const key = symbols.slice().sort().join("|");
+ const table = {
+ "clock|ring": ["A future kept warm", "future z recognizes the ring as a promise that learned to wait.", "promise and return"],
+ "crayon sun|door": ["Safety drawn before it was understood", "young z makes a door because a page can become a room.", "play and shelter"],
+ "console|tea": ["Logic learned to bow", "aphy accepts the cup from sensei chi and stops explaining for one line.", "systems softened by patience"],
+ "kitchen light|ring": ["Home on purpose", "lima turns the symbol back toward the ordinary room where it belongs.", "warmth and vow"],
+ "mirror|ring": ["The promise has a reflection", "the observatory shows the same memory from the side that was quiet.", "echo and vow"]
+ };
+ const picked = table[key] || (symbols.length
+ ? [`${symbols.join(" / ")}`, "sensei chi says the meaning is not late. It is arriving by a slower path.", "private weather"]
+ : ["Nothing has combined yet.", "young z has drawn a door in the margin and refuses to explain why that helps.", "waiting"]);
+ return { title: picked[0], reading: picked[1], meaning: picked[2] };
+ }
+
+ function bumpTrust(current, characters) {
+ const next = { ...(current || {}) };
+ characters.forEach((name) => {
+ if (["lima", "aphy", "sensei chi", "young z", "future z"].includes(name)) next[name] = Number(next[name] || 0) + 1;
+ });
+ return next;
+ }
+
+ function escapeHtml(value) {
+ return String(value || "").replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", "\"": """, "'": "'" })[char]);
+ }
+
+ function escapeAttr(value) {
+ return escapeHtml(value).replace(/`/g, "`");
+ }
+
function setupSortable(container, _render, after) {
let dragged = null;
container.addEventListener("dragstart", (event) => {
diff --git a/assets/styles/play.css b/assets/styles/play.css
index 5f16f40..62d3928 100644
--- a/assets/styles/play.css
+++ b/assets/styles/play.css
@@ -495,6 +495,132 @@
background: var(--surface);
}
+.observatory-ritual {
+ --ritual-sky: #17130f;
+ --ritual-gold: #d6b36a;
+ --ritual-blue: #75a9bd;
+ --ritual-red: #d06b78;
+ --ritual-green: #7fb089;
+ --ritual-violet: #a58ac9;
+}
+
+.observatory-panel {
+ display: grid;
+ gap: 1rem;
+}
+
+.observatory-sky {
+ position: relative;
+ min-height: clamp(340px, 58vw, 620px);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ overflow: hidden;
+ background:
+ radial-gradient(circle at 50% 50%, rgba(214, 179, 106, 0.16), transparent 12%),
+ radial-gradient(circle at 28% 34%, rgba(117, 169, 189, 0.14), transparent 18%),
+ radial-gradient(circle at 72% 70%, rgba(208, 107, 120, 0.12), transparent 16%),
+ repeating-radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.07) 0 1px, transparent 1px 58px),
+ var(--ritual-sky);
+}
+
+.observatory-sky::after {
+ content: "";
+ position: absolute;
+ inset: 12%;
+ border: 1px dashed rgba(246, 216, 137, 0.16);
+ border-radius: 50%;
+ pointer-events: none;
+}
+
+.observatory-star {
+ position: absolute;
+ left: var(--x);
+ top: var(--y);
+ width: var(--size);
+ height: var(--size);
+ min-height: 0;
+ padding: 0;
+ border-radius: 50%;
+ border-color: rgba(255, 248, 232, 0.58);
+ background: radial-gradient(circle, #fff8e8 0 20%, var(--ritual-gold) 21% 52%, rgba(214, 179, 106, 0.18) 53% 100%);
+ color: #17130f;
+ font-weight: 900;
+ transform: translate(-50%, -50%);
+ box-shadow: 0 0 18px rgba(214, 179, 106, 0.28);
+}
+
+.observatory-star:hover,
+.observatory-star.is-active {
+ background: radial-gradient(circle, #fff8e8 0 18%, var(--ritual-red) 19% 56%, rgba(208, 107, 120, 0.22) 57% 100%);
+ color: #fff8e8;
+ box-shadow: 0 0 28px rgba(208, 107, 120, 0.46);
+}
+
+.observatory-star span {
+ display: grid;
+ place-items: center;
+ width: 100%;
+ height: 100%;
+ font-size: 0.78rem;
+ text-transform: uppercase;
+}
+
+.observatory-drawer,
+.dream-sentence {
+ padding: 1rem;
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ background: color-mix(in oklab, var(--surface) 86%, var(--play-paper) 14%);
+}
+
+.observatory-thread-actions,
+.symbol-cloud,
+.return-memory-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.65rem;
+}
+
+.temporal-console {
+ grid-template-columns: minmax(0, 1fr) minmax(190px, auto);
+}
+
+.return-memory {
+ display: grid;
+ flex: 1 1 250px;
+ gap: 0.35rem;
+ min-height: 100px;
+ text-align: left;
+}
+
+.return-memory span,
+.symbol-chip span {
+ color: var(--muted);
+ font-size: 0.86rem;
+}
+
+.symbol-cloud {
+ align-items: stretch;
+}
+
+.symbol-chip {
+ flex: 1 1 150px;
+ display: grid;
+ gap: 0.3rem;
+ text-align: left;
+ background:
+ linear-gradient(135deg, color-mix(in oklab, var(--surface) 88%, var(--ritual-blue) 12%), var(--surface));
+}
+
+.symbol-chip.is-active {
+ background: var(--play-ink);
+ color: #fff8e8;
+}
+
+.symbol-chip.is-active span {
+ color: rgba(255, 248, 232, 0.72);
+}
+
.rpg-shell {
display: grid;
grid-template-columns: minmax(0, 960px) minmax(280px, 1fr);
@@ -804,6 +930,7 @@
.play-console,
.recipe-tool,
.sigil-tool,
+ .temporal-console,
.rpg-shell {
grid-template-columns: 1fr;
}
diff --git a/play/future-z-return-log.org b/play/future-z-return-log.org
new file mode 100644
index 0000000..41c7019
--- /dev/null
+++ b/play/future-z-return-log.org
@@ -0,0 +1,37 @@
+#+TITLE: Future Z's Return Log
+#+OPTIONS: num:nil title:nil toc:nil
+#+NO_SIDENOTES: t
+#+HIDDEN_PAGE: t
+#+DATE: <2026-05-10 Sun>
+
+#+BEGIN_EXPORT html
+
+ Back to Play
+
+
Layer 4 / return weather
+
Future Z's Return Log
+
This room changes by being visited. Future z does not hurry the message; the page keeps count quietly.
+
+
+
+
+
First opening
+
The log is still warming up.
+
future z has left the first note face down, so the ink does not smudge.
+
+
+
+
+
+
+
+
+
+
+
+ Temporal echoes are settling.
+
+
+
+
+#+END_EXPORT
diff --git a/play/gentle-constellarium.org b/play/gentle-constellarium.org
new file mode 100644
index 0000000..bc78695
--- /dev/null
+++ b/play/gentle-constellarium.org
@@ -0,0 +1,42 @@
+#+TITLE: Gentle Constellarium
+#+OPTIONS: num:nil title:nil toc:nil
+#+NO_SIDENOTES: t
+#+HIDDEN_PAGE: t
+#+DATE: <2026-05-10 Sun>
+
+#+BEGIN_EXPORT html
+
+ Back to Play
+
+
Layer 2 / restored thread
+
Gentle Constellarium
+
The observatory has loosened into a sky. Choose a dim memory, then a second light that feels close enough to hold it.
+
+
+
+
+ The sky is gathering memory nodes.
+
+
+
+
+
Selected thread
+
Waiting for a first light
+
lima keeps a hand near the thread, not pulling, just steadying.
+
+
+
+
+
+
+
+
+
+
+
+
Recovered constellations
+
+
+
+
+#+END_EXPORT
diff --git a/play/subconscious-index.org b/play/subconscious-index.org
new file mode 100644
index 0000000..3dc271b
--- /dev/null
+++ b/play/subconscious-index.org
@@ -0,0 +1,36 @@
+#+TITLE: The Subconscious Index
+#+OPTIONS: num:nil title:nil toc:nil
+#+NO_SIDENOTES: t
+#+HIDDEN_PAGE: t
+#+DATE: <2026-05-10 Sun>
+
+#+BEGIN_EXPORT html
+
+ Back to Play
+
+
Layer 5 / symbolic drift
+
The Subconscious Index
+
The website keeps speaking through objects. Choose recurring symbols until they begin arranging themselves into a sentence.
+
+
+
+
+ The index is reading the recurring objects.
+
+
+
+
+
Dream sentence
+
Nothing has combined yet.
+
young z has drawn a door in the margin and refuses to explain why that helps.