- {STAGE_BLOCKS.map((block) => {
+ {blocks.map((block) => {
const built = builtIds.has(block.id)
return (
Sim is the open-source AI workspace where teams build, deploy, and manage AI agents. Connect
@@ -72,25 +82,16 @@ export function Hero() {
production-ready for teams of every size.
-
-
-
+
Sim is your AI workspace
for building agentic workflows.
-
-
-
- The open-source workspace where teams build, deploy, and manage AI agents.
-
-
-
-
-
-
-
+ >
+ }
+ description='The open-source workspace where teams build, deploy, and manage AI agents.'
+ />
{
const sentinel = sentinelRef.current
if (!sentinel) return
- const observer = new IntersectionObserver(([entry]) => setScrolled(!entry.isIntersecting))
+ const scrollPort = sentinel.parentElement
+ if (!scrollPort) return
+
+ const observer = new IntersectionObserver(([entry]) => setScrolled(!entry.isIntersecting), {
+ root: scrollPort,
+ })
observer.observe(sentinel)
return () => observer.disconnect()
}, [])
@@ -92,8 +98,8 @@ export function NavbarShell({ children }: NavbarShellProps) {
{children}
diff --git a/apps/sim/app/(landing)/components/navbar/navbar.tsx b/apps/sim/app/(landing)/components/navbar/navbar.tsx
index e0fcbc8f9c9..5799d9780ea 100644
--- a/apps/sim/app/(landing)/components/navbar/navbar.tsx
+++ b/apps/sim/app/(landing)/components/navbar/navbar.tsx
@@ -74,6 +74,9 @@ export function Navbar({ stars, logoOnly = false }: NavbarProps) {
{NAV_MENUS.map((menu) => (
))}
+
+ Enterprise
+
Pricing
diff --git a/apps/sim/app/(landing)/components/platform-page/components/platform-hero/platform-hero.tsx b/apps/sim/app/(landing)/components/platform-page/components/platform-hero/platform-hero.tsx
index f52250eef1e..5dabef8f612 100644
--- a/apps/sim/app/(landing)/components/platform-page/components/platform-hero/platform-hero.tsx
+++ b/apps/sim/app/(landing)/components/platform-page/components/platform-hero/platform-hero.tsx
@@ -1,5 +1,6 @@
import { cn } from '@sim/emcn'
import { HeroCta } from '@/app/(landing)/components/hero-cta'
+import { LANDING_HERO_CTA_GAP } from '@/app/(landing)/components/landing-layout'
import { PlatformVisualFrame } from '@/app/(landing)/components/platform-page/components/platform-visual-frame'
import { PLATFORM_SPACING } from '@/app/(landing)/components/platform-page/constants'
import type { PlatformHeroConfig } from '@/app/(landing)/components/platform-page/types'
@@ -49,7 +50,9 @@ export function PlatformHero({ hero }: PlatformHeroProps) {
{hero.description}
-
+
+
+
{hero.visual}
diff --git a/apps/sim/app/(landing)/components/solutions-page/components/solutions-card-row/components/solutions-card/solutions-card.tsx b/apps/sim/app/(landing)/components/solutions-page/components/solutions-card-row/components/solutions-card/solutions-card.tsx
index 7c67cccf721..2dd29e9ca1f 100644
--- a/apps/sim/app/(landing)/components/solutions-page/components/solutions-card-row/components/solutions-card/solutions-card.tsx
+++ b/apps/sim/app/(landing)/components/solutions-page/components/solutions-card-row/components/solutions-card/solutions-card.tsx
@@ -1,40 +1,97 @@
import { cn } from '@sim/emcn'
import { SolutionsVisualFrame } from '@/app/(landing)/components/solutions-page/components/solutions-visual-frame'
-import { SOLUTIONS_SPACING } from '@/app/(landing)/components/solutions-page/constants'
+import {
+ SOLUTIONS_FEATURE_TILE_TONE,
+ SOLUTIONS_SPACING,
+ SOLUTIONS_TEXT_MEASURE,
+ SOLUTIONS_VISUAL,
+} from '@/app/(landing)/components/solutions-page/constants'
import type { SolutionsCardConfig } from '@/app/(landing)/components/solutions-page/types'
/**
* A single solutions card - an `
` with an `` title, a body-color
- * description, and a reserved visual panel beneath. Text sits directly on the
- * canvas (matching the hero and every other landing section); only the visual
- * carries the `--surface-2` panel chrome, so the product mock reads as the one
- * elevated surface and never blends into a competing card fill.
+ * description, and a reserved visual area. The default split variant keeps copy
+ * on the page canvas with a framed visual beneath it. The feature-tile variant
+ * moves copy and the visual slot into one larger bordered surface for pages that
+ * need a unified callout card.
*
* The card owns the gap between its text and visual (`cardTextToVisual`) and the
* title→description stack (`cardTextStack`) - both from named spacing constants.
- * The text block grows (`flex-1`) so the visual pins to the bottom of the
- * grid-stretched cell: every card's visual aligns on one baseline regardless of
- * description length. The visual lands in a fixed-height {@link SolutionsVisualFrame}
- * so the row stays uniform and CLS is zero.
+ * The split variant lands the visual in a fixed-height
+ * {@link SolutionsVisualFrame}; the feature-tile variant reserves a larger
+ * flexible slot inside its own frame for future UI.
*
- * A content unit only: it accepts copy and a visual node, never any layout knob.
+ * A content unit only: it accepts copy and a visual node plus a controlled visual
+ * treatment, never arbitrary spacing or class overrides.
*/
interface SolutionsCardProps {
card: SolutionsCardConfig
/** Stable id wiring the `` into the page outline. */
headingId: string
+ /** Visual treatment. Defaults to the original split text + framed visual layout. */
+ variant?: 'split' | 'featureTile'
}
-export function SolutionsCard({ card, headingId }: SolutionsCardProps) {
+export function SolutionsCard({ card, headingId, variant = 'split' }: SolutionsCardProps) {
+ const featureTile = variant === 'featureTile'
+ const featureTileTone = SOLUTIONS_FEATURE_TILE_TONE[card.featureTileTone ?? 'light']
+ const featureTileDescription =
+ card.featureTileDescriptionTone === 'soft' && card.featureTileTone === 'dark'
+ ? SOLUTIONS_FEATURE_TILE_TONE.dark.descriptionSoft
+ : featureTileTone.description
+ const textBlock = (
+
+
+ {card.title}
+
+
+ {card.description}
+
+
+ )
+
+ if (featureTile) {
+ return (
+
+ {textBlock}
+
+
+
+ )
+ }
+
return (
-
-
- {card.title}
-
-
{card.description}
-
+ {textBlock}
{card.visual}
diff --git a/apps/sim/app/(landing)/components/solutions-page/components/solutions-card-row/solutions-card-row.tsx b/apps/sim/app/(landing)/components/solutions-page/components/solutions-card-row/solutions-card-row.tsx
index 33b4b616b14..d0aa0db0e5f 100644
--- a/apps/sim/app/(landing)/components/solutions-page/components/solutions-card-row/solutions-card-row.tsx
+++ b/apps/sim/app/(landing)/components/solutions-page/components/solutions-card-row/solutions-card-row.tsx
@@ -3,7 +3,10 @@ import {
SolutionsCard,
SolutionsPillCta,
} from '@/app/(landing)/components/solutions-page/components/solutions-card-row/components'
-import { SOLUTIONS_SPACING } from '@/app/(landing)/components/solutions-page/constants'
+import {
+ SOLUTIONS_SPACING,
+ SOLUTIONS_TEXT_MEASURE,
+} from '@/app/(landing)/components/solutions-page/constants'
import type { SolutionsCardRowConfig } from '@/app/(landing)/components/solutions-page/types'
/**
@@ -15,11 +18,18 @@ import type { SolutionsCardRowConfig } from '@/app/(landing)/components/solution
* Rendered as a labelled `` for a clean, crawlable landmark; each card
* is an `` with an ``, keeping the strict H2 → H3 hierarchy. Every
* gap (header sub-stack, header-to-grid, and inter-card) is owned by named
- * spacing constants; this component exposes no layout prop.
+ * spacing constants. Only the row header stack can opt into centered text; card
+ * text remains left aligned.
*/
interface SolutionsCardRowProps {
row: SolutionsCardRowConfig
+ /** Header stack alignment. Defaults to the original left-aligned layout. */
+ align?: 'left' | 'center'
+ /** Card treatment. Defaults to the original split copy + visual layout. */
+ cardVariant?: 'split' | 'featureTile'
+ /** Header typography treatment. Defaults to the original larger solutions header. */
+ headerVariant?: 'standard' | 'feature'
}
/** Maps a supported card count to its grid column class; anything else falls back to three-up. */
@@ -28,9 +38,16 @@ const GRID_COLS: Record = {
4: 'grid-cols-4',
}
-export function SolutionsCardRow({ row }: SolutionsCardRowProps) {
+export function SolutionsCardRow({
+ row,
+ align = 'left',
+ cardVariant = 'split',
+ headerVariant = 'standard',
+}: SolutionsCardRowProps) {
const headingId = `solutions-row-${row.id}-heading`
const gridCols = GRID_COLS[row.cards.length] ?? GRID_COLS[3]
+ const centered = align === 'center'
+ const featureHeader = headerVariant === 'feature'
return (
-
+
{row.title}
-
+
{row.subtitle}
-
+
+
+
))}
diff --git a/apps/sim/app/(landing)/components/solutions-page/components/solutions-hero/solutions-hero.tsx b/apps/sim/app/(landing)/components/solutions-page/components/solutions-hero/solutions-hero.tsx
index 81fdff060a2..400832024f8 100644
--- a/apps/sim/app/(landing)/components/solutions-page/components/solutions-hero/solutions-hero.tsx
+++ b/apps/sim/app/(landing)/components/solutions-page/components/solutions-hero/solutions-hero.tsx
@@ -1,30 +1,84 @@
-import { cn } from '@sim/emcn'
+import { ChipTag, cn } from '@sim/emcn'
+import { LandingHeroHeader } from '@/app/(landing)/components/hero/components/hero-header'
import { HeroCta } from '@/app/(landing)/components/hero-cta'
+import {
+ LANDING_CONTENT_WIDTH,
+ LANDING_GUTTER,
+ LANDING_HERO_CTA_GAP,
+ LANDING_HERO_TOP_PADDING,
+} from '@/app/(landing)/components/landing-layout'
import { SolutionsVisualFrame } from '@/app/(landing)/components/solutions-page/components/solutions-visual-frame'
-import { SOLUTIONS_SPACING } from '@/app/(landing)/components/solutions-page/constants'
+import {
+ SOLUTIONS_SPACING,
+ SOLUTIONS_TEXT_MEASURE,
+} from '@/app/(landing)/components/solutions-page/constants'
import type { SolutionsHeroConfig } from '@/app/(landing)/components/solutions-page/types'
/**
- * Solutions hero - the only `` on a solutions page. Left-aligned header copy
- * (headline + supporting description) sits above the same CTA as the landing
- * hero ({@link HeroCta}, the single source of truth), then a full-width solutions
- * visual underneath.
+ * Solutions hero - the only `` on a solutions page. Header copy (headline +
+ * supporting description) sits above the same CTA as the landing hero
+ * ({@link HeroCta}, the single source of truth), then a full-width solutions visual
+ * underneath. Pages can optionally center this header stack, use the home hero
+ * top layout, and show a mono chip above the headline, matching the home landing
+ * feature tags.
*
* The header column and the visual are stacked in one flex column; the header's
- * own sub-stack (headline → description → CTA) and the gap down to the visual are
+ * own sub-stack (tag → headline → description → CTA) and the gap down to the visual are
* both owned by named spacing constants, so a consumer page passes only copy and
* a visual node - never any spacing. The visual renders into a reserved-aspect
* {@link SolutionsVisualFrame} (CLS = 0).
*
* Carries the page's sr-only ~50-word product summary for AI citation (GEO). The
- * section's horizontal gutter is owned by `SolutionsPage`; this component sets none.
+ * standard variant inherits its gutter from `SolutionsPage`; the home variant
+ * owns the exact shared homepage cap and gutter because enterprise renders it
+ * outside the solutions content wrapper.
*/
interface SolutionsHeroProps {
hero: SolutionsHeroConfig
+ /** Header stack alignment. Defaults to the original left-aligned layout. */
+ align?: 'left' | 'center'
+ /** Visual treatment for the top hero. Defaults to the original solutions layout. */
+ variant?: 'solutions' | 'home'
}
-export function SolutionsHero({ hero }: SolutionsHeroProps) {
+export function SolutionsHero({ hero, align = 'left', variant = 'solutions' }: SolutionsHeroProps) {
+ const centered = align === 'center'
+ const homeVariant = variant === 'home'
+
+ const eyebrow = hero.eyebrow ? {hero.eyebrow} : null
+
+ if (homeVariant) {
+ return (
+
+ {hero.summary}
+
+
+
+
+ {hero.visual}
+
+
+ )
+ }
+
return (
{hero.summary}
-
+
+ {eyebrow}
+
-
+
{hero.description}
-
+
+
+
{hero.visual}
diff --git a/apps/sim/app/(landing)/components/solutions-page/constants.ts b/apps/sim/app/(landing)/components/solutions-page/constants.ts
index 595ebe8811e..a6baf4c1e9f 100644
--- a/apps/sim/app/(landing)/components/solutions-page/constants.ts
+++ b/apps/sim/app/(landing)/components/solutions-page/constants.ts
@@ -1,13 +1,19 @@
+import {
+ LANDING_GUTTER,
+ LANDING_HERO_TOP_PADDING,
+ LANDING_SECTION_RHYTHM,
+} from '@/app/(landing)/components/landing-layout'
+
/**
* Solutions-layout spacing - the single source of truth for every gutter, gap,
* inset, and reserved dimension used across the solutions-page components.
*
- * The padding fortress lives here. No solutions component accepts a `className`,
- * `style`, or any layout-override prop, and none hard-codes a spacing value
- * inline. Every measurement a reviewer might want to audit - the horizontal
- * gutter, the inter-section rhythm, the card-grid gaps, the card stack, and the
- * fixed visual-slot dimensions - is named in this one file. To change spacing
- * you edit a constant here; a consumer page literally cannot reach it.
+ * The padding fortress lives here. No solutions component accepts a `className`
+ * or `style`, and none hard-codes a spacing value inline. Every measurement a
+ * reviewer might want to audit - the horizontal gutter, the inter-section rhythm,
+ * the card-grid gaps, the card stack, and the fixed visual-slot dimensions - is
+ * named in this one file. To change spacing you edit a constant here; consumer
+ * pages only choose from controlled component variants.
*
* All values are Tailwind class fragments (not raw numbers) so they compose
* directly into `className` strings inside the components and stay legible.
@@ -19,7 +25,7 @@ export const SOLUTIONS_SPACING = {
* solutions content starts on the wordmark's vertical line at every
* breakpoint. Sections and cards never set their own gutter.
*/
- gutter: 'px-20 max-lg:px-8 max-sm:px-5',
+ gutter: LANDING_GUTTER,
/**
* Inter-section vertical rhythm - the gap of the `
` flex column that
* `SolutionsPage` owns. Sections carry no vertical margin/padding of their own,
@@ -27,9 +33,9 @@ export const SOLUTIONS_SPACING = {
* card row. Tightens on smaller screens in lockstep with the landing ``
* (`gap-[120px] max-lg:gap-[88px] max-sm:gap-16`).
*/
- sectionRhythm: 'gap-[120px] max-lg:gap-[88px] max-sm:gap-16',
- /** Hero text top padding, matching the landing hero (`pt-[112px] max-sm:pt-12`). */
- heroTopPadding: 'pt-[112px] max-sm:pt-12',
+ sectionRhythm: LANDING_SECTION_RHYTHM,
+ /** Hero text top padding, matching the landing hero at every breakpoint. */
+ heroTopPadding: LANDING_HERO_TOP_PADDING,
/** Vertical stack gap inside the hero header column (headline → description → CTA). */
heroStack: 'gap-[22px]',
/** Gap between the hero header column and the full-width hero visual beneath it. */
@@ -38,12 +44,37 @@ export const SOLUTIONS_SPACING = {
cardRowHeaderToGrid: 'gap-12',
/** Vertical stack gap inside a card row's header (title → subtitle → CTA). */
cardRowHeaderStack: 'gap-5',
+ /**
+ * Extra top separation for the header CTA over the stack gap. Title and
+ * subtitle are one copy group and stay tight; the CTA is a separate action
+ * group, so its subtitle→CTA gap lands at 2× the title→subtitle gap
+ * (standard: 20px + 20px = 40px; feature: 12px + 12px = 24px).
+ */
+ cardRowHeaderCtaGap: 'mt-5',
+ cardRowHeaderCtaGapFeature: 'mt-3',
/** Gap between cards within a card-row grid (both axes). */
cardGridGap: 'gap-8',
/** Minimum gap between a card's text block and its visual panel. */
cardTextToVisual: 'gap-5',
/** Vertical stack gap inside a card's text block (title → description). */
cardTextStack: 'gap-2',
+ /** Inner padding for feature tiles where copy and the visual slot share one frame. */
+ cardFeatureTilePadding: 'p-8 max-lg:p-6',
+} as const
+
+/**
+ * Readable text measures for the recurring solutions-page copy surfaces.
+ * Paragraph width is expressed in characters so line length tracks type size
+ * instead of the viewport. `min-w-0` keeps flex items wrapping cleanly on narrow
+ * screens, and `w-full` gives centered copy a stable measure.
+ */
+export const SOLUTIONS_TEXT_MEASURE = {
+ /** Hero support copy: broad enough for the primary value prop, still under long-form prose width. */
+ heroDescription: 'w-full min-w-0 max-w-[58ch]',
+ /** Section subtitles: slightly tighter than hero copy so centered headers feel intentional. */
+ rowSubtitle: 'w-full min-w-0 max-w-[52ch]',
+ /** Card descriptions: short scan lines inside three-up card grids. */
+ cardDescription: 'min-w-0 max-w-[38ch]',
} as const
/**
@@ -57,4 +88,26 @@ export const SOLUTIONS_VISUAL = {
heroAspect: 'aspect-[16/9]',
/** Fixed height of a card's visual panel - uniform across every card. */
cardHeight: 'h-[240px]',
+ /** Minimum height for framed feature tiles with copy and future UI in one surface. */
+ featureTileMinHeight: 'min-h-[440px] max-lg:min-h-[400px] max-sm:min-h-[380px]',
+} as const
+
+/**
+ * Feature-tile surface + copy colors. Pages pick a tone per card via
+ * {@link SolutionsCardConfig.featureTileTone}; the component maps it here so
+ * each tile can diverge without shared hard-coded classes.
+ */
+export const SOLUTIONS_FEATURE_TILE_TONE = {
+ light: {
+ surface: 'bg-[var(--surface-3)]',
+ title: 'text-[var(--text-primary)]',
+ description: 'text-[var(--text-muted)]',
+ },
+ dark: {
+ surface: 'bg-[var(--text-secondary)]',
+ title: 'text-[var(--text-inverse)]',
+ description: 'text-[var(--surface-3)]',
+ /** Softer body copy on dark tiles — `#E6E6E6` via `--surface-6` (`#E5E5E5`). */
+ descriptionSoft: 'text-[var(--surface-6)]',
+ },
} as const
diff --git a/apps/sim/app/(landing)/components/solutions-page/types.ts b/apps/sim/app/(landing)/components/solutions-page/types.ts
index 0f5eecae736..e1379dc3f28 100644
--- a/apps/sim/app/(landing)/components/solutions-page/types.ts
+++ b/apps/sim/app/(landing)/components/solutions-page/types.ts
@@ -18,8 +18,10 @@ export interface SolutionsPillCta {
href: string
}
-/** The solutions hero - header copy, the shared CTA, and a full-width visual. */
+/** The solutions hero - optional tag, header copy, the shared CTA, and a full-width visual. */
export interface SolutionsHeroConfig {
+ /** Optional mono chip shown above the page's single ``. */
+ eyebrow?: string
/**
* The page's single ``. Per the constitution it should name the module and
* "Sim"/"AI workspace" (e.g. "Workflows - the visual builder in Sim, the AI workspace").
@@ -56,8 +58,22 @@ export interface SolutionsCardConfig {
* owns the spacing around both the text and this frame.
*/
visual: ReactNode
+ /**
+ * Feature-tile surface tone - only read when the row renders
+ * `cardVariant='featureTile'`. Defaults to `'light'` so tiles can mix light
+ * and dark backgrounds within the same row.
+ */
+ featureTileTone?: SolutionsFeatureTileTone
+ /**
+ * Optional description color on feature tiles. `'soft'` is for lighter body
+ * copy on dark surfaces without changing the row's default tone map.
+ */
+ featureTileDescriptionTone?: 'soft'
}
+/** Controlled surface tones for {@link SolutionsCardConfig.featureTileTone}. */
+export type SolutionsFeatureTileTone = 'light' | 'dark'
+
/**
* A card row - the core repeating unit. A header (title + subtitle + CTA) above a
* grid of 3 or 4 cards. The grid column count is derived from `cards.length`, so
diff --git a/apps/sim/app/(landing)/enterprise/components/enterprise-platform-loop/enterprise-home-stage.tsx b/apps/sim/app/(landing)/enterprise/components/enterprise-platform-loop/enterprise-home-stage.tsx
new file mode 100644
index 00000000000..76445bb29f8
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/enterprise-platform-loop/enterprise-home-stage.tsx
@@ -0,0 +1,292 @@
+'use client'
+
+import { useEffect, useState } from 'react'
+import { ChevronDown, cn } from '@sim/emcn'
+import {
+ ArrowRight,
+ ArrowUp,
+ ClipboardList,
+ Files,
+ Mic,
+ Paperclip,
+ Plus,
+ ShieldCheck,
+ Shuffle,
+ Slash,
+ Table,
+} from '@sim/emcn/icons'
+import { ThinkingLoader } from '@/components/ui'
+import {
+ COMPOSER_PLACEHOLDER,
+ ENTERPRISE_GREETING,
+ ENTERPRISE_PROMPT,
+ ENTERPRISE_REPLY,
+ type EnterpriseLoopPhase,
+ PROMPT_CHAR_MS,
+ REPLY_WORD_MS,
+ SUGGESTED_ACTIONS,
+} from '@/app/(landing)/enterprise/components/enterprise-platform-loop/stage-data'
+
+const REPLY_WORDS = ENTERPRISE_REPLY.split(' ')
+
+/** Greyscale leading icons for the suggested-action rows, in row order. */
+const ACTION_ICONS = [Table, ShieldCheck, ClipboardList, Files] as const
+
+const Caret = () => (
+
+)
+
+interface ComposerProps {
+ /** Rendered in the text region (placeholder span or typed prompt). */
+ children: React.ReactNode
+ /** Fills the send disc with the active ink once the prompt has text. */
+ active: boolean
+}
+
+/**
+ * The Mothership composer chrome - white rounded field, text region on top,
+ * icon rail beneath (add / attach / skills left, mic + send disc right) -
+ * matching the homepage loop's composer and the real `UserInput`.
+ */
+function Composer({ children, active }: ComposerProps) {
+ return (
+
+
+ {children}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+interface EnterpriseHomeStageProps {
+ /** Current beat, driven by the parent {@link EnterprisePlatformLoop} clock. */
+ phase: EnterpriseLoopPhase
+ /** True during the brief fade-out before the cycle restarts. */
+ fading: boolean
+}
+
+/**
+ * The main pane of the enterprise loop - the real workspace's NEW-CHAT home
+ * view, replayed: the centered greeting, the composer (placeholder, then the
+ * enterprise prompt typing out, then the send disc arming), and the
+ * "Suggested actions" rows beneath. On `dispatch` the home layer exits first
+ * and the conversation layer (the sent prompt as a user bubble over the goo
+ * {@link ThinkingLoader}, with the composer docked at the bottom) enters after
+ * a beat; the loader thinks while the parent's stage pane builds the workflow,
+ * then the reply streams in word by word on `reply`.
+ *
+ * Purely presentational; the clock lives in the parent so the chat and the
+ * workflow stage animate off one timeline. Both typewriters derive from
+ * ELAPSED time so throttled background tabs catch up instead of stalling
+ * mid-sentence.
+ */
+export function EnterpriseHomeStage({ phase, fading }: EnterpriseHomeStageProps) {
+ const isTyping = phase === 'typing'
+ const isReply = phase === 'reply'
+ const inConversation = phase === 'dispatch' || isReply
+ const promptDone = phase === 'typed' || inConversation
+ const [typedChars, setTypedChars] = useState(0)
+ const [revealedWords, setRevealedWords] = useState(0)
+
+ useEffect(() => {
+ if (!isTyping) {
+ setTypedChars(0)
+ return
+ }
+
+ const media = window.matchMedia('(prefers-reduced-motion: reduce)')
+ let interval: ReturnType | null = null
+
+ const type = () => {
+ const startedAt = performance.now()
+ interval = setInterval(() => {
+ const elapsed = performance.now() - startedAt
+ const n = Math.min(Math.floor(elapsed / PROMPT_CHAR_MS) + 1, ENTERPRISE_PROMPT.length)
+ setTypedChars(n)
+ if (n >= ENTERPRISE_PROMPT.length && interval) clearInterval(interval)
+ }, PROMPT_CHAR_MS)
+ }
+
+ const syncMotionPreference = () => {
+ if (interval) clearInterval(interval)
+ if (media.matches) {
+ setTypedChars(ENTERPRISE_PROMPT.length)
+ return
+ }
+ type()
+ }
+
+ syncMotionPreference()
+ media.addEventListener('change', syncMotionPreference)
+ return () => {
+ media.removeEventListener('change', syncMotionPreference)
+ if (interval) clearInterval(interval)
+ }
+ }, [isTyping])
+
+ // Stream the reply word by word while the phase holds on 'reply'; any other
+ // phase (the next cycle's reset) rewinds it for the following pass.
+ useEffect(() => {
+ if (!isReply) {
+ setRevealedWords(0)
+ return
+ }
+
+ const media = window.matchMedia('(prefers-reduced-motion: reduce)')
+ let interval: ReturnType | null = null
+
+ const stream = () => {
+ const startedAt = performance.now()
+ interval = setInterval(() => {
+ const elapsed = performance.now() - startedAt
+ const n = Math.min(Math.floor(elapsed / REPLY_WORD_MS) + 1, REPLY_WORDS.length)
+ setRevealedWords(n)
+ if (n >= REPLY_WORDS.length && interval) clearInterval(interval)
+ }, REPLY_WORD_MS)
+ }
+
+ const syncMotionPreference = () => {
+ if (interval) clearInterval(interval)
+ if (media.matches) {
+ setRevealedWords(REPLY_WORDS.length)
+ return
+ }
+ stream()
+ }
+
+ syncMotionPreference()
+ media.addEventListener('change', syncMotionPreference)
+ return () => {
+ media.removeEventListener('change', syncMotionPreference)
+ if (interval) clearInterval(interval)
+ }
+ }, [isReply])
+
+ const visiblePrompt = promptDone ? ENTERPRISE_PROMPT : ENTERPRISE_PROMPT.slice(0, typedChars)
+ const hasText = visiblePrompt.length > 0
+
+ return (
+
+ {/* Home layer - greeting, composer, suggested actions. Exits FIRST on
+ dispatch (no delay) so the swap never reads as two stacked layers. */}
+
+
{ENTERPRISE_GREETING}
+
+
+
+ {hasText ? (
+ <>
+ {visiblePrompt}
+ {isTyping && }
+ >
+ ) : (
+ {COMPOSER_PLACEHOLDER}
+ )}
+
+
+
+
+
+ Suggested actions
+
+
+
+ Shuffle
+
+
+
+
+ {SUGGESTED_ACTIONS.map((action, i) => {
+ const Icon = ACTION_ICONS[i]
+ return (
+
0 && 'border-t'
+ )}
+ >
+
+
+ {action}
+
+
+
+ )
+ })}
+
+
+
+
+
+ {/* Conversation layer - the sent exchange. Enters AFTER the home layer's
+ 200ms exit so the handoff is a choreographed swap, not a crossfade.
+ The loader thinks through the workflow build, then the reply streams. */}
+
+
+
+ {ENTERPRISE_PROMPT}
+
+ {phase === 'dispatch' && (
+
+ )}
+
+ {REPLY_WORDS.slice(0, revealedWords).join(' ')}
+
+
+
+
+ Send message to Sim
+
+
+
+
+ )
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/enterprise-platform-loop/enterprise-platform-loop.tsx b/apps/sim/app/(landing)/enterprise/components/enterprise-platform-loop/enterprise-platform-loop.tsx
new file mode 100644
index 00000000000..3a34086dba2
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/enterprise-platform-loop/enterprise-platform-loop.tsx
@@ -0,0 +1,169 @@
+'use client'
+
+import { useEffect, useLayoutEffect, useRef, useState } from 'react'
+import { cn } from '@sim/emcn'
+import { HeroWorkflowStage } from '@/app/(landing)/components/hero/components/hero-platform-loop/hero-workflow-stage'
+import { EnterpriseHomeStage } from '@/app/(landing)/enterprise/components/enterprise-platform-loop/enterprise-home-stage'
+import { EnterpriseSidebar } from '@/app/(landing)/enterprise/components/enterprise-platform-loop/enterprise-sidebar'
+import {
+ BUILD_STEP_MS,
+ ENTERPRISE_STAGE_BLOCKS,
+ ENTERPRISE_STAGE_CANVAS,
+ ENTERPRISE_STAGE_EDGES,
+ type EnterpriseLoopPhase,
+ LOOP_TIMELINE,
+ RESET_FADE_MS,
+} from '@/app/(landing)/enterprise/components/enterprise-platform-loop/stage-data'
+
+/**
+ * The window interior's design space, matching the homepage loop's capture
+ * geometry (the 2560x1470 shot is a 1280x735 CSS layout shown in the 1080x620
+ * window, so the app's native type reads at the same ~84.4% "mini app" scale):
+ * the sidebar column is 249px, and the workspace container is inset 7-8px.
+ */
+const DESIGN = { width: 1280, height: 735 } as const
+
+/**
+ * The enterprise hero's platform loop - a sibling of the homepage
+ * `HeroPlatformLoop` that shares its architecture (fixed design-space layer
+ * scaled to the window via ResizeObserver + `transform: scale`, a parent-owned
+ * timeline clock driving presentational stages, reduced-motion showing a
+ * static finished frame) but diverges in content: where the homepage overlays
+ * a live chat over a baked screenshot, this variant renders the WHOLE interior
+ * live - the {@link EnterpriseSidebar} (a filled-out Brightwave workspace) and
+ * the {@link EnterpriseHomeStage} (the real new-chat home view, replaying an
+ * enterprise prompt) - because its sidebar content differs from the shot's.
+ *
+ * Timeline (see `stage-data.ts` - later stages append beats there): idle
+ * new-chat view → prompt types out → send arms → dispatch (user bubble +
+ * thinking, full-width) → the stage pane slides in from the right (the real
+ * `MothershipView` `w-0 ↔ w-1/2` width transition) → the invoice workflow
+ * assembles block by block (the shared {@link HeroWorkflowStage}, staged with
+ * the enterprise flow) → the reply streams in → hold → fade → restart.
+ *
+ * Everything is `pointer-events-none` decorative, matching the hero's
+ * `aria-hidden` frame. Under `prefers-reduced-motion` the loop never starts:
+ * the finished exchange, open stage, and fully-built workflow render
+ * statically.
+ */
+export function EnterprisePlatformLoop() {
+ const regionRef = useRef(null)
+ const [phase, setPhase] = useState('idle')
+ const [stageOpen, setStageOpen] = useState(false)
+ const [builtCount, setBuiltCount] = useState(0)
+ const [fading, setFading] = useState(false)
+ const [cycleId, setCycleId] = useState(0)
+ const [scale, setScale] = useState(1)
+
+ // Track the rendered region width and scale the design-space layer to fill
+ // it, keeping the live layer's proportions locked to the window's.
+ useLayoutEffect(() => {
+ const el = regionRef.current
+ if (!el) return
+ const measure = () => {
+ const w = el.getBoundingClientRect().width
+ if (w > 40) setScale(w / DESIGN.width)
+ }
+ measure()
+ const ro = new ResizeObserver(measure)
+ ro.observe(el)
+ return () => ro.disconnect()
+ }, [])
+
+ useEffect(() => {
+ const media = window.matchMedia('(prefers-reduced-motion: reduce)')
+ let timers: ReturnType[] = []
+
+ const clearScheduled = () => {
+ timers.forEach(clearTimeout)
+ timers = []
+ }
+
+ const showFinished = () => {
+ clearScheduled()
+ setFading(false)
+ setPhase('reply')
+ setStageOpen(true)
+ setBuiltCount(ENTERPRISE_STAGE_BLOCKS.length)
+ }
+
+ const runCycle = () => {
+ setFading(false)
+ setPhase('idle')
+ setStageOpen(false)
+ setBuiltCount(0)
+ setCycleId((c) => c + 1)
+ timers = [
+ setTimeout(() => setPhase('typing'), LOOP_TIMELINE.typing),
+ setTimeout(() => setPhase('typed'), LOOP_TIMELINE.typed),
+ setTimeout(() => setPhase('dispatch'), LOOP_TIMELINE.dispatch),
+ setTimeout(() => setStageOpen(true), LOOP_TIMELINE.stageOpen),
+ ...ENTERPRISE_STAGE_BLOCKS.map((_, i) =>
+ setTimeout(() => setBuiltCount(i + 1), LOOP_TIMELINE.buildStart + i * BUILD_STEP_MS)
+ ),
+ setTimeout(() => setPhase('reply'), LOOP_TIMELINE.reply),
+ setTimeout(() => setFading(true), LOOP_TIMELINE.total - RESET_FADE_MS),
+ setTimeout(runCycle, LOOP_TIMELINE.total),
+ ]
+ }
+
+ const syncMotionPreference = () => {
+ clearScheduled()
+ if (media.matches) {
+ showFinished()
+ return
+ }
+ runCycle()
+ }
+
+ syncMotionPreference()
+ media.addEventListener('change', syncMotionPreference)
+ return () => {
+ media.removeEventListener('change', syncMotionPreference)
+ clearScheduled()
+ }
+ }, [])
+
+ return (
+
+ )
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/enterprise-platform-loop/enterprise-sidebar.tsx b/apps/sim/app/(landing)/enterprise/components/enterprise-platform-loop/enterprise-sidebar.tsx
new file mode 100644
index 00000000000..ba70b41c8ab
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/enterprise-platform-loop/enterprise-sidebar.tsx
@@ -0,0 +1,147 @@
+import { ChevronDown, cn, Home, Library } from '@sim/emcn'
+import {
+ Calendar,
+ Database,
+ File,
+ HelpCircle,
+ Integration,
+ MoreHorizontal,
+ PanelLeft,
+ Plus,
+ Search,
+ Settings,
+ Table,
+} from '@sim/emcn/icons'
+import Image from 'next/image'
+import {
+ SIDEBAR_CHATS,
+ SIDEBAR_WORKFLOWS,
+} from '@/app/(landing)/enterprise/components/enterprise-platform-loop/stage-data'
+
+const WORKSPACE_NAV = [
+ { label: 'Tables', icon: Table },
+ { label: 'Files', icon: File },
+ { label: 'Knowledge base', icon: Database },
+ { label: 'Scheduled tasks', icon: Calendar },
+ { label: 'Logs', icon: Library },
+] as const
+
+interface IconRowProps {
+ icon: React.ComponentType<{ className?: string }>
+ label: string
+ active?: boolean
+}
+
+/** A sidebar nav row with a leading icon, like the real workspace sidebar. */
+function IconRow({ icon: Icon, label, active = false }: IconRowProps) {
+ return (
+
+
+ {label}
+
+ )
+}
+
+/** A bare text row - the real sidebar's chat and workflow entries. */
+function TextRow({ label }: { label: string }) {
+ return (
+
+ {label}
+
+ )
+}
+
+/** Muted section heading (Chats / Workspace / Workflows). */
+function SectionLabel({ label, actions }: { label: string; actions?: boolean }) {
+ return (
+
+
{label}
+ {actions && (
+
+
+
+
+ )}
+
+ )
+}
+
+/**
+ * The Brightwave workspace sidebar, rendered live (the homepage loop keeps its
+ * baked-screenshot sidebar; the enterprise loop draws its own so the content
+ * can read like a large tenured deployment): the workspace header, New chat /
+ * Search / Integrations, a filled-out Chats history, the Workspace nav, a full
+ * Workflows section, and the Help / Settings footer. Purely decorative -
+ * hover/click behavior is owned by the parent's `pointer-events-none` frame.
+ */
+export function EnterpriseSidebar() {
+ return (
+
+ {/* Workspace header, matching the real product's WorkspaceHeader chip
+ (borderless `chipVariants()` geometry: h-[30px] rounded-lg px-2 with
+ mx-0.5, 16px logo, text-sm name, 6x10 chevron) and therefore the
+ homepage's baked sidebar pixels - logo + name + chevron as a bare
+ row, panel toggle right-aligned outside it. */}
+
+
+ {/* The exact Brightwave mark the homepage capture seeds
+ (`readme-tour-capture` sets `logoUrl: '/landing/rivian-logo.svg'`),
+ so both platform previews show the same company logo. */}
+
+ Brightwave
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {SIDEBAR_CHATS.map((chat) => (
+
+ ))}
+
+
+
+
+
+
+ {WORKSPACE_NAV.map((item) => (
+
+ ))}
+
+
+
+
+
+
+ {SIDEBAR_WORKFLOWS.map((workflow) => (
+
+ ))}
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/enterprise-platform-loop/index.ts b/apps/sim/app/(landing)/enterprise/components/enterprise-platform-loop/index.ts
new file mode 100644
index 00000000000..075f40c14c3
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/enterprise-platform-loop/index.ts
@@ -0,0 +1,3 @@
+export { EnterpriseHomeStage } from '@/app/(landing)/enterprise/components/enterprise-platform-loop/enterprise-home-stage'
+export { EnterprisePlatformLoop } from '@/app/(landing)/enterprise/components/enterprise-platform-loop/enterprise-platform-loop'
+export { EnterpriseSidebar } from '@/app/(landing)/enterprise/components/enterprise-platform-loop/enterprise-sidebar'
diff --git a/apps/sim/app/(landing)/enterprise/components/enterprise-platform-loop/stage-data.ts b/apps/sim/app/(landing)/enterprise/components/enterprise-platform-loop/stage-data.ts
new file mode 100644
index 00000000000..497881d33e5
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/enterprise-platform-loop/stage-data.ts
@@ -0,0 +1,180 @@
+import { AgentIcon, ConditionalIcon, MailIcon, StartIcon, TableIcon } from '@/components/icons'
+import type { BlockDef } from '@/app/(landing)/components/hero/components/hero-visual/workflow-data'
+
+/**
+ * Content + timing config for the enterprise hero's platform loop. Kept in one
+ * place (and phase starts derived, not hardcoded) so later stages - tables,
+ * files, knowledge base, logs walkthroughs - extend the timeline by appending
+ * beats here rather than reworking the clock.
+ */
+
+/** The new-chat greeting, personalized like the real workspace Home. */
+export const ENTERPRISE_GREETING = 'What should we get done, Morgan?'
+
+/** Placeholder shown in the composer before the prompt types out. */
+export const COMPOSER_PLACEHOLDER = 'Ask Sim to automate a process.'
+
+/**
+ * The prompt the loop types - a large company's multi-system operational
+ * workflow (AP + NetSuite + finance review + audit trail), concise enough to
+ * type on screen.
+ */
+export const ENTERPRISE_PROMPT =
+ 'When a vendor invoice lands in AP, match it against the PO in NetSuite, flag exceptions for finance review, and log the approval trail.'
+
+/** Typewriter cadence for the composer prompt. */
+export const PROMPT_CHAR_MS = 28
+
+/** Sim's reply, streamed word by word once the workflow finishes building. */
+export const ENTERPRISE_REPLY =
+ "On it. I'll match each AP invoice to its PO in NetSuite, route exceptions to finance review, and log the full approval trail."
+
+/** Word-reveal cadence for the streamed reply. */
+export const REPLY_WORD_MS = 55
+
+/**
+ * Recent chats - enterprise-flavored, so Brightwave reads long-tenured. Four
+ * entries: together with the five workflows this fills the sidebar's 735px
+ * design height exactly, without clipping the Workflows section.
+ */
+export const SIDEBAR_CHATS = [
+ 'Vendor invoice exceptions',
+ 'Q3 access review',
+ 'NetSuite sync errors',
+ 'Supplier onboarding docs',
+] as const
+
+/** Deployed workflows - a fuller section than the homepage's three. */
+export const SIDEBAR_WORKFLOWS = [
+ 'Invoice exception routing',
+ 'Employee onboarding',
+ 'Vendor risk scoring',
+ 'IT access provisioning',
+ 'Weekly compliance report',
+] as const
+
+/** Suggested actions under the composer, mirroring the real Home rows. */
+export const SUGGESTED_ACTIONS = [
+ 'Reconcile vendor invoices in NetSuite',
+ 'Triage pending IT access requests',
+ 'Summarize open compliance exceptions',
+ 'Draft the weekly ops readiness report',
+] as const
+
+/**
+ * The workflow the chat "builds" on the stage pane - the invoice-matching flow
+ * the prompt describes, distilled to what reads at mini-app scale: Start feeds
+ * the NetSuite PO match, exceptions are flagged, and the flow fans out to
+ * finance review and the audit log. Same geometry conventions as the homepage
+ * stage (250px blocks, vertical spine at x=155, terminals fanned at y=580);
+ * tiles use the platform's grey text ramp - color is reserved for real
+ * third-party marks, and none of these carry one.
+ *
+ * Ordered by build sequence; an edge draws once both endpoints are on canvas.
+ */
+export const ENTERPRISE_STAGE_BLOCKS: BlockDef[] = [
+ {
+ id: 'start',
+ name: 'Start',
+ icon: StartIcon,
+ bgColor: 'var(--text-muted)',
+ isTrigger: true,
+ rows: [{ title: 'Inputs', value: '-' }],
+ x: 155,
+ y: 12,
+ },
+ {
+ id: 'match',
+ name: 'Match PO',
+ icon: AgentIcon,
+ bgColor: 'var(--text-primary)',
+ rows: [
+ { title: 'Messages', value: '-' },
+ { title: 'Model', value: '-' },
+ ],
+ x: 155,
+ y: 172,
+ },
+ {
+ id: 'exceptions',
+ name: 'Flag exceptions',
+ icon: ConditionalIcon,
+ bgColor: 'var(--text-secondary)',
+ rows: [{ title: 'Conditions', value: '-' }],
+ x: 155,
+ y: 372,
+ },
+ {
+ id: 'review',
+ name: 'Finance review',
+ icon: MailIcon,
+ bgColor: 'var(--text-body)',
+ isTerminal: true,
+ rows: [
+ { title: 'To', value: '-' },
+ { title: 'Subject', value: '-' },
+ ],
+ x: 0,
+ y: 560,
+ },
+ {
+ id: 'audit',
+ name: 'Audit log',
+ icon: TableIcon,
+ bgColor: 'var(--text-muted)',
+ isTerminal: true,
+ rows: [
+ { title: 'Table', value: '-' },
+ { title: 'Operation', value: '-' },
+ ],
+ x: 310,
+ y: 560,
+ },
+]
+
+/** Source → target pairs, drawn in order as their endpoints land on canvas. */
+export const ENTERPRISE_STAGE_EDGES: ReadonlyArray = [
+ ['start', 'match'],
+ ['match', 'exceptions'],
+ ['exceptions', 'review'],
+ ['exceptions', 'audit'],
+]
+
+/** Design-space bounding box of the layout above. */
+export const ENTERPRISE_STAGE_CANVAS = { width: 560, height: 680 } as const
+
+/** Where the main pane is within one loop pass. */
+export type EnterpriseLoopPhase = 'idle' | 'typing' | 'typed' | 'dispatch' | 'reply'
+
+/** The idle new-chat view holds this long before typing starts. */
+const IDLE_HOLD_MS = 1400
+/** Rest on the fully-typed prompt before "send". */
+const TYPED_HOLD_MS = 700
+/** Thinking runs alone this long before the stage pane slides open. */
+const STAGE_OPEN_AFTER_MS = 900
+/** First block lands this long after the pane opens. */
+const BUILD_START_AFTER_MS = 500
+/** Block N (build order) pops in at buildStart + N * BUILD_STEP_MS. */
+export const BUILD_STEP_MS = 620
+/** The reply starts streaming this long after the last block lands. */
+const REPLY_AFTER_MS = 500
+/** The finished scene (reply + built canvas) holds this long. */
+const REPLY_HOLD_MS = 4800
+/** Fade-out length before the cycle restarts. */
+export const RESET_FADE_MS = 300
+
+/**
+ * Derived phase starts. Later stages (tables, files, knowledge base, logs)
+ * slot in after `reply` by extending {@link EnterpriseLoopPhase} and appending
+ * starts here.
+ */
+export const LOOP_TIMELINE = (() => {
+ const typing = IDLE_HOLD_MS
+ const typed = typing + ENTERPRISE_PROMPT.length * PROMPT_CHAR_MS
+ const dispatch = typed + TYPED_HOLD_MS
+ const stageOpen = dispatch + STAGE_OPEN_AFTER_MS
+ const buildStart = stageOpen + BUILD_START_AFTER_MS
+ const reply = buildStart + (ENTERPRISE_STAGE_BLOCKS.length - 1) * BUILD_STEP_MS + REPLY_AFTER_MS
+ const total = reply + REPLY_HOLD_MS
+ return { typing, typed, dispatch, stageOpen, buildStart, reply, total } as const
+})()
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/access-control-graphic.module.css b/apps/sim/app/(landing)/enterprise/components/feature-graphics/access-control-graphic.module.css
new file mode 100644
index 00000000000..a40f3e3e89f
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/access-control-graphic.module.css
@@ -0,0 +1,136 @@
+/**
+ * The AccessControlGraphic's motion, all on one shared 6s cycle: the six
+ * connector edges draw in with a dash-normalized stroke sweep (every path
+ * carries `pathLength=1`, so a dasharray/dashoffset of 1 spans the whole
+ * curve regardless of geometry — the deploy tile's pattern), staggered so
+ * the grants wire up one after another, quiet edges first and the
+ * emphasized Engineering → Deploy edge last and slowest. Each edge's
+ * stagger is baked into its own keyframe percentages rather than an
+ * `animation-delay`, so every edge resets on the same loop boundary and
+ * the cycle reads draw-once-then-hold. As the emphasized edge lands, the
+ * grant node blooms the family's soft ring pulse. Under
+ * prefers-reduced-motion everything is static: edges fully drawn, no
+ * pulse.
+ */
+
+.edgeDraw {
+ stroke-dasharray: 1;
+ stroke-dashoffset: 1;
+}
+
+.edgeDraw0 {
+ animation: access-edge-draw-0 6s ease-in-out infinite;
+}
+
+.edgeDraw1 {
+ animation: access-edge-draw-1 6s ease-in-out infinite;
+}
+
+.edgeDraw2 {
+ animation: access-edge-draw-2 6s ease-in-out infinite;
+}
+
+.edgeDraw3 {
+ animation: access-edge-draw-3 6s ease-in-out infinite;
+}
+
+.edgeDraw4 {
+ animation: access-edge-draw-4 6s ease-in-out infinite;
+}
+
+.edgeDrawEmphasized {
+ animation: access-edge-draw-emphasized 6s ease-in-out infinite;
+}
+
+@keyframes access-edge-draw-0 {
+ 0% {
+ stroke-dashoffset: 1;
+ }
+ 14%,
+ 100% {
+ stroke-dashoffset: 0;
+ }
+}
+
+@keyframes access-edge-draw-1 {
+ 0%,
+ 7% {
+ stroke-dashoffset: 1;
+ }
+ 21%,
+ 100% {
+ stroke-dashoffset: 0;
+ }
+}
+
+@keyframes access-edge-draw-2 {
+ 0%,
+ 14% {
+ stroke-dashoffset: 1;
+ }
+ 28%,
+ 100% {
+ stroke-dashoffset: 0;
+ }
+}
+
+@keyframes access-edge-draw-3 {
+ 0%,
+ 21% {
+ stroke-dashoffset: 1;
+ }
+ 35%,
+ 100% {
+ stroke-dashoffset: 0;
+ }
+}
+
+@keyframes access-edge-draw-4 {
+ 0%,
+ 28% {
+ stroke-dashoffset: 1;
+ }
+ 42%,
+ 100% {
+ stroke-dashoffset: 0;
+ }
+}
+
+@keyframes access-edge-draw-emphasized {
+ 0%,
+ 38% {
+ stroke-dashoffset: 1;
+ }
+ 56%,
+ 100% {
+ stroke-dashoffset: 0;
+ }
+}
+
+.grantPulse {
+ animation: access-grant-pulse 6s ease-out infinite;
+}
+
+@keyframes access-grant-pulse {
+ 0%,
+ 54% {
+ box-shadow: 0 0 0 0 color-mix(in srgb, var(--text-secondary) 35%, transparent);
+ }
+ 72% {
+ box-shadow: 0 0 0 5px color-mix(in srgb, var(--text-secondary) 0%, transparent);
+ }
+ 100% {
+ box-shadow: 0 0 0 0 color-mix(in srgb, var(--text-secondary) 0%, transparent);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .edgeDraw {
+ animation: none;
+ stroke-dashoffset: 0;
+ }
+
+ .grantPulse {
+ animation: none;
+ }
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/access-control-graphic.tsx b/apps/sim/app/(landing)/enterprise/components/feature-graphics/access-control-graphic.tsx
new file mode 100644
index 00000000000..7c328dae692
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/access-control-graphic.tsx
@@ -0,0 +1,195 @@
+import { ChipTag, cn } from '@sim/emcn'
+import Image from 'next/image'
+import styles from '@/app/(landing)/enterprise/components/feature-graphics/access-control-graphic.module.css'
+import { FeatureGraphicShell } from '@/app/(landing)/enterprise/components/feature-graphics/feature-graphic-shell'
+
+/** Fixed pixel canvas the vignette is drawn on, centered inside the shell. */
+const CANVAS = { WIDTH: 320, HEIGHT: 250 } as const
+
+/** Vertical anchors: team output ports on top, chip input ports below. */
+const PORT_Y = { TEAM: 72, CHIP: 208 } as const
+
+const TEAMS = [
+ { avatar: '/landing/team-avatar-1.png', name: 'Engineering', x: 64, leftClass: 'left-[64px]' },
+ { avatar: '/landing/team-avatar-2.png', name: 'Support', x: 160, leftClass: 'left-[160px]' },
+ { avatar: '/landing/team-avatar-3.png', name: 'Ops', x: 256, leftClass: 'left-[256px]' },
+] as const
+
+const CHIPS = [
+ { label: 'Build', x: 80, leftClass: 'left-[80px]' },
+ { label: 'Review', x: 160, leftClass: 'left-[160px]' },
+ { label: 'Deploy', x: 240, leftClass: 'left-[240px]' },
+] as const
+
+interface Edge {
+ /** Team output-port x coordinate. */
+ from: number
+ /** Chip input-port x coordinate. */
+ to: number
+ /** Whether this is the emphasized live grant (stronger ink). */
+ emphasized?: boolean
+}
+
+/**
+ * Team → permission grants: Engineering builds, reviews, and deploys
+ * (Deploy is the live grant); Support builds and reviews; Ops reviews.
+ */
+const EDGES: readonly Edge[] = [
+ { from: 64, to: 80 },
+ { from: 64, to: 160 },
+ { from: 160, to: 80 },
+ { from: 160, to: 160 },
+ { from: 256, to: 160 },
+ { from: 64, to: 240, emphasized: true },
+] as const
+
+/**
+ * Per-index draw classes for the quiet edges — the stagger order is baked
+ * into each class's keyframes so all edges share one 6s loop boundary.
+ * The emphasized edge uses its own dedicated class instead.
+ */
+const EDGE_DRAW_CLASSES = [
+ styles.edgeDraw0,
+ styles.edgeDraw1,
+ styles.edgeDraw2,
+ styles.edgeDraw3,
+ styles.edgeDraw4,
+] as const
+
+/** Builds a node-graph edge: vertical tangents easing into both ports. */
+function edgePath(edge: Edge): string {
+ const { from, to } = edge
+ const bend = 62
+ return `M ${from} ${PORT_Y.TEAM} C ${from} ${PORT_Y.TEAM + bend} ${to} ${PORT_Y.CHIP - bend} ${to} ${PORT_Y.CHIP}`
+}
+
+/**
+ * Workspace access told as a vertical role graph, with no window framing:
+ * three gradient-circle team avatars across the top flow down through
+ * curved node-graph edges — 1px bezier strokes in the deploy tile's faint
+ * guide-line grey, each bending toward and landing on the specific
+ * permission chip that team holds — so who-can-do-what reads as directed
+ * wiring. Scopes narrow across the teams (Engineering builds, reviews, and
+ * deploys; Support builds and reviews; Ops reviews). Engineering's Deploy
+ * grant is the emphasized element: its curve carries the stronger
+ * `--text-secondary` ink into a filled junction node that blooms the row's
+ * shared 6s ring pulse; the other chips sit behind quiet hollow
+ * port nodes and mono chips (fills stepped up to `--surface-6` so the
+ * pills stay legible on the grey ground).
+ *
+ * Motion (from `access-control-graphic.module.css`, one shared 6s cycle):
+ * the edges draw in with a dash-normalized stroke sweep (`pathLength=1`,
+ * the deploy tile's pattern), staggered so the grants wire up one after
+ * another — quiet edges first, the emphasized Deploy edge last — then
+ * hold drawn for the rest of the loop, and the grant node's ring pulse
+ * blooms as the emphasized edge lands. Under `prefers-reduced-motion` the
+ * graph renders fully drawn and static.
+ *
+ * The avatar assets are grey radial gradients on a black square, so each
+ * sits in a `rounded-full overflow-hidden` clip with a slight scale-up to
+ * crop the black canvas past the circle's edge.
+ *
+ * The feature tile's visual slot bleeds `2rem` right (`1.5rem` under
+ * `max-lg`) but not left, so this centered vignette adds matching right
+ * padding to land on the tile's visible center instead of the bled slot's
+ * center. The fixed-size canvas is `shrink-0`: if it were allowed to
+ * shrink at narrow tile widths its absolutely-positioned columns (fixed
+ * `left-*` coordinates) would drift right of the box's true midline;
+ * instead it keeps its geometry and overflows both edges equally, so the
+ * Support → Review axis always sits on the tile's center.
+ */
+export function AccessControlGraphic() {
+ return (
+
+
+
+
+ {EDGES.map((edge, index) => (
+
+ ))}
+
+
+ {TEAMS.map((team, index) => (
+
+
+
+
+
+ {team.name}
+
+
+ ))}
+
+ {CHIPS.map((chip) => {
+ const emphasized = EDGES.some((edge) => edge.emphasized && edge.to === chip.x)
+ return (
+
+
+
+ {chip.label}
+
+
+ )
+ })}
+
+
+
+ )
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/audit-trail-graphic.module.css b/apps/sim/app/(landing)/enterprise/components/feature-graphics/audit-trail-graphic.module.css
new file mode 100644
index 00000000000..67a0d36a6ee
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/audit-trail-graphic.module.css
@@ -0,0 +1,47 @@
+/**
+ * The AuditTrailGraphic's motion: the newest ledger entry stamps in once on
+ * load (a short settle from above — an append, never a re-append, since the
+ * record is immutable), and its actor avatar then carries the row's shared
+ * quiet 6s ring-pulse beat (same bloom as the access tile's live grant and
+ * the lifecycle tile's live node) to mark the trail as live. Under
+ * prefers-reduced-motion both are removed and the ledger sits static.
+ */
+
+.stampIn {
+ animation: audit-stamp-in 0.9s cubic-bezier(0.22, 1, 0.36, 1) 0.35s backwards;
+}
+
+@keyframes audit-stamp-in {
+ from {
+ opacity: 0;
+ transform: translateY(-6px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.sealPulse {
+ animation: audit-seal-pulse 6s ease-out infinite;
+}
+
+@keyframes audit-seal-pulse {
+ 0%,
+ 20% {
+ box-shadow: 0 0 0 0 color-mix(in srgb, var(--text-secondary) 35%, transparent);
+ }
+ 36% {
+ box-shadow: 0 0 0 5px color-mix(in srgb, var(--text-secondary) 0%, transparent);
+ }
+ 100% {
+ box-shadow: 0 0 0 0 color-mix(in srgb, var(--text-secondary) 0%, transparent);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .stampIn,
+ .sealPulse {
+ animation: none;
+ }
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/audit-trail-graphic.tsx b/apps/sim/app/(landing)/enterprise/components/feature-graphics/audit-trail-graphic.tsx
new file mode 100644
index 00000000000..10fce784194
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/audit-trail-graphic.tsx
@@ -0,0 +1,165 @@
+import { ChipTag, cn } from '@sim/emcn'
+import Image from 'next/image'
+import styles from '@/app/(landing)/enterprise/components/feature-graphics/audit-trail-graphic.module.css'
+import { FeatureGraphicShell } from '@/app/(landing)/enterprise/components/feature-graphics/feature-graphic-shell'
+
+interface AuditEntry {
+ /** Human-readable action label, matching how the audit UI renders `AuditAction` codes. */
+ action: string
+ /** Attributed actor shown on the detail line. */
+ actor: string
+ /** Resource the action touched. */
+ resource: string
+ /** Relative or absolute timestamp, newest first. */
+ time: string
+ /** Gradient team avatar attributing the entry to its actor. */
+ avatar: string
+}
+
+/**
+ * Ledger records newest-first, drawn from the real `AuditAction` codes in
+ * `packages/audit` (`workflow.deployed`, `permission_group.updated`,
+ * `credential.accessed`, `workflow.created`) rendered as spaced words the
+ * way the workspace audit UI formats them, and threading the running
+ * Support-agent story: Maya ships v3, the Support permission group is
+ * tuned, a Zendesk credential access is recorded, and the trail reaches
+ * back to the workflow's creation.
+ */
+const ENTRIES: readonly AuditEntry[] = [
+ {
+ action: 'Workflow deployed',
+ actor: 'Maya Chen',
+ resource: 'Support agent v3',
+ time: 'Now',
+ avatar: '/landing/team-avatar-1.png',
+ },
+ {
+ action: 'Permission group updated',
+ actor: 'Jordan Lee',
+ resource: 'Support team',
+ time: '2 min ago',
+ avatar: '/landing/team-avatar-2.png',
+ },
+ {
+ action: 'Credential accessed',
+ actor: 'Sam Ortiz',
+ resource: 'Zendesk OAuth',
+ time: '26 min ago',
+ avatar: '/landing/team-avatar-3.png',
+ },
+ {
+ action: 'Workflow created',
+ actor: 'Maya Chen',
+ resource: 'Support agent',
+ time: 'Jun 14',
+ avatar: '/landing/team-avatar-1.png',
+ },
+] as const
+
+/** Per-row ink treatments, quieter with age like the lifecycle timeline. */
+const ROW_TONES = [
+ { action: 'text-[var(--text-primary)]', avatar: 'opacity-100' },
+ { action: 'text-[var(--text-secondary)]', avatar: 'opacity-80' },
+ { action: 'text-[var(--text-muted)]', avatar: 'opacity-60' },
+ { action: 'text-[var(--text-muted)]', avatar: 'opacity-40' },
+] as const
+
+/**
+ * Sim's audit trail told as an append-only ledger rather than a product
+ * window: a frameless, centered vignette (the access tile's composition,
+ * which sits beside it in the row) where each record is a plain row —
+ * gradient actor avatar, the action label in the row's regular sans face
+ * (`font-medium text-small`, the same treatment the standards tile gives
+ * its row titles), an "actor · resource" attribution line, and a
+ * right-aligned timestamp. The newest record is the selected event: it
+ * sits on a solid white card wearing the build tile's window chrome
+ * exactly — `--white` fill, 1px `--border-1` hairline, `rounded-xl`,
+ * `shadow-sm` — while the older records rest directly on the tile,
+ * quietening with age until a mask gradient dissolves the oldest —
+ * implying the trail continues into history. A small "Audit log"
+ * header with an `Append-only` mono ChipTag (fill stepped up to
+ * `--surface-6` so the pill stays legible on the grey ground) names the
+ * surface without window chrome.
+ *
+ * Motion (from `audit-trail-graphic.module.css`): the newest record stamps
+ * in once from above — an append, never re-played, since the record is
+ * immutable — and its avatar then carries the row's shared 6s ring-pulse
+ * beat (matching the access tile's grant node and the lifecycle tile's
+ * live node). Both are removed under `prefers-reduced-motion`.
+ *
+ * The avatar assets are grey radial gradients on a black square, so each
+ * sits in a `rounded-full overflow-hidden` clip with a slight scale-up to
+ * crop the black canvas past the circle's edge.
+ *
+ * The feature tile's visual slot bleeds `2rem` right (`1.5rem` under
+ * `max-lg`) but not left, so this centered vignette adds matching right
+ * padding to land on the tile's visible center instead of the bled slot's
+ * center.
+ */
+export function AuditTrailGraphic() {
+ return (
+
+
+
+
+ Audit log
+
+ Append-only
+
+
+
+
+ {ENTRIES.map((entry, index) => {
+ const tone = ROW_TONES[index]
+ const newest = index === 0
+
+ return (
+
+
+
+
+
+
+ {entry.action}
+
+
+ {entry.actor} · {entry.resource}
+
+
+
+ {entry.time}
+
+
+ )
+ })}
+
+
+
+
+ )
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/build-methods-graphic.tsx b/apps/sim/app/(landing)/enterprise/components/feature-graphics/build-methods-graphic.tsx
new file mode 100644
index 00000000000..d9d6ce9fa17
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/build-methods-graphic.tsx
@@ -0,0 +1,337 @@
+'use client'
+
+import { useEffect, useState } from 'react'
+import { cn } from '@sim/emcn'
+import { ArrowUp, FolderCode, Mic, Paperclip, Plus, Slash } from '@sim/emcn/icons'
+import { ThinkingLoader } from '@/components/ui'
+import { FeatureGraphicShell } from '@/app/(landing)/enterprise/components/feature-graphics/feature-graphic-shell'
+import { FeaturePlatformPanel } from '@/app/(landing)/enterprise/components/feature-graphics/feature-platform-panel'
+
+interface CodeSegment {
+ text: string
+ tone?: 'muted' | 'primary'
+}
+
+/** The `support-agent.ts` contents, split into tone-colored typewriter segments. */
+const CODE_LINES: CodeSegment[][] = [
+ [
+ { text: 'import', tone: 'muted' },
+ { text: ' ' },
+ { text: '{ agent }', tone: 'primary' },
+ { text: ' ' },
+ { text: 'from', tone: 'muted' },
+ { text: ' ' },
+ { text: "'@sim/sdk'", tone: 'primary' },
+ ],
+ [
+ { text: 'const', tone: 'muted' },
+ { text: ' ' },
+ { text: 'supportAgent', tone: 'primary' },
+ { text: ' ' },
+ { text: '= await', tone: 'muted' },
+ { text: ' ' },
+ { text: 'agent', tone: 'primary' },
+ ],
+ [{ text: ' .workflow({' }],
+ [
+ { text: ' ' },
+ { text: 'name:', tone: 'muted' },
+ { text: ' ' },
+ { text: "'Support agent'", tone: 'primary' },
+ { text: ',' },
+ ],
+ [{ text: ' ' }, { text: 'instructions:', tone: 'muted' }],
+ [{ text: ' ' }, { text: "'Answer customer questions'", tone: 'primary' }, { text: ',' }],
+ [
+ { text: ' ' },
+ { text: 'tools:', tone: 'muted' },
+ { text: ' ' },
+ { text: '[zendesk, slack]', tone: 'primary' },
+ { text: ',' },
+ ],
+ [{ text: ' })' }],
+]
+
+const CODE_LINE_LENGTHS = CODE_LINES.map((line) =>
+ line.reduce((total, segment) => total + segment.text.length, 0)
+)
+const CODE_LINE_STARTS = CODE_LINE_LENGTHS.map((_, index) =>
+ CODE_LINE_LENGTHS.slice(0, index).reduce((total, length) => total + length, 0)
+)
+const CODE_TOTAL_CHARS = CODE_LINE_LENGTHS.reduce((total, length) => total + length, 0)
+
+const PROMPT = 'Create a support agent that answers customer questions'
+const REPLY =
+ 'On it — scaffolding a support agent with Zendesk and Slack that answers customer questions.'
+const REPLY_WORDS = REPLY.split(' ')
+
+const CODE_START_MS = 500
+const CODE_CHAR_MS = 24
+const COMPOSER_IN_MS = 5300
+const PROMPT_START_MS = 6000
+const PROMPT_CHAR_MS = 55
+const SEND_MS = 9600
+const CHAT_ENTER_MS = 10100
+const REPLY_MS = 11600
+const REPLY_WORD_MS = 80
+const FADE_MS = 15100
+const LOOP_MS = 15700
+
+type BuildMethodsPhase =
+ | 'idle'
+ | 'code'
+ | 'composer'
+ | 'prompt'
+ | 'exit'
+ | 'chat'
+ | 'reply'
+ | 'fade'
+
+const SEGMENT_TONE_CLASS = {
+ muted: 'text-[var(--text-muted)]',
+ primary: 'text-[var(--text-primary)]',
+} as const
+
+/** Renders one code line clipped to the number of characters typed so far. */
+function renderCodeLine(segments: CodeSegment[], visibleChars: number) {
+ const rendered = []
+ let remaining = visibleChars
+ for (let index = 0; index < segments.length && remaining > 0; index++) {
+ const segment = segments[index]
+ rendered.push(
+
+ {segment.text.slice(0, remaining)}
+
+ )
+ remaining -= segment.text.length
+ }
+ return rendered
+}
+
+/**
+ * Decorative loop for the "Build visually or with code" tile: the
+ * `support-agent.ts` editor types itself out, the platform composer slides up
+ * and receives a typed prompt, and the send beat swaps the editor for a
+ * headerless Mothership chat surface — the same agent built from code or from
+ * a message. All window bodies sit on `--white`, matching the hero surfaces.
+ *
+ * Window handoffs are exit-before-enter orchestration, never crossfades: the
+ * editor fully exits (slides down and fades over 380ms) during the `exit`
+ * phase, the stage holds empty for a beat, and only then does the chat enter
+ * (slide-up fade, 420ms). The composer is a pure transform slide from below
+ * the shell's clipped edge — its opacity never animates, so it never reads as
+ * a translucent layer over the editor. Panel transitions are disabled on the
+ * `idle` reset so state snaps back while the scene-level fade has the tile at
+ * zero opacity. Local timers + CSS transitions only, mirroring the other
+ * landing loops; under `prefers-reduced-motion` it renders the finished
+ * editor + composer as a static frame.
+ */
+export function BuildMethodsGraphic() {
+ const [phase, setPhase] = useState('idle')
+ const [typedCodeChars, setTypedCodeChars] = useState(0)
+ const [typedPromptChars, setTypedPromptChars] = useState(0)
+ const [revealedReplyWords, setRevealedReplyWords] = useState(0)
+ const [reducedMotion, setReducedMotion] = useState(false)
+
+ useEffect(() => {
+ const media = window.matchMedia('(prefers-reduced-motion: reduce)')
+ const timeouts: ReturnType[] = []
+ const intervals: ReturnType[] = []
+
+ const clearScheduled = () => {
+ for (const timeout of timeouts) clearTimeout(timeout)
+ for (const interval of intervals) clearInterval(interval)
+ timeouts.length = 0
+ intervals.length = 0
+ }
+
+ const showFinished = () => {
+ setReducedMotion(true)
+ setPhase('composer')
+ setTypedCodeChars(CODE_TOTAL_CHARS)
+ setTypedPromptChars(0)
+ setRevealedReplyWords(0)
+ }
+
+ const startCodeTyping = () => {
+ setPhase('code')
+ const startedAt = performance.now()
+ const interval = setInterval(() => {
+ const elapsed = performance.now() - startedAt
+ const next = Math.min(Math.floor(elapsed / CODE_CHAR_MS) + 1, CODE_TOTAL_CHARS)
+ setTypedCodeChars(next)
+ if (next >= CODE_TOTAL_CHARS) clearInterval(interval)
+ }, CODE_CHAR_MS)
+ intervals.push(interval)
+ }
+
+ const startPromptTyping = () => {
+ setPhase('prompt')
+ const startedAt = performance.now()
+ const interval = setInterval(() => {
+ const elapsed = performance.now() - startedAt
+ const next = Math.min(Math.floor(elapsed / PROMPT_CHAR_MS) + 1, PROMPT.length)
+ setTypedPromptChars(next)
+ if (next >= PROMPT.length) clearInterval(interval)
+ }, PROMPT_CHAR_MS)
+ intervals.push(interval)
+ }
+
+ const startReply = () => {
+ setPhase('reply')
+ const startedAt = performance.now()
+ const interval = setInterval(() => {
+ const elapsed = performance.now() - startedAt
+ const next = Math.min(Math.floor(elapsed / REPLY_WORD_MS) + 1, REPLY_WORDS.length)
+ setRevealedReplyWords(next)
+ if (next >= REPLY_WORDS.length) clearInterval(interval)
+ }, REPLY_WORD_MS)
+ intervals.push(interval)
+ }
+
+ const runLoop = () => {
+ clearScheduled()
+ setReducedMotion(false)
+ setPhase('idle')
+ setTypedCodeChars(0)
+ setTypedPromptChars(0)
+ setRevealedReplyWords(0)
+
+ timeouts.push(setTimeout(startCodeTyping, CODE_START_MS))
+ timeouts.push(setTimeout(() => setPhase('composer'), COMPOSER_IN_MS))
+ timeouts.push(setTimeout(startPromptTyping, PROMPT_START_MS))
+ timeouts.push(setTimeout(() => setPhase('exit'), SEND_MS))
+ timeouts.push(setTimeout(() => setPhase('chat'), CHAT_ENTER_MS))
+ timeouts.push(setTimeout(startReply, REPLY_MS))
+ timeouts.push(setTimeout(() => setPhase('fade'), FADE_MS))
+ timeouts.push(setTimeout(runLoop, LOOP_MS))
+ }
+
+ const syncMotionPreference = () => {
+ clearScheduled()
+ if (media.matches) {
+ showFinished()
+ return
+ }
+ runLoop()
+ }
+
+ syncMotionPreference()
+ media.addEventListener('change', syncMotionPreference)
+ return () => {
+ media.removeEventListener('change', syncMotionPreference)
+ clearScheduled()
+ }
+ }, [])
+
+ const codeTypingActive = phase === 'code' && typedCodeChars < CODE_TOTAL_CHARS
+ const composerVisible = reducedMotion || !['idle', 'code'].includes(phase)
+ const editorExited = !reducedMotion && ['exit', 'chat', 'reply', 'fade'].includes(phase)
+ const chatOpen = !reducedMotion && ['chat', 'reply', 'fade'].includes(phase)
+ const promptText = phase === 'prompt' || phase === 'exit' ? PROMPT.slice(0, typedPromptChars) : ''
+ const replyText = REPLY_WORDS.slice(0, revealedReplyWords).join(' ')
+ const lastStartedLine = CODE_LINE_STARTS.reduce(
+ (last, start, index) => (typedCodeChars > start ? index : last),
+ -1
+ )
+
+ return (
+
+
+
+
+ {CODE_LINES.map((line, index) =>
+ typedCodeChars > CODE_LINE_STARTS[index] ? (
+
+
+ {index + 1}
+
+
+ {renderCodeLine(line, typedCodeChars - CODE_LINE_STARTS[index])}
+ {codeTypingActive && index === lastStartedLine && (
+
+ )}
+
+
+ ) : null
+ )}
+
+
+
+
+
+
+ {PROMPT}
+
+ {phase === 'chat' &&
}
+
+ {replyText}
+
+
+
+
+
+
+ {promptText || 'Send message to Sim'}
+ {phase === 'prompt' && typedPromptChars < PROMPT.length && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/deploy-graphic.module.css b/apps/sim/app/(landing)/enterprise/components/feature-graphics/deploy-graphic.module.css
new file mode 100644
index 00000000000..9b7fdd22473
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/deploy-graphic.module.css
@@ -0,0 +1,141 @@
+/**
+ * Shared 4.5s animation timeline for the DeployGraphic vignette: the connector
+ * line sweep and the browser outline trace. Each connector line is a 1px
+ * faint grey track (styled in the TSX) clipping an absolutely-positioned
+ * white sweep that travels top to bottom; the lower line's sweep is delayed
+ * so the two read as one continuous flow. The moment the lower sweep's
+ * leading edge lands on the browser's top edge the outline trace takes over:
+ * four rounded-rect border overlay copies (a top pair and a side pair,
+ * mirrored left/right) are revealed by animated clip-path inset windows, so
+ * the white line splits at top-center, draws outward along the top border,
+ * bends around the exact rounded-corner geometry, and continues down the
+ * sides while the tail releases from center and follows the head — a
+ * traveling pulse that runs out and off the outline. Under
+ * prefers-reduced-motion everything is static: plain faint lines, no sweep,
+ * no trace.
+ *
+ * Timeline (percent of 4.5s): each sweep's full pass spans 22% (~1s); the
+ * lower sweep is delayed 0.9s, so its leading edge reaches the line bottom at
+ * 0.9s + 0.5s = 1.395s = 31%, where the trace begins with no gap. Trace head
+ * runs center → corner over 31→43%, head descends the sides while the tail
+ * crosses the top over 43→55%, and the tail descends and exits the open
+ * bottom by 67%; the loop rests until 100%. The top windows keep a 14px-tall
+ * strip (12px radius + 1px border on each box edge) so the corner arc
+ * belongs to the top pair; the side pair starts 13px down, exactly where the
+ * arc ends.
+ */
+
+.sweep {
+ position: absolute;
+ inset: 0;
+ background: var(--text-inverse);
+ transform: translateY(-101%);
+ animation: deploy-line-sweep 4.5s linear infinite;
+}
+
+.sweepLower {
+ animation-delay: 0.9s;
+}
+
+@keyframes deploy-line-sweep {
+ 0% {
+ transform: translateY(-101%);
+ }
+ 22% {
+ transform: translateY(101%);
+ }
+ 100% {
+ transform: translateY(101%);
+ }
+}
+
+.traceTopLeft {
+ clip-path: inset(0 50% calc(100% - 14px) 50%);
+ animation: deploy-trace-top-left 4.5s linear infinite;
+}
+
+.traceTopRight {
+ clip-path: inset(0 50% calc(100% - 14px) 50%);
+ animation: deploy-trace-top-right 4.5s linear infinite;
+}
+
+.traceSideLeft {
+ clip-path: inset(13px calc(100% - 14px) calc(100% - 13px) 0);
+ animation: deploy-trace-side-left 4.5s linear infinite;
+}
+
+.traceSideRight {
+ clip-path: inset(13px 0 calc(100% - 13px) calc(100% - 14px));
+ animation: deploy-trace-side-right 4.5s linear infinite;
+}
+
+@keyframes deploy-trace-top-left {
+ 0%,
+ 31% {
+ clip-path: inset(0 50% calc(100% - 14px) 50%);
+ }
+ 43% {
+ clip-path: inset(0 50% calc(100% - 14px) 0);
+ }
+ 55%,
+ 100% {
+ clip-path: inset(0 100% calc(100% - 14px) 0);
+ }
+}
+
+@keyframes deploy-trace-top-right {
+ 0%,
+ 31% {
+ clip-path: inset(0 50% calc(100% - 14px) 50%);
+ }
+ 43% {
+ clip-path: inset(0 0 calc(100% - 14px) 50%);
+ }
+ 55%,
+ 100% {
+ clip-path: inset(0 0 calc(100% - 14px) 100%);
+ }
+}
+
+@keyframes deploy-trace-side-left {
+ 0%,
+ 43% {
+ clip-path: inset(13px calc(100% - 14px) calc(100% - 13px) 0);
+ }
+ 55% {
+ clip-path: inset(13px calc(100% - 14px) 0 0);
+ }
+ 67%,
+ 100% {
+ clip-path: inset(100% calc(100% - 14px) 0 0);
+ }
+}
+
+@keyframes deploy-trace-side-right {
+ 0%,
+ 43% {
+ clip-path: inset(13px 0 calc(100% - 13px) calc(100% - 14px));
+ }
+ 55% {
+ clip-path: inset(13px 0 0 calc(100% - 14px));
+ }
+ 67%,
+ 100% {
+ clip-path: inset(100% 0 0 calc(100% - 14px));
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .sweep {
+ display: none;
+ animation: none;
+ }
+
+ .traceTopLeft,
+ .traceTopRight,
+ .traceSideLeft,
+ .traceSideRight {
+ display: none;
+ animation: none;
+ }
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/deploy-graphic.tsx b/apps/sim/app/(landing)/enterprise/components/feature-graphics/deploy-graphic.tsx
new file mode 100644
index 00000000000..ef16b8d2229
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/deploy-graphic.tsx
@@ -0,0 +1,144 @@
+import type { CSSProperties } from 'react'
+import { ChipTag, chipContentLabelClass, chipGeometryClass, cn } from '@sim/emcn'
+import { CircleCheck, Lock } from '@sim/emcn/icons'
+import { ThinkingLoader } from '@/components/ui'
+import styles from '@/app/(landing)/enterprise/components/feature-graphics/deploy-graphic.module.css'
+import { FeatureGraphicShell } from '@/app/(landing)/enterprise/components/feature-graphics/feature-graphic-shell'
+
+/**
+ * The ThinkingLoader's light-grey material (its dark-surface theme from
+ * `thinking-loader.module.css`), asserted inline because the always-light
+ * landing would otherwise ink the loader dark — invisible on the dark
+ * Deploy button.
+ */
+const DEPLOY_LOADER_INK = {
+ '--tl-grad-inner': '#a7a7a7',
+ '--tl-grad-outer': '#d6d6d6',
+ '--tl-glow': 'rgba(255, 255, 255, 0.9)',
+} as CSSProperties
+
+/**
+ * The moment of a one-click deploy, told top to bottom: the agent being
+ * shipped, the Deploy button, and a
+ * minimal dark browser window rising from the bottom edge with the hosted
+ * endpoint's URL in its address bar — the deployed agent is literally a live
+ * web address, no infrastructure in between. The window is an outlined shell
+ * (grey hairline, tile shows through) whose address bar carries the Deploy
+ * button's `--text-muted` fill, so click and outcome read as one system. A
+ * single causal vignette instead of a workflow canvas, so the click itself is
+ * the subject. Faint `--text-muted-inverse` hairlines (mixed toward the dark
+ * tile background so they read as guides) link agent → button → browser, and
+ * a white progress sweep (from `deploy-graphic.module.css`) loops down the
+ * two hairlines in sequence on a shared 4.5s timeline — pill → button, then
+ * button → browser. The instant the sweep's leading edge lands on the
+ * browser's top edge, the outline trace takes over with no gap: the white
+ * line splits at the top-center connection point and draws outward both
+ * ways along the top border, bends around the rounded corners, and
+ * continues down the sides while its tail releases from center and follows
+ * the head — a traveling pulse that runs out and off the outline rather
+ * than painting it permanently. It is implemented as four rounded-rect
+ * border overlay copies (top pair + side pair, mirrored) revealed by
+ * animated `clip-path` inset windows, so the trace follows the exact
+ * rounded-corner geometry at any width. Under `prefers-reduced-motion`
+ * everything is static — plain faint lines, no sweep or trace.
+ *
+ * The Deploy button's icon is the gooey {@link ThinkingLoader} pinned to
+ * `relay` — the Return shape (work coming back) — looping its ball pass,
+ * inked light so it reads on the dark button.
+ *
+ * The agent header follows the nested-corner radius rule: the `v3` ChipTag is
+ * `rounded-md` (6px) and sits 6px (`py-1.5` / `pr-1.5`) from the container
+ * edge, so the container radius is 6px + 6px = 12px.
+ *
+ * The feature tile's visual slot bleeds `2rem` right (`1.5rem` under `max-lg`)
+ * but not left, so this centered vignette adds matching right padding to land
+ * on the tile's visible center instead of the bled slot's center.
+ *
+ * The browser window is pinned to `h-24` (96px) so its top edge lands on the
+ * same line as the neighboring build-methods tile's composer (76px tall +
+ * `bottom-5`), keeping the tile row horizontally aligned; both connector
+ * lines are `flex-1` with mirrored margins, so the Deploy button stays
+ * equidistant between the agent pill and the browser at any tile height.
+ */
+export function DeployGraphic() {
+ return (
+
+
+
+
+ Support agent
+
+ v3
+
+
+
+
+
+
+
+
+ Deploy
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ sim.ai/agents/support
+
+
+
+
+
+
+ Live in production
+
+ Just now
+
+
+
+
+ )
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/feature-graphic-node.tsx b/apps/sim/app/(landing)/enterprise/components/feature-graphics/feature-graphic-node.tsx
new file mode 100644
index 00000000000..4001189c07c
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/feature-graphic-node.tsx
@@ -0,0 +1,114 @@
+import type { ComponentType, SVGProps } from 'react'
+import { cn } from '@sim/emcn'
+
+type FeatureGraphicIcon = ComponentType>
+
+export interface FeatureGraphicNodeRow {
+ label: string
+ value: string
+}
+
+interface FeatureGraphicNodeProps {
+ detail?: string
+ handle?: 'source' | 'target'
+ icon: FeatureGraphicIcon
+ label: string
+ rows?: readonly FeatureGraphicNodeRow[]
+ tone?: 'outline' | 'filled'
+}
+
+/**
+ * Product-native node for enterprise feature graphics. Outline nodes establish
+ * context; filled nodes use the Carbon-grey focal depth reserved for the feature
+ * the card is explaining.
+ */
+export function FeatureGraphicNode({
+ detail,
+ handle,
+ icon: Icon,
+ label,
+ rows,
+ tone = 'outline',
+}: FeatureGraphicNodeProps) {
+ const filled = tone === 'filled'
+
+ return (
+
+
+
+
+
+
+
+ {label}
+
+ {detail && (
+
+ {detail}
+
+ )}
+
+
+
+ {rows && rows.length > 0 && (
+
+
+ {rows.map((row) => (
+
+ {row.label}
+
+ {row.value}
+
+
+ ))}
+
+
+ )}
+
+ {handle && (
+
+ )}
+
+ )
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/feature-graphic-shell.tsx b/apps/sim/app/(landing)/enterprise/components/feature-graphics/feature-graphic-shell.tsx
new file mode 100644
index 00000000000..071a21cbb18
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/feature-graphic-shell.tsx
@@ -0,0 +1,14 @@
+import type { ReactNode } from 'react'
+
+interface FeatureGraphicShellProps {
+ children: ReactNode
+}
+
+/** Shared crop canvas for platform-faithful enterprise feature previews. */
+export function FeatureGraphicShell({ children }: FeatureGraphicShellProps) {
+ return (
+
+ )
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/feature-platform-panel.tsx b/apps/sim/app/(landing)/enterprise/components/feature-graphics/feature-platform-panel.tsx
new file mode 100644
index 00000000000..27bfb18c9d4
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/feature-platform-panel.tsx
@@ -0,0 +1,41 @@
+import type { ComponentType, ReactNode, SVGProps } from 'react'
+import { cn } from '@sim/emcn'
+
+interface FeaturePlatformPanelProps {
+ children: ReactNode
+ className?: string
+ icon: ComponentType>
+ title: string
+}
+
+/**
+ * A cropped workspace surface using the same lightweight header treatment as
+ * Sim's product views. Individual graphics provide real platform controls as
+ * the content rather than drawing a separate illustration language. The
+ * window anchors to the slot's left edge (`left-0`) so it left-aligns with
+ * the tile's title/description text column, bleeding off the right edge.
+ */
+export function FeaturePlatformPanel({
+ children,
+ className,
+ icon: Icon,
+ title,
+}: FeaturePlatformPanelProps) {
+ return (
+
+
+
+
+
+ {title}
+
+ {children}
+
+ )
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/index.ts b/apps/sim/app/(landing)/enterprise/components/feature-graphics/index.ts
new file mode 100644
index 00000000000..13db5526bc2
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/index.ts
@@ -0,0 +1,15 @@
+export { AccessControlGraphic } from './access-control-graphic'
+export { AuditTrailGraphic } from './audit-trail-graphic'
+export { BuildMethodsGraphic } from './build-methods-graphic'
+export { DeployGraphic } from './deploy-graphic'
+export { FeatureGraphicNode, type FeatureGraphicNodeRow } from './feature-graphic-node'
+export { FeatureGraphicShell } from './feature-graphic-shell'
+export { FeaturePlatformPanel } from './feature-platform-panel'
+export { ItPlatformTeamsGraphic } from './it-platform-teams-graphic'
+export { LifecycleGraphic } from './lifecycle-graphic'
+export { OperationsTeamsGraphic } from './operations-teams-graphic'
+export { RollbackGraphic } from './rollback-graphic'
+export { RunMonitoringGraphic } from './run-monitoring-graphic'
+export { StagingGraphic } from './staging-graphic'
+export { StandardsGraphic } from './standards-graphic'
+export { TechnicalTeamsGraphic } from './technical-teams-graphic'
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/it-platform-teams-graphic.module.css b/apps/sim/app/(landing)/enterprise/components/feature-graphics/it-platform-teams-graphic.module.css
new file mode 100644
index 00000000000..0306573446f
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/it-platform-teams-graphic.module.css
@@ -0,0 +1,32 @@
+/**
+ * The ItPlatformTeamsGraphic's only motion: a soft ring pulse blooming
+ * from the Active policy tag — the family's shared quiet 6s
+ * state-confirmation beat (the staging tile's Promote button, the
+ * technical tile's Approved tag). The tag itself is borderless
+ * (`shadow-none` strips the gray variant's inset ring, matching the
+ * Approved tag), so the keyframes bloom from a bare pill. Under
+ * prefers-reduced-motion the pulse is removed and the tag sits static.
+ */
+
+.activePulse {
+ animation: it-platform-active-pulse 6s ease-out infinite;
+}
+
+@keyframes it-platform-active-pulse {
+ 0%,
+ 20% {
+ box-shadow: 0 0 0 0 color-mix(in srgb, var(--text-secondary) 30%, transparent);
+ }
+ 36% {
+ box-shadow: 0 0 0 5px color-mix(in srgb, var(--text-secondary) 0%, transparent);
+ }
+ 100% {
+ box-shadow: 0 0 0 0 color-mix(in srgb, var(--text-secondary) 0%, transparent);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .activePulse {
+ animation: none;
+ }
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/it-platform-teams-graphic.tsx b/apps/sim/app/(landing)/enterprise/components/feature-graphics/it-platform-teams-graphic.tsx
new file mode 100644
index 00000000000..0c6040f9dfb
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/it-platform-teams-graphic.tsx
@@ -0,0 +1,120 @@
+import { ChipTag, cn } from '@sim/emcn'
+import { CircleCheck } from '@sim/emcn/icons'
+import Image from 'next/image'
+import { FeatureGraphicShell } from '@/app/(landing)/enterprise/components/feature-graphics/feature-graphic-shell'
+import styles from '@/app/(landing)/enterprise/components/feature-graphics/it-platform-teams-graphic.module.css'
+
+interface PolicyControl {
+ /** Enforced control name, one per pillar of the tile's claim. */
+ label: string
+ /** Right-aligned enforcement detail. */
+ detail: string
+}
+
+/**
+ * The three enforced controls mirror the tile's claim word for word —
+ * access controls (SSO), governance (reviewer approval), and audit
+ * trails (retention).
+ */
+const CONTROLS: readonly PolicyControl[] = [
+ { label: 'Single sign-on', detail: 'Enforced' },
+ { label: 'Reviewer approval', detail: 'Required' },
+ { label: 'Audit history', detail: '90 days' },
+] as const
+
+/**
+ * IT governance told as a frameless policy vignette (the audit and
+ * monitoring tiles' composition): no window chrome — a small "Workspace
+ * policies" header sits directly on the tile ground, attributed on the
+ * right to the managing team: a 16px gradient avatar (the access and
+ * audit tiles' team-avatar treatment) beside a `Managed by IT` mono
+ * ChipTag, its fill stepped up to `--surface-6` so the pill stays
+ * legible on the grey ground (the staging and rollback tiles'
+ * environment-pill treatment). Below it, the tile's highlight: the active Production
+ * policy lifted onto a white card in the audit tile's exact chrome
+ * (`--white` fill, 1px `--border-1` hairline, `rounded-xl`, `shadow-sm`)
+ * pairing the policy name and its scope line with an `Active` tag that
+ * carries the tile's one motion, the family's shared quiet 6s ring pulse
+ * (from `it-platform-teams-graphic.module.css`, removed under
+ * `prefers-reduced-motion`). Under the card, the policy's enforced
+ * controls read as quiet hairline-ruled rows — a passing circle-check,
+ * the control name, and a right-aligned enforcement detail — one row per
+ * pillar of the tile's claim: access controls, governance, audit trails.
+ *
+ * The avatar asset is a grey radial gradient on a black square, so it
+ * sits in a `rounded-full overflow-hidden` clip with a slight scale-up
+ * to crop the black canvas past the circle's edge.
+ *
+ * The feature tile's visual slot bleeds `2rem` right (`1.5rem` under
+ * `max-lg`) but not left, so this centered vignette adds matching right
+ * padding to land on the tile's visible center instead of the bled
+ * slot's center. The column is fluid (`w-full max-w-[312px]`) so it
+ * never exceeds the compensated slot at narrow tile widths — the policy
+ * name truncates instead of clipping.
+ */
+export function ItPlatformTeamsGraphic() {
+ return (
+
+
+
+
+
+ Workspace
+
+
+
+
+
+
+ Managed by IT
+
+
+
+
+
+
+
+ Production policy
+
+
+ Applies to every workspace
+
+
+
+ Active
+
+
+
+
+ {CONTROLS.map((control, index) => (
+
0 && 'border-[var(--border-1)] border-t'
+ )}
+ >
+
+
+ {control.label}
+
+
+ {control.detail}
+
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/lifecycle-graphic.module.css b/apps/sim/app/(landing)/enterprise/components/feature-graphics/lifecycle-graphic.module.css
new file mode 100644
index 00000000000..47f80cff721
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/lifecycle-graphic.module.css
@@ -0,0 +1,30 @@
+/**
+ * The LifecycleGraphic's only motion: a soft ring pulse blooming from the
+ * live version node every 6s — quiet state confirmation rather than travel,
+ * keeping this tile stiller than the deploy tile beside it. The spine's
+ * connector lines are static (styled in the TSX). Under
+ * prefers-reduced-motion the pulse is removed and the node sits static.
+ */
+
+.livePulse {
+ animation: lifecycle-live-pulse 6s ease-out infinite;
+}
+
+@keyframes lifecycle-live-pulse {
+ 0%,
+ 20% {
+ box-shadow: 0 0 0 0 color-mix(in srgb, var(--text-secondary) 35%, transparent);
+ }
+ 36% {
+ box-shadow: 0 0 0 5px color-mix(in srgb, var(--text-secondary) 0%, transparent);
+ }
+ 100% {
+ box-shadow: 0 0 0 0 color-mix(in srgb, var(--text-secondary) 0%, transparent);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .livePulse {
+ animation: none;
+ }
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/lifecycle-graphic.tsx b/apps/sim/app/(landing)/enterprise/components/feature-graphics/lifecycle-graphic.tsx
new file mode 100644
index 00000000000..22eb8a347bb
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/lifecycle-graphic.tsx
@@ -0,0 +1,115 @@
+import { ChipTag, cn } from '@sim/emcn'
+import { Clock } from '@sim/emcn/icons'
+import { FeatureGraphicShell } from '@/app/(landing)/enterprise/components/feature-graphics/feature-graphic-shell'
+import styles from '@/app/(landing)/enterprise/components/feature-graphics/lifecycle-graphic.module.css'
+
+/** Older timeline entries below the live/saved pair, quietest first to render. */
+const OLDER_VERSIONS = [
+ { detail: 'Published Jun 28', label: 'v1' },
+ { detail: 'Saved Jun 21', label: 'v0.9' },
+ { detail: 'Saved Jun 14', label: 'v0.8' },
+] as const
+
+/**
+ * A version-history timeline housed in an outlined window shell that mirrors
+ * the build tile's editor window exactly — same slot geometry (`top-5`,
+ * `left-0` so the window left-aligns with the tile's text column, bled
+ * right/bottom), same `rounded-tl-xl` radius, same top + left
+ * hairline `--border-1` edges — but drawn with no fill so the tile shows
+ * through: the two side-by-side tiles read as the same window in the same
+ * place, one filled, one outlined. An outlined title bar ("Versions" with a
+ * `Clock` icon in the build header's `size-6` `rounded-md` icon box — drawn
+ * as a `--border-1` hairline outline instead of the filled grey — no fill,
+ * `--border-1` bottom rule at the build header's `h-12` height) crowns the
+ * window. Inside, a quiet vertical spine of faint
+ * static 1px connector segments (the light analog of the deploy tile's guide
+ * lines) links version nodes bottom-up — older versions lower and
+ * progressively quieter, v3 at the top as the live entry. The live row is a
+ * white pill card following the nested-corner radius rule (6px ChipTag + 6px
+ * gap = 12px container), echoing the deploy tile's agent header; older rows
+ * are bare text rows that fade toward `--text-muted` (the v2 `Saved`
+ * tag's fill stepped up to `--surface-6` so the pill stays legible on
+ * the grey ground), and a mask gradient
+ * dissolves the oldest entries so the timeline reads as continuing into
+ * history past the window's bottom edge.
+ *
+ * The only motion is a soft ring pulse on the live node (from
+ * `lifecycle-graphic.module.css`), removed under `prefers-reduced-motion`.
+ */
+export function LifecycleGraphic() {
+ return (
+
+
+
+
+
+
+ Versions
+
+
+
+
+
+
+
+
+ v3
+ Live
+
+
+ Published today
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ v2
+
+ Saved
+
+
+ Published yesterday
+
+
+
+
+ {OLDER_VERSIONS.map((version) => (
+
+
+
+
+
+
+
+
+
+
+
+ {version.label}
+
+ {version.detail}
+
+
+
+
+ ))}
+
+
+
+ )
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/operations-teams-graphic.module.css b/apps/sim/app/(landing)/enterprise/components/feature-graphics/operations-teams-graphic.module.css
new file mode 100644
index 00000000000..85e9a7a6de7
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/operations-teams-graphic.module.css
@@ -0,0 +1,380 @@
+/**
+ * One shared 16s timeline of four 4s route phases, a precomputed shuffle
+ * so the routing feels varied without true randomness:
+ *
+ * phase 0 (0%–25%): Zendesk -> Slack
+ * phase 1 (25%–50%): Sheets -> Jira
+ * phase 2 (50%–75%): Gmail -> Salesforce
+ * phase 3 (75%–100%): Zendesk -> Jira
+ *
+ * Within each 4s phase: the source tag's white filled state crossfades
+ * in over its outline (0–0.5s), a white pulse falls down its wire
+ * (0.4–1.2s), dwells in the router while the hub blooms (1.2–2s),
+ * travels out to the destination (2–2.8s), the destination tag's fill
+ * crossfades in as the route connects (2.7–3.1s), both hold (to 3.6s),
+ * then fade back to outlined (to 4s).
+ *
+ * Tag fill overlays rest transparent (opacity-0, set inline in the TSX)
+ * so under prefers-reduced-motion — where every animation here is
+ * disabled — all six tags render outlined and static.
+ */
+
+.pulse {
+ stroke-dasharray: 0.28 1;
+ stroke-dashoffset: 0.28;
+ opacity: 0;
+}
+
+.pulseInA {
+ animation: ops-pulse-in-a 16s linear infinite;
+}
+
+@keyframes ops-pulse-in-a {
+ 0%,
+ 2.4% {
+ stroke-dashoffset: 0.28;
+ opacity: 0;
+ }
+ 2.5% {
+ stroke-dashoffset: 0.28;
+ opacity: 1;
+ }
+ 7.5% {
+ stroke-dashoffset: -1;
+ opacity: 1;
+ }
+ 7.6%,
+ 77.4% {
+ stroke-dashoffset: 0.28;
+ opacity: 0;
+ }
+ 77.5% {
+ stroke-dashoffset: 0.28;
+ opacity: 1;
+ }
+ 82.5% {
+ stroke-dashoffset: -1;
+ opacity: 1;
+ }
+ 82.6%,
+ 100% {
+ stroke-dashoffset: -1;
+ opacity: 0;
+ }
+}
+
+.pulseInB {
+ animation: ops-pulse-in-b 16s linear infinite;
+}
+
+@keyframes ops-pulse-in-b {
+ 0%,
+ 27.4% {
+ stroke-dashoffset: 0.28;
+ opacity: 0;
+ }
+ 27.5% {
+ stroke-dashoffset: 0.28;
+ opacity: 1;
+ }
+ 32.5% {
+ stroke-dashoffset: -1;
+ opacity: 1;
+ }
+ 32.6%,
+ 100% {
+ stroke-dashoffset: -1;
+ opacity: 0;
+ }
+}
+
+.pulseInC {
+ animation: ops-pulse-in-c 16s linear infinite;
+}
+
+@keyframes ops-pulse-in-c {
+ 0%,
+ 52.4% {
+ stroke-dashoffset: 0.28;
+ opacity: 0;
+ }
+ 52.5% {
+ stroke-dashoffset: 0.28;
+ opacity: 1;
+ }
+ 57.5% {
+ stroke-dashoffset: -1;
+ opacity: 1;
+ }
+ 57.6%,
+ 100% {
+ stroke-dashoffset: -1;
+ opacity: 0;
+ }
+}
+
+.pulseOutA {
+ animation: ops-pulse-out-a 16s linear infinite;
+}
+
+@keyframes ops-pulse-out-a {
+ 0%,
+ 12.4% {
+ stroke-dashoffset: 0.28;
+ opacity: 0;
+ }
+ 12.5% {
+ stroke-dashoffset: 0.28;
+ opacity: 1;
+ }
+ 17.5% {
+ stroke-dashoffset: -1;
+ opacity: 1;
+ }
+ 17.6%,
+ 100% {
+ stroke-dashoffset: -1;
+ opacity: 0;
+ }
+}
+
+.pulseOutB {
+ animation: ops-pulse-out-b 16s linear infinite;
+}
+
+@keyframes ops-pulse-out-b {
+ 0%,
+ 37.4% {
+ stroke-dashoffset: 0.28;
+ opacity: 0;
+ }
+ 37.5% {
+ stroke-dashoffset: 0.28;
+ opacity: 1;
+ }
+ 42.5% {
+ stroke-dashoffset: -1;
+ opacity: 1;
+ }
+ 42.6%,
+ 87.4% {
+ stroke-dashoffset: 0.28;
+ opacity: 0;
+ }
+ 87.5% {
+ stroke-dashoffset: 0.28;
+ opacity: 1;
+ }
+ 92.5% {
+ stroke-dashoffset: -1;
+ opacity: 1;
+ }
+ 92.6%,
+ 100% {
+ stroke-dashoffset: -1;
+ opacity: 0;
+ }
+}
+
+.pulseOutC {
+ animation: ops-pulse-out-c 16s linear infinite;
+}
+
+@keyframes ops-pulse-out-c {
+ 0%,
+ 62.4% {
+ stroke-dashoffset: 0.28;
+ opacity: 0;
+ }
+ 62.5% {
+ stroke-dashoffset: 0.28;
+ opacity: 1;
+ }
+ 67.5% {
+ stroke-dashoffset: -1;
+ opacity: 1;
+ }
+ 67.6%,
+ 100% {
+ stroke-dashoffset: -1;
+ opacity: 0;
+ }
+}
+
+.fillSrcA {
+ animation: ops-fill-src-a 16s linear infinite;
+}
+
+@keyframes ops-fill-src-a {
+ 0% {
+ opacity: 0;
+ }
+ 3.1% {
+ opacity: 1;
+ }
+ 22.5% {
+ opacity: 1;
+ }
+ 24.9%,
+ 75% {
+ opacity: 0;
+ }
+ 78.1% {
+ opacity: 1;
+ }
+ 97.5% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
+}
+
+.fillSrcB {
+ animation: ops-fill-src-b 16s linear infinite;
+}
+
+@keyframes ops-fill-src-b {
+ 0%,
+ 25% {
+ opacity: 0;
+ }
+ 28.1% {
+ opacity: 1;
+ }
+ 47.5% {
+ opacity: 1;
+ }
+ 49.9%,
+ 100% {
+ opacity: 0;
+ }
+}
+
+.fillSrcC {
+ animation: ops-fill-src-c 16s linear infinite;
+}
+
+@keyframes ops-fill-src-c {
+ 0%,
+ 50% {
+ opacity: 0;
+ }
+ 53.1% {
+ opacity: 1;
+ }
+ 72.5% {
+ opacity: 1;
+ }
+ 74.9%,
+ 100% {
+ opacity: 0;
+ }
+}
+
+.fillDstA {
+ animation: ops-fill-dst-a 16s linear infinite;
+}
+
+@keyframes ops-fill-dst-a {
+ 0%,
+ 16.9% {
+ opacity: 0;
+ }
+ 19.4% {
+ opacity: 1;
+ }
+ 22.5% {
+ opacity: 1;
+ }
+ 24.9%,
+ 100% {
+ opacity: 0;
+ }
+}
+
+.fillDstB {
+ animation: ops-fill-dst-b 16s linear infinite;
+}
+
+@keyframes ops-fill-dst-b {
+ 0%,
+ 41.9% {
+ opacity: 0;
+ }
+ 44.4% {
+ opacity: 1;
+ }
+ 47.5% {
+ opacity: 1;
+ }
+ 49.9%,
+ 91.9% {
+ opacity: 0;
+ }
+ 94.4% {
+ opacity: 1;
+ }
+ 97.5% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
+}
+
+.fillDstC {
+ animation: ops-fill-dst-c 16s linear infinite;
+}
+
+@keyframes ops-fill-dst-c {
+ 0%,
+ 66.9% {
+ opacity: 0;
+ }
+ 69.4% {
+ opacity: 1;
+ }
+ 72.5% {
+ opacity: 1;
+ }
+ 74.9%,
+ 100% {
+ opacity: 0;
+ }
+}
+
+.routerBloom {
+ animation: ops-router-bloom 4s linear infinite;
+}
+
+@keyframes ops-router-bloom {
+ 0%,
+ 30% {
+ box-shadow: 0 0 0 0 color-mix(in srgb, var(--text-muted-inverse) 45%, transparent);
+ }
+ 50% {
+ box-shadow: 0 0 0 6px color-mix(in srgb, var(--text-muted-inverse) 0%, transparent);
+ }
+ 100% {
+ box-shadow: 0 0 0 0 color-mix(in srgb, var(--text-muted-inverse) 0%, transparent);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .pulseInA,
+ .pulseInB,
+ .pulseInC,
+ .pulseOutA,
+ .pulseOutB,
+ .pulseOutC,
+ .fillSrcA,
+ .fillSrcB,
+ .fillSrcC,
+ .fillDstA,
+ .fillDstB,
+ .fillDstC,
+ .routerBloom {
+ animation: none;
+ }
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/operations-teams-graphic.tsx b/apps/sim/app/(landing)/enterprise/components/feature-graphics/operations-teams-graphic.tsx
new file mode 100644
index 00000000000..755b7238f4e
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/operations-teams-graphic.tsx
@@ -0,0 +1,296 @@
+import type { CSSProperties } from 'react'
+import { cn } from '@sim/emcn'
+import { ThinkingLoader } from '@/components/ui'
+import { FeatureGraphicShell } from '@/app/(landing)/enterprise/components/feature-graphics/feature-graphic-shell'
+import styles from '@/app/(landing)/enterprise/components/feature-graphics/operations-teams-graphic.module.css'
+
+/**
+ * Fixed pixel canvas the switchboard is drawn on, centered inside the
+ * shell. Kept to 280px wide (chips reaching only x 32–250) so every chip
+ * stays inside the tile's visible area even at the narrowest three-up
+ * tile width (~274px visible at 1024), and 248px tall — the tallest
+ * canvas that clears the slot's shortest inner box (~257px at 1024 after
+ * bottom-bleed compensation) so the staggered top pills never clip while
+ * the wires keep long vertical runs.
+ */
+const CANVAS = { WIDTH: 280, HEIGHT: 248 } as const
+
+/**
+ * The ThinkingLoader's light-grey material (its dark-surface theme from
+ * `thinking-loader.module.css`), asserted inline exactly as the deploy
+ * tile's Deploy button does — the always-light landing would otherwise
+ * ink the loader dark, invisible on the dark tile ground showing through
+ * the outlined router hub.
+ */
+const ROUTER_LOADER_INK = {
+ '--tl-grad-inner': '#a7a7a7',
+ '--tl-grad-outer': '#d6d6d6',
+ '--tl-glow': 'rgba(255, 255, 255, 0.9)',
+} as CSSProperties
+
+interface Port {
+ /** Tailwind classes positioning the size-2 port dot, centered on the port's canvas x/y. */
+ dotClass: string
+ /** Tailwind classes placing the tag's center on the port's canvas x, above/below its dot. */
+ chipClass: string
+ /** Tool name rendered as an outlined tag beside the port. */
+ label: string
+ /** CSS-module animation class driving this tag's white connect-fill on its scheduled phases. */
+ fillClass: string
+}
+
+/**
+ * Inbound ops tools across the top, staggered like a constellation rather
+ * than a rank: Zendesk rides highest on the center axis (x 140), Gmail
+ * sits lowest at the left (x 56), Sheets between at the right (x 224).
+ */
+const SOURCES: readonly Port[] = [
+ {
+ dotClass: 'top-[48px] left-[52px]',
+ chipClass: 'top-[32px] left-[56px]',
+ label: 'Gmail',
+ fillClass: styles.fillSrcC,
+ },
+ {
+ dotClass: 'top-[28px] left-[136px]',
+ chipClass: 'top-[12px] left-[140px]',
+ label: 'Zendesk',
+ fillClass: styles.fillSrcA,
+ },
+ {
+ dotClass: 'top-[38px] left-[220px]',
+ chipClass: 'top-[22px] left-[224px]',
+ label: 'Sheets',
+ fillClass: styles.fillSrcB,
+ },
+] as const
+
+/**
+ * Outbound destinations mirrored below, staggered the same way: Slack at
+ * the left (x 56), Salesforce hanging lowest on the center axis (x 140),
+ * Jira highest at the right (x 224).
+ */
+const DESTINATIONS: readonly Port[] = [
+ {
+ dotClass: 'top-[192px] left-[52px]',
+ chipClass: 'top-[216px] left-[56px]',
+ label: 'Slack',
+ fillClass: styles.fillDstA,
+ },
+ {
+ dotClass: 'top-[212px] left-[136px]',
+ chipClass: 'top-[236px] left-[140px]',
+ label: 'Salesforce',
+ fillClass: styles.fillDstC,
+ },
+ {
+ dotClass: 'top-[202px] left-[220px]',
+ chipClass: 'top-[226px] left-[224px]',
+ label: 'Jira',
+ fillClass: styles.fillDstB,
+ },
+] as const
+
+/**
+ * Wires from each source port gathering into the router hub's top pole
+ * (140, 93) — all three terminate at the same point so they read as a
+ * tied bundle feeding the agent, with vertical tangents at both ends
+ * (the access tile's edge geometry) and long vertical runs so each curve
+ * travels before it arrives.
+ */
+const IN_PATHS = {
+ gmail: 'M 56 52 C 56 74 140 66 140 93',
+ zendesk: 'M 140 32 L 140 93',
+ sheets: 'M 224 42 C 224 70 140 64 140 93',
+} as const
+
+/**
+ * Wires fanning out from the router hub's bottom pole (140, 155) — the
+ * shared origin splitting to each destination, echoing the deploy tile's
+ * single pipeline branching.
+ */
+const OUT_PATHS = {
+ slack: 'M 140 155 C 140 182 56 174 56 196',
+ salesforce: 'M 140 155 L 140 216',
+ jira: 'M 140 155 C 140 184 224 178 224 206',
+} as const
+
+/** Faint-ink stroke for the resting wires (the deploy tile's guide-line grey, quieter). */
+const QUIET_STROKE = 'color-mix(in srgb, var(--text-muted-inverse) 28%, transparent)'
+
+/** Shared 1px outline ink for the tags, port dots, and router hub ring. */
+const OUTLINE_INK = 'border-[color:color-mix(in_srgb,var(--text-muted-inverse)_45%,transparent)]'
+
+/** Shared SVG props for a resting wire. */
+const WIRE_PROPS = { stroke: QUIET_STROKE, strokeWidth: '1' } as const
+
+/** Shared SVG props for a traveling white request-pulse overlay on a wire. */
+const PULSE_PROPS = {
+ pathLength: 1,
+ stroke: 'var(--text-inverse)',
+ strokeWidth: '1.25',
+ strokeLinecap: 'round',
+} as const
+
+/** An outlined integration tag whose white connect-fill crossfades in on its scheduled phases. */
+function PortTag({ port }: { port: Port }) {
+ return (
+
+
+ {port.label}
+
+ {port.label}
+
+
+
+ )
+}
+
+/**
+ * Operations automation told as a vertical switchboard — the access tile's
+ * top-to-bottom composition (sources above, curved bezier edges flowing
+ * down to what they feed) in the dark tiles' ink: three ops tools wired in
+ * across the top (Gmail, Zendesk, Sheets), an outlined circular router hub
+ * at center — the agent — and three destinations wired out below (Slack,
+ * Salesforce, Jira). Both rows stagger their tag heights into a loose
+ * constellation, giving each wire its own long arc instead of ranking the
+ * tags into flat rows. Every element is drawn in the same outlined
+ * language: the six tool tags rest as transparent pills with 1px light
+ * hairline borders and light-grey ink, the hub is a transparent circle
+ * with the same hairline ring, and the wires are 1px curved SVG paths
+ * with vertical tangents (the access tile's edge geometry) gathering into
+ * the hub's top pole and fanning from its bottom pole — a tied bundle in,
+ * a split out.
+ *
+ * Inside the hub, the platform's gooey {@link ThinkingLoader} runs its
+ * default full morph cycle (no pinned variant, unlike the deploy button's
+ * `relay`) wearing the deploy button's exact light-grey ink override, so
+ * the page's two dark-tile loaders read as the same material — the agent
+ * visibly churning through work between dispatches. The loader stops
+ * cycling on its own under `prefers-reduced-motion`.
+ *
+ * Motion (from `operations-teams-graphic.module.css`, one shared 16s
+ * timeline of four 4s route phases — Zendesk→Slack, Sheets→Jira,
+ * Gmail→Salesforce, Zendesk→Jira — a precomputed shuffle so the routing
+ * feels varied): in each phase the source tag's white filled state
+ * crossfades in over its outline (ink flipping dark with it), a white
+ * pulse falls down its wire into the router — which blooms the family's
+ * ring pulse while the agent classifies — then out the bottom pole to
+ * the destination tag, whose fill crossfades in as the route connects.
+ * Both ends hold briefly, fade back to outlined, and the next pair
+ * begins. Everything is static and outlined under
+ * `prefers-reduced-motion`.
+ *
+ * The feature tile's visual slot bleeds `2rem` right and bottom (`1.5rem`
+ * under `max-lg`) but not left or top, so the canvas sits inside a
+ * matching right- and bottom-padded box to land on the tile's visible
+ * center. The fixed-size canvas is centered with a transform (`left-1/2`
+ * + `-translate-x-1/2`, the standards tile's approach) so at narrow tile
+ * widths it keeps its wiring geometry and overflows both edges equally
+ * instead of drifting.
+ */
+export function OperationsTeamsGraphic() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {SOURCES.map((port) => (
+
+
+
+
+ ))}
+
+ {DESTINATIONS.map((port) => (
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/rollback-graphic.tsx b/apps/sim/app/(landing)/enterprise/components/feature-graphics/rollback-graphic.tsx
new file mode 100644
index 00000000000..52276001e1b
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/rollback-graphic.tsx
@@ -0,0 +1,123 @@
+import { Button, ChipTag } from '@sim/emcn'
+import { Undo } from '@sim/emcn/icons'
+import { FeatureGraphicShell } from '@/app/(landing)/enterprise/components/feature-graphics/feature-graphic-shell'
+
+/**
+ * Rollback told as a "return to known-good" gesture inside the lifecycle
+ * tile's outlined window chrome: the window is a 1px `--border-1` hairline
+ * outline with no fill (tile grey showing through), anchored to the text
+ * column's left edge (`left-0`, `top-5`) and bleeding off the right and
+ * bottom edges with a `rounded-tl-xl` top-left corner — the same slot
+ * geometry as the build and lifecycle windows. A "Version history" title
+ * bar with a right-aligned `Production` mono ChipTag (its fill stepped up
+ * to `--surface-6` so the pill stays legible on the grey ground) crowns
+ * the window at the family's `h-12` header height, ruled by a hairline
+ * divider.
+ *
+ * Inside, a short newest-first version rail runs down the left gutter —
+ * v4 as the quiet current row (its `Current` tag on the grey-ground
+ * `--surface-6` pill fill), then a plain vertical hairline connector
+ * down to v3, the lifecycle timeline's spine language. v3 is the tile's
+ * highlight: the node fills solid and
+ * the row lifts onto a white card (`--white` fill, 1px `--border-1`
+ * hairline, nested `rounded-lg` inside the `rounded-tl-xl` window,
+ * `shadow-sm`) pairing "v3 · Stable · Maya Chen" with the tile's
+ * strongest element, the solid Roll back button led by a small `Undo`
+ * glyph inked in the button's inverse text color. A quieter v2 row
+ * dissolves through a mask gradient below, implying the history continues
+ * past the window's bled bottom edge — the lifecycle tile's fade device.
+ *
+ * The window bleeds `2rem` past the tile's visible right edge (`1.5rem`
+ * under `max-lg`), so the header and body carry matching extra right
+ * padding (`pr-12 max-lg:pr-10` = the bleed plus the usual `1rem` gutter)
+ * to keep the Production tag and the v3 card's Roll back button fully
+ * inside the visible tile.
+ *
+ * The rail is fully static. The connector hairlines are absolutely
+ * positioned inside fixed-height spacer rows so they can extend past the
+ * row box into the adjacent rows' empty gutter space, ending a couple of
+ * pixels from each node — the lifecycle spine's tight node-to-line
+ * clearance — without disturbing the flow layout.
+ */
+export function RollbackGraphic() {
+ return (
+
+
+
+
+ Version history
+
+
+ Production
+
+
+
+
+
+
+
+
+
+ v4
+
+ Current
+
+
+ Published 2h ago
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ v3
+ Stable
+
+
+ Maya Chen · Jun 30
+
+
+
+
+ Roll back
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ v2
+ Saved Jun 24
+
+
+
+
+
+ )
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/run-monitoring-graphic.module.css b/apps/sim/app/(landing)/enterprise/components/feature-graphics/run-monitoring-graphic.module.css
new file mode 100644
index 00000000000..846d5304769
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/run-monitoring-graphic.module.css
@@ -0,0 +1,30 @@
+/**
+ * The RunMonitoringGraphic's only motion: a soft ring pulse blooming from
+ * the header's live-status dot every 6s — the family's shared quiet
+ * state-confirmation beat (the lifecycle tile's live node, the staging
+ * tile's Promote button). Under prefers-reduced-motion the pulse is
+ * removed and the dot sits static.
+ */
+
+.livePulse {
+ animation: run-monitoring-live-pulse 6s ease-out infinite;
+}
+
+@keyframes run-monitoring-live-pulse {
+ 0%,
+ 20% {
+ box-shadow: 0 0 0 0 color-mix(in srgb, var(--text-secondary) 35%, transparent);
+ }
+ 36% {
+ box-shadow: 0 0 0 5px color-mix(in srgb, var(--text-secondary) 0%, transparent);
+ }
+ 100% {
+ box-shadow: 0 0 0 0 color-mix(in srgb, var(--text-secondary) 0%, transparent);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .livePulse {
+ animation: none;
+ }
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/run-monitoring-graphic.tsx b/apps/sim/app/(landing)/enterprise/components/feature-graphics/run-monitoring-graphic.tsx
new file mode 100644
index 00000000000..981f33ccd50
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/run-monitoring-graphic.tsx
@@ -0,0 +1,128 @@
+import type { ReactNode } from 'react'
+import { ChipTag, cn } from '@sim/emcn'
+import { FeatureGraphicShell } from '@/app/(landing)/enterprise/components/feature-graphics/feature-graphic-shell'
+import styles from '@/app/(landing)/enterprise/components/feature-graphics/run-monitoring-graphic.module.css'
+
+interface LogField {
+ /** Row label, matching the workspace Log Details panel's key column. */
+ label: string
+ /** Right-aligned value cell — plain text or a quiet mono chip. */
+ value: ReactNode
+}
+
+/**
+ * The Log Details keys the real panel leads with, distilled to tile scale:
+ * workflow name, shortened run id, trigger, and duration. The workspace
+ * UI's green Level/Trigger tags become quiet grey mono chips per the
+ * row's monochrome vocabulary.
+ */
+const LOG_FIELDS: readonly LogField[] = [
+ {
+ label: 'Workflow',
+ value: (
+
+ Support agent
+
+ ),
+ },
+ {
+ label: 'Run ID',
+ value: (
+
+ afda69e9
+
+ ),
+ },
+ {
+ label: 'Trigger',
+ value: (
+
+ Schedule
+
+ ),
+ },
+ {
+ label: 'Duration',
+ value: 1.56s ,
+ },
+] as const
+
+/**
+ * The workspace's Log Details panel told as a frameless vignette (the
+ * access and standards tiles' composition): no window chrome — a small
+ * "Log details" header with a live-status dot (the tile's one emphasized
+ * motion) sits directly on the tile ground above the panel's key-value
+ * ledger — Workflow, shortened Run ID, Trigger, and Duration as airy
+ * rows ruled by quiet 1px `--border-1` hairlines, the real UI's green
+ * tags quieted to grey mono chips (fills stepped up to `--surface-6` so
+ * the pills stay legible on the grey ground). The closing "Workflow
+ * output" section
+ * lifts its two-line JSON fragment onto the tile's highlight: a white
+ * card in the audit tile's exact chrome (`--white` fill, 1px `--border-1`
+ * hairline, rounded, `shadow-sm`), the run's payload presented as the
+ * artifact worth watching.
+ *
+ * The only motion is the soft ring pulse on the live dot (from
+ * `run-monitoring-graphic.module.css`), the family's shared quiet 6s
+ * beat, removed under `prefers-reduced-motion`.
+ *
+ * The feature tile's visual slot bleeds `2rem` right (`1.5rem` under
+ * `max-lg`) but not left, so this centered vignette adds matching right
+ * padding to land on the tile's visible center instead of the bled
+ * slot's center. The column is fluid (`w-full max-w-[312px]`) so it
+ * never exceeds the compensated slot at narrow tile widths — the
+ * workflow value and JSON lines truncate instead of clipping.
+ */
+export function RunMonitoringGraphic() {
+ return (
+
+
+
+
+
+ Log details
+
+
+
+ Live
+
+
+
+ {LOG_FIELDS.map((field, index) => (
+
0 && 'border-[var(--border-1)] border-t'
+ )}
+ >
+ {field.label}
+ {field.value}
+
+ ))}
+
+
+
Workflow output
+
+
+ {'{ "status": '}
+ "completed"
+ ,
+
+
+ {' "resolved": '}
+ 24
+ {' }'}
+
+
+
+
+
+
+ )
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/staging-graphic.module.css b/apps/sim/app/(landing)/enterprise/components/feature-graphics/staging-graphic.module.css
new file mode 100644
index 00000000000..85a358479fb
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/staging-graphic.module.css
@@ -0,0 +1,30 @@
+/**
+ * The StagingGraphic's only motion: a soft ring pulse blooming from the
+ * Promote button — the same quiet 6s state-confirmation beat as the
+ * access tile's grant node and the lifecycle tile's live node, keeping
+ * the family's shared rhythm. Under prefers-reduced-motion the pulse is
+ * removed and the button sits static.
+ */
+
+.promotePulse {
+ animation: staging-promote-pulse 6s ease-out infinite;
+}
+
+@keyframes staging-promote-pulse {
+ 0%,
+ 20% {
+ box-shadow: 0 0 0 0 color-mix(in srgb, var(--text-secondary) 30%, transparent);
+ }
+ 36% {
+ box-shadow: 0 0 0 5px color-mix(in srgb, var(--text-secondary) 0%, transparent);
+ }
+ 100% {
+ box-shadow: 0 0 0 0 color-mix(in srgb, var(--text-secondary) 0%, transparent);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .promotePulse {
+ animation: none;
+ }
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/staging-graphic.tsx b/apps/sim/app/(landing)/enterprise/components/feature-graphics/staging-graphic.tsx
new file mode 100644
index 00000000000..a2c50aec8cc
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/staging-graphic.tsx
@@ -0,0 +1,123 @@
+import { Button, ChipTag, cn } from '@sim/emcn'
+import { ArrowRight, CircleCheck } from '@sim/emcn/icons'
+import Image from 'next/image'
+import { FeatureGraphicShell } from '@/app/(landing)/enterprise/components/feature-graphics/feature-graphic-shell'
+import styles from '@/app/(landing)/enterprise/components/feature-graphics/staging-graphic.module.css'
+
+/** Named pre-promotion checks, each rendered as a passing row. */
+const CHECKS = ['Evals passed', 'No breaking changes', 'Approved by Ops'] as const
+
+/**
+ * A GitHub-style promotion surface compressed to tile scale, on the
+ * lifecycle tile's outlined ground: the window is a 1px `--border-1`
+ * hairline outline with no fill (tile grey showing through,
+ * `rounded-xl`), topped by a plain title header ("Support agent" + a
+ * `v4` mono tag on the grey-ground `--surface-6` pill fill). Below,
+ * three hairline-ruled sections tell the
+ * merge-area story with an inverted surface hierarchy — the build being
+ * promoted is the highlight, a white card (`--white` fill, `--border-1`
+ * hairline, nested `rounded-lg`, `shadow-sm`, echoing the audit tile's
+ * selected-record card) pairing a short hash chip with its change
+ * message and a "Maya Chen · 2h ago" attribution line (gradient avatar
+ * shared with the access and audit tiles); the named check gates sit
+ * directly on the grey ground as quiet passing rows; and the promotion
+ * bar pairs `Staging → Live` environment tags (fills stepped up to
+ * `--surface-6` so the pills stay legible on the grey ground) with the
+ * tile's strongest element, the solid Promote button.
+ *
+ * The only motion is a soft ring pulse on the Promote button (from
+ * `staging-graphic.module.css`), the family's shared quiet 6s beat,
+ * removed under `prefers-reduced-motion`.
+ *
+ * The avatar asset is a grey radial gradient on a black square, so it
+ * sits in a `rounded-full overflow-hidden` clip with a slight scale-up to
+ * crop the black canvas past the circle's edge.
+ *
+ * The feature tile's visual slot bleeds `2rem` right and bottom
+ * (`1.5rem` under `max-lg`) but not left or top, so this centered window
+ * adds matching right and bottom padding to land on the tile's visible
+ * center instead of the bled slot's center — keeping comparable air
+ * above and below the window. The section paddings stay compact
+ * (`h-10` header, `py-2.5` sections) so the window's intrinsic height
+ * plus the bottom-bleed compensation fits inside the slot's
+ * `overflow-hidden` bounds — a taller window would push the top hairline
+ * and corners past the slot's top clip line and truncate them. The
+ * window is fluid (`w-full max-w-[312px]`) so it never
+ * exceeds the compensated slot and both edges stay visible at narrow
+ * tile widths — text rows truncate instead of clipping, and the
+ * `Staging → Live` tags yield below `xl` so the promotion bar keeps the
+ * Promote button inside the window.
+ */
+export function StagingGraphic() {
+ return (
+
+
+
+
+
+ Support agent
+
+
+ v4
+
+
+
+
+
+
+ a3f8c21
+
+ Tighten refund policy checks
+
+
+
+
+
+
+ Maya Chen · 2h ago
+
+
+
+
+
+ {CHECKS.map((check) => (
+
+
+ {check}
+
+ ))}
+
+
+
+
+
+ Staging
+
+
+
+ Live
+
+
+
+ Promote
+
+
+
+
+
+ )
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/standards-graphic.module.css b/apps/sim/app/(landing)/enterprise/components/feature-graphics/standards-graphic.module.css
new file mode 100644
index 00000000000..87c5745af4c
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/standards-graphic.module.css
@@ -0,0 +1,106 @@
+/**
+ * The StandardsGraphic's only motion: the certification seal blooms the
+ * row's shared 6s ring pulse — the same quiet state-confirmation beat as
+ * the lifecycle tile's live node and the access tile's grant node, inked
+ * with the deploy tile's light `--text-muted-inverse` so it reads on the
+ * dark `--text-secondary` tile surface. Removed under
+ * prefers-reduced-motion so the seal sits static.
+ */
+
+.sealPulse {
+ animation: standards-seal-pulse 6s ease-out infinite;
+}
+
+@keyframes standards-seal-pulse {
+ 0%,
+ 20% {
+ box-shadow: 0 0 0 0 color-mix(in srgb, var(--text-muted-inverse) 55%, transparent);
+ }
+ 36% {
+ box-shadow: 0 0 0 6px color-mix(in srgb, var(--text-muted-inverse) 0%, transparent);
+ }
+ 100% {
+ box-shadow: 0 0 0 0 color-mix(in srgb, var(--text-muted-inverse) 0%, transparent);
+ }
+}
+
+/**
+ * Connector draw loop, following the deploy tile's dash vocabulary:
+ * paths normalized to pathLength=1 draw in, hold, then un-draw forward
+ * (dashoffset continues to -1 so the line exits in its direction of
+ * travel) on the same 6s timeline as the seal pulse. The trailing
+ * connector (seal → Open source) runs the identical keyframes
+ * phase-shifted by 0.7s so the two lines read as a stagger.
+ */
+
+.connector {
+ stroke-dasharray: 1;
+ stroke-dashoffset: 1;
+ animation: standards-connector-draw 6s ease-in-out infinite;
+}
+
+.connectorTrailing {
+ animation-delay: 0.7s;
+}
+
+@keyframes standards-connector-draw {
+ 0% {
+ stroke-dashoffset: 1;
+ }
+ 16% {
+ stroke-dashoffset: 0;
+ }
+ 68% {
+ stroke-dashoffset: 0;
+ }
+ 84%,
+ 100% {
+ stroke-dashoffset: -1;
+ }
+}
+
+/**
+ * Orbital ring sweeps — the deploy tile's white progress sweep bent around
+ * the certificate rings: each ring carries a fixed 50° arc stroked with a
+ * comet gradient (bright at the clockwise leading end, transparent at the
+ * tail) inside a group that rotates around the seal center for a slow,
+ * seamless orbit. Rotation replaced the earlier stroke-dash offset loop
+ * because a dash that wraps the circle's path start renders a seam
+ * artifact (a stray cap segment at the wrap point); a rotating group has
+ * no seam. Both orbits share the 14s period; the outer ring runs half a
+ * revolution out of phase so the two sweeps never align.
+ */
+
+.ringSweep {
+ transform-box: view-box;
+ transform-origin: 160px 112px;
+ animation: standards-ring-orbit 14s linear infinite;
+}
+
+.ringSweepOuter {
+ animation-delay: -7s;
+}
+
+@keyframes standards-ring-orbit {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .sealPulse {
+ animation: none;
+ }
+
+ .connector {
+ animation: none;
+ stroke-dashoffset: 0;
+ }
+
+ .ringSweep {
+ display: none;
+ }
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/standards-graphic.tsx b/apps/sim/app/(landing)/enterprise/components/feature-graphics/standards-graphic.tsx
new file mode 100644
index 00000000000..80b6e4b1a8b
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/standards-graphic.tsx
@@ -0,0 +1,161 @@
+import { ChipTag, cn } from '@sim/emcn'
+import { ShieldCheck } from '@sim/emcn/icons'
+import { FeatureGraphicShell } from '@/app/(landing)/enterprise/components/feature-graphics/feature-graphic-shell'
+import styles from '@/app/(landing)/enterprise/components/feature-graphics/standards-graphic.module.css'
+
+/**
+ * Enterprise standards told as a certification seal, with no window
+ * framing: a shield-check medallion at the center wearing the deploy
+ * tile's Deploy-button chrome — the same `--text-muted` fill with
+ * `--text-inverse` ink — so the two dark tiles' hero elements read as
+ * siblings. It is ringed by two concentric hairlines like a certificate
+ * mark, with the outer ring dissolving through a mask the way the row's
+ * older elements fade. The tile's two claims anchor the seal from either
+ * side over short hairline connectors with port dots (the access tile's
+ * junction vocabulary): a `SOC 2` chip and an `Open source` chip, whose
+ * mono `--surface-5` fills read as light pills on the dark ground
+ * exactly like the deploy tile's ChipTag. Each chip is anchored by its
+ * connector-facing edge to the connector's endpoint (not centered on a
+ * fixed x), so both junctions stay flush regardless of label width. A `Verified` pill beneath the seal carries the emphasized
+ * state on the deploy tile's `--text-muted` chip fill with `--text-inverse`
+ * ink (ChipTag's solid variant would vanish here — its fill is the tile's
+ * own `--text-secondary`). The seal blooms the row's shared 6s ring pulse
+ * (from `standards-graphic.module.css`, removed under
+ * `prefers-reduced-motion`).
+ *
+ * Inks follow the deploy tile's dark-surface palette: hairlines and dots
+ * mix `--text-muted-inverse` toward transparent (45% on the working
+ * lines, fainter on the decorative rings), and the port dots fill with
+ * the tile's `--text-secondary` so the connector reads as passing
+ * beneath them.
+ *
+ * The two connectors are SVG paths normalized to `pathLength=1` so they
+ * draw in with the page's dash vocabulary (the deploy tile's stagger):
+ * SOC 2 draws into the seal first, the seal draws out to Open source a
+ * beat later, both hold, then un-draw forward and loop on the shared 6s
+ * timeline. Under `prefers-reduced-motion` they render fully drawn with
+ * no animation.
+ *
+ * Each ring also carries a comet arc — the deploy tile's white connector
+ * sweep made circular: a fixed 50° arc over the hairline, stroked with a
+ * `userSpaceOnUse` linear gradient that is bright at the clockwise
+ * leading tip and fades to transparent along the trailing side, inside a
+ * group that rotates around the seal center (a rotating group rather
+ * than a dash offset, because a dash wrapping the circle's path start
+ * renders a stray seam-cap artifact). The two orbits share one 14s
+ * period but the outer runs phase-shifted half a revolution, so the
+ * sweeps are never synchronized; the outer sweep sits inside the same
+ * fade mask as its hairline so it dissolves through the bottom of the
+ * tile like the ring it traces. Under `prefers-reduced-motion` the
+ * sweeps are hidden entirely.
+ *
+ * The feature tile's visual slot bleeds `2rem` right (`1.5rem` under
+ * `max-lg`) but not left, so the canvas sits inside a matching
+ * right-padded box to land on the tile's visible center instead of the
+ * bled slot's center. The canvas itself is centered with a transform
+ * (`left-1/2` + `-translate-x-1/2`) rather than flex `justify-center`
+ * because an overflowing fixed-size flex item start-aligns instead of
+ * centering once the tile gets narrower than the canvas.
+ */
+export function StandardsGraphic() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SOC 2
+
+
+ Open source
+
+
+
+ Verified
+
+
+
+
+
+ )
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/technical-teams-graphic.module.css b/apps/sim/app/(landing)/enterprise/components/feature-graphics/technical-teams-graphic.module.css
new file mode 100644
index 00000000000..4183e7cb77b
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/technical-teams-graphic.module.css
@@ -0,0 +1,31 @@
+/**
+ * The TechnicalTeamsGraphic's only motion: a soft ring pulse blooming
+ * from the Approved tag on the review card — the same quiet 6s
+ * state-confirmation beat as the staging tile's Promote button and the
+ * audit tile's newest-entry avatar, keeping the family's shared rhythm.
+ * Under prefers-reduced-motion the pulse is removed and the tag sits
+ * static.
+ */
+
+.approvedPulse {
+ animation: technical-approved-pulse 6s ease-out infinite;
+}
+
+@keyframes technical-approved-pulse {
+ 0%,
+ 20% {
+ box-shadow: 0 0 0 0 color-mix(in srgb, var(--text-secondary) 30%, transparent);
+ }
+ 36% {
+ box-shadow: 0 0 0 5px color-mix(in srgb, var(--text-secondary) 0%, transparent);
+ }
+ 100% {
+ box-shadow: 0 0 0 0 color-mix(in srgb, var(--text-secondary) 0%, transparent);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .approvedPulse {
+ animation: none;
+ }
+}
diff --git a/apps/sim/app/(landing)/enterprise/components/feature-graphics/technical-teams-graphic.tsx b/apps/sim/app/(landing)/enterprise/components/feature-graphics/technical-teams-graphic.tsx
new file mode 100644
index 00000000000..9ef2691a21a
--- /dev/null
+++ b/apps/sim/app/(landing)/enterprise/components/feature-graphics/technical-teams-graphic.tsx
@@ -0,0 +1,120 @@
+import { ChipTag, cn } from '@sim/emcn'
+import { CircleCheck } from '@sim/emcn/icons'
+import Image from 'next/image'
+import { FeatureGraphicShell } from '@/app/(landing)/enterprise/components/feature-graphics/feature-graphic-shell'
+import styles from '@/app/(landing)/enterprise/components/feature-graphics/technical-teams-graphic.module.css'
+
+interface DiffLine {
+ /** Gutter marker — space for context, `-` for removed, `+` for added. */
+ marker: ' ' | '-' | '+'
+ /** The code content after the marker. */
+ code: string
+}
+
+/**
+ * A one-word behavior change to the same `Support agent` config the
+ * build tile types out — the surrounding config lines ground the excerpt
+ * as real code while the `-`/`+` pair stays the focal point.
+ */
+const DIFF_LINES: readonly DiffLine[] = [
+ { marker: ' ', code: " name: 'Support agent'," },
+ { marker: ' ', code: ' instructions:' },
+ { marker: ' ', code: " 'Answer customer questions'," },
+ { marker: ' ', code: ' tools: [zendesk, slack],' },
+ { marker: '-', code: " onError: 'retry'," },
+ { marker: '+', code: " onError: 'escalate'," },
+ { marker: ' ', code: '})' },
+] as const
+
+/** Per-marker ink treatments: added strongest, removed and context quiet. */
+const MARKER_TONES: Record = {
+ ' ': 'text-[var(--text-muted)]',
+ '-': 'text-[var(--text-muted)] opacity-70',
+ '+': 'text-[var(--text-primary)]',
+}
+
+/**
+ * Technical collaboration told as a distilled code-review vignette, in
+ * the frameless composition its row-mates share (the IT tile's policy
+ * ledger, the operations tile's handoff): no window chrome and no header
+ * — the tile leads straight with the change under review, a seven-line
+ * mono excerpt of the Support-agent config sitting bare on the tile.
+ * Context and removed lines read in quiet `--text-muted`, the single
+ * added line carrying `--text-primary` ink so the edit is the first
+ * thing read. The review's
+ * verdict is the tile's one highlight: a white card in the audit tile's
+ * exact chrome (`--white` fill, 1px `--border-1` hairline, `rounded-xl`,
+ * `shadow-sm`) pairing the reviewer — gradient avatar (shared with the
+ * access, audit, and staging tiles), name, and an "Approved these
+ * changes" attribution line — with an `Approved` tag that carries the
+ * tile's only motion, the family's shared quiet 6s ring pulse (from
+ * `technical-teams-graphic.module.css`, removed under
+ * `prefers-reduced-motion`). A closing hairline-ruled row counts the
+ * resolved review threads, keeping the together-ness of the claim
+ * without a second emphasis.
+ *
+ * The avatar asset is a grey radial gradient on a black square, so it
+ * sits in a `rounded-full overflow-hidden` clip with a slight scale-up
+ * to crop the black canvas past the circle's edge.
+ *
+ * The feature tile's visual slot bleeds `2rem` right (`1.5rem` under
+ * `max-lg`) but not left, so this centered vignette adds matching right
+ * padding to land on the tile's visible center instead of the bled
+ * slot's center. The column is fluid (`w-full max-w-[312px]`) so it
+ * never exceeds the compensated slot at narrow tile widths — diff lines
+ * and the reviewer's attribution truncate instead of clipping.
+ */
+export function TechnicalTeamsGraphic() {
+ return (
+
+
+
+
+ {DIFF_LINES.map((line) => (
+
+ {line.marker} {line.code}
+
+ ))}
+
+
+
+
+
+
+
+
+ Jordan Lee
+
+
+ Approved these changes
+
+
+
+ Approved
+
+
+
+
+
+
+ Review threads resolved
+
+ 2 of 2
+
+
+
+
+ )
+}
diff --git a/apps/sim/app/(landing)/enterprise/enterprise.tsx b/apps/sim/app/(landing)/enterprise/enterprise.tsx
index df62e6e8d78..3e1d3d53e1d 100644
--- a/apps/sim/app/(landing)/enterprise/enterprise.tsx
+++ b/apps/sim/app/(landing)/enterprise/enterprise.tsx
@@ -1,4 +1,7 @@
-import { ChipLink, cn } from '@sim/emcn'
+import { cn } from '@sim/emcn'
+import Image from 'next/image'
+import { Cta } from '@/app/(landing)/components/cta/cta'
+import { LANDING_CONTENT_WIDTH, LANDING_GUTTER } from '@/app/(landing)/components/landing-layout'
import {
SolutionsCardRow,
SolutionsHero,
@@ -7,58 +10,96 @@ import {
} from '@/app/(landing)/components/solutions-page/components'
import { SOLUTIONS_SPACING } from '@/app/(landing)/components/solutions-page/constants'
import type { SolutionsPageConfig } from '@/app/(landing)/components/solutions-page/types'
+import { EnterprisePlatformLoop } from '@/app/(landing)/enterprise/components/enterprise-platform-loop'
+import {
+ AccessControlGraphic,
+ AuditTrailGraphic,
+ BuildMethodsGraphic,
+ DeployGraphic,
+ ItPlatformTeamsGraphic,
+ LifecycleGraphic,
+ OperationsTeamsGraphic,
+ RollbackGraphic,
+ RunMonitoringGraphic,
+ StagingGraphic,
+ StandardsGraphic,
+ TechnicalTeamsGraphic,
+} from '@/app/(landing)/enterprise/components/feature-graphics'
/**
* Enterprise landing page (`/enterprise`) - the flagship surface for teams
* evaluating Sim as their enterprise AI agent platform.
*
* Structurally it mirrors {@link SolutionsPage} (hero → logos → card rows) but
- * composes its own `` so it can append a trailing CTA band that
+ * composes its own `` so it can append the shared homepage CTA that
* `SolutionsPage` does not render. The shared `SOLUTIONS_SPACING` constants own
- * the gutter and inter-section rhythm, and the shared solutions components own
- * the hero, logos, and card-row chrome - so this file is pure content plus the
- * closing CTA.
+ * the enterprise content gutter and inter-section rhythm, while the homepage
+ * {@link Cta} owns the closing conversion band.
*
* The strict heading outline is H1 (hero) → H2 (each card row + the CTA) → H3
- * (each card), never skipped. Server Component; the only interactive leaves are
- * the shared `HeroCta` (inside `SolutionsHero`) and the CTA's `ChipLink`s.
+ * (each card), never skipped. Server Component; the interactive leaves live in
+ * the shared landing components.
*/
const ENTERPRISE_CONFIG: SolutionsPageConfig = {
module: 'Enterprise',
path: '/enterprise',
hero: {
- heading: 'The Enterprise AI Agent Platform for Teams',
- description:
- 'Sim is the enterprise AI workspace where teams build, deploy, and govern AI agents. Connect 1,000+ integrations and every major LLM to run agents that automate real work, with security, approvals, and audit trails built into the enterprise AI agent platform.',
+ heading: 'The AI Agent Platform for Enterprise Teams',
+ description: 'Build, deploy, and govern enterprise AI agents in one workspace.',
summary:
'Sim is the open-source enterprise AI agent platform where IT, operations, and technical teams build, deploy, and govern enterprise AI agents in one AI workspace. Connect 1,000+ integrations and every major LLM, with security, role-based access, approvals, observability, versioning, and audit trails for reliable deployment across teams.',
- visual: null,
+ /**
+ * The enterprise architectural backdrop with the enterprise-specific
+ * platform loop framed in front of it: the same white demo window the
+ * homepage hero uses, filled by the {@link EnterprisePlatformLoop} - a
+ * sibling of the homepage `HeroPlatformLoop` that renders the whole
+ * interior live (Brightwave sidebar + the real new-chat home view) and
+ * replays an enterprise prompt. The `variant='home'` hero renders this
+ * into the same `aspect-[1300/720]` media frame the homepage uses.
+ */
+ visual: (
+ <>
+
+
+ >
+ ),
},
rows: [
{
id: 'build',
title: 'Build, Deploy, and Manage Enterprise AI Agents in One Workspace',
subtitle:
- 'Sim gives teams one workspace to build enterprise AI agents, ship them to production, and manage every enterprise AI agent across its lifecycle.',
+ 'Build enterprise AI agents, ship to production, and manage every version from one workspace.',
cta: { label: 'Start building', href: '/signup' },
cards: [
{
title: 'Build visually or with code',
- description:
- 'Sim lets teams build enterprise AI agents in a visual workflow builder, in natural language with Mothership, or with code.',
- visual: null,
+ description: 'Create agents in the visual builder, through chat, or directly in code.',
+ visual: ,
},
{
title: 'Deploy in one click',
description:
- 'Sim deploys an enterprise AI agent to staging or production from the same workspace, with no separate infrastructure to manage.',
- visual: null,
+ 'Move agents from staging to production without managing separate infrastructure.',
+ featureTileTone: 'dark',
+ featureTileDescriptionTone: 'soft',
+ visual: ,
},
{
title: 'Manage the full lifecycle',
- description:
- 'Sim keeps every enterprise AI agent versioned, monitored, and editable, so teams manage changes without rebuilding from scratch.',
- visual: null,
+ description: 'Version, monitor, and edit every agent as your workflows evolve.',
+ visual: ,
},
],
},
@@ -66,53 +107,50 @@ const ENTERPRISE_CONFIG: SolutionsPageConfig = {
id: 'governance',
title: 'Governance and Security for Enterprise AI Agents',
subtitle:
- 'Sim is the enterprise AI agent platform built for security and control, so teams can trust every enterprise AI agent they run in production.',
+ 'Security, approvals, and controls keep enterprise AI agents trusted in production.',
cta: { label: 'See security', href: '/demo' },
cards: [
{
title: 'Control who can do what',
description:
- 'Sim gives administrators role-based access and approvals, so the right people build enterprise AI agents and the right people sign off before they go live.',
- visual: null,
+ 'Set roles and approval paths so the right people build, review, and launch agents.',
+ visual: ,
},
{
title: 'Prove every action',
- description:
- 'Sim logs each agent run block by block, giving teams a complete audit trail of what every enterprise AI agent did and why.',
- visual: null,
+ description: 'Trace every run block by block with a complete audit trail.',
+ visual: ,
},
{
title: 'Meet enterprise standards',
description:
- 'Sim is SOC2 compliant and open source, so security teams can review how the enterprise AI agent platform works before they adopt it.',
- visual: null,
+ 'SOC2 compliance and open source give security teams a clear path to review.',
+ visual: ,
+ featureTileTone: 'dark',
+ featureTileDescriptionTone: 'soft',
},
],
},
{
id: 'deploy',
title: 'Deploy Enterprise Workflow Agents with Confidence',
- subtitle:
- 'Sim ships enterprise workflow agents from staging to production with the observability and versioning teams need to deploy reliably across the organization.',
+ subtitle: 'Stage, observe, and version workflow agents before they reach production.',
cta: { label: 'Explore deployment', href: '/signup' },
cards: [
{
title: 'Stage before you ship',
- description:
- 'Sim runs enterprise workflow agents in staging first, so teams test changes safely before they reach production.',
- visual: null,
+ description: 'Test changes in staging before they affect live workflows.',
+ visual: ,
},
{
title: 'Watch every run',
- description:
- 'Sim gives teams full observability across live logs, run history, and monitoring for every enterprise workflow agent in production.',
- visual: null,
+ description: 'See live logs, run history, and monitoring in one place.',
+ visual: ,
},
{
title: 'Roll back safely',
- description:
- 'Sim versions every workflow, so teams deploy with confidence and revert any enterprise workflow agent in seconds.',
- visual: null,
+ description: 'Version workflows and roll back production agents in seconds.',
+ visual: ,
},
],
},
@@ -120,26 +158,25 @@ const ENTERPRISE_CONFIG: SolutionsPageConfig = {
id: 'teams',
title: 'Built for Enterprise Teams',
subtitle:
- 'Sim is built for the teams that run enterprise AI agents, and the governance, lifecycle management, and collaboration an enterprise AI agent demands.',
+ 'Built for the teams that own enterprise AI agents across IT, operations, and engineering.',
cta: { label: 'Talk to sales', href: '/demo' },
cards: [
{
title: 'IT and platform teams',
- description:
- 'IT teams use Sim to build enterprise AI agents with the access controls, governance, and audit trails their organization requires.',
- visual: null,
+ description: 'Give IT the access controls, governance, and audit trails they need.',
+ visual: ,
},
{
title: 'Operations teams',
- description:
- 'Operations teams use Sim to deploy enterprise AI agents that automate real work across the tools they already run.',
- visual: null,
+ description: 'Automate real work across the tools operations teams already use.',
+ featureTileTone: 'dark',
+ featureTileDescriptionTone: 'soft',
+ visual: ,
},
{
title: 'Technical teams',
- description:
- 'Engineering and technical teams collaborate in Sim to ship, review, and maintain every enterprise AI agent together.',
- visual: null,
+ description: 'Let technical teams ship, review, and maintain agents together.',
+ visual: ,
},
],
},
@@ -152,38 +189,30 @@ export default function EnterprisePage() {
-
-
- {ENTERPRISE_CONFIG.rows.map((row) => (
-
- ))}
+
-
-
- Build enterprise AI agents in Sim
-
-
-
- Get started
-
-
- Contact sales
-
-
-
+
+ {ENTERPRISE_CONFIG.rows.map((row) => (
+
+ ))}
+
+
+
>
)
diff --git a/apps/sim/app/(landing)/landing.tsx b/apps/sim/app/(landing)/landing.tsx
index 60e4eb83b54..5eabaaaeb2c 100644
--- a/apps/sim/app/(landing)/landing.tsx
+++ b/apps/sim/app/(landing)/landing.tsx
@@ -1,3 +1,4 @@
+import { cn } from '@sim/emcn'
import {
Cta,
Features,
@@ -6,6 +7,7 @@ import {
Mothership,
ProductDemo,
} from '@/app/(landing)/components'
+import { LANDING_SECTION_RHYTHM } from '@/app/(landing)/components/landing-layout'
/**
* Landing page root - owns the section order and the `` content region.
@@ -23,7 +25,7 @@ import {
*/
export default function Landing() {
return (
-
+
diff --git a/apps/sim/next.config.ts b/apps/sim/next.config.ts
index 9affd751b0b..c11bcd6cc2c 100644
--- a/apps/sim/next.config.ts
+++ b/apps/sim/next.config.ts
@@ -170,6 +170,9 @@ const nextConfig: NextConfig = {
: []),
'localhost:3000',
'localhost:3001',
+ '127.0.0.1',
+ '127.0.0.1:3011',
+ '127.0.0.1:3012',
],
}),
transpilePackages: [
diff --git a/apps/sim/proxy.ts b/apps/sim/proxy.ts
index 741e30081db..bc6c4942c61 100644
--- a/apps/sim/proxy.ts
+++ b/apps/sim/proxy.ts
@@ -3,7 +3,7 @@ import { getSessionCookie } from 'better-auth/cookies'
import { type NextRequest, NextResponse } from 'next/server'
import { sendToProfound } from './lib/analytics/profound'
import { getEnv } from './lib/core/config/env'
-import { isAuthDisabled, isHosted } from './lib/core/config/env-flags'
+import { isAuthDisabled, isDev, isHosted } from './lib/core/config/env-flags'
import { generateRuntimeCSP } from './lib/core/security/csp'
import { getClientIp } from './lib/core/utils/request'
@@ -138,8 +138,8 @@ function handleRootPathRedirects(
return null
}
- if (!isHosted) {
- // Self-hosted: Always redirect based on session
+ if (!isHosted && !isDev) {
+ // Self-hosted production: Always redirect based on session.
if (hasActiveSession) {
return NextResponse.redirect(new URL('/workspace', request.url))
}
diff --git a/apps/sim/public/landing/enterprise-hero-background.png b/apps/sim/public/landing/enterprise-hero-background.png
new file mode 100644
index 00000000000..c1ca725252f
Binary files /dev/null and b/apps/sim/public/landing/enterprise-hero-background.png differ
diff --git a/apps/sim/public/landing/team-avatar-1.png b/apps/sim/public/landing/team-avatar-1.png
new file mode 100644
index 00000000000..df7fbe02ac2
Binary files /dev/null and b/apps/sim/public/landing/team-avatar-1.png differ
diff --git a/apps/sim/public/landing/team-avatar-2.png b/apps/sim/public/landing/team-avatar-2.png
new file mode 100644
index 00000000000..8861e073e8f
Binary files /dev/null and b/apps/sim/public/landing/team-avatar-2.png differ
diff --git a/apps/sim/public/landing/team-avatar-3.png b/apps/sim/public/landing/team-avatar-3.png
new file mode 100644
index 00000000000..2da21e8d53b
Binary files /dev/null and b/apps/sim/public/landing/team-avatar-3.png differ
diff --git a/skills-lock.json b/skills-lock.json
index b7242b568ab..3c1d35fad0b 100644
--- a/skills-lock.json
+++ b/skills-lock.json
@@ -12,6 +12,12 @@
"sourceType": "github",
"skillPath": "skills/emil-design-eng/SKILL.md",
"computedHash": "8bdf9e4e6de7a4969147bf4828a4ad2c5aacd9fba4b690b250a85e0467ca387d"
+ },
+ "make-interfaces-feel-better": {
+ "source": "jakubkrehel/make-interfaces-feel-better",
+ "sourceType": "github",
+ "skillPath": "skills/make-interfaces-feel-better/SKILL.md",
+ "computedHash": "54b6e221bbe4e2d1a8461c3f9ecabe69daf891f964e9ee5e699c323daa018f2e"
}
}
}