// drover-cli.jsx — Cyber CLI: terminal aesthetic, phosphor green, ASCII feel.
// All-mono. No round corners. Subtle CRT scanline overlay.
const CLI = {
bg: '#0a0d0a', chrome: '#070a07', panel: '#0d110d', panel2: '#111511',
border: '#1f2a1f', borderSoft: '#162016',
text: '#bfe7c0', // phosphor green-white
dim: '#5a8a5e', dimmer: '#3a5a3d',
accent: '#5cf08a', accentDim: '#2a8a44',
danger: '#ff6b6b', warn: '#ffd166', pass: '#5cf08a', skip: '#6a8a6e',
inputBg: '#0a0e0a',
};
function CliWindow({ initial }) {
const t = CLI;
const D = window.useDrover(initial);
const palette = { pending: t.dimmer, running: t.accent, passed: t.pass, failed: t.danger, skipped: t.skip };
const fontMono = '"JetBrains Mono","IBM Plex Mono","Cascadia Mono",ui-monospace,monospace';
const isActive = D.phase === 'active';
return (
{/* CRT scanlines */}
{/* vignette */}
{/* title bar — like a terminal tab */}
$
drover-go
— ~ — 80×24
{['settings','min','close'].map(k => (
{k==='close'?'×':k==='min'?'−':'⚙'}
))}
{/* prompt-style form */}
┌── socks5 proxy ─────────────────────────────────────────
host:
D.update({ host: v })}
placeholder="95.165.72.59 / example.com" onSubmit={D.runCheck} style={{ flex: 1 }}/>
port:
D.update({ port: v.replace(/\D/g,'') })}
placeholder="12334" onSubmit={D.runCheck} style={{ width: 70 }}/>
{D.form.auth && (
user:
D.update({ login: v })}
placeholder="login" onSubmit={D.runCheck} style={{ flex: 1 }}/>
pass:
D.update({ password: v })} type="password"
placeholder="••••••" onSubmit={D.runCheck} style={{ flex: 1 }}/>
)}
{/* status */}
┌── status ─────────────────────────────────────────────────
{D.phase === 'idle'
?
{'> '} ready to check_
:
{D.phase === 'checking'
?
{'> '} running diagnostics...
: (D.lastSummary?.failed === 0
?
[ ✓ ] all checks passed. ready to start.
:
[ ! ] {D.lastSummary?.failed}/{D.tests.length} failed. some features won't work.
)}
{D.tests.map(test => {
const r = D.results[test.id];
const state = r?.result || (D.running === test.id ? 'running' : 'pending');
const sym = state==='passed'?'✓':state==='failed'?'✗':state==='skipped'?'~':state==='running'?'›':'·';
const c = state==='passed'?t.pass:state==='failed'?t.danger:state==='skipped'?t.skip:state==='running'?t.accent:t.dimmer;
return (
{sym}
{test.label}
{r?.metric || (state==='running'?'…':'')}
{r?.result === 'failed' && (
)}
{r?.result === 'failed' && r.expanded && (
)}
);
})}
}
{/* actions */}
{(() => {
const allFailed = D.lastSummary && D.lastSummary.failed === D.tests.length;
const ok = D.phase === 'checked' && !allFailed;
const warning = isActive && (D.lastSummary?.failed||0) > 0;
if (isActive) {
const c = warning ? t.warn : t.accent;
return (
[ ACTIVE{warning ? ' / UDP-WARN' : ''} ]
);
}
return (
);
})()}
{isActive && (
↑ {window.fmtBytes(D.stats.up).padEnd(10)}
↓ {window.fmtBytes(D.stats.down).padEnd(10)}
tcp={D.stats.tcp}
udp={D.stats.udp}
up={window.fmtUptime(D.stats.uptimeS)}
)}
{/* logs */}
{D.logsOpen && (
<>
{[['copy', () => navigator.clipboard?.writeText(D.logs.map(x=>`[${x.level}] ${x.msg}`).join('\n'))],
['clear', D.clearLogs], ['file', null]].map(([l, fn]) => (
))}
el && (el.scrollTop = el.scrollHeight)}
style={{ maxHeight: 110, overflowY:'auto', padding:'5px 14px',
fontFamily: fontMono, fontSize: 10, lineHeight: 1.55, color: t.dim }}>
{D.logs.map((l,i) => (
{window.fmtTime(l.t)}{' '}
{l.level}{' '}
{l.msg}
))}
>
)}
);
}
function cliHead(t) { return { color: t.dim, fontSize: 10.5, marginBottom: 2, letterSpacing: 0.3 }; }
function CliRow({ children, style }) {
return {children}
;
}
function CliInput({ value, onChange, placeholder, type, onSubmit, style, t, id }) {
return onChange(e.target.value)} placeholder={placeholder}
onKeyDown={e => e.key === 'Enter' && onSubmit?.()}
style={{
background: t.inputBg, color: t.accent, border:'none',
borderBottom:`1px solid ${t.borderSoft}`, borderRadius: 0,
padding:'3px 4px', fontFamily: 'inherit', fontSize: 12,
outline:'none', boxSizing:'border-box', textShadow:`0 0 4px ${t.accent}60`, ...style,
}}/>;
}
window.CliWindow = CliWindow;