Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
bd3aedd
chore: fix conflicts
adithyaakrishna Mar 17, 2026
4e1c779
feat: generate og images
adithyaakrishna Mar 17, 2026
78304d7
chore: support for mermaid diagrams
adithyaakrishna Mar 17, 2026
2c3ad20
chore: update code editor
adithyaakrishna Mar 17, 2026
a3e44a2
chore: fix conflicts
adithyaakrishna Mar 17, 2026
dcaaa7d
chore: fix conflicts
adithyaakrishna Mar 17, 2026
4f21cc5
chore: add mermaid
adithyaakrishna Mar 17, 2026
a33c252
chore: fix conflicts
adithyaakrishna Mar 17, 2026
5e77b1e
chore: fix conflicts
adithyaakrishna Mar 17, 2026
8df9059
feat: updated links and spacing
adithyaakrishna Mar 17, 2026
5de72aa
chore: fix conflicts
adithyaakrishna Mar 17, 2026
92e1bef
chore: fix conflicts
adithyaakrishna Mar 17, 2026
a8ca914
chore: add cursor
adithyaakrishna Mar 17, 2026
794d38f
chore: fix conflicts
adithyaakrishna Mar 17, 2026
d9521cd
chore: updating caching
adithyaakrishna Mar 17, 2026
e7d85b1
chore: fix conflicts
adithyaakrishna Mar 17, 2026
18783a7
chore: remove unused component
adithyaakrishna Mar 17, 2026
774efca
chore: fix imports and review changes
adithyaakrishna Mar 17, 2026
0a2527c
chore: fix link issue
adithyaakrishna Mar 17, 2026
c085df7
feat: updated changelog and addressed duplicate fns
adithyaakrishna Mar 17, 2026
7f19cf0
chore: add landing animation for templates
adithyaakrishna Mar 17, 2026
a6bee35
feat: finessing
adithyaakrishna Mar 18, 2026
5416ddb
chore: blur contents on line hover
adithyaakrishna Mar 18, 2026
47cd9c5
chore: update changelog
adithyaakrishna Mar 18, 2026
30ce4ba
chore: other updates
adithyaakrishna Mar 18, 2026
2c776da
chore: fixed review comments
adithyaakrishna Mar 18, 2026
891ad23
chore: fix comments
adithyaakrishna Mar 18, 2026
7b31362
chore: update contents and fix responsivenes
adithyaakrishna Mar 18, 2026
af7a7e5
chore: handle unmount
adithyaakrishna Mar 18, 2026
fa19d84
chore: fix slugger
adithyaakrishna Mar 18, 2026
101a955
chore: updated for comments
adithyaakrishna Mar 18, 2026
b14eccb
chore: review fixes
adithyaakrishna Mar 19, 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 @@ -303,7 +303,7 @@ export default function Collaboration() {
</div>

<Link
href='/studio/multiplayer'
href='/blog/multiplayer'
target='_blank'
rel='noopener noreferrer'
className='relative mx-4 mb-6 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:mx-8 md:absolute md:bottom-10 md:left-[80px] md:z-20 md:mx-0 md:mb-0'
Expand Down
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