1<!DOCTYPE html>
2<html lang="zh-CN">
3<!-- v=1782700001 -->
4<head>
5<meta charset="UTF-8">
6<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
7<meta name="description" content="秃然飞升 — 程序员版单机文字冒险RPG,有剧情有选择有探索">
8<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
9<meta http-equiv="Pragma" content="no-cache">
10<meta http-equiv="Expires" content="0">
11<meta name="keywords" content="文字冒险,程序员游戏,修仙,单机RPG">
12<title>秃然飞升 - η空间 · 文字冒险RPG</title>
13<link rel="stylesheet" href="/assets/pet-cat.css">
14<style>
15*{margin:0;padding:0;box-sizing:border-box}
16body{
17 font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Noto Sans SC",sans-serif;
18 background:#0a0e1a;color:#c9d1d9;min-height:100vh;display:flex;flex-direction:column
19}
20.header-bar{
21 position:relative;
22 background:linear-gradient(135deg,#0d1117,#161b22);
23 padding:14px 16px;text-align:center;border-bottom:1px solid #30363d;
24 position:sticky;top:0;z-index:50
25}
26.header-bar h1{font-size:18px;background:linear-gradient(135deg,#58a6ff,#7c3aed);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
27.header-bar .sub{color:#8b949e;font-size:12px;margin-top:2px}
28
29#app{flex:1;display:flex;flex-direction:column;max-width:680px;margin:0 auto;width:100%;padding:10px}
30
31/* status */
32.status-bar{
33 background:#161b22;border:1px solid #30363d;border-radius:10px;padding:10px 12px;
34 display:grid;grid-template-columns:repeat(6,1fr);gap:4px;font-size:12px;flex-shrink:0
35}
36.stat-item{text-align:center}
37.stat-item .l{color:#8b949e;font-size:10px}
38.stat-item .v{font-weight:600;font-size:13px}
39.stat-lv .v{color:#c084fc}
40.stat-hp .v{color:#f85149}
41.stat-mp .v{color:#58a6ff}
42.stat-exp .v{color:#d29922}
43.bars{flex-shrink:0;margin-top:6px;display:flex;flex-direction:column;gap:3px}
44.bar-row{display:flex;align-items:center;gap:5px;font-size:10px}
45.bar-row .bl{width:22px;text-align:right;color:#8b949e;font-size:10px}
46.bar-outer{flex:1;height:6px;background:#21262d;border-radius:3px;overflow:hidden}
47.bar-inner{height:100%;border-radius:3px;transition:width .4s}
48.bar-hp .bar-inner{background:linear-gradient(90deg,#f85149,#da3633)}
49.bar-mp .bar-inner{background:linear-gradient(90deg,#58a6ff,#1f6feb)}
50.bar-exp .bar-inner{background:linear-gradient(90deg,#d29922,#9e6a03)}
51.bar-row .bt{width:38px;text-align:left;color:#8b949e;font-size:10px}
52
53/* main scene */
54.scene-area{
55 flex:1;background:#0d1117;border:1px solid #30363d;border-radius:10px;
56 margin:8px 0;padding:14px;overflow-y:auto;min-height:200px;max-height:55vh;
57 font-size:14px;line-height:1.7;scroll-behavior:smooth
58}
59.scene-area::-webkit-scrollbar{width:4px}
60.scene-area::-webkit-scrollbar-thumb{background:#30363d;border-radius:2px}
61.scene-title{color:#f0c040;font-weight:600;font-size:16px;margin-bottom:8px;text-align:center}
62.scene-text{margin-bottom:8px;color:#e6edf3}
63.scene-text .em{color:#d29922;font-weight:600}
64.scene-text .good{color:#3fb950}
65.scene-text .bad{color:#f85149}
66.scene-text .info{color:#58a6ff}
67.scene-text .special{color:#d2a8ff}
68.scene-text .sys{color:#8b949e;font-style:italic;font-size:13px}
69.scene-text .mono{font-family:'Courier New',monospace;color:#58a6ff;background:#161b22;padding:1px 6px;border-radius:3px;font-size:13px}
70.scene-text p{margin-bottom:6px}
71.divider{text-align:center;color:#30363d;margin:6px 0;font-size:12px}
72
73/* choice buttons */
74.choices{flex-shrink:0;display:flex;flex-direction:column;gap:6px;margin-top:2px}
75.choice-btn{
76 padding:12px 18px;border:1px solid #30363d;border-radius:8px;
77 background:#161b22;color:#c9d1d9;font-size:14px;cursor:pointer;
78 text-align:left;transition:all .15s;font-family:inherit;line-height:1.4
79}
80.choice-btn:hover{border-color:#58a6ff;background:#1c2128;transform:translateX(4px)}
81.choice-btn:disabled{opacity:.35;cursor:not-allowed;transform:none}
82.choice-btn.locked{opacity:.45;border-color:#484f58;cursor:not-allowed;transform:none;color:#8b949e}
83.choice-btn.locked:hover{border-color:#484f58;background:transparent;transform:none}
84.gold-cost{color:#d29922;font-size:11px;margin-left:4px}
85.item-req{font-size:10px;margin-left:4px;padding:1px 4px;border-radius:3px}
86.item-req.need{color:#f85149;background:#2d1b1b}
87.item-req.have{color:#3fb950;background:#1b2d1b}
88.code-block{background:#0d1117;border:1px solid #30363d;border-radius:6px;padding:12px;font-family:monospace;font-size:12px;line-height:1.6;color:#c9d1d9;margin:8px 0;white-space:pre-wrap;overflow-x:auto}
89.code-keyword{color:#ff7b72}.code-string{color:#a5d6ff}.code-comment{color:#8b949e;font-style:italic}.code-func{color:#d2a8ff}.code-correct{color:#3fb950;font-weight:bold}.code-wrong{color:#f85149;text-decoration:line-through}
90.choice-btn .tag{display:inline-block;background:#21262d;color:#8b949e;border-radius:3px;padding:0 6px;font-size:11px;margin-right:6px}
91
92/* nav tabs */
93.nav-tabs{flex-shrink:0;display:grid;grid-template-columns:repeat(4,1fr);gap:3px;margin-top:6px}
94.nav-tab{
95 padding:8px;border:1px solid #30363d;border-radius:8px;
96 background:#161b22;color:#8b949e;font-size:13px;cursor:pointer;
97 text-align:center;transition:all .15s;font-family:inherit
98}
99.nav-tab:hover{border-color:#58a6ff;color:#c9d1d9}
100.nav-tab.active{border-color:#58a6ff;color:#58a6ff;background:#0d1b2a}
101.nav-tab.locked{opacity:.45;cursor:not-allowed}
102
103@keyframes shake{0%,100%{transform:translateX(0)}25%{transform:translateX(-4px)}75%{transform:translateX(4px)}}
104@keyframes pulse{0%,100%{opacity:1}50%{opacity:.7}}
105@keyframes slideIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
106.fade-in{animation:slideIn .3s ease-out}
107.shake{animation:shake .3s}
108.pulse{animation:pulse 1s infinite}
109
110@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
111@keyframes shake{0%,100%{transform:translateX(0)}25%{transform:translateX(-5px)}75%{transform:translateX(5px)}}
112@keyframes pulse{0%,100%{opacity:1}50%{opacity:.6}}
113.fade-in{animation:fadeIn .3s ease-out}
114.shake{animation:shake .3s}
115.pulse{animation:pulse 1s infinite}
116
117/* explore tabs */
118.explore-panel{display:none;flex:1;flex-direction:column}
119.explore-panel.active{display:flex}
120.map-area{
121 flex:1;margin:8px 0;
122}
123.map-node{
124 padding:12px 8px;border:1px solid #30363d;border-radius:8px;
125 background:#161b22;text-align:center;cursor:pointer;transition:all .15s
126}
127.map-node:hover{border-color:#58a6ff;background:#1c2128}
128.map-node.current{border-color:#d29922;background:#1a1a0a}
129.map-node.disabled{opacity:.4;cursor:not-allowed}
130.map-node .mi{font-size:24px}
131.map-node .mn{font-size:11px;margin-top:4px;color:#8b949e}
132.map-node.current .mn{color:#d29922}
133.explore-log{
134 background:#0d1117;border:1px solid #30363d;border-radius:8px;
135 padding:14px;min-height:65vh;max-height:75vh;overflow-y:auto;
136 font-size:13px;line-height:1.5;flex-shrink:0
137}
138.explore-log::-webkit-scrollbar{width:4px}
139.explore-log::-webkit-scrollbar-thumb{background:#30363d;border-radius:2px}
140
141/* fight overlay */
142.fight-overlay{
143 display:none;position:fixed;top:0;left:0;right:0;bottom:0;
144 background:rgba(0,0,0,.85);z-index:100;justify-content:center;align-items:center
145}
146.fight-panel{
147 background:#161b22;border:2px solid #f85149;border-radius:16px;
148 padding:20px;max-width:380px;width:90%;text-align:center
149}
150.fight-panel h2{color:#f85149;font-size:18px}
151.fight-panel .mname{color:#d2a8ff;font-size:20px;font-weight:700;margin:6px 0}
152.fight-panel .minfo{color:#8b949e;font-size:12px}
153.fight-log{
154 background:#0d1117;border-radius:8px;padding:8px;margin:8px 0;
155 min-height:50px;max-height:100px;overflow-y:auto;font-size:13px;text-align:left;line-height:1.4
156}
157.fight-actions{display:grid;grid-template-columns:1fr 1fr;gap:6px}
158.fight-btn{
159 padding:10px;border:1px solid #30363d;border-radius:8px;
160 background:#21262d;color:#c9d1d9;font-size:14px;cursor:pointer;transition:all .15s;font-family:inherit
161}
162.fight-btn:hover:not(:disabled){border-color:#58a6ff;background:#1c2128}
163.fight-btn.atk{border-color:#f85149;color:#f85149}
164.fight-btn.atk:hover{background:#2d1115}
165.fight-btn.skl{border-color:#58a6ff;color:#58a6ff}
166.fight-btn.skl:hover{background:#0d1b2a}
167.fight-btn.run{border-color:#8b949e;color:#8b949e}
168@keyframes hitFlash{0%{background:rgba(248,81,73,0.5);transform:scale(1.05)}50%{background:rgba(248,81,73,0.3);transform:scale(1.02)}100%{background:transparent;transform:scale(1)}}.hit-flash{animation:hitFlash .4s ease-out}
169@keyframes shake{0%,100%{transform:translateX(0)}20%{transform:translateX(-4px)}40%{transform:translateX(4px)}60%{transform:translateX(-2px)}80%{transform:translateX(2px)}}.hit-shake{animation:shake .3s ease-in-out}
170/* save info */
171.game-foot{text-align:center;padding:6px 0;font-size:11px;color:#484f58;flex-shrink:0}
172.game-foot a{color:#58a6ff;text-decoration:none}
173
174/* chapter complete */
175.chapter-complete{
176 text-align:center;padding:16px;background:linear-gradient(135deg,#0d1117,#1a1a2e);
177 border:1px solid #d29922;border-radius:12px;margin:8px 0
178}
179.chapter-complete h2{color:#d29922;font-size:18px}
180.chapter-complete p{color:#8b949e;font-size:13px;margin:6px 0}
181
182/* transition */
183.fade-in{animation:fadeIn .35s}
184@keyframes fadeIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
185
186#playerCanvas{border-radius:10px;display:block;margin:0 auto;image-rendering:pixelated}
187.portrait-wrap{text-align:center;padding:6px;background:#0d1117;border:1px solid #30363d;border-radius:10px;flex-shrink:0}
188
189/* Animation keyframes - C scheme */
190@keyframes fadeInLine{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
191@keyframes slideUpBtn{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}
192@keyframes particleBurst{0%{transform:scale(1);opacity:1}100%{transform:scale(3);opacity:0}}
193@keyframes glowPulse{0%,100%{box-shadow:0 0 5px rgba(88,166,255,0.3)}50%{box-shadow:0 0 20px rgba(88,166,255,0.8)}}
194@keyframes floatUp{0%{transform:translateY(0);opacity:1}100%{transform:translateY(-40px);opacity:0}}@keyframes dmgBoom{0%{transform:scale(0.5);opacity:0}30%{transform:scale(1.4);opacity:1}60%{transform:scale(1);opacity:1}100%{transform:scale(0.8);opacity:0}}.dmg-pop{text-align:center;font-size:36px;font-weight:900;color:#f85149;text-shadow:0 0 20px rgba(248,81,73,0.8),0 0 40px rgba(248,81,73,0.4);animation:dmgBoom .8s ease-out;line-height:1;padding:8px 0}
195.ani-fade{animation:fadeInLine .4s ease-out}
196.ani-btn{animation:slideUpBtn .3s ease-out;animation-fill-mode:both}
197.ani-btn:nth-child(1){animation-delay:0.1s}
198.ani-btn:nth-child(2){animation-delay:0.2s}
199.ani-btn:nth-child(3){animation-delay:0.3s}
200.ani-btn:nth-child(4){animation-delay:0.4s}
201.particle{position:fixed;pointer-events:none;font-size:20px;animation:floatUp 1s ease-out;z-index:999}
202
203@media(max-width:480px){
204 .status-bar{grid-template-columns:repeat(2,1fr)}
205 .scene-area{min-height:150px;max-height:45vh;font-size:13px}
206 .header-bar h1{font-size:16px}
207 .map-area{grid-template-columns:repeat(2,1fr)}
208 .explore-log{min-height:55vh;max-height:65vh}
209 .fight-panel{padding:14px}
210}
211</style>
212<style>.src-btn{display:inline-block;padding:6px 14px;background:transparent;border:1px solid #7c3aed;color:#c084fc;border-radius:6px;font-size:12px;text-decoration:none;transition:all .2s;margin-left:8px}.src-btn:hover{background:#7c3aed;color:#fff}</style>
213</head>
214<body>
215
216<div class="header-bar">
217
218 <h1>🚀 秃然飞升</h1>
219 <div class="sub">Bug太多,不如修仙</div>
220</div>
221
222<div id="app">
223
224 <!-- bars only -->
225 <div class="bars">
226 <div class="bar-row bar-hp"><div class="bl">❤️</div><div class="bar-outer"><div class="bar-inner" id="hpBar" style="width:100%"></div></div><div class="bt" id="hpText">100/100</div></div>
227 <div class="bar-row bar-mp"><div class="bl">💧</div><div class="bar-outer"><div class="bar-inner" id="mpBar" style="width:100%"></div></div><div class="bt" id="mpText">50/50</div></div>
228 <div class="bar-row bar-exp"><div class="bl">⭐</div><div class="bar-outer"><div class="bar-inner" id="expBar" style="width:0%"></div></div><div class="bt" id="expText">0/100</div></div>
229 </div>
230
231 <!-- story tab -->
232 <div id="storyPanel" class="explore-panel active">
233 <div class="scene-area" id="sceneArea"></div>
234 <div class="choices" id="choices"></div>
235 </div>
236
237 <!-- explore tab -->
238 <div id="explorePanel" class="explore-panel">
239 <div class="explore-log" id="exploreLog"></div>
240 <div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;margin:4px 0;">
241 <button class="choice-btn" onclick="startTraining()" style="text-align:center;font-size:12px;padding:6px;border-color:#d2a8ff;">🧘 修炼(3秒冷却)</button>
242 <button class="choice-btn" onclick="startBounty()" style="text-align:center;font-size:12px;padding:6px;border-color:#f85149;">🐛 打怪赚钱</button>
243 </div>
244 <button class="choice-btn" onclick="toggleTutorialPanel()" style="width:100%;text-align:center;font-size:11px;padding:4px;margin-bottom:4px;border-color:#7c3aed;color:#c084fc;">📝 新手任务 <span id="tutorialBadge">0/8</span></button>
245 <div id="tutorialPanel" style="display:none;"></div>
246 <button class="choice-btn" onclick="showFragmentProgress()" style="width:100%;margin-top:4px;text-align:center;font-size:12px;padding:6px;border-color:#d29922;color:#d29922;">💾 碎片进度</button>
247 <div style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:4px;"><button class="choice-btn" onclick="doExplore()" style="text-align:center;font-size:11px;padding:5px;border-color:#58a6ff;color:#58a6ff;">🗺️ 探索</button><button class="choice-btn" onclick="treatLinYue()" style="text-align:center;font-size:11px;padding:5px;border-color:#ff69b4;color:#ff69b4;">💕 约林悦</button><button class="choice-btn" onclick="openPotionShop()" style="text-align:center;font-size:11px;padding:5px;border-color:#3fb950;color:#3fb950;">🧪 道具店</button><button class="choice-btn" onclick="showPartimeOptions()" style="text-align:center;font-size:11px;padding:5px;border-color:#f0c040;color:#f0c040;">💼 打工</button></div>
248 <button class="choice-btn" onclick="showDungeonList()" style="width:100%;margin-top:6px;text-align:center;font-size:13px;padding:8px;border-color:#c084fc;color:#c084fc;">🏰 副本剧情</button>
249 <div id="dungeonList"></div>
250 </div>
251
252 <!-- status tab -->
253 <div id="statusPanel" class="explore-panel">
254 <div class="scene-area" id="statusArea" style="max-height:none"></div><div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-top:6px;"><button class="choice-btn" onclick="openInventory()" style="text-align:center;font-size:12px;padding:6px;border-color:#d2a8ff;color:#d2a8ff;">🎒 背包</button><button class="choice-btn" onclick="showSkillTree()" style="text-align:center;font-size:12px;padding:6px;border-color:#58a6ff;color:#58a6ff;">📚 技能树</button></div>
255 </div>
256
257 <!-- nav -->
258 <div class="nav-tabs">
259 <button class="nav-tab active" id="tabStory" onclick="switchTab('story')">📖 剧情</button>
260 <button class="nav-tab" id="tabExplore" onclick="switchTab('explore')" style="font-size:12px">🗺️ <span id="exploreLabel">探索</span></button>
261 <button class="nav-tab" id="tabStatus" onclick="switchTab('status');renderStatus()">📊 状态</button>
262 <button class="nav-tab" id="tabShop" onclick="switchTab('status');renderShop()" style="border-color:#f0c040;color:#f0c040;">🛒 商店</button>
263 </div>
264
265 <div class="game-foot">
266 💾 自动存档 · <a href="javascript:saveGame()">手动存档</a> · <a href="javascript:if(confirm('重置后进度会丢失,确定?'))resetGame()">重置</a> · <a href="/games/cultivation-source.php" style="color:#7c3aed;">📄 源码</a>
267 </div>
268</div>
269
270<div class="fight-overlay" id="fightOverlay">
271 <div class="fight-panel">
272 <h2>⚔️ BUG 出现了!</h2>
273 <div class="mname" id="mName">NullPointerException</div>
274 <div class="minfo" id="mInfo">气血: 50</div>
275 <div id="pStats" style="font-size:13px;color:#c9d1d9;font-weight:600;margin:6px 0;padding:6px;background:#0d1117;border-radius:6px;">你 气血:100/100 | 灵力:50/50 | 攻击:10</div>
276 <div style="font-size:12px;color:#8b949e;margin:4px 0;">你-气血:100 灵力:50 攻击:10</div>
277 <div class="fight-log" id="fightLog"></div><div id="comboDisplay" style="text-align:center;font-size:14px;font-weight:700;margin-top:4px;min-height:22px;"></div>
278 <div class="fight-actions">
279 <button class="fight-btn atk" onclick="fightAct('attack')">🗡️ 普攻</button>
280 <button class="fight-btn skl" onclick="fightAct('skill')" id="skillBtn">✨ 技能 (30灵力)</button>
281 <button class="fight-btn" onclick="fightAct('defend')">🛡️ 防御</button>
282 <div id="skillExtra" style="display:grid;grid-template-columns:1fr 1fr;gap:4px;margin-top:4px;"></div>
283 <button class="fight-btn run" onclick="fightAct('run')">🏃 逃跑</button>
284 </div>
285 </div>
286</div>
287
288
289
290<script src="cultivation-chapters.js?v=1783300001"></script>
291<script>
292const SKILL_TREE = [
293 {id:'heavy',n:'🗡️ 重击',desc:'消耗30灵力,造成2倍伤害',costSP:2,minLv:3,costMP:30,fn:function(){return {d:G.atk*2+Math.floor(Math.random()*10),msg:'重击!'}}},
294 {id:'debug',n:'💻 Debug',desc:'消耗20灵力,恢复30%气血',costSP:2,minLv:4,costMP:20,fn:function(){var h=Math.floor(G.maxHp*0.3);G.hp=Math.min(G.maxHp,G.hp+h);return {d:0,msg:'气血 +'+h,heal:true}}},
295 {id:'chain',n:'🔥 连斩',desc:'消耗25灵力,连续攻击3次(每次0.7倍)',costSP:3,minLv:5,costMP:25,fn:function(){var t=0;for(var i=0;i<3;i++)t+=Math.floor(G.atk*0.7+Math.random()*3);return {d:t,msg:'连斩 x3!'}}},
296 {id:'refactor',n:'⚡ 重构',desc:'消耗50灵力,造成3倍伤害',costSP:4,minLv:8,costMP:50,fn:function(){return {d:G.atk*3+Math.floor(Math.random()*15),msg:'重构完成!毁灭性打击!'}}},
297 {id:'flow',n:'☯️ 心流',desc:'消耗40灵力,恢复全部灵力并提升下次攻击',costSP:5,minLv:10,costMP:40,fn:function(){G.mp=G.maxMp;return {d:0,msg:'进入心流状态!',buff:1.5}}},
298];
299const TITLES = [{min:1,n:'实习僧'},{min:3,n:'初级码农'},{min:5,n:'搬砖工'},{min:7,n:'高级搬砖工'},{min:10,n:'全栈小能手'},{min:13,n:'资深架构师'},{min:16,n:'技术大牛'},{min:20,n:'代码仙人'},{min:25,n:'飞升大神'},{min:30,n:'👑 神'}];
300const MONSTERS = [
301 // ★ 简单 (diff:1) - Lv.1-3
302 {n:'OffByOneError',hp:20,atk:2,d:'就差一点!每次都这么说。',diff:1},
303 {n:'SyntaxError',hp:25,atk:4,d:'少了个分号的小鬼在代码里蹦跶!',diff:1},
304 {n:'404 Not Found',hp:20,atk:3,d:'一只找不到资源的幽灵飘了过来。',diff:1},
305 {n:'UndefinedVariable',hp:22,atk:3,d:'"这个变量我没定义过啊!"它一脸无辜。',diff:1},
306 {n:'InfiniteLoop',hp:18,atk:4,d:'一直在原地打转的小Bug,看着有点晕。',diff:1},
307 {n:'TypoError',hp:15,atk:3,d:'拼写错误的小鬼,把"receive"写成了"recieve"。',diff:1},
308 {n:'MissingSemicolon',hp:20,atk:2,d:'少了一个分号,整段代码都崩了。',diff:1},
309
310 // ★★ 中等 (diff:2) - Lv.4-8
311 {n:'NullPointerException',hp:40,atk:6,d:'一个空指针妖怪扑来!',diff:2},
312 {n:'TypeError',hp:45,atk:7,d:'"字符串不能当数字用!"它嚣张喊道。',diff:2},
313 {n:'MergeConflict',hp:50,atk:6,d:'"你的代码和我的不一样!"它吼道。',diff:2},
314 {n:'Deadlock',hp:55,atk:7,d:'两个线程互相等着对方释放锁,僵住了!',diff:2},
315 {n:'MemoryLeak',hp:48,atk:8,d:'内存泄漏的幽灵,一直在偷偷吃掉你的内存。',diff:2},
316 {n:'RaceCondition',hp:42,atk:9,d:'竞态条件的双头怪!两个头各写各的。',diff:2},
317 {n:'DeprecatedAPI',hp:38,atk:7,d:'过时的API警告!"这个方法已经被弃用了。"',diff:2},
318 {n:'CORS Error',hp:35,atk:6,d:'跨域问题的异形!前端调不通后端。',diff:2},
319
320 // ★★★ 困难 (diff:3) - Lv.9-15
321 {n:'OutOfMemoryError',hp:70,atk:10,d:'内存泄漏的大胖子!堆空间快满了!',diff:3},
322 {n:'502 Bad Gateway',hp:75,atk:11,d:'网关错误的巨兽挡在路上!',diff:3},
323 {n:'StackOverflowError',hp:60,atk:12,d:'无限递归的深渊之影!函数调用栈爆了!',diff:3},
324 {n:'SQLInjection',hp:80,atk:10,d:'SQL注入的暗影!"DROP TABLE users——"',diff:3},
325 {n:'DDoS Attack',hp:85,atk:12,d:'分布式拒绝服务的洪流!服务器撑不住了!',diff:3},
326 {n:'BrokenPipeline',hp:65,atk:13,d:'CI/CD流水线断裂!构建卡在中间不动了。',diff:3},
327 {n:'ConfigHell',hp:70,atk:11,d:'配置地狱的恶魔!环境变量全乱了。',diff:3},
328
329 // ★★★★ BOSS (diff:4) - Lv.16+
330 {n:'👑 The Last Bug',hp:120,atk:18,d:'传说中的终极Bug!所有Bug的源头!',diff:4},
331 {n:'👑 Kernel Panic',hp:110,atk:20,d:'内核恐慌!整个操作系统都蓝屏了!',diff:4},
332 {n:'👑 Production Outage',hp:150,atk:15,d:'全线宕机!网站挂了!客户在怒吼!',diff:4},
333 {n:'👑 Data Corruption',hp:130,atk:22,d:'数据损坏的巨兽!数据库在流血!',diff:4},
334];
335const EXPLORE_EVENTS = [
336 {t:'你在工位下发现了一本《代码整洁之道》,翻了几页感觉受益匪浅!',atk:1,cat:'good'},
337 {t:'茶水间的咖啡机坏了,你只能喝速溶,心情不佳,HP-5',hp:-5,cat:'bad'},
338 {t:'同事请你吃零食,心情大好,HP+10',hp:10,cat:'good'},
339 {t:'你在代码仓库里发现了一坨祖传代码,注释写着别碰……你决定装作没看见',cat:'info'},
340 {t:'你在会议室捡到一本《设计模式》,偷偷收了起来!',cat:'good'},
341 {t:'天台上风很大,你抽了根烟(虽然你不抽烟),思考人生。什么也没发生。',cat:'info'},
342 {t:'老板路过你的工位,你赶紧切屏到IDE,装作在写代码',cat:'info'},
343 {t:'午休时间你去楼下便利店买了瓶可乐,感觉活过来了!',hp:5,cat:'good'},
344 {t:'你的代码被review了,改了三轮终于过了!',exp:10,cat:'good'},
345 {t:'产品经理又双叒叕改需求了!你深呼吸,告诉自己冷静',hp:-5,cat:'bad'},
346 {t:'你在地铁上给老人让座,他教你一个编程技巧,好人好报!',hp:15,cat:'good'},
347 {t:'公司的咖啡机修好了!新换的豆子味道不错,精神焕发!',mp:15,cat:'good'},
348 {t:'你发现一个隐藏的代码片段,是离职同事留下的彩蛋注释',exp:20,cat:'good'},
349 {t:'产品经理给你发了一个新需求,你又加班到十点',hp:-10,cat:'bad'},
350 {t:'突然断电了,你写了一半的代码没保存!',hp:-15,cat:'bad'},
351 {t:'你在GitHub上收到一个star,有人觉得你的工具很实用!',mp:10,cat:'good'},
352 {t:'隔壁工位的小王给你带了一杯奶茶,暖心!',hp:10,cat:'good'},
353 {t:'你午睡时梦到了完美的架构设计,醒来后全忘了',cat:'info'},
354 {t:'刷到一篇技术贴说99%的程序员都不知道的VSCode技巧,结果是标题党',cat:'info'},
355 {t:'老板在全员会上表扬了你上季度的表现,同事纷纷投来赞许的目光',exp:25,cat:'good'},
356];
357const WEAPONS = [{n:'😤 双手键盘',atk:0,cost:0,desc:'公司配的,能用'},{n:'⌨️ 机械键盘',atk:3,cost:100,desc:'青轴,声音很大,气势很足'},{n:'🔌 静电容键盘',atk:6,cost:300,desc:'码农终极键盘,手感如丝般顺滑'},{n:'💻 MacBook Pro',atk:10,cost:800,desc:'背光一亮的瞬间,Bug都怕你'},{n:'🚀 外星人电脑',atk:15,cost:1500,desc:'RGB拉满,性能爆表,一看就是大佬'}];
358
359// 每章解锁要求: level, weapon, armor, accessory, affection, money
360// 数组索引 = 章节号 (第0章要求索引0)
361var CHAPTER_REQS = [
362 {level:1}, // Ch0 面试: 无要求
363 {level:1}, // Ch1 入职: 无要求
364 {level:2}, // Ch2 第一个任务: 等级2
365 {level:3,weapon:1}, // Ch3 线上事故: 等级3+武器1
366 {level:4,armor:1}, // Ch4 初露锋芒: 等级4+护甲1
367 {level:5}, // Ch5 996考验: 等级5
368 {level:6,fate_love:1}, // Ch6 转角遇到爱: 等级6+真心碎片≥1
369 {level:7}, // Ch7 团建之旅: 等级7
370 {level:8,weapon:2,fate_tech:2}, // Ch8 重构之战: 等级8+武器2+技术碎片≥2
371 {level:9}, // Ch9 出差偶遇: 等级9
372 {level:10,fate_work:3}, // Ch10 跳槽风波: 等级10+奋斗碎片≥3
373 {level:11,armor:2,fate_daily:3}, // Ch11 技术主管: 等级11+护甲2+奇遇碎片≥3
374 {level:12,money:500,fate_work:4},// Ch12 老张要创业: 等级12+500金+奋斗碎片≥4
375 {level:13,fate_love:2}, // Ch13 第一次吵架: 等级13+真心碎片≥2
376 {level:14,fate_tech:3}, // Ch14 生死时刻: 等级14+技术碎片≥3
377 {level:15}, // Ch15 逆袭之路: 等级15
378 {level:16,fate_work:5,fate_love:3}, // Ch16 年会之夜: 等级16+奋斗5+真心3
379 {level:17}, // Ch17 巅峰之上: 等级17
380 {level:18,fate_love:5}, // Ch18 见家长: 等级18+真心碎片≥5
381 {level:20}, // Ch19 最终抉择: 等级20
382];
383function checkChapterReq(chapterIdx){
384 if(chapterIdx>=CHAPTER_REQS.length)return null;
385 var req=CHAPTER_REQS[chapterIdx];
386 if(!req)return null;
387 var missing=[];
388 if(req.level&&G.level<req.level)missing.push('等级'+req.level+'级');
389 if(req.weapon&&(G.weapon===undefined||G.weapon<req.weapon)){var wn=WEAPONS[req.weapon]?WEAPONS[req.weapon].n:'武器Lv.'+req.weapon;missing.push('装备['+wn+']');}
390 if(req.armor&&(G.armor===undefined||G.armor<req.armor)){var an=ARMORS[req.armor]?ARMORS[req.armor].n:'护甲Lv.'+req.armor;missing.push('装备['+an+']');}
391 if(req.accessory&&(G.accessory===undefined||G.accessory<req.accessory))missing.push('饰品等级'+req.accessory);
392 if(req.affection&&S.affection<req.affection)missing.push('林悦好感度'+req.affection);
393 if(req.money&&G.money<req.money)missing.push(req.money+'金币');
394 if(!S.fateFragments)S.fateFragments={};
395 if(req.fate_work&&(S.fateFragments.work||0)<req.fate_work)missing.push('奋斗碎片≧'+req.fate_work+'(去探索刷事件收集)');
396 if(req.fate_love&&(S.fateFragments.love||0)<req.fate_love)missing.push('真心碎片≧'+req.fate_love+'(去探索刷事件收集)');
397 if(req.fate_daily&&(S.fateFragments.daily||0)<req.fate_daily)missing.push('奇遇碎片≧'+req.fate_daily+'(去探索刷事件收集)');
398 if(req.fate_tech&&(S.fateFragments.tech||0)<req.fate_tech)missing.push('技术碎片≧'+req.fate_tech+'(去探索刷事件收集)');
399 if(req.fate_mystery&&(S.fateFragments.mystery||0)<req.fate_mystery)missing.push('神秘碎片≧'+req.fate_mystery+'(去探索刷事件收集)');
400 return missing.length>0?missing:null;
401}
402function showLockedChapter(nextCh,missing){
403 var req=CHAPTER_REQS[nextCh];
404 var chTitle=CHAPTERS[nextCh]?CHAPTERS[nextCh].title:'未知章节';
405 var h='<div style="margin:10px 0;padding:14px;border:2px solid #da3633;border-radius:10px;background:#1a1a2e;">';
406 h+='<div style="font-size:15px;font-weight:600;color:#f85149;margin-bottom:8px;">🔒 下一章「'+chTitle+'」被锁</div>';
407 h+='<div style="margin-bottom:10px;">';
408 missing.forEach(function(m){
409 var icon='📋';
410 if(m.indexOf('等级')>=0)icon='⭐';
411 else if(m.indexOf('武器')>=0||m.indexOf('装备')>=0)icon='🗡️';
412 else if(m.indexOf('护甲')>=0)icon='🛡️';
413 else if(m.indexOf('金币')>=0)icon='💰';
414 else if(m.indexOf('碎片')>=0||m.indexOf('奋斗')>=0)icon='💼';
415 else if(m.indexOf('真心')>=0)icon='❤️';
416 else if(m.indexOf('奇遇')>=0)icon='🎲';
417 else if(m.indexOf('技术')>=0)icon='💻';
418 else if(m.indexOf('神秘')>=0)icon='👻';
419 else if(m.indexOf('好感')>=0)icon='💕';
420 h+='<div style="display:flex;align-items:center;gap:6px;padding:4px 0;font-size:13px;">'+icon+' <span style="color:#c9d1d9;">'+m.replace(/(.*?)/g,'')+'</span></div>';
421 });
422 h+='</div><div style="font-size:12px;color:#8b949e;margin-bottom:10px;">💡 下面的操作可以帮你满足条件:</div>';
423 h+='<div style="display:flex;flex-direction:column;gap:5px;">';
424 var hasLevel=false,hasGold=false,hasWeapon=false,hasArmor=false,hasFate=false;
425 missing.forEach(function(m){
426 if(m.indexOf('等级')>=0)hasLevel=true;
427 if(m.indexOf('金币')>=0)hasGold=true;
428 if(m.indexOf('武器')>=0)hasWeapon=true;
429 if(m.indexOf('护甲')>=0)hasArmor=true;
430 if(m.indexOf('碎片')>=0||m.indexOf('奋斗')>=0||m.indexOf('真心')>=0||m.indexOf('奇遇')>=0||m.indexOf('技术')>=0||m.indexOf('神秘')>=0||m.indexOf('好感')>=0)hasFate=true;
431 });
432 if(hasLevel)h+='<button class="choice-btn" onclick="switchTab(\'explore\')" style="text-align:center;font-size:12px;padding:8px;border-color:#d2a8ff;">⭐ 去修炼升级 — 点击修炼得经验</button>';
433 if(hasGold)h+='<button class="choice-btn" onclick="startBounty()" style="text-align:center;font-size:12px;padding:8px;border-color:#f85149;">💰 去打怪赚钱 — 每次战斗赚金币</button>';
434 if(hasWeapon||hasArmor)h+='<button class="choice-btn" onclick="openShop()" style="text-align:center;font-size:12px;padding:8px;border-color:#f0c040;">🗡️ 去商店买装备 — 提升武器/护甲</button>';
435 if(hasFate)h+='<button class="choice-btn" onclick="switchTab(\'explore\')" style="text-align:center;font-size:12px;padding:8px;border-color:#c084fc;">🗺️ 去探索刷碎片 — 触发随机事件得碎片</button>';
436 h+='<button class="choice-btn" onclick="location.reload()" style="text-align:center;font-size:12px;padding:8px;border-color:#30363d;color:#8b949e;">🔄 刷新页面 — 检查进度</button>';
437 h+='</div></div>';
438 addScene(h);
439}
440const ARMORS = [{n:'👕 T恤',def:0,hp:0,cost:0,desc:'太普通了,毫无防御力'},{n:'👔 格子衫',def:2,hp:10,cost:60,desc:'程序员标配,穿上就有buff'},{n:'🧥 连帽衫',def:4,hp:30,cost:200,desc:'低调保暖,适合通宵加班'},{n:'🤵 西装',def:7,hp:60,cost:500,desc:'穿上它,产品经理都让你三分'},{n:'🛡️ 程序猿战甲',def:12,hp:120,cost:1200,desc:'传说级装备,代码怎么写都不崩'}];
441const ACCESSORIES = [{n:'🚫 无',mp:0,cost:0,desc:'光秃秃的,啥也没有'},{n:'☕ 咖啡杯',mp:10,cost:40,desc:'喝一口,灵感就来'},{n:'🪪 工牌',mp:25,cost:100,desc:'挂着它,感觉自己是正式员工了'},{n:'🫖 保温杯',mp:40,cost:300,desc:'中年程序员标配,枸杞养生'},{n:'⌚ 智能手表',mp:60,cost:600,desc:'看消息、计步数、装X三合一'}];
442const WEAPON_SHOP=[100,300,800,1500],ARMOR_SHOP=[60,200,500,1200],ACC_SHOP=[40,100,300,600];
443const SHOP_ITEMS=[{id:'potion',n:'🧪 气血丹',cost:20,desc:'恢复50气血',fn:function(){G.hp=Math.min(G.maxHp,G.hp+50);}},{id:'boost',n:'⚡ 力量符',cost:30,desc:'战斗+5攻击力(永久)',fn:function(){G.atk+=5;}},{id:'mp_pot',n:'🍵 灵力茶',cost:25,desc:'恢复30灵力',fn:function(){G.mp=Math.min(G.maxMp,G.mp+30);}}];
444var G={hp:100,maxHp:100,mp:50,maxMp:50,atk:10,def:2,level:1,exp:0,title:'实习僧',money:200,weapon:0,armor:0,accessory:0,potion:0,boost:0,sp:0,skills:[],items:[],inventory:[]};
445var S={chapter:0,scene:null,exploring:false,flags:{},unlockedMaps:[],currentLoc:'desk',explored:{},fightState:null,inChapter:false,chapterActive:false,affection:0,affectionBuff:{atk:0,def:0,exp:0,remaining:0},dungeonDone:{},tutorial:{}};
446function resetGame(){
447 G={hp:100,maxHp:100,mp:50,maxMp:50,atk:10,def:2,level:1,exp:0,title:'实习僧',money:200,weapon:0,armor:0,accessory:0,potion:0,boost:0,sp:0,skills:[],items:[],inventory:[]};
448 S={chapter:0,scene:null,exploring:false,flags:{},unlockedMaps:[],currentLoc:'desk',explored:{},fightState:null,inChapter:false,chapterActive:false,affection:0,affectionBuff:{atk:0,def:0,exp:0,remaining:0},dungeonDone:{},tutorial:{}};
449 localStorage.removeItem('cultivation_save');localStorage.removeItem('cultivation_state');
450 showChapterStart(0);updateUI();
451}
452function saveGame(){try{localStorage.setItem('cultivation_save',JSON.stringify({G:G,S:{chapter:S.chapter,scene:S.scene,flags:S.flags,affection:S.affection,affectionBuff:S.affectionBuff,exploring:S.exploring,dungeonDone:S.dungeonDone,fateFragments:S.fateFragments}}));}catch(e){}}
453function loadGame(){try{var d=JSON.parse(localStorage.getItem('cultivation_save'));if(d&&d.G&&d.S){Object.assign(G,d.G);Object.assign(S,d.S);if(S.fateFragments&&S.fateFragments.other&&S.fateFragments.other>0){var o=S.fateFragments.other;var e=Math.floor(o/3);S.fateFragments.work=(S.fateFragments.work||0)+e;S.fateFragments.love=(S.fateFragments.love||0)+e;S.fateFragments.daily=(S.fateFragments.daily||0)+e;S.fateFragments.other=0;saveGame();}return true;}}catch(e){}return false;}
454function updateUI(){
455 var n=getExpToLv();
456 var hb=document.getElementById('hpBar'),mb=document.getElementById('mpBar'),eb=document.getElementById('expBar');
457 if(hb)hb.style.width=(G.hp/G.maxHp*100)+'%';
458 if(mb)mb.style.width=(G.mp/G.maxMp*100)+'%';
459 if(eb)eb.style.width=(G.exp/n*100)+'%';
460 var ht=document.getElementById('hpText'),mt=document.getElementById('mpText'),et=document.getElementById('expText');
461 if(ht)ht.textContent=G.hp+'/'+G.maxHp;
462 if(mt)mt.textContent=G.mp+'/'+G.maxMp;
463 if(et)et.textContent=G.exp+'/'+n;
464}
465function processDialogue(html){
466 // Characters with their emoji and color
467 var chars={
468 '林悦':{emoji:'💕',color:'#ff69b4',cls:'chat-linyue'},
469 '老张':{emoji:'👨💻',color:'#58a6ff',cls:'chat-zhang'},
470 '产品经理':{emoji:'😤',color:'#f85149',cls:'chat-pm'},
471 '产品':{emoji:'😤',color:'#f85149',cls:'chat-pm'},
472 '老板':{emoji:'👔',color:'#d29922',cls:'chat-boss'},
473 '组长':{emoji:'👓',color:'#7c3aed',cls:'chat-lead'},
474 '面试官':{emoji:'📋',color:'#8b949e',cls:'chat-interview'},
475 'HR':{emoji:'📎',color:'#c084fc',cls:'chat-hr'},
476 '小王':{emoji:'🐱',color:'#3fb950',cls:'chat-wang'},
477 '保洁阿姨':{emoji:'🧹',color:'#d2a8ff',cls:'chat-cleaner'},
478 '保安':{emoji:'🛡️',color:'#c9d1d9',cls:'chat-guard'},
479 '前台':{emoji:'💼',color:'#ffa657',cls:'chat-front'}
480 };
481 // Detect character dialogue patterns
482 for(var name in chars){
483 var c=chars[name];
484 var re=new RegExp('(<[^>]*>)?'+name+'[::]([^<]*(<[^>]*>)?)*','g');
485 html=html.replace(re, function(match){
486 return '<div class="chat-line"><div class="chat-avatar '+c.cls+'" style="background:'+c.color+'22;border-color:'+c.color+'55">'+c.emoji+'</div><div class="chat-body"><span class="chat-name" style="color:'+c.color+'">'+name+'</span><div class="chat-bubble">'+match.replace(name+':','').replace(name+':','')+'</div></div></div>';
487 });
488 }
489 return html;
490}
491function addScene(h,cls){var a=document.getElementById('sceneArea'),d=document.createElement('div');d.className='scene-text ani-fade'+(cls?' '+cls:'');d.innerHTML=processDialogue(h);a.appendChild(d);a.scrollTop=a.scrollHeight;}
492function clearScene(){document.getElementById('sceneArea').innerHTML='';document.getElementById('choices').innerHTML='';}
493function getExpToLv(){return 100+G.level*20;}
494function gainExp(n){
495 G.exp+=n||0;var need=getExpToLv();
496 while(G.exp>=need){G.exp-=need;G.level++;need=getExpToLv();
497 var nt=TITLES.filter(function(t){return t.min<=G.level;});if(nt.length>0)G.title=nt[nt.length-1].n;
498 addScene('<p class="good">🎉 升级!当前境界:'+G.title+'</p>');var sa=document.getElementById('sceneArea');if(sa){var r=sa.getBoundingClientRect();showParticle('🎉',r.left+r.width/2,r.top+r.height/2);}
499 G.sp+=1;G.maxHp+=10;G.maxMp+=5;G.hp=Math.min(G.maxHp,G.hp+20);G.mp=Math.min(G.maxMp,G.mp+10);
500 }updateUI();
501}
502function canAfford(ch){
503 if(ch.cost&&ch.cost.gold&&G.money<ch.cost.gold)return false;
504 if(ch.require){
505 if(ch.require.item&&!G.items.includes(ch.require.item))return false;
506 if(ch.require.weapon!==undefined&&(G.weapon===undefined||G.weapon<ch.require.weapon))return false;
507 if(ch.require.armor!==undefined&&(G.armor===undefined||G.armor<ch.require.armor))return false;
508 if(ch.require.accessory!==undefined&&(G.accessory===undefined||G.accessory<ch.require.accessory))return false;
509 if(ch.require.skill!==undefined&&(!G.skills||!G.skills.includes(ch.require.skill)))return false;
510 }
511 if(ch.condition){
512 if(ch.condition.goldBelow!==undefined&&G.money>=ch.condition.goldBelow)return false;
513 if(ch.condition.hpBelow!==undefined&&G.hp>=ch.condition.hpBelow)return false;
514 if(ch.condition.hpAbove!==undefined&&G.hp<=ch.condition.hpAbove)return false;
515 if(ch.condition.lvBelow!==undefined&&G.level>=ch.condition.lvBelow)return false;
516 if(ch.condition.lvAbove!==undefined&&G.level<=ch.condition.lvAbove)return false;
517 }return true;
518}
519function setChoices(choices){
520 var c=document.getElementById('choices');c.innerHTML='';
521 if(!choices||choices.length===0){c.innerHTML='<div class="sys" style="color:#484f58;text-align:center;padding:8px;font-size:12px">—— 没有更多选项 ——</div>';return;}
522 choices.forEach(function(ch,i){
523 var ok=canAfford(ch),b=document.createElement('button');
524 b.className='choice-btn ani-btn'+(ok?'':' locked');
525 var label=ch.t;
526 if(ch.cost&&ch.cost.gold)label+=' <span class="gold-cost">💰'+ch.cost.gold+'</span>';
527 if(ch.require){
528 var reqs=[];
529 if(ch.require.weapon!==undefined){var wn=WEAPONS[ch.require.weapon]?WEAPONS[ch.require.weapon].n:'武器';reqs.push('<span class="item-req'+(G.weapon>=ch.require.weapon?' have':' need')+'">['+wn+']</span>');}
530 if(ch.require.armor!==undefined){var an=ARMORS[ch.require.armor]?ARMORS[ch.require.armor].n:'护甲';reqs.push('<span class="item-req'+(G.armor>=ch.require.armor?' have':' need')+'">['+an+']</span>');}
531 if(ch.require.accessory!==undefined){var an2=ACCESSORIES[ch.require.accessory]?ACCESSORIES[ch.require.accessory].n:'饰品';reqs.push('<span class="item-req'+(G.accessory>=ch.require.accessory?' have':' need')+'">['+an2+']</span>');}
532 if(ch.require.skill!==undefined){var sk=SKILL_TREE.find(function(s){return s.id===ch.require.skill;});var sn=sk?sk.n:ch.require.skill;reqs.push('<span class="item-req'+(G.skills&&G.skills.includes(ch.require.skill)?' have':' need')+'">['+sn+']</span>');}
533 if(reqs.length)label+=' <span style="font-size:10px">'+reqs.join(' ')+'</span>';
534 }
535 b.innerHTML=label;b.setAttribute('data-idx',i);
536 if(ok){b.onclick=function(){chooseOption(ch);};}else{b.onclick=null;b.title='条件不足';}
537 c.appendChild(b);
538 });
539}
540function chooseOption(ch){
541 if(!ch)return;document.querySelectorAll('#choices .choice-btn').forEach(function(b){b.disabled=true;});
542 if(ch.next==='CHAPTER_COMPLETE'){var nextCh=S.chapter+1;var missing=checkChapterReq(nextCh);if(missing){showLockedChapter(nextCh,missing);saveGame();return;}S.chapter=nextCh;G.sp+=1;if(S.chapter<CHAPTERS.length)showChapterStart(S.chapter);else{addScene('<div class="sys">所有剧情已完成,自由探索。</div>');switchTab('explore');}saveGame();return;}
543 if(ch.next==='__FIGHT__'){S.postFightScene=S.chapter;startFight(function(){var cd=CHAPTERS[S.postFightScene||S.chapter],aid=null;for(var k in cd.scenes){if(k.indexOf('_after_fight')>=0){aid=k;break;}}if(!aid){for(var k in cd.scenes){if(k.endsWith('_end')||k.endsWith('End')){aid=k;break;}}}if(aid&&cd.scenes[aid])showScene(cd.scenes[aid],aid);});return;}
544 if(ch.next==='__EXPLORE__'){switchTab('explore');return;}
545 if(ch.next==='__RESET__'){resetGame();return;}
546 clearScene();document.getElementById('choices').innerHTML='';
547 if(ch.cost&&ch.cost.gold){if(G.money<ch.cost.gold){addScene('<p class="bad">💰 金币不够!</p>');return;}G.money-=ch.cost.gold;addScene('<p class="bad">💰 -'+ch.cost.gold+' 金币</p>');}
548 if(ch.fight){S.customBoss=ch.fight;startFight(function(){var cd=CHAPTERS[S.chapter];if(ch.next&&cd.scenes[ch.next])showScene(cd.scenes[ch.next],ch.next);});return;}
549 var nxt=ch.next,cd=CHAPTERS[S.chapter];
550 if(nxt&&cd&&cd.scenes&&cd.scenes[nxt]){showScene(cd.scenes[nxt],nxt);requestAnimationFrame(function(){document.getElementById('sceneArea').scrollTop=0;});}else{addScene('<p class="bad">场景 '+nxt+' 不存在。</p>');}
551 if(ch.fx){
552 if(ch.fx.hp)G.hp=Math.max(1,Math.min(G.maxHp,G.hp+ch.fx.hp));
553 if(ch.fx.mp)G.mp=Math.min(G.maxMp,G.mp+ch.fx.mp);
554 if(ch.fx.atk)G.atk+=ch.fx.atk;if(ch.fx.def)G.def+=ch.fx.def;
555 if(ch.fx.exp)gainExp(ch.fx.exp);
556 if(ch.fx.money){G.money+=ch.fx.money;addScene('<p class="good">💰 +'+ch.fx.money+' 金币</p>');}
557 if(ch.fx.affection){S.affection=Math.min(100,S.affection+ch.fx.affection);addScene('<p class="good">❤️ 林悦好感度 +'+ch.fx.affection+'</p>');}
558 }
559 S.flags[ch.next]=true;updateUI();saveGame();
560}
561var ATMOS_MAP={0:'interview',1:'office',2:'office',3:'crisis',4:'office',5:'night',6:'rose',7:'green',8:'peak',9:'cafe',10:'crisis',11:'gold',12:'warm',13:'rose',14:'crisis',15:'gold',16:'purple',17:'peak',18:'warm',19:'sunset'};
562function setAtmosphere(idx){
563 var type=ATMOS_MAP[idx]||'office';
564 var sa=document.getElementById('sceneArea');if(sa){sa.classList.remove('atmos-office','atmos-warm','atmos-night','atmos-rose','atmos-cafe','atmos-gold','atmos-crisis','atmos-green','atmos-purple','atmos-peak','atmos-sunset');sa.classList.add('atmos-'+type);}
565 var el=document.getElementById('exploreLog');if(el){el.classList.remove('atmos-office','atmos-warm','atmos-night','atmos-rose','atmos-cafe','atmos-gold','atmos-crisis','atmos-green','atmos-purple','atmos-peak','atmos-sunset');el.classList.add('atmos-'+type);}
566}
567
568var SCENE_THEMES=[
569 {keys:["面试","简历","offer","笔试","投递"],e:"📋",bg:["#1a2332","#2a3a5a"],t:"面试",sub:"准备 · 紧张 · 机会"},
570 {keys:["林悦","约","约会","一起吃饭","奶茶","咖啡店","日料","电影","公园","爬山","书店","陶艺","音乐节","散步","宵夜","拉面"],e:"💕",bg:["#2a1a2a","#5a2a4a"],t:"与她",sub:"温暖 · 心动 · 陪伴"},
571 {keys:["Bug","报错","故障","事故","崩溃","宕机","502","500","死锁","注入","DDoS"],e:"🐛",bg:["#2a0a0a","#5a1a1a"],t:"Bug",sub:"排查 · 修复 · 战斗"},
572 {keys:["上线","部署","发布","功能","提测","评审","上线了","测试"],e:"🚀",bg:["#0a1a1a","#1a3a1a"],t:"上线",sub:"紧张 · 验收 · 通过"},
573 {keys:["代码","重构","架构","Docker","容器","SQL","索引","接口","API","数据库"],e:"💻",bg:["#0a0a1a","#1a1a3a"],t:"技术",sub:"编码 · 架构 · 优化"},
574 {keys:["老张","mentor","review","代码审查"],e:"👨",bg:["#1a1a2a","#2a2a4a"],t:"老张",sub:"严肃 · 面冷心热 · 成长"},
575 {keys:["加班","熬夜","深夜","凌晨","通宵","十二点","一点"],e:"🌙",bg:["#0a0a1a","#0a0a2a"],t:"深夜",sub:"键盘声 · 一个人 · 坚持"},
576 {keys:["开会","会议","评审","周会","站会","复盘"],e:"📊",bg:["#1a1a1a","#2a2a2a"],t:"会议",sub:"白板 · 讨论 · 决策"},
577 {keys:["咖啡","喝","水杯","茶水间"],e:"☕",bg:["#1a1a10","#3a2a10"],t:"休息",sub:"咖啡 · 放松 · 充电"},
578 {keys:["吃饭","食堂","面馆","烤串","外卖","午餐","晚餐"],e:"🍜",bg:["#2a1a10","#5a3a20"],t:"吃饭",sub:"美食 · 聊聊 · 烟火气"},
579 {keys:["王姐","总监","老板","CEO","晋升","述职"],e:"👔",bg:["#1a1a2a","#2a1a3a"],t:"职场",sub:"汇报 · 认可 · 成长"},
580 {keys:["同事","团建","聚餐","年会","庆祝","Team"],e:"🎉",bg:["#1a1a0a","#3a2a0a"],t:"团队",sub:"欢乐 · 归属 · 一起"},
581 {keys:["出差","酒店","旅行","车站","高铁"],e:"🏨",bg:["#1a1a2a","#3a3a5a"],t:"出差",sub:"在路上 · 陌生城市"},
582 {keys:["妈妈","妈","电话","家里","老家","父母"],e:"🏠",bg:["#1a1a0a","#2a1a0a"],t:"家",sub:"牵挂 · 温暖 · 后盾"},
583 {keys:["打印机","神秘","秘密","后门","异常"],e:"👻",bg:["#0a0a2a","#1a0a3a"],t:"神秘",sub:"未知 · 调查 · 发现"},
584];
585var THEME_DEFAULT={e:"📝",bg:["#161b22","#1a2332"],t:"日常",sub:"继续 · 前行 · 选择"};
586
587function getAutoTheme(text){
588 if(!text)return THEME_DEFAULT;
589 var best={score:0,theme:THEME_DEFAULT};
590 SCENE_THEMES.forEach(function(t){
591 var score=0;
592 t.keys.forEach(function(k){if(text.indexOf(k)>=0)score+=k.length;});
593 if(score>best.score){best.score=score;best.theme=t;}
594 });
595 return best.theme;
596}
597
598function drawAutoCanvas(theme,w,h){
599 if(!theme)theme=THEME_DEFAULT;
600 var cv=document.createElement("canvas");cv.width=w||320;cv.height=h||90;
601 cv.style.cssText="width:100%;max-width:"+(w||320)+"px;height:"+(h||90)+"px;border-radius:8px;margin-bottom:6px;display:block;";
602 var dx=cv.getContext("2d");
603 var dg=dx.createLinearGradient(0,0,w||320,h||90);dg.addColorStop(0,theme.bg[0]);dg.addColorStop(1,theme.bg[1]);
604 dx.fillStyle=dg;dx.fillRect(0,0,w||320,h||90);
605 dx.globalAlpha=0.06;
606 for(var di=0;di<5;di++){dx.beginPath();dx.arc(30+Math.random()*260,10+Math.random()*70,14+Math.random()*26,0,Math.PI*2);dx.fillStyle="#fff";dx.fill();}
607 dx.globalAlpha=1;
608 dx.font="30px sans-serif";dx.textAlign="center";dx.fillText(theme.e,55,52);
609 dx.fillStyle="#c9d1d9";dx.font="bold 14px sans-serif";dx.textAlign="left";dx.fillText(theme.t,90,36);
610 dx.fillStyle="#8b949e";dx.font="11px sans-serif";dx.fillText(theme.sub,90,58);
611 return cv;
612}
613
614function showChapterStart(idx){
615 if(idx>=CHAPTERS.length||idx<0)return;S.chapter=idx;S.scene=null;setAtmosphere(idx);
616 var ch=CHAPTERS[idx];clearScene();
617 addScene('<div class="scene-title">📖 '+ch.title+'</div>','');
618 requestAnimationFrame(function(){document.getElementById('sceneArea').scrollTop=0;});
619 if(ch.start&&ch.scenes&&ch.scenes[ch.start])showScene(ch.scenes[ch.start],ch.start);
620}
621function showScene(obj,sid){setAtmosphere(S.chapter);
622 if(!obj)return;clearScene();document.getElementById('choices').innerHTML='';
623 // Key scene illustrations
624 var keyScenes={'ch1_first_day':'🎒','ch6_meet_linyue':'💕','ch6_coffee_chat':'☕','ch9_hotel_lobby':'🏨','ch14_coding_all_night':'💻','ch16_new_year':'🎉','ch19_final_choice':'🌟'};
625 if(keyScenes[sid]){
626 var c=document.createElement('canvas');c.className='scene-illus';c.width=320;c.height=100;
627 var ictx=c.getContext('2d');
628 var bg={'ch1_first_day':['#1a2332','#2a3a5a'],'ch6_meet_linyue':['#2a1a2a','#5a2a4a'],'ch6_coffee_chat':['#2a1a10','#5a3a20'],'ch9_hotel_lobby':['#1a1a2a','#3a3a5a'],'ch14_coding_all_night':['#0a0a1a','#1a1a3a'],'ch16_new_year':['#1a1a0a','#3a2a0a'],'ch19_final_choice':['#0a1a2a','#2a0a3a']}[sid]||['#161b22','#1a2332'];
629 var grad=ictx.createLinearGradient(0,0,320,100);grad.addColorStop(0,bg[0]);grad.addColorStop(1,bg[1]);
630 ictx.fillStyle=grad;ictx.fillRect(0,0,320,100);
631 var emojis={'ch1_first_day':'🏢','ch6_meet_linyue':'💕','ch6_coffee_chat':'☕','ch9_hotel_lobby':'🏨','ch14_coding_all_night':'💻','ch16_new_year':'🎉','ch19_final_choice':'🌟'};
632 var titles={'ch1_first_day':'第一天上班','ch6_meet_linyue':'转角遇到爱','ch6_coffee_chat':'咖啡店','ch9_hotel_lobby':'出差之夜','ch14_coding_all_night':'生死时刻','ch16_new_year':'年会之夜','ch19_final_choice':'最终选择'};
633 var subs={'ch1_first_day':'写字楼 · 工位 · 新开始','ch6_meet_linyue':'电梯口 · 她笑了笑 · 世界安静了','ch6_coffee_chat':'窗外的雨 · 两杯拿铁','ch9_hotel_lobby':'陌生的城市 · 一条消息','ch14_coding_all_night':'凌晨三点 · 一个人 · 一个Bug','ch16_new_year':'灯火 · 掌声 · 一年','ch19_final_choice':'这一路走来 · 所有路都指向这一刻'};
634 ictx.font='28px sans-serif';ictx.textAlign='center';ictx.fillText(emojis[sid],60,55);
635 ictx.fillStyle='#c9d1d9';ictx.font='bold 13px sans-serif';ictx.textAlign='left';ictx.fillText(titles[sid],100,40);
636 ictx.fillStyle='#8b949e';ictx.font='11px sans-serif';ictx.fillText(subs[sid],100,65);
637 var sa=document.getElementById('sceneArea');if(sa)c.style.marginBottom='8px';
638 var first=sa&&sa.firstChild?sa.firstChild:null;
639 if(sa)sa.insertBefore(c,first);
640 }
641 var ch=CHAPTERS[S.chapter];addScene('<div class="sys">━━━ '+ch.title+' ━━━</div>','');
642 var t=obj.text;t=t.replace(/\\{(\\w+)\\}/g,function(m,k){return endVar(k);});addScene(t,'');
643 setChoices(obj.choices);updateUI();if(sid)S.scene=sid;requestAnimationFrame(function(){document.getElementById('sceneArea').scrollTop=0;});
644 // Auto illustration for non-key scenes
645 if(!keyScenes[sid]){
646 var theme=getAutoTheme(obj.text);
647 var cv=drawAutoCanvas(theme,320,90);
648 var sa=document.getElementById("sceneArea");
649 if(sa)sa.insertBefore(cv,sa.firstChild);
650 }
651 if((!obj.choices||obj.choices.length===0)&&obj.next&&obj.next!=='__RESET__'&&obj.next!=='__EXPLORE__'){
652 setTimeout(function(){var cd=CHAPTERS[S.chapter];if(cd&&cd.scenes&&cd.scenes[obj.next])showScene(cd.scenes[obj.next],obj.next);},1500);
653 }
654}
655function endVar(key){
656 var v={
657 gift_mom:S.flags&&S.flags['ch0_gift']?'<p>妈穿着你送的那条羊绒围巾坐在第一排。她头发白了不少,但笑容比什么时候都开心。</p>':'',
658 invest_zhang:S.flags&&S.flags['ch12_invest']?'<p>老张端着酒杯走过来:<span class="info">\"当年你投我那200金,现在翻了多少倍来着?\"</span></p>':'',
659 mech_keyboard:G.weapon>=1?'<p>你的桌上还放着那把青轴机械键盘——键帽都磨亮了,但你舍不得换。</p>':'<p>桌上放着公司配的那把键盘,用得顺手了就一直没换。</p>',
660 treat_linyue:S.flags&&S.flags['ch1_treat_lunch']?'<p>林悦坐在你旁边,笑着说起第一次吃面你抢着买单的事。</p>':'',
661 bought_flowers:S.flags&&S.flags['ch13_flowers']?'<p>你偶尔还会想起那次吵架后买的那束向日葵——那是你第一次见她哭。</p>':'',
662 bought_suit:G.armor>=3?'<p>你穿着当年年会买的那套西装。它在衣柜里挂了这么多年,依然合身。</p>':'',
663 rich:G.money>500?'你的银行账户里还有很多存款。':'你的银行账户里只剩下一点积蓄了。',
664 linyue_love:S.affection>50?'<p>她握着你的手。这么多年了,她的手还是那么暖。</p>':(S.affection>20?'<p>你们并肩坐着,肩膀轻轻靠在一起。</p>':'<p>你们并排坐着,中间隔了一点距离。</p>'),
665 saved_zhang:S.flags&&S.flags['ch3_mybug']?'<p>老张后来在年会上敬了你一杯酒:<span class="info">\"那一次要不是你,我可能就背锅了。\"</span></p>':'',
666 team_trust:S.flags&&S.flags['ch11_apply_lead']?'<p>当年的团队现在各奔东西了——但你们有个群,逢年过节还在发红包。</p>':'',
667 fate_summary:function(){var ff=S.fateFragments||{},m='work',mx=ff.work||0;['love','daily','tech','mystery'].forEach(function(k){var v=ff[k]||0;if(v>mx){mx=v;m=k;}});var msgs={work:'你无数次改bug到凌晨——那些深夜的坚守——最终垒成了你脚下的台阶。',love:'林悦一直站在你身后——你的每一次低谷——都有她的光和热。',daily:'生活中的小确幸——公园下棋、菜市场抢鸡蛋、捡漏键盘——这些平淡的快乐填满了你的日子。',tech:'从Vue2到Vue3、从MySQL5.7到8.0、从裸机到Docker——你踩过的每一个坑都让你走得更远。',mystery:'你在代码深处发现的那些秘密——打印机幽灵、隐藏的.secret文件夹——它们提醒你这不是一个普通的世界。'};return msgs[m]||'';}(),
668 fate_all:(S.fateFragments?Object.values(S.fateFragments).reduce(function(a,b){return a+(b||0);},0):0)>=40?'<p class="special" style="text-align:center;margin-top:16px;font-size:15px;color:#ff69b4;">✨ 你集齐了所有命运碎片。这个世界记住你了。✨</p>':'',
669 fate_mystery:(S.fateFragments&&(S.fateFragments.mystery||0)>=3)?'<p style="text-align:center;color:#8b949e;font-style:italic;font-size:12px;margin-top:20px;">——致那位在2005年火灾中备份数据的程序员,你的代码还在运行。——</p>':'',
670 fate_promotion:(S.fateFragments&&(S.fateFragments.work||0)>=3)?'<p>组长翻了你交上来的述职报告——看到你参与的项目列表时停了一下:<span class="info">"你这两年参与了六个项目?加班不少吧。"</span></p><p>你笑了笑没说话。那些深夜改Bug的时光——那些副本里练出来的经验——都在报告里的每一行里。</p><p>组长点了点头:<span class="good">"行——这次晋升答辩我全力推荐你。"</span></p>':'',
671 fate_birthday:(S.fateFragments&&(S.fateFragments.love||0)>=3)?'<p>你打开手机相册——翻到一张照片:你们在游乐园摩天轮上拍的。她的头发被风吹起来——你正好在看她——她正好在看镜头。</p><p>她凑过来看了一眼照片——笑了:<span class="info">"你还存着呢。"</span></p><p>"删了可惜。"你锁了屏幕。</p><p>她没说话——但往你身边靠了靠。</p>':'',
672 };
673 return v[key]||'';return v[key]||'';
674}
675function getDifficulty(){
676 var lv=G.level||1;
677 if(lv<=3)return 1;
678 if(lv<=8)return 2;
679 if(lv<=15)return 3;
680 return 4;
681}
682function getRandomMonster(diff){
683 var pool=MONSTERS.filter(function(m){return m.diff===diff;});
684 if(pool.length===0)pool=MONSTERS.filter(function(m){return m.diff===1;});
685 return pool[Math.floor(Math.random()*pool.length)];
686}
687function startFight(cb){
688 var area=document.getElementById('fightOverlay');
689 if(!area)return;area.style.display='flex';
690 S.fightState={hp:30,atk:5,d:'一个Bug出现!',cb:cb||null,enemyIdx:-1,m:null,dmgPop:0};
691 if(S.customBoss){var b=S.customBoss;S.fightState.hp=b.hp||30;S.fightState.atk=b.atk||5;S.fightState.d=b.d||'一个Bug出现!';S.customBoss=null;}
692 else{
693 var diff=getDifficulty();
694 var m=getRandomMonster(diff);
695 S.fightState.hp=m.hp;S.fightState.atk=m.atk;S.fightState.d=m.d;S.fightState.enemyIdx=-1;
696 S.fightState.monsterName=m.n;S.fightState.diff=m.diff;
697 }
698 renderFight();
699}
700function getDiffLabel(diff){
701 var labels={1:'<span style="color:#3fb950;">★简单</span>',2:'<span style="color:#d29922;">★★中等</span>',3:'<span style="color:#f85149;">★★★困难</span>',4:'<span style="color:#ff69b4;">★★★★BOSS</span>'};
702 return labels[diff]||'';
703}
704function renderFight(){
705 var fs=S.fightState;if(!fs)return;
706 var dLabel=getDiffLabel(fs.diff);
707 document.getElementById('mInfo').innerHTML='气血: '+fs.hp+' | 攻击: '+fs.atk+(fs.diff?' | '+dLabel:'');
708 var _wn=WEAPONS[G.weapon]?WEAPONS[G.weapon].n:'无装备';var _an=ARMORS[G.armor]?ARMORS[G.armor].n:'无装备';var _acn=ACCESSORIES[G.accessory]?ACCESSORIES[G.accessory].n:'无装备';document.getElementById('pStats').innerHTML='▸ 你 气血:'+G.hp+'/'+G.maxHp+' | 灵力:'+G.mp+'/'+G.maxMp+' | 攻击:'+G.atk+' <span style="font-size:11px;color:#8b949e;">🗡'+_wn+' 🛡'+_an+' 💍'+_acn+'</span>';
709 var log='<div>🐛 '+fs.d+'</div>';
710 if(fs.dmgPop>0){
711 log+='<div class="dmg-pop" id="dmgPop">-'+fs.dmgPop+'</div>';
712 setTimeout(function(){var el=document.getElementById('dmgPop');if(el)el.remove();},800);
713 }
714 log+=(fs.m?'<div>'+fs.m+'</div>':'');
715 document.getElementById('fightLog').innerHTML=log;
716 document.getElementById('skillBtn').disabled=false;renderSkillButtons();
717}
718function fightAct(act){
719 var fs=S.fightState;if(!fs)return;var dmg=0,msg='';
720 if(act==='attack'){var buffAtk=S.affectionBuff&&S.affectionBuff.remaining>0?S.affectionBuff.atk:0;dmg=G.atk+buffAtk+Math.floor(Math.random()*5);msg='你发起攻击!'+(buffAtk?' <span style="color:#ff69b4;">[好感加成+'+buffAtk+']</span>':'');var _wi=G.weapon||0;if(_wi>=1){var _we=[null,{c:0.10,b:2,l:'⌨️青轴轰鸣!'},{c:0.15,b:3,l:'🔌静电容如丝!'},{c:0.20,b:0.5,l:'💻背光亮起!',m:1},{c:0.25,b:5,l:'🚀RGB全功率!'}];var _w=_we[_wi];if(_w&&Math.random()<_w.c){var _ex=_w.m?Math.floor(dmg*_w.b):_w.b;dmg+=_ex;msg+=' <span style="color:#58a6ff;">['+_w.l+'+'+_ex+']</span>';}}}
721 else if(act==='defend'){dmg=0;var _ai=G.armor||0;var _alist=[null,{n:'格子衫',d:1,c:0.12},{n:'连帽衫',d:2,c:0.18},{n:'西装',d:3,c:0.22},{n:'战甲',d:5,c:0.30}];msg='你摆出防御姿态!';if(_ai>=1&&_alist[_ai]){var _a=_alist[_ai];msg+=' <span style="color:#3fb950;">🛡'+_a.n+'弹开</span>';if(Math.random()<_a.c){msg+=' <span style="color:#3fb950;">[格挡!减伤+'+_a.d+']</span>';}}}
722 else if(act==='skill'){return;}
723 else{
724 var sk=SKILL_TREE.find(function(s){return s.id===act;});
725 if(sk){if(G.mp<sk.costMP){msg='灵力不足!';return;}G.mp-=sk.costMP;var r=sk.fn();dmg=r.d;msg=r.msg;if(r.heal){G.hp=Math.min(G.maxHp,G.hp+r.d);G.hp=Math.min(G.maxHp,G.hp+0);}if(r.buff){G.atk=Math.floor(G.atk*r.buff);}}
726 }
727 if(dmg>0){fs.hp-=dmg;fs.dmgPop=dmg;msg+=' 造成 '+dmg+' 点伤害!';var fp=document.getElementById('fightOverlay');if(fp){var fr=fp.getBoundingClientRect();showParticle('💥',fr.left+fr.width/2,fr.top+fr.height/2);}var mi=document.getElementById('mInfo');if(mi){mi.classList.add('hit-flash','hit-shake');setTimeout(function(){mi.classList.remove('hit-flash','hit-shake');},500);}}
728 if(fs.hp<=0){fs.m='⚡ 击败成功!';G.money=(G.money||0)+Math.floor(5+Math.random()*11);var _acIdx=G.accessory||0;var _accR=[0,5,8,12,15];var _accC=[0,0.12,0.18,0.22,0.28];var _accN=['','☕咖啡杯','🪪工牌','🫖保温杯','⌚智能手表'];if(_acIdx>=1&&Math.random()<_accC[_acIdx]){G.mp=Math.min(G.maxMp,G.mp+_accR[_acIdx]);fs.m+=' <span style="color:#58a6ff;">['+_accN[_acIdx]+'恢复灵力+'+_accR[_acIdx]+']</span>';}if(S.affectionBuff&&S.affectionBuff.remaining>0){S.affectionBuff.remaining--;var expBonus=Math.floor(10*(S.affectionBuff.exp||0));if(expBonus>0){gainExp(expBonus);msg+=' <span style="color:#ff69b4;">[好感经验+'+expBonus+']</span>';}var buf=S.affectionBuff;exploreLog('<div style="color:#8b949e;font-size:11px;">💕 好感加成剩余 '+buf.remaining+' 场</div>');if(S.affectionBuff.remaining<=0){S.affectionBuff={atk:0,def:0,exp:0,remaining:0};exploreLog('<div style="color:#8b949e;font-size:11px;">💕 好感加成已耗尽</div>');}}renderFight();updateUI();setTimeout(function(){document.getElementById('fightOverlay').style.display='none';if(fs.cb)fs.cb();},800);return;}
729 if(act!=='defend'){var buffDef=S.affectionBuff&&S.affectionBuff.remaining>0?S.affectionBuff.def:0;var totalDef=G.def+buffDef;var ed=Math.max(1,fs.atk-Math.floor(totalDef/2)+Math.floor(Math.random()*3));G.hp-=ed;msg+=' 敌人反击 '+ed+' 点伤害!';var pi=document.getElementById('pStats');if(pi){pi.classList.add('hit-shake');setTimeout(function(){pi.classList.remove('hit-shake');},300);}+(buffDef?' <span style="color:#ff69b4;">[好感减伤+'+buffDef+']</span>':'');}
730 if(G.hp<=0){G.hp=0;msg+=' 你倒下了……';fs.m=msg+' <span class="bad">💀 战斗失败</span>';renderFight();var _rBtn=document.getElementById('fightLog');if(_rBtn){var _rb=document.createElement('button');_rb.className='choice-btn';_rb.textContent='💰 修养恢复 (50金 → 半血重试)';_rb.style.width='100%';_rb.style.marginTop='10px';_rb.style.padding='10px';_rb.onclick=function(){if(G.money<50){alert('金币不足!需要50金。');return;}G.money=Math.max(0,G.money-50);G.hp=Math.floor(G.maxHp*0.5);G.mp=Math.floor(G.maxMp*0.5);S.fightState.hp=S.fightState.initHp||30;S.fightState.atk=S.fightState.initAtk||5;renderFight();updateUI();var fl=document.getElementById('fightLog');if(fl)fl.innerHTML='<div class=\"good\">💊 修养完毕!恢复至半血再战!</div>';setTimeout(function(){(S.fightState.cb||function(){})();},600);};_rBtn.appendChild(_rb);}return;}
731 fs.m=msg;renderFight();updateUI();
732}
733function renderSkillButtons(){
734 var el=document.getElementById('skillExtra');if(!el)return;el.innerHTML='';
735 var learned=G.skills||[];if(learned.length===0)return;
736 learned.forEach(function(sid){
737 var sk=SKILL_TREE.find(function(s){return s.id===sid;});if(!sk)return;
738 var btn=document.createElement('button');btn.className='fight-btn';btn.textContent=sk.n+' ('+sk.costMP+'灵力)';
739 btn.disabled=G.mp<sk.costMP;btn.onclick=function(){fightAct(sid);};el.appendChild(btn);
740 });
741}
742function switchTab(tab){
743 document.querySelectorAll('.explore-panel').forEach(function(p){p.classList.remove('active');});
744 if(tab==='story'){document.getElementById('storyPanel').classList.add('active');}
745 else if(tab==='explore'){document.getElementById('explorePanel').classList.add('active');}
746 else if(tab==='status'){document.getElementById('statusPanel').classList.add('active');}
747}
748var PT_STORIES={
749enmu:[
750 {t:'<p>你在闲鱼上接了一个外包单子——给一个小公司的官网加一个在线客服功能。需求听起来简单,但对方的服务器还是Windows Server 2008,PHP版本古老得连composer都跑不了。</p><p>你折腾了半天才搭好环境。写代码反而只花了一个小时。你交付的时候附了一份环境升级建议书。对方很满意,说下次还找你。</p>'},
751 {t:'<p>一个前同事找你帮忙:他们公司的内部管理系统需要一个批量导出报表的功能。你打开代码一看——好家伙,整个系统是用Access数据库+ASP写的,比你的年龄还大。</p><p>你硬着头皮给这个老古董加了一个新功能。跑通了之后,你觉得自己可以去修文物了。</p>'},
752 {t:'<p>你在猪八戒上接了一个小活——给一个连锁奶茶店开发一个会员积分小程序的后端API。</p><p>需求很明确,对方的技术对接人也靠谱。你花了周末两个下午就搞定了。上线那天,对方发来了第一单的交易截图:<span class="good">「刚上线就有三单积分入账了,谢谢段哥!」</span>你看着截图,觉得这周末没白过。</p>'},
753 {t:'<p>一个做电商的朋友找你:他们的商品详情页加载太慢了,想让你帮忙优化一下。</p><p>你打开页面一看——首页一次性加载了三百多张商品图片,每张都是原图未压缩。你帮他们加上了图片懒加载、CDN加速和缩略图方案。加载时间从8秒降到了1.2秒。</p><p>朋友请了你一顿火锅作为额外酬劳。这单值。</p>'},
754 {t:'<p>你在GitHub上看到一个开源项目,提了一个PR被合并了。项目维护者私信你:<span class="info">「你的代码质量不错,有没有兴趣接一个付费功能开发?」</span></p><p>你接了下来。功能不难,但因为是给知名项目做贡献,你做得很认真。代码review了两轮才提交。合并那天,你的名字出现在了项目的CONTRIBUTORS名单里。</p><p>你截图发给了林悦。她回了一个👍。</p>'},
755 {t:'<p>一个外包中介找到你:有一个跨国公司的中国区网站需要做多语言适配。需求是中译英、中译日。</p><p>你以为只是改改i18n配置,结果打开代码发现——文本全写死在HTML里的。没有框架、没有模板引擎、没有国际化方案。裸写HTML。</p><p>你花了三天时间先引入了Vue,再把所有文本抽成JSON配置。改完之后,整个项目的代码量少了40%。你交活的时候附了一份《前端项目重构建议书》。对方看了之后说:<span class="good">「你干脆帮我们把整个前端重写了吧。」</span></p><p>下一个单子就这么来了。</p>'},
756 {t:'<p>你在技术群里看到有人求助:服务器的MySQL突然连不上了,库里的数据很重要但没有最近的备份。</p><p>你远程连上去看了看——不是数据丢了,是磁盘满了,MySQL自己挂了。你清理了慢查询日志和binlog,释放了20GB空间,MySQL自己恢复了。对方千恩万谢,要给你转账,你没收,说:<span class="good">「记得开定时备份。」</span></p>'},
757 {t:'<p>一个创业公司找你写一个MVP(最小可行产品)。需求:一个带用户系统和支付功能的预约平台。工期:两周。</p><p>你白天上班,晚上写外包。连着熬了十天。第十天晚上十一点,你终于把最后一个接口调通了。你提交代码的时候,顺便写了一份部署文档和API文档。</p><p>对方CTO验收后说了一句话:<span class="good">「你一个人顶我们半个技术团队。」</span></p><p>你关掉电脑,倒在床上,心想——下次不接这么急的单了。但你也知道,下次你还会接的。</p>'},
758],
759freelance:[
760 {t:'<p>你接了一个自由职业项目:给一个海外初创公司开发一个数据分析看板。技术栈是React + D3.js + Node.js。</p><p>对方的PM在Slack上给了你一份非常详细的PRD,每个功能点都有原型图和验收标准。你几乎不需要沟通就能直接开干。这种项目做起来太舒服了。</p><p>两周后交付,对方一次验收通过,还给你加了20%的bonus。你心想:跟专业的团队合作,真是享受。</p>'},
761 {t:'<p>你在Upwork上接了一个数据清洗的单子:一个研究机构有十几万条科研数据,格式混乱、字段缺失、编码不一致,需要清洗成标准格式。</p><p>你写了一个Python脚本,用pandas做数据清洗。跑了三个小时。跑完之后,数据从十几万条规整到了八万条有效记录。你还顺便生成了一份数据质量报告。</p><p>对方的研究员拿到数据后说:<span class="good">「这份数据我们之前三个人整理了两个月都没弄完。」</span></p>'},
762 {t:'<p>你接到一个自由职业项目:为一个在线教育平台开发自动批改作业的系统。核心功能是让学生提交代码,后端自动编译、运行、比对输出结果。</p><p>你设计了沙箱环境防止恶意代码,写了一套测试用例管理方案。项目做完了之后,你自己跑了一遍所有的测试——全部通过。</p><p>平台上线那周,有三百多个学生提交了作业。系统零故障。你看着后台统计面板,成就感满满的。</p>'},
763 {t:'<p>一个做IoT的创业公司找你帮忙:他们的设备上报数据太多,服务器经常被打满。你看看他们的架构——HTTP短连接上报,每次上报都是全量数据。</p><p>你帮他们设计了新的数据上报协议:基于MQTT、增量上报、服务端批量写入。改造完成后,服务器的CPU使用率从85%降到了15%。对方的技术合伙人请你吃了一顿人均五百的日料。</p><p>吃饭的时候他说:<span class="good">「你要不要全职来我们公司?给你CTO。」</span>你笑了笑,说考虑一下。</p>'},
764 {t:'<p>你在网上看到一个求助帖:一个公益组织的网站被黑了,首页被篡改成了赌博广告。他们没钱请安全公司,在求助有没有好心人帮忙。</p><p>你花了一个周末帮他们恢复了网站、修补了漏洞(一个老旧的WordPress插件)、加了防火墙和WAF。你还写了一篇《中小网站安全防护指南》发给了他们。</p><p>对方负责人感动得不行,非要给你寄一面锦旗。你说:<span class="good">「不用了,请把这个指南转发给需要的其他公益组织就行。」</span></p>'},
765 {t:'<p>你接了一个技术写作的单子:给一个开源数据库中间件写中文文档。</p><p>你花了三天把项目源码看了一遍,又花了五天写文档。你不仅写了API文档,还写了三篇最佳实践教程。项目维护者在Twitter上转发了你的文档,说:<span class="good">「This is the best Chinese documentation we have ever received.」</span></p><p>你的GitHub star一夜之间多了两百个。</p>'},
766 {t:'<p>一个做AI绘画的创业公司找你优化API响应速度。他们的文生图接口平均耗时12秒,用户投诉率很高。</p><p>你分析了调用链后发现:每次请求都会重新加载模型。你在服务层加了一个模型缓存池,又把图片处理从同步改成了异步+WebSocket推送。</p><p>优化后,接口响应降到了3秒以内。用户满意度从65%提升到了92%。对方老板在全员会上念了你的名字。</p>'},
767 {t:'<p>你接到一个最奇怪的外包:开发一个帮人自动生成周报的工具。</p><p>需求是这样的:读取Git提交记录、Jira任务状态、Slack消息关键词,自动生成一份格式精美的周报。你一边写一边笑——这可能是你写过的最讽刺的代码了。帮程序员写他们最讨厌写的东西。</p><p>产品上线后,两周内就有了两百多个付费用户。创始人给你发了一个大红包。</p><p>你心想:<span class="info">「程序员为了不写周报,什么都愿意付费。」</span></p>'},
768]
769};
770var PARTIME_TITLES=[
771{id:'enmu',n:'💼 外包私活',desc:'接点小活赚外快 · 无消耗',cost:0,
772 fn:function(){if(S.chapter===G.partimeCooldown){return '冷却中每2章可接1次';}var stories=PT_STORIES.enmu;var evt=stories[Math.floor(Math.random()*stories.length)];var gold=80+Math.floor(Math.random()*41);G.money+=gold;G.sp+=1;G.partimeCooldown=S.chapter;return {txt:evt.t,gold:gold};}},
773{id:'freelance',n:'💻 自由职业',desc:'专业接单赚大钱 · 消耗30灵力(Lv.5+)',cost:30,minLv:5,
774 fn:function(){if(S.chapter===G.partimeCooldown){return '冷却中每2章可接1次';}if(G.level<5){return '等级不足需要Lv.5';}G.mp=Math.max(0,G.mp-30);var stories=PT_STORIES.freelance;var evt=stories[Math.floor(Math.random()*stories.length)];var gold=120+Math.floor(Math.random()*61);G.money+=gold;G.sp+=2;G.partimeCooldown=S.chapter;return {txt:evt.t,gold:gold};}}
775];
776function sfxClick(){}
777function doPartime(id){
778 var pt=PARTIME_TITLES.find(function(x){return x.id===id;});if(!pt)return;
779 if((G.level||0)<(pt.minLv||0)){exploreLog('<div class="bad">❌ 等级不足,需要Lv.'+pt.minLv+'</div>');return;}
780 if(G.mp<pt.cost){exploreLog('<div class="bad">❌ 灵力不足</div>');return;}
781 if(S.chapter===G.partimeCooldown){exploreLog('<div class="bad">❌ 每2章可接1次私活,推进剧情再来</div>');return;}
782
783 // Work story illustration
784 var themeWork=getAutoTheme(result.txt||"打工");
785 var cvWork=drawAutoCanvas(themeWork,280,80);
786 var elWork=document.getElementById("exploreLog");
787 if(elWork){var w=document.createElement("div");w.appendChild(cvWork);elWork.appendChild(w);elWork.scrollTop=elWork.scrollHeight;}
788 var result=pt.fn();if(typeof result==='string'){if(result.indexOf('冷却')>=0||result.indexOf('不足')>=0){exploreLog('<div class="bad">'+result+'</div>');return;}exploreLog('<div class="good">✅ '+result+'</div>');updateUI();saveGame();return;}
789 exploreLog('<div style="color:#c9d1d9;border-left:3px solid #f0c040;padding-left:10px;margin:6px 0;">💼 打工故事:'+result.txt+'</div><div class="good" style="margin-top:4px;">💰 +'+result.gold+' 金币</div>');updateUI();saveGame();
790}
791
792function refreshChoices(){
793 var cd=CHAPTERS[S.chapter];
794 if(!cd||!cd.scenes||!S.scene)return;
795 var sc=cd.scenes[S.scene];
796 if(sc&&sc.choices){
797 var c=document.getElementById('choices');
798 c.innerHTML='';
799 sc.choices.forEach(function(ch,idx){
800 if(!canAfford(ch))return;
801 var b=document.createElement('button');b.className='choice-btn';b.textContent=ch.t;
802 b.onclick=function(){chooseOption(ch);};
803 c.appendChild(b);
804 });
805 }
806}
807
808function buyWeapon(i){if(G.money<WEAPON_SHOP[i-1]){alert('金币不足');return;}if(G.weapon>=i){alert('已拥有更好的');return;}G.money-=WEAPON_SHOP[i-1];G.weapon=i;G.atk=10+Math.floor(G.weapon/2)+WEAPONS[i].atk;updateUI();renderStatus();saveGame();switchTab('story');if(S.scene){refreshChoices();}}
809function buyArmor(i){if(G.money<ARMOR_SHOP[i-1]){alert('金币不足');return;}if(G.armor>=i){alert('已拥有更好的');return;}G.money-=ARMOR_SHOP[i-1];G.armor=i;G.def=2+G.armor*2;G.maxHp=100+G.armor*10;G.hp=Math.min(G.maxHp,G.hp);updateUI();renderStatus();saveGame();switchTab('story');if(S.scene){refreshChoices();}}
810function buyAccessory(i){if(G.money<ACC_SHOP[i-1]){alert('金币不足');return;}if(G.accessory>=i){alert('已拥有更好的');return;}G.money-=ACC_SHOP[i-1];G.accessory=i;G.maxMp=50+G.accessory*10;G.mp=Math.min(G.maxMp,G.mp);updateUI();renderStatus();saveGame();switchTab('story');if(S.scene){refreshChoices();}}
811function buyItem(id){var si=SHOP_ITEMS.find(function(x){return x.id===id;});if(!si)return;if(G.money<si.cost){alert('金币不足');return;}G.money-=si.cost;si.fn();alert("购买了 "+si.n);updateUI();renderStatus();saveGame();switchTab('story');}
812function sellWeapon(i){if(G.weapon<i)return;var val=Math.floor(WEAPONS[i].cost*0.5);G.money+=val;G.weapon=i-1;updateUI();renderStatus();saveGame();switchTab('story');}
813function sellArmor(i){if(G.armor<i)return;var val=Math.floor(ARMORS[i].cost*0.5);G.money+=val;G.armor=i-1;updateUI();renderStatus();saveGame();switchTab('story');}
814function sellAccessory(i){if(G.accessory<i)return;var val=Math.floor(ACCESSORIES[i].cost*0.5);G.money+=val;G.accessory=i-1;updateUI();renderStatus();saveGame();switchTab('story');}
815function renderStatus(){
816var area=document.getElementById('statusArea');if(!area)return;
817// ── Section 1: Portrait + Identity Card ──
818var h='<div style="display:flex;gap:10px;margin-bottom:8px;">';
819h+='<div class="portrait-wrap"><canvas id="playerCanvas" width="110" height="140"></canvas></div>';
820h+='<div style="flex:1;border:1px solid #30363d;border-radius:8px;padding:8px;background:#161b22;">';
821h+='<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;"><strong style="font-size:14px;color:#c9d1d9;">'+G.title+'</strong><span style="color:#8b949e;font-size:12px;">Lv.'+G.level+'</span></div>';
822h+='<div style="color:#f0c040;font-size:13px;font-weight:600;margin-bottom:6px;">💰 '+G.money+' 金</div>';
823// Equip summary
824var wn=WEAPONS[G.weapon]?WEAPONS[G.weapon].n:'无';var an=ARMORS[G.armor]?ARMORS[G.armor].n:'无';var acn=ACCESSORIES[G.accessory]?ACCESSORIES[G.accessory].n:'无';
825var ws=WEAPONS[G.weapon]?'<span style="color:#58a6ff">⚔️+'+WEAPONS[G.weapon].atk+'</span>':'';var as_=ARMORS[G.armor]?'<span style="color:#3fb950">🛡️+'+ARMORS[G.armor].def+' ❤️+'+ARMORS[G.armor].hp+'</span>':'';var acs=ACCESSORIES[G.accessory]?'<span style="color:#58a6ff">💧+'+ACCESSORIES[G.accessory].mp+'</span>':'';
826h+='<div style="font-size:11px;color:#8b949e;line-height:1.6;"><div>🗡️ '+wn+' '+ws+'</div><div>🛡️ '+an+' '+as_+'</div><div>💍 '+acn+' '+acs+'</div></div>';
827h+='</div></div>';
828
829// ── Section 2: HP/MP/EXP bars ──
830var hpp=Math.round(G.hp/G.maxHp*100),mpp=Math.round(G.mp/G.maxMp*100),n=getExpToLv(),expp=Math.round(G.exp/n*100);
831h+='<div style="border:1px solid #30363d;border-radius:8px;padding:6px 10px;background:#0d1117;margin-bottom:6px;">';
832h+='<div style="display:flex;align-items:center;gap:4px;font-size:11px;margin-bottom:3px;"><span>❤️</span><div style="flex:1;height:6px;background:#21262d;border-radius:3px;overflow:hidden;"><div style="width:'+hpp+'%;height:100%;background:linear-gradient(90deg,#f85149,#da3633);border-radius:3px;transition:width .3s;"></div></div><span style="width:60px;text-align:right;color:#8b949e;">'+G.hp+'/'+G.maxHp+'</span></div>';
833h+='<div style="display:flex;align-items:center;gap:4px;font-size:11px;margin-bottom:3px;"><span>💧</span><div style="flex:1;height:6px;background:#21262d;border-radius:3px;overflow:hidden;"><div style="width:'+mpp+'%;height:100%;background:linear-gradient(90deg,#58a6ff,#1f6feb);border-radius:3px;transition:width .3s;"></div></div><span style="width:60px;text-align:right;color:#8b949e;">'+G.mp+'/'+G.maxMp+'</span></div>';
834h+='<div style="display:flex;align-items:center;gap:4px;font-size:11px;"><span>⭐</span><div style="flex:1;height:6px;background:#21262d;border-radius:3px;overflow:hidden;"><div style="width:'+expp+'%;height:100%;background:linear-gradient(90deg,#d29922,#9e6a03);border-radius:3px;transition:width .3s;"></div></div><span style="width:60px;text-align:right;color:#8b949e;">'+G.exp+'/'+n+'</span></div>';
835h+='</div>';
836
837// ── Section 3: Stats Row ──
838h+='<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:4px;margin-bottom:6px;">';
839h+='<div style="border:1px solid #30363d;border-radius:6px;padding:6px;text-align:center;background:#161b22;"><div style="font-size:10px;color:#8b949e;">⚔️ 攻击</div><div style="font-size:15px;font-weight:600;color:#f85149;">'+G.atk+'</div></div>';
840h+='<div style="border:1px solid #30363d;border-radius:6px;padding:6px;text-align:center;background:#161b22;"><div style="font-size:10px;color:#8b949e;">🛡️ 防御</div><div style="font-size:15px;font-weight:600;color:#3fb950;">'+G.def+'</div></div>';
841h+='<div style="border:1px solid #30363d;border-radius:6px;padding:6px;text-align:center;background:#161b22;"><div style="font-size:10px;color:#8b949e;">✨ 技能点</div><div style="font-size:15px;font-weight:600;color:#d2a8ff;">'+G.sp+'</div></div>';
842h+='</div>';
843
844// ── Section 4: Affection + Fragments ──
845h+='<div style="border:1px solid #30363d;border-radius:8px;padding:8px;background:#161b22;">';
846// Affection
847var affDisp=S.affection||0;var affBars='';for(var ai=0;ai<10;ai++){affBars+=ai*10<affDisp?'<span style="color:#ff69b4;">█</span>':'<span style="color:#30363d;">█</span>';}
848h+='<div style="font-size:12px;margin-bottom:4px;"><span style="color:#ff69b4;">💕 林悦好感 '+affDisp+'/100</span> <span style="font-size:10px;">'+affBars+'</span></div>';
849if(S.affectionBuff&&S.affectionBuff.remaining>0){h+='<div style="font-size:11px;color:#f0c040;margin-bottom:4px;">⚡ 好感加成: 攻击+'+S.affectionBuff.atk+' 防御+'+S.affectionBuff.def+' 剩余'+S.affectionBuff.remaining+'场</div>';}
850// Fragments
851if(S.fateFragments){var ff=S.fateFragments;var total=(ff.work||0)+(ff.love||0)+(ff.daily||0)+(ff.tech||0)+(ff.mystery||0);
852h+='<div style="font-size:12px;color:#c084fc;font-weight:600;">💠 命运碎片 '+total+'/40</div>';
853h+='<div style="font-size:10px;color:#8b949e;margin-top:2px;">💼'+(ff.work||0)+'/10 ❤️'+(ff.love||0)+'/10 🎲'+(ff.daily||0)+'/10 💻'+(ff.tech||0)+'/5 👻'+(ff.mystery||0)+'/5</div>';}
854h+='</div>';
855
856area.innerHTML=h;
857setTimeout(function(){drawPlayerPortrait();},50);
858}
859function drawPlayerPortrait(){
860var cv=document.getElementById('playerCanvas');if(!cv)return;
861var ctx=cv.getContext('2d');if(!ctx)return;
862var W=110,H=140;ctx.clearRect(0,0,W,H);
863ctx.fillStyle='#161b22';ctx.beginPath();if(ctx.roundRect)ctx.roundRect(0,0,W,H,10);else ctx.rect(0,0,W,H);ctx.fill();
864
865var skin='#f5d6b8',hair='#3d2b1f',eye='#333',eyeHi='#fff',mouth='#c97';
866var armorIdx=Math.min(G.armor||0,4);
867var clothColors=[['#e8e8e8','#ddd','#fff'],['#c04040','#a03030','#e8b8b8'],['#666','#555','#888'],['#1a1a2e','#0d0d1a','#fff'],['#1a1a3e','#0a0a2e','#4a4aff']];
868var cc=clothColors[armorIdx];
869var cx=55,headY=30,headR=24;
870
871// Hair back
872ctx.fillStyle=hair;ctx.beginPath();ctx.arc(cx,headY-2,headR+3,Math.PI,0);ctx.fill();
873ctx.fillRect(cx-headR-3,headY-5,6,35);ctx.fillRect(cx+headR-3,headY-5,6,35);
874// Head
875ctx.fillStyle=skin;ctx.beginPath();ctx.arc(cx,headY,headR,0,Math.PI*2);ctx.fill();
876// Bangs
877ctx.fillStyle=hair;ctx.beginPath();ctx.ellipse(cx,headY-8,headR+2,12,0,Math.PI,0);ctx.fill();
878for(var i=0;i<3;i++){var bx=cx-14+i*14;ctx.beginPath();ctx.ellipse(bx,headY-2,6,16,-0.2+i*0.2,Math.PI,0);ctx.fill();}
879// Eyes
880ctx.fillStyle='#fff';ctx.beginPath();ctx.ellipse(cx-10,headY+2,6,7,0,0,Math.PI*2);ctx.fill();ctx.beginPath();ctx.ellipse(cx+10,headY+2,6,7,0,0,Math.PI*2);ctx.fill();
881ctx.fillStyle='#6a4c93';ctx.beginPath();ctx.ellipse(cx-10,headY+3,4,5,0,0,Math.PI*2);ctx.fill();ctx.beginPath();ctx.ellipse(cx+10,headY+3,4,5,0,0,Math.PI*2);ctx.fill();
882ctx.fillStyle=eye;ctx.beginPath();ctx.arc(cx-10,headY+3,2.5,0,Math.PI*2);ctx.fill();ctx.beginPath();ctx.arc(cx+10,headY+3,2.5,0,Math.PI*2);ctx.fill();
883ctx.fillStyle=eyeHi;ctx.beginPath();ctx.arc(cx-8,headY,1.5,0,Math.PI*2);ctx.fill();ctx.beginPath();ctx.arc(cx+12,headY,1.5,0,Math.PI*2);ctx.fill();
884// Blush
885ctx.fillStyle='rgba(255,150,150,0.2)';ctx.beginPath();ctx.ellipse(cx-16,headY+10,5,3,0,0,Math.PI*2);ctx.fill();ctx.beginPath();ctx.ellipse(cx+16,headY+10,5,3,0,0,Math.PI*2);ctx.fill();
886// Mouth
887ctx.fillStyle=mouth;ctx.beginPath();ctx.arc(cx,headY+14,3,0.1,Math.PI-0.1);ctx.stroke();
888
889// Body
890var bodyTop=headY+headR+5;
891ctx.fillStyle=skin;ctx.fillRect(cx-5,bodyTop,10,8);
892var torsoTop=bodyTop+8,torsoW=42,torsoH=48;
893
894if(armorIdx===0){
895 ctx.fillStyle=cc[0];ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx-torsoW/2,torsoTop,torsoW,torsoH,6);else ctx.rect(cx-torsoW/2,torsoTop,torsoW,torsoH);ctx.fill();
896 ctx.fillStyle=cc[1];ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx-8,torsoTop-2,16,10,4);else ctx.rect(cx-8,torsoTop-2,16,10);ctx.fill();
897 ctx.fillStyle=cc[0];ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx-torsoW/2-8,torsoTop+4,10,torsoH-16,4);else ctx.rect(cx-torsoW/2-8,torsoTop+4,10,torsoH-16);ctx.fill();
898 ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx+torsoW/2-2,torsoTop+4,10,torsoH-16,4);else ctx.rect(cx+torsoW/2-2,torsoTop+4,10,torsoH-16);ctx.fill();
899}else if(armorIdx===1){
900 ctx.fillStyle=cc[0];ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx-torsoW/2,torsoTop,torsoW,torsoH,6);else ctx.rect(cx-torsoW/2,torsoTop,torsoW,torsoH);ctx.fill();
901 ctx.fillStyle='rgba(200,60,60,0.3)';
902 ctx.fillRect(cx-torsoW/2+5,torsoTop,2,torsoH);ctx.fillRect(cx-torsoW/2+18,torsoTop,2,torsoH);ctx.fillRect(cx-torsoW/2+30,torsoTop,2,torsoH);
903 for(var r=0;r<5;r++){ctx.fillRect(cx-torsoW/2,torsoTop+r*10,torsoW,1.5);}
904 ctx.fillStyle='#222';ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx-4,torsoTop+2,8,6,3);else ctx.rect(cx-4,torsoTop+2,8,6);ctx.fill();
905 ctx.fillRect(cx-1,torsoTop+12,2,2);ctx.fillRect(cx-1,torsoTop+20,2,2);ctx.fillRect(cx-1,torsoTop+28,2,2);
906 ctx.fillStyle=cc[0];ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx-torsoW/2-8,torsoTop+4,10,torsoH-16,4);else ctx.rect(cx-torsoW/2-8,torsoTop+4,10,torsoH-16);ctx.fill();
907 ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx+torsoW/2-2,torsoTop+4,10,torsoH-16,4);else ctx.rect(cx+torsoW/2-2,torsoTop+4,10,torsoH-16);ctx.fill();
908}else if(armorIdx===2){
909 ctx.fillStyle=cc[0];ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx-torsoW/2,torsoTop,torsoW,torsoH,6);else ctx.rect(cx-torsoW/2,torsoTop,torsoW,torsoH);ctx.fill();
910 ctx.fillStyle=cc[1];ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx-torsoW/2-2,torsoTop-6,torsoW+4,14,6);else ctx.rect(cx-torsoW/2-2,torsoTop-6,torsoW+4,14);ctx.fill();
911 ctx.fillStyle=cc[2];ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx-12,torsoTop+18,24,10,4);else ctx.rect(cx-12,torsoTop+18,24,10);ctx.fill();
912 ctx.fillStyle=cc[0];ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx-torsoW/2-8,torsoTop+4,10,torsoH-16,4);else ctx.rect(cx-torsoW/2-8,torsoTop+4,10,torsoH-16);ctx.fill();
913 ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx+torsoW/2-2,torsoTop+4,10,torsoH-16,4);else ctx.rect(cx+torsoW/2-2,torsoTop+4,10,torsoH-16);ctx.fill();
914}else if(armorIdx===3){
915 ctx.fillStyle=cc[0];ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx-torsoW/2,torsoTop,torsoW,torsoH,6);else ctx.rect(cx-torsoW/2,torsoTop,torsoW,torsoH);ctx.fill();
916 ctx.fillStyle=cc[1];ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx-torsoW/2,torsoTop+4,torsoW/2-2,torsoH-10,4);else ctx.rect(cx-torsoW/2,torsoTop+4,torsoW/2-2,torsoH-10);ctx.fill();
917 ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx+2,torsoTop+4,torsoW/2-2,torsoH-10,4);else ctx.rect(cx+2,torsoTop+4,torsoW/2-2,torsoH-10);ctx.fill();
918 ctx.fillStyle=cc[2];ctx.beginPath();ctx.moveTo(cx-6,torsoTop+2);ctx.lineTo(cx+6,torsoTop+2);ctx.lineTo(cx,torsoTop+16);ctx.closePath();ctx.fill();
919 ctx.fillStyle='#c04040';ctx.fillRect(cx-2,torsoTop+6,4,24);
920 ctx.fillStyle=cc[1];ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx-torsoW/2-8,torsoTop+4,10,torsoH-20,4);else ctx.rect(cx-torsoW/2-8,torsoTop+4,10,torsoH-20);ctx.fill();
921 ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx+torsoW/2-2,torsoTop+4,10,torsoH-20,4);else ctx.rect(cx+torsoW/2-2,torsoTop+4,10,torsoH-20);ctx.fill();
922}else{
923 ctx.fillStyle=cc[0];ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx-torsoW/2,torsoTop,torsoW,torsoH,6);else ctx.rect(cx-torsoW/2,torsoTop,torsoW,torsoH);ctx.fill();
924 ctx.fillStyle=cc[1];ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx-16,torsoTop+4,32,12,4);else ctx.rect(cx-16,torsoTop+4,32,12);ctx.fill();
925 ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx-14,torsoTop+20,28,10,3);else ctx.rect(cx-14,torsoTop+20,28,10);ctx.fill();
926 ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx-12,torsoTop+34,24,10,3);else ctx.rect(cx-12,torsoTop+34,24,10);ctx.fill();
927 ctx.fillStyle=cc[2];ctx.fillRect(cx-18,torsoTop+4,2,12);ctx.fillRect(cx+16,torsoTop+4,2,12);
928 ctx.fillStyle='rgba(74,74,255,0.5)';ctx.fillRect(cx-1,torsoTop+18,2,6);ctx.fillRect(cx-1,torsoTop+32,2,6);
929 ctx.fillStyle=cc[0];ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx-torsoW/2-10,torsoTop-2,14,14,6);else ctx.rect(cx-torsoW/2-10,torsoTop-2,14,14);ctx.fill();
930 ctx.beginPath();if(ctx.roundRect)ctx.roundRect(cx+torsoW/2-4,torsoTop-2,14,14,6);else ctx.rect(cx+torsoW/2-4,torsoTop-2,14,14);ctx.fill();
931}
932// Arms
933ctx.fillStyle=skin;ctx.fillRect(cx-torsoW/2-14,torsoTop+8,6,18);ctx.fillRect(cx+torsoW/2+8,torsoTop+8,6,18);
934// Weapon glow
935if((G.weapon||0)>=1){
936 ctx.font='14px sans-serif';ctx.textAlign='center';ctx.textBaseline='middle';
937 ctx.fillStyle='rgba(88,166,255,0.15)';ctx.beginPath();ctx.arc(cx+torsoW/2+18,torsoTop+torsoH-8,14,0,Math.PI*2);ctx.fill();
938 ctx.fillStyle='#58a6ff';ctx.fillText('⚔️',cx+torsoW/2+18,torsoTop+torsoH-8);
939}
940// Armor name
941var armorNames=['','格子衫','连帽衫','西装','战甲'];
942if(armorIdx>0){ctx.font='9px sans-serif';ctx.textAlign='center';ctx.fillStyle='#8b949e';ctx.fillText(armorNames[armorIdx],cx,H-8);}
943}
944function renderShop(){
945var area=document.getElementById('statusArea');if(!area)return;
946var h='';
947h+='<div class="sys" style="margin-top:10px;">商店</div>'
948h+='<div style="text-align:center;font-size:15px;color:#f0c040;padding:8px;background:#1a1a0a;border:1px solid #30363d;border-radius:8px;margin:6px 0;">💰 金币: '+G.money+'</div>';
949
950for(var i=1;i<WEAPONS.length;i++){var w=WEAPONS[i];var owned=G.weapon>=i;
951h+='<div style="display:flex;justify-content:space-between;align-items:center;border:1px solid #30363d;border-radius:6px;padding:8px;margin:3px 0;background:'+(owned?'#0d3d0d':'#161b22')+';">';
952h+='<div><div style="font-weight:600;">'+w.n+'</div><div style="font-size:11px;color:#8b949e;">'+w.desc+'</div><div style="font-size:11px;color:#58a6ff;margin-top:2px;">⚔️ 攻击+'+w.atk+'</div></div><div style="display:flex;gap:6px;align-items:center;"><span style="color:#f0c040;">'+w.cost+'</span>';
953if(owned){h+='<span style="font-size:12px;color:#3fb950;">已装备</span>';}else{h+='<button class="choice-btn" onclick="buyWeapon('+i+')" style="padding:4px 10px;font-size:12px;">购买</button>';}
954h+='</div></div>';}
955for(var i=1;i<ARMORS.length;i++){var a=ARMORS[i];var owned=G.armor>=i;
956h+='<div style="display:flex;justify-content:space-between;align-items:center;border:1px solid #30363d;border-radius:6px;padding:8px;margin:3px 0;background:'+(owned?'#0d3d0d':'#161b22')+';">';
957h+='<div><div style="font-weight:600;">'+a.n+'</div><div style="font-size:11px;color:#8b949e;">'+a.desc+'</div><div style="font-size:11px;color:#3fb950;margin-top:2px;">🛡️ 防御+'+a.def+' · ❤️ 血量+'+a.hp+'</div></div><div style="display:flex;gap:6px;align-items:center;"><span style="color:#f0c040;">'+a.cost+'</span>';
958if(owned){h+='<span style="font-size:12px;color:#3fb950;">已装备</span>';}else{h+='<button class="choice-btn" onclick="buyArmor('+i+')" style="padding:4px 10px;font-size:12px;">购买</button>';}
959h+='</div></div>';}
960for(var i=1;i<ACCESSORIES.length;i++){var ac=ACCESSORIES[i];var owned=G.accessory>=i;
961h+='<div style="display:flex;justify-content:space-between;align-items:center;border:1px solid #30363d;border-radius:6px;padding:8px;margin:3px 0;background:'+(owned?'#0d3d0d':'#161b22')+';">';
962h+='<div><div style="font-weight:600;">'+ac.n+'</div><div style="font-size:11px;color:#8b949e;">'+ac.desc+'</div><div style="font-size:11px;color:#58a6ff;margin-top:2px;">💧 灵力上限+'+ac.mp+'</div></div><div style="display:flex;gap:6px;align-items:center;"><span style="color:#f0c040;">'+ac.cost+'</span>';
963if(owned){h+='<span style="font-size:12px;color:#3fb950;">已装备</span>';}else{h+='<button class="choice-btn" onclick="buyAccessory('+i+')" style="padding:4px 10px;font-size:12px;">购买</button>';}
964h+='</div></div>';}
965h+='<div style="font-size:12px;color:#8b949e;margin:6px 0;">消耗品</div>';
966SHOP_ITEMS.forEach(function(si){
967h+='<div style="display:flex;justify-content:space-between;align-items:center;border:1px solid #30363d;border-radius:6px;padding:8px;margin:3px 0;background:#161b22;"><div><div style="font-weight:600;">'+si.n+'</div><div style="font-size:11px;color:#8b949e;">'+si.desc+'</div></div><div style="display:flex;gap:6px;align-items:center;"><span style="color:#f0c040;">'+si.cost+'</span><button class="choice-btn" onclick="buyItem(\''+si.id+'\')" style="padding:4px 10px;font-size:12px;">购买</button></div></div>';});
968
969area.innerHTML=h;
970}
971function showSkillTree(){
972 var html='<div class="sys">📚 技能树</div><div style="margin:10px 0;font-size:13px;color:#8b949e;">消耗技能点(SP)学习新技能</div>';
973 var learned=G.skills||[];
974 SKILL_TREE.forEach(function(sk){
975 var has=learned.indexOf(sk.id)>=0;
976 var can=G.level>=sk.minLv;
977 html+='<div style="border:1px solid #30363d;border-radius:6px;padding:10px;margin:6px 0;background:'+(has?'#0d3d0d':(can?'#1a1a2e':'#161b22'))+';">';
978 html+='<div style="display:flex;justify-content:space-between;"><strong>'+sk.n+'</strong><span>'+(has?'✅ 已学':'技能点:'+sk.costSP+' 等级:'+sk.minLv)+'</span></div>';
979 html+='<div style="font-size:12px;color:#8b949e;margin:4px 0;">'+sk.desc+'</div>';
980 if(!has&&can){html+='<button class="choice-btn" onclick="learnSkill(\''+sk.id+'\')" style="padding:4px 12px;font-size:12px;">🔓 学习</button>';}
981 html+='</div>';
982 });
983 html+='<button class="choice-btn" onclick="document.getElementById(\'skillPanel\').style.display=\'none\';" style="width:100%;margin-top:8px;">关闭</button>';
984 var sp=document.getElementById('skillPanel')||function(){var d=document.createElement('div');d.id='skillPanel';d.style.cssText='position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:320px;max-height:80vh;overflow-y:auto;background:#0d1117;border:1px solid #30363d;border-radius:8px;padding:16px;z-index:1000;';document.body.appendChild(d);return d;}();
985 sp.style.display='block';sp.innerHTML=html;
986}
987function learnSkill(sid){
988 var sk=SKILL_TREE.find(function(s){return s.id===sid;});if(!sk)return;
989 if(G.level<sk.minLv){alert('等级不足!需要 Lv.'+sk.minLv);return;}
990 if(G.sp<sk.costSP){alert('技能点不足!需要 '+sk.costSP+' SP');return;}
991 G.sp-=sk.costSP;if(!G.skills)G.skills=[];G.skills.push(sid);
992 updateUI();showSkillTree();addScene('<p class="good">🌟 学会技能:「'+sk.n+'」</p>');
993}
994var _trainingCD=0;
995function exploreLog(msg){var el=document.getElementById('exploreLog');if(!el)return;var d=document.createElement('div');d.innerHTML=processDialogue(msg);el.appendChild(d);el.scrollTop=el.scrollHeight;}
996function showPartimeOptions(){
997 PARTIME_TITLES.forEach(function(pt){
998 var can=(G.level||0)>=(pt.minLv||0);
999 var gold=pt.cost>0?'消耗'+pt.cost+'灵力':'无消耗';
1000 exploreLog('<div style="border:1px solid '+(can?'#f0c040':'#30363d')+';border-radius:6px;padding:8px;margin:4px 0;background:'+(can?'#1a1a0a':'#111')+';"><div style="display:flex;justify-content:space-between;align-items:center;"><div><div style="font-weight:600;">'+pt.n+'</div><div style="font-size:11px;color:#8b949e;">'+pt.desc+'</div></div>'+(can?'<button class="choice-btn" onclick="doPartime(\''+pt.id+'\')" style="padding:4px 10px;font-size:12px;border-color:#f0c040;">💼 开工</button>':'<span style="color:#484f58;font-size:11px;">需Lv.'+pt.minLv+'</span>')+'</div></div>');
1001 });
1002 exploreLog('<div style="font-size:11px;color:#8b949e;margin-top:4px;">💡 打工每2章可接1次,推进剧情可刷新</div>');
1003}
1004function showDungeonList(){
1005 if(typeof window.DUNGEON_SCENES==='undefined'){exploreLog('<div class="bad">❌ 副本数据未加载</div>');return;}
1006 var ds=window.DUNGEON_SCENES,ch=S.chapter||0,el=document.getElementById('dungeonList');
1007 var avail=ds.filter(function(d){if(d.minChapter>ch)return false;if(d.category==='情感互动'&&ch<2)return false;return true;});
1008 var todo=avail.filter(function(d){return !(S.dungeonDone&&S.dungeonDone[d.id]);});
1009 var doneList=avail.filter(function(d){return S.dungeonDone&&S.dungeonDone[d.id];});
1010 if(avail.length===0){exploreLog('<div style="color:#8b949e;">📭 暂无可用副本,推进主线剧情解锁</div>');return;}
1011 var h='<div style="border:1px solid #30363d;border-radius:8px;padding:8px;margin:4px 0;background:#161b22;">';
1012 h+='<div style="font-size:12px;color:#8b949e;margin-bottom:6px;">🎯 未完成 ('+todo.length+') · 已完成 ('+doneList.length+')</div>';
1013 // Incomplete dungeons
1014 todo.forEach(function(d){
1015 h+='<div style="display:flex;justify-content:space-between;align-items:center;padding:6px 8px;margin:3px 0;border:1px solid #30363d;border-radius:6px;background:#1a1a2e;cursor:pointer;" onclick="playDungeon(\''+d.id+'\')">';
1016 h+='<div><div style="font-weight:600;font-size:13px;">📖 '+d.title+'</div><div style="font-size:11px;color:#8b949e;">'+(d.category||'副本')+' · 第'+d.minChapter+'章解锁</div></div>';
1017 h+='<span style="color:#c084fc;font-size:11px;">开始 →</span>';
1018 h+='</div>';
1019 });
1020 // Completed dungeons (collapsible)
1021 if(doneList.length>0){
1022 var collapsed=S._dungeonCollapsed!==false;
1023 h+='<div style="margin-top:6px;border-top:1px solid #30363d;padding-top:6px;">';
1024 h+='<div style="display:flex;justify-content:space-between;align-items:center;cursor:pointer;padding:4px 0;" onclick="S._dungeonCollapsed=!S._dungeonCollapsed;showDungeonList();">';
1025 h+='<span style="font-size:12px;color:#8b949e;">✅ 已完成 ('+doneList.length+')</span>';
1026 h+='<span style="font-size:11px;color:#8b949e;">'+(collapsed?'展开 ▼':'收起 ▲')+'</span></div>';
1027 if(!collapsed){
1028 doneList.forEach(function(d){
1029 h+='<div style="display:flex;align-items:center;padding:4px 8px;margin:2px 0;font-size:12px;color:#8b949e;">✅ '+d.title+'</div>';
1030 });
1031 }
1032 h+='</div>';
1033 }
1034 h+='</div>';
1035 el.innerHTML=h;
1036 exploreLog('<div class="good">📋 副本列表已刷新</div>');
1037}
1038function playDungeon(id){
1039 var ds=(typeof DUNGEON_SCENES!=='undefined'?DUNGEON_SCENES:window.DUNGEON_SCENES).find(function(d){return d.id===id;});
1040 if(!ds){exploreLog('<div class="bad">❌ 副本不存在</div>');return;}
1041 S.currentDungeon=id;
1042 // Clear explore log and start fresh
1043 var el=document.getElementById('exploreLog');
1044 if(el)el.innerHTML='';
1045 showDungeonScene({text:'<div style="text-align:center;font-size:15px;font-weight:600;color:#c084fc;margin-bottom:10px;">📖 '+(ds.title||'副本')+'</div><div style="text-align:center;font-size:12px;color:#8b949e;margin-bottom:12px;">'+(ds.category||'')+'</div>'});
1046 // Auto illustration
1047 var theme=getAutoTheme(ds.title||"副本");
1048 var cv=drawAutoCanvas(theme,320,90);
1049 var el=document.getElementById("exploreLog");
1050 if(el){var w=document.createElement("div");w.appendChild(cv);el.appendChild(w);el.scrollTop=el.scrollHeight;}
1051 if(ds.scene){showDungeonScene(ds.scene);}
1052 else{var firstKey=Object.keys(ds.scenes)[0];showDungeonScene(ds.scenes[firstKey]);}
1053 // Scroll to top of explore log
1054 if(el)setTimeout(function(){el.scrollTop=0;el.scrollIntoView({behavior:'smooth',block:'start'});},50);
1055}
1056function showDungeonScene(sc){
1057 exploreLog('<div style="border-top:1px solid #30363d;margin:4px 0;"></div>');
1058 exploreLog('<div class="scene-text">'+sc.text+'</div>');
1059 if(sc.choices&&sc.choices.length>0){
1060 var btns='<div style="display:flex;flex-direction:column;gap:4px;margin-top:6px;">';
1061 sc.choices.forEach(function(ch){
1062 btns+='<button class="choice-btn" onclick="dungeonChoice(\''+ch.next+'\',\''+encodeURIComponent(JSON.stringify(ch.fx||{}))+'\')" style="text-align:left;font-size:12px;padding:6px 10px;">'+ch.t+'</button>';
1063 });
1064 btns+='</div>';
1065 exploreLog(btns);
1066 }else{exploreLog('<div class="sys">—— 完 ——</div>');}
1067}
1068function dungeonChoice(next,fxStr){
1069 var fx=JSON.parse(decodeURIComponent(fxStr));
1070 if(fx.exp){gainExp(fx.exp);}
1071 if(fx.money){G.money=(G.money||0)+fx.money;}
1072 if(fx.hp&&G.hp){G.hp=Math.min(G.maxHp,G.hp+fx.hp);}
1073 if(fx.mp&&G.mp){G.mp=Math.min(G.maxMp,G.mp+fx.mp);}
1074 if(fx.affection){S.affection=(S.affection||0)+fx.affection;S.affection=Math.min(100,S.affection);}
1075 if(next==='__DUNGEON_END__'||next==='CHAPTER_COMPLETE'){
1076 completeDungeon();
1077 return;
1078 }
1079 var ds=(typeof DUNGEON_SCENES!=='undefined'?DUNGEON_SCENES:window.DUNGEON_SCENES).find(function(d){return d.id===S.currentDungeon;});
1080 if(!ds){exploreLog('<div class="bad">❌ 数据异常</div>');return;}
1081 if(ds.ends&&ds.ends[next]){showDungeonScene(ds.ends[next]);}
1082 else if(ds.scenes&&ds.scenes[next]){showDungeonScene(ds.scenes[next]);}
1083 else{exploreLog('<div class="bad">❌ 场景 '+next+' 不存在</div>');}
1084 updateUI();saveGame();
1085}
1086function completeDungeon(){
1087 var id=S.currentDungeon;if(!S.dungeonDone)S.dungeonDone={};
1088 S.dungeonDone[id]=true;S.currentDungeon=null;
1089 var ch=0,ds=DUNGEON_SCENES&&DUNGEON_SCENES.find(function(d){return d.id===id;});if(ds)ch=ds.minChapter||0;
1090 var baseGold=5+ch*3;G.money=(G.money||0)+baseGold;
1091 exploreLog('<div class="good" style="margin-top:8px;">✅ 副本完成!'+baseGold+'金币</div>');
1092 var r=Math.random();
1093 if(r<0.45){
1094 var extra=5+Math.floor(Math.random()*11);G.money+=extra;
1095 exploreLog('<div style="color:#d29922;">📦 普通宝箱:金币+'+extra+'</div>');
1096 }else if(r<0.75){
1097 var exp=20+Math.floor(Math.random()*31);gainExp(exp);
1098 exploreLog('<div style="color:#58a6ff;">📦 精致宝箱:经验+'+exp+'</div>');
1099 }else if(r<0.95){
1100 var si=SHOP_ITEMS[Math.floor(Math.random()*SHOP_ITEMS.length)];
1101 if(!G.inventory)G.inventory=[];G.inventory.push({id:si.id,n:si.n});
1102 exploreLog('<div style="color:#c084fc;">📦 稀有宝箱:获得「'+si.n+'」</div>');
1103 }else{
1104 var bigGold=50+Math.floor(Math.random()*51);G.money+=bigGold;gainExp(50);
1105 var si=SHOP_ITEMS[Math.floor(Math.random()*SHOP_ITEMS.length)];
1106 if(!G.inventory)G.inventory=[];G.inventory.push({id:si.id,n:si.n});
1107 exploreLog('<div style="color:#ff69b4;font-weight:600;">👑 传说宝箱:金币+'+bigGold+'、经验+50、获得「'+si.n+'」</div>');
1108 }
1109 if(!S.fateFragments)S.fateFragments={};
1110 var catMap={'项目跟进':'work','情感互动':'love','奇遇':'daily','技术挑战':'tech','神秘事件':'mystery'};
1111 var cat=catMap[ds&&ds.category]||'other';S.fateFragments[cat]=(S.fateFragments[cat]||0)+1;
1112 var catNames={work:'奋斗',love:'真心',daily:'奇遇',tech:'技术',mystery:'神秘'};
1113 exploreLog('<div style="color:#c084fc;font-size:12px;margin-top:4px;">💠 获得「'+catNames[cat]+'硎片」('+(S.fateFragments[cat])+'/'+(cat==='tech'||cat==='mystery'?5:10)+')</div>');
1114 exploreLog('<div class="sys" style="margin-top:4px;">💾 已保存</div>');
1115 updateUI();saveGame();showDungeonList();
1116}
1117function startTraining(){
1118 if(Date.now()<_trainingCD){exploreLog('<div style="color:#8b949e;">⏳ 修炼冷却中...</div>');return;}
1119 _trainingCD=Date.now()+3000;
1120 // Training illustration
1121 var themeTrain=getAutoTheme("修炼静心");
1122 var cvTrain=drawAutoCanvas(themeTrain,280,80);
1123 var elTrain=document.getElementById("exploreLog");
1124 if(elTrain){var w=document.createElement("div");w.appendChild(cvTrain);elTrain.appendChild(w);elTrain.scrollTop=elTrain.scrollHeight;}
1125 var events=[
1126 {t:'<p>你盘腿坐好,闭上眼睛。脑海中浮现出今天写的代码——一个循环里藏着一个小小的逻辑漏洞。你仔细想了想,找到了它。</p><p class="good">顿悟!Bug不攻自破。</p>',expMin:15,expMax:35,hp:-5},
1127 {t:'<p>你打开了一本积灰的技术书——<em>《代码整洁之道》</em>。翻到折角的那一页,上面写着:「好的代码不需要注释来解释自己。」你盯着这一行看了很久,若有所悟。</p>',expMin:10,expMax:25},
1128 {t:'<p>你在工位前坐定,深呼吸。窗外的夕阳照在键盘上,键帽反射着金色的光。你忽然想起刚入职时啥也不会的自己——那些熬夜debug的夜晚、那些终于跑通测试时的狂喜。</p><p class="good">初心回来了。</p>',expMin:8,expMax:18},
1129 {t:'<p>你决定静坐冥想。脑子里全是今天的 deadline、产品经理的需求变更、测试姐姐发来的 Bug 列表……你深呼吸三次,把杂念一只一只按下去。终于,世界安静了。</p>',expMin:5,expMax:15},
1130 {t:'<p>你尝试理解一个开源项目的核心架构。代码像瀑布一样在眼前流过。你花了一炷香的时间,终于看懂了那个著名的状态机实现。</p><p class="good">功力大涨!</p>',expMin:18,expMax:40,hp:-8},
1131 {t:'<p>你走到窗边,看着远处城市的灯火。北京很大,你很渺小。但你知道——这座城市里的每一个灯火,都代表着有人在写他们自己的故事。而你也在写自己的故事,一行代码一行代码地写。</p>',expMin:6,expMax:12},
1132 {t:'<p>你打开一个空白编辑器,试图写一段全新的算法实现。写了三行,删了五行。又写了七行,全部高亮、按下 Delete。你盯着空白的屏幕,忽然笑了——</p><p class="info">「最好的代码,有时候是还没写出来的那段。」</p>',expMin:10,expMax:22},
1133 {t:'<p>你在网上看到一篇技术博客,讲解了一个你一直没搞懂的性能优化技巧。你读完之后,拍了一下大腿——这么简单!</p>',expMin:12,expMax:28},
1134 {t:'<p>你泡了一杯茶,靠在椅背上闭目养神。茶叶在杯子里慢慢舒展,像一段段代码在内存中铺开。你问自己:<span class="info">「如果今天是我写代码的最后一天,我会写什么?」</span></p>',expMin:5,expMax:20},
1135 {t:'<p>你在技术论坛上回答了一个新手的问题。你写得很耐心,从原理讲到实践。对方回了一句:「谢谢大佬,讲得太清楚了!」</p><p>你嘴角微微上扬。</p>',expMin:8,expMax:22,hp:5},
1136 {t:'<p>你在键盘前坐了很久,一个字都没写。你想起老张说过的话——「码农也是手艺人。手艺人的功夫,不在手上,在心里。」</p><p>你闭上眼睛。让心先静下来。</p>',expMin:4,expMax:12},
1137 {t:'<p>你试着用刚学的设计模式重构一段旧代码。拆了一半发现——原来的代码虽然丑但在这场景下跑得快三倍。</p><p class="bad">你默默改了回去,加了一行注释:「有时候,丑但是快。」</p>',expMin:10,expMax:20,hp:-3},
1138 {t:'<p>你在命令行里敲下 <code>git log --oneline</code>,看着自己这一年的commit记录——从「fix typo」到「重构核心模块」到「项目上线v2」。</p><p class="good">这一年,你没有白过。</p>',expMin:12,expMax:30},
1139 {t:'<p>你躺在地板上,看着天花板。天花板上有水渍,形状像一只Bug。你想:如果Bug都长在天花板上就好了——看得见,摸得到,修起来也简单。</p>',expMin:5,expMax:15},
1140 {t:'<p>你遇到一个特别难缠的Bug——线上偶发,本地复现不了。你站起来走了两圈。回来的时候你忽然想到——是不是缓存的问题?你加了一行清缓存的代码,Bug消失了。</p><p class="good">灵感就在你站起来的那两步里。</p>',expMin:15,expMax:35},
1141 {t:'<p>你翻到自己一年前写的代码。一段switch-case写了二百多行。你看了半天才看懂自己当时在想什么。你在文件顶部加了一行注释:<span class="info">「写给未来的自己:别笑,你当时就是这么写的。」</span></p>',expMin:8,expMax:18},
1142 {t:'<p>你在深夜的办公室里独自写代码。空调关了,只有显示器的风扇嗡嗡作响。你戴上耳机,循环一首老歌。世界缩小到只有屏幕这么大。你写得很顺,一行接一行。</p>',expMin:10,expMax:25},
1143 {t:'<p>你尝试说服自己今天不写代码了。你打开视频网站,刷了五分钟就关掉了——刷视频比写代码还累。你默默打开了IDE。</p>',expMin:3,expMax:10,hp:3},
1144 {t:'<p>你正写得入神,突然屏幕一黑——停电了。整层楼都笑了。你拿出手机,借着屏幕的光写了三行伪代码在备忘录里。电来了之后,你五分钟就把那三行写成了完整的模块。</p>',expMin:10,expMax:30},
1145 {t:'<p>你收到了一个pull request的review意见——指出了你代码里一个潜在的并发问题。你改完之后,给对方回了一句「学到了」。</p>',expMin:10,expMax:20},
1146 {t:'<p>你试了一个新的AI编程助手。它帮你生成了接口文档和测试。效率确实提升了,但你有点不安。你关掉AI,自己手写了最后一个函数。有些东西,还是想自己来。</p>',expMin:5,expMax:15},
1147 {t:'<p>你参加了一个技术分享会。台上讲了分布式事务。你记了满满三页笔记。散会后你追到电梯口问了一个问题,对方停下来认真回答了。</p><p class="good">这就是技术圈的可爱之处——大家都愿意分享。</p>',expMin:14,expMax:30,hp:5},
1148 {t:'<p>你做了一个艰难的决定:把一个跑了三年的旧服务下线,替换成自己写的新服务。上线时手在微微发抖。绿灯亮起的那一刻,你长长地呼出一口气。又一个里程碑过去了。</p>',expMin:20,expMax:45,hp:-10},
1149 {t:'<p>你深夜打车回家。司机问你做什么工作的。你说写代码的。他说:「程序员啊,厉害。」你说:「也没什么,就是每天跟Bug打交道。」他笑了笑:「那跟医生差不多嘛。」你觉得这个比喻还挺贴切。</p>',expMin:5,expMax:15},
1150 // 毛选修炼
1151 {t:'<p>你翻开《毛泽东选集》,读到那句:<span class="info">「星星之火,可以燎原」</span>。你盯着这八个字看了很久。你想到自己写的每一行代码——它微小、脆弱、不值一提。但也许有一天,它会成为某个系统的核心。</p><p class="good">你默默地又写了几行。</p>',expMin:12,expMax:28,hp:5},
1152 {t:'<p>你读到了《实践论》中的一句话:<span class="info">「你要知道梨子的滋味,你就得变革梨子,亲口吃一吃」</span>。你心想:这不就是"别纸上谈兵,先动手试试"吗?你打开IDE,把纠结了一天的方案直接写了一版出来试试。</p>',expMin:10,expMax:22},
1153 {t:'<p>毛选里写道:<span class="info">「不打无准备之仗」</span>。你反思了一下自己每次上线前慌慌张张的样子。你打开笔记,开始写一份上线检查清单。以后每次发布,挨个打勾。</p><p class="good">经验 + 行动力。</p>',expMin:8,expMax:20,hp:-3},
1154 {t:'<p>你读到《矛盾论》,上面说:<span class="info">「矛盾存在于一切事物的发展过程中」</span>。你想了想自己项目里的Bug——旧的修完新的又来,似乎永远改不完。你笑了。不是Bug太多,是项目还在发展。</p>',expMin:6,expMax:18},
1155 {t:'<p>你翻开毛选,一段话映入眼帘:<span class="info">「在战略上要藐视敌人,在战术上要重视敌人」</span>。你把这句话贴在了显示器边框上。面对复杂的系统重构——气势上不能输,细节上一个都不能漏。</p>',expMin:14,expMax:30,hp:-5},
1156 {t:'<p>你读到《愚公移山》,陷入了沉思。<span class="info">「每天挖山不止」</span>——你想到自己正在维护的祖传代码。十几万行,没有文档,注释全是拼音。但你每天都在改,每天都在推进。总有一天,这座山会被你挖平的。</p>',expMin:8,expMax:22,hp:8},
1157 {t:'<p>毛选里有一句你特别喜欢:<span class="info">「世界上怕就怕"认真"二字」</span>。你盯着屏幕上一个顽固的Bug,深吸一口气。你一行一行地读代码,一个变量一个变量地跟踪。四十分钟后。你找到了。是一个多线程场景下变量被意外修改了。</p><p class="good">认真,真的有用。</p>',expMin:15,expMax:35,hp:-8},
1158 {t:'<p>你在《毛选》里读到:<span class="info">「调查就像"十月怀胎",解决问题就像"一朝分娩"」</span>。你反思自己之前遇到问题就急着改代码的习惯。以后出了问题,先调查清楚,再动手修。</p>',expMin:8,expMax:18,hp:3},
1159 {t:'<p>你合上《毛选》,回味那句:<span class="info">「好好学习,天天向上」</span>。六个字,小学生都认识。但真正做到的没几个。你打开了一本技术书,决定今天不写新代码,专门学一个新东西。</p>',expMin:10,expMax:25,hp:5},
1160 {t:'<p>你读到《论持久战》,里面讲三个阶段:防御、相持、反攻。你想到自己刚入职的时候什么都不懂(防御阶段),现在能独立干活了(相持阶段),也许再过一年就能带项目了(反攻阶段)。</p><p class="good">路还长,但不慌了。</p>',expMin:10,expMax:24},
1161 {t:'<p>你翻开毛选,看到那句:<span class="info">「一万年太久,只争朝夕」</span>。你想了想那些一直想做但一直说"下次再说"的事。你打开笔记,写下了这个月要完成的三个目标。</p>',expMin:6,expMax:16,hp:5},
1162 {t:'<p>你读到《反对本本主义》里的一句话:<span class="info">「没有调查,就没有发言权」</span>。你想起上次评审会上跟产品经理争论一个需求——你当时说的"做不了"其实没经过充分评估。下次。先调查,再发言。</p>',expMin:8,expMax:18},
1163 // 更多毛选修炼
1164 {t:'<p>你翻到《毛选》里那句:<span class="info">「中国人民有志气,有能力,一定要在不远的将来,赶上和超过世界先进水平」</span>。你看着自己手头的项目,虽然现在还有一堆烂摊子,但你相信——只要持续迭代,总有一天能追上行业最佳实践。</p>',expMin:10,expMax:22,hp:3},
1165 {t:'<p>你读到:<span class="info">「读书是学习,使用也是学习,而且是更重要的学习」</span>。你意识到光看技术文章不写代码,就像看菜谱不下厨。你关掉了浏览器里收藏夹里的第十篇没看完的文章,打开IDE开始写。</p>',expMin:8,expMax:20},
1166 {t:'<p>毛选里有一句让你停了好久:<span class="info">「错误和挫折教训了我们,使我们比较地聪明起来了」</span>。你想起上个月线上事故的那个不眠夜。虽然当时很难受,但现在再看——那次事故教会你的东西,比读十本书都多。</p>',expMin:10,expMax:25,hp:-5},
1167 {t:'<p>你读到:<span class="info">「武器是战争的重要的因素,但不是决定的因素,决定的因素是人不是物」</span>。你看着自己的旧电脑想:工具固然重要,但写出好代码的,永远是坐在屏幕前的那个人。你关掉了购物网站上的MacBook Pro页面。</p>',expMin:6,expMax:16},
1168 {t:'<p>《毛选》里讲:<span class="info">「我们需要的是热烈而镇定的情绪,紧张而有秩序的工作」</span>。你深呼吸。把面前的三块任务排了一个优先级。先做最重要的,再做最紧急的。不慌不忙,一条一条来。</p>',expMin:8,expMax:18,hp:5},
1169 {t:'<p>你读到:<span class="info">「往往有这种情形,有利的情况和主动的恢复,产生于「再坚持一下」的努力之中」</span>。你正在调试一个折磨了你三天的Bug。你咬了咬牙。再坚持一下。又过了一个小时,你终于找到了root cause。</p><p class="good">再坚持一下——毛爷爷诚不欺我。</p>',expMin:15,expMax:35,hp:-10},
1170 {t:'<p>你翻开《中国革命战争的战略问题》,读到:<span class="info">「重要的问题在善于学习」</span>。你反思自己:每天忙忙碌碌写代码,但真正"学"到的东西有多少?你决定每天留出半小时专门学习,不做任务、不写需求、只看书。</p>',expMin:10,expMax:22},
1171 {t:'<p>你读到:<span class="info">「你对于那个问题不能解决么?那末,你就去调查那个问题的现状和它的历史吧!」</span>。你遇到一个诡异的数据不一致问题。你没有急着改代码,而是先翻了翻Git历史——发现三个月前的一次重构里改了字段映射规则。</p><p class="good">问题解决了。调查比瞎改有用一百倍。</p>',expMin:12,expMax:28,hp:-5},
1172 {t:'<p>你读到:<span class="info">「感觉到了的东西,我们不能立刻理解它,只有理解了的东西才更深刻地感觉它」</span>。你以前一直照着文档调API,调通了就行。今天你决定花时间看看底层原理。看完之后——原来如此。以后再也不怕这个API出问题了。</p>',expMin:10,expMax:24},
1173 {t:'<p>毛选里说:<span class="info">「什么叫工作,工作就是斗争」</span>。你看着今天的任务列表——三个需求、两个Bug、一个评审会。你挽起袖子。不就是干吗。</p>',expMin:5,expMax:15,hp:-3},
1174 {t:'<p>你读到:<span class="info">「白求恩同志毫不利己专门利人的精神,表现在他对工作的极端的负责任」</span>。你想想自己写的代码——变量命名随意、异常处理偷懒、边界情况没想全。你打开今天刚提交的PR,又仔细过了一遍。发现了一个漏掉的null判断。</p><p class="good">对工作极端负责——你默默记住了这个词。</p>',expMin:8,expMax:20,hp:-5},
1175 {t:'<p>你读到:<span class="info">「我们必须学会全面地看问题,不但要看到事物的正面,也要看到它的反面」</span>。产品经理又来跟你吵需求了。这次你没有直接说"不行",而是试着站在他的角度想了一下。你发现他说的也有道理。你们达成了一版双方都能接受的方案。</p>',expMin:8,expMax:18,hp:8},
1176 {t:'<p>《毛选》里有一句话:<span class="info">「往往在敌人十分起劲自己十分困难的时候,正是敌人开始不利自己开始有利的时候」</span>。你在一个特别难的项目上已经熬了两个月了。你觉得快撑不住了。但你看了看近期的进展——最难的模块已经验收通过了。</p><p class="good">曙光就在前面。</p>',expMin:10,expMax:24},
1177 {t:'<p>你读到:<span class="info">「感觉到了的东西我们不能立刻理解它」</span>。你想起昨天看到一个报错日志,你没仔细看就贴到群里问人了。你打开日志重新读了一遍。原来括号里写了原因——你根本就没看完。</p><p class="bad">以后看完日志再问问题。</p>',expMin:5,expMax:12},
1178 {t:'<p>你读到《关于纠正党内的错误思想》,里面讲:<span class="info">「单纯的军事观点」</span>——你想到自己身上也有类似的问题:只关注技术不关注业务。你决定下次跟产品经理开会的时候,认真听听用户的真实场景。</p>',expMin:6,expMax:16},
1179 {t:'<p>你读到一段话:<span class="info">「群众是真正的英雄,而我们自己则往往是幼稚可笑的」</span>。你想想自己刚入职时觉得自己什么都懂的样子——现在想起来脸都红。你给组里的新人倒了一杯水:<span class="good">「有什么不懂的随时问我。」</span></p>',expMin:8,expMax:18,hp:5},
1180 {t:'<p>你读到:<span class="info">「谅解、支援和友谊,比什么都重要」</span>。你跟隔壁组因为接口联调的事吵了一架。你冷静下来,给对方发了一条消息:<span class="good">「刚才语气不好,别往心里去。接口的事我们拉个会一起对齐一下。」</span>对方秒回了一个OK。</p>',expMin:6,expMax:14,hp:8},
1181 {t:'<p>你读到《毛选》里关于调查研究的论述:<span class="info">「不做调查没有发言权,不做正确的调查同样没有发言权」</span>。你心想:不仅要做事,还要用对的方法做事。你把自己平时排查Bug的步骤写成了SOP,以后按流程来。</p>',expMin:8,expMax:20},
1182 ];
1183 var evt=events[Math.floor(Math.random()*events.length)];
1184 var exp=evt.expMin+Math.floor(Math.random()*(evt.expMax-evt.expMin+1));
1185 if(evt.hp){
1186 if(evt.hp>0)G.hp=Math.min(G.maxHp||100,G.hp+evt.hp);
1187 else G.hp=Math.max(1,G.hp+evt.hp);
1188 }
1189 gainExp(exp);updateUI();saveGame();
1190 exploreLog('<div style="color:#c9d1d9;border-left:3px solid #7c3aed;padding-left:10px;margin-top:6px;">🧘 修炼感悟:'+evt.t+'</div><div class="good" style="margin-top:6px;">✨ 经验 +'+exp+'</div>');
1191 // Random fragment from cultivation (15%)
1192 if(Math.random()<0.15){
1193 if(!S.fateFragments)S.fateFragments={};
1194 var fragTypes=['work','love','daily','tech','mystery'];var fType=fragTypes[Math.floor(Math.random()*fragTypes.length)];
1195 S.fateFragments[fType]=(S.fateFragments[fType]||0)+1;
1196 var fNames={work:'奋斗',love:'真心',daily:'奇遇',tech:'技术',mystery:'神秘'};
1197 exploreLog('<div style="color:#c084fc;font-size:11px;margin-top:2px;">💠 修炼中获得「'+fNames[fType]+'碎片」('+(S.fateFragments[fType])+'/'+(fType==='tech'||fType==='mystery'?5:10)+')</div>');
1198 }
1199}
1200function doExplore(){
1201 if(!S.fateFragments)S.fateFragments={};
1202 var evt=EXPLORE_EVENTS[Math.floor(Math.random()*EXPLORE_EVENTS.length)];
1203 var txt=evt.t;
1204 if(evt.hp){var oh=G.hp;G.hp=Math.max(1,Math.min(G.maxHp||100,G.hp+evt.hp));txt+='<div>'+((evt.hp>0)?'<span class="good">❤️ +'+evt.hp+'</span>':'<span class="bad">❤️ '+evt.hp+'</span>')+'</div>';}
1205 if(evt.mp){G.mp=Math.min(G.maxMp||50,G.mp+evt.mp);txt+='<div><span class="info">💧 +'+evt.mp+'</span></div>';}
1206 if(evt.exp){gainExp(evt.exp);txt+='<div><span class="good">⭐ +'+evt.exp+'经验</span></div>';}
1207 if(evt.atk){G.atk+=evt.atk;txt+='<div><span class="good">⚔️ 攻击+'+evt.atk+'</span></div>';}
1208 if(Math.random()<0.20){
1209 var fTypes=['work','love','daily','tech','mystery'];var ft=fTypes[Math.floor(Math.random()*fTypes.length)];
1210 S.fateFragments[ft]=(S.fateFragments[ft]||0)+1;
1211 var fn={work:'奋斗',love:'真心',daily:'奇遇',tech:'技术',mystery:'神秘'};
1212 txt+='<div style="color:#c084fc;font-size:11px;">💠 获得「'+fn[ft]+'碎片」('+S.fateFragments[ft]+'/'+(ft==='tech'||ft==='mystery'?5:10)+')</div>';
1213
1214 // Explore illustration
1215 var themeExplore=getAutoTheme(evt.t);
1216 var cvExplore=drawAutoCanvas(themeExplore,280,80);
1217 var elExplore=document.getElementById("exploreLog");
1218 if(elExplore){var w=document.createElement("div");w.appendChild(cvExplore);elExplore.appendChild(w);elExplore.scrollTop=elExplore.scrollHeight;}
1219 }
1220 exploreLog('<div style="color:#c9d1d9;border-left:3px solid #58a6ff;padding-left:10px;margin:6px 0;">🗺️ 探索发现:'+txt+'</div>');
1221 updateUI();saveGame();
1222}
1223function startBounty(){
1224 if(G.mp<10){exploreLog('<div class="bad">❌ 灵力不足,无法战斗</div>');return;}
1225 G.mp=Math.max(0,G.mp-10);
1226 var diff=getDifficulty();
1227 var m=getRandomMonster(diff);
1228 S.fightState={hp:m.hp,atk:m.atk,
1229 d:'🐛 野生「'+m.n+'」出现了!',cb:function(){
1230 exploreLog('<div class="good">🐛 打败「'+m.n+'」!金币+'+Math.floor(5+Math.random()*11)+'</div>');
1231 updateUI();saveGame();
1232 },enemyIdx:-1,m:null,initHp:0,initAtk:0,diff:diff};
1233 S.fightState.monsterName=m.n;
1234 document.getElementById('fightOverlay').style.display='flex';renderFight();
1235}
1236function startDungeon(){
1237 if(S.dungeonStage===undefined)S.dungeonStage=0;
1238 var stages=[{n:'低级副本',hp:25,atk:4},{n:'中级副本',hp:40,atk:7},{n:'高级副本',hp:60,atk:10}];
1239 var st=stages[S.dungeonStage]||stages[stages.length-1];
1240 S.fightState={hp:st.hp,atk:st.atk,d:'🏰 '+st.n+' BOSS出现!',
1241 cb:function(){
1242 if(S.dungeonStage<stages.length-1){S.dungeonStage++;var gold=10+S.dungeonStage*15;G.money=(G.money||0)+gold;exploreLog('<div class="good">🏰 闯过'+st.n+'!金币+'+gold+',进入下一层!</div>');}
1243 else{S.dungeonStage=0;G.money=(G.money||0)+100;exploreLog('<div class="good">🏆 副本通关!获得100金币奖励!</div>');}
1244 updateUI();saveGame();
1245 },enemyIdx:-1,m:null,initHp:0,initAtk:0};
1246 document.getElementById('fightOverlay').style.display='flex';renderFight();
1247}
1248var DATE_STORIES_EARLY=[
1249 {t:'你约林悦去公司楼下那家奶茶店。她点了一杯波波奶茶,你点了一杯柠檬茶。等单的时候你们站在柜台旁边,她低头刷手机,你偷偷瞄了一眼——她在看一个CSS动画的教程。你说:"周末还学啊?"她抬起头笑了笑:"不是学,是看到一个好看的动效想抄一下。"你心里想:这个人对代码的热爱,是真的。',affMin:0,affBonus:5},
1250 {t:'你们一起去吃了那家兰州拉面。她加了两勺辣椒,你只敢加半勺。她看着你的碗笑了:"你这辣度,在我们湖南是要被开除户籍的。"你嘴硬说:"我这是养生。"她笑得更欢了。你低头吃面的时候,嘴角也翘着。窗外的北京初秋,阳光透过玻璃洒在桌上,那一刻你觉得这碗面特别好吃。',affMin:0,affBonus:6},
1251 {t:'你约她晚饭后散步。你们沿着公司后面的那条小河走了两圈。她讲她大学时参加编程比赛的事——通宵调一个算法,第二天早上发现少了一个括号。"那种感觉你知道吗?找了四个小时的Bug,结果是少了个括号。"你说:"太知道了。"你们对视了一眼,同时笑了出来。那种被理解的默契,比任何话都暖。',affMin:0,affBonus:5},
1252 {t:'你请她喝咖啡。她选了一家你从没去过的小众咖啡店,藏在胡同深处。店里有一只橘猫趴在窗台上晒太阳。她点了杯拿铁,你点了美式。猫跳到你腿上,你僵住了——你不讨厌猫,但你不知道怎么跟猫相处。她笑得前仰后合:"你别动,我给你拍张照。"你一脸无奈,但在快门按下的那一刻,你其实也在笑。',affMin:0,affBonus:5},
1253 {t:'午休时间你约她去楼下便利店。她拿了一瓶乌龙茶,你拿了可乐。结账的时候你抢着付了,她斜了你一眼:"下次我请。"你们站在便利店门口的台阶上喝东西,看着来来往往的人。她说:"你知道吗,我有时候觉得,这样的中午挺好的。不用开会,不用回消息,就站着喝瓶水。"你点了点头。你也是这么想的。',affMin:0,affBonus:4},
1254];
1255
1256var DATE_STORIES_MID=[
1257 {t:'你约她周末去逛公园。她穿了一件浅蓝色的卫衣,头发扎成了马尾。阳光透过树叶洒在她脸上,斑驳的光影一晃一晃的。你们走在银杏树下,金黄的叶子落了一地。她弯腰捡了一片叶子夹在手机壳里:"留个纪念。"你看着她做这个动作,心里忽然涌上一股说不清的感觉——不是紧张,不是兴奋,而是一种安心的、想一直这样走下去的感觉。你赶紧移开了视线,假装在看远处的湖。',affMin:20,affBonus:8},
1258 {t:'你们去了一家日料店。她点了一瓶清酒,给你倒了一杯:"不喝多,就一杯。"你们碰杯的时候,她的手指碰到了你的手指。你心跳漏了一拍,但装作若无其事地喝了一口。她吃着三文鱼寿司,忽然说:"你有没有想过,如果当初你没来面试,我们现在会在干嘛?"你愣了一下,然后说:"可能在跟别的Bug搏斗吧。"她笑了:"那还是现在比较好。"你低头吃寿司,觉得那块三文鱼特别甜。',affMin:20,affBonus:7},
1259 {t:'你们约了周六下午看电影。她选了一部冷门的国产文艺片,你说你没听说过这个导演。她说:"所以才要看啊,说不定是宝藏呢。"电影讲的是一个程序员回乡创业的故事。看到一半的时候,她转头小声说:"这个男主角好像你。"你愣了一下:"哪里像?""都挺倔的。"她说完就转回去继续看了。但你注意到她说这句话的时候,嘴角是弯着的。散场后你们走在商场里,灯光很亮,你的心跳也很快。',affMin:20,affBonus:7},
1260 {t:'你约她去了那家她说过的书店。藏在三里屯的一个拐角,不大,但很安静。她在编程书那一排站了很久,抽出一本《代码整洁之道》翻了翻又放回去,又抽出一本《重构》看了看封面。你站在旁边,发现她的手指很修长,翻书页的动作很轻。她忽然转过头来:"你是不是在看我?"你被抓了个正着,耳朵一下就红了:"我……我在看那本书。"她笑了一下,没有拆穿你。你后来偷偷买了那本她看了最久的书。',affMin:20,affBonus:8},
1261 {t:'你们在公司加班到很晚,你提议去吃宵夜。路边的大排档,塑料桌椅,昏黄的灯泡。她点了一份炒河粉,你点了烤串。等菜的时候她托着下巴看着马路发呆。路灯把她的侧脸照得很柔和。你忽然说:"你有没有觉得,加班到半夜然后一起吃宵夜,其实还挺好的?"她转过头看你,眼睛里有一点疲惫,也有一点光:"嗯。是不错。"那一刻你觉得,如果每个加班的夜晚都能这样过,那加班也不算太糟。',affMin:20,affBonus:7},
1262];
1263
1264var DATE_STORIES_LATE=[
1265 {t:'你们约好周末去爬山。她穿了一件白色的冲锋衣,背了一个小包。山路不陡,但她走得很慢——不是累的,是在看路边的花。"你看这个,这个叫野菊花。"她蹲下来拍了一张照片。你站在旁边等她,忽然觉得时间过得很慢也很好。山顶上风很大,她的头发被吹得乱七八糟,她笑着说:"完了,我的发型。"你说:"没事,反正也不好看。"她锤了你一下。但你们都在笑。下山的时候她走在你前面,夕阳把她的影子拉得很长。你跟在她后面,踩着光的影子一步步往下走。',affMin:50,affBonus:10},
1266 {t:'你请她吃了一顿正经的西餐。她穿了一条深蓝色的连衣裙,你第一次看她穿裙子。你坐在对面,有一瞬间觉得有点不真实——这个人,从电梯口的偶遇到坐在你对面的烛光里,居然走了这么久。她切牛排的动作很优雅,你低头看了看自己切得乱七八糟的那块,有点不好意思。她注意到了,笑着说:"没事,我第一次吃也这样。"你看着她眼睛里的烛光,忽然觉得这顿饭吃什么根本不重要。重要的是对面坐着谁。',affMin:50,affBonus:10},
1267 {t:'你们去了那个她说过想去的音乐节。人很多,她怕走散,拉住了你的手腕——不是手心,是手腕。那个动作很轻,像怕冒犯到你。你反手握住了她的手。她愣了一下,然后没有松开。台上乐队在唱一首你听不懂的歌,但鼓点跟你的心跳一个频率。散场后你们坐在草地上等打车,她靠在你肩膀上。你一动也不敢动。北京的夜晚星星很少,但你觉得今晚的天空很好看。',affMin:50,affBonus:12},
1268 {t:'她生日那天,你提前订了一个蛋糕。不是什么大蛋糕,小小的,上面写了一句"代码无Bug"。你下班前把蛋糕放在她工位上,旁边放了一本你偷偷买的那本《重构》——就是书店那天她看了最久的书。她看到的时候愣住了,然后低头笑了笑。你站在不远处,假装在倒水。她走过来,站在你旁边,声音很轻:"你怎么知道我想要那本书?"你装作漫不经心:"猜的。"她没再说话。但你转身的时候,看到她在擦眼角。',affMin:50,affBonus:12},
1269 {t:'你们一起去做了一次陶艺。她做得有模有样,你做出来的东西像一坨被压扁的土豆。她看着你的作品笑了很久,笑到直不起腰。你有点不服气:"我第一次做,已经很好了好吗!"她擦了擦笑出来的眼泪:"对对对,很有艺术感。"然后她伸手帮你扶了一下转盘上的泥胚。她的手指碰到你的手,沾着湿漉漉的泥。你们都没有躲开。最后她的作品是一个精致的小碗,你的是一个歪歪扭扭的杯子。她把两个放在一起拍了张照片,说:"这样看还挺配的。"',affMin:50,affBonus:11},
1270];
1271
1272var DATE_ART={
1273 early:['milk_tea','noodles','river','coffee','store'],
1274 mid:['park','japanese','cinema','bookstore','stall'],
1275 late:['mountain','candle','music','birthday','pottery']
1276};
1277var DATE_ART_INFO={
1278 milk_tea:{e:'🧋',bg:['#2a1a2a','#5a2a4a'],t:'奶茶店',sub:'波波奶茶 · 柠檬茶'},
1279 noodles:{e:'🍜',bg:['#2a1a10','#5a3a20'],t:'兰州拉面',sub:'加两勺辣椒 · 初秋的阳光'},
1280 river:{e:'🌉',bg:['#0a1a2a','#1a3a5a'],t:'河边散步',sub:'两圈 · 四个小时的Bug'},
1281 coffee:{e:'☕',bg:['#1a1a10','#3a2a10'],t:'胡同咖啡',sub:'橘猫 · 拿铁 · 快门声'},
1282 store:{e:'🏪',bg:['#1a1a2a','#2a2a4a'],t:'便利店',sub:'乌龙茶 · 可乐 · 台阶上'},
1283 park:{e:'🌳',bg:['#0a1a0a','#1a3a1a'],t:'公园',sub:'银杏叶 · 马尾辫 · 安心'},
1284 japanese:{e:'🍣',bg:['#1a0a1a','#3a1a3a'],t:'日料',sub:'清酒 · 手指 · 对视'},
1285 cinema:{e:'🎬',bg:['#0a0a1a','#1a1a3a'],t:'电影院',sub:'冷门文艺片 · "这个男主角好像你"'},
1286 bookstore:{e:'📚',bg:['#1a1a1a','#2a2a2a'],t:'书店',sub:'编程书 · 翻书页 · 被抓'},
1287 stall:{e:'🍢',bg:['#1a0a00','#3a1a00'],t:'大排档',sub:'炒河粉 · 烤串 · 路灯'},
1288 mountain:{e:'⛰️',bg:['#0a1a2a','#1a2a4a'],t:'爬山',sub:'野菊花 · 冲锋衣 · 夕阳'},
1289 candle:{e:'🕯️',bg:['#1a0a1a','#2a0a2a'],t:'烛光晚餐',sub:'深蓝连衣裙 · 对面的人'},
1290 music:{e:'🎵',bg:['#0a0a2a','#2a0a3a'],t:'音乐节',sub:'人群 · 手腕 · 肩膀'},
1291 birthday:{e:'🎂',bg:['#1a0a0a','#3a1a0a'],t:'生日惊喜',sub:'小蛋糕 · "代码无Bug"'},
1292 pottery:{e:'🏺',bg:['#0a1a1a','#1a2a2a'],t:'陶艺',sub:'转盘 · 泥巴 · "挺配的"'},
1293};
1294function getDateStory(){
1295 var aff=S.affection||0;
1296 var pools=[DATE_STORIES_EARLY,DATE_STORIES_MID,DATE_STORIES_LATE];
1297 var artKeys=['early','mid','late'];
1298 var poolIndex=0;
1299 if(aff>=50)poolIndex=2;
1300 else if(aff>=20)poolIndex=1;
1301 var pool=pools[poolIndex];
1302 var idx=Math.floor(Math.random()*pool.length);
1303 var story=pool[idx];
1304 story.artId=DATE_ART[artKeys[poolIndex]][idx];
1305 return story;
1306}
1307function treatLinYue(){
1308 if(!S.affection)S.affection=0;if(!S.affectionBuff)S.affectionBuff={atk:0,def:0,exp:0,remaining:0};
1309 if(!S.chapter||S.chapter<2){exploreLog('<div class="bad">❌ 你们还不太熟,先走主线剧情吧(至少第2章后)</div>');return;}
1310 if(S.dateCooldown===S.chapter){exploreLog('<div class="bad">❌ 这章已经约过了,推进剧情再来吧</div>');return;}
1311 if(G.money<30){exploreLog('<div class="bad">❌ 金币不足,约林悦需要30金</div>');return;}
1312 G.money-=30;
1313 var story=getDateStory();
1314 S.affection+=story.affBonus;S.affection=Math.min(100,S.affection);
1315 S.dateCooldown=S.chapter;
1316 if(story.artId&&DATE_ART_INFO[story.artId]){
1317 var da=DATE_ART_INFO[story.artId];
1318 var el=document.getElementById('exploreLog');
1319 if(el){
1320 var wrap=document.createElement('div');
1321 var cv=document.createElement('canvas');cv.width=280;cv.height=90;
1322 cv.style.cssText='width:100%;max-width:280px;height:90px;border-radius:8px;margin-bottom:4px;display:block;';
1323 var dx=cv.getContext('2d');
1324 var dg=dx.createLinearGradient(0,0,280,90);dg.addColorStop(0,da.bg[0]);dg.addColorStop(1,da.bg[1]);
1325 dx.fillStyle=dg;dx.fillRect(0,0,280,90);
1326 dx.globalAlpha=0.08;
1327 for(var di=0;di<6;di++){dx.beginPath();dx.arc(30+Math.random()*220,10+Math.random()*70,12+Math.random()*28,0,Math.PI*2);dx.fillStyle='#fff';dx.fill();}
1328 dx.globalAlpha=1;
1329 dx.font='28px sans-serif';dx.textAlign='center';dx.fillText(da.e,50,52);
1330 dx.fillStyle='#f0c0d0';dx.font='bold 13px sans-serif';dx.textAlign='left';dx.fillText('❤️ '+da.t,82,37);
1331 dx.fillStyle='#b0a0b0';dx.font='10px sans-serif';dx.fillText(da.sub,82,58);
1332 dx.font='12px sans-serif';dx.fillStyle='rgba(255,105,180,0.25)';dx.fillText('❤️',248,78);
1333 wrap.appendChild(cv);el.appendChild(wrap);el.scrollTop=el.scrollHeight;
1334 }
1335 }
1336 exploreLog('<div style="color:#c9d1d9;border-left:3px solid #ff69b4;padding-left:10px;margin:6px 0;">❤️ 与林悦的约会:'+story.t+'</div><div class="good" style="margin-top:4px;">❤️ 好感度 +'+story.affBonus+'</div>');
1337 var buffDesc='';
1338 if(S.affection>=80){S.affectionBuff={atk:10,def:5,exp:0.3,remaining:3};buffDesc='<div style="color:#ff69b4;">❤️🔥 挚爱 buff激活:攻击+10 防御+5 经验+30%(持续3场战斗)</div>';}
1339 else if(S.affection>=50){S.affectionBuff={atk:5,def:3,exp:0,remaining:3};buffDesc='<div style="color:#ff69b4;">💕 亲密 buff激活:攻击+5 防御+3(持续3场战斗)</div>';}
1340 else if(S.affection>=20){S.affectionBuff={atk:2,def:0,exp:0,remaining:3};buffDesc='<div style="color:#ff69b4;">🪶 心动 buff激活:攻击+2(持续3场战斗)</div>';}
1341 if(buffDesc)exploreLog(buffDesc);
1342 var prog='<div style="font-size:11px;margin-top:4px;">好感度: '+S.affection+'/100';
1343 var bars='';for(var i=0;i<10;i++){bars+=i*10<S.affection?'<span style="color:#ff69b4;">█</span>':'<span style="color:#30363d;">█</span>';}
1344 exploreLog(prog+' '+bars+'</div>');
1345 updateUI();saveGame();
1346}
1347function openPotionShop(){
1348 var h='<div class="sys">🧪 道具店</div>';
1349 SHOP_ITEMS.forEach(function(si){
1350 h+='<div style="border:1px solid #30363d;border-radius:6px;padding:8px;margin:3px 0;background:#161b22;">';
1351 h+='<div style="font-weight:600;">'+si.n+' <span style="color:#f0c040;">'+si.cost+'金</span></div>';
1352 h+='<div style="font-size:12px;color:#8b949e;">'+si.desc+'</div>';
1353 h+='<button class="choice-btn" onclick="buyItem(\''+si.id+'\')" style="padding:4px 10px;font-size:12px;margin-top:4px;">购买</button>';
1354 h+='</div>';
1355 });
1356 h+='<button class="choice-btn" onclick="this.parentElement.style.display=\'none\';" style="width:100%;margin-top:6px;">关闭</button>';
1357 var d=document.getElementById('potionShopOverlay');
1358 if(!d){d=document.createElement('div');d.id='potionShopOverlay';d.style.cssText='position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:300px;background:#0d1117;border:1px solid #30363d;border-radius:8px;padding:16px;z-index:1000;';document.body.appendChild(d);}
1359 d.style.display='block';d.innerHTML=h;
1360}
1361function useItem(idx){
1362if(!G.inventory||idx>=G.inventory.length)return;
1363var it=G.inventory[idx];
1364var si=SHOP_ITEMS.find(function(x){return x.id===it.id;});
1365if(!si){alert('道具数据异常');return;}
1366if(si.id==='potion'){
1367 if(G.hp>=G.maxHp){alert('气血已满,无需使用!');return;}
1368 G.hp=Math.min(G.maxHp,G.hp+50);
1369 G.inventory.splice(idx,1);
1370 updateUI();
1371 openInventory();
1372 return;
1373}
1374if(si.id==='boost'){
1375 G.atk+=5;
1376 G.inventory.splice(idx,1);
1377 updateUI();
1378 openInventory();
1379 return;
1380}
1381if(si.id==='mp_pot'){
1382 if(G.mp>=G.maxMp){alert('灵力已满,无需使用!');return;}
1383 G.mp=Math.min(G.maxMp,G.mp+30);
1384 G.inventory.splice(idx,1);
1385 updateUI();
1386 openInventory();
1387 return;
1388}
1389alert('道具「'+it.n+'」使用成功!');
1390G.inventory.splice(idx,1);
1391updateUI();
1392openInventory();
1393}
1394function openInventory(){
1395 var h='<div class="sys">🎒 背包</div>';
1396 var inv=G.inventory||[];
1397 if(inv.length===0)h+='<div style="text-align:center;padding:20px;color:#8b949e;">🎒 背包空空如也</div>';
1398 else inv.forEach(function(it,i){var si=SHOP_ITEMS.find(function(x){return x.id===it.id;});h+='<div style="border:1px solid #30363d;border-radius:6px;padding:8px;margin:3px 0;background:#161b22;display:flex;justify-content:space-between;align-items:center;cursor:pointer;" onclick="useItem('+i+')"><div style="font-weight:600;">'+it.n+'</div><span style="color:#3fb950;font-size:11px;">使用 →</span></div>';});
1399 h+='<button class="choice-btn" onclick="this.parentElement.style.display=\'none\';" style="width:100%;margin-top:6px;">关闭</button>';
1400 var el=document.getElementById('inventoryOverlay')||function(){var d=document.createElement('div');d.id='inventoryOverlay';d.style.cssText='position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:300px;max-height:70vh;overflow-y:auto;background:#0d1117;border:1px solid #30363d;border-radius:8px;padding:16px;z-index:1000;';document.body.appendChild(d);return d;}();
1401 el.style.display='block';el.innerHTML=h;
1402}
1403
1404function showParticle(text,x,y){
1405 var el=document.createElement('div');el.className='particle';el.textContent=text;
1406 el.style.left=(x-10)+'px';el.style.top=(y-10)+'px';document.body.appendChild(el);
1407 setTimeout(function(){if(el.parentNode)el.parentNode.removeChild(el);},1000);
1408}
1409function init(){
1410 if(CHAPTERS&&CHAPTERS.length>0){
1411 if(loadGame()){
1412 if(S.chapter<CHAPTERS.length&&S.scene&&CHAPTERS[S.chapter].scenes[S.scene])showScene(CHAPTERS[S.chapter].scenes[S.scene],S.scene);
1413 else showChapterStart(S.chapter);
1414 }else showChapterStart(0);
1415 }
1416
1417 setInterval(saveGame,15000);
1418}
1419window.onload=init;
1420
1421
1422
1423/* ── Injected: Tutorial + Fragment Progress ── */
1424var FRAGMENT_NAMES = {work:'💼 奋斗碎片',love:'❤️ 真心碎片',daily:'🎲 奇遇碎片',tech:'💻 技术碎片',mystery:'👻 神秘碎片'};
1425var FRAGMENT_ICONS = {work:'💼',love:'❤️',daily:'🎲',tech:'💻',mystery:'👻'};
1426var FRAGMENT_MAX = {work:10,love:10,daily:10,tech:5,mystery:5};
1427var FRAGMENT_KEYS = ['work','love','daily','tech','mystery'];
1428
1429function showFragmentProgress(){
1430 if(!S.fateFragments)S.fateFragments={};
1431 var ff=S.fateFragments,ch=S.chapter||0;
1432 var nextCh=ch+1,nextReq=null,nextTitle='???';
1433 if(nextCh<CHAPTER_REQS.length)nextReq=CHAPTER_REQS[nextCh];
1434 if(nextCh<CHAPTERS.length)nextTitle=CHAPTERS[nextCh].title;
1435 if(nextCh>=CHAPTERS.length)nextTitle='🏆 最终结局';
1436 var h='<div style="border:1px solid #d29922;border-radius:8px;padding:10px;margin:4px 0;background:#161b22;">';
1437 h+='<div style="font-size:13px;font-weight:600;color:#d29922;margin-bottom:8px;">💾 命运碎片收集</div>';
1438 h+='<div style="border:1px solid #30363d;border-radius:6px;padding:8px;margin-bottom:8px;background:#0d1117;">';
1439 if(nextReq&&nextCh<CHAPTERS.length){
1440 h+='<div style="font-size:11px;color:#8b949e;">🔮 下一章《'+nextTitle+'》要求:</div>';
1441 var hasFateReq=false;
1442 FRAGMENT_KEYS.forEach(function(k){
1443 var fk='fate_'+k;
1444 if(nextReq[fk]){
1445 hasFateReq=true;
1446 var have=ff[k]||0,need=nextReq[fk];
1447 var ok=have>=need?'✅':'❌';
1448 h+='<div style="font-size:11px;margin:2px 0;">'+ok+' '+FRAGMENT_ICONS[k]+' '+have+'/'+need+'</div>';
1449 }
1450 });
1451 if(!hasFateReq)h+='<div style="font-size:11px;color:#3fb950;">✅ 无碎片要求,推进等级即可</div>';
1452 var others=[];
1453 if(nextReq.level&&G.level<nextReq.level)others.push('等级'+nextReq.level);
1454 if(nextReq.weapon&&(G.weapon===undefined||G.weapon<nextReq.weapon)){var wn=WEAPONS[nextReq.weapon]?WEAPONS[nextReq.weapon].n:'武器Lv.'+nextReq.weapon;others.push('装备'+wn);}
1455 if(nextReq.armor&&(G.armor===undefined||G.armor<nextReq.armor)){var an=ARMORS[nextReq.armor]?ARMORS[nextReq.armor].n:'护甲Lv.'+nextReq.armor;others.push('装备'+an);}
1456 if(nextReq.money&&G.money<nextReq.money)others.push(nextReq.money+'金币');
1457 if(others.length>0)h+='<div style="font-size:10px;color:#f85149;margin-top:4px;">⚠️ 还需: '+others.join('、')+'</div>';
1458 }else if(nextCh<CHAPTERS.length){
1459 h+='<div style="font-size:11px;color:#3fb950;">✅ 只需等级≥'+CHAPTER_REQS[nextCh].level+'即可解锁</div>';
1460 }else{
1461 h+='<div style="font-size:11px;color:#3fb950;">🏆 所有章节已解锁!</div>';
1462 }
1463 h+='</div><div style="margin-bottom:6px;">';
1464 FRAGMENT_KEYS.forEach(function(k){
1465 var have=ff[k]||0,max=FRAGMENT_MAX[k];
1466 var pct=Math.min(100,Math.round(have/max*100));
1467 var bc=k==='work'?'#d29922':k==='love'?'#ff69b4':k==='daily'?'#58a6ff':k==='tech'?'#c084fc':'#f85149';
1468 h+='<div style="margin-bottom:4px;"><div style="display:flex;justify-content:space-between;font-size:10px;color:#8b949e;margin-bottom:1px;"><span>'+FRAGMENT_NAMES[k]+'</span><span>'+have+'/'+max+'</span></div>';
1469 h+='<div style="height:6px;background:#21262d;border-radius:3px;overflow:hidden;"><div style="width:'+pct+'%;height:100%;background:'+bc+';border-radius:3px;transition:width .4s;"></div></div></div>';
1470 });
1471 h+='</div><div style="font-size:10px;color:#8b949e;margin-bottom:4px;">💡 获取碎片:</div>';
1472 h+='<div style="display:grid;grid-template-columns:1fr 1fr;gap:3px;"><button class="choice-btn" onclick="switchTab(\'explore\')" style="text-align:center;font-size:10px;padding:4px;border-color:#58a6ff;">🗺️ 探索(20%)</button>';
1473 h+='<button class="choice-btn" onclick="switchTab(\'explore\');showDungeonList()" style="text-align:center;font-size:10px;padding:4px;border-color:#c084fc;">🏰 副本剧情</button>';
1474 h+='<button class="choice-btn" onclick="switchTab(\'explore\');startTraining()" style="text-align:center;font-size:10px;padding:4px;border-color:#d2a8ff;">🧘 修炼(15%)</button>';
1475 h+='<button class="choice-btn" onclick="switchTab(\'status\');renderStatus()" style="text-align:center;font-size:10px;padding:4px;border-color:#8b949e;">📊 返回</button></div></div>';
1476 var el=document.getElementById('statusArea');
1477 if(el){el.innerHTML=h;switchTab('status');}
1478}
1479
1480// ── Tutorial System ──
1481var TUTORIAL_TASKS=[
1482 {id:'start_story',icon:'📖',title:'开启剧情',desc:'看完第零章面试剧情',reward:{exp:10,money:5},check:function(){return S.chapter>=1;}},
1483 {id:'train',icon:'🧘',title:'修炼升级',desc:'修炼提升至2级',reward:{exp:20,money:10},check:function(){return G.level>=2;}},
1484 {id:'fight',icon:'🐛',title:'打怪赚钱',desc:'打一次Bug赚金币',reward:{exp:15,money:20},check:function(){return G.money>=220;}},
1485 {id:'buy_equip',icon:'🗡️',title:'购买装备',desc:'商店买机械键盘(100金)',reward:{exp:10,money:5},check:function(){return G.weapon>=1;}},
1486 {id:'explore',icon:'🗺️',title:'探索收集',desc:'点击探索触发随机事件',reward:{exp:20,money:10},check:function(){return S.fateFragments&&((S.fateFragments.work||0)+(S.fateFragments.love||0)+(S.fateFragments.daily||0)+(S.fateFragments.tech||0)+(S.fateFragments.mystery||0))>0;}},
1487 {id:'advance_story',icon:'🚀',title:'推进剧情',desc:'解锁第3章「线上事故」',reward:{exp:30,money:20},check:function(){return S.chapter>=3;}},
1488 {id:'dungeon',icon:'🏰',title:'闯副本',desc:'完成一个副本剧情',reward:{exp:25,money:30},check:function(){return S.dungeonDone&&Object.keys(S.dungeonDone).length>0;}},
1489 {id:'affection',icon:'💕',title:'约林悦',desc:'好感度10约会一次',reward:{exp:20,money:15},check:function(){return (S.affection||0)>=10;}}
1490];
1491function getTutorialProgress(){
1492 if(!S.tutorial)S.tutorial={};var d=0;
1493 TUTORIAL_TASKS.forEach(function(t){if(S.tutorial[t.id])d++;});
1494 return {done:d,total:TUTORIAL_TASKS.length};
1495}
1496function checkTutorial(id){
1497 if(!S.tutorial)S.tutorial={};if(S.tutorial[id])return false;
1498 var task=null;TUTORIAL_TASKS.forEach(function(t){if(t.id===id)task=t;});
1499 if(!task||!task.check())return false;
1500 S.tutorial[id]=true;var r=task.reward||{};
1501 if(r.exp){G.exp=(G.exp||0)+r.exp;var nl=getExpToLv();while(G.exp>=nl){G.exp-=nl;G.level++;nl=getExpToLv();}}
1502 if(r.money)G.money=(G.money||0)+r.money;
1503 updateUI();saveGame();var p=getTutorialProgress();
1504 exploreLog('<div style="border:1px solid #3fb950;border-radius:8px;padding:10px;margin:4px 0;background:#1a2e1a;"><span style="color:#3fb950;font-weight:600;">✅ 任务完成:'+task.icon+' '+task.title+'</span><br><span style="font-size:11px;color:#8b949e;">奖励:'+(r.exp?'经验+'+r.exp+' ':'')+(r.money?'金币+'+r.money:'')+'</span><br><span style="font-size:11px;color:#8b949e;">📊 进度 '+p.done+'/'+p.total+'</span></div>');
1505 updateTutorialBadge();return true;
1506}
1507function checkAllTutorials(){TUTORIAL_TASKS.forEach(function(t){if(t.check())checkTutorial(t.id);});}
1508function showTutorialPanel(){
1509 checkAllTutorials();var p=getTutorialProgress();
1510 var h='<div style="border:1px solid #7c3aed;border-radius:8px;padding:10px;margin:4px 0;background:#1a1a2e;">';
1511 h+='<div style="font-size:13px;font-weight:600;color:#c084fc;margin-bottom:6px;">📝 新手任务 <span style="font-size:11px;color:#8b949e;font-weight:400;">'+p.done+'/'+p.total+'</span></div>';
1512 h+='<div style="height:4px;background:#21262d;border-radius:2px;margin-bottom:8px;overflow:hidden;"><div style="width:'+(p.total>0?(p.done/p.total*100):0)+'%;height:100%;background:linear-gradient(90deg,#58a6ff,#7c3aed);border-radius:2px;transition:width .4s;"></div></div>';
1513 TUTORIAL_TASKS.forEach(function(t){
1514 var done=S.tutorial&&S.tutorial[t.id];
1515 h+='<div style="display:flex;align-items:center;gap:6px;padding:4px 6px;margin:2px 0;border:1px solid '+(done?'#3fb950':'#30363d')+';border-radius:5px;background:'+(done?'#1a2e1a':'#161b22')+';opacity:'+(done?0.6:1)+';">';
1516 h+='<span style="font-size:14px;">'+(done?'✅':t.icon)+'</span>';
1517 h+='<div style="flex:1;"><div style="font-size:11px;font-weight:600;color:'+(done?'#3fb950':'#c9d1d9')+';">'+(done?'[已完成] '+t.title:t.title)+'</div><div style="font-size:9px;color:#8b949e;">'+(done?'奖励已领取':t.desc)+'</div></div></div>';
1518 });
1519 h+='<div style="text-align:center;margin-top:6px;"><button class="choice-btn" onclick="toggleTutorialPanel()" style="font-size:10px;padding:3px 10px;border-color:#30363d;">收起 ↑</button></div></div>';
1520 return h;
1521}
1522function toggleTutorialPanel(){
1523 var el=document.getElementById('tutorialPanel');
1524 if(!el)return;
1525 if(el.style.display==='none'||!el.style.display){el.innerHTML=showTutorialPanel();el.style.display='block';}
1526 else el.style.display='none';
1527}
1528function updateTutorialBadge(){
1529 var badge=document.getElementById('tutorialBadge');if(!badge)return;
1530 var p=getTutorialProgress();
1531 badge.textContent=p.done+'/'+p.total;
1532 if(p.done===p.total)badge.textContent='✅全部完成';
1533}
1534// Auto-check tutorials in key functions
1535var _origDoExplore=doExplore;
1536doExplore=function(){_origDoExplore();checkAllTutorials();};
1537var _origStartBounty=startBounty;
1538startBounty=function(){_origStartBounty();setTimeout(checkAllTutorials,100);};
1539
1540</script>
1541</body>
1542</html>