【ENHYPEN·结缘】重逢 · 橙光叙事
by BlazeStar26928 lines32.8 KB
<!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.