diff --git a/apps/sim/app/(landing)/(pages)/enterprise/page.tsx b/apps/sim/app/(landing)/(pages)/enterprise/page.tsx new file mode 100644 index 00000000000..c5421087517 --- /dev/null +++ b/apps/sim/app/(landing)/(pages)/enterprise/page.tsx @@ -0,0 +1,29 @@ +'use client' + +import NavWrapper from '../../components/nav-wrapper' +import Clients from '../../components/sections/clients' +import CTA from '../../components/sections/cta' +import Hero from '../../components/sections/enterprise/hero' +import Security from '../../components/sections/enterprise/security' +import Showcase from '../../components/sections/enterprise/showcase' +import Footer from '../../components/sections/footer' + +function Enterprise() { + return ( +
+ {}} /> + + + + + + + + + {/* Footer */} +
+ ) +} + +export default Enterprise diff --git a/apps/sim/app/(landing)/(pages)/pricing/page.tsx b/apps/sim/app/(landing)/(pages)/pricing/page.tsx new file mode 100644 index 00000000000..360100e08e0 --- /dev/null +++ b/apps/sim/app/(landing)/(pages)/pricing/page.tsx @@ -0,0 +1,322 @@ +'use client' + +import { CheckIcon } from 'lucide-react' +import NavWrapper from '../../components/nav-wrapper' +import Clients from '../../components/sections/clients' +import CTA from '../../components/sections/cta' +import Footer from '../../components/sections/footer' + +function Pricing() { + return ( +
+ {}} /> + +
+
+

+ Simple pricing that scales{' '} + + with you + +

+

+ Choose the plan that's right for you. +

+
+
+
+
+

+ Free +

+

Free

+
+
    +
  • + + + + + $5 usage included + +
  • +
  • + + + + + Public template access + +
  • +
  • + + + + + Community support + +
  • +
  • + + + + + Limited log retention + +
  • +
  • + + + + + CLI/SDK Access + +
  • +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+

Pro

+

$20/month

+
+
    +
  • + + + + + $5 usage included + +
  • +
  • + + + + + Public template access + +
  • +
  • + + + + + Community support + +
  • +
  • + + + + + Limited log retention + +
  • +
  • + + + + CLI/SDK Access +
  • +
+ +
+
+
+

+ Team +

+

$40/month

+
+
    +
  • + + + + + $5 usage included + +
  • +
  • + + + + + Public template access + +
  • +
  • + + + + + Community support + +
  • +
  • + + + + + Limited log retention + +
  • +
  • + + + + + CLI/SDK Access + +
  • +
+ +
+
+
+

+ Enterprise +

+

Custom

+
+
    +
  • + + + + + $5 usage included + +
  • +
  • + + + + + Public template access + +
  • +
  • + + + + + Community support + +
  • +
  • + + + + + Limited log retention + +
  • +
  • + + + + + CLI/SDK Access + +
  • +
+ +
+
+
+ + + + + + {/* Footer */} +
+ ) +} + +export default Pricing diff --git a/apps/sim/app/(landing)/components/blog-card.tsx b/apps/sim/app/(landing)/components/blog-card.tsx index 0b809dbb6cd..125c300b524 100644 --- a/apps/sim/app/(landing)/components/blog-card.tsx +++ b/apps/sim/app/(landing)/components/blog-card.tsx @@ -35,7 +35,7 @@ export const BlogCard = ({ }: BlogCardProps) => { return ( -
+
{image ? ( - - {stars} + + {stars} ) } diff --git a/apps/sim/app/(landing)/components/hero-block.tsx b/apps/sim/app/(landing)/components/hero-block.tsx deleted file mode 100644 index 4a91292fd4f..00000000000 --- a/apps/sim/app/(landing)/components/hero-block.tsx +++ /dev/null @@ -1,245 +0,0 @@ -// Assuming custom icons exist for Sim specific things, otherwise use Lucide - -import type React from 'react' -import { memo } from 'react' -import { - // For header icon - ChevronDown, - CodeXml, - // For Add Tool button - PlusIcon, -} from 'lucide-react' -import { Handle, type NodeProps, Position } from 'reactflow' -import { AgentIcon, ConnectIcon, SlackIcon, StartIcon } from '@/components/icons' -import { CodeBlock } from '@/components/ui/code-block' -import { cn } from '@/lib/utils' - -// Removed DotPattern import - -// Configuration for the new block types based on the image -const blockConfig = { - start: { - icon: StartIcon, // Assuming a custom StartIcon - color: '#2563eb', // Blue - name: 'Start', - }, - function: { - icon: CodeXml, - color: '#e11d48', // Red - name: 'Function 1', - }, - agent: { - icon: AgentIcon, // Assuming custom AgentIcon - color: '#9333ea', // Purple - name: 'Agent 1', - }, - router: { - icon: ConnectIcon, // Assuming custom ConnectIcon - color: '#16a34a', // Green - name: 'Router 1', - }, - slack: { - icon: SlackIcon, // Assuming custom SlackIcon - color: '#611F69', // Slack-like color (adjust if needed) - name: 'Slack 1', - }, -} - -export const HeroBlock = memo(({ id, data }: NodeProps) => { - const type = data.type as keyof typeof blockConfig - const config = blockConfig[type] || blockConfig.function - const Icon = config.icon - const nodeName = config.name - const iconBgColor = config.color // Get color from config - const _horizontalHandles = true // Default to horizontal handles like in workflow-block - - // Determine if we should show the input handle - // Don't show for start blocks, function1 in hero section, or id=function1 - const showInputHandle = - type !== 'start' && !(type === 'function' && id === 'function1' && data.isHeroSection) - - return ( - // Apply group relative here for handles -
- {/* Don't show input handle for starter blocks or function1 */} - {showInputHandle && ( - - )} - - {/* Use BlockCard, passing Icon, title, and iconBgColor */} - - {/* Render type-specific content as children */} -
- {/* --- Start Block Content --- */} - {type === 'start' && ( - <> -
Start workflow
- -

Run Manually

- -
- - )} - - {/* --- Function Block Content --- */} - {type === 'function' && ( -
- -
- )} - - {/* --- Agent Block Content --- */} - {type === 'agent' && ( -
-
-

Agent

- Enter System Prompt -
-
-

User Prompts

- Enter Context -
-
-
-

Model

- -

GPT-4o

- -
-
-
-

Tools

- - - Add Tools - -
-
-
- )} - - {/* --- Router Block Content --- */} - {type === 'router' && ( -
-
-

Prompt

- Enter Prompt -
-
-

Model

- -

GPT-4o

- -
-
-
- )} - - {/* --- Slack Block Content --- */} - {type === 'slack' && ( -
-
-

Channel

- Enter Slack channel (#general) -
-
-

Message

- -

Enter your alert message

-
-
-
- )} -
-
- - {/* Output Handle - Don't show for slack1 */} - {id !== 'slack1' && ( - - )} -
- ) -}) - -const Container = ({ children, className }: { children: React.ReactNode; className?: string }) => { - return ( -
- {children} -
- ) -} - -// Modify BlockCard to accept and use iconBgColor prop -const BlockCard = ({ - Icon, - iconBgColor, - title, - children, -}: { - Icon: any - iconBgColor: string - title: string - children: React.ReactNode -}) => { - return ( -
-
- {/* Apply background color using inline style */} -
- -
-

{title}

-
-
{children}
-
- ) -} - -HeroBlock.displayName = 'HeroBlock' diff --git a/apps/sim/app/(landing)/components/hero-edge.tsx b/apps/sim/app/(landing)/components/hero-edge.tsx deleted file mode 100644 index 3223638027f..00000000000 --- a/apps/sim/app/(landing)/components/hero-edge.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { BaseEdge, type EdgeProps, getSmoothStepPath } from 'reactflow' - -export const HeroEdge = ({ - id, - sourceX, - sourceY, - targetX, - targetY, - sourcePosition, - targetPosition, - style = {}, - markerEnd, -}: EdgeProps) => { - const isHorizontal = sourcePosition === 'right' || sourcePosition === 'left' - - const [edgePath] = getSmoothStepPath({ - sourceX, - sourceY, - sourcePosition, - targetX, - targetY, - targetPosition, - borderRadius: 8, - offset: isHorizontal ? 30 : 20, - }) - - return ( - - ) -} diff --git a/apps/sim/app/(landing)/components/hero-workflow.tsx b/apps/sim/app/(landing)/components/hero-workflow.tsx deleted file mode 100644 index bb0b785ede9..00000000000 --- a/apps/sim/app/(landing)/components/hero-workflow.tsx +++ /dev/null @@ -1,279 +0,0 @@ -'use client' - -import { useEffect, useMemo } from 'react' -import { motion } from 'framer-motion' -import ReactFlow, { - ConnectionLineType, - type Edge, - type EdgeTypes, - type Node, - type NodeTypes, - Position, - ReactFlowProvider, - useEdgesState, - useNodesState, - useReactFlow, - type Viewport, -} from 'reactflow' -import 'reactflow/dist/style.css' - -import { HeroBlock } from '@/app/(landing)/components/hero-block' -import { HeroEdge } from '@/app/(landing)/components/hero-edge' -import { useWindowSize } from '@/app/(landing)/components/use-window-size' - -const nodeTypes: NodeTypes = { heroBlock: HeroBlock } -const edgeTypes: EdgeTypes = { heroEdge: HeroEdge } - -// Desktop layout -const desktopNodes: Node[] = [ - { - id: 'function1', - type: 'heroBlock', - position: { x: 150, y: 400 }, - data: { type: 'function', isHeroSection: true }, - sourcePosition: Position.Right, - targetPosition: Position.Left, - }, - { - id: 'agent1', - type: 'heroBlock', - position: { x: 600, y: 600 }, - data: { type: 'agent' }, - sourcePosition: Position.Right, - targetPosition: Position.Left, - }, - { - id: 'router1', - type: 'heroBlock', - position: { x: 1050, y: 600 }, - data: { type: 'router' }, - sourcePosition: Position.Right, - targetPosition: Position.Left, - }, - { - id: 'slack1', - type: 'heroBlock', - position: { x: 1500, y: 400 }, - data: { type: 'slack' }, - sourcePosition: Position.Right, - targetPosition: Position.Left, - }, -] - -const desktopEdges: Edge[] = [ - { - id: 'func1-agent1', - source: 'function1', - target: 'agent1', - sourceHandle: 'source', - targetHandle: 'target', - type: 'heroEdge', - animated: true, - style: { stroke: '#404040', strokeWidth: 2, strokeDasharray: '5,5' }, - zIndex: 5, - }, - { - id: 'agent1-router1', - source: 'agent1', - target: 'router1', - sourceHandle: 'source', - targetHandle: 'target', - type: 'heroEdge', - animated: true, - style: { stroke: '#404040', strokeWidth: 2, strokeDasharray: '5,5' }, - zIndex: 5, - }, - { - id: 'router1-slack1', - source: 'router1', - target: 'slack1', - sourceHandle: 'source', - targetHandle: 'target', - type: 'heroEdge', - animated: true, - style: { stroke: '#404040', strokeWidth: 2, strokeDasharray: '5,5' }, - zIndex: 5, - }, -] - -const tabletNodes: Node[] = [ - { - id: 'function1', - type: 'heroBlock', - position: { x: 50, y: 480 }, - data: { type: 'function', isHeroSection: true }, - }, - { id: 'agent1', type: 'heroBlock', position: { x: 300, y: 660 }, data: { type: 'agent' } }, - { id: 'router1', type: 'heroBlock', position: { x: 550, y: 660 }, data: { type: 'router' } }, - { id: 'slack1', type: 'heroBlock', position: { x: 800, y: 480 }, data: { type: 'slack' } }, -].map((n) => ({ ...n, sourcePosition: Position.Right, targetPosition: Position.Left })) - -const tabletEdges = desktopEdges.map((edge) => ({ - ...edge, - sourceHandle: 'source', - targetHandle: 'target', - type: 'heroEdge', -})) - -// Mobile: only the agent node, centered under text -const makeMobileNodes = (w: number, h: number): Node[] => { - const BLOCK_HALF = 100 - return [ - { - id: 'agent1', - type: 'heroBlock', - position: { x: w / 2 - BLOCK_HALF - 180, y: h / 2 }, - data: { type: 'agent' }, - sourcePosition: Position.Right, - targetPosition: Position.Left, - }, - { - id: 'slack1', - type: 'heroBlock', - position: { x: w / 2 - BLOCK_HALF + 180, y: h / 2 + 200 }, - data: { type: 'slack' }, - sourcePosition: Position.Right, - targetPosition: Position.Left, - }, - ] -} - -const mobileEdges: Edge[] = [ - { - id: 'agent1-slack1', - source: 'agent1', - target: 'slack1', - sourceHandle: 'source', - targetHandle: 'target', - type: 'heroEdge', - animated: true, - style: { stroke: '#404040', strokeWidth: 2, strokeDasharray: '5,5' }, - zIndex: 5, - }, -] - -const workflowVariants = { - hidden: { opacity: 0, scale: 0.98 }, - visible: { opacity: 1, scale: 1 }, -} - -export function HeroWorkflow() { - const { width = 0, height = 0 } = useWindowSize() - const isMobile = width < 768 - const isTablet = width >= 768 && width < 1024 - - const [nodes, setNodes, onNodesChange] = useNodesState([]) - const [edges, setEdges, onEdgesChange] = useEdgesState([]) - const { fitView } = useReactFlow() - - // Default viewport to make elements smaller - const defaultViewport: Viewport = useMemo(() => ({ x: 0, y: 0, zoom: 0.8 }), []) - - // Load layout - useEffect(() => { - if (isMobile) { - setNodes(makeMobileNodes(width, height)) - setEdges(mobileEdges) - } else if (isTablet) { - setNodes(tabletNodes) - setEdges(tabletEdges) - } else { - setNodes(desktopNodes) - setEdges(desktopEdges) - } - }, [width, height, isMobile, isTablet, setNodes, setEdges]) - - // Center and scale - useEffect(() => { - if (nodes.length) { - if (isMobile) { - fitView({ padding: 0.2 }) // reduced padding to zoom in more - } else { - fitView({ padding: 0.2 }) // added padding to create more space around elements - } - } - }, [nodes, edges, fitView, isMobile]) - - return ( - - - - - - ) -} - -export default function HeroWorkflowProvider() { - return ( - - - - ) -} diff --git a/apps/sim/app/(landing)/components/magicui/flickering-grid.tsx b/apps/sim/app/(landing)/components/magicui/flickering-grid.tsx new file mode 100644 index 00000000000..15a3e282968 --- /dev/null +++ b/apps/sim/app/(landing)/components/magicui/flickering-grid.tsx @@ -0,0 +1,190 @@ +'use client' + +import type React from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { cn } from '@/lib/utils' + +interface FlickeringGridProps extends React.HTMLAttributes { + squareSize?: number + gridGap?: number + flickerChance?: number + color?: string + width?: number + height?: number + className?: string + maxOpacity?: number +} + +export const FlickeringGrid: React.FC = ({ + squareSize = 4, + gridGap = 6, + flickerChance = 0.3, + color = 'rgb(0, 0, 0)', + width, + height, + className, + maxOpacity = 0.3, + ...props +}) => { + const canvasRef = useRef(null) + const containerRef = useRef(null) + const [isInView, setIsInView] = useState(false) + const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 }) + + const memoizedColor = useMemo(() => { + const toRGBA = (color: string) => { + if (typeof window === 'undefined') { + return `rgba(0, 0, 0,` + } + const canvas = document.createElement('canvas') + canvas.width = canvas.height = 1 + const ctx = canvas.getContext('2d') + if (!ctx) return 'rgba(255, 0, 0,' + ctx.fillStyle = color + ctx.fillRect(0, 0, 1, 1) + const [r, g, b] = Array.from(ctx.getImageData(0, 0, 1, 1).data) + return `rgba(${r}, ${g}, ${b},` + } + return toRGBA(color) + }, [color]) + + const setupCanvas = useCallback( + (canvas: HTMLCanvasElement, width: number, height: number) => { + const dpr = window.devicePixelRatio || 1 + canvas.width = width * dpr + canvas.height = height * dpr + canvas.style.width = `${width}px` + canvas.style.height = `${height}px` + const cols = Math.floor(width / (squareSize + gridGap)) + const rows = Math.floor(height / (squareSize + gridGap)) + + const squares = new Float32Array(cols * rows) + for (let i = 0; i < squares.length; i++) { + squares[i] = Math.random() * maxOpacity + } + + return { cols, rows, squares, dpr } + }, + [squareSize, gridGap, maxOpacity] + ) + + const updateSquares = useCallback( + (squares: Float32Array, deltaTime: number) => { + for (let i = 0; i < squares.length; i++) { + if (Math.random() < flickerChance * deltaTime) { + squares[i] = Math.random() * maxOpacity + } + } + }, + [flickerChance, maxOpacity] + ) + + const drawGrid = useCallback( + ( + ctx: CanvasRenderingContext2D, + width: number, + height: number, + cols: number, + rows: number, + squares: Float32Array, + dpr: number + ) => { + ctx.clearRect(0, 0, width, height) + ctx.fillStyle = 'transparent' + ctx.fillRect(0, 0, width, height) + + for (let i = 0; i < cols; i++) { + for (let j = 0; j < rows; j++) { + const opacity = squares[i * rows + j] + ctx.fillStyle = `${memoizedColor}${opacity})` + ctx.fillRect( + i * (squareSize + gridGap) * dpr, + j * (squareSize + gridGap) * dpr, + squareSize * dpr, + squareSize * dpr + ) + } + } + }, + [memoizedColor, squareSize, gridGap] + ) + + useEffect(() => { + const canvas = canvasRef.current + const container = containerRef.current + if (!canvas || !container) return + + const ctx = canvas.getContext('2d') + if (!ctx) return + + let animationFrameId: number + let gridParams: ReturnType + + const updateCanvasSize = () => { + const newWidth = width || container.clientWidth + const newHeight = height || container.clientHeight + setCanvasSize({ width: newWidth, height: newHeight }) + gridParams = setupCanvas(canvas, newWidth, newHeight) + } + + updateCanvasSize() + + let lastTime = 0 + const animate = (time: number) => { + if (!isInView) return + + const deltaTime = (time - lastTime) / 1000 + lastTime = time + + updateSquares(gridParams.squares, deltaTime) + drawGrid( + ctx, + canvas.width, + canvas.height, + gridParams.cols, + gridParams.rows, + gridParams.squares, + gridParams.dpr + ) + animationFrameId = requestAnimationFrame(animate) + } + + const resizeObserver = new ResizeObserver(() => { + updateCanvasSize() + }) + + resizeObserver.observe(container) + + const intersectionObserver = new IntersectionObserver( + ([entry]) => { + setIsInView(entry.isIntersecting) + }, + { threshold: 0 } + ) + + intersectionObserver.observe(canvas) + + if (isInView) { + animationFrameId = requestAnimationFrame(animate) + } + + return () => { + cancelAnimationFrame(animationFrameId) + resizeObserver.disconnect() + intersectionObserver.disconnect() + } + }, [setupCanvas, updateSquares, drawGrid, width, height, isInView]) + + return ( +
+ +
+ ) +} diff --git a/apps/sim/app/(landing)/components/magicui/orbiting-circles.tsx b/apps/sim/app/(landing)/components/magicui/orbiting-circles.tsx index 98a05e8bf14..861775faa61 100644 --- a/apps/sim/app/(landing)/components/magicui/orbiting-circles.tsx +++ b/apps/sim/app/(landing)/components/magicui/orbiting-circles.tsx @@ -31,9 +31,9 @@ export function OrbitingCircles({ - + )} {React.Children.map(children, (child, index) => { diff --git a/apps/sim/app/(landing)/components/nav-client.tsx b/apps/sim/app/(landing)/components/nav-client.tsx index 305f5ad8725..23212fd75b2 100644 --- a/apps/sim/app/(landing)/components/nav-client.tsx +++ b/apps/sim/app/(landing)/components/nav-client.tsx @@ -1,7 +1,6 @@ 'use client' import { useEffect, useState } from 'react' -import { AnimatePresence, motion } from 'framer-motion' import { Menu } from 'lucide-react' import Image from 'next/image' import Link from 'next/link' @@ -15,58 +14,8 @@ import { SheetTitle, SheetTrigger, } from '@/components/ui/sheet' -import { useBrandConfig } from '@/lib/branding/branding' import { usePrefetchOnHover } from '@/app/(landing)/utils/prefetch' -// --- Framer Motion Variants --- -const desktopNavContainerVariants = { - hidden: { opacity: 0, y: -10 }, - visible: { - opacity: 1, - y: 0, - transition: { - delay: 0.2, - duration: 0.3, - }, - }, -} - -const mobileSheetContainerVariants = { - hidden: { x: '100%' }, - visible: { - x: 0, - transition: { duration: 0.3 }, - }, - exit: { - x: '100%', - transition: { duration: 0.2 }, - }, -} - -const mobileNavItemsContainerVariants = { - hidden: { opacity: 0 }, - visible: { opacity: 1 }, -} - -const mobileNavItemVariants = { - hidden: { opacity: 0, x: 20 }, - visible: { - opacity: 1, - x: 0, - transition: { duration: 0.3 }, - }, -} - -const mobileButtonVariants = { - hidden: { opacity: 0, y: 10 }, - visible: { - opacity: 1, - y: 0, - transition: { duration: 0.3 }, - }, -} -// --- End Framer Motion Variants --- - // Component for Navigation Links const NavLinks = ({ mobile, @@ -78,25 +27,24 @@ const NavLinks = ({ onContactClick?: () => void }) => { const navigationLinks = [ - // { href: "/", label: "Marketplace" }, - ...(currentPath !== '/' ? [{ href: '/', label: 'Home' }] : []), { href: 'https://docs.sim.ai/', label: 'Docs', external: true }, - // { href: '/', label: 'Blog' }, - { href: '/contributors', label: 'Contributors' }, + { href: '/pricing', label: 'Pricing' }, + { href: '/enterprise', label: 'Enterprise' }, + // { href: '/blog', label: 'Blog' }, ] const handleContributorsHover = usePrefetchOnHover() // Common CSS class for navigation items - const navItemClass = `text-white/60 hover:text-white/100 text-base ${ - mobile ? 'p-2.5 text-lg font-medium text-left' : 'p-1.5' - } rounded-md transition-colors duration-200 block md:inline-block` + const navItemClass = `text-muted-foreground hover:text-foreground text-sm font-medium ${ + mobile ? 'p-2.5 text-sm text-left' : 'px-3 py-2' + } rounded-lg transition-colors duration-200 block md:inline-block` return ( <> {navigationLinks.map((link) => { const linkElement = ( - +
{link.label} - +
) - // Wrap the motion.div with SheetClose if mobile + // Wrap the div with SheetClose if mobile return mobile ? ( {linkElement} @@ -117,34 +65,6 @@ const NavLinks = ({ linkElement ) })} - - {/* Enterprise button with the same action as contact */} - {onContactClick && - (mobile ? ( - - - - Enterprise - - - - ) : ( - - - Enterprise - - - ))} ) } @@ -166,7 +86,6 @@ export default function NavClient({ const [isMobile, setIsMobile] = useState(initialIsMobile ?? false) const [isSheetOpen, setIsSheetOpen] = useState(false) const _router = useRouter() - const brand = useBrandConfig() useEffect(() => { setMounted(true) @@ -181,10 +100,10 @@ export default function NavClient({ // until we've measured the viewport if (!mounted) { return ( -