auto-save 2026-04-18 13:05 (+6, ~3, -6)
@@ -223,6 +223,13 @@
|
||||
"message": "auto-save 2026-04-18 12:53 (~1)",
|
||||
"hash": "93823d9",
|
||||
"files_changed": 1
|
||||
},
|
||||
{
|
||||
"ts": "2026-04-18T12:59:27+08:00",
|
||||
"type": "commit",
|
||||
"message": "auto-save 2026-04-18 12:59 (+2, ~1)",
|
||||
"hash": "7a1c957",
|
||||
"files_changed": 8
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -46,8 +46,8 @@ SCENES = [
|
||||
{
|
||||
"id": "ch14_linwanqiao_window",
|
||||
"age_hint": "NOT the protagonist — focus on a different character, a composed woman in her mid-30s named LIN WANQIAO",
|
||||
"scene": "Floor-to-ceiling window of the 31st floor of a city office tower, late morning. A composed elegant Chinese woman in her mid-30s, wavy shoulder-length hair, beige trench coat, pale gold earrings, stands with her back turned to the viewer, facing the window. In the reflection on the glass AND on the wall-mounted TV to her right, a news feed is broadcasting — blurred shot of a black emergency response vehicle parked at a small alley; beside the vehicle a thin-shouldered man in an old coat carries a black canvas bag. In her hand she holds a cup of coffee, its steam rising. The open-plan office behind her is chaotic — colleagues crowded around a projector in a meeting room. She herself stands still, not moving. Camera framing behind her shoulder. Her expression cannot be seen clearly.",
|
||||
"mood": "Cold blue-white. City panic in the background, stillness in the foreground. Regret without tears."
|
||||
"scene": "Floor-to-ceiling window of the 31st floor of a city office tower, late morning under heavy overcast sky. A composed elegant Chinese woman in her mid-30s, wavy shoulder-length hair, beige trench coat, pale gold earrings, stands with her back turned to the viewer, facing the window. In her hand she holds a cup of coffee, faint steam rising. On a wall-mounted flat TV to her right, the screen shows ONLY a silent footage: a dim gray narrow city alley, an unmarked black van parked at the alley entrance, a thin-shouldered Chinese man in a plain worn dark coat walking past the van carrying a plain black canvas shoulder bag. The TV screen shows NO NEWS BANNER, NO ENGLISH TEXT, NO HEADLINE, NO SUBTITLE — the entire screen is just the alley footage with cinematic film grain. The open-plan office behind her is chaotic — out-of-focus colleagues crowded around a projector in a distant glass-walled meeting room. She herself stands still, not moving. Camera framed from slightly behind her shoulder. Her face cannot be seen clearly.",
|
||||
"mood": "Cold blue-white. The city in the background is in disarray, stillness in the foreground. A look across time, regret without tears. ABSOLUTELY NO TEXT ANYWHERE ON THE TV OR IN THE SCENE."
|
||||
},
|
||||
{
|
||||
"id": "ch16_victory_night_alone",
|
||||
@@ -63,9 +63,9 @@ SCENES = [
|
||||
},
|
||||
{
|
||||
"id": "s2_blank_second_page",
|
||||
"age_hint": "around 64 years old, hair half white, thin, composed, sitting in a small simple book study at sunset",
|
||||
"scene": "A clean simple home study by a wide river window at dusk. Golden horizontal sunset light cutting in across the wooden desk. GU CHENZHOU, now 64, hair half white, thin, sits at the desk. On the desk: an old worn canvas bag with 'GUIXU · 2038' on its side, opened; a very old small notebook lies open, the first page shows one hand-written line of Chinese and the second page is blank. His hand with thin veined skin rests on the blank second page. His eyes are fixed on the empty page. On his right, a laptop screen glows faintly — on that laptop screen we can see three Chinese characters '我 怕 的——' at the top of a blank text editor with a blinking cursor after them. No one else in the room. Absolute stillness. Framing close enough to see both the notebook and the laptop screen in the same shot.",
|
||||
"mood": "Quiet, ceremonial, doubt. An old hero facing a second choice. Open ending."
|
||||
"age_hint": "around 64 years old — NOT older, NOT 70+. Hair is salt-and-pepper with roughly equal black and grey strands, still thick and neatly combed, NOT bald, NOT thinning on top. Face remains the same person as in the reference image — just aged by about 20 years. Slim but upright posture. Still composed and sharp.",
|
||||
"scene": "A clean simple home study by a wide river window at dusk. Warm golden horizontal sunset light cutting in across a worn wooden desk. GU CHENZHOU, same face as the reference image but about 64 years old, hair still thick but now half black half grey, sits alone at the desk. On the desk, in the foreground: an old worn canvas shoulder bag with the faded handwritten text 'GUIXU · 2038' on its side — the bag lies open. A very old small hand-bound notebook lies open on the desk — the first page shows one line of small hand-written Chinese characters; the second page is completely blank. His hand with slightly prominent veins rests lightly on the blank second page. His eyes are fixed on the empty page, a silent expression of doubt. To his right, a slim modern laptop sits — its screen glowing faintly — on the laptop screen is a nearly-empty minimal text editor, and at the top of the editor three large Chinese characters are clearly readable: '我 怕 的——' followed by a blinking cursor. The laptop screen is positioned and framed so the viewer can clearly read those three Chinese characters. No other person in the room. Absolute stillness.",
|
||||
"mood": "Quiet, ceremonial, doubt. An old hero facing a second choice. Open ending. Warm golden light against the cold blue river outside."
|
||||
},
|
||||
]
|
||||
|
||||
@@ -147,20 +147,24 @@ def generate_one(scene: dict, ref_data_url: str) -> tuple[str, Path | None, str
|
||||
|
||||
|
||||
def main():
|
||||
print(f"=== gen {len(SCENES)} images ===")
|
||||
only = sys.argv[1:] if len(sys.argv) > 1 else None
|
||||
scenes = [s for s in SCENES if not only or s["id"] in only]
|
||||
print(f"=== gen {len(scenes)} images ===")
|
||||
if only:
|
||||
print(f"filter: {only}")
|
||||
print(f"ref image: {REF_IMG} ({REF_IMG.stat().st_size // 1024}KB)")
|
||||
ref_url = encode_ref_image()
|
||||
print(f"ref base64 len: {len(ref_url) // 1024}KB")
|
||||
print(f"out dir: {OUT_DIR}")
|
||||
print()
|
||||
print("scenes:")
|
||||
for s in SCENES:
|
||||
for s in scenes:
|
||||
print(f" - {s['id']}")
|
||||
print()
|
||||
|
||||
results = []
|
||||
with ThreadPoolExecutor(max_workers=3) as pool:
|
||||
futures = [pool.submit(generate_one, s, ref_url) for s in SCENES]
|
||||
futures = [pool.submit(generate_one, s, ref_url) for s in scenes]
|
||||
for f in as_completed(futures):
|
||||
results.append(f.result())
|
||||
|
||||
|
||||
BIN
web/images/extra/ch06_panic_night.jpg
Normal file
|
After Width: | Height: | Size: 343 KiB |
|
Before Width: | Height: | Size: 650 KiB |
BIN
web/images/extra/ch10_cafe_read_alone.jpg
Normal file
|
After Width: | Height: | Size: 337 KiB |
|
Before Width: | Height: | Size: 1.5 MiB |
BIN
web/images/extra/ch14_linwanqiao_window.jpg
Normal file
|
After Width: | Height: | Size: 366 KiB |
|
Before Width: | Height: | Size: 740 KiB |
BIN
web/images/extra/ch16_victory_night_alone.jpg
Normal file
|
After Width: | Height: | Size: 546 KiB |
|
Before Width: | Height: | Size: 829 KiB |
BIN
web/images/extra/ring_classroom_10yrs_later.jpg
Normal file
|
After Width: | Height: | Size: 374 KiB |
|
Before Width: | Height: | Size: 694 KiB |
BIN
web/images/extra/s2_blank_second_page.jpg
Normal file
|
After Width: | Height: | Size: 401 KiB |
|
Before Width: | Height: | Size: 1.6 MiB |
140
web/index.html
@@ -449,6 +449,80 @@ section {
|
||||
letter-spacing: 0.2em;
|
||||
}
|
||||
|
||||
/* ---------- 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;
|
||||
@@ -675,6 +749,10 @@ footer p { margin: 8px 0; }
|
||||
<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>
|
||||
|
||||
@@ -696,22 +774,38 @@ const CHAPTERS = [
|
||||
{ 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: 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: '在行业边缘漂泊多年' },
|
||||
{ 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: '金融交通医疗能源连锁崩塌' },
|
||||
{ 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: '凭古法工程修复核心调度' },
|
||||
{ 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 重新成为工具而非拐杖' },
|
||||
{ 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: '第二部 · 序章 · 留白' }
|
||||
] },
|
||||
];
|
||||
|
||||
function renderChapterList() {
|
||||
@@ -751,6 +845,27 @@ function renderGallery() {
|
||||
</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) {
|
||||
@@ -804,12 +919,27 @@ function renderNovel(md) {
|
||||
}
|
||||
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" 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>
|
||||
${extrasHtml}
|
||||
${idx < sections.length - 1 ? '<div class="chapter-divider">· · ·</div>' : ''}
|
||||
</article>
|
||||
`);
|
||||
|
||||