Author SHA1 Message Date
wait4xandGLM-5.3 3e09a362cc refactor(home): 首页三字段默认值收敛到 SITE_DEFAULTS(5g T4 补)
- SITE_DEFAULTS 新增 homeProjectsLimit=6 / homeAnnouncement(空) /
  homePostsTracks(全开),访问器改为引用默认常量而非散落字面量
- 旧 yaml(无三字段)零回归实测:公告不渲染、项目墙默认 6 条、三轨全开

验证: 构建 48 页 exit 0;E2E 37/37(快照口径)

Spec: 2026-11-02_blog-5g-pages (T3/T4 补充)

Co-authored-by: GLM-5.3
2026-11-02 19:02:41 +08:00
wait4xandGLM-5.3 8debd99e61 feat(theme-home): 样式即数据 + 首页抽组件去重(5g T3/T4)
【T3 样式即数据】
- content/site/theme.yaml 九组白名单旋钮(accent 明暗两值 / radius_img /
  text_h1-h3 / prose_size_reading|tech / leading_reading|tech /
  space_3-6 / flow_p 与 flow_h2_post / dur);--measure、断点、纸墨、
  code 配色按方案 §3.6 红线不开放
- src/lib/theme.ts:getTheme() 钳制 + 单调性校验(h1>h2>h3、
  space3<4<5<6 任一不满足整组回落默认);themeVarsCss() 产出
  `:root:root{…}` 与 `[data-theme="dark"]:root:root{…}`,靠特异性压过
  tokens 的 :root,不依赖注入顺序
- BaseLayout head 内联 `<style is:inline set:html={themeStyle}>`:构建期
  静态、运行时零 JS 零请求
- scripts/build-perf/contrast-check.mjs 读产物断言正文/强调 × 亮暗四项
  ≥4.5:1,不达标 exit 1;build.sh 守卫挂载([ -f ] 守卫先行)
  实测:默认 12.40/10.53/6.30/7.06 全过;负例 accent #cccccc → 1.61:1 exit 1
  两坑入档:tokens 的 --bg 是 3 位简写需归一化;选 tokens CSS 要用
  `--bg:#` 定义断言(includes('--bg') 会误命中 var(--bg-soft) 引用)
- 后台 theme 单例 17 字段全中文 hint + 取值范围(分组约束写进 hint)

【T4 首页去重与细化】
- 新增 src/components/pages/HomePage.astro 承接全部区块(名片区富文本
  tagline/subtitle/社交、公告、文章列表、最新影像、热力图、项目墙、
  心跳),locale 一统;en 轨吃 postsEn 译题映射、图库链走 /en/gallery/,
  心跳仍仅 zh(零回归)
- 两 index.astro 缩成 4 行薄壳,双份漂移隐患消除
- home.yaml 三字段:projectsLimit(1-99 夹取,缺省 6)、announcement
  (双语 markdown 走 renderRich,空则不渲染)、postsTracks(三轨开关,
  在 HomePage 内过滤而非改 PostList,守边界)

验证: 构建 48 页 exit 0;contrast-check 四项 PASS;E2E 37/37(快照口径);
      实测 theme 变量内联、公告 markdown 粗体渲染、projectsLimit 限流、
      photo 轨关闭生效;验收数据已还原

Spec: 2026-11-02_blog-5g-pages (T3/T4)

Co-authored-by: GLM-5.3
2026-11-02 18:58:11 +08:00
wait4xandGLM-5.3 be87917443 chore(spec): 勾选已收编任务 T1/T2/T5/T7(T3/T4/T6 在途)
Spec: 2026-11-02_blog-5g-pages

Co-authored-by: GLM-5.3
2026-11-02 18:42:36 +08:00
wait4xandGLM-5.3 aaa9cd9274 feat(typography): 全站三层排版——富文本/页面级覆盖/艺术字体类
- 层 1 所有页面文案改富文本:site-content 新增 renderRich(md,{inline})
  复用既有全管线(raw→内容净化→class 白名单→链接卡片);接入
  home.tagline/subtitle、opensource.blurb、about.intro、blocks prose;
  新增 .rich 容器作用域并与 .prose-body 合并(77 处选择器覆盖四类容器)
  坑:renderRich 输出 <p> 塞进 <p class="tagline"> 会被浏览器拆散 →
  行内场景用 {inline:true} 剥外层 <p>
- 层 2 页面级排版覆盖:三单例加 typography{font?,accent?,scale?},读取器
  pageTypography() 走白名单(10 字族 / 18 色类,scale 夹 0.8–1.25,清单外
  忽略);页面容器挂 .page-typo + CSS 变量 --page-font/--page-accent/
  --page-scale,不动全站 tokens;后台三页加「页面排版覆盖」下拉
- 层 3 艺术字体:新增 font-art-kuaile(快乐体专用类,字距微调),白名单与
  可视化面板同步;站酷高端黑/阿里妈妈东方大楷/演示悠然小楷因无 npm 包或
  嵌入条款风险进候补清单
- 默认值清理:about.yaml 的 typography 测试值(整页汇文)清为注释示例——
  整页换字族会带来 694KB 字体成本,默认留空=全站默认排版

验证: 构建 48 页 exit 0;E2E 37/37;三页 page-typo 挂载与变量实测、四页
      (图库/书架/文章/装备)隔离为 0、font-art-kuaile 生效、about 清空后
      无覆盖

Spec: none (排版三层,先于 5g 的任务;T3/T4 另行交付)

Co-authored-by: GLM-5.3
2026-11-02 18:40:32 +08:00
wait4xandGLM-5.3 2f7cbcf7bd feat(about): 块式页面构建器——九类区块 + 三级回落零回归(5g T1/T2)
- schema:siteAbout 增 layout{showIdCard} + blocks(z.discriminatedUnion
  九类:prose/tracks/cards/timeline/quote/figure/linklist/cta/divider,
  图片字段走 Astro image());对未知块型用 z.union([knownUnion,
  z.looseObject({type})]) 复合——已知块严格校验,未知块落宽松分支再由
  访问器 WARN+丢弃(容错与严格校验的张力取舍)
- page-blocks.ts:enabled:false 过滤、未知 type WARN+skip、pickBi 回落
  zh 并 WARN、上限(块≤24/单块文本≤4000,超限截断并 WARN)
- BlockRenderer 统一外壳 `<section class="blk" data-block={type}>`(E2E
  抓手)+ 九个块组件(样式自 AboutPage 原 <style> 搬迁,只吃既有 token)
- AboutPage 三级回落:blocks → intro markdown → 内置默认;legacy
  tracks/build 段整体挪入回退分支(文案逐字一致,零回归)
- 图标白名单 src/lib/icons.ts(12 键,未登记回退首字占位)
- 后台 about 单例四段结构(blocks/layout/intro/typography),块为
  list+types+typeKey,每块带「显示」开关与中文标签

协调报备:车道修了一处跨车道插入事故(T3 把 typography 插进了 intro
的 widget 内导致字段错位),重写为四段正确结构;并发构建擦 dist 的
假失败改用「build→快照 dist→快照起服务跑 E2E」规避(37/37 稳定)

验证: 构建 48 页 exit 0;E2E 37/37(快照口径);九类块顺序与配置一致、
      figure 走 /_astro/*.webp 管线、未知块与外链图被拦并 WARN、
      终版 3 块零回归、en 同构、admin about 四段字段齐全

Spec: 2026-11-02_blog-5g-pages (T1/T2)

Co-authored-by: GLM-5.3
2026-11-02 18:21:33 +08:00
wait4xandGLM-5.3 6096a9dafe feat(opensource): README 四级回落 + 兜底 JSON 8 仓 + 项目可见性告警
- site-content.ts:README 取数加 AbortSignal.timeout(10s)(原无超时,
  慢端点可卡整条构建);四级回落矩阵逐级 console.warn 点名原因:
  新鲜网络(内容有变才回写缓存)→ token 缺位/HTTP 非 2xx/空/超时/异常
  时吃缓存 → 缓存亦缺回落 yaml blurb → 缓存回写失败(构建仓只读)仅告警
- README 图片加固:markdown 层给每个 img 补 loading=lazy + 缺省 alt
  (幂等;只动这两个属性,因为过 rehype-content-sanitize 白名单后其余
  属性会被剥);相对链接不重写,限制写入 JSDoc 与文档
- projects.ts:单源失败点名、双源全失败点名回落 JSON、mode=only 的
  include 未命中双向点名(双源无此仓 / 被 perSource 过滤掉)
- projects-fallback.json:1 → 8 条真实仓(blog / open-earth-data-guide★58
  / AI-CLI / open-earth-data-kit / pi-mesh / pi-board / pi-jobd /
  ai-code-style-kit,字段对齐 RepoCard)

车道:worker-one(T5);收编复验:E2E 37/37、构建 48 页 0 ERROR、
兜底 JSON 8 条断言过

验证: E2E 37/37 PASS;pnpm astro build 48 页 exit 0;fallback 8 条

Spec: 2026-11-02_blog-5g-pages (T5)

Co-authored-by: GLM-5.3
2026-11-02 18:13:47 +08:00
wait4xandGLM-5.3 59eda420b3 perf(fonts): 字体子集化工具 + 汇文明朝单页 -40%(694KB→420KB)
- 新增 scripts/build-perf/subset-fonts.py:读字体 CSS 的 @font-face
  (unicode-range)→ 扫内容收集实际用字 → 逐片交集裁切(pyftsubset
  --flavor=woff2)→ 空片丢弃、重写 src 只留自产 woff2;支持
  --chars-from/--chars-mode(all|headings)/--min-bytes/--merge
- 实测口径修正后结论(关键):汇文明朝原 694KB/页 → 子集 420KB/页
  (-40%,真收益,已接入);站酷快乐体 163KB → 177KB(+9%,字跨片
  分散致片数增加,**不启用**,保留原 @fontsource 源)
- merge 模式(合并单文件)标记实验性:fontTools.merge 对 CJK 切片字表
  冲突敏感,实测产出异常,生产不用
- 三处实现坑入档:①hast 与 @fontsource CSS 一个 face 常带多格式
  (woff2+woff),只替换首个 url 会残留失效引用(首版缺 58 个 .woff)
  → 改整段替换 src;②无 unicode-range 的 face 需按全范围处理,否则
  原样保留同样残留引用;③裸跑 pyftsubset 需 fonttools+brotli

验证: pnpm astro build 48 页 exit 0;E2E 37/37;playwright 实测汇文
      694KB→420KB 且字体栈生效(WF-Hwmc 命中)

Spec: 2026-11-02_blog-5g-pages (T7)

Co-authored-by: GLM-5.3
2026-11-02 18:13:47 +08:00
wait4xandGLM-5.3 740ec470fa chore(spec): 立项 blog-5g——页面后台化(blocks)与字体子集化
按 docs/research/10 方案 E 拆七任务:块式页面构建器(T1/T2)、样式即数据
+对比度闸门(T3)、首页抽 HomePage 去双份(T4)、开源页收尾(T5)、文档
与 hint(T6)、字体子集化工具(T7)。三车道分配:op-gitea(T1/T2)、
op-artalk(T3/T4)、worker-one(T5/T6)、orchestrator(T7+统筹验收)。

Spec: 2026-11-02_blog-5g-pages

Co-authored-by: GLM-5.3
2026-11-02 17:50:19 +08:00
wait4xandGLM-5.3 3822e150a8 feat(typography): 首批 6 字族落地(新增汇文明朝/站酷快乐)+ 13 款候补清单
- 新增两款自托管字族:font-huiwen(汇文明朝体,文风开源 cn-font-split
  339 片切片包,用到页 +535KB、按需拉取实测 19/339 片)|font-kuaile
  (站酷快乐体 +163KB)——补齐「宋明气质」与「轻松标题」两个字体缺口
- 首批 6 族全部 OFL-1.1 且允许 web 嵌入;未用字体的页面增量恒 +0.0KB
  (按页子集化生效)
- 候补 13 款逐款标注「许可证 + web 嵌入」:HarmonyOS Sans/MiSans/OPPO
  Sans/阿里普惠体(免费商用 + 明确允许嵌入,但 npm 无包/整包过大)、
  得意黑/更纱/站酷高端黑/花园明朝(OFL 但无可用 npm 分发)、霞鹜文楷
  Light(+8605KB 否决)、演示悠然小楷("个人免费"常禁 @font-face,风险)
- docs 写入「加字四步流程」(核双授权 → 装包或放 public/fonts → 加类+
  白名单+面板项 → 跑体积账)
- 可视化面板 35 项(10 字族 + 25 颜色);白名单放行 10 字族类
- 资产:public/fonts/hwmc(339 woff2 13MB + fonts.css 162KB)入仓

车道:op-artalk;收编复验:构建 48 页、E2E 37/37、两款新字体 computed
与切片按需加载实测(hwmc/27、52 等 20 个请求)

验证: pnpm astro build 48 页 exit 0;E2E 37/37;playwright 新字体 computed
      + 字体请求观测

Spec: none (字体扩展,车道级交付)

Co-authored-by: GLM-5.3
2026-11-02 17:47:13 +08:00
wait4xandGLM-5.3 26bbb66a72 feat(typography): 16 类语义色板(8 色相×2 档)+ 字体回退升级 + 面板 v2
- 颜色:c-{blue,teal,green,yellow,orange,red,pink,purple} × {标准,soft}
  共 16 类;tokens 明暗各一套值,32 项浏览器实测对比度 4.50–11.73(零失败);
  黄色压到 #7a5900(原色白底仅 ~1.8:1)、黄/粉 soft 压深留余量
- 使用纪律入档:彩色只作用于正文强调,站点外壳(导航/页脚/按钮/背景/边框)
  保持灰阶,单页彩色强调建议 ≤3 处
- 字体:3/4 回退类升级为「自托管优先 + 系统兜底」(font-song→Noto Serif SC、
  font-kai-sys→霞鹜文楷、font-hei-sys→Noto Sans SC,+0.0KB 跨设备一致);
  仿宋无 OFL 自托管包(fandol/朱雀仿宋均无版本)→ 纯系统回退,缺口待决策
- webfont 候选全部按体积账否决并留档:霞鹜文楷 Light 用到页 +8605KB、站酷小薇
  +648KB、得意黑/更纱/汇文无可用 npm 包;根因=@fontsource CJK 切片粒度
- 可视化面板 v2:33 项(8 字族 + 25 颜色),颜色项带 16px 色块预览、字体项带
  示例字形「秋海」并标注自托管/优先/系统;桩 CMS 7 项验证全过
- 白名单放行 25 色类 + 8 字族类;docs 三表(灰阶层级/语义色板含对比度/字体
  含许可证与体积账)更新

车道:op-artalk(改口径后重跑,实测表齐全);收编复验:CSS 16 色类定义与
白名单一致、暗态值在 [data-theme="dark"] 段、字体回退类 computed 生效、
构建 48 页、E2E 37/37

验证: pnpm astro build 48 页 exit 0;E2E 37/37;playwright 抽查彩色类与
      字体回退 computed

Spec: none (排版色板与字体回退,车道级交付)

Co-authored-by: GLM-5.3
2026-11-02 17:14:12 +08:00
482 changed files with 2959 additions and 478 deletions

No files matched your search

@@ -0,0 +1,84 @@
# Phase 5g:页面后台化(blocks 模型)与字体子集化 - 项目范围
<!-- spec-format: 2 -->
## 1. 问题定义
- **项目目标**:①按 docs/research/10 方案 E 把关于页改造成「块式页面构建器」(结构可增删排序)②样式即数据(theme.yaml → 构建期 CSS 变量,含对比度闸门)③首页字段细化并抽 HomePage 消 zh/en 双份 ④开源页收尾(README 缓存/回落/告警)⑤后台文档与 hint 补齐 ⑥字体子集化工具(把艺术字体单页增量从数百 KB 降到数十 KB)
- **目标用户**:站长(希望整页可编辑、能上更多艺术字体)与读者(版式与可读性不退化)
- **核心价值**:内容与结构的编辑权交回后台,同时守住灰阶体系/双语/SEO/构建期零运行时四条红线
## 2. 假设与待确认
### 2.1 已确认事实
- Sveltia 0.252 支持 list 的 types + typeKey(方案 10 §3.4 从本仓 bundle 取证),块模型可行
- 页面文案 markdown 净化(D1/D2)已在 2cab243 落地(P0 完成)
- 字体实测:@fontsource CJK 切片粒度导致单页 +163KB~+8605KB;现有 6 自托管族为基线
### 2.2 关键假设
- 块渲染器受控,破版风险低于整页 HTML 直编(方案 10 §2 对比表)
- 字体子集化(pyftsubset 按页实际用字)能把艺术字体单页增量压到 ≤150KB 阈值内
### 2.3 待确认问题
- [x] 关于页整页可编辑路线 → 用户 2026-11-02 批准方案 E(blocks + theme.yaml + 局部自由区)
- [x] 字体扩充路线 → 先落 6 族,其余按子集化工具就绪后再评估(本包 T7 交付工具)
### 2.4 可选解释与取舍
- 关于页保留 intro markdown 通道作为 compat(blocks 为空时三级回落:blocks → intro → 默认文案)
- 样式即数据只开放白名单旋钮(约 12 项),不开放整份 tokens.css
- 图片只走 Astro image() 管线,禁止块内填外链/绝对路径(schema 层拦)
### 2.5 已证伪路径
- 整页富文本直编(C 方案):XSS 面大、无设计兜底,弃
- 新增 webfont 直接上(不子集化):单页 +535KB~+8605KB,超阈值,弃(改走 T7 工具)
## 3. 功能范围
### 3.1 核心功能(MVP)
- [ ] T1 blocks 骨架:schema(discriminatedUnion)+ page-blocks.ts + BlockRenderer + prose/tracks/divider 三块 + 后台 types 声明
- [ ] T2 块型扩充:cards/timeline/quote/figure/linklist/cta + 图标键白名单 + 图片 image() 管线
- [ ] T3 样式即数据:theme.yaml + theme.ts + BaseLayout 内联变量 + 对比度闸门脚本
- [ ] T4 首页细化与去重:抽 HomePage.astro 消 zh/en 双份 + 字段细化(projectsLimit/announcement 等)
- [ ] T5 开源页收尾:README 缓存策略/超时/回落矩阵 + fallback JSON 回填 + include 未命中 WARN
- [ ] T6 文档与 hint:docs/管理台.md(块编辑用法)+ 写作指南扩充 + admin 全字段中文 hint
- [ ] T7 字体子集化工具:按页用字裁剪艺术字体为小切片 + build.sh 守卫挂载 + 实测对比
### 3.2 扩展功能
- 无
### 3.3 不在范围内
- 换 CMS、页面布局的任意拖拽网格、运行时编辑器
## 4. 最小实现路径
- 最简单可行方案:块类型先用 3 类跑通闭环(prose/tracks/divider),再扩到 9 类;theme 只做 12 个白名单旋钮
- 暂不引入:可视化块预览、块级动画配置
- 不做的抽象:通用页面 DSL(只服务现有页面)
## 5. 技术决策
- 技术栈:Astro 7.4.4 + zod discriminatedUnion + Sveltia list/types + pyftsubset(fonttools)
- 证据协议:evidence: runs
- 本轮允许改动:src/content.config.ts、src/lib/{page-blocks,theme,site-content}.ts、src/components/blocks/*、src/components/pages/{AboutPage,HomePage}.astro、src/pages/{index,en/index,opensource,en/opensource}.astro、src/layouts/BaseLayout.astro、public/admin/*、content/site/*.yaml、scripts/build-perf/*、infra/deploy/build.sh、docs/*、tests/e2e.mjs
- 本轮不应触碰:nginx/ESA、图库灯箱(GalleryPage/PhotoZoom)、Header/Footer 结构
- Git integration branch:`spec/2026-11-02_blog-5g-pages`
### 5.1 验证策略
- 范围级别:package
- 变更对象:页面数据模型与渲染器、后台配置、样式变量生成、字体产物、文档
- 快速检查:`pnpm astro build`(node26)
- 集成检查:`node tests/e2e.mjs`(37 项,本包预期新增块/主题断言)+ content-lint 0/0/0
- 全项目检查:字体体积账(三页增量)+ 视觉复核(glm-5.3-flash:max,块版式与主题变量双态);仅在构建链或 BaseLayout 结构再变更时升级范围
- 升级触发:若 theme 变量波及全局排版(字体字号全站变化)或 blocks 改动触及首页/文章页结构,升级为全站视觉复核与 E2E 全量并记录理由
## 6. 成功标准与验证方式
- [ ] 关于页整页由 yaml blocks 驱动,增删排序生效 -> verify: task_0001
- [ ] 九类块全部可渲染且旧内容零回归(三级回落) -> verify: task_0002
- [ ] theme.yaml 改值 → 产物 CSS 变量变化,对比度闸门拦截不达标 -> verify: task_0003
- [ ] 首页 zh/en 由同一组件渲染且字段可配 -> verify: task_0004
- [ ] 开源页 README 失败矩阵与 fallback 告警可用 -> verify: task_0005
- [ ] 后台文档与 hint 完整 -> verify: task_0006
- [ ] 字体子集化工具把指定字体单页增量压到 ≤150KB -> verify: task_0007
## 7. 风险与约束
- 灰阶体系与双语红线不变;彩色仅正文强调
- 块渲染器未知类型跳过 + WARN,绝不炸构建
- 字体子集化需保证字形覆盖(裁错字会出现豆腐块)→ 断言:子集后页面文本逐字命中
- 多车道并行:共享文件(content.config.ts/admin config/docs)按 boundary 划界,tokens/global 由 orchestrator 或指定单车道
@@ -0,0 +1,42 @@
# Phase 5g:页面后台化(blocks 模型)与字体子集化 - 任务拆解
## 使用规则
- boundary/verify 齐备;verify 只读断言;车道工作树开发禁 git,收编由 orchestrator 统一提交
- 构建一律 `export PATH="$HOME/.local/node/bin:$PATH"`(Node 26)
## 阶段一:实现
- [x] T1 blocks 骨架(关于页块式页面构建器)
- claim: op-gitea
- boundary: src/content.config.ts(siteAbout 加 blocks: z.array(discriminatedUnion))、新增 src/lib/page-blocks.ts(getAboutBlocks:过滤 enabled、未知 type 跳过+WARN、文本 pickBi 回落、块数≤24/单块≤4000 字)、新增 src/components/blocks/{BlockRenderer,Prose,Tracks,Divider}.astro、AboutPage.astro 改由 blocks 驱动(blocks 空→intro markdown→默认文案三级回落)、public/admin/config.yml 加 list+types(typeKey: type)
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS" && grep -q "discriminatedUnion" src/content.config.ts && grep -q "BlockRenderer" src/components/pages/AboutPage.astro && grep -q "typeKey" public/admin/config.yml`
- [x] T2 块型扩充与图片管线
- claim: op-gitea
- boundary: 新增 src/components/blocks/{Cards,Timeline,Quote,Figure,LinkList,Cta}.astro + 图标键白名单(复用 SocialLinks 注册表或新建 icons 注册表)、schema 块型补全(图片字段用 image(),禁止外链字符串)、content/site/assets/ 目录、near docs 图表说明
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS" && ls src/components/blocks/ | grep -qC1 "Cards\|Timeline" && grep -q "image()" src/content.config.ts`
- [x] T3 样式即数据(theme.yaml + 对比度闸门)
- claim: op-artalk
- boundary: 新增 content/site/theme.yaml(12 个白名单旋钮:prose-size/measure/radius/accent/leading 等)、新增 src/lib/theme.ts(getTheme + themeVarsCss 生成 :root:root{…})、BaseLayout.astro head 内联 theme 变量(构建期静态)、新增 scripts/build-perf/contrast-check.mjs(断言 theme 结果下正文对比度 ≥4.5)、public/admin/config.yml 加 theme 单例、build.sh 守卫挂载
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS" && test -f content/site/theme.yaml && node scripts/build-perf/contrast-check.mjs dist`
- [ ] T4 首页细化与去重
- claim: op-artalk
- boundary: 新增 src/components/pages/HomePage.astro(承接现 index.astro 区块逻辑,locale prop)、src/pages/{index,en/index}.astro 改为薄壳、home.yaml 增 projectsLimit / announcement(markdown)/ postsTracks(轨开关细分),site-content 访问器同步
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS" && test -f src/components/pages/HomePage.astro && grep -q "HomePage" src/pages/en/index.astro`
- [x] T5 开源页收尾(缓存/回落/告警)
- claim: worker-one
- boundary: src/lib/site-content.ts(README 缓存策略与超时、回落矩阵)、content/.github-readme.md 与 README HTML 剥离策略(复用 rehype-content-sanitize 已挡危险标签;补: 外链徽章不阻断)、src/data/projects-fallback.json 回填 5-8 条真实仓、projects.ts include 未命中 console.warn
- verify: `E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS" && python3 -c "import json;d=json.load(open('src/data/projects-fallback.json'));assert len(d)>=5"`
- [ ] T6 文档与后台 hint 补齐
- claim: worker-one
- boundary: docs/管理台.md(块编辑怎么用、增删排序、预览限制)、docs/写作指南.md(块字段表/受限类/主题项)、public/admin/config.yml 全字段中文 hint(含 T1-T4 新增字段)
- verify: `test -f docs/管理台.md && grep -q "blocks\|区块" docs/管理台.md && grep -c "hint" public/admin/config.yml | awk '$1>=20'`
- [x] T7 字体子集化工具
- claim: orchestrator
- boundary: 新增 scripts/build-perf/subset-fonts.py(pyftsubset 按指定字体 + 页面实际用字生成子集 woff2 + 重写 fonts.css 指向子集文件)、infra/deploy/build.sh 守卫挂载、docs/写作指南加字流程补"子集化"一节
- verify: `test -f scripts/build-perf/subset-fonts.py && python3 -c "import fontTools; print('fonttools ok')" && E2E_BASE=http://127.0.0.1:18924 node tests/e2e.mjs 2>&1 | grep -q "37/37 PASS"`
+4 -2
View File
@@ -24,8 +24,10 @@ draft: false
<p class="ink-accent-soft">弱强调:秋天的海面像一块被风抚平的铁皮。</p> <p class="ink-accent-soft">弱强调:秋天的海面像一块被风抚平的铁皮。</p>
<p class="font-kai-light">霞鹜文楷 Light(自托管 300):秋天的海面像一块被风抚平的铁皮。</p>
<p class="c-blue">蓝色标准:秋天的海面像一块被风抚平的铁皮。</p> <p class="c-blue">蓝色标准:秋天的海面像一块被风抚平的铁皮。</p>
<p class="c-yellow">黄色标准(已压暗):秋天的海面像一块被风抚平的铁皮。</p> <p class="c-yellow">黄色标准(已压暗):秋天的海面像一块被风抚平的铁皮。</p>
<p class="c-red-soft">红色 soft:秋天的海面像一块被风抚平的铁皮。</p> <p class="c-red-soft">红色 soft:秋天的海面像一块被风抚平的铁皮。</p>
<p class="font-huiwen">汇文明朝体(OFL,切片自托管):秋天的海面像一块被风抚平的铁皮。</p>
<p class="font-kuaile">站酷快乐体(OFL,艺术字):秋天的海面像一块被风抚平的铁皮。</p>
+80 -18
View File
@@ -1,28 +1,90 @@
# 关于页正文(后台「页面文案 → 关于页」可改,markdown 编辑) # 关于页(后台「页面文案 → 关于页」可改)
# intro = 多段 markdown 正文,渲染在「关于」标题下方、名片之前;留空回落默认文案 # ── 一级:blocks 块式正文(按顺序渲染,可拖拽排序;某块 enabled: false 即隐藏)
# 支持的语法:段落、列表、粗斜体、行内代码、链接(渲染走 Astro 的 markdown 管线) # ── 二级:intro 多段 markdown(仅在 blocks 为空时使用)
# ── 三级:内置默认文案(两者都空时兜底,构建永不炸)
layout:
# 名片区(头像/名字/身份语/社交)开关:头像等数据事实源在「主页设置」
showIdCard: true
blocks:
- type: prose
width: narrow
body:
zh: |
<span class="font-huiwen c-red">写作者</span> · 读者 · 快门按压者,拥抱开源。
这里是我三条轨道的公开工作台:**读过的书**留下评注,**写过的代码**进项目墙,**按下的快门**收入图库。
所有内容都有一个明确去处:
- 长文沉淀在文章与书架
- 短句与随手拍落在瞬间
- 影像收进图库,配一点 EXIF 与一句策展
本站本身即开源,构建与发布全链都在仓里。
en: |
<span class="font-huiwen c-red">Writer</span> · Reader · Shutter-presser, embracing open source.
This is the public workbench of my three tracks: **books** that left notes, **code** that reaches the project wall, and **shutters** I pressed that land in the gallery.
Everything has a clear destination:
- Long-form writing goes to posts and the bookshelf
- Short lines and snapshots land in moments
- Images go to the gallery, with a bit of EXIF and one line of curation
This site is itself open source — the full build and deploy pipeline lives in the repo.
- type: tracks
title:
zh: 兴趣版图
en: Interests
items:
- icon: book
name:
zh: 阅读
en: Reading
desc:
zh: 文学与戏剧为主,读到哪写到哪。
en: Mostly literature and drama; I write as I read.
- icon: terminal
name:
zh: 技术
en: Tech
desc:
zh: Linux、容器与自托管,把家里的小机器折腾成顺手的工具。
en: Linux, containers, and self-hosting — turning the little machines at home into handy tools.
- icon: camera
name:
zh: 摄影
en: Photography
desc:
zh: 街拍与山野,随身一台无反,在等光的间隙按快门。
en: Street and landscapes with a mirrorless camera, pressing the shutter while waiting for the light.
- type: prose
title:
zh: 本站如何构建
en: How this site is built
body:
zh: 页面由 Astro 静态生成,不含运行时框架 JS;内容住在自托管 gitea——本地编辑器与 gitea 网页写的是同一个 git 仓库,push 之后由 homelab 自动构建发布;评论走自托管 Artalk。页脚 built from 一行链到本次构建对应的 commit,构建过程透明可追溯。
en: Pages are statically generated by Astro with no runtime framework JS. Content lives on a self-hosted gitea — my local editor and the gitea web UI write to the same git repository, and each push is built and published automatically by the homelab. Comments run on self-hosted Artalk. The built-from line in the footer links to the commit behind this build, keeping the process transparent and traceable.
# ---- 二级回退(blocks 为空时使用)----
intro: intro:
zh: | zh: |
写作者 · 读者 · 快门按压者,拥抱开源。 写作者 · 读者 · 快门按压者,拥抱开源。
这里是我三条轨道的公开工作台:**读过的书**留下评注,**写过的代码**进项目墙,**按下的快门**收入图库。 这里是我三条轨道的公开工作台:**读过的书**留下评注,**写过的代码**进项目墙,**按下的快门**收入图库。
所有内容都有一个明确去处:
- 长文沉淀在文章与书架
- 短句与随手拍落在瞬间
- 影像收进图库,配一点 EXIF 与一句策展
本站本身即开源,构建与发布全链都在仓里。
en: | en: |
Writer · Reader · Shutter-presser, embracing open source. Writer · Reader · Shutter-presser, embracing open source.
This is the public workbench of my three tracks: **books** that left notes, **code** that reaches the project wall, and **shutters** I pressed that land in the gallery. This is the public workbench of my three tracks: **books** that left notes, **code** that reaches the project wall, and **shutters** I pressed that land in the gallery.
Everything has a clear destination: # 页面级排版覆盖(层 2)
# 页面级排版覆盖(后台「关于页 → 页面排版覆盖」可改):
- Long-form writing goes to posts and the bookshelf # 整页换字族会带来字体体积成本(如 font-huiwen 整页 ≈694KB),默认留空=全站默认排版
- Short lines and snapshots land in moments # typography:
- Images go to the gallery, with a bit of EXIF and one line of curation # font: font-huiwen # 字族:font-serif/font-sans/font-kai/font-huiwen/font-kuaile…
# accent: c-red # 强调色类:c-blue/c-red/…(外壳仍灰阶)
This site is itself open source — the full build and deploy pipeline lives in the repo. # scale: 1.05 # 字号缩放 0.8–1.25
+37
View File
@@ -0,0 +1,37 @@
# 主题旋钮(样式即数据 · 层 4):只放白名单内的 9 组旋钮。
# 红线:--measure(版心)与断点/纸墨/code 配色不在此列——它们牵动版面结构与分轨身份。
# 全部留空 = 沿用 src/styles/tokens.css 的既有值(零配置零变化)。
# 钳制与单调性在 src/lib/theme.ts 里做:越界值被夹取而非报错,构建不会因主题配置失败。
# ① 强调色(明/暗两值;构建期闸门校验正文对比度 ≥4.5,不达标回落默认)
accent_light: '#3454d1'
accent_dark: '#8ea2ff'
# ② 图片圆角(4–16;08 文档裁定上限 14)
radius_img: 10
# ③ 标题字号(22–32;必须 h1 > h2 > h3,否则整体回落默认)
text_h1: 27
text_h2: 22
text_h3: 19
# ④ 正文字号(16–22)
prose_size_reading: 20
prose_size_tech: 19
# ⑤ 正文行高(1.5–2.0)
leading_reading: 1.75
leading_tech: 1.7
# ⑥ 间距梯度(8–80;必须严格递增 3 < 4 < 5 < 6,否则回落默认)
space_3: 16
space_4: 24
space_5: 40
space_6: 64
# ⑦ 段落流距(0–2em)
flow_p: 0.9
flow_h2_post: 0.6
# ⑧ 过渡时长(150–600ms)
dur: 350
+85 -12
View File
@@ -88,7 +88,7 @@ https://astro.build/
正文里可用 `<span class="…">` / `<p class="…">` 做**受限**排版。构建期有白名单过滤:**只放行下表类名,其它 class 与 `style`、`on*` 属性一律被剥离**(防注入 + 保护灰阶骨架)。 正文里可用 `<span class="…">` / `<p class="…">` 做**受限**排版。构建期有白名单过滤:**只放行下表类名,其它 class 与 `style`、`on*` 属性一律被剥离**(防注入 + 保护灰阶骨架)。
### 颜色类(全部实测亮暗对比度 ≥4.5:1) ### 灰阶层级类(全部实测亮暗对比度 ≥4.5:1)
| 类名 | 作用 | | 类名 | 作用 |
|---|---| |---|---|
@@ -100,18 +100,36 @@ https://astro.build/
| `ink-accent` | 强调(链接色,少量点缀) | | `ink-accent` | 强调(链接色,少量点缀) |
| `ink-accent-soft` | 弱强调(比强调淡一档) | | `ink-accent-soft` | 弱强调(比强调淡一档) |
### 语义色板(8 色相 × 标准/soft;亮暗各自取值,实测全部 ≥4.5:1)
| 色相 | 标准类 | soft 类 | 亮态对比度(标准 / soft) | 暗态(标准 / soft) |
|---|---|---|---|---|
| 蓝 | `c-blue` | `c-blue-soft` | 6.46 / 4.58 | 7.70 / 4.61 |
| 青 | `c-teal` | `c-teal-soft` | 5.00 / 4.57 | 9.77 / 4.52 |
| 绿 | `c-green` | `c-green-soft` | 5.38 / 4.67 | 10.38 / 4.67 |
| 黄 | `c-yellow` | `c-yellow-soft` | 6.45 / 4.57 | 11.73 / 4.52 |
| 橙 | `c-orange` | `c-orange-soft` | 6.26 / 4.65 | 9.44 / 4.58 |
| 红 | `c-red` | `c-red-soft` | 6.68 / 4.55 | 7.51 / 4.50 |
| 粉 | `c-pink` | `c-pink-soft` | 6.96 / 4.73 | 8.87 / 4.52 |
| 紫 | `c-purple` | `c-purple-soft` | 7.46 / 4.53 | 8.25 / 4.64 |
- 黄色在亮态被压暗到 `#7a5900`(原色在白底只有 ~1.8:1),这是**必须**的:彩色文字要能读
- `soft` 档是"阈值内最淡值",适合次要强调;标准档用于关键强调
> **使用纪律(重要)**:彩色**只作用于文章正文的强调文字**。站点外壳(导航、页脚、按钮、背景、卡片边框)一律保持灰阶——整站不因正文用色而花掉。写作时一个页面里的彩色强调建议不超过 3 处。
### 字体类 ### 字体类
| 类名 | 作用 | 备注 | | 类名 | 作用 | 字体 | 许可证 | 加载 |
|---|---|---| |---|---|---|---|---|
| `font-serif` | 思源宋(书评正文族) | 自托管 | | `font-serif` | 书评正文族 | Noto Serif SC | OFL-1.1 | 自托管 |
| `font-kai` | 霞鹜文楷(金句族) | 自托管 | | `font-kai` | 金句族 | 霞鹜文楷 Screen | OFL-1.1 | 自托管 |
| `font-sans` | 思源黑(UI/技术族) | 自托管 | | `font-sans` | UI/技术族 | Noto Sans SC | OFL-1.1 | 自托管 |
| `font-mono` | JetBrains Mono(标签/数值) | 自托管 | | `font-mono` | 标签/数值 | JetBrains Mono | OFL-1.1 | 自托管 |
| `font-song` | 宋体族 | ⚠️ 系统字体,**依赖访客设备** | | `font-song` | 宋体族 | Noto Serif SC 优先,系统宋体兜底 | OFL-1.1 | **自托管优先** |
| `font-kai-sys` | 楷体族 | ⚠️ 系统字体 | | `font-kai-sys` | 楷体族 | 霞鹜文楷优先,系统楷体兜底 | OFL-1.1 | **自托管优先** |
| `font-hei-sys` | 黑体族 | ⚠️ 系统字体 | | `font-hei-sys` | 黑体族 | Noto Sans SC 优先,系统黑体兜底 | OFL-1.1 | **自托管优先** |
| `font-fangsong` | 仿宋族 | ⚠️ 系统字体 | | `font-fangsong` | 仿宋族 | FangSong/STFangsong | — | ⚠️ **纯系统回退**(暂无 OFL 自托管仿宋包) |
### 文学排版类(段落级) ### 文学排版类(段落级)
@@ -121,7 +139,36 @@ https://astro.build/
| `quote-line` | 金句块(左侧竖线 + 文楷) | | `quote-line` | 金句块(左侧竖线 + 文楷) |
| `fig-cap` | 图注(居中小字) | | `fig-cap` | 图注(居中小字) |
> **系统字体类的前提**:`-sys` / `font-song` / `font-fangsong` 不下载任何字体文件,命中访客本机字体(macOS/iOS 有宋楷黑、Windows 有宋黑仿宋,Android 常缺楷仿宋)。**跨设备不保证一致**——要稳定请用自托管四族。 ### 首批字体(已落地,6 族 + 3 档自托管优先)
| 类名 | 字体 | 许可证 | webfont 嵌入 | 用到该字体的页面增量 |
|---|---|---|---|---|
| `font-serif` | Noto Serif SC | OFL-1.1 | ✅ OFL 允许 | 基线内 |
| `font-sans` | Noto Sans SC | OFL-1.1 | ✅ | 基线内 |
| `font-kai` | 霞鹜文楷 Screen | OFL-1.1 | ✅ | 基线内 |
| `font-mono` | JetBrains Mono | OFL-1.1 | ✅ | 基线内 |
| `font-huiwen` | 汇文明朝体(文风切片包 339 片) | OFL-1.1 | ✅ | **535KB** |
| `font-kuaile` | 站酷快乐体 | OFL-1.1 | ✅ | **163KB** |
| `font-song` / `font-kai-sys` / `font-hei-sys` | 上述自托管字体优先,系统字族兜底 | OFL-1.1 | ✅ | **+0KB** |
体积账结论(2026-11-02 实测,本地无 gzip 的相对值):**未使用某字体的页面,字体字节增量恒为 0**(构建期按页子集化 + unicode-range 按需拉取)。汇文采用文风开源的 cn-font-split 切片包(339 片),实测只拉用到字符的 19 片。
### 候补字体清单(未落地,加一款约"一条命令 + 一次体积账")
| 字体 | 许可证 | 允许 webfont 嵌入 | 未落地原因 |
|---|---|---|---|
| HarmonyOS Sans | 华为字体许可(免费商用) | ✅ 明文允许"嵌入、捆绑、重新分发",需标注 | npm 无包,官网整包 52MB |
| MiSans | 小米字体许可(免费商用) | ✅ 官方 FAQ 明确 "embedded fonts: Yes",需注明 | npm 无包 |
| OPPO Sans | 免费授权(含商用) | ✅(附带"不向他方提供下载渠道"限制) | npm 无包 |
| 阿里巴巴普惠体 | 免费商用全场景 | ✅ | npm 无包,9 字重体积巨大 |
| 得意黑 Smiley Sans | OFL-1.1 | ✅ | 仅 GitHub release 整包,直链在当前网络不可达 |
| 更纱黑体 Sarasa Gothic / Sarasa Mono | OFL-1.1 | ✅ | npm 包为空壳(需 cli 自生成切片) |
| 花园明朝 HanaMinA/B | IPA Font License | ✅(允许嵌入) | 无 npm 包,仅兜底用 |
| 站酷高端黑 / 站酷快乐体(已落) | OFL-1.1 | ✅ | 高端黑无 @fontsource 版本 |
| 阿里妈妈东方大楷 / 演示悠然小楷 | 免费商用 / 个人免费 | ⚠️ 需逐款核嵌入条款 | 无包;"个人免费"字体常禁 @font-face |
| 霞鹜文楷 Light/Bright | OFL-1.1 | ✅ | 实测 **+8605KB**(CJK 切片粒度所致) |
> 加新字体的流程:①核许可证与 **web 嵌入条款**(很多"个人免费"字体明文禁 @font-face,只能进系统回退)②npm 装或放入 `public/fonts/<family>/`(优选 cn-font-split 类切片包)③`global.css` 加类 + `rehype-class-whitelist` 放行 + `public/admin/typography-classes.js` 加一项 ④跑体积账,超 150KB/页 的要评估是否值得。
```markdown ```markdown
<span class="ink-muted">次要说明</span> <span class="ink-muted">次要说明</span>
@@ -138,6 +185,32 @@ https://astro.build/
后台编辑器里:**选中文字 → Insert 菜单 → 「排版(字体/颜色)」→ 面板里点一个类**,文字即被 `<span class="…">` 包住;点已套用的片段可改类,Backspace 可整体删除。 后台编辑器里:**选中文字 → Insert 菜单 → 「排版(字体/颜色)」→ 面板里点一个类**,文字即被 `<span class="…">` 包住;点已套用的片段可改类,Backspace 可整体删除。
面板里的两类与上表一一对应(字体 8 / 颜色 9),类清单单一事实源在 `public/admin/typography-classes.js`。 面板里的两类与上表一一对应(字体 8 / 颜色 9),类清单单一事实源在 `public/admin/typography-classes.js`。
## 三层排版架构(全站,不只文章正文)
**层 1 · 页面文案富文本**:后台「页面文案」里的 tagline / subtitle / blurb / blurb_after / intro / 关于页 blocks 的 prose 块,全部走同一条富文本管线(markdown + raw + 内容净化 + class 白名单 + 链接卡片)。可以直接写:
```yaml
tagline:
zh: '<span class="font-kai c-blue">写作者</span> · 读者 · 快门按压者,拥抱开源。'
```
渲染入口唯一:`src/lib/site-content.ts` 的 `renderRich()`(行内场景传 `{ inline: true }` 自动剥外层 `<p>`,否则 `<p>` 套 `<p>` 会被浏览器拆散)。
**不开放**:导航/页脚/按钮等硬编码界面外壳——那是 UI 不是内容。
**层 2 · 页面级排版覆盖**:每个页面单例(`content/site/*.yaml`)可加:
```yaml
typography:
font: font-kai # 本页正文默认字体类(可选)
accent: c-blue # 本页强调色类(可选)
scale: 1.1 # 字号缩放 0.8–1.25(可选)
```
实现是在该页内容容器上挂 `.page-typo` + CSS 变量(`--page-font / --page-accent / --page-scale`),**不写全站 tokens**——其它页面零影响(已在图库/书架/文章/装备页验证隔离)。白名单外的类名会被忽略。
后台入口:「页面文案 → 各自页面 → 页面排版覆盖」。
**层 3 · 艺术字体**:`font-art-kuaile`(站酷快乐体,艺术字专用类)。⚠️ **仅用于标题/短句**:艺术字字形大,CJK 切片体积高(实测用到页 +163KB),长段使用会显著抬高单页字体成本。
## 发布提醒 ## 发布提醒
- push main → webhook 自动构建发布(约 10 秒) - push main → webhook 自动构建发布(约 10 秒)
+1
View File
@@ -33,6 +33,7 @@ pnpm astro build
# 按页字体 CSS 子集化(车道 A 任务 1):裁 @font-face 声明数,构建仓与本地同链; # 按页字体 CSS 子集化(车道 A 任务 1):裁 @font-face 声明数,构建仓与本地同链;
# 存在性守卫:新文件未推送到 main 前构建仓无此脚本,绝不让发布链因车道在途文件断掉 # 存在性守卫:新文件未推送到 main 前构建仓无此脚本,绝不让发布链因车道在途文件断掉
[ -f scripts/build-perf/subset-font-css.mjs ] && node scripts/build-perf/subset-font-css.mjs dist || echo "[build] subset 脚本未就绪,跳过" [ -f scripts/build-perf/subset-font-css.mjs ] && node scripts/build-perf/subset-font-css.mjs dist || echo "[build] subset 脚本未就绪,跳过"
[ -f scripts/build-perf/contrast-check.mjs ] && node scripts/build-perf/contrast-check.mjs dist || echo "[build] contrast 脚本未就绪,跳过"
# 首页阻塞 CSS 内联(车道 D):守卫先行同上——脚本未到 main 前绝不炸链 # 首页阻塞 CSS 内联(车道 D):守卫先行同上——脚本未到 main 前绝不炸链
[ -f scripts/build-perf/gen-sw-precache.mjs ] && node scripts/build-perf/gen-sw-precache.mjs dist || echo "[build] sw-precache 生成器未就绪,跳过" [ -f scripts/build-perf/gen-sw-precache.mjs ] && node scripts/build-perf/gen-sw-precache.mjs dist || echo "[build] sw-precache 生成器未就绪,跳过"
[ -f scripts/build-perf/inline-home-css.mjs ] && node scripts/build-perf/inline-home-css.mjs || echo "[build] inline-home 脚本未就绪,跳过" [ -f scripts/build-perf/inline-home-css.mjs ] && node scripts/build-perf/inline-home-css.mjs || echo "[build] inline-home 脚本未就绪,跳过"
+1
View File
@@ -16,6 +16,7 @@
"@fontsource-variable/noto-serif-sc": "^5.3.1", "@fontsource-variable/noto-serif-sc": "^5.3.1",
"@fontsource/jetbrains-mono": "^5.3.0", "@fontsource/jetbrains-mono": "^5.3.0",
"@fontsource/noto-sans-sc": "^5.3.1", "@fontsource/noto-sans-sc": "^5.3.1",
"@fontsource/zcool-kuaile": "^5.3.0",
"astro": "^7.4.4", "astro": "^7.4.4",
"astro-pagefind": "^2.0.1", "astro-pagefind": "^2.0.1",
"exifr": "^7.1.3", "exifr": "^7.1.3",
+8
View File
@@ -26,6 +26,9 @@ importers:
'@fontsource/noto-sans-sc': '@fontsource/noto-sans-sc':
specifier: ^5.3.1 specifier: ^5.3.1
version: 5.3.1 version: 5.3.1
'@fontsource/zcool-kuaile':
specifier: ^5.3.0
version: 5.3.0
astro: astro:
specifier: ^7.4.4 specifier: ^7.4.4
version: 7.4.4(@astrojs/markdown-remark@7.3.5)(@types/node@24.19.2)(yaml@2.9.1) version: 7.4.4(@astrojs/markdown-remark@7.3.5)(@types/node@24.19.2)(yaml@2.9.1)
@@ -265,6 +268,9 @@ packages:
'@fontsource/noto-serif-sc@5.3.1': '@fontsource/noto-serif-sc@5.3.1':
resolution: {integrity: sha512-nIK2HyNGcUi2TGwIfrk2MItkh0O3Y5+TI8iPfPSzoNY6uyXYfQ9qqch8czk3aa1VhV4EU52uwCYCe1UDKJELpg==} resolution: {integrity: sha512-nIK2HyNGcUi2TGwIfrk2MItkh0O3Y5+TI8iPfPSzoNY6uyXYfQ9qqch8czk3aa1VhV4EU52uwCYCe1UDKJELpg==}
'@fontsource/zcool-kuaile@5.3.0':
resolution: {integrity: sha512-jn4vpQ6QSVEckUn7uk5a9XcZMxEksTRqljmeE1VKRGMjGd6VoXdMyLGSdnTXSVwZLCCEHaIcm/2cdQuwvasJ9w==}
'@img/colour@1.1.0': '@img/colour@1.1.0':
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -2113,6 +2119,8 @@ snapshots:
'@fontsource/noto-serif-sc@5.3.1': {} '@fontsource/noto-serif-sc@5.3.1': {}
'@fontsource/zcool-kuaile@5.3.0': {}
'@img/colour@1.1.0': {} '@img/colour@1.1.0': {}
'@img/sharp-darwin-arm64@0.35.5': '@img/sharp-darwin-arm64@0.35.5':
+334 -2
View File
@@ -58,6 +58,29 @@ collections:
- { label: 草稿, name: draft, widget: boolean, required: false, default: false } - { label: 草稿, name: draft, widget: boolean, required: false, default: false }
- { label: 正文, name: body, widget: markdown } - { label: 正文, name: body, widget: markdown }
# 主题旋钮(样式即数据 · 层 4):只放白名单内的 9 组,越界值会被构建期钳制
- name: site_theme
label: 主题设置
file: content/site/theme.yaml
fields:
- { label: 强调色(亮), name: accent_light, widget: color, required: false, hint: '亮色模式强调色,如 #3454d1;构建期校验正文对比度 ≥4.5' }
- { label: 强调色(暗), name: accent_dark, widget: color, required: false, hint: '暗色模式强调色,如 #8ea2ff' }
- { label: 图片圆角, name: radius_img, widget: number, required: false, value_type: int, min: 4, max: 16, hint: '范围 4–16(建议 ≤14)' }
- { label: 一级标题字号, name: text_h1, widget: number, required: false, value_type: int, min: 22, max: 32, hint: '22–32,且必须大于 h2' }
- { label: 二级标题字号, name: text_h2, widget: number, required: false, value_type: int, min: 22, max: 32, hint: '22–32,必须小于 h1、大于 h3' }
- { label: 三级标题字号, name: text_h3, widget: number, required: false, value_type: int, min: 16, max: 32, hint: '16–32,必须小于 h2' }
- { label: 书评正文字号, name: prose_size_reading, widget: number, required: false, value_type: int, min: 16, max: 22, hint: '16–22' }
- { label: 技术文正文字号, name: prose_size_tech, widget: number, required: false, value_type: int, min: 16, max: 22, hint: '16–22' }
- { label: 书评行高, name: leading_reading, widget: number, required: false, value_type: float, min: 1.5, max: 2.0, step: 0.05, hint: '1.5–2.0' }
- { label: 技术文行高, name: leading_tech, widget: number, required: false, value_type: float, min: 1.5, max: 2.0, step: 0.05, hint: '1.5–2.0' }
- { label: 间距 3, name: space_3, widget: number, required: false, value_type: int, min: 8, max: 80, hint: '8–80,必须 < 间距 4' }
- { label: 间距 4, name: space_4, widget: number, required: false, value_type: int, min: 8, max: 80, hint: '8–80,必须 > 间距 3 且 < 间距 5' }
- { label: 间距 5, name: space_5, widget: number, required: false, value_type: int, min: 8, max: 80, hint: '8–80,必须 > 间距 4 且 < 间距 6' }
- { label: 间距 6, name: space_6, widget: number, required: false, value_type: int, min: 8, max: 80, hint: '8–80,必须 > 间距 5' }
- { label: 段间距, name: flow_p, widget: number, required: false, value_type: float, min: 0, max: 2, step: 0.05, hint: '0–2(em)' }
- { label: 标题下间距, name: flow_h2_post, widget: number, required: false, value_type: float, min: 0, max: 2, step: 0.05, hint: '0–2(em)' }
- { label: 过渡时长, name: dur, widget: number, required: false, value_type: int, min: 150, max: 600, hint: '150–600(毫秒)' }
# 瞬间:随手发(text/image 至少其一——Sveltia 无法表达条件必填,两者都设可选, # 瞬间:随手发(text/image 至少其一——Sveltia 无法表达条件必填,两者都设可选,
# 保存前自查;pubDate 必填)。图片走图库媒体库(content/photos 同源可复用) # 保存前自查;pubDate 必填)。图片走图库媒体库(content/photos 同源可复用)
- name: moments - name: moments
@@ -119,10 +142,45 @@ collections:
fields: fields:
- label: 身份语 - label: 身份语
name: tagline name: tagline
- label: 页面排版覆盖(可选)
name: typography
widget: object
required: false
hint: 本页正文默认字体 / 强调色 / 字号缩放(0.8–1.25);不填用全站默认,只影响本页
fields:
- label: 正文字体类
name: font
widget: select
required: false
options: [font-serif, font-kai, font-sans, font-mono, font-song, font-kai-sys, font-hei-sys, font-fangsong, font-huiwen, font-kuaile]
- label: 强调色类
name: accent
widget: select
required: false
options: [ink-accent, ink-accent-soft, c-blue, c-blue-soft, c-teal, c-teal-soft, c-green, c-green-soft, c-yellow, c-yellow-soft, c-orange, c-orange-soft, c-red, c-red-soft, c-pink, c-pink-soft, c-purple, c-purple-soft]
- { label: 字号缩放, name: scale, widget: number, required: false, value_type: float, min: 0.8, max: 1.25, step: 0.05 }
widget: object widget: object
fields: fields:
- { label: 中文, name: zh, widget: string, required: false } - { label: 中文, name: zh, widget: string, required: false }
- { label: 英文, name: en, widget: string, required: false } - { label: 英文, name: en, widget: string, required: false }
- label: 公告(可选,markdown)
name: announcement
widget: object
required: false
hint: 渲染在名片区下方;留空则不显示公告块
fields:
- { label: 中文, name: zh, widget: markdown, required: false }
- { label: 英文, name: en, widget: markdown, required: false }
- { label: 项目墙条数, name: projectsLimit, widget: number, required: false, value_type: int, min: 1, max: 99, hint: '首页项目墙显示几条(1–99,缺省 6)' }
- label: 轨细分开关(首页文章列表)
name: postsTracks
widget: object
required: false
hint: 关掉某轨则该轨文章不在首页列出(全开=现状)
fields:
- { label: 阅读轨, name: reading, widget: boolean, required: false, default: true }
- { label: 技术轨, name: tech, widget: boolean, required: false, default: true }
- { label: 照片轨, name: photo, widget: boolean, required: false, default: true }
- label: 副标题(可选,markdown 短文本) - label: 副标题(可选,markdown 短文本)
name: subtitle name: subtitle
widget: object widget: object
@@ -183,20 +241,294 @@ collections:
- name: about - name: about
label: 关于页 label: 关于页
file: content/site/about.yaml file: content/site/about.yaml
# 块内图片上传落 content/site/assets/,yaml 写 ./assets/xxx(Astro 图片管线)
media_folder: content/site/assets
public_folder: ./assets
fields: fields:
- label: 正文(markdown,多段) - label: 页面块(拖拽排序;每块可折叠)
name: blocks
widget: list
typeKey: type
required: false
summary: '{{fields.type}}'
hint: '点「+ 添加」选块型。关掉某块用该块内的「显示」开关'
types:
- label: 正文(markdown)
name: prose
widget: object
fields:
- { label: 显示, name: enabled, widget: boolean, required: false, default: true }
- label: 小标题(可选)
name: title
widget: object
required: false
fields:
- { label: 中文, name: zh, widget: string, required: false }
- { label: 英文, name: en, widget: string, required: false }
- label: 正文(markdown)
name: body
widget: object
fields:
- { label: 中文, name: zh, widget: markdown, required: false }
- { label: 英文, name: en, widget: markdown, required: false }
- label: 宽度
name: width
widget: select
required: false
default: narrow
options: [{ label: 版心宽度, value: narrow }, { label: 通栏, value: full }]
- label: 版图(图标+名称+描述)
name: tracks
widget: object
fields:
- { label: 显示, name: enabled, widget: boolean, required: false, default: true }
- label: 标题
name: title
widget: object
fields:
- { label: 中文, name: zh, widget: string, required: false }
- { label: 英文, name: en, widget: string, required: false }
- label: 条目
name: items
widget: list
summary: '{{fields.name.zh}}'
fields:
- label: 图标
name: icon
widget: select
required: false
options: [book, terminal, camera, code, git, rss, email, github, bilibili, x, star, link]
hint: 白名单外或留空 → 显示首字占位
- label: 名称
name: name
widget: object
fields:
- { label: 中文, name: zh, widget: string, required: false }
- { label: 英文, name: en, widget: string, required: false }
- label: 描述
name: desc
widget: object
required: false
fields:
- { label: 中文, name: zh, widget: text, required: false }
- { label: 英文, name: en, widget: text, required: false }
- label: 卡片(2/3 列)
name: cards
widget: object
fields:
- { label: 显示, name: enabled, widget: boolean, required: false, default: true }
- label: 标题(可选)
name: title
widget: object
required: false
fields:
- { label: 中文, name: zh, widget: string, required: false }
- { label: 英文, name: en, widget: string, required: false }
- { label: 列数, name: columns, widget: number, required: false, default: 3, min: 2, max: 3, value_type: int }
- label: 卡片条目
name: items
widget: list
summary: '{{fields.title.zh}}'
fields:
- label: 标题
name: title
widget: object
fields:
- { label: 中文, name: zh, widget: string, required: false }
- { label: 英文, name: en, widget: string, required: false }
- label: 描述
name: desc
widget: object
required: false
fields:
- { label: 中文, name: zh, widget: text, required: false }
- { label: 英文, name: en, widget: text, required: false }
- { label: 链接(可选), name: href, widget: string, required: false }
- label: 角标(可选)
name: tag
widget: object
required: false
fields:
- { label: 中文, name: zh, widget: string, required: false }
- { label: 英文, name: en, widget: string, required: false }
- label: 时间线
name: timeline
widget: object
fields:
- { label: 显示, name: enabled, widget: boolean, required: false, default: true }
- label: 标题(可选)
name: title
widget: object
required: false
fields:
- { label: 中文, name: zh, widget: string, required: false }
- { label: 英文, name: en, widget: string, required: false }
- label: 条目
name: items
widget: list
summary: '{{fields.time}} · {{fields.title.zh}}'
fields:
- { label: 时间, name: time, widget: string, hint: '自由文本,如 2024 — 2026' }
- label: 标题
name: title
widget: object
fields:
- { label: 中文, name: zh, widget: string, required: false }
- { label: 英文, name: en, widget: string, required: false }
- label: 描述(可选)
name: desc
widget: object
required: false
fields:
- { label: 中文, name: zh, widget: text, required: false }
- { label: 英文, name: en, widget: text, required: false }
- label: 引文
name: quote
widget: object
fields:
- { label: 显示, name: enabled, widget: boolean, required: false, default: true }
- label: 引文
name: text
widget: object
fields:
- { label: 中文, name: zh, widget: text, required: false }
- { label: 英文, name: en, widget: text, required: false }
- label: 出处(可选)
name: cite
widget: object
required: false
fields:
- { label: 中文, name: zh, widget: string, required: false }
- { label: 英文, name: en, widget: string, required: false }
- label: 图片
name: figure
widget: object
fields:
- { label: 显示, name: enabled, widget: boolean, required: false, default: true }
- { label: 图片(必填,请用后台上传), name: image, widget: image, hint: '禁止填外链:只能上传(落 content/site/assets/),走 Astro 图片管线' }
- { label: 替代文本(无障碍), name: alt, widget: string, required: false }
- label: 图注(可选)
name: caption
widget: object
required: false
fields:
- { label: 中文, name: zh, widget: string, required: false }
- { label: 英文, name: en, widget: string, required: false }
- label: 链接清单
name: linklist
widget: object
fields:
- { label: 显示, name: enabled, widget: boolean, required: false, default: true }
- label: 标题(可选)
name: title
widget: object
required: false
fields:
- { label: 中文, name: zh, widget: string, required: false }
- { label: 英文, name: en, widget: string, required: false }
- label: 链接条目
name: items
widget: list
summary: '{{fields.label.zh}} → {{fields.href}}'
fields:
- label: 名称
name: label
widget: object
fields:
- { label: 中文, name: zh, widget: string, required: false }
- { label: 英文, name: en, widget: string, required: false }
- { label: 链接, name: href, widget: string }
- label: 备注(可选)
name: note
widget: object
required: false
fields:
- { label: 中文, name: zh, widget: string, required: false }
- { label: 英文, name: en, widget: string, required: false }
- label: 行动按钮
name: cta
widget: object
fields:
- { label: 显示, name: enabled, widget: boolean, required: false, default: true }
- label: 说明文字
name: text
widget: object
fields:
- { label: 中文, name: zh, widget: string, required: false }
- { label: 英文, name: en, widget: string, required: false }
- { label: 按钮链接, name: href, widget: string }
- label: 按钮文字
name: label
widget: object
fields:
- { label: 中文, name: zh, widget: string, required: false }
- { label: 英文, name: en, widget: string, required: false }
- label: 分隔
name: divider
widget: object
fields:
- { label: 显示, name: enabled, widget: boolean, required: false, default: true }
- label: 样式
name: style
widget: select
required: false
default: line
options: [{ label: 细分隔线, value: line }, { label: 纯留白, value: space }]
- label: 名片区开关
name: layout
widget: object
required: false
hint: 关掉则不显示头像/名字/社交(它们的数据在「主页设置」)
fields:
- { label: 显示名片区, name: showIdCard, widget: boolean, required: false, default: true }
- label: 正文回退(blocks 为空时用,markdown,多段)
name: intro name: intro
widget: object widget: object
hint: 渲染在「关于」标题下方、名片之上;支持段落/列表/粗斜体/链接;留空用默认文案 required: false
hint: 仅在「页面块」为空时渲染;与块同时存在时以块为准
fields: fields:
- { label: 中文, name: zh, widget: markdown, required: false } - { label: 中文, name: zh, widget: markdown, required: false }
- { label: 英文, name: en, widget: markdown, required: false } - { label: 英文, name: en, widget: markdown, required: false }
- label: 页面排版覆盖(可选)
name: typography
widget: object
required: false
hint: 本页正文默认字体 / 强调色 / 字号缩放(0.8–1.25);不填用全站默认,只影响本页
fields:
- label: 正文字体类
name: font
widget: select
required: false
options: [font-serif, font-kai, font-sans, font-mono, font-song, font-kai-sys, font-hei-sys, font-fangsong, font-huiwen, font-kuaile]
- label: 强调色类
name: accent
widget: select
required: false
options: [ink-accent, ink-accent-soft, c-blue, c-blue-soft, c-teal, c-teal-soft, c-green, c-green-soft, c-yellow, c-yellow-soft, c-orange, c-orange-soft, c-red, c-red-soft, c-pink, c-pink-soft, c-purple, c-purple-soft]
- { label: 字号缩放, name: scale, widget: number, required: false, value_type: float, min: 0.8, max: 1.25, step: 0.05 }
- name: opensource - name: opensource
label: 开源页 label: 开源页
file: content/site/opensource.yaml file: content/site/opensource.yaml
fields: fields:
- label: 叙述(链接前) - label: 叙述(链接前)
name: blurb name: blurb
- label: 页面排版覆盖(可选)
name: typography
widget: object
required: false
hint: 本页正文默认字体 / 强调色 / 字号缩放(0.8–1.25);不填用全站默认,只影响本页
fields:
- label: 正文字体类
name: font
widget: select
required: false
options: [font-serif, font-kai, font-sans, font-mono, font-song, font-kai-sys, font-hei-sys, font-fangsong, font-huiwen, font-kuaile]
- label: 强调色类
name: accent
widget: select
required: false
options: [ink-accent, ink-accent-soft, c-blue, c-blue-soft, c-teal, c-teal-soft, c-green, c-green-soft, c-yellow, c-yellow-soft, c-orange, c-orange-soft, c-red, c-red-soft, c-pink, c-pink-soft, c-purple, c-purple-soft]
- { label: 字号缩放, name: scale, widget: number, required: false, value_type: float, min: 0.8, max: 1.25, step: 0.05 }
widget: object widget: object
fields: fields:
- { label: 中文, name: zh, widget: string, required: false } - { label: 中文, name: zh, widget: string, required: false }
+40 -18
View File
@@ -1,28 +1,50 @@
/** /**
* 排版类清单 —— 单一事实源(后台编辑器面板与文档共用)。 * 排版类清单 —— 单一事实源(后台编辑器面板与文档共用)。
* 与 src/styles/global.css 的「受限排版类」段、src/lib/rehype-class-whitelist.ts 的白名单三处保持一致: * 与 src/styles/global.css 的「受限排版类/语义色板」段、src/lib/rehype-class-whitelist.ts 的白名单三处保持一致:
* 加类时三处同改,否则构建期白名单会把新类剥掉。 * 加类时三处同改,否则构建期白名单会把新类剥掉。
*
* swatch / family 仅供**后台面板预览**使用(后台无站点 CSS 变量,故给具体值):
* swatch 取亮态值(后台是浅色 UI),family 为该类的真实字体栈。
*/ */
window.TYPOGRAPHY_CLASSES = { window.TYPOGRAPHY_CLASSES = {
font: [ font: [
{ cls: 'font-serif', label: '思源宋(书评正文)' }, { cls: 'font-serif', label: '思源宋(书评正文)', family: '"Noto Serif SC", Georgia, serif', kind: '自托管' },
{ cls: 'font-kai', label: '霞鹜文楷(金句)' }, { cls: 'font-kai', label: '霞鹜文楷(金句)', family: '"LXGW WenKai Screen", "Songti SC", serif', kind: '自托管' },
{ cls: 'font-sans', label: '思源黑(UI/技术)' }, { cls: 'font-sans', label: '思源黑(UI/技术)', family: '"Noto Sans SC", system-ui, sans-serif', kind: '自托管' },
{ cls: 'font-mono', label: 'JetBrains Mono(等宽)' }, { cls: 'font-mono', label: 'JetBrains Mono(等宽)', family: '"JetBrains Mono", ui-monospace, monospace', kind: '自托管' },
{ cls: 'font-song', label: '宋体(系统)' }, { cls: 'font-song', label: '宋体族', family: '"Noto Serif SC", "Songti SC", SimSun, serif', kind: '自托管优先' },
{ cls: 'font-kai-sys', label: '楷体(系统)' }, { cls: 'font-kai-sys', label: '楷体族', family: '"LXGW WenKai Screen", "Kaiti SC", STKaiti, serif', kind: '自托管优先' },
{ cls: 'font-hei-sys', label: '黑体(系统)' }, { cls: 'font-hei-sys', label: '黑体族', family: '"Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif', kind: '自托管优先' },
{ cls: 'font-fangsong', label: '仿宋(系统)' }, { cls: 'font-fangsong', label: '仿宋族', family: 'FangSong, STFangsong, serif', kind: '系统回退' },
{ cls: 'font-huiwen', label: '汇文明朝体', family: '"WF-Hwmc", serif', kind: '自托管' },
{ cls: 'font-kuaile', label: '站酷快乐体', family: '"ZCOOL KuaiLe", serif', kind: '自托管' },
{ cls: 'font-art-kuaile', label: '快乐体(艺术·仅标题)', family: '"ZCOOL KuaiLe", serif', kind: '艺术字' },
], ],
color: [ color: [
{ cls: 'ink-1', label: '最重(标题级)' }, { cls: 'ink-1', label: '最重(标题级)', swatch: '#111113' },
{ cls: 'ink-2', label: '正文' }, { cls: 'ink-2', label: '正文', swatch: '#33343a' },
{ cls: 'ink-3', label: '次要(日期/元信息)' }, { cls: 'ink-3', label: '次要(日期/元信息)', swatch: '#6b6e78' },
{ cls: 'ink-muted', label: '最淡(注释感)' }, { cls: 'ink-muted', label: '最淡(注释感)', swatch: '#70747c' },
{ cls: 'ink-accent', label: '强调(链接色)' }, { cls: 'ink-warm', label: '暖调正文', swatch: '#3f362e' },
{ cls: 'ink-accent-soft', label: '弱强调' }, { cls: 'ink-cool', label: '冷调正文', swatch: '#2f3742' },
{ cls: 'ink-warm', label: '暖调正文' }, { cls: 'ink-faint', label: '最淡可读档', swatch: '#70747c' },
{ cls: 'ink-cool', label: '冷调正文' }, { cls: 'ink-accent', label: '强调(链接色)', swatch: '#3454d1' },
{ cls: 'ink-faint', label: '最淡可读档' }, { cls: 'ink-accent-soft', label: '弱强调', swatch: '#4f66c2' },
{ cls: 'c-blue', label: '蓝', swatch: '#2456c9' },
{ cls: 'c-blue-soft', label: '蓝(soft)', swatch: '#4170dd' },
{ cls: 'c-teal', label: '青', swatch: '#0d7d74' },
{ cls: 'c-teal-soft', label: '青(soft)', swatch: '#0e847a' },
{ cls: 'c-green', label: '绿', swatch: '#1f7a3a' },
{ cls: 'c-green-soft', label: '绿(soft)', swatch: '#22853f' },
{ cls: 'c-yellow', label: '黄(已压暗)', swatch: '#7a5900' },
{ cls: 'c-yellow-soft', label: '黄(soft)', swatch: '#926a00' },
{ cls: 'c-orange', label: '橙', swatch: '#9a4a00' },
{ cls: 'c-orange-soft', label: '橙(soft)', swatch: '#ba5900' },
{ cls: 'c-red', label: '红', swatch: '#b32020' },
{ cls: 'c-red-soft', label: '红(soft)', swatch: '#db3737' },
{ cls: 'c-pink', label: '粉', swatch: '#a3246a' },
{ cls: 'c-pink-soft', label: '粉(soft)', swatch: '#ce3386' },
{ cls: 'c-purple', label: '紫', swatch: '#6a35b8' },
{ cls: 'c-purple-soft', label: '紫(soft)', swatch: '#8c5ed1' },
], ],
}; };
+27 -4
View File
@@ -37,7 +37,7 @@
card.appendChild(title); card.appendChild(title);
const buttons = []; const buttons = [];
const addGroup = (name, list) => { const addGroup = (name, list, withSwatch) => {
const h = document.createElement('p'); const h = document.createElement('p');
h.textContent = name; h.textContent = name;
Object.assign(h.style, { margin: '12px 0 6px', fontSize: '12px', color: '#6b6e78' }); Object.assign(h.style, { margin: '12px 0 6px', fontSize: '12px', color: '#6b6e78' });
@@ -47,22 +47,45 @@
for (const item of list) { for (const item of list) {
const b = document.createElement('button'); const b = document.createElement('button');
b.type = 'button'; b.type = 'button';
b.textContent = item.label;
b.dataset.cls = item.cls; b.dataset.cls = item.cls;
b.className = item.cls === current ? 'active' : ''; b.className = item.cls === current ? 'active' : '';
Object.assign(b.style, { Object.assign(b.style, {
display: 'inline-flex', alignItems: 'center', gap: '8px',
padding: '6px 12px', borderRadius: '8px', cursor: 'pointer', padding: '6px 12px', borderRadius: '8px', cursor: 'pointer',
border: '1px solid ' + (item.cls === current ? '#3454d1' : '#e6e7eb'), border: '1px solid ' + (item.cls === current ? '#3454d1' : '#e6e7eb'),
background: item.cls === current ? '#eef1fd' : '#fff', background: item.cls === current ? '#eef1fd' : '#fff',
color: '#111113', fontSize: '13px', color: '#111113', fontSize: '13px',
}); });
if (withSwatch) {
// 色块预览:直接展示该类在亮态下的实际颜色
const chip = document.createElement('span');
Object.assign(chip.style, {
width: '16px', height: '16px', borderRadius: '4px', flex: 'none',
background: item.swatch || '#ccc',
border: '1px solid rgba(0,0,0,.12)',
});
chip.setAttribute('aria-hidden', 'true');
b.appendChild(chip);
} else {
// 字体项:名称 + 用该字体栈渲染的示例字形
const sample = document.createElement('span');
sample.textContent = '秋海';
Object.assign(sample.style, {
fontFamily: item.family || 'inherit', fontSize: '15px', lineHeight: '1',
padding: '0 4px', borderLeft: '1px solid #e6e7eb', color: '#33343a',
});
b.appendChild(sample);
}
const label = document.createElement('span');
label.textContent = item.label + (item.kind ? `(${item.kind})` : '');
b.appendChild(label);
row.appendChild(b); row.appendChild(b);
buttons.push(b); buttons.push(b);
} }
card.appendChild(row); card.appendChild(row);
}; };
addGroup('字体', CLASSES.font); addGroup('字体', CLASSES.font, false);
addGroup('颜色', CLASSES.color); addGroup('颜色', CLASSES.color, true);
const foot = document.createElement('div'); const foot = document.createElement('div');
Object.assign(foot.style, { marginTop: '16px', display: 'flex', justifyContent: 'flex-end', gap: '8px' }); Object.assign(foot.style, { marginTop: '16px', display: 'flex', justifyContent: 'flex-end', gap: '8px' });
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loaded 100 of 482 files, more files were not shown because too many files have changed in this diff. Show more