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

132 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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.
*
* - 3 rotating cutting blades at 120° intervals
* - Outer halo ring
* - Inner core circle
* - Continuous rotation with optional pulse glow
*
* Zero layout impact — position it absolutely in its parent.
*/
interface DrillBitSpinnerProps {
/** Diameter in pixels */
size?: number;
/** Color of the drill elements */
color?: string;
/** Rotation duration in seconds */
duration?: number;
/** Overall opacity 01 */
opacity?: number;
/** Whether to show a pulse glow ring */
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;
// 3 blade tip positions at 120° intervals
// Each blade: a rounded rectangle swept from center outward
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 }}
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})`}>
{/* Main blade body */}
<rect
x={r - bladeW / 2}
y={r - coreR}
width={bladeW}
height={bladeLen}
rx={bladeW / 2}
fill={color}
opacity={0.7}
transform={`rotate(0 ${r} ${r})`}
/>
{/* Blade tip accent */}
<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 (mud port) */}
<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>
);
}