- 可视化选择(核心痛点解决):Sveltia 原生支持 registerEditorComponent
的 inline 形态且 onInsert 能拿到 selectedText → 实现「选中文字 → Insert ▸
排版 → 选字体/颜色 → 自动包 span class」完整链路;因 inline 组件只能进
Insert 菜单(field-based 虽可上工具栏但拿不到 selectedText),两个独立组件
会互相抢 pattern 匹配,故收敛为单入口 + 18 项分组面板(键盘可达/Esc 取消/
onEdit 回填当前类)
- public/admin/typography-classes.js 为单一事实源(扩展与文档共用)
- 颜色扩充 4 类:ink-warm / ink-cool / ink-faint / ink-accent-soft,全部亮暗
双态实测 ≥4.5:1(ink-faint 取白底达标的最淡值 4.69/4.75)
- 字体:体积账否决 webfont 新增(@fontsource zcool-xiaowei 用到即 +647.7KB,
远超 150KB 阈值;smiley-sans 无可用版本、sarasa 包为空壳),改为落地 4 个
**系统回退类**(font-song/font-kai-sys/font-hei-sys/font-fangsong,零下载,
文档注明依赖访客设备)
- 白名单 17 类同步;docs/写作指南.md 三表更新
- docs/research/10-页面后台化方案.md:五方案对比 + 推荐方案 E(页面骨架 blocks
/ 区块字段 / 局部自由区 / 样式即数据四层)+ 关于页九类区块 + 消毒与验收断言
+ 分阶段清单(约 7.2d)
验证: 16/16 颜色 computed 与对比度实测;选择器 7/7(桩 CMS 覆盖注册/插入/
编辑三层);构建 48 页 exit 0;E2E 37/37
Spec: none (排版能力 + 方案文档)
Co-authored-by: GLM-5.3
329 lines
15 KiB
CSS
329 lines
15 KiB
CSS
/* ============================================================
|
||
全局样式 —— 建立在 tokens.css 之上
|
||
约束:零运行时框架;动效全部包 prefers-reduced-motion
|
||
============================================================ */
|
||
@import "./tokens.css";
|
||
|
||
/* ---------- 轻量 reset ---------- */
|
||
*, *::before, *::after { box-sizing: border-box; }
|
||
* { margin: 0; }
|
||
html { -webkit-text-size-adjust: 100%; }
|
||
body {
|
||
font-family: var(--font-sans);
|
||
font-size: 16px;
|
||
line-height: 1.6;
|
||
color: var(--ink-2);
|
||
background: var(--bg);
|
||
}
|
||
img, svg, video { display: block; max-width: 100%; height: auto; }
|
||
input, button, textarea, select { font: inherit; color: inherit; }
|
||
|
||
/* 键盘焦点可见但克制 */
|
||
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||
|
||
/* 跳过导航(无障碍动线) */
|
||
.skip-link {
|
||
position: absolute; left: -9999px; top: var(--space-2);
|
||
padding: var(--space-2) var(--space-3); background: var(--bg); z-index: 100;
|
||
}
|
||
.skip-link:focus { left: var(--space-2); }
|
||
|
||
/* ---------- 链接(全站唯一强调色纪律) ---------- */
|
||
a { color: inherit; text-decoration: none; }
|
||
.link, .prose a {
|
||
color: var(--accent);
|
||
text-decoration: underline;
|
||
text-decoration-color: color-mix(in oklab, var(--accent) 40%, transparent);
|
||
text-underline-offset: 0.2em;
|
||
transition: text-decoration-color var(--dur) var(--ease), color var(--dur) var(--ease);
|
||
}
|
||
.link:hover, .prose a:hover { text-decoration-color: var(--accent); }
|
||
|
||
/* ---------- 版心 ---------- */
|
||
.container { max-width: var(--measure); margin-inline: auto; padding-inline: var(--space-3); }
|
||
.container-wide { max-width: calc(var(--measure) + 200px); margin-inline: auto; padding-inline: var(--space-3); }
|
||
|
||
/* ============================================================
|
||
正文分轨(丙路线核心:书评纸墨 × 技术灰阶)
|
||
============================================================ */
|
||
|
||
/* ---- 书评轨:纸面板 + 衬线 + 20/1.75 ---- */
|
||
.prose-reading {
|
||
font-family: var(--font-serif);
|
||
font-size: var(--prose-size-reading);
|
||
line-height: var(--leading-reading);
|
||
color: var(--paper-ink);
|
||
background: var(--paper);
|
||
max-width: var(--measure);
|
||
padding: var(--space-5) var(--space-4);
|
||
border-block: 1px solid var(--paper-line);
|
||
overflow-wrap: break-word;
|
||
}
|
||
.prose-reading p + p { margin-top: 0.9em; } /* 段间距制,无首行缩进 */
|
||
.prose-reading h2, .prose-reading h3 { line-height: var(--leading-heading); text-wrap: balance; }
|
||
.prose-reading h2 { font-size: var(--text-h2); margin-top: 1.8em; }
|
||
.prose-reading h3 { font-size: var(--text-h3); margin-top: 1.5em; }
|
||
|
||
/* ---- 技术轨:无衬线 + 19/1.7 ---- */
|
||
.prose-tech {
|
||
font-family: var(--font-sans);
|
||
font-size: var(--prose-size-tech);
|
||
line-height: var(--leading-tech);
|
||
color: var(--ink-2);
|
||
max-width: var(--measure);
|
||
overflow-wrap: break-word; /* 移动端审计 M1:裸长 URL 曾撑破 88px(只给了书评轨) */
|
||
}
|
||
.prose-tech p + p { margin-top: 0.9em; }
|
||
.prose-tech h2 { font-size: var(--text-h2); margin-top: 1.8em; line-height: var(--leading-heading); text-wrap: balance; }
|
||
.prose-tech h3 { font-size: var(--text-h3); margin-top: 1.5em; line-height: var(--leading-heading); }
|
||
|
||
/* ---------- 引文组件(报告 §3:A 墨线长书摘 / D 金句) ---------- */
|
||
/* 裸 blockquote(markdown 原生输出无类名)默认即模式 A;显式类可覆盖 */
|
||
.prose-reading blockquote, blockquote.quote-block {
|
||
border-left: 1px solid var(--paper-line);
|
||
border-left-width: 2px;
|
||
padding-left: 1.35em;
|
||
margin-block: 1.2em;
|
||
color: color-mix(in oklab, var(--paper-ink) 72%, transparent);
|
||
font-size: 1em;
|
||
font-style: normal; /* 反例修正:higan 整段加粗弃用 */
|
||
font-weight: 400;
|
||
}
|
||
.quote-cite {
|
||
display: block;
|
||
margin-top: 0.6em;
|
||
font-size: 0.875rem;
|
||
font-family: var(--font-sans);
|
||
color: var(--ink-3);
|
||
}
|
||
/* 模式 D:金句(62px 巨型引号水印) */
|
||
.pull-quote {
|
||
font-family: var(--font-kai);
|
||
position: relative;
|
||
margin-block: 1.6em;
|
||
padding-left: 2.2em;
|
||
font-size: 0.96em;
|
||
}
|
||
.pull-quote::before {
|
||
content: "\201C";
|
||
position: absolute; left: 0; top: -0.12em;
|
||
font-family: Georgia, serif;
|
||
font-size: 62px;
|
||
line-height: 1;
|
||
opacity: 0.18;
|
||
color: var(--mood);
|
||
}
|
||
|
||
/* ---------- 代码(终端风,亮暗同底) ---------- */
|
||
code, kbd, samp { font-family: var(--font-mono); }
|
||
:not(pre) > code {
|
||
font-size: 0.875em;
|
||
background: var(--bg-soft);
|
||
padding: 0.15em 0.4em;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--line);
|
||
}
|
||
pre {
|
||
background: var(--code-bg);
|
||
color: var(--code-ink);
|
||
font-size: 16px;
|
||
line-height: 1.6;
|
||
padding: var(--space-3) var(--space-4);
|
||
border-radius: 8px;
|
||
overflow-x: auto; /* 禁 break-all:正文横向滚动绝不发生 */
|
||
tab-size: 2;
|
||
}
|
||
pre code { background: none; border: none; padding: 0; font-size: inherit; }
|
||
/* 终端窗点缀(圆点由组件层加,此处只管字色纪律) */
|
||
.term-dots { display: flex; gap: 6px; margin-bottom: var(--space-2); }
|
||
.term-dots span { width: 10px; height: 10px; border-radius: 50%; background: #30363d; }
|
||
|
||
/* ---------- 正文媒体(图/视频/音频,Phase 5 媒体支持) ---------- */
|
||
/* 图片:markdown 相对路径经 Astro 图片服务优化;版心内圆角限宽 */
|
||
.prose-reading img, .prose-tech img {
|
||
border-radius: 10px;
|
||
margin-block: 1.2em;
|
||
max-width: 100%;
|
||
height: auto;
|
||
}
|
||
/* 图注:**显式类驱动**(`<p class="fig-cap">说明</p>`)。此前按 `p > em:only-child` 自动命中,
|
||
把正文里任何独占成段的斜体都变成块级居中——用户实测的排版 bug(斜体单独一行并居中)由此而来。 */
|
||
.prose-reading p.fig-cap, .prose-tech p.fig-cap,
|
||
.prose-reading p.fig-cap em, .prose-tech p.fig-cap em {
|
||
display: block;
|
||
text-align: center;
|
||
font-size: 0.85em;
|
||
font-family: var(--font-sans);
|
||
color: var(--ink-3);
|
||
margin-top: -0.6em;
|
||
margin-bottom: 1.2em;
|
||
font-style: normal;
|
||
}
|
||
/* 行内斜体基线:不换行、不居中(正常强调语义) */
|
||
.prose-reading em, .prose-tech em,
|
||
.prose-reading i, .prose-tech i {
|
||
display: inline;
|
||
font-style: italic;
|
||
}
|
||
/* ── 受限排版类(文章内通过 <span class> 使用;rehype 白名单只放行这些类名) ──
|
||
颜色一律走 tokens,亮暗双态都可读;禁任意 hex/内联样式(保持灰阶骨架)。 */
|
||
.prose-reading .ink-1, .prose-tech .ink-1 { color: var(--ink-1); }
|
||
.prose-reading .ink-2, .prose-tech .ink-2 { color: var(--ink-2); }
|
||
.prose-reading .ink-3, .prose-tech .ink-3 { color: var(--ink-3); }
|
||
.prose-reading .ink-muted, .prose-tech .ink-muted { color: var(--ink-4); }
|
||
.prose-reading .ink-accent, .prose-tech .ink-accent { color: var(--accent); }
|
||
.prose-reading .ink-accent-soft, .prose-tech .ink-accent-soft { color: var(--accent-soft-text); }
|
||
.prose-reading .ink-warm, .prose-tech .ink-warm { color: var(--ink-warm); }
|
||
.prose-reading .ink-cool, .prose-tech .ink-cool { color: var(--ink-cool); }
|
||
.prose-reading .ink-faint, .prose-tech .ink-faint { color: var(--ink-faint); }
|
||
/* 语义色板:彩色只作用于正文强调(站点外壳保持灰阶)。
|
||
每色相两档:标准 / soft(阈值内最淡);亮暗各取独立值,均实测 ≥4.5:1。 */
|
||
.prose-reading .c-blue, .prose-tech .c-blue { color: var(--c-blue); }
|
||
.prose-reading .c-blue-soft, .prose-tech .c-blue-soft { color: var(--c-blue-soft); }
|
||
.prose-reading .c-teal, .prose-tech .c-teal { color: var(--c-teal); }
|
||
.prose-reading .c-teal-soft, .prose-tech .c-teal-soft { color: var(--c-teal-soft); }
|
||
.prose-reading .c-green, .prose-tech .c-green { color: var(--c-green); }
|
||
.prose-reading .c-green-soft, .prose-tech .c-green-soft { color: var(--c-green-soft); }
|
||
.prose-reading .c-yellow, .prose-tech .c-yellow { color: var(--c-yellow); }
|
||
.prose-reading .c-yellow-soft, .prose-tech .c-yellow-soft { color: var(--c-yellow-soft); }
|
||
.prose-reading .c-orange, .prose-tech .c-orange { color: var(--c-orange); }
|
||
.prose-reading .c-orange-soft, .prose-tech .c-orange-soft { color: var(--c-orange-soft); }
|
||
.prose-reading .c-red, .prose-tech .c-red { color: var(--c-red); }
|
||
.prose-reading .c-red-soft, .prose-tech .c-red-soft { color: var(--c-red-soft); }
|
||
.prose-reading .c-pink, .prose-tech .c-pink { color: var(--c-pink); }
|
||
.prose-reading .c-pink-soft, .prose-tech .c-pink-soft { color: var(--c-pink-soft); }
|
||
.prose-reading .c-purple, .prose-tech .c-purple { color: var(--c-purple); }
|
||
.prose-reading .c-purple-soft, .prose-tech .c-purple-soft { color: var(--c-purple-soft); }
|
||
.prose-reading .font-serif, .prose-tech .font-serif { font-family: var(--font-serif); }
|
||
.prose-reading .font-kai, .prose-tech .font-kai { font-family: var(--font-kai); }
|
||
.prose-reading .font-sans, .prose-tech .font-sans { font-family: var(--font-sans); }
|
||
.prose-reading .font-mono, .prose-tech .font-mono { font-family: var(--font-mono); font-size: 0.92em; }
|
||
/* 系统字族回退类:不下载字体,命中访客本机字体——跨设备不保证一致(docs 已注明) */
|
||
/* 自托管优先 + 系统兜底(2026-11-02 口径):跨设备一致性由站内 webfont 保证,
|
||
系统字族只作最后兜底;font-fangsong 暂无 OFL 自托管包,仍为纯系统回退(docs 已注明)。 */
|
||
.prose-reading .font-song, .prose-tech .font-song {
|
||
font-family: "Noto Serif SC Variable", "Noto Serif SC", "Songti SC", SimSun, serif;
|
||
}
|
||
.prose-reading .font-kai-sys, .prose-tech .font-kai-sys {
|
||
font-family: "LXGW WenKai Screen", "Kaiti SC", STKaiti, KaiTi, serif;
|
||
}
|
||
.prose-reading .font-hei-sys, .prose-tech .font-hei-sys {
|
||
font-family: "Noto Sans SC Variable", "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||
}
|
||
.prose-reading .font-fangsong, .prose-tech .font-fangsong {
|
||
font-family: FangSong, STFangsong, "FangSong_GB2312", serif;
|
||
}
|
||
/* 霞鹜文楷 Light(自托管 @fontsource,OFL-1.1):更轻的金句字重 */
|
||
.prose-reading .font-kai-light, .prose-tech .font-kai-light {
|
||
font-family: 'LXGW WenKai', var(--font-kai);
|
||
font-weight: 300;
|
||
}
|
||
/* 文学常用:首字下沉(书评开篇)与金句块(引文式的独立强调段) */
|
||
.prose-reading p.drop-cap::first-letter, .prose-tech p.drop-cap::first-letter {
|
||
float: left;
|
||
font-family: var(--font-serif);
|
||
font-size: 3.1em;
|
||
line-height: 1;
|
||
padding-right: 0.08em;
|
||
margin-top: 0.05em;
|
||
color: var(--paper-ink);
|
||
}
|
||
.prose-reading .quote-line, .prose-tech .quote-line {
|
||
display: block;
|
||
font-family: var(--font-kai);
|
||
color: var(--paper-ink);
|
||
border-left: 2px solid var(--paper-line);
|
||
padding-left: 0.9em;
|
||
margin: 1.4em 0;
|
||
}
|
||
|
||
/* 视频与音频:原生标签,markdown raw HTML 直书(指南有模板) */
|
||
.prose-reading video, .prose-tech video,
|
||
.prose-reading audio, .prose-tech audio {
|
||
display: block;
|
||
width: 100%;
|
||
margin-block: 1.2em;
|
||
}
|
||
.prose-reading video, .prose-tech video {
|
||
border-radius: 10px;
|
||
background: #000;
|
||
max-height: 72dvh;
|
||
}
|
||
.prose-reading audio, .prose-tech audio {
|
||
border-radius: 999px;
|
||
}
|
||
|
||
/* ---------- 列表/分隔 ---------- */
|
||
hr { border: none; border-top: 1px solid var(--line); margin-block: var(--space-4); }
|
||
|
||
/* ---------- 选区(半透明强调,避免刺眼实心) ---------- */
|
||
::selection { background: var(--accent-soft); }
|
||
|
||
/* ---------- 触屏与安全区(§4e:交互组件显式 .tap 类达 44px 目标) ---------- */
|
||
.tap { min-height: 44px; min-width: 44px; display: inline-flex; align-items: center; justify-content: center; }
|
||
.safe-area { padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); }
|
||
|
||
/* ---------- 移动端排版降档(≤560px:书评 20→18) ---------- */
|
||
@media (max-width: 560px) {
|
||
:root { --prose-size-reading: 18px; --prose-size-tech: 18px; --text-h1: 24px; }
|
||
.prose-reading { padding: var(--space-4) var(--space-3); }
|
||
}
|
||
|
||
/* ---------- 动效总闸(唯一曲线已入 tokens) ---------- */
|
||
/* ===== 动效层(Phase 5e):背景辉光 + 入场 + 微交互 ===== */
|
||
:root { --mx: 50%; --my: 30%; }
|
||
.bg-glow {
|
||
position: fixed; inset: 0; z-index: -1; pointer-events: none;
|
||
background: radial-gradient(600px circle at var(--mx) var(--my),
|
||
color-mix(in oklab, var(--ink-1) 4%, transparent), transparent 70%);
|
||
opacity: 0; transition: opacity 0.6s ease;
|
||
}
|
||
@media (pointer: fine) {
|
||
.bg-glow { opacity: 1; }
|
||
@media (prefers-reduced-motion: reduce) { .bg-glow { opacity: 0; } }
|
||
}
|
||
/* 正文入场:一次性轻上浮(VT 换页内容替换自然重放,克制 12px/0.4s) */
|
||
#main > * { animation: rise-in 0.4s ease both; }
|
||
@keyframes rise-in { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
|
||
@media (prefers-reduced-motion: reduce) {
|
||
#main > * { animation: none; }
|
||
}
|
||
/* 列表行微交互(桌面悬停语言:ink 系已有变色,补 1px 上浮) */
|
||
@media (pointer: fine) {
|
||
.postlist .row { transition: transform 0.15s ease; }
|
||
.postlist .row:hover { transform: translateY(-1px); }
|
||
}
|
||
@media (prefers-reduced-motion: reduce) {
|
||
*, *::before, *::after {
|
||
animation-duration: 0.01ms !important;
|
||
animation-iteration-count: 1 !important;
|
||
transition-duration: 0.01ms !important;
|
||
}
|
||
}
|
||
|
||
|
||
/* ===== Phase 5f T7:流距变量应用(标题前后距/p+p 统一双轨) ===== */
|
||
.prose-reading h2, .prose-tech h2 { margin-top: var(--flow-h2-pre); margin-bottom: var(--flow-h2-post); }
|
||
.prose-reading h3, .prose-tech h3 { margin-top: var(--flow-h3-pre); margin-bottom: var(--flow-h3-post); }
|
||
.prose-reading p + p, .prose-tech p + p { margin-top: var(--flow-p); }
|
||
/* 页面标题字重 700→600(视觉裁定 #6:要轻减字重不减号;!important 压组件 scoped 默认) */
|
||
h1.page-title { font-weight: 600 !important; }
|
||
/* focus ring 收尾(O 卡:统一可键盘巡览性) */
|
||
a:focus-visible, button:focus-visible, summary:focus-visible {
|
||
outline: 2px solid var(--ink-3); outline-offset: 2px; border-radius: 4px;
|
||
}
|
||
/* 列表行距微增(裁定 #7:行距 +2~4px 优先于删线) */
|
||
.postlist .row { padding-block: calc(var(--space-2) + 2px); }
|
||
|
||
/* ===== Phase 5f T4:reader 纯文本模式(08 文档 §4.4 清单,视觉语义=去装饰非去内容) ===== */
|
||
html.reader .bg-glow { display: none !important; }
|
||
html.reader .site-header .nav-actions { display: none; } /* 收图标钮:保品牌+导航词 */
|
||
html.reader .site-footer .fine > *:not(a[href*="beian"]):not(.icp-sep) { display: none; }
|
||
html.reader .atk-pv-line { display: none; } /* 元信息让位正文 */
|
||
html.reader .prose-reading, html.reader .prose-tech {
|
||
font-size: calc(var(--prose-size-reading, 1.05rem) + 0.0625rem);
|
||
max-width: 56ch; /* 行宽收窄 */
|
||
}
|
||
html.reader #main > * { animation: none; } /* 入场动效关闭 */
|
||
/* ICP 备案链接永不隐藏(合规底线);分隔点号随之保留必要最小集 */
|