/* dapp.jsx — 穿山甲 VPN desktop · window + views (i18n) */
const { useState, useEffect, useRef } = React;
function DToggle({ on, onChange }) {
return
;
}
function DLangSwitch({ lang, setLang }) {
return
{[['zh','中文'],['en','EN']].map(([v,l])=>(
))}
;
}
function NavItem({ icon, label, active, onClick }) {
return (
);
}
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 (
{map.cap}
{map.sub}
{smart && }
{smart ? `${t('smartSelect')} · ` : ''}{dName(server,lang)} · {server.ping}ms
{free && (
{t('quotaToday')}
{mins} {t('minutes')}
{t('quotaFree')}
{adDone ? (
{t('adUnlocked')}
) : (
)}
)}
{status==='on' &&
{[['arrow-down','86.4','Mb/s'],['arrow-up','12.1','Mb/s'],['zap',String(server.ping),'ms']].map(([ic,v,u])=>(
{v} {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 (
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)' }} />
{/* smart select — recommended card */}
{list.map(s=>{
const active = s.code===current;
return (
);
})}
);
}
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 (
{[[t('trafficMonth'),'42.6 GB'],[t('avgPing'),'29 ms'],[t('durMonth'),'86.4 h']].map(([l,v])=>(
))}
);
}
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})=>(
);
return (
} last onClick={onRedeem} />
setBoot(!boot)} />} />
setAuto(!auto)} />} />
setKill(!kill)} />} last />
} />
setDark(!dark)} />} />
WireGuard} />
} />
v2.4.0 (macOS)} last />
} onClick={onReplayAuth} />
} onClick={onReplayOnboarding} />
setFree(!free)} />} last />
);
}
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.pings.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 (
setStage('onboarding')} />
);
if(stage==='onboarding') return (
setStage('app')} />
);
let main;
if(view==='connect') main=setAdDone(true)} elapsed={elapsed} />;
else if(view==='servers') main=;
else if(view==='stats') main=;
else if(view==='account') main=setView('plans')} onSignOut={()=>setStage('auth')} />;
else if(view==='plans') main=setView('redeem')} />;
else if(view==='redeem') main=;
else if(view==='contact') main=;
else main=setStage('auth')} onReplayOnboarding={()=>setStage('onboarding')} onRedeem={()=>setView('redeem')} />;
return (
{lang==='zh'?'穿山甲':'Pangolin'}
PANGOLIN
setView('connect')} />
setView('servers')} />
setView('stats')} />
setView('account')} />
setView('contact')} />
setView('settings')} />
{free?t('freePlanName'):t('proMember')}
{free?t('quotaFree'):t('until')}
{(view==='redeem'||view==='plans') &&
}
{titles[view]}
{status==='on'?'● '+t('connected')+' '+server.code:'○ '+t('notConnected')}
{main}
);
}
Object.assign(window, { DesktopApp });