"use client"; import { useEffect, useRef } from "react"; import { animate, motion, useInView, useMotionValue, useReducedMotion } from "motion/react"; type NeonTubeProps = { text: string; className?: string; /** Gas colour. */ color?: string; /** Power. Set false to switch the sign off. */ on?: boolean; /** Index of the worn letter that keeps stuttering, -1 for none. */ stutter?: number; }; const SCRIPT = "font-['Segoe_Script','Snell_Roundhand','Brush_Script_MT',cursive]"; type LetterProps = { ch: string; delay: number; lit: boolean; worn: boolean; color: string; reduce: boolean }; function Letter({ ch, delay, lit, worn, color, reduce }: LetterProps) { const glow = useMotionValue(0); useEffect(() => { if (reduce) { glow.set(lit ? 1 : 0); return; } if (!lit) { const off = animate(glow, 0, { duration: 0.18 }); return () => off.stop(); } let loop: ReturnType | undefined; const strike = animate(glow, [0, 1, 0.1, 0, 0.85, 0.15, 1], { duration: 0.8, delay, times: [0, 0.14, 0.26, 0.5, 0.64, 0.78, 1] }); if (worn) { strike.then(() => { loop = animate(glow, [1, 0.2, 1, 0.45, 1], { duration: 0.45, repeat: Infinity, repeatDelay: 4.5 }); }); } return () => { strike.stop(); loop?.stop(); }; }, [lit, delay, worn, reduce, glow]); return ( <> {ch} {ch} ); } /* NeonTube: bent glass tube on clips, grey and dead at rest. Power up and each letter strikes on its own, one worn letter keeps stuttering. */ export function NeonTube({ text, className = SCRIPT, color = "#ff3d81", on = true, stutter = 3 }: NeonTubeProps) { const reduce = useReducedMotion() ?? false; const box = useRef(null); const inView = useInView(box, { amount: 0.6 }); const lit = on && inView; const words = text.split(" "); return ( {text} {words.map((word, w) => { const start = words.slice(0, w).join(" ").length + (w ? 1 : 0); return ( {w > 0 && " "} {[...word].map((ch, c) => { const i = start + c; return ( {i % 3 === 1 && ( )} ); })} ); })} ); }