feat(launch): 热力图与 webhook 发布闭环(T2/T3)

- T2 (op-artalk 车道):gitea heatmap 构建期拉取+fallback+空态三保险,
  53×7 灰阶格复用 tokens(亮暗自动),日界锚定 Asia/Shanghai,零 JS,
  zh/en 首页挂载;fallback 经死端点实测不破构建
- T3:webhook_receiver.py(HMAC 强制+main-only+构建锁)以 systemd
  --user 常驻 13021;gitea hook 注册(events=push, branch=main)
- 三个深坑:gitea 容器回宿主须走桥网关 IP 而非 127.0.0.1;gitea 28
  egress 拦私网须 ALLOWED_HOST_LIST(ALLOWLIST 是废弃名);systemd
  用户服务缺用户 PATH 致 build 静默失败(stamp 不更新即症状)

验证: 测试投递 202→构建→staging 戳更新(09:48:37 实测);
      dist 热力图 376 格真数据(9 次贡献)zh/en 双页

Spec: 2026-10-25_add-blog-launch
Co-authored-by: GLM-5.3
This commit is contained in:
wait4xandGLM-5.3 committed 2026-10-25 09:49:19 +08:00
1 parent 0c54a43bb5
commit 938558ad4c
12 files changed
+692 -110

No files matched your search

+106
View File
@@ -0,0 +1,106 @@
---
// 贡献热力图(极客层 P0):纯 CSS grid,零运行时 JS
// 数据聚合见 src/lib/heatmap.ts;灰阶四档直接复用 tokens --ink-1..4(亮暗两套自动跟随)
import { loadHeatmap, levelOf } from '../../lib/heatmap';
// 文案全部由页面层注入(locale 无关挂法),keys 与 ui.ts heatmap_* 一一对应
interface Labels {
title: string;
summary: string; // 含 {n} 占位符
less: string;
more: string;
note: string;
}
interface Props {
labels: Labels;
}
const { labels } = Astro.props;
let weeks: import('../../lib/heatmap').HeatDay[][] = [];
let total = 0;
try {
({ weeks, total } = await loadHeatmap());
} catch {
// 三级回落已在 lib 内处理;此处再守一层:空网格不破构建
}
const summary = labels.summary.replace('{n}', String(total));
---
<section class="heatmap">
<h2 class="hm-title">{labels.title}</h2>
<!-- 53 列窄栏触屏放不下,容器横滚(格子原生 title 提示,无 JS) -->
<div class="hm-scroll">
<div class="hm-grid" role="img" aria-label={summary}>
{weeks.map((week) =>
week.map((day) => (
<span
class={`hm-c hm-l${levelOf(day.count)}`}
title={`${day.date} · ${day.count}`}
aria-hidden="true"
/>
))
)}
</div>
</div>
<p class="hm-legend">
<span aria-hidden="true">{labels.less}</span>
{[0, 1, 2, 3, 4].map((l) => (
<span class={`hm-c hm-l${l}`} aria-hidden="true" />
))}
<span aria-hidden="true">{labels.more}</span>
<span class="hm-note">{labels.note}</span>
</p>
</section>
<style>
.heatmap { margin-top: var(--space-6); }
.hm-title {
font-size: 14px;
font-family: var(--font-mono);
color: var(--ink-3);
letter-spacing: 0.08em;
margin-bottom: var(--space-3);
}
.hm-scroll {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scrollbar-width: thin;
}
/* 列优先流:子元素按周展平后自上而下再向右,得 7 行 × 53 列 */
.hm-grid {
display: grid;
grid-auto-flow: column;
grid-template-rows: repeat(7, 11px);
grid-auto-columns: 11px;
gap: 3px;
width: max-content;
}
.hm-c {
width: 11px;
height: 11px;
border-radius: 2px;
background: transparent;
}
/* 空档 l0 用极淡描边标位;l1-l4 = ink-4 → ink-1(亮暗模式各自成对,多则更醒目) */
.hm-l0 { border: 1px solid var(--line); }
.hm-l1 { background: var(--ink-4); }
.hm-l2 { background: var(--ink-3); }
.hm-l3 { background: var(--ink-2); }
.hm-l4 { background: var(--ink-1); }
.hm-legend {
display: flex;
align-items: center;
gap: 4px;
margin: var(--space-2) 0 0;
font-size: 11px;
color: var(--ink-3);
}
.hm-legend .hm-c { width: 9px; height: 9px; }
.hm-note {
margin-left: var(--space-3);
font-family: var(--font-mono);
font-size: 10px;
color: var(--ink-4);
letter-spacing: 0.04em;
}
</style>