> ## Content Index
> Fetch the complete content index at: https://nigzu.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Blumid：开发一款为纯粹阅读而生的 Ghost 主题
- URL: https://nigzu.com/ghost-theme-blumid-introduction/
- Published: 2025-06-09T00:44:17.000Z
- Updated: 2025-06-09T00:44:17.000Z
- Author: 宁嘉
- Tags: Ghost, 砖工技术

酝酿许久，我终于完成了这款一直想开发的博客主题——Blumid，也就是你现在所看到的模样。它基本完整实现了 Ghost 官方提供的所有功能（如搜索、会员、内容卡、原生评论等），更在设计理念和功能细节上倾注了大量心血。

Blumid 的核心是“**内容优先**”。在这个信息过载的时代，许多博客追求海报级的视觉冲击力，但这往往会分散读者的注意力。Blumid 选择克制与优雅，旨在通过柔和的视觉引导，为读者创造一个沉浸、愉悦的阅读环境。

如果你喜欢这款主题，欢迎你：

- **前往 GitHub 下载并安装到自己的博客上：**[**code-gal/Ghost-Theme-Blumid**](https://github.com/code-gal/Ghost-Theme-Blumid?ref=nigzu.com "null")
- **点亮一颗星 (Star)✨** 来支持这个项目。
- **订阅我的博客**，以充分体验它的所有特点，重点在精心设计的侧边栏。

## 设计理念

我将 Blumid 的核心风格概括为 **“晶莫渐变”**，旨在突出内容本身，并提供舒适的视觉体验。

- **模块化与现代感**：内容以清晰的圆角卡片形式组织，信息层级分明。整体界面简洁，减少视觉干扰，让核心内容成为焦点。
- **柔和氛围**：页面背景采用主题色的柔和渐变，导航栏、主体栏及侧边栏等容器则应用了**毛玻璃效果**。白天模式下呈现淡雅阴影，夜间模式下则带有细微的边框泛光，营造出轻盈、舒适的视觉层次。
- **色彩体系**：主色调参考了流行的**莫兰迪色系**，柔和而不张扬。背景色除了推荐方案外，还完全支持自定义。同时，主题支持根据操作系统偏好自动切换亮/暗模式，并提供手动切换选项。
- **交互贴合习惯**：布局全面响应式设计，无缝适配各种屏幕尺寸。导航栏、侧边栏、卡片等所有关键元素都经过精心设计，确保在任何设备上都有一致且流畅的交互体验。
- **轻量与原生**：坚持使用原生的 CSS 与 JavaScript，最大限度地减少对第三方框架的依赖，确保主题轻盈、高效。
- **图标系统**：使用 Handlebars Partials 嵌入内联 SVG 图标，风格统一，自适应尺寸和主题颜色。

## 技术规范

作为一个 Web 项目，Blumid 遵循了业界领先的开发标准，确保其稳定、高效且易于维护。

- **100% 通过 Gscan**：项目已通过 Ghost 官方的 **`gscan` 工具**全面检测，符合主题最佳实践。
- **代码质量**：代码结构清晰，注释完善，遵循 BEM 命名规范，并使用现代 CSS 技术 (Flexbox, Grid)，`package.json` 文件也包含了完整的主题元数据。
- **无障碍访问 (Accessibility)**：遵循 WCAG 标准，采用语义化 HTML 标签，为交互元素提供 `aria-label`，保证足够的色彩对比度并支持键盘导航，致力于提供无障碍的访问体验。
- **SEO 优化**：内置了完善的 SEO 结构化数据，有助于提升网站在搜索引擎中的可见性。
- **标准模板结构**：主题遵循标准的 Ghost 模板结构，方便开发维护。关键文件及目录包括：

  - `default.hbs`: 基础布局模板
  - `index.hbs`: 首页文章列表
  - `post.hbs`: 文章页模板
  - `page.hbs`: 独立页面模板
  - `partials/`: 可复用的模板片段
  - `locales/`: 语言文件目录
  - `assets/`: 存放 CSS、JavaScript、字体和图片等静态资源。

## 核心功能

Blumid 为内容显示实现多种增强效果，并提供丰富的自定义选项，它可以适应个性化需求。

### 增强的内容体验

- **图片点击放大**：文章页和侧边栏中的图片均支持点击放大，方便查看原图细节。
- **便利贴式信息卡**：文章的标签、日期等元信息会以别致的便利贴样式展示，并集成了一键分享功能。
- **代码高亮**：使用 `Prism.js` 实现代码高亮，支持显示语言类型和一键复制，并能自适应日夜模式。
- **Mermaid 图表**：原生支持 `Mermaid` 语法，只需在 Markdown 或 HTML 中标注语言类型，即可将代码块渲染为流程图、时序图等。
- **LaTeX 公式**：通过 `KaTeX` 解析，支持在文章页中渲染数学公式。为获得最佳效果，建议将公式写入 `math` 或 `latex` 类型的代码块中。
- **优化的内容卡**：表格、引用以及所有 Ghost 官方内容卡都经过设计，确保在各种设备上都有美观且响应式的显示效果。

### 丰富的自定义选项

你可以在 Ghost 后台的 **Design & branding** 中轻松完成所有设置，或通过 **Code injection** 实现更高级的定制。

#### 1\. 全屏首页

可选的沉浸式首页，可用于展示精美的背景图与网站元信息，适合需要内容预加载或希望第一时间展示关键信息的网站。用户可通过滚动或点击箭头平滑过渡到文章列表。

![](https://nigzu.com/file/Blumid%E5%85%A8%E5%B1%8F%E9%A6%96%E9%A1%B5%E5%B1%95%E7%A4%BA.png)

#### 2\. 导航

头部和页脚导航栏分别展示Ghost 后台设置的主要和次要的导航条目，头部导航栏滚动自动显隐，小屏幕下自动折叠，小图标按钮切换侧边栏显示、颜色模式、搜索和会员。

#### 3\. 侧边栏收纳

Page页是没有侧边栏的，你可以选择完全关闭，打造极简的阅读体验。如果打开，它会智能响应不同设备，交互方式清晰自然：

- 手机/平板：从屏幕左侧向右滑动可呼出侧边栏，点击内容区域外即可隐藏，避免与阅读手势冲突。
- 桌面端：通过点击导航栏的图标来显示或隐藏。

![](https://nigzu.com/file/Blumid%E4%B8%BB%E9%A2%98%E4%BE%A7%E8%BE%B9%E6%A0%8F%E8%87%AA%E9%80%82%E5%BA%94%E5%B8%83%E5%B1%80.gif)

#### 4\. 多功能侧边栏内容

在文章页，侧边栏会自动生成**文章大纲**。在列表页（首页、标签页等），你可以选择展示三种不同内容：

- **网站信息 (Site\_Info)**：默认模式，展示网站 Logo、作者信息和标签云。

![](https://nigzu.com/file/Blumid%E4%B8%BB%E9%A2%98%E4%BE%A7%E8%BE%B9%E6%A0%8F%E4%BF%A1%E6%81%AF%E5%9B%BE.jpg)

- **RSS Feed**：订阅并展示外部 RSS 源，非常适合聚合你在其他平台（如 Memos、Blinko、Mastodon）发布的内容。

![](https://nigzu.com/file/Blumid%E4%B8%BB%E9%A2%98%E4%BE%A7%E8%BE%B9%E6%A0%8FRSS%E6%A8%A1%E5%BC%8F%E5%B1%95%E7%A4%BA.gif)

- **代码注入 (Code\_Injection)**：通过注入自定义脚本，嵌入聊天框、个人状态等独特模块。
- **会员专属内容**：你可以将 RSS Feed 或代码注入的侧边栏内容设置为“仅会员可见”，为订阅用户提供专属价值。

#### 5\. 自定义渐变背景

背景颜色支持完全自定义，你可以随心所欲地调整日间与夜间模式下的顶部和底部色彩。

![](https://nigzu.com/file/Blumid%E4%B8%BB%E9%A2%98%E6%97%A5%E9%97%B4%E6%A8%A1%E5%BC%8F%E9%A6%96%E9%A1%B5.png)

![](https://nigzu.com/file/Blumid%E4%B8%BB%E9%A2%98%E5%A4%9C%E9%97%B4%E6%A8%A1%E5%BC%8F%E6%96%87%E7%AB%A0%E9%A1%B5.png)

**推荐颜色配置**：

- **日间**：`#A0C4FF` (天空蓝), `#FFFFE0` (阳光黄), `#E4F9E0` (青草绿), `#FFFFFF` (纯白)
- **夜间**：`#000000` (星空黑), `#2C003E` (霓虹紫), `#C76D00` (落日橙), `#7D7D7D` (纯灰)

#### 6\. 自定义字体与笔头

主题内置了优雅的字体回退方案，并允许你通过后台或代码注入引入自定义字体（如霞鹜文楷）。

文章分隔线上的“笔头”图标也支持替换（支持 SVG 和 PNG），增添个性化细节。

#### 7\. 多语言支持

主题内置中、英双语，并会根据 Ghost 后台的语言设置自动切换。你也可以轻松在 `locales` 文件夹中添加更多语言的翻译文件。

## 进阶功能详解

### 关于侧边栏 RSS 的 CORS 问题

由于浏览器同源策略，直接在前端请求第三方 RSS 源可能会遇到 **CORS** 限制。解决方案如下：

1. **控制自己的源**：如果 RSS 源站属于你，可以在服务器响应中添加 `Access-Control-Allow-Origin` 等头部。
2. **使用 CORS 代理**：对于无法控制的第三方源，可以使用 CORS 代理服务。其原理是让代理服务器去请求 RSS 源，然后将结果返回给你的博客，从而绕过浏览器限制。

**一些公共 CORS 代理服务对比：**

| 代理名称                  | URL                                 | 工作方式                                   | API 密钥 | 速率限制/备注                           |
| --------------------- | ----------------------------------- | -------------------------------------- | ------ | --------------------------------- |
| **allorigins.win**    | https://api.allorigins.win          | /get?url={TARGET\_URL}                 | 不需要    | 免费开源，速率限制未明确说明。                   |
| **CORS.SH**           | https://proxy.cors.sh               | /{TARGET\_URL}                         | 需要     | 提供免费（限开源项目）和付费套餐。                 |
| **cors-anywhere**     | https://cors-anywhere.herokuapp.com | /{TARGET\_URL}                         | 不需要    | 仅为演示服务器，不稳定，不建议生产使用。              |
| **Cloudflare Worker** | https://test.cors.workers.dev       | /?url={TARGET\_URL} (示例，具体取决于worker实现) | 不需要    | Cloudflare Worker的演示实例，用于测试。可以自建。 |

**使用示例**：若你的 RSS 源是 `https://example.com/rss`，你可以在后台设置为 `https://api.allorigins.win/get?url=https://example.com/rss`。

### 关于侧边栏代码注入

此功能为你提供了一个锚点元素 `<div id="initChatBox" class="sidebar-section"></div>`，你可以通过它在侧边栏嵌入任何动态内容，例如微博时间线或一个独立的聊天盒子。

我特别推荐结合 [**Cactus Comments**](https://github.com/code-gal/cactus-client?ref=nigzu.com "null") 使用。如果你部署了自己的 Matrix 服务，它可以作为一个功能强大的网站聊天室，甚至是一个独立的短内容发布渠道，与本主题的会员系统和样式完美融合。参考教程：[Matrix 评论系统：我为 Cactus Comments 开发了些新功能](https://nigzu.com/matrix-cactus-comments-new-features-installation-tutorial/ "null")。

在这个主题上，有一些特别的推荐设置：

```javascript
<link rel="stylesheet" href="https://.../cactus-style.css" type="text/css">
<script type="text/javascript" src="https://.../cactus.js"></script>
<script type="text/javascript" src="https://.../cactus-lang.js"></script>
<script>
document.addEventListener('DOMContentLoaded', async function() {
    // 1. 定义获取Ghost会员名的函数
    async function getGhostMemberName() {
        try {
            // 尝试访问Ghost会员API端点
            const response = await fetch('/members/api/member/');
            if (response.status === 204) {
                // 204 No Content - 表示会员未登录
                return null;
            }
            if (response.ok) {
                const member = await response.json();
                if (member && member.name && member.name.trim() !== '') {
                    // console.log('Ghost Member: Name found -', member.name);
                    return member.name;
                } else if (member && member.email) {
                    // 如果没有名字，可以考虑使用邮箱前缀作为备选
                    const emailUsername = member.email.split('@')[0];
                    return emailUsername;
                }
                console.log('Ghost Member: Logged in, but no usable name or email found.');
                return null;
            } else {
                console.error('Ghost Member: Error fetching member data -', response.status);
                return null;
            }
        } catch (error) {
            console.error('Ghost Member: Exception fetching member data -', error);
            return null;
        }
    }

    // 2. 尝试获取Ghost会员名
    const ghostUserName = await getGhostMemberName();

    // 3. 初始化聊天盒子 (Cactus Comments)
    const chatBoxContainer = document.getElementById("initChatBox");
    if (chatBoxContainer) {
        // 确保 initComments 函数已在全局作用域定义或通过其他方式引入
        if (typeof initComments === 'function') {
            initComments({
                node: document.getElementById("initChatBox"),
                defaultHomeserverUrl: "https://matrix.xxx.com:8448",
                serverName: "xxx.com",
                siteName: "xxx",
                loginEnabled: true,
                guestPostingEnabled: true,
                commentEnabled: true,
                updateInterval: 60,
                pageSize:10,
                commentSectionId: "blog",
                isAuthenticated: true,
                translationsData: cactusTranslations,
                language: "cn"
            });

            // 4. 如果获取到了Ghost会员名，则尝试填充到聊天输入框
            if (ghostUserName) {
                let attempts = 0;
                const maxAttempts = 15; // 尝试次数 (15 * 500ms = 7.5秒)
                const interval = 500; // 尝试间隔 (毫秒)

                function attemptToFillUsername() {
                    // 根据您提供的HTML结构定位输入框
                    const nameInput = document.querySelector(".cactus-editor-name input[type='text']");

                    if (nameInput) {
                        nameInput.value = ghostUserName;
                        // 触发 'input' 事件，以便聊天客户端可能依赖的事件监听器能够感知到值的变化
                        nameInput.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
                        // 有些组件可能监听 'change' 事件
                        nameInput.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
                    } else if (attempts < maxAttempts) {
                        attempts++;
                        setTimeout(attemptToFillUsername, interval);
                    } else {
                        console.warn('ChatBox: Username input field not found after multiple attempts. Could not prefill with Ghost member name.');
                    }
                }
                attemptToFillUsername(); // 开始尝试填充
            }
        } else {
            console.error('Error: initComments function is not defined. Cannot initialize chatbox.');
        }
    }
});
</script>
<style>
    .cactus-container {
        flex-direction: column-reverse;
    }
    .cactus-comments-container {
        flex-direction: column-reverse;
    }
        .cactus-comments-list {
        flex-direction: column-reverse;
    }
    .cactus-comment-avatar{
        width: 25px !important;
    }
    .cactus-comment-avatar-placeholder,
    .cactus-comment-avatar>img {
        width: 25px !important;
        height: 25px !important;
    }
  .cactus-login-form-wrapper {
      position: sticky;
  }
  .cactus-editor-name {
      display:none;
  }
  .cactus-comment{
      gap: 0.5em;
  }
</style>

```