feat(layout): 基础布局/导航/页脚/明暗不闪烁

- BaseLayout:字体三轨统持、viewport-fit=cover、theme-color 双套、skip-link
- Header:≤4 项导航纪律 + sticky 毛玻璃 + aria-current;搜索 icon 占位待 T8
- Footer:ICP 备案链接(法定保留)+ built-from 具名槽位待 T7
- ThemeScript:head 内联先于首帧,手选 > 系统偏好,防白闪

验证: pnpm astro build exit 0;dist/index.html 含 ICP 文案与跳到正文链

Spec: 2026-10-24_add-blog-skeleton
Co-authored-by: GLM-5.3
This commit is contained in:
wait4xandGLM-5.3 committed 2026-10-24 23:09:45 +08:00
1 parent ee53279822
commit b50eddf2bb
5 files changed
+185 -13

No files matched your search

+39
View File
@@ -0,0 +1,39 @@
---
// 页脚:版权 + ICP 备案(法定保留)+ 构建透明槽位(T7 built-from 填充)
---
<footer class="site-footer safe-area">
<div class="container">
<p class="fine">
© {new Date().getFullYear()} wait4x ·
<a href="https://beian.miit.gov.cn/" rel="noopener noreferrer" target="_blank" class="link">
陕ICP备2026014860号-1
</a>
</p>
<!-- T7 注入:built from {commit} · {time} · Astro(链到 gitea commit) -->
<slot name="build-info" />
</div>
</footer>
<style>
.site-footer {
border-top: 1px solid var(--line);
margin-top: var(--space-6);
padding-block: var(--space-4);
}
.fine {
font-size: 13px;
color: var(--ink-3);
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
justify-content: center;
}
:global(.build-info) {
margin-top: var(--space-1);
text-align: center;
font-family: var(--font-mono);
font-size: 12px;
color: var(--ink-4);
}
</style>
+84
View File
@@ -0,0 +1,84 @@
---
// 顶部导航:≤4 项纪律(防 higan 十项复发);当前页 aria-current
const nav = [
{ href: '/archives/', label: '文章' },
{ href: '/gallery/', label: '图库' },
{ href: '/about/', label: '关于' },
];
const path = Astro.url.pathname;
const isCurrent = (href: string) =>
path === href || path === href.replace(/\/$/, '') || path.startsWith(href);
---
<header class="site-header safe-area">
<div class="container-wide header-inner">
<a href="/" class="brand" aria-label="wait4x 首页">wait4x</a>
<nav aria-label="主导航">
<ul>
{nav.map((item) => (
<li>
<a
href={item.href}
class="tap nav-link"
aria-current={isCurrent(item.href) ? 'page' : undefined}
>
{item.label}
</a>
</li>
))}
<!-- 搜索入口:T8 Pagefind 面板接管,先留可聚焦占位 -->
<li>
<button class="tap nav-link" type="button" data-search-trigger aria-label="搜索">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<circle cx="11" cy="11" r="7"></circle>
<path d="m20 20-3.5-3.5"></path>
</svg>
</button>
</li>
</ul>
</nav>
</div>
</header>
<style>
.site-header {
border-bottom: 1px solid var(--line);
position: sticky;
top: 0;
background: color-mix(in oklab, var(--bg) 88%, transparent);
backdrop-filter: blur(8px);
z-index: 10;
}
.header-inner {
display: flex;
align-items: center;
justify-content: space-between;
height: 56px;
}
.brand {
font-family: var(--font-mono);
font-weight: 700;
font-size: 18px;
color: var(--ink-1);
letter-spacing: 0.02em;
}
nav ul {
display: flex;
gap: var(--space-2);
list-style: none;
padding: 0;
margin: 0;
}
.nav-link {
font-size: 15px;
color: var(--ink-3);
padding-inline: var(--space-2);
border-radius: 6px;
transition: color var(--dur) var(--ease);
}
.nav-link:hover { color: var(--ink-1); }
.nav-link[aria-current='page'] { color: var(--accent); }
@media (max-width: 560px) {
.nav-link { font-size: 14px; padding-inline: var(--space-1); }
}
</style>
+14
View File
@@ -0,0 +1,14 @@
---
// 明暗不闪烁脚本:<head> 内联、先于首帧。
// 优先级:用户手选(localStorage) > 系统偏好;切换按钮(T7 键盘/组件)写同一 key。
---
<script is:inline>
(function () {
var saved = localStorage.getItem('theme');
var theme = saved === 'light' || saved === 'dark'
? saved
: (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.dataset.theme = theme;
})();
</script>
+44
View File
@@ -0,0 +1,44 @@
---
// 基础布局:字体/样式统一在此持有(页面不再各自引包)
// 明暗不闪烁:ThemeScript 必须在 <head> 内联先于首帧执行
import 'lxgw-wenkai-screen-webfont/lxgwwenkaiscreen.css';
import '@fontsource/noto-sans-sc/400.css';
import '@fontsource/noto-sans-sc/500.css';
import '@fontsource/noto-sans-sc/700.css';
import '@fontsource/jetbrains-mono/400.css';
import '@fontsource/jetbrains-mono/700.css';
import '../styles/global.css';
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import ThemeScript from '../components/ThemeScript.astro';
interface Props {
title: string;
description?: string;
/** 正文轨:reading 书评纸墨 / tech 技术(body 级字体氛围) */
track?: 'reading' | 'tech';
}
const { title, description = 'wait4x 的博客:阅读 · 技术 · 摄影', track = 'tech' } = Astro.props;
---
<!doctype html>
<html lang="zh" data-track={track}>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="generator" content={Astro.generator} />
<meta name="description" content={description} />
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#191d23" media="(prefers-color-scheme: dark)" />
<title>{title}</title>
<ThemeScript />
</head>
<body>
<a class="skip-link" href="#main">跳到正文</a>
<Header />
<main id="main" class="container">
<slot />
</main>
<Footer />
</body>
</html>
+4 -13
View File
@@ -1,15 +1,6 @@
--- ---
// T1 占位首页:T5 起替换为 BaseLayout + 真实首页结构 import BaseLayout from '../layouts/BaseLayout.astro';
// 字体三轨在此预挂(T5 移入 BaseLayout):unicode-range 预分片自托管
import 'lxgw-wenkai-screen-webfont/lxgwwenkaiscreen.css';
import '@fontsource/noto-sans-sc/400.css';
import '@fontsource/noto-sans-sc/500.css';
import '@fontsource/noto-sans-sc/700.css';
import '@fontsource/jetbrains-mono/400.css';
import '@fontsource/jetbrains-mono/700.css';
import '../styles/global.css';
--- ---
<html lang="zh"> <BaseLayout title="wait4x">
<head><meta charset="utf-8" /><title>wait4x</title></head> <p>Phase 1 · T5 布局就绪,首页结构 T6 填充。</p>
<body>Phase 1 skeleton</body> </BaseLayout>
</html>