<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/rss/feed.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>余弦の博客</title><description>WA 的一声就哭了</description><link>https://blog.cosine.ren</link><item><title>FE Bits Vol.27 | Oxfmt Beta 发布，Chromium「CSS 漏洞」实为 UAF</title><link>https://blog.cosine.ren/post/weekly-27</link><guid isPermaLink="false">weekly-27</guid><description>本期周刊：过完年恢复更新，同时开源了轻量级 macOS 划词翻译 MoePeek（Swift 6，约 5MB/50MB）。社区焦点：React 基金会迁入 Linux 基金会、TanStack 推出 Hotkeys、Oxfmt 进 Beta（Prettier 规则全兼容、多格式与 Tailwind 类排序）、Claude Code 上线远程控制；所谓“CSS 漏洞”实为 Chromium UAF。文章精选涵盖更安全的 Error.isError、显式资源管理 using/[Symbol.dispose]、高鲁棒 React 组件、十亿行虚拟滚动，以及 CSS 列表/zoom/sprites 实战。CSS 新特性关注 border-shape；工具与玩具包括 Modern CSS Snippets、CanWeUse 聚合、broz 截图浏览器、SVG Studio，并附多款精致 CodePen 与趣站。</description><pubDate>Sun, 01 Mar 2026 15:21:22 GMT</pubDate><content:encoded>
关于本周刊
&lt;div&gt;
&lt;p&gt;本期网址 &lt;a href=&quot;https://blog.cosine.ren/post/weekly-27&quot;&gt;https://blog.cosine.ren/post/weekly-27&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;本周刊更新时间期望是在每周天。&lt;/p&gt;
&lt;p&gt;推荐订阅本周刊的 &lt;a href=&quot;https://blog.cosine.ren/rss.xml&quot;&gt;RSS&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;公众号 前端周周谈 FE Bits，点击阅读原文链接可查看原文。&lt;/p&gt;
&lt;p&gt;QQ 讨论小群 598022684 / &lt;a href=&quot;https://discord.gg/XzvrvNMcSe&quot;&gt;Discord 群&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;本周刊文章内容同时也开源在 &lt;a href=&quot;https://github.com/yusixian/fe-bits-weekly&quot;&gt;fe-bits-weekly&lt;/a&gt;，欢迎关注。&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;今天是 2026 年 3 月 1 日，星期天。&lt;/p&gt;
&lt;h2&gt;个人动态&lt;a href=&quot;#个人动态&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;过年放假，给我放爽了，停更的这阵子疯狂写代码，肝项目，现在周刊恢复更新啦～&lt;/p&gt;
&lt;p&gt;想说些什么，却又有些不知道该说什么，等我更新年终总结吧（真有人 3 月份才写去年的年终总结啊）（哦原来那个人是我啊.webp）&lt;/p&gt;
&lt;p&gt;过完了年，也跳完了槽，在新公司干了一阵子之后，愈发觉得自己的选择没错，这阵子发生了很多～&lt;/p&gt;
&lt;p&gt;有关注我的前端频道的应该就能明白我最近在做什么，所以也在这里正儿八经发一下我们公司的招聘。招很多前端，有图像编辑器经验的话更好！岗位都在 JD 里了，二次元浓度越高越好（逃）欢迎佬来撩，远程办公，年限不限，实习兼职都可以，主要看适配程度，感兴趣的欢迎直接邮箱 &lt;code&gt;ball@mewtant.io&lt;/code&gt; 投递！备注一下 cos 内推的就行，也欢迎私聊我了解了解情况～&lt;/p&gt;
&lt;p&gt;我进来工作的感觉反正是灰常爽的！太二次元了很对我电波！&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;【我们的产品】我们是一家专注于研究二次元生成式 AI 的创业公司，目前已研发上线的 text-to-image AI 生成和消费平台已成为海外头部产品，用户量和市场占有率迅速增长中。&lt;br /&gt;
【我们的资本背景】现已获得 makers fund 等多家一线顶级机构的多轮投资，目前估值上亿美元。&lt;br /&gt;
【我们的团队】公司总部位于新加坡，五湖四海的小伙伴同样支持远程办公。公司团队成员背景丰富，技术能力扎实，没有无聊的潜规则，实习小天才和大师傅都能尽情发挥和学习。&lt;br /&gt;
【你能得到】获得赛道最前沿技术/产品认知，一起 work on 一个产品，切实体验让想象成为现实的经历，一段有意义且实现自身价值的工作。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;JD 相关： job.mewtant.io &lt;br /&gt;
公司相关的 pr：曾获红杉投资、估值上亿美元，二次元公司凭动漫向 AI 生图掘金海外&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;企业文化 Be Like（直接偷偷 saka 发的）&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;
&lt;h3&gt;MoePeek&lt;a href=&quot;#moepeek&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;之前 Easydict 经常漏内存太麻了，用久了就卡的要死，然后我的划词翻译需求又其实特别少，正好之前学了点 Swift 就自己过年顺手 vibe 写了一个（&lt;/p&gt;
&lt;p&gt;主要自己用，纯 Swift 6 构建，安装体积约 5 MB ，后台运行内存约 50 MB ，很放心的挂后台，如果开 OCR 截图的话运行内存差不多到 100 MB&lt;/p&gt;
&lt;p&gt;开源出来欢迎提 Issue ～ 但是不一定会加新功能了，会修修 bug&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这个项目有 PopClip 集成，就在 Release 中，README 里有常见问题说明&lt;/li&gt;
&lt;li&gt;成熟方案建议 Bob 或者 clicknow，这几个商业化的项目做的都很成熟&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PS：我真的很喜欢 Easydict 但是找他们的内存泄漏问题再改有点难了  hhh  索性 vibe 一个只加自己需要的功能&lt;/p&gt;
&lt;p&gt;一款轻量级 macOS 划词翻译工具，纯 Swift 6 开发，安装体积仅 5MB ，后台运行内存稳定约 50MB&lt;/p&gt;
&lt;p&gt;GitHub: &lt;a href=&quot;https://github.com/cosZone/MoePeek&quot;&gt;https://github.com/cosZone/MoePeek&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://github.com/cosZone/MoePeek/raw/main/Resources/MoePeek-promo.webp&quot; alt=&quot;MoePeek-promo.webp&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;社区动态&lt;a href=&quot;#社区动态&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://react.dev/blog/2026/02/24/the-react-foundation&quot;&gt;React 基金会：由 Linux 基金会托管的 React 新家园&lt;/a&gt;：React 官方宣布 React 基金会正式在 Linux 基金会（Linux Foundation）旗下成立，标志着 React 正式脱离 Meta 成为独立的开源项目。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://x.com/tan_stack/status/2022348762299904354&quot;&gt;TanStack 团队发布新库 TanStack Hotkeys&lt;/a&gt;，解决键盘快捷键开发中常见的跨平台、作用域冲突、输入框聚焦忽略等“小坑”，提供类型安全（Type-safety）、快捷键序列识别、状态追踪及 React 等框架适配器，并集成 Devtools 插件提升开发体验。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://oxc.rs/blog/2026-02-24-oxfmt-beta&quot;&gt;Oxfmt Beta&lt;/a&gt;：Oxfmt 宣布进入 Beta 阶段，自 Alpha 版本以来，Oxfmt 大幅扩展了功能，包括 100% 兼容 Prettier 的 JavaScript 和 TypeScript 格式化规则、支持多种文件格式（如 JSON, YAML, HTML, CSS 等）、内置 Tailwind CSS 类排序和可配置的导入语句排序功能，并提供了 Node.js API 和广泛的编辑器支持。（开始用好一阵子了，很香）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://x.com/claudeai/status/2026418435408986423&quot;&gt;Claude Code 发布远程控制功能&lt;/a&gt;
：Claude Code 发布了名为“远程控制”(Remote Control) 的新功能，允许 Max 和 Pro 用户在终端启动任务后，通过手机或网页端继续控制 Claude Code 会话，实现跨设备无缝工作。目前，该功能已向 Max 用户开放，并将很快扩展至 Pro 用户。用户可通过运行 &lt;code&gt;claude rc&lt;/code&gt; 命令开始使用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://css-tricks.com/an-exploit-in-css/&quot;&gt;An Exploit ... in CSS?!&lt;/a&gt;：很有意思，这篇文章提到了 Chromium 浏览器中一个被报道为“CSS 漏洞”的零日漏洞 (zero-day exploit) &lt;a href=&quot;https://nvd.nist.gov/vuln/detail/CVE-2026-2441&quot;&gt;CVE-2026-2441&lt;/a&gt;，并澄清了其技术细节，指出恶意并非源于 CSS 本身，而是 Chromium 渲染引擎中一个 Use After Free (UAF) 的内存管理缺陷。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;文章&lt;a href=&quot;#文章&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://allthingssmitty.com/2026/02/23/from-instanceof-to-error-iserror-safer-error-checking-in-javascript/&quot;&gt;从 instanceof 到 Error.isError：JavaScript 中更安全的错误检查&lt;/a&gt;：这篇文章介绍了在 JavaScript 中使用 &lt;code&gt;Error.isError()&lt;/code&gt; 代替 &lt;code&gt;instanceof Error&lt;/code&gt; 进行错误检查的优势，尤其是在处理跨域（cross-realm）错误时的安全性和可靠性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://shud.in/thoughts/build-bulletproof-react-components&quot;&gt;构建无懈可击的 React 组件&lt;/a&gt;：一位 Vercel 资深工程师分享如何构建能应对真实复杂场景的健壮 React 组件，涵盖服务端渲染（Server Rendering）、水合（Hydration）、多实例、并发渲染（Concurrent Rendering）、组合（Composition）、Portal、View Transition、Activity、数据泄露防护及未来兼容性等现代 React 应用中的关键挑战。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.sanity.io/blog/the-logo-soup-problem?ref=ww-rss&quot;&gt;Logo 泛滥问题（及其解决方案）&lt;/a&gt;：Sanity 团队开发了 React 组件  解决网页中多品牌 Logo 排列时因尺寸、比例与视觉重量不一致导致的“Logo Soup”混乱问题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://piccalil.li/blog/its-about-to-get-a-lot-easier-for-your-javascript-to-clean-up-after-itself/?ref=articles-rss-feed&quot;&gt;你的 JavaScript 清理自身的工作很快就会变得简单很多&lt;/a&gt;：JavaScript 即将通过 Explicit Resource Management 提案引入 &lt;code&gt;using&lt;/code&gt; 与 &lt;code&gt;[Symbol.dispose]&lt;/code&gt;，使资源清理更统一、可靠且自动化。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://piccalil.li/blog/an-in-depth-guide-to-customising-lists-with-css/?ref=articles-rss-feed&quot;&gt;深入解析 CSS 自定义列表样式指南&lt;/a&gt;：本文深入探讨了如何使用 CSS 从基础到高级全面自定义 HTML 列表样式，并兼顾了排版美学与无障碍访问。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://tkdodo.eu/blog/creating-query-abstractions&quot;&gt;Creating Query Abstractions&lt;/a&gt;：在 React 开发中，开发者习惯通过封装自定义 Hook 来复用 &lt;code&gt;useQuery&lt;/code&gt; 的逻辑。然而作者指出，在结合 TypeScript 时，这种做法不仅会导致类型推断 (Type Inference) 失效，还容易引发复杂的“泛型地狱”。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.stefanjudis.com/today-i-learned/css-zoom-to-scale-elements/&quot;&gt;Zoom!&lt;/a&gt;：解析了 CSS &lt;code&gt;zoom&lt;/code&gt; 属性在实际布局中的实用性，及其与 &lt;code&gt;transform: scale&lt;/code&gt; 的区别。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.joshwcomeau.com/animation/sprites/&quot;&gt;Remember sprites&lt;/a&gt;：2026，重新审视 CSS 图像精灵 (Sprites) 技术，结合 &lt;code&gt;object-fit&lt;/code&gt;、&lt;code&gt;object-position&lt;/code&gt; 和 &lt;code&gt;step()&lt;/code&gt; 动画函数，实现有趣的逐帧动画。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://rednegra.net/blog/20260212-virtual-scroll/&quot;&gt;Virtual Scrolling for Billions of Rows — Techniques from HighTable&lt;/a&gt;：本文介绍了 &lt;code&gt;&amp;lt;HighTable&amp;gt;&lt;/code&gt; React 组件中如何通过五种核心技术实现数十亿行数据的虚拟滚动 (Virtual Scrolling，实现高性能和可访问性。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;CSS 新特性&lt;a href=&quot;#css-新特性&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://una.im/border-shape/&quot;&gt;border-shape: the future of the non-rectangular web&lt;/a&gt;：本文深入介绍了即将到来的 CSS 属性 &lt;code&gt;border-shape&lt;/code&gt;，它将彻底改变构建非矩形网页元素的方式。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;趣站&lt;a href=&quot;#趣站&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://webgl.souhonzan.org/entry/?v=3100&quot;&gt;Matthew Pothier 先生的个人作品集网站，融合了色彩在重叠图层中晕染开来的视觉效果&lt;/a&gt;：一位电影摄影师极简且具有独特 WebGL 色彩流动效果的个人作品集网站。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;站点地址：&lt;a href=&quot;https://www.matthewpothier.com/&quot;&gt;Matthew Pothier-Cinematographer&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/02/5d2a5606f38507d635e1906977392334.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://kaomojicool.club/&quot;&gt;Express yourself with Unicode&lt;/a&gt;：Kaomoji Cool Club 收集了大量有趣的颜文字 (Kaomoji)，让你用 Unicode 字符表达自我。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/03/bd1aa5e00e558586672193b9c4a5c2d1.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;工具&lt;a href=&quot;#工具&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://modern-css.com/?ref=ww-rss&quot;&gt;Modern CSS Code Snippets&lt;/a&gt;：modern-css.com 是一个对比旧有 CSS 技巧与现代原生解决方案的参考网站，帮助开发者摆脱过时方法，利用最新 CSS 特性实现更简洁、高效的网页设计，很实用了，还有 RSS。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://canwe.dev/?ref=ww-rss&quot;&gt;Can We Use&lt;/a&gt;：&lt;code&gt;canwe.dev&lt;/code&gt; 是一个聚合了多种实用网页开发工具的网站，可以帮助开发者快速查询 Can I Use / Can I Stop、电子邮件客户端支持、可访问性信息、Web 平台功能进展及浏览器开发路线图等，为前端开发者提供一站式的信息查询服务。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/antfu/broz&quot;&gt;broz&lt;/a&gt;：antfu 出品，一款简洁、无边框的屏幕截图浏览器，&lt;code&gt;npx broz antfu.me&lt;/code&gt; 即可使用&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.svg.studio/&quot;&gt;SVG Studio&lt;/a&gt;，又一款基于浏览器的 SVG 处理工具，集成了优化、调试及修复渲染问题等多种功能。很好用。
&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/02/3d0370087c85d5a68f81dbbaeab53ce4.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/02/dbe1a5fd03d9a393038ee22527e9284b.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Codepen&lt;a href=&quot;#codepen&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;Flood Above the Floor&lt;a href=&quot;#flood-above-the-floor&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/wakana-k/pen/Eayqjwr&quot;&gt;Eayqjwr&lt;/a&gt; by wakana-k (&lt;a href=&quot;https://codepen.io/wakana-k&quot;&gt;@wakana-k&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;步入 Wakana Y.K. 创作的这个 Three.js 场景，你会发现自己置身于一个优雅的房间，积水没过脚踝。环顾四周进行探索，或者思考一下那些精美的装修正遭受着多么严重的水浸破坏。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/03/0726d2d45ab323a0a6aaeb512ec6f840.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;Sliding border glow on hover for beveled cards&lt;a href=&quot;#sliding-border-glow-on-hover-for-beveled-cards&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/thebabydino/pen/EayVXKj&quot;&gt;EayVXKj&lt;/a&gt; by thebabydino (&lt;a href=&quot;https://codepen.io/thebabydino&quot;&gt;@thebabydino&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ana Tudor 通过 &lt;code&gt;corner-shape&lt;/code&gt; 和 &lt;code&gt;background-clip&lt;/code&gt; 展示了现代 CSS 的大师级用法，以此回答了 Reddit 上的一个提问，并为尚不支持这些特性的浏览器提供了回退方案。请查看 Ana 那份注释极其详尽的代码，以了解具体实现细节。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;文本特效&lt;a href=&quot;#文本特效&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt;
  &lt;a href=&quot;https://codepen.io/collection/Poergz&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;https://codepen.io/collection/Poergz&lt;/div&gt;
          &lt;div&gt;codepen.io&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      
        
      
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;Pedro Ondiviela 分享了一系列醒目的 SVG 滤镜和 CSS 文本效果。每个效果都是可编辑的，因此你可以使用自己的文字进行尝试。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Aerodynamic Typography 空气动力学字体&lt;a href=&quot;#aerodynamic-typography-空气动力学字体&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/mike-at-redspace/pen/EayBZwy&quot;&gt;EayBZwy&lt;/a&gt; by mike-at-redspace (&lt;a href=&quot;https://codepen.io/mike-at-redspace&quot;&gt;@mike-at-redspace&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;使用鼠标滚轮控制风扇，在这个来自 mike-at-redspace 的有趣 Matter.js Pen 中观看字母飞舞并从墙壁上反弹。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/03/5debf56b71f277677cbbd746f48470df.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Refs&lt;a href=&quot;#refs&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://webweekly.email/archive/web-weekly-184/&quot;&gt;Web Weekly #184&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://codepen.io/spark/498&quot;&gt;Codepen Spark #498&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>category:周刊</category></item><item><title>FE Bits Vol.26 | Gatsby 支持 React 19，Rspress 2.0 发布</title><link>https://blog.cosine.ren/post/weekly-26</link><guid isPermaLink="false">weekly-26</guid><description>本期周刊回顾了作者在博客中新增的歌单与播放器功能，支持多种音乐列表和自定义语法，并介绍了Moe Copy AI的更新与深色模式。同时，汇总了Web平台最新动态，包括Vite、Gatsby、Oxc等项目的进展，以及CSS锚点定位、无JavaScript视频嵌入等实用技术，展现了前端生态的持续演进与创新。</description><pubDate>Sun, 08 Feb 2026 16:42:38 GMT</pubDate><content:encoded>
关于本周刊
&lt;div&gt;
&lt;p&gt;本期网址 &lt;a href=&quot;https://blog.cosine.ren/post/weekly-26&quot;&gt;https://blog.cosine.ren/post/weekly-26&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;本周刊更新时间期望是在每周天。&lt;/p&gt;
&lt;p&gt;推荐订阅本周刊的 &lt;a href=&quot;https://blog.cosine.ren/rss.xml&quot;&gt;RSS&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;公众号 前端周周谈 FE Bits，点击阅读原文链接可查看原文。&lt;/p&gt;
&lt;p&gt;QQ 讨论小群 598022684 / &lt;a href=&quot;https://discord.gg/XzvrvNMcSe&quot;&gt;Discord 群&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;本周刊文章内容同时也开源在 &lt;a href=&quot;https://github.com/yusixian/fe-bits-weekly&quot;&gt;fe-bits-weekly&lt;/a&gt;，欢迎关注。&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;今天是 2026 年 2 月 8 日，星期天。&lt;/p&gt;
&lt;p&gt;虽然发的有点晚，已经 0 点了。&lt;/p&gt;
&lt;p&gt;就快要过年放假啦！愉悦地停更俩星期！&lt;/p&gt;
&lt;p&gt;这周忙自己的项目很愉悦，给我的博客新增了以前的 shoka 语法兼容和&lt;a href=&quot;https://blog.cosine.ren/music&quot;&gt;歌单&lt;/a&gt;版块，全部可开关。&lt;/p&gt;
&lt;p&gt;这周把心心念念的博客歌单和播放器加上了，&lt;span&gt;对我就是故意在写这个语法让你们看看&lt;/span&gt;，并且添加了之前 Hexo Shoka 主题的 Markdown 扩展语法兼容支持。&lt;/p&gt;
&lt;p&gt;可在这里查看全部新增的语法演示：&lt;a href=&quot;https://koharu.cosine.ren/post/note/shoka-features&quot;&gt;Shoka 主题 Markdown 语法演示&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;然后增加音乐播放器和歌单页面，都可以开关～&lt;/p&gt;
&lt;p&gt;真的是一点一点变成我想要的样子了～&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://github.com/cosZone/astro-koharu/releases/tag/v2.6.0&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://github.com/fluidicon.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;github.com&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;Release v2.6.0 · cosZone/astro-koharu&lt;/h3&gt;
        &lt;p&gt;Complete #85
为 astro-koharu 博客添加 Hexo Shoka 主题的 Markdown 扩展语法兼容支持，实现了完整的从 Shoka 主题迁移所需的 Markdown 功能集。
可在这里查看全部新增的语法演示：Shoka 主题 Markdown 语法演示
刚上线可能还存在很多 bug，欢迎 issue 报告。
新增 Markdown 语法 文字特效: ++ins+…&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://github.com/cosZone/astro-koharu/releases/tag/v2.6.0&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://opengraph.githubassets.com/1df9397acb81185af7ecac2d7f26f33cefcb92555bf165c0f80a2f450a679713/cosZone/astro-koharu/releases/tag/v2.6.0&quot; alt=&quot;Release v2.6.0 · cosZone/astro-koharu&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;所以……现在我要激情安利&lt;a href=&quot;https://blog.cosine.ren/music&quot;&gt;山山歌单&lt;/a&gt;了!&lt;/p&gt;
&lt;p&gt;只要你喜欢山山和中 V 我们就是好朋友！&lt;span&gt;不喜欢也可以是&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;
&lt;p&gt;&lt;span&gt;山山嘿嘿嘿山山……&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/02/a0cf89fcc829ca1804efd94da6d25e1b.webp&quot; alt=&quot;BlockNote image&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;并且这周给 Moe Copy AI v0.3.5 新增了完整的 Prompt Template 管理功能，支持预设模版和自定义模版。之前还添加了深色模式！（是的没错都是我自用的功能）&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://moe.cosine.ren/docs/changelog&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://moe.cosine.ren/favicon.ico?favicon.d2f625b3.ico&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;moe.cosine.ren&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;MoeCopy AI - 萌萌哒的 AI 网页数据提取助手&lt;/h3&gt;
        &lt;p&gt;智能识别并提取网页中的结构化数据，为 AI 模型提供高质量输入&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://moe.cosine.ren/docs/changelog&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://moe.cosine.ren/img/banner.webp&quot; alt=&quot;MoeCopy AI - 萌萌哒的 AI 网页数据提取助手&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/02/c9b63e5b248226dbd560969277b41f0a.webp&quot; alt=&quot;BlockNote image&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;社区动态&lt;a href=&quot;#社区动态&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;GitHub 正在讨论开源项目维护者如何应对日益增加的低质量、AI 生成的拉取请求（PR），可能包括允许用户完全禁用 PR，或者至少限制 PR 只能由协作者访问等操作，可以在这里参加讨论：&lt;a href=&quot;https://github.com/orgs/community/discussions/185387&quot;&gt;Exploring Solutions to Tackle Low-Quality Contributions on GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.gatsbyjs.com/docs/reference/release-notes/v5.16/&quot;&gt;Gatsby v5.16&lt;/a&gt; 支持 React 19，&lt;a href=&quot;https://rspress.rs/blog/rspress-v2&quot;&gt;Rspress 2.0&lt;/a&gt; 重大升级&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://web.dev/blog/web-platform-01-2026?hl=en&quot;&gt;New to the web platform in January&lt;/a&gt;：汇总了 2026 年 1 月 Web 平台的重大进展，包括 Chrome 144 和 Firefox 147 稳定版的发布。其中 CSS 锚点定位 (CSS Anchor Positioning) 和 Navigation API 随着 Firefox 的支持，都正式达到 Baseline Newly Available（新可用基准）状态。此外，Chrome 144 引入了期待已久的 Temporal API、声明式的 &lt;code&gt;&amp;lt;geolocation&amp;gt;&lt;/code&gt; 元素以及用于自定义页内搜索样式的 &lt;code&gt;::search-text&lt;/code&gt; 伪元素&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.appinn.com/internet-archive-link-fixer/&quot;&gt;互联网档案馆发布插件，解决 40% 的互联网死链问题 - 小众软件&lt;/a&gt;：互联网档案馆（Internet Archive）推出了 WordPress 插件 Link Fixer，旨在解决互联网日益严重的“链接腐化”（Link Rot）问题。该插件通过自动检测失效的 URL（如 404 错误），并引导用户访问 Wayback Machine 中存储的历史网页快照，据称能修复约 40% 的死链。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;ViteLand 最新消息：2026 年 1 月回顾&lt;a href=&quot;#viteland-最新消息2026-年-1-月回顾&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt;
  &lt;a href=&quot;https://voidzero.dev/posts/whats-new-jan-2026&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://voidzero.dev/favicon.svg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;voidzero.dev&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;What’s New in ViteLand: January 2026 Recap&lt;/h3&gt;
        &lt;p&gt;Our January 2026 recap features the unified redesign across VoidZero, Vite, Vitest, Rolldown, and Oxc websites, plus updates across all projects and community highlights.&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://voidzero.dev/posts/whats-new-jan-2026&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://voidzero.dev/covers/update-2026-jan.jpg&quot; alt=&quot;What’s New in ViteLand: January 2026 Recap&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;h4&gt;品牌与设计统一&lt;a href=&quot;#品牌与设计统一&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;实现了 VoidZero 旗下所有项目（Vite, Vitest, Rolldown, Oxc）的视觉身份统一，发布了全新的官网和 Logo。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;更新了 Vite 的启动模板（Starter Templates），将新品牌标识内置其中。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;强调了工具链的深度集成：Vite 8 在底层直接使用 Rolldown 和 Oxc，实现了一致的开发者体验。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;核心项目进展&lt;a href=&quot;#核心项目进展&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Vite：自 2020 年发布以来，累计下载量超过 30 亿次；React 服务端组件（React Server Components, RSC）插件进行了优化，以支持 TanStack Start 等框架。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Vitest：启动 4.1 测试版（Beta），引入测试标签（Test Tags）功能，并支持通过禁用 viteModuleRunner 选项在脱离 Vite 的情况下运行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Rolldown：正式达成候选发布版（Release Candidate, RC）里程碑，API 进入稳定阶段；推出“Lazy Barrel Optimization”，在 AntDesign 等场景下可减少 92% 的模块编译，提速 2 倍。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Oxc：Oxlint 支持 oxlint.config.ts 动态配置；Oxfmt 实现了与 Prettier 100% 的一致性（Conformance），并新增了 Tailwind CSS 类名排序功能。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;社区动态与生态&lt;a href=&quot;#社区动态与生态&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;榜单表现：在 2025 JavaScript Rising Stars 榜单中，Vite、Oxc、Rolldown 和 tsdown 均名列前茅。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;生态迁移：Hugging Face、Turborepo 和 Lichess 等知名项目宣布从 ESLint/Prettier 迁移至 Oxlint/Oxfmt。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;周边工具：社区推出了基于 Oxc 的代码体积优化工具 jsshaker 以及 Oxlint 的终端用户界面工具 oxlint-tui。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;文章&lt;a href=&quot;#文章&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://thedailywtf.com/articles/a-percise-parser&quot;&gt;A Percise Parser&lt;/a&gt;：本文介绍了一个因硬编码地区数值格式（Locale Formatting）并使用拙劣的字符串操作，导致国际化失效的 JavaScript 解析器（Parser）案例。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://kizu.dev/shrinkwrap-solution/&quot;&gt;Solving Shrinkwrap: New Experimental Technique&lt;/a&gt;&lt;br /&gt;
好文，利用 CSS Anchor Positioning（锚点定位）和 Scroll-Driven Animations（滚动驱动动画）解决网页排版中“自动换行导致的容器宽度无法自动收缩（Shrinkwrap）”这一经典难题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://frontendmasters.com/blog/performance-optimized-video-embeds-with-zero-javascript/&quot;&gt;Performance-Optimized Video Embeds with Zero JavaScript&lt;/a&gt;：本文介绍了一种利用原生 HTML 标签 &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; 和 &lt;code&gt;&amp;lt;summary&amp;gt;&lt;/code&gt; 来优化视频嵌入性能的方法。通过这种方案，开发者可以在完全不使用 JavaScript 的情况下实现视频的按需加载。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2026/02/css-scope-alternative-naming-conventions/&quot;&gt;CSS @scope: An Alternative To Naming Conventions And Heavy Abstractions&lt;/a&gt;：介绍如何利用 CSS 原生的 &lt;code&gt;@scope&lt;/code&gt; 规则来替代复杂的 BEM 命名规范或繁重的 CSS 框架，实现更简洁、可维护的样式隔离。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://css-tricks.com/css-bar-charts-using-modern-functions/&quot;&gt;CSS Bar Charts Using Modern Functions | CSS-Tricks&lt;/a&gt;：本文介绍了如何利用 CSS 现代函数 &lt;code&gt;sibling-index()&lt;/code&gt; 和增强后的 &lt;code&gt;attr()&lt;/code&gt; 函数，以更简洁、高效的方式构建纯 CSS 柱状图。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;新特性&lt;a href=&quot;#新特性&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nerdy.dev/nice-select?utm_source=rss&quot;&gt;Nice Select · February 3, 2026&lt;/a&gt;：本文展示了如何利用最新的 CSS 特性（如 &lt;code&gt;appearance: base-select&lt;/code&gt;）在保持原生无障碍性的同时，打造高度可定制且视觉华丽的 &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; 下拉组件。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
  &lt;a href=&quot;https://codepen.io/editor/argyleink/pen/019c1f28-bbc2-7bac-ad4a-a7e41d3730f1&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;CodePen - editor&lt;/div&gt;
          &lt;div&gt;Pen: pen&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      
        
      
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://rolandfranke.nl/frontend-stories/drawing-connections-with-css-anchor-positioning/&quot;&gt;Drawing Connections with CSS Anchor Positioning - Roland Franke&lt;/a&gt;：锚点定位真好玩儿吧。在无需 JavaScript 的情况下实现 UI 元素（如评论与其回复）之间的视觉连线。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/ROL4ND909/pen/gbMxLdL&quot;&gt;gbMxLdL&lt;/a&gt; by ROL4ND909 (&lt;a href=&quot;https://codepen.io/ROL4ND909&quot;&gt;@ROL4ND909&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;h2&gt;工具&lt;a href=&quot;#工具&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jamiepine/voicebox&quot;&gt;jamiepine/voicebox&lt;/a&gt; 一款由 Qwen3-TTS 驱动的开源、本地优先语音克隆与合成工具&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Codepen 与趣站&lt;a href=&quot;#codepen-与趣站&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;不规则网格上带有凹面圆角的卡片&lt;a href=&quot;#不规则网格上带有凹面圆角的卡片&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/thebabydino/pen/WbxpKPQ&quot;&gt;WbxpKPQ&lt;/a&gt; by thebabydino (&lt;a href=&quot;https://codepen.io/thebabydino&quot;&gt;@thebabydino&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“卡片和按钮之间的缝隙实现了真正的透明效果。没有尖角，全部都是圆角。除了 .jpg 格式的卡片背景图外，没有其他图片。卡片形状会根据按钮的大小和形状进行调整——您可以尝试只更改按钮元素的字体大小来查看效果。这不需要任何 JavaScript 代码——一切都归功于 CSS 子网格的神奇功能！” by Ana Tudor&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/02/37b2d1691b9e51e8f8297d681dd55ac2.webp&quot; alt=&quot;BlockNote image&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;带有自定义 select 的文件夹堆叠&lt;a href=&quot;#带有自定义-select-的文件夹堆叠&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/captainbrosset/pen/dPXdgae&quot;&gt;dPXdgae&lt;/a&gt; by captainbrosset (&lt;a href=&quot;https://codepen.io/captainbrosset&quot;&gt;@captainbrosset&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.chrome.com/blog/a-customizable-select&quot;&gt;现在选择元素是可以自定义的&lt;/a&gt;，Patrick Brosset 向我们展示了点击后会弹出的文件夹堆叠，这给我们带来了很多乐趣。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/02/85203d57c955d41b833849bfdb16c99d.webp&quot; alt=&quot;BlockNote image&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;二进制时钟&lt;a href=&quot;#二进制时钟&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/prisoner849/pen/zxBpPYW&quot;&gt;zxBpPYW&lt;/a&gt; by prisoner849 (&lt;a href=&quot;https://codepen.io/prisoner849&quot;&gt;@prisoner849&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;prisoner849 在这段 Three.js 代码中展示了一款精美的时钟。即使你不懂&lt;a href=&quot;https://en.wikipedia.org/wiki/Binary_clock&quot;&gt;二进制时间&lt;/a&gt;，也能欣赏它的美——拿起时钟转动一下，从各个角度欣赏它塑料材质的光泽。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/02/7324e64ce37bdd35a183678a166e493f.webp&quot; alt=&quot;BlockNote image&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Refs&lt;a href=&quot;#refs&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://javascriptweekly.com/issues/771&quot;&gt;JavaScript Weekly #771&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://codepen.io/spark/495&quot;&gt;Codepen Spark #495&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>category:周刊</category></item><item><title>FE Bits Vol.25 | Yarn 6 将用 Rust 重写，CSS Grid Lanes 进展</title><link>https://blog.cosine.ren/post/weekly-25</link><guid isPermaLink="false">weekly-25</guid><description>
本期网址 https://blog.cosine.ren/post/weekly-25
本周刊更新时间期望是在每周天。
推荐订阅本周刊的 RSS。
公众号 前端周周谈 FE Bits，点击阅读原文链接可查看原文。
QQ 讨论小群 598022684 / Discord 群</description><pubDate>Sun, 01 Feb 2026 15:20:36 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本期网址 &lt;a href=&quot;https://blog.cosine.ren/post/weekly-25&quot;&gt;https://blog.cosine.ren/post/weekly-25&lt;/a&gt;&lt;br /&gt;
本周刊更新时间期望是在每周天。&lt;br /&gt;
推荐订阅本周刊的 &lt;a href=&quot;https://blog.cosine.ren/rss.xml&quot;&gt;RSS&lt;/a&gt;。&lt;br /&gt;
公众号 前端周周谈 FE Bits，点击阅读原文链接可查看原文。&lt;br /&gt;
QQ 讨论小群 598022684 / &lt;a href=&quot;https://discord.gg/XzvrvNMcSe&quot;&gt;Discord 群&lt;/a&gt;&lt;br /&gt;
本周刊文章内容同时也开源在 &lt;a href=&quot;https://github.com/yusixian/fe-bits-weekly&quot;&gt;fe-bits-weekly&lt;/a&gt;，欢迎关注。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;今天是 2026 年 2 月 1 日，星期天。&lt;/p&gt;
&lt;p&gt;已经 2 月了啊，好快哦。&lt;/p&gt;
&lt;h2&gt;个人动态&lt;a href=&quot;#个人动态&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;碎碎念&lt;a href=&quot;#碎碎念&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;看了超时空辉夜姬，给我看的好感动……好久没看到这么令人感动的国宴了……&lt;/p&gt;
&lt;p&gt;简直是神，还是真百，推荐所有人观看美少女贴贴，在我心里是作画运镜无死角全方面的神作～&lt;/p&gt;
&lt;p&gt;撒糖撒糖还是撒糖，后劲也大，全程都在库库截图，我没有什么不满的我觉得美少女特别有活力的贴贴特别好。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/e4c28d8555e361e7bd0f9ee55e4838de.webp&quot; alt=&quot;太甜了吧！&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/da6440716dc4a4cf25cf3d76ad422272.webp&quot; alt=&quot;两只小可爱！&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;上一次看完有如此感慨的还是游戏人生剧场版……但是那个高度……&lt;/p&gt;
&lt;p&gt;这部的优缺点都很明显，在强无敌的作画下剧情的平平无奇反而变成了短板，但是我觉得还是瑕不掩瑜，非常非常值得一看！&lt;/p&gt;
&lt;p&gt;泛式说得好啊：&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://www.bilibili.com/video/BV1Fcz1BZEVz&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;https://www.bilibili.com/video/BV1Fcz1BZEVz&lt;/div&gt;
          &lt;div&gt;bilibili.com&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      
        
      
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;这个杂谈说得也很好：&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://www.bilibili.com/video/BV1w8zrBHEZC&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;https://www.bilibili.com/video/BV1w8zrBHEZC&lt;/div&gt;
          &lt;div&gt;bilibili.com&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      
        
      
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;h3&gt;项目更新&lt;a href=&quot;#项目更新&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;给博客做了以下更新。&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://github.com/cosZone/astro-koharu/releases/tag/v2.5.0&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://github.com/fluidicon.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;github.com&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;Release v2.5.0 · cosZone/astro-koharu&lt;/h3&gt;
        &lt;p&gt;What’s Changed v2.5.0 by @yusixian in #72 修复了一个 slug 大小写不匹配 的 bug，当博客文件用 PascalCase 命名（如 MyPost.md）时，AI 摘要和相关文章推荐会加载失败，改为生成时统一小写 #70 优化 SEO 新增独立的无后端 CMS 管理应用，支持文章管理、浏览器内编辑、Markdown 预览等功能。 #75…&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://github.com/cosZone/astro-koharu/releases/tag/v2.5.0&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://opengraph.githubassets.com/d68ce383a4b0d8a876f0eeda7519e5aaf2f5513336b059bf0b767737b99233fc/cosZone/astro-koharu/releases/tag/v2.5.0&quot; alt=&quot;Release v2.5.0 · cosZone/astro-koharu&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;a href=&quot;https://github.com/cosZone/astro-koharu/releases/tag/v2.5.1&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://github.com/fluidicon.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;github.com&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;Release v2.5.1 · cosZone/astro-koharu&lt;/h3&gt;
        &lt;p&gt;What’s Changed v2.5.1 by @yusixian in #78 Fixes #77 #76 统一时区处理，支持在 config/site.yaml 配置站点时区
修复 gray-matter 将 YAML 日期错误解析为 UTC 的问题
修复 CMS 前端 JSON.stringify 导致日期时区偏移的问题
增强链接预览，为链接预览功能添加图片错误处理机制 Fu…&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://github.com/cosZone/astro-koharu/releases/tag/v2.5.1&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://opengraph.githubassets.com/f225b05925465c4ec2d6fffb370f4e660dd4a9ec11f2f5014e1fd2bb0d6e2575/cosZone/astro-koharu/releases/tag/v2.5.1&quot; alt=&quot;Release v2.5.1 · cosZone/astro-koharu&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;给 Moe Copy AI 插件做了深色模式，还在持续优化中。&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://github.com/yusixian/moe-copy-ai/releases/tag/0.3.3&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://github.com/fluidicon.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;github.com&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;Release v0.3.3 · yusixian/moe-copy-ai&lt;/h3&gt;
        &lt;p&gt;文档 | Chrome 商店 | Firefox 附加组件
本次更新实现完整的深色模式支持，并对整个设计系统进行了重构。 v0.3.3 by @yusixian in #37 新功能 主题选择能力：集成全局 Theme Context，组件统一感知主题
使用 Tailwind CSS 插件与集中化主题变量，统一主题管理
UI 组件支持主题感知样式并改进类名管理 重构 组件样式全面迁…&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://github.com/yusixian/moe-copy-ai/releases/tag/0.3.3&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://opengraph.githubassets.com/81340f92b29eb38686860ad0ddd6be2ee1b46fdd29a65b23c923c156a88fbcf0/yusixian/moe-copy-ai/releases/tag/0.3.3&quot; alt=&quot;Release v0.3.3 · yusixian/moe-copy-ai&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/02/2b63baa07b19d4dd20d14233b7628790.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;社区动态&lt;a href=&quot;#社区动态&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://webkit.org/blog/17758/when-will-css-grid-lanes-arrive-how-long-until-we-can-use-it/&quot;&gt;When will CSS Grid Lanes arrive? How long until we can use it?&lt;/a&gt;：探讨了 CSS Grid Lanes（原生瀑布流布局）在各浏览器的实现进度，并详细介绍了如何通过渐进增强（Progressive Enhancement）技术在当下提前使用这一新特性。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Safari 已经在技术预览版中支持&lt;/li&gt;
&lt;li&gt;Firefox 在 2020 年最早实现了早期草案，目前正致力于更新至最新的规范语法和 &lt;code&gt;flow-tolerance&lt;/code&gt; 属性&lt;/li&gt;
&lt;li&gt;Chrome 与 Edge 的加入：Chromium 团队在经历了语法争议后，目前已达成共识并正在更新其实现代码，标志着三大引擎达成一致。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://yarn6.netlify.app/blog/2026-01-28-yarn-6-preview/&quot;&gt;Yarn 6 预览版发布&lt;/a&gt;，为了极致性能正转向使用 Rust 语言重构，预计 2026 年 Q3 发布稳定版。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.joshtumath.uk/posts/2026-01-27-try-text-scaling-support-in-chrome-canary/&quot;&gt;Try text scaling support in Chrome Canary&lt;/a&gt;：介绍 Chrome Canary 中新增的 &lt;code&gt;&amp;lt;meta name=&quot;text-scale&quot;&amp;gt;&lt;/code&gt; 标签，该功能允许网页响应移动端操作系统的全局字体大小设置。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;React 紧急发布 19.2.4、19.1.5 和 19.0.4 版本，修复了 React Server Components (RSC) 中未完全解决的拒绝服务 (DoS) 漏洞：&lt;a href=&quot;https://github.com/facebook/react/security/advisories/GHSA-83fc-fqcc-2hmg&quot;&gt;Denial of Service Vulnerabilities in React Server Components&lt;/a&gt; (真累啊)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://vercel.com/blog/agents-md-outperforms-skills-in-our-agent-evals&quot;&gt;Vercel 的最新研究&lt;/a&gt;表明，在针对 Next.js 16 开发的最新评估中，他们发现将 8KB 的文档索引塞进 AGENTS.md 文件中效果更好，因为 skills 无法可靠地触发。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SolidJS 创始人 Ryan Carniato 发布 &lt;a href=&quot;https://dev.to/this-is-learning/javascript-frameworks-heading-into-2026-2hel&quot;&gt;JavaScript Frameworks - Heading into 2026&lt;/a&gt;，分析当前充满活力的前端开发领域。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;优质文章&lt;a href=&quot;#优质文章&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2026/01/unstacking-css-stacking-contexts/&quot;&gt;Unstacking CSS Stacking Contexts — Smashing Magazine&lt;/a&gt;：非常好的好文章！条理清晰，深入剖析 CSS 层叠上下文 (Stacking Contexts) 的工作原理，解释为什么 &lt;code&gt;z-index&lt;/code&gt; 会失效，并提供实用的调试技巧与解决方案。被安利了 &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=mikerheault.vscode-better-css-stacking-contexts&quot;&gt;Better CSS Stacking Contexts&lt;/a&gt; 这个 vscode 插件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://css-tricks.com/there-is-no-need-to-trap-focus-on-a-dialog-element/&quot;&gt;There is No Need to Trap Focus on a Dialog Element&lt;/a&gt;：介绍在使用原生 &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; 元素时，为什么开发者不再需要手动实现复杂的焦点陷阱（Focus Trap）逻辑。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.blog/engineering/from-pixels-to-characters-the-engineering-behind-github-copilot-clis-animated-ascii-banner/&quot;&gt;From pixels to characters: The engineering behind GitHub Copilot CLI’s animated ASCII banner&lt;/a&gt;：本文深入探讨了为 GitHub Copilot CLI 开发 3 秒 ASCII 动画背后的复杂工程实现，涵盖了终端渲染限制、色彩一致性及无障碍设计。
&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/02/a0bab00540fdb5e998994464a75cce10.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;10 Tips for Claude Code&lt;a href=&quot;#10-tips-for-claude-code&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt;&lt;/div&gt;
&lt;p&gt;Claude Code 的开发者 Boris Cherny 分享了其使用 Anthropic 最新推出的 Claude Code 命令行工具的进阶技巧，涵盖自动化、调试、学习模式及多代理协作。&lt;/p&gt;
&lt;h4&gt;1. 自动化与技能扩展（Skills &amp;amp; Automation）&lt;a href=&quot;#1-自动化与技能扩展skills--automation&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;建议将每日重复操作转化为自定义技能（Skills），并提交至 Git 仓库供跨项目复用。&lt;/li&gt;
&lt;li&gt;创建 &lt;code&gt;/techdebt&lt;/code&gt; 指令，在每个会话结束时运行，用于发现并消除重复代码。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2. 高效调试与流程集成（Debugging &amp;amp; CI/CD）&lt;a href=&quot;#2-高效调试与流程集成debugging--cicd&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;启用 Slack MCP（Model Context Protocol），直接将 Slack 中的 Bug 讨论贴入 Claude 进行修复，减少上下文切换（Context switching）。&lt;/li&gt;
&lt;li&gt;授权 Claude 自动修复失败的 CI 测试，或通过分析 Docker 日志定位问题，无需微观管理（Micromanage）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3. 提示词工程与代码评审（Prompting &amp;amp; Code Review）&lt;a href=&quot;#3-提示词工程与代码评审prompting--code-review&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;让 Claude 扮演评审者（Reviewer），通过“对我提交的代码进行质询，直到通过测试再创建 PR”等提示词提升质量。&lt;/li&gt;
&lt;li&gt;要求 Claude 对比主分支（Main branch）和功能分支的行为差异，证明代码的有效性。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;4. 环境配置与工具优化（Environment &amp;amp; Setup）&lt;a href=&quot;#4-环境配置与工具优化environment--setup&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;推荐使用 Ghostty 终端以获得更好的同步渲染和 24 位色彩支持。（我用 warp 也很好用）&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;/statusline&lt;/code&gt; 自定义状态栏，实时显示上下文使用情况（Context usage）和当前 Git 分支。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;5. 复杂任务与数据分析（Subagents &amp;amp; Analytics）&lt;a href=&quot;#5-复杂任务与数据分析subagents--analytics&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在请求中加入 “use subagents” 指令，让 Claude 投入更多算力（Compute）处理复杂问题，并保持主代理上下文整洁。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;通过编写技能调用 BigQuery (bq) 等命令行工具，直接在 Claude Code 中进行实时数据指标分析。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;6. 深度学习与知识内化（Learning &amp;amp; Understanding）&lt;a href=&quot;#6-深度学习与知识内化learning--understanding&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;在 &lt;code&gt;/config&lt;/code&gt; 中开启“解释模式”（Explanatory/Learning output style），让 Claude 解释代码变更背后的原因（Why）。 (我觉得这个解释模式真的好用)&lt;/li&gt;
&lt;li&gt;利用 Claude 生成视觉化的 HTML 演示文稿（Slides）或 ASCII 图表，辅助理解陌生的代码库或协议。&lt;/li&gt;
&lt;li&gt;构建间隔重复（Spaced-repetition）学习技能，通过问答形式填补知识盲区。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;新特性&lt;a href=&quot;#新特性&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://css-tricks.com/how-to-style-the-new-search-text-and-other-highlight-pseudo-elements/&quot;&gt;如何为新的 ::search-text 和其他高亮伪元素设计样式&lt;/a&gt;：介绍 Chrome 144 新推出的 &lt;code&gt;::search-text&lt;/code&gt; 伪元素，以及如何利用 CSS 相对颜色语法统一美化各种文本高亮样式。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.zhangxinxu.com/wordpress/2026/01/insertbefore-movebefore-dom/&quot;&gt;告别 insertBefore，使用 moveBefore 移动 DOM 元素&lt;/a&gt; 介绍了原生 DOM 新 API &lt;code&gt;moveBefore&lt;/code&gt; 的用法、优势及其在 Web Components 中的应用，并对比了传统 &lt;code&gt;insertBefore&lt;/code&gt; 的局限性。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;工具&lt;a href=&quot;#工具&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/benjitaylor/agentation&quot;&gt;agentation&lt;/a&gt;：一款为 AI Agents 设计的视觉反馈工具，通过点击并添加标注，随后生成包含元素路径、CSS 选择器（Selectors）、位置等详细上下文的 Markdown 文本。&lt;a href=&quot;https://agentation.dev&quot;&gt;文档&lt;/a&gt;| &lt;a href=&quot;https://x.com/benjitaylor/status/2014109590972145908&quot;&gt;Tweet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/7707ab61737d10cd6788684bd391b8ee.webp&quot; alt=&quot;agentation 演示&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这个 &lt;a href=&quot;https://marginlab.ai/trackers/claude-code/&quot;&gt;MarginLab&lt;/a&gt; 挺有意思的，一个针对 Claude Code 和 Codex 的独立性能监控工具，模拟真实用户编码场景来监测大模型的“降智”，&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;/div&gt;
&lt;h2&gt;趣站与 Codepen 精选&lt;a href=&quot;#趣站与-codepen-精选&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;React Tilt Button - 3D Tactile React Button Component&lt;a href=&quot;#react-tilt-button---3d-tactile-react-button-component&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;好喜欢这个！感觉是很巧妙的设计。
一个具备 3D 触感、支持倾斜和深度效果的 React 交互按钮组件。它利用 CSS 的 3D 变换（3D Transforms）技术，为传统的按钮元素注入了物理深度（Depth）和动态倾斜（Tilt）效果。组件不仅内置了从复古游戏（Arcade）到现代金属（Steel）等多种预设主题，还开放了高度细粒度的自定义参数，让开发者可以轻松调整按钮的海拔高度（Elevation）、圆角（Radius）及光泽感（Glare），从而打造出极具表现力的用户界面。&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://react-tilt-button.vercel.app/&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://react-tilt-button.vercel.app/vite.svg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;react-tilt-button.vercel.app&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;React Tilt Button - 3D Tactile React Button Component&lt;/h3&gt;
        &lt;p&gt;A beautiful 3D tactile React button with squishy press, hover tilt, depth and smooth interactions. Perfect for modern UI and landing pages.&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://react-tilt-button.vercel.app/&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://react-tilt-button.vercel.app/og-image.png&quot; alt=&quot;React Tilt Button - 3D Tactile React Button Component&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/02/7b566e0382bffe8ecafba88829007a02.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;GitHub 源码&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://github.com/archisvaze/react-tilt-button&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://github.com/fluidicon.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;github.com&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;GitHub - archisvaze/react-tilt-button: A tactile 3D button for React that tilts on hover and squishes on press. Modern and dependency-free.&lt;/h3&gt;
        &lt;p&gt;A tactile 3D button for React that tilts on hover and squishes on press. Modern and dependency-free. - archisvaze/react-tilt-button&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://github.com/archisvaze/react-tilt-button&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://opengraph.githubassets.com/2c155ce820402d6313e59d84fcacfb3b81cce127a9f0aa33997eaf0b268240e5/archisvaze/react-tilt-button&quot; alt=&quot;GitHub - archisvaze/react-tilt-button: A tactile 3D button for React that tilts on hover and squishes on press. Modern and dependency-free.&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;h3&gt;Super Monkey Ball&lt;a href=&quot;#super-monkey-ball&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;开发商 Twilight 将&lt;a href=&quot;https://example.com&quot;&gt;世嘉经典游戏《超级猴子球》移植到网页上&lt;/a&gt;，做得非常出色且运行流畅。&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://bsky.app/profile/twilightpb.bsky.social/post/3mdbynphtbc2l&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://web-cdn.bsky.app/static/favicon.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;bsky.app&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;Twilight (@twilightpb.bsky.social)&lt;/h3&gt;
        &lt;p&gt;I ported Super Monkey Ball to a website.
You can access it in the replies.
(or just type what you see in the video, I’m not your boss.)&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://bsky.app/profile/twilightpb.bsky.social/post/3mdbynphtbc2l&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://cdn.bsky.app/img/avatar/plain/did:plc:ndjdn5t6bck3nmlsrqk4223m/bafkreidroiu5crxbv6zt3buq7mrj5t5l5bos23sxksur3v4er7hbgf4xt4@jpeg&quot; alt=&quot;Twilight (@twilightpb.bsky.social)&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/02/d94a3de701caad8680595c0835025a67.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://monkeyball-online.pages.dev/&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://t3.gstatic.com/faviconV2?client=SOCIAL&amp;amp;type=FAVICON&amp;amp;fallback_opts=TYPE,SIZE,URL&amp;amp;url=https://pages.dev/&amp;amp;size=128&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;monkeyball-online.pages.dev&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;SMB1 Web Gameplay&lt;/h3&gt;
        
        &lt;div&gt;
          &lt;span&gt;https://monkeyball-online.pages.dev/&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;可以查看&lt;a href=&quot;https://github.com/sndrec/WebMonkeyBall&quot;&gt;源代码&lt;/a&gt;。&lt;/p&gt;
&lt;h3&gt;Typewriter Web Component V2&lt;a href=&quot;#typewriter-web-component-v2&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/luis-lessrain/pen/EaKjXwB&quot;&gt;EaKjXwB&lt;/a&gt; by luis-lessrain (&lt;a href=&quot;https://codepen.io/luis-lessrain&quot;&gt;@luis-lessrain&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;目前该项目仅在 CodePen 上提供，但它的效果非常出色。演示允许您交互式地启动、暂停/恢复、完成和重置动画。此外，还有一个进度条和按钮，可以跳转到特定的进度点。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/02/78044015d16e7335da5a0cfc3bd00e7d.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Refs&lt;a href=&quot;#refs&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://frontendfoc.us/issues/726&quot;&gt;Frontend Focus #726&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://react.statuscode.com/issues/460&quot;&gt;React Status #460&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>category:周刊</category></item><item><title>FE Bits Vol.24 | Rolldown 1.0 RC、Anime.js v4.3 自动布局与 Chrome 145 100vw 滚动条感知</title><link>https://blog.cosine.ren/post/weekly-24</link><guid isPermaLink="false">weekly-24</guid><description>本期聚焦前端性能与动画两大热点：Rolldown 正式发布 1.0 RC，凭借 Rust 带来 10–30 倍打包提速；Anime.js v4.3 引入自动布局动画功能，极大简化复杂界面过渡；Chrome 145 修复长期困扰的 `100vw` 滚动条问题，并支持嵌套 Overscroll 效果。与此同时，Vercel 发布代理技能平台 skills.sh，CSS Anchor Positioning 带来“随动”布局新模式。作者更新了 Moe Copy AI v0.3.0 新 UI，并分享 Electron 上架 MAS 与 astro-koharu 本地编辑器的开发心得。</description><pubDate>Sun, 25 Jan 2026 10:53:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本期网址 &lt;a href=&quot;https://blog.cosine.ren/post/weekly-24&quot;&gt;https://blog.cosine.ren/post/weekly-24&lt;/a&gt;&lt;br /&gt;
本周刊更新时间期望是在每周天。&lt;br /&gt;
推荐订阅本周刊的 &lt;a href=&quot;https://blog.cosine.ren/rss.xml&quot;&gt;RSS&lt;/a&gt;。&lt;br /&gt;
公众号 前端周周谈 FE Bits，点击阅读原文链接可查看原文。&lt;br /&gt;
QQ 讨论小群 598022684 / &lt;a href=&quot;https://discord.gg/XzvrvNMcSe&quot;&gt;Discord 群&lt;/a&gt;&lt;br /&gt;
本周刊文章内容同时也开源在 &lt;a href=&quot;https://github.com/yusixian/fe-bits-weekly&quot;&gt;fe-bits-weekly&lt;/a&gt;，欢迎关注。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;今天是 2026 年 1 月 25 日，星期天。&lt;/p&gt;
&lt;h2&gt;个人动态&lt;a href=&quot;#个人动态&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;碎碎念&lt;a href=&quot;#碎碎念&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;以下是一些碎碎念杂谈：&lt;/p&gt;
&lt;p&gt;越用 AI 会越有种「啊他果然取代不了我」的感觉，尤其是他犯蠢的时候。&lt;/p&gt;
&lt;p&gt;但是很多时候又很方便确确实实省下来了很多摸鱼时间，又爱又恨的感觉。&lt;/p&gt;
&lt;p&gt;也不奇怪很多程序员会排斥 AI 写代码，因为架构上真的很容易犯蠢，写的代码但凡是个代码洁癖都会感觉哎呦还不如自己来写，但是重复性业务工作真的太省劲了。&lt;/p&gt;
&lt;p&gt;虽然下沉市场和资本感觉都在 AI 狂欢，但我体感上的话，感觉其实平常程序员的生活是从写代码变成审计划和审代码改代码了，更爽了，写自己的东西更有劲了 x&lt;/p&gt;
&lt;p&gt;ai 真的容易堆屎山，我现在每次写完让 opus 4.5 review 一遍 codex review 一遍都差不多了我再看才能好点儿，不过他们自己 review 的效果还是挺好的。&lt;/p&gt;
&lt;p&gt;PS: 仅限前端/ Swift 领域，后端我只能通过同事的使用感受推测。&lt;/p&gt;
&lt;h3&gt;项目更新&lt;a href=&quot;#项目更新&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt;
  &lt;a href=&quot;https://github.com/yusixian/moe-copy-ai&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://github.com/fluidicon.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;github.com&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;GitHub - yusixian/moe-copy-ai: ✨ 萌萌哒的 AI 网页数据提取助手 ✨&lt;/h3&gt;
        &lt;p&gt;✨ 萌萌哒的 AI 网页数据提取助手 ✨. Contribute to yusixian/moe-copy-ai development by creating an account on GitHub.&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://github.com/yusixian/moe-copy-ai&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://opengraph.githubassets.com/73b751073039fdebe6dfd14b7cf57c550ffcfe294e3cc5224e1ff721efa45863/yusixian/moe-copy-ai&quot; alt=&quot;GitHub - yusixian/moe-copy-ai: ✨ 萌萌哒的 AI 网页数据提取助手 ✨&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;我很喜欢现在 &lt;a href=&quot;https://moe.cosine.ren/docs/user-guide/usage#%E6%89%B9%E9%87%8F%E6%8A%93%E5%8F%96&quot;&gt;Moe Copy AI 的内容提取功能&lt;/a&gt;～&lt;/p&gt;
&lt;p&gt;现在可以直接选中提取推文串进行总结的，准备下个版本改改加一下常用的选择器，现在还是直接选中元素提取出来进行总结。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/d61710239f2f8c765531ef38cb1df897.webp&quot; alt=&quot;Moe Copy AI 内容提取功能示例&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;然后这周 Moe Copy AI 发了 &lt;a href=&quot;https://github.com/yusixian/moe-copy-ai/releases/tag/0.3.0&quot;&gt;v0.3.0&lt;/a&gt;，太感动了终于有大佬 &lt;a href=&quot;https://github.com/hakadao&quot;&gt;@hakadao&lt;/a&gt; 帮忙改了一下最开始 AI 写的一坨的 UI 了，工作量巨大，现在 UI 清爽多了！（&lt;a href=&quot;https://github.com/yusixian/moe-copy-ai/pull/26&quot;&gt;#26&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;虽然现在还在陆续改，但是我也看不下去之前的了所以直接推了一版（&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://moe.cosine.ren/docs/changelog#v030---2026-01-22&quot;&gt;更新日志&lt;/a&gt; | &lt;a href=&quot;https://chromewebstore.google.com/detail/moe-copy-ai/dfmlcfckmfgabpgbaobgapdfmjiihnck&quot;&gt;下载&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/f1c2f06395200826a0c7e99afd7fedcc.webp&quot; alt=&quot;Moe Copy AI 新 UI 示例&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;博客更新&lt;a href=&quot;#博客更新&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;然后博客更新这边，最近把我们的 Electron 应用配置上了 Mac 端的 TestFlight，用 GitHub Actions 实现自动化构建和上传。踩了不少坑，记录一下整个流程。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.cosine.ren/post/electron-mas-testflight-guide&quot;&gt;用 GitHub Actions 自动化 Electron 上架 MAS（Mac App Store）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;然后给 astro-koharu 添加了无后端的编辑功能，只在本地 dev 模式有，还打算加一个无后端管理本地文章的 CMS 功能，灵感来源于上周发的 &lt;a href=&quot;https://blog.jim-nielsen.com/2026/os-as-cms/&quot;&gt;OS 即 CMS 文章&lt;/a&gt;&lt;br /&gt;
编辑器使用的是 &lt;a href=&quot;https://github.com/TypeCellOS/BlockNote&quot;&gt;BlockNote&lt;/a&gt; 是好用的，基于 Prosemirror 和 Tiptap。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/a1c1d69ef48c758010e553e882e470db.webp&quot; alt=&quot;astro-koharu 编辑功能演示1&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/6c6956e3b49729ddf272669f3f738f13.webp&quot; alt=&quot;astro-koharu 编辑功能演示1&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;另外现在可以自动创建不存在的分类映射了，也可以改～&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/1d86afe19ed2fe921990657685393c2d.webp&quot; alt=&quot;astro-koharu 编辑功能演示3&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;社区动态&lt;a href=&quot;#社区动态&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://%3Chttps://voidzero.dev/posts/announcing-rolldown-rc%3E&quot;&gt;Announcing Rolldown 1.0 RC&lt;/a&gt;：Rolldown 正式发布 1.0 发布候选版本（RC），作为 Vite 未来的核心打包工具，它在保持 Rollup 兼容性的同时，凭借 Rust 实现了 10-30 倍的速度提升。此 RC 标志着 API 的稳定性。在 1.0 之前不会有任何破坏性改动。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Vercel 公布了 &lt;a href=&quot;http://skills.sh&quot;&gt;skills.sh&lt;/a&gt;，是一个用于查找和共享代理 Skills 的网站，其中 Remotion 也出了 skill，现在只需要使用 Claude Code 即可通过编程方式制作视频！通过 &lt;code&gt;npx skills add remotion-dev/skills&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;/div&gt;
&lt;div&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.bram.us/2026/01/15/100vw-horizontal-overflow-no-more/&quot;&gt;Using 100vw is now scrollbar-aware&lt;/a&gt;：从 Chrome 145 开始，如果根元素（而非  &lt;code&gt;body&lt;/code&gt; ）设置了&lt;code&gt;overflow: scroll&lt;/code&gt;，则在  &lt;code&gt;vw&lt;/code&gt;  的大小中应考虑默认滚动条宽度，解决了长期以来因视口单位导致的网页多余水平滚动问题。该改进同样适用于 vh（对应水平滚动条）以及视口单位的小、大、动态变体（sv*, lv*, dv*）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Anime.js v4.3&lt;a href=&quot;#animejs-v43&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Anime.js v4.3 发布，引入强大的自动布局（Auto Layout）动画功能，支持平滑处理复杂的 CSS 布局变换。&lt;/p&gt;
&lt;p&gt;这个真的好诶！跨框架造福前端人。&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;
&lt;p&gt;官方提供了 &lt;a href=&quot;https://animejs.com/documentation/layout&quot;&gt;文档说明&lt;/a&gt; 和 &lt;a href=&quot;https://github.com/juliangarnier/anime/releases/tag/v4.3.0&quot;&gt;发布日志&lt;/a&gt;，并在 CodePen 上发布了 &lt;a href=&quot;https://codepen.io/collection/yykPaw&quot;&gt;示例集合&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;找了一个示例如下，太丝滑了：&lt;/p&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/juliangarnier/pen/PwzmxwR&quot;&gt;PwzmxwR&lt;/a&gt; by juliangarnier (&lt;a href=&quot;https://codepen.io/juliangarnier&quot;&gt;@juliangarnier&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;h2&gt;优质文章&lt;a href=&quot;#优质文章&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.solazy.me/20260120/&quot;&gt;不会编程的人能靠 AI 独立开发应用吗？&lt;/a&gt;：作者认为，AI 确实大幅降低了“将想法翻译成代码”的技术门槛，但“不会编程”、“靠 AI”和“应用”这些词语的边界远比想象中模糊。在与 AI 协作的过程中，使用者会不自觉地习得编程概念，AI 更多是作为博学但死板的“徒弟”或“翻译官”，帮助有逻辑、有想法的人实现愿景，而非代替思考和定义问题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://%3Chttps://nextjs.org/blog/turbopack-incremental-computation%3E&quot;&gt;Inside Turbopack: Building Faster by Building Less&lt;/a&gt;：深入解析 Next.js 的新一代打包工具 Turbopack 如何通过增量计算（Incremental Computation）和细粒度缓存实现极致的开发响应速度。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://paulmakeswebsites.com/writing/shadcn-radio-button/&quot;&gt;The Incredible Overcomplexity of the Shadcn Radio Button&lt;/a&gt;：探讨了现代前端 UI 框架（如 Shadcn）如何将原本简单的原生单选按钮变得异常复杂，并呼吁回归 CSS 原生开发。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://css-tricks.com/http-archive-2025-web-almanac/&quot;&gt;HTTP Archive 2025 Web Almanac | CSS-Tricks&lt;/a&gt;：介绍 HTTP Archive 发布的 2025 年度《Web 年鉴》报告，总结了当前 Web 性能、CSS 趋势、可访问性及资源膨胀等核心数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://atlas9.dev/blog/soft-delete.html&quot;&gt;The challenges of soft delete&lt;/a&gt;：探讨了传统“软删除”模式（如 &lt;code&gt;archived_at&lt;/code&gt; 列）带来的长期复杂性，并对比分析了触发器、应用层事件和 WAL 等更优的替代方案。&lt;br /&gt;
对于新项目，作者优先推荐基于触发器的方案，因为它部署简单、不引入额外基建，且有效隔离了活跃数据与归档数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2026/01/rethinking-pixel-perfect-web-design/&quot;&gt;Rethinking “Pixel Perfect” Web Design — Smashing Magazine&lt;/a&gt;：探讨了在多设备、响应式和动态内容的现代 Web 环境下，为何传统的“像素级完美（Pixel Perfect）”观念已不再适用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/lin-michael/localspace&quot;&gt;localspace&lt;/a&gt;：现代化的  &lt;code&gt;localForage&lt;/code&gt;  替代方案，提供简单的浏览器存储封装。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nerdy.dev/overscroll-effects-on-nested-scrollers-in-all-browsers?utm_source=rss&quot;&gt;Overscroll Effects On Nested Scrollers In All Browsers&lt;/a&gt;：Chrome 145 现已支持嵌套滚动容器的过度滚动效果（Overscroll Effects），实现了全浏览器体验的一致性。此前该效果仅在根页面有效，且只有 Safari 和 Firefox 支持子容器的回弹。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.logrocket.com/how-to-animate-svg-css-tutorial-examples/&quot;&gt;How to animate SVG with CSS: Tutorial with examples&lt;/a&gt;：这篇教程详细介绍了如何利用 CSS 属性和关键帧动画为 SVG（Scalable Vector Graphics，可缩放矢量图形）添加动态效果，从基础嵌入到复杂的路径动画均有涵盖。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;新特性&lt;a href=&quot;#新特性&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://una.im/follow-the-anchor&quot;&gt;Follow-the-leader pattern with CSS anchor positioning&lt;/a&gt;：介绍如何利用 CSS 锚点定位（CSS Anchor Positioning）实现元素跟随交互目标移动的“随动”（Follow-the-leader）模式。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/a7ab50a04bff59e7940cac51bb2ff71d.webp&quot; alt=&quot;Follow-the-leader 示例&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;工具&lt;a href=&quot;#工具&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/darula-hpp/shimmer-from-structure&quot;&gt;darula-hpp/shimmer-from-structure&lt;/a&gt;：一个能够根据 React 组件实际 DOM 结构自动生成像素级精确骨架屏（Shimmer Skeleton）的工具库。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://frontendfoc.us/link/179582/web&quot;&gt;Better SVG&lt;/a&gt;：用于编辑带有实时预览功能的 SVG 文件的 VS Code 扩展程序，包含许多实用功能，例如实时预览、颜色选择器、缩放/平移控件、可编辑的配色方案 currentColor 值、深色背景切换以及 SVGO 集成，一键优化 svg 体积。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/d6869db7c4de12b7514420cb44836be6.webp&quot; alt=&quot;扩展示例&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;趣站与 Codepen 精选&lt;a href=&quot;#趣站与-codepen-精选&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;&lt;strong&gt;使用 CSS 中的 drop-shadow() 滤镜为不规则形状添加阴影&lt;/strong&gt;&lt;a href=&quot;#使用-css-中的-drop-shadow-滤镜为不规则形状添加阴影&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt;
  &lt;a href=&quot;https://www.wearedevelopers.com/en/magazine/675/adding-shadows-to-irregular-shapes-in-css-with-filter-drop-shadow-675&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://www.wearedevelopers.com/favicon.ico&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;wad-platform-prod.pages.dev&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;Adding shadows to irregular shapes in CSS with filter: drop-shadow()&lt;/h3&gt;
        &lt;p&gt;Using box-shadow you can add shadows to any rectangular HTML element. But you can also use filter: drop-shadow() to add shadows to irregular shaped images.&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://www.wearedevelopers.com/en/magazine/675/adding-shadows-to-irregular-shapes-in-css-with-filter-drop-shadow-675&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://wearedevelopers.imgix.net/magazine/articles/675/images/hero/G3DzxuRQ8PzUmma7b3Bs-1767631393.jpeg?w=1200&amp;amp;auto=compress,format&quot; alt=&quot;Adding shadows to irregular shapes in CSS with filter: drop-shadow()&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;Chris Heilmann 向我们展示了如何使用  &lt;code&gt;filter: drop-shadow()&lt;/code&gt;  为不规则形状添加阴影，该过滤器可以识别“图像的透明部分或 SVG 路径的形状，并相应地应用阴影”。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/codepo8/pen/qENbVmb&quot;&gt;qENbVmb&lt;/a&gt; by codepo8 (&lt;a href=&quot;https://codepen.io/codepo8&quot;&gt;@codepo8&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/61443be0ae7d2172563bb0d574c7355d.webp&quot; alt=&quot;BlockNote image&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;使用 superellipse() 函数创建迷人的角形 — 仅 CSS&lt;/strong&gt;&lt;a href=&quot;#使用-superellipse-函数创建迷人的角形--仅-css&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;在这个由 Zoran Jambor 创作的 Pen 中，一个简单的圆形通过“ &lt;code&gt;superellipse()&lt;/code&gt; CSS 函数的随机值”演变成万花筒般的形状。点击“关于”按钮，即可查看关于  &lt;code&gt;superellipse()&lt;/code&gt;  函数的简短教程和实用资源。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/ZoranJambor/pen/ogLLLgr&quot;&gt;ogLLLgr&lt;/a&gt; by ZoranJambor (&lt;a href=&quot;https://codepen.io/ZoranJambor&quot;&gt;@ZoranJambor&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/605f54710d717bbd7b63235bd90c4d0d.webp&quot; alt=&quot;Codepen 示例2&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;CSS 光学错觉&lt;/strong&gt;&lt;a href=&quot;#css-光学错觉&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Alvaro Montoro 在这份令人惊艳的合集中分享了大量用纯 CSS 实现的著名光学错觉作品。其中一张在电视模式下简直太震撼了 ‍&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;
  &lt;a href=&quot;https://codepen.io/collection/GpWqKk&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;https://codepen.io/collection/GpWqKk&lt;/div&gt;
          &lt;div&gt;codepen.io&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      
        
      
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/7896eaf9e6daefc9237eec2a9f17bd19.webp&quot; alt=&quot;Codepen 示例3&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;旧布随风飘扬&lt;/strong&gt;&lt;a href=&quot;#旧布随风飘扬&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;在 Sabo Sugi 的这个 Three.js 场景中，一块饱经风霜的布料在微风中轻轻飘动。深入设置控件，即可更改图像、颜色、风力和撕裂效果。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/sabosugi/pen/ByzLYpb&quot;&gt;ByzLYpb&lt;/a&gt; by sabosugi (&lt;a href=&quot;https://codepen.io/sabosugi&quot;&gt;@sabosugi&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/cae6c7302db262b055672db83ada32ff.webp&quot; alt=&quot;Codepen 示例4&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Refs&lt;a href=&quot;#refs&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://react.statuscode.com/issues/459&quot;&gt;React Status #459&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://frontendfoc.us/issues/725&quot;&gt;Frontend Focus #725&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://codepen.io/spark/491&quot;&gt;Code Spark #491&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://codepen.io/spark/492&quot;&gt;Code Spark #492&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>category:周刊</category></item><item><title>用 GitHub Actions 自动化 Electron 上架 MAS（Mac App Store）</title><link>https://blog.cosine.ren/post/electron-mas-testflight-guide</link><guid isPermaLink="false">electron-mas-testflight-guide</guid><description>最近把我们的 Electron 桌面应用配置上了 Mac 端的 TestFlight，用 GitHub Actions 实现了自动化构建和上传。踩了不少坑，记录一下整个流程。

我对 Electron 和 MAS 上架流程也不熟悉，这篇文章是一点点摸索出来的。目前已经能够成功上传到</description><pubDate>Fri, 23 Jan 2026 15:02:00 GMT</pubDate><content:encoded>&lt;p&gt;最近把我们的 Electron 桌面应用配置上了 Mac 端的 TestFlight，用 GitHub Actions 实现了自动化构建和上传。踩了不少坑，记录一下整个流程。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我对 Electron 和 MAS 上架流程也不熟悉，这篇文章是一点点摸索出来的。目前已经能够成功上传到 TestFlight，但如有错漏请务必帮忙指出！
部分记录是通过和 AI 对话的方式存留下来的，可能有错漏。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ 时间成本警告&lt;/strong&gt;：整个 workflow 会消耗大量 GitHub Actions 免费额度！苹果的代码签名和公证（notarization）过程需要&lt;strong&gt;硬等 3～5 分钟&lt;/strong&gt;，这段时间 runner 只能空转等待苹果服务器响应，非常不划算。如果你的项目频繁构建，建议考虑自建 runner 或优化触发策略。&lt;/p&gt;
&lt;p&gt;这是苹果官方的文档 &lt;a href=&quot;https://developer.apple.com/help/app-store-connect/manage-builds/upload-builds&quot;&gt;Upload builds&lt;/a&gt;，介绍了如何使用 Apple 官方工具及 API 将 App 构建版本上传至 App Store Connect 的指南。&lt;/p&gt;
&lt;p&gt;先放最终的效果，为什么会有这么麻烦的东西啊（小声抱怨）：&lt;/p&gt;
&lt;p&gt;配置完成后：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;手动触发 workflow&lt;/li&gt;
&lt;li&gt;勾选 “Upload to App Store Connect” 则上传到 TestFlight，否则仅打包&lt;/li&gt;
&lt;li&gt;在 App Store Connect 的 TestFlight 标签页查看构建&lt;/li&gt;
&lt;li&gt;分发给测试人员&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/97171cec29213ff372766ab6ad8cd642.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;整个流程从 DMG 直接下载迁移到 TestFlight 大概花了半天时间，主要是在各种证书和 Bundle ID 配置上踩坑。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/9fb7dd3cf11aa0698134bee9c1cc203e.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/4c04f375b22f3cc86b5f8197c28ec8c6.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;根据文档里 Apple 的推荐上传方式如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Xcode&lt;/strong&gt;：苹果官方集成开发环境（IDE），支持从开发、测试到提交的全流程管理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transporter&lt;/strong&gt;：提供图形界面的 macOS 应用，适合简单快速地上传并查看交付日志和历史&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;xcrun altool&lt;/strong&gt;：通过 Xcode 自带的 xcrun 来调用 altool，这是命令行工具，可用于验证应用二进制文件并将其上传到 App Store Connect&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;App Store Connect API&lt;/strong&gt;：基于 REST 的 API，支持通过 JSON Web Tokens (JWT) 进行身份验证，实现自动化上传流程&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因为我们要使用 action，所以这里我们使用 &lt;code&gt;xcrun altool&lt;/code&gt; 进行上传&lt;/p&gt;
&lt;h2&gt;背景&lt;a href=&quot;#背景&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;我们的应用是用 Electron + React + TypeScript 构建的，之前通过 GitHub Releases 分发 DMG 安装包。现在想通过 TestFlight 进行 beta 测试，最终上架 Mac App Store。以下步骤假设你的 app 名称为 &lt;code&gt;AppCat&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;前置条件：在 App Store Connect 创建应用&lt;a href=&quot;#前置条件在-app-store-connect-创建应用&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在开始配置证书之前，需要在 App Store Connect 创建应用：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;打开 &lt;a href=&quot;https://appstoreconnect.apple.com/apps&quot;&gt;App Store Connect - 我的 App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;点击 ”+” → &lt;strong&gt;新建 App&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;平台勾选 &lt;strong&gt;macOS&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;名称填写应用名&lt;/li&gt;
&lt;li&gt;Bundle ID 选择或创建一个（&lt;strong&gt;必须和 &lt;code&gt;electron-builder.yml&lt;/code&gt; 中的 &lt;code&gt;appId&lt;/code&gt; 完全一致！&lt;/strong&gt;）&lt;/li&gt;
&lt;li&gt;SKU 随便填，如 &lt;code&gt;appcat-macos&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ 如果跳过这一步，后面上传时会报错 “No suitable application records were found”。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们的应用之前上架过 iOS，所以这里就不赘述了，就是常规的上架流程，现在是要上架 Mac 端的 App Store。&lt;/p&gt;
&lt;h2&gt;两种分发方式的区别&lt;a href=&quot;#两种分发方式的区别&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;首先要理解，Mac 应用有两种分发方式，它们需要&lt;strong&gt;不同的证书&lt;/strong&gt;：&lt;/p&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;分发方式&lt;/th&gt;&lt;th&gt;证书类型&lt;/th&gt;&lt;th&gt;用途&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;直接下载 (DMG)&lt;/td&gt;&lt;td&gt;Developer ID Application&lt;/td&gt;&lt;td&gt;网站/GitHub 下载安装&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Mac App Store&lt;/td&gt;&lt;td&gt;3rd Party Mac Developer Application&lt;/td&gt;&lt;td&gt;App Store / TestFlight&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;另外，MAS 版本&lt;strong&gt;强制要求沙盒&lt;/strong&gt;，这意味着一些功能（如 &lt;code&gt;desktopCapturer&lt;/code&gt; 屏幕截图）可能受限。&lt;/p&gt;
&lt;h2&gt;第一步：创建证书&lt;a href=&quot;#第一步创建证书&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;1.1 创建 CSR 文件&lt;a href=&quot;#11-创建-csr-文件&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;打开本地的&lt;strong&gt;钥匙串访问&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;顶部菜单栏 → 钥匙串访问 → 证书助理 → 从证书颁发机构请求证书...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/2e325997c59f87d5db48522988036f2c.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;然后填写：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户电子邮件地址：你的邮箱&lt;/li&gt;
&lt;li&gt;常用名称：随便填，如 “AppCat MAS”&lt;/li&gt;
&lt;li&gt;选择”存储到磁盘”&lt;/li&gt;
&lt;li&gt;CA 邮件地址：&lt;strong&gt;留空&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;保存 &lt;code&gt;.certSigningRequest&lt;/code&gt; 文件。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/117890a08223ffec41fe04e92e2a4043.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;1.2 创建 Mac App Distribution 证书&lt;a href=&quot;#12-创建-mac-app-distribution-证书&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;打开 &lt;a href=&quot;https://developer.apple.com/account/resources/certificates/list&quot;&gt;Apple Developer - Certificates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;点击 ”+” 按钮&lt;/li&gt;
&lt;li&gt;选择 &lt;strong&gt;Mac App Distribution&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;上传刚才的 CSR 文件&lt;/li&gt;
&lt;li&gt;下载证书，双击安装到钥匙串（选择”登录”钥匙串）&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;1.3 创建 Mac Installer Distribution 证书&lt;a href=&quot;#13-创建-mac-installer-distribution-证书&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;重复上面的步骤，但选择 &lt;strong&gt;Mac Installer Distribution&lt;/strong&gt;。这个证书用于签名 &lt;code&gt;.pkg&lt;/code&gt; 安装包。&lt;/p&gt;
&lt;h2&gt;第二步：创建 Provisioning Profile&lt;a href=&quot;#第二步创建-provisioning-profile&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;打开 &lt;a href=&quot;https://developer.apple.com/account/resources/profiles/list&quot;&gt;Apple Developer - Profiles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;点击 ”+”&lt;/li&gt;
&lt;li&gt;选择 &lt;strong&gt;Mac App Store Connect&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;选择你的 App ID（&lt;strong&gt;必须和 App Store Connect 中的 Bundle ID 一致！&lt;/strong&gt;）&lt;/li&gt;
&lt;li&gt;选择刚创建的 Mac App Distribution 证书&lt;/li&gt;
&lt;li&gt;命名并下载&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;重要&lt;/strong&gt;：Profile 中的 Bundle ID 必须和 App Store Connect 中的完全一致，否则上传会报错 “No suitable application records were found”。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这一步得到 &lt;code&gt;appcat_profile_mas.provisionprofile&lt;/code&gt; 文件（文件名可自定义，但要和后续配置保持一致）。&lt;/p&gt;
&lt;h2&gt;第三步：创建 App Store Connect API Key&lt;a href=&quot;#第三步创建-app-store-connect-api-key&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;用于 CI/CD 自动上传构建。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;打开 &lt;a href=&quot;https://appstoreconnect.apple.com/access/integrations/api&quot;&gt;App Store Connect - 用户和访问 - 密钥&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;点击 ”+” 创建新密钥&lt;/li&gt;
&lt;li&gt;名称随便填，权限选 &lt;strong&gt;App 管理&lt;/strong&gt; 或 &lt;strong&gt;管理员&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;点击 &lt;strong&gt;“生成”&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;找到 Issuer ID&lt;a href=&quot;#找到-issuer-id&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Issuer ID&lt;/strong&gt; 显示在密钥页面的&lt;strong&gt;顶部&lt;/strong&gt;，是一个 UUID 格式的字符串，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;50ce4b17-dd5e-4550-877b-7a7bb0d608d7&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所有属于同一团队的 API Key 共享同一个 Issuer ID，这个值不是敏感信息。&lt;/p&gt;
&lt;h3&gt;获取 Key ID&lt;a href=&quot;#获取-key-id&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;创建密钥后，&lt;strong&gt;Key ID&lt;/strong&gt; 会显示在密钥列表中（密钥 ID 列），例如 &lt;code&gt;BU64538829&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;下载 .p8 私钥文件&lt;a href=&quot;#下载-p8-私钥文件&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;点击 &lt;strong&gt;“下载 API 密钥”&lt;/strong&gt; 下载 &lt;code&gt;.p8&lt;/code&gt; 文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文件名格式为 &lt;code&gt;AuthKey_XXXXX.p8&lt;/code&gt;，其中 &lt;code&gt;XXXXX&lt;/code&gt; 是 Key ID&lt;/li&gt;
&lt;li&gt;这个文件是 API 认证的私钥，泄露会导致安全问题&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;极其重要&lt;/strong&gt;：&lt;code&gt;.p8&lt;/code&gt; 私钥文件整个团队&lt;strong&gt;只能下载一次&lt;/strong&gt;！下载后立即保存到安全位置（如密码管理器）。如果丢失，只能撤销当前密钥并重新创建，届时需要更新所有使用该密钥的 CI/CD 配置。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这一步得到三个值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AuthKey_XXXXX.p8&lt;/code&gt; 文件 → 后面转为 &lt;code&gt;ASC_API_KEY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Key ID → 对应后续我们在环境变量设置的 &lt;code&gt;ASC_API_KEY_ID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Issuer ID → 对应 &lt;code&gt;ASC_ISSUER_ID&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;第四步：导出证书为 .p12&lt;a href=&quot;#第四步导出证书为-p12&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;CI/CD 需要 &lt;code&gt;.p12&lt;/code&gt; 格式的证书。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;打开钥匙串访问&lt;/li&gt;
&lt;li&gt;左侧选择”登录”，上方选择”我的证书”&lt;/li&gt;
&lt;li&gt;找到 “3rd Party Mac Developer Application: xxx”
&lt;ul&gt;
&lt;li&gt;右键 → 导出&lt;/li&gt;
&lt;li&gt;格式选 &lt;code&gt;.p12&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;设置密码&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;同样导出 “3rd Party Mac Developer Installer: xxx”&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这一步得到了 &lt;code&gt;mas_app.p12&lt;/code&gt; 和 &lt;code&gt;mas_installer.p12&lt;/code&gt; 文件。&lt;/p&gt;
&lt;h2&gt;第五步：electron-builder 配置&lt;a href=&quot;#第五步electron-builder-配置&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;electron-builder.yml&lt;/code&gt; 中添加 MAS 配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;appId&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;app.bundlename.AppCat&lt;/span&gt;&lt;span&gt; # 必须和 App Store Connect 一致！这里替换为你的 app bundleId&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 现有的 Developer ID 配置保持不变&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;mac&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  entitlementsInherit&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;build/entitlements.mac.plist&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  hardenedRuntime&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  notarize&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 新增 Mac App Store 配置&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;mas&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  hardenedRuntime&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; # MAS 用沙盒代替&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  entitlements&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;build/entitlements.mas.plist&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  entitlementsInherit&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;build/entitlements.mas.inherit.plist&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  provisioningProfile&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;build/appcat_profile_mas.provisionprofile&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  notarize&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; # MAS 不需要 notarize&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  category&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;public.app-category.productivity&lt;/span&gt;&lt;span&gt; # 这里选择你的应用分类&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# PKG 配置&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pkg&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  isRelocatable&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  overwriteAction&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;upgrade&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  artifactName&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;${name}-${version}-mas.${ext}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第六步：创建 MAS Entitlements&lt;a href=&quot;#第六步创建-mas-entitlements&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;MAS 必须启用沙盒。创建 &lt;code&gt;build/entitlements.mas.plist&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;?&lt;/span&gt;&lt;span&gt;xml&lt;/span&gt;&lt;span&gt; version&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;1.0&quot;&lt;/span&gt;&lt;span&gt; encoding&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;UTF-8&quot;&lt;/span&gt;&lt;span&gt;?&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;!&lt;/span&gt;&lt;span&gt;DOCTYPE&lt;/span&gt;&lt;span&gt; plist&lt;/span&gt;&lt;span&gt; PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;plist&lt;/span&gt;&lt;span&gt; version&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;1.0&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&amp;gt;com.apple.security.app-sandbox&amp;lt;/&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&amp;gt;com.apple.security.network.client&amp;lt;/&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&amp;gt;com.apple.security.files.user-selected.read-write&amp;lt;/&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&amp;gt;com.apple.security.files.downloads.read-write&amp;lt;/&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;plist&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建 &lt;code&gt;build/entitlements.mas.inherit.plist&lt;/code&gt;（子进程继承）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;?&lt;/span&gt;&lt;span&gt;xml&lt;/span&gt;&lt;span&gt; version&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;1.0&quot;&lt;/span&gt;&lt;span&gt; encoding&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;UTF-8&quot;&lt;/span&gt;&lt;span&gt;?&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;!&lt;/span&gt;&lt;span&gt;DOCTYPE&lt;/span&gt;&lt;span&gt; plist&lt;/span&gt;&lt;span&gt; PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;plist&lt;/span&gt;&lt;span&gt; version&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;1.0&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&amp;gt;com.apple.security.app-sandbox&amp;lt;/&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&amp;gt;com.apple.security.inherit&amp;lt;/&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;plist&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第七步：配置 GitHub Secrets&lt;a href=&quot;#第七步配置-github-secrets&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在 GitHub 仓库的 Settings → Environments 中创建 &lt;code&gt;staging&lt;/code&gt; 环境，添加以下 Secrets：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 转换文件为 base64&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;base64&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; mas_app.p12&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; pbcopy&lt;/span&gt;&lt;span&gt;        # → MAS_APP_CERT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;base64&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; mas_installer.p12&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; pbcopy&lt;/span&gt;&lt;span&gt;  # → MAS_INSTALLER_CERT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;base64&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; appcat_profile_mas.provisionprofile&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; pbcopy&lt;/span&gt;&lt;span&gt;  # → MAS_PROVISIONING_PROFILE&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;base64&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; AuthKey_XXXXX.p8&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; pbcopy&lt;/span&gt;&lt;span&gt;   # → ASC_API_KEY&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;





































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Secret&lt;/th&gt;&lt;th&gt;描述&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;MAS_APP_CERT&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Mac App Distribution 证书 (base64)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;MAS_INSTALLER_CERT&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Mac Installer Distribution 证书 (base64)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;MAC_CERTS_PASSWORD&lt;/code&gt;&lt;/td&gt;&lt;td&gt;证书密码&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;MAS_PROVISIONING_PROFILE&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Provisioning Profile (base64)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ASC_API_KEY&lt;/code&gt;&lt;/td&gt;&lt;td&gt;App Store Connect API 私钥 (base64)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ASC_API_KEY_ID&lt;/code&gt;&lt;/td&gt;&lt;td&gt;API Key ID&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ASC_ISSUER_ID&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Issuer ID&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/4c04f375b22f3cc86b5f8197c28ec8c6.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;第八步：GitHub Actions Workflow&lt;a href=&quot;#第八步github-actions-workflow&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;创建 &lt;code&gt;.github/workflows/release-mas.yml&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;这里其实我创建了自己的 self-host action runner 试了试，就没去掉，可以忽略，默认行为是用 GitHub 的 runner。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Build and Release MAS&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  workflow_dispatch&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    inputs&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      upload_to_app_store&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Upload to App Store Connect&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;boolean&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        default&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      use_self_hosted_runner&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Use self-hosted runner&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;boolean&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        default&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;jobs&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  build-mas&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    runs-on&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;${{ inputs.use_self_hosted_runner &amp;amp;&amp;amp; fromJSON(&apos;[&quot;self-hosted&quot;, &quot;macOS&quot;, &quot;ARM64&quot;]&apos;) || &apos;macos-14&apos; }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    environment&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;staging&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    permissions&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      contents&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;write&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    steps&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;uses&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;actions/checkout@v4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Setup Node.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;actions/setup-node@v4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          node-version&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Setup pnpm&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;pnpm/action-setup@v4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          version&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;9&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Install dependencies&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        run&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;pnpm install --frozen-lockfile&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Import Mac App Store Certificates&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;apple-actions/import-codesign-certs@v3&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          p12-file-base64&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;${{ secrets.MAS_APP_CERT }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          p12-password&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;${{ secrets.MAC_CERTS_PASSWORD }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          keychain-password&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;${{ secrets.MAC_CERTS_PASSWORD }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Import Mac Installer Certificate&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;apple-actions/import-codesign-certs@v3&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          p12-file-base64&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;${{ secrets.MAS_INSTALLER_CERT }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          p12-password&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;${{ secrets.MAC_CERTS_PASSWORD }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          create-keychain&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          keychain-password&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;${{ secrets.MAC_CERTS_PASSWORD }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Download Provisioning Profile&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        run&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          echo &quot;${{ secrets.MAS_PROVISIONING_PROFILE }}&quot; | base64 -d &amp;gt; build/appcat_profile_mas.provisionprofile&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Build MAS App&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        run&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;MAS_BUILD=true pnpm exec electron-vite build &amp;amp;&amp;amp; pnpm exec electron-builder --mac mas --publish never&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        env&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          # 你的 env 环境变量等&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          VITE_API_BASE_URL&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;${{ vars.VITE_API_BASE_URL }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Upload to App Store Connect&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;${{ inputs.upload_to_app_store }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        run&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          # Setup API Key file&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          mkdir -p ~/.private_keys&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          echo &quot;${{ secrets.ASC_API_KEY }}&quot; | base64 -d &amp;gt; ~/.private_keys/AuthKey_${{ secrets.ASC_API_KEY_ID }}.p8&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          # Find and upload the pkg file&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          PKG_FILE=$(find dist -name &quot;*.pkg&quot; -type f | head -1)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          echo &quot;Uploading: $PKG_FILE&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          xcrun altool --upload-app \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            --type macos \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            --file &quot;$PKG_FILE&quot; \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            --apiKey ${{ secrets.ASC_API_KEY_ID }} \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            --apiIssuer ${{ secrets.ASC_ISSUER_ID }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Upload Build Artifacts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;actions/upload-artifact@v4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;mas-build&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          path&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            dist/*.pkg&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          retention-days&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;14&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;踩坑记录&lt;a href=&quot;#踩坑记录&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;证书导入密码问题&lt;a href=&quot;#证书导入密码问题&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;第二次导入证书时必须指定 &lt;code&gt;keychain-password&lt;/code&gt;，而且要和第一次创建 keychain 时的密码一致：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 第一次导入，创建 keychain&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;uses&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;apple-actions/import-codesign-certs@v3&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    keychain-password&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;${{ secrets.MAC_CERTS_PASSWORD }}&lt;/span&gt;&lt;span&gt; # 必须指定！&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 第二次导入，复用 keychain&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;uses&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;apple-actions/import-codesign-certs@v3&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    create-keychain&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    keychain-password&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;${{ secrets.MAC_CERTS_PASSWORD }}&lt;/span&gt;&lt;span&gt; # 必须一致！&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;No suitable application records 错误&lt;a href=&quot;#no-suitable-application-records-错误&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这个错误可能有多种原因：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原因 1：Bundle ID 不匹配&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;必须确保这三个地方的 Bundle ID &lt;strong&gt;完全一致&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;electron-builder.yml&lt;/code&gt; 中的 &lt;code&gt;appId&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;App Store Connect 中的 Bundle ID&lt;/li&gt;
&lt;li&gt;Provisioning Profile 绑定的 App ID&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;查看 Profile 绑定的 Bundle ID：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;security&lt;/span&gt;&lt;span&gt; cms&lt;/span&gt;&lt;span&gt; -D&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; appcat_profile_mas.provisionprofile&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; grep&lt;/span&gt;&lt;span&gt; -A1&lt;/span&gt;&lt;span&gt; &quot;application-identifier&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;原因 2：API Key 权限不够&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;创建 API Key 时必须选择 &lt;strong&gt;App 管理 (App Manager)&lt;/strong&gt; 或 &lt;strong&gt;管理员 (Admin)&lt;/strong&gt; 权限。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原因 3：App Store Connect 没有 macOS 应用&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;确保在 App Store Connect 中已创建应用，且平台包含 &lt;strong&gt;macOS&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;GH_TOKEN 报错&lt;a href=&quot;#gh_token-报错&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;MAS 构建不需要发布到 GitHub，添加 &lt;code&gt;--publish never&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;electron-builder&lt;/span&gt;&lt;span&gt; --mac&lt;/span&gt;&lt;span&gt; mas&lt;/span&gt;&lt;span&gt; --publish&lt;/span&gt;&lt;span&gt; never&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;缺少高分辨率图标&lt;a href=&quot;#缺少高分辨率图标&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;App Store 要求 1024x1024 的图标（512pt @2x）。确保 &lt;code&gt;.icns&lt;/code&gt; 文件包含这个尺寸。&lt;/p&gt;
&lt;h3&gt;API Key 必须写入文件&lt;a href=&quot;#api-key-必须写入文件&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;xcrun altool&lt;/code&gt; 需要从文件读取 &lt;code&gt;.p8&lt;/code&gt; 私钥，不能直接传入 base64 字符串。在 CI 中需要先解码到指定目录：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Upload to App Store Connect&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  run&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    # altool 会在这个目录查找私钥文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    mkdir -p ~/.private_keys&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    echo &quot;${{ secrets.ASC_API_KEY }}&quot; | base64 -d &amp;gt; ~/.private_keys/AuthKey_${{ secrets.ASC_API_KEY_ID }}.p8&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    xcrun altool --upload-app \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      --apiKey ${{ secrets.ASC_API_KEY_ID }} \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      --apiIssuer ${{ secrets.ASC_ISSUER_ID }} \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;PKG 文件路径问题&lt;a href=&quot;#pkg-文件路径问题&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;xcrun altool --file dist/*.pkg&lt;/code&gt; 可能会报错 “file cannot be found”，原因：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;无匹配文件时&lt;/strong&gt;：bash 默认会把 &lt;code&gt;dist/*.pkg&lt;/code&gt; 当作字面字符串传递&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多文件匹配时&lt;/strong&gt;：所有匹配的文件都会作为参数传递，但 &lt;code&gt;--file&lt;/code&gt; 只接受一个&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;用 &lt;code&gt;find&lt;/code&gt; 命令显式获取文件路径更可靠：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;PKG_FILE&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt; dist&lt;/span&gt;&lt;span&gt; -name&lt;/span&gt;&lt;span&gt; &quot;*.pkg&quot;&lt;/span&gt;&lt;span&gt; -type&lt;/span&gt;&lt;span&gt; f&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; head&lt;/span&gt;&lt;span&gt; -1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;xcrun&lt;/span&gt;&lt;span&gt; altool&lt;/span&gt;&lt;span&gt; --upload-app&lt;/span&gt;&lt;span&gt; --file&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$PKG_FILE&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;希望这篇指南能帮你少走弯路。&lt;/p&gt;
&lt;p&gt;再次提醒：&lt;strong&gt;这个 workflow 会消耗大量 GitHub Actions 免费额度&lt;/strong&gt;，苹果的公证过程硬等 3～5 分钟，runner 只能空转。如果你的团队频繁构建，强烈建议自建 runner 或者优化触发策略（比如只在打 tag 时触发）。&lt;/p&gt;</content:encoded><category>category:笔记</category><category>category:前端</category><category>tag:git</category><category>tag:action</category><category>tag:Electron</category><category>tag:MAS</category></item><item><title>FE Bits Vol.23 | jQuery 4 发布，Chrome 新增垂直标签页功能，Astro 被 Cloudflare 收购</title><link>https://blog.cosine.ren/post/weekly-23</link><guid isPermaLink="false">weekly-23</guid><description>本期周刊：jQuery 4.0.0 时隔十年正式发布，带来全面现代化改进；Chrome 145 终于实验性支持垂直标签页；Astro 团队加入 Cloudflare，继续专注框架核心开发；Vercel 发布 React 最佳实践规则集，助力 AI 编写更优代码；另有 V8 性能实测、Markdown 演化史与「告别 Scroll Fade」等精彩阅读推荐。</description><pubDate>Sun, 18 Jan 2026 13:50:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本期网址 &lt;a href=&quot;https://blog.cosine.ren/post/weekly-23&quot;&gt;https://blog.cosine.ren/post/weekly-23&lt;/a&gt; &lt;br /&gt;
本周刊更新时间期望是在每周天。 &lt;br /&gt;
推荐订阅本周刊的 &lt;a href=&quot;https://blog.cosine.ren/rss.xml&quot;&gt;RSS&lt;/a&gt;。&lt;br /&gt;
公众号 前端周周谈 FE Bits，点击阅读原文链接可查看原文。&lt;br /&gt;
QQ 讨论小群 598022684 / &lt;a href=&quot;https://discord.gg/XzvrvNMcSe&quot;&gt;Discord 群&lt;/a&gt; &lt;br /&gt;
本周刊文章内容同时也开源在 &lt;a href=&quot;https://github.com/yusixian/fe-bits-weekly&quot;&gt;fe-bits-weekly&lt;/a&gt;，欢迎关注。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;今天是 2026 年 1 月 18 日，星期天。&lt;/p&gt;
&lt;p&gt;Moe Copy AI 发了 &lt;a href=&quot;https://github.com/yusixian/moe-copy-ai/releases/tag/0.2.1&quot;&gt;v0.2.1&lt;/a&gt; 和 &lt;a href=&quot;https://github.com/yusixian/moe-copy-ai/releases/tag/0.2.2&quot;&gt;v0.2.2&lt;/a&gt;，做了国际化，将悬浮球的功能抽取至侧边栏单页抓取 tab 了，内容提取下也加了 AI 总结按钮。&lt;/p&gt;
&lt;p&gt;感谢 &lt;a href=&quot;https://github.com/XueHua-s&quot;&gt;@XueHua-s&lt;/a&gt; 贡献的 htmlToMarkdown 重构为使用 unifined 改进～（&lt;a href=&quot;https://github.com/yusixian/moe-copy-ai/pull/19&quot;&gt;#19&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;然后是 astro-koharu 主题，将周刊功能泛化为了多系列，featuredSeries 支持数组与自定义 slug，动态生成系列页面并替代固定 &lt;code&gt;/weekly&lt;/code&gt; 了，现在可以新增以下如图所示的系列文章了。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/9caacaeaa2c22fc74e2aeb5b95513350.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/1e44232d7b78a496bd12c54320442b6c.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;并且评论组件现在支持切换三种评论组件了（waline / remark42 / giscus）&lt;/p&gt;
&lt;p&gt;开心！Waline 做评论真是又好看又好用啊&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/0fcd4666e5e8a0b2abb0ee517bf26166.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;这周都在肝项目，所以周刊短短的！&lt;/p&gt;
&lt;h2&gt;社区动态&lt;a href=&quot;#社区动态&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.jquery.com/2026/01/17/jquery-4-0-0/&quot;&gt;jQuery 4.0.0&lt;/a&gt;：时隔近十年，jQuery 发布了 4.0.0 正式版，带来了大量现代化改进，包括移除对旧版浏览器（IE 10 及更早版本）的支持、引入新特性以及优化内部实现。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.bram.us/2026/01/16/chrome-145-adds-experimental-support-for-vertical-tabs/&quot;&gt;Chrome 145 adds Experimental Support for Vertical Tabs&lt;/a&gt;：Chrome 145 实验性支持垂直标签页。（天呐 2026 了 Chrome 终于把垂直标签页学过来了太感动了）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://vercel.com/blog/introducing-react-best-practices&quot;&gt;隆重推出：React 最佳实践&lt;/a&gt;：Vercel 团队将 &lt;em&gt;“十余年 React 和 Next.js 优化经验”&lt;/em&gt; 提炼成了&lt;a href=&quot;https://github.com/vercel-labs/agent-skills/tree/main/skills/react-best-practices/rules&quot;&gt;一套 Markdown 文件&lt;/a&gt;， 旨在供 Claude Code 等代码助手使用，当然您也可以自行阅读。其目标是帮助这些助手编写更优质的 React 代码，而无需过多指导。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可以通过 &lt;code&gt;npx add-skill vercel-labs/agent-skills&lt;/code&gt; 直接安装&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://astro.build/blog/joining-cloudflare/&quot;&gt;The Astro Technology Company joins Cloudflare | Astro&lt;/a&gt;：Astro 框架背后的公司 The Astro Technology Company 宣布加入 Cloudflare，以获取更多资源并专注于框架核心开发，承诺保持开源和平台中立。（真好，我很喜欢 astro 写网站的感觉）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;并且 &lt;a href=&quot;https://astro.build/blog/year-in-review-2025/&quot;&gt;Astro 回顾了过去一年&lt;/a&gt;，展望了 2025 年的更新、变化和新增功能吗，并且&lt;a href=&quot;https://astro.build/blog/astro-6-beta/&quot;&gt;发布了 Astro 6 的 beta 版本。&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.sanity.io/blog/you-should-never-build-a-cms?ref=ww-rss&quot;&gt;“You should never build a CMS” | Sanity&lt;/a&gt;：Sanity 回应 Cursor 将 CMS 迁移至 Markdown 的热议，分享了许多使用 CMS 的理由。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;文章与社区动态&lt;a href=&quot;#文章与社区动态&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://gethopp.app/blog/hate-webkit&quot;&gt;Why I hate WebKit, a (non) love letter&lt;/a&gt;：非常好文章推一下，Tauri WebKit 坑点大全，珍爱生命远离 WebKit！这个标题和 url 真是直抒胸臆。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://waspdev.com/articles/2026-01-01/javascript-for-of-loops-are-actually-fast&quot;&gt;JavaScript 的 for-of 循环实际上很快 (V8)&lt;/a&gt;：探讨现代 V8 引擎中 &lt;code&gt;for-of&lt;/code&gt; 循环的性能表现，打破了开发者社区中关于其性能显著弱于传统循环的固有印象。作者建议现在除非是处理极端海量数据且对每一毫秒都敏感的场景，否则应优先使用语义更佳的 &lt;code&gt;for-of&lt;/code&gt; 循环。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://theorangeone.net/posts/rss-guids/&quot;&gt;GUIDs - How I messed up my RSS feed&lt;/a&gt;：这篇文章讲述了作者如何因在 RSS feed 中添加 UTM 参数导致所有文章被重复添加，并介绍了 GUID 字段来解决此类问题的经验。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://innei.in/posts/tech/lobehub-performance-dx-optimization&quot;&gt;记 LobeHub 的性能和 DX 优化&lt;/a&gt;：作者分享了入职 LobeHub 一个月以来在性能和开发体验（DX）方面进行的优化工作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/gibbok/typescript-book&quot;&gt;《简明 TypeScript 手册》&lt;/a&gt; 是一本简短精炼的 TypeScript 指南，免费开放阅读。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://fant.io/react/&quot;&gt;How to Steal Any React Component&lt;/a&gt;
网页做的还挺有趣的，介绍如何使用 React 的内部数据结构（通过 &lt;a href=&quot;https://github.com/acdlite/react-fiber-architecture&quot;&gt;Fiber&lt;/a&gt;）和 LLM 来重建生产 React 应用程序中的组件，而无需原始源代码。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.anildash.com/2026/01/09/how-markdown-took-over-the-world/&quot;&gt;How Markdown took over the world&lt;/a&gt;：回顾 Markdown 的诞生历程及其如何从一个小众博客工具演变为统治现代互联网和 AI 交互的通用文本标准。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://dbushell.com/2026/01/09/death-to-scroll-fade/&quot;&gt;Death to Scroll Fade!&lt;/a&gt; 批判网页设计中过度使用“滚动渐入”（Scroll Fade）效果的弊端，呼吁回归简洁高效的用户体验。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.columkelly.com/blog/use-optimistic&quot;&gt;useOptimistic Won’t Save You&lt;/a&gt;：深入探讨 React 19 的 useOptimistic 钩子，说明其必须结合 Transition 和 Action 状态管理才能真正处理复杂的竞态条件。
作者最后建议，这些复杂的底层 API 更多是为框架开发者设计的，普通开发者应倾向于使用成熟框架提供的抽象。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.debugbear.com/blog/measuring-react-app-performance&quot;&gt;如何衡量和优化 React 性能&lt;/a&gt;：全面介绍了衡量和优化 React 应用性能的工具与技术，重点讲解了 React 19.2 的新特性以及各类运行时和构建时的优化策略。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://allthingssmitty.com/2026/01/12/stop-turning-everything-into-arrays-and-do-less-work-instead/&quot;&gt;停止将所有内容都变成数组（并减少工作量）&lt;/a&gt;：介绍如何利用 JavaScript 原生的迭代器辅助函数（Iterator helpers）实现延迟计算，从而优化处理大数据集时的性能和内存占用。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;新特性&lt;a href=&quot;#新特性&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://remysharp.com/2026/01/13/bytes-i-can-delete-after-all-this-time&quot;&gt;Bytes I can delete after all this time&lt;/a&gt;
由于 CSS 和 JS 的进步，越来越多的技术我们不再需要了，Remy Sharp 分享了一份我们可以抛弃的技术清单。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;CSS 布局与样式增强&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;text-underline-offset&lt;/code&gt; 属性轻松控制文本下划线的距离，无需再用复杂的伪元素模拟。&lt;/li&gt;
&lt;li&gt;在 Flexbox 布局中直接使用 &lt;code&gt;gap&lt;/code&gt; 属性，告别过去需要通过处理 &lt;code&gt;margin&lt;/code&gt; (外边距) 来设置间距的繁琐操作。&lt;/li&gt;
&lt;li&gt;采用原生 CSS 嵌套 (Nesting) 和在选择器内嵌套媒体查询 (Media Queries)，使样式结构更清晰且无需预处理器。&lt;/li&gt;
&lt;li&gt;利用 &lt;code&gt;clamp(min, variable, max)&lt;/code&gt; 实现流体响应式尺寸，简化了复杂的断点计算。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;content: open-quote&lt;/code&gt; 结合 &lt;code&gt;q&lt;/code&gt; 标签实现根据语言自动适配的本地化引号。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JavaScript 语法与交互优化&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;采用可选的 &lt;code&gt;catch&lt;/code&gt; 绑定 (Optional catch binding)，在不需要错误对象时直接写 &lt;code&gt;catch&lt;/code&gt;，避免定义未使用的变量。&lt;/li&gt;
&lt;li&gt;指针事件 (Pointer Events) 的普及使得开发者可以用一套逻辑替代旧有的 &lt;code&gt;click&lt;/code&gt; (点击) 和 &lt;code&gt;touch&lt;/code&gt; (触摸) 事件的混合逻辑。&lt;/li&gt;
&lt;li&gt;弃用旧的性能黑科技，如使用 &lt;code&gt;~~&lt;/code&gt; 或 &lt;code&gt;| 0&lt;/code&gt; 取整，现代引擎下 &lt;code&gt;Math.floor()&lt;/code&gt; 的可读性更高且性能不再是瓶颈。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;资源优化与现代格式&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AVIF 图像格式已获得全面支持，相比 JPEG 可轻松实现 50% 以上的体积缩减。&lt;/li&gt;
&lt;li&gt;提供了使用命令行工具 &lt;code&gt;avifenc&lt;/code&gt; 批量转换图片的实用脚本示例。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;然后是两篇锚点定位，都是自动连线：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://css-tip.com/connected-circles-2/&quot;&gt;Connecting Circles With Anchor Positioning II&lt;/a&gt;：CSS Tips 锚点定位系列文章，通过改进之前的实现，展示了如何使用 CSS 锚点定位技术动态连接两个圆圈，并在连接箭头的形状中计算并显示它们之间的距离。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.zhangxinxu.com/wordpress/2026/01/css-anchor-position-connect/&quot;&gt;纯 CSS 实现两个元素之间折线自动相连&lt;/a&gt;：本文详细介绍了如何利用 CSS 锚点定位实现两个元素之间纯 CSS 自动连接的折线效果，并探讨了当前方案的实现原理及局限性。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;趣站与 Codepen 精选&lt;a href=&quot;#趣站与-codepen-精选&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://css-tricks.com/playing-with-codepen-slidevars/&quot;&gt;Playing With CodePen slideVars&lt;/a&gt;：介绍 CodePen 官方推出的 slideVars 工具，它可以自动检测 CSS 变量并生成交互式控制面板，现在在 codepen 里写交互式 Demo 更方便了。&lt;/p&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/geoffgraham/pen/myEOqJg&quot;&gt;myEOqJg&lt;/a&gt; by geoffgraham (&lt;a href=&quot;https://codepen.io/geoffgraham&quot;&gt;@geoffgraham&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/86fef9a9ebcbbd142278ebb09cb030f2.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Refs&lt;a href=&quot;#refs&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://javascriptweekly.com/issues/768&quot;&gt;JavaScript Weekly #768&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://react.statuscode.com/issues/458&quot;&gt;React Status #458&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>category:周刊</category></item><item><title>Fork 主题如何更新？基于 Ink 构建主题更新 CLI 工具</title><link>https://blog.cosine.ren/post/interactive-git-update-cli</link><guid isPermaLink="false">interactive-git-update-cli</guid><description>本文图表、伪代码等由 AI 辅助编写
背景
当你 fork 了一个开源项目作为自己的博客主题，如何优雅地从上游仓库同步更新？手动敲一串 Git 命令既繁琐又容易出错；但直接点 Fork 的 Sync 按钮，又可能覆盖你的自定义配置和内容。</description><pubDate>Mon, 12 Jan 2026 12:30:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;本文图表、伪代码等由 AI 辅助编写&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;背景&lt;a href=&quot;#背景&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;当你 fork 了一个开源项目作为自己的博客主题，如何优雅地从上游仓库同步更新？手动敲一串 Git 命令既繁琐又容易出错；但直接点 Fork 的 Sync 按钮，又可能覆盖你的自定义配置和内容。&lt;/p&gt;
&lt;p&gt;很多人因此在「保持更新」和「保留修改」之间左右为难：要么干脆二开后不再同步，要么每次更新都提心吊胆。&lt;/p&gt;
&lt;p&gt;这也是为什么不少项目会像 &lt;code&gt;@fumadocs/cli&lt;/code&gt; 一样，提供专门的 CLI 来完成更新等相关操作。&lt;/p&gt;
&lt;p&gt;本文将介绍如何&lt;strong&gt;简单地&lt;/strong&gt;构建一个交互式 CLI 工具，把 fork 同步的流程自动化起来。&lt;/p&gt;
&lt;p&gt;这个工具的核心目标是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;安全&lt;/strong&gt;：更新前检查工作区状态，必要时可备份&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;透明&lt;/strong&gt;：预览所有变更，让用户决定是否更新&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;友好&lt;/strong&gt;：出现冲突时给出明确指引&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;具体的代码可以看这个 PR：&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://github.com/cosZone/astro-koharu/pull/43&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://github.com/fluidicon.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;github.com&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;Build/backup by yusixian · Pull Request #43 · cosZone/astro-koharu&lt;/h3&gt;
        &lt;p&gt;Summary 新增 Koharu CLI 交互式命令行工具，提供博客内容备份/还原、主题更新、内容生成、备份管理等功能
优化类型定义和组件逻辑
更新文档以反映新的 CLI 工具使用方式 主要改进
Koharu CLI
基于 Ink 构建的交互式 TUI 工具：
pnpm koharu # 交互式主菜单
pnpm koharu backup # 备份博客内容 (--full 完整备份)…&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://github.com/cosZone/astro-koharu/pull/43&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://opengraph.githubassets.com/cdf5e0624094f745aadf42b50e9d28bb8b2c475beecb92fc111c7df358b35753/cosZone/astro-koharu/pull/43&quot; alt=&quot;Build/backup by yusixian · Pull Request #43 · cosZone/astro-koharu&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;不过这个 PR 只是最初的版本，后面又缝缝补补了不少东西，整体流程是我研究一个周末后摸索出的，如有不足，那一定是我考虑不周，欢迎指出～&lt;/p&gt;
&lt;p&gt;在这个 PR 里，我基于 Ink 构建了一个交互式 TUI 工具，提供了博客内容备份/还原、主题更新、内容生成、备份管理等功能：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; # 交互式主菜单&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; backup&lt;/span&gt;&lt;span&gt; # 备份博客内容 (--full 完整备份)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; restore&lt;/span&gt;&lt;span&gt; # 还原备份 (--latest, --dry-run, --force)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; update&lt;/span&gt;&lt;span&gt; # 从上游同步更新 (--check, --skip-backup, --force)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; generate&lt;/span&gt;&lt;span&gt; # 生成内容资产 (LQIP, 相似度, AI 摘要)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; clean&lt;/span&gt;&lt;span&gt; # 清理旧备份 (--keep N)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; list&lt;/span&gt;&lt;span&gt; # 查看所有备份&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/388028f78817bf496d1c8223ef4b26b4.gif&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;其中备份功能可以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;基础备份：博客文章、配置、头像、.env&lt;/li&gt;
&lt;li&gt;完整备份：包含所有图片和生成的资产文件&lt;/li&gt;
&lt;li&gt;自动生成 &lt;code&gt;manifest.json&lt;/code&gt; 记录主题版本与备份元信息（时间等）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;还原功能可以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;交互式选择备份文件&lt;/li&gt;
&lt;li&gt;支持 &lt;code&gt;--dry-run&lt;/code&gt; 预览模式&lt;/li&gt;
&lt;li&gt;显示备份类型、版本、时间等元信息&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;主题更新功能可以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;自动配置 upstream remote 指向原始仓库&lt;/li&gt;
&lt;li&gt;预览待合并的提交列表（显示 hash、message、时间）&lt;/li&gt;
&lt;li&gt;更新前可选备份，支持冲突检测与处理&lt;/li&gt;
&lt;li&gt;合并成功后自动安装依赖&lt;/li&gt;
&lt;li&gt;支持 &lt;code&gt;--check&lt;/code&gt; 仅检查更新、&lt;code&gt;--force&lt;/code&gt; 跳过工作区检查&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;整体架构&lt;a href=&quot;#整体架构&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic sequence-snake-steps-underline-text&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title Git Update 命令流程&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  desc 从 upstream 同步更新的完整工作流&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 检查状态&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 验证当前分支和工作区状态&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/source-branch-check&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 配置远程&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 确保 upstream remote 已配置&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/source-repository&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 获取更新&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 从 upstream 拉取最新提交&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/cloud-download&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 预览变更&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 显示待合并的提交列表&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/file-find&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 确认备份&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 可选：备份当前内容&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/backup-restore&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 执行合并&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 合并 upstream 分支到本地&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/merge&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 处理结果&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 成功则安装依赖，冲突则提示解决&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/check-circle&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;更新相关 Git 命令详解&lt;a href=&quot;#更新相关-git-命令详解&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;1. 检查当前分支&lt;a href=&quot;#1-检查当前分支&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; rev-parse&lt;/span&gt;&lt;span&gt; --abbrev-ref&lt;/span&gt;&lt;span&gt; HEAD&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：获取当前所在分支的名称。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;参数解析&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rev-parse&lt;/code&gt;：解析 Git 引用&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--abbrev-ref&lt;/code&gt;：输出简短的引用名称（如 &lt;code&gt;main&lt;/code&gt;），而不是完整的 SHA&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;使用场景&lt;/strong&gt;：确保用户在正确的分支（如 &lt;code&gt;main&lt;/code&gt;）上执行更新，避免在 feature 分支上意外合并上游代码。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; currentBranch&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; execSync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;git rev-parse --abbrev-ref HEAD&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (currentBranch &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; &quot;main&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  throw&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`仅支持在 main 分支执行更新，当前分支: ${&lt;/span&gt;&lt;span&gt;currentBranch&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 检查工作区状态&lt;a href=&quot;#2-检查工作区状态&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; status&lt;/span&gt;&lt;span&gt; --porcelain&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：以机器可读的格式输出工作区状态。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;参数解析&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--porcelain&lt;/code&gt;：输出稳定、易于解析的格式，不受 Git 版本和语言设置影响&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;输出格式&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;M  modified-file.ts      # 已暂存的修改&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; M unstaged-file.ts      # 未暂存的修改&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;?? untracked-file.ts     # 未跟踪的文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;A  new-file.ts           # 新添加的文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;D  deleted-file.ts       # 删除的文件&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;前两个字符分别表示暂存区和工作区的状态。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; statusOutput&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; execSync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;git status --porcelain&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; uncommittedFiles&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; statusOutput.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; line.&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; isClean&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; uncommittedFiles.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; ===&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 管理远程仓库&lt;a href=&quot;#3-管理远程仓库&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;h4&gt;检查 remote 是否存在&lt;a href=&quot;#检查-remote-是否存在&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; remote&lt;/span&gt;&lt;span&gt; get-url&lt;/span&gt;&lt;span&gt; upstream&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：获取指定 remote 的 URL，如果不存在会报错。&lt;/p&gt;
&lt;h4&gt;添加 upstream remote&lt;a href=&quot;#添加-upstream-remote&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 将 URL 替换为你的上游仓库地址&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; remote&lt;/span&gt;&lt;span&gt; add&lt;/span&gt;&lt;span&gt; upstream&lt;/span&gt;&lt;span&gt; https://github.com/original/repo.git&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：添加一个名为 &lt;code&gt;upstream&lt;/code&gt; 的远程仓库，指向原始项目。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么需要 upstream？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当你 fork 一个项目后，你的 &lt;code&gt;origin&lt;/code&gt; 指向你自己的 fork，而 &lt;code&gt;upstream&lt;/code&gt; 指向原始项目。这样可以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从 &lt;code&gt;upstream&lt;/code&gt; 拉取原项目的更新&lt;/li&gt;
&lt;li&gt;向 &lt;code&gt;origin&lt;/code&gt; 推送你的修改&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// UPSTREAM_URL 需替换为你的上游仓库地址&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; UPSTREAM_URL&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;https://github.com/original/repo.git&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; ensureUpstreamRemote&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; execSync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;git remote get-url upstream&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    execSync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`git remote add upstream ${&lt;/span&gt;&lt;span&gt;UPSTREAM_URL&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; UPSTREAM_URL&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 获取远程更新&lt;a href=&quot;#4-获取远程更新&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt; upstream&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：从 upstream 远程仓库下载所有分支的最新提交，但&lt;strong&gt;不会自动合并&lt;/strong&gt;到本地分支。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;与 &lt;code&gt;git pull&lt;/code&gt; 的区别&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fetch&lt;/code&gt; 只下载数据，不修改本地代码&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pull&lt;/code&gt; = &lt;code&gt;fetch&lt;/code&gt; + &lt;code&gt;merge&lt;/code&gt;，会自动合并&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用 &lt;code&gt;fetch&lt;/code&gt; 可以让我们先预览变更，再决定是否合并。&lt;/p&gt;
&lt;h3&gt;5. 计算提交差异&lt;a href=&quot;#5-计算提交差异&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; rev-list&lt;/span&gt;&lt;span&gt; --left-right&lt;/span&gt;&lt;span&gt; --count&lt;/span&gt;&lt;span&gt; HEAD...upstream/main&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：计算本地分支与 upstream/main 之间的提交差异。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;参数解析&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rev-list&lt;/code&gt;：列出提交记录&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--left-right&lt;/code&gt;：区分左侧（本地）和右侧（远程）的提交&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--count&lt;/code&gt;：只输出计数，不列出具体提交&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HEAD...upstream/main&lt;/code&gt;：三个点表示对称差集&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;输出示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;2    5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;表示本地有 2 个提交不在 upstream 上（ahead），upstream 有 5 个提交不在本地（behind）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; revList&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; execSync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;git rev-list --left-right --count HEAD...upstream/main&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;aheadStr&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;behindStr&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; revList.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\t&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; aheadCount&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; parseInt&lt;/span&gt;&lt;span&gt;(aheadStr, &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; behindCount&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; parseInt&lt;/span&gt;&lt;span&gt;(behindStr, &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`本地领先 ${&lt;/span&gt;&lt;span&gt;aheadCount&lt;/span&gt;&lt;span&gt;} 个提交，落后 ${&lt;/span&gt;&lt;span&gt;behindCount&lt;/span&gt;&lt;span&gt;} 个提交`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6. 查看待合并的提交&lt;a href=&quot;#6-查看待合并的提交&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; log&lt;/span&gt;&lt;span&gt; HEAD..upstream/main&lt;/span&gt;&lt;span&gt; --pretty=format:&lt;/span&gt;&lt;span&gt;&quot;%h|%s|%ar|%an&quot;&lt;/span&gt;&lt;span&gt; --no-merges&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：列出 upstream/main 上有但本地没有的提交。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;参数解析&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;HEAD..upstream/main&lt;/code&gt;：两个点表示 A 到 B 的差集（B 有而 A 没有的）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--pretty=format:&quot;...&quot;&lt;/code&gt;：自定义输出格式
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;%h&lt;/code&gt;：短 hash&lt;/li&gt;
&lt;li&gt;&lt;code&gt;%s&lt;/code&gt;：提交信息&lt;/li&gt;
&lt;li&gt;&lt;code&gt;%ar&lt;/code&gt;：相对时间（如 “2 days ago”）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;%an&lt;/code&gt;：作者名&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--no-merges&lt;/code&gt;：排除 merge commit&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;输出示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;a1b2c3d|feat: add dark mode|2 days ago|Author Name&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;e4f5g6h|fix: typo in readme|3 days ago|Author Name&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; commitFormat&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;%h|%s|%ar|%an&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; output&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; execSync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  `git log HEAD..upstream/main --pretty=format:&quot;${&lt;/span&gt;&lt;span&gt;commitFormat&lt;/span&gt;&lt;span&gt;}&quot; --no-merges`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; commits&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; output&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;(Boolean)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;hash&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; line.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;|&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; { hash, message, date, author };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7. 查看远程文件内容&lt;a href=&quot;#7-查看远程文件内容&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; show&lt;/span&gt;&lt;span&gt; upstream/main:package.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：直接查看远程分支上某个文件的内容，无需切换分支或合并。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用场景&lt;/strong&gt;：获取上游仓库的版本号，用于显示”将更新到 x.x.x 版本”。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; packageJson&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; execSync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;git show upstream/main:package.json&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;version&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;(packageJson);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`最新版本: ${&lt;/span&gt;&lt;span&gt;version&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;8. 执行合并&lt;a href=&quot;#8-执行合并&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; merge&lt;/span&gt;&lt;span&gt; upstream/main&lt;/span&gt;&lt;span&gt; --no-edit&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：将 upstream/main 分支合并到当前分支。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;参数解析&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--no-edit&lt;/code&gt;：使用自动生成的合并提交信息，不打开编辑器&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;合并策略&lt;/strong&gt;：Git 会自动选择合适的合并策略：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fast-forward&lt;/strong&gt;：如果本地没有新提交，直接移动指针&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Three-way merge&lt;/strong&gt;：如果有分叉，创建一个合并提交&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：本工具采用 merge 同步上游，保留本地历史。如果你的需求是”强制与上游一致”（丢弃本地修改），需要使用 rebase 或 reset 方案，不在本文讨论范围。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;9. 检测合并冲突&lt;a href=&quot;#9-检测合并冲突&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; diff&lt;/span&gt;&lt;span&gt; --name-only&lt;/span&gt;&lt;span&gt; --diff-filter=U&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：列出所有未解决冲突的文件。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;参数解析&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--name-only&lt;/code&gt;：只输出文件名&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--diff-filter=U&lt;/code&gt;：只显示 Unmerged（未合并/冲突）的文件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;另一种方式是解析 &lt;code&gt;git status --porcelain&lt;/code&gt; 的输出，查找冲突标记：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; statusOutput&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; execSync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;git status --porcelain&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; conflictFiles&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; statusOutput&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; status&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; line.&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // U = Unmerged, AA = both added, DD = both deleted&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; status.&lt;/span&gt;&lt;span&gt;includes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;U&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; status &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;AA&quot;&lt;/span&gt;&lt;span&gt; ||&lt;/span&gt;&lt;span&gt; status &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;DD&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 注：为简化展示，这里直接截取路径&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 若需完整兼容重命名/特殊路径，应使用更严格的 porcelain 解析&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; line.&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;10. 中止合并&lt;a href=&quot;#10-中止合并&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; merge&lt;/span&gt;&lt;span&gt; --abort&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：中止当前的合并操作，恢复到合并前的状态。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用场景&lt;/strong&gt;：当用户遇到冲突但不想手动解决时，可以选择中止合并。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; abortMerge&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; boolean&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    execSync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;git merge --abort&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;状态机设计&lt;a href=&quot;#状态机设计&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;如果是简单粗暴的使用 &lt;code&gt;useEffect&lt;/code&gt; 的话，会出现很多 &lt;code&gt;useEffect&lt;/code&gt; 那自然很不好。&lt;/p&gt;
&lt;p&gt;整个更新流程使用简单的 &lt;code&gt;useReducer&lt;/code&gt; + Effect Map 模式管理，将状态转换逻辑和副作用处理分离，确保流程清晰可控。&lt;/p&gt;
&lt;h3&gt;为什么不用 Redux？&lt;a href=&quot;#为什么不用-redux&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在设计 CLI 状态管理时，很自然会想到 Redux，毕竟它是 React 生态中最成熟的状态管理方案，而且还是用着 Ink 来进行开发的。但对于 CLI 工具，&lt;code&gt;useReducer&lt;/code&gt; 是更合适的选择，理由如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;状态作用域单一：CLI 工具通常是&lt;strong&gt;单组件树&lt;/strong&gt;结构，不存在跨页面、跨路由的状态共享需求，&lt;/li&gt;
&lt;li&gt;无需 Middleware 生态：Redux 的强大之处在于中间件生态（redux-thunk、redux-saga、redux-observable），用于处理复杂的异步流程。但我们的场景不需要那么复杂。&lt;/li&gt;
&lt;li&gt;依赖最小化：CLI 工具应该&lt;strong&gt;快速启动、轻量运行&lt;/strong&gt;。&lt;code&gt;useReducer&lt;/code&gt; 内置于 React，不会引入额外依赖（当然 React 本身也是依赖，不过我的项目里本来就需要它）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;总之，对这个场景来说 Redux 有点”过度设计”。&lt;/p&gt;
&lt;h3&gt;那咋整？&lt;a href=&quot;#那咋整&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reducer&lt;/strong&gt;：集中管理所有状态转换逻辑，纯函数易于测试&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Effect Map&lt;/strong&gt;：状态到副作用的映射，统一处理异步操作&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;单一 Effect&lt;/strong&gt;：一个 &lt;code&gt;useEffect&lt;/code&gt; 驱动整个流程&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面是完整的状态转换流程图，展示了所有可能的状态转换路径和条件分支：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：Mermaid stateDiagram 中状态名不能包含连字符 &lt;code&gt;-&lt;/code&gt;，这里使用 camelCase 命名。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;stateDiagram-v2
    [*] --&amp;gt; checking: 开始更新

    checking --&amp;gt; error: 不在 main 分支
    checking --&amp;gt; dirtyWarning: 工作区不干净 &amp;amp;&amp;amp; !force
    checking --&amp;gt; fetching: 工作区干净 || force

    dirtyWarning --&amp;gt; [*]: 用户取消
    dirtyWarning --&amp;gt; fetching: 用户继续

    fetching --&amp;gt; upToDate: behindCount = 0
    fetching --&amp;gt; backupConfirm: behindCount &amp;gt; 0 &amp;amp;&amp;amp; !skipBackup
    fetching --&amp;gt; preview: behindCount &amp;gt; 0 &amp;amp;&amp;amp; skipBackup

    backupConfirm --&amp;gt; backingUp: 用户确认备份
    backupConfirm --&amp;gt; preview: 用户跳过备份

    backingUp --&amp;gt; preview: 备份完成
    backingUp --&amp;gt; error: 备份失败

    preview --&amp;gt; [*]: checkOnly 模式
    preview --&amp;gt; merging: 用户确认更新
    preview --&amp;gt; [*]: 用户取消

    merging --&amp;gt; conflict: 合并冲突
    merging --&amp;gt; installing: 合并成功

    conflict --&amp;gt; [*]: 用户处理冲突

    installing --&amp;gt; done: 依赖安装成功
    installing --&amp;gt; error: 依赖安装失败

    done --&amp;gt; [*]
    error --&amp;gt; [*]
    upToDate --&amp;gt; [*]&lt;/pre&gt;
&lt;h3&gt;类型定义&lt;a href=&quot;#类型定义&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 12 种状态覆盖完整流程&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; UpdateStatus&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;span&gt; &quot;checking&quot;&lt;/span&gt;&lt;span&gt; // 检查 Git 状态&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;span&gt; &quot;dirty-warning&quot;&lt;/span&gt;&lt;span&gt; // 工作区有未提交更改&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;span&gt; &quot;backup-confirm&quot;&lt;/span&gt;&lt;span&gt; // 确认备份&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;span&gt; &quot;backing-up&quot;&lt;/span&gt;&lt;span&gt; // 正在备份&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;span&gt; &quot;fetching&quot;&lt;/span&gt;&lt;span&gt; // 获取更新&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;span&gt; &quot;preview&quot;&lt;/span&gt;&lt;span&gt; // 显示更新预览&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;span&gt; &quot;merging&quot;&lt;/span&gt;&lt;span&gt; // 合并中&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;span&gt; &quot;installing&quot;&lt;/span&gt;&lt;span&gt; // 安装依赖&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;span&gt; &quot;done&quot;&lt;/span&gt;&lt;span&gt; // 完成&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;span&gt; &quot;conflict&quot;&lt;/span&gt;&lt;span&gt; // 有冲突&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;span&gt; &quot;up-to-date&quot;&lt;/span&gt;&lt;span&gt; // 已是最新&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;span&gt; &quot;error&quot;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// 错误&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// Action 驱动状态转换&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; UpdateAction&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &quot;GIT_CHECKED&quot;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;payload&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; GitStatusInfo&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &quot;FETCHED&quot;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;payload&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; UpdateInfo&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &quot;BACKUP_CONFIRM&quot;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; &quot;BACKUP_SKIP&quot;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; &quot;UPDATE_CONFIRM&quot;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; &quot;INSTALLED&quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &quot;BACKUP_DONE&quot;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;backupFile&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &quot;MERGED&quot;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;payload&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; MergeResult&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &quot;ERROR&quot;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Reducer 集中状态转换&lt;a href=&quot;#reducer-集中状态转换&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;所有状态转换逻辑集中在 reducer 中，每个 case 只处理当前状态下合法的 action：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; updateReducer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; UpdateState&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;action&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; UpdateAction&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; UpdateState&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;options&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; state;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 通用错误处理：任何状态都可以转到 error&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (action.type &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;ERROR&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;state, status: &lt;/span&gt;&lt;span&gt;&quot;error&quot;&lt;/span&gt;&lt;span&gt;, error: action.error };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  switch&lt;/span&gt;&lt;span&gt; (status) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    case&lt;/span&gt;&lt;span&gt; &quot;checking&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (action.type &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; &quot;GIT_CHECKED&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; state;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;payload&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;gitStatus&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; action;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (gitStatus.currentBranch &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; &quot;main&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          ...&lt;/span&gt;&lt;span&gt;state,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          status: &lt;/span&gt;&lt;span&gt;&quot;error&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          error: &lt;/span&gt;&lt;span&gt;&quot;仅支持在 main 分支执行更新&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;gitStatus.isClean &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; !&lt;/span&gt;&lt;span&gt;options.force) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;state, status: &lt;/span&gt;&lt;span&gt;&quot;dirty-warning&quot;&lt;/span&gt;&lt;span&gt;, gitStatus };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;state, status: &lt;/span&gt;&lt;span&gt;&quot;fetching&quot;&lt;/span&gt;&lt;span&gt;, gitStatus };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    case&lt;/span&gt;&lt;span&gt; &quot;fetching&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (action.type &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; &quot;FETCHED&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; state;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;payload&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;updateInfo&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; action;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (updateInfo.behindCount &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;state, status: &lt;/span&gt;&lt;span&gt;&quot;up-to-date&quot;&lt;/span&gt;&lt;span&gt;, updateInfo };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; nextStatus&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; options.skipBackup &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &quot;preview&quot;&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; &quot;backup-confirm&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;state, status: nextStatus, updateInfo };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // ... 其他状态处理&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Effect Map：统一副作用处理&lt;a href=&quot;#effect-map统一副作用处理&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;每个需要执行副作用的状态对应一个 effect 函数，可返回 cleanup 函数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; EffectFn&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  state&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; UpdateState&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  dispatch&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Dispatch&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;UpdateAction&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; (() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; undefined&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; statusEffects&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Partial&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Record&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;UpdateStatus&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;EffectFn&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  checking&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;span&gt;_state&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;dispatch&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; gitStatus&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; checkGitStatus&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ensureUpstreamRemote&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    dispatch&lt;/span&gt;&lt;span&gt;({ type: &lt;/span&gt;&lt;span&gt;&quot;GIT_CHECKED&quot;&lt;/span&gt;&lt;span&gt;, payload: gitStatus });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; undefined&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  fetching&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;span&gt;_state&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;dispatch&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fetchUpstream&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; info&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; getUpdateInfo&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    dispatch&lt;/span&gt;&lt;span&gt;({ type: &lt;/span&gt;&lt;span&gt;&quot;FETCHED&quot;&lt;/span&gt;&lt;span&gt;, payload: info });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; undefined&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  installing&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;span&gt;_state&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;dispatch&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; cancelled &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    installDeps&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (cancelled) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      dispatch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        result.success&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          ?&lt;/span&gt;&lt;span&gt; { type: &lt;/span&gt;&lt;span&gt;&quot;INSTALLED&quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          :&lt;/span&gt;&lt;span&gt; { type: &lt;/span&gt;&lt;span&gt;&quot;ERROR&quot;&lt;/span&gt;&lt;span&gt;, error: result.error }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      cancelled &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }; &lt;/span&gt;&lt;span&gt;// cleanup&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;组件使用&lt;a href=&quot;#组件使用&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;组件中只需一个核心 &lt;code&gt;useEffect&lt;/code&gt; 来驱动整个状态机：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; UpdateApp&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;checkOnly&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;skipBackup&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;force&lt;/span&gt;&lt;span&gt; }) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;dispatch&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useReducer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    updateReducer,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    { checkOnly, skipBackup, force },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    createInitialState&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 核心：单一 effect 处理所有副作用&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; effect&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; statusEffects[state.status];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;effect) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; effect&lt;/span&gt;&lt;span&gt;(state, dispatch);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }, [state.status, state]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // UI 渲染基于 state.status&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; &amp;lt;&lt;/span&gt;&lt;span&gt;Box&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;...&amp;lt;/&lt;/span&gt;&lt;span&gt;Box&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种模式的优势：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;可测试性&lt;/strong&gt;：Reducer 是纯函数，可以独立测试状态转换&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可维护性&lt;/strong&gt;：状态逻辑集中，不会分散在多个 &lt;code&gt;useEffect&lt;/code&gt; 中&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可扩展性&lt;/strong&gt;：添加新状态只需在 reducer 和 effect map 各加一个 case&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;用户交互设计&lt;a href=&quot;#用户交互设计&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;使用 React Ink 构建终端 UI，提供友好的交互体验：&lt;/p&gt;
&lt;h3&gt;预览更新&lt;a href=&quot;#预览更新&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;发现&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt; 个新提交:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  a1b2c3d&lt;/span&gt;&lt;span&gt; feat:&lt;/span&gt;&lt;span&gt; add&lt;/span&gt;&lt;span&gt; dark&lt;/span&gt;&lt;span&gt; mode&lt;/span&gt;&lt;span&gt; (2 &lt;/span&gt;&lt;span&gt;days&lt;/span&gt;&lt;span&gt; ago&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  e4f5g6h&lt;/span&gt;&lt;span&gt; fix:&lt;/span&gt;&lt;span&gt; responsive&lt;/span&gt;&lt;span&gt; layout&lt;/span&gt;&lt;span&gt; (3 &lt;/span&gt;&lt;span&gt;days&lt;/span&gt;&lt;span&gt; ago&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  i7j8k9l&lt;/span&gt;&lt;span&gt; docs:&lt;/span&gt;&lt;span&gt; update&lt;/span&gt;&lt;span&gt; readme&lt;/span&gt;&lt;span&gt; (1 &lt;/span&gt;&lt;span&gt;week&lt;/span&gt;&lt;span&gt; ago&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;span&gt; 还有&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt; 个提交&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;注意:&lt;/span&gt;&lt;span&gt; 本地有&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt; 个未推送的提交&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;确认更新到最新版本？&lt;/span&gt;&lt;span&gt; (Y/n)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;处理冲突&lt;a href=&quot;#处理冲突&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;发现合并冲突&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;冲突文件:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt; src/config.ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt; src/components/Header.tsx&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;你可以:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  1.&lt;/span&gt;&lt;span&gt; 手动解决冲突后运行:&lt;/span&gt;&lt;span&gt; git&lt;/span&gt;&lt;span&gt; add&lt;/span&gt;&lt;span&gt; .&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; commit&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  2.&lt;/span&gt;&lt;span&gt; 中止合并恢复到更新前状态&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;备份文件:&lt;/span&gt;&lt;span&gt; backup-2026-01-10-full.tar.gz&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;是否中止合并？&lt;/span&gt;&lt;span&gt; (Y/n)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;完整代码实现&lt;a href=&quot;#完整代码实现&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;Git 操作封装&lt;a href=&quot;#git-操作封装&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { execSync } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;node:child_process&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; git&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; execSync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`git ${&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    encoding: &lt;/span&gt;&lt;span&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    stdio: [&lt;/span&gt;&lt;span&gt;&quot;pipe&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;pipe&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;pipe&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }).&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; gitSafe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; git&lt;/span&gt;&lt;span&gt;(args);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; checkGitStatus&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; GitStatusInfo&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; currentBranch&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; git&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;rev-parse --abbrev-ref HEAD&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; statusOutput&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; gitSafe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;status --porcelain&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &quot;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; uncommittedFiles&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; statusOutput&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; line.&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    currentBranch,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    isClean: uncommittedFiles.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; ===&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 注：简化处理，完整兼容需更严格的 porcelain 解析&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    uncommittedFiles: uncommittedFiles.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; line.&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;()),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; getUpdateInfo&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; UpdateInfo&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; revList&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    gitSafe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;rev-list --left-right --count HEAD...upstream/main&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &quot;0&lt;/span&gt;&lt;span&gt;\t&lt;/span&gt;&lt;span&gt;0&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;aheadStr&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;behindStr&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; revList.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\t&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; commitFormat&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;%h|%s|%ar|%an&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; commitsOutput&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    gitSafe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      `log HEAD..upstream/main --pretty=format:&quot;${&lt;/span&gt;&lt;span&gt;commitFormat&lt;/span&gt;&lt;span&gt;}&quot; --no-merges`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &quot;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; commits&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; commitsOutput&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;(Boolean)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;hash&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; line.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;|&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; { hash, message, date, author };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    behindCount: &lt;/span&gt;&lt;span&gt;parseInt&lt;/span&gt;&lt;span&gt;(behindStr, &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    aheadCount: &lt;/span&gt;&lt;span&gt;parseInt&lt;/span&gt;&lt;span&gt;(aheadStr, &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    commits,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; mergeUpstream&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; MergeResult&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    git&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;merge upstream/main --no-edit&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; { success: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;, hasConflict: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;, conflictFiles: [] };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; conflictFiles&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; getConflictFiles&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      success: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      hasConflict: conflictFiles.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; &amp;gt;&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      conflictFiles,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; getConflictFiles&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;[] {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; output&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; gitSafe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;diff --name-only --diff-filter=U&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &quot;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; output.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;(Boolean);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Git 命令速查表&lt;a href=&quot;#git-命令速查表&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;

































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;命令&lt;/th&gt;&lt;th&gt;作用&lt;/th&gt;&lt;th&gt;场景&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;git rev-parse --abbrev-ref HEAD&lt;/code&gt;&lt;/td&gt;&lt;td&gt;获取当前分支名&lt;/td&gt;&lt;td&gt;验证分支&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;git status --porcelain&lt;/code&gt;&lt;/td&gt;&lt;td&gt;机器可读的状态输出&lt;/td&gt;&lt;td&gt;检查工作区&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;git remote get-url &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;获取 remote URL&lt;/td&gt;&lt;td&gt;检查 remote&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;git remote add &amp;lt;name&amp;gt; &amp;lt;url&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;添加 remote&lt;/td&gt;&lt;td&gt;配置 upstream&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;git fetch &amp;lt;remote&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;下载远程更新&lt;/td&gt;&lt;td&gt;获取更新&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;git rev-list --left-right --count A...B&lt;/code&gt;&lt;/td&gt;&lt;td&gt;统计差异提交数&lt;/td&gt;&lt;td&gt;计算 ahead/behind&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;git log A..B --pretty=format:&quot;...&quot;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;列出差异提交&lt;/td&gt;&lt;td&gt;预览更新&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;git show &amp;lt;ref&amp;gt;:&amp;lt;path&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;查看远程文件&lt;/td&gt;&lt;td&gt;获取版本号&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;git merge &amp;lt;branch&amp;gt; --no-edit&lt;/code&gt;&lt;/td&gt;&lt;td&gt;自动合并&lt;/td&gt;&lt;td&gt;执行更新&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;git diff --name-only --diff-filter=U&lt;/code&gt;&lt;/td&gt;&lt;td&gt;列出冲突文件&lt;/td&gt;&lt;td&gt;检测冲突&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;git merge --abort&lt;/code&gt;&lt;/td&gt;&lt;td&gt;中止合并&lt;/td&gt;&lt;td&gt;回滚操作&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;Git 命令功能分类&lt;a href=&quot;#git-命令功能分类&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;为了更好地理解这些命令的用途，下面按功能将它们分类展示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic hierarchy-structure&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title Git 命令功能分类&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  desc 按操作类型组织的命令清单&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 状态检查&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/information&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label git rev-parse&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 获取当前分支名&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label git status --porcelain&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 检查工作区状态&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 远程管理&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/server-network&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label git remote get-url&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 检查 remote 是否存在&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label git remote add&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 添加 upstream remote&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label git fetch&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 下载远程更新&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 提交分析&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/source-commit&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label git rev-list&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 统计提交差异&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label git log&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 查看提交历史&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label git show&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 查看远程文件内容&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 合并操作&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/source-merge&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label git merge&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 执行分支合并&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label git merge --abort&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 中止合并恢复状态&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 冲突检测&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/alert-octagon&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label git diff --diff-filter=U&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 列出未解决冲突文件&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;备份还原功能实现&lt;a href=&quot;#备份还原功能实现&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;除了主题更新，CLI 还提供了完整的备份还原功能，确保用户数据安全。&lt;/p&gt;
&lt;p&gt;备份和还原是两个互补的操作，下图展示了它们的完整工作流：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic compare-hierarchy-row-letter-card-compact-card&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 备份与还原流程对比&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  desc 两个互补操作的完整工作流&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 备份流程&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/backup-restore&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 检查配置&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 确定备份类型和范围&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 创建临时目录&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 准备暂存空间&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 复制文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 按配置复制所需文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 生成 manifest&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 记录版本和元信息&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 压缩打包&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc tar.gz 压缩存档&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 清理临时目录&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 删除暂存目录&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 还原流程&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/restore&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 选择备份&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 读取 manifest 显示备份信息&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 解压到临时目录&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 提取归档内容（包含 manifest）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 读取 manifest.files&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 获取实际备份成功的文件列表&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 按映射复制文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 使用自动生成的 RESTORE_MAP&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 清理临时目录&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 删除解压的暂存文件&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;备份项配置&lt;a href=&quot;#备份项配置&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;备份系统采用配置驱动的方式，定义需要备份的文件和目录：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; interface&lt;/span&gt;&lt;span&gt; BackupItem&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  src&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// 源路径（相对于项目根目录）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  dest&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// 备份内目标路径&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  label&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// 显示标签&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  required&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; boolean&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// 是否为必需项（basic 模式包含）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; BACKUP_ITEMS&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; BackupItem&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 基础备份项（required: true）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    src: &lt;/span&gt;&lt;span&gt;&quot;src/content/blog&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    dest: &lt;/span&gt;&lt;span&gt;&quot;content/blog&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    label: &lt;/span&gt;&lt;span&gt;&quot;博客文章&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    required: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    src: &lt;/span&gt;&lt;span&gt;&quot;config/site.yaml&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    dest: &lt;/span&gt;&lt;span&gt;&quot;config/site.yaml&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    label: &lt;/span&gt;&lt;span&gt;&quot;网站配置&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    required: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    src: &lt;/span&gt;&lt;span&gt;&quot;src/pages/about.md&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    dest: &lt;/span&gt;&lt;span&gt;&quot;pages/about.md&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    label: &lt;/span&gt;&lt;span&gt;&quot;关于页面&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    required: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    src: &lt;/span&gt;&lt;span&gt;&quot;public/img/avatar.webp&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    dest: &lt;/span&gt;&lt;span&gt;&quot;img/avatar.webp&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    label: &lt;/span&gt;&lt;span&gt;&quot;用户头像&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    required: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  { src: &lt;/span&gt;&lt;span&gt;&quot;.env&quot;&lt;/span&gt;&lt;span&gt;, dest: &lt;/span&gt;&lt;span&gt;&quot;env&quot;&lt;/span&gt;&lt;span&gt;, label: &lt;/span&gt;&lt;span&gt;&quot;环境变量&quot;&lt;/span&gt;&lt;span&gt;, required: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 完整备份额外项目（required: false）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  { src: &lt;/span&gt;&lt;span&gt;&quot;public/img&quot;&lt;/span&gt;&lt;span&gt;, dest: &lt;/span&gt;&lt;span&gt;&quot;img&quot;&lt;/span&gt;&lt;span&gt;, label: &lt;/span&gt;&lt;span&gt;&quot;所有图片&quot;&lt;/span&gt;&lt;span&gt;, required: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    src: &lt;/span&gt;&lt;span&gt;&quot;src/assets/lqips.json&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    dest: &lt;/span&gt;&lt;span&gt;&quot;assets/lqips.json&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    label: &lt;/span&gt;&lt;span&gt;&quot;LQIP 数据&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    required: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    src: &lt;/span&gt;&lt;span&gt;&quot;src/assets/similarities.json&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    dest: &lt;/span&gt;&lt;span&gt;&quot;assets/similarities.json&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    label: &lt;/span&gt;&lt;span&gt;&quot;相似度数据&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    required: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    src: &lt;/span&gt;&lt;span&gt;&quot;src/assets/summaries.json&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    dest: &lt;/span&gt;&lt;span&gt;&quot;assets/summaries.json&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    label: &lt;/span&gt;&lt;span&gt;&quot;AI 摘要数据&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    required: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;备份流程&lt;a href=&quot;#备份流程&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;备份操作使用 tar.gz 格式压缩，并生成 manifest.json 记录元信息：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; runBackup&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  isFullBackup&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; boolean&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  onProgress&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;results&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; BackupResult&lt;/span&gt;&lt;span&gt;[]) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; BackupOutput&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 1. 创建备份目录和临时目录&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  fs.&lt;/span&gt;&lt;span&gt;mkdirSync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BACKUP_DIR&lt;/span&gt;&lt;span&gt;, { recursive: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; timestamp&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Date&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;toISOString&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;[:.]&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;-&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;19&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; tempDir&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BACKUP_DIR&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;`.tmp-backup-${&lt;/span&gt;&lt;span&gt;timestamp&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 2. 过滤备份项目（基础备份只包含 required: true 的项目）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; itemsToBackup&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; BACKUP_ITEMS&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span&gt;item&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; item.required &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; isFullBackup&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 3. 复制文件到临时目录&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; results&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; BackupResult&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; item&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; itemsToBackup) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; srcPath&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;PROJECT_ROOT&lt;/span&gt;&lt;span&gt;, item.src);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; destPath&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(tempDir, item.dest);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (fs.&lt;/span&gt;&lt;span&gt;existsSync&lt;/span&gt;&lt;span&gt;(srcPath)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      fs.&lt;/span&gt;&lt;span&gt;cpSync&lt;/span&gt;&lt;span&gt;(srcPath, destPath, { recursive: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      results.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;({ item, success: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;, skipped: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      results.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;({ item, success: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;, skipped: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    onProgress&lt;/span&gt;&lt;span&gt;?.([&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;results]); &lt;/span&gt;&lt;span&gt;// 进度回调&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 4. 生成 manifest.json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; manifest&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    name: &lt;/span&gt;&lt;span&gt;&quot;astro-koharu-backup&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    version: &lt;/span&gt;&lt;span&gt;getVersion&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    type: isFullBackup &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &quot;full&quot;&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; &quot;basic&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    timestamp,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    created_at: &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; Date&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;toISOString&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    files: Object.&lt;/span&gt;&lt;span&gt;fromEntries&lt;/span&gt;&lt;span&gt;(results.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; [r.item.dest, r.success])),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  fs.&lt;/span&gt;&lt;span&gt;writeFileSync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(tempDir, &lt;/span&gt;&lt;span&gt;&quot;manifest.json&quot;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;(manifest, &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 5. 压缩并清理&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  tarCreate&lt;/span&gt;&lt;span&gt;(backupFilePath, tempDir);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  fs.&lt;/span&gt;&lt;span&gt;rmSync&lt;/span&gt;&lt;span&gt;(tempDir, { recursive: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;, force: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; { results, backupFile: backupFilePath, fileSize, timestamp };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;tar 操作封装&lt;a href=&quot;#tar-操作封装&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用系统 tar 命令进行压缩和解压，并添加路径遍历安全检查：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 安全验证：防止路径遍历攻击&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; validateTarEntries&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;entries&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;[], &lt;/span&gt;&lt;span&gt;archivePath&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; entry&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; entries) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (entry.&lt;/span&gt;&lt;span&gt;includes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\0&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      throw&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`tar entry contains null byte`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; normalized&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; path.posix.&lt;/span&gt;&lt;span&gt;normalize&lt;/span&gt;&lt;span&gt;(entry);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (path.posix.&lt;/span&gt;&lt;span&gt;isAbsolute&lt;/span&gt;&lt;span&gt;(normalized)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      throw&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`tar entry is absolute path: ${&lt;/span&gt;&lt;span&gt;entry&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (normalized.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;includes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;..&quot;&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      throw&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`tar entry contains parent traversal: ${&lt;/span&gt;&lt;span&gt;entry&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 创建压缩包&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; tarCreate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;archivePath&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;sourceDir&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  spawnSync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;tar&quot;&lt;/span&gt;&lt;span&gt;, [&lt;/span&gt;&lt;span&gt;&quot;-czf&quot;&lt;/span&gt;&lt;span&gt;, archivePath, &lt;/span&gt;&lt;span&gt;&quot;-C&quot;&lt;/span&gt;&lt;span&gt;, sourceDir, &lt;/span&gt;&lt;span&gt;&quot;.&quot;&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 解压到指定目录&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; tarExtract&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;archivePath&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;destDir&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  listTarEntries&lt;/span&gt;&lt;span&gt;(archivePath); &lt;/span&gt;&lt;span&gt;// 先验证条目安全性&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  spawnSync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;tar&quot;&lt;/span&gt;&lt;span&gt;, [&lt;/span&gt;&lt;span&gt;&quot;-xzf&quot;&lt;/span&gt;&lt;span&gt;, archivePath, &lt;/span&gt;&lt;span&gt;&quot;-C&quot;&lt;/span&gt;&lt;span&gt;, destDir]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 读取 manifest（不解压整个文件）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; tarExtractManifest&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;archivePath&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; result&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; spawnSync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;tar&quot;&lt;/span&gt;&lt;span&gt;, [&lt;/span&gt;&lt;span&gt;&quot;-xzf&quot;&lt;/span&gt;&lt;span&gt;, archivePath, &lt;/span&gt;&lt;span&gt;&quot;-O&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;manifest.json&quot;&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; result.status &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; result.stdout &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;还原流程&lt;a href=&quot;#还原流程&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;还原操作基于 manifest 驱动，确保只还原实际备份成功的文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 路径映射：从备份项配置自动生成，确保一致性&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; RESTORE_MAP&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Record&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Object.&lt;/span&gt;&lt;span&gt;fromEntries&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  BACKUP_ITEMS&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;item&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; [item.dest, item.src])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; restoreBackup&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;backupPath&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; RestoreResult&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 1. 创建临时目录并解压&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; tempDir&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; fs.&lt;/span&gt;&lt;span&gt;mkdtempSync&lt;/span&gt;&lt;span&gt;(path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(os.&lt;/span&gt;&lt;span&gt;tmpdir&lt;/span&gt;&lt;span&gt;(), &lt;/span&gt;&lt;span&gt;&quot;restore-&quot;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  tarExtract&lt;/span&gt;&lt;span&gt;(backupPath, tempDir);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 2. 读取 manifest 获取实际备份的文件列表&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; manifestPath&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(tempDir, &lt;/span&gt;&lt;span&gt;&quot;manifest.json&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; manifest&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;(fs.&lt;/span&gt;&lt;span&gt;readFileSync&lt;/span&gt;&lt;span&gt;(manifestPath, &lt;/span&gt;&lt;span&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; restored&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; skipped&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 3. 基于 manifest.files 还原（只还原成功备份的文件）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;backupPath&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;success&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;of&lt;/span&gt;&lt;span&gt; Object.&lt;/span&gt;&lt;span&gt;entries&lt;/span&gt;&lt;span&gt;(manifest.files)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 跳过备份失败的文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;success) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      skipped.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(backupPath);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      continue&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; projectPath&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; RESTORE_MAP&lt;/span&gt;&lt;span&gt;[backupPath];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;projectPath) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      console.&lt;/span&gt;&lt;span&gt;warn&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`未知的备份路径: ${&lt;/span&gt;&lt;span&gt;backupPath&lt;/span&gt;&lt;span&gt;}，跳过`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      skipped.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(backupPath);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      continue&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; srcPath&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(tempDir, backupPath);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; destPath&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;PROJECT_ROOT&lt;/span&gt;&lt;span&gt;, projectPath);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (fs.&lt;/span&gt;&lt;span&gt;existsSync&lt;/span&gt;&lt;span&gt;(srcPath)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      fs.&lt;/span&gt;&lt;span&gt;mkdirSync&lt;/span&gt;&lt;span&gt;(path.&lt;/span&gt;&lt;span&gt;dirname&lt;/span&gt;&lt;span&gt;(destPath), { recursive: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      fs.&lt;/span&gt;&lt;span&gt;cpSync&lt;/span&gt;&lt;span&gt;(srcPath, destPath, { recursive: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      restored.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(projectPath);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      skipped.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(backupPath);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 4. 清理临时目录&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  fs.&lt;/span&gt;&lt;span&gt;rmSync&lt;/span&gt;&lt;span&gt;(tempDir, { recursive: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;, force: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    restored,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    skipped,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    backupType: manifest.type,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    version: manifest.version,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Dry-Run 模式详解&lt;a href=&quot;#dry-run-模式详解&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Dry-run（预演模式）是 CLI 工具中常见的安全特性，允许用户在实际执行前预览操作结果。本实现采用&lt;strong&gt;函数分离 + 条件渲染&lt;/strong&gt;的模式。&lt;/p&gt;
&lt;p&gt;下图展示了预览模式和实际执行模式的核心区别：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic compare-binary-horizontal-badge-card-arrow&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title Dry-Run 模式与实际执行对比&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  desc 预览模式和实际还原的关键区别&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 预览模式&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 安全的只读预览&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/eye&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 提取 manifest.json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 调用 tarExtractManifest 不解压整个归档&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 读取 manifest.files&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 获取实际备份的文件列表&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 统计文件数量&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 调用 tarList 计算每个路径的文件数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 不修改任何文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 零副作用,可安全执行&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 实际执行&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 基于 manifest 的还原&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/content-save&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 解压整个归档&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 调用 tarExtract 提取所有文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 读取 manifest.files&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 获取实际备份成功的文件列表&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 按 manifest 复制文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 只还原 success: true 的文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 显示跳过的文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 报告 success: false 的文件&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;预览函数和执行函数&lt;a href=&quot;#预览函数和执行函数&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;关键在于提供两个功能相似但副作用不同的函数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 预览函数：只读取 manifest，不解压不修改文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; getRestorePreview&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;backupPath&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; RestorePreviewItem&lt;/span&gt;&lt;span&gt;[] {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 只提取 manifest.json，不解压整个归档&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; manifestContent&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; tarExtractManifest&lt;/span&gt;&lt;span&gt;(backupPath);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;manifestContent) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    throw&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;无法读取备份 manifest&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; manifest&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;(manifestContent);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; previewItems&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; RestorePreviewItem&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 基于 manifest.files 生成预览&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;backupPath&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;success&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;of&lt;/span&gt;&lt;span&gt; Object.&lt;/span&gt;&lt;span&gt;entries&lt;/span&gt;&lt;span&gt;(manifest.files)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;success) &lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// 跳过备份失败的文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; projectPath&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; RESTORE_MAP&lt;/span&gt;&lt;span&gt;[backupPath];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;projectPath) &lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 从归档中统计文件数量（不解压）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; files&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; tarList&lt;/span&gt;&lt;span&gt;(backupPath);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; matchingFiles&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; files.&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      (&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; f &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; backupPath &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; f.&lt;/span&gt;&lt;span&gt;startsWith&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;backupPath&lt;/span&gt;&lt;span&gt;}/`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; fileCount&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; matchingFiles.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    previewItems.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      path: projectPath,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      fileCount: fileCount &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      backupPath,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; previewItems;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 执行函数：实际解压并复制文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; restoreBackup&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;backupPath&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; RestoreResult&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; tempDir&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; fs.&lt;/span&gt;&lt;span&gt;mkdtempSync&lt;/span&gt;&lt;span&gt;(path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(os.&lt;/span&gt;&lt;span&gt;tmpdir&lt;/span&gt;&lt;span&gt;(), &lt;/span&gt;&lt;span&gt;&quot;restore-&quot;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  tarExtract&lt;/span&gt;&lt;span&gt;(backupPath, tempDir); &lt;/span&gt;&lt;span&gt;// 实际解压&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 读取 manifest 驱动还原&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; manifest&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fs.&lt;/span&gt;&lt;span&gt;readFileSync&lt;/span&gt;&lt;span&gt;(path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(tempDir, &lt;/span&gt;&lt;span&gt;&quot;manifest.json&quot;&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; restored&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;backupPath&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;success&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;of&lt;/span&gt;&lt;span&gt; Object.&lt;/span&gt;&lt;span&gt;entries&lt;/span&gt;&lt;span&gt;(manifest.files)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;success) &lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; projectPath&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; RESTORE_MAP&lt;/span&gt;&lt;span&gt;[backupPath];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // ... 实际复制文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fs.&lt;/span&gt;&lt;span&gt;cpSync&lt;/span&gt;&lt;span&gt;(srcPath, destPath, { recursive: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    restored.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(projectPath);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; { restored, skipped: [], backupType: manifest.type };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;两个函数的核心区别：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;预览&lt;/strong&gt;：调用 &lt;code&gt;tarExtractManifest()&lt;/code&gt; 只提取 manifest，再用 &lt;code&gt;tarList()&lt;/code&gt; 统计文件数量&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;执行&lt;/strong&gt;：调用 &lt;code&gt;tarExtract()&lt;/code&gt; 解压整个归档，基于 manifest.files 复制文件&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;组件层：条件分发&lt;a href=&quot;#组件层条件分发&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;在 React 组件中，根据 &lt;code&gt;dryRun&lt;/code&gt; 参数决定调用哪个函数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; RestoreAppProps&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  dryRun&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; boolean&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// 是否为预览模式&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  force&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; boolean&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// 是否跳过确认&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; RestoreApp&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;dryRun&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;force&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; RestoreAppProps&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setResult&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;&amp;lt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    items&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; RestorePreviewItem&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;[];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    backupType&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    skipped&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;[];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 预览模式：只读取 manifest&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; runDryRun&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useCallback&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; previewItems&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; getRestorePreview&lt;/span&gt;&lt;span&gt;(selectedBackup);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setResult&lt;/span&gt;&lt;span&gt;({ items: previewItems });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setStatus&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;done&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }, [selectedBackup]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 实际还原：基于 manifest 执行还原&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; runRestore&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useCallback&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setStatus&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;restoring&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;restored&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;skipped&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;backupType&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; restoreBackup&lt;/span&gt;&lt;span&gt;(selectedBackup);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setResult&lt;/span&gt;&lt;span&gt;({ items: restored, backupType, skipped });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setStatus&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;done&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }, [selectedBackup]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 确认时根据模式分发&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  function&lt;/span&gt;&lt;span&gt; handleConfirm&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (dryRun) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      runDryRun&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      runRestore&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关键设计：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;统一数据结构&lt;/strong&gt;：&lt;code&gt;result&lt;/code&gt; 可以容纳预览和执行两种结果&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;类型区分&lt;/strong&gt;：预览返回 &lt;code&gt;RestorePreviewItem[]&lt;/code&gt;（含 fileCount），执行返回 &lt;code&gt;string[]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;额外信息&lt;/strong&gt;：执行模式返回 &lt;code&gt;backupType&lt;/code&gt; 和 &lt;code&gt;skipped&lt;/code&gt;，用于显示完整信息&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;UI 层：差异化展示&lt;a href=&quot;#ui-层差异化展示&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;预览模式和实际执行模式在 UI 上有明确区分：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* 确认提示：显示备份类型和文件数量 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt; color&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;yellow&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {dryRun &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &quot;[预览模式] &quot;&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; &quot;&quot;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  确认还原 {result?.backupType} 备份? 此操作将覆盖现有文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* 完成状态：根据模式显示不同标题 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt; bold&lt;/span&gt;&lt;span&gt; color&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;green&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {dryRun &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &quot;预览模式&quot;&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; &quot;还原完成&quot;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* 结果展示：预览模式显示文件数量统计 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  result?.items.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;item&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; isPreviewItem&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; typeof&lt;/span&gt;&lt;span&gt; item &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; &quot;string&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; filePath&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; isPreviewItem &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; item.path &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; item;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; fileCount&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; isPreviewItem &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; item.fileCount &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt; key&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{filePath}&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt; color&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;green&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;{&lt;/span&gt;&lt;span&gt;&quot;  &quot;&lt;/span&gt;&lt;span&gt;}+ &amp;lt;/&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt;&amp;gt;{filePath}&amp;lt;/&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {&lt;/span&gt;&lt;span&gt;/* 预览模式额外显示文件数量 */&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {isPreviewItem &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; fileCount &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &amp;lt;&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt; dimColor&lt;/span&gt;&lt;span&gt;&amp;gt; ({fileCount} 文件)&amp;lt;/&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        )}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* 统计文案：使用 &quot;将&quot; vs &quot;已&quot; 区分 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {dryRun &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &quot;将&quot;&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; &quot;已&quot;&lt;/span&gt;&lt;span&gt;}还原: &amp;lt;&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt; color&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;green&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;{result?.items.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;}&amp;lt;/&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt;&amp;gt;{&lt;/span&gt;&lt;span&gt;&quot; &quot;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  项&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* 显示跳过的文件（仅实际执行模式） */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  !&lt;/span&gt;&lt;span&gt;dryRun &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; result?.skipped &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; result.skipped.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; &amp;gt;&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;Box&lt;/span&gt;&lt;span&gt; flexDirection&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;column&quot;&lt;/span&gt;&lt;span&gt; marginTop&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;}&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt; color&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;yellow&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;跳过的文件:&amp;lt;/&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      {result.skipped.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt; key&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{file} &lt;/span&gt;&lt;span&gt;dimColor&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          {&lt;/span&gt;&lt;span&gt;&quot;  &quot;&lt;/span&gt;&lt;span&gt;}- {file}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;/&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      ))}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;Box&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* 预览模式特有提示 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  dryRun &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; &amp;lt;&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt; color&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;yellow&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;这是预览模式，没有文件被修改&amp;lt;/&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* 实际执行模式：显示后续步骤 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  !&lt;/span&gt;&lt;span&gt;dryRun &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;Box&lt;/span&gt;&lt;span&gt; flexDirection&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;column&quot;&lt;/span&gt;&lt;span&gt; marginTop&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;}&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt; dimColor&lt;/span&gt;&lt;span&gt;&amp;gt;后续步骤:&amp;lt;/&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt; dimColor&lt;/span&gt;&lt;span&gt;&amp;gt;{&lt;/span&gt;&lt;span&gt;&quot;  &quot;&lt;/span&gt;&lt;span&gt;}1. pnpm install # 安装依赖&amp;lt;/&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt; dimColor&lt;/span&gt;&lt;span&gt;&amp;gt;{&lt;/span&gt;&lt;span&gt;&quot;  &quot;&lt;/span&gt;&lt;span&gt;}2. pnpm build # 构建项目&amp;lt;/&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;Box&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;命令行使用&lt;a href=&quot;#命令行使用&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 预览模式：查看将要还原的内容&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; restore&lt;/span&gt;&lt;span&gt; --dry-run&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 实际执行&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; restore&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 跳过确认直接执行&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; restore&lt;/span&gt;&lt;span&gt; --force&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 还原最新备份（预览）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; restore&lt;/span&gt;&lt;span&gt; --latest&lt;/span&gt;&lt;span&gt; --dry-run&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;输出对比&lt;a href=&quot;#输出对比&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;预览模式输出（Full 备份）&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;备份文件:&lt;/span&gt;&lt;span&gt; backup-2026-01-10-12-30-00-full.tar.gz&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;备份类型:&lt;/span&gt;&lt;span&gt; full&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;主题版本:&lt;/span&gt;&lt;span&gt; 1.2.0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;备份时间:&lt;/span&gt;&lt;span&gt; 2026-01-10&lt;/span&gt;&lt;span&gt; 12:30:00&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[预览模式] 确认还原 full 备份&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; 此操作将覆盖现有文件 (&lt;/span&gt;&lt;span&gt;Y/n&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;预览模式&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  +&lt;/span&gt;&lt;span&gt; src/content/blog&lt;/span&gt;&lt;span&gt; (42 &lt;/span&gt;&lt;span&gt;文件&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  +&lt;/span&gt;&lt;span&gt; config/site.yaml&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  +&lt;/span&gt;&lt;span&gt; src/pages/about.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  +&lt;/span&gt;&lt;span&gt; .env&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  +&lt;/span&gt;&lt;span&gt; public/img&lt;/span&gt;&lt;span&gt; (128 &lt;/span&gt;&lt;span&gt;文件&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  +&lt;/span&gt;&lt;span&gt; src/assets/lqips.json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  +&lt;/span&gt;&lt;span&gt; src/assets/similarities.json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  +&lt;/span&gt;&lt;span&gt; src/assets/summaries.json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;将还原:&lt;/span&gt;&lt;span&gt; 8&lt;/span&gt;&lt;span&gt; 项&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;这是预览模式，没有文件被修改&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;预览模式输出（Basic 备份）&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;备份文件:&lt;/span&gt;&lt;span&gt; backup-2026-01-10-12-30-00-basic.tar.gz&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;备份类型:&lt;/span&gt;&lt;span&gt; basic&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;主题版本:&lt;/span&gt;&lt;span&gt; 1.2.0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;备份时间:&lt;/span&gt;&lt;span&gt; 2026-01-10&lt;/span&gt;&lt;span&gt; 12:30:00&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[预览模式] 确认还原 basic 备份&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; 此操作将覆盖现有文件 (&lt;/span&gt;&lt;span&gt;Y/n&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;预览模式&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  +&lt;/span&gt;&lt;span&gt; src/content/blog&lt;/span&gt;&lt;span&gt; (42 &lt;/span&gt;&lt;span&gt;文件&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  +&lt;/span&gt;&lt;span&gt; config/site.yaml&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  +&lt;/span&gt;&lt;span&gt; src/pages/about.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  +&lt;/span&gt;&lt;span&gt; .env&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  +&lt;/span&gt;&lt;span&gt; public/img/avatar.webp&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;将还原:&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt; 项&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;这是预览模式，没有文件被修改&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;实际执行输出（含跳过的文件）&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;还原完成&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  +&lt;/span&gt;&lt;span&gt; src/content/blog&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  +&lt;/span&gt;&lt;span&gt; config/site.yaml&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  +&lt;/span&gt;&lt;span&gt; src/pages/about.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  +&lt;/span&gt;&lt;span&gt; .env&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  +&lt;/span&gt;&lt;span&gt; public/img&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;跳过的文件:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt; src/assets/lqips.json&lt;/span&gt;&lt;span&gt; (备份时不存在)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;已还原:&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt; 项&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;后续步骤:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  1.&lt;/span&gt;&lt;span&gt; pnpm&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; # 安装依赖&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  2.&lt;/span&gt;&lt;span&gt; pnpm&lt;/span&gt;&lt;span&gt; build&lt;/span&gt;&lt;span&gt; # 构建项目&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  3.&lt;/span&gt;&lt;span&gt; pnpm&lt;/span&gt;&lt;span&gt; dev&lt;/span&gt;&lt;span&gt; # 启动开发服务器&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;写在最后&lt;a href=&quot;#写在最后&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;能看到这里，那很厉害了，觉得还挺喜欢的话，欢迎给我一个 star 呢～&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://github.com/cosZone/astro-koharu&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://github.com/fluidicon.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;github.com&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;GitHub - cosZone/astro-koharu: astro-koharu 是一个萌系/二次元/粉蓝配色的 astro 主题博客，灵感来自 Hexo 的 Shoka 主题，加了很多自己的小巧思，性能优越。&lt;/h3&gt;
        &lt;p&gt;astro-koharu 是一个萌系/二次元/粉蓝配色的 astro 主题博客，灵感来自 Hexo 的 Shoka 主题，加了很多自己的小巧思，性能优越。 - cosZone/astro-koharu&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://github.com/cosZone/astro-koharu&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://opengraph.githubassets.com/2d40439fc78bd8179de9715479d3aade2e2d35453ac7b364b41b5fa9be7133de/cosZone/astro-koharu&quot; alt=&quot;GitHub - cosZone/astro-koharu: astro-koharu 是一个萌系/二次元/粉蓝配色的 astro 主题博客，灵感来自 Hexo 的 Shoka 主题，加了很多自己的小巧思，性能优越。&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;自认为这次实现的这个 CLI 对于我自己的需求来说，相当好用，只恨没有早一些实践，如果你看到这篇文章，可以放心大胆的去构建。&lt;/p&gt;
&lt;p&gt;相关链接如下&lt;/p&gt;
&lt;h3&gt;React Ink&lt;a href=&quot;#react-ink&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vadimdemedes/ink&quot;&gt;Ink - GitHub&lt;/a&gt; - React for interactive command-line apps，官方仓库&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vadimdemedes/ink-ui&quot;&gt;Ink UI&lt;/a&gt; - Ink 的 UI 组件库，提供 TextInput、Spinner、ProgressBar 等组件&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.logrocket.com/using-ink-ui-react-build-interactive-custom-clis/&quot;&gt;Using Ink UI with React to build interactive, custom CLIs - LogRocket&lt;/a&gt; - Ink UI 使用教程&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ivanleo.com/blog/migrating-to-react-ink&quot;&gt;Building a Coding CLI with React Ink&lt;/a&gt; - 实战教程，包含流式输出实现&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.freecodecamp.org/news/react-js-ink-cli-tutorial/&quot;&gt;React + Ink CLI Tutorial - FreeCodeCamp&lt;/a&gt; - 入门教程&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lirantal/nodejs-cli-apps-best-practices&quot;&gt;Node.js CLI Apps Best Practices - GitHub&lt;/a&gt; - Node.js CLI 最佳实践清单&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Git 同步 Fork&lt;a href=&quot;#git-同步-fork&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/articles/syncing-a-fork&quot;&gt;Syncing a fork - GitHub Docs&lt;/a&gt; - 官方文档&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.atlassian.com/git/tutorials/git-forks-and-upstreams&quot;&gt;Git Upstreams and Forks - Atlassian&lt;/a&gt; - Atlassian 的详细教程&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.freecodecamp.org/news/how-to-sync-your-fork-with-the-original-git-repository/&quot;&gt;How to Sync Your Fork with the Original Git Repository - FreeCodeCamp&lt;/a&gt; - 完整同步指南&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;状态机与 useReducer&lt;a href=&quot;#状态机与-usereducer&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://kyleshevlin.com/how-to-use-usereducer-as-a-finite-state-machine/&quot;&gt;How to Use useReducer as a Finite State Machine - Kyle Shevlin&lt;/a&gt; - 将 useReducer 用作状态机的经典文章&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.to/rohanfaiyazkhan/turning-your-react-component-into-a-finite-state-machine-with-usereducer-14nm&quot;&gt;Turning your React Component into a Finite State Machine - DEV&lt;/a&gt; - 状态机实战教程&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>category:工具</category><category>tag:Git</category><category>tag:Ink</category><category>tag:React</category></item><item><title>FE Bits Vol.22 | CSS @scope 全面可用，ViteLand 12 月回顾</title><link>https://blog.cosine.ren/post/weekly-22</link><guid isPermaLink="false">weekly-22</guid><description>本期包含个人与项目进展：Discord 公群开放，Koharu 主题 v2 发布并上架 Astro 商店，新增 Koharu CLI（备份/还原、主题更新、内容生成、备份管理），Moe Copy AI 上架 Firefox；技术侧聚焦 CSS @scope 全面可用与 ViteLand 12 月回顾（Oxc 数十倍性能、Vite 8 Beta 原生插件、Vitest 引入 OpenTelemetry 与 fs 缓存、Rolldown 优化分块与选项）；精选 Temporal、Web Install API、现代 Hooks、包体积减重、加速计/Three.js/WebGL 动效等文章；工具与灵感涵盖 DiceBear、在线抖动器及 GitHub .png/.keys 小技巧，另荐 Addy Osmani 14 年 21 条职业经验与多款趣站/CodePen 作品。</description><pubDate>Sun, 11 Jan 2026 11:30:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本期网址 &lt;a href=&quot;https://blog.cosine.ren/post/weekly-22&quot;&gt;https://blog.cosine.ren/post/weekly-22&lt;/a&gt; &lt;br /&gt;
本周刊更新时间期望是在每周天。 &lt;br /&gt;
推荐订阅本周刊的 &lt;a href=&quot;https://blog.cosine.ren/rss.xml&quot;&gt;RSS&lt;/a&gt;。&lt;br /&gt;
公众号 前端周周谈 FE Bits，点击阅读原文链接可查看原文。&lt;br /&gt;
QQ 讨论小群 598022684 / &lt;a href=&quot;https://discord.gg/XzvrvNMcSe&quot;&gt;Discord 群&lt;/a&gt; &lt;br /&gt;
本周刊文章内容同时也开源在 &lt;a href=&quot;https://github.com/yusixian/fe-bits-weekly&quot;&gt;fe-bits-weekly&lt;/a&gt;，欢迎关注。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;今天是 2026 年 1 月 11 日，星期天。&lt;/p&gt;
&lt;p&gt;年终总结在写了（还在写？）&lt;/p&gt;
&lt;p&gt;建了个 discord 群，虽然不怎么上 discord（）&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://discord.gg/XzvrvNMcSe&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://t2.gstatic.com/faviconV2?client=SOCIAL&amp;amp;type=FAVICON&amp;amp;fallback_opts=TYPE,SIZE,URL&amp;amp;url=https://discord.gg/XzvrvNMcSe&amp;amp;size=128&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;discord.com&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;Join the Cos 的 Discord 小窝 Discord Server!&lt;/h3&gt;
        &lt;p&gt;不知道做什么的服务器。比较随便。因为后知后觉的意识到虽然我用 tg 聊天比较多，但是 discord 群作为公开群好像比较合适比较方便（嗯？）总之想找我聊天的可以加，后续应该会作为各种项目里边 link 的公开群。 | 34 members&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://discord.gg/XzvrvNMcSe&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;因为后知后觉的意识到 Discord 群作为公开群好像比较合适比较方便（嗯）&lt;/p&gt;
&lt;p&gt;总之想找我聊天或者问问题的都可以加，后续应该会作为各种项目里边 link 的公开群。&lt;/p&gt;
&lt;p&gt;然后是博客主题更新，新增了 Koharu CLI 交互式命令行工具，提供博客内容备份/还原、主题更新、内容生成、备份管理等功能。配置文件也统一更改为 config/site.yaml 了。&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://github.com/cosZone/astro-koharu/releases/tag/v2.0.0&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://github.com/fluidicon.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;github.com&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;Release v2.0.0 · cosZone/astro-koharu&lt;/h3&gt;
        &lt;p&gt;What’s Changed Feat/yaml by @yusixian in #42
Build/backup by @yusixian in #43 Full Changelog: v1.4.0...v2.0.0
Breaking Changes 配置文件统一更改为 config/site.yaml
Umami 分析配置从环境变量移至 site.yaml
Remark42 配置移…&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://github.com/cosZone/astro-koharu/releases/tag/v2.0.0&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://opengraph.githubassets.com/25eadea4f0615caaca667ca13814688f133cd7e91a834785a199c9abe9a0b3bf/cosZone/astro-koharu/releases/tag/v2.0.0&quot; alt=&quot;Release v2.0.0 · cosZone/astro-koharu&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/388028f78817bf496d1c8223ef4b26b4.gif&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;然后觉得还挺有意思的，之后准备写一篇博客简单讲讲（实现交互式 Git 主题更新 CLI 的完整方案）&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/2867558b13c393a4e16b4f9c6ca186a2.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;上架了 astro 主题商店，发了一篇 v2ex 帖子。&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://astro.build/themes/details/koharu&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://astro.build/favicon.svg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;astro.build&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;Koharu | Astro&lt;/h3&gt;
        &lt;p&gt;astro-koharu is a cute ACG pink and blue color Astro themed blog. It is inspired by the Shoka theme of Hexo and features many of its own ingenious touches. It has excellent performance.&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://astro.build/themes/details/koharu&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://storage.googleapis.com/dev-portal-bucket/xq05eeltzcyad34rai60b3tzb2k2af0gfpwcoh.webp&quot; alt=&quot;Koharu | Astro&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;a href=&quot;https://v2ex.com/t/1184086&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://v2ex.com/static/icon-192.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;v2ex.com&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;写了一个 Astro 博客主题 Koharu，加了一点点前端魔法～ - V2EX&lt;/h3&gt;
        &lt;p&gt;分享创造 - @cosyu - 极简的主题超级多，来贡献一点点花里胡哨风的主题。（其实一开始，只是想简单做做，不知道为什么就变成现在这样啦）已经在 astro 主题商店上架啦～主题： https://astr&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://v2ex.com/t/1184086&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://cdn.v2ex.com/avatar/83fd/0d63/657412_xlarge.png?m=1741008936&quot; alt=&quot;写了一个 Astro 博客主题 Koharu，加了一点点前端魔法～ - V2EX&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;并且 Moe Copy AI 插件上架火狐商店了（没想到不怎么需要适配）&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://addons.mozilla.org/zh-CN/firefox/addon/moe-copy-ai/&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://addons.mozilla.org/favicon.ico?v=3&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;addons.mozilla.org&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;Moe Copy AI – 下载  Firefox 扩展（zh-CN）&lt;/h3&gt;
        &lt;p&gt;下载 Firefox 上的 Moe Copy AI。MoeCopy AI 是一款智能网页内容提取工具，能够准确抓取文章、元数据和图片，输出为 Markdown/HTML/纯文本格式，为 AI 模型提供高质量输入。&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://addons.mozilla.org/zh-CN/firefox/addon/moe-copy-ai/&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://addons.mozilla.org/user-media/previews/full/345/345968.png?modified=1767630571&quot; alt=&quot;Moe Copy AI – 下载 🦊 Firefox 扩展（zh-CN）&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;h2&gt;社区动态&lt;a href=&quot;#社区动态&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;CSS &lt;code&gt;@scope&lt;/code&gt; 规则已广泛可用&lt;a href=&quot;#css-scope-规则已广泛可用&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;CSS &lt;code&gt;@scope&lt;/code&gt; 已获得所有主流浏览器支持，该规则允许您选择特定 DOM 子树中的元素，精确定位元素，而无需编写难以覆盖的过于具体的选择器。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MDN：&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@scope&quot;&gt;@scope - CSS | MDN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;文章：&lt;a href=&quot;https://frontendmasters.com/blog/how-to-scope-css-now-that-its-baseline/&quot;&gt;How to @scope CSS Now That It’s Baseline&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;用法形如&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;@scope&lt;/span&gt;&lt;span&gt; (.article-body) to (figure) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  img&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    border&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; solid&lt;/span&gt;&lt;span&gt; black&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    background-color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;goldenrod&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ViteLand 12 月回顾&lt;a href=&quot;#viteland-12-月回顾&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://voidzero.dev/posts/whats-new-dec-2025&quot;&gt;What’s New in ViteLand: December 2025 Recap&lt;/a&gt;：ViteLand 生态在 2025 年 12 月迎来了一系列重要更新，涵盖了 Vite、Vitest、Rolldown、Oxc 等项目。&lt;/p&gt;
&lt;p&gt;Oxlint 和 Oxfmt 如今已能取代传统工具，而且还在不断改进。可以查看 &lt;a href=&quot;https://github.com/oxc-project/oxc/issues/17411&quot;&gt;2026 年路线图&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;期待能在项目里广泛使用的那天！&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Oxc 的性能突破&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Oxlint 和 Oxfmt 在代码校验（linting）和格式化（formatting）方面提供比 ESLint 和 Prettier 更快的速度（分别快 50-100 倍和 30 倍）。&lt;/li&gt;
&lt;li&gt;Oxlint 已推出 1.0 版本，并新增了类型感知校验（type-aware linting）和兼容 ESLint 的 JS 插件。&lt;/li&gt;
&lt;li&gt;Oxfmt 新增了对嵌入式语言和实验性导入排序的支持。&lt;/li&gt;
&lt;li&gt;Bun、Vue、Preact、date-fns、Inquirer.js、Shopify、Miro、Airbnb 等多个项目已迁移至 Oxc。&lt;/li&gt;
&lt;li&gt;Oxc 团队计划在 2026 年推出对动态配置（dynamic configs）的支持。&lt;/li&gt;
&lt;li&gt;Oxc 在性能优化方面持续投入，实现了语义分析性能的提升。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Vite 项目更新&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;发布了多个 Vite 8 beta 版本，修复了 bug 并提升了稳定性。&lt;/li&gt;
&lt;li&gt;Vite 8 beta 提供了第二版原生插件（native plugins），改进了动态导入（dynamic import）和导入 glob（import glob）插件的对齐。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Vitest 项目更新&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;新增实验性的 OpenTelemetry 支持，用于分析测试性能瓶颈。&lt;/li&gt;
&lt;li&gt;Vitest UI、CLI 和 VS Code 扩展支持分析和分解导入，识别减慢测试速度的依赖。&lt;/li&gt;
&lt;li&gt;支持通过 &lt;code&gt;experimental.fsCache&lt;/code&gt; 缓存转换后的文件，加速后续测试运行。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Rolldown 项目更新&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;支持通过手动指定 &lt;code&gt;tsconfig&lt;/code&gt; 来使用 TypeScript 项目引用（TypeScript project references）。&lt;/li&gt;
&lt;li&gt;优化了默认的代码块（chunking）算法，减少了生成的代码块数量。&lt;/li&gt;
&lt;li&gt;提供了 &lt;code&gt;postBanner&lt;/code&gt; 和 &lt;code&gt;postFooter&lt;/code&gt; 选项，用于在打包后的代码前后追加内容。&lt;/li&gt;
&lt;li&gt;插件的 &lt;code&gt;resolveId&lt;/code&gt; 钩子支持通过 &lt;code&gt;importerId&lt;/code&gt; 进行过滤，提高性能。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;社区动态与合作&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Framer 发布了关于使用 Rolldown 改进性能的案例研究。&lt;/li&gt;
&lt;li&gt;TypeScript.fm 和 Syntax.fm 播客讨论了 ViteLand 项目的更新和发展预测。&lt;/li&gt;
&lt;li&gt;Ninja Squad 撰写了关于使用 Vitest 浏览器模式测试 Angular 的文章。&lt;/li&gt;
&lt;li&gt;社区涌现出多个基于 Oxc 和 Rolldown 的新工具和迁移案例，如：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;oxlint-plugin-complexity&lt;/code&gt;：用于校验代码复杂度的 Oxlint 插件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Roll(down)phobia&lt;/code&gt;：基于 Rolldown 的包大小分析工具。&lt;/li&gt;
&lt;li&gt;RSC Explorer：使用 Vite 8 和 Rolldown 构建的 RSC（React Server Components）工具。&lt;/li&gt;
&lt;li&gt;Facetpack：使用 Oxc 替换 Babel 的 React Native 工具。&lt;/li&gt;
&lt;li&gt;MonkeyType：迁移到 Oxlint 以获得更快的类型感知校验速度。&lt;/li&gt;
&lt;li&gt;create-better-t-stack：从 Biome 迁移到 Oxlint 和 Oxfmt。&lt;/li&gt;
&lt;li&gt;Ultracite：提供基于 Oxc 的预设配置。&lt;/li&gt;
&lt;li&gt;rollipop：完全基于 Rolldown 的 React Native 构建工具。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;文章与社区动态&lt;a href=&quot;#文章与社区动态&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://emilkowal.ski/ui/building-a-toast-component&quot;&gt;Building a toast component&lt;/a&gt;：Emil 分享了制作  &lt;a href=&quot;https://sonner.emilkowal.ski/&quot;&gt;Sonner&lt;/a&gt;  时候的设计思路与技术实现，太细致了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.developerway.com/posts/bundle-size-investigation&quot;&gt;包大小调查：一步一步缩小 JavaScript 的指南&lt;/a&gt;：相当实用的好文，教你怎么减小打包体积，分析 JavaScript 打包大小、优化导入方式、解决重复库和处理传递依赖。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://allthingssmitty.com/2025/12/01/react-has-changed-your-hooks-should-too/&quot;&gt;React 已发生变化，您的 hook 也应如此&lt;/a&gt;：当前主流项目仍以传统方式使用 React Hooks（如滥用 useEffect），忽视了 React 在并发模式与异步数据处理上的演变。作者从设计理念出发，提出现代 Hook 的核心精神是“架构化的状态与逻辑管理”，强调使用 &lt;code&gt;useSyncExternalStore&lt;/code&gt;、&lt;code&gt;useDeferredValue&lt;/code&gt;、&lt;code&gt;useEffectEvent&lt;/code&gt; 等新 API 优化性能和可测试性，鼓励开发者更多关注派生状态（derived state）而非副作用（side effects）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://acusti.ca/blog/2025/12/09/how-ai-coding-agents-hid-a-timebomb-in-our-app/&quot;&gt;AI 编码代理如何在我们的应用中隐藏定时炸弹&lt;/a&gt;: AI Agent 写代码的时候无意中在应用程序中引入了一个隐藏的、导致浏览器崩溃的无限 React 组件树，而 React 19 的 &lt;code&gt;&amp;lt;Activity&amp;gt;&lt;/code&gt; 组件则巧妙地掩盖了这个问题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://joyeecheung.github.io/blog/2025/12/30/require-esm-in-node-js-implementers-tales/&quot;&gt;require(esm) in Node.js: implementer’s tales&lt;/a&gt;：Joyee Cheung 是 Node.js 的核心贡献者之一，长期以来一直致力于 Node.js 对 &lt;code&gt;require(esm)&lt;/code&gt; 的支持（即使用 require 加载 ES 模块）。在这篇系列文章中，她分两部分详细阐述了 &lt;code&gt;require(esm)&lt;/code&gt; 实现历程及其具体细节。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://frontendmasters.com/blog/beyond-the-mouse-animating-with-mobile-accelerometers/&quot;&gt;超越鼠标：使用移动加速计制作动画&lt;/a&gt;：探讨了如何利用移动设备的加速计和运动传感器为网页动画注入动态交互，从而在移动设备上实现与桌面端鼠标交互相媲美的沉浸式用户体验。
（这个我两年前有做过类似的需求，也叫陀螺仪，iOS 必须请求陀螺仪权限才能用，除了这个缺点，整体上其实用起来挺舒服的）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://frontendmasters.com/blog/a-first-look-at-the-web-install-api/&quot;&gt;A first look at the Web Install API&lt;/a&gt;：探讨了新兴的 Web Install API，该 API 已在 Chromium 浏览器中进入 Origin Trial 阶段，并在 Microsoft Edge beta 版中展现出最完整的功能。此 API 的目标是允许网站通过简单的按钮点击直接安装 PWA (Progressive Web Apps)。相关链接: &lt;a href=&quot;https://blogs.windows.com/msedgedev/2025/11/24/the-web-install-api-is-ready-for-testing/&quot;&gt;The Web Install API is ready for testing&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://frontendmasters.com/blog/important-and-css-custom-properties/&quot;&gt;Important and CSS Custom Properties&lt;/a&gt;：澄清了 CSS 自定义属性 (&lt;code&gt;CSS Custom Properties&lt;/code&gt;) 中 &lt;code&gt;!important&lt;/code&gt; 的作用机制，指出 &lt;code&gt;!important&lt;/code&gt; 修饰的是声明本身而非其值，从而避免常见误解。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://tympanus.net/codrops/2026/01/04/efecto-building-real-time-ascii-and-dithering-effects-with-webgl-shaders/&quot;&gt;用 WebGL 着色器构建实时 ASCII 和抖动效果&lt;/a&gt;：很厉害的 ASCII 艺术生成网站 &lt;a href=&quot;https://efecto.app/&quot;&gt;https://efecto.app/&lt;/a&gt; 之前发过，这篇是作者的详细技术解析。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://tympanus.net/codrops/2026/01/07/infinite-canvas-building-a-seamless-pan-anywhere-image-space/&quot;&gt;无限画布：建立一个无缝的，平铺任何地方的图像空间&lt;/a&gt;：很详细的教程，详细介绍了如何使用 React Three Fiber 构建一个无限可平移的图像画布，通过区块 (chunk) 渲染和性能优先的技术，创建一个流畅且可无尽探索的图像空间。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/b42a0836b926029e589b98995214216f.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.logrocket.com/beyond-rxjs-guide-tanstack-pacer/&quot;&gt;超越 RxJS: TanStack Pacer 指南&lt;/a&gt;：讲了怎么使用 TanStack Pacer 来管理前端应用中的异步事件时序，以优化性能并避免 RxJS 的复杂性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.logrocket.com/native-alternatives-vscode/&quot;&gt;VSCode 的 6 种快速的（原生）替代方案&lt;/a&gt;：本文探讨了广受欢迎的 VSCode 因其基于 Electron 的架构所带来的性能局限性，并介绍了六款用 C++、Rust 等原生语言开发的快速、高效的替代编辑器。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.geedea.pro/essays/clean-home/&quot;&gt;程序员和整洁的 Home&lt;/a&gt;：如何通过遵循 XDG Base Directory Specification (XDG BDS) 来整理混乱的 Home 目录，从而更好地管理配置文件、数据和缓存文件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://howtotestfrontend.com/resources/vitest-browser-mode-guide-and-setup-info&quot;&gt;Vitest Browser Mode - The Future of Frontend Testing&lt;/a&gt;： 介绍了 Vitest Browser Mode，可以在真实浏览器环境中进行前端组件测试。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;新特性&lt;a href=&quot;#新特性&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://css-tricks.com/whats-important-2/&quot;&gt;重要信息 #2：条件视图转换、CSS/SVG 文本特效、最佳 CSS Bluesky 等&lt;/a&gt;：回顾了 2025 年末 CSS 领域的一些重要进展、新特性、社区讨论以及行业动态，并展望了 2026 年的趋势。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://piccalil.li/blog/date-is-out-and-temporal-is-in/?ref=articles-rss-feed&quot;&gt;Date is out, Temporal is in&lt;/a&gt;：一篇 Temporal 的介绍。JavaScript &lt;code&gt;Temporal&lt;/code&gt; 是 &lt;code&gt;Date&lt;/code&gt; 的完美替代方案，已进入 TC39 标准化过程的第三阶段 (Stage 3)，在 Chrome 和 Firefox 等主流浏览器的最新版本中落地实现。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nerdy.dev/4-css-features-every-front-end-developer-should-know-in-2026?utm_source=rss&quot;&gt;2026 年每个前端开发者都应该知道的 4 个 CSS 特性&lt;/a&gt;：介绍了前端开发者在 2026 年应该掌握的四个重要 CSS 新特性，包括 &lt;code&gt;sibling-index()&lt;/code&gt;、&lt;code&gt;@container scroll-state()&lt;/code&gt;、&lt;code&gt;text-box&lt;/code&gt; 和 &lt;code&gt;typed attr()&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;工具&lt;a href=&quot;#工具&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;做友链功能的时候发现的，还挺好用的：&lt;a href=&quot;https://github.com/dicebear/dicebear&quot;&gt;DiceBear&lt;/a&gt; 是一个功能强大的头像库，旨在帮助设计师和开发者快速为项目创建多样化的头像。它提供了从抽象形状到精心设计的人物角色等多种头像风格。用户不仅可以生成随机头像，还可以通过设定 seed 实现确定性头像生成，或利用丰富的定制选项创建个性化头像。DiceBear 通过 JavaScript 库、HTTPS API、命令行工具 (CLI)、Figma 插件和在线 Playground 等多种方式提供。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ditherimage.online/&quot;&gt;Dither Image Online&lt;/a&gt;：一个免费、快速、实时的在线抖动（dithered）图像生成器，或者说像素艺术风格，效果太酷了！&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/5bff5c48402d8f31baff6664b658431f.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你知道吗？可以通过在 GitHub 个人资料页面 URL 后添加 &lt;code&gt;.png&lt;/code&gt; 来获取你的 GitHub 个人资料图片，例如 &lt;code&gt;github․com/USERNAMEHERE.png&lt;/code&gt; - 还可以附加 &lt;code&gt;.keys&lt;/code&gt; 来获取公钥，附加 &lt;code&gt;.atom&lt;/code&gt; 来获取公开时间线活动的动态。&lt;a href=&quot;https://bsky.app/profile/cassidoo.co/post/3mbs2sqipuc25&quot;&gt;Refernce&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;趣站与 Codepen 精选&lt;a href=&quot;#趣站与-codepen-精选&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;GACHAGO! MarbleRace&lt;a href=&quot;#gachago-marblerace&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;非常可爱的网站！看了看首页还有很多有趣的东西&lt;/p&gt;
&lt;p&gt;是一个基于物理模拟的弹珠台式抽奖工具，灵感来源于游戏内的抽奖玩法，通过模拟物理碰撞（例如红色柱子增加弹力）来决定结果，使得抽奖过程更具趣味性和不可预测性。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/ea73017f59b01b1c427a860da51c21eb.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://www.gachago.net/MarbleRace&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://www.gachago.net/web-icon/apple-touch-icon-256x256.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;gachago.net&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;GACHAGO! - 線上抽獎、扭蛋、轉盤、骰子模擬網站&lt;/h3&gt;
        &lt;p&gt;GACHAGO!! 是一個提供各種隨機線上抽獎的小工具網站，提供扭蛋、轉盤和骰子的線上模擬器。適合用來幫助你做決定！&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://www.gachago.net/MarbleRace&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://gachago.net/web-icon/og-image.png&quot; alt=&quot;GACHAGO! - 線上抽獎、扭蛋、轉盤、骰子模擬網站&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;div&gt;&lt;/div&gt;
&lt;p&gt;作者是 &lt;a href=&quot;https://x.com/Mant0uStudio&quot;&gt;@Mant0uStudio&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Mr. Panda’s Psychologically Safe Portfolio&lt;a href=&quot;#mr-pandas-psychologically-safe-portfolio&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;很有创意的一个滚动驱动的 portfolio 页面：&lt;a href=&quot;https://www.mr-pandas-psychologically-safe-portfolio.com/&quot;&gt;Mr. Panda’s Psychologically Safe Portfolio&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开源在：&lt;a href=&quot;https://github.com/andrewwoan/mr-pandas-psychologically-safe-portfolio&quot;&gt;GitHub - andrewwoan/mr-pandas-psychologically-safe-portfolio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;技术解析文章：&lt;a href=&quot;https://tympanus.net/codrops/2025/12/30/the-increasing-importance-of-psychological-safety-and-self-awareness-for-creative-work/?_thumbnail_id=107253&quot;&gt;The Increasing Importance of Psychological Safety and Self-Awareness for Creative Work&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/2f73902c267d3f94972cbf124ed7af7b.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;像素化图片掉落特效&lt;a href=&quot;#像素化图片掉落特效&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://tympanus.net/codrops/2026/01/05/how-to-create-a-pixel-to-voxel-video-drop-effect-with-three-js-and-rapier/&quot;&gt;How to Create a Pixel-to-Voxel Video Drop Effect with Three.js and Rapier&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这个教程详细介绍了如何利用 Three.js、着色器和 Rapier 物理引擎，将 2D 视频流转化为动态的 3D 体素 (voxel) 下落效果。文章从核心概念——像素和重力——出发，逐步讲解了如何构建一个由 &lt;code&gt;InstancedMesh&lt;/code&gt; 和刚体 (rigid bodies) 组成的平面，通过着色器实现平铺像素到 3D 体素的涟漪 (ripple) 变形效果，并最终通过 JavaScript 模拟涟漪进程来激活物理引擎，使体素在逼真的物理作用下散射和恢复。作者还分享了实现过程中的技术细节、优化考量以及未来扩展的可能性。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/9945061fadee6845fa2eaf24796b5756.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;Bongo Cat Codes #2 - Jamming&lt;a href=&quot;#bongo-cat-codes-2---jamming&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;好萌，被萌到了&lt;/p&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/carolineartz/pen/qBOEzQa&quot;&gt;qBOEzQa&lt;/a&gt; by carolineartz (&lt;a href=&quot;https://codepen.io/carolineartz&quot;&gt;@carolineartz&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/e0f7bfdff20d71ab1a258f9346972134.webp&quot; alt=&quot;Bongo Cat Codes #2&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;果冻压扁按钮&lt;a href=&quot;#果冻压扁按钮&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;你需要预留一点时间来压扁这个东西。无论是垂直拖动光标，还是单击 “跟随鼠标 “复选框，都可以将这个由 Voicu Apostol 制作的内含果冻的按钮的压扁效果与鼠标移动联系起来。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/cerpow/pen/LEZYxqM&quot;&gt;LEZYxqM&lt;/a&gt; by cerpow (&lt;a href=&quot;https://codepen.io/cerpow&quot;&gt;@cerpow&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;好可爱的按钮！看了一下原理。&lt;/p&gt;
&lt;p&gt;这不是真正的 3D 果冻，而是通过快速切换预渲染的图片序列来模拟果冻挤压效果，类似电影胶片的原理。预加载了 215 张连续的果冻形变图片，这些图片记录了果冻从静止到被挤压到回弹的完整动画过程，通过切换不同帧号的图片来做的。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/2e8bfc8d0c7b929a26ba63df6ddfacec.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;文本框边框动画旋转 [CSS &amp;amp; SVG] V2&lt;a href=&quot;#文本框边框动画旋转-css--svg-v2&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;在 Fernando Cohen 制作的这个流畅的 CSS 和 SVG 动画中，文字栅栏围绕着一张照片的边缘。看看悬停照片时会发生什么，还可以看看他的 &lt;a href=&quot;https://codepen.io/designfenix/pen/gOGVXWw&quot;&gt;2022 年的早期迭代作品&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/designfenix/pen/OPyapww&quot;&gt;OPyapww&lt;/a&gt; by designfenix (&lt;a href=&quot;https://codepen.io/designfenix&quot;&gt;@designfenix&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/626dbe1ad688022823daf884f69eeb36.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;看了一下原理，这是一个使用 SVG 和 CSS 创建的&lt;strong&gt;文字沿着有机形状边框旋转动画&lt;/strong&gt;效果。&lt;/p&gt;
&lt;p&gt;首先使用 &lt;strong&gt;SVG Blob 形状生成&lt;/strong&gt;，创造有机的 blob 形状&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;path d=&quot;M43.1,-68.5C56.2,-58.6,67.5,-47.3...&quot; /&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个路径数据可能来自 &lt;a href=&quot;https://www.blobmaker.app/&quot;&gt;Blob Generator&lt;/a&gt; 等工具&lt;/p&gt;
&lt;p&gt;然后是使用 clip-path 进行&lt;strong&gt;图片裁剪&lt;/strong&gt;，将矩形图片裁剪成 blob 形状，同时通过 &lt;code&gt;preserveAspectRatio=&quot;xMidYMid slice&quot;&lt;/code&gt; 确保图片填充满整个区域。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;clipPath id=&quot;blobClip&quot;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;path d=&quot;...&quot; transform=&quot;translate(100 100)&quot;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/clipPath&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;image clip-path=&quot;url(#blobClip)&quot; /&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后使用 &lt;code&gt;pathLength&lt;/code&gt; 进行&lt;strong&gt;文字路径动画&lt;/strong&gt;，定义文字路径&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;path id=&quot;text&quot; d=&quot;...&quot; fill=&quot;none&quot; stroke=&quot;none&quot; pathLength=&quot;100&quot; /&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pathLength=&quot;100&quot;&lt;/code&gt; 标准化路径长度，便于动画计算&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fill=&quot;none&quot; stroke=&quot;none&quot;&lt;/code&gt; 路径本身不可见，仅作为文字轨迹&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;将文字沿路径排列：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;text class=&quot;text-content&quot;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;textPath href=&quot;#text&quot; startOffset=&quot;0%&quot;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ❤ MADE WITH LOVE ❤ MADE WITH LOVE...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;animate attributeName=&quot;startOffset&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;             from=&quot;0%&quot; to=&quot;100%&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;             dur=&quot;15s&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;             repeatCount=&quot;indefinite&quot; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/textPath&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/text&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;textPath&amp;gt;&lt;/code&gt; 让文字沿着指定路径排列&lt;/li&gt;
&lt;li&gt;&lt;code&gt;startOffset&lt;/code&gt; 控制文字在路径上的起始位置&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;animate&amp;gt;&lt;/code&gt; 元素实现 SMIL 动画（SVG 原生动画）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那无缝循环是怎么做的呢？他用了两段文字，当第一段文字从 0% 移动到 100% 时，尾部会消失，第二段文字从 -100% 移动到 0%，正好填补空白。两段文字首尾相连，形成无缝循环。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;!-- 第一段文字：0% → 100% --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;textPath href=&quot;#text&quot; startOffset=&quot;0%&quot;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;animate from=&quot;0%&quot; to=&quot;100%&quot; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/textPath&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;!-- 第二段文字：-100% → 0% --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;textPath href=&quot;#text&quot; startOffset=&quot;100%&quot;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;animate from=&quot;-100%&quot; to=&quot;0%&quot; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/textPath&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后是这个 &lt;strong&gt;交互式缩放效果&lt;/strong&gt;，是通过悬停时放大裁剪区域（而非整个 SVG）来做的。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/605f335c304c6e98761fc87d3199ef86.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;文摘&lt;a href=&quot;#文摘&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;在谷歌工作 14 年的 21 条经验&lt;a href=&quot;#在谷歌工作-14-年的-21-条经验&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;最近读的有一篇文章感觉很棒，所以单独列出来。&lt;/p&gt;
&lt;p&gt;每一条都是我赞同的！写得真好啊。&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://addyosmani.com/blog/21-lessons/&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://addyosmani.com/assets/images/favicons/android-chrome-192x192.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;addyosmani.com&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;21 Lessons From 14 Years at Google&lt;/h3&gt;
        &lt;p&gt;Lessons learned from 14 years of engineering at Google, focusing on what truly matters beyond just writing great code.&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://addyosmani.com/blog/21-lessons/&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://addyosmani.com/assets/images/21-lessons.jpg&quot; alt=&quot;21 Lessons From 14 Years at Google&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;Addy Osmani 在 Google 14 年的职业生涯中，总结出 21 条经验教训。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;以用户为中心&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;真正的价值来自于深入理解并解决用户问题，而非仅仅热衷于技术本身。&lt;/li&gt;
&lt;li&gt;通过参与客服、与用户交谈、观察用户痛点来深入理解问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;协作与对齐&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“对”固然重要，但“一起达成对”才是真正的挑战和价值所在。&lt;/li&gt;
&lt;li&gt;避免成为“房间里最聪明的人”，应创造空间让他人参与，并对自己的观点保持适度怀疑。&lt;/li&gt;
&lt;li&gt;绝大多数“慢”的团队实际上是“错位”的团队，即方向、接口或优先级不明确。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;行动与交付&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;完美的追求会阻碍进步，先行动、再完善、再优化。&lt;/li&gt;
&lt;li&gt;“行动导向”比“分析瘫痪”更能带来清晰度，可编辑错误页面，但无法编辑空白页面。&lt;/li&gt;
&lt;li&gt;AI 可以辅助快速迭代和反馈。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;清晰度而非技巧性&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;代码需要清晰易懂，以便他人（尤其是在紧急情况下）能够理解和维护。&lt;/li&gt;
&lt;li&gt;为维护者（包括 2 AM 时的你）而非自己写代码。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;谨慎创新&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;创新是有成本的，应谨慎使用“创新代币”。&lt;/li&gt;
&lt;li&gt;只在有独特价值的地方创新，其他地方应倾向于使用成熟、可靠的技术。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;价值的可视化与倡导&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优秀的成果需要被看见，代码不会替你说话，人会。&lt;/li&gt;
&lt;li&gt;要让你的影响对他人可见，尤其是在你不在场时。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;代码的最小化&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最“好”的代码是你根本不需要写的代码。&lt;/li&gt;
&lt;li&gt;在构建之前，思考“如果我们不这样做，会发生什么？”。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;兼容性即产品&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在大规模系统下，即使是 bug 也会成为用户的依赖。&lt;/li&gt;
&lt;li&gt;API 设计实际上是 API 退休设计，需要考虑兼容性、时间和用户体验。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;聚焦可控因素&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在大型组织中，很多变量不可控，应专注于自己的影响范围。&lt;/li&gt;
&lt;li&gt;保持理智和高效的关键在于聚焦可控之事。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;抽象的代价&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;抽象会转移复杂性，而非消除它，当问题发生时，你仍需要理解底层。&lt;/li&gt;
&lt;li&gt;持续学习“更低级”的知识，以应对抽象失效的情况。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;教学即学习&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;写作和教学是促进理解的有效方式，能暴露自己知识的不足。&lt;/li&gt;
&lt;li&gt;尝试用简单的方式解释事物，是检验和深化理解的捷径。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;“粘合剂”工作的价值与可见性&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文档、入职、跨团队协调等“粘合剂”工作至关重要，但容易被忽视。&lt;/li&gt;
&lt;li&gt;要将这些工作视为可见的影响，而非仅仅是“乐于助人”。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;避免轻易获胜&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果总是轻易获胜，可能意味着积累了“沉默的阻力”。&lt;/li&gt;
&lt;li&gt;真正的对齐需要时间、理解和妥协，而非赢得争论。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;度量与目标&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;任何被量化的指标都会被“博弈”。&lt;/li&gt;
&lt;li&gt;在衡量时，应同时关注速度和质量/风险，并注重趋势分析。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;承认未知&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;领导者承认“我不知道”能创造一个更安全的环境，鼓励团队成员也敢于提问。&lt;/li&gt;
&lt;li&gt;好奇心和承认未知才能促进团队学习。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;人脉网络&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;人脉是你最重要的长期资产，远比任何一份工作都持久。&lt;/li&gt;
&lt;li&gt;以好奇和慷慨的态度建立和维护人脉。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;性能提升源于移除&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;许多性能提升来自于移除不必要的工作，而非增加技术复杂度。&lt;/li&gt;
&lt;li&gt;最快的代码是那些从不运行的代码。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;流程的本质&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;良好的流程应简化协调、降低失败成本，而非制造官僚主义。&lt;/li&gt;
&lt;li&gt;如果流程的价值无法解释，它可能只是负担。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;时间价值&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;职业生涯后期，时间成为比金钱更宝贵的资源。&lt;/li&gt;
&lt;li&gt;有意识地权衡时间和回报，避免过度追求金钱而牺牲宝贵的时间。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;复利效应&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;专业技能的提升需要刻意练习和时间积累，没有捷径。&lt;/li&gt;
&lt;li&gt;将职业生涯视为复利增长，而非购买彩票，持之以恒会带来长远回报。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;核心思想&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;保持好奇心、谦逊，并始终记住工作关乎人（用户和团队成员）。&lt;/li&gt;
&lt;li&gt;工程师的职业生涯足够长，可以从错误中学习，并分享经验。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Refs&lt;a href=&quot;#refs&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://codepen.io/spark/490&quot;&gt;Codepen Spark #490&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ruanyifeng.com/blog/2026/01/weekly-issue-380.html&quot;&gt;科技爱好者周刊（第 380 期）：为什么人们拥抱”不对称收益” - 阮一峰的网络日志&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webweekly.email/archive/web-weekly-179&quot;&gt;Web Weekly #179&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nodeweekly.com/issues/606&quot;&gt;Node Weekly #606&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>category:周刊</category></item><item><title>使用 ImageMagick 和 libvips 进行服务器端批量图片压缩</title><link>https://blog.cosine.ren/post/server-batch-image-compression</link><guid isPermaLink="false">server-batch-image-compression</guid><description>前言
最近需要处理别人的服务器上大量历史图片的压缩问题。因为历史遗留原因，public/uploads 下累积了不少高分辨率的全景图和常规照片，占用存储空间越来越大。虽然之前在前端生产环境中一直使用 sharp-cli、@napi-rs/image</description><pubDate>Tue, 06 Jan 2026 17:38:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;a href=&quot;#前言&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;最近需要处理别人的服务器上大量历史图片的压缩问题。因为历史遗留原因，&lt;code&gt;public/uploads&lt;/code&gt; 下累积了不少高分辨率的全景图和常规照片，占用存储空间越来越大。虽然之前在前端生产环境中一直使用 &lt;a href=&quot;https://github.com/vseventer/sharp-cli&quot;&gt;sharp-cli&lt;/a&gt;、&lt;a href=&quot;https://image.napi.rs/&quot;&gt;@napi-rs/image&lt;/a&gt; 等进行图片处理，但这次面对的是&lt;strong&gt;已存储的历史图片批量压缩&lt;/strong&gt;场景，而且这次提供的环境是堡垒机，只能通过网页终端进行操作，没有 GUI，需要更灵活的命令行工具。&lt;/p&gt;
&lt;p&gt;这篇文章记录了完整过程，希望对有类似需求的朋友有所帮助。&lt;/p&gt;
&lt;h3&gt;为什么不用 sharp-cli?&lt;a href=&quot;#为什么不用-sharp-cli&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;sharp-cli&lt;/code&gt; 和 &lt;code&gt;@napi-rs/image&lt;/code&gt; 一般是我在生产环境中的首选,性能优异且 API 友好。但这次需求是&lt;strong&gt;批量处理已存储的大量图片&lt;/strong&gt;，而 sharp-cli 更适合集成到构建流程或 Node.js 脚本中。对于这种一次性批量操作，传统的 ImageMagick/libvips 命令行工具更加直接。&lt;/p&gt;
&lt;h3&gt;工具对比&lt;a href=&quot;#工具对比&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在压缩之前我看过了这些工具：&lt;/p&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;工具&lt;/th&gt;&lt;th&gt;优势&lt;/th&gt;&lt;th&gt;劣势&lt;/th&gt;&lt;th&gt;适用场景&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;ImageMagick&lt;/td&gt;&lt;td&gt;功能全面,文档丰富&lt;/td&gt;&lt;td&gt;大图处理内存占用高&lt;/td&gt;&lt;td&gt;通用图片处理&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;libvips&lt;/td&gt;&lt;td&gt;流式处理,内存效率极高&lt;/td&gt;&lt;td&gt;功能相对聚焦&lt;/td&gt;&lt;td&gt;超大图片/批量处理&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;sharp-cli&lt;/td&gt;&lt;td&gt;性能优异,API 友好&lt;/td&gt;&lt;td&gt;需要 Node.js 环境&lt;/td&gt;&lt;td&gt;构建流程集成&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Squoosh CLI&lt;/td&gt;&lt;td&gt;支持现代格式(WebP/AVIF)&lt;/td&gt;&lt;td&gt;已停止维护&lt;/td&gt;&lt;td&gt;❌ 不推荐生产使用&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;guetzli/mozjp&lt;/td&gt;&lt;td&gt;极致压缩(JPEG)&lt;/td&gt;&lt;td&gt;处理速度极慢&lt;/td&gt;&lt;td&gt;追求极限压缩率场景&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;最终选择：&lt;strong&gt;ImageMagick + libvips 组合方案&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一开始使用 ImageMagick 处理 10MB 以下的常规图片，很快速很方便，但发现超大图片（100MB+）会爆内存。&lt;/li&gt;
&lt;li&gt;libvips 是 sharp 底层使用的库，处理超大全景图（100MB+）绰绰有余。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic compare-hierarchy-left-right-circle-node-pill-badge&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 工具选择策略&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label ImageMagick&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 常规图片&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc &amp;lt;10MB 的图片&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 功能全面&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 支持各种格式转换&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 语法简单&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc mogrify 一行命令&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label libvips&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 超大图片&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 100MB+ 全景图&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 内存效率&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 流式处理不爆内存&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label sharp底层&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 生产级性能保证&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;开始前准备&lt;a href=&quot;#开始前准备&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic list-row-simple-horizontal-arrow&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 操作流程&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 检查容量&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 查看磁盘剩余空间&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 统计大小&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 评估压缩前文件大小&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 备份数据&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 防止误操作&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 执行压缩&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 使用合适工具&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最重要的两点当然是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;查看原来的所有图片大小&lt;/li&gt;
&lt;li&gt;备份你要压缩的图片（因为 mogrify 会在原图的基础上直接进行压缩，所以备份很重要）&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# du（disk usage)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# -sh: 只显示每个参数的总大小，以人类可读的格式显示(K, M, G)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# sort -hr: 按大小倒序排列&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;du&lt;/span&gt;&lt;span&gt; -sh&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; sort&lt;/span&gt;&lt;span&gt; -hr&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 只列出 17 开头的&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;du&lt;/span&gt;&lt;span&gt; -sh&lt;/span&gt;&lt;span&gt; 17&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; sort&lt;/span&gt;&lt;span&gt; -hr&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;root@3d:/myapp/storage/uploads/panorama/backup#&lt;/span&gt;&lt;span&gt; du&lt;/span&gt;&lt;span&gt; -sh&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; sort&lt;/span&gt;&lt;span&gt; -hr&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;147M&lt;/span&gt;&lt;span&gt;    1766019200667-1c2d8x.jpg&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;130M&lt;/span&gt;&lt;span&gt;    1766019053272-aylt0b.jpg&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;120M&lt;/span&gt;&lt;span&gt;    1766019107903-jl2i2s.jpg&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;63M&lt;/span&gt;&lt;span&gt;     1765977415640-hu7yap.jpg&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;……&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后备份，备份前记得 &lt;code&gt;df -h&lt;/code&gt; 查看磁盘剩余可用空间。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;df&lt;/span&gt;&lt;span&gt; -h&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 可用空间足够，则进行备份&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;tar&lt;/span&gt;&lt;span&gt; -cvf&lt;/span&gt;&lt;span&gt; uploads_backup_&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt; +%Y%m%d&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.tar&lt;/span&gt;&lt;span&gt; uploads/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 或者比如说我要备份 17 开头的&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;tar&lt;/span&gt;&lt;span&gt; -czvf&lt;/span&gt;&lt;span&gt; backup_17_&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt; +%Y%m%d&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.tar.gz&lt;/span&gt;&lt;span&gt; 17&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 备份所有 17 开头且大于 10M 的文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;tar&lt;/span&gt;&lt;span&gt; -czvf&lt;/span&gt;&lt;span&gt; backup_17_large_&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt; +%Y%m%d&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.tar.gz&lt;/span&gt;&lt;span&gt; $(&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt; .&lt;/span&gt;&lt;span&gt; -name&lt;/span&gt;&lt;span&gt; &quot;17*&quot;&lt;/span&gt;&lt;span&gt; -size&lt;/span&gt;&lt;span&gt; +10M&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;会把 upload 目录备份为 uploads_backup_20251223.tar 文件&lt;/p&gt;
&lt;h2&gt;工具安装&lt;a href=&quot;#工具安装&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;然后就到了开始实行压缩的环节&lt;/p&gt;
&lt;h3&gt;ImageMagick&lt;a href=&quot;#imagemagick&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt;
  &lt;a href=&quot;https://imagemagick.org/script/command-line-processing.php&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://imagemagick.org/image/logo.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;imagemagick.org&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;ImageMagick | Command-line Processing&lt;/h3&gt;
        &lt;p&gt;ImageMagick is a powerful open-source software suite for creating, editing, converting, and manipulating images in over 200 formats. Ideal for developers, designers, and researchers.&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://imagemagick.org/script/command-line-processing.php&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://imagemagick.org/image/logo.png&quot; alt=&quot;ImageMagick | Command-line Processing&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Debian/Ubuntu&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;sudo&lt;/span&gt;&lt;span&gt; apt&lt;/span&gt;&lt;span&gt; update&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span&gt;sudo&lt;/span&gt;&lt;span&gt; apt&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; imagemagick&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# macOS&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; imagemagick&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 验证&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;magick&lt;/span&gt;&lt;span&gt; -version&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要注意的是新版 ImageMagick 7+ 推荐使用 magick 命令，但是服务器上比较老所以我用的旧语法。&lt;/p&gt;
&lt;h3&gt;libvips&lt;a href=&quot;#libvips&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt;
  &lt;a href=&quot;https://www.libvips.org/API/8.17/using-vipsthumbnail.html&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://www.libvips.org/favicon.ico&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;libvips.org&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;Vips: Using &amp;gt; vipsthumbnail&lt;/h3&gt;
        &lt;p&gt;Reference for Vips-8.0: Using &amp;gt; vipsthumbnail&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://www.libvips.org/API/8.17/using-vipsthumbnail.html&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://www.libvips.org/API/8.17/owl.jpg&quot; alt=&quot;Vips: Using &amp;gt; vipsthumbnail&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Debian/Ubuntu&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;sudo&lt;/span&gt;&lt;span&gt; apt&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; libvips-tools&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# macOS&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; vips&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 验证&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;vipsthumbnail&lt;/span&gt;&lt;span&gt; --version&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;踩坑记录&lt;a href=&quot;#踩坑记录&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;超大图片内存溢出&lt;a href=&quot;#超大图片内存溢出&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;处理 150MB 的全景图时遭遇 &lt;code&gt;cache resources exhausted&lt;/code&gt; 错误:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 假设以 change 开头的是我们要批量压缩的图片&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 原图可能有 20000x 以上，可以 resize 到 9000x 以下&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;mogrify&lt;/span&gt;&lt;span&gt; -quality&lt;/span&gt;&lt;span&gt; 75&lt;/span&gt;&lt;span&gt; -resize&lt;/span&gt;&lt;span&gt; &quot;9000x&amp;gt;&quot;&lt;/span&gt;&lt;span&gt; ./change&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;.jpg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;根本原因:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ImageMagick 采用&lt;strong&gt;全图加载到内存&lt;/strong&gt;的处理方式:&lt;/p&gt;
&lt;p&gt;对于 150MB 的航拍全景图 JPEG 文件，其实际分辨率可达 23000×11000 左右。解压后的像素数据大小为：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;20000 × 10000 × 3 字节 = 600MB&lt;/strong&gt;（RGB 格式）&lt;/p&gt;
&lt;p&gt;ImageMagick 在处理过程中还需要：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;创建多个中间缓冲区&lt;/li&gt;
&lt;li&gt;存储变换矩阵&lt;/li&gt;
&lt;li&gt;保存临时结果&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此&lt;strong&gt;实际内存占用可达 900MB-2GB&lt;/strong&gt;，超出了默认资源限制。（我不确定这里的描述是否准确，但反正那个小水管服务器上是跑不了的）&lt;/p&gt;
&lt;p&gt;ImageMagick 处理超大图很吃力，所以我请出了 &lt;strong&gt;libvips&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 1. 先看看有哪些文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt; .&lt;/span&gt;&lt;span&gt; -maxdepth&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt; -name&lt;/span&gt;&lt;span&gt; &quot;17*.jpg&quot;&lt;/span&gt;&lt;span&gt; -size&lt;/span&gt;&lt;span&gt; +10M&lt;/span&gt;&lt;span&gt; -exec&lt;/span&gt;&lt;span&gt; ls&lt;/span&gt;&lt;span&gt; -lh&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;span&gt; \;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 2. 备份(安全起见)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;tar&lt;/span&gt;&lt;span&gt; -czvf&lt;/span&gt;&lt;span&gt; backup_17_10M_&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt; +%Y%m%d&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.tar.gz&lt;/span&gt;&lt;span&gt; 17&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;.jpg&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 3. 执行压缩&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; f &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; 17*.jpg&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  size&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;stat&lt;/span&gt;&lt;span&gt; -c%s&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; 2&amp;gt;&lt;/span&gt;&lt;span&gt;/dev/null&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; [ $size &lt;/span&gt;&lt;span&gt;-gt&lt;/span&gt;&lt;span&gt; 10485760&lt;/span&gt;&lt;span&gt; ]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    echo&lt;/span&gt;&lt;span&gt; &quot;Compressing: &lt;/span&gt;&lt;span&gt;$f&lt;/span&gt;&lt;span&gt; ($(&lt;/span&gt;&lt;span&gt;du&lt;/span&gt;&lt;span&gt; -h&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$f&lt;/span&gt;&lt;span&gt;&quot; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; cut&lt;/span&gt;&lt;span&gt; -f1&lt;/span&gt;&lt;span&gt;))&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    vipsthumbnail&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; -s&lt;/span&gt;&lt;span&gt; 9000&lt;/span&gt;&lt;span&gt; -o&lt;/span&gt;&lt;span&gt; &quot;temp_[Q=75].jpg&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; [ &lt;/span&gt;&lt;span&gt;-f&lt;/span&gt;&lt;span&gt; &quot;temp_.jpg&quot;&lt;/span&gt;&lt;span&gt; ]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      mv&lt;/span&gt;&lt;span&gt; &quot;temp_.jpg&quot;&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      echo&lt;/span&gt;&lt;span&gt; &quot;Done: &lt;/span&gt;&lt;span&gt;$f&lt;/span&gt;&lt;span&gt; → $(&lt;/span&gt;&lt;span&gt;du&lt;/span&gt;&lt;span&gt; -h&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$f&lt;/span&gt;&lt;span&gt;&quot; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; cut&lt;/span&gt;&lt;span&gt; -f1&lt;/span&gt;&lt;span&gt;)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;done&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 4. 检查结果&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;du&lt;/span&gt;&lt;span&gt; -sh&lt;/span&gt;&lt;span&gt; 17&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;.jpg&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; sort&lt;/span&gt;&lt;span&gt; -hr&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意这里 &lt;code&gt;temp_[Q=75].jpg&lt;/code&gt; 中的 &lt;code&gt;[Q=75]&lt;/code&gt; 会被替换为空，这是 &lt;a href=&quot;https://www.libvips.org/API/8.17/using-vipsthumbnail.html&quot;&gt;vipsthumbnail&lt;/a&gt; 的文件名替换规则。&lt;/p&gt;
&lt;p&gt;在 vipsthumbnail 命令中，-o 参数支持特殊的占位符替换。而 &lt;code&gt;[Q=75]&lt;/code&gt; 是一个保存选项占位符，用于指定 JPEG 质量&lt;/p&gt;
&lt;p&gt;在最终生成的文件名中，这部分会被移除（替换为空）&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/3448a8e1a926ac6ddfb846640d601601.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;大图片压缩圆满完成。&lt;/p&gt;
&lt;h3&gt;分目录 mogrify 批量压缩&lt;a href=&quot;#分目录-mogrify-批量压缩&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;注意 mogrify 会在原图的基础上直接进行压缩，所以备份很重要&lt;/p&gt;
&lt;p&gt;较小的图片使用 ImageMagick 的 &lt;code&gt;mogrify&lt;/code&gt; 进行&lt;strong&gt;就地批量修改&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 先备份&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cp&lt;/span&gt;&lt;span&gt; -r&lt;/span&gt;&lt;span&gt; .&lt;/span&gt;&lt;span&gt; ../backup_before_mogrify/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 就地修改（mogrify）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;mogrify&lt;/span&gt;&lt;span&gt; -quality&lt;/span&gt;&lt;span&gt; 75&lt;/span&gt;&lt;span&gt; -resize&lt;/span&gt;&lt;span&gt; &quot;4000x&amp;gt;&quot;&lt;/span&gt;&lt;span&gt; input.jpg&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 或输出到新文件（convert）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;convert&lt;/span&gt;&lt;span&gt; input.jpg&lt;/span&gt;&lt;span&gt; -quality&lt;/span&gt;&lt;span&gt; 75&lt;/span&gt;&lt;span&gt; -resize&lt;/span&gt;&lt;span&gt; &quot;4000x&amp;gt;&quot;&lt;/span&gt;&lt;span&gt; output.jpg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;其他常用的 resize 选项:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&quot;4000x&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;     # 只缩小,宽度限制4000px&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&quot;4000x4000&amp;gt;&quot;&lt;/span&gt;&lt;span&gt; # 只缩小,宽高都不超过4000px&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&quot;50%&quot;&lt;/span&gt;&lt;span&gt;        # 缩小到50%&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&quot;4000x3000!&quot;&lt;/span&gt;&lt;span&gt; # 强制缩放到指定尺寸(可能变形)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有很多的子目录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;root@3d:/myapp/storage/uploads/panorama#&lt;/span&gt;&lt;span&gt; du&lt;/span&gt;&lt;span&gt; -sh&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; sort&lt;/span&gt;&lt;span&gt; -hr&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;131M&lt;/span&gt;&lt;span&gt;    cmjpqjihk002mlm01qyyk3vqc&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;131M&lt;/span&gt;&lt;span&gt;    cmjpp9pfk001ulm01dr2aina9&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;129M&lt;/span&gt;&lt;span&gt;    cmjxtmsfs00eilm01iso8kaq2&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;128M&lt;/span&gt;&lt;span&gt;    cmja74wwh00fcl701ie777fnj&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;127M&lt;/span&gt;&lt;span&gt;    cmja751nb00fel701u4oqmgfk&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;126M&lt;/span&gt;&lt;span&gt;    cmk0yw2zx004nlp01i7r22uoe&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;里面是这种格式的相对小的图片。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;root@3d:/myapp/storage/uploads/panorama/cmjpqjihk002mlm01qyyk3vqc#&lt;/span&gt;&lt;span&gt; du&lt;/span&gt;&lt;span&gt; -sh&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; sort&lt;/span&gt;&lt;span&gt; -hr&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;26M&lt;/span&gt;&lt;span&gt;     cmjpqjihk002mlm01qyyk3vqc.right.jpg&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;26M&lt;/span&gt;&lt;span&gt;     cmjpqjihk002mlm01qyyk3vqc.left.jpg&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;25M&lt;/span&gt;&lt;span&gt;     cmjpqjihk002mlm01qyyk3vqc.back.jpg&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;24M&lt;/span&gt;&lt;span&gt;     cmjpqjihk002mlm01qyyk3vqc.front.jpg&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;18M&lt;/span&gt;&lt;span&gt;     cmjpqjihk002mlm01qyyk3vqc.bottom.jpg&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;13M&lt;/span&gt;&lt;span&gt;     cmjpqjihk002mlm01qyyk3vqc.top.jpg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那我就直接用 &lt;code&gt;mogrify -quality 75 -resize &quot;4000x&amp;gt;&quot; ./public/uploads/**/*.jpg&lt;/code&gt; 了，快一些。&lt;/p&gt;
&lt;p&gt;我需要压缩大于 20M 的目录，并且批量先 &lt;code&gt;cd&lt;/code&gt; 进目录，处理完再 &lt;code&gt;cd ..&lt;/code&gt;，过程中我还想看进度:&lt;/p&gt;
&lt;p&gt;先备份大于 20M 的目录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;backup_file&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;../panorama_backup_$(&lt;/span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt; +%Y%m%d_%H%M%S).tar.gz&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;dirs_to_backup&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; dir &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; */&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  size&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;du&lt;/span&gt;&lt;span&gt; -sm&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$dir&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; cut&lt;/span&gt;&lt;span&gt; -f1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; [ $size &lt;/span&gt;&lt;span&gt;-gt&lt;/span&gt;&lt;span&gt; 20&lt;/span&gt;&lt;span&gt; ]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    echo&lt;/span&gt;&lt;span&gt; &quot;加入备份: &lt;/span&gt;&lt;span&gt;$dir&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;$size&lt;/span&gt;&lt;span&gt; MB)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    dirs_to_backup&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$dirs_to_backup&lt;/span&gt;&lt;span&gt; $dir&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;done&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;tar&lt;/span&gt;&lt;span&gt; -czf&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$backup_file&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; $dirs_to_backup&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &quot;备份完成: &lt;/span&gt;&lt;span&gt;$backup_file&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;备忘录&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;du -sm&lt;/code&gt; 中的 -m 参数指定了单位是 MB（megabytes）&lt;/li&gt;
&lt;li&gt;-s 是 summarize（汇总）&lt;/li&gt;
&lt;li&gt;-m 是 megabytes（兆字节）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cut -f1&lt;/code&gt; 提取第一列（数字部分）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tar -czf&lt;/code&gt; 创建一个用 gzip 压缩的 tar 归档文件，名字叫 backup.tar.gz&lt;/li&gt;
&lt;li&gt;-c = create 创建新的归档文件&lt;/li&gt;
&lt;li&gt;-z = gzip 使用 gzip 压缩&lt;/li&gt;
&lt;li&gt;-f = file 指定文件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;开始压缩，使用 &lt;code&gt;mogrify -quality 75 -resize &quot;4000x&amp;gt;&quot; &quot;$img&quot;&lt;/code&gt;（ImageMagick 7+ 推荐使用 &lt;code&gt;magick mogrify&lt;/code&gt; 代替旧版的 &lt;code&gt;mogrify&lt;/code&gt;）&lt;/p&gt;
&lt;p&gt;使用 ImageMagick 压缩和调整图片&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mogrify&lt;/code&gt; 直接修改原图片（不创建副本）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-quality 75&lt;/code&gt; 设置 JPEG 质量为 75%&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-resize &quot;4000x&amp;gt;&quot;&lt;/code&gt; 如果宽度超过 4000 像素就缩小，保持宽高比，&lt;code&gt;&amp;gt;&lt;/code&gt; 表示只缩小不放大&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以下命令的大意是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;遍历所有以 cmj 开头的子目录，检查每个目录的大小&lt;/li&gt;
&lt;li&gt;筛选大于 20 MB 的目录进行处理（跳过已经压缩过的小目录）&lt;/li&gt;
&lt;li&gt;对符合条件的目录：进入目录 -&amp;gt; 统计其中的 JPG 文件数量 -&amp;gt; 逐个处理每张 JPG 图片 -&amp;gt; 降低质量到 75%，如果宽度超过 4000 像素则缩小到 4000 像素（保持宽高比）&lt;/li&gt;
&lt;li&gt;显示实时进度（如 [3/10] photo.jpg）&lt;/li&gt;
&lt;li&gt;处理完成后返回上级目录，继续处理下一个目录&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; dir &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; cmj*/&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  size&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;du&lt;/span&gt;&lt;span&gt; -sm&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$dir&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; cut&lt;/span&gt;&lt;span&gt; -f1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; [ $size &lt;/span&gt;&lt;span&gt;-gt&lt;/span&gt;&lt;span&gt; 20&lt;/span&gt;&lt;span&gt; ]; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    echo&lt;/span&gt;&lt;span&gt; &quot;处理: &lt;/span&gt;&lt;span&gt;$dir&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;$size&lt;/span&gt;&lt;span&gt; MB)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    cd&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$dir&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    total&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;$(&lt;/span&gt;&lt;span&gt;ls&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt;.jpg&lt;/span&gt;&lt;span&gt; 2&amp;gt;&lt;/span&gt;&lt;span&gt;/dev/null&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; wc&lt;/span&gt;&lt;span&gt; -l&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    count&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    for&lt;/span&gt;&lt;span&gt; img &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; *.jpg&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      ((count&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      echo&lt;/span&gt;&lt;span&gt; &quot;  [&lt;/span&gt;&lt;span&gt;$count&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;$total&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;$img&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      mogrify&lt;/span&gt;&lt;span&gt; -quality&lt;/span&gt;&lt;span&gt; 75&lt;/span&gt;&lt;span&gt; -resize&lt;/span&gt;&lt;span&gt; &quot;4000x&amp;gt;&quot;&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;$img&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    done&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    cd&lt;/span&gt;&lt;span&gt; ..&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    echo&lt;/span&gt;&lt;span&gt; &quot;完成: &lt;/span&gt;&lt;span&gt;$dir&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  fi&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;备忘录&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;wc -l&lt;/code&gt; 中的 &lt;code&gt;-l&lt;/code&gt; 是 lines（行数），统计行数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;((count++))&lt;/code&gt; 是 bash 的算术运算语法，让变量自增 1&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mogrify&lt;/code&gt; 是 ImageMagick 工具，直接修改原文件而不创建副本&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-quality 75&lt;/code&gt; 设置 JPEG 压缩质量为 75%（0-100，数值越高质量越好文件越大）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-resize &quot;4000x&amp;gt;&quot;&lt;/code&gt; 中的 &lt;code&gt;&amp;gt;&lt;/code&gt; 表示”只缩小不放大”，如果图片宽度 ≤4000 则不处理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/68449cba4e1a5c76e26a2d27ce22de76.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;效果拔群。&lt;/p&gt;
&lt;h2&gt;写在最后&lt;a href=&quot;#写在最后&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;为什么不直接用 sharp-cli?&lt;a href=&quot;#为什么不直接用-sharp-cli&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;sharp 非常优秀,我在生产环境的图片上传流程中一直使用。但对于&lt;strong&gt;已存储的历史图片批量压缩&lt;/strong&gt;,传统命令行工具更灵活:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不需要写 Node.js 脚本&lt;/li&gt;
&lt;li&gt;直接用 shell 管道/find 组合&lt;/li&gt;
&lt;li&gt;libvips 本身就是 sharp 的底层引擎&lt;/li&gt;
&lt;li&gt;下次再用直接改改就可以&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;如何达到最优的压缩效果？&lt;a href=&quot;#如何达到最优的压缩效果&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;更细致的压缩质量的话，可以看这篇 Stack Overflow 帖子，很具体地讨论了使用 ImageMagick 压缩 JPEG 图片文件以实现最小文件大小但又不损失过多质量的问题。&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://stackoverflow.com/questions/7261855/best-options-parameters-for-imagemagick-to-compress-jpegs-files-to-the-minimal-s&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;https://stackoverflow.com/questions/7261855/best-options-parameters-for-imagemagick-to-compress-jpegs-files-to-the-minimal-s&lt;/div&gt;
          &lt;div&gt;stackoverflow.com&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      
        
      
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;以下是 AI 辅助总结的该帖子的要点总结&lt;/p&gt;
&lt;h4&gt;问题&lt;a href=&quot;#问题&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;原始提问者（Javis Perez）遇到的问题是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;压缩效果不佳：&lt;/strong&gt; 使用 ImageMagick 压缩 JPEG 文件时，无法获得显著的文件大小差异。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输出文件反而更大：&lt;/strong&gt; 在默认情况下，输出的图片文件大小甚至比输入文件更大。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;尝试后仍不理想：&lt;/strong&gt; 即使使用了 &lt;code&gt;+profile &quot;*&quot;&lt;/code&gt; 和 &lt;code&gt;-quality 70&lt;/code&gt; 选项，输出文件（264KB）仍然接近或略大于输入文件（255KB）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;明确的目标：&lt;/strong&gt; 希望能将图片压缩到至少 150KB，并寻求实现这一目标的 ImageMagick 选项。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;解决方式&lt;a href=&quot;#解决方式&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;帖子中提供了多种解决方案和参数组合，可以归纳为以下几点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;核心参数组合 (最受推荐和高赞的方案)：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Command:&lt;/strong&gt; &lt;code&gt;convert -strip -interlace Plane -gaussian-blur 0.05 -quality 85% source.jpg result.jpg&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;参数解释:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-quality 85%&lt;/code&gt;: 设置 JPEG 压缩质量为 85%（可根据需求调整，通常在 60-85 之间，数字越低文件越小，但质量损失越大）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-strip&lt;/code&gt;: 移除图片中的所有元数据（如 EXIF 信息、评论等），这是无损缩小文件大小的有效方法。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-interlace Plane&lt;/code&gt; (或 &lt;code&gt;-interlace JPEG&lt;/code&gt;): 创建渐进式 JPEG，虽然不直接减小文件大小，但能改善图片在网络上的加载体验。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-gaussian-blur 0.05&lt;/code&gt;: 应用一个非常小的（如 0.05）高斯模糊。这个参数有争议，一些用户认为它能平滑图像中的噪点，从而提高压缩率，显著减小文件大小；另一些用户则认为它会造成图像模糊，不如直接降低&lt;code&gt;-quality&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Google PageSpeed Insights 推荐的优化方案：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Command:&lt;/strong&gt; &lt;code&gt;convert image.jpg -sampling-factor 4:2:0 -strip -quality 85 -interlace JPEG -colorspace RGB image_converted.jpg&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;参数解释:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-sampling-factor 4:2:0&lt;/code&gt;: 这是色彩子采样，将色度通道的采样率减半，同时不影响亮度通道。人眼对亮度变化比色度变化更敏感，因此这种方法能在不明显影响视觉质量的情况下大幅度减小文件大小。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-strip&lt;/code&gt; 和 &lt;code&gt;-quality 85&lt;/code&gt; 及 &lt;code&gt;-interlace JPEG&lt;/code&gt;: 同上。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-colorspace RGB&lt;/code&gt;: 确保图片以正确的 RGB 色彩空间处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;补充选项：&lt;/strong&gt; &lt;code&gt;-define jpeg:dct-method=float&lt;/code&gt;: 使用更精确的浮点离散余弦变换（DCT）而不是默认的快速整数版本，可以在不增加文件大小的情况下略微提高转换的保真度。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;直接指定目标文件大小：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Command:&lt;/strong&gt; &lt;code&gt;convert image.jpg -define jpeg:extent=150kb result.jpg&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;参数解释:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-define jpeg:extent=&lt;/code&gt;: 从 ImageMagick v6.5.8-2 开始，可以直接指定 JPEG 输出文件的最大大小（例如 “150kb”）。ImageMagick 会自动调整质量以达到这个目标大小。这是解决原始问题中”压缩到 150kb”最直接的方法，但会带来质量损失。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;图像尺寸调整（最显著的减小文件大小的方法）：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Command (示例):&lt;/strong&gt; &lt;code&gt;mogrify -quality &quot;97%&quot; -resize 2048x2048 -filter Lanczos -interlace Plane -gaussian-blur 0.05&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;参数解释:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-resize WxH&lt;/code&gt; 或 &lt;code&gt;-adaptive-resize X%&lt;/code&gt;: 调整图像的尺寸。这是减小文件大小最有效的方法之一，尤其是对于高分辨率图片。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-filter Lanczos&lt;/code&gt;: 一种高质量的缩放滤镜（通常是默认值）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;注意：&lt;/strong&gt; 减小图片尺寸会改变图片的实际像素数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;其他考量及建议：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;批处理：&lt;/strong&gt; 对于多个文件，可以使用&lt;code&gt;mogrify&lt;/code&gt;命令替代&lt;code&gt;convert&lt;/code&gt;。例如：&lt;code&gt;mogrify -strip -interlace Plane -gaussian-blur 0.05 -quality 85% *.jpg&lt;/code&gt;。&lt;strong&gt;注意：&lt;/strong&gt; &lt;code&gt;mogrify&lt;/code&gt;会覆盖原文件，操作前务必备份。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;质量与模糊的权衡：&lt;/strong&gt; 帖子中多次提及 &lt;code&gt;-gaussian-blur&lt;/code&gt; 的争议。许多用户认为简单地降低 &lt;code&gt;-quality&lt;/code&gt; 即可达到目的，或者结合 &lt;code&gt;-sampling-factor 4:2:0&lt;/code&gt; 更有效，避免引入不必要的模糊。对于大尺寸图片，微小的模糊可能不易察觉；对于小图片，则效果可能不佳。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;外部工具：&lt;/strong&gt; 推荐使用如 ImageOptim、pngquant 等外部工具进行进一步的无损或有损优化。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重新压缩的副作用：&lt;/strong&gt; 有评论指出，对已压缩的 JPEG 进行再压缩，即使文件大小增大，也会导致图像质量下降。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Refs&lt;a href=&quot;#refs&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/61205784/convert-and-mogrify-the-correct-way-to-use-them-in-modern-versions-of-imagemagi&quot;&gt;convert and mogrify: The correct way to use them in modern versions of ImageMagick&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/7261855/best-options-parameters-for-imagemagick-to-compress-jpegs-files-to-the-minimal-s&quot;&gt;Best options parameters for ImageMagick to compress JPEGs files to the minimal size without losing too much quality?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://imagemagick.org/script/command-line-tools.php&quot;&gt;ImageMagick Official Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.libvips.org/API/current/&quot;&gt;libvips Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sharp.pixelplumbing.com/&quot;&gt;sharp - High performance Node.js image processing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.google.com/speed/webp/docs/cwebp&quot;&gt;cwebp - WebP encoder&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;本文随时修订中,有错漏可直接评论&lt;/em&gt;&lt;/p&gt;</content:encoded><category>category:工具</category><category>tag:图片优化</category><category>tag:Shell</category></item><item><title>Infographic 信息图指南</title><link>https://blog.cosine.ren/post/infographic-guide</link><guid isPermaLink="false">infographic-guide</guid><description>本文示例图表由 AI 创作
给博客更新了新功能，可以渲染 Infographic 图表，这里也介绍一下。
Infographic 是蚂蚁集团 AntV 团队推出的新一代声明式信息图表生成与渲染框架，是专为 AI 时代设计的声明式信息图表渲染引擎。
它采用简洁的声明式语法，支持 AI</description><pubDate>Sat, 03 Jan 2026 09:31:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;本文示例图表由 AI 创作&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;给博客更新了新功能，可以渲染 Infographic 图表，这里也介绍一下。&lt;/p&gt;
&lt;p&gt;Infographic 是蚂蚁集团 AntV 团队推出的新一代声明式信息图表生成与渲染框架，是专为 AI 时代设计的声明式信息图表渲染引擎。&lt;/p&gt;
&lt;p&gt;它采用简洁的声明式语法，支持 AI 流式输出与实时渲染，并内置了超过 200 种信息图表模板、组件和布局。&lt;/p&gt;
&lt;p&gt;该框架不仅提供高质量的 SVG 输出，还拥有丰富的主题系统（包括手绘风和渐变色）以及配套的在线编辑器和 Skills，极大提升了信息展示的效率。&lt;/p&gt;
&lt;p&gt;用下来，感觉比 Mermaid 图表好用。后续还可以改一下主题。&lt;/p&gt;
&lt;h2&gt;什么是 Infographic&lt;a href=&quot;#什么是-infographic&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Infographic（信息图）是一种将数据、信息和知识以视觉化方式呈现的图表形式。相比传统的文字描述，信息图能够更直观、更有吸引力地传达信息。&lt;/p&gt;
&lt;p&gt;在本博客中，你可以直接在 Markdown 代码块中使用 &lt;code&gt;infographic&lt;/code&gt; 标记来创建各种类型的信息图，支持：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;列表展示&lt;/li&gt;
&lt;li&gt;流程说明&lt;/li&gt;
&lt;li&gt;数据对比&lt;/li&gt;
&lt;li&gt;层级结构&lt;/li&gt;
&lt;li&gt;统计图表&lt;/li&gt;
&lt;li&gt;象限分析&lt;/li&gt;
&lt;li&gt;关系展示&lt;/li&gt;
&lt;li&gt;还有很多其他的模版，参见 Infographic 官方网站的示例&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
  &lt;a href=&quot;https://infographic.antv.vision/gallery&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://infographic.antv.vision/favicon.svg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;en-us.infographic.antv.vision&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;Gallery – AntV Infographic&lt;/h3&gt;
        &lt;p&gt;AntV Infographic - A powerful visualization library for building interactive and customizable infographics.&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://infographic.antv.vision/gallery&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://en-us.infographic.antv.vision/images/og-gallery.png&quot; alt=&quot;Gallery – AntV Infographic&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;h2&gt;怎么写的？&lt;a href=&quot;#怎么写的&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;官方仓库里现在有 skill，我也集成到我博客中了，只需要让 claude code 根据我文章的信息，使用这些 skill 来创建，非常方便了。&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://github.com/antvis/Infographic/tree/main/.skills&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://github.com/fluidicon.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;github.com&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;Infographic/.skills at main · antvis/Infographic&lt;/h3&gt;
        &lt;p&gt; An Infographic Generation and Rendering Framework, bring words to life with AI! - antvis/Infographic&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://github.com/antvis/Infographic/tree/main/.skills&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://repository-images.githubusercontent.com/1054812421/de39c53f-53b2-42cf-8d80-070065435002&quot; alt=&quot;Infographic/.skills at main · antvis/Infographic&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;自己写的话，官方也提供了&lt;a href=&quot;https://infographic.antv.vision/editor&quot;&gt;编辑器&lt;/a&gt;，还有很多社区贡献的&lt;a href=&quot;https://github.com/antvis/Infographic/issues/99&quot;&gt;编辑器&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://infographic.antv.vision/gallery&quot;&gt;Infographic Gallery&lt;/a&gt; 这里有很多示例，另外图标是在 &lt;a href=&quot;https://infographic.antv.vision/icon&quot;&gt;Infographic Icon&lt;/a&gt; 这里语义化搜索的。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/f0268b8dd87483ec63c42d15fc93465f.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/e705dc4fe862007f7236c5a8954ddf67.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;基本语法&lt;a href=&quot;#基本语法&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在代码块中使用 &lt;code&gt;infographic&lt;/code&gt; 标记，第一行指定模板名称，然后使用类似 YAML 的语法定义数据：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;```infographic&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;infographic &amp;lt;template-name&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 标题&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  desc 描述&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 条目名称&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 条目描述&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon icon-name&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;```&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;列表类模板 (list-*)&lt;a href=&quot;#列表类模板-list-&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;适合展示信息列表、特性清单、技术栈等。&lt;/p&gt;
&lt;h3&gt;网格卡片布局&lt;a href=&quot;#网格卡片布局&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;list-grid-badge-card&lt;/code&gt; 模板展示卡片式列表：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic list-grid-badge-card&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 前端技术栈&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  desc 现代化前端开发常用技术&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label TypeScript&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 类型安全的 JavaScript 超集&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon language-typescript&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label React&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 用于构建用户界面的 JavaScript 库&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon react&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label Astro&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 现代化静态站点生成器&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/rocket-launch&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label Tailwind CSS&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 实用优先的 CSS 框架&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon tailwind&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label Vite&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 下一代前端构建工具&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/lightning-bolt&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label Biome&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 一体化的 Web 工具链&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon biome&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;糖果风格卡片&lt;a href=&quot;#糖果风格卡片&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;list-grid-candy-card-lite&lt;/code&gt; 创建更有趣的卡片样式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic list-grid-candy-card-lite&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 博客特色功能&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  desc 本博客的特色功能如下&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 深色模式&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/brightness6&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      illus creative-experiment&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 优雅的主题切换&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/theme-light-dark&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 全站搜索&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 基于 Pagefind 的无后端搜索&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/magnify&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label Markdown 增强&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 支持 GFM、Mermaid、Infographic&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/markdown&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;水平箭头列表&lt;a href=&quot;#水平箭头列表&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;list-row-horizontal-icon-arrow&lt;/code&gt; 展示线性列表：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic list-row-simple-horizontal-arrow&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 企业优势列表&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  desc 展示企业在不同维度上的核心优势与表现值&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 品牌影响力&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 85&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 在目标用户群中具备较强认知与信任度&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      time 2021&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mingcute/diamond-2-fill&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      illus creative-experiment&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 技术研发力&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 90&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 拥有自研核心系统与持续创新能力&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      time 2022&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mingcute/code-fill&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      illus code-thinking&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 市场增长快&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 78&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 近一年用户规模实现快速增长&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      time 2023&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mingcute/wallet-4-line&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      illus business-analytics&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 服务满意度&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 88&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 用户对服务体系整体评分较高&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      time 2020&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mingcute/happy-line&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      illus feeling-happy&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 数据资产全&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 92&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 构建了完整用户标签与画像体系&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      time 2022&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mingcute/user-4-line&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      illus mobile-photos&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 创新能力强&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 83&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 新产品上线频率高于行业平均&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      time 2023&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mingcute/rocket-line&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      illus creativity&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;theme light&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  palette antv&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;流程/顺序类模板 (sequence-*)&lt;a href=&quot;#流程顺序类模板-sequence-&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;适合展示步骤、流程、时间线等有顺序关系的信息。&lt;/p&gt;
&lt;h3&gt;之字形步骤&lt;a href=&quot;#之字形步骤&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;sequence-zigzag-steps-underline-text&lt;/code&gt; 展示流程步骤：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic sequence-zigzag-steps-underline-text&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 博客搭建流程&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 选择框架&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 选择 Astro 作为静态站点生成器&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 设计主题&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 参考 Shoka 主题进行设计&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 开发功能&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 实现文章系统、搜索、评论等功能&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 部署上线&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 使用 Vercel 进行自动化部署&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;圆形流程&lt;a href=&quot;#圆形流程&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;sequence-circular-simple&lt;/code&gt; 展示循环流程：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic sequence-circular-simple&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title PDCA 循环&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label Plan&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 制定计划&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label Do&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 执行实施&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label Check&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 检查验证&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label Act&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 改进优化&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;垂直路线图&lt;a href=&quot;#垂直路线图&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;sequence-roadmap-vertical-simple&lt;/code&gt; 展示时间线或路线图：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic sequence-roadmap-vertical-simple&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 项目里程碑&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 2024 Q1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 项目启动，完成基础架构&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 2024 Q2&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 实现核心功能，开始内容迁移&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 2024 Q3&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 优化性能，添加高级功能&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 2024 Q4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 正式发布，持续优化&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;金字塔结构&lt;a href=&quot;#金字塔结构&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;sequence-pyramid-simple&lt;/code&gt; 展示层级递进关系：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic sequence-pyramid-simple&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 马斯洛需求层次&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 自我实现&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 尊重需求&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 社交需求&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 安全需求&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 生理需求&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;theme&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  palette&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - #8b5cf6&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - #3b82f6&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - #06b6d4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - #10b981&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - #f59e0b&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;对比类模板 (compare-*)&lt;a href=&quot;#对比类模板-compare-&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;适合展示二元对比、优缺点分析等。&lt;/p&gt;
&lt;h3&gt;水平二元对比&lt;a href=&quot;#水平二元对比&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;compare-binary-horizontal-simple-fold&lt;/code&gt; 进行对比：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic compare-binary-horizontal-simple-fold&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title SSR vs SSG&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 服务端渲染 (SSR)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 实时生成&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 每次请求时渲染页面&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 动态内容&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 适合频繁更新的内容&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 服务器负载&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 需要服务器资源&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 静态生成 (SSG)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 构建时生成&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 提前生成所有页面&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 静态内容&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 适合内容相对稳定的场景&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label CDN 友好&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          desc 可以部署到 CDN 边缘节点&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;SWOT 分析&lt;a href=&quot;#swot-分析&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;compare-swot&lt;/code&gt; 进行 SWOT 分析：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic compare-swot&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 技术博客 SWOT 分析&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 优势 (Strengths)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 技术积累&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 个人品牌&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 知识沉淀&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 劣势 (Weaknesses)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 时间投入&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 持续更新压力&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 初期流量低&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 机会 (Opportunities)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 技术社区活跃&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 开源生态发展&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 个人成长空间&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 威胁 (Threats)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 内容同质化&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 平台竞争&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 技术快速迭代&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;层级类模板 (hierarchy-*)&lt;a href=&quot;#层级类模板-hierarchy-&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;适合展示组织结构、分类体系等树形关系。&lt;/p&gt;
&lt;h3&gt;系统分层结构&lt;a href=&quot;#系统分层结构&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;hierarchy-structure&lt;/code&gt; 展示多层架构，非常适合展示系统架构、模块分层：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic hierarchy-structure&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 系统分层结构&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  desc 展示不同层级的模块与功能分组&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 展现层&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 小程序&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label APP&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label PAD&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 客户端&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label WEB&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 应用层&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 核心模块&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能2&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能3&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能5&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能6&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 基础模块&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能2&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能3&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能5&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能6&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 其他模块&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能2&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能3&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能5&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能6&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 平台层&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 模块1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能2&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能3&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 模块2&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能2&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能3&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 模块3&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能2&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能3&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 功能4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;科技风格树形图&lt;a href=&quot;#科技风格树形图&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;hierarchy-tree-tech-style-capsule-item&lt;/code&gt; 展示层级结构：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic hierarchy-tree-tech-style-capsule-item&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 前端技术体系&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 前端开发&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 基础技术&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label HTML&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label CSS&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label JavaScript&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 框架/库&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label React&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label Vue&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label Astro&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 工程化&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label Vite&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label Webpack&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label Rollup&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;圆角矩形树形图&lt;a href=&quot;#圆角矩形树形图&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;hierarchy-tree-curved-line-rounded-rect-node&lt;/code&gt; 展示层级：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic hierarchy-tree-curved-line-rounded-rect-node&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 博客内容分类&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 技术文章&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 前端&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label React&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label TypeScript&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 后端&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label Node.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            - label 数据库&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 生活随笔&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 年度总结&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        - label 读书笔记&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;图表类模板 (chart-*)&lt;a href=&quot;#图表类模板-chart-&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;适合展示统计数据、数值对比等。&lt;/p&gt;
&lt;h3&gt;柱状图&lt;a href=&quot;#柱状图&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;chart-column-simple&lt;/code&gt; 展示数据对比：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic chart-column-simple&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 月度文章发布统计&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 1月&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 5&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 2月&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 8&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 3月&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 12&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 4月&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 6&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 5月&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 10&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 6月&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 15&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;条形图&lt;a href=&quot;#条形图&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;chart-bar-plain-text&lt;/code&gt; 展示横向对比：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic chart-bar-plain-text&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 编程语言使用占比&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label TypeScript&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 45&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label JavaScript&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 25&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label Python&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 15&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label Go&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 10&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label Others&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;饼图&lt;a href=&quot;#饼图&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;chart-pie-plain-text&lt;/code&gt; 展示占比分布：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic chart-pie-plain-text&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 访问来源分布&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 搜索引擎&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 45&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 社交媒体&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 30&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 直接访问&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 15&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 外部链接&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 10&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;环形图&lt;a href=&quot;#环形图&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;chart-pie-donut-pill-badge&lt;/code&gt; 创建环形图：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic chart-pie-donut-pill-badge&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 技术栈占比&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 前端&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 50&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 后端&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 30&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label DevOps&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 20&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;折线图&lt;a href=&quot;#折线图&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;chart-line-plain-text&lt;/code&gt; 展示趋势：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic chart-line-plain-text&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 博客访问量趋势&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 第1周&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 100&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 第2周&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 150&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 第3周&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 200&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 第4周&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 280&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 第5周&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 350&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 第6周&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 420&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;象限分析 (quadrant-*)&lt;a href=&quot;#象限分析-quadrant-&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;适合展示四象限分析、优先级矩阵等。&lt;/p&gt;
&lt;h3&gt;简单卡片象限&lt;a href=&quot;#简单卡片象限&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;quadrant-quarter-simple-card&lt;/code&gt; 进行象限分析：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic quadrant-quarter-simple-card&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 四象限分析&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 重要且紧急&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 直接规避风险&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      illus notify&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 重要不紧急&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 采取风险控制措施&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      illus coffee&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 不重要但紧急&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 通过保险转移风险&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      illus diary&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 不重要不紧急&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 选择接受风险&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      illus invest&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;关系图 (relation-*)&lt;a href=&quot;#关系图-relation-&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;适合展示元素间的关联关系。&lt;/p&gt;
&lt;h3&gt;圆形图标关系&lt;a href=&quot;#圆形图标关系&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;relation-circular-progress&lt;/code&gt; 展示关系网络：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic relation-circle-circular-progress&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 子公司盈利分析&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  desc 各子公司财务表现，盈利同比增长&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 云计算子公司&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 25&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 年度净利润率达25%，成为集团核心增长引擎&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mingcute/cardano-ada-fill&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 人工智能子公司&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 40&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc AI业务快速扩张，盈利同比增长40%&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mingcute/openai-fill&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 物联网子公司&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 1000&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc IoT设备出货量突破千万，盈利稳步提升&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mingcute/medium-fill&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 金融科技子公司&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 18&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 数字支付业务增长迅猛，净利润率18%&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mingcute/paypal-fill&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 新能源子公司&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value 50&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 绿色能源项目实现规模化盈利，增长潜力巨大&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mingcute/drone-fill&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;主题定制&lt;a href=&quot;#主题定制&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;可以通过 &lt;code&gt;theme&lt;/code&gt; 块自定义颜色方案：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic list-grid-badge-card&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 自定义配色示例&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 主色调&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 品牌主色&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 辅助色&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 强调色彩&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 中性色&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 背景文字&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;theme&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  palette&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - #3b82f6&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - #8b5cf6&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - #f97316&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - #06b6d4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - #10b981&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;实用技巧&lt;a href=&quot;#实用技巧&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;1. 选择合适的模板&lt;a href=&quot;#1-选择合适的模板&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;根据要展示的信息类型选择对应的模板：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;列表信息&lt;/strong&gt; → &lt;code&gt;list-*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流程步骤&lt;/strong&gt; → &lt;code&gt;sequence-*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据对比&lt;/strong&gt; → &lt;code&gt;compare-*&lt;/code&gt; 或 &lt;code&gt;chart-*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;层级关系&lt;/strong&gt; → &lt;code&gt;hierarchy-*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优先级分析&lt;/strong&gt; → &lt;code&gt;quadrant-*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关联关系&lt;/strong&gt; → &lt;code&gt;relation-*&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 合理使用图标&lt;a href=&quot;#2-合理使用图标&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;a href=&quot;https://pictogrammers.com/library/mdi/&quot;&gt;Material Design Icons&lt;/a&gt; 让信息图更生动：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;icon mdi/rocket-launch&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;icon mdi/heart&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;icon mdi/lightbulb&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;icon mdi/chart-line&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 控制信息密度&lt;a href=&quot;#3-控制信息密度&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;每个信息图不要包含过多条目（建议 3-8 个）&lt;/li&gt;
&lt;li&gt;使用简洁的标签和描述&lt;/li&gt;
&lt;li&gt;复杂信息可以拆分成多个信息图&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. 注意主题适配&lt;a href=&quot;#4-注意主题适配&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;信息图会自动跟随博客的深色/浅色主题切换，无需额外配置。&lt;/p&gt;
&lt;h2&gt;总结&lt;a href=&quot;#总结&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Infographic 为 Markdown 文档提供了强大的可视化能力，能够让技术博客、文档、笔记更加生动易读。合理使用各种模板，可以显著提升内容的表现力和可读性。&lt;/p&gt;
&lt;p&gt;更多模板和详细文档，请访问 &lt;a href=&quot;https://infographic.antv.vision/gallery&quot;&gt;Infographic 官方网站的示例&lt;/a&gt;。&lt;/p&gt;</content:encoded><category>category:工具</category><category>tag:可视化</category><category>tag:前端</category></item><item><title>FE Bits Vol.21 | 博客圣诞特效与 Moe Copy 更新，AntV 推出 Infographic</title><link>https://blog.cosine.ren/post/weekly-21</link><guid isPermaLink="false">weekly-21</guid><description>本期分享了个人项目和前端技术动态，包括博客中实现的 CSS 低质量图片占位符（LQIP）、可开关的圣诞特效模块，以及 Moe Copy AI 插件的 v0.2.0 更新。同时介绍了 CSS 新特性如文本装饰内边距（text-decoration-inset）、锚点定位回退检测和容器样式查询，盘点了前端社区热点与实用工具。</description><pubDate>Wed, 31 Dec 2025 13:49:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本期网址 &lt;a href=&quot;https://blog.cosine.ren/post/weekly-21&quot;&gt;https://blog.cosine.ren/post/weekly-21&lt;/a&gt;
本周刊更新时间期望是在每周天。
推荐订阅本周刊的 &lt;a href=&quot;https://blog.cosine.ren/rss.xml&quot;&gt;RSS&lt;/a&gt;。
公众号 前端周周谈 FE Bits，点击阅读原文链接可查看原文。
QQ 讨论小群 598022684，讨论前端技术 &amp;amp; 生活，也可在群里投稿自己的文章，随意加入，比较偏向粉丝群的性质～
本周刊文章内容同时也开源在 &lt;a href=&quot;https://github.com/yusixian/fe-bits-weekly&quot;&gt;fe-bits-weekly&lt;/a&gt;，欢迎关注。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;今天是 2025 年 12 月 31 日，星期三。&lt;/p&gt;
&lt;p&gt;元旦放假，周刊停更一期，所以今天更新上周天的。&lt;/p&gt;
&lt;p&gt;年终总结在写了在写了，但是今天就不一定写的出来了。&lt;/p&gt;
&lt;h2&gt;个人项目&lt;a href=&quot;#个人项目&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这周圣诞节，前端圈都没什么动静，不过正好我做了一堆自己项目的更新。&lt;/p&gt;
&lt;p&gt;今天一总结，发现真不少啊。&lt;/p&gt;
&lt;p&gt;发了一篇博客「&lt;a href=&quot;https://blog.cosine.ren/post/git-worktrunk-guide&quot;&gt;Worktrunk 完全指南：让 Git Worktree 和 Claude Code 和平共处&lt;/a&gt;」因为我非常喜欢这个工具于是写了一篇博文卖安利～&lt;/p&gt;
&lt;h3&gt;博客项目&lt;a href=&quot;#博客项目&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;首先是博客的一些更新&lt;/p&gt;
&lt;h4&gt;LQIP&lt;a href=&quot;#lqip&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;一开始想在博客中，实现类似 &lt;a href=&quot;https://leanrada.com/notes/css-only-lqip&quot;&gt;Minimal CSS-only blurry image placeholders&lt;/a&gt; 的 CSS-only LQIP（低质量图片占位符），使用单个 CSS 自定义属性 —lqip 编码图片的模糊预览。&lt;/p&gt;
&lt;p&gt;这篇文章的技术原理是使用 20 位整数编码图片信息（8 位 Oklab 基础色 + 12 位亮度分量），在 CSS 中通过位运算解码（&lt;code&gt;mod()&lt;/code&gt;, &lt;code&gt;round(down)&lt;/code&gt;, &lt;code&gt;pow()&lt;/code&gt; 等），使用径向渐变叠加渲染模糊效果，配合二次缓动实现平滑过渡。&lt;/p&gt;
&lt;p&gt;我选择简化一些的实现，不追求 CSS Only 了，因为打算先做博客内部的图片，文章内部的外部图片等后续优化的时候再一起做，放上最终效果在这里：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/40e44c8ac166183d5f823d7aa81fa792.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/2e91463883247a340dc99ddc1c97ae74.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;需要运行一下 &lt;code&gt;nr generate:lqips&lt;/code&gt; 就会生成一个 &lt;code&gt;lqips.json&lt;/code&gt; 的 json 文件在 assets 下，若没有这个文件则不提供占位符～&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://blog.cosine.ren/post/astro-lqip-implementation&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://blog.cosine.ren/favicon.ico&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;blog.cosine.ren&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;在 Astro 博客中实现 LQIP（低质量图片占位符） | 笔记 / 前端 | 余弦の博客&lt;/h3&gt;
        &lt;p&gt;本文由 AI 辅助写作，作记录用 一开始想在博客中，实现类似 Minimal CSS-only blurry image placeholders 的 CSS-only LQIP（低质量图片占位符），使用单个 CSS 自定义属性 --lqip 编码图片的模糊预览。 这篇文章的技术原理是使用 20…&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://blog.cosine.ren/post/astro-lqip-implementation&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://blog.cosine.ren/img/avatar.webp&quot; alt=&quot;在 Astro 博客中实现 LQIP（低质量图片占位符） | 笔记 / 前端 | 余弦の博客&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;后续看到有人提出更好的实现，已加入 &lt;a href=&quot;https://cos.featurebase.app/p/feature-geng-hao-de-lqip&quot;&gt;TODO&lt;/a&gt;！&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;
&lt;h4&gt;圣诞特效&lt;a href=&quot;#圣诞特效&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;究～极～花里胡哨！！
圣诞节到了，想给博客加点节日氛围。最终实现了一套可开关的圣诞特效模块，包括雪花和彩灯以及圣诞配色方案～&lt;/p&gt;
&lt;p&gt;雪花效果是从 Shadertoy 上的 &lt;a href=&quot;https://www.shadertoy.com/view/ldsGDn&quot;&gt;Just snow&lt;/a&gt; 学来的！
彩灯是参考 &lt;a href=&quot;https://codepen.io/tobyj/pen/QjvEex&quot;&gt;Toby J 的 CodePen&lt;/a&gt; 实现，优化调整了性能，调整为圣诞配色（红、绿、金）&lt;/p&gt;
&lt;p&gt;我知道肯定会有人说花里胡哨所以我加了开关，欢迎品鉴～&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://blog.cosine.ren/post/astro-christmas-effects&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://blog.cosine.ren/favicon.ico&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;blog.cosine.ren&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;为 Astro 博客添加可插拔的圣诞特效模块 | 笔记 / 前端 | 余弦の博客&lt;/h3&gt;
        &lt;p&gt;本文由 AI 辅助写作，作记录用 圣诞节到了，想给博客加点节日氛围。最终实现了一套可插拔的圣诞特效模块，包括： 3D 雪花飘落（React Three Fiber + GLSL 着色器） 悬挂彩灯装饰（CSS 动画） 头像圣诞帽（SVG） 圣诞配色方案（CSS 变量） 可拖拽圣诞球开关…&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://blog.cosine.ren/post/astro-christmas-effects&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://blog.cosine.ren/img/avatar.webp&quot; alt=&quot;为 Astro 博客添加可插拔的圣诞特效模块 | 笔记 / 前端 | 余弦の博客&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/7a36339bd35536b9ea9621db6cd238a2.webp&quot; alt=&quot;圣诞特效展示 1&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;如果嫌弃干扰阅读了，可以点击开关关掉～&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/e3cd9efe4bf5f40215566de09d537b58.gif&quot; alt=&quot;圣诞特效展示 2&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;注意看这个雪是双层的，内容容器夹在雪花中间，远景雪花在文章后面飘过，近景雪花在前面飘过，配合视差效果，感觉还挺满意的。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/cabfd68dc8313ff59d0c744ca224a6ed.gif&quot; alt=&quot;圣诞特效展示 3&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;上面的雪花特效是最初的版本，现在的版本是后来又做了一些优化。&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;
&lt;h4&gt;使用指南和反馈&lt;a href=&quot;#使用指南和反馈&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;把&lt;a href=&quot;https://blog.cosine.ren/post/astro-koharu-guide&quot;&gt;博客的使用指南&lt;/a&gt;先简单搓出来了，然后换了一下切换昼夜模式的过渡动画，从左往右扫过去的感觉。
虽然说现阶段还不建议使用，但如果想 fork 出去改改还是都可以的，预计元旦假期我会重点进行重构和性能优化、然后开放一个清理后的仓库方便部署，到那个时候就可以玩耍了！&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;
&lt;p&gt;然后发现 alma 的&lt;a href=&quot;https://feedback.alma.now/&quot;&gt;反馈页面&lt;/a&gt;看起来很棒，看了下是用的 featurebase.app
于是我也想试试这个，给博客主题做了一个反馈页面（有没有人用再说吧）：&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://cos.featurebase.app/&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://695396c48082c8bf1df0da64.featurebase-attachments.com/c/static/019b6e8a-6277-7d35-aba4-89663e4d17e7/osoqe5zoiw.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;cos.featurebase.app&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;Feedback - astro-koharu&lt;/h3&gt;
        &lt;p&gt;Give astro-koharu feedback on how they could improve their product.&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://cos.featurebase.app/&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://og.featurebase.app/?type=root&amp;amp;title=Have+something+to+say%3F&amp;amp;description=Tell+astro-koharu+how+they+could+make+the+product+more+useful+to+you.&amp;amp;companyLogo=https%3A%2F%2F695396c48082c8bf1df0da64.featurebase-attachments.com%2Fc%2Fstatic%2F019b6e8a-6277-7d35-aba4-89663e4d17e7%2Fosoqe5zoiw.png&amp;amp;companyName=astro-koharu&amp;amp;companyThemeColor=%23d67b87&amp;amp;companyTLP=bottom&amp;amp;category=Feedback&quot; alt=&quot;Feedback - astro-koharu&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;h3&gt;Moe Copy AI 插件更新&lt;a href=&quot;#moe-copy-ai-插件更新&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Moe Copy AI 插件也大改版了，然后收到了好朋友的 PR！发布了 v0.2.0，变得越来越好用了&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://github.com/yusixian/moe-copy-ai/releases/tag/0.2.0&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://github.com/fluidicon.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;github.com&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;Release v0.2.0 · yusixian/moe-copy-ai&lt;/h3&gt;
        &lt;p&gt;[v0.2.0] - 2025-12-31
Chrome 商店：https://chromewebstore.google.com/detail/moe-copy-ai/dfmlcfckmfgabpgbaobgapdfmjiihnck
Release Notes 新功能 侧边栏 (Side Panel) - 全新的扩展侧边栏界面，提供更高效的工作流程 分段式标签导航，在批量抓取和内容…&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://github.com/yusixian/moe-copy-ai/releases/tag/0.2.0&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://opengraph.githubassets.com/d34c8ce481c93c99bd49af2eb6e44a9003439be8deff0d19b9e4e808c63a8b75/yusixian/moe-copy-ai/releases/tag/0.2.0&quot; alt=&quot;Release v0.2.0 · yusixian/moe-copy-ai&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;div&gt;&lt;/div&gt;
&lt;p&gt;PR 在原本的直接 fetch 的基础上，提供了「后台打开新 tab」和在「当前页面跳转」来获取
下一页按钮选择器改用 XPath，还新增了自动翻页功能，支持跨页批量抓取，感谢 &lt;a href=&quot;https://github.com/hyoban&quot;&gt;@hyoban&lt;/a&gt; 贡献的这个&lt;a href=&quot;https://github.com/yusixian/moe-copy-ai/pull/15&quot;&gt;PR #15&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/22489cee45cbea49bba41cb8acf413d4.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;欢迎使用！欢迎反馈！文档在&lt;a href=&quot;https://moe.cosine.ren/docs&quot;&gt;这里&lt;/a&gt;。&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://chromewebstore.google.com/detail/moe-copy-ai/dfmlcfckmfgabpgbaobgapdfmjiihnck&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://ssl.gstatic.com/chrome/webstore/images/icon_144px.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;chromewebstore.google.com&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;Moe Copy AI - Chrome Web Store&lt;/h3&gt;
        &lt;p&gt;✨ 萌萌哒的 AI 网页数据提取助手 ✨&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://chromewebstore.google.com/detail/moe-copy-ai/dfmlcfckmfgabpgbaobgapdfmjiihnck&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://lh3.googleusercontent.com/S-T-SIPjgzoLP_8FJOAaVo4B1AbVLa6V1lg6rtl0nTePtQBrek9dnMNBDT5_mYYOwSIHUFucdA1PQGGy3_z8GYcON0s=s128-rj-sc0x00ffffff&quot; alt=&quot;Moe Copy AI - Chrome Web Store&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;a href=&quot;https://github.com/yusixian/moe-copy-ai&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://github.com/fluidicon.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;github.com&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;GitHub - yusixian/moe-copy-ai: ✨ 萌萌哒的 AI 网页数据提取助手 ✨&lt;/h3&gt;
        &lt;p&gt;✨ 萌萌哒的 AI 网页数据提取助手 ✨. Contribute to yusixian/moe-copy-ai development by creating an account on GitHub.&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://github.com/yusixian/moe-copy-ai&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://opengraph.githubassets.com/73b751073039fdebe6dfd14b7cf57c550ffcfe294e3cc5224e1ff721efa45863/yusixian/moe-copy-ai&quot; alt=&quot;GitHub - yusixian/moe-copy-ai: ✨ 萌萌哒的 AI 网页数据提取助手 ✨&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;h2&gt;文章与社区动态&lt;a href=&quot;#文章与社区动态&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/antvis/Infographic&quot;&gt;antvis/Infographic&lt;/a&gt;：蚂蚁集团 AntV 团队推出新一代声明式信息图表（Infographic）生成与渲染框架，旨在通过 AI 驱动让数据叙事更简单。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nolanlawson.com/2025/12/22/how-i-use-ai-agents-to-write-code/&quot;&gt;How I use AI agents to write code&lt;/a&gt;：好文，我也是这样转变的（？）或者说没有转变。有趣的是 AI 出来之后我觉得编码热情或者说想构建产品的欲望越来越强烈了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.logrocket.com/frontend-wrapped-2025/&quot;&gt;Frontend Wrapped 2025: The 10 storylines that defined the year - LogRocket Blog&lt;/a&gt;：2025 年前端世界发生了不少大事，这篇文章盘点了那些塑造了这一年的十大热点。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.patreon.com/posts/ever-wanted-to-147025063&quot;&gt;Ever wanted to have a rotating rainbow segments border&lt;/a&gt;：介绍并对比了使用 CSS &lt;code&gt;conic-gradient()&lt;/code&gt; 与 SVG &lt;code&gt;&amp;lt;rect&amp;gt;&lt;/code&gt; 两种技术实现旋转彩虹分段边框的方法。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://joyeecheung.github.io/blog/2025/12/30/require-esm-in-node-js-implementers-tales/&quot;&gt;require(esm) in Node.js: implementer’s tales&lt;/a&gt;：Node.js 核心贡献者 Joyee Cheung 深入解析 &lt;code&gt;require(esm)&lt;/code&gt; 功能实现过程中遇到的关键挑战与解决方案。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;CSS 新特性&lt;a href=&quot;#css-新特性&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.zhangxinxu.com/wordpress/2025/12/css-anchor-container-query/&quot;&gt;补全不足，CSS 锚点定位支持锚定容器回退检测了&lt;/a&gt;：Chrome 143+ 引入 CSS 锚点定位回退检测机制，可自动检测容器布局变化并动态切换定位方向，解决传统锚点定位在空间受限时的显示异常问题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.zhangxinxu.com/wordpress/2025/12/css-style-container-range-syntax/&quot;&gt;今日学习 CSS style()样式查询及其 range 范围语法&lt;/a&gt;：介绍了 CSS @container 的样式查询 (Style Query) 新能力，展示从变量匹配到范围语法 (Range Syntax)、再到针对自身元素匹配的进阶使用技巧。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;text-decoration-inset&lt;a href=&quot;#text-decoration-inset&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;MDN：&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/text-decoration-inset&quot;&gt;text-decoration-inset&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;兼容性：实验性，文中仅提到 Firefox 146+ 下有效。&lt;/li&gt;
&lt;li&gt;介绍文章：&lt;a href=&quot;https://css-tricks.com/text-decoration-inset-is-like-padding-for-text-decorations/&quot;&gt;text-decoration-inset is Like Padding for Text Decorations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/mrdanielschwarz/pen/VYaPVgr&quot;&gt;VYaPVgr&lt;/a&gt; by mrdanielschwarz (&lt;a href=&quot;https://codepen.io/mrdanielschwarz&quot;&gt;@mrdanielschwarz&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;CSS 中的 &lt;code&gt;text-decoration-inset&lt;/code&gt; 允许开发者像设置内边距一样裁剪文字装饰（如下划线）的左右边缘，从而实现文字装饰与文字内容的精确对齐。该属性支持 em 等相对单位，使装饰能随字体大小缩放，并且可以配合动画和过渡效果，创造出更多原生的、引人注目的文字装饰动态效果。&lt;/p&gt;
&lt;h2&gt;工具&lt;a href=&quot;#工具&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alam00000/bentopdf&quot;&gt;BentoPDF&lt;/a&gt;: 功能强大、注重隐私的开源 PDF 工具集，可自部署，提供超过 50 种 PDF 操作工具，覆盖编辑、转换、安全、优化等多个维度。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Codepen 精选&lt;a href=&quot;#codepen-精选&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;CSS-Only Stacking Cards&lt;a href=&quot;#css-only-stacking-cards&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 CSS Scroll-Timeline 实现的卡片堆叠，无需 JavaScript：&lt;/p&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/karabharat/pen/YPWXqmx&quot;&gt;YPWXqmx&lt;/a&gt; by karabharat (&lt;a href=&quot;https://codepen.io/karabharat&quot;&gt;@karabharat&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/ca34d5f3371ded092938b025f2d235f4.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;easy rainbow segments card border cases&lt;a href=&quot;#easy-rainbow-segments-card-border-cases&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/thebabydino/pen/RNRPEqb&quot;&gt;RNRPEqb&lt;/a&gt; by thebabydino (&lt;a href=&quot;https://codepen.io/thebabydino&quot;&gt;@thebabydino&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;文章：&lt;a href=&quot;https://www.patreon.com/posts/ever-wanted-to-147025063&quot;&gt;Ever wanted to have a rotating rainbow segments border&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;实现一个由等分彩虹色段组成的旋转边框效果。作者指出当容器的宽高比远离正方形时，单纯使用 CSS 的 conic-gradient() 方法无法保持色段等分，且分隔线难以垂直于边框。因此，文章提出了更优的解决方案：使用 SVG 的  元素来精确控制每个色段的形状和动画，从而获得更稳定、更完美的视觉效果。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/58b95038b527cd20177db153d47f73fc.gif&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;</content:encoded><category>category:周刊</category></item><item><title>Worktrunk 完全指南：让 Git Worktree 和 Claude Code 和平共处</title><link>https://blog.cosine.ren/post/git-worktrunk-guide</link><guid isPermaLink="false">git-worktrunk-guide</guid><description>写在前面
最近用 Claude Code 写代码的频率越来越高，有时候会同时开三四个任务：一个在修 bug，一个在加新功能，一个在 review 中，还有一个在跑测试验证新点子。问题来了：它们都需要独立的工作目录，不然会互相串代码。
Git 的 worktree</description><pubDate>Wed, 31 Dec 2025 12:10:00 GMT</pubDate><content:encoded>&lt;h2&gt;写在前面&lt;a href=&quot;#写在前面&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;最近用 Claude Code 写代码的频率越来越高，有时候会同时开三四个任务：一个在修 bug，一个在加新功能，一个在 review 中，还有一个在跑测试验证新点子。问题来了：它们都需要独立的工作目录，不然会互相串代码。&lt;/p&gt;
&lt;p&gt;Git 的 &lt;code&gt;worktree&lt;/code&gt; 功能本来就是为这种场景设计的，但原生命令实在有些啰嗦。传统创建一个新 worktree 要这样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; worktree&lt;/span&gt;&lt;span&gt; add&lt;/span&gt;&lt;span&gt; -b&lt;/span&gt;&lt;span&gt; feature-auth&lt;/span&gt;&lt;span&gt; ../myproject-feature-auth&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; ../myproject-feature-auth&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;删掉它又得回到主目录：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; ../myproject&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; worktree&lt;/span&gt;&lt;span&gt; remove&lt;/span&gt;&lt;span&gt; ../myproject-feature-auth&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; branch&lt;/span&gt;&lt;span&gt; -d&lt;/span&gt;&lt;span&gt; feature-auth&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分支名要输三次，每次都要手动 &lt;code&gt;cd&lt;/code&gt;。干这事儿多了，会觉得有些浪费生命，这也是我之前为什么不喜欢 worktree。&lt;/p&gt;
&lt;p&gt;直到发现 &lt;a href=&quot;https://worktrunk.dev/&quot;&gt;Worktrunk&lt;/a&gt;，才发现 worktree 管理原来可以这么优雅（当然还有很多别的工具）。&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;
&lt;p&gt;用下来感觉非常好，写一篇博客卖一卖安利。&lt;/p&gt;
&lt;h2&gt;Worktrunk 是什么&lt;a href=&quot;#worktrunk-是什么&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;简单来说，Worktrunk 是一个 &lt;strong&gt;Git Worktree 的 CLI 包装工具&lt;/strong&gt;，专门为&lt;strong&gt;并行运行多个 AI Agent&lt;/strong&gt; 的场景优化。它的核心理念是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;每个 worktree 对应一个分支，用分支名管理 worktree，路径自动生成。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;对比一下命令差异就能感受到差异：&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;任务&lt;/th&gt;&lt;th&gt;Worktrunk&lt;/th&gt;&lt;th&gt;原生 Git&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;切换到某个 worktree&lt;/td&gt;&lt;td&gt;&lt;code&gt;wt switch feat&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;cd ../repo.feat&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;创建 worktree + 启动 Claude&lt;/td&gt;&lt;td&gt;&lt;code&gt;wt switch -c -x claude feat&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;git worktree add -b feat ../repo.feat &amp;amp;&amp;amp; cd ../repo.feat &amp;amp;&amp;amp; claude&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;清理当前 worktree&lt;/td&gt;&lt;td&gt;&lt;code&gt;wt remove&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;cd ../repo &amp;amp;&amp;amp; git worktree remove ../repo.feat &amp;amp;&amp;amp; git branch -d feat&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;列出所有 worktree 状态&lt;/td&gt;&lt;td&gt;&lt;code&gt;wt list&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;git worktree list&lt;/code&gt; (只显示路径)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;基本就是把 &lt;code&gt;git worktree&lt;/code&gt; 的操作复杂度从「记三个参数」降到「说个分支名」。&lt;/p&gt;
&lt;h2&gt;为什么需要 Worktrunk?&lt;a href=&quot;#为什么需要-worktrunk&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;如果你只是偶尔用一下 worktree，原生命令当然就行了。但当你需要&lt;strong&gt;频繁并行开发&lt;/strong&gt;（比如同时跑多个 AI Agent）或者&lt;strong&gt;需要自动化流程&lt;/strong&gt;（创建 worktree 后自动装依赖、跑测试）的时候，Worktrunk 能省下不少时间和心智负担。它本质上是把「用 worktree 的正确姿势」固化成了工具。&lt;/p&gt;
&lt;h2&gt;安装与配置&lt;a href=&quot;#安装与配置&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;安装&lt;a href=&quot;#安装&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;macOS/Linux (推荐 Homebrew)：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; max-sixty/worktrunk/wt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;或者用 Cargo：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;cargo&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; worktrunk&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Shell 集成&lt;a href=&quot;#shell-集成&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;装完之后&lt;strong&gt;必须跑这一步&lt;/strong&gt;，否则 &lt;code&gt;wt switch&lt;/code&gt; 无法切换目录：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span&gt; shell&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它会在你的 &lt;code&gt;.zshrc&lt;/code&gt; 或 &lt;code&gt;.bashrc&lt;/code&gt; 里加一段函数，让 &lt;code&gt;wt&lt;/code&gt; 命令可以改变当前 shell 的工作目录。&lt;/p&gt;
&lt;p&gt;装完重启终端，跑 &lt;code&gt;wt --version&lt;/code&gt; 确认安装成功。&lt;/p&gt;
&lt;h2&gt;核心命令详解&lt;a href=&quot;#核心命令详解&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;&lt;code&gt;wt switch&lt;/code&gt; - 切换/创建 Worktree&lt;a href=&quot;#wt-switch---切换创建-worktree&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://worktrunk.dev/switch/&quot;&gt;wt switch | Worktrunk&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这是最常用的命令，用法极简：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 切换到已存在的 worktree (分支名 feat)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; switch&lt;/span&gt;&lt;span&gt; feat&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 基于主分支 main 创建新 worktree + 分支 feat&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; switch&lt;/span&gt;&lt;span&gt; -c&lt;/span&gt;&lt;span&gt; feat&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 创建 worktree + 自动启动 Claude Code&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; switch&lt;/span&gt;&lt;span&gt; -c&lt;/span&gt;&lt;span&gt; feat&lt;/span&gt;&lt;span&gt; -x&lt;/span&gt;&lt;span&gt; claude&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 基于 dev 分支创建新 worktree + 分支 feat-new (而不是 main) + 自动启动 claude&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; switch&lt;/span&gt;&lt;span&gt; -c&lt;/span&gt;&lt;span&gt; feat-new&lt;/span&gt;&lt;span&gt; -b&lt;/span&gt;&lt;span&gt; dev&lt;/span&gt;&lt;span&gt; -x&lt;/span&gt;&lt;span&gt; claude&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;路径规则&lt;/strong&gt;： Worktrunk 会自动在主仓库的&lt;strong&gt;同级目录&lt;/strong&gt;下创建 worktree，命名格式是 &lt;code&gt;&amp;lt;repo&amp;gt;.&amp;lt;branch&amp;gt;&lt;/code&gt;。比如主仓库在 &lt;code&gt;~/code/myproject&lt;/code&gt;，分支 &lt;code&gt;feat&lt;/code&gt; 的 worktree 就在 &lt;code&gt;~/code/myproject.feat&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;参数说明&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-c&lt;/code&gt; / &lt;code&gt;--create&lt;/code&gt;：创建新 worktree + 分支&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-x &amp;lt;cmd&amp;gt;&lt;/code&gt;：切换后自动执行命令 (比如 &lt;code&gt;claude&lt;/code&gt;, &lt;code&gt;code&lt;/code&gt;, &lt;code&gt;npm install&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-b &amp;lt;base&amp;gt;&lt;/code&gt;：指定基础分支 (默认是 &lt;code&gt;main&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Shortcut&lt;/th&gt;&lt;th&gt;含义&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;^&lt;/code&gt;&lt;/td&gt;&lt;td&gt;默认分支（main/master）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;@&lt;/code&gt;&lt;/td&gt;&lt;td&gt;当前分支/工作树&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;-&lt;/code&gt;&lt;/td&gt;&lt;td&gt;之前的工作目录（例如 &lt;code&gt;cd -&lt;/code&gt; ）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; switch&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt;                      # Back to previous&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; switch&lt;/span&gt;&lt;span&gt; ^&lt;/span&gt;&lt;span&gt;                      # Default branch worktree&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; switch&lt;/span&gt;&lt;span&gt; --create&lt;/span&gt;&lt;span&gt; fix&lt;/span&gt;&lt;span&gt; --base=@&lt;/span&gt;&lt;span&gt;  # Branch from current HEAD&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;实战场景&lt;a href=&quot;#实战场景&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;假设你在跑三个并行任务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Terminal 1: 修复认证 bug&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; switch&lt;/span&gt;&lt;span&gt; -c&lt;/span&gt;&lt;span&gt; -x&lt;/span&gt;&lt;span&gt; claude&lt;/span&gt;&lt;span&gt; fix-auth&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# Terminal 2: 重构 API&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; switch&lt;/span&gt;&lt;span&gt; -c&lt;/span&gt;&lt;span&gt; -x&lt;/span&gt;&lt;span&gt; claude&lt;/span&gt;&lt;span&gt; refactor-api&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# Terminal 3: 加新功能&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; switch&lt;/span&gt;&lt;span&gt; -c&lt;/span&gt;&lt;span&gt; -x&lt;/span&gt;&lt;span&gt; claude&lt;/span&gt;&lt;span&gt; feat-dashboard&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每个 Claude 实例跑在独立目录里，互不干扰。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;wt list&lt;/code&gt; - 查看所有 Worktree&lt;a href=&quot;#wt-list---查看所有-worktree&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://worktrunk.dev/list/&quot;&gt;wt list | Worktrunk&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; list&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出大概长这样:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; list&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  Branch&lt;/span&gt;&lt;span&gt;       Status&lt;/span&gt;&lt;span&gt;        HEAD±&lt;/span&gt;&lt;span&gt;    main↕&lt;/span&gt;&lt;span&gt;  Remote⇅&lt;/span&gt;&lt;span&gt;  Commit&lt;/span&gt;&lt;span&gt;    Age&lt;/span&gt;&lt;span&gt;   Message&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt; feature-api&lt;/span&gt;&lt;span&gt;  +&lt;/span&gt;&lt;span&gt;   ↕⇡&lt;/span&gt;&lt;span&gt;     +54&lt;/span&gt;&lt;span&gt;   -5&lt;/span&gt;&lt;span&gt;   ↑4&lt;/span&gt;&lt;span&gt;  ↓1&lt;/span&gt;&lt;span&gt;   ⇡3&lt;/span&gt;&lt;span&gt;      ec97decc&lt;/span&gt;&lt;span&gt;  30m&lt;/span&gt;&lt;span&gt;   Add&lt;/span&gt;&lt;span&gt; API&lt;/span&gt;&lt;span&gt; tests&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;^&lt;/span&gt;&lt;span&gt; main&lt;/span&gt;&lt;span&gt;             ^⇅&lt;/span&gt;&lt;span&gt;                         ⇡1&lt;/span&gt;&lt;span&gt;  ⇣1&lt;/span&gt;&lt;span&gt;  6088adb3&lt;/span&gt;&lt;span&gt;  4d&lt;/span&gt;&lt;span&gt;    Merge&lt;/span&gt;&lt;span&gt; fix-auth:&lt;/span&gt;&lt;span&gt; hardened&lt;/span&gt;&lt;span&gt; to…&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; fix-auth&lt;/span&gt;&lt;span&gt;         ↕&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;                ↑2&lt;/span&gt;&lt;span&gt;  ↓1&lt;/span&gt;&lt;span&gt;     |&lt;/span&gt;&lt;span&gt;     127407de&lt;/span&gt;&lt;span&gt;  5h&lt;/span&gt;&lt;span&gt;    Add&lt;/span&gt;&lt;span&gt; secure&lt;/span&gt;&lt;span&gt; token&lt;/span&gt;&lt;span&gt; storage&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;○&lt;/span&gt;&lt;span&gt; Showing&lt;/span&gt;&lt;span&gt; 3&lt;/span&gt;&lt;span&gt; worktrees,&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt; with&lt;/span&gt;&lt;span&gt; changes,&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt; ahead,&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt; column&lt;/span&gt;&lt;span&gt; hidden&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/b12f258c2137deadbbc05beada5680de.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;比原生 &lt;code&gt;git worktree list&lt;/code&gt; 强很多，可以看到很多信息&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Git 状态&lt;/strong&gt;：有多少未提交/未追踪文件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI 状态&lt;/strong&gt;：如果配置了 GitHub Actions,会显示最新的构建状态&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于管理多个 worktree 来说,这个视图非常实用。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;wt remove&lt;/code&gt; - 清理 Worktree&lt;a href=&quot;#wt-remove---清理-worktree&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://worktrunk.dev/remove/&quot;&gt;wt remove | Worktrunk&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 删除当前 worktree + 分支&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; remove&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 删除指定 worktree&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; remove&lt;/span&gt;&lt;span&gt; feature-branch&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; remove&lt;/span&gt;&lt;span&gt; old-feature&lt;/span&gt;&lt;span&gt; another-branch&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 删除 worktree 但保留分支&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; remove&lt;/span&gt;&lt;span&gt; --no-delete-branch&lt;/span&gt;&lt;span&gt; feature-branch&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 强制删除未合并的分支&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; remove&lt;/span&gt;&lt;span&gt; -D&lt;/span&gt;&lt;span&gt; experimental&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;wt merge&lt;/code&gt; - 合并工作流&lt;a href=&quot;#wt-merge---合并工作流&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://worktrunk.dev/merge/&quot;&gt;wt merge | Worktrunk&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这个命令封装了「合并 → 清理」的完整流程：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 把当前 worktree 合并到默认分支 如 main&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; merge&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 把当前 worktree 合并到 dev 分支&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; merge&lt;/span&gt;&lt;span&gt; dev&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 合并后保留工作树&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; merge&lt;/span&gt;&lt;span&gt; --no-remove&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 保留提交历史（不合并）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; merge&lt;/span&gt;&lt;span&gt; --no-squash&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 跳过提交/合并（除非使用 --no-rebase 参数，否则 rebase 仍会运行）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; merge&lt;/span&gt;&lt;span&gt; --no-commit&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果配置了 &lt;a href=&quot;https://worktrunk.dev/hook/&quot;&gt;&lt;strong&gt;pre-merge hook&lt;/strong&gt;&lt;/a&gt;，会在合并前自动跑测试或 lint。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;wt select&lt;/code&gt; - 交互式选择器&lt;a href=&quot;#wt-select---交互式选择器&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://worktrunk.dev/select/&quot;&gt;wt select | Worktrunk&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如果你记不清 worktree 名字,可以用 &lt;code&gt;select&lt;/code&gt; 命令:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; select&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;会弹出一个 fzf 风格的界面,用方向键选择要切换的 worktree。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/362d232f3f5a0d035809dcbe2e14ab46.gif&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Hooks&lt;a href=&quot;#hooks&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://worktrunk.dev/hook/&quot;&gt;wt hook | Worktrunk&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Worktrunk 支持在 worktree 生命周期的不同阶段执行命令，同时支持全局用户 hook 配置（&lt;code&gt;~/.config/worktrunk/config.toml&lt;/code&gt; ）和项目 hook 配置（ &lt;code&gt;.config/wt.toml&lt;/code&gt;）
好的,我只给你补充 Hook 系统这一段内容:&lt;/p&gt;
&lt;h3&gt;Hook 类型一览&lt;a href=&quot;#hook-类型一览&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;





















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Hook&lt;/th&gt;&lt;th&gt;触发时机&lt;/th&gt;&lt;th&gt;阻塞模式&lt;/th&gt;&lt;th&gt;失败即停&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;post-create&lt;/code&gt;&lt;/td&gt;&lt;td&gt;worktree 创建后&lt;/td&gt;&lt;td&gt;是&lt;/td&gt;&lt;td&gt;否&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;post-start&lt;/code&gt;&lt;/td&gt;&lt;td&gt;worktree 创建后&lt;/td&gt;&lt;td&gt;否(后台)&lt;/td&gt;&lt;td&gt;否&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;post-switch&lt;/code&gt;&lt;/td&gt;&lt;td&gt;每次切换后&lt;/td&gt;&lt;td&gt;否(后台)&lt;/td&gt;&lt;td&gt;否&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;pre-commit&lt;/code&gt;&lt;/td&gt;&lt;td&gt;merge 时提交前&lt;/td&gt;&lt;td&gt;是&lt;/td&gt;&lt;td&gt;是&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;pre-merge&lt;/code&gt;&lt;/td&gt;&lt;td&gt;合并到目标分支前&lt;/td&gt;&lt;td&gt;是&lt;/td&gt;&lt;td&gt;是&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;post-merge&lt;/code&gt;&lt;/td&gt;&lt;td&gt;合并成功后&lt;/td&gt;&lt;td&gt;是&lt;/td&gt;&lt;td&gt;否&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;pre-remove&lt;/code&gt;&lt;/td&gt;&lt;td&gt;worktree 删除前&lt;/td&gt;&lt;td&gt;是&lt;/td&gt;&lt;td&gt;是&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;阻塞模式&lt;/strong&gt;: 命令执行完成前,主流程会等待&lt;br /&gt;
&lt;strong&gt;失败即停&lt;/strong&gt;: 第一个失败的命令会中止整个操作&lt;/p&gt;
&lt;h3&gt;核心 Hooks 说明&lt;a href=&quot;#核心-hooks-说明&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;post-create&lt;/strong&gt; - worktree 创建后立即执行,会阻塞直到完成:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;post-create&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;install = &lt;/span&gt;&lt;span&gt;&quot;npm ci&quot;&lt;/span&gt;&lt;span&gt;                    # 安装依赖&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;migrate = &lt;/span&gt;&lt;span&gt;&quot;npm run db:migrate&quot;&lt;/span&gt;&lt;span&gt;        # 数据库迁移&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;env = &lt;/span&gt;&lt;span&gt;&quot;cp .env.example .env&quot;&lt;/span&gt;&lt;span&gt;          # 复制环境配置&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;post-start&lt;/strong&gt; - worktree 创建后在后台执行,不阻塞切换:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;post-start&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;build = &lt;/span&gt;&lt;span&gt;&quot;npm run build&quot;&lt;/span&gt;&lt;span&gt;               # 构建项目&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;server = &lt;/span&gt;&lt;span&gt;&quot;npm run dev&quot;&lt;/span&gt;&lt;span&gt;                # 启动开发服务器&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;日志输出到 &lt;code&gt;.git/wt-logs/{branch}-{source}-post-start-{name}.log&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;post-switch&lt;/strong&gt; - 每次 &lt;code&gt;wt switch&lt;/code&gt; 后在后台执行(无论是新建、切换还是切到当前):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;post-switch = &lt;/span&gt;&lt;span&gt;&quot;echo &apos;Switched to {{ branch }}&apos;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;pre-commit&lt;/strong&gt; - merge 时提交前执行,失败即停:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;pre-commit&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;format = &lt;/span&gt;&lt;span&gt;&quot;cargo fmt -- --check&quot;&lt;/span&gt;&lt;span&gt;       # 代码格式检查&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;lint = &lt;/span&gt;&lt;span&gt;&quot;cargo clippy -- -D warnings&quot;&lt;/span&gt;&lt;span&gt;  # Lint 检查&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;pre-merge&lt;/strong&gt; - 合并到目标分支前执行,失败即停:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;pre-merge&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;test = &lt;/span&gt;&lt;span&gt;&quot;cargo test&quot;&lt;/span&gt;&lt;span&gt;                   # 运行测试&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;build = &lt;/span&gt;&lt;span&gt;&quot;cargo build --release&quot;&lt;/span&gt;&lt;span&gt;       # 生产构建&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;post-merge&lt;/strong&gt; - 合并成功后在目标分支的 worktree 执行(如果存在),否则在主 worktree 执行:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;post-merge = &lt;/span&gt;&lt;span&gt;&quot;cargo install --path .&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;pre-remove&lt;/strong&gt; - worktree 删除前执行,失败即停:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;pre-remove&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cleanup = &lt;/span&gt;&lt;span&gt;&quot;rm -rf /tmp/cache/{{ branch }}&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;模板变量&lt;a href=&quot;#模板变量&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Hook 命令支持模板变量,运行时自动展开:&lt;/p&gt;






































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;变量&lt;/th&gt;&lt;th&gt;示例&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;{{ repo }}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;my-project&lt;/td&gt;&lt;td&gt;仓库名&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;{{ branch }}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;feature-foo&lt;/td&gt;&lt;td&gt;分支名&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;{{ worktree }}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;/path/to/worktree&lt;/td&gt;&lt;td&gt;worktree 绝对路径&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;{{ worktree_name }}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;my-project.feature-foo&lt;/td&gt;&lt;td&gt;worktree 目录名&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;{{ repo_root }}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;/path/to/main&lt;/td&gt;&lt;td&gt;主仓库根路径&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;{{ default_branch }}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;main&lt;/td&gt;&lt;td&gt;默认分支名&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;{{ commit }}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;a1b2c3d4e5f6…&lt;/td&gt;&lt;td&gt;HEAD 完整 SHA&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;{{ short_commit }}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;a1b2c3d&lt;/td&gt;&lt;td&gt;HEAD 短 SHA&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;{{ remote }}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;origin&lt;/td&gt;&lt;td&gt;主远程名称&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;{{ remote_url }}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;mailto:git@github.com&quot;&gt;git@github.com&lt;/a&gt;&lt;div&gt;&lt;/div&gt;/repo.git&lt;/td&gt;&lt;td&gt;远程 URL&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;{{ upstream }}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;origin/feature&lt;/td&gt;&lt;td&gt;上游跟踪分支&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;{{ target }}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;main&lt;/td&gt;&lt;td&gt;目标分支(仅 merge hooks)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;安全机制&lt;a href=&quot;#安全机制&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;项目 hook(&lt;code&gt;.config/wt.toml&lt;/code&gt;)首次运行需要用户批准:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;▲ repo needs approval to execute 3 commands:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;○ post-create install:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  echo &apos;Installing dependencies...&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;❯ Allow and remember? [y/N]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;批准记录保存在用户配置中&lt;/li&gt;
&lt;li&gt;命令变更需要重新批准&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--yes&lt;/code&gt; 跳过提示(适用于 CI)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--no-verify&lt;/code&gt; 完全跳过 hooks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;管理批准记录:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; hook&lt;/span&gt;&lt;span&gt; approvals&lt;/span&gt;&lt;span&gt; add&lt;/span&gt;&lt;span&gt;      # 预批准当前项目所有命令&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; hook&lt;/span&gt;&lt;span&gt; approvals&lt;/span&gt;&lt;span&gt; clear&lt;/span&gt;&lt;span&gt;    # 清除当前项目批准&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; hook&lt;/span&gt;&lt;span&gt; approvals&lt;/span&gt;&lt;span&gt; clear&lt;/span&gt;&lt;span&gt; --global&lt;/span&gt;&lt;span&gt;  # 清除全局批准&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;用户级 Hooks&lt;a href=&quot;#用户级-hooks&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;~/.config/worktrunk/config.toml&lt;/code&gt; 中配置的 hook 会对所有仓库生效,且不需要批准:&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;项目 hook&lt;/th&gt;&lt;th&gt;用户 hook&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;配置位置&lt;/td&gt;&lt;td&gt;&lt;code&gt;.config/wt.toml&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;~/.config/worktrunk/config.toml&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;作用范围&lt;/td&gt;&lt;td&gt;单个仓库&lt;/td&gt;&lt;td&gt;所有仓库&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;需要批准&lt;/td&gt;&lt;td&gt;是&lt;/td&gt;&lt;td&gt;否&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;执行顺序&lt;/td&gt;&lt;td&gt;后执行&lt;/td&gt;&lt;td&gt;先执行&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;我觉得看下来，这个 hook 非常重要，可以&lt;strong&gt;复用主 worktree 的缓存&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;post-create&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 软链接 node_modules 避免重复安装&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cache = &lt;/span&gt;&lt;span&gt;&quot;ln -sf {{ repo_root }}/node_modules node_modules&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;env = &lt;/span&gt;&lt;span&gt;&quot;cp {{ repo_root }}/.env.local .env&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;独立运行 Hooks&lt;a href=&quot;#独立运行-hooks&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;除了自动触发,也可以手动运行 hook 进行测试或重试:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; hook&lt;/span&gt;&lt;span&gt; pre-merge&lt;/span&gt;&lt;span&gt;           # 运行 pre-merge hooks&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; hook&lt;/span&gt;&lt;span&gt; pre-merge&lt;/span&gt;&lt;span&gt; --yes&lt;/span&gt;&lt;span&gt;     # 跳过批准提示(适用于 CI)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; hook&lt;/span&gt;&lt;span&gt; show&lt;/span&gt;&lt;span&gt;                # 查看所有已配置的 hooks&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;: Hook 开发测试、CI 流水线、失败后重试等。&lt;/p&gt;
&lt;h3&gt;Claude Code 插件监测状态&lt;a href=&quot;#claude-code-插件监测状态&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;span&gt; plugin&lt;/span&gt;&lt;span&gt; marketplace&lt;/span&gt;&lt;span&gt; add&lt;/span&gt;&lt;span&gt; max-sixty/worktrunk&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;span&gt; plugin&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; worktrunk@worktrunk&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;手动设置状态标记&lt;a href=&quot;#手动设置状态标记&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;除了自动追踪 Claude 状态,你也可以手动给 worktree 设置任何标记，用于其他工作流：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 给当前分支设置标记&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span&gt; state&lt;/span&gt;&lt;span&gt; marker&lt;/span&gt;&lt;span&gt; set&lt;/span&gt;&lt;span&gt; &quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 给指定分支设置标记&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span&gt; state&lt;/span&gt;&lt;span&gt; marker&lt;/span&gt;&lt;span&gt; set&lt;/span&gt;&lt;span&gt; &quot;✅&quot;&lt;/span&gt;&lt;span&gt; --branch&lt;/span&gt;&lt;span&gt; feature&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 直接操作 Git Config (高级用法)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span&gt; worktrunk.state.feature.marker&lt;/span&gt;&lt;span&gt; &apos;{&quot;marker&quot;:&quot;&quot;,&quot;set_at&quot;:0}&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Statusline 状态栏&lt;a href=&quot;#statusline-状态栏&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;wt list statusline --claude-code&lt;/code&gt; 可以输出一行紧凑的状态信息,用于 Claude Code 的状态栏显示:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;~/w/myproject.feature-auth ! @+42 -8 ↑3 ⇡1 ● | Opus&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;LLM Commit Messages 自动生成提交信息&lt;a href=&quot;#llm-commit-messages-自动生成提交信息&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://worktrunk.dev/llm-commits/&quot;&gt;LLM Commit Messages | Worktrunk&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Worktrunk 可以调用 LLM 自动生成 commit message，集成在 &lt;code&gt;wt merge&lt;/code&gt;、&lt;code&gt;wt step commit&lt;/code&gt;、&lt;code&gt;wt step squash&lt;/code&gt; 等命令中。&lt;/p&gt;
&lt;p&gt;不配置也能用，默认会基于文件名生成简单的 message，但配置 LLM 后能生成更有意义的提交信息。&lt;/p&gt;
&lt;h3&gt;安装与配置&lt;a href=&quot;#安装与配置-1&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 安装 llm 工具&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用的是 &lt;a href=&quot;https://llm.datasette.io/&quot;&gt;llm&lt;/a&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;uv&lt;/span&gt;&lt;span&gt; tool&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; -U&lt;/span&gt;&lt;span&gt; llm&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 或者直接 brew&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; llm&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 配置 API Key&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;选择你喜欢的 LLM 提供商：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 使用 Claude (推荐)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;llm&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; llm-anthropic&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;llm&lt;/span&gt;&lt;span&gt; keys&lt;/span&gt;&lt;span&gt; set&lt;/span&gt;&lt;span&gt; anthropic&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 输入你的 Anthropic API Key&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 或者使用 OpenAI&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;llm&lt;/span&gt;&lt;span&gt; keys&lt;/span&gt;&lt;span&gt; set&lt;/span&gt;&lt;span&gt; openai&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 输入你的 OpenAI API Key&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. 配置 Worktrunk&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;创建配置文件（如果还没有）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span&gt; create&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编辑 &lt;code&gt;~/.config/worktrunk/config.toml&lt;/code&gt;，添加：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;commit-generation&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;command = &lt;/span&gt;&lt;span&gt;&quot;llm&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;args = [&lt;/span&gt;&lt;span&gt;&quot;-m&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;claude-3-5-haiku-latest&quot;&lt;/span&gt;&lt;span&gt;]  &lt;/span&gt;&lt;span&gt;# 或 gpt-4o-mini&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;使用场景&lt;a href=&quot;#使用场景&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;配置完成后，以下命令会自动生成 commit message：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;场景 1: 合并分支时生成 squash commit&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; wt&lt;/span&gt;&lt;span&gt; merge&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;◎&lt;/span&gt;&lt;span&gt; Squashing&lt;/span&gt;&lt;span&gt; 3&lt;/span&gt;&lt;span&gt; commits&lt;/span&gt;&lt;span&gt; into&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;span&gt; single&lt;/span&gt;&lt;span&gt; commit&lt;/span&gt;&lt;span&gt; (5 &lt;/span&gt;&lt;span&gt;files,&lt;/span&gt;&lt;span&gt; +48&lt;/span&gt;&lt;span&gt;)...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;◎&lt;/span&gt;&lt;span&gt; Generating&lt;/span&gt;&lt;span&gt; squash&lt;/span&gt;&lt;span&gt; commit&lt;/span&gt;&lt;span&gt; message...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   feat(auth&lt;/span&gt;&lt;span&gt;): Implement JWT authentication system&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   -&lt;/span&gt;&lt;span&gt; Add&lt;/span&gt;&lt;span&gt; JWT&lt;/span&gt;&lt;span&gt; token&lt;/span&gt;&lt;span&gt; generation&lt;/span&gt;&lt;span&gt; and&lt;/span&gt;&lt;span&gt; validation&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   -&lt;/span&gt;&lt;span&gt; Implement&lt;/span&gt;&lt;span&gt; refresh&lt;/span&gt;&lt;span&gt; token&lt;/span&gt;&lt;span&gt; mechanism&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   -&lt;/span&gt;&lt;span&gt; Add&lt;/span&gt;&lt;span&gt; middleware&lt;/span&gt;&lt;span&gt; for&lt;/span&gt;&lt;span&gt; protected&lt;/span&gt;&lt;span&gt; routes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;场景 2: 提交当前变更&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; wt&lt;/span&gt;&lt;span&gt; step&lt;/span&gt;&lt;span&gt; commit&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;◎&lt;/span&gt;&lt;span&gt; Generating&lt;/span&gt;&lt;span&gt; commit&lt;/span&gt;&lt;span&gt; message...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   fix(api&lt;/span&gt;&lt;span&gt;): Handle null response in user endpoint&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;场景 3: 把多个 commit 压缩成一个&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; wt&lt;/span&gt;&lt;span&gt; step&lt;/span&gt;&lt;span&gt; squash&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;◎&lt;/span&gt;&lt;span&gt; Squashing&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt; commits...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;◎&lt;/span&gt;&lt;span&gt; Generating&lt;/span&gt;&lt;span&gt; squash&lt;/span&gt;&lt;span&gt; commit&lt;/span&gt;&lt;span&gt; message...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   refactor(ui&lt;/span&gt;&lt;span&gt;): Modernize component architecture&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;使用其他 AI 工具&lt;a href=&quot;#使用其他-ai-工具&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;除了 &lt;code&gt;llm&lt;/code&gt;，任何能从 stdin 读取提示词并输出文本的工具都行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 使用 aichat&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;commit-generation&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;command = &lt;/span&gt;&lt;span&gt;&quot;aichat&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;args = [&lt;/span&gt;&lt;span&gt;&quot;-m&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;claude:claude-3-5-haiku-latest&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 使用自定义脚本&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;commit-generation&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;command = &lt;/span&gt;&lt;span&gt;&quot;./scripts/generate-commit.sh&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Fallback 行为&lt;a href=&quot;#fallback-行为&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;如果没配置 LLM，Worktrunk 会生成基于文件名的简单 message：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Changes to auth.rs &amp;amp; config.rs&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;CI 状态集成&lt;a href=&quot;#ci-状态集成&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;如果你用 GitHub Actions,Worktrunk 可以在 &lt;code&gt;wt list&lt;/code&gt; 里显示构建状态:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 配置 GitHub Token&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span&gt; github.token&lt;/span&gt;&lt;span&gt; &quot;ghp_xxxxxxxxxxxx&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 之后 wt list 就会显示 CI 状态&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; list&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;实战工作流&lt;a href=&quot;#实战工作流&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;并行跑多个 Claude Code&lt;a href=&quot;#并行跑多个-claude-code&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;假设你在用 &lt;a href=&quot;https://zellij.dev/&quot;&gt;Zellij&lt;/a&gt; 或 Tmux 管理多个终端窗口:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Pane 1: 修 bug&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; switch&lt;/span&gt;&lt;span&gt; -c&lt;/span&gt;&lt;span&gt; -x&lt;/span&gt;&lt;span&gt; claude&lt;/span&gt;&lt;span&gt; fix-login-bug&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# Pane 2: 重构&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; switch&lt;/span&gt;&lt;span&gt; -c&lt;/span&gt;&lt;span&gt; -x&lt;/span&gt;&lt;span&gt; claude&lt;/span&gt;&lt;span&gt; refactor-utils&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# Pane 3: 新功能&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; switch&lt;/span&gt;&lt;span&gt; -c&lt;/span&gt;&lt;span&gt; -x&lt;/span&gt;&lt;span&gt; claude&lt;/span&gt;&lt;span&gt; feat-export&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每个 Claude 在独立目录里工作,你可以随时切回主目录看全局状态:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; switch&lt;/span&gt;&lt;span&gt; main&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; list&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;临时验证想法&lt;a href=&quot;#临时验证想法&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;有时候你想快速验证一个想法,但不想污染当前分支:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 创建临时 worktree&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; switch&lt;/span&gt;&lt;span&gt; -c&lt;/span&gt;&lt;span&gt; -x&lt;/span&gt;&lt;span&gt; code&lt;/span&gt;&lt;span&gt; experiment&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 验证完毕,直接删掉&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; remove&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为 worktree 是独立目录,删掉不会影响其他分支。&lt;/p&gt;
&lt;h3&gt;Review PR 时跑代码&lt;a href=&quot;#review-pr-时跑代码&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;有 PR 来了,你想在本地跑一下代码:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 基于 PR 分支创建 worktree&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; switch&lt;/span&gt;&lt;span&gt; -c&lt;/span&gt;&lt;span&gt; review-pr-123&lt;/span&gt;&lt;span&gt; -b&lt;/span&gt;&lt;span&gt; origin/pr-123&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 装依赖、跑测试&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; run&lt;/span&gt;&lt;span&gt; test&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# Review 完毕,清理&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;wt&lt;/span&gt;&lt;span&gt; remove&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;参考资料&lt;a href=&quot;#参考资料&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://worktrunk.dev/&quot;&gt;Worktrunk 官网&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.anthropic.com/engineering/claude-code-best-practices&quot;&gt;Anthropic - Claude Code Best Practices&lt;/a&gt; (官方推荐的 worktree 工作流)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://incident.io/blog/shipping-faster-with-claude-code-and-git-worktrees&quot;&gt;incident.io - Shipping faster with Claude Code and Git Worktrees&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/git-worktree&quot;&gt;Git Worktree 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>category:工具</category><category>tag:ClaudeCode</category><category>tag:AI</category><category>tag:Git</category></item><item><title>astro-koharu 使用指南</title><link>https://blog.cosine.ren/post/astro-koharu-guide</link><guid isPermaLink="false">astro-koharu-guide</guid><description>astro-koharu 博客的完整使用指南，包含快速开始、配置说明、文章系统、界面功能等详细介绍</description><pubDate>Mon, 29 Dec 2025 13:55:00 GMT</pubDate><content:encoded>&lt;p&gt;一份完整的 astro-koharu 博客系统使用指南，帮助你快速上手并充分利用所有功能特性。&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://github.com/cosZone/astro-koharu&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://github.com/fluidicon.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;github.com&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;GitHub - cosZone/astro-koharu: astro-koharu 是一个萌系/二次元/粉蓝配色的 astro 主题博客，灵感来自 Hexo 的 Shoka 主题，加了很多自己的小巧思，性能优越。&lt;/h3&gt;
        &lt;p&gt;astro-koharu 是一个萌系/二次元/粉蓝配色的 astro 主题博客，灵感来自 Hexo 的 Shoka 主题，加了很多自己的小巧思，性能优越。 - cosZone/astro-koharu&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://github.com/cosZone/astro-koharu&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://opengraph.githubassets.com/5e01917353e07c35ffbae3d1f2972b649677cbb216b3eb75fdf2eb455ca1304d/cosZone/astro-koharu&quot; alt=&quot;GitHub - cosZone/astro-koharu: astro-koharu 是一个萌系/二次元/粉蓝配色的 astro 主题博客，灵感来自 Hexo 的 Shoka 主题，加了很多自己的小巧思，性能优越。&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;h2&gt;快速开始&lt;a href=&quot;#快速开始&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;项目简介&lt;a href=&quot;#项目简介&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;astro-koharu 是一个基于 Astro 5.x 构建的现代化博客系统，从 Hexo 迁移而来，设计灵感和初衷都来自 &lt;a href=&quot;https://github.com/amehime/hexo-theme-shoka&quot;&gt;Shoka&lt;/a&gt; 主题。欢迎 &lt;a href=&quot;https://github.com/cosZone/astro-koharu/fork&quot;&gt;fork&lt;/a&gt; 出来制作自己的主题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心特点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;基于 Astro 5.x，静态站点生成，性能优异&lt;/li&gt;
&lt;li&gt;优雅的深色/浅色主题切换&lt;/li&gt;
&lt;li&gt;基于 Pagefind 的无后端全站搜索&lt;/li&gt;
&lt;li&gt;完整的 Markdown 增强功能（GFM、代码高亮、自动目录）&lt;/li&gt;
&lt;li&gt;灵活的多级分类与标签系统（从 Shoka 主题迁移，后续会考虑将其改为可关闭的）&lt;/li&gt;
&lt;li&gt;多系列文章支持（周刊、书摘等自定义系列，支持自定义 URL slug）&lt;/li&gt;
&lt;li&gt;响应式设计&lt;/li&gt;
&lt;li&gt;草稿与置顶功能&lt;/li&gt;
&lt;li&gt;阅读进度条与阅读时间估算&lt;/li&gt;
&lt;li&gt;移动端文章阅读头部&lt;/li&gt;
&lt;li&gt;友链系统与归档页面&lt;/li&gt;
&lt;li&gt;多语言支持（i18n）&lt;/li&gt;
&lt;li&gt;RSS 订阅支持&lt;/li&gt;
&lt;li&gt;LQIP（低质量图片占位符）&lt;/li&gt;
&lt;li&gt;圣诞特辑（可开关）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;本地开发&lt;a href=&quot;#本地开发&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 克隆项目&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; clone&lt;/span&gt;&lt;span&gt; https://github.com/cosZone/astro-koharu.git&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; astro-koharu&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 安装依赖&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 启动开发服务器&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; dev&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 构建生产版本&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; build&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 预览生产构建&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; preview&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;快速部署&lt;a href=&quot;#快速部署&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 Vercel 进行一键部署：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://vercel.com/new/clone?repository-url=https://github.com/cosZone/astro-koharu&amp;amp;project-name=astro-koharu&amp;amp;repository-name=astro-koharu&quot;&gt;&lt;img src=&quot;https://vercel.com/button&quot; alt=&quot;Deploy with Vercel&quot; loading=&quot;lazy&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;基本配置&lt;a href=&quot;#基本配置&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;站点配置&lt;a href=&quot;#站点配置&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;编辑 &lt;code&gt;config/site.yaml&lt;/code&gt; 文件配置站点基本信息：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# =============================================================================&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 站点基础信息&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# =============================================================================&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;site&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;余弦の博客&lt;/span&gt;&lt;span&gt; # 网站标题&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  alternate&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;cosine&lt;/span&gt;&lt;span&gt; # 英文短名（用作 logo 文本）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  subtitle&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;WA 的一声就哭了&lt;/span&gt;&lt;span&gt; # 副标题&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;cos&lt;/span&gt;&lt;span&gt; # 站点作者简称&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;FE / ACG / 手工 / 深色模式强迫症 / INFP&lt;/span&gt;&lt;span&gt; # 站点简介&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  avatar&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;/img/avatar.webp&lt;/span&gt;&lt;span&gt; # 头像路径&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  showLogo&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; # 是否显示 logo&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  author&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;cos&lt;/span&gt;&lt;span&gt; # 文章作者&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  url&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;https://blog.cosine.ren/&lt;/span&gt;&lt;span&gt; # 站点域名&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  startYear&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2020&lt;/span&gt;&lt;span&gt; # 站点创建年份&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  keywords&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;# SEO 关键词&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;cos&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;cosine&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;博客&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;技术&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;前端&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;本地轻 CMS 应用&lt;a href=&quot;#本地轻-cms-应用&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;本项目提供独立的 CMS 管理应用，支持文章管理、浏览器内编辑、Markdown 预览等功能。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/a1c1d69ef48c758010e553e882e470db.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/6c6956e3b49729ddf272669f3f738f13.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2026/01/1d86afe19ed2fe921990657685393c2d.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;启动 CMS：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 首次使用需安装依赖&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; cms:install&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 启动 CMS（默认端口 4322）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; cms&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CMS 提供以下功能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt; 文章仪表盘：查看文章统计、分类分布、最近更新&lt;/li&gt;
&lt;li&gt; 浏览器内编辑器：基于 BlockNote 的富文本编辑，支持 Markdown&lt;/li&gt;
&lt;li&gt; 草稿/发布切换：一键切换文章状态&lt;/li&gt;
&lt;li&gt; 置顶管理：快速置顶/取消置顶文章&lt;/li&gt;
&lt;li&gt;➕ 新建文章：交互式创建文章，自动生成 frontmatter&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;本地编辑器跳转&lt;a href=&quot;#本地编辑器跳转&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;文章页的编辑按钮支持一键跳转到本地编辑器（VS Code / Cursor / Zed 等）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;配置文件：&lt;/strong&gt; &lt;code&gt;config/site.yaml&lt;/code&gt; 的 &lt;code&gt;dev&lt;/code&gt; 部分&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;dev&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  localProjectPath&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;/Users/yourname/path/to/astro-koharu&quot;&lt;/span&gt;&lt;span&gt; # 本地项目绝对路径&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  contentRelativePath&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;src/content/blog&quot;&lt;/span&gt;&lt;span&gt; # 博客内容目录&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  editors&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;vscode&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;VS Code&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;devicon-plain:vscode&lt;/span&gt;&lt;span&gt; # 可从 https://icon-sets.iconify.design/ 搜寻图标&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      urlTemplate&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;vscode://file{path}&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Cursor&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;simple-icons:cursor&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      urlTemplate&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;cursor://file{path}&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;zed&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Zed&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;simple-icons:zedindustries&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      urlTemplate&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;zed://file{path}&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;配置说明：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;localProjectPath&lt;/code&gt; 必须是本机的绝对路径，否则无法生成正确的文件路径&lt;/li&gt;
&lt;li&gt;&lt;code&gt;urlTemplate&lt;/code&gt; 支持 &lt;code&gt;&lt;/code&gt; 占位符，会被替换为文件的完整路径&lt;/li&gt;
&lt;li&gt;配置后，文章页会显示编辑按钮，点击可直接在本地编辑器中打开文件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;特色分类配置：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在首页底部展示的精选分类卡片：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;featuredCategories&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;link&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;life&lt;/span&gt;&lt;span&gt; # 分类链接（对应 category_map）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;随笔&lt;/span&gt;&lt;span&gt; # 显示名称&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    image&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;/img/cover/2.webp&lt;/span&gt;&lt;span&gt; # 封面图片&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;生活记录、年度总结等&lt;/span&gt;&lt;span&gt; # 描述&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;link&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;note/front-end&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;前端笔记&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    image&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;/img/cover/1.webp&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;前端相关的笔记&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  # ... 更多分类&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;多系列文章配置：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;配置特色系列（如周刊、书摘等），支持多个系列，每个系列拥有独立页面和自定义 URL：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;featuredSeries&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;slug&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;weekly&lt;/span&gt;&lt;span&gt; # URL 路径: /weekly（必填，作为页面路由）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    categoryName&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;周刊&lt;/span&gt;&lt;span&gt; # 分类名称（用于匹配文章）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;FE Bits&lt;/span&gt;&lt;span&gt; # 显示标签&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fullName&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;FE Bits 前端周周谈&lt;/span&gt;&lt;span&gt; # 完整名称&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; # 描述（支持多行）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      之前在自己的频道进行一些输出，于是有了这个周刊！&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      更新时间期望是在每周天&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    cover&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;/img/weekly_header.webp&lt;/span&gt;&lt;span&gt; # 封面图&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    enabled&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; # 是否启用&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    icon&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ri:newspaper-line&lt;/span&gt;&lt;span&gt; # 导航图标（可选）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    highlightOnHome&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; # 是否在首页高亮最新文章（可选，默认 true）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    links&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;# 相关链接&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      github&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;https://github.com/your-username/your-repo&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      rss&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;/rss.xml&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;slug&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;reading&lt;/span&gt;&lt;span&gt; # URL 路径: /reading&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    categoryName&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;书摘&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;读书笔记&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fullName&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;我的读书笔记&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;读书摘录与感悟&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    cover&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;/img/reading_header.webp&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    enabled&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    highlightOnHome&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; # 此系列不在首页高亮&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;字段说明：&lt;/strong&gt;&lt;/p&gt;




























































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;字段&lt;/th&gt;&lt;th&gt;必填&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;slug&lt;/code&gt;&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;td&gt;URL 路径，如 &lt;code&gt;weekly&lt;/code&gt; 对应 &lt;code&gt;/weekly&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;categoryName&lt;/code&gt;&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;td&gt;分类名称，用于匹配文章&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;label&lt;/code&gt;&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;td&gt;显示标签（默认使用 categoryName）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;enabled&lt;/code&gt;&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;td&gt;是否启用此系列（默认 true）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;fullName&lt;/code&gt;&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;td&gt;完整名称（用于页面标题）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;description&lt;/code&gt;&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;td&gt;系列描述&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;cover&lt;/code&gt;&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;td&gt;封面图片路径&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;icon&lt;/code&gt;&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;td&gt;导航图标（Iconify 格式）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;highlightOnHome&lt;/code&gt;&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;td&gt;是否在首页高亮最新文章（默认 true）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;links&lt;/code&gt;&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;td&gt;相关链接（github、rss 等）&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;社交媒体配置&lt;a href=&quot;#社交媒体配置&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;config/site.yaml&lt;/code&gt; 中配置社交媒体链接：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;social&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  github&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    url&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;https://github.com/your-username&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    icon&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ri:github-fill&lt;/span&gt;&lt;span&gt; # Iconify 图标名&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;#191717&quot;&lt;/span&gt;&lt;span&gt; # 主题色&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  bilibili&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    url&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;https://space.bilibili.com/your-uid&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    icon&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ri:bilibili-fill&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;#da708a&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  email&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    url&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;mailto:your@email.com&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    icon&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ri:mail-line&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;#55acd5&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  rss&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    url&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;/rss.xml&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    icon&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ri:rss-line&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;#ff6600&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  # ... 更多平台&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;支持的平台：GitHub, Twitter, Bilibili, 网易云音乐, Email, RSS 等。完整配置请参考 &lt;code&gt;config/site.yaml&lt;/code&gt; 文件。&lt;/p&gt;
&lt;h3&gt;导航配置&lt;a href=&quot;#导航配置&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;config/site.yaml&lt;/code&gt; 中自定义导航菜单：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;navigation&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;首页&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    path&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    icon&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;fa6-solid:house-chimney&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;周刊&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    path&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;/weekly&lt;/span&gt;&lt;span&gt; # 对应 featuredSeries 中 slug: weekly 的系列&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    icon&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ri:newspaper-line&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;读书笔记&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    path&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;/reading&lt;/span&gt;&lt;span&gt; # 对应 featuredSeries 中 slug: reading 的系列&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    icon&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ri:book-open-line&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;文章&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    icon&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ri:quill-pen-ai-fill&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    children&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;# 支持嵌套子菜单&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;分类&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        path&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;/categories&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        icon&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ri:grid-fill&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;标签&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        path&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;/tags&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        icon&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;fa6-solid:tags&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;归档&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        path&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;/archives&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        icon&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ri:archive-2-fill&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;友链&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    path&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;/friends&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    icon&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ri:links-line&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;关于&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    path&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;/about&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    icon&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;fa6-regular:circle-user&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：系列页面的路径格式为 &lt;code&gt;/{slug}&lt;/code&gt;，需要与 &lt;code&gt;featuredSeries&lt;/code&gt; 中配置的 &lt;code&gt;slug&lt;/code&gt; 字段一致。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;分类映射配置&lt;a href=&quot;#分类映射配置&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;config/site.yaml&lt;/code&gt; 中配置中文分类名到 URL slug 的映射：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# =============================================================================&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# Category Map&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# Maps Chinese category names to URL-friendly English slugs&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# =============================================================================&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;categoryMap&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  # Primary categories&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  随笔&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;life&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  笔记&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  工具&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;tools&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  周刊&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;weekly&lt;/span&gt;&lt;span&gt; # 用于分类页面 /categories/weekly&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  书摘&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;reading&lt;/span&gt;&lt;span&gt; # 用于分类页面 /categories/reading&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  # Secondary categories (for nested paths)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  前端&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;front-end&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  # Add more as needed:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  # 后端: back-end&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  # 算法: algorithm&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样，&quot;随笔&quot; 分类的 URL 会是 &lt;code&gt;/categories/life&lt;/code&gt;，而不是 &lt;code&gt;/categories/随笔&lt;/code&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：&lt;code&gt;categoryMap&lt;/code&gt; 仅用于分类页面（&lt;code&gt;/categories/*&lt;/code&gt;）的 URL 映射。系列页面的 URL（如 &lt;code&gt;/weekly&lt;/code&gt;、&lt;code&gt;/reading&lt;/code&gt;）由 &lt;code&gt;featuredSeries&lt;/code&gt; 中的 &lt;code&gt;slug&lt;/code&gt; 字段单独配置。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;文章系统&lt;a href=&quot;#文章系统&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;创建文章&lt;a href=&quot;#创建文章&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;方式一：使用 Koharu CLI（推荐）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用交互式 CLI 工具快速创建文章：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; post&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CLI 工具会引导你输入标题、分类、标签等信息，自动生成 frontmatter 和 markdown 文件。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;方式二：手动创建&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;src/content/blog/&lt;/code&gt; 目录下创建 Markdown 文件。目录结构会影响文章的分类：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;src/content/blog/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── life/              # 随笔分类&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   └── 2024-life-review.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── note/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── front-end/     # 笔记 &amp;gt; 前端&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   └── react/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │       └── React学习小记.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   └── algorithm/     # 笔记 &amp;gt; 算法&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│       └── 动态规划学习笔记.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└── tools/             # 工具分类&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── vscode插件推荐.md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Frontmatter 字段说明&lt;a href=&quot;#frontmatter-字段说明&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;每篇文章开头需要包含 YAML frontmatter：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;必填字段：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;文章标题&lt;/span&gt;&lt;span&gt; # 必填&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2024-12-06&lt;/span&gt;&lt;span&gt; # 必填，发布日期&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;常用可选字段：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;文章标题&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2024-12-06&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;updated&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2024-12-15&lt;/span&gt;&lt;span&gt; # 最近更新时间（可选，存在时会在文章页显示）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;文章摘要描述&lt;/span&gt;&lt;span&gt; # 用于 SEO 和列表展示，如不填写会自动使用 AI 摘要或提取正文前 150 字&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;link&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;custom-url-slug&lt;/span&gt;&lt;span&gt; # 自定义 URL（默认使用文件名）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cover&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;/img/cover/1.webp&lt;/span&gt;&lt;span&gt; # 封面图片&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;tags&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;# 标签列表&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;JavaScript&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;React&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;categories&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;# 分类（见下方详细说明）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;笔记&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;subtitle&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;副标题&lt;/span&gt;&lt;span&gt; # 文章副标题&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;catalog&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; # 是否显示目录（默认 true）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;tocNumbering&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; # 是否显示目录编号（默认 true）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;draft&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; # 是否为草稿（默认 false）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;sticky&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; # 是否置顶（默认 false）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;excludeFromSummary&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; # 是否排除 AI 摘要和相似度计算（默认 false，系列文章建议设为 true）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;math&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; # 是否启用数学公式渲染（默认 false，启用后支持 KaTeX）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;quiz&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; # 是否启用练习题交互（默认 false，启用后支持四种题型）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;password&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;mySecret&lt;/span&gt;&lt;span&gt; # 整篇文章加密密码（可选，设置后整篇文章需输入密码才能阅读）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;关于 description 字段：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;文章描述的优先级：手写 &lt;code&gt;description&lt;/code&gt; &amp;gt; AI 自动摘要 &amp;gt; Markdown 正文前 150 字&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;建议为重要文章手写描述，以获得更好的 SEO 效果&lt;/li&gt;
&lt;li&gt;如果省略描述，系统会自动使用 AI 生成的摘要（需运行 &lt;code&gt;pnpm generate:summaries&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;如果既没有手写描述也没有 AI 摘要，则自动提取文章正文的前 150 个字符&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;关于 link 字段（自定义 URL）：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;⚠️ &lt;strong&gt;重要&lt;/strong&gt;：&lt;code&gt;link&lt;/code&gt; 字段会被&lt;strong&gt;自动转换为小写&lt;/strong&gt;，以保持 URL 的一致性和规范性。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;规范化行为&lt;/strong&gt;：无论你输入 &lt;code&gt;MyPost&lt;/code&gt;、&lt;code&gt;myPost&lt;/code&gt; 还是 &lt;code&gt;mypost&lt;/code&gt;，最终 URL 都会是 &lt;code&gt;/post/mypost&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;文件名大小写无关&lt;/strong&gt;：文章文件名可以使用任意大小写（如 &lt;code&gt;MyPost.md&lt;/code&gt;），系统会自动处理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI 摘要和相似度&lt;/strong&gt;：生成的 &lt;code&gt;summaries.json&lt;/code&gt; 和 &lt;code&gt;similarities.json&lt;/code&gt; 中的 key 也会统一为小写&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最佳实践&lt;/strong&gt;：建议直接使用小写和连字符（如 &lt;code&gt;my-awesome-post&lt;/code&gt;），避免混淆&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# ✅ 推荐写法&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;link&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;my-awesome-post&lt;/span&gt;&lt;span&gt;  # URL: /post/my-awesome-post&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# ⚠️ 会被转为小写&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;link&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;MyAwesomePost&lt;/span&gt;&lt;span&gt;    # URL: /post/myawesomepost（不是 /post/MyAwesomePost）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;link&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;My-Awesome-Post&lt;/span&gt;&lt;span&gt;  # URL: /post/my-awesome-post&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果省略 &lt;code&gt;link&lt;/code&gt; 字段，系统会使用文件名（同样会转为小写）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 文件: src/content/blog/MyPost.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 省略 link 字段 → URL: /post/mypost&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;分类系统&lt;a href=&quot;#分类系统&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;astro-koharu 支持灵活的分类配置：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;单层分类：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;categories&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;工具&lt;/span&gt;&lt;span&gt; # 或者 [&apos;工具&apos;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应 URL: &lt;code&gt;/categories/tools&lt;/code&gt;（根据 &lt;code&gt;categoryMap&lt;/code&gt; 映射）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;多层嵌套分类：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;categories&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - [&lt;/span&gt;&lt;span&gt;笔记&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;前端&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;React&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这会创建层级关系：笔记 → 前端 → React&lt;/p&gt;
&lt;p&gt;对应 URL: &lt;code&gt;/categories/note/front-end/react&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;标签系统&lt;a href=&quot;#标签系统&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;标签是扁平的，不支持层级：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;tags&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;JavaScript&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;TypeScript&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;学习笔记&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所有标签会在 &lt;code&gt;/tags&lt;/code&gt; 页面展示，点击标签可查看该标签下的所有文章。&lt;/p&gt;
&lt;h3&gt;草稿功能&lt;a href=&quot;#草稿功能&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;设置 &lt;code&gt;draft: true&lt;/code&gt; 将文章标记为草稿：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;未完成的文章&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;draft&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;行为：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;本地开发&lt;/strong&gt; (&lt;code&gt;pnpm dev&lt;/code&gt;)：草稿可见，文章卡片右上角显示 &quot;DRAFT&quot; 标识&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生产构建&lt;/strong&gt; (&lt;code&gt;pnpm build&lt;/code&gt;)：草稿自动过滤，不会出现在任何列表中&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;置顶功能&lt;a href=&quot;#置顶功能&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;设置 &lt;code&gt;sticky: true&lt;/code&gt; 将文章置顶：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;重要公告&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;sticky&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;行为：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;置顶文章显示在首页 &quot;置顶文章&quot; 区域&lt;/li&gt;
&lt;li&gt;置顶文章按日期排序（最新的在前）&lt;/li&gt;
&lt;li&gt;不影响其他页面（分类、标签、归档）的排序&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;系列文章&lt;a href=&quot;#系列文章&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;配置了 &lt;code&gt;featuredSeries&lt;/code&gt; 的系列（见基本配置），其分类下的文章会：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;拥有专属的系列页面（URL 由 &lt;code&gt;slug&lt;/code&gt; 决定，如 &lt;code&gt;/weekly&lt;/code&gt;、&lt;code&gt;/reading&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;不出现在普通文章列表（&lt;code&gt;/posts&lt;/code&gt;）中&lt;/li&gt;
&lt;li&gt;如果系列设置了 &lt;code&gt;highlightOnHome: true&lt;/code&gt;，最新一篇会在首页高亮显示&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;FE Bits Vol.16&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;categories&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;周刊&lt;/span&gt;&lt;span&gt; # 对应某个 featuredSeries 的 categoryName&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;excludeFromSummary&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; # 可选：排除 AI 摘要生成&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;《代码大全》读书笔记&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;categories&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;书摘&lt;/span&gt;&lt;span&gt; # 对应另一个 featuredSeries 的 categoryName&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示&lt;/strong&gt;：文章的 &lt;code&gt;categories&lt;/code&gt; 字段需要与 &lt;code&gt;featuredSeries&lt;/code&gt; 中某个系列的 &lt;code&gt;categoryName&lt;/code&gt; 匹配才会被归入该系列。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;独立页面&lt;a href=&quot;#独立页面&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;除了博客文章外，你可以在 &lt;code&gt;src/pages/&lt;/code&gt; 目录下创建 &lt;code&gt;.md&lt;/code&gt; 文件来添加独立页面（如&quot;关于&quot;、&quot;歌单&quot;等）。这些页面使用 &lt;code&gt;PageLayout.astro&lt;/code&gt; 布局，支持完整的 Markdown 增强语法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;创建独立页面：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;src/pages/&lt;/code&gt; 目录下新建 &lt;code&gt;.md&lt;/code&gt; 文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;layout&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;../layouts/PageLayout.astro&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;歌单&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;我喜欢的音乐&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;coverTitle&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;我的歌单&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;comments&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;页面内容...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Frontmatter 字段：&lt;/strong&gt;&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;字段&lt;/th&gt;&lt;th&gt;必填&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;layout&lt;/code&gt;&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;td&gt;固定为 &lt;code&gt;../layouts/PageLayout.astro&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;td&gt;页面标题（用于浏览器标签页）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;description&lt;/code&gt;&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;td&gt;页面描述（用于 SEO）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;coverTitle&lt;/code&gt;&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;td&gt;封面显示的标题（默认使用 &lt;code&gt;title&lt;/code&gt;）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;comments&lt;/code&gt;&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;td&gt;是否显示评论区（默认 &lt;code&gt;true&lt;/code&gt;）&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;添加导航入口：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;config/site.yaml&lt;/code&gt; 的 &lt;code&gt;navigation&lt;/code&gt; 中添加对应菜单项：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;navigation&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  # ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;歌单&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    path&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;/music&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    icon&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ri:music-2-fill&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示&lt;/strong&gt;：&lt;code&gt;src/pages/&lt;/code&gt; 下所有 &lt;code&gt;.md&lt;/code&gt; 文件会被 Koharu CLI 的备份功能自动覆盖，无需额外配置。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;界面功能&lt;a href=&quot;#界面功能&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;主题切换&lt;a href=&quot;#主题切换&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;点击右上角的太阳/月亮图标切换深色/浅色模式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;代码高亮：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;浅色模式：&lt;code&gt;github-light&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;深色模式：&lt;code&gt;github-dark&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;全站搜索&lt;a href=&quot;#全站搜索&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;基于 &lt;a href=&quot;https://pagefind.app/&quot;&gt;Pagefind&lt;/a&gt; 的静态站点搜索，无需后端服务器。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;打开搜索：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;点击导航栏搜索图标&lt;/li&gt;
&lt;li&gt;快捷键：&lt;code&gt;Cmd/Ctrl + K&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;特性：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;支持中文分词&lt;/li&gt;
&lt;li&gt;实时搜索结果&lt;/li&gt;
&lt;li&gt;高亮匹配关键词&lt;/li&gt;
&lt;li&gt;显示文章摘要和元信息&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;文章阅读功能&lt;a href=&quot;#文章阅读功能&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;目录导航 (Table of Contents)：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;自动提取文章标题（h2-h6）生成目录&lt;/li&gt;
&lt;li&gt;使用 CSS 计数器自动为标题添加层级编号（如 1., 1.1., 1.1.1.）&lt;/li&gt;
&lt;li&gt;支持通过 frontmatter 的 &lt;code&gt;tocNumbering: false&lt;/code&gt; 字段关闭编号显示&lt;/li&gt;
&lt;li&gt;点击目录项跳转到对应章节&lt;/li&gt;
&lt;li&gt;滚动时自动高亮当前章节&lt;/li&gt;
&lt;li&gt;桌面端显示在右侧边栏，移动端折叠&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;目录编号控制：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;我的文章&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;tocNumbering&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; # 关闭目录编号（默认为 true）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;默认情况下，所有文章的目录都会显示层级编号&lt;/li&gt;
&lt;li&gt;设置 &lt;code&gt;tocNumbering: false&lt;/code&gt; 可以关闭特定文章的编号显示&lt;/li&gt;
&lt;li&gt;编号通过 CSS 计数器实现，零运行时开销&lt;/li&gt;
&lt;li&gt;同时适用于桌面端侧边栏和移动端下拉目录&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;阅读进度条：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;页面顶部显示阅读进度&lt;/li&gt;
&lt;li&gt;实时更新当前阅读位置&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;标题锚点链接：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个标题自动生成 ID&lt;/li&gt;
&lt;li&gt;悬停标题时显示 &lt;code&gt;#&lt;/code&gt; 链接图标&lt;/li&gt;
&lt;li&gt;点击可复制带锚点的 URL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;系列文章导航：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;文章底部显示同系列的上一篇/下一篇：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;基于最深层分类自动分组&lt;/li&gt;
&lt;li&gt;按发布日期排序&lt;/li&gt;
&lt;li&gt;显示文章标题和封面&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;阅读时间估算：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;文章卡片显示预计阅读时间（基于字数计算）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;移动端文章阅读头部：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在移动端（≤992px）浏览文章时，顶部导航栏会显示专为阅读优化的头部：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;圆形阅读进度&lt;/strong&gt; - 实时显示当前阅读进度的圆形进度条&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;当前章节标题&lt;/strong&gt; - 显示当前所在的 H2/H3 章节标题，切换时带有平滑动画&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可展开目录&lt;/strong&gt; - 点击标题区域可展开完整的文章目录，快速跳转到任意章节&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;特性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;滚动时自动更新当前章节&lt;/li&gt;
&lt;li&gt;支持 &lt;code&gt;prefers-reduced-motion&lt;/code&gt; 减少动画&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;响应式设计&lt;a href=&quot;#响应式设计&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;桌面端：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;双栏布局（主内容 + 侧边栏）&lt;/li&gt;
&lt;li&gt;固定导航栏&lt;/li&gt;
&lt;li&gt;悬浮目录&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;平板：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;自适应布局调整&lt;/li&gt;
&lt;li&gt;简化侧边栏&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;移动端：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单栏布局&lt;/li&gt;
&lt;li&gt;抽屉式导航菜单（汉堡菜单）&lt;/li&gt;
&lt;li&gt;折叠式目录&lt;/li&gt;
&lt;li&gt;触摸优化的交互&lt;/li&gt;
&lt;li&gt;文章页专属阅读头部（进度圈 + 当前标题 + 可展开目录）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;特色功能&lt;a href=&quot;#特色功能&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;系列文章系统&lt;a href=&quot;#系列文章系统&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;featuredSeries&lt;/code&gt; 支持配置多个系列，每个系列会自动生成独立页面：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;专属系列页面&lt;/strong&gt; (&lt;code&gt;/{slug}&lt;/code&gt;)：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个启用的系列都有独立页面（如 &lt;code&gt;/weekly&lt;/code&gt;、&lt;code&gt;/reading&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;显示该系列的所有文章&lt;/li&gt;
&lt;li&gt;系列头图和介绍&lt;/li&gt;
&lt;li&gt;相关链接（GitHub, RSS 等）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;首页展示：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置 &lt;code&gt;highlightOnHome: true&lt;/code&gt; 的系列，其最新文章会在首页高亮显示&lt;/li&gt;
&lt;li&gt;设置 &lt;code&gt;highlightOnHome: false&lt;/code&gt; 的系列不在首页展示&lt;/li&gt;
&lt;li&gt;所有系列文章独立于普通文章列表&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt; &lt;strong&gt;设计说明：分离关注点&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;featuredSeries 的设计初衷是&lt;strong&gt;将高产出分类从首页分离&lt;/strong&gt;，避免首页被单一类型文章刷屏。适用场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;周刊/日记&lt;/strong&gt;：更新频繁，数量庞大&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;读书笔记/书摘&lt;/strong&gt;：独立成系列，方便按系列浏览&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;任何文章数量较多的分类&lt;/strong&gt;：当某分类文章数量远超其他分类时&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;首页行为&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;系列文章从首页主列表排除&lt;/li&gt;
&lt;li&gt;设置 &lt;code&gt;highlightOnHome: true&lt;/code&gt; 时，最新一篇在首页顶部高亮&lt;/li&gt;
&lt;li&gt;其余文章通过系列专属页面（如 &lt;code&gt;/weekly&lt;/code&gt;）访问&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;其他页面正常展示&lt;/strong&gt;：系列文章在归档、分类、标签、搜索等页面仍与普通文章一起显示，仅首页主列表做了分离。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;配置示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;featuredSeries&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;slug&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;weekly&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    categoryName&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;周刊&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    highlightOnHome&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; # 首页展示最新周刊&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    # ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;slug&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;reading&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    categoryName&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;书摘&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    highlightOnHome&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; # 不在首页展示&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    # ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;归档页面&lt;a href=&quot;#归档页面&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;访问 &lt;code&gt;/archives&lt;/code&gt; 查看所有文章的归档视图：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;按年份分组&lt;/li&gt;
&lt;li&gt;显示每年的文章数量&lt;/li&gt;
&lt;li&gt;时间线式展示&lt;/li&gt;
&lt;li&gt;包含文章发布日期、标题、分类&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;友链系统&lt;a href=&quot;#友链系统&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;访问 &lt;code&gt;/friends&lt;/code&gt; 查看友情链接页面：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;功能：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;友链卡片展示&lt;/li&gt;
&lt;li&gt;友链申请表单（可自定义）&lt;/li&gt;
&lt;li&gt;支持头像、名称、描述、链接&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;LQIP（低质量图片占位符）&lt;a href=&quot;#lqip低质量图片占位符&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;LQIP（Low Quality Image Placeholder）是一种图片加载优化技术，在高清图片加载完成前，先显示一个低质量的占位符，避免页面出现空白或布局抖动。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;特性：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt; 构建时自动提取图片主色调，生成 CSS 渐变占位符&lt;/li&gt;
&lt;li&gt;⚡ 零运行时开销 —— 纯 CSS 实现，无需 JavaScript 解码&lt;/li&gt;
&lt;li&gt; 极小数据体积 —— 每张图片仅需 18 字符存储&lt;/li&gt;
&lt;li&gt; 外部图片自动降级为纯色占位符&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;支持的组件：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文章卡片封面 (&lt;code&gt;PostItemCard&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;页面横幅 (&lt;code&gt;Cover&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;分类卡片背景 (&lt;code&gt;CategoryCards&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;系列封面 (&lt;code&gt;SeriesCover&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;侧边栏头像 (&lt;code&gt;HomeInfo&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;使用方式：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 生成 LQIP 数据（处理 public/img/ 下所有图片）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; generate:lqips&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;生成效果：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;LQIP 数据保存在 &lt;code&gt;src/assets/lqips.json&lt;/code&gt;，格式如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;cover/1.webp&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;87a3c4c2dfefbddae9&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;cover/2.webp&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;6e3b38ae7472af7574&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每个值是 18 个十六进制字符（3 个颜色），运行时解码为 CSS 渐变：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;linear-gradient(135deg, &lt;/span&gt;&lt;span&gt;#87a3c4&lt;/span&gt;&lt;span&gt; 0%, &lt;/span&gt;&lt;span&gt;#c2dfef&lt;/span&gt;&lt;span&gt; 50%, &lt;/span&gt;&lt;span&gt;#bddae9&lt;/span&gt;&lt;span&gt; 100%)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用 sharp 将图片缩放到 2×2 像素&lt;/li&gt;
&lt;li&gt;提取四象限的平均色（左上、右上、左下、右下）&lt;/li&gt;
&lt;li&gt;选取 3 个颜色生成 135 度斜向渐变&lt;/li&gt;
&lt;li&gt;存储为紧凑的十六进制字符串&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;在组件中使用：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { getLqipStyle, getLqipProps } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;@lib/lqip&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 方式 1：直接获取样式字符串&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; style&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; getLqipStyle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;/img/cover/1.webp&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 返回: &quot;background-image:linear-gradient(...)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 方式 2：获取完整的 props（支持外部图片降级）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; lqipProps&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; getLqipProps&lt;/span&gt;&lt;span&gt;(coverUrl);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 本地图片返回: { style: &quot;background-image:...&quot; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 外部图片返回: { class: &quot;lqip-fallback&quot; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; style&lt;/span&gt;&lt;span&gt;={style}&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/img/cover/1.webp&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意事项：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;生成的 &lt;code&gt;src/assets/lqips.json&lt;/code&gt; 需要提交到 git&lt;/li&gt;
&lt;li&gt;添加新图片后需要重新运行 &lt;code&gt;pnpm generate:lqips&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;外部图片（http/https 开头）会自动使用纯色占位符降级&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;相关文章推荐&lt;a href=&quot;#相关文章推荐&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;参考 &lt;a href=&quot;https://alexop.dev/posts/semantic-related-posts-astro-transformersjs/&quot;&gt;No Server, No Database: Smarter Related Posts in Astro with &lt;code&gt;transformers.js&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;基于语义相似度的智能文章推荐系统，使用 &lt;a href=&quot;https://huggingface.co/docs/transformers.js&quot;&gt;transformers.js&lt;/a&gt; 在本地生成文章嵌入向量，计算文章间的语义相似度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;特性：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt; 基于 AI 嵌入模型（Snowflake Arctic Embed）的语义理解&lt;/li&gt;
&lt;li&gt; 自动计算文章间的相似度，推荐最相关的 5 篇文章&lt;/li&gt;
&lt;li&gt; 构建时预计算，运行时零开销&lt;/li&gt;
&lt;li&gt; 支持通过 frontmatter 排除特定文章&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;使用方式：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 生成相似度数据（本地运行，会自动下载模型，约需 3-5 分钟）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; generate:similarities&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 生成的文件会提交到 git，Vercel 等平台直接使用&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;排除特定文章：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在文章 frontmatter 中设置 &lt;code&gt;excludeFromSummary: true&lt;/code&gt; 可排除该文章：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;周刊第 1 期&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;excludeFromSummary&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; # 排除此文章的相似度计算和 AI 摘要生成&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示&lt;/strong&gt;：系列文章（如周刊）通常建议设置 &lt;code&gt;excludeFromSummary: true&lt;/code&gt;，避免影响其他文章的推荐质量。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;配置计算内容：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;可以选择是否将文章正文纳入相似度计算：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// true: 使用 标题 + 描述 + 正文（更准确，速度较慢）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// false: 仅使用 标题 + 描述（更快，适合文章数量较多的情况）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; INCLUDE_BODY&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;包含正文&lt;/strong&gt;：相似度更精确，能识别内容层面的相关性，但生成速度较慢&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;仅标题+描述&lt;/strong&gt;：生成速度快，适合描述写得比较详细的博客&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 使用 Snowflake/snowflake-arctic-embed-m-v2.0 计算 168 篇文章（标题+描述）的时间&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Done!&lt;/span&gt;&lt;span&gt; Generated&lt;/span&gt;&lt;span&gt; similarities&lt;/span&gt;&lt;span&gt; for&lt;/span&gt;&lt;span&gt; 168&lt;/span&gt;&lt;span&gt; posts&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; 4.1s&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 使用 Snowflake/snowflake-arctic-embed-m-v2.0 计算 168 篇文章（标题+描述+正文）的时间&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Done!&lt;/span&gt;&lt;span&gt; Generated&lt;/span&gt;&lt;span&gt; similarities&lt;/span&gt;&lt;span&gt; for&lt;/span&gt;&lt;span&gt; 168&lt;/span&gt;&lt;span&gt; posts&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; 219.3s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这差别有点大，但是我个人很喜欢带正文的结果，效果显然会更好。所以索性再加一个跑 AI 总结的功能。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;模型选择：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;默认使用 &lt;code&gt;Snowflake/snowflake-arctic-embed-m-v2.0&lt;/code&gt; 模型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;模型大小&lt;/strong&gt;：约 90MB（首次运行会自动下载到 &lt;code&gt;.cache/transformers&lt;/code&gt; 目录）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;向量维度&lt;/strong&gt;：768 维&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能&lt;/strong&gt;：平衡了质量和速度，适合中文和英文内容&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生成时间&lt;/strong&gt;：约 3-5 分钟（169 篇文章）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如需更换模型，编辑 &lt;code&gt;src/scripts/generateSimilarities.ts&lt;/code&gt; 中的 &lt;code&gt;MODEL_NAME&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; MODEL_NAME&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;Snowflake/snowflake-arctic-embed-m-v2.0&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 可选替代方案：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// const MODEL_NAME = &apos;sentence-transformers/all-MiniLM-L6-v2&apos;; // 更小更快（约 23MB），384 维&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// const MODEL_NAME = &apos;BAAI/bge-small-zh-v1.5&apos;;  // 针对中文优化&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;其他可选模型对比：&lt;/strong&gt;&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;模型&lt;/th&gt;&lt;th&gt;大小&lt;/th&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;优势&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Snowflake/snowflake-arctic-embed-m-v2.0&lt;/code&gt;&lt;/td&gt;&lt;td&gt;~90MB&lt;/td&gt;&lt;td&gt;768&lt;/td&gt;&lt;td&gt;质量高，中英文均衡&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;sentence-transformers/all-MiniLM-L6-v2&lt;/code&gt;&lt;/td&gt;&lt;td&gt;~23MB&lt;/td&gt;&lt;td&gt;384&lt;/td&gt;&lt;td&gt;轻量快速&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;BAAI/bge-small-zh-v1.5&lt;/code&gt;&lt;/td&gt;&lt;td&gt;~95MB&lt;/td&gt;&lt;td&gt;512&lt;/td&gt;&lt;td&gt;中文专用&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;注意事项：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;需要本地运行生成脚本（Vercel 等平台无法运行模型）&lt;/li&gt;
&lt;li&gt;生成的 &lt;code&gt;src/assets/similarities.json&lt;/code&gt; 需要提交到 git&lt;/li&gt;
&lt;li&gt;如果没有生成相似度文件，相关文章模块不会显示&lt;/li&gt;
&lt;li&gt;模型文件会缓存在 &lt;code&gt;.cache/transformers&lt;/code&gt; 目录（已添加到 &lt;code&gt;.gitignore&lt;/code&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;AI 自动摘要&lt;a href=&quot;#ai-自动摘要&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;基于 &lt;a href=&quot;https://huggingface.co/docs/transformers.js&quot;&gt;transformers.js&lt;/a&gt; 的智能摘要生成系统，使用先进的 AI 模型为文章自动生成高质量摘要。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;与相关文章推荐的关系：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;AI 摘要功能与相关文章推荐功能相辅相成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;相似度计算&lt;/strong&gt;需要读取文章全文，计算成本较高（约 3-5 分钟）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI 摘要&lt;/strong&gt;可以在不读取全文的情况下提供优质描述，同时生成的摘要也能帮助改善相似度计算的效果&lt;/li&gt;
&lt;li&gt;两者共享相同的模型缓存机制，节省存储空间&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;特性：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt; 基于先进的文本生成模型（Xenova/LaMini-Flan-T5-783M）&lt;/li&gt;
&lt;li&gt; 自动为缺少描述的文章生成摘要&lt;/li&gt;
&lt;li&gt;✨ 文章详情页支持打字机动画展示，增强阅读体验&lt;/li&gt;
&lt;li&gt; 智能 fallback：优先使用手写 description，无描述时自动使用 AI 摘要&lt;/li&gt;
&lt;li&gt; 构建时预生成，运行时零开销&lt;/li&gt;
&lt;li&gt;♿ 支持无障碍访问和 prefers-reduced-motion&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;使用方式：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 生成 AI 摘要（本地运行，首次会下载模型，约需 5-10 分钟）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; generate:summaries&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 生成的文件要提交到 git，然后可以在 Vercel 等平台直接使用&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;生成效果：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;AI 摘要会保存在 &lt;code&gt;src/assets/summaries.json&lt;/code&gt; 文件中，格式如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;article-slug&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;文章标题&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;summary&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;AI 生成的摘要内容...&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;在哪里使用：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;文章详情页&lt;/strong&gt;：面包屑导航下方显示可折叠的 AI 摘要卡片&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;默认收起状态，点击&quot;展开&quot;按钮触发&lt;/li&gt;
&lt;li&gt;展开后以打字机动画逐字显示摘要内容&lt;/li&gt;
&lt;li&gt;打字机动画仅播放一次，支持 &lt;code&gt;prefers-reduced-motion&lt;/code&gt; 用户偏好&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;文章卡片&lt;/strong&gt;：作为描述的 fallback&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优先级：手写 &lt;code&gt;description&lt;/code&gt; &amp;gt; AI 摘要 &amp;gt; Markdown 前 150 字&lt;/li&gt;
&lt;li&gt;在文章列表、首页、分类页等处自动使用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;模型选择：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;默认使用 &lt;code&gt;Xenova/LaMini-Flan-T5-783M&lt;/code&gt; 模型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;模型大小&lt;/strong&gt;：约 300MB（首次运行会自动下载到 &lt;code&gt;.cache/transformers&lt;/code&gt; 目录）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生成质量&lt;/strong&gt;：高质量的中英文摘要生成&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生成时间&lt;/strong&gt;：约 5-10 分钟（169 篇文章）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如需更换模型，编辑 &lt;code&gt;src/scripts/generateSummaries.ts&lt;/code&gt; 中的 &lt;code&gt;MODEL_NAME&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; MODEL_NAME&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;Xenova/LaMini-Flan-T5-783M&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 可选替代方案：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// const MODEL_NAME = &apos;Xenova/distilbart-cnn-6-6&apos;; // 更快，英文效果好&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// const MODEL_NAME = &apos;facebook/bart-large-cnn&apos;;   // 质量更高，但速度较慢&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;配置提示词：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;可以自定义生成摘要的提示词，编辑 &lt;code&gt;src/scripts/generateSummaries.ts&lt;/code&gt; 中的 &lt;code&gt;PROMPT_TEMPLATE&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; PROMPT_TEMPLATE&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;content&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  `请为以下文章生成一段简洁的摘要（100-150字）：&lt;/span&gt;&lt;span&gt;\n\n&lt;/span&gt;&lt;span&gt;标题：${&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;\n\n&lt;/span&gt;&lt;span&gt;内容：${&lt;/span&gt;&lt;span&gt;content&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意事项：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;需要本地运行生成脚本（Vercel 等平台无法运行大模型）&lt;/li&gt;
&lt;li&gt;生成的 &lt;code&gt;src/assets/summaries.json&lt;/code&gt; 需要提交到 git&lt;/li&gt;
&lt;li&gt;如果没有生成摘要文件，会自动 fallback 到 Markdown 文本提取&lt;/li&gt;
&lt;li&gt;模型文件会缓存在 &lt;code&gt;.cache/transformers&lt;/code&gt; 目录（已添加到 &lt;code&gt;.gitignore&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;首次运行需要下载模型，建议在网络良好的环境下进行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;最佳实践：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;与相似度计算配合使用&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 先生成摘要&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; generate:summaries&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 再计算相似度（可以使用摘要代替全文，提升速度）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; generate:similarities&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;选择性生成摘要&lt;/strong&gt;：为了节省时间，脚本会跳过已有 &lt;code&gt;description&lt;/code&gt; 的文章&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;提交到版本控制&lt;/strong&gt;：将生成的 JSON 文件提交到 git，避免在 CI/CD 环境重复生成&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;圣诞特辑&lt;a href=&quot;#圣诞特辑&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;节日限定的圣诞氛围特效系统，包含多种可独立开关的视觉效果，为博客增添节日气氛。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;特性：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;雪花飘落 —— Canvas 实现的雪花动画，分前景和背景两层，支持视差效果&lt;/li&gt;
&lt;li&gt;圣诞配色 —— 红绿金主题色替换默认粉蓝配色，支持深色/浅色模式&lt;/li&gt;
&lt;li&gt;圣诞帽装饰 —— 侧边栏头像上的圣诞帽&lt;/li&gt;
&lt;li&gt;圣诞灯串 —— Header 顶部的装饰灯串动画&lt;/li&gt;
&lt;li&gt;圣诞饰品切换 —— 导航栏的装饰饰品&lt;/li&gt;
&lt;li&gt;运行时开关 —— 右下角浮动按钮可随时切换特效，设置自动保存&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;配置方式：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;编辑 &lt;code&gt;config/site.yaml&lt;/code&gt; 中的 &lt;code&gt;christmas&lt;/code&gt; 配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;christmas&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  enabled&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; # 总开关&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  features&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    snowfall&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; # 雪花飘落&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    christmasColorScheme&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; # 圣诞配色&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    christmasCoverDecoration&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; # 灯串装饰&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    christmasHat&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; # 圣诞帽&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    readingTimeSnow&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; # 阅读时间雪花特效&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  snowfall&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    speed&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.5&lt;/span&gt;&lt;span&gt; # 飘落速度（默认 0.5）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    intensity&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.7&lt;/span&gt;&lt;span&gt; # 桌面端雪花密度（0-1）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    mobileIntensity&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.4&lt;/span&gt;&lt;span&gt; # 移动端雪花密度（0-1）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    maxLayers&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt; # 最大雪花层数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    maxIterations&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt; # 最大迭代次数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    mobileMaxLayers&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt; # 移动端最大层数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    mobileMaxIterations&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt; # 移动端最大迭代次数&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;用户控制：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;页面右下角悬浮按钮（雪花图标）可切换圣诞特效开关&lt;/li&gt;
&lt;li&gt;用户偏好自动保存到 localStorage，跨会话保持&lt;/li&gt;
&lt;li&gt;支持 &lt;code&gt;prefers-reduced-motion&lt;/code&gt; 偏好，自动禁用动画&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;技术实现：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;雪花使用 Canvas 2D 渲染，分层实现视差效果&lt;/li&gt;
&lt;li&gt;配色通过 CSS 变量覆盖，零运行时开销&lt;/li&gt;
&lt;li&gt;状态管理使用 nanostores，支持跨组件同步&lt;/li&gt;
&lt;li&gt;完全响应式，移动端自动降低雪花密度&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;关闭圣诞特效：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;设置 &lt;code&gt;christmasConfig.enabled = false&lt;/code&gt; 即可完全关闭所有圣诞特效。&lt;/p&gt;
&lt;h3&gt;站点公告系统&lt;a href=&quot;#站点公告系统&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;无后端的站点公告系统，支持在配置文件中管理公告，首次访问自动弹出，关闭后可通过页脚入口再次查看。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;特性：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;无后端 —— 公告内容写在配置文件，无需数据库&lt;/li&gt;
&lt;li&gt;Toast 通知 —— 右下角浮动通知，支持多条堆叠显示&lt;/li&gt;
&lt;li&gt;多条公告 —— 支持配置多条公告，按优先级排序&lt;/li&gt;
&lt;li&gt;时间控制 —— 支持设置公告的开始/结束日期，自动控制显示&lt;/li&gt;
&lt;li&gt;自定义颜色 —— 每条公告可设置独立颜色，覆盖默认类型颜色&lt;/li&gt;
&lt;li&gt;时间线弹窗 —— 公告列表采用时间线样式，带渐变连接线&lt;/li&gt;
&lt;li&gt;Hover 已读 —— 悬停 Toast 1 秒后自动标记已读&lt;/li&gt;
&lt;li&gt;已读追踪 —— localStorage 记录已读状态，返回访问不重复弹出&lt;/li&gt;
&lt;li&gt;再次查看 —— 页脚入口可随时查看所有公告，带未读红点提示&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;配置方式：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;编辑 &lt;code&gt;config/site.yaml&lt;/code&gt; 添加公告：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;announcements&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;welcome-2026&lt;/span&gt;&lt;span&gt; # 唯一标识&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2026 年新年快乐!&lt;/span&gt;&lt;span&gt; # 公告标题&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    content&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;新年快乐! 感谢大家一直以来的支持~&lt;/span&gt;&lt;span&gt; # 公告内容&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;info&lt;/span&gt;&lt;span&gt; # 类型：info | warning | success | important&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    priority&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;300&lt;/span&gt;&lt;span&gt; # 优先级（越高越先显示）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;#ED788C&quot;&lt;/span&gt;&lt;span&gt; # 自定义颜色（可选，覆盖 type 默认色）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    publishDate&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;2026-01-01&quot;&lt;/span&gt;&lt;span&gt; # 显示日期（可选，用于时间线展示）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    startDate&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;2025-12-31T00:00:00+08:00&quot;&lt;/span&gt;&lt;span&gt; # 开始日期（可选）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    endDate&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;2026-01-15T23:59:59+08:00&quot;&lt;/span&gt;&lt;span&gt; # 结束日期（可选）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;site-update-01&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;站点更新公告&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    content&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;新增站点公告系统，现在支持多条公告同时显示！&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;info&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    priority&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;#6366F1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    publishDate&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;2025-01-02&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果需要添加链接（可选）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;announcements&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;example-with-link&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;示例公告&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    content&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;公告内容&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;info&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    link&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      url&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;https://example.com&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      text&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;了解更多&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      external&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;公告类型样式：&lt;/strong&gt;&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;类型&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;th&gt;默认颜色&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;info&lt;/code&gt;&lt;/td&gt;&lt;td&gt;信息通知&lt;/td&gt;&lt;td&gt;蓝色 (#3b82f6)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;warning&lt;/code&gt;&lt;/td&gt;&lt;td&gt;警告提示&lt;/td&gt;&lt;td&gt;黄色 (#eab308)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;success&lt;/code&gt;&lt;/td&gt;&lt;td&gt;成功消息&lt;/td&gt;&lt;td&gt;绿色 (#22c55e)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;important&lt;/code&gt;&lt;/td&gt;&lt;td&gt;重要公告&lt;/td&gt;&lt;td&gt;红色 (#ef4444)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;设置 &lt;code&gt;color&lt;/code&gt; 字段可覆盖上述默认颜色&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;交互流程：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;首次访问&lt;/strong&gt;：0.5 秒后自动弹出未读公告 Toast（多条堆叠显示）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hover 已读&lt;/strong&gt;：悬停在 Toast 上 1 秒后自动标记已读&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;手动关闭&lt;/strong&gt;：点击 Dismiss 关闭 Toast&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;点击&quot;View all&quot;&lt;/strong&gt;：关闭所有 Toast，打开时间线弹窗&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;时间线弹窗&lt;/strong&gt;：点击公告卡片标记已读，显示发布日期和渐变连接线&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;页脚入口&lt;/strong&gt;：随时可点击查看所有公告，未读时显示红点&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;返回访问&lt;/strong&gt;：只显示真正未读的公告&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;注意事项：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;公告 &lt;code&gt;id&lt;/code&gt; 必须唯一，用于追踪已读状态&lt;/li&gt;
&lt;li&gt;省略 &lt;code&gt;startDate&lt;/code&gt; 表示立即生效，省略 &lt;code&gt;endDate&lt;/code&gt; 表示永不过期&lt;/li&gt;
&lt;li&gt;&lt;code&gt;publishDate&lt;/code&gt; 用于时间线弹窗中的日期显示，省略时使用 &lt;code&gt;startDate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;过期公告建议从配置中删除，保持配置简洁&lt;/li&gt;
&lt;li&gt;已读状态存储在 localStorage，key 为 &lt;code&gt;announcement-read-ids&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Markdown 增强&lt;a href=&quot;#markdown-增强&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;语法支持：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub Flavored Markdown (GFM)
&lt;ul&gt;
&lt;li&gt;表格&lt;/li&gt;
&lt;li&gt;任务列表&lt;/li&gt;
&lt;li&gt;删除线&lt;/li&gt;
&lt;li&gt;自动链接&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Mermaid 图表：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;支持在 Markdown 中使用 Mermaid 语法绘制流程图、时序图、架构图等。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;```mermaid&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    A[构建时脚本] --&amp;gt; B[JSON 数据文件] --&amp;gt; C[运行时工具函数]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;```&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;flowchart LR
    A[构建时脚本] --&amp;gt; B[JSON 数据文件] --&amp;gt; C[运行时工具函数]&lt;/pre&gt;
&lt;p&gt;支持的图表类型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;flowchart&lt;/code&gt; / &lt;code&gt;graph&lt;/code&gt; - 流程图&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sequenceDiagram&lt;/code&gt; - 时序图&lt;/li&gt;
&lt;li&gt;&lt;code&gt;classDiagram&lt;/code&gt; - 类图&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stateDiagram&lt;/code&gt; - 状态图&lt;/li&gt;
&lt;li&gt;&lt;code&gt;erDiagram&lt;/code&gt; - ER 图&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gantt&lt;/code&gt; - 甘特图&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pie&lt;/code&gt; - 饼图&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mindmap&lt;/code&gt; - 思维导图&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;图表会自动跟随深色/浅色主题切换。更多语法参考 &lt;a href=&quot;https://mermaid.js.org/&quot;&gt;Mermaid 官方文档&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Infographic 信息图：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;支持使用 &lt;a href=&quot;https://infographic.antv.vision/&quot;&gt;@antv/infographic&lt;/a&gt; 在 Markdown 中绘制精美的信息图表，适合展示流程、对比、层级、统计等数据。&lt;/p&gt;
&lt;p&gt;使用方式：在代码块中使用 &lt;code&gt;infographic&lt;/code&gt; 标记，第一行指定模板名称，然后使用类似 YAML 的语法定义数据：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;```infographic&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;infographic list-grid-badge-card&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 技术栈&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  desc 我的常用技术栈&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label TypeScript&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 类型安全的 JavaScript&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/language-typescript&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label React&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 用户界面库&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/react&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label Astro&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 现代化静态站点生成器&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/rocket-launch&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;```&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;infographic list-grid-badge-card&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title 技术栈&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  desc 我的常用技术栈&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label TypeScript&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 类型安全的 JavaScript&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/language-typescript&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label React&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 用户界面库&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/react&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label Astro&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      desc 现代化静态站点生成器&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      icon mdi/rocket-launch&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;可用模板类型：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;列表类&lt;/strong&gt; (&lt;code&gt;list-*&lt;/code&gt;)：展示信息列表&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;list-grid-badge-card&lt;/code&gt; - 卡片网格布局&lt;/li&gt;
&lt;li&gt;&lt;code&gt;list-grid-candy-card-lite&lt;/code&gt; - 糖果风格卡片&lt;/li&gt;
&lt;li&gt;&lt;code&gt;list-row-horizontal-icon-arrow&lt;/code&gt; - 水平图标箭头列表&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;流程/顺序类&lt;/strong&gt; (&lt;code&gt;sequence-*&lt;/code&gt;)：展示步骤、流程或阶段&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sequence-zigzag-steps-underline-text&lt;/code&gt; - 之字形步骤&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sequence-circular-simple&lt;/code&gt; - 圆形流程&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sequence-roadmap-vertical-simple&lt;/code&gt; - 垂直路线图&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sequence-pyramid-simple&lt;/code&gt; - 金字塔结构&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;对比类&lt;/strong&gt; (&lt;code&gt;compare-*&lt;/code&gt;)：二元或多元对比&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;compare-binary-horizontal-simple-fold&lt;/code&gt; - 水平二元对比&lt;/li&gt;
&lt;li&gt;&lt;code&gt;compare-swot&lt;/code&gt; - SWOT 分析&lt;/li&gt;
&lt;li&gt;&lt;code&gt;compare-hierarchy-left-right-circle-node-pill-badge&lt;/code&gt; - 层级左右对比&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;层级类&lt;/strong&gt; (&lt;code&gt;hierarchy-*&lt;/code&gt;)：展示树形结构&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;hierarchy-tree-tech-style-capsule-item&lt;/code&gt; - 科技风格树形图&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hierarchy-tree-curved-line-rounded-rect-node&lt;/code&gt; - 曲线连接树形图&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;图表类&lt;/strong&gt; (&lt;code&gt;chart-*&lt;/code&gt;)：数据可视化&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;chart-column-simple&lt;/code&gt; - 柱状图&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chart-bar-plain-text&lt;/code&gt; - 条形图&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chart-pie-plain-text&lt;/code&gt; - 饼图&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chart-line-plain-text&lt;/code&gt; - 折线图&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;其他&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;quadrant-*&lt;/code&gt; - 象限分析图&lt;/li&gt;
&lt;li&gt;&lt;code&gt;relation-*&lt;/code&gt; - 关系图&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;数据字段说明：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;title&lt;/code&gt; - 标题（可选）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;desc&lt;/code&gt; - 描述文本（可选）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;items&lt;/code&gt; - 条目数组，每个条目可包含：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;label&lt;/code&gt; - 主标签文本&lt;/li&gt;
&lt;li&gt;&lt;code&gt;value&lt;/code&gt; - 数值（用于图表类模板）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;desc&lt;/code&gt; - 描述文本&lt;/li&gt;
&lt;li&gt;&lt;code&gt;icon&lt;/code&gt; - 图标名称（格式：&lt;code&gt;mdi/icon-name&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;children&lt;/code&gt; - 子条目（用于层级结构）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;主题定制：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;可以在数据后添加 &lt;code&gt;theme&lt;/code&gt; 块自定义颜色：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;```infographic&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;infographic sequence-pyramid-simple&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 基础层&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 中间层&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - label 顶层&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;theme&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  palette&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - #3b82f6&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - #8b5cf6&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - #f97316&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;```&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;信息图会自动跟随深色/浅色主题切换，并使用项目的寒蝉全圆体字体渲染。更多模板和语法参考 &lt;a href=&quot;https://infographic.antv.vision/&quot;&gt;Infographic 官方文档&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;代码高亮：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;基于 Shiki&lt;/li&gt;
&lt;li&gt;支持双主题（深色/浅色）&lt;/li&gt;
&lt;li&gt;支持语言标注&lt;/li&gt;
&lt;li&gt;行号显示&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;```javascript&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; hello&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;```&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; hello&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;标题自动链接：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;所有标题自动生成可点击的锚点链接。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;链接自动嵌入：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;独行的特殊链接会自动转换为嵌入组件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Twitter/X 链接&lt;/strong&gt;：自动嵌入 Tweet 组件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CodePen 链接&lt;/strong&gt;：自动嵌入交互式 CodePen 演示&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;其他链接&lt;/strong&gt;：显示 OG 预览卡片（包含标题、描述、图片等）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;!-- 独行链接会被嵌入 --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;https://x.com/vercel_dev/status/1997059920936775706&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;https://codepen.io/botteu/pen/YPKBrJX/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;https://github.com/vercel/react-tweet&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;反爬严格，获取不到元信息的链接&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;https://zhuanlan.zhihu.com/p/1900483903984243480&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;!-- 段落中的链接保持不变 --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;这是一个 [&lt;/span&gt;&lt;span&gt;普通链接&lt;/span&gt;&lt;span&gt;](&lt;/span&gt;&lt;span&gt;https://example.com&lt;/span&gt;&lt;span&gt;)，不会被嵌入。&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&lt;/div&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/botteu/pen/YPKBrJX/&quot;&gt;YPKBrJX&lt;/a&gt; by botteu (&lt;a href=&quot;https://codepen.io/botteu&quot;&gt;@botteu&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://github.com/vercel/react-tweet&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://github.com/fluidicon.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;github.com&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;GitHub - vercel/react-tweet: Embed tweets in your React application.&lt;/h3&gt;
        &lt;p&gt;Embed tweets in your React application. Contribute to vercel/react-tweet development by creating an account on GitHub.&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://github.com/vercel/react-tweet&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://opengraph.githubassets.com/a55cc4a7f7db90fbf062ee90a71d48aac4fe6a2895e6c07047be1655d3b2923f/vercel/react-tweet&quot; alt=&quot;GitHub - vercel/react-tweet: Embed tweets in your React application.&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;反爬严格，获取不到元信息的链接&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://zhuanlan.zhihu.com/p/1900483903984243480&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;https://zhuanlan.zhihu.com/p/1900483903984243480&lt;/div&gt;
          &lt;div&gt;zhuanlan.zhihu.com&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      
        
      
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;p&gt;这是一个 &lt;a href=&quot;https://example.com&quot;&gt;普通链接&lt;/a&gt;，不会被嵌入。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Shoka 兼容 Markdown 语法：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;astro-koharu 从 Hexo Shoka 主题迁移了一套丰富的 Markdown 扩展语法，所有功能均可通过 &lt;code&gt;config/site.yaml&lt;/code&gt; 的 &lt;code&gt;content&lt;/code&gt; 配置项独立开关。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;文字特效（&lt;code&gt;enableShokaEffects&lt;/code&gt;）：&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;支持多种行内文字装饰效果：&lt;/p&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;语法&lt;/th&gt;&lt;th&gt;效果&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;++文字++&lt;/code&gt;&lt;/td&gt;&lt;td&gt;下划线&lt;/td&gt;&lt;td&gt;&lt;code&gt;&amp;lt;ins&amp;gt;&lt;/code&gt; 标签&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;++文字++{.wavy}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;波浪下划线&lt;/td&gt;&lt;td&gt;支持 &lt;code&gt;.wavy&lt;/code&gt; 修饰符&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;++文字++{.dot}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;着重点&lt;/td&gt;&lt;td&gt;支持 &lt;code&gt;.dot&lt;/code&gt; 修饰符&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;++文字++{.primary}&lt;/code&gt;&lt;/td&gt;&lt;td&gt;彩色下划线&lt;/td&gt;&lt;td&gt;支持 &lt;code&gt;.primary&lt;/code&gt; &lt;code&gt;.success&lt;/code&gt; &lt;code&gt;.warning&lt;/code&gt; &lt;code&gt;.danger&lt;/code&gt; &lt;code&gt;.info&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;==文字==&lt;/code&gt;&lt;/td&gt;&lt;td&gt;高亮&lt;/td&gt;&lt;td&gt;&lt;code&gt;&amp;lt;mark&amp;gt;&lt;/code&gt; 标签&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;~文字~&lt;/code&gt;&lt;/td&gt;&lt;td&gt;下标&lt;/td&gt;&lt;td&gt;&lt;code&gt;&amp;lt;sub&amp;gt;&lt;/code&gt; 标签，如 H&lt;sub&gt;2&lt;/sub&gt;O&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;^文字^&lt;/code&gt;&lt;/td&gt;&lt;td&gt;上标&lt;/td&gt;&lt;td&gt;&lt;code&gt;&amp;lt;sup&amp;gt;&lt;/code&gt; 标签，如 E=mc&lt;sup&gt;2&lt;/sup&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;示例效果：&lt;/p&gt;
&lt;p&gt;这是下划线文字 波浪下划线 着重点标记&lt;/p&gt;
&lt;p&gt;主色调 成功 警告 危险 信息&lt;/p&gt;
&lt;p&gt;&lt;mark&gt;这是高亮文字&lt;/mark&gt;&lt;/p&gt;
&lt;p&gt;H&lt;sub&gt;2&lt;/sub&gt;O 是水的化学式，E = mc&lt;sup&gt;2&lt;/sup&gt; 是质能方程&lt;/p&gt;
&lt;p&gt;&lt;em&gt;颜色文字与特殊样式（&lt;code&gt;enableShokaAttrs&lt;/code&gt;）：&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;&lt;span&gt;文字&lt;/span&gt;&lt;/code&gt; 语法为文字添加颜色和样式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;红色&lt;/span&gt;&lt;span&gt;]{.red} [&lt;/span&gt;&lt;span&gt;粉色&lt;/span&gt;&lt;span&gt;]{.pink} [&lt;/span&gt;&lt;span&gt;橙色&lt;/span&gt;&lt;span&gt;]{.orange} [&lt;/span&gt;&lt;span&gt;黄色&lt;/span&gt;&lt;span&gt;]{.yellow}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;绿色&lt;/span&gt;&lt;span&gt;]{.green} [&lt;/span&gt;&lt;span&gt;水色&lt;/span&gt;&lt;span&gt;]{.aqua} [&lt;/span&gt;&lt;span&gt;蓝色&lt;/span&gt;&lt;span&gt;]{.blue} [&lt;/span&gt;&lt;span&gt;紫色&lt;/span&gt;&lt;span&gt;]{.purple} [&lt;/span&gt;&lt;span&gt;灰色&lt;/span&gt;&lt;span&gt;]{.grey}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;这段文字会有彩虹渐变效果&lt;/span&gt;&lt;span&gt;]{.rainbow}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Ctrl&lt;/span&gt;&lt;span&gt;]{.kbd} + [&lt;/span&gt;&lt;span&gt;C&lt;/span&gt;&lt;span&gt;]{.kbd} 复制，[&lt;/span&gt;&lt;span&gt;Ctrl&lt;/span&gt;&lt;span&gt;]{.kbd} + [&lt;/span&gt;&lt;span&gt;V&lt;/span&gt;&lt;span&gt;]{.kbd} 粘贴&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;默认&lt;/span&gt;&lt;span&gt;]{.label .default} [&lt;/span&gt;&lt;span&gt;主要&lt;/span&gt;&lt;span&gt;]{.label .primary} [&lt;/span&gt;&lt;span&gt;信息&lt;/span&gt;&lt;span&gt;]{.label .info}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;成功&lt;/span&gt;&lt;span&gt;]{.label .success} [&lt;/span&gt;&lt;span&gt;警告&lt;/span&gt;&lt;span&gt;]{.label .warning} [&lt;/span&gt;&lt;span&gt;危险&lt;/span&gt;&lt;span&gt;]{.label .danger}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例效果：&lt;/p&gt;
&lt;p&gt;&lt;span&gt;红色&lt;/span&gt; &lt;span&gt;粉色&lt;/span&gt; &lt;span&gt;橙色&lt;/span&gt; &lt;span&gt;黄色&lt;/span&gt; &lt;span&gt;绿色&lt;/span&gt; &lt;span&gt;水色&lt;/span&gt; &lt;span&gt;蓝色&lt;/span&gt; &lt;span&gt;紫色&lt;/span&gt; &lt;span&gt;灰色&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;这段文字会有彩虹渐变效果&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;Ctrl&lt;/span&gt; + &lt;span&gt;C&lt;/span&gt; 复制，&lt;span&gt;Ctrl&lt;/span&gt; + &lt;span&gt;V&lt;/span&gt; 粘贴&lt;/p&gt;
&lt;p&gt;&lt;span&gt;默认&lt;/span&gt; &lt;span&gt;主要&lt;/span&gt; &lt;span&gt;信息&lt;/span&gt; &lt;span&gt;成功&lt;/span&gt; &lt;span&gt;警告&lt;/span&gt; &lt;span&gt;危险&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;隐藏文字 / Spoiler（&lt;code&gt;enableShokaSpoiler&lt;/code&gt;）：&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;这里有一段!!隐藏文字，点击显示!!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;这里有一段!!模糊文字，鼠标悬停显示!!{.blur}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例效果：&lt;/p&gt;
&lt;p&gt;这里有一段隐藏文字，点击显示&lt;/p&gt;
&lt;p&gt;这里有一段&lt;span&gt;模糊文字，鼠标悬停显示&lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;默认模式：点击后粒子消散动画揭示文字（基于 spoilerjs Web Component）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.blur&lt;/code&gt; 模式：鼠标悬停时模糊消失&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;注音标注 / Ruby（&lt;code&gt;enableShokaRuby&lt;/code&gt;）：&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;为 CJK 文字添加注音，适用于日语假名、汉语拼音等：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{漢字^かんじ}的注音示例&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{取り返す^とりかえす}是日语中&quot;取回&quot;的意思&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例效果：&lt;/p&gt;
&lt;p&gt;{漢字&lt;sup&gt;かんじ}的注音示例。{取り返す&lt;/sup&gt;とりかえす}是日语中&quot;取回&quot;的意思。&lt;/p&gt;
&lt;p&gt;渲染为 HTML &lt;code&gt;&amp;lt;ruby&amp;gt;&lt;/code&gt; 标签，浏览器原生支持。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;提醒块 / Note Blocks（&lt;code&gt;enableShokaContainers&lt;/code&gt;）：&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;:::&lt;/code&gt; 语法创建不同样式的提醒块：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;:::default&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;这是默认提醒块&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;:::&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;:::primary&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;这是主要提醒块，用于重要提示&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;:::&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;:::info&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;这是信息提醒块&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;:::&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;:::success&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;这是成功提醒块&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;:::&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;:::warning&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;这是警告提醒块&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;:::&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;:::danger&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;这是危险提醒块&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;:::&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;:::info no-icon&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;这是没有图标的信息块&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;:::&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例效果：&lt;/p&gt;
&lt;div&gt;
&lt;p&gt;这是信息提醒块，用于提供额外信息&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;p&gt;这是警告提醒块，请注意&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;p&gt;这是危险提醒块，务必谨慎&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;支持的样式：&lt;code&gt;default&lt;/code&gt;、&lt;code&gt;primary&lt;/code&gt;、&lt;code&gt;info&lt;/code&gt;、&lt;code&gt;success&lt;/code&gt;、&lt;code&gt;warning&lt;/code&gt;、&lt;code&gt;danger&lt;/code&gt;。添加 &lt;code&gt;no-icon&lt;/code&gt; 可隐藏图标。提醒块内部支持嵌套 Markdown 语法。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;折叠块 / Collapse（&lt;code&gt;enableShokaContainers&lt;/code&gt;）：&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;+++&lt;/code&gt; 语法创建可折叠内容（渲染为 &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; + &lt;code&gt;&amp;lt;summary&amp;gt;&lt;/code&gt;）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;+++primary 点击展开详细内容&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;折叠的内容，支持 &lt;/span&gt;&lt;span&gt;**Markdown**&lt;/span&gt;&lt;span&gt; 格式化。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 列表项 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 列表项 2&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;+++&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;+++warning 注意事项&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;需要注意的内容&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;+++&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;+++danger 危险操作&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;请确保你知道自己在做什么！&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;+++&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例效果：&lt;/p&gt;

点击展开详细内容
&lt;div&gt;
&lt;p&gt;折叠的内容，支持 &lt;strong&gt;Markdown&lt;/strong&gt; 格式化。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;列表项 1&lt;/li&gt;
&lt;li&gt;列表项 2&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;


注意事项
&lt;div&gt;
&lt;p&gt;需要注意的内容&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;支持的样式：&lt;code&gt;primary&lt;/code&gt;、&lt;code&gt;info&lt;/code&gt;、&lt;code&gt;success&lt;/code&gt;、&lt;code&gt;warning&lt;/code&gt;、&lt;code&gt;danger&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;标签卡 / Tabs（&lt;code&gt;enableShokaContainers&lt;/code&gt;）：&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;;;;&lt;/code&gt; 语法创建标签页切换，同一组 ID 的标签卡会自动组合：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;;;;mygroup JavaScript&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;```js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;Hello, World!&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;```&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;;;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;;;;mygroup Python&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;```python&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;print(&apos;Hello, World!&apos;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;```&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;;;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;;;;mygroup Rust&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;```rust&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;fn main() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    println!(&quot;Hello, World!&quot;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;```&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;;;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例效果：&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
JavaScript
Python
Rust
&lt;/div&gt;
&lt;div&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;Hello, World!&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;Hello, World!&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt; main&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    println!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Hello, World!&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;;;;groupId 标签名&lt;/code&gt; 定义一个标签页，同一 &lt;code&gt;groupId&lt;/code&gt; 的标签自动组合&lt;/li&gt;
&lt;li&gt;第一个标签默认激活&lt;/li&gt;
&lt;li&gt;标签内支持任意 Markdown 内容&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;友链卡片（&lt;code&gt;enableShokaHexoTags&lt;/code&gt;）：&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;{% links %}&lt;/code&gt; 标签在文章中插入友链卡片网格：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{% links %}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; site: 博客名称&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  url: https://example.com&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  owner: 站长昵称&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  desc: 站点描述&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  image: https://example.com/avatar.png&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  color: &apos;#ed788b&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; site: 另一个博客&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  url: https://example2.com&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  owner: Alice&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  desc: 一个热爱技术的博客&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  image: https://api.dicebear.com/7.x/avataaars/svg?seed=Alice&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  color: &apos;#BEDCFF&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{% endlinks %}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例效果：&lt;/p&gt;
&lt;div&gt;
&lt;a href=&quot;https://blog.cosine.ren&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;https://blog.cosine.ren/img/avatar.webp&quot; alt=&quot;余弦の博客&quot; loading=&quot;lazy&quot; /&gt;&lt;div&gt;&lt;div&gt;余弦の博客&lt;/div&gt;&lt;div&gt;FE / ACG / 手工&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;
&lt;a href=&quot;https://example.com&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;https://api.dicebear.com/7.x/avataaars/svg?seed=Alice&quot; alt=&quot;示例博客&quot; loading=&quot;lazy&quot; /&gt;&lt;div&gt;&lt;div&gt;示例博客&lt;/div&gt;&lt;div&gt;一个热爱技术的博客&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;卡片数据使用 YAML 格式，支持 &lt;code&gt;site&lt;/code&gt;、&lt;code&gt;url&lt;/code&gt;、&lt;code&gt;owner&lt;/code&gt;、&lt;code&gt;desc&lt;/code&gt;、&lt;code&gt;image&lt;/code&gt;、&lt;code&gt;color&lt;/code&gt; 字段。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;音频播放器（&lt;code&gt;enableShokaHexoTags&lt;/code&gt;）：&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;{% media audio %}&lt;/code&gt; 标签嵌入音频播放器，支持网易云音乐等平台（通过 Meting API 解析）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{% media audio %}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; name: 歌曲名称&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  url: https://music.163.com/#/song?id=3339210292&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{% endmedia %}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例效果：&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;
&lt;p&gt;支持歌单模式，可配置多个分组：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{% media audio %}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; title: 歌单名称 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  list:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span&gt; https://music.163.com/#/playlist?id=8676645748&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; title: 歌单名称 2&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  list:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span&gt; https://music.163.com/#/playlist?id=17606384886&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{% endmedia %}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;视频播放器（&lt;code&gt;enableShokaHexoTags&lt;/code&gt;）：&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;{% media video %}&lt;/code&gt; 标签嵌入视频播放器：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{% media video %}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; name: 视频 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  url: https://example.com/video1.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; name: 视频 2&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  url: https://example.com/video2.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{% endmedia %}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&lt;/div&gt;
&lt;p&gt;多个视频时自动显示播放列表。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;练习题系统（&lt;code&gt;enableQuiz&lt;/code&gt;）：&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;支持四种交互式题型，适合教程和学习笔记。需在文章 frontmatter 中设置 &lt;code&gt;quiz: true&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;单选题：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 下列哪个是 JavaScript 的基本数据类型？{.quiz}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt; Object{.options}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt; Array{.options}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt; Symbol{.correct}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt; Function{.options}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;gt; 解析：Symbol 是 ES6 引入的基本数据类型。&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例效果：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;下列哪个是 JavaScript 的基本数据类型？
&lt;ul&gt;
&lt;li&gt;Object&lt;/li&gt;
&lt;li&gt;Array&lt;/li&gt;
&lt;li&gt;Symbol&lt;/li&gt;
&lt;li&gt;Function&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;解析：Symbol 是 ES6 引入的基本数据类型，而 Object、Array、Function 都是引用类型。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;选项标记 &lt;code&gt;&lt;/code&gt; 为正确答案，&lt;code&gt;{.options}&lt;/code&gt; 为干扰项&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;多选题：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 以下哪些是 CSS 布局方式？{.quiz .multi}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt; Flexbox{.correct}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt; jQuery{.options}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt; Grid{.correct}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span&gt; Float{.correct}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;gt; 解析：Flexbox、Grid 和 Float 都是 CSS 布局方式。&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例效果：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;以下哪些是 CSS 布局方式？
&lt;ul&gt;
&lt;li&gt;Flexbox&lt;/li&gt;
&lt;li&gt;jQuery&lt;/li&gt;
&lt;li&gt;Grid&lt;/li&gt;
&lt;li&gt;Float&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;解析：Flexbox、Grid 和 Float 都是 CSS 布局方式。jQuery 是一个 JavaScript 库。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;添加 &lt;code&gt;.multi&lt;/code&gt; 标记启用多选模式&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;判断题：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; `const`&lt;/span&gt;&lt;span&gt; 声明的变量不能重新赋值，但可以修改其属性。{.quiz .true}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;gt; 解析：&lt;/span&gt;&lt;span&gt;`const`&lt;/span&gt;&lt;span&gt; 只保证变量绑定不可变。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; HTML 是一种编程语言。{.quiz}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;gt; 解析：HTML 是标记语言，不是编程语言。&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例效果：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;const&lt;/code&gt; 声明的变量不能重新赋值，但可以修改其属性。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;解析：&lt;code&gt;const&lt;/code&gt; 只保证变量绑定不可变，如果变量指向一个对象，其属性仍然可以修改。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;HTML 是一种编程语言。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;解析：HTML（超文本标记语言）是一种标记语言，不是编程语言。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;添加 &lt;code&gt;.true&lt;/code&gt; 表示陈述正确，不添加 &lt;code&gt;.true&lt;/code&gt; 则表示错误&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;填空题：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; CSS 中，[&lt;/span&gt;&lt;span&gt;Flexbox&lt;/span&gt;&lt;span&gt;]{.gap} 适合一维布局，[&lt;/span&gt;&lt;span&gt;Grid&lt;/span&gt;&lt;span&gt;]{.gap} 适合二维布局。{.quiz .fill}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;gt; 常见错误：[&lt;/span&gt;&lt;span&gt;Float&lt;/span&gt;&lt;span&gt;]{.mistake}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例效果：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CSS 中，&lt;span&gt;Flexbox&lt;/span&gt; 适合一维布局，&lt;span&gt;Grid&lt;/span&gt; 适合二维布局。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;常见错误：&lt;span&gt;Float&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[答案]&lt;/code&gt; 标记正确答案（支持多个空）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[错误答案]&lt;/code&gt; 标记常见错误（首次答错时提示）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;gt;&lt;/code&gt; 引用块内容为解析说明&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;数学公式（&lt;code&gt;enableMath&lt;/code&gt;）：&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;基于 KaTeX 渲染数学公式。需在文章 frontmatter 中设置 &lt;code&gt;math: true&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;行内公式：$E = mc^2$&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;块级公式：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;$$&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;$$&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例效果：&lt;/p&gt;
&lt;p&gt;行内公式：&lt;span&gt;&lt;span&gt;E=mc2E = mc^2&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;E&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;m&lt;/span&gt;&lt;span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;块级公式：&lt;/p&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;∑n=1∞1n2=π26\sum_{n=1}^{\infty} \frac{1}{n^2} = \frac{\pi^2}{6}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;∑&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;∞&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;6&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;π&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;p&gt;&lt;em&gt;代码块增强（&lt;code&gt;enableCodeMeta&lt;/code&gt;）：&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;代码块支持额外的元数据标注：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;```js title=&quot;hello.js&quot; url=&quot;https://example.com&quot; linkText=&quot;查看源码&quot; mark:1,3&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; greeting&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;Hello&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;World&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;greeting&lt;/span&gt;&lt;span&gt;}, ${&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;}!`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;```&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;```bash command:(&quot;$&quot;:1-3)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; astro&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; run&lt;/span&gt;&lt;span&gt; dev&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; run&lt;/span&gt;&lt;span&gt; build&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;```&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;元数据&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;title=&quot;文件名&quot;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;显示代码块标题&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;url=&quot;链接&quot;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;添加外部源码链接&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;linkText=&quot;文字&quot;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;自定义链接文字（默认为 URL）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;mark:1,3&lt;/code&gt;&lt;/td&gt;&lt;td&gt;高亮指定行&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;command:(&quot;$&quot;:1-3)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;标记 shell 命令行（显示 &lt;code&gt;$&lt;/code&gt; 前缀）&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;示例效果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; greeting&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;Hello&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;World&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;greeting&lt;/span&gt;&lt;span&gt;}, ${&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;}!`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; astro&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; run&lt;/span&gt;&lt;span&gt; dev&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; run&lt;/span&gt;&lt;span&gt; build&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Shoka 功能配置总览：&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;所有 Shoka 兼容功能均可在 &lt;code&gt;config/site.yaml&lt;/code&gt; 的 &lt;code&gt;content&lt;/code&gt; 部分独立开关：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;content&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  # Shoka 兼容功能（默认全部启用，设为 false 可关闭）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  enableShokaContainers&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;   # :::提醒块 ;;;标签卡 +++折叠块&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  enableShokaAttrs&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;        # &lt;span&gt;text&lt;/span&gt; 属性语法&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  enableShokaEffects&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;      # ++下划线++ ==高亮== ~下标~ ^上标^&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  enableShokaSpoiler&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;      # !!隐藏文字!!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  enableShokaRuby&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;         # {文字^注音} 注音标注&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  enableShokaHexoTags&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;     # {% links %} {% media %} Hexo 标签&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  enableMath&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;              # $数学公式$ KaTeX 渲染&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  enableCodeMeta&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;          # 代码块增强 (title, mark, command)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  enableQuiz&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;              # 练习题交互功能&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  enableEncryptedBlock&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;    # :::encrypted{password=&quot;...&quot;} 加密内容块&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示&lt;/strong&gt;：完整的语法演示可参考 &lt;a href=&quot;/post/shoka-features&quot;&gt;Shoka 主题 Markdown 语法演示&lt;/a&gt; 文章。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;内容加密（&lt;code&gt;enableEncryptedBlock&lt;/code&gt;）：&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;博客支持两种加密方式，满足不同的内容保护需求：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 加密块 —— 文章局部加密&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在文章中使用 &lt;code&gt;:::encrypted{password=&quot;...&quot;}&lt;/code&gt; 语法包裹需要加密的内容：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;这部分内容公开可见。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;:::encrypted{password=&quot;demo&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;这段内容需要输入密码 &quot;demo&quot; 才能查看。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;支持完整的 Markdown 语法，包括代码块、列表、图片等。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;:::&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;这部分也是公开的。&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;加密块适合在一篇文章中&lt;strong&gt;部分隐藏&lt;/strong&gt;敏感内容（如答案、剧透、私密笔记），其余内容正常展示。需要在 &lt;code&gt;config/site.yaml&lt;/code&gt; 中启用 &lt;code&gt;enableEncryptedBlock: true&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 加密文章 —— 整篇文章加密&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在文章的 frontmatter 中添加 &lt;code&gt;password&lt;/code&gt; 字段即可加密整篇文章：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;我的私密文章&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2026-01-01&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;password&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;mySecretPassword&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;categories&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;笔记&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;这里的所有内容都会被加密...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;加密文章会显示一个全屏的解锁界面，输入正确密码后才能查看内容。解锁后代码高亮、目录导航、Mermaid 图表等增强功能会自动重新初始化。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;安全模型说明：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;加密功能使用 &lt;strong&gt;AES-256-GCM&lt;/strong&gt; 算法，安全模型如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;构建时加密&lt;/strong&gt;：密码仅在 &lt;code&gt;pnpm build&lt;/code&gt; 时用于加密，生成的 HTML 中&lt;strong&gt;不包含密码明文&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;客户端解密&lt;/strong&gt;：读者在浏览器中输入密码后，通过 Web Crypto API 在本地解密，密码不会发送到任何服务器&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;密钥派生&lt;/strong&gt;：使用 PBKDF2（100,000 次迭代）从密码派生加密密钥，提高暴力破解成本&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;搜索排除&lt;/strong&gt;：加密内容自动添加 &lt;code&gt;data-pagefind-ignore&lt;/code&gt;，不会被 Pagefind 搜索索引&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;注意&lt;/strong&gt;：此加密设计的主要目的是&lt;strong&gt;防止搜索引擎和爬虫索引加密内容&lt;/strong&gt;，而非抵御针对性攻击。密文和盐值嵌入在公开的 HTML 中，理论上可被离线暴力破解。请使用强密码，不要用于保护高度敏感的信息。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;加密文章的特殊行为：&lt;/strong&gt;&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;方面&lt;/th&gt;&lt;th&gt;行为&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;RSS 订阅&lt;/td&gt;&lt;td&gt;标题前加  前缀，内容替换为&quot;此文章已加密&quot;提示&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SEO / meta&lt;/td&gt;&lt;td&gt;description 使用 frontmatter 中的 &lt;code&gt;description&lt;/code&gt;（若未设置则显示通用加密提示）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;搜索索引&lt;/td&gt;&lt;td&gt;加密内容不会被 Pagefind 索引&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;目录导航&lt;/td&gt;&lt;td&gt;解锁前不显示，解锁后自动重建&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AI 摘要&lt;/td&gt;&lt;td&gt;基于加密前的原文生成（构建时可访问明文）&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;其他增强：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;自动目录生成&lt;/li&gt;
&lt;li&gt;阅读时间计算&lt;/li&gt;
&lt;li&gt;外部链接自动添加 &lt;code&gt;target=&quot;_blank&quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;多语言支持（i18n）&lt;a href=&quot;#多语言支持i18n&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;博客内置完整的国际化支持，可轻松添加多种语言。&lt;/p&gt;
&lt;h4&gt;基本配置&lt;a href=&quot;#基本配置-1&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;在 &lt;code&gt;config/site.yaml&lt;/code&gt; 的 &lt;code&gt;i18n&lt;/code&gt; 部分配置支持的语言：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;i18n&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  defaultLocale&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;zh&lt;/span&gt;&lt;span&gt;        # 默认语言（URL 无前缀）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  locales&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;code&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;zh&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;中文&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;code&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;en&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;English&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      # enabled: false     # 设为 false 可暂时禁用（保留内容但不生成路由）&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置多语言后：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;默认语言的页面 URL 不带前缀（如 &lt;code&gt;/post/hello&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;其他语言自动加前缀（如 &lt;code&gt;/en/post/hello&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;导航栏（桌面端）和移动端抽屉中自动显示语言切换器&lt;/li&gt;
&lt;li&gt;每个语言各自生成独立的 RSS 订阅源（如 &lt;code&gt;/en/rss.xml&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;HTML &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 中自动输出 hreflang 标签，有利于 SEO&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果只配置了一种语言，i18n 功能不会激活，不会产生额外的路由或 UI 元素。&lt;/p&gt;
&lt;h4&gt;翻译体系&lt;a href=&quot;#翻译体系&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;i18n 系统采用两层翻译架构：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. UI 字符串（TypeScript）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;界面上的按钮文字、提示信息等 UI 文本通过 TypeScript 翻译字典管理，位于 &lt;code&gt;src/i18n/translations/&lt;/code&gt; 目录：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;zh.ts&lt;/code&gt;：默认语言（中文），包含所有翻译 key（约 170 个），是唯一的 source-of-truth&lt;/li&gt;
&lt;li&gt;&lt;code&gt;en.ts&lt;/code&gt;：英文翻译，只需提供需要覆盖的 key，未提供的自动回退到中文&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;翻译字符串支持 &lt;code&gt;{param}&lt;/code&gt; 占位符插值：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// zh.ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&apos;post.totalPosts&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;共 {count} 篇文章&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// en.ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&apos;post.totalPosts&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;{count} posts&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 内容字符串（YAML）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;分类名、系列名、精选分类描述等内容级文本通过 &lt;code&gt;config/i18n-content.yaml&lt;/code&gt; 管理。默认语言的值直接读取 &lt;code&gt;config/site.yaml&lt;/code&gt;，此文件仅存放非默认语言的翻译：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;en&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  categories&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    life&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Life&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    note&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Notes&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    tools&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Tools&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  series&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    weekly&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;My Weekly&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      fullName&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;My Tech Weekly&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  featuredCategories&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    life&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Life&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Life journals and essays&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;categories&lt;/code&gt; 的 key 是分类的 URL slug（对应 &lt;code&gt;config/site.yaml&lt;/code&gt; 中 &lt;code&gt;categoryMap&lt;/code&gt; 的值），&lt;code&gt;series&lt;/code&gt; 的 key 是系列的 slug。&lt;/p&gt;
&lt;h4&gt;添加翻译文章&lt;a href=&quot;#添加翻译文章&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;将翻译文章放在 &lt;code&gt;src/content/blog/&amp;lt;locale&amp;gt;/&lt;/code&gt; 目录下，保持与默认语言相同的路径结构：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;src/content/blog/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── life/hello-world.md            # 默认语言 (zh)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── tools/getting-started.md       # 默认语言 (zh)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── en/life/hello-world.md         # 英文翻译&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└── en/tools/getting-started.md    # 英文翻译&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;回退机制&lt;/strong&gt;：当用户切换到非默认语言时，系统会显示该语言已有的翻译文章，对于尚未翻译的文章则自动回退显示默认语言内容，并在文章顶部标注提示信息。&lt;/p&gt;
&lt;h4&gt;添加新语言&lt;a href=&quot;#添加新语言&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;以添加日语（ja）为例：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在 &lt;code&gt;config/site.yaml&lt;/code&gt; 的 &lt;code&gt;i18n.locales&lt;/code&gt; 中添加新语言：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;i18n&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  locales&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;code&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;zh&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;中文&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;code&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;en&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;English&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;code&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ja&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;日本語&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;创建 UI 翻译文件 &lt;code&gt;src/i18n/translations/ja.ts&lt;/code&gt;：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt; { UIStrings } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;../types&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; uiStrings&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; UIStrings&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &apos;nav.home&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;ホーム&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &apos;common.search&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;検索&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // ... 按需翻译，未提供的 key 自动回退到中文&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;在 &lt;code&gt;src/i18n/translations/index.ts&lt;/code&gt; 中注册：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { uiStrings &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; ja } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;./ja&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; translations&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Record&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;DefaultUIStrings&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; UIStrings&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  zh,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  en,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ja, &lt;/span&gt;&lt;span&gt;// 新增&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4.（可选）在 &lt;code&gt;config/i18n-content.yaml&lt;/code&gt; 中添加日语的内容翻译。&lt;/p&gt;
&lt;p&gt;5.（可选）在 &lt;code&gt;src/content/blog/ja/&lt;/code&gt; 目录下添加日语文章。&lt;/p&gt;
&lt;h4&gt;在组件中使用翻译&lt;a href=&quot;#在组件中使用翻译&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Astro 组件&lt;/strong&gt;（&lt;code&gt;.astro&lt;/code&gt; 文件）中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { getLocaleFromUrl, t, localizedPath } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;@/i18n&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; locale&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; getLocaleFromUrl&lt;/span&gt;&lt;span&gt;(Astro.url.pathname);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;{&lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;(locale, &lt;/span&gt;&lt;span&gt;&apos;post.totalPosts&apos;&lt;/span&gt;&lt;span&gt;, { count: &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt; })}&amp;lt;/&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; href&lt;/span&gt;&lt;span&gt;={&lt;/span&gt;&lt;span&gt;localizedPath&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;/archives&apos;&lt;/span&gt;&lt;span&gt;, locale)}&amp;gt;归档&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;React 组件&lt;/strong&gt;（&lt;code&gt;.tsx&lt;/code&gt; 文件）中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { useTranslation } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;@hooks/useTranslation&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; MyComponent&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;locale&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useTranslation&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;{&lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;common.search&apos;&lt;/span&gt;&lt;span&gt;)}&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;RSS 订阅&lt;a href=&quot;#rss-订阅&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;访问 &lt;code&gt;/rss.xml&lt;/code&gt; 获取 RSS feed。启用多语言时，每个语言有独立的 RSS 源（如 &lt;code&gt;/en/rss.xml&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;包含内容：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最新文章列表&lt;/li&gt;
&lt;li&gt;文章摘要&lt;/li&gt;
&lt;li&gt;发布日期&lt;/li&gt;
&lt;li&gt;文章链接&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;数据统计&lt;a href=&quot;#数据统计&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;集成 Umami 分析（可选）。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;config/site.yaml&lt;/code&gt; 中配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;analytics&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  umami&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    enabled&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;your-umami-id&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    endpoint&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;https://stats.example.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;开发指南&lt;a href=&quot;#开发指南&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;目录结构&lt;a href=&quot;#目录结构&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;astro-koharu/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── src/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── components/      # 组件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   ├── common/      # 通用组件（错误边界等）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   ├── ui/          # UI 组件（按钮、卡片等）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   ├── layout/      # 布局组件（头部、侧边栏等）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   ├── post/        # 文章相关组件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   ├── category/    # 分类组件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   └── theme/       # 主题切换&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── content/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   └── blog/        # 博客文章（Markdown）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │       └── en/      # 英文翻译文章（按 locale 子目录组织）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── i18n/            # 国际化模块&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   ├── config.ts    # locale 配置（读取 site.yaml）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   ├── utils.ts     # 翻译函数、URL 工具&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   ├── content.ts   # 内容翻译加载（读取 i18n-content.yaml）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   └── translations/  # UI 翻译字典（zh.ts, en.ts）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── layouts/         # 页面布局模板&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── pages/           # 页面路由&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   └── [lang]/      # 非默认语言的镜像路由&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── lib/             # 工具函数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── hooks/           # React hooks&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── constants/       # 常量配置&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── store/           # 全局状态（nanostores）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── scripts/         # 构建脚本&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── styles/          # 全局样式&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   └── types/           # TypeScript 类型定义&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── public/              # 静态资源&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   └── img/             # 图片资源&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── config/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── site.yaml        # 站点配置（含分类映射、i18n 配置）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   └── i18n-content.yaml  # 内容级翻译（分类名、系列名等）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── astro.config.mjs     # Astro 配置&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── tailwind.config.mjs  # Tailwind 配置&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└── tsconfig.json        # TypeScript 配置&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;路径别名&lt;a href=&quot;#路径别名&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;项目配置了以下路径别名（在 &lt;code&gt;tsconfig.json&lt;/code&gt; 中）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { something } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;@/xxx&quot;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// → src/xxx&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; Component &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;@components/xxx&quot;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// → src/components/xxx&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { util } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;@lib/xxx&quot;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// → src/lib/xxx&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; config &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;@constants/xxx&quot;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// → src/constants/xxx&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// ... 更多别名见 tsconfig.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;常用命令&lt;a href=&quot;#常用命令&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 开发&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; dev&lt;/span&gt;&lt;span&gt;              # 启动开发服务器（默认 localhost:4321）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 构建&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; build&lt;/span&gt;&lt;span&gt;            # 构建生产版本&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; preview&lt;/span&gt;&lt;span&gt;          # 预览生产构建&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 代码质量&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; lint&lt;/span&gt;&lt;span&gt;             # 运行 ESLint&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; lint-md&lt;/span&gt;&lt;span&gt;          # 检查 Markdown 文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; lint-md:fix&lt;/span&gt;&lt;span&gt;      # 自动修复 Markdown 问题&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; knip&lt;/span&gt;&lt;span&gt;             # 查找未使用的文件和依赖&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# Koharu CLI&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt;                   # 交互式主菜单&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt;               # 新建内容（交互式选择）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; post&lt;/span&gt;&lt;span&gt;          # 新建博客文章&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; friend&lt;/span&gt;&lt;span&gt;        # 新建友情链接&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; backup&lt;/span&gt;&lt;span&gt;            # 备份博客内容（--full 完整备份）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; restore&lt;/span&gt;&lt;span&gt;           # 还原备份（--latest, --dry-run）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; update&lt;/span&gt;&lt;span&gt;            # 更新主题（--check, --clean, --rebase, --tag, --dry-run 等）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; generate&lt;/span&gt;&lt;span&gt;          # 生成内容资产（交互式选择）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; generate&lt;/span&gt;&lt;span&gt; lqips&lt;/span&gt;&lt;span&gt;    # 生成 LQIP 占位符&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; generate&lt;/span&gt;&lt;span&gt; similarities&lt;/span&gt;&lt;span&gt;  # 生成相似度向量&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; generate&lt;/span&gt;&lt;span&gt; summaries&lt;/span&gt;&lt;span&gt;     # 生成 AI 摘要&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; generate&lt;/span&gt;&lt;span&gt; all&lt;/span&gt;&lt;span&gt;      # 生成全部资产&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; clean&lt;/span&gt;&lt;span&gt;             # 清理旧备份（--keep N）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; list&lt;/span&gt;&lt;span&gt;              # 查看所有备份&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 工具&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; change&lt;/span&gt;&lt;span&gt;           # 生成 CHANGELOG.md（基于 git-cliff）&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Docker 部署&lt;a href=&quot;#docker-部署&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;astro-koharu 支持通过 Docker 进行容器化部署，适合需要自托管的场景。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;快速开始：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 1. 编辑 config/site.yaml，配置 comment.remark42 和 analytics.umami 部分&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 2. 构建并启动&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; compose&lt;/span&gt;&lt;span&gt; -f&lt;/span&gt;&lt;span&gt; docker/docker-compose.yml&lt;/span&gt;&lt;span&gt; up&lt;/span&gt;&lt;span&gt; -d&lt;/span&gt;&lt;span&gt; --build&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 3. 访问&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;open&lt;/span&gt;&lt;span&gt; http://localhost:4321&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;目录结构：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;docker/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── Dockerfile           # 多阶段构建配置&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── docker-compose.yml   # 编排配置&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── nginx/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   └── default.conf     # Nginx 静态服务配置&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└── rebuild.sh           # 便捷重建脚本&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;关于生成脚本：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;以下脚本&lt;strong&gt;需要在本地运行&lt;/strong&gt;，不能在 Docker 构建时执行：&lt;/p&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;脚本&lt;/th&gt;&lt;th&gt;原因&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;pnpm generate:lqips&lt;/code&gt;&lt;/td&gt;&lt;td&gt;使用 &lt;code&gt;sharp&lt;/code&gt; 原生模块处理图片&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;pnpm generate:similarities&lt;/code&gt;&lt;/td&gt;&lt;td&gt;需下载 500MB+ 的 ML 模型&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;pnpm generate:summaries&lt;/code&gt;&lt;/td&gt;&lt;td&gt;需连接本地 LLM 服务器&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;推荐工作流：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 本地开发：添加新图片或文章后&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; generate:all&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 提交生成的数据文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; add&lt;/span&gt;&lt;span&gt; src/assets/&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;.json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; commit&lt;/span&gt;&lt;span&gt; -m&lt;/span&gt;&lt;span&gt; &quot;chore: update generated assets&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 重建 Docker 容器&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;./docker/rebuild.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用 rebuild.sh：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; docker&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;./rebuild.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;该脚本会：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;检查环境变量文件&lt;/li&gt;
&lt;li&gt;停止现有容器&lt;/li&gt;
&lt;li&gt;重新构建并启动&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;评论与统计配置：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;config/site.yaml&lt;/code&gt; 中配置评论系统和统计：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 评论系统（可选）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;comment&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  remark42&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    enabled&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    host&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;https://your-remark-server.com/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    siteId&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;your-site-id&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 统计系统（可选）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;analytics&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  umami&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    enabled&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;your-umami-id&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    endpoint&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;https://your-umami-server.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker 端口可在 &lt;code&gt;.env&lt;/code&gt; 中配置 &lt;code&gt;BLOG_PORT=4321&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意事项：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;生成的 JSON 文件必须提交到 git，Docker 构建时会直接使用&lt;/li&gt;
&lt;li&gt;如果忘记运行生成脚本，相关功能（LQIP 占位符、相关文章推荐等）将不可用&lt;/li&gt;
&lt;li&gt;Docker 镜像基于 nginx&lt;div&gt;&lt;/div&gt;，仅包含静态文件，无需 Node.js 运行时&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Koharu CLI&lt;a href=&quot;#koharu-cli&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;博客自带交互式命令行工具，提供备份还原、主题更新、内容生成、新建内容等功能。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;启动方式：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt;              # 交互式主菜单&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;新建内容&lt;a href=&quot;#新建内容&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;使用 CLI 快速创建博客文章和友链：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 交互式选择创建类型（文章或友链）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 或直接指定类型&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; post&lt;/span&gt;&lt;span&gt;     # 新建博客文章&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; friend&lt;/span&gt;&lt;span&gt;   # 新建友情链接&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;新建博客文章功能：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;交互式输入文章信息：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;标题&lt;/strong&gt; - 文章标题（必填）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slug&lt;/strong&gt; - 自定义 URL（可选，默认根据标题自动生成拼音）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;描述&lt;/strong&gt; - 文章摘要（可选）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分类&lt;/strong&gt; - 从已有分类中选择（必选）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标签&lt;/strong&gt; - 添加标签，逗号分隔（可选）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;草稿&lt;/strong&gt; - 是否保存为草稿（默认否）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;自动生成 frontmatter（包含 title、date、categories、tags 等）&lt;/li&gt;
&lt;li&gt;检查文件是否已存在，避免覆盖&lt;/li&gt;
&lt;li&gt;文章保存在对应的分类目录下（如 &lt;code&gt;src/content/blog/note/front-end/my-post.md&lt;/code&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;新建友情链接功能：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;交互式输入友链信息：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;站点名称&lt;/strong&gt; - 友站的名称（必填）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;站点 URL&lt;/strong&gt; - 友站的链接（必填，需完整 URL）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;站长昵称&lt;/strong&gt; - 友站站长的昵称（必填）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;站点描述&lt;/strong&gt; - 友站的简介（必填）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;头像 URL&lt;/strong&gt; - 友站的头像链接（必填）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;主题色&lt;/strong&gt; - 友站的主题色（可选，可选择预设颜色或自定义十六进制）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;自动追加到 &lt;code&gt;config/site.yaml&lt;/code&gt; 的 &lt;code&gt;friends.data&lt;/code&gt; 数组&lt;/li&gt;
&lt;li&gt;保留 YAML 文件的格式和注释&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;使用示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 创建新文章&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; post&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 按提示输入：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 标题: React Hooks 使用指南&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# Slug: (自动生成 react-hooks-shi-yong-zhi-nan，可修改或清空)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 描述: React Hooks 的完整使用教程&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 分类: 选择&quot;笔记 → 前端&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 标签: React, Hooks, 教程&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 草稿: 否&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 创建友链&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; friend&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 按提示输入友站信息&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;备份与还原&lt;a href=&quot;#备份与还原&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;更新主题前，建议先备份你的个人内容：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 基础备份（博客文章、配置、头像、.env）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; backup&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 完整备份（包含所有图片和生成的资产）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; backup&lt;/span&gt;&lt;span&gt; --full&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 查看所有备份&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; list&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 还原最新备份&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; restore&lt;/span&gt;&lt;span&gt; --latest&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 预览将要还原的文件（不实际还原）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; restore&lt;/span&gt;&lt;span&gt; --dry-run&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 强制还原（覆盖已存在的文件）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; restore&lt;/span&gt;&lt;span&gt; --force&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 清理旧备份（保留最近 5 个）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; clean&lt;/span&gt;&lt;span&gt; --keep&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;备份文件存储在 &lt;code&gt;backups/&lt;/code&gt; 目录，格式为 &lt;code&gt;backup-YYYY-MM-DD-HHMMSS.tar.gz&lt;/code&gt;。&lt;/p&gt;
&lt;h4&gt;更新主题&lt;a href=&quot;#更新主题&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;使用 CLI 自动更新主题，完成备份 → 拉取 → 合并 → 安装依赖的完整流程：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 完整更新流程（默认会先备份）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; update&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 仅检查是否有更新&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; update&lt;/span&gt;&lt;span&gt; --check&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 跳过备份直接更新&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; update&lt;/span&gt;&lt;span&gt; --skip-backup&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 强制模式（跳过工作区检查和确认）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; update&lt;/span&gt;&lt;span&gt; --force&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 更新到指定版本（如 v2.1.0）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; update&lt;/span&gt;&lt;span&gt; --tag&lt;/span&gt;&lt;span&gt; v2.1.0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# clean 模式（零冲突，强制备份，适合首次迁移或冲突较多时）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; update&lt;/span&gt;&lt;span&gt; --clean&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# rebase 模式（重写历史，强制备份，适合熟悉 git 的用户）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; update&lt;/span&gt;&lt;span&gt; --rebase&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 预览操作（不实际执行，可配合 --clean 或 --rebase 使用）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; update&lt;/span&gt;&lt;span&gt; --dry-run&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;选项说明：&lt;/strong&gt;&lt;/p&gt;





































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;选项&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;--check&lt;/code&gt;&lt;/td&gt;&lt;td&gt;仅检查更新，不执行合并&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;--skip-backup&lt;/code&gt;&lt;/td&gt;&lt;td&gt;跳过备份步骤（clean/rebase 模式下无效，强制备份）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;--force&lt;/code&gt;&lt;/td&gt;&lt;td&gt;跳过工作区脏检查和确认提示（不影响合并方式）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;--tag&lt;/code&gt;&lt;/td&gt;&lt;td&gt;指定目标版本（如 &lt;code&gt;v2.1.0&lt;/code&gt;），支持升级和降级&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;--clean&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Clean 模式，零冲突更新（替换主题文件 + 还原用户内容）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;--rebase&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Rebase 模式，重写历史完全同步上游（强制要求备份）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;--dry-run&lt;/code&gt;&lt;/td&gt;&lt;td&gt;预览操作，不实际执行&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;三种更新模式：&lt;/strong&gt;&lt;/p&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;模式&lt;/th&gt;&lt;th&gt;命令&lt;/th&gt;&lt;th&gt;适合场景&lt;/th&gt;&lt;th&gt;备份&lt;/th&gt;&lt;th&gt;冲突处理&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;默认&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;pnpm koharu update&lt;/code&gt;&lt;/td&gt;&lt;td&gt;日常更新&lt;/td&gt;&lt;td&gt;可选&lt;/td&gt;&lt;td&gt;用户内容自动保留，主题冲突手动解决&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Clean&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;--clean&lt;/code&gt;&lt;/td&gt;&lt;td&gt;首次迁移、冲突较多&lt;/td&gt;&lt;td&gt;强制&lt;/td&gt;&lt;td&gt;零冲突&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Rebase&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;--rebase&lt;/code&gt;&lt;/td&gt;&lt;td&gt;熟悉 git 的用户&lt;/td&gt;&lt;td&gt;强制&lt;/td&gt;&lt;td&gt;需手动解决&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;默认模式（Merge）：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;git merge --no-ff&lt;/code&gt; 合并上游更新，保留 merge-base 信息，让后续更新的冲突更少。&lt;/p&gt;
&lt;p&gt;智能冲突处理：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;用户内容文件&lt;/strong&gt;（博客文章、配置、独立页面、图片等）发生冲突时，自动保留本地版本&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;主题文件&lt;/strong&gt;（组件、脚本、样式等）发生冲突时，需要手动解决&lt;/li&gt;
&lt;li&gt;如果所有冲突都是用户内容 → 整个更新自动完成，零手动操作&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; &lt;strong&gt;提交格式：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;chore: merge upstream theme v2.3.2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Clean 模式：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;用上游最新版本&lt;strong&gt;替换所有主题文件&lt;/strong&gt;，然后从备份&lt;strong&gt;还原用户内容&lt;/strong&gt;，实现零冲突更新。&lt;/p&gt;
&lt;p&gt;执行流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;强制备份你的博客内容&lt;/li&gt;
&lt;li&gt;创建 merge commit 记录版本关系&lt;/li&gt;
&lt;li&gt;用上游文件覆盖本地所有文件&lt;/li&gt;
&lt;li&gt;从刚才的备份还原用户内容（博客文章、配置、独立页面、图片、.env）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;⚠️ &lt;strong&gt;注意：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你对&lt;strong&gt;主题文件&lt;/strong&gt;的自定义修改（如改了某个组件、布局、样式）&lt;strong&gt;不会被保留&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;备份范围包含：&lt;code&gt;src/content/blog/&lt;/code&gt;、&lt;code&gt;config/site.yaml&lt;/code&gt;、&lt;code&gt;src/pages/*.md&lt;/code&gt;、&lt;code&gt;public/img/&lt;/code&gt;、&lt;code&gt;.env&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;--full&lt;/code&gt; 备份选项可额外保留 favicon、LQIP、相似度、AI 摘要等生成资产&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;适用场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首次从旧版本迁移，历史冲突太多无法正常 merge&lt;/li&gt;
&lt;li&gt;没有自定义主题文件，只写了博客内容&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Rebase 模式：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;执行 &lt;code&gt;git rebase upstream/main&lt;/code&gt;（或指定的 tag），将本地提交重放到上游之上。适合熟悉 git 操作的用户。&lt;/p&gt;
&lt;p&gt;⚠️ &lt;strong&gt;注意&lt;/strong&gt;：Rebase 模式会重写 Git 历史，请确保已备份重要内容。CLI 会强制要求备份（忽略 &lt;code&gt;--skip-backup&lt;/code&gt; 和 &lt;code&gt;--force&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用 &lt;code&gt;--dry-run&lt;/code&gt; 预览：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;所有模式都支持 &lt;code&gt;--dry-run&lt;/code&gt; 预览操作效果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; update&lt;/span&gt;&lt;span&gt; --dry-run&lt;/span&gt;&lt;span&gt;          # 预览默认 merge&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; update&lt;/span&gt;&lt;span&gt; --clean&lt;/span&gt;&lt;span&gt; --dry-run&lt;/span&gt;&lt;span&gt;  # 预览 clean 模式&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; update&lt;/span&gt;&lt;span&gt; --rebase&lt;/span&gt;&lt;span&gt; --dry-run&lt;/span&gt;&lt;span&gt; # 预览 rebase 操作&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt; 给熟悉 git 的用户：&lt;/strong&gt; CLI 更新命令是对 git 操作的封装便利工具。如果你对 git 比较熟悉，建议直接使用 &lt;code&gt;git fetch upstream &amp;amp;&amp;amp; git rebase upstream/main&lt;/code&gt; 手动操作，这样能更精确地控制合并过程。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;更新流程说明：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;检查 Git 状态&lt;/strong&gt; — 确保工作区干净（无未提交的更改）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;备份当前内容&lt;/strong&gt; — 可选（clean/rebase 模式强制备份）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;设置 upstream&lt;/strong&gt; — 自动添加 &lt;code&gt;upstream&lt;/code&gt; remote（如果不存在）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;获取最新代码&lt;/strong&gt; — &lt;code&gt;git fetch upstream&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;显示更新预览&lt;/strong&gt; — 列出新增的提交和更新日志&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;执行更新&lt;/strong&gt; — 根据所选模式执行 merge / clean / rebase&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安装依赖&lt;/strong&gt; — &lt;code&gt;pnpm install&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;处理合并冲突：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;默认 merge 模式下，用户内容冲突会被自动解决（保留本地版本）。如果主题文件仍有冲突，CLI 会显示冲突文件列表。你可以：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;选择&quot;中止合并&quot;恢复到更新前状态&lt;/li&gt;
&lt;li&gt;手动解决冲突后运行 &lt;code&gt;git add . &amp;amp;&amp;amp; git commit&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 如果选择手动解决冲突&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; status&lt;/span&gt;&lt;span&gt;                    # 查看冲突文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 编辑冲突文件，保留需要的内容&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; add&lt;/span&gt;&lt;span&gt; .&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; commit&lt;/span&gt;&lt;span&gt; -m&lt;/span&gt;&lt;span&gt; &quot;merge: resolve conflicts&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;更新时使用的 Git 命令：&lt;/strong&gt;&lt;/p&gt;





















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;操作&lt;/th&gt;&lt;th&gt;命令&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;检查工作区状态&lt;/td&gt;&lt;td&gt;&lt;code&gt;git status --porcelain&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;获取当前分支&lt;/td&gt;&lt;td&gt;&lt;code&gt;git rev-parse --abbrev-ref HEAD&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;检查 upstream&lt;/td&gt;&lt;td&gt;&lt;code&gt;git remote -v&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;添加 upstream&lt;/td&gt;&lt;td&gt;&lt;code&gt;git remote add upstream https://github.com/cosZone/astro-koharu.git&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;获取更新&lt;/td&gt;&lt;td&gt;&lt;code&gt;git fetch upstream&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;查看新提交数量&lt;/td&gt;&lt;td&gt;&lt;code&gt;git rev-list --left-right --count HEAD...upstream/main&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;查看新提交列表&lt;/td&gt;&lt;td&gt;&lt;code&gt;git log HEAD..upstream/main --pretty=format:&quot;%h | %s | %ar | %an&quot;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;默认合并&lt;/td&gt;&lt;td&gt;&lt;code&gt;git merge --no-ff upstream/main&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Clean 合并&lt;/td&gt;&lt;td&gt;&lt;code&gt;git merge -s ours upstream/main&lt;/code&gt; + &lt;code&gt;git checkout upstream/main -- .&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Rebase&lt;/td&gt;&lt;td&gt;&lt;code&gt;git rebase upstream/main&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;中止合并&lt;/td&gt;&lt;td&gt;&lt;code&gt;git merge --abort&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h4&gt;内容生成&lt;a href=&quot;#内容生成&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;生成各种内容资产：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 交互式选择生成类型&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; generate&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 或直接指定类型&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; generate&lt;/span&gt;&lt;span&gt; lqips&lt;/span&gt;&lt;span&gt;        # 生成 LQIP 图片占位符&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; generate&lt;/span&gt;&lt;span&gt; similarities&lt;/span&gt;&lt;span&gt; # 生成语义相似度向量&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; generate&lt;/span&gt;&lt;span&gt; summaries&lt;/span&gt;&lt;span&gt;    # 生成 AI 摘要&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; koharu&lt;/span&gt;&lt;span&gt; generate&lt;/span&gt;&lt;span&gt; all&lt;/span&gt;&lt;span&gt;          # 生成全部&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;如何添加新页面&lt;a href=&quot;#如何添加新页面&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;在 &lt;code&gt;src/pages/&lt;/code&gt; 目录创建 &lt;code&gt;.astro&lt;/code&gt; 文件&lt;/li&gt;
&lt;li&gt;Astro 使用文件系统路由，文件路径即 URL 路径&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;src/pages/about.astro       → /about&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;src/pages/tags/[tag].astro  → /tags/:tag（动态路由）&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;如何自定义样式&lt;a href=&quot;#如何自定义样式&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;全局样式：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;编辑 &lt;code&gt;src/styles/index.css&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;组件样式：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用 Tailwind CSS 工具类或 Astro 的 &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; 标签。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tailwind 配置：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;编辑 &lt;code&gt;tailwind.config.ts&lt;/code&gt; 自定义主题、颜色、字体等。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;主题变量：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;src/styles/index.css&lt;/code&gt; 中定义的 CSS 变量：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;:root&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  --primary-color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;#ff6b6b&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* ... 更多变量 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;动画系统&lt;a href=&quot;#动画系统&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 CSS 动画以及 &lt;a href=&quot;https://motion.dev/&quot;&gt;Motion&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;动画配置：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;src/constants/anim/&lt;/code&gt; 目录中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;spring.ts&lt;/code&gt; - 弹簧动画配置&lt;/li&gt;
&lt;li&gt;&lt;code&gt;variants.ts&lt;/code&gt; - 动画变体定义&lt;/li&gt;
&lt;li&gt;&lt;code&gt;props.ts&lt;/code&gt; - 可复用的动画属性&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;使用示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { motion } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;motion/react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { fadeIn } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;@constants/anim/variants&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;motion.div&lt;/span&gt;&lt;span&gt; variants&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{fadeIn} &lt;/span&gt;&lt;span&gt;initial&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;hidden&quot;&lt;/span&gt;&lt;span&gt; animate&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;visible&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  内容&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;motion.div&lt;/span&gt;&lt;span&gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;与 Hexo/Shoka 主题的对比&lt;a href=&quot;#与-hexoshoka-主题的对比&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;保留的特性&lt;a href=&quot;#保留的特性&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;✅ 分类和标签系统&lt;/li&gt;
&lt;li&gt;✅ 文章置顶功能&lt;/li&gt;
&lt;li&gt;✅ 深色/浅色主题切换&lt;/li&gt;
&lt;li&gt;✅ 响应式设计&lt;/li&gt;
&lt;li&gt;✅ 友链页面&lt;/li&gt;
&lt;li&gt;✅ 归档页面&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;改进之处&lt;a href=&quot;#改进之处&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;性能：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;⚡ 静态站点生成 (SSG)，加载速度更快&lt;/li&gt;
&lt;li&gt;⚡ 按需加载 JavaScript&lt;/li&gt;
&lt;li&gt;⚡ 图片优化&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;开发体验：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;️ TypeScript 类型安全&lt;/li&gt;
&lt;li&gt;️ 热模块替换 (HMR)&lt;/li&gt;
&lt;li&gt;️ 现代化的开发工具链&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;功能增强：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt; 更强大的全站搜索（Pagefind）&lt;/li&gt;
&lt;li&gt; 内容集合 (Content Collections) 类型安全&lt;/li&gt;
&lt;li&gt; Tailwind CSS 4.x 样式系统&lt;/li&gt;
&lt;li&gt; View Transitions API 页面过渡&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;技术栈差异&lt;a href=&quot;#技术栈差异&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;方面&lt;/th&gt;&lt;th&gt;Hexo + Shoka&lt;/th&gt;&lt;th&gt;astro-koharu&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;框架&lt;/td&gt;&lt;td&gt;Hexo (Node.js)&lt;/td&gt;&lt;td&gt;Astro 5.x&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;模板引擎&lt;/td&gt;&lt;td&gt;EJS/Pug&lt;/td&gt;&lt;td&gt;Astro + React&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;样式&lt;/td&gt;&lt;td&gt;Stylus&lt;/td&gt;&lt;td&gt;Tailwind CSS 4.x&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;构建工具&lt;/td&gt;&lt;td&gt;Webpack&lt;/td&gt;&lt;td&gt;Vite&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;类型检查&lt;/td&gt;&lt;td&gt;无&lt;/td&gt;&lt;td&gt;TypeScript&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;内容管理&lt;/td&gt;&lt;td&gt;文件系统&lt;/td&gt;&lt;td&gt;Content Collections&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;常见问题&lt;a href=&quot;#常见问题&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;如何修改封面图片？&lt;a href=&quot;#如何修改封面图片&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在文章 frontmatter 中设置 &lt;code&gt;cover&lt;/code&gt; 字段：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;cover&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;/img/cover/1.webp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;图片放在 &lt;code&gt;public/img/&lt;/code&gt; 目录。如果不设置，会使用默认封面。&lt;/p&gt;
&lt;h3&gt;如何自定义域名？&lt;a href=&quot;#如何自定义域名&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;部署到 Vercel 后，在 Vercel 项目设置中添加自定义域名，然后更新 &lt;code&gt;config/site.yaml&lt;/code&gt; 中的 &lt;code&gt;site.url&lt;/code&gt; 字段。&lt;/p&gt;
&lt;h3&gt;如何添加评论功能？&lt;a href=&quot;#如何添加评论功能&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;项目支持三种评论系统：&lt;strong&gt;Waline&lt;/strong&gt;、&lt;strong&gt;Giscus&lt;/strong&gt;、&lt;strong&gt;Remark42&lt;/strong&gt;。在 &lt;code&gt;config/site.yaml&lt;/code&gt; 的 &lt;code&gt;comment&lt;/code&gt; 配置块中选择使用的提供商。&lt;/p&gt;
&lt;h4&gt;Waline（推荐）&lt;a href=&quot;#waline推荐&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://waline.js.org/&quot;&gt;Waline&lt;/a&gt; 是一个简洁、安全的评论系统，支持多种部署方式（Vercel、Railway、Zeabur 等）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;特点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt; 部署简单，支持多种平台一键部署&lt;/li&gt;
&lt;li&gt; 支持 Markdown、表情、@提及、邮件通知&lt;/li&gt;
&lt;li&gt; 内置浏览量统计、评论管理后台&lt;/li&gt;
&lt;li&gt; 支持多种登录方式（匿名、社交账号）&lt;/li&gt;
&lt;li&gt;️ 内置反垃圾评论、敏感词过滤&lt;/li&gt;
&lt;li&gt; 自动跟随站点深色/浅色主题&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;前置要求：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;部署 Waline 服务端（&lt;a href=&quot;https://waline.js.org/guide/deploy/&quot;&gt;部署指南&lt;/a&gt;）&lt;/li&gt;
&lt;li&gt;获取服务端 URL&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;配置示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;comment&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  provider&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;waline&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  waline&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    serverURL&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;https://your-waline-server.vercel.app&lt;/span&gt;&lt;span&gt; # Waline 服务端地址（必填）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    lang&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;zh-CN&lt;/span&gt;&lt;span&gt; # 语言&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    dark&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;html.dark&lt;/span&gt;&lt;span&gt; # 暗黑模式 CSS 选择器&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    meta&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;# 评论者信息字段&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;nick&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;mail&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;link&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    requiredMeta&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;# 必填字段&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - &lt;/span&gt;&lt;span&gt;nick&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    login&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;enable&lt;/span&gt;&lt;span&gt; # 登录模式 (&apos;enable&apos; | &apos;disable&apos; | &apos;force&apos;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    wordLimit&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; # 评论字数限制 (0 = 无限制)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    pageSize&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt; # 每页评论数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    imageUploader&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; # 图片上传功能&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    highlighter&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; # 代码高亮&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    texRenderer&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; # LaTeX 渲染&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    search&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; # 搜索功能&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    reaction&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; # 文章反应功能&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    # recaptchaV3Key: &apos;&apos; # reCAPTCHA v3 Key (可选)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    # turnstileKey: &apos;&apos; # Cloudflare Turnstile Key (可选)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;参数说明：&lt;/strong&gt;&lt;/p&gt;





































































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;参数&lt;/th&gt;&lt;th&gt;类型&lt;/th&gt;&lt;th&gt;默认值&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;serverURL&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;必填&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Waline 服务端地址&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;lang&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;&apos;zh-CN&apos;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;界面语言（支持 zh-CN, en, jp 等）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;dark&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;&apos;html.dark&apos;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;暗黑模式 CSS 选择器&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;meta&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;string[]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[&apos;nick&apos;,&apos;mail&apos;,&apos;link&apos;]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;评论者信息字段&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;requiredMeta&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;string[]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;[&apos;nick&apos;]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;必填字段&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;login&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;&apos;enable&apos;&lt;/code&gt; | &lt;code&gt;&apos;disable&apos;&lt;/code&gt; | &lt;code&gt;&apos;force&apos;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;&apos;enable&apos;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;登录模式&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;wordLimit&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;&lt;td&gt;评论字数限制（0 = 无限制）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;pageSize&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;number&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;10&lt;/code&gt;&lt;/td&gt;&lt;td&gt;每页评论数&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;imageUploader&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;&lt;td&gt;是否启用图片上传&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;highlighter&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;&lt;td&gt;是否启用代码高亮&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;texRenderer&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;&lt;td&gt;是否启用 LaTeX 渲染&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;search&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;&lt;td&gt;是否启用搜索功能&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;reaction&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;&lt;td&gt;是否启用文章反应功能&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;recaptchaV3Key&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;reCAPTCHA v3 Key（可选，防垃圾评论）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;turnstileKey&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;Cloudflare Turnstile Key（可选）&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;部署 Waline 服务端：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;推荐使用 Vercel 一键部署：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;访问 &lt;a href=&quot;https://waline.js.org/guide/get-started/&quot;&gt;Waline 快速开始&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;点击 &quot;Deploy with Vercel&quot; 按钮&lt;/li&gt;
&lt;li&gt;登录 Vercel，授权 GitHub 仓库&lt;/li&gt;
&lt;li&gt;配置环境变量（数据库连接、管理员邮箱等）&lt;/li&gt;
&lt;li&gt;部署完成后获取服务端 URL（如 &lt;code&gt;https://your-waline.vercel.app&lt;/code&gt;）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;主题自动切换：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Waline 组件已实现主题自动切换，通过 &lt;code&gt;dark&lt;/code&gt; 参数（默认 &lt;code&gt;html.dark&lt;/code&gt;）自动跟随站点深色/浅色模式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;参考链接：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://waline.js.org/&quot;&gt;Waline 官网&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://waline.js.org/guide/deploy/&quot;&gt;部署指南&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://waline.js.org/reference/client/&quot;&gt;配置参数&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Remark42&lt;a href=&quot;#remark42&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://remark42.com/&quot;&gt;Remark42&lt;/a&gt; 是一个轻量级的自托管评论系统，隐私友好，无需第三方服务。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;特点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt; 自托管，完全掌控数据&lt;/li&gt;
&lt;li&gt; 无广告、无追踪&lt;/li&gt;
&lt;li&gt; 支持多种存储后端（BoltDB、Memory）&lt;/li&gt;
&lt;li&gt; 支持多种社交登录（GitHub、Google、Twitter 等）&lt;/li&gt;
&lt;li&gt; 邮件通知、评论审核&lt;/li&gt;
&lt;li&gt; 自动跟随站点深色/浅色主题&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;前置要求：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;部署 Remark42 服务端（&lt;a href=&quot;https://remark42.com/docs/getting-started/installation/&quot;&gt;部署指南&lt;/a&gt;）&lt;/li&gt;
&lt;li&gt;配置站点 ID 和域名&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;配置示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;comment&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  provider&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;remark42&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  remark42&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    host&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;https://comment.example.com/&lt;/span&gt;&lt;span&gt; # Remark42 服务器地址（必填）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    siteId&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;your-site-id&lt;/span&gt;&lt;span&gt; # 站点 ID（必填）&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;参数说明：&lt;/strong&gt;&lt;/p&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;参数&lt;/th&gt;&lt;th&gt;类型&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;host&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Remark42 服务器地址（&lt;strong&gt;必填&lt;/strong&gt;，需带 &lt;code&gt;http://&lt;/code&gt; 或 &lt;code&gt;https://&lt;/code&gt;）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;siteId&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;&lt;td&gt;站点 ID（&lt;strong&gt;必填&lt;/strong&gt;，在 Remark42 服务端配置中定义）&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;部署 Remark42 服务端：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;推荐使用 Docker 部署：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; run&lt;/span&gt;&lt;span&gt; -d&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  --name&lt;/span&gt;&lt;span&gt; remark42&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -p&lt;/span&gt;&lt;span&gt; 8080:8080&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -e&lt;/span&gt;&lt;span&gt; REMARK_URL=https://comment.example.com&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -e&lt;/span&gt;&lt;span&gt; SECRET=your-secret-key&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -e&lt;/span&gt;&lt;span&gt; SITE=your-site-id&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -e&lt;/span&gt;&lt;span&gt; AUTH_GITHUB_CID=your-github-client-id&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -e&lt;/span&gt;&lt;span&gt; AUTH_GITHUB_CSEC=your-github-client-secret&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -v&lt;/span&gt;&lt;span&gt; /path/to/data:/srv/var&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  umputun/remark42:latest&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;详细配置请参考 &lt;a href=&quot;https://remark42.com/docs/getting-started/installation/&quot;&gt;Remark42 安装指南&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;主题自动切换：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Remark42 组件已实现主题自动切换，使用 &lt;code&gt;MutationObserver&lt;/code&gt; 监听站点主题变化，自动调用 &lt;code&gt;window.REMARK42.changeTheme()&lt;/code&gt; 更新评论框主题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;参考链接：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://remark42.com/&quot;&gt;Remark42 官网&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://remark42.com/docs/getting-started/installation/&quot;&gt;安装指南&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://remark42.com/docs/configuration/&quot;&gt;配置文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Giscus&lt;a href=&quot;#giscus&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://giscus.app&quot;&gt;Giscus&lt;/a&gt; 是基于 GitHub Discussions 的评论系统，无需自建后端，评论数据存储在你的 GitHub 仓库中。&lt;/p&gt;
&lt;p&gt;具体配置可以看看这篇文章：&lt;a href=&quot;https://zhuanlan.zhihu.com/p/693434928&quot;&gt;https://zhuanlan.zhihu.com/p/693434928&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;前置要求：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;仓库必须是&lt;a href=&quot;https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/setting-repository-visibility#making-a-repository-public&quot;&gt;公开的&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;安装 &lt;a href=&quot;https://github.com/apps/giscus&quot;&gt;giscus app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;在仓库中&lt;a href=&quot;https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/enabling-or-disabling-github-discussions-for-a-repository&quot;&gt;启用 Discussions 功能&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;获取配置参数：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;访问 &lt;a href=&quot;https://giscus.app/zh-CN&quot;&gt;giscus.app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;输入你的仓库名称（格式：&lt;code&gt;owner/repo&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;选择页面与 Discussion 的映射方式（推荐 &lt;code&gt;pathname&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;选择 Discussion 分类（推荐 &lt;code&gt;Announcements&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;启用所需功能（reactions、评论框位置等）&lt;/li&gt;
&lt;li&gt;复制生成的 &lt;code&gt;data-repo-id&lt;/code&gt; 和 &lt;code&gt;data-category-id&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;配置示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;comment&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  provider&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;giscus&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  giscus&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    repo&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;username/repo&lt;/span&gt;&lt;span&gt; # GitHub 仓库名 (owner/repo 格式)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    repoId&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;R_kgDOxxxxxx&lt;/span&gt;&lt;span&gt; # 仓库 ID (从 giscus.app 获取)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    category&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Announcements&lt;/span&gt;&lt;span&gt; # Discussion 分类名称&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    categoryId&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;DIC_kwDOxxxxxx&lt;/span&gt;&lt;span&gt; # 分类 ID (从 giscus.app 获取)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    mapping&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;pathname&lt;/span&gt;&lt;span&gt; # 映射方式&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    reactionsEnabled&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;1&quot;&lt;/span&gt;&lt;span&gt; # 启用 reactions (&apos;1&apos; 启用, &apos;0&apos; 禁用)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    emitMetadata&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;0&quot;&lt;/span&gt;&lt;span&gt; # 发送元数据&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    inputPosition&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;top&lt;/span&gt;&lt;span&gt; # 输入框位置 (&apos;top&apos; | &apos;bottom&apos;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    lang&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;zh-CN&lt;/span&gt;&lt;span&gt; # 语言&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;参数说明：&lt;/strong&gt;&lt;/p&gt;
















































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;参数&lt;/th&gt;&lt;th&gt;类型&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;repo&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;&lt;td&gt;GitHub 仓库，格式为 &lt;code&gt;owner/repo&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;repoId&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;&lt;td&gt;仓库 ID，从 giscus.app 获取&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;category&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Discussion 分类名称&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;categoryId&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;&lt;td&gt;分类 ID，从 giscus.app 获取&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;mapping&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;&lt;td&gt;页面与 Discussion 的映射方式&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;term&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;&lt;td&gt;当 &lt;code&gt;mapping&lt;/code&gt; 为 &lt;code&gt;specific&lt;/code&gt; 或 &lt;code&gt;number&lt;/code&gt; 时使用&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;strict&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;&apos;0&apos; | &apos;1&apos;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;严格匹配模式，默认 &lt;code&gt;&apos;0&apos;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;reactionsEnabled&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;&apos;0&apos; | &apos;1&apos;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;是否启用 reactions，默认 &lt;code&gt;&apos;1&apos;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;emitMetadata&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;&apos;0&apos; | &apos;1&apos;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;是否发送页面元数据，默认 &lt;code&gt;&apos;0&apos;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;inputPosition&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;&apos;top&apos; | &apos;bottom&apos;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;评论输入框位置，默认 &lt;code&gt;&apos;top&apos;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;lang&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;&lt;td&gt;界面语言，默认 &lt;code&gt;&apos;zh-CN&apos;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;host&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;&lt;td&gt;自托管 Giscus 实例的地址（可选）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;theme&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;string&lt;/code&gt;&lt;/td&gt;&lt;td&gt;固定主题（不设置则跟随站点主题切换）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;loading&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;&apos;lazy&apos; | &apos;eager&apos;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;加载方式，默认 &lt;code&gt;&apos;lazy&apos;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;映射方式说明：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pathname&lt;/code&gt;（推荐）：使用页面路径匹配，如 &lt;code&gt;/post/my-article&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;url&lt;/code&gt;：使用完整 URL 匹配&lt;/li&gt;
&lt;li&gt;&lt;code&gt;title&lt;/code&gt;：使用页面标题匹配&lt;/li&gt;
&lt;li&gt;&lt;code&gt;og:title&lt;/code&gt;：使用 Open Graph 标题匹配&lt;/li&gt;
&lt;li&gt;&lt;code&gt;specific&lt;/code&gt;：使用 &lt;code&gt;term&lt;/code&gt; 参数指定的值&lt;/li&gt;
&lt;li&gt;&lt;code&gt;number&lt;/code&gt;：使用 &lt;code&gt;term&lt;/code&gt; 参数指定的 Discussion 编号&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;主题自动切换：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;本主题已实现 Giscus 评论框的主题自动切换，会跟随站点的深色/浅色模式自动调整。实现原理：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;组件挂载时读取当前主题&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;MutationObserver&lt;/code&gt; 监听 &lt;code&gt;document.documentElement&lt;/code&gt; 的 &lt;code&gt;class&lt;/code&gt; 变化&lt;/li&gt;
&lt;li&gt;检测到主题切换时通过 &lt;code&gt;postMessage&lt;/code&gt; 通知 Giscus iframe 更新主题&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;参考链接：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://giscus.app/zh-CN&quot;&gt;giscus 官网&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/giscus/giscus-component&quot;&gt;giscus-component 文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;草稿文章如何预览？&lt;a href=&quot;#草稿文章如何预览&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;运行 &lt;code&gt;pnpm dev&lt;/code&gt; 本地开发模式，草稿会自动显示（带 DRAFT 标识）。&lt;/p&gt;
&lt;h3&gt;如何关闭某些功能？&lt;a href=&quot;#如何关闭某些功能&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;关闭某个系列&lt;/strong&gt;：设置该系列的 &lt;code&gt;enabled: false&lt;/code&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;featuredSeries&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;slug&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;weekly&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    enabled&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; # 禁用此系列&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    # ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关闭所有系列&lt;/strong&gt;：将 &lt;code&gt;featuredSeries&lt;/code&gt; 设为空数组 &lt;code&gt;[]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关闭搜索&lt;/strong&gt;：移除 &lt;code&gt;astro.config.mjs&lt;/code&gt; 中的 &lt;code&gt;pagefind()&lt;/code&gt; 集成&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关闭统计&lt;/strong&gt;：设置 &lt;code&gt;analytics.umami.enabled = false&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关闭评论&lt;/strong&gt;：移除 &lt;code&gt;comment.provider&lt;/code&gt; 配置或将其设置为空&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关闭 Shoka 语法&lt;/strong&gt;：在 &lt;code&gt;config/site.yaml&lt;/code&gt; 的 &lt;code&gt;content&lt;/code&gt; 部分将对应功能设为 &lt;code&gt;false&lt;/code&gt;，如 &lt;code&gt;enableShokaContainers: false&lt;/code&gt;（关闭提醒块/折叠块/标签卡）、&lt;code&gt;enableShokaSpoiler: false&lt;/code&gt;（关闭隐藏文字）等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关闭数学公式&lt;/strong&gt;：设置 &lt;code&gt;content.enableMath = false&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关闭练习题&lt;/strong&gt;：设置 &lt;code&gt;content.enableQuiz = false&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;如何更改文章 URL 格式？&lt;a href=&quot;#如何更改文章-url-格式&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;默认使用文件名作为 URL。可以通过 &lt;code&gt;link&lt;/code&gt; 字段自定义：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;link&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;my-custom-url&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;URL 特殊字符处理&lt;a href=&quot;#url-特殊字符处理&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;文章链接（&lt;code&gt;link&lt;/code&gt;）和标签（&lt;code&gt;tags&lt;/code&gt;）支持包含特殊字符，系统会自动进行 URL 编码处理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;文章链接特殊字符：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;C# 学习笔记&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;link&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;test-C#&lt;/span&gt;&lt;span&gt; # 包含 # 字符&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;访问时 URL 会自动编码为 &lt;code&gt;/post/test-C%23&lt;/code&gt;，确保浏览器正确解析。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;标签特殊字符：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;tags&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;C#&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;C++&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;.NET&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;Node.js&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;标签会自动转换为 URL 安全的格式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;C#&lt;/code&gt; → &lt;code&gt;/tags/c%23&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C++&lt;/code&gt; → &lt;code&gt;/tags/c%2B%2B&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.NET&lt;/code&gt; → &lt;code&gt;/tags/.net&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Node.js&lt;/code&gt; → &lt;code&gt;/tags/node.js&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;注意事项：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;特殊字符包括：&lt;code&gt;#&lt;/code&gt;、&lt;code&gt;+&lt;/code&gt;、&lt;code&gt;&amp;amp;&lt;/code&gt;、&lt;code&gt;?&lt;/code&gt;、&lt;code&gt;%&lt;/code&gt;、空格等&lt;/li&gt;
&lt;li&gt;标签中的 &lt;code&gt;/&lt;/code&gt; 会被替换为 &lt;code&gt;-&lt;/code&gt;（如 &lt;code&gt;前端/React&lt;/code&gt; → &lt;code&gt;前端-react&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;分类名称通过 &lt;code&gt;categoryMap&lt;/code&gt; 映射，建议使用纯英文 slug 避免编码问题&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参考资源&lt;a href=&quot;#参考资源&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.astro.build/&quot;&gt;Astro 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tailwindcss.com/docs&quot;&gt;Tailwind CSS 文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://motion.dev/docs&quot;&gt;Motion 文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pagefind.app/&quot;&gt;Pagefind 文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://shoka.lostyu.me/computer-science/note/theme-shoka-doc/&quot;&gt;Shoka 主题文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;更新日志&lt;a href=&quot;#更新日志&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;查看 &lt;a href=&quot;https://github.com/cosZone/astro-koharu/blob/main/CHANGELOG.md&quot;&gt;CHANGELOG.md&lt;/a&gt; 了解版本更新历史。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;如有问题或建议，欢迎在 &lt;a href=&quot;https://github.com/cosZone/astro-koharu/issues&quot;&gt;GitHub Issues&lt;/a&gt; 中反馈。&lt;/p&gt;</content:encoded><category>category:工具</category><category>tag:Astro</category><category>tag:博客</category><category>tag:教程</category></item><item><title>为 Astro 博客添加可插拔的圣诞特效模块</title><link>https://blog.cosine.ren/post/astro-christmas-effects</link><guid isPermaLink="false">astro-christmas-effects</guid><description>本文由 AI 辅助写作，作记录用
圣诞节到了，想给博客加点节日氛围。最终实现了一套可插拔的圣诞特效模块，包括：

3D 雪花飘落（React Three Fiber + GLSL 着色器）
悬挂彩灯装饰（CSS 动画）
头像圣诞帽（SVG）
圣诞配色方案（CSS 变量）
可拖拽圣诞球开关</description><pubDate>Wed, 24 Dec 2025 17:11:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;本文由 AI 辅助写作，作记录用&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;圣诞节到了，想给博客加点节日氛围。最终实现了一套可插拔的圣诞特效模块，包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;3D 雪花飘落（React Three Fiber + GLSL 着色器）&lt;/li&gt;
&lt;li&gt;悬挂彩灯装饰（CSS 动画）&lt;/li&gt;
&lt;li&gt;头像圣诞帽（SVG）&lt;/li&gt;
&lt;li&gt;圣诞配色方案（CSS 变量）&lt;/li&gt;
&lt;li&gt;可拖拽圣诞球开关&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;先放最终效果：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/7a36339bd35536b9ea9621db6cd238a2.webp&quot; alt=&quot;圣诞特效展示 1&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;如果嫌弃干扰阅读了，可以点击开关关掉～&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/e3cd9efe4bf5f40215566de09d537b58.gif&quot; alt=&quot;圣诞特效展示 2&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;注意看这个雪是双层的，内容容器夹在雪花中间，远景雪花在文章后面飘过，近景雪花在前面飘过，配合视差效果，感觉还挺满意的。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/cabfd68dc8313ff59d0c744ca224a6ed.gif&quot; alt=&quot;圣诞特效展示 3&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;技术选型上，雪花飘落使用 React Three Fiber 实现 &lt;strong&gt;GPU 着色器驱动&lt;/strong&gt; 的 6 层粒子系统，支持鼠标视差效果，在视觉效果和性能之间取得平衡。&lt;/p&gt;
&lt;h2&gt;整体架构&lt;a href=&quot;#整体架构&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;flowchart TB
    subgraph Config[配置层]
        SC[site-config.ts&amp;lt;br/&amp;gt;christmasConfig]
    end

    subgraph State[状态管理]
        NS[nanostores&amp;lt;br/&amp;gt;christmasEnabled]
        LS[localStorage&amp;lt;br/&amp;gt;持久化]
    end

    subgraph Effects[特效组件]
        SF[SnowfallCanvas&amp;lt;br/&amp;gt;GLSL 雪花着色器]
        CL[ChristmasLights&amp;lt;br/&amp;gt;CSS 彩灯]
        CH[ChristmasHat&amp;lt;br/&amp;gt;SVG 圣诞帽]
        CT[christmas-theme.css&amp;lt;br/&amp;gt;配色方案]
    end

    subgraph UI[用户界面]
        OT[ChristmasOrnamentToggle&amp;lt;br/&amp;gt;可拖拽开关]
    end

    SC --&amp;gt; NS
    NS &amp;lt;--&amp;gt; LS
    NS --&amp;gt; SF
    SC --&amp;gt; CL
    OT --&amp;gt; NS
    SC --&amp;gt; CH
    SC --&amp;gt; CT&lt;/pre&gt;
&lt;p&gt;所有特效都通过 &lt;code&gt;christmasConfig.enabled&lt;/code&gt; 控制，运行时状态通过 nanostores 管理，支持用户手动切换并持久化到 localStorage。&lt;/p&gt;
&lt;h2&gt;配置结构设计&lt;a href=&quot;#配置结构设计&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;src/constants/site-config.ts&lt;/code&gt; 中定义圣诞配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; ChristmasConfig&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  enabled&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; boolean&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  features&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    snowfall&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; boolean&lt;/span&gt;&lt;span&gt;;             &lt;/span&gt;&lt;span&gt;// 雪花飘落&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    christmasColorScheme&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; boolean&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// 圣诞配色&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    christmasCoverDecoration&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; boolean&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// Cover 彩灯装饰&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    christmasHat&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; boolean&lt;/span&gt;&lt;span&gt;;         &lt;/span&gt;&lt;span&gt;// 头像圣诞帽&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  snowfall&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    speed&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;;                 &lt;/span&gt;&lt;span&gt;// 下落速度 (1 = 正常)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    intensity&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;;             &lt;/span&gt;&lt;span&gt;// 雪花密度 (0-1)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    mobileIntensity&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;;       &lt;/span&gt;&lt;span&gt;// 移动端密度 (0-1)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; christmasConfig&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; ChristmasConfig&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  enabled: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  features: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    snowfall: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    christmasColorScheme: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    christmasCoverDecoration: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    christmasHat: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  snowfall: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    speed: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    intensity: &lt;/span&gt;&lt;span&gt;0.6&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    mobileIntensity: &lt;/span&gt;&lt;span&gt;0.4&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种设计允许细粒度控制各个特效，方便调试和按需启用。&lt;/p&gt;
&lt;h2&gt;状态管理：跨 Astro/React 边界&lt;a href=&quot;#状态管理跨-astroreact-边界&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Astro 的岛屿架构中，&lt;code&gt;.astro&lt;/code&gt; 组件和 React 组件需要共享状态。使用 &lt;a href=&quot;https://github.com/nanostores/nanostores&quot;&gt;nanostores&lt;/a&gt; 作为轻量级状态管理：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// src/store/christmas.ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { atom } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;nanostores&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; STORAGE_KEY&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;christmas-enabled&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 运行时开关状态&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; christmasEnabled&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; atom&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;boolean&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 初始化：从 localStorage 恢复状态&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; initChristmasState&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt; window &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;undefined&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; stored&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; localStorage.&lt;/span&gt;&lt;span&gt;getItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;STORAGE_KEY&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (stored &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    christmasEnabled.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(stored &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;true&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  syncChristmasClass&lt;/span&gt;&lt;span&gt;(christmasEnabled.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 同步 html.christmas class（用于 CSS 配色切换）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; syncChristmasClass&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;enabled&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; boolean&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt; document &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;undefined&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  document.documentElement.classList.&lt;/span&gt;&lt;span&gt;toggle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;christmas&apos;&lt;/span&gt;&lt;span&gt;, enabled);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 切换开关&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; toggleChristmas&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; newValue&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; !&lt;/span&gt;&lt;span&gt;christmasEnabled.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  christmasEnabled.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(newValue);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  localStorage.&lt;/span&gt;&lt;span&gt;setItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;STORAGE_KEY&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;(newValue));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  syncChristmasClass&lt;/span&gt;&lt;span&gt;(newValue);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 启用圣诞特效&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; enableChristmas&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  christmasEnabled.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  localStorage.&lt;/span&gt;&lt;span&gt;setItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;STORAGE_KEY&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;true&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  syncChristmasClass&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 禁用圣诞特效&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; disableChristmas&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  christmasEnabled.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  localStorage.&lt;/span&gt;&lt;span&gt;setItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;STORAGE_KEY&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;false&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  syncChristmasClass&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 React 组件中使用 &lt;code&gt;@nanostores/react&lt;/code&gt; 的 &lt;code&gt;useStore&lt;/code&gt; 订阅状态：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { useStore } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;@nanostores/react&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { christmasEnabled } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;@store/christmas&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; SnowfallCanvas&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; isEnabled&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useStore&lt;/span&gt;&lt;span&gt;(christmasEnabled);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;isEnabled) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // ...渲染雪花&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 Astro 组件的 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 中直接使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { christmasEnabled, toggleChristmas } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;@store/christmas&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;christmasEnabled.&lt;/span&gt;&lt;span&gt;subscribe&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;enabled&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 更新 UI 状态&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;雪花飘落：React Three Fiber 粒子系统&lt;a href=&quot;#雪花飘落react-three-fiber-粒子系统&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;选择 React Three Fiber + 自定义 GLSL 着色器，参考 &lt;a href=&quot;https://www.shadertoy.com/view/ldsGDn&quot;&gt;Shadertoy 上的 “Just snow”&lt;/a&gt; 效果，完全在 GPU 上计算和渲染 6 层雪花。&lt;/p&gt;
&lt;h3&gt;粒子系统实现&lt;a href=&quot;#粒子系统实现&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;ShaderMaterial&lt;/code&gt; 实现全屏着色器渲染：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// src/components/christmas/SnowParticles.tsx&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { useFrame, useThree } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;@react-three/fiber&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { useMemo, useRef, &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; MutableRefObject } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;react&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; as&lt;/span&gt;&lt;span&gt; THREE &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;three&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; SnowShaderMaterial&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  vertexShader: &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    varying vec2 vUv;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    void main() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      vUv = uv;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      gl_Position = vec4(position, 1.0);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  `&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  fragmentShader: &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    uniform float uTime;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    uniform vec2 uResolution;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    uniform float uSpeed;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    uniform float uIntensity;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    uniform vec2 uMouse;      // 鼠标视差偏移&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    uniform int uLayerStart;  // 渲染层范围起始&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    uniform int uLayerEnd;    // 渲染层范围结束&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    varying vec2 vUv;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    void main() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      vec2 fragCoord = vUv * uResolution;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      float snow = 0.0;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      float time = uTime * uSpeed;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // 6层雪花，每层12次迭代&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      for(int k = 0; k &amp;lt; 6; k++) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if(k &amp;lt; uLayerStart || k &amp;gt; uLayerEnd) continue;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        for(int i = 0; i &amp;lt; 12; i++) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          float cellSize = 2.0 + (float(i) * 3.0);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          float downSpeed = 0.3 + (sin(time * 0.4 + float(k + i * 20)) + 1.0) * 0.00008;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          // 视差偏移：近景层偏移更多&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          float parallaxFactor = 0.5 + float(k) * 0.1;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          vec2 mouseOffset = uMouse * parallaxFactor;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          vec2 uv = (fragCoord.xy / uResolution.x) + mouseOffset + vec2(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            0.01 * sin((time + float(k * 6185)) * 0.6 + float(i)) * (5.0 / float(i)),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            downSpeed * (time + float(k * 1352)) * (1.0 / float(i))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          vec2 uvStep = (ceil((uv) * cellSize - vec2(0.5, 0.5)) / cellSize);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          float x = fract(sin(dot(uvStep.xy, vec2(12.9898 + float(k) * 12.0, 78.233 + float(k) * 315.156))) * 43758.5453 + float(k) * 12.0) - 0.5;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          float y = fract(sin(dot(uvStep.xy, vec2(62.2364 + float(k) * 23.0, 94.674 + float(k) * 95.0))) * 62159.8432 + float(k) * 12.0) - 0.5;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          float d = 5.0 * distance((uvStep.xy + vec2(x * sin(y), y) * sin(time * 2.5) * 0.7 / cellSize), uv.xy);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          float omiVal = fract(sin(dot(uvStep.xy, vec2(32.4691, 94.615))) * 31572.1684);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          if(omiVal &amp;lt; 0.08) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            snow += (x + 1.0) * 0.4 * clamp(1.9 - d * (15.0 + (x * 6.3)) * (cellSize / 1.4), 0.0, 1.0);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      gl_FragColor = vec4(1.0, 1.0, 1.0, snow * uIntensity);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  `&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; SnowParticlesProps&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  speed&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  intensity&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  parallaxRef&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; MutableRefObject&lt;/span&gt;&lt;span&gt;&amp;lt;{ &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt; }&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  layerRange&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; SnowParticles&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  speed&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  intensity&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 0.6&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  parallaxRef&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  layerRange&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; SnowParticlesProps&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; shaderMaterial&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useRef&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;THREE&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ShaderMaterial&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useThree&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;layerStart&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;layerEnd&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; layerRange;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; uniforms&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useMemo&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; ({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    uTime: { value: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    uResolution: { value: &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; THREE&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Vector2&lt;/span&gt;&lt;span&gt;(size.width, size.height) },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    uSpeed: { value: speed },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    uIntensity: { value: intensity },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    uMouse: { value: &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; THREE&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Vector2&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;) },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    uLayerStart: { value: layerStart },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    uLayerEnd: { value: layerEnd },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }), [size.width, size.height, speed, intensity, layerStart, layerEnd]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  useFrame&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (shaderMaterial.current) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      shaderMaterial.current.uniforms.uTime.value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; state.clock.&lt;/span&gt;&lt;span&gt;getElapsedTime&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (parallaxRef) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        shaderMaterial.current.uniforms.uMouse.value.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(parallaxRef.current.x, parallaxRef.current.y);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;mesh&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;planeGeometry&lt;/span&gt;&lt;span&gt; args&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{[&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;]} /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;shaderMaterial&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        ref&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{shaderMaterial}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        transparent&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        depthWrite&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        blending&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;THREE&lt;/span&gt;&lt;span&gt;.AdditiveBlending}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uniforms&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{uniforms}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        vertexShader&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{SnowShaderMaterial.vertexShader}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        fragmentShader&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{SnowShaderMaterial.fragmentShader}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;mesh&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;核心思路：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;planeGeometry&lt;/code&gt; 创建全屏四边形&lt;/li&gt;
&lt;li&gt;Fragment Shader 中用 6 层嵌套循环生成不同大小、速度的雪花&lt;/li&gt;
&lt;li&gt;每层雪花有独立的下落速度和风力相位&lt;/li&gt;
&lt;li&gt;通过 &lt;code&gt;uLayerStart&lt;/code&gt; 和 &lt;code&gt;uLayerEnd&lt;/code&gt; uniform 控制渲染哪些层（用于前后景分离）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Canvas 配置：不阻挡点击&lt;a href=&quot;#canvas-配置不阻挡点击&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;一个关键问题是 R3F Canvas 默认会拦截所有鼠标事件，导致下方元素无法点击。解决方案：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Canvas&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 全屏着色器使用正交相机&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  orthographic&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  camera&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{ zoom: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, position: [&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;] }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 禁用 R3F 的事件系统&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  eventSource&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  eventPrefix&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{ pointerEvents: &lt;/span&gt;&lt;span&gt;&apos;none&apos;&lt;/span&gt;&lt;span&gt; }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  gl&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    antialias: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    alpha: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    powerPreference: &lt;/span&gt;&lt;span&gt;&apos;low-power&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// 省电模式&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  dpr&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1.5&lt;/span&gt;&lt;span&gt;]} &lt;/span&gt;&lt;span&gt;// 限制 DPR，移动端性能优化&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;鼠标视差效果&lt;a href=&quot;#鼠标视差效果&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;参考 &lt;a href=&quot;https://www.shadertoy.com/view/ldsGDn&quot;&gt;Shadertoy 上的 “Just snow”&lt;/a&gt; 效果，添加鼠标移动时的视差偏移，让雪花更有层次感。&lt;/p&gt;
&lt;p&gt;核心思路：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用 Motion 的 &lt;code&gt;useMotionValue&lt;/code&gt; + &lt;code&gt;useSpring&lt;/code&gt; 追踪并平滑鼠标位置&lt;/li&gt;
&lt;li&gt;将平滑后的值传递给着色器的 &lt;code&gt;uMouse&lt;/code&gt; uniform&lt;/li&gt;
&lt;li&gt;着色器中不同层使用不同的视差因子，近景层偏移更多&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// SnowfallCanvas.tsx - 鼠标追踪&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; mouseX&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useMotionValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; mouseY&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useMotionValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; smoothMouseX&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useSpring&lt;/span&gt;&lt;span&gt;(mouseX, { stiffness: &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt;, damping: &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; smoothMouseY&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useSpring&lt;/span&gt;&lt;span&gt;(mouseY, { stiffness: &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt;, damping: &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (isMobile) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// 移动端无鼠标&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; handleMouseMove&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; MouseEvent&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 标准化到 -0.5 ~ 0.5&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    mouseX.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(e.clientX &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; window.innerWidth &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 0.5&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    mouseY.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(e.clientY &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; window.innerHeight &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 0.5&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; handleMouseLeave&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 鼠标离开窗口时回到中心&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    mouseX.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    mouseY.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  window.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;mousemove&apos;&lt;/span&gt;&lt;span&gt;, handleMouseMove, { passive: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  document.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;mouseleave&apos;&lt;/span&gt;&lt;span&gt;, handleMouseLeave);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    window.&lt;/span&gt;&lt;span&gt;removeEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;mousemove&apos;&lt;/span&gt;&lt;span&gt;, handleMouseMove);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    document.&lt;/span&gt;&lt;span&gt;removeEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;mouseleave&apos;&lt;/span&gt;&lt;span&gt;, handleMouseLeave);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}, [isMobile]);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;着色器中应用视差：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;uniform&lt;/span&gt;&lt;span&gt; vec2&lt;/span&gt;&lt;span&gt; uMouse;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 在每层雪花的 UV 计算中&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt; parallaxFactor &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0.5&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; float&lt;/span&gt;&lt;span&gt;(k) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 0.1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; // k=0~5 → 0.5~1.0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;vec2&lt;/span&gt;&lt;span&gt; mouseOffset &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; uMouse &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; parallaxFactor;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;vec2&lt;/span&gt;&lt;span&gt; uv &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (fragCoord.xy &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; uResolution.x) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; mouseOffset &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; ...;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于 Motion 的 spring 值在 React 渲染循环外更新，而 R3F 的 &lt;code&gt;useFrame&lt;/code&gt; 也在外部运行，需要用桥接组件订阅 spring 变化并存入 ref：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; SnowParticlesWithParallax&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  speed&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  intensity&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  smoothMouseX&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  smoothMouseY&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  parallaxStrength&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  layerRange&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  speed&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  intensity&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  smoothMouseX&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; MotionValue&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  smoothMouseY&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; MotionValue&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  parallaxStrength&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  layerRange&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; parallaxRef&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useRef&lt;/span&gt;&lt;span&gt;({ x: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, y: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; unsubX&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; smoothMouseX.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;change&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      parallaxRef.current.x &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; v &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; parallaxStrength;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; unsubY&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; smoothMouseY.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;change&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      parallaxRef.current.y &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; v &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; parallaxStrength;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;unsubX&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;unsubY&lt;/span&gt;&lt;span&gt;(); };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }, [smoothMouseX, smoothMouseY, parallaxStrength]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; &amp;lt;&lt;/span&gt;&lt;span&gt;SnowParticles&lt;/span&gt;&lt;span&gt; speed&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{speed} &lt;/span&gt;&lt;span&gt;intensity&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{intensity} &lt;/span&gt;&lt;span&gt;parallaxRef&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{parallaxRef} &lt;/span&gt;&lt;span&gt;layerRange&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{layerRange} /&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;双层渲染：前景与背景分离&lt;a href=&quot;#双层渲染前景与背景分离&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;为了让雪花更有层次感，将 6 层雪花分成两组渲染到不同的 z-index：&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层级&lt;/th&gt;&lt;th&gt;z-index&lt;/th&gt;&lt;th&gt;着色器层 (k)&lt;/th&gt;&lt;th&gt;效果&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;背景雪&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;k=0-2&lt;/td&gt;&lt;td&gt;远景、小雪花、视差弱&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;内容&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;正常文档流&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;文章容器&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;前景雪&lt;/td&gt;&lt;td&gt;50&lt;/td&gt;&lt;td&gt;k=3-5&lt;/td&gt;&lt;td&gt;近景、大雪花、视差强&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;实现方式是给 &lt;code&gt;SnowParticles&lt;/code&gt; 添加 &lt;code&gt;layerRange&lt;/code&gt; prop，着色器中根据范围跳过不需要的层：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;uniform&lt;/span&gt;&lt;span&gt; int&lt;/span&gt;&lt;span&gt; uLayerStart;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;uniform&lt;/span&gt;&lt;span&gt; int&lt;/span&gt;&lt;span&gt; uLayerEnd;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; k &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;; k &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; 6&lt;/span&gt;&lt;span&gt;; k&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt;(k &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; uLayerStart &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; k &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; uLayerEnd) &lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // ... 原有雪花渲染逻辑&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后渲染两个 Canvas：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;/* 背景雪 */&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;SnowfallCanvas&lt;/span&gt;&lt;span&gt; zIndex&lt;/span&gt;&lt;span&gt;={&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;layerRange&lt;/span&gt;&lt;span&gt;={[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;]} /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;/* 前景雪 */&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;SnowfallCanvas&lt;/span&gt;&lt;span&gt; zIndex&lt;/span&gt;&lt;span&gt;={&lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;layerRange&lt;/span&gt;&lt;span&gt;={[&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;]} /&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样内容容器就”夹”在雪花中间，远景雪花在文章后面飘过，近景雪花在前面飘过，配合视差效果产生很好的深度感。&lt;/p&gt;
&lt;h2&gt;圣诞彩灯：CSS 动画&lt;a href=&quot;#圣诞彩灯css-动画&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在 Cover 顶部添加悬挂式彩灯装饰，参考 &lt;a href=&quot;https://codepen.io/tobyj/pen/QjvEex&quot;&gt;Toby J 的 CodePen&lt;/a&gt; 实现，调整为圣诞配色（红、绿、金）。&lt;/p&gt;
&lt;h3&gt;实现原理&lt;a href=&quot;#实现原理&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;彩灯效果完全使用 CSS 实现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;灯泡使用 &lt;code&gt;border-radius: 50%&lt;/code&gt; 创建椭圆形&lt;/li&gt;
&lt;li&gt;灯座使用 &lt;code&gt;::before&lt;/code&gt; 伪元素&lt;/li&gt;
&lt;li&gt;电线使用 &lt;code&gt;::after&lt;/code&gt; 伪元素的 &lt;code&gt;border-bottom&lt;/code&gt; + &lt;code&gt;border-radius&lt;/code&gt; 模拟弧形&lt;/li&gt;
&lt;li&gt;闪烁效果使用 &lt;code&gt;@keyframes&lt;/code&gt; 动画改变 &lt;code&gt;background&lt;/code&gt; 和 &lt;code&gt;box-shadow&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// src/components/christmas/ChristmasLights.astro&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 生成足够数量的灯泡覆盖最大屏幕宽度&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; lightCount&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 50&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;ul&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;lightrope&quot;&lt;/span&gt;&lt;span&gt; aria-hidden&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;true&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {Array.&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;({ length: lightCount }).&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; &amp;lt;&lt;/span&gt;&lt;span&gt;li&lt;/span&gt;&lt;span&gt; /&amp;gt;)}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;ul&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .lightrope&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    --globe-width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;12&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    --globe-height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;28&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    --globe-spacing&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;40&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    --globe-spread&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    --light-off-opacity&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.4&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /* 圣诞配色 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    --light-red&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;185&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    --light-green&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;34&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;139&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;34&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    --light-gold&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;218&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;165&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;32&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    position&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;fixed&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    top&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;42&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    left&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    z-index&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    pointer-events&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .lightrope&lt;/span&gt;&lt;span&gt; li&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;inline-block&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--globe-width&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--globe-height&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    margin&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;calc&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--globe-spacing&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    border-radius&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    background&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;rgb&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--light-red&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    box-shadow&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; 4&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; 24&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; 3&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; rgba&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--light-red&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    animation&lt;/span&gt;&lt;span&gt;: flash-red &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt; ease-in-out&lt;/span&gt;&lt;span&gt; infinite&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* 绿色灯泡 - 每隔一个 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .lightrope&lt;/span&gt;&lt;span&gt; li&lt;/span&gt;&lt;span&gt;:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2n + 1&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    background&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;rgb&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--light-green&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    animation-name&lt;/span&gt;&lt;span&gt;: flash-green;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    animation-duration&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.4&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* 金色灯泡 - 每 4 个中的第 2 个 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .lightrope&lt;/span&gt;&lt;span&gt; li&lt;/span&gt;&lt;span&gt;:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;4n + 2&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    background&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;rgb&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--light-gold&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    animation-name&lt;/span&gt;&lt;span&gt;: flash-gold;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    animation-duration&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1.1&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* 灯座 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .lightrope&lt;/span&gt;&lt;span&gt; li&lt;/span&gt;&lt;span&gt;::before&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    content&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    position&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;absolute&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    top&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;-5&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;9&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    background&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;hsl&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--background&lt;/span&gt;&lt;span&gt;) / &lt;/span&gt;&lt;span&gt;0.6&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    border-radius&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* 连接电线 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .lightrope&lt;/span&gt;&lt;span&gt; li&lt;/span&gt;&lt;span&gt;::after&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    content&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    position&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;absolute&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    top&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;-14&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    left&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;9&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;52&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;18&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    border-bottom&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;solid&lt;/span&gt;&lt;span&gt; hsl&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--background&lt;/span&gt;&lt;span&gt;) / &lt;/span&gt;&lt;span&gt;0.6&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    border-radius&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* 闪烁动画 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  @keyframes&lt;/span&gt;&lt;span&gt; flash-red&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    0%&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    100%&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      background&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;rgb&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--light-red&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      box-shadow&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; 4&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; 24&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; 3&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; rgba&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--light-red&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    50%&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      background&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;rgba&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--light-red&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;0.4&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      box-shadow&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; 4&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; 24&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; 3&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; rgba&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--light-red&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;0.2&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* 无障碍：减少动画 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  @media&lt;/span&gt;&lt;span&gt; (prefers-reduced-motion: reduce) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .lightrope&lt;/span&gt;&lt;span&gt; li&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      animation&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;在 Cover 中使用&lt;a href=&quot;#在-cover-中使用&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// src/components/ui/cover/Cover.astro&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;+&lt;/span&gt; import { christmasConfig } from &apos;@constants/site-config&apos;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;+&lt;/span&gt; import ChristmasLights from &apos;@components/christmas/ChristmasLights.astro&apos;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;div class=&quot;relative flex h-[60dvh] max-h-200 overflow-hidden&quot;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;+&lt;/span&gt; {christmasConfig.enabled &amp;amp;&amp;amp; christmasConfig.features.christmasCoverDecoration &amp;amp;&amp;amp; &amp;lt;ChristmasLights /&amp;gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;div class=&quot;absolute inset-0 h-full bg-black/40&quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;彩灯使用 &lt;code&gt;position: fixed&lt;/code&gt; 固定在视口顶部，&lt;code&gt;z-index: 100&lt;/code&gt; 确保显示在导航栏之上。电线和灯座颜色使用 &lt;code&gt;hsl(var(--background) / 0.6)&lt;/code&gt; 自动适配亮暗主题。&lt;/p&gt;
&lt;h3&gt;彩灯性能优化&lt;a href=&quot;#彩灯性能优化&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;初版彩灯使用 &lt;code&gt;box-shadow&lt;/code&gt; 动画实现闪烁效果，但存在严重性能问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;50 个元素使用 &lt;code&gt;will-change: box-shadow, background&lt;/code&gt;&lt;/strong&gt;：创建过多合成层，占用大量内存&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;box-shadow&lt;/code&gt; 动画触发 paint&lt;/strong&gt;：每帧都需要重绘，CPU 开销大&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动画持续运行&lt;/strong&gt;：即使 Tab 不可见或圣诞模式关闭，动画仍在消耗资源&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;优化方案：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  function&lt;/span&gt;&lt;span&gt; initLightsVisibility&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // Tab 可见性检测 - 不可见时暂停动画&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; handleVisibility&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      document.documentElement.classList.&lt;/span&gt;&lt;span&gt;toggle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;lights-paused&apos;&lt;/span&gt;&lt;span&gt;, document.hidden);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    handleVisibility&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    document.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;visibilitychange&apos;&lt;/span&gt;&lt;span&gt;, handleVisibility);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (document.readyState &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; &apos;loading&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;initLightsVisibility&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  document.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;astro:page-load&apos;&lt;/span&gt;&lt;span&gt;, initLightsVisibility);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .lightrope&lt;/span&gt;&lt;span&gt; li&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /* 静态 box-shadow - 不参与动画 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    box-shadow&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      0&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; 4&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; 24&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; 6&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; rgba&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--light-red&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      0&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; 8&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; rgba&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--light-red&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;0.6&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /* 仅使用 opacity + scale 动画 - GPU 合成，无 paint 开销 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    animation&lt;/span&gt;&lt;span&gt;: flash-bulb &lt;/span&gt;&lt;span&gt;1.2&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt; ease-in-out&lt;/span&gt;&lt;span&gt; infinite&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* 流水灯效果 - 每 6 个灯泡一组 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .lightrope&lt;/span&gt;&lt;span&gt; li&lt;/span&gt;&lt;span&gt;:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;6n + 1&lt;/span&gt;&lt;span&gt;) { &lt;/span&gt;&lt;span&gt;animation-delay&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .lightrope&lt;/span&gt;&lt;span&gt; li&lt;/span&gt;&lt;span&gt;:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;6n + 2&lt;/span&gt;&lt;span&gt;) { &lt;/span&gt;&lt;span&gt;animation-delay&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.15&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .lightrope&lt;/span&gt;&lt;span&gt; li&lt;/span&gt;&lt;span&gt;:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;6n + 3&lt;/span&gt;&lt;span&gt;) { &lt;/span&gt;&lt;span&gt;animation-delay&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.3&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .lightrope&lt;/span&gt;&lt;span&gt; li&lt;/span&gt;&lt;span&gt;:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;6n + 4&lt;/span&gt;&lt;span&gt;) { &lt;/span&gt;&lt;span&gt;animation-delay&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.45&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .lightrope&lt;/span&gt;&lt;span&gt; li&lt;/span&gt;&lt;span&gt;:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;6n + 5&lt;/span&gt;&lt;span&gt;) { &lt;/span&gt;&lt;span&gt;animation-delay&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.6&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .lightrope&lt;/span&gt;&lt;span&gt; li&lt;/span&gt;&lt;span&gt;:nth-child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;6n&lt;/span&gt;&lt;span&gt;) { &lt;/span&gt;&lt;span&gt;animation-delay&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.75&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  @keyframes&lt;/span&gt;&lt;span&gt; flash-bulb&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    0%&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;100%&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      opacity&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      transform&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;scale&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    50%&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      opacity&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.4&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      transform&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;scale&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.9&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* Tab 不可见时暂停 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  :global(&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;.lights-paused&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;.lightrope&lt;/span&gt;&lt;span&gt; li&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    animation-play-state&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;paused&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* 圣诞模式关闭时暂停 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  :global(&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;:not&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;.christmas&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;.lightrope&lt;/span&gt;&lt;span&gt; li&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    animation-play-state&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;paused&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;核心优化点：&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;优化项&lt;/th&gt;&lt;th&gt;之前&lt;/th&gt;&lt;th&gt;之后&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;动画属性&lt;/td&gt;&lt;td&gt;&lt;code&gt;box-shadow&lt;/code&gt; + &lt;code&gt;background&lt;/code&gt; (触发 paint)&lt;/td&gt;&lt;td&gt;&lt;code&gt;opacity&lt;/code&gt; + &lt;code&gt;transform: scale()&lt;/code&gt; (GPU 合成)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;合成层&lt;/td&gt;&lt;td&gt;50+ (&lt;code&gt;will-change&lt;/code&gt;)&lt;/td&gt;&lt;td&gt;自动管理&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Tab 不可见&lt;/td&gt;&lt;td&gt;持续运行&lt;/td&gt;&lt;td&gt;暂停&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;圣诞模式关闭&lt;/td&gt;&lt;td&gt;持续运行 (仅隐藏)&lt;/td&gt;&lt;td&gt;暂停&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;视觉效果&lt;/td&gt;&lt;td&gt;随机闪烁&lt;/td&gt;&lt;td&gt;流水灯 + 缩放&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;另外将配色升级为四色方案（红、绿、金、蓝），发光效果使用双层 &lt;code&gt;box-shadow&lt;/code&gt; 增强，动画周期缩短到 1.2s 让节奏更活泼。&lt;/p&gt;
&lt;h2&gt;圣诞帽：SVG 绘制&lt;a href=&quot;#圣诞帽svg-绘制&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;头像上的圣诞帽使用纯 SVG 绘制，支持自定义尺寸、位置、旋转角度和颜色：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; Props&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  className&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  size&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  offset&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  rotate&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  colors&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    primary&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    secondary&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    white&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  className&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  size&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;100%&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  offset&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; { x: &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;28&lt;/span&gt;&lt;span&gt;, y: &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  rotate&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 10&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  colors&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    primary: &lt;/span&gt;&lt;span&gt;&apos;#FF4E50&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    secondary: &lt;/span&gt;&lt;span&gt;&apos;#C00000&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    white: &lt;/span&gt;&lt;span&gt;&apos;#FFFFFF&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Astro.props;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 生成唯一 ID 避免多个帽子时 gradient 冲突&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; uniqueId&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Math.&lt;/span&gt;&lt;span&gt;random&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;36&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;substring&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;9&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  class:list&lt;/span&gt;&lt;span&gt;={[&lt;/span&gt;&lt;span&gt;&apos;christmas-hat-wrapper&apos;&lt;/span&gt;&lt;span&gt;, className]}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  style&lt;/span&gt;&lt;span&gt;={{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    width: &lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt; size &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;number&apos;&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; `${&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;}px`&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; size,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    height: &lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt; size &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;number&apos;&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; `${&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;}px`&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; size,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    top: &lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;offset&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;}%`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    left: &lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;offset&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;}%`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    transform: &lt;/span&gt;&lt;span&gt;`rotate(${&lt;/span&gt;&lt;span&gt;rotate&lt;/span&gt;&lt;span&gt;}deg)`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt; viewBox&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;0 0 200 200&quot;&lt;/span&gt;&lt;span&gt; fill&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;none&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;defs&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;linearGradient&lt;/span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;={&lt;/span&gt;&lt;span&gt;`hatGradient_${&lt;/span&gt;&lt;span&gt;uniqueId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;} ...&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;stop&lt;/span&gt;&lt;span&gt; stop-color&lt;/span&gt;&lt;span&gt;={colors.primary}&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;stop&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;stop&lt;/span&gt;&lt;span&gt; offset&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;1&quot;&lt;/span&gt;&lt;span&gt; stop-color&lt;/span&gt;&lt;span&gt;={colors.secondary}&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;stop&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/&lt;/span&gt;&lt;span&gt;linearGradient&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;defs&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;!-- 帽身 --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt; d&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;M179.712 87.522 ...&quot;&lt;/span&gt;&lt;span&gt; fill&lt;/span&gt;&lt;span&gt;={&lt;/span&gt;&lt;span&gt;`url(#hatGradient_${&lt;/span&gt;&lt;span&gt;uniqueId&lt;/span&gt;&lt;span&gt;})`&lt;/span&gt;&lt;span&gt;}&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;!-- 白边 --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt; d&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;M181.512 88.5738 ...&quot;&lt;/span&gt;&lt;span&gt; stroke&lt;/span&gt;&lt;span&gt;={colors.white} &lt;/span&gt;&lt;span&gt;stroke-width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;28&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;!-- 毛球 --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;circle&lt;/span&gt;&lt;span&gt; cx&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;38.5&quot;&lt;/span&gt;&lt;span&gt; cy&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;144.5&quot;&lt;/span&gt;&lt;span&gt; r&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;19.5&quot;&lt;/span&gt;&lt;span&gt; fill&lt;/span&gt;&lt;span&gt;={colors.white}&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;circle&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .christmas-hat-wrapper&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    position&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;absolute&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    pointer-events&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    z-index&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    transition&lt;/span&gt;&lt;span&gt;: transform &lt;/span&gt;&lt;span&gt;0.3&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt; ease&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    filter&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;drop-shadow&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; 10&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; 15&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; rgba&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0.1&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过 props 可以灵活调整帽子的位置和样式，适配不同尺寸的头像。使用 &lt;code&gt;linearGradient&lt;/code&gt; 实现帽身的渐变效果，&lt;code&gt;drop-shadow&lt;/code&gt; 添加阴影增加立体感。&lt;/p&gt;
&lt;h2&gt;圣诞配色方案&lt;a href=&quot;#圣诞配色方案&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;通过 CSS 自定义属性实现主题切换，当 &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; 有 &lt;code&gt;.christmas&lt;/code&gt; 类时激活：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/* src/styles/christmas/christmas-theme.css */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.christmas&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  --christmas-red&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; 70&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; 45&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  --christmas-green&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;150&lt;/span&gt;&lt;span&gt; 60&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; 35&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  --christmas-gold&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;43&lt;/span&gt;&lt;span&gt; 75&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; 55&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* 覆盖主色调 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  --primary&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--christmas-red&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.christmas.dark&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  --christmas-red&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; 65&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; 55&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  --gradient-bg-start&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;#1a1512&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  --gradient-bg-end&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;#0d1a0d&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 Layout 中根据配置添加类名：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt; class:list&lt;/span&gt;&lt;span&gt;={[{ christmas: christmasConfig.enabled &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; christmasConfig.features.christmasColorScheme }]}&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;开关按钮&lt;a href=&quot;#开关按钮&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在悬浮按钮组中添加圣诞开关：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  christmasConfig.enabled &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;toggle-christmas&quot;&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;rounded-full p-2 shadow-lg backdrop-blur-sm&quot;&lt;/span&gt;&lt;span&gt; aria-label&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;切换圣诞特效&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;Icon&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;ri:snowy-fill&quot;&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;christmas-on h-5 w-5&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;Icon&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;ri:snowy-line&quot;&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;christmas-off hidden h-5 w-5&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  import&lt;/span&gt;&lt;span&gt; { christmasEnabled, toggleChristmas, initChristmasState } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;@store/christmas&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  function&lt;/span&gt;&lt;span&gt; init&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    initChristmasState&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; btn&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;getElementById&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;toggle-christmas&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    btn?.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;click&apos;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      toggleChristmas&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 订阅状态变化，更新按钮图标&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    christmasEnabled.&lt;/span&gt;&lt;span&gt;subscribe&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;enabled&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; onIcon&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; btn?.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;.christmas-on&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; offIcon&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; btn?.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;.christmas-off&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      onIcon?.classList.&lt;/span&gt;&lt;span&gt;toggle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;hidden&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;enabled);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      offIcon?.classList.&lt;/span&gt;&lt;span&gt;toggle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;hidden&apos;&lt;/span&gt;&lt;span&gt;, enabled);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  document.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;astro:page-load&apos;&lt;/span&gt;&lt;span&gt;, init);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;圣诞球开关：可拖拽的装饰组件&lt;a href=&quot;#圣诞球开关可拖拽的装饰组件&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;除了简单的图标按钮，还实现了一个更有趣的交互方式：悬挂在页面右上角的圣诞球装饰，支持拖拽下拉触发开关。&lt;/p&gt;
&lt;h3&gt;实现原理&lt;a href=&quot;#实现原理-1&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用 Motion 的 drag API 实现拖拽交互：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;useMotionValue&lt;/code&gt; 追踪拖拽位置&lt;/li&gt;
&lt;li&gt;&lt;code&gt;useTransform&lt;/code&gt; 派生绳子高度&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dragSnapToOrigin&lt;/code&gt; 自动回弹&lt;/li&gt;
&lt;li&gt;拖拽超过阈值时触发状态切换&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// src/components/christmas/ChristmasOrnamentToggle.tsx&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { cn } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;@lib/utils&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { useStore } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;@nanostores/react&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { christmasEnabled, disableChristmas, toggleChristmas } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;@store/christmas&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { AnimatePresence, motion, useMotionValue, useReducedMotion, useTransform } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;motion/react&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { useState } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;react&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; DRAG_THRESHOLD&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 80&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; ORNAMENT_SIZE&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 72&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; STRING_HEIGHT&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 80&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; ChristmasOrnamentToggle&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; isEnabled&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useStore&lt;/span&gt;&lt;span&gt;(christmasEnabled);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; shouldReduceMotion&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useReducedMotion&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;isPulling&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setIsPulling&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;isHovered&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setIsHovered&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; y&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useMotionValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 绳子高度跟随球的位置，确保始终连接&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; stringHeight&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useTransform&lt;/span&gt;&lt;span&gt;(y, (&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; STRING_HEIGHT&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; Math.&lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, v));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; handleDragEnd&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setIsPulling&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; currentY&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; y.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (currentY &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; DRAG_THRESHOLD&lt;/span&gt;&lt;span&gt; /&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (isEnabled) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        disableChristmas&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        toggleChristmas&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;fixed top-0 right-20 z-90 flex w-[100px] justify-center&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      {&lt;/span&gt;&lt;span&gt;/* 绳子 - 高度随拖拽变化 */&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;motion.div&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;pointer-events-none absolute top-0 w-[2px] origin-top bg-linear-to-b from-yellow-700 via-yellow-500 to-yellow-400&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{ height: stringHeight }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      {&lt;/span&gt;&lt;span&gt;/* 球体 - 可拖拽 */&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;motion.button&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;cn&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &apos;absolute z-101 cursor-grab touch-none select-none active:cursor-grabbing&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &apos;rounded-full outline-none focus-visible:ring-4 focus-visible:ring-yellow-400/50&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        )}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          width: &lt;/span&gt;&lt;span&gt;ORNAMENT_SIZE&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          height: &lt;/span&gt;&lt;span&gt;ORNAMENT_SIZE&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          top: &lt;/span&gt;&lt;span&gt;STRING_HEIGHT&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          y, &lt;/span&gt;&lt;span&gt;// Motion 拖拽自动更新此值&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        drag&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{shouldReduceMotion &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; &apos;y&apos;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        dragConstraints&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{ top: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, bottom: &lt;/span&gt;&lt;span&gt;DRAG_THRESHOLD&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; 30&lt;/span&gt;&lt;span&gt; }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        dragElastic&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;0.2&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        dragSnapToOrigin&lt;/span&gt;&lt;span&gt; // 松开后自动回弹&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        onDragStart&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; setIsPulling&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;)}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        onDragEnd&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{handleDragEnd}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; toggleChristmas&lt;/span&gt;&lt;span&gt;()}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        whileHover&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{ scale: &lt;/span&gt;&lt;span&gt;1.05&lt;/span&gt;&lt;span&gt; }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        whileTap&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{ scale: &lt;/span&gt;&lt;span&gt;0.95&lt;/span&gt;&lt;span&gt; }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        aria-label&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{isEnabled &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &apos;关闭圣诞模式&apos;&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; &apos;开启圣诞模式&apos;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        aria-pressed&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{isEnabled}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;button&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;OrnamentSvg&lt;/span&gt;&lt;span&gt; isEnabled&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{isEnabled} /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {&lt;/span&gt;&lt;span&gt;/* 拖拽提示 */&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;AnimatePresence&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          {isEnabled &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; (isPulling &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; isHovered) &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &amp;lt;&lt;/span&gt;&lt;span&gt;motion.div&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;              initial&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{ opacity: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, y: &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt; }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;              animate&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{ opacity: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, y: &lt;/span&gt;&lt;span&gt;30&lt;/span&gt;&lt;span&gt; }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;              exit&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{ opacity: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;              className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;pointer-events-none absolute top-1/2 left-1/2 -translate-x-1/2&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;              &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;rounded-full bg-red-950/90 px-3 py-1 text-[10px] text-white&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                下拉关闭&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;              &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &amp;lt;/&lt;/span&gt;&lt;span&gt;motion.div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          )}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;/&lt;/span&gt;&lt;span&gt;AnimatePresence&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/&lt;/span&gt;&lt;span&gt;motion.button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;关键技术点&lt;a href=&quot;#关键技术点&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;绳子与球的同步&lt;/strong&gt;：将 &lt;code&gt;y&lt;/code&gt; MotionValue 同时用于球的位置和绳子高度计算，确保两者始终连接&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;dragSnapToOrigin&lt;/strong&gt;：Motion 内置的回弹动画，松开后自动回到初始位置&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;useReducedMotion&lt;/strong&gt;：尊重用户的动画偏好，禁用拖拽时改为点击切换&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;dragElastic&lt;/strong&gt;：设置 0.2 的弹性系数，超出约束时有轻微的弹性效果&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;性能优化&lt;a href=&quot;#性能优化&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;移动端适配&lt;a href=&quot;#移动端适配&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;使用自定义 hook 检测移动端并调整雪花密度：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; isMobile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useIsMobile&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; shouldReduceMotion&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useReducedMotion&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 移动端使用更低的密度&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; finalIntensity&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; isMobile &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; mobileIntensity &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; intensity;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 移动端禁用鼠标视差&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; finalParallaxStrength&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; isMobile &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; parallaxStrength;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 尊重用户的动画偏好设置&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (shouldReduceMotion &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; !&lt;/span&gt;&lt;span&gt;isChristmasEnabled) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;R3F Canvas 配置&lt;a href=&quot;#r3f-canvas-配置&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Canvas&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  orthographic&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  camera&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{ zoom: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, position: [&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;] }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  gl&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    antialias: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,      &lt;/span&gt;&lt;span&gt;// 禁用抗锯齿&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    alpha: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    powerPreference: &lt;/span&gt;&lt;span&gt;&apos;low-power&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  dpr&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1.5&lt;/span&gt;&lt;span&gt;]}           &lt;/span&gt;&lt;span&gt;// 限制像素比&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;文件清单&lt;a href=&quot;#文件清单&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;文件&lt;/th&gt;&lt;th&gt;用途&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;src/constants/site-config.ts&lt;/code&gt;&lt;/td&gt;&lt;td&gt;圣诞配置定义&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;src/store/christmas.ts&lt;/code&gt;&lt;/td&gt;&lt;td&gt;状态管理&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;src/components/christmas/SnowfallCanvas.tsx&lt;/code&gt;&lt;/td&gt;&lt;td&gt;R3F 雪花画布包装器&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;src/components/christmas/SnowParticles.tsx&lt;/code&gt;&lt;/td&gt;&lt;td&gt;GLSL 着色器粒子系统&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;src/components/christmas/ChristmasLights.astro&lt;/code&gt;&lt;/td&gt;&lt;td&gt;CSS 彩灯装饰&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;src/components/christmas/ChristmasHat.astro&lt;/code&gt;&lt;/td&gt;&lt;td&gt;SVG 圣诞帽&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;src/components/christmas/ChristmasEffects.astro&lt;/code&gt;&lt;/td&gt;&lt;td&gt;双层雪花包装器&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;src/components/christmas/ChristmasOrnamentToggle.tsx&lt;/code&gt;&lt;/td&gt;&lt;td&gt;可拖拽圣诞球开关&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;src/styles/christmas/christmas-theme.css&lt;/code&gt;&lt;/td&gt;&lt;td&gt;配色方案&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;总结&lt;a href=&quot;#总结&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这套圣诞特效模块的设计原则如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;可插拔&lt;/strong&gt;：通过配置开关，不影响非节日期间的使用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能友好&lt;/strong&gt;：GLSL 着色器 GPU 渲染 + CSS 动画，移动端自动降级&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用户可控&lt;/strong&gt;：提供可拖拽圣诞球开关，尊重 &lt;code&gt;prefers-reduced-motion&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跨框架&lt;/strong&gt;：nanostores 实现 Astro/React 状态同步&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;层次感&lt;/strong&gt;：双层渲染 + 鼠标视差，雪花有前后景深度&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;明年可以在此基础上扩展更多节日特效（新年烟花？万圣节南瓜？），架构已经预留了扩展空间。&lt;/p&gt;
&lt;h2&gt;Refs&lt;a href=&quot;#refs&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.shadertoy.com/view/ldsGDn&quot;&gt;Shadertoy - Just snow&lt;/a&gt; - 雪花着色器参考&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://codepen.io/tobyj/pen/QjvEex&quot;&gt;Toby J 的 CodePen&lt;/a&gt; 彩灯参考实现&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;本文随时修订中，有错漏可直接评论&lt;/em&gt;&lt;/p&gt;</content:encoded><category>category:笔记</category><category>category:前端</category><category>tag:Astro</category><category>tag:R3F</category><category>tag:WebGL</category><category>tag:CSS</category></item><item><title>FE Bits Vol.20 | 博客更新与 FEDAY 见闻，Shadcn Create 发布</title><link>https://blog.cosine.ren/post/weekly-20</link><guid isPermaLink="false">weekly-20</guid><description>本期周刊记录了赴长沙参加 FEDAY 2025 的见闻与个人博客更新：移植 Fumadocs 风格的移动端目录、为图片加入 LQIP 并显著提升性能；前端动态包括 shadcn 3.6 推出可定制组件库、Storybook v7+ 构建或泄露 .env 的安全公告、TanStack Start 增加 Vue 支持、Chrome DevTools 144 支持单请求限速；技巧分享用 SVG vectorEffect 修复虚线缩放问题；精选多篇 React/CSS/WebGPU/GSAP 优质内容与 CodePen 作品。</description><pubDate>Sun, 21 Dec 2025 14:15:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本期网址 &lt;a href=&quot;https://blog.cosine.ren/post/weekly-20&quot;&gt;https://blog.cosine.ren/post/weekly-20&lt;/a&gt;
本周刊更新时间期望是在每周天。
推荐订阅本周刊的 &lt;a href=&quot;https://blog.cosine.ren/rss.xml&quot;&gt;RSS&lt;/a&gt;。
公众号 前端周周谈 FE Bits，点击阅读原文链接可查看原文。
QQ 讨论小群 598022684，讨论前端技术 &amp;amp; 生活，也可在群里投稿自己的文章，随意加入，比较偏向粉丝群的性质～
本周刊文章内容同时也开源在 &lt;a href=&quot;https://github.com/yusixian/fe-bits-weekly&quot;&gt;fe-bits-weekly&lt;/a&gt;，欢迎关注。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;今天是 2025 年 12 月 21 日，星期天。&lt;/p&gt;
&lt;p&gt;这周相当的充实，去参加了 FEDAY 2025，之前没参加过这种活动，很好奇，加上反正今年去了好多活动，感觉这种活动偶尔参加一下还很有意思的，而且顺便也能去长沙旅游一趟，就去了。&lt;/p&gt;
&lt;p&gt;参加完的感受是今年的主题特别特别多与 AI 相关的，可以看出 AI 对前端的助益是真的很大，我是比较 AI 乐观主义的，觉得 AI 能帮我省下来很多时间干我想干的事，并且也取代不了前端，但真的需要多进行学习，不断学习。&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://cos.afilmory.art/photos/MVIMG_20251220_090431&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;https://cos.afilmory.art/photos/MVIMG_20251220_090431&lt;/div&gt;
          &lt;div&gt;cos.afilmory.art&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      
        
      
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;a href=&quot;https://cos.afilmory.art/photos/IMG_20251219_183401&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://cos.afilmory.art/static/web/apple-touch-icon.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;cos.afilmory.art&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;IMG_20251219_183401 on cos’ Photos&lt;/h3&gt;
        &lt;p&gt;余弦的旅行照片与游戏截图~&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://cos.afilmory.art/photos/IMG_20251219_183401&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://cos.afilmory.art/og/IMG_20251219_183401&quot; alt=&quot;IMG_20251219_183401 on cos’ Photos&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;div&gt;
  &lt;a href=&quot;https://cos.afilmory.art/photos/IMG_20251219_182549&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://cos.afilmory.art/static/web/apple-touch-icon.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;cos.afilmory.art&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;IMG_20251219_182549 on cos’ Photos&lt;/h3&gt;
        &lt;p&gt;余弦的旅行照片与游戏截图~&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://cos.afilmory.art/photos/IMG_20251219_182549&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://cos.afilmory.art/og/IMG_20251219_182549&quot; alt=&quot;IMG_20251219_182549 on cos’ Photos&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;然后呢，这周将 &lt;a href=&quot;https://www.fumadocs.dev/docs/ui&quot;&gt;Fumadocs&lt;/a&gt; 里我很喜欢的移动端 header 目录搬到我的博客 &lt;a href=&quot;https://github.com/cosZone/astro-koharu&quot;&gt;astro-koharu&lt;/a&gt; 了，我很喜欢～&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/de3ae273e856a0b5105166b1e7d97fdf.webp&quot; alt=&quot;移动端目录&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;然后为图片加上了 LQIP，写了 &lt;a href=&quot;https://blog.cosine.ren/post/astro-lqip-implementation&quot;&gt;在 Astro 博客中实现 LQIP（低质量图片占位符）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/40e44c8ac166183d5f823d7aa81fa792.webp&quot; alt=&quot;LQIP_01&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/2e91463883247a340dc99ddc1c97ae74.webp&quot; alt=&quot;LQIP_02&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;然后将性能等，也优化了一下：&lt;/p&gt;













&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;优化前&lt;/th&gt;&lt;th&gt;优化后&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/6260b997f694ec82699bb55ba5a12c9b.webp&quot; alt=&quot;性能优化前&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/td&gt;&lt;td&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/e93f40c340a626c4ab72212a84cf6d5d.webp&quot; alt=&quot;性能优化后&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;生态与社区动态&lt;a href=&quot;#生态与社区动态&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ui.shadcn.com/docs/changelog&quot;&gt;shadcn 发布 3.6&lt;/a&gt;：借助新的 &lt;code&gt;npx shadcn create&lt;/code&gt; CLI 或者 &lt;a href=&quot;https://ui.shadcn.com/create&quot;&gt;New Project&lt;/a&gt;，现在可以使用 Radix UI 或 Base UI 来创建你自己的定制 shadcn 组件库了。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/6f81247bb1f1ccd42df35c4ae94557bc.webp&quot; alt=&quot;shadcn create&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://storybook.js.org/blog/security-advisory/&quot;&gt;Storybook 安全公告&lt;/a&gt;：
又一个 &lt;a href=&quot;https://github.com/storybookjs/storybook/security/advisories/GHSA-8452-54wp-rmv6?ref=storybookblog.ghost.io&quot;&gt;CVE-2025-68429 7.3/10&lt;/a&gt;， Storybook v7+ 版本构建时可能会不小心把 .env 打包进去。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/TanStack/router/pull/6055&quot;&gt;Vue start by birkskyum · Pull Request #6055&lt;/a&gt;：继 React 和 Solid 之后，TanStack Start 又新增了对 Vue 的支持。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.chrome.com/blog/throttle-individual-network-requests?hl=en&quot;&gt;Throttle individual network requests&lt;/a&gt;：Chrome DevTools 144 版本可以让你单独对每个网络请求限速进行调试。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;小贴士&lt;a href=&quot;#小贴士&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;写了这么一段代码，想要一段可随高度变化可控制间距的虚线。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;pointer-events-none absolute bottom-0 left-px h-[calc(100%+1rem)] w-[1.5px]&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  viewBox&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;0 0 2 366&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  preserveAspectRatio&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;none meet&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  aria-hidden&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;true&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt; d&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;M0.749978 365.5L0.750106 0&quot;&lt;/span&gt;&lt;span&gt; stroke&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;white&quot;&lt;/span&gt;&lt;span&gt; strokeOpacity&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;0.2&quot;&lt;/span&gt;&lt;span&gt; strokeWidth&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;1.5&quot;&lt;/span&gt;&lt;span&gt; strokeDasharray&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;5.25 5.25&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现不对！高度特别小的时候挤在一块了。&lt;/p&gt;
&lt;p&gt;咋办？使用 &lt;code&gt;vectorEffect=&quot;non-scaling-stroke&quot;&lt;/code&gt;&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/SVG/Reference/Attribute/vector-effect&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://developer.mozilla.org/favicon.ico&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;developer.mozilla.org&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;vector-effect - SVG：可缩放矢量图形 | MDN&lt;/h3&gt;
        &lt;p&gt;vector-effect 属性指明绘制对象时要使用的矢量效果。在任何其他合成操作（如滤镜，蒙版和剪辑等）之前，都要应用矢量效果。&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://developer.mozilla.org/zh-CN/docs/Web/SVG/Reference/Attribute/vector-effect&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;non-scaling-stroke 的使用可以看这篇文章：&lt;a href=&quot;https://www.zhangxinxu.com/wordpress/2018/12/vector-effect-non-scaling-stroke/&quot;&gt;CSS vector-effect 与 SVG stroke 描边缩放&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;MDN 的官方解释是：该值修改了笔触的方式。通常，笔触涉及在当前用户坐标系中计算形状路径的笔触轮廓，并用笔触颜料（颜色或渐变）填充轮廓。该值的最终视觉效果是笔触宽度不依赖于元素的变换（包括非均匀缩放和剪切变换）和缩放级别。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;为什么不用 &lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/CSS/Reference/Properties/border-style&quot;&gt;&lt;code&gt;border-style: dashed;&lt;/code&gt;&lt;/a&gt; ？因为他没有办法定义线段的长度和大小，视不同实现而定。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;             strokeOpacity=&quot;0.2&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;             strokeWidth=&quot;1.5&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;             strokeDasharray=&quot;5.25 5.25&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;+&lt;/span&gt;            vectorEffect=&quot;non-scaling-stroke&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;           /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;         &amp;lt;/svg&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完美实现！&lt;/p&gt;













&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;使用 &lt;code&gt;vectorEffect=&quot;non-scaling-stroke&quot;&lt;/code&gt; 前&lt;/th&gt;&lt;th&gt;使用 &lt;code&gt;vectorEffect=&quot;non-scaling-stroke&quot;&lt;/code&gt; 后&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/9c3a0cfd4fe806d978f0d7cdea031869.webp&quot; alt=&quot;前&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/td&gt;&lt;td&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/d2caa5c99c35e45de7f4b8fed1f5944e.webp&quot; alt=&quot;后&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;文章与视频&lt;a href=&quot;#文章与视频&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://acusti.ca/blog/2025/12/16/react-compiler-silent-failures-and-how-to-fix-them/&quot;&gt;React Compiler 的静默失败 (以及如何修复它们)&lt;/a&gt;：作者分享了在使用 React Compiler 过程中遇到的“静默失败”问题，即编译器在无法优化组件时不会报错，而是回退到标准 React 行为，这可能导致性能下降。为了解决这个问题，作者发现并利用了一个未文档化的 ESLint 规则 &lt;code&gt;react-hooks/todo&lt;/code&gt;，通过将其设置为错误级别，可以在编译失败时中断构建过程。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tympanus.net/codrops/2025/12/17/building-responsive-scroll-triggered-curved-path-animations-with-gsap/&quot;&gt;使用 GSAP 构建响应式、滚动触发的曲线路径动画&lt;/a&gt;：这篇文章提供了一个好工具 &lt;a href=&quot;https://codepen.io/betawaxx/pen/JoGZQLZ&quot;&gt;Paths &amp;amp; Control Points&lt;/a&gt;，让开发者能够拖拽控制点实时调整曲线。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cyandev.app/post/make-a-snowfall-with-webgpu&quot;&gt;用 WebGPU 来造一场雪 | Cyandev&lt;/a&gt;：手把手教你如何用 WebGPU 在网页上下雪，兼顾性能与效果，让你的博客在圣诞带上节日色彩～&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.logrocket.com/react-children-prop-typescript/&quot;&gt;How to type React children correctly in TypeScript&lt;/a&gt;：还在为 React 中 &lt;code&gt;children&lt;/code&gt; prop 的 TypeScript 类型定义挠头？这篇博文介绍了如何在 React 应用中如何正确使用 TypeScript 定义 &lt;code&gt;children&lt;/code&gt; prop 的类型。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://frontendmasters.com/blog/different-page-transitions-for-different-circumstances/&quot;&gt;Different Page Transitions For Different Circumstances&lt;/a&gt;：这篇博文教你如何使用 &lt;code&gt;View Transitions&lt;/code&gt;（视图转换）为多页应用程序（MPA）创建差异化的页面切换动画。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://acusti.ca/blog/2025/12/09/how-ai-coding-agents-hid-a-timebomb-in-our-app/&quot;&gt;How AI Coding Agents Hid a Timebomb in Our App&lt;/a&gt;：一篇关于 AI 编程助手“无意间”在应用里埋下了一颗定时炸弹的故事：
&lt;blockquote&gt;
&lt;p&gt;一个由 AI 编程助手（AI Coding Agent）删除的代码注释，导致一个关键的 &lt;code&gt;readOnly&lt;/code&gt; 属性被移除，进而引发了 React 组件的无限递归渲染。更糟的是，React 19 的 &lt;code&gt;&amp;lt;Activity&amp;gt;&lt;/code&gt; 组件将这个 Bug 隐藏了起来，使其在数分钟内都不会崩溃，极大增加了调试难度。作者通过痛苦的调试过程，最终发现罪魁祸首是 AI 删除了注释，以及自己没有为关键的结构性约束编写测试。文章强调了在 AI 辅助开发背景下，测试而非注释，才是确保代码质量和安全的关键。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;CSS 新特性&lt;a href=&quot;#css-新特性&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2025/12/state-logic-native-power-css-wrapped-2025/&quot;&gt;State, Logic, And Native Power: CSS Wrapped 2025&lt;/a&gt;：深入解读了 Chrome 团队发布的“&lt;a href=&quot;https://chrome.dev/css-wrapped-2025/&quot;&gt;CSS Wrapped 2025&lt;/a&gt;”年度总结报告，强调 CSS 正从单纯的样式语言向构建动态、强大应用的原生工具集转变，开启了一个激动人心的前端新时代。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://css-tricks.com/creating-scroll-based-animations-in-full-view/&quot;&gt;Creating Scroll-Based Animations in Full view()&lt;/a&gt;：介绍了 CSS &lt;code&gt;animation-timeline&lt;/code&gt; 属性中的 &lt;code&gt;view()&lt;/code&gt; 函数，它允许开发者创建基于元素在滚动容器（scrollport）中可见性（visibility）的动画。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://css-tricks.com/what-else-could-container-queries-query/&quot;&gt;What Else Could Container Queries… Query?&lt;/a&gt;：容器查询（container queries）可不止查询尺寸那么简单，未来还有更多可能性等待探索！&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=55uUK-iJeNM&quot;&gt; 2025 年所有浏览器支持的 11 个 CSS 新特性&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://css-tricks.com/responsive-list-of-avatars-using-modern-css-part-2/&quot;&gt;Responsive List of Avatars Using Modern CSS (Part 2)&lt;/a&gt;：是 “使用现代 CSS 创建响应式头像列表”系列的第二部分，主要探讨如何利用 &lt;code&gt;offset&lt;/code&gt; 或 &lt;code&gt;transform&lt;/code&gt; 属性，结合 &lt;code&gt;sibling-index()&lt;/code&gt; 和 &lt;code&gt;sibling-count()&lt;/code&gt; 等 CSS 函数，实现一个响应式圆形头像列表的布局。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;工具&lt;a href=&quot;#工具&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://stuffandnonsense.co.uk/toon-text/tool.html&quot;&gt;Andy Clarke’s Toon Text&lt;/a&gt;：Andy Clarke 开发的一款卡通标题文本生成器，它可以生成具有卡通风格的文本，并提供 CSS 代码。
&lt;a href=&quot;https://css-tricks.com/toon-title-text-generator/&quot;&gt;介绍文章&lt;/a&gt;还重点介绍了其核心功能 Splinter.js，它通过包裹每个字符的 &lt;span&gt; 标签，并加入 ARIA 属性，在实现逐字样式控制的同时，增强了可访问性，解决了传统工具可能导致辅助技术识别障碍的问题。&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/ab482b28222826e47f5618422ab4f5fe.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Raycast 插件：&lt;a href=&quot;https://www.raycast.com/j3lte/css-tricks&quot;&gt;https://www.raycast.com/j3lte/css-tricks&lt;/a&gt;
介绍文章：&lt;a href=&quot;https://css-tricks.com/search-css-tricks-raycast-extension/&quot;&gt;Search CSS-Tricks Raycast Extension&lt;/a&gt;
&lt;blockquote&gt;
&lt;p&gt;一个 Raycast 扩展，可以直接从本地计算机搜索 CSS-Tricks 文章。该扩展使用 WordPress REST API 获取实时搜索结果，提供了一种快速查找和复制文章 URL 的方法。单击结果会显示摘要并在浏览器中打开文章。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Codepen 精选&lt;a href=&quot;#codepen-精选&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;div&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;Anderson Mancini(@Andersonmancini):  太阳出来了 ☀️
我终于完成了将我的“终极镜头光晕”移植到 #threejs WebGPU 的工作。性能简直令人难以置信，因为现在 threejs 已经将遮挡查询直接集成到了渲染管线中。如果你需要知道某个物体是否被遮挡，只需直接向 threejs 查询即可。文档中有示例。
这样一来，我就不再需要光线投射器来检测遮挡了，这就是性能大幅提升的原因。
如果您想查看结果：https:// planpoint-webgpu.vercel.app&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;/div&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/2ed4fd7cb089993bee9270bdcb98a548.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;CyberPopover 2025&lt;a href=&quot;#cyberpopover-2025&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/jh3y/pen/yyNWGNG&quot;&gt;yyNWGNG&lt;/a&gt; by jh3y (&lt;a href=&quot;https://codepen.io/jh3y&quot;&gt;@jh3y&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在 Jhey Tompkins 这款 Codepen 中，点击闪烁按钮即可打开带有电子音效的 CyberPopover。作为 Jhey 的经典之作，这款 Pen 是可配置的！打开右上角的面板，即可进行设置。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/403b16f637bc713974df139ee1b554d0.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;无需 JavaScript 的 Lightbox（灯箱）&lt;a href=&quot;#无需-javascript-的-lightbox灯箱&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/daviddarnes/pen/bNbagPy&quot;&gt;bNbagPy&lt;/a&gt; by daviddarnes (&lt;a href=&quot;https://codepen.io/daviddarnes&quot;&gt;@daviddarnes&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;David Darnes 分享了一个快速演示，展示如何使用 Popover API 构建一个简单的照片灯箱，无需 JS，只需不到 30 行 CSS 代码。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/18647a9d75aaa24ece6ba9ca36b8b458.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Refs&lt;a href=&quot;#refs&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://react.statuscode.com/issues/456&quot;&gt;React Status #456&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://frontendfoc.us/issues/722&quot;&gt;Frontend Focus Issue 722: December 17, 2025&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://thisweekinreact.com/newsletter/263&quot;&gt;This Week In React #263&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>category:周刊</category></item><item><title>在 Astro 博客中实现 LQIP（低质量图片占位符）</title><link>https://blog.cosine.ren/post/astro-lqip-implementation</link><guid isPermaLink="false">astro-lqip-implementation</guid><description>本文由 AI 辅助写作，作记录用
一开始想在博客中，实现类似 Minimal CSS-only blurry image placeholders 的 CSS-only LQIP（低质量图片占位符），使用单个 CSS 自定义属性 —lqip 编码图片的模糊预览。
这篇文章的技术原理是使用 20</description><pubDate>Sat, 20 Dec 2025 09:52:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;本文由 AI 辅助写作，作记录用&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;一开始想在博客中，实现类似 &lt;a href=&quot;https://leanrada.com/notes/css-only-lqip&quot;&gt;Minimal CSS-only blurry image placeholders&lt;/a&gt; 的 CSS-only LQIP（低质量图片占位符），使用单个 CSS 自定义属性 —lqip 编码图片的模糊预览。&lt;/p&gt;
&lt;p&gt;这篇文章的技术原理是使用 20 位整数编码图片信息（8 位 Oklab 基础色 + 12 位亮度分量），在 CSS 中通过位运算解码（&lt;code&gt;mod()&lt;/code&gt;, &lt;code&gt;round(down)&lt;/code&gt;, &lt;code&gt;pow()&lt;/code&gt; 等），使用径向渐变叠加渲染模糊效果，配合二次缓动实现平滑过渡。&lt;/p&gt;
&lt;p&gt;我选择简化一些的实现，不追求 CSS Only 了，因为打算先做博客内部的图片，文章内部的外部图片等后续优化的时候再一起做，放上最终效果在这里：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/40e44c8ac166183d5f823d7aa81fa792.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/2e91463883247a340dc99ddc1c97ae74.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;需要运行一下 &lt;code&gt;nr generate:lqips&lt;/code&gt; 就会生成一个 &lt;code&gt;lqips.json&lt;/code&gt; 的 json 文件在 assets 下，若没有这个文件则不提供占位符～&lt;/p&gt;
&lt;p&gt;以下为 AI 生成的记录，仅做小改。&lt;/p&gt;
&lt;h2&gt;什么是 LQIP&lt;a href=&quot;#什么是-lqip&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;LQIP（Low Quality Image Placeholder）是一种图片加载优化技术，在高清图片加载完成前，先显示一个低质量的占位符，避免页面出现空白或布局抖动。&lt;/p&gt;
&lt;p&gt;常见的 LQIP 实现方式包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;缩略图方案&lt;/strong&gt;：生成一张极小的图片（如 20x20），加载时放大并模糊显示&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BlurHash&lt;/strong&gt;：将图片编码为一个短字符串，在客户端解码渲染&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;主色调方案&lt;/strong&gt;：提取图片的主色调作为纯色背景&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;渐变方案&lt;/strong&gt;：提取多个色彩点生成 CSS 渐变&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;本文介绍的是&lt;strong&gt;渐变方案&lt;/strong&gt;，通过构建时提取图片的四象限主色，生成 CSS 线性渐变作为占位符。这种方案的优势在于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;零运行时开销&lt;/strong&gt;：纯 CSS 实现，无需 JavaScript 解码&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;极小的数据体积&lt;/strong&gt;：每张图片仅需 18 个字符存储&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;视觉效果自然&lt;/strong&gt;：渐变色比纯色更贴近原图的色彩分布&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;方案设计&lt;a href=&quot;#方案设计&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;整体架构分为三个部分：&lt;/p&gt;
&lt;pre&gt;flowchart LR
    A[构建时生成脚本&amp;lt;br/&amp;gt;generateLqips.ts] --&amp;gt; B[JSON 数据文件&amp;lt;br/&amp;gt;lqips.json]
    B --&amp;gt; C[运行时工具函数&amp;lt;br/&amp;gt;lqip.ts]

    A -.-&amp;gt; D[sharp 处理图片&amp;lt;br/&amp;gt;提取四象限颜色]
    C -.-&amp;gt; E[Astro 组件调用&amp;lt;br/&amp;gt;生成 CSS 渐变]&lt;/pre&gt;
&lt;h3&gt;数据格式设计&lt;a href=&quot;#数据格式设计&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;为了最小化 JSON 体积，我们采用紧凑的存储格式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;cover/1.webp&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;87a3c4c2dfefbddae9&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;cover/2.webp&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;6e3b38ae7472af7574&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每个值是 18 个十六进制字符，由 3 个颜色组成（去掉 &lt;code&gt;#&lt;/code&gt; 前缀）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;字符 0-5：左上角颜色&lt;/li&gt;
&lt;li&gt;字符 6-11：右上角颜色&lt;/li&gt;
&lt;li&gt;字符 12-17：右下角颜色&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;运行时将其解码为 CSS 渐变：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;linear-gradient(135deg, &lt;/span&gt;&lt;span&gt;#87a3c4&lt;/span&gt;&lt;span&gt; 0%, &lt;/span&gt;&lt;span&gt;#c2dfef&lt;/span&gt;&lt;span&gt; 50%, &lt;/span&gt;&lt;span&gt;#bddae9&lt;/span&gt;&lt;span&gt; 100%)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;（PS：这个其实是权衡了牺牲了一点可读性，但是还想保留一点可编辑性，别喷我喵我知道肯定会有人问为什么不用二进制格式）&lt;/p&gt;
&lt;h2&gt;构建时生成脚本&lt;a href=&quot;#构建时生成脚本&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;src/scripts/generateLqips.ts&lt;/code&gt; 负责在构建时处理所有图片：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; sharp &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;sharp&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { glob } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;glob&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; fs &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;fs/promises&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; path &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;path&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; chalk &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;chalk&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// --------- 配置 ---------&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; IMAGE_GLOB&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;public/img/**/*.{webp,jpg,jpeg,png}&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; OUTPUT_FILE&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;src/assets/lqips.json&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// --------- 类型定义 ---------&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; RgbColor&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  r&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  g&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  b&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; LqipMap&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Record&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// --------- 颜色工具函数 ---------&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; * RGB 转十六进制字符串&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; rgbToHex&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rgb&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; RgbColor&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; toHex&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    Math.&lt;/span&gt;&lt;span&gt;round&lt;/span&gt;&lt;span&gt;(Math.&lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, Math.&lt;/span&gt;&lt;span&gt;min&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;255&lt;/span&gt;&lt;span&gt;, n)))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      .&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      .&lt;/span&gt;&lt;span&gt;padStart&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;0&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; `#${&lt;/span&gt;&lt;span&gt;toHex&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rgb&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}${&lt;/span&gt;&lt;span&gt;toHex&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rgb&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}${&lt;/span&gt;&lt;span&gt;toHex&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rgb&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// --------- 图片处理 ---------&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; * 处理单张图片，生成紧凑的颜色字符串&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; processImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;imagePath&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // Resize to 2x2 to get 4 quadrant colors&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; resized&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; sharp&lt;/span&gt;&lt;span&gt;(imagePath).&lt;/span&gt;&lt;span&gt;resize&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, { fit: &lt;/span&gt;&lt;span&gt;&apos;fill&apos;&lt;/span&gt;&lt;span&gt; }).&lt;/span&gt;&lt;span&gt;raw&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;toBuffer&lt;/span&gt;&lt;span&gt;({ resolveWithObject: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; channels&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; resized.info.channels;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; resized.data;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // Extract 4 colors (top-left, top-right, bottom-left, bottom-right)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; colors&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;; i &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; 4&lt;/span&gt;&lt;span&gt;; i&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; offset&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; channels;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; rgb&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; RgbColor&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        r: data[offset],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        g: data[offset &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        b: data[offset &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      colors.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rgbToHex&lt;/span&gt;&lt;span&gt;(rgb));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 紧凑存储：3 个颜色（左上、右上、右下），去掉 # 前缀&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 用于生成 135deg 斜向渐变&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; compact&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `${&lt;/span&gt;&lt;span&gt;colors&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}${&lt;/span&gt;&lt;span&gt;colors&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}${&lt;/span&gt;&lt;span&gt;colors&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; compact;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(chalk.&lt;/span&gt;&lt;span&gt;red&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`  Error processing ${&lt;/span&gt;&lt;span&gt;imagePath&lt;/span&gt;&lt;span&gt;}:`&lt;/span&gt;&lt;span&gt;), error);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; * 文件路径转短键名&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; filePathToKey&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;filePath&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // public/img/cover/1.webp → cover/1.webp&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; filePath.&lt;/span&gt;&lt;span&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt;\/&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt;\/&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// --------- 主函数 ---------&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; main&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; startTime&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Date.&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(chalk.&lt;/span&gt;&lt;span&gt;cyan&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;=== LQIP Generator ===&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; files&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; glob&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IMAGE_GLOB&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;files.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(chalk.&lt;/span&gt;&lt;span&gt;yellow&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;No image files found.&apos;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(chalk.&lt;/span&gt;&lt;span&gt;blue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`Found ${&lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;} images&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; lqips&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; LqipMap&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; processed &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; file&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; files) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    process.stdout.&lt;/span&gt;&lt;span&gt;write&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;\r&lt;/span&gt;&lt;span&gt;  Processing ${&lt;/span&gt;&lt;span&gt;processed&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;}/${&lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;}...`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; compact&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; processImage&lt;/span&gt;&lt;span&gt;(file);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (compact &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; key&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; filePathToKey&lt;/span&gt;&lt;span&gt;(file);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      lqips[key] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; compact;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      processed&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; dir&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; path.&lt;/span&gt;&lt;span&gt;dirname&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;OUTPUT_FILE&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  await&lt;/span&gt;&lt;span&gt; fs.&lt;/span&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt;(dir, { recursive: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  await&lt;/span&gt;&lt;span&gt; fs.&lt;/span&gt;&lt;span&gt;writeFile&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;OUTPUT_FILE&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;(lqips, &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; elapsed&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ((Date.&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; startTime) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 1000&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;toFixed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(chalk.&lt;/span&gt;&lt;span&gt;green&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;\n\n&lt;/span&gt;&lt;span&gt;Done! Generated LQIP for ${&lt;/span&gt;&lt;span&gt;processed&lt;/span&gt;&lt;span&gt;} images in ${&lt;/span&gt;&lt;span&gt;elapsed&lt;/span&gt;&lt;span&gt;}s`&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(chalk.&lt;/span&gt;&lt;span&gt;cyan&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`Output saved to: ${&lt;/span&gt;&lt;span&gt;OUTPUT_FILE&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;核心原理：2x2 缩放&lt;a href=&quot;#核心原理2x2-缩放&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;sharp 的 &lt;code&gt;resize(2, 2, { fit: &apos;fill&apos; })&lt;/code&gt; 将图片缩放到 2x2 像素，每个像素代表原图四分之一区域的平均色：&lt;/p&gt;
&lt;pre&gt;flowchart LR
    subgraph orig[原图]
        direction TB
        subgraph row1[&quot; &quot;]
            A1[&quot;区域 A&quot;]
            B1[&quot;区域 B&quot;]
        end
        subgraph row2[&quot; &quot;]
            C1[&quot;区域 C&quot;]
            D1[&quot;区域 D&quot;]
        end
    end

    orig -.-&amp;gt;|2x2 缩放| scaled

    subgraph scaled[2x2 缩放结果]
        direction TB
        subgraph row3[&quot; &quot;]
            A2[&quot;A&quot;]
            B2[&quot;B&quot;]
        end
        subgraph row4[&quot; &quot;]
            C2[&quot;C&quot;]
            D2[&quot;D&quot;]
        end
    end&lt;/pre&gt;
&lt;p&gt;我们选取 A（左上）、B（右上）、D（右下）三个颜色，生成 135 度斜向渐变，这样既能覆盖图片的主要色彩分布，又避免存储冗余数据。&lt;/p&gt;
&lt;h2&gt;运行时工具函数&lt;a href=&quot;#运行时工具函数&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;src/lib/lqip.ts&lt;/code&gt; 提供运行时 API：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// 导入构建时生成的 LQIP 数据&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; lqips&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Record&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; lqipData&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; import&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;@assets/lqips.json&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  lqips &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; lqipData.default &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; Record&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 文件不存在时静默失败（首次构建前）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; * 图片路径转 LQIP 键名&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; imagePathToKey&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;imagePath&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; imagePath.&lt;/span&gt;&lt;span&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;span&gt;\/&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt;\/&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; * 获取图片的 LQIP 渐变 CSS&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; getLqipGradient&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;imagePath&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; undefined&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; key&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; imagePathToKey&lt;/span&gt;&lt;span&gt;(imagePath);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; compact&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; lqips[key];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (compact?.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; !==&lt;/span&gt;&lt;span&gt; 18&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; undefined&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 解码紧凑格式：18 字符 → 3 个十六进制颜色&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; c1&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `#${&lt;/span&gt;&lt;span&gt;compact&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; c2&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `#${&lt;/span&gt;&lt;span&gt;compact&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;12&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; c3&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `#${&lt;/span&gt;&lt;span&gt;compact&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;12&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;18&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; `linear-gradient(135deg, ${&lt;/span&gt;&lt;span&gt;c1&lt;/span&gt;&lt;span&gt;} 0%, ${&lt;/span&gt;&lt;span&gt;c2&lt;/span&gt;&lt;span&gt;} 50%, ${&lt;/span&gt;&lt;span&gt;c3&lt;/span&gt;&lt;span&gt;} 100%)`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; * 判断是否为外部图片&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; isExternalImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;imagePath&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; boolean&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; imagePath.&lt;/span&gt;&lt;span&gt;startsWith&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;http://&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; imagePath.&lt;/span&gt;&lt;span&gt;startsWith&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;https://&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; * 获取 LQIP 内联样式&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; getLqipStyle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;imagePath&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; undefined&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;isExternalImage&lt;/span&gt;&lt;span&gt;(imagePath)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; undefined&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; gradient&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; getLqipGradient&lt;/span&gt;&lt;span&gt;(imagePath);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; gradient &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; `background-image:${&lt;/span&gt;&lt;span&gt;gradient&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; undefined&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; * 获取 LQIP props（用于组件）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; getLqipProps&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;imagePath&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;?:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; } {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;isExternalImage&lt;/span&gt;&lt;span&gt;(imagePath)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; { class: &lt;/span&gt;&lt;span&gt;&apos;lqip-fallback&apos;&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; style&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; getLqipStyle&lt;/span&gt;&lt;span&gt;(imagePath);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; style &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; { style } &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;外部图片降级处理&lt;a href=&quot;#外部图片降级处理&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;对于外部图片（用户自定义的封面 URL），我们无法在构建时获取，因此提供 CSS 降级方案：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/* src/styles/components/lqip.css */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.lqip-fallback&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  background-color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;hsl&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--muted&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;在 Astro 组件中使用&lt;a href=&quot;#在-astro-组件中使用&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;文章卡片封面&lt;a href=&quot;#文章卡片封面&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;PostItemCard.astro&lt;/code&gt; 中应用 LQIP：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { getLqipProps } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;@lib/lqip&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; finalCover&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; cover &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; randomCover &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; defaultCoverList[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; lqipProps&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; getLqipProps&lt;/span&gt;&lt;span&gt;(finalCover);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; href&lt;/span&gt;&lt;span&gt;={href} &lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;={lqipProps.style} &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;={&lt;/span&gt;&lt;span&gt;cn&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;relative overflow-hidden&apos;&lt;/span&gt;&lt;span&gt;, lqipProps.class)}&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;Image&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;={finalCover} &lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt;={&lt;/span&gt;&lt;span&gt;600&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;={&lt;/span&gt;&lt;span&gt;186&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;loading&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;lazy&quot;&lt;/span&gt;&lt;span&gt; alt&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;post cover&quot;&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;h-full w-full object-cover&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;页面横幅&lt;a href=&quot;#页面横幅&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Cover.astro&lt;/code&gt; 中应用 LQIP：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { getLqipStyle } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;@lib/lqip&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; bannerLqipStyle&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; getLqipStyle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;/img/site_header_1920.webp&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;relative h-full w-full&quot;&lt;/span&gt;&lt;span&gt; style&lt;/span&gt;&lt;span&gt;={bannerLqipStyle} &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;banner-box&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;Image&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/img/site_header_1920.webp&quot;&lt;/span&gt;&lt;span&gt; width&lt;/span&gt;&lt;span&gt;={&lt;/span&gt;&lt;span&gt;1920&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;={&lt;/span&gt;&lt;span&gt;1080&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;alt&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;cover&quot;&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;h-full w-full object-cover&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;存储优化&lt;a href=&quot;#存储优化&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在迭代过程中，我们进行了多轮体积优化：&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;版本&lt;/th&gt;&lt;th&gt;格式示例&lt;/th&gt;&lt;th&gt;每条约&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;v1 完整 CSS&lt;/td&gt;&lt;td&gt;&lt;code&gt;linear-gradient(135deg, #87a3c4 0%, ...)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;80 字节&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;v2 紧凑颜色&lt;/td&gt;&lt;td&gt;&lt;code&gt;87a3c4c2dfefbddae9&lt;/code&gt;&lt;/td&gt;&lt;td&gt;47 字节&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;v3 短键名&lt;/td&gt;&lt;td&gt;&lt;code&gt;cover/1.webp&lt;/code&gt; → 同上&lt;/td&gt;&lt;td&gt;&lt;strong&gt;42 字节&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;最终版本相比 v1 减少约 &lt;strong&gt;50%&lt;/strong&gt; 体积。按 1000 张图片估算：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;v1：~80KB&lt;/li&gt;
&lt;li&gt;v3：~42KB&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于更激进的优化，还可以考虑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Base64 编码&lt;/strong&gt;：3 色 = 9 字节 → 12 字符 Base64&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;二进制格式&lt;/strong&gt;：完全避免 JSON 开销&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但考虑到可读性和调试便利性，当前的 JSON 格式已经是较好的平衡点。&lt;/p&gt;
&lt;h2&gt;构建集成&lt;a href=&quot;#构建集成&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;package.json&lt;/code&gt; 中添加脚本：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;scripts&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;generate:lqips&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;npx tsx src/scripts/generateLqips.ts&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以在 CI/CD 中配置为构建前置步骤，或在添加新图片后手动执行。&lt;/p&gt;
&lt;h2&gt;效果展示&lt;a href=&quot;#效果展示&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;以红叶图片为例：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;原图路径：&lt;code&gt;/img/cover/2.webp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;LQIP 数据：&lt;code&gt;6e3b38ae7472af7574&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;解码渐变：&lt;code&gt;linear-gradient(135deg, #6e3b38 0%, #ae7472 50%, #af7574 100%)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;渐变色准确反映了原图的暖红色调，在图片加载前提供了良好的视觉预期。&lt;/p&gt;
&lt;h2&gt;总结&lt;a href=&quot;#总结&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;本文介绍了一种轻量级的 LQIP 实现方案：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;构建时&lt;/strong&gt;：使用 sharp 提取图片四象限主色，生成紧凑的颜色字符串&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;运行时&lt;/strong&gt;：解码为 CSS 渐变，作为图片容器的背景&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;降级策略&lt;/strong&gt;：外部图片使用纯色占位符&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这种方案的核心优势在于&lt;strong&gt;零运行时开销&lt;/strong&gt;和&lt;strong&gt;极小的数据体积&lt;/strong&gt;，非常适合静态站点生成器如 Astro、Next.js 等场景。&lt;/p&gt;
&lt;h2&gt;Refs&lt;a href=&quot;#refs&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://leanrada.com/notes/css-only-lqip/&quot;&gt;CSS-only LQIP&lt;/a&gt; - 原始灵感来源，使用 20 位整数编码的高级方案&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sharp.pixelplumbing.com/&quot;&gt;sharp 文档&lt;/a&gt; - Node.js 高性能图片处理库&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blurha.sh/&quot;&gt;BlurHash&lt;/a&gt; - 另一种 LQIP 方案&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;本文随时修订中，有错漏可直接评论&lt;/em&gt;&lt;/p&gt;</content:encoded><category>category:笔记</category><category>category:前端</category><category>tag:图片优化</category><category>tag:Astro</category><category>tag:LQIP</category></item><item><title>FE Bits Vol.19｜站点新功能与 React 披露两个新的 RSC 漏洞</title><link>https://blog.cosine.ren/post/weekly-19</link><guid isPermaLink="false">weekly-19</guid><description>本期周刊记录了站点新增“零后端”相关推荐与 AI 摘要；缓存增量构建、上线零开销。生态方面，RSC 披露新漏洞并已修复，Three.js r182 发布，Deno 2.6 推出 dx，TypeScript 7 原生工具链进展显著，CSS 多项新特性推进。</description><pubDate>Sun, 14 Dec 2025 12:46:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本期网址 &lt;a href=&quot;https://blog.cosine.ren/post/weekly-19&quot;&gt;https://blog.cosine.ren/post/weekly-19&lt;/a&gt;
本周刊更新时间期望是在每周天。
推荐订阅本周刊的 &lt;a href=&quot;https://blog.cosine.ren/rss.xml&quot;&gt;RSS&lt;/a&gt;。
公众号 前端周周谈 FE Bits，点击阅读原文链接可查看原文。
QQ 讨论小群 598022684，讨论前端技术 &amp;amp; 生活，也可在群里投稿自己的文章，随意加入，比较偏向粉丝群的性质～
本周刊文章内容同时也开源在 &lt;a href=&quot;https://github.com/yusixian/fe-bits-weekly&quot;&gt;fe-bits-weekly&lt;/a&gt;，欢迎关注。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;今天是 2025 年 12 月 14 日，星期天。&lt;/p&gt;
&lt;p&gt;本周给我的博客站点增加了无需后端的相关推荐和 AI 摘要功能。&lt;/p&gt;
&lt;p&gt;相关推荐是参考了这篇超赞的实现：&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://alexop.dev/posts/semantic-related-posts-astro-transformersjs/&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://alexop.dev/favicon.svg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;alexop.dev&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;No Server, No Database: Smarter Related Posts in Astro with `transformers.js` | alexop.dev&lt;/h3&gt;
        &lt;p&gt;How I used Hugging Face embeddings to create smart “Related Posts” for my Astro blog—no backend, no database, just TypeScript.&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://alexop.dev/posts/semantic-related-posts-astro-transformersjs/&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://alexop.dev/posts/no-server-no-database-smarter-related-posts-in-astro-with-transformers-js/index.png&quot; alt=&quot;No Server, No Database: Smarter Related Posts in Astro with `transformers.js` | alexop.dev&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;用 transformers.js 在本地算“语义相似度”，自动挑出最相关的 5 篇文章。&lt;/p&gt;
&lt;p&gt;然后自己又实现了一下，支持排除特定文章（比如周刊），还可以选择是否把“正文”也纳入计算：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只用标题+描述：速度飞快，适合文章多的情况，但不是很准。&lt;/li&gt;
&lt;li&gt;加上正文：效果明显更好，就是生成会慢亿些。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 使用 Snowflake/snowflake-arctic-embed-m-v2.0 计算 168 篇文章（标题+描述）的时间&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Done!&lt;/span&gt;&lt;span&gt; Generated&lt;/span&gt;&lt;span&gt; similarities&lt;/span&gt;&lt;span&gt; for&lt;/span&gt;&lt;span&gt; 168&lt;/span&gt;&lt;span&gt; posts&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; 4.1s&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 使用 Snowflake/snowflake-arctic-embed-m-v2.0 计算 168 篇文章（标题+描述+正文）的时间&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Done!&lt;/span&gt;&lt;span&gt; Generated&lt;/span&gt;&lt;span&gt; similarities&lt;/span&gt;&lt;span&gt; for&lt;/span&gt;&lt;span&gt; 168&lt;/span&gt;&lt;span&gt; posts&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; 219.3s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这速度差别有亿点大，但是我个人很喜欢带正文的结果，效果显然会更好。&lt;/p&gt;
&lt;p&gt;那有没有更折中的方案呢？于是我决定再加一个跑 AI 摘要的功能，使用 &lt;a href=&quot;https://xsai.js.org/docs/packages/generate/text&quot;&gt;xsai&lt;/a&gt; sdk，本地 LM Studio 使用 &lt;code&gt;qwen/qwen3-4b-2507&lt;/code&gt; 模型跑一遍，跑起来结果又快又好！新增文章通过缓存，只需要跑新增文章的摘要就行，而且效果跟使用正文差不多！&lt;/p&gt;
&lt;p&gt;文章详情里还有个可折叠的摘要卡片，点开有打字机小动画（尊重无动画偏好）&lt;/p&gt;
&lt;p&gt;都在构建时本地跑完 git 提交，线上零开销。后面会继续调模型和缓存等，优化一下，写的比较匆忙。&lt;/p&gt;
&lt;p&gt;欢迎来试试新功能！精力有限还没有写部署文档等，但是有一篇尚未发布的让 AI 写的 &lt;a href=&quot;https://github.com/cosZone/astro-koharu/blob/main/src/content/blog/tools/astro-koharu-%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97.md&quot;&gt;astro-koharu-使用指南&lt;/a&gt;，可能会有错漏，但是大体配置上差不多，求 star～&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://github.com/cosZone/astro-koharu&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://github.com/fluidicon.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;github.com&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;GitHub - cosZone/astro-koharu: astro-koharu 是一个萌系/二次元/粉蓝配色的 astro 主题博客，灵感来自 Hexo 的 Shoka 主题，加了很多自己的小巧思，性能优越。&lt;/h3&gt;
        &lt;p&gt;astro-koharu 是一个萌系/二次元/粉蓝配色的 astro 主题博客，灵感来自 Hexo 的 Shoka 主题，加了很多自己的小巧思，性能优越。 - cosZone/astro-koharu&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://github.com/cosZone/astro-koharu&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://opengraph.githubassets.com/2d40439fc78bd8179de9715479d3aade2e2d35453ac7b364b41b5fa9be7133de/cosZone/astro-koharu&quot; alt=&quot;GitHub - cosZone/astro-koharu: astro-koharu 是一个萌系/二次元/粉蓝配色的 astro 主题博客，灵感来自 Hexo 的 Shoka 主题，加了很多自己的小巧思，性能优越。&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/3262e3664b7525f5d3770b8d0fde419a.webp&quot; alt=&quot;跑出来的文件&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/b168de06cc98f80e26754f03e92c0f13.webp&quot; alt=&quot;AI 摘要&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/76252d37c923a25bdb5d68c8d2ef5dc7.webp&quot; alt=&quot;相关推荐&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;很开心，忙里偷闲写这些东西，超级开心。&lt;/p&gt;
&lt;h2&gt;生态与社区动态&lt;a href=&quot;#生态与社区动态&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.epicreact.dev/react-routers-take-on-react-server-components-4bj7q&quot;&gt;React Router’s take on React Server Components&lt;/a&gt;：React Router 正在添加 RSC 支持。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;你知道 React Router 正在添加对 React Server Components (RSC) 的支持吗？虽然目前还处于实验阶段，但距离正式发布已经非常接近了，而且我认为 React Router 对 RSC 的实现非常出色。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;threejs.org/changelog/?r182&quot;&gt;Three.js r182 released &lt;/a&gt;：进行了全面升级，核心改动包括迁移到 ESLint 9、引入 flat config、对 WebGLRenderer 与 WebGPURenderer 进行多项性能与功能优化（如更高效的阴影映射、EAC 纹理支持、VSM 代码重构），节点系统得到显著改进（支持更多类型、优化缓存、引入 float packing/unpacking），同时大量 bug 修复、文档清理与示例更新，整体提升了代码质量、可维护性与渲染效率。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;/div&gt;
&lt;p&gt;一些官方示例：&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://deno.com/blog/v2.6&quot;&gt;Deno 2.6: dx is the new npx&lt;/a&gt;：Deno 2.6 通过引入 &lt;code&gt;dx&lt;/code&gt;（类似 &lt;code&gt;npx&lt;/code&gt;）简化了包二进制的执行，新增细粒度权限控制与权限代理，使用 Go 编写的 &lt;code&gt;tsgo&lt;/code&gt; 加速 TypeScript 类型检查，并支持 Wasm 源码导入。安全方面加入 &lt;code&gt;deno audit&lt;/code&gt; 与 &lt;code&gt;--socket&lt;/code&gt;，改进依赖管理与脚本审批。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://code.visualstudio.com/blogs/2025/12/03/introducing-vs-code-insiders-podcast&quot;&gt;微软推出 VS Code Insiders 播客&lt;/a&gt;：由 Visual Studio Code 官方团队打造，深度访谈开发者、产品经理以及社区贡献者，解密新特性、实验工具和 AI 驱动的工作流。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;想知道世界上最受欢迎的代码编辑器背后究竟发生了什么吗？ VS Code Insiders Podcast 将为你揭开神秘面纱，带你深入了解塑造 Visual Studio Code 未来的功能、决策和人员。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.chrome.com/blog/chrome-devtools-mcp-debug-your-browser-session?hl=en&quot;&gt;使用 Chrome DevTools MCP，让您的编码代理调试浏览器会话&lt;/a&gt;：Chrome DevTools MCP 终于能让你的 AI 编程助手直接介入浏览器调试了，更丝滑了！好耶！&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;如需将 chrome-devtools-mcp 服务器连接到正在运行的 Chrome 实例，请使用 —autoConnect 命令行实参来设置 MCP 服务器&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;注意： 在 Chrome M144 达到稳定版之前，您必须指定 —channel=canary&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;mcpServers&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;chrome-devtools&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;command&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;npx&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;args&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;chrome-devtools-mcp@latest&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;--autoConnect&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;--channel=canary&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/blog/upvote-features?hl=en&quot;&gt;为您希望看到的网络功能投票&lt;/a&gt;：现在，你可以通过点赞 (upvote) 来影响你最想看到的 Web 特性啦！&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;WebDX 社区组织推出了一项新功能，允许开发者直接通过“点赞 (upvote)”来表达对特定 Web 特性的需求，以帮助浏览器厂商优先考虑开发。虽然点赞多不代表直接采纳，但开发者的需求将成为浏览器厂商决策的重要参考因素。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;你是否曾经在 Can I Use 网站上看到某个你非常想使用的功能被标记为红色，然后恨不得直接戳戳浏览器厂商，说：“嘿，我需要这个功能！”？现在你可以了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;WebDX 社区小组推出了一种新方式，让您可以直接表达哪些 Web 功能对您来说最重要。现在，您可以为希望在主流浏览器之间实现互操作的功能投票 。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;从今天起，您可以在 web.dev、 caniuse.com 和 webstatus.dev 上找到这些已集成的点赞功能。其他平台，例如 MDN，也在研究类似的集成方案。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;React 在研究 React2Shell 过程中披露了两个新的 RSC 漏洞&lt;a href=&quot;#react-在研究-react2shell-过程中披露了两个新的-rsc-漏洞&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://react.dev/blog/2025/12/11/denial-of-service-and-source-code-exposure-in-react-server-components&quot;&gt;React 在研究 React2Shell 过程中披露了两个新的 RSC 漏洞&lt;/a&gt;：CVE‑2025‑55184 与 CVE‑2025‑67779（高危 - 拒绝服务）以及 CVE‑2025‑55183（中危 - 源代码泄露）。这些缺陷可被恶意 HTTP 请求触发，导致服务器无限循环或泄露服务器函数内部代码。React 已在 19.0.3、19.1.4、19.2.3 版本中修复，受影响的框架与打包器（如 Next、React‑Router、Vite 等）亦需同步升级。&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://react.dev/blog/2025/12/11/denial-of-service-and-source-code-exposure-in-react-server-components&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://react.dev/apple-touch-icon.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;react.dev&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;Denial of Service and Source Code Exposure in React Server Components – React&lt;/h3&gt;
        &lt;p&gt;The library for web and native user interfaces&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://react.dev/blog/2025/12/11/denial-of-service-and-source-code-exposure-in-react-server-components&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://react.dev/images/og-blog.png&quot; alt=&quot;Denial of Service and Source Code Exposure in React Server Components – React&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;拒绝服务（高危）： CVE-2025-55184 与 CVE-2025-67779 (CVSS 7.5) 可被恶意 HTTP 请求触发，导致服务器无限循环&lt;/li&gt;
&lt;li&gt;源代码暴露（中等）： CVE-2025-55183 (CVSS 5.3) 可能会泄露服务器函数内部代码，源代码中硬编码的 secret 可能会被暴露，运行时 secret 如 process.env.SECRET 不受影响。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;官方解释称：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;重大漏洞披露后，往往会发现后续漏洞。当一个重大漏洞被披露后，研究人员会仔细检查相邻的代码路径，寻找变种的利用技术，以测试初始缓解措施是否可以被绕过。
这种模式在整个行业中普遍存在。例如，在 Log4Shell 之后，随着社区对原始修复程序的审查，又报告了其他 CVE。额外的披露可能会令人沮丧，但这通常是健康应对机制的标志。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;只有源代码中的 secret 才能被泄露。
源代码中硬编码的 secret 可能会被暴露，但运行时 secret 如 process.env.SECRET 不受影响。
暴露代码的范围仅限于服务器函数内部的代码，这可能包括其他功能，具体取决于捆绑器提供的内联支持程度。
一定要核对生产包。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;
  &lt;a href=&quot;https://nextjs.org/blog/security-update-2025-12-11&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://nextjs.org/favicon.ico?favicon.d29c4393.ico&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;nextjs.org&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;Next.js Security Update: December 11, 2025&lt;/h3&gt;
        &lt;p&gt;Two additional vulnerabilities have been identified in React Server Components. Users should upgrade to patched versions immediately.&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://nextjs.org/blog/security-update-2025-12-11&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://h8dxkfmaphn8o0p3.public.blob.vercel-storage.com/static/blog/security-update-2025-12-11/twitter-card.jpeg&quot; alt=&quot;Next.js Security Update: December 11, 2025&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;Next 用户可以使用 &lt;code&gt;npx fix-react2shell-next&lt;/code&gt; 进行升级。&lt;/p&gt;
&lt;p&gt;我看到这个消息的第一想法嘛，如下：&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;
&lt;div&gt;&lt;/div&gt;
&lt;h2&gt;Shopify Editions Winter 2026&lt;a href=&quot;#shopify-editions-winter-2026&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这是每半年固定发布，用于介绍 Shopify 又做了哪些功能更新的网站！&lt;/p&gt;
&lt;p&gt;性能和兼容性可能相对一般，但做了完整的移动端适配，纯炫技网页，浓缩了超多细节和彩蛋和 CSS 新特性。&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://www.shopify.com/editions/winter2026&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://cdn.shopify.com/s/files/1/0951/3130/4218/files/favicon_256.png?v=1760634627&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;shopify.com&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;Shopify Editions | Winter ’26&lt;/h3&gt;
        &lt;p&gt;The commerce renaissance is here. Explore 150+ product updates across AI, retail, and more.&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://www.shopify.com/editions/winter2026&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://cdn.shopify.com/s/files/1/0951/3130/4218/files/share.png?v=1765212809&quot; alt=&quot;Shopify Editions | Winter ’26&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://x.com/avstorm/status/1998853569936851292&quot;&gt;彩蛋 1&lt;/a&gt;：点击钥匙，会弹出一个新的无边框窗口来进行解锁。应该用到了&lt;a href=&quot;https://www.zhangxinxu.com/wordpress/2025/01/js-broadcast-channel-api/&quot;&gt;跨窗口通信&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;以前有一个类似的&lt;a href=&quot;https://juejin.cn/post/7306033185934622731&quot;&gt;尝试用 three.js 实现了这个跨窗口的粒子动画&lt;/a&gt;，我猜应该是和这个一个原理，&lt;a href=&quot;https://github.com/bgstaal/multipleWindow3dScene&quot;&gt;GitHub 仓库&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/01162d230f16b2910b7710d15155a67e.gif&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;彩蛋 2：解锁后这个窗口还能跟着原来的页面滚动。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/7546226e60f8ce11962da28800eadb8c.gif&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://x.com/wentingyong/status/1999230215097082200&quot;&gt;彩蛋 3&lt;/a&gt;：点击帽子&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/9c230863dbf24faf4135dc3df2744c67.gif&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;技术解析：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://x.com/jh3yy/status/1999131244345405664&quot;&gt;jhey ʕ•ᴥ•ʔ(@jh3yy)&lt;/a&gt;：今年的版本页面真是太棒了——有太多精彩的细节了 ‍
很荣幸能参与其中，团队表现出色 
除了令人惊叹的 WebGL 技术之外，还巧妙地运用了 CSS 容器查询、计数器、过渡/动画和 SVG！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;文章与视频&lt;a href=&quot;#文章与视频&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.pkh.me/p/48-a-series-of-tricks-and-techniques-i-learned-doing-tiny-glsl-demos.html&quot;&gt;我在制作微型 GLSL 演示时学到的一系列技巧和技巧&lt;/a&gt;：作者分享了自己在制作 512 字符限制的微型 GLSL（OpenGL Shading Language）图形演示过程中，积累的一系列图形渲染技巧与心得。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://devblogs.microsoft.com/typescript/progress-on-typescript-7-december-2025/&quot;&gt;TypeScript 7 的进展 - 2025 年 12 月&lt;/a&gt;：TypeScript 团队把编译器和语言服务迁移到原生代码（代号 Project Corsa），现在已提供可在 VS Code 中使用的预览版。编辑器功能完整、性能提升显著，编译器在类型检查和增量构建上几乎全兼容 5.9，并实现十倍左右的速度提升。文章还列出了与 5.9 的差异、即将废弃的选项以及后续路线图，强调 6.0 将是基于旧代码的最后一次发布，之后所有重点转向 7.0。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;今年早些时候， &lt;a href=&quot;https://devblogs.microsoft.com/typescript/typescript-native-port/&quot;&gt;TypeScript 团队宣布，我们一直在将编译器和语言服务移植到原生代码&lt;/a&gt;， 以充分利用原生代码的优势，从而获得更佳的原始性能、内存使用效率和并行处理能力。这项工作（代号“Project Corsa”，即将更名为“TypeScript 7.0”）是一项意义重大的工程，但我们在过去的几个月中取得了长足的进步。我们很高兴向大家介绍目前的进展，并展示全新的 TypeScript 工具集如今的“真实”面貌。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://slicker.me/react/useEffectEvent.html&quot;&gt;Do’s and Don’ts of useEffectEvent in React&lt;/a&gt;：了解 React 新增的 useEffectEvent hook，用来在 Effect 中获取最新的 props / state 而不触发 Effect 重新执行。本文列举了它的使用场景、何时可使用与何时不可使用与迁移建议。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.appsignal.com/2025/11/26/manage-a-nextjs-monorepo-with-prisma.html&quot;&gt;Manage a Next.js Monorepo with Prisma&lt;/a&gt;：本文展示了使用 Next.js 与 Prisma 在 Postgres 上实现完整的 CRUD Pizza 订餐系统一套流程。（是比较完整的一篇实践，小项目这么折腾全栈是可以的，但是大项目的话，写后端需要慎重～）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.debugbear.com/blog/2025-in-web-performance?utm_campaign=web-weekly&quot;&gt;2025 回顾：Web 性能有什么新变化？&lt;/a&gt;：轻松回顾 2025 年前端性能的变化与新工具，包括 Core Web Vitals 指标的跨浏览器推进、新的 Lighthouse 与 DevTools 检测能力、浏览器 API（如 Scheduler API、软导航观测）的演进、CrUX 和实时监测数据的细化、对图像格式（如 JPEG XL）的最新态势，结尾展望了 2026 年可能的方向（更多 AI 集成与更完整的软导航指标支持）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nerdy.dev/adjust-perceived-typepace-weight-for-dark-mode-without-layout-shift&quot;&gt;Using CSS to fix the irradiation illusion&lt;/a&gt;：在深色背景下，白色文本会显得比同等粗细的黑色文本更粗。这种现象叫做“辐照错觉”（irradiation illusion），本文介绍了如何利用可变字体（variable fonts）的 &lt;code&gt;GRAD&lt;/code&gt; 轴（grade axis）在不改变字形大小的情况下调整文本的可感知粗细。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://cekrem.github.io/posts/tailwind-targeting-child-elements/&quot;&gt;Tailwind CSS: Targeting Child Elements (when you have to)&lt;/a&gt;：本文详细介绍了如何利用 Tailwind 的任意变体 (Arbitrary Variants) 和 &lt;code&gt;[&amp;amp;_selector]&lt;/code&gt; 语法来实现子元素样式控制。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;CSS 新特性&lt;a href=&quot;#css-新特性&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://chrome.dev/css-wrapped-2025/&quot;&gt;CSS Wrapped 2025&lt;/a&gt;：一份来自 Chrome 团队的年度 CSS 功能大盘点，看看明年你的 CSS 能玩出什么新花样！&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
  &lt;a href=&quot;https://chrome.dev/css-wrapped-2025/&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://chrome.dev/css-wrapped-2025/favicon.svg&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;chrome.dev&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;CSS Wrapped 2025&lt;/h3&gt;
        &lt;p&gt;Sculpt dynamic interfaces, stretch your imagination, and play with these 22 powerful new CSS features that landed in Chrome this year.&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://chrome.dev/css-wrapped-2025/&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://chrome.dev/css-wrapped-2025/social.jpg&quot; alt=&quot;CSS Wrapped 2025&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/bbd61522ff87b2d9429540600e3fe669.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://css-tricks.com/fit-width-text-in-1-line-of-css/&quot;&gt;Fit width text in 1 line of CSS&lt;/a&gt;：CSS 新属性 &lt;code&gt;text-grow&lt;/code&gt; 用一行 CSS 就能让文本自动填满容器宽度，告别过去的复杂方案，不过它还在实验阶段。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://css-tricks.com/scrollytelling-on-steroids-with-scroll-state-queries/&quot;&gt;Scrollytelling on Steroids With Scroll-State Queries&lt;/a&gt;：使用 CSS 的新滚动状态查询，让网页故事像游戏一样交互。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.bram.us/2025/12/12/css-scroll-triggered-animations-are-coming-to-chrome/&quot;&gt;CSS Scroll-Triggered Animations are coming to Chrome!&lt;/a&gt;：Chrome 浏览器将在明年初推出纯 CSS 实现的滚动触发动画。这是一种基于时间，并在特定滚动偏移量（scroll offset）触发的动画，与现有的滚动驱动动画不同。这项功能有望在 Chrome 145 版本正式发布，它将替代部分 &lt;code&gt;IntersectionObserver&lt;/code&gt; 的用途。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.zhangxinxu.com/wordpress/2025/12/css-progress-function/&quot;&gt;CSS progress()函数简介&lt;/a&gt;：本文介绍了 CSS 新增的 &lt;code&gt;progress()&lt;/code&gt; 函数，它能将指定范围内的值映射为 0 到 1 之间的进度值，兼容性一般，还处于 Chrome 的实验阶段。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;小贴士&lt;a href=&quot;#小贴士&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;使用 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/scrollbar-gutter&quot;&gt;scrollbar-gutter&lt;/a&gt; CSS 属性可以为滚动条预留空间，防止滚动条出现或消失时布局发生偏移。&lt;/p&gt;
&lt;p&gt;可以将 body （或任何其他容器元素）上的 &lt;code&gt;scrollbar-gutter&lt;/code&gt; 属性设置为 &lt;code&gt;stable&lt;/code&gt;，以确保始终为滚动条预留空间，防止布局偏移。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;scrollbar-gutter&lt;/code&gt; 属性是 CSS 中一个相对较新的属性，自 2024 年 12 月起，主流现代浏览器都支持它。因此，可以放心地使用它来渐进式的提升网页的用户体验。&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://www.amitmerchant.com/one-css-trick-to-eliminate-scrollbar-layout-shifts/&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://www.amitmerchant.com/images/apple-touch-icon.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;amitmerchant.com&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;One CSS Trick to Eliminate Scrollbar Layout Shifts&lt;/h3&gt;
        &lt;p&gt;When a webpage’s content exceeds the viewport height, browsers typically introduce a vertical scrollbar. This can lead to layout shifts, especially when navigating between pages or toggling content visibility, as the presence or absence of the scrollbar alters the available width for content.&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://www.amitmerchant.com/one-css-trick-to-eliminate-scrollbar-layout-shifts/&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://www.amitmerchant.com/cdn/one-css-trick-to-eliminate-scrollbar-layout-shifts.png&quot; alt=&quot;One CSS Trick to Eliminate Scrollbar Layout Shifts&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;h2&gt;工具&lt;a href=&quot;#工具&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://vercel.com/changelog/new-npm-package-for-automatic-recovery-of-broken-streaming-markdown&quot;&gt;remend&lt;/a&gt;：Vercel 推出新 npm 包 remend，自动修复流式 Markdown 的破碎语法，尤其适用于处理 LLM 等应用。提取自 Vercel 的 Streamdown 库，该库是 react-markdown 的即插即用替代方案，专为 AI 驱动的流媒体应用而设计。（PS：与 Streamdown 同类型的 &lt;a href=&quot;https://x.com/simon_he1995&quot;&gt;Simon He&lt;/a&gt; 的 &lt;a href=&quot;https://github.com/Simon-He95/markstream-vue&quot;&gt;markstream-vue&lt;/a&gt; 也很好！）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;React Grab&lt;a href=&quot;#react-grab&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.react-grab.com/blog/agent&quot;&gt;React Grab&lt;/a&gt;：React Scan 的作者 Aiden 出的新工具，它允许你从应用组件中“抓取”上下文，并将其提供给你选择的智能体，以便进行详细的修改。&lt;/p&gt;
&lt;p&gt;CLI 设置只需要在你的项目根目录下运行这个命令，将检测您的框架并自动添加必要的脚本。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; grab@latest&lt;/span&gt;&lt;span&gt; init&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Claude Code 的设置的话，服务器通过 4567 端口运行，并与 Claude Agent SDK 接口连接。将以下脚本添加到 package.json 中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;scripts&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;dev&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;npx @react-grab/claude-code@latest &amp;amp;&amp;amp; next dev&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Codepen 精选&lt;a href=&quot;#codepen-精选&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;霓虹正弦波效果 GLSL&lt;a href=&quot;#霓虹正弦波效果-glsl&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Fabio E Zola 的这段 GLSL 动画中，闪烁的正弦波脉动着。您可以暂停动画、截屏，或从顶部的控制面板切换像素比例。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/2673b2f8e847cd11f37714e71c559315.gif&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;花瓣&lt;a href=&quot;#花瓣&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/rileyjshaw/pen/xbVQQGr&quot;&gt;xbVQQGr&lt;/a&gt; by rileyjshaw (&lt;a href=&quot;https://codepen.io/rileyjshaw&quot;&gt;@rileyjshaw&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Riley Shaw 分享了一朵梦幻般的 CSS 和 JS 花朵，花瓣如万花筒般旋转 &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/8a0df10caab02e9888e12a0a91dff989.gif&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;伪 3D 旋转球切换（仅 CSS）&lt;a href=&quot;#伪-3d-旋转球切换仅-css&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/alvaromontoro/pen/xbVydXq&quot;&gt;xbVydXq&lt;/a&gt; by alvaromontoro (&lt;a href=&quot;https://codepen.io/alvaromontoro&quot;&gt;@alvaromontoro&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Alvaro Montoro 巧妙地通过移动点阵图案来模拟旋转动画，这个“切换组件是用 HTML 和 CSS 创建的。没有图片，也没有 JS。”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/4b69790f736320068d65f4ac076b6c2a.gif&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Refs&lt;a href=&quot;#refs&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://codepen.io/spark/486&quot;&gt;Codepen Spark #486&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://react.statuscode.com/issues/454&quot;&gt;React Status Issue 454: December 3, 2025&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nodeweekly.com/issues/604&quot;&gt;Node Weekly Issue 604: December 9, 2025&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://react.statuscode.com/issues/455&quot;&gt;React Status Issue 455: December 10, 2025&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.stefanjudis.com/blog/web-weekly-178/&quot;&gt;Web Weekly #178&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>category:周刊</category></item><item><title>我的 Claude Code 使用小记 2</title><link>https://blog.cosine.ren/post/my-claude-code-record-2</link><guid isPermaLink="false">my-claude-code-record-2</guid><description>本文地址

  
    
      
        
          
          blog.cosine.ren
        
        我的 Claude Code 使用小记 2 | 笔记 / AI | 余弦の博客
        本文地址</description><pubDate>Sat, 06 Dec 2025 18:55:03 GMT</pubDate><content:encoded>&lt;p&gt;本文地址&lt;/p&gt;
&lt;div&gt;
  &lt;a href=&quot;https://blog.cosine.ren/post/my-claude-code-record-2&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://blog.cosine.ren/favicon.ico&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;blog.cosine.ren&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;我的 Claude Code 使用小记 2 | 笔记 / AI | 余弦の博客&lt;/h3&gt;
        &lt;p&gt;本文地址 https://blog.cosine.ren/post/my-claude-code-record-2 距离我 8 月份写下《我的 Claude Code 使用小记》已经过去了近 4 个月，在这段时间的高强度使用后，是时候分享一些新的体验了。…&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://blog.cosine.ren/post/my-claude-code-record-2&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://blog.cosine.ren/img/avatar.webp&quot; alt=&quot;我的 Claude Code 使用小记 2 | 笔记 / AI | 余弦の博客&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;距离我 8 月份写下《&lt;a href=&quot;https://blog.cosine.ren/post/my-claude-code-record&quot;&gt;我的 Claude Code 使用小记&lt;/a&gt;》已经过去了近 4 个月，在这段时间的高强度使用后，是时候分享一些新的体验了。&lt;/p&gt;
&lt;p&gt;很短，并且比较支离破碎流水账，主要是为了记录。&lt;/p&gt;
&lt;h2&gt;使用数据一览&lt;a href=&quot;#使用数据一览&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;统计工具使用 &lt;a href=&quot;https://github.com/winfunc/opcode&quot;&gt;opcode&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/57e168afdc5a8a2f589c12b5943d91f7.webp&quot; alt=&quot;20251205-近 30 天用量显示 01&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/606ae9b7d31727199a70d2ac2c45d9d5.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;数据可以看出，&lt;strong&gt;近 30 天我消耗了 743&lt;span&gt;&lt;span&gt;的Token∗∗，使用量集中在单个项目上。背后的原因是：公司报销了几个月的100 的 Token**，使用量集中在单个项目上。背后的原因是：公司报销了几个月的 100&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;的&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;∗&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;∗&lt;/span&gt;&lt;span&gt;，使用量集中在单个项目上。背后的原因是：公司报销了几个月的&lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 的 Claude Code Max，让我们几位前端和后端工程师一起尝试&lt;/strong&gt;从零到一构建一个”相对复杂”的 Swift 原生音视频 APP**。现在 APP 已经接近上架阶段。&lt;/p&gt;
&lt;h2&gt;使用体验&lt;a href=&quot;#使用体验&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;起初我非常担心账号会被封禁，因为社交媒体上各种封号消息层出不穷。但这 4 个月下来，我的账号一直安然无恙。退一步说，即使真的被封，Anthropic 也会退款，重新注册一个账号即可继续使用。&lt;/p&gt;
&lt;p&gt;而要我说我的使用体验的话，那就是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;每月 100 美元的 Claude Code Max 带来的辅助价值，远超这个价格本身。&lt;/strong&gt;
即使公司后续不再报销，我也会自费继续订阅。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;最近 30 天我烧掉的 743$ 的 Token，都是有计划、有目的的使用。给出的结果都很令人满意。&lt;/p&gt;
&lt;p&gt;在使用 Claude Code CLI 的同时，我也一直订阅着 Cursor（公司以前报销的年费）和订阅了 3 个月的 Codex。中间有一段时间，体感 Claude Code 确实经历了“降智期”，并且在他们修复了 bug 之后，体验就好起来了，但自从 &lt;strong&gt;Claude Opus 4.5&lt;/strong&gt; 发布后，它就很少让我失望了（当然，对于那些明知它做不来的任务，我也不会强求）。&lt;/p&gt;
&lt;p&gt;Codex 我体验的时间则不太多，体感上的话 Codex CLI 的体验还是挺好的，就是太慢了，并且 20$ 特别容易限额，虽说慢工出细活，可是大部分工作还是用 CC 做。&lt;/p&gt;
&lt;p&gt;Claude 以前也是，但出了 sonnet 和 opus 4.5 之后做到了有速度的同时，大部分情况不用改，小部分情况下，我只需要小改一下就可以用，那就更没有想用 codex 的理由了。&lt;/p&gt;
&lt;p&gt;相比之下，Cursor 最大的作用还是他的 Tab 补全，他的 Agent 我已经很少用了。&lt;/p&gt;
&lt;h2&gt;我的工作流&lt;a href=&quot;#我的工作流&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;Skills&lt;a href=&quot;#skills&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Skills 是 Claude 可动态加载的指令、脚本和资源集合，用于提升特定任务表现。它帮助 Claude 重复执行标准化任务，如文档制作、数据分析或任务自动化。&lt;/p&gt;
&lt;p&gt;官方文章：「&lt;a href=&quot;https://support.claude.com/en/articles/12512176-what-are-skills&quot;&gt;什么是技能&lt;/a&gt;」、「&lt;a href=&quot;https://support.claude.com/en/articles/12512180-using-skills-in-claude&quot;&gt;如何在 Claude 中使用&lt;/a&gt;」及「&lt;a href=&quot;https://support.claude.com/en/articles/12512198-how-to-create-custom-skills&quot;&gt;如何创建自定义技能&lt;/a&gt;」
更全面的一篇：&lt;a href=&quot;https://code.claude.com/docs/en/skills#create-a-skill&quot;&gt;Agent Skills - Claude Code Docs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;可以通过在 Claude Code 中运行以下命令，将 GitHub 存储库注册为 Claude Code 插件市场：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 把 https://github.com/anthropics/skills 注册为插件市场&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/plugin marketplace add anthropics/skills&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 安装 document-skills&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/plugin install document-skills@anthropic-agent-skills&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装插件后，您只需提及该技能即可使用。例如，如果安装了 &lt;code&gt;document-skills&lt;/code&gt; 插件，可以让 Claude Code 执行类似这样的操作：“使用 PDF 技能从 &lt;code&gt;path/to/some-file.pdf&lt;/code&gt; 中提取表单字段”。&lt;/p&gt;
&lt;h4&gt;创建自定义技能&lt;a href=&quot;#创建自定义技能&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;只需包含一个 &lt;code&gt;SKILL.md&lt;/code&gt; 文件，含 YAML 格式的前置信息与任务指令。前置信息需包含两个字段：&lt;code&gt;name&lt;/code&gt;（唯一标识符）和 &lt;code&gt;description&lt;/code&gt;（功能与使用说明）。官方有模板示例 &lt;code&gt;template-skill&lt;/code&gt;，帮助快速生成自定义技能结构。&lt;/p&gt;
&lt;p&gt;也可以使用这个进行新 skill 的创建：&lt;a href=&quot;https://github.com/anthropics/claude-code/tree/main/plugins/plugin-dev&quot;&gt;claude-code/plugins/plugin-dev&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/plugin marketplace add anthropics/claude-code&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/plugin install plugin-dev@claude-code-plugins&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;触发词 “create a skill”, “add a skill to plugin”, “write a new skill”, “improve skill description”, “organize skill content”&lt;/p&gt;
&lt;p&gt;这个 plugin-dev 技能可以用来创建 ClaudeCode 的各种东西，不限于配置 Hook、MCP、插件结构、配置、命令、智能体 (Agent) 到技能 (Skill)&lt;/p&gt;
&lt;h4&gt;用来做什么？&lt;a href=&quot;#用来做什么&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;官方提供的几个 Skills 示例在这里，可以按需选择。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/anthropics/skills/blob/main/skills/algorithmic-art/SKILL.md&quot;&gt;algorithmic-art&lt;/a&gt;：利用 p5.js 制作算法艺术 (generative art)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/anthropics/skills/blob/main/skills/brand-guidelines/SKILL.md&quot;&gt;brand-guidelines&lt;/a&gt;：将 Anthropic 的官方品牌颜色和字体应用于任何需要 Anthropic 风格和感觉的物品。当需要使用品牌颜色或风格指南、视觉格式或公司设计标准时，请使用此功能。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/anthropics/skills/blob/main/skills/canvas-design/SKILL.md&quot;&gt;canvas-design&lt;/a&gt;：运用设计理念，创作精美的 .png 和 .pdf 格式视觉作品。当用户要求创作海报、艺术作品、设计或其他静态作品时，应该运用这项技能。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/anthropics/skills/blob/main/skills/doc-coauthoring/SKILL.md&quot;&gt;doc-coauthoring&lt;/a&gt;：引导用户完成结构化的文档协作工作流程。适用于用户需要编写文档、提案、技术规范、决策文档或类似结构化内容的情况。目标是编写一份真正对读者有用的文档。&lt;/li&gt;
&lt;li&gt;……&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我用到的 Skill 主要是他的&lt;a href=&quot;https://github.com/anthropics/skills/blob/main/skills/frontend-design/SKILL.md&quot;&gt;前端设计技能&lt;/a&gt;，现在可以用 &lt;a href=&quot;https://www.claude.com/blog/improving-frontend-design-through-skills&quot;&gt;Claude Code 的 Skill 弥补他的前端设计技能&lt;/a&gt;，在 Claude Code 里用这两条命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/plugin&lt;/span&gt;&lt;span&gt; marketplace&lt;/span&gt;&lt;span&gt; add&lt;/span&gt;&lt;span&gt; anthropics/claude-code&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# 安装 frontend-design 技能&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/plugin&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; frontend-design@claude-code-plugins&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&lt;/div&gt;
&lt;p&gt;其他 Claude Code 官方使用的 Skills 可以在 &lt;a href=&quot;https://github.com/anthropics/claude-code/tree/main/plugins&quot;&gt;claude-code/plugins&lt;/a&gt; 中找到。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/e380c6a9748d6419c9120de67caefbed.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;更细致的，就先不深入讲了。&lt;/p&gt;
&lt;h3&gt;学习项目&lt;a href=&quot;#学习项目&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;使用 Claude Code 的 Plan Mode，以博客项目为例子，看一下 Plan Mode 的流程&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; 我需要对这个项目进行学习，学习其整体结构与核心实现，输出一系列 md 文档在 docs 文件夹下供我参考。&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Plan Mode 会询问你一些问题，提交后再继续进行规划：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/11/96308fe8e286ce8d3e94aa70c54d4ab5.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;确认后，他会接着进行追问补充：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/11/8f5059d926bb26fc61a64c3eb4c57f83.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;最后会问你是自动接受更改还是手动允许 edit，有 git 进行管理那当然是自动接受。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/11/909bc9169f74627bc07720c0bcfb12b4.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Claude Code 特别喜欢画这种 ASCII 图，你也可以在 &lt;code&gt;CLAUDE.md&lt;/code&gt; 中特别要求他不画这些。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/11/f58eda5e26c2a82a2a3bcedd37d75bd8.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;最终生成的示例文档，可以在这里进行查看。注意只是用来做个示例，项目本身是不断变化的，这个文档只是适合初上手来进行学习。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/11/7308bffabf29bd7c50d32eb2e885b8fa.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;生成的文档质量嘛，虽然有些地方还是会有错漏，并且经常使用各种 ASCII 图，作为新项目了解学习来说我觉得是够的。（当然，不要傻傻呼呼的认为全都是对的，实际看到哪里还需要以代码为准）&lt;/p&gt;
&lt;h3&gt;新功能从 0 到 1&lt;a href=&quot;#新功能从-0-到-1&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;使用 Claude Code 的 Plan Mode，让模型只输出“变更计划（哪些文件、改动点、预期 diff）”，先不写代码。&lt;/li&gt;
&lt;li&gt;ClaudeCode 的 Plan Mode 会生成计划，并询问一些你可能没讲清楚的地方&lt;/li&gt;
&lt;li&gt;补充并 Review 计划完毕后让他按计划生成代码，落到本地跑编译与最小样例。&lt;/li&gt;
&lt;li&gt;再次要求模型自检：列出潜在失败场景、边界条件和建议测试用例。&lt;/li&gt;
&lt;li&gt;做小改动和对接打磨，然后 Commit&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# 使用 Claude Sonnet 4.5&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; @docs/overview/10-markdown-system.md 开始进行优化，我有以下需求：[Image &lt;/span&gt;&lt;span&gt;#1]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  1. 行内代码 &lt;/span&gt;&lt;span&gt;``&lt;/span&gt;&lt;span&gt; 语法 的优化&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  2. 代码块 &lt;/span&gt;&lt;span&gt;``` &lt;/span&gt;&lt;span&gt;的优化，希望加上复制按钮，模仿&lt;/span&gt;&lt;span&gt; mac 窗口样式，左侧三个圆点+代码语言显示，右侧是全屏预览、复制按钮&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  3.&lt;/span&gt;&lt;span&gt; 标题的样式优化，标题后的 &lt;/span&gt;&lt;span&gt;# 与标题居中对齐，标题前加一个弱化的小 H3 H4 标签标明是几级标题&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  4.&lt;/span&gt;&lt;span&gt; 列表样式优化（无序和有序）&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/11/a2e70368414daf8731b2255dfbe9d9dc.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/11/1e11583842e24dad27f3c02480757989.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;Bugfix&lt;a href=&quot;#bugfix&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这些我就懒得放示例了，也是基本上一句话就可以，有的需要更多的人力介入。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;喂给他报错日志/最小复现工程。&lt;/li&gt;
&lt;li&gt;让模型列出“定位假说清单、验证步骤、最小改动方案”。&lt;/li&gt;
&lt;li&gt;实现、自检&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;重构/迁移&lt;a href=&quot;#重构迁移&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;同样 Plan Mode 描述重构需求，让其生成文档计划等&lt;/li&gt;
&lt;li&gt;让模型先写 codemod，只在小部分上试跑。&lt;/li&gt;
&lt;li&gt;观察 diff，定义切分点和随时可回滚的边界。&lt;/li&gt;
&lt;li&gt;分批推进，并进行回归测试。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;模型与工具的选型与切换&lt;a href=&quot;#模型与工具的选型与切换&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;好用的模型有很多，成本和使用场景也需要考虑，以下是我选模型的基准：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;重要的架构设计/大重构：用强模型（质量优先）。&lt;/li&gt;
&lt;li&gt;批量生成测试/样例：用便宜模型（成本优先）。&lt;/li&gt;
&lt;li&gt;读 log / 写小脚本/摘要：用更快模型（速度优先）。&lt;/li&gt;
&lt;li&gt;小模型可以用 Plan Mode 先生产 “变更计划 + 验收用例” 的 PRD，spec 驱动开发，而大模型负责实现。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Happy&lt;a href=&quot;#happy&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;最近在尝试 &lt;a href=&quot;https://github.com/slopus/happy&quot;&gt;happy&lt;/a&gt;，相当好用，可以手机或平板随时随地访问家里或者服务器上的 Claude Code 写代码。&lt;/p&gt;
&lt;p&gt;是因为看到了这条 Tweet 才决定使用一下体验的：&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;
&lt;div&gt;
  &lt;a href=&quot;https://happy.engineering/docs/use-cases/hemingway-technique/&quot; target=&quot;_blank&quot;&gt;
    &lt;div&gt;
      &lt;div&gt;
        &lt;div&gt;
          &lt;img src=&quot;https://happy.engineering/apple-touch-icon.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
          &lt;span&gt;slopus.github.io&lt;/span&gt;
        &lt;/div&gt;
        &lt;h3&gt;Happy - Claude Code Mobile Client&lt;/h3&gt;
        &lt;p&gt;Free, open-source mobile app for Claude Code. Control Claude AI from your phone with end-to-end encryption and seamless workflow.&lt;/p&gt;
        &lt;div&gt;
          &lt;span&gt;https://happy.engineering/docs/use-cases/hemingway-technique/&lt;/span&gt;
           
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;&lt;img src=&quot;https://slopus.github.io/og-image.png&quot; alt=&quot;Happy - Claude Code Mobile Client&quot; loading=&quot;lazy&quot; /&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;欧内斯特·海明威在写作时使用了一个聪明的技巧。他会说到一半就停下来，或者在他知道接下来要说什么的时候停下来。这样第二天再开始写就容易多了。他不需要考虑从哪里开始。这个想法很简单：通过准备好清晰的下一步来保持你的动力。&lt;/p&gt;
&lt;p&gt;作为一名开发者，我听说过把未完成的工作留到第二天早上再继续。但这种提高效率的技巧对我来说从来没用。安排一项有意义的任务需要 10 到 20 分钟，这对于即兴发挥来说太长了。尤其是在我已经加班到很晚的时候，我最不想做的就是再花 20 分钟为明天的工作做准备。&lt;/p&gt;
&lt;p&gt;现在有了 Claude Code 和 MCP 工具（适用于 JIRA 或 Linear），一切都改变了。我可以躺在床上运行自定义的睡前任务，而不是刷 Reddit 或 Instagram。我已经设置了 &lt;code&gt;~/.claude/agents/bedtime.md&lt;/code&gt; 文件，用来查找简单的任务。这些任务我今晚可以开始，明天就能完成。&lt;/p&gt;
&lt;p&gt;我描述一下我想要的功能或者我正在思考的问题。然后我和克劳德一起花大约 5 分钟时间进行规划。我们制定了一个我满意的实施方案。方案经我批准后，克劳德就开始着手实施。我把手机插上充电器，然后睡觉。&lt;/p&gt;
&lt;p&gt;我醒来时，Happy 发来一条通知：“4 个文件待审核，新增 237 行代码”。这件小事让我一天有个美好的开始。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/430d35d96d4b49061d0ffbbf119a96b3.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;注重隐私的话，可以 &lt;a href=&quot;https://zhuanlan.zhihu.com/p/1951677637014561414&quot;&gt;自部署 happy server 远程操控 claude code 以及 codex&lt;/a&gt;，官方也都提供&lt;a href=&quot;https://happy.engineering/docs/guides/self-hosting/&quot;&gt;教程&lt;/a&gt;，可以说是很贴心了。&lt;/p&gt;
&lt;p&gt;2025.12.11 补充更新一下，Happy CLI 0.11.2 自带的版本是没有 Opus 4.5 只有 Opus 4.1 是因为版本太旧，可以参考这个 issue
&lt;a href=&quot;https://github.com/slopus/happy-cli/issues/84&quot;&gt;https://github.com/slopus/happy-cli/issues/84&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; $(&lt;/span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; root&lt;/span&gt;&lt;span&gt; -g&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;/happy-coder&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; @anthropic-ai/claude-code@latest&lt;/span&gt;&lt;span&gt; --save&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;更新就可以用上 Opus 4.5 了！后续的版本更新也一样。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/12/c1839549ca20a38b3bdc417995f39e4c.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;FAQ&lt;a href=&quot;#faq&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Q：我该如何知道哪个模型编程性能最好？
A：&lt;a href=&quot;https://lmarena.ai/leaderboard/webdev&quot;&gt;WebDev Leaderboard | LMArena&lt;/a&gt; 可以作为一部分参考，但是更多的还是要靠自己对性能、成本的考虑。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/11/0b9a3167e74e1426e37b6deffb430f14.webp&quot; alt=&quot;2025-11-29 数据&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Q：什么时候不要用 AI？&lt;br /&gt;
A：技术债务极重、逻辑到处乱飞的大型老项目，想要用 AI 一定不能直接“给我实现 XXX”，可以用它先梳理一遍老项目的坑，并自己加以补充，再进行实现。强烈推荐看下面的文章。&lt;/p&gt;
&lt;h2&gt;文章推荐&lt;a href=&quot;#文章推荐&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;关于 Claude Code 以及 AI 辅助编程，以下这些文章推荐阅读。&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://yousali.com/posts/20251124-how-to-coding-with-ai/&quot;&gt;从「写代码」到「验代码」：AI 搭档写走 3 年，我踩出来的协作路线图&lt;/a&gt;：在推上看到一篇好文章，写给已经在或准备在真实生产项目里用 AI Coding 的后端 / 全栈工程师和技术管理者。它不会教你「按钮在哪里」「哪个 prompt 最神」，而是想在大约 15 分钟里，帮你搞清楚三件事：哪些任务交给 AI 最「划算」；怎么让项目本身变得更「AI 友好」，提高一次命中率；当生成不再是瓶颈时，工程师应该如何设计验证流程，把时间花在真正值钱的地方。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://eliocapella.com/blog/ai-library-migration-guide/&quot;&gt;Migrating 6000 React tests using AI Agents and ASTs&lt;/a&gt;：好文章，关于使用 AI 进行重构迁移的教科书式文章。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://hyf.me/blog/claude-code-in-rolldown&quot;&gt;我使用 Claude Code 开发 Rolldown 的体验&lt;/a&gt;：在 Rolldown 开发中高强度使用 Claude Code 进行真实开发的思考与经验分享。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;几个月前，我自认为对 AI 认知是比较贴切，能写点脚本、做下网页开发，但处理不了 Rolldown 这种复杂度的项目。
而现在，过去两周里，它几乎替我写了所有的代码。整个流程没有魔法，只有跟着官方文档《&lt;a href=&quot;https://www.anthropic.com/engineering/claude-code-best-practices&quot;&gt;Claude Code: Best practices for agentic coding&lt;/a&gt;》的笨拙使用，仅仅是这样，就已经颠覆了我的认知。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.pseudoyu.com/posts/weekly_review_102&quot;&gt;周报 #102 - 我是如何使用 AI 的&lt;/a&gt;：作者分享了自己在开发、文档和日程管理中如何高频使用 AI 工具的实战经验，&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://guangzhengli.com/blog/zh/vibe-coding-and-context-coding&quot;&gt;谈谈 AI 编程工具的进化与 Vibe Coding&lt;/a&gt;：“氛围编程（Vibe Coding）” 的实际含义是让 AI 全流程写代码，开发者几乎不干预。当前被泛化为所有 AI 辅助编程形式，但作者主张应将它与与 Context Coding 区分&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://overreacted.io/how-to-fix-any-bug/&quot;&gt;How to Fix Any Bug&lt;/a&gt;：少有的讲述「在 vibe coding 中如何调试问题」的文章～当然也适合正常的 bugfix。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.blog/ai-and-ml/github-copilot/how-to-write-a-great-agents-md-lessons-from-over-2500-repositories/&quot;&gt;How to write a great agents.md: Lessons from over 2,500 repositories&lt;/a&gt;：GitHub 分析了超过 2500 个公开仓库中的 agents.md 文件，归纳出优秀 &lt;code&gt;agents.md&lt;/code&gt; 的写法（这个很有用）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://4ark.me/posts/2025-11-04-openspec/#3-%E5%88%9B%E5%BB%BA%E6%8F%90%E6%A1%88&quot;&gt;OpenSpec 使用心得&lt;/a&gt;：安利了 &lt;a href=&quot;https://github.com/Fission-AI/OpenSpec&quot;&gt;OpenSpec&lt;/a&gt; 驱动开发&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.humanlayer.dev/blog/writing-a-good-claude-md&quot;&gt;Writing a good CLAUDE.md&lt;/a&gt;：一篇教你如何写出既清晰又高效的 CLAUDE.md 的文章。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我觉得这篇&lt;a href=&quot;https://guangzhengli.com/blog/zh/vibe-coding-and-context-coding&quot;&gt;谈谈 AI 编程工具的进化与 Vibe Coding&lt;/a&gt;里的一段话说得很好：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;从这个阶段开始，水平一般的程序员的数量会开始减少直到消亡，这个过程与其说是 AI 抢走了工作饭碗，不如说是被优秀的程序员抢走了工作饭碗，并且这两者的收入在这个阶段的差距也会不断加大。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;时代的大潮浩浩荡荡，我并不想把未来想的过于悲观，也不想把事情讲的过于残酷，但在工业机器的轰鸣下，没有人会真的在意那些古法手工制作者的声音。在计算机出现之前，你也难以想象售票员、电话接线员是多么庞大的一个群体。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;当然，我并不是说编程水平一般的程序员就找不到出路了，实际上在 AI 的加持下，一个编程水平一般的程序员，如果有不错的商业嗅觉，加上一定的营销能力，创造的商业价值远比在社会分工中当个螺丝钉要大的多。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;过去可能需要很多人相互协作才能完成的工作，利用 AI 的杠杆可以大大的缩减工作时间和人员规模，未来的独立开发和小规模的团队协作一定会变得更加主流。&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><category>category:笔记</category><category>category:AI</category><category>tag:AI</category><category>tag:ClaudeCode</category></item><item><title>FE Bits Vol.17｜WebGPU 主流浏览器全支持，AntD 6 正式发布</title><link>https://blog.cosine.ren/post/weekly-17</link><guid isPermaLink="false">weekly-17</guid><description>本期周刊：博客 UI 更新并更名为 astro-koharu，域名迁至 blog.cosine.ren；计划自研评论与迁移旧评。社区要闻：WebGPU 主流浏览器全面支持、Ant Design 6 发布、Edge 上线站点级扩展开关、npm 再现供应链攻击、TanStack Pacer 发布、Better Auth 1.4 更新；精选文章与 CSS 新特性、工具与代码示例一览。</description><pubDate>Sun, 30 Nov 2025 12:35:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本期网址 &lt;a href=&quot;https://blog.cosine.ren/post/weekly-17&quot;&gt;https://blog.cosine.ren/post/weekly-17&lt;/a&gt;
本周刊更新时间期望是在每周天
目前推荐使用 &lt;a href=&quot;https://folo.is/&quot;&gt;Folo&lt;/a&gt; 订阅本周刊的 &lt;a href=&quot;https://quaily.com/cosine/feed/atom&quot;&gt;Quaily RSS&lt;/a&gt;。
公众号 前端周周谈 FE Bits，点击阅读原文链接可查看原文。
QQ 讨论小群 598022684，日常讨论前端技术 &amp;amp; 生活，也可在群里投稿自己的文章，随意加入，比较偏向粉丝群的性质～
本周刊文章内容同时也开源在 &lt;a href=&quot;https://github.com/yusixian/fe-bits-weekly&quot;&gt;fe-bits-weekly&lt;/a&gt;，欢迎关注。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;今天是 2025 年 11 月 30 日，星期天。&lt;/p&gt;
&lt;p&gt;给博客优化了一下 UI，代码块和列表、标题的样式都加上了，然后给博客主题换了个名字，正式一些。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/11/eeae70b254e02df105e0119f2be173d6.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/11/bdaf68d2324e4b4f1dcd758c5bd25276.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;现在的主题叫 &lt;a href=&quot;https://github.com/cosZone/astro-koharu&quot;&gt;astro-koharu&lt;/a&gt;，博客的正式地址也挪到了 &lt;a href=&quot;http://blog.cosine.ren&quot;&gt;blog.cosine.ren&lt;/a&gt;，很开心。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“小春日和” （こはるびより）指的是晚秋到初冬这段时期，持续的一段似春天般温暖的晴天。也就是中文中的“小阳春”。
灵感来自 Hexo 的 Shoka 主题，但不再追求一比一复刻，而是保留它的优点，用更轻量的技术栈，做一款属于自己的个人博客主题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;下一步打算自己弄个评论系统，Remark42 感觉没动力用，想参考 &lt;a href=&quot;https://valine.js.org/&quot;&gt;Valine&lt;/a&gt; 自己弄一个，然后把旧的评论迁移过去。&lt;/p&gt;
&lt;h2&gt;生态与社区动态&lt;a href=&quot;#生态与社区动态&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://web.dev/blog/webgpu-supported-major-browsers?hl=zh-cn&quot;&gt;主流浏览器现已支持 WebGPU&lt;/a&gt;：WebGPU 现已在主流浏览器全面支持了，但是 Safari 是 iOS 26 才支持，而且 Safari 移动端给的显存嘛……少少的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.appinn.com/edge-site-extension-toggle/&quot;&gt;Edge 终于支持网站级扩展开关：新增「允许在此网站使用扩展」功能｜可针对任意网站，一键关闭所有扩展&lt;/a&gt;：Edge 出了个实用小功能，可以对单个网站一键关掉所有扩展（这下真的是只要你不做就会有浏览器帮你做这个功能了，想给我那个破插件加一直懒得加来着，Chrome 能不能跟进啊）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://socket.dev/blog/shai-hulud-strikes-again-v2&quot;&gt;npm 再次爆发 “Shai Hulud” 供应链攻击&lt;/a&gt;，利用 post-install 脚本窃取 token。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;TanStack 最近发布了 &lt;a href=&quot;https://tanstack.com/pacer/latest&quot;&gt;TanStack Pacer&lt;/a&gt;，这是一个框架无关的性能优化工具集，提供防抖、节流、限流、队列管理与批处理等核心原语，适用于任何 JavaScript 框架。
它提供了一个专用的 React 适配器（&lt;code&gt;@tanstack/react-pacer&lt;/code&gt;），在核心 Pacer 工具之上提供了一组易于使用的 hook，例如 &lt;code&gt;useDebouncedValue&lt;/code&gt;、 &lt;code&gt;useThrottledCallback&lt;/code&gt; 、 &lt;code&gt;useQueuedState&lt;/code&gt; 和 &lt;code&gt;useBatcher&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Ant Design 组件库发布 v6&lt;a href=&quot;#ant-design-组件库发布-v6&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/ant-design/ant-design/issues/55805&quot;&gt; Ant Design 6.0 来了！ &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;单开一个标题以表尊重～&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;React 版本提升&lt;/strong&gt;：最低要求 React 18，推荐使用 React 19。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;启用 React Compiler&lt;/strong&gt;：在构建产物中优化性能，开发者可自行选择是否开启。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;纯 CSS Variables 样式架构&lt;/strong&gt;：不再兼容 IE，样式实现零运行时（zeroRuntime）模式，支持实时多主题切换。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;组件语义化结构&lt;/strong&gt;：所有组件 DOM 结构优化，支持函数式类名配置 (&lt;code&gt;classNames&lt;/code&gt;) 与内联样式 (&lt;code&gt;styles&lt;/code&gt;)，提升定制能力与可维护性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;移除废弃 API&lt;/strong&gt;：彻底移除 v4 遗留逻辑，统一 API 命名，同时兼容 v5 的使用方式。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;新组件与功能增强&lt;a href=&quot;#新组件与功能增强&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Masonry 瀑布流组件&lt;/strong&gt;：优化图片展示与卡片排布体验。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tooltip 支持平移切换&lt;/strong&gt;：多内容提示实现滑动过渡。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;InputNumber spinner 模式&lt;/strong&gt;：交互式加减按钮布局更直观。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Drawer 支持拖拽&lt;/strong&gt;：用户可调整抽屉大小。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模糊蒙版背景&lt;/strong&gt;：所有弹层默认使用模糊效果，可按需关闭。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不再支持 IE&lt;/strong&gt;，建议替换废弃 API。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;One More Thing —— Ant Design X 2.0&lt;a href=&quot;#one-more-thing--ant-design-x-20&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;面向 AI 场景的组件库同步升级，提供更智能的交互能力。&lt;/li&gt;
&lt;li&gt;新版本强化渲染性能与灵活性，是探索 AI 驱动界面的关键工具。&lt;/li&gt;
&lt;li&gt;更多详情可参考 &lt;a href=&quot;https://github.com/ant-design/x/issues/1358&quot;&gt; Ant Design X 2.0 正式发布了 &lt;/a&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Better Auth v1.4&lt;a href=&quot;#better-auth-v14&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.better-auth.com/blog/1-4&quot;&gt;Better Auth 1.4&lt;/a&gt; 发布，引入了无数据库的 Stateless Auth、SCIM Provisioning、OAuth 自定义状态、数据库 joins 优化、JWT 密钥轮换、API Key 二级存储、CLI 索引支持、SSO 域名验证等众多特性。此外，还推出了支持 UUID 主键、强化的邮箱变更流程、新的错误页、更好的插件结构和多项安全改善。&lt;/p&gt;
&lt;h2&gt;文章与视频&lt;a href=&quot;#文章与视频&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://yousali.com/posts/20251124-how-to-coding-with-ai/&quot;&gt;从「写代码」到「验代码」：AI 搭档写走 3 年，我踩出来的协作路线图&lt;/a&gt;：在推上看到一篇&lt;a href=&quot;https://x.com/y0usali/status/1993276386963079478&quot;&gt;好文章&lt;/a&gt;，写给已经在或准备在真实生产项目里用 AI Coding 的后端 / 全栈工程师和技术管理者。它不会教你「按钮在哪里」「哪个 prompt 最神」，而是想在大约 15 分钟里，帮你搞清楚三件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;哪些任务交给 AI 最「划算」；&lt;/li&gt;
&lt;li&gt;怎么让项目本身变得更「AI 友好」，提高一次命中率；&lt;/li&gt;
&lt;li&gt;当生成不再是瓶颈时，工程师应该如何设计验证流程，把时间花在真正值钱的地方。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://sspai.com/post/103819&quot;&gt;谈谈不自律的良好生活 - 少数派&lt;/a&gt;：很温柔的文章，提出「自洽而非自律」，「好好生活」不靠逼迫，而靠理解与自洽。
入选年度我最喜欢的文章，值得停下来，慢慢看。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://infrequently.org/2025/11/performance-inequality-gap-2026/&quot;&gt;The Performance Inequality Gap, 2026&lt;/a&gt;：作者继续延续多年系列，对 2026 年移动与桌面设备的性能、网络条件及网页负载趋势进行分析。
核心观点是：移动端尤其低端 Android 设备的硬件与网络改善速度，赶不上网页体积膨胀的速度。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://adactio.com/journal/22265&quot;&gt;Why use React?&lt;/a&gt;：一篇发问式思考文章，探讨为什么开发者选择 React，以及这项选择对前端用户体验意味着什么。作者倡导让框架留在服务器，充分利用浏览器本身的强大功能。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://eliocapella.com/blog/ai-library-migration-guide/&quot;&gt;Migrating 6000 React tests using AI Agents and ASTs&lt;/a&gt;：一次关于用 AI Agents 和 AST 自动迁移 6000 个 React 测试的实战记录，是一篇好文章。
作者公司使用旧版 React Testing Library 编写了 970 个测试文件，总计 6000 个多测试用例。而升级至 v14 后 API 完全异步化，行为变化大，手动迁移代价极高，于是作者决定尝试用 AI 辅助完成大规模迁移。
比较感慨的是其中的迁移过程，最后一周时间共执行 50 次迁移，形成 50 个独立 PR，每个 PR 半小时。作者展望未来 AI 将进一步解放开发者，从“重复劳动”转向更有创造力的工作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.harmonyintelligence.com/taking-down-next-js-servers&quot;&gt;以 0.0001 美分的价格干掉 Next.js 服务器&lt;/a&gt;：Harmony Intelligence 团队发现了一个未经身份验证的拒绝服务 (DoS) 漏洞，该漏洞仅需一个 HTTP 请求和极少的资源即可导致自托管的 Next.js 服务器崩溃。通过限制请求大小的反向代理可以防止此攻击；仅靠速率限制不足以提供充分的保护。
受影响范围：所有带有中间件的自托管 Next.js 服务器（Vercel 托管的不受影响）。
受影响版本：≤15.5.4 的旧版本。
缓解措施：升级/ 配置诸如 nginx 反向代理限制请求体大小&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.skk.moe/post/dns-as-code-via-dnscontrol/&quot;&gt;用代码和 Git 管理 DNS 记录 —— DNSControl 和 GitHub Actions CI/CD 实践&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://sspai.com/post/104040&quot;&gt;写在 PicGo 即将 8 周年之际&lt;/a&gt;：也是一篇很有「情感」的文章啊。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;CSS 新特性&lt;a href=&quot;#css-新特性&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.zhangxinxu.com/wordpress/2025/11/css-details-target-content-open/&quot;&gt;巧用 CSS ::details-content 伪元素实现任意展开动画&lt;/a&gt;：聊聊 &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; 元素这几年新增加的 &lt;code&gt;::details-content&lt;/code&gt; 伪元素，使得内容展开收起可实现平滑动画。同时讲解了内容锚点（hash）自动展开机制，以及 &lt;code&gt;scroll-margin-block-start&lt;/code&gt; 的小技巧。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.stefanjudis.com/today-i-learned/light-dark-isnt-the-same-as-prefers-color-scheme/&quot;&gt;light-dark() isn’t always the same as prefers-color-scheme&lt;/a&gt;：作者发现新的 CSS 函数 light-dark() 并非 prefers-color-scheme 的替代品，它们在实现逻辑上有细微却重要的差异。&lt;code&gt;prefers-color-scheme&lt;/code&gt; 更偏向全局系统偏好，而 &lt;code&gt;light-dark()&lt;/code&gt; 更适合组件级主题控制。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://frontendmasters.com/blog/how-to-add-and-remove-items-from-a-native-css-carousel-with-css/&quot;&gt;How to Add and Remove Items From a Native CSS Carousel (……with CSS)&lt;/a&gt;：本文介绍如何利用 CSS Overflow Module Level 5 的新特性（如 &lt;code&gt;::scroll-button()&lt;/code&gt; 与 &lt;code&gt;::scroll-marker&lt;/code&gt;）创建一个完全不用 JavaScript 的原生 CSS 轮播组件。通过结合 HTML 的复选框控制展示项、CSS 伪元素控制滚动行为与可视化反馈，作者实现了可动态增删轮播项、支持自动滚动、滚动锚点定位的完整组件，展示了未来 CSS 在交互与状态管理上的潜力。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;工具&lt;a href=&quot;#工具&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://colorpalette.pro/&quot;&gt;Color Palette Pro&lt;/a&gt;：一款能像音乐合成器一样自由调制色彩的在线调色工具，支持多种色彩空间与样式切换，实时生成色阶、色环、阴影与高光调和效果，非常适合设计师进行系统化配色或视觉一致性探索。
&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/11/0b4ecbdb5000d0cb584b73af3c4fb391.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/trekhleb/javascript-algorithms&quot;&gt;JavaScript Algorithms and Data Structures&lt;/a&gt;
一个涵盖经典算法与数据结构的 JavaScript 示例仓库，其中每个算法和数据结构都有其独立的 README 文件，包含相关的解释和进一步阅读的链接。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/williamtroup/Heat.js&quot;&gt;williamtroup/Heat.js&lt;/a&gt;：Heat.js 是一个零依赖、基于 TypeScript 开发的轻量前端可视化库，可生成可定制的热力图、图表和统计数据。开发者可通过配置 DOM 属性（data-heat-js）或使用 API 快速实现自定义热力图等。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Codepen 精选&lt;a href=&quot;#codepen-精选&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;Concentric Border Radii&lt;a href=&quot;#concentric-border-radii&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/botteu/pen/YPKBrJX&quot;&gt;YPKBrJX&lt;/a&gt; by botteu (&lt;a href=&quot;https://codepen.io/botteu&quot;&gt;@botteu&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“快来看看！这个内容器的边框半径取决于外容器的边框半径以及内外容器之间的内边距。” 在这个来自 botteu 的交互式 Pen 中，您可以滑动滑块来尝试不同的半径、内边距和颜色。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/11/9de30b5a6757dcc4363636ea41c7ec66.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Refs&lt;a href=&quot;#refs&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.stefanjudis.com/blog/web-weekly-176/&quot;&gt;Web Weekly #176&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://javascriptweekly.com/issues/763&quot;&gt;JavaScript Weekly Issue 763: November 28, 2025&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://codepen.io/spark/484&quot;&gt;Code Spark #484&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>category:周刊</category><category>tag:前端</category><category>tag:JavaScript</category><category>tag:CSS</category></item><item><title>FE Bits Vol.16｜Cloudflare 事故报告出炉，CSSWG 确认 Masonry 布局语法 grid-lanes</title><link>https://blog.cosine.ren/post/weekly-16</link><guid isPermaLink="false">weekly-16</guid><description>本期：Git 2.52 首次引入 Rust；Cloudflare 故障；Angular v21 聚焦 AI；CSS 进展：Masonry grid‑lanes、Chrome 144 锚点变换感知。</description><pubDate>Sun, 23 Nov 2025 11:30:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本期网址 &lt;a href=&quot;https://blog.cosine.ren/post/weekly-16&quot;&gt;https://blog.cosine.ren/post/weekly-16&lt;/a&gt;
本周刊更新时间期望是在每周天
目前推荐使用 &lt;a href=&quot;https://folo.is/&quot;&gt;Folo&lt;/a&gt; 订阅本周刊的 &lt;a href=&quot;https://quaily.com/cosine/feed/atom&quot;&gt;Quaily RSS&lt;/a&gt;。
公众号 前端周周谈 FE Bits，点击阅读原文链接可查看原文。
QQ 讨论小群 598022684，日常讨论前端技术 &amp;amp; 生活，也可在群里投稿自己的文章，随意加入，比较偏向粉丝群的性质～
本周刊文章内容同时也开源在 &lt;a href=&quot;https://github.com/yusixian/fe-bits-weekly&quot;&gt;fe-bits-weekly&lt;/a&gt;，欢迎关注。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;今天是 2025 年 11 月 23 日，星期天。&lt;/p&gt;
&lt;p&gt;网站用 &lt;a href=&quot;https://github.com/shishkin/astro-pagefind&quot;&gt;astro-pagefind&lt;/a&gt; 实现了无需后端的搜索，好用的。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/11/429d0e73b863269741029207fbed29fc.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;计划在没有后端的情况下把网站功能完善，然后再逐步加上有后端的功能，这样没后端的也好部署，就像以前用 hexo 的体验一样。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/11/d01f66f7df2a7dd34388608e6fdc1230.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;生态与社区动态&lt;a href=&quot;#生态与社区动态&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.blog/open-source/git/highlights-from-git-2-52/&quot;&gt;Highlights from Git 2.52&lt;/a&gt;：Git 2.52 带来了新命令、性能优化，此版本首次使用 Rust 代码来实现 Git 的一些内部功能，Git 3.0 将全面要求 Rust。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cloudflare 于北京时间 2025 年 11 月 18 日晚间发生全球性网络故障，事件影响遍及多个地区和服务，包括 WARP 及 Application Services，最终在连续两个多小时后逐步恢复正常。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;故障时间线与恢复进展
20&lt;/p&gt;&lt;div&gt;&lt;/div&gt; 起部分地区恢复，之后多次“再爆炸”与恢复循环。
21&lt;div&gt;&lt;/div&gt; 官方确认在伦敦禁用 WARP 访问，说明问题基本定位。
21&lt;div&gt;&lt;/div&gt;~21&lt;div&gt;&lt;/div&gt; 官方多次更新状态，逐步恢复 Access、WARP 与应用服务（Application Services）。
22&lt;div&gt;&lt;/div&gt; 时 X（即 Twitter）网页版恢复访问&lt;p&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果你好奇原因，那么可以查看&lt;a href=&quot;https://blog.cloudflare.com/18-november-2025-outage/&quot;&gt;官方事故报告&lt;/a&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://survey.devographics.com/en-US/survey/state-of-react/2025&quot;&gt;React 2025 年现状调查&lt;/a&gt;：React 生态系统年度调查又开始了。提交截止日期为 11 月 25 日，也就是下周二。感兴趣的可以在这里查看 &lt;a href=&quot;https://2024.stateofreact.com/en-US&quot;&gt;2024 年的调查结果&lt;/a&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这种调查的填写过程中其实能学到不少东西。类似之前的 JS / CSS 的年度调查。&lt;/p&gt;
&lt;p&gt;React 团队近年来以稳健的节奏迭代，引入了 Server Components、Compiler 等，还设立了 React Foundation 进行管理。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.angular.dev/announcing-angular-v21-57946c34f14b&quot;&gt;Announcing Angular v21&lt;/a&gt;：Angular 迎来 v21，这是一次面向未来的版本更新，专注于 AI 集成、可访问性及现代开发体验。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reddit &lt;code&gt;/r/node&lt;/code&gt; 讨论 &lt;a href=&quot;https://www.reddit.com/r/node/comments/1ov6xrd/nestjs_is_bad_change_my_mind/&quot;&gt;&lt;strong&gt;NestJS&lt;/strong&gt; 的优缺点&lt;/a&gt;，引发社区思考框架选型取舍。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;文章与视频&lt;a href=&quot;#文章与视频&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.blog/ai-and-ml/github-copilot/how-to-write-a-great-agents-md-lessons-from-over-2500-repositories/&quot;&gt;如何撰写出色的 agents.md 文件：来自 2500 多个代码库的经验总结&lt;/a&gt;：GitHub 分享了从 2500+ 仓库提炼出的经验，教你如何写出真正有用的 &lt;code&gt;agents.md&lt;/code&gt; 文件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://catcoding.me/p/avoid-mistake/&quot;&gt;谈谈工作中的犯错 | CatCoding&lt;/a&gt; 错误不仅是个人问题，也暴露团队在设计、权限、质量控制、代码 Review 等方面的短板。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://cyandev.app/post/from-cloudflare-outage-to-code-safety&quot;&gt;从 Cloudflare 故障到代码安全 | Cyandev&lt;/a&gt;：从一次 Rust 相关的服务中断聊到内存安全与代码复杂度。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://frontendmasters.com/blog/how-to-create-3d-images-in-css-with-the-layered-pattern/&quot;&gt;如何使用分层模式在 CSS 中创建 3D 图像&lt;/a&gt;：使用 CSS 的 &lt;code&gt;transform-style: preserve-3d&lt;/code&gt; 让所有层在三维空间可见，利用透视与光影制造立体幻觉，并通过 &lt;code&gt;perspective&lt;/code&gt;、&lt;code&gt;translateZ()&lt;/code&gt;、&lt;code&gt;filter&lt;/code&gt; 等技巧实现动态的 3D 效果。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/11/2ca04a3a3ba0f48802a02210f4fc825c.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2025/11/keyframes-tokens-standardizing-animation-across-projects/&quot;&gt;Keyframes Tokens 跨项目动画标准化&lt;/a&gt;：如何通过将动画关键帧 (&lt;code&gt;@keyframes&lt;/code&gt;) 设计为可重用的 Keyframes Tokens，来实现动画系统的标准化与可维护化。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.readwriterachel.com/things-i-learned/2025/11/09/devtools-1.html&quot;&gt;我打赌你不知道可以用 Chrome 浏览器开发工具做的六件事 Part 1&lt;/a&gt;：带你发现 Chrome DevTools 中那些被忽略却超实用的隐藏技巧，提升调试效率。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本篇是 Part 1，涵盖前三个内容：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用 &lt;code&gt;console.time()&lt;/code&gt; 和 &lt;code&gt;console.timeEnd()&lt;/code&gt; 精准计时函数执行。&lt;/li&gt;
&lt;li&gt;利用 DOM Breakpoints 实时捕捉元素变化，并自动暂停脚本运行。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;monitor()&lt;/code&gt; 在控制台监听任意函数调用。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;断点是真的很重要，严肃学习！&lt;/p&gt;
&lt;h2&gt;CSS 新特性&lt;a href=&quot;#css-新特性&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://frontendmasters.com/blog/more-css-random-learning-through-experiments/&quot;&gt;通过实验学习更多 CSS random() 功能&lt;/a&gt;：作者通过多个实验展示了如何用 CSS 的 &lt;code&gt;random()&lt;/code&gt; 函数创造动态有趣的视觉效果，如旋转星空、滚动视差(parallax)星空及基于滚动的点阵动效，虽然目前仅 &lt;a href=&quot;https://developer.apple.com/safari/technology-preview/&quot;&gt;Safari 技术预览版&lt;/a&gt;支持该功能，但文章提供了可视化演示和代码片段，展示了 &lt;code&gt;random()&lt;/code&gt; 带来的新创意空间和未来样式系统的潜能。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.zhangxinxu.com/wordpress/2025/11/css-state-function/&quot;&gt;介绍下与 CSS 自定义组件相关的 &lt;/a&gt;&lt;/p&gt;&lt;div&gt;&lt;/div&gt;&lt;a href=&quot;https://www.zhangxinxu.com/wordpress/2025/11/css-state-function/&quot;&gt;() 函数&lt;/a&gt;：介绍了 CSS &lt;div&gt;&lt;/div&gt;() 伪类函数，这是 Web Components(网页组件) 的新特性，可用来根据组件内部状态修改样式。&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.bram.us/2025/11/20/anchor-positioning-is-transform-aware-in-chrome-144/&quot;&gt;在 Chrome 浏览器 144+ 中，锚点定位具有变换感知功能&lt;/a&gt;：Chrome 144 将 Anchor Positioning 改为对 transform 变换敏感（transform-aware），更新后 tooltip、浮层等会根据元素的变换后位置进行定位。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/12022#issuecomment-3525043825&quot;&gt;Masonry Switch 语法 #1022&lt;/a&gt;：CSS 工作组（CSSWG）确定采用新语法 &lt;code&gt;display: grid-lanes&lt;/code&gt; 来启用 CSS Grid Level 3 新增的 Masonry 布局。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;小贴士&lt;a href=&quot;#小贴士&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;虽然发过那么多次 CSS 新特性的博文推荐，但是在项目中实际用上的没那么多，决定开个新栏目，记录一下平常真在项目里用上了的小 tip！&lt;/p&gt;
&lt;p&gt;CSS &lt;code&gt;scroll-state&lt;/code&gt; 用来渐进式的增强滚动容器底部的羽化遮罩时还是很好用的～&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/11/eb89146b7bf76508c7e2a67325d0b710.gif&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/* https://github.com/parcel-bundler/lightningcss/issues/887 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.scroll-feather-mask&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  container-type&lt;/span&gt;&lt;span&gt;: scroll-state;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@container&lt;/span&gt;&lt;span&gt; scroll-state(scrollable: bottom) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .scroll-feather-mask::before&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    content&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    background&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;linear-gradient&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt; bottom&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;transparent&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--gradient-bg-start&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;70&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--gradient-bg-start&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    position&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;absolute&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    left&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    right&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    bottom&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    z-index&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;block&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;rem&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;参考链接&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://frontendmasters.com/blog/adaptive-alerts-a-css-scroll-state-use-case/&quot;&gt;Adaptive Alerts (a CSS scroll-state Use Case)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.zhangxinxu.com/wordpress/2025/08/css-container-scroll-state/&quot;&gt;CSS @container scroll-state 容器滚动查询&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://css-tricks.com/chrome-133-goodies/#aa-scroll-states-in-container-queries&quot;&gt;Chrome 133 Goodies&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;工具&lt;a href=&quot;#工具&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://imageconverter.dev/&quot;&gt;Free Online Image Converter&lt;/a&gt; 无需登录、无需上传服务器的在线图片转换工具，完全运行于浏览器端，支持多种主流与新兴图片格式的相互转换，可调整输出格式及质量，立即下载结果，没有水印、不限数量。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://baselinejs.vercel.app/&quot;&gt;Baseline JS Docs&lt;/a&gt;：检测 JavaScript 代码是否符合 Web 平台 Baseline 标准的 ESLint 插件，让代码对所有浏览器都更友好。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;趣站与 Codepen 精选&lt;a href=&quot;#趣站与-codepen-精选&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;h3&gt;Classic 8×8-pixel B&amp;amp;W Mac patterns&lt;a href=&quot;#classic-88-pixel-bw-mac-patterns&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;网站：&lt;a href=&quot;https://paulsmith.github.io/classic-mac-patterns/&quot;&gt;https://paulsmith.github.io/classic-mac-patterns/&lt;/a&gt;
介绍文章： &lt;a href=&quot;https://pauladamsmith.com/blog/2025/09/classic-mac-patterns.html&quot;&gt;Classic 8×8-pixel B&amp;amp;W Mac patterns&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;由 Paul Smith 制作的 Classic Mac OS System 1 背景图案，这些极小尺寸的复古像素图案可通过 CSS &lt;code&gt;background-repeat&lt;/code&gt; 实现无限平铺效果。尽管源自经典的 Mac OS 设计风格，但它们在现代网页中依然有独特的美感和实用价值。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/11/c9884109fffdba36b2b9c28ba003611a.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;Creepy Button&lt;a href=&quot;#creepy-button&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/jkantner/pen/ZYWKvqW&quot;&gt;ZYWKvqW&lt;/a&gt; by jkantner (&lt;a href=&quot;https://codepen.io/jkantner&quot;&gt;@jkantner&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“这个按钮在监视你”。从 Jon Kantner 这款俏皮的按钮下面探出头来，你会发现一双卡通眼睛正在跟踪你的一举一动。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/11/b2d6e6c953d6300483d906c7d699efdd.gif&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;Liquid Glass Clock&lt;a href=&quot;#liquid-glass-clock&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/alexandrevacassin/pen/YPqqqwr&quot;&gt;YPqqqwr&lt;/a&gt; by alexandrevacassin (&lt;a href=&quot;https://codepen.io/alexandrevacassin&quot;&gt;@alexandrevacassin&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;顾名思义，一个液态玻璃数字时钟。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;https://r2.cosine.ren/i/2025/11/ab2be23ad6110f1d322cc6f90d9d57aa.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Refs&lt;a href=&quot;#refs&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://react.statuscode.com/issues/452&quot;&gt;React Status Issue 452: November 19, 2025&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://javascriptweekly.com/issues/762&quot;&gt;JavaScript Weekly Issue 762: November 21, 2025&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nodeweekly.com/issues/601&quot;&gt;Node Weekly Issue 601: November 18, 2025&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://frontendfoc.us/issues/718&quot;&gt;Frontend Focus Issue 718: November 19, 2025&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://codepen.io/spark/483&quot;&gt;Codepen Spark #483&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>category:周刊</category><category>tag:前端</category><category>tag:JavaScript</category><category>tag:CSS</category></item></channel></rss>