<?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>光王 / King0fLight — 更新</title>
    <link>https://www.king0flight.com/</link>
    <description>做站时踩过的坑、刚想明白的事，以及写下来的长文章。</description>
    <language>zh-CN</language>
    <lastBuildDate>Sat, 07 Nov 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://www.king0flight.com/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>动画里别把 scale 和 blur 放一起</title>
      <link>https://www.king0flight.com/notes/scale-blur</link>
      <guid isPermaLink="true">https://www.king0flight.com/notes/scale-blur</guid>
      <pubDate>Sat, 07 Nov 2026 00:00:00 +0000</pubDate>
      <description>给每节的环境光加了漂移动画之后，无头浏览器里整站开始掉帧，连不相干的跑马灯都卡。 blur 是一次性的：栅格化完就是一张纹理。transform 平移它，是整帧搬纹理，很便宜。但动画里还写了 scale(1.16)——缩放会改变模糊的视觉半径，浏览器只好每一帧都重算那 64px 的高斯模糊。软件渲染下，每帧都是一次全尺…</description>
      <content:encoded><![CDATA[<p>给每节的环境光加了漂移动画之后，无头浏览器里整站开始掉帧，连不相干的跑马灯都卡。</p>
<p>blur 是一次性的：栅格化完就是一张纹理。transform 平移它，是整帧搬纹理，很便宜。但动画里还写了 <code>scale(1.16)</code>——缩放会改变模糊的视觉半径，浏览器只好每一帧都重算那 64px 的高斯模糊。软件渲染下，每帧都是一次全尺寸卷积。</p>
<p>改法是缩放从动画里删掉，光斑要多大就把元素本身做多大，动画只留 translate。</p>
<p>抓到它的是现成的无头测试：解码动画要在 4 秒内播完，一直正常，某天开始超时。帧率掉没掉，测试先于肉眼知道。</p>
]]></content:encoded>
    </item>
    <item>
      <title>正则的组引用是怎么静默丢掉的</title>
      <link>https://www.king0flight.com/notes/regex-backreference</link>
      <guid isPermaLink="true">https://www.king0flight.com/notes/regex-backreference</guid>
      <pubDate>Sat, 07 Nov 2026 00:00:00 +0000</pubDate>
      <description>替换串里的组引用过了两道转义，一道没写对就静默变成空串。没有报错，只是引用丢了。</description>
      <content:encoded><![CDATA[<p>给 RSS 里的外链统一加 <code>target=&quot;_blank&quot;</code>，用的是 <code>re.sub</code> 加组引用。生成完一跑，XML 解析直接报错，指向一行 <code>&lt;a href=&quot;&quot;&gt;</code>。</p>
<p>链接的地址没了。替换串里的 <code>\1</code> 在脚本字符串里过了两道转义，一道没写对，它就静默地变成了空串——没有报错，没有警告，只是引用丢了，剩下个合法的空字符串。</p>
<p>改法是用回调函数：<code>lambda m: '...' + m.group(1) + '...'</code>。组引用不经过字符串转义，就没有可丢的地方。</p>
<p>肉眼永远看不出来这种错——<code>href=&quot;&quot;</code> 是完全合法的。能抓住它的还是那套笨办法：把 feed 当 XML 完整解析一遍。</p>
]]></content:encoded>
    </item>
    <item>
      <title>grep 数出来的，可能是注释</title>
      <link>https://www.king0flight.com/notes/grep-trap</link>
      <guid isPermaLink="true">https://www.king0flight.com/notes/grep-trap</guid>
      <pubDate>Sat, 07 Nov 2026 00:00:00 +0000</pubDate>
      <description>有类装饰靠 html[data-js] 这个属性决定出不出场。给五个手写页面补这个开关时，用 grep -c data-js 确认过——每一页都有。上了线，那些开关在浏览器里就是不出现。 原因：页面里讲退路的注释也写着「data-js」。grep 数的是包含这个词的行数，注释和代码它不分。 后来改搜 dataset.j…</description>
      <content:encoded><![CDATA[<p>有类装饰靠 <code>html[data-js]</code> 这个属性决定出不出场。给五个手写页面补这个开关时，用 <code>grep -c data-js</code> 确认过——每一页都有。上了线，那些开关在浏览器里就是不出现。</p>
<p>原因：页面里讲退路的注释也写着「data-js」。grep 数的是包含这个词的行数，注释和代码它不分。</p>
<p>后来改搜 <code>dataset.js</code>——只有真正可执行的那一行才这么写。再往后干脆让测试开一个禁用 JavaScript 的浏览器去看：按钮在不在，浏览器说了算。</p>
<p>文本搜索找到的是字，不是会跑的字。</p>
]]></content:encoded>
    </item>
    <item>
      <title>十轮改造，把手写站从「炫」做到「顺」</title>
      <link>https://www.king0flight.com/blog/ten-rounds</link>
      <guid isPermaLink="true">https://www.king0flight.com/blog/ten-rounds</guid>
      <pubDate>Sat, 07 Nov 2026 00:00:00 +0000</pubDate>
      <description>首页够炫了，点进文章却像换了个网站。十轮改造把光场、搜索、离线、质感、订阅、分享逐层补齐——这篇记下每层学到了什么，以及测试网住了哪些坑。</description>
      <content:encoded><![CDATA[<p>「首页够炫了。」有人看完这么说，「但点进文章，像换了一个网站。」</p>
<p>这话是对的。首页有入场幕、一间能拖的 3D 小屋、钉住的横向画廊；而文章页连个目录都没有，标题点不了，读完长文只能一路滚回顶部。皮和里子不在一个水平线上。</p>
<p>于是有了从 v32 到 v41 的十轮改造。这篇按层记下每一步学到了什么——比「改了什么」更重要的是「什么被测试网住了」。</p>
<h2 id="先把底色点亮"><a class="h-anchor" href="#先把底色点亮" aria-label="跳到「先把底色点亮」这一节" title="这一节的链接">#</a>先把底色点亮</h2>
<p>第一刀砍向「单调」。整页从上到下共用一块深底，其实每节早就配了自己的色相——靛蓝一路走到珊瑚——但那层环境光只有 27% 的透明度，几乎看不见。</p>
<p>把它点亮：每节两团光，位置交错、相位相反，四五十秒漂移一圈。滚动的时候像穿过一片一片的光。</p>
<p>然后掉帧了。无头浏览器里整站的动画卡成幻灯片，连不相干的跑马灯都卡。查下来是漂移动画里写了 <code>scale</code>：模糊是静态栅格，平移是整帧搬纹理，很便宜；缩放却会改变模糊的视觉半径，浏览器只好每帧重算那 64px 的高斯模糊。删掉 scale，光斑多大就把元素做多大，动画只留平移。</p>
<p>这件事让我确立了一条规矩：动画只动 transform，而且别碰带 filter 的东西。</p>
<h2 id="ctrlk把全站装进一个输入框"><a class="h-anchor" href="#ctrlk把全站装进一个输入框" aria-label="跳到「Ctrl+K：把全站装进一个输入框」这一节" title="这一节的链接">#</a>Ctrl+K：把全站装进一个输入框</h2>
<p>第二层是导航。页面上有七个入口，博客里四篇文章、笔记里十几条，全靠翻。</p>
<p>现在全站任何页面按 <code>Ctrl+K</code>，一个输入框从顶部落下来：页面、作品、文章、笔记，加「切主题」「回顶部」两个动作，敲几个字回车就到。</p>
<p>有意思的是数据怎么来。生成文章的脚本顺手产出一个三 KB 的 JSON 索引，面板第一次打开才去取，之后走浏览器缓存——平时零开销。按钮和面板全部由脚本创建，没有脚本的浏览器里它们压根不出现，不留假开关。</p>
<h2 id="断网也能读"><a class="h-anchor" href="#断网也能读" aria-label="跳到「断网也能读」这一节" title="这一节的链接">#</a>断网也能读</h2>
<p>第三层是 Service Worker。策略分三种：页面「网络优先」，在线永远最新，断网回落缓存；带版本号的静态资源「缓存优先」，换版本即换键；索引和订阅源「先给缓存、后台更新」。预缓存只装骨架，文章访问到才进缓存——读过的都能离线看。</p>
<p>这层埋了一个我到现在都后怕的坑：网络优先的分支起初没检查响应状态码，服务器的 404 会被当成「最新版本」写进缓存。也就是说，一个不存在的页面，断网时会永远看到它。现在 404 原样透传，只有真正的网络失败才走兜底。</p>
<h2 id="质感是成批来的"><a class="h-anchor" href="#质感是成批来的" aria-label="跳到「质感是成批来的」这一节" title="这一节的链接">#</a>质感是成批来的</h2>
<p>后面的几轮开始成批处理：画廊卡片换上色相环描边和指针倾斜；RSS 带上全文，订阅器里直接读完；顺着分享链接落到某一节时，那节闪一下告诉你「就是这」；文章可以打印成 PDF，外链把地址印在纸面上；最长的笔记列表页加了页内索引，视口外的条目干脆跳过渲染。</p>
<p>这一批的共性是每一件都很小，但都在真实的使用场景里：分享出去的人、打印的人、用订阅器的人、滚到底想回头的人。</p>
<h2 id="测试是安全网"><a class="h-anchor" href="#测试是安全网" aria-label="跳到「测试是安全网」这一节" title="这一节的链接">#</a>测试是安全网</h2>
<p>十轮下来，验收测试从 129 条涨到 181 条。比数量更重要的是它们抓到过的东西：</p>
<p>自动摘要里出现一个 <code>\1</code>，裸插进结构化数据，整个 JSON 块静默失效——生成一篇新笔记的当晚就被老测试拦下。浅色系统的访客首访被强塞深色主题，三行内联脚本之外还有一行启动逻辑会把系统跟随强行拍回深色——也是测试先说的。</p>
<p>这些错有一个共同点：肉眼看不出来。<code>href=&quot;&quot;</code> 是合法的空字符串，浅色主题在别人电脑上，而我这边永远是深色。</p>
<p>两个教训写进了脚本注释。一是动画里 scale 和 blur 别放一起；二是正则替换串里的组引用会过好几道转义，一道错了就静默丢掉——后来一律改用回调函数拼，没有可丢的地方。</p>
<h2 id="还开着的事"><a class="h-anchor" href="#还开着的事" aria-label="跳到「还开着的事」这一节" title="这一节的链接">#</a>还开着的事</h2>
<p>评论和访问统计还在等账号侧的配置，入口都留好了。</p>
<p>比这更难的其实是内容。皮有 181 条测试守着，改坏了会红；内容没有测试，只有自己守着自己——写一篇是一篇，每篇都得值得别人花时间。</p>
<p>这个站到今天 40 多个版本。下一步大概还是老样子：看到一个问题，记下来，修掉，写一篇笔记。</p>
]]></content:encoded>
    </item>
    <item>
      <title>浅色主题的另一半</title>
      <link>https://www.king0flight.com/notes/light-theme-contrast</link>
      <guid isPermaLink="true">https://www.king0flight.com/notes/light-theme-contrast</guid>
      <pubDate>Mon, 19 Oct 2026 00:00:00 +0000</pubDate>
      <description>深色主题调了十几版，浅色一直是&quot;能看就行&quot;。 今天量了一下对比度：邮箱那一行的粉色标签 #ff6fb5 在白底上只有 2.6:1，规格表的紫色小字 3.0:1。 WCAG 对 12px 文字的要求是 4.5:1。深色下这两处都在 8:1 左右，所以一直没觉得有问题。 改法不复杂：浅色主题单独配一套加深的色相，深色那套不…</description>
      <content:encoded><![CDATA[<p>深色主题调了十几版，浅色一直是&quot;能看就行&quot;。</p>
<p>今天量了一下对比度：邮箱那一行的粉色标签 <code>#ff6fb5</code> 在白底上只有 <strong>2.6:1</strong>，规格表的紫色小字 <strong>3.0:1</strong>。<a href="https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html" target="_blank" rel="noopener noreferrer">WCAG 对 12px 文字的要求</a>是 4.5:1。深色下这两处都在 8:1 左右，所以一直没觉得有问题。</p>
<p>改法不复杂：浅色主题单独配一套加深的色相，深色那套不动。以前以为主题切换就是换背景和文字色，现在知道强调色也得各配一套。</p>
<p>顺带一句：这个对比度是脚本读计算样式算出来的，肉眼真的看不出来。</p>
]]></content:encoded>
    </item>
    <item>
      <title>390 像素上的那颗按钮</title>
      <link>https://www.king0flight.com/notes/header-toggle</link>
      <guid isPermaLink="true">https://www.king0flight.com/notes/header-toggle</guid>
      <pubDate>Mon, 19 Oct 2026 00:00:00 +0000</pubDate>
      <description>主题切换按钮在 320px 上是靠右的，到了 375 和 390 就跑到 logo 和导航中间去了。 原因是页头用 space-between 排三个元素：宽度够的时候，空白被平均分成两份，按钮就悬在中间。它没有坏，只是没人管过它在中等宽度下的位置。 改法是把导航推到右端、按钮跟在导航后面，而不是让三个元素平分空白。…</description>
      <content:encoded><![CDATA[<p>主题切换按钮在 320px 上是靠右的，到了 375 和 390 就跑到 logo 和导航中间去了。</p>
<p>原因是页头用 <code>space-between</code> 排三个元素：宽度够的时候，空白被平均分成两份，按钮就悬在中间。它没有坏，只是没人管过它在中等宽度下的位置。</p>
<p>改法是把导航推到右端、按钮跟在导航后面，而不是让三个元素平分空白。</p>
<p>记下来是因为这类问题在桌面浏览器里永远发现不了——得把窗口拖到 390px 才看得见。</p>
]]></content:encoded>
    </item>
    <item>
      <title>一条命令，把内部文档发上了公网</title>
      <link>https://www.king0flight.com/blog/deploy-dot</link>
      <guid isPermaLink="true">https://www.king0flight.com/blog/deploy-dot</guid>
      <pubDate>Mon, 19 Oct 2026 00:00:00 +0000</pubDate>
      <description>部署命令写成 deploy . 之后，任何人访问 /部署说明.md 都能读到我的账户 ID。这篇记录怎么发现的、为什么 .assetsignore 没用，以及现在的发布约定。</description>
      <content:encoded><![CDATA[<p>第一次把网站发上线的时候，部署命令是这样的：</p>
<pre><code>npx wrangler pages deploy .
</code></pre>
<p>（这条命令出自 <a href="https://developers.cloudflare.com/pages/get-started/direct-upload/" target="_blank" rel="noopener noreferrer">Cloudflare Pages 的直传部署</a>。）</p>
<p><code>. </code>代表当前目录。命令跑完，网站正常打开，我也就没再看它。</p>
<h2 id="问题是怎么暴露的"><a class="h-anchor" href="#问题是怎么暴露的" aria-label="跳到「问题是怎么暴露的」这一节" title="这一节的链接">#</a>问题是怎么暴露的</h2>
<p>后来发现：整个项目目录都被传上去了。也就是说，下面这些地址在公网上可以直接打开：</p>
<pre><code>https://king0flight.com/部署说明.md
https://king0flight.com/avatar.png
</code></pre>
<p>部署说明里有 Cloudflare 账户 ID、DNS 记录、凭据文件的位置。虽然里面没有密码，但把这些东西挂在公网上，无论如何都不该发生。</p>
<h2 id="为什么-assetsignore-没用"><a class="h-anchor" href="#为什么-assetsignore-没用" aria-label="跳到「为什么 .assetsignore 没用」这一节" title="这一节的链接">#</a>为什么 .assetsignore 没用</h2>
<p>最容易想到的办法是加一个 <code>.assetsignore</code>，把内部文件排除掉。</p>
<p>它不生效。<code>.assetsignore</code> 是 Workers 静态资源那一套的机制，Cloudflare Pages 不读它。对 Pages 来说，你传上去什么，它就发什么。</p>
<h2 id="改法改成白名单而不是黑名单"><a class="h-anchor" href="#改法改成白名单而不是黑名单" aria-label="跳到「改法：改成白名单，而不是黑名单」这一节" title="这一节的链接">#</a>改法：改成白名单，而不是黑名单</h2>
<p>真正管用的做法是把结构改掉：</p>
<pre><code>personal-website/
├── public/          ← 只发这个目录
│   ├── index.html
│   ├── css/
│   ├── js/
│   └── ...
├── blog/            ← 博客源文件，不发布
├── tools/           ← 生成脚本，不发布
├── tests/           ← 测试，不发布
└── 部署说明.md       ← 内部文档，不发布
</code></pre>
<p>部署命令随之变成：</p>
<pre><code>npx wrangler pages deploy public --project-name king0flight --branch main
</code></pre>
<p>黑名单的思路是&quot;我记得排除哪些&quot;，白名单的思路是&quot;只有这些能出去&quot;。前者会漏，后者不会。</p>
<h2 id="怎么验证"><a class="h-anchor" href="#怎么验证" aria-label="跳到「怎么验证」这一节" title="这一节的链接">#</a>怎么验证</h2>
<p>改完不能只看首页能不能打开，要专门请求那几个内部路径，检查两件事：</p>
<ul>
<li>状态码和 <code>Content-Type</code>：内部文档现在返回的是站点的 HTML 回退页（<code>text/html</code>），不是 <code>text/markdown</code></li>
<li>正文里搜关键词：账户 ID、凭据路径这些字符串一处都不该出现</li>
</ul>
<p>这一步现在成了固定动作：每次发布后 <code>curl</code> 一遍内部路径。看起来有点多余，但它只需十秒。</p>
<h2 id="一个遗留问题"><a class="h-anchor" href="#一个遗留问题" aria-label="跳到「一个遗留问题」这一节" title="这一节的链接">#</a>一个遗留问题</h2>
<p>已经发出去的资源会被 Cloudflare 边缘缓存，非 HTML 资源的默认缓存是七天。所以在切换后的短时间内，裸域上那两个地址还可能返回旧内容；<code>www</code> 和 <code>pages.dev</code> 域是即时干净的。</p>
<p>急不来，等它过期。</p>
<h2 id="留下的习惯"><a class="h-anchor" href="#留下的习惯" aria-label="跳到「留下的习惯」这一节" title="这一节的链接">#</a>留下的习惯</h2>
<p>发布目录永远显式写出来，不用 <code>.</code>。多打十几个字符，换掉一整类事故。</p>
]]></content:encoded>
    </item>
    <item>
      <title>域名没错，是网络记错了</title>
      <link>https://www.king0flight.com/blog/dns-old-zone</link>
      <guid isPermaLink="true">https://www.king0flight.com/blog/dns-old-zone</guid>
      <pubDate>Sun, 18 Oct 2026 00:00:00 +0000</pubDate>
      <description>手机打开裸域看到的是 GitHub 的 404，同一网络下 www 却正常。查到最后，是华为云一个没删的旧解析区域在替 Cloudflare 回答。</description>
      <content:encoded><![CDATA[<h2 id="现象"><a class="h-anchor" href="#现象" aria-label="跳到「现象」这一节" title="这一节的链接">#</a>现象</h2>
<p>手机访问 <code>king0flight.com</code>，看到的是 GitHub Pages 的 404：</p>
<blockquote>
<p>There isn't a GitHub Pages site here.</p>
</blockquote>
<p>同一台手机、同一个网络，访问 <code>www.king0flight.com</code> 却是新站。电脑上也正常。</p>
<p>第一反应是&quot;域名解析还没生效&quot;。但域名迁移已经过去一段时间了。</p>
<h2 id="先排除掉的可能性"><a class="h-anchor" href="#先排除掉的可能性" aria-label="跳到「先排除掉的可能性」这一节" title="这一节的链接">#</a>先排除掉的可能性</h2>
<p>按顺序查了一遍：</p>
<ul>
<li><strong><a href="https://developers.cloudflare.com/dns/" target="_blank" rel="noopener noreferrer">Cloudflare 的 DNS 记录</a></strong>：<code>@</code> 和 <code>www</code> 都正确 CNAME 到 <code>king0flight.pages.dev</code>，开了代理</li>
<li><strong>自定义域状态</strong>：两个域名都是 active，证书已签发</li>
<li><strong>页面内容</strong>：直接用 IP 和 <code>pages.dev</code> 地址取，都是新站</li>
<li><strong>移动端 UA</strong>：服务端没有做任何 UA 判断</li>
<li><strong>HTTP 到 HTTPS 跳转</strong>：正常</li>
</ul>
<p>服务端看起来没有问题。那就说明，手机拿到的地址就不对。</p>
<h2 id="关键的一条命令"><a class="h-anchor" href="#关键的一条命令" aria-label="跳到「关键的一条命令」这一节" title="这一节的链接">#</a>关键的一条命令</h2>
<p>正常查解析是这样：</p>
<pre><code>nslookup king0flight.com 223.5.5.5
</code></pre>
<p>返回的是 Cloudflare 的地址，正确。</p>
<p>但真正说明问题的是这一条——<strong>直接去问华为云的域名服务器</strong>：</p>
<pre><code>nslookup king0flight.com ns1.huaweicloud-dns.com
</code></pre>
<p>返回的是：</p>
<pre><code>185.199.108.153
185.199.109.153
185.199.110.153
185.199.111.153
</code></pre>
<p>这是 GitHub Pages 的四个 IP。</p>
<p>也就是说：域名已经托管到 Cloudflare，但华为云那边还有一个<strong>没删掉的旧解析区域</strong>，依然在对外回答，而且回答的是迁移之前的内容。谁问它，它就给旧地址。</p>
<h2 id="为什么只有部分设备中招"><a class="h-anchor" href="#为什么只有部分设备中招" aria-label="跳到「为什么只有部分设备中招」这一节" title="这一节的链接">#</a>为什么只有部分设备中招</h2>
<p>两层原因叠在一起：</p>
<ol>
<li><strong>旧区域还在</strong>（主因）：华为云的云解析里那个 <code>king0flight.com</code> 没有删除</li>
<li><strong>NS 委派有缓存</strong>：改 NS 之后，各级解析器对委派关系的缓存最长 48 小时。这段时间里，一部分网络仍然会去问华为云</li>
</ol>
<p>所以会出现&quot;同一个域名，不同网络看到不同网站&quot;这种看起来很不讲理的现象。</p>
<h2 id="处理"><a class="h-anchor" href="#处理" aria-label="跳到「处理」这一节" title="这一节的链接">#</a>处理</h2>
<ul>
<li>在华为云删掉旧区域。删完再查 <code>nslookup ... ns1.huaweicloud-dns.com</code>，返回变成 <code>Query refused</code>，说明它不再乱答了</li>
<li>本地或运营商 DNS 缓存：换 DNS（223.5.5.5 / 1.1.1.1）、换网络，或者等它过期</li>
<li>清浏览器缓存、开无痕模式<strong>没有用</strong>——这是 DNS 层的问题，不是浏览器的问题</li>
</ul>
<h2 id="一条判断口诀"><a class="h-anchor" href="#一条判断口诀" aria-label="跳到「一条判断口诀」这一节" title="这一节的链接">#</a>一条判断口诀</h2>
<blockquote>
<p>用公共 DNS 查，返回 Cloudflare 的地址，就说明域名和服务端都没问题；这时候所有异常都出在&quot;当前这条网络的缓存&quot;上。</p>
</blockquote>
<p>这句话后来省了我很多时间。以前遇到&quot;网站打不开&quot;，我会从服务器一路查到代码；现在先分清是&quot;服务端答错了&quot;还是&quot;有人替它答了&quot;。</p>
]]></content:encoded>
    </item>
    <item>
      <title>第十九版</title>
      <link>https://www.king0flight.com/notes/v19</link>
      <guid isPermaLink="true">https://www.king0flight.com/notes/v19</guid>
      <pubDate>Sat, 17 Oct 2026 00:00:00 +0000</pubDate>
      <description>这一版想做&quot;暗黑电影感&quot;：左边一条竖导轨，章节背后放一个巨大的幽灵编号，鼠标移到作品上时旁边浮出预览。 技术上没什么新鲜的，难的是克制。加导轨的时候顺手加了滚动百分比，后来又加了章节圆点；每一样单独看都成立，堆在一起就开始抢戏。 现在的做法是让它们各自只出现一次：导轨只在桌面，幽灵编号只在章节开头，预览只给精细指针。屏…</description>
      <content:encoded><![CDATA[<p>这一版想做&quot;暗黑电影感&quot;：左边一条竖导轨，章节背后放一个巨大的幽灵编号，鼠标移到作品上时旁边浮出预览。</p>
<p>技术上没什么新鲜的，难的是克制。加导轨的时候顺手加了滚动百分比，后来又加了章节圆点；每一样单独看都成立，堆在一起就开始抢戏。</p>
<p>现在的做法是让它们各自只出现一次：导轨只在桌面，幽灵编号只在章节开头，预览只给精细指针。屏幕窄一点、或者用触屏，这些东西就自动不出现。</p>
]]></content:encoded>
    </item>
    <item>
      <title>我给自己的网站写了 31 个测试</title>
      <link>https://www.king0flight.com/blog/tests</link>
      <guid isPermaLink="true">https://www.king0flight.com/blog/tests</guid>
      <pubDate>Sat, 17 Oct 2026 00:00:00 +0000</pubDate>
      <description>不是为了覆盖率，是为了改完敢发布。记录这套测试里最有用的几项、两个测量上的坑，以及它到底拦住过什么。</description>
      <content:encoded><![CDATA[<p>改版改到第十几版的时候，出现了一个规律：每修好一处，就会弄坏另一处。</p>
<p>导航在小屏上被挤出去、图片没了页面塌掉、浅色主题下某个字看不见——这些问题都有一个共同点：<strong>在桌面浏览器里，用鼠标，正常字号，永远看不到</strong>。</p>
<p>所以我开始写测试。不是单元测试，是拿一个无头浏览器把页面打开，量它。</p>
<h2 id="工具"><a class="h-anchor" href="#工具" aria-label="跳到「工具」这一节" title="这一节的链接">#</a>工具</h2>
<ul>
<li><a href="https://docs.pytest.org/" target="_blank" rel="noopener noreferrer"><code>pytest</code></a> 组织用例</li>
<li><a href="https://playwright.dev/python/" target="_blank" rel="noopener noreferrer"><code>playwright</code></a> 开一个独立的无头 Chromium，不碰我日常用的浏览器</li>
<li>测试自己起一个 <code>http.server</code>，指向 <code>public/</code>，不访问线上</li>
</ul>
<p>一条命令跑完：</p>
<pre><code>pytest tests/ -q
</code></pre>
<h2 id="最有用的几项"><a class="h-anchor" href="#最有用的几项" aria-label="跳到「最有用的几项」这一节" title="这一节的链接">#</a>最有用的几项</h2>
<p><strong>横向溢出检查</strong>。遍历页面上所有可见元素，只要有一个伸出视口右边，就失败。装饰性的巨型描边字和跑马灯轨道会故意溢出，所以它们都标了 <code>aria-hidden</code>，检查时跳过。</p>
<p><strong>200% 文字缩放</strong>。把所有元素的计算字号翻倍，再跑一遍溢出检查。<a href="https://www.w3.org/TR/WCAG22/#resize-text" target="_blank" rel="noopener noreferrer">WCAG 要求</a>页面在 200% 缩放下仍然可用，这一项抓到的都是真问题。</p>
<p><strong>关掉脚本</strong>。<code>java_script_enabled=False</code> 打开页面，断言正文、导航、展开说明都还在。渐进增强不是口号，关掉 JS 一眼就知道有没有做到。</p>
<p><strong>图片加载失败</strong>。把所有图片请求中断，看布局会不会塌。</p>
<p><strong>署名拼写</strong>。扫 <code>public/</code> 下所有 HTML/CSS/JS，只允许 <code>King0fLight</code>、<code>king0flight</code>、<code>KING0FLIGHT</code> 三种写法。这条是因为 <code>0</code> 和 <code>o</code> 长得太像，被指出过两次。</p>
<p><strong>内部文件不可访问</strong>。直接请求部署说明、博客源文件、测试文件，必须 404。</p>
<h2 id="两个测量上的坑"><a class="h-anchor" href="#两个测量上的坑" aria-label="跳到「两个测量上的坑」这一节" title="这一节的链接">#</a>两个测量上的坑</h2>
<p><strong>一、量 200% 缩放时不能边读边写。</strong></p>
<p>我第一版是遍历元素，读到字号就乘二写回去。结果子元素继承了父元素放大后的值，逐层翻倍，最后量到 <code>font-size: 4352px</code>、整页两万多像素宽，差点以为页面真的坏了。</p>
<p>正确做法是两段式：先把所有元素的计算字号读进数组，再统一写入。</p>
<p><strong>二、别把 Cloudflare 的 308 当成内容丢失。</strong></p>
<p>Pages 会把 <code>/*.html</code> 重定向到去掉后缀的地址。用 <code>curl</code> 验证线上内容时如果不加 <code>-L</code>，拿到的是空响应体，很容易误判成&quot;页面内容丢了&quot;。测试里不涉及这一点，但线上排查时踩过一次。</p>
<h2 id="它到底拦住了什么"><a class="h-anchor" href="#它到底拦住了什么" aria-label="跳到「它到底拦住了什么」这一节" title="这一节的链接">#</a>它到底拦住了什么</h2>
<ul>
<li>页头 <code>fixed</code> 加固定高度，在 320px + 200% 缩放下把主题按钮挤出屏幕——改成 <code>sticky</code> 后解决</li>
<li>首页标题的句点跑到了&quot;光王&quot;前面（遍历活的 <code>childNodes</code>、边删边加的经典错误）——这个不是测试抓到的，是自己看截图看出来的，也说明自动检查覆盖不到所有东西</li>
<li>新增页面忘了同步 <code>?v=</code> 版本号——资源版本号是测试里写死的常量，忘了就红</li>
</ul>
<h2 id="最后"><a class="h-anchor" href="#最后" aria-label="跳到「最后」这一节" title="这一节的链接">#</a>最后</h2>
<p>31 项，40 秒。覆盖率谈不上，但它给了我一件事：<strong>改完敢按发布</strong>。</p>
<p>对一个一直在改的个人网站来说，这比多写几个功能重要。</p>
]]></content:encoded>
    </item>
    <item>
      <title>五个色相</title>
      <link>https://www.king0flight.com/notes/five-hues</link>
      <guid isPermaLink="true">https://www.king0flight.com/notes/five-hues</guid>
      <pubDate>Wed, 14 Oct 2026 00:00:00 +0000</pubDate>
      <description>有一版我把渐变收得太狠，整页只剩黑白灰，看起来像没做完。 这一版给每节配一个色相：关于是靛蓝，过程是紫，本站是兰紫，折腾是品红，联系是珊瑚。往下滚，颜色从冷走到暖。 关键是色只落在小面积上——章节编号、时间轴圆点、悬停竖条。正文和块面不动，否则又会变回&quot;渐变满天飞&quot;。</description>
      <content:encoded><![CDATA[<p>有一版我把渐变收得太狠，整页只剩黑白灰，看起来像没做完。</p>
<p>这一版给每节配一个色相：关于是靛蓝，过程是紫，本站是兰紫，折腾是品红，联系是珊瑚。往下滚，颜色从冷走到暖。</p>
<p>关键是色只落在小面积上——章节编号、时间轴圆点、悬停竖条。正文和块面不动，否则又会变回&quot;渐变满天飞&quot;。</p>
]]></content:encoded>
    </item>
    <item>
      <title>部署成功了，看到的还是上一版</title>
      <link>https://www.king0flight.com/notes/cache-buster</link>
      <guid isPermaLink="true">https://www.king0flight.com/notes/cache-buster</guid>
      <pubDate>Wed, 14 Oct 2026 00:00:00 +0000</pubDate>
      <description>改完提亮那版，部署输出 ✨ Deployment complete!，立刻 curl 生产域，拿到的还是 ?v=17。 第一反应是部署没生效。其实是 Cloudflare 的边缘缓存：同一个 URL 会先给你一份旧的。 验证方法很简单——URL 后面加个随机参数，或者直接打开这次部署自己的 &lt;hash&gt;.king0fl…</description>
      <content:encoded><![CDATA[<p>改完提亮那版，部署输出 <code>✨ Deployment complete!</code>，立刻 <code>curl</code> 生产域，拿到的还是 <code>?v=17</code>。</p>
<p>第一反应是部署没生效。其实是 Cloudflare 的边缘缓存：同一个 URL 会先给你一份旧的。</p>
<p>验证方法很简单——URL 后面加个随机参数，或者直接打开这次部署自己的 <code>&lt;hash&gt;.king0flight.pages.dev</code>。</p>
<p>现在每次验证线上内容都带 cache-buster，不再用裸地址判断。</p>
]]></content:encoded>
    </item>
    <item>
      <title>页头不该是 fixed</title>
      <link>https://www.king0flight.com/notes/sticky-header</link>
      <guid isPermaLink="true">https://www.king0flight.com/notes/sticky-header</guid>
      <pubDate>Mon, 05 Oct 2026 00:00:00 +0000</pubDate>
      <description>小屏加上 200% 文字缩放时，主题按钮会被挤出屏幕右边。 原因是页头用了 position: fixed、高度写死 68px，装不下就把东西往外挤。 改成 sticky、回到文档流，装不下就换行长高。桌面观感没变，因为原来预留的高度和页头自身的高度正好相等。 这个问题是测试跑出来的：320px、375px、200%…</description>
      <content:encoded><![CDATA[<p>小屏加上 200% 文字缩放时，主题按钮会被挤出屏幕右边。</p>
<p>原因是页头用了 <code>position: fixed</code>、高度写死 68px，装不下就把东西往外挤。</p>
<p>改成 <code>sticky</code>、回到文档流，装不下就换行长高。桌面观感没变，因为原来预留的高度和页头自身的高度正好相等。</p>
<p>这个问题是测试跑出来的：320px、375px、200% 缩放各来一遍，它自己就露出来了。</p>
]]></content:encoded>
    </item>
    <item>
      <title>0 和 o</title>
      <link>https://www.king0flight.com/notes/zero-vs-o</link>
      <guid isPermaLink="true">https://www.king0flight.com/notes/zero-vs-o</guid>
      <pubDate>Fri, 02 Oct 2026 00:00:00 +0000</pubDate>
      <description>数字零和小写 o 在旧字体里几乎一样，于是署名看起来一直是错的。换成等宽字体之后，这个误会才解开。</description>
      <content:encoded><![CDATA[<p>署名是 <strong>King0fLight</strong>，<code>0</code> 是数字零。</p>
<p>有很长一段时间，站上用的是 Georgia 斜体——那个字体里的 <code>0</code> 和小写 <code>o</code> 几乎一样，看起来就是 <code>KingofLight</code>。源码没错，是字体让它看起来错了。</p>
<p>后来把所有署名换成等宽字体（Cascadia Code / Consolas），数字 <code>0</code> 中间带一撇，一眼能分清。</p>
<p>还加了个测试，扫 <code>public/</code> 下所有文件，只允许 <code>King0fLight</code>、<code>king0flight</code>、<code>KING0FLIGHT</code> 三种写法，别的都报错。</p>
]]></content:encoded>
    </item>
    <item>
      <title>跑马灯为什么要把内容抄两遍</title>
      <link>https://www.king0flight.com/notes/ticker</link>
      <guid isPermaLink="true">https://www.king0flight.com/notes/ticker</guid>
      <pubDate>Fri, 02 Oct 2026 00:00:00 +0000</pubDate>
      <description>首屏底下那条字，左边永远露着半个单词，看着像排版断了。 它其实一直在动。做法是把整串内容复制一份接在后面，轨道宽度变成两份，动画从 0 平移到 -50%——正好走完第一份，画面和第二份的开头对齐，然后瞬间跳回原点。因为两段内容一样，眼睛看不出接缝。 两侧再叠一层渐变蒙版，被裁掉的边缘才会淡出去，而不是硬切一刀。 不用…</description>
      <content:encoded><![CDATA[<p>首屏底下那条字，左边永远露着半个单词，看着像排版断了。</p>
<p>它其实一直在动。做法是把整串内容<strong>复制一份接在后面</strong>，轨道宽度变成两份，动画从 <code>0</code> 平移到 <code>-50%</code>——正好走完第一份，画面和第二份的开头对齐，然后瞬间跳回原点。因为两段内容一样，眼睛看不出接缝。</p>
<p>两侧再叠一层渐变蒙版，被裁掉的边缘才会淡出去，而不是硬切一刀。</p>
<p>不用 JS，也不用测文字宽度，前提是&quot;复制一份&quot;这个动作得手动做对。</p>
]]></content:encoded>
    </item>
    <item>
      <title>给网站写测试</title>
      <link>https://www.king0flight.com/notes/tests</link>
      <guid isPermaLink="true">https://www.king0flight.com/notes/tests</guid>
      <pubDate>Fri, 02 Oct 2026 00:00:00 +0000</pubDate>
      <description>一开始只想确认&quot;关掉 JS 还能不能读&quot;，后来越加越多：320 到 1440 的布局、200% 文字缩放、键盘能不能一路 Tab 下来、图片挂了会不会塌、内部文件能不能被访问。 现在一共 31 项，跑一遍不到 40 秒。 最有用的其实只有两项：横向溢出检查，和 200% 缩放。这两个能抓到肉眼在桌面上永远看不到的问题。</description>
      <content:encoded><![CDATA[<p>一开始只想确认&quot;关掉 JS 还能不能读&quot;，后来越加越多：320 到 1440 的布局、200% 文字缩放、键盘能不能一路 Tab 下来、图片挂了会不会塌、内部文件能不能被访问。</p>
<p>现在一共 31 项，跑一遍不到 40 秒。</p>
<p>最有用的其实只有两项：横向溢出检查，和 200% 缩放。这两个能抓到肉眼在桌面上永远看不到的问题。</p>
]]></content:encoded>
    </item>
    <item>
      <title>量 200% 缩放时踩的坑</title>
      <link>https://www.king0flight.com/notes/resize-measurement</link>
      <guid isPermaLink="true">https://www.king0flight.com/notes/resize-measurement</guid>
      <pubDate>Fri, 02 Oct 2026 00:00:00 +0000</pubDate>
      <description>写探测脚本的时候，我一边读 font-size 一边写回去。 结果子元素继承了父元素放大过的值，逐层翻倍，最后量到 font-size: 4352px、整页两万多像素宽。差点以为页面真有 bug。 正确做法是先读完全部元素的计算字号、存成数组，再统一写入。测试里现在是两段式的。</description>
      <content:encoded><![CDATA[<p>写探测脚本的时候，我一边读 <code>font-size</code> 一边写回去。</p>
<p>结果子元素继承了父元素放大过的值，逐层翻倍，最后量到 <code>font-size: 4352px</code>、整页两万多像素宽。差点以为页面真有 bug。</p>
<p>正确做法是先读完全部元素的计算字号、存成数组，再统一写入。测试里现在是两段式的。</p>
]]></content:encoded>
    </item>
    <item>
      <title>开站：从这里开始记录</title>
      <link>https://www.king0flight.com/blog/hello-world</link>
      <guid isPermaLink="true">https://www.king0flight.com/blog/hello-world</guid>
      <pubDate>Fri, 02 Oct 2026 00:00:00 +0000</pubDate>
      <description>博客的第一篇。说一下这个站，以及以后打算写点什么。</description>
      <content:encoded><![CDATA[<p>这是博客的第一篇文章。</p>
<h2 id="为什么要有这一页"><a class="h-anchor" href="#为什么要有这一页" aria-label="跳到「为什么要有这一页」这一节" title="这一节的链接">#</a>为什么要有这一页</h2>
<p>首页介绍了我是谁。这一页不一样——它会一直往下长。</p>
<p>我打算把那些<strong>不值得单独发一篇、但也不想忘掉</strong>的东西放在这里：踩过的坑、试出来的办法、正在做的小东西。</p>
<h2 id="会写什么"><a class="h-anchor" href="#会写什么" aria-label="跳到「会写什么」这一节" title="这一节的链接">#</a>会写什么</h2>
<p>大概是这几类：</p>
<ul>
<li>做东西的过程，和中间遇到的问题</li>
<li>学会了、想记住的知识点</li>
<li>偶尔的胡思乱想</li>
</ul>
<h2 id="现在有什么"><a class="h-anchor" href="#现在有什么" aria-label="跳到「现在有什么」这一节" title="这一节的链接">#</a>现在有什么</h2>
<p>说实话，现在几乎什么都还没有。这一页也是刚开。</p>
<p>但总得有个开头。第一件要写的事，就是这个网站本身——它是怎么从一张空白页，变成你现在看到的样子。</p>
<p>那篇会放在后面，等我整理好。</p>
<hr />
<p><em>这一页由 <code>blog/posts/</code> 下的 Markdown 文件生成，写 <code>.md</code> 就能发布下一篇文章。</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>域名没问题，是网络记错了</title>
      <link>https://www.king0flight.com/notes/dns-old-zone</link>
      <guid isPermaLink="true">https://www.king0flight.com/notes/dns-old-zone</guid>
      <pubDate>Thu, 01 Oct 2026 00:00:00 +0000</pubDate>
      <description>手机访问不带 www 的域名，一直看到旧博客的 404；同一网络下 www 却正常。 查了一圈：Cloudflare 的记录是对的，证书也是对的。最后是这一句问出来的—— nslookup king0flight.com ns1.huaweicloud-dns.com 华为云那边的旧解析区域还活着，还在回答旧 GitH…</description>
      <content:encoded><![CDATA[<p>手机访问不带 www 的域名，一直看到旧博客的 404；同一网络下 www 却正常。</p>
<p>查了一圈：Cloudflare 的记录是对的，证书也是对的。最后是这一句问出来的——</p>
<pre><code>nslookup king0flight.com ns1.huaweicloud-dns.com
</code></pre>
<p>华为云那边的旧解析区域还活着，还在回答旧 GitHub Pages 的 IP。删掉旧区域、等本地 DNS 缓存过期，就正常了。</p>
<p>判断口诀：公共 DNS（223.5.5.5）解析到 Cloudflare 的地址，就说明域名和服务端都没问题，问题在当前这条网络的缓存上。</p>
]]></content:encoded>
    </item>
    <item>
      <title>一条命令，把内部文档发上了公网</title>
      <link>https://www.king0flight.com/notes/deploy-dot</link>
      <guid isPermaLink="true">https://www.king0flight.com/notes/deploy-dot</guid>
      <pubDate>Thu, 01 Oct 2026 00:00:00 +0000</pubDate>
      <description>部署命令原先写的是 wrangler pages deploy .，把整个项目目录发了上去——包括部署说明文档和头像源文件。 也就是说，任何人访问 /部署说明.md 都能读到账户 ID 和一堆内部记录。 后来改成只发 public/，内部文档、博客源文件、测试全部留在外面。 .assetsignore 在 Pages…</description>
      <content:encoded><![CDATA[<p>部署命令原先写的是 <code>wrangler pages deploy .</code>，把整个项目目录发了上去——包括部署说明文档和头像源文件。</p>
<p>也就是说，任何人访问 <code>/部署说明.md</code> 都能读到账户 ID 和一堆内部记录。</p>
<p>后来改成只发 <code>public/</code>，内部文档、博客源文件、测试全部留在外面。<code>.assetsignore</code> 在 Pages 上不生效，想靠它排除文件是没用的。</p>
<p>现在每次发完都会 <code>curl</code> 一下那几个内部路径，确认返回的是 HTML 回退页，不是原文。</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
