// 贡献热力图数据(极客层 P0):构建期拉自托管 gitea 年历,聚合 53 周 × 7 日 // 三级回落(远端 → 仓内样例 → 空数组)保证网络单点故障不破构建;见 Heatmap.astro export interface HeatDay { date: string; // YYYY-MM-DD count: number; } export interface HeatmapYear { weeks: HeatDay[][]; // 53 列(周)× 7 行(日,周一为首行) total: number; } export const HEATMAP_API = 'https://git.wait4x.online/api/v1/users/wait4x/heatmap'; interface RawHeat { timestamp: number; contributions: number; } /** 分档阈值:0 / 1-2 / 3-5 / 6-9 / 10+ → 灰阶 l0-l4,固定阈值可解释(GitHub 同款哲学) */ export function levelOf(count: number): number { if (count >= 10) return 4; if (count >= 6) return 3; if (count >= 3) return 2; if (count >= 1) return 1; return 0; } // 站点受众时区:日界按上海(+8)切,保证 gitea 后台与博客热力图对同一天计数一致; // 显式固定而非取构建机时区——避免构建环境变化悄悄改变历史归日 const DAY_FMT = new Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit', }); function toDateString(tsSec: number): string { return DAY_FMT.format(new Date(tsSec * 1000)); } /** * 聚合原始贡献点为 53 周年历:起点为「今天所在周的周一」前推 52 周, * 网格尾部未来的日子填 0(格渲染为空档),首列可能含早于窗口的 0 天。 */ export async function loadHeatmap(): Promise { let raw: RawHeat[] = []; try { const res = await fetch(HEATMAP_API, { headers: { Accept: 'application/json', 'User-Agent': 'wait4x-blog-build' }, signal: AbortSignal.timeout(10_000), }); if (!res.ok) throw new Error(`${HEATMAP_API} -> ${res.status}`); raw = (await res.json()) as RawHeat[]; } catch { // 远端失败回落仓内样例(与 API 同构的原始点,走同一聚合管道保证形态一致) raw = (await import('../data/heatmap-fallback.json')).default as RawHeat[]; } const byDate = new Map(); for (const { timestamp, contributions } of raw) { const key = toDateString(timestamp); byDate.set(key, (byDate.get(key) ?? 0) + contributions); } // 以 Asia/Shanghai 的“今天”定窗(与归日时区一致) const today = new Date(toDateString(Math.floor(Date.now() / 1000)) + 'T12:00:00+08:00'); const dow = (today.getUTCDay() + 6) % 7; // 周一=0 const gridStart = new Date(today.getTime() - (dow + 52 * 7) * 86_400_000); const weeks: HeatDay[][] = []; let total = 0; for (let w = 0; w < 53; w++) { const week: HeatDay[] = []; for (let d = 0; d < 7; d++) { const day = new Date(gridStart.getTime() + (w * 7 + d) * 86_400_000); const key = toDateString(Math.floor(day.getTime() / 1000)); const count = day > today ? 0 : (byDate.get(key) ?? 0); total += count; week.push({ date: key, count }); } weeks.push(week); } return { weeks, total }; }