/* Hand Hub — Live Simulation Recreates the Veo3 / Google Flow dark UI and animates an AI cursor driving every manual click, type, upload, submit. The point is to make the viewer FEEL: "the robot is doing my hands' work." */ const { useState, useEffect, useRef, useMemo } = React; // ── Step script (one platform loop) ───────────────────────────────── // Each step describes: (a) what the cursor does, (b) what mutates on screen, // (c) the human-readable caption shown below the browser. const PROMPT_TEXT = "Cinematic wide shot, vịnh Hạ Long sương sớm, drone bay chậm trên mặt nước, ánh nắng vàng xuyên qua các đảo đá vôi, máy quay tiến chậm"; const PLATFORMS = { veo3: { url: "labs.google/fx/vi/tools/flow", profile: "veo3", label: "Veo 3 · Google Flow" }, grok: { url: "grok.com/imagine", profile: "grok", label: "Grok Imagine" }, kling: { url: "klingai.com/text-to-video", profile: "kling", label: "Kling AI" }, }; const STEPS = [ { id: 1, cap: "Mở Chrome profile", detail: "Veo 3 · profile=veo3", target: "url", wait: 900 }, { id: 2, cap: "Click vào ô prompt", detail: "contenteditable[role=textbox]", target: "promptBox", click: true, wait: 700 }, { id: 3, cap: "Bấm nút '+' để mở cài đặt", detail: "click button[aria-label=add]", target: "plus", click: true, wait: 700, mutate: { menu: true } }, { id: 4, cap: "Chọn loại 'Video'", detail: "menuitem · type=video", target: "menuVideo", click: true, wait: 600, mutate: { type: "video" } }, { id: 5, cap: "Chọn 'Khung hình'", detail: "menuitem · mode=frame", target: "menuFrame", click: true, wait: 600, mutate: { mode: "frame" } }, { id: 6, cap: "Chọn tỷ lệ 16:9", detail: "aspect=16:9", target: "menu169", click: true, wait: 600, mutate: { aspect: "16:9" } }, { id: 7, cap: "Chọn model 'Veo 3.1 - Lite'", detail: "model=Veo3.1-Lite", target: "menuModel", click: true, wait: 600 }, { id: 8, cap: "Chọn thời lượng 8s", detail: "duration=8s", target: "menu8s", click: true, wait: 700, mutate: { dur: "8s" } }, { id: 9, cap: "Đóng menu, quay về prompt", detail: "esc · focus textarea", target: "promptBox", click: true, wait: 700, mutate: { menu: false, focus: true } }, { id: 10, cap: "Gõ prompt 142 ký tự", detail: "type · 38 wpm jitter", target: "promptBox", wait: 2400, mutate: { typing: true } }, { id: 11, cap: "Bấm nút gửi", detail: "click button[arrow]", target: "submit", click: true, wait: 800, mutate: { typing: false, armed: true } }, { id: 12, cap: "Chờ render — 28%", detail: "polling DOM · video src", target: "thumb", wait: 1100, mutate: { armed: false, thumb: 28 } }, { id: 13, cap: "Render xong, tải về máy", detail: "~/Videos/Veo3/job_1.mp4", target: "thumb", wait: 900, mutate: { thumb: 100 } }, { id: 14, cap: "Sang job kế trong hàng đợi", detail: "next job · giữ profile veo3", target: "url", wait: 900, mutate: { reset: true, platform: "veo3" } }, ]; // ── Hit-test coordinates inside the 760×460 browser canvas ────────── // Adjusted so the cursor lands on real-looking UI element centers. const COORDS = { url: { x: 320, y: 26 }, promptBox: { x: 360, y: 388 }, plus: { x: 100, y: 422 }, menuVideo: { x: 605, y: 226 }, menuFrame: { x: 480, y: 268 }, menu169: { x: 605, y: 312 }, menuModel: { x: 540, y: 354 }, menu8s: { x: 615, y: 396 }, submit: { x: 632, y: 422 }, thumb: { x: 130, y: 110 }, }; // ── Pixel flower — recreated from screenshot ─────────────────────── // 8×8 grid; 1 = pixel on, 0 = off const FLOWER = [ "00111100", "01111110", "11000011", "11000011", "01111110", "00011000", "00111100", "00000000", ]; function PixelFlower() { return (
{FLOWER.flatMap((row, r) => row.split("").map((c, i) => ( )) )}
); } // ── Icons ────────────────────────────────────────────────────────── const Ico = { back: , search:, plus: , film: , gear: , help: , more: , sliders:, arrowR: , grid: , download:, image: , videoIc:, frame: , compose:, phone: , }; // ── AI cursor with robot tag ──────────────────────────────────────── function AICursor({ x, y, label, ripple }) { return ( <> {ripple !== null && ( )}
Hand Hub
); } // ── The fake Flow dark UI ────────────────────────────────────────── function FlowPage({ s, typed }) { const promptText = typed > 0 ? PROMPT_TEXT.slice(0, typed) : ""; const showPlaceholder = !s.typing && promptText.length === 0; return (
{/* top toolbar */}
May 09, 09:51 PM
{Ico.search}
ULTRA d
{/* left rail */}
{Ico.grid}
{Ico.image}
{Ico.compose}
{/* stage */}
{/* loading thumbnail */}
0 ? "show" : "")}>
{Ico.image}
{s.thumb}% {s.thumb > 0 && s.thumb < 100 &&
}
{!s.thumb && (
Bắt đầu tạo hoặc thả nội dung nghe nhìn
)}
{/* settings popup */}
{Ico.image} Hình ảnh
{Ico.videoIc} Video
{Ico.frame} Khung hình
{Ico.compose} Thành phần
9:16
16:9
1x
x2
x3
x4
Veo 3.1 - Lite
4s
6s
8s
Quá trình tạo sẽ tốn 5 tín dụng
{/* prompt bar */}
{showPlaceholder ? "Bạn muốn tạo gì?" : promptText} {s.typing && }
Video 1x
Flow có thể mắc sai sót nên bạn hãy xác minh nội dung do công cụ này tạo
); } // ── Queue model ──────────────────────────────────────────────────── const QUEUE = [ { id: 1, title: "Hạ Long bay sương sớm", platform: "veo3" }, { id: 2, title: "Phố cổ Hội An đèn lồng đêm", platform: "veo3" }, { id: 3, title: "Cận cảnh hoa sen nở chậm", platform: "veo3" }, { id: 4, title: "Drone biển Phú Quốc hoàng hôn", platform: "veo3" }, { id: 5, title: "Trẻ thả diều ruộng bậc thang", platform: "veo3" }, { id: 6, title: "Cà phê sữa đá nhỏ giọt", platform: "veo3" }, ]; // ── Main component ───────────────────────────────────────────────── function Simulation() { const [stepIdx, setStepIdx] = useState(0); const [paused, setPaused] = useState(false); const [rippleKey, setRippleKey] = useState(null); const [typed, setTyped] = useState(0); const [activeJob, setActiveJob] = useState(0); const [cursorTarget, setCursorTarget] = useState("url"); const [armedKey, setArmedKey] = useState(null); // Aggregated state passed to FlowPage const [state, setState] = useState({ menu: false, type: "video", mode: "frame", aspect: "16:9", dur: "8s", focus: false, typing: false, armed: false, thumb: 0, platform: "veo3" }); // Step driver useEffect(() => { if (paused) return; const step = STEPS[stepIdx]; const target = step.target; setCursorTarget(target); setArmedKey(target); let typingTimer; const advance = setTimeout(() => { if (step.click) { setRippleKey(Date.now()); } // Apply mutations if (step.mutate) { if (step.mutate.reset) { // job done — bump activeJob and reset visuals after a beat setTimeout(() => { setActiveJob(j => Math.min(j + 1, QUEUE.length - 1)); setState({ menu: false, type: "video", mode: "frame", aspect: "16:9", dur: "8s", focus: false, typing: false, armed: false, thumb: 0, platform: step.mutate.platform || "veo3" }); setTyped(0); }, 300); } else { setState(s => ({ ...s, ...step.mutate })); } } // After step.wait, advance const cont = setTimeout(() => { setArmedKey(null); setStepIdx(i => (i + 1) % STEPS.length); }, step.wait); return () => clearTimeout(cont); }, 240); // Type animation during step 10 if (step.mutate && step.mutate.typing) { const total = PROMPT_TEXT.length; let i = 0; typingTimer = setInterval(() => { i += Math.random() < 0.5 ? 2 : 3; setTyped(Math.min(i, total)); if (i >= total) clearInterval(typingTimer); }, step.wait / (total / 2.5)); } if (!step.mutate || !step.mutate.typing) { // Reset typed when starting a new platform loop if (step.mutate && step.mutate.reset) setTyped(0); } return () => { clearTimeout(advance); if (typingTimer) clearInterval(typingTimer); }; }, [stepIdx, paused]); // Pass armedKey through state for FlowPage const flowState = { ...state, armedKey }; // Cursor coordinates const coords = COORDS[cursorTarget] || COORDS.url; // Active platform / URL const platform = PLATFORMS[state.platform] || PLATFORMS.veo3; return (
{/* LEFT side */} {/* RIGHT — fake browser */}
🔒 {platform.url}
Profile: {platform.profile}
STEP {String(stepIdx+1).padStart(2,"0")}/{String(STEPS.length).padStart(2,"0")} {STEPS[stepIdx].cap}
{STEPS[stepIdx].detail}
); } window.Simulation = Simulation;