From 5678ddcfd9c344b62b98f6e69586c3fa8ca61ffb Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Tue, 26 May 2026 11:31:31 +0200 Subject: [PATCH] fix: interactive map visuals and docker dev setup - Removed confusing SVG lines from map - Restored distinct clickable markers for major references - Fixed corepack failing to install pnpm v11 in Docker by explicitly forcing v10 - Added direct port mapping 3001:3001 to bypass proxy issues --- Dockerfile.dev | 4 +- app/[locale]/[slug]/page.tsx | 8 + app/[locale]/page.tsx | 6 + app/[locale]/referenzen/page.tsx | 9 +- components/blocks/DataGridPulse.tsx | 52 ++ components/blocks/DeepDrillAnimation.tsx | 64 ++ components/blocks/GrowthChart.tsx | 80 ++ components/blocks/InteractiveGermanyMap.tsx | 294 ++++--- components/blocks/ScaleOfImpact.tsx | 102 +++ components/ui/AnimatedCounter.tsx | 48 ++ components/ui/index.ts | 1 + content/de/bohrtechnik.mdx | 2 + content/de/home.mdx | 2 + content/de/kabeltiefbau.mdx | 2 + content/de/ueber-uns.mdx | 4 +- content/en/bohrtechnik.mdx | 2 + content/en/home.mdx | 2 + content/en/kabeltiefbau.mdx | 2 + content/en/ueber-uns.mdx | 2 + docker-compose.dev.yml | 2 + lib/map-data.ts | 854 +++++++++++++++++++- 21 files changed, 1367 insertions(+), 175 deletions(-) create mode 100644 components/blocks/DataGridPulse.tsx create mode 100644 components/blocks/DeepDrillAnimation.tsx create mode 100644 components/blocks/GrowthChart.tsx create mode 100644 components/blocks/ScaleOfImpact.tsx create mode 100644 components/ui/AnimatedCounter.tsx diff --git a/Dockerfile.dev b/Dockerfile.dev index bd6326c7e..2b70290ec 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -5,8 +5,8 @@ RUN apk add --no-cache libc6-compat python3 make g++ curl WORKDIR /app -# Enable corepack for pnpm -RUN corepack enable +# Install pnpm v10 explicitly (corepack defaults to v11 which requires Node 22) +RUN npm install -g pnpm@10.18.3 # Pre-set the pnpm store directory ENV PNPM_HOME="/pnpm" diff --git a/app/[locale]/[slug]/page.tsx b/app/[locale]/[slug]/page.tsx index 11db4efc8..66d542117 100644 --- a/app/[locale]/[slug]/page.tsx +++ b/app/[locale]/[slug]/page.tsx @@ -26,8 +26,12 @@ import { FaqBlock } from '@/components/blocks/FaqBlock'; import { CertificatesBlock } from '@/components/blocks/CertificatesBlock'; import { HeroSection } from '@/components/blocks/HeroSection'; import { TeamGrid } from '@/components/blocks/TeamGrid'; +import { DeepDrillAnimation } from '@/components/blocks/DeepDrillAnimation'; +import { DataGridPulse } from '@/components/blocks/DataGridPulse'; +import { GrowthChart } from '@/components/blocks/GrowthChart'; import JsonLd from '@/components/JsonLd'; import { Button } from '@/components/ui/Button'; +import { AnimatedCounter } from '@/components/ui'; const mdxComponents = { @@ -47,6 +51,10 @@ const mdxComponents = { TeamGrid, JsonLd, Button, + AnimatedCounter, + GrowthChart, + DeepDrillAnimation, + DataGridPulse, // Standard HTML element mapping for consistent E-TIB typography h1: (props: any) => , // Hidden because Hero handles H1 h2: (props: any) => , diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx index eb5cfe2e3..271b43010 100644 --- a/app/[locale]/page.tsx +++ b/app/[locale]/page.tsx @@ -18,6 +18,9 @@ import JsonLd from '@/components/JsonLd'; import { Button } from '@/components/ui/Button'; import { Heading } from '@/components/ui/Heading'; +import { AnimatedCounter } from '@/components/ui'; +import { InteractiveGermanyMap } from '@/components/blocks/InteractiveGermanyMap'; +import { ScaleOfImpact } from '@/components/blocks/ScaleOfImpact'; import { getAllReferences } from '@/lib/references'; const mdxComponents = (references: any[]) => ({ @@ -31,6 +34,9 @@ const mdxComponents = (references: any[]) => ({ JsonLd, Button, Heading, + AnimatedCounter, + InteractiveGermanyMap, + ScaleOfImpact, }); export async function generateMetadata(props: any): Promise { diff --git a/app/[locale]/referenzen/page.tsx b/app/[locale]/referenzen/page.tsx index 96f2b94da..b2d449a1a 100644 --- a/app/[locale]/referenzen/page.tsx +++ b/app/[locale]/referenzen/page.tsx @@ -8,7 +8,7 @@ import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button'; import { SITE_URL } from '@/lib/schema'; import Image from 'next/image'; import { InteractiveGermanyMap } from '@/components/blocks/InteractiveGermanyMap'; -import { defaultLocations } from '@/lib/map-data'; +import { defaultLocations, minorLocations } from '@/lib/map-data'; interface PageProps { params: Promise<{ @@ -52,8 +52,8 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca const references = await getAllReferences(locale); - // Enrich the static map locations with dynamic project data (images, details) - const enrichedLocations = defaultLocations.map(loc => { + const allLocations = [...defaultLocations, ...minorLocations]; + const enrichedLocations = allLocations.map(loc => { if (loc.type === 'project') { const ref = references.find(r => r.slug === loc.id); if (ref) { @@ -71,9 +71,10 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca }); return ( -
+
{/* Map Hero Section */} Erfolgreich umgesetzte
Projekte.} description="Vom Breitbandausbau bis zur komplexen 110kV-Trasse: Ein Auszug unserer bundesweiten Projekte, bei denen wir Infrastruktur für die Zukunft geschaffen haben. Entdecken Sie unsere Standorte." diff --git a/components/blocks/DataGridPulse.tsx b/components/blocks/DataGridPulse.tsx new file mode 100644 index 000000000..72376ebd3 --- /dev/null +++ b/components/blocks/DataGridPulse.tsx @@ -0,0 +1,52 @@ +'use client'; + +import React from 'react'; +import { motion } from 'framer-motion'; +import { useLocale } from 'next-intl'; + +export function DataGridPulse() { + const locale = useLocale(); + return ( +
+ + {/* Blueprint grid background */} +
+ + {/* Pulsing horizontal lines (Data Flow) */} + + + +
+

+ {locale === 'en' ? 'Network Expansion in Numbers' : 'Netzausbau in Zahlen'} +

+ +
+
+ 372.161m +
+

+ {locale === 'en' + ? 'Cable routes laid since 2023. A massive infrastructure achievement, driven by our own machinery.' + : 'Verlegte Kabeltrassen seit 2023. Eine massive Infrastrukturleistung, getragen von unserem eigenen Maschinenpark.'} +

+
+
+ +
+ ); +} diff --git a/components/blocks/DeepDrillAnimation.tsx b/components/blocks/DeepDrillAnimation.tsx new file mode 100644 index 000000000..ceea70ce6 --- /dev/null +++ b/components/blocks/DeepDrillAnimation.tsx @@ -0,0 +1,64 @@ +'use client'; + +import React, { useRef } from 'react'; +import { motion, useScroll, useTransform } from 'framer-motion'; +import { useLocale } from 'next-intl'; + +export function DeepDrillAnimation() { + const containerRef = useRef(null); + const locale = useLocale(); + + const { scrollYProgress } = useScroll({ + target: containerRef, + offset: ["start center", "end center"] + }); + + const drillDepth = useTransform(scrollYProgress, [0, 1], ["0%", "100%"]); + + return ( +
+ +
+ +
+ +

+ {locale === 'en' ? 'Precision in Depth' : 'Präzision in der Tiefe'} +

+

+ {locale === 'en' + ? 'Horizontal Directional Drilling (HDD) allows us to cross obstacles and rivers with extreme care.' + : 'Horizontalspülbohrverfahren (HDD) erlaubt uns, Hindernisse und Flüsse extrem schonend zu unterqueren.'} +

+ + {/* The Drill Path */} +
+ + {/* The Drill Head */} +
+ +
+ + {/* The Reveal Data */} + +
+ 102.771m +
+
+ {locale === 'en' ? 'Drilled distance since 2023' : 'Gebohrte Strecke seit 2023'} +
+
+ +
+
+ ); +} diff --git a/components/blocks/GrowthChart.tsx b/components/blocks/GrowthChart.tsx new file mode 100644 index 000000000..15869c7c2 --- /dev/null +++ b/components/blocks/GrowthChart.tsx @@ -0,0 +1,80 @@ +'use client'; + +import React from 'react'; +import { motion } from 'framer-motion'; + +export function GrowthChart() { + const data = [ + { year: '2023', hdd: 29436, kabel: 76727 }, + { year: '2024', hdd: 28680, kabel: 131964 }, + { year: '2025', hdd: 44655, kabel: 163470 }, + ]; + + const maxVal = 163470; // Highest value for scaling + + return ( +
+
+

Bewiesene Leistungsfähigkeit

+

Unsere verlässliche Baukapazität der letzten drei Jahre (in Metern)

+
+ +
+ {data.map((item, index) => { + const hddWidth = (item.hdd / maxVal) * 100; + const kabelWidth = (item.kabel / maxVal) * 100; + + return ( +
+
{item.year}
+
+ {/* Kabelverlegung Bar */} +
+
+ +
+
+ Kabel: {new Intl.NumberFormat('de-DE').format(item.kabel)}m +
+
+ + {/* HDD Bar */} +
+
+ +
+
+ HDD: {new Intl.NumberFormat('de-DE').format(item.hdd)}m +
+
+
+
+ ); + })} +
+ +
+
+
+ Kabelverlegung +
+
+
+ HDD Spülbohrung +
+
+
+ ); +} diff --git a/components/blocks/InteractiveGermanyMap.tsx b/components/blocks/InteractiveGermanyMap.tsx index 9a1677166..70d3923d2 100644 --- a/components/blocks/InteractiveGermanyMap.tsx +++ b/components/blocks/InteractiveGermanyMap.tsx @@ -6,7 +6,10 @@ import { MapPin, Factory, Zap, CheckCircle2, ArrowUpRight } from 'lucide-react'; import Image from 'next/image'; import { useRouter } from 'next/navigation'; import { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder'; -import { Location, defaultLocations } from '@/lib/map-data'; +import { Location, defaultLocations, minorLocations } from '@/lib/map-data'; +import { useLocale } from 'next-intl'; + +const allLocations = [...defaultLocations, ...minorLocations]; interface Stat { value: string; @@ -31,15 +34,17 @@ export function InteractiveGermanyMap({ { value: '100', suffix: '%', label: 'Überregionale Reichweite' }, { value: '2', suffix: '+', label: 'Operative Standorte' }, ], - locations = defaultLocations, + locations = allLocations, isHero = false }: InteractiveGermanyMapProps) { const [activeLocation, setActiveLocation] = useState(null); const router = useRouter(); + const locale = useLocale(); const hq = locations.find((l) => l.type === 'hq'); const branch = locations.find((l) => l.type === 'branch'); const projects = locations.filter((l) => l.type === 'project'); + const minorNodes = locations.filter((l) => l.type === 'minor_node'); return (
@@ -91,80 +96,49 @@ export function InteractiveGermanyMap({
{/* Map Right */} -
+
{/* Map Container - Enforce strict aspect ratio matching the SVG (1024x1024) */} -
+
+ {/* Map SVG */} -
+
Deutschlandkarte
- {/* Connections (Lines) */} - - {hq && branch && ( - - )} - {hq && projects.map((proj, idx) => ( - - ))} - + {/* Minor Location Markers (The 100+ generic projects) */} + {locations.filter(l => l.type === 'minor_node').map((loc, idx) => ( +
setActiveLocation(loc)} + onMouseLeave={() => setActiveLocation(null)} + > +
+
+
+ ))} - {/* Location Pins */} - {locations.map((loc, idx) => { + {/* Major & Reference Location Markers (HQ, Branch, Project) */} + {locations.filter(l => l.type !== 'minor_node').map((loc, idx) => { const isHQ = loc.type === 'hq'; const isBranch = loc.type === 'branch'; const isActive = activeLocation?.id === loc.id; - const isNearLeft = loc.x < 50; - const isNearRight = loc.x >= 50; - const isNearTop = loc.y < 55; // 55% threshold to strongly avoid the top header - - const tooltipClasses = [ - 'absolute w-[280px] sm:w-64 bg-white text-neutral-dark p-5 rounded-2xl shadow-2xl z-[9999] border border-neutral-100', - isNearLeft ? 'left-1/2 sm:left-0 -translate-x-1/2 sm:translate-x-0' : 'left-1/2 sm:left-auto sm:right-0 -translate-x-1/2 sm:translate-x-0', - isNearTop - ? 'top-full mt-4 before:absolute before:-top-6 before:left-0 before:w-full before:h-6' - : 'bottom-full mb-4 before:absolute before:-bottom-6 before:left-0 before:w-full before:h-6' - ].join(' '); - - const arrowClasses = [ - 'absolute border-8 border-transparent drop-shadow-sm hidden sm:block', - isNearLeft ? 'left-6' : 'right-6', - isNearTop ? '-top-4 border-b-white border-t-0' : '-bottom-4 border-t-white border-b-0' - ].join(' '); - return (
setActiveLocation(loc)} onMouseLeave={() => setActiveLocation(null)} @@ -177,121 +151,125 @@ export function InteractiveGermanyMap({
)} - {/* Marker Dot */} + {/* Elegant Marker Dot */} - {(isHQ || isBranch) && } + {(isHQ || isBranch) && } - - {/* Tooltip */} - - {isActive && ( - - {loc.featuredImage && ( - - {loc.name} -
-
- {isHQ ? : } - {loc.name} -
- - )} - -
- {!loc.featuredImage && ( - - {isHQ ? : } - {loc.name} - - )} - - - {loc.description} - - - {loc.details && ( -
    - {loc.details.map((detail, i) => ( - -
    - -
    - {detail} -
    - ))} -
- )} - - {loc.href && ( - - Zum Projekt - - - )} -
- - {/* Arrow Down/Up */} -
- - )} -
); })} + + {/* Global Tooltip */} + + {activeLocation && ( + +
+ + {activeLocation.featuredImage && ( +
+ {activeLocation.name} +
+
+ )} + +
+
+ + + {activeLocation.type === 'hq' ? 'Hauptsitz' : + activeLocation.type === 'branch' ? 'Niederlassung' : + activeLocation.type === 'minor_node' ? 'Projekt' : 'Referenz'} + +
+
+ {activeLocation.name} +
+ {activeLocation.type !== 'minor_node' ? ( +
+ {activeLocation.description} +
+ ) : ( +
+ {activeLocation.description === 'wind' ? 'Errichtung Leitungstrassen & Netzanbindung für Windpark-Infrastruktur.' : + activeLocation.description === 'pv' ? 'Bau von Mittelspannungstrassen zur Einspeisung der PV-Anlage.' : + activeLocation.description === 'fiber' ? 'Komplexer FTTx Breitbandausbau inkl. LWL-Montage & Tiefbau.' : + activeLocation.description === 'power' ? '110kV Hochspannungstrasse Erdkabelverlegung.' : + activeLocation.description === 'battery' ? 'Infrastrukturausbau für BESS (Batteriespeicher).' : + 'Komplexes Infrastrukturprojekt (Tiefbau & HDD).'} +
+ )} + + {(activeLocation.details || activeLocation.type === 'minor_node') && ( +
    + {activeLocation.type === 'minor_node' ? ( + <> +
  • + + { + activeLocation.description === 'wind' ? 'Netzanbindung Windpark' : + activeLocation.description === 'pv' ? 'Einspeisung PV-Anlage' : + activeLocation.description === 'fiber' ? 'FTTx Infrastruktur' : + activeLocation.description === 'power' ? '110kV Trasse' : + activeLocation.description === 'battery' ? 'BESS Anschluss' : 'Infrastrukturprojekt' + } +
  • +
  • + + Tiefbau & Spülbohrung (HDD) +
  • + + ) : ( + activeLocation.details?.map((detail, i) => ( +
  • + + {detail} +
  • + )) + )} +
+ )} + + {activeLocation.href && ( +
+ {locale === 'en' ? 'Learn more' : 'Zum Projekt'} + +
+ )} +
+
+ + )} +
-
diff --git a/components/blocks/ScaleOfImpact.tsx b/components/blocks/ScaleOfImpact.tsx new file mode 100644 index 000000000..e12a9ffdd --- /dev/null +++ b/components/blocks/ScaleOfImpact.tsx @@ -0,0 +1,102 @@ +'use client'; + +import React, { useRef } from 'react'; +import { motion, useScroll, useTransform } from 'framer-motion'; +import { useLocale } from 'next-intl'; + +export function ScaleOfImpact() { + const containerRef = useRef(null); + const locale = useLocale(); + + // Track scroll progress through this container + const { scrollYProgress } = useScroll({ + target: containerRef, + offset: ["start end", "end start"] + }); + + // Map scroll progress to SVG path length + const pathLength = useTransform(scrollYProgress, [0.2, 0.8], [0, 1]); + + // Opacity for the massive background numbers + const bgOpacity = useTransform(scrollYProgress, [0.1, 0.4, 0.8, 1], [0, 0.1, 0.1, 0]); + + return ( +
+ {/* Massive Watermark Numbers */} + +
+ 474.932 +
+
+ + {/* Foreground Content */} +
+ +

+ {locale === 'en' ? ( + <>An infrastructure
that connects the country. + ) : ( + <>Eine Infrastruktur,
die das Land verbindet. + )} +

+ +

+ {locale === 'en' + ? "We don't just talk about the energy transition. We build it. In the last 36 months alone, we have completed a route that stretches from our headquarters in Guben all the way to Munich." + : "Wir sprechen nicht nur über die Energiewende. Wir bauen sie. Allein in den letzten 36 Monaten haben wir eine Strecke realisiert, die von unserem Hauptsitz in Guben bis nach München reicht." + } +

+ + {/* Abstract SVG Line Animation */} +
+ + {/* Background dim line */} + + {/* Glowing animated line */} + + +
+ + {/* Static Data Grid (No jumping text) */} +
+
+
100+
+
{locale === 'en' ? 'Major Projects' : 'Großprojekte'}
+
+
+
372km
+
{locale === 'en' ? 'Cable Laying' : 'Kabelverlegung'}
+
+
+
102km
+
{locale === 'en' ? 'HDD Drilling' : 'Spülbohrung'}
+
+
+
+
+ ); +} diff --git a/components/ui/AnimatedCounter.tsx b/components/ui/AnimatedCounter.tsx new file mode 100644 index 000000000..b22b734ab --- /dev/null +++ b/components/ui/AnimatedCounter.tsx @@ -0,0 +1,48 @@ +'use client'; + +import React, { useEffect, useState } from 'react'; +import { motion, useMotionValue, useTransform, animate, useInView } from 'framer-motion'; + +interface AnimatedCounterProps { + value: number; + duration?: number; + delay?: number; + suffix?: string; + prefix?: string; + className?: string; +} + +export function AnimatedCounter({ + value, + duration = 2, + delay = 0, + suffix = '', + prefix = '', + className = '', +}: AnimatedCounterProps) { + const count = useMotionValue(0); + const rounded = useTransform(count, (latest) => { + // Format with thousands separator + return new Intl.NumberFormat('de-DE').format(Math.round(latest)); + }); + + const ref = React.useRef(null); + const inView = useInView(ref, { once: true, margin: "-100px" }); + + useEffect(() => { + if (inView) { + const controls = animate(count, value, { + duration: duration, + delay: delay, + ease: 'easeOut', + }); + return controls.stop; + } + }, [inView, value, duration, delay, count]); + + return ( + + {prefix}{rounded}{suffix} + + ); +} diff --git a/components/ui/index.ts b/components/ui/index.ts index 2063a1a5a..d91c3bd8b 100644 --- a/components/ui/index.ts +++ b/components/ui/index.ts @@ -7,5 +7,6 @@ export * from './Card'; export * from './Badge'; export * from './Input'; export * from './Textarea'; +export * from './AnimatedCounter'; export * from './Label'; export * from './Callout'; diff --git a/content/de/bohrtechnik.mdx b/content/de/bohrtechnik.mdx index b153b890f..87cc35197 100644 --- a/content/de/bohrtechnik.mdx +++ b/content/de/bohrtechnik.mdx @@ -29,6 +29,8 @@ layout: "fullBleed" Mit unserem spezialisierten Gerätepark beherrschen wir die unterschiedlichsten Verfahren. Beim **Horizontalspülbohrverfahren (HDD)** steuern wir den Bohrkopf punktgenau unter der Erde hindurch. Wir realisieren dabei Bohrlängen von bis zu 250 Metern am Stück und können Schutzrohre mit einem Durchmesser von bis zu 400 mm einziehen. Für kürzere Distanzen, wie beispielsweise schnelle und schonende Hausanschlüsse, setzen wir unsere **Erdraketen** (Bodenverdrängungsverfahren) ein. Hierbei erreichen wir Durchörterungslängen von bis zu 15 Metern für Rohre bis 160 mm Durchmesser – ideal um Vorgärten und Einfahrten komplett unversehrt zu lassen. + +
+ + diff --git a/content/de/kabeltiefbau.mdx b/content/de/kabeltiefbau.mdx index 3e0003c2f..b3723fa0b 100644 --- a/content/de/kabeltiefbau.mdx +++ b/content/de/kabeltiefbau.mdx @@ -31,6 +31,8 @@ layout: "fullBleed" Durch unsere langjährige Erfahrung erkennen wir Problemstellungen frühzeitig und erarbeiten für unsere Auftraggeber im Vorfeld sichere und zufriedenstellende Lösungen. Die E-TIB GmbH hat sich voll und ganz auf den Kabelleitungstiefbau spezialisiert. Wir bieten Ihnen das komplette Leistungsspektrum zur Errichtung von Kabeltrassen aus einer Hand – mit eigenem, hochmodernem Maschinenpark und festangestelltem Fachpersonal aus der Region. In direkter Zusammenarbeit mit unserem Partnerbüro, der E-TIB Ingenieurgesellschaft mbH, können wir zudem frühzeitig Planungsfehler ausschließen und selbst die komplexesten Dokumentationsanforderungen souverän erfüllen. + +
- + - +
+ + + + diff --git a/content/en/kabeltiefbau.mdx b/content/en/kabeltiefbau.mdx index ac4b2e102..19d2e8f0a 100644 --- a/content/en/kabeltiefbau.mdx +++ b/content/en/kabeltiefbau.mdx @@ -53,6 +53,8 @@ layout: "fullBleed" ]} /> + +
+ +