Files
workspace/code/g3soft-libs/docs/.vitepress/theme/custom.css
T
2026-10-09 22:05:02 +08:00

697 lines
24 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.
:root {
--vp-nav-home-bg-color: var(--vp-nav-bg-color);
--vp-sidebar-bg-color: var(--vp-c-bg);
/* 三栏容器「内部」的顶部留白。
语义参考 Docus:容器本身紧贴 header,留白由容器自己的 pt-8 提供。
实测 docus.dev(页面顶部):
左栏 aside top=64(贴齐) padding-top=32px → 内容 90
右栏目录首块 top=64(贴齐) padding-top=32px → 内容 96
正文区块 top=64(贴齐) padding-top=32px → 内容 96
即三处的共同规律是「贴齐 + 32px」,不是「距 header 62px」。
改这一个值即可整体升降。 */
--g3-content-top-padding: 32px;
/* 左栏内部自带的净垂直偏移:.nav 的 padding-top(8px) 与
.g3-list 的 margin-top(-6px) 相抵后为 +2px。
补偿它以让三栏内容落在同一水平线上(见下方 .VPSidebar[class])。 */
--g3-sidebar-inner-offset: 2px;
}
/* --------------------------------------------------------------------------
内容区顶部留白(对齐 Docus:容器贴齐 header + 内部 32px)
VitePress 三处的来源不同,需分别覆盖:
1) 正文:.VPDoc[data-v-*] { padding: 48px 32px 0 } → 顶部内边距改为 32px
2) 左侧边栏:.VPSidebar[data-v-*] { padding-top: var(--vp-nav-height) }
改用 margin-top:由「容器盒子上缘贴齐 header、内容靠 padding 下推」
变为「整个容器盒子上缘落到 header 之下」。二者内容落点一致。
3) 右侧目录:**不要**动 --vp-doc-top-height(原因见下方说明)。
⚠ 关于 --vp-doc-top-height(踩过的坑):
VitePress 的 .aside-container 计算式是
padding-top: calc(nav-height + layout-top-height + var(--vp-doc-top-height) + 48px)
它**已经内置了 48px 基线**,该变量只是额外增量。
我第一次把它设成 32px,得到 64 + 32 + 48 = 144px,右栏被推低了 80px。
Docus 的 32px 已被这个 48px 基线覆盖,所以保持默认 0(即不设该变量)。
⚠ 特异性:VitePress 的规则带 [data-v-*] 属性选择器,权重高于单纯的
`.VPDoc` / `.VPSidebar`。实测只写类名不生效(计算值仍停在 48px/64px),
因此这里用 [class] 补齐权重,与 VitePress 打平后再靠顺序取胜。
-------------------------------------------------------------------------- */
@media (min-width: 960px) {
/* 正文:VitePress 默认 48px → 32px */
.VPDoc[class] {
padding-top: var(--g3-content-top-padding);
}
/* 左侧边栏:把「避让固定 header + 32px 呼吸空间」整体交给 margin-top。
padding-top 只把内容往下推,盒子仍从 top:0 起、背景一直铺到固定 header
后面,于是必须靠 .curtain 去遮「内容滚进 header 区」的那一段;
改用 margin-top 后盒子整体落到 header 之下,padding-top 归 0,
.curtain 随之多余(见下方规则)。
内容落点与改动前完全一致(都等于 nav-height + 32 - 2)。
额外减 2px 的原因(实测推算):
.VPSidebar padding-top: 96px
.nav padding-top: 8px → 104
.g3-list margin-top: -6px → 98 ← 内容实际落点
即左栏内部自带「+8 -6 = +2」的净偏移。减去它,三栏内容才都落在 96px。
(这两个值来自侧栏自身的复制样式,不宜改动,否则影响分组间距。) */
.VPSidebar[class] {
margin-top: calc(
var(--vp-nav-height) + var(--g3-content-top-padding) - var(--g3-sidebar-inner-offset)
);
padding-top: 0;
/* 底部留白:VitePress 默认 96px,Docus 是对称的 py-8 = 32px。
现在 .VPSidebar 自己就是滚动容器(与 Docus 的 overflow-y-auto 一致),
96px 在滚到底时会显得过厚。 */
padding-bottom: var(--g3-content-top-padding);
}
/* 移除 VitePress 桌面端 <VPSidebar> 内的首个 <div class="curtain">:
position:sticky; top:-64px; height:nav-height; 背景=侧栏底色,
唯一职责是遮住「侧栏内容滚到固定 header 后面」的区域。
本主题已把 .VPSidebar 的上缘整体移到 header 之下(padding-top → margin-top),
内容不可能再进入 header 区域,curtain 反而会在顶部压出一块色块,故隐藏。
用 [class] 把特异性提到 (0,3,0),压过 VitePress 的 .curtain[data-v-*] (0,2,0)。 */
.VPSidebar[class] .curtain {
display: none;
}
/* 右侧目录:.aside-container 是 position:fixed,其 padding-top 公式为
calc(nav-height + layout-top-height + var(--vp-doc-top-height) + 48px)
其中 48px 是 VitePress 写死、且**不与 --vp-doc-top-height 联动**的基线。
Docus 对应位置为 nav(64) + 32 = 96px,而公式给出 64 + 48 = 112px。
由于基线是硬编码的,只能整条覆盖(用 [class] 补齐特异性)。 */
.aside-container[class] {
padding-top: calc(var(--vp-nav-height) + var(--g3-content-top-padding));
}
}
/* ==========================================================================
侧边栏 —— 照抄 Docus / @nuxt/ui 的 ContentNavigation
--------------------------------------------------------------------------
数据来源:从 https://docus.dev/en/getting-started/studio 用无头 Chrome
抓取的「真实计算样式」,不是从 Tailwind 类名反推。以下每一项都是实测值。
DOM(实测):
aside > div.relative > nav > ul.isolate.-mx-2.5.-mt-1.5
li.flex.flex-col.data-[state=open]:mb-1.5 ← 有子项
button.group.relative.w-full.px-2.5.py-1.5... ← 分组标题
span.iconify.i-lucide:rocket.shrink-0.size-4.mx-0.5.text-dimmed
span.truncate ← 标题文字
div[accordion content]
ul.ms-5.border-s.border-default ← 子列表
li ← 无子项
a.group.relative.w-full...
实测值(1440px / light):
ul.list margin: -6px -10px 0; list-style: none; isolation: isolate
li(有子项) display:flex; flex-direction:column
button/a.link display:flex; align-items:center; gap:6px
padding:6px 10px; height:32px; font-size:14px
border-radius:0 (圆角 6px 画在 ::before 上)
分组标题 font-weight:600; color: oklch(0.21 …) = text-highlighted
普通项 font-weight:400; color: oklch(0.552 …) = text-muted
激活项 font-weight:500; color: oklch(0.696 0.17 162.48) = primary
background: transparent(无背景块)
::after 竖线:1px × 28px, left:-6px, top/bottom:2px
图标 16px × 16px;margin-inline: 2px;color: text-dimmed
ul.listWithChildren margin-inline-start:20px; border-inline-start:1px;
padding-inline-start:0
::before position:absolute; inset-inline:0; inset-block:1px;
border-radius:6px(仅用于 focus 轮廓,默认透明)
注意 variant 是 'link' 而非 'pill':激活项没有高亮底色,
只有「文字变品牌色 + 左侧 1px 竖线」。
========================================================================== */
/* --- Nuxt UI token → VitePress 变量映射 --- */
:root {
--g3-nav-text-dimmed: var(--vp-c-text-3);
--g3-nav-text-muted: var(--vp-c-text-2);
--g3-nav-text-highlighted: var(--vp-c-text-1);
--g3-nav-border: var(--vp-c-divider);
--g3-nav-radius: 6px; /* rounded-md */
}
.VPSidebar {
background-color: var(--vp-c-bg) !important;
}
/* 隐藏内置侧边栏 —— 改由 G3Sidebar.vue 渲染。
VPSidebarGroup 无包裹元素,直接产出并列的 .nav > .group,
只能用结构位置区分(:not 里必须排除自建节点)。 */
.VPSidebar > .nav > *:not(.g3-nav-root) {
display: none;
}
/* 底部留白只由 .VPSidebar 容器提供(见文件开头 .VPSidebar[class] 的
padding-bottom)。这里把 .nav 自带的 32px 去掉,否则两者叠加成 64px,
比 Docus 的单份 py-8(32px) 厚一倍(实测滚到底时留白 70px → 应为 38px)。
顶部的 8px 保留:它与 .g3-list 的 -6px 相抵后构成左栏内部节奏,
已在 --g3-sidebar-inner-offset 中补偿。 */
.VPSidebar .nav {
padding: 8px 0 0;
}
/* --------------------------------------------------------------------------
ul.list = isolate -mx-2.5 -mt-1.5
→ 左右各 -10px、顶部 -6px,让条目的 hover 区比容器更宽
-------------------------------------------------------------------------- */
.g3-nav-root {
width: 100%;
}
.g3-list {
display: block;
margin: -6px -10px 0;
padding: 0;
list-style: none;
isolation: isolate;
}
/* li.itemWithChildren = flex flex-col */
.g3-item-with-children {
display: flex;
flex-direction: column;
}
.g3-item {
display: block;
}
/* --------------------------------------------------------------------------
link = px-2.5 py-1.5 gap-1.5 text-sm flex items-center
-------------------------------------------------------------------------- */
.g3-nav-link {
position: relative;
display: flex;
align-items: center;
gap: 6px;
width: 100%;
padding: 6px 10px;
font-size: 14px;
font-weight: 400;
line-height: 20px;
color: var(--g3-nav-text-muted);
text-align: start;
text-decoration: none;
border: 0;
background: transparent;
cursor: pointer;
transition: color 0.2s ease;
}
/* before:absolute before:rounded-md before:inset-x-0 before:inset-y-px
link 变体下 ::before 本身不上色,仅承载 focus 轮廓的圆角形状 */
.g3-nav-link::before {
content: '';
position: absolute;
inset-inline: 0;
inset-block: 1px;
z-index: -1;
border-radius: var(--g3-nav-radius);
}
/* 分组标题:trigger = font-semibold,颜色为 text-highlighted */
.g3-nav-trigger {
font-weight: 600;
color: var(--g3-nav-text-highlighted);
}
/* hover(link 变体 & 未激活)→ text-highlighted */
.g3-nav-link:hover {
color: var(--g3-nav-text-highlighted);
}
/* 分组标题已展开态(Nuxt UI: data-[state=open]:text-highlighted) */
.g3-nav-trigger[aria-expanded='true'] {
color: var(--g3-nav-text-highlighted);
}
/* --------------------------------------------------------------------------
激活项:variant 'link' + active + highlight
link → font-medium + text-primary(无背景块)
after → 左侧 1px 竖线,inset-y-0.5(-2px),inset-inline-start:-6px
实测:width 1px, height 28px, left -6px, top 2px, bottom 2px
-------------------------------------------------------------------------- */
.g3-nav-link.is-active {
font-weight: 500;
color: var(--vp-c-brand-1);
}
.g3-nav-link.is-active::after {
content: '';
position: absolute;
inset-inline-start: -6px;
top: 2px;
bottom: 2px;
display: block;
width: 1px;
border-radius: 9999px;
background-color: var(--vp-c-brand-1);
transition: background-color 0.2s ease;
}
/* --------------------------------------------------------------------------
文字:truncate
-------------------------------------------------------------------------- */
.g3-nav-title {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* --------------------------------------------------------------------------
图标:shrink-0 size-4 mx-0.5
实测 16×16,左右各 2px(Docus 在 app.config.ts 里把 size-5 覆盖成 size-4)
-------------------------------------------------------------------------- */
.g3-nav-icon {
flex: 0 0 auto;
width: 16px;
height: 16px;
margin-inline: 2px;
color: var(--g3-nav-text-dimmed);
transition: color 0.2s ease;
}
.g3-nav-link:hover .g3-nav-icon {
color: var(--vp-c-text-1);
}
.g3-nav-link.is-active .g3-nav-icon {
color: var(--vp-c-brand-1);
}
/* --------------------------------------------------------------------------
ul.listWithChildren = ms-5 border-s border-default
实测:margin-inline-start 20px;border-inline-start 1px;padding-inline-start 0
-------------------------------------------------------------------------- */
.g3-list-with-children {
margin: 0 0 0 20px;
margin-inline-start: 20px;
padding: 0;
list-style: none;
border-inline-start: 1px solid var(--g3-nav-border);
}
/* --------------------------------------------------------------------------
level>0 的条目:Nuxt UI 的 item / itemWithChildren 在 level=true 时
带 "ps-1.5 -ms-px" → padding-inline-start:6px; margin-inline-start:-1px
关键:这两个值加在 <li> 上,不是加在 <a>/<button> 上。
实测 docus.dev 的几何关系:
li left=42 padding-inline-start:6px margin-inline-start:-1px
a.link left=48 ← 被 li 的 6px padding 右推
::after left=-6px → 48-6 = 42 ← 正好落在子列表描边线上
若把 padding 加在 <a> 上,<a> 的 border box 左缘不变(仍是 42),
竖线会跑到 36px,比描边线左偏 6px(即「线跑到线的左边」)。
-------------------------------------------------------------------------- */
.g3-item,
.g3-item-with-children {
padding-inline-start: 6px;
margin-inline-start: -1px;
}
/* 顶层(level 0)不适用上面的偏移,保持与容器对齐 */
.g3-list > .g3-item,
.g3-list > .g3-item-with-children {
padding-inline-start: 0;
margin-inline-start: 0;
}
/* itemWithChildren 展开时底部留 6px(data-[state=open]:mb-1.5) */
.g3-item-with-children:has(> .g3-nav-content:not([style*='display: none'])) {
margin-bottom: 6px;
}
.g3-nav-content {
overflow: hidden;
}
@media (prefers-reduced-motion: reduce) {
.g3-nav-link,
.g3-nav-icon,
.g3-nav-link.is-active::after {
transition: none;
}
}
/* ==========================================================================
右侧目录(TOC)—— 照抄 Docus / @nuxt/ui ContentToc 的 circuit 指示器
--------------------------------------------------------------------------
依据:@nuxt/ui@4.11.2 dist/runtime/components/content/ContentToc.vue
+ https://docus.dev 实测计算样式
实测值:
标题 14px / 600(font-semibold)
链接 display:flex; align-items:center; padding:4px 0;
14px / 400; line-height:20px; height:28px
border-radius:4px; color: text-muted
链接 hover text-default
链接激活 text-primary;fontWeight 仍 400(注意:不是 500)
子层级 ul margin-inline-start:12px(ms-3)
指示器容器 position:absolute; width:12px; start-0; margin-inline-start:10px
轨道 absolute inset-0; 背景 border-default
活动块 width:100%(12px); height:var(--indicator-size);
translateY(var(--indicator-position)); 背景 primary
过渡 translate,height 0.2s ease-out
← 无圆角;「拐弯」由 SVG mask 裁出
========================================================================== */
/* 隐藏内置目录(改用 G3DocAside) */
.VPDocAsideOutline {
display: none !important;
}
.g3-toc {
padding: 0;
}
.g3-toc-trigger {
display: flex;
align-items: center;
gap: 6px;
margin: -6px 0 0;
padding: 6px 0;
font-size: 14px;
font-weight: 600;
line-height: 20px;
color: var(--vp-c-text-1);
}
.g3-toc-content {
position: relative;
display: flex;
padding-top: 12px;
}
/* --------------------------------------------------------------------------
指示器:整条 12px 宽色带,靠 mask-image 裁成折线
-------------------------------------------------------------------------- */
.g3-toc-indicator {
position: absolute;
inset-block: 12px 0; /* 与 content 的 padding-top 对齐 */
inset-inline-start: 0;
z-index: 0;
width: 12px;
margin-inline-start: 10px; /* ms-2.5 */
pointer-events: none;
/* mask 由组件的 circuitMask 计算后内联注入 */
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
}
/* 轨道:未激活部分的底色 */
.g3-toc-indicator-line {
position: absolute;
inset: 0;
background-color: var(--vp-c-divider);
}
/* 活动块:品牌色,高度与位移由 CSS 变量驱动 */
.g3-toc-indicator-active {
position: absolute;
inset-inline: 0;
top: 0;
width: 100%;
height: var(--indicator-size, 0);
background-color: var(--vp-c-brand-1);
transform: translateY(var(--indicator-position, 0));
transition:
transform 0.2s ease-out,
height 0.2s ease-out;
}
@media (prefers-reduced-motion: reduce) {
.g3-toc-indicator-active {
transition: none;
}
}
/* --------------------------------------------------------------------------
列表与链接
--------------------------------------------------------------------------
关键:ul.list 带 ps-6.5 = padding-inline-start:26px(Docus 实测)。
指示器(12px 宽 + ml 10px)是绝对定位在 content 上的,
这 26px 的 padding 把条目文字整体推到轨道右侧。
漏掉它文字就会压在轨道的 12px 上(实测差 26px:
文字左缘 − 指示器右缘 = -23px,正确值是 +3px)。
-------------------------------------------------------------------------- */
.g3-toc-list,
.g3-toc-list-with-children {
position: relative;
z-index: 1;
margin: 0;
padding: 0;
list-style: none;
}
.g3-toc-list {
padding-inline-start: 26px; /* ps-6.5 */
}
.g3-toc-list-with-children {
margin-inline-start: 12px; /* ms-3 */
padding-inline-start: 0;
}
.g3-toc-item {
min-width: 0;
margin-inline-start: -1px; /* -ms-px:与指示器轨道对齐 */
padding-inline-start: 0;
}
.g3-toc-link {
display: flex;
align-items: center;
padding: 4px 0; /* py-1 */
font-size: 14px;
font-weight: 400;
line-height: 20px;
height: 28px;
border-radius: 4px; /* rounded-sm */
color: var(--g3-nav-text-muted);
text-decoration: none;
transition: color 0.2s ease;
}
.g3-toc-link:hover {
color: var(--vp-c-text-1); /* hover:text-default */
}
.g3-toc-link.is-active {
color: var(--vp-c-brand-1); /* text-primary;字重保持 400 */
}
.g3-toc-link-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@media (prefers-reduced-motion: reduce) {
.g3-toc-link {
transition: none;
}
}
.VPNavBar,
.VPNavBar.home,
.VPNavBar.home.top,
.VPNavBar:not(.home),
.VPNavBar .content-body,
.VPNavBar.home .content-body,
.VPNavBar.home.top .content-body,
.VPNavBar:not(.home) .content-body {
background-color: var(--vp-nav-bg-color) !important;
}
.VPNavBar .divider,
.VPNavBar.has-sidebar .divider {
padding-left: 0 !important;
background-color: var(--vp-c-gutter) !important;
}
/* --------------------------------------------------------------------------
主题切换:改用自建图标按钮 G3AppearanceToggle.vue,挂在
nav-bar-content-after 槽的末尾(= 顶栏最右),左侧自带竖分割线。
VitePress 默认主题在两处渲染同一个「太阳/月亮滑块开关」,都要隐藏,
否则会和新按钮重复:
≥1280px .VPNavBarAppearance 顶栏里(位于搜索与板块导航之间)
768–1280px .VPNavBarExtra 的 flyout 本站 nav 为空、无多语言无社交链接,
该 flyout 的内容恰好只有这一项开关
移动端抽屉里的 .VPNavScreenAppearance 是独立的一整行,保持不动。
⚠ 将来若给本站加了 nav / socialLinks / 多语言,.VPNavBarExtra 会有别的
内容,届时请删掉这一条,改为只隐藏其中的 .appearance 分组。
-------------------------------------------------------------------------- */
.VPNavBarAppearance,
.VPNavBarExtra {
display: none !important;
}
.VPNavBar .divider-line,
.VPNavBar.home.top .divider-line,
.VPNavBar:not(.home) .divider-line {
background-color: var(--vp-c-gutter) !important;
}
.VPNavBarTitle.has-sidebar .title {
border-bottom-color: transparent !important;
}
@media (min-width: 960px) {
.VPNavBar .wrapper,
.VPNavBar.has-sidebar .wrapper {
padding: 0 32px !important;
}
.VPNavBar .container,
.VPNavBar.has-sidebar .container {
max-width: calc(var(--vp-layout-max-width) - 64px) !important;
}
.VPNavBar > .wrapper > .container > .title,
.VPNavBar.has-sidebar > .wrapper > .container > .title {
position: static !important;
z-index: auto !important;
flex-shrink: 0 !important;
padding: 0 !important;
width: auto !important;
height: calc(var(--vp-nav-height) - 1px) !important;
background-color: transparent !important;
}
.VPNavBar > .wrapper > .container > .content,
.VPNavBar.has-sidebar > .wrapper > .container > .content {
position: static !important;
z-index: auto !important;
flex-grow: 1 !important;
padding: 0 !important;
}
.VPNavBar .content-body,
.VPNavBar.has-sidebar .content-body {
position: static !important;
justify-content: flex-end !important;
padding: 0 !important;
}
}
.g3-header-sections {
display: flex;
align-items: center;
gap: 4px;
margin-left: 12px;
}
/* 顶栏右侧的板块导航。
原先用的是 'pill' 变体(激活项铺 --vp-c-brand-soft 底色块),与本文件开头
给侧边栏定下的 'link' 变体不一致 —— 那里明确写了「激活项没有高亮底色,
只有文字变品牌色 + 1px 竖线」,右侧目录也是同一套。
这里统一到 'link':激活 = 品牌色文字 + 1px 指示条;hover = 文字提到
text-highlighted(不再是品牌色 + 底色)。
侧边栏是竖向排列、指示条在左;这里是横向排列,指示条落到文字下方。 */
.g3-header-section {
position: relative;
display: inline-flex;
align-items: center;
gap: 6px;
padding: 0 10px;
height: 32px;
color: var(--vp-c-text-2);
font-size: 13px;
font-weight: 500;
line-height: 1;
transition: color 0.2s;
}
.g3-header-section:hover {
color: var(--vp-c-text-1);
}
.g3-header-section.is-active {
color: var(--vp-c-brand-1);
}
/* 指示条:规格同侧边栏激活项的 1px 竖线,只是换到水平轴 */
.g3-header-section.is-active::after {
content: '';
position: absolute;
inset-inline: 0;
bottom: 0;
height: 1px;
border-radius: 9999px;
background-color: var(--vp-c-brand-1);
}
.g3-header-section svg {
flex: 0 0 auto;
}
.VPNavScreen .g3-header-sections {
display: grid;
gap: 8px;
margin: 24px 0 0;
padding-top: 24px;
border-top: 1px solid var(--vp-c-divider);
}
.VPNavScreen .g3-header-section {
justify-content: flex-start;
width: 100%;
height: 44px;
padding: 0 12px;
font-size: 14px;
}
/* 移动端抽屉里是竖排的整行列表,底色块比下划线更像「列表选中」,
这里保留 pill 变体,并把桌面端的下划线指示条去掉。 */
.VPNavScreen .g3-header-section:hover {
color: var(--vp-c-brand-1);
background-color: var(--vp-c-default-soft);
}
.VPNavScreen .g3-header-section.is-active {
color: var(--vp-c-brand-1);
background-color: var(--vp-c-brand-soft);
}
.VPNavScreen .g3-header-section.is-active::after {
content: none;
}
@media (max-width: 767px) {
.VPNavBar .g3-header-sections {
display: none;
}
}
@media (min-width: 768px) {
.VPNavScreen .g3-header-sections {
display: none;
}
}
/* ==========================================================================
Demo 预览区溢出兜底
--------------------------------------------------------------------------
vitepress-demo-plugin 自己没给预览区设 overflow,比容器宽的示例会直接「跃出」
边框:实测 grid/no-wrap.vue(故意演示「不换行、超出 24 列」)的第 4 个 col
溢出边框 195px;将来任何宽 demo 也会同样破坏页面布局。
加 overflow-x: auto 让内容横向可滚,永远留在框内。
注意:这是兜底,不是解决办法 —— 示例自身写错 prop / 布局导致的溢出应当在
示例侧修掉,别靠这条规则把问题盖住。
========================================================================== */
.vitepress-demo-plugin__container > .vitepress-demo-plugin-preview {
overflow-x: auto;
}