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
240 lines
11 KiB
Python
240 lines
11 KiB
Python
#!/usr/bin/env python3
|
||
"""software-step-tutorial.op — 软件操作步骤卡(1080×1350,4:5 单卡)
|
||
|
||
教程档九张里的「工具/软件」那一张,也是唯一一张**深色**教程。
|
||
|
||
### 和既有 screenshot-tutorial 的区别(为什么不是重复选题)
|
||
|
||
仓里已有 `screenshot-tutorial`:暖米底 3:4、五帧轮播、三个整页截图位,教
|
||
的是「怎么做一套教程图」。这一张换了三个维度,不是同一张的改色版:
|
||
|
||
- **画幅与帧数**:4:5 **单卡**。一张图讲完一件事,适合评论区直接甩链接
|
||
的场景;轮播那张适合当系列首图。
|
||
- **信息主元素**:主元素是**菜单路径条**(文件 › 导出为 › PNG 2x),不
|
||
是截图。软件教程真正难的是「那个按钮在哪」,路径条把它一行说清;截图
|
||
退成佐证,只留一个。
|
||
- **色温**:深色 UI 风。软件截图九成是浅色界面,压在深底上边界自己就出
|
||
来了,不需要再给它画边框——暖米底那张必须靠 3px 描边才框得住截图。
|
||
|
||
### 配色推导(采样 → 收敛 → 论证)
|
||
|
||
- **采样**:从「代码编辑器 / 设计工具的深色主题」这一族取色:底不是纯
|
||
黑而是带一点青的石板灰,面板逐级提亮 3 档,强调色取终端绿系。
|
||
- **收敛**:中性 5 档(L 0.09 / 0.13 / 0.17 / 0.24 / 0.65 / 0.96)+ 1 个
|
||
有彩色(青绿 #35D6A4,chroma 0.13)。
|
||
- **论证**:深色界面里最贵的资源是「亮」——把亮度留给三样东西:标题、
|
||
截图、路径条末端那个真正要点的按钮。其余全部压在 0.09-0.24 之间,读
|
||
者的眼睛自然被推到该看的地方。青绿而不是蓝紫:蓝紫在深底上是「廉价 AI
|
||
科技风」的签名色,青绿在终端/编辑器语境里有真实出处。
|
||
|
||
### 负约束(本模板明令不做的事)
|
||
|
||
- **不用蓝紫渐变、不用外发光。** 深色 + 发光是廉价科技风的固定搭配,本
|
||
模板的层级全部由**面板明度**给,一处发光都没有。
|
||
- 不画伪 UI。除了那一个截图位,不虚构窗口标题栏、假按钮、假代码——虚构
|
||
的界面细节永远不可读,读者一眼就知道是假的。
|
||
- 不用 emoji 当图标;图标只用单色线性 lucide,且只出现在路径分隔与占位。
|
||
- 不做圆角以外的装饰形状(不加斜切、不加网格纹理、不加扫描线)。
|
||
- 步骤最多 3 条。第 4 条就该拆成第二张卡,不缩字号硬塞。
|
||
- 说明句不写「点击此处即可轻松完成」这类零信息量的套话,每句都要带一个
|
||
可验证的具体值(倍数、格式、勾选项)。
|
||
|
||
硬契约:
|
||
- 内容距边缘 ≥72px(这里 72)
|
||
- 配色全部走 color_vars;换主色只需改 c-accent 与 c-accent-ink 两个
|
||
- 正文与背景对比度 ≥2.0(实测表见文件末尾)
|
||
- **CJK 行高比西文全线高 0.2**:display 1.2,标题 1.35,正文 1.7
|
||
- **CJK 负字距不超过 -0.02em**(64px 标题 → -1.2px = -0.019em)
|
||
- 汉字走 Noto Sans SC,数字与拉丁走 Inter(等价于「西文在前中文在后」的
|
||
fallback 链,在 .op 里按节点写死)
|
||
- 顶层 frame 显式写 x/y
|
||
- 截图位实测 936×387(2.42:1)——改上下任何一块的高度都要重量
|
||
- 截图位是 frame(合法图片拖放目标),提示做成它的子节点:拖图后整块占位
|
||
连提示一起被替换
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
|
||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||
|
||
from oplib import (Ids, color_vars, frame, icon_font, solid, stroke, text,
|
||
write_doc)
|
||
|
||
ids = Ids()
|
||
|
||
VARS = color_vars({
|
||
"c-bg": "#14171A",
|
||
"c-panel": "#1D2126",
|
||
"c-card": "#262C33",
|
||
"c-line": "#3A424B",
|
||
"c-ink": "#F3F6F8",
|
||
"c-muted": "#9AA6B0",
|
||
"c-accent": "#35D6A4",
|
||
# 强调块上的字。深底模板最容易翻车的一处:把 c-ink 直接放到 accent 上
|
||
# 只有 1.9:1。这里给一个专用的深字色。
|
||
"c-accent-ink": "#08231B",
|
||
})
|
||
|
||
CJK = "Noto Sans SC"
|
||
NUM = "Inter"
|
||
|
||
W, H = 1080, 1350
|
||
EDGE = 72
|
||
|
||
LH_DISPLAY, LH_HEAD, LH_BODY = 1.2, 1.35, 1.7
|
||
|
||
# 菜单路径。末尾那一段是真正要点的东西,所以只有它反白成实心 chip。
|
||
PATH = ["文件", "导出为", "PNG · 2x"]
|
||
|
||
# (序号, 一句话动作, 带具体值的说明)
|
||
STEPS = [
|
||
("1", "先把画板选中,再打开导出",
|
||
"没选中画板时导出的是整个画布,边上会多出一圈空白。"),
|
||
("2", "倍数选 2x,格式选 PNG",
|
||
"1x 发出去在手机上发虚;JPG 会把文字边缘压出灰边。"),
|
||
("3", "勾掉「包含背景」再导",
|
||
"留白交给发布平台去铺,导出时带底色会和它的底打架。"),
|
||
]
|
||
|
||
|
||
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=20, 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 chip(label, *, bg, fg, weight=600, size=26):
|
||
node = frame(ids, "路径段", width="fit_content", height="fit_content",
|
||
layout="horizontal", padding=[12, 22], cornerRadius=10,
|
||
alignItems="center", justifyContent="center", fill=solid(bg))
|
||
node["children"] = [
|
||
text(ids, "路径段文字", label, size, weight, fg, family=CJK,
|
||
width="fit_content", growth="auto", line_height=1.4),
|
||
]
|
||
return node
|
||
|
||
|
||
# ------------------------------------------------------------------ 页头
|
||
def header():
|
||
tag = frame(ids, "档位标签", width="fit_content", height="fit_content",
|
||
layout="horizontal", padding=[10, 20], cornerRadius=999,
|
||
alignItems="center", justifyContent="center",
|
||
fill=solid("$c-card"))
|
||
tag["children"] = [
|
||
text(ids, "档位文字", "操作教程 · 共 3 步", 24, 600, "$c-accent",
|
||
family=CJK, width="fit_content", growth="auto", line_height=1.4),
|
||
]
|
||
return col("页头", [
|
||
tag,
|
||
text(ids, "主标题", "三步把设计稿\n导成能直接发的图", 64, 700,
|
||
"$c-ink", family=CJK, line_height=LH_DISPLAY, spacing=-1.2),
|
||
text(ids, "副标题", "菜单藏得深,记住下面这条路径就够了。", 26, 400,
|
||
"$c-muted", family=CJK, line_height=LH_BODY),
|
||
], gap=20)
|
||
|
||
|
||
# ------------------------------------------------------------------ 路径条
|
||
def path_bar():
|
||
kids = []
|
||
for index, seg in enumerate(PATH):
|
||
if index:
|
||
kids.append(icon_font(ids, "路径分隔", "chevron-right", 22,
|
||
"$c-muted"))
|
||
last = index == len(PATH) - 1
|
||
kids.append(chip(seg,
|
||
bg="$c-accent" if last else "$c-panel",
|
||
fg="$c-accent-ink" if last else "$c-ink"))
|
||
bar = row("菜单路径条", kids, gap=12, padding=[16, 20])
|
||
bar["fill"] = solid("$c-panel")
|
||
bar["cornerRadius"] = 16
|
||
bar["stroke"] = stroke("$c-line", 2)
|
||
return bar
|
||
|
||
|
||
# ------------------------------------------------------------------ 截图位
|
||
def shot():
|
||
node = frame(ids, "截图占位", width="fill_container",
|
||
height="fill_container", layout="vertical", gap=16,
|
||
alignItems="center", justifyContent="center",
|
||
cornerRadius=20, fill=solid("$c-card"),
|
||
stroke=stroke("$c-line", 2))
|
||
node["children"] = [
|
||
icon_font(ids, "占位图标", "image", 56, "$c-muted"),
|
||
text(ids, "占位标题", "把导出面板的截图拖进来", 30, 600, "$c-ink",
|
||
family=CJK, align="center", line_height=LH_HEAD),
|
||
text(ids, "占位规格", "占位比例 2.4:1,导出前记得关掉通知",
|
||
24, 400, "$c-muted", family=CJK, align="center",
|
||
line_height=LH_BODY),
|
||
]
|
||
return node
|
||
|
||
|
||
# ------------------------------------------------------------------ 步骤
|
||
def step_row(no, title, desc):
|
||
box = frame(ids, "序号底", width=48, height=48, layout="horizontal",
|
||
alignItems="center", justifyContent="center", cornerRadius=12,
|
||
fill=solid("$c-panel"))
|
||
box["children"] = [
|
||
text(ids, "序号", no, 26, 700, "$c-accent", family=NUM,
|
||
width="fit_content", growth="auto", line_height=1.0),
|
||
]
|
||
return row("步骤", [
|
||
box,
|
||
col("步骤文案", [
|
||
text(ids, "步骤标题", title, 28, 600, "$c-ink", family=CJK,
|
||
line_height=LH_HEAD),
|
||
text(ids, "步骤说明", desc, 24, 400, "$c-muted", family=CJK,
|
||
line_height=LH_BODY),
|
||
], gap=6),
|
||
], gap=18, align="start")
|
||
|
||
|
||
def steps():
|
||
return col("步骤区", [step_row(*s) for s in STEPS], gap=18)
|
||
|
||
|
||
# ------------------------------------------------------------------ 页脚
|
||
def footer():
|
||
return row("页脚", [
|
||
text(ids, "账号名", "@ 你的账号名", 24, 600, "$c-muted", family=CJK,
|
||
width="fit_content", growth="auto", line_height=1.4),
|
||
text(ids, "用法提示", "深色块是图片位,直接把截图拖上去", 22, 400,
|
||
"$c-muted", family=CJK, width="fit_content", growth="auto",
|
||
line_height=1.4),
|
||
], gap=16, align="center", justifyContent="space_between")
|
||
|
||
|
||
def build():
|
||
page = frame(ids, "软件操作步骤卡", width=W, height=H, layout="vertical",
|
||
padding=[64, EDGE], gap=32, fill=solid("$c-bg"),
|
||
clipContent=True)
|
||
page["children"] = [header(), path_bar(), shot(), steps(), footer()]
|
||
page["x"], page["y"] = 0, 0
|
||
return [page]
|
||
|
||
|
||
# 对比度(WCAG 相对亮度比,op-design-lint 门槛 2.0;数值由脚本实测):
|
||
# c-ink on c-bg 16.58 c-muted on c-bg 7.24
|
||
# c-ink on c-panel 14.91 c-muted on c-panel 6.52
|
||
# c-ink on c-card 12.98 c-muted on c-card 5.67
|
||
# c-accent on c-bg 9.69 c-accent on c-panel 8.71
|
||
# c-accent on c-card 7.59 c-accent-ink on c-accent 8.93
|
||
# 承载正文的最低一对是 5.67(占位规格压在 c-card 上),仍高于 AA 正文
|
||
# 门槛 4.5。c-line 只用于描边,是非文字图形,不参与文字对比度。
|
||
|
||
if __name__ == "__main__":
|
||
write_doc(sys.argv[1], VARS, build(), "软件操作步骤卡 · 4:5 单卡")
|