feat(nav): 书架进导航(车道 G)——图标钮出舱钉死右侧

- 评估实数:第 6 项增量 zh +34-44/en +49-51px,旧结构 en 达
  102-125px 滚区且主题钮出首屏——收纳策略=搜索/语言/主题三钮
  移出 nav 滚动区钉死 .nav-actions 常驻右侧,内滚只波及文本项,
  任何宽度三功能钮可见(搜索是移动端主发现路径不可藏)
- nav 序:开源 · 文章 · 书架 · 图库 · 瞬间 · 关于(书架=阅读轨
  延伸紧跟文章);ui.ts +nav_books/kbd_gs_books
- Keyboard g-chord 增 s 键(g s→书架,双轨前缀路由),帮助表
  按既有 kbd_* 词条模式接入
- E2E en 导航断言扩为 ≥6 且含 /en/books/
- 已知余量:en 390-430 文本区滚 ~114px(词条已最短化,再省需砍
  语义留观);zh 全宽段 1-16px 基本无感

车道:op-gitea(八格扫描+E2E 33/33);收编验收:序/双轨 href/
三宽度三功能钮可见性/g s 实跳全过

验证: pnpm astro build exit 0;本地 dist 六项 playwright 全过;
      E2E 33/33(推送后 staging 复验)

Spec: none (导航扩容+出舱策略,车道级交付)

Co-authored-by: GLM-5.3
This commit is contained in:
wait4xandGLM-5.3 committed 2026-10-25 22:47:17 +08:00
1 parent 73c31ffb2a
commit 3a59edc79a
4 files changed
+44 -27

No files matched your search

+39 -26
View File
@@ -10,10 +10,11 @@ const prefix = curLocale === 'en' ? '/en' : '';
const isCurrent = (href: string) =>
path === href || path === href.replace(/\/$/, '') || path.startsWith(href);
// 顺序:开源 · 文章 · 图库 · 瞬间 · 关于(瞬间新进,开源前置)
// 顺序:开源 · 文章 · 书架 · 图库 · 瞬间 · 关于(书架=阅读轨延伸紧跟文章;瞬间新进,开源前置)
const nav = [
{ href: `${prefix}/opensource/`, label: t('nav_opensource', curLocale) },
{ href: `${prefix}/archives/`, label: t('nav_posts', curLocale) },
{ href: `${prefix}/books/`, label: t('nav_books', curLocale) },
{ href: `${prefix}/gallery/`, label: t('nav_gallery', curLocale) },
{ href: `${prefix}/moments/`, label: t('nav_moments', curLocale) },
{ href: `${prefix}/about/`, label: t('nav_about', curLocale) },
@@ -26,8 +27,9 @@ const switchLabel = curLocale === 'zh' ? 'EN' : '中';
<header class="site-header safe-area">
<div class="container-wide header-inner">
<a href={homeHref} class="brand" aria-label={`wait4x ${t('nav_home', curLocale)}`}>wait4x</a>
<nav aria-label="主导航">
<ul>
<div class="nav-side">
<nav aria-label="主导航">
<ul>
{nav.map((item) => (
<li>
<a
@@ -39,30 +41,26 @@ const switchLabel = curLocale === 'zh' ? 'EN' : '中';
</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>
<!-- 语言切换:与主题钮同为触屏友好的 mono 小钮 -->
<li>
<a href={switchHref} class="tap nav-link nav-lang" hreflang={curLocale === 'zh' ? 'en' : 'zh'}>{switchLabel}</a>
</li>
<!-- 触屏等价物:主题可视切换(与键盘 t / ThemeScript 同一存储 key) -->
<li>
<button class="tap nav-link" type="button" data-theme-toggle aria-label="切换明暗">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<circle cx="12" cy="12" r="4"></circle>
<path d="M12 2v2m0 16v2M4.9 4.9l1.4 1.4m11.4 11.4 1.4 1.4M2 12h2m16 0h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"></path>
</svg>
</button>
</li>
</ul>
</nav>
</nav>
<!-- 图标钮出舱(车道 G):6 文本项后移动端内滚只波及导航词,三个功能钮任何宽度常驻可见
(实测 390 en 第 6 项后文本区滚 ~116px,旧结构会把主题钮一并推出首屏) -->
<div class="nav-actions">
<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>
<a href={switchHref} class="tap nav-link nav-lang" hreflang={curLocale === 'zh' ? 'en' : 'zh'}>{switchLabel}</a>
<button class="tap nav-link" type="button" data-theme-toggle aria-label="切换明暗">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<circle cx="12" cy="12" r="4"></circle>
<path d="M12 2v2m0 16v2M4.9 4.9l1.4 1.4m11.4 11.4 1.4 1.4M2 12h2m16 0h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"></path>
</svg>
</button>
</div>
</div>
</div>
</header>
@@ -110,6 +108,21 @@ const switchLabel = curLocale === 'zh' ? 'EN' : '中';
padding: 0;
margin: 0;
}
/* nav-side:导航+功能钮右聚;nav 可收缩(min-width:0)——内滚只发生在文本区 */
.nav-side {
display: flex;
align-items: center;
gap: clamp(2px, 0.8vw, 8px);
min-width: 0;
margin-left: auto;
}
nav { min-width: 0; }
.nav-actions {
display: flex;
align-items: center;
gap: clamp(2px, 0.8vw, 8px);
flex: none;
}
.nav-link {
font-size: 15px;
color: var(--ink-3);
+2
View File
@@ -10,6 +10,7 @@ const pre = locale === 'en' ? '/en' : '';
const routes: Record<string, string> = {
h: `${pre}/`,
a: `${pre}/archives/`,
s: `${pre}/books/`,
g: `${pre}/gallery/`,
m: `${pre}/moments/`,
b: `${pre}/about/`,
@@ -25,6 +26,7 @@ const routes: Record<string, string> = {
<tr><td><kbd>?</kbd></td><td>{t('kbd_this_help', locale)}</td></tr>
<tr><td><kbd>g</kbd> <kbd>h</kbd></td><td>{t('kbd_gh_home', locale)}</td></tr>
<tr><td><kbd>g</kbd> <kbd>a</kbd></td><td>{t('kbd_ga_posts', locale)}</td></tr>
<tr><td><kbd>g</kbd> <kbd>s</kbd></td><td>{t('kbd_gs_books', locale)}</td></tr>
<tr><td><kbd>g</kbd> <kbd>g</kbd></td><td>{t('kbd_gg_gallery', locale)}</td></tr>
<tr><td><kbd>g</kbd> <kbd>m</kbd></td><td>{t('kbd_gm_moments', locale)}</td></tr>
<tr><td><kbd>/terminal</kbd></td><td>{t('kbd_terminal', locale)}</td></tr>