From 782e9f66b9a02500da3d382726c3d98cd5b48267 Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Sun, 8 Mar 2026 15:02:27 +0530 Subject: [PATCH 1/3] feat: smoother animation for icons --- .../app/(landing)/components/hero/hero.tsx | 78 ++++++++++++++++--- 1 file changed, 68 insertions(+), 10 deletions(-) diff --git a/apps/sim/app/(landing)/components/hero/hero.tsx b/apps/sim/app/(landing)/components/hero/hero.tsx index 546dc47627f..21c3feba22b 100644 --- a/apps/sim/app/(landing)/components/hero/hero.tsx +++ b/apps/sim/app/(landing)/components/hero/hero.tsx @@ -1,6 +1,7 @@ 'use client' import React from 'react' +import { motion } from 'framer-motion' import { ArrowUp, CodeIcon } from 'lucide-react' import { useRouter } from 'next/navigation' import { type Edge, type Node, Position } from 'reactflow' @@ -152,6 +153,16 @@ export default function Hero() { const [lastHoveredIndex, setLastHoveredIndex] = React.useState(null) const intervalRef = React.useRef(null) + const iconRowRef = React.useRef(null) + const buttonRefs = React.useRef<(HTMLDivElement | null)[]>([]) + const [pillLayout, setPillLayout] = React.useState<{ + left: number + top: number + width: number + height: number + } | null>(null) + const [layoutVersion, setLayoutVersion] = React.useState(0) + /** * Handle service icon click to populate textarea with template */ @@ -246,6 +257,29 @@ export default function Hero() { } } + const activeIconIndex = + isUserHovering && lastHoveredIndex !== null ? lastHoveredIndex : autoHoverIndex + + React.useLayoutEffect(() => { + const container = iconRowRef.current + const target = buttonRefs.current[activeIconIndex] + if (!container || !target) return + const cr = container.getBoundingClientRect() + const tr = target.getBoundingClientRect() + setPillLayout({ + left: tr.left - cr.left, + top: tr.top - cr.top, + width: tr.width, + height: tr.height, + }) + }, [activeIconIndex, layoutVersion, visibleIconCount]) + + React.useEffect(() => { + const onResize = () => setLayoutVersion((v) => v + 1) + window.addEventListener('resize', onResize) + return () => window.removeEventListener('resize', onResize) + }, []) + /** * Handle form submission */ @@ -377,24 +411,48 @@ export default function Hero() { Build and deploy AI agent workflows

- {/* Service integration buttons */} + {pillLayout !== null && ( + + )} {serviceIcons.slice(0, visibleIconCount).map((service, index) => { const Icon = service.icon return ( - handleServiceClick(service.key as keyof typeof SERVICE_TEMPLATES)} - onMouseEnter={() => setLastHoveredIndex(index)} - style={service.style} - isAutoHovered={!isUserHovering && index === autoHoverIndex} + ref={(el) => { + buttonRefs.current[index] = el + }} > - - + handleServiceClick(service.key as keyof typeof SERVICE_TEMPLATES)} + onMouseEnter={() => setLastHoveredIndex(index)} + style={service.style} + isAutoHovered={!isUserHovering && index === autoHoverIndex} + > + + +
) })} From 48782d2bd3bb391a769adcc6500046f4b15504e5 Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Sun, 8 Mar 2026 15:54:35 +0530 Subject: [PATCH 2/3] chore: fix state lag on hover --- .../components/hero/components/icon-button.tsx | 11 +++++++++-- apps/sim/app/(landing)/components/hero/hero.tsx | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/sim/app/(landing)/components/hero/components/icon-button.tsx b/apps/sim/app/(landing)/components/hero/components/icon-button.tsx index c8e523e2f86..254853437f0 100644 --- a/apps/sim/app/(landing)/components/hero/components/icon-button.tsx +++ b/apps/sim/app/(landing)/components/hero/components/icon-button.tsx @@ -9,6 +9,7 @@ interface IconButtonProps { style?: React.CSSProperties 'aria-label': string isAutoHovered?: boolean + highlightFromParent?: boolean } export function IconButton({ @@ -18,7 +19,13 @@ export function IconButton({ style, 'aria-label': ariaLabel, isAutoHovered = false, + highlightFromParent = false, }: IconButtonProps) { + const showOwnHighlight = !highlightFromParent && isAutoHovered + const hoverHighlight = !highlightFromParent + ? 'hover:border-[#E5E5E5] hover:shadow-[0_2px_4px_0_rgba(0,0,0,0.08)]' + : '' + return (