Created
June 5, 2025 19:28
-
-
Save sibbng/35f336a1ad2ea4d0d58cb905dac5b90e to your computer and use it in GitHub Desktop.
v0 clone voby
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "imports": { | |
| "voby": "https://esm.sh/voby", | |
| "oby": "https://esm.sh/oby", | |
| "react": "https://esm.sh/react", | |
| "gsap": "https://esm.sh/gsap", | |
| "gsap/ScrollTrigger": "https://esm.sh/gsap/ScrollTrigger", | |
| "@/components/ui/button": "https://esm.sh/@/components/ui/button", | |
| "@/components/ui/input": "https://esm.sh/@/components/ui/input", | |
| "@/components/ui/card": "https://esm.sh/@/components/ui/card", | |
| "lucide-react": "https://esm.sh/lucide-react" | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| @theme { | |
| --font-sans: 'Inter', sans-serif; | |
| } | |
| @custom-variant dark (&:where(.dark, .dark *)); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { $, render, For, If, Observable, useEffect } from 'voby'; | |
| import { gsap } from 'gsap'; | |
| // --- Helper Components & Icons --- | |
| const Icon = ({ path, className = "w-5 h-5" }) => <svg class={className} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d={path}></path></svg>; | |
| const TimeIcon = () => <Icon path="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />; | |
| const ProjectsIcon = () => <Icon path="M3 3h7v7H3zM14 3h7v7h-7zM14 14h7v7h-7zM3 14h7v7H3z" />; | |
| const CommunityIcon = () => <Icon path="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.653-.124-1.28-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.653.124-1.28.356-1.857m0 0a3 3 0 015.288 0M12 14a4 4 0 100-8 4 4 0 000 8z" />; | |
| const ChevronRightIcon = () => <Icon className="w-4 h-4" path="m9 18 6-6-6-6" />; | |
| const ChevronDownIcon = () => <Icon className="w-4 h-4" path="m6 9 6 6 6-6" />; | |
| const SparklesIcon = () => <Icon className="w-4 h-4" path="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 5l-1.414-1.414A2 2 0 0012 2a2 2 0 00-2.586 1.586L8 5m8 0a2 2 0 012 2v2h-4V7a2 2 0 012-2z" />; | |
| const PaperclipIcon = () => <Icon className="w-4 h-4" path="M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48" />; | |
| const ArrowUpIcon = () => <Icon className="w-5 h-5" path="M5 10l7-7m0 0l7 7m-7-7v18" />; | |
| const CloseIcon = () => <Icon className="w-4 h-4" path="M18 6L6 18M6 6l12 12" />; | |
| const SettingsIcon = () => <Icon path="M12 6V3M12 21v-3M4.93 4.93l2.12 2.12M16.95 16.95l2.12 2.12M4.93 19.07l2.12-2.12M16.95 7.05l2.12-2.12M12 18a6 6 0 100-12 6 6 0 000 12z" />; | |
| const GithubIcon = () => <Icon path="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.54 2.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22" />; | |
| const MenuIcon = () => <Icon path="M3 12h18M3 6h18M3 18h18" />; | |
| const FileIcon = () => <Icon className="w-4 h-4" path="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></Icon>; | |
| // --- Resizer Handle Component --- | |
| const Resizer = ({ onPointerDown }) => ( | |
| <div | |
| class="w-1.5 bg-neutral-800 hover:bg-blue-600 active:bg-blue-500 transition-colors duration-200 cursor-col-resize flex-shrink-0" | |
| onPointerDown={onPointerDown} | |
| ></div> | |
| ); | |
| // --- Main Application Component --- | |
| const CodingAgentApp = (): JSX.Element => { | |
| // --- State Management --- | |
| const activeView = $<'preview' | 'code'>('code'); | |
| const isConsoleOpen = $(false); | |
| const isVersionOpen = $(true); | |
| const isRecentsOpen = $(true); | |
| const isSidebarOpen = $(true); | |
| const isResizing = $(false); // New state to disable GSAP during resize | |
| // --- Panel Widths --- | |
| const sidebarWidth = $(256); | |
| const rightPanelWidth = $(window.innerWidth / 2.5); | |
| // --- Refs for Animation & Sizing --- | |
| const sidebarRef = $<HTMLElement | null>(null); | |
| const versionContentRef = $<HTMLDivElement | null>(null); | |
| const recentsContentRef = $<HTMLDivElement | null>(null); | |
| const consoleDrawerRef = $<HTMLDivElement | null>(null); | |
| // --- Resizing Logic --- | |
| const createResizeHandler = (setWidth, clampMin, clampMax, invert = false) => (e: PointerEvent) => { | |
| e.preventDefault(); | |
| isResizing(true); | |
| const startX = e.clientX; | |
| const startWidth = setWidth(); | |
| const doDrag = (moveEvent: PointerEvent) => { | |
| let dx = moveEvent.clientX - startX; | |
| if (invert) { | |
| dx = -dx; | |
| } | |
| const newWidth = startWidth + dx; | |
| setWidth(Math.max(clampMin, Math.min(clampMax, newWidth))); | |
| }; | |
| const stopDrag = () => { | |
| window.removeEventListener('pointermove', doDrag); | |
| window.removeEventListener('pointerup', stopDrag); | |
| document.body.style.cursor = ''; | |
| document.body.style.userSelect = ''; | |
| setTimeout(() => isResizing(false), 50); // Defer to prevent conflicts | |
| }; | |
| document.body.style.cursor = 'col-resize'; | |
| document.body.style.userSelect = 'none'; | |
| window.addEventListener('pointermove', doDrag); | |
| window.addEventListener('pointerup', stopDrag); | |
| }; | |
| const handleSidebarResize = createResizeHandler(sidebarWidth, 200, 400); | |
| const handleMainResize = createResizeHandler(rightPanelWidth, 300, Infinity, true); | |
| // --- GSAP Animations --- | |
| useEffect(() => { | |
| if (isResizing()) return; // Guard against animating during resize | |
| const element = sidebarRef(); | |
| if (!element) return; | |
| gsap.to(element, { | |
| width: isSidebarOpen() ? sidebarWidth() : 0, | |
| minWidth: isSidebarOpen() ? sidebarWidth() : 0, | |
| opacity: isSidebarOpen() ? 1 : 0, | |
| duration: 0.4, | |
| ease: 'power3.inOut' | |
| }); | |
| }); | |
| useEffect(() => { | |
| if (isResizing()) return; | |
| const element = versionContentRef(); | |
| if (!element) return; | |
| gsap.to(element, { | |
| height: isVersionOpen() ? 'auto' : 0, | |
| opacity: isVersionOpen() ? 1 : 0, | |
| paddingTop: isVersionOpen() ? 8 : 0, | |
| paddingBottom: isVersionOpen() ? 8 : 0, | |
| duration: 0.4, | |
| ease: 'power3.inOut' | |
| }); | |
| }); | |
| useEffect(() => { | |
| if (isResizing()) return; | |
| const element = recentsContentRef(); | |
| if (!element) return; | |
| gsap.to(element, { | |
| height: isRecentsOpen() ? 'auto' : 0, | |
| opacity: isRecentsOpen() ? 1 : 0, | |
| marginTop: isRecentsOpen() ? 8 : 0, | |
| duration: 0.4, | |
| ease: 'power3.inOut' | |
| }); | |
| }); | |
| useEffect(() => { | |
| if (isResizing()) return; | |
| const element = consoleDrawerRef(); | |
| if (!element) return; | |
| gsap.to(element, { | |
| height: isConsoleOpen() ? '8rem' : 0, | |
| duration: 0.4, | |
| ease: 'power3.inOut' | |
| }); | |
| }); | |
| const codeContent = ` | |
| "use client" | |
| import { useEffect, useRef } from "react" | |
| import { gsap } from "gsap" | |
| // ... more code ... | |
| `.trim(); | |
| return ( | |
| <div class="relative flex h-screen bg-neutral-900 text-neutral-300 font-sans overflow-hidden"> | |
| {/* --- Global Header --- */} | |
| <header class="absolute top-0 left-0 right-0 flex justify-between items-center px-4 py-2 border-b border-neutral-800 z-20 bg-neutral-900/80 backdrop-blur-sm"> | |
| <div class="flex items-center gap-4"> | |
| <button onClick={() => isSidebarOpen(v => !v)} class="text-neutral-400 hover:text-white"><MenuIcon /></button> | |
| <div class="w-6 h-6 bg-yellow-400 rounded-md"></div> | |
| <span class="text-sm font-semibold text-white">Gsap landing page</span> | |
| </div> | |
| <div class="flex items-center gap-4"> | |
| <button class="flex items-center gap-2 text-sm text-neutral-300 hover:text-white"><SettingsIcon /></button> | |
| <button class="flex items-center gap-2 text-sm text-neutral-300 hover:text-white"><GithubIcon /></button> | |
| <button class="text-sm bg-neutral-800 px-3 py-1 rounded-md hover:bg-neutral-700">Share</button> | |
| <button class="text-sm bg-white text-black font-semibold px-4 py-1 rounded-md hover:bg-neutral-200">Publish</button> | |
| <div class="w-8 h-8 bg-yellow-400 rounded-full"></div> | |
| </div> | |
| </header> | |
| {/* --- Main Content Wrapper --- */} | |
| <div class="flex flex-1 pt-14"> | |
| {/* --- Left Sidebar --- */} | |
| <aside class="bg-neutral-950/50 border-r border-neutral-800 flex flex-col p-0 overflow-hidden" style={() => ({ width: `${sidebarWidth()}px`, minWidth: `${sidebarWidth()}px` })} ref={sidebarRef}> | |
| <div style={() => ({ width: `${sidebarWidth()}px`, minWidth: `${sidebarWidth()}px` })} class="h-full p-3 space-y-4 flex flex-col flex-1"> | |
| <button class="w-full bg-neutral-800 text-white font-semibold py-2 rounded-md hover:bg-neutral-700 transition-colors">New Chat</button> | |
| <nav class="space-y-2"> | |
| <a href="#" class="flex items-center gap-3 px-2 py-1.5 rounded-md text-neutral-400 hover:bg-neutral-800 hover:text-white"><TimeIcon /> Recents</a> | |
| <a href="#" class="flex items-center gap-3 px-2 py-1.5 rounded-md text-neutral-400 hover:bg-neutral-800 hover:text-white"><ProjectsIcon /> Projects</a> | |
| </nav> | |
| <div class="flex-1 overflow-y-auto space-y-4"> | |
| <div class="text-sm"> | |
| <button class="w-full flex items-center justify-between text-neutral-500" onClick={() => isRecentsOpen(v => !v)}> | |
| <span>Recents</span> | |
| <If when={isRecentsOpen} fallback={<ChevronRightIcon />}><ChevronDownIcon /></If> | |
| </button> | |
| <div class="overflow-hidden h-0" ref={recentsContentRef}> | |
| <div class="space-y-1"> | |
| <a href="#" class="block bg-neutral-800 text-white font-semibold px-2 py-1.5 rounded-md">Gsap landing page</a> | |
| <a href="#" class="block text-neutral-400 px-2 py-1.5 rounded-md hover:bg-neutral-800">Voby app example</a> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-neutral-800 p-3 rounded-md text-sm"> | |
| <p class="font-semibold text-white">New Feature</p> | |
| <p class="text-neutral-400">Introducing GitHub sync on v0</p> | |
| </div> | |
| </div> | |
| </aside> | |
| <Resizer onPointerDown={handleSidebarResize} /> | |
| {/* --- Center & Right Panels --- */} | |
| <div class="flex-1 flex overflow-hidden"> | |
| <main class="flex-1 flex flex-col min-w-0"> | |
| <div class="flex-1 p-6 overflow-y-auto"> | |
| <div class="w-full max-w-4xl mx-auto text-sm"> | |
| {/* ... Chat Content ... */} | |
| <div class="flex items-start gap-4 mb-8"> | |
| <div class="w-8 h-8 rounded-full bg-yellow-400 flex-shrink-0"></div> | |
| <p class="text-white pt-1">generate modern agency landing page with gsap</p> | |
| </div> | |
| <div class="flex items-start gap-4"> | |
| <div class="w-8 h-8 rounded-full bg-neutral-700 flex-shrink-0"></div> | |
| <div class="flex-1"> | |
| <p class="text-neutral-400 text-sm mb-4">Thought for 5 seconds</p> | |
| <p class="text-white mb-6">I'll create a modern agency landing page with smooth GSAP animations, featuring hero animations, scroll-triggered effects, and elegant transitions.</p> | |
| <div class="border border-neutral-700 rounded-lg mb-6"> | |
| <button class="w-full flex justify-between items-center p-3" onClick={() => isVersionOpen(v => !v)}> | |
| <div class="flex items-center gap-2 text-sm"><span>Version 1</span><If when={isVersionOpen} fallback={<ChevronRightIcon />}><ChevronDownIcon /></If></div> | |
| <div><span class="text-xs bg-neutral-700 px-2 py-1 rounded-md mr-2">Latest</span><span class="text-xs bg-blue-600 text-white px-2 py-1 rounded-md">Viewing</span></div> | |
| </button> | |
| <div class="overflow-hidden h-0 p-0" ref={versionContentRef}> | |
| <div class="px-3 space-y-2"> | |
| <div class="flex items-center gap-2 text-sm p-2 bg-neutral-800/50 rounded-md"><FileIcon /> app/page.tsx</div> | |
| <div class="flex items-center gap-2 text-sm p-2 bg-neutral-800/50 rounded-md"><FileIcon /> components/ui/card.tsx</div> | |
| </div> | |
| </div> | |
| </div> | |
| <p class="mb-6">I've created a modern agency landing page with sophisticated GSAP animations including:</p> | |
| <h3 class="text-white font-semibold mb-3">Key Features:</h3> | |
| <ul class="list-disc list-inside space-y-2 text-neutral-300 mb-6"> | |
| <li><span class="font-semibold text-white">Hero Section:</span> Animated text reveals, staggered stats, and parallax background</li> | |
| <li><span class="font-semibold text-white">Scroll Animations:</span> Triggered animations for each section as you scroll</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <footer class="p-4 w-full max-w-4xl mx-auto"> | |
| <div class="space-y-3"> | |
| <div> | |
| <span class="text-sm font-semibold text-white">Suggestions</span> | |
| <div class="flex gap-2 mt-2"> | |
| <button class="text-sm bg-neutral-800 px-3 py-1.5 rounded-md hover:bg-neutral-700">Add Integration</button> | |
| <button class="text-sm bg-neutral-800 px-3 py-1.5 rounded-md hover:bg-neutral-700">Add contact form</button> | |
| </div> | |
| </div> | |
| <div class="bg-yellow-900/30 border border-yellow-700/50 text-yellow-300 text-sm px-4 py-2 rounded-lg flex justify-between items-center"> | |
| <span>You are running low on credits. Your limit will reset on July 1.</span> | |
| <a href="#" class="font-semibold text-white hover:underline">Upgrade Plan</a> | |
| </div> | |
| <div class="bg-neutral-800 border border-neutral-700 rounded-lg p-3"> | |
| <textarea class="w-full bg-transparent focus:outline-none resize-none" placeholder="Ask a follow-up..." rows={2}></textarea> | |
| <div class="flex justify-between items-center"> | |
| <button class="flex items-center gap-1 text-sm text-neutral-400 hover:text-white"><SparklesIcon /> v0-1.5-md <ChevronDownIcon /></button> | |
| <div class="flex items-center gap-2"> | |
| <button class="text-neutral-400 hover:text-white"><PaperclipIcon /></button> | |
| <button class="bg-neutral-600 text-white p-1.5 rounded-md hover:bg-neutral-500"><ArrowUpIcon /></button> | |
| </div> | |
| </div> | |
| </div> | |
| <p class="text-center text-xs text-neutral-600">v0 may make mistakes. Please use with discretion.</p> | |
| </div> | |
| </footer> | |
| </main> | |
| <Resizer onPointerDown={handleMainResize} /> | |
| <aside class="bg-black/50 border-l border-neutral-800 flex flex-col" style={() => ({ width: `${rightPanelWidth()}px`, minWidth: '300px' })}> | |
| <div class="flex justify-between items-center p-3 border-b border-neutral-800"> | |
| <div class="flex gap-1 bg-neutral-800 p-1 rounded-md"> | |
| <button onClick={() => activeView('preview')} class={() => `px-3 py-1 text-sm rounded ${activeView() === 'preview' ? 'bg-neutral-600 text-white' : 'text-neutral-400'}`}>Preview</button> | |
| <button onClick={() => activeView('code')} class={() => `px-3 py-1 text-sm rounded ${activeView() === 'code' ? 'bg-neutral-600 text-white' : 'text-neutral-400'}`}>Code</button> | |
| </div> | |
| </div> | |
| <div class="flex-1 p-4 overflow-auto relative"> | |
| <If when={() => activeView() === 'code'}><pre class="text-sm"><code class="language-javascript font-mono">{codeContent}</code></pre></If> | |
| <If when={() => activeView() === 'preview'}><div class="w-full h-full bg-white text-black p-4"><h1 class="text-2xl font-bold">Agency Landing Page</h1></div></If> | |
| </div> | |
| <div class="relative border-t border-neutral-800"> | |
| <button class="w-full text-left p-3 text-sm text-neutral-400" onClick={() => isConsoleOpen(v => !v)}>Console</button> | |
| <div class="absolute bottom-full left-0 right-0 h-0 bg-neutral-900 border-t border-neutral-700 overflow-hidden flex flex-col" ref={consoleDrawerRef}> | |
| <div class="p-2 flex justify-between items-center bg-neutral-800 flex-shrink-0"> | |
| <p class="text-xs">Console Output</p> | |
| <button onClick={() => isConsoleOpen(false)} class="text-neutral-400 hover:text-white"><CloseIcon /></button> | |
| </div> | |
| <div class="p-4 bg-black h-full overflow-y-auto"><pre class="text-xs font-mono text-green-400">{`> GSAP animation sequence started...`}</pre></div> | |
| </div> | |
| </div> | |
| </aside> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| render(<CodingAgentApp />, document.getElementById('app')); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment