feat(geek): 极客层 P0 四件套——键盘/构建透明/页面转场/项目墙
- Keyboard:? 帮助浮层 + t 明暗 + g-chords 导航(300ms 缓冲,输入域旁路);
路由表经 data-routes 注入(module 脚本 currentScript 为 null 的坑)
- BuildInfo:vite.define 四重回退注入 commit/时间,hash 链到 gitea
- ClientRouter 页面转场(reduced-motion 降级在 global.css 总闸)
- ProjectWall:构建时双源拉取(gitea+github)置顶排序+缓存兜底,零运行时 JS
- Header 补主题可视切换钮:键盘 t 的触屏等价物(同 key 同逻辑)
验证: pnpm astro build exit 0;dist 标记 built from×2/kbd-help×4/
view-transition×2; astro check 未跑(无类型错误构建期可证伪面有限)
Spec: 2026-10-24_add-blog-skeleton
Co-authored-by: GLM-5.3
This commit is contained in:
1 parent
b50eddf2bb
commit
725bfde21f
8 files changed
+324
-2
No files matched your search
@@ -0,0 +1,98 @@
|
||||
---
|
||||
// 键盘层(极客层 P0):? 帮助 / t 主题 / g-chords 导航
|
||||
// 惯例来源:GitHub 键位(g+N、? 帮助);每项能力都有可视入口(触屏等价物原则)
|
||||
const routes: Record<string, string> = {
|
||||
h: '/',
|
||||
a: '/archives/',
|
||||
g: '/gallery/',
|
||||
m: '/moments/',
|
||||
b: '/about/',
|
||||
};
|
||||
---
|
||||
|
||||
<dialog id="kbd-help" class="kbd-help" data-routes={JSON.stringify(routes)}>
|
||||
<h2>键盘快捷键</h2>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr><td><kbd>t</kbd></td><td>切换明暗</td></tr>
|
||||
<tr><td><kbd>?</kbd></td><td>本帮助</td></tr>
|
||||
<tr><td><kbd>g</kbd> <kbd>h</kbd></td><td>首页</td></tr>
|
||||
<tr><td><kbd>g</kbd> <kbd>a</kbd></td><td>文章</td></tr>
|
||||
<tr><td><kbd>g</kbd> <kbd>g</kbd></td><td>图库</td></tr>
|
||||
<tr><td><kbd>g</kbd> <kbd>m</kbd></td><td>瞬间</td></tr>
|
||||
<tr><td><kbd>Esc</kbd></td><td>关闭浮层</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="hint">提示:试试终端彩蛋(Phase 4 上线 <code>~</code>)</p>
|
||||
</dialog>
|
||||
|
||||
<script>
|
||||
// chord 缓冲 300ms;输入域聚焦时整体旁路;Esc 关一切浮层
|
||||
// 路由表由 data-routes 注入(module 脚本里 currentScript 恒为 null,不能走内联取参)
|
||||
const helpEl = document.getElementById('kbd-help') as HTMLElement & { dataset: { routes?: string } };
|
||||
const routes: Record<string, string> = JSON.parse(helpEl?.dataset.routes ?? '{}');
|
||||
let chord = '';
|
||||
let chordTimer: number | undefined;
|
||||
|
||||
const inField = () => {
|
||||
const el = document.activeElement;
|
||||
return el instanceof HTMLElement &&
|
||||
(el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.isContentEditable);
|
||||
};
|
||||
|
||||
const toggleTheme = () => {
|
||||
const root = document.documentElement;
|
||||
const next = root.dataset.theme === 'dark' ? 'light' : 'dark';
|
||||
root.dataset.theme = next;
|
||||
localStorage.setItem('theme', next);
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
document.querySelector('dialog[open]')?.close();
|
||||
return;
|
||||
}
|
||||
if (inField() || e.metaKey || e.ctrlKey || e.altKey) return;
|
||||
|
||||
if (e.key === 't') { toggleTheme(); return; }
|
||||
if (e.key === '?') {
|
||||
(document.getElementById('kbd-help') as HTMLDialogElement | null)?.showModal();
|
||||
return;
|
||||
}
|
||||
if (e.key === 'g') {
|
||||
chord = 'g';
|
||||
clearTimeout(chordTimer);
|
||||
chordTimer = window.setTimeout(() => (chord = ''), 300);
|
||||
return;
|
||||
}
|
||||
if (chord === 'g' && routes[e.key]) {
|
||||
location.href = routes[e.key]!;
|
||||
chord = '';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.kbd-help {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
background: var(--bg);
|
||||
color: var(--ink-2);
|
||||
padding: var(--space-4);
|
||||
max-width: 340px;
|
||||
}
|
||||
.kbd-help::backdrop { background: rgba(0, 0, 0, 0.4); }
|
||||
.kbd-help h2 { font-size: var(--text-h3); color: var(--ink-1); margin-bottom: var(--space-3); }
|
||||
table { border-collapse: collapse; }
|
||||
td { padding: 4px 10px 4px 0; font-size: 14px; }
|
||||
kbd {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-bottom-width: 2px;
|
||||
border-radius: 4px;
|
||||
padding: 1px 6px;
|
||||
background: var(--bg-soft);
|
||||
}
|
||||
.hint { margin-top: var(--space-3); font-size: 12px; color: var(--ink-3); }
|
||||
</style>
|
||||
Reference in new issue
Block a user