/* 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])=>(
{l}
{v}
))}
{t('weekTraffic')}
{vals.map((v,i)=>(
{v}
{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})=>(
{title}
{sub&&
{sub}
}
{right}
); 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 });