113616b039
Release / release (push) Successful in 3m19s
Stash the full claude.ai/design output (12 JSX variants — brutalist, classic, cli, compact, fluent-live, glass, hero-live, minimal, sketches, studio, wizard-live, workshop — plus shared hooks and a standalone HTML preview) for reference when we get to the Wails frontend in Phase 6/7. Source archive: C:\Users\root\Downloads\app(1).zip (~1MB). Not wired into any build target yet — current GUI is the temporary MessageBox stub. Pulling these in is the goal of the Wails phase. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
349 lines
16 KiB
React
349 lines
16 KiB
React
// drover-glass.jsx — Variant 3: Glassmorphism.
|
|
// Soft animated gradient backdrop. Frosted blur on every panel. Subtle glow on active.
|
|
|
|
const GlassTheme = {
|
|
d: {
|
|
bgGrad: 'radial-gradient(120% 80% at 0% 0%, #2b1d4a 0%, #131326 50%, #0d1024 100%)',
|
|
glassBg: 'rgba(255,255,255,0.06)',
|
|
glassBg2: 'rgba(255,255,255,0.10)',
|
|
border: 'rgba(255,255,255,0.14)',
|
|
borderSoft: 'rgba(255,255,255,0.08)',
|
|
text: '#f3f1ff', dim: '#bdbcd6', dimmer: '#7e7d99',
|
|
accent: '#9b8bff', accentGlow: '#9b8bff',
|
|
danger: '#ff8a98', warn: '#ffc46b', pass: '#7ee0b3', skip: '#9aa0aa',
|
|
inputBg: 'rgba(0,0,0,0.18)', primaryFg: '#1a1538',
|
|
},
|
|
l: {
|
|
bgGrad: 'radial-gradient(110% 70% at 0% 0%, #c8d8ff 0%, #f0e8ff 55%, #ffeaf2 100%)',
|
|
glassBg: 'rgba(255,255,255,0.55)',
|
|
glassBg2: 'rgba(255,255,255,0.75)',
|
|
border: 'rgba(255,255,255,0.7)',
|
|
borderSoft: 'rgba(255,255,255,0.5)',
|
|
text: '#1a1538', dim: '#5e5b7a', dimmer: '#8e8cab',
|
|
accent: '#5e4bcf', accentGlow: '#9b8bff',
|
|
danger: '#c0463f', warn: '#a06a14', pass: '#2c8a5a', skip: '#8a8aa0',
|
|
inputBg: 'rgba(255,255,255,0.6)', primaryFg: '#ffffff',
|
|
},
|
|
};
|
|
|
|
function GlassWindow({ mode = 'dark', initial }) {
|
|
const t = GlassTheme[mode === 'dark' ? 'd' : 'l'];
|
|
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",ui-monospace,monospace';
|
|
const isActive = D.phase === 'active';
|
|
|
|
return (
|
|
<div style={{
|
|
width: 480, height: 640, color: t.text, display: 'flex', flexDirection: 'column',
|
|
fontFamily: fontUI, fontSize: 13, position: 'relative',
|
|
background: t.bgGrad, overflow: 'hidden', borderRadius: 0,
|
|
}}>
|
|
{/* gradient orbs */}
|
|
<div style={{ position:'absolute', width: 280, height: 280, borderRadius:'50%',
|
|
background: 'radial-gradient(closest-side, rgba(155,139,255,0.55), transparent)',
|
|
top: -80, right: -80, filter: 'blur(20px)', pointerEvents:'none' }}/>
|
|
<div style={{ position:'absolute', width: 240, height: 240, borderRadius:'50%',
|
|
background: mode==='dark' ? 'radial-gradient(closest-side, rgba(126,224,179,0.35), transparent)'
|
|
: 'radial-gradient(closest-side, rgba(255,180,210,0.55), transparent)',
|
|
bottom: -90, left: -60, filter: 'blur(30px)', pointerEvents:'none' }}/>
|
|
|
|
{/* title */}
|
|
<GlassTitle t={t}/>
|
|
|
|
<div style={{ flex: 1, overflow:'auto', padding: '10px 14px 0', position:'relative', zIndex: 1 }}>
|
|
<GlassPanel t={t}>
|
|
<GlassHeader t={t}>SOCKS5 Proxy</GlassHeader>
|
|
<div style={{ display:'flex', gap: 10 }}>
|
|
<GField t={t} label="Host" style={{ flex: 1 }}>
|
|
<input value={D.form.host} onChange={e => D.update({ host: e.target.value })}
|
|
placeholder="95.165.72.59 или example.com"
|
|
onKeyDown={e => e.key === 'Enter' && D.runCheck()} style={glassInput(t, fontUI)}/>
|
|
</GField>
|
|
<GField t={t} label="Port" style={{ width: 96 }}>
|
|
<input value={D.form.port} onChange={e => D.update({ port: e.target.value.replace(/\D/g,'') })}
|
|
placeholder="12334" inputMode="numeric"
|
|
onKeyDown={e => e.key === 'Enter' && D.runCheck()} style={glassInput(t, fontUI)}/>
|
|
</GField>
|
|
</div>
|
|
<div style={{ height: 12 }}/>
|
|
<GlassToggle t={t} checked={D.form.auth}
|
|
onChange={(v) => { D.update({ auth: v }); if (v) setTimeout(()=>document.getElementById('gl-login')?.focus(),30); }}>
|
|
Authentication
|
|
</GlassToggle>
|
|
<div style={{ display:'flex', gap: 10, marginTop: 10, opacity: D.form.auth ? 1 : 0.4,
|
|
pointerEvents: D.form.auth ? 'auto':'none' }}>
|
|
<GField t={t} label="Login" style={{ flex: 1 }}>
|
|
<input id="gl-login" disabled={!D.form.auth} value={D.form.login}
|
|
onChange={e => D.update({ login: e.target.value })} placeholder="user"
|
|
onKeyDown={e => e.key === 'Enter' && D.runCheck()} style={glassInput(t, fontUI, !D.form.auth)}/>
|
|
</GField>
|
|
<GField t={t} label="Password" style={{ flex: 1 }}>
|
|
<input disabled={!D.form.auth} type="password" value={D.form.password}
|
|
onChange={e => D.update({ password: e.target.value })} placeholder="••••••"
|
|
onKeyDown={e => e.key === 'Enter' && D.runCheck()} style={glassInput(t, fontUI, !D.form.auth)}/>
|
|
</GField>
|
|
</div>
|
|
<div style={{ height: 14 }}/>
|
|
<GlassPrimary t={t} onClick={D.runCheck} disabled={D.phase === 'checking' || isActive}>
|
|
{D.phase === 'checking' ? 'Checking…' : 'Check connection'}
|
|
</GlassPrimary>
|
|
</GlassPanel>
|
|
|
|
<div style={{ height: 12 }}/>
|
|
<GlassPanel t={t}>
|
|
<GlassHeader t={t}>Status</GlassHeader>
|
|
<GlassStatus t={t} D={D} palette={palette} fontMono={fontMono}/>
|
|
</GlassPanel>
|
|
|
|
<div style={{ height: 12 }}/>
|
|
<GlassPanel t={t}>
|
|
<div style={{ display:'flex', gap: 10 }}>
|
|
<GlassStartBtn t={t} D={D}/>
|
|
<GlassStopBtn t={t} D={D}/>
|
|
</div>
|
|
{isActive && <GlassLiveStats t={t} stats={D.stats} fontMono={fontMono}/>}
|
|
</GlassPanel>
|
|
<div style={{ height: 12 }}/>
|
|
</div>
|
|
|
|
<GlassLogs t={t} D={D} fontMono={fontMono}/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function GlassTitle({ t }) {
|
|
const cell = { width: 44, height: 32, display:'flex', alignItems:'center', justifyContent:'center', cursor:'pointer', color: t.dim };
|
|
return (
|
|
<div style={{
|
|
height: 36, background: t.glassBg, backdropFilter: 'blur(20px)',
|
|
borderBottom: `1px solid ${t.borderSoft}`, display:'flex', alignItems:'center', position:'relative', zIndex: 2,
|
|
}}>
|
|
<div style={{ display:'flex', alignItems:'center', gap: 9, padding:'0 14px', flex:1 }}>
|
|
<window.BrandMark size={15} color={t.accent}/>
|
|
<span style={{ fontSize: 13, fontWeight: 600 }}>Drover-Go</span>
|
|
<span style={{ fontSize: 11, color: t.dimmer }}>0.4.2</span>
|
|
</div>
|
|
<div style={{ display:'flex' }}>
|
|
<div style={cell}><window.IconGear color={t.dim}/></div>
|
|
<div style={cell}><window.IconMin color={t.dim}/></div>
|
|
<div style={cell}><window.IconClose color={t.dim}/></div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function GlassPanel({ t, children, style }) {
|
|
return <div style={{
|
|
background: t.glassBg, backdropFilter: 'blur(24px) saturate(140%)',
|
|
WebkitBackdropFilter: 'blur(24px) saturate(140%)',
|
|
border: `1px solid ${t.border}`, borderRadius: 14, padding: 14,
|
|
boxShadow: `inset 0 1px 0 ${t.borderSoft}`,
|
|
...style,
|
|
}}>{children}</div>;
|
|
}
|
|
function GlassHeader({ t, children }) {
|
|
return <div style={{ fontSize: 11.5, fontWeight: 600, color: t.dim, marginBottom: 10, letterSpacing: 1.5, textTransform:'uppercase' }}>{children}</div>;
|
|
}
|
|
function GField({ t, label, children, style }) {
|
|
return <label style={{ display:'flex', flexDirection:'column', gap: 5, ...style }}>
|
|
<span style={{ fontSize: 11, color: t.dim }}>{label}</span>{children}
|
|
</label>;
|
|
}
|
|
function glassInput(t, fontUI, disabled) {
|
|
return {
|
|
background: t.inputBg, color: disabled ? t.dimmer : t.text,
|
|
border: `1px solid ${t.borderSoft}`, borderRadius: 8, padding: '8px 11px',
|
|
fontSize: 13, fontFamily: fontUI, outline:'none', width:'100%', boxSizing:'border-box',
|
|
backdropFilter: 'blur(8px)',
|
|
};
|
|
}
|
|
function GlassToggle({ t, checked, onChange, children }) {
|
|
return (
|
|
<label style={{ display:'inline-flex', alignItems:'center', gap: 10, cursor:'pointer' }}>
|
|
<span style={{
|
|
width: 36, height: 20, borderRadius: 10, padding: 2, boxSizing:'border-box',
|
|
background: checked ? t.accent : 'rgba(255,255,255,0.18)',
|
|
boxShadow: checked ? `0 0 12px ${t.accentGlow}80` : 'none',
|
|
transition: 'background .14s, box-shadow .14s', display:'flex', alignItems:'center',
|
|
}}>
|
|
<span style={{
|
|
width: 14, height: 14, borderRadius: 7, background: 'white',
|
|
transform: checked ? 'translateX(16px)' : 'translateX(0)', transition: 'transform .14s',
|
|
}}/>
|
|
</span>
|
|
<input type="checkbox" checked={checked} onChange={e => onChange(e.target.checked)} style={{ display:'none' }}/>
|
|
<span style={{ fontSize: 13 }}>{children}</span>
|
|
</label>
|
|
);
|
|
}
|
|
function GlassPrimary({ t, onClick, disabled, children, style }) {
|
|
return (
|
|
<button onClick={onClick} disabled={disabled} style={{
|
|
width:'100%', padding: '10px 14px', border:'none', borderRadius: 10,
|
|
background: disabled ? 'rgba(255,255,255,0.08)' : `linear-gradient(135deg, ${t.accent}, ${t.accentGlow})`,
|
|
color: disabled ? t.dimmer : t.primaryFg,
|
|
fontWeight: 600, fontSize: 13, cursor: disabled?'not-allowed':'pointer',
|
|
boxShadow: disabled ? 'none' : `0 6px 24px ${t.accentGlow}55, inset 0 1px 0 rgba(255,255,255,0.4)`,
|
|
...style,
|
|
}}>{children}</button>
|
|
);
|
|
}
|
|
function GlassStatus({ t, D, palette, fontMono }) {
|
|
if (D.phase === 'idle') {
|
|
return (
|
|
<div style={{ display:'flex', alignItems:'center', gap: 10 }}>
|
|
<span style={{ width: 8, height: 8, borderRadius: 4, background: t.dimmer }}/>
|
|
<span style={{ color: t.dim }}>Ready to check</span>
|
|
</div>
|
|
);
|
|
}
|
|
const allOk = D.lastSummary?.failed === 0;
|
|
return (
|
|
<div>
|
|
{D.phase === 'checking'
|
|
? <div style={{ display:'flex', alignItems:'center', gap: 9, marginBottom: 8 }}>
|
|
<window.StatusDot state="running" palette={palette} size={14}/>
|
|
<span style={{ fontWeight: 500 }}>Running diagnostics…</span>
|
|
</div>
|
|
: <div style={{
|
|
padding: '7px 11px', borderRadius: 8, marginBottom: 10,
|
|
background: allOk ? 'rgba(126,224,179,0.16)' : 'rgba(255,196,107,0.18)',
|
|
border: `1px solid ${allOk ? t.pass : t.warn}55`,
|
|
color: allOk ? t.pass : t.warn, fontWeight: 600, fontSize: 12.5,
|
|
}}>{allOk ? 'All checks passed. Ready to start.' : `${D.lastSummary?.failed} of ${D.tests.length} checks failed. Some features won't work.`}</div>}
|
|
<div>
|
|
{D.tests.map(test => {
|
|
const r = D.results[test.id];
|
|
const state = r?.result || (D.running === test.id ? 'running' : 'pending');
|
|
return (
|
|
<div key={test.id} style={{ padding:'4px 0' }}>
|
|
<div style={{ display:'flex', alignItems:'center', gap: 9, height: 26 }}>
|
|
<window.StatusDot state={state} palette={palette} size={14}/>
|
|
<span style={{ color: state==='pending'?t.dim:t.text, fontSize: 13 }} title={test.desc}>{test.label}</span>
|
|
<span style={{ marginLeft:'auto', fontFamily: fontMono, fontSize: 11.5,
|
|
color: state==='failed'?t.danger:state==='skipped'?t.skip:t.dim }}>
|
|
{r?.metric}
|
|
</span>
|
|
{r?.result === 'failed' && (
|
|
<button onClick={() => D.toggleExpand(test.id)} style={{
|
|
background:'transparent', border:'none', cursor:'pointer', padding: 4, color: t.dim,
|
|
}}><window.IconChevron color={t.dim} dir={r.expanded?'up':'down'}/></button>
|
|
)}
|
|
</div>
|
|
{r?.result === 'failed' && r.expanded && (
|
|
<div className="drv-fadein" style={{
|
|
margin: '4px 0 6px 23px', padding: 10, borderRadius: 8,
|
|
background: 'rgba(255,138,152,0.10)', border: `1px solid ${t.danger}40`,
|
|
fontSize: 12,
|
|
}}>
|
|
<div style={{ color: t.danger, fontWeight: 600, marginBottom: 3 }}>{r.error}</div>
|
|
<div style={{ color: t.dim, lineHeight: 1.5 }}>{r.hint}</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
function GlassStartBtn({ t, D }) {
|
|
const allFailed = D.lastSummary && D.lastSummary.failed === D.tests.length;
|
|
const checkedOk = D.phase === 'checked' && !allFailed;
|
|
const active = D.phase === 'active';
|
|
const warning = active && (D.lastSummary?.failed || 0) > 0;
|
|
if (active) {
|
|
const c = warning ? t.warn : t.pass;
|
|
return (
|
|
<div style={{
|
|
flex: 1, padding: '10px 14px', borderRadius: 10, fontWeight: 600, fontSize: 13,
|
|
background: warning ? 'rgba(255,196,107,0.16)' : 'rgba(126,224,179,0.16)',
|
|
border: `1px solid ${c}66`, color: c,
|
|
boxShadow: `0 0 24px ${c}40, inset 0 1px 0 rgba(255,255,255,0.18)`,
|
|
display:'flex', alignItems:'center', justifyContent:'center', gap: 9,
|
|
}}>
|
|
<span className="drv-pulsedot" style={{ width: 8, height: 8, borderRadius: 4, background: c, boxShadow: `0 0 8px ${c}` }}/>
|
|
Active{warning ? ' · UDP fallback' : ''}
|
|
</div>
|
|
);
|
|
}
|
|
return <GlassPrimary t={t} onClick={D.startProxy} disabled={!checkedOk} style={{ flex: 1 }}>Start proxying</GlassPrimary>;
|
|
}
|
|
function GlassStopBtn({ t, D }) {
|
|
const enabled = D.phase === 'active';
|
|
return (
|
|
<button onClick={D.stopProxy} disabled={!enabled} style={{
|
|
flex: 1, padding: '10px 14px', borderRadius: 10,
|
|
background: 'rgba(255,255,255,0.08)', color: enabled ? t.text : t.dimmer,
|
|
border: `1px solid ${t.borderSoft}`, fontWeight: 600, fontSize: 13,
|
|
cursor: enabled ? 'pointer':'not-allowed', backdropFilter: 'blur(8px)',
|
|
}}>Stop</button>
|
|
);
|
|
}
|
|
function GlassLiveStats({ t, stats, fontMono }) {
|
|
const C = ({ icon, val, lbl }) => (
|
|
<div style={{ display:'flex', alignItems:'center', gap: 4 }}>
|
|
{icon}<span style={{ fontFamily: fontMono, fontSize: 11.5 }}>{val}</span>
|
|
{lbl && <span style={{ fontSize: 9.5, color: t.dimmer, textTransform:'uppercase', letterSpacing:0.5 }}>{lbl}</span>}
|
|
</div>
|
|
);
|
|
return (
|
|
<div style={{
|
|
marginTop: 11, paddingTop: 11, borderTop: `1px solid ${t.borderSoft}`,
|
|
display:'flex', justifyContent:'space-between', color: t.dim,
|
|
}}>
|
|
<C icon={<window.IconArrowUp color={t.pass}/>} val={window.fmtBytes(stats.up)}/>
|
|
<C icon={<window.IconArrowDown color={t.accent}/>} val={window.fmtBytes(stats.down)}/>
|
|
<C val={stats.tcp} lbl="tcp"/>
|
|
<C val={stats.udp} lbl="udp"/>
|
|
<C val={window.fmtUptime(stats.uptimeS)} lbl="up"/>
|
|
</div>
|
|
);
|
|
}
|
|
function GlassLogs({ t, D, fontMono }) {
|
|
return (
|
|
<div style={{
|
|
borderTop: `1px solid ${t.borderSoft}`,
|
|
background: t.glassBg, backdropFilter: 'blur(20px)',
|
|
flexShrink: 0, position:'relative', zIndex: 2,
|
|
}}>
|
|
<button onClick={() => D.setLogsOpen(!D.logsOpen)} style={{
|
|
width:'100%', padding: '9px 16px', display:'flex', alignItems:'center', gap:10,
|
|
background:'transparent', border:'none', color: t.dim, cursor:'pointer', fontSize: 12,
|
|
}}>
|
|
<window.IconChevron color={t.dim} dir={D.logsOpen ? 'down' : 'right'}/>
|
|
<span style={{ fontWeight: 600, letterSpacing: 0.5 }}>Logs</span>
|
|
<span style={{ marginLeft: 'auto', fontFamily: fontMono, fontSize: 11, color: t.dimmer }}>{D.logs.length}</span>
|
|
</button>
|
|
{D.logsOpen && (
|
|
<>
|
|
<div style={{ display:'flex', gap: 6, padding: '0 16px 8px' }}>
|
|
{['Copy all','Clear','Open log file'].map((l,i) => (
|
|
<button key={l} onClick={i===0 ? () => navigator.clipboard?.writeText(D.logs.map(x=>`[${x.level}] ${x.msg}`).join('\n')) : i===1 ? D.clearLogs : undefined}
|
|
style={{
|
|
background:'rgba(255,255,255,0.05)', border:`1px solid ${t.borderSoft}`,
|
|
borderRadius: 6, padding: '4px 9px', fontSize: 11, color: t.dim, cursor:'pointer', fontFamily: fontMono,
|
|
}}>{l}</button>
|
|
))}
|
|
</div>
|
|
<div className="drv-log" ref={el => el && (el.scrollTop = el.scrollHeight)}
|
|
style={{ maxHeight: 130, overflowY: 'auto', padding: '8px 16px',
|
|
fontFamily: fontMono, fontSize: 11, lineHeight: 1.6, color: t.dim }}>
|
|
{D.logs.map((l,i) => (
|
|
<div key={i}>
|
|
<span style={{ color: t.dimmer }}>{window.fmtTime(l.t)}</span>{' '}
|
|
<span style={{ color: l.level==='ERROR'?t.danger:l.level==='WARN'?t.warn:t.accent, fontWeight: 600 }}>[{l.level}]</span>{' '}
|
|
{l.msg}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
window.GlassWindow = GlassWindow;
|