# VHS Tracking Wobble Text

A shadcn/ui block from the [@weinc](https://we.inc/components?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/weinc/vhs-tracking-wobble-text-with-scanlines).

- Page, with a live preview: https://shadcn.exchange/r/weinc/vhs-tracking-wobble-text-with-scanlines
- Screenshot: https://shadcn.exchange/shot/weinc/vhs-tracking-wobble-text-with-scanlines/light.png
- Registry item: https://we.inc/r/vhs-tracking-wobble-text-with-scanlines.json
- Listed: 2026-10-28, source last changed 2026-10-28

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/weinc/vhs-tracking-wobble-text-with-scanlines.json
```

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

## Files

### `components/we-inc/vhs-tracking-wobble-text-with-scanlines.tsx`

```tsx
import { useEffect, useRef, useState } from "react";

const headline = "Be kind, rewind.";

export default function VhsTrackingWobbleText() {
  const root = useRef<HTMLElement>(null);
  const [visible, setVisible] = useState(true);

  useEffect(() => {
    const el = root.current;
    if (!el) return;
    const io = new IntersectionObserver(([entry]) => setVisible(entry.isIntersecting));
    io.observe(el);
    return () => io.disconnect();
  }, []);

  return (
    <section
      ref={root}
      data-paused={visible ? "false" : "true"}
      className="w10vhs relative overflow-hidden bg-[#1a1315] px-5 py-20 sm:px-10 sm:py-24"
    >
      <style>{`
        .w10vhs { --w10vhs-ink: #f4e6cf; --w10vhs-warm: #ff6f59; --w10vhs-cool: #4fd1c5; --w10vhs-speed: 6s; }
        .w10vhs-bg { background: radial-gradient(80% 70% at 50% 40%, #3a2526 0%, #1a1315 70%); }
        .w10vhs-scan { background: repeating-linear-gradient(to bottom, rgba(0,0,0,0.28) 0 2px, transparent 2px 4px); }
        .w10vhs-band { position: absolute; left: 0; right: 0; height: 18%; top: 0; transform: translateY(-120%); background: linear-gradient(to bottom, transparent, rgba(255,240,220,0.07) 40%, rgba(255,240,220,0.12) 50%, rgba(255,240,220,0.07) 60%, transparent); animation: w10vhs-band var(--w10vhs-speed) linear infinite; }
        .w10vhs-stack { position: relative; display: inline-grid; }
        .w10vhs-stack > span { grid-area: 1 / 1; }
        .w10vhs-base { color: var(--w10vhs-ink); text-shadow: -2px 0 rgba(255,111,89,0.55), 2px 0 rgba(79,209,197,0.5), 0 0 18px rgba(255,200,160,0.25); animation: w10vhs-wobble 2.8s ease-in-out infinite; }
        .w10vhs-tear { color: var(--w10vhs-ink); text-shadow: -5px 0 var(--w10vhs-warm), 5px 0 var(--w10vhs-cool); clip-path: inset(0 0 100% 0); animation: w10vhs-tear var(--w10vhs-speed) linear infinite; }
        .w10vhs-osd { animation: w10vhs-osd 1.6s steps(1, end) infinite; }
        @keyframes w10vhs-wobble { 0%, 100% { transform: translateX(0) skewX(0deg); } 30% { transform: translateX(1.5px) skewX(-0.6deg); } 60% { transform: translateX(-1px) skewX(0.4deg); } }
        @keyframes w10vhs-band { from { transform: translateY(-120%); } to { transform: translateY(580%); } }
        @keyframes w10vhs-tear {
          0% { clip-path: inset(0 0 100% 0); transform: translateX(0); }
          20% { clip-path: inset(0 0 82% 0); transform: translateX(9px); }
          40% { clip-path: inset(30% 0 52% 0); transform: translateX(-7px); }
          60% { clip-path: inset(58% 0 24% 0); transform: translateX(11px); }
          80% { clip-path: inset(84% 0 0 0); transform: translateX(-5px); }
          100% { clip-path: inset(100% 0 0 0); transform: translateX(0); }
        }
        @keyframes w10vhs-osd { 0% { opacity: 1; } 50% { opacity: 0.35; } }
        .w10vhs[data-paused="true"] .w10vhs-band, .w10vhs[data-paused="true"] .w10vhs-base, .w10vhs[data-paused="true"] .w10vhs-tear, .w10vhs[data-paused="true"] .w10vhs-osd { animation-play-state: paused; }
        @media (prefers-reduced-motion: reduce) {
          .w10vhs-band, .w10vhs-base, .w10vhs-osd { animation: none; }
          .w10vhs-band { transform: translateY(210%); }
          .w10vhs-tear { animation: none; clip-path: inset(46% 0 40% 0); transform: translateX(6px); }
        }
      `}</style>
      <div aria-hidden="true" className="w10vhs-bg pointer-events-none absolute inset-0" />
      <div aria-hidden="true" className="pointer-events-none absolute inset-0 overflow-hidden">
        <div className="w10vhs-band" />
      </div>
      <div aria-hidden="true" className="absolute left-5 top-5 font-mono text-sm tracking-widest text-[#f4e6cf] sm:left-8 sm:top-7">
        <span className="w10vhs-osd">PLAY ▶</span>
      </div>
      <div aria-hidden="true" className="absolute right-5 top-5 font-mono text-sm tracking-widest text-[#f4e6cf] sm:right-8 sm:top-7">SP 0:04:12</div>
      <div className="relative mx-auto max-w-4xl pt-6 text-center">
        <p className="font-mono text-xs uppercase tracking-[0.28em] text-[#d9b99a]">Corner Video Club · since 1991</p>
        <h1 className="mt-6 font-serif text-[clamp(46px,9vw,108px)] font-bold italic leading-none tracking-tight">
          <span className="sr-only">{headline}</span>
          <span aria-hidden="true" className="w10vhs-stack">
            <span className="w10vhs-base">{headline}</span>
            <span className="w10vhs-tear">{headline}</span>
          </span>
        </h1>
        <p className="mx-auto mt-7 max-w-lg text-base leading-relaxed text-[#e3d3bd] sm:text-lg">
          Four thousand tapes, two working VCRs to borrow, and a late return box that never closes.
        </p>
        <a
          href="#"
          className="mt-9 inline-flex h-12 items-center rounded-md border border-[#f4e6cf]/60 px-6 font-mono text-sm uppercase tracking-widest text-[#f4e6cf] transition hover:bg-[#f4e6cf] hover:text-[#1a1315] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#4fd1c5] focus-visible:ring-offset-2 focus-visible:ring-offset-[#1a1315]"
        >
          Browse the shelves
        </a>
      </div>
      <div aria-hidden="true" className="w10vhs-scan pointer-events-none absolute inset-0" />
    </section>
  );
}
```
