diff --git a/README.md b/README.md index 2fc2bef504..17e2ad1ae5 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@

-

Build and deploy AI agent workflows in minutes.

+

The open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to orchestrate agentic workflows.

Sim.ai diff --git a/apps/docs/app/[lang]/[[...slug]]/page.tsx b/apps/docs/app/[lang]/[[...slug]]/page.tsx index 0a4afc9818..5c7710e8a7 100644 --- a/apps/docs/app/[lang]/[[...slug]]/page.tsx +++ b/apps/docs/app/[lang]/[[...slug]]/page.tsx @@ -264,15 +264,17 @@ export async function generateMetadata(props: { return { title: data.title, description: - data.description || 'Sim visual workflow builder for AI applications documentation', + data.description || + 'Documentation for Sim — the open-source platform to build AI agents and run your agentic workforce.', keywords: [ - 'AI workflow builder', - 'visual workflow editor', - 'AI automation', - 'workflow automation', 'AI agents', - 'no-code AI', - 'drag and drop workflows', + 'agentic workforce', + 'AI agent platform', + 'agentic workflows', + 'LLM orchestration', + 'AI automation', + 'knowledge base', + 'AI integrations', data.title?.toLowerCase().split(' '), ] .flat() @@ -282,7 +284,8 @@ export async function generateMetadata(props: { openGraph: { title: data.title, description: - data.description || 'Sim visual workflow builder for AI applications documentation', + data.description || + 'Documentation for Sim — the open-source platform to build AI agents and run your agentic workforce.', url: fullUrl, siteName: 'Sim Documentation', type: 'article', @@ -303,7 +306,8 @@ export async function generateMetadata(props: { card: 'summary_large_image', title: data.title, description: - data.description || 'Sim visual workflow builder for AI applications documentation', + data.description || + 'Documentation for Sim — the open-source platform to build AI agents and run your agentic workforce.', images: [ogImageUrl], creator: '@simdotai', site: '@simdotai', diff --git a/apps/docs/app/[lang]/layout.tsx b/apps/docs/app/[lang]/layout.tsx index d76a11f103..ef8165751b 100644 --- a/apps/docs/app/[lang]/layout.tsx +++ b/apps/docs/app/[lang]/layout.tsx @@ -63,7 +63,7 @@ export default async function Layout({ children, params }: LayoutProps) { '@type': 'WebSite', name: 'Sim Documentation', description: - 'Comprehensive documentation for Sim - the visual workflow builder for AI Agent Workflows.', + 'Documentation for Sim — the open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows.', url: 'https://docs.sim.ai', publisher: { '@type': 'Organization', diff --git a/apps/docs/app/layout.tsx b/apps/docs/app/layout.tsx index 103de41769..5548fd7e74 100644 --- a/apps/docs/app/layout.tsx +++ b/apps/docs/app/layout.tsx @@ -7,26 +7,27 @@ export default function RootLayout({ children }: { children: ReactNode }) { export const metadata = { metadataBase: new URL('https://docs.sim.ai'), title: { - default: 'Sim Documentation - Visual Workflow Builder for AI Applications', + default: 'Sim Documentation — Build AI Agents & Run Your Agentic Workforce', template: '%s', }, description: - 'Comprehensive documentation for Sim - the visual workflow builder for AI applications. Create powerful AI agents, automation workflows, and data processing pipelines by connecting blocks on a canvas—no coding required.', + 'Documentation for Sim — the open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows.', keywords: [ - 'AI workflow builder', - 'visual workflow editor', - 'AI automation', - 'workflow automation', 'AI agents', - 'no-code AI', - 'drag and drop workflows', + 'agentic workforce', + 'AI agent platform', + 'open-source AI agents', + 'agentic workflows', + 'LLM orchestration', 'AI integrations', - 'workflow canvas', - 'AI Agent Workflow Builder', - 'workflow orchestration', - 'agent builder', - 'AI workflow automation', - 'visual programming', + 'knowledge base', + 'AI automation', + 'workflow builder', + 'AI workflow orchestration', + 'enterprise AI', + 'AI agent deployment', + 'intelligent automation', + 'AI tools', ], authors: [{ name: 'Sim Team', url: 'https://sim.ai' }], creator: 'Sim', @@ -53,9 +54,9 @@ export const metadata = { alternateLocale: ['es_ES', 'fr_FR', 'de_DE', 'ja_JP', 'zh_CN'], url: 'https://docs.sim.ai', siteName: 'Sim Documentation', - title: 'Sim Documentation - Visual Workflow Builder for AI Applications', + title: 'Sim Documentation — Build AI Agents & Run Your Agentic Workforce', description: - 'Comprehensive documentation for Sim - the visual workflow builder for AI applications. Create powerful AI agents, automation workflows, and data processing pipelines.', + 'Documentation for Sim — the open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows.', images: [ { url: 'https://docs.sim.ai/api/og?title=Sim%20Documentation', @@ -67,9 +68,9 @@ export const metadata = { }, twitter: { card: 'summary_large_image', - title: 'Sim Documentation - Visual Workflow Builder for AI Applications', + title: 'Sim Documentation — Build AI Agents & Run Your Agentic Workforce', description: - 'Comprehensive documentation for Sim - the visual workflow builder for AI applications.', + 'Documentation for Sim — the open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows.', creator: '@simdotai', site: '@simdotai', images: ['https://docs.sim.ai/api/og?title=Sim%20Documentation'], diff --git a/apps/docs/app/llms.txt/route.ts b/apps/docs/app/llms.txt/route.ts index bf5a7a68b6..352f4cc904 100644 --- a/apps/docs/app/llms.txt/route.ts +++ b/apps/docs/app/llms.txt/route.ts @@ -37,9 +37,9 @@ export async function GET() { const manifest = `# Sim Documentation -> Visual Workflow Builder for AI Applications +> The open-source platform to build AI agents and run your agentic workforce. -Sim is a visual workflow builder for AI applications that lets you build AI agent workflows visually. Create powerful AI agents, automation workflows, and data processing pipelines by connecting blocks on a canvas—no coding required. +Sim is the open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows. Create agents, workflows, knowledge bases, tables, and docs. Trusted by over 100,000 builders. ## Documentation Overview diff --git a/apps/docs/components/docs-layout/toc-footer.tsx b/apps/docs/components/docs-layout/toc-footer.tsx index 01eb5d81a1..6e8fca4bfd 100644 --- a/apps/docs/components/docs-layout/toc-footer.tsx +++ b/apps/docs/components/docs-layout/toc-footer.tsx @@ -13,9 +13,9 @@ export function TOCFooter() {

Start building today
-
Trusted by over 60,000 builders.
+
Trusted by over 100,000 builders.
- Build Agentic workflows visually on a drag-and-drop canvas or with natural language. + The open-source platform to build AI agents and run your agentic workforce.
({ + opacity: targetOpacity, + transition: { duration: ENTER_DURATION }, + }), + exit: { + opacity: 0, + transition: { duration: EXIT_DURATION }, + }, +} + +/** Maps a BlockAnimState to the framer-motion animate value. */ +function toAnimateValue(state: BlockAnimState): string { + if (state === 'entering' || state === 'visible') return 'visible' + if (state === 'exiting') return 'exit' + return 'hidden' +} + +/** Shared SVG wrapper that staggers child rects in and out. */ +function AnimatedBlocksSvg({ + width, + height, + viewBox, + rects, + animState = 'entering', +}: { + width: number + height: number + viewBox: string + rects: readonly BlockRect[] + animState?: BlockAnimState +}) { + return ( + + {rects.map((r, i) => ( + + ))} + + ) +} + +/** + * Rect data for the top-right position. + * Two-row horizontal strip, ordered left-to-right. + */ +const TOP_RIGHT_RECTS: readonly BlockRect[] = [ + { opacity: 1, x: '0', y: '0', width: '16.8626', height: '33.7252', fill: '#2ABBF8' }, + { opacity: 0.6, x: '0', y: '0', width: '85.3433', height: '16.8626', fill: '#2ABBF8' }, + { opacity: 1, x: '0', y: '0', width: '16.8626', height: '16.8626', fill: '#2ABBF8' }, + { opacity: 0.6, x: '34.2403', y: '0', width: '34.2403', height: '33.7252', fill: '#2ABBF8' }, + { opacity: 1, x: '34.2403', y: '0', width: '16.8626', height: '16.8626', fill: '#2ABBF8' }, + { opacity: 1, x: '51.6188', y: '16.8626', width: '16.8626', height: '16.8626', fill: '#2ABBF8' }, + { opacity: 1, x: '68.4812', y: '0', width: '54.6502', height: '16.8626', fill: '#00F701' }, + { opacity: 0.6, x: '106.268', y: '0', width: '34.2403', height: '33.7252', fill: '#00F701' }, + { opacity: 0.6, x: '106.268', y: '0', width: '51.103', height: '16.8626', fill: '#00F701' }, + { opacity: 1, x: '123.6484', y: '16.8626', width: '16.8626', height: '16.8626', fill: '#00F701' }, + { opacity: 0.6, x: '157.371', y: '0', width: '34.2403', height: '16.8626', fill: '#FFCC02' }, + { opacity: 1, x: '157.371', y: '0', width: '16.8626', height: '16.8626', fill: '#FFCC02' }, + { opacity: 0.6, x: '208.993', y: '0', width: '68.4805', height: '16.8626', fill: '#FA4EDF' }, + { opacity: 0.6, x: '209.137', y: '0', width: '16.8626', height: '33.7252', fill: '#FA4EDF' }, + { opacity: 0.6, x: '243.233', y: '0', width: '34.2403', height: '33.7252', fill: '#FA4EDF' }, + { opacity: 1, x: '243.233', y: '0', width: '16.8626', height: '16.8626', fill: '#FA4EDF' }, + { opacity: 0.6, x: '260.096', y: '0', width: '34.04', height: '16.8626', fill: '#FA4EDF' }, + { opacity: 1, x: '260.611', y: '16.8626', width: '16.8626', height: '16.8626', fill: '#FA4EDF' }, +] + +/** + * Rect data for the top-left position. + * Same two-row structure as top-right with rotated colour palette: + * blue→green, green→yellow, yellow→pink, pink→blue. + */ +const TOP_LEFT_RECTS: readonly BlockRect[] = [ + { opacity: 1, x: '0', y: '0', width: '16.8626', height: '33.7252', fill: '#00F701' }, + { opacity: 0.6, x: '0', y: '0', width: '85.3433', height: '16.8626', fill: '#00F701' }, + { opacity: 1, x: '0', y: '0', width: '16.8626', height: '16.8626', fill: '#00F701' }, + { opacity: 0.6, x: '34.2403', y: '0', width: '34.2403', height: '33.7252', fill: '#00F701' }, + { opacity: 1, x: '34.2403', y: '0', width: '16.8626', height: '16.8626', fill: '#00F701' }, + { opacity: 1, x: '51.6188', y: '16.8626', width: '16.8626', height: '16.8626', fill: '#00F701' }, + { opacity: 1, x: '68.4812', y: '0', width: '54.6502', height: '16.8626', fill: '#FFCC02' }, + { opacity: 0.6, x: '106.268', y: '0', width: '34.2403', height: '33.7252', fill: '#FFCC02' }, + { opacity: 0.6, x: '106.268', y: '0', width: '51.103', height: '16.8626', fill: '#FFCC02' }, + { opacity: 1, x: '123.6484', y: '16.8626', width: '16.8626', height: '16.8626', fill: '#FFCC02' }, + { opacity: 0.6, x: '157.371', y: '0', width: '34.2403', height: '16.8626', fill: '#FA4EDF' }, + { opacity: 1, x: '157.371', y: '0', width: '16.8626', height: '16.8626', fill: '#FA4EDF' }, + { opacity: 0.6, x: '208.993', y: '0', width: '68.4805', height: '16.8626', fill: '#2ABBF8' }, + { opacity: 0.6, x: '209.137', y: '0', width: '16.8626', height: '33.7252', fill: '#2ABBF8' }, + { opacity: 0.6, x: '243.233', y: '0', width: '34.2403', height: '33.7252', fill: '#2ABBF8' }, + { opacity: 1, x: '243.233', y: '0', width: '16.8626', height: '16.8626', fill: '#2ABBF8' }, + { opacity: 0.6, x: '260.096', y: '0', width: '34.04', height: '16.8626', fill: '#2ABBF8' }, + { opacity: 1, x: '260.611', y: '16.8626', width: '16.8626', height: '16.8626', fill: '#2ABBF8' }, +] + +/** + * Rect data for the left position. + * Two-column vertical strip, ordered top-to-bottom. + */ +const LEFT_RECTS: readonly BlockRect[] = [ + { + opacity: 0.6, + width: '34.240', + height: '33.725', + fill: '#FA4EDF', + transform: 'matrix(0 1 1 0 0 0)', + }, + { + opacity: 0.6, + width: '16.8626', + height: '68.480', + fill: '#FA4EDF', + transform: 'matrix(-1 0 0 1 33.727 0)', + }, + { + opacity: 1, + width: '16.8626', + height: '16.8626', + fill: '#FA4EDF', + transform: 'matrix(-1 0 0 1 33.727 17.378)', + }, + { + opacity: 0.6, + width: '16.8626', + height: '33.986', + fill: '#FA4EDF', + transform: 'matrix(0 1 1 0 0 51.616)', + }, + { + opacity: 0.6, + width: '16.8626', + height: '140.507', + fill: '#00F701', + transform: 'matrix(-1 0 0 1 33.986 85.335)', + }, + { + opacity: 0.4, + x: '17.119', + y: '136.962', + width: '34.240', + height: '16.8626', + fill: '#FFCC02', + transform: 'rotate(-90 17.119 136.962)', + }, + { + opacity: 1, + x: '17.119', + y: '136.962', + width: '16.8626', + height: '16.8626', + fill: '#FFCC02', + transform: 'rotate(-90 17.119 136.962)', + }, + { + opacity: 0.5, + width: '34.240', + height: '33.725', + fill: '#00F701', + transform: 'matrix(0 1 1 0 0.257 153.825)', + }, + { + opacity: 1, + width: '16.8626', + height: '16.8626', + fill: '#00F701', + transform: 'matrix(0 1 1 0 0.257 153.825)', + }, +] + +/** + * Rect data for the right-side position (right edge of screenshot). + * Same two-column structure as left with rotated colours: + * pink→blue, green→pink, yellow→green. + */ +const RIGHT_SIDE_RECTS: readonly BlockRect[] = [ + { + opacity: 0.6, + width: '34.240', + height: '33.725', + fill: '#2ABBF8', + transform: 'matrix(0 1 1 0 0 0)', + }, + { + opacity: 0.6, + width: '16.8626', + height: '68.480', + fill: '#2ABBF8', + transform: 'matrix(-1 0 0 1 33.727 0)', + }, + { + opacity: 1, + width: '16.8626', + height: '16.8626', + fill: '#2ABBF8', + transform: 'matrix(-1 0 0 1 33.727 17.378)', + }, + { + opacity: 0.6, + width: '16.8626', + height: '33.986', + fill: '#2ABBF8', + transform: 'matrix(0 1 1 0 0 51.616)', + }, + { + opacity: 0.6, + width: '16.8626', + height: '140.507', + fill: '#FA4EDF', + transform: 'matrix(-1 0 0 1 33.986 85.335)', + }, + { + opacity: 0.4, + x: '17.119', + y: '136.962', + width: '34.240', + height: '16.8626', + fill: '#00F701', + transform: 'rotate(-90 17.119 136.962)', + }, + { + opacity: 1, + x: '17.119', + y: '136.962', + width: '16.8626', + height: '16.8626', + fill: '#00F701', + transform: 'rotate(-90 17.119 136.962)', + }, + { + opacity: 0.5, + width: '34.240', + height: '33.725', + fill: '#FA4EDF', + transform: 'matrix(0 1 1 0 0.257 153.825)', + }, + { + opacity: 1, + width: '16.8626', + height: '16.8626', + fill: '#FA4EDF', + transform: 'matrix(0 1 1 0 0.257 153.825)', + }, +] + +/** + * Rect data for the right-edge position (far right of screen). + * Two-column vertical strip, ordered top-to-bottom. + */ +const RIGHT_RECTS: readonly BlockRect[] = [ + { + opacity: 0.6, + width: '16.8626', + height: '33.726', + fill: '#FA4EDF', + transform: 'matrix(0 1 1 0 0 0)', + }, + { + opacity: 0.6, + width: '34.241', + height: '16.8626', + fill: '#FA4EDF', + transform: 'matrix(0 1 1 0 16.891 0)', + }, + { + opacity: 0.6, + width: '16.8626', + height: '68.482', + fill: '#FA4EDF', + transform: 'matrix(-1 0 0 1 33.739 16.888)', + }, + { + opacity: 0.6, + width: '16.8626', + height: '33.726', + fill: '#FA4EDF', + transform: 'matrix(0 1 1 0 0 33.776)', + }, + { + opacity: 1, + width: '16.8626', + height: '16.8626', + fill: '#FA4EDF', + transform: 'matrix(-1 0 0 1 33.739 34.272)', + }, + { + opacity: 0.6, + width: '16.8626', + height: '33.726', + fill: '#FA4EDF', + transform: 'matrix(0 1 1 0 0.012 68.510)', + }, + { + opacity: 0.6, + width: '16.8626', + height: '102.384', + fill: '#2ABBF8', + transform: 'matrix(-1 0 0 1 33.787 102.384)', + }, + { + opacity: 0.4, + x: '17.131', + y: '153.859', + width: '34.241', + height: '16.8626', + fill: '#00F701', + transform: 'rotate(-90 17.131 153.859)', + }, + { + opacity: 1, + x: '17.131', + y: '153.859', + width: '16.8626', + height: '16.8626', + fill: '#00F701', + transform: 'rotate(-90 17.131 153.859)', + }, +] + +/** Number of rects per position, used to compute animation durations. */ +const RECT_COUNTS: Record = { + topRight: TOP_RIGHT_RECTS.length, + topLeft: TOP_LEFT_RECTS.length, + left: LEFT_RECTS.length, + rightSide: RIGHT_SIDE_RECTS.length, + rightEdge: RIGHT_RECTS.length, +} + +/** Total enter animation time for a position (seconds). */ +function enterTime(pos: BlockPosition): number { + return (RECT_COUNTS[pos] - 1) * ENTER_STAGGER + ENTER_DURATION +} + +/** Total exit animation time for a position (seconds). */ +function exitTime(pos: BlockPosition): number { + return (RECT_COUNTS[pos] - 1) * EXIT_STAGGER + EXIT_DURATION +} + +/** A single step in the repeating animation cycle. */ +type CycleStep = + | { action: 'exit'; position: BlockPosition } + | { action: 'enter'; position: BlockPosition } + | { action: 'hold'; ms: number } + +/** + * The repeating cycle sequence. After all steps, the layout returns to its + * initial state (topRight + left + rightEdge) so the loop is seamless. + * + * Order: exit top → exit right-edge → enter right-side-of-preview → + * exit left → enter top-left → exit right-side → enter left → + * exit top-left → enter top-right → enter right-edge → back to initial. + */ +const CYCLE_STEPS: readonly CycleStep[] = [ + { action: 'exit', position: 'topRight' }, + { action: 'exit', position: 'rightEdge' }, + { action: 'enter', position: 'rightSide' }, + { action: 'hold', ms: HOLD_BETWEEN_MS }, + { action: 'exit', position: 'left' }, + { action: 'enter', position: 'topLeft' }, + { action: 'hold', ms: HOLD_BETWEEN_MS }, + { action: 'exit', position: 'rightSide' }, + { action: 'enter', position: 'left' }, + { action: 'hold', ms: HOLD_BETWEEN_MS }, + { action: 'exit', position: 'topLeft' }, + { action: 'enter', position: 'topRight' }, + { action: 'hold', ms: HOLD_BETWEEN_MS }, + { action: 'enter', position: 'rightEdge' }, + { action: 'hold', ms: HOLD_BETWEEN_MS }, +] + +/** + * Drives the block-cycling animation loop. Returns the current animation + * state for every position so each component can be driven declaratively. + * + * Lifecycle: + * 1. All three initial groups (topRight, left, rightEdge) enter together. + * 2. After a hold period the cycle begins, processing each step in order. + * 3. Repeats indefinitely, returning to the initial layout every cycle. + */ +export function useBlockCycle(): Record { + const [states, setStates] = useState>({ + topRight: 'entering', + left: 'entering', + rightEdge: 'entering', + rightSide: 'hidden', + topLeft: 'hidden', + }) + + useEffect(() => { + const cancelled = { current: false } + const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) + + const run = async () => { + const longestEnter = Math.max( + enterTime('topRight'), + enterTime('left'), + enterTime('rightEdge') + ) + await delay(longestEnter * 1000) + if (cancelled.current) return + + setStates({ + topRight: 'visible', + left: 'visible', + rightEdge: 'visible', + rightSide: 'hidden', + topLeft: 'hidden', + }) + + await delay(INITIAL_HOLD_MS) + if (cancelled.current) return + + while (!cancelled.current) { + for (const step of CYCLE_STEPS) { + if (cancelled.current) return + + if (step.action === 'exit') { + setStates((prev) => ({ ...prev, [step.position]: 'exiting' })) + await delay(exitTime(step.position) * 1000) + if (cancelled.current) return + setStates((prev) => ({ ...prev, [step.position]: 'hidden' })) + await delay(TRANSITION_PAUSE_MS) + } else if (step.action === 'enter') { + setStates((prev) => ({ ...prev, [step.position]: 'entering' })) + await delay(enterTime(step.position) * 1000) + if (cancelled.current) return + setStates((prev) => ({ ...prev, [step.position]: 'visible' })) + await delay(TRANSITION_PAUSE_MS) + } else { + await delay(step.ms) + } + + if (cancelled.current) return + } + } + } + + run() + return () => { + cancelled.current = true + } + }, []) + + return states +} + +interface AnimatedBlockProps { + animState?: BlockAnimState +} + +/** Two-row horizontal strip at the top-right of the hero. */ +export function BlocksTopRightAnimated({ animState = 'entering' }: AnimatedBlockProps) { + return ( + + ) +} + +/** Two-row horizontal strip at the top-left of the hero. */ +export function BlocksTopLeftAnimated({ animState = 'entering' }: AnimatedBlockProps) { + return ( + + ) +} + +/** Two-column vertical strip on the left edge of the screenshot. */ +export function BlocksLeftAnimated({ animState = 'entering' }: AnimatedBlockProps) { + return ( + + ) +} + +/** Two-column vertical strip on the right edge of the screenshot. */ +export function BlocksRightSideAnimated({ animState = 'entering' }: AnimatedBlockProps) { + return ( + + ) +} + +/** Two-column vertical strip at the far-right edge of the screen. */ +export function BlocksRightAnimated({ animState = 'entering' }: AnimatedBlockProps) { + return ( + + ) +} diff --git a/apps/sim/app/(home)/components/hero/components/hero-preview/components/hero-preview-panel/hero-preview-panel.tsx b/apps/sim/app/(home)/components/hero/components/hero-preview/components/hero-preview-panel/hero-preview-panel.tsx new file mode 100644 index 0000000000..fddee8ba68 --- /dev/null +++ b/apps/sim/app/(home)/components/hero/components/hero-preview/components/hero-preview-panel/hero-preview-panel.tsx @@ -0,0 +1,156 @@ +'use client' + +import { memo, useCallback, useRef, useState } from 'react' +import { ArrowUp } from 'lucide-react' +import Link from 'next/link' +import { useRouter } from 'next/navigation' +import { createPortal } from 'react-dom' +import { BubbleChatPreview, ChevronDown, MoreHorizontal, Play } from '@/components/emcn' +import { LandingPromptStorage } from '@/lib/core/utils/browser-storage' + +/** + * Lightweight static panel replicating the real workspace panel styling. + * The copilot tab is active with a functional user input. + * When submitted, stores the prompt and redirects to /signup (same as landing hero). + * + * Structure mirrors the real Panel component: + * aside > div.border-l.pt-[14px] > Header(px-8) > Tabs(px-8,pt-14) > Content(pt-12) + * inside Content > Copilot > header-bar(mx-[-1px]) > UserInput(p-8) + */ +export const HeroPreviewPanel = memo(function HeroPreviewPanel() { + const router = useRouter() + const [inputValue, setInputValue] = useState('') + const textareaRef = useRef(null) + const [cursorPos, setCursorPos] = useState<{ x: number; y: number } | null>(null) + + const isEmpty = inputValue.trim().length === 0 + + const handleSubmit = useCallback(() => { + if (isEmpty) return + LandingPromptStorage.store(inputValue) + router.push('/signup') + }, [isEmpty, inputValue, router]) + + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault() + handleSubmit() + } + }, + [handleSubmit] + ) + + return ( +
+
+ {/* Header — More + Chat | Deploy + Run */} +
+
+
+ +
+
+ +
+
+ setCursorPos({ x: e.clientX, y: e.clientY })} + onMouseLeave={() => setCursorPos(null)} + > +
+ Deploy +
+
+ + Run +
+ + {cursorPos && + createPortal( +
+ {/* Decorative color bars — mirrors hero top-right block sequence */} +
+
+
+
+
+
+
+
+
+
+
+ Get started + +
+
, + document.body + )} +
+ + {/* Tabs */} +
+
+
+ Copilot +
+
+ Toolbar +
+
+ Editor +
+
+
+ + {/* Tab content — copilot */} +
+
+ {/* Copilot header bar — matches mx-[-1px] in real copilot */} +
+ New Chat +
+ + {/* User input — matches real UserInput at p-[8px] inside copilot welcome state */} +
+
+