- 层 1 所有页面文案改富文本:site-content 新增 renderRich(md,{inline})
复用既有全管线(raw→内容净化→class 白名单→链接卡片);接入
home.tagline/subtitle、opensource.blurb、about.intro、blocks prose;
新增 .rich 容器作用域并与 .prose-body 合并(77 处选择器覆盖四类容器)
坑:renderRich 输出 <p> 塞进 <p class="tagline"> 会被浏览器拆散 →
行内场景用 {inline:true} 剥外层 <p>
- 层 2 页面级排版覆盖:三单例加 typography{font?,accent?,scale?},读取器
pageTypography() 走白名单(10 字族 / 18 色类,scale 夹 0.8–1.25,清单外
忽略);页面容器挂 .page-typo + CSS 变量 --page-font/--page-accent/
--page-scale,不动全站 tokens;后台三页加「页面排版覆盖」下拉
- 层 3 艺术字体:新增 font-art-kuaile(快乐体专用类,字距微调),白名单与
可视化面板同步;站酷高端黑/阿里妈妈东方大楷/演示悠然小楷因无 npm 包或
嵌入条款风险进候补清单
- 默认值清理:about.yaml 的 typography 测试值(整页汇文)清为注释示例——
整页换字族会带来 694KB 字体成本,默认留空=全站默认排版
验证: 构建 48 页 exit 0;E2E 37/37;三页 page-typo 挂载与变量实测、四页
(图库/书架/文章/装备)隔离为 0、font-art-kuaile 生效、about 清空后
无覆盖
Spec: none (排版三层,先于 5g 的任务;T3/T4 另行交付)
Co-authored-by: GLM-5.3
144 lines
4.5 KiB
Plaintext
144 lines
4.5 KiB
Plaintext
---
|
|
// /en/ 首页:与 zh 版结构逐区对齐(名片区/文章列表/最新影像/热力图/项目墙)
|
|
import { Image } from 'astro:assets';
|
|
import { getCollection } from 'astro:content';
|
|
import BaseLayout from '../../layouts/BaseLayout.astro';
|
|
import IdCard from '../../components/IdCard.astro';
|
|
import PostList from '../../components/PostList.astro';
|
|
import ProjectWall from '../../components/geek/ProjectWall.astro';
|
|
import Heatmap from '../../components/geek/Heatmap.astro';
|
|
import { t } from '../../i18n/ui';
|
|
import { homeConfig, homeTagline, pageTypography, renderMarkdown, renderRich } from '../../lib/site-content';
|
|
import SocialLinks from '../../components/SocialLinks.astro';
|
|
|
|
// 首页可配项(后台主页设置):副标题/影像张数/区块开关/每轨条数,字段级回落
|
|
const cfg = await homeConfig('en');
|
|
const subtitleHtml = cfg.subtitle ? await renderMarkdown(cfg.subtitle) : '';
|
|
const posts = (await getCollection('posts', ({ data }) => !data.draft && !data.tags.includes('hidden'))).sort(
|
|
(a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime(),
|
|
);
|
|
// 列表标题吃 postsEn 译题(缺译回退 zh,与 /en/archives 同策略)
|
|
const enTitle = Object.fromEntries(
|
|
(await getCollection('postsEn')).map((e) => [e.id, e.data.title]),
|
|
);
|
|
const photos = (await getCollection('photos')).slice(0, Math.max(1, cfg.latestPhotos));
|
|
// 身份语与 zh 版同源(content/site/home.yaml 的 en 字段),缺值回落默认
|
|
const tagline = await homeTagline('en');
|
|
const taglineHtml = await renderRich(tagline, { inline: true });
|
|
const typo = await pageTypography('siteHome', 'home');
|
|
---
|
|
|
|
<BaseLayout title="wait4x" description="wait4x's blog: reading · code · photography">
|
|
<div class={typo.className || undefined} style={typo.style || undefined}>
|
|
<IdCard>
|
|
<p class="tagline rich" set:html={taglineHtml} />
|
|
{subtitleHtml && <div class="subtitle" set:html={subtitleHtml} />}
|
|
<SocialLinks />
|
|
</IdCard>
|
|
|
|
{cfg.sections.tracks && <PostList {posts} locale="en" titleOverrides={enTitle} perTrack={cfg.perTrackLimit} />}
|
|
|
|
{cfg.sections.photos && cfg.latestPhotos > 0 && (
|
|
<section class="latest" aria-label={t('latest_photos', 'en')}>
|
|
<div class="latest-head">
|
|
<span class="lab">{t('latest_photos', 'en')}</span>
|
|
<a class="tap more" href="/en/gallery/">{t('enter_gallery', 'en')}</a>
|
|
</div>
|
|
<div class="thumbs">
|
|
{photos.map((photo) => (
|
|
<a class="thumb" href="/en/gallery/" aria-label={`Gallery: ${photo.data.title}`}>
|
|
<Image
|
|
src={photo.data.image}
|
|
alt={photo.data.alt ?? photo.data.title}
|
|
widths={[180, 360]}
|
|
sizes="(min-width: 820px) 20vw, 42vw"
|
|
/>
|
|
</a>
|
|
))}
|
|
</div>
|
|
</section>
|
|
)}
|
|
|
|
{cfg.sections.heatmap && <Heatmap
|
|
labels={{
|
|
title: t('heatmap_title', 'en'),
|
|
summary: t('heatmap_summary', 'en'),
|
|
less: t('heatmap_less', 'en'),
|
|
more: t('heatmap_more', 'en'),
|
|
note: t('heatmap_note', 'en'),
|
|
}}
|
|
/>}
|
|
{cfg.sections.projects && <ProjectWall locale="en" />}
|
|
</div>
|
|
</BaseLayout>
|
|
|
|
<style>
|
|
.subtitle {
|
|
font-size: 13.5px;
|
|
color: var(--ink-4);
|
|
margin-top: 2px;
|
|
letter-spacing: 0.01em;
|
|
}
|
|
.subtitle :global(p) { margin: 0; }
|
|
.subtitle :global(a) { color: var(--accent); }
|
|
.tagline {
|
|
font-size: 14px;
|
|
color: var(--ink-3);
|
|
margin-top: 4px;
|
|
letter-spacing: 0.01em;
|
|
}
|
|
.socials {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
margin-top: var(--space-2);
|
|
}
|
|
.social {
|
|
color: var(--ink-3);
|
|
border-radius: 8px;
|
|
transition: color var(--dur) var(--ease);
|
|
}
|
|
.social:hover { color: var(--accent); }
|
|
|
|
.latest { margin-top: var(--space-6); }
|
|
.latest-head {
|
|
display: flex;
|
|
align-items: baseline;
|
|
justify-content: space-between;
|
|
gap: var(--space-3);
|
|
}
|
|
.lab {
|
|
font-size: 12.5px;
|
|
color: var(--ink-4);
|
|
letter-spacing: 0.08em;
|
|
}
|
|
.more {
|
|
font-size: 13px;
|
|
color: var(--accent);
|
|
padding-inline: var(--space-2);
|
|
transition: color var(--dur) var(--ease);
|
|
}
|
|
.more:hover { color: var(--ink-1); }
|
|
.thumbs {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: var(--space-2);
|
|
margin-top: var(--space-3);
|
|
}
|
|
.thumb {
|
|
aspect-ratio: 1;
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
}
|
|
.thumb :global(img) {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
transition: transform var(--dur) var(--ease);
|
|
}
|
|
.thumb:hover :global(img) { transform: scale(1.03); }
|
|
@media (min-width: 820px) {
|
|
.thumbs { grid-template-columns: repeat(4, 1fr); }
|
|
}
|
|
</style>
|