Files
website/.vitepress/theme/styles/portal.css
T
Dai0814 e4757446cf
mirror-to-gitee / mirror (push) Successful in 4s
feat(portal): 补入整站页脚,并让项目列表页不显示页脚
用户反馈 /projects/(项目列表)页底部的品牌/联系/版权区应该去掉。

背景:这几个文件此前一直未提交——远端仓库其实没有页脚,
PortalFooter.vue 只存在于本地,HEAD 版 index.ts 也没引用它。
所以本次把页脚整套补齐,再叠加「按页面决定是否展示」的逻辑。

新增/改动
- .vitepress/theme/components/PortalFooter.vue(新增,274 行)
  品牌区 + 产品体系/文档资源/关于我们/联系我们四栏 + 二维码浮层 + 版权条
- .vitepress/theme/styles/portal.css:补入全部 .pf-foot-* 样式(508 行)
  只搬运页脚相关规则,不夹带工作区里其它未提交的样式改动
- .vitepress/theme/index.ts:挂载页脚 + 页面级显隐判定
- pages/projects/index.md:frontmatter 加 footer: false

页脚显隐判定(isDocPage)
「文档型页面不挂页脚」——这类页面有左侧目录树、右侧本页目录、
底部上一页/下一页翻页,读者正在阅读流里,450px 的品牌收尾区是纯噪音。
命中任一条件即隐藏:
  1) 路径 /projects/<id>/manual/...
  2) frontmatter 有 sidebar / outline / aside
  3) frontmatter 显式 footer: false

frontmatter 的 footer 可双向覆盖(这解决了 /projects/ 的困境):
  该页有 outline: deep,按上面的规则本会被判成文档页而隐藏页脚,
  但它本质是「浏览页」,需要显式 footer: false 才符合预期。
  同理 footer: true 可强制显示。

验证(干净 clone 构建后实测)
- /projects/        无页脚(footer=false,无版权条)
- /                  有页脚(453px,渐变背景正常)
- 文档页(详情/对比/快速开始/手册)均无页脚
- 自定义整页(关于/联系/演示/官方文档)均有页脚
2026-11-06 13:39:41 +08:00

1633 lines
35 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 {
--p-accent: #4f46e5;
--p-accent-2: #06b6d4;
--p-accent-soft: rgba(79, 70, 229, 0.08);
--p-accent-line: rgba(79, 70, 229, 0.22);
--p-surface: #ffffff;
--p-surface-alt: #f7f8fb;
--p-line: rgba(15, 23, 42, 0.08);
--p-line-strong: rgba(15, 23, 42, 0.14);
--p-ink: #0f172a;
--p-ink-2: #475569;
--p-ink-3: #8a94a6;
--p-shadow: 0 1px 2px rgba(15, 23, 42, 0.04), 0 8px 24px -12px rgba(15, 23, 42, 0.12);
--p-shadow-hover: 0 1px 2px rgba(15, 23, 42, 0.05), 0 18px 40px -18px rgba(15, 23, 42, 0.22);
--p-radius: 16px;
--p-radius-sm: 10px;
--p-max: 1180px;
}
.portal {
color: var(--p-ink);
background: var(--p-surface);
}
html.dark {
--p-accent: #8b8bfa;
--p-accent-2: #45d3e8;
--p-accent-soft: rgba(139, 139, 250, 0.12);
--p-accent-line: rgba(139, 139, 250, 0.28);
--p-surface: #0c0f16;
--p-surface-alt: #11151e;
--p-line: rgba(255, 255, 255, 0.09);
--p-line-strong: rgba(255, 255, 255, 0.16);
--p-ink: #eef1f6;
--p-ink-2: #a8b2c4;
--p-ink-3: #6f7a8d;
--p-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 24px -12px rgba(0, 0, 0, 0.5);
--p-shadow-hover: 0 1px 2px rgba(0, 0, 0, 0.35), 0 18px 40px -18px rgba(0, 0, 0, 0.7);
}
/* 让 page 布局摆脱默认窄栏,门户整页自管宽度 */
.portal-page .VPPage,
.portal-page .vp-doc.container {
max-width: none !important;
padding: 0 !important;
}
.portal-page .VPContent {
padding: 0 !important;
}
.portal-page .vp-doc > div > h1:first-child,
.portal-page .vp-doc > h1:first-child {
display: none;
}
.portal *,
.portal *::before,
.portal *::after {
box-sizing: border-box;
}
/* ---------- 容器与分区 ---------- */
.p-wrap {
max-width: var(--p-max);
margin: 0 auto;
padding: 0 28px;
}
.p-section {
padding: 96px 0;
border-top: 1px solid var(--p-line);
}
.p-section--alt {
background: var(--p-surface-alt);
}
.p-section--tight {
padding: 72px 0;
}
/* ---------- 区块标题(居中) ---------- */
.p-head {
max-width: 720px;
margin: 0 auto 56px;
text-align: center;
}
.p-head--left {
margin-left: 0;
text-align: left;
}
.p-eyebrow {
display: inline-flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
padding: 4px 12px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.06em;
color: var(--p-accent);
background: var(--p-accent-soft);
border: 1px solid var(--p-accent-line);
border-radius: 999px;
}
.p-title {
margin: 0;
font-size: 36px;
font-weight: 650;
line-height: 1.25;
letter-spacing: -0.02em;
color: var(--p-ink);
}
.p-desc {
margin: 16px 0 0;
font-size: 16px;
line-height: 1.75;
color: var(--p-ink-2);
}
@media (max-width: 720px) {
.p-section {
padding: 64px 0;
}
.p-title {
font-size: 26px;
}
.p-desc {
font-size: 15px;
}
.p-head {
margin-bottom: 36px;
}
}
/* ---------- Hero ---------- */
.p-hero {
position: relative;
padding: 104px 0 88px;
text-align: center;
overflow: hidden;
}
.p-hero::before {
content: '';
position: absolute;
inset: -40% -20% auto;
height: 620px;
background: radial-gradient(50% 50% at 50% 50%, var(--p-accent-soft) 0%, transparent 70%);
pointer-events: none;
}
.p-hero > .p-wrap {
position: relative;
}
.p-hero-title {
margin: 20px 0 0;
font-size: 60px;
font-weight: 700;
line-height: 1.1;
letter-spacing: -0.035em;
color: var(--p-ink);
}
.p-hero-title em {
font-style: normal;
background: linear-gradient(100deg, var(--p-accent), var(--p-accent-2));
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
.p-hero-sub {
margin: 22px auto 0;
max-width: 640px;
font-size: 17px;
line-height: 1.8;
color: var(--p-ink-2);
}
.p-hero-actions {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
margin-top: 34px;
}
.p-btn {
display: inline-flex;
align-items: center;
gap: 8px;
height: 46px;
padding: 0 26px;
font-size: 15px;
font-weight: 600;
border-radius: 999px;
border: 1px solid transparent;
text-decoration: none !important;
transition: transform 0.15s, box-shadow 0.2s, background 0.2s, border-color 0.2s;
}
.p-btn:hover {
transform: translateY(-1px);
}
.p-btn--primary {
color: #fff !important;
background: var(--p-accent);
box-shadow: 0 8px 20px -8px var(--p-accent);
}
.p-btn--primary:hover {
box-shadow: 0 14px 28px -10px var(--p-accent);
}
.p-btn--ghost {
color: var(--p-ink) !important;
background: var(--p-surface);
border-color: var(--p-line-strong);
}
.p-btn--ghost:hover {
border-color: var(--p-accent);
color: var(--p-accent) !important;
}
@media (max-width: 720px) {
.p-hero {
padding: 56px 0 48px;
}
.p-hero-title {
font-size: 29px;
letter-spacing: -0.02em;
line-height: 1.28;
}
.p-hero-sub {
font-size: 14.5px;
line-height: 1.75;
margin-top: 18px;
}
.p-hero-actions {
margin-top: 26px;
}
.p-btn {
height: 44px;
padding: 0 22px;
font-size: 14.5px;
}
}
/* ---------- 数据条 ---------- */
.p-stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0;
max-width: 900px;
margin: 64px auto 0;
padding: 28px 0;
border-top: 1px solid var(--p-line);
border-bottom: 1px solid var(--p-line);
}
.p-stat {
text-align: center;
padding: 0 12px;
}
.p-stat + .p-stat {
border-left: 1px solid var(--p-line);
}
.p-stat-num {
display: block;
font-size: 34px;
font-weight: 700;
letter-spacing: -0.03em;
line-height: 1.1;
color: var(--p-ink);
}
.p-stat-num span {
font-size: 18px;
font-weight: 600;
color: var(--p-accent);
}
.p-stat-label {
display: block;
margin-top: 8px;
font-size: 13px;
color: var(--p-ink-3);
}
@media (max-width: 720px) {
.p-stats {
grid-template-columns: repeat(2, 1fr);
gap: 24px 0;
margin-top: 44px;
}
.p-stat:nth-child(3) {
border-left: none;
}
.p-stat-num {
font-size: 26px;
}
}
/* ---------- 通用卡片网格 ---------- */
.p-grid {
display: grid;
gap: 20px;
}
.p-grid--3 {
grid-template-columns: repeat(3, 1fr);
}
.p-grid--4 {
grid-template-columns: repeat(4, 1fr);
}
@media (max-width: 1024px) {
.p-grid--4 {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 860px) {
.p-grid--3,
.p-grid--4 {
grid-template-columns: 1fr;
}
}
.p-card {
display: flex;
flex-direction: column;
padding: 28px 26px;
background: var(--p-surface);
border: 1px solid var(--p-line);
border-radius: var(--p-radius);
box-shadow: var(--p-shadow);
transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s;
}
.p-card:hover {
transform: translateY(-3px);
border-color: var(--p-accent-line);
box-shadow: var(--p-shadow-hover);
}
.p-card-index {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
margin-bottom: 16px;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.02em;
color: var(--p-accent);
background: var(--p-accent-soft);
border-radius: 8px;
}
.p-card-title {
margin: 0;
font-size: 16px;
font-weight: 650;
line-height: 1.4;
color: var(--p-ink);
}
.p-card-text {
margin: 10px 0 0;
font-size: 14px;
line-height: 1.75;
color: var(--p-ink-2);
}
/* ---------- 标签切换 ---------- */
.p-tabs {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px;
margin-bottom: 40px;
}
.p-tab {
height: 38px;
padding: 0 18px;
font-family: inherit;
font-size: 14px;
font-weight: 500;
color: var(--p-ink-2);
background: transparent;
border: 1px solid var(--p-line-strong);
border-radius: 999px;
cursor: pointer;
transition: all 0.18s;
}
.p-tab:hover {
color: var(--p-ink);
border-color: var(--p-ink-3);
}
.p-tab.is-active {
color: #fff;
background: var(--p-accent);
border-color: var(--p-accent);
}
html.dark .p-tab.is-active {
color: #0c0f16;
}
/* ---------- 左右图文 ---------- */
.p-split {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 64px;
align-items: center;
}
@media (max-width: 960px) {
.p-split {
grid-template-columns: 1fr;
gap: 40px;
}
}
.p-list {
margin: 28px 0 0;
padding: 0;
list-style: none;
}
.p-list li {
position: relative;
padding: 14px 0 14px 30px;
font-size: 15px;
line-height: 1.7;
color: var(--p-ink-2);
border-top: 1px solid var(--p-line);
}
.p-list li::before {
content: '';
position: absolute;
left: 4px;
top: 22px;
width: 7px;
height: 7px;
border-radius: 2px;
background: var(--p-accent);
}
.p-list strong {
color: var(--p-ink);
font-weight: 600;
}
/* ---------- 代码面板 ---------- */
.p-panel {
padding: 22px 24px;
font-family: var(--vp-font-family-mono);
font-size: 13px;
line-height: 1.9;
color: var(--p-ink-2);
background: var(--p-surface-alt);
border: 1px solid var(--p-line);
border-radius: var(--p-radius);
}
.p-panel .c {
color: var(--p-ink-3);
}
.p-panel .k {
color: var(--p-accent);
}
/* ---------- 步骤 ---------- */
.p-steps {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
margin-top: 8px;
}
@media (max-width: 860px) {
.p-steps {
grid-template-columns: 1fr;
}
}
.p-step {
padding: 26px;
background: var(--p-surface);
border: 1px solid var(--p-line);
border-radius: var(--p-radius);
}
.p-step-num {
display: inline-block;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.08em;
color: var(--p-accent);
}
.p-step h3 {
margin: 10px 0 0;
font-size: 16px;
font-weight: 650;
color: var(--p-ink);
}
.p-step p {
margin: 8px 0 0;
font-size: 14px;
line-height: 1.75;
color: var(--p-ink-2);
}
.p-step code {
display: block;
margin-top: 14px;
padding: 10px 12px;
font-size: 12.5px;
color: var(--p-ink);
background: var(--p-surface-alt);
border: 1px solid var(--p-line);
border-radius: var(--p-radius-sm);
word-break: break-all;
white-space: pre-wrap;
}
/* ---------- 底部 CTA ---------- */
.p-cta {
text-align: center;
padding: 88px 0;
}
.p-cta .p-title {
font-size: 32px;
}
/* ---------- 项目卡片 ---------- */
.p-proj {
display: flex;
flex-direction: column;
padding: 30px 28px;
background: var(--p-surface);
border: 1px solid var(--p-line);
border-radius: var(--p-radius);
box-shadow: var(--p-shadow);
color: var(--p-ink) !important;
text-decoration: none !important;
transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s;
}
.p-proj:hover {
transform: translateY(-3px);
border-color: var(--p-accent-line);
box-shadow: var(--p-shadow-hover);
}
.p-proj-top {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
}
.p-proj-name {
margin: 0;
font-size: 21px;
font-weight: 680;
letter-spacing: -0.01em;
color: var(--p-ink);
border: none;
padding: 0;
}
.p-badge {
flex: none;
padding: 3px 10px;
font-size: 11.5px;
font-weight: 600;
color: var(--p-accent);
background: var(--p-accent-soft);
border: 1px solid var(--p-accent-line);
border-radius: 999px;
}
.p-proj-tagline {
margin: 10px 0 0;
font-size: 14px;
font-weight: 600;
color: var(--p-ink-2);
}
.p-proj-summary {
margin: 12px 0 0;
font-size: 14px;
line-height: 1.8;
color: var(--p-ink-2);
}
.p-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin: 18px 0 0;
padding: 0;
list-style: none;
}
.p-tags li {
padding: 3px 9px;
font-size: 11.5px;
color: var(--p-ink-3);
background: var(--p-surface-alt);
border: 1px solid var(--p-line);
border-radius: 6px;
}
.p-proj-foot {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-top: 22px;
padding-top: 18px;
border-top: 1px solid var(--p-line);
font-size: 12.5px;
color: var(--p-ink-3);
}
.p-proj-more {
font-weight: 600;
color: var(--p-accent);
}
/* ============================================================
内容页(非首页)的细节调整
============================================================ */
/* 属性表首列不要挤成竖排 */
.vp-doc table th:first-child,
.vp-doc table td:first-child {
white-space: nowrap;
}
/* 表格更轻,去掉重边框感 */
.vp-doc table {
display: table;
width: 100%;
}
.vp-doc tr {
background: transparent !important;
}
.vp-doc th {
font-weight: 600;
color: var(--p-ink, inherit);
}
/* 详情页一级标题更醒目 */
.vp-doc h1 {
font-size: 38px;
font-weight: 700;
letter-spacing: -0.025em;
line-height: 1.2;
}
@media (max-width: 720px) {
.vp-doc h1 {
font-size: 28px;
}
}
/* 二级标题加一条短强调线 */
.vp-doc h2 {
padding-top: 40px;
margin-top: 56px;
font-size: 26px;
font-weight: 650;
letter-spacing: -0.02em;
}
.vp-doc h2::before {
content: '';
display: block;
width: 32px;
height: 3px;
margin-bottom: 18px;
border-radius: 2px;
background: var(--p-accent, #4f46e5);
}
.vp-doc h3 {
font-size: 18px;
font-weight: 650;
}
/* 提示块圆角统一 */
.vp-doc .custom-block {
border-radius: 10px;
}
/* 行内代码轻一点 */
.vp-doc :not(pre) > code {
border-radius: 5px;
font-size: 0.86em;
}
/* 内容页正文用强调色变量,避免与默认主题脱节 */
.vp-doc a {
color: var(--p-accent);
text-decoration-color: var(--p-accent-line);
}
.vp-doc a:hover {
text-decoration-color: var(--p-accent);
}
/* ============================================================
产品功能卡片
============================================================ */
.pf-card {
position: relative;
}
.pf-name {
font-size: 17px;
}
.pf-tagline {
margin: 8px 0 0;
font-size: 13.5px;
font-weight: 600;
color: var(--p-accent);
}
.pf-list {
margin: 20px 0 0;
padding: 0;
list-style: none;
flex: 1;
}
.pf-list li {
position: relative;
padding: 9px 0 9px 22px;
font-size: 13.5px;
line-height: 1.6;
color: var(--p-ink-2);
border-top: 1px solid var(--p-line);
}
.pf-list li:first-child {
border-top: none;
}
.pf-list li::before {
content: '';
position: absolute;
left: 2px;
top: 17px;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--p-accent);
opacity: 0.55;
}
.pf-more {
display: inline-block;
margin-top: 20px;
font-size: 13px;
font-weight: 600;
color: var(--p-accent);
text-decoration: none !important;
}
.pf-more:hover {
text-decoration: underline !important;
text-underline-offset: 3px;
}
/* ============================================================
核心能力卡片
============================================================ */
.ca-card {
position: relative;
padding: 26px 24px;
}
.ca-tag {
display: inline-block;
align-self: flex-start;
width: auto;
margin-bottom: 14px;
padding: 2px 9px;
font-size: 11.5px;
font-weight: 600;
letter-spacing: 0.04em;
color: var(--p-accent);
background: var(--p-accent-soft);
border-radius: 5px;
}
.ca-card .p-card-title {
font-size: 15.5px;
}
.ca-card .p-card-text {
font-size: 13.5px;
line-height: 1.7;
}
/* ============================================================
官方文档
============================================================ */
.doc-grid {
margin-bottom: 8px;
}
.doc-card {
text-decoration: none !important;
color: var(--p-ink) !important;
}
.doc-top {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
}
.doc-top .p-card-title {
font-size: 15.5px;
}
.doc-arrow {
flex: none;
margin-top: 3px;
color: var(--p-ink-3);
transition: color 0.18s, transform 0.18s;
}
.doc-card:hover .doc-arrow {
color: var(--p-accent);
transform: translate(1px, -1px);
}
.doc-card .p-card-text {
flex: 1;
font-size: 13.5px;
line-height: 1.7;
}
.doc-project {
display: inline-block;
align-self: flex-start;
margin-top: 18px;
padding: 2px 9px;
font-size: 11.5px;
color: var(--p-ink-3);
background: var(--p-surface-alt);
border: 1px solid var(--p-line);
border-radius: 5px;
}
html.dark .doc-project {
background: rgba(255, 255, 255, 0.04);
}
.doc-note {
margin: 28px 0 0;
text-align: center;
font-size: 13px;
color: var(--p-ink-3);
}
.doc-note a {
color: var(--p-accent);
text-decoration: none;
}
.doc-note a:hover {
text-decoration: underline;
text-underline-offset: 3px;
}
/* ============================================================
导航:锚点项在窄屏隐藏,避免挤压
============================================================ */
@media (max-width: 860px) {
.VPNavBarMenuGroup:nth-child(n + 5):nth-child(-n + 7) {
display: none;
}
}
/* ============================================================
强调区块 —— 深色底,用于需要跳出视觉节奏的区块
浅色主题下用近黑底,暗色主题下用略亮于页面底的深灰,
两种主题下都能形成明显层次,而不是和相邻区块同一色阶。
============================================================ */
.p-section--deep {
position: relative;
border-top: none;
background: #0d1220;
color: #e8edf7;
overflow: hidden;
}
.p-section--deep::before {
content: '';
position: absolute;
inset: -30% -10% auto;
height: 460px;
background: radial-gradient(46% 60% at 50% 0%, rgba(99, 102, 241, 0.28) 0%, transparent 72%);
pointer-events: none;
}
.p-section--deep > .p-wrap {
position: relative;
}
/* 强调区内的标题与正文反白 */
.p-section--deep .p-eyebrow {
color: #a9b4ff;
background: rgba(129, 140, 248, 0.14);
border-color: rgba(129, 140, 248, 0.35);
}
.p-section--deep .p-title {
color: #f4f7fd;
}
.p-section--deep .p-desc {
color: #a8b4c9;
}
/* 强调区内的卡片:半透明白,压在深底上 */
.p-section--deep .p-card {
background: rgba(255, 255, 255, 0.055);
border-color: rgba(255, 255, 255, 0.12);
box-shadow: none;
}
.p-section--deep .p-card:hover {
background: rgba(255, 255, 255, 0.075);
border-color: rgba(129, 140, 248, 0.5);
box-shadow: 0 18px 40px -20px rgba(0, 0, 0, 0.8);
}
.p-section--deep .p-card-title {
color: #f0f4fb;
}
.p-section--deep .p-card-text {
color: #b0bccf;
}
.p-section--deep .ca-tag {
color: #b9c2ff;
background: rgba(129, 140, 248, 0.16);
}
/* 强调区顶部/底部加一道极细的高光线,强化分隔 */
.p-section--deep::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(129, 140, 248, 0.55), transparent);
}
/* 暗色主题下:页面底是 #0c0f16,强调区改用略亮的冷色并加强光晕,
靠"提亮"而不是"压深"来形成层次 */
html.dark .p-section--deep {
background: #161b2c;
}
html.dark .p-section--deep::before {
height: 520px;
background: radial-gradient(52% 64% at 50% 0%, rgba(99, 102, 241, 0.34) 0%, transparent 70%);
}
html.dark .p-section--deep .p-card {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.11);
}
html.dark .p-section--deep .p-card:hover {
background: rgba(255, 255, 255, 0.08);
border-color: rgba(165, 174, 255, 0.55);
}
html.dark .p-section--deep .p-title {
color: #ffffff;
}
html.dark .p-section--deep .p-card-text {
color: #c2cbdb;
}
/* ============================================================
导航栏锚点高亮
VitePress 只按 path 判定 active,同一路径下的多个锚点会一起亮。
这里用 data-anchor-active 属性接管,并压掉错误的默认 active。
选择器用 :not([data-anchor-active]) 精确排除正确项。
============================================================ */
/* 压制 VitePress 默认 active 只在首页生效:
首页上「/」「/#features」等路径部分相同,默认逻辑会把多个项同时点亮;
其它页面(/projects/、/about)必须保留正常高亮。 */
html.portal-nav-home .VPNavBarMenuLink.active:not([data-anchor-active]) {
color: var(--vp-c-text-1);
}
.VPNavBarMenuLink[data-anchor-active],
.VPNavBarMenuLink.active[data-anchor-active] {
color: var(--vp-c-brand-1);
}
/* 首页内锚点项未激活时保持常态色,并补一个 hover 反馈 */
.VPNavBarMenuLink[href*='#']:not([data-anchor-active]):hover {
color: var(--vp-c-brand-1);
}
/* ============================================================
左上角站点标题两行排版
第一行:雪羊科技(主标题)
第二行:物联网卡与设备管理平台(副标题,更弱)
副标题用 display:block 自然换行,无需 <br>
============================================================ */
.VPNavBarTitle .title-text {
line-height: 1.2;
}
.VPNavBarTitle .title-text .site-subtitle {
display: block;
margin-top: 2px;
font-size: 11.5px;
font-weight: 500;
letter-spacing: 0.02em;
color: var(--p-ink-3);
}
@media (max-width: 860px) {
.VPNavBarTitle .title-text .site-subtitle {
font-size: 10.5px;
}
}
/* ============================================================
导航左上角品牌区:两行结构
第一行:雪羊科技(品牌名)
第二行:物联网卡与设备管理平台(定位副标题,更弱)
siteTitle 里的副标题是 <span class="site-subtitle">,由 v-html 插入。
注意:它的父级是 VitePress 自己的裸 <span>(无 class),
所以选择器不能写 .title-text .site-subtitle —— 那个类根本不存在。
直接用 .VPNavBarTitle .site-subtitle 即可。
============================================================ */
.VPNavBarTitle .title {
line-height: 1.2;
gap: 10px;
}
/* 品牌名与副标题的容器:两行紧凑排列,整体垂直居中 */
.VPNavBarTitle .title > span {
display: flex;
flex-direction: column;
justify-content: center;
line-height: 1.15;
/* 品牌名与副标题作为一个整体在导航高度内垂直居中 */
height: var(--vp-nav-height);
}
.VPNavBarTitle .site-subtitle {
display: block;
margin-top: 3px;
font-size: 11px;
font-weight: 500;
letter-spacing: 0.03em;
line-height: 1.25;
color: var(--p-ink-3);
}
@media (max-width: 860px) {
.VPNavBarTitle .site-subtitle {
font-size: 10.5px;
margin-top: 0;
}
}
/* 窄屏下副标题可能挤压,超窄时隐藏,只留品牌名 */
@media (max-width: 480px) {
.VPNavBarTitle .site-subtitle {
display: none;
}
}
/* ============================================================
整站页脚(PortalFooter)
结构对齐商业站范式:品牌 + 多栏链接 + 联系方式 + 版权条。
配色沿用蓝调浅色体系(不铺深色),靠浅蓝底与细分隔线分区。
============================================================ */
.pf-foot {
position: relative;
/* 页脚整宽仍是通栏(保证有侧栏的页面能盖住 fixed 侧栏),
但浅蓝底纹只铺在内容宽度内:用 background-size + center 让底纹
与 .p-wrap 的 1180px 对齐,两侧自然留白。
这样视觉上不再是一条横贯整屏的浅蓝色带(1440 视口下比内容宽 300px)。 */
background-image: linear-gradient(180deg, #f7faff 0%, #eef4fe 100%);
background-repeat: no-repeat;
background-position: top center;
background-size: min(100%, var(--p-max)) 100%;
color: var(--p-ink-2);
}
/* 底纹与内容之间加一道极淡的描边,收窄后不至于是「悬空的一块色」 */
.pf-foot::before {
content: '';
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: min(100%, var(--p-max));
height: 1px;
background: var(--p-line);
pointer-events: none;
}
/**
* 有侧边栏的页面(/projects/、/guide/ 等文档页):
*
* 早先的做法是给页脚加 padding-left: var(--vp-sidebar-width) 去「避让」侧栏。
* 但侧栏是 fixed + 不透明底色(#f6f6f7),一直铺到视口底部,于是页脚那条
* 浅蓝底纹被侧栏盖掉左边一大块,只剩内容被推到右边 —— 宽屏下左边空白可达
* 视口宽度的 1/4(1440→272px、1920→512px),看上去像「页脚缺了一块」。
*
* 现在的做法:页脚保持整宽、内容用 .p-wrap 居中(与首页/演示页完全一致),
* 只把页脚的层级抬到侧栏之上(桌面端侧栏 z-index = --vp-z-index-sidebar = 25)。
* 侧栏由此在页脚上边缘「结束」,页脚恢复整幅底色;侧栏导航项目都排在最顶部,
* 被页脚覆盖的只是它下方那段空白。
* 移动端(<960px)侧栏是抽屉,层级必须保持在上层,故不抬。
*/
@media (min-width: 960px) {
.pf-foot {
position: relative;
z-index: calc(var(--vp-z-index-sidebar) + 1);
}
/**
* ⚠️ 空白断层的真凶:VitePress 原生给 `.aside-content`(右侧「本页目录」容器)
* 设了 `min-height: calc(100vh - nav - 48px)`。
* 短页面时它会先把 `.VPDoc .container` 撑到接近一屏,
* 再叠上页脚 → 总高 = 一屏 + 页脚,滚到底就只剩「一大片空白 + 页脚」。
* 正文其实只有 466px,却被撑到 749px,多出 283px 纯空白。
*
* 修法:取消目录容器的强制高度,让它按自身内容高走。
* 目录条目少就短,条目多自然变长,不再拉高整页。
*/
.VPDoc .aside-content {
min-height: 0;
}
/**
* 短页面时让页脚自然跟随内容,不要把正文区强行撑满一屏。
* (撑满一屏的旧写法会把页脚推到"一屏之外",制造空白断层)
*/
.VPDoc {
min-height: 0;
}
/**
* 页脚压住侧栏下缘时,把那道「切边」做成 26px 渐隐
* (--pf-side-encroach 由 theme/sidebar-fade.ts 按滚动位置写入),
* 否则侧栏最后一个导航项会只露半行,像渲染坏了。
*/
.VPSidebar.pf-side-fade {
--pf-side-cut: calc(100% - var(--pf-side-encroach, 0px));
-webkit-mask-image: linear-gradient(
to bottom,
#000 calc(var(--pf-side-cut) - 26px),
transparent var(--pf-side-cut)
);
mask-image: linear-gradient(
to bottom,
#000 calc(var(--pf-side-cut) - 26px),
transparent var(--pf-side-cut)
);
}
}
html.dark .pf-foot {
/* 必须用 background-image 而不是简写 background:
简写会把 background-size 重置回 auto,暗色下底纹又铺满整屏。 */
background-image: linear-gradient(180deg, rgba(37, 99, 235, 0.10) 0%, transparent 100%);
background-repeat: no-repeat;
background-position: top center;
background-size: min(100%, var(--p-max)) 100%;
}
.pf-foot-main {
display: grid;
/* 品牌区略宽(描述文案不碎行),中段三列够放「秀杰 DTU 管理平台」,
右栏够放长邮箱且「邮箱」标签不被压成两行。
二维码改为点击浮出后,右栏不再需要为 246px 预留宽度。 */
grid-template-columns: minmax(0, 1.25fr) minmax(0, 1.8fr) minmax(0, 0.92fr);
gap: 38px;
padding: 64px 0 48px;
}
/* ---------- 品牌区 ---------- */
.pf-foot-logo {
display: inline-flex;
align-items: center;
gap: 11px;
font-size: 22px;
font-weight: 700;
letter-spacing: -0.01em;
color: var(--p-ink) !important;
text-decoration: none !important;
}
.pf-foot-logo img {
width: 42px;
height: 42px;
object-fit: contain;
}
.pf-foot-slogan {
margin: 18px 0 0;
font-size: 16.5px;
font-weight: 650;
letter-spacing: 0.01em;
color: var(--p-accent);
}
.pf-foot-desc {
margin: 10px 0 0;
max-width: 400px;
font-size: 15.5px;
line-height: 1.78;
color: var(--p-ink-2);
/* 避免中文末行只剩一两个字(如「署。」独占一行) */
text-wrap: pretty;
}
.pf-foot-chips {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 20px 0 0;
padding: 0;
list-style: none;
}
.pf-foot-chips li {
padding: 6px 13px;
font-size: 13px;
color: var(--p-ink-2);
background: var(--p-surface);
border: 1px solid var(--p-line);
border-radius: 999px;
}
/* ---------- 链接栏 ---------- */
.pf-foot-cols {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 30px;
}
.pf-foot-col h4,
.pf-foot-contact h4 {
margin: 0 0 18px;
font-size: 15px;
font-weight: 650;
letter-spacing: 0.02em;
color: var(--p-ink);
}
.pf-foot-col ul {
margin: 0;
padding: 0;
list-style: none;
}
.pf-foot-col li + li {
margin-top: 11px;
}
.pf-foot-col a {
display: inline-block;
font-size: 16px;
line-height: 1.45;
color: var(--p-ink-2) !important;
text-decoration: none !important;
white-space: nowrap;
transition: color 0.16s;
}
.pf-foot-col a:hover {
color: var(--p-accent) !important;
}
/* ---------- 联系方式 ---------- */
.pf-foot-contact {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.pf-foot-contact-row {
display: flex;
/* 图标与首行文字对齐,而不是与整行(可能多行)垂直居中。
否则长邮箱折行时,图标会落在两行中间,看起来「歪了」。 */
align-items: flex-start;
gap: 8px;
width: 100%;
padding: 0;
text-decoration: none !important;
}
/* 行距与左侧链接栏同节奏:23px 行高 + 11px 间隔 */
.pf-foot-contact-row + .pf-foot-contact-row {
margin-top: 11px;
}
/* 前置图标:与首行文字同高,浅蓝底上取中灰蓝,hover 变蓝 */
.pf-foot-contact-icon {
flex: none;
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
/* 18px 图标放进 23px 行高里,补 2.5px 让它与首行文字视觉居中;
行高 23px、字号 14/16px 时文字基线偏下,这个值实测最贴。 */
margin-top: 2.5px;
color: #94a3b8;
transition: color 0.16s;
}
.pf-foot-contact-icon svg {
width: 100%;
height: 100%;
}
.pf-foot-contact-row:hover .pf-foot-contact-icon {
color: var(--p-accent);
}
.pf-foot-contact-text {
display: flex;
flex-direction: row;
align-items: baseline;
gap: 10px;
min-width: 0;
}
/* 标签不换行:栏宽一度被长邮箱挤压,「邮箱」曾被压成两行 */
.pf-foot-contact-label {
flex: none;
white-space: nowrap;
}
/* 复制按钮:外观与普通文本行一致,抹掉浏览器默认按钮样式 */
.pf-foot-contact-copy {
flex: 0 1 auto;
padding: 0;
font-family: inherit;
text-align: left;
background: transparent;
border: none;
cursor: pointer;
}
/* 轻提示:常态不抢眼,hover / 已复制时变蓝 */
.pf-foot-copy-hint {
margin-left: 7px;
font-style: normal;
font-size: 12px;
font-weight: 500;
color: var(--p-ink-3);
opacity: 0;
transition: opacity 0.16s, color 0.16s;
}
.pf-foot-contact-row:hover .pf-foot-copy-hint {
opacity: 1;
color: var(--p-accent);
}
/* 已复制:常显并高亮 */
.pf-foot-contact-row.is-copied .pf-foot-copy-hint {
opacity: 1;
color: var(--p-accent);
font-weight: 600;
}
.pf-foot-contact-row.is-copied .pf-foot-contact-value {
color: var(--p-accent);
}
.pf-foot-contact-row--btn .pf-foot-contact-value {
display: inline-flex;
align-items: baseline;
}
.pf-foot-contact-label {
font-size: 14px;
line-height: 23px;
color: var(--p-ink-3);
}
.pf-foot-contact-value {
font-size: 16px;
font-weight: 400;
line-height: 23px;
color: var(--p-ink-2);
transition: color 0.16s;
/* 不折行:右栏仅 243px,contact@xueyangai.com 在 16px 下会断成两行,
一断行图标就会「浮」在两行之间。 */
white-space: nowrap;
}
/* 邮箱偏长,单独降一档字号,保证在 243px 栏里单行放下 */
.pf-foot-contact-row[data-key='mail'] .pf-foot-contact-value {
font-size: 15px;
letter-spacing: -0.01em;
}
.pf-foot-contact-row:hover .pf-foot-contact-value {
color: var(--p-accent);
}
/* ---------- 企业微信二维码(点击浮出) ----------
二维码必须按 246px 1:1 显示(实测小于此尺寸解码失败,浏览器缩放也会扫不出),
这比页脚右栏还宽,直接平铺会把整栏拉宽。所以改成一行触发器 + 向上浮出的浮层:
平时与「电话/邮箱」同构,点击后浮层盖在上方,不影响页脚栅格。 */
.pf-foot-qr {
position: relative;
width: 100%;
margin-top: 11px;
}
.pf-foot-qr-trigger {
display: flex;
/* 与电话/邮箱行一致:图标对齐首行,不随多行垂直居中 */
align-items: flex-start;
gap: 8px;
width: 100%;
padding: 0;
font-family: inherit;
text-align: left;
background: none;
border: 0;
cursor: pointer;
}
.pf-foot-qr-trigger .pf-foot-contact-icon {
width: 18px;
height: 18px;
}
.pf-foot-qr-trigger .pf-foot-contact-icon svg {
width: 100%;
height: 100%;
}
.pf-foot-qr-trigger:hover .pf-foot-contact-icon,
.pf-foot-qr-trigger.is-open .pf-foot-contact-icon {
color: var(--p-accent);
}
.pf-foot-qr-value {
display: inline-flex;
align-items: baseline;
gap: 6px;
}
.pf-foot-qr-trigger:hover .pf-foot-contact-value,
.pf-foot-qr-trigger.is-open .pf-foot-contact-value {
color: var(--p-accent);
}
/* 浮层:向上展开,右对齐,避免超出视口右缘 */
.pf-foot-qr-pop {
position: absolute;
bottom: calc(100% + 12px);
right: 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
padding: 10px 10px 12px;
background: #ffffff;
border: 1px solid var(--p-line-strong);
border-radius: 14px;
box-shadow: 0 24px 56px -20px rgba(15, 23, 42, 0.32);
z-index: 10;
}
.pf-foot-qr-pop img {
display: block;
/* 1:1,禁止浏览器重采样 —— 缩放会让码点边缘发灰导致扫不出 */
width: 246px;
height: 246px;
image-rendering: pixelated;
border-radius: 6px;
}
.pf-foot-qr-pop-hint {
font-size: 12.5px;
line-height: 1.4;
color: var(--p-ink-3);
}
/* 小三角,指向触发器 */
.pf-foot-qr-pop::after {
content: '';
position: absolute;
bottom: -6px;
right: 26px;
width: 12px;
height: 12px;
background: #ffffff;
border-right: 1px solid var(--p-line-strong);
border-bottom: 1px solid var(--p-line-strong);
transform: rotate(45deg);
}
/* 暗色主题下二维码保持白底黑码,不能被反色 */
html.dark .pf-foot-qr-pop,
html.dark .pf-foot-qr-pop::after {
background: #ffffff;
}
.pf-foot-btn {
margin-top: 22px;
height: 44px;
padding: 0 24px;
font-size: 15.5px;
}
/* ---------- 版权条 ---------- */
.pf-foot-bottom {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 20px 0 28px;
border-top: 1px solid var(--p-line);
font-size: 13.5px;
color: var(--p-ink-3);
}
.pf-foot-bottom-right {
display: inline-flex;
align-items: center;
gap: 8px;
}
.pf-foot-bottom-right a {
color: var(--p-ink-3) !important;
text-decoration: none !important;
}
.pf-foot-bottom-right a:hover {
color: var(--p-accent) !important;
}
.pf-foot-bottom-right i {
font-style: normal;
opacity: 0.6;
}
/* ---------- 响应式 ---------- */
@media (max-width: 1024px) {
.pf-foot-main {
grid-template-columns: 1fr 1fr;
gap: 40px;
}
.pf-foot-contact {
grid-column: 1 / -1;
border-top: 1px solid var(--p-line);
padding-top: 28px;
}
}
@media (max-width: 720px) {
.pf-foot-main {
grid-template-columns: 1fr;
gap: 32px;
padding: 44px 0 32px;
}
.pf-foot-cols {
grid-template-columns: repeat(2, 1fr);
gap: 24px;
}
.pf-foot-bottom {
flex-direction: column;
align-items: flex-start;
}
}