Spaces:
Running
Running
File size: 1,890 Bytes
ace64e6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | 'use client';
import { motion } from 'framer-motion';
import { Dna } from '@phosphor-icons/react';
/**
* Branded smooth loader — a spinning DNA ring plus a cascade of dots.
* Use `compact` for inline states (buttons, small cards) and the default
* size for full-section loading states.
*/
export function SmoothLoader({
label = 'Loading…',
compact = false,
}: {
label?: string;
compact?: boolean;
}) {
return (
<div
role="status"
aria-label={label}
className={`flex flex-col items-center justify-center ${compact ? 'gap-2' : 'gap-4 py-12'}`}
>
<div className={`relative ${compact ? 'w-9 h-9' : 'w-14 h-14'}`}>
<span className="absolute inset-0 rounded-full border border-accent-cyan/15" />
<motion.span
className="absolute inset-0 rounded-full border-t-2 border-r-2 border-accent-cyan/70"
animate={{ rotate: 360 }}
transition={{ duration: 1.1, repeat: Infinity, ease: 'linear' }}
/>
<motion.span
className="absolute inset-[18%] rounded-full border-b-2 border-accent-purple/60"
animate={{ rotate: -360 }}
transition={{ duration: 1.6, repeat: Infinity, ease: 'linear' }}
/>
<Dna
className={`absolute inset-0 m-auto text-accent-cyan ${compact ? 'w-3.5 h-3.5' : 'w-5 h-5'}`}
weight="duotone"
/>
</div>
<div className="flex items-center gap-1.5">
{[0, 1, 2].map(i => (
<motion.span
key={i}
className="w-1.5 h-1.5 rounded-full bg-accent-cyan"
animate={{ opacity: [0.2, 1, 0.2], scale: [0.8, 1.15, 0.8] }}
transition={{ duration: 0.9, repeat: Infinity, delay: i * 0.16 }}
/>
))}
</div>
{label && <p className={`text-text-secondary ${compact ? 'text-xs' : 'text-sm'}`}>{label}</p>}
</div>
);
}
|