• 01 - Critter Models 02
  • 02 - VantaJS Shaders
  • 03 - App Bundle 02
  • 04 - Cathedrals
  • 05 - App Bundle 01
  • 06 - Retroscopic
  • 07 - Games
  • 08 - Voronoi
  • 09 - Terrain
  • 10 - Critter Models 01
  • 11 - Chromatophores
  • 12 - Conformal Maps
  • 13 - Echo Logix
  • 14 - Music
  • 15 - Colour Theory
MIKE'S NOTEBOOK
Notes
Number:
Project:
07Games
└ Rockfall└ Speak & Math└ Space Invaders└ Apache└ Asteroids└ Molly Roger└ Carrom└ Chess└ Connect 4└ Anagrams
08Voronoi
└ 001└ 002└ 003└ 004└ 005
09Terrain
└ Simple Polygon└ Simple Flyover└ Infinite Flyover 01└ Infinite Flyover 02└ Hexagonal Plane└ Mountain Ridges
10Critter Models 01
└ Wireframe Spider└ Wireframe Fly└ Wireframe Scorpion└ Wireframe Mosquito└ Wireframe Bee└ Wireframe Butterfly└ Wireframe Locust
11Chromatophores
└ 01└ 02└ 03└ 04
12Conformal Maps
└ 01└ 02└ 03└ 04└ 05
02 / 03

MOLLY ROGER - INTERACTIVE ARCADE GAME SOURCE CODE

<!DOCTYPE html>
<html lang="en-GB">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>The Ghost of Molly Roger</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@600;800;900&family=Share+Tech+Mono&display=swap" rel="stylesheet">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Share Tech Mono', 'Segoe UI', monospace;
        }

        :root {
            --accent: #8A6DFF;
            --accent-secondary: #3A86FF;
            --accent-glow: rgba(138, 109, 255, 0.18);
        }

        /* Institution: Worthington, the Board, the approved path - brass and ink */
        body[data-theme="institution"] {
            --accent: #C9A227;
            --accent-secondary: #8A6D3A;
            --accent-glow: rgba(201, 162, 39, 0.16);
        }

        /* Dream: Molly, Dreamspace, the heist - violet and electric blue */
        body[data-theme="dream"] {
            --accent: #8A6DFF;
            --accent-secondary: #3A86FF;
            --accent-glow: rgba(138, 109, 255, 0.22);
        }

        /* Neutral: Grace, the library, in-between moments - teal */
        body[data-theme="neutral"] {
            --accent: #3FBBFF;
            --accent-secondary: #2FBFA0;
            --accent-glow: rgba(63, 187, 255, 0.16);
        }

        body {
            transition: background 0.6s ease;
        }

        body {
            background-color: #000;
            background-image: radial-gradient(rgba(138,109,255,0.06) 1px, transparent 1px);
            background-size: 5px 5px;
            color: #E9E9F0;
            line-height: 1.5;
            min-height: 100vh;
            display: flex;
            flex-direction: column;
            align-items: center;
            overflow-x: hidden;
        }

        .main-container {
            width: 100%;
            max-width: 900px;
            display: flex;
            flex-direction: column;
            align-items: center;
            margin: 0 auto;
            padding: 0 15px;
        }

        header {
            background: #000;
            color: #E9E9F0;
            padding: 10px 16px;
            text-align: center;
            width: 100%;
            border-bottom: 1px solid #2A3B52;
            flex-shrink: 0;
        }

        h1 {
            font-family: 'Orbitron', sans-serif;
            font-size: 1.3rem;
            font-weight: 900;
            letter-spacing: 1px;
            text-transform: uppercase;
            margin-bottom: 4px;
            color: #E9E9F0;
            text-shadow: -1.5px 0 #00F0FF, 1.5px 0 #FF2E9A;
        }

        .subtitle {
            font-size: 0.62rem;
            opacity: 0.85;
            font-weight: 400;
            color: #8A6DFF;
            letter-spacing: 0.15em;
            text-transform: uppercase;
            margin-bottom: 8px;
        }

        .header-links {
            display: flex;
            justify-content: center;
            gap: 10px;
            margin-top: 8px;
            flex-wrap: wrap;
        }

        .header-link {
            color: #E9E9F0;
            text-decoration: none;
            transition: all 0.2s;
            display: flex;
            align-items: center;
            gap: 4px;
            cursor: pointer;
            font-size: 0.65rem;
            letter-spacing: 0.03em;
            padding: 4px 9px;
            border-radius: 2px;
            border: 1px solid transparent;
            background: rgba(138, 109, 255, 0.08);
        }

        .header-link:hover {
            color: #8A6DFF;
            border-color: #8A6DFF;
            background: rgba(138, 109, 255, 0.2);
        }

        .status-bar {
            display: flex;
            justify-content: space-between;
            background: #000;
            padding: 5px 15px;
            width: 100%;
            font-size: 0.65rem;
            letter-spacing: 0.02em;
            color: #E9E9F0;
            border-bottom: 1px solid #2A3B52;
            flex-shrink: 0;
        }

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

        /* ---------- Breadcrumb trail ---------- */
        .breadcrumb-container {
            display: flex;
            align-items: center;
            flex-wrap: wrap;
            gap: 3px;
            width: 100%;
            padding: 6px 15px;
            background: rgba(138, 109, 255, 0.04);
            border-bottom: 1px solid #2A3B52;
            font-size: 0.6rem;
        }

        .breadcrumb-item {
            color: #8FA0BD;
            cursor: pointer;
            padding: 2px 7px;
            border-radius: 2px;
            transition: all 0.2s;
            white-space: nowrap;
            border: 1px solid transparent;
        }

        .breadcrumb-item:hover {
            color: #8A6DFF;
            background: rgba(138, 109, 255, 0.15);
            border-color: rgba(138, 109, 255, 0.3);
        }

        .breadcrumb-item.current {
            color: #E9E9F0;
            background: rgba(138, 109, 255, 0.25);
            cursor: default;
            font-weight: 600;
            border-color: #8A6DFF;
        }

        .breadcrumb-item.current:hover {
            background: rgba(138, 109, 255, 0.25);
        }

        .breadcrumb-arrow {
            color: #3A4B62;
            font-size: 0.6rem;
        }

        .breadcrumb-more {
            color: #5A6B82;
            font-size: 0.7rem;
            padding: 3px 6px;
        }

        .scene-content {
            width: 100%;
            display: flex;
            flex-direction: column;
            padding: 20px 0;
            background: #000;
            position: relative;
        }

        /* ---------- Illustrated scene panel ---------- */
        .illustration-panel {
            position: relative;
            width: 100%;
            aspect-ratio: 800 / 300;
            border-radius: 2px 2px 0 0;
            overflow: hidden;
            border: 1px solid var(--accent);
            border-bottom: none;
            background: #000;
            transition: border-color 0.6s ease;
            z-index: 1;
        }

        .illustration-panel::after {
            content: "";
            position: absolute;
            inset: 0;
            background-image: radial-gradient(circle, rgba(0,0,0,0.9) 1px, transparent 1.2px);
            background-size: 3px 3px;
            mix-blend-mode: multiply;
            opacity: 0.55;
            pointer-events: none;
        }

        .illustration-bg {
            position: absolute;
            inset: 0;
        }

        .illustration-bg svg {
            width: 100%;
            height: 100%;
            display: block;
        }

        .illustration-vignette {
            position: absolute;
            inset: 0;
            background: linear-gradient(to top, rgba(0,0,0,0.75) 0%, rgba(0,0,0,0.25) 45%, rgba(0,0,0,0) 75%);
        }

        .illustration-location-chip {
            position: absolute;
            top: 8px;
            left: 8px;
            background: rgba(0,0,0,0.65);
            border: 1px solid var(--accent);
            color: var(--accent);
            font-family: 'Share Tech Mono', monospace;
            font-size: 0.55rem;
            letter-spacing: 0.06em;
            text-transform: uppercase;
            padding: 2px 8px;
            border-radius: 2px;
            transition: border-color 0.6s ease, color 0.6s ease;
        }

        .illustration-portrait {
            position: absolute;
            bottom: 0;
            right: 5%;
            width: 24%;
            max-width: 140px;
            transition: opacity 0.3s ease;
        }

        .illustration-portrait svg {
            width: 100%;
            height: auto;
            display: block;
            filter: drop-shadow(0 0 20px var(--accent-glow));
        }

        .illustration-portrait.hidden {
            opacity: 0;
        }

        .illustration-speaker-chip {
            position: absolute;
            bottom: 8px;
            left: 10px;
            font-family: 'Orbitron', sans-serif;
            font-weight: 800;
            font-size: 0.72rem;
            letter-spacing: 0.06em;
            text-transform: uppercase;
            color: var(--accent);
            text-shadow: 0 0 8px var(--accent), 0 2px 6px rgba(0,0,0,0.9);
            transition: color 0.6s ease;
        }

        .scene-content::before {
            content: "";
            position: absolute;
            top: 0;
            left: 50%;
            transform: translateX(-50%);
            width: 140%;
            height: 180px;
            background: radial-gradient(ellipse at top, var(--accent-glow), transparent 70%);
            pointer-events: none;
            z-index: 0;
            transition: background 0.8s ease;
        }

        .choices-container, .relationship-container {
            position: relative;
            z-index: 1;
        }

        .scene-text {
            font-family: 'Share Tech Mono', monospace;
            font-size: 0.82rem;
            line-height: 1.55;
            white-space: pre-line;
            background:
                radial-gradient(circle, rgba(255,255,255,0.035) 1px, transparent 1.2px) 0 0 / 4px 4px,
                linear-gradient(180deg, rgba(20,18,30,0.94), rgba(5,5,10,0.97));
            padding: 16px 18px;
            width: 100%;
            border: 1px solid var(--accent);
            border-top: 1px solid rgba(255,255,255,0.08);
            border-radius: 0 0 2px 2px;
            box-shadow: 0 20px 40px -24px var(--accent-glow);
            margin-bottom: 12px;
            flex-shrink: 0;
            min-height: auto;
            max-height: none;
            transition: border-color 0.6s ease, box-shadow 0.6s ease;
            position: relative;
            z-index: 1;
        }

        .scene-text.typing::after {
            content: "▌";
            animation: blink 1s infinite;
            color: var(--accent);
            font-weight: bold;
        }

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

        .choices-container {
            display: flex;
            flex-direction: column;
            gap: 6px;
            margin-top: 8px;
            width: 100%;
        }

        .choice-btn {
            font-family: 'Share Tech Mono', monospace;
            background: linear-gradient(180deg, rgba(30,25,50,0.55), rgba(15,12,25,0.7));
            border: 1px solid rgba(138, 109, 255, 0.25);
            padding: 9px 12px;
            text-align: left;
            cursor: pointer;
            transition: all 0.2s ease;
            font-size: 0.78rem;
            border-radius: 2px;
            display: flex;
            align-items: center;
            gap: 8px;
            color: #E9E9F0;
            width: 100%;
        }

        .choice-btn:hover {
            background: linear-gradient(180deg, rgba(40,34,64,0.65), rgba(20,16,32,0.8));
            border-color: var(--accent);
            transform: translateY(-1px);
            box-shadow: 0 4px 12px var(--accent-glow);
        }

        .choice-number {
            background: var(--accent-glow);
            border: 1px solid var(--accent);
            color: var(--accent);
            width: 18px;
            height: 18px;
            border-radius: 2px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 0.62rem;
            font-weight: 600;
            flex-shrink: 0;
            transition: background 0.5s ease, border-color 0.5s ease, color 0.5s ease;
        }

        .choice-btn.risky {
            border-color: rgba(255, 107, 107, 0.35);
        }

        .choice-btn.risky:hover {
            border-color: #FF6B6B;
            box-shadow: 0 4px 8px rgba(255, 107, 107, 0.25);
        }

        .risky-icon {
            color: #FF6B6B;
            margin-right: 6px;
            font-size: 0.8rem;
        }

        .relationship-container {
            border-radius: 2px !important;
            background: rgba(138, 109, 255, 0.05);
            padding: 10px 15px;
            border-radius: 8px;
            margin: 10px 0;
            border: 1px solid rgba(138, 109, 255, 0.1);
            width: 100%;
        }

        .relationship-container h3 {
            font-family: 'Orbitron', sans-serif;
            font-size: 0.6rem;
            letter-spacing: 0.08em;
            text-transform: uppercase;
            margin-bottom: 6px;
            color: #8A6DFF;
        }

        .relationship-bar {
            display: flex;
            align-items: center;
            gap: 10px;
            margin: 6px 0;
        }

        .relationship-fill {
            height: 5px;
            background: linear-gradient(90deg, #8A6DFF, #3A86FF);
            border-radius: 3px;
            transition: width 0.3s ease;
        }

        .relationship-label {
            font-size: 0.62rem;
            min-width: 110px;
            color: #B8C7D9;
        }

        .controls {
            display: flex;
            justify-content: space-between;
            padding: 12px 15px;
            background: #000;
            width: 100%;
            border-top: 1px solid #2A3B52;
            flex-shrink: 0;
        }

        .control-btn {
            background: #2A3B52;
            border: 1px solid #3A4B62;
            padding: 8px 15px;
            border-radius: 6px;
            cursor: pointer;
            display: flex;
            align-items: center;
            gap: 6px;
            font-size: 0.8rem;
            color: #E9E9F0;
            transition: all 0.2s;
        }

        .control-btn:hover {
            background: #3A4B62;
            border-color: #8A6DFF;
        }

        .control-btn:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }

        footer {
            width: 100%;
            text-align: center;
            padding: 10px;
            background: #000;
            border-top: 1px solid #2A3B52;
            flex-shrink: 0;
        }

        .attribution-footer {
            font-size: 0.7rem;
            color: #8A6DFF;
            line-height: 1.4;
            max-width: 800px;
            margin: 0 auto;
            padding: 10px;
            background: rgba(138, 109, 255, 0.05);
            border-radius: 8px;
            border: 1px solid rgba(138, 109, 255, 0.1);
        }

        .attribution-footer a {
            color: #3A86FF;
            text-decoration: none;
            font-weight: 500;
        }

        .attribution-footer a:hover {
            text-decoration: underline;
            color: #8A6DFF;
        }

        .overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.9);
            display: none;
            align-items: center;
            justify-content: center;
            z-index: 2000;
            padding: 15px;
            overflow-y: auto;
        }

        .overlay-content {
            background: #1E2B3C;
            border-radius: 12px;
            max-width: 700px;
            width: 100%;
            max-height: 85vh;
            overflow-y: auto;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
            position: relative;
            border: 1px solid #2A3B52;
        }

        .overlay-header {
            background: #152238;
            color: #E9E9F0;
            padding: 15px 20px;
            text-align: center;
            position: sticky;
            top: 0;
            z-index: 10;
            border-bottom: 1px solid #2A3B52;
        }

        .close-btn {
            position: absolute;
            right: 15px;
            top: 50%;
            transform: translateY(-50%);
            background: transparent;
            border: 1px solid #3A4B62;
            color: #E9E9F0;
            padding: 6px 12px;
            border-radius: 6px;
            cursor: pointer;
            display: flex;
            align-items: center;
            gap: 6px;
            transition: all 0.2s;
            font-size: 0.8rem;
        }

        .close-btn:hover {
            background: rgba(138, 109, 255, 0.2);
            border-color: #8A6DFF;
        }

        .overlay-body {
            padding: 20px;
            color: #E9E9F0;
        }

        .terminal-glow {
            background: #253447;
            padding: 20px;
            border-radius: 10px;
            border: 1px solid rgba(138, 109, 255, 0.2);
        }

        .story-title {
            font-family: 'Orbitron', sans-serif;
            font-size: 1.8rem;
            font-weight: 900;
            margin-bottom: 15px;
            color: #E9E9F0;
            text-align: center;
            background: linear-gradient(135deg, #8A6DFF, #3A86FF);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
        }

        .story-content {
            display: flex;
            flex-direction: column;
            gap: 15px;
        }

        .intro {
            font-size: 0.9rem;
            line-height: 1.5;
        }

        .highlight {
            color: #8A6DFF;
            font-weight: 600;
        }

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

        .section-world {
            border-color: rgba(138, 109, 255, 0.6);
        }

        .section-role {
            border-color: rgba(58, 134, 255, 0.6);
        }

        .section-gameplay {
            border-color: rgba(63, 187, 255, 0.6);
        }

        .section h2 {
            font-size: 1.3rem;
            margin-bottom: 8px;
            color: #E9E9F0;
        }

        .section p {
            font-size: 0.9rem;
            line-height: 1.5;
        }

        .quote {
            margin-top: 20px;
            padding: 15px;
            background: rgba(138, 109, 255, 0.05);
            border-radius: 8px;
            border: 1px solid rgba(138, 109, 255, 0.2);
            text-align: center;
        }

        .quote p {
            font-style: italic;
            color: #8A6DFF;
            font-size: 0.9rem;
        }

        .attribution {
            margin-top: 20px;
            padding: 15px;
            background: rgba(138, 109, 255, 0.1);
            border-radius: 8px;
            border: 1px solid rgba(138, 109, 255, 0.3);
            text-align: center;
        }

        .attribution p {
            color: #8A6DFF;
            font-size: 0.85rem;
        }

        .attribution a {
            color: #3A86FF;
            text-decoration: none;
            font-weight: 600;
        }

        .attribution a:hover {
            text-decoration: underline;
        }

        .save-indicator {
            position: fixed;
            bottom: 20px;
            right: 20px;
            background: #3AFF8F;
            color: #0A1A2F;
            padding: 8px 12px;
            border-radius: 6px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
            display: none;
            align-items: center;
            gap: 6px;
            font-size: 0.8rem;
            font-weight: 600;
            z-index: 1000;
            transition: background 0.2s;
        }

        /* ---------- Journey overlay ---------- */
        .journey-empty {
            text-align: center;
            color: #8FA0BD;
            font-size: 0.85rem;
            padding: 20px;
        }

        .journey-item {
            display: flex;
            align-items: center;
            gap: 12px;
            padding: 12px;
            border-radius: 8px;
            margin-bottom: 8px;
            border: 1px solid rgba(138, 109, 255, 0.15);
            background: rgba(138, 109, 255, 0.03);
            transition: all 0.2s;
        }

        .journey-item:hover {
            background: rgba(138, 109, 255, 0.1);
            border-color: #8A6DFF;
        }

        .journey-item.current {
            border-color: #8A6DFF;
            background: rgba(138, 109, 255, 0.15);
        }

        .journey-step-number {
            background: #2A3B52;
            color: #8A6DFF;
            width: 26px;
            height: 26px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 0.75rem;
            font-weight: 700;
            flex-shrink: 0;
        }

        .journey-step-content {
            flex: 1;
        }

        .journey-step-label {
            font-size: 0.9rem;
            font-weight: 600;
            color: #E9E9F0;
        }

        .journey-step-choice {
            font-size: 0.78rem;
            color: #8FA0BD;
            margin-top: 2px;
            font-style: italic;
        }

        .journey-jump-icon {
            color: #8A6DFF;
            font-size: 0.85rem;
            flex-shrink: 0;
        }

        .journey-hint {
            font-size: 0.75rem;
            color: #8FA0BD;
            text-align: center;
            margin-bottom: 15px;
            padding: 8px;
            background: rgba(138, 109, 255, 0.05);
            border-radius: 6px;
        }

        /* ---------- Loading state ---------- */
        .loading-text {
            display: flex;
            align-items: center;
            gap: 10px;
            color: #8FA0BD;
            font-style: italic;
        }

        .loading-text i {
            color: #8A6DFF;
        }

        .continue-banner {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 10px;
            background: rgba(138, 109, 255, 0.08);
            border: 1px solid rgba(138, 109, 255, 0.25);
            border-radius: 8px;
            padding: 10px 15px;
            margin-bottom: 12px;
            font-size: 0.8rem;
            width: 100%;
        }

        .continue-banner span {
            color: #B8C7D9;
        }

        .continue-banner-btn {
            background: #8A6DFF;
            border: none;
            color: white;
            padding: 6px 12px;
            border-radius: 6px;
            font-size: 0.75rem;
            cursor: pointer;
            white-space: nowrap;
        }

        .continue-banner-btn:hover {
            background: #7A5DEF;
        }

        /* ---------- Ending presentation ---------- */
        .ending-eyebrow {
            font-family: 'Share Tech Mono', monospace;
            font-size: 0.6rem;
            letter-spacing: 0.3em;
            text-transform: uppercase;
            color: var(--accent);
            text-align: center;
            margin: 16px 0 6px;
            opacity: 0.75;
        }

        .ending-title {
            font-family: 'Orbitron', sans-serif;
            font-weight: 900;
            font-size: 1.5rem;
            letter-spacing: 0.08em;
            text-transform: uppercase;
            text-align: center;
            color: var(--accent);
            text-shadow: 0 0 18px var(--accent), -1.5px 0 #00F0FF, 1.5px 0 #FF2E9A;
            margin-bottom: 16px;
        }

        .illustration-panel.ending-panel {
            border-width: 2px;
            box-shadow: 0 0 30px var(--accent-glow);
        }

        .illustration-panel.ending-panel::before {
            content: "";
            position: absolute;
            inset: 0;
            background: radial-gradient(ellipse at center, transparent 40%, rgba(0,0,0,0.55) 100%);
            pointer-events: none;
        }

        .ending-summary {
            width: 100%;
            background: linear-gradient(180deg, rgba(20,18,30,0.94), rgba(5,5,10,0.97));
            border: 1px solid var(--accent);
            border-top: none;
            border-radius: 0;
            padding: 14px 18px;
            margin-bottom: 12px;
            font-family: 'Share Tech Mono', monospace;
            font-size: 0.62rem;
            color: #8FA0BD;
        }

        .ending-summary-row {
            display: flex;
            justify-content: space-between;
            padding: 3px 0;
            border-bottom: 1px solid rgba(138,109,255,0.1);
        }

        .ending-summary-row:last-child {
            border-bottom: none;
        }

        .ending-summary-label {
            letter-spacing: 0.05em;
            text-transform: uppercase;
            opacity: 0.7;
        }

        .ending-summary-value {
            color: #E9E9F0;
            font-weight: 700;
        }

        .ending-discovered {
            text-align: center;
            font-size: 0.6rem;
            color: var(--accent);
            letter-spacing: 0.08em;
            text-transform: uppercase;
            margin-top: 10px;
            padding-top: 8px;
            border-top: 1px solid rgba(138,109,255,0.15);
        }

        .choice-btn.play-again {
            justify-content: center;
            font-family: 'Orbitron', sans-serif;
            font-weight: 700;
            letter-spacing: 0.1em;
            text-transform: uppercase;
            font-size: 0.72rem;
            padding: 12px;
            border: 1px solid var(--accent);
            background: var(--accent-glow);
        }

        .choice-btn.play-again:hover {
            background: var(--accent);
            color: #050403;
        }

        .act-transition {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.94);
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            z-index: 3000;
            opacity: 0;
            pointer-events: none;
            transition: opacity 0.6s ease;
        }

        .act-transition.visible {
            opacity: 1;
            pointer-events: all;
        }

        .act-title {
            font-family: 'Orbitron', sans-serif;
            font-weight: 900;
            font-size: 1.8rem;
            color: var(--accent);
            text-shadow: 0 0 16px var(--accent), -1.5px 0 #00F0FF, 1.5px 0 #FF2E9A;
            letter-spacing: 0.14em;
            text-align: center;
        }

        .act-subtitle {
            font-family: 'Share Tech Mono', monospace;
            font-style: italic;
            color: #B8C7D9;
            font-size: 1.1rem;
            margin-top: 10px;
            letter-spacing: 0.04em;
        }

        .scanlines {
            position: fixed;
            inset: 0;
            pointer-events: none;
            z-index: 1500;
            background-image: repeating-linear-gradient(
                to bottom,
                rgba(255,255,255,0.02) 0px,
                rgba(255,255,255,0.02) 1px,
                transparent 1px,
                transparent 3px
            );
            mix-blend-mode: overlay;
        }

        ::-webkit-scrollbar {
            width: 8px;
        }

        ::-webkit-scrollbar-track {
            background: #1A2B3F;
            border-radius: 4px;
        }

        ::-webkit-scrollbar-thumb {
            background: #8A6DFF;
            border-radius: 4px;
        }

        ::-webkit-scrollbar-thumb:hover {
            background: #3A86FF;
        }

        /* ---------- Light Mode Overrides ---------- */
        body.light-mode {
            background-color: #ffffff;
            background-image: radial-gradient(rgba(138,109,255,0.15) 1px, transparent 1px);
            color: #1a1a2e;
        }
        body.light-mode header,
        body.light-mode .status-bar,
        body.light-mode .breadcrumb-container,
        body.light-mode .controls,
        body.light-mode footer,
        body.light-mode .scene-content,
        body.light-mode .illustration-panel {
            background: #ffffff;
            color: #1a1a2e;
            border-color: rgba(138, 109, 255, 0.2);
        }
        body.light-mode .scene-content::before {
            background: radial-gradient(ellipse at top, rgba(138, 109, 255, 0.1), transparent 70%);
        }
        body.light-mode .illustration-panel::after {
            background-image: radial-gradient(circle, rgba(255,255,255,0.8) 1px, transparent 1.2px);
            mix-blend-mode: screen;
            opacity: 0.7;
        }
        body.light-mode .illustration-vignette {
            background: linear-gradient(to top, rgba(255,255,255,0.9) 0%, rgba(255,255,255,0.4) 45%, rgba(255,255,255,0) 75%);
        }
        body.light-mode .illustration-location-chip {
            background: rgba(255,255,255,0.85);
            color: var(--accent);
            border-color: var(--accent);
        }
        body.light-mode .illustration-portrait svg {
            filter: drop-shadow(0 0 10px rgba(138, 109, 255, 0.3));
        }
        body.light-mode .scene-text {
            background: #ffffff;
            color: #1a1a2e;
            border-color: rgba(138, 109, 255, 0.3);
            box-shadow: 0 15px 30px -15px rgba(138, 109, 255, 0.15);
        }
        body.light-mode h1, 
        body.light-mode .header-link {
            color: #1a1a2e;
        }
        body.light-mode h1 {
            text-shadow: none;
        }
        body.light-mode .breadcrumb-item {
            color: #5A6B82;
        }
        body.light-mode .breadcrumb-item.current {
            color: #1a1a2e;
            background: rgba(138, 109, 255, 0.15);
        }
        body.light-mode .choice-btn {
            background: #fdfdfd;
            color: #1a1a2e;
            border-color: rgba(138, 109, 255, 0.3);
        }
        body.light-mode .choice-btn:hover {
            background: #f0f0f5;
            border-color: var(--accent);
            box-shadow: 0 4px 12px rgba(138, 109, 255, 0.15);
        }
        body.light-mode .control-btn {
            background: #f0f0f5;
            color: #1a1a2e;
            border-color: rgba(138, 109, 255, 0.2);
        }
        body.light-mode .control-btn:hover {
            background: #ffffff;
            border-color: var(--accent);
        }
        body.light-mode .overlay-content {
            background: #ffffff;
            border-color: rgba(138, 109, 255, 0.2);
        }
        body.light-mode .overlay-header {
            background: #f5f5f5;
            color: #1a1a2e;
            border-color: rgba(138, 109, 255, 0.2);
        }
        body.light-mode .close-btn {
            color: #1a1a2e;
            border-color: rgba(138, 109, 255, 0.3);
        }
        body.light-mode .close-btn:hover {
            background: rgba(138, 109, 255, 0.1);
        }
        body.light-mode .overlay-body {
            color: #1a1a2e;
        }
        body.light-mode .terminal-glow {
            background: #fafafa;
            border: 1px solid rgba(138, 109, 255, 0.3);
        }
        body.light-mode .section h2,
        body.light-mode .story-title,
        body.light-mode .journey-step-label {
            color: #1a1a2e;
        }
        body.light-mode .ending-summary {
            background: #fafafa;
            border-color: rgba(138, 109, 255, 0.3);
            color: #1a1a2e;
        }
        body.light-mode .ending-summary-value {
            color: #1a1a2e;
        }
        body.light-mode .act-transition {
            background: rgba(255, 255, 255, 0.94);
        }
        body.light-mode .scanlines {
            background-image: repeating-linear-gradient(
                to bottom,
                rgba(0,0,0,0.02) 0px,
                rgba(0,0,0,0.02) 1px,
                transparent 1px,
                transparent 3px
            );
        }
    </style>
</head>
<body>
    <div class="scanlines"></div>
    <div class="main-container">
        <header>
            <h1>The Ghost of Molly Roger</h1>
            <div class="subtitle">A Psychic Adventure in Victorian England</div>
            <div class="header-links">
                <a class="header-link" id="about-link">
                    <i class="fas fa-info-circle"></i>
                    About This Game
                </a>
                <a class="header-link" id="story-link">
                    <i class="fas fa-book"></i>
                    Read The Story
                </a>
                <a class="header-link" id="journey-link">
                    <i class="fas fa-route"></i>
                    Your Journey
                </a>
            </div>
        </header>

        <div class="status-bar">
            <div class="status-item">
                <i class="fas fa-user"></i>
                <span>Student Psychic</span>
            </div>
            <div class="status-item">
                <i class="fas fa-brain"></i>
                <span id="psychic-level">Psychic Level: 1</span>
            </div>
            <div class="status-item">
                <i class="fas fa-compass"></i>
                <span id="path">Path: Undecided</span>
            </div>
        </div>

        <div class="breadcrumb-container" id="breadcrumb-container">
            <!-- Breadcrumb trail populated by JavaScript -->
        </div>

        <div class="scene-content">
            <div class="continue-banner" id="continue-banner" style="display:none;">
                <span><i class="fas fa-bookmark"></i> A saved journey was found.</span>
                <button class="continue-banner-btn" id="dismiss-continue-banner">Got it</button>
            </div>

            <div class="illustration-panel" id="illustration-panel">
                <div class="illustration-bg" id="illustration-bg"></div>
                <div class="illustration-vignette"></div>
                <div class="illustration-location-chip" id="illustration-location-chip"></div>
                <div class="illustration-portrait" id="illustration-portrait"></div>
                <div class="illustration-speaker-chip" id="illustration-speaker-chip"></div>
            </div>

            <div class="ending-eyebrow" id="ending-eyebrow" style="display:none;">The story ends</div>
            <div class="ending-title" id="ending-title" style="display:none;"></div>

            <div class="scene-text" id="scene-text">
                <!-- Text will be typed here -->
            </div>

            <div class="ending-summary" id="ending-summary" style="display:none;">
                <!-- Ending stats populated by JavaScript -->
            </div>

            <div class="relationship-container" id="relationship-container">
                <!-- Relationship bars will be populated by JavaScript -->
            </div>

            <div class="choices-container" id="choices-container">
                <!-- Choices will be populated by JavaScript -->
            </div>
        </div>

        <div class="controls">
            <button class="control-btn" id="restart-btn">
                <i class="fas fa-redo"></i>
                <span>Restart Game</span>
            </button>
            <button class="control-btn" id="save-btn">
                <i class="fas fa-save"></i>
                <span>Save Progress</span>
            </button>
        </div>
        <div id="persistence-note" style="width:100%; text-align:center; font-size:0.7rem; color:#8FA0BD; padding:4px 0 8px;"></div>

        <footer>
            <div class="attribution-footer">
                <p>
                    Based on the SCP Foundation tale
                    <a href="https://scp-wiki.wikidot.com/the-ghost-of-molly-roger" target="_blank">"The Ghost of Molly Roger"</a>
                    by psul. Licensed under
                    <a href="https://creativecommons.org/licenses/by-sa/3.0/" target="_blank">CC BY-SA 3.0</a>.
                </p>
                <p>
                    The SCP Foundation and all related content are the property of their respective authors and creators.
                </p>
            </div>
        </footer>
    </div>

    <div class="save-indicator" id="save-indicator">
        <i class="fas fa-check"></i>
        <span>Progress Saved</span>
    </div>

    <div class="act-transition" id="act-transition">
        <div class="act-title" id="act-title"></div>
        <div class="act-subtitle" id="act-subtitle"></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>
                <div class="subtitle">A Psychic Adventure in Victorian England</div>
            </div>

            <div class="overlay-body">
                <div class="terminal-glow">
                    <h1 class="story-title">THE STORY</h1>

                    <div class="story-content">
                        <p class="intro">
                            <span class="highlight">VICTORIAN ENGLAND, 1845</span> is a text-based adventure game
                            set in a world where psychic abilities are real but heavily regulated by authorities.
                        </p>

                        <div class="section section-world">
                            <h2>The World</h2>
                            <p>
                                The year is 1845. London is a city of contrasts -
                                gaslit streets and shadowy alleys,
                                rigid social norms and hidden psychic talents. The Ridgewell Edutainment Centre teaches
                                young psychics to control their abilities, but some powers cannot be contained.
                            </p>
                            <p>
                                In this world, psychic projections are real manifestations of thought and emotion.
                                The most famous of these is Molly Roger, a pirate phantom born from the mind of
                                bank clerk Roger Molly. She represents everything the authorities fear -
                                uncontrolled
                                creativity, dangerous individuality, and the power to reshape reality itself.
                            </p>
                        </div>

                        <div class="section section-role">
                            <h2>Your Role</h2>
                            <p>
                                You are a student at Ridgewell Edutainment Centre, just discovering your psychic abilities.
                                When Molly Roger contacts you, you're thrust into a world of conceptual piracy and psychic
                                rebellion. Every choice you make will determine not just your fate, but the future of
                                psychic expression in England.
                            </p>
                            <p>
                                Will you help Molly become fully real? Will you expose the school's secrets? Or will
                                you follow the approved path of regulated psychic development?
                            </p>
                        </div>

                        <div class="section section-gameplay">
                            <h2>The Gameplay</h2>
                            <p>
                                Navigate through branching storylines where your decisions matter. Build relationships
                                with key characters, develop your psychic abilities, and explore the Dreamscape where
                                thoughts become reality. Each path leads to different outcomes in this interactive
                                narrative experience.
                            </p>
                            <p>
                                Your choices affect your relationships with Molly Roger, Professor Worthington, and
                                other characters, opening up new story possibilities and changing how the narrative unfolds.
                                Every step you take is recorded in your Journey log, so you can always look back - or
                                leap back - to a fork you didn't take.
                            </p>
                        </div>

                        <div class="quote">
                            <p>"Some stories are better when shared between two souls."</p>
                        </div>

                        <div class="attribution">
                            <p>
                                <strong>Based on the SCP Foundation tale:</strong><br>
                                <a href="https://scp-wiki.wikidot.com/the-ghost-of-molly-roger" target="_blank">
                                    "The Ghost of Molly Roger" by psul
                                </a>
                            </p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- 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>The Ghost of Molly Roger</h1>
                <div class="subtitle">The Original Story</div>
            </div>

            <div class="overlay-body">
                <div class="terminal-glow">
                    <div class="story-content">
                        <p><strong>Listen.</strong></p>

                        <p>The Ridgewell Edutainment Centre was a place for the careful folding of young minds. The year was 1845. The air smelled of genetic samples and regret.</p>

                        <p>This is how the system works: they teach you to build cages, then they teach you to live inside them. The psychic ones, the ones who could project, were taught to make simple shapes. A sphere. A cube. Useful. Compliant. Dull.</p>

                        <p>So it goes.</p>

                        <p>I was one of those. A student psychic. My name is irrelevant, which is a professional way of saying it was a placeholder. A blank page in a ledger.</p>

                        <p>Then the page was written on by a ghost.</p>

                        <p>A pirate ghost.</p>

                        <p>Her name was Molly Roger. She was a psychic projection who had achieved what all the best ideas achieve: she had outgrown her creator.</p>

                        <p>The creator was a man named Roger Molly. A bank clerk. Can you imagine? He dreamed of ocean spray and freedom, and his brain, being a literal-minded organ, conjured a pirate. A woman. The inverse of himself. His own happy, horrid haunt.</p>

                        <p>He merged with her eventually. <em>Gone plunderin'</em>, he wrote to his sister. A perfect sentence, that. A full stop that is also a door swinging open into a new story.</p>

                        <p>But that was his story. This is mine.</p>

                        <p>Molly Roger slipped into my head during a lecture on controlled projection. Her voice was not a sound but a sensation, like the memory of sunlight on your skin in the depths of a London winter.</p>

                        <p><em>I be Molly Roger, lass!</em> it said. <em>Ye have the Sight!</em></p>

                        <p>It was an invitation to unlearn everything.</p>

                        <p>Which is the beginning of all real education. The kind they don't put on a curriculum.</p>

                        <p>So I met her in the Dreamspace. Which is just a fancy word for the shared attic of our minds, where we keep the things we're told to forget. Molly wanted to steal the letter 'W'.</p>

                        <p>A conceptual heist.</p>

                        <p>The grammar police were not amused.</p>

                        <p>But for three days, the British Empire stuttered and stammered, all its whats, whens, and whys temporarily on holiday. It was beautiful. It was a poem written in negative space.</p>

                        <p>It was also, the Board of Psychic Regulation informed us, a crime against the very nature of reality. Which is just their word for the way things are, as opposed to the way things could be.</p>

                        <p>The Board. Let me tell you about the Board. The Board is a they. They are a them. They are the people who believe a story should have only one ending, pre-approved and stamped in triplicate. They believe a mind should make cubes. Not pirates. Never pirates. Pirates are messy. Pirates change the ending halfway through.</p>

                        <p>Molly wanted to become real. Not just a thought, but a person. She needed my skin, she said. But she didn't mean it like a monster in a story. She meant it like a character in a poem, stepping off the page and into the life of the reader, asking for a cup of tea and a new point of view.</p>

                        <p>She showed me what happened to Roger Molly. It wasn't a death. It was a collaboration. A merger. Two solitudes protecting each other, two different songs learning to harmonise. He got her adventure. She got his reality. They both got to be someone new.</p>

                        <p>Professor Worthington, a man who smelled of mothballs and misplaced authority, offered me the approved path. A life of making psychic cubes in a world of straight lines. A safe, predictable story.<br>
                        A cage with very clean bars.</p>

                        <p>I looked at Molly. I looked at the cage.</p>

                        <p>The thing about cages is you can't see the outside from the inside. You can only see the bars.</p>

                        <p>We performed the ritual in the old bank where Roger Molly once worked. The air tasted of ozone and old ledgers. The Board arrived, of course. They always arrive. They are professional party-poopers, the sworn enemies of a good story.</p>

                        <p>Worthington shouted. "That projection will consume you!"</p>

                        <p>He had it backwards. A cage consumes you. A key sets you free.</p>

                        <p>The merger felt not like being eaten, but like a conversation that had been waiting to happen my whole life. My caution, her courage. Her laughter, my silence. We were not two jigsaw pieces forcing a fit.</p>

                        <p>We were two different pictures, and when you held them together, they made a third, more interesting picture. A picture that could move.</p>

                        <p>So it goes.</p>

                        <p>Now, we are a they. We are a them. We sail the Dreamspace, a pirate and a student, a ghost and a girl, a they and a them. The Board calls us rogue. We call us free.</p>

                        <p>Sometimes we are more pirate, stealing the concept of Monday and giving everyone a three-day weekend.</p>

                        <p>Sometimes we are more student, helping others draw outside the lines of their own minds. It depends on the weather. It depends on the story we want to tell that day.</p>

                        <p>The moral of the story?</p>

                        <p>There isn't one. That's the first thing they get wrong. Stories aren't here to moralize. They're here to be. To exist. To resonate.</p>

                        <p>Or maybe the resonance is the moral. The idea that some ghosts are more alive than the living. And that the best stories, the ones that truly matter, are the ones you have to steal for yourself from the vault of the world-as-it-is.</p>

                        <p><strong>And so it begins.</strong></p>

                        <div class="attribution">
                            <p>
                                <strong>Original SCP Foundation tale by psul:</strong><br>
                                <a href="https://scp-wiki.wikidot.com/the-ghost-of-molly-roger" target="_blank">
                                    Read the original story on the SCP Wiki
                                </a>
                            </p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- Journey Overlay -->
    <div class="overlay" id="journey-overlay">
        <div class="overlay-content">
            <div class="overlay-header">
                <button class="close-btn" id="close-journey">
                    <i class="fas fa-times"></i>
                    Close
                </button>
                <h1>Your Journey</h1>
                <div class="subtitle">Every fork you've taken so far</div>
            </div>

            <div class="overlay-body">
                <div class="journey-hint">
                    <i class="fas fa-info-circle"></i>
                    Click any earlier step to leap back to that moment. Your relationships and psychic level will rewind with you.
                </div>
                <div id="journey-list">
                    <!-- Journey steps populated by JavaScript -->
                </div>
            </div>
        </div>
    </div>

    <script>
        // ---------- THEME SYNC ----------
        function syncTheme() {
            try {
                let isDark = true;
                if (window.parent && window.parent.document) {
                    isDark = window.parent.document.documentElement.classList.contains('dark');
                } else {
                    const theme = localStorage.getItem('theme');
                    isDark = theme === 'dark' || (!theme && window.matchMedia('(prefers-color-scheme: dark)').matches);
                }
                
                if (!isDark) {
                    document.body.classList.add('light-mode');
                } else {
                    document.body.classList.remove('light-mode');
                }
            } catch (e) {
                console.warn("Could not sync theme", e);
            }
        }
        
        syncTheme();
        if (window.parent && window.parent.document) {
            try {
                const observer = new MutationObserver(syncTheme);
                observer.observe(window.parent.document.documentElement, { attributes: true, attributeFilter: ['class'] });
            } catch(e) {}
        }
        window.addEventListener('storage', syncTheme);

        // ---------- GAME DATA ----------
        function graceEpilogueText(state) {
            if (state.relationships["Grace"] >= 50) {
                return "Word reaches you, months later, folded into an ordinary letter. Grace has done it. Quietly, carefully, with every safeguard the two of you once discussed, she merged with her own long-suppressed Sight.\n\nShe signs the letter with a new steadiness in her handwriting: 'Grace - and something more.' The Board never even noticed. That, she writes, was rather the point.";
            }
            return "You hear about Grace secondhand, weeks later, from a nervous first-year. She tried her own merger alone, without the safeguards she once tried to teach you.\n\nInspector Vane caught her mid-ritual. She wasn't silenced - not quite - but she was quietly reassigned to a distant parish school, her research confiscated, her name struck from the register of promising students. You never got the chance to say goodbye.";
        }

        const scenes = {
            start: {
                id: "start",
                theme: "neutral",
                speaker: "Molly Roger",
                act: 1,
                text: "LONDON, 1845. RIDGEWELL EDUTAINMENT CENTRE.\n\nYou sit in class, the droning voice of Professor Worthington fading as a strange presence fills your mind. A pirate woman with wild eyes and a confident smirk appears in your thoughts.\n\n'I be Molly Roger, lass! Ye have the Sight! Meet me in Dreamspace tonight, or spend yer life as another dull London drone!'\n\nYour psychic abilities are awakening. The school board watches for unauthorised projections. Your future: Compliance or adventure?",
                choices: [
                    { text: "Investigate this Molly Roger in the library", nextScene: "research" },
                    { text: "Practise basic projections after class", nextScene: "practice" },
                    { text: "Report the incident to Professor Worthington", nextScene: "report" },
                ],
                relationshipChanges: {
                    "Molly Roger": 10,
                    "Professor Worthington": 0,
                    "Grace": 0
                },
                psychicChange: 0
            },

            research: {
                id: "research",
                theme: "neutral",
                speaker: "Grace",
                act: 1,
                text: "The Combination Library smells of old paper and genetic samples. You find Roger Molly's letters tucked away in the restricted section.\n\nAs you read about his transformation, a student approaches - Grace, the school's most talented psychic. 'I see you've found the good stuff. Molly contacted you too, didn't she? She's been recruiting.'\n\nGrace offers to help you understand your abilities, for a price: she needs help with her own 'extracurricular activities.'",
                choices: [
                    { text: "Accept Grace's offer - learn from the best", nextScene: "grace_help", relationshipChanges: {"Grace": 15} },
                    { text: "Decline politely and research alone", nextScene: "research_alone" },
                    { text: "Ask about Molly Roger's true intentions", nextScene: "molly_truth" },
                ],
                relationshipChanges: {
                    "Molly Roger": 5,
                    "Grace": 5
                },
                psychicChange: 1
            },

            practice: {
                id: "practice",
                theme: "dream",
                speaker: "Molly Roger",
                act: 1,
                text: "After classes, you sneak into the abandoned observatory. Your first attempts at projection produce only shimmering shapes, but then Molly Roger appears fully formed.\n\n'Not bad for a landlubber! Now, let's see if ye have real spirit. I need a partner for a little... conceptual acquisition.'\n\nShe explains her plan to 'borrow' the letter 'W' from the British alphabet. It's harmless fun that could get you expelled if caught.",
                choices: [
                    { text: "Join Molly's dream heist", nextScene: "dream_heist", relationshipChanges: {"Molly Roger": 20} },
                    { text: "Practise conventional projections instead", nextScene: "conventional" },
                    { text: "Ask Molly about Roger Molly's transformation", nextScene: "transformation_questions" },
                ],
                relationshipChanges: {
                    "Molly Roger": 25,
                    "Professor Worthington": -10
                },
                psychicChange: 2
            },

            dream_heist: {
                id: "dream_heist",
                theme: "dream",
                speaker: "Molly Roger",
                act: 2,
                text: "The Dreamscape shifts around you - a sea of concepts and memories. Molly teaches you to navigate this psychic ocean.\n\n'There be the Royal Alphabet Vault! Guarded by conceptual hounds and grammar police. We need a distraction while I snatch our prize.'\n\nYour projection skills are still developing, but Molly believes in you. This could prove your worth - or get your psychic abilities permanently restricted.",
                choices: [
                    { text: "Create a distraction with false memories", nextScene: "distraction_success" },
                    { text: "Try to reason with the conceptual guardians", nextScene: "reason_attempt" },
                    { text: "Let Molly take the lead completely", nextScene: "molly_lead", relationshipChanges: {"Molly Roger": -5} },
                ],
                relationshipChanges: {
                    "Molly Roger": 10
                },
                psychicChange: 3
            },

            distraction_success: {
                id: "distraction_success",
                theme: "dream",
                speaker: "Molly Roger",
                act: 2,
                text: "You project images of scandalous royal affairs, distracting the grammar police while Molly slips past. The letter 'W' disappears from British consciousness for three glorious days.\n\nMolly laughs uproariously. 'Ye have the makings of a proper pirate! Now, about that body I need...'\n\nShe grows serious, explaining her desire to become fully real. She believes you could help her achieve what Roger Molly ultimately did.\n\nBack in the waking halls of Ridgewell, you pass a woman in Board grey standing very still at the end of the corridor, a small ledger open in her gloved hands. Inspector Vane doesn't call out. She simply watches you walk past, makes a single mark, and closes the book.",
                choices: [
                    { text: "Promise to help her become real", nextScene: "help_molly", relationshipChanges: {"Molly Roger": 25} },
                    { text: "Suggest finding another way", nextScene: "alternative_solutions" },
                    { text: "Ask what happened to Roger Molly exactly", nextScene: "roger_fate" },
                ],
                relationshipChanges: {
                    "Molly Roger": 15
                },
                psychicChange: 2
            },

            grace_help: {
                id: "grace_help",
                theme: "neutral",
                speaker: "Grace",
                act: 1,
                text: "Grace's hideout is hidden behind a false bookshelf in the library. Stolen projection equipment and forbidden texts fill the room.\n\n'First lesson: Projection isn't about creating something new. It's about revealing what's already there in your soul.' She gestures to your shimmering form. 'Molly thinks she created herself, but Roger Molly had that pirate in him all along.'\n\nGrace offers proper training, but warns that the school board is cracking down on 'unauthorised creativity.'",
                choices: [
                    { text: "Begin training with Grace", nextScene: "grace_training", relationshipChanges: {"Grace": 20} },
                    { text: "Ask about her connection to Molly Roger", nextScene: "grace_connection" },
                    { text: "This feels too dangerous - leave", nextScene: "research_alone", relationshipChanges: {"Grace": -15} },
                ],
                relationshipChanges: {
                    "Grace": 10
                },
                psychicChange: 2
            },

            transformation_questions: {
                id: "transformation_questions",
                theme: "dream",
                speaker: "Molly Roger",
                act: 2,
                text: "Molly's projection flickers. 'Roger Molly... he didn't disappear. We merged. Two souls sharing one skin, like sailors sharing a hammock.'\n\nShe shows you memories: Roger Molly's quiet London life, her boisterous dream existence, and the moment they decided neither was complete alone.\n\n'They call it a merger, but it's more like... becoming whole. Roger Molly got adventure. I got reality. We both got to be someone new.'",
                choices: [
                    { text: "That sounds wonderful", nextScene: "embrace_possibility", relationshipChanges: {"Molly Roger": 15} },
                    { text: "That sounds terrifying", nextScene: "fear_possibility", relationshipChanges: {"Molly Roger": -10} },
                    { text: "Ask if you could meet Roger Molly", nextScene: "meet_roger" },
                ],
                relationshipChanges: {
                    "Molly Roger": 5
                },
                psychicChange: 1
            },

            help_molly: {
                id: "help_molly",
                theme: "dream",
                speaker: "Molly Roger",
                act: 2,
                text: "Molly's excitement is palpable. 'There be two paths, lass! The slow way - months of psychic practice until we can merge proper. Or the fast way - a ritual at the old bank where Roger Molly worked. Risky, but immediate.'\n\nMeanwhile, Professor Worthington has noticed your unusual psychic activity. He offers you a position on the 'approved psychics' list if you renounce Molly's influence.",
                choices: [
                    { text: "Begin slow training with Molly", nextScene: "slow_path", relationshipChanges: {"Molly Roger": 10, "Professor Worthington": -20} },
                    { text: "Plan the bank ritual", nextScene: "vane_confrontation", relationshipChanges: {"Molly Roger": 15, "Professor Worthington": -30} },
                    { text: "Consider Worthington's offer", nextScene: "worthington_offer", relationshipChanges: {"Molly Roger": -25, "Professor Worthington": 20} },
                ],
                relationshipChanges: {
                    "Molly Roger": 5
                },
                psychicChange: 1
            },

            bank_planning: {
                id: "bank_planning",
                theme: "dream",
                speaker: "Molly Roger",
                act: 3,
                text: "The old bank stands abandoned, but psychic residue from Roger Molly's time there makes it a perfect convergence point.\n\nGrace appears unexpectedly. 'I helped Roger Molly with his merger. It's dangerous - some personalities don't blend well. You could lose yourself entirely.'\n\nMolly scoffs. 'Grace always was the cautious one! Life's about risk, me heartie!'",
                choices: [
                    { text: "Trust Molly and proceed", nextScene: "bank_ritual", relationshipChanges: {"Molly Roger": 20, "Grace": -15} },
                    { text: "Listen to Grace's concerns", nextScene: "grace_warning", relationshipChanges: {"Molly Roger": -10, "Grace": 15} },
                    { text: "Ask Grace about helping Roger Molly", nextScene: "roger_story" },
                ],
                relationshipChanges: {
                    "Molly Roger": 5,
                    "Grace": 5
                },
                psychicChange: 1
            },

            bank_ritual: {
                id: "bank_ritual",
                theme: "dream",
                speaker: "Professor Worthington",
                act: 3,
                text: "The ritual chamber hums with psychic energy. Molly's projection grows solid, her pirate garb becoming real cloth and leather.\n\n'Ready to become legend, lass? This will change everything.'\n\nProfessor Worthington bursts in, enforcers behind him - and Inspector Vane close at his shoulder, calm as a closing door. 'Stop this madness!' Worthington shouts. 'That projection will consume you!' Vane says nothing at all. She doesn't need to.",
                choices: [
                    { text: "Complete the ritual quickly", risky: true, nextScene: (state) => (state.psychicLevel >= 5 && state.relationships["Molly Roger"] >= 40) ? "ritual_complete" : "ritual_consumed" },
                    { text: "Try to reason with Worthington", nextScene: "reason_with_professor" },
                    { text: "Have Molly create a distraction", nextScene: "escape_attempt", relationshipChanges: {"Molly Roger": 10} },
                ],
                relationshipChanges: {
                    "Professor Worthington": -10
                },
                psychicChange: 2
            },

            ritual_complete: {
                id: "ritual_complete",
                theme: "dream",
                speaker: null,
                act: 3,
                text: "Energy floods through you. Molly's laughter mixes with your thoughts, her memories becoming yours. When you open your eyes, you see through both sets of memories.\n\nYou're still you, but more. Molly's confidence, Roger Molly's kindness, your own determination - woven together into something new.\n\nThe school enforcers stare in shock as you stand, a psychic aura glowing around you. Worthington whispers, 'What have we done?'",
                choices: [
                    { text: "Embrace your new existence", nextScene: "grace_epilogue_pirate" },
                    { text: "Try to reverse the process", nextScene: "reversal_attempt" },
                ],
                relationshipChanges: {
                    "Molly Roger": 50
                },
                psychicChange: 10
            },

            pirate_life: {
                id: "pirate_life",
                theme: "dream",
                speaker: null,
                ending: true,
                endingTitle: "True Merger",
                text: "You and Molly sail the conceptual seas, a merged being with two souls. The Board never manages to properly explain what happened at the old bank that night, and Inspector Vane's official report calls it 'inconclusive' - the closest she has ever come to admitting she was wrong. She still watches, sometimes, from a distance. She has never come closer.\n\nProfessor Worthington resigns his post within the year. He writes to you exactly once: 'I was afraid of what I didn't understand. I hope you'll forgive an old man for it.'\n\nSome days you're more Molly, pulling grand heists in Dreamspace. Other days you're more yourself, helping other young psychics find their path.\n\nGrace sends occasional letters: 'Roger Molly would be proud. You found a better balance than we ever did.'\n\nSome stories are better when shared.",
                choices: [
                    { text: "Play Again", nextScene: "start" },
                ],
                relationshipChanges: {},
                psychicChange: 0
            },

            slow_path: {
                id: "slow_path",
                theme: "dream",
                speaker: null,
                act: 3,
                text: "Months pass. You and Molly grow closer through shared dreams and small adventures. Your projection skills become legendary at school, even as you hide their true purpose.\n\nGraduation approaches. Professor Worthington offers you a teaching position, while Molly has located a ship and crew waiting for a captain.\n\nYour bond with Molly is now strong enough for safe merger, but is it what you still want?",
                choices: [
                    { text: "Merge with Molly and become pirates", nextScene: "grace_epilogue_peaceful" },
                    { text: "Accept the teaching position", nextScene: "teacher_ending" },
                    { text: "Remain friends but stay separate", nextScene: "friends_ending" },
                ],
                relationshipChanges: {
                    "Molly Roger": 10
                },
                psychicChange: 5
            },

            peaceful_merger: {
                id: "peaceful_merger",
                theme: "dream",
                speaker: null,
                ending: true,
                endingTitle: "Balanced Existence",
                text: "The merger is gentle, like two rivers joining. You remain conscious of both identities, able to shift between them or blend as needed.\n\nInspector Vane investigates for months and finds nothing she can prove - no ritual performed in haste, no rule broken she can point to in writing. She closes the file at last with a single handwritten note: 'No violation found.' It is, from her, as close to surrender as the Board allows.\n\nProfessor Worthington becomes an unlikely advocate, quietly steering nervous new students your way instead of turning them in. You become the most famous psychic pirate in history, but also a mentor to young psychics.\n\nYour legend grows: the teacher who became a pirate, the projection who became real, the student who changed everything.\n\nWhy choose one life when you can live both?",
                choices: [
                    { text: "Play Again", nextScene: "start" },
                ],
                relationshipChanges: {},
                psychicChange: 0
            },

            report: {
                id: "report",
                theme: "institution",
                speaker: "Professor Worthington",
                act: 1,
                text: "Professor Worthington listens gravely. 'Molly Roger... we'd hoped she'd faded away. She represents everything the Board fears - uncontrolled creativity, dangerous individuality.'\n\nHe glances toward the door, where a spectacled woman in Board grey has been listening without a word. 'Inspector Vane oversees cases like this now,' he says quietly, once she's gone. 'She's the reason I can't simply look the other way anymore.'\n\nHe offers to become your mentor, protecting you from Molly's influence in exchange for your loyalty. The safe path, the approved path.\n\nBut that night, Molly appears in your dreams anyway. 'So ye chose the cage over the sea? Pity. I'll find another heart brave enough.'",
                choices: [
                    { text: "Accept Worthington's mentorship", nextScene: "approved_psychic" },
                    { text: "Change your mind and contact Molly", nextScene: "contact_molly_late", relationshipChanges: {"Professor Worthington": -30} },
                ],
                relationshipChanges: {
                    "Professor Worthington": 20,
                    "Molly Roger": -20
                },
                psychicChange: 0
            },

            approved_psychic: {
                id: "approved_psychic",
                theme: "institution",
                speaker: null,
                ending: true,
                endingTitle: "Approved Psychic",
                text: "Years pass. You become a respected psychic scholar, publishing papers on 'safe projection techniques.' Your life is comfortable, approved, and utterly predictable.\n\nInspector Vane's file on you closes quietly within the year, marked simply: 'No further activity.' She moves on to the next name on her list, and you feel relief, and something else too, something you don't examine too closely.\n\nProfessor Worthington remains your mentor and, eventually, something like a friend, though neither of you ever quite says so.\n\nOccasionally you hear rumours: Molly found another student, they merged, they're sailing the conceptual seas having adventures you can only imagine.\n\nYou have security, respect, and the Board's approval. But sometimes, in quiet moments, you hear ghostly laughter on the wind.\n\nThe safest cage is the one you don't see.",
                choices: [
                    { text: "Play Again", nextScene: "start" },
                ],
                relationshipChanges: {},
                psychicChange: 0
            },

            research_alone: {
                id: "research_alone",
                theme: "neutral",
                speaker: null,
                act: 1,
                text: "You continue researching alone, finding more of Roger Molly's letters. They reveal his growing connection to Molly and his eventual decision to merge with her.\n\nThe more you learn, the more you understand why the school fears such unions - they create beings that cannot be controlled, that exist beyond approved psychic boundaries.\n\nWhat will you do with this knowledge?",
                choices: [
                    { text: "Seek out Molly with your new understanding", nextScene: "practice" },
                    { text: "Share your findings with Professor Worthington", nextScene: "report" },
                    { text: "Look for Grace to discuss what you've learned", nextScene: "grace_help" },
                ],
                relationshipChanges: {
                    "Molly Roger": 5
                },
                psychicChange: 2
            },

            conventional: {
                id: "conventional",
                theme: "institution",
                speaker: null,
                act: 1,
                text: "You focus on approved projection techniques, creating simple shapes and controlled manifestations. Professor Worthington praises your progress.\n\nBut at night, Molly's voice still whispers at the edge of your consciousness, promising adventures beyond the school's rigid curriculum.\n\nThe pull between safety and freedom grows stronger with each passing day.",
                choices: [
                    { text: "Continue with conventional training", nextScene: "approved_psychic" },
                    { text: "Give in and contact Molly", nextScene: "dream_heist", relationshipChanges: {"Molly Roger": 10} },
                ],
                relationshipChanges: {
                    "Professor Worthington": 10
                },
                psychicChange: 1
            },

            teacher_ending: {
                id: "teacher_ending",
                theme: "institution",
                speaker: null,
                ending: true,
                endingTitle: "The Reformer",
                text: "You accept Professor Worthington's offer and become a teacher at Ridgewell. Over the years, you subtly introduce more creative approaches to psychic development, slowly changing the system from within.\n\nInspector Vane still visits, twice a year, precise as clockwork, and reviews your lesson plans without ever quite finding grounds to object. You've learned to write your rebellions between the lines she's trained herself not to read. It is, in its way, a kind of truce.\n\nMolly visits your dreams occasionally, sharing tales of her adventures. Though you chose different paths, you remain connected through the bond you forged.\n\nYour students never know about your past with the legendary psychic pirate, but they benefit from the balance you've found between tradition and innovation.\n\nSometimes the most revolutionary acts happen quietly.",
                choices: [
                    { text: "Play Again", nextScene: "start" },
                ],
                relationshipChanges: {},
                psychicChange: 0
            },

            friends_ending: {
                id: "friends_ending",
                theme: "neutral",
                speaker: null,
                ending: true,
                endingTitle: "The Diplomat",
                text: "You and Molly remain close friends and allies, but maintain your separate existences. You become a bridge between the psychic establishment and the rogue elements in Dreamspace.\n\nInspector Vane becomes, against every instinct either of you has, something like a professional counterpart. She never approves of what you do. But she has learned to call on you before she acts instead of after, and you have learned that's the only kind of victory some people are capable of offering.\n\nProfessor Worthington quietly supplies you with warnings when the Board grows restless, his old fear of Molly worn down at last into something more like respect.\n\nYour unique position allows you to negotiate treaties, prevent conflicts, and create new opportunities for psychic expression that satisfy both traditionalists and rebels. Molly still pulls her conceptual heists, but now she shares the spoils with the school - on her terms, of course.\n\nNot every partnership requires merging souls.",
                choices: [
                    { text: "Play Again", nextScene: "start" },
                ],
                relationshipChanges: {},
                psychicChange: 0
            },

            molly_truth: {
                id: "molly_truth",
                theme: "neutral",
                speaker: "Grace",
                act: 1,
                text: "Grace leans closer, her voice dropping to a whisper. 'Molly wants what all projections want - to be real. But she's different. Most projections fade when their creators lose interest, but Molly... she's grown beyond Roger Molly.'\n\nShe glances around nervously before continuing. 'The Board fears her because she represents a new kind of being - one that doesn't need a creator to exist. If she succeeds in becoming fully real, it would change everything we know about psychic abilities.'",
                choices: [
                    { text: "That makes me want to help her more", nextScene: "practice", relationshipChanges: {"Molly Roger": 15} },
                    { text: "That sounds dangerously unstable", nextScene: "report", relationshipChanges: {"Molly Roger": -10} },
                    { text: "Ask Grace why she's helping Molly", nextScene: "grace_motives" },
                ],
                relationshipChanges: {
                    "Grace": 5
                },
                psychicChange: 1
            },

            grace_motives: {
                id: "grace_motives",
                theme: "neutral",
                speaker: "Grace",
                act: 1,
                text: "Grace's expression softens. 'My grandmother was one of Roger Molly's first students. She believed in what he and Molly were trying to achieve - a world where we don't have to choose between being psychic and being ourselves.'\n\nShe shows you a faded photograph of a woman who looks remarkably like Molly Roger. 'The Board took everything from my family when they declared Roger Molly a rogue. I'm not just helping Molly - I'm finishing what my grandmother started.'",
                choices: [
                    { text: "Join Grace in her mission", nextScene: "grace_help", relationshipChanges: {"Grace": 20} },
                    { text: "This is too personal for me", nextScene: "research_alone", relationshipChanges: {"Grace": -10} },
                ],
                relationshipChanges: {
                    "Grace": 10
                },
                psychicChange: 1
            },
            reason_attempt: {
                id: "reason_attempt",
                theme: "dream",
                speaker: null,
                act: 2,
                text: "You step forward, addressing the conceptual hounds and grammar police directly. 'We mean no lasting harm - only a brief loan, freely returned.'\n\nThe guardians pause, considering. Whether they yield depends on how convincingly you've mastered your own mind so far - and whether Molly trusts you enough to let you speak for her.",
                choices: [
                    { text: "See if your words are enough", risky: true, nextScene: (state) => (state.psychicLevel >= 3) ? "distraction_success" : "silenced" },
                    { text: "Retreat and let Molly handle it", nextScene: "molly_lead" },
                ],
                relationshipChanges: {},
                psychicChange: 0
            },

            molly_lead: {
                id: "molly_lead",
                theme: "dream",
                speaker: "Molly Roger",
                act: 2,
                text: "You hang back, letting Molly work alone. She's audacious but reckless without a steadying hand - she comes back clutching only a stray comma instead of the full letter, a smaller victory, and a flicker of disappointment crosses her projection.\n\n'Ye could've helped, lass. Still - not nothing, eh?'",
                choices: [
                    { text: "Ask what she plans to do with it", nextScene: "help_molly" },
                    { text: "Promise to be braver next time", nextScene: "distraction_success" },
                ],
                relationshipChanges: {
                    "Molly Roger": -5
                },
                psychicChange: 1
            },

            alternative_solutions: {
                id: "alternative_solutions",
                theme: "dream",
                speaker: null,
                act: 2,
                text: "You propose a middle path: what if Molly didn't need to become fully real at all? What if there were psychic sanctuaries - places where projections like her could exist safely, semi-real, without the all-or-nothing leap Roger Molly took?\n\nMolly considers this, arms crossed, clearly unconvinced but curious enough to hear more.",
                choices: [
                    { text: "Research sanctuary theory with Grace", nextScene: "grace_connection" },
                    { text: "Decide the safe theory isn't enough - help her merge after all", nextScene: "help_molly", relationshipChanges: {"Molly Roger": 10} },
                ],
                relationshipChanges: {},
                psychicChange: 1
            },

            roger_fate: {
                id: "roger_fate",
                theme: "dream",
                speaker: "Molly Roger",
                act: 2,
                text: "Molly's projection softens as she tells it properly this time - not the legend, the truth. Roger Molly didn't vanish in a flash of light. It took him months of quiet, deliberate practice, each session drawing them a little closer, until the merger felt inevitable rather than dangerous.\n\n'He wasn't reckless, lass. He was ready. That's the difference.'",
                choices: [
                    { text: "That settles it - commit to helping her merge", nextScene: "help_molly", relationshipChanges: {"Molly Roger": 10} },
                    { text: "You want proof this is safe first - find Grace", nextScene: "grace_connection" },
                ],
                relationshipChanges: {},
                psychicChange: 1
            },

            grace_training: {
                id: "grace_training",
                theme: "neutral",
                speaker: "Grace",
                act: 2,
                text: "Weeks of secret training follow. Grace is a patient, exacting teacher, and your projections sharpen from shimmering guesses into deliberate, controlled shapes.\n\nOne evening she sets down her chalk and admits something she hasn't told anyone: she's been quietly working against the Board for years, ever since they erased her grandmother's part in Roger Molly's story. She's not just teaching you. She's recruiting you.\n\nA shadow crosses the frosted glass of the library door. Grace goes still, chalk halfway to the slate, until the footsteps pass on. 'Inspector Vane,' she says quietly, once it's safe. 'She doesn't knock. She just... checks. She's been checking on me for a year.'",
                choices: [
                    { text: "Continue toward Molly's heist", nextScene: "practice" },
                    { text: "Ask Grace to stand with you at the ritual, when the time comes", nextScene: "roger_story" },
                ],
                relationshipChanges: {
                    "Grace": 10
                },
                psychicChange: 3
            },

            grace_connection: {
                id: "grace_connection",
                theme: "neutral",
                speaker: "Grace",
                act: 1,
                text: "Grace hesitates, then shows you a faded photograph: a woman who looks remarkably like Molly Roger, but isn't. 'My grandmother. She stood witness at Roger Molly's own merger. The Board took her post at the school for it, and erased the record afterward - afraid of what precedent it might set.'\n\nHer voice drops. 'I have the Sight too, same as you. I've just never dared use it the way Molly's asking you to.'",
                choices: [
                    { text: "Trust Grace fully - train with her", nextScene: "grace_training" },
                    { text: "This is a lot to take in - step back and think", nextScene: "research_alone" },
                ],
                relationshipChanges: {
                    "Grace": 10
                },
                psychicChange: 1
            },

            embrace_possibility: {
                id: "embrace_possibility",
                theme: "dream",
                speaker: "Molly Roger",
                act: 2,
                text: "You let yourself imagine it fully - a life fused with Molly's spirit, adventure and caution woven together. Molly lights up at your enthusiasm and pulls you deeper into Dreamspace, showing you flickering visions of possible futures: a ship's deck, a lecture hall, a sanctuary built from stolen concepts.\n\n'See? It's not an ending, lass. It's a whole new chapter.'",
                choices: [
                    { text: "Pursue this - join the dream heist", nextScene: "dream_heist" },
                    { text: "Ask what could go wrong, just to be sure", nextScene: "fear_possibility" },
                ],
                relationshipChanges: {
                    "Molly Roger": 10
                },
                psychicChange: 1
            },

            fear_possibility: {
                id: "fear_possibility",
                theme: "dream",
                speaker: "Molly Roger",
                act: 2,
                text: "Molly's bravado drops, just for a moment. 'Mergers can go wrong, aye. Rush it, or do it without trust between ye, and some psychics come out the other side... hollow. The Board calls them Hollow Ones. Husks wearing a person's face.'\n\nShe meets your eyes, unusually serious. 'That's why I'd rather wait for ye to be ready than have ye be sorry.'",
                choices: [
                    { text: "Agree - build trust slowly first", nextScene: "practice" },
                    { text: "Decide the risk isn't worth it - report to Worthington", nextScene: "report", relationshipChanges: {"Molly Roger": -15} },
                ],
                relationshipChanges: {},
                psychicChange: 0
            },

            meet_roger: {
                id: "meet_roger",
                theme: "dream",
                speaker: "Molly Roger",
                act: 1,
                text: "Molly hesitates, then draws you deeper into Dreamspace, past the sea of stolen concepts, into a quiet inner chamber. A calm, warm presence greets you there - not quite Roger, not quite Molly, but both at once.\n\n'The merger was a gift, not a loss,' the presence tells you gently. 'But the Board's grown crueller since my time. Be careful who you trust with your plans.'",
                choices: [
                    { text: "Feel reassured - continue toward practice", nextScene: "practice" },
                    { text: "Ask what 'crueller' means now", nextScene: "grace_connection" },
                ],
                relationshipChanges: {
                    "Molly Roger": 10
                },
                psychicChange: 2
            },

            worthington_offer: {
                id: "worthington_offer",
                theme: "institution",
                speaker: "Professor Worthington",
                act: 2,
                text: "You meet Worthington privately, in his cramped, mothball-scented office. He isn't cruel, you realise - he's afraid. He shows you records of psychics who merged badly and came out the other side as Hollow Ones, empty and unrecoverable.\n\n'I can protect you from that fate,' he says quietly. Then, lower: 'Inspector Vane already suspects. If it comes from me first, I might be able to soften how this goes for you. If it comes from her alone, I can't promise anything.'",
                choices: [
                    { text: "Betray Molly's plans to protect yourself", risky: true, nextScene: "informant" },
                    { text: "Protect Molly and lie to his face", nextScene: "bank_planning", relationshipChanges: {"Professor Worthington": -10} },
                ],
                relationshipChanges: {},
                psychicChange: 0
            },

            informant: {
                id: "informant",
                theme: "institution",
                speaker: null,
                ending: true,
                endingTitle: "The Informant",
                text: "You tell Worthington everything, and within the hour, Inspector Vane has it too - written up, signed, filed. She thanks you with the same flat courtesy she'd show anyone useful.\n\nThat night, her enforcers raid the old bank. You feel Molly sense the betrayal a heartbeat before they arrive - her presence recoils in your mind, then goes silent. Grace never speaks to you again.\n\nYou keep your position, your safety, your unremarkable life. Years later, in quiet unguarded moments, you'll wonder whether the empire's straight lines were ever worth a pirate's silenced laughter.\n\nSome cages are built by the people who lock themselves inside them.",
                choices: [
                    { text: "Play Again", nextScene: "start" },
                ],
                relationshipChanges: {},
                psychicChange: 0
            },

            grace_warning: {
                id: "grace_warning",
                theme: "neutral",
                speaker: "Grace",
                act: 3,
                text: "You pause before the ritual. Grace walks you through proper safeguards - anchoring phrases, a trusted witness, doing it only once both of you are certain, not just eager. It isn't the fast path, but it's a careful one.\n\nWith her help, you and Molly prepare properly, this time.",
                choices: [
                    { text: "Proceed to the ritual, now properly prepared", nextScene: "bank_ritual", relationshipChanges: {"Molly Roger": 15, "Grace": 10} },
                    { text: "Decide it's too risky after all - walk away", nextScene: "friends_ending" },
                ],
                relationshipChanges: {},
                psychicChange: 1
            },

            roger_story: {
                id: "roger_story",
                theme: "neutral",
                speaker: "Grace",
                act: 3,
                text: "Grace tells it plainly: her grandmother stood witness at Roger Molly's own merger, decades ago, in this very building. Afterward, the Board erased the record entirely, afraid of the precedent a successful, willing merger might set for other students.\n\n'They didn't fear that it failed,' Grace says. 'They feared that it worked.'",
                choices: [
                    { text: "This gives you confidence - proceed to the ritual", nextScene: "bank_ritual" },
                    { text: "Ask Grace to stand witness for you too", nextScene: "grace_warning" },
                ],
                relationshipChanges: {
                    "Grace": 10
                },
                psychicChange: 1
            },

            reason_with_professor: {
                id: "reason_with_professor",
                theme: "institution",
                speaker: null,
                act: 3,
                text: "'This isn't consumption,' you tell Worthington, standing your ground. 'It's collaboration. Ask Roger Molly, if you don't believe me - he's still in there, and he's not sorry.'\n\nWorthington hesitates, torn between fear and something that might be doubt. Whether he stands his enforcers down depends on whether you've given him any reason, until now, to trust you.",
                choices: [
                    { text: "See if he listens", nextScene: (state) => (state.relationships["Professor Worthington"] >= 20) ? "ritual_complete" : "silenced" },
                    { text: "Give up talking and act fast instead", nextScene: "escape_attempt" },
                ],
                relationshipChanges: {},
                psychicChange: 0
            },

            escape_attempt: {
                id: "escape_attempt",
                theme: "dream",
                speaker: null,
                act: 3,
                text: "Molly conjures a squall of illusory gulls and cannon-smoke, filling the chamber with chaos. In the confusion, you finish binding the ritual before the enforcers can reach you - breathless, half-laughing, entirely too close for comfort.",
                choices: [
                    { text: "Finish becoming whole", nextScene: "ritual_complete" },
                ],
                relationshipChanges: {
                    "Molly Roger": 10
                },
                psychicChange: 2
            },

            reversal_attempt: {
                id: "reversal_attempt",
                theme: "dream",
                speaker: "Molly Roger",
                act: 3,
                text: "You try to pull back, to unmake what's been done - but a merger, once willingly begun, doesn't unravel so easily. Molly's voice, gentler now than you've ever heard it, tells you it's alright, that you don't have to be afraid, that you can still choose how you live this.\n\nSlowly, you stop fighting it.",
                choices: [
                    { text: "Accept what you've become", nextScene: "grace_epilogue_pirate" },
                ],
                relationshipChanges: {
                    "Molly Roger": 20
                },
                psychicChange: 3
            },

            silenced: {
                id: "silenced",
                theme: "institution",
                speaker: null,
                ending: true,
                endingTitle: "Silenced",
                text: "Inspector Vane's enforcers close in, and whatever confidence you had wasn't enough. Vane herself fastens the suppression collar - cold iron worked with dampening sigils - with the brisk efficiency of someone who has done this before.\n\nMolly's voice fades from your mind like a tide going out - not destroyed, the Board insists, only 'quieted.' You spend the rest of your years at Ridgewell as a cautionary tale told to first-years: the promising student who reached for too much, too soon.\n\nSometimes, late at night, you think you still hear pirate laughter at the edge of hearing, just out of reach.\n\nThe safest cage is still a cage.",
                choices: [
                    { text: "Play Again", nextScene: "start" },
                ],
                relationshipChanges: {},
                psychicChange: 0
            },

            contact_molly_late: {
                id: "contact_molly_late",
                theme: "dream",
                speaker: "Molly Roger",
                act: 2,
                text: "That night you reach into Dreamspace, desperate, calling for her. Molly appears, arms crossed, wary in a way she never was before.\n\n'Changed yer mind, have ye? Trust's not so easily won back, lass.' She'll work with you - but she keeps you at arm's length from here on.",
                choices: [
                    { text: "Prove yourself through practice", nextScene: "practice" },
                    { text: "Accept a more cautious, distant partnership", nextScene: "friends_ending" },
                ],
                relationshipChanges: {
                    "Molly Roger": 5
                },
                psychicChange: 0
            },

            ritual_consumed: {
                id: "ritual_consumed",
                theme: "dream",
                speaker: null,
                ending: true,
                endingTitle: "Consumed",
                text: "You rush the binding, impatient, underprepared. For a moment it feels like triumph - then Molly's voice grows too loud, drowning out your own thoughts entirely. You reach for your own name and can't quite remember it.\n\nWhen the enforcers arrive, Inspector Vane is the only one who doesn't look surprised. She simply makes a note, files it, and leaves instructions for the pirate captain standing where a student used to be, laughing with a stranger's mouth. No trace of you left to call your own.\n\nWorthington was wrong about many things. Vane was right about this one. The merger can consume you, if you let it happen too fast, too unready.\n\nSome doors should only be opened slowly.",
                choices: [
                    { text: "Play Again", nextScene: "start" },
                ],
                relationshipChanges: {},
                psychicChange: 0
            }
,
            vane_confrontation: {
                id: "vane_confrontation",
                theme: "institution",
                speaker: "Inspector Vane",
                act: 3,
                text: "She's waiting for you in the corridor outside, as if she already knew which way you'd choose. Inspector Vane, Board Oversight. She doesn't raise her voice.\n\n'I know about the letter W. I know about the bank. I know about Grace's grandmother, too - these things are all written down somewhere, if you know where to look.' She lets that sit. 'I'm not here to stop you tonight. I'm here so that whatever happens next, you can't say you weren't warned.'\n\nShe walks on before you can answer.",
                choices: [
                    { text: "Defy her openly - you've made your choice", nextScene: "bank_planning" },
                    { text: "Say nothing and let her wonder", nextScene: "bank_planning" },
                ],
                relationshipChanges: {},
                psychicChange: 0
            },

            grace_epilogue_pirate: {
                id: "grace_epilogue_pirate",
                theme: "neutral",
                speaker: "Grace",
                act: 3,
                text: graceEpilogueText,
                choices: [
                    { text: "Continue", nextScene: "pirate_life" },
                ],
                relationshipChanges: {},
                psychicChange: 0
            },

            grace_epilogue_peaceful: {
                id: "grace_epilogue_peaceful",
                theme: "neutral",
                speaker: "Grace",
                act: 3,
                text: graceEpilogueText,
                choices: [
                    { text: "Continue", nextScene: "peaceful_merger" },
                ],
                relationshipChanges: {},
                psychicChange: 0
            }

        };

        // ---------- ILLUSTRATED BACKGROUNDS (one per theme, reused across matching scenes) ----------
        const sceneBackgrounds = {
            dream: `<svg viewBox="0 0 800 340" preserveAspectRatio="xMidYMid slice" xmlns="http://www.w3.org/2000/svg">
                <defs>
                    <radialGradient id="bgDream" cx="55%" cy="30%" r="90%">
                        <stop offset="0%" stop-color="#2A2154"/>
                        <stop offset="55%" stop-color="#151033"/>
                        <stop offset="100%" stop-color="#08061A"/>
                    </radialGradient>
                </defs>
                <rect width="800" height="340" fill="url(#bgDream)"/>
                <polygon points="19,10 8,29 -14,29 -25,10 -14,-9 8,-9" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="57,10 46,29 24,29 13,10 24,-9 46,-9" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="94,10 83,29 61,29 50,10 61,-9 83,-9" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="132,10 121,29 99,29 88,10 99,-9 121,-9" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="169,10 158,29 136,29 125,10 136,-9 158,-9" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="206,10 195,29 173,29 162,10 173,-9 195,-9" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="244,10 233,29 211,29 200,10 211,-9 233,-9" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="281,10 270,29 248,29 237,10 248,-9 270,-9" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="319,10 308,29 286,29 275,10 286,-9 308,-9" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="38,43 27,62 5,62 -6,43 5,24 27,24" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="76,43 64,62 43,62 32,43 42,24 64,24" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="113,43 102,62 80,62 69,43 80,24 102,24" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="150,43 139,62 117,62 106,43 117,24 139,24" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="188,43 177,62 155,62 144,43 155,24 177,24" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="225,43 214,62 192,62 181,43 192,24 214,24" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="262,43 252,62 230,62 218,43 230,24 252,24" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="300,43 289,62 267,62 256,43 267,24 289,24" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="337,43 326,62 304,62 293,43 304,24 326,24" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="19,76 8,95 -14,95 -25,76 -14,57 8,57" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="57,76 46,95 24,95 13,76 24,57 46,57" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="94,76 83,95 61,95 50,76 61,57 83,57" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="132,76 121,95 99,95 88,76 99,57 121,57" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="169,76 158,95 136,95 125,76 136,57 158,57" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="206,76 195,95 173,95 162,76 173,57 195,57" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="244,76 233,95 211,95 200,76 211,57 233,57" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="281,76 270,95 248,95 237,76 248,57 270,57" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="319,76 308,95 286,95 275,76 286,57 308,57" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="38,109 27,128 5,128 -6,109 5,90 27,90" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="76,109 64,128 43,128 32,109 42,90 64,90" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="113,109 102,128 80,128 69,109 80,90 102,90" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="150,109 139,128 117,128 106,109 117,90 139,90" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="188,109 177,128 155,128 144,109 155,90 177,90" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="225,109 214,128 192,128 181,109 192,90 214,90" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="262,109 252,128 230,128 218,109 230,90 252,90" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="300,109 289,128 267,128 256,109 267,90 289,90" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="337,109 326,128 304,128 293,109 304,90 326,90" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="19,142 8,161 -14,161 -25,142 -14,123 8,123" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="57,142 46,161 24,161 13,142 24,123 46,123" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="94,142 83,161 61,161 50,142 61,123 83,123" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="132,142 121,161 99,161 88,142 99,123 121,123" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="169,142 158,161 136,161 125,142 136,123 158,123" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="206,142 195,161 173,161 162,142 173,123 195,123" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="244,142 233,161 211,161 200,142 211,123 233,123" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="281,142 270,161 248,161 237,142 248,123 270,123" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="319,142 308,161 286,161 275,142 286,123 308,123" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="38,175 27,194 5,194 -6,175 5,156 27,156" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="76,175 64,194 43,194 32,175 42,156 64,156" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="113,175 102,194 80,194 69,175 80,156 102,156" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="150,175 139,194 117,194 106,175 117,156 139,156" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="188,175 177,194 155,194 144,175 155,156 177,156" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="225,175 214,194 192,194 181,175 192,156 214,156" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="262,175 252,194 230,194 218,175 230,156 252,156" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="300,175 289,194 267,194 256,175 267,156 289,156" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="337,175 326,194 304,194 293,175 304,156 326,156" fill="none" stroke="#3A2E6B" stroke-width="0.5" opacity="0.35"/>
                <polygon points="460,150 595,150 582,185" fill="#8A6DFF" opacity="0.1"/>
                <polygon points="460,150 623,197 573,235" fill="#3A86FF" opacity="0.09"/>
                <polygon points="460,150 563,227 497,247" fill="#5C4FA8" opacity="0.13"/>
                <polygon points="460,150 482,207 438,207" fill="#B8AAFF" opacity="0.15"/>
                <polygon points="460,150 439,206 401,195" fill="#8A6DFF" opacity="0.14"/>
                <polygon points="460,150 398,197 370,176" fill="#3A86FF" opacity="0.09"/>
                <polygon points="460,150 325,189 311,150" fill="#5C4FA8" opacity="0.2"/>
                <polygon points="460,150 353,150 363,122" fill="#B8AAFF" opacity="0.11"/>
                <polygon points="460,150 300,104 349,67" fill="#8A6DFF" opacity="0.21"/>
                <polygon points="460,150 354,70 422,50" fill="#3A86FF" opacity="0.14"/>
                <polygon points="460,150 410,17 510,17" fill="#5C4FA8" opacity="0.09"/>
                <polygon points="460,150 507,27 591,51" fill="#B8AAFF" opacity="0.12"/>
                <polygon points="460,150 529,98 559,121" fill="#8A6DFF" opacity="0.1"/>
                <polygon points="460,150 580,115 593,150" fill="#3A86FF" opacity="0.19"/>
                <circle cx="145" cy="151" r="1.6" fill="#E9E9F0" opacity="0.42"/>
                <circle cx="438" cy="16" r="0.7" fill="#E9E9F0" opacity="0.34"/>
                <circle cx="544" cy="111" r="1.1" fill="#E9E9F0" opacity="0.51"/>
                <circle cx="363" cy="78" r="1.9" fill="#E9E9F0" opacity="0.56"/>
                <circle cx="195" cy="149" r="1.4" fill="#E9E9F0" opacity="0.64"/>
                <circle cx="584" cy="75" r="2.2" fill="#E9E9F0" opacity="0.3"/>
                <circle cx="334" cy="197" r="0.8" fill="#E9E9F0" opacity="0.47"/>
                <circle cx="31" cy="174" r="1.8" fill="#E9E9F0" opacity="0.51"/>
                <circle cx="700" cy="82" r="1.7" fill="#E9E9F0" opacity="0.52"/>
                <circle cx="464" cy="119" r="1.9" fill="#E9E9F0" opacity="0.68"/>
                <circle cx="379" cy="173" r="0.7" fill="#E9E9F0" opacity="0.57"/>
                <circle cx="518" cy="258" r="1.9" fill="#E9E9F0" opacity="0.38"/>
                <circle cx="309" cy="174" r="0.6" fill="#E9E9F0" opacity="0.46"/>
                <circle cx="134" cy="30" r="0.7" fill="#E9E9F0" opacity="0.6"/>
                <circle cx="103" cy="64" r="1.2" fill="#E9E9F0" opacity="0.64"/>
                <circle cx="64" cy="117" r="1.5" fill="#E9E9F0" opacity="0.65"/>
                <circle cx="655" cy="225" r="1.0" fill="#E9E9F0" opacity="0.44"/>
                <circle cx="287" cy="230" r="2.1" fill="#E9E9F0" opacity="0.32"/>
                <circle cx="141" cy="60" r="1.0" fill="#E9E9F0" opacity="0.47"/>
                <circle cx="471" cy="68" r="0.6" fill="#E9E9F0" opacity="0.44"/>
                <circle cx="295" cy="147" r="2.1" fill="#E9E9F0" opacity="0.56"/>
                <circle cx="412" cy="161" r="1.7" fill="#E9E9F0" opacity="0.27"/>
                <circle cx="720" cy="203" r="2.0" fill="#E9E9F0" opacity="0.61"/>
                <circle cx="314" cy="104" r="0.8" fill="#E9E9F0" opacity="0.54"/>
                <circle cx="50" cy="18" r="0.9" fill="#E9E9F0" opacity="0.32"/>
                <circle cx="272" cy="14" r="0.6" fill="#E9E9F0" opacity="0.32"/>
                <circle cx="81" cy="95" r="0.6" fill="#E9E9F0" opacity="0.64"/>
                <circle cx="491" cy="39" r="1.0" fill="#E9E9F0" opacity="0.41"/>
                <circle cx="291" cy="32" r="2.0" fill="#E9E9F0" opacity="0.7"/>
                <circle cx="373" cy="126" r="0.7" fill="#E9E9F0" opacity="0.3"/>
                <circle cx="274" cy="69" r="1.9" fill="#E9E9F0" opacity="0.32"/>
                <circle cx="18" cy="247" r="1.4" fill="#E9E9F0" opacity="0.32"/>
                <circle cx="435" cy="7" r="1.4" fill="#E9E9F0" opacity="0.69"/>
                <circle cx="691" cy="181" r="1.0" fill="#E9E9F0" opacity="0.42"/>
                <circle cx="134" cy="201" r="1.5" fill="#E9E9F0" opacity="0.6"/>
                <circle cx="264" cy="58" r="1.9" fill="#E9E9F0" opacity="0.69"/>
                <circle cx="682" cy="210" r="1.9" fill="#E9E9F0" opacity="0.58"/>
                <circle cx="181" cy="135" r="1.2" fill="#E9E9F0" opacity="0.26"/>
                <circle cx="22" cy="73" r="1.0" fill="#E9E9F0" opacity="0.56"/>
                <circle cx="765" cy="116" r="2.1" fill="#E9E9F0" opacity="0.69"/>
                <text x="590" y="200" font-family="Orbitron, sans-serif" font-size="60" fill="#00F0FF" opacity="0.18" transform="rotate(-6 590 200)">W</text>
                <text x="596" y="203" font-family="Orbitron, sans-serif" font-size="60" fill="#FF2E9A" opacity="0.14" transform="rotate(-6 596 203)">W</text>
                <circle cx="460" cy="150" r="6" fill="#E9E9F0" opacity="0.9"/>
                <circle cx="460" cy="150" r="16" fill="none" stroke="#8A6DFF" stroke-width="1" opacity="0.6"/>
                <circle cx="460" cy="150" r="30" fill="none" stroke="#3A86FF" stroke-width="0.6" opacity="0.4"/>
            </svg>`,

            institution: `<svg viewBox="0 0 800 340" preserveAspectRatio="xMidYMid slice" xmlns="http://www.w3.org/2000/svg">
                <defs>
                    <radialGradient id="bgInst" cx="50%" cy="0%" r="95%">
                        <stop offset="0%" stop-color="#241C0F"/>
                        <stop offset="55%" stop-color="#14100A"/>
                        <stop offset="100%" stop-color="#08060A"/>
                    </radialGradient>
                    <linearGradient id="beam" x1="0" y1="0" x2="0" y2="1">
                        <stop offset="0%" stop-color="#C9A227" stop-opacity="0.28"/>
                        <stop offset="100%" stop-color="#C9A227" stop-opacity="0"/>
                    </linearGradient>
                </defs>
                <rect width="800" height="340" fill="url(#bgInst)"/>
                <line x1="0" y1="0" x2="800" y2="0" stroke="#3A2E10" stroke-width="0.4" opacity="0.25"/>
                <line x1="0" y1="18" x2="800" y2="18" stroke="#3A2E10" stroke-width="0.4" opacity="0.25"/>
                <line x1="0" y1="36" x2="800" y2="36" stroke="#3A2E10" stroke-width="0.4" opacity="0.25"/>
                <line x1="0" y1="54" x2="800" y2="54" stroke="#3A2E10" stroke-width="0.4" opacity="0.25"/>
                <line x1="0" y1="72" x2="800" y2="72" stroke="#3A2E10" stroke-width="0.4" opacity="0.25"/>
                <line x1="0" y1="90" x2="800" y2="90" stroke="#3A2E10" stroke-width="0.4" opacity="0.25"/>
                <line x1="0" y1="108" x2="800" y2="108" stroke="#3A2E10" stroke-width="0.4" opacity="0.25"/>
                <line x1="0" y1="126" x2="800" y2="126" stroke="#3A2E10" stroke-width="0.4" opacity="0.25"/>
                <line x1="0" y1="144" x2="800" y2="144" stroke="#3A2E10" stroke-width="0.4" opacity="0.25"/>
                <line x1="0" y1="162" x2="800" y2="162" stroke="#3A2E10" stroke-width="0.4" opacity="0.25"/>
                <line x1="0" y1="180" x2="800" y2="180" stroke="#3A2E10" stroke-width="0.4" opacity="0.25"/>
                <line x1="0" y1="198" x2="800" y2="198" stroke="#3A2E10" stroke-width="0.4" opacity="0.25"/>
                <line x1="0" y1="216" x2="800" y2="216" stroke="#3A2E10" stroke-width="0.4" opacity="0.25"/>
                <line x1="0" y1="234" x2="800" y2="234" stroke="#3A2E10" stroke-width="0.4" opacity="0.25"/>
                <line x1="0" y1="252" x2="800" y2="252" stroke="#3A2E10" stroke-width="0.4" opacity="0.25"/>
                <line x1="0" y1="270" x2="800" y2="270" stroke="#3A2E10" stroke-width="0.4" opacity="0.25"/>
                <line x1="0" y1="288" x2="800" y2="288" stroke="#3A2E10" stroke-width="0.4" opacity="0.25"/>
                <line x1="0" y1="306" x2="800" y2="306" stroke="#3A2E10" stroke-width="0.4" opacity="0.25"/>
                <line x1="0" y1="324" x2="800" y2="324" stroke="#3A2E10" stroke-width="0.4" opacity="0.25"/>
                <rect x="20" y="146" width="28" height="194" fill="#C9A227" opacity="0.33"/>
                <rect x="60" y="139" width="30" height="201" fill="#C9A227" opacity="0.16"/>
                <rect x="102" y="90" width="34" height="250" fill="#C9A227" opacity="0.14"/>
                <rect x="146" y="86" width="17" height="254" fill="#C9A227" opacity="0.28"/>
                <rect x="170" y="48" width="45" height="292" fill="#C9A227" opacity="0.27"/>
                <rect x="228" y="276" width="19" height="64" fill="#C9A227" opacity="0.24"/>
                <rect x="254" y="222" width="20" height="118" fill="#C9A227" opacity="0.13"/>
                <rect x="286" y="78" width="28" height="262" fill="#C9A227" opacity="0.24"/>
                <rect x="328" y="121" width="30" height="219" fill="#C9A227" opacity="0.23"/>
                <rect x="367" y="41" width="46" height="299" fill="#C9A227" opacity="0.31"/>
                <rect x="427" y="225" width="24" height="115" fill="#C9A227" opacity="0.19"/>
                <rect x="458" y="184" width="39" height="156" fill="#C9A227" opacity="0.31"/>
                <rect x="508" y="77" width="45" height="263" fill="#C9A227" opacity="0.12"/>
                <rect x="561" y="167" width="43" height="173" fill="#C9A227" opacity="0.35"/>
                <rect x="615" y="129" width="16" height="211" fill="#C9A227" opacity="0.3"/>
                <rect x="640" y="200" width="17" height="140" fill="#C9A227" opacity="0.34"/>
                <rect x="672" y="221" width="18" height="119" fill="#C9A227" opacity="0.14"/>
                <rect x="697" y="237" width="40" height="103" fill="#C9A227" opacity="0.25"/>
                <rect x="747" y="104" width="20" height="236" fill="#C9A227" opacity="0.15"/>
                <polygon points="400,0 300,340 500,340" fill="url(#beam)"/>
                <line x1="120" y1="70" x2="260" y2="40" stroke="#C9A227" stroke-width="0.6" opacity="0.35"/>
                <line x1="260" y1="40" x2="400" y2="90" stroke="#C9A227" stroke-width="0.6" opacity="0.35"/>
                <line x1="400" y1="90" x2="540" y2="50" stroke="#C9A227" stroke-width="0.6" opacity="0.35"/>
                <line x1="540" y1="50" x2="680" y2="100" stroke="#C9A227" stroke-width="0.6" opacity="0.35"/>
                <line x1="120" y1="70" x2="300" y2="160" stroke="#C9A227" stroke-width="0.6" opacity="0.35"/>
                <line x1="400" y1="90" x2="300" y2="160" stroke="#C9A227" stroke-width="0.6" opacity="0.35"/>
                <line x1="300" y1="160" x2="450" y2="210" stroke="#C9A227" stroke-width="0.6" opacity="0.35"/>
                <line x1="540" y1="50" x2="600" y2="180" stroke="#C9A227" stroke-width="0.6" opacity="0.35"/>
                <line x1="600" y1="180" x2="450" y2="210" stroke="#C9A227" stroke-width="0.6" opacity="0.35"/>
                <line x1="680" y1="100" x2="600" y2="180" stroke="#C9A227" stroke-width="0.6" opacity="0.35"/>
                <circle cx="120" cy="70" r="3" fill="#C9A227" opacity="0.7"/>
                <circle cx="260" cy="40" r="3" fill="#C9A227" opacity="0.7"/>
                <circle cx="400" cy="90" r="3" fill="#C9A227" opacity="0.7"/>
                <circle cx="540" cy="50" r="3" fill="#C9A227" opacity="0.7"/>
                <circle cx="680" cy="100" r="3" fill="#C9A227" opacity="0.7"/>
                <circle cx="300" cy="160" r="3" fill="#C9A227" opacity="0.7"/>
                <circle cx="600" cy="180" r="3" fill="#C9A227" opacity="0.7"/>
                <circle cx="450" cy="210" r="3" fill="#C9A227" opacity="0.7"/>
            </svg>`,

            neutral: `<svg viewBox="0 0 800 340" preserveAspectRatio="xMidYMid slice" xmlns="http://www.w3.org/2000/svg">
                <defs>
                    <radialGradient id="bgNeutral" cx="60%" cy="10%" r="95%">
                        <stop offset="0%" stop-color="#0F2A2E"/>
                        <stop offset="55%" stop-color="#0A1A20"/>
                        <stop offset="100%" stop-color="#050D12"/>
                    </radialGradient>
                </defs>
                <rect width="800" height="340" fill="url(#bgNeutral)"/>
                <line x1="528" y1="177" x2="584" y2="99" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="528" y1="177" x2="514" y2="80" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="415" y1="122" x2="320" y2="167" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="415" y1="122" x2="514" y2="80" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="323" y1="219" x2="320" y2="167" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="323" y1="219" x2="264" y2="134" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="248" y1="50" x2="228" y2="72" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="248" y1="50" x2="276" y2="74" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="228" y1="72" x2="248" y2="50" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="228" y1="72" x2="276" y2="74" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="276" y1="74" x2="248" y2="50" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="276" y1="74" x2="228" y2="72" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="118" y1="85" x2="112" y2="166" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="118" y1="85" x2="186" y2="128" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="264" y1="134" x2="276" y2="74" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="264" y1="134" x2="320" y2="167" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="185" y1="34" x2="228" y2="72" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="185" y1="34" x2="248" y2="50" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="186" y1="128" x2="228" y2="72" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="186" y1="128" x2="264" y2="134" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="320" y1="167" x2="323" y2="219" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="320" y1="167" x2="264" y2="134" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="618" y1="103" x2="584" y2="99" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="618" y1="103" x2="514" y2="80" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="112" y1="166" x2="118" y2="85" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="112" y1="166" x2="186" y2="128" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="584" y1="99" x2="618" y2="103" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="584" y1="99" x2="514" y2="80" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="514" y1="80" x2="584" y2="99" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="514" y1="80" x2="528" y2="177" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="699" y1="187" x2="618" y2="103" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <line x1="699" y1="187" x2="584" y2="99" stroke="#2FBFA0" stroke-width="0.5" opacity="0.3"/>
                <circle cx="528" cy="177" r="2.6" fill="#BFEFE0" opacity="0.83"/>
                <circle cx="415" cy="122" r="1.6" fill="#BFEFE0" opacity="0.65"/>
                <circle cx="323" cy="219" r="2.5" fill="#BFEFE0" opacity="0.76"/>
                <circle cx="248" cy="50" r="1.9" fill="#BFEFE0" opacity="0.46"/>
                <circle cx="228" cy="72" r="2.3" fill="#BFEFE0" opacity="0.45"/>
                <circle cx="276" cy="74" r="2.5" fill="#BFEFE0" opacity="0.40"/>
                <circle cx="118" cy="85" r="2.0" fill="#BFEFE0" opacity="0.62"/>
                <circle cx="264" cy="134" r="1.4" fill="#BFEFE0" opacity="0.62"/>
                <circle cx="185" cy="34" r="2.1" fill="#BFEFE0" opacity="0.57"/>
                <circle cx="186" cy="128" r="2.8" fill="#BFEFE0" opacity="0.79"/>
                <circle cx="320" cy="167" r="2.8" fill="#BFEFE0" opacity="0.51"/>
                <circle cx="618" cy="103" r="1.2" fill="#BFEFE0" opacity="0.57"/>
                <circle cx="112" cy="166" r="1.4" fill="#BFEFE0" opacity="0.72"/>
                <circle cx="584" cy="99" r="1.7" fill="#BFEFE0" opacity="0.46"/>
                <circle cx="514" cy="80" r="1.7" fill="#BFEFE0" opacity="0.58"/>
                <circle cx="699" cy="187" r="2.5" fill="#BFEFE0" opacity="0.61"/>
                <circle cx="600" cy="130" r="70" fill="none" stroke="#3FBBFF" stroke-width="1" opacity="0.35"/>
                <circle cx="560" cy="110" r="45" fill="none" stroke="#3FBBFF" stroke-width="1" opacity="0.29"/>
                <circle cx="640" cy="150" r="30" fill="none" stroke="#3FBBFF" stroke-width="1" opacity="0.23"/>
                <path d="M0 300 Q200 280 400 300 T800 300 L800 340 L0 340 Z" fill="#081418" opacity="0.7"/>
            </svg>`
        };

        // ---------- CHARACTER PORTRAITS (abstract faceted "psychic signatures") ----------
        const characterPortraits = {
            "Molly Roger": `<svg viewBox="0 0 200 320" xmlns="http://www.w3.org/2000/svg">
                <defs>
                    <radialGradient id="core8A6DFF" cx="50%" cy="50%" r="50%">
                    <stop offset="0%" stop-color="#8A6DFF" stop-opacity="0.9"/>
                    <stop offset="100%" stop-color="#8A6DFF" stop-opacity="0"/>
                </radialGradient>
            </defs>
            <circle cx="100" cy="170" r="34" fill="url(#core8A6DFF)"/>
                <polygon points="60,68 100,20 140,68" fill="#1A1338" opacity="0.9" stroke="#0D0A22" stroke-width="0.6"/>
                <polygon points="60,68 100,45 100,20" fill="#3A2E6B" opacity="0.6" stroke="#0D0A22" stroke-width="0.6"/>
                <polygon points="100,45 140,68 100,20" fill="#5C4FA8" opacity="0.6" stroke="#0D0A22" stroke-width="0.6"/>
                <polygon points="100,170 100,60 130,68" fill="#8A6DFF" opacity="0.55" stroke="#0D0A22" stroke-width="0.6"/>
                <polygon points="100,170 130,68 128,100" fill="#5C4FA8" opacity="0.72" stroke="#0D0A22" stroke-width="0.6"/>
                <polygon points="100,170 128,100 118,122" fill="#3A86FF" opacity="0.6" stroke="#0D0A22" stroke-width="0.6"/>
                <polygon points="100,170 118,122 145,145" fill="#241B4A" opacity="0.85" stroke="#0D0A22" stroke-width="0.6"/>
                <polygon points="100,170 145,145 150,230" fill="#B8AAFF" opacity="0.65" stroke="#0D0A22" stroke-width="0.6"/>
                <polygon points="100,170 150,230 145,290" fill="#8A6DFF" opacity="0.55" stroke="#0D0A22" stroke-width="0.6"/>
                <polygon points="100,170 145,290 100,300" fill="#5C4FA8" opacity="0.72" stroke="#0D0A22" stroke-width="0.6"/>
                <polygon points="100,170 100,300 55,290" fill="#3A86FF" opacity="0.6" stroke="#0D0A22" stroke-width="0.6"/>
                <polygon points="100,170 55,290 50,230" fill="#241B4A" opacity="0.85" stroke="#0D0A22" stroke-width="0.6"/>
                <polygon points="100,170 50,230 55,145" fill="#B8AAFF" opacity="0.65" stroke="#0D0A22" stroke-width="0.6"/>
                <polygon points="100,170 55,145 82,122" fill="#8A6DFF" opacity="0.55" stroke="#0D0A22" stroke-width="0.6"/>
                <polygon points="100,170 82,122 72,100" fill="#5C4FA8" opacity="0.72" stroke="#0D0A22" stroke-width="0.6"/>
                <polygon points="100,170 72,100 70,68" fill="#3A86FF" opacity="0.6" stroke="#0D0A22" stroke-width="0.6"/>
                <polygon points="100,170 70,68 100,60" fill="#241B4A" opacity="0.85" stroke="#0D0A22" stroke-width="0.6"/>
                <rect x="78" y="93" width="44" height="4" fill="#E9E9F0" opacity="0.85"/>
            </svg>`,

            "Professor Worthington": `<svg viewBox="0 0 200 320" xmlns="http://www.w3.org/2000/svg">
                <defs>
                    <radialGradient id="coreC9A227" cx="50%" cy="50%" r="50%">
                    <stop offset="0%" stop-color="#C9A227" stop-opacity="0.9"/>
                    <stop offset="100%" stop-color="#C9A227" stop-opacity="0"/>
                </radialGradient>
            </defs>
            <circle cx="100" cy="170" r="34" fill="url(#coreC9A227)"/>
                <polygon points="70,68 100,50 130,68 118,80 82,80" fill="#0A0806" opacity="0.85"/>
                <polygon points="100,170 100,60 130,68" fill="#C9A227" opacity="0.5" stroke="#0A0806" stroke-width="0.6"/>
                <polygon points="100,170 130,68 128,100" fill="#8A6D3A" opacity="0.75" stroke="#0A0806" stroke-width="0.6"/>
                <polygon points="100,170 128,100 118,122" fill="#1B160C" opacity="0.9" stroke="#0A0806" stroke-width="0.6"/>
                <polygon points="100,170 118,122 145,145" fill="#3A2E10" opacity="0.7" stroke="#0A0806" stroke-width="0.6"/>
                <polygon points="100,170 145,145 150,230" fill="#E8D28A" opacity="0.45" stroke="#0A0806" stroke-width="0.6"/>
                <polygon points="100,170 150,230 145,290" fill="#C9A227" opacity="0.5" stroke="#0A0806" stroke-width="0.6"/>
                <polygon points="100,170 145,290 100,300" fill="#8A6D3A" opacity="0.75" stroke="#0A0806" stroke-width="0.6"/>
                <polygon points="100,170 100,300 55,290" fill="#1B160C" opacity="0.9" stroke="#0A0806" stroke-width="0.6"/>
                <polygon points="100,170 55,290 50,230" fill="#3A2E10" opacity="0.7" stroke="#0A0806" stroke-width="0.6"/>
                <polygon points="100,170 50,230 55,145" fill="#E8D28A" opacity="0.45" stroke="#0A0806" stroke-width="0.6"/>
                <polygon points="100,170 55,145 82,122" fill="#C9A227" opacity="0.5" stroke="#0A0806" stroke-width="0.6"/>
                <polygon points="100,170 82,122 72,100" fill="#8A6D3A" opacity="0.75" stroke="#0A0806" stroke-width="0.6"/>
                <polygon points="100,170 72,100 70,68" fill="#1B160C" opacity="0.9" stroke="#0A0806" stroke-width="0.6"/>
                <polygon points="100,170 70,68 100,60" fill="#3A2E10" opacity="0.7" stroke="#0A0806" stroke-width="0.6"/>
                <rect x="76" y="90" width="20" height="14" fill="none" stroke="#C9A227" stroke-width="2" opacity="0.85"/>
                <rect x="104" y="90" width="20" height="14" fill="none" stroke="#C9A227" stroke-width="2" opacity="0.85"/>
                <line x1="96" y1="97" x2="104" y2="97" stroke="#C9A227" stroke-width="2" opacity="0.85"/>
            </svg>`,

            "Grace": `<svg viewBox="0 0 200 320" xmlns="http://www.w3.org/2000/svg">
                <defs>
                    <radialGradient id="core3FBBFF" cx="50%" cy="50%" r="50%">
                    <stop offset="0%" stop-color="#3FBBFF" stop-opacity="0.9"/>
                    <stop offset="100%" stop-color="#3FBBFF" stop-opacity="0"/>
                </radialGradient>
            </defs>
            <circle cx="100" cy="170" r="34" fill="url(#core3FBBFF)"/>
                <polygon points="70,68 100,42 130,68 118,58 100,50 82,58" fill="#081418" opacity="0.85"/>
                <polygon points="100,170 100,60 130,68" fill="#3FBBFF" opacity="0.5" stroke="#081418" stroke-width="0.6"/>
                <polygon points="100,170 130,68 128,100" fill="#2FBFA0" opacity="0.68" stroke="#081418" stroke-width="0.6"/>
                <polygon points="100,170 128,100 118,122" fill="#0F2A2E" opacity="0.82" stroke="#081418" stroke-width="0.6"/>
                <polygon points="100,170 118,122 145,145" fill="#123A38" opacity="0.6" stroke="#081418" stroke-width="0.6"/>
                <polygon points="100,170 145,145 150,230" fill="#9FE1CB" opacity="0.55" stroke="#081418" stroke-width="0.6"/>
                <polygon points="100,170 150,230 145,290" fill="#3FBBFF" opacity="0.5" stroke="#081418" stroke-width="0.6"/>
                <polygon points="100,170 145,290 100,300" fill="#2FBFA0" opacity="0.68" stroke="#081418" stroke-width="0.6"/>
                <polygon points="100,170 100,300 55,290" fill="#0F2A2E" opacity="0.82" stroke="#081418" stroke-width="0.6"/>
                <polygon points="100,170 55,290 50,230" fill="#123A38" opacity="0.6" stroke="#081418" stroke-width="0.6"/>
                <polygon points="100,170 50,230 55,145" fill="#9FE1CB" opacity="0.55" stroke="#081418" stroke-width="0.6"/>
                <polygon points="100,170 55,145 82,122" fill="#3FBBFF" opacity="0.5" stroke="#081418" stroke-width="0.6"/>
                <polygon points="100,170 82,122 72,100" fill="#2FBFA0" opacity="0.68" stroke="#081418" stroke-width="0.6"/>
                <polygon points="100,170 72,100 70,68" fill="#0F2A2E" opacity="0.82" stroke="#081418" stroke-width="0.6"/>
                <polygon points="100,170 70,68 100,60" fill="#123A38" opacity="0.6" stroke="#081418" stroke-width="0.6"/>
                <rect x="118" y="155" width="20" height="26" rx="2" fill="#3FBBFF" opacity="0.55"/><rect x="121" y="160" width="14" height="3" fill="#0A1A20"/><rect x="121" y="167" width="14" height="3" fill="#0A1A20"/>
            </svg>`,

            "Inspector Vane": `<svg viewBox="0 0 200 320" xmlns="http://www.w3.org/2000/svg">
                <defs>
                    <radialGradient id="coreC9A227" cx="50%" cy="50%" r="50%">
                    <stop offset="0%" stop-color="#C9A227" stop-opacity="0.9"/>
                    <stop offset="100%" stop-color="#C9A227" stop-opacity="0"/>
                </radialGradient>
            </defs>
            <circle cx="100" cy="170" r="34" fill="url(#coreC9A227)"/>
                <polygon points="72,68 100,55 128,68 122,74 100,64 78,74" fill="#0A0806" opacity="0.9"/>
                <polygon points="100,170 100,60 130,68" fill="#C9A227" opacity="0.65" stroke="#050403" stroke-width="0.6"/>
                <polygon points="100,170 130,68 128,100" fill="#221C10" opacity="0.88" stroke="#050403" stroke-width="0.6"/>
                <polygon points="100,170 128,100 118,122" fill="#0A0806" opacity="0.95" stroke="#050403" stroke-width="0.6"/>
                <polygon points="100,170 118,122 145,145" fill="#3A2E10" opacity="0.7" stroke="#050403" stroke-width="0.6"/>
                <polygon points="100,170 145,145 150,230" fill="#8A6D3A" opacity="0.5" stroke="#050403" stroke-width="0.6"/>
                <polygon points="100,170 150,230 145,290" fill="#C9A227" opacity="0.65" stroke="#050403" stroke-width="0.6"/>
                <polygon points="100,170 145,290 100,300" fill="#221C10" opacity="0.88" stroke="#050403" stroke-width="0.6"/>
                <polygon points="100,170 100,300 55,290" fill="#0A0806" opacity="0.95" stroke="#050403" stroke-width="0.6"/>
                <polygon points="100,170 55,290 50,230" fill="#3A2E10" opacity="0.7" stroke="#050403" stroke-width="0.6"/>
                <polygon points="100,170 50,230 55,145" fill="#8A6D3A" opacity="0.5" stroke="#050403" stroke-width="0.6"/>
                <polygon points="100,170 55,145 82,122" fill="#C9A227" opacity="0.65" stroke="#050403" stroke-width="0.6"/>
                <polygon points="100,170 82,122 72,100" fill="#221C10" opacity="0.88" stroke="#050403" stroke-width="0.6"/>
                <polygon points="100,170 72,100 70,68" fill="#0A0806" opacity="0.95" stroke="#050403" stroke-width="0.6"/>
                <polygon points="100,170 70,68 100,60" fill="#3A2E10" opacity="0.7" stroke="#050403" stroke-width="0.6"/>
                <rect x="76" y="96" width="48" height="3" fill="#C9A227" opacity="0.9"/><rect x="120" y="152" width="14" height="20" fill="#3A2E10" opacity="0.85"/>
            </svg>`
        };

        // ---------- GAME STATE ----------
        let currentSceneId = "start";
        let psychicLevel = 1;
        let currentPath = "Undecided";
        let relationships = {
            "Molly Roger": 0,
            "Professor Worthington": 0,
            "Grace": 0
        };
        let currentTypingInterval = null;
        let lastShownAct = 0;
        let discoveredEndings = new Set();

        const actInfo = {
            1: { title: "Act I", subtitle: "Awakening" },
            2: { title: "Act II", subtitle: "The heist" },
            3: { title: "Act III", subtitle: "The choice" }
        };

        // journeySteps: full history of the run. Each entry captures the state
        // AFTER arriving at that scene, so we can rewind to any earlier point.
        // { sceneId, label, choiceText, snapshot: { psychicLevel, currentPath, relationships } }
        let journeySteps = [];

        const STORAGE_KEY = "molly-roger-save";

        // DOM elements (will be initialised later)
        let sceneTextEl, choicesContainerEl, relationshipContainerEl, psychicLevelEl, pathEl,
            restartBtn, saveBtn, saveIndicator, aboutLink, aboutOverlay, closeAbout,
            storyLink, storyOverlay, closeStory, journeyLink, journeyOverlay, closeJourney,
            journeyListEl, breadcrumbContainerEl, continueBanner, dismissContinueBanner, persistenceNoteEl,
            illustrationPanelEl, illustrationBgEl, illustrationPortraitEl, illustrationSpeakerChipEl, illustrationLocationChipEl,
            actTransitionEl, actTitleEl, actSubtitleEl,
            endingEyebrowEl, endingTitleEl, endingSummaryEl;

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

        function formatSceneLabel(sceneId) {
            return sceneId
                .replace(/_/g, ' ')
                .replace(/\b\w/g, c => c.toUpperCase());
        }

        function typeText(text, element, callback) {
            if (currentTypingInterval) clearInterval(currentTypingInterval);

            element.innerHTML = "";
            element.classList.add("typing");

            let i = 0;
            const speed = 20;
            const processedText = fixTextFormatting(text);

            currentTypingInterval = setInterval(() => {
                if (i < processedText.length) {
                    if (processedText.charAt(i) === '\n') {
                        element.innerHTML += '<br>';
                    } else {
                        element.innerHTML += processedText.charAt(i);
                    }
                    i++;
                } else {
                    clearInterval(currentTypingInterval);
                    element.classList.remove("typing");
                    currentTypingInterval = null;
                    if (callback) callback();
                }
            }, speed);
        }

        const characterColors = {
            "Molly Roger": "#8A6DFF",
            "Professor Worthington": "#C9A227",
            "Grace": "#3FBBFF"
        };

        function updateRelationships() {
            relationshipContainerEl.innerHTML = "<h3>Relationships</h3>";

            for (const [character, value] of Object.entries(relationships)) {
                const color = characterColors[character] || "#8A6DFF";

                const relationshipEl = document.createElement("div");
                relationshipEl.className = "relationship-bar";

                const label = document.createElement("div");
                label.className = "relationship-label";
                label.innerHTML = `<span style="display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:6px;background:${color};"></span>${character}`;

                const barContainer = document.createElement("div");
                barContainer.style.flex = "1";
                barContainer.style.background = "#1A2B3F";
                barContainer.style.borderRadius = "3px";
                barContainer.style.height = "5px";
                barContainer.style.overflow = "hidden";

                const barFill = document.createElement("div");
                barFill.className = "relationship-fill";
                barFill.style.width = `${value}%`;
                barFill.style.background = color;

                barContainer.appendChild(barFill);
                relationshipEl.appendChild(label);
                relationshipEl.appendChild(barContainer);

                relationshipContainerEl.appendChild(relationshipEl);
            }
        }

        function updateStatus() {
            psychicLevelEl.textContent = `Psychic Level: ${psychicLevel}`;
            pathEl.textContent = `Path: ${currentPath}`;
        }

        function updateCurrentPath() {
            const mollyRel = relationships["Molly Roger"];
            const worthingtonRel = relationships["Professor Worthington"];

            if (mollyRel > 50 && worthingtonRel < 30) {
                currentPath = "Rebellion";
            } else if (worthingtonRel > 50 && mollyRel < 30) {
                currentPath = "Institutional";
            } else if (mollyRel > 30 && worthingtonRel > 30) {
                currentPath = "Balanced";
            } else {
                currentPath = "Undecided";
            }
        }

        // ---------- BREADCRUMB TRAIL ----------
        function renderBreadcrumbs() {
            breadcrumbContainerEl.innerHTML = "";

            if (journeySteps.length === 0) return;

            const maxVisible = 5;
            const recent = journeySteps.slice(-maxVisible);
            const startIndex = journeySteps.length - recent.length;

            if (startIndex > 0) {
                const more = document.createElement("span");
                more.className = "breadcrumb-more";
                more.textContent = `+${startIndex} earlier…`;
                more.title = "See your full journey";
                more.style.cursor = "pointer";
                more.addEventListener("click", openJourneyOverlay);
                breadcrumbContainerEl.appendChild(more);

                const arrow = document.createElement("span");
                arrow.className = "breadcrumb-arrow";
                arrow.innerHTML = '<i class="fas fa-chevron-right"></i>';
                breadcrumbContainerEl.appendChild(arrow);
            }

            recent.forEach((step, i) => {
                const idx = startIndex + i;
                const isCurrent = idx === journeySteps.length - 1;

                const chip = document.createElement("span");
                chip.className = "breadcrumb-item" + (isCurrent ? " current" : "");
                chip.textContent = step.label;
                if (step.choiceText) chip.title = `You chose: "${step.choiceText}"`;

                if (!isCurrent) {
                    chip.addEventListener("click", () => jumpToStep(idx));
                }

                breadcrumbContainerEl.appendChild(chip);

                if (i < recent.length - 1) {
                    const arrow = document.createElement("span");
                    arrow.className = "breadcrumb-arrow";
                    arrow.innerHTML = '<i class="fas fa-chevron-right"></i>';
                    breadcrumbContainerEl.appendChild(arrow);
                }
            });
        }

        // ---------- JOURNEY OVERLAY ----------
        function renderJourneyOverlay() {
            journeyListEl.innerHTML = "";

            if (journeySteps.length === 0) {
                journeyListEl.innerHTML = '<div class="journey-empty">Your journey hasn\'t begun yet.</div>';
                return;
            }

            journeySteps.forEach((step, idx) => {
                const isCurrent = idx === journeySteps.length - 1;

                const item = document.createElement("div");
                item.className = "journey-item" + (isCurrent ? " current" : "");

                item.innerHTML = `
                    <div class="journey-step-number">${idx + 1}</div>
                    <div class="journey-step-content">
                        <div class="journey-step-label">${step.label}</div>
                        ${step.choiceText ? `<div class="journey-step-choice">"${step.choiceText}"</div>` : ''}
                    </div>
                    ${isCurrent ? '<i class="fas fa-map-marker-alt journey-jump-icon" title="You are here"></i>' : '<i class="fas fa-undo journey-jump-icon" title="Jump back here"></i>'}
                `;

                if (!isCurrent) {
                    item.addEventListener("click", () => jumpToStep(idx));
                }

                journeyListEl.appendChild(item);
            });
        }

        function openJourneyOverlay() {
            renderJourneyOverlay();
            journeyOverlay.style.display = "flex";
        }

        function jumpToStep(index) {
            const step = journeySteps[index];
            if (!step) return;

            psychicLevel = step.snapshot.psychicLevel;
            currentPath = step.snapshot.currentPath;
            relationships = { ...step.snapshot.relationships };
            journeySteps = journeySteps.slice(0, index + 1);
            lastShownAct = (scenes[step.sceneId] && scenes[step.sceneId].act) || 0;

            updateStatus();
            journeyOverlay.style.display = "none";
            loadScene(step.sceneId, null, true);
        }

        // ---------- PERSISTENT STORAGE ----------
        // Three-tier fallback:
        //  1. window.storage  - only exists inside Claude.ai's artifact preview.
        //  2. localStorage    - works on a real, self-hosted website.
        //  3. in-memory only  - last resort; progress survives the tab but not a reload.
        // Every tier is wrapped so a failure in one silently drops to the next
        // instead of surfacing a raw error to the player.
        let persistenceMode = "unknown"; // "claude" | "local" | "memory"

        function localStorageAvailable() {
            try {
                const testKey = "__molly_roger_probe__";
                localStorage.setItem(testKey, "1");
                localStorage.removeItem(testKey);
                return true;
            } catch (e) {
                return false;
            }
        }

        async function storageSet(key, value) {
            if (typeof window.storage !== "undefined" && window.storage) {
                try {
                    const result = await window.storage.set(key, value, false);
                    if (result) {
                        persistenceMode = "claude";
                        return true;
                    }
                } catch (e) {
                    console.log("window.storage.set unavailable, falling back", e);
                }
            }
            if (localStorageAvailable()) {
                try {
                    localStorage.setItem(key, value);
                    persistenceMode = "local";
                    return true;
                } catch (e) {
                    console.log("localStorage.setItem unavailable, falling back", e);
                }
            }
            persistenceMode = "memory";
            return false;
        }

        async function storageGet(key) {
            if (typeof window.storage !== "undefined" && window.storage) {
                try {
                    const result = await window.storage.get(key);
                    if (result && result.value) {
                        persistenceMode = "claude";
                        return result.value;
                    }
                } catch (e) {
                    console.log("window.storage.get unavailable, falling back", e);
                }
            }
            if (localStorageAvailable()) {
                try {
                    const value = localStorage.getItem(key);
                    if (value) {
                        persistenceMode = "local";
                        return value;
                    }
                } catch (e) {
                    console.log("localStorage.getItem unavailable, falling back", e);
                }
            }
            return null;
        }

        function updatePersistenceNote() {
            if (!persistenceNoteEl) return;
            const notes = {
                claude: "Progress saves to your Claude account.",
                local: "Progress saves to this browser.",
                memory: "Progress won't be saved after you close this tab."
            };
            persistenceNoteEl.textContent = notes[persistenceMode] || "";
            persistenceNoteEl.style.color = persistenceMode === "memory" ? "#FFB86B" : "#8FA0BD";
        }

        async function saveGame(showIndicator = true) {
            const gameState = {
                currentSceneId,
                psychicLevel,
                currentPath,
                relationships,
                journeySteps,
                discoveredEndings: Array.from(discoveredEndings)
            };

            const ok = await storageSet(STORAGE_KEY, JSON.stringify(gameState));
            updatePersistenceNote();

            if (showIndicator) {
                if (ok) {
                    saveIndicator.style.background = "#3AFF8F";
                    saveIndicator.querySelector("span").textContent = "Progress Saved";
                } else {
                    saveIndicator.style.background = "#FFB86B";
                    saveIndicator.querySelector("span").textContent = "Saved for this session only";
                }
                saveIndicator.style.display = "flex";
                setTimeout(() => { saveIndicator.style.display = "none"; }, 2200);
            }
        }

        async function loadGame() {
            const raw = await storageGet(STORAGE_KEY);
            updatePersistenceNote();

            if (raw) {
                try {
                    const gameState = JSON.parse(raw);
                    currentSceneId = gameState.currentSceneId || "start";
                    psychicLevel = gameState.psychicLevel || 1;
                    currentPath = gameState.currentPath || "Undecided";
                    relationships = gameState.relationships || relationships;
                    journeySteps = gameState.journeySteps || [];
                    discoveredEndings = new Set(gameState.discoveredEndings || []);

                    updateStatus();
                    lastShownAct = (scenes[currentSceneId] && scenes[currentSceneId].act) || 0;

                    if (continueBanner) {
                        continueBanner.style.display = "flex";
                    }

                    // isJump = true: state is already restored above, just render the scene.
                    loadScene(currentSceneId, null, true);
                    return true;
                } catch (e) {
                    console.warn("Saved data was corrupted, starting fresh.", e);
                }
            }
            return false;
        }

        // ---------- ACT TRANSITIONS ----------
        function showActTransition(actNumber) {
            return new Promise((resolve) => {
                const info = actInfo[actNumber];
                if (!info || !actTransitionEl) { resolve(); return; }
                actTitleEl.textContent = info.title;
                actSubtitleEl.textContent = info.subtitle;
                actTransitionEl.classList.add("visible");
                setTimeout(() => {
                    actTransitionEl.classList.remove("visible");
                    setTimeout(resolve, 600);
                }, 1800);
            });
        }

        // ---------- ENDING PRESENTATION ----------
        function renderEndingPresentation(scene) {
            const totalEndings = Object.values(scenes).filter(s => s.ending).length;

            illustrationPanelEl.classList.add("ending-panel");
            endingEyebrowEl.style.display = "block";
            endingTitleEl.style.display = "block";
            endingTitleEl.textContent = scene.endingTitle || "The end";

            const characters = ["Molly Roger", "Professor Worthington", "Grace"];
            const relRows = characters.map(c =>
                `<div class="ending-summary-row"><span class="ending-summary-label">${c}</span><span class="ending-summary-value">${relationships[c]}%</span></div>`
            ).join("");

            endingSummaryEl.innerHTML = `
                <div class="ending-summary-row"><span class="ending-summary-label">Path</span><span class="ending-summary-value">${currentPath}</span></div>
                <div class="ending-summary-row"><span class="ending-summary-label">Psychic level</span><span class="ending-summary-value">${psychicLevel}</span></div>
                <div class="ending-summary-row"><span class="ending-summary-label">Choices made</span><span class="ending-summary-value">${journeySteps.length}</span></div>
                ${relRows}
                <div class="ending-discovered">${discoveredEndings.size} of ${totalEndings} endings discovered</div>
            `;
            endingSummaryEl.style.display = "block";
        }

        function clearEndingPresentation() {
            illustrationPanelEl.classList.remove("ending-panel");
            endingEyebrowEl.style.display = "none";
            endingTitleEl.style.display = "none";
            endingSummaryEl.style.display = "none";
        }

        // ---------- ILLUSTRATION PANEL ----------
        function renderIllustrationPanel(scene, sceneId) {
            const bgSvg = sceneBackgrounds[scene.theme] || sceneBackgrounds.neutral;
            illustrationBgEl.innerHTML = bgSvg;

            illustrationLocationChipEl.textContent = formatSceneLabel(sceneId);

            if (scene.speaker && characterPortraits[scene.speaker]) {
                illustrationPortraitEl.innerHTML = characterPortraits[scene.speaker];
                illustrationPortraitEl.classList.remove("hidden");
                illustrationSpeakerChipEl.textContent = scene.speaker;
                illustrationSpeakerChipEl.style.display = "block";
            } else {
                illustrationPortraitEl.innerHTML = "";
                illustrationPortraitEl.classList.add("hidden");
                illustrationSpeakerChipEl.style.display = "none";
            }
        }

        // ---------- SCENE LOADING ----------
        async function loadScene(sceneId, choiceText, isJump = false) {
            const scene = scenes[sceneId];
            if (!scene) {
                console.error(`Scene ${sceneId} not found!`);
                return;
            }

            const shouldShowActTransition = !isJump && scene.act && scene.act > lastShownAct;
            if (shouldShowActTransition) {
                await showActTransition(scene.act);
                lastShownAct = scene.act;
            }

            currentSceneId = sceneId;
            document.body.setAttribute("data-theme", scene.theme || "neutral");
            renderIllustrationPanel(scene, sceneId);

            if (scene.ending) {
                discoveredEndings.add(sceneId);
                renderEndingPresentation(scene);
            } else {
                clearEndingPresentation();
            }

            if (!isJump) {
                if (scene.psychicChange) {
                    psychicLevel = Math.max(1, psychicLevel + scene.psychicChange);
                }

                if (scene.relationshipChanges) {
                    for (const [character, change] of Object.entries(scene.relationshipChanges)) {
                        if (relationships.hasOwnProperty(character)) {
                            relationships[character] = Math.max(0, Math.min(100, relationships[character] + change));
                        }
                    }
                }

                updateCurrentPath();

                journeySteps.push({
                    sceneId,
                    label: formatSceneLabel(sceneId),
                    choiceText: choiceText || (sceneId === "start" ? "Begin the story" : null),
                    snapshot: {
                        psychicLevel,
                        currentPath,
                        relationships: { ...relationships }
                    }
                });
            }

            // Clear choices and relationships while text types
            choicesContainerEl.innerHTML = "";
            relationshipContainerEl.innerHTML = "";
            sceneTextEl.className = "scene-text fade-in";

            updateStatus();
            renderBreadcrumbs();

            const resolvedText = typeof scene.text === "function"
                ? scene.text({ psychicLevel, relationships, currentPath })
                : scene.text;

            typeText(resolvedText, sceneTextEl, () => {
                updateRelationships();

                scene.choices.forEach((choice, index) => {
                    const button = document.createElement("button");
                    button.className = "choice-btn fade-in" + (choice.risky ? " risky" : "") + (choice.text === "Play Again" ? " play-again" : "");
                    button.innerHTML = `
                        <div class="choice-number">${index + 1}</div>
                        <span>${choice.risky ? '<i class="fas fa-triangle-exclamation risky-icon" title="This choice carries real risk"></i>' : ''}${choice.text}</span>
                    `;
                    button.addEventListener("click", () => {
                        const target = typeof choice.nextScene === "function"
                            ? choice.nextScene({ psychicLevel, relationships, currentPath })
                            : choice.nextScene;
                        loadScene(target, choice.text, false);
                    });
                    choicesContainerEl.appendChild(button);
                });
            });

            // Persist after every scene change (silent, no toast).
            saveGame(false);
        }

        // ---------- INITIALISATION ----------
        async function initGame() {
            // Grab DOM elements now that the DOM is ready
            sceneTextEl = document.getElementById("scene-text");
            choicesContainerEl = document.getElementById("choices-container");
            relationshipContainerEl = document.getElementById("relationship-container");
            psychicLevelEl = document.getElementById("psychic-level");
            pathEl = document.getElementById("path");
            restartBtn = document.getElementById("restart-btn");
            saveBtn = document.getElementById("save-btn");
            saveIndicator = document.getElementById("save-indicator");
            aboutLink = document.getElementById("about-link");
            aboutOverlay = document.getElementById("about-overlay");
            closeAbout = document.getElementById("close-about");
            storyLink = document.getElementById("story-link");
            storyOverlay = document.getElementById("story-overlay");
            closeStory = document.getElementById("close-story");
            journeyLink = document.getElementById("journey-link");
            journeyOverlay = document.getElementById("journey-overlay");
            closeJourney = document.getElementById("close-journey");
            journeyListEl = document.getElementById("journey-list");
            breadcrumbContainerEl = document.getElementById("breadcrumb-container");
            continueBanner = document.getElementById("continue-banner");
            dismissContinueBanner = document.getElementById("dismiss-continue-banner");
            persistenceNoteEl = document.getElementById("persistence-note");
            illustrationPanelEl = document.getElementById("illustration-panel");
            illustrationBgEl = document.getElementById("illustration-bg");
            illustrationPortraitEl = document.getElementById("illustration-portrait");
            illustrationSpeakerChipEl = document.getElementById("illustration-speaker-chip");
            illustrationLocationChipEl = document.getElementById("illustration-location-chip");
            actTransitionEl = document.getElementById("act-transition");
            actTitleEl = document.getElementById("act-title");
            actSubtitleEl = document.getElementById("act-subtitle");
            endingEyebrowEl = document.getElementById("ending-eyebrow");
            endingTitleEl = document.getElementById("ending-title");
            endingSummaryEl = document.getElementById("ending-summary");

            // Attach event listeners
            restartBtn.addEventListener("click", () => {
                if (confirm("Are you sure you want to restart the game? Your progress will be lost.")) {
                    psychicLevel = 1;
                    currentPath = "Undecided";
                    relationships = {
                        "Molly Roger": 0,
                        "Professor Worthington": 0,
                        "Grace": 0
                    };
                    journeySteps = [];
                    lastShownAct = 0;
                    if (continueBanner) continueBanner.style.display = "none";
                    updateStatus();
                    loadScene("start", null, false);
                }
            });

            saveBtn.addEventListener("click", () => {
                saveGame(true);
            });

            aboutLink.addEventListener("click", () => {
                aboutOverlay.style.display = "flex";
            });

            closeAbout.addEventListener("click", () => {
                aboutOverlay.style.display = "none";
            });

            storyLink.addEventListener("click", () => {
                storyOverlay.style.display = "flex";
            });

            closeStory.addEventListener("click", () => {
                storyOverlay.style.display = "none";
            });

            journeyLink.addEventListener("click", openJourneyOverlay);

            closeJourney.addEventListener("click", () => {
                journeyOverlay.style.display = "none";
            });

            if (dismissContinueBanner) {
                dismissContinueBanner.addEventListener("click", () => {
                    continueBanner.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";
                }
            });

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

            // Show a brief loading state while we check for a saved journey.
            sceneTextEl.innerHTML = '<div class="loading-text"><i class="fas fa-spinner fa-spin"></i> Consulting the Dreamspace for your saved journey…</div>';

            const loaded = await loadGame();
            if (!loaded) {
                loadScene("start", null, false);
            }
        }

        window.addEventListener("DOMContentLoaded", initGame);
    </script>
</body>
</html>
MikeHiggins.me.uk LinkedIn GitHub Bluesky Contra Upwork
© 2026 Mike Higgins. All rights reserved.