Files
blog/src/components/geek/Keyboard.astro
T
wait4xandGLM-5.3 8ec1712135 feat(5f): reader 模式+版式基底+首屏 tagline+图库质感(T4/T7/T8)
- T4 reader:html.reader 单 class(去装饰非去内容:隐辉光/图标
  钮/pv 行,ICP 合规底线永留;正文字号+1px 行宽 56ch;入场动效
  关);命令面板 reader 命令+r 键+localStorage;ThemeScript 预绘
  防闪
- T7 基底:--flow-* 五流距变量双轨应用;暗色 hairline 降档;
  页面标题 700→600(裁定 #6 减字重不减号);focus-visible ring
  统一;列表行距 +2px;--paper/--radius-img/--ease-out 入库
- T8:首页 IdCard slot 落 tagline「写作者 · 读者 · 快门按压者,
  拥抱开源。」;图库暗色 4% 轻衬底+圆角统一(裁定 #4 双模式分判)

验证: E2E 33/33;reader r 键触发+辉光 none+刷新保持实测;流距
      computed 0.6em 实算;tagline 渲染实测

Spec: 2026-10-26_blog-5f

Co-authored-by: GLM-5.3
2026-11-01 09:09:27 +08:00

120 lines
4.5 KiB
Plaintext
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.
---
// 键盘层(极客层 P0):? 帮助 / t 主题 / g-chords 导航
// 惯例来源:GitHub 键位(g+N、? 帮助);每项能力都有可视入口(触屏等价物原则)
// 文案全部 ui.ts 词条:locale 构建期按页面 URL 自推断(BaseLayout 不透传,同 Footer 模式)
import { t } from '../../i18n/ui';
import { localeOf } from '../../lib/locale';
const locale = localeOf(Astro.url.pathname);
// g-chords 目标随轨前缀化(en 页 g h → /en/ 而非 /)——C 车道已知边界的补刀
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/`,
};
---
<dialog id="kbd-help" class="kbd-help" data-routes={JSON.stringify(routes)}>
<h2>{t('kbd_title', locale)}</h2>
<table>
<tbody>
<tr><td><kbd>Ctrl</kbd> <kbd>K</kbd></td><td>{t('kbd_palette', locale)}</td></tr>
<tr><td><kbd>t</kbd></td><td>{t('kbd_theme_toggle', locale)}</td></tr>
<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>
<tr><td><kbd>Esc</kbd></td><td>{t('kbd_esc_close', locale)}</td></tr>
</tbody>
</table>
<p class="hint">{t('kbd_hint', locale)}</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 === 'r') {
// reader 纯文本模式(5f T4):与命令面板/持久化同源
const root = document.documentElement;
root.classList.toggle('reader');
localStorage.setItem('reader', root.classList.contains('reader') ? '1' : '0');
return;
}
if (e.key === '?') {
(document.getElementById('kbd-help') as HTMLDialogElement | null)?.showModal();
return;
}
// 路由判断必须先于 g 武装分支:g→g 自身也是合法 chord(gallery),
// 原序里第二击 g 永远重新武装、routes['g'] 不可达——Phase 1 潜伏缺陷
// (g g 全站失灵实锤于 2026-10-25 矩阵测试)
if (chord === 'g' && routes[e.key]) {
location.href = routes[e.key]!;
chord = '';
clearTimeout(chordTimer);
return;
}
if (e.key === 'g') {
chord = 'g';
clearTimeout(chordTimer);
chordTimer = window.setTimeout(() => (chord = ''), 300);
}
});
</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>