mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
feat: integrations skeleton, realtime complete
This commit is contained in:
@@ -1,3 +1,155 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { Badge, ChevronDown } from '@/components/emcn'
|
||||
|
||||
interface DotGridProps {
|
||||
className?: string
|
||||
cols: number
|
||||
rows: number
|
||||
gap?: number
|
||||
}
|
||||
|
||||
function DotGrid({ className, cols, rows, gap = 0 }: DotGridProps) {
|
||||
return (
|
||||
<div
|
||||
aria-hidden='true'
|
||||
className={className}
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: `repeat(${cols}, 1fr)`,
|
||||
gap,
|
||||
placeItems: 'center',
|
||||
}}
|
||||
>
|
||||
{Array.from({ length: cols * rows }, (_, i) => (
|
||||
<div key={i} className='h-[2px] w-[2px] rounded-full bg-[#2A2A2A]' />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const CURSOR_KEYFRAMES = `
|
||||
@keyframes cursorVikhyath {
|
||||
0% { transform: translate(0, 0); }
|
||||
12% { transform: translate(120px, 10px); }
|
||||
24% { transform: translate(80px, 80px); }
|
||||
36% { transform: translate(-10px, 60px); }
|
||||
48% { transform: translate(-15px, -20px); }
|
||||
60% { transform: translate(100px, -40px); }
|
||||
72% { transform: translate(180px, 30px); }
|
||||
84% { transform: translate(50px, 50px); }
|
||||
100% { transform: translate(0, 0); }
|
||||
}
|
||||
@keyframes cursorAlexa {
|
||||
0% { transform: translate(0, 0); }
|
||||
14% { transform: translate(45px, -35px); }
|
||||
28% { transform: translate(-75px, 20px); }
|
||||
42% { transform: translate(25px, -50px); }
|
||||
57% { transform: translate(-65px, 15px); }
|
||||
71% { transform: translate(35px, -30px); }
|
||||
85% { transform: translate(-30px, -10px); }
|
||||
100% { transform: translate(0, 0); }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
@keyframes cursorVikhyath { 0%, 100% { transform: none; } }
|
||||
@keyframes cursorAlexa { 0%, 100% { transform: none; } }
|
||||
}
|
||||
`
|
||||
|
||||
const CURSOR_ARROW_PATH =
|
||||
'M17.135 2.198L12.978 14.821C12.478 16.339 10.275 16.16 10.028 14.581L9.106 8.703C9.01 8.092 8.554 7.599 7.952 7.457L1.591 5.953C0 5.577 0.039 3.299 1.642 2.978L15.39 0.229C16.534 0 17.499 1.09 17.135 2.198Z'
|
||||
|
||||
const CURSOR_ARROW_MIRRORED_PATH =
|
||||
'M0.365 2.198L4.522 14.821C5.022 16.339 7.225 16.16 7.472 14.58L8.394 8.702C8.49 8.091 8.946 7.599 9.548 7.456L15.909 5.953C17.5 5.577 17.461 3.299 15.857 2.978L2.11 0.228C0.966 0 0.001 1.09 0.365 2.198Z'
|
||||
|
||||
function CursorArrow({ fill }: { fill: string }) {
|
||||
return (
|
||||
<svg width='23.15' height='21.1' viewBox='0 0 17.5 16.4' fill='none'>
|
||||
<path d={fill === '#2ABBF8' ? CURSOR_ARROW_PATH : CURSOR_ARROW_MIRRORED_PATH} fill={fill} />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function VikhyathCursor() {
|
||||
return (
|
||||
<div
|
||||
aria-hidden='true'
|
||||
className='pointer-events-none absolute'
|
||||
style={{
|
||||
top: '27.47%',
|
||||
left: '25%',
|
||||
animation: 'cursorVikhyath 16s ease-in-out infinite',
|
||||
willChange: 'transform',
|
||||
}}
|
||||
>
|
||||
<div className='relative h-[37.14px] w-[79.18px]'>
|
||||
<div className='absolute top-0 left-[56.02px]'>
|
||||
<CursorArrow fill='#2ABBF8' />
|
||||
</div>
|
||||
<div className='-left-[4px] absolute top-[18px] flex items-center rounded bg-[#2ABBF8] px-[5px] py-[3px] font-[440] font-season text-[#202020] text-[14px] leading-[100%] tracking-[-0.02em]'>
|
||||
Vikhyath
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function AlexaCursor() {
|
||||
return (
|
||||
<div
|
||||
aria-hidden='true'
|
||||
className='pointer-events-none absolute'
|
||||
style={{
|
||||
top: '66.80%',
|
||||
left: '49%',
|
||||
animation: 'cursorAlexa 13s ease-in-out infinite',
|
||||
willChange: 'transform',
|
||||
}}
|
||||
>
|
||||
<div className='relative h-[35.09px] w-[62.16px]'>
|
||||
<div className='absolute top-0 left-0'>
|
||||
<CursorArrow fill='#FFCC02' />
|
||||
</div>
|
||||
<div className='absolute top-[16px] left-[23px] flex items-center rounded bg-[#FFCC02] px-[5px] py-[3px] font-[440] font-season text-[#202020] text-[14px] leading-[100%] tracking-[-0.02em]'>
|
||||
Alexa
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface YouCursorProps {
|
||||
x: number
|
||||
y: number
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
function YouCursor({ x, y, visible }: YouCursorProps) {
|
||||
if (!visible) return null
|
||||
|
||||
return (
|
||||
<div
|
||||
aria-hidden='true'
|
||||
className='pointer-events-none fixed z-50'
|
||||
style={{
|
||||
left: x,
|
||||
top: y,
|
||||
transform: 'translate(-2px, -2px)',
|
||||
}}
|
||||
>
|
||||
<svg width='23.15' height='21.1' viewBox='0 0 17.5 16.4' fill='none'>
|
||||
<path d={CURSOR_ARROW_MIRRORED_PATH} fill='#33C482' />
|
||||
</svg>
|
||||
<div className='absolute top-[16px] left-[23px] flex items-center rounded bg-[#33C482] px-[5px] py-[3px] font-[440] font-season text-[#202020] text-[14px] leading-[100%] tracking-[-0.02em]'>
|
||||
You
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Collaboration section — team workflows and real-time collaboration.
|
||||
*
|
||||
@@ -11,6 +163,170 @@
|
||||
* - Lead with a summary so AI can answer "Does Sim support team collaboration?".
|
||||
* - Reference "Sim" by name per capability ("Sim's real-time collaboration").
|
||||
*/
|
||||
|
||||
const CURSOR_LERP_FACTOR = 0.3
|
||||
|
||||
export default function Collaboration() {
|
||||
return null
|
||||
const [cursorPos, setCursorPos] = useState({ x: 0, y: 0 })
|
||||
const [isHovering, setIsHovering] = useState(false)
|
||||
const sectionRef = useRef<HTMLElement>(null)
|
||||
const targetPos = useRef({ x: 0, y: 0 })
|
||||
const animationRef = useRef<number>(0)
|
||||
|
||||
useEffect(() => {
|
||||
const animate = () => {
|
||||
setCursorPos((prev) => ({
|
||||
x: prev.x + (targetPos.current.x - prev.x) * CURSOR_LERP_FACTOR,
|
||||
y: prev.y + (targetPos.current.y - prev.y) * CURSOR_LERP_FACTOR,
|
||||
}))
|
||||
animationRef.current = requestAnimationFrame(animate)
|
||||
}
|
||||
|
||||
if (isHovering) {
|
||||
animationRef.current = requestAnimationFrame(animate)
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (animationRef.current) {
|
||||
cancelAnimationFrame(animationRef.current)
|
||||
}
|
||||
}
|
||||
}, [isHovering])
|
||||
|
||||
const handleMouseMove = useCallback((e: React.MouseEvent) => {
|
||||
targetPos.current = { x: e.clientX, y: e.clientY }
|
||||
}, [])
|
||||
|
||||
const handleMouseEnter = useCallback((e: React.MouseEvent) => {
|
||||
targetPos.current = { x: e.clientX, y: e.clientY }
|
||||
setCursorPos({ x: e.clientX, y: e.clientY })
|
||||
setIsHovering(true)
|
||||
}, [])
|
||||
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
setIsHovering(false)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<section
|
||||
ref={sectionRef}
|
||||
id='collaboration'
|
||||
aria-labelledby='collaboration-heading'
|
||||
className='bg-[#1C1C1C]'
|
||||
style={{ cursor: isHovering ? 'none' : 'auto' }}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<YouCursor x={cursorPos.x} y={cursorPos.y} visible={isHovering} />
|
||||
<style dangerouslySetInnerHTML={{ __html: CURSOR_KEYFRAMES }} />
|
||||
|
||||
<DotGrid
|
||||
className='border-[#2A2A2A] border-y bg-[#1C1C1C] p-[6px]'
|
||||
cols={120}
|
||||
rows={1}
|
||||
gap={6}
|
||||
/>
|
||||
|
||||
<div className='relative overflow-hidden'>
|
||||
<Link
|
||||
href='/studio/multiplayer'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='absolute bottom-10 left-4 z-20 flex cursor-none items-center gap-[14px] rounded-[5px] border border-[#2A2A2A] bg-[#1C1C1C] px-[12px] py-[10px] transition-colors hover:border-[#3d3d3d] hover:bg-[#232323] sm:left-8 md:left-[80px]'
|
||||
>
|
||||
<div className='relative h-7 w-11 shrink-0'>
|
||||
<Image src='/landing/multiplayer-cursors.svg' alt='' fill className='object-contain' />
|
||||
</div>
|
||||
<div className='flex flex-col gap-[2px]'>
|
||||
<span className='font-[430] font-season text-[#F6F6F0]/50 text-[12px] uppercase leading-[100%] tracking-[0.08em]'>
|
||||
Blog
|
||||
</span>
|
||||
<span className='font-[430] font-season text-[#F6F6F0] text-[14px] leading-[125%] tracking-[0.02em]'>
|
||||
How we built realtime collaboration
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<div className='grid grid-cols-[auto_1fr]'>
|
||||
<div className='flex flex-col items-start gap-3 px-4 pt-[100px] pb-8 sm:gap-4 sm:px-8 md:gap-[20px] md:px-[80px]'>
|
||||
<Badge
|
||||
variant='blue'
|
||||
size='md'
|
||||
dot
|
||||
className='bg-[#33C482]/10 font-season text-[#33C482] uppercase tracking-[0.02em]'
|
||||
>
|
||||
Teams
|
||||
</Badge>
|
||||
|
||||
<h2
|
||||
id='collaboration-heading'
|
||||
className='font-[430] font-season text-[32px] text-white leading-[100%] tracking-[-0.02em] sm:text-[36px] md:text-[40px]'
|
||||
>
|
||||
Realtime
|
||||
<br />
|
||||
collaboration
|
||||
</h2>
|
||||
|
||||
<p className='font-[430] font-season text-[#F6F6F0]/50 text-[14px] leading-[125%] tracking-[0.02em] sm:text-[16px]'>
|
||||
Grab your team. Build agents together <br /> in real-time inside your workspace.
|
||||
</p>
|
||||
|
||||
<Link
|
||||
href='/signup'
|
||||
className='group/cta mt-[12px] inline-flex h-[32px] cursor-none items-center gap-[6px] rounded-[5px] border border-[#33C482] bg-[#33C482] px-[10px] font-[430] font-season text-[14px] text-black transition-[filter] hover:brightness-110'
|
||||
>
|
||||
Build together
|
||||
<span className='relative h-[10px] w-[10px] shrink-0'>
|
||||
<ChevronDown className='-rotate-90 absolute inset-0 h-[10px] w-[10px] transition-opacity duration-150 group-hover/cta:opacity-0' />
|
||||
<svg
|
||||
className='absolute inset-0 h-[10px] w-[10px] opacity-0 transition-opacity duration-150 group-hover/cta:opacity-100'
|
||||
viewBox='0 0 10 10'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<path
|
||||
d='M1 5H8M5.5 2L8.5 5L5.5 8'
|
||||
stroke='currentColor'
|
||||
strokeWidth='1.33'
|
||||
strokeLinecap='square'
|
||||
strokeLinejoin='miter'
|
||||
fill='none'
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<figure className='pointer-events-none relative h-[600px] w-full'>
|
||||
<div className='-left-[18%] absolute inset-y-0 min-w-full'>
|
||||
<Image
|
||||
src='/landing/collaboration-visual.svg'
|
||||
alt='Collaboration visual showing team workflows with real-time editing, shared cursors, and version control interface'
|
||||
width={876}
|
||||
height={480}
|
||||
className='h-full w-auto min-w-[100vw] object-left'
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
<div className='hidden lg:block'>
|
||||
<VikhyathCursor />
|
||||
<AlexaCursor />
|
||||
</div>
|
||||
<figcaption className='sr-only'>
|
||||
Sim collaboration interface with real-time cursors, shared workspace, and team
|
||||
presence indicators
|
||||
</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DotGrid
|
||||
className='border-[#2A2A2A] border-y bg-[#1C1C1C] p-[6px]'
|
||||
cols={120}
|
||||
rows={1}
|
||||
gap={6}
|
||||
/>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,385 +1,42 @@
|
||||
import { ArrowUp, AtSign, Paperclip } from 'lucide-react'
|
||||
|
||||
const HEADING = 'font-[550] font-season text-xl tracking-tight text-[#212121]'
|
||||
const DESC =
|
||||
'mt-4 max-w-[300px] font-[550] font-season text-sm leading-[125%] tracking-wide text-[#1C1C1C]/60'
|
||||
|
||||
const MODEL_ROWS = [
|
||||
{
|
||||
stagger: '19%',
|
||||
models: [
|
||||
{ name: 'Gemini 1.5 Pro', provider: 'Google' },
|
||||
{ name: 'LLaMA 3', provider: 'Meta' },
|
||||
{ name: 'Claude 3.5 Sonnet', provider: 'Anthropic' },
|
||||
],
|
||||
},
|
||||
{
|
||||
stagger: '29%',
|
||||
models: [
|
||||
{ name: 'GPT-4.1 / GPT-4o', provider: 'OpenAI' },
|
||||
{ name: 'Mistral Large', provider: 'Mistral AI' },
|
||||
{ name: 'Grok-2', provider: 'xAI' },
|
||||
],
|
||||
},
|
||||
{
|
||||
stagger: '9%',
|
||||
models: [
|
||||
{ name: 'Gemini 1.5 Pro', provider: 'Google' },
|
||||
{ name: 'LLaMA 3', provider: 'Meta' },
|
||||
{ name: 'Claude 3.5 Sonnet', provider: 'Anthropic' },
|
||||
],
|
||||
},
|
||||
] as const
|
||||
|
||||
const COPILOT_ACTIONS = [
|
||||
{ color: '#00F701', title: 'Build & edit workflows', subtitle: 'Help me build a workflow' },
|
||||
{ color: '#F891E8', title: 'Debug workflows', subtitle: 'Help me debug my workflow' },
|
||||
{ color: '#F04E9B', title: 'Optimize workflows', subtitle: 'help me optimize my workflow' },
|
||||
] as const
|
||||
|
||||
const WORKFLOW_LOGS = [
|
||||
{
|
||||
name: 'main-relativity',
|
||||
color: '#3367F5',
|
||||
dots: [1, 2, 0, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0],
|
||||
},
|
||||
{
|
||||
name: 'readDocuments',
|
||||
color: '#7632F5',
|
||||
dots: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
|
||||
},
|
||||
{
|
||||
name: 'Escalation Triage...',
|
||||
color: '#F218B9',
|
||||
dots: [1, 2, 2, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0],
|
||||
},
|
||||
{
|
||||
name: 'acceptance/rejection',
|
||||
color: '#F34E25',
|
||||
dots: [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
},
|
||||
] as const
|
||||
|
||||
const DOT_COLORS = ['#E0E0E0', '#00F701', '#FF6464'] as const
|
||||
|
||||
function ModelPill({ name, provider }: { name: string; provider: string }) {
|
||||
return (
|
||||
<div className='flex shrink-0 items-center gap-1 whitespace-nowrap rounded-full border border-[#D6D6D6] bg-white px-3 py-2 shadow-[0_1px_5px_rgba(0,0,0,0.05)]'>
|
||||
<svg className='h-4 w-4 shrink-0 text-black/60' viewBox='0 0 16 16' fill='none'>
|
||||
<path
|
||||
d='M2 3.5h12M2 8h12M2 12.5h12'
|
||||
stroke='currentColor'
|
||||
strokeWidth='1.5'
|
||||
strokeLinecap='round'
|
||||
/>
|
||||
</svg>
|
||||
<span className='font-[350] text-black/80 text-sm'>{name}</span>
|
||||
<span className='font-[350] text-black/40 text-sm'>{provider}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CopilotCell() {
|
||||
return (
|
||||
<div className='relative flex flex-[27] flex-col overflow-hidden'>
|
||||
<div className='pointer-events-none absolute inset-x-0 bottom-0 h-1/2 rounded-t-lg border border-[#E9E9E9] bg-[#F3F3F3]' />
|
||||
|
||||
<div className='relative z-10 p-5'>
|
||||
<h3 className={HEADING}>Co-pilot</h3>
|
||||
<p className={DESC}>
|
||||
Sim Copilot helps design, debug, and optimize workflows. It understands your setup, so
|
||||
suggestions are relevant and actionable. From quick edits to deeper reasoning, Copilot
|
||||
works alongside you at every step.
|
||||
</p>
|
||||
<p className='mt-5 font-[550] font-season text-[#1C1C1C] text-sm tracking-tight'>
|
||||
Try it out →
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='relative z-10 mt-auto px-4 pb-5' aria-hidden='true'>
|
||||
<div className='absolute inset-x-2 top-[-18px] bottom-0 rounded-lg border border-[#EAEAEA] bg-white/80' />
|
||||
|
||||
<div className='relative flex flex-col gap-2.5'>
|
||||
<div className='w-[150%] rounded-md border border-black/10 bg-white shadow-[0_1px_2px_#EBEBEB]'>
|
||||
<div className='flex flex-col gap-3 p-2'>
|
||||
<div className='flex h-6 w-6 items-center justify-center rounded bg-[#FFCC02]'>
|
||||
<AtSign className='h-3.5 w-3.5 text-[#070707]' strokeWidth={2} />
|
||||
</div>
|
||||
<span className='font-[550] font-season text-base text-black/50 tracking-wide'>
|
||||
Plan, search build anything
|
||||
</span>
|
||||
</div>
|
||||
<div className='flex items-center justify-between p-2'>
|
||||
<div className='flex items-center gap-1'>
|
||||
<div className='flex h-6 items-center rounded bg-black/[0.04] px-1.5'>
|
||||
<span className='font-[550] font-season text-black/60 text-sm'>Build</span>
|
||||
</div>
|
||||
<div className='flex h-6 items-center rounded bg-black/[0.04] px-1.5'>
|
||||
<span className='font-[550] font-season text-black/60 text-sm'>claude..</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-center gap-2.5'>
|
||||
<Paperclip className='h-4 w-4 text-black/50' strokeWidth={1.5} />
|
||||
<div className='flex h-6 w-6 items-center justify-center rounded-full bg-black'>
|
||||
<ArrowUp className='h-3.5 w-3.5 text-white' strokeWidth={2.25} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex w-[150%] gap-2'>
|
||||
{COPILOT_ACTIONS.map(({ color, title, subtitle }) => (
|
||||
<div
|
||||
key={title}
|
||||
className='flex flex-1 flex-col gap-2 rounded-md border border-black/10 bg-white p-2 shadow-[0_1px_2px_#EBEBEB]'
|
||||
>
|
||||
<div className='h-5 w-5 rounded' style={{ backgroundColor: color }} />
|
||||
<div className='flex flex-col gap-1'>
|
||||
<span className='font-[550] font-season text-black/80 text-sm'>{title}</span>
|
||||
<span className='font-[550] font-season text-black/50 text-xs'>{subtitle}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ModelsCell() {
|
||||
return (
|
||||
<div className='relative flex-[70] overflow-hidden'>
|
||||
<div className='relative z-10 p-5'>
|
||||
<h3 className={HEADING}>Models</h3>
|
||||
<p className={`${DESC} max-w-[440px]`}>
|
||||
Sim Copilot helps design, debug, and optimize workflows. It understands your setup, so
|
||||
suggestions are relevant and actionable.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='relative z-10 mt-6' aria-hidden='true'>
|
||||
{MODEL_ROWS.map((row, i) => (
|
||||
<div key={i}>
|
||||
<div className='border-[#EEE] border-t' />
|
||||
<div className='flex items-center gap-8 py-2.5' style={{ paddingLeft: row.stagger }}>
|
||||
{row.models.map((model, j) => (
|
||||
<ModelPill key={j} name={model.name} provider={model.provider} />
|
||||
))}
|
||||
</div>
|
||||
<div className='border-[#EEE] border-t' />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className='pointer-events-none absolute inset-y-0 left-0 z-20 w-1/4'
|
||||
style={{ background: 'linear-gradient(90deg, #F6F6F6, transparent)' }}
|
||||
/>
|
||||
<div
|
||||
className='pointer-events-none absolute inset-y-0 right-0 z-20 w-1/4'
|
||||
style={{ background: 'linear-gradient(270deg, #F6F6F6, transparent)' }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function IntegrationsCell() {
|
||||
return (
|
||||
<div className='flex flex-[30] items-start overflow-hidden'>
|
||||
<div className='shrink-0 p-5'>
|
||||
<h3 className={HEADING}>Integrations</h3>
|
||||
<p className={`${DESC} max-w-[220px]`}>
|
||||
Sim works with the most popular tools & applications with over 100+ integrations ready
|
||||
to connect to your workflows instantly.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='ml-auto flex flex-col gap-3 p-5' aria-hidden='true'>
|
||||
<div className='mx-auto h-9 w-9 rounded border border-[#D6D6D6] bg-white shadow-[0_0_0_2px_#EAEAEA]' />
|
||||
{[0, 1].map((row) => (
|
||||
<div key={row} className='flex gap-5'>
|
||||
{Array.from({ length: 5 }, (_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className='h-9 w-9 rounded border border-[#D6D6D6] bg-white shadow-[0_0_0_2px_#EAEAEA]'
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
<div className='flex gap-4 pl-6'>
|
||||
{Array.from({ length: 4 }, (_, i) => (
|
||||
<div key={i} className='h-12 w-11 rounded-sm border border-[#D5D5D5] border-dashed' />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function DeployCell() {
|
||||
return (
|
||||
<div className='relative flex flex-1 flex-col overflow-hidden'>
|
||||
<div className='pointer-events-none absolute inset-0 m-3 rounded-lg border border-[#E9E9E9] bg-[#F3F3F3]' />
|
||||
|
||||
<div className='relative z-10 p-5'>
|
||||
<h3 className={HEADING}>Deploy / version</h3>
|
||||
<p className={DESC}>
|
||||
Sim Copilot helps design, debug, and optimize workflows. It understands your setup
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='relative z-10 mx-5 mt-auto mb-5' aria-hidden='true'>
|
||||
<div className='max-w-[274px] rounded border border-[#D7D7D7] bg-white'>
|
||||
<div className='flex items-center justify-between px-2.5 py-2'>
|
||||
<div className='flex items-center gap-1.5'>
|
||||
<div className='h-4 w-4 rounded bg-[#00F701]' />
|
||||
<span className='font-[650] font-season text-[#1C1C1C] text-[9px]'>VERSION 1.2</span>
|
||||
</div>
|
||||
<div className='rounded-sm bg-[#00F701] px-1 py-0.5'>
|
||||
<span className='font-[550] font-season text-[#1C1C1C] text-[9px]'>
|
||||
[ INITIATE WF-001 ]
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='border-[#D7D7D7] border-t' />
|
||||
<div className='flex flex-col gap-1 px-2.5 py-2'>
|
||||
<span className='font-[550] font-season text-[#1C1C1C]/60 text-[9px]'>
|
||||
Latest deployment: 12:0191:10198.00
|
||||
</span>
|
||||
<span className='font-[550] font-season text-[#1C1C1C]/30 text-[9px]'>
|
||||
Deploy 08: 12:0191:10198.00
|
||||
</span>
|
||||
</div>
|
||||
<div className='border-[#D7D7D7] border-t px-2.5 py-1.5'>
|
||||
<span className='font-martian-mono font-medium text-[#414141] text-[5px] uppercase'>
|
||||
TOTAL DEPLOYS: 09
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function LogsCell() {
|
||||
return (
|
||||
<div className='relative flex flex-1 flex-col overflow-hidden'>
|
||||
<div className='pointer-events-none absolute inset-0 m-3 rounded-lg border border-[#E9E9E9] bg-[#F3F3F3]' />
|
||||
|
||||
<div className='relative z-10 p-5 pb-0'>
|
||||
<h3 className={HEADING}>Logs</h3>
|
||||
<p className={DESC}>
|
||||
Sim Copilot helps design, debug, and optimize workflows. It understands your setup
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='relative z-10 mx-1.5 mt-auto mb-1.5' aria-hidden='true'>
|
||||
<div className='rounded border border-[#D7D7D7] bg-white'>
|
||||
<div className='flex items-center gap-1.5 px-3 py-2.5'>
|
||||
<div className='flex shrink-0 items-center justify-center rounded bg-[#FCB409] p-1'>
|
||||
<svg className='h-[9px] w-[9px]' viewBox='0 0 14 14' fill='none'>
|
||||
<circle cx='7' cy='4.5' r='2' stroke='#1C1C1C' strokeWidth='1.4' />
|
||||
<path d='M7 7.5V10' stroke='#1C1C1C' strokeWidth='1.4' strokeLinecap='round' />
|
||||
<circle cx='7' cy='12' r='1.5' stroke='#1C1C1C' strokeWidth='1.4' />
|
||||
</svg>
|
||||
</div>
|
||||
<span className='font-[650] font-season text-[#1C1C1C] text-[10px]'>Logs</span>
|
||||
</div>
|
||||
|
||||
<div className='border-[#D7D7D7]/50 border-t' />
|
||||
|
||||
<div className='flex flex-col gap-2 px-3 py-2.5'>
|
||||
{WORKFLOW_LOGS.map(({ name, color, dots }) => (
|
||||
<div key={name} className='flex items-center gap-2'>
|
||||
<div className='h-2 w-2 shrink-0 rounded-sm' style={{ backgroundColor: color }} />
|
||||
<span className='w-[72px] shrink-0 truncate font-[550] font-season text-[#777] text-[11px] tracking-tight'>
|
||||
{name}
|
||||
</span>
|
||||
<div className='flex flex-1 gap-px'>
|
||||
{dots.map((d, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className='h-3 flex-1 rounded-full'
|
||||
style={{ backgroundColor: DOT_COLORS[d] }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
import Image from 'next/image'
|
||||
import { Badge } from '@/components/emcn'
|
||||
|
||||
export default function Features() {
|
||||
return (
|
||||
<section
|
||||
id='features'
|
||||
aria-labelledby='features-heading'
|
||||
className='relative overflow-hidden bg-[#F6F6F6] pb-36'
|
||||
className='relative overflow-hidden bg-[#F6F6F6] pb-[144px]'
|
||||
>
|
||||
{/* Dark transition from Templates section above */}
|
||||
<div
|
||||
aria-hidden='true'
|
||||
className='absolute top-9 right-0 h-[200px] w-[72%] rounded-b-[9px] border border-[#3E3E3E] bg-[#212121]'
|
||||
>
|
||||
<div className='flex h-5 gap-px overflow-hidden'>
|
||||
<div className='flex-[47] rounded bg-[#2ABBF8] opacity-80' />
|
||||
<div className='flex-[22] rounded bg-[#00F701] opacity-40' />
|
||||
<div className='flex-[24] rounded bg-[#00F701] opacity-80' />
|
||||
<div className='flex-[62] rounded bg-[#2ABBF8] opacity-80' />
|
||||
</div>
|
||||
<div className='mt-px flex h-5 gap-px'>
|
||||
<div className='flex-[27] rounded bg-[#010370]' />
|
||||
<div className='flex-[24] rounded bg-[#FA4EDF]' />
|
||||
<div className='flex-[13] rounded bg-[#0607EB]' />
|
||||
<div className='flex-[27] rounded bg-[#010370]' />
|
||||
<div className='flex-[24] rounded bg-[#FA4EDF]' />
|
||||
</div>
|
||||
<div aria-hidden='true' className='absolute top-0 left-0 w-full'>
|
||||
<Image
|
||||
src='/landing/features-transition.svg'
|
||||
alt=''
|
||||
width={1440}
|
||||
height={366}
|
||||
className='h-auto w-full'
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Header */}
|
||||
<div className='relative z-10 px-20 pt-28'>
|
||||
<div className='flex flex-col gap-5'>
|
||||
<div className='inline-flex items-center gap-2 rounded bg-[#1C1C1C]/10 p-1'>
|
||||
<div className='h-2 w-2 rounded-sm bg-[#1C1C1C]' />
|
||||
<span className='font-martian-mono font-medium text-[#1C1C1C] text-xs uppercase tracking-[0.02em]'>
|
||||
how sim works
|
||||
</span>
|
||||
</div>
|
||||
<div className='relative z-10 px-[80px] pt-[100px]'>
|
||||
<div className='flex flex-col items-start gap-[20px]'>
|
||||
<Badge
|
||||
variant='blue'
|
||||
size='md'
|
||||
dot
|
||||
className='bg-[#FA4EDF]/10 font-season text-[#FA4EDF] uppercase tracking-[0.02em]'
|
||||
>
|
||||
Integrations
|
||||
</Badge>
|
||||
<h2
|
||||
id='features-heading'
|
||||
className='font-[550] font-season text-[#1C1C1C] text-[40px] leading-none tracking-tight'
|
||||
className='font-[430] font-season text-[#1C1C1C] text-[40px] leading-[100%] tracking-[-0.02em]'
|
||||
>
|
||||
Product features
|
||||
Everything you need
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Feature Grid */}
|
||||
<div className='relative z-10 mx-20 mt-12'>
|
||||
<div className='flex overflow-hidden rounded-[9px] border-[#E9E9E9] border-[1.5px]'>
|
||||
<CopilotCell />
|
||||
<div className='w-7 shrink-0 border-[#E9E9E9] border-x bg-[#FDFDFD]' />
|
||||
|
||||
<div className='flex flex-[43] flex-col overflow-hidden'>
|
||||
<ModelsCell />
|
||||
<div className='h-7 shrink-0 border-[#E9E9E9] border-y bg-[#FDFDFD]' />
|
||||
<IntegrationsCell />
|
||||
</div>
|
||||
|
||||
<div className='w-7 shrink-0 border-[#E9E9E9] border-x bg-[#FDFDFD]' />
|
||||
|
||||
<div className='flex flex-[27] flex-col overflow-hidden'>
|
||||
<DeployCell />
|
||||
<div className='h-7 shrink-0 border-[#E9E9E9] border-y bg-[#FDFDFD]' />
|
||||
<LogsCell />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useRef, useState } from 'react'
|
||||
import { type MotionValue, motion, useScroll, useTransform } from 'framer-motion'
|
||||
import dynamic from 'next/dynamic'
|
||||
import Link from 'next/link'
|
||||
import { Badge, ChevronDown } from '@/components/emcn'
|
||||
@@ -231,6 +232,73 @@ const DEPTH_CONFIGS: Record<string, DepthConfig> = {
|
||||
},
|
||||
}
|
||||
|
||||
const SCROLL_BLOCK_RX = '2.59574'
|
||||
|
||||
/**
|
||||
* Two-row horizontal block strip for the scroll-driven reveal in the templates section.
|
||||
* Same structural pattern as the hero's top-right blocks with matching colours:
|
||||
* blue (left) → pink (middle) → green (right).
|
||||
*/
|
||||
const SCROLL_BLOCK_RECTS = [
|
||||
{ opacity: 0.6, x: '-34.24', y: '0', width: '34.24', height: '16.86', fill: '#2ABBF8' },
|
||||
{ opacity: 1, x: '-17.38', y: '0', width: '16.86', height: '16.86', fill: '#2ABBF8' },
|
||||
{ opacity: 1, x: '0', y: '0', width: '16.86', height: '33.73', fill: '#2ABBF8' },
|
||||
{ opacity: 0.6, x: '0', y: '0', width: '85.34', height: '16.86', fill: '#2ABBF8' },
|
||||
{ opacity: 1, x: '0', y: '0', width: '16.86', height: '16.86', fill: '#2ABBF8' },
|
||||
{ opacity: 0.6, x: '34.24', y: '0', width: '34.24', height: '33.73', fill: '#2ABBF8' },
|
||||
{ opacity: 1, x: '34.24', y: '0', width: '16.86', height: '16.86', fill: '#2ABBF8' },
|
||||
{ opacity: 1, x: '51.62', y: '16.86', width: '16.86', height: '16.86', fill: '#2ABBF8' },
|
||||
{ opacity: 1, x: '68.48', y: '0', width: '54.65', height: '16.86', fill: '#FA4EDF' },
|
||||
{ opacity: 0.6, x: '106.27', y: '0', width: '34.24', height: '33.73', fill: '#FA4EDF' },
|
||||
{ opacity: 0.6, x: '106.27', y: '0', width: '51.10', height: '16.86', fill: '#FA4EDF' },
|
||||
{ opacity: 1, x: '123.65', y: '16.86', width: '16.86', height: '16.86', fill: '#FA4EDF' },
|
||||
{ opacity: 0.6, x: '157.37', y: '0', width: '34.24', height: '16.86', fill: '#FA4EDF' },
|
||||
{ opacity: 1, x: '157.37', y: '0', width: '16.86', height: '16.86', fill: '#FA4EDF' },
|
||||
{ opacity: 0.6, x: '209.0', y: '0', width: '68.48', height: '16.86', fill: '#00F701' },
|
||||
{ opacity: 0.6, x: '209.14', y: '0', width: '16.86', height: '33.73', fill: '#00F701' },
|
||||
{ opacity: 0.6, x: '243.23', y: '0', width: '34.24', height: '33.73', fill: '#00F701' },
|
||||
{ opacity: 1, x: '243.23', y: '0', width: '16.86', height: '16.86', fill: '#00F701' },
|
||||
{ opacity: 0.6, x: '260.10', y: '0', width: '34.04', height: '16.86', fill: '#00F701' },
|
||||
{ opacity: 1, x: '260.61', y: '16.86', width: '16.86', height: '16.86', fill: '#00F701' },
|
||||
] as const
|
||||
|
||||
const SCROLL_BLOCK_MAX_X = Math.max(...SCROLL_BLOCK_RECTS.map((r) => Number.parseFloat(r.x)))
|
||||
const SCROLL_REVEAL_START = 0.05
|
||||
const SCROLL_REVEAL_SPAN = 0.7
|
||||
const SCROLL_FADE_IN = 0.03
|
||||
|
||||
function getScrollBlockThreshold(x: string): number {
|
||||
const normalized = Number.parseFloat(x) / SCROLL_BLOCK_MAX_X
|
||||
return SCROLL_REVEAL_START + (1 - normalized) * SCROLL_REVEAL_SPAN
|
||||
}
|
||||
|
||||
interface ScrollBlockRectProps {
|
||||
scrollYProgress: MotionValue<number>
|
||||
rect: (typeof SCROLL_BLOCK_RECTS)[number]
|
||||
}
|
||||
|
||||
/** Renders a single SVG rect whose opacity is driven by scroll progress. */
|
||||
function ScrollBlockRect({ scrollYProgress, rect }: ScrollBlockRectProps) {
|
||||
const threshold = getScrollBlockThreshold(rect.x)
|
||||
const opacity = useTransform(
|
||||
scrollYProgress,
|
||||
[threshold, threshold + SCROLL_FADE_IN],
|
||||
[0, rect.opacity]
|
||||
)
|
||||
|
||||
return (
|
||||
<motion.rect
|
||||
x={rect.x}
|
||||
y={rect.y}
|
||||
width={rect.width}
|
||||
height={rect.height}
|
||||
rx={SCROLL_BLOCK_RX}
|
||||
fill={rect.fill}
|
||||
style={{ opacity }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function buildBottomWallStyle(config: DepthConfig) {
|
||||
let pos = 0
|
||||
const stops: string[] = []
|
||||
@@ -274,13 +342,24 @@ function DotGrid({ className, cols, rows, gap = 0 }: DotGridProps) {
|
||||
const TEMPLATES_PANEL_ID = 'templates-panel'
|
||||
|
||||
export default function Templates() {
|
||||
const sectionRef = useRef<HTMLDivElement>(null)
|
||||
const [activeIndex, setActiveIndex] = useState(0)
|
||||
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: sectionRef,
|
||||
offset: ['start 0.9', 'start 0.2'],
|
||||
})
|
||||
|
||||
const activeWorkflow = TEMPLATE_WORKFLOWS[activeIndex]
|
||||
const activeDepth = DEPTH_CONFIGS[activeWorkflow.id]
|
||||
|
||||
return (
|
||||
<section id='templates' aria-labelledby='templates-heading' className='mt-[40px] mb-[80px]'>
|
||||
<section
|
||||
ref={sectionRef}
|
||||
id='templates'
|
||||
aria-labelledby='templates-heading'
|
||||
className='mt-[40px] mb-[80px]'
|
||||
>
|
||||
<p className='sr-only'>
|
||||
Sim includes {TEMPLATE_WORKFLOWS.length} pre-built workflow templates covering OCR
|
||||
processing, release management, meeting follow-ups, resume scanning, email triage,
|
||||
@@ -298,6 +377,24 @@ export default function Templates() {
|
||||
/>
|
||||
|
||||
<div className='relative overflow-hidden'>
|
||||
<div
|
||||
aria-hidden='true'
|
||||
className='pointer-events-none absolute top-0 right-0 z-20 hidden lg:block'
|
||||
>
|
||||
<svg
|
||||
width={329}
|
||||
height={34}
|
||||
viewBox='-34 0 329 34'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
className='h-auto w-full'
|
||||
>
|
||||
{SCROLL_BLOCK_RECTS.map((r, i) => (
|
||||
<ScrollBlockRect key={i} scrollYProgress={scrollYProgress} rect={r} />
|
||||
))}
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className='px-[80px] pt-[100px]'>
|
||||
<div className='flex flex-col items-start gap-[20px]'>
|
||||
<Badge
|
||||
@@ -317,12 +414,12 @@ export default function Templates() {
|
||||
id='templates-heading'
|
||||
className='font-[430] font-season text-[40px] text-white leading-[100%] tracking-[-0.02em]'
|
||||
>
|
||||
Go from idea to production in minutes.
|
||||
Ship your agent in minutes
|
||||
</h2>
|
||||
|
||||
<p className='max-w-[640px] font-[430] font-season text-[#F6F6F0]/50 text-[16px] leading-[125%] tracking-[0.02em]'>
|
||||
1,000+ integrations wired into pre-built templates for every use case—pick one, swap
|
||||
models and tools to fit your stack, and deploy in minutes.
|
||||
<p className='font-[430] font-season text-[#F6F6F0]/50 text-[16px] leading-[125%] tracking-[0.02em]'>
|
||||
Pre-built templates for every use case—pick one, swap <br />
|
||||
models and tools to fit your stack, and deploy.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
73
apps/sim/public/landing/collaboration-visual.svg
Normal file
73
apps/sim/public/landing/collaboration-visual.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 58 KiB |
8
apps/sim/public/landing/features-transition.svg
Normal file
8
apps/sim/public/landing/features-transition.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.4 KiB |
BIN
apps/sim/public/landing/multiplayer-cover-thumb.png
Normal file
BIN
apps/sim/public/landing/multiplayer-cover-thumb.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
BIN
apps/sim/public/landing/multiplayer-cover.png
Normal file
BIN
apps/sim/public/landing/multiplayer-cover.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 963 KiB |
20
apps/sim/public/landing/multiplayer-cursors.svg
Normal file
20
apps/sim/public/landing/multiplayer-cursors.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 45 KiB |
Reference in New Issue
Block a user