feat: distribute animated DrillBitSpinner across all major components as a background watermark

Former-commit-id: ad28a5d93b773be795cd0d73fd6ed2accd257a10
This commit is contained in:
2026-05-11 11:00:07 +02:00
parent 8fc4cadd19
commit c97ee638d9
7 changed files with 180 additions and 29 deletions

View File

@@ -0,0 +1,131 @@
'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>
);
}