# Hero Section v2

> Two-column hero with headline on the left, live event-stream animation on the right, and a brands marquee strip below. GSAP timeline animates each section on mount.

A shadcn/ui block from the [@uiception](https://uiception.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/uiception/hero-section-v2).

- Page, with a live preview: https://shadcn.exchange/r/uiception/hero-section-v2
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/uiception/hero-section-v2-light-495396beefd433f9.png
- Registry item: https://uiception.com/r/hero-section-v2.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://uiception.com/r/hero-section-v2.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `gsap`
- Registry items: `https://uiception.com/r/event-stream.json`, `https://uiception.com/r/brands-section-v2.json`

## Files

### `registry/new-york/blocks/hero-section-v2/page.tsx`

```tsx
import HeroSectionV2 from "./components/hero-section-v2"

export default function Page() {
  return <HeroSectionV2 />
}
```

### `registry/new-york/blocks/hero-section-v2/components/hero-section-v2.tsx`

```tsx
import HeroContent from "./hero-content"

export default function HeroSectionV2() {
  return (
    <section className="py-4 md:py-6 lg:py-8">
      <div className="mx-auto max-w-6xl px-4">
        <HeroContent />
      </div>
    </section>
  )
}
```

### `registry/new-york/blocks/hero-section-v2/components/hero-content.tsx`

```tsx
"use client"

import { useHeroAnimation } from "../hooks/use-hero-animation"
import EventStream from "../../event-stream/components/event-stream"
import BrandsSectionV2 from "../../brands-section-v2/components/brands-section-v2"

const WORD_INITIAL: React.CSSProperties = {
  opacity: 0,
  transform: "translateY(24px)",
  display: "inline-block",
}

function Word({
  children,
  className,
}: {
  children: string
  className?: string
}) {
  return (
    <span
      style={WORD_INITIAL}
      className={`word${className ? ` ${className}` : ""}`}
    >
      {children}
    </span>
  )
}

export default function HeroContent() {
  const { titleRef, descRef, streamRef, brandsRef } = useHeroAnimation()

  return (
    <>
      <div className="grid gap-10 lg:grid-cols-2 lg:items-center lg:gap-16">
        <div className="flex flex-col gap-6">
          <h1
            ref={titleRef}
            className="text-4xl font-medium tracking-tight sm:text-5xl lg:text-6xl"
          >
            <Word>Every</Word> <Word>event,</Word>{" "}
            <Word className="font-normal italic">delivered</Word>{" "}
            <Word className="font-normal italic">instantly</Word>
          </h1>
          <p
            ref={descRef}
            style={{ opacity: 0 }}
            className="text-lg leading-relaxed font-light"
          >
            Connect your services with a reliable event pipeline. Orders,
            payments, and shipments stream in real time, exactly when you need
            them.
          </p>

        </div>

        <div ref={streamRef} style={{ opacity: 0 }}>
          <EventStream />
        </div>
      </div>

      <section ref={brandsRef} style={{ opacity: 0 }}>
        <BrandsSectionV2 />
      </section>
    </>
  )
}
```

### `registry/new-york/blocks/hero-section-v2/hooks/use-hero-animation.ts`

```ts
import { useEffect, useRef } from "react"
import gsap from "gsap"

export function useHeroAnimation() {
  const titleRef  = useRef<HTMLHeadingElement>(null)
  const descRef   = useRef<HTMLParagraphElement>(null)
  const streamRef = useRef<HTMLDivElement>(null)
  const brandsRef = useRef<HTMLElement>(null)

  useEffect(() => {
    const targets = [
      titleRef.current?.querySelectorAll(".word"),
      descRef.current,
      streamRef.current,
      brandsRef.current,
    ].filter(Boolean)

    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
      gsap.set(targets, { clearProps: "all" })
      return
    }

    const ctx = gsap.context(() => {
      const tl = gsap.timeline({ defaults: { ease: "power4.out" } })

      tl.to(titleRef.current?.querySelectorAll(".word") ?? [], {
        opacity: 1,
        y: 0,
        duration: 0.8,
        stagger: 0.07,
      })

      .to(descRef.current, {
        opacity: 1,
        filter: "blur(0px)",
        y: 0,
        duration: 0.9,
        ease: "power3.out",
      }, "-=0.45")

      .to(streamRef.current, {
        opacity: 1,
        scale: 1,
        y: 0,
        rotationX: 0,
        transformPerspective: 1000,
        transformOrigin: "50% 0%",
        duration: 1.1,
        ease: "expo.out",
      }, "<0.1")

      .to(brandsRef.current, {
        opacity: 1,
        y: 0,
        duration: 0.6,
        ease: "power3.out",
      }, "-=0.5")
    })

    return () => ctx.revert()
  }, [])

  return { titleRef, descRef, streamRef, brandsRef }
}
```
