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

+16 -5
View File
@@ -7,7 +7,7 @@
import { Image } from 'astro:assets';
import { existsSync } from 'node:fs';
import TypingLines from './TypingLines.astro';
import { homeAvatar, homeSocials, openSourceCopy } from '../../lib/site-content';
import { homeAvatar, homeSocials, openSourceCopy, renderRich } from '../../lib/site-content';
import { githubUserStats, loadProjects } from '../../lib/projects';
import { iconPath } from '../../lib/icons';
import type { Locale } from '../../lib/locale';
@@ -20,6 +20,10 @@ const { locale = 'zh' } = Astro.props;
const avatar = await homeAvatar();
const socials = await homeSocials();
const copy = await openSourceCopy(locale);
// 开篇叙述合一(5j T1):story 非空 → 整段走富文本管线(markdown + 内容净化 + 排版类白名单),
// 链接就是 markdown 普通链接,不再需要 blurb/blurb_after 两半夹一个 repo 链接;
// story 为空 → 回落旧的 blurb + <a> + blurb_after 拼接(旧 yaml 零回归)。
const storyHtml = copy.story.trim() ? await renderRich(copy.story, { inline: true }) : null;
// 仓库数据:档案卡只关心"语言占比 + 星标汇总",取全量(不受项目墙可见性影响)
let langs: { name: string; count: number; pct: number }[] = [];
@@ -105,9 +109,13 @@ const L = {
wait4x<span class="pf-handle mono">@wait4xx</span>
</p>
<TypingLines lines={lines} />
<p class="pf-bio">
{copy.blurb}<a class="pf-link" href={copy.repoUrl}>{copy.repoLabel}</a>{' '}{copy.blurbAfter.trim()}
</p>
{storyHtml ? (
<p class="pf-bio" set:html={storyHtml} />
) : (
<p class="pf-bio">
{copy.blurb}<a class="pf-link" href={copy.repoUrl}>{copy.repoLabel}</a>{' '}{copy.blurbAfter.trim()}
</p>
)}
<ul class="pf-socials">
{socials.map((s) => (
<li>
@@ -239,7 +247,10 @@ const L = {
line-height: 1.65;
color: var(--ink-2);
}
.pf-link {
/* 5j T1:story 走 markdown 渲染,链接不再是 .pf-link 而是裸 <a>(set:html 内容不参与 scoped),
两路共用同一套链接样式,观感不因改写法而漂移 */
.pf-link,
.pf-bio :global(a) {
color: var(--accent);
text-decoration: underline;
text-underline-offset: 3px;
+58
View File
@@ -0,0 +1,58 @@
---
/**
* 热力图网格(可复用渲染件):给 5 个动效方案预览共用,避免把 371 个格子抄五遍。
*
* 每格带 `data-c/--c`(列=周)与 `data-r/--r`(行=星期),动效方案靠这两个自定义属性
* 算错峰延迟;`data-l` 是灰阶档(0–4,复用 tokens 的 --ink-1..4)。
* 全部格子 aria-hidden:语义由外层 role="img" + aria-label 概括(371 个 aria 节点是噪音)。
*/
import { levelOf, type HeatDay } from '../../lib/heatmap';
interface Props {
weeks: HeatDay[][];
/** 网格类型,仅用于选择器命名(不动行为) */
variant: string;
/** 单元格错峰延迟基数(ms),0 = 不设延迟 */
step?: number;
}
const { weeks, variant, step = 0 } = Astro.props;
---
<div class={`hg hg-${variant}`} data-hg={variant}>
{weeks.map((week, ci) =>
week.map((day, ri) => (
<span
class={`hg-c hg-l${levelOf(day.count)}`}
data-c={ci}
data-r={ri}
data-count={day.count}
data-date={day.date}
style={step ? `--c:${ci};--r:${ri};--d:${(ci + ri) * step}ms` : `--c:${ci};--r:${ri}`}
aria-hidden="true"
/>
)),
)}
</div>
<style>
.hg {
display: grid;
grid-auto-flow: column;
grid-template-rows: repeat(7, 10px);
grid-auto-columns: 10px;
gap: 3px;
}
.hg-c {
width: 10px;
height: 10px;
border-radius: 2px;
/* 与线上热力图同口径:空格用 hairline 描边、四档用 ink 灰阶(亮暗自动跟随),
accent 只留给各方案的"高亮"(glow / flow / 连续数字),不参与数据编码 */
}
.hg-l0 { border: 1px solid var(--line); }
.hg-l1 { background: var(--ink-4); }
.hg-l2 { background: var(--ink-3); }
.hg-l3 { background: var(--ink-2); }
.hg-l4 { background: var(--ink-1); }
</style>