Files
pangolin/design/_ds_bundle.js
T
wangjia a642bf16a2 feat: 同步 design/ 设计系统(含 iPad tablet kit) + 架构任务拆分(todo/)
design/ 同步自最新设计导出,新增 ui_kits/tablet/ 平板分栏布局;todo/ 录入 18 个并行实施任务。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 23:57:57 +08:00

10644 lines
291 KiB
JavaScript
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.
/* @ds-bundle: {"format":3,"namespace":"VPNDesignSystem_4c9564","components":[],"sourceHashes":{"ui_kits/desktop/dapp.jsx":"0261ad8c8093","ui_kits/desktop/dflows.jsx":"b52d86fc92ea","ui_kits/desktop/dparts.jsx":"e7fec2d75fc0","ui_kits/desktop/macos-window.jsx":"83e5cb33277e","ui_kits/mobile/flows.jsx":"838173b11d20","ui_kits/mobile/ios-frame.jsx":"39f3a091d97d","ui_kits/mobile/parts.jsx":"44efd184bf6a","ui_kits/mobile/screens.jsx":"cde11b0b1ad6","ui_kits/tablet/tabapp.jsx":"86e2e0f22462","ui_kits/usercenter/ucapp.jsx":"abe00336c077","ui_kits/usercenter/ucparts.jsx":"ba322907d9b7","ui_kits/website/site.js":"47ee2f38bcef"},"inlinedExternals":[],"unexposedExports":[]} */
(() => {
const __ds_ns = (window.VPNDesignSystem_4c9564 = window.VPNDesignSystem_4c9564 || {});
const __ds_scope = {};
(__ds_ns.__errors = __ds_ns.__errors || []);
// ui_kits/desktop/dapp.jsx
try { (() => {
/* dapp.jsx — 穿山甲 VPN desktop · window + views (i18n) */
const {
useState,
useEffect,
useRef
} = React;
function DToggle({
on,
onChange
}) {
return /*#__PURE__*/React.createElement("div", {
onClick: onChange,
style: {
width: 42,
height: 25,
borderRadius: 999,
position: 'relative',
cursor: 'pointer',
flexShrink: 0,
background: on ? 'var(--accent)' : 'var(--border-strong)',
transition: 'background 220ms var(--ease-out)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
position: 'absolute',
top: 3,
left: on ? 20 : 3,
width: 19,
height: 19,
borderRadius: '50%',
background: '#fff',
boxShadow: 'var(--shadow-sm)',
transition: 'left 220ms var(--ease-out)'
}
}));
}
function DLangSwitch({
lang,
setLang
}) {
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
background: 'var(--bg-subtle)',
borderRadius: 999,
padding: 3,
gap: 2
}
}, [['zh', '中文'], ['en', 'EN']].map(([v, l]) => /*#__PURE__*/React.createElement("button", {
key: v,
onClick: () => setLang(v),
style: {
border: 'none',
cursor: 'pointer',
borderRadius: 999,
padding: '4px 12px',
fontFamily: 'var(--font-sans)',
fontSize: 12,
fontWeight: 700,
background: lang === v ? 'var(--accent)' : 'transparent',
color: lang === v ? 'var(--fg-on-accent)' : 'var(--fg3)',
transition: 'all 140ms var(--ease-out)'
}
}, l)));
}
function NavItem({
icon,
label,
active,
onClick
}) {
return /*#__PURE__*/React.createElement("button", {
onClick: onClick,
style: {
display: 'flex',
alignItems: 'center',
gap: 11,
width: '100%',
textAlign: 'left',
border: 'none',
cursor: 'pointer',
background: active ? 'var(--accent-subtle)' : 'transparent',
color: active ? 'var(--accent)' : 'var(--fg2)',
padding: '9px 12px',
borderRadius: 'var(--radius-md)',
fontFamily: 'var(--font-sans)',
fontSize: 13.5,
fontWeight: active ? 600 : 500,
transition: 'background-color 140ms var(--ease-out), color 140ms var(--ease-out)'
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: icon,
size: 18,
stroke: active ? 2.3 : 2,
color: active ? 'var(--accent)' : 'var(--fg3)'
}), label);
}
function DConnect({
t,
lang,
status,
onToggle,
server,
smart,
free,
mins,
adDone,
onAd,
elapsed
}) {
const map = {
off: {
fill: 'var(--sand-100)',
fg: 'var(--clay-500)',
icon: 'power',
cap: t('capOff'),
sub: t('subOff'),
ring: 'track'
},
connecting: {
fill: 'var(--accent)',
fg: '#fff',
icon: 'loader-circle',
cap: t('capConnecting'),
sub: t('subConnecting'),
ring: 'spin'
},
on: {
fill: 'var(--success)',
fg: '#fff',
icon: 'shield-check',
cap: t('capOn'),
sub: t('subOn'),
ring: 'full'
}
}[status];
const fmt = s => `${String(Math.floor(s / 3600)).padStart(2, '0')}:${String(Math.floor(s % 3600 / 60)).padStart(2, '0')}:${String(s % 60).padStart(2, '0')}`;
const shadow = status === 'off' ? 'inset 0 0 0 1px var(--sand-200), var(--shadow-md)' : `0 0 0 8px ${status === 'on' ? 'var(--success-subtle)' : 'var(--accent-subtle)'}, var(--shadow-lg)`;
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
gap: 24,
padding: '10px 40px'
}
}, /*#__PURE__*/React.createElement("button", {
onClick: onToggle,
style: {
position: 'relative',
width: 176,
height: 176,
borderRadius: '50%',
border: 'none',
cursor: 'pointer',
background: map.fill,
color: map.fg,
boxShadow: shadow,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 7,
transition: 'box-shadow 300ms var(--ease-out), background-color 300ms var(--ease-out)'
}
}, /*#__PURE__*/React.createElement("svg", {
viewBox: "0 0 176 176",
style: {
position: 'absolute',
inset: 0,
width: '100%',
height: '100%',
transformOrigin: 'center',
animation: map.ring === 'spin' ? 'pg-spin 1.4s linear infinite' : 'none'
}
}, map.ring === 'track' && /*#__PURE__*/React.createElement("circle", {
cx: "88",
cy: "88",
r: "81",
fill: "none",
stroke: "var(--sand-200)",
strokeWidth: "3",
strokeDasharray: "2 8",
strokeLinecap: "round"
}), map.ring === 'spin' && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("circle", {
cx: "88",
cy: "88",
r: "81",
fill: "none",
stroke: "rgba(255,255,255,.3)",
strokeWidth: "4"
}), /*#__PURE__*/React.createElement("circle", {
cx: "88",
cy: "88",
r: "81",
fill: "none",
stroke: "#fff",
strokeWidth: "4",
strokeLinecap: "round",
strokeDasharray: "130 380"
})), map.ring === 'full' && /*#__PURE__*/React.createElement("circle", {
cx: "88",
cy: "88",
r: "81",
fill: "none",
stroke: "rgba(255,255,255,.85)",
strokeWidth: "4",
strokeLinecap: "round"
})), /*#__PURE__*/React.createElement(DIcon, {
name: map.icon,
size: 46,
stroke: 1.7,
style: status === 'connecting' ? {
animation: 'pg-spin 1s linear infinite'
} : {}
}), status === 'off' && /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-sans)',
fontWeight: 700,
fontSize: 13
}
}, t('connectNow')), status === 'connecting' && /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-mono)',
fontWeight: 700,
fontSize: 13,
letterSpacing: '0.08em'
}
}, "\xB7\xB7\xB7"), status === 'on' && /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-mono)',
fontWeight: 600,
fontSize: 15
}
}, fmt(elapsed))), /*#__PURE__*/React.createElement("div", {
style: {
textAlign: 'center'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontWeight: 700,
fontSize: 22,
color: 'var(--fg1)'
}
}, map.cap), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 13,
color: 'var(--fg3)',
marginTop: 4
}
}, map.sub), /*#__PURE__*/React.createElement("div", {
style: {
display: 'inline-flex',
alignItems: 'center',
gap: 7,
marginTop: 10,
background: 'var(--surface)',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-full)',
padding: '6px 14px',
fontSize: 12.5,
fontWeight: 600,
color: 'var(--fg2)'
}
}, smart && /*#__PURE__*/React.createElement(DIcon, {
name: "zap",
size: 13,
stroke: 2.4,
color: "var(--accent)"
}), /*#__PURE__*/React.createElement("span", {
style: {
whiteSpace: 'nowrap'
}
}, smart ? `${t('smartSelect')} · ` : '', dName(server, lang), " \xB7 ", server.ping, "ms"))), free && /*#__PURE__*/React.createElement("div", {
style: {
width: 340,
background: 'var(--surface)',
borderRadius: 'var(--radius-lg)',
padding: '12px 14px',
boxShadow: 'var(--shadow-sm)',
border: '1px solid var(--border)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 7,
marginBottom: 8
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: "clock",
size: 14,
stroke: 2.2,
color: "var(--accent)"
}), /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 12,
fontWeight: 600,
color: 'var(--fg2)'
}
}, t('quotaToday')), /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 13.5,
fontWeight: 600,
color: 'var(--fg1)',
whiteSpace: 'nowrap'
}
}, mins, " ", t('minutes')), /*#__PURE__*/React.createElement("span", {
style: {
marginLeft: 'auto',
fontSize: 10.5,
fontWeight: 600,
color: 'var(--fg3)',
background: 'var(--bg-subtle)',
padding: '3px 9px',
borderRadius: 'var(--radius-full)',
whiteSpace: 'nowrap'
}
}, t('quotaFree'))), /*#__PURE__*/React.createElement("div", {
style: {
height: 6,
background: 'var(--bg-subtle)',
borderRadius: 3,
overflow: 'hidden',
marginBottom: 10
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: `${Math.min(100, mins / 10 * 100)}%`,
height: '100%',
background: mins <= 3 ? 'var(--warning)' : 'var(--accent)',
borderRadius: 3
}
})), adDone ? /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 7,
color: 'var(--success)',
fontWeight: 700,
fontSize: 13,
padding: '8px'
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: "check-circle",
size: 15,
stroke: 2.2,
color: "var(--success)"
}), t('adUnlocked')) : /*#__PURE__*/React.createElement("button", {
onClick: onAd,
style: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 7,
width: '100%',
border: '1.5px solid var(--accent-border)',
background: 'var(--accent-subtle)',
color: 'var(--accent)',
fontFamily: 'var(--font-sans)',
fontWeight: 700,
fontSize: 13,
padding: '8px',
borderRadius: 'var(--radius-full)',
cursor: 'pointer'
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: "play-circle",
size: 15,
stroke: 2.2
}), t('watchAd'))), status === 'on' && /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 10
}
}, [['arrow-down', '86.4', 'Mb/s'], ['arrow-up', '12.1', 'Mb/s'], ['zap', String(server.ping), 'ms']].map(([ic, v, u]) => /*#__PURE__*/React.createElement("div", {
key: ic,
style: {
display: 'flex',
alignItems: 'center',
gap: 7,
background: 'var(--surface)',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-md)',
padding: '8px 14px',
boxShadow: 'var(--shadow-sm)'
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: ic,
size: 15,
stroke: 2.2,
color: "var(--accent)"
}), /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 14,
color: 'var(--fg1)'
}
}, v, /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 10,
color: 'var(--fg3)'
}
}, " ", u))))));
}
function DServers({
t,
lang,
current,
onPick
}) {
const [q, setQ] = useState('');
const list = DSERVERS.filter(s => (s.name + s.en).toLowerCase().includes(q.toLowerCase()));
const smartActive = current === 'AUTO';
return /*#__PURE__*/React.createElement("div", {
style: {
padding: '4px 28px 24px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 9,
background: 'var(--bg-subtle)',
borderRadius: 'var(--radius-md)',
padding: '9px 13px',
marginBottom: 16,
maxWidth: 340
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: "search",
size: 17,
color: "var(--fg3)"
}), /*#__PURE__*/React.createElement("input", {
value: q,
onChange: e => setQ(e.target.value),
placeholder: t('search'),
style: {
border: 'none',
background: 'transparent',
outline: 'none',
flex: 1,
fontFamily: 'var(--font-sans)',
fontSize: 13.5,
color: 'var(--fg1)'
}
})), /*#__PURE__*/React.createElement("button", {
onClick: () => onPick('AUTO'),
style: {
display: 'flex',
alignItems: 'center',
gap: 12,
width: '100%',
textAlign: 'left',
cursor: 'pointer',
background: 'var(--accent-subtle)',
border: `1.5px solid ${smartActive ? 'var(--accent)' : 'var(--accent-border)'}`,
borderRadius: 'var(--radius-xl)',
padding: '14px',
boxShadow: smartActive ? 'var(--shadow-md)' : 'var(--shadow-sm)',
marginBottom: 10
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 38,
height: 38,
borderRadius: 'var(--radius-md)',
background: 'linear-gradient(150deg,var(--clay-500),var(--clay-700))',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: "zap",
size: 18,
stroke: 2.2,
color: "#fff"
})), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 8
}
}, /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-sans)',
fontSize: 14.5,
fontWeight: 700,
color: 'var(--fg1)'
}
}, t('smartSelect')), /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 10,
fontWeight: 700,
color: '#fff',
background: 'var(--accent)',
padding: '2px 8px',
borderRadius: 999,
letterSpacing: '0.04em'
}
}, t('recommended'))), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 11.5,
color: 'var(--fg2)',
marginTop: 3
}
}, t('smartSub'))), smartActive && /*#__PURE__*/React.createElement(DIcon, {
name: "check",
size: 18,
color: "var(--accent)",
stroke: 2.6
})), /*#__PURE__*/React.createElement("div", {
style: {
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: 10
}
}, list.map(s => {
const active = s.code === current;
return /*#__PURE__*/React.createElement("button", {
key: s.code,
onClick: () => onPick(s.code),
style: {
display: 'flex',
alignItems: 'center',
gap: 12,
textAlign: 'left',
cursor: 'pointer',
background: active ? 'var(--accent-subtle)' : 'var(--surface)',
border: `1.5px solid ${active ? 'var(--accent-border)' : 'var(--border)'}`,
borderRadius: 'var(--radius-lg)',
padding: '12px 14px',
boxShadow: 'var(--shadow-sm)'
}
}, /*#__PURE__*/React.createElement(DCC, {
code: s.code,
active: active
}), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
minWidth: 0
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-sans)',
fontSize: 14,
fontWeight: 600,
color: 'var(--fg1)'
}
}, dName(s, lang)), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 11.5,
color: 'var(--fg3)',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}
}, dSub(s, lang))), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-end',
gap: 5
}
}, /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 11.5,
color: 'var(--fg2)'
}
}, s.ping, "ms"), /*#__PURE__*/React.createElement(DSignal, {
ping: s.ping
})), active && /*#__PURE__*/React.createElement(DIcon, {
name: "check",
size: 17,
color: "var(--accent)"
}));
})));
}
function DStats({
t,
lang
}) {
const vals = [2.1, 3.4, 1.8, 4.6, 5.2, 6.1, 3.0];
const labels = t('days');
const max = 6.1;
return /*#__PURE__*/React.createElement("div", {
style: {
padding: '8px 32px 24px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 14,
marginBottom: 22
}
}, [[t('trafficMonth'), '42.6 GB'], [t('avgPing'), '29 ms'], [t('durMonth'), '86.4 h']].map(([l, v]) => /*#__PURE__*/React.createElement("div", {
key: l,
style: {
flex: 1,
background: 'var(--surface)',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-lg)',
padding: '16px 18px',
boxShadow: 'var(--shadow-sm)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12,
color: 'var(--fg3)',
fontWeight: 600
}
}, l), /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 24,
fontWeight: 500,
color: 'var(--fg1)',
marginTop: 6
}
}, v)))), /*#__PURE__*/React.createElement("div", {
style: {
background: 'var(--surface)',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-lg)',
padding: '18px 20px',
boxShadow: 'var(--shadow-sm)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 13,
fontWeight: 600,
color: 'var(--fg1)',
marginBottom: 18
}
}, t('weekTraffic')), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'flex-end',
gap: 18,
height: 120
}
}, vals.map((v, i) => /*#__PURE__*/React.createElement("div", {
key: i,
style: {
flex: 1,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 8
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 10,
color: 'var(--fg3)'
}
}, v), /*#__PURE__*/React.createElement("div", {
style: {
width: '100%',
maxWidth: 34,
height: `${v / max * 86}px`,
background: 'var(--accent)',
borderRadius: '6px 6px 0 0',
opacity: 0.85
}
}), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 11,
color: 'var(--fg3)'
}
}, labels[i]))))));
}
function DSettings({
t,
lang,
setLang,
dark,
setDark,
free,
setFree,
onReplayAuth,
onReplayOnboarding,
onRedeem
}) {
const [auto, setAuto] = useState(true);
const [kill, setKill] = useState(true);
const [boot, setBoot] = useState(true);
const card = {
background: 'var(--surface)',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-lg)',
boxShadow: 'var(--shadow-sm)',
overflow: 'hidden',
marginBottom: 16
};
const Row = ({
title,
sub,
right,
last,
onClick
}) => /*#__PURE__*/React.createElement("div", {
onClick: onClick,
style: {
display: 'flex',
alignItems: 'center',
gap: 12,
padding: '14px 18px',
borderBottom: last ? 'none' : '1px solid var(--border)',
cursor: onClick ? 'pointer' : 'default'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
flex: 1
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 14,
fontWeight: 500,
color: 'var(--fg1)'
}
}, title), sub && /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12,
color: 'var(--fg3)',
marginTop: 2
}
}, sub)), right);
return /*#__PURE__*/React.createElement("div", {
style: {
padding: '8px 32px 24px',
maxWidth: 560
}
}, /*#__PURE__*/React.createElement("div", {
style: card
}, /*#__PURE__*/React.createElement(Row, {
title: t('redeemEntry'),
sub: t('proMember'),
right: /*#__PURE__*/React.createElement(DIcon, {
name: "chevron-right",
size: 18,
color: "var(--fg3)"
}),
last: true,
onClick: onRedeem
})), /*#__PURE__*/React.createElement("div", {
style: card
}, /*#__PURE__*/React.createElement(Row, {
title: t('autostart'),
sub: t('autostartSub'),
right: /*#__PURE__*/React.createElement(DToggle, {
on: boot,
onChange: () => setBoot(!boot)
})
}), /*#__PURE__*/React.createElement(Row, {
title: t('smartRoute'),
sub: t('smartRouteSub'),
right: /*#__PURE__*/React.createElement(DToggle, {
on: auto,
onChange: () => setAuto(!auto)
})
}), /*#__PURE__*/React.createElement(Row, {
title: t('killSwitch'),
sub: t('killSwitchSub'),
right: /*#__PURE__*/React.createElement(DToggle, {
on: kill,
onChange: () => setKill(!kill)
}),
last: true
})), /*#__PURE__*/React.createElement("div", {
style: card
}, /*#__PURE__*/React.createElement(Row, {
title: t('language'),
right: /*#__PURE__*/React.createElement(DLangSwitch, {
lang: lang,
setLang: setLang
})
}), /*#__PURE__*/React.createElement(Row, {
title: t('darkAppearance'),
sub: dark ? t('stateOn') : t('followLight'),
right: /*#__PURE__*/React.createElement(DToggle, {
on: dark,
onChange: () => setDark(!dark)
})
}), /*#__PURE__*/React.createElement(Row, {
title: t('protocol'),
right: /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 13,
color: 'var(--fg3)'
}
}, "WireGuard")
}), /*#__PURE__*/React.createElement(Row, {
title: t('checkUpdate'),
right: /*#__PURE__*/React.createElement(DIcon, {
name: "chevron-right",
size: 18,
color: "var(--fg3)"
})
}), /*#__PURE__*/React.createElement(Row, {
title: t('version'),
right: /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 13,
color: 'var(--fg3)'
}
}, "v2.4.0 (macOS)"),
last: true
})), /*#__PURE__*/React.createElement("div", {
style: card
}, /*#__PURE__*/React.createElement(Row, {
title: t('replayAuth'),
sub: t('replayAuthSub'),
right: /*#__PURE__*/React.createElement(DIcon, {
name: "chevron-right",
size: 18,
color: "var(--fg3)"
}),
onClick: onReplayAuth
}), /*#__PURE__*/React.createElement(Row, {
title: t('replayOnboarding'),
sub: t('replayOnboardingSub'),
right: /*#__PURE__*/React.createElement(DIcon, {
name: "chevron-right",
size: 18,
color: "var(--fg3)"
}),
onClick: onReplayOnboarding
}), /*#__PURE__*/React.createElement(Row, {
title: t('freeDemo'),
sub: t('freeDemoSub'),
right: /*#__PURE__*/React.createElement(DToggle, {
on: free,
onChange: () => setFree(!free)
}),
last: true
})));
}
function DesktopApp() {
const [stage, setStage] = useState('app'); // app | auth | onboarding — UI kit opens on the main app
const [view, setView] = useState('connect');
const [status, setStatus] = useState('off');
const [code, setCode] = useState('AUTO'); // 'AUTO' = 智能选择(默认)
const [dark, setDark] = useState(false);
const [lang, setLang] = useState('zh');
const [free, setFree] = useState(true); // 演示:免费版视角(与移动端一致)
const [mins, setMins] = useState(6);
const [adDone, setAdDone] = useState(false);
const [elapsed, setElapsed] = useState(0);
const timer = useRef(null);
const t = dT(lang);
const smart = code === 'AUTO';
const server = smart ? DSERVERS.reduce((a, b) => a.ping < b.ping ? a : b) : DSERVERS.find(s => s.code === code);
const titles = {
connect: t('connect'),
servers: t('servers'),
stats: t('stats'),
account: t('navAccount'),
plans: t('choosePlan'),
redeem: t('redeemTitle'),
contact: t('navContact'),
settings: t('settings')
};
function toggle() {
if (status === 'off') {
setStatus('connecting');
setTimeout(() => {
setStatus('on');
setElapsed(0);
}, 1500);
} else setStatus('off');
}
function pick(c) {
setCode(c);
setView('connect');
if (status === 'on') {
setStatus('connecting');
setTimeout(() => {
setStatus('on');
setElapsed(0);
}, 1200);
}
}
useEffect(() => {
if (status === 'on') timer.current = setInterval(() => setElapsed(e => e + 1), 1000);
return () => clearInterval(timer.current);
}, [status]);
if (stage === 'auth') return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
minHeight: '100vh',
padding: 28,
boxSizing: 'border-box'
}
}, /*#__PURE__*/React.createElement(DAuthFlow, {
t: t,
lang: lang,
dark: dark,
onDone: () => setStage('onboarding')
}));
if (stage === 'onboarding') return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
minHeight: '100vh',
padding: 28,
boxSizing: 'border-box'
}
}, /*#__PURE__*/React.createElement(DOnboarding, {
t: t,
lang: lang,
dark: dark,
onDone: () => setStage('app')
}));
let main;
if (view === 'connect') main = /*#__PURE__*/React.createElement(DConnect, {
t: t,
lang: lang,
status: status,
onToggle: toggle,
server: server,
smart: smart,
free: free,
mins: mins,
adDone: adDone,
onAd: () => setAdDone(true),
elapsed: elapsed
});else if (view === 'servers') main = /*#__PURE__*/React.createElement(DServers, {
t: t,
lang: lang,
current: code,
onPick: pick
});else if (view === 'stats') main = /*#__PURE__*/React.createElement(DStats, {
t: t,
lang: lang
});else if (view === 'account') main = /*#__PURE__*/React.createElement(DAccountView, {
t: t,
lang: lang,
free: free,
onUpgrade: () => setView('plans'),
onSignOut: () => setStage('auth')
});else if (view === 'plans') main = /*#__PURE__*/React.createElement(DPlansView, {
t: t,
lang: lang,
onChoose: () => setView('redeem')
});else if (view === 'redeem') main = /*#__PURE__*/React.createElement(DRedeemView, {
t: t,
lang: lang
});else if (view === 'contact') main = /*#__PURE__*/React.createElement(DContactView, {
t: t,
lang: lang
});else main = /*#__PURE__*/React.createElement(DSettings, {
t: t,
lang: lang,
setLang: setLang,
dark: dark,
setDark: setDark,
free: free,
setFree: setFree,
onReplayAuth: () => setStage('auth'),
onReplayOnboarding: () => setStage('onboarding'),
onRedeem: () => setView('redeem')
});
return /*#__PURE__*/React.createElement("div", {
"data-theme": dark ? 'dark' : 'light',
style: {
width: 920,
height: 600,
borderRadius: 16,
overflow: 'hidden',
display: 'flex',
background: 'var(--bg)',
boxShadow: '0 0 0 1px rgba(0,0,0,0.12), 0 28px 70px rgba(0,0,0,0.4)',
fontFamily: 'var(--font-sans)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 204,
flexShrink: 0,
background: 'var(--bg-subtle)',
borderRight: '1px solid var(--border)',
display: 'flex',
flexDirection: 'column',
padding: '14px 12px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
padding: '2px 4px 16px'
}
}, /*#__PURE__*/React.createElement(MacTrafficLights, null)), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 9,
padding: '0 6px 16px'
}
}, /*#__PURE__*/React.createElement(DMark, {
size: 28
}), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontWeight: 700,
fontSize: 16,
color: 'var(--fg1)',
lineHeight: 1
}
}, lang === 'zh' ? '穿山甲' : 'Pangolin'), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 9,
fontWeight: 600,
letterSpacing: '0.18em',
color: 'var(--accent)',
marginTop: 3
}
}, "PANGOLIN"))), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
gap: 3
}
}, /*#__PURE__*/React.createElement(NavItem, {
icon: "power",
label: t('navConnect'),
active: view === 'connect',
onClick: () => setView('connect')
}), /*#__PURE__*/React.createElement(NavItem, {
icon: "globe",
label: t('navServers'),
active: view === 'servers',
onClick: () => setView('servers')
}), /*#__PURE__*/React.createElement(NavItem, {
icon: "chart-no-axes-column",
label: t('navStats'),
active: view === 'stats',
onClick: () => setView('stats')
}), /*#__PURE__*/React.createElement(NavItem, {
icon: "user",
label: t('navAccount'),
active: view === 'account' || view === 'plans' || view === 'redeem',
onClick: () => setView('account')
}), /*#__PURE__*/React.createElement(NavItem, {
icon: "message-circle",
label: t('navContact'),
active: view === 'contact',
onClick: () => setView('contact')
}), /*#__PURE__*/React.createElement(NavItem, {
icon: "settings",
label: t('navSettings'),
active: view === 'settings',
onClick: () => setView('settings')
})), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1
}
}), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 10,
background: 'var(--surface)',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-md)',
padding: '9px 11px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 30,
height: 30,
borderRadius: '50%',
background: 'linear-gradient(150deg,var(--clay-500),var(--clay-700))',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: "crown",
size: 15,
color: "#fff"
})), /*#__PURE__*/React.createElement("div", {
style: {
minWidth: 0
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12,
fontWeight: 600,
color: 'var(--fg1)',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}
}, free ? t('freePlanName') : t('proMember')), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 10.5,
color: 'var(--fg3)',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}
}, free ? t('quotaFree') : t('until'))))), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
display: 'flex',
flexDirection: 'column',
minWidth: 0
}
}, /*#__PURE__*/React.createElement("div", {
style: {
height: 52,
flexShrink: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 24px',
borderBottom: '1px solid var(--border)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 10
}
}, (view === 'redeem' || view === 'plans') && /*#__PURE__*/React.createElement("button", {
onClick: () => setView('account'),
style: {
border: 'none',
background: 'transparent',
cursor: 'pointer',
padding: 4,
display: 'flex'
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: "arrow-left",
size: 20,
color: "var(--fg1)"
})), /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontWeight: 700,
fontSize: 17,
color: 'var(--fg1)'
}
}, titles[view])), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 12
}
}, /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 12,
fontWeight: 600,
whiteSpace: 'nowrap',
color: status === 'on' ? 'var(--success)' : 'var(--fg3)'
}
}, status === 'on' ? '● ' + t('connected') + ' ' + server.code : '○ ' + t('notConnected')), /*#__PURE__*/React.createElement("button", {
onClick: () => setDark(!dark),
style: {
border: 'none',
background: 'transparent',
cursor: 'pointer',
padding: 6,
borderRadius: 'var(--radius-sm)',
display: 'flex'
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: dark ? 'sun' : 'moon',
size: 18,
color: "var(--fg2)"
})))), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
overflow: 'auto',
minHeight: 0
}
}, main)));
}
Object.assign(window, {
DesktopApp
});
})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/desktop/dapp.jsx", error: String((e && e.message) || e) }); }
// ui_kits/desktop/dflows.jsx
try { (() => {
/* dflows.jsx — 穿山甲 VPN desktop · auth + account management (integrated dashboard) + redeem */
const {
useState: useStateD
} = React;
const dCard = {
background: 'var(--surface)',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-xl)',
boxShadow: 'var(--shadow-sm)'
};
const dInput = {
border: '1.5px solid var(--border-strong)',
borderRadius: 'var(--radius-md)',
padding: '11px 13px',
fontFamily: 'var(--font-sans)',
fontSize: 14,
color: 'var(--fg1)',
background: 'var(--bg)',
outline: 'none',
width: '100%',
boxSizing: 'border-box'
};
function DField({
label,
children
}) {
return /*#__PURE__*/React.createElement("label", {
style: {
display: 'block'
}
}, /*#__PURE__*/React.createElement("span", {
style: {
display: 'block',
fontSize: 12.5,
fontWeight: 600,
color: 'var(--fg2)',
marginBottom: 6
}
}, label), children);
}
/* ───────── AUTH — split panel, brand left / form right ───────── */
function DAuthFlow({
t,
lang,
dark,
onDone
}) {
const [mode, setMode] = useStateD('login');
const [step, setStep] = useStateD(0);
const [email, setEmail] = useStateD('');
const [code, setCode] = useStateD('');
const [pw, setPw] = useStateD('');
const [sent, setSent] = useStateD(false);
const [totp, setTotp] = useStateD(false); // 2FA step after login (PRO)
const [otp, setOtp] = useStateD('');
const valid = /\S+@\S+\.\S+/.test(email);
const tab = (id, label) => /*#__PURE__*/React.createElement("button", {
onClick: () => {
setMode(id);
setStep(0);
setSent(false);
},
style: {
flex: 1,
border: 'none',
cursor: 'pointer',
background: 'transparent',
padding: '10px 0',
fontFamily: 'var(--font-sans)',
fontSize: 15,
fontWeight: mode === id ? 700 : 500,
color: mode === id ? 'var(--fg1)' : 'var(--fg3)',
borderBottom: mode === id ? '2px solid var(--accent)' : '2px solid transparent'
}
}, label);
const primary = (label, onClick, enabled) => /*#__PURE__*/React.createElement("button", {
onClick: enabled ? onClick : undefined,
disabled: !enabled,
style: {
width: '100%',
border: 'none',
borderRadius: 'var(--radius-full)',
padding: '13px',
fontFamily: 'var(--font-sans)',
fontWeight: 700,
fontSize: 15,
cursor: enabled ? 'pointer' : 'not-allowed',
background: 'var(--accent)',
color: 'var(--fg-on-accent)',
opacity: enabled ? 1 : 0.45,
marginTop: 4
}
}, label);
return /*#__PURE__*/React.createElement("div", {
"data-theme": dark ? 'dark' : 'light',
style: {
width: 880,
height: 560,
borderRadius: 16,
overflow: 'hidden',
display: 'flex',
boxShadow: '0 0 0 1px rgba(0,0,0,0.12), 0 28px 70px rgba(0,0,0,0.4)',
fontFamily: 'var(--font-sans)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 340,
flexShrink: 0,
background: 'linear-gradient(165deg,var(--clay-500),var(--clay-800))',
color: '#fff',
padding: '48px 40px',
display: 'flex',
flexDirection: 'column'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 11
}
}, /*#__PURE__*/React.createElement(DMark, {
size: 34,
color: "#fff"
}), /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontWeight: 700,
fontSize: 20
}
}, lang === 'zh' ? '穿山甲' : 'Pangolin')), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontWeight: 700,
fontSize: 28,
lineHeight: 1.3
}
}, t('authSidePitch'))), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12,
opacity: .8,
letterSpacing: '0.04em'
}
}, t('authTagline'))), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
background: 'var(--bg)',
padding: '44px 46px',
display: 'flex',
flexDirection: 'column'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
borderBottom: '1px solid var(--border)',
marginBottom: 26
}
}, tab('login', t('tabLogin')), tab('register', t('tabRegister'))), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
display: 'flex',
flexDirection: 'column',
gap: 16
}
}, mode === 'login' && !totp && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(DField, {
label: t('emailLabel')
}, /*#__PURE__*/React.createElement("input", {
style: dInput,
value: email,
onChange: e => setEmail(e.target.value),
placeholder: t('emailPh')
})), /*#__PURE__*/React.createElement(DField, {
label: t('pwLabel')
}, /*#__PURE__*/React.createElement("input", {
style: dInput,
type: "password",
value: pw,
onChange: e => setPw(e.target.value),
placeholder: t('pwPh')
})), /*#__PURE__*/React.createElement("div", {
style: {
textAlign: 'right',
marginTop: -6
}
}, /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 13,
color: 'var(--accent)',
fontWeight: 600,
cursor: 'pointer'
}
}, t('forgotPw'))), primary(t('doLogin'), () => setTotp(true), valid && pw.length > 0)), mode === 'login' && totp && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 8
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: "shield-check",
size: 18,
color: "var(--accent)"
}), /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-display)',
fontWeight: 700,
fontSize: 17,
color: 'var(--fg1)'
}
}, t('twoFATitle')), /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 10,
fontWeight: 700,
color: 'var(--accent)',
background: 'var(--accent-subtle)',
border: '1px solid var(--accent-border)',
padding: '2px 8px',
borderRadius: 999
}
}, "PRO")), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12.5,
color: 'var(--fg3)',
lineHeight: 1.6
}
}, t('twoFAHint')), /*#__PURE__*/React.createElement("input", {
value: otp,
onChange: e => setOtp(e.target.value.replace(/\D/g, '').slice(0, 6)),
placeholder: "\xB7\xB7\xB7\xB7\xB7\xB7",
style: {
...dInput,
fontFamily: 'var(--font-mono)',
fontSize: 20,
letterSpacing: '0.5em',
textAlign: 'center'
}
}), primary(t('twoFAConfirm'), onDone, otp.length === 6), /*#__PURE__*/React.createElement("div", {
onClick: () => setTotp(false),
style: {
textAlign: 'center',
fontSize: 12.5,
color: 'var(--fg3)',
cursor: 'pointer'
}
}, t('twoFABack'))), mode === 'register' && step === 0 && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(DField, {
label: t('emailLabel')
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 8
}
}, /*#__PURE__*/React.createElement("input", {
style: dInput,
value: email,
onChange: e => setEmail(e.target.value),
placeholder: t('emailPh')
}), /*#__PURE__*/React.createElement("button", {
onClick: () => valid && setSent(true),
style: {
border: '1.5px solid var(--border-strong)',
background: 'transparent',
borderRadius: 'var(--radius-md)',
padding: '0 16px',
cursor: valid ? 'pointer' : 'default',
color: valid ? 'var(--accent)' : 'var(--fg3)',
fontWeight: 700,
fontSize: 13,
whiteSpace: 'nowrap',
flexShrink: 0
}
}, sent ? t('resend') : t('sendCode')))), sent && /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12,
color: 'var(--success)',
marginTop: -8,
display: 'flex',
alignItems: 'center',
gap: 6
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: "check-circle",
size: 14,
color: "var(--success)"
}), t('codeSentTo'), " ", email), /*#__PURE__*/React.createElement(DField, {
label: t('codeLabel')
}, /*#__PURE__*/React.createElement("input", {
style: {
...dInput,
fontFamily: 'var(--font-mono)',
letterSpacing: '0.3em'
},
value: code,
onChange: e => setCode(e.target.value.replace(/\D/g, '').slice(0, 6)),
placeholder: "\xB7\xB7\xB7\xB7\xB7\xB7"
})), primary(t('doNext'), () => setStep(1), sent && code.length === 6)), mode === 'register' && step === 1 && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 13,
color: 'var(--fg2)',
display: 'flex',
alignItems: 'center',
gap: 7
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: "check-circle",
size: 15,
color: "var(--success)"
}), email), /*#__PURE__*/React.createElement(DField, {
label: t('pwLabel')
}, /*#__PURE__*/React.createElement("input", {
style: dInput,
type: "password",
value: pw,
onChange: e => setPw(e.target.value),
placeholder: t('setPwPh')
})), primary(t('doCreate'), onDone, pw.length >= 6))), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 11,
color: 'var(--fg3)',
textAlign: 'center',
lineHeight: 1.5,
paddingTop: 18
}
}, t('tos'))));
}
/* ───────── ACCOUNT (integrated: plan + account info + devices) ───────── */
function DAccountView({
t,
lang,
free,
onUpgrade,
onSignOut
}) {
const [twoFA, setTwoFA] = useStateD(true);
const [devices, setDevices] = useStateD([{
id: 1,
icon: 'laptop',
name: 'MacBook Pro',
os: 'macOS 26',
active: t('thisDevice'),
me: true
}, {
id: 2,
icon: 'smartphone',
name: 'iPhone 17',
os: 'iOS 26',
active: '2 min',
me: false
}, {
id: 3,
icon: 'monitor-smartphone',
name: 'iPad Air',
os: 'iPadOS 26',
active: lang === 'zh' ? '3 小时前' : '3h ago',
me: false
}]);
const infoRow = (label, value, action, last) => /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 12,
padding: '14px 18px',
borderBottom: last ? 'none' : '1px solid var(--border)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 96,
fontSize: 13,
fontWeight: 600,
color: 'var(--fg2)'
}
}, label), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
fontSize: 14,
color: 'var(--fg1)'
}
}, value), action);
const smallBtn = {
border: '1.5px solid var(--border-strong)',
background: 'transparent',
color: 'var(--fg2)',
fontSize: 13,
fontWeight: 600,
padding: '7px 14px',
borderRadius: 'var(--radius-full)',
cursor: 'pointer'
};
return /*#__PURE__*/React.createElement("div", {
style: {
padding: '8px 32px 28px',
display: 'flex',
flexDirection: 'column',
gap: 22,
maxWidth: 760
}
}, /*#__PURE__*/React.createElement("div", {
style: {
...dCard,
background: 'linear-gradient(150deg,var(--clay-600),var(--clay-800))',
border: 'none',
color: '#fff',
padding: '20px 24px',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 15
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 48,
height: 48,
borderRadius: '50%',
background: 'rgba(255,255,255,.18)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: "crown",
size: 24,
color: "#fff"
})), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12.5,
opacity: .8
}
}, t('curPlan')), /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontSize: 20,
fontWeight: 700,
marginTop: 2
}
}, free ? t('freePlanName') : t('proMember')), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 11.5,
opacity: .8,
marginTop: 3,
fontFamily: 'var(--font-mono)',
whiteSpace: 'nowrap'
}
}, free ? t('quotaFree') : `${t('expires')} 2026-12-31 · ${t('accUsage')} 42.6 GB`))), /*#__PURE__*/React.createElement("button", {
onClick: onUpgrade,
style: {
border: 'none',
background: '#fff',
color: 'var(--clay-700)',
fontWeight: 700,
fontSize: 14,
padding: '11px 20px',
borderRadius: 'var(--radius-full)',
cursor: 'pointer',
display: 'inline-flex',
alignItems: 'center',
gap: 8
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: "ticket",
size: 16,
color: "var(--clay-700)"
}), t('renew'))), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontSize: 17,
fontWeight: 700,
color: 'var(--fg1)',
marginBottom: 14
}
}, t('accInfoTitle')), /*#__PURE__*/React.createElement("div", {
style: {
...dCard,
overflow: 'hidden'
}
}, infoRow(t('accEmail'), 'me@pangolin.vpn', /*#__PURE__*/React.createElement("button", {
style: smallBtn
}, t('accChange'))), infoRow(t('accPassword'), '••••••••••', /*#__PURE__*/React.createElement("button", {
style: smallBtn
}, t('accChange'))), infoRow(t('twoFA'), t('twoFASub'), /*#__PURE__*/React.createElement(DToggle, {
on: twoFA,
onChange: () => setTwoFA(!twoFA)
})), infoRow(t('ucEntry'), t('ucEntrySub'), /*#__PURE__*/React.createElement("button", {
onClick: () => window.open('../usercenter/index.html', '_blank'),
style: {
...smallBtn,
display: 'inline-flex',
alignItems: 'center',
gap: 6
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: "external-link",
size: 14,
color: "var(--fg2)"
}), t('ucOpen'))), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 12,
padding: '14px 18px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 96,
fontSize: 13,
fontWeight: 600,
color: 'var(--danger)'
}
}, t('accSignOut')), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1
}
}), /*#__PURE__*/React.createElement("button", {
onClick: onSignOut,
style: {
...smallBtn,
color: 'var(--danger)',
borderColor: 'var(--danger-subtle)',
background: 'var(--danger-subtle)',
display: 'inline-flex',
alignItems: 'center',
gap: 7
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: "log-out",
size: 15,
color: "var(--danger)"
}), t('accSignOut'))))), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontSize: 17,
fontWeight: 700,
color: 'var(--fg1)'
}
}, t('myDevices')), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12.5,
color: 'var(--fg3)',
margin: '3px 0 14px'
}
}, t('devicesSub')), /*#__PURE__*/React.createElement("div", {
style: {
...dCard,
overflow: 'hidden'
}
}, devices.map((d, i) => /*#__PURE__*/React.createElement("div", {
key: d.id,
style: {
display: 'flex',
alignItems: 'center',
gap: 14,
padding: '14px 18px',
borderBottom: i < devices.length - 1 ? '1px solid var(--border)' : 'none'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 40,
height: 40,
borderRadius: 'var(--radius-md)',
background: 'var(--accent-subtle)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: d.icon,
size: 20,
color: "var(--accent)"
})), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 14.5,
fontWeight: 600,
color: 'var(--fg1)',
display: 'flex',
alignItems: 'center',
gap: 8
}
}, d.name, d.me && /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 11,
fontWeight: 600,
color: 'var(--success)',
background: 'var(--success-subtle)',
padding: '2px 8px',
borderRadius: 999
}
}, d.active)), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12,
color: 'var(--fg3)',
marginTop: 2
}
}, d.os, !d.me && ` · ${t('lastActive')} ${d.active}`)), !d.me && /*#__PURE__*/React.createElement("button", {
onClick: () => setDevices(devices.filter(x => x.id !== d.id)),
style: {
border: '1.5px solid var(--border-strong)',
background: 'transparent',
color: 'var(--fg2)',
fontSize: 13,
fontWeight: 600,
padding: '7px 14px',
borderRadius: 'var(--radius-full)',
cursor: 'pointer'
}
}, t('remove')))))));
}
/* ───────── PLANS (shown on renew / upgrade) ───────── */
function DPlansView({
t,
lang,
onChoose
}) {
const plans = [{
id: 'free',
name: t('free'),
price: '¥0',
feats: t('feFree'),
cta: t('current'),
variant: 'cur'
}, {
id: 'pro',
name: t('proPlan'),
price: '¥25',
feats: t('fePro'),
cta: t('upgrade'),
variant: 'pro'
}, {
id: 'team',
name: t('team'),
price: '¥99',
feats: t('feTeam'),
cta: t('choose'),
variant: 'plain'
}];
return /*#__PURE__*/React.createElement("div", {
style: {
padding: '8px 32px 28px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'grid',
gridTemplateColumns: '1fr 1fr 1fr',
gap: 16,
alignItems: 'start',
maxWidth: 760
}
}, plans.map(p => {
const isPro = p.variant === 'pro';
return /*#__PURE__*/React.createElement("div", {
key: p.id,
style: {
...dCard,
position: 'relative',
padding: '22px 20px',
...(isPro ? {
background: 'linear-gradient(160deg,var(--clay-600),var(--clay-800))',
border: 'none',
color: '#fff',
boxShadow: 'var(--shadow-md)'
} : {})
}
}, isPro && /*#__PURE__*/React.createElement("div", {
style: {
position: 'absolute',
top: -10,
left: '50%',
transform: 'translateX(-50%)',
background: '#fff',
color: 'var(--clay-700)',
fontSize: 11,
fontWeight: 700,
padding: '3px 11px',
borderRadius: 999,
boxShadow: 'var(--shadow-sm)'
}
}, t('mostPopular')), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 13.5,
fontWeight: 600,
color: isPro ? '#fff' : 'var(--fg2)',
opacity: isPro ? .85 : 1
}
}, p.name), /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontSize: 30,
fontWeight: 700,
marginTop: 6,
color: isPro ? '#fff' : 'var(--fg1)'
}
}, p.price, /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 13,
fontWeight: 500,
opacity: .7
}
}, t('perMonth'))), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
gap: 9,
margin: '16px 0 18px'
}
}, p.feats.map((f, i) => /*#__PURE__*/React.createElement("div", {
key: i,
style: {
display: 'flex',
alignItems: 'center',
gap: 9,
fontSize: 13,
color: isPro ? 'rgba(255,255,255,.92)' : 'var(--fg2)'
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: "check",
size: 15,
color: isPro ? '#fff' : 'var(--success)',
stroke: 2.4
}), f))), /*#__PURE__*/React.createElement("button", {
onClick: p.variant !== 'cur' ? onChoose : undefined,
style: {
width: '100%',
border: p.variant === 'cur' ? '1.5px solid var(--border-strong)' : 'none',
cursor: p.variant === 'cur' ? 'default' : 'pointer',
borderRadius: 'var(--radius-full)',
padding: '11px',
fontWeight: 600,
fontSize: 14,
background: isPro ? '#fff' : p.variant === 'cur' ? 'transparent' : 'var(--accent)',
color: isPro ? 'var(--clay-700)' : p.variant === 'cur' ? 'var(--fg2)' : '#fff'
}
}, p.cta));
})));
}
/* ───────── REDEEM & BUY ───────── */
function DRedeemView({
t,
lang
}) {
const [code, setCode] = useStateD('');
const [ok, setOk] = useStateD(false);
const channels = [{
icon: 'shopping-bag',
name: t('chStore'),
sub: t('chStoreSub'),
accent: true
}, {
icon: 'credit-card',
name: 'USDT (TRC20)',
sub: t('chUsdtSub'),
mono: false
}, {
icon: 'send',
name: 'Telegram',
sub: t('chTgSub'),
mono: true
}, {
icon: 'message-circle',
name: 'LINE',
sub: t('chLineSub'),
mono: true
}, {
icon: 'mail',
name: t('chEmail'),
sub: t('chEmailSub'),
mono: true
}];
return /*#__PURE__*/React.createElement("div", {
style: {
padding: '8px 32px 28px',
maxWidth: 680
}
}, /*#__PURE__*/React.createElement("div", {
style: {
...dCard,
padding: '20px 22px',
marginBottom: 24
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 14.5,
fontWeight: 700,
color: 'var(--fg1)',
marginBottom: 12
}
}, t('redeemCodeTitle')), ok ? /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 9,
color: 'var(--success)',
fontWeight: 600,
fontSize: 15,
padding: '6px 0'
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: "check-circle",
size: 20,
color: "var(--success)"
}), t('redeemOk')) : /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 10
}
}, /*#__PURE__*/React.createElement("input", {
value: code,
onChange: e => setCode(e.target.value.toUpperCase()),
placeholder: t('redeemPh'),
style: {
...dInput,
fontFamily: 'var(--font-mono)',
letterSpacing: '0.08em'
}
}), /*#__PURE__*/React.createElement("button", {
onClick: () => code.length >= 4 && setOk(true),
style: {
border: 'none',
borderRadius: 'var(--radius-md)',
padding: '0 22px',
background: 'var(--accent)',
color: '#fff',
fontWeight: 700,
fontSize: 14,
cursor: 'pointer',
flexShrink: 0
}
}, t('redeemBtn')))), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 14.5,
fontWeight: 700,
color: 'var(--fg1)',
marginBottom: 6
}
}, t('buyTitle')), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12.5,
color: 'var(--fg3)',
lineHeight: 1.5,
marginBottom: 14
}
}, t('buySub')), /*#__PURE__*/React.createElement("div", {
style: {
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: 12
}
}, channels.map(c => /*#__PURE__*/React.createElement("button", {
key: c.name,
style: {
display: 'flex',
alignItems: 'center',
gap: 13,
textAlign: 'left',
cursor: 'pointer',
background: c.accent ? 'var(--accent-subtle)' : 'var(--surface)',
border: `1px solid ${c.accent ? 'var(--accent-border)' : 'var(--border)'}`,
borderRadius: 'var(--radius-lg)',
padding: '14px 16px',
boxShadow: 'var(--shadow-sm)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 40,
height: 40,
borderRadius: 'var(--radius-md)',
background: c.accent ? 'var(--accent)' : 'var(--bg-subtle)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: c.icon,
size: 20,
color: c.accent ? '#fff' : 'var(--accent)'
})), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
minWidth: 0
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 14.5,
fontWeight: 600,
color: 'var(--fg1)'
}
}, c.name), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12,
color: 'var(--fg3)',
fontFamily: c.mono ? 'var(--font-mono)' : 'var(--font-sans)',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}
}, c.sub)), /*#__PURE__*/React.createElement(DIcon, {
name: "external-link",
size: 16,
color: "var(--fg3)"
})))));
}
/* ───────── ONBOARDING — window card, 3 screens ───────── */
function DOnboarding({
t,
lang,
dark,
onDone
}) {
const [i, setI] = useStateD(0);
const steps = [{
icon: 'globe',
title: t('ob1Title'),
sub: t('ob1Sub'),
cta: t('obNext')
}, {
icon: 'lock',
title: t('ob2Title'),
sub: t('ob2Sub'),
cta: t('obAllow')
}, {
icon: 'shield-check',
title: t('ob3Title'),
sub: t('ob3Sub'),
cta: t('obStart')
}];
const s = steps[i];
const last = i === steps.length - 1;
return /*#__PURE__*/React.createElement("div", {
"data-theme": dark ? 'dark' : 'light',
style: {
width: 560,
height: 520,
borderRadius: 16,
overflow: 'hidden',
display: 'flex',
flexDirection: 'column',
background: 'var(--bg)',
boxShadow: '0 0 0 1px rgba(0,0,0,0.12), 0 28px 70px rgba(0,0,0,0.4)',
fontFamily: 'var(--font-sans)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '18px 22px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 9
}
}, /*#__PURE__*/React.createElement(DMark, {
size: 24
}), /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-display)',
fontWeight: 700,
fontSize: 15,
color: 'var(--fg1)'
}
}, lang === 'zh' ? '穿山甲' : 'Pangolin')), !last && /*#__PURE__*/React.createElement("button", {
onClick: onDone,
style: {
border: 'none',
background: 'transparent',
cursor: 'pointer',
fontSize: 13,
fontWeight: 600,
color: 'var(--fg3)'
}
}, t('obSkip'))), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
textAlign: 'center',
padding: '0 56px',
gap: 22
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 108,
height: 108,
borderRadius: '50%',
background: 'var(--accent-subtle)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: s.icon,
size: 52,
stroke: 1.6,
color: "var(--accent)"
})), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontSize: 26,
fontWeight: 700,
color: 'var(--fg1)'
}
}, s.title), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 15,
color: 'var(--fg2)',
lineHeight: 1.6,
marginTop: 10,
maxWidth: 380
}
}, s.sub))), /*#__PURE__*/React.createElement("div", {
style: {
padding: '0 40px 36px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 20
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 8
}
}, steps.map((_, k) => /*#__PURE__*/React.createElement("div", {
key: k,
style: {
width: k === i ? 22 : 8,
height: 8,
borderRadius: 999,
background: k === i ? 'var(--accent)' : 'var(--border-strong)',
transition: 'all 220ms var(--ease-out)'
}
}))), /*#__PURE__*/React.createElement("button", {
onClick: () => last ? onDone() : setI(i + 1),
style: {
width: '100%',
maxWidth: 360,
border: 'none',
borderRadius: 'var(--radius-full)',
padding: '13px',
fontFamily: 'var(--font-sans)',
fontWeight: 700,
fontSize: 15,
cursor: 'pointer',
background: 'var(--accent)',
color: 'var(--fg-on-accent)'
}
}, s.cta)));
}
/* ───────── CONTACT US ───────── */
function DContactView({
t,
lang
}) {
const channels = [{
icon: 'send',
name: t('contactTg'),
sub: '@PangolinVPN_bot',
accent: true
}, {
icon: 'message-circle',
name: t('contactLine'),
sub: '@pangolinvpn'
}, {
icon: 'mail',
name: t('contactEmail'),
sub: 'support@pangolin.vpn'
}, {
icon: 'shopping-bag',
name: t('contactStore'),
sub: 'shop.pangolin.vpn'
}];
return /*#__PURE__*/React.createElement("div", {
style: {
padding: '8px 32px 28px',
maxWidth: 680
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 13.5,
color: 'var(--fg2)',
lineHeight: 1.6,
marginBottom: 18
}
}, t('contactIntro')), /*#__PURE__*/React.createElement("div", {
style: {
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: 12,
marginBottom: 22
}
}, channels.map(c => /*#__PURE__*/React.createElement("button", {
key: c.name,
style: {
display: 'flex',
alignItems: 'center',
gap: 13,
textAlign: 'left',
cursor: 'pointer',
background: c.accent ? 'var(--accent-subtle)' : 'var(--surface)',
border: `1px solid ${c.accent ? 'var(--accent-border)' : 'var(--border)'}`,
borderRadius: 'var(--radius-lg)',
padding: '14px 16px',
boxShadow: 'var(--shadow-sm)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 40,
height: 40,
borderRadius: 'var(--radius-md)',
background: c.accent ? 'var(--accent)' : 'var(--bg-subtle)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0
}
}, /*#__PURE__*/React.createElement(DIcon, {
name: c.icon,
size: 20,
color: c.accent ? '#fff' : 'var(--accent)'
})), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
minWidth: 0
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 14.5,
fontWeight: 600,
color: 'var(--fg1)'
}
}, c.name), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12,
color: 'var(--fg3)',
fontFamily: 'var(--font-mono)',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}
}, c.sub)), /*#__PURE__*/React.createElement(DIcon, {
name: "external-link",
size: 16,
color: "var(--fg3)"
})))), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 14
}
}, /*#__PURE__*/React.createElement("div", {
style: {
...dCard,
flex: 1,
padding: '16px 18px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12.5,
fontWeight: 600,
color: 'var(--fg3)'
}
}, t('contactHoursTitle')), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 15,
fontWeight: 600,
color: 'var(--fg1)',
marginTop: 6,
fontFamily: 'var(--font-mono)'
}
}, t('contactHours'))), /*#__PURE__*/React.createElement("div", {
style: {
...dCard,
flex: 2,
padding: '16px 18px',
display: 'flex',
alignItems: 'center',
gap: 14
}
}, /*#__PURE__*/React.createElement("div", {
style: {
flex: 1
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12.5,
fontWeight: 600,
color: 'var(--fg3)'
}
}, t('contactFaqTitle')), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 13,
color: 'var(--fg2)',
marginTop: 5,
lineHeight: 1.5
}
}, t('contactFaq'))), /*#__PURE__*/React.createElement("button", {
style: {
border: '1.5px solid var(--border-strong)',
background: 'transparent',
color: 'var(--fg1)',
fontSize: 13,
fontWeight: 600,
padding: '9px 15px',
borderRadius: 'var(--radius-full)',
cursor: 'pointer',
whiteSpace: 'nowrap',
flexShrink: 0
}
}, t('contactFaqBtn')))));
}
Object.assign(window, {
DAuthFlow,
DOnboarding,
DAccountView,
DPlansView,
DRedeemView,
DContactView
});
})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/desktop/dflows.jsx", error: String((e && e.message) || e) }); }
// ui_kits/desktop/dparts.jsx
try { (() => {
/* dparts.jsx — 穿山甲 VPN desktop · atoms (inline Lucide paths) */
const DLUCIDE = {
power: '<path d="M12 2v10"/><path d="M18.36 6.64a9 9 0 1 1-12.73 0"/>',
'shield-check': '<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/><path d="m9 12 2 2 4-4"/>',
globe: '<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>',
'loader-circle': '<path d="M21 12a9 9 0 1 1-6.219-8.56"/>',
'chart-no-axes-column': '<line x1="18" x2="18" y1="20" y2="10"/><line x1="12" x2="12" y1="20" y2="4"/><line x1="6" x2="6" y1="20" y2="14"/>',
settings: '<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/>',
check: '<path d="M20 6 9 17l-5-5"/>',
zap: '<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>',
'arrow-down': '<path d="M12 5v14"/><path d="m19 12-7 7-7-7"/>',
'arrow-up': '<path d="M12 19V5"/><path d="m5 12 7-7 7 7"/>',
search: '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',
moon: '<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>',
sun: '<circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/>',
crown: '<path d="M11.562 3.266a.5.5 0 0 1 .876 0L15.39 8.87a1 1 0 0 0 1.516.294L21.183 5.5a.5.5 0 0 1 .798.519l-2.834 10.246a1 1 0 0 1-.956.734H5.81a1 1 0 0 1-.957-.734L2.02 6.02a.5.5 0 0 1 .798-.519l4.276 3.664a1 1 0 0 0 1.516-.294z"/><path d="M5 21h14"/>',
mail: '<rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/>',
lock: '<rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
'check-circle': '<path d="M21.801 10A10 10 0 1 1 17 3.335"/><path d="m9 11 3 3L22 4"/>',
'arrow-left': '<path d="m12 19-7-7 7-7"/><path d="M19 12H5"/>',
'chevron-right': '<path d="m9 18 6-6-6-6"/>',
send: '<path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/><path d="m21.854 2.147-10.94 10.939"/>',
'message-circle': '<path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/>',
'external-link': '<path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>',
'shopping-bag': '<path d="M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4Z"/><path d="M3 6h18"/><path d="M16 10a4 4 0 0 1-8 0"/>',
laptop: '<path d="M18 5a2 2 0 0 1 2 2v8.526a2 2 0 0 0 .212.894l1.575 3.15A.5.5 0 0 1 21.342 21H2.658a.5.5 0 0 1-.445-.724l1.575-3.15A2 2 0 0 0 4 16.526V7a2 2 0 0 1 2-2z"/><path d="M20.054 15.987H3.946"/>',
smartphone: '<rect width="14" height="20" x="5" y="2" rx="2" ry="2"/><path d="M12 18h.01"/>',
'monitor-smartphone': '<path d="M18 8V5a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h8"/><path d="M10 19v-3.96 3.15"/><path d="M7 19h5"/><rect width="6" height="10" x="16" y="12" rx="2"/>',
'log-out': '<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" x2="9" y1="12" y2="12"/>',
ticket: '<path d="M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"/><path d="M13 5v2"/><path d="M13 17v2"/><path d="M13 11v2"/>',
clock: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
'play-circle': '<circle cx="12" cy="12" r="10"/><polygon points="10 8 16 12 10 16 10 8"/>',
compass: '<path d="m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z"/><circle cx="12" cy="12" r="10"/>',
'refresh-cw': '<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M3 21v-5h5"/>',
'credit-card': '<rect width="20" height="14" x="2" y="5" rx="2"/><line x1="2" x2="22" y1="10" y2="10"/>',
user: '<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>'
};
function DIcon({
name,
size = 20,
stroke = 2,
color = 'currentColor',
style = {}
}) {
return /*#__PURE__*/React.createElement("svg", {
width: size,
height: size,
viewBox: "0 0 24 24",
fill: "none",
stroke: color,
strokeWidth: stroke,
strokeLinecap: "round",
strokeLinejoin: "round",
style: {
display: 'block',
flexShrink: 0,
...style
},
dangerouslySetInnerHTML: {
__html: DLUCIDE[name] || ''
}
});
}
function DMark({
size = 28,
color = 'var(--accent)'
}) {
return /*#__PURE__*/React.createElement("svg", {
width: size,
height: size,
viewBox: "0 0 96 96",
fill: "none",
style: {
display: 'block'
}
}, /*#__PURE__*/React.createElement("g", {
transform: "translate(96,0) scale(-1,1)"
}, /*#__PURE__*/React.createElement("path", {
d: "M16 59 Q17 51 26 50 Q34 31 45 30 Q55 30 62 37 Q69 41 75 45 Q85 49 89 44 Q92 50 84 52 Q75 53 67 53 Q66 62 60 62 L56 62 Q54 55 49 55 Q47 62 37 62 L33 62 Q31 56 26 56 Q20 59 16 59 Z",
fill: color
}), /*#__PURE__*/React.createElement("g", {
stroke: "#fff",
strokeWidth: "2.4",
strokeLinecap: "round",
strokeLinejoin: "round",
fill: "none",
opacity: "0.5"
}, /*#__PURE__*/React.createElement("path", {
d: "M26 50 Q34 43 42 50"
}), /*#__PURE__*/React.createElement("path", {
d: "M36 48 Q44 41 52 48"
}), /*#__PURE__*/React.createElement("path", {
d: "M46 48 Q54 41 62 49"
})), /*#__PURE__*/React.createElement("circle", {
cx: "23",
cy: "55",
r: "2.2",
fill: "#fff",
opacity: "0.85"
})));
}
function DCC({
code,
active,
size = 34
}) {
return /*#__PURE__*/React.createElement("div", {
style: {
width: size,
height: size,
borderRadius: 'var(--radius-md)',
flexShrink: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontFamily: 'var(--font-mono)',
fontSize: size * 0.31,
fontWeight: 600,
background: active ? 'var(--accent)' : 'var(--bg-subtle)',
color: active ? '#fff' : 'var(--fg2)'
}
}, code);
}
function DSignal({
ping
}) {
const lvl = ping < 40 ? 3 : ping < 90 ? 2 : 1;
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 2,
alignItems: 'flex-end',
height: 13
}
}, [4, 8, 13].map((h, i) => /*#__PURE__*/React.createElement("div", {
key: i,
style: {
width: 3,
height: h,
borderRadius: 1,
background: i < lvl ? 'var(--success)' : 'var(--border-strong)'
}
})));
}
const DSTRINGS = {
connect: {
zh: '连接',
en: 'Connect'
},
servers: {
zh: '选择节点',
en: 'Choose server'
},
stats: {
zh: '统计',
en: 'Stats'
},
settings: {
zh: '设置',
en: 'Settings'
},
navConnect: {
zh: '连接',
en: 'Connect'
},
navServers: {
zh: '节点',
en: 'Servers'
},
navStats: {
zh: '统计',
en: 'Stats'
},
navSettings: {
zh: '设置',
en: 'Settings'
},
proMember: {
zh: 'PRO 会员',
en: 'PRO member'
},
until: {
zh: '至 2026-12-31',
en: 'until 2026-12-31'
},
connected: {
zh: '已连接',
en: 'Connected'
},
notConnected: {
zh: '未连接',
en: 'Not connected'
},
capOff: {
zh: '未连接',
en: 'Not connected'
},
subOff: {
zh: '点击下方按钮开始保护',
en: 'Tap the button below to start'
},
capConnecting: {
zh: '连接中…',
en: 'Connecting…'
},
subConnecting: {
zh: '正在建立加密隧道',
en: 'Establishing encrypted tunnel'
},
capOn: {
zh: '已连接',
en: 'Connected'
},
subOn: {
zh: '你的网络已加密保护',
en: 'Your network is encrypted'
},
connectNow: {
zh: '点击连接',
en: 'CONNECT'
},
search: {
zh: '搜索国家 / 城市',
en: 'Search country / city'
},
trafficMonth: {
zh: '本月流量',
en: 'Traffic'
},
avgPing: {
zh: '平均延迟',
en: 'Avg ping'
},
durMonth: {
zh: '本月时长',
en: 'Time'
},
weekTraffic: {
zh: '本周流量 (GB)',
en: 'This week (GB)'
},
autostart: {
zh: '开机自启动',
en: 'Launch at login'
},
autostartSub: {
zh: '登录系统时自动启动穿山甲',
en: 'Start Pangolin when you log in'
},
smartRoute: {
zh: '智能分流',
en: 'Smart routing'
},
smartRouteSub: {
zh: '海外应用加速,本地直连',
en: 'Accelerate overseas apps, keep local direct'
},
killSwitch: {
zh: 'Kill Switch',
en: 'Kill Switch'
},
killSwitchSub: {
zh: '断线时阻断网络,防止 IP 泄露',
en: 'Block traffic if the VPN drops'
},
language: {
zh: '语言',
en: 'Language'
},
darkAppearance: {
zh: '深色外观',
en: 'Dark appearance'
},
stateOn: {
zh: '已开启',
en: 'On'
},
followLight: {
zh: '跟随浅色',
en: 'Off'
},
protocol: {
zh: '协议',
en: 'Protocol'
},
version: {
zh: '版本',
en: 'Version'
},
days: {
zh: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
en: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
/* 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'
},
authSidePitch: {
zh: '一个账户,全平台同步。守护你的每一次连接。',
en: 'One account, every device. Protecting every connection.'
},
/* 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: 'Redeem'
},
redeemOk: {
zh: '激活成功 · PRO 已开通',
en: 'Activated · PRO unlocked'
},
buyTitle: {
zh: '购买渠道',
en: 'Where to buy'
},
buySub: {
zh: '客户端内不支持直接支付。请通过以下渠道获取激活码:',
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'
},
smartSelect: {
zh: '智能选择',
en: 'Smart select'
},
smartSub: {
zh: '根据当前网络环境,自动选择最优节点',
en: 'Picks the best node for your network'
},
recommended: {
zh: '推荐',
en: 'Recommended'
},
twoFA: {
zh: '双重认证',
en: 'Two-factor auth'
},
twoFASub: {
zh: 'PRO 专享 · 登录需身份验证器动态码',
en: 'PRO only · TOTP code at login'
},
ucEntry: {
zh: 'Web 用户中心',
en: 'Web account center'
},
ucEntrySub: {
zh: '订阅导入 · 邀请返利',
en: 'Subscription import · referral'
},
ucOpen: {
zh: '打开',
en: 'Open'
},
/* quota + free (口径见 CLAUDE.md §7,与移动端一致) */
quotaToday: {
zh: '今日剩余',
en: 'Left today'
},
minutes: {
zh: '分钟',
en: 'min'
},
quotaFree: {
zh: '免费版 · 每日 10 分钟',
en: 'Free · 10 min/day'
},
watchAd: {
zh: '看广告开始使用',
en: 'Watch ad to start'
},
adUnlocked: {
zh: '已解锁 · 今日可用',
en: 'Unlocked for today'
},
freePlanName: {
zh: '免费版',
en: 'Free'
},
freeDemo: {
zh: '演示:免费版视角',
en: 'Demo: free-tier view'
},
freeDemoSub: {
zh: '切换连接页的额度展示与套餐横幅',
en: 'Toggles quota UI & plan banner'
},
checkUpdate: {
zh: '检查更新',
en: 'Check for updates'
},
replayOnboarding: {
zh: '查看首次引导',
en: 'View onboarding'
},
replayOnboardingSub: {
zh: '演示用 · 重看 3 屏引导',
en: 'Demo · replay the 3 intro screens'
},
/* 2FA login step (与用户中心一致) */
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'
},
chTgSub: {
zh: '@PangolinVPN_bot',
en: '@PangolinVPN_bot'
},
chLineSub: {
zh: '@pangolinvpn',
en: '@pangolinvpn'
},
chEmail: {
zh: '邮箱客服',
en: 'Email support'
},
chEmailSub: {
zh: 'buy@pangolin.vpn',
en: 'buy@pangolin.vpn'
},
/* account management (integrated dashboard) */
navAccount: {
zh: '账户',
en: 'Account'
},
curPlan: {
zh: '当前套餐',
en: 'Current plan'
},
renew: {
zh: '续费 / 升级',
en: 'Renew / Upgrade'
},
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 your 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']
},
/* account info */
accInfoTitle: {
zh: '账户信息',
en: 'Account info'
},
accEmail: {
zh: '邮箱',
en: 'Email'
},
accPassword: {
zh: '密码',
en: 'Password'
},
accChange: {
zh: '修改',
en: 'Change'
},
accUsage: {
zh: '本月用量',
en: 'Used this month'
},
accSignOut: {
zh: '退出登录',
en: 'Sign out'
},
backToAccount: {
zh: '返回账户',
en: 'Back to account'
},
/* demo replay */
replayAuth: {
zh: '查看登录 / 注册流程',
en: 'View login / sign-up'
},
replayAuthSub: {
zh: '演示用 · 退出到登录页',
en: 'Demo · back to login'
},
replayOnboarding: {
zh: '查看首次引导',
en: 'View onboarding'
},
replayOnboardingSub: {
zh: '演示用 · 重看引导页',
en: 'Demo · replay intro'
},
/* 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: 'Your system 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.'
},
/* contact us */
navContact: {
zh: '联系我们',
en: 'Contact us'
},
contactIntro: {
zh: '遇到问题?通过以下任一渠道联系我们,通常数分钟内回复。',
en: 'Need help? Reach us through any channel below — we usually reply within minutes.'
},
contactTg: {
zh: 'Telegram 客服',
en: 'Telegram support'
},
contactLine: {
zh: 'LINE 客服',
en: 'LINE support'
},
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)'
},
contactFaqTitle: {
zh: '常见问题',
en: 'FAQ'
},
contactFaq: {
zh: '连不上 / 兑换码无效 / 多设备登录,先查阅帮助中心。',
en: 'Connection issues, invalid codes, multi-device — check the Help Center first.'
},
contactFaqBtn: {
zh: '打开帮助中心',
en: 'Open Help Center'
}
};
function dT(lang) {
return k => {
const v = DSTRINGS[k];
return v ? v[lang] || k : k;
};
}
function dName(s, lang) {
return lang === 'zh' ? s.name : s.en;
}
function dSub(s, lang) {
if (lang === 'zh') return s.tag || s.en;
return s.tag === '流媒体优化' ? 'Streaming' : s.tag === 'P2P' ? 'P2P' : s.name;
}
Object.assign(window, {
DIcon,
DMark,
DCC,
DSignal,
DSTRINGS,
dT,
dName,
dSub,
DSERVERS: [{
code: 'HK',
name: '香港 · 流媒体',
en: 'Hong Kong',
ping: 18,
tag: '流媒体优化'
}, {
code: 'JP',
name: '日本 东京',
en: 'Tokyo',
ping: 32,
tag: 'P2P'
}, {
code: 'TW',
name: '台湾 台北',
en: 'Taipei',
ping: 28,
tag: ''
}, {
code: 'SG',
name: '新加坡',
en: 'Singapore',
ping: 54,
tag: 'P2P'
}, {
code: 'KR',
name: '韩国 首尔',
en: 'Seoul',
ping: 41,
tag: ''
}, {
code: 'US',
name: '美国 洛杉矶',
en: 'Los Angeles',
ping: 146,
tag: '流媒体优化'
}, {
code: 'DE',
name: '德国 法兰克福',
en: 'Frankfurt',
ping: 198,
tag: ''
}, {
code: 'UK',
name: '英国 伦敦',
en: 'London',
ping: 210,
tag: ''
}]
});
})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/desktop/dparts.jsx", error: String((e && e.message) || e) }); }
// ui_kits/desktop/macos-window.jsx
try { (() => {
/* BEGIN USAGE */
// MacOS.jsx — Simplified macOS Tahoe (Liquid Glass) window
// Based on the macOS Tahoe UI Kit. No image assets, no dependencies.
// Exports (to window): MacWindow, MacSidebar, MacSidebarItem, MacSidebarHeader, MacToolbar, MacGlass, MacTrafficLights
//
// Usage — wrap your app content in <MacWindow> to get the window chrome
// (traffic lights + titlebar). Props: width, height, title, sidebar (pass a
// <MacSidebar> element); compose MacToolbar/MacGlass inside as needed:
//
// <MacWindow width={980} height={620} title="Documents"
// sidebar={<MacSidebar>…</MacSidebar>}>
// ...your app content...
// </MacWindow>
/* END USAGE */
const MAC_FONT = '-apple-system, BlinkMacSystemFont, "SF Pro", "Helvetica Neue", sans-serif';
// ─────────────────────────────────────────────────────────────
// Liquid glass primitive — blur + white tint + inset highlight
// ─────────────────────────────────────────────────────────────
function MacGlass({
children,
radius = 296,
dark = false,
style = {}
}) {
return /*#__PURE__*/React.createElement("div", {
style: {
position: 'relative',
borderRadius: radius,
...style
}
}, /*#__PURE__*/React.createElement("div", {
style: {
position: 'absolute',
inset: 0,
borderRadius: radius,
background: dark ? 'rgba(255,255,255,0.08)' : 'rgba(255,255,255,0.35)',
backdropFilter: 'blur(40px) saturate(180%)',
WebkitBackdropFilter: 'blur(40px) saturate(180%)',
border: dark ? '0.5px solid rgba(255,255,255,0.12)' : '0.5px solid rgba(255,255,255,0.6)',
boxShadow: dark ? '0 8px 40px rgba(0,0,0,0.2)' : '0 8px 40px rgba(0,0,0,0.08), inset 0 1px 0 rgba(255,255,255,0.4)'
}
}), /*#__PURE__*/React.createElement("div", {
style: {
position: 'relative',
zIndex: 1
}
}, children));
}
// ─────────────────────────────────────────────────────────────
// Traffic lights (14px, Tahoe colors)
// ─────────────────────────────────────────────────────────────
function MacTrafficLights({
style = {}
}) {
const dot = bg => /*#__PURE__*/React.createElement("div", {
style: {
width: 14,
height: 14,
borderRadius: '50%',
background: bg,
border: '0.5px solid rgba(0,0,0,0.1)'
}
});
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 9,
alignItems: 'center',
padding: 1,
...style
}
}, dot('#ff736a'), dot('#febc2e'), dot('#19c332'));
}
// ─────────────────────────────────────────────────────────────
// Toolbar — title + single glass pill icon
// ─────────────────────────────────────────────────────────────
function MacToolbar({
title = 'Folder'
}) {
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 8,
alignItems: 'center',
padding: 8,
flexShrink: 0
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: MAC_FONT,
fontSize: 15,
fontWeight: 700,
color: 'rgba(0,0,0,0.85)',
whiteSpace: 'nowrap',
paddingLeft: 8
}
}, title), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1
}
}), /*#__PURE__*/React.createElement(MacGlass, null, /*#__PURE__*/React.createElement("div", {
style: {
width: 36,
height: 36,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 14,
height: 14,
borderRadius: '50%',
background: '#4c4c4c',
opacity: 0.4
}
}))), /*#__PURE__*/React.createElement(MacGlass, null, /*#__PURE__*/React.createElement("div", {
style: {
width: 140,
height: 36,
display: 'flex',
alignItems: 'center',
gap: 6,
padding: '0 12px'
}
}, /*#__PURE__*/React.createElement("svg", {
width: "13",
height: "13",
viewBox: "0 0 13 13",
fill: "none"
}, /*#__PURE__*/React.createElement("circle", {
cx: "5.5",
cy: "5.5",
r: "4",
stroke: "#727272",
strokeWidth: "1.5"
}), /*#__PURE__*/React.createElement("path", {
d: "M8.5 8.5l3 3",
stroke: "#727272",
strokeWidth: "1.5",
strokeLinecap: "round"
})), /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: MAC_FONT,
fontSize: 13,
fontWeight: 500,
color: '#727272'
}
}, "Search"))));
}
// ─────────────────────────────────────────────────────────────
// Sidebar — frosted glass panel floating inside the window
// ─────────────────────────────────────────────────────────────
function MacSidebarItem({
label,
selected = false
}) {
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 6,
height: 24,
padding: '4px 10px 4px 6px',
margin: '0 10px',
borderRadius: 8,
position: 'relative',
fontFamily: MAC_FONT,
fontSize: 11,
fontWeight: 500
}
}, selected && /*#__PURE__*/React.createElement("div", {
style: {
position: 'absolute',
inset: 0,
borderRadius: 8,
background: 'rgba(0,0,0,0.11)',
mixBlendMode: 'multiply'
}
}), /*#__PURE__*/React.createElement("div", {
style: {
width: 14,
height: 14,
borderRadius: '50%',
background: selected ? '#007aff' : 'rgba(0,0,0,0.4)',
opacity: selected ? 1 : 0.5,
flexShrink: 0,
position: 'relative'
}
}), /*#__PURE__*/React.createElement("span", {
style: {
color: 'rgba(0,0,0,0.85)',
position: 'relative'
}
}, label));
}
function MacSidebar({
children
}) {
return /*#__PURE__*/React.createElement("div", {
style: {
width: 220,
height: '100%',
padding: 8,
flexShrink: 0,
position: 'relative',
display: 'flex',
flexDirection: 'column'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
position: 'absolute',
inset: 8,
borderRadius: 18,
background: 'rgba(210,225,245,0.45)',
backdropFilter: 'blur(50px) saturate(200%)',
WebkitBackdropFilter: 'blur(50px) saturate(200%)',
border: '0.5px solid rgba(255,255,255,0.5)',
boxShadow: '0 8px 40px rgba(0,0,0,0.10), inset 0 1px 0 rgba(255,255,255,0.35)'
}
}), /*#__PURE__*/React.createElement("div", {
style: {
position: 'relative',
zIndex: 1,
padding: '10px 0',
display: 'flex',
flexDirection: 'column',
gap: 2
}
}, /*#__PURE__*/React.createElement("div", {
style: {
height: 32,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 10px',
marginBottom: 4
}
}, /*#__PURE__*/React.createElement(MacTrafficLights, null)), children));
}
function MacSidebarHeader({
title
}) {
return /*#__PURE__*/React.createElement("div", {
style: {
padding: '14px 18px 5px',
fontFamily: MAC_FONT,
fontSize: 11,
fontWeight: 700,
color: 'rgba(0,0,0,0.5)'
}
}, title);
}
// ─────────────────────────────────────────────────────────────
// Window — r:26, big shadow, sidebar + toolbar + content
// ─────────────────────────────────────────────────────────────
function MacWindow({
width = 900,
height = 600,
title = 'Folder',
sidebar,
children
}) {
return /*#__PURE__*/React.createElement("div", {
style: {
width,
height,
borderRadius: 26,
overflow: 'hidden',
background: '#fff',
boxShadow: '0 0 0 1px rgba(0,0,0,0.23), 0 16px 48px rgba(0,0,0,0.35)',
display: 'flex',
position: 'relative',
fontFamily: MAC_FONT
}
}, /*#__PURE__*/React.createElement(MacSidebar, null, sidebar), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
display: 'flex',
flexDirection: 'column'
}
}, /*#__PURE__*/React.createElement(MacToolbar, {
title: title
}), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
overflow: 'auto',
padding: '4px 8px'
}
}, children)));
}
Object.assign(window, {
MacWindow,
MacSidebar,
MacSidebarItem,
MacSidebarHeader,
MacToolbar,
MacGlass,
MacTrafficLights
});
})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/desktop/macos-window.jsx", error: String((e && e.message) || e) }); }
// ui_kits/mobile/flows.jsx
try { (() => {
/* flows.jsx — 穿山甲 VPN mobile · auth, onboarding, redeem/buy */
const {
useState: useStateF
} = React;
/* shared field */
function Field({
icon,
label,
children
}) {
return /*#__PURE__*/React.createElement("label", {
style: {
display: 'block'
}
}, /*#__PURE__*/React.createElement("span", {
style: {
display: 'block',
fontSize: 12,
fontWeight: 600,
color: 'var(--fg2)',
marginBottom: 7
}
}, label), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 10,
background: 'var(--surface)',
border: '1.5px solid var(--border-strong)',
borderRadius: 'var(--radius-md)',
padding: '12px 14px'
}
}, icon && /*#__PURE__*/React.createElement(Icon, {
name: icon,
size: 18,
color: "var(--fg3)"
}), children));
}
const inputStyle = {
border: 'none',
background: 'transparent',
outline: 'none',
flex: 1,
fontFamily: 'var(--font-sans)',
fontSize: 15,
color: 'var(--fg1)',
minWidth: 0
};
/* ───────── AUTH (login + email-code register) ───────── */
function AuthFlow({
t,
lang,
onDone
}) {
const [mode, setMode] = useStateF('login'); // login | register
const [step, setStep] = useStateF(0); // register: 0 email+code, 1 set password
const [email, setEmail] = useStateF('');
const [code, setCode] = useStateF('');
const [pw, setPw] = useStateF('');
const [sent, setSent] = useStateF(false);
const [totp, setTotp] = useStateF(false); // 2FA step after login (PRO)
const [otp, setOtp] = useStateF('');
const valid = /\S+@\S+\.\S+/.test(email);
const tab = (id, label) => /*#__PURE__*/React.createElement("button", {
onClick: () => {
setMode(id);
setStep(0);
setSent(false);
},
style: {
flex: 1,
border: 'none',
cursor: 'pointer',
background: 'transparent',
padding: '10px 0',
fontFamily: 'var(--font-sans)',
fontSize: 15,
fontWeight: mode === id ? 700 : 500,
color: mode === id ? 'var(--fg1)' : 'var(--fg3)',
borderBottom: mode === id ? '2px solid var(--accent)' : '2px solid transparent'
}
}, label);
const primaryBtn = (label, onClick, enabled) => /*#__PURE__*/React.createElement("button", {
onClick: enabled ? onClick : undefined,
disabled: !enabled,
style: {
width: '100%',
border: 'none',
borderRadius: 'var(--radius-full)',
padding: '14px',
fontFamily: 'var(--font-sans)',
fontWeight: 700,
fontSize: 15,
cursor: enabled ? 'pointer' : 'not-allowed',
background: 'var(--accent)',
color: 'var(--fg-on-accent)',
opacity: enabled ? 1 : 0.45,
marginTop: 4
}
}, label);
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
height: '100%',
padding: '0 28px',
background: 'var(--bg)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 12,
paddingTop: 18,
paddingBottom: 28
}
}, /*#__PURE__*/React.createElement(Mark, {
size: 56
}), /*#__PURE__*/React.createElement("div", {
style: {
textAlign: 'center'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontWeight: 700,
fontSize: 24,
color: 'var(--fg1)'
}
}, t('brand')), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 13,
color: 'var(--accent)',
fontWeight: 600,
letterSpacing: '0.04em',
marginTop: 4
}
}, t('authTagline')))), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
borderBottom: '1px solid var(--border)',
marginBottom: 22
}
}, tab('login', t('tabLogin')), tab('register', t('tabRegister'))), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
gap: 16,
flex: 1
}
}, mode === 'login' && !totp && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Field, {
icon: "mail",
label: t('emailLabel')
}, /*#__PURE__*/React.createElement("input", {
style: inputStyle,
value: email,
onChange: e => setEmail(e.target.value),
placeholder: t('emailPh')
})), /*#__PURE__*/React.createElement(Field, {
icon: "lock",
label: t('pwLabel')
}, /*#__PURE__*/React.createElement("input", {
style: inputStyle,
type: "password",
value: pw,
onChange: e => setPw(e.target.value),
placeholder: t('pwPh')
})), /*#__PURE__*/React.createElement("div", {
style: {
textAlign: 'right',
marginTop: -6
}
}, /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 13,
color: 'var(--accent)',
fontWeight: 600
}
}, t('forgotPw'))), primaryBtn(t('doLogin'), () => setTotp(true), valid && pw.length > 0)), mode === 'login' && totp && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 8
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "shield-check",
size: 19,
color: "var(--accent)"
}), /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-display)',
fontWeight: 700,
fontSize: 18,
color: 'var(--fg1)'
}
}, t('twoFATitle')), /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 10,
fontWeight: 700,
color: 'var(--accent)',
background: 'var(--accent-subtle)',
border: '1px solid var(--accent-border)',
padding: '2px 8px',
borderRadius: 999
}
}, "PRO")), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12.5,
color: 'var(--fg3)',
lineHeight: 1.6,
marginTop: -6
}
}, t('twoFAHint')), /*#__PURE__*/React.createElement(Field, {
icon: "shield-check",
label: t('twoFATitle')
}, /*#__PURE__*/React.createElement("input", {
style: {
...inputStyle,
fontFamily: 'var(--font-mono)',
fontSize: 20,
letterSpacing: '0.5em',
textAlign: 'center'
},
value: otp,
onChange: e => setOtp(e.target.value.replace(/\D/g, '').slice(0, 6)),
placeholder: "\xB7\xB7\xB7\xB7\xB7\xB7"
})), primaryBtn(t('twoFAConfirm'), onDone, otp.length === 6), /*#__PURE__*/React.createElement("div", {
onClick: () => setTotp(false),
style: {
textAlign: 'center',
fontSize: 12.5,
color: 'var(--fg3)',
cursor: 'pointer'
}
}, t('twoFABack'))), mode === 'register' && step === 0 && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Field, {
icon: "mail",
label: t('emailLabel')
}, /*#__PURE__*/React.createElement("input", {
style: inputStyle,
value: email,
onChange: e => setEmail(e.target.value),
placeholder: t('emailPh')
}), /*#__PURE__*/React.createElement("button", {
onClick: () => valid && setSent(true),
style: {
border: 'none',
background: 'transparent',
cursor: valid ? 'pointer' : 'default',
color: valid ? 'var(--accent)' : 'var(--fg3)',
fontWeight: 700,
fontSize: 13,
whiteSpace: 'nowrap',
flexShrink: 0
}
}, sent ? t('resend') : t('sendCode'))), sent && /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12,
color: 'var(--success)',
marginTop: -8,
display: 'flex',
alignItems: 'center',
gap: 6
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "check-circle",
size: 14,
color: "var(--success)"
}), t('codeSentTo'), " ", email), /*#__PURE__*/React.createElement(Field, {
icon: "shield-check",
label: t('codeLabel')
}, /*#__PURE__*/React.createElement("input", {
style: {
...inputStyle,
fontFamily: 'var(--font-mono)',
letterSpacing: '0.3em'
},
value: code,
onChange: e => setCode(e.target.value.replace(/\D/g, '').slice(0, 6)),
placeholder: "\xB7\xB7\xB7\xB7\xB7\xB7"
})), primaryBtn(t('doNext'), () => setStep(1), sent && code.length === 6)), mode === 'register' && step === 1 && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 13,
color: 'var(--fg2)',
display: 'flex',
alignItems: 'center',
gap: 7,
marginBottom: -4
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "check-circle",
size: 15,
color: "var(--success)"
}), email), /*#__PURE__*/React.createElement(Field, {
icon: "lock",
label: t('pwLabel')
}, /*#__PURE__*/React.createElement("input", {
style: inputStyle,
type: "password",
value: pw,
onChange: e => setPw(e.target.value),
placeholder: t('setPwPh')
})), primaryBtn(t('doCreate'), onDone, pw.length >= 6))), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 11,
color: 'var(--fg3)',
textAlign: 'center',
lineHeight: 1.5,
padding: '18px 8px 24px'
}
}, t('tos')));
}
/* ───────── ONBOARDING (3 screens) ───────── */
function Onboarding({
t,
lang,
onDone
}) {
const [i, setI] = useStateF(0);
const slides = [{
icon: 'globe',
tint: 'var(--accent)',
bg: 'var(--accent-subtle)',
title: t('ob1Title'),
sub: t('ob1Sub'),
cta: t('obNext')
}, {
icon: 'shield',
tint: 'var(--accent)',
bg: 'var(--accent-subtle)',
title: t('ob2Title'),
sub: t('ob2Sub'),
cta: t('obAllow')
}, {
icon: 'shield-check',
tint: 'var(--success)',
bg: 'var(--success-subtle)',
title: t('ob3Title'),
sub: t('ob3Sub'),
cta: t('obStart')
}];
const s = slides[i];
const next = () => i < 2 ? setI(i + 1) : onDone();
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
height: '100%',
padding: '0 28px',
background: 'var(--bg)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
justifyContent: 'flex-end',
paddingTop: 10
}
}, /*#__PURE__*/React.createElement("button", {
onClick: onDone,
style: {
border: 'none',
background: 'transparent',
cursor: 'pointer',
color: 'var(--fg3)',
fontSize: 14,
fontWeight: 600,
padding: 8
}
}, t('obSkip'))), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 28,
textAlign: 'center'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 120,
height: 120,
borderRadius: '50%',
background: s.bg,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}
}, /*#__PURE__*/React.createElement(Icon, {
name: s.icon,
size: 52,
stroke: 1.6,
color: s.tint
})), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontWeight: 700,
fontSize: 26,
color: 'var(--fg1)'
}
}, s.title), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 15,
color: 'var(--fg2)',
lineHeight: 1.6,
marginTop: 12,
maxWidth: 280
}
}, s.sub))), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
justifyContent: 'center',
gap: 8,
marginBottom: 22
}
}, slides.map((_, k) => /*#__PURE__*/React.createElement("div", {
key: k,
style: {
width: k === i ? 22 : 7,
height: 7,
borderRadius: 999,
background: k === i ? 'var(--accent)' : 'var(--border-strong)',
transition: 'all 220ms var(--ease-out)'
}
}))), /*#__PURE__*/React.createElement("button", {
onClick: next,
style: {
width: '100%',
border: 'none',
borderRadius: 'var(--radius-full)',
padding: '15px',
fontFamily: 'var(--font-sans)',
fontWeight: 700,
fontSize: 15,
cursor: 'pointer',
background: 'var(--accent)',
color: 'var(--fg-on-accent)',
marginBottom: 26
}
}, s.cta));
}
/* ───────── REDEEM & BUY ───────── */
function RedeemScreen({
t,
lang,
onBack
}) {
const [code, setCode] = useStateF('');
const [ok, setOk] = useStateF(false);
const channels = [{
icon: 'shopping-bag',
name: t('chStore'),
sub: t('chStoreSub'),
accent: true
}, {
icon: 'credit-card',
name: 'USDT (TRC20)',
sub: t('chUsdtSub')
}, {
icon: 'send',
name: 'Telegram',
sub: t('chTgSub')
}, {
icon: 'message-circle',
name: 'LINE',
sub: t('chLineSub')
}, {
icon: 'mail',
name: t('chEmail'),
sub: t('chEmailSub')
}];
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
height: '100%'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 8,
padding: '6px 16px 14px'
}
}, /*#__PURE__*/React.createElement("button", {
onClick: onBack,
style: {
border: 'none',
background: 'transparent',
cursor: 'pointer',
padding: 6,
display: 'flex'
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "arrow-left",
size: 22,
color: "var(--fg1)"
})), /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-display)',
fontWeight: 700,
fontSize: 18,
color: 'var(--fg1)',
whiteSpace: 'nowrap'
}
}, t('redeemTitle'))), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
overflow: 'auto',
padding: '0 20px 20px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
background: 'var(--surface)',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-lg)',
boxShadow: 'var(--shadow-sm)',
padding: 18,
marginBottom: 22
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 14,
fontWeight: 700,
color: 'var(--fg1)',
marginBottom: 12
}
}, t('redeemCodeTitle')), ok ? /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 9,
color: 'var(--success)',
fontWeight: 600,
fontSize: 15,
padding: '6px 0'
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "check-circle",
size: 20,
color: "var(--success)"
}), t('redeemOk')) : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 10
}
}, /*#__PURE__*/React.createElement("input", {
value: code,
onChange: e => setCode(e.target.value.toUpperCase()),
placeholder: t('redeemPh'),
style: {
flex: 1,
minWidth: 0,
border: '1.5px solid var(--border-strong)',
borderRadius: 'var(--radius-md)',
padding: '12px 14px',
fontFamily: 'var(--font-mono)',
fontSize: 14,
letterSpacing: '0.08em',
color: 'var(--fg1)',
background: 'var(--bg)',
outline: 'none'
}
}), /*#__PURE__*/React.createElement("button", {
onClick: () => code.length >= 4 && setOk(true),
style: {
border: 'none',
borderRadius: 'var(--radius-md)',
padding: '0 20px',
background: 'var(--accent)',
color: '#fff',
fontWeight: 700,
fontSize: 14,
cursor: 'pointer',
flexShrink: 0
}
}, t('redeemBtn'))))), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 14,
fontWeight: 700,
color: 'var(--fg1)',
marginBottom: 6
}
}, t('buyTitle')), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12.5,
color: 'var(--fg3)',
lineHeight: 1.5,
marginBottom: 14
}
}, t('buySub')), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
gap: 10
}
}, channels.map(c => /*#__PURE__*/React.createElement("button", {
key: c.name,
style: {
display: 'flex',
alignItems: 'center',
gap: 13,
width: '100%',
textAlign: 'left',
cursor: 'pointer',
background: c.accent ? 'var(--accent-subtle)' : 'var(--surface)',
border: `1px solid ${c.accent ? 'var(--accent-border)' : 'var(--border)'}`,
borderRadius: 'var(--radius-lg)',
padding: '14px 16px',
boxShadow: 'var(--shadow-sm)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 40,
height: 40,
borderRadius: 'var(--radius-md)',
background: c.accent ? 'var(--accent)' : 'var(--bg-subtle)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0
}
}, /*#__PURE__*/React.createElement(Icon, {
name: c.icon,
size: 20,
color: c.accent ? '#fff' : 'var(--accent)'
})), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 15,
fontWeight: 600,
color: 'var(--fg1)'
}
}, c.name), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12.5,
color: 'var(--fg3)',
fontFamily: c.icon === 'send' || c.icon === 'mail' || c.icon === 'message-circle' ? 'var(--font-mono)' : 'var(--font-sans)'
}
}, c.sub)), /*#__PURE__*/React.createElement(Icon, {
name: "external-link",
size: 17,
color: "var(--fg3)"
}))))));
}
/* ───────── PLANS (shown on upgrade) ───────── */
function PlansScreen({
t,
lang,
onBack,
onChoose
}) {
const plans = [{
id: 'free',
name: t('free'),
price: '¥0',
feats: t('feFree'),
cta: t('current'),
variant: 'cur'
}, {
id: 'pro',
name: t('proPlan'),
price: '¥25',
feats: t('fePro'),
cta: t('upgrade'),
variant: 'pro'
}, {
id: 'team',
name: t('team'),
price: '¥99',
feats: t('feTeam'),
cta: t('choose'),
variant: 'plain'
}];
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
height: '100%'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 8,
padding: '6px 16px 14px'
}
}, /*#__PURE__*/React.createElement("button", {
onClick: onBack,
style: {
border: 'none',
background: 'transparent',
cursor: 'pointer',
padding: 6,
display: 'flex'
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "arrow-left",
size: 22,
color: "var(--fg1)"
})), /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-display)',
fontWeight: 700,
fontSize: 18,
color: 'var(--fg1)',
whiteSpace: 'nowrap'
}
}, t('choosePlan'))), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
overflow: 'auto',
padding: '4px 20px 20px',
display: 'flex',
flexDirection: 'column',
gap: 14
}
}, plans.map(p => {
const isPro = p.variant === 'pro';
return /*#__PURE__*/React.createElement("div", {
key: p.id,
style: {
position: 'relative',
borderRadius: 'var(--radius-xl)',
padding: '18px 20px',
...(isPro ? {
background: 'linear-gradient(155deg,var(--clay-600),var(--clay-800))',
color: '#fff',
boxShadow: 'var(--shadow-md)'
} : {
background: 'var(--surface)',
border: '1px solid var(--border)',
boxShadow: 'var(--shadow-sm)'
})
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'baseline',
justifyContent: 'space-between'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 14,
fontWeight: 600,
color: isPro ? '#fff' : 'var(--fg2)',
opacity: isPro ? 0.9 : 1
}
}, p.name, isPro && /*#__PURE__*/React.createElement("span", {
style: {
marginLeft: 8,
fontSize: 10.5,
fontWeight: 700,
background: '#fff',
color: 'var(--clay-700)',
padding: '2px 8px',
borderRadius: 999
}
}, t('mostPopular'))), /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontSize: 26,
fontWeight: 700,
color: isPro ? '#fff' : 'var(--fg1)'
}
}, p.price, /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 12,
fontWeight: 500,
opacity: 0.7
}
}, t('perMonth')))), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexWrap: 'wrap',
gap: '7px 16px',
margin: '14px 0 16px'
}
}, p.feats.map((f, i) => /*#__PURE__*/React.createElement("div", {
key: i,
style: {
display: 'flex',
alignItems: 'center',
gap: 7,
fontSize: 12.5,
color: isPro ? 'rgba(255,255,255,.92)' : 'var(--fg2)'
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "check",
size: 14,
color: isPro ? '#fff' : 'var(--success)',
stroke: 2.4
}), f))), /*#__PURE__*/React.createElement("button", {
onClick: p.variant !== 'cur' ? onChoose : undefined,
style: {
width: '100%',
border: p.variant === 'cur' ? '1.5px solid var(--border-strong)' : 'none',
cursor: p.variant === 'cur' ? 'default' : 'pointer',
borderRadius: 'var(--radius-full)',
padding: '12px',
fontWeight: 700,
fontSize: 14,
background: isPro ? '#fff' : p.variant === 'cur' ? 'transparent' : 'var(--accent)',
color: isPro ? 'var(--clay-700)' : p.variant === 'cur' ? 'var(--fg2)' : '#fff'
}
}, p.cta));
})));
}
/* ───────── CONTACT US ───────── */
function ContactScreen({
t,
lang,
onBack
}) {
const channels = [{
icon: 'send',
name: 'Telegram',
sub: '@PangolinVPN_bot',
accent: true
}, {
icon: 'message-circle',
name: 'LINE',
sub: '@pangolinvpn'
}, {
icon: 'mail',
name: t('contactEmail'),
sub: 'support@pangolin.vpn'
}, {
icon: 'shopping-bag',
name: t('contactStore'),
sub: 'shop.pangolin.vpn'
}];
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
height: '100%'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 8,
padding: '6px 16px 14px'
}
}, /*#__PURE__*/React.createElement("button", {
onClick: onBack,
style: {
border: 'none',
background: 'transparent',
cursor: 'pointer',
padding: 6,
display: 'flex'
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "arrow-left",
size: 22,
color: "var(--fg1)"
})), /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-display)',
fontWeight: 700,
fontSize: 18,
color: 'var(--fg1)',
whiteSpace: 'nowrap'
}
}, t('contactTitle'))), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
overflow: 'auto',
padding: '0 20px 20px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 13,
color: 'var(--fg2)',
lineHeight: 1.6,
marginBottom: 16
}
}, t('contactIntro')), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
gap: 10,
marginBottom: 18
}
}, channels.map(c => /*#__PURE__*/React.createElement("button", {
key: c.name,
style: {
display: 'flex',
alignItems: 'center',
gap: 13,
width: '100%',
textAlign: 'left',
cursor: 'pointer',
background: c.accent ? 'var(--accent-subtle)' : 'var(--surface)',
border: `1px solid ${c.accent ? 'var(--accent-border)' : 'var(--border)'}`,
borderRadius: 'var(--radius-lg)',
padding: '14px 16px',
boxShadow: 'var(--shadow-sm)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 40,
height: 40,
borderRadius: 'var(--radius-md)',
background: c.accent ? 'var(--accent)' : 'var(--bg-subtle)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0
}
}, /*#__PURE__*/React.createElement(Icon, {
name: c.icon,
size: 20,
color: c.accent ? '#fff' : 'var(--accent)'
})), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
minWidth: 0
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 15,
fontWeight: 600,
color: 'var(--fg1)'
}
}, c.name), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12.5,
color: 'var(--fg3)',
fontFamily: 'var(--font-mono)'
}
}, c.sub)), /*#__PURE__*/React.createElement(Icon, {
name: "external-link",
size: 17,
color: "var(--fg3)"
})))), /*#__PURE__*/React.createElement("div", {
style: {
background: 'var(--surface)',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-lg)',
boxShadow: 'var(--shadow-sm)',
padding: '14px 16px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12,
fontWeight: 600,
color: 'var(--fg3)'
}
}, t('contactHoursTitle')), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 14,
fontWeight: 600,
color: 'var(--fg1)',
marginTop: 5,
fontFamily: 'var(--font-mono)'
}
}, t('contactHours')))));
}
Object.assign(window, {
AuthFlow,
Onboarding,
RedeemScreen,
PlansScreen,
ContactScreen
});
})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/mobile/flows.jsx", error: String((e && e.message) || e) }); }
// ui_kits/mobile/ios-frame.jsx
try { (() => {
/* BEGIN USAGE */
// iOS.jsx — Simplified iOS 26 (Liquid Glass) device frame
// Based on the iOS 26 UI Kit + Figma status bar spec. No assets, no deps.
// Exports (to window): IOSDevice, IOSStatusBar, IOSNavBar, IOSGlassPill, IOSList, IOSListRow, IOSKeyboard
//
// Usage — wrap your screen content in <IOSDevice> to get the bezel, status bar
// and home indicator (props: title, dark, keyboard):
//
// <IOSDevice title="Settings">
// ...your screen content...
// </IOSDevice>
// <IOSDevice dark title="Search" keyboard>…</IOSDevice>
/* END USAGE */
// ─────────────────────────────────────────────────────────────
// Status bar
// ─────────────────────────────────────────────────────────────
function IOSStatusBar({
dark = false,
time = '9:41'
}) {
const c = dark ? '#fff' : '#000';
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 154,
alignItems: 'center',
justifyContent: 'center',
padding: '21px 24px 19px',
boxSizing: 'border-box',
position: 'relative',
zIndex: 20,
width: '100%'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
height: 22,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
paddingTop: 1.5
}
}, /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: '-apple-system, "SF Pro", system-ui',
fontWeight: 590,
fontSize: 17,
lineHeight: '22px',
color: c
}
}, time)), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
height: 22,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 7,
paddingTop: 1,
paddingRight: 1
}
}, /*#__PURE__*/React.createElement("svg", {
width: "19",
height: "12",
viewBox: "0 0 19 12"
}, /*#__PURE__*/React.createElement("rect", {
x: "0",
y: "7.5",
width: "3.2",
height: "4.5",
rx: "0.7",
fill: c
}), /*#__PURE__*/React.createElement("rect", {
x: "4.8",
y: "5",
width: "3.2",
height: "7",
rx: "0.7",
fill: c
}), /*#__PURE__*/React.createElement("rect", {
x: "9.6",
y: "2.5",
width: "3.2",
height: "9.5",
rx: "0.7",
fill: c
}), /*#__PURE__*/React.createElement("rect", {
x: "14.4",
y: "0",
width: "3.2",
height: "12",
rx: "0.7",
fill: c
})), /*#__PURE__*/React.createElement("svg", {
width: "17",
height: "12",
viewBox: "0 0 17 12"
}, /*#__PURE__*/React.createElement("path", {
d: "M8.5 3.2C10.8 3.2 12.9 4.1 14.4 5.6L15.5 4.5C13.7 2.7 11.2 1.5 8.5 1.5C5.8 1.5 3.3 2.7 1.5 4.5L2.6 5.6C4.1 4.1 6.2 3.2 8.5 3.2Z",
fill: c
}), /*#__PURE__*/React.createElement("path", {
d: "M8.5 6.8C9.9 6.8 11.1 7.3 12 8.2L13.1 7.1C11.8 5.9 10.2 5.1 8.5 5.1C6.8 5.1 5.2 5.9 3.9 7.1L5 8.2C5.9 7.3 7.1 6.8 8.5 6.8Z",
fill: c
}), /*#__PURE__*/React.createElement("circle", {
cx: "8.5",
cy: "10.5",
r: "1.5",
fill: c
})), /*#__PURE__*/React.createElement("svg", {
width: "27",
height: "13",
viewBox: "0 0 27 13"
}, /*#__PURE__*/React.createElement("rect", {
x: "0.5",
y: "0.5",
width: "23",
height: "12",
rx: "3.5",
stroke: c,
strokeOpacity: "0.35",
fill: "none"
}), /*#__PURE__*/React.createElement("rect", {
x: "2",
y: "2",
width: "20",
height: "9",
rx: "2",
fill: c
}), /*#__PURE__*/React.createElement("path", {
d: "M25 4.5V8.5C25.8 8.2 26.5 7.2 26.5 6.5C26.5 5.8 25.8 4.8 25 4.5Z",
fill: c,
fillOpacity: "0.4"
}))));
}
// ─────────────────────────────────────────────────────────────
// Liquid glass pill — blur + tint + shine
// ─────────────────────────────────────────────────────────────
function IOSGlassPill({
children,
dark = false,
style = {}
}) {
return /*#__PURE__*/React.createElement("div", {
style: {
height: 44,
minWidth: 44,
borderRadius: 9999,
position: 'relative',
overflow: 'hidden',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
boxShadow: dark ? '0 2px 6px rgba(0,0,0,0.35), 0 6px 16px rgba(0,0,0,0.2)' : '0 1px 3px rgba(0,0,0,0.07), 0 3px 10px rgba(0,0,0,0.06)',
...style
}
}, /*#__PURE__*/React.createElement("div", {
style: {
position: 'absolute',
inset: 0,
borderRadius: 9999,
backdropFilter: 'blur(12px) saturate(180%)',
WebkitBackdropFilter: 'blur(12px) saturate(180%)',
background: dark ? 'rgba(120,120,128,0.28)' : 'rgba(255,255,255,0.5)'
}
}), /*#__PURE__*/React.createElement("div", {
style: {
position: 'absolute',
inset: 0,
borderRadius: 9999,
boxShadow: dark ? 'inset 1.5px 1.5px 1px rgba(255,255,255,0.15), inset -1px -1px 1px rgba(255,255,255,0.08)' : 'inset 1.5px 1.5px 1px rgba(255,255,255,0.7), inset -1px -1px 1px rgba(255,255,255,0.4)',
border: dark ? '0.5px solid rgba(255,255,255,0.15)' : '0.5px solid rgba(0,0,0,0.06)'
}
}), /*#__PURE__*/React.createElement("div", {
style: {
position: 'relative',
zIndex: 1,
display: 'flex',
alignItems: 'center',
padding: '0 4px'
}
}, children));
}
// ─────────────────────────────────────────────────────────────
// Navigation bar — glass pills + large title
// ─────────────────────────────────────────────────────────────
function IOSNavBar({
title = 'Title',
dark = false,
trailingIcon = true
}) {
const muted = dark ? 'rgba(255,255,255,0.6)' : '#404040';
const text = dark ? '#fff' : '#000';
const pillIcon = content => /*#__PURE__*/React.createElement(IOSGlassPill, {
dark: dark
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 36,
height: 36,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}
}, content));
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
gap: 10,
paddingTop: 62,
paddingBottom: 10,
position: 'relative',
zIndex: 5
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 16px'
}
}, pillIcon(/*#__PURE__*/React.createElement("svg", {
width: "12",
height: "20",
viewBox: "0 0 12 20",
fill: "none",
style: {
marginLeft: -1
}
}, /*#__PURE__*/React.createElement("path", {
d: "M10 2L2 10l8 8",
stroke: muted,
strokeWidth: "2.5",
strokeLinecap: "round",
strokeLinejoin: "round"
}))), trailingIcon && pillIcon(/*#__PURE__*/React.createElement("svg", {
width: "22",
height: "6",
viewBox: "0 0 22 6"
}, /*#__PURE__*/React.createElement("circle", {
cx: "3",
cy: "3",
r: "2.5",
fill: muted
}), /*#__PURE__*/React.createElement("circle", {
cx: "11",
cy: "3",
r: "2.5",
fill: muted
}), /*#__PURE__*/React.createElement("circle", {
cx: "19",
cy: "3",
r: "2.5",
fill: muted
})))), /*#__PURE__*/React.createElement("div", {
style: {
padding: '0 16px',
fontFamily: '-apple-system, system-ui',
fontSize: 34,
fontWeight: 700,
lineHeight: '41px',
color: text,
letterSpacing: 0.4
}
}, title));
}
// ─────────────────────────────────────────────────────────────
// Grouped list (inset card, r:26) + row (52px)
// ─────────────────────────────────────────────────────────────
function IOSListRow({
title,
detail,
icon,
chevron = true,
isLast = false,
dark = false
}) {
const text = dark ? '#fff' : '#000';
const sec = dark ? 'rgba(235,235,245,0.6)' : 'rgba(60,60,67,0.6)';
const ter = dark ? 'rgba(235,235,245,0.3)' : 'rgba(60,60,67,0.3)';
const sep = dark ? 'rgba(84,84,88,0.65)' : 'rgba(60,60,67,0.12)';
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
minHeight: 52,
padding: '0 16px',
position: 'relative',
fontFamily: '-apple-system, system-ui',
fontSize: 17,
letterSpacing: -0.43
}
}, icon && /*#__PURE__*/React.createElement("div", {
style: {
width: 30,
height: 30,
borderRadius: 7,
background: icon,
marginRight: 12,
flexShrink: 0
}
}), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
color: text
}
}, title), detail && /*#__PURE__*/React.createElement("span", {
style: {
color: sec,
marginRight: 6
}
}, detail), chevron && /*#__PURE__*/React.createElement("svg", {
width: "8",
height: "14",
viewBox: "0 0 8 14",
style: {
flexShrink: 0
}
}, /*#__PURE__*/React.createElement("path", {
d: "M1 1l6 6-6 6",
stroke: ter,
strokeWidth: "2",
fill: "none",
strokeLinecap: "round",
strokeLinejoin: "round"
})), !isLast && /*#__PURE__*/React.createElement("div", {
style: {
position: 'absolute',
bottom: 0,
right: 0,
left: icon ? 58 : 16,
height: 0.5,
background: sep
}
}));
}
function IOSList({
header,
children,
dark = false
}) {
const hc = dark ? 'rgba(235,235,245,0.6)' : 'rgba(60,60,67,0.6)';
const bg = dark ? '#1C1C1E' : '#fff';
return /*#__PURE__*/React.createElement("div", null, header && /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: '-apple-system, system-ui',
fontSize: 13,
color: hc,
textTransform: 'uppercase',
padding: '8px 36px 6px',
letterSpacing: -0.08
}
}, header), /*#__PURE__*/React.createElement("div", {
style: {
background: bg,
borderRadius: 26,
margin: '0 16px',
overflow: 'hidden'
}
}, children));
}
// ─────────────────────────────────────────────────────────────
// Device frame
// ─────────────────────────────────────────────────────────────
function IOSDevice({
children,
width = 402,
height = 874,
dark = false,
title,
keyboard = false
}) {
return /*#__PURE__*/React.createElement("div", {
style: {
width,
height,
borderRadius: 48,
overflow: 'hidden',
position: 'relative',
background: dark ? '#000' : '#F2F2F7',
boxShadow: '0 40px 80px rgba(0,0,0,0.18), 0 0 0 1px rgba(0,0,0,0.12)',
fontFamily: '-apple-system, system-ui, sans-serif',
WebkitFontSmoothing: 'antialiased'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
position: 'absolute',
top: 11,
left: '50%',
transform: 'translateX(-50%)',
width: 126,
height: 37,
borderRadius: 24,
background: '#000',
zIndex: 50
}
}), /*#__PURE__*/React.createElement("div", {
style: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
zIndex: 10
}
}, /*#__PURE__*/React.createElement(IOSStatusBar, {
dark: dark
})), /*#__PURE__*/React.createElement("div", {
style: {
height: '100%',
display: 'flex',
flexDirection: 'column'
}
}, title !== undefined && /*#__PURE__*/React.createElement(IOSNavBar, {
title: title,
dark: dark
}), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
overflow: 'auto'
}
}, children), keyboard && /*#__PURE__*/React.createElement(IOSKeyboard, {
dark: dark
})), /*#__PURE__*/React.createElement("div", {
style: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
zIndex: 60,
height: 34,
display: 'flex',
justifyContent: 'center',
alignItems: 'flex-end',
paddingBottom: 8,
pointerEvents: 'none'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 139,
height: 5,
borderRadius: 100,
background: dark ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.25)'
}
})));
}
// ─────────────────────────────────────────────────────────────
// Keyboard — iOS 26 liquid glass
// ─────────────────────────────────────────────────────────────
function IOSKeyboard({
dark = false
}) {
const glyph = dark ? 'rgba(255,255,255,0.7)' : '#595959';
const sugg = dark ? 'rgba(255,255,255,0.6)' : '#333';
const keyBg = dark ? 'rgba(255,255,255,0.22)' : 'rgba(255,255,255,0.85)';
// special-key icons
const icons = {
shift: /*#__PURE__*/React.createElement("svg", {
width: "19",
height: "17",
viewBox: "0 0 19 17"
}, /*#__PURE__*/React.createElement("path", {
d: "M9.5 1L1 9.5h4.5V16h8V9.5H18L9.5 1z",
fill: glyph
})),
del: /*#__PURE__*/React.createElement("svg", {
width: "23",
height: "17",
viewBox: "0 0 23 17"
}, /*#__PURE__*/React.createElement("path", {
d: "M7 1h13a2 2 0 012 2v11a2 2 0 01-2 2H7l-6-7.5L7 1z",
fill: "none",
stroke: glyph,
strokeWidth: "1.6",
strokeLinejoin: "round"
}), /*#__PURE__*/React.createElement("path", {
d: "M10 5l7 7M17 5l-7 7",
stroke: glyph,
strokeWidth: "1.6",
strokeLinecap: "round"
})),
ret: /*#__PURE__*/React.createElement("svg", {
width: "20",
height: "14",
viewBox: "0 0 20 14"
}, /*#__PURE__*/React.createElement("path", {
d: "M18 1v6H4m0 0l4-4M4 7l4 4",
fill: "none",
stroke: "#fff",
strokeWidth: "1.8",
strokeLinecap: "round",
strokeLinejoin: "round"
}))
};
const key = (content, {
w,
flex,
ret,
fs = 25,
k
} = {}) => /*#__PURE__*/React.createElement("div", {
key: k,
style: {
height: 42,
borderRadius: 8.5,
flex: flex ? 1 : undefined,
width: w,
minWidth: 0,
background: ret ? '#08f' : keyBg,
boxShadow: '0 1px 0 rgba(0,0,0,0.075)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontFamily: '-apple-system, "SF Compact", system-ui',
fontSize: fs,
fontWeight: 458,
color: ret ? '#fff' : glyph
}
}, content);
const row = (keys, pad = 0) => /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 6.5,
justifyContent: 'center',
padding: `0 ${pad}px`
}
}, keys.map(l => key(l, {
flex: true,
k: l
})));
return /*#__PURE__*/React.createElement("div", {
style: {
position: 'relative',
zIndex: 15,
borderRadius: 27,
overflow: 'hidden',
padding: '11px 0 2px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
boxShadow: dark ? '0 -2px 20px rgba(0,0,0,0.09)' : '0 -1px 6px rgba(0,0,0,0.018), 0 -3px 20px rgba(0,0,0,0.012)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
position: 'absolute',
inset: 0,
borderRadius: 27,
backdropFilter: 'blur(12px) saturate(180%)',
WebkitBackdropFilter: 'blur(12px) saturate(180%)',
background: dark ? 'rgba(120,120,128,0.14)' : 'rgba(255,255,255,0.25)'
}
}), /*#__PURE__*/React.createElement("div", {
style: {
position: 'absolute',
inset: 0,
borderRadius: 27,
boxShadow: dark ? 'inset 1.5px 1.5px 1px rgba(255,255,255,0.15)' : 'inset 1.5px 1.5px 1px rgba(255,255,255,0.7), inset -1px -1px 1px rgba(255,255,255,0.4)',
border: dark ? '0.5px solid rgba(255,255,255,0.15)' : '0.5px solid rgba(0,0,0,0.06)',
pointerEvents: 'none'
}
}), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 20,
alignItems: 'center',
padding: '8px 22px 13px',
width: '100%',
boxSizing: 'border-box',
position: 'relative'
}
}, ['"The"', 'the', 'to'].map((w, i) => /*#__PURE__*/React.createElement(React.Fragment, {
key: i
}, i > 0 && /*#__PURE__*/React.createElement("div", {
style: {
width: 1,
height: 25,
background: '#ccc',
opacity: 0.3
}
}), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
textAlign: 'center',
fontFamily: '-apple-system, system-ui',
fontSize: 17,
color: sugg,
letterSpacing: -0.43,
lineHeight: '22px'
}
}, w)))), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
gap: 13,
padding: '0 6.5px',
width: '100%',
boxSizing: 'border-box',
position: 'relative'
}
}, row(['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p']), row(['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'], 20), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 14.25,
alignItems: 'center'
}
}, key(icons.shift, {
w: 45,
k: 'shift'
}), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 6.5,
flex: 1
}
}, ['z', 'x', 'c', 'v', 'b', 'n', 'm'].map(l => key(l, {
flex: true,
k: l
}))), key(icons.del, {
w: 45,
k: 'del'
})), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 6,
alignItems: 'center'
}
}, key('ABC', {
w: 92.25,
fs: 18,
k: 'abc'
}), key('', {
flex: true,
k: 'space'
}), key(icons.ret, {
w: 92.25,
ret: true,
k: 'ret'
}))), /*#__PURE__*/React.createElement("div", {
style: {
height: 56,
width: '100%',
position: 'relative'
}
}));
}
Object.assign(window, {
IOSDevice,
IOSStatusBar,
IOSNavBar,
IOSGlassPill,
IOSList,
IOSListRow,
IOSKeyboard
});
})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/mobile/ios-frame.jsx", error: String((e && e.message) || e) }); }
// ui_kits/mobile/parts.jsx
try { (() => {
/* parts.jsx — 穿山甲 VPN mobile · shared atoms
Icon uses inline Lucide path data (lucide.dev) for reliability in React. */
const LUCIDE = {
power: '<path d="M12 2v10"/><path d="M18.36 6.64a9 9 0 1 1-12.73 0"/>',
'shield-check': '<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/><path d="m9 12 2 2 4-4"/>',
globe: '<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>',
settings: '<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/>',
'chevron-right': '<path d="m9 18 6-6-6-6"/>',
'chevron-down': '<path d="m6 9 6 6 6-6"/>',
check: '<path d="M20 6 9 17l-5-5"/>',
zap: '<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>',
'loader-circle': '<path d="M21 12a9 9 0 1 1-6.219-8.56"/>',
user: '<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>',
x: '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
crown: '<path d="M11.562 3.266a.5.5 0 0 1 .876 0L15.39 8.87a1 1 0 0 0 1.516.294L21.183 5.5a.5.5 0 0 1 .798.519l-2.834 10.246a1 1 0 0 1-.956.734H5.81a1 1 0 0 1-.957-.734L2.02 6.02a.5.5 0 0 1 .798-.519l4.276 3.664a1 1 0 0 0 1.516-.294z"/><path d="M5 21h14"/>',
'map-pin': '<path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"/><circle cx="12" cy="10" r="3"/>',
'arrow-down': '<path d="M12 5v14"/><path d="m19 12-7 7-7-7"/>',
'arrow-up': '<path d="M12 19V5"/><path d="m5 12 7-7 7 7"/>',
search: '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',
moon: '<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>',
sun: '<circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/>',
shield: '<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/>',
'refresh-cw': '<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M3 21v-5h5"/>',
chart: '<line x1="18" x2="18" y1="20" y2="10"/><line x1="12" x2="12" y1="20" y2="4"/><line x1="6" x2="6" y1="20" y2="14"/>',
wifi: '<path d="M12 20h.01"/><path d="M2 8.82a15 15 0 0 1 20 0"/><path d="M5 12.859a10 10 0 0 1 14 0"/><path d="M8.5 16.429a5 5 0 0 1 7 0"/>',
mail: '<rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/>',
lock: '<rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
'arrow-left': '<path d="m12 19-7-7 7-7"/><path d="M19 12H5"/>',
'shopping-bag': '<path d="M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4Z"/><path d="M3 6h18"/><path d="M16 10a4 4 0 0 1-8 0"/>',
send: '<path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/><path d="m21.854 2.147-10.94 10.939"/>',
'message-circle': '<path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/>',
'external-link': '<path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>',
'check-circle': '<path d="M21.801 10A10 10 0 1 1 17 3.335"/><path d="m9 11 3 3L22 4"/>',
'log-out': '<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" x2="9" y1="12" y2="12"/>',
compass: '<path d="m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z"/><circle cx="12" cy="12" r="10"/>',
laptop: '<path d="M18 5a2 2 0 0 1 2 2v8.526a2 2 0 0 0 .212.894l1.575 3.15A.5.5 0 0 1 21.342 21H2.658a.5.5 0 0 1-.445-.724l1.575-3.15A2 2 0 0 0 4 16.526V7a2 2 0 0 1 2-2z"/><path d="M20.054 15.987H3.946"/>',
smartphone: '<rect width="14" height="20" x="5" y="2" rx="2" ry="2"/><path d="M12 18h.01"/>',
'monitor-smartphone': '<path d="M18 8V5a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h8"/><path d="M10 19v-3.96 3.15"/><path d="M7 19h5"/><rect width="6" height="10" x="16" y="12" rx="2"/>',
ticket: '<path d="M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"/><path d="M13 5v2"/><path d="M13 17v2"/><path d="M13 11v2"/>',
clock: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
'credit-card': '<rect width="20" height="14" x="2" y="5" rx="2"/><line x1="2" x2="22" y1="10" y2="10"/>',
'play-circle': '<circle cx="12" cy="12" r="10"/><polygon points="10 8 16 12 10 16 10 8"/>'
};
function Icon({
name,
size = 24,
stroke = 2,
color = 'currentColor',
style = {}
}) {
return /*#__PURE__*/React.createElement("svg", {
width: size,
height: size,
viewBox: "0 0 24 24",
fill: "none",
stroke: color,
strokeWidth: stroke,
strokeLinecap: "round",
strokeLinejoin: "round",
style: {
display: 'block',
flexShrink: 0,
...style
},
dangerouslySetInnerHTML: {
__html: LUCIDE[name] || ''
}
});
}
/* Brand mark — walking pangolin (matches app icon), facing right */
function Mark({
size = 40,
color = 'var(--accent)'
}) {
return /*#__PURE__*/React.createElement("svg", {
width: size,
height: size,
viewBox: "0 0 96 96",
fill: "none",
style: {
display: 'block'
}
}, /*#__PURE__*/React.createElement("g", {
transform: "translate(96,0) scale(-1,1)"
}, /*#__PURE__*/React.createElement("path", {
d: "M16 59 Q17 51 26 50 Q34 31 45 30 Q55 30 62 37 Q69 41 75 45 Q85 49 89 44 Q92 50 84 52 Q75 53 67 53 Q66 62 60 62 L56 62 Q54 55 49 55 Q47 62 37 62 L33 62 Q31 56 26 56 Q20 59 16 59 Z",
fill: color
}), /*#__PURE__*/React.createElement("g", {
stroke: "#fff",
strokeWidth: "2.4",
strokeLinecap: "round",
strokeLinejoin: "round",
fill: "none",
opacity: "0.5"
}, /*#__PURE__*/React.createElement("path", {
d: "M26 50 Q34 43 42 50"
}), /*#__PURE__*/React.createElement("path", {
d: "M36 48 Q44 41 52 48"
}), /*#__PURE__*/React.createElement("path", {
d: "M46 48 Q54 41 62 49"
})), /*#__PURE__*/React.createElement("circle", {
cx: "23",
cy: "55",
r: "2.2",
fill: "#fff",
opacity: "0.85"
})));
}
/* Country code tile */
function CC({
code,
active
}) {
return /*#__PURE__*/React.createElement("div", {
style: {
width: 38,
height: 38,
borderRadius: 'var(--radius-md)',
flexShrink: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontFamily: 'var(--font-mono)',
fontSize: 12,
fontWeight: 600,
background: active ? 'var(--accent)' : 'var(--bg-subtle)',
color: active ? '#fff' : 'var(--fg2)'
}
}, code);
}
/* Signal bars from ping */
function Signal({
ping
}) {
const lvl = ping < 40 ? 3 : ping < 90 ? 2 : 1;
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 2,
alignItems: 'flex-end',
height: 14
}
}, [5, 9, 14].map((h, i) => /*#__PURE__*/React.createElement("div", {
key: i,
style: {
width: 3,
height: h,
borderRadius: 1,
background: i < lvl ? 'var(--success)' : 'var(--border-strong)'
}
})));
}
/* 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 /*#__PURE__*/React.createElement("button", {
onClick: onClick,
disabled: disabled,
style: {
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
gap: 8,
fontFamily: 'var(--font-sans)',
fontWeight: 600,
fontSize: 15,
border: 'none',
borderRadius: 'var(--radius-full)',
padding: '13px 22px',
cursor: disabled ? 'not-allowed' : 'pointer',
transition: 'all 140ms var(--ease-out)',
opacity: disabled ? 0.5 : 1,
...v,
...style
}
}, children);
}
/* ───────── 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: []
}]
});
})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/mobile/parts.jsx", error: String((e && e.message) || e) }); }
// ui_kits/mobile/screens.jsx
try { (() => {
/* screens.jsx — 穿山甲 VPN mobile · screens + bottom tab (i18n) */
const {
useState,
useEffect,
useRef
} = React;
/* ───────── Top brand bar ───────── */
function TopBar({
t,
lang,
right
}) {
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '6px 20px 14px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 9
}
}, /*#__PURE__*/React.createElement(Mark, {
size: 28
}), /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-display)',
fontWeight: 700,
fontSize: 18,
color: 'var(--fg1)',
letterSpacing: '-0.01em'
}
}, t('brand'))), right);
}
/* ───────── CONNECT screen ───────── */
function ConnectScreen({
t,
lang,
status,
onToggle,
server,
smart,
free,
mins,
adDone,
onAd,
goServers,
elapsed
}) {
const map = {
off: {
fill: 'var(--sand-100)',
fg: 'var(--clay-500)',
icon: 'power',
cap: t('capOff'),
ring: 'track'
},
connecting: {
fill: 'var(--accent)',
fg: '#fff',
icon: 'loader-circle',
cap: t('capConnecting'),
ring: 'spin'
},
on: {
fill: 'var(--success)',
fg: '#fff',
icon: 'shield-check',
cap: t('capOn'),
ring: 'full'
}
}[status];
const fmt = s => `${String(Math.floor(s / 3600)).padStart(2, '0')}:${String(Math.floor(s % 3600 / 60)).padStart(2, '0')}:${String(s % 60).padStart(2, '0')}`;
const shadow = status === 'off' ? 'inset 0 0 0 1px var(--sand-200), var(--shadow-md)' : `0 0 0 9px ${status === 'on' ? 'var(--success-subtle)' : 'var(--accent-subtle)'}, var(--shadow-lg)`;
// in-circle center block
const center = status === 'off' ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-sans)',
fontWeight: 700,
fontSize: 14
}
}, t('connectNow'))) : status === 'connecting' ? /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-mono)',
fontWeight: 700,
fontSize: 14,
letterSpacing: '0.08em'
}
}, "\xB7\xB7\xB7") : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-mono)',
fontWeight: 600,
fontSize: 18
}
}, fmt(elapsed)), /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-sans)',
fontWeight: 700,
fontSize: 10,
letterSpacing: '0.1em',
opacity: .9
}
}, t('secure')));
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
height: '100%'
}
}, /*#__PURE__*/React.createElement(TopBar, {
t: t,
lang: lang,
right: /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 12,
whiteSpace: 'nowrap',
color: status === 'on' ? 'var(--success)' : 'var(--fg3)',
fontWeight: 600
}
}, status === 'on' ? t('online') : t('offline'))
}), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 26,
padding: '0 24px'
}
}, /*#__PURE__*/React.createElement("button", {
onClick: onToggle,
style: {
position: 'relative',
width: 208,
height: 208,
borderRadius: '50%',
border: 'none',
cursor: 'pointer',
background: map.fill,
color: map.fg,
boxShadow: shadow,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 6,
transition: 'box-shadow 300ms var(--ease-out), background-color 300ms var(--ease-out)'
}
}, /*#__PURE__*/React.createElement("svg", {
viewBox: "0 0 208 208",
style: {
position: 'absolute',
inset: 0,
width: '100%',
height: '100%',
transformOrigin: 'center',
animation: map.ring === 'spin' ? 'pg-spin 1.4s linear infinite' : 'none'
}
}, map.ring === 'track' && /*#__PURE__*/React.createElement("circle", {
cx: "104",
cy: "104",
r: "96",
fill: "none",
stroke: "var(--sand-200)",
strokeWidth: "3",
strokeDasharray: "2 8",
strokeLinecap: "round"
}), map.ring === 'spin' && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("circle", {
cx: "104",
cy: "104",
r: "96",
fill: "none",
stroke: "rgba(255,255,255,.3)",
strokeWidth: "4"
}), /*#__PURE__*/React.createElement("circle", {
cx: "104",
cy: "104",
r: "96",
fill: "none",
stroke: "#fff",
strokeWidth: "4",
strokeLinecap: "round",
strokeDasharray: "150 453"
})), map.ring === 'full' && /*#__PURE__*/React.createElement("circle", {
cx: "104",
cy: "104",
r: "96",
fill: "none",
stroke: "rgba(255,255,255,.85)",
strokeWidth: "4",
strokeLinecap: "round"
})), /*#__PURE__*/React.createElement(Icon, {
name: map.icon,
size: 50,
stroke: 1.7,
style: status === 'connecting' ? {
animation: 'pg-spin 1s linear infinite'
} : {}
}), center), /*#__PURE__*/React.createElement("div", {
style: {
textAlign: 'center'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-sans)',
fontSize: 15,
fontWeight: 600,
color: 'var(--fg2)',
whiteSpace: 'nowrap'
}
}, map.cap))), /*#__PURE__*/React.createElement("div", {
style: {
padding: '0 20px 18px',
display: 'flex',
flexDirection: 'column',
gap: 12
}
}, free && /*#__PURE__*/React.createElement("div", {
style: {
background: 'var(--surface)',
borderRadius: 'var(--radius-lg)',
padding: '12px 14px',
boxShadow: 'var(--shadow-sm)',
border: '1px solid var(--border)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 7,
marginBottom: 8
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "clock",
size: 15,
stroke: 2.2,
color: "var(--accent)"
}), /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 12,
fontWeight: 600,
color: 'var(--fg2)'
}
}, t('quotaToday')), /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 14,
fontWeight: 600,
color: 'var(--fg1)',
whiteSpace: 'nowrap'
}
}, mins, " ", t('minutes')), /*#__PURE__*/React.createElement("span", {
style: {
marginLeft: 'auto',
fontSize: 10.5,
fontWeight: 600,
color: 'var(--fg3)',
background: 'var(--bg-subtle)',
padding: '3px 9px',
borderRadius: 'var(--radius-full)',
whiteSpace: 'nowrap'
}
}, t('quotaFree'))), /*#__PURE__*/React.createElement("div", {
style: {
height: 6,
background: 'var(--bg-subtle)',
borderRadius: 3,
overflow: 'hidden',
marginBottom: 10
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: `${Math.min(100, mins / 10 * 100)}%`,
height: '100%',
background: mins <= 3 ? 'var(--warning)' : 'var(--accent)',
borderRadius: 3,
transition: 'width 300ms var(--ease-out)'
}
})), adDone ? /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 7,
color: 'var(--success)',
fontWeight: 700,
fontSize: 13,
padding: '9px'
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "check-circle",
size: 16,
stroke: 2.2,
color: "var(--success)"
}), t('adUnlocked')) : /*#__PURE__*/React.createElement("button", {
onClick: onAd,
style: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 7,
width: '100%',
border: '1.5px solid var(--accent-border)',
background: 'var(--accent-subtle)',
color: 'var(--accent)',
fontFamily: 'var(--font-sans)',
fontWeight: 700,
fontSize: 13,
padding: '9px',
borderRadius: 'var(--radius-full)',
cursor: 'pointer'
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "play-circle",
size: 16,
stroke: 2.2
}), t('watchAd'))), status === 'on' && /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 12
}
}, [['arrow-down', t('download'), '86.4', 'Mb/s'], ['arrow-up', t('upload'), '12.1', 'Mb/s'], ['zap', t('latency'), String(server.ping), 'ms']].map(([ic, l, v, u]) => /*#__PURE__*/React.createElement("div", {
key: l,
style: {
flex: 1,
background: 'var(--surface)',
borderRadius: 'var(--radius-lg)',
padding: '12px 14px',
boxShadow: 'var(--shadow-sm)',
border: '1px solid var(--border)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 5,
color: 'var(--fg3)',
fontSize: 11,
fontWeight: 600,
marginBottom: 4
}
}, /*#__PURE__*/React.createElement(Icon, {
name: ic,
size: 13,
stroke: 2.2,
color: "var(--accent)"
}), l), /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 17,
fontWeight: 500,
color: 'var(--fg1)'
}
}, v, /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 11,
color: 'var(--fg3)'
}
}, " ", u))))), /*#__PURE__*/React.createElement("button", {
onClick: goServers,
style: {
display: 'flex',
alignItems: 'center',
gap: 12,
width: '100%',
textAlign: 'left',
background: 'var(--surface)',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-lg)',
padding: '12px 14px',
cursor: 'pointer',
boxShadow: 'var(--shadow-sm)'
}
}, /*#__PURE__*/React.createElement(CC, {
code: server.code,
active: true
}), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-sans)',
fontSize: 15,
fontWeight: 600,
color: 'var(--fg1)',
display: 'flex',
alignItems: 'center',
gap: 7
}
}, smart ? t('smartSelect') : srvName(server, lang), smart && /*#__PURE__*/React.createElement(Icon, {
name: "zap",
size: 13,
stroke: 2.4,
color: "var(--accent)"
})), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12,
color: 'var(--fg3)'
}
}, smart ? `${srvName(server, lang)} · ${server.ping}ms` : `${srvSub(server, lang)} · ${server.ping}ms`)), /*#__PURE__*/React.createElement(Icon, {
name: "chevron-right",
size: 20,
color: "var(--fg3)"
}))));
}
/* ───────── SERVERS screen ───────── */
function ServersScreen({
t,
lang,
current,
onPick,
q,
setQ
}) {
const list = SERVERS.filter(s => (s.name + s.en).toLowerCase().includes(q.toLowerCase()));
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
height: '100%'
}
}, /*#__PURE__*/React.createElement(TopBar, {
t: t,
lang: lang
}), /*#__PURE__*/React.createElement("div", {
style: {
padding: '0 20px 12px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontSize: 24,
fontWeight: 700,
color: 'var(--fg1)',
marginBottom: 12
}
}, t('chooseNode')), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 10,
background: 'var(--bg-subtle)',
borderRadius: 'var(--radius-md)',
padding: '10px 14px'
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "search",
size: 18,
color: "var(--fg3)"
}), /*#__PURE__*/React.createElement("input", {
value: q,
onChange: e => setQ(e.target.value),
placeholder: t('search'),
style: {
border: 'none',
background: 'transparent',
outline: 'none',
flex: 1,
fontFamily: 'var(--font-sans)',
fontSize: 15,
color: 'var(--fg1)'
}
}))), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
overflow: 'auto',
padding: '0 20px 20px'
}
}, /*#__PURE__*/React.createElement("button", {
onClick: () => onPick('AUTO'),
style: {
display: 'flex',
alignItems: 'center',
gap: 13,
width: '100%',
textAlign: 'left',
padding: '15px 14px',
border: `1.5px solid ${current === 'AUTO' ? 'var(--accent)' : 'var(--accent-border)'}`,
cursor: 'pointer',
background: 'var(--accent-subtle)',
borderRadius: 'var(--radius-xl)',
boxShadow: current === 'AUTO' ? 'var(--shadow-md)' : 'var(--shadow-sm)',
marginBottom: 12
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 42,
height: 42,
borderRadius: 'var(--radius-md)',
background: 'linear-gradient(150deg,var(--clay-500),var(--clay-700))',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "zap",
size: 21,
stroke: 2.2,
color: "#fff"
})), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 8
}
}, /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-sans)',
fontSize: 15.5,
fontWeight: 700,
color: 'var(--fg1)'
}
}, t('smartSelect')), /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 10,
fontWeight: 700,
color: '#fff',
background: 'var(--accent)',
padding: '2px 8px',
borderRadius: 999,
letterSpacing: '0.04em'
}
}, t('recommended'))), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12,
color: 'var(--fg2)',
marginTop: 3,
lineHeight: 1.5
}
}, t('smartSub'))), current === 'AUTO' && /*#__PURE__*/React.createElement(Icon, {
name: "check",
size: 19,
color: "var(--accent)",
stroke: 2.6
})), /*#__PURE__*/React.createElement("div", {
style: {
background: 'var(--surface)',
borderRadius: 'var(--radius-lg)',
boxShadow: 'var(--shadow-sm)',
overflow: 'hidden',
border: '1px solid var(--border)'
}
}, list.map((s, i) => {
const active = s.code === current;
return /*#__PURE__*/React.createElement("button", {
key: s.code,
onClick: () => onPick(s.code),
style: {
display: 'flex',
alignItems: 'center',
gap: 13,
width: '100%',
textAlign: 'left',
padding: '12px 14px',
border: 'none',
cursor: 'pointer',
borderBottom: i < list.length - 1 ? '1px solid var(--border)' : 'none',
background: active ? 'var(--accent-subtle)' : 'transparent'
}
}, /*#__PURE__*/React.createElement(CC, {
code: s.code,
active: active
}), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-sans)',
fontSize: 15,
fontWeight: 600,
color: 'var(--fg1)'
}
}, srvName(s, lang)), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12,
color: 'var(--fg3)'
}
}, srvSub(s, lang))), /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 12,
color: 'var(--fg2)'
}
}, s.ping, "ms"), /*#__PURE__*/React.createElement(Signal, {
ping: s.ping
}), active && /*#__PURE__*/React.createElement(Icon, {
name: "check",
size: 18,
color: "var(--accent)"
}));
}))));
}
/* ───────── SETTINGS screen ───────── */
function Toggle({
on,
onChange
}) {
return /*#__PURE__*/React.createElement("div", {
onClick: onChange,
style: {
width: 46,
height: 28,
borderRadius: 999,
position: 'relative',
cursor: 'pointer',
flexShrink: 0,
background: on ? 'var(--accent)' : 'var(--border-strong)',
transition: 'background 220ms var(--ease-out)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
position: 'absolute',
top: 3,
left: on ? 21 : 3,
width: 22,
height: 22,
borderRadius: '50%',
background: '#fff',
boxShadow: 'var(--shadow-sm)',
transition: 'left 220ms var(--ease-out)'
}
}));
}
/* segmented language switch 中 / EN */
function LangSwitch({
lang,
setLang
}) {
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
background: 'var(--bg-subtle)',
borderRadius: 'var(--radius-full)',
padding: 3,
gap: 2
}
}, [['zh', '中文'], ['en', 'EN']].map(([v, l]) => /*#__PURE__*/React.createElement("button", {
key: v,
onClick: () => setLang(v),
style: {
border: 'none',
cursor: 'pointer',
borderRadius: 'var(--radius-full)',
padding: '5px 13px',
fontFamily: 'var(--font-sans)',
fontSize: 12.5,
fontWeight: 700,
background: lang === v ? 'var(--accent)' : 'transparent',
color: lang === v ? 'var(--fg-on-accent)' : 'var(--fg3)',
transition: 'all 140ms var(--ease-out)'
}
}, l)));
}
function Row({
icon,
title,
sub,
right,
last,
onClick
}) {
return /*#__PURE__*/React.createElement("div", {
onClick: onClick,
style: {
display: 'flex',
alignItems: 'center',
gap: 13,
padding: '13px 16px',
borderBottom: last ? 'none' : '1px solid var(--border)',
cursor: onClick ? 'pointer' : 'default'
}
}, icon && /*#__PURE__*/React.createElement("div", {
style: {
width: 32,
height: 32,
borderRadius: 'var(--radius-sm)',
background: 'var(--accent-subtle)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0
}
}, /*#__PURE__*/React.createElement(Icon, {
name: icon,
size: 17,
color: "var(--accent)",
stroke: 2.2
})), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-sans)',
fontSize: 15,
fontWeight: 500,
color: 'var(--fg1)'
}
}, title), sub && /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12,
color: 'var(--fg3)'
}
}, sub)), right);
}
function SettingsScreen({
t,
lang,
setLang,
dark,
setDark,
free,
setFree,
onRedeem,
onUpgrade,
onContact,
onSignOut,
onReplayAuth,
onReplayOnboarding
}) {
const [autostart, setAuto] = useState(true);
const [kill, setKill] = useState(true);
const [twoFA, setTwoFA] = useState(true);
const [devices, setDevices] = useState([{
id: 1,
icon: 'laptop',
name: 'MacBook Pro',
os: 'macOS 26',
active: t('thisDevice'),
me: true
}, {
id: 2,
icon: 'smartphone',
name: 'iPhone 17',
os: 'iOS 26',
active: '2 min',
me: false
}, {
id: 3,
icon: 'monitor-smartphone',
name: 'iPad Air',
os: 'iPadOS 26',
active: lang === 'zh' ? '3 小时前' : '3h ago',
me: false
}]);
const card = {
background: 'var(--surface)',
borderRadius: 'var(--radius-lg)',
boxShadow: 'var(--shadow-sm)',
border: '1px solid var(--border)',
overflow: 'hidden',
marginBottom: 18
};
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
height: '100%'
}
}, /*#__PURE__*/React.createElement(TopBar, {
t: t,
lang: lang
}), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
overflow: 'auto',
padding: '0 20px 20px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontSize: 24,
fontWeight: 700,
color: 'var(--fg1)',
marginBottom: 14
}
}, t('meTitle')), /*#__PURE__*/React.createElement("div", {
style: {
background: 'linear-gradient(155deg,var(--clay-600),var(--clay-800))',
borderRadius: 'var(--radius-xl)',
padding: 18,
color: '#fff',
marginBottom: 18,
boxShadow: 'var(--shadow-md)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 11
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 44,
height: 44,
borderRadius: '50%',
background: 'rgba(255,255,255,0.18)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "crown",
size: 22,
color: "#fff"
})), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontWeight: 700,
fontSize: 16
}
}, "me@pangolin.vpn"), /*#__PURE__*/React.createElement("div", {
style: {
display: 'inline-flex',
alignItems: 'center',
gap: 4,
fontSize: 11,
fontWeight: 600,
background: 'rgba(255,255,255,0.2)',
padding: '2px 8px',
borderRadius: 999,
marginTop: 4
}
}, free ? t('freePlanName') : t('proMember')))), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'flex-end',
marginTop: 16,
paddingTop: 14,
borderTop: '1px solid rgba(255,255,255,0.18)',
fontSize: 12
}
}, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
style: {
opacity: 0.7
}
}, free ? t('quotaToday') : t('expires')), /*#__PURE__*/React.createElement("div", {
style: {
fontWeight: 600,
marginTop: 2,
fontFamily: 'var(--font-mono)',
whiteSpace: 'nowrap'
}
}, free ? t('quotaFree') : '2026-12-31 · 42.6 GB')), /*#__PURE__*/React.createElement("button", {
onClick: onUpgrade,
style: {
border: 'none',
background: '#fff',
color: 'var(--clay-700)',
fontWeight: 700,
fontSize: 13,
padding: '9px 16px',
borderRadius: 'var(--radius-full)',
cursor: 'pointer',
display: 'inline-flex',
alignItems: 'center',
gap: 6
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "ticket",
size: 15,
color: "var(--clay-700)"
}), t('upgradeBtn')))), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 13,
fontWeight: 700,
color: 'var(--fg2)',
margin: '4px 4px 10px'
}
}, t('accInfoTitle')), /*#__PURE__*/React.createElement("div", {
style: card
}, /*#__PURE__*/React.createElement(Row, {
icon: "mail",
title: t('accEmail'),
sub: "me@pangolin.vpn",
right: /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 13,
color: 'var(--accent)',
fontWeight: 600
}
}, t('accChange'))
}), /*#__PURE__*/React.createElement(Row, {
icon: "lock",
title: t('accPassword'),
sub: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
right: /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 13,
color: 'var(--accent)',
fontWeight: 600
}
}, t('accChange'))
}), /*#__PURE__*/React.createElement(Row, {
icon: "shield",
title: t('twoFA'),
sub: t('twoFASub'),
right: /*#__PURE__*/React.createElement(Toggle, {
on: twoFA,
onChange: () => setTwoFA(!twoFA)
}),
last: true
})), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 13,
fontWeight: 700,
color: 'var(--fg2)',
margin: '4px 4px 4px'
}
}, t('myDevices')), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 11.5,
color: 'var(--fg3)',
margin: '0 4px 10px'
}
}, t('devicesSub')), /*#__PURE__*/React.createElement("div", {
style: card
}, devices.map((d, i) => /*#__PURE__*/React.createElement("div", {
key: d.id,
style: {
display: 'flex',
alignItems: 'center',
gap: 12,
padding: '12px 16px',
borderBottom: i < devices.length - 1 ? '1px solid var(--border)' : 'none'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 34,
height: 34,
borderRadius: 'var(--radius-md)',
background: 'var(--accent-subtle)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0
}
}, /*#__PURE__*/React.createElement(Icon, {
name: d.icon,
size: 18,
color: "var(--accent)"
})), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
minWidth: 0
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 14,
fontWeight: 600,
color: 'var(--fg1)',
display: 'flex',
alignItems: 'center',
gap: 7
}
}, d.name, d.me && /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 10.5,
fontWeight: 600,
color: 'var(--success)',
background: 'var(--success-subtle)',
padding: '1px 7px',
borderRadius: 999
}
}, d.active)), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 11.5,
color: 'var(--fg3)',
marginTop: 1
}
}, d.os, !d.me && ` · ${d.active}`)), !d.me && /*#__PURE__*/React.createElement("button", {
onClick: () => setDevices(devices.filter(x => x.id !== d.id)),
style: {
border: '1.5px solid var(--border-strong)',
background: 'transparent',
color: 'var(--fg2)',
fontSize: 12.5,
fontWeight: 600,
padding: '6px 12px',
borderRadius: 'var(--radius-full)',
cursor: 'pointer'
}
}, t('remove'))))), /*#__PURE__*/React.createElement("div", {
style: card
}, /*#__PURE__*/React.createElement(Row, {
icon: "shopping-bag",
title: t('redeemEntry'),
right: /*#__PURE__*/React.createElement(Icon, {
name: "chevron-right",
size: 18,
color: "var(--fg3)"
}),
onClick: onRedeem
}), /*#__PURE__*/React.createElement(Row, {
icon: "external-link",
title: t('ucEntry'),
sub: t('ucEntrySub'),
right: /*#__PURE__*/React.createElement(Icon, {
name: "chevron-right",
size: 18,
color: "var(--fg3)"
}),
onClick: () => window.open('../usercenter/index.html', '_blank')
}), /*#__PURE__*/React.createElement(Row, {
icon: "message-circle",
title: t('contactEntry'),
right: /*#__PURE__*/React.createElement(Icon, {
name: "chevron-right",
size: 18,
color: "var(--fg3)"
}),
last: true,
onClick: onContact
})), /*#__PURE__*/React.createElement("div", {
style: card
}, /*#__PURE__*/React.createElement(Row, {
icon: "zap",
title: t('smartRoute'),
sub: t('smartRouteSub'),
right: /*#__PURE__*/React.createElement(Toggle, {
on: autostart,
onChange: () => setAuto(!autostart)
})
}), /*#__PURE__*/React.createElement(Row, {
icon: "shield",
title: t('killSwitch'),
sub: t('killSwitchSub'),
right: /*#__PURE__*/React.createElement(Toggle, {
on: kill,
onChange: () => setKill(!kill)
}),
last: true
})), /*#__PURE__*/React.createElement("div", {
style: card
}, /*#__PURE__*/React.createElement(Row, {
icon: "globe",
title: t('language'),
right: /*#__PURE__*/React.createElement(LangSwitch, {
lang: lang,
setLang: setLang
})
}), /*#__PURE__*/React.createElement(Row, {
icon: dark ? 'moon' : 'sun',
title: t('darkAppearance'),
sub: dark ? t('stateOn') : t('followLight'),
right: /*#__PURE__*/React.createElement(Toggle, {
on: dark,
onChange: () => setDark(!dark)
})
}), /*#__PURE__*/React.createElement(Row, {
icon: "shield",
title: t('protocol'),
right: /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 13,
color: 'var(--fg3)'
}
}, "WireGuard")
}), /*#__PURE__*/React.createElement(Row, {
icon: "refresh-cw",
title: t('checkUpdate'),
right: /*#__PURE__*/React.createElement(Icon, {
name: "chevron-right",
size: 18,
color: "var(--fg3)"
}),
last: true
})), /*#__PURE__*/React.createElement("div", {
style: card
}, /*#__PURE__*/React.createElement(Row, {
icon: "log-out",
title: t('replayAuth'),
sub: t('replayAuthSub'),
right: /*#__PURE__*/React.createElement(Icon, {
name: "chevron-right",
size: 18,
color: "var(--fg3)"
}),
onClick: onReplayAuth
}), /*#__PURE__*/React.createElement(Row, {
icon: "compass",
title: t('replayOnboarding'),
sub: t('replayOnboardingSub'),
right: /*#__PURE__*/React.createElement(Icon, {
name: "chevron-right",
size: 18,
color: "var(--fg3)"
}),
onClick: onReplayOnboarding
}), /*#__PURE__*/React.createElement(Row, {
icon: "clock",
title: t('freeDemo'),
sub: t('freeDemoSub'),
right: /*#__PURE__*/React.createElement(Toggle, {
on: free,
onChange: () => setFree(!free)
}),
last: true
})), /*#__PURE__*/React.createElement("div", {
style: {
textAlign: 'center',
fontSize: 12,
color: 'var(--fg3)',
fontFamily: 'var(--font-mono)'
}
}, "\u7A7F\u5C71\u7532 \xB7 v2.4.0")));
}
/* ───────── STATS screen ───────── */
function StatsScreen({
t,
lang
}) {
const vals = [2.1, 3.4, 1.8, 4.6, 5.2, 6.1, 3.0];
const max = 6.1;
const labels = t('days7');
const card = {
background: 'var(--surface)',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-lg)',
boxShadow: 'var(--shadow-sm)'
};
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
height: '100%'
}
}, /*#__PURE__*/React.createElement(TopBar, {
t: t,
lang: lang
}), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
overflow: 'auto',
padding: '0 20px 20px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontSize: 24,
fontWeight: 700,
color: 'var(--fg1)',
marginBottom: 16
}
}, t('statsTitle')), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 12,
marginBottom: 16
}
}, [[t('trafficMonth'), '42.6', 'GB'], [t('avgPing'), '29', 'ms'], [t('durMonth'), '86.4', 'h']].map(([l, v, u]) => /*#__PURE__*/React.createElement("div", {
key: l,
style: {
...card,
flex: 1,
padding: '14px 14px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 11,
color: 'var(--fg3)',
fontWeight: 600
}
}, l), /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 20,
fontWeight: 500,
color: 'var(--fg1)',
marginTop: 6
}
}, v, /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 11,
color: 'var(--fg3)'
}
}, " ", u))))), /*#__PURE__*/React.createElement("div", {
style: {
...card,
padding: '18px 18px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 13,
fontWeight: 600,
color: 'var(--fg1)',
marginBottom: 18
}
}, t('weekTraffic')), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'flex-end',
gap: 12,
height: 120
}
}, vals.map((v, i) => /*#__PURE__*/React.createElement("div", {
key: i,
style: {
flex: 1,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 7
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 10,
color: 'var(--fg3)'
}
}, v), /*#__PURE__*/React.createElement("div", {
style: {
width: '100%',
maxWidth: 30,
height: `${v / max * 84}px`,
background: 'var(--accent)',
borderRadius: '6px 6px 0 0',
opacity: 0.85
}
}), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 11,
color: 'var(--fg3)'
}
}, labels[i])))))));
}
/* ───────── Bottom tab ───────── */
function BottomTab({
t,
tab,
setTab
}) {
const items = [['connect', 'power', t('tabConnect')], ['servers', 'globe', t('tabServers')], ['stats', 'chart', t('tabStats')], ['me', 'user', t('tabMe')]];
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
borderTop: '1px solid var(--border)',
background: 'var(--surface)',
paddingBottom: 22
}
}, items.map(([id, ic, label]) => {
const a = tab === id;
return /*#__PURE__*/React.createElement("button", {
key: id,
onClick: () => setTab(id),
style: {
flex: 1,
border: 'none',
background: 'transparent',
cursor: 'pointer',
padding: '10px 0 4px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 4
}
}, /*#__PURE__*/React.createElement(Icon, {
name: ic,
size: 22,
stroke: a ? 2.4 : 2,
color: a ? 'var(--accent)' : 'var(--fg3)'
}), /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-sans)',
fontSize: 11,
fontWeight: a ? 700 : 500,
color: a ? 'var(--accent)' : 'var(--fg3)'
}
}, label));
}));
}
Object.assign(window, {
ConnectScreen,
ServersScreen,
StatsScreen,
SettingsScreen,
BottomTab,
LangSwitch
});
})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/mobile/screens.jsx", error: String((e && e.message) || e) }); }
// ui_kits/tablet/tabapp.jsx
try { (() => {
/* tabapp.jsx — 穿山甲 iPad 客户端 · 分栏布局
复用 ../mobile/parts.jsx 的 Icon/Mark/CC/Signal/SERVERS/makeT/srvName/srvSub。
仅 iPad 专属字串定义在本文件 TSTR。 */
const {
useState,
useEffect,
useRef
} = React;
/* iPad 专属字串(双语单显) */
const TSTR = {
navConnect: {
zh: '连接',
en: 'Connect'
},
navNodes: {
zh: '节点',
en: 'Nodes'
},
navStats: {
zh: '统计',
en: 'Stats'
},
navAccount: {
zh: '账户',
en: 'Account'
},
capOff: {
zh: '未连接 · 轻点连接',
en: 'Not connected · tap to start'
},
capConnecting: {
zh: '连接中 Connecting…',
en: 'Connecting…'
},
capOn: {
zh: '已连接 · 网络已加密',
en: 'Connected · traffic encrypted'
},
download: {
zh: '下载',
en: 'Down'
},
upload: {
zh: '上传',
en: 'Up'
},
latency: {
zh: '延迟',
en: 'Ping'
},
curNode: {
zh: '当前节点',
en: 'Current node'
},
searchPh: {
zh: '搜索国家 / 城市',
en: 'Search country / city'
},
statTraffic: {
zh: '本月流量',
en: 'Data this month'
},
statPing: {
zh: '平均延迟',
en: 'Avg latency'
},
statTime: {
zh: '本月时长',
en: 'Time this month'
},
weekTraffic: {
zh: '本周流量 (GB)',
en: 'This week (GB)'
},
days: {
zh: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
en: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
acctEmail: {
zh: '邮箱',
en: 'Email'
},
language: {
zh: '语言',
en: 'Language'
},
darkMode: {
zh: '深色外观',
en: 'Dark appearance'
},
smartRoute: {
zh: '智能分流',
en: 'Smart routing'
},
smartRouteSub: {
zh: '海外应用走加速,国内直连',
en: 'Accelerate abroad, direct at home'
},
killSwitch: {
zh: 'Kill Switch',
en: 'Kill Switch'
},
killSub: {
zh: '断线时阻断网络,防止泄露',
en: 'Block traffic if the link drops'
},
freeBanner: {
zh: '免费版',
en: 'Free plan'
},
trialNote: {
zh: '升级 PRO:不限时长 · 80+ 加速线路 · 5 台设备',
en: 'Go PRO: unlimited time · 80+ routes · 5 devices'
},
version: {
zh: '穿山甲 · iPadOS · v2.4.0',
en: 'Pangolin · iPadOS · v2.4.0'
}
};
function makeTT(lang) {
return k => (TSTR[k] && TSTR[k][lang]) !== undefined ? TSTR[k][lang] : k;
}
function fmtHMS(s) {
return `${String(Math.floor(s / 3600)).padStart(2, '0')}:${String(Math.floor(s % 3600 / 60)).padStart(2, '0')}:${String(s % 60).padStart(2, '0')}`;
}
/* ── 左侧栏 ── */
function TabRail({
tt,
view,
setView,
free,
t
}) {
const items = [['connect', 'power', 'navConnect'], ['nodes', 'globe', 'navNodes'], ['stats', 'chart', 'navStats'], ['account', 'user', 'navAccount']];
return /*#__PURE__*/React.createElement("div", {
style: {
width: 232,
flexShrink: 0,
background: 'var(--bg-subtle)',
borderRight: '1px solid var(--border)',
display: 'flex',
flexDirection: 'column',
padding: '18px 14px 16px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 10,
padding: '0 8px 20px'
}
}, /*#__PURE__*/React.createElement(Mark, {
size: 30
}), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontWeight: 700,
fontSize: 17,
color: 'var(--fg1)',
lineHeight: 1
}
}, "\u7A7F\u5C71\u7532"), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 9,
fontWeight: 600,
letterSpacing: '0.2em',
color: 'var(--accent)',
marginTop: 3
}
}, "PANGOLIN"))), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
gap: 4
}
}, items.map(([id, ic, lk]) => {
const a = view === id;
return /*#__PURE__*/React.createElement("button", {
key: id,
onClick: () => setView(id),
style: {
display: 'flex',
alignItems: 'center',
gap: 12,
width: '100%',
textAlign: 'left',
border: 'none',
cursor: 'pointer',
background: a ? 'var(--accent-subtle)' : 'transparent',
color: a ? 'var(--accent)' : 'var(--fg2)',
padding: '13px 14px',
borderRadius: 'var(--radius-md)',
fontFamily: 'var(--font-sans)',
fontSize: 15,
fontWeight: a ? 700 : 500,
transition: 'background-color 140ms var(--ease-out), color 140ms var(--ease-out)',
minHeight: 48
}
}, /*#__PURE__*/React.createElement(Icon, {
name: ic,
size: 20,
stroke: a ? 2.4 : 2,
color: a ? 'var(--accent)' : 'var(--fg3)'
}), tt(lk));
})), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1
}
}), /*#__PURE__*/React.createElement("div", {
style: {
background: free ? 'var(--surface)' : 'linear-gradient(150deg,var(--clay-600),var(--clay-800))',
border: free ? '1px solid var(--border)' : 'none',
borderRadius: 'var(--radius-lg)',
padding: '12px 13px',
color: free ? 'var(--fg1)' : '#fff'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 9
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 32,
height: 32,
borderRadius: '50%',
background: free ? 'var(--bg-subtle)' : 'rgba(255,255,255,0.18)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0
}
}, /*#__PURE__*/React.createElement(Icon, {
name: free ? 'user' : 'crown',
size: 16,
color: free ? 'var(--fg2)' : '#fff'
})), /*#__PURE__*/React.createElement("div", {
style: {
minWidth: 0
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12.5,
fontWeight: 700,
whiteSpace: 'nowrap'
}
}, free ? tt('freeBanner') : t('proMember')), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 10.5,
opacity: 0.65,
whiteSpace: 'nowrap'
}
}, "me@pangolin.vpn")))));
}
/* ── 连接视图:左大按钮 + 右信息列 ── */
function TabConnect({
t,
tt,
lang,
status,
onToggle,
server,
smart,
free,
mins,
adDone,
onAd,
elapsed,
goNodes
}) {
const map = {
off: {
ring: 'var(--border)',
fill: 'var(--surface)',
fg: 'var(--fg2)',
icon: 'power',
label: 'TAP',
cap: tt('capOff')
},
connecting: {
ring: 'var(--accent-subtle)',
fill: 'var(--accent)',
fg: '#fff',
icon: 'loader-circle',
label: '···',
cap: tt('capConnecting')
},
on: {
ring: 'var(--success-subtle)',
fill: 'var(--success)',
fg: '#fff',
icon: 'shield-check',
label: 'SAFE',
cap: tt('capOn')
}
}[status];
const card = {
background: 'var(--surface)',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-lg)',
boxShadow: 'var(--shadow-sm)'
};
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
height: '100%',
alignItems: 'stretch'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
flex: 1.25,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 26
}
}, /*#__PURE__*/React.createElement("button", {
onClick: onToggle,
style: {
width: 216,
height: 216,
borderRadius: '50%',
border: 'none',
cursor: 'pointer',
background: map.fill,
color: map.fg,
boxShadow: `0 0 0 11px ${map.ring}, var(--shadow-lg)`,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 12,
transition: 'box-shadow 300ms var(--ease-out), background-color 300ms var(--ease-out)'
}
}, /*#__PURE__*/React.createElement(Icon, {
name: map.icon,
size: 60,
stroke: 1.6,
style: status === 'connecting' ? {
animation: 'pg-spin 1s linear infinite'
} : {}
}), /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-mono)',
fontWeight: 700,
fontSize: 14,
letterSpacing: '0.08em'
}
}, map.label)), /*#__PURE__*/React.createElement("div", {
style: {
textAlign: 'center'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-sans)',
fontSize: 16,
fontWeight: 600,
color: 'var(--fg2)',
whiteSpace: 'nowrap'
}
}, map.cap), status === 'on' && /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 28,
fontWeight: 500,
color: 'var(--fg1)',
marginTop: 8,
letterSpacing: '-0.01em'
}
}, fmtHMS(elapsed)))), /*#__PURE__*/React.createElement("div", {
style: {
width: 332,
flexShrink: 0,
display: 'flex',
flexDirection: 'column',
gap: 14,
justifyContent: 'center',
paddingRight: 28
}
}, free && /*#__PURE__*/React.createElement("div", {
style: {
...card,
padding: '14px 16px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 8,
marginBottom: 9
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "clock",
size: 15,
stroke: 2.2,
color: "var(--accent)"
}), /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 12.5,
fontWeight: 600,
color: 'var(--fg2)'
}
}, t('quotaToday')), /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 14,
fontWeight: 600,
color: 'var(--fg1)',
whiteSpace: 'nowrap'
}
}, mins, " ", t('minutes')), /*#__PURE__*/React.createElement("span", {
style: {
marginLeft: 'auto',
fontSize: 10.5,
fontWeight: 600,
color: 'var(--fg3)',
background: 'var(--bg-subtle)',
padding: '3px 9px',
borderRadius: 'var(--radius-full)',
whiteSpace: 'nowrap'
}
}, t('quotaFree'))), /*#__PURE__*/React.createElement("div", {
style: {
height: 6,
background: 'var(--bg-subtle)',
borderRadius: 3,
overflow: 'hidden',
marginBottom: 11
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: `${Math.min(100, mins / 10 * 100)}%`,
height: '100%',
background: mins <= 3 ? 'var(--warning)' : 'var(--accent)',
borderRadius: 3
}
})), adDone ? /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 7,
color: 'var(--success)',
fontWeight: 700,
fontSize: 13,
padding: '8px'
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "check-circle",
size: 16,
stroke: 2.2,
color: "var(--success)"
}), t('adUnlocked')) : /*#__PURE__*/React.createElement("button", {
onClick: onAd,
style: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 7,
width: '100%',
border: '1.5px solid var(--accent-border)',
background: 'var(--accent-subtle)',
color: 'var(--accent)',
fontFamily: 'var(--font-sans)',
fontWeight: 700,
fontSize: 13.5,
padding: '10px',
borderRadius: 'var(--radius-full)',
cursor: 'pointer',
minHeight: 44
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "play-circle",
size: 16,
stroke: 2.2
}), t('watchAd'))), /*#__PURE__*/React.createElement("button", {
onClick: goNodes,
style: {
...card,
display: 'flex',
alignItems: 'center',
gap: 13,
textAlign: 'left',
padding: '13px 15px',
cursor: 'pointer',
minHeight: 48
}
}, /*#__PURE__*/React.createElement(CC, {
code: server.code,
active: true
}), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 11,
fontWeight: 600,
color: 'var(--fg3)',
marginBottom: 2
}
}, tt('curNode')), /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-sans)',
fontSize: 15,
fontWeight: 600,
color: 'var(--fg1)',
display: 'flex',
alignItems: 'center',
gap: 7
}
}, smart ? t('smartSelect') : srvName(server, lang), smart && /*#__PURE__*/React.createElement(Icon, {
name: "zap",
size: 13,
stroke: 2.4,
color: "var(--accent)"
})), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12,
color: 'var(--fg3)'
}
}, smart ? `${srvName(server, lang)} · ${server.ping}ms` : `${srvSub(server, lang)} · ${server.ping}ms`)), /*#__PURE__*/React.createElement(Icon, {
name: "chevron-right",
size: 20,
color: "var(--fg3)"
})), status === 'on' && /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 10
}
}, [['arrow-down', tt('download'), '86.4', 'Mb/s'], ['arrow-up', tt('upload'), '12.1', 'Mb/s'], ['zap', tt('latency'), String(server.ping), 'ms']].map(([ic, l, v, u]) => /*#__PURE__*/React.createElement("div", {
key: ic,
style: {
...card,
flex: 1,
padding: '11px 13px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 5,
color: 'var(--fg3)',
fontSize: 11,
fontWeight: 600,
marginBottom: 4
}
}, /*#__PURE__*/React.createElement(Icon, {
name: ic,
size: 13,
stroke: 2.2,
color: "var(--accent)"
}), l), /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 16,
fontWeight: 500,
color: 'var(--fg1)'
}
}, v, /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 10.5,
color: 'var(--fg3)'
}
}, " ", u)))))));
}
/* ── 节点视图 ── */
function TabNodes({
t,
tt,
lang,
current,
onPick
}) {
const [q, setQ] = useState('');
const list = SERVERS.filter(s => (s.name + s.en).toLowerCase().includes(q.toLowerCase()));
const smartActive = current === 'AUTO';
return /*#__PURE__*/React.createElement("div", {
style: {
padding: '6px 28px 24px',
overflow: 'auto',
height: '100%',
boxSizing: 'border-box'
}
}, /*#__PURE__*/React.createElement("button", {
onClick: () => onPick('AUTO'),
style: {
display: 'flex',
alignItems: 'center',
gap: 13,
width: '100%',
textAlign: 'left',
cursor: 'pointer',
background: 'var(--accent-subtle)',
border: `1.5px solid ${smartActive ? 'var(--accent)' : 'var(--accent-border)'}`,
borderRadius: 'var(--radius-xl)',
padding: '15px 16px',
boxShadow: smartActive ? 'var(--shadow-md)' : 'var(--shadow-sm)',
marginBottom: 14,
minHeight: 56
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 42,
height: 42,
borderRadius: 'var(--radius-md)',
background: 'linear-gradient(150deg,var(--clay-500),var(--clay-700))',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "zap",
size: 21,
stroke: 2.2,
color: "#fff"
})), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 8
}
}, /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-sans)',
fontSize: 15.5,
fontWeight: 700,
color: 'var(--fg1)'
}
}, t('smartSelect')), /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 10,
fontWeight: 700,
color: '#fff',
background: 'var(--accent)',
padding: '2px 8px',
borderRadius: 999,
letterSpacing: '0.04em'
}
}, t('recommended'))), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12,
color: 'var(--fg2)',
marginTop: 3
}
}, t('smartSub'))), smartActive && /*#__PURE__*/React.createElement(Icon, {
name: "check",
size: 19,
color: "var(--accent)",
stroke: 2.6
})), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 10,
background: 'var(--bg-subtle)',
borderRadius: 'var(--radius-md)',
padding: '11px 14px',
marginBottom: 14,
maxWidth: 380
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "search",
size: 18,
color: "var(--fg3)"
}), /*#__PURE__*/React.createElement("input", {
value: q,
onChange: e => setQ(e.target.value),
placeholder: tt('searchPh'),
style: {
border: 'none',
background: 'transparent',
outline: 'none',
flex: 1,
fontFamily: 'var(--font-sans)',
fontSize: 14.5,
color: 'var(--fg1)'
}
})), /*#__PURE__*/React.createElement("div", {
style: {
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: 11
}
}, list.map(s => {
const active = s.code === current;
return /*#__PURE__*/React.createElement("button", {
key: s.code,
onClick: () => onPick(s.code),
style: {
display: 'flex',
alignItems: 'center',
gap: 12,
textAlign: 'left',
cursor: 'pointer',
background: active ? 'var(--accent-subtle)' : 'var(--surface)',
border: `1.5px solid ${active ? 'var(--accent-border)' : 'var(--border)'}`,
borderRadius: 'var(--radius-lg)',
padding: '13px 15px',
boxShadow: 'var(--shadow-sm)',
minHeight: 56
}
}, /*#__PURE__*/React.createElement(CC, {
code: s.code,
active: active
}), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
minWidth: 0
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-sans)',
fontSize: 14.5,
fontWeight: 600,
color: 'var(--fg1)'
}
}, srvName(s, lang)), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12,
color: 'var(--fg3)',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}
}, srvSub(s, lang))), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-end',
gap: 5
}
}, /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 12,
color: 'var(--fg2)'
}
}, s.ping, "ms"), /*#__PURE__*/React.createElement(Signal, {
ping: s.ping
})), active && /*#__PURE__*/React.createElement(Icon, {
name: "check",
size: 18,
color: "var(--accent)"
}));
})));
}
/* ── 统计视图 ── */
function TabStats({
tt,
lang
}) {
const vals = [2.1, 3.4, 1.8, 4.6, 5.2, 6.1, 3.0];
const days = TSTR.days[lang];
const max = Math.max(...vals);
const card = {
background: 'var(--surface)',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-lg)',
boxShadow: 'var(--shadow-sm)'
};
return /*#__PURE__*/React.createElement("div", {
style: {
padding: '6px 28px 24px',
overflow: 'auto',
height: '100%',
boxSizing: 'border-box'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 14,
marginBottom: 20
}
}, [[tt('statTraffic'), '42.6 GB'], [tt('statPing'), '29 ms'], [tt('statTime'), '86.4 h']].map(([l, v]) => /*#__PURE__*/React.createElement("div", {
key: l,
style: {
...card,
flex: 1,
padding: '18px 20px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12.5,
color: 'var(--fg3)',
fontWeight: 600
}
}, l), /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 26,
fontWeight: 500,
color: 'var(--fg1)',
marginTop: 7
}
}, v)))), /*#__PURE__*/React.createElement("div", {
style: {
...card,
padding: '20px 22px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 13.5,
fontWeight: 600,
color: 'var(--fg1)',
marginBottom: 20
}
}, tt('weekTraffic')), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'flex-end',
gap: 22,
height: 150
}
}, vals.map((v, i) => /*#__PURE__*/React.createElement("div", {
key: i,
style: {
flex: 1,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 9
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 10.5,
color: 'var(--fg3)'
}
}, v), /*#__PURE__*/React.createElement("div", {
style: {
width: '100%',
maxWidth: 40,
height: `${v / max * 104}px`,
background: 'var(--accent)',
borderRadius: '6px 6px 0 0',
opacity: 0.85
}
}), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 11.5,
color: 'var(--fg3)'
}
}, days[i]))))));
}
/* ── 账户视图 ── */
function TabToggle({
on,
onChange
}) {
return /*#__PURE__*/React.createElement("div", {
onClick: onChange,
style: {
width: 48,
height: 29,
borderRadius: 999,
position: 'relative',
cursor: 'pointer',
flexShrink: 0,
background: on ? 'var(--accent)' : 'var(--border-strong)',
transition: 'background 220ms var(--ease-out)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
position: 'absolute',
top: 3,
left: on ? 22 : 3,
width: 23,
height: 23,
borderRadius: '50%',
background: '#fff',
boxShadow: 'var(--shadow-sm)',
transition: 'left 220ms var(--ease-out)'
}
}));
}
function TabAccount({
t,
tt,
lang,
setLang,
dark,
setDark,
free,
setFree
}) {
const [smartRoute, setSmartRoute] = useState(true);
const [kill, setKill] = useState(true);
const card = {
background: 'var(--surface)',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-lg)',
boxShadow: 'var(--shadow-sm)',
overflow: 'hidden',
marginBottom: 16
};
const Row = ({
icon,
title,
sub,
right,
last
}) => /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 13,
padding: '15px 18px',
borderBottom: last ? 'none' : '1px solid var(--border)',
minHeight: 48,
boxSizing: 'border-box'
}
}, icon && /*#__PURE__*/React.createElement("div", {
style: {
width: 34,
height: 34,
borderRadius: 'var(--radius-sm)',
background: 'var(--accent-subtle)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0
}
}, /*#__PURE__*/React.createElement(Icon, {
name: icon,
size: 17,
color: "var(--accent)",
stroke: 2.2
})), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 15,
fontWeight: 500,
color: 'var(--fg1)'
}
}, title), sub && /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12,
color: 'var(--fg3)',
marginTop: 2
}
}, sub)), right);
const seg = val => ({
border: 'none',
cursor: 'pointer',
padding: '7px 16px',
borderRadius: 'var(--radius-full)',
fontFamily: 'var(--font-sans)',
fontSize: 13,
fontWeight: 600,
minHeight: 32,
background: lang === val ? 'var(--surface)' : 'transparent',
color: lang === val ? 'var(--fg1)' : 'var(--fg3)',
boxShadow: lang === val ? 'var(--shadow-sm)' : 'none'
});
return /*#__PURE__*/React.createElement("div", {
style: {
padding: '6px 28px 24px',
overflow: 'auto',
height: '100%',
boxSizing: 'border-box',
maxWidth: 640
}
}, /*#__PURE__*/React.createElement("div", {
style: {
background: free ? 'var(--surface)' : 'linear-gradient(155deg,var(--clay-600),var(--clay-800))',
border: free ? '1px solid var(--border)' : 'none',
borderRadius: 'var(--radius-xl)',
padding: 20,
color: free ? 'var(--fg1)' : '#fff',
marginBottom: 18,
boxShadow: 'var(--shadow-md)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 12
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 46,
height: 46,
borderRadius: '50%',
background: free ? 'var(--bg-subtle)' : 'rgba(255,255,255,0.18)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}
}, /*#__PURE__*/React.createElement(Icon, {
name: free ? 'user' : 'crown',
size: 22,
color: free ? 'var(--fg2)' : '#fff'
})), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
style: {
fontWeight: 700,
fontSize: 16.5
}
}, "me@pangolin.vpn"), /*#__PURE__*/React.createElement("div", {
style: {
display: 'inline-block',
fontSize: 11,
fontWeight: 600,
background: free ? 'var(--bg-subtle)' : 'rgba(255,255,255,0.2)',
color: free ? 'var(--fg2)' : '#fff',
padding: '2px 9px',
borderRadius: 999,
marginTop: 5
}
}, free ? tt('freeBanner') : t('proMember')))), /*#__PURE__*/React.createElement("div", {
style: {
marginTop: 14,
paddingTop: 13,
borderTop: `1px solid ${free ? 'var(--border)' : 'rgba(255,255,255,0.18)'}`,
fontSize: 12.5,
opacity: free ? 1 : 0.92,
color: free ? 'var(--fg2)' : '#fff'
}
}, free ? tt('trialNote') : '2026-12-31 · 42.6 GB')), /*#__PURE__*/React.createElement("div", {
style: card
}, /*#__PURE__*/React.createElement(Row, {
icon: "zap",
title: tt('smartRoute'),
sub: tt('smartRouteSub'),
right: /*#__PURE__*/React.createElement(TabToggle, {
on: smartRoute,
onChange: () => setSmartRoute(!smartRoute)
})
}), /*#__PURE__*/React.createElement(Row, {
icon: "shield",
title: tt('killSwitch'),
sub: tt('killSub'),
right: /*#__PURE__*/React.createElement(TabToggle, {
on: kill,
onChange: () => setKill(!kill)
}),
last: true
})), /*#__PURE__*/React.createElement("div", {
style: card
}, /*#__PURE__*/React.createElement(Row, {
icon: "globe",
title: tt('language'),
right: /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 4,
background: 'var(--bg-subtle)',
borderRadius: 'var(--radius-full)',
padding: 3
}
}, /*#__PURE__*/React.createElement("button", {
style: seg('zh'),
onClick: () => setLang('zh')
}, "\u4E2D\u6587"), /*#__PURE__*/React.createElement("button", {
style: seg('en'),
onClick: () => setLang('en')
}, "EN"))
}), /*#__PURE__*/React.createElement(Row, {
icon: dark ? 'moon' : 'sun',
title: tt('darkMode'),
right: /*#__PURE__*/React.createElement(TabToggle, {
on: dark,
onChange: () => setDark(!dark)
})
}), /*#__PURE__*/React.createElement(Row, {
icon: "clock",
title: t('freeDemo'),
sub: t('freeDemoSub'),
right: /*#__PURE__*/React.createElement(TabToggle, {
on: free,
onChange: () => setFree(!free)
}),
last: true
})), /*#__PURE__*/React.createElement("div", {
style: {
textAlign: 'center',
fontSize: 12,
color: 'var(--fg3)',
fontFamily: 'var(--font-mono)'
}
}, tt('version')));
}
/* ── iPad 外壳 + 应用 ── */
function TabletApp() {
const [view, setView] = useState('connect');
const [status, setStatus] = useState('off');
const [code, setCode] = useState('AUTO');
const [lang, setLang] = useState('zh');
const [dark, setDark] = useState(false);
const [free, setFree] = useState(true);
const [mins, setMins] = useState(6);
const [adDone, setAdDone] = useState(false);
const [elapsed, setElapsed] = useState(0);
const timer = useRef(null);
const t = makeT(lang),
tt = makeTT(lang);
const smart = code === 'AUTO';
const server = smart ? SERVERS.reduce((a, b) => a.ping < b.ping ? a : b) : SERVERS.find(s => s.code === code);
const titles = {
connect: tt('navConnect'),
nodes: tt('navNodes'),
stats: tt('navStats'),
account: tt('navAccount')
};
function toggle() {
if (status === 'off') {
setStatus('connecting');
setTimeout(() => {
setStatus('on');
setElapsed(0);
}, 1500);
} else setStatus('off');
}
function pick(c) {
setCode(c);
setView('connect');
if (status === 'on') {
setStatus('connecting');
setTimeout(() => {
setStatus('on');
setElapsed(0);
}, 1200);
}
}
useEffect(() => {
if (status === 'on') timer.current = setInterval(() => setElapsed(e => e + 1), 1000);
return () => clearInterval(timer.current);
}, [status]);
let main;
if (view === 'connect') main = /*#__PURE__*/React.createElement(TabConnect, {
t: t,
tt: tt,
lang: lang,
status: status,
onToggle: toggle,
server: server,
smart: smart,
free: free,
mins: mins,
adDone: adDone,
onAd: () => setAdDone(true),
elapsed: elapsed,
goNodes: () => setView('nodes')
});else if (view === 'nodes') main = /*#__PURE__*/React.createElement(TabNodes, {
t: t,
tt: tt,
lang: lang,
current: code,
onPick: pick
});else if (view === 'stats') main = /*#__PURE__*/React.createElement(TabStats, {
tt: tt,
lang: lang
});else main = /*#__PURE__*/React.createElement(TabAccount, {
t: t,
tt: tt,
lang: lang,
setLang: setLang,
dark: dark,
setDark: setDark,
free: free,
setFree: setFree
});
return /*#__PURE__*/React.createElement("div", {
style: {
background: '#1a1714',
borderRadius: 34,
padding: 16,
boxShadow: '0 0 0 1px rgba(0,0,0,0.3), 0 30px 80px rgba(0,0,0,0.45)'
}
}, /*#__PURE__*/React.createElement("div", {
"data-theme": dark ? 'dark' : 'light',
style: {
width: 1180,
height: 820,
borderRadius: 20,
overflow: 'hidden',
background: 'var(--bg)',
display: 'flex',
flexDirection: 'column',
fontFamily: 'var(--font-sans)',
position: 'relative'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
height: 30,
flexShrink: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 22px',
fontFamily: 'var(--font-sans)',
fontSize: 12.5,
fontWeight: 600,
color: 'var(--fg1)'
}
}, /*#__PURE__*/React.createElement("span", null, "9:41"), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 8
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "wifi",
size: 15,
stroke: 2.2,
color: "var(--fg1)"
}), /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 11.5
}
}, "87%"), /*#__PURE__*/React.createElement("div", {
style: {
width: 24,
height: 12,
border: '1.5px solid var(--fg2)',
borderRadius: 3.5,
padding: 1.5,
boxSizing: 'border-box'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: '80%',
height: '100%',
background: 'var(--fg1)',
borderRadius: 1.5
}
})))), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
minHeight: 0,
display: 'flex'
}
}, /*#__PURE__*/React.createElement(TabRail, {
tt: tt,
view: view,
setView: setView,
free: free,
t: t
}), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
minWidth: 0,
display: 'flex',
flexDirection: 'column'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
height: 56,
flexShrink: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 28px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontWeight: 700,
fontSize: 19,
color: 'var(--fg1)'
}
}, titles[view]), /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 12.5,
fontWeight: 600,
color: status === 'on' ? 'var(--success)' : 'var(--fg3)',
whiteSpace: 'nowrap'
}
}, status === 'on' ? '● ' + server.code : '○')), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
minHeight: 0
}
}, main)))));
}
Object.assign(window, {
TabletApp
});
})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/tablet/tabapp.jsx", error: String((e && e.message) || e) }); }
// ui_kits/usercenter/ucapp.jsx
try { (() => {
/* ucapp.jsx — 穿山甲 Web 用户中心 · views */
const {
useState: useStateU
} = React;
const ucCard = {
background: 'var(--surface)',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-xl)',
boxShadow: 'var(--shadow-sm)'
};
const ucInput = {
border: '1.5px solid var(--border-strong)',
borderRadius: 'var(--radius-md)',
padding: '12px 14px',
fontFamily: 'var(--font-sans)',
fontSize: 14.5,
color: 'var(--fg1)',
background: 'var(--surface)',
outline: 'none',
width: '100%',
boxSizing: 'border-box'
};
/* viewport hook — mobile ≤ 700px */
function useIsMobileUC() {
const [m, setM] = useStateU(typeof window !== 'undefined' && window.innerWidth <= 700);
React.useEffect(() => {
const f = () => setM(window.innerWidth <= 700);
window.addEventListener('resize', f);
return () => window.removeEventListener('resize', f);
}, []);
return m;
}
function UCLang({
lang,
setLang
}) {
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
background: 'var(--bg-subtle)',
borderRadius: 999,
padding: 3,
gap: 2
}
}, [['zh', '中文'], ['en', 'EN']].map(([v, l]) => /*#__PURE__*/React.createElement("button", {
key: v,
onClick: () => setLang(v),
style: {
border: 'none',
cursor: 'pointer',
borderRadius: 999,
padding: '5px 12px',
fontFamily: 'var(--font-sans)',
fontSize: 12.5,
fontWeight: 700,
background: lang === v ? 'var(--accent)' : 'transparent',
color: lang === v ? 'var(--fg-on-accent)' : 'var(--fg3)'
}
}, l)));
}
/* ───────── Login (+ PRO 2FA step) ───────── */
function UCLogin({
t,
lang,
setLang,
onDone
}) {
const [email, setEmail] = useStateU('');
const [pw, setPw] = useStateU('');
const [step, setStep] = useStateU('cred'); // cred | totp
const [otp, setOtp] = useStateU('');
const ok = /\S+@\S+\.\S+/.test(email) && pw.length > 0;
return /*#__PURE__*/React.createElement("div", {
style: {
minHeight: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'var(--bg)',
padding: 24
}
}, /*#__PURE__*/React.createElement("div", {
style: {
...ucCard,
width: 400,
maxWidth: '100%',
padding: '36px 34px',
boxSizing: 'border-box'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: 26
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 10
}
}, /*#__PURE__*/React.createElement(UCMark, {
size: 32
}), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontWeight: 700,
fontSize: 17,
color: 'var(--fg1)',
lineHeight: 1
}
}, "\u7A7F\u5C71\u7532"), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 8.5,
fontWeight: 600,
letterSpacing: '0.2em',
color: 'var(--accent)',
marginTop: 3
}
}, "PANGOLIN"))), /*#__PURE__*/React.createElement(UCLang, {
lang: lang,
setLang: setLang
})), step === 'cred' ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontWeight: 700,
fontSize: 22,
color: 'var(--fg1)'
}
}, t('loginTitle')), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 13.5,
color: 'var(--fg3)',
margin: '6px 0 24px'
}
}, t('loginSub')), /*#__PURE__*/React.createElement("label", {
style: {
display: 'block',
marginBottom: 14
}
}, /*#__PURE__*/React.createElement("span", {
style: {
display: 'block',
fontSize: 12.5,
fontWeight: 600,
color: 'var(--fg2)',
marginBottom: 6
}
}, t('emailLabel')), /*#__PURE__*/React.createElement("input", {
style: ucInput,
value: email,
onChange: e => setEmail(e.target.value),
placeholder: t('emailPh')
})), /*#__PURE__*/React.createElement("label", {
style: {
display: 'block'
}
}, /*#__PURE__*/React.createElement("span", {
style: {
display: 'block',
fontSize: 12.5,
fontWeight: 600,
color: 'var(--fg2)',
marginBottom: 6
}
}, t('pwLabel')), /*#__PURE__*/React.createElement("input", {
style: ucInput,
type: "password",
value: pw,
onChange: e => setPw(e.target.value),
placeholder: t('pwPh')
})), /*#__PURE__*/React.createElement("div", {
style: {
textAlign: 'right',
margin: '10px 0 18px'
}
}, /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 13,
color: 'var(--accent)',
fontWeight: 600,
cursor: 'pointer'
}
}, t('forgotPw'))), /*#__PURE__*/React.createElement("button", {
onClick: ok ? () => setStep('totp') : undefined,
disabled: !ok,
style: {
width: '100%',
border: 'none',
borderRadius: 'var(--radius-full)',
padding: '13px',
fontFamily: 'var(--font-sans)',
fontWeight: 700,
fontSize: 15,
cursor: ok ? 'pointer' : 'not-allowed',
background: 'var(--accent)',
color: 'var(--fg-on-accent)',
opacity: ok ? 1 : 0.45
}
}, t('doLogin')), /*#__PURE__*/React.createElement("div", {
style: {
textAlign: 'center',
fontSize: 12.5,
color: 'var(--fg3)',
marginTop: 18
}
}, t('noAccount'))) : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 9,
marginBottom: 6
}
}, /*#__PURE__*/React.createElement(UCIcon, {
name: "shield-check",
size: 20,
color: "var(--accent)"
}), /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-display)',
fontWeight: 700,
fontSize: 20,
color: 'var(--fg1)'
}
}, t('twoFATitle')), /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 10.5,
fontWeight: 700,
color: 'var(--accent)',
background: 'var(--accent-subtle)',
border: '1px solid var(--accent-border)',
padding: '2px 8px',
borderRadius: 999
}
}, "PRO")), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 13,
color: 'var(--fg3)',
margin: '4px 0 20px',
lineHeight: 1.6
}
}, t('twoFAHint')), /*#__PURE__*/React.createElement("input", {
value: otp,
onChange: e => setOtp(e.target.value.replace(/\D/g, '').slice(0, 6)),
placeholder: "\xB7\xB7\xB7\xB7\xB7\xB7",
style: {
...ucInput,
fontFamily: 'var(--font-mono)',
fontSize: 22,
letterSpacing: '0.5em',
textAlign: 'center',
padding: '14px'
}
}), /*#__PURE__*/React.createElement("button", {
onClick: otp.length === 6 ? onDone : undefined,
disabled: otp.length !== 6,
style: {
width: '100%',
border: 'none',
borderRadius: 'var(--radius-full)',
padding: '13px',
marginTop: 16,
fontFamily: 'var(--font-sans)',
fontWeight: 700,
fontSize: 15,
cursor: otp.length === 6 ? 'pointer' : 'not-allowed',
background: 'var(--accent)',
color: 'var(--fg-on-accent)',
opacity: otp.length === 6 ? 1 : 0.45
}
}, t('twoFAConfirm')), /*#__PURE__*/React.createElement("div", {
onClick: () => setStep('cred'),
style: {
textAlign: 'center',
fontSize: 12.5,
color: 'var(--fg3)',
marginTop: 18,
cursor: 'pointer'
}
}, t('twoFABack')))));
}
/* ───────── Overview ───────── */
function UCOverview({
t,
lang,
free,
mobile,
goSub,
goRedeem
}) {
const vals = [1.8, 2.4, 1.2, 3.1, 4.0, 5.2, 2.6];
const max = 5.2;
const labels = lang === 'zh' ? ['一', '二', '三', '四', '五', '六', '日'] : ['M', 'T', 'W', 'T', 'F', 'S', 'S'];
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
gap: 20
}
}, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontSize: 25,
fontWeight: 700,
color: 'var(--fg1)'
}
}, t('greeting')), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 13.5,
color: 'var(--fg3)',
marginTop: 3
}
}, "me@pangolin.vpn")), /*#__PURE__*/React.createElement("div", {
style: {
...ucCard,
background: 'linear-gradient(150deg,var(--clay-600),var(--clay-800))',
border: 'none',
color: '#fff',
padding: '20px 24px',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
gap: 16,
flexWrap: 'wrap'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 14
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 46,
height: 46,
borderRadius: '50%',
background: 'rgba(255,255,255,.18)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}
}, /*#__PURE__*/React.createElement(UCIcon, {
name: "crown",
size: 23,
color: "#fff"
})), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12,
opacity: .8
}
}, t('curPlan')), /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontSize: 19,
fontWeight: 700,
marginTop: 2
}
}, free ? t('freePlan') : t('proMember')), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 11.5,
opacity: .8,
marginTop: 3,
fontFamily: 'var(--font-mono)',
whiteSpace: 'nowrap'
}
}, free ? t('quotaFree') : `${t('expires')} 2026-12-31`))), /*#__PURE__*/React.createElement("button", {
onClick: goRedeem,
style: {
border: 'none',
background: '#fff',
color: 'var(--clay-700)',
fontWeight: 700,
fontSize: 13.5,
padding: '11px 20px',
borderRadius: 'var(--radius-full)',
cursor: 'pointer',
display: 'inline-flex',
alignItems: 'center',
gap: 7
}
}, /*#__PURE__*/React.createElement(UCIcon, {
name: "ticket",
size: 15,
color: "var(--clay-700)"
}), t('renew'))), /*#__PURE__*/React.createElement("div", {
style: {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit,minmax(160px,1fr))',
gap: 14
}
}, [['clock', t('quotaToday'), free ? '6' : '∞', free ? 'min' : ''], ['arrow-down', t('dataToday'), '0.8', 'GB'], ['smartphone', t('devices'), '2 / 5', '']].map(([ic, l, v, u]) => /*#__PURE__*/React.createElement("div", {
key: l,
style: {
...ucCard,
padding: '16px 18px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 7,
color: 'var(--fg3)',
fontSize: 12,
fontWeight: 600
}
}, /*#__PURE__*/React.createElement(UCIcon, {
name: ic,
size: 15,
color: "var(--accent)"
}), l), /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 23,
fontWeight: 500,
color: 'var(--fg1)',
marginTop: 7
}
}, v, u && /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 12,
color: 'var(--fg3)'
}
}, " ", u))))), /*#__PURE__*/React.createElement("div", {
style: {
display: 'grid',
gridTemplateColumns: mobile ? '1fr' : '1.6fr 1fr',
gap: 14,
alignItems: 'start'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
...ucCard,
padding: '18px 22px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 13.5,
fontWeight: 600,
color: 'var(--fg1)',
marginBottom: 16
}
}, t('usageTitle')), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'flex-end',
gap: 14,
height: 110
}
}, vals.map((v, i) => /*#__PURE__*/React.createElement("div", {
key: i,
style: {
flex: 1,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 6
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 9.5,
color: 'var(--fg3)'
}
}, v), /*#__PURE__*/React.createElement("div", {
style: {
width: '100%',
maxWidth: 30,
height: `${v / max * 76}px`,
background: 'var(--accent)',
borderRadius: '5px 5px 0 0',
opacity: .85
}
}), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 10.5,
color: 'var(--fg3)'
}
}, labels[i]))))), /*#__PURE__*/React.createElement("div", {
style: {
...ucCard,
padding: '16px 18px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 13.5,
fontWeight: 600,
color: 'var(--fg1)',
marginBottom: 12
}
}, t('quickSub')), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
gap: 8
}
}, [['link', t('qaSub'), goSub], ['ticket', t('qaRedeem'), goRedeem], ['download', t('qaApp'), null]].map(([ic, l, fn]) => /*#__PURE__*/React.createElement("button", {
key: l,
onClick: fn || undefined,
style: {
display: 'flex',
alignItems: 'center',
gap: 11,
width: '100%',
textAlign: 'left',
cursor: 'pointer',
background: 'var(--bg-subtle)',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-md)',
padding: '11px 13px'
}
}, /*#__PURE__*/React.createElement(UCIcon, {
name: ic,
size: 16,
color: "var(--accent)"
}), /*#__PURE__*/React.createElement("span", {
style: {
flex: 1,
fontSize: 13.5,
fontWeight: 600,
color: 'var(--fg1)'
}
}, l), /*#__PURE__*/React.createElement(UCIcon, {
name: "chevron-right",
size: 15,
color: "var(--fg3)"
})))))));
}
/* ───────── Subscription ───────── */
function UCSub({
t,
lang,
mobile
}) {
const [copied, setCopied] = useStateU(false);
const [reset, setReset] = useStateU(false);
const url = 'https://sub.pangolin.vpn/s/8f3kx92m' + (reset ? '-new' : '');
const clients = [{
name: '穿山甲 App',
sub: 'iOS / Android / 桌面',
icon: 'shield-check',
accent: true
}, {
name: 'Shadowrocket',
sub: 'iOS',
icon: 'external-link'
}, {
name: 'Clash Verge',
sub: 'Windows / macOS',
icon: 'external-link'
}, {
name: 'v2rayN',
sub: 'Windows',
icon: 'external-link'
}, {
name: 'sing-box',
sub: '全平台',
icon: 'external-link'
}];
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
gap: 18,
maxWidth: 720
}
}, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontSize: 25,
fontWeight: 700,
color: 'var(--fg1)'
}
}, t('subTitle')), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 13,
color: 'var(--fg3)',
marginTop: 5,
lineHeight: 1.6
}
}, t('subDesc'))), /*#__PURE__*/React.createElement("div", {
style: {
display: 'grid',
gridTemplateColumns: mobile ? '1fr' : '1fr auto',
gap: 14,
alignItems: 'start'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
...ucCard,
padding: '18px 20px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 9,
background: 'var(--bg-subtle)',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-md)',
padding: '11px 13px',
marginBottom: 12
}
}, /*#__PURE__*/React.createElement(UCIcon, {
name: "link",
size: 15,
color: "var(--fg3)"
}), /*#__PURE__*/React.createElement("span", {
style: {
flex: 1,
fontFamily: 'var(--font-mono)',
fontSize: 12.5,
color: 'var(--fg1)',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}
}, url)), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 10,
flexWrap: 'wrap'
}
}, /*#__PURE__*/React.createElement("button", {
onClick: () => {
setCopied(true);
setTimeout(() => setCopied(false), 1800);
},
style: {
display: 'inline-flex',
alignItems: 'center',
gap: 7,
border: 'none',
borderRadius: 'var(--radius-full)',
padding: '10px 18px',
background: 'var(--accent)',
color: '#fff',
fontWeight: 700,
fontSize: 13.5,
cursor: 'pointer'
}
}, /*#__PURE__*/React.createElement(UCIcon, {
name: copied ? 'check' : 'copy',
size: 15,
color: "#fff"
}), copied ? t('subCopied') : t('subCopy')), /*#__PURE__*/React.createElement("button", {
onClick: () => setReset(true),
style: {
display: 'inline-flex',
alignItems: 'center',
gap: 7,
border: '1.5px solid var(--border-strong)',
borderRadius: 'var(--radius-full)',
padding: '10px 18px',
background: 'transparent',
color: 'var(--fg2)',
fontWeight: 600,
fontSize: 13.5,
cursor: 'pointer'
}
}, /*#__PURE__*/React.createElement(UCIcon, {
name: "refresh-cw",
size: 14,
color: "var(--fg2)"
}), t('subReset'))), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 11.5,
color: reset ? 'var(--success)' : 'var(--fg3)',
marginTop: 10,
display: 'flex',
alignItems: 'center',
gap: 6
}
}, reset && /*#__PURE__*/React.createElement(UCIcon, {
name: "check-circle",
size: 13,
color: "var(--success)"
}), reset ? t('subResetOk') : t('subResetSub'))), /*#__PURE__*/React.createElement("div", {
style: {
...ucCard,
padding: '16px',
width: mobile ? '100%' : 170,
boxSizing: 'border-box',
textAlign: 'center'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 138,
height: 138,
margin: '0 auto',
borderRadius: 'var(--radius-md)',
background: 'var(--bg-subtle)',
border: '1px dashed var(--border-strong)',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 8
}
}, /*#__PURE__*/React.createElement(UCIcon, {
name: "qr-code",
size: 42,
color: "var(--fg3)",
stroke: 1.5
}), /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 10.5,
color: 'var(--fg3)',
fontWeight: 600
}
}, t('scanTitle'))), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 10.5,
color: 'var(--fg3)',
marginTop: 10,
lineHeight: 1.5
}
}, t('scanSub')))), /*#__PURE__*/React.createElement("div", {
style: {
...ucCard,
padding: '18px 20px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 14.5,
fontWeight: 700,
color: 'var(--fg1)'
}
}, t('importTitle')), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12.5,
color: 'var(--fg3)',
margin: '4px 0 14px'
}
}, t('importSub')), /*#__PURE__*/React.createElement("div", {
style: {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill,minmax(190px,1fr))',
gap: 10
}
}, clients.map(c => /*#__PURE__*/React.createElement("button", {
key: c.name,
style: {
display: 'flex',
alignItems: 'center',
gap: 11,
textAlign: 'left',
cursor: 'pointer',
background: c.accent ? 'var(--accent-subtle)' : 'var(--surface)',
border: `1px solid ${c.accent ? 'var(--accent-border)' : 'var(--border)'}`,
borderRadius: 'var(--radius-lg)',
padding: '12px 14px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 34,
height: 34,
borderRadius: 'var(--radius-md)',
background: c.accent ? 'var(--accent)' : 'var(--bg-subtle)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0
}
}, /*#__PURE__*/React.createElement(UCIcon, {
name: c.icon,
size: 17,
color: c.accent ? '#fff' : 'var(--accent)'
})), /*#__PURE__*/React.createElement("div", {
style: {
minWidth: 0
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 13.5,
fontWeight: 600,
color: 'var(--fg1)',
whiteSpace: 'nowrap'
}
}, c.name), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 11,
color: 'var(--fg3)'
}
}, c.sub))))), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 11.5,
color: 'var(--fg3)',
marginTop: 12
}
}, t('fmtNote'))));
}
/* ───────── Redeem ───────── */
function UCRedeem({
t,
lang
}) {
const [code, setCode] = useStateU('');
const [ok, setOk] = useStateU(false);
const channels = [{
icon: 'shopping-bag',
name: t('chStore'),
sub: t('chStoreSub'),
accent: true
}, {
icon: 'credit-card',
name: 'USDT (TRC20)',
sub: t('chUsdtSub')
}, {
icon: 'send',
name: 'Telegram',
sub: '@PangolinVPN_bot',
mono: true
}, {
icon: 'message-circle',
name: 'LINE',
sub: '@pangolinvpn',
mono: true
}, {
icon: 'mail',
name: t('chEmail'),
sub: 'buy@pangolin.vpn',
mono: true
}];
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
gap: 18,
maxWidth: 680
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontSize: 25,
fontWeight: 700,
color: 'var(--fg1)'
}
}, t('navRedeem')), /*#__PURE__*/React.createElement("div", {
style: {
...ucCard,
padding: '20px 22px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 14.5,
fontWeight: 700,
color: 'var(--fg1)',
marginBottom: 12
}
}, t('redeemTitle')), ok ? /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 9,
color: 'var(--success)',
fontWeight: 600,
fontSize: 15,
padding: '4px 0'
}
}, /*#__PURE__*/React.createElement(UCIcon, {
name: "check-circle",
size: 19,
color: "var(--success)"
}), t('redeemOk')) : /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 10
}
}, /*#__PURE__*/React.createElement("input", {
value: code,
onChange: e => setCode(e.target.value.toUpperCase()),
placeholder: t('redeemPh'),
style: {
...ucInput,
fontFamily: 'var(--font-mono)',
letterSpacing: '0.08em'
}
}), /*#__PURE__*/React.createElement("button", {
onClick: () => code.length >= 4 && setOk(true),
style: {
border: 'none',
borderRadius: 'var(--radius-md)',
padding: '0 22px',
background: 'var(--accent)',
color: '#fff',
fontWeight: 700,
fontSize: 14,
cursor: 'pointer',
flexShrink: 0
}
}, t('redeemBtn')))), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 14.5,
fontWeight: 700,
color: 'var(--fg1)',
marginBottom: 5
}
}, t('buyTitle')), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12.5,
color: 'var(--fg3)',
lineHeight: 1.5,
marginBottom: 13
}
}, t('buySub')), /*#__PURE__*/React.createElement("div", {
style: {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit,minmax(240px,1fr))',
gap: 11
}
}, channels.map(c => /*#__PURE__*/React.createElement("button", {
key: c.name,
style: {
display: 'flex',
alignItems: 'center',
gap: 12,
textAlign: 'left',
cursor: 'pointer',
background: c.accent ? 'var(--accent-subtle)' : 'var(--surface)',
border: `1px solid ${c.accent ? 'var(--accent-border)' : 'var(--border)'}`,
borderRadius: 'var(--radius-lg)',
padding: '13px 15px',
boxShadow: 'var(--shadow-sm)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: 38,
height: 38,
borderRadius: 'var(--radius-md)',
background: c.accent ? 'var(--accent)' : 'var(--bg-subtle)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0
}
}, /*#__PURE__*/React.createElement(UCIcon, {
name: c.icon,
size: 18,
color: c.accent ? '#fff' : 'var(--accent)'
})), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
minWidth: 0
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 14,
fontWeight: 600,
color: 'var(--fg1)'
}
}, c.name), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 11.5,
color: 'var(--fg3)',
fontFamily: c.mono ? 'var(--font-mono)' : 'var(--font-sans)',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}
}, c.sub)), /*#__PURE__*/React.createElement(UCIcon, {
name: "external-link",
size: 15,
color: "var(--fg3)"
}))))));
}
/* ───────── Invite ───────── */
function UCInvite({
t,
lang
}) {
const [copied, setCopied] = useStateU(false);
const records = [{
mail: 'k***@gmail.com',
date: '2026-06-08',
paid: true
}, {
mail: 'w***@outlook.com',
date: '2026-06-05',
paid: false
}, {
mail: 'z***@qq.com',
date: '2026-05-30',
paid: true
}];
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
gap: 18,
maxWidth: 680
}
}, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-display)',
fontSize: 25,
fontWeight: 700,
color: 'var(--fg1)'
}
}, t('inviteTitle')), /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 13,
color: 'var(--fg3)',
marginTop: 5,
lineHeight: 1.6
}
}, t('inviteSub'))), /*#__PURE__*/React.createElement("div", {
style: {
...ucCard,
padding: '18px 20px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12.5,
fontWeight: 600,
color: 'var(--fg2)',
marginBottom: 8
}
}, t('inviteLink')), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 10
}
}, /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
display: 'flex',
alignItems: 'center',
gap: 9,
background: 'var(--bg-subtle)',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-md)',
padding: '11px 13px',
minWidth: 0
}
}, /*#__PURE__*/React.createElement(UCIcon, {
name: "gift",
size: 15,
color: "var(--fg3)"
}), /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 12.5,
color: 'var(--fg1)',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}
}, "https://pangolin.vpn/r/PG8F3K")), /*#__PURE__*/React.createElement("button", {
onClick: () => {
setCopied(true);
setTimeout(() => setCopied(false), 1800);
},
style: {
display: 'inline-flex',
alignItems: 'center',
gap: 7,
border: 'none',
borderRadius: 'var(--radius-md)',
padding: '0 18px',
background: 'var(--accent)',
color: '#fff',
fontWeight: 700,
fontSize: 13.5,
cursor: 'pointer',
flexShrink: 0
}
}, /*#__PURE__*/React.createElement(UCIcon, {
name: copied ? 'check' : 'copy',
size: 15,
color: "#fff"
}), copied ? t('subCopied') : t('subCopy')))), /*#__PURE__*/React.createElement("div", {
style: {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit,minmax(150px,1fr))',
gap: 14
}
}, [[t('invited'), '8', ''], [t('paidUsers'), '3', ''], [t('earned'), '¥45', '']].map(([l, v]) => /*#__PURE__*/React.createElement("div", {
key: l,
style: {
...ucCard,
padding: '16px 18px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
fontSize: 12,
color: 'var(--fg3)',
fontWeight: 600
}
}, l), /*#__PURE__*/React.createElement("div", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 23,
fontWeight: 500,
color: 'var(--fg1)',
marginTop: 7
}
}, v)))), /*#__PURE__*/React.createElement("div", {
style: {
...ucCard,
overflow: 'hidden'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
padding: '13px 18px',
fontSize: 13.5,
fontWeight: 700,
color: 'var(--fg1)',
borderBottom: '1px solid var(--border)'
}
}, t('inviteRecord')), records.map((r, i) => /*#__PURE__*/React.createElement("div", {
key: i,
style: {
display: 'flex',
alignItems: 'center',
gap: 12,
padding: '12px 18px',
borderBottom: i < records.length - 1 ? '1px solid var(--border)' : 'none'
}
}, /*#__PURE__*/React.createElement("span", {
style: {
flex: 1,
fontFamily: 'var(--font-mono)',
fontSize: 13,
color: 'var(--fg1)'
}
}, r.mail), /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-mono)',
fontSize: 12,
color: 'var(--fg3)'
}
}, r.date), /*#__PURE__*/React.createElement("span", {
style: {
fontSize: 11,
fontWeight: 600,
padding: '3px 10px',
borderRadius: 999,
background: r.paid ? 'var(--success-subtle)' : 'var(--bg-subtle)',
color: r.paid ? 'var(--success)' : 'var(--fg3)'
}
}, r.paid ? t('recPaid') : t('recRegistered'))))));
}
/* ───────── App shell ───────── */
function UserCenterApp() {
const [stage, setStage] = useStateU('app'); // app | login — opens on the dashboard
const [view, setView] = useStateU('overview');
const [lang, setLang] = useStateU('zh');
const [free] = useStateU(false);
const [dir, setDir] = useStateU(0); // -1 左进 / 1 右进 / 0 无动画
const mobile = useIsMobileUC();
const t = ucT(lang);
const touchRef = React.useRef({
x: 0,
y: 0
});
const ORDER = ['overview', 'sub', 'redeem', 'invite'];
function go(id) {
const a = ORDER.indexOf(view),
b = ORDER.indexOf(id);
setDir(b > a ? 1 : b < a ? -1 : 0);
setView(id);
setTimeout(() => setDir(0), 300);
}
function onTouchStart(e) {
const p = e.touches ? e.touches[0] : e;
touchRef.current = {
x: p.clientX,
y: p.clientY
};
}
function onTouchEnd(e) {
const p = e.changedTouches ? e.changedTouches[0] : e;
const dx = p.clientX - touchRef.current.x,
dy = p.clientY - touchRef.current.y;
if (Math.abs(dx) > 60 && Math.abs(dx) > Math.abs(dy) * 1.5) {
const i = ORDER.indexOf(view);
const ni = dx < 0 ? Math.min(ORDER.length - 1, i + 1) : Math.max(0, i - 1);
if (ni !== i) go(ORDER[ni]);
}
}
if (stage === 'login') return /*#__PURE__*/React.createElement(UCLogin, {
t: t,
lang: lang,
setLang: setLang,
onDone: () => setStage('app')
});
const nav = [['overview', 'layout-dashboard', t('navOverview')], ['sub', 'link', t('navSub')], ['redeem', 'ticket', t('navRedeem')], ['invite', 'users', t('navInvite')]];
let main;
if (view === 'overview') main = /*#__PURE__*/React.createElement(UCOverview, {
t: t,
lang: lang,
free: free,
mobile: mobile,
goSub: () => setView('sub'),
goRedeem: () => setView('redeem')
});else if (view === 'sub') main = /*#__PURE__*/React.createElement(UCSub, {
t: t,
lang: lang,
mobile: mobile
});else if (view === 'redeem') main = /*#__PURE__*/React.createElement(UCRedeem, {
t: t,
lang: lang
});else main = /*#__PURE__*/React.createElement(UCInvite, {
t: t,
lang: lang
});
const navBtns = nav.map(([id, ic, l]) => /*#__PURE__*/React.createElement("button", {
key: id,
onClick: () => go(id),
style: {
display: 'inline-flex',
alignItems: 'center',
gap: 7,
border: 'none',
cursor: 'pointer',
borderRadius: 'var(--radius-md)',
padding: '8px 13px',
flexShrink: 0,
background: view === id ? 'var(--accent-subtle)' : 'transparent',
color: view === id ? 'var(--accent)' : 'var(--fg2)',
fontFamily: 'var(--font-sans)',
fontSize: 13.5,
fontWeight: view === id ? 700 : 500,
whiteSpace: 'nowrap'
}
}, /*#__PURE__*/React.createElement(UCIcon, {
name: ic,
size: 15,
color: view === id ? 'var(--accent)' : 'var(--fg3)'
}), l));
return /*#__PURE__*/React.createElement("div", {
style: {
minHeight: '100vh',
background: 'var(--bg)',
fontFamily: 'var(--font-sans)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
position: 'sticky',
top: 0,
zIndex: 10,
background: 'color-mix(in srgb, var(--bg) 85%, transparent)',
backdropFilter: 'blur(12px)',
borderBottom: '1px solid var(--border)'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
maxWidth: 1000,
margin: '0 auto',
padding: mobile ? '0 16px' : '0 24px',
height: mobile ? 54 : 60,
display: 'flex',
alignItems: 'center',
gap: mobile ? 12 : 22
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 9
}
}, /*#__PURE__*/React.createElement(UCMark, {
size: 26
}), /*#__PURE__*/React.createElement("span", {
style: {
fontFamily: 'var(--font-display)',
fontWeight: 700,
fontSize: 16.5,
color: 'var(--fg1)'
}
}, "\u7A7F\u5C71\u7532")), !mobile && /*#__PURE__*/React.createElement("nav", {
style: {
display: 'flex',
gap: 4,
flex: 1
}
}, navBtns), mobile && /*#__PURE__*/React.createElement("div", {
style: {
flex: 1
}
}), /*#__PURE__*/React.createElement(UCLang, {
lang: lang,
setLang: setLang
}), /*#__PURE__*/React.createElement("button", {
onClick: () => setStage('login'),
style: {
display: 'inline-flex',
alignItems: 'center',
gap: 7,
border: 'none',
background: 'transparent',
cursor: 'pointer',
color: 'var(--fg2)',
fontSize: 13,
fontWeight: 600,
padding: 6
}
}, /*#__PURE__*/React.createElement(UCIcon, {
name: "log-out",
size: 15,
color: "var(--fg3)"
}), !mobile && t('signOut'))), mobile && /*#__PURE__*/React.createElement("nav", {
style: {
display: 'flex',
gap: 2,
padding: '0 12px 8px',
overflowX: 'auto'
}
}, navBtns)), /*#__PURE__*/React.createElement("div", {
onTouchStart: mobile ? onTouchStart : undefined,
onTouchEnd: mobile ? onTouchEnd : undefined,
style: {
maxWidth: 1000,
margin: '0 auto',
padding: mobile ? '20px 16px 40px' : '30px 24px 48px'
}
}, /*#__PURE__*/React.createElement("div", {
key: view,
onAnimationEnd: () => setDir(0),
style: {
animation: dir !== 0 ? `uc-in-${dir === 1 ? 'l' : 'r'} 200ms var(--ease-out)` : 'none'
}
}, main)));
}
Object.assign(window, {
UserCenterApp
});
})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/usercenter/ucapp.jsx", error: String((e && e.message) || e) }); }
// ui_kits/usercenter/ucparts.jsx
try { (() => {
/* ucparts.jsx — 穿山甲 Web 用户中心 · atoms + i18n */
const UCLUCIDE = {
'layout-dashboard': '<rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/><rect width="7" height="9" x="14" y="12" rx="1"/><rect width="7" height="5" x="3" y="16" rx="1"/>',
link: '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>',
ticket: '<path d="M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"/><path d="M13 5v2"/><path d="M13 17v2"/><path d="M13 11v2"/>',
users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
copy: '<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>',
'refresh-cw': '<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M3 21v-5h5"/>',
'qr-code': '<rect width="5" height="5" x="3" y="3" rx="1"/><rect width="5" height="5" x="16" y="3" rx="1"/><rect width="5" height="5" x="3" y="16" rx="1"/><path d="M21 16h-3a2 2 0 0 0-2 2v3"/><path d="M21 21v.01"/><path d="M12 7v3a2 2 0 0 1-2 2H7"/><path d="M3 12h.01"/><path d="M12 3h.01"/><path d="M12 16v.01"/><path d="M16 12h1"/><path d="M21 12v.01"/><path d="M12 21v-1"/>',
download: '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/>',
check: '<path d="M20 6 9 17l-5-5"/>',
'check-circle': '<path d="M21.801 10A10 10 0 1 1 17 3.335"/><path d="m9 11 3 3L22 4"/>',
zap: '<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>',
clock: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
'shield-check': '<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/><path d="m9 12 2 2 4-4"/>',
crown: '<path d="M11.562 3.266a.5.5 0 0 1 .876 0L15.39 8.87a1 1 0 0 0 1.516.294L21.183 5.5a.5.5 0 0 1 .798.519l-2.834 10.246a1 1 0 0 1-.956.734H5.81a1 1 0 0 1-.957-.734L2.02 6.02a.5.5 0 0 1 .798-.519l4.276 3.664a1 1 0 0 0 1.516-.294z"/><path d="M5 21h14"/>',
'credit-card': '<rect width="20" height="14" x="2" y="5" rx="2"/><line x1="2" x2="22" y1="10" y2="10"/>',
send: '<path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/><path d="m21.854 2.147-10.94 10.939"/>',
'message-circle': '<path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/>',
mail: '<rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/>',
'shopping-bag': '<path d="M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4Z"/><path d="M3 6h18"/><path d="M16 10a4 4 0 0 1-8 0"/>',
'log-out': '<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" x2="9" y1="12" y2="12"/>',
'external-link': '<path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>',
gift: '<rect x="3" y="8" width="18" height="4" rx="1"/><path d="M12 8v13"/><path d="M19 12v7a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-7"/><path d="M7.5 8a2.5 2.5 0 0 1 0-5A4.8 8 0 0 1 12 8a4.8 8 0 0 1 4.5-5 2.5 2.5 0 0 1 0 5"/>',
smartphone: '<rect width="14" height="20" x="5" y="2" rx="2" ry="2"/><path d="M12 18h.01"/>',
lock: '<rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
'chevron-right': '<path d="m9 18 6-6-6-6"/>',
'arrow-down': '<path d="M12 5v14"/><path d="m19 12-7 7-7-7"/>'
};
function UCIcon({
name,
size = 18,
stroke = 2,
color = 'currentColor',
style = {}
}) {
return /*#__PURE__*/React.createElement("svg", {
width: size,
height: size,
viewBox: "0 0 24 24",
fill: "none",
stroke: color,
strokeWidth: stroke,
strokeLinecap: "round",
strokeLinejoin: "round",
style: {
display: 'block',
flexShrink: 0,
...style
},
dangerouslySetInnerHTML: {
__html: UCLUCIDE[name] || ''
}
});
}
function UCMark({
size = 30,
color = 'var(--accent)'
}) {
return /*#__PURE__*/React.createElement("svg", {
width: size,
height: size,
viewBox: "0 0 96 96",
fill: "none",
style: {
display: 'block'
}
}, /*#__PURE__*/React.createElement("g", {
transform: "translate(96,0) scale(-1,1)"
}, /*#__PURE__*/React.createElement("path", {
d: "M16 59 Q17 51 26 50 Q34 31 45 30 Q55 30 62 37 Q69 41 75 45 Q85 49 89 44 Q92 50 84 52 Q75 53 67 53 Q66 62 60 62 L56 62 Q54 55 49 55 Q47 62 37 62 L33 62 Q31 56 26 56 Q20 59 16 59 Z",
fill: color
}), /*#__PURE__*/React.createElement("g", {
stroke: "#fff",
strokeWidth: "2.4",
strokeLinecap: "round",
strokeLinejoin: "round",
fill: "none",
opacity: "0.5"
}, /*#__PURE__*/React.createElement("path", {
d: "M27 50 Q35 43 43 50"
}), /*#__PURE__*/React.createElement("path", {
d: "M37 48 Q45 41 53 48"
}), /*#__PURE__*/React.createElement("path", {
d: "M47 48 Q55 41 63 49"
})), /*#__PURE__*/React.createElement("circle", {
cx: "19",
cy: "56",
r: "2.2",
fill: "#fff",
opacity: "0.85"
})));
}
const UCSTRINGS = {
navOverview: {
zh: '概览',
en: 'Overview'
},
navSub: {
zh: '订阅',
en: 'Subscription'
},
navRedeem: {
zh: '兑换 & 购买',
en: 'Redeem & buy'
},
navInvite: {
zh: '邀请返利',
en: 'Referral'
},
signOut: {
zh: '退出',
en: 'Sign out'
},
/* login */
loginTitle: {
zh: '登录用户中心',
en: 'Log in to your account'
},
loginSub: {
zh: '管理订阅、兑换激活码、查看用量',
en: 'Manage subscription, redeem codes, track usage'
},
emailLabel: {
zh: '邮箱',
en: 'Email'
},
emailPh: {
zh: '你的邮箱地址',
en: 'your@email.com'
},
pwLabel: {
zh: '密码',
en: 'Password'
},
pwPh: {
zh: '输入密码',
en: 'Enter password'
},
doLogin: {
zh: '登录',
en: 'Log in'
},
forgotPw: {
zh: '忘记密码?',
en: 'Forgot password?'
},
noAccount: {
zh: '没有账户?在 App 内注册',
en: 'No account? Sign up in the app'
},
/* overview */
greeting: {
zh: '欢迎回来',
en: 'Welcome back'
},
curPlan: {
zh: '当前套餐',
en: 'Current plan'
},
freePlan: {
zh: '免费版',
en: 'Free'
},
proMember: {
zh: 'PRO 会员',
en: 'PRO member'
},
expires: {
zh: '有效期至',
en: 'Expires'
},
renew: {
zh: '续费 / 升级',
en: 'Renew / Upgrade'
},
quotaToday: {
zh: '今日剩余时长',
en: 'Time left today'
},
dataToday: {
zh: '今日已用流量',
en: 'Data used today'
},
devices: {
zh: '在线设备',
en: 'Devices online'
},
quotaFree: {
zh: '免费版 · 每日 10 分钟',
en: 'Free · 10 min/day'
},
usageTitle: {
zh: '近 7 日流量 (GB)',
en: 'Last 7 days (GB)'
},
quickSub: {
zh: '快速操作',
en: 'Quick actions'
},
qaSub: {
zh: '获取订阅链接',
en: 'Get subscription'
},
qaRedeem: {
zh: '兑换激活码',
en: 'Redeem a code'
},
qaApp: {
zh: '下载 App',
en: 'Download apps'
},
/* subscription */
subTitle: {
zh: '我的订阅',
en: 'My subscription'
},
subDesc: {
zh: '订阅链接是你的专属凭证,泄露后他人可使用你的额度。请勿分享。',
en: 'Your subscription link is a private credential. Never share it.'
},
subCopy: {
zh: '复制链接',
en: 'Copy link'
},
subCopied: {
zh: '已复制',
en: 'Copied'
},
subReset: {
zh: '重置链接',
en: 'Reset link'
},
subResetSub: {
zh: '旧链接立即失效,所有设备需重新导入',
en: 'Old link stops working; re-import on all devices'
},
subResetOk: {
zh: '已重置,请重新导入',
en: 'Reset — re-import on your devices'
},
scanTitle: {
zh: '扫码导入',
en: 'Scan to import'
},
scanSub: {
zh: '用穿山甲 App 或三方客户端扫码',
en: 'Scan with the Pangolin app or a 3rd-party client'
},
importTitle: {
zh: '一键导入三方客户端',
en: 'One-tap import'
},
importSub: {
zh: '已安装对应客户端时,点击即自动导入订阅。',
en: 'If the client is installed, tapping imports the subscription automatically.'
},
fmtNote: {
zh: '同一链接同时兼容 sing-box / Clash.Meta / v2ray 格式。',
en: 'One link serves sing-box / Clash.Meta / v2ray formats.'
},
/* redeem */
redeemTitle: {
zh: '兑换激活码',
en: 'Redeem a code'
},
redeemPh: {
zh: '输入激活码',
en: 'Enter activation code'
},
redeemBtn: {
zh: '激活',
en: 'Redeem'
},
redeemOk: {
zh: '激活成功 · 套餐已到账',
en: 'Activated — plan applied'
},
buyTitle: {
zh: '购买渠道',
en: 'Where to buy'
},
buySub: {
zh: '本站不直接收款。通过以下渠道购买激活码,回到本页兑换即可:',
en: 'We never take payment on this site. Buy a code via a channel below, then redeem here:'
},
chStore: {
zh: '自助发卡商店',
en: 'Self-serve store'
},
chStoreSub: {
zh: '支付宝 / 微信 · 自动发码',
en: 'Alipay / WeChat · instant code'
},
chUsdtSub: {
zh: '链上转账 · 最隐私 · 自动发码',
en: 'On-chain · most private · instant code'
},
chEmail: {
zh: '邮箱客服',
en: 'Email support'
},
/* invite */
inviteTitle: {
zh: '邀请返利',
en: 'Referral'
},
inviteSub: {
zh: '好友通过你的链接注册并付费,你获得其首单 20% 余额返利,可抵扣续费。',
en: 'When a friend signs up and pays via your link, you earn 20% of their first order as credit.'
},
inviteLink: {
zh: '我的邀请链接',
en: 'My invite link'
},
invited: {
zh: '已邀请',
en: 'Invited'
},
paidUsers: {
zh: '已付费',
en: 'Converted'
},
earned: {
zh: '累计返利',
en: 'Credit earned'
},
inviteRecord: {
zh: '邀请记录',
en: 'History'
},
recEmpty: {
zh: '暂无邀请记录',
en: 'No referrals yet'
},
recRegistered: {
zh: '已注册',
en: 'Registered'
},
recPaid: {
zh: '已付费',
en: 'Paid'
},
/* 2FA */
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'
}
};
function ucT(lang) {
return k => {
const v = UCSTRINGS[k];
return v ? v[lang] || k : k;
};
}
Object.assign(window, {
UCIcon,
UCMark,
UCSTRINGS,
ucT
});
})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/usercenter/ucparts.jsx", error: String((e && e.message) || e) }); }
// ui_kits/website/site.js
try { (() => {
/* site.js — 穿山甲 VPN 官网 · i18n + 交互 */
(function () {
// ---------- i18n ----------
const I18N = {
'ann.txt': ['新用户注册即享 7 天免费试用', 'New users get a 7-day free trial'],
'ann.cta': ['立即注册 →', 'Sign up →'],
'su.ph': ['输入邮箱,免费注册', 'Enter email to sign up free'],
'su.btn': ['免费注册', 'Sign up free'],
'su.ok': ['验证码已发送!下载 App 并用同一邮箱继续完成注册。', 'Code sent! Download the app and continue with the same email.'],
'su.hint': ['免费注册 · 无需付款 · 7 天免费试用', 'Free to join · no payment · 7-day free trial'],
'su.dl': ['或直接下载 App', 'or just download the app'],
'why.eyebrow': ['免费注册', 'Sign up'],
'why.h': ['注册一次,处处可用', 'One account, everywhere'],
'why.sub': ['一个邮箱就能开始。注册免费,不需要任何付款信息。', 'All you need is an email. Free to join — no payment details, ever.'],
'why.1t': ['7 天免费试用', '7-day free trial'],
'why.1d': ['新用户注册即享 7 天免费使用,无需任何付款信息。', 'New accounts get 7 days of free, unrestricted use — no payment details.'],
'why.2t': ['多端同步', 'Multi-device sync'],
'why.2d': ['手机、平板、电脑共用一个账户,套餐与设置随身走。', 'Phone, tablet and desktop share one account — your plan follows you.'],
'why.3t': ['无需付款信息', 'No payment details'],
'why.3d': ['注册只要邮箱。想升级时,再通过外部渠道兑换激活码。', 'Just an email to join. Upgrade later with an activation code.'],
'why.s1': ['填写邮箱', 'Enter email'],
'why.s2': ['输入验证码', 'Verify code'],
'why.s3': ['开始畅连', 'Start connecting'],
'why.cta': ['立即免费注册', 'Sign up free'],
'cta.btn2': ['免费注册', 'Sign up free'],
'nav.product': ['产品', 'Product'],
'nav.pricing': ['定价', 'Pricing'],
'nav.download': ['下载', 'Download'],
'nav.docs': ['文档', 'Docs'],
'nav.blog': ['Blog', 'Blog'],
'nav.login': ['登录', 'Log in'],
'nav.get': ['立即下载', 'Get the app'],
'hero.eyebrow': ['极速 · 稳定 · 省心', 'Fast · Stable · Effortless'],
'hero.h1': ['极速畅连,\n网络如丝顺滑', 'Faster, smoother,\neverywhere'],
'hero.lede': ['轻盈、亲和、即开即用的跨平台网络加速应用。一键连接,智能选线,稳定不掉线。', 'A lightweight, friendly cross-platform network accelerator. One tap, smart routing, rock-solid.'],
'hero.cta1': ['免费下载', 'Download free'],
'hero.cta2': ['查看定价', 'See pricing'],
'hero.t1': ['80+ 全球加速线路', '80+ global routes'],
'hero.t2': ['严格无日志', 'Strict no-logs'],
'hero.t3': ['端到端加密', 'End-to-end encrypted'],
'orb.cap': ['已连接', 'CONNECTED'],
'orb.down': ['下载', 'Down'],
'orb.up': ['上传', 'Up'],
'orb.node': ['香港 · 流媒体', 'Hong Kong'],
'orb.nodesub': ['延迟 18ms · 已连接', '18ms · Connected'],
'strip.txt': ['已支持', 'Available on'],
'feat.eyebrow': ['产品', 'Product'],
'feat.h': ['为日常而生的连接体验', 'Built for everyday connection'],
'feat.sub': ['没有复杂配置,没有技术门槛。打开就用,把稳定与隐私留给我们。', 'No complex setup, no jargon. Just open and go — we handle speed and privacy.'],
'feat.1t': ['一键连接', 'One-tap connect'],
'feat.1d': ['一颗按钮,智能挑选最快线路。无需手动配置,也能秒连。', 'One button picks the fastest route. No manual setup, instant connect.'],
'feat.2t': ['智能分流', 'Smart routing'],
'feat.2d': ['按规则智能分流,该加速的加速,本地服务直连,互不打扰。', 'Smart rule-based routing — accelerate what needs it, keep local traffic direct.'],
'feat.3t': ['80+ 全球加速线路', '80+ global routes'],
'feat.3d': ['覆盖港日新美欧韩等地,视频与游戏优化线路随心选。', 'Routes across HK / JP / SG / US / EU and more, tuned for video & gaming.'],
'feat.4t': ['严格无日志', 'Strict no-logs'],
'feat.4d': ['我们不记录你的浏览数据。隐私是底线,不是卖点。', 'We never log your browsing. Privacy is the baseline, not a feature.'],
'feat.5t': ['Kill Switch', 'Kill Switch'],
'feat.5d': ['一旦连接中断,立即阻断网络,杜绝真实 IP 泄露。', 'If the tunnel drops, traffic is blocked instantly — no IP leaks.'],
'feat.6t': ['多端同步', 'Multi-device'],
'feat.6d': ['一个账户,手机、平板、电脑同时在线,最多 5 台设备。', 'One account across phone, tablet and desktop — up to 5 devices.'],
'price.eyebrow': ['定价', 'Pricing'],
'price.h': ['简单透明的价格', 'Simple, honest pricing'],
'price.sub': ['随时升级或取消。所有套餐均含核心加密与无日志承诺。', 'Upgrade or cancel anytime. Every plan includes core encryption and our no-logs promise.'],
'price.monthly': ['按月', 'Monthly'],
'price.yearly': ['按年', 'Yearly'],
'price.save': ['省 20%', 'Save 20%'],
'price.permo': ['/月', '/mo'],
'price.free': ['免费版', 'Free'],
'price.freedesc': ['适合轻度使用与尝鲜', 'For light, casual use'],
'price.pro': ['专业版', 'Pro'],
'price.prodesc': ['个人用户的最佳选择', 'Best for individuals'],
'price.team': ['团队版', 'Team'],
'price.teamdesc': ['小团队集中管理', 'For small teams'],
'price.popular': ['最受欢迎', 'Most popular'],
'price.cta_free': ['免费下载', 'Download free'],
'price.cta_pro': ['获取激活码', 'Get a code'],
'price.cta_team': ['获取激活码', 'Get a code'],
'pf.free1': ['每日 10 分钟时长', '10 min per day'],
'pf.free2': ['仅 1 个基础节点', '1 basic node only'],
'pf.free3': ['使用前观看广告', 'Watch an ad to start'],
'pf.free4': ['核心加密 · 无日志', 'Core encryption · no-logs'],
'pf.pro1': ['80+ 全球加速线路', '80+ global routes'],
'pf.pro2': ['无限流量 · 极速', 'Unlimited · top speed'],
'pf.pro3': ['5 台设备同时在线', '5 devices at once'],
'pf.pro4': ['流媒体 & P2P 优化', 'Streaming & P2P routes'],
'pf.pro5': ['Kill Switch · 智能分流', 'Kill Switch · smart routing'],
'pf.team1': ['专业版全部功能', 'Everything in Pro'],
'pf.team2': ['10 个成员席位', '10 seats'],
'pf.team3': ['集中计费与管理', 'Central billing & admin'],
'pf.team4': ['优先客服', 'Priority support'],
'pay.title': ['为什么网页上没有支付按钮?', 'Why no checkout on this page?'],
'pay.body': ['出于风控与隐私考虑,穿山甲不在网页或 App 内直接收款。你可以通过以下任一渠道获取激活码,在客户端内兑换即可开通。', 'For risk and privacy reasons, Pangolin never takes payment in the web or app. Get an activation code through any channel below, then redeem it in the client.'],
'pay.store': ['自助发卡商店', 'Self-serve store'],
'pay.usdt': ['USDT (TRC20) · 最隐私', 'USDT (TRC20) · most private'],
'cmp.h': ['功能对比', 'Compare plans'],
'cmp.feature': ['功能', 'Feature'],
'cmp.locations': ['可用线路', 'Routes'],
'cmp.data': ['流量', 'Data'],
'cmp.time': ['每日时长', 'Daily time'],
'cmp.time_free': ['10 分钟/天(试用期不限)', '10 min/day (trial: unlimited)'],
'cmp.devices': ['设备数', 'Devices'],
'cmp.speed': ['极速线路', 'Top-speed routes'],
'cmp.stream': ['视频优化', 'Video routes'],
'cmp.kill': ['Kill Switch', 'Kill Switch'],
'cmp.support': ['客服', 'Support'],
'cmp.unlimited': ['无限', 'Unlimited'],
'cmp.daily': ['不限', 'Unlimited'],
'cmp.basic': ['标准', 'Standard'],
'cmp.priority': ['优先', 'Priority'],
'dl.eyebrow': ['下载', 'Download'],
'dl.h': ['全平台,随处可用', 'Every platform, everywhere'],
'dl.sub': ['一个账户,所有设备同步。下载即用,无需配置。', 'One account syncs every device. Download and go.'],
'dl.get': ['下载', 'Download'],
'docs.eyebrow': ['文档', 'Docs'],
'docs.h': ['需要帮助?都在这里', 'Need help? Its all here'],
'docs.sub': ['从快速上手到协议细节,清晰直接。', 'From quickstart to protocol details — clear and direct.'],
'docs.1t': ['快速开始', 'Quickstart'],
'docs.1d': ['三分钟完成注册、下载与首次连接。', 'Sign up, download and connect in three minutes.'],
'docs.2t': ['常见问题', 'FAQ'],
'docs.2d': ['连接、计费、设备与兑换码的常见疑问。', 'Connection, billing, devices and redeem codes.'],
'docs.3t': ['协议与安全', 'Protocol & security'],
'docs.3d': ['WireGuard、加密方式与无日志架构说明。', 'WireGuard, encryption and our no-logs architecture.'],
'docs.4t': ['隐私政策', 'Privacy policy'],
'docs.4d': ['我们收集什么、不收集什么,一目了然。', 'Exactly what we collect — and what we never do.'],
'docs.read': ['阅读', 'Read'],
'blog.eyebrow': ['Blog', 'Blog'],
'blog.h': ['来自团队的最新动态', 'Latest from the team'],
'blog.sub': ['产品更新、安全科普与节点公告。', 'Product updates, security explainers and node news.'],
'blog.1tag': ['产品更新', 'Product'],
'blog.1t': ['v2.4:更快的智能分流与全新统计页', 'v2.4: faster smart routing & a new stats tab'],
'blog.1d': ['本次更新重写了分流引擎,连接建立速度提升约 40%。', 'We rewrote the routing engine — connections are ~40% faster.'],
'blog.2tag': ['安全', 'Security'],
'blog.2t': ['无日志到底意味着什么?', 'What “no-logs” really means'],
'blog.2d': ['我们逐条拆解收集与不收集的数据,以及背后的架构。', 'A line-by-line look at what we keep — and what we never touch.'],
'blog.3tag': ['公告', 'News'],
'blog.3t': ['新增首尔与法兰克福高速节点', 'New high-speed nodes: Seoul & Frankfurt'],
'blog.3d': ['两条新线路已上线,为东北亚与欧洲用户带来更低延迟。', 'Two new routes are live, cutting latency for NE Asia and Europe.'],
'blog.readmore': ['阅读全文', 'Read more'],
'cta.h': ['准备好体验极速畅连了吗?', 'Ready for a faster connection?'],
'cta.p': ['免费下载,数分钟内开始你的第一次极速连接。', 'Download free and get connected in minutes.'],
'cta.btn': ['免费下载', 'Download free'],
'ft.tag': ['极简、轻量、亲和的跨平台网络加速应用。守护你的每一次连接。', 'A minimal, friendly cross-platform network accelerator. Protecting every connection.'],
'ft.product': ['产品', 'Product'],
'ft.resources': ['资源', 'Resources'],
'ft.contact': ['联系与渠道', 'Contact'],
'ft.features': ['功能', 'Features'],
'ft.pricing': ['定价', 'Pricing'],
'ft.download': ['下载', 'Download'],
'ft.docs': ['文档', 'Docs'],
'ft.blog': ['Blog', 'Blog'],
'ft.faq': ['常见问题', 'FAQ'],
'ft.privacy': ['隐私政策', 'Privacy'],
'ft.status': ['服务状态', 'Status'],
'ft.hours': ['服务时间 每日 9:0024:00 (GMT+8)', 'Hours: Daily 9:0024:00 (GMT+8)'],
'ft.copy': ['© 2026 穿山甲 · Pangolin', '© 2026 Pangolin'],
'ft.madenote': ['App 内不支持直接支付 · 资金流全走外部渠道', 'No in-app payment · all purchases via external channels']
};
let lang = localStorage.getItem('pg_site_lang') || 'zh';
function applyLang() {
const i = lang === 'zh' ? 0 : 1;
document.documentElement.lang = lang === 'zh' ? 'zh' : 'en';
document.querySelectorAll('[data-i18n]').forEach(el => {
const k = el.getAttribute('data-i18n');
if (I18N[k]) el.textContent = I18N[k][i];
});
document.querySelectorAll('[data-i18n-ph]').forEach(el => {
const k = el.getAttribute('data-i18n-ph');
if (I18N[k]) el.placeholder = I18N[k][i];
});
document.querySelectorAll('.langseg button').forEach(b => b.classList.toggle('on', b.dataset.lang === lang));
}
window.__setLang = function (l) {
lang = l;
localStorage.setItem('pg_site_lang', l);
applyLang();
};
// ---------- pricing monthly/yearly ----------
const PRICES = {
free: ['¥0', '¥0'],
pro: ['¥25', '¥20'],
team: ['¥99', '¥79']
};
let cycle = 'monthly';
window.__setCycle = function (c) {
cycle = c;
const i = c === 'monthly' ? 0 : 1;
document.querySelectorAll('[data-price]').forEach(el => {
const p = el.getAttribute('data-price');
el.firstChild.textContent = PRICES[p][i];
});
document.querySelectorAll('.price-toggle button').forEach(b => b.classList.toggle('on', b.dataset.cycle === c));
};
// ---------- signup (demo: real impl sends a verification code) ----------
window.__signup = function (e) {
e.preventDefault();
const em = document.getElementById('su-email');
if (!/\S+@\S+\.\S+/.test(em.value)) {
em.focus();
return false;
}
document.getElementById('su-ok').hidden = false;
return false;
};
// ---------- announcement bar ----------
window.__closeAnn = function () {
const a = document.getElementById('ann');
if (a) a.remove();
};
// ---------- mobile menu ----------
window.__toggleMenu = function () {
document.getElementById('mnav').classList.toggle('open');
};
// ---------- scroll glass header ----------
function onScroll() {
document.querySelector('.hdr').classList.toggle('scrolled', window.scrollY > 8);
}
window.addEventListener('scroll', onScroll, {
passive: true
});
// ---------- init ----------
document.addEventListener('DOMContentLoaded', function () {
applyLang();
onScroll();
// close mobile menu when a link is tapped
document.querySelectorAll('#mnav a').forEach(a => a.addEventListener('click', () => document.getElementById('mnav').classList.remove('open')));
if (window.lucide) lucide.createIcons();
});
})();
})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/website/site.js", error: String((e && e.message) || e) }); }
})();