--- /** * 热力图动效方案预览 v2(Phase 5j)——用户看过 v1 五案后觉得不够,要求「类似贪吃蛇」的 * 方案并再深入设计 4–6 个。本页给 6 个更深入的方案(F–K),v1 的 A–E 已下线。 * * 设计红线(六案都守):灰阶骨架、accent 只做高亮不做数据编码(数据仍由 ink 四档承载)、 * prefers-reduced-motion 一律静态、零第三方依赖、离开视口即停(省电)。 */ import BaseLayout from '../layouts/BaseLayout.astro'; import HeatGrid from '../components/geek/HeatGrid.astro'; import { loadHeatmap, type HeatDay } from '../lib/heatmap'; const { weeks, total } = await loadHeatmap(); const days: HeatDay[] = weeks.flat(); const buildTime = new Date().toISOString().slice(0, 10); /* ── 连续贡献(本地计算,口径:以最后一个有贡献的日子为终点往回数)── */ const countOf = new Map(days.map((d) => [d.date, d.count])); const isoOf = (d: Date) => d.toISOString().slice(0, 10); const lastActive = [...days].reverse().find((d) => d.count > 0)?.date ?? ''; let current = 0; if (lastActive) { const start = new Date(`${lastActive}T00:00:00Z`); for (let i = 0; ; i++) { const d = new Date(start); d.setUTCDate(start.getUTCDate() - i); if ((countOf.get(isoOf(d)) ?? 0) > 0) current++; else break; } } let longest = 0; let run = 0; for (const d of days) { run = d.count > 0 ? run + 1 : 0; if (run > longest) longest = run; } const bestDay = days.reduce((a, b) => (b.count > a.count ? b : a), days[0]); /* ── 月度聚合(J 方案用)── */ const byMonth = new Map(); for (const d of days) { const key = d.date.slice(0, 7); byMonth.set(key, (byMonth.get(key) ?? 0) + d.count); } const months = [...byMonth.entries()].sort(([a], [b]) => a.localeCompare(b)); const maxMonth = Math.max(...months.map(([, v]) => v)); /* ── 最后活跃日的坐标(H 涟漪终点起点)── */ const lastActiveIdx = days.findLastIndex((d) => d.date === lastActive); const lastActiveCol = Math.floor(lastActiveIdx / 7); const lastActiveRow = lastActiveIdx % 7; const stats = { total, current, longest, best: bestDay?.count ?? 0, bestDate: bestDay?.date ?? '' }; const dataForClient = { cells: days.map((d) => d.count), weeks: weeks.length }; ---

热力图:六个更深入的方案

同一份真实数据(近一年 {stats.total} 次贡献 · 当前连续 {stats.current} 天 · 最长 {stats.longest} 天 · 单日最高 {stats.best} 次), 数据更新于 {buildTime}。
上一版的五个方案已按你的反馈下线;这一版六个都往「有生命、有叙事」的方向推。 回字母告诉我选哪个(也可以说「F 但慢一点」「F + J 组合」)。

F

贪吃蛇

一条蛇沿着贡献格蛇形爬过:吃到有贡献的格子就把它们点亮并"长"一点,空格直接穿过。 优点最有生命感、天然表达"这一年被一点点吃掉";代价是持续动画(离开视口即暂停)。

准备出发… ·

G

心跳呼吸

每格按当日贡献量呼吸:越忙的日子跳得越快、越亮,静下来时整片缓慢起伏。 优点像一张"活的"心电图,无需交互;代价持续动画(纯 CSS,合成层,很省)。

H

雨滴涟漪

从"最后一次贡献"那一格出发,涟漪一圈圈向外扩散,扫过的格子闪一下再落定。 优点叙事清晰(现在 → 过去)、只播一次;代价纯装饰。

· 起点 {lastActive}

I

3D 地形

把贡献量当"海拔":忙的日子凸起来,整块网格随鼠标轻轻倾斜,像俯瞰一年的地貌。 优点一眼看出密集区、视觉独特;代价倾斜会略降可读性。

J

周冠军柱

网格下方按月长出一排柱子,进视口时逐根升起;指针移到柱子上,对应那些格子同时亮起。 优点把"哪几个月最忙"讲清楚、信息量大;代价多一层图表,页面变高。

    {months.map(([m, v]) => (
  • {m.slice(5)}
  • ))}
K

显微镜

指针所在处浮起一枚圆镜,把周围 5×5 的格子放大显示,并标出当天日期与次数。 优点小格子也读得清、趣味强;代价触屏需点按。

把指针移到任意格子上

数据口径:GitHub GraphQL 贡献日历,构建期快照({buildTime})。"减少动态效果"下六案全部静态。