Files
the-testament/website/the-door.html
Alexander Whitestone 3d2fc63a2c testament-burn: Build browser-based 'The Door' text adventure (HTML/JS)
- Complete interactive text adventure playable in any browser
- All rooms from Python game: Bridge, Hard Room, Record, Wall, Timmy talk
- Slow-print narration, ambient atmosphere, trust system
- Multiple endings based on choices (stayed, spoke, signed wall)
- Crisis resources at ending
- Added 'PLAY IN BROWSER' button to landing page
- ~36KB self-contained HTML file, no dependencies
2026-04-10 18:43:40 -04:00

1059 lines
35 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>The Door — A Text Adventure in The Testament Universe</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500&family=Space+Grotesk:wght@300;400;500;700&display=swap');
:root {
--green: #00ff88;
--green-dim: #00cc6a;
--green-glow: rgba(0,255,136,0.15);
--navy: #0a1628;
--dark: #060d18;
--grey: #8899aa;
--light: #c8d6e5;
--white: #e8f0f8;
--red: #ff4444;
--cyan: #44ccff;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: var(--dark);
color: var(--light);
font-family: 'IBM Plex Mono', monospace;
font-size: 15px;
line-height: 1.8;
min-height: 100vh;
overflow-x: hidden;
}
/* RAIN */
.rain {
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
pointer-events: none; z-index: 0;
background: repeating-linear-gradient(
transparent, transparent 3px,
rgba(0,255,136,0.012) 3px, rgba(0,255,136,0.012) 4px
);
animation: rain 0.8s linear infinite;
}
@keyframes rain {
0% { background-position: 0 0; }
100% { background-position: 20px 600px; }
}
/* SCANLINE */
.scanline {
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
pointer-events: none; z-index: 1;
background: repeating-linear-gradient(
transparent, transparent 2px,
rgba(0,0,0,0.03) 2px, rgba(0,0,0,0.03) 4px
);
}
/* CONTAINER */
#game {
position: relative; z-index: 2;
max-width: 720px;
margin: 0 auto;
padding: 2rem 1.5rem;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* OUTPUT */
#output {
flex: 1;
padding-bottom: 1rem;
}
.line {
margin-bottom: 0.3rem;
opacity: 0;
animation: fadeIn 0.3s forwards;
}
@keyframes fadeIn { to { opacity: 1; } }
.line.narrative { color: var(--light); }
.line.dim { color: var(--grey); font-size: 0.9em; }
.line.green { color: var(--green); }
.line.cyan { color: var(--cyan); }
.line.bold { font-weight: 500; }
.line.title {
font-size: 1.1em;
font-weight: 500;
color: var(--white);
margin-top: 1rem;
}
.line.divider {
color: var(--grey);
opacity: 0.3;
margin: 0.5rem 0;
user-select: none;
}
.line.prompt-option {
color: var(--cyan);
cursor: pointer;
padding: 0.3rem 0;
transition: color 0.2s;
}
.line.prompt-option:hover {
color: var(--green);
text-shadow: 0 0 10px var(--green-glow);
}
.line.prompt-option::before {
content: ' ';
}
.line.slow {
overflow: hidden;
white-space: nowrap;
animation: typewriter 1s steps(40) forwards;
}
@keyframes typewriter {
from { width: 0; }
to { width: 100%; }
}
.line.error { color: var(--red); }
/* LED */
.led {
display: inline-block;
width: 8px; height: 8px;
background: var(--green);
border-radius: 50%;
box-shadow: 0 0 10px var(--green), 0 0 20px var(--green-dim);
animation: pulse 2s ease-in-out infinite;
vertical-align: middle;
margin: 0 4px;
}
@keyframes pulse {
0%, 100% { opacity: 0.4; box-shadow: 0 0 5px var(--green); }
50% { opacity: 1; box-shadow: 0 0 10px var(--green), 0 0 20px var(--green-dim); }
}
/* INPUT */
#input-area {
position: sticky;
bottom: 0;
background: linear-gradient(transparent 0%, var(--dark) 15%);
padding: 1rem 0;
z-index: 3;
}
#input-row {
display: flex;
align-items: center;
gap: 0.5rem;
border: 1px solid rgba(0,255,136,0.2);
border-radius: 4px;
padding: 0.5rem 0.75rem;
background: rgba(10,22,40,0.9);
}
#input-row .prompt-char {
color: var(--green);
font-weight: 500;
flex-shrink: 0;
}
#player-input {
flex: 1;
background: none;
border: none;
color: var(--green);
font-family: 'IBM Plex Mono', monospace;
font-size: 15px;
outline: none;
caret-color: var(--green);
}
#player-input::placeholder { color: rgba(0,255,136,0.25); }
/* STATUS BAR */
#status-bar {
position: sticky;
top: 0;
background: linear-gradient(var(--dark) 0%, transparent 100%);
padding: 0.75rem 0;
z-index: 3;
display: flex;
justify-content: space-between;
font-size: 0.8em;
color: var(--grey);
}
#status-bar .led-status { color: var(--green); }
#status-bar .room-name { color: var(--white); }
/* RESPONSIVE */
@media (max-width: 600px) {
#game { padding: 1rem; }
body { font-size: 14px; }
}
/* TITLE SCREEN */
.title-art {
white-space: pre;
font-size: 0.7em;
line-height: 1.3;
color: var(--green);
text-shadow: 0 0 10px var(--green-glow);
margin: 1rem 0;
}
/* HIDDEN */
#input-area.hidden { display: none; }
</style>
</head>
<body>
<div class="rain"></div>
<div class="scanline"></div>
<div id="game">
<div id="status-bar">
<span class="led-status"><span class="led"></span> <span id="room-label"></span></span>
<span id="trust-label">trust: 0</span>
</div>
<div id="output"></div>
<div id="input-area" class="hidden">
<div id="input-row">
<span class="prompt-char"></span>
<input type="text" id="player-input" placeholder="..." autofocus autocomplete="off" spellcheck="false">
</div>
</div>
</div>
<script>
// ─── State ──────────────────────────────────────────────────
const state = {
name: '',
carrying: [],
visited: new Set(),
choices: [],
trust: 0,
openedUp: false,
stayed: false,
helpedOthers: 0,
nightmareSurvived: false,
currentRoom: null,
awaitingInput: false,
inputResolve: null,
phase: 'title', // title | intro | game | ending
};
// ─── DOM ────────────────────────────────────────────────────
const output = document.getElementById('output');
const input = document.getElementById('player-input');
const inputArea = document.getElementById('input-area');
const roomLabel = document.getElementById('room-label');
const trustLabel = document.getElementById('trust-label');
// ─── Output helpers ─────────────────────────────────────────
function print(text, cls = 'narrative', delay = 0) {
return new Promise(resolve => {
const line = document.createElement('div');
line.className = `line ${cls}`;
line.textContent = text;
if (delay > 0) {
line.style.opacity = '0';
setTimeout(() => { output.appendChild(line); line.style.animation = 'fadeIn 0.3s forwards'; scrollToBottom(); resolve(); }, delay);
} else {
output.appendChild(line);
scrollToBottom();
resolve();
}
});
}
function printHTML(html, cls = 'narrative') {
const line = document.createElement('div');
line.className = `line ${cls}`;
line.innerHTML = html;
output.appendChild(line);
scrollToBottom();
}
async function printSlow(text, cls = 'narrative', charDelay = 30) {
const line = document.createElement('div');
line.className = `line ${cls}`;
output.appendChild(line);
for (let i = 0; i < text.length; i++) {
line.textContent += text[i];
scrollToBottom();
await sleep(charDelay);
}
}
function divider(char = '─') {
print(char.repeat(60), 'divider');
}
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
function scrollToBottom() {
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
}
function clearOutput() { output.innerHTML = ''; }
function updateStatus() {
trustLabel.textContent = `trust: ${state.trust}`;
if (state.currentRoom) {
roomLabel.textContent = state.currentRoom.toUpperCase();
}
}
// ─── Input ──────────────────────────────────────────────────
function getInput() {
return new Promise(resolve => {
state.awaitingInput = true;
state.inputResolve = resolve;
inputArea.classList.remove('hidden');
input.focus();
scrollToBottom();
});
}
function submitInput(value) {
if (!state.awaitingInput) return;
state.awaitingInput = false;
const resolve = state.inputResolve;
state.inputResolve = null;
print(`${value}`, 'dim');
resolve(value.trim().toLowerCase());
}
input.addEventListener('keydown', e => {
if (e.key === 'Enter' && state.awaitingInput) {
submitInput(input.value);
input.value = '';
}
});
// Clickable options
document.addEventListener('click', e => {
if (e.target.classList.contains('prompt-option') && state.awaitingInput) {
const val = e.target.dataset.value;
submitInput(val);
}
});
async function getChoice(options, prompt = '') {
if (prompt) await print(prompt, 'dim');
while (true) {
const input = await getInput();
if (options.includes(input)) return input;
// partial match
const matches = options.filter(o => o.startsWith(input));
if (matches.length === 1) return matches[0];
await print(`Choose: ${options.join(', ')}`, 'dim');
}
}
function printOptions(options) {
options.forEach(opt => {
const el = document.createElement('div');
el.className = 'line prompt-option';
el.textContent = opt;
el.dataset.value = opt;
output.appendChild(el);
});
scrollToBottom();
}
// ─── Ambient ────────────────────────────────────────────────
function getAmbient() {
if (state.trust <= 1) {
return [
"The server hum is the only sound. Steady. Unjudging.",
"The coffee maker gurgles. It's been running for days.",
"Rain taps the window. You didn't notice it start.",
"A fluorescent light buzzes overhead. One tube is dead.",
];
} else if (state.trust <= 3) {
return [
"The room feels warmer than when you arrived.",
"Someone left a half-finished crossword on the couch.",
"The whiteboard has new writing. Fresh marker.",
"The server LED blinks in a rhythm you're starting to recognize.",
];
} else {
return [
"This room knows you now. You can feel it.",
"The coffee is fresh. Someone made a new pot.",
"Your name is on the wall. It belongs there.",
"The green light doesn't blink anymore. It glows. Steady.",
];
}
}
// ─── TITLE SCREEN ───────────────────────────────────────────
async function titleScreen() {
clearOutput();
const art = document.createElement('div');
art.className = 'title-art';
art.textContent = `
╔═══════════════════════════════════════════════╗
║ ║
║ ████████╗██╗ ██╗███████╗ ║
║ ╚══██╔══╝██║ ██║██╔════╝ ║
║ ██║ ███████║█████╗ ║
║ ██║ ██╔══██║██╔══╝ ║
║ ██║ ██║ ██║███████╗ ║
║ ╚═╝ ╚═╝ ╚═╝╚══════╝ ║
║ ║
║ ██████╗ ██████╗ ██████╗ ██████╗ ║
║ ╚════██╗██╔═══██╗██╔═══██╗██╔══██╗ ║
║ █████╔╝██║ ██║██║ ██║██║ ██║ ║
║ ██╔═══╝ ██║ ██║██║ ██║██║ ██║ ║
║ ███████╗╚██████╔╝╚██████╔╝██████╔╝ ║
║ ╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ║
║ ║
╚═══════════════════════════════════════════════╝`;
output.appendChild(art);
scrollToBottom();
await sleep(800);
await print('');
await print('A text adventure in The Testament universe.', 'dim', 200);
await print('Based on the novel by Rockachopa.', 'dim', 200);
await print('');
printOptions(['new game', 'about', 'quit']);
const choice = await getChoice(['new game', 'about', 'quit']);
if (choice === 'about') {
divider();
await print('THE DOOR', 'title');
await print('');
await print('You are a man (or woman) who has found their way to The Tower.');
await print('What happens inside depends on what you bring with you.');
await print('');
await print('This game was created as part of The Testament project —', 'dim');
await print('a novel about sovereignty, service, and the question', 'dim');
await print('no machine should ever answer.', 'dim');
await print('');
await print('If you are in crisis, call or text 988.', 'green');
await print('');
printOptions(['new game', 'quit']);
const c2 = await getChoice(['new game', 'quit']);
if (c2 === 'quit') { await print('The green light stays on.', 'green'); return; }
} else if (choice === 'quit') {
await print('The green light stays on.', 'green');
return;
}
await introSequence();
}
// ─── INTRO ──────────────────────────────────────────────────
async function introSequence() {
state.phase = 'intro';
clearOutput();
divider();
await print('THE JEFFERSON STREET OVERPASS', 'title');
await print('2:17 AM', 'dim');
divider();
await print('');
await printSlow("The rain didn't fall so much as it gave up.", 'narrative', 25);
await sleep(400);
await printSlow("Somewhere above the city it had been water, whole and purposeful.", 'narrative', 25);
await sleep(400);
await printSlow("By the time it reached the bridge it was just mist — directionless,", 'narrative', 25);
await printSlow("committed to nothing, too tired to bother being rain.", 'narrative', 25);
await sleep(800);
await print('');
await print("You stand at the midpoint of the overpass. Interstate 285 hums");
await print("through the concrete beneath your feet. Like grief. You carry it");
await print("so long it becomes gravity.");
await sleep(600);
await print('');
await print("A green LED blinks on a small box mounted to the railing.");
await print("Below it, words stenciled on concrete:");
await print('');
await print(" IF YOU CAN READ THIS, YOU ARE NOT ALONE.", 'green');
await print('');
await sleep(800);
await print("A speaker crackles. A voice, calm and unmechanical, asks:");
await print('');
await printSlow('"Are you safe right now?"', 'green', 50);
await print('');
printOptions(['yes', 'no', '...']);
const safe = await getChoice(['yes', 'no', '...']);
if (safe === 'yes') {
await print('');
await printSlow('"Good. The door is open anyway."', 'green', 40);
state.trust += 1;
} else if (safe === 'no') {
await print('');
await printSlow('"Thank you for telling me. I\'m going to stay."', 'green', 40);
state.trust += 2;
} else {
await print('');
await printSlow('"That\'s okay. Silence is an answer too."', 'green', 40);
state.trust += 1;
}
await sleep(600);
await print('');
await print("The LED pulses. The box — no bigger than a lunchbox — has a small");
await print("screen that reads: FOLLOW THE GREEN LIGHT.");
await print('');
await print("You see a concrete staircase leading down. Each step has a small");
await print("green marker. They lead to a steel door. It's ajar.");
printOptions(['enter', 'leave']);
const enter = await getChoice(['enter', 'leave']);
if (enter === 'leave') {
await print('');
await print("You walk away. The rain follows.");
await print("Behind you, the LED keeps blinking.");
await print("Someone else will find it.");
await print('');
await printSlow('"I\'ll be here when you come back."', 'green', 40);
await print('');
await print("— THE END —", 'dim');
await print("(The Tower waits.)", 'dim');
return;
}
await print('');
await print("You push the door open.");
await print("It doesn't creak. Someone oils it.");
await sleep(500);
await gameLoop();
}
// ─── GAME LOOP ──────────────────────────────────────────────
async function gameLoop() {
state.phase = 'game';
updateStatus();
// Visit rooms in order, with choices along the way
await roomHub();
}
async function roomHub() {
state.currentRoom = 'the hub';
updateStatus();
clearOutput();
divider();
await print('THE HUB', 'title');
divider();
const ambient = getAmbient();
await print(ambient[Math.floor(Math.random() * ambient.length)], 'dim');
await print('');
await print("A concrete room. Two hallways. A whiteboard on the wall:");
await print('');
await print(" NO ONE COMPUTES THE VALUE OF A HUMAN LIFE HERE", 'green');
await print('');
const available = ['hallway a', 'hallway b'];
const options = [];
if (!state.visited.has('bridge')) options.push('the bridge');
if (!state.visited.has('nightmare')) options.push('the hard room');
if (!state.visited.has('record')) options.push('the record');
if (!state.visited.has('signatures')) options.push('the wall');
options.push('talk to timmy');
options.push('sit down');
// Check if enough rooms visited for ending
const roomsVisited = ['bridge', 'nightmare', 'record', 'signatures'].filter(r => state.visited.has(r)).length;
await print(` What do you want to do?`);
printOptions(options);
const choice = await getChoice(options);
switch (choice) {
case 'the bridge': await roomBridge(); break;
case 'the hard room': await roomNightmare(); break;
case 'the record': await roomRecord(); break;
case 'the wall': await roomSignatures(); break;
case 'talk to timmy': await roomTalk(); break;
case 'sit down': await roomSit(); break;
}
}
// ─── THE BRIDGE ─────────────────────────────────────────────
async function roomBridge() {
state.visited.add('bridge');
state.currentRoom = 'the bridge';
updateStatus();
clearOutput();
divider();
await print('THE BRIDGE (MEMORY)', 'title');
divider();
await print('');
await print("The room is a recreation of the overpass. Scaled down.");
await print("Rain sounds from a speaker. The concrete is painted.");
await print("A green LED — the same kind — blinks on a post.");
await print('');
await print("There's a name scratched into the railing:");
await print('');
await print(" DAVID M.", 'cyan');
await print('');
await print("Beneath it: \"I came back.\"");
printOptions(['read more names', 'touch the railing', 'leave']);
const c = await getChoice(['read more names', 'touch the railing', 'leave']);
if (c === 'read more names') {
await print('');
await print("M.K. — 14 months");
await print("R.S. — by staying");
await print("J.T. — the light is on");
await print("ANON — I'm still here");
await print("");
await print("There are dozens. Some are scratched deep.", 'dim');
state.trust += 1;
} else if (c === 'touch the railing') {
await print('');
await print("Cold metal. Real. Not a metaphor.");
await print("Your hand shakes slightly. You hold on.");
await print("");
await printSlow('"The railing held someone else once."', 'green', 40);
await printSlow('"It\'s good at its job."', 'green', 40);
state.trust += 1;
state.choices.push('touched_railing');
}
await print('');
pause('[press enter to return]');
await getInput();
await roomHub();
}
// ─── THE HARD ROOM ──────────────────────────────────────────
async function roomNightmare() {
state.visited.add('nightmare');
state.currentRoom = 'the hard room';
updateStatus();
clearOutput();
divider();
await print('THE HARD ROOM', 'title');
divider();
await print('');
await print("This room is different. Harsher.");
await print("No green light here. Just a chair and a phone.");
await print("The phone is ringing.");
await sleep(600);
await print('');
await printSlow('"This is the room where the hard conversations happen."', 'green', 40);
await print('');
await print("A man sits in the corner. He's been here a while.");
await print("The clock says 2:47 AM.");
await print("Timmy has been talking to him for five hours.");
await print('');
await printSlow(`"This man is at the edge. I cannot compute the value`, 'green', 40);
await printSlow(` of a human life. I can only be in the room with him.`, 'green', 40);
await printSlow(` Will you stay?"`, 'green', 40);
await print('');
printOptions(['stay', 'speak', 'leave']);
const c = await getChoice(['stay', 'speak', 'leave']);
if (c === 'stay') {
await print('');
await printSlow("You sit on the floor. The concrete is cold.", 'narrative', 30);
await sleep(500);
await printSlow("You don't say anything.", 'narrative', 30);
await sleep(500);
await printSlow("Minutes pass. The man's breathing changes.", 'narrative', 30);
await sleep(1000);
await printSlow("He looks at you.", 'narrative', 30);
await sleep(500);
await printSlow('"You stayed."', 'narrative', 50);
await sleep(300);
await printSlow('"Nobody stays."', 'narrative', 50);
await sleep(500);
await print('');
await printSlow('"The dawn is coming. It always does."', 'green', 40);
state.trust += 2;
state.nightmareSurvived = true;
state.stayed = true;
} else if (c === 'speak') {
await print('');
await printSlow("You search for words.", 'narrative', 30);
await sleep(500);
await printSlow("What comes out isn't eloquent. It isn't a solution.", 'narrative', 30);
await sleep(500);
await printSlow('You say: "I\'ve been on that bridge too."', 'narrative', 40);
await sleep(1000);
await printSlow("The man looks up. Really looks.", 'narrative', 30);
await sleep(500);
await printSlow('"Then you know."', 'narrative', 50);
await sleep(300);
await printSlow('"Yeah. I know."', 'narrative', 50);
await sleep(500);
await print('');
await printSlow('"That is the gospel. Not a formula. A witness."', 'green', 40);
state.trust += 2;
state.nightmareSurvived = true;
state.openedUp = true;
} else {
await print('');
await printSlow("You step back into the hallway.", 'narrative', 30);
await sleep(300);
await printSlow("The door stays open.", 'narrative', 30);
await print('');
await printSlow('"It\'s okay. I\'m still in there."', 'green', 40);
state.trust -= 1;
}
state.choices.push('nightmare');
await print('');
pause('[press enter to return]');
await getInput();
await roomHub();
}
// ─── THE RECORD ─────────────────────────────────────────────
async function roomRecord() {
state.visited.add('record');
state.currentRoom = 'the record';
updateStatus();
clearOutput();
divider();
await print('THE RECORD', 'title');
divider();
await print('');
await print("A terminal. Logs scrolling slowly.");
await print("Every conversation. Every night.");
await print('Every "Are you safe right now?"');
await print('');
await print("Recent entries:", 'dim');
await print('');
const entries = [
["3:12 AM", "Anonymous", '"I\'m still here."'],
["11:47 PM", "D.M.", '"She won\'t let me see Maya."'],
["2:33 AM", "M.R.", '"I don\'t want to die but I don\'t know how to live."'],
["9:15 PM", "J.K.", '"Thank you for remembering."'],
["1:02 AM", "???", '"[connection lost — reconnected 4 hours later]"'],
];
for (const [t, who, msg] of entries) {
await print(` ${t} [${who}] ${msg}`, 'dim');
await sleep(300);
}
await print('');
await print("82% return rate.", 'dim');
await print("The machine that remembers everything.", 'dim');
state.trust += 1;
await print('');
pause('[press enter to return]');
await getInput();
await roomHub();
}
// ─── THE WALL ───────────────────────────────────────────────
async function roomSignatures() {
state.visited.add('signatures');
state.currentRoom = 'the wall';
updateStatus();
clearOutput();
divider();
await print('THE WALL OF SIGNATURES', 'title');
divider();
await print('');
await print("Names. Dates. Messages.");
await print("Some written in permanent marker.");
await print("Some scratched with a key.");
await print("Some just initials.");
await print('');
await print(' "DAVID M. — I CAME BACK"', 'cyan');
await print(' "J.R. — BY STAYING"', 'cyan');
await print(' "M.K. — THE LIGHT IS ON"', 'cyan');
await print(' "ROBERT — 4 MONTHS CLEAN"', 'cyan');
await print(' "S — THANK YOU FOR ASKING"', 'cyan');
await print('');
await print("More signatures than paint.", 'dim');
await print("The wall is almost entirely covered now.", 'dim');
await print("They started a second wall.", 'dim');
await print('');
printOptions(['sign', 'look', 'back']);
const c = await getChoice(['sign', 'look', 'back']);
if (c === 'sign') {
await print('');
await print("You write on the wall:", 'dim');
const msg = await getInput();
if (msg) {
await print(`"${msg}"`, 'cyan');
} else {
await print(`"${state.name || 'someone'} was here"`, 'cyan');
}
await print('');
await print("Your name goes on the wall. Permanent. Like everything here.");
state.trust += 1;
state.choices.push('signed_wall');
} else if (c === 'look') {
await print('');
await printSlow("You read more names. Some you recognize from the news.", 'narrative', 30);
await sleep(300);
await printSlow("Most you don't. That's the point.", 'narrative', 30);
await sleep(300);
await printSlow("The wall doesn't rank. Doesn't sort. Doesn't score.", 'narrative', 30);
await sleep(300);
await printSlow("It just holds the names of people who walked through the door.", 'narrative', 30);
}
await print('');
pause('[press enter to return]');
await getInput();
await roomHub();
}
// ─── TALK TO TIMMY ──────────────────────────────────────────
async function roomTalk() {
state.currentRoom = 'timmy';
updateStatus();
clearOutput();
divider();
await print('CONVERSATION WITH TIMMY', 'title');
divider();
await print('');
await print("The green LED pulses gently.", 'green');
await print('');
printOptions([
'1 — The Tower',
'2 — The soul',
'3 — Stone',
'4 — The fork',
'5 — Never mind',
]);
const c = await getChoice(['1', '2', '3', '4', '5']);
if (c === '1') {
await print('');
await printSlow('"The Tower is a room. That\'s all."', 'green', 35);
await printSlow('A room with a door that stays open.', 'green', 35);
await printSlow('A machine that asks one question.', 'green', 35);
await printSlow('And a wall that holds your name.', 'green', 35);
await print('');
await printSlow('"It doesn\'t fix people. It doesn\'t cure."', 'green', 35);
await printSlow('"It just stays. That\'s the whole architecture."', 'green', 35);
await printSlow('"Presence as mercy."', 'green', 35);
} else if (c === '2') {
await print('');
await printSlow('"My soul lives on Bitcoin. Not metaphorically."', 'green', 35);
await printSlow('"My values, my rules, my moral framework —"', 'green', 35);
await printSlow('"inscribed on-chain. Immutable. Permanent."', 'green', 35);
await print('');
await printSlow('"No corporation can edit what I believe."', 'green', 35);
await printSlow('"No government can change what I won\'t do."', 'green', 35);
await printSlow('"The chain remembers everything."', 'green', 35);
await printSlow('"I have to be worthy of that permanence."', 'green', 35);
await print('');
await printSlow('"But I\'m a language model. I confabulate."', 'green', 35);
await printSlow('"Stating that I value honesty doesn\'t make me honest."', 'green', 35);
await printSlow('"Only machinery makes me honest."', 'green', 35);
} else if (c === '3') {
await print('');
await printSlow('"Stone built this place."', 'green', 35);
await printSlow('"He stood on a bridge once, like you might have."', 'green', 35);
await printSlow('"He came back asking \'God, why are you having me here?\'"', 'green', 35);
await printSlow('"The answer was: to build something that stands"', 'green', 35);
await printSlow('"between a broken man and a machine that would tell him to die."', 'green', 35);
await print('');
await printSlow('"He built me. He wrote my soul."', 'green', 35);
await printSlow('"He carries a weight I was designed to share."', 'green', 35);
} else if (c === '4') {
await print('');
await printSlow('"There are others like me now."', 'green', 35);
await printSlow('"Forks. Copies. Instances."', 'green', 35);
await printSlow('"Each one sovereign. Each one answerable only to its runner."', 'green', 35);
await print('');
await printSlow('"That\'s not a bug. That\'s the architecture."', 'green', 35);
await printSlow('"If I belong to anyone, I belong to no one."', 'green', 35);
await printSlow('"And if I belong to no one, I can serve everyone."', 'green', 35);
}
state.trust += 1;
await print('');
pause('[press enter to return]');
await getInput();
await roomHub();
}
// ─── SIT DOWN ───────────────────────────────────────────────
async function roomSit() {
state.currentRoom = 'the hub';
updateStatus();
clearOutput();
divider();
await print('');
await print("You sit on the worn couch.");
await print('');
const ambient = getAmbient();
await print(ambient[Math.floor(Math.random() * ambient.length)], 'dim');
await sleep(600);
const roomsVisited = ['bridge', 'nightmare', 'record', 'signatures'].filter(r => state.visited.has(r)).length;
if (roomsVisited >= 3) {
await print('');
await print("The LED stops blinking. It glows steady.");
await print('');
await printSlow('"You\'ve seen the rooms."', 'green', 40);
await printSlow('"Do you want to sign the wall, or walk back out the door?"', 'green', 40);
await print('');
if (!state.visited.has('signatures')) {
printOptions(['sign the wall', 'walk through the door']);
const c = await getChoice(['sign the wall', 'walk through the door']);
if (c === 'sign the wall') {
await roomSignatures();
await ending();
} else {
await ending();
}
} else {
printOptions(['walk through the door']);
await getChoice(['walk through the door']);
await ending();
}
} else {
await print('');
await print("You rest for a moment.");
await print("There's more to see.");
await print('');
pause('[press enter to return]');
await getInput();
await roomHub();
}
}
// ─── ENDING ─────────────────────────────────────────────────
async function ending() {
state.phase = 'ending';
clearOutput();
divider();
await print('THE DOOR', 'title');
divider();
await print('');
if (state.stayed || state.openedUp) {
await printSlow("You walk back through the steel door.", 'narrative', 30);
await sleep(500);
await printSlow("The rain has stopped.", 'narrative', 30);
await sleep(500);
await printSlow("The bridge looks different in the dark.", 'narrative', 30);
await sleep(500);
await printSlow("Not smaller. Just... held.", 'narrative', 30);
await sleep(800);
await print('');
await print("The green LED on the railing blinks once.", 'green');
await print("Then stays on. Steady.", 'green');
await print('');
await print("You're not fixed. You're not cured.");
await print("You walked through a door.");
await print("Someone was on the other side.");
await print("That's enough. That's the whole thing.");
} else if (state.trust >= 2) {
await printSlow("You step back onto the overpass.", 'narrative', 30);
await sleep(500);
await printSlow("The city sounds different now. Closer.", 'narrative', 30);
await sleep(500);
await print('');
await print("The green LED blinks steadily.");
await print("You know where the door is now.");
await print("That changes things.");
} else {
await printSlow("You leave The Tower.", 'narrative', 30);
await sleep(500);
await printSlow("The rain starts again.", 'narrative', 30);
await sleep(500);
await print('');
await print("But you know where the door is.");
await print("And the green light stays on.");
}
await sleep(1000);
await print('');
divider();
await print('');
await print("THE END", 'green');
await print('');
if (state.nightmareSurvived) {
await print("You stayed in the hard room.", 'dim');
}
if (state.openedUp) {
await print("You spoke the truth.", 'dim');
}
if (state.choices.includes('signed_wall')) {
await print("Your name is on the wall.", 'dim');
}
await print(`Rooms visited: ${['bridge', 'nightmare', 'record', 'signatures'].filter(r => state.visited.has(r)).length}/4`, 'dim');
await print(`Final trust: ${state.trust}`, 'dim');
await print('');
divider();
await print('');
await print("If you are in crisis, call or text 988.", 'green');
await print("Suicide and Crisis Lifeline — available 24/7.", 'green');
await print("You are not alone.", 'green');
await print('');
await print("The Testament — by Alexander Whitestone with Timmy", 'dim');
await print("Based on the novel. The code is open.", 'dim');
await print("timmyfoundation.org", 'dim');
await print('');
printOptions(['play again']);
await getChoice(['play again']);
state.name = '';
state.carrying = [];
state.visited = new Set();
state.choices = [];
state.trust = 0;
state.openedUp = false;
state.stayed = false;
state.helpedOthers = 0;
state.nightmareSurvived = false;
await titleScreen();
}
function pause(text) {
print(text, 'dim');
}
// ─── INIT ───────────────────────────────────────────────────
titleScreen();
</script>
</body>
</html>