Files
pangolin/web/website/src/layouts/Site.astro
T
wangjia 4df8dc6f87 feat(web): 官网 ui_kits/website → Astro 纯静态 SSG 迁移 (tsk_acMYQ-Z-EIF_)
新建 web/website/(Astro 零 SSR):

- 组件迁移:交互块保留 .jsx 经 @astrojs/react(Header/AnnouncementBar/SignupForm/PricingPlans),纯展示块转 .astro 去运行时 JS;像素以原型为基准。
- 令牌同源:build-tokens.mjs 从 design/colors_and_type.css 生成 tokens.gen.css,仅剔除第三方 Google Fonts @import,数值不改。
- 字体自托管:@fontsource(Sora/Manrope/Noto Sans SC/JetBrains Mono),无第三方 CDN。
- 图标:Lucide 构建期内联 SVG(替代 unpkg CDN),零运行时、零 CDN。
- i18n:/(zh)与 /en/(en)双路由单显,语言切换组件;文案沿用 ui_kits 脱敏文案。
- 安全:public/_headers 严格 CSP(全 self + 自托管资源 + 内联片段 sha256,无 unsafe-inline)+ HSTS;无支付表单。
- CI:.gitea/workflows/website.yml 构建(红线扫描+lint+CSP 哈希)→ 同时发布 Cloudflare Pages 主站与镜像。
- 灾备:README 写明干净环境 npm ci && npm run build 可直接部署到任意静态托管;dist 指纹确定性,主站镜像一致。

测试:npm run lint(0 error)/ npm test(build+CSP 哈希注入+红线扫描 0 命中)/ 两次干净构建 dist 指纹一致。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 14:26:40 +08:00

92 lines
3.3 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.
---
/**
* Site.astro — 官网整页布局(构建期单显一种语言)。
* 两条路由复用:/ → lang="zh"/en/ → lang="en"(铁律 6 单显不并排)。
*
* 样式来源顺序(全部自托管,无第三方 CDN):
* @fontsource/* → 自托管字体 @font-face(替代原型的 Google Fonts CDN
* tokens.gen.css → 由 design/colors_and_type.css 生成(唯一令牌真相源)
* website.css → 逐字迁自 ui_kits/website
* site-extra.css → 仅承载「原型内联 style= → class」的等价工具类
*/
import '@fontsource/sora/500.css';
import '@fontsource/sora/600.css';
import '@fontsource/sora/700.css';
import '@fontsource/manrope/400.css';
import '@fontsource/manrope/500.css';
import '@fontsource/manrope/600.css';
import '@fontsource/manrope/700.css';
import '@fontsource/noto-sans-sc/400.css';
import '@fontsource/noto-sans-sc/500.css';
import '@fontsource/noto-sans-sc/700.css';
import '@fontsource/jetbrains-mono/400.css';
import '@fontsource/jetbrains-mono/500.css';
import '../styles/tokens.gen.css';
import '../styles/website.css';
import '../styles/site-extra.css';
import { createT, type Lang } from '../i18n/strings';
import Header from '../components/Header.jsx';
import AnnouncementBar from '../components/AnnouncementBar.jsx';
import Hero from '../components/Hero.astro';
import Features from '../components/Features.astro';
import WhySignup from '../components/WhySignup.astro';
import Pricing from '../components/Pricing.astro';
import Download from '../components/Download.astro';
import Docs from '../components/Docs.astro';
import Blog from '../components/Blog.astro';
import CtaBand from '../components/CtaBand.astro';
import Footer from '../components/Footer.astro';
interface Props { lang: Lang }
const { lang } = Astro.props;
const t = createT(lang);
const headerT = {
product: t('nav.product'),
pricing: t('nav.pricing'),
download: t('nav.download'),
docs: t('nav.docs'),
blog: t('nav.blog'),
login: t('nav.login'),
get: t('nav.get'),
suBtn: t('su.btn'),
};
---
<!doctype html>
<html lang={lang === 'zh' ? 'zh-CN' : 'en'}>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{t('meta.title')}</title>
<meta name="description" content={t('meta.desc')} />
<meta name="robots" content="index,follow" />
<link rel="canonical" href={new URL(lang === 'zh' ? '/' : '/en/', Astro.site).href} />
<link rel="alternate" hreflang="zh-CN" href={new URL('/', Astro.site).href} />
<link rel="alternate" hreflang="en" href={new URL('/en/', Astro.site).href} />
<link rel="alternate" hreflang="x-default" href={new URL('/', Astro.site).href} />
<meta name="theme-color" content="#B96A3D" />
<meta property="og:type" content="website" />
<meta property="og:title" content={t('meta.title')} />
<meta property="og:description" content={t('meta.desc')} />
<meta property="og:locale" content={lang === 'zh' ? 'zh_CN' : 'en_US'} />
</head>
<body>
<AnnouncementBar client:idle text={t('ann.txt')} cta={t('ann.cta')} />
<Header client:load lang={lang} t={headerT} />
<a id="top"></a>
<Hero t={t} />
<Features t={t} />
<WhySignup t={t} />
<Pricing t={t} />
<Download t={t} />
<Docs t={t} />
<Blog t={t} />
<CtaBand t={t} />
<Footer t={t} />
</body>
</html>