Skip to content

Commit 7f19cf0

Browse files
chore: add landing animation for templates
1 parent c085df7 commit 7f19cf0

File tree

1 file changed

+102
-140
lines changed

1 file changed

+102
-140
lines changed

apps/sim/app/(home)/components/templates/templates.tsx

Lines changed: 102 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
'use client'
22

33
import { useCallback, useEffect, useRef, useState } from 'react'
4-
import { createLogger } from '@sim/logger'
5-
import { AnimatePresence, type MotionValue, motion, useScroll, useTransform } from 'framer-motion'
4+
import { type MotionValue, motion, useScroll, useTransform } from 'framer-motion'
65
import dynamic from 'next/dynamic'
7-
import { useRouter } from 'next/navigation'
6+
import Link from 'next/link'
87
import { Badge, ChevronDown } from '@/components/emcn'
98
import { LandingWorkflowSeedStorage } from '@/lib/core/utils/browser-storage'
9+
1010
import { cn } from '@/lib/core/utils/cn'
1111
import { TEMPLATE_WORKFLOWS } from '@/app/(home)/components/templates/template-workflows'
12-
12+
import { createLogger } from '@sim/logger'
13+
import { useRouter } from 'next/navigation'
1314
const logger = createLogger('LandingTemplates')
1415

1516
const LandingPreviewWorkflow = dynamic(
@@ -337,7 +338,7 @@ function DotGrid({ className, cols, rows, gap = 0 }: DotGridProps) {
337338
}}
338339
>
339340
{Array.from({ length: cols * rows }, (_, i) => (
340-
<div key={i} className='h-[1.5px] w-[1.5px] rounded-full bg-[#2A2A2A]' />
341+
<div key={i} className='h-[2px] w-[2px] rounded-full bg-[#2A2A2A]' />
341342
))}
342343
</div>
343344
)
@@ -424,8 +425,8 @@ export default function Templates() {
424425

425426
<div className='bg-[#1C1C1C]'>
426427
<DotGrid
427-
className='overflow-hidden border-[#2A2A2A] border-y bg-[#1C1C1C] p-[6px]'
428-
cols={160}
428+
className='border-[#2A2A2A] border-y bg-[#1C1C1C] p-[6px]'
429+
cols={120}
429430
rows={1}
430431
gap={6}
431432
/>
@@ -449,7 +450,7 @@ export default function Templates() {
449450
</svg>
450451
</div>
451452

452-
<div className='px-[20px] pt-[60px] lg:px-[80px] lg:pt-[100px]'>
453+
<div className='px-[80px] pt-[100px]'>
453454
<div className='flex flex-col items-start gap-[20px]'>
454455
<Badge
455456
variant='blue'
@@ -466,132 +467,107 @@ export default function Templates() {
466467

467468
<h2
468469
id='templates-heading'
469-
className='font-[430] font-season text-[28px] text-white leading-[100%] tracking-[-0.02em] lg:text-[40px]'
470+
className='font-[430] font-season text-[40px] text-white leading-[100%] tracking-[-0.02em]'
470471
>
471472
Ship your agent in minutes
472473
</h2>
473474

474-
<p className='font-[430] font-season text-[#F6F6F0]/50 text-[15px] leading-[150%] tracking-[0.02em] lg:text-[18px]'>
475+
<p className='font-[430] font-season text-[#F6F6F0]/50 text-[16px] leading-[125%] tracking-[0.02em]'>
475476
Pre-built templates for every use case—pick one, swap{' '}
476477
<br className='hidden lg:inline' />
477478
models and tools to fit your stack, and deploy.
478479
</p>
479480
</div>
480481
</div>
481482

482-
<div className='mt-[40px] flex border-[#2A2A2A] border-y lg:mt-[73px]'>
483-
<div className='shrink-0'>
484-
<div className='h-full lg:hidden'>
485-
<DotGrid
486-
className='h-full w-[24px] overflow-hidden border-[#2A2A2A] border-r p-[4px]'
487-
cols={2}
488-
rows={55}
489-
gap={4}
490-
/>
491-
</div>
492-
<div className='hidden h-full lg:block'>
493-
<DotGrid
494-
className='h-full w-[80px] overflow-hidden border-[#2A2A2A] border-r p-[6px]'
495-
cols={8}
496-
rows={55}
497-
gap={6}
498-
/>
499-
</div>
500-
</div>
483+
<div className='mt-[73px] flex border-[#2A2A2A] border-y'>
484+
<DotGrid
485+
className='w-[80px] shrink-0 overflow-hidden border-[#2A2A2A] border-r p-[6px]'
486+
cols={6}
487+
rows={55}
488+
gap={6}
489+
/>
501490

502-
<div className='flex min-w-0 flex-1 flex-col lg:flex-row'>
491+
<div className='flex min-w-0 flex-1'>
503492
<div
504493
role='tablist'
505494
aria-label='Workflow templates'
506-
className='flex w-full shrink-0 flex-col border-[#2A2A2A] lg:w-[300px] lg:border-r'
495+
className='flex w-[300px] shrink-0 flex-col border-[#2A2A2A] border-r'
507496
>
508497
{TEMPLATE_WORKFLOWS.map((workflow, index) => {
509498
const isActive = index === activeIndex
499+
const depth = DEPTH_CONFIGS[workflow.id]
510500
return (
511-
<div key={workflow.id}>
512-
<button
513-
id={`template-tab-${index}`}
514-
type='button'
515-
role='tab'
516-
aria-selected={isActive}
517-
aria-controls={TEMPLATES_PANEL_ID}
518-
onClick={() => setActiveIndex(index)}
519-
className={cn(
520-
'relative w-full text-left',
521-
isActive
522-
? 'z-10'
523-
: cn(
524-
'flex items-center px-[12px] py-[10px] hover:bg-[#232323]/50',
525-
index < TEMPLATE_WORKFLOWS.length - 1 &&
526-
'shadow-[inset_0_-1px_0_0_#2A2A2A]'
527-
)
528-
)}
501+
<button
502+
key={workflow.id}
503+
id={`template-tab-${index}`}
504+
type='button'
505+
role='tab'
506+
aria-selected={isActive}
507+
aria-controls={TEMPLATES_PANEL_ID}
508+
onClick={() => setActiveIndex(index)}
509+
className={cn(
510+
'relative text-left',
511+
isActive
512+
? 'z-10'
513+
: 'shadow-[inset_0_-1px_0_0_#2A2A2A] last:shadow-none hover:bg-[#232323]/50'
514+
)}
515+
>
516+
<div
517+
className='pointer-events-none absolute top-[-8px] bottom-0 left-0 w-2'
518+
style={{
519+
clipPath: LEFT_WALL_CLIP,
520+
backgroundColor: hexToRgba(depth.color, 0.63),
521+
opacity: isActive ? 1 : 0,
522+
transition: isActive
523+
? 'opacity 250ms cubic-bezier(0.2, 0, 0, 1) 50ms'
524+
: 'opacity 200ms cubic-bezier(0.4, 0, 1, 1)',
525+
}}
526+
/>
527+
<div
528+
className='pointer-events-none absolute right-[-8px] bottom-0 left-2 h-2'
529+
style={{
530+
...buildBottomWallStyle(depth),
531+
opacity: isActive ? 1 : 0,
532+
transition: isActive
533+
? 'opacity 250ms cubic-bezier(0.2, 0, 0, 1) 50ms'
534+
: 'opacity 200ms cubic-bezier(0.4, 0, 1, 1)',
535+
}}
536+
/>
537+
<div
538+
className='relative flex items-center px-[12px] py-[10px]'
539+
style={{
540+
transform: isActive ? 'translate(8px, -8px)' : 'translate(0px, 0px)',
541+
backgroundColor: isActive ? '#242424' : 'transparent',
542+
boxShadow: isActive
543+
? 'inset 0 0 0 1.5px #3E3E3E'
544+
: 'inset 0 0 0 1.5px transparent',
545+
transition: isActive
546+
? 'transform 350ms cubic-bezier(0.34, 1.4, 0.64, 1), background-color 250ms ease 30ms, box-shadow 250ms ease 30ms'
547+
: 'transform 300ms cubic-bezier(0.4, 0, 0.2, 1), background-color 200ms ease, box-shadow 200ms ease',
548+
}}
529549
>
530-
{isActive ? (
531-
(() => {
532-
const depth = DEPTH_CONFIGS[workflow.id]
533-
return (
534-
<>
535-
<div
536-
className='absolute top-[-8px] bottom-0 left-0 w-2'
537-
style={{
538-
clipPath: LEFT_WALL_CLIP,
539-
backgroundColor: hexToRgba(depth.color, 0.63),
540-
}}
541-
/>
542-
<div
543-
className='absolute right-[-8px] bottom-0 left-2 h-2'
544-
style={buildBottomWallStyle(depth)}
545-
/>
546-
<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]'>
547-
<span className='flex-1 font-[430] font-season text-[16px] text-white'>
548-
{workflow.name}
549-
</span>
550-
<ChevronDown
551-
className='-rotate-90 h-[11px] w-[11px] shrink-0'
552-
style={{ color: depth.color }}
553-
/>
554-
</div>
555-
</>
556-
)
557-
})()
558-
) : (
559-
<span className='font-[430] font-season text-[#F6F6F0]/50 text-[16px]'>
560-
{workflow.name}
561-
</span>
562-
)}
563-
</button>
564-
565-
<AnimatePresence>
566-
{isActive && isMobile && (
567-
<motion.div
568-
initial={{ height: 0, opacity: 0 }}
569-
animate={{ height: 'auto', opacity: 1 }}
570-
exit={{ height: 0, opacity: 0 }}
571-
transition={{ duration: 0.25, ease: [0.4, 0, 0.2, 1] }}
572-
className='overflow-hidden'
573-
>
574-
<div className='aspect-[16/10] w-full border-[#2A2A2A] border-y bg-[#1b1b1b]'>
575-
<LandingPreviewWorkflow
576-
workflow={workflow}
577-
animate
578-
fitViewOptions={{ padding: 0.15, maxZoom: 1.3 }}
579-
/>
580-
</div>
581-
<div className='p-[12px]'>
582-
<button
583-
type='button'
584-
onClick={handleUseTemplate}
585-
disabled={isPreparingTemplate}
586-
className='inline-flex h-[32px] w-full cursor-pointer items-center justify-center gap-[6px] rounded-[5px] border border-[#FFFFFF] bg-[#FFFFFF] font-[430] font-season text-[14px] text-black transition-colors active:bg-[#E0E0E0]'
587-
>
588-
{isPreparingTemplate ? 'Preparing...' : 'Use template'}
589-
</button>
590-
</div>
591-
</motion.div>
592-
)}
593-
</AnimatePresence>
594-
</div>
550+
<span
551+
className='flex-1 font-[430] font-season text-[16px]'
552+
style={{
553+
color: isActive ? '#FFFFFF' : 'rgba(246, 246, 240, 0.5)',
554+
transition: 'color 250ms ease',
555+
}}
556+
>
557+
{workflow.name}
558+
</span>
559+
<ChevronDown
560+
className='-rotate-90 h-[11px] w-[11px] shrink-0'
561+
style={{
562+
color: depth.color,
563+
opacity: isActive ? 1 : 0,
564+
transition: isActive
565+
? 'opacity 200ms ease 150ms'
566+
: 'opacity 150ms ease',
567+
}}
568+
/>
569+
</div>
570+
</button>
595571
)
596572
})}
597573
</div>
@@ -610,13 +586,11 @@ export default function Templates() {
610586
fitViewOptions={{ padding: 0.15, maxZoom: 1.3 }}
611587
/>
612588
</div>
613-
<button
614-
type='button'
615-
onClick={handleUseTemplate}
616-
disabled={isPreparingTemplate}
617-
className='group/cta absolute top-[16px] right-[16px] z-10 inline-flex h-[32px] cursor-pointer items-center gap-[6px] rounded-[5px] border border-[#FFFFFF] bg-[#FFFFFF] px-[10px] font-[430] font-season text-[14px] text-black transition-colors hover:border-[#E0E0E0] hover:bg-[#E0E0E0]'
589+
<Link
590+
href='/signup'
591+
className='group/cta absolute top-[16px] right-[16px] z-10 inline-flex h-[32px] 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'
618592
>
619-
{isPreparingTemplate ? 'Preparing...' : 'Use template'}
593+
Use template
620594
<span className='relative h-[10px] w-[10px] shrink-0'>
621595
<ChevronDown className='-rotate-90 absolute inset-0 h-[10px] w-[10px] transition-opacity duration-150 group-hover/cta:opacity-0' />
622596
<svg
@@ -635,31 +609,19 @@ export default function Templates() {
635609
/>
636610
</svg>
637611
</span>
638-
</button>
612+
</Link>
639613
</div>
640614
</div>
641615

642-
<div className='shrink-0'>
643-
<div className='h-full lg:hidden'>
644-
<DotGrid
645-
className='h-full w-[24px] overflow-hidden border-[#2A2A2A] border-l p-[4px]'
646-
cols={2}
647-
rows={55}
648-
gap={4}
649-
/>
650-
</div>
651-
<div className='hidden h-full lg:block'>
652-
<DotGrid
653-
className='h-full w-[80px] overflow-hidden border-[#2A2A2A] border-l p-[6px]'
654-
cols={8}
655-
rows={55}
656-
gap={6}
657-
/>
658-
</div>
659-
</div>
616+
<DotGrid
617+
className='w-[80px] shrink-0 overflow-hidden border-[#2A2A2A] border-l p-[6px]'
618+
cols={6}
619+
rows={55}
620+
gap={6}
621+
/>
660622
</div>
661623
</div>
662624
</div>
663625
</section>
664626
)
665-
}
627+
}

0 commit comments

Comments
 (0)