Files
workspace/code/g3soft-libs/docs/.vitepress/theme/custom.css
T
2026-10-09 17:32:14 +08:00

622 lines
20 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) }
它的 padding-top 同时承担「避开固定 header」的职责,所以要保留
nav 高度,再叠加 32px 的呼吸空间。
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);
}
/* 左侧边栏:padding-top 本来就是 nav 高度(用于避让固定 header),
在其基础上叠加 32px。
额外减 2px 的原因(实测推算):
.VPSidebar padding-top: 96px
.nav padding-top: 8px → 104
.g3-list margin-top: -6px → 98 ← 内容实际落点
即左栏内部自带「+8 -6 = +2」的净偏移。减去它,三栏内容才都落在 96px。
(这两个值来自侧栏自身的复制样式,不宜改动,否则影响分组间距。) */
.VPSidebar[class] {
padding-top: calc(
var(--vp-nav-height) + var(--g3-content-top-padding) - var(--g3-sidebar-inner-offset)
);
/* 底部留白:VitePress 默认 96px,Docus 是对称的 py-8 = 32px。
现在 .VPSidebar 自己就是滚动容器(与 Docus 的 overflow-y-auto 一致),
96px 在滚到底时会显得过厚。 */
padding-bottom: var(--g3-content-top-padding);
}
/* 右侧目录:.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;
}
.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;
}
.g3-header-section {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 0 10px;
height: 32px;
border: 1px solid transparent;
border-radius: 6px;
color: var(--vp-c-text-2);
font-size: 13px;
font-weight: 500;
line-height: 1;
transition:
color 0.2s,
background-color 0.2s,
border-color 0.2s;
}
.g3-header-section:hover {
color: var(--vp-c-brand-1);
background-color: var(--vp-c-default-soft);
}
.g3-header-section.is-active {
color: var(--vp-c-brand-1);
background-color: var(--vp-c-brand-soft);
border-color: transparent;
}
.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;
}
@media (max-width: 767px) {
.VPNavBar .g3-header-sections {
display: none;
}
}
@media (min-width: 768px) {
.VPNavScreen .g3-header-sections {
display: none;
}
}