# Magnifier Loupe Over a Price List

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/magnifier-loupe-cursor-over-a-seed-catalogue-price-list).

- Page, with a live preview: https://shadcn.exchange/r/weinc/magnifier-loupe-cursor-over-a-seed-catalogue-price-list
- Screenshot: https://shadcn.exchange/shot/weinc/magnifier-loupe-cursor-over-a-seed-catalogue-price-list/light.png
- Registry item: https://we.inc/r/magnifier-loupe-cursor-over-a-seed-catalogue-price-list.json
- Listed: 2026-10-28, source last changed 2026-10-28

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/weinc/magnifier-loupe-cursor-over-a-seed-catalogue-price-list.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: `framer-motion`

## Files

### `components/we-inc/magnifier-loupe-cursor-over-a-seed-catalogue-price-list.tsx`

```tsx
import { useEffect, useRef } from "react";
import { useReducedMotion } from "framer-motion";

const ZOOM = 1.8; // magnification inside the loupe
const LENS = 240; // loupe diameter in px
const seeds = [
  { name: "Broad bean, Winter Lantern", note: "Sow Feb to Apr. Thirty seeds.", price: "$3.40" },
  { name: "Pea, Hedgerow Sugar", note: "Sweet flat pods, climbs to 5 ft.", price: "$3.10" },
  { name: "Beetroot, Cellar Blood", note: "Deep red globes that store well.", price: "$2.80" },
  { name: "Carrot, Long Barn", note: "Tapered roots for heavy soil.", price: "$2.60" },
  { name: "Lettuce, Velvet Oak", note: "Pick-again leaves, bronze tips.", price: "$2.90" },
  { name: "Chard, Five Lamps", note: "Stems in gold, pink and white.", price: "$3.00" },
  { name: "Tomato, Ridge Cherry", note: "Small fruit, early, very sweet.", price: "$3.80" },
  { name: "Squash, Pale Moon", note: "Grey-blue skin, dense orange flesh.", price: "$3.60" },
  { name: "Leek, Frost Hold", note: "Stands all winter in the bed.", price: "$2.70" },
  { name: "Kale, Black Crow", note: "Crinkled leaves, sweeter after frost.", price: "$2.90" },
  { name: "Sweet pea, Old Parlour", note: "Strong scent, mixed soft colours.", price: "$3.50" },
  { name: "Calendula, Ember Mix", note: "Edible petals, self-seeds gently.", price: "$2.40" },
];

function SeedList() {
  return (
    <div className="w2lup-list px-5 py-6 sm:px-8 sm:py-8">
      <div className="flex flex-wrap items-baseline justify-between gap-2 border-b-2 border-[#3b2f22] pb-3">
        <h3 className="font-serif text-xl font-semibold">Spring sowing list</h3>
        <p className="text-xs font-semibold uppercase tracking-[0.18em] text-[#6b5641]">Price per packet</p>
      </div>
      <ul className="mt-2 grid gap-x-10 md:grid-cols-2">
        {seeds.map((s) => (
          <li key={s.name} className="flex items-baseline gap-3 border-b border-dotted border-[#b9a68c] py-2.5">
            <span className="min-w-0 flex-1">
              <span className="block text-[15px] font-semibold">{s.name}</span>
              <span className="block text-[13px] italic text-[#5c4a37]">{s.note}</span>
            </span>
            <span className="text-[15px] font-semibold tabular-nums">{s.price}</span>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default function MagnifierLoupeCursorOverASeedCataloguePriceList() {
  const reduce = useReducedMotion();
  const rootRef = useRef<HTMLElement>(null);
  const sheetRef = useRef<HTMLDivElement>(null);
  const lensRef = useRef<HTMLDivElement>(null);
  const cloneRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const root = rootRef.current, sheet = sheetRef.current, lens = lensRef.current, clone = cloneRef.current;
    if (!root || !sheet || !lens || !clone || reduce || !matchMedia("(hover: hover) and (pointer: fine)").matches) return;
    root.setAttribute("data-on", "");
    const R = LENS / 2;
    let x = 0, y = 0, tx = 0, ty = 0, live = false, frame = 0, idle = 0;
    const t0 = performance.now();
    const fit = () => { clone.style.width = `${sheet.clientWidth}px`; };
    const loop = (now: number) => {
      if (!live) {
        const t = (now - t0) / 1000;
        tx = sheet.clientWidth * (0.5 + 0.38 * Math.sin(t * 0.42)); ty = sheet.clientHeight * (0.5 + 0.36 * Math.sin(t * 0.9 + 0.5));
      }
      x += (tx - x) * 0.18; y += (ty - y) * 0.18;
      lens.style.transform = `translate(${x - R}px, ${y - R}px)`;
      clone.style.transform = `translate(${R - x * ZOOM}px, ${R - y * ZOOM}px) scale(${ZOOM})`;
      frame = requestAnimationFrame(loop);
    };
    const start = () => { if (!frame) frame = requestAnimationFrame(loop); };
    const stop = () => { cancelAnimationFrame(frame); frame = 0; };
    const onMove = (e: PointerEvent) => {
      if (e.pointerType === "touch") return;
      const r = sheet.getBoundingClientRect();
      tx = e.clientX - r.left; ty = e.clientY - r.top;
      live = true; clearTimeout(idle); root.removeAttribute("data-kb");
    };
    const onLeave = () => { idle = window.setTimeout(() => { live = false; }, 1500); };
    const onFocus = () => { if (root.querySelector(":focus-visible")) root.setAttribute("data-kb", ""); };
    const onBlur = () => root.removeAttribute("data-kb");
    fit();
    const ro = new ResizeObserver(fit);
    ro.observe(sheet);
    const io = new IntersectionObserver(([e]) => (e.isIntersecting ? start() : stop()));
    io.observe(root);
    sheet.addEventListener("pointermove", onMove);
    sheet.addEventListener("pointerleave", onLeave);
    root.addEventListener("focusin", onFocus);
    root.addEventListener("focusout", onBlur);
    return () => {
      stop(); io.disconnect(); ro.disconnect(); clearTimeout(idle);
      root.removeAttribute("data-on");
      sheet.removeEventListener("pointermove", onMove);
      sheet.removeEventListener("pointerleave", onLeave);
      root.removeEventListener("focusin", onFocus);
      root.removeEventListener("focusout", onBlur);
    };
  }, [reduce]);

  return (
    <section ref={rootRef} className="w2lup-root bg-[#e9dfcd] px-5 py-14 text-[#2b2117] sm:px-10 sm:py-16">
      <style>{`
        .w2lup-lens { position: absolute; left: 0; top: 0; width: ${LENS}px; height: ${LENS}px; border-radius: 50%; overflow: hidden; pointer-events: none;
          background: #fbf6ec; opacity: 0; transition: opacity .35s ease; will-change: transform;
          box-shadow: 0 0 0 5px #3b2f22, 0 0 0 6px rgba(255,255,255,.25), 0 18px 40px -10px rgba(43,33,23,.55); }
        .w2lup-lens::after { content: ""; position: absolute; inset: 0; border-radius: 50%;
          background: radial-gradient(circle at 32% 26%, rgba(255,255,255,.45), rgba(255,255,255,0) 42%), radial-gradient(circle, rgba(0,0,0,0) 62%, rgba(43,33,23,.18) 100%); }
        .w2lup-clone { position: absolute; left: 0; top: 0; transform-origin: 0 0; }
        .w2lup-root[data-on] .w2lup-lens { opacity: 1; }
        .w2lup-root[data-kb] .w2lup-lens { opacity: 0; }
        @media (prefers-reduced-motion: reduce) { .w2lup-lens { display: none; } }
      `}</style>
      <div className="mx-auto max-w-5xl">
        <div className="mb-8 flex flex-wrap items-end justify-between gap-4">
          <div>
            <p className="text-xs font-semibold uppercase tracking-[0.22em] text-[#7a4b22]">Aldergrove Seed Co.</p>
            <h2 className="mt-2 font-serif text-[clamp(1.8rem,4vw,2.6rem)] leading-tight">The fine print, up close</h2>
          </div>
          <a href="#" className="inline-flex min-h-[44px] items-center rounded-md bg-[#2b2117] px-5 text-sm font-semibold text-[#f6efe2] hover:bg-[#3e3022] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#7a4b22] focus-visible:ring-offset-2 focus-visible:ring-offset-[#e9dfcd]">Order a seed box</a>
        </div>
        <div ref={sheetRef} className="relative overflow-hidden rounded-sm bg-[#fbf6ec] shadow-[0_1px_0_rgba(43,33,23,.12),0_24px_50px_-30px_rgba(43,33,23,.6)]">
          <SeedList />
          <div ref={lensRef} className="w2lup-lens" aria-hidden="true">
            <div ref={cloneRef} className="w2lup-clone">
              <SeedList />
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}
```
