"use client"; import { motion, useReducedMotion } from "motion/react"; type DNAFlowProps = { className?: string; color?: string; /** Colour of the second strand. */ accent?: string; /** Loader width in pixels. Height is half of it. */ size?: number; /** Number of base pairs across. */ pairs?: number; /** 1 is the default pace; 2 is twice as fast. */ speed?: number; /** Read out by screen readers. */ label?: string; }; const STEPS = 16; // One full turn sampled at STEPS points (plus the closing point). const ANGLES = Array.from({ length: STEPS + 1 }, (_, k) => (k / STEPS) * Math.PI * 2); /* DNAFlow: two strands twist round each other as a double helix; rungs stretch between them. */ export function DNAFlow({ className = "", color = "#a78bfa", accent = "#22d3ee", size = 96, pairs = 8, speed = 1, label = "Loading", }: DNAFlowProps) { const reduce = useReducedMotion(); const duration = 2.4 / speed; const height = size / 2; const amp = height * 0.38; const dot = size * 0.07; // Strand point at angle t: y from cos, depth (size + brightness) from sin. const strand = (offset: number) => ({ y: ANGLES.map((t) => amp * Math.cos(t + offset)), scale: ANGLES.map((t) => 0.7 + 0.3 * Math.sin(t + offset)), opacity: ANGLES.map((t) => 0.45 + 0.55 * ((Math.sin(t + offset) + 1) / 2)), }); const strandA = strand(0); const strandB = strand(Math.PI); const rung = { scaleY: ANGLES.map((t) => Math.abs(Math.cos(t))) }; return (
{label} {Array.from({ length: pairs }, (_, i) => { const phase = (i / pairs) * Math.PI * 2; const transition = { duration, repeat: Infinity, ease: "linear" as const, delay: -(i / pairs) * duration }; // Reduced motion: hold the helix still at this column's phase. const still = (offset: number) => ({ y: amp * Math.cos(phase + offset), scale: 0.7 + 0.3 * Math.sin(phase + offset), }); return ( {[ { c: color, frames: strandA, offset: 0 }, { c: accent, frames: strandB, offset: Math.PI }, ].map(({ c, frames, offset }) => ( ))} ); })}
); }