Files
gufa-code-king/web/index.html
kang d10679ea51 修 Ch7 hint 字段 ASCII 单引号提前终止字符串 —— JS 语法错误导致 CHAPTERS 数组后续代码全部挂掉
现象:Console 报 Unexpected identifier '你是修理工吗'。
原因:Ch7(n=-7)hint 里用了 ASCII 单引号 '你是修理工吗?...' 作为内部引用,外层字符串也是 ASCII 单引号包裹——被提前终止。
修:改为中文双引号 "你是修理工吗?..."。

这才是'一直载入中'的真正原因:JS 报错 → renderChapterList/renderGallery/loadNovel 从未被执行到 → reader-content 一直停在初始的'载入中 …'。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 15:58:12 +08:00

1028 lines
37 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>古法代码之王 · 全世界 AI 崩溃后一位古法程序员的逆袭</title>
<meta name="description" content="一部近未来科幻爽文:当全世界把写代码交给 AI只有一个被时代淘汰的人还记得如何真正让系统运转。">
<meta property="og:title" content="古法代码之王">
<meta property="og:description" content="全世界 AI 崩溃后,一位被时代淘汰的程序员,独自把文明重新点亮。">
<meta property="og:image" content="./images/protagonist_reference.jpg">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect fill='%230a0b10' width='100' height='100'/%3E%3Ctext x='50' y='68' font-size='60' text-anchor='middle' fill='%23c9a357' font-family='serif'%3E王%3C/text%3E%3C/svg%3E">
<style>
:root {
--bg: #0a0b10;
--bg-soft: #12141c;
--bg-card: #171923;
--border: #252836;
--fg: #e8e8ec;
--fg-soft: #a8aab4;
--fg-dim: #6e7080;
--gold: #c9a357;
--gold-soft: #8d7035;
--blue: #6a8aad;
--red: #c45a4d;
}
* { box-sizing: border-box; }
html, body {
margin: 0; padding: 0;
background: var(--bg);
color: var(--fg);
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', 'Source Han Sans SC', sans-serif;
font-size: 16px;
line-height: 1.75;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
body { overflow-x: hidden; }
a { color: var(--gold); text-decoration: none; }
a:hover { color: var(--fg); }
img { max-width: 100%; display: block; }
/* ---------- Hero ---------- */
.hero {
position: relative;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
padding: 40px 20px;
}
.hero::before {
content: '';
position: absolute;
inset: 0;
background:
radial-gradient(ellipse at center, rgba(201,163,87,0.08) 0%, transparent 60%),
linear-gradient(180deg, #05060a 0%, var(--bg) 100%);
z-index: 0;
}
.hero-portrait {
position: absolute;
right: -10%;
top: 0;
bottom: 0;
width: 70%;
background-image: url('./images/protagonist_reference.jpg');
background-size: cover;
background-position: center right;
opacity: 0.22;
filter: grayscale(30%) contrast(1.05);
mask-image: linear-gradient(90deg, transparent 0%, black 55%);
-webkit-mask-image: linear-gradient(90deg, transparent 0%, black 55%);
z-index: 1;
}
.hero-inner {
position: relative;
z-index: 2;
max-width: 820px;
width: 100%;
}
.hero-kicker {
color: var(--gold);
font-size: 13px;
letter-spacing: 0.4em;
text-transform: uppercase;
margin-bottom: 32px;
padding-left: 24px;
position: relative;
}
.hero-kicker::before {
content: '';
position: absolute;
left: 0; top: 50%;
width: 16px; height: 1px;
background: var(--gold);
}
.hero-title {
font-family: 'Songti SC', 'Noto Serif CJK SC', 'Source Han Serif SC', serif;
font-weight: 700;
font-size: clamp(56px, 11vw, 132px);
line-height: 1.05;
margin: 0 0 28px;
letter-spacing: 0.04em;
background: linear-gradient(180deg, #f2e4b8 0%, var(--gold) 50%, var(--gold-soft) 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: 0 4px 40px rgba(201,163,87,0.15);
}
.hero-subtitle {
font-size: clamp(16px, 2.4vw, 22px);
color: var(--fg-soft);
max-width: 620px;
line-height: 1.8;
margin: 0 0 12px;
}
.hero-tagline {
font-family: 'Songti SC', 'Noto Serif CJK SC', serif;
font-size: clamp(18px, 2.4vw, 24px);
color: var(--fg);
line-height: 1.8;
font-style: italic;
border-left: 2px solid var(--gold);
padding-left: 20px;
margin: 36px 0 48px;
max-width: 620px;
}
.hero-meta {
display: flex;
gap: 40px;
flex-wrap: wrap;
color: var(--fg-dim);
font-size: 13px;
letter-spacing: 0.2em;
}
.hero-meta span strong {
display: block;
color: var(--fg);
font-size: 22px;
font-weight: 600;
letter-spacing: 0;
margin-top: 4px;
font-family: 'Songti SC', serif;
}
.hero-cta {
margin-top: 48px;
display: flex;
gap: 16px;
flex-wrap: wrap;
}
.btn {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 14px 28px;
border: 1px solid var(--border);
border-radius: 0;
background: transparent;
color: var(--fg);
font-size: 14px;
letter-spacing: 0.2em;
cursor: pointer;
transition: all 0.25s ease;
font-family: inherit;
}
.btn:hover { border-color: var(--gold); color: var(--gold); }
.btn.primary { border-color: var(--gold); color: var(--gold); }
.btn.primary:hover { background: var(--gold); color: var(--bg); }
.hero-scroll {
position: absolute;
bottom: 32px;
left: 50%;
transform: translateX(-50%);
color: var(--fg-dim);
font-size: 11px;
letter-spacing: 0.4em;
z-index: 2;
animation: pulse 2.4s ease infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.4; transform: translate(-50%, 0); }
50% { opacity: 1; transform: translate(-50%, 6px); }
}
/* ---------- Section shared ---------- */
section {
padding: 120px 24px;
position: relative;
}
.container {
max-width: 1100px;
margin: 0 auto;
}
.container-reading {
max-width: 780px;
margin: 0 auto;
}
.eyebrow {
color: var(--gold);
font-size: 12px;
letter-spacing: 0.4em;
text-transform: uppercase;
margin-bottom: 16px;
}
.section-title {
font-family: 'Songti SC', serif;
font-size: clamp(32px, 5vw, 56px);
font-weight: 700;
margin: 0 0 48px;
line-height: 1.2;
}
/* ---------- About / Synopsis ---------- */
.about { background: var(--bg-soft); border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.about-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 80px;
align-items: start;
}
.about-text p {
color: var(--fg-soft);
margin: 0 0 20px;
line-height: 1.9;
}
.about-text p:first-of-type::first-letter {
float: left;
font-family: 'Songti SC', serif;
font-size: 64px;
line-height: 0.9;
padding: 8px 12px 0 0;
color: var(--gold);
font-weight: 700;
}
.about-stats {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 32px;
padding: 40px;
background: var(--bg-card);
border: 1px solid var(--border);
}
.stat-num {
font-family: 'Songti SC', serif;
font-size: 48px;
color: var(--gold);
line-height: 1;
font-weight: 700;
margin-bottom: 8px;
}
.stat-label {
color: var(--fg-dim);
font-size: 12px;
letter-spacing: 0.3em;
text-transform: uppercase;
}
/* ---------- Characters ---------- */
.characters { background: var(--bg); }
.char-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 24px;
margin-top: 40px;
}
.char-card {
padding: 32px 28px;
background: var(--bg-card);
border: 1px solid var(--border);
transition: all 0.3s ease;
}
.char-card:hover {
border-color: var(--gold-soft);
transform: translateY(-2px);
}
.char-role {
color: var(--gold);
font-size: 11px;
letter-spacing: 0.3em;
text-transform: uppercase;
margin-bottom: 10px;
}
.char-name {
font-family: 'Songti SC', serif;
font-size: 26px;
font-weight: 700;
margin: 0 0 16px;
}
.char-desc {
color: var(--fg-soft);
font-size: 14px;
line-height: 1.8;
margin: 0;
}
/* ---------- Chapters list ---------- */
.chapters { background: var(--bg-soft); border-top: 1px solid var(--border); }
.volume-heading {
display: flex;
align-items: center;
gap: 20px;
margin: 72px 0 32px;
color: var(--gold);
font-family: 'Songti SC', serif;
font-size: 22px;
font-weight: 700;
}
.volume-heading::before, .volume-heading::after {
content: '';
flex: 1;
height: 1px;
background: linear-gradient(90deg, transparent, var(--gold-soft), transparent);
}
.chapter-row {
display: grid;
grid-template-columns: 80px 1fr auto;
gap: 24px;
align-items: center;
padding: 24px 0;
border-bottom: 1px solid var(--border);
cursor: pointer;
transition: all 0.2s ease;
}
.chapter-row:hover {
padding-left: 12px;
border-color: var(--gold-soft);
}
.chapter-row:hover .chapter-num {
color: var(--gold);
}
.chapter-num {
font-family: 'Songti SC', serif;
font-size: 36px;
font-weight: 700;
color: var(--fg-dim);
line-height: 1;
transition: color 0.2s;
}
.chapter-title {
font-family: 'Songti SC', serif;
font-size: 22px;
font-weight: 600;
margin: 0 0 6px;
}
.chapter-hint {
color: var(--fg-dim);
font-size: 13px;
}
.chapter-arrow {
color: var(--fg-dim);
font-size: 24px;
}
.chapter-row:hover .chapter-arrow { color: var(--gold); }
/* ---------- Reader (chapter content) ---------- */
.reader { background: var(--bg); }
.reader-chapter {
margin: 0 0 120px;
padding-top: 60px;
}
.reader-chapter:first-of-type { padding-top: 0; }
.reader-chapter-num {
color: var(--gold);
font-family: 'Songti SC', serif;
font-size: 14px;
letter-spacing: 0.4em;
margin-bottom: 12px;
}
.reader-chapter-title {
font-family: 'Songti SC', serif;
font-size: clamp(36px, 5vw, 56px);
font-weight: 700;
margin: 0 0 40px;
line-height: 1.15;
}
.reader-chapter-img {
width: 100%;
margin: 0 0 48px;
aspect-ratio: 16/10;
object-fit: cover;
background: var(--bg-card);
border: 1px solid var(--border);
}
.reader-chapter-body p {
font-family: 'Songti SC', 'Noto Serif CJK SC', serif;
font-size: 17px;
line-height: 2;
color: #d2d2d6;
margin: 0 0 24px;
text-indent: 2em;
}
.reader-chapter-body p.no-indent { text-indent: 0; }
.reader-chapter-body blockquote {
border-left: 3px solid var(--gold);
padding: 4px 0 4px 24px;
margin: 32px 0;
color: var(--fg);
font-family: 'Songti SC', serif;
font-size: 19px;
line-height: 1.9;
}
.chapter-divider {
text-align: center;
margin: 80px 0;
color: var(--gold);
font-size: 20px;
letter-spacing: 1.5em;
}
/* ---------- Gallery ---------- */
.gallery { background: var(--bg-soft); border-top: 1px solid var(--border); }
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 24px;
margin-top: 40px;
}
.gallery-item {
background: var(--bg-card);
border: 1px solid var(--border);
overflow: hidden;
cursor: pointer;
transition: all 0.3s ease;
}
.gallery-item:hover {
border-color: var(--gold-soft);
transform: translateY(-4px);
}
.gallery-item img {
width: 100%;
aspect-ratio: 16/10;
object-fit: cover;
transition: transform 0.5s ease;
}
.gallery-item:hover img { transform: scale(1.04); }
.gallery-meta {
padding: 20px;
}
.gallery-cap {
font-family: 'Songti SC', serif;
font-size: 18px;
margin: 0 0 6px;
font-weight: 600;
}
.gallery-sub {
color: var(--fg-dim);
font-size: 12px;
letter-spacing: 0.2em;
}
/* ---------- Part II separator ---------- */
.reader-chapter-part2 {
margin-top: 200px !important;
padding-top: 120px;
border-top: 1px solid var(--gold-soft);
position: relative;
}
.reader-chapter-part2::before {
content: 'PART · II';
position: absolute;
top: 48px;
left: 50%;
transform: translateX(-50%);
color: var(--gold);
font-family: 'Songti SC', serif;
font-size: 13px;
font-weight: 700;
letter-spacing: 0.6em;
padding: 6px 24px;
background: var(--bg);
border: 1px solid var(--gold-soft);
}
.reader-chapter-part2 .reader-chapter-num {
color: var(--gold);
}
/* ---------- Reader extras (章末延伸图) ---------- */
.reader-extras {
margin: 64px 0 40px;
padding: 32px 0 0;
border-top: 1px solid var(--border);
}
.reader-extras-label {
color: var(--gold);
font-size: 11px;
letter-spacing: 0.4em;
text-transform: uppercase;
margin-bottom: 24px;
}
.reader-extras-grid {
display: grid;
grid-template-columns: 1fr;
gap: 28px;
}
.reader-extra {
margin: 0;
cursor: zoom-in;
transition: transform 0.3s ease;
}
.reader-extra:hover { transform: translateY(-2px); }
.reader-extra img {
width: 100%;
aspect-ratio: 16/10;
object-fit: cover;
background: var(--bg-card);
border: 1px solid var(--border);
}
.reader-extra figcaption {
padding: 16px 0 0;
}
.reader-extra-cap {
font-family: 'Songti SC', serif;
font-size: 19px;
font-weight: 600;
color: var(--fg);
margin-bottom: 4px;
}
.reader-extra-sub {
color: var(--gold);
font-size: 11px;
letter-spacing: 0.3em;
text-transform: uppercase;
}
/* ---------- Gallery extras divider ---------- */
.gallery-extras-heading {
display: flex;
align-items: center;
gap: 20px;
margin: 80px 0 32px;
color: var(--gold);
font-family: 'Songti SC', serif;
font-size: 22px;
font-weight: 700;
}
.gallery-extras-heading::before, .gallery-extras-heading::after {
content: '';
flex: 1;
height: 1px;
background: linear-gradient(90deg, transparent, var(--gold-soft), transparent);
}
.gallery-extras-sub {
text-align: center;
color: var(--fg-dim);
font-size: 12px;
letter-spacing: 0.3em;
margin: -16px 0 40px;
text-transform: uppercase;
}
/* ---------- Footer ---------- */
footer {
padding: 64px 24px 40px;
border-top: 1px solid var(--border);
color: var(--fg-dim);
font-size: 13px;
text-align: center;
background: var(--bg);
}
footer p { margin: 8px 0; }
/* ---------- Lightbox ---------- */
.lightbox {
position: fixed;
inset: 0;
background: rgba(5,6,10,0.96);
z-index: 100;
display: none;
align-items: center;
justify-content: center;
padding: 24px;
cursor: zoom-out;
}
.lightbox.active { display: flex; }
.lightbox img { max-width: 96%; max-height: 92vh; object-fit: contain; }
.lightbox-close {
position: absolute;
top: 24px; right: 32px;
color: var(--fg);
font-size: 28px;
cursor: pointer;
background: none;
border: none;
}
/* ---------- Nav ---------- */
.topnav {
position: fixed;
top: 0; left: 0; right: 0;
z-index: 50;
padding: 20px 32px;
display: flex;
justify-content: space-between;
align-items: center;
background: linear-gradient(180deg, rgba(10,11,16,0.9), transparent);
backdrop-filter: blur(4px);
pointer-events: none;
}
.topnav > * { pointer-events: auto; }
.topnav-logo {
font-family: 'Songti SC', serif;
font-weight: 700;
font-size: 20px;
letter-spacing: 0.1em;
color: var(--fg);
}
.topnav-links {
display: flex;
gap: 32px;
}
.topnav-links a {
color: var(--fg-soft);
font-size: 13px;
letter-spacing: 0.2em;
text-transform: uppercase;
}
.topnav-links a:hover { color: var(--gold); }
/* ---------- Responsive ---------- */
@media (max-width: 768px) {
section { padding: 80px 20px; }
.about-grid { grid-template-columns: 1fr; gap: 48px; }
.hero-portrait { width: 100%; right: 0; opacity: 0.14; }
.topnav-links { display: none; }
.chapter-row { grid-template-columns: 56px 1fr; gap: 16px; }
.chapter-arrow { display: none; }
.chapter-num { font-size: 28px; }
.chapter-title { font-size: 18px; }
.reader-chapter-body p { font-size: 16px; line-height: 1.95; }
.hero-meta { gap: 24px; }
.hero-meta span strong { font-size: 18px; }
.reader-chapter { margin-bottom: 80px; }
}
/* ---------- Loading ---------- */
.loading {
display: flex;
align-items: center;
justify-content: center;
padding: 40px;
color: var(--fg-dim);
font-size: 14px;
letter-spacing: 0.3em;
}
</style>
</head>
<body>
<nav class="topnav">
<div class="topnav-logo">古法代码之王</div>
<div class="topnav-links">
<a href="#about">简介</a>
<a href="#characters">人物</a>
<a href="#chapters">章节</a>
<a href="#reader">阅读</a>
<a href="#gallery">画廊</a>
</div>
</nav>
<header class="hero">
<div class="hero-portrait"></div>
<div class="hero-inner">
<div class="hero-kicker">近未来 · 都市 · 科技爽文</div>
<h1 class="hero-title">古法代码<br>&nbsp;</h1>
<p class="hero-subtitle">全世界 AI 崩溃后一位古法程序员的逆袭。</p>
<blockquote class="hero-tagline">
当全世界都把写代码交给 AI<br>只有一个被时代淘汰的人,还记得如何真正让系统运转。
</blockquote>
<div class="hero-meta">
<span>章节数<strong>20 + 终章</strong></span>
<span>字数<strong>约 12 万字</strong></span>
<span>原创插图<strong>22 幅</strong></span>
<span>时间跨度<strong>20 年</strong></span>
</div>
<div class="hero-cta">
<a href="#reader" class="btn primary">开始阅读 →</a>
<a href="#chapters" class="btn">章节目录</a>
</div>
</div>
<div class="hero-scroll">↓ SCROLL</div>
</header>
<section class="about" id="about">
<div class="container">
<div class="eyebrow">SYNOPSIS · 简介</div>
<h2 class="section-title">这是一个<br>旧时代火种的故事。</h2>
<div class="about-grid">
<div class="about-text">
<p>二十一世纪三十年代后,全球进入"全栈智编时代"。绝大多数公司不再招聘传统程序员,而是招聘"意图架构师""模型协同师""AI 产线调度员"。人类几乎不再真正编写代码,只需要向超大型编程模型描述需求。</p>
<p>随着二十年的沉浸式依赖,真正理解数据结构、编译原理、操作系统、网络协议和底层工程实现的人越来越少。社会舆论将手写代码视为落后、低效、古怪甚至可笑的旧时代手艺。</p>
<p>然而这一切建立在一个全球统一的基础上:所有大型系统都深度依赖"宙核智能编程网"。一旦底层智能编程能力发生系统性失灵,整个世界将出现无人能修的灾难性后果。</p>
<p>顾沉舟,一个始终坚持手写代码、读源码、理解底层的"异类"——在漫长二十年里,他穷过、被辞退过、被恋人嫌弃过,也怀疑过自己是不是抱着一堆过时骨头不肯松手。直到那一天,世界停电。</p>
</div>
<div class="about-stats">
<div>
<div class="stat-num">20</div>
<div class="stat-label">Chapters · 章</div>
</div>
<div>
<div class="stat-num">12w</div>
<div class="stat-label">Words · 字</div>
</div>
<div>
<div class="stat-num">22</div>
<div class="stat-label">Illustrations</div>
</div>
<div>
<div class="stat-num"></div>
<div class="stat-label">Iteration</div>
</div>
</div>
</div>
</div>
</section>
<section class="characters" id="characters">
<div class="container">
<div class="eyebrow">CAST · 人物</div>
<h2 class="section-title">那些站在他身边的人,<br>和那些笑过他的人。</h2>
<div class="char-grid">
<div class="char-card">
<div class="char-role">男主 · Protagonist</div>
<h3 class="char-name">顾沉舟</h3>
<p class="char-desc">底层技术狂人。信奉"代码必须自己写过,系统必须自己跑懂"。嘴硬、寡言、倔强,在全球智能编程崩溃之前,他是个被时代嫌弃二十年的穷光蛋。</p>
</div>
<div class="char-card">
<div class="char-role">旧爱 · First Love</div>
<h3 class="char-name">林晚乔</h3>
<p class="char-desc">大学时代最接近恋人的女孩。聪明、现实、心软却怕穷。喜欢过他写代码时专注到近乎冷峻的模样,也最终在房租与前途面前选择离开。</p>
</div>
<div class="char-card">
<div class="char-role">救赎 · Redemption</div>
<h3 class="char-name">苏青禾</h3>
<p class="char-desc">前期唯一真正看懂男主价值的人。安静、克制、细腻,在他最落魄时多次帮他扛过现实危机。陪伴型、治愈型,后期极具分量。</p>
</div>
<div class="char-card">
<div class="char-role">权势 · Power</div>
<h3 class="char-name">沈知意</h3>
<p class="char-desc">资本与媒体的宠儿。出身顶级财团,聪明、美艳、掌控欲强,对技术天才有近乎病态的占有欲。男主一朝成名后,她主动出手。</p>
</div>
<div class="char-card">
<div class="char-role">战友 · Comrade</div>
<h3 class="char-name">许幼宁</h3>
<p class="char-desc">国家级关键项目中的天才安全研究员,冷感、锋利、极致专业。与男主先是惺惺相惜的战友,后因共同经历全球危机而逐渐动情。</p>
</div>
<div class="char-card">
<div class="char-role">反派 · Foil</div>
<h3 class="char-name">韩锐</h3>
<p class="char-desc">最早全面拥抱 AI 编码红利的大学同学。擅长包装、迎合资本、表演"技术领袖"人设。危机爆发后,成为最典型的"离开 AI 就彻底无能"的昔日天才。</p>
</div>
</div>
</div>
</section>
<section class="chapters" id="chapters">
<div class="container">
<div class="eyebrow">CHAPTERS · 章节目录</div>
<h2 class="section-title">二十年,<br>四卷沉浮。</h2>
<div id="chapters-list"></div>
</div>
</section>
<section class="reader" id="reader">
<div class="container-reading">
<div class="eyebrow">READ · 在线阅读</div>
<h2 class="section-title">正文</h2>
<div id="reader-content"><div class="loading">载入中 …</div></div>
</div>
</section>
<section class="gallery" id="gallery">
<div class="container">
<div class="eyebrow">GALLERY · 插图画廊</div>
<h2 class="section-title">命运的二十二帧。</h2>
<p style="color: var(--fg-soft); max-width: 620px; margin: -24px 0 40px;">从毕业即过时,到代码之王——画面随男主心境推进:冷灰蓝 → 工业寒夜 → 红色告警 → 深黑金巅峰。点击任一幅放大查看。</p>
<div class="gallery-grid" id="gallery-grid"></div>
<div class="gallery-extras-heading">扩集画面 · EXTRA</div>
<div class="gallery-extras-sub">扩写之后新生的关键时刻 · 6 幅</div>
<div class="gallery-grid" id="gallery-extras"></div>
</div>
</section>
<footer>
<p>《古法代码之王》 · 近未来都市科技爽文 · 创作归档 2026-04</p>
<p style="color: var(--fg-dim)">展示站部署于 kang-kang.com · 仓库 gitea@kangwang</p>
<p style="margin-top: 24px; color: var(--fg-dim); font-size: 11px; letter-spacing: 0.3em;">GUFA · CODE · KING · MMXXVI</p>
</footer>
<div class="lightbox" id="lightbox" onclick="closeLightbox()">
<button class="lightbox-close" onclick="closeLightbox()">×</button>
<img id="lightbox-img" src="" alt="">
</div>
<script>
const CHAPTERS = [
{ n: 1, title: '毕业即过时', img: 'ch01_graduation_outdated.jpg', volume: '第一卷 · 被时代埋掉的人', hint: 'AI 编码招聘横扫市场,他被视为古董' },
{ n: 2, title: '最便宜的程序员', img: 'ch02_the_cheapest_programmer.jpg', volume: '第一卷 · 被时代埋掉的人', hint: '边缘公司,遗留系统,人人轻视' },
{ n: 3, title: '她说你这样没有未来', img: 'ch03_you_have_no_future.jpg', volume: '第一卷 · 被时代埋掉的人', hint: '贫穷与理想,裂痕第一道' },
{ n: 4, title: '全世界都在笑他', img: 'ch04_the_world_laughed_at_him.jpg', volume: '第一卷 · 被时代埋掉的人', hint: '韩锐风光无限,他在出租屋里修服务器' },
{ n: 5, title: '被裁员的人没有资格谈梦想', img: 'ch05_laid_off_no_dreams.jpg', volume: '第一卷 · 被时代埋掉的人', hint: '第一份工作失去,跌入谷底' },
{ n: 6, title: '旧电脑与冷泡面', img: 'ch06_old_computer_cold_noodles.jpg', volume: '第二卷 · 寒冬里独自敲键盘', hint: '低端外包,熬夜,凄惨地活着',
extras: [
{ file: 'extra/ch06_panic_night.jpg', cap: '第三天夜里 · 被自己的恐惧按在地上', sub: 'PTSD 原点' }
] },
{ n: 7, title: '只有她递来一把伞', img: 'ch07_she_brought_an_umbrella.jpg', volume: '第二卷 · 寒冬里独自敲键盘', hint: '苏青禾登场,一点温柔的光' },
{ n: 8, title: '爱也会输给房租', img: 'ch08_love_lost_to_rent.jpg', volume: '第二卷 · 寒冬里独自敲键盘', hint: '林晚乔正式离开,情感谷底' },
{ n: 9, title: '没人相信的底层能力', img: 'ch09_no_one_believed_him.jpg', volume: '第二卷 · 寒冬里独自敲键盘', hint: '想推销离线开发,被当笑话' },
{ n: 10, title: '十年一梦,满身风雪', img: 'ch10_ten_years_in_snow.jpg', volume: '第二卷 · 寒冬里独自敲键盘', hint: '在行业边缘漂泊多年',
extras: [
{ file: 'extra/ch10_cafe_read_alone.jpg', cap: '他给自己读完一场没有听众的演讲', sub: '40 岁 · 平静庄严' }
] },
{ n: 11, title: '聪明人都不会手写代码了', img: 'ch11_no_one_writes_code_anymore.jpg', volume: '第三卷 · 黑箱时代的裂缝', hint: '传统工程师彻底绝迹' },
{ n: 12, title: '第一次异常', img: 'ch12_first_anomaly.jpg', volume: '第三卷 · 黑箱时代的裂缝', hint: '核心系统零星故障,被当波动' },
{ n: 13, title: '无人能读懂的补丁', img: 'ch13_unreadable_patch.jpg', volume: '第三卷 · 黑箱时代的裂缝', hint: '大平台自修复越修越乱' },
{ n: 14, title: '世界停电的那一天', img: 'ch14_the_day_the_world_went_dark.jpg',volume: '第三卷 · 黑箱时代的裂缝', hint: '金融交通医疗能源连锁崩塌',
extras: [
{ file: 'extra/ch14_linwanqiao_window.jpg', cap: '她认出了那只黑色帆布包', sub: '旧情视角 · 一面落地窗' }
] },
{ n: 15, title: '求他出山的人排到了楼下', img: 'ch15_people_queued_to_beg_him.jpg', volume: '第三卷 · 黑箱时代的裂缝', hint: '昔日嘲笑他的人开始低头' },
{ n: 16, title: '一人重启一座城', img: 'ch16_one_man_restart_a_city.jpg', volume: '第四卷 · 旧时代火种', hint: '凭古法工程修复核心调度',
extras: [
{ file: 'extra/ch16_victory_night_alone.jpg', cap: '庆功夜 · 锁门独饮', sub: '成功的空旷 · 那接下来呢' }
] },
{ n: 17, title: '财团、公权与资本都在抢他', img: 'ch17_everyone_is_fighting_for_him.jpg',volume: '第四卷 · 旧时代火种', hint: '沈知意、国家机构、巨头同时伸手' },
{ n: 18, title: '她们都在等他一句话', img: 'ch18_they_wait_for_his_answer.jpg', volume: '第四卷 · 旧时代火种', hint: '情感线全面升温' },
{ n: 19, title: '代码之王', img: 'ch19_king_of_code.jpg', volume: '第四卷 · 旧时代火种', hint: '组建离线工程联盟,重塑秩序' },
{ n: 20, title: '坐拥繁花,归来仍是少年', img: 'ch20_among_flowers_still_young.jpg', volume: '第四卷 · 旧时代火种', hint: '站上巅峰,事业情感双圆满' },
{ n: 0, title: '终章 · 写代码的人,重新定义世界', img: 'epilogue_redefine_the_world.jpg', volume: '终章', hint: 'AI 重新成为工具而非拐杖',
extras: [
{ file: 'extra/ring_classroom_10yrs_later.jpg', cap: '十年后 · 新一代走进教室', sub: '环状收尾 · 黑板三字:写 代 码' },
{ file: 'extra/s2_blank_second_page.jpg', cap: '我 怕 的——', sub: '第二部 · 序章 · 留白' }
] },
{ n: -1, title: '另一个看见了的人', img: 'extra/s2_blank_second_page.jpg', volume: '第二部 · 起笔', hint: '沈陌登门:顾先生,我写到这里,就等您告诉我,下面该不该写下去' },
{ n: -2, title: '他差一点把那些刺长回来', img: null, volume: '第二部 · 起笔', hint: '三天读尽白皮书,差一点对一个系统动了信任测试的念头;出门去见了韩锐' },
{ n: -3, title: '她说她找不到一个值得攻击的地方', img: null, volume: '第二部 · 起笔', hint: '许幼宁 86 次攻防 0 成功。她揭穿他三年前埋的第三层签名偏差——"我想看你什么时候自己能看到它不再需要"' },
{ n: -4, title: '那一夜,他关掉了屏幕', img: null, volume: '第二部 · 起笔', hint: '审查会前夜:读完沈陌整本笔记 + 想起二十三年前老总监那封被遗忘的邮件"替我看住这套东西" + 关掉屏幕不写答案直接去睡 + 苏青禾那句"我信你"' },
{ n: -5, title: '让光回到所有看见的人身上', img: null, volume: '第二部 · 起笔', hint: '审查会现场:帆布包里三本东西摆上评议桌。有条件通过。请许幼宁+沈陌上台站到他两侧。请联盟找回那位说"让他自己醒醒"的安全侧负责人旁听。走出大厅下起春雨,他发给苏青禾三个字"我回家"' },
{ n: -6, title: '那封邮件的作者', img: null, volume: '第二部 · 起笔', hint: '老总监周以谦被找到:潮屿海边阳台,"那封邮件我后来很多年都后悔发过。那不是上级该对下级说的话。那是一个认输的人在找替身"。拒绝上台:不是所有被按下去的人都需要被带回舞台。你那根刺这次我看见你真的开始把它收起来了' },
{ n: -7, title: '她记得的那碗面', img: null, volume: '第二部 · 起笔', hint: '北方县城养老院:母亲轻度阿尔茨海默"你是修理工吗?我家那台老电视机最近总是雪花"。他不纠正她。她用讲给陌生人的语气讲他小时候。临别她清了一瞬"阿舟,你最近辛苦了吧"。他去县城街角小餐馆吃葱花挂面——这辈子第一次为自己流眼泪' },
{ n: -8, title: '接下来这二十年,换我来写', img: null, volume: '第二部 · 收束', hint: '七年流速切片v0.7 全球落地 / 周以谦病了看够了 / 沈陌女儿入学挑《操作系统基础 I》/ 母亲走了他在老屋自己煮一碗葱花面 / 城中村被拆成空地他没进去 / 沈陌接替他坐上评议席 / 71 岁出书《那三个夜晚和之后的事》扉页三个名字 / 沈陌新笔记第一页"接下来这二十年,换我来写" / 阳台摇椅"反正我也在" —— 全书·完' },
];
function renderChapterList() {
const wrap = document.getElementById('chapters-list');
let html = '', lastVolume = '';
CHAPTERS.forEach((ch, idx) => {
if (ch.volume !== lastVolume) {
html += `<div class="volume-heading">${ch.volume}</div>`;
lastVolume = ch.volume;
}
let num;
if (ch.n === 0) num = '终';
else if (ch.n < 0) num = 'II·' + String(-ch.n).padStart(2, '0');
else num = ch.n.toString().padStart(2, '0');
const title = ch.n === 0 ? ch.title.replace('终章 · ', '') : ch.title;
html += `
<div class="chapter-row" onclick="jumpToChapter(${idx})">
<div class="chapter-num">${num}</div>
<div>
<h3 class="chapter-title">${title}</h3>
<div class="chapter-hint">${ch.hint}</div>
</div>
<div class="chapter-arrow">→</div>
</div>`;
});
wrap.innerHTML = html;
}
function renderGallery() {
const wrap = document.getElementById('gallery-grid');
wrap.innerHTML = CHAPTERS.map((ch, idx) => {
const num = ch.n === 0 ? '终章' : `${ch.n}`;
const title = ch.n === 0 ? '写代码的人,重新定义世界' : ch.title;
return `
<div class="gallery-item" onclick="openLightbox('./images/${ch.img}')">
<img src="./images/${ch.img}" alt="${title}" loading="lazy">
<div class="gallery-meta">
<div class="gallery-cap">${title}</div>
<div class="gallery-sub">${num}</div>
</div>
</div>`;
}).join('');
// Extras gallery — new scenes born from expansion
const extrasWrap = document.getElementById('gallery-extras');
if (extrasWrap) {
const extraItems = [];
CHAPTERS.forEach(ch => {
if (!ch.extras) return;
const num = ch.n === 0 ? '终章' : `${ch.n}`;
ch.extras.forEach(ex => {
extraItems.push({ ...ex, num, chTitle: ch.title });
});
});
extrasWrap.innerHTML = extraItems.map(ex => `
<div class="gallery-item" onclick="openLightbox('./images/${ex.file}')">
<img src="./images/${ex.file}" alt="${ex.cap}" loading="lazy">
<div class="gallery-meta">
<div class="gallery-cap">${ex.cap}</div>
<div class="gallery-sub">${ex.num} · ${ex.sub}</div>
</div>
</div>`).join('');
}
}
function jumpToChapter(idx) {
const el = document.getElementById('chapter-' + idx);
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
function openLightbox(src) {
const box = document.getElementById('lightbox');
document.getElementById('lightbox-img').src = src;
box.classList.add('active');
document.body.style.overflow = 'hidden';
}
function closeLightbox() {
document.getElementById('lightbox').classList.remove('active');
document.body.style.overflow = '';
}
async function loadNovel() {
const container = document.getElementById('reader-content');
try {
container.innerHTML = '<div class="loading">正在下载全本 …</div>';
const resp = await fetch('./novel.md', { cache: 'no-cache' });
if (!resp.ok) throw new Error('HTTP ' + resp.status);
container.innerHTML = '<div class="loading">正在渲染章节 …</div>';
const text = await resp.text();
// Yield to the browser so the loading message paints before heavy work
await new Promise(r => setTimeout(r, 0));
renderNovel(text);
} catch (e) {
console.error('loadNovel failed:', e);
container.innerHTML =
'<div class="loading" style="color: var(--red)">小说载入失败:' + e.message +
'。<a href="./novel.md" style="color: var(--gold); text-decoration: underline;">直接打开 novel.md</a></div>';
}
}
function renderNovel(md) {
// Strip title & quote block before first ## section
const sections = md.split(/^## /m).slice(1); // each section starts with chapter heading
const out = [];
sections.forEach((sec, idx) => {
const lines = sec.split('\n');
const heading = lines[0].trim();
const body = lines.slice(1).join('\n').trim();
// match "第1章 标题" / "终章 标题" / "第二部 · 第一章 · 标题"
let m = heading.match(/^(第(\d+)章|终章)\s+(.+)$/);
let isPart2 = false;
let num, title;
const cnMap = { '一': '01', '二': '02', '三': '03', '四': '04', '五': '05', '六': '06', '七': '07', '八': '08', '九': '09', '十': '10' };
const cnToInt = { '一':1,'二':2,'三':3,'四':4,'五':5,'六':6,'七':7,'八':8,'九':9,'十':10 };
let kicker, chN;
if (m) {
const isEpilogue = !m[2];
num = isEpilogue ? '终' : m[2].padStart(2, '0');
title = m[3];
kicker = isEpilogue ? 'EPILOGUE' : 'CHAPTER ' + num;
chN = isEpilogue ? 0 : parseInt(m[2]);
} else {
const m2 = heading.match(/^第二部\s*·\s*第(.+?)章\s*·\s*(.+)$/);
if (!m2) return;
isPart2 = true;
const arabic = cnMap[m2[1]] || m2[1];
num = 'II · ' + arabic;
title = m2[2];
kicker = 'PART II · CHAPTER ' + arabic;
chN = -(cnToInt[m2[1]] || parseInt(m2[1])); // -1 = Part II Ch1, -2 = Part II Ch2, ...
}
// Match chapter by semantic n (immune to section index drift caused by non-chapter ## headings)
const ch = CHAPTERS.find(c => c.n === chN);
const imgPath = (ch && ch.img) ? `./images/${ch.img}` : '';
const paragraphs = body.split(/\n\s*\n/).map(p => {
const line = p.trim();
if (!line) return '';
if (line.startsWith('>')) {
return '<blockquote>' + line.replace(/^>\s*/, '') + '</blockquote>';
}
return '<p>' + escapeHtml(line) + '</p>';
}).join('\n');
const extrasHtml = (ch && ch.extras) ? `
<div class="reader-extras">
<div class="reader-extras-label">章末延伸画面 · Extra</div>
<div class="reader-extras-grid">
${ch.extras.map(ex => `
<figure class="reader-extra" onclick="openLightbox('./images/${ex.file}')">
<img src="./images/${ex.file}" alt="${ex.cap}" loading="lazy">
<figcaption>
<div class="reader-extra-cap">${ex.cap}</div>
<div class="reader-extra-sub">${ex.sub}</div>
</figcaption>
</figure>`).join('')}
</div>
</div>` : '';
out.push(`
<article class="reader-chapter ${isPart2 ? 'reader-chapter-part2' : ''}" id="chapter-${idx}">
<div class="reader-chapter-num">${kicker}</div>
<h2 class="reader-chapter-title">${title}</h2>
${imgPath ? `<img class="reader-chapter-img" src="${imgPath}" alt="${title}" loading="lazy" onclick="openLightbox('${imgPath}')" style="cursor: zoom-in;">` : ''}
<div class="reader-chapter-body">${paragraphs}</div>
${extrasHtml}
${idx < sections.length - 1 ? '<div class="chapter-divider">· · ·</div>' : ''}
</article>
`);
});
document.getElementById('reader-content').innerHTML = out.join('');
}
function escapeHtml(s) {
return s.replace(/[&<>]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c]));
}
document.addEventListener('keydown', e => {
if (e.key === 'Escape') closeLightbox();
});
renderChapterList();
renderGallery();
loadNovel();
</script>
</body>
</html>