Files
wordpress-kratos-yang/assets/css/toc.css
T
laoyang 6f51d50a58 3.2.30: 文章目录进后台(新增「自动目录」标签页)—— 位置/插入点/默认展开可配,手写 [TOC] 永远优先
后台「主题设置」新增第 5 个标签页「自动目录」,7 个选项:
  目录位置   正文中间 / 页面左侧(浮动)/ 页面右侧(浮动)
  添加方式   手动(只认 [TOC])/ 自动
  自动插入位置  文章开头 / 第一个 H1、H2 之前 / 文章结尾
  默认状态   默认显示 / 默认隐藏
  另外:目录标题、收录层级(H1~H4)、是否显示序号

设计要点:
- 插入方式拆成 toc_mode(manual/auto) x toc_anchor(start/first/end) 两个正交
  字段,不合成一个四值下拉:侧栏模式下插入位置毫无意义,合并会产生歧义。
- 手写 [TOC] 绝对优先:yang_toc_plan() 里 $has_marker 最先返回,inline_at
  ='marker' 且 rail=false —— 否则同一篇会出现两份目录,老文章升级后突然多
  出一个目录。这条是刻意守住的。
- 纯函数 + 薄壳分层:normalize_settings / plan / inject / build_list 全部不查库,
  单进程可喂几十种组合;settings() 只负责读库 + 调纯函数 + static 缓存。
- <details open> 是唯一可靠的「默认收起」手段:展开态不接受 CSS 覆盖,只能
  由 open 属性决定,所以隐藏时必须不输出该属性。
- 侧栏收起态必须 translateX(±100%) 完全移出视口:只做 opacity 会让透明面板
  留在原位吃掉正文点击(探针 elementFromPoint 才抓得到)。
- yang_toc_rail_ready() 开关:侧栏在 wp_footer 输出,那时正文可能没走
  the_content(缓存插件/OEmbed),标题没 id ⇒ 侧栏链接全跳不动却看起来正常。
  宁可不出侧栏。
- 后台联动隐藏「插入位置」不用 jQuery show()/hide():会把 .section 的行内
  display 写成 block,悄悄回退 3.2.28 的 display:flow-root BFC 修复。改用
  class + display:none!important。

验证(全部在本轮重跑):
  _test_toc.php       53/0   旧 TOC 行为回归
  _toc330_logic.php   88/0   逻辑判据
  _toc330_falsify     13/13  注入全变红且红对地方
  _geo330_rail.js     73/0   侧栏真实几何(左/右 x 展开/收起 x 宽窄屏)
  _geo330_falsify     8/8
  _guard330.php       46/0   后台控件与运行时默认值双向一致
  _guard330_falsify   8/8
  _admin330_tab_check 36/0   新标签页几何 + 联动 + 载入态
  _admin330_falsify   8/8
  _admin328_check     13/0   3.2.28 布局回归

发布:只发 zip + readme + update.json 到 theme-update/,不动服务器主题目录,
用户在 WP 后台自行升级(沿用 3.2.24 起的策略)。
2026-10-25 12:18:52 +08:00

551 lines
13 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*!
* 文章目录(TOC)样式
* Yongquan Yang <yangyq.net>
*
* ── 设计原则 ────────────────────────────────────────────────
* 1. **不抢正文的视觉**:目录是辅助导航,不是内容。所以用浅灰背景、
* 细边框、小字号,让它在页面上"退后一步"。
* 2. **用主题主色**:强调色统一为 #00a2ff,与主题其他强调元素一致。
* 注意主题**没有**定义 CSS 变量(主色是各处硬编码的),所以这里
* 直接写字面值,不要臆造 var(--kratos-primary) 之类的变量 —— 那会
* 静默失效,目录永远是默认灰色,且看不出是哪里错了。
* 3. **移动端友好**:窄屏下缩进减小、字号略小,但不折叠成下拉 ——
* 目录本来就是给长文用的,手机上更需要它。
*/
.yang-toc {
margin: 24px 0;
border: 1px solid #ebeef5;
border-left: 3px solid #00a2ff;
border-radius: 3px;
background-color: #fafbfc;
font-size: 14px;
line-height: 1.6;
}
/* <details> 的默认三角标记去掉,用自己的图标 */
.yang-toc > summary {
list-style: none;
cursor: pointer;
user-select: none;
}
.yang-toc > summary::-webkit-details-marker {
display: none;
}
.yang-toc > summary::marker {
content: "";
}
.yang-toc-head {
display: flex;
align-items: center;
padding: 11px 16px;
color: #303133;
font-weight: 600;
font-size: 15px;
transition: color 0.15s ease;
}
.yang-toc-head:hover {
color: #00a2ff;
}
/* 折叠箭头:用 CSS 画的三角,不依赖图标字体或 SVG 文件 */
.yang-toc > summary .yang-toc-title::before {
content: "";
display: inline-block;
width: 0;
height: 0;
margin-right: 8px;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
border-left: 6px solid currentColor;
vertical-align: middle;
transition: transform 0.18s ease;
}
/* 展开时箭头朝下(旋转 90°) */
.yang-toc[open] > summary .yang-toc-title::before {
transform: rotate(90deg);
}
/* 不可折叠时(toggle=0)不显示箭头 */
.yang-toc:not(details) .yang-toc-head .yang-toc-title::before {
display: none;
}
.yang-toc-title {
flex: 0 0 auto;
}
/* 条目数徽标 */
.yang-toc-count {
flex: 0 0 auto;
margin-left: 8px;
padding: 1px 7px;
border-radius: 9px;
background-color: #eef1f6;
color: #909399;
font-weight: 400;
font-size: 12px;
line-height: 1.5;
}
.yang-toc-body {
padding: 0 16px 12px;
}
.yang-toc-list {
margin: 0;
padding-left: 0;
list-style: none;
}
/* 二级以上的列表缩进,并加一条引导线 */
.yang-toc-list .yang-toc-list {
margin-left: 9px;
padding-left: 14px;
border-left: 1px dashed #e2e6ee;
}
.yang-toc-item {
margin: 0;
padding: 0;
list-style: none;
}
.yang-toc-link {
display: flex;
align-items: baseline;
padding: 4px 6px;
border-radius: 3px;
color: #4a4f57;
text-decoration: none;
transition: color 0.15s ease, background-color 0.15s ease;
}
.yang-toc-link:hover,
.yang-toc-link:focus {
background-color: #eef4ff;
color: #00a2ff;
text-decoration: none;
}
/* 序号 */
.yang-toc-num {
flex: 0 0 auto;
min-width: 1.9em;
margin-right: 5px;
color: #a8abb2;
font-variant-numeric: tabular-nums;
font-size: 13px;
}
.yang-toc-text {
flex: 1 1 auto;
overflow: hidden;
text-overflow: ellipsis;
}
/* 层级视觉差异:越深越淡、字号越小 */
.yang-toc-lv3 > .yang-toc-link {
color: #6b7280;
font-size: 13.5px;
}
.yang-toc-lv4 > .yang-toc-link,
.yang-toc-lv5 > .yang-toc-link,
.yang-toc-lv6 > .yang-toc-link {
color: #8b9099;
font-size: 13px;
}
/* 当前阅读位置的条目高亮(JS 加 .is-active) */
.yang-toc-link.is-active {
background-color: #eef4ff;
color: #00a2ff;
font-weight: 600;
}
/* ── 标题锚点:点击跳转后被固定导航栏挡住的问题 ────────────────
* 主题的 .k-nav 是 fixed-top,滚动后固定约 67px 高。
* 纯 CSS 方案是给目标元素加 scroll-margin-top —— 浏览器原生支持,
* 且不依赖 JS(用 #anchor 直接访问、前进后退、外部链接进来都生效)。
*
* 为什么不用 JS 算偏移:JS 方案只在"点了目录"的情况下有效,
* 从外部带 #hash 进来、或按浏览器后退回到锚点时都不会触发。
* scroll-margin-top 是声明式的,所有情况自动生效。
*
* 67px 是导航栏高度;再加 16px 呼吸空间。管理工具栏(admin bar)
* 存在时再往上加,否则锚点会被工具栏盖住。
*/
.yang-toc-target,
.k-main .details .content h1[id],
.k-main .details .content h2[id],
.k-main .details .content h3[id],
.k-main .details .content h4[id],
.k-main .details .content h5[id],
.k-main .details .content h6[id] {
scroll-margin-top: 84px;
}
body.admin-bar .k-main .details .content h1[id],
body.admin-bar .k-main .details .content h2[id],
body.admin-bar .k-main .details .content h3[id],
body.admin-bar .k-main .details .content h4[id],
body.admin-bar .k-main .details .content h5[id],
body.admin-bar .k-main .details .content h6[id] {
scroll-margin-top: 116px;
}
/* 滚动到锚点时轻微闪一下,让用户确认"跳到这里了" */
@keyframes yang-toc-flash {
0%,
100% {
background-color: transparent;
}
30% {
background-color: #fffbe6;
}
}
.yang-toc-flash {
animation: yang-toc-flash 1.1s ease;
}
/* ── 窄屏 ── */
@media screen and (max-width: 768px) {
.yang-toc {
margin: 18px 0;
font-size: 13.5px;
}
.yang-toc-head {
padding: 10px 13px;
font-size: 14px;
}
.yang-toc-body {
padding: 0 10px 10px;
}
.yang-toc-list .yang-toc-list {
margin-left: 4px;
padding-left: 10px;
}
.yang-toc-link {
padding: 5px 5px;
}
.yang-toc-lv3 > .yang-toc-link {
font-size: 13px;
}
}
/* ── 正文内嵌槽(自动插入时用) ──────────────────────────────
* 自动插入的目录外面包一层 slot。加这层不是为了好看,是为了让
* 「目录在哪」这件事在 DOM 里有痕迹:插入位置变了、或者根本没插进去,
* 看 slot 的 class / 位置就能判断,不用去猜正文里的 <details> 是谁输出的。
*
* 边距放在 slot 上、把 .yang-toc 自身的 margin 归零:
* 两层都带 margin 时外边距会叠加,插在开头/结尾的那几种位置尤其难看
* (开头会顶出一块空白)。
*/
.yang-toc-slot {
margin: 24px 0;
}
.yang-toc-slot > .yang-toc {
margin: 0;
}
/* ── 侧边浮动目录 ─────────────────────────────────────────────
* 结构:贴边的一条细标签 + 一块可滑出的面板。
*
* 关键几何约束(容易被后来的改动破坏):
* · 容器是 0 宽的定位锚点,不可见、不吃点击(pointer-events:none);
* 真正可点的只有标签和面板。
* · 收起态面板必须**完全**移出视口(translateX(±100%)),
* 不能只做透明度。留一点"露头"看着舒服,但它会盖住正文列边缘,
* 窄屏上正好压掉正文的第一个字 —— 那种问题肉眼很难定位。
* · z-index 低于导航(Bootstrap navbar 是 1030),
* 万一两者在高度上相遇,导航仍然压在上面。
*/
.yang-toc-rail {
position: fixed;
top: 0;
bottom: 0;
width: 0;
z-index: 1000;
pointer-events: none;
}
.yang-toc-rail-left {
left: 0;
}
.yang-toc-rail-right {
right: 0;
}
.yang-toc-rail > * {
pointer-events: auto;
}
/* 贴边标签:竖排文字,主色底 */
.yang-toc-rail-tab {
position: absolute;
top: 50%;
width: 34px;
padding: 14px 0;
border: 0;
border-radius: 0 6px 6px 0;
background-color: #00a2ff;
color: #fff;
box-shadow: 1px 0 6px rgba(0, 0, 0, 0.12);
cursor: pointer;
font-size: 13px;
line-height: 1;
transform: translateY(-50%);
transition: opacity 0.18s ease, background-color 0.18s ease;
}
.yang-toc-rail-left .yang-toc-rail-tab {
left: 0;
}
.yang-toc-rail-right .yang-toc-rail-tab {
right: 0;
border-radius: 6px 0 0 6px;
box-shadow: -1px 0 6px rgba(0, 0, 0, 0.12);
}
.yang-toc-rail-tab:hover,
.yang-toc-rail-tab:focus {
background-color: #0089d8;
}
.yang-toc-rail-tab-text {
display: block;
letter-spacing: 3px;
writing-mode: vertical-rl;
text-orientation: upright;
}
/* 展开时标签让位给面板(面板已经贴边盖住它了,留着只是碍眼) */
.yang-toc-rail.is-open .yang-toc-rail-tab {
opacity: 0;
pointer-events: none;
}
/* 面板 */
.yang-toc-rail-panel {
position: absolute;
top: 50%;
display: flex;
flex-direction: column;
width: 288px;
max-height: 72vh;
border: 1px solid #ebeef5;
background-color: #fff;
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.14);
font-size: 14px;
line-height: 1.6;
opacity: 0;
visibility: hidden;
transition: opacity 0.2s ease, transform 0.28s ease, visibility 0s linear 0.28s;
}
.yang-toc-rail-left .yang-toc-rail-panel {
left: 0;
border-left: 3px solid #00a2ff;
border-radius: 0 4px 4px 0;
transform: translate(-100%, -50%);
}
.yang-toc-rail-right .yang-toc-rail-panel {
right: 0;
border-right: 3px solid #00a2ff;
border-radius: 4px 0 0 4px;
transform: translate(100%, -50%);
}
.yang-toc-rail.is-open .yang-toc-rail-panel {
opacity: 1;
visibility: visible;
transform: translate(0, -50%);
transition: opacity 0.2s ease, transform 0.28s ease, visibility 0s;
}
.yang-toc-rail-head {
display: flex;
flex: 0 0 auto;
align-items: center;
padding: 12px 12px 8px 14px;
border-bottom: 1px solid #f3f5f8;
}
.yang-toc-rail-title {
color: #303133;
font-weight: 600;
font-size: 15px;
}
.yang-toc-rail-close {
flex: 0 0 auto;
width: 24px;
height: 24px;
margin-left: auto;
padding: 0;
border: 0;
border-radius: 3px;
background: none;
color: #909399;
cursor: pointer;
font-size: 18px;
line-height: 1;
}
.yang-toc-rail-close:hover,
.yang-toc-rail-close:focus {
background-color: #f2f4f8;
color: #00a2ff;
}
.yang-toc-rail-body {
flex: 1 1 auto;
overflow-y: auto;
padding: 10px 12px 12px;
overscroll-behavior: contain;
}
/* ── 窄屏:面板按视口宽度收,标签再窄一点 ── */
@media screen and (max-width: 768px) {
.yang-toc-rail-panel {
width: min(78vw, 300px);
max-height: 70vh;
}
.yang-toc-rail-tab {
width: 30px;
padding: 12px 0;
font-size: 12px;
}
.yang-toc-rail-head {
padding: 10px 10px 8px 12px;
}
.yang-toc-rail-body {
padding: 8px 10px 10px;
}
}
/* 动效敏感的用户:不要滑出动画,直接显隐 */
@media (prefers-reduced-motion: reduce) {
.yang-toc-rail-panel,
.yang-toc-rail.is-open .yang-toc-rail-panel,
.yang-toc-rail-tab {
transition: none;
}
}
/* ── 打印:目录展开、去背景、不折行 ────────────────────────── */
@media print {
.yang-toc {
border: 1px solid #ccc;
background: none;
page-break-inside: avoid;
}
.yang-toc-count {
display: none;
}
.yang-toc-link {
color: #000;
}
/* 侧栏是屏幕上的快捷入口,纸上没有意义,还会盖住正文 */
.yang-toc-rail {
display: none;
}
}
/* ── 暗色主题适配 ──────────────────────────────────────────
* 主题没有内置暗色模式,但用户可能在「自定义 CSS」里加
* prefers-color-scheme。这里跟随同一个媒体查询,避免目录
* 在暗色下变成一块刺眼的白板。
*/
@media (prefers-color-scheme: dark) {
html.yang-dark .yang-toc {
border-color: #303640;
border-left-color: #00a2ff;
background-color: #1d2026;
}
html.yang-dark .yang-toc-head {
color: #d5d9e0;
}
html.yang-dark .yang-toc-count {
background-color: #2a2f38;
color: #8b929d;
}
html.yang-dark .yang-toc-link {
color: #b6bcc6;
}
html.yang-dark .yang-toc-link:hover,
html.yang-dark .yang-toc-link.is-active {
background-color: #252b34;
color: #00a2ff;
}
html.yang-dark .yang-toc-list .yang-toc-list {
border-left-color: #333944;
}
html.yang-dark .yang-toc-num {
color: #767d88;
}
/* 侧边目录:面板底色/边框/文字跟内嵌目录同一套暗色值,
否则暗色站点上会凭空出现一块白板。标签本身是主色底白字,
两种模式下都不用改。 */
html.yang-dark .yang-toc-rail-panel {
border-color: #303640;
background-color: #1d2026;
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.5);
}
html.yang-dark .yang-toc-rail-left .yang-toc-rail-panel {
border-left-color: #00a2ff;
}
html.yang-dark .yang-toc-rail-right .yang-toc-rail-panel {
border-right-color: #00a2ff;
}
html.yang-dark .yang-toc-rail-head {
border-bottom-color: #2a2f38;
}
html.yang-dark .yang-toc-rail-title {
color: #d5d9e0;
}
html.yang-dark .yang-toc-rail-close {
color: #8b929d;
}
html.yang-dark .yang-toc-rail-close:hover,
html.yang-dark .yang-toc-rail-close:focus {
background-color: #252b34;
color: #00a2ff;
}
}