"use client"; import { useRef, useState, type PointerEvent } from "react"; import { animate, motion, useAnimationFrame, useInView, useMotionTemplate, useMotionValue, useReducedMotion, useSpring, useTransform } from "motion/react"; type PolaroidDevelopProps = { src: string; alt: string; /** Written on the white strip. */ caption?: string; /** Seconds to develop on its own. */ duration?: number; /** How much each shake speeds it up. 0 turns shaking off. */ shake?: number; /** Resting tilt, in degrees. */ tilt?: number; className?: string; }; /* PolaroidDevelop: an instant photo that comes out blank and develops on its own. Shake it and it goes faster. Press it when done for a new shot. */ export function PolaroidDevelop({ src, alt, caption, duration = 9, shake = 1, tilt = -2, className = "" }: PolaroidDevelopProps) { const reduce = useReducedMotion(); const box = useRef(null); const seen = useInView(box, { amount: 0.4 }); const [done, setDone] = useState(false); const p = useMotionValue(0); const flash = useMotionValue(0); const push = useMotionValue(0); const lean = useSpring(push, { stiffness: 320, damping: 11 }); const rotate = useTransform(lean, (v) => tilt + v); const lastDir = useRef(0); const saturate = useTransform(p, [0.1, 1], [0.15, 1.04]); const contrast = useTransform(p, [0, 0.55, 1], [0.5, 0.82, 1.03]); const bright = useTransform(p, [0, 0.45, 1], [1.15, 0.95, 1]); const warm = useTransform(p, [0.2, 0.6, 1], [0, 0.38, 0.06]); const filter = useMotionTemplate`saturate(${saturate}) contrast(${contrast}) brightness(${bright}) sepia(${warm})`; const milk = useTransform(p, [0, 0.72], [1, 0]); const reach = useTransform(p, [0, 0.72], [-30, 110]); const edge = useTransform(reach, (r) => r + 46); const mask = useMotionTemplate`radial-gradient(circle at 46% 42%, transparent ${reach}%, #000 ${edge}%)`; useAnimationFrame((_, dt) => { if (push.get() !== 0) push.set(Math.abs(push.get()) < 0.05 ? 0 : push.get() * 0.9); if (!seen || done) return; const next = reduce ? 1 : Math.min(1, p.get() + dt / (duration * 1000)); p.set(next); if (next === 1) setDone(true); }); function hurry(by: number) { if (done) return; const next = Math.min(1, p.get() + by); p.set(next); if (next === 1) setDone(true); } function move(e: PointerEvent) { if (reduce || !shake) return; const dx = e.movementX; if (Math.abs(dx) < 2) return; push.set(Math.max(-9, Math.min(9, push.get() + dx * 0.5))); const dir = Math.sign(dx); if (dir !== lastDir.current) hurry(0.022 * shake); lastDir.current = dir; } function press() { if (done) { p.set(0); setDone(false); if (!reduce) animate(flash, [1, 0], { duration: 0.6, ease: "easeOut" }); return; } if (reduce || !shake) return; push.set(push.get() > 0 ? -8 : 8); hurry(0.14 * shake); } return (
{caption}
); }