Files
pangolin/web/website/src/components/PricingPlans.jsx
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

61 lines
1.9 KiB
React

/**
* PricingPlans.jsx — 月/年切换 + 三档套餐卡(React island)。
* 迁自原型 .price-toggle + .price-grid + site.js __setCycle。
* 铁律 10:CTA 为「获取激活码 / 免费下载」,不含任何支付表单;价格对齐 design/CLAUDE.md §7。
*/
import { useState } from 'react';
import { Check } from 'lucide-react';
export default function PricingPlans({ data }) {
const [cycle, setCycle] = useState('monthly');
const i = cycle === 'monthly' ? 0 : 1;
return (
<>
<div class="wrap center">
<div class="price-toggle">
<button
class={cycle === 'monthly' ? 'on' : undefined}
data-cycle="monthly"
onClick={() => setCycle('monthly')}
>
{data.monthly}
</button>
<button
class={cycle === 'yearly' ? 'on' : undefined}
data-cycle="yearly"
onClick={() => setCycle('yearly')}
>
<span>{data.yearly}</span>
<span class="save">{data.save}</span>
</button>
</div>
</div>
<div class="wrap">
<div class="price-grid">
{data.plans.map((p) => (
<div key={p.key} class={p.highlight ? 'plan feat-plan' : 'plan'}>
{p.highlight && <div class="pop">{data.popular}</div>}
<div class="pname">{p.name}</div>
<div class="price">
{p.price[i]}
<small>{data.permo}</small>
</div>
<div class="pdesc">{p.desc}</div>
<ul class="feats">
{p.feats.map((f, fi) => (
<li key={fi}>
<Check />
<span>{f}</span>
</li>
))}
</ul>
<button class={`pcta ${p.ctaClass}`}>{p.cta}</button>
</div>
))}
</div>
</div>
</>
);
}