🎮ArcadeLab

【ENHYPEN·结缘】重逢 · 橙光叙事

by BlazeStar26
928 lines32.8 KB
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>【ENHYPEN·结缘】重逢 · 橙光叙事</title>
    <!-- Google Font 优雅衬线字体 -->
    <link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;600;700&display=swap" rel="stylesheet" />
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Noto Serif SC', '华文楷体', 'KaiTi', serif;
            background: #1a1410;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            padding: 16px;
        }

        /* --- 主游戏容器 --- */
        #game-container {
            width: 100%;
            max-width: 820px;
            height: 92vh;
            max-height: 720px;
            background: #2d231e;
            border-radius: 32px;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(255, 215, 175, 0.15);
            overflow: hidden;
            display: flex;
            flex-direction: column;
            position: relative;
        }

        /* --- 顶部状态栏 --- */
        #top-bar {
            height: 52px;
            min-height: 52px;
            background: rgba(30, 22, 18, 0.85);
            backdrop-filter: blur(6px);
            border-bottom: 1px solid rgba(255, 200, 160, 0.12);
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 0 22px;
            flex-shrink: 0;
            z-index: 10;
        }

        #top-bar .game-title {
            font-size: 15px;
            font-weight: 700;
            color: #f0d5c0;
            letter-spacing: 3px;
            text-shadow: 0 0 12px rgba(220, 160, 120, 0.2);
        }

        #top-bar .game-title span {
            color: #dbaa7a;
        }

        #top-bar .chapter-info {
            font-size: 13px;
            color: #b5957a;
            background: rgba(60, 42, 34, 0.6);
            padding: 4px 16px;
            border-radius: 30px;
            border: 1px solid rgba(200, 160, 130, 0.15);
            letter-spacing: 1px;
        }

        /* --- 角色状态面板 (浮动在画面中) --- */
        #char-status {
            position: absolute;
            top: 68px;
            right: 20px;
            background: rgba(20, 14, 11, 0.8);
            backdrop-filter: blur(8px);
            border: 1px solid rgba(255, 200, 160, 0.12);
            border-radius: 16px;
            padding: 12px 16px;
            min-width: 140px;
            z-index: 8;
            font-size: 12px;
            color: #d4c0b0;
            line-height: 1.7;
            pointer-events: none;
            transition: opacity 0.4s ease;
            box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
        }

        #char-status .status-name {
            font-weight: 700;
            color: #f0d5c0;
            font-size: 13px;
            letter-spacing: 1px;
        }

        #char-status .status-name .tag {
            font-weight: 400;
            font-size: 10px;
            color: #b5957a;
            margin-left: 6px;
        }

        #char-status .status-item {
            display: flex;
            justify-content: space-between;
            gap: 12px;
            font-size: 11px;
            color: #c4aea0;
        }

        #char-status .status-item .label {
            color: #9a7f6e;
        }

        #char-status .status-item .value {
            color: #f0d5c0;
            font-weight: 600;
        }

        #char-status .divider {
            height: 1px;
            background: linear-gradient(to right, transparent, rgba(200, 160, 130, 0.2), transparent);
            margin: 6px 0;
        }

        /* --- 主画面 --- */
        #main-scene {
            flex: 1;
            position: relative;
            background: #2a1f1a;
            overflow: hidden;
            display: flex;
            flex-direction: column;
            justify-content: flex-end;
        }

        /* 场景背景层 */
        #scene-bg {
            position: absolute;
            inset: 0;
            background: #2a1f1a;
            transition: background 0.8s ease;
            z-index: 0;
        }

        #scene-bg::after {
            content: '';
            position: absolute;
            inset: 0;
            background: radial-gradient(ellipse at 50% 80%, rgba(60, 40, 30, 0.3), transparent 70%);
            pointer-events: none;
        }

        /* 场景氛围文字 (代替背景图) */
        #bg-atmosphere {
            position: absolute;
            bottom: 80px;
            left: 30px;
            font-size: 13px;
            color: rgba(180, 150, 130, 0.25);
            letter-spacing: 4px;
            font-weight: 300;
            z-index: 1;
            pointer-events: none;
            writing-mode: vertical-rl;
            text-orientation: mixed;
            line-height: 2;
            font-family: 'Noto Serif SC', serif;
        }

        /* 角色立绘占位 (左右) */
        .character-sprite {
            position: absolute;
            bottom: 140px;
            z-index: 2;
            transition: all 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
            opacity: 0;
            transform: translateY(20px) scale(0.96);
            pointer-events: none;
            text-align: center;
            width: 140px;
        }

        .character-sprite.show {
            opacity: 1;
            transform: translateY(0) scale(1);
        }

        .character-sprite .avatar {
            width: 100px;
            height: 100px;
            margin: 0 auto 6px;
            border-radius: 50%;
            background: linear-gradient(145deg, #3d2c24, #251b16);
            border: 2px solid rgba(200, 160, 130, 0.2);
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 40px;
            box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
            transition: border-color 0.4s;
        }

        .character-sprite .avatar .emoji {
            filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.4));
        }

        .character-sprite .char-name {
            font-size: 13px;
            color: #e8d5c5;
            font-weight: 600;
            text-shadow: 0 2px 12px rgba(0, 0, 0, 0.8);
            letter-spacing: 2px;
        }

        .character-sprite .char-sub {
            font-size: 10px;
            color: #b5957a;
            opacity: 0.7;
            margin-top: 2px;
            letter-spacing: 1px;
        }

        .character-sprite.left {
            left: 24px;
        }

        .character-sprite.right {
            right: 24px;
        }

        .character-sprite.center {
            left: 50%;
            transform: translateX(-50%) translateY(20px) scale(0.96);
        }
        .character-sprite.center.show {
            transform: translateX(-50%) translateY(0) scale(1);
        }

        /* --- 对话框区域 --- */
        #dialog-area {
            position: relative;
            z-index: 5;
            background: linear-gradient(180deg, rgba(20, 14, 10, 0.0) 0%, rgba(20, 14, 10, 0.92) 25%, #1a1410 100%);
            padding: 24px 28px 20px 28px;
            min-height: 200px;
            border-top: 1px solid rgba(255, 200, 160, 0.06);
            flex-shrink: 0;
        }

        /* 说话人 */
        #speaker-name {
            font-size: 15px;
            font-weight: 700;
            color: #f0d5c0;
            margin-bottom: 6px;
            letter-spacing: 2px;
            min-height: 28px;
            display: flex;
            align-items: center;
            gap: 10px;
        }

        #speaker-name .speaker-tag {
            font-size: 10px;
            font-weight: 400;
            color: #b5957a;
            background: rgba(60, 42, 34, 0.5);
            padding: 1px 12px;
            border-radius: 20px;
            border: 1px solid rgba(200, 160, 130, 0.1);
        }

        /* 对话内容 */
        #dialog-text {
            font-size: 17px;
            line-height: 1.8;
            color: #f0e4d8;
            min-height: 72px;
            letter-spacing: 0.5px;
            text-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
            white-space: pre-wrap;
            word-break: break-word;
        }

        #dialog-text .cursor-blink {
            display: inline-block;
            width: 2px;
            height: 1em;
            background: #f0d5c0;
            margin-left: 2px;
            vertical-align: text-bottom;
            animation: blink 0.9s step-end infinite;
        }

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

        /* 继续提示 */
        #continue-hint {
            text-align: right;
            font-size: 12px;
            color: #9a7f6e;
            margin-top: 4px;
            opacity: 0;
            transition: opacity 0.4s;
            letter-spacing: 2px;
        }

        #continue-hint.show {
            opacity: 0.6;
        }

        /* --- 选项区域 --- */
        #choices-area {
            display: none;
            flex-wrap: wrap;
            gap: 10px;
            margin-top: 12px;
            padding-top: 12px;
            border-top: 1px solid rgba(200, 160, 130, 0.08);
        }

        #choices-area.show {
            display: flex;
        }

        .choice-btn {
            font-family: 'Noto Serif SC', serif;
            background: rgba(60, 44, 36, 0.7);
            backdrop-filter: blur(4px);
            border: 1px solid rgba(200, 160, 130, 0.2);
            color: #f0e4d8;
            padding: 10px 20px;
            border-radius: 30px;
            font-size: 15px;
            cursor: pointer;
            transition: all 0.25s ease;
            flex: 1 1 auto;
            min-width: 120px;
            text-align: center;
            letter-spacing: 1px;
            box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2);
            font-weight: 600;
        }

        .choice-btn:hover {
            background: rgba(180, 140, 110, 0.25);
            border-color: rgba(220, 180, 150, 0.5);
            transform: translateY(-2px);
            box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
            color: #fff;
        }

        .choice-btn:active {
            transform: scale(0.97);
        }

        /* --- 标题画面 (覆盖层) --- */
        #title-screen {
            position: absolute;
            inset: 0;
            z-index: 20;
            background: #1a1410;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            padding: 40px 30px;
            transition: opacity 1.2s ease, transform 1.2s ease;
        }

        #title-screen.hide {
            opacity: 0;
            transform: scale(1.04);
            pointer-events: none;
        }

        #title-screen .title-main {
            font-size: 42px;
            font-weight: 700;
            color: #f0d5c0;
            letter-spacing: 8px;
            text-shadow: 0 0 40px rgba(200, 150, 110, 0.15);
            margin-bottom: 8px;
            line-height: 1.3;
            text-align: center;
        }

        #title-screen .title-main .accent {
            color: #dbaa7a;
        }

        #title-screen .title-sub {
            font-size: 16px;
            color: #b5957a;
            letter-spacing: 6px;
            margin-bottom: 30px;
            font-weight: 300;
        }

        #title-screen .title-divider {
            width: 60px;
            height: 2px;
            background: linear-gradient(to right, transparent, #dbaa7a, transparent);
            margin-bottom: 30px;
        }

        #title-screen .title-quote {
            font-size: 15px;
            color: #9a7f6e;
            max-width: 380px;
            text-align: center;
            line-height: 1.9;
            letter-spacing: 1px;
            margin-bottom: 40px;
            font-style: italic;
        }

        #start-btn {
            font-family: 'Noto Serif SC', serif;
            background: transparent;
            border: 1.5px solid rgba(200, 160, 130, 0.4);
            color: #f0d5c0;
            padding: 14px 52px;
            border-radius: 50px;
            font-size: 18px;
            letter-spacing: 6px;
            cursor: pointer;
            transition: all 0.4s ease;
            font-weight: 600;
            background: rgba(60, 42, 34, 0.2);
        }

        #start-btn:hover {
            background: rgba(180, 140, 110, 0.15);
            border-color: #dbaa7a;
            box-shadow: 0 0 30px rgba(200, 150, 110, 0.08);
            transform: scale(1.02);
        }

        #title-screen .title-credit {
            position: absolute;
            bottom: 28px;
            font-size: 11px;
            color: #5a4a3e;
            letter-spacing: 3px;
        }

        /* --- 响应式 --- */
        @media (max-width: 600px) {
            #game-container {
                height: 96vh;
                max-height: none;
                border-radius: 20px;
            }
            #top-bar .game-title {
                font-size: 13px;
                letter-spacing: 1px;
            }
            #top-bar .chapter-info {
                font-size: 11px;
                padding: 2px 12px;
            }
            #dialog-area {
                padding: 16px 18px 14px 18px;
                min-height: 160px;
            }
            #dialog-text {
                font-size: 15px;
                line-height: 1.7;
                min-height: 56px;
            }
            #speaker-name {
                font-size: 13px;
            }
            .choice-btn {
                font-size: 13px;
                padding: 8px 14px;
                min-width: 80px;
            }
            #char-status {
                top: 60px;
                right: 12px;
                padding: 8px 12px;
                min-width: 110px;
                font-size: 11px;
            }
            .character-sprite {
                width: 100px;
                bottom: 120px;
            }
            .character-sprite .avatar {
                width: 70px;
                height: 70px;
                font-size: 28px;
            }
            .character-sprite .char-name {
                font-size: 11px;
            }
            .character-sprite.left {
                left: 10px;
            }
            .character-sprite.right {
                right: 10px;
            }
            #title-screen .title-main {
                font-size: 28px;
                letter-spacing: 4px;
            }
            #title-screen .title-sub {
                font-size: 13px;
                letter-spacing: 3px;
            }
            #title-screen .title-quote {
                font-size: 13px;
                padding: 0 10px;
            }
            #bg-atmosphere {
                display: none;
            }
        }

        @media (max-width: 420px) {
            .character-sprite {
                width: 70px;
                bottom: 100px;
            }
            .character-sprite .avatar {
                width: 54px;
                height: 54px;
                font-size: 22px;
            }
            .character-sprite .char-name {
                font-size: 10px;
            }
            .character-sprite .char-sub {
                font-size: 8px;
            }
            #dialog-text {
                font-size: 14px;
            }
            #top-bar .game-title {
                font-size: 11px;
            }
            #top-bar .chapter-info {
                font-size: 10px;
                padding: 2px 8px;
            }
        }
    </style>
</head>
<body>

    <div id="game-container">

        <!-- ====== 标题画面 ====== -->
        <div id="title-screen">
            <div class="title-main">结<span class="accent">·</span>缘</div>
            <div class="title-sub">—— 重逢 · 破镜 ——</div>
            <div class="title-divider"></div>
            <div class="title-quote">
                “我不后悔留下她。<br>我只是……不后悔而已。”
            </div>
            <button id="start-btn">开 始</button>
            <div class="title-credit">✦ ENHYPEN · 大学ABO 橙光叙事 ✦</div>
        </div>

        <!-- ====== 顶部栏 ====== -->
        <div id="top-bar">
            <div class="game-title">✦ 结<span>·</span>缘</div>
            <div class="chapter-info" id="chapter-display">第一章 · 重逢</div>
        </div>

        <!-- ====== 主场景 ====== -->
        <div id="main-scene">

            <!-- 背景 -->
            <div id="scene-bg"></div>
            <div id="bg-atmosphere">· 校园 · 午后 ·</div>

            <!-- 角色立绘 -->
            <div class="character-sprite left" id="sprite-left">
                <div class="avatar"><span class="emoji">🐕</span></div>
                <div class="char-name">沈载伦</div>
                <div class="char-sub">Alpha · 英语系</div>
            </div>
            <div class="character-sprite right" id="sprite-right">
                <div class="avatar"><span class="emoji">🐱</span></div>
                <div class="char-name">李羲承</div>
                <div class="char-sub">Omega · 实用音乐</div>
            </div>
            <div class="character-sprite center" id="sprite-center">
                <div class="avatar"><span class="emoji">👶</span></div>
                <div class="char-name">李知妤</div>
                <div class="char-sub">· 2岁 ·</div>
            </div>

            <!-- 角色状态面板 -->
            <div id="char-status">
                <div class="status-name">沈载伦 <span class="tag">❤ 李羲承</span></div>
                <div class="divider"></div>
                <div class="status-item"><span class="label">信息素</span><span class="value">海盐 · 阳光棉</span></div>
                <div class="status-item"><span class="label">状态</span><span class="value" id="status-jake">平静</span></div>
                <div class="divider"></div>
                <div class="status-item"><span class="label">标记</span><span class="value">终身标记 ✓</span></div>
                <div class="status-item"><span class="label">羁绊</span><span class="value" id="status-bond">破碎 · 追寻</span></div>
            </div>

            <!-- ====== 对话框 ====== -->
            <div id="dialog-area">
                <div id="speaker-name">
                    <span id="speaker-label">叙述</span>
                    <span class="speaker-tag" id="speaker-tag">·</span>
                </div>
                <div id="dialog-text"><span class="cursor-blink"></span></div>
                <div id="continue-hint">▼ 点击继续</div>

                <!-- 选项区域 -->
                <div id="choices-area"></div>
            </div>

        </div><!-- /main-scene -->
    </div><!-- /game-container -->

    <script>
        // ============================================================
        //  ENHYPEN · 橙光叙事引擎  —  重逢 · 第一章
        // ============================================================

        // ---------- DOM 引用 ----------
        const titleScreen = document.getElementById('title-screen');
        const startBtn = document.getElementById('start-btn');
        const sceneBg = document.getElementById('scene-bg');
        const bgAtmo = document.getElementById('bg-atmosphere');
        const speakerLabel = document.getElementById('speaker-label');
        const speakerTag = document.getElementById('speaker-tag');
        const dialogText = document.getElementById('dialog-text');
        const continueHint = document.getElementById('continue-hint');
        const choicesArea = document.getElementById('choices-area');
        const chapterDisplay = document.getElementById('chapter-display');
        const statusJake = document.getElementById('status-jake');
        const statusBond = document.getElementById('status-bond');

        const spriteLeft = document.getElementById('sprite-left');
        const spriteRight = document.getElementById('sprite-right');
        const spriteCenter = document.getElementById('sprite-center');

        // ---------- 状态 ----------
        let currentSceneId = null;
        let isTyping = false;
        let typeTimer = null;
        let choiceMade = false;
        let sceneHistory = [];

        // ---------- 场景数据 ----------
        const scenes = {

            // ====== 序幕 ======
            prologue: {
                id: 'prologue',
                type: 'narration',
                speaker: null,
                text: '两年。\n七百三十个日夜。\n他找了李羲承两年。\n从澳洲到韩国,从愤怒到绝望,从执念到偏执。\n——今天,他找到了。',
                bg: '#2a1f1a',
                atmo: '· 回忆 · 煎熬 ·',
                left: 'show',
                right: 'hide',
                center: 'hide',
                next: 'scene1',
                statusJake: '焦虑 · 期待',
                statusBond: '破碎 · 追寻',
                chapter: '序章 · 归途'
            },

            // ====== 场景1:校园重逢 ======
            scene1: {
                id: 'scene1',
                type: 'narration',
                speaker: null,
                text: '转学第一天,首尔大学校园。\n阳光透过银杏叶洒下斑驳的光影。\n沈载伦抱着课本走过林荫道,忽然——\n他停住了。\n\n前方二十米,一个瘦削的身影正低头看手机。\n深色卫衣,宽松长裤,后颈贴着一块信息素贴。\n是李羲承。\n\n他瘦了太多。\n\n载伦的手开始发抖。',
                bg: '#2f241f',
                atmo: '· 校园 · 银杏 · 午后 ·',
                left: 'show',
                right: 'show',
                center: 'hide',
                next: 'scene2',
                statusJake: '震惊 · 颤抖',
                statusBond: '断裂 · 渴望',
                chapter: '第一章 · 重逢'
            },

            // ====== 场景2:追上去 ======
            scene2: {
                id: 'scene2',
                type: 'dialogue',
                speaker: '沈载伦',
                tag: 'Alpha · 英语系',
                text: '「……羲承。」\n\n声音沙哑得不像自己。\n\n前方的人顿住了。\n李羲承缓缓转过身来——\n那一瞬间,载伦看到了他眼底的惊惶。\n像是被猎人追到绝路的鹿。\n\n两年了。\n他躲了两年,藏了两年,\n可沈载伦还是找到了他。',
                bg: '#2f241f',
                atmo: '· 对视 · 心跳 · 停滞 ·',
                left: 'show',
                right: 'show',
                center: 'hide',
                next: null, // 选项
                statusJake: '失控 · 心痛',
                statusBond: '崩裂 · 执念',
                chapter: '第一章 · 重逢',
                isChoice: true,
                choices: [
                    { text: '「……你过得好吗?」', next: 'choiceA1' },
                    { text: '「这两年……你去哪了?」', next: 'choiceA2' }
                ]
            },

            // ====== 分支 A1 ======
            choiceA1: {
                id: 'choiceA1',
                type: 'dialogue',
                speaker: '沈载伦',
                tag: 'Alpha · 英语系',
                text: '「……你过得好吗?」\n\n这句话问出口的瞬间,载伦就后悔了。\n好?怎么可能好?\n羲承瘦了那么多,眼下的青黑浓得化不开。\n\n李羲承没有回答。\n他只是攥紧了手机,指节泛白。\n\n就在这时,手机响了。\n羲承接起电话,声音温柔得不像刚才——\n「嗯,妈咪马上到,知妤乖。」\n\n载伦怔住了。\n\n……知妤?',
                bg: '#2f241f',
                atmo: '· 电话 · 名字 · 裂痕 ·',
                left: 'show',
                right: 'show',
                center: 'hide',
                next: 'scene3',
                statusJake: '困惑 · 刺痛',
                statusBond: '裂痕 · 秘密',
                chapter: '第一章 · 重逢'
            },

            // ====== 分支 A2 ======
            choiceA2: {
                id: 'choiceA2',
                type: 'dialogue',
                speaker: '沈载伦',
                tag: 'Alpha · 英语系',
                text: '「这两年……你去哪了?」\n\n语气里有压抑的怒意,也有藏不住的委屈。\n李羲承别开视线,声音很轻:\n「……没去哪。就在这儿。」\n\n「就在这儿?」载伦几乎要笑出来,「我找了你两年——」\n\n手机响了。\n羲承接起电话,声音骤然柔软:\n「嗯,妈咪马上到,知妤乖。」\n\n载伦的话卡在喉咙里。\n\n……谁?',
                bg: '#2f241f',
                atmo: '· 质问 · 电话 · 惊雷 ·',
                left: 'show',
                right: 'show',
                center: 'hide',
                next: 'scene3',
                statusJake: '愤怒 · 心碎',
                statusBond: '裂痕 · 秘密',
                chapter: '第一章 · 重逢'
            },

            // ====== 场景3:知妤 ======
            scene3: {
                id: 'scene3',
                type: 'narration',
                speaker: null,
                text: '载伦跟了上去。\n\n他不知道自己为什么要跟。\n也许是因为那个名字——「知妤」。\n也许是因为羲承接电话时,那抹他从未见过的温柔。\n\n幼儿园门口。\n一个穿着淡黄色连衣裙的小女孩扑进羲承怀里,\n奶声奶气地喊:「妈咪!」\n\n羲承蹲下来,把她抱起来。\n小女孩搂着他的脖子,歪着头,\n看向不远处怔怔站着的载伦。\n\n她有一双下垂眼。\n和载伦一模一样。\n\n「……par……pa?」',
                bg: '#2a1f1a',
                atmo: '· 幼儿园 · 黄昏 · 真相 ·',
                left: 'show',
                right: 'show',
                center: 'show',
                next: 'scene4',
                statusJake: '天崩地裂',
                statusBond: '真相 · 父亲',
                chapter: '第一章 · 重逢'
            },

            // ====== 场景4:抉择 ======
            scene4: {
                id: 'scene4',
                type: 'narration',
                speaker: null,
                text: '李羲承的瞳孔骤然收缩。\n他下意识把知妤往怀里带了带,\n像一只护崽的猫。\n\n「……沈载伦。」\n\n他的声音在发抖。\n\n载伦往前走了一步。\n羲承退了一步。\n\n沉默。\n\n良久,载伦开口了——\n\n「你问过我吗?」\n「你问过我愿不愿意吗?」\n\n他的声音很轻,却像刀子。\n\n羲承的眼泪突然就掉了下来。',
                bg: '#2a1f1a',
                atmo: '· 对峙 · 泪水 · 裂痕 ·',
                left: 'show',
                right: 'show',
                center: 'show',
                next: null,
                statusJake: '崩溃 · 质问',
                statusBond: '撕裂 · 渴望',
                chapter: '第一章 · 重逢',
                isChoice: true,
                choices: [
                    { text: '「……让我抱抱她。」', next: 'ending1' },
                    { text: '「你欠我一个解释。」', next: 'ending2' }
                ]
            },

            // ====== 结局1 ======
            ending1: {
                id: 'ending1',
                type: 'narration',
                speaker: null,
                text: '「……让我抱抱她。」\n\n载伦的声音哑得几乎听不清。\n羲承怔了很久。\n\n怀里的知妤歪着头,伸出小手,\n朝载伦的方向抓了抓。\n\n「……pa……pa。」\n\n羲承的眼泪砸在知妤的头发上。\n他慢慢地,把女儿递了过去。\n\n载伦接过那个小小的、温热的身躯,\n忽然就跪了下来。\n\n他哭了。\n\n「对不起。」\n「……对不起。」\n\n知妤用小手擦了擦他的脸,\n咯咯地笑了。\n\n——有些缘分,断了两千公里,\n还是连上了。\n\n【第一章 · 完】',
                bg: '#1f1612',
                atmo: '· 拥抱 · 原谅 · 新生 ·',
                left: 'show',
                right: 'show',
                center: 'show',
                next: null,
                statusJake: '悔恨 · 爱',
                statusBond: '重建 · 父亲',
                chapter: '第一章 · 完',
                isEnd: true,
                endText: '—— 结·缘 第一章 完 ——'
            },

            // ====== 结局2 ======
            ending2: {
                id: 'ending2',
                type: 'narration',
                speaker: null,
                text: '「你欠我一个解释。」\n\n载伦的声音很冷,\n但眼底是烧灼的痛。\n\n羲承低着头,把知妤的脸轻轻按在自己肩窝。\n\n「……我知道。」\n「但我不知道怎么开口。」\n\n「那就慢慢说。」载伦上前一步,\n「我有的是时间。」\n「——这次,我不会再让你逃了。」\n\n羲承抬起头,泪眼里映着夕阳。\n和载伦坚定的脸。\n\n有些结,解了两年,\n终于松开了第一道。\n\n【第一章 · 完】',
                bg: '#1f1612',
                atmo: '· 对峙 · 承诺 · 未完 ·',
                left: 'show',
                right: 'show',
                center: 'show',
                next: null,
                statusJake: '坚定 · 不放手',
                statusBond: '破冰 · 续章',
                chapter: '第一章 · 完',
                isEnd: true,
                endText: '—— 结·缘 第一章 完 ——'
            }
        };

        // ---------- 工具函数 ----------
        function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }

        function getCharEmoji(name) {
            const map = { '沈载伦': '🐕', '李羲承': '🐱', '李知妤': '👶', '叙述': '📖' };
            return map[name] || '✦';
        }

        function getCharColor(name) {
            const map = { '沈载伦': '#dbaa7a', '李羲承': '#b8a0c0', '李知妤': '#f0c8a0', '叙述': '#b5957a' };
            return map[name] || '#b5957a';
        }

        // ---------- 渲染场景 ----------
        function renderScene(sceneId) {
            const scene = scenes[sceneId];
            if (!scene) return;

            currentSceneId = sceneId;
            choiceMade = false;

            // 更新章节
            if (scene.chapter) chapterDisplay.textContent = scene.chapter;

            // 背景
            if (scene.bg) {
                sceneBg.style.background = scene.bg;
            }
            if (scene.atmo) {
                bgAtmo.textContent = scene.atmo;
            }

            // 立绘
            spriteLeft.className = 'character-sprite left' + (scene.left === 'show' ? ' show' : '');
            spriteRight.className = 'character-sprite right' + (scene.right === 'show' ? ' show' : '');
            spriteCenter.className = 'character-sprite center' + (scene.center === 'show' ? ' show' : '');

            // 状态
            if (scene.statusJake) statusJake.textContent = scene.statusJake;
            if (scene.statusBond) statusBond.textContent = scene.statusBond;

            // 隐藏选项
            choicesArea.classList.remove('show');
            choicesArea.innerHTML = '';
            continueHint.classList.remove('show');

            // 清空对话框
            speakerLabel.textContent = scene.speaker || '叙述';
            speakerTag.textContent = scene.tag || '·';
            if (scene.speaker) {
                speakerLabel.style.color = getCharColor(scene.speaker);
            } else {
                speakerLabel.style.color = '#b5957a';
            }

            // 如果是结局
            if (scene.isEnd) {
                dialogText.innerHTML = scene.text.replace(/\n/g, '<br>');
                continueHint.classList.add('show');
                continueHint.textContent = '✦ 已完结 · 感谢阅读 ✦';
                return;
            }

            // 打字机效果
            typeText(scene.text, () => {
                // 打字完成后
                if (scene.isChoice && scene.choices) {
                    showChoices(scene.choices);
                } else if (scene.next) {
                    continueHint.classList.add('show');
                    continueHint.textContent = '▼ 点击继续';
                } else {
                    // 没有next也没有choice —— 应该是结局
                    continueHint.classList.add('show');
                    continueHint.textContent = '✦ 已完结 · 感谢阅读 ✦';
                }
            });
        }

        // ---------- 打字机效果 ----------
        function typeText(html, onComplete) {
            if (typeTimer) { clearInterval(typeTimer);

Game Source: 【ENHYPEN·结缘】重逢 · 橙光叙事

Creator: BlazeStar26

Libraries: none

Complexity: complex (928 lines, 32.8 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: enhypen-blazestar26-mr9goow4" to link back to the original. Then publish at arcadelab.ai/publish.