Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
22415eb
chore: fix conflicts
adithyaakrishna Mar 21, 2026
0d2b78b
feat: generate og images
adithyaakrishna Mar 17, 2026
8d66069
chore: support for mermaid diagrams
adithyaakrishna Mar 17, 2026
f9d7954
chore: update code editor
adithyaakrishna Mar 17, 2026
4339bc8
chore: fix conflicts
adithyaakrishna Mar 21, 2026
e31b8fb
chore: fix conflicts
adithyaakrishna Mar 17, 2026
894ae33
chore: fix conflicts
adithyaakrishna Mar 21, 2026
4fd6270
chore: fix conflicts
adithyaakrishna Mar 21, 2026
860a734
chore: fix conflicts
adithyaakrishna Mar 17, 2026
f8fadcc
chore: fix conflicts
adithyaakrishna Mar 21, 2026
8588071
chore: fix conflicts
adithyaakrishna Mar 21, 2026
84bc3eb
chore: fix conflicts
adithyaakrishna Mar 17, 2026
7ff6a3f
chore: fix conflicts
adithyaakrishna Mar 17, 2026
20e0f0e
chore: add cursor
adithyaakrishna Mar 17, 2026
e093c26
chore: fix conflicts
adithyaakrishna Mar 17, 2026
18e04ea
chore: updating caching
adithyaakrishna Mar 17, 2026
d8145b8
chore: fix conflicts
adithyaakrishna Mar 17, 2026
dbeb89f
chore: remove unused component
adithyaakrishna Mar 17, 2026
80ac51f
chore: fix imports and review changes
adithyaakrishna Mar 17, 2026
488a111
chore: fix link issue
adithyaakrishna Mar 17, 2026
4e8b9fa
chore: fix conflicts
adithyaakrishna Mar 21, 2026
b2e855e
chore: add landing animation for templates
adithyaakrishna Mar 17, 2026
7fbdef4
feat: finessing
adithyaakrishna Mar 18, 2026
6367e8b
chore: blur contents on line hover
adithyaakrishna Mar 18, 2026
e8022e4
chore: update changelog
adithyaakrishna Mar 18, 2026
f053b68
chore: other updates
adithyaakrishna Mar 18, 2026
2f055e7
chore: fixed review comments
adithyaakrishna Mar 18, 2026
5f74c5f
chore: fix comments
adithyaakrishna Mar 18, 2026
ab77d8b
chore: fix conflicts
adithyaakrishna Mar 21, 2026
7397c00
chore: handle unmount
adithyaakrishna Mar 18, 2026
1b7f8d3
chore: fix conflicts
adithyaakrishna Mar 21, 2026
a7849b9
chore: fix conflicts
adithyaakrishna Mar 21, 2026
7ad3e12
chore: review fixes
adithyaakrishna Mar 19, 2026
47238aa
chore: update designs
adithyaakrishna Mar 19, 2026
83285d2
chore: fix timeout
adithyaakrishna Mar 19, 2026
6441ea3
chore: update blog content
adithyaakrishna Mar 19, 2026
555a3cf
chore: fix review
adithyaakrishna Mar 19, 2026
bcbbe5e
chore: update contents
adithyaakrishna Mar 21, 2026
6255b3e
chore: finalize layout
adithyaakrishna Mar 21, 2026
88b1c79
chore: lint and color
adithyaakrishna Mar 21, 2026
a7c72a4
chore: fix review changes
adithyaakrishna Mar 21, 2026
3d2d895
chore: lint fixes
adithyaakrishna Mar 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ function WorkspacePreview({ activeTab, isActive }: { activeTab: number; isActive

const [expandedTab, setExpandedTab] = useState<number | null>(null)
const [revealedRows, setRevealedRows] = useState(0)
const prevTabRef = useRef(activeTab)
const wasExpandedRef = useRef(false)
const expandTransitionRef = useRef<'scale' | 'crossfade'>('scale')

const isMothership = activeTab === 0 && isActive
const isExpandTab = activeTab >= 1 && activeTab <= 4 && isActive
Expand Down Expand Up @@ -189,17 +192,37 @@ function WorkspacePreview({ activeTab, isActive }: { activeTab: number; isActive
}, [inView, isMothership])

useEffect(() => {
const prevTab = prevTabRef.current
const wasPrevExpanded = wasExpandedRef.current
prevTabRef.current = activeTab

if (!isExpandTab || !showGrid) {
if (!isExpandTab) {
wasExpandedRef.current = false
setExpandedTab(null)
setRevealedRows(0)
}
return
}
setExpandedTab(null)
setRevealedRows(0)
const timer = setTimeout(() => setExpandedTab(activeTab), 300)
return () => clearTimeout(timer)

const comingFromExpanded =
wasPrevExpanded && prevTab >= 1 && prevTab <= 4 && prevTab !== activeTab

if (comingFromExpanded) {
expandTransitionRef.current = 'crossfade'
wasExpandedRef.current = true
setRevealedRows(EXPAND_ROW_COUNTS[activeTab] ?? 10)
setExpandedTab(activeTab)
} else {
expandTransitionRef.current = 'scale'
setExpandedTab(null)
setRevealedRows(0)
const timer = setTimeout(() => {
wasExpandedRef.current = true
setExpandedTab(activeTab)
}, 300)
return () => clearTimeout(timer)
}
}, [isExpandTab, activeTab, showGrid])

useEffect(() => {
Expand Down Expand Up @@ -279,23 +302,37 @@ function WorkspacePreview({ activeTab, isActive }: { activeTab: number; isActive
</div>
)}

{isExpanded && expandTarget && (
<motion.div
key={expandedTab}
className='absolute inset-0 overflow-hidden border border-[#E5E5E5] bg-white'
initial={{ opacity: 0, scale: 0.15 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.55, ease: [0.4, 0, 0.2, 1] }}
style={{
transformOrigin: `${GRID_PAD + expandTarget.col * GRID_STEP + CARD_SIZE / 2}px ${GRID_PAD + expandTarget.row * GRID_STEP + CARD_SIZE / 2}px`,
}}
>
{expandedTab === 1 && <MockFullTable revealedRows={revealedRows} />}
{expandedTab === 2 && <MockFullFiles />}
{expandedTab === 3 && <MockFullKnowledgeBase revealedRows={revealedRows} />}
{expandedTab === 4 && <MockFullLogs revealedRows={revealedRows} />}
</motion.div>
)}
<AnimatePresence mode='wait'>
{isExpanded && expandTarget && (
<motion.div
key={expandedTab}
className='absolute inset-0 overflow-hidden border border-[#E5E5E5] bg-white'
initial={
expandTransitionRef.current === 'crossfade'
? { opacity: 0 }
: { opacity: 0, scale: 0.15 }
}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0 }}
transition={{
duration: expandTransitionRef.current === 'crossfade' ? 0.3 : 0.55,
ease: [0.4, 0, 0.2, 1],
}}
style={
expandTransitionRef.current === 'scale'
? {
transformOrigin: `${GRID_PAD + expandTarget.col * GRID_STEP + CARD_SIZE / 2}px ${GRID_PAD + expandTarget.row * GRID_STEP + CARD_SIZE / 2}px`,
}
: undefined
}
>
{expandedTab === 1 && <MockFullTable revealedRows={revealedRows} />}
{expandedTab === 2 && <MockFullFiles />}
{expandedTab === 3 && <MockFullKnowledgeBase revealedRows={revealedRows} />}
{expandedTab === 4 && <MockFullLogs revealedRows={revealedRows} />}
</motion.div>
)}
</AnimatePresence>
</div>
)
}
Expand Down
132 changes: 77 additions & 55 deletions apps/sim/app/(home)/components/features/features.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
'use client'

import { useRef, useState } from 'react'
import { type MotionValue, motion, useScroll, useTransform } from 'framer-motion'
import { AnimatePresence, type MotionValue, motion, useScroll, useTransform } from 'framer-motion'
import Image from 'next/image'
import Link from 'next/link'
import { Badge, ChevronDown } from '@/components/emcn'
import { hexToRgba } from '@/lib/core/utils/formatting'
import { FeaturesPreview } from '@/app/(home)/components/features/components/features-preview'

function hexToRgba(hex: string, alpha: number): string {
const r = Number.parseInt(hex.slice(1, 3), 16)
const g = Number.parseInt(hex.slice(3, 5), 16)
const b = Number.parseInt(hex.slice(5, 7), 16)
return `rgba(${r},${g},${b},${alpha})`
}

const FEATURE_TABS = [
{
label: 'Mothership',
Expand Down Expand Up @@ -168,6 +162,8 @@ function DotGrid({
)
}

const INDICATOR_TRANSITION_MS = 300

export default function Features() {
const sectionRef = useRef<HTMLDivElement>(null)
const [activeTab, setActiveTab] = useState(0)
Expand Down Expand Up @@ -259,7 +255,10 @@ export default function Features() {
aria-selected={index === activeTab}
onClick={() => setActiveTab(index)}
className={`relative h-full flex-1 items-center justify-center whitespace-nowrap px-[12px] font-medium font-season text-[#212121] text-[12px] uppercase lg:px-0 lg:text-[14px]${tab.hideOnMobile ? ' hidden lg:flex' : ' flex'}${index > 0 ? ' border-[#E9E9E9] border-l' : ''}`}
style={{ backgroundColor: index === activeTab ? '#FDFDFD' : '#F6F6F6' }}
style={{
backgroundColor: index === activeTab ? '#FDFDFD' : '#F6F6F6',
transition: `background-color ${INDICATOR_TRANSITION_MS}ms ease`,
}}
>
{tab.mobileLabel ? (
<>
Expand All @@ -269,21 +268,25 @@ export default function Features() {
) : (
tab.label
)}
{index === activeTab && (
<div className='absolute right-0 bottom-0 left-0 flex h-[6px]'>
{tab.segments.map(([opacity, width], i) => (
<div
key={i}
className='h-full shrink-0'
style={{
width: `${width}%`,
backgroundColor: tab.color,
opacity,
}}
/>
))}
</div>
)}
<div
className='pointer-events-none absolute right-0 bottom-0 left-0 flex h-[6px]'
style={{
opacity: index === activeTab ? 1 : 0,
transition: `opacity ${INDICATOR_TRANSITION_MS}ms ease`,
}}
>
{tab.segments.map(([segOpacity, width], i) => (
<div
key={i}
className='h-full shrink-0'
style={{
width: `${width}%`,
backgroundColor: tab.color,
opacity: segOpacity,
}}
/>
))}
</div>
</button>
))}
</div>
Expand All @@ -299,38 +302,57 @@ export default function Features() {
</div>

<div className='mt-[32px] flex flex-col gap-[24px] px-[24px] lg:mt-[60px] lg:grid lg:grid-cols-[1fr_2.8fr] lg:gap-[60px] lg:px-[120px]'>
<div className='flex flex-col items-start justify-between gap-[24px] pt-[20px] lg:h-[560px] lg:gap-0'>
<div className='flex flex-col items-start gap-[16px]'>
<h3 className='font-[430] font-season text-[#1C1C1C] text-[24px] leading-[120%] tracking-[-0.02em] lg:text-[28px]'>
{FEATURE_TABS[activeTab].title}
</h3>
<p className='font-[430] font-season text-[#1C1C1C]/50 text-[16px] leading-[150%] tracking-[0.02em] lg:text-[18px]'>
{FEATURE_TABS[activeTab].description}
</p>
</div>
<Link
href='/signup'
className='group/cta inline-flex h-[32px] items-center gap-[6px] rounded-[5px] border border-[#1D1D1D] bg-[#1D1D1D] px-[10px] font-[430] font-season text-[14px] text-white transition-colors hover:border-[#2A2A2A] hover:bg-[#2A2A2A]'
>
{FEATURE_TABS[activeTab].cta}
<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'
<div className='relative flex flex-col items-start justify-between gap-[24px] pt-[20px] lg:h-[560px] lg:gap-0'>
<AnimatePresence mode='wait'>
<motion.div
key={activeTab}
className='flex flex-col items-start gap-[16px]'
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -8 }}
transition={{ duration: 0.25, ease: [0.4, 0, 0.2, 1] }}
>
<h3 className='font-[430] font-season text-[#1C1C1C] text-[24px] leading-[120%] tracking-[-0.02em] lg:text-[28px]'>
{FEATURE_TABS[activeTab].title}
</h3>
<p className='font-[430] font-season text-[#1C1C1C]/50 text-[16px] leading-[150%] tracking-[0.02em] lg:text-[18px]'>
{FEATURE_TABS[activeTab].description}
</p>
</motion.div>
</AnimatePresence>
<AnimatePresence mode='wait'>
<motion.div
key={activeTab}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2, ease: [0.4, 0, 0.2, 1] }}
>
<Link
href='/signup'
className='group/cta inline-flex h-[32px] items-center gap-[6px] rounded-[5px] border border-[#1D1D1D] bg-[#1D1D1D] px-[10px] font-[430] font-season text-[14px] text-white transition-colors hover:border-[#2A2A2A] hover:bg-[#2A2A2A]'
>
<path
d='M1 5H8M5.5 2L8.5 5L5.5 8'
stroke='currentColor'
strokeWidth='1.33'
strokeLinecap='square'
strokeLinejoin='miter'
fill='none'
/>
</svg>
</span>
</Link>
{FEATURE_TABS[activeTab].cta}
<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'
>
<path
d='M1 5H8M5.5 2L8.5 5L5.5 8'
stroke='currentColor'
strokeWidth='1.33'
strokeLinecap='square'
strokeLinejoin='miter'
fill='none'
/>
</svg>
</span>
</Link>
</motion.div>
</AnimatePresence>
</div>

<FeaturesPreview activeTab={activeTab} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export function LandingPreview() {
/>
</motion.div>
<div className='flex min-w-0 flex-1 flex-col py-[8px] pr-[8px] pl-[8px] lg:pl-0'>
<div className='flex flex-1 overflow-hidden rounded-[8px] border border-[#2c2c2c] bg-[#1b1b1b]'>
<div className='flex flex-1 overflow-hidden rounded border border-[#2c2c2c] bg-[#1b1b1b]'>
<div
className={
isWorkflowView
Expand Down
16 changes: 5 additions & 11 deletions apps/sim/app/(home)/components/templates/templates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useRouter } from 'next/navigation'
import { Badge, ChevronDown } from '@/components/emcn'
import { LandingWorkflowSeedStorage } from '@/lib/core/utils/browser-storage'
import { cn } from '@/lib/core/utils/cn'
import { hexToRgba } from '@/lib/core/utils/formatting'
import { TEMPLATE_WORKFLOWS } from '@/app/(home)/components/templates/template-workflows'

const logger = createLogger('LandingTemplates')
Expand All @@ -23,13 +24,6 @@ const LandingPreviewWorkflow = dynamic(
}
)

function hexToRgba(hex: string, alpha: number): string {
const r = Number.parseInt(hex.slice(1, 3), 16)
const g = Number.parseInt(hex.slice(3, 5), 16)
const b = Number.parseInt(hex.slice(5, 7), 16)
return `rgba(${r},${g},${b},${alpha})`
}

const LEFT_WALL_CLIP = 'polygon(0 8px, 100% 0, 100% 100%, 0 100%)'
const BOTTOM_WALL_CLIP = 'polygon(0 0, 100% 0, calc(100% - 8px) 100%, 0 100%)'

Expand Down Expand Up @@ -412,7 +406,7 @@ export default function Templates() {
ref={sectionRef}
id='templates'
aria-labelledby='templates-heading'
className='mt-[40px] mb-[80px]'
className='mt-[40px] mb-[80px] overflow-x-clip'
>
<p className='sr-only'>
Sim includes {TEMPLATE_WORKFLOWS.length} pre-built workflow templates covering OCR
Expand Down Expand Up @@ -449,7 +443,7 @@ export default function Templates() {
</svg>
</div>

<div className='px-[20px] pt-[60px] lg:px-[80px] lg:pt-[100px]'>
<div className='px-4 pt-[60px] sm:px-6 lg:px-[80px] lg:pt-[100px]'>
<div className='flex flex-col items-start gap-[20px]'>
<Badge
variant='blue'
Expand Down Expand Up @@ -517,7 +511,7 @@ export default function Templates() {
aria-controls={TEMPLATES_PANEL_ID}
onClick={() => setActiveIndex(index)}
className={cn(
'relative w-full text-left',
'relative w-full overflow-x-clip text-left',
isActive
? 'z-10'
: cn(
Expand All @@ -543,7 +537,7 @@ export default function Templates() {
className='absolute right-[-8px] bottom-0 left-2 h-2'
style={buildBottomWallStyle(depth)}
/>
<div className='-translate-y-2 relative flex translate-x-2 items-center bg-[#242424] px-[12px] py-[10px] shadow-[inset_0_0_0_1.5px_#3E3E3E]'>
<div className='-translate-y-2 relative flex w-full translate-x-2 items-center bg-[#242424] px-[12px] py-[10px] shadow-[inset_0_0_0_1.5px_#3E3E3E]'>
<span className='flex-1 font-[430] font-season text-[16px] text-white'>
{workflow.name}
</span>
Expand Down
Loading
Loading