Book a demo
-
-
-
+
+
-
-
-
+ className='transition-transform duration-200 ease-out group-hover/cta:translate-x-[30%]'
+ />
+
diff --git a/apps/sim/app/(home)/components/features/components/features-preview.tsx b/apps/sim/app/(landing)/components/features/components/features-preview.tsx
similarity index 86%
rename from apps/sim/app/(home)/components/features/components/features-preview.tsx
rename to apps/sim/app/(landing)/components/features/components/features-preview.tsx
index a6dd9d43fee..e9a69ae8f9f 100644
--- a/apps/sim/app/(home)/components/features/components/features-preview.tsx
+++ b/apps/sim/app/(landing)/components/features/components/features-preview.tsx
@@ -17,7 +17,6 @@ import {
SlackIcon,
xAIIcon,
} from '@/components/icons'
-import { CsvIcon, JsonIcon, MarkdownIcon, PdfIcon } from '@/components/icons/document-icons'
import { cn } from '@/lib/core/utils/cn'
interface FeaturesPreviewProps {
@@ -25,7 +24,7 @@ interface FeaturesPreviewProps {
}
export function FeaturesPreview({ activeTab }: FeaturesPreviewProps) {
- const isWorkspaceTab = activeTab <= 4
+ const isWorkspaceTab = activeTab <= 3
return (
@@ -66,7 +65,7 @@ const CARD_GAP = 8
const GRID_STEP = CARD_SIZE + CARD_GAP
const GRID_PAD = 8
-type CardVariant = 'prompt' | 'table' | 'workflow' | 'knowledge' | 'logs' | 'file'
+type CardVariant = 'prompt' | 'table' | 'workflow' | 'logs' | 'file'
interface CardDef {
row: number
@@ -80,19 +79,19 @@ const MOTHERSHIP_CARDS: CardDef[] = [
{ row: 0, col: 0, variant: 'prompt', label: 'prompt.md' },
{ row: 1, col: 0, variant: 'table', label: 'Leads' },
{ row: 0, col: 1, variant: 'workflow', label: 'Email Bot', color: '#7C3AED' },
- { row: 1, col: 1, variant: 'knowledge', label: 'Company KB' },
+ { row: 1, col: 1, variant: 'file', label: 'handbook.md' },
{ row: 2, col: 0, variant: 'logs', label: 'Run Logs' },
{ row: 0, col: 2, variant: 'file', label: 'notes.md' },
{ row: 2, col: 1, variant: 'workflow', label: 'Onboarding', color: '#2563EB' },
{ row: 1, col: 2, variant: 'table', label: 'Contacts' },
{ row: 2, col: 2, variant: 'file', label: 'report.pdf' },
{ row: 3, col: 0, variant: 'table', label: 'Tickets' },
- { row: 0, col: 3, variant: 'knowledge', label: 'Product Wiki' },
+ { row: 0, col: 3, variant: 'file', label: 'wiki.md' },
{ row: 3, col: 1, variant: 'logs', label: 'Audit Trail' },
{ row: 1, col: 3, variant: 'workflow', label: 'Support', color: '#059669' },
{ row: 2, col: 3, variant: 'file', label: 'data.csv' },
{ row: 3, col: 2, variant: 'table', label: 'Users' },
- { row: 3, col: 3, variant: 'knowledge', label: 'HR Docs' },
+ { row: 3, col: 3, variant: 'file', label: 'policies.pdf' },
{ row: 0, col: 4, variant: 'workflow', label: 'Pipeline', color: '#DC2626' },
{ row: 1, col: 4, variant: 'logs', label: 'API Logs' },
{ row: 2, col: 4, variant: 'table', label: 'Orders' },
@@ -100,7 +99,7 @@ const MOTHERSHIP_CARDS: CardDef[] = [
{ row: 0, col: 5, variant: 'logs', label: 'Deploys' },
{ row: 1, col: 5, variant: 'table', label: 'Campaigns' },
{ row: 2, col: 5, variant: 'workflow', label: 'Intake', color: '#D97706' },
- { row: 3, col: 5, variant: 'knowledge', label: 'Research' },
+ { row: 3, col: 5, variant: 'file', label: 'research.pdf' },
{ row: 4, col: 0, variant: 'file', label: 'readme.md' },
{ row: 4, col: 1, variant: 'table', label: 'Revenue' },
{ row: 4, col: 2, variant: 'workflow', label: 'Sync', color: '#0891B2' },
@@ -110,27 +109,25 @@ const MOTHERSHIP_CARDS: CardDef[] = [
{ row: 0, col: 6, variant: 'table', label: 'Analytics' },
{ row: 1, col: 6, variant: 'workflow', label: 'Digest', color: '#6366F1' },
{ row: 0, col: 7, variant: 'file', label: 'brief.md' },
- { row: 2, col: 6, variant: 'knowledge', label: 'Playbooks' },
+ { row: 2, col: 6, variant: 'file', label: 'playbook.md' },
{ row: 1, col: 7, variant: 'logs', label: 'Webhooks' },
{ row: 3, col: 6, variant: 'file', label: 'export.csv' },
{ row: 2, col: 7, variant: 'workflow', label: 'Alerts', color: '#E11D48' },
{ row: 4, col: 6, variant: 'logs', label: 'Metrics' },
{ row: 3, col: 7, variant: 'table', label: 'Feedback' },
- { row: 4, col: 7, variant: 'knowledge', label: 'Runbooks' },
+ { row: 4, col: 7, variant: 'file', label: 'runbook.md' },
]
const EXPAND_TARGETS: Record = {
1: { row: 1, col: 0 },
2: { row: 0, col: 2 },
- 3: { row: 1, col: 1 },
- 4: { row: 2, col: 0 },
+ 3: { row: 2, col: 0 },
}
const EXPAND_ROW_COUNTS: Record = {
1: 8,
2: 10,
- 3: 10,
- 4: 7,
+ 3: 7,
}
function WorkspacePreview({ activeTab, isActive }: { activeTab: number; isActive: boolean }) {
@@ -146,7 +143,7 @@ function WorkspacePreview({ activeTab, isActive }: { activeTab: number; isActive
const [revealedRows, setRevealedRows] = useState(0)
const isMothership = activeTab === 0 && isActive
- const isExpandTab = activeTab >= 1 && activeTab <= 4 && isActive
+ const isExpandTab = activeTab >= 1 && activeTab <= 3 && isActive
const expandTarget = EXPAND_TARGETS[activeTab] ?? null
useEffect(() => {
@@ -292,8 +289,7 @@ function WorkspacePreview({ activeTab, isActive }: { activeTab: number; isActive
>
{expandedTab === 1 && }
{expandedTab === 2 && }
- {expandedTab === 3 && }
- {expandedTab === 4 && }
+ {expandedTab === 3 && }
)}
@@ -393,8 +389,6 @@ function MiniCardIcon({ variant, color }: { variant: CardVariant; color?: string
/>
)
}
- case 'knowledge':
- return
case 'logs':
return
}
@@ -410,8 +404,6 @@ function MiniCardBody({ variant, color }: { variant: CardVariant; color?: string
return
case 'workflow':
return
- case 'knowledge':
- return
case 'logs':
return
}
@@ -498,21 +490,6 @@ function WorkflowCardBody({ color }: { color: string }) {
)
}
-const KB_WIDTHS = [70, 85, 55, 80, 48] as const
-
-function KnowledgeCardBody() {
- return (
-
- {KB_WIDTHS.map((w, i) => (
-
- ))}
-
- )
-}
-
const LOG_ENTRIES = [
{ color: '#22C55E', width: 65 },
{ color: '#22C55E', width: 78 },
@@ -579,33 +556,6 @@ The team agreed to prioritize the new onboarding flow. Key decisions:
Follow up with engineering on the timeline for the API v2 migration. Draft the proposal for the board meeting next week.`
-const MOCK_KB_COLUMNS = ['Name', 'Size', 'Tokens', 'Chunks', 'Status'] as const
-
-const KB_FILE_ICONS: Record
>> = {
- pdf: PdfIcon,
- md: MarkdownIcon,
- csv: CsvIcon,
- json: JsonIcon,
-}
-
-function getKBFileIcon(filename: string) {
- const ext = filename.split('.').pop()?.toLowerCase() ?? ''
- return KB_FILE_ICONS[ext] ?? File
-}
-
-const MOCK_KB_DATA = [
- ['product-specs.pdf', '4.2 MB', '12.4k', '86', 'enabled'],
- ['eng-handbook.md', '1.8 MB', '8.2k', '54', 'enabled'],
- ['api-reference.json', '920 KB', '4.1k', '32', 'enabled'],
- ['release-notes.md', '340 KB', '2.8k', '18', 'enabled'],
- ['onboarding-guide.pdf', '2.1 MB', '6.5k', '42', 'processing'],
- ['data-export.csv', '560 KB', '3.4k', '24', 'enabled'],
- ['runbook.md', '280 KB', '1.9k', '14', 'enabled'],
- ['compliance.pdf', '180 KB', '1.2k', '8', 'disabled'],
- ['style-guide.md', '410 KB', '2.6k', '20', 'enabled'],
- ['metrics.csv', '1.4 MB', '5.8k', '38', 'enabled'],
-] as const
-
const MD_COMPONENTS: Components = {
h1: ({ children }) => (
= {
- enabled: { bg: '#DCFCE7', text: '#166534', label: 'Enabled' },
- disabled: { bg: '#F3F4F6', text: '#6B7280', label: 'Disabled' },
- processing: { bg: '#F3E8FF', text: '#7C3AED', label: 'Processing' },
-}
-
-function MockFullKnowledgeBase({ revealedRows }: { revealedRows: number }) {
- return (
-
-
-
-
- Knowledge Base
- /
- Company KB
-
-
-
-
-
-
- Sort
-
-
- Filter
-
-
-
-
-
-
-
-
- {MOCK_KB_COLUMNS.map((col) => (
-
- ))}
-
-
-
-
-
-
- {MOCK_KB_COLUMNS.map((col) => (
-
- {col}
-
- ))}
-
-
-
- {MOCK_KB_DATA.slice(0, revealedRows).map((row, i) => {
- const status = KB_STATUS_STYLES[row[4]] ?? KB_STATUS_STYLES.enabled
- const DocIcon = getKBFileIcon(row[0])
- return (
-
-
- {i + 1}
-
-
-
-
- {row[0]}
-
-
- {row.slice(1, 4).map((cell, j) => (
-
- {cell}
-
- ))}
-
-
- {status.label}
-
-
-
- )
- })}
-
-
-
-
- )
-}
-
const MOCK_LOG_COLORS = [
'#7C3AED',
'#2563EB',
diff --git a/apps/sim/app/(home)/components/features/features.tsx b/apps/sim/app/(landing)/components/features/features.tsx
similarity index 62%
rename from apps/sim/app/(home)/components/features/features.tsx
rename to apps/sim/app/(landing)/components/features/features.tsx
index 1b5d9b8c9a8..9f3a6751b9e 100644
--- a/apps/sim/app/(home)/components/features/features.tsx
+++ b/apps/sim/app/(landing)/components/features/features.tsx
@@ -4,8 +4,8 @@ import { useRef, useState } from 'react'
import { type MotionValue, motion, useScroll, useTransform } from 'framer-motion'
import Image from 'next/image'
import Link from 'next/link'
-import { Badge, ChevronDown } from '@/components/emcn'
-import { FeaturesPreview } from '@/app/(home)/components/features/components/features-preview'
+import { Badge } from '@/components/emcn'
+import { FeaturesPreview } from '@/app/(landing)/components/features/components/features-preview'
function hexToRgba(hex: string, alpha: number): string {
const r = Number.parseInt(hex.slice(1, 3), 16)
@@ -14,7 +14,19 @@ function hexToRgba(hex: string, alpha: number): string {
return `rgba(${r},${g},${b},${alpha})`
}
-const FEATURE_TABS = [
+interface FeatureTab {
+ label: string
+ mobileLabel?: string
+ color: string
+ badgeColor?: string
+ title: string
+ description: string
+ cta: string
+ segments: number[][]
+ hideOnMobile?: boolean
+}
+
+const FEATURE_TABS: FeatureTab[] = [
{
label: 'Mothership',
color: '#FA4EDF',
@@ -75,27 +87,6 @@ const FEATURE_TABS = [
[1, 10],
],
},
- {
- label: 'Knowledge Base',
- mobileLabel: 'Knowledge',
- color: '#8B5CF6',
- title: 'Your context engine',
- description:
- 'Sync institutional knowledge from 30+ live connectors — Notion, Drive, Slack, Confluence, and more — so every agent draws from the same truth across your entire organization.',
- cta: 'Explore knowledge base',
- segments: [
- [0.3, 10],
- [0.25, 8],
- [0.4, 10],
- [0.5, 10],
- [0.65, 10],
- [0.8, 10],
- [0.9, 12],
- [1, 10],
- [0.95, 10],
- [1, 10],
- ],
- },
{
label: 'Logs',
hideOnMobile: true,
@@ -138,36 +129,6 @@ function ScrollLetter({ scrollYProgress, charIndex, children }: ScrollLetterProp
return {children}
}
-function DotGrid({
- cols,
- rows,
- width,
- borderLeft,
-}: {
- cols: number
- rows: number
- width?: number
- borderLeft?: boolean
-}) {
- return (
-
- {Array.from({ length: cols * rows }, (_, i) => (
-
- ))}
-
- )
-}
-
export default function Features() {
const sectionRef = useRef(null)
const [activeTab, setActiveTab] = useState(0)
@@ -183,7 +144,7 @@ export default function Features() {
aria-labelledby='features-heading'
className='relative overflow-hidden bg-[var(--landing-bg-section)]'
>
-
+
-
+
Workspace
+
+ Sim's workspace includes four core features: Mothership, an AI command center for
+ natural-language control of your entire workspace; Tables, a built-in database for
+ filtering, sorting, and wiring data directly into workflows; Files, a shared document
+ store for uploading, creating, and sharing documents, spreadsheets, and media across
+ teams and agents; and Logs, full execution tracing with inputs, outputs, cost, and
+ duration for every run.
+
{HEADING_LETTERS.map((char, i) => (
@@ -226,45 +195,36 @@ export default function Features() {
-
+
-
+
{FEATURE_TABS.map((tab, index) => (
setActiveTab(index)}
- className={`relative h-full flex-1 items-center justify-center whitespace-nowrap px-3 font-medium font-season text-[var(--landing-text-dark)] text-caption uppercase lg:px-0 lg:text-sm${tab.hideOnMobile ? ' hidden lg:flex' : ' flex'}${index > 0 ? ' border-[var(--divider)] border-l' : ''}`}
+ className={`relative h-full min-w-0 flex-1 items-center justify-center px-2 font-medium font-season text-[var(--landing-text-dark)] text-caption uppercase lg:px-0 lg:text-sm${tab.hideOnMobile ? ' hidden lg:flex' : ' flex'}${index > 0 ? ' border-[var(--divider)] border-l' : ''}`}
style={{ backgroundColor: index === activeTab ? '#FDFDFD' : '#F6F6F6' }}
>
- {tab.mobileLabel ? (
- <>
- {tab.mobileLabel}
- {tab.label}
- >
- ) : (
- tab.label
- )}
+ {tab.label}
{index === activeTab && (
{tab.segments.map(([opacity, width], i) => (
@@ -284,17 +244,18 @@ export default function Features() {
))}
-
+
-
+
@@ -306,26 +267,9 @@ export default function Features() {
{FEATURE_TABS[activeTab].cta}
-
-
-
-
-
-
diff --git a/apps/sim/app/(home)/components/footer/footer-cta.tsx b/apps/sim/app/(landing)/components/footer/footer-cta.tsx
similarity index 68%
rename from apps/sim/app/(home)/components/footer/footer-cta.tsx
rename to apps/sim/app/(landing)/components/footer/footer-cta.tsx
index 984252f270a..c1c95a638da 100644
--- a/apps/sim/app/(home)/components/footer/footer-cta.tsx
+++ b/apps/sim/app/(landing)/components/footer/footer-cta.tsx
@@ -3,7 +3,7 @@
import { useCallback, useRef, useState } from 'react'
import { ArrowUp } from 'lucide-react'
import Link from 'next/link'
-import { useLandingSubmit } from '@/app/(home)/components/landing-preview/components/landing-preview-panel/landing-preview-panel'
+import { useLandingSubmit } from '@/app/(landing)/components/landing-preview/components/landing-preview-panel/landing-preview-panel'
import { useAnimatedPlaceholder } from '@/hooks/use-animated-placeholder'
const MAX_HEIGHT = 120
@@ -41,14 +41,21 @@ export function FooterCTA() {
}, [])
return (
-
-
+
+
What should we get done?
textareaRef.current?.focus()}
>
@@ -85,17 +93,17 @@ export function FooterCTA() {
href='https://docs.sim.ai'
target='_blank'
rel='noopener noreferrer'
- className={`${CTA_BUTTON} border-[var(--landing-border-subtle)] text-[var(--landing-text-dark)] transition-colors hover:bg-[var(--landing-bg-skeleton)]`}
+ className={`${CTA_BUTTON} border-[var(--landing-border-strong)] text-[var(--landing-text)] transition-colors hover:bg-[var(--landing-bg-elevated)]`}
>
Docs
Get started
-
+
)
}
diff --git a/apps/sim/app/(home)/components/footer/footer.tsx b/apps/sim/app/(landing)/components/footer/footer.tsx
similarity index 66%
rename from apps/sim/app/(home)/components/footer/footer.tsx
rename to apps/sim/app/(landing)/components/footer/footer.tsx
index 33ef8dcf9b0..4cc34914e88 100644
--- a/apps/sim/app/(home)/components/footer/footer.tsx
+++ b/apps/sim/app/(landing)/components/footer/footer.tsx
@@ -1,6 +1,6 @@
import Image from 'next/image'
import Link from 'next/link'
-import { FooterCTA } from '@/app/(home)/components/footer/footer-cta'
+import { FooterCTA } from '@/app/(landing)/components/footer/footer-cta'
const LINK_CLASS =
'text-sm text-[var(--landing-text-muted)] transition-colors hover:text-[var(--landing-text)]'
@@ -9,17 +9,17 @@ interface FooterItem {
label: string
href: string
external?: boolean
+ arrow?: boolean
+ externalArrow?: boolean
}
const PRODUCT_LINKS: FooterItem[] = [
- { label: 'Pricing', href: '/#pricing' },
- { label: 'Enterprise', href: 'https://form.typeform.com/to/jqCO12pF', external: true },
{ label: 'Self Hosting', href: 'https://docs.sim.ai/self-hosting', external: true },
{ label: 'MCP', href: 'https://docs.sim.ai/mcp', external: true },
{ label: 'Knowledge Base', href: 'https://docs.sim.ai/knowledgebase', external: true },
{ label: 'Tables', href: 'https://docs.sim.ai/tables', external: true },
{ label: 'API', href: 'https://docs.sim.ai/api-reference/getting-started', external: true },
- { label: 'Status', href: 'https://status.sim.ai', external: true },
+ { label: 'Status', href: 'https://status.sim.ai', external: true, externalArrow: true },
]
const RESOURCES_LINKS: FooterItem[] = [
@@ -29,7 +29,7 @@ const RESOURCES_LINKS: FooterItem[] = [
{ label: 'Models', href: '/models' },
// { label: 'Academy', href: '/academy' },
{ label: 'Partners', href: '/partners' },
- { label: 'Careers', href: 'https://jobs.ashbyhq.com/sim', external: true },
+ { label: 'Careers', href: 'https://jobs.ashbyhq.com/sim', external: true, externalArrow: true },
{ label: 'Changelog', href: '/changelog' },
]
@@ -47,7 +47,7 @@ const BLOCK_LINKS: FooterItem[] = [
]
const INTEGRATION_LINKS: FooterItem[] = [
- { label: 'All Integrations →', href: '/integrations' },
+ { label: 'All Integrations', href: '/integrations', arrow: true },
{ label: 'Confluence', href: 'https://docs.sim.ai/tools/confluence', external: true },
{ label: 'Slack', href: 'https://docs.sim.ai/tools/slack', external: true },
{ label: 'GitHub', href: 'https://docs.sim.ai/tools/github', external: true },
@@ -71,10 +71,20 @@ const INTEGRATION_LINKS: FooterItem[] = [
]
const SOCIAL_LINKS: FooterItem[] = [
- { label: 'X (Twitter)', href: 'https://x.com/simdotai', external: true },
- { label: 'LinkedIn', href: 'https://www.linkedin.com/company/simstudioai/', external: true },
- { label: 'Discord', href: 'https://discord.gg/Hr4UWYEcTT', external: true },
- { label: 'GitHub', href: 'https://github.com/simstudioai/sim', external: true },
+ { label: 'X (Twitter)', href: 'https://x.com/simdotai', external: true, externalArrow: true },
+ {
+ label: 'LinkedIn',
+ href: 'https://www.linkedin.com/company/simstudioai/',
+ external: true,
+ externalArrow: true,
+ },
+ { label: 'Discord', href: 'https://discord.gg/Hr4UWYEcTT', external: true, externalArrow: true },
+ {
+ label: 'GitHub',
+ href: 'https://github.com/simstudioai/sim',
+ external: true,
+ externalArrow: true,
+ },
]
const LEGAL_LINKS: FooterItem[] = [
@@ -82,25 +92,62 @@ const LEGAL_LINKS: FooterItem[] = [
{ label: 'Privacy Policy', href: '/privacy' },
]
+function ChevronArrow({ external }: { external?: boolean }) {
+ return (
+
+
+
+
+ )
+}
+
function FooterColumn({ title, items }: { title: string; items: FooterItem[] }) {
return (
{title}
- {items.map(({ label, href, external }) =>
+ {items.map(({ label, href, external, arrow, externalArrow }) =>
external ? (
{label}
+ {externalArrow && }
) : (
-
+
{label}
+ {arrow &&
}
)
)}
@@ -117,13 +164,31 @@ export default function Footer({ hideCTA }: FooterProps) {
return (
{!hideCTA && }
-
-
+
+
+
+
+
+
@@ -145,29 +210,6 @@ export default function Footer({ hideCTA }: FooterProps) {
-
- {/*
-
-
- */}
diff --git a/apps/sim/app/(landing)/components/hero/hero.tsx b/apps/sim/app/(landing)/components/hero/hero.tsx
new file mode 100644
index 00000000000..775f241c337
--- /dev/null
+++ b/apps/sim/app/(landing)/components/hero/hero.tsx
@@ -0,0 +1,97 @@
+'use client'
+
+import dynamic from 'next/dynamic'
+import Link from 'next/link'
+import { DemoRequestModal } from '@/app/(landing)/components/demo-request/demo-request-modal'
+
+const LandingPreview = dynamic(
+ () =>
+ import('@/app/(landing)/components/landing-preview/landing-preview').then(
+ (mod) => mod.LandingPreview
+ ),
+ {
+ ssr: false,
+ loading: () =>
,
+ }
+)
+
+/** Shared base classes for CTA link buttons — matches Deploy/Run button styling in the preview panel. */
+const CTA_BASE =
+ 'inline-flex items-center h-[32px] rounded-[5px] border px-2.5 font-[430] font-season text-sm'
+
+export default function Hero() {
+ return (
+
+
+ Sim is an open-source AI agent platform. Sim lets teams build AI agents and run an agentic
+ workforce by connecting 1,000+ integrations and LLMs — including OpenAI, Anthropic Claude,
+ Google Gemini, Mistral, and xAI Grok — to deploy and orchestrate agentic workflows. Users
+ create agents, workflows, knowledge bases, tables, and docs. Sim is trusted by over 100,000
+ builders at startups and Fortune 500 companies. Sim is SOC2 compliant.
+
+
+
+
+ Build AI Agents
+
+
+ Sim is the AI Workspace for Agent Builders
+
+
+
+
+
+ Get a demo
+
+
+
+ Get started
+
+
+
+
+
+
+ )
+}
diff --git a/apps/sim/app/(landing)/components/index.ts b/apps/sim/app/(landing)/components/index.ts
index ea11fd29b76..24c7c2d3435 100644
--- a/apps/sim/app/(landing)/components/index.ts
+++ b/apps/sim/app/(landing)/components/index.ts
@@ -1,4 +1,27 @@
+import Collaboration from '@/app/(landing)/components/collaboration/collaboration'
+import Enterprise from '@/app/(landing)/components/enterprise/enterprise'
import ExternalRedirect from '@/app/(landing)/components/external-redirect'
+import Features from '@/app/(landing)/components/features/features'
+import Footer from '@/app/(landing)/components/footer/footer'
+import Hero from '@/app/(landing)/components/hero/hero'
import LegalLayout from '@/app/(landing)/components/legal-layout'
+import Navbar from '@/app/(landing)/components/navbar/navbar'
+import Pricing from '@/app/(landing)/components/pricing/pricing'
+import StructuredData from '@/app/(landing)/components/structured-data'
+import Templates from '@/app/(landing)/components/templates/templates'
+import Testimonials from '@/app/(landing)/components/testimonials/testimonials'
-export { LegalLayout, ExternalRedirect }
+export {
+ Collaboration,
+ Enterprise,
+ ExternalRedirect,
+ Features,
+ Footer,
+ Hero,
+ LegalLayout,
+ Navbar,
+ Pricing,
+ StructuredData,
+ Templates,
+ Testimonials,
+}
diff --git a/apps/sim/app/(home)/components/landing-preview/components/landing-preview-files/landing-preview-files.tsx b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-files/landing-preview-files.tsx
similarity index 94%
rename from apps/sim/app/(home)/components/landing-preview/components/landing-preview-files/landing-preview-files.tsx
rename to apps/sim/app/(landing)/components/landing-preview/components/landing-preview-files/landing-preview-files.tsx
index eaea9b0f66c..b9ca9f6c196 100644
--- a/apps/sim/app/(home)/components/landing-preview/components/landing-preview-files/landing-preview-files.tsx
+++ b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-files/landing-preview-files.tsx
@@ -3,11 +3,11 @@ import { DocxIcon, PdfIcon } from '@/components/icons/document-icons'
import type {
PreviewColumn,
PreviewRow,
-} from '@/app/(home)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
+} from '@/app/(landing)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
import {
LandingPreviewResource,
ownerCell,
-} from '@/app/(home)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
+} from '@/app/(landing)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
/** Generic audio/zip icon using basic SVG since no dedicated component exists */
function AudioIcon({ className }: { className?: string }) {
diff --git a/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-home/landing-preview-home.tsx b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-home/landing-preview-home.tsx
new file mode 100644
index 00000000000..35cb85c1654
--- /dev/null
+++ b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-home/landing-preview-home.tsx
@@ -0,0 +1,479 @@
+'use client'
+
+import { memo, useCallback, useEffect, useRef, useState } from 'react'
+import { AnimatePresence, motion } from 'framer-motion'
+import { ArrowUp, Table } from 'lucide-react'
+import { Blimp, Checkbox, ChevronDown } from '@/components/emcn'
+import { TypeBoolean, TypeNumber, TypeText } from '@/components/emcn/icons'
+import { useLandingSubmit } from '@/app/(landing)/components/landing-preview/components/landing-preview-panel/landing-preview-panel'
+import { EASE_OUT } from '@/app/(landing)/components/landing-preview/components/landing-preview-workflow/workflow-data'
+import { useAnimatedPlaceholder } from '@/hooks/use-animated-placeholder'
+
+const C = {
+ SURFACE: '#292929',
+ BORDER: '#3d3d3d',
+ TEXT_PRIMARY: '#e6e6e6',
+ TEXT_BODY: '#cdcdcd',
+ TEXT_SECONDARY: '#b3b3b3',
+ TEXT_TERTIARY: '#939393',
+ TEXT_ICON: '#939393',
+} as const
+
+const AUTO_PROMPT = 'Analyze our customer leads and identify the top prospects'
+
+const MOCK_RESPONSE =
+ 'I analyzed your **Customer Leads** table and found **3 top prospects** with the highest lead scores:\n\n1. **Carol Davis** (StartupCo) — Score: 94\n2. **Frank Lee** (Ventures) — Score: 88\n3. **Alice Johnson** (Acme Corp) — Score: 87\n\nAll three are qualified leads. Want me to draft outreach emails?'
+
+const HOME_TYPE_MS = 40
+const HOME_TYPE_START_MS = 600
+const TOOL_CALL_DELAY_MS = 500
+const RESPONSE_DELAY_MS = 800
+const RESOURCE_PANEL_DELAY_MS = 600
+
+const MINI_TABLE_COLUMNS = [
+ { id: 'name', label: 'Name', type: 'text' as const, width: '32%' },
+ { id: 'company', label: 'Company', type: 'text' as const, width: '30%' },
+ { id: 'score', label: 'Score', type: 'number' as const, width: '18%' },
+ { id: 'qualified', label: 'Qualified', type: 'boolean' as const, width: '20%' },
+]
+
+const MINI_TABLE_ROWS = [
+ { name: 'Alice Johnson', company: 'Acme Corp', score: '87', qualified: 'true' },
+ { name: 'Bob Williams', company: 'TechCo', score: '62', qualified: 'false' },
+ { name: 'Carol Davis', company: 'StartupCo', score: '94', qualified: 'true' },
+ { name: 'Dan Miller', company: 'BigCorp', score: '71', qualified: 'true' },
+ { name: 'Eva Chen', company: 'Design IO', score: '45', qualified: 'false' },
+ { name: 'Frank Lee', company: 'Ventures', score: '88', qualified: 'true' },
+]
+
+const COLUMN_TYPE_ICONS = {
+ text: TypeText,
+ number: TypeNumber,
+ boolean: TypeBoolean,
+} as const
+
+interface LandingPreviewHomeProps {
+ autoType?: boolean
+}
+
+type ChatPhase = 'input' | 'sent' | 'tool-call' | 'responding' | 'done'
+
+/**
+ * Landing preview replica of the workspace Home view.
+ *
+ * When `autoType` is true, automatically types a prompt, sends it,
+ * shows a mothership agent group with tool calls, types a response,
+ * and opens a resource panel — matching the real workspace chat UI.
+ */
+export const LandingPreviewHome = memo(function LandingPreviewHome({
+ autoType = false,
+}: LandingPreviewHomeProps) {
+ const landingSubmit = useLandingSubmit()
+ const [inputValue, setInputValue] = useState('')
+ const textareaRef = useRef
(null)
+ const animatedPlaceholder = useAnimatedPlaceholder()
+
+ const [chatPhase, setChatPhase] = useState('input')
+ const [responseTypedLength, setResponseTypedLength] = useState(0)
+ const [showResourcePanel, setShowResourcePanel] = useState(false)
+ const [toolsExpanded, setToolsExpanded] = useState(true)
+
+ const typeIntervalRef = useRef | null>(null)
+ const responseIntervalRef = useRef | null>(null)
+ const timersRef = useRef[]>([])
+
+ const clearAllTimers = useCallback(() => {
+ for (const t of timersRef.current) clearTimeout(t)
+ timersRef.current = []
+ if (typeIntervalRef.current) clearInterval(typeIntervalRef.current)
+ if (responseIntervalRef.current) clearInterval(responseIntervalRef.current)
+ typeIntervalRef.current = null
+ responseIntervalRef.current = null
+ }, [])
+
+ useEffect(() => {
+ if (!autoType) return
+
+ setChatPhase('input')
+ setResponseTypedLength(0)
+ setShowResourcePanel(false)
+ setToolsExpanded(true)
+ setInputValue('')
+
+ const t1 = setTimeout(() => {
+ let idx = 0
+ typeIntervalRef.current = setInterval(() => {
+ idx++
+ setInputValue(AUTO_PROMPT.slice(0, idx))
+ if (idx >= AUTO_PROMPT.length) {
+ if (typeIntervalRef.current) clearInterval(typeIntervalRef.current)
+ typeIntervalRef.current = null
+
+ const t2 = setTimeout(() => {
+ setChatPhase('sent')
+
+ const t3 = setTimeout(() => {
+ setChatPhase('tool-call')
+
+ const t4 = setTimeout(() => {
+ setShowResourcePanel(true)
+ }, RESOURCE_PANEL_DELAY_MS)
+ timersRef.current.push(t4)
+
+ const t5 = setTimeout(() => {
+ setToolsExpanded(false)
+ setChatPhase('responding')
+ let rIdx = 0
+ responseIntervalRef.current = setInterval(() => {
+ rIdx++
+ setResponseTypedLength(rIdx)
+ if (rIdx >= MOCK_RESPONSE.length) {
+ if (responseIntervalRef.current) clearInterval(responseIntervalRef.current)
+ responseIntervalRef.current = null
+ setChatPhase('done')
+ }
+ }, 8)
+ }, TOOL_CALL_DELAY_MS + RESPONSE_DELAY_MS)
+ timersRef.current.push(t5)
+ }, TOOL_CALL_DELAY_MS)
+ timersRef.current.push(t3)
+ }, 400)
+ timersRef.current.push(t2)
+ }
+ }, HOME_TYPE_MS)
+ }, HOME_TYPE_START_MS)
+ timersRef.current.push(t1)
+
+ return clearAllTimers
+ }, [autoType, clearAllTimers])
+
+ const isEmpty = inputValue.trim().length === 0
+
+ const handleSubmit = useCallback(() => {
+ if (isEmpty) return
+ landingSubmit(inputValue)
+ }, [isEmpty, inputValue, landingSubmit])
+
+ const handleKeyDown = useCallback(
+ (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault()
+ handleSubmit()
+ }
+ },
+ [handleSubmit]
+ )
+
+ const handleInput = useCallback((e: React.FormEvent) => {
+ const target = e.target as HTMLTextAreaElement
+ target.style.height = 'auto'
+ target.style.height = `${Math.min(target.scrollHeight, 200)}px`
+ }, [])
+
+ if (chatPhase !== 'input') {
+ const isResponding = chatPhase === 'responding' || chatPhase === 'done'
+ const showToolCall = chatPhase === 'tool-call' || isResponding
+
+ return (
+
+ {/* Chat area — matches mothership-view layout */}
+
+
+ {/* User message — rounded bubble, right-aligned */}
+
+
+
+
+ {/* Assistant — no bubble, full-width prose */}
+
+ {showToolCall && (
+
+ {/* Agent group header — icon + label + chevron */}
+ setToolsExpanded((p) => !p)}
+ className='flex cursor-pointer items-center gap-2'
+ >
+
+
+
+
+ Mothership
+
+
+
+
+ {/* Tool call items — collapsible */}
+
+
+
+
+ }
+ title='Read Customer Leads'
+ />
+
+
+
+
+ {/* Response prose — full width, no card */}
+ {isResponding && (
+
+
+
+ )}
+
+ )}
+
+
+
+
+ {/* Resource panel — slides in from right */}
+
+ {showResourcePanel && (
+
+
+
+ )}
+
+
+ )
+ }
+
+ return (
+
+
+ What should we get done?
+
+
+
+ textareaRef.current?.focus()}
+ >
+
+
+
+ )
+})
+
+/**
+ * Single tool call row matching the real `ToolCallItem` layout:
+ * indented icon + display title.
+ */
+function ToolCallRow({ icon, title }: { icon: React.ReactNode; title: string }) {
+ return (
+
+ )
+}
+
+/**
+ * Renders chat response as full-width prose with bold markdown
+ * and progressive reveal for the typing effect.
+ */
+function ChatMarkdown({
+ content,
+ visibleLength,
+ isTyping,
+}: {
+ content: string
+ visibleLength: number
+ isTyping: boolean
+}) {
+ const visible = content.slice(0, visibleLength)
+ const rendered = visible.replace(/\*\*(.+?)\*\*/g, '$1 ').replace(/\n/g, ' ')
+
+ return (
+
+
+ {isTyping && (
+
+ )}
+
+ )
+}
+
+/**
+ * Mini Customer Leads table panel matching the resource panel pattern.
+ */
+function MiniTablePanel() {
+ return (
+
+
+
+
+
+ {MINI_TABLE_COLUMNS.map((col) => (
+
+ ))}
+
+
+
+ {MINI_TABLE_COLUMNS.map((col) => {
+ const Icon = COLUMN_TYPE_ICONS[col.type]
+ return (
+
+
+
+
+ {col.label}
+
+
+
+
+ )
+ })}
+
+
+
+ {MINI_TABLE_ROWS.map((row, i) => (
+
+ {MINI_TABLE_COLUMNS.map((col) => {
+ const val = row[col.id as keyof typeof row]
+ return (
+
+ {col.type === 'boolean' ? (
+
+
+
+ ) : (
+ {val}
+ )}
+
+ )
+ })}
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/apps/sim/app/(home)/components/landing-preview/components/landing-preview-knowledge/landing-preview-knowledge.tsx b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-knowledge/landing-preview-knowledge.tsx
similarity index 90%
rename from apps/sim/app/(home)/components/landing-preview/components/landing-preview-knowledge/landing-preview-knowledge.tsx
rename to apps/sim/app/(landing)/components/landing-preview/components/landing-preview-knowledge/landing-preview-knowledge.tsx
index f56f87ce7e9..aa216e5d574 100644
--- a/apps/sim/app/(home)/components/landing-preview/components/landing-preview-knowledge/landing-preview-knowledge.tsx
+++ b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-knowledge/landing-preview-knowledge.tsx
@@ -13,8 +13,8 @@ import {
import type {
PreviewColumn,
PreviewRow,
-} from '@/app/(home)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
-import { LandingPreviewResource } from '@/app/(home)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
+} from '@/app/(landing)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
+import { LandingPreviewResource } from '@/app/(landing)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
const DB_ICON =
diff --git a/apps/sim/app/(home)/components/landing-preview/components/landing-preview-logs/landing-preview-logs.tsx b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-logs/landing-preview-logs.tsx
similarity index 98%
rename from apps/sim/app/(home)/components/landing-preview/components/landing-preview-logs/landing-preview-logs.tsx
rename to apps/sim/app/(landing)/components/landing-preview/components/landing-preview-logs/landing-preview-logs.tsx
index f1406bb81dc..2070fab6468 100644
--- a/apps/sim/app/(home)/components/landing-preview/components/landing-preview-logs/landing-preview-logs.tsx
+++ b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-logs/landing-preview-logs.tsx
@@ -59,7 +59,7 @@ const MOCK_LOGS: LogRow[] = [
workflowColor: '#33C482',
date: 'Apr 1 09:15 AM',
status: 'error',
- cost: '318 credits',
+ cost: '1 credit',
trigger: 'api',
triggerLabel: 'API',
duration: '2.7s',
@@ -70,7 +70,7 @@ const MOCK_LOGS: LogRow[] = [
workflowColor: '#a855f7',
date: 'Apr 1 08:30 AM',
status: 'completed',
- cost: '89 credits',
+ cost: '2 credits',
trigger: 'schedule',
triggerLabel: 'Schedule',
duration: '0.8s',
@@ -81,7 +81,7 @@ const MOCK_LOGS: LogRow[] = [
workflowColor: '#f97316',
date: 'Mar 31 10:14 PM',
status: 'completed',
- cost: '241 credits',
+ cost: '7 credits',
trigger: 'webhook',
triggerLabel: 'Webhook',
duration: '4.1s',
@@ -92,7 +92,7 @@ const MOCK_LOGS: LogRow[] = [
workflowColor: '#ec4899',
date: 'Mar 31 08:45 PM',
status: 'completed',
- cost: '112 credits',
+ cost: '2 credits',
trigger: 'manual',
triggerLabel: 'Manual',
duration: '0.9s',
@@ -103,7 +103,7 @@ const MOCK_LOGS: LogRow[] = [
workflowColor: '#0ea5e9',
date: 'Mar 31 07:22 PM',
status: 'completed',
- cost: '197 credits',
+ cost: '3 credits',
trigger: 'api',
triggerLabel: 'API',
duration: '1.6s',
@@ -114,7 +114,7 @@ const MOCK_LOGS: LogRow[] = [
workflowColor: '#f59e0b',
date: 'Mar 31 06:11 PM',
status: 'error',
- cost: '284 credits',
+ cost: '1 credit',
trigger: 'schedule',
triggerLabel: 'Schedule',
duration: '3.2s',
diff --git a/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-panel/landing-preview-panel.tsx b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-panel/landing-preview-panel.tsx
new file mode 100644
index 00000000000..4ca06238463
--- /dev/null
+++ b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-panel/landing-preview-panel.tsx
@@ -0,0 +1,474 @@
+'use client'
+
+import { memo, useCallback, useEffect, useRef, useState } from 'react'
+import { AnimatePresence, motion } from 'framer-motion'
+import { ArrowUp } from 'lucide-react'
+import Link from 'next/link'
+import { useRouter } from 'next/navigation'
+import { createPortal } from 'react-dom'
+import { Blimp, BubbleChatPreview, ChevronDown, MoreHorizontal, Play } from '@/components/emcn'
+import { AgentIcon, HubspotIcon, OpenAIIcon, SalesforceIcon } from '@/components/icons'
+import { LandingPromptStorage } from '@/lib/core/utils/browser-storage'
+import {
+ EASE_OUT,
+ type EditorPromptData,
+ getEditorPrompt,
+ getWorkflowAnimationTiming,
+ type PreviewWorkflow,
+ TYPE_INTERVAL_MS,
+ TYPE_START_BUFFER_MS,
+} from '@/app/(landing)/components/landing-preview/components/landing-preview-workflow/workflow-data'
+
+type PanelTab = 'copilot' | 'editor'
+
+const EDITOR_BLOCK_ICONS: Record> = {
+ agent: AgentIcon,
+ mothership: Blimp,
+}
+
+const TABS_WITH_TOOLBAR: { id: PanelTab | 'toolbar'; label: string; disabled?: boolean }[] = [
+ { id: 'copilot', label: 'Copilot' },
+ { id: 'toolbar', label: 'Toolbar', disabled: true },
+ { id: 'editor', label: 'Editor' },
+]
+
+/**
+ * Stores the prompt in browser storage and redirects to /signup.
+ * Shared by both the copilot panel and the landing home view.
+ */
+export function useLandingSubmit() {
+ const router = useRouter()
+ return useCallback(
+ (text: string) => {
+ const trimmed = text.trim()
+ if (!trimmed) return
+ LandingPromptStorage.store(trimmed)
+ router.push('/signup')
+ },
+ [router]
+ )
+}
+
+interface LandingPreviewPanelProps {
+ activeWorkflow?: PreviewWorkflow
+ animationKey?: number
+ onHighlightBlock?: (blockId: string | null) => void
+}
+
+/**
+ * Workspace panel replica with switchable Copilot / Editor tabs.
+ *
+ * On every workflow switch (`animationKey` change):
+ * 1. Resets to Copilot tab.
+ * 2. Waits for blocks + edges to finish animating.
+ * 3. Slides the tab indicator to Editor and types the agent's prompt.
+ * 4. Highlights the agent block with the blue ring on the canvas.
+ */
+export const LandingPreviewPanel = memo(function LandingPreviewPanel({
+ activeWorkflow,
+ animationKey = 0,
+ onHighlightBlock,
+}: LandingPreviewPanelProps) {
+ const landingSubmit = useLandingSubmit()
+ const [inputValue, setInputValue] = useState('')
+ const textareaRef = useRef(null)
+ const [cursorPos, setCursorPos] = useState<{ x: number; y: number } | null>(null)
+
+ const [activeTab, setActiveTab] = useState('copilot')
+ const [typedLength, setTypedLength] = useState(0)
+
+ const workflowRef = useRef(activeWorkflow)
+ workflowRef.current = activeWorkflow
+ const typeIntervalRef = useRef | null>(null)
+
+ const editorPrompt = activeWorkflow ? getEditorPrompt(activeWorkflow) : null
+
+ const userSwitchedTabRef = useRef(false)
+
+ const handleTabSwitch = useCallback(
+ (tab: PanelTab) => {
+ userSwitchedTabRef.current = true
+ setActiveTab(tab)
+ if (tab === 'editor' && editorPrompt) {
+ onHighlightBlock?.(editorPrompt.blockId)
+ } else {
+ onHighlightBlock?.(null)
+ }
+ },
+ [editorPrompt, onHighlightBlock]
+ )
+
+ useEffect(() => {
+ if (userSwitchedTabRef.current) return
+
+ setActiveTab('copilot')
+ setTypedLength(0)
+ onHighlightBlock?.(null)
+ if (typeIntervalRef.current) clearInterval(typeIntervalRef.current)
+
+ const workflow = workflowRef.current
+ if (!workflow) return
+
+ const prompt = workflow ? getEditorPrompt(workflow) : null
+ if (!prompt) return
+
+ const { editorDelay } = getWorkflowAnimationTiming(workflow)
+
+ const switchTimer = setTimeout(() => {
+ if (userSwitchedTabRef.current) return
+ setActiveTab('editor')
+ onHighlightBlock?.(prompt.blockId)
+ }, editorDelay)
+
+ const typeTimer = setTimeout(() => {
+ if (userSwitchedTabRef.current) return
+ let charIndex = 0
+ typeIntervalRef.current = setInterval(() => {
+ charIndex++
+ setTypedLength(charIndex)
+ if (charIndex >= prompt.prompt.length) {
+ if (typeIntervalRef.current) clearInterval(typeIntervalRef.current)
+ typeIntervalRef.current = null
+ }
+ }, TYPE_INTERVAL_MS)
+ }, editorDelay + TYPE_START_BUFFER_MS)
+
+ return () => {
+ clearTimeout(switchTimer)
+ clearTimeout(typeTimer)
+ if (typeIntervalRef.current) {
+ clearInterval(typeIntervalRef.current)
+ typeIntervalRef.current = null
+ }
+ }
+ }, [animationKey, onHighlightBlock])
+
+ const isEmpty = inputValue.trim().length === 0
+
+ const handleSubmit = useCallback(() => {
+ if (isEmpty) return
+ landingSubmit(inputValue)
+ }, [isEmpty, inputValue, landingSubmit])
+
+ const handleKeyDown = useCallback(
+ (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault()
+ handleSubmit()
+ }
+ },
+ [handleSubmit]
+ )
+
+ return (
+
+
+ {/* Header */}
+
+
+
setCursorPos({ x: e.clientX, y: e.clientY })}
+ onMouseLeave={() => setCursorPos(null)}
+ >
+
+ Deploy
+
+
+
+ {cursorPos &&
+ createPortal(
+
,
+ document.body
+ )}
+
+
+ {/* Tabs with sliding active indicator */}
+
+
+ {TABS_WITH_TOOLBAR.map((tab) => {
+ if (tab.disabled) {
+ return (
+
+ {tab.label}
+
+ )
+ }
+ const isActive = activeTab === tab.id
+ return (
+
handleTabSwitch(tab.id as PanelTab)}
+ className='relative flex h-[28px] items-center rounded-md border border-transparent px-2 py-[5px] font-medium text-[12.5px] transition-colors hover:border-[#3d3d3d] hover:bg-[#363636] hover:text-[#e6e6e6]'
+ style={{ color: isActive ? '#e6e6e6' : '#787878' }}
+ >
+ {isActive && (
+
+ )}
+ {tab.label}
+
+ )
+ })}
+
+
+
+ {/* Tab content with cross-fade */}
+
+
+ {activeTab === 'copilot' && (
+
+
+
+ New Chat
+
+
+
+
+ )}
+
+ {activeTab === 'editor' && (
+
+
+
+ )}
+
+
+
+
+ )
+})
+
+const TOOL_ICONS: Record> = {
+ hubspot: HubspotIcon,
+ salesforce: SalesforceIcon,
+}
+
+const MODEL_ICON_MAP: Record> = {
+ 'gpt-': OpenAIIcon,
+}
+
+function getModelIcon(model: string) {
+ const lower = model.toLowerCase()
+ for (const [prefix, icon] of Object.entries(MODEL_ICON_MAP)) {
+ if (lower.startsWith(prefix)) return icon
+ }
+ return null
+}
+
+interface EditorTabContentProps {
+ editorPrompt: EditorPromptData | null
+ typedLength: number
+}
+
+/**
+ * Editor tab replicating the real agent editor layout:
+ * header bar, then scrollable sub-block fields.
+ */
+function EditorTabContent({ editorPrompt, typedLength }: EditorTabContentProps) {
+ if (!editorPrompt) {
+ return (
+
+ Select a block to edit
+
+ )
+ }
+
+ const { blockName, blockType, bgColor, prompt, model, tools } = editorPrompt
+ const visibleText = prompt.slice(0, typedLength)
+ const isTyping = typedLength < prompt.length
+ const BlockIcon = EDITOR_BLOCK_ICONS[blockType]
+ const ModelIcon = model ? getModelIcon(model) : null
+
+ return (
+
+ {/* Editor header */}
+
+ {BlockIcon && (
+
+
+
+ )}
+
+ {blockName}
+
+
+
+ {/* Sub-block fields */}
+
+
+ {/* System Prompt */}
+
+
+ System Prompt
+
+
+
+ {visibleText}
+ {isTyping && (
+
+ )}
+
+
+
+
+ {/* Model */}
+ {model && (
+
+
+ Model
+
+
+ {ModelIcon && }
+ {model}
+
+
+
+ )}
+
+ {/* Tools */}
+ {tools.length > 0 && (
+
+
+ Tools
+
+
+ {tools.map((tool) => {
+ const ToolIcon = TOOL_ICONS[tool.type]
+ return (
+
+ {ToolIcon && (
+
+
+
+ )}
+
{tool.name}
+
+ )
+ })}
+
+
+ )}
+
+ {/* Temperature */}
+
+
+ Temperature
+ 0.7
+
+
+
+
+ {/* Response Format */}
+
+
+ Response Format
+
+
+ plain text
+
+
+
+
+
+ )
+}
diff --git a/apps/sim/app/(home)/components/landing-preview/components/landing-preview-resource/landing-preview-resource.tsx b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-resource/landing-preview-resource.tsx
similarity index 100%
rename from apps/sim/app/(home)/components/landing-preview/components/landing-preview-resource/landing-preview-resource.tsx
rename to apps/sim/app/(landing)/components/landing-preview/components/landing-preview-resource/landing-preview-resource.tsx
diff --git a/apps/sim/app/(home)/components/landing-preview/components/landing-preview-scheduled-tasks/landing-preview-scheduled-tasks.tsx b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-scheduled-tasks/landing-preview-scheduled-tasks.tsx
similarity index 89%
rename from apps/sim/app/(home)/components/landing-preview/components/landing-preview-scheduled-tasks/landing-preview-scheduled-tasks.tsx
rename to apps/sim/app/(landing)/components/landing-preview/components/landing-preview-scheduled-tasks/landing-preview-scheduled-tasks.tsx
index 0750f4536ce..42c63e01046 100644
--- a/apps/sim/app/(home)/components/landing-preview/components/landing-preview-scheduled-tasks/landing-preview-scheduled-tasks.tsx
+++ b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-scheduled-tasks/landing-preview-scheduled-tasks.tsx
@@ -2,8 +2,8 @@ import { Calendar } from '@/components/emcn/icons'
import type {
PreviewColumn,
PreviewRow,
-} from '@/app/(home)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
-import { LandingPreviewResource } from '@/app/(home)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
+} from '@/app/(landing)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
+import { LandingPreviewResource } from '@/app/(landing)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
const CAL_ICON =
diff --git a/apps/sim/app/(home)/components/landing-preview/components/landing-preview-sidebar/landing-preview-sidebar.tsx b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-sidebar/landing-preview-sidebar.tsx
similarity index 98%
rename from apps/sim/app/(home)/components/landing-preview/components/landing-preview-sidebar/landing-preview-sidebar.tsx
rename to apps/sim/app/(landing)/components/landing-preview/components/landing-preview-sidebar/landing-preview-sidebar.tsx
index de104c6286c..1b754f65f36 100644
--- a/apps/sim/app/(home)/components/landing-preview/components/landing-preview-sidebar/landing-preview-sidebar.tsx
+++ b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-sidebar/landing-preview-sidebar.tsx
@@ -11,7 +11,7 @@ import {
Table,
} from '@/components/emcn/icons'
import { cn } from '@/lib/core/utils/cn'
-import type { PreviewWorkflow } from '@/app/(home)/components/landing-preview/components/landing-preview-workflow/workflow-data'
+import type { PreviewWorkflow } from '@/app/(landing)/components/landing-preview/components/landing-preview-workflow/workflow-data'
export type SidebarView =
| 'home'
diff --git a/apps/sim/app/(home)/components/landing-preview/components/landing-preview-tables/landing-preview-tables.tsx b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-tables/landing-preview-tables.tsx
similarity index 89%
rename from apps/sim/app/(home)/components/landing-preview/components/landing-preview-tables/landing-preview-tables.tsx
rename to apps/sim/app/(landing)/components/landing-preview/components/landing-preview-tables/landing-preview-tables.tsx
index 42539519a59..0bc1b2299cd 100644
--- a/apps/sim/app/(home)/components/landing-preview/components/landing-preview-tables/landing-preview-tables.tsx
+++ b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-tables/landing-preview-tables.tsx
@@ -1,6 +1,7 @@
'use client'
-import { useState } from 'react'
+import { useEffect, useState } from 'react'
+import { AnimatePresence, motion } from 'framer-motion'
import { Checkbox } from '@/components/emcn'
import {
ChevronDown,
@@ -15,11 +16,11 @@ import { cn } from '@/lib/core/utils/cn'
import type {
PreviewColumn,
PreviewRow,
-} from '@/app/(home)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
+} from '@/app/(landing)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
import {
LandingPreviewResource,
ownerCell,
-} from '@/app/(home)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
+} from '@/app/(landing)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
const CELL = 'border-[var(--border)] border-r border-b px-2 py-[7px] align-middle select-none'
const CELL_CHECKBOX =
@@ -525,28 +526,59 @@ function SpreadsheetView({ tableId, tableName, onBack }: SpreadsheetViewProps) {
)
}
-export function LandingPreviewTables() {
+interface LandingPreviewTablesProps {
+ autoOpenTableId?: string | null
+}
+
+const tableViewTransition = {
+ initial: { opacity: 0, x: 20 },
+ animate: { opacity: 1, x: 0 },
+ exit: { opacity: 0, x: -20 },
+ transition: { duration: 0.25, ease: [0.16, 1, 0.3, 1] as const },
+} as const
+
+export function LandingPreviewTables({ autoOpenTableId }: LandingPreviewTablesProps = {}) {
const [openTableId, setOpenTableId] = useState(null)
- if (openTableId !== null) {
- return (
- setOpenTableId(null)}
- />
- )
- }
+ useEffect(() => {
+ if (!autoOpenTableId) return
+ const timer = setTimeout(() => {
+ setOpenTableId(autoOpenTableId)
+ }, 800)
+ return () => clearTimeout(timer)
+ }, [autoOpenTableId])
return (
- setOpenTableId(id)}
- />
+
+ {openTableId !== null ? (
+
+ setOpenTableId(null)}
+ />
+
+ ) : (
+
+ setOpenTableId(id)}
+ />
+
+ )}
+
)
}
diff --git a/apps/sim/app/(home)/components/landing-preview/components/landing-preview-workflow/landing-preview-workflow.tsx b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-workflow/landing-preview-workflow.tsx
similarity index 75%
rename from apps/sim/app/(home)/components/landing-preview/components/landing-preview-workflow/landing-preview-workflow.tsx
rename to apps/sim/app/(landing)/components/landing-preview/components/landing-preview-workflow/landing-preview-workflow.tsx
index 7cc0079a8ff..fcbf20cf662 100644
--- a/apps/sim/app/(home)/components/landing-preview/components/landing-preview-workflow/landing-preview-workflow.tsx
+++ b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-workflow/landing-preview-workflow.tsx
@@ -1,6 +1,6 @@
'use client'
-import { useCallback, useMemo, useState } from 'react'
+import { useCallback, useEffect, useMemo, useState } from 'react'
import { motion } from 'framer-motion'
import ReactFlow, {
applyEdgeChanges,
@@ -16,22 +16,24 @@ import ReactFlow, {
ReactFlowProvider,
} from 'reactflow'
import 'reactflow/dist/style.css'
-import { PreviewBlockNode } from '@/app/(home)/components/landing-preview/components/landing-preview-workflow/preview-block-node'
+import { PreviewBlockNode } from '@/app/(landing)/components/landing-preview/components/landing-preview-workflow/preview-block-node'
import {
EASE_OUT,
type PreviewWorkflow,
toReactFlowElements,
-} from '@/app/(home)/components/landing-preview/components/landing-preview-workflow/workflow-data'
+} from '@/app/(landing)/components/landing-preview/components/landing-preview-workflow/workflow-data'
interface FitViewOptions {
padding?: number
maxZoom?: number
+ minZoom?: number
}
interface LandingPreviewWorkflowProps {
workflow: PreviewWorkflow
animate?: boolean
fitViewOptions?: FitViewOptions
+ highlightedBlockId?: string | null
}
/**
@@ -88,21 +90,35 @@ function PreviewEdge({
const NODE_TYPES: NodeTypes = { previewBlock: PreviewBlockNode }
const EDGE_TYPES: EdgeTypes = { previewEdge: PreviewEdge }
const PRO_OPTIONS = { hideAttribution: true }
-const DEFAULT_FIT_VIEW_OPTIONS = { padding: 0.3, maxZoom: 1 } as const
+const DEFAULT_FIT_VIEW_OPTIONS = { padding: 0.5, maxZoom: 1 } as const
/**
* Inner flow component. Keyed on workflow ID by the parent so it remounts
* cleanly on workflow switch — fitView fires on mount with zero delay.
*/
-function PreviewFlow({ workflow, animate = false, fitViewOptions }: LandingPreviewWorkflowProps) {
+function PreviewFlow({
+ workflow,
+ animate = false,
+ fitViewOptions,
+ highlightedBlockId,
+}: LandingPreviewWorkflowProps) {
const { nodes: initialNodes, edges: initialEdges } = useMemo(
- () => toReactFlowElements(workflow, animate),
- [workflow, animate]
+ () => toReactFlowElements(workflow, animate, highlightedBlockId),
+ [workflow, animate, highlightedBlockId]
)
const [nodes, setNodes] = useState(initialNodes)
const [edges, setEdges] = useState(initialEdges)
+ useEffect(() => {
+ setNodes((prev) =>
+ prev.map((node) => ({
+ ...node,
+ data: { ...node.data, isHighlighted: highlightedBlockId === node.id },
+ }))
+ )
+ }, [highlightedBlockId])
+
const onNodesChange: OnNodesChange = useCallback(
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
[]
@@ -114,6 +130,7 @@ function PreviewFlow({ workflow, animate = false, fitViewOptions }: LandingPrevi
)
const resolvedFitViewOptions = fitViewOptions ?? DEFAULT_FIT_VIEW_OPTIONS
+ const minZoom = fitViewOptions?.minZoom ?? 0.5
return (
-
+
)
diff --git a/apps/sim/app/(home)/components/landing-preview/components/landing-preview-workflow/preview-block-node.tsx b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-workflow/preview-block-node.tsx
similarity index 96%
rename from apps/sim/app/(home)/components/landing-preview/components/landing-preview-workflow/preview-block-node.tsx
rename to apps/sim/app/(landing)/components/landing-preview/components/landing-preview-workflow/preview-block-node.tsx
index 262b677e131..2c49f1106ee 100644
--- a/apps/sim/app/(home)/components/landing-preview/components/landing-preview-workflow/preview-block-node.tsx
+++ b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-workflow/preview-block-node.tsx
@@ -39,7 +39,7 @@ import {
BLOCK_STAGGER,
EASE_OUT,
type PreviewTool,
-} from '@/app/(home)/components/landing-preview/components/landing-preview-workflow/workflow-data'
+} from '@/app/(landing)/components/landing-preview/components/landing-preview-workflow/workflow-data'
/** Map block type strings to their icon components. */
const BLOCK_ICONS: Record
> = {
@@ -105,6 +105,7 @@ interface PreviewBlockData {
hideSourceHandle?: boolean
index?: number
animate?: boolean
+ isHighlighted?: boolean
}
/**
@@ -137,6 +138,7 @@ export const PreviewBlockNode = memo(function PreviewBlockNode({
hideSourceHandle,
index = 0,
animate = false,
+ isHighlighted = false,
} = data
const Icon = BLOCK_ICONS[blockType]
const delay = animate ? index * BLOCK_STAGGER : 0
@@ -264,6 +266,10 @@ export const PreviewBlockNode = memo(function PreviewBlockNode({
isConnectableEnd={false}
/>
)}
+
+ {isHighlighted && (
+
+ )}
)
diff --git a/apps/sim/app/(home)/components/landing-preview/components/landing-preview-workflow/workflow-data.ts b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-workflow/workflow-data.ts
similarity index 65%
rename from apps/sim/app/(home)/components/landing-preview/components/landing-preview-workflow/workflow-data.ts
rename to apps/sim/app/(landing)/components/landing-preview/components/landing-preview-workflow/workflow-data.ts
index 2fb6c2b567f..dceeb71829f 100644
--- a/apps/sim/app/(home)/components/landing-preview/components/landing-preview-workflow/workflow-data.ts
+++ b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-workflow/workflow-data.ts
@@ -66,7 +66,11 @@ const IT_SERVICE_WORKFLOW: PreviewWorkflow = {
bgColor: '#701ffc',
rows: [
{ title: 'Model', value: 'claude-sonnet-4.6' },
- { title: 'System Prompt', value: 'Triage incoming IT...' },
+ {
+ title: 'System Prompt',
+ value:
+ 'Triage incoming IT support requests from Slack, categorize by severity, and create Jira tickets for the appropriate team.',
+ },
],
tools: [{ name: 'Knowledge Base', type: 'knowledge_base', bgColor: '#10B981' }],
position: { x: 420, y: 40 },
@@ -91,7 +95,7 @@ const IT_SERVICE_WORKFLOW: PreviewWorkflow = {
}
/**
- * Self-healing CRM workflow — Schedule -> Mothership
+ * Self-healing CRM workflow — Schedule -> Agent
*/
const SELF_HEALING_CRM_WORKFLOW: PreviewWorkflow = {
id: 'wf-self-healing-crm',
@@ -111,20 +115,27 @@ const SELF_HEALING_CRM_WORKFLOW: PreviewWorkflow = {
hideTargetHandle: true,
},
{
- id: 'mothership-1',
+ id: 'agent-crm',
name: 'CRM Agent',
- type: 'mothership',
- bgColor: '#33C482',
- rows: [{ title: 'Prompt', value: 'Audit CRM records, fix...' }],
+ type: 'agent',
+ bgColor: '#701ffc',
+ rows: [
+ { title: 'Model', value: 'gpt-5.4' },
+ {
+ title: 'System Prompt',
+ value:
+ 'Audit CRM records, identify data inconsistencies, and fix duplicate contacts, missing fields, and stale pipeline entries across HubSpot and Salesforce.',
+ },
+ ],
tools: [
{ name: 'HubSpot', type: 'hubspot', bgColor: '#FF7A59' },
{ name: 'Salesforce', type: 'salesforce', bgColor: '#E0E0E0' },
],
- position: { x: 420, y: 180 },
+ position: { x: 420, y: 140 },
hideSourceHandle: true,
},
],
- edges: [{ id: 'e-3', source: 'schedule-1', target: 'mothership-1' }],
+ edges: [{ id: 'e-3', source: 'schedule-1', target: 'agent-crm' }],
}
/**
@@ -154,7 +165,11 @@ const CUSTOMER_SUPPORT_WORKFLOW: PreviewWorkflow = {
bgColor: '#701ffc',
rows: [
{ title: 'Model', value: 'gpt-5.4' },
- { title: 'System Prompt', value: 'Resolve customer issues...' },
+ {
+ title: 'System Prompt',
+ value:
+ 'Resolve customer support issues using the knowledge base, draft a response, and notify the team in Slack.',
+ },
],
tools: [
{ name: 'Knowledge', type: 'knowledge_base', bgColor: '#10B981' },
@@ -228,7 +243,8 @@ const EDGE_STYLE = { stroke: '#454545', strokeWidth: 1.5 } as const
*/
export function toReactFlowElements(
workflow: PreviewWorkflow,
- animate = false
+ animate = false,
+ highlightedBlockId?: string | null
): {
nodes: Node[]
edges: Edge[]
@@ -250,6 +266,7 @@ export function toReactFlowElements(
hideSourceHandle: block.hideSourceHandle,
index,
animate,
+ isHighlighted: highlightedBlockId === block.id,
},
draggable: true,
selectable: false,
@@ -278,3 +295,74 @@ export function toReactFlowElements(
return { nodes, edges }
}
+
+/** Block types that carry an editable prompt suitable for the Editor tab. */
+const AGENT_BLOCK_TYPES = new Set(['agent', 'mothership'])
+
+export interface EditorPromptData {
+ blockId: string
+ blockName: string
+ blockType: string
+ bgColor: string
+ prompt: string
+ model: string | null
+ tools: PreviewTool[]
+}
+
+/**
+ * Extracts the editor-facing prompt from the first agent/mothership block.
+ *
+ * @returns Block metadata + prompt + model + tools, or `null` when the workflow has no agent.
+ */
+export function getEditorPrompt(workflow: PreviewWorkflow): EditorPromptData | null {
+ for (const block of workflow.blocks) {
+ if (!AGENT_BLOCK_TYPES.has(block.type)) continue
+ const promptRow = block.rows.find((r) => r.title === 'Prompt' || r.title === 'System Prompt')
+ if (promptRow) {
+ const modelRow = block.rows.find((r) => r.title === 'Model')
+ return {
+ blockId: block.id,
+ blockName: block.name,
+ blockType: block.type,
+ bgColor: block.bgColor,
+ prompt: promptRow.value,
+ model: modelRow?.value ?? null,
+ tools: block.tools ?? [],
+ }
+ }
+ }
+ return null
+}
+
+/**
+ * Computes the delay (ms) before the Editor tab should activate.
+ * Accounts for all block staggers + edge draw durations + a small buffer.
+ */
+export function getWorkflowAnimationTiming(workflow: PreviewWorkflow): { editorDelay: number } {
+ const maxBlockIndex = Math.max(0, workflow.blocks.length - 1)
+ const hasEdges = workflow.edges.length > 0
+ const edgeDuration = hasEdges ? 0.4 : 0
+ const buffer = 0.15
+ const total = maxBlockIndex * BLOCK_STAGGER + BLOCK_STAGGER + edgeDuration + buffer
+ return { editorDelay: Math.round(total * 1000) }
+}
+
+/** Milliseconds between each character typed in the Editor prompt animation. */
+export const TYPE_INTERVAL_MS = 30
+
+/** Extra pause (ms) after switching to the Editor tab before typing begins. */
+export const TYPE_START_BUFFER_MS = 150
+
+/** How long to dwell on a completed step before advancing (ms). */
+export const STEP_DWELL_MS = 2500
+
+/**
+ * Computes the total time (ms) a workflow step occupies, including
+ * canvas animation, editor typing, and a dwell period.
+ */
+export function getWorkflowStepDuration(workflow: PreviewWorkflow): number {
+ const { editorDelay } = getWorkflowAnimationTiming(workflow)
+ const prompt = getEditorPrompt(workflow)
+ const typingTime = prompt ? prompt.prompt.length * TYPE_INTERVAL_MS : 0
+ return editorDelay + TYPE_START_BUFFER_MS + typingTime + STEP_DWELL_MS
+}
diff --git a/apps/sim/app/(landing)/components/landing-preview/landing-preview.tsx b/apps/sim/app/(landing)/components/landing-preview/landing-preview.tsx
new file mode 100644
index 00000000000..f990c259f07
--- /dev/null
+++ b/apps/sim/app/(landing)/components/landing-preview/landing-preview.tsx
@@ -0,0 +1,322 @@
+'use client'
+
+import { useCallback, useEffect, useRef, useState } from 'react'
+import { AnimatePresence, motion, type Variants } from 'framer-motion'
+import { LandingPreviewFiles } from '@/app/(landing)/components/landing-preview/components/landing-preview-files/landing-preview-files'
+import { LandingPreviewHome } from '@/app/(landing)/components/landing-preview/components/landing-preview-home/landing-preview-home'
+import { LandingPreviewKnowledge } from '@/app/(landing)/components/landing-preview/components/landing-preview-knowledge/landing-preview-knowledge'
+import { LandingPreviewLogs } from '@/app/(landing)/components/landing-preview/components/landing-preview-logs/landing-preview-logs'
+import { LandingPreviewPanel } from '@/app/(landing)/components/landing-preview/components/landing-preview-panel/landing-preview-panel'
+import { LandingPreviewScheduledTasks } from '@/app/(landing)/components/landing-preview/components/landing-preview-scheduled-tasks/landing-preview-scheduled-tasks'
+import type { SidebarView } from '@/app/(landing)/components/landing-preview/components/landing-preview-sidebar/landing-preview-sidebar'
+import { LandingPreviewSidebar } from '@/app/(landing)/components/landing-preview/components/landing-preview-sidebar/landing-preview-sidebar'
+import { LandingPreviewTables } from '@/app/(landing)/components/landing-preview/components/landing-preview-tables/landing-preview-tables'
+import { LandingPreviewWorkflow } from '@/app/(landing)/components/landing-preview/components/landing-preview-workflow/landing-preview-workflow'
+import {
+ EASE_OUT,
+ getWorkflowStepDuration,
+ PREVIEW_WORKFLOWS,
+} from '@/app/(landing)/components/landing-preview/components/landing-preview-workflow/workflow-data'
+
+const containerVariants: Variants = {
+ hidden: {},
+ visible: {
+ transition: { staggerChildren: 0.15 },
+ },
+}
+
+const sidebarVariants: Variants = {
+ hidden: { opacity: 0, x: -12 },
+ visible: {
+ opacity: 1,
+ x: 0,
+ transition: {
+ x: { duration: 0.25, ease: EASE_OUT },
+ opacity: { duration: 0.25, ease: EASE_OUT },
+ },
+ },
+}
+
+const panelVariants: Variants = {
+ hidden: { opacity: 0, x: 12 },
+ visible: {
+ opacity: 1,
+ x: 0,
+ transition: {
+ x: { duration: 0.25, ease: EASE_OUT },
+ opacity: { duration: 0.25, ease: EASE_OUT },
+ },
+ },
+}
+
+const viewTransition = {
+ initial: { opacity: 0 },
+ animate: { opacity: 1 },
+ exit: { opacity: 0 },
+ transition: { duration: 0.2, ease: EASE_OUT },
+} as const
+
+interface DemoStep {
+ type: 'workflow' | 'tables' | 'home' | 'logs'
+ workflowId?: string
+ tableId?: string
+ duration: number
+}
+
+const WORKFLOW_MAP = new Map(PREVIEW_WORKFLOWS.map((w) => [w.id, w]))
+
+const HOME_STEP_MS = 12000
+const LOGS_STEP_MS = 5000
+
+/** Full desktop sequence: CRM -> home -> logs -> ITSM -> support -> repeat */
+const DESKTOP_STEPS: DemoStep[] = [
+ {
+ type: 'workflow',
+ workflowId: 'wf-self-healing-crm',
+ duration: getWorkflowStepDuration(WORKFLOW_MAP.get('wf-self-healing-crm')!),
+ },
+ { type: 'home', duration: HOME_STEP_MS },
+ { type: 'logs', duration: LOGS_STEP_MS },
+ {
+ type: 'workflow',
+ workflowId: 'wf-it-service',
+ duration: getWorkflowStepDuration(WORKFLOW_MAP.get('wf-it-service')!),
+ },
+ {
+ type: 'workflow',
+ workflowId: 'wf-customer-support',
+ duration: getWorkflowStepDuration(WORKFLOW_MAP.get('wf-customer-support')!),
+ },
+]
+
+/**
+ * Interactive workspace preview for the hero section.
+ *
+ * Desktop: auto-cycles CRM -> home -> logs -> ITSM -> support -> repeat.
+ * Mobile: static workflow canvas (no animation, no cycling).
+ * User interaction permanently stops the auto-cycle.
+ */
+export function LandingPreview() {
+ const [activeView, setActiveView] = useState
('workflow')
+ const [activeWorkflowId, setActiveWorkflowId] = useState(PREVIEW_WORKFLOWS[0].id)
+ const animationKeyRef = useRef(0)
+ const [animationKey, setAnimationKey] = useState(0)
+ const [highlightedBlockId, setHighlightedBlockId] = useState(null)
+ const [autoTableId, setAutoTableId] = useState(null)
+ const [autoTypeHome, setAutoTypeHome] = useState(false)
+ const [isDesktop, setIsDesktop] = useState(true)
+
+ const demoIndexRef = useRef(0)
+ const demoTimerRef = useRef | null>(null)
+ const autoCycleActiveRef = useRef(true)
+ const isDesktopRef = useRef(true)
+
+ const clearDemoTimer = useCallback(() => {
+ if (demoTimerRef.current) {
+ clearTimeout(demoTimerRef.current)
+ demoTimerRef.current = null
+ }
+ }, [])
+
+ const applyDemoStep = useCallback((step: DemoStep) => {
+ setAutoTableId(null)
+ setAutoTypeHome(false)
+
+ if (step.type === 'workflow' && step.workflowId) {
+ setActiveWorkflowId(step.workflowId)
+ setActiveView('workflow')
+ animationKeyRef.current += 1
+ setAnimationKey(animationKeyRef.current)
+ } else if (step.type === 'tables') {
+ setActiveView('tables')
+ setAutoTableId(step.tableId ?? null)
+ } else if (step.type === 'home') {
+ setActiveView('home')
+ setAutoTypeHome(true)
+ } else if (step.type === 'logs') {
+ setActiveView('logs')
+ }
+ }, [])
+
+ const scheduleNextStep = useCallback(() => {
+ if (!autoCycleActiveRef.current) return
+ const steps = DESKTOP_STEPS
+ const currentStep = steps[demoIndexRef.current]
+ demoTimerRef.current = setTimeout(() => {
+ if (!autoCycleActiveRef.current) return
+ demoIndexRef.current = (demoIndexRef.current + 1) % steps.length
+ applyDemoStep(steps[demoIndexRef.current])
+ scheduleNextStep()
+ }, currentStep.duration)
+ }, [applyDemoStep])
+
+ useEffect(() => {
+ const desktop = window.matchMedia('(min-width: 1024px)').matches
+ isDesktopRef.current = desktop
+ setIsDesktop(desktop)
+ if (!desktop) return
+ applyDemoStep(DESKTOP_STEPS[0])
+ scheduleNextStep()
+ return clearDemoTimer
+ }, [applyDemoStep, scheduleNextStep, clearDemoTimer])
+
+ const stopAutoCycle = useCallback(() => {
+ autoCycleActiveRef.current = false
+ clearDemoTimer()
+ }, [clearDemoTimer])
+
+ const handleSelectWorkflow = useCallback(
+ (id: string) => {
+ stopAutoCycle()
+ setAutoTableId(null)
+ setAutoTypeHome(false)
+ setHighlightedBlockId(null)
+ setActiveWorkflowId(id)
+ setActiveView('workflow')
+ animationKeyRef.current += 1
+ setAnimationKey(animationKeyRef.current)
+ },
+ [stopAutoCycle]
+ )
+
+ const handleSelectHome = useCallback(() => {
+ stopAutoCycle()
+ setAutoTableId(null)
+ setAutoTypeHome(false)
+ setHighlightedBlockId(null)
+ setActiveView('home')
+ }, [stopAutoCycle])
+
+ const handleSelectNav = useCallback(
+ (id: SidebarView) => {
+ stopAutoCycle()
+ setAutoTableId(null)
+ setAutoTypeHome(false)
+ setHighlightedBlockId(null)
+ setActiveView(id)
+ },
+ [stopAutoCycle]
+ )
+
+ const handleHighlightBlock = useCallback((blockId: string | null) => {
+ setHighlightedBlockId(blockId)
+ }, [])
+
+ const activeWorkflow =
+ PREVIEW_WORKFLOWS.find((w) => w.id === activeWorkflowId) ?? PREVIEW_WORKFLOWS[0]
+
+ const isWorkflowView = activeView === 'workflow'
+
+ return (
+
+
+
+
+
+
+
+ {isDesktop ? (
+
+ {activeView === 'workflow' && (
+
+
+
+ )}
+ {activeView === 'home' && (
+
+
+
+ )}
+ {activeView === 'tables' && (
+
+
+
+ )}
+ {activeView === 'files' && (
+
+
+
+ )}
+ {activeView === 'knowledge' && (
+
+
+
+ )}
+ {activeView === 'logs' && (
+
+
+
+ )}
+ {activeView === 'scheduled-tasks' && (
+
+
+
+ )}
+
+ ) : (
+
+
+
+ )}
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/sim/app/(landing)/components/legal-layout.tsx b/apps/sim/app/(landing)/components/legal-layout.tsx
index a829a67b47f..3f5ebdee8de 100644
--- a/apps/sim/app/(landing)/components/legal-layout.tsx
+++ b/apps/sim/app/(landing)/components/legal-layout.tsx
@@ -1,7 +1,7 @@
import { getNavBlogPosts } from '@/lib/blog/registry'
import { isHosted } from '@/lib/core/config/feature-flags'
-import Footer from '@/app/(home)/components/footer/footer'
-import Navbar from '@/app/(home)/components/navbar/navbar'
+import Footer from '@/app/(landing)/components/footer/footer'
+import Navbar from '@/app/(landing)/components/navbar/navbar'
interface LegalLayoutProps {
title: string
diff --git a/apps/sim/app/(home)/components/navbar/components/blog-dropdown.tsx b/apps/sim/app/(landing)/components/navbar/components/blog-dropdown.tsx
similarity index 90%
rename from apps/sim/app/(home)/components/navbar/components/blog-dropdown.tsx
rename to apps/sim/app/(landing)/components/navbar/components/blog-dropdown.tsx
index 81db744ab67..dca4a4c131f 100644
--- a/apps/sim/app/(home)/components/navbar/components/blog-dropdown.tsx
+++ b/apps/sim/app/(landing)/components/navbar/components/blog-dropdown.tsx
@@ -44,9 +44,9 @@ function BlogCard({
unoptimized
/>
-
+
{title}
@@ -66,7 +66,7 @@ export function BlogDropdown({ posts }: BlogDropdownProps) {
if (!featured) return null
return (
-
+
-
+
+
{PREVIEW_CARDS.map((card) => (
diff --git a/apps/sim/app/(home)/components/navbar/navbar.tsx b/apps/sim/app/(landing)/components/navbar/navbar.tsx
similarity index 83%
rename from apps/sim/app/(home)/components/navbar/navbar.tsx
rename to apps/sim/app/(landing)/components/navbar/navbar.tsx
index b04972f2ff2..0cc2a8c306a 100644
--- a/apps/sim/app/(home)/components/navbar/navbar.tsx
+++ b/apps/sim/app/(landing)/components/navbar/navbar.tsx
@@ -10,9 +10,9 @@ import { cn } from '@/lib/core/utils/cn'
import {
BlogDropdown,
type NavBlogPost,
-} from '@/app/(home)/components/navbar/components/blog-dropdown'
-import { DocsDropdown } from '@/app/(home)/components/navbar/components/docs-dropdown'
-import { GitHubStars } from '@/app/(home)/components/navbar/components/github-stars'
+} from '@/app/(landing)/components/navbar/components/blog-dropdown'
+import { DocsDropdown } from '@/app/(landing)/components/navbar/components/docs-dropdown'
+import { GitHubStars } from '@/app/(landing)/components/navbar/components/github-stars'
import { getBrandConfig } from '@/ee/whitelabeling'
type DropdownId = 'docs' | 'blog' | null
@@ -29,10 +29,9 @@ const NAV_LINKS: NavLink[] = [
{ label: 'Docs', href: 'https://docs.sim.ai', external: true, icon: 'chevron', dropdown: 'docs' },
{ label: 'Blog', href: '/blog', icon: 'chevron', dropdown: 'blog' },
{ label: 'Pricing', href: '/#pricing' },
- { label: 'Enterprise', href: 'https://form.typeform.com/to/jqCO12pF', external: true },
]
-const LOGO_CELL = 'flex items-center pl-5 lg:pl-20 pr-5'
+const LOGO_CELL = 'flex items-center pl-5 lg:pl-16 pr-5'
const LINK_CELL = 'flex items-center px-3.5'
interface NavbarProps {
@@ -49,7 +48,6 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps
const useHomeLinks = isAuthenticated || isBrowsingHome
const logoHref = useHomeLinks ? '/?home' : '/'
const [activeDropdown, setActiveDropdown] = useState(null)
- const [hoveredLink, setHoveredLink] = useState(null)
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const closeTimerRef = useRef | null>(null)
@@ -91,12 +89,10 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps
return () => mq.removeEventListener('change', handler)
}, [])
- const anyHighlighted = activeDropdown !== null || hoveredLink !== null
-
return (
@@ -134,13 +130,9 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps
useHomeLinks && rawHref.startsWith('/#') ? `/?home${rawHref.slice(1)}` : rawHref
const hasDropdown = !!dropdown
const isActive = hasDropdown && activeDropdown === dropdown
- const isThisHovered = hoveredLink === label
- const isHighlighted = isActive || isThisHovered
- const isDimmed = anyHighlighted && !isHighlighted
const linkClass = cn(
icon ? `${LINK_CELL} gap-2` : LINK_CELL,
- 'transition-colors duration-200',
- isDimmed && 'text-[color-mix(in_srgb,var(--landing-text-subtle)_60%,transparent)]'
+ 'h-[30px] self-center rounded-[5px] transition-colors duration-200 group-hover:bg-[var(--landing-bg-elevated)]'
)
const chevron = icon === 'chevron' &&
@@ -148,7 +140,7 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps
return (
openDropdown(dropdown)}
onMouseLeave={scheduleClose}
>
@@ -157,51 +149,44 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps
href={href}
target='_blank'
rel='noopener noreferrer'
- className={cn(linkClass, 'h-full cursor-pointer')}
+ itemProp='url'
+ className={cn(linkClass, 'cursor-pointer')}
>
{label}
{chevron}
) : (
-
+
{label}
{chevron}
)}
-
- {dropdown === 'docs' && }
- {dropdown === 'blog' && }
-
+ {isActive && (
+
+ {dropdown === 'docs' && }
+ {dropdown === 'blog' && }
+
+ )}
)
}
return (
-
setHoveredLink(label)}
- onMouseLeave={() => setHoveredLink(null)}
- >
+
{external ? (
-
+
{label}
{chevron}
) : (
-
+
{label}
{chevron}
@@ -209,14 +194,7 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps
)
})}
-
setHoveredLink('github')}
- onMouseLeave={() => setHoveredLink(null)}
- >
+
@@ -225,7 +203,7 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps
@@ -271,7 +249,7 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps
diff --git a/apps/sim/app/(home)/components/pricing/pricing.tsx b/apps/sim/app/(landing)/components/pricing/pricing.tsx
similarity index 85%
rename from apps/sim/app/(home)/components/pricing/pricing.tsx
rename to apps/sim/app/(landing)/components/pricing/pricing.tsx
index c36a0ab2bf7..455aea124c8 100644
--- a/apps/sim/app/(home)/components/pricing/pricing.tsx
+++ b/apps/sim/app/(landing)/components/pricing/pricing.tsx
@@ -1,6 +1,8 @@
+'use client'
+
import Link from 'next/link'
import { Badge } from '@/components/emcn'
-import { DemoRequestModal } from '@/app/(home)/components/demo-request/demo-request-modal'
+import { DemoRequestModal } from '@/app/(landing)/components/demo-request/demo-request-modal'
interface PricingTier {
id: string
@@ -83,7 +85,7 @@ const PRICING_TIERS: PricingTier[] = [
'SSO & SCIM · SOC2',
'Self hosting · Dedicated support',
],
- cta: { label: 'Book a demo', action: 'demo-request' },
+ cta: { label: 'Get a demo', action: 'demo-request' },
},
]
@@ -110,7 +112,21 @@ function PricingCard({ tier }: PricingCardProps) {
const isPro = tier.id === 'pro'
return (
-
+
+
+
+
+
-
+
Pricing
+
+ Sim pricing: Community plan is free with 1,000 credits and 5GB storage. Pro plan is $25
+ per month with 6,000 credits and 50GB storage. Max plan is $100 per month with 25,000
+ credits and 500GB storage. Enterprise pricing is custom with SSO, SCIM, SOC2 compliance,
+ self-hosting, and dedicated support. All plans include CLI, SDK, and MCP access.
+
diff --git a/apps/sim/app/(home)/components/structured-data.tsx b/apps/sim/app/(landing)/components/structured-data.tsx
similarity index 84%
rename from apps/sim/app/(home)/components/structured-data.tsx
rename to apps/sim/app/(landing)/components/structured-data.tsx
index a16f0ef8464..1fc0122650a 100644
--- a/apps/sim/app/(home)/components/structured-data.tsx
+++ b/apps/sim/app/(landing)/components/structured-data.tsx
@@ -4,7 +4,7 @@
* Renders a `