feat(books): 书架三案按视觉复核修完(长标题裁切/参差/孤儿位/无暗示)
视觉复核逐案挑出的缺陷全修:
A 竖排色脊:书名/作者按脊内竖向容量确定性截断(此前 10 字书名与 9 字作者挤成
第二窄列被 40px 脊宽裁掉);脊顶上留 10px hover 余量 + 底部基线;情绪色混色
从 16% 提到 26% 并加内描边(淡紫脊原本在白底上几乎消隐,墙被读断);短标题脊
补竖排年份填白;移动端脊高 120→172 保持 1:4 书脊比例,不再退化成矮胖色块
B 封面网格:改 3 列(5 本排 3+2,消掉 4+1 孤儿位);标题定两行高、卡片等高
(实测同行 394px 一致),同排基线不再锯齿;占位卡从单字换成竖排书名,视觉
重量向真封面靠拢
C 抽书阅读:首行默认展开 + 每行加「封面与简介 ▾」暗示(此前收起态没有任何可点
信号,看着像静态列表);去掉恒为同值的年份列;标题改定宽列 + 省略号(实测
作者列 x 全部 518,消除锯齿);作者前加「·」并与标题拉开色阶;色条加宽成贯通行
高的窄脊
验证: 构建 exit 0;content-lint 54 页 0/0/0;Playwright 实测 A 标题/作者零裁切、
B 三列且同行等高 394、C 作者列全对齐 518 且首行展开、390 下脊高 172 不溢出
Spec: 2026-11-02_blog-5i-fonts-polish (T7 预览修订)
Co-authored-by: GLM-5.3
This commit is contained in:
1 parent
6061c16396
commit
31fb52cb06
1 file changed
+83
-19
@@ -31,6 +31,8 @@ const firstPara = (body: string | undefined): string => {
|
||||
return line.replace(/[*_>`[\]]/g, '').slice(0, 60);
|
||||
};
|
||||
|
||||
/** 竖排书脊的高度有限:中文书名超过 ~11 字就必然溢出,用确定性截断代替裁切 */
|
||||
const clip = (t: string, n: number) => (t.length > n ? `${t.slice(0, n)}…` : t);
|
||||
const items = books.map((b) => ({
|
||||
id: b.id,
|
||||
title: b.data.title,
|
||||
@@ -40,6 +42,9 @@ const items = books.map((b) => ({
|
||||
mood: b.data.mood ?? 'var(--ink-4)',
|
||||
year: b.data.pubDate.getFullYear(),
|
||||
href: `/archives/${b.id}/`,
|
||||
spineTitle: clip(b.data.title, 11),
|
||||
spineAuthor: clip(b.data.bookAuthor ?? '', 4),
|
||||
short: clip(b.data.title, 3),
|
||||
}));
|
||||
const years = [...new Set(items.map((i) => i.year))].sort((a, b) => b - a);
|
||||
const withCover = items.filter((i) => i.cover).length;
|
||||
@@ -74,8 +79,10 @@ const withCover = items.filter((i) => i.cover).length;
|
||||
{items.filter((i) => i.year === year).map((b) => (
|
||||
<li>
|
||||
<a class="va-spine" href={b.href} style={`--mood:${b.mood}`} title={`${b.title}${b.author ? ' · ' + b.author : ''}`}>
|
||||
<span class="va-spine-title">{b.title}</span>
|
||||
{b.author && <span class="va-spine-author">{b.author}</span>}
|
||||
<span class="va-spine-title">{b.spineTitle}</span>
|
||||
{b.spineAuthor && <span class="va-spine-author">{b.spineAuthor}</span>}
|
||||
{/* 短标题脊上半段会空出一大块,用年份竖排填补(视觉复核:空脊读起来空洞) */}
|
||||
<span class="va-spine-year mono">{String(b.year).slice(2)}</span>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
@@ -105,7 +112,8 @@ const withCover = items.filter((i) => i.cover).length;
|
||||
) : (
|
||||
<span class="vb-placeholder">
|
||||
<i class="vb-stripe" aria-hidden="true" />
|
||||
<span class="vb-initial">{b.title.slice(0, 1)}</span>
|
||||
{/* 单字无识别价值 → 换竖排书名(2–3 字) */}
|
||||
<span class="vb-vertical">{b.short}</span>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
@@ -133,12 +141,13 @@ const withCover = items.filter((i) => i.cover).length;
|
||||
<ul class="vc-list">
|
||||
{items.map((b) => (
|
||||
<li>
|
||||
<details class="vc-book" style={`--mood:${b.mood}`}>
|
||||
<details class="vc-book" style={`--mood:${b.mood}`} open={b === items[0]}>
|
||||
<summary class="vc-spine">
|
||||
<span class="vc-bar" aria-hidden="true" />
|
||||
<span class="vc-title">{b.title}</span>
|
||||
{b.author && <span class="vc-author">{b.author}</span>}
|
||||
<span class="vc-date mono">{b.year}</span>
|
||||
{/* 展开暗示:没有它就只是 5 行灰字列表(视觉复核 P1) */}
|
||||
<span class="vc-cue mono">封面与简介 ▾</span>
|
||||
</summary>
|
||||
<div class="vc-body">
|
||||
{b.cover && (
|
||||
@@ -207,9 +216,12 @@ const withCover = items.filter((i) => i.cover).length;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
/* 上沿留出 hover 抬升余量(视觉复核:脊顶贴着容器上沿,位移会被裁) */
|
||||
padding: 10px 0 0;
|
||||
margin: 0;
|
||||
align-items: flex-end;
|
||||
/* 底部基线:让一排脊有"落在地上"的参照 */
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
.va-spine {
|
||||
position: relative;
|
||||
@@ -218,11 +230,14 @@ const withCover = items.filter((i) => i.cover).length;
|
||||
justify-content: space-between;
|
||||
width: 44px;
|
||||
height: 190px;
|
||||
padding: 10px 6px;
|
||||
padding: 10px 6px 8px;
|
||||
border-radius: 3px 6px 6px 3px;
|
||||
background: color-mix(in oklab, var(--mood) 16%, var(--bg-soft));
|
||||
border: 1px solid color-mix(in oklab, var(--mood) 34%, var(--line));
|
||||
/* 最浅下限:情绪色偏淡时(如淡紫 #e6e0f5)脊会几乎消隐、墙被读断,
|
||||
26% 混色 + 内描边把它拉回可见范围 */
|
||||
background: color-mix(in oklab, var(--mood) 26%, var(--bg-soft));
|
||||
border: 1px solid color-mix(in oklab, var(--mood) 44%, var(--line));
|
||||
border-left: 5px solid var(--mood);
|
||||
box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--mood) 18%, transparent);
|
||||
transition: translate var(--dur) var(--ease), box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease);
|
||||
}
|
||||
.va-spine:hover {
|
||||
@@ -243,15 +258,25 @@ const withCover = items.filter((i) => i.cover).length;
|
||||
writing-mode: vertical-rl;
|
||||
font-size: 10px;
|
||||
color: var(--ink-4);
|
||||
max-height: 48px;
|
||||
max-height: 60px;
|
||||
overflow: hidden;
|
||||
/* 超长作者不再硬挤第二列(视觉复核:挤成第二窄列被 40px 脊宽裁掉);
|
||||
Astro 侧已按 5 字截断,此处高度只兜极端字宽 */
|
||||
white-space: nowrap;
|
||||
}
|
||||
.va-spine-year {
|
||||
writing-mode: vertical-rl;
|
||||
font-size: 9.5px;
|
||||
color: color-mix(in oklab, var(--ink-4) 70%, transparent);
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
|
||||
/* ══════════ B:封面网格 ══════════ */
|
||||
.vb-year + .vb-year { margin-top: var(--space-5); }
|
||||
.vb-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
/* 3 列:5 本书排成 3+2,避免 4+1 的孤儿位(视觉复核);卡片也更大、封面更可读 */
|
||||
grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
|
||||
gap: var(--space-3);
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
@@ -260,6 +285,8 @@ const withCover = items.filter((i) => i.cover).length;
|
||||
.vb-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
height: 100%;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2);
|
||||
border: 1px solid var(--line);
|
||||
@@ -295,14 +322,26 @@ const withCover = items.filter((i) => i.cover).length;
|
||||
width: 12px;
|
||||
background: var(--mood);
|
||||
}
|
||||
.vb-initial {
|
||||
.vb-vertical {
|
||||
writing-mode: vertical-rl;
|
||||
font-family: var(--font-serif);
|
||||
font-size: 40px;
|
||||
font-size: 19px;
|
||||
letter-spacing: 0.16em;
|
||||
color: color-mix(in oklab, var(--mood) 62%, var(--ink-2));
|
||||
opacity: 0.85;
|
||||
}
|
||||
.vb-body { display: flex; flex-direction: column; gap: 1px; }
|
||||
.vb-title { font-family: var(--font-serif); font-size: 14.5px; color: var(--ink-1); line-height: 1.35; }
|
||||
.vb-body { display: flex; flex-direction: column; gap: 1px; flex: 1; }
|
||||
/* 标题定两行高:只 1 行的卡不会把作者/描述顶上移,同排基线才齐(视觉复核抓到的锯齿) */
|
||||
.vb-title {
|
||||
font-family: var(--font-serif);
|
||||
font-size: 14.5px;
|
||||
color: var(--ink-1);
|
||||
line-height: 1.35;
|
||||
min-height: calc(1.35em * 2);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
.vb-author { font-size: 11.5px; color: var(--ink-4); }
|
||||
.vb-desc {
|
||||
font-size: 12.5px;
|
||||
@@ -330,9 +369,32 @@ const withCover = items.filter((i) => i.cover).length;
|
||||
.vc-spine::-webkit-details-marker { display: none; }
|
||||
.vc-spine:hover { background: var(--bg-soft); }
|
||||
.vc-bar { width: 6px; align-self: stretch; border-radius: 3px; background: var(--mood); flex: none; }
|
||||
.vc-title { font-family: var(--font-serif); font-size: 15px; color: var(--ink-1); }
|
||||
/* 标题定宽列:作者起点不再随书名长短逐行右移(视觉复核的第二列锯齿) */
|
||||
.vc-title {
|
||||
font-family: var(--font-serif);
|
||||
font-size: 15px;
|
||||
color: var(--ink-1);
|
||||
/* 定宽 + 省略:作者列起点不再随书名长短漂移(视觉复核的第二列锯齿) */
|
||||
width: 11em;
|
||||
flex: none;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.vc-author { font-size: 12px; color: var(--ink-4); }
|
||||
.vc-date { margin-left: auto; font-size: 11.5px; color: var(--ink-4); }
|
||||
.vc-author::before { content: '·'; margin-right: 6px; opacity: 0.5; }
|
||||
.vc-cue {
|
||||
margin-left: auto;
|
||||
font-size: 11.5px;
|
||||
color: var(--ink-4);
|
||||
white-space: nowrap;
|
||||
transition: color var(--dur) var(--ease);
|
||||
}
|
||||
.vc-spine:hover .vc-cue { color: var(--accent); }
|
||||
details[open] > .vc-spine .vc-cue { color: var(--ink-3); }
|
||||
details[open] > .vc-spine .vc-cue::after { content: ''; }
|
||||
/* 色条改成贯通行高 80% 的窄脊,不再像表单色标 */
|
||||
.vc-bar { width: 8px !important; margin: 4px 0; }
|
||||
.vc-body {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
@@ -347,7 +409,9 @@ const withCover = items.filter((i) => i.cover).length;
|
||||
.vc-link { font-size: 13px; color: var(--accent); }
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.va-spine { width: 38px; height: 160px; }
|
||||
/* 移动端保持书脊比例(1:4 左右):压到 120px 会退化成矮胖色块 */
|
||||
.va-spine { width: 40px; height: 172px; }
|
||||
.va-wall { padding-top: 8px; }
|
||||
.vb-grid { grid-template-columns: repeat(auto-fill, minmax(126px, 1fr)); gap: var(--space-2); }
|
||||
.vc-body { flex-direction: column; }
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user