Files
e-tib.com/components/ui/decorations/DrillBitSpinner.tsx
2026-05-11 21:31:49 +02:00

126 lines
3.4 KiB
TypeScript

'use client';
import { motion } from 'framer-motion';
/**
* DrillBitSpinner
*
* Abstract animated drill head cross-section.
* Resembles an HDD tri-blade drill bit seen from the front.
*
* The wrapping div uses a radial-gradient mask so it fades naturally
* into whatever background it's placed on — no hard edge clipping.
*/
interface DrillBitSpinnerProps {
size?: number;
color?: string;
duration?: number;
opacity?: number;
glow?: boolean;
className?: string;
}
export function DrillBitSpinner({
size = 120,
color = '#0e7a5c',
duration = 8,
opacity = 1,
glow = true,
className = '',
}: DrillBitSpinnerProps) {
const r = size / 2;
const outerR = r * 0.92;
const bladeLen = r * 0.62;
const bladeW = r * 0.18;
const coreR = r * 0.22;
const blades = [0, 120, 240].map((deg) => {
const rad = (deg * Math.PI) / 180;
return {
x: r + Math.cos(rad) * (bladeLen * 0.5),
y: r + Math.sin(rad) * (bladeLen * 0.5),
rotate: deg,
};
});
return (
<div
className={`relative pointer-events-none select-none ${className}`}
style={{
width: size,
height: size,
opacity,
// Radial gradient mask: the spinner fades out toward all edges.
// This prevents any hard clip when partially outside the container.
WebkitMaskImage: 'radial-gradient(circle at center, black 20%, transparent 70%)',
maskImage: 'radial-gradient(circle at center, black 20%, transparent 70%)',
}}
aria-hidden="true"
>
{/* Glow bloom (pulsing) */}
{glow && (
<motion.div
className="absolute inset-0 rounded-full"
style={{ background: `radial-gradient(circle, ${color}40 0%, transparent 70%)` }}
animate={{ scale: [1, 1.4, 1], opacity: [0.6, 0.2, 0.6] }}
transition={{ duration: 3, repeat: Infinity, ease: 'easeInOut' }}
/>
)}
{/* The rotating drill assembly */}
<motion.svg
viewBox={`0 0 ${size} ${size}`}
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="absolute inset-0 w-full h-full"
animate={{ rotate: 360 }}
transition={{ duration, repeat: Infinity, ease: 'linear' }}
>
{/* Outer halo ring */}
<circle
cx={r} cy={r} r={outerR}
stroke={color}
strokeWidth={size * 0.018}
strokeDasharray={`${outerR * 0.4} ${outerR * 0.15}`}
opacity={0.35}
/>
{/* Three cutting blades */}
{blades.map((blade, i) => (
<g key={i} transform={`rotate(${blade.rotate} ${r} ${r})`}>
<rect
x={r - bladeW / 2}
y={r - coreR}
width={bladeW}
height={bladeLen}
rx={bladeW / 2}
fill={color}
opacity={0.7}
/>
<circle
cx={r}
cy={r - coreR - bladeLen + bladeW / 2}
r={bladeW * 0.55}
fill={color}
opacity={0.9}
/>
</g>
))}
{/* Inner core ring */}
<circle
cx={r} cy={r} r={coreR + size * 0.025}
stroke={color}
strokeWidth={size * 0.022}
opacity={0.5}
/>
{/* Center nozzle */}
<circle cx={r} cy={r} r={coreR * 0.55} fill={color} opacity={0.85} />
<circle cx={r} cy={r} r={coreR * 0.22} fill="white" opacity={0.6} />
</motion.svg>
</div>
);
}