"use client"; import { useEffect, useRef } from "react"; import { animate, motion, useInView, useMotionTemplate, useMotionValue, useReducedMotion, useTransform } from "motion/react"; type PaintRollerProps = { text: string; className?: string; /** Paint colour. */ paint?: string; /** Colour of the letters once painted. */ ink?: string; /** Seconds for one pass. */ duration?: number; /** Play only the first time it scrolls into view. */ once?: boolean; }; const PAD = "px-[0.45em] py-[0.18em]"; const DRIPS = [{ at: 16, size: 0.42 }, { at: 47, size: 0.26 }, { at: 78, size: 0.5 }]; // The roller leaves a dry, uneven edge along the top and bottom of the coat. const dryEdge = (() => { const top: string[] = []; const bottom: string[] = []; for (let i = 0; i <= 40; i++) { const x = i * 2.5; top.push(`${x}% ${(i * 37) % 7}%`); bottom.push(`${100 - x}% ${100 - (i * 53) % 8}%`); } return `polygon(${[...top, ...bottom].join(",")})`; })(); /* PaintRoller: letters pencilled on a wall until a real roller passes, laying a streaky wet coat and leaving drips. */ export function PaintRoller({ text, className = "", paint = "#0f8b8d", ink = "#fff8ec", duration = 1.5, once = true, }: PaintRollerProps) { const reduce = useReducedMotion(); const box = useRef(null); const inView = useInView(box, { once, amount: 0.6 }); const done = useMotionValue(reduce ? 100 : 0); const left = useTransform(done, (d) => 100 - d); const coat = useMotionTemplate`inset(-10% ${left}% -10% 0)`; const rollerLeft = useMotionTemplate`${done}%`; const turn = useTransform(done, (d) => d * 3); const nap = useMotionTemplate`${turn}px 0, 0 0`; const drip0 = useTransform(done, [DRIPS[0].at + 8, DRIPS[0].at + 40], [0, 1]); const drip1 = useTransform(done, [DRIPS[1].at + 8, DRIPS[1].at + 40], [0, 1]); const drip2 = useTransform(done, [DRIPS[2].at + 8, 100], [0, 1]); const drips = [drip0, drip1, drip2]; useEffect(() => { if (reduce) { done.set(100); return; } const pass = animate(done, inView ? 100 : 0, inView ? { duration, ease: "easeInOut" } : { duration: 0 }); return () => pass.stop(); }, [inView, reduce, duration, done]); return ( {text} {text} {DRIPS.map((drip, i) => ( ))} {text} ); }