// drover-wizard-live.jsx — Wizard / stepper variant. // 3 steps: Configure → Verify → Connect. State derives from D.phase. // Step 1 = idle (form). Step 2 = checking/checked (diagnostics). Step 3 = active (running proxy). const WIZARD_THEME = { l: { bg: '#fafafa', chrome: '#ffffff', panel: '#ffffff', panelAlt: '#f6f7f9', border: '#ececec', borderHard:'#dcdcdc', text: '#1a1a1a', dim: '#666666', dimmer: '#9aa0a6', accent: '#1e6fd9', accentSoft:'#eef4fc', danger: '#c0463f', warn: '#a8731e', pass: '#21a655', skip: '#888888', }, d: { bg: '#0f1115', chrome: '#171a20', panel: '#1a1d23', panelAlt: '#13151a', border: '#2a2d33', borderHard:'#3a3d45', text: '#e6e8ec', dim: '#9aa0a6', dimmer: '#5d6168', accent: '#5b9bf0', accentSoft:'#1a253a', danger: '#e57373', warn: '#d9a155', pass: '#5cba8b', skip: '#7c8088', }, }; function WizardWindow({ mode = 'light', initial }) { const themeKey = mode === 'dark' ? 'd' : 'l'; const t = WIZARD_THEME[themeKey]; const D = window.useDrover(initial); const palette = { pending: t.dimmer, running: t.accent, passed: t.pass, failed: t.danger, skipped: t.skip }; const fontUI = "'Inter','Segoe UI',system-ui,sans-serif"; const fontMono = "'JetBrains Mono','SF Mono',ui-monospace,Consolas,monospace"; // Determine current step from phase const phase = D.phase; const step = phase === 'idle' ? 1 : (phase === 'checking' || phase === 'checked') ? 2 : 3; // active return (
{/* Stepper */}
{[[1, 'Configure'], [2, 'Verify'], [3, 'Connect']].map(([n, label], i) => { const done = step > n; const current = step === n; const dim = step < n; return (
{done ? '✓' : n} {label}
{i < 2 &&
n ? t.accent : t.borderHard, margin: '0 10px', transition: 'background .2s', }} />} ); })}
{/* Step content */}
{step === 1 && } {step === 2 && } {step === 3 && }
{/* Footer */}
{ if (step === 2) D.stopProxy?.(), D.setPhase('idle'); if (step === 3) D.stopProxy(); }} disabled={step === 1}> {step === 3 ? 'Disconnect' : 'Back'} { if (step === 1) D.runCheck(); else if (step === 2) D.startProxy(); }} disabled={ (step === 1 && (D.phase === 'checking')) || (step === 2 && (D.phase === 'checking' || D.lastSummary?.failed === D.tests.length)) || (step === 3) }> {step === 1 && 'Verify →'} {step === 2 && (D.phase === 'checking' ? 'Verifying…' : 'Connect →')} {step === 3 && 'Connected'}
); } function WizardTitleBar({ t }) { return (
Drover-Go · Setup
); } function WizTitleBtn({ children, t, hoverBg, hoverFg }) { const [hover, setHover] = React.useState(false); return (
setHover(true)} onMouseLeave={() => setHover(false)} style={{ width: 38, height: 28, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', background: hover ? (hoverBg || 'rgba(127,127,127,.1)') : 'transparent', color: hover && hoverFg ? hoverFg : 'inherit', borderRadius: 4, }}>{children}
); } // ─── Step 1: Configure ─────────────────────────────────────────────────── function WizConfigure({ t, D, fontMono }) { const inputStyle = (disabled) => ({ height: 36, background: t.panel, color: disabled ? t.dimmer : t.text, border: `1px solid ${t.borderHard}`, borderRadius: 6, padding: '0 12px', fontFamily: fontMono, fontSize: 13, outline: 'none', width: '100%', boxSizing: 'border-box', transition: 'border-color .12s, box-shadow .12s', }); const onFocus = (e) => { e.target.style.borderColor = t.accent; e.target.style.boxShadow = `0 0 0 3px ${t.accentSoft}`; }; const onBlur = (e) => { e.target.style.borderColor = t.borderHard; e.target.style.boxShadow = 'none'; }; return ( <>

Configure your proxy

Введите адрес SOCKS5-сервера. На следующем шаге мы проверим, что Discord будет работать через него.
i Нажмите Verify →, чтобы продолжить.
); } // ─── Step 2: Verify ─────────────────────────────────────────────────── function WizVerify({ t, D, fontMono, palette, themeKey }) { const phase = D.phase; const total = D.tests.length; const completed = Object.keys(D.results).length; const failed = D.lastSummary?.failed ?? 0; const ringFrac = phase === 'checked' ? 1 : completed / total; const ringDash = 163.4; return ( <>

{phase === 'checking' ? 'Verifying your proxy' : (failed === 0 ? 'All checks passed' : 'Some checks failed')}

{phase === 'checking' ? <>Запускаем 7 проверок против {D.form.host}:{D.form.port} : (failed === 0 ? <>Прокси работает. Discord — голос, чат и демонстрация — будет работать через него. : <>{failed} из {total} проверок не прошли. Часть функций работать не будет.)}
{/* progress + ring */}
0 ? t.danger : phase === 'checked' ? t.pass : t.accent} strokeWidth="6" strokeLinecap="round" strokeDasharray={ringDash} strokeDashoffset={ringDash * (1 - ringFrac)} transform="rotate(-90 32 32)" style={{ transition: 'stroke-dashoffset .35s, stroke .2s' }} /> {phase === 'checked' ? `${total - failed}/${total}` : `${completed}/${total}`}
{phase === 'checking' ? <>Testing {D.tests.find(x => x.id === D.running)?.label || '…'} : (failed === 0 ? 'Готово к подключению' : 'Завершено с ошибками')}
{phase === 'checking' ? 'This usually takes 5–15 seconds.' : 'См. отчёт ниже.'}
{/* test list */}
{D.tests.map((test) => { const r = D.results[test.id]; const state = r?.result || (D.running === test.id ? 'running' : 'pending'); return (
{test.label} {r?.metric || (state === 'running' ? 'running…' : '')} {r?.result === 'failed' && ( )}
{r?.result === 'failed' && r.expanded && (
{r.error}
{r.hint}
)}
); })}
); } // ─── Step 3: Connect (active) ─────────────────────────────────────────── function WizConnect({ t, D, fontMono }) { const stats = D.stats; const failed = D.lastSummary?.failed ?? 0; return ( <>

0 ? t.warn : t.pass, display: 'inline-block', }} /> {failed > 0 ? 'Connected · UDP fallback' : 'Connected'}

Discord routes through {D.form.host}:{D.form.port} {' · '}{window.fmtUptime(stats.uptimeS)}
{/* stats grid */}
0 ? t.warn : t.pass} />
{/* logs panel inline */}
RECENT LOG
el && (el.scrollTop = el.scrollHeight)}> {D.logs.slice(-30).map((l, i) => (
{window.fmtTime(l.t)} {' '} [{l.level}] {' '} {l.msg}
))}
); } function WizStat({ t, fontMono, label, value, accent }) { return (
{label}
{value}
); } function WizardPrimaryBtn({ t, onClick, disabled, children }) { const [hover, setHover] = React.useState(false); return ( ); } function WizardSecondaryBtn({ t, onClick, disabled, children }) { const [hover, setHover] = React.useState(false); return ( ); } window.WizardWindow = WizardWindow;