/* parts.jsx — 穿山甲 VPN mobile · shared atoms
Icon uses inline Lucide path data (lucide.dev) for reliability in React. */
const LUCIDE = {
power: '',
'shield-check': '',
globe: '',
settings: '',
'chevron-right': '',
'chevron-down': '',
check: '',
zap: '',
'loader-circle': '',
user: '',
x: '',
crown: '',
'map-pin': '',
'arrow-down': '',
'arrow-up': '',
search: '',
moon: '',
sun: '',
shield: '',
'refresh-cw': '',
chart: '',
wifi: '',
mail: '',
lock: '',
'arrow-left': '',
'shopping-bag': '',
send: '',
'message-circle': '',
'external-link': '',
'check-circle': '',
'log-out': '',
compass: '',
laptop: '',
smartphone: '',
'monitor-smartphone': '',
ticket: '',
clock: '',
'credit-card': '',
'play-circle': '',
};
function Icon({ name, size = 24, stroke = 2, color = 'currentColor', style = {} }) {
return (
);
}
/* Brand mark — walking pangolin (matches app icon), facing right */
function Mark({ size = 40, color = 'var(--accent)' }) {
return (
);
}
/* Country code tile */
function CC({ code, active }) {
return (
{code}
);
}
/* Signal bars from ping */
function Signal({ ping }) {
const lvl = ping < 40 ? 3 : ping < 90 ? 2 : 1;
return (
{[5, 9, 14].map((h, i) => (
))}
);
}
/* Pill button */
function Pill({ children, variant = 'primary', onClick, style = {}, disabled }) {
const v = {
primary: { background: 'var(--accent)', color: 'var(--fg-on-accent)' },
secondary: { background: 'var(--surface)', color: 'var(--fg1)', border: '1.5px solid var(--border-strong)' },
ghost: { background: 'transparent', color: 'var(--accent)' },
danger: { background: 'var(--danger-subtle)', color: 'var(--danger)' },
}[variant];
return (
);
}
/* ───────── i18n ─────────
Real product shows ONE language at a time (toggle in settings).
Bilingual specimens only live in the design-system preview cards. */
const STRINGS = {
brand: { zh: '穿山甲', en: 'Pangolin' },
online: { zh: '● 在线', en: '● Online' },
offline: { zh: '○ 离线', en: '○ Offline' },
capOff: { zh: '未连接 · 轻点连接', en: 'Tap to connect' },
capConnecting:{ zh: '连接中…', en: 'Connecting…' },
capOn: { zh: '已连接 · 网络已加密', en: 'Connected · Encrypted' },
centerOff: { zh: 'TAP', en: 'TAP' },
download: { zh: '下载', en: 'Down' },
upload: { zh: '上传', en: 'Up' },
latency: { zh: '延迟', en: 'Ping' },
chooseNode: { zh: '选择节点', en: 'Choose server' },
search: { zh: '搜索国家 / 城市', en: 'Search country / city' },
tabConnect: { zh: '连接', en: 'Connect' },
tabServers: { zh: '节点', en: 'Servers' },
tabStats: { zh: '统计', en: 'Stats' },
tabMe: { zh: '我的', en: 'Account' },
connectNow: { zh: '点击连接', en: 'CONNECT' },
secure: { zh: '已加密', en: 'SECURE' },
statsTitle: { zh: '使用统计', en: 'Statistics' },
trafficMonth: { zh: '本月流量', en: 'Traffic' },
avgPing: { zh: '平均延迟', en: 'Avg ping' },
durMonth: { zh: '本月时长', en: 'Time' },
weekTraffic: { zh: '本周流量 (GB)', en: 'This week (GB)' },
days7: { zh: ['一','二','三','四','五','六','日'], en: ['M','T','W','T','F','S','S'] },
proMember: { zh: 'PRO 会员', en: 'PRO member' },
expires: { zh: '有效期至', en: 'Expires' },
usage: { zh: '本月用量', en: 'Used this month' },
smartRoute: { zh: '智能分流', en: 'Smart routing' },
smartRouteSub:{ zh: '海外应用加速,本地直连', en: 'Accelerate overseas apps, keep local direct' },
killSwitch: { zh: 'Kill Switch', en: 'Kill Switch' },
killSwitchSub:{ zh: '断线时阻断网络,防止泄露', en: 'Block traffic if the VPN drops' },
darkAppearance:{ zh: '深色外观', en: 'Dark appearance' },
stateOn: { zh: '已开启', en: 'On' },
followLight: { zh: '跟随浅色', en: 'Off' },
protocol: { zh: '协议', en: 'Protocol' },
checkUpdate: { zh: '检查更新', en: 'Check for updates' },
language: { zh: '语言', en: 'Language' },
/* auth */
authTagline: { zh: '极速 · 稳定 · 省心', en: 'Fast · Stable · Effortless' },
tabLogin: { zh: '登录', en: 'Log in' },
tabRegister: { zh: '注册', en: 'Sign up' },
emailLabel: { zh: '邮箱', en: 'Email' },
emailPh: { zh: '你的邮箱地址', en: 'your@email.com' },
pwLabel: { zh: '密码', en: 'Password' },
pwPh: { zh: '输入密码', en: 'Enter password' },
setPwPh: { zh: '设置登录密码(用于多端登录)', en: 'Set a password (for multi-device login)' },
codeLabel: { zh: '邮箱验证码', en: 'Verification code' },
codeSentTo: { zh: '验证码已发送至', en: 'Code sent to' },
sendCode: { zh: '发送验证码', en: 'Send code' },
resend: { zh: '重新发送', en: 'Resend' },
doLogin: { zh: '登录', en: 'Log in' },
doNext: { zh: '下一步', en: 'Next' },
doCreate: { zh: '创建账户', en: 'Create account' },
forgotPw: { zh: '忘记密码?', en: 'Forgot password?' },
tos: { zh: '继续即代表同意《服务条款》与《隐私政策》', en: 'By continuing you agree to our Terms & Privacy Policy' },
stepEmail: { zh: '验证邮箱', en: 'Verify email' },
stepPw: { zh: '设置密码', en: 'Set password' },
/* onboarding */
obSkip: { zh: '跳过', en: 'Skip' },
obNext: { zh: '下一步', en: 'Next' },
obAllow: { zh: '允许并继续', en: 'Allow & continue' },
obStart: { zh: '开始使用', en: 'Get started' },
ob1Title: { zh: '一键连接全球', en: 'Connect worldwide' },
ob1Sub: { zh: '80+ 节点,智能选择最快线路,稳定不掉线。', en: '80+ locations, auto-fastest routing, rock-solid.' },
ob2Title: { zh: '授权网络配置', en: 'Allow network setup' },
ob2Sub: { zh: '系统会请求添加一个网络配置,用于建立加密隧道。', en: 'iOS will ask to add a network profile to build the encrypted tunnel.' },
ob3Title: { zh: '安全 · 无日志', en: 'Private by design' },
ob3Sub: { zh: '端到端加密,我们不记录你的任何浏览数据。', en: 'End-to-end encrypted. We keep zero browsing logs.' },
/* redeem & buy */
redeemEntry: { zh: '兑换 & 购买', en: 'Redeem & buy' },
redeemTitle: { zh: '兑换 & 购买', en: 'Redeem & buy' },
redeemCodeTitle:{ zh: '兑换激活码', en: 'Redeem a code' },
redeemPh: { zh: '输入激活码', en: 'Enter activation code' },
redeemBtn: { zh: '激活', en: 'Activate' },
redeemOk: { zh: '激活成功 · PRO 已开通', en: 'Activated · PRO unlocked' },
buyTitle: { zh: '购买渠道', en: 'Where to buy' },
buySub: { zh: 'App 内不支持直接支付。请通过以下渠道获取激活码:', en: 'In-app payment is unavailable. Get an activation code via:' },
chStore: { zh: '自助发卡商店', en: 'Self-serve store' },
chStoreSub: { zh: '支付宝 / 微信 · 自动发码', en: 'Alipay / WeChat · instant code' },
chUsdtSub: { zh: '链上转账 · 最隐私 · 自动发码', en: 'On-chain · most private · instant code' },
chTgSub: { zh: '@PangolinVPN_bot', en: '@PangolinVPN_bot' },
chLineSub: { zh: '@pangolinvpn', en: '@pangolinvpn' },
chEmail: { zh: '邮箱', en: 'Email' },
chEmailSub: { zh: 'buy@pangolin.vpn', en: 'buy@pangolin.vpn' },
back: { zh: '返回', en: 'Back' },
replayAuth: { zh: '查看登录 / 注册流程', en: 'View login / sign-up' },
twoFA: { zh: '双重认证', en: 'Two-factor auth' },
twoFASub: { zh: 'PRO 专享 · 登录需身份验证器动态码', en: 'PRO only · TOTP code at login' },
twoFATitle: { zh: '双重认证', en: 'Two-factor auth' },
twoFAHint: { zh: '你的账户已开启双重认证。请输入身份验证器 App 中的 6 位动态码。', en: 'Two-factor auth is on for this account. Enter the 6-digit code from your authenticator app.' },
twoFAConfirm: { zh: '验证并登录', en: 'Verify & log in' },
twoFABack: { zh: '返回上一步', en: 'Back' },
ucEntry: { zh: 'Web 用户中心', en: 'Web account center' },
ucEntrySub: { zh: '订阅导入 · 邀请返利', en: 'Subscription import · referral' },
replayAuthSub:{ zh: '演示用 · 退出到登录页', en: 'Demo · back to login' },
replayOnboarding: { zh: '查看首次引导', en: 'View onboarding' },
replayOnboardingSub: { zh: '演示用 · 重看 3 屏引导', en: 'Demo · replay the 3 intro screens' },
/* account management (integrated) */
meTitle: { zh: '我的', en: 'Account' },
curPlan: { zh: '当前套餐', en: 'Current plan' },
expires: { zh: '有效期至', en: 'Expires' },
upgradeBtn: { zh: '续费 / 升级', en: 'Renew / Upgrade' },
accInfoTitle: { zh: '账户信息', en: 'Account info' },
accEmail: { zh: '邮箱', en: 'Email' },
accPassword: { zh: '密码', en: 'Password' },
accChange: { zh: '修改', en: 'Change' },
accSignOut: { zh: '退出登录', en: 'Sign out' },
myDevices: { zh: '我的设备', en: 'My devices' },
devicesSub: { zh: 'PRO 套餐最多 5 台设备同时在线', en: 'Up to 5 devices on PRO' },
thisDevice: { zh: '当前设备', en: 'This device' },
lastActive: { zh: '最近活跃', en: 'Last active' },
remove: { zh: '移除', en: 'Remove' },
choosePlan: { zh: '选择套餐', en: 'Choose plan' },
free: { zh: '免费版', en: 'Free' },
proPlan: { zh: '专业版', en: 'Pro' },
team: { zh: '团队版', en: 'Team' },
perMonth: { zh: '/月', en: '/mo' },
current: { zh: '当前', en: 'Current' },
upgrade: { zh: '立即升级', en: 'Upgrade' },
choose: { zh: '选择', en: 'Choose' },
mostPopular: { zh: '最受欢迎', en: 'Popular' },
feFree: { zh: ['仅 1 个基础节点','每日 10 分钟时长','使用前观看广告','注册享 7 天免费试用'], en: ['1 basic node only','10 min per day','Watch an ad to start','7-day free trial on sign-up'] },
fePro: { zh: ['80+ 全球节点','无限流量 · 极速','5 台设备同时在线','流媒体优化'], en: ['80+ locations','Unlimited · fast','5 devices','Streaming optimized'] },
feTeam: { zh: ['Pro 全部功能','10 个成员席位','集中计费与管理','优先客服'], en: ['Everything in Pro','10 seats','Central billing','Priority support'] },
/* contact */
contactEntry: { zh: '联系我们', en: 'Contact us' },
contactTitle: { zh: '联系我们', en: 'Contact us' },
contactIntro: { zh: '遇到问题?通过以下任一渠道联系我们,通常数分钟内回复。', en: 'Need help? Reach us via any channel below — usually replies in minutes.' },
contactEmail: { zh: '邮箱客服', en: 'Email support' },
contactStore: { zh: '自助发卡商店', en: 'Self-serve store' },
contactHoursTitle: { zh: '服务时间', en: 'Support hours' },
contactHours: { zh: '每日 9:00 – 24:00 (GMT+8)', en: 'Daily 9:00 – 24:00 (GMT+8)' },
devicesCount: { zh: '台设备', en: 'devices' },
/* smart select + quota (口径见 CLAUDE.md §7) */
smartSelect: { zh: '智能选择', en: 'Smart select' },
smartSub: { zh: '根据当前网络环境,自动选择最优节点', en: 'Picks the best node for your network' },
recommended: { zh: '推荐', en: 'Recommended' },
quotaToday: { zh: '今日剩余', en: 'Left today' },
minutes: { zh: '分钟', en: 'min' },
quotaFree: { zh: '免费版 · 每日 10 分钟', en: 'Free · 10 min/day' },
quotaTrial: { zh: '体验期 · 7 天免费使用', en: 'Trial · 7 days free' },
watchAd: { zh: '看广告开始使用', en: 'Watch ad to start' },
adUnlocked: { zh: '已解锁 · 今日可用', en: 'Unlocked for today' },
freePlanName: { zh: '免费版', en: 'Free' },
upgradeNow: { zh: '升级 PRO', en: 'Go PRO' },
freeDemo: { zh: '演示:免费版视角', en: 'Demo: free-tier view' },
freeDemoSub: { zh: '切换连接页的额度展示与套餐横幅', en: 'Toggles quota UI & plan banner' },
};
function makeT(lang) { return (k) => (STRINGS[k] && STRINGS[k][lang]) || k; }
/* localized server label */
function srvName(s, lang) { return lang === 'zh' ? s.name : s.en; }
function srvSub(s, lang) {
const tag = s.tags && s.tags[0];
if (lang === 'zh') return (tag ? tag : s.en);
return (tag === '流媒体优化' ? 'Streaming' : tag === 'P2P' ? 'P2P' : s.name);
}
Object.assign(window, { Icon, Mark, CC, Signal, Pill, STRINGS, makeT, srvName, srvSub, SERVERS: [
{ code: 'HK', name: '香港 · 流媒体', en: 'Hong Kong', ping: 18, tags: ['流媒体优化'] },
{ code: 'JP', name: '日本 东京', en: 'Tokyo', ping: 32, tags: ['P2P'] },
{ code: 'SG', name: '新加坡', en: 'Singapore', ping: 54, tags: ['P2P'] },
{ code: 'TW', name: '台湾 台北', en: 'Taipei', ping: 28, tags: [] },
{ code: 'US', name: '美国 洛杉矶', en: 'Los Angeles', ping: 146, tags: ['流媒体优化'] },
{ code: 'DE', name: '德国 法兰克福', en: 'Frankfurt', ping: 198, tags: [] },
{ code: 'UK', name: '英国 伦敦', en: 'London', ping: 210, tags: [] },
{ code: 'KR', name: '韩国 首尔', en: 'Seoul', ping: 41, tags: [] },
] });