diff --git a/components/blocks/InteractiveGermanyMap.tsx b/components/blocks/InteractiveGermanyMap.tsx index c28856e8c..a73b42396 100644 --- a/components/blocks/InteractiveGermanyMap.tsx +++ b/components/blocks/InteractiveGermanyMap.tsx @@ -1,13 +1,13 @@ 'use client'; -import React, { useState, useRef } from 'react'; +import React, { useState, useRef, useCallback, useMemo } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; -import { MapPin, Factory, Zap, CheckCircle2, ArrowUpRight } from 'lucide-react'; +import { MapPin, CheckCircle2, ArrowUpRight } from 'lucide-react'; import Image from 'next/image'; -import { useRouter, usePathname } from 'next/navigation'; +import { usePathname } from 'next/navigation'; import Link from 'next/link'; import { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder'; -import { Location, defaultLocations, minorLocations, projectLocations } from '@/lib/map-data'; +import { Location, projectLocations } from '@/lib/map-data'; import { standorteLocations } from '@/lib/standorte-data'; import { useLocale, useTranslations } from 'next-intl'; @@ -28,6 +28,59 @@ interface InteractiveGermanyMapProps { hideStandorte?: boolean; } +const MinorNode = React.memo(({ loc, isActive, onEnter, onLeave }: { loc: Location, isActive: boolean, onEnter: (loc: Location) => void, onLeave: () => void }) => ( +
onEnter(loc)} + onMouseLeave={onLeave} + onTouchStart={() => onEnter(loc)} + onClick={() => onEnter(loc)} + > +
+
+
+)); +MinorNode.displayName = 'MinorNode'; + +const MajorNode = React.memo(({ loc, isActive, idx, onEnter, onLeave }: { loc: Location, isActive: boolean, idx: number, onEnter: (loc: Location) => void, onLeave: () => void }) => { + const isHQ = loc.type === 'hq'; + const isBranch = loc.type === 'branch'; + return ( +
onEnter(loc)} + onMouseLeave={onLeave} + onTouchStart={() => onEnter(loc)} + onClick={() => onEnter(loc)} + > + {(isHQ || isBranch) && ( +
+ )} + + {(isHQ || isBranch) && } + +
+ ); +}); +MajorNode.displayName = 'MajorNode'; + export function InteractiveGermanyMap({ badge, title, @@ -40,22 +93,21 @@ export function InteractiveGermanyMap({ const [activeLocation, setActiveLocation] = useState(null); const hoverTimeoutRef = useRef(null); - const handleMouseEnter = (loc: Location) => { + const handleMouseEnter = useCallback((loc: Location) => { if (hoverTimeoutRef.current) { clearTimeout(hoverTimeoutRef.current); hoverTimeoutRef.current = null; } setActiveLocation(loc); - }; + }, []); - const handleMouseLeave = () => { + const handleMouseLeave = useCallback(() => { const timeout = setTimeout(() => { setActiveLocation(null); }, 200); hoverTimeoutRef.current = timeout; - }; + }, []); - const router = useRouter(); const pathname = usePathname(); const locale = useLocale(); const t = useTranslations('InteractiveGermanyMap'); @@ -67,14 +119,11 @@ export function InteractiveGermanyMap({ ]; const finalBadge = badge || tStandard('badge'); - // the map is mostly used with specific props, so we leave title & description to fallbacks if not provided const finalTitle = title || (locale === 'en' ? <>Nationwide
in operation for you. : <>Deutschlandweit
für Sie im Einsatz.); const finalDescription = description || (locale === 'en' ? 'From our strategic locations in Guben, Kirchheilingen and Bülstedt, we control and implement complex infrastructure projects nationwide.' : 'Von unseren strategischen Standorten in Guben, Kirchheilingen und Bülstedt steuern und realisieren wir komplexe Infrastrukturprojekte im gesamten Bundesgebiet.'); - const finalLocations = React.useMemo(() => { + const finalLocations = useMemo(() => { if (hideStandorte) return locations; - - // Ensure the 3 standorte are always included, without duplicating if already present by ID const base = [...locations]; standorteLocations.forEach(sl => { if (!base.some(l => l.id === sl.id)) { @@ -84,19 +133,28 @@ export function InteractiveGermanyMap({ return base; }, [locations, hideStandorte]); + const mapBackground = useMemo(() => ( +
+ {locale +
+ ), [locale]); + return (
- {/* Animated Border Glow */} {!isHero && } - {/* Background Effects */}
- {/* Content Left */}
@@ -117,7 +175,6 @@ export function InteractiveGermanyMap({ {finalDescription}

- {/* Industrial Stats Grid */}
{finalStats.map((stat, i) => { const StatCard = () => ( @@ -148,85 +205,32 @@ export function InteractiveGermanyMap({
- {/* Map Right */}
- {/* Map Container - Enforce strict aspect ratio matching the SVG (1024x1024) */}
- {/* Map SVG */} -
- {locale l.type === 'minor_node').map((loc, idx) => ( + -
+ ))} - {/* Minor Location Markers (The 100+ generic projects) */} - {finalLocations.filter(l => l.type === 'minor_node').map((loc, idx) => { - const isActive = activeLocation === loc; - return ( -
handleMouseEnter(loc)} - onMouseLeave={handleMouseLeave} - onTouchStart={() => handleMouseEnter(loc)} - onClick={() => handleMouseEnter(loc)} - > -
-
-
- )})} + {finalLocations.filter(l => l.type !== 'minor_node').map((loc, idx) => ( + + ))} - {/* Major Location Markers (HQ, Branch, Project) */} - {finalLocations.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; - - return ( -
handleMouseEnter(loc)} - onMouseLeave={handleMouseLeave} - onTouchStart={() => handleMouseEnter(loc)} - onClick={() => handleMouseEnter(loc)} - > - {/* Ping Animation for HQ / Branch */} - {(isHQ || isBranch) && ( -
- )} - - {/* Elegant Marker Dot */} - - {(isHQ || isBranch) && } - -
- ); - })} - - {/* Global Tooltip */} {activeLocation && (
- {activeLocation.featuredImage && (