/*! * 文章目录(TOC)样式 * Yongquan Yang * * ── 设计原则 ──────────────────────────────────────────────── * 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; } /*
的默认三角标记去掉,用自己的图标 */ .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 / 位置就能判断,不用去猜正文里的
是谁输出的。 * * 边距放在 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; } }