跳转到主要内容

品牌外观

替换站名、Logo、favicon、主色、深浅色与字体,只需改配置与两个 SCSS 入口文件。

本页的前提是站点已能构建(十分钟上手)。站名、Logo、页宽和页脚 先在 hugo.yml 中设置。选择系统已有或站点已加载的字体,用 params.ui.fonts 即可;分区强调色也有对应配置键。

图标放在 static/ 下。需要修改 SCSS 变量时再使用 assets/scss/_variables_project.scss;自定义样式或新增 @font-face 声明放在 assets/scss/_styles_project.scss。不要改主题目录里的文件:升级会替换它们。

视觉预设

OINK 1.2.0 默认使用 Paper:暖纸色背景、墨色正文、蓝色链接、Plex Sans、标题 尾随细线与外框表格。Slate 保留 OINK 原有的冷灰蓝外观。开启读者选择:

params:
  ui:
    preset: paper
    preset_menu: true
    dark_mode: true

主题的 preset_menu 默认值为 false。设置 preset: slate 即可保留原有外观。 风格与明暗分别保存;选择站点默认项(悬停提示中注明)恢复跟随站点。所有风格共用一个 样式表,字体全部本地加载。

本地主题已提供 Ink 与 Terminal 实验版,需要显式开启:

params:
  ui:
    preset: paper
    preset_menu: [paper, slate, ink, terminal]
    dark_mode: true

Ink 使用黑白表面、红色标记、直角面板和带下划线的正文链接。Terminal 使用等宽 控件与标题、青色链接、琥珀强调和紧凑的桌面导航,长文正文仍为无衬线字体。两者 均支持明暗切换并复用现有字体。菜单使用两列排列的四个简洁按钮,各带主题色图标, 不加实验标记。preset_menu: true 提供 Paper、 Slate 和站点默认值,不会自动开启所有实验。设置 preset: ink 或 preset: terminal 可将实验风格设为站点默认值,不依赖读者菜单。测试范围与后续设计工作见 实验记录。

只使用 [data-bs-theme='dark'] 的站点自定义深色规则,优先级低于 Paper 深色色板。 可以保留 Slate,或改用 [data-td-preset='paper'][data-bs-theme='dark'] 限定规则。 字体配置与分区 theme_color 在所有预设下继续优先。打印始终使用浅色与白纸背景。

站名

站名出现在顶栏、浏览器标题与页脚。多语言站每种语言各写一个:

hugo.yml
title: 产品文档

languages:
  en:
    title: Product Docs
    label: English
    locale: en-US
    weight: 1
  zh:
    title: 产品文档
    label: 简体中文
    locale: zh-CN
    weight: 2

顶层 title 是兜底,languages.<lang>.title 优先。

主题默认使用自带的 assets/icons/logo.svg。替换步骤是把图标文件放进站点的 assets/ 或 static/,再在配置里指向它。

hugo.yml
params:
  logo: images/product-mark.svg
  wordmark: logo.svg
  • params.logo 是方形图标,顶栏、侧栏与页脚共用。放在 assets/ 下会经过 Hugo 资源管线(可指纹化),放在 static/ 下按原样发布;两种写法都是相对 assets/、static/ 根的路径。
  • params.wordmark 是横向字标。设置后顶栏用它替代「图标 + 站名」,窄屏放不下时回落到 params.logo。不设置则保持「图标 + 站名」。

源 SVG 应紧贴图形边缘裁切,否则各处尺寸对不齐。SVG 必须带 viewBox,颜色继承 currentColor,或者在深浅色下都有足够对比度。

本站两个参数都不设:顶栏用主题自带的 assets/icons/logo.svg 搭配以展示字体渲染的站名。

favicon

favicon 没有参数。主题扫描站点 static/ 目录里的约定文件名,发现哪个就在每个页面输出对应的 <link>:

文件 生成的链接
static/favicon.ico rel="icon"
static/favicon.svg rel="icon" type="image/svg+xml"
static/favicon-32x32.png rel="icon" 带 sizes,按尺寸升序输出
static/apple-touch-icon.png rel="apple-touch-icon"
static/apple-touch-icon-180x180.png rel="apple-touch-icon" 带 sizes

够用的最小组合是 favicon.ico + favicon.svg + apple-touch-icon.png。带尺寸后缀的文件必须是正方形(NxN),否则不会被识别。

这些文件用任意图形工具生成即可。主题不需要 Node.js,Hugo 只发布 static/ 里已经存在的文件。

Web App Manifest 一类的额外 head 元数据不在扫描范围内,用 layouts/_partials/hooks/head-end.html 钩子自行输出;要改变发现规则本身(换目录、增加文件名),在站点 layouts/ 下覆盖 layouts/_partials/favicons.html。

主色与配色

配色分两层:Bootstrap 的语义色(编译期 Sass 变量)和 OINK 的品牌层(运行期 CSS 自定义属性)。

先改语义色,它决定按钮、链接、提示块的色调:

assets/scss/_variables_project.scss
$primary: #315f8f;
$secondary: #b4762e;
$success: #2c7a4b;
$warning: #9a6700;
$danger: #b42318;

这个文件在 Bootstrap 与 OINK 默认值 之前 加载,是覆盖 Sass 变量的位置。需要引用 Bootstrap 已定义的变量或 map 时,改用 _variables_project_after_bs.scss。

品牌层是一组 CSS 自定义属性,浅色和深色 必须成对覆盖,否则一种模式下会漏色:

assets/scss/_styles_project.scss
:root {
  --td-brand-copper: #a66722;
  --td-brand-mark-from: #1d588c;
  --td-brand-mark-to: #a66722;
}

[data-bs-theme='dark'] {
  --td-brand-copper: #e0a35c;
  --td-brand-mark-from: #7fb8e8;
  --td-brand-mark-to: #e0a35c;
}

可覆盖的品牌属性有 --td-brand-elev(浮层底色)、--td-brand-silk(次要文字)、--td-brand-copper 与 --td-brand-copper-dim(强调色与它的弱化版)、--td-brand-line-strong(分隔线)、--td-brand-header-bg(顶栏背景)、--td-brand-shadow-sm / --td-brand-shadow-md(阴影)、--td-brand-mark-from / --td-brand-mark-to / --td-brand-mark-gradient(品牌渐变)。

分区主题色

上面的品牌配色决定整站外观。theme_color 用一个十六进制颜色调整当前分区的 界面强调色,让读者通过选中状态和导航提示辨认所在分区。

hugo.yml
params:
  ui:
    theme_color: '#6d28d9'
    theme_color_dark: '#a78bfa' # 可选

它按分区写比按站点写有用得多。写进分区根的 cascade,整个分区就有了身份 —— 藏青的文档、紫色的博客、橙色的教程 —— 而站点默认仍是品牌色:

content/blog/_index.md
cascade:
  theme_color: '#6d28d9'
  theme_color_dark: '#a78bfa'

Hugo 会把这些 cascade 值同时解析到分区首页与子页,因此只声明这一对即可。同一对 解析结果既驱动页面强调色,也驱动根切换器里该分区的图标。

它作用于:侧栏选中行与悬停背景、当前目录项的背景及其指示线和位置标记、 悬停或聚焦的 Book 章节小标题、标签与徽章的悬停状态、卡片的悬停边框、分享按钮的 悬停背景、文本选中背景、焦点环,以及侧栏根切换器中的分区图标。

它刻意不作用于:正文链接、外链、行内代码。这些是阅读约定,不是品牌表面 —— 一页密集的标识符在任何分区都该读成「代码与正文」,链接在哪里都该看起来像链接。 这也是强调色单独占一个自定义属性、而不是去重刷 Bootstrap 链接色的原因。

暗色一半是可选的。省略时,从亮色向白提亮,直到在暗色画布上达到 AA 正文对比度, 所以只填一个颜色的作者不可能产出不可读的暗色配色。派生结果不再符合期望的品牌 色相时,再自己指定暗色一半。亮色才是主键:单独设置 theme_color_dark,或者把它放在 一个非法的 theme_color 旁边,两种模式都不会着色 —— 主题会发出警告并保留默认配色, 而不是只给暗色模式上色。

要让某一页恢复默认配色,在 front matter 中设置 theme_color: false。 这会同时取消继承的亮色与暗色分区配色,不产生警告。其他非十六进制取值 (数字、true、颜色名)都会告警。

对比度是检查,不是强制

主题按默认页面背景检查颜色,低于 AA 正文对比度(4.5:1)时告警,颜色仍会生效。 若使用自定义背景或必须保留品牌色,可根据实际对比度选择是否忽略该告警。 发布构建使用 --panicOnWarning,因此需要调整颜色,或将告警给出的 ID 加入 ignoreLogs 后才能通过。

这个检查是拿颜色对着页面画布读的。有些交互表面会同时把它用作文字与半透明淡铺; 例如可点击的实心徽章在 hover 时,是强调色文字压在 12% 的同色淡铺上。这一对比 画布检查更紧。如果颜色只是刚好过线,还要检查这些表面,必要时再调深一档。

Hugo 按键合并参数:某一页在同时设了 theme_color_dark 的分区里只覆盖 theme_color,会继承那个暗色。要么两个都覆盖,要么都不覆盖。

深浅色模式

主题默认 不显示 深浅色控件。开启方式:

hugo.yml
params:
  ui:
    dark_mode: true

太阳/月亮图标表示当前状态:浅色显示太阳,深色显示月亮。 点击顶栏或底栏的「外观」,打开浅色、深色、跟随系统单选组,支持触屏与键盘, 手机上显示为底部表单。选择保存在浏览器本地并同步到其他标签页,没有选择时跟随 prefers-color-scheme。head 内联脚本在样式表加载前应用实际明暗状态。

只要深色调色板、不要控件时写 dark_mode: { show_menu: false, enable: true };dark_mode: false(默认)两者都不启用。

自定义组件在两种模式下都要给出可读的悬停、聚焦、禁用、选中状态,正文对比度至少 4.5:1、大号文字 3:1。

字体

字体策略在构建期选择,切换视觉预设时使用对应的内置字体:

hugo.yml
params:
  ui:
    typography: technical # technical | system
  • technical(默认):Paper 的界面、正文与展示标题使用 IBM Plex Sans;Slate 的界面与正文使用 Inter,展示标题使用 Chakra Petch。两者的字标使用 Chakra Petch,代码使用 IBM Plex Mono。中文与 emoji 落到平台字体。Plex Sans 与 Inter 包含本地拉丁、西里尔、希腊与越南语子集,不请求 Google Fonts。
  • 实验字体:Ink 的界面、正文与标题使用 Inter;Terminal 的控件与标题使用 IBM Plex Mono,正文使用 Plex Sans。两者复用现有代码字体。显式 fonts.ui 仍控制主字体;需要独立正文字体时设置 fonts.body。
  • system:界面、展示、元数据、打印与等宽角色全部回到平台字体栈,浏览器不请求品牌字体。字体文件仍随主题分发,只是不被引用。

非法取值告警并回落到 technical,普通 hugo server 照常可用;发布门禁开着 --panicOnWarning,这类告警在那里才是硬失败。选中的值写入 <html data-td-typography="…">,可在浏览器中确认。

自定义字体

字体角色是八个 CSS 自定义属性,覆盖它们即可,不必查找组件选择器:

属性 配置键 用在哪
--td-ui-font-family ui 导航、控件与界面文字
--td-body-font-family body 正文与博客
--td-heading-font-family heading 正文标题
--td-code-font-family code 代码与终端
--td-display-font-family display 展示型大标题
--td-meta-font-family meta 技术标签与元数据
--td-brand-font-family brand 字标
--td-print-font-family print 打印正文

ui 是主字体:body 经它解析,heading 又经 body 解析,所以只写 ui 一行,界面、正文与标题一起换掉。

在配置里换

只是想换一套字体族,不必碰 SCSS,写 params.ui.fonts 即可:

hugo.yml
params:
  ui:
    fonts:
      # 主字体:界面、正文、标题一起跟着走
      ui: "'Source Han Sans SC', 'PingFang SC', sans-serif"
      # 等宽要带中文兜底,否则中英混排的代码块会对不齐
      code: "'Sarasa Mono SC', 'Noto Sans Mono CJK SC', monospace"

这里写的是字体族名,不是字体文件。主题不会因为这个键去下载或加载任何字体:所写的族必须是读者机器上已有的,或者站点自己在样式表里 @font-face 声明过的。所以每个列表都要以通用族(sans-serif、monospace、serif)收尾——读者没有你写的字体时,落到那里。

取值只放行纯粹的字体族语法:带引号的名字、裸标识符、允许前导连字符(-apple-system),以及任何文字系统写成的名字(苹方 合法)。分号、花括号、括号、url()、尖括号一律不过关。未知角色或不合法取值只告警并单独丢弃,同一份 map 里其余的行照常生效。什么都不设时,<head> 里连这个 style 元素都不会出现。

该块在样式表之后输出,这正是作者字体能在同等优先级下压过 typography 预设的原因。

在样式表里换

要自带字体文件,或者只给某一类内容换字体,仍然走样式表。把 .woff2 放进站点 static/webfonts/,在项目样式中声明字体,再指定各角色使用的字体族:

assets/scss/_styles_project.scss
@font-face {
  font-family: 'My Sans';
  font-display: swap;
  font-style: normal;
  font-weight: 400 800;
  src: url('../webfonts/my-sans-variable.woff2') format('woff2');
}

:root {
  --td-ui-font-family: 'My Sans', 'Noto Sans SC', sans-serif;
  --td-body-font-family: var(--td-ui-font-family);
  --td-heading-font-family: var(--td-ui-font-family);
  --td-display-font-family: var(--td-heading-font-family);
}

角色按 CSS 规则继承,只给某类内容换字体也不必复制组件选择器:

assets/scss/_styles_project.scss
body.td-blog {
  --td-body-font-family: 'My Serif', 'Noto Serif SC', serif;
  --td-heading-font-family: var(--td-body-font-family);
}

等宽字体要带中文兜底,否则中英混排的代码块会对不齐:

assets/scss/_styles_project.scss
:root {
  --td-code-font-family: 'My Mono', 'Sarasa Mono SC', 'Noto Sans Mono CJK SC', monospace;
}

从 Docsy 迁移过来的站点不必改写法。旧的 Sass 变量仍然喂进对应角色,写在 _variables_project.scss 里照样生效,优先级高于预设默认值:

旧 Sass 变量 喂给的字体角色 说明
$td-fonts-serif --td-ui-font-family / --td-body-font-family Docsy 的界面字体栈,赋值给 $font-family-sans-serif
$font-family-sans-serif --td-ui-font-family / --td-body-font-family 项目给出自己的栈时,technical 预设不再把预设内置的无衬线字体放在它前面
$font-family-base --td-ui-font-family / --td-body-font-family Bootstrap 的正文变量,经 --bs-body-font-family 进入角色
$headings-font-family --td-heading-font-family 不设置时标题继承正文角色
$font-family-code --td-code-font-family 代码、终端与 pre / code / kbd
$td-font-family-monospace --bs-font-monospace 赋值给 $font-family-monospace
$font-family-monospace --bs-font-monospace system 预设下,项目的显式取值优先于平台等宽栈

Docsy 的三个 Google Fonts 变量 $td-enable-google-fonts、$td-google-font-name 与 $td-web-font-path 主题已不再读取。它们留在 _variables_project.scss 里不影响构建,也不产生任何效果:随主题分发的是 IBM Plex Sans、Inter、Chakra Petch 与 IBM Plex Mono,所有预设都不向 Google Fonts 发请求。打印角色 --td-print-font-family 跟随正文角色,主题不为纸张单独提供字体。

YAML 里只接受字体族名。远程字体 URL 与任意 CSS 都不接受:字体文件与样式必须是可审查的本地输入,一次普通构建不会因为字体发出任何网络请求。

页宽

hugo.yml
params:
  page_width: normal # normal | wide | full

page_width 控制外壳整体宽度,可逐页或按分区 cascade 覆盖。Book 页另有一个 reading_width(slim / normal / wide),改的是正文阅读行宽,不是外壳。 两个键取值非法都会在普通预览中告警并回退;带 --panicOnWarning 的发布构建会失败。

hugo.yml
params:
  ui:
    footer_style: fat # fat | slim | none
  copyright:
    authors: '[产品团队](https://example.com/)'
    from_year: 2026
    to_year: present
  footer_center_info: 'Powered by [Oink](https://oink.pgsty.com)'
  • fat(默认):多列链接网格 + 版权行;
  • slim:只有版权行;
  • none:不渲染页脚。

页面 front matter(含分区 cascade)可以覆盖它,本站的文档栏目用的是 footer_style: slim。无法识别的取值在普通预览中告警并回退到 fat,严格发布构建 拒绝这条警告。

多列网格的数据在 data/footer/<语言>.yaml,写法见导航与菜单。配了 fat 但没有数据时自动降级成 slim,可以先开启再补内容。

params.copyright 接受 Markdown 字符串,或 authors / from_year / to_year 三键的 map(present 表示今年)。footer_center_info 是页脚中间的行内 Markdown,显式设为空字符串即隐藏中间区域。

SCSS 入口与不该做的事

站点的 SCSS 覆盖进入主题的同一个样式包,生产构建仍然只有一份带指纹与完整性校验的样式表。三个入口文件放在站点 assets/scss/ 下:

文件 什么时候用
_variables_project.scss 在 Bootstrap 与 OINK 默认值之前设置 Sass 变量($primary、字体变量)
_variables_project_after_bs.scss 设置依赖 Bootstrap 已有定义的变量或 map
_styles_project.scss 在主题组件样式之后写选择器与 CSS 自定义属性

编译顺序是:Bootstrap 函数 → 项目变量 → OINK 默认值与 Bootstrap → Bootstrap 之后的项目变量 → OINK 组件与品牌层 → 项目样式。

CSS 接口有明确边界。字体那一节的八个字体角色与 --td-brand-* 品牌属性是公开接口,主题在小版本之间保持它们的名字与含义。组件别名(如 --td-asciinema-font-family)只承诺在该组件范围内有效,未在文档中记录的 --td-shell-* 一类变量是实现细节,随时可能改名或消失。

不该做的事:

  • 不改主题目录里的任何文件(hugo mod 会覆盖);
  • 不单独 @import 主题的内部 partial,它们不是公开的 Sass 接口,导入顺序可能变化;
  • 不为了改一个颜色去覆盖 baseof.html。有设计变量就用变量,没有再写作用域尽量小的选择器;
  • 不引用远程样式表或字体 CDN。

需要额外的第三方 CSS 时,用 layouts/_partials/hooks/head-end.html 钩子发布本地资源,不在 Markdown 里写 <link>。

验证

hugo --printPathWarnings --panicOnWarning
  • 构建输出 Total in …,没有 ERROR / WARN;
  • 页面源码里 <html> 上有 data-td-typography="technical"(或所选的预设);
  • 浏览器中顶栏显示自己的 Logo 与站名,标签页图标是自己的 favicon;
  • 切到深色模式再看一遍正文、表格、提示块、代码块与焦点框。配色改动容易只在一种模式下验证过;
  • 换一种语言,确认站名随之切换。

字体是否已替换,用浏览器开发者工具查任意一段正文的 font-family:应当包含自己设置的字体族;再查看实际渲染字体,确认文件加载或系统字体回退符合预期。