Files
drover-go/docs/design/v2/Drover-Go GUI.html
T
root 113616b039
Release / release (push) Successful in 3m19s
docs/design/v2: add 12-variant React design archive
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>
2026-05-01 03:12:02 +03:00

250 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8"/>
<title>Drover-Go — Desktop GUI explorations</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<style>
html, body { margin: 0; padding: 0; background: #f0eee9; font-family: -apple-system, "Segoe UI", system-ui, sans-serif; }
</style>
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
</head>
<body>
<template id="__bundler_thumbnail" data-bg-color="#f0eee9">
<svg viewBox="0 0 1200 800" xmlns="http://www.w3.org/2000/svg">
<rect width="1200" height="800" fill="#f0eee9"/>
<g transform="translate(600 400)">
<circle r="160" fill="none" stroke="#2a7d76" stroke-width="22"/>
<path d="M -90 -50 L 0 40 L 90 -50" fill="none" stroke="#2a7d76" stroke-width="22" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 0 40 L 0 130" fill="none" stroke="#2a7d76" stroke-width="22" stroke-linecap="round"/>
</g>
<text x="600" y="660" text-anchor="middle" font-family="ui-monospace, monospace" font-size="36" font-weight="700" fill="#2a251f" letter-spacing="6">DROVER-GO</text>
</svg>
</template>
<div id="root"></div>
<script type="text/babel" src="design-canvas.jsx"></script>
<script type="text/babel" src="drover-shared.jsx"></script>
<script type="text/babel" src="drover-classic.jsx"></script>
<script type="text/babel" src="drover-minimal.jsx"></script>
<script type="text/babel" src="drover-glass.jsx"></script>
<script type="text/babel" src="drover-brutalist.jsx"></script>
<script type="text/babel">
const { useState, useEffect } = React;
// For each variant we want to show several states side-by-side. To get
// distinct states without manual interaction in the artboards, we use a
// small "preset" wrapper that performs scripted setup against the variant's
// useDrover hook by lifting it up: we pass an `initial` form and an
// imperative control prop that the variant doesn't need — instead we just
// re-mount with different presets to land at idle / checking / passed /
// active / failed / failed+active states.
//
// We achieve this with a small Driver component that wraps the variant and
// drives it through actions on mount.
function Driver({ Component, mode, preset }) {
// preset: 'idle' | 'checking' | 'passed' | 'active' | 'failed' | 'active-warn' | 'auth-passed'
const ref = React.useRef(null);
const [boot, setBoot] = React.useState(false);
React.useEffect(() => { setBoot(true); }, []);
return (
<PresetHost Component={Component} mode={mode} preset={preset}/>
);
}
// Re-implements the variant render but injects a controller that drives
// the inner useDrover state via portal. Simpler approach: forward an
// `onReady` ref to the inner D state — but the variants don't expose D.
// Easiest working approach: wrap each variant and intercept by re-mounting
// through React + run actions via a *parallel* useDrover instance is not
// possible. So: we expose a tiny "preset" API directly inside each variant
// via the `initial` argument that mutates on first paint.
// The variants all accept an `initial` form. We extend the contract: the
// variants now also accept `__preset` and `__scenario` to seed the state
// machine on mount. To do that without rewriting all four files, we use a
// shim: PresetVariant wraps a variant and uses an effect on the first
// render of the inner DOM to dispatch synthetic clicks/programmatic state
// via a "Programmatic" pattern: each variant exposes its useDrover-result
// through a global hook map. Cleaner: just patch useDrover to broadcast.
// ── implementation: monkey-patch useDrover to record latest D per slot ──
if (!window.__drvSlots) {
const orig = window.useDrover;
window.__drvSlots = new Map();
window.useDrover = function(initial) {
const D = orig(initial);
const slot = (initial && initial.__slot) || null;
React.useEffect(() => {
if (slot) window.__drvSlots.set(slot, D);
});
return D;
};
}
function PresetHost({ Component, mode, preset }) {
const slot = React.useId();
React.useEffect(() => {
let cancelled = false;
const wait = () => new Promise(r => requestAnimationFrame(r));
(async () => {
await wait(); await wait();
const D = window.__drvSlots.get(slot);
if (!D || cancelled) return;
if (preset === 'idle') return;
if (preset === 'checking') {
// Set scenario, kick check, but don't await — we want it mid-flight
D.runCheck();
return;
}
if (preset === 'passed') {
D.setLogsOpen?.(false);
D.runCheck();
// wait for completion
for (let i = 0; i < 60 && !cancelled; i++) {
await new Promise(r => setTimeout(r, 200));
const cur = window.__drvSlots.get(slot);
if (cur?.phase === 'checked') break;
}
return;
}
if (preset === 'auth-passed') {
D.update({ auth: true, login: 'drover', password: 'secret123' });
await wait();
const cur1 = window.__drvSlots.get(slot);
cur1.runCheck();
for (let i = 0; i < 60 && !cancelled; i++) {
await new Promise(r => setTimeout(r, 200));
const cur = window.__drvSlots.get(slot);
if (cur?.phase === 'checked') break;
}
return;
}
if (preset === 'failed') {
// We need to switch scenario before runCheck.
D.setScenario?.('udpFail');
await wait();
const cur1 = window.__drvSlots.get(slot);
cur1.runCheck();
for (let i = 0; i < 60 && !cancelled; i++) {
await new Promise(r => setTimeout(r, 200));
const cur = window.__drvSlots.get(slot);
if (cur?.phase === 'checked') break;
}
return;
}
if (preset === 'active') {
D.runCheck();
for (let i = 0; i < 60 && !cancelled; i++) {
await new Promise(r => setTimeout(r, 200));
const cur = window.__drvSlots.get(slot);
if (cur?.phase === 'checked') break;
}
await wait();
window.__drvSlots.get(slot)?.startProxy?.();
// let stats accumulate for a bit
await new Promise(r => setTimeout(r, 1500));
return;
}
if (preset === 'active-warn') {
D.setScenario?.('udpFail');
await wait();
window.__drvSlots.get(slot)?.runCheck?.();
for (let i = 0; i < 60 && !cancelled; i++) {
await new Promise(r => setTimeout(r, 200));
const cur = window.__drvSlots.get(slot);
if (cur?.phase === 'checked') break;
}
await wait();
window.__drvSlots.get(slot)?.startProxy?.();
await new Promise(r => setTimeout(r, 1500));
return;
}
})();
return () => { cancelled = true; window.__drvSlots.delete(slot); };
}, [preset, slot]);
return <Component mode={mode} initial={{ __slot: slot }} />;
}
// ─── Section header card with reasoning ────────────────────────────────
function ReasoningCard() {
return (
<div style={{
width: 520, padding: '16px 18px',
background: '#ffffff', border: '1px solid rgba(0,0,0,0.08)', borderRadius: 10,
fontSize: 13, lineHeight: 1.55, color: '#2a251f',
boxShadow: '0 1px 3px rgba(0,0,0,0.04)',
}}>
<div style={{ fontWeight: 700, marginBottom: 4, letterSpacing: 0.2 }}>Drover-Go · GUI explorations</div>
<div style={{ color: '#5c5650' }}>
Один экран · 480×640 · фикс. размер для Win11. Четыре стилистики, dark + light, по нескольку состояний:
idle checking all-passed active. Отдельные борды показывают сценарий с провалом UDP
и активный режим с предупреждением. Все артборды живые: кликабельны, имеют focus / hover / disabled.
</div>
</div>
);
}
function App() {
const { DesignCanvas, DCSection, DCArtboard } = window;
const W = 480, H = 640;
const variants = [
{ id: 'classic', Component: window.ClassicWindow, title: 'Classic devtool',
subtitle: 'Hairlines, dense mono metrics. Сдержанный teal-акцент. Под аудиторию sysadmin/devs.' },
{ id: 'minimal', Component: window.MinimalWindow, title: 'Minimal · Fluent',
subtitle: 'Под Windows 11. Карточки с микро-границей, мягкий slate-blue, system feel.' },
{ id: 'glass', Component: window.GlassWindow, title: 'Glassmorphism',
subtitle: 'Frosted blur поверх градиента, glow на active. Для тех, кому хочется vibe.' },
{ id: 'brutal', Component: window.BrutWindow, title: 'Brutalist',
subtitle: 'Жёсткие границы, без скруглений, моно. Acid-lime акцент, hard-shadow на кнопках.' },
];
const presets = [
{ id: 'idle', label: 'idle (dark)', mode: 'dark', preset: 'idle' },
{ id: 'idle-light', label: 'idle (light)', mode: 'light', preset: 'idle' },
{ id: 'checking', label: 'checking… (dark)', mode: 'dark', preset: 'checking' },
{ id: 'passed', label: 'all passed (dark)', mode: 'dark', preset: 'passed' },
{ id: 'auth-passed', label: 'with auth (light)', mode: 'light', preset: 'auth-passed' },
{ id: 'failed', label: 'UDP failed (light)', mode: 'light', preset: 'failed' },
{ id: 'active', label: 'active proxy (dark)', mode: 'dark', preset: 'active' },
{ id: 'active-warn', label: 'active · UDP warn (light)', mode: 'light', preset: 'active-warn' },
];
return (
<>
<DesignCanvas>
<DCSection id="intro" title="About" subtitle="Brief & approach">
<DCArtboard id="intro-card" label="reasoning" width={520} height={140}>
<ReasoningCard/>
</DCArtboard>
</DCSection>
{variants.map(v => (
<DCSection key={v.id} id={v.id} title={v.title} subtitle={v.subtitle}>
{presets.map(p => (
<DCArtboard key={p.id} id={`${v.id}-${p.id}`} label={p.label} width={W} height={H}>
<PresetHost Component={v.Component} mode={p.mode} preset={p.preset}/>
</DCArtboard>
))}
</DCSection>
))}
</DesignCanvas>
</>
);
}
// Wait until all script files loaded
function tryMount() {
if (window.ClassicWindow && window.MinimalWindow && window.GlassWindow && window.BrutWindow && window.DesignCanvas) {
ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
} else {
setTimeout(tryMount, 50);
}
}
tryMount();
</script>
</body>
</html>