<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>亦舟</title>
    <link>https://soarcraft.cn</link>
    <description>写代码,做点小工具,偶尔把想明白的事情写下来。</description>
    <language>zh-CN</language>
    <lastBuildDate>Wed, 16 Sep 2026 00:00:00 GMT</lastBuildDate>
    <atom:link href="https://soarcraft.cn/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>测试</title>
      <link>https://soarcraft.cn/blog/post-698a11ee</link>
      <guid isPermaLink="true">https://soarcraft.cn/blog/post-698a11ee</guid>
      <pubDate>Wed, 16 Sep 2026 00:00:00 GMT</pubDate>
      <atom:updated>2026-09-16T00:00:00.000Z</atom:updated>
      <description></description>

      <content:encoded><![CDATA[<p>1
31</p>]]></content:encoded>
    </item>
    <item>
      <title>你好,世界</title>
      <link>https://soarcraft.cn/blog/hello-world</link>
      <guid isPermaLink="true">https://soarcraft.cn/blog/hello-world</guid>
      <pubDate>Thu, 10 Sep 2026 00:00:00 GMT</pubDate>
      <atom:updated>2026-09-15T00:00:00.000Z</atom:updated>
      <description>这个站点是怎么来的,以及我打算在这里写点什么。</description>
      <category>杂记</category>
      <content:encoded><![CDATA[<p>搭建一个自己的角落这件事,我想了很久。用过各种平台的博客、笔记软件、社交网络,最后发现它们都有同一个问题:那个地方不完全属于你。</p>
<h2 id="为什么要自己搭">为什么要自己搭</h2>
<p>平台会改版,会关停,会调整推荐算法。你写的东西散落在别人的数据库里,格式、链接、排版都不由你决定。而一个自己掌控的站点,最坏的情况也不过是换个域名继续活着。</p>
<blockquote>
<p>拥有自己的一小块互联网,是件很朴素但很踏实的事。</p>
</blockquote>
<h2 id="这里会有什么">这里会有什么</h2>
<p>大致三类内容:</p>
<ul>
<li><strong>技术笔记</strong> —— 踩过的坑、想明白的原理、值得复用的方案</li>
<li><strong>项目复盘</strong> —— 做了什么、为什么这么做、哪里做得不够好</li>
<li><strong>一些杂念</strong> —— 读书、工具、以及对日常生活的观察</li>
</ul>
<p>更新频率不承诺,但会尽量写自己真正想写的东西。</p>
<h2 id="技术选型">技术选型</h2>
<p>这个站用 Next.js + Tailwind CSS 搭建,文章用 Markdown 写在 <span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>content/posts/</span></span></code></span> 目录下,提交后自动构建。没有后台,没有数据库,一篇文章就是一个文件。</p>
<p>简单,可控,能活很久。</p>]]></content:encoded>
    </item>
    <item>
      <title>搞懂 React Server Components:一次讲清楚边界在哪</title>
      <link>https://soarcraft.cn/blog/react-server-components</link>
      <guid isPermaLink="true">https://soarcraft.cn/blog/react-server-components</guid>
      <pubDate>Sat, 22 Aug 2026 00:00:00 GMT</pubDate>
      <description>RSC 不是「在服务端渲染 React」那么简单。真正要理解的是序列化边界,以及它能穿过什么、不能穿过什么。</description>
      <category>React</category>
      <category>Next.js</category>
      <content:encoded><![CDATA[<p>第一次用 App Router 的时候,我踩了个很典型的坑:在一个 Server Component 里给子组件传了个函数当 prop,然后浏览器控制台给我报了一句话——</p>
<blockquote>
<p>Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".</p>
</blockquote>
<p>当时我只觉得规则很烦。后来想明白了:RSC 的核心不是「服务端渲染」,而是<strong>序列化边界</strong>。</p>
<h2 id="边界才是重点">边界才是重点</h2>
<p>服务端组件渲染的结果是<strong>一段可以被序列化的数据流</strong>(RSC Payload),它要通过网络传给客户端。能穿过这条边界的只有:</p>
<ul>
<li>普通对象、数组、字符串、数字、布尔、null</li>
<li>Date、Map、Set、TypedArray</li>
<li>JSX 元素(本身也是可序列化的描述)</li>
<li>Promise(会被流式处理)</li>
<li>标记为 <span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>"use server"</span></span></code></span> 的 server action 引用</li>
</ul>
<p>传不过去的是:<strong>函数、类实例、Symbol、以及任何带原型的自定义对象</strong>。</p>
<figure data-rehype-pretty-code-figure=""><div class="code-block"><button type="button" class="copy-button" data-copy-button="" aria-label="复制代码">复制</button><pre tabindex="0" data-language="tsx" data-theme="github-light github-dark"><code data-language="tsx" data-theme="github-light github-dark" style="display: grid;"><span data-line=""><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// ❌ 这样不行</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">ClientChart</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> formatter</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{(</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">v</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> v.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">toFixed</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">)} /></span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// ✅ 把格式化的事情留给客户端自己决定</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">&#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">ClientChart</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> precision</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">} /></span></span></code></pre></div></figure>
<h2 id="use-client不是这个组件在浏览器跑">「use client」不是「这个组件在浏览器跑」</h2>
<p>更准确的理解是:<strong>它是客户端的入口标记</strong>。打包时,构建工具从这个标记开始,把它 import 的所有模块一起打进客户端 bundle。</p>
<p>所以一个常见误区是:以为加了 <span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>"use client"</span></span></code></span> 的文件就完全不在服务端执行。实际上客户端组件在 SSR 阶段依然会在服务端跑一遍产出 HTML,只是它不会在服务端「重新渲染」。</p>
<h2 id="什么时候该切到客户端">什么时候该切到客户端</h2>
<p>判断标准其实只有一条:<strong>这个组件需不需要响应交互或访问浏览器 API</strong>。</p>
<p>需要用到 <span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>useState</span></span></code></span>、<span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>useEffect</span></span></code></span>、<span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>onClick</span></span></code></span>、<span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>window</span></span></code></span>、<span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>localStorage</span></span></code></span> —— 那就加 <span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>"use client"</span></span></code></span>。否则,留在服务端。</p>
<p>一个实用技巧:把 <span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>"use client"</span></span></code></span> 尽量往下推。不要给整个页面加,只给真正需要交互的那颗叶子节点加。父级保持服务端,能传给它的 JSX 会作为 <span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>children</span></span></code></span> 原样透传,不占用客户端 bundle 体积。</p>
<figure data-rehype-pretty-code-figure=""><div class="code-block"><button type="button" class="copy-button" data-copy-button="" aria-label="复制代码">复制</button><pre tabindex="0" data-language="tsx" data-theme="github-light github-dark"><code data-language="tsx" data-theme="github-light github-dark" style="display: grid;"><span data-line=""><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// 页面是服务端组件</span></span>
<span data-line=""><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> default</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> function</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0"> Page</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span data-line=""><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">ThemeToggle</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      {</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* 这棵子树依然是服务端渲染的 */</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      &#x3C;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">ServerRenderedList</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> /></span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    &#x3C;/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">ThemeToggle</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">></span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  )</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre></div></figure>
<h2 id="小结">小结</h2>
<ul>
<li>RSC 的本质是<strong>序列化边界</strong>,不是渲染位置</li>
<li><span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>"use client"</span></span></code></span> 标记的是<strong>打包入口</strong>,不是执行环境</li>
<li>把客户端边界<strong>往下推</strong>,bundle 才会小</li>
</ul>
<p>想清楚这三点,App Router 里 90% 的困惑会自己消失。</p>]]></content:encoded>
    </item>
    <item>
      <title>深色模式不该只是换个背景色</title>
      <link>https://soarcraft.cn/blog/css-dark-mode</link>
      <guid isPermaLink="true">https://soarcraft.cn/blog/css-dark-mode</guid>
      <pubDate>Thu, 30 Jul 2026 00:00:00 GMT</pubDate>
      <description>把亮色主题反过来写一遍,是最容易出错的做法。聊聊对比度、层次、以及那个恼人的首屏闪白。</description>
      <category>CSS</category>
      <category>设计</category>
      <content:encoded><![CDATA[<p>很多深色模式做出来像「把灯关掉」,而好的深色模式应该像「换了个房间」。</p>
<h2 id="不要直接反色">不要直接反色</h2>
<p>纯黑 <span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>#000</span></span></code></span> 配纯白 <span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>#fff</span></span></code></span> 的对比度是 21:1,远超阅读需要,长时间看会刺眼,而且在 OLED 屏上会有明显的文字拖影。</p>
<p>我的做法是:<strong>背景不用纯黑,文字不用纯白</strong>。</p>
<figure data-rehype-pretty-code-figure=""><div class="code-block"><button type="button" class="copy-button" data-copy-button="" aria-label="复制代码">复制</button><pre tabindex="0" data-language="css" data-theme="github-light github-dark"><code data-language="css" data-theme="github-light github-dark" style="display: grid;"><span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">:root</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span data-line=""><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">  --bg</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">#ffffff</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span data-line=""><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">  --fg</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">#18181b</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span data-line=""><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">  --muted</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">#71717a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">.dark</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span data-line=""><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">  --bg</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">#0b0b0f</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span data-line=""><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">  --fg</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">#e4e4e7</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span data-line=""><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">  --muted</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">#a1a1aa</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* 注意:深色下 muted 要更亮 */</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre></div></figure>
<p>关键点在 <span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>--muted</span></span></code></span>。深色背景上,次要文字需要<strong>提高</strong>亮度才能保持同样的可读性,直接复用亮色主题的灰度会让正文看不清。</p>
<h2 id="层次靠亮度不靠阴影">层次靠亮度,不靠阴影</h2>
<p>亮色模式里我们用阴影表达「浮起来」。到了深色模式,阴影几乎看不见,应该用<strong>表面亮度</strong>分层:</p>
<ul>
<li>页面底色:最暗</li>
<li>卡片:比底色亮一点点</li>
<li>悬浮层/弹窗:再亮一点点</li>
</ul>
<p>每级大概差 3%~5% 的亮度就够,多了会显得脏。</p>
<h2 id="解决首屏闪白">解决首屏闪白</h2>
<p>如果主题存在 <span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>localStorage</span></span></code></span> 里,React 水合之前页面已经渲染完了,于是用户会看到一闪而过的亮色。</p>
<p>解法是在 <span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>&#x3C;head></span></span></code></span> 里塞一段<strong>同步执行</strong>的内联脚本,在 CSS 生效前就把 <span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>class</span></span></code></span> 打到 <span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>&#x3C;html></span></span></code></span> 上:</p>
<figure data-rehype-pretty-code-figure=""><div class="code-block"><button type="button" class="copy-button" data-copy-button="" aria-label="复制代码">复制</button><pre tabindex="0" data-language="js" data-theme="github-light github-dark"><code data-language="js" data-theme="github-light github-dark" style="display: grid;"><span data-line=""><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">try</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span data-line=""><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> t</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> localStorage.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">getItem</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'theme'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">??</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'system'</span></span>
<span data-line=""><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">  const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> dark</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> t </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'dark'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> ||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> (t </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">===</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> 'system'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583"> &#x26;&#x26;</span></span>
<span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">    matchMedia</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'(prefers-color-scheme: dark)'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">).matches)</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  document.documentElement.classList.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">toggle</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">'dark'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, dark)</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">} </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">catch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {}</span></span></code></pre></div></figure>
<p>再给 <span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>&#x3C;html></span></span></code></span> 加 <span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>suppressHydrationWarning</span></span></code></span>,避免 React 因为服务端/客户端 HTML 不一致报警告。</p>
<h2 id="图片和插画">图片和插画</h2>
<p>别忘了图片。纯白底的图片在深色模式下会变成一块刺眼的亮斑。一个便宜的处理方式:</p>
<figure data-rehype-pretty-code-figure=""><div class="code-block"><button type="button" class="copy-button" data-copy-button="" aria-label="复制代码">复制</button><pre tabindex="0" data-language="css" data-theme="github-light github-dark"><code data-language="css" data-theme="github-light github-dark" style="display: grid;"><span data-line=""><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">.dark</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D"> img</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span data-line=""><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  filter</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">brightness</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0.9</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">) </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">contrast</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1.05</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span data-line=""><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre></div></figure>
<p>更好的做法是准备两套资源,用 <span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>&#x3C;picture></span></span></code></span> 配合 <span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>prefers-color-scheme</span></span></code></span> 切换。</p>
<h2 id="最后">最后</h2>
<p>深色模式的验收标准很简单:<strong>在晚上关了灯,用它读十分钟文章,眼睛不累</strong>。达不到就继续调。</p>]]></content:encoded>
    </item>
    <item>
      <title>我用的写作工具链:越简单越好</title>
      <link>https://soarcraft.cn/blog/writing-tools</link>
      <guid isPermaLink="true">https://soarcraft.cn/blog/writing-tools</guid>
      <pubDate>Thu, 18 Jun 2026 00:00:00 GMT</pubDate>
      <description>折腾过十几款编辑器之后,我回到了纯文本。工具不决定写作质量,但烂工具会显著拖慢你。</description>
      <category>工具</category>
      <category>杂记</category>
      <content:encoded><![CDATA[<p>工具折腾了三四年,最后收敛到一套极其朴素的东西。</p>
<h2 id="写作纯-markdown">写作:纯 Markdown</h2>
<p>Obsidian 或者其他什么都行,核心是<strong>文件就是文件</strong>。不锁进某个云服务的私有格式里,不依赖特定客户端才能打开。</p>
<p>我只在写的时候用两三个插件,其余时间保持原生。插件越多,越容易在某次更新后崩掉。</p>
<h2 id="配图截图--极简标注">配图:截图 + 极简标注</h2>
<ul>
<li>截图:CleanShot X / Snipaste</li>
<li>标注:能画框画箭头就够了,不需要花哨</li>
</ul>
<h2 id="发布git-push">发布:git push</h2>
<p>写完把 <span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>.md</span></span></code></span> 文件丢进目录,提交,推上去,CI 自动构建。整个流程和写代码一样。</p>
<p>这个好处是<strong>发布没有心理负担</strong>。不用登录后台、不用调排版、不用点一堆按钮。写作和发布之间只隔一个 <span data-rehype-pretty-code-figure=""><code data-language="txt" data-theme="github-light github-dark"><span data-line=""><span>git commit</span></span></code></span>。</p>
<h2 id="我不用的东西">我不用的东西</h2>
<ul>
<li><strong>AI 自动配图</strong> —— 风格太统一,反而没了辨识度</li>
<li><strong>复杂的排版工具</strong> —— 排版应该在写的时候就完成,而不是写完再调</li>
<li><strong>阅读量统计</strong> —— 有的话我会忍不住看,然后开始为了数据写</li>
</ul>
<h2 id="一句实在话">一句实在话</h2>
<p>工具链的终极目标是让你<strong>忘记工具</strong>。当你不再想着「我该用哪个软件写」的时候,这套工具链才算配好了。</p>]]></content:encoded>
    </item>
  </channel>
</rss>