openpencil/templates/step0/_generators/tpl_compare_version_diff.py
Fini c998d551eb feat(editor): grow the template library to fifty-eight across six scenes
Every non-deck, non-web scene now holds ten templates: cards (spec
themes as living samples), infographics, tutorials, comparisons and
carousels (nine spec themes with per-set master-board rules), plus
two web landings whose 'generate from this' pins their measured
style guides — a basis the document swap used to drop before the
turn ever saw it. All fifty-eight ship generator-reproduced (sha256-
matched), audit-clean, with baked preview cards, and the scene tab
row pins Slides second-from-last and Web last. Two prompt-center
entries carry the web pair with re-rendered previews and honest
render provenance.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 02:24:43 +08:00

213 lines
9 KiB
Python
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.

#!/usr/bin/env python3
"""version-diff-comparison.op — 新旧版本变化1080×1080 方版 1:1
对比档里唯一一张**行内**对照:不分左右两栏,每一行自己就是一次「旧 → 新」。
读者不需要在两栏之间来回扫,只要顺着列表往下滑,每一行都在一处完成对比。
### 最近邻论证(为什么它不是已有的哪一张)
- **before-after1600×900**:那张也是「旧 → 新」,但只有**一组**,主
体是两张大截图。这张是**五组**,每组只有一对短语——它比的不是画面而是
条目,所以能塞进一个方版里滑着看。
- **本批 02 好坏双栏**:那张把「错」和「对」各自聚成一栏,眼睛左右扫;
这张把「旧」和「新」在**同一行内**并置,眼睛只往下走。同为二元对照,
版式的骨架完全不同。
- **本批 07 时间对比**:那张是一年前 / 现在的**整体气质**变化,主体是两
组并列的状态;这张是**逐条变更**,主体是一份可以照着核对的清单。
### 配色推导(采样 → 收敛 → 论证)
- **采样**:从「版本记录」这件事采——它属于终端、日志、更新说明那一类
版面,记忆色是墨绿黑底上的浅字,不是任何品牌色。
- **收敛**单色相墨绿H≈175°的一条七级明度序列 L 0.09 / 0.21 / 0.38 /
0.59 / 0.87 / 0.92 / 1.0chroma 深处 0.03、浅处 0.008,只有一个色相。
- **论证**:一行里有两个值在争注意力,如果给「新」配一个高饱和色,五行
下来就是五个亮点,等于没有重点。改用**明度**:旧值是浅底弱字(已经退
场),新值是深底反白(已经生效)。五行的深块自然连成一条视觉纵线,读
者第一眼看到的就是「新版是什么样」。
### 负约束(本模板明令不做的事)
- **不用第二个色相。** 整张图只有墨绿一族 + 中性白。
- 不用红绿。版本变化没有对错,旧值当时也是对的。
- **不给旧值加删除线。** 删除线在中文里会压在字身上、糊成一片;「退场」
由浅底弱字表达就够了。
- 不写「重磅 / 史诗级 / 全新升级」。每行只写变成了什么。
- 不用蓝紫渐变、霓虹线条、伪 3D、阴影、emoji 图标。
- 每个值 ≤8 字:新旧两个 chip 等宽,写长了两边会一高一低,纵线就断了。
- 不列超过六行。列表一长,读者就会开始跳读,「逐条核对」这个前提就没了。
硬契约:
- 内容距边缘 ≥64px这里 64
- 固定 1:1 画幅:根高写死 1080靠 space_between 分配三块之间的空隙
- 新旧两个 chip 必须都是 fill_container等宽是「同一件事的两个取值」的
视觉前提
- 配色全部走 color_vars单色序列改一处色相即整张换肤
- 正文与背景对比度 ≥2.0(最低一对见文件末尾实测表)
- **CJK 行高**:大标题 1.2,行标题 1.3,正文 1.6
- **CJK 负字距不超过 -0.02em**;版本号与数字走 Inter
"""
import os
import sys
sys.path.insert(0, os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(
os.path.dirname(os.path.abspath(__file__))))),
"templates", "step0", "_generators"))
from oplib import (Ids, color_vars, frame, icon_font, rect, solid, text,
write_doc)
ids = Ids()
VARS = color_vars({
"c-bg": "#EEF2F1",
"c-card": "#FFFFFF",
"c-panel": "#DCE4E2",
"c-line": "#C7D2CF",
"c-ink": "#0C1817",
"c-deep": "#1D3733",
"c-muted": "#465F5B",
"c-faint": "#7C918D",
"c-inv-muted": "#9CB0AC",
})
CJK = "Noto Sans SC"
NUM = "Inter"
W, H = 1080, 1080
EDGE = 64
LABEL_W = 168
LH_DISPLAY, LH_HEAD, LH_BODY = 1.2, 1.3, 1.6
OLD_VER, NEW_VER = "v1.4", "v2.0"
# (类别, 旧值, 新值)。每个值 ≤8 字:两个 chip 等宽,长短不一会把纵线撑歪。
CHANGES = [
("首页加载", "3.4 秒", "0.9 秒"),
("导航层级", "三层菜单", "一层直达"),
("深色模式", "没有", "跟随系统"),
("导出格式", "只有 PNG", "还能存 PDF"),
("免费额度", "10 次 / 月", "50 次 / 月"),
]
def col(name, children, *, gap=16, width="fill_container", align="start",
**props):
node = frame(ids, name, width=width, height="fit_content",
layout="vertical", gap=gap, alignItems=align, fill=[],
**props)
node["children"] = children
return node
def row(name, children, *, gap=16, align="center", width="fill_container",
**props):
node = frame(ids, name, width=width, height="fit_content",
layout="horizontal", gap=gap, alignItems=align, fill=[],
**props)
node["children"] = children
return node
def version_chip(label, current):
"""版本号徽记。旧版描边、新版实心——形状语言在页头就先声明一次。"""
node = frame(ids, "版本徽记", width="fit_content", height="fit_content",
layout="horizontal", padding=[9, 20], cornerRadius=8,
alignItems="center", justifyContent="center",
fill=solid("$c-ink") if current else [])
if not current:
node["stroke"] = {"thickness": 2, "fill": solid("$c-line")}
node["children"] = [
text(ids, "版本号", label, 26, 700,
"$c-card" if current else "$c-muted", family=NUM,
width="fit_content", growth="auto", line_height=1.4),
]
return node
# --------------------------------------------------------------------- 页头
def head():
return col("页头", [
row("版本行", [
version_chip(OLD_VER, False),
icon_font(ids, "版本箭头", "arrow-right", 26, "$c-faint"),
version_chip(NEW_VER, True),
], gap=14, align="center", width="fit_content"),
text(ids, "主标题", "这版改了什么", 72, 700, "$c-ink", family=CJK,
line_height=LH_DISPLAY, spacing=-1.4),
text(ids, "副标题", "五处能被立刻用上的变化,深色那一半是现在的样子。",
26, 400, "$c-muted", family=CJK, line_height=LH_BODY),
], gap=20)
# ------------------------------------------------------------------- 变更行
def value_chip(content, current):
"""一个取值。旧 = 浅底弱字(退场),新 = 深底反白(生效)。"""
box = frame(ids, "取值", width="fill_container", height=64,
layout="horizontal", padding=[0, 22], gap=0,
alignItems="center", justifyContent="start", cornerRadius=8,
fill=solid("$c-deep" if current else "$c-panel"))
box["children"] = [
text(ids, "取值文字", content, 27, 700 if current else 400,
"$c-card" if current else "$c-muted", family=NUM,
line_height=1.4),
]
return box
def change_row(label, old, new):
return row("变更行", [
text(ids, "类别", label, 26, 600, "$c-ink", family=CJK,
width=LABEL_W, line_height=1.4),
value_chip(old, False),
icon_font(ids, "变为", "arrow-right", 24, "$c-faint"),
value_chip(new, True),
], gap=14, align="center")
def changes():
items = [change_row(*change) for change in CHANGES]
return col("变更列表", items, gap=14)
# --------------------------------------------------------------------- 页脚
def tail():
band = col("页脚", [
text(ids, "结语", "另有六项小修复,都写在更新日志里。", 29, 600,
"$c-card", family=CJK, line_height=LH_HEAD),
row("署名行", [
text(ids, "账号名", "@ 你的账号名", 25, 600, "$c-card",
family=CJK, width="fit_content", growth="auto",
line_height=1.4),
text(ids, "更新说明", "每次发版都出一张", 23, 400, "$c-inv-muted",
family=CJK, width="fit_content", growth="auto",
line_height=1.4),
], gap=14),
], gap=12, padding=[32, 32])
band["fill"] = solid("$c-ink")
return band
def build():
page = frame(ids, "新旧版本变化", width=W, height=H, layout="vertical",
padding=[56, EDGE], gap=36, justifyContent="space_between",
alignItems="start", fill=solid("$c-bg"), clipContent=True)
page["children"] = [head(), changes(), tail()]
page["x"], page["y"] = 0, 0
return [page]
# 对比度WCAG 相对亮度比op-design-lint 门槛 2.0;数值由 contrast.py 实测):
# c-ink on c-bg 16.06 c-muted on c-bg 6.10
# c-muted on c-panel 5.33 c-card on c-deep 12.75
# c-card on c-ink 18.12 c-inv-muted on c-ink 7.96
# c-faint on c-bg 2.96(只画两处箭头与旧版号描边,非正文)
# 承载正文的最低一对是 5.33 —— 旧值压在浅底 chip 上,过 AA 正文门槛4.5
# 但明显轻于同行的新值12.75),「退场 / 生效」的落差就是这么来的。
if __name__ == "__main__":
write_doc(sys.argv[1], VARS, build(), "新旧版本变化")