/* 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 all traffic if connection 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: [] }, ] });