立项:《古法代码之王》展示站

- 21 章小说正文 + 22 幅原创插图
- 近未来都市科幻爽文主题(AI 崩溃后古法程序员逆袭)
- 单页静态展示站(nginx:alpine + Dockerfile,Coolify 部署)
- Hero 主角参考图半透明叠加 + 金色渐变标题
- 章节目录 + 逐章阅读 + 画廊 lightbox

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
kang
2026-04-18 10:32:14 +08:00
commit b8c0ab157d
40 changed files with 8930 additions and 0 deletions

834
web/index.html Normal file
View File

@@ -0,0 +1,834 @@
<!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;
}
/* ---------- 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>
</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: '低端外包,熬夜,凄惨地活着' },
{ 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: '在行业边缘漂泊多年' },
{ 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: '金融交通医疗能源连锁崩塌' },
{ 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: '凭古法工程修复核心调度' },
{ 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 重新成为工具而非拐杖' },
];
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;
}
const num = ch.n === 0 ? '终' : 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('');
}
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() {
try {
const resp = await fetch('./novel.md');
const text = await resp.text();
renderNovel(text);
} catch (e) {
document.getElementById('reader-content').innerHTML =
'<div class="loading" style="color: var(--red)">小说载入失败:' + e.message + '</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章 标题" or "终章 标题"
const m = heading.match(/^(第(\d+)章|终章)\s+(.+)$/);
if (!m) return;
const isEpilogue = !m[2];
const num = isEpilogue ? '终' : m[2].padStart(2, '0');
const title = m[3];
const ch = CHAPTERS[idx];
const imgPath = ch ? `./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');
out.push(`
<article class="reader-chapter" id="chapter-${idx}">
<div class="reader-chapter-num">${isEpilogue ? 'EPILOGUE' : 'CHAPTER ' + num}</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>
${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>