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>
250 lines
12 KiB
HTML
250 lines
12 KiB
HTML
<!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>
|