视频加载失败

全站视觉移植复刻:把 Firefly 改造成 animal-island-ui 风格的七期工程(含复刻清单与踩坑全录)

3432 字
17 分钟
全站视觉移植复刻:把 Firefly 改造成 animal-island-ui 风格的七期工程(含复刻清单与踩坑全录)

全站视觉移植复刻:把 Firefly 改造成 animal-island-ui 风格的七期工程(含复刻清单与踩坑全录)#

2026-10-11 至 10-12,本博客完成了一次全站视觉移植:从原有主题换成 animal-island-ui(治愈系海岛风格 React 组件库)的设计语言。22 个文件修改(+275/−73 行)+ 6 个字体文件 + 33 份参考规范,功能零损失。

本文写给两类读者:

  • 未来的维护者(包括新会话的 AI 助手):读完应知道每一期改了哪些文件、为什么、精确色值在哪、验证基线是多少、哪些坑已经踩过;
  • 想复现的访客:文末「复刻清单」从零列出全部步骤,所有关键代码在正文中可照抄。

〇、决策:为什么不引库、也不从零重写#

起因是想把博客「完全变成 animal-island-ui 的风格」。三条路线对比后选了风格移植:

路线结论原因
直接安装它的 React npm 包❌本博客是 Astro 7 + Svelte 5 岛屿,混入 React 运行时得不偿失;且库里没有文章卡、目录、归档等博客部件
移植它的设计规范到现有站点✅风格 = CSS + design token,可整体搬运;内容、友圈、发布台、评论、搜索、性能优化成果全部保留
按它的风格从零重写博客❌功能与内容全部归零,按月计的工程量

关键依据:项目作者自己为「不装库只抄风格」留了官方通道——docs/design-system/css-variables.md 第一句就是 “When re-implementing the style without depending on the component library, declare the following variables on :root”。README 里那条 import 'animal-island-ui/style' 规则是库使用者的须知,不装库时不仅不适用(没有它的组件需要被样式化),而且不该引(会把整套 reset 和组件类灌进 Tailwind 体系打架)。

暗色模式:用户明确不做,所有改造只管亮色(涉及暗色的规则都用 :root:not(.dark) 或原样保留)。


一、准备工作:把规范拉到本地#

风格复刻的「唯一真相源」是仓库里的两处:

  1. 技能包 skills/animal-island-ui-style/(SKILL.md 中英双语 + 13 份组件 props 参考)
  2. 设计系统 docs/design-system/(design-tokens.md 精确值、design-rules.md 七律与 16 条反模式、components/ 逐组件像素级 CSS)

本机 raw.githubusercontent.com 直连不稳,用 api.github.com 的 contents 接口拿 base64 再解码最稳,一个 Node 脚本批量拉:

// 拉取单个文件:GET https://api.github.com/repos/<owner>/<repo>/contents/<path>
// 返回 JSON 的 content 字段即 base64,解码写盘即可
const res = await fetch(url, { headers: { Accept: "application/vnd.github+json" } });
const json = await res.json();
await writeFile(dest, Buffer.from(json.content, "base64").toString("utf8"));

共下载 33 个文件,落在 docs/animal-island-ui-style/(含 design-system/ 子目录)。本站后续所有色值都以这份本地规范为准,动样式前先查它。

技能包里 14 条「硬规则」中真正管视觉的核心几条:

  1. 不用纯黑文字(#000/#111),不用冷灰背景(#fafafa/#f5f5f5)
  2. 交互元素圆角 ≥ 12px,按钮和输入框一律 50px pill
  3. 3D 堆叠阴影(0 5px 0 #bdaea0)只属于 primary 按钮,default 类只用柔和抬升影
  4. 字体圆体(Nunito + Noto Sans SC),字重 body 500、标题 600–900,永不低于 400
  5. 动效统一 cubic-bezier(0.4, 0, 0.2, 1),时长 0.15–0.35s
  6. 焦点环:输入类黄色 #ffcc00、按钮薄荷 #19c8b9,绝不用冷蓝

核心 token(完整值见本地 design-system/css-variables.md):

:root {
--animal-primary: #19c8b9; /* hover #3dd4c6 / active #11a89b */
--animal-text: #794f27; /* 标题/主文字(大地棕) */
--animal-text-body: #725d42; /* 正文 */
--animal-text-secondary: #9f927d; /* 次级 */
--animal-bg: #f8f8f0; /* 页面底(羊皮纸) */
--animal-bg-content: rgb(247, 243, 223); /* 卡片/内容区(奶油) */
--animal-border: #c4b89e;
--animal-radius: 18px; --animal-radius-pill: 50px;
--animal-shadow-btn: #bdaea0; /* 3D 堆叠色 */
--animal-ease: cubic-bezier(0.4, 0, 0.2, 1);
}

二、七期实录#

一期:全局色板地基(4 文件)#

目标:全站从冷灰/白/黑换成羊皮纸 + 大地棕 + 薄荷。

文件改动
src/config/siteConfig.tsthemeColor.hue: 40 → 185。站点主色是 oklch 色相机制,185 ≈ #19c8b9 的薄荷色相,全站 var(--primary) 派生色自动跟随
src/utils/setting-utils.tsgetDefaultHue 兜底 "250" → "185"
src/styles/variables.styl亮色中性色板整块替换(见下表)+ 阴影暖化
src/styles/main.css@theme 里重定义 Tailwind 的 black(见下)

variables.styl 亮色 :root 关键映射(暗色块未动):

变量旧值新值
--primaryoklch(0.70 0.14 var(--hue))oklch(0.75 0.13 var(--hue))(hue=185 时精确对准 #19c8b9)
--page-bgoklch 灰#f8f8f0
--card-bgwhite#f7f3df
--card-bg-transparent白透明rgb(247 243 223 / …)
--btn-contentoklch#794f27
--btn-regular-bgoklch#fffbe7(hover #f4eeda / active #e9e1c6)
--btn-plain-bg-hover / activeoklch#efe9d6 / #fdfbf4
--deep-text近黑 oklch#794f27
--title-activeoklch#11a89b
--line-divider / --line-color冷黑透明rgba(121,79,39,.12) / .16
--card-border-colorblack#c4b89e
--meta-divider / --content-meta冷黑透明rgba(121,79,39,.22) / .72
--mutedoklch#f0ece2
--selection-bgoklch#e6f9f6
--license-block-bg冷黑透明rgba(121,79,39,.06)
--float-panel-bgwhite#f8f8f0

--inline-code-bg/color、--enter-btn-* 引用了上面的 btn 变量,自动跟随,无需单独改。链接、TOC、admonitions 等 var(--hue) 派生项在 hue 185 下自动变薄荷/青绿。

阴影冷黑 → 暖棕(rgba(61, 52, 40, ·),色值取自规范的 elevation 阴影):

--shadow-sm: 0 2px 4px rgba(61, 52, 40, 0.06);
--shadow-md: 0 3px 10px rgba(61, 52, 40, 0.1);
--shadow-lg: 0 8px 24px rgba(61, 52, 40, 0.14);
--shadow-xl: 0 12px 32px rgba(61, 52, 40, 0.18);
--shadow-navbar: 0 4px 16px rgba(61, 52, 40, 0.12);
--shadow-button: 0 3px 10px rgba(61, 52, 40, 0.16);

杠杆点:@theme 重定义 black(main.css):

@theme {
--color-black: #5c3a1a; /* 暖深棕,替代纯黑语义 */
}

Tailwind 的 text-black/75、bg-black/5 等全部引用主题变量,一行改动让全站 34 处散落的 text-black/* 和 .text-90/75/50 工具类自动转暖,不用逐个模板改。

二期:按钮系统(11 文件)#

关键机制(复刻必读):main.css 里 .btn-* 原有规则都在 @layer components(161–619 行的大块)内,而模板里散落的 rounded-lg 等是 utilities 层——层序上 utilities 永远赢。所以按钮的 pill 化规则刻意写在文件末尾、不进任何层:未分层的 normal 声明在级联里排最后,无条件压过所有分层规则。

/* main.css 末尾(未分层)—— animal-island 二期·按钮系统 */
.btn-plain, .btn-regular, .btn-card {
border-radius: 50px; /* 硬规则:按钮一律 pill */
transition:
background-color 0.15s var(--ease-standard),
color 0.15s var(--ease-standard),
box-shadow 0.2s var(--ease-standard),
transform 0.15s var(--ease-standard),
opacity 0.15s var(--ease-standard);
}
/* default 填充按钮:柔和抬升投影 rest → hover 加深 */
.btn-regular, .btn-card { box-shadow: 0 2px 4px 0 rgba(61,52,40,0.06); }
.btn-regular:hover, .btn-card:hover { box-shadow: 0 3px 10px 0 rgba(61,52,40,0.1); }
/* ghost 按钮透明底不投影,hover 填底后再给,避免浮出脏晕 */
.btn-plain:hover { box-shadow: 0 2px 4px 0 rgba(61,52,40,0.06); }
/* primary 主按钮:3D 堆叠阴影,全站唯一(硬规则 5) */
.btn-primary {
background-color: var(--primary);
color: #fff;
border-radius: 50px;
font-weight: 600;
box-shadow: 0 5px 0 0 #bdaea0;
transition:
background-color 0.15s var(--ease-standard),
color 0.15s var(--ease-standard),
box-shadow 0.15s var(--ease-standard),
transform 0.15s var(--ease-standard);
}
.btn-primary:hover {
background-color: #3dd4c6;
transform: translateY(-1px);
box-shadow: 0 6px 0 0 #bdaea0;
}
.btn-primary:active {
background-color: #11a89b;
transform: translateY(2px);
box-shadow: 0 1px 0 0 #bdaea0;
}

btn-primary 接入了 10 处真 CTA(只在原类上追加 class,背景/圆角/hover 由未分层规则统一接管):

404.astro、sponsor.astro、atom.astro、rss.astro、posts/[...slug].astro(赞助按钮×2)、EncryptedContent.astro(解锁提交)、AnimeCard/AnimeDetailModal、BilibiliCard/BilibiliDetailModal。

刻意不给 3D 的:分页当前页、筛选器激活态(状态指示非动作按钮)、导航栏 ghost 胶囊(只配柔和影)。

焦点环不用动——全站已有 outline: 2px solid var(--primary) 基线(main.css @layer components),默认色相下就是薄荷。

三期:导航栏 / 浮动 UI / 页脚(5 文件)#

  • navbar.css:吸顶滚动投影 rgba(0,0,0,.1) → rgba(61,52,40,.12);壁纸模式下拉/浮动面板的冷白玻璃 → 奶油玻璃 rgba(248,248,240,.55),冷黑描边 → rgba(121,79,39,.1)
  • FloatingControls.astro(阅读进度徽章):外框与圆环底轨 → rgba(121,79,39,.15)
  • FloatingButton.astro:冷黑 1px 框 → 同上
  • FloatingTOC.astro:空目录占位字 text-gray-500 → text-(--content-meta)
  • Footer.astro:零改动——它的 border-black/10 和 text-50 被一期的 --color-black 重定义自动救了

四期:卡片系统统一 + 灰阶大手术(2 文件)#

杠杆点二:整个灰阶重定义。全站 neutral 206 处 + gray 37 处冷灰,在 @theme 里整阶换成羊皮纸阶:

@theme {
--color-neutral-50: #fbfaf3; --color-gray-50: #fbfaf3;
--color-neutral-100: #f0ece2; --color-gray-100: #f0ece2;
--color-neutral-200: #e5ddc8; --color-gray-200: #e5ddc8;
--color-neutral-300: #c4b89e; --color-gray-300: #c4b89e;
--color-neutral-400: #b3a68d; --color-gray-400: #b3a68d;
--color-neutral-500: #9f927d; --color-gray-500: #9f927d;
--color-neutral-600: #8a7b66; --color-gray-600: #8a7b66;
--color-neutral-700: #725d42; --color-gray-700: #725d42;
--color-neutral-800: #5a4426; --color-gray-800: #5a4426;
--color-neutral-900: #44331c; --color-gray-900: #44331c;
--color-neutral-950: #2e2110; --color-gray-950: #2e2110;
}

卡片试点收编:早期在 PostCard.astro scoped 样式里做过一版写死色值的试点,四期把它删掉,升格为 main.css 末尾的全局规则(发现 ProjectCard 复用了 post-card-wrapper 类名,白捡覆盖):

/* 偏离规范硬规则 8(Card 无 box-shadow):本站页面底与卡片底同为羊皮纸、
色差极小,需要暖色抬升投影做分离;hover 上浮 -2px 与规范一致。 */
.post-card-wrapper.card-base,
.album-card.card-base {
background-color: var(--card-bg);
box-shadow: 0 3px 10px rgba(61, 52, 40, 0.1);
transition:
transform 0.3s var(--ease-standard),
box-shadow 0.3s var(--ease-standard),
background-color 0.15s ease;
}
.post-card-wrapper.card-base:hover,
.album-card.card-base:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(61, 52, 40, 0.14);
}
.post-card-wrapper .post-card-title { color: var(--deep-text); }
.post-card-wrapper a.post-card-title:hover,
.post-card-wrapper .post-card-title:hover { color: var(--title-active); }
.post-card-wrapper .post-card-title::before { background-color: var(--primary); }
.post-card-wrapper .description { color: var(--content-meta); }
.album-card .album-card-title { color: var(--deep-text); }
.album-card .album-card-title:hover { color: var(--title-active); }

banner 横幅没改:它的配色只有「照片上的白字 + 黑投影」,属于图片可读性机制,不在违规用色范围。

五期:Markdown 正文与代码块(2 文件)#

真相:文章正文一直吃的是 @tailwindcss/typography 插件的默认冷灰(body = gray-700 蓝灰、标题 = gray-900 冷近黑)。插件的 --tw-prose-* 变量挂在 .prose 类上(注意:这个类藏在 Astro 的 class:list 表达式里,grep class="...prose..." 抓不到)。

markdown.css 末尾用未分层规则整组覆盖,仅亮色生效:

:root:not(.dark) .prose,
:root:not(.dark) .custom-md {
--tw-prose-body: #725d42;
--tw-prose-headings: #794f27;
--tw-prose-lead: #794f27;
--tw-prose-bold: #5c3a1a;
--tw-prose-counters: #9f927d;
--tw-prose-bullets: #8a7b66;
--tw-prose-hr: #d4c9b4;
--tw-prose-quotes: #725d42;
--tw-prose-quote-borders: #d4c9b4;
--tw-prose-captions: #9f927d;
--tw-prose-code: #794f27;
}

代码块(expressive-code.css):亮色 one-light 默认冷白 #fafafa → 奶油,圆角对齐规范 20px:

html[data-theme='one-light'] body:not(.wallpaper-transparent)
.expressive-code .frame {
background-color: #f7f3df !important;
border: 1px solid rgba(121, 79, 39, 0.14);
border-radius: 20px;
}

壁纸透明模式的既有规则(半透明 --card-bg-transparent)保留,只补了 border-radius: 20px;两种模式条件互斥不冲突。行内代码、表格、blockquote 引条在前几期已随变量自动转暖。

排版期:Nunito 圆体接入(2 文件 + 6 字体)#

现状原为:正文 Latin = Work Sans,标题链 = Playfair + 思源宋体(衬线)——与规范「圆体无衬线」完全相反。

  1. 下载 Nunito 静态 woff2(fontsource 发行文件,latin 子集,6 字重共约 97KB)。jsdelivr/fastly/unpkg 三源轮询,失败回落 npm 塔包:
public/assets/fonts/nunito-latin-{400,500,600,700,800,900}-normal.woff2
  1. src/config/fontConfig.ts:

    • fontsList 新增 Nunito 定义(provider: "local",六个 variants 指向上列文件)
    • selected: ["--font-work-sans", …] → ["--font-nunito", "--font-hanyi-wenhei"](拉丁 Nunito,CJK 回退汉仪文黑)
    • headingFont: ["--font-playfair", "--font-noto-serif-sc"] → ["--font-nunito", "--font-hanyi-wenhei"](处刑衬线链)
    • bannerSubtitleFont: "--font-inter" → "--font-nunito"(横幅主标题 Zen Maru Gothic 本来就是圆体,保留)
  2. main.css:

    • @theme 的 --font-sans → 'Nunito', 'Noto Sans SC', …
    • 正文字重 500(规范 body 500):
@layer base {
body { font-weight: 500; }
}

必须放 base 层——放未分层会因层序压过 utilities 的 font-bold/font-medium,全站粗体全废。

改 fontConfig 后 dev server 必须重启(文件头注释写明),且首次冷启动约 57 秒(配置变更触发 content store 清空重建)——排查「dev 卡死」时先数到 60 秒再下结论。

终验:静态三件套#

检查结果
pnpm type-check0 错
pnpm check12 errors,全部为 anime.astro/vndb.astro/i18n 存量基线(原基线 17),改动文件零新增
pnpm build(带 NODE_OPTIONS=--use-system-ca)exit 0,Pagefind 33 页索引正常

浏览器探针法(改样式后即时验证,不开截图也能拿到铁证):

// 计算色值 / 字体 / 圆角
getComputedStyle(document.querySelector('.custom-md h2')).color
document.fonts.check('700 16px Nunito') // 字体是否真加载
// 变量挂载点排查:沿祖先链找 --tw-prose-body 在哪一层有值

三、关键机制与坑(复刻必读)#

  1. Tailwind v4 层序是本次的核心手法:@layer utilities > components > base,而未分层 > 所有分层。想让全局换肤盖过模板里散落的工具类(rounded-*、text-black/*),唯一干净解法是把规则写在层外。反过来,想让 utility 还能覆盖(如 font-bold),就必须把规则放进 base 层。
  2. --color-black / neutral / gray 三个主题变量是最大杠杆:改 3 行定义 = 全站 240+ 处冷色自动转暖。新写样式前先想「这属于哪个语义族」,能挂族就不写死。
  3. data-theme 属性是双语义:它同时是「Expressive Code 高亮主题名」(one-light/one-dark-pro)和「友圈 FCL 样式的明暗信号」。想做规范里的深巧克力代码块(#2b2118)需要让亮色模式也用暗色语法主题——会破坏友圈桥接,必须先把 EC 主题选择解耦到独立属性(如 themeSelector: 'html[data-ec-theme]')。这是本文搁置的可选工程。
  4. localStorage.hue 覆盖默认色相:站点机制是存储值优先于 siteConfig。移植后旧浏览器仍会用旧色相渲染出「奶油底+琥珀主色」的缝合怪,控制台 localStorage.removeItem("hue") 后刷新。
  5. grep 会被 Astro 语法骗:class:list={["prose …"]} 里的类名用 class="..." 模式搜不到,定位样式来源时优先浏览器探针(getComputedStyle),不要只信文本搜索。
  6. 本机 pnpm 是 ps1 脚本,Start-Process -FilePath "pnpm" 间歇报「不是有效 Win32 应用」,后台拉起要用 pnpm.cmd 全路径。
  7. Playwright 截图会等 document.fonts.ready,Google Fonts 被墙时永不返回——本机验证截图要么先拦截字体域名,要么走 CDP Page.captureScreenshot。

四、复刻清单(从零照做)#

  1. 下载规范:api.github.com 批量拉 skills/animal-island-ui-style/ + docs/design-system/ 到本地参考目录
  2. 定默认色相:换算目标主色的 oklch 色相,改 siteConfig.themeColor.hue 与 setting-utils.ts 兜底
  3. 改 variables.styl 亮色中性块 + 阴影暖化(对照本文一期映射表)
  4. main.css 的 @theme 加入 --color-black 与 neutral/gray 双阶暖色映射
  5. main.css 文件末尾(层外) 加按钮 pill / 柔和投影 / btn-primary 3D 规则,逐处给真 CTA 追加 btn-primary
  6. 导航栏、浮动组件里手写冷黑 rgba(0,0,0,·) 的地方换暖棕
  7. main.css 层外加卡片全局规则(对照四期代码)
  8. markdown.css 末尾加 :root:not(.dark) 守卫的 11 个 --tw-prose-*;expressive-code.css 加奶油代码块规则
  9. 下载 Nunito woff2 → fontConfig.ts 加定义并切 selected/headingFont → @theme --font-sans → @layer base 字重 500
  10. 重启 dev(等足 60 秒冷启动)→ pnpm type-check && pnpm check && pnpm build 三件套

五、未尽事项#

  • 深巧克力代码块(#2b2118 + #3d3028 描边):等 data-theme 解耦,方案见「坑 3」
  • 暗色模式:按用户要求未设计;现所有暖色规则以亮色为界,将来做暗色需另出一套映射
  • Noto Sans SC 替换汉仪文黑:CJK 已是黑体、视觉差距小,且要接 10MB+ 字体进子集管线,暂无必要
  • 筛选器激活态、分页当前页是「状态」而非「动作」,按规范不配 3D,但如要强化主按钮气质可再议
  • font-showcase.astro/png 两个未跟踪文件非本次工程产物,未纳入提交

六、验收状态#

七期全部完成并验收:type-check 0 错、check 12 错(全为存量)、build 全链路 exit 0。相关基建另有前作:13文章(阅读进度)、18文章(性能优化)、19文章(导航栏改造)。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

全站视觉移植复刻:把 Firefly 改造成 animal-island-ui 风格的七期工程(含复刻清单与踩坑全录)
https://azuma.mstzuomu.space/posts/azuma.zuomu-blog-20/
作者
左沐
发布于
2026-10-12
许可协议
CC BY-NC-SA 4.0
相关文章智能推荐
1
全站动效补完与 anime.js 数字滚动:五项动效计划的落地、四轮实测踩坑与复刻清单
技术分享完整记录 Firefly 博客一次「按门禁筛出来的动效改造」:五项动效计划(浮层开合/模态进出场/公告折叠/翻页桥接/切标签)的病灶定位、精确参数与四个实测才现形的 bug,以及用 anime.js 给侧栏统计做数字滚动的选型决策、v4 API 与零面积 IntersectionObserver 抢跑坑,附可照做的复刻清单
2
导航栏 UI 改造全记录:卡片边框、悬浮胶囊、三段收拢与 PC 收拢态快捷面板(附完整复现指南)
技术分享一次连续迭代的完整复盘——从卡片边框体系、悬浮胶囊导航,到三段结构拆分、下滑收拢动画,再到 PC 收拢态汉堡的控件快捷面板与"行点击原位弹卡"的两处根因修复,追加 PC 搜索控件图标化与收拢动画卡顿的逐帧诊断修复。每个批次改了哪些文件、为什么这么改、踩了哪些坑(CSS 层序、正圆公式、包含块、Vite 缓存、Playwright headless 动画冻结),附 git 重放与手工重做两条复现路径及验收清单
3
基于Firefly主题的列表封面位置改造
技术分享不想让封面挤在侧边?Firefly封面置顶记录
4
Firefly 博客实战:给文章页加阅读进度条与圆环百分比
技术分享从设计思路到逐文件代码,手把手为 Firefly/Astro 博客实现纯客户端阅读进度功能:顶部细条、右下角与悬浮按钮同款的圆环百分比,零网络请求,兼容 Swup 切页
5
Firefly 博客接入友链朋友圈:从零开始的完整部署教程
技术分享手把手教你为博客搭建自动更新的友链朋友圈,含全部代码改动与实战踩坑排查
随机文章随机推荐
Profile Image of the Author
陌殊途左沐
热爱是拯救无趣人生的唯一途径
公告
欢迎来到我的博客!这里是左沐的个人空间,分享我的学习、生活和兴趣爱好。希望你能在这里找到有趣的内容,请不要对我的喜好做出评价哦!请勿使用公网访问本站。
分类
访客信息
加载中...
标签
最新动态
站点统计
文章
30
分类
2
标签
52
总字数
58,096
运行时长
0 天
最后活动
0 天前
总浏览量
-
访客数
-
文章目录