跳转到主要内容

外壳与导航契约

导航权威、沉浸式博客、搜索、操作、分类法、索引与页尾组合契约。
OINK 1.2.0 契约

本契约描述 v1.2.0 的正式行为。唯一的中英文契约源文件位于 content/docs/design/。

权威来源与导航

关注点 权威来源
全局导航 Hugo menus.main
Docs / Book 侧栏与翻页 内容树或 data/docs_nav.json
根栏目切换器 解析后的顶层内容根
内容发现 各语言的本地搜索索引
页面与命令面板操作 共享操作注册表

任何功能都不能引入另一套菜单或页面树。菜单只允许一层子项交互;更深层级会警告, 并平铺到带链接的分组标题下。外部链接使用 target="_blank" rel="noopener noreferrer";内部链接保持语言与子路径感知。

顶部导航栏的桌面视图与抽屉视图投影同一棵树,每个下拉面板都是一列宽度适中的 “图标 + 标题"行——mega 面板与其 columns 菜单参数已退役,配置 columns 会发出警告并保持单列。菜单描述只是配置数据,不再渲染。链接树在任何宽度都保持居中: lg 以上是文字链接,之下收缩为图标链接。lg 与 md 之间,右端保留搜索、版本、 语言、主题与 GitHub,没有菜单按钮;md 以下外观入口保留在搜索与抽屉入口旁,版本、语言与 GitHub 移入底栏工具组,此时首页 与显式 Landing 页在搜索旁增加一枚抽屉入口,展开完整的带标签菜单树。带侧栏的外壳页面 在这个位置打开自己的侧栏抽屉;从 md 起均不显示抽屉按钮。语言链接指向页面译文,缺少译文时 指向对应语言首页;多个语言共享主机与 base path 时保持相对链接,只有语言拥有 独立 baseURL 时才变成绝对链接;hreflang 始终使用绝对链接,而且只列出真实译文, 不把切换器提供的语言首页回退当成译文。博客分页使用每一页自己的 canonical URL; 后续分页不输出跨语言 alternate,因为不同语言归档的分页边界未必相同。 navbar_autohide 从 768px 起只对精细指针生效,绝不作用于触控或抽屉宽度; 隐藏的导航栏不交还占位:两种状态下布局都保留导航栏横带,固定顶栏正好占满这条 横带、下边框画在带内,显现时原地淡入、不遮挡静止内容,hero 页面忽略该策略、 保留自己的叠加导航栏。首页与 hero 页面共用同一套柔和边界:导航栏不画下边框、 滚动时不投阴影,改由栏下一小段渐隐过渡收束边缘。

侧栏与翻页共享同一个根和顺序。manual_link、build.render: link、分隔行、 隐藏节点与占位节点保留各自已定义的语义。sidebar_icon_policy 可取默认的 all、 groups 或 none;图标是一对 Font Awesome class。无效策略遵循共享的警告与 回退契约。达到 sidebar_cache_limit 后,两种 walker 只有在语言、导航根与实际 影响输出的有效设置均相同时才复用中性标记。没有 JavaScript 时这份标记仍然可见; 普通外壳运行时只补上 active 路径。会输出 sidebar_headings 的 Book 页面保持页面 专属,并绕过共享树缓存。

根候选先收集可链接、非分隔项的顶层分区,再收集 sidebar_root_for: self 分区,并按 URL 去重。 两种来源都遵守显式的 sidebar_root_menu: false;未设置或 true 时保留。 当前解析出的根即使被排除在全站候选之外,仍会追加。零入口不输出控件,单入口 输出静态链接;所有 URL 保留语言和部署路径前缀。分隔项和使用 build.render: never 的分区不会成为切换器链接。

sidebar_divider 叶子保持静态标题;分区节点在两套侧栏遍历器中保留子项,标题不跳转, 启用折叠时提供真正的展开按钮。分组自身不进入翻页序列,子页保持原有顺序。配合 build.render: never 可省略分区自己的输出而不隐藏后代。面包屑显示不带链接的标题, 搜索跳过分组,导航 JSON 提升其子节点,Print 保留子文档。Book 目录保留分组标题和子项链接, 但跳过未发布的分组正文标题。toc_hide 仍隐藏整棵子树, 不能代替分组。显式导航键使用不含语言和部署前缀的路径,实际链接则保留这两个前缀。 显式导航的 sections 数组为空时发出警告,并在所有导航输出中回退到内容树。 两种权威来源都剪掉 toc_hide 子树;导航 JSON 将 manual_link_relref 保留为指向 解析目标的内部链接,不把占位页自身当成页面身份。

自 OINK 1.1 起提供

OINK 1.1.0 提供本节的展开状态 API 与隐藏内容隔离;1.0.0 不提供该 API。

window.OinkSidebar 管理已注册的树分支及可搬迁的 TOC、反向链接和分类法分组, 不依赖它们当前的 DOM 父节点。setExpanded(id, boolean, {source}) 对有效目标返回 true,对未知 ID 或非布尔值返回 false。getState(id) 返回新的 {id, expanded} 快照 或 null。ID 使用现有 aria-controls 指定的受控区域 ID,不能修改注册范围之外的元素。

来源为 user、active-path、responsive 和默认的 api。所有写入先提交 aria-expanded、td-is-open、本地化标签和区域的 inert 状态,再向 document 发送一次 oink:sidebar-disclosure 事件,详情为 {id, expanded, source}。重复写入相同状态不发 事件。API 恢复时保留当前路径祖先展开,用户仍可主动折叠这些分支。关闭含焦点的区域时, 先把焦点归还给展开按钮,再执行隔离。

ready 是初始活动路径补全和响应式搬迁完成后解析为 API 的 Promise;isReady 和 oink:sidebar-ready 也提供可供晚加载消费者检查的完成状态。可选的持久化由站点负责: 等待 ready,在 try/catch 中读取存储,再通过 setter 恢复有效 ID。OINK 自身保存整栏 折叠、宽度和滚动位置,不定义读者分支选择的版本/语言存储格式。

桌面折叠和移动抽屉关闭时,面板内容设为 inert,面板标记 aria-hidden。先移出焦点再 隔离,打开时先解除隔离再聚焦。面板本身继续充当 16px 指针感应区,外部恢复按钮保持 可用。悬浮、Escape、遮罩关闭、断点清理和滚动解锁保留原有行为。这些运行时属性不进入 无 JavaScript 的服务端回退标记。

TOC 整栏折叠时也隔离其隐藏面板。如果焦点原本位于折叠按钮,先移至可见的浮动恢复按钮; 恢复整栏后,焦点回到栏内可见的控制按钮。右侧内容搬入移动侧栏时,先清除原先的整栏 隔离,再由抽屉管理交互。抽屉的 Tab 循环只包括可见、非 inert 的控件,排除隐藏或 折叠区域中的后代。

沉浸式博客展示

OINK 没有 article 类型或第二套外壳。沉浸式阅读由普通博客外壳上的四个独立键 组成,可设在页面或分区 cascade 上;分区索引会重复它自己也需要的值:

featured_image: hero
toc_style: flow
toc_taxonomies: false
sidebar_enabled: false

博客外壳默认不渲染面包屑导航——文章应作为独立作品阅读——所以这份配置不需要 相应的键。breadcrumb 仍是普通键,页面或 cascade 可以在任何外壳上明确打开 或关闭它。

hero 在单页与分区索引上把共享特色图片用作装饰性的全出血背景。没有图片时 渲染普通开场;banner 与 wash 仍只用于单页。顶部导航栏以对比遮罩叠在 hero 上,并随页面一起滚动。

toc_style 可取 fixed 或 flow;flow 在文章旁放置更宽的导轨,并且只在滚动 之后固定。它的静止位置与文章信息行对齐;页面没有信息行时,与描述对齐。标题 换行数无法预知,因此由 docs-shell.js 测量偏移;没有 JavaScript 时,导轨从 文章起点开始。toc_taxonomies: false 移除术语云;导轨既无 TOC 又无术语云时 完全不渲染。notoc 仍是页面级 TOC 退出键。这些开关不改变署名、标签、系列、 翻页顺序、feed 或页尾组合;导轨在 xl 断点以下消失。

搜索、操作与运行时

params.offline_search 选择启用各语言的本地索引。启用后默认也在 hugo server 期间构建;大型编辑循环可以设置 offline_search_on_serve: false。HTML 搜索出现 在首页、外壳页面,以及启用 landing_search 的落地页上。其它非外壳页面与 Print 不包含对话框、Lunr 或命令面板。

搜索元数据包括 search_keywords、默认值为 1 的 search_boost,以及 search_exclude。索引携带 URL、标题、分类法、摘录、小标题、description、 正文或摘要、根、分区、类型、关键词、boost、面包屑导航与图标。夹具预算为原始 2 MiB、gzip 512 KiB。站点可以通过 hooks/search-keywords-extra.html 返回额外 字符串。关键词用于匹配与排序;CJK 查询仅命中关键词时,结果显示页面描述或 摘录,不展示关键词列表。正文命中仍使用匹配位置附近的文字作为摘要。

内置操作 ID 包括 copy_markdown、copy_link、open_chatgpt、open_claude、 view_markdown、view_history、edit_page、create_child_page、create_issue、 create_project_issue、print_section、print、switch_preset、switch_theme、 switch_language、switch_version 与 open_github。分享栏之外的 copy_link 只出现在命令面板中。站点通过 languages.<lang>.params.ui.command_palette.commands 配置的命令可以打开安全 URL,或调用内置 ID,绝不能注入 JavaScript。剪贴板的旧式回退恢复此前的焦点、选区及其方向, 但复制期间其他控件已获得焦点时不再抢回。

编辑、历史与新建子页操作要求源文件具有仓库相对路径。物理文件名和站点工作目录 先统一为 / 分隔符,再判断包含关系。path_base_for_github_subdir 匹配归一化后的 路径:站点内内容使用相对工作目录的路径,外部挂载使用绝对路径。字符串正则可以 移除匹配的内容,{from, to} 映射可以替换它;外部来源必须显式匹配规则。 映射并整理路径后,空路径、.、绝对路径、带盘符的路径以及以 .. 路径段开头的 结果都会隐藏这三项操作。文档 issue 与项目 issue 操作仍按各自的仓库配置提供。 Windows 映射应匹配 / 而不是 \;归一化不会改变文件名大小写。

命令面板有空状态、文本搜索状态与 > 命令状态;快捷链接来自导航。它没有历史、 语义搜索、个性化或远程回退。搜索查询留在浏览器内,默认不发送遥测。

OinkSurfaceCoordinator 协调命令面板、抽屉、根栏目、语言与版本菜单。各界面自行 管理焦点恢复与 Escape。键盘导航会忽略可编辑控件与模态框,Ctrl/Cmd+K 同样避让 其他已打开的 dialog,包括固定定位的 ARIA 对话框。/、\、f、c 打开搜索或命令;j/k 移动标题;q/e 翻页;h 改变展示方式;l/y、 t、r 分别打开语言、主题与根栏目选项。侧栏 WASD/方向键导航使用真实焦点, 不会改写 Tab 顺序。 不带链接的分隔分组按钮也参与树导航。从子页按 Left/a 先聚焦父分组,再按一次才折叠; Right/d 展开已折叠的分组,已展开时进入第一个可见子项。上一页/下一页仍只遍历链接, 不会把分组按钮当成页面。

页面大纲从同一套标题模型与滚动容器计算后的 scroll-padding-top 推导光标和可见 标题范围;SVG 线条与圆点共享同一组动画值,不会漂移。合法 URL 片段会被解码; 非法百分号序列则回退到字面的标题 ID,建立链接索引和选中页尾请求的标题时遵循 同一规则。禁止增加臆测性的 DOM 修复遍历。这项跟踪始终由普通外壳运行时负责。params.ui.scroll_spy 与页面键 scroll_spy 在整个 1.x 期间都是静默兼容 no-op,不加载独立运行时;只有未来的 破坏性版本才会删除它们。

搜索尾部扩展

自 OINK 1.1 起提供

OINK 1.1.0 包含该 API,1.0.0 中不存在。

受信任的站点 JavaScript 可调用 OinkCommandPalette.registerSearchTail({id, rows, activate});YAML 和操作清单仍然只接受 数据。资源包继续按本地搜索开关装配。注册要求唯一且符合 [A-Za-z0-9][A-Za-z0-9_-]* 的 ID,以及两个函数;非法或重复注册抛出异常。返回的注销函数可重复调用,旧句柄不能删除 复用该 ID 的新注册。面板打开时的注册变化会合并调度一次渲染,注销会取消该扩展的待完成操作。

rows(context) 同步返回描述符。context 是冻结的 {query, locale, phase, pageResultCount} 快照:query 去除首尾空白,locale 使用 HTML 的语言标签,phase 为 results、empty 或 error,数量只统计上限截取后的本地页面结果。仅在非空文本搜索完成后调用扩展,不在空查询、 命令、选择或加载状态调用。扩展行按注册顺序放在本地化的 Actions 组中,排在全部原生页面及 操作之后。原生空结果、错误提示与输入触发的索引重试保留。

描述符必须提供扩展内唯一、符合相同语法的 id,以及非空字符串 title。 可选的 description、icon、disabledReason 为字符串,available 为布尔值,默认 true。 OINK 复制并冻结这些字段,把显示字符串当作文本渲染。描述符非法、ID 重复、返回异步结果或 回调抛出异常时,本轮跳过整个扩展,其他扩展不受影响。不承诺回调的精确调用次数。

activate(row, context) 只通过普通结果行的激活路径调用,收到复制后的描述符和生成该行时 的上下文,另带 AbortSignal 与 handoff()。操作待完成时阻止其他结果行激活,包括进入原生 选择菜单。同步异常和 Promise 拒绝会释放待完成状态、保持面板打开,并播报本地化的操作失败信息。成功值被忽略;成功时关闭 面板,不从其他界面抢回焦点。关闭、重新打开、渲染的查询改变或注销会取消待完成操作,旧操作 的迟到结果不能修改新会话。

打开另一个受协调器管理的界面前,调用 context.handoff()。它关闭面板,但不归还焦点,也不 取消本次激活。随后由站点管理新界面的焦点与错误提示。之后的新 Palette 会话或注销仍可取消 尚未完成的操作;成功完成不会取消已经移交的操作。OINK 不强制超时。

rows() 必须保持纯净。这是受信任代码的契约,不是安全沙箱。默认查询仍留在本地,不内置 远程服务商或遥测。扩展的网络行为和服务商所需授权由站点负责。

分享

params.ui.share 默认为空,可接受 16 个目标的任意有序子集:x、bluesky、 mastodon、facebook、linkedin、reddit、hackernews、telegram、 whatsapp、line、pinterest、weibo、chatgpt、claude、email、copy。 页面列表会替换继承列表;share: false 退出。未知项会警告并丢弃。只有普通页面 渲染分享栏;Print、Markdown 与 RSS 省略它。

目标是携带页面永久链接与标题的普通 intent 链接,外加本地 copy_link 按钮。 Pinterest 图片来自共享特色图片解析器。ChatGPT 与 Claude 接收构建期生成的永久 链接提示,与页面菜单里的助理操作相互独立。Discord 没有公共 intent 目标,因此 有意不提供。

分享栏不加载平台 SDK、iframe、脚本、样式表、计数器或 campaign 参数;只有读者 主动点击链接时才产生请求。它是一行带无障碍标签的字形。 share/items.html 解析目标,share/bar.html 负责渲染。

注记

页面注记在 annotation-items.html 中解析描述项,再通过 page-meta-lastmod.html 渲染;两者都可以做窄范围覆盖。各行顺序如下:

行 条件
最后修改 已设置 Lastmod
上游 front matter 中的 upstream_link 非空
翻译 配置的权威语言存在译文,而且本页包含作者正文

upstream_link 是页面级事实;cascade 有效,upstream_link: "" 表示退出。 其它上游事实按站点参数 → data/upstreams[upstream_source] → front matter 解析: upstream_name、upstream_copyright、upstream_license、upstream_notice, 以及可选的 upstream_ref、upstream_modified。存在链接时,前四项必填。无效或 残缺的署名会警告,而且不渲染法律声明;不支持的 URL 会被拒绝。发布门禁通过 --panicOnWarning 拒绝这类警告。

upstream_modified 改变署名动词并链接提交历史,不增加新行。notice 页面承载 完整的许可证与免责声明。翻译说明通过 params.ui.translation_notice 选择启用, 以页面键 translation_notice 参与 cascade,跳过生成页面或无正文页面;以本语言 原创的页面可以用 translation_notice: false 关闭。

作者与系列

博客文章页头依次为标题、信息行、术语徽章、作者署名、系列条;description 在其后 引出正文。信息行 article-info.html 始终包含日期;启用 reading_time 后再增加 字数与分钟数。Front matter 的 upstream_link 与注记使用同一个页面级事实, 并在共享 URL 策略保护下增加本地化的原文链接。术语行只是裸徽章组,分类法名称 位于分组标签中,不显示前缀。术语徽章静止时是浅中性底与弱化文字,前置该分类法的 term 图标;可点击徽章在 hover 或 focus 时才取得当前分区的强调色淡铺、边框与文字。 图标词汇表由 taxonomy-icon.html 独家拥有——每个 分类法配一对图标:整体分类法一枚、单个术语一枚(folder-open/folder、 tags/tag、cubes/cube、users/user-pen、series 用 book-bookmark/book,其余用 shapes);params.ui.taxonomy_icons 可覆盖: 字符串同时作用于两个表面,taxonomy/term map 分别设置;无效输入警告并保留 内置。右栏词云只在云头戴整体图标:云 chip 保持"文本 + 计数”——分类法已经亮明 身份,再在每个 chip 上重复图标只是噪声。独立的分类法目录卡片会带一枚术语图标; 作者署名只放人物——头像、姓名与个人资料的一行简介—— 不带标签或日期。列表行、卡片与术语归档共享同一形态的元数据行:日期、一条本地化 的作者与分区短语,以及由同一个 reading_time 开关控制的字数和分钟数。句子下方 是独立成行、自动换行的徽章行,按分类法字母序列出页面在全部分类法下的词条,每枚 徽章佩戴各自的 term 图标;卡片排除 authors——其句中已具名。

只有声明 taxonomies: {author: authors} 才启用作者。作者 term 页面拥有显示名称、 摘要、正文与特色图片头像;没有 profile 时,回退到链接标题、首字母与归档。 authors-resolve.html 在文章页头、列表行中保留 front matter 顺序,并为每位作者 生成一个 RSS dc:creator。没有 authors 时,旧 author 保持原样;两者同时 存在时,authors 无警告胜出。自定义作者分类法复数名按普通分类法处理。

只有声明 taxonomies: {series: series} 才启用系列。Term 页面拥有引言;不新增 参数、数据文件、封面模型或运行时。页面使用 series: [name] 与可选的 series_weight。series-pages.html 先按 weight 排有权重成员,再按日期升序排 无权重成员,并用 Path 打破平局;系列条与 term 页面共享该顺序。第一个命名系列 得到一条 HTML/Print 系列条。面板是半透明加模糊,而不是一张不透明卡片:hero 文章会把题图铺在这一段背后,不透明底色等于在画面上挖个洞;普通文章上这层色调 就落回页面自身的底色,所以一种处理同时服务两种场景。summary 拥有整行与末端 箭头;系列名连同它的分类法图标,仍是 summary 的兄弟链接,覆盖在一份隐藏的等宽 占位文字上,避免 summary 内出现嵌套交互控件。展开后先划一条细线,再在同一层 表面上把成员阅读顺序放进一个保持 DOM 顺序的自适应网格。每个链接都把序号纳入 点击目标,序号贴在固定方格轨道的末端,因此无论多少篇,标题都对齐在同一条边上; 窄屏保持一栏,只有当每个标题仍有可读宽度时才增加等宽栏,因此桌面面板能用满自身 宽度,也不会把一条选中背景拖过整篇正文。悬停与读者所在位置直接借用侧栏导航 处理这两种状态的同两种底色,当前篇再加上填充序号与加粗标题,不靠颜色单独表意。打印时显示同一份展开 列表,收为单栏。单篇系列与非 HTML 输出省略它。编号、交叉引用与聚合输出仍属于 Book。

默认文章分类法徽章会排除保留的 authors 与 series,因为专属界面已经展示 它们。显式设置 params.taxonomy.page_header 可以恢复任意一项。

博客索引与页面组合

博客分区索引使用 params.ui.blog_index:默认的 list 与 cards 都是按最新优先 排列的一段扁平结果,共享 blog_index_size 分页;元数据行已经显示日期,所以不再 需要年份标题。table 把整个分区显示为日期、标题、标签行,不分页。卡片使用共享 首图、本地化日期/作者/分区元数据、标签与三行摘要。

分类法页(/tags/、/authors/)与其术语页共用一个页头 shell/taxonomy-head.html。分类法页以整体分类法图标的着色方块、本地化名称与 术语数开头;术语页以术语标题与取自 ui_taxonomy_pages、按当前 locale 的 CLDR 复数类别选择的页面数开头,没有渲染面包屑时标题上方再加一行 kicker,写明分类法并链回列表页——面包屑开启时它在上一行已经做了这两件事:代表 生成的分类法页的那一级面包屑借用页头同一个本地化标签,而不是 Hugo 的复数名标题。 页头之下,分类法页把术语排成单行卡片网格 shell/taxonomy-cards.html:使用次数 多者在前、同数按字母序(与右栏词云同序),以 auto-fill 填满等宽列,因此术语 很少时两张卡片也不会被拉宽到整页。一张卡片就是术语图标、术语名与页面数,整张 卡片即链接;只有作者以署名同款小头像开头,走同一个头像 partial。卡片不带描述、 不带最新一页:术语没有标题与计数之外值得一说的内容,多出的那一行只会让网格发糊。 不再有筛选芯片行与「全部」芯片:分区根已经在侧栏与顶栏里。术语页保持行列表, 作者资料页保留自己的页头。

分类法页与术语页的右栏以 shell/taxonomy-switcher.html 开头:声明的每种分类法 一行——整体图标、本地化名称、术语数——链向其列表页,当前分类法置于选中底色上。 这是从一种分类法的页面去另一种的路:词云芯片跳到术语页,词云头只负责折叠;只有 一种分类法的站点不渲染切换器。这一组与词云共用 toc_taxonomies 开关。分类法页 的词云按全站统计(taxonomy-root.html 对该 kind 不返回根),并省略自己那一组, 它的术语就是旁边的卡片;术语页保持分区作用域与完整的一组。

params.ui.blog_index_toggle 为当前分页切片渲染三种形态,并允许读者循环切换。 配置值控制首次绘制,隐藏形态不加载图片。读者存储的选择只作用于发布了全部三种 形态的索引:切换器关闭的分区只发布一种形态,并始终显示它。Front matter 或 cascade 可为每个分区覆盖站点模式。没有切换器的 table 仍是完整且不分页的归档。

params.logo 始终是品牌标志;params.wordmark 或站点标题是紧凑宽度下隐藏的 文字部分。Docs、Book、Blog 与 Swagger 共享一个外壳模型。页尾顺序为分享、反馈、 注记、翻页、评论。Docs/Book 翻页遵循侧栏前序遍历;Blog 按 weight 后接日期倒序; pager: false 退出。静态输出省略翻页 UI。

每一种实际渲染的页脚形态,都会在最底层栏右侧保留纯图标工具组,顺序为版本、 语言、主题、快捷键帮助。各菜单向上展开;版本触发器不直接显示当前分支或版本名。 胖页脚的折叠箭头排在工具组之后。低于 lg 时,底层栏放弃版权/居中/工具组的 三列布局,改为三行全宽居中堆叠,工具组在最后一行。这些全局控件不再出现在 侧栏底部;footer_style: none 会移除整条底栏。

OINK 没有归档外壳、任意深度飞出菜单、第二个导航权威、查询上传,也没有针对已 移除配置的浏览器兼容 shim。反馈只通过既有 gtag 发出 docs_feedback,在本地 保存选择,而且不替代 Giscus。

验证

bin/check-navigation-contract.py、bin/check-shell.py、JavaScript 测试、输出 golden 与消费站点浏览器套件覆盖导航、语言与子路径链接、博客变体、页尾顺序、 键盘行为、无障碍与响应式布局。

外观控件

顶栏与底栏共用点击或键盘展开的外观控件,Landing 手机抽屉另有带标签的入口。 preset_menu 允许选择时显示原生风格单选组;dark_mode.show_menu 开启时显示 浅色、深色、跟随系统单选组。选择立即生效,面板保持打开。Enter、Space 或向下 方向键展开;方向键在组内选择,Tab 在组间移动,Escape 关闭并归还焦点。触发按钮 图标表示实际明暗状态:浅色显示太阳,深色显示月亮,跟随系统变化时也同步更新。

英文分组标题为 Style 和 Light,中文为「风格」与「明暗」。风格选项以两列独立 按钮排列,使用带主题色的纸页、叠层、笔尖或终端图标与预设名称,不显示 Aa 预览 或实验标记。站点默认项在悬停提示与无障碍名称中注明,选择后清除保存的风格。 选中项使用淡色背景与边框,键盘焦点另有轮廓线。

桌面使用锚定触发器的非模态 dialog,外部点击或焦点离开时关闭。低于 768 px 或 从 Landing 抽屉进入时,通过 showModal() 在浏览器顶层打开底部表单,提供关闭 按钮和 44 px 选项目标。关闭表单保留下方抽屉。表面协调器在展开前关闭无关弹层。 t 快捷键继续通过 switch_theme 切换明暗;switch_preset 是独立的命令面板选项。

本地 Ink/Terminal 实验仍需显式配置;preset_menu: true 继续提供 Paper/Slate 与站点默认值。两者复用同一套状态、键盘、命令面板与底部表单 机制。Terminal 压紧桌面导航行,保留正文与手机触控目标尺寸。