<?xml version="1.0" encoding="utf-8"?>
        <feed xmlns="http://www.w3.org/2005/Atom">
        <title>younisekai</title>
        <subtitle>异世界手记</subtitle>
        <link href="https://kazeyouni.pages.dev/" rel="alternate" type="text/html"/>
        <link href="https://kazeyouni.pages.dev/atom.xml" rel="self" type="application/atom+xml"/>
        <id>https://kazeyouni.pages.dev/</id>
        <updated>2026-10-11T10:08:17.702Z</updated>
        <language>zh_CN</language>
        <entry>
            <title>你好，younisekai</title>
            <link href="https://kazeyouni.pages.dev/posts/hello-younisekai/" rel="alternate" type="text/html"/>
            <id>https://kazeyouni.pages.dev/posts/hello-younisekai/</id>
            <published>2026-03-01T00:00:00.000Z</published>
            <updated>2026-03-04T00:00:00.000Z</updated>
            <summary>本站的第一篇文章。说说这个模板到底是什么、目录长什么样，以及怎样在十分钟内发出属于你自己的第一篇。</summary>
            <content type="html"><![CDATA[<p>欢迎来到这里。这是本站的第一篇文章，也是这套模板的自我介绍。</p>
<p>如果你刚刚把项目克隆下来，<code>npm install</code> 之后还不知道从哪下手，那么读完这一篇，你应该就能写出并发出自己的第一篇内容了。</p>
<h2>younisekai 是什么</h2>
<p>一句话：<strong>一个「写完 Markdown 就能发」的静态博客模板</strong>。</p>
<p>它用 <a href="https://astro.build/">Astro</a> 做构建，用 Tailwind CSS 排版，用 Svelte 写交互组件，最终产出的是一堆纯静态文件——没有数据库，没有运行时服务端，<code>dist/</code> 目录丢到任何静态托管上就能跑。</p>
<ul>
<li><strong>内容是文件</strong>：文章是 Markdown，项目、技能、历程、日记、相册是 JSON，全部放在 <code>src/content/</code> 里，可以用 Git 管理、可以随时整体迁移。</li>
<li><strong>自带后台</strong>：接上内容管理系统之后，可以在浏览器里直接写文章、改数据，不用装编辑器。</li>
<li><strong>该有的细节都有</strong>：暗色模式、全站搜索、无限级目录树、代码高亮与折叠、文章加密、复制保护、阅读进度与字数统计。</li>
<li><strong>零后端依赖</strong>：搜索索引在构建时生成，文章加密在浏览器端完成，评论等外部服务默认关闭、需要时再打开。</li>
</ul>
<p>:::tip
这套模板是移植并改造自开源项目 <a href="https://github.com/Spr-Aachen/Twilight">Spr-Aachen/Twilight</a>（MIT 许可），在此基础上重新设计了中文内容结构、数据集合页面与示例文档。协议与致谢写在<a href="/about/">关于页面</a>里。
:::</p>
<h2>目录结构</h2>
<pre><code>项目根目录/
├── public/                     # 直接拷贝进产物的静态资源
│   └── assets/images/          # 封面、头像、占位图、壁纸
├── src/
│   ├── components/             # 组件（卡片、侧栏挂件、评论……）
│   ├── content/                # 所有内容与数据
│   │   ├── posts/              # 文章（Markdown）
│   │   ├── projects/           # 项目（JSON + 封面图）
│   │   ├── skills/             # 技能（JSON）
│   │   ├── timeline/           # 历程（JSON）
│   │   ├── diary/              # 日记（JSON + 配图）
│   │   ├── albums/             # 相册（JSON + 图片）
│   │   ├── friends/            # 友链（JSON）
│   │   ├── about.md            # 「关于」页正文
│   │   └── friends.md          # 「友链」页正文
│   ├── layouts/                # 页面骨架
│   ├── pages/                  # 路由：文件即页面
│   ├── styles/                 # 样式与主题变量
│   └── utils/                  # 读取与整理内容的工具函数
├── astro.config.mjs            # 构建与插件配置
└── younisekai.config.yaml      # 站点、主题、侧栏、评论等总配置
</code></pre>
<h2>内容集合速查</h2>
<table>
<thead>
<tr>
<th>想改什么</th>
<th>去哪里改</th>
<th>一个文件对应</th>
</tr>
</thead>
<tbody>
<tr>
<td>一篇文章</td>
<td><code>src/content/posts/*.md</code></td>
<td>一篇文章</td>
</tr>
<tr>
<td>一个项目</td>
<td><code>src/content/projects/&lt;目录&gt;/intro.json</code></td>
<td>一个项目卡片</td>
</tr>
<tr>
<td>一项技能</td>
<td><code>src/content/skills/*.json</code></td>
<td>一张技能卡</td>
</tr>
<tr>
<td>一段历程</td>
<td><code>src/content/timeline/*.json</code></td>
<td>时间线上的一个节点</td>
</tr>
<tr>
<td>一条日记</td>
<td><code>src/content/diary/&lt;目录&gt;/*.json</code></td>
<td>一条带图的短动态</td>
</tr>
<tr>
<td>一本相册</td>
<td><code>src/content/albums/&lt;目录&gt;/*.json</code></td>
<td>一个相册与其中的照片</td>
</tr>
<tr>
<td>一个友链</td>
<td><code>src/content/friends/*.json</code></td>
<td>友链页上的一张卡片</td>
</tr>
<tr>
<td>关于 / 友链页正文</td>
<td><code>src/content/about.md</code>、<code>src/content/friends.md</code></td>
<td>一个单页</td>
</tr>
</tbody>
</table>
<p>:::note
页面是按目录约定生成的，所以<strong>文件名就是数据的一部分</strong>：文章的文件名决定它的网址，数据文件的名字决定它在列表里的 id。取名时用英文小写加连字符，最省心。
:::</p>
<h2>十分钟发出第一篇</h2>
<ol>
<li><strong>安装依赖</strong>：在项目根目录执行 <code>npm install</code>。</li>
<li><strong>新建文件</strong>：在 <code>src/content/posts/</code> 下建一个 Markdown 文件，例如 <code>my-first-note.md</code>——文件名会成为网址的一部分。</li>
<li><strong>写 frontmatter</strong>：文件开头两行 <code>---</code> 之间填写标题、日期、分类等信息，最少写 <code>title</code>、<code>published</code>、<code>description</code> 就够。</li>
<li><strong>写正文</strong>：常规 Markdown 语法即可，需要图表、提示框、数学公式时参考<a href="/posts/markdown-showcase/">排版速查</a>与<a href="/posts/diagrams-and-math/">图表与公式</a>。</li>
<li><strong>本地预览</strong>：<code>npm run dev</code>，打开终端提示的地址，文章位于 <code>/posts/my-first-note/</code>。</li>
<li><strong>构建发布</strong>：<code>npm run build</code>，把生成的 <code>dist/</code> 上传到任意静态托管即可。</li>
</ol>
<h2>接下来看什么</h2>
<ul>
<li>想先把环境和部署跑通：<a href="/posts/guide/getting-started/">快速上手：从本地到线上</a></li>
<li>想搞清楚每个 frontmatter 字段：<a href="/posts/guide/writing-guide/">写作指南：frontmatter 全解析</a></li>
<li>想知道正文能用哪些语法：<a href="/posts/markdown-showcase/">排版速查表</a></li>
<li>想换主题色、改侧栏挂件：<a href="/posts/theme-configuration/">主题与站点配置</a></li>
</ul>
<p>写博客最难的一步从来不是技术，而是「打开编辑器」。既然第一篇已经在这里了，第二篇就顺手写掉吧。</p>
]]></content>
            <author>
                <name>younisekai</name>
            </author>
            <category term="站点公告"></category>
            <category term="Astro" label="Astro"></category>
            <category term="静态站点" label="静态站点"></category>
            <category term="入门" label="入门"></category>
            </entry>
        <entry>
            <title>从一个静态博客的样式混乱，聊到一套撑得住的前端样式体系</title>
            <link href="https://kazeyouni.pages.dev/posts/long-article/" rel="alternate" type="text/html"/>
            <id>https://kazeyouni.pages.dev/posts/long-article/</id>
            <published>2026-08-20T00:00:00.000Z</published>
            <updated>2026-09-28T00:00:00.000Z</updated>
            <summary>一篇偏长的实践笔记：设计变量怎么收进一层、断点为什么不该太多、组件样式该守哪几条纪律，以及暗色模式收尾时最容易漏掉的两件事。</summary>
            <content type="html"><![CDATA[<p>这篇文章记录的是一次真实的返工：一个已经上线、文章也写了几十篇的静态站点，因为样式越改越乱，被迫停下来重新梳理整套前端样式体系。整个过程花了两周，其中一周在拆自己以前写的代码。</p>
<p>如果你也遇到过「改一个按钮的颜色，结果列表页的边框变了」这种事，这篇大概对你有用。</p>
<h2>样式为什么会先崩</h2>
<p>样式问题从来不是突然出现的，它有三个非常典型的前兆。</p>
<h3>症状一：同一个颜色有四种写法</h3>
<p>打开样式表，会看到 <code>#7C3AED</code>、<code>#7c3aed</code>、<code>rgb(124 58 237)</code> 和 <code>hsl(262 83% 58%)</code> 同时存在，都表示同一个紫。它们最初都来自同一个设计稿，只是每个人复制的时候顺手改了写法。</p>
<h3>症状二：间距靠感觉</h3>
<p><code>margin: 14px</code>、<code>padding: 13px</code>、<code>gap: 1.1rem</code> 散落在各个组件里。数值本身不算错，但它们之间没有关系，所以页面上到处是「差两像素」的错位。</p>
<h3>症状三：改一处影响一片</h3>
<p>这是最危险的：某个类名被多个页面复用，但复用的方式各不相同。改动它就像拆炸弹——你永远不知道谁会跟着炸。</p>
<p>:::note
前两个症状只是难看，第三个症状才是必须重构的信号。<strong>当一个改动的影响范围无法预测时，样式体系就已经失效了。</strong>
:::</p>
<h2>第一步：把设计变量收进一层</h2>
<p>重构的第一步不是删代码，而是<strong>把所有裸值收进一层变量</strong>。原则很简单：组件里只允许出现变量，不允许出现具体的颜色值和尺寸值。</p>
<h3>色板：一层色相加明度阶梯</h3>
<p>与其定义二十个命名颜色，不如定义「一个色相 + 一套明度阶梯」。这样换主题时只改一个色相数值，整站配色会一起跟着走：</p>
<pre><code>:root {
    /* 主色：一个色相，若干明度 */
    --primary-hue: 255;
    --primary: hsl(var(--primary-hue) 70% 55%);
    --primary-hover: hsl(var(--primary-hue) 70% 48%);
    --primary-light: hsl(var(--primary-hue) 70% 92%);

    /* 中性色：同一色相、低饱和度，比纯灰更耐看 */
    --text-90: hsl(var(--primary-hue) 12% 12%);
    --text-75: hsl(var(--primary-hue) 10% 30%);
    --text-50: hsl(var(--primary-hue) 8% 50%);
    --line-divider: hsl(var(--primary-hue) 12% 88%);
}
</code></pre>
<p>关键在于中性色也带上色相：纯灰（<code>hsl(0 0% 50%)</code>）放在有色调的界面上会显得脏，而带一点点主色相的中性色会自然很多。</p>
<h3>间距与圆角：给一套有限的台阶</h3>
<p>间距不要自由取值，给一套 6–8 个台阶就够，所有间距从台阶里挑：</p>
<table>
<thead>
<tr>
<th>变量</th>
<th>数值</th>
<th>典型用途</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--space-1</code></td>
<td>4px</td>
<td>图标与文字的间隙</td>
</tr>
<tr>
<td><code>--space-2</code></td>
<td>8px</td>
<td>标签内边距</td>
</tr>
<tr>
<td><code>--space-3</code></td>
<td>12px</td>
<td>卡片内元素间距</td>
</tr>
<tr>
<td><code>--space-4</code></td>
<td>16px</td>
<td>卡片内边距</td>
</tr>
<tr>
<td><code>--space-6</code></td>
<td>24px</td>
<td>区块之间的间距</td>
</tr>
<tr>
<td><code>--space-8</code></td>
<td>32px</td>
<td>页面大区块间距</td>
</tr>
<tr>
<td><code>--space-12</code></td>
<td>48px</td>
<td>首屏与页脚</td>
</tr>
</tbody>
</table>
<p>圆角同理：<code>--radius-sm</code>（6px）、<code>--radius-md</code>（10px）、<code>--radius-lg</code>（14px）、<code>--radius-full</code>。<strong>台阶之外不允许出现新数值</strong>，这条纪律比变量本身更重要。</p>
<h3>在 Tailwind 里落地</h3>
<p>用 Tailwind 的话，不必放弃实用类，只要把变量接到主题配置里：</p>
<pre><code>// tailwind.config 里的关键部分
export default {
    theme: {
        extend: {
            colors: {
                primary: "var(--primary)",
                "primary-hover": "var(--primary-hover)",
            },
            spacing: {
                // 把台阶映射成 Tailwind 的间距刻度
                13: "var(--space-13, 3.25rem)",
            },
            borderRadius: {
                lg: "var(--radius-lg)",
            },
        },
    },
};
</code></pre>
<p>这样写有两个好处：组件里仍然用 <code>bg-primary p-4 rounded-lg</code> 这样直观的类名；颜色的真实来源只有一个变量，换主题时不必全站替换。</p>
<h2>第二步：断点与栅格</h2>
<h3>断点不是越多越好</h3>
<p>见过最夸张的项目定义了九个断点，结果每个断点都要单独调一次布局，维护成本直接翻倍。实际上大部分内容型站点只需要三到四个：</p>
<table>
<thead>
<tr>
<th>断点</th>
<th>宽度</th>
<th>覆盖的设备</th>
</tr>
</thead>
<tbody>
<tr>
<td>默认</td>
<td>0 起</td>
<td>手机竖屏</td>
</tr>
<tr>
<td><code>sm</code></td>
<td>640px</td>
<td>手机横屏、小平板</td>
</tr>
<tr>
<td><code>md</code></td>
<td>768px</td>
<td>平板竖屏</td>
</tr>
<tr>
<td><code>lg</code></td>
<td>1024px</td>
<td>桌面</td>
</tr>
</tbody>
</table>
<p>再往上通常不需要新的断点，只需要限制内容的最大宽度——让正文宽度保持在 65–75 个字符之间，比给他加断点更有效。</p>
<h3>内容优先的调整顺序</h3>
<p>调整布局时，按这个顺序改，返工最少：</p>
<ol>
<li><strong>先让内容能读</strong>：正文行宽、行高、段落间距；</li>
<li><strong>再让结构能站</strong>：栅格换列、侧栏折叠；</li>
<li><strong>最后调装饰</strong>：圆角、阴影、动效。</li>
</ol>
<p>反过来先调阴影和圆角，内容一改就又得重来。</p>
<p>:::tip
测试时不要只拖浏览器宽度，一定要在真机上看一遍。手机上最常见的两个问题是：横向滚动条（多半是某个固定宽度元素溢出）和被虚拟键盘顶起来的输入框。
:::</p>
<h2>第三步：组件样式的三条纪律</h2>
<h3>纪律一：组件内不写魔法数字</h3>
<pre><code>/* 不好：14px 是从哪来的？没人知道 */
.card__title {
    margin-bottom: 14px;
}

/* 好：来自间距台阶，改台阶就能全站同步 */
.card__title {
    margin-bottom: var(--space-3);
}
</code></pre>
<h3>纪律二：状态成组出现</h3>
<p>一个交互元素至少有四种状态：默认、悬停、激活、禁用；如果它是链接，还有访问过；如果支持键盘，还要有焦点态。<strong>写样式时把这几种状态一次写完</strong>，不要等出问题再补：</p>
<pre><code>.button {
    background: var(--primary);
    color: white;
    transition: background-color 150ms ease;
}

.button:hover { background: var(--primary-hover); }
.button:active { transform: translateY(1px); }
.button:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.button:disabled { opacity: .5; cursor: not-allowed; }
</code></pre>
<h3>纪律三：样式随组件走</h3>
<p>样式写在组件文件里（scoped style），而不是在全局样式表里按类名遥控组件的内部结构。全局样式表只应该做三件事：定义变量、重置默认样式、处理跨组件的排版规则（例如 Markdown 正文）。</p>
<h2>第四步：暗色模式怎么收尾</h2>
<p>暗色模式做起来快，做对慢。核心思路是<strong>用变量推导，而不是逐个元素覆盖</strong>。</p>
<h3>只在变量层切换</h3>
<pre><code>:root {
    --card-bg: hsl(var(--primary-hue) 30% 99%);
    --text-90: hsl(var(--primary-hue) 12% 12%);
}

.dark {
    --card-bg: hsl(var(--primary-hue) 18% 10%);
    --text-90: hsl(var(--primary-hue) 10% 92%);
}
</code></pre>
<p>这样一来，组件层完全不需要知道当前是哪种模式。</p>
<h3>两件最容易漏的事</h3>
<ol>
<li><strong>对比度</strong>：暗色模式下把深色文字换成浅色文字时，很容易忽略「次级文字」——它在浅色模式下是 60% 灰，直接搬到暗色背景上会低到看不清。次级文字的对比度建议不低于 4.5:1。</li>
<li><strong>媒体素材</strong>：纯白背景的图片、透明度为 1 的壁纸、亮色主题的代码高亮，都会在暗色模式下显得刺眼。处理方式是给图片容器加一层极淡的遮罩、给壁纸加上可配置的不透明度，代码高亮则直接切换主题。</li>
</ol>
<p>:::caution
不要用 CSS 滤镜去「反色」处理图片——肤色、品牌色和截图里的 UI 都会变得诡异。宁可给图片加遮罩，也不要整体反色。
:::</p>
<h2>维护：让体系活下来</h2>
<p>重构完成只是开始，体系能不能撑住要看后面几个月的维护。</p>
<h3>每周一次的样式巡检</h3>
<p>花十分钟做三件事，收益极高：</p>
<ul>
<li>搜一遍裸色值（<code>#</code> 开头的十六进制）与裸像素值，看有没有新出现的漏网之鱼；</li>
<li>检查是否有新的类名重复定义（同一组件两处样式互相覆盖）；</li>
<li>在暗色模式下把主要页面点一遍，重点是表格、代码块和表单。</li>
</ul>
<h3>什么时候该重构</h3>
<p>出现下面任意一条，就该安排重构了：</p>
<ul>
<li>改一个样式需要同时动三处以上；</li>
<li>无法回答「这个颜色变量到底影响哪些页面」；</li>
<li>新页面的样式要靠复制旧页面再删改来产出；</li>
<li>每次改完都要在四个断点、两种主题下手工检查一遍。</li>
</ul>
<p>重构不等于重写。这次我做的是「先建立变量层、再逐页替换」，全程没有推翻任何页面结构，也就没有出现长期无法发布的分支。<strong>能小步走的重构，不要用大爆炸的方式做。</strong></p>
<p>回头看，样式问题本质上是<strong>约定问题</strong>：变量只有一个来源、间距只有一套台阶、状态一次写完、样式跟着组件走。这四条守住之后，代码量其实差不多，但改动的可预测性完全不同——你终于能回答「改这里会影响什么」这个问题了。</p>
]]></content>
            <author>
                <name>younisekai</name>
            </author>
            <category term="技术笔记"></category>
            <category term="技术笔记 / 前端"></category>
            <category term="前端" label="前端"></category>
            <category term="Tailwind CSS" label="Tailwind CSS"></category>
            <category term="排版" label="排版"></category>
            </entry>
        <entry>
            <title>复制保护四种开关</title>
            <link href="https://kazeyouni.pages.dev/posts/copy-protection/" rel="alternate" type="text/html"/>
            <id>https://kazeyouni.pages.dev/posts/copy-protection/</id>
            <published>2026-07-22T00:00:00.000Z</published>
            <updated>2026-07-22T00:00:00.000Z</updated>
            <summary>blockSelection、blockClipboard、blockContextMenu、blockDevTools 四项复制保护各自做什么、怎么验证、什么时候不该开。</summary>
            <content type="html"><![CDATA[<p>你正在看的这一页，四项复制保护<strong>全部开启</strong>。可以顺手试一下：拖选这段文字、按 <code>Ctrl+C</code>、点右键、按 <code>F12</code>——四种操作都会被拦下来。</p>
<h2>怎么配置</h2>
<pre><code>copyProtection:
    blockSelection: true      # 禁止选中文本
    blockClipboard: true      # 拦截复制、剪切、粘贴
    blockContextMenu: true    # 禁止右键菜单
    blockDevTools: true       # 屏蔽 F12、Ctrl+U、Ctrl+S
</code></pre>
<p>四个开关<strong>互相独立</strong>，默认全部为 <code>false</code>。按需要逐项开启，比「一键全开」更好：全开之后读者想复制一段代码示例都做不到，体验会明显变差。</p>
<h2>四项开关分别做什么</h2>
<table>
<thead>
<tr>
<th>开关</th>
<th>实际行为</th>
<th>生效方式</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>blockSelection</code></td>
<td>全站禁用文本选中（<code>user-select: none</code>），但输入框与文本域保持可选中</td>
<td>注入一条全局样式</td>
</tr>
<tr>
<td><code>blockClipboard</code></td>
<td>拦截 <code>copy</code>、<code>cut</code>、<code>paste</code> 三个事件并阻止默认行为</td>
<td>注册事件监听</td>
</tr>
<tr>
<td><code>blockContextMenu</code></td>
<td>拦截 <code>contextmenu</code> 事件，右键不再弹出菜单</td>
<td>注册事件监听</td>
</tr>
<tr>
<td><code>blockDevTools</code></td>
<td>拦截 <code>F12</code>，以及按下 <code>Ctrl</code> 时的 <code>U</code>（查看源代码）与 <code>S</code>（保存页面）</td>
<td>注册键盘监听</td>
</tr>
</tbody>
</table>
<p>:::note
这些保护都是<strong>浏览器端</strong>的。只要愿意关掉 JavaScript，或者直接在源码层面抓取，任何前端限制都能被绕过。它的定位是「提高随手复制的成本」，而不是「防止内容被拿走」。
:::</p>
<h2>逐项验证</h2>
<p>打开本页后可以按下面的方式自检：</p>
<ol>
<li><strong>选中</strong>：试着用鼠标拖选这段话（包括代码块与表格），选中高亮不会出现；但页面上的搜索框仍可正常输入与选中。</li>
<li><strong>复制</strong>：按 <code>Ctrl+C</code> 不会把内容写进剪贴板；<code>Ctrl+X</code>、<code>Ctrl+V</code> 同样被拦截。</li>
<li><strong>右键</strong>：右键单击页面任意位置都不会出现菜单。</li>
<li><strong>开发者工具</strong>：按 <code>F12</code>、<code>Ctrl+U</code>、<code>Ctrl+S</code> 都不会触发对应动作。</li>
</ol>
<pre><code>// 被拦截的就是这三类事件，核心只有几行
document.addEventListener("copy", (e) =&gt; e.preventDefault());
document.addEventListener("contextmenu", (e) =&gt; e.preventDefault());
document.addEventListener("keydown", (e) =&gt; {
    if (e.key === "F12") e.preventDefault();
});
</code></pre>
<h2>该不该开</h2>
<table>
<thead>
<tr>
<th>场景</th>
<th>建议</th>
</tr>
</thead>
<tbody>
<tr>
<td>原创长文、连载小说、需要防搬运的稿件</td>
<td>可以开 <code>blockClipboard</code> 与 <code>blockContextMenu</code></td>
</tr>
<tr>
<td>教读者复制代码的技术文章</td>
<td>只开 <code>blockContextMenu</code>，<strong>不要</strong>开 <code>blockClipboard</code></td>
</tr>
<tr>
<td>加密后的私人笔记</td>
<td>四项全开，配合 <code>encrypted</code> 使用</td>
</tr>
<tr>
<td>想让内容被引用、被分享的公开文章</td>
<td>保持默认全关，复制与引用都是好事</td>
</tr>
</tbody>
</table>
<p>:::tip
开了 <code>blockSelection</code> 之后，读者的浏览体验影响最大——没法选取文字，就没法用翻译插件、也没法做笔记。如果不是明确不想被复制的内容，建议保留默认值。
:::</p>
<h2>与加密的分工</h2>
<ul>
<li><strong>加密（<code>encrypted</code> + <code>password</code>）</strong>：管「能不能读」，正文在解锁前是密文；</li>
<li><strong>复制保护（<code>copyProtection</code>）</strong>：管「读了之后能不能带走」，作用于整个页面。</li>
</ul>
<p>两者可以叠加，也可以只用一种。加密的用法与注意事项见<a href="/posts/encrypted-note/">给文章加一把锁</a>。</p>
]]></content>
            <author>
                <name>younisekai</name>
            </author>
            <category term="主题定制"></category>
            <category term="复制保护" label="复制保护"></category>
            <category term="站点配置" label="站点配置"></category>
            </entry>
        <entry>
            <title>主题与站点配置：改一个 YAML 就够了</title>
            <link href="https://kazeyouni.pages.dev/posts/theme-configuration/" rel="alternate" type="text/html"/>
            <id>https://kazeyouni.pages.dev/posts/theme-configuration/</id>
            <published>2026-06-08T00:00:00.000Z</published>
            <updated>2026-07-30T00:00:00.000Z</updated>
            <summary>站点信息、主题色相、壁纸模式、导航菜单、侧栏挂件、文章卡片与评论，全部集中在一个配置文件里，这里逐项说明怎么改。</summary>
            <content type="html"><![CDATA[<p>这套模板的设计原则是：<strong>日常调整不碰源码</strong>。站点长什么样、导航有几级、侧栏放哪些卡片、要不要开评论，都由项目根目录下的一个 YAML 文件决定。</p>
<h2>配置文件在哪</h2>
<table>
<thead>
<tr>
<th>项目</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>文件位置</td>
<td>项目根目录的 <code>younisekai.config.yaml</code></td>
</tr>
<tr>
<td>谁在读它</td>
<td><code>src/config.ts</code> 用 <code>?raw</code> 读入，做归一化后导出给页面与组件使用</td>
</tr>
<tr>
<td>生效时机</td>
<td><strong>构建时</strong>读取，改完请重启开发服务器（或重新构建），热更新不会重新解析 YAML</td>
</tr>
<tr>
<td>敏感信息</td>
<td>不放这里。后台登录用的 GitHub OAuth 凭据走 <code>.env</code>（见 <code>.env.example</code>），YAML 只放可以公开的站点配置</td>
</tr>
</tbody>
</table>
<p>:::warning
不要在 YAML 里随意增删层级。每一项的位置都是有意义的，写错层级时要么该字段被忽略，要么直接抛出配置错误。
:::</p>
<h2>站点基础信息</h2>
<table>
<thead>
<tr>
<th>字段</th>
<th>示例值</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>site.siteURL</code></td>
<td><code>https://younisekai.example.com/</code></td>
<td>站点根地址，<strong>必须以斜杠结尾</strong>；RSS、Atom、站点地图与分享卡片都依赖它，部署前务必改成自己的域名</td>
</tr>
<tr>
<td><code>site.title</code></td>
<td><code>younisekai</code></td>
<td>站点标题，显示在导航栏左上角与浏览器标签页</td>
</tr>
<tr>
<td><code>site.subtitle</code></td>
<td><code>异世界手记</code></td>
<td>站点副标题，用于默认描述与页脚</td>
</tr>
<tr>
<td><code>site.lang</code></td>
<td><code>zh_CN</code></td>
<td>站点语言，可选 <code>en</code> / <code>ja</code> / <code>zh_hans</code> / <code>zh_hant</code>；<code>zh_CN</code>、<code>zh_TW</code> 这类地区写法会自动归一化</td>
</tr>
<tr>
<td><code>site.keywords</code></td>
<td>字符串数组</td>
<td>站点关键词，用于生成 <code>&lt;meta name="keywords"&gt;</code></td>
</tr>
<tr>
<td><code>site.timeZone</code></td>
<td><code>8</code></td>
<td>UTC 偏移小时数（-12 ~ 12），用于「几小时前」这类相对时间</td>
</tr>
<tr>
<td><code>site.defaultTheme</code></td>
<td><code>dark</code></td>
<td>默认主题：<code>system</code> 跟随系统、<code>light</code> 浅色、<code>dark</code> 深色</td>
</tr>
<tr>
<td><code>site.favicon</code></td>
<td>见下</td>
<td>站点图标数组，留空则使用内置默认图标</td>
</tr>
</tbody>
</table>
<p>字体与图标的写法：</p>
<pre><code>site:
    font:
        # 标识会用于生成 .font-&lt;标识&gt; 工具类，请勿包含空格
        NotoSansSC:
            src: "https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&amp;display=swap"
            family: "Noto Sans SC"
    favicon:
        - src: "/favicon/icon-light.svg"   # 路径相对 /public 目录
          theme: "light"                   # 适用主题：light / dark
          sizes: "any"                     # SVG 矢量图写 any
        - src: "/favicon/icon-dark.svg"
          theme: "dark"
          sizes: "any"
</code></pre>
<h2>主题色：只改一个色相</h2>
<p>整套配色由 <code>site.themeColor.hue</code> 一个数字推导出来，取值 0–360：</p>
<table>
<thead>
<tr>
<th>色相</th>
<th>大致观感</th>
<th>适合的站点气质</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>0</code></td>
<td>红</td>
<td>强烈、有攻击性</td>
</tr>
<tr>
<td><code>30</code></td>
<td>橙</td>
<td>温暖、有食欲</td>
</tr>
<tr>
<td><code>150</code></td>
<td>绿</td>
<td>自然、平静</td>
</tr>
<tr>
<td><code>200</code></td>
<td>青</td>
<td>清爽、偏科技</td>
</tr>
<tr>
<td><code>265</code></td>
<td>蓝紫</td>
<td>冷静、偏理性（本站默认值）</td>
</tr>
<tr>
<td><code>300</code></td>
<td>紫</td>
<td>神秘、偏创作</td>
</tr>
<tr>
<td><code>345</code></td>
<td>粉</td>
<td>柔和、偏生活</td>
</tr>
</tbody>
</table>
<pre><code>site:
    themeColor:
        hue: 265
</code></pre>
<p>明度与饱和度由主题在 CSS 变量层统一处理，浅色与暗色模式都会自动得到可用的对比度，不需要你手调具体颜色值。</p>
<h2>壁纸与首屏</h2>
<table>
<thead>
<tr>
<th>字段</th>
<th>可选值</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>site.wallpaper.mode</code></td>
<td><code>banner</code> / <code>fullscreen</code> / <code>none</code></td>
<td>横幅、全屏壁纸、纯色背景</td>
</tr>
<tr>
<td><code>site.wallpaper.src.desktop</code></td>
<td>路径或数组</td>
<td>桌面壁纸，路径相对 <code>/public</code>；<strong>数组长度大于 1 时自动轮播</strong></td>
</tr>
<tr>
<td><code>site.wallpaper.src.mobile</code></td>
<td>路径或数组</td>
<td>移动端壁纸，规则同上</td>
</tr>
<tr>
<td><code>site.wallpaper.position</code></td>
<td><code>top</code> / <code>center</code> / <code>bottom</code></td>
<td>壁纸的裁切重心</td>
</tr>
<tr>
<td><code>site.wallpaper.carousel.enable</code></td>
<td>布尔</td>
<td>是否轮播；关闭时多图会随机显示一张</td>
</tr>
<tr>
<td><code>site.wallpaper.carousel.interval</code></td>
<td>秒</td>
<td>轮播间隔</td>
</tr>
<tr>
<td><code>site.wallpaper.carousel.kenBurns</code></td>
<td>布尔</td>
<td>缓慢推近的 Ken Burns 效果</td>
</tr>
<tr>
<td><code>site.wallpaper.banner.homeText.enable</code></td>
<td>布尔</td>
<td>是否只在首页显示横幅文字</td>
</tr>
<tr>
<td><code>site.wallpaper.banner.homeText.title</code></td>
<td>字符串</td>
<td>横幅大标题</td>
</tr>
<tr>
<td><code>site.wallpaper.banner.homeText.subtitle</code></td>
<td>字符串或数组</td>
<td>副标题，数组会依次轮播</td>
</tr>
<tr>
<td><code>site.wallpaper.banner.homeText.typewriter.enable</code></td>
<td>布尔</td>
<td>副标题打字机效果（含 <code>speed</code>、<code>deleteSpeed</code>、<code>pauseTime</code>）</td>
</tr>
<tr>
<td><code>site.wallpaper.banner.credit.enable</code></td>
<td>布尔</td>
<td>是否显示横幅图片来源（<code>text</code>、<code>url</code>）</td>
</tr>
<tr>
<td><code>site.wallpaper.banner.navbar.transparentMode</code></td>
<td><code>semi</code> / <code>full</code> / <code>semifull</code></td>
<td>导航栏透明策略：半透明加圆角、完全透明、滚动时动态变化</td>
</tr>
<tr>
<td><code>site.wallpaper.banner.waves.enable</code></td>
<td>布尔</td>
<td>横幅底部水波纹效果；<code>performanceMode</code> 可简化动画以省性能</td>
</tr>
<tr>
<td><code>site.wallpaper.fullscreen.opacity</code></td>
<td>0–1</td>
<td>全屏壁纸模式下内容面板的不透明度</td>
</tr>
<tr>
<td><code>site.wallpaper.fullscreen.blur</code></td>
<td>像素</td>
<td>全屏壁纸的模糊程度</td>
</tr>
<tr>
<td><code>site.wallpaper.fullscreen.zIndex</code></td>
<td>整数</td>
<td>全屏壁纸的层级</td>
</tr>
</tbody>
</table>
<p>首屏加载页由 <code>site.loadingOverlay</code> 控制：<code>enable</code> 开关、<code>waitForFonts</code>（等字体加载）、<code>waitForImages</code>（等首图解码）、<code>maxWait</code>（最长等待秒数），标题与转圈动画各自有 <code>enable</code> 与 <code>interval</code>。</p>
<h2>导航菜单</h2>
<p><code>navbar.links</code> 是一个数组，每一项可以是<strong>预设名</strong>（字符串），也可以是自定义对象。</p>
<p>可用的预设名：<code>Home</code>、<code>Archive</code>、<code>Projects</code>、<code>Skills</code>、<code>Timeline</code>、<code>Diary</code>、<code>Albums</code>、<code>Friends</code>、<code>About</code>（定义在 <code>src/constants/link-presets.ts</code>）。</p>
<pre><code>navbar:
    links:
        - "Home"                 # 一级：预设
        - "Archive"
        - # 一级：自定义，点击后进入中转页
          name: "展览"
          url: "/exhibition/"
          icon: "material-symbols:person"
          description: "收藏我的创作、技能与旅途片段"
          children:              # 子链接可以是预设名，也可以是自定义对象，层级不限
              - "Projects"
              - "Skills"
              - "Timeline"
              - "Diary"
              - "Albums"
        - "Friends"
        - "About"
</code></pre>
<table>
<thead>
<tr>
<th>自定义字段</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>name</code></td>
<td>导航显示名</td>
</tr>
<tr>
<td><code>url</code></td>
<td>目标地址；填 <code>/exhibition/</code> 这类站内地址时会自动生成一个中转页，点击后进入卡片式入口</td>
</tr>
<tr>
<td><code>icon</code></td>
<td>Iconify 图标名，例如 <code>material-symbols:person</code></td>
</tr>
<tr>
<td><code>description</code></td>
<td>中转页卡片上的说明文字</td>
</tr>
<tr>
<td><code>external</code></td>
<td>是否为站外链接（站外会在新标签页打开）</td>
</tr>
<tr>
<td><code>children</code></td>
<td>子链接数组，可继续嵌套</td>
</tr>
</tbody>
</table>
<p>:::caution
预设名写错（多一个空格、大小写不一致、引用了不存在的预设）会在启动时直接抛出 <code>Unknown LinkPreset</code> 并中断构建。改完导航先用 <code>npm run dev</code> 跑一次最保险。
:::</p>
<h2>侧边栏挂件</h2>
<p>侧栏分左右两栏，每一栏都是一个挂件数组。可用类型：</p>
<table>
<thead>
<tr>
<th><code>type</code></th>
<th>作用</th>
<th>常用附加字段</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>directory</code></td>
<td>按内容集合自动生成的目录树</td>
<td><code>position</code></td>
</tr>
<tr>
<td><code>categories</code></td>
<td>文章分类树</td>
<td><code>depth</code>（展开层数）、<code>responsive.collapseThreshold</code></td>
</tr>
<tr>
<td><code>tags</code></td>
<td>标签云</td>
<td><code>responsive.collapseThreshold</code></td>
</tr>
<tr>
<td><code>toc</code></td>
<td>当前文章的目录</td>
<td><code>depth</code>（1–6）</td>
</tr>
<tr>
<td><code>statistics</code></td>
<td>站点统计</td>
<td><code>visibility</code></td>
</tr>
<tr>
<td><code>profile</code></td>
<td>资料卡（内容来自 <code>profile</code> 段）</td>
<td><code>visibility</code></td>
</tr>
<tr>
<td><code>announcement</code></td>
<td>公告卡（内容来自 <code>announcement</code> 段）</td>
<td><code>visibility</code></td>
</tr>
</tbody>
</table>
<p>每一项都支持下面三个通用字段：</p>
<pre><code>sidebar:
    components:
        left:
            - type: "categories"
              position: "sticky"        # top 跟随内容顶部 | sticky 吸顶
              depth: 3                  # 展开到第三层
              responsive:
                  collapseThreshold: 5  # 超过 5 项时默认折叠
        right:
            - type: "profile"
              position: "top"
              visibility:
                  mode: "exclude"       # include 只在匹配页面显示 / exclude 只在非匹配页面显示
                  paths: ["^/posts/", "^/archive"]   # 正则字符串
</code></pre>
<p>:::tip
<code>visibility.paths</code> 里填的是<strong>正则字符串</strong>：<code>^/$</code> 表示只在首页显示，<code>^/posts/</code> 表示所有文章页。想让某个挂件只在文章页出现，用 <code>mode: include</code> 配合 <code>["^/posts/"]</code>。
:::</p>
<h2>资料卡与公告</h2>
<pre><code>profile:
    avatar: "/assets/images/avatar.svg"        # 相对 /public 目录
    name: "younisekai"
    bio: "在异世界的间隙里，写点代码，也写点故事。"
    links:
        - name: "GitHub"
          icon: "fa6-brands:github"
          url: "https://github.com/yourname/younisekai"
        - name: "RSS"
          icon: "fa6-solid:rss"
          url: "/rss.xml"

announcement:
    title: "异世界公告"
    content: "欢迎来到 younisekai，这里记录着我的代码、文字与旅途。"
    closable: true          # 允许访客关闭公告
    link:
        enable: true
        text: "了解更多"
        url: "/about/"
        external: false
</code></pre>
<p>资料卡的社交链接支持站内地址（如 <code>/rss.xml</code>）与站外地址，图标同样是 Iconify 名称。</p>
<h2>文章卡片、许可与评论</h2>
<table>
<thead>
<tr>
<th>字段</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>post.card.cover.side</code></td>
<td>列表卡片里封面的位置：<code>left</code> 或 <code>right</code></td>
</tr>
<tr>
<td><code>post.card.cover.width</code></td>
<td>封面占卡片宽度的比例</td>
</tr>
<tr>
<td><code>post.card.cover.showContent</code></td>
<td>封面上是否叠加标题、标签与摘要</td>
</tr>
<tr>
<td><code>post.card.cover.showDefaultCover</code></td>
<td>文章没写封面时是否显示默认封面</td>
</tr>
<tr>
<td><code>post.card.titleSize</code></td>
<td>卡片标题字号，Tailwind 文本类，例如 <code>text-2xl</code></td>
</tr>
<tr>
<td><code>post.showLastModified</code></td>
<td>是否显示「最后编辑」卡片</td>
</tr>
<tr>
<td><code>post.expressiveCode.theme</code></td>
<td>代码高亮主题</td>
</tr>
<tr>
<td><code>post.license.enable</code></td>
<td>是否在文章底部显示版权卡片</td>
</tr>
<tr>
<td><code>post.license.name</code> / <code>url</code></td>
<td>全站默认许可协议，可被文章的 <code>licenseName</code> / <code>licenseUrl</code> 覆盖</td>
</tr>
<tr>
<td><code>post.comment.enable</code></td>
<td>评论总开关</td>
</tr>
<tr>
<td><code>post.comment.provider</code></td>
<td><code>waline</code> 或 <code>twikoo</code>；留空则自动选择已填好必填项的那个</td>
</tr>
<tr>
<td><code>post.comment.waline.serverURL</code></td>
<td>Waline 服务端地址</td>
</tr>
<tr>
<td><code>post.comment.twikoo.envId</code></td>
<td>Twikoo 环境 ID</td>
</tr>
</tbody>
</table>
<p>:::note
评论是唯一依赖外部服务的功能：要么把服务端地址与 <code>enable</code> 一起配好，要么保持关闭。关闭时页面上不会留下任何占位区域。
:::</p>
<h2>其它开关</h2>
<table>
<thead>
<tr>
<th>配置段</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>footer.enable</code></td>
<td>是否注入自定义页脚 HTML（备案号之类），内容写在 <code>footer.customHtml</code></td>
</tr>
<tr>
<td><code>particle.enable</code></td>
<td>背景粒子：<code>particleNum</code> 数量、<code>size</code> / <code>opacity</code> / <code>speed</code> 的取值范围、<code>limitTimes</code> 越界次数、<code>zIndex</code> 层级</td>
</tr>
</tbody>
</table>
<h2>改配置的推荐流程</h2>
<ol>
<li><strong>只改 YAML，不动 <code>src/</code> 里的源码</strong>——升级模板时会轻松很多。</li>
<li>改完先 <code>npm run dev</code> 看效果，再 <code>npm run build</code> 验证一次构建。</li>
<li>站点地址、站点标题、导航、侧栏这四项是「先定下来」的配置项，越早改越省事。</li>
<li>用 Git 记录每次配置改动，出问题能一键回退。</li>
</ol>
<p>配置之外的东西（新增挂件类型、改配色算法、加新页面）属于源码级定制，建议单独开分支，并在提交信息里写清动机，方便日后与上游对照。</p>
]]></content>
            <author>
                <name>younisekai</name>
            </author>
            <category term="主题定制"></category>
            <category term="主题定制" label="主题定制"></category>
            <category term="站点配置" label="站点配置"></category>
            </entry>
        <entry>
            <title>图表与数学公式：Mermaid 与 KaTeX</title>
            <link href="https://kazeyouni.pages.dev/posts/diagrams-and-math/" rel="alternate" type="text/html"/>
            <id>https://kazeyouni.pages.dev/posts/diagrams-and-math/</id>
            <published>2026-05-11T00:00:00.000Z</published>
            <updated>2026-05-11T00:00:00.000Z</updated>
            <summary>用 Mermaid 画流程图、时序图、状态图、甘特图和饼图，用 KaTeX 写行内与独立公式，全部是可复制的纯文本写法。</summary>
            <content type="html"><![CDATA[<p>图表和公式是「技术文章值得写下来」的重要原因之一：一张图能替掉三段解释，一个公式能让「大概是这样」变成「就是这样」。</p>
<h2>为什么用文本画图</h2>
<p>Mermaid 的输入是纯文本，输出由浏览器现场渲染。好处很实际：</p>
<ul>
<li>图可以进 Git，改动可 diff、可回溯；</li>
<li>换主题、换配色不用重画；</li>
<li>不用打开任何绘图软件，写文章时顺手就画了。</li>
</ul>
<p>写法就是在代码块上标注 <code>mermaid</code>：</p>
<pre><code>```mermaid
graph LR
    A[写作] --&gt; B[构建]
```
</code></pre>
<h2>流程图</h2>
<p>从写作到上线，这条链路值得画出来——它解释了为什么「本地能跑」和「线上能跑」是两件事。</p>
<pre><code>graph TD
    A[写 Markdown] --&gt; B{frontmatter 是否合法}
    B --&gt;|否| C[构建报错并指出字段]
    B --&gt;|是| D[渲染正文与扩展语法]
    D --&gt; E[生成页面与数据集合页]
    E --&gt; F[生成 Pagefind 搜索索引]
    F --&gt; G{是否生产构建}
    G --&gt;|是| H[跳过 draft 文章]
    G --&gt;|否| I[草稿也一起预览]
    H --&gt; J[输出 dist 目录]
    I --&gt; J
    J --&gt; K[部署到静态托管]
</code></pre>
<p>:::tip
节点文字里出现 <code>{}</code> <code>[]</code> <code>()</code> 时要用引号包起来，例如 <code>A["带括号的 (文字)"]</code>，否则 Mermaid 会把括号当成形状语法。
:::</p>
<h2>时序图</h2>
<p>带交互的功能适合用时序图说清楚「谁在什么时候做了什么」。以访问一篇加密文章为例：</p>
<pre><code>sequenceDiagram
    participant U as 读者
    participant B as 浏览器
    participant S as 静态页面

    U-&gt;&gt;B: 打开文章地址
    B-&gt;&gt;S: 请求页面
    S--&gt;&gt;B: 返回加密后的正文（密文）
    B--&gt;&gt;U: 显示密码输入框
    U-&gt;&gt;B: 输入密码并解锁
    B-&gt;&gt;B: 本地 AES 解密
    alt 密码正确
        B--&gt;&gt;U: 展开正文并渲染图表
    else 密码错误
        B--&gt;&gt;U: 提示「密码不正确」
    end
</code></pre>
<p>关键结论写在图里了：<strong>解密发生在浏览器本地</strong>，服务端从头到尾只发密文。</p>
<h2>状态图</h2>
<p>文章的「一生」其实是个状态机，用状态图画出来，<code>draft</code> 与 <code>updated</code> 的作用就一目了然：</p>
<pre><code>stateDiagram-v2
    [*] --&gt; Draft
    state "草稿" as Draft
    state "待发布" as Ready
    state "已发布" as Published
    state "已更新" as Updated
    Draft --&gt; Ready : 补全 frontmatter
    Ready --&gt; Published : draft 改为 false
    Published --&gt; Updated : 修改正文并更新 updated
    Updated --&gt; Published : 重新构建
    Published --&gt; Draft : 撤回修改
    Published --&gt; [*] : 删除文件
</code></pre>
<h2>甘特图</h2>
<p>给内容排期的时候，甘特图比待办清单直观：</p>
<pre><code>gantt
    title 站点内容建设排期
    dateFormat YYYY-MM-DD
    axisFormat %m/%d

    section 基础设施
    目录与数据集合           :done, a1, 2026-03-01, 10d
    搜索索引接入             :done, a2, after a1, 6d

    section 内容
    示例文章与写作指南       :active, b1, 2026-03-12, 20d
    图表与公式专题           :b2, after b1, 8d
    长文示例与归档整理       :b3, after b2, 10d

    section 收尾
    上线前检查               :c1, after b3, 5d
    正式上线                 :milestone, after c1, 0d
</code></pre>
<h2>饼图</h2>
<p>数据占比这种一眼就懂的东西，交给饼图：</p>
<pre><code>pie title 文章分类占比（示例数据）
    "内容创作" : 4
    "主题定制" : 3
    "入门指南" : 1
    "站点公告" : 1
    "技术笔记" : 1
</code></pre>
<p>可以放心的是，Mermaid 支持的类型远不止这五种，类图、实体关系图、思维导图、时间线都能写，语法都是同一套「文字描述结构」的思路。</p>
<h2>数学公式</h2>
<p>行内公式写在单个美元符号之间，例如质能方程 $E = mc^2$、求根公式 $x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}$，或者站点里更常见的阅读时长估算 $t = \lceil w / 300 \rceil$。</p>
<p>独立成行的公式用两个美元符号，会居中并单独占一行：</p>
<p>$$
\text{阅读时长（分钟）} = \left\lceil \frac{\text{正文字数}}{300} \right\rceil
$$</p>
<h3>多行对齐</h3>
<p>用 <code>aligned</code> 环境可以让等号对齐，推导过程会好读很多：</p>
<p>$$
\begin{aligned}
S(n) &amp;= \sum_{i=1}^{n} i \
&amp;= \frac{n(n+1)}{2} \
&amp;= \frac{n^2 + n}{2}
\end{aligned}
$$</p>
<h3>矩阵与分段函数</h3>
<p>$$
A = \begin{pmatrix}
a_{11} &amp; a_{12} \
a_{21} &amp; a_{22}
\end{pmatrix}
\qquad
f(x) =
\begin{cases}
x^2, &amp; x \ge 0 \
-x,  &amp; x &lt; 0
\end{cases}
$$</p>
<h3>常用符号速查</h3>
<table>
<thead>
<tr>
<th>想要的效果</th>
<th>写法</th>
</tr>
</thead>
<tbody>
<tr>
<td>分式</td>
<td><code>\frac{a}{b}</code></td>
</tr>
<tr>
<td>求和、连乘</td>
<td><code>\sum_{i=1}^{n}</code>、<code>\prod_{i=1}^{n}</code></td>
</tr>
<tr>
<td>积分</td>
<td><code>\int_{0}^{\infty} e^{-x}\,dx</code></td>
</tr>
<tr>
<td>极限</td>
<td><code>\lim_{x \to 0}</code></td>
</tr>
<tr>
<td>希腊字母</td>
<td><code>\alpha \beta \gamma \Delta \Omega</code></td>
</tr>
<tr>
<td>上下标</td>
<td><code>x_i^2</code>、<code>a_{n+1}</code></td>
</tr>
<tr>
<td>向量与范数</td>
<td><code>\vec{v}</code>、<code>\lVert v \rVert</code></td>
</tr>
<tr>
<td>文本混排</td>
<td><code>\text{说明文字}</code></td>
</tr>
</tbody>
</table>
<p>例如把上面几个符号拼起来：</p>
<p>$$
\lim_{n \to \infty} \prod_{k=1}^{n} \left(1 + \frac{1}{k}\right)^{k} \quad \text{与} \quad \int_{0}^{1} x^{\alpha-1}(1-x)^{\beta-1},dx = B(\alpha, \beta)
$$</p>
<h3>几个容易踩的坑</h3>
<p>:::warning
<strong>美元符号会被当成公式定界符。</strong> 想写出「价格 100 美元」这种句子时，请写 <code>100 美元</code> 而不是 <code>100$</code>，否则后面的文字会被解析成公式。
:::</p>
<p>:::note
公式里的反斜杠在 Markdown 中是转义字符，命令行之类的场合请放到行内代码里（<code>`\frac`</code>），不要直接裸写。
:::</p>
<h2>组合建议</h2>
<ol>
<li><strong>一篇文章最多两三张图</strong>，超过就说明结构需要重新组织。</li>
<li><strong>公式能简就简</strong>：能一句话说清的结论，不必写成三重积分。</li>
<li><strong>图表要给标题或上下文</strong>，脱离正文的图对读者和搜索引擎都不友好。</li>
<li>图和公式都是纯文本，<strong>改动时优先改文字</strong>，别去调整间距——渲染效果由主题统一控制。</li>
</ol>
]]></content>
            <author>
                <name>younisekai</name>
            </author>
            <category term="内容创作"></category>
            <category term="Mermaid" label="Mermaid"></category>
            <category term="KaTeX" label="KaTeX"></category>
            <category term="排版" label="排版"></category>
            </entry>
        <entry>
            <title>排版速查表：Markdown 能写成什么样</title>
            <link href="https://kazeyouni.pages.dev/posts/markdown-showcase/" rel="alternate" type="text/html"/>
            <id>https://kazeyouni.pages.dev/posts/markdown-showcase/</id>
            <published>2026-04-06T00:00:00.000Z</published>
            <updated>2026-04-28T00:00:00.000Z</updated>
            <summary>把标题、列表、引用、表格、代码块、脚注、图片和本站扩展语法全部演示一遍，写文章时可以直接照着抄。</summary>
            <content type="html"><![CDATA[<p>这一页是写给自己的备忘录：需要哪种排版，直接从这里复制写法。文中所有示例都是<strong>真实渲染</strong>的，不是截图。</p>
<h2>标题层级</h2>
<p>正文用 <code>##</code> 起步比较合适——文章标题本身已经占掉了最高一级。</p>
<h3>三级标题</h3>
<h4>四级标题</h4>
<p>标题会自动生成锚点，鼠标悬停时右侧出现 <code>#</code>，侧栏目录也会按层级同步展开。</p>
<h2>文本与强调</h2>
<p><strong>加粗</strong>、<em>斜体</em>、<s>删除线</s>、<code>行内代码</code>、<a href="https://astro.build/">普通链接</a>、上标式写法如 H~2~O 与 x^2^（部分语法取决于渲染器，不保证全部生效）。</p>
<p>需要打断行时，行尾两个空格即可，例如这一行结尾有两个空格，<br />
所以这一句出现在了新的一行。</p>
<h2>列表</h2>
<p>无序列表：</p>
<ul>
<li>第一项</li>
<li>第二项
<ul>
<li>嵌套一层</li>
<li>再嵌套一层</li>
</ul>
</li>
<li>第三项</li>
</ul>
<p>有序列表：</p>
<ol>
<li>收集素材</li>
<li>写出草稿</li>
<li>通读一遍并删掉三分之一</li>
</ol>
<p>任务列表（GitHub 风格）：</p>
<ul>
<li>[x] 建好目录结构</li>
<li>[x] 接入搜索</li>
<li>[ ] 补齐示例内容</li>
<li>[ ] 换个自己的域名</li>
</ul>
<h2>引用</h2>
<blockquote>
<p>引用适合放别人的观点、规范原文或需要特别强调的一段话。</p>
<p>引用内部同样支持<strong>行内样式</strong>与列表：</p>
<ul>
<li>一个要点</li>
<li>另一个要点</li>
</ul>
</blockquote>
<h2>表格</h2>
<table>
<thead>
<tr>
<th>列名</th>
<th>类型</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>title</code></td>
<td>字符串</td>
<td>左对齐是默认值</td>
</tr>
<tr>
<td><code>pinned</code></td>
<td>布尔</td>
<td>居中对齐</td>
</tr>
<tr>
<td><code>published</code></td>
<td>日期</td>
<td>右对齐</td>
</tr>
</tbody>
</table>
<p>表格支持对齐语法（<code>:---</code>、<code>:---:</code>、<code>---:</code>），单元格里可以用行内代码与链接。</p>
<h2>代码块</h2>
<h3>多语言示例</h3>
<pre><code>// JavaScript：把文章按发布时间倒序排列
export function sortByPublished(posts) {
    return [...posts].sort((a, b) =&gt; new Date(b.published) - new Date(a.published));
}
</code></pre>
<pre><code>// TypeScript：给内容集合的字段加上类型
interface PostFrontmatter {
    title: string;
    published: Date;
    tags: string[];
    pinned?: boolean;
}

export function isPinned(post: PostFrontmatter): boolean {
    return post.pinned === true;
}
</code></pre>
<pre><code># Python：粗略统计每个标签出现的次数
from collections import Counter

def tag_cloud(posts):
    counter = Counter(tag for post in posts for tag in post["tags"])
    return counter.most_common(10)
</code></pre>
<pre><code>{
    "title": "示例条目",
    "tags": ["Markdown", "排版"],
    "visible": true
}
</code></pre>
<pre><code>/* CSS：给引用块加一条左侧色带 */
blockquote {
    border-inline-start: 3px solid var(--primary);
    padding-inline-start: 1rem;
}
</code></pre>
<pre><code># 构建并在本地预览产物
npm run build
npm run preview
</code></pre>
<h3>标题、行号与文本高亮</h3>
<p>给代码块加上 <code>title</code> 会显示文件名栏，<code>showLineNumbers=true</code> 打开行号，<code>ins</code> / <code>del</code> / <code>mark</code> 可以标记新增、删除与重点行：</p>
<pre><code>```js title="sort-posts.js" showLineNumbers=true ins={4} del={2} mark={6}
```
</code></pre>
<p>渲染效果如下：</p>
<pre><code>function sortPosts(posts) {
    return posts.sort((a, b) =&gt; a.date - b.date);
    const items = [...posts];
    return items.sort((a, b) =&gt; new Date(b.published) - new Date(a.published));
    // 下面这一行是这次改动的重点
    console.log("排序完成", items.length);
}
</code></pre>
<h3>可折叠代码段</h3>
<p>内容很长的代码块可以折叠起来，读者点一下「展开」再看细节。语法是在代码块信息串里写 <code>collapse={起始行-结束行}</code>：</p>
<pre><code>```ts collapse={2-6}
```
</code></pre>
<p>实际效果：</p>
<pre><code>export const siteMeta = {
    name: "younisekai",
    version: "1.0.0",
    generator: "Astro",
    search: "pagefind",
    comments: false,
};

export function describe() {
    return `${siteMeta.name} v${siteMeta.version}`;
}
</code></pre>
<h2>脚注</h2>
<p>脚注适合放补充说明和引用出处<a href="%E8%BF%99%E6%98%AF%E4%B8%80%E4%B8%AA%E8%84%9A%E6%B3%A8%EF%BC%8C%E7%82%B9%E5%8F%B3%E4%B8%8A%E8%A7%92%E7%9A%84%E5%BA%8F%E5%8F%B7%E5%8F%AF%E4%BB%A5%E8%B7%B3%E5%9B%9E%E6%AD%A3%E6%96%87%E3%80%82">^1</a>。定义可以写在文章任意位置，渲染时会自动收到文末[^docs]。</p>
<p>[^docs]: 官方文档通常是最靠得住的参考资料，例如 <a href="https://docs.astro.build/">Astro 文档</a>。</p>
<h2>链接与图片</h2>
<p>行内链接：<a href="https://docs.astro.build/">Astro 官方文档</a>、<a href="https://pagefind.app/">Pagefind 搜索</a>。</p>
<p>图片可以直接引用 <code>public</code> 目录下的占位图：</p>
<p><img src="https://kazeyouni.pages.dev/assets/images/placeholders/p-01.svg" alt="一张渐变占位图" /></p>
<p>也可以引用与文章放在一起的图片，写法是 <code>./路径</code>（相对于当前 Markdown 文件）：</p>
<p><img src="https://kazeyouni.pages.dev/_astro/relative-image-demo.BXGg3a8x_IKsGD.webp" alt="与文章放在一起的示例图" /></p>
<p>:::tip
相对路径（<code>./xxx.png</code>）适合会跟着文章一起搬家的素材；<code>public</code> 目录下的绝对路径（<code>/assets/...</code>）适合全站复用的封面与头像。注意相对路径引用的是图片<strong>文件本身</strong>，构建时会被一并处理；一旦路径写错，构建会直接报 <code>ImageNotFound</code> 而不是静默跳过。
:::</p>
<h2>GitHub 仓库卡片</h2>
<p>写一行指令就能得到一张会实时拉取数据的仓库卡片：</p>
<p>::github{repo="withastro/astro"}</p>
<pre><code>::github{repo="withastro/astro"}
</code></pre>
<p>:::note
卡片里的 star 数、协议、语言等信息由访客浏览器现场请求 GitHub API 获取，所以第一次打开时会短暂显示「Waiting...」。仓库名必须写成 <code>作者/仓库</code> 的格式。
:::</p>
<h2>提示框</h2>
<h3>五种类型</h3>
<p>:::note
note：需要读者留意、但即使跳读也不该错过的信息。
:::</p>
<p>:::tip
tip：能让人少走弯路的可选建议。
:::</p>
<p>:::important
important：完成某件事所必需的关键信息。
:::</p>
<p>:::warning
warning：存在风险、需要立刻注意的内容。
:::</p>
<p>:::caution
caution：某个操作可能带来的负面后果。
:::</p>
<p>如上所示，写法是三个冒号包住一段内容：</p>
<pre><code>:::note
需要读者留意的信息。
:::
</code></pre>
<h3>自定义标题</h3>
<p>在类型后面接方括号即可覆盖默认标题：</p>
<p>:::note[这里的标题是我自己写的]
标题支持中英文与行内样式，正文部分照常支持 Markdown。
:::</p>
<pre><code>:::note[这里的标题是我自己写的]
标题支持中英文与行内样式。
:::
</code></pre>
<h3>GitHub 风格提示框</h3>
<p>习惯 GitHub 写法的话，用引用块加类型标记也可以：</p>
<blockquote>
<p>[!TIP]
这种写法在 GitHub 上能正确渲染，在本站同样会被识别成提示框。</p>
</blockquote>
<pre><code>&gt; [!TIP]
&gt; 这种写法在本站同样会被识别成提示框。
</code></pre>
<h3>隐藏文本</h3>
<p>需要防剧透或者放一点小彩蛋时，用行内指令：</p>
<p>这段内容的结论是 :spoiler[其实答案就在下一段，但我先藏起来了]。</p>
<pre><code>这段内容的结论是 :spoiler[其实答案就在这里]。
</code></pre>
<h2>组合使用的建议</h2>
<ol>
<li><strong>少即是多</strong>：一篇文章里提示框超过五个，读者就会开始忽略它们。</li>
<li><strong>代码块给语言名</strong>：<code>bash</code>、<code>json</code>、<code>ts</code> 这些标记不仅有配色，也让复制按钮与语言角标更准确。</li>
<li><strong>长代码折叠</strong>：超过 20 行的示例，用 <code>collapse</code> 折叠，把注意力留给结论。</li>
<li><strong>图片要写替代文本</strong>：方括号里的描述对无障碍阅读与 RSS 都很重要，别留空。</li>
</ol>
<p>语法速查就到这里。图表与公式见<a href="/posts/diagrams-and-math/">图表与数学公式</a>。</p>
]]></content>
            <author>
                <name>younisekai</name>
            </author>
            <category term="内容创作"></category>
            <category term="Markdown" label="Markdown"></category>
            <category term="排版" label="排版"></category>
            </entry>
        <entry>
            <title>写作指南：frontmatter 全解析</title>
            <link href="https://kazeyouni.pages.dev/posts/guide/writing-guide/" rel="alternate" type="text/html"/>
            <id>https://kazeyouni.pages.dev/posts/guide/writing-guide/</id>
            <published>2026-03-18T00:00:00.000Z</published>
            <updated>2026-05-06T00:00:00.000Z</updated>
            <summary>逐项解释文章开头的每个 frontmatter 字段，并说清草稿、分类、标签、封面与复制保护到底是怎么生效的。</summary>
            <content type="html"><![CDATA[<p>一篇文章的开头两行 <code>---</code> 之间，就是 frontmatter：它不参与正文排版，却决定了文章出现在哪里、长什么样、给谁看。</p>
<h2>一篇文章的最小结构</h2>
<pre><code>---
title: 我的第一篇笔记
published: 2026-03-18
description: 一句话说清这篇写的是什么。
---

正文从这里开始。
</code></pre>
<p>只写这三个字段就能发布。其余字段按需添加，全部缺省值都是「不启用」。</p>
<h2>字段总览</h2>
<table>
<thead>
<tr>
<th>字段</th>
<th>类型</th>
<th>默认值</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>title</code></td>
<td>字符串</td>
<td>必填</td>
<td>文章标题，同时用于列表页、RSS 与浏览器标题</td>
</tr>
<tr>
<td><code>directoryTitle</code></td>
<td>字符串</td>
<td>空</td>
<td>侧栏目录树中显示的名字；留空时回退为 <code>title</code></td>
</tr>
<tr>
<td><code>published</code></td>
<td>日期</td>
<td>空</td>
<td>发布日期，用于排序；留空时回退到文件的 Git 首次提交时间</td>
</tr>
<tr>
<td><code>updated</code></td>
<td>日期</td>
<td>空</td>
<td>更新日期；填写后会显示「上次编辑」卡片与文章底部的差异信息</td>
</tr>
<tr>
<td><code>description</code></td>
<td>字符串</td>
<td>空</td>
<td>摘要，显示在列表卡片、分享卡片与搜索结果里</td>
</tr>
<tr>
<td><code>cover</code></td>
<td>字符串</td>
<td>空</td>
<td>封面图，见下方「封面怎么填」</td>
</tr>
<tr>
<td><code>coverInContent</code></td>
<td>布尔</td>
<td><code>false</code></td>
<td>是否在正文顶部再渲染一次封面大图</td>
</tr>
<tr>
<td><code>category</code></td>
<td>字符串或数组</td>
<td>空</td>
<td>分类，数组即嵌套分类</td>
</tr>
<tr>
<td><code>tags</code></td>
<td>数组或逗号分隔字符串</td>
<td>空</td>
<td>标签，用于标签云与归档筛选</td>
</tr>
<tr>
<td><code>lang</code></td>
<td>字符串</td>
<td>空</td>
<td>内容语言，例如 <code>zh_CN</code>；用于 RSS 与 HTML 的语言标记</td>
</tr>
<tr>
<td><code>pinned</code></td>
<td>布尔</td>
<td><code>false</code></td>
<td>是否置顶到文章列表最前</td>
</tr>
<tr>
<td><code>author</code></td>
<td>字符串</td>
<td>空</td>
<td>作者名，显示在版权卡片里；留空则用站点资料页的名字</td>
</tr>
<tr>
<td><code>sourceLink</code></td>
<td>字符串</td>
<td>空</td>
<td>原文或来源链接，填写后版权卡片会指向它</td>
</tr>
<tr>
<td><code>licenseName</code></td>
<td>字符串</td>
<td>空</td>
<td>本篇文章的许可协议名称，例如 <code>CC BY 4.0</code></td>
</tr>
<tr>
<td><code>licenseUrl</code></td>
<td>字符串</td>
<td>空</td>
<td>许可协议链接</td>
</tr>
<tr>
<td><code>comment</code></td>
<td>布尔</td>
<td><code>true</code></td>
<td>这篇文章是否允许评论；实际是否显示还取决于站点配置里的评论总开关</td>
</tr>
<tr>
<td><code>draft</code></td>
<td>布尔</td>
<td><code>false</code></td>
<td>草稿标记，生产构建会跳过草稿</td>
</tr>
<tr>
<td><code>encrypted</code></td>
<td>布尔</td>
<td><code>false</code></td>
<td>是否对正文加密</td>
</tr>
<tr>
<td><code>password</code></td>
<td>字符串</td>
<td>空</td>
<td>解锁密码，与 <code>encrypted: true</code> 配对使用</td>
</tr>
<tr>
<td><code>copyProtection</code></td>
<td>对象</td>
<td>全部 <code>false</code></td>
<td>页面级复制保护开关，见下文</td>
</tr>
<tr>
<td><code>routeName</code></td>
<td>字符串</td>
<td>空</td>
<td>额外的自定义地址（不替换默认地址）</td>
</tr>
</tbody>
</table>
<p>:::tip
<code>prevTitle</code>、<code>prevSlug</code>、<code>nextTitle</code>、<code>nextSlug</code> 这四个字段由构建过程自动填充，用来生成文章底部的上一篇/下一篇跳转，<strong>不要在 frontmatter 里手写</strong>。
:::</p>
<h2>日期怎么写</h2>
<p>推荐 ISO 8601 的 <code>YYYY-MM-DD</code>，需要精确到时间就写 <code>YYYY-MM-DDTHH:mm:ss</code>。<code>published</code> 决定排序，<code>updated</code> 只影响「最后修改于多久之前」的展示：</p>
<pre><code>published: 2026-03-18
updated: 2026-05-06
</code></pre>
<p>:::note
没有写 <code>published</code> 时，构建会依次尝试读取该文件的 Git 首次提交时间、文件创建时间。为了排序稳定，公开发布的文章建议都显式写上日期。
:::</p>
<h2>分类与标签</h2>
<p>分类是「树」，标签是「云」，两者用途不同：分类回答「这篇属于哪个板块」，标签回答「这篇讲到了哪些点」。</p>
<pre><code># 单层分类
category: 内容创作

# 嵌套分类：写在数组里，用「 / 」连接展示
category: [技术笔记, 前端]

# 标签可以写成数组
tags: [Markdown, 排版]

# 也可以写成逗号分隔的字符串，效果相同
tags: Markdown, 排版
</code></pre>
<p>嵌套分类在本站的表现：侧栏的「文章类别」会按层级展开，点击后跳转到归档页的筛选结果，地址形如 <code>/archive/?category=技术笔记%20%2F%20前端</code>。也就是说嵌套分类是<strong>整体匹配</strong>的，点「技术笔记 / 前端」只会筛出同时属于这两层的文章。</p>
<h2>封面怎么填</h2>
<p><code>cover</code> 支持三种写法，按前缀区分：</p>
<pre><code>cover: /assets/images/covers/cover-01.svg   # 以 / 开头：public 目录下的文件
cover: ./cover.svg                          # 以 ./ 开头：与文章同目录的文件
cover: https://example.com/cover.png        # 以 http 开头：网络图片
</code></pre>
<p>至于 <code>coverInContent</code>：</p>
<ul>
<li><code>coverInContent: false</code>（默认）：封面只出现在<strong>列表卡片</strong>上，正文里不会再出现一次大图。</li>
<li><code>coverInContent: true</code>：封面额外渲染在<strong>正文标题与元信息之下</strong>，形成「题图」效果，适合长文。</li>
</ul>
<p>两者可以同时给，也可以只给 <code>cover</code>，甚至完全不给——没封面时列表卡片会使用默认封面。</p>
<h2>草稿机制</h2>
<pre><code>draft: true
</code></pre>
<ul>
<li>开发模式下草稿<strong>照常显示</strong>，方便你边写边预览。</li>
<li>生产构建（<code>npm run build</code>）会跳过所有草稿，RSS、站点地图、搜索结果与侧栏目录树里都不会出现它们。</li>
<li>想让人在看得到、但暂时不想进列表，可以用 <code>pinned</code> + 不写 <code>description</code> 之类的组合，不过那属于权宜之计，正式做法仍是草稿开关。</li>
</ul>
<h2>加密与复制保护</h2>
<p>正文加密只需要两个字段：</p>
<pre><code>encrypted: true
password: "younisekai"
</code></pre>
<p>构建时会把渲染好的正文用 AES 加密后写进页面，只有在访客输入正确密码时才会在浏览器端解密展开。演示与注意事项见<a href="/posts/encrypted-note/">给文章加一把锁</a>。</p>
<p>复制保护是四个互不影响的小开关：</p>
<pre><code>copyProtection:
    blockSelection: true      # 禁止选中文本
    blockClipboard: true      # 拦截复制、剪切、粘贴
    blockContextMenu: true    # 禁止右键菜单
    blockDevTools: true       # 屏蔽 F12、Ctrl+U、Ctrl+S
</code></pre>
<p>四项默认都是 <code>false</code>，按需开启；每一项的具体行为见<a href="/posts/copy-protection/">复制保护四种开关</a>。</p>
<h2>作者、来源与许可</h2>
<pre><code>author: 游音
sourceLink: https://example.com/original
licenseName: CC BY 4.0
licenseUrl: https://creativecommons.org/licenses/by/4.0/
</code></pre>
<p>版权卡片会优先使用这里填的值，<code>licenseName</code> 留空时则回落到站点配置里的默认协议。转载、翻译或引用他人内容时，把 <code>sourceLink</code> 填上是最基本的礼貌。</p>
<h2>被忽略的文件</h2>
<p><code>src/content/posts/</code> 下<strong>以 <code>_</code> 开头的文件不会生成页面</strong>，可以放心用来放模板、素材说明和草稿片段。本仓库里的 <code>_frontmatter-template.md</code> 就是这么一份可直接复制的模板。</p>
]]></content>
            <author>
                <name>younisekai</name>
            </author>
            <category term="内容创作"></category>
            <category term="Markdown" label="Markdown"></category>
            <category term="写作技巧" label="写作技巧"></category>
            <category term="排版" label="排版"></category>
            </entry>
        <entry>
            <title>快速上手：从本地到线上</title>
            <link href="https://kazeyouni.pages.dev/posts/start/" rel="alternate" type="text/html"/>
            <id>https://kazeyouni.pages.dev/posts/start/</id>
            <published>2026-03-09T00:00:00.000Z</published>
            <updated>2026-04-02T00:00:00.000Z</updated>
            <summary>环境准备、本地开发、新建内容、构建与部署的完整流程，附常用脚本命令速查与上线前检查清单。</summary>
            <content type="html"><![CDATA[<p>这一篇只讲「怎么把它跑起来并发布出去」。所有命令都在项目根目录执行。</p>
<h2>环境准备</h2>
<table>
<thead>
<tr>
<th>依赖</th>
<th>版本要求</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>Node.js</td>
<td><code>&gt;= 20.3.0</code></td>
<td>构建、脚本与 Astro 都依赖它，版本写在 <code>package.json</code> 的 <code>engines</code> 里</td>
</tr>
<tr>
<td>包管理器</td>
<td>npm / pnpm / yarn 任选</td>
<td>仓库内附带 <code>package-lock.json</code>，用 npm 最省事</td>
</tr>
<tr>
<td>Git</td>
<td>任意较新版本</td>
<td>文章的发布日期在缺省时会回退到文件的 Git 首次提交时间</td>
</tr>
</tbody>
</table>
<p>:::note
搜索索引、图片处理这些都跑在本地构建阶段，不需要额外安装数据库或服务端环境。真正需要联网的只有两处：Mermaid 图表（运行时从 CDN 取库）与 GitHub 卡片（运行时请求 GitHub API）。
:::</p>
<h2>安装与启动</h2>
<pre><code># 1. 安装依赖
npm install

# 2. 启动本地开发服务器（默认 http://localhost:4321/）
npm run dev
</code></pre>
<p>开发服务器带热更新：改 Markdown、改 JSON、改样式，浏览器里会立刻反映出来。文章出现在 <code>/posts/&lt;文件名&gt;/</code>，数据集合分别对应 <code>/projects/</code>、<code>/skills/</code>、<code>/timeline/</code>、<code>/diary/</code>、<code>/albums/</code>。</p>
<h2>常用脚本速查</h2>
<table>
<thead>
<tr>
<th>命令</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>npm run dev</code></td>
<td>生成图标数据后启动开发服务器</td>
</tr>
<tr>
<td><code>npm run build</code></td>
<td>先生成图标数据，再执行 Astro 构建并调用 Pagefind 生成搜索索引</td>
</tr>
<tr>
<td><code>npm run preview</code></td>
<td>本地预览构建产物（建议每次发版前跑一遍）</td>
</tr>
<tr>
<td><code>npm run check</code></td>
<td>Astro 官方检查：内容集合 schema、组件引用、类型问题</td>
</tr>
<tr>
<td><code>npm run type-check</code></td>
<td>只跑 TypeScript 类型检查，不产出文件</td>
</tr>
<tr>
<td><code>npm run new-post -- "文章标题"</code></td>
<td>按 schema 生成一篇带完整 frontmatter 的空文章</td>
</tr>
<tr>
<td><code>npm run assets</code></td>
<td>重新生成资源配置相关的产物</td>
</tr>
<tr>
<td><code>npm run check-stylus</code></td>
<td>检查内联 Stylus 样式能否正常编译</td>
</tr>
</tbody>
</table>
<h2>用脚本新建一篇文章</h2>
<p>手写 frontmatter 容易漏字段，所以仓库里准备了一个小脚本：</p>
<pre><code># 在 posts 根目录建一篇文章
npm run new-post -- "我的第一篇笔记"

# 放在子目录里（目录不存在会自动创建）
npm run new-post -- "guide/写作速查"

# 顺便把标签也写好
npm run new-post -- "读书笔记" --tags 读书,随笔

# 也可以显式指定扩展名
npm run new-post -- "随笔.mdx"
</code></pre>
<p>脚本会把文件写到 <code>src/content/posts/</code> 下，并填好与内容集合 schema 一致的全部字段，接着你只要改内容即可。</p>
<h2>目录与命名约定</h2>
<ul>
<li><strong>文章</strong>：<code>src/content/posts/</code> 下的 <code>.md</code> 或 <code>.mdx</code> 文件，<strong>文件名就是网址</strong>。放在子目录里网址也会带上子目录，例如本文件位于 <code>posts/guide/getting-started.md</code>，默认地址是 <code>/posts/guide/getting-started/</code>。</li>
<li><strong>数据</strong>：项目、技能、历程、日记、相册、友链各占一个目录，<strong>文件名就是条目的 id</strong>，请保持唯一。</li>
<li><strong>以 <code>_</code> 开头的文件会被忽略</strong>：<code>src/content/posts/_草稿模板.md</code> 不会生成页面，适合放模板、片段和素材说明。</li>
<li>目录名与文件名建议用英文小写加连字符，避免网址里出现需要转义的中文。</li>
</ul>
<h3>侧栏目录树里的显示名</h3>
<p>侧栏目录树会用两种方式取名：<strong>目录节点</strong>直接用目录名，<strong>文章节点</strong>则优先用 frontmatter 里的 <code>directoryTitle</code>，没写就回退到 <code>title</code>。所以「目录名保持英文、显示名交给 <code>directoryTitle</code>」是推荐做法：</p>
<pre><code>---
title: 快速上手：从本地到线上
directoryTitle: 快速上手
---
</code></pre>
<h3>自定义文章地址</h3>
<p>给文章加上 <code>routeName</code> 之后，它在默认地址之外<strong>额外</strong>多出一个更短的地址：</p>
<pre><code>---
title: 快速上手：从本地到线上
routeName: "start"
---
</code></pre>
<p>上面这段配置会让这篇同时可以通过 <code>/posts/start/</code> 访问（默认的 <code>/posts/guide/getting-started/</code> 依然有效）。适合把常被引用的文章挂一个短地址，比如 <code>/posts/start/</code>、<code>/posts/faq/</code>。</p>
<h2>构建与部署</h2>
<pre><code>npm run build      # 产出 dist/，并在其中生成 pagefind/ 搜索索引
npm run preview    # 用本地服务器预览 dist/ 的实际效果
</code></pre>
<p>构建脚本会自动识别部署平台并把索引写进对应的产物目录：GitHub Actions、Cloudflare Pages、Netlify 用 <code>dist/</code>，Vercel 用 <code>.vercel/output/static/</code>。纯静态部署时，<code>dist/</code> 整个目录丢到任意静态托管（对象存储 + CDN、Nginx、GitHub Pages 都行）即可。</p>
<p>:::tip
如果部署平台支持缓存，记得把 <code>dist/pagefind/</code> 一起上传。少了它，页面还在，但搜索框会一直转圈。
:::</p>
<p>仓库里还带了 <code>Dockerfile</code> 与 <code>docker-compose.yml</code>，想自建服务器的话可以直接构建镜像运行。</p>
<h2>上线前检查清单</h2>
<ul>
<li>[ ] <code>npm run check</code> 没有报错</li>
<li>[ ] 站点的 <code>siteURL</code> 已改成自己的域名（影响 RSS、站点地图与分享卡片）</li>
<li>[ ] <code>npm run new-post</code> 建的那几篇示例文章已替换成自己的内容</li>
<li>[ ] 想隐藏的示例文章加上 <code>draft: true</code>，或直接删掉文件</li>
<li>[ ] <code>cover</code> 指向的图片确实存在（本地相对路径用 <code>./xxx.svg</code>，<code>public</code> 目录里的用 <code>/assets/...</code>）</li>
<li>[ ] 需要加密的文章同时设置了 <code>encrypted: true</code> 与 <code>password</code></li>
<li>[ ] 评论等外部服务要么配置好，要么在配置里显式关掉</li>
</ul>
<h2>常见问题</h2>
<p><strong>构建成功但文章不见了？</strong>
先看 <code>draft</code>。生产构建会跳过所有 <code>draft: true</code> 的文章，开发模式则仍然显示，方便边写边看。</p>
<p><strong>封面图没显示？</strong>
检查路径写法：以 <code>/</code> 开头表示 <code>public</code> 目录下的文件，以 <code>./</code> 开头表示与文章同目录的文件，以 <code>http</code> 开头表示网络图片。</p>
<p><strong>想换主题色、改侧栏组件顺序？</strong>
这些都在站点总配置文件里，字段说明见<a href="/posts/theme-configuration/">主题与站点配置</a>。</p>
]]></content>
            <author>
                <name>younisekai</name>
            </author>
            <category term="入门指南"></category>
            <category term="入门" label="入门"></category>
            <category term="Astro" label="Astro"></category>
            <category term="站点配置" label="站点配置"></category>
            </entry>
        </feed>