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>
This commit is contained in:
wangjia
2026-06-13 14:26:40 +08:00
parent 787151245e
commit 4df8dc6f87
37 changed files with 9390 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
---
import Brand from './Brand.astro';
import type { T } from '../i18n/strings';
interface Props { t: T }
const { t } = Astro.props;
---
<footer class="ftr">
<div class="wrap">
<div class="top">
<div>
<div class="nm">
<Brand variant="footer" size={28} />
穿山甲
</div>
<p class="tag">{t('ft.tag')}</p>
</div>
<div>
<h4>{t('ft.product')}</h4>
<ul>
<li><a href="#product">{t('ft.features')}</a></li>
<li><a href="#pricing">{t('ft.pricing')}</a></li>
<li><a href="#download">{t('ft.download')}</a></li>
</ul>
</div>
<div>
<h4>{t('ft.resources')}</h4>
<ul>
<li><a href="#docs">{t('ft.docs')}</a></li>
<li><a href="#blog">{t('ft.blog')}</a></li>
<li><a href="#docs">{t('ft.faq')}</a></li>
<li><a href="#docs">{t('ft.privacy')}</a></li>
</ul>
</div>
<div>
<h4>{t('ft.contact')}</h4>
<ul>
<li><a class="mono">shop.pangolin.vpn</a></li>
<li><a class="mono">Telegram @PangolinVPN_bot</a></li>
<li><a class="mono">LINE @pangolinvpn</a></li>
<li><a class="mono">support@pangolin.vpn</a></li>
</ul>
</div>
</div>
<div class="bot">
<span>{t('ft.copy')}</span>
<span>{t('ft.madenote')}</span>
<span>{t('ft.hours')}</span>
</div>
</div>
</footer>