← Mike Higgins Home
Custom Code Experiment •

Tokyo Runner

Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
    <title>Tokyo Runner | Cyberpunk Text Adventure</title>
    <!-- preload Font Awesome for performance -->
    <link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
    <noscript><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"></noscript>
    <style>
        /* ------------------------------
           RESET & GLOBAL STYLES
        ------------------------------ */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
        }

        body {
            background: #000000;
            color: #E9E9F0;
            line-height: 1.6;
            min-height: 100vh;
            display: flex;
            flex-direction: column;
        }

        /* Status bar (top) */
        .status-bar {
            display: flex;
            justify-content: space-between;
            background: #27161E;
            padding: 12px 30px;
            border-bottom: 1px solid rgba(233, 233, 240, 0.1);
            font-size: 0.9rem;
            color: #E9E9F0;
        }

        .status-item {
            display: flex;
            align-items: center;
            gap: 8px;
        }

        /* Main scene container */
        .scene-container {
            display: flex;
            flex-direction: column;
            flex-grow: 1;
            padding: 0;
            justify-content: center;
        }

        .scene-content {
            padding: 20px;
            display: flex;
            flex-direction: column;
            gap: 15px;
            max-width: 850px;
            margin: 0 auto;
            width: 100%;
        }

        /* Text display */
        .scene-text {
            font-size: 1rem;
            line-height: 1.5;
            background: #1a1a1a;
            padding: 18px;
            border-radius: 6px;
            border-left: 3px solid #FF6936;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
            min-height: 120px;
            position: relative;
            color: #E9E9F0;
        }

        /* Typing cursor animation */
        .scene-text.typing::after {
            content: "▌";
            animation: blink 1s infinite;
            color: #FF6936;
            font-weight: bold;
            font-size: 0.9rem;
        }

        @keyframes blink {
            0%, 100% { opacity: 1; }
            50% { opacity: 0; }
        }

        /* Choice buttons */
        .choices-container {
            display: flex;
            flex-direction: column;
            gap: 8px;
        }

        .choice-btn {
            background: #2a2a2a;
            border: 1px solid #444;
            padding: 12px 16px;
            text-align: left;
            cursor: pointer;
            transition: all 0.2s ease;
            font-size: 0.95rem;
            border-radius: 6px;
            display: flex;
            align-items: center;
            gap: 8px;
            color: #E9E9F0;
            box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);
            touch-action: manipulation;
        }

        .choice-btn:hover {
            background: #3a2a2a;
            border-color: #FF6936;
            transform: translateY(-1px);
            box-shadow: 0 2px 8px rgba(255, 105, 54, 0.3);
        }

        .choice-number {
            background: #FF6936;
            color: white;
            width: 24px;
            height: 24px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 0.85rem;
            font-weight: 600;
            flex-shrink: 0;
        }

        /* Special scene styles */
        .game-over {
            background: #3a1a1a;
            border-left-color: #FF6936;
            font-weight: 600;
        }

        .mission-complete {
            background: #1a3a1a;
            border-left-color: #4CAF50;
            font-weight: 600;
        }

        .ending {
            background: #2a1a3a;
            border-left-color: #9C27B0;
            font-weight: 600;
        }

        /* Fade animation */
        .fade-in {
            animation: fadeIn 0.4s ease-in;
        }

        @keyframes fadeIn {
            from { opacity: 0; }
            to { opacity: 1; }
        }

        /* Footer controls (simplified) */
        .controls {
            display: flex;
            justify-content: center;
            padding: 15px 30px;
            background: #27161E;
            border-top: 1px solid #444;
            width: 100%;
            gap: 20px;
            margin-top: auto;
            flex-wrap: wrap;
        }

        .control-group {
            display: flex;
            gap: 12px;
            flex-wrap: wrap;
            justify-content: center;
        }

        .control-btn {
            background: #3B1D22;
            border: 1px solid #555;
            padding: 10px 24px;
            border-radius: 8px;
            cursor: pointer;
            display: flex;
            align-items: center;
            gap: 8px;
            font-size: 0.9rem;
            color: #E9E9F0;
            transition: all 0.2s;
            white-space: nowrap;
        }

        .control-btn:hover {
            background: #5e2e2e;
            border-color: #FF6936;
            transform: translateY(-1px);
        }

        .control-btn#new-game-btn {
            background: #4a2a2a;
            border-color: #FF6936;
        }

        /* Responsive adjustments */
        @media (max-width: 768px) {
            .scene-content {
                padding: 15px;
                gap: 12px;
            }
            .controls {
                padding: 15px 20px;
            }
            .control-btn {
                padding: 10px 16px;
                font-size: 0.9rem;
            }
        }

        @media (max-width: 480px) {
            .control-group {
                gap: 8px;
            }
            .control-btn {
                width: 100%;
                justify-content: center;
            }
        }

        /* ---------- START OVERLAY (full screen, blocks game) ---------- */
        .start-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: radial-gradient(circle at center, #0a0a1a, #000000);
            display: flex;
            align-items: center;
            justify-content: center;
            z-index: 2000;
            backdrop-filter: blur(4px);
            transition: opacity 0.6s ease;
            flex-direction: column;
            gap: 30px;
        }

        .start-card {
            background: rgba(20, 20, 30, 0.9);
            backdrop-filter: blur(12px);
            border-radius: 32px;
            padding: 40px 48px;
            text-align: center;
            border: 1px solid rgba(255, 105, 54, 0.5);
            box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6), 0 0 20px rgba(255, 105, 54, 0.2);
            max-width: 550px;
            width: 90%;
        }

        .start-card h1 {
            font-size: 3rem;
            font-weight: 800;
            background: linear-gradient(135deg, #FF6936, #ff9a6e);
            -webkit-background-clip: text;
            background-clip: text;
            color: transparent;
            margin-bottom: 16px;
            letter-spacing: 2px;
        }

        .start-card p {
            color: #ccc;
            margin-bottom: 32px;
            font-size: 1.1rem;
        }

        .start-btn-large {
            background: #FF6936;
            border: none;
            color: #0a0a0a;
            font-size: 1.5rem;
            font-weight: bold;
            padding: 14px 42px;
            border-radius: 60px;
            cursor: pointer;
            transition: transform 0.2s, box-shadow 0.2s;
            display: inline-flex;
            align-items: center;
            gap: 12px;
            box-shadow: 0 0 12px rgba(255, 105, 54, 0.6);
        }

        .start-btn-large:hover {
            transform: scale(1.02);
            background: #ff824e;
            box-shadow: 0 0 20px rgba(255, 105, 54, 0.9);
        }

        /* ---------- OVERLAYS (About / Story) ---------- */
        .overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.8);
            display: none;
            align-items: center;
            justify-content: center;
            z-index: 1000;
            padding: 20px;
        }

        .overlay-content {
            background: #1a1a1a;
            border-radius: 12px;
            max-width: 800px;
            width: 100%;
            max-height: 90vh;
            overflow-y: auto;
            border: 1px solid #444;
        }

        .overlay-header {
            background: #3B1D22;
            padding: 25px 30px;
            text-align: center;
            position: relative;
            border-bottom: 1px solid #444;
        }

        .close-btn {
            position: absolute;
            right: 20px;
            top: 50%;
            transform: translateY(-50%);
            background: transparent;
            border: 1px solid rgba(233, 233, 240, 0.3);
            color: #E9E9F0;
            padding: 8px 16px;
            border-radius: 6px;
            cursor: pointer;
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .overlay-body {
            padding: 30px;
        }

        .terminal-glow {
            background: #2a2a2a;
            padding: 25px;
            border-radius: 8px;
            border: 1px solid rgba(255, 105, 54, 0.2);
        }

        .story-title {
            font-size: 2.5rem;
            font-weight: 700;
            margin-bottom: 20px;
            text-align: center;
        }

        .story-text p {
            margin-bottom: 1.5rem;
        }

        .signature {
            font-style: italic;
            text-align: right;
            margin-top: 2rem;
            color: #FF6936;
        }

        .section {
            border-left: 3px solid;
            padding-left: 20px;
            margin: 25px 0;
        }

        .section-world {
            border-color: #FF6936;
        }

        .highlight {
            color: #FF6936;
        }

        .quote {
            padding: 20px;
            background: rgba(255, 105, 54, 0.05);
            border-radius: 8px;
            text-align: center;
        }
    </style>
</head>
<body>

    <!-- Status Bar (visible after game starts) -->
    <div class="status-bar">
        <div class="status-item"><i class="fas fa-user"></i><span>Runner_303</span></div>
        <div class="status-item"><i class="fas fa-coins"></i><span id="credits">47 Credits</span></div>
        <div class="status-item"><i class="fas fa-clock"></i><span id="time">48h Remaining</span></div>
    </div>

    <main class="scene-container">
        <div class="scene-content">
            <div id="scene-text" aria-live="polite" aria-atomic="true" class="scene-text"></div>
            <div class="choices-container" id="choices-container"></div>
        </div>
    </main>

    <!-- Footer: only New Game, About, Story (no save / no github) -->
    <div class="controls">
        <div class="control-group">
            <button class="control-btn" id="new-game-btn"><i class="fas fa-play"></i><span>New Game</span></button>
            <button class="control-btn" id="about-btn"><i class="fas fa-info-circle"></i><span>About</span></button>
            <button class="control-btn" id="story-btn"><i class="fas fa-book"></i><span>Story</span></button>
        </div>
    </div>

    <!-- START OVERLAY (blocks game until clicked) -->
    <div id="startOverlay" class="start-overlay">
        <div class="start-card">
            <h1>TOKYO RUNNER</h1>
            <p>Neo-Tokyo 2087 • Cyberpunk text adventure<br>Every choice shapes the future.</p>
            <button id="startGameBtn" class="start-btn-large"><i class="fas fa-terminal"></i> START GAME</button>
        </div>
    </div>

    <!-- About Overlay -->
    <div class="overlay" id="about-overlay">
        <div class="overlay-content">
            <div class="overlay-header">
                <button class="close-btn" id="close-about"><i class="fas fa-times"></i>Close</button>
                <h1>Tokyo Runner</h1>
            </div>
            <div class="overlay-body">
                <div class="terminal-glow">
                    <h1 class="story-title">THE STORY</h1>
                    <p class="intro"><span class="highlight">NEO-TOKYO 2087</span> is a text-based adventure set in a dystopian cyberpunk future.</p>
                    <div class="section section-world"><h2>The World</h2><p>Megacorporations rule. Privacy is extinct. You're a hacker living on the fringes. When a mysterious message arrives, you're thrust into a conspiracy that could reshape the city.</p></div>
                    <div class="section section-world"><h2>Your Role</h2><p>Every choice matters. Will you expose the truth, walk away, or burn it all down? Multiple endings await.</p></div>
                    <div class="quote"><p>"In a world where data is power, knowledge is rebellion."</p></div>
                </div>
            </div>
        </div>
    </div>

    <!-- Full Story Overlay -->
    <div class="overlay" id="story-overlay">
        <div class="overlay-content">
            <div class="overlay-header">
                <button class="close-btn" id="close-story"><i class="fas fa-times"></i>Close</button>
                <h1>Tokyo Runner: Prologue</h1>
            </div>
            <div class="overlay-body">
                <div class="terminal-glow">
                    <div class="story-text">
                        <p>The flat wakes me. Not the light, the flat itself. The walls press in, humming that low corporate frequency. I am Runner_303. The name is a coat I put on.</p>
                        <p>A message buzzes in my teeth. Ghost. A job. A way out. The specifics are a shimmer, but the weight of it is real. My account balance is a shrug. Rent is a fact.</p>
                        <p>I go to the alley. The rain here isn't water, it's the city sweating out last night's synthetic alcohol. Ghost is there—a person-shaped gap in the air. They talk about Nexus Corp, about a sister taken. The chip is warm in my hand. I put it in my pocket.</p>
                        <p>I say I'm in. The plan is a map drawn on fog. We choose the front door. The bold choice. The stupid choice. I walk into the lobby, a fiction walking through fact. I hack the system, become Dr. Chen, a lie so confident the truth steps aside.</p>
                        <p>The server room is silent—a quiet that has swallowed screams. I jack in. Data isn't ones and zeroes. It's memory, feeling, the cold fear of a woman on a steel table. Ghost's sister, being smoothed into something dull and usable.</p>
                        <p>Then the alarm. I run. I burst out into neon night, sending the data like a whisper. Ghost replies: Thank you. The story has changed. I'm a room where a terrible, beautiful secret now lives. And it's looking out through my eyes. The end is just the beginning telling itself a different story.</p>
                        <div class="signature">- Runner_303</div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script>
        /* ------------------------------
           GAME DATA (full branching story)
        ------------------------------ */
        const scenes = {
            start: {
                id: "start",
                text: "YEAR 2087. NEO-TOKYO SPRAWL.\n\nYou wake up in your cramped flat, the neon glow of advertisements bleeding through broken blinds. Your neural implant buzzes with an encrypted message:\n\n'RUNNER_303, we have a job. BIG PAYOUT. Meet at the usual spot. -GHOST'\n\nYour account balance: 47 credits. Rent due: 1000 credits. Time: Running out.",
                choices: [
                    { text: "Check the message details", nextScene: "investigate" },
                    { text: "Head to the meeting spot", nextScene: "alley" },
                    { text: "Ignore it and go back to sleep", nextScene: "sleep" }
                ],
                timeCost: 0,
                creditChange: 0
            },
            investigate: {
                id: "investigate",
                text: "You boot up your hacking rig, the ancient keyboard clacking as you decrypt the message. Ghost is reliable, but paranoid. The job details reveal:\n\nTARGET: Nexus Corporation\nOBJECTIVE: Extract research data from their mainframe\nPAY: 50,000 credits\nRISK LEVEL: ████ EXTREME\n\nNexus Corp... The megacorporation that owns half the city. This is either your ticket out or a one-way trip to a corporate black site.",
                choices: [
                    { text: "Accept the job - you need the money", nextScene: "alley" },
                    { text: "Dig deeper into Nexus Corp first", nextScene: "research" },
                    { text: "Delete the message and lie low", nextScene: "sleep" }
                ],
                timeCost: 1,
                creditChange: 0
            },
            alley: {
                id: "alley",
                text: "The rain-slicked alley reeks of synthetic waste and ozone. Ghost materialises from the shadows, their face hidden behind a holographic mask.\n\n'You came. Good. The corp's data contains evidence of illegal human experimentation. People are dying, Runner. We need someone with your skills to get in, grab the data, and get out.'\n\nGhost slides you a data chip. 'Building schematics. Security protocols. You've got 48 hours before they rotate their ICE. You in or out?'",
                choices: [
                    { text: "I'm in. Let's take down these corp bastards.", nextScene: "planning" },
                    { text: "What's the real reason you need this data?", nextScene: "truth" },
                    { text: "This is too hot. I'm out.", nextScene: "abandon" }
                ],
                timeCost: 2,
                creditChange: 0
            },
            sleep: {
                id: "sleep",
                text: "You pull the thin blanket over your head, trying to block out the neon haze and the constant hum of the city.\n\nThree days later, your flat door explodes inward. Corporate enforcers. Someone leaked that Ghost contacted you. They don't ask questions.\n\n[GAME OVER]\n\nIn the shadows of Neo-Tokyo, hesitation is death.",
                choices: [
                    { text: "Start Over", nextScene: "start" }
                ],
                timeCost: 72,
                creditChange: 0
            },
            research: {
                id: "research",
                text: "You dive into the dark net, your neural implant burning hot as you navigate through encrypted databases.\n\nNexus Corporation: Biotech division. Recent acquisitions of homeless populations. Unregistered medical facilities in the lower city. And then you find it - leaked footage of the experiments.\n\nYour stomach turns. Whatever they're developing, it's turning people into... something else.\n\nThe message from Ghost makes sense now. This isn't just a payday. This is a chance to expose them.",
                choices: [
                    { text: "Contact Ghost - I'm ready to move", nextScene: "alley" },
                    { text: "Send the footage to underground media", nextScene: "leak" }
                ],
                timeCost: 3,
                creditChange: -10
            },
            planning: {
                id: "planning",
                text: "Ghost's hideout is a maze of stolen tech and jury-rigged servers. They spread the corporate blueprints across a holographic display.\n\n'Three entry points: Front lobby - heavily guarded but predictable. Air duct system - tight squeeze, minimal security. Underground parking - need a pass, but quieter approach.'\n\nYour cyberdeck hums with potential. You've broken into corporate systems before, but never anything this big.",
                choices: [
                    { text: "Front lobby - hack security, walk in bold", nextScene: "lobby" },
                    { text: "Air ducts - stealth approach", nextScene: "ducts" },
                    { text: "Underground parking - steal a pass first", nextScene: "parking" }
                ],
                timeCost: 4,
                creditChange: -20
            },
            truth: {
                id: "truth",
                text: "Ghost's holographic mask flickers, and for a moment you see genuine anger beneath.\n\n'My sister worked for Nexus. Cleaner. Saw something she shouldn't have. Now she's in one of their 'facilities', being used as a test subject. The data we're after includes facility locations and subject lists. I need to find her.'\n\nGhost's voice cracks. 'The 50,000? That's real. But I'm not doing this for money. I'm doing it because no one else will.'\n\nYou understand desperation. You've lived it.",
                choices: [
                    { text: "I'll help you find your sister. Let's plan this.", nextScene: "planning" },
                    { text: "We should leak everything to the media", nextScene: "leak" }
                ],
                timeCost: 1,
                creditChange: 0
            },
            lobby: {
                id: "lobby",
                text: "The Nexus Corp lobby is a cathedral of steel and glass. You walk in wearing a stolen employee badge, your neural implant spoofing your biometrics.\n\nThe security checkpoint looms ahead. Armed guards. Automated turrets. Biometric scanners that could blow your cover in seconds.\n\nYour cyberdeck is ready. One chance to hack their system or talk your way through. Choose wisely.",
                choices: [
                    { text: "Hack the security system remotely", nextScene: "hack_success" },
                    { text: "Bluff your way through with confidence", nextScene: "bluff" },
                    { text: "Cause a distraction and slip past", nextScene: "distraction" }
                ],
                timeCost: 2,
                creditChange: 0
            },
            hack_success: {
                id: "hack_success",
                text: "Your fingers dance across your cyberdeck. The building's security matrix unfolds before you - a beautiful, deadly maze of code. You find the vulnerability, exploit it, and...\n\nAccess granted.\n\nThe guards don't even look up as you walk past. The turrets remain dormant. You're in.\n\nThe lift rises to the 47th floor. Research Division. This is where the real data is kept. And where the real dangers begin.",
                choices: [
                    { text: "Proceed to the mainframe", nextScene: "mainframe" }
                ],
                timeCost: 1,
                creditChange: 0
            },
            mainframe: {
                id: "mainframe",
                text: "The server room hums with power. Rows of black monoliths storing the corporation's darkest secrets. You jack into the mainframe directly.\n\nThe data floods your neural implant. Test subjects. Locations. Research notes on bioweapon development. Everything Ghost needed, and so much worse.\n\nThen the alarm screams. You've been detected. Security AI is converging on your location. Time to run.",
                choices: [
                    { text: "Upload data to Ghost and escape", nextScene: "victory" },
                    { text: "Copy everything and fight your way out", nextScene: "fight" }
                ],
                timeCost: 1,
                creditChange: 0
            },
            victory: {
                id: "victory",
                text: "You burst through the emergency exit, sirens wailing behind you. The data is safe, uploaded to Ghost's encrypted servers. You've done it.\n\nThree days later, the leaked information hits every underground news network in the city. Nexus Corp's stock plummets. Federal investigations are launched. People are freed from the black sites.\n\nGhost finds their sister. Alive. Traumatised, but alive.\n\nYour account balance: 50,000 credits. Enough to disappear, start over somewhere clean. Or stay and fight.\n\n[MISSION COMPLETE]\n\nThe neon city glows a little less dark tonight.",
                choices: [
                    { text: "Play Again", nextScene: "start" }
                ],
                timeCost: 72,
                creditChange: 50000
            },
            abandon: {
                id: "abandon",
                text: "You turn away from Ghost, walking back into the neon-soaked night.\n\n'Runner, wait—'\n\nYou don't look back. Some fights aren't worth fighting.\n\nTwo weeks later, you see the news: 'Nexus Corp Announces Major Breakthrough in Biotech'. The photo shows smiling executives. The truth remains buried.\n\nGhost never contacts you again.\n\n[ENDING: WALKING AWAY]\n\nSometimes the safest choice is the one you regret forever.",
                choices: [
                    { text: "Start Over", nextScene: "start" }
                ],
                timeCost: 336,
                creditChange: 0
            },
            leak: {
                id: "leak",
                text: "You upload everything to every underground media outlet you can find. Anonymous. Untraceable. Or so you hope.\n\nThe story breaks within hours. Protests erupt. Nexus Corp denies everything, but the evidence is damning.\n\nThen your door explodes inward. Not corp security this time. Federal agents. They want to know how you got the data. They want Ghost.\n\nYou're a whistleblower now. Protected by law, hunted by corporations. Your life will never be simple again.\n\n[ENDING: WHISTLEBLOWER]\n\nSometimes doing the right thing means giving up everything.",
                choices: [
                    { text: "Start Over", nextScene: "start" }
                ],
                timeCost: 24,
                creditChange: 0
            },
            ducts: {
                id: "ducts",
                text: "The air duct system is tight, claustrophobic, and smells like recycled nightmares. You crawl through darkness, guided only by your neural implant's AR overlay.\n\nVoices echo below. Guards. You're directly above the server room now. One wrong move and you'll alert the entire building.",
                choices: [
                    { text: "Drop down silently", nextScene: "mainframe" },
                    { text: "Wait for guards to leave", nextScene: "mainframe" }
                ],
                timeCost: 2,
                creditChange: 0
            },
            parking: {
                id: "parking",
                text: "The underground parking garage is dimly lit, camera blindspots marked in your vision by Ghost's intel. You approach a lone corporate drone loading their car.\n\nQuick neural hack, clone their badge, they won't even remember. Easy. Unless something goes wrong.",
                choices: [
                    { text: "Clone the badge", nextScene: "lobby" },
                    { text: "Find another way", nextScene: "ducts" }
                ],
                timeCost: 1,
                creditChange: 0
            },
            bluff: {
                id: "bluff",
                text: "You stride up to the security checkpoint with absolute confidence. 'Bio-research division. Dr. Chen is expecting me. You want to delay project REDACTED?'\n\nThe guard hesitates. Your fake credentials are good, but not perfect. This is a gamble.",
                choices: [
                    { text: "Maintain eye contact, assert authority", nextScene: "hack_success" },
                    { text: "Hack the terminal while they're distracted", nextScene: "hack_success" }
                ],
                timeCost: 1,
                creditChange: 0
            },
            distraction: {
                id: "distraction",
                text: "You trigger a false alarm in the opposite wing. Fire suppression systems activate. Guards scramble. Chaos erupts.\n\nIn the confusion, you slip past security. Amateur hour. You're in.\n\nBut someone saw you. A camera you missed. The hunt begins.",
                choices: [
                    { text: "Race to the server room", nextScene: "mainframe" }
                ],
                timeCost: 1,
                creditChange: 0
            },
            fight: {
                id: "fight",
                text: "Security drones round the corner, weapons armed. You didn't want it to end this way, but you're not leaving empty-handed.\n\nYour cyberdeck overloads their systems. EMP bursts. Sparks fly. You run through smoke and debris, data secure, body bleeding.\n\nYou make it out. Barely. The data's safe. But you've left a trail.\n\n[ENDING: SCORCHED EARTH]\n\nNexus Corp knows your face now. The hunt never ends.",
                choices: [
                    { text: "Start Over", nextScene: "start" }
                ],
                timeCost: 2,
                creditChange: 0
            }
        };

        /* ------------------------------
           GAME STATE & DOM REFERENCES
        ------------------------------ */
        let currentSceneId = "start";
        let credits = 47;
        let timeRemaining = 48;
        let currentTypingId = null;

        const sceneTextEl = document.getElementById("scene-text");
        const choicesContainer = document.getElementById("choices-container");
        const creditsEl = document.getElementById("credits");
        const timeEl = document.getElementById("time");
        const newGameBtn = document.getElementById("new-game-btn");
        const aboutBtn = document.getElementById("about-btn");
        const storyBtn = document.getElementById("story-btn");
        const aboutOverlay = document.getElementById("about-overlay");
        const storyOverlay = document.getElementById("story-overlay");
        const closeAbout = document.getElementById("close-about");
        const closeStory = document.getElementById("close-story");
        const startOverlay = document.getElementById("startOverlay");
        const startGameLarge = document.getElementById("startGameBtn");

        /* ------------------------------
           HELPER FUNCTIONS
        ------------------------------ */
        function cancelTyping() {
            if (currentTypingId) {
                cancelAnimationFrame(currentTypingId);
                currentTypingId = null;
                sceneTextEl.classList.remove("typing");
                choicesContainer.style.pointerEvents = "";
            }
        }

        function fixTextFormatting(t) {
            return t.replace(/([.!?])([A-Za-z])/g, '$1 $2')
                    .replace(/([.!?]')([A-Za-z])/g, '$1 $2')
                    .replace(/([.!?]"\s*)([A-Za-z])/g, '$1 $2');
        }

        function typeText(text, element, callback) {
            cancelTyping();
            element.innerHTML = "";
            element.classList.add("typing");
            choicesContainer.style.pointerEvents = "none";
            const processed = fixTextFormatting(text);
            const total = processed.length;
            let i = 0;
            const start = performance.now();

            function frame(t) {
                const progress = Math.min((t - start) / (total * 16), 1);
                const newI = Math.floor(progress * total);
                if (newI > i) {
                    const chunk = processed.slice(i, newI).replace(/\n/g, '<br>');
                    element.insertAdjacentHTML('beforeend', chunk);
                    i = newI;
                }
                if (i < total) {
                    currentTypingId = requestAnimationFrame(frame);
                } else {
                    element.classList.remove("typing");
                    currentTypingId = null;
                    choicesContainer.style.pointerEvents = "";
                    element.scrollIntoView({ behavior: 'smooth', block: 'start' });
                    if (callback) callback();
                }
            }
            currentTypingId = requestAnimationFrame(frame);
        }

        function updateStatus() {
            creditsEl.textContent = `${credits} Credits`;
            if (timeRemaining <= 0) {
                timeEl.textContent = "TIME'S UP";
                timeEl.style.color = "#FF6936";
            } else if (timeRemaining <= 12) {
                timeEl.textContent = `${timeRemaining}h Remaining`;
                timeEl.style.color = "#FF6936";
            } else {
                timeEl.textContent = `${timeRemaining}h Remaining`;
                timeEl.style.color = "#E9E9F0";
            }
        }

        /* ------------------------------
           CORE GAME LOGIC
        ------------------------------ */
        function resetGameAndStart() {
            cancelTyping();
            credits = 47;
            timeRemaining = 48;
            currentSceneId = "start";
            updateStatus();
            loadScene("start", true);
        }

        function loadScene(sceneId, freshStart = false) {
            const scene = scenes[sceneId];
            if (!scene) return;

            if (!freshStart) {
                if (scene.timeCost) timeRemaining = Math.max(0, timeRemaining - scene.timeCost);
                if (scene.creditChange) credits += scene.creditChange;
            }
            currentSceneId = sceneId;

            // Reset text area classes
            sceneTextEl.className = "scene-text fade-in";
            if (["sleep", "abandon", "fight"].includes(sceneId)) {
                sceneTextEl.classList.add("game-over");
            } else if (sceneId === "victory") {
                sceneTextEl.classList.add("mission-complete");
            } else if (sceneId === "leak") {
                sceneTextEl.classList.add("ending");
            } else {
                sceneTextEl.classList.remove("game-over", "mission-complete", "ending");
            }

            choicesContainer.innerHTML = "";
            updateStatus();

            typeText(scene.text, sceneTextEl, () => {
                scene.choices.forEach((choice, idx) => {
                    const btn = document.createElement("button");
                    btn.className = "choice-btn fade-in";
                    btn.innerHTML = `<div class="choice-number">${idx + 1}</div><span>${choice.text}</span>`;
                    btn.accessKey = (idx + 1).toString();
                    btn.addEventListener("click", () => {
                        if (choice.nextScene === "start") {
                            resetGameAndStart();
                        } else {
                            loadScene(choice.nextScene, false);
                        }
                    });
                    choicesContainer.appendChild(btn);
                });
            });
        }

        /* ------------------------------
           START OVERLAY & EVENT HANDLERS
        ------------------------------ */
        function dismissStartOverlayAndBegin() {
            startOverlay.style.opacity = "0";
            setTimeout(() => {
                startOverlay.style.display = "none";
            }, 500);
            resetGameAndStart();
        }

        startGameLarge.addEventListener("click", dismissStartOverlayAndBegin);
        newGameBtn.addEventListener("click", () => {
            if (startOverlay.style.display !== "none") {
                dismissStartOverlayAndBegin();
            } else {
                resetGameAndStart();
            }
        });

        aboutBtn.addEventListener("click", () => aboutOverlay.style.display = "flex");
        closeAbout.addEventListener("click", () => aboutOverlay.style.display = "none");
        storyBtn.addEventListener("click", () => storyOverlay.style.display = "flex");
        closeStory.addEventListener("click", () => storyOverlay.style.display = "none");

        aboutOverlay.addEventListener("click", (e) => {
            if (e.target === aboutOverlay) aboutOverlay.style.display = "none";
        });
        storyOverlay.addEventListener("click", (e) => {
            if (e.target === storyOverlay) storyOverlay.style.display = "none";
        });

        // The game starts hidden behind the overlay – no automatic load.
        // The overlay is visible initially; user clicks START GAME.
    </script>
</body>
</html>