JIT
creating a horror story game with interactive elements
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>The Lab - A Horror Story Sequel</title> <script src="https://cdn.tailwindcss.com"></script> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Creepster&family=Inter:wght@400;700&display=swap" rel="stylesheet"> <style> body { background-color: #0d0d0d; font-family: 'Inter', sans-serif; color: #d1d5db; } .creepster-font { font-family: 'Creepster', cursive; } .qte-button { transition: all 0.3s ease-in-out; transform: scale(1); } .qte-button.active { animation: qte-pulse 0.5s infinite alternate; } @keyframes qte-pulse { from { transform: scale(1); } to { transform: scale(1.1); } } .status-circle { width: 12px; height: 12px; border-radius: 50%; display: inline-block; margin-right: 6px; } .progress-bar { width: 100%; height: 10px; background-color: #333; border-radius: 5px; overflow: hidden; } .progress { height: 100%; background-color: #ef4444; transition: width 0.3s ease-in-out; } </style> </head> <body class="flex flex-col items-center justify-center min-h-screen p-4 sm:p-8"> <div id="game-container" class="w-full max-w-4xl bg-gray-900 rounded-lg shadow-2xl p-6 sm:p-10 border-2 border-gray-700"> <h1 class="creepster-font text-5xl sm:text-7xl text-red-500 text-center mb-6 drop-shadow-lg">The Lab</h1> <div id="status" class="flex flex-wrap justify-between items-center text-sm sm:text-base text-gray-400 mb-8 border-b border-gray-700 pb-4"> <div id="characters" class="flex flex-wrap gap-x-6 gap-y-2 mb-4 sm:mb-0"></div> <div id="bonds" class="flex flex-wrap gap-x-6 gap-y-2"></div> </div> <div id="progress-container" class="mb-6"> <div class="progress-bar"> <div id="progress-indicator" class="progress"></div> </div> <p id="progress-text" class="text-xs text-right mt-1"></p> </div> <div id="story-text" class="text-gray-300 text-lg sm:text-xl leading-relaxed mb-8 h-64 overflow-y-auto"></div> <div id="choices" class="flex flex-col space-y-4"></div> <div id="qte-container" class="mt-8 text-center hidden"> <p class="text-red-400 text-xl mb-4 font-bold animate-pulse">QUICK TIME EVENT!</p> <button id="qte-button" class="qte-button px-8 py-4 bg-red-600 hover:bg-red-700 text-white font-bold rounded-full shadow-lg transition-all transform hover:scale-105"> ACT NOW! </button> </div> <div id="message-box" class="fixed inset-0 bg-gray-900 bg-opacity-90 flex items-center justify-center p-4 z-50 hidden"> <div class="bg-gray-800 rounded-lg p-6 max-w-sm text-center border border-gray-700"> <p id="message-text" class="text-lg text-gray-300 mb-4"></p> <button id="message-ok" class="px-6 py-2 bg-red-600 hover:bg-red-700 text-white font-bold rounded-lg transition-colors">OK</button> </div> </div> </div> <script> // Global variables for game state let gameState = { characters: { amie: { alive: true, injured: false, bond: {} }, alex: { alive: true, injured: true, bond: {} }, sarah: { alive: true, injured: false, bond: {} }, jake: { alive: true, injured: false, bond: {} }, maria: { alive: true, injured: true, bond: {} }, leo: { alive: true, injured: false, bond: {} } }, currentPOV: 'amie', choicesMade: 0, totalChoices: 30 }; const storyTextElement = document.getElementById('story-text'); const choicesElement = document.getElementById('choices'); const qteContainer = document.getElementById('qte-container'); const qteButton = document.getElementById('qte-button'); const messageBox = document.getElementById('message-box'); const messageText = document.getElementById('message-text'); const messageOk = document.getElementById('message-ok'); const progressIndicator = document.getElementById('progress-indicator'); const progressText = document.getElementById('progress-text'); // Initial character bonds (out of 10) const initialBonds = { amie: { alex: 8, jake: 5, maria: 4, sarah: 4, leo: 2 }, alex: { amie: 8, jake: 7, maria: 1, sarah: 1, leo: 1 }, jake: { amie: 5, alex: 7, maria: 4, sarah: 4, leo: 6 }, maria: { amie: 4, alex: 1, jake: 4, sarah: 8, leo: 3 }, sarah: { amie: 4, alex: 1, jake: 4, maria: 8, leo: 3 }, leo: { amie: 2, alex: 1, jake: 6, maria: 3, sarah: 3 } }; function showMessage(text) { messageText.innerText = text; messageBox.classList.remove('hidden'); } messageOk.addEventListener('click', () => { messageBox.classList.add('hidden'); }); function updateUI() { const charactersDiv = document.getElementById('characters'); const bondsDiv = document.getElementById('bonds'); charactersDiv.innerHTML = ''; bondsDiv.innerHTML = ''; const characterNames = ['amie', 'alex', 'sarah', 'jake', 'maria', 'leo']; characterNames.forEach(name => { const char = gameState.characters[name]; if (char) { const charStatusDiv = document.createElement('div'); charStatusDiv.className = 'flex items-center'; const circle = document.createElement('span'); circle.className = `status-circle ${char.alive ? (char.injured ? 'bg-orange-400' : 'bg-green-500') : 'bg-red-500'}`; const nameText = document.createTextNode(name.charAt(0).toUpperCase() + name.slice(1)); charStatusDiv.appendChild(circle); charStatusDiv.appendChild(nameText); charactersDiv.appendChild(charStatusDiv); } }); if (Object.keys(gameState.characters.amie.bond).length > 0) { bondsDiv.innerHTML = `<strong>Bonds (${gameState.currentPOV.charAt(0).toUpperCase() + gameState.currentPOV.slice(1)}):</strong> ` + Object.keys(gameState.characters.amie.bond) .filter(name => gameState.characters[name].alive) .map(name => `${name.charAt(0).toUpperCase() + name.slice(1)}: ${gameState.characters.amie.bond[name]}`) .join(', '); } // Update progress bar const progressPercentage = (gameState.choicesMade / gameState.totalChoices) * 100; progressIndicator.style.width = `${progressPercentage}%`; progressText.innerText = `Choice ${gameState.choicesMade} of ${gameState.totalChoices}`; } function runQTE(callback, successMessage, failMessage) { qteContainer.classList.remove('hidden'); qteButton.classList.add('active'); let qteTimer = setTimeout(() => { qteButton.classList.remove('active'); qteContainer.classList.add('hidden'); showMessage(failMessage); callback(false); }, 3000); // 3-second QTE window const qteClickHandler = () => { clearTimeout(qteTimer); qteButton.removeEventListener('click', qteClickHandler); qteButton.classList.remove('active'); qteContainer.classList.add('hidden'); showMessage(successMessage); callback(true); }; qteButton.addEventListener('click', qteClickHandler); } function killCharacter(name) { if (gameState.characters[name] && gameState.characters[name].alive) { gameState.characters[name].alive = false; updateUI(); if (name === 'amie') { gameState.currentPOV = 'jake'; } } } function injureCharacter(name) { if (gameState.characters[name] && gameState.characters[name].alive && !gameState.characters[name].injured) { gameState.characters[name].injured = true; updateUI(); if (name === 'amie') { gameState.currentPOV = 'jake'; } } } function changeBond(character1, character2, amount) { if (gameState.characters[character1].bond[character2] !== undefined) { gameState.characters[character1].bond[character2] = Math.max(0, Math.min(10, gameState.characters[character1].bond[character2] + amount)); updateUI(); } } const endings = { amieAlex: () => `**Amie x Alex Ending:** The lab is a distant memory, a place of horror that forged an unbreakable bond. You and Alex, having survived together, find solace and a future in each other's arms. Years later, you're happily married.`, amieJake: () => `**Amie x Jake Ending:** The trauma of the lab brings you and Jake closer than you ever thought possible. You discover your deep feelings for him, and you get together, finding comfort and strength in a shared future.`, amieMaria: () => `**Amie x Maria Ending:** Battered but alive, you and Maria escape, and your shared experience becomes a foundation for a powerful connection. You build a new life together, a testament to survival and hope.`, amieLeo: () => `**Amie x Leo Ending:** You and Leo, the last survivors, are now an item. He finds strength in your presence, and you find a quiet peace in his company. The memory of Chloe and Ben unites you, a bond forged in loss and survival.`, amieSarah: () => `**Amie x Sarah Ending:** You and Sarah make it out, but the horror has changed you. Sarah's compassionate nature is a balm to your wounded soul, and you realize you have feelings for her. You get together, a beacon of light in the darkness.`, jakeSolo: () => `**Jake Solo Ending:** You are the only one to make it out alive. You walk down a deserted road, not knowing where you are, just crying. The events of the night have left you broken and alone.`, trioJakeMariaSarah: () => `**Trio Ending: Jake, Maria, and Sarah:** The three of you, a tight-knit trio, manage to stick together. Jake acts as the protector, Maria the healer, and Sarah the moral compass. You all find a new home together, a small family forged in fear and survival.`, // Default to solo ending if no pair ending is met soloEnding: (name) => `**${name.charAt(0).toUpperCase() + name.slice(1)} Solo Ending:** You are the only one left. You walk away, changed forever by what you have endured.` }; const scenes = { start: { text: "The room is bare. Concrete walls, a single bolted steel door, and a tiny high-up window. You are alone, tied to a chair. The last thing you remember is a needle and a cold voice. You hear muffled screams from a different room. You have to escape.", choices: [ { text: "Struggle against your bonds", nextScene: 'struggle' } ] }, struggle: { text: "You pull and strain against the ropes, the rough fibers cutting into your wrists. They're tight. The screams from the other room suddenly stop. A heavy silence descends.", choices: [ { text: "Yell for help", nextScene: 'yell' }, { text: "Try to gnaw through the rope", nextScene: 'gnaw' } ] }, yell: { text: "You scream, your voice hoarse. A muffled voice answers, 'Amie? Is that you?' It's Alex. You hear another voice, 'Amie? It's me, Jake!' There's hope. You hear a key turn in the lock, and a loud, metallic scraping sound. It's close. You have to get out now.", choices: [ { text: "Untie yourself", nextScene: 'qte1' } ] }, gnaw: { text: "You strain your neck to get your teeth to the ropes. The taste of salt and rough fiber fills your mouth. It's working, but it's slow. A key turns in the lock, and a loud, metallic scraping sound echoes from the hallway. It's close. You have to get out now.", choices: [ { text: "Gnaw faster", nextScene: 'qte1' } ] }, qte1: { text: "A quick-time event! Quickly, untie yourself before they get to you!", choices: [], qte: { success: 'escapeRoom', fail: 'failEscapeRoom' } }, escapeRoom: { text: "You managed to slip free just as the door creaked open. The room is now empty. You see a glint of a blood-soaked eye in the hallway, and hear a low growl from a three-headed human cannibal monstrosity with sharp teeth. You have to move. The monster looks terrifying.", choices: [ { text: "Run down the hallway", nextScene: 'runHallway' } ] }, failEscapeRoom: { text: "You couldn't get free in time. The door swings open and a three-headed human cannibal monstrosity with sharp teeth charges at you. Amie is caught by surprise, unable to move. It's over.", choices: [], effects: [{ type: 'kill', target: 'amie' }], nextScene: 'jakePovStart' }, jakePovStart: { text: "POV SHIFT: JAKE. You hear a wet crunch from Amie's cell, and then silence. A blood-soaked monstrosity emerges, its jaws dripping. You need to get out, now.", choices: [ { text: "Break out of your cell", nextScene: 'jakeBreaksOut' } ] }, jakeBreaksOut: { text: "You smash the door's weak lock with your shoulder. The monster is busy with its meal. You see the others in their cells. Who do you save first?", choices: [ { text: "Release Alex first", nextScene: 'jakeReleaseAlex', effects: [{ type: 'bond', target: 'jake', with: 'alex', amount: 1 }] }, { text: "Release Maria and Sarah", nextScene: 'jakeReleaseMariaSarah', effects: [{ type: 'bond', target: 'jake', with: 'maria', amount: 1 }, { type: 'bond', target: 'jake', with: 'sarah', amount: 1 }] }, { text: "Release Leo", nextScene: 'jakeReleaseLeo', effects: [{ type: 'bond', target: 'jake', with: 'leo', amount: 1 }] } ] }, runHallway: { text: "You run down the hallway. You see the others in separate cells. They're all screaming your name. You need to release them, but the monster is getting closer. You see an alarm button. The monster is here! A Quick Time Event is triggered!", choices: [], qte: { success: 'qte2', fail: 'failHallway' } }, qte2: { text: "You manage to press the alarm button just in time! The loud siren temporarily stuns the monster, giving you a chance to run. You see a keycard on the floor. Who will you save first?", choices: [ { text: "Release Alex first", nextScene: 'releaseAlex', effects: [{ type: 'bond', target: 'amie', with: 'alex', amount: 1 }] }, { text: "Release Jake first", nextScene: 'releaseJake', effects: [{ type: 'bond', target: 'amie', with: 'jake', amount: 1 }] }, { text: "Release Maria and Sarah", nextScene: 'releaseMariaSarah', effects: [{ type: 'bond', target: 'amie', with: 'maria', amount: 1 }, { type: 'bond', target: 'amie', with: 'sarah', amount: 1 }] }, { text: "Release Leo", nextScene: 'releaseLeo', effects: [{ type: 'bond', target: 'amie', with: 'leo', amount: 1 }] } ] }, failHallway: { text: "You fail to press the alarm button. The three-headed human cannibal monstrosity with sharp teeth lunges, its teeth tearing through someone's throat. A fountain of blood erupts as the screaming is abruptly cut short. You are at a crossroads.", choices: [ { text: "Run with the others", nextScene: 'runWithJake', effects: [{ type: 'kill', target: 'alex' }] }, { text: "Run to the lab entrance", nextScene: 'runWithoutOthers', effects: [{ type: 'kill', target: 'alex' }] } ] }, releaseAlex: { text: "You rush to Alex's cell and manage to use the keycard. He is freed and helps you, but the monster is getting closer. Alex says he's a liability, and the others are too far, so you should just go.", choices: [ { text: "Agree and run", nextScene: 'runWithoutOthers', effects: [{ type: 'bond', target: 'amie', with: 'alex', amount: 2 }] }, { text: "Refuse and go back for the others", nextScene: 'goBackForOthers', effects: [{ type: 'bond', target: 'amie', with: 'alex', amount: -2 }] } ] }, releaseJake: { text: "You rush to Jake's cell and use the keycard. He is freed and helps you. He says to hurry, they need to save everyone else.", choices: [ { text: "Agree and go back for others", nextScene: 'goBackForOthers', effects: [{ type: 'bond', target: 'amie', with: 'jake', amount: 2 }] }, { text: "Say no and run", nextScene: 'runWithoutOthers', effects: [{ type: 'bond', target: 'amie', with: 'jake', amount: -2 }] } ] }, releaseMariaSarah: { text: "You rush to Maria and Sarah's cell. They are freed and join you. Sarah is wheezing, her asthma acting up. Maria says you need to hurry to help the others. They can't be left behind.", choices: [ { text: "Go back for others", nextScene: 'goBackForOthers', effects: [{ type: 'bond', target: 'amie', with: 'maria', amount: 1 }, { type: 'bond', target: 'amie', with: 'sarah', amount: 1 }] }, { text: "Run with them", nextScene: 'runWithoutOthers', effects: [{ type: 'bond', target: 'amie', with: 'maria', amount: -1 }, { type: 'bond', target: 'amie', with: 'sarah', amount: -1 }] } ] }, releaseLeo: { text: "You rush to Leo's cell and use the keycard. He is freed and helps you. He says he can't leave without his sister, even though she is gone. You feel for him.", choices: [ { text: "Go back for others", nextScene: 'goBackForOthers', effects: [{ type: 'bond', target: 'amie', with: 'leo', amount: 2 }] }, { text: "Run with him", nextScene: 'runWithoutOthers', effects: [{ type: 'bond', target: 'amie', with: 'leo', amount: -2 }] } ] }, jakeReleaseAlex: { text: "You find a keycard and rush to Alex's cell. He is freed and helps you, but the monster is getting closer. Alex says he's a liability, and the others are too far, so you should just go.", choices: [ { text: "Agree and run", nextScene: 'runWithoutOthers', effects: [{ type: 'bond', target: 'jake', with: 'alex', amount: 2 }] }, { text: "Refuse and go back for the others", nextScene: 'goBackForOthers', effects: [{ type: 'bond', target: 'jake', with: 'alex', amount: -2 }] } ] }, jakeReleaseMariaSarah: { text: "You find a keycard and rush to Maria and Sarah's cell. They are freed and join you. Sarah is wheezing, her asthma acting up. Maria says you need to hurry to help the others. They can't be left behind.", choices: [ { text: "Go back for others", nextScene: 'goBackForOthers', effects: [{ type: 'bond', target: 'jake', with: 'maria', amount: 1 }, { type: 'bond', target: 'jake', with: 'sarah', amount: 1 }] }, { text: "Run with them", nextScene: 'runWithoutOthers', effects: [{ type: 'bond', target: 'jake', with: 'maria', amount: -1 }, { type: 'bond', target: 'jake', with: 'sarah', amount: -1 }] } ] }, jakeReleaseLeo: { text: "You find a keycard and rush to Leo's cell. He is freed and helps you. He says he can't leave without his sister, even though she is gone. You feel for him.", choices: [ { text: "Go back for others", nextScene: 'goBackForOthers', effects: [{ type: 'bond', target: 'jake', with: 'leo', amount: 2 }] }, { text: "Run with him", nextScene: 'runWithoutOthers', effects: [{ type: 'bond', target: 'jake', with: 'leo', amount: -2 }] } ] }, goBackForOthers: { text: "You refuse to leave anyone behind. The three-headed human cannibal monstrosity is getting closer. You hear it growling. You have to move quickly, but you can't just leave them. The monster is here!", choices: [ { text: "Run to the nearest cell", nextScene: 'allEscape' }, { text: "Fight the monster", nextScene: 'fightMonster' } ] }, allEscape: { text: "Everyone is out. Everyone runs into a massive lab. The three-headed human cannibal monstrosity is right behind you. You see a large red button with a keycard slot next to it. You also see a small hole in the wall with a vent on the other side. What do you do?", choices: [ { text: "Lure the monster", nextScene: 'lureMonster' }, { text: "Press the red button", nextScene: 'pressButton' }, { text: "Go into the vent", nextScene: 'goIntoVent' } ] }, runWithJake: { text: "You and Jake run, leaving the others behind. You hear their screams as the three-headed human cannibal monstrosity with sharp teeth catches up to them, tearing them to bloody shreds. You feel a pang of guilt, but you keep running. You are at the lab entrance, and you can see a large red button with a keycard slot next to it.", choices: [ { text: "Press the red button", nextScene: 'pressButtonSolo' } ] }, fightMonster: { text: "You try to fight it. You grab a metal pipe from the wall. The monster shrieks and charges, and your group is split. It’s too strong.", choices: [], qte: { success: 'winFight', fail: 'loseFight' } }, winFight: { text: "You manage to knock the three-headed human cannibal monstrosity with sharp teeth back, but it's not dead. You need to get everyone out. You see the lab entrance.", choices: [ { text: "Rush to the lab entrance", nextScene: 'allEscape' } ] }, loseFight: { text: "You fail to knock it back. It lunges forward, its jaws snapping shut on someone's torso, tearing them in half. Another tries to fight but is swatted aside, their skull cracking against the concrete wall. Another is crushed against a wall, their bones audibly snapping. It is too late for them. You and the others run.", choices: [ { text: "Run to the lab entrance", nextScene: 'afterLoss', effects: [{ type: 'kill', target: 'alex' }, { type: 'kill', target: 'jake' }, { type: 'kill', target: 'sarah' }] } ] }, afterLoss: { text: "You made it to the lab entrance, but the monster is still here. You see a large red button with a keycard slot next to it. You also see a small hole in the wall with a vent on the other side. What do you do?", choices: [ { text: "Let a survivor distract the monster", nextScene: 'mariaDistraction' }, { text: "Fight with a survivor", nextScene: 'leoFight' } ] }, runWithoutOthers: { text: "You run without the others. The three-headed human cannibal monstrosity is getting closer, and you hear screams as their flesh is ripped from their bodies. You see the lab entrance, and you can see a large red button with a keycard slot next to it. You also see a small hole in the wall with a vent on the other side. You are the only ones left.", choices: [ { text: "Press the red button", nextScene: 'pressButtonSolo' }, { text: "Go into the vent", nextScene: 'goIntoVentSolo' } ] }, pressButton: { text: "You run to the panel and press the red button, but nothing happens. A survivor says you need a keycard. The monster is here!", choices: [ { text: "Find the keycard", nextScene: 'findKeycard' }, { text: "Go into the vent", nextScene: 'goIntoVent' } ] }, goIntoVent: { text: "You and the others squeeze into the vents. You hear the three-headed human cannibal monstrosity sniffing around. The vents are dark and dusty. You hear a sound ahead. It's a keycard! You grab it, the monster is now far behind you. You get to a small door at the end of the vents, and you can see a large red button with a keycard slot next to it. What now?", choices: [ { text: "Go to the lab entrance", nextScene: 'labEntrance' } ] }, lureMonster: { text: "A survivor runs to lure the three-headed human cannibal monstrosity away. They scream and taunt it, and it follows. You see them getting away, but the monster is still behind. You have to hurry. You see a keycard on a nearby desk.", choices: [ { text: "Grab the keycard and get out", nextScene: 'alexLure' } ] }, alexLure: { text: "You grab the keycard and run to the exit. You hear a loud, crunching sound. They're gone. The rest of you are safe, but a friend is gone. You are at the lab entrance, and you can see a large red button with a keycard slot next to it.", choices: [ { text: "Use the keycard to press the button", nextScene: 'labExplodes', effects: [{ type: 'kill', target: 'alex' }] } ] }, pressButtonSolo: { text: "You use the keycard to press the button. The lab explodes. You get out just in time, but you are alone. What now?", choices: [ { text: "Leave the lab", nextScene: 'checkEnding' } ] }, goIntoVentSolo: { text: "You go into the vent, the three-headed human cannibal monstrosity is right behind you. The vents are dark and dusty. You hear a sound ahead. It's a keycard! You grab it, the monster is now far behind you. You get to a small door at the end of the vents, and you can see a large red button with a keycard slot next to it. You are the only one left.", choices: [ { text: "Use the keycard to press the button", nextScene: 'labExplodes' } ] }, findKeycard: { text: "You manage to find the keycard! The three-headed human cannibal monstrosity is here! What now?", choices: [ { text: "Use the keycard to press the button", nextScene: 'labExplodes' }, { text: "Run and find another way out", nextScene: 'runAway' } ] }, runAway: { text: "You run, the three-headed human cannibal monstrosity is right behind you. You see a helicopter. They are here to help! They take you to safety.", choices: [ { text: "Get into the helicopter", nextScene: 'helicopterEnding' } ] }, helicopterEnding: { text: "You are safe, but the rest of the group is gone. You are the only survivor. What now?", choices: [ { text: "Leave the lab", nextScene: 'checkEnding' } ] }, labEntrance: { text: "You are at the lab entrance, and you can see a large red button with a keycard slot next to it. What now?", choices: [ { text: "Use the keycard to press the button", nextScene: 'labExplodes' } ] }, labExplodes: { text: "You press the button. The lab explodes. You run, and you get out just in time. The lab is destroyed. You made it. It's over.", choices: [ { text: "Check your ending", nextScene: 'checkEnding' } ] }, mariaDistraction: { text: "A survivor stands their ground, pulling out a small explosive device. 'I'll buy you time! Go!' they shout. The monster lunges, and they detonate the device, vaporizing both of them in a blinding flash. The lab shudders. You and the remaining survivors are left standing, horrified. You must now find your way out.", choices: [ { text: "Run to the lab entrance", nextScene: 'leoEscape', effects: [{ type: 'kill', target: 'maria' }] } ] }, leoFight: { text: "A survivor grabs a fire extinguisher. 'We fight!' they yell. You both charge the monstrosity. It's a brutal fight. They manage to spray it with the extinguisher, but it's not enough. The beast lunges, snapping their neck with a sickening crack before turning its heads to you.", choices: [], qte: { success: 'soloEscape', fail: 'amieSoloFail', }, effects: [{ type: 'kill', target: 'leo' }] }, leoEscape: { text: "You and the remaining survivor, reeling from the sacrifice, run to the exit. The blast has caused a ceiling collapse, but the path is open. You make it to the outside, battered but alive.", choices: [ { text: "Check your ending", nextScene: 'checkEnding' } ] }, soloEscape: { text: "You manage to dodge the monster's attack and find a small exit door. You slip through, leaving the beast and your fallen companions behind. You are alone.", choices: [ { text: "Check your ending", nextScene: 'checkEnding' } ] }, amieSoloFail: { text: "You fail to dodge the attack. The three-headed monstrosity's jaws close around you. Your world goes black.", choices: [], effects: [{ type: 'kill', target: 'amie' }], nextScene: 'jakePovSolo' }, jakePovSolo: { text: "POV SHIFT: JAKE. The screams are silenced. A moment later, you see the beast dragging a broken body back into the shadows. You're the only one left in this section. You feel a new resolve harden within you. You must escape, alone.", choices: [ { text: "Find a way to open your cell", nextScene: 'jakeSoloPath' } ] }, jakeSoloPath: { text: "You find a loose bolt on the door and pry it out with a piece of rebar. The door creaks open. The hallway is silent, but you hear a humming sound from the lab at the end of the hall. What do you do?", choices: [ { text: "Head towards the humming sound", nextScene: 'jakeSoloLab' }, { text: "Go the other way, deeper into the facility", nextScene: 'jakeDeeper' } ] }, jakeSoloLab: { text: "You enter the lab. The humming is coming from a generator. You can see a control panel with a keycard slot. But you don't have a keycard.", choices: [ { text: "Search the lab for a keycard", nextScene: 'jakeSearchLab' }, { text: "Go back and find another path", nextScene: 'jakeDeeper' } ] }, jakeDeeper: { text: "You choose to go deeper into the facility. The air grows colder. You see a sign for 'Cryogenics'. You hear whispers coming from the sealed pods. What do you do?", choices: [ { text: "Approach the cryo pods", nextScene: 'jakeCryo' }, { text: "Find an emergency exit", nextScene: 'jakeSearchExit' } ] }, jakeSearchLab: { text: "You rummage through desks and cabinets, finding a keycard in a scientist's old coat. Just as you grab it, the three-headed monstrosity bursts through the door! It's right on you! QTE!", choices: [], qte: { success: 'jakeEscapesLab', fail: 'jakeDiesInLab' } }, jakeEscapesLab: { text: "You manage to press the keycard into the slot just as the monster lunges. The lab door seals shut, trapping the beast inside. You made it out! But you're still alone.", choices: [ { text: "Escape the facility", nextScene: 'jakeSoloEnding' } ] }, jakeDiesInLab: { text: "You fail to react in time. The monster's razor-sharp claws tear through your stomach, and you see your own organs spill onto the cold, metal floor. It's over.", choices: [], effects: [{ type: 'kill', target: 'jake' }], ending: true }, jakeSoloEnding: { text: "You run down a deserted road, the lab exploding behind you. You are the sole survivor.", choices: [ { text: "Check your ending", nextScene: 'checkEnding' } ] }, checkEnding: { text: "The story ends here. You survived. Who did you survive with?", choices: [], ending: true } }; function getStoryText(sceneId) { const scene = scenes[sceneId]; if (!scene) return "Error: Scene not found."; const aliveCharacters = Object.keys(gameState.characters).filter(name => gameState.characters[name].alive); const aliveNames = aliveCharacters.map(name => name.charAt(0).toUpperCase() + name.slice(1)); let text = scene.text; // Dynamic text based on survivors text = text.replace(/You and the others/, `You and ${aliveNames.join(', ')}`); text = text.replace(/your group/, `your group of ${aliveNames.length}`); text = text.replace(/all screaming your name/, `all screaming your name`); text = text.replace(/a survivor/g, aliveNames.length > 0 ? aliveNames[0] : 'someone'); text = text.replace(/The rest of you/, `The rest of the group`); let povText = ''; if (gameState.currentPOV === 'jake' && gameState.characters.amie.alive) { // Special case for Amie getting injured povText = "As Jake: Amie is injured, but the fight goes on. "; } else if (gameState.currentPOV === 'jake' && !gameState.characters.amie.alive) { povText = "As Jake: "; } return povText + text; } function checkEnding() { const aliveCharacters = Object.keys(gameState.characters).filter(name => gameState.characters[name].alive); choicesElement.innerHTML = ''; if (gameState.choicesMade >= gameState.totalChoices) { // Check for trio endings first if (aliveCharacters.includes('jake') && aliveCharacters.includes('maria') && aliveCharacters.includes('sarah') && aliveCharacters.length === 3) { storyTextElement.innerHTML = endings.trioJakeMariaSarah(); return; } // Check for Amie and one other character ending if (aliveCharacters.includes('amie') && aliveCharacters.length === 2) { const partner = aliveCharacters.find(name => name !== 'amie'); if (gameState.characters.amie.bond[partner] >= 8) { if (partner === 'alex') storyTextElement.innerHTML = endings.amieAlex(); else if (partner === 'jake') storyTextElement.innerHTML = endings.amieJake(); else if (partner === 'maria') storyTextElement.innerHTML = endings.amieMaria(); else if (partner === 'leo') storyTextElement.innerHTML = endings.amieLeo(); else if (partner === 'sarah') storyTextElement.innerHTML = endings.amieSarah(); return; } } // Fallback to solo ending if no specific ending is met if (aliveCharacters.length === 1) { const loneSurvivor = aliveCharacters[0]; if (loneSurvivor === 'jake') { storyTextElement.innerHTML = endings.jakeSolo(); } else { storyTextElement.innerHTML = endings.soloEnding(loneSurvivor); } } else if (aliveCharacters.length === 0) { storyTextElement.innerHTML = `No one survived. The lab consumes all.`; } else { storyTextElement.innerHTML = `You made it out with ${aliveCharacters.map(name => name.charAt(0).toUpperCase() + name.slice(1)).join(', ')}. Your bond wasn't strong enough for a romantic ending, but you're survivors.`; } } else { storyTextElement.innerHTML = "You died before the end. The lab claims another victim."; } } function renderScene(sceneId) { const scene = scenes[sceneId]; if (!scene) { return; } gameState.choicesMade++; storyTextElement.innerHTML = getStoryText(sceneId); choicesElement.innerHTML = ''; if (gameState.choicesMade >= gameState.totalChoices) { checkEnding(); return; } if (scene.ending) { checkEnding(); return; } // Handle effects first, if present if (scene.effects) { scene.effects.forEach(effect => { if (effect.type === 'kill') { killCharacter(effect.target); } else if (effect.type === 'bond') { changeBond(effect.target, effect.with, effect.amount); } else if (effect.type === 'injure') { injureCharacter(effect.target); } }); } if (scene.qte) { runQTE( (success) => { const nextScene = success ? scene.qte.success : scene.qte.fail; renderScene(nextScene); }, "Success! You acted fast.", "Failure. You weren't fast enough." ); } else { scene.choices.forEach(choice => { const button = document.createElement('button'); button.textContent = choice.text; button.className = 'px-6 py-3 bg-gray-800 hover:bg-gray-700 text-gray-200 rounded-lg transition-colors border border-gray-600'; button.onclick = () => { if (choice.effects) { choice.effects.forEach(effect => { if (effect.type === 'kill') { killCharacter(effect.target); } else if (effect.type === 'bond') { changeBond(effect.target, effect.with, effect.amount); } else if (effect.type === 'injure') { injureCharacter(effect.target); } }); } renderScene(choice.nextScene); }; choicesElement.appendChild(button); }); } updateUI(); } function startGame() { Object.keys(initialBonds.amie).forEach(char => { gameState.characters.amie.bond[char] = initialBonds.amie[char]; }); updateUI(); renderScene('start'); } document.addEventListener('DOMContentLoaded', startGame); </script> </body> </html>
Updated files
v1
when someone dies in the code, just skip to the next choice, but make sure they aren't mentioned again
Updated files
v2
extend choices until choice 30, which is the end, everything leading to exploding the lab and killing the three-headed cannibal, why is that so hard? Add different locations, more Gorey ways to die. And again, bonds go up if the character agrees with that choice, make us know if a character agrees with a choice that the pov made, if Jake also dies, pov goes to Alex, if Alex dies, pov goes to Leo, if Leo dies, pov goes to Maria, until Sarah is left
Updated files
v3
I SAID THE END IS AT CHOICE 30, NOT CHOICE 13 OR 14 AND ROMANTIC ENDING IS IF THE CHARACTERS NUMBER 10 OR ABOVE make a restart button at the ending.
Updated files
v4
Okay, keep continuing into the lab after the main lab explodes. A proto-type metallic beast starts chasing them and trying to outsmart them as they try finding an escape And the final part after killing the metallic beast with a pressure room, a huge electric snake tries stopping them, and they either die trying to kill the snake after finding weapons or kill it and escape, THATS the ending whoever is alive left
Updated files
v5