nomadweb/frontend/src/components/DataBudgetPlanner.tsx
eric 90abeac6f6 Add first-month landing cost and mobile data budget planners.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-29 02:16:45 -05:00

104 lines
3.8 KiB
TypeScript
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.

"use client";
import { useMemo, useState } from "react";
const WORK_GB = [
{ id: "docs", emoji: "📄", label: "文档/邮件", gb: 3 },
{ id: "meet", emoji: "🎥", label: "视频会议", gb: 12 },
{ id: "code", emoji: "💻", label: "云开发/同步", gb: 8 },
{ id: "stream", emoji: "🎬", label: "娱乐流媒体", gb: 15 },
{ id: "maps", emoji: "🗺️", label: "地图导航", gb: 2 },
{ id: "backup", emoji: "☁️", label: "相册备份", gb: 10 },
];
const PLANS = [
{ gb: 10, label: "轻量", tip: "以咖啡馆/办公网为主" },
{ gb: 30, label: "日常", tip: "远程办公够用" },
{ gb: 50, label: "充足", tip: "会议多也不慌" },
{ gb: 100, label: "豪横", tip: "流媒体+备份无压力" },
];
export default function DataBudgetPlanner() {
const [selected, setSelected] = useState(["docs", "meet", "code", "maps"]);
const [meetHours, setMeetHours] = useState(8);
const [hotspotDays, setHotspotDays] = useState(6);
const total = useMemo(() => {
let gb = WORK_GB.filter((w) => selected.includes(w.id)).reduce((s, w) => s + w.gb, 0);
// scale video by meeting hours (base assumes ~8h)
if (selected.includes("meet")) {
gb += Math.max(0, (meetHours - 8) * 1.2);
}
// hotspot premium days add overhead
gb += hotspotDays * 0.8;
return Math.round(gb * 10) / 10;
}, [selected, meetHours, hotspotDays]);
const plan = PLANS.find((p) => total <= p.gb) || PLANS[PLANS.length - 1];
const pct = Math.min((total / 100) * 100, 100);
const toggle = (id: string) => {
setSelected((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]));
};
return (
<section className="section data-budget-section" id="data-budget">
<div className="container">
<div className="section-header reveal">
<span className="section-tag">📶 DATA BUDGET</span>
<h2>流量预算规划</h2>
<p>按办公习惯估算月流量,选 SIM / eSIM 套餐更有数</p>
</div>
<div className="data-budget-card reveal">
<div className="data-budget-meter">
<div className="data-budget-gauge">
<div className="data-budget-fill" style={{ width: `${pct}%` }} />
</div>
<div className="data-budget-hero">
<strong>{total} GB</strong>
<span>/ 月估算</span>
</div>
<p className="data-budget-plan">
建议套餐档:<b>{plan.gb}GB · {plan.label}</b> — {plan.tip}
</p>
</div>
<div className="data-budget-grid">
{WORK_GB.map((w) => {
const on = selected.includes(w.id);
return (
<button
key={w.id}
type="button"
className={`data-budget-item${on ? " on" : ""}`}
onClick={() => toggle(w.id)}
aria-pressed={on}
>
<span>{w.emoji}</span>
<div>
<strong>{w.label}</strong>
<small>~{w.gb} GB</small>
</div>
<i>{on ? "✓" : "+"}</i>
</button>
);
})}
</div>
<div className="data-budget-sliders">
<label>
<span>视频会议时长:{meetHours} 小时/月</span>
<input type="range" min={0} max={40} value={meetHours} onChange={(e) => setMeetHours(+e.target.value)} />
</label>
<label>
<span>纯热点办公天数:{hotspotDays} 天/月</span>
<input type="range" min={0} max={20} value={hotspotDays} onChange={(e) => setHotspotDays(+e.target.value)} />
</label>
</div>
</div>
</div>
</section>
);
}