做个人站最容易掉进的坑,是把它当成作品陈列柜:卡片、圆角、渐变、居中,每个模板都差不多。这一次我换了一个出发点——把主页当作一台正在运行的观测仪器来设计。网格是刻度,编号是通道,状态灯告诉你系统是否在线,动效是仪器对输入的物理响应。

这个比喻给了我一条很好用的判断标准:任何装饰,都必须能被解释为“仪表的一部分”。解释不了的,就删掉。

一种信号色

整站只有一种强调色,我叫它 signal。它不是用来“好看”的,而是用来表示“这里有信号”:在线状态、当前所在的导航、滚动进度、需要你注意的那一个按钮。

Token 深色 浅色 用途
--bg #09090B #F7F7F8 页面底
--fg #EDEDEF #121215 主文字
--fg-muted #8A8A93 #5C5C66 次要文字,对比度 ≥ 4.5:1
--signal #7CFFB2 #0AA864 唯一强调色
--signal-ink #7CFFB2 #067A48 小字号的信号色文字

浅色模式里有一个细节:#0AA864 这种绿放在浅底上只有 2.9:1 的对比度,做指示灯没问题,做文字就不及格。所以多拆出一个 --signal-ink,专门给链接和编号用。

网格是刻度

12 列、24px 的槽、最大 1440px 的容器。和大多数网格系统不同的是,这里的网格是可见的:整页有极淡的列参考线,Hero 和页脚铺着方格场,四角标着十字准星。

Signal 的 12 列网格示意:页边距、列与槽

可见网格有一个副作用:它会逼着你对齐。任何没有落在列边界上的元素,都会被参考线出卖。

动效:像仪器一样响应

动效只有三条规则:

  1. 进入动画只做位移 + 透明度,不做缩放弹跳;
  2. 滚动驱动的动画一律 scrub,让人感觉是自己在推动;
  3. hover 反馈不超过 200ms。

缓动曲线在 CSS 和 GSAP 里各有一份,需要保证它们是同一条曲线:

ts
// CSS 的 --ease-out: cubic-bezier(.22, 1, .36, 1) 就是 easeOutQuint,
// 对应 GSAP 的 power4.out;--ease-in-out 是 easeInOutCubic,对应 power2.inOut
export const EASE = {
  out: 'power4.out',
  inOut: 'power2.inOut',
} as const;

gsap.from(chars, {
  yPercent: 110,
  opacity: 0,
  duration: 0.9,
  ease: EASE.out,
  stagger: 0.018,
});

最后,所有动效都必须响应系统的“减少动态效果”设置。这不是锦上添花,而是底线:

css
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

下一步是首页:三维点阵的 Hero、层叠滑入的作品卡片,以及一台真正会“响应”光标的仪器。