# Torch Grid

> One soft light follows your pointer across a grid of cards, washing over the one you're on and catching the rims of its neighbours across the gaps.

- Page: https://devl.dev/motion/torch-grid
- Kind: motion · Cursor
- Install: `npx shadcn@latest add https://devl.dev/r/torch-grid.json`
- Raw source: https://devl.dev/raw/torch-grid.tsx
- License: MIT. The coss ui components it uses (https://coss.com/ui) are AGPL-3.0.

## Usage

```tsx
import { GitBranchIcon, GlobeIcon, type LucideIcon, ShieldCheckIcon, ZapIcon } from "lucide-react";
import { TorchCard, TorchGrid } from "@/components/torch-grid";

const features: { icon: LucideIcon; title: string; body: string; stat: string }[] = [
  { icon: ZapIcon, title: "Instant sync", body: "Edits land on every open tab before you lift your finger.", stat: "38 ms p95" },
  { icon: ShieldCheckIcon, title: "Audit trail", body: "Every change signed, timestamped and searchable.", stat: "400 days" },
  { icon: GitBranchIcon, title: "Branch previews", body: "Each pull request gets its own live copy to click through.", stat: "Per PR" },
  { icon: GlobeIcon, title: "Edge regions", body: "Requests are served from whichever region answers first.", stat: "18 regions" },
];

export default function TorchGridDemo() {
  return (
    <div className="@container w-full max-w-xl">
      <TorchGrid className="grid grid-cols-1 gap-3 @md:grid-cols-2">
      {features.map((f) => (
        <TorchCard className="flex flex-col gap-6 p-5" key={f.title}>
          <div className="flex items-center justify-between">
            <span className="grid size-9 place-items-center rounded-xl border bg-background text-foreground shadow-xs/5">
              <f.icon className="size-4" />
            </span>
            <span className="font-mono text-muted-foreground text-xs tabular-nums">{f.stat}</span>
          </div>
          <div className="flex flex-col gap-1">
            <h3 className="font-medium text-sm">{f.title}</h3>
            <p className="text-pretty text-muted-foreground text-sm">{f.body}</p>
          </div>
        </TorchCard>
      ))}
      </TorchGrid>
    </div>
  );
}
```

## Props

| Prop | Type | Default |
| --- | --- | --- |
| `radius`? | `number` | `260` |

Also accepts: `ComponentProps<"div">`.

## Source: components/torch-grid.tsx

```tsx
"use client";

import { type ComponentProps, type CSSProperties, type PointerEvent, useRef } from "react";
import { cn } from "@/lib/utils";

export interface TorchGridProps extends ComponentProps<"div"> {
  radius?: number;
}

export function TorchGrid({
  radius = 260,
  className,
  style,
  onPointerEnter,
  onPointerMove,
  onPointerLeave,
  ...props
}: TorchGridProps) {
  const ref = useRef<HTMLDivElement>(null);

  const measure = () => {
    const grid = ref.current;
    if (!grid) return;
    const g = grid.getBoundingClientRect();
    for (const card of grid.querySelectorAll<HTMLElement>("[data-spotlight-card]")) {
      const c = card.getBoundingClientRect();
      card.style.setProperty("--card-x", `${c.left - g.left}px`);
      card.style.setProperty("--card-y", `${c.top - g.top}px`);
    }
  };

  const track = (event: PointerEvent<HTMLDivElement>) => {
    const grid = ref.current;
    if (!grid) return;
    const g = grid.getBoundingClientRect();
    grid.style.setProperty("--spot-x", `${event.clientX - g.left}px`);
    grid.style.setProperty("--spot-y", `${event.clientY - g.top}px`);
  };

  return (
    <div
      className={cn("group/spotlight relative", className)}
      data-spotlight-grid=""
      onPointerEnter={(e) => {
        onPointerEnter?.(e);
        if (e.pointerType !== "mouse") return;
        measure();
        track(e);
        ref.current?.setAttribute("data-lit", "");
      }}
      onPointerLeave={(e) => {
        onPointerLeave?.(e);
        ref.current?.removeAttribute("data-lit");
      }}
      onPointerMove={(e) => {
        onPointerMove?.(e);
        if (e.pointerType === "mouse") track(e);
      }}
      ref={ref}
      style={{ "--spot-r": `${radius}px`, ...style } as CSSProperties}
      {...props}
    />
  );
}

export type TorchCardProps = ComponentProps<"div">;

const atX = "(var(--spot-x, -999px) - var(--card-x, 0px))";
const atY = "(var(--spot-y, -999px) - var(--card-y, 0px))";

export function TorchCard({ className, children, ...props }: TorchCardProps) {
  return (
    <div
      className={cn("relative isolate rounded-2xl border bg-card shadow-xs/5", className)}
      data-spotlight-card=""
      {...props}
    >
      <span
        aria-hidden
        className="pointer-events-none absolute inset-0 -z-10 rounded-[inherit] opacity-0 transition-opacity duration-300 ease-out group-data-lit/spotlight:opacity-100 [--spot-fill:color-mix(in_srgb,var(--color-foreground)_5%,transparent)] dark:[--spot-fill:color-mix(in_srgb,var(--color-foreground)_9%,transparent)]"
        style={{ background: `radial-gradient(var(--spot-r) circle at calc${atX} calc${atY}, var(--spot-fill), transparent 70%)` }}
      />
      <span
        aria-hidden
        className="pointer-events-none absolute -inset-px z-10 rounded-[inherit] border border-transparent opacity-0 transition-opacity duration-300 ease-out group-data-lit/spotlight:opacity-100 [--spot-rim:color-mix(in_srgb,var(--color-foreground)_32%,transparent)] dark:[--spot-rim:color-mix(in_srgb,var(--color-foreground)_44%,transparent)]"
        style={{
          background: `radial-gradient(calc(var(--spot-r) * 0.7) circle at calc(${atX} + 1px) calc(${atY} + 1px), var(--spot-rim), transparent 70%) border-box`,
          mask: "linear-gradient(#000 0 0) padding-box exclude, linear-gradient(#000 0 0) border-box",
        }}
      />
      {children}
    </div>
  );
}
```
