"use client"; import { useRef } from "react"; import { motion, useInView, useReducedMotion } from "motion/react"; type RansomNoteProps = { text: string; className?: string; /** Change it to cut the letters again from other pages. */ seed?: number; /** Seconds between letters landing. */ stagger?: number; /** Play only the first time it scrolls into view. */ once?: boolean; }; const CUTS = [ { paper: "#fbf3df", ink: "#141414", font: "Georgia, serif", weight: 700 }, { paper: "#141414", ink: "#ffffff", font: "Impact, 'Arial Narrow', sans-serif", weight: 400 }, { paper: "#e0392b", ink: "#ffffff", font: "'Arial Black', Arial, sans-serif", weight: 900 }, { paper: "#f6cf45", ink: "#141414", font: "'Times New Roman', serif", weight: 700, italic: true }, { paper: "#2558b0", ink: "#ffffff", font: "Verdana, sans-serif", weight: 700 }, { paper: "#ffffff", ink: "#c2185b", font: "'Courier New', monospace", weight: 700 }, { paper: "#d8d0bc", ink: "#2b2b2b", font: "Arial, Helvetica, sans-serif", weight: 800, dots: true }, { paper: "#2f8f5b", ink: "#fbf3df", font: "Georgia, serif", weight: 700, italic: true }, ]; // Same seed, same note: 0 to 1 from integers, so server and browser agree. const roll = (n: number) => (Math.imul(n + 7, 2654435761) >>> 16) / 65536; function scissorCut(n: number) { const j = (k: number, most: number) => (roll(n * 8 + k) * most).toFixed(1); return `polygon(${j(1, 9)}% ${j(2, 8)}%, 100% ${j(3, 11)}%, ${100 - +j(4, 8)}% ${100 - +j(5, 7)}%, ${j(6, 12)}% 100%)`; } /* RansomNote: every letter cut from a different page, then slapped down crooked. Same seed gives the same note. */ export function RansomNote({ text, className = "", seed = 1, stagger = 0.06, once = true }: RansomNoteProps) { const reduce = useReducedMotion(); const box = useRef(null); const inView = useInView(box, { once, amount: 0.2 }); let letter = -1; let last = -1; return ( {text} {text.split(" ").map((word, w) => ( {[...word].map((ch, i) => { letter += 1; const n = seed * 1000 + letter; const cut = (last + 1 + Math.floor(roll(n) * (CUTS.length - 1))) % CUTS.length; last = cut; const look = CUTS[cut]; const tilt = roll(n + 1) * 14 - 7; const lift = roll(n + 2) * 0.16 - 0.08; const size = 0.84 + roll(n + 3) * 0.32; const glyph = roll(n + 4) > 0.5 ? ch.toUpperCase() : ch.toLowerCase(); const hidden = { opacity: 0, scale: 1.9, rotate: tilt + 24 }; return ( {glyph} {letter % 5 === 2 && } ); })} ))} ); }