feat: content-aware HDDBore and TrenchLaying animations with drillbit transition

Former-commit-id: 1a541fc31c5b24aa083c3441a9ab642c7d49da59
This commit is contained in:
2026-05-11 21:49:57 +02:00
parent e33dcdddac
commit 2e1e34a617
12 changed files with 236 additions and 214 deletions

View File

@@ -1,71 +0,0 @@
'use client';
import { motion } from 'framer-motion';
interface AbstractBoreProps {
className?: string;
invert?: boolean;
}
/**
* AbstractBore
*
* Visualizes Horizontal Directional Drilling (HDD).
* A smooth, parabolic underground arc. A beam of energy (the drill/cable)
* travels along this path, fading in gently at the start and fading out at the end.
* Purely abstract, no literal drill bits or circles.
*/
export function AbstractBore({ className = '', invert = false }: AbstractBoreProps) {
const color = invert ? 'rgba(10, 61, 40, 0.6)' : 'rgba(14, 122, 92, 0.6)';
const glowColor = invert ? 'rgba(10, 61, 40, 1)' : 'rgba(20, 184, 138, 1)';
// Parabolic path: Starts left, dips deep in the middle, comes up right
const pathD = "M -100 50 C 200 50 300 250 500 250 C 700 250 800 50 1100 50";
// The length of this cubic bezier is approx 1260
const pathLength = 1260;
// The length of the moving beam
const beamLength = 200;
return (
<svg
viewBox="0 0 1000 300"
preserveAspectRatio="none"
className={`absolute pointer-events-none select-none w-full ${className}`}
aria-hidden="true"
>
{/* Subtle track background */}
<path
d={pathD}
stroke={color}
strokeWidth="1"
fill="none"
opacity={0.15}
/>
{/* Moving Energy Beam */}
<motion.path
d={pathD}
stroke={glowColor}
strokeWidth="2"
fill="none"
strokeLinecap="round"
// Dasharray: beam length, then gap for the rest of the path
strokeDasharray={`${beamLength} ${pathLength}`}
animate={{
// Move from right off-screen to left off-screen (or vice versa)
// Starting at pathLength pushes the dash off the start of the curve.
// Ending at -beamLength pushes it completely off the end.
strokeDashoffset: [pathLength, -beamLength],
// Fade in during the first 20%, stay solid, fade out during last 20%
opacity: [0, 1, 1, 0]
}}
transition={{
duration: 6,
ease: "linear",
repeat: Infinity,
times: [0, 0.15, 0.85, 1] // Syncs with the offset to fade exactly at edges
}}
/>
</svg>
);
}

View File

@@ -1,70 +0,0 @@
'use client';
import { motion } from 'framer-motion';
interface AbstractTrenchProps {
className?: string;
invert?: boolean;
}
/**
* AbstractTrench
*
* Visualizes open trench construction (Offene Bauweise / Graben).
* A sharp, geometric profile representing a cut into the earth.
* A beam of energy flows through the trench, hugging the sharp corners,
* fading in and out at the horizontal edges.
* Purely abstract, architectural styling.
*/
export function AbstractTrench({ className = '', invert = false }: AbstractTrenchProps) {
const color = invert ? 'rgba(10, 61, 40, 0.6)' : 'rgba(14, 122, 92, 0.6)';
const glowColor = invert ? 'rgba(10, 61, 40, 1)' : 'rgba(20, 184, 138, 1)';
// Geometric trench path: straight, 45-deg drop, straight bottom, 45-deg rise, straight
const pathD = "M -100 50 L 300 50 L 350 200 L 650 200 L 700 50 L 1100 50";
// Length of this polyline is approx 1260
const pathLength = 1260;
const beamLength = 250;
return (
<svg
viewBox="0 0 1000 300"
preserveAspectRatio="none"
className={`absolute pointer-events-none select-none w-full ${className}`}
aria-hidden="true"
>
{/* Subtle track background */}
<path
d={pathD}
stroke={color}
strokeWidth="1"
fill="none"
strokeLinejoin="round"
opacity={0.15}
/>
{/* Moving Energy Beam */}
<motion.path
d={pathD}
stroke={glowColor}
strokeWidth="2"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
// Dasharray: beam length, then gap for the rest of the path
strokeDasharray={`${beamLength} ${pathLength}`}
animate={{
strokeDashoffset: [pathLength, -beamLength],
opacity: [0, 1, 1, 0]
}}
transition={{
duration: 7.5,
ease: "linear",
repeat: Infinity,
times: [0, 0.15, 0.85, 1], // Syncs with the offset to fade exactly at edges
delay: 1.5 // Offset from AbstractBore if used together
}}
/>
</svg>
);
}

View File

@@ -0,0 +1,93 @@
'use client';
import { motion } from 'framer-motion';
interface HDDBoreAnimationProps {
className?: string;
invert?: boolean;
}
/**
* HDDBoreAnimation
*
* Visualizes Horizontal Directional Drilling (Spülbohren).
* 1. A segmented drill rod (Gestänge) pushes through a subterranean arc (Pilotbohrung).
* 2. It pulls back a thick, solid orange pipe (Rohreinzug).
*
* This directly ties to E-TIB's core services, visually representing the
* physical act of laying a conduit pipe under an obstacle.
*/
export function HDDBoreAnimation({ className = '', invert = false }: HDDBoreAnimationProps) {
// Track color
const color = invert ? '#0a3d28' : '#0e7a5c';
// Bright orange for the conduit pipe (Kabelschutzrohr)
const pipeColor = '#ff6b00';
// Parabolic path under an implied obstacle
const pathD = "M 50 100 C 300 100 400 250 600 250 C 800 250 900 100 1150 100";
// Length of this cubic bezier is approx 1300
const pathLen = 1300;
return (
<svg
viewBox="0 0 1200 300"
preserveAspectRatio="none"
className={`absolute pointer-events-none select-none w-full ${className}`}
aria-hidden="true"
>
{/* Planned path (subtle) */}
<path
d={pathD}
stroke={color}
strokeWidth="2"
strokeDasharray="5 15"
fill="none"
opacity={0.15}
/>
{/* Step 1: Drill Rod (Pilotbohrung) pushing Left -> Right */}
<motion.path
d={pathD}
stroke={color}
strokeWidth="4"
fill="none"
strokeLinecap="round"
// Segmented rod look (40px rod, 5px gap)
strokeDasharray={`40 5 ${pathLen}`}
animate={{
strokeDashoffset: [pathLen, 0, 0, 0, pathLen],
opacity: [0, 1, 1, 0, 0]
}}
transition={{
duration: 10,
repeat: Infinity,
ease: "easeInOut",
times: [0, 0.4, 0.45, 0.46, 1] // 0-4s: push, 4-4.5s: pause, 4.6s: vanish
}}
/>
{/* Step 2: Cable/Pipe Pullback (Rohreinzug) Right -> Left */}
<motion.path
d={pathD}
stroke={pipeColor}
strokeWidth="8"
fill="none"
strokeLinecap="round"
// Solid pipe
strokeDasharray={`${pathLen} ${pathLen}`}
animate={{
// Starts hidden at the right (offset = -pathLen)
// Pulls back to left (offset = 0)
strokeDashoffset: [-pathLen, -pathLen, 0, 0, -pathLen],
opacity: [0, 0, 1, 1, 0]
}}
transition={{
duration: 10,
repeat: Infinity,
ease: "easeInOut",
times: [0, 0.45, 0.85, 0.95, 1] // 0-4.5s: hidden, 4.5-8.5s: pull back, 8.5-9.5s: rest, 10s: reset
}}
/>
</svg>
);
}

View File

@@ -0,0 +1,100 @@
'use client';
import { motion } from 'framer-motion';
interface TrenchLayingAnimationProps {
className?: string;
invert?: boolean;
}
/**
* TrenchLayingAnimation
*
* Visualizes open trench construction (Offene Bauweise / Graben).
* 1. A trench is cut into the earth line.
* 2. A thick orange conduit pipe (Leerrohr) is laid into the trench.
* 3. The trench is backfilled with earth.
*
* This directly ties to E-TIB's core services, showing the process
* of laying cables rather than just abstract geometry.
*/
export function TrenchLayingAnimation({ className = '', invert = false }: TrenchLayingAnimationProps) {
const color = invert ? '#0a3d28' : '#0e7a5c';
const earthColor = invert ? 'rgba(10, 61, 40, 0.4)' : 'rgba(14, 122, 92, 0.4)';
const pipeColor = '#ff6b00';
// The trench profile: flat, 45-deg drop, flat bottom, 45-deg rise, flat
const trenchPath = "M 50 100 L 300 100 L 350 200 L 850 200 L 900 100 L 1150 100";
// The dug out area (used for the backfill animation)
const trenchFill = "M 300 100 L 350 200 L 850 200 L 900 100 Z";
// Length of the polyline is approx 1300
const pathLen = 1300;
return (
<svg
viewBox="0 0 1200 300"
preserveAspectRatio="none"
className={`absolute pointer-events-none select-none w-full ${className}`}
aria-hidden="true"
>
{/* Ground surface line (always visible outside the trench) */}
<line x1="0" y1="100" x2="1200" y2="100" stroke={color} strokeWidth="2" opacity={0.3} />
{/* Step 1: Trench cutting Left -> Right */}
<motion.path
d={trenchPath}
stroke={color}
strokeWidth="3"
fill="none"
strokeLinejoin="round"
strokeDasharray={`${pathLen} ${pathLen}`}
animate={{
strokeDashoffset: [pathLen, 0, 0, 0, pathLen]
}}
transition={{
duration: 10,
repeat: Infinity,
times: [0, 0.25, 0.8, 0.9, 1], // Cuts quickly (0-2.5s), stays open, closes at 9s
ease: "linear"
}}
/>
{/* Step 2: Pipe Laying (Drops into trench closely behind the cut) */}
<motion.path
d={trenchPath}
stroke={pipeColor}
strokeWidth="6"
fill="none"
strokeLinejoin="round"
strokeLinecap="round"
strokeDasharray={`${pathLen} ${pathLen}`}
animate={{
strokeDashoffset: [pathLen, pathLen, 0, 0, pathLen],
opacity: [0, 1, 1, 0, 0]
}}
transition={{
duration: 10,
repeat: Infinity,
times: [0, 0.1, 0.4, 0.8, 1], // Starts unspooling at 1s, finishes laying at 4s
ease: "linear"
}}
/>
{/* Step 3: Sand/Earth backfill (Fades in over the pipe) */}
<motion.path
d={trenchFill}
fill={earthColor}
initial={{ opacity: 0 }}
animate={{
opacity: [0, 0, 1, 1, 0]
}}
transition={{
duration: 10,
repeat: Infinity,
times: [0, 0.4, 0.5, 0.8, 1], // Fades in between 4s and 5s, covers the pipe
ease: "easeInOut"
}}
/>
</svg>
);
}