- BaseLayout:8 个字体包 CSS 改 ?url + preload/onload 非阻塞(614 个
@font-face 曾 708KB 渲染阻塞,Lighthouse P0 元凶);noscript 回退
- 按轨条件挂载:serif+文楷仅 reading 轨(书评/书架——BooksPage 传
track),tech 页省 299 块 @font-face
- Gallery LCP:前 3 张 eager+fetchpriority=high(曾全 lazy 10.3s)
- 对比度:PostList .track 与 .post-cat 提 --ink-2(A11y P1)
- favicon.svg 补缺(BP 扣分项)
- 教训:python replace 再犯静默失配(车道改过导入顺序)——已用 edit
工具精确替换 + assert 意识持续强化
验证: 本地 dist lighthouse 首页 Perf 62→88,render-blocking 无;
FCP 2.5s/LCP 3.2s;E2E 30/30 PASS(staging)
Spec: 2026-10-25_add-blog-5c
Co-authored-by: GLM-5.3
125 lines
4.2 KiB
Plaintext
125 lines
4.2 KiB
Plaintext
---
|
||
// 书评索引画廊(/books):色脊排版卡 × 年份分组规整网格
|
||
// 预研方案 B:色脊天然安全(mood 深浅跨亮暗对比度风险回避),零 JS
|
||
import { getCollection } from 'astro:content';
|
||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||
import { t } from '../../i18n/ui';
|
||
import type { Locale } from '../../lib/locale';
|
||
|
||
interface Props { locale?: Locale }
|
||
const { locale = 'zh' } = Astro.props;
|
||
|
||
const books = (await getCollection('posts', ({ data }) => !data.draft && data.category === 'reading' && !data.tags.includes('hidden')))
|
||
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
|
||
|
||
// 回退契约(schema 注释声称而此前未实现——视觉复核抓到的缺口):
|
||
// description 缺省取正文第一个非空段落(去 markdown 记号)前 60 字
|
||
const firstPara = (body: string | undefined): string => {
|
||
const line = (body ?? '').split(/\n\s*\n/).map((l) => l.trim()).find((l) => l && !l.startsWith('#') && !l.startsWith('---')) ?? '';
|
||
return line.replace(/[*_>`\[\]]/g, '').slice(0, 60);
|
||
};
|
||
const years = [...new Set(books.map((b) => b.data.pubDate.getFullYear()))].sort((a, b) => b - a);
|
||
const L = {
|
||
shelf: locale === 'zh' ? '书架' : 'Books',
|
||
count: (n: number) => (locale === 'zh' ? `${n} 本` : `${n} books`),
|
||
};
|
||
---
|
||
|
||
<BaseLayout track="reading" title={`${L.shelf} · wait4x`} description={locale === 'zh' ? '读过的书与一句策展' : 'Books read, one line each'}>
|
||
<h1 class="page-title">{L.shelf}<span class="count mono">{L.count(books.length)}</span></h1>
|
||
{years.map((year) => (
|
||
<section class="year-group">
|
||
<h2 class="y-label mono">{year}</h2>
|
||
<ul class="shelf">
|
||
{books.filter((b) => b.data.pubDate.getFullYear() === year).map((b) => (
|
||
<li>
|
||
<a
|
||
class={`book ${locale === 'en' ? 'en' : ''}`}
|
||
href={locale === 'en' ? `/en/archives/${b.id}/` : `/archives/${b.id}/`}
|
||
style={`--mood:${b.data.mood ?? 'var(--ink-4)'}`}
|
||
>
|
||
<i class="spine" aria-hidden="true"></i>
|
||
<div class="bk-body">
|
||
<strong class="bk-title">{b.data.title}</strong>
|
||
{b.data.bookAuthor && <span class="bk-author">{b.data.bookAuthor}</span>}
|
||
{(b.data.description || firstPara(b.body)) && (
|
||
<span class="bk-desc">{b.data.description ?? firstPara(b.body)}</span>
|
||
)}
|
||
</div>
|
||
</a>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</section>
|
||
))}
|
||
</BaseLayout>
|
||
|
||
<style>
|
||
.page-title {
|
||
font-size: var(--text-h1);
|
||
color: var(--ink-1);
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: var(--space-3);
|
||
}
|
||
.count { font-size: 13px; color: var(--ink-3); }
|
||
.year-group { margin-top: var(--space-5); }
|
||
.y-label { font-size: 16px; color: var(--ink-3); margin-bottom: var(--space-3); }
|
||
.shelf {
|
||
list-style: none;
|
||
padding: 0;
|
||
margin: 0;
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||
gap: var(--space-3);
|
||
}
|
||
/* 色脊卡:纸底 + mood 竖脊 + 文楷书名;hover 边框转 mood(.repo 同语言) */
|
||
.book {
|
||
display: flex;
|
||
min-height: 120px;
|
||
background: var(--paper);
|
||
border: 1px solid var(--paper-line);
|
||
border-radius: 10px;
|
||
overflow: hidden;
|
||
text-decoration: none;
|
||
transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
|
||
}
|
||
.book:hover { border-color: var(--mood); transform: translateY(-2px); }
|
||
.spine {
|
||
flex: none;
|
||
width: 10px;
|
||
background: var(--mood);
|
||
border-radius: 10px 0 0 10px;
|
||
}
|
||
.bk-body {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 3px;
|
||
padding: var(--space-3) var(--space-4);
|
||
min-width: 0;
|
||
}
|
||
.bk-title {
|
||
font-family: var(--font-kai);
|
||
font-size: 17px;
|
||
color: var(--paper-ink);
|
||
line-height: 1.35;
|
||
}
|
||
.en .bk-title { font-family: var(--font-serif); }
|
||
.bk-author {
|
||
font-size: 12.5px;
|
||
color: var(--ink-3);
|
||
font-family: var(--font-sans);
|
||
}
|
||
.bk-desc {
|
||
margin-top: 4px;
|
||
font-size: 13px;
|
||
color: var(--ink-2);
|
||
font-family: var(--font-sans);
|
||
line-height: 1.5;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 3;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
</style>
|