Lines Spotlight

Full-bleed layout rules that sit quiet until a cursor spotlight reveals them. Ships the screen-line utilities it measures — the same ones ruling this page.

npx shadcn@latest add https://www.obuseklearns.dev/r/lines-spotlight.json

Usage

lines-spotlight-demo.tsx
"use client";

import { useRef, useState } from "react";

/**
 * Scoped restatement of the real effect. `LayoutSpotlight` measures the whole
 * document and is already mounted once in the root layout, so a preview can't
 * mount a second copy — this draws the same masked-duplicate trick inside a
 * single stage instead.
 */
const SPOT_MASK = {
  maskImage:
    "radial-gradient(120px circle at var(--spot-x, -999px) var(--spot-y, -999px), black, transparent 70%)",
  WebkitMaskImage:
    "radial-gradient(120px circle at var(--spot-x, -999px) var(--spot-y, -999px), black, transparent 70%)",
} as const;

/** Quarter-height rows and rails inset by a fifth, as percentages of the stage. */
const HORIZONTALS = ["25%", "50%", "75%"];
const VERTICALS = ["20%", "80%"];

function Lines({ className }: { className: string }) {
  return (
    <>
      {HORIZONTALS.map((offset) => (
        <span
          key={`h-${offset}`}
          className={`absolute inset-x-0 h-px ${className}`}
          style={{ top: `calc(${offset} - 1px)` }}
        />
      ))}
      {VERTICALS.map((offset) => (
        <span
          key={`v-${offset}`}
          className={`absolute inset-y-0 w-px ${className}`}
          style={{ left: `calc(${offset} - 1px)` }}
        />
      ))}
    </>
  );
}

export function LinesSpotlightDemo() {
  const stageRef = useRef<HTMLDivElement>(null);
  const [active, setActive] = useState(false);

  const track = (event: React.PointerEvent<HTMLDivElement>) => {
    const stage = stageRef.current;
    if (!stage || event.pointerType === "touch") {
      return;
    }

    const rect = stage.getBoundingClientRect();
    stage.style.setProperty("--spot-x", `${event.clientX - rect.left}px`);
    stage.style.setProperty("--spot-y", `${event.clientY - rect.top}px`);
    setActive(true);
  };

  return (
    <div className="flex w-full flex-col items-center gap-4">
      <div
        ref={stageRef}
        onPointerMove={track}
        onPointerLeave={() => setActive(false)}
        className="screen-line-top screen-line-bottom screen-line-left screen-line-right relative h-56 w-full max-w-sm overflow-hidden"
      >
        <Lines className="bg-line" />
        <div
          aria-hidden
          className="pointer-events-none absolute inset-0 motion-reduce:hidden"
          style={SPOT_MASK}
        >
          <Lines className="bg-foreground/70" />
        </div>
      </div>

      {/* Pointer tracking is skipped on touch, so the prompt is too. */}
      <p className="hidden font-mono text-xs text-muted-foreground pointer-fine:block">
        {active ? "tracking pointer" : "move the cursor over the grid"}
      </p>
    </div>
  );
}