Files
pangolin/design/ui_kits/desktop/dflows.jsx
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

314 lines
24 KiB
React

/* 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 <label style={{ display:'block' }}>
<span style={{ display:'block', fontSize:12.5, fontWeight:600, color:'var(--fg2)', marginBottom:6 }}>{label}</span>
{children}
</label>;
}
/* ───────── 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)=>(
<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}</button>
);
const primary=(label,onClick,enabled)=>(
<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}</button>
);
return (
<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)' }}>
{/* brand panel */}
<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' }}>
<div style={{ display:'flex', alignItems:'center', gap:11 }}>
<DMark size={34} color="#fff" /><div style={{ fontFamily:'var(--font-display)', fontWeight:700, fontSize:20 }}>{lang==='zh'?'穿山甲':'Pangolin'}</div>
</div>
<div style={{ flex:1, display:'flex', flexDirection:'column', justifyContent:'center' }}>
<div style={{ fontFamily:'var(--font-display)', fontWeight:700, fontSize:28, lineHeight:1.3 }}>{t('authSidePitch')}</div>
</div>
<div style={{ fontSize:12, opacity:.8, letterSpacing:'0.04em' }}>{t('authTagline')}</div>
</div>
{/* form panel */}
<div style={{ flex:1, background:'var(--bg)', padding:'44px 46px', display:'flex', flexDirection:'column' }}>
<div style={{ display:'flex', borderBottom:'1px solid var(--border)', marginBottom:26 }}>{tab('login',t('tabLogin'))}{tab('register',t('tabRegister'))}</div>
<div style={{ flex:1, display:'flex', flexDirection:'column', gap:16 }}>
{mode==='login' && !totp && (<>
<DField label={t('emailLabel')}><input style={dInput} value={email} onChange={e=>setEmail(e.target.value)} placeholder={t('emailPh')} /></DField>
<DField label={t('pwLabel')}><input style={dInput} type="password" value={pw} onChange={e=>setPw(e.target.value)} placeholder={t('pwPh')} /></DField>
<div style={{ textAlign:'right', marginTop:-6 }}><span style={{ fontSize:13, color:'var(--accent)', fontWeight:600, cursor:'pointer' }}>{t('forgotPw')}</span></div>
{primary(t('doLogin'), ()=>setTotp(true), valid && pw.length>0)}
</>)}
{mode==='login' && totp && (<>
<div style={{ display:'flex', alignItems:'center', gap:8 }}>
<DIcon name="shield-check" size={18} color="var(--accent)" />
<span style={{ fontFamily:'var(--font-display)', fontWeight:700, fontSize:17, color:'var(--fg1)' }}>{t('twoFATitle')}</span>
<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</span>
</div>
<div style={{ fontSize:12.5, color:'var(--fg3)', lineHeight:1.6 }}>{t('twoFAHint')}</div>
<input value={otp} onChange={e=>setOtp(e.target.value.replace(/\D/g,'').slice(0,6))} placeholder="······"
style={{ ...dInput, fontFamily:'var(--font-mono)', fontSize:20, letterSpacing:'0.5em', textAlign:'center' }} />
{primary(t('twoFAConfirm'), onDone, otp.length===6)}
<div onClick={()=>setTotp(false)} style={{ textAlign:'center', fontSize:12.5, color:'var(--fg3)', cursor:'pointer' }}>{t('twoFABack')}</div>
</>)}
{mode==='register' && step===0 && (<>
<DField label={t('emailLabel')}>
<div style={{ display:'flex', gap:8 }}>
<input style={dInput} value={email} onChange={e=>setEmail(e.target.value)} placeholder={t('emailPh')} />
<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')}</button>
</div>
</DField>
{sent && <div style={{ fontSize:12, color:'var(--success)', marginTop:-8, display:'flex', alignItems:'center', gap:6 }}><DIcon name="check-circle" size={14} color="var(--success)" />{t('codeSentTo')} {email}</div>}
<DField label={t('codeLabel')}><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="······" /></DField>
{primary(t('doNext'), ()=>setStep(1), sent && code.length===6)}
</>)}
{mode==='register' && step===1 && (<>
<div style={{ fontSize:13, color:'var(--fg2)', display:'flex', alignItems:'center', gap:7 }}><DIcon name="check-circle" size={15} color="var(--success)" />{email}</div>
<DField label={t('pwLabel')}><input style={dInput} type="password" value={pw} onChange={e=>setPw(e.target.value)} placeholder={t('setPwPh')} /></DField>
{primary(t('doCreate'), onDone, pw.length>=6)}
</>)}
</div>
<div style={{ fontSize:11, color:'var(--fg3)', textAlign:'center', lineHeight:1.5, paddingTop:18 }}>{t('tos')}</div>
</div>
</div>
);
}
/* ───────── 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)=>(
<div style={{ display:'flex', alignItems:'center', gap:12, padding:'14px 18px', borderBottom:last?'none':'1px solid var(--border)' }}>
<div style={{ width:96, fontSize:13, fontWeight:600, color:'var(--fg2)' }}>{label}</div>
<div style={{ flex:1, fontSize:14, color:'var(--fg1)' }}>{value}</div>
{action}
</div>
);
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 (
<div style={{ padding:'8px 32px 28px', display:'flex', flexDirection:'column', gap:22, maxWidth:760 }}>
{/* plan banner */}
<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' }}>
<div style={{ display:'flex', alignItems:'center', gap:15 }}>
<div style={{ width:48, height:48, borderRadius:'50%', background:'rgba(255,255,255,.18)', display:'flex', alignItems:'center', justifyContent:'center' }}><DIcon name="crown" size={24} color="#fff" /></div>
<div>
<div style={{ fontSize:12.5, opacity:.8 }}>{t('curPlan')}</div>
<div style={{ fontFamily:'var(--font-display)', fontSize:20, fontWeight:700, marginTop:2 }}>{free?t('freePlanName'):t('proMember')}</div>
<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`}</div>
</div>
</div>
<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 }}><DIcon name="ticket" size={16} color="var(--clay-700)" />{t('renew')}</button>
</div>
{/* account info */}
<div>
<div style={{ fontFamily:'var(--font-display)', fontSize:17, fontWeight:700, color:'var(--fg1)', marginBottom:14 }}>{t('accInfoTitle')}</div>
<div style={{ ...dCard, overflow:'hidden' }}>
{infoRow(t('accEmail'), 'me@pangolin.vpn', <button style={smallBtn}>{t('accChange')}</button>)}
{infoRow(t('accPassword'), '••••••••••', <button style={smallBtn}>{t('accChange')}</button>)}
{infoRow(t('twoFA'), t('twoFASub'), <DToggle on={twoFA} onChange={()=>setTwoFA(!twoFA)} />)}
{infoRow(t('ucEntry'), t('ucEntrySub'), <button onClick={()=>window.open('../usercenter/index.html','_blank')} style={{ ...smallBtn, display:'inline-flex', alignItems:'center', gap:6 }}><DIcon name="external-link" size={14} color="var(--fg2)" />{t('ucOpen')}</button>)}
<div style={{ display:'flex', alignItems:'center', gap:12, padding:'14px 18px' }}>
<div style={{ width:96, fontSize:13, fontWeight:600, color:'var(--danger)' }}>{t('accSignOut')}</div>
<div style={{ flex:1 }} />
<button onClick={onSignOut} style={{ ...smallBtn, color:'var(--danger)', borderColor:'var(--danger-subtle)', background:'var(--danger-subtle)', display:'inline-flex', alignItems:'center', gap:7 }}><DIcon name="log-out" size={15} color="var(--danger)" />{t('accSignOut')}</button>
</div>
</div>
</div>
{/* devices */}
<div>
<div style={{ fontFamily:'var(--font-display)', fontSize:17, fontWeight:700, color:'var(--fg1)' }}>{t('myDevices')}</div>
<div style={{ fontSize:12.5, color:'var(--fg3)', margin:'3px 0 14px' }}>{t('devicesSub')}</div>
<div style={{ ...dCard, overflow:'hidden' }}>
{devices.map((d,i)=>(
<div key={d.id} style={{ display:'flex', alignItems:'center', gap:14, padding:'14px 18px', borderBottom:i<devices.length-1?'1px solid var(--border)':'none' }}>
<div style={{ width:40, height:40, borderRadius:'var(--radius-md)', background:'var(--accent-subtle)', display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0 }}><DIcon name={d.icon} size={20} color="var(--accent)" /></div>
<div style={{ flex:1 }}>
<div style={{ fontSize:14.5, fontWeight:600, color:'var(--fg1)', display:'flex', alignItems:'center', gap:8 }}>{d.name}{d.me && <span style={{ fontSize:11, fontWeight:600, color:'var(--success)', background:'var(--success-subtle)', padding:'2px 8px', borderRadius:999 }}>{d.active}</span>}</div>
<div style={{ fontSize:12, color:'var(--fg3)', marginTop:2 }}>{d.os}{!d.me && ` · ${t('lastActive')} ${d.active}`}</div>
</div>
{!d.me && <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')}</button>}
</div>
))}
</div>
</div>
</div>
);
}
/* 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 (
<div style={{ padding:'8px 32px 28px' }}>
<div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:16, alignItems:'start', maxWidth:760 }}>
{plans.map(p=>{
const isPro=p.variant==='pro';
return (
<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 && <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')}</div>}
<div style={{ fontSize:13.5, fontWeight:600, color:isPro?'#fff':'var(--fg2)', opacity:isPro?.85:1 }}>{p.name}</div>
<div style={{ fontFamily:'var(--font-display)', fontSize:30, fontWeight:700, marginTop:6, color:isPro?'#fff':'var(--fg1)' }}>{p.price}<span style={{ fontSize:13, fontWeight:500, opacity:.7 }}>{t('perMonth')}</span></div>
<div style={{ display:'flex', flexDirection:'column', gap:9, margin:'16px 0 18px' }}>
{p.feats.map((f,i)=>(<div key={i} style={{ display:'flex', alignItems:'center', gap:9, fontSize:13, color:isPro?'rgba(255,255,255,.92)':'var(--fg2)' }}><DIcon name="check" size={15} color={isPro?'#fff':'var(--success)'} stroke={2.4} />{f}</div>))}
</div>
<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}</button>
</div>
);
})}
</div>
</div>
);
}
/* ───────── 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 (
<div style={{ padding:'8px 32px 28px', maxWidth:680 }}>
<div style={{ ...dCard, padding:'20px 22px', marginBottom:24 }}>
<div style={{ fontSize:14.5, fontWeight:700, color:'var(--fg1)', marginBottom:12 }}>{t('redeemCodeTitle')}</div>
{ok ? (
<div style={{ display:'flex', alignItems:'center', gap:9, color:'var(--success)', fontWeight:600, fontSize:15, padding:'6px 0' }}><DIcon name="check-circle" size={20} color="var(--success)" />{t('redeemOk')}</div>
) : (
<div style={{ display:'flex', gap:10 }}>
<input value={code} onChange={e=>setCode(e.target.value.toUpperCase())} placeholder={t('redeemPh')} style={{ ...dInput, fontFamily:'var(--font-mono)', letterSpacing:'0.08em' }} />
<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')}</button>
</div>
)}
</div>
<div style={{ fontSize:14.5, fontWeight:700, color:'var(--fg1)', marginBottom:6 }}>{t('buyTitle')}</div>
<div style={{ fontSize:12.5, color:'var(--fg3)', lineHeight:1.5, marginBottom:14 }}>{t('buySub')}</div>
<div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12 }}>
{channels.map(c=>(
<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)' }}>
<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 }}><DIcon name={c.icon} size={20} color={c.accent?'#fff':'var(--accent)'} /></div>
<div style={{ flex:1, minWidth:0 }}>
<div style={{ fontSize:14.5, fontWeight:600, color:'var(--fg1)' }}>{c.name}</div>
<div style={{ fontSize:12, color:'var(--fg3)', fontFamily:c.mono?'var(--font-mono)':'var(--font-sans)', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>{c.sub}</div>
</div>
<DIcon name="external-link" size={16} color="var(--fg3)" />
</button>
))}
</div>
</div>
);
}
/* ───────── 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 (
<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)' }}>
<div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'18px 22px' }}>
<div style={{ display:'flex', alignItems:'center', gap:9 }}><DMark size={24} /><span style={{ fontFamily:'var(--font-display)', fontWeight:700, fontSize:15, color:'var(--fg1)' }}>{lang==='zh'?'穿山甲':'Pangolin'}</span></div>
{!last && <button onClick={onDone} style={{ border:'none', background:'transparent', cursor:'pointer', fontSize:13, fontWeight:600, color:'var(--fg3)' }}>{t('obSkip')}</button>}
</div>
<div style={{ flex:1, display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center', textAlign:'center', padding:'0 56px', gap:22 }}>
<div style={{ width:108, height:108, borderRadius:'50%', background:'var(--accent-subtle)', display:'flex', alignItems:'center', justifyContent:'center' }}>
<DIcon name={s.icon} size={52} stroke={1.6} color="var(--accent)" />
</div>
<div>
<div style={{ fontFamily:'var(--font-display)', fontSize:26, fontWeight:700, color:'var(--fg1)' }}>{s.title}</div>
<div style={{ fontSize:15, color:'var(--fg2)', lineHeight:1.6, marginTop:10, maxWidth:380 }}>{s.sub}</div>
</div>
</div>
<div style={{ padding:'0 40px 36px', display:'flex', flexDirection:'column', alignItems:'center', gap:20 }}>
<div style={{ display:'flex', gap:8 }}>
{steps.map((_,k)=>(<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)' }} />))}
</div>
<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}</button>
</div>
</div>
);
}
/* ───────── 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 (
<div style={{ padding:'8px 32px 28px', maxWidth:680 }}>
<div style={{ fontSize:13.5, color:'var(--fg2)', lineHeight:1.6, marginBottom:18 }}>{t('contactIntro')}</div>
<div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12, marginBottom:22 }}>
{channels.map(c=>(
<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)' }}>
<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 }}><DIcon name={c.icon} size={20} color={c.accent?'#fff':'var(--accent)'} /></div>
<div style={{ flex:1, minWidth:0 }}>
<div style={{ fontSize:14.5, fontWeight:600, color:'var(--fg1)' }}>{c.name}</div>
<div style={{ fontSize:12, color:'var(--fg3)', fontFamily:'var(--font-mono)', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>{c.sub}</div>
</div>
<DIcon name="external-link" size={16} color="var(--fg3)" />
</button>
))}
</div>
<div style={{ display:'flex', gap:14 }}>
<div style={{ ...dCard, flex:1, padding:'16px 18px' }}>
<div style={{ fontSize:12.5, fontWeight:600, color:'var(--fg3)' }}>{t('contactHoursTitle')}</div>
<div style={{ fontSize:15, fontWeight:600, color:'var(--fg1)', marginTop:6, fontFamily:'var(--font-mono)' }}>{t('contactHours')}</div>
</div>
<div style={{ ...dCard, flex:2, padding:'16px 18px', display:'flex', alignItems:'center', gap:14 }}>
<div style={{ flex:1 }}>
<div style={{ fontSize:12.5, fontWeight:600, color:'var(--fg3)' }}>{t('contactFaqTitle')}</div>
<div style={{ fontSize:13, color:'var(--fg2)', marginTop:5, lineHeight:1.5 }}>{t('contactFaq')}</div>
</div>
<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')}</button>
</div>
</div>
</div>
);
}
Object.assign(window, { DAuthFlow, DOnboarding, DAccountView, DPlansView, DRedeemView, DContactView });