这是本节的多页打印视图。 .
定制外观
1 - 字体预设
OINK 把字体选择收敛到七个语义化 CSS 自定义属性之后。站点覆盖这些角色即可换字体,不需要知道主题内部用了哪些选择器。
整套机制由 Hugo 在构建期编译进同一份静态样式表:不引入 JavaScript、不请求远程字体服务、不需要运行时预设加载器。
两个内置预设
| 预设 | 效果 |
|---|---|
technical |
默认值。保留 Chakra Petch 与 IBM Plex Mono 的工程观感,字体文件全部本地打包 |
system |
展示、元信息、打印与等宽角色改用平台字体栈,不请求任何 OINK 品牌字体 |
选中的值会输出到 <html> 的 data-td-typography
属性上。无效取值直接让构建失败,而不是静默改变站点外观。
system预设下,OINK 的品牌字体文件仍然是主题的静态资源,只是浏览器在默认配置下不会去请求它们。
七个语义角色
-
--td-ui-font-family,CSS 属性 导航、控件与界面外壳。默认继承
--bs-body-font-family。-
--td-body-font-family,CSS 属性 文章与博客正文。默认继承 UI 角色。
-
--td-heading-font-family,CSS 属性 内容标题。默认取
$headings-font-family,未设置时回落到正文角色。-
--td-code-font-family,CSS 属性 代码与终端内容。默认取
$font-family-code。-
--td-display-font-family,CSS 属性 文字商标与展示型标题。默认 Chakra Petch,然后回落到 UI 角色。
-
--td-meta-font-family,CSS 属性 技术标签与元信息。默认 IBM Plex Mono,然后回落到代码角色。
-
--td-print-font-family,CSS 属性 仅用于打印输出的正文。
主题组件只消费这些角色或组件级别名(例如
--td-asciinema-font-family),不会直接写死字体名。依赖方向始终是:
反向引用是禁止的——Bootstrap 的自定义属性不会引用 OINK 的属性,这样依赖图保持单向,不会产生自定义属性循环。
使用站点自己的字体
把本地 .woff2 放到站点的 static/webfonts/ 下,在 _styles_project.scss
里声明字体并覆盖需要的角色。Hugo会在主题样式之后加载这个文件:
远程 URL 和任意 CSS 不能通过 YAML 注入。 字体文件和样式表始终是本地的、可审查的构建输入。
按内容类型分别设置
角色是正常继承的,所以给某一类内容单独换字体不需要新增全局预设,也不用复制组件选择器:
与旧配置的兼容
已有的 Sass 定制仍然是这套系统的第一输入。OINK 复用既有变量而不是另起一套:
| 既有变量 | OINK 的解读 |
|---|---|
$font-family-base / $font-family-sans-serif |
Bootstrap 正文字体,进而作为 UI 与正文角色 |
$headings-font-family |
显式配置时作为标题角色 |
$font-family-monospace |
Bootstrap 等宽基线 |
$font-family-code |
代码角色,以及普通 code、pre、kbd、samp |
$td-google-font-name |
默认打印字体 |
这些覆盖写在
assets/scss/_variables_project.scss,与 Docsy 一致,会被编译进角色默认值。
_styles_project.scss
里的自定义属性覆盖在更晚的层级生效,因此仍可用于上下文相关的定制。项目设置优先于预设默认值是有意为之。
范围说明
这是设计令牌工作的第一片:只覆盖字体。语义颜色、表面、圆角、密度和外观预设不在其中——它们应该在各自的 Bootstrap与壳层令牌契约有了对应的回归覆盖之后再单独引入。
下一步
2 - 外观与风格
OINK 在 Bootstrap 与 Docsy 基础上提供完整的视觉系统,并将字体、图标、样式和浏览器端代码全部本地化。使用方无需重建 Node 依赖树,就能通过设计变量和项目样式完成定制。
项目样式
Hugo Extended 通过 Hugo Pipes 编译主题 SCSS。项目覆盖项会进入同一个资源包,因此生产构建可以对一份同源样式表完成压缩、指纹和完整性校验。
项目样式文件
在站点的 assets/scss/ 目录中覆盖以下文件:
| 文件 | 用途 |
|---|---|
_variables_project.scss |
在 Bootstrap 与 OINK 默认值之前设置变量 |
_variables_project_after_bs.scss |
设置依赖 Bootstrap 定义的变量或映射 |
_styles_project.scss |
在主题组件样式之后加载项目选择器 |
先从最小覆盖项开始:
普通品牌定制不要直接修改纳管的 Bootstrap、Font Awesome 或本地字体文件。主题更新会覆盖这些改动,也会模糊依赖边界。
高级样式定制
稳定的定制层次、字体 preset、语义字体角色与按内容限定作用域的完整说明,参见高级定制。
OINK 的 SCSS 导入顺序如下:
- Bootstrap 函数;
- 项目变量;
- OINK 默认值与 Bootstrap;
- Bootstrap 之后的项目变量;
- OINK 组件与本地品牌层;
- 项目样式。
稳定的设计决策应通过变量或 CSS 自定义属性表达。没有合适设计变量时才覆盖选择器,而且作用域应尽量缩小到具体组件。许多颜色会随主题变化,因此必须检查浅色与深色输出。
⚠️ 重置内部样式
OINK 的内部 Partial 并不是公开 Sass API。单独导入或屏蔽内部文件会让站点耦合到仓库布局和导入顺序。产品确实需要完全不同的页面框架时,应覆盖 Hugo 布局或有意识地维护主题分支,而不是重置整份样式表。
额外样式
隔离的第三方 CSS 可以通过钩子发布为本地资源:
将模板放在
layouts/partials/hooks/head-end.html。如果规则属于站点设计系统,应优先写入项目 SCSS 文件。绝不能把远程样式表当作隐式后备资源。
颜色与颜色主题
主题各处都可以使用 Bootstrap 语义颜色与 OINK 品牌设计变量。语义名称比具体色值更能说明用途。
站点颜色
在编译前设置 Bootstrap 变量:
OINK 的标准品牌层还公开
--td-brand-elev、--td-brand-silk、--td-brand-copper、--td-brand-header-bg
和 --td-brand-mark-gradient 等 CSS 属性。应同时在 :root 与
[data-bs-theme='dark'] 中成对覆盖:
浅色/深色主题与模式支持
颜色 主题 是组件采用的配色方案,颜色 模式
则是整个站点当前处于浅色还是深色状态。OINK 使用 Bootstrap 的
data-bs-theme="light|dark"
属性,并把读者明确选择的模式保存在浏览器本地存储中。没有明确选择时,站点跟随
prefers-color-scheme。
每个自定义组件都必须为两种模式定义可读状态,包括悬停、焦点、禁用、选中和代码颜色。不能只用颜色传递含义。
浅色/深色模式
样例站默认启用颜色模式支持并显示选择器:
选择器会在页面进入正常交互前更新文档,以减少错误主题闪烁。OINK 的脚本从本地加载,不会联系外部服务。
为站点选择主题或颜色模式
多数站点应使用默认自动行为。只有完整视觉系统已经在某种模式下通过测试,而且读者确实不需要另一种模式时,才应强制指定。截图不足以完成验证:还要检查真实正文、表格、告警、表单、图表、代码和焦点指示器。
禁用深色模式
如需禁用深色模式并隐藏菜单:
实验值 enable-only (experimental)
会启用主题感知样式,但不显示选择器。该配置面仍可能变化,只能作为过渡选项使用。
选择具有良好对比度的颜色
所有组件状态都应满足 WCAG 对比度要求,并以浏览器实际计算后的颜色为准,包括叠加在图片上的半透明图层。作为工作基线,普通文字的对比度至少为 4.5:1,大号文字至少为 3:1;焦点和非文本界面指示器同样需要足够对比度。自动化工具可以发现常见问题,但仍需进行键盘和人工视觉审查。
字体
OINK 不会拉取 Google Fonts。主题使用的 Open Sans、Chakra Petch、IBM Plex
Mono 与 Font Awesome 字体文件均保存在本地。由于历史原因,旧版 Sass 变量
$td-enable-google-fonts 实际控制的是随主题提供的 Open Sans 字体。
在 _variables_project.scss 中设置字体:
OINK 还提供构建时字体 preset 与不依赖运行时脚本的语义字体角色。博客、OpenAPI 或代码密集页面的作用域示例与完整公开接口,参见高级定制。
新增字体时,应制作所需子集并自行托管,包含必要字形,设置
font-display: swap,在 VENDOR.json
中记录许可证,并测试 CJK 后备字体。页面渲染不能依赖字体 CDN。
CSS 工具类
在允许原始 HTML 的 Markdown 和布局中可以使用 Bootstrap 工具类。内容优先使用语义化 Markdown 与 OINK 短代码;工具类只适合在不同断点下仍易于理解的小范围表现调整。项目级模式应写入
_styles_project.scss。
代码块
OINK 默认支持 Hugo Chroma,并提供本地纳管的 Prism 兼容选项。一个站点应统一选择一种高亮器;同时启用会产生重复标记或样式。文件名、复制策略、换行、折叠、行锚点与可分享代码组的完整说明参见代码块与代码组。
使用 Chroma 进行代码高亮
Chroma 在 Hugo 构建期间运行,不需要浏览器端高亮器。代码块应指定语言:
Chroma 基础样式配置
在 Hugo 中配置标记渲染:
OINK 使用基于 class 的输出,以便浅色和深色模式采用不同样式。重新生成配色时,应将 CSS 保存在本地,并结合品牌背景完成审查。
浅色/深色代码样式及其他配置
主题在 assets/scss/td/chroma/
中提供两套 Chroma 配色,并按模式应用。项目覆盖项应在相应主题属性下定位
.chroma,不要硬编码全局背景。
选择控制台代码块内容
终端记录使用
console。OINK 会调整提示符和输出的选中行为,使读者复制命令时不会带上装饰性提示符。命令与输出应各占一行,而且不能只靠颜色区分。
未指定语言的代码块
没有标签的围栏会渲染为纯代码。只有确实不存在相应语法时才这样做;命令会话应标为
console 或 bash,不要让 Chroma 猜测。
复制到剪贴板
除非 params.disable_click2copy_chroma
为 true,否则 Chroma 会显示复制按钮。已部署站点中的剪贴板访问需要安全上下文。该控件必须支持键盘操作,而且不应复制行号或提示符。
使用 Prism 进行代码高亮
设置:
即可使用 OINK 本地提供的 prism.js 与
prism.css。这是面向既有站点的兼容选项;若要尽量减少浏览器负担,优先使用 Chroma。
没有语言的代码块
Prism 同样会把没有标签的代码块当作纯文本。应补充正确的语言 class,而不是启用启发式检测。
扩展 Prism 语言或插件
构建并纳管准确的 Prism 资源包,通过受控主题变更替换本地文件,记录版本与许可证,并添加覆盖该语言或插件的 Fixture。运行时不得从 CDN 拉取 Prism 组件。
导航栏
OINK 导航栏包含项目标识、主菜单、按需显示的版本与语言选择器、颜色模式控件、搜索以及仓库链接。除非页面用
navbar_enabled
关闭,它会在所有布局上渲染。
默认外观
导航栏使用本地品牌配色和固定的最小高度,并且只有两种状态,而不是桌面布局加一套独立的移动菜单。
移动端
小于 lg
时,导航栏保留 Logo,其余条目全部变成右对齐的图标,没有任何内容藏进汉堡按钮。小于
md
时,带外壳的页面会再多出一个用于打开侧边栏抽屉的图标。应测试较长的中文标签、200% 缩放、触控目标、焦点顺序以及两种页面方向;同时为每个顶层菜单项配置
pre 图标,因为在这个宽度下只有图标会保留下来。
桌面端
lg
及以上时,主菜单连同文字标签在一行内展开;版本、语言、模式、搜索与仓库控件成组排在末尾。父级菜单项在悬停或键盘聚焦时展开面板,点击则直接跳转。不要添加过多自定义入口,以免把控件挤出视口。
覆盖图上的默认半透明效果
blocks/cover
短代码会把导航栏标记为覆盖图感知状态。导航栏起初为半透明,页面滚动后恢复常规背景。
自定义导航栏
行为通过配置调整,表现通过项目 SCSS 调整。覆盖导航栏 Partial 时,必须保留导航地标、焦点顺序、无障碍标签和响应式溢出行为。
导航栏高度
在主题样式编译前覆盖
$td-navbar-min-height。锚点偏移、侧边栏高度、移动端换行和覆盖图都依赖该值,因此必须重新测试。
背景颜色与透明度
在两种模式下分别设置 --td-navbar-bg-color 或
--td-brand-header-bg。背景为半透明时,应在所有覆盖图上验证对比度,并为滚动状态提供不透明背景。
设置导航栏浅色/深色主题
覆盖图需要浅色前景控件时,页面可以在 Front Matter 或 cascade 中设置
ui.navbar_theme: dark。这只会调整导航栏组件样式,不会强制改变整个站点的颜色模式。
自定义覆盖图上的半透明效果
可以在站点级禁用半透明:
覆盖图不可预测,或无障碍审查无法保证对比度时,应优先关闭该效果。
设置项目徽标与名称样式
徽标 Partial 覆盖项放在 layouts/partials/,源资源放在 assets/ 或
static/。具有信息含义的标志应提供有意义的替代文本;纯装饰标志应使用空替代文本。SVG 必须包含 view
box,并为两种模式继承或定义颜色。
OINK 样例使用带本地渐变效果的文字标识。站点标题在语言配置中修改,视觉变量在项目 SCSS 中修改;可选择的文字能够胜任时,不要用图片替代品牌名称。
浅色/深色模式菜单
params.ui.showLightDarkModeMenu
为 true 时显示选择器。点击导航栏图标在浅色与深色之间切换,悬停或聚焦则展开「跟随系统 / 浅色 / 深色」选择器,其中「跟随系统」采用读者操作系统的设置。应把它留在共享导航中,使颜色状态在所有语言和页面类型中保持一致。
告警
Markdown 告警类型会映射到语义化 OINK/Bootstrap 样式。.alert-*
与告警渲染钩子应成对调整,保留可见标签或图标,并测试每种背景中的链接和行内代码。语法参见添加内容。
表格
Markdown 表格具有响应式和主题感知样式。单元格应保持简洁,表头应使用真正的标题单元格;需要上下文时可在自定义 HTML 中添加标题,并在移动端测试横向溢出。不能用表格布局互不相关的内容。
自定义模板
Hugo 会优先解析站点布局,再解析主题布局。只复制确实需要修改的最小 Partial,并在同步上游时进行对比;覆盖完整
baseof.html 可能会悄然遗漏后续的无障碍与资源流水线修复。
在 head 或 body 末尾添加代码
Head 附加内容使用 layouts/partials/hooks/head-end.html,脚本或结束集成使用
layouts/partials/hooks/body-end.html。资源应自行托管,只在需要的页面加载,并与生产 CSP 保持兼容。
在页面正文前添加横幅
根据页面参数设置条件,并覆盖相应钩子或内容 Partial。横幅不得遮挡页面标题、困住键盘焦点,也不能把锚点目标挤到固定导航下方。
为 body 元素添加自定义 class
在页面 Front Matter 或分区 cascade 中设置 body_class:
OINK 会把该值追加到自动生成的 body class。请使用项目专属且有语义的名称,绝不能向该字段写入不可信内容。
3 - 高级定制
OINK 提供一套小而清晰的分层定制接口,站点不需要复制组件选择器,也不需要分叉主题。全部样式仍由 Hugo Extended 与 Hugo Pipes 处理,不引入 Node.js、npm、PostCSS、远程字体服务或浏览器端 preset 加载器。
本文记录正式支持的扩展点,以及第一组公开的语义 token:字体系统。颜色模式、代码高亮、导航栏与模板定制示例,参见外观与风格。
选择正确的定制层
优先使用能够表达需求的最高层接口:
| 层级 | 扩展点 | 适用场景 |
|---|---|---|
| Hugo 配置 | hugo.yaml 或页面 Front Matter |
字体 preset、页面宽度等正式支持的选项 |
| Sass 基础层 | assets/scss/_variables_project.scss |
影响整个样式包的 Docsy 与 Bootstrap 既有变量 |
| Bootstrap 后置 Sass | assets/scss/_variables_project_after_bs.scss |
少数依赖 Bootstrap 变量或 map 的覆盖项 |
| 语义 CSS | assets/scss/_styles_project.scss |
语义角色、某类内容、某组页面或单个组件 |
| Hugo 模板 | layouts/ 与 Partial hook |
CSS 无法表达的结构或 DOM 修改 |
先尝试配置项或既有 Sass 变量;需要缩小作用域时,再使用语义 CSS 属性。只有找不到合适 token 时才覆盖选择器,只有结构本身必须变化时才覆盖模板。
站点品牌定制不要直接修改主题、Bootstrap、Font Awesome 或主题字体目录中的文件。这些修改难以审计,也会在升级时丢失或产生冲突。
CSS 接口如何分层
OINK 保持单向依赖:
Docsy 与 Bootstrap 的既有变量继续充当基础层。只有多个组件需要共享同一种含义时,OINK 才增加语义角色,例如“文章正文”或“技术元信息”。Bootstrap 属性不会反向引用 OINK 角色,因此不会形成 CSS 自定义属性循环。
接口稳定性有明确边界:
- 在可行范围内,既有 Docsy 与 Bootstrap 变量继续作为兼容输入;
- 下文列出的字体角色是面向站点定制的公开 API;
--td-asciinema-font-family这类已记录的组件别名只承诺更窄的组件级用途;- 未记录的
--td-shell-*与选择器局部属性属于实现细节。不能因为某个属性以--td-*开头,就默认它是公共 API。
字体 preset
在 hugo.yaml 中选择全站内置 preset:
| Preset | 效果 | 字体请求 |
|---|---|---|
technical |
默认 OINK 风格,包括 Chakra Petch 展示文字与 IBM Plex Mono 技术文字 | 只使用主题本地文件 |
system |
使用操作系统 sans 与 monospace 字体栈,适合作为中性基础,也能缩减文本字体请求 | 除非项目 CSS 主动引用,否则不会请求 OINK 自带的文本字体 |
OINK 会把最终取值写入 <html> 元素的 data-td-typography
属性。无效值会让 Hugo 构建失败,而不是静默回退。这是站点构建时选择,不是依赖 JavaScript 的读者偏好设置。
Preset 只提供默认值,项目 Sass 与 CSS 始终拥有最终控制权。例如,站点即使选择了
system,只要项目样式明确引用 IBM Plex Mono,浏览器仍会加载它。
公开字体角色
组件只消费语义角色,不直接指定品牌字体:
| CSS 属性 | 控制范围 | 默认来源 |
|---|---|---|
--td-ui-font-family |
导航、控件、搜索与通用界面外壳 | Bootstrap 正文字体 |
--td-body-font-family |
文档与博客正文 | UI 角色 |
--td-heading-font-family |
文章标题 | $headings-font-family,未设置时使用正文角色 |
--td-code-font-family |
code、pre、kbd、samp 与终端内容 |
$font-family-code |
--td-display-font-family |
字标与展示型标题 | Chakra Petch,后备为 UI 角色 |
--td-meta-font-family |
技术标签与元信息 | IBM Plex Mono,后备为代码角色 |
--td-print-font-family |
打印正文及默认打印标题 | $td-google-font-name,后备为 Bootstrap 正文字体 |
大多数情况下应修改较宽泛的语义角色。例如 Asciinema 使用
--td-asciinema-font-family,其默认值是
--td-code-font-family。只有终端回放确实需要与其他代码不同的字体时,才单独覆盖组件别名。
复用 Docsy 与 Bootstrap Sass 变量
OINK 直接解释既有名字,不再增加一组平行的 Sass 开关:
| 既有变量 | OINK 的解释方式 |
|---|---|
$td-fonts-serif、$font-family-sans-serif、$font-family-base |
Bootstrap 正文字体,继而成为 UI 与正文角色 |
$headings-font-family |
明确设置时成为标题角色 |
$td-font-family-monospace、$font-family-monospace |
Bootstrap 等宽字体基础 |
$font-family-code |
代码角色与普通代码元素 |
$td-google-font-name |
默认打印字体 |
与 Docsy 一样,把这些编译期覆盖项写入 _variables_project.scss:
这会修改编译后的默认值。同一站点的不同区域需要不同风格时,请在
_styles_project.scss 中覆盖语义 CSS 属性。
添加站点自有字体
把经过审查的 .woff2 文件放在使用方站点的 static/webfonts/ 目录中,再到
_styles_project.scss 声明并分配字体:
内容可能包含中文时,应为代码字体明确提供 CJK 后备:
应对子集进行裁剪并自行托管字体,包含站点需要的全部文字,设置
font-display: swap,并记录许可证。OINK 有意不允许通过 YAML 注入任意字体 URL 或 CSS 字符串。
按内容类型限定样式
语义属性可以正常继承,因此内容专属风格不需要第二份样式表,也不需要复制组件规则。博客页面已经带有
td-blog body class:
OINK 还会为 Swagger/OpenAPI 页面添加
td-swagger。如果是项目自己定义的页面类型,可在 Front Matter 或 section
cascade 中设置 body_class:
请使用站点自有且具有语义的 class 名称,绝不能向 body_class 写入不可信内容。
让布局与字体彼此独立
字体、文章宽度与组件结构是三个独立维度。OINK 现有的 page_width 参数支持
normal、wide 与 full,既可全局设置,也可在页面 Front Matter 中覆盖:
这样无需制造一个包办一切的 preset,就能组合出不同使用体验:
| 使用场景 | 推荐组合 |
|---|---|
| 标准文档 | page_width: normal 加全站字体 preset |
| 全宽画布 | page_width: full,必要时再加页面专属 body_class |
| 博客或文章阅读 | 在 td-blog 中覆盖正文与标题角色,通常保持 normal 宽度 |
| 代码密集型参考页 | 用项目 body class 调整代码与元信息角色 |
| OpenAPI 参考页 | 使用 Swagger layout 与 td-swagger,渲染器专属结构留在对应布局中 |
将这些关注点分开,可以避免 preset 数量膨胀,后续 editorial、API 或代码风格也能复用同一组语义角色。
颜色与组件 surface
编译期配色应使用 $primary、$secondary、$danger
等 Bootstrap 变量;运行时则优先使用
--bs-body-bg、--bs-body-color、--bs-link-color 与 --bs-border-color
等 Bootstrap 语义属性。
OINK 还记录了一小组品牌层属性,包括
--td-brand-elev、--td-brand-silk、--td-brand-copper、--td-brand-header-bg
与
--td-brand-mark-gradient。浅色与深色值应成对覆盖,完整示例参见外观与风格。
不要仅仅因为某个 shell 或组件 token 的名字看起来顺手,就在全局覆盖它。应先修改它的 Bootstrap 来源或已记录的语义来源;只有该组件确实需要独立变化时,才使用组件别名。
验收清单
发布定制样式前:
- 使用当前支持的最旧与最新 Hugo Extended 版本构建;
- 检查浅色、深色、打印、forced-colors 与 reduced-motion;
- 检查涉及范围内的文档、博客、代码、搜索与 OpenAPI 页面;
- 检查窄屏和宽屏,以及较长的 CJK 文本与代码行;
- 确认字体请求全部来自本地,加载有意、许可证清楚,而且体积没有不必要的膨胀;
- 优先使用一次语义覆盖,避免重复堆叠选择器补丁。
这些约束共同守住 OINK 的核心前提:定制之后,站点仍然只依赖一个 Hugo 二进制文件,不增加额外构建或运行时依赖。