# Feature Section v2

> Four-card feature grid with uppercase titles, descriptions, unique x.aiâ€“style SVG illustrations, and scroll-triggered entrance animations per card.

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

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

## Install

```bash
npx shadcn@latest add https://uiception.com/r/feature-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

- Registry items: `card`

## Files

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

```tsx
import FeatureSectionV2 from "./components/feature-section-v2"

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

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

```tsx
import FeatureShowcase from "./feature-showcase"

import "../styles/feature-section-v2.css"

export default function FeatureSectionV2() {
  return (
    <section className="py-4 md:py-6 lg:py-8">
      <div className="mx-auto max-w-4xl px-4">
        <div className="mb-6 flex flex-col items-center gap-2.5 text-center md:mb-8">
          <h2 className="text-4xl tracking-tighter lg:text-5xl">
            Everything your team needs
          </h2>
          <p className="max-w-xl text-3.75 leading-relaxed text-muted-foreground">
            See what is happening in real time, let automations handle the
            routine, and connect the tools you already use without any extra
            setup.
          </p>
        </div>
        <FeatureShowcase />
      </div>
    </section>
  )
}
```

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

```tsx
import type { ComponentType } from "react"

import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import AccessControlIllustration from "./access-control-illustration"
import { FeatureCardMedia } from "./feature-card-media"
import IntegrationsIllustration from "./integrations-illustration"
import RealTimeAnalyticsIllustration from "./real-time-analytics-illustration"
import WorkflowAutomationIllustration from "./workflow-automation-illustration"
import { features, type FeatureIllustrationId } from "../lib/features"

const illustrationComponents: Record<FeatureIllustrationId, ComponentType> = {
  "workflow-automation": WorkflowAutomationIllustration,
  "real-time-analytics": RealTimeAnalyticsIllustration,
  "access-control": AccessControlIllustration,
  integrations: IntegrationsIllustration,
}

export default function FeatureShowcase() {
  return (
    <ul className="m-0 grid list-none grid-cols-1 gap-2 p-0 sm:grid-cols-2">
      {features.map(({ id, title, description, illustration }) => {
        const Illustration = illustrationComponents[illustration]

        return (
          <li key={id} className="flex min-w-0">
            <Card
              size="sm"
              className="group/card flex w-full flex-col gap-0 overflow-hidden rounded-lg bg-background py-0 ring-1 ring-border transition-shadow duration-200 hover:shadow-[0_2px_8px_rgba(0,0,0,0.04)]"
            >
              <FeatureCardMedia>
                <Illustration />
              </FeatureCardMedia>
              <CardHeader className="gap-0.5 px-3 pt-2 pb-0">
                <CardTitle className="font-mono text-[0.625rem] leading-[1.25] font-medium tracking-[0.1em] text-foreground/90 uppercase">
                  {title}
                </CardTitle>
              </CardHeader>
              <CardContent className="px-3 pt-1 pb-2.5">
                <CardDescription className="m-0 line-clamp-3 text-[0.75rem] leading-[1.4]">
                  {description}
                </CardDescription>
              </CardContent>
            </Card>
          </li>
        )
      })}
    </ul>
  )
}
```

### `registry/new-york/blocks/feature-section-v2/components/feature-card-media.tsx`

```tsx
import type { ReactNode } from "react"

import { cn } from "@/lib/utils"

import { FEATURE_CARD_MEDIA_HEIGHT } from "./feature-illustration"

type FeatureCardMediaProps = {
  children: ReactNode
  className?: string
}

export function FeatureCardMedia({ children, className }: FeatureCardMediaProps) {
  return (
    <div
      className={cn(
        "relative w-full shrink-0 overflow-hidden bg-background",
        FEATURE_CARD_MEDIA_HEIGHT,
        className
      )}
      aria-hidden
    >
      {children}
    </div>
  )
}
```

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

```tsx
import type { ReactNode } from "react"

import { cn } from "@/lib/utils"

export const FEATURE_CARD_MEDIA_HEIGHT = "h-56 min-h-56"

type FeatureIllustrationProps = {
  viewBox: string
  children: ReactNode
  className?: string
  svgClassName?: string
}

export function FeatureIllustration({
  viewBox,
  children,
  className,
  svgClassName,
}: FeatureIllustrationProps) {
  return (
    <div
      className={cn(
        "size-full min-h-0 opacity-40 motion-safe:transition-opacity duration-200 ease-out group-hover/card:opacity-70",
        className
      )}
    >
      <svg
        aria-hidden
        viewBox={viewBox}
        preserveAspectRatio="xMidYMid meet"
        className={cn("text-foreground block size-full", svgClassName)}
        fill="none"
        xmlns="http://www.w3.org/2000/svg"
      >
        {children}
      </svg>
    </div>
  )
}
```

### `registry/new-york/blocks/feature-section-v2/components/workflow-automation-illustration.tsx`

```tsx
"use client"

import { cn } from "@/lib/utils"

import { FeatureIllustration } from "./feature-illustration"
import { featureIllustrationViewBoxes } from "../lib/features"
import { useInViewOnce } from "../lib/use-in-view-once"

export default function WorkflowAutomationIllustration() {
  const { ref, active } = useInViewOnce()

  return (
    <div ref={ref} className="size-full min-h-0">
      <FeatureIllustration
        viewBox={featureIllustrationViewBoxes["workflow-automation"]}
        svgClassName={cn("fsv2-wf-chart", active && "fsv2-wf-chart--active")}
      >
        <rect
          x="24"
          y="28"
          width="440"
          height="326"
          rx="2"
          fill="var(--card)"
          stroke="url(#fsv2_wf_frame)"
        />
        <line x1="24" y1="68" x2="464" y2="68" stroke="currentColor" strokeOpacity="0.35" />
        <circle cx="44" cy="48" r="4" stroke="currentColor" strokeOpacity="0.5" />
        <circle cx="58" cy="48" r="4" stroke="currentColor" strokeOpacity="0.35" />
        <circle cx="72" cy="48" r="4" stroke="currentColor" strokeOpacity="0.2" />

        <g className="fsv2-wf-trigger">
          <rect
            x="48"
            y="108"
            width="112"
            height="88"
            rx="4"
            fill="var(--card)"
            stroke="url(#fsv2_wf_panel)"
            strokeDasharray="6 4"
          />
          <line x1="64" y1="132" x2="144" y2="132" stroke="currentColor" strokeOpacity="0.55" />
          <line x1="64" y1="152" x2="128" y2="152" stroke="currentColor" strokeOpacity="0.35" />
          <line x1="64" y1="172" x2="136" y2="172" stroke="currentColor" strokeOpacity="0.25" />
        </g>

        <g className="motion-safe:transition-transform duration-200 ease-out group-hover/card:translate-x-0.5">
          <path
            className="fsv2-wf-flow__line"
            pathLength={1}
            d="M176 152H208"
            stroke="url(#fsv2_wf_flow)"
            strokeWidth="1.5"
            strokeLinecap="round"
            fill="none"
          />
          <path
            className="fsv2-wf-flow__arrow"
            d="M208 152L198 146"
            stroke="currentColor"
            strokeOpacity="0.6"
            strokeLinecap="round"
          />
          <path
            className="fsv2-wf-flow__arrow"
            d="M208 152L198 158"
            stroke="currentColor"
            strokeOpacity="0.6"
            strokeLinecap="round"
          />
        </g>

        <g className="fsv2-wf-queue">
          <rect
            x="224"
            y="96"
            width="216"
            height="200"
            rx="4"
            fill="var(--card)"
            stroke="url(#fsv2_wf_panel)"
          />
          <rect
            x="240"
            y="116"
            width="184"
            height="28"
            rx="3"
            stroke="currentColor"
            strokeOpacity="0.15"
          />
          <rect
            x="240"
            y="156"
            width="184"
            height="28"
            rx="3"
            stroke="currentColor"
            strokeOpacity="0.55"
            fill="currentColor"
            fillOpacity="0.04"
          />
          <circle cx="252" cy="170" r="4" fill="currentColor" fillOpacity="0.7" />
          <rect
            x="240"
            y="196"
            width="184"
            height="28"
            rx="3"
            stroke="currentColor"
            strokeOpacity="0.12"
          />
          <rect
            x="240"
            y="236"
            width="184"
            height="28"
            rx="3"
            stroke="currentColor"
            strokeOpacity="0.08"
          />
        </g>

        <rect
          x="388"
          y="40"
          width="52"
          height="18"
          rx="9"
          stroke="currentColor"
          strokeOpacity="0.45"
        />
        <circle cx="426" cy="49" r="6" fill="currentColor" fillOpacity="0.35" />

        <defs>
          <linearGradient
            id="fsv2_wf_frame"
            x1="244"
            y1="28"
            x2="244"
            y2="354"
            gradientUnits="userSpaceOnUse"
          >
            <stop stopColor="var(--foreground)" />
            <stop offset="0.8" stopColor="var(--card)" />
          </linearGradient>
          <linearGradient
            id="fsv2_wf_panel"
            x1="0"
            y1="0"
            x2="0"
            y2="1"
            gradientUnits="objectBoundingBox"
          >
            <stop stopColor="var(--foreground)" />
            <stop offset="0.75" stopColor="var(--foreground)" stopOpacity="0" />
          </linearGradient>
          <linearGradient
            id="fsv2_wf_flow"
            x1="176"
            y1="152"
            x2="208"
            y2="152"
            gradientUnits="userSpaceOnUse"
          >
            <stop stopColor="var(--foreground)" stopOpacity="0.2" />
            <stop offset="1" stopColor="var(--foreground)" />
          </linearGradient>
        </defs>
      </FeatureIllustration>
    </div>
  )
}
```

### `registry/new-york/blocks/feature-section-v2/components/real-time-analytics-illustration.tsx`

```tsx
"use client"

import { cn } from "@/lib/utils"

import { FeatureIllustration } from "./feature-illustration"
import { featureIllustrationViewBoxes } from "../lib/features"
import { useInViewOnce } from "../lib/use-in-view-once"

export default function RealTimeAnalyticsIllustration() {
  const { ref, active } = useInViewOnce()

  return (
    <div ref={ref} className="size-full min-h-0">
      <FeatureIllustration
        viewBox={featureIllustrationViewBoxes["real-time-analytics"]}
        svgClassName={cn("fsv2-an-chart", active && "fsv2-an-chart--active")}
      >
        <rect
          x="32"
          y="36"
          width="424"
          height="310"
          fill="var(--card)"
          stroke="url(#fsv2_an_frame)"
        />
        <line x1="32" y1="76" x2="456" y2="76" stroke="currentColor" strokeOpacity="0.3" />

        <g className="fsv2-an-live">
          <rect
            x="52"
            y="52"
            width="48"
            height="16"
            rx="8"
            stroke="currentColor"
            strokeOpacity="0.4"
          />
          <circle cx="64" cy="60" r="3" fill="#22c55e" fillOpacity="0.9" />
          <line x1="72" y1="60" x2="88" y2="60" stroke="currentColor" strokeOpacity="0.5" />
        </g>

        <path
          d="M72 108H416M72 148H416M72 188H416M72 228H416M72 268H416M72 308H416"
          stroke="currentColor"
          strokeOpacity="0.08"
        />

        <g className="motion-safe:transition-opacity duration-200 ease-out group-hover/card:opacity-100 opacity-90">
          <path
            className="fsv2-an-spark"
            pathLength={1}
            d="M72 248 L120 220 L168 232 L216 180 L264 196 L312 140 L360 156 L408 108"
            stroke="url(#fsv2_an_spark)"
            strokeWidth="1.5"
            strokeLinecap="round"
            strokeLinejoin="round"
            fill="none"
          />
        </g>

        <rect
          className="fsv2-an-bar fsv2-an-bar--1"
          x="88"
          y="248"
          width="36"
          height="60"
          fill="url(#fsv2_an_bar)"
          fillOpacity="0.25"
          stroke="currentColor"
          strokeOpacity="0.2"
        />
        <rect
          className="fsv2-an-bar fsv2-an-bar--2"
          x="136"
          y="220"
          width="36"
          height="88"
          fill="url(#fsv2_an_bar)"
          fillOpacity="0.3"
          stroke="currentColor"
          strokeOpacity="0.25"
        />
        <rect
          className="fsv2-an-bar fsv2-an-bar--3"
          x="184"
          y="196"
          width="36"
          height="112"
          fill="url(#fsv2_an_bar_active)"
          fillOpacity="0.35"
          stroke="currentColor"
          strokeOpacity="0.65"
        />
        <rect
          className="fsv2-an-bar fsv2-an-bar--4"
          x="232"
          y="212"
          width="36"
          height="96"
          fill="url(#fsv2_an_bar)"
          fillOpacity="0.28"
          stroke="currentColor"
          strokeOpacity="0.22"
        />
        <rect
          className="fsv2-an-bar fsv2-an-bar--5"
          x="280"
          y="168"
          width="36"
          height="140"
          fill="url(#fsv2_an_bar)"
          fillOpacity="0.32"
          stroke="currentColor"
          strokeOpacity="0.28"
        />
        <rect
          className="fsv2-an-bar fsv2-an-bar--6"
          x="328"
          y="184"
          width="36"
          height="124"
          fill="url(#fsv2_an_bar)"
          fillOpacity="0.26"
          stroke="currentColor"
          strokeOpacity="0.2"
        />

        <g className="fsv2-an-marker">
          <line
            x1="184"
            y1="196"
            x2="220"
            y2="196"
            stroke="currentColor"
            strokeOpacity="0.7"
            strokeDasharray="4 3"
          />
          <circle cx="220" cy="196" r="3" fill="currentColor" fillOpacity="0.8" />
        </g>

        <defs>
          <linearGradient
            id="fsv2_an_frame"
            x1="244"
            y1="36"
            x2="244"
            y2="346"
            gradientUnits="userSpaceOnUse"
          >
            <stop stopColor="var(--foreground)" />
            <stop offset="0.8" stopColor="var(--card)" />
          </linearGradient>
          <linearGradient
            id="fsv2_an_spark"
            x1="72"
            y1="248"
            x2="408"
            y2="108"
            gradientUnits="userSpaceOnUse"
          >
            <stop stopColor="var(--foreground)" stopOpacity="0.15" />
            <stop offset="1" stopColor="var(--foreground)" />
          </linearGradient>
          <linearGradient
            id="fsv2_an_bar"
            x1="0"
            y1="1"
            x2="0"
            y2="0"
            gradientUnits="objectBoundingBox"
          >
            <stop stopColor="var(--foreground)" stopOpacity="0" />
            <stop offset="1" stopColor="var(--foreground)" />
          </linearGradient>
          <linearGradient
            id="fsv2_an_bar_active"
            x1="0"
            y1="1"
            x2="0"
            y2="0"
            gradientUnits="objectBoundingBox"
          >
            <stop stopColor="var(--foreground)" stopOpacity="0.1" />
            <stop offset="1" stopColor="var(--foreground)" />
          </linearGradient>
        </defs>
      </FeatureIllustration>
    </div>
  )
}
```

### `registry/new-york/blocks/feature-section-v2/components/access-control-illustration.tsx`

```tsx
"use client"

import { cn } from "@/lib/utils"

import { FeatureIllustration } from "./feature-illustration"
import { featureIllustrationViewBoxes } from "../lib/features"
import { useInViewOnce } from "../lib/use-in-view-once"

export default function AccessControlIllustration() {
  const { ref, active } = useInViewOnce()

  return (
    <div ref={ref} className="size-full min-h-0">
      <FeatureIllustration
        viewBox={featureIllustrationViewBoxes["access-control"]}
        svgClassName={cn("fsv2-ac-chart", active && "fsv2-ac-chart--active")}
      >
        <g className="fsv2-ac-layer fsv2-ac-layer--3">
          <rect
            x="72"
            y="56"
            width="344"
            height="280"
            fill="var(--card)"
            stroke="url(#fsv2_ac_layer3)"
            strokeOpacity="0.35"
          />
          <line x1="96" y1="96" x2="392" y2="96" stroke="currentColor" strokeOpacity="0.12" />
          <line x1="96" y1="128" x2="320" y2="128" stroke="currentColor" strokeOpacity="0.08" />
        </g>

        <g className="fsv2-ac-layer fsv2-ac-layer--2">
          <rect
            x="56"
            y="44"
            width="344"
            height="280"
            fill="var(--card)"
            stroke="url(#fsv2_ac_layer2)"
            strokeOpacity="0.55"
          />
          <line x1="80" y1="84" x2="376" y2="84" stroke="currentColor" strokeOpacity="0.2" />
          <line x1="80" y1="116" x2="340" y2="116" stroke="currentColor" strokeOpacity="0.14" />
          <rect
            x="80"
            y="140"
            width="72"
            height="20"
            rx="4"
            stroke="currentColor"
            strokeOpacity="0.25"
          />
        </g>

        <g className="fsv2-ac-layer fsv2-ac-layer--1 motion-safe:transition-transform duration-200 ease-out group-hover/card:-translate-y-0.5">
          <rect
            x="40"
            y="32"
            width="344"
            height="280"
            fill="var(--card)"
            stroke="url(#fsv2_ac_layer1)"
          />
          <line x1="64" y1="72" x2="360" y2="72" stroke="currentColor" strokeOpacity="0.35" />
          <line x1="64" y1="104" x2="360" y2="104" stroke="currentColor" strokeOpacity="0.28" />
          <line x1="64" y1="136" x2="300" y2="136" stroke="currentColor" strokeOpacity="0.2" />
          <line x1="64" y1="168" x2="360" y2="168" stroke="currentColor" strokeOpacity="0.14" />

          <rect
            x="64"
            y="292"
            width="56"
            height="18"
            rx="4"
            stroke="currentColor"
            strokeOpacity="0.35"
          />
          <rect
            x="132"
            y="292"
            width="72"
            height="18"
            rx="4"
            stroke="currentColor"
            strokeOpacity="0.2"
          />
        </g>

        <g className="fsv2-ac-lock">
          <rect
            x="196"
            y="188"
            width="96"
            height="88"
            rx="6"
            fill="var(--card)"
            stroke="url(#fsv2_ac_lock)"
          />
          <path
            d="M228 224V212C228 200.954 236.954 192 248 192C259.046 192 268 200.954 268 212V224"
            stroke="currentColor"
            strokeOpacity="0.75"
            strokeWidth="1.5"
            strokeLinecap="round"
          />
          <rect
            x="220"
            y="224"
            width="56"
            height="40"
            rx="4"
            stroke="currentColor"
            strokeOpacity="0.75"
          />
          <circle cx="248" cy="240" r="4" fill="currentColor" fillOpacity="0.6" />
          <line x1="248" y1="244" x2="248" y2="252" stroke="currentColor" strokeOpacity="0.5" />
        </g>

        <defs>
          <linearGradient
            id="fsv2_ac_layer1"
            x1="212"
            y1="32"
            x2="212"
            y2="312"
            gradientUnits="userSpaceOnUse"
          >
            <stop stopColor="var(--foreground)" />
            <stop offset="0.8" stopColor="var(--foreground)" stopOpacity="0" />
          </linearGradient>
          <linearGradient
            id="fsv2_ac_layer2"
            x1="228"
            y1="44"
            x2="228"
            y2="324"
            gradientUnits="userSpaceOnUse"
          >
            <stop stopColor="var(--foreground)" stopOpacity="0.7" />
            <stop offset="0.8" stopColor="var(--foreground)" stopOpacity="0" />
          </linearGradient>
          <linearGradient
            id="fsv2_ac_layer3"
            x1="244"
            y1="56"
            x2="244"
            y2="336"
            gradientUnits="userSpaceOnUse"
          >
            <stop stopColor="var(--foreground)" stopOpacity="0.4" />
            <stop offset="0.8" stopColor="var(--foreground)" stopOpacity="0" />
          </linearGradient>
          <linearGradient
            id="fsv2_ac_lock"
            x1="244"
            y1="188"
            x2="244"
            y2="276"
            gradientUnits="userSpaceOnUse"
          >
            <stop stopColor="var(--foreground)" />
            <stop offset="1" stopColor="var(--foreground)" stopOpacity="0.2" />
          </linearGradient>
        </defs>
      </FeatureIllustration>
    </div>
  )
}
```

### `registry/new-york/blocks/feature-section-v2/components/integrations-illustration.tsx`

```tsx
"use client"

import { cn } from "@/lib/utils"

import { FeatureIllustration } from "./feature-illustration"
import { featureIllustrationViewBoxes } from "../lib/features"
import { useInViewOnce } from "../lib/use-in-view-once"

export default function IntegrationsIllustration() {
  const { ref, active } = useInViewOnce()

  return (
    <div ref={ref} className="size-full min-h-0">
      <FeatureIllustration
        viewBox={featureIllustrationViewBoxes.integrations}
        svgClassName={cn("fsv2-in-chart", active && "fsv2-in-chart--active")}
      >
        <rect
          x="28"
          y="24"
          width="432"
          height="334"
          fill="var(--card)"
          stroke="url(#fsv2_in_frame)"
        />

        <path
          className="fsv2-in-spoke fsv2-in-spoke--1"
          pathLength={1}
          d="M244 191 L120 100"
          stroke="url(#fsv2_in_line)"
          strokeOpacity="0.45"
          fill="none"
        />
        <path
          className="fsv2-in-spoke fsv2-in-spoke--2"
          pathLength={1}
          d="M244 191 L368 100"
          stroke="url(#fsv2_in_line)"
          strokeOpacity="0.45"
          fill="none"
        />
        <path
          className="fsv2-in-spoke fsv2-in-spoke--3"
          pathLength={1}
          d="M244 191 L96 220"
          stroke="url(#fsv2_in_line)"
          strokeOpacity="0.35"
          fill="none"
        />
        <path
          className="fsv2-in-spoke fsv2-in-spoke--4"
          pathLength={1}
          d="M244 191 L392 220"
          stroke="url(#fsv2_in_line)"
          strokeOpacity="0.35"
          fill="none"
        />
        <path
          className="fsv2-in-spoke fsv2-in-spoke--5"
          pathLength={1}
          d="M244 191 L244 300"
          stroke="url(#fsv2_in_line)"
          strokeOpacity="0.5"
          fill="none"
        />

        <g className="fsv2-in-node fsv2-in-node--1 motion-safe:transition-transform duration-200 ease-out group-hover/card:-translate-y-1">
          <rect
            x="88"
            y="72"
            width="64"
            height="48"
            rx="4"
            fill="var(--card)"
            stroke="currentColor"
            strokeOpacity="0.35"
          />
          <line x1="100" y1="88" x2="140" y2="88" stroke="currentColor" strokeOpacity="0.4" />
          <line x1="100" y1="104" x2="128" y2="104" stroke="currentColor" strokeOpacity="0.25" />
        </g>

        <g className="fsv2-in-node fsv2-in-node--2 motion-safe:transition-transform duration-200 ease-out group-hover/card:-translate-y-1">
          <rect
            x="336"
            y="72"
            width="64"
            height="48"
            rx="4"
            fill="var(--card)"
            stroke="currentColor"
            strokeOpacity="0.35"
          />
          <line x1="348" y1="88" x2="388" y2="88" stroke="currentColor" strokeOpacity="0.4" />
          <line x1="348" y1="104" x2="376" y2="104" stroke="currentColor" strokeOpacity="0.25" />
        </g>

        <g className="fsv2-in-node fsv2-in-node--3 motion-safe:transition-transform duration-200 ease-out group-hover/card:translate-x-1">
          <rect
            x="64"
            y="196"
            width="64"
            height="48"
            rx="4"
            fill="var(--card)"
            stroke="currentColor"
            strokeOpacity="0.28"
          />
          <line x1="76" y1="212" x2="116" y2="212" stroke="currentColor" strokeOpacity="0.3" />
        </g>

        <g className="fsv2-in-node fsv2-in-node--4 motion-safe:transition-transform duration-200 ease-out group-hover/card:-translate-x-1">
          <rect
            x="360"
            y="196"
            width="64"
            height="48"
            rx="4"
            fill="var(--card)"
            stroke="currentColor"
            strokeOpacity="0.28"
          />
          <line x1="372" y1="212" x2="412" y2="212" stroke="currentColor" strokeOpacity="0.3" />
        </g>

        <g className="fsv2-in-node fsv2-in-node--5 motion-safe:transition-transform duration-200 ease-out group-hover/card:translate-y-1">
          <rect
            x="212"
            y="276"
            width="64"
            height="48"
            rx="4"
            fill="var(--card)"
            stroke="currentColor"
            strokeOpacity="0.4"
          />
          <line x1="224" y1="292" x2="264" y2="292" stroke="currentColor" strokeOpacity="0.35" />
        </g>

        <g className="fsv2-in-hub">
          <circle
            cx="244"
            cy="191"
            r="44"
            fill="var(--card)"
            stroke="url(#fsv2_in_hub)"
            strokeWidth="1.5"
          />
          <circle cx="244" cy="191" r="6" fill="currentColor" fillOpacity="0.7" />
          <line x1="244" y1="163" x2="244" y2="219" stroke="currentColor" strokeOpacity="0.5" />
          <line x1="216" y1="191" x2="272" y2="191" stroke="currentColor" strokeOpacity="0.5" />
        </g>

        <defs>
          <linearGradient
            id="fsv2_in_frame"
            x1="244"
            y1="24"
            x2="244"
            y2="358"
            gradientUnits="userSpaceOnUse"
          >
            <stop stopColor="var(--foreground)" />
            <stop offset="0.8" stopColor="var(--card)" />
          </linearGradient>
          <linearGradient
            id="fsv2_in_hub"
            x1="244"
            y1="147"
            x2="244"
            y2="235"
            gradientUnits="userSpaceOnUse"
          >
            <stop stopColor="var(--foreground)" />
            <stop offset="1" stopColor="var(--foreground)" stopOpacity="0.15" />
          </linearGradient>
          <linearGradient
            id="fsv2_in_line"
            x1="244"
            y1="191"
            x2="244"
            y2="100"
            gradientUnits="userSpaceOnUse"
          >
            <stop stopColor="var(--foreground)" stopOpacity="0.6" />
            <stop offset="1" stopColor="var(--foreground)" stopOpacity="0" />
          </linearGradient>
        </defs>
      </FeatureIllustration>
    </div>
  )
}
```

### `registry/new-york/blocks/feature-section-v2/lib/use-in-view-once.ts`

```ts
"use client"

import { useEffect, useRef, useState } from "react"

export function useInViewOnce(threshold = 0.35) {
  const ref = useRef<HTMLDivElement>(null)
  const [active, setActive] = useState(false)

  useEffect(() => {
    const el = ref.current
    if (!el || active) return

    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry?.isIntersecting) {
          setActive(true)
          observer.disconnect()
        }
      },
      { threshold }
    )

    observer.observe(el)
    return () => observer.disconnect()
  }, [active, threshold])

  return { ref, active }
}
```

### `registry/new-york/blocks/feature-section-v2/lib/features.ts`

```ts
export type FeatureIllustrationId =
  | "workflow-automation"
  | "real-time-analytics"
  | "access-control"
  | "integrations"

export const featureIllustrationViewBoxes = {
  "workflow-automation": "16 20 456 342",
  "real-time-analytics": "24 28 440 318",
  "access-control": "32 24 396 294",
  integrations: "20 16 448 348",
} as const satisfies Record<FeatureIllustrationId, string>

export type Feature = {
  id: string
  title: string
  description: string
  illustration: FeatureIllustrationId
}

export const features: Feature[] = [
  {
    id: "workflow-automation",
    title: "Workflow automation",
    description:
      "Set it up once and let it run. Your team gets back the time that used to disappear into repetitive work.",
    illustration: "workflow-automation",
  },
  {
    id: "real-time-analytics",
    title: "Real-time analytics",
    description:
      "Pull live data into your dashboards so you can act on it while it still matters, not days later when the weekly export finally lands.",
    illustration: "real-time-analytics",
  },
  {
    id: "access-control",
    title: "Access control",
    description:
      "Give people exactly the access they need, keep sensitive areas locked down, and put an end to the permission requests piling up in your inbox.",
    illustration: "access-control",
  },
  {
    id: "integrations",
    title: "Integrations",
    description:
      "Connect the tools your team already uses in minutes, without turning it into another engineering project.",
    illustration: "integrations",
  },
]
```

### `registry/new-york/blocks/feature-section-v2/styles/feature-section-v2.css`

```css
@keyframes fsv2-illus-draw {
  to {
    stroke-dashoffset: 0;
  }
}

@keyframes fsv2-illus-fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes fsv2-illus-scale-in {
  from {
    opacity: 0;
    transform: scale(0.88);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes fsv2-illus-grow-up {
  from {
    opacity: 0;
    transform: scaleY(0.55);
  }
  to {
    opacity: 1;
    transform: scaleY(1);
  }
}

.fsv2-wf-trigger,
.fsv2-wf-queue {
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
}

.fsv2-wf-flow__line {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

.fsv2-wf-flow__arrow {
  opacity: 0;
}

.fsv2-wf-chart--active .fsv2-wf-trigger {
  animation: fsv2-illus-scale-in 520ms cubic-bezier(0.22, 1, 0.36, 1) 120ms forwards;
}

.fsv2-wf-chart--active .fsv2-wf-flow__line {
  animation: fsv2-illus-draw 480ms cubic-bezier(0.22, 1, 0.36, 1) 360ms forwards;
}

.fsv2-wf-chart--active .fsv2-wf-flow__arrow {
  animation: fsv2-illus-fade-in 320ms ease-out 720ms forwards;
}

.fsv2-wf-chart--active .fsv2-wf-queue {
  animation: fsv2-illus-scale-in 560ms cubic-bezier(0.16, 1, 0.3, 1) 520ms forwards;
}

.fsv2-an-spark {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

.fsv2-an-live {
  opacity: 0;
}

.fsv2-an-bar {
  opacity: 0;
  transform-box: fill-box;
  transform-origin: bottom center;
}

.fsv2-an-chart--active .fsv2-an-live {
  animation: fsv2-illus-fade-in 360ms ease-out 100ms forwards;
}

.fsv2-an-chart--active .fsv2-an-spark {
  animation: fsv2-illus-draw 1.1s cubic-bezier(0.16, 1, 0.3, 1) 280ms forwards;
}

.fsv2-an-chart--active .fsv2-an-bar--1 {
  animation: fsv2-illus-grow-up 480ms cubic-bezier(0.22, 1, 0.36, 1) 900ms forwards;
}

.fsv2-an-chart--active .fsv2-an-bar--2 {
  animation: fsv2-illus-grow-up 480ms cubic-bezier(0.22, 1, 0.36, 1) 1020ms forwards;
}

.fsv2-an-chart--active .fsv2-an-bar--3 {
  animation: fsv2-illus-grow-up 520ms cubic-bezier(0.16, 1, 0.3, 1) 1140ms forwards;
}

.fsv2-an-chart--active .fsv2-an-bar--4 {
  animation: fsv2-illus-grow-up 480ms cubic-bezier(0.22, 1, 0.36, 1) 1260ms forwards;
}

.fsv2-an-chart--active .fsv2-an-bar--5 {
  animation: fsv2-illus-grow-up 480ms cubic-bezier(0.22, 1, 0.36, 1) 1380ms forwards;
}

.fsv2-an-chart--active .fsv2-an-bar--6 {
  animation: fsv2-illus-grow-up 480ms cubic-bezier(0.22, 1, 0.36, 1) 1500ms forwards;
}

.fsv2-an-marker {
  opacity: 0;
}

.fsv2-an-chart--active .fsv2-an-marker {
  animation: fsv2-illus-fade-in 360ms ease-out 1.65s forwards;
}

.fsv2-ac-layer {
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
}

.fsv2-ac-chart--active .fsv2-ac-layer--3 {
  animation: fsv2-illus-scale-in 480ms ease-out 120ms forwards;
}

.fsv2-ac-chart--active .fsv2-ac-layer--2 {
  animation: fsv2-illus-scale-in 520ms cubic-bezier(0.22, 1, 0.36, 1) 280ms forwards;
}

.fsv2-ac-chart--active .fsv2-ac-layer--1 {
  animation: fsv2-illus-scale-in 560ms cubic-bezier(0.16, 1, 0.3, 1) 460ms forwards;
}

.fsv2-ac-lock {
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
}

.fsv2-ac-chart--active .fsv2-ac-lock {
  animation: fsv2-illus-scale-in 480ms cubic-bezier(0.34, 1.2, 0.64, 1) 720ms forwards;
}

.fsv2-in-hub {
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
}

.fsv2-in-spoke {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

.fsv2-in-node {
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
}

.fsv2-in-chart--active .fsv2-in-hub {
  animation: fsv2-illus-scale-in 520ms cubic-bezier(0.22, 1, 0.36, 1) 140ms forwards;
}

.fsv2-in-chart--active .fsv2-in-spoke--1 {
  animation: fsv2-illus-draw 420ms ease-out 400ms forwards;
}

.fsv2-in-chart--active .fsv2-in-spoke--2 {
  animation: fsv2-illus-draw 420ms ease-out 480ms forwards;
}

.fsv2-in-chart--active .fsv2-in-spoke--3 {
  animation: fsv2-illus-draw 420ms ease-out 560ms forwards;
}

.fsv2-in-chart--active .fsv2-in-spoke--4 {
  animation: fsv2-illus-draw 420ms ease-out 640ms forwards;
}

.fsv2-in-chart--active .fsv2-in-spoke--5 {
  animation: fsv2-illus-draw 420ms ease-out 720ms forwards;
}

.fsv2-in-chart--active .fsv2-in-node--1,
.fsv2-in-chart--active .fsv2-in-node--2 {
  animation: fsv2-illus-scale-in 400ms ease-out 1.2s forwards;
}

.fsv2-in-chart--active .fsv2-in-node--3,
.fsv2-in-chart--active .fsv2-in-node--4 {
  animation: fsv2-illus-scale-in 400ms ease-out 1.32s forwards;
}

.fsv2-in-chart--active .fsv2-in-node--5 {
  animation: fsv2-illus-scale-in 400ms ease-out 1.44s forwards;
}

@media (prefers-reduced-motion: reduce) {
  .fsv2-wf-trigger,
  .fsv2-wf-queue,
  .fsv2-wf-flow__arrow,
  .fsv2-an-live,
  .fsv2-an-bar,
  .fsv2-an-marker,
  .fsv2-ac-layer,
  .fsv2-ac-lock,
  .fsv2-in-hub,
  .fsv2-in-node {
    opacity: 1;
    transform: none;
    animation: none;
  }

  .fsv2-wf-flow__line,
  .fsv2-an-spark,
  .fsv2-in-spoke {
    stroke-dashoffset: 0;
    animation: none;
  }
}
```
