本项目的全部视觉参数(颜色、阴影、圆角、字体、背景网格)都收敛为 --wp-* 前缀的 CSS 变量,定义在 src/styles/main.css。换肤、换主色、换字体都只需要动这一个文件。
1. 换肤架构(三层)
第 1 层:设计 token(CSS 变量)
:root, .light { --wp-accent: #e86233; --wp-bg: #f5f4f0; ... } ← 亮色值(.light 供子树强制亮色)
.dark { --wp-accent: #db7753; --wp-bg: #141413; ... } ← 暗色值
│
▼
第 2 层:Tailwind 桥接(@theme inline)
@theme inline {
--color-accent: var(--wp-accent);
--color-paper: var(--wp-bg);
...
}
→ 自动生成工具类:bg-accent / text-accent / border-line / shadow-card ...
│
▼
第 3 层:组件使用语义类
<article class="border border-line-light bg-surface shadow-card">
<a class="hover:text-accent">
由此得到两条实践结论:
| 想做的事 | 正确做法 | 错误做法 |
|---|---|---|
| 换主色 | 改 :root 和 .dark 里的 --wp-accent 等变量 | 全局替换组件里的 text-accent |
| 加一个新颜色 | 1、加 --wp-xxx;2、在 @theme inline 里加 --color-xxx | 在组件里写死 #RRGGBB |
| 改圆角 | 改 --radius-wp(部分组件写死了 px,见#8.3 说明) | — |
2. 换主色:最小改动步骤
场景:把柑橘橙(
#e86233)换成蓝紫(#5b6ee1)。这是最常见的换肤需求,共用 6 步。
第 1 步:改亮色主色三件套
src/styles/main.css 的 :root 块:
:root {
--wp-accent: #5b6ee1; /* 原 #e86233 —— 主色,按钮/链接/高亮 */
--wp-accent-hover: #7484e8; /* 原 #ea7b53 —— hover 亮一档 */
--wp-accent-deep: #3f4fb0; /* 原 #b6502b —— 深一档,用于链接文字 */
/* --wp-link / --wp-link-hover 默认引用上面两个变量,无需改 */
}
第 2 步:改暗色主色三件套
.dark 块(暗色下要降低饱和度并提亮,否则刺眼):
.dark {
--wp-accent: #8b9bf0; /* 原 #db7753 */
--wp-accent-hover: #a3b0f4; /* 原 #e28969 */
--wp-accent-deep: #7286e8; /* 原 #c86541 */
}
第 3 步:改背景网格线颜色(最容易被忘的一步)
--wp-grid-image 是一段内联 SVG data URI,线条颜色硬编码在 URL 编码里,不会跟随 --wp-accent:
/* 亮色:stroke='%23E86233' ← 注意 # 被编码成 %23 */
--wp-grid-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='28' viewBox='0 0 28 28'%3E%3Cpath d='M0 0L28 0M0 0L0 28' fill='none' stroke='%23E86233' stroke-opacity='0.14' stroke-width='1'/%3E%3C/svg%3E");
/* 暗色:stroke='%23DB7753',透明度 0.11 */
把两处 %23E86233 / %23DB7753 分别替换成新主色的十六进制(不带 #,前面加 %23)。width='28' height='28' 是网格单元尺寸,改这里要同步改 body 的 background-size: 28px 28px。
第 4 步:检查 --wp-gradient-pink 与品牌色
| 变量 | 当前值 | 是否随主色 |
|---|---|---|
--wp-gradient-pink | linear-gradient(135deg, #8a667e 0%, #b06b78 100%) | 否,独立品牌渐变(用于 .wp-gradient-bg) |
--wp-gold | 亮 #8a6939 / 暗 #b08d4e | 否,用于「置顶」角标 |
ProfileCard.astro 的 platformBrands | 各平台官方色 | 否,社交按钮专属 |
第 5 步:AboutPage.astro 主题色(可选)
因为 about 是一个特殊的独立页面,为自定义页面,所以请自行参考修改。
第 6 步:验证
pnpm dev # 切换明暗主题,检查:链接、按钮 hover、网格背景、分页当前页、置顶角标、代码块左侧色条、TOC 激活项
3. --wp-* 变量全表
3.1 背景与表面
| 变量 | 亮色 | 暗色 | 用途 |
|---|---|---|---|
--wp-bg | #f5f4f0 | #141413 | 页面底色(也是 Tailwind paper) |
--wp-surface | #ffffff | #1b1a18 | 卡片 / 面板表面 |
--wp-surface-hover | #f4eae2 | #23221f | 悬停底色、图片占位底 |
--wp-code-bg | #ece7dc | #0e0d0c | 代码块背景、表头背景 |
--wp-header-bg | rgba(245,244,240,0.65) | rgba(20,20,19,0.65) | 吸顶头部背景(配合 backdrop-filter: blur(3px)) |
--wp-glass-bg | rgba(245,244,240,0.15) | rgba(20,20,19,0.55) | .wp-glass 玻璃面板(<main>、页脚) |
--wp-grid-image | SVG data URI,stroke-opacity: 0.14 | 同结构,0.11 | 方格草稿纸背景 |
3.2 主色与链接
| 变量 | 亮色 | 暗色 | 用途 |
|---|---|---|---|
--wp-accent | #e86233 | #db7753 | 主色:强调文字、按钮、边框高亮、进度条 |
--wp-accent-hover | #ea7b53 | #e28969 | 主色 hover |
--wp-accent-deep | #b6502b | #c86541 | 主色加深(正文链接 --wp-link 等) |
--wp-link | var(--wp-accent-deep) | var(--wp-accent) | 正文链接颜色 |
--wp-link-hover | var(--wp-accent) | var(--wp-accent-hover) | 链接 hover |
注意亮/暗的链接策略不同:亮色模式链接用「深一档」保证对比度,暗色模式直接用主色(暗底上深色不可读)。改
--wp-link时请维持这个思路。
3.3 文字
| 变量 | 亮色 | 暗色 | 用途 | Tailwind 类 |
|---|---|---|---|---|
--wp-text | #322e2a | #e7e2d9 | 正文主色 | text-ink |
--wp-text-secondary | #625b52 | #9c948a | 次级文字(摘要、引用块) | text-ink-2 |
--wp-text-tertiary | #6b645a | #90887b | 三级文字(日期、元信息、占位) | text-ink-3 |
3.4 边框与阴影
| 变量 | 亮色 | 暗色 | 用途 |
|---|---|---|---|
--wp-border | #e0d9cb | #2b2924 | 常规边框、表格线、滚动条滑块 |
--wp-border-light | #e9e2d5 | #23211e | 更浅的分隔线(卡片边缘、页脚上边线) |
--wp-shadow-card | 0 1px 3px rgba(0,0,0,.04), 0 1px 2px rgba(0,0,0,.02) | 0 1px 3px rgba(0,0,0,.3), 0 1px 2px rgba(0,0,0,.2) | 卡片默认阴影 |
--wp-shadow-card-hover | 0 4px 12px rgba(0,0,0,.06), 0 2px 4px rgba(0,0,0,.03) | 0 4px 12px rgba(0,0,0,.4), 0 2px 4px rgba(0,0,0,.25) | 卡片 hover 阴影 |
3.5 语义色
| 变量 | 亮色 | 暗色 | 用途 |
|---|---|---|---|
--wp-gold | #8a6939 | #b08d4e | 「置顶」角标背景(card-badge-left) |
--wp-silver | #686d75 | #96989c | 备用中性色 |
--wp-safe-blue | #4a6e8c | #6c8ca4 | 友链空状态插画的眼泪 |
--wp-life-green | #5c7355 | #7a9173 | 备用 |
--wp-gradient-pink | linear-gradient(135deg,#8a667e,#b06b78) | linear-gradient(135deg,#865f7c,#a96676) | .wp-gradient-bg 渐变背景 |
3.6 提示容器标题色
| 变量 | 亮色 | 暗色 | 对应容器 |
|---|---|---|---|
--wp-info-title | #3f6fa3 | #7fa8ce | :::info |
--wp-tip-title | #52713b | #8fb078 | :::tip |
--wp-warn-title | #8a650d | #d4a94e | :::warning |
--wp-danger-title | #a94f41 | #d08377 | :::danger |
只有标题文字色是变量,容器左边框色在
markdown.css里写死:info#6fa8dc、tip#7ba05b、warning#d9a441、danger#c96a5b、detailsvar(--wp-text-tertiary)。想换要同时改两处(见#8.2)。
3.7 代码高亮配色(Prism)
这些是构建期写进 HTML 的
<span class="token ...">,颜色由 CSS 变量在运行时决定,所以改颜色不需要重新构建。
| 变量 | 亮色 | 暗色 | 作用的 token |
|---|---|---|---|
--wp-syntax-keyword | #984d2a | #d49060 | keyword important atrule rule |
--wp-syntax-string | #466f37 | #8fb87a | string char attr-value selector |
--wp-syntax-comment | #69625a | #847d72 | comment prolog cdata(斜体) |
--wp-syntax-number | #2a7265 | #6fb8a8 | number boolean constant symbol |
--wp-syntax-function | #7b4b2a | #c88860 | function class-name |
--wp-syntax-type | #7f5061 | #c99bae | (在变量表中保留,CSS 中由 function/class-name 规则覆盖) |
--wp-syntax-operator | #6b6560 | #a8a099 | tag punctuation operator entity url |
--wp-syntax-attribute | #8f4e18 | #e2a062 | attr-name property |
--wp-syntax-meta | #855656 | #c9999b | 备用 |
--wp-syntax-regexp | #a34e31 | #e08060 | regex variable |
--wp-diff-del | #a94442 | #d48a85 | token.deleted |
--wp-diff-add | #376c37 | #94c086 | token.inserted |
4. Tailwind 桥接表(@theme inline)
main.css 第 94–116 行把这些变量注册为 Tailwind 主题变量,从而生成工具类:
| Tailwind 变量 | 映射到 | 生成的常用类 |
|---|---|---|
--color-paper | --wp-bg | bg-paper text-paper |
--color-surface | --wp-surface | bg-surface |
--color-surface-hover | --wp-surface-hover | bg-surface-hover hover:bg-surface-hover |
--color-code-bg | --wp-code-bg | bg-code-bg |
--color-accent | --wp-accent | bg-accent text-accent border-accent hover:text-accent |
--color-accent-hover | --wp-accent-hover | hover:bg-accent-hover |
--color-accent-deep | --wp-accent-deep | bg-accent-deep text-accent-deep |
--color-ink | --wp-text | text-ink |
--color-ink-2 | --wp-text-secondary | text-ink-2 |
--color-ink-3 | --wp-text-tertiary | text-ink-3 |
--color-line | --wp-border | border-line text-line |
--color-line-light | --wp-border-light | border-line-light |
--color-gold | --wp-gold | bg-gold text-gold |
--color-silver | --wp-silver | — |
--color-safe-blue | --wp-safe-blue | — |
--color-life-green | --wp-life-green | — |
--font-serif | sans-serif | font-serif |
--font-mono | monospace | font-mono |
--radius-wp | 8px | rounded-wp |
--shadow-card | --wp-shadow-card | shadow-card |
--shadow-card-hover | --wp-shadow-card-hover | shadow-card-hover |
新增一个语义色的完整示例(加一个「成功绿」):
/* 1) 在 :root 里定义 */
:root { --wp-success: #3f8f5f; }
.dark { --wp-success: #7bc39a; }
/* 2) 在 @theme inline 里桥接 */
@theme inline {
--color-success: var(--wp-success);
}
/* 3) 在组件里使用 */
<span class="text-success">已保存</span>
5. 字体与排版
5.1 当前字体策略
/* main.css */
@theme inline {
--font-serif: sans-serif; /* ← 注意:font-serif 实际也是无衬线 */
--font-mono: monospace;
}
body {
font-family: sans-serif;
font-size: 16px;
line-height: 1.7;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
设计取向:零字体网络请求(AboutPage 的「未来计划」中也写明「自定义字体」仍在评估,因为要考虑加载时间)。所以标题用的 font-serif 实际渲染为系统无衬线字体。
5.2 换成自定义字体(需权衡)
引入 web 字体会破坏「零请求」取向!
若确定要换,注意
astro.config.mjs的vite.build.assetsInlineLimit = 4096:小于 4KB 的资源会被内联,字体文件通常远大于此,会作为独立文件输出。
推荐把字体文件放 public/fonts/(astro.config.mjs 的 swup ignore 已包含 /fonts/ 前缀,不会走 SPA 导航),然后:
/* main.css 顶部 */
@font-face {
font-family: 'MySans';
src: url('/fonts/MySans.woff2') format('woff2');
font-display: swap; /* 避免文字长时间不可见 */
unicode-range: U+0000-00FF; /* 按需裁剪,减小体积 */
}
@theme inline {
--font-serif: 'MySans', system-ui, sans-serif;
}
body { font-family: 'MySans', system-ui, sans-serif; }
5.3 常用排版参数调整位置
| 想改什么 | 改哪里 | 当前值 |
|---|---|---|
| 全局基准字号 | main.css 的 body { font-size } | 16px |
| 全局行高 | main.css 的 body { line-height } | 1.7 |
| 页面内容最大宽度 | BaseLayout.astro 的 <main class="max-w-[832px]"> | 800px |
| 头部高度 | SiteHeader.astro 的 h-[60px] max-md:h-[52px] | 60 / 52px |
| 文章正文字号/行高 | markdown.css 的 .post-content | 1.06rem / 1.85 |
| 文章标题字号 | markdown.css 的 .post-content h1..h6 | 1.75 / 1.45 / 1.2 / 1.05 / 1 / 0.95 rem |
| 代码块字号/行高 | markdown.css 的 .code-body 变量 | --code-font-size: 0.85rem、--code-line-height: 1.7 |
| 表格字号 | markdown.css 的 .post-content table | 0.95rem |
为什么
<main>是max-w-[832px]而内容宽度算800px?
832px 是玻璃卡片外框的宽度,正文实际可读宽度 = 外框 − 左右内边距。旧写法 max-w-[800px] + px-6(800 − 2×24 = 752),新写法 max-w-[832px] + px-4(832 − 2×16 = 800)。页脚 SiteFooter.astro 同步用 max-w-[832px] + px-4 保持外框对齐。因此若想改内容宽度到 W,应把两处改成 max-w-[W+32px] 且保持 px-4;页面内部的 max-w-[800px](首页列表、文章页头/尾、评论等)则直接用内容宽度 W。
标题的「层级色条」机制:
.post-content :is(h1..h6)::before 用 --i(0–5)计算高度:height: calc(100% - var(--i) * 14%)。所以 h1 的色条最长、h6 最短。想改成等长,删掉 calc() 直接用 100% 即可。
6. 暗色模式实现细节
6.1 切换机制
| 环节 | 实现 |
|---|---|
| 类名 | document.documentElement.classList.toggle('dark', …) |
| Tailwind 暗色变体 | @custom-variant dark (&:where(.dark, .dark *))(main.css 第 4 行) |
| 默认值来源 | <html data-default-theme>(由 siteConfig.defaultTheme 渲染) |
| 存储 key | localStorage.themeMode,取值 'light' | 'dark'(无 'auto' 存储值) |
'auto' 语义 | 不写 localStorage,实时跟随 prefers-color-scheme |
| 系统变化监听 | initTheme() 中 media.addEventListener('change', …),仅当 themeMode.get() === 'system' 时应用 |
| 切换动画抑制 | applyTheme 在变化时给 <html> 加 is-theme-transitioning,300ms 后移除;该类用 !important 禁用全站 transition。不禁用 animation:animation-name 被置 none 再恢复会取消并重建动画,导致入口动画(如友链卡片 friend-card-in)在切换结束后重播抖动 |
| 触点扩散动画 | 点击 [data-theme-toggle] 且浏览器支持 View Transitions API 时,用 document.startViewTransition 做圆形扩散:新主题快照以按钮圆心 clip-path: circle() 从 0 半径扩散至铺满全屏(500ms,ease-in-out),旧主题快照垫底;布局零变化、无缩放(纯裁切)。移动端(max-width: 768px)改由 main.css 媒体查询接管:新快照 translateY(100%) → 0 自底部划入,旧快照同步淡出,时长与缓动一致。prefers-reduced-motion 或不支持的浏览器回退为瞬时切换。默认 VT 交叉淡入由 main.css 的 ::view-transition-old/new(root) { animation: none } 关闭 |
| 同步机制 | 每次切换派发 document 上的 theme-change 事件(detail 为 'light'/'dark'),评论插件据此更新 iframe 主题 |
移动端与桌面端对于 View Transition API 在实现上存在显著差异,所有做了两套动画方案:
- 桌面端:圆形扩散
- 移动端(
max-width: 768px):底部划入 + 淡出
6.2 主题状态 API(src/stores/theme.ts)
import { themeMode, resolvedTheme, toggleTheme, initTheme } from '@/stores/theme'
themeMode.get() // 'light' | 'dark' | 'system'
themeMode.set('dark') // 写入 localStorage 并立即生效
resolvedTheme.get() // 'light' | 'dark'('system' 已按系统偏好解析)
toggleTheme() // 在亮/暗之间切换(写入 localStorage)
| 导出 | 说明 |
|---|---|
ThemeMode | 'light' | 'dark' | 'system' 类型 |
themeMode | { get(), set(mode) } |
resolvedTheme | { get() },把 'system' 解析成实际值 |
toggleTheme(origin?) | 快捷切换;传入触发按钮元素时走触点扩散动画(圆心即按钮中心) |
initTheme() | 幂等(内部 initialized 标志);绑定 astro:after-swap 重新应用主题、matchMedia 变化监听、[data-theme-toggle] 点击委托 |
initTheme()在BaseLayout的<script>中调用一次即全局生效。
6.3 写自定义样式时如何兼容暗色
方式一(推荐):只用变量
.my-box {
background: var(--wp-surface);
border: 1px solid var(--wp-border-light);
color: var(--wp-text);
}
方式二:需要在暗色下换具体值时
.my-box { background: rgba(255,255,255,0.35); }
.dark .my-box { background: rgba(255,255,255,0.03); } /* markdown.css 的 .code-header 就是这么做的 */
方式三:Tailwind 的 dark: 变体
<span class="text-accent-deep dark:text-accent">CC BY-NC-SA 4.0</span>
7. 全局样式文件与工具类
7.1 CSS 文件清单
| 文件 | 引入位置 | 职责 |
|---|---|---|
styles/main.css | BaseLayout.astro(import '@/styles/main.css') | 变量定义、Tailwind 引入、@theme 桥接、body 基础排版、滚动条、动画 keyframes、Swup 过渡 |
styles/markdown.css | main.css 内 @import "./markdown.css" | 全部 .post-content 内部元素(标题、代码块、表格、图片、容器、KaTeX、脚注) |
styles/PostPage.css | PostPage.astro | 桌面侧边 TOC 定位与激活态 |
styles/PostCard.css | PostCard.astro | 卡片角标(.card-badge 系列)、克隆内 hover 镜像样式 |
styles/ProfileCard.css | ProfileCard.astro | 个人名片、统计区、下方社交方块条、时钟层与数码管、「唤回宠物」按钮(.pet-restore,仅补一条 [hidden] 规则把 hidden 属性接回来,其余复用 .card-toggle) |
styles/main.css 的共享 .card-preview 样式块 | 被 ProfileCard.astro / PostCard.astro 共同使用 | 名片与文章卡共用的预览方窗覆盖层(窗口裁切、--preview-size、is-on 开关、reduced-motion 兜底) |
components/Pagination.astro 的内联 <style> | Pagination.astro | 分页按钮(未抽成独立 CSS 文件,是唯一的例外) |
styles/ScrollControls.css | ScrollControls.astro | 右下按钮组 |
styles/comments.css | Comments.astro | 评论骨架屏与状态类 |
styles/FriendsPage.css | FriendsPage.astro | 友链卡片、空状态插画动画 |
styles/music-player.css | MusicPlayer.astro | 播放器全部样式 |
styles/Pet.css | Pet.astro | 左下角宠物:--pet-unit 尺寸链(clamp + 视口/边距约束)、贴内容列左侧的 left 计算、与右侧按钮组最低边对齐的 bottom(--pet-bottom)、方格描边轮廓与 will-change;长按气泡(.pet-card 及其 is-open / is-closing、pet-card-in/out)、蹦一下(pet-hop)、消解 / 聚合的方格层(.pet-grid / .pet-cell)与让位规则(is-leaving / pet-assembling / pet-off)也在这里;颜色只取 --wp-accent,随主题自动适配 |
7.2 main.css 提供的全局工具类
| 类名 | 定义 | 用途 |
|---|---|---|
.mdi-icon | vertical-align: middle; line-height: 1 | 所有 Icon 的基线对齐 |
.wp-glass | Tailwind 类,配合 --wp-glass-bg | <main> 与页脚的玻璃面板 |
.wp-gradient-bg | background: var(--wp-gradient-pink); color: #fff | 品牌渐变块 |
.wp-overlay | animation: wp-fade-in .18s ease-out | 弹窗遮罩淡入 |
.wp-modal-panel | animation: wp-pop-in .2s ease-out | 弹窗面板上浮 |
.wp-overlay.wp-closing | animation: wp-fade-out .16s ease-in forwards | 弹窗遮罩关闭淡出(JS 加类,0.16s 后置 hidden + 移除类) |
.wp-modal-panel.wp-closing | animation: wp-pop-out .16s ease-in forwards | 弹窗面板关闭下沉缩回 |
.wp-drawer-panel-right / -left | wp-slide-in-right/left .22s | 抽屉面板(预留) |
.wp-spinning | animation: wp-spin .8s linear infinite | 加载旋转(预留) |
html.is-theme-transitioning * | 仅禁 transition(!important) | 主题切换瞬间防颜色渐变叠加;不禁 animation,避免动画取消后重播 |
全局元素规则必须放进
@layer
main.css里针对裸元素的规则(a/a:hover的颜色与过渡)统一写在@layer base { … }内。原因:Tailwind v4 的工具类都位于utilities层,而未分层的规则优先级高于任何层——裸a { color: … }一旦漏在层外,就会压掉组件里给锚点写的text-*(V1.0.9 修对比度时实测踩过这个坑)。
- 新增全局 CSS 规则一律放进
@layer(默认放base);这是 R4 的硬约束;- 想覆盖 base 层的裸元素颜色:用工具类(如
text-ink-2),或写有意不分层的作用域规则(markdown.css的.post-content a就属于后者,优先级更高,正文链接仍取--wp-link);- 排查手段:DevTools 里若看到
a { color: var(--wp-link) }压过.text-ink-2,说明该规则漏在层外了。
7.3 动画 keyframes 一览
| keyframe | 效果 | 使用者 |
|---|---|---|
wp-spin | 360° 旋转 | .wp-spinning |
wp-fade-in | 透明度 0→1 | .wp-overlay |
wp-pop-in | translateY(8px) scale(.98) → 正常 | .wp-modal-panel |
wp-fade-out | 透明度 1→0(ease-in 0.16s) | .wp-overlay.wp-closing |
wp-pop-out | 正常 → translateY(8px) scale(.98) 并淡出 | .wp-modal-panel.wp-closing |
wp-slide-in-right / wp-slide-in-left | 从左右两侧滑入 | 抽屉面板 |
img-shimmer | 背景位置 120% → -120% | 文章图片 LQIP 骨架(markdown.css) |
comment-shimmer | 同上 | 评论骨架(comments.css) |
已有 prefers-reduced-motion 降级的位置:
| 文件 | 覆盖的动画 |
|---|---|
markdown.css | img-shimmer、图片 opacity 过渡 |
comments.css | comment-shimmer |
FriendsPage.css:196 | 友链空状态插画 |
main.css:250 | .transition-swup-fade |
ScrollControls.css | 侧边整合菜单弹出层的展开/收起动画(prefers-reduced-motion 时 transition: none) |
PostPage.css | 文章阅读动效(正文块滚动淡入上移):整块包在 @media (prefers-reduced-motion: no-preference) 里,reduced-motion 用户直接常显、无初始隐藏;JS 侧 initReadingReveal() 也有 reduced-motion 提前返回 |
弹窗关闭动画不靠 CSS 降级,由 SearchModal / PostPage 的 close() 在 prefers-reduced-motion 时直接瞬时隐藏(跳过 0.16s 等待)。动画时长在 CSS 与 JS(setTimeout(done, 160))各有写死,改时长需两处同步。
尚无降级:music-player.css:62 的 music-spin 6s linear infinite(keyframes 定义在 :64),以及 main.css 的 wp-spin / wp-fade-in / wp-pop-in / wp-slide-in-right / wp-slide-in-left。
加新动画时建议补上
prefers-reduced-motion降级。
7.4 Swup 过渡样式
.transition-swup-fade { opacity: 1; will-change: opacity; transition: opacity .32s cubic-bezier(.22, 1, .36, 1); }
html.is-animating .transition-swup-fade { opacity: 0; transition: opacity .16s ease-in; } /* 淡出:旧内容渐隐 */
html.is-changing:not(.is-animating) .transition-swup-fade { opacity: 1; transition: opacity .35s cubic-bezier(.22, 1, .36, 1); } /* 淡入:新内容渐显 */
body > .swup-progress-bar { top: 0; z-index: 200; background-color: var(--wp-accent); }
| 想改什么 | 改哪里 |
|---|---|
| 换页淡出时长 | html.is-animating .transition-swup-fade 的 transition(ease-in) |
| 换页淡入时长 | html.is-changing:not(.is-animating) .transition-swup-fade 的 transition(cubic-bezier) |
| 顶部进度条颜色 | .swup-progress-bar 的 background-color |
| 关闭动画 | astro.config.mjs 的 swup({ animationClass: … }) 置空,或删掉 .transition-swup-fade 类 |
8. 局部样式修改速查
8.1 文章内标题左侧色条
markdown.css:
.post-content :is(h1,h2,h3,h4,h5,h6)::before {
content: ""; position: absolute; left: 0; top: 50%; translate: 0 -50%;
width: 4px; /* ← 色条宽度 */
border-radius: 2px;
background: var(--wp-accent); /* ← 色条颜色 */
height: calc(100% - var(--i) * 14%); /* ← 层级递减幅度 */
}
同时在 .post-content :is(h1..h6) 上设置了 padding-left: 0.9rem(给色条留位置)与 scroll-margin-top: calc(60px + 1rem)(锚点跳转避开吸顶头部,改头部高度时要同步这里)。
8.2 提示容器(:::info 等)
每类容器需要改两个位置:
| 类型 | 左边框色(markdown.css,写死) | 标题色变量(main.css) |
|---|---|---|
info | #6fa8dc | --wp-info-title |
tip | #7ba05b | --wp-tip-title |
warning | #d9a441 | --wp-warn-title |
danger | #c96a5b | --wp-danger-title |
details | var(--wp-text-tertiary) | 无(用默认文字色) |
容器共同样式:padding: 1em 1.25em、border-radius: var(--radius-wp)、border-left-width: 3px、background: var(--wp-surface)、font-size: 0.98rem。
8.3 圆角不统一的问题
--radius-wp: 8px 只在部分位置生效。实际引用 var(--radius-wp) 的文件(全仓 grep):markdown.css(.md-container :443、.img-frame :373/:395、blockquote :101、.md-toc :532)、ScrollControls.css:8、music-player.css:76/:135、FriendsPage.css:9。
以下位置是写死的 px,想全局改圆角需要一并替换:
| 位置 | 写死值 |
|---|---|
文章卡片 PostCard.astro | rounded-[12px] |
代码块 markdown.css | border-radius: 12px |
名片 .card(ProfileCard.css) | border-radius: 12px |
卡片角标 .card-badge(PostCard.css) | 12px 4px 12px 4px 等对角组合 |
| 分页按钮 | rounded(Tailwind 默认 4px) |
| 弹窗面板 | rounded-[8px] |
<main> / 页脚 | rounded-[8px] |
标签胶囊 TaxonomyPage(kind="tags") | rounded-[8px]、rounded-[3px] |
TOC 激活项右侧 PostPage.css | border-radius: 0 4px 4px 0(未用 --radius-wp) |
建议:若要做「全局圆角开关」,把上述硬编码替换为
var(--radius-wp)或新增--radius-card变量统一管理。
8.4 背景网格
body {
background-image: var(--wp-grid-image);
background-size: 28px 28px; /* ← 必须与 SVG 的 width/height 一致 */
background-attachment: fixed; /* ← 注释说明:限制背景跟随滚动 */
}
| 想改什么 | 做法 |
|---|---|
| 网格更密/更疏 | 同时改 SVG 的 width/height、viewBox 的 M0 0L28 0M0 0L0 28 以及 background-size |
| 网格线更明显 | 改 stroke-opacity(亮 0.14 / 暗 0.11) |
| 完全去掉网格 | background-image: none |
| 网格随页面滚动 | 删掉 background-attachment: fixed |
鼠标划过点亮(参考顶栏 hover 的实心主题色方块):BaseLayout 在 <body> 首子节点挂载 #grid-trail(position: fixed; z-index: -1,压在网格背景之上、页面内容之下),pointermove 按 28px 网格换算指针所在格子生成 .grid-trail-cell(var(--wp-accent) 实心方块、无圆角,grid-trail-out 0.65s 淡出后自动移除节点);快速划过时线性插值补齐跳过的格子形成连续轨迹。仅响应鼠标(pointerType === 'mouse'),prefers-reduced-motion 下不生成。关闭方法:删除 #grid-trail 节点与对应脚本及 main.css 的 .grid-trail-cell 样式。
卡片「另一套主题」预览方窗(ProfileCard 名片与 PostCard 文章卡共用机制):每张卡片内嵌一层 .card-preview 覆盖层(position: absolute; inset: -1px、overflow: hidden、border-radius: 12px、pointer-events: none、inert),覆盖层基础样式(窗口裁切、--preview-size、is-on 开关、reduced-motion 兜底)定义在 main.css 的共享块中,两种卡片都调用 src/lib/pointerPreview.ts 的 attachPointerPreview(card, options)(共用同一份结构、节流与动画时序,差异只在 options:prepareClone 定制克隆、syncClone 增量同步、resetOnDetach、hover 悬停镜像、resyncEvents)。鼠标在卡内移动时用 clip-path: inset(...) 只留出指针处 56×56 的方窗,窗口里是卡片克隆件套上相反主题 token 后的真实渲染(亮色模式给克隆加 .dark、暗色模式加 .light),所以看到的就是「切到另一套主题时长什么样」。克隆在脚本加载期构建一次并常驻,状态变化只做就地同步、从不重建节点(重建换新 <img> 要等解码,空帧在方窗里会表现为头像闪烁)。名片刷漆切换时钟时克隆层会跟着本体跑同一套 clip-path 关键帧(否则克隆层只能在全显/全裁之间二选一,方窗会在整段动效里停在切换前的画面),落定瞬间再由 profile-card-clock-settle 事件同步到终态;时钟模式下方窗照常唤起,显示反色时钟(亮段 --wp-accent 随反主题翻转)。文章卡克隆额外剔除 .card-badge / .card-actions 角标与操作条(方窗内不骑边),且因覆盖层 pointer-events: none 克隆收不到 :hover,脚本把指针命中的 data-hover 链接(标题/系列/标签)按序号镜像到克隆节点加 .preview-hover 类,由 PostCard.css 用克隆自身的反主题变量复刻 hover 效果。.light 与 :root 同级(main.css 的 :root, .light)且声明同一套亮色 token,专供这种「在子树里翻转主题」的场景。想改窗口尺寸改 main.css 的 --preview-size(仅此一处,两处卡片共享);关闭方法:删掉组件里的 .card-preview 节点与对应的 attachPointerPreview() 调用、main.css 的共享 .card-preview 样式块,以及 PostCard.css 的 .preview-hover 镜像规则(.light 可保留,它只是 token 作用域)。详见 02-组件说明.md#41-profilecardastro 与 02-组件说明.md#42-postcardastro。
8.5 滚动条
::-webkit-scrollbar { width: 0; height: 6px; } /* 竖向滚动条隐藏(宽度 0) */
::-webkit-scrollbar-thumb { background: var(--wp-border); border-radius: 3px; }
::-webkit-scrollbar-track { background: transparent; }
项目刻意隐藏了竖向滚动条,只保留 6px 的横向细滚动条(用于代码块/表格横向滚动)。若想恢复竖向滚动条,把
width: 0改成width: 8px。
9. 样式改动自检清单
- 亮色与暗色都检查过(
ScrollControls的主题按钮切换) - 硬编码颜色是否改为
var(--wp-*)或语义类 - 新增颜色是否同时定义了
:root与.dark两套值 -
--wp-grid-image里的stroke='%23XXXXXX'是否与新主色一致 -
AboutPage.astro的 16 处#E86233是否需要同步 - 移动端断点
768px/640px/400px是否正常 - 新增动画是否补了
prefers-reduced-motion降级 - 改头部高度后,是否同步了
markdown.css的scroll-margin-top -
pnpm check与pnpm build通过