"use client"; /** * CASE CARD — replaces the rejected repetitive photos with a coded, animated * data visual unique per case: * - 3D tilt that tracks the pointer (Motion springs, transform-only). * - An animated bars/sparkline "result chart" drawn in CSS + SVG, growing * into view (no two cards look alike: different bars, colours, labels). * - A glare/sheen that follows the cursor across the surface. * Reduced-motion / touch: flat card, bars still grow on view via CSS. */ import { useRef } from "react"; import { motion, useMotionValue, useSpring, useTransform, useReducedMotion, } from "motion/react"; export type CaseData = { tag: string; problem: string; result: string; how: string; metricNum: string; metricLabel: string; bars: number[]; // 0..100 heights — unique per case accent: string; // brand accent for this card }; export default function CaseCard({ data, index }: { data: CaseData; index: number }) { const reduce = useReducedMotion(); const ref = useRef(null); const mx = useMotionValue(0.5); const my = useMotionValue(0.5); const rx = useSpring(useTransform(my, [0, 1], [7, -7]), { stiffness: 200, damping: 20 }); const ry = useSpring(useTransform(mx, [0, 1], [-9, 9]), { stiffness: 200, damping: 20 }); const glare = useTransform( [mx, my], ([gx, gy]: number[]) => `radial-gradient(circle at ${gx * 100}% ${gy * 100}%, rgba(255,255,255,0.14), transparent 45%)` ); const onMove = (e: React.PointerEvent) => { if (reduce || e.pointerType !== "mouse") return; const el = ref.current; if (!el) return; const r = el.getBoundingClientRect(); mx.set((e.clientX - r.left) / r.width); my.set((e.clientY - r.top) / r.height); }; const reset = () => { mx.set(0.5); my.set(0.5); }; return (
{String(index + 1).padStart(2, "0")} {data.tag}
{/* coded data visual — unique bars per case */}

Before {data.problem}

{data.result}

{data.how}

{data.metricNum} {data.metricLabel}
{!reduce && (
); }