feat(opensource): 热力图五方案预览 + 开源页叙述合一(5j T1 + 预设)

用户两项新需求:
①「开源页的叙述为什么要区分链接前和链接后?完全可以用 markdown 富文本统一替代」
②「热力图可以有更多的动效,而不是静态的一张图,探索相关方案供我审阅」

T1 叙述合一:
- opensource.yaml 新增 story(双语 markdown,链接内联),blurb/blurb_after 保留为
  legacy(story 为空时才用)→ 旧 yaml 零回归
- site-content 访问器取 story,GithubProfile 走 renderRich 行内模式;markdown 生成的
  裸 <a> 补 .pf-bio :global(a) 样式,与 .pf-link 观感一致
- 实测产物:pf-bio 内联 <a> 渲染成功、pf-link 拼接痕迹 0 处

热力图五方案预览(/heatmap-preview/,供用户选型):
- 新增 geek/HeatGrid.astro:可复用网格渲染件(371 格带 --c/--r 供错峰延迟),
  沿用线上同款 ink 灰阶(accent 只做高亮,不参与数据编码)
- A 入场波纹(视口内一次性斜向漫过)、B 光标光晕(radial-gradient 跟随 + 日期读数)、
  C 年度回放(滑块 scrub + 累计计数 + 自动播放)、D 连续电流(最近连续段流光 +
  连续天数大数)、E 悬停周详情(整列抬起 + 周合计/最好一天面板)
- 五案均守灰阶红线、均在 prefers-reduced-motion 下静态化、零第三方依赖、无常驻 rAF
- **两处真坑**:①网格是子组件渲染,页面级 scoped 样式够不到其内部元素 → 与格内元素
  相关的选择器必须 :global()(否则 A 的动画与 E 的抬列静默不生效)②「当前连续」按
  今天起算会得 0(今天尚未贡献)→ 改为以最后一个有贡献的日子为终点往回数,并标注
  「截至 <日期>」

另修 E2E 过时断言:书架改版为抽书阅读后,原「年份折叠组」断言必然失败,
改为「抽书行渲染 + 点击可抽开」(56/56 PASS)

验证: 构建 51 页 exit 0;content-lint 52 页 0/0/0;E2E 56/56;
      Playwright 逐案实测:A 动画 hmRise 0.42s 且播完 opacity=1、B 光晕 opacity=1
      并显示「日期 · 次数」、C 拖到第 10 周读数与淡出正确、D 12 格流光 hmFlow +
      连续 5 天(截至 2026-10-24)、E 整列 lift matrix(…,-2);390 零横向溢出;
      reduced-motion 下动画 none、光晕 display:none

Spec: 2026-11-03_blog-5j-content-refine (T1 + 热力图预览)

Co-authored-by: GLM-5.3
This commit is contained in:
wait4xandGLM-5.3 committed 2026-11-04 20:09:10 +08:00
1 parent 6abca9f412
commit 7e0167a8bb
8 files changed
+604 -8

No files matched your search

+10 -3
View File
@@ -187,10 +187,17 @@ ok('归档 首年展开/往年折叠 + 月分组', arch.total >= 1 && arch.open
await page.goto(BASE + '/books/', { waitUntil: 'networkidle' });
const books = await page.evaluate(() => {
const all = [...document.querySelectorAll('.year-group')];
return { total: all.length, open: all.filter((d) => d.hasAttribute('open')).length, firstOpen: all[0]?.hasAttribute('open') ?? false };
// 书架已改版为「抽书阅读」(Phase 5i T7):不再有年份折叠组,
// 改为每本书一个 details 行;断言 = 至少一行、首行收起、点击能展开出正文。
const all = [...document.querySelectorAll('.book')];
return { total: all.length, open: all.filter((d) => d.hasAttribute('open')).length };
});
ok('书架 当年展开/往昔折叠', books.total >= 1 && books.open === 1 && books.firstOpen, JSON.stringify(books));
ok('书架 抽书行渲染', books.total >= 1, JSON.stringify(books));
await page.locator('.book').first().locator('summary').click();
await page.waitForTimeout(250);
ok('书架 点击可抽开', (await page.locator('.book[open] .body').count()) === 1);
await page.locator('.book').first().locator('summary').click();
await page.waitForTimeout(200);
// 4) 命令面板 + 搜索
await page.keyboard.press('Control+k');