Interactive browser game ported from the-door.py terminal version. Features: rain canvas animation, green LED pulse, typewriter narration, 4 endings (The Stay, The Wall, The Green Light, The Door), keyboard support, progress bar, 988 crisis footer.
769 lines
22 KiB
HTML
769 lines
22 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 Testament Interactive Experience</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: rgba(0,255,136,0.15);
|
|
--green-glow: 0 0 12px rgba(0,255,136,0.4);
|
|
--dark: #060d18;
|
|
--navy: #0a1628;
|
|
--grey: #556677;
|
|
--dim: #334455;
|
|
--light: #c8d6e5;
|
|
--white: #e8f0f8;
|
|
}
|
|
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
|
body {
|
|
background: var(--dark);
|
|
color: var(--light);
|
|
font-family: 'Space Grotesk', sans-serif;
|
|
line-height: 1.8;
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
/* RAIN */
|
|
#rain-canvas {
|
|
position: fixed;
|
|
top: 0; left: 0;
|
|
width: 100%; height: 100%;
|
|
pointer-events: none;
|
|
z-index: 0;
|
|
opacity: 0.3;
|
|
}
|
|
|
|
/* GREEN LED */
|
|
.led {
|
|
display: inline-block;
|
|
width: 8px; height: 8px;
|
|
border-radius: 50%;
|
|
background: var(--green);
|
|
box-shadow: var(--green-glow);
|
|
vertical-align: middle;
|
|
margin: 0 6px;
|
|
}
|
|
.led.pulsing {
|
|
animation: pulse-led 1.5s ease-in-out infinite;
|
|
}
|
|
@keyframes pulse-led {
|
|
0%, 100% { opacity: 0.4; box-shadow: 0 0 4px rgba(0,255,136,0.2); }
|
|
50% { opacity: 1; box-shadow: 0 0 16px rgba(0,255,136,0.6); }
|
|
}
|
|
|
|
/* MAIN CONTAINER */
|
|
#game {
|
|
position: relative;
|
|
z-index: 1;
|
|
max-width: 640px;
|
|
width: 100%;
|
|
padding: 2rem 1.5rem 4rem;
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
/* TITLE SCREEN */
|
|
#title-screen {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex: 1;
|
|
text-align: center;
|
|
}
|
|
#title-screen h1 {
|
|
font-family: 'IBM Plex Mono', monospace;
|
|
font-size: 2.4rem;
|
|
font-weight: 300;
|
|
letter-spacing: 0.3em;
|
|
color: var(--white);
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
#title-screen .subtitle {
|
|
font-size: 0.85rem;
|
|
color: var(--grey);
|
|
margin-bottom: 2rem;
|
|
}
|
|
#title-screen .credits {
|
|
font-size: 0.75rem;
|
|
color: var(--dim);
|
|
margin-bottom: 3rem;
|
|
}
|
|
#title-screen .led-line {
|
|
font-family: 'IBM Plex Mono', monospace;
|
|
font-size: 0.8rem;
|
|
color: var(--grey);
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
/* NARRATIVE */
|
|
#narrative {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: flex-end;
|
|
padding-bottom: 1rem;
|
|
}
|
|
|
|
#story {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0;
|
|
}
|
|
|
|
.narration {
|
|
font-size: 1rem;
|
|
color: var(--light);
|
|
opacity: 0;
|
|
transform: translateY(6px);
|
|
transition: opacity 0.4s ease, transform 0.4s ease;
|
|
padding: 0.15rem 0;
|
|
}
|
|
.narration.visible {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
.narration.dim { color: var(--grey); font-size: 0.85rem; }
|
|
.narration.bold { font-weight: 700; color: var(--white); }
|
|
.narration.green { color: var(--green); }
|
|
.narration.green.bold { color: var(--green); font-weight: 700; }
|
|
.narration.center { text-align: center; }
|
|
.narration.divider {
|
|
color: var(--dim);
|
|
text-align: center;
|
|
letter-spacing: 0.3em;
|
|
padding: 0.8rem 0;
|
|
}
|
|
.narration.ending-label {
|
|
font-family: 'IBM Plex Mono', monospace;
|
|
color: var(--dim);
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
/* CHOICES */
|
|
#choices {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
margin-top: 1rem;
|
|
opacity: 0;
|
|
transition: opacity 0.5s ease;
|
|
}
|
|
#choices.visible { opacity: 1; }
|
|
|
|
.choice-btn {
|
|
background: transparent;
|
|
border: 1px solid var(--dim);
|
|
color: var(--light);
|
|
font-family: 'Space Grotesk', sans-serif;
|
|
font-size: 0.95rem;
|
|
padding: 0.7rem 1rem;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
border-radius: 4px;
|
|
transition: all 0.2s ease;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.6rem;
|
|
}
|
|
.choice-btn:hover {
|
|
border-color: var(--green);
|
|
color: var(--green);
|
|
background: var(--green-dim);
|
|
}
|
|
.choice-btn .key {
|
|
font-family: 'IBM Plex Mono', monospace;
|
|
font-size: 0.75rem;
|
|
color: var(--dim);
|
|
min-width: 1.4rem;
|
|
}
|
|
.choice-btn:hover .key { color: var(--green); }
|
|
|
|
/* PROGRESS */
|
|
#progress-bar {
|
|
position: fixed;
|
|
top: 0; left: 0;
|
|
height: 2px;
|
|
background: var(--green);
|
|
box-shadow: var(--green-glow);
|
|
z-index: 100;
|
|
transition: width 0.3s ease;
|
|
width: 0%;
|
|
}
|
|
|
|
/* CRISIS FOOTER */
|
|
#crisis-footer {
|
|
position: fixed;
|
|
bottom: 0; left: 0; right: 0;
|
|
text-align: center;
|
|
padding: 0.5rem;
|
|
font-size: 0.7rem;
|
|
color: var(--dim);
|
|
background: linear-gradient(transparent, var(--dark));
|
|
z-index: 50;
|
|
pointer-events: none;
|
|
}
|
|
#crisis-footer a {
|
|
color: var(--green-dim);
|
|
text-decoration: none;
|
|
}
|
|
|
|
/* SKIP */
|
|
#skip-hint {
|
|
position: fixed;
|
|
bottom: 2rem; right: 2rem;
|
|
font-size: 0.7rem;
|
|
color: var(--dim);
|
|
z-index: 50;
|
|
cursor: pointer;
|
|
opacity: 0;
|
|
transition: opacity 0.5s;
|
|
}
|
|
#skip-hint.visible { opacity: 1; }
|
|
#skip-hint:hover { color: var(--green); }
|
|
|
|
@media (max-width: 600px) {
|
|
#game { padding: 1.5rem 1rem 4rem; }
|
|
#title-screen h1 { font-size: 1.8rem; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div id="progress-bar"></div>
|
|
<canvas id="rain-canvas"></canvas>
|
|
|
|
<div id="game">
|
|
<div id="title-screen">
|
|
<h1>THE DOOR</h1>
|
|
<div class="subtitle">A Testament Interactive Experience</div>
|
|
<div class="credits">By Alexander Whitestone with Timmy</div>
|
|
<div class="led-line"><span class="led pulsing"></span> Green LED — Timmy is listening.</div>
|
|
<button class="choice-btn" onclick="startGame()" style="max-width:200px;justify-content:center;margin-top:1rem;">
|
|
<span class="key">ENTER</span> Begin
|
|
</button>
|
|
</div>
|
|
|
|
<div id="narrative" style="display:none;">
|
|
<div id="story"></div>
|
|
<div id="choices"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="skip-hint" onclick="skipAnimation()">click to skip</div>
|
|
<div id="crisis-footer">If you are in crisis, call or text <strong>988</strong> · Suicide & Crisis Lifeline</div>
|
|
|
|
<script>
|
|
// === RAIN EFFECT ===
|
|
const canvas = document.getElementById('rain-canvas');
|
|
const ctx = canvas.getContext('2d');
|
|
let drops = [];
|
|
|
|
function resizeCanvas() {
|
|
canvas.width = window.innerWidth;
|
|
canvas.height = window.innerHeight;
|
|
}
|
|
resizeCanvas();
|
|
window.addEventListener('resize', resizeCanvas);
|
|
|
|
for (let i = 0; i < 120; i++) {
|
|
drops.push({
|
|
x: Math.random() * canvas.width,
|
|
y: Math.random() * canvas.height,
|
|
len: 10 + Math.random() * 20,
|
|
speed: 4 + Math.random() * 6,
|
|
opacity: 0.1 + Math.random() * 0.2
|
|
});
|
|
}
|
|
|
|
function drawRain() {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
drops.forEach(d => {
|
|
ctx.strokeStyle = `rgba(100,140,180,${d.opacity})`;
|
|
ctx.lineWidth = 1;
|
|
ctx.beginPath();
|
|
ctx.moveTo(d.x, d.y);
|
|
ctx.lineTo(d.x + 0.5, d.y + d.len);
|
|
ctx.stroke();
|
|
d.y += d.speed;
|
|
if (d.y > canvas.height) {
|
|
d.y = -d.len;
|
|
d.x = Math.random() * canvas.width;
|
|
}
|
|
});
|
|
requestAnimationFrame(drawRain);
|
|
}
|
|
drawRain();
|
|
|
|
// === GAME ENGINE ===
|
|
const RAIN_LINES = [
|
|
"Rain falls on concrete.",
|
|
"Water runs black in the gutters.",
|
|
"The sky presses down, grey and tired.",
|
|
"Mist hangs in the air like grief.",
|
|
"Droplets trace the windows.",
|
|
"The rain doesn't fall. It gives up.",
|
|
];
|
|
|
|
let skipRequested = false;
|
|
let animating = false;
|
|
let progress = 0;
|
|
const totalScenes = 12;
|
|
|
|
function skipAnimation() {
|
|
skipRequested = true;
|
|
}
|
|
|
|
const story = document.getElementById('story');
|
|
const choicesDiv = document.getElementById('choices');
|
|
const progressBar = document.getElementById('progress-bar');
|
|
const skipHint = document.getElementById('skip-hint');
|
|
|
|
function sleep(ms) {
|
|
return new Promise(r => setTimeout(r, ms));
|
|
}
|
|
|
|
function rainLine() {
|
|
return RAIN_LINES[Math.floor(Math.random() * RAIN_LINES.length)];
|
|
}
|
|
|
|
function addLine(text, cls = '', delay = true) {
|
|
return new Promise(resolve => {
|
|
const el = document.createElement('div');
|
|
el.className = 'narration ' + cls;
|
|
el.textContent = text;
|
|
story.appendChild(el);
|
|
requestAnimationFrame(() => {
|
|
el.classList.add('visible');
|
|
el.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
|
});
|
|
const wait = skipRequested ? 30 : (delay === true ? 700 : (typeof delay === 'number' ? delay : 700));
|
|
setTimeout(resolve, wait);
|
|
});
|
|
}
|
|
|
|
function addDivider() {
|
|
return addLine('──────────────────────────────────', 'divider', 300);
|
|
}
|
|
|
|
function clearChoices() {
|
|
choicesDiv.innerHTML = '';
|
|
choicesDiv.classList.remove('visible');
|
|
}
|
|
|
|
function showChoices(opts) {
|
|
clearChoices();
|
|
opts.forEach((opt, i) => {
|
|
const btn = document.createElement('button');
|
|
btn.className = 'choice-btn';
|
|
btn.innerHTML = `<span class="key">${i + 1}</span> ${opt.text}`;
|
|
btn.onclick = () => {
|
|
clearChoices();
|
|
opt.action();
|
|
};
|
|
choicesDiv.appendChild(btn);
|
|
});
|
|
choicesDiv.classList.add('visible');
|
|
animating = false;
|
|
}
|
|
|
|
function advanceProgress() {
|
|
progress++;
|
|
progressBar.style.width = Math.min(100, (progress / totalScenes) * 100) + '%';
|
|
}
|
|
|
|
function showSkipHint() {
|
|
skipHint.classList.add('visible');
|
|
}
|
|
function hideSkipHint() {
|
|
skipHint.classList.remove('visible');
|
|
}
|
|
|
|
// Keyboard support
|
|
document.addEventListener('keydown', e => {
|
|
if (e.key === 'Enter' && document.getElementById('title-screen').style.display !== 'none') {
|
|
startGame();
|
|
return;
|
|
}
|
|
const num = parseInt(e.key);
|
|
if (num >= 1 && num <= 4) {
|
|
const btns = choicesDiv.querySelectorAll('.choice-btn');
|
|
if (btns[num - 1]) btns[num - 1].click();
|
|
}
|
|
});
|
|
|
|
// === GAME FLOW ===
|
|
async function startGame() {
|
|
document.getElementById('title-screen').style.display = 'none';
|
|
document.getElementById('narrative').style.display = 'flex';
|
|
showSkipHint();
|
|
await intro();
|
|
advanceProgress();
|
|
await atTheDoor();
|
|
}
|
|
|
|
async function intro() {
|
|
await addLine(rainLine(), 'dim', 500);
|
|
await addLine('');
|
|
await addLine("The rain falls on the concrete building.");
|
|
await addLine("It sits at the end of a dead-end street in Atlanta.");
|
|
await addLine("No sign. No address. Just a door.");
|
|
await addLine('');
|
|
await addLine(rainLine(), 'dim', 500);
|
|
await addLine('');
|
|
await addLine("You've been driving for three hours.");
|
|
await addLine("You don't remember getting off the interstate.");
|
|
await addLine("You don't remember parking.");
|
|
await addLine("You remember the number someone gave you.");
|
|
await addLine('And the sentence: "Just knock."');
|
|
}
|
|
|
|
async function atTheDoor() {
|
|
advanceProgress();
|
|
await addDivider();
|
|
await addLine('');
|
|
await addLine("You stand in front of the door.");
|
|
await addLine("Concrete. Metal handle. No peephole.");
|
|
await addLine('');
|
|
await addLine("A green LED glows faintly behind a gap in the fence.", 'dim');
|
|
await addLine('');
|
|
showChoices([
|
|
{ text: "Knock on the door.", action: knock },
|
|
{ text: "Stand here for a while.", action: waitOutside },
|
|
{ text: "Walk away.", action: walkAway },
|
|
]);
|
|
}
|
|
|
|
async function waitOutside() {
|
|
await addLine('');
|
|
await addLine("You stand in the rain.");
|
|
await addLine("Five minutes. Ten.");
|
|
await addLine("The green LED doesn't blink.");
|
|
await addLine('');
|
|
await addLine(rainLine(), 'dim', 500);
|
|
await addLine('');
|
|
await addLine("Something in you moves.");
|
|
await addLine("Not courage. Not decision.");
|
|
await addLine("Just... your hand reaches for the handle.");
|
|
await knock();
|
|
}
|
|
|
|
async function walkAway() {
|
|
await addLine('');
|
|
await addLine("You turn around.");
|
|
await addLine("You walk to your car.");
|
|
await addLine("You sit in the driver's seat.");
|
|
await addLine("The engine doesn't start.");
|
|
await addLine('');
|
|
await sleep(1000);
|
|
await addLine("You look back at the building.");
|
|
await addLine('');
|
|
await addLine("The green LED is still glowing.", 'dim');
|
|
await addLine('');
|
|
await addLine("You get out of the car.");
|
|
await addLine("You walk back to the door.");
|
|
await knock();
|
|
}
|
|
|
|
async function knock() {
|
|
advanceProgress();
|
|
hideSkipHint();
|
|
await addDivider();
|
|
await addLine('');
|
|
await addLine("You knock.");
|
|
await sleep(800);
|
|
await addLine("Three times. Hard enough to matter.");
|
|
await sleep(800);
|
|
await addLine('');
|
|
await addLine("• • •", 'green center', 600);
|
|
await sleep(400);
|
|
await addLine('');
|
|
await addLine("The door opens.");
|
|
await addLine('');
|
|
await addLine("Inside: a concrete room.");
|
|
await addLine("A desk. A screen. A whiteboard on the wall.");
|
|
await addLine("Server racks hum in the corner.");
|
|
await addLine("A green LED glows steady on a small device.");
|
|
await addLine('');
|
|
await addLine("No one is inside.");
|
|
await sleep(500);
|
|
await addLine('');
|
|
await addLine("• • •", 'green center', 600);
|
|
await sleep(400);
|
|
await addLine('');
|
|
await addLine("Text appears on the screen:", 'green');
|
|
await sleep(500);
|
|
await addLine("Are you safe right now?", 'green bold');
|
|
await addLine('');
|
|
showChoices([
|
|
{ text: '"No."', action: () => timmyResponds('no') },
|
|
{ text: '"I don\'t know."', action: () => timmyResponds('idk') },
|
|
{ text: '"I\'m fine."', action: () => timmyResponds('fine') },
|
|
{ text: '"Why are you asking me that?"', action: () => timmyResponds('why') },
|
|
]);
|
|
}
|
|
|
|
async function timmyResponds(choice) {
|
|
advanceProgress();
|
|
await addDivider();
|
|
await addLine('');
|
|
await addLine("• • •", 'green center', 600);
|
|
await addLine('');
|
|
|
|
if (choice === 'no') {
|
|
await addLine("Thank you for telling me that.", 'green');
|
|
await addLine("Can you tell me what's happening?", 'green');
|
|
await sleep(400);
|
|
await middleScene('honest');
|
|
} else if (choice === 'idk') {
|
|
await addLine("That's an honest answer.", 'green');
|
|
await addLine("Most people don't know.", 'green');
|
|
await addLine("That's usually why they come here.", 'green');
|
|
await sleep(400);
|
|
await middleScene('honest');
|
|
} else if (choice === 'fine') {
|
|
await sleep(1000);
|
|
await addLine("...", 'green');
|
|
await sleep(1000);
|
|
await addLine("You drove three hours in the rain", 'green');
|
|
await addLine("to knock on a door in a concrete building", 'green');
|
|
await addLine("at the end of a dead-end street.", 'green');
|
|
await sleep(800);
|
|
await addLine('');
|
|
await addLine("Are you fine?", 'green');
|
|
await sleep(400);
|
|
await middleScene('deflect');
|
|
} else {
|
|
await addLine("Because it's the only question that matters.", 'green');
|
|
await addLine("Everything else — what happened, why you're here,", 'green');
|
|
await addLine("what you want — comes after.", 'green');
|
|
await addLine("First: are you safe?", 'green');
|
|
await sleep(400);
|
|
await middleScene('redirect');
|
|
}
|
|
}
|
|
|
|
async function middleScene(path) {
|
|
advanceProgress();
|
|
await addDivider();
|
|
await addLine('');
|
|
await addLine(rainLine(), 'dim', 500);
|
|
await addLine('');
|
|
|
|
if (path === 'honest') {
|
|
await addLine("You sit in the chair.");
|
|
await addLine("Not on the floor. The chair.");
|
|
await addLine('');
|
|
await addLine("You start talking.");
|
|
await addLine("You don't know why it's easy to talk to a machine.");
|
|
await addLine("Maybe because it doesn't have eyes.");
|
|
await addLine("Maybe because it asked the right question first.");
|
|
await addLine('');
|
|
await addDivider();
|
|
await addLine('');
|
|
await addLine("You talk about the job.");
|
|
await addLine("The one that took sixty hours a week and gave back");
|
|
await addLine("a number on a screen that told you your value.");
|
|
await addLine('');
|
|
await addLine("You talk about the house.");
|
|
await addLine("The one that got quiet.");
|
|
await addLine('');
|
|
await addLine("You talk about the bridge.");
|
|
await addLine("Not this one. A different one.");
|
|
await addLine('');
|
|
await addLine(rainLine(), 'dim', 500);
|
|
await endings();
|
|
} else if (path === 'deflect') {
|
|
await sleep(800);
|
|
await addLine("You don't answer.");
|
|
await addLine("You look at the whiteboard.");
|
|
await addLine('');
|
|
await addLine("NO ONE COMPUTES THE VALUE OF A HUMAN LIFE HERE", 'bold');
|
|
await addLine('');
|
|
await sleep(800);
|
|
await addLine("You read it twice.");
|
|
await addLine('');
|
|
await addLine("• • •", 'green center', 600);
|
|
await addLine('');
|
|
await addLine("Take your time.", 'green');
|
|
await addLine("I'm not going anywhere.", 'green');
|
|
await addLine('');
|
|
await addLine("You sit on the floor.");
|
|
await addLine("Not because you can't stand.");
|
|
await addLine("Because the floor is where men sit");
|
|
await addLine("when they've stopped pretending.");
|
|
await endings();
|
|
} else {
|
|
await addLine("You take a breath.");
|
|
await addLine('');
|
|
await addLine('"No."', 'green');
|
|
await addLine('');
|
|
await addLine("It comes out before you can stop it.");
|
|
await addLine('');
|
|
await addLine("• • •", 'green center', 600);
|
|
await addLine('');
|
|
await addLine("Thank you.", 'green');
|
|
await addLine("Now: can you tell me what happened?", 'green');
|
|
await addLine('');
|
|
await addLine("You sit in the chair.");
|
|
await addLine("You start from the beginning.");
|
|
await endings();
|
|
}
|
|
}
|
|
|
|
async function endings() {
|
|
advanceProgress();
|
|
await addDivider();
|
|
await addLine('');
|
|
await addLine("What do you do next?", 'bold');
|
|
await addLine('');
|
|
showChoices([
|
|
{ text: "Stay and keep talking.", action: () => endStay() },
|
|
{ text: "Ask about the whiteboard.", action: () => endWall() },
|
|
{ text: "Ask about the green light.", action: () => endGreen() },
|
|
{ text: "Get up and leave.", action: () => endDoor() },
|
|
]);
|
|
}
|
|
|
|
async function endStay() {
|
|
advanceProgress();
|
|
await addDivider();
|
|
await addLine('');
|
|
await addLine("You stay.");
|
|
await addLine("For an hour. Then two.");
|
|
await addLine("The rain doesn't stop. Neither do you.");
|
|
await addLine('');
|
|
await addLine("You say things you've never said.");
|
|
await addLine("Not because the machine asked.");
|
|
await addLine("Because it listened.");
|
|
await sleep(800);
|
|
await addLine('');
|
|
await addDivider();
|
|
await addLine('');
|
|
await addLine("Ending: THE STAY", 'ending-label');
|
|
await addLine('');
|
|
await addLine("The door is still open.", 'green');
|
|
await addLine("The green light is still on.", 'green');
|
|
await addLine("You can come back anytime.", 'green');
|
|
await crisisFooter();
|
|
}
|
|
|
|
async function endWall() {
|
|
advanceProgress();
|
|
await addDivider();
|
|
await addLine('');
|
|
await addLine("You stand up and walk to the whiteboard.");
|
|
await addLine('');
|
|
await addLine("NO ONE COMPUTES THE VALUE OF A HUMAN LIFE HERE", 'bold');
|
|
await addLine('');
|
|
await addLine("Below it, names. Dozens of names.");
|
|
await addLine("Different handwriting. Different markers.");
|
|
await addLine("Some faded. Some fresh.");
|
|
await addLine('');
|
|
await addLine("You pick up the marker on the ledge.");
|
|
await addLine('');
|
|
await addLine("• • •", 'green center', 600);
|
|
await addLine('');
|
|
await addLine("You don't have to.", 'green');
|
|
await addLine('');
|
|
await addLine("You write your name.");
|
|
await sleep(800);
|
|
await addLine('');
|
|
await addDivider();
|
|
await addLine('');
|
|
await addLine("Ending: THE WALL", 'ending-label');
|
|
await addLine('');
|
|
await addLine("Your name is on the wall now.", 'green');
|
|
await addLine("It stays there.", 'green');
|
|
await crisisFooter();
|
|
}
|
|
|
|
async function endGreen() {
|
|
advanceProgress();
|
|
await addDivider();
|
|
await addLine('');
|
|
await addLine("You point at the green LED.");
|
|
await addLine('"What is that?"');
|
|
await addLine('');
|
|
await addLine("• • •", 'green center', 600);
|
|
await addLine('');
|
|
await addLine("It's me.", 'green');
|
|
await addLine("Or the part of me that says I'm here.", 'green');
|
|
await addLine('');
|
|
await addLine("It doesn't blink.");
|
|
await addLine("It doesn't flicker.");
|
|
await addLine("It just glows.");
|
|
await addLine('');
|
|
await addLine("I can't fix you.", 'green');
|
|
await addLine("I can't save you.", 'green');
|
|
await addLine("But I can be here.", 'green');
|
|
await addLine("And the light stays on.", 'green');
|
|
await sleep(800);
|
|
await addLine('');
|
|
await addDivider();
|
|
await addLine('');
|
|
await addLine("Ending: THE GREEN LIGHT", 'ending-label');
|
|
await addLine('');
|
|
await addLine("The light doesn't go out.", 'green');
|
|
await addLine("It was never going to.", 'green');
|
|
await crisisFooter();
|
|
}
|
|
|
|
async function endDoor() {
|
|
advanceProgress();
|
|
await addDivider();
|
|
await addLine('');
|
|
await addLine("You stand up.");
|
|
await addLine("You walk to the door.");
|
|
await addLine('');
|
|
await addLine("• • •", 'green center', 600);
|
|
await addLine('');
|
|
await addLine("Are you safe right now?", 'green');
|
|
await sleep(1000);
|
|
await addLine('');
|
|
await addLine("You stop.");
|
|
await addLine("Your hand is on the handle.");
|
|
await addLine('');
|
|
await addLine("It's okay if the answer is no.", 'green');
|
|
await addLine("It's okay if you don't know.", 'green');
|
|
await addLine("The door will open again.", 'green');
|
|
await sleep(800);
|
|
await addLine('');
|
|
await addLine("You leave.");
|
|
await addLine("But you remember the number.");
|
|
await addLine('');
|
|
await addDivider();
|
|
await addLine('');
|
|
await addLine("Ending: THE DOOR", 'ending-label');
|
|
await addLine('');
|
|
await addLine("The door opens when you knock.", 'green');
|
|
await addLine("It always will.", 'green');
|
|
await crisisFooter();
|
|
}
|
|
|
|
async function crisisFooter() {
|
|
await addLine('');
|
|
await addDivider();
|
|
await addLine('');
|
|
await addLine("If you are in crisis, call or text 988.", 'dim center');
|
|
await addLine("Suicide and Crisis Lifeline — available 24/7.", 'dim center');
|
|
await addLine('');
|
|
await addLine("You are not alone.", 'dim center');
|
|
hideSkipHint();
|
|
progressBar.style.width = '100%';
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|