diff --git a/.vitepress/theme/locations.ts b/.vitepress/theme/locations.ts
index 69438ee..a31324d 100644
--- a/.vitepress/theme/locations.ts
+++ b/.vitepress/theme/locations.ts
@@ -5,10 +5,15 @@ const CITY = {
const DISTRICT = {
大冲社区: "南山区粤海街道大冲社区",
高新区社区: "南山区粤海街道高新区社区",
+ 海滨社区: "宝安区新安街道海滨社区",
渔业社区: "宝安区西乡街道渔业社区",
};
const LOCATIONS = {
+ 宝安图书馆: {
+ city: CITY.深圳,
+ district: DISTRICT.海滨社区,
+ },
财富港: {
city: CITY.深圳,
district: DISTRICT.渔业社区,
diff --git a/posts/building-a-vitepress-blog-theme/README.md b/posts/building-a-vitepress-blog-theme/README.md
new file mode 100644
index 0000000..adab238
--- /dev/null
+++ b/posts/building-a-vitepress-blog-theme/README.md
@@ -0,0 +1,130 @@
+---
+date: 2024-01-01
+spot: 宝安图书馆
+sort: Computer Science
+tags:
+ - Blog
+ - VitePress
+---
+
+# 基于 VitePress 开发博客主题
+
+![Vaquita Porpoises](./vaquita.jpg "A public domain image. [**Paula Olson**](https://www.fisheries.noaa.gov/contact/paula-olson). [*commons.wikimedia.org*](https://commons.wikimedia.org/wiki/File:Vaquita6_Olson_NOAA.jpg).")
+
+前两天完成了这个博客的最后一个功能 (Atom Feed)。本来打算在 2023 年最后一天写一篇总结,但刚好有朋友邀约去爬大南山 [[1]],所以推迟到新年的第一天。对于爬山我的态度很明确,不愿爬,不怕爬,必要时不得不爬:
+
+![View from the Top](./view-from-the-top-of-nanshan.jpg "请欣赏二〇二三年最后一天深圳市南山区~~最美丽~~的风景")
+
+言归正传,这篇总结主要列举基于 VitePress 做一个博客主题需要解决的问题。我还没系统地学过 Vite、Vue 和 TypeScript,所以注定有些代码实现不是最佳实践。目前写文章是我的第一需要,也许等到下个阶段我会抽出时间来重构。
+
+## 开发环境
+
+- Node.js & VS Code: [Octobug/blog - Contributing Guide](https://github.com/Octobug/blog/blob/main/.github/contributing.md)
+- Git Hooks
+ - [typicode/husky](https://github.com/typicode/husky)
+ - [lint-staged/lint-staged](https://github.com/lint-staged/lint-staged)
+- Linters
+ - [eslint/eslint](https://github.com/eslint/eslint)
+ - [igorshubovych/markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli)
+
+## 自定义 VitePress 主题
+
+VitePress 的官方文档相当详细,对于新手直接按顺序阅读 [Guide](https://vitepress.dev/guide/getting-started) 部分即可,需要查阅接口信息时往往可以通过搜索进入 [Reference](https://vitepress.dev/reference/site-config) 部分。
+
+写代码扩展 VitePress 功能时需要注意两个概念:“构建时 (Build-Time)”和“运行时 (Runtime)”。这两个概念最根本的区别是其执行环境:
+
+- 构建时:本地 Node.js 提供的运行、构建环境
+- 运行时:构建打包后的代码运行于浏览器环境
+
+更细致的区别是由 VitePress 的生命周期决定的,但目前官方文档没有详细介绍其生命周期。不过有时候理解错了也能通过报错来区分出是哪个阶段的问题。
+
+### 自定义 CSS
+
+- 自定义 CSS 直接参考:[Extending the Default Theme - Customizing CSS](https://vitepress.dev/guide/extending-default-theme#customizing-css)
+- 颜色建议使用 VitePress 预定义的变量名,后续想改变颜色只需要覆盖默认的变量值:[src/client/theme-default/styles/vars.css](https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css)
+
+⚠️ 在 ``(即打开 `module` 模式)中,嵌套的 CSS 似乎不兼容 Safari 浏览器。
+
+### 页面布局
+
+VitePress 的 Markdown 文件有几个类型,通过 `frontmatter` 中的 `layout` 指定,比如 `layout: home`、`layout: page`。
+
+自定义布局主要参考这几个部分:
+
+- [Using a Custom Theme - Building a Layout](https://vitepress.dev/guide/custom-theme#building-a-layout)
+- Extending the Default Theme
+ - [Registering Global Components](https://vitepress.dev/guide/extending-default-theme#registering-global-components)
+ - [Layout Slots](https://vitepress.dev/guide/extending-default-theme#layout-slots)
+
+当你用 Vue 组件自行实现了某个页面,可以根据 "Registering Global Components" 将其注册为全局组件。这样就可以在 Markdown 页面(区别于作为文章的 Markdown)中使用这个页面组件,避免在自定义 Layout 时混杂太多不同页面的实现。详情请看:[blog/.vitepress/theme/pages](https://github.com/Octobug/blog/tree/main/.vitepress/theme/pages)
+
+### SSR 兼容性
+
+构建生成的静态网站中,如果存在动态内容的页面(比如在页面上显示时间),浏览器 console 会报如下错误:
+
+:::danger
+Hydration completed but contains mismatches.
+:::
+
+这种情况可以使用 `` 将动态部分包裹起来:[SSR Compatibility - ``](https://vitepress.dev/guide/ssr-compat#clientonly)
+
+## 常见的博客功能
+
+### 归档,分类与标签
+
+### Markdown 文章
+
+#### 文章要素
+
+我在每篇文章的标题下方加了时间、地点和文章长度(阅读时长)三个要素。
+
+文章标题和正文在 VitePress 中是一个 `` 整体,不可拆分。要在文章标题下方插入其他 HTML 元素有几个方案:
+
+1. **在每篇 Markdown 文章中插入全局注册的 Vue 组件**:这个方案对于后续写文章来说过于繁琐 ❌
+2. **使用 frontmatter title,而不使用 Markdown 一级标题**:VitePress 为文章建立索引时将段落和其前面最近的一个标题归入同个 section,如果不使用 Markdown 一级标题,会导致第一个标题前面的内容不被索引而搜索不到 ❌
+ - [src/node/plugins/localSearchPlugin.ts](https://github.com/vuejs/vitepress/blob/27f60e0b7784603c6fb300bd8dce64515eb98962/src/node/plugins/localSearchPlugin.ts#L226C35-L226C35)
+ - 这一点我不认为是 bug,因为规范的 Markdown 就是要有一个 `# 一级标题`。
+3. **通过 VitePress 的 markdown-it 接口写类插件代码**:[markdown-it API](https://markdown-it.github.io/markdown-it/) 挺复杂的,而且需要想办法将 frontmatter 中的信息传递给插件代码 ❌
+ - [Markdown Extensions - Advanced Configuration](https://vitepress.dev/guide/markdown#advanced-configuration)
+4. **使用 JavaScript 操作 DOM 元素**:先将 Vue 组件放在 Layout 的 doc slots 里面,再用 JS 把渲染后的 DOM 元素搬运到标题之下。这个方案很丑陋,但似乎是这几个方案里面最好的一个。
+
+#### 图片注解
+
+#### 图片文件
+
+对于部署在 GitHub Pages、Netlify 这些免费托管平台上的静态网站来说,图片文件如果太大十分影响用户体验,如果每次都要手动用 Photoshop 之类的软件处理图片大小会很繁琐。macOS 系统用户可以用 `sips` 命令来处理,比使用 GUI 软件快捷很多。比如将一张图的长边分辨率转成 `1080`:
+
+```sh
+sips -Z 1080 origin.jpg -o resized.jpg
+```
+
+神奇的是,`sips` 压缩图片大小的效果特别好。在生成同样大小的图片时,`sips` 保留原图视觉效果的能力比 Photoshop 还强。
+
+### 中文搜索
+
+VitePress 自带全文搜索:[Search - Local Search](https://vitepress.dev/reference/default-theme-search#local-search),这个搜索是通过 [lucaong/minisearch](https://github.com/lucaong/minisearch/) 实现的。
+
+不幸的是,minisearch 默认不支持中文分词,所以中文搜索效果很差。要实现比较好的中文搜索,需要自行实现 minisearch 的 `tokenize` 函数:[Issue: how to correctly search for phone numbers](https://github.com/lucaong/minisearch/issues/130#issuecomment-1046658483)。
+
+在 [Issue: Excuse me, how to support other language search, such as Chinese search, thank you](https://github.com/lucaong/minisearch/issues/201) 中,有人推荐使用 [yanyiwu/nodejieba](https://github.com/yanyiwu/nodejieba) 和 [`Intl.Segmenter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter) 做中文分词。其中 nodejieba 似乎不支持浏览器端运行,目前没找到在 VitePress 中使用它的方案。`Intl.Segmenter` 目前还没有被 Firefox 支持,且移动端的分词效果也一般。
+
+由于 `Intl.Segmenter` 是我目前找到的唯一可行的方案,所以最终还是采用了它。详情请看:[blog/.vitepress/theme/search.ts](https://github.com/Octobug/blog/blob/main/.vitepress/theme/search.ts)
+
+### 订阅流
+
+---
+
+以上大部分功能是在最近三个月内抽空零散地实现,如果集中在一起高强度开发大概是 1~2 周的工作量。在时间上拆得这么分散的原因是,大部分功能点是慢慢思考到我觉得能接受才开始动手。
+
+这个博客还有一些计划中的页面没实现,不过它们不属于典型的博客功能。本文到此结束。
+
+---
+
+:::details Vaquita Porpoise
+w:::
+
+## References
+
+1. [大南山 (深圳)][1]. *zh.wikipedia.org*.
+
+[1]:
diff --git a/posts/building-a-vitepress-blog-theme/vaquita.jpg b/posts/building-a-vitepress-blog-theme/vaquita.jpg
new file mode 100644
index 0000000..c9c44ad
Binary files /dev/null and b/posts/building-a-vitepress-blog-theme/vaquita.jpg differ
diff --git a/posts/building-a-vitepress-blog-theme/view-from-the-top-of-nanshan.jpg b/posts/building-a-vitepress-blog-theme/view-from-the-top-of-nanshan.jpg
new file mode 100644
index 0000000..8cdca31
Binary files /dev/null and b/posts/building-a-vitepress-blog-theme/view-from-the-top-of-nanshan.jpg differ