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>
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user