diff --git a/app/[locale]/[slug]/page.tsx b/app/[locale]/[slug]/page.tsx index 3a442f855..4857f9167 100644 --- a/app/[locale]/[slug]/page.tsx +++ b/app/[locale]/[slug]/page.tsx @@ -185,7 +185,7 @@ export default async function Page(props: { params: Promise<{ locale: string; sl
{/* Generic Hero Section (only for standard pages) */} {!isFullBleed && ( -
+
diff --git a/app/[locale]/blog/[slug]/page.tsx b/app/[locale]/blog/[slug]/page.tsx index fb76bd5a1..83a4ecaf7 100644 --- a/app/[locale]/blog/[slug]/page.tsx +++ b/app/[locale]/blog/[slug]/page.tsx @@ -99,7 +99,7 @@ export default async function BlogPost({ params }: BlogPostProps) { {/* Featured Image Header */} {post.frontmatter.featuredImage ? ( -
+
{/* Hero Section - Immersive Magazine Feel */} -
+
{featuredPost && featuredPost.frontmatter.featuredImage && ( <> {/* Hero Section */} -
+
{reference.frontmatter.featuredImage && (
return { title: 'Referenzen | E-TIB Gruppe', - description: 'Erfolgreich abgeschlossene Projekte der E-TIB Gruppe im Bereich Kabeltiefbau, Spülbohrtechnik und Netzinfrastruktur.', + description: 'Erfolgreich abgeschlossene Projekte der E-TIB Gruppe im Bereich Kabelnetzbau, Spülbohrtechnik und Netzinfrastruktur.', alternates: { canonical: `${SITE_URL}/${locale}/referenzen`, languages: { @@ -91,7 +91,7 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca locations={enrichedLocations} stats={[ { value: '100', suffix: '%', label: locale === 'en' ? 'Nationwide Reach' : 'Überregionale Reichweite' }, - { value: enrichedLocations.length.toString(), suffix: '+', label: locale === 'en' ? 'Completed Projects' : 'Abgeschlossene Projekte' } + { value: '200', suffix: '+', label: locale === 'en' ? 'Completed Projects' : 'Abgeschlossene Projekte' } ]} /> diff --git a/app/[locale]/standorte/[slug]/page.tsx b/app/[locale]/standorte/[slug]/page.tsx index 936391ee5..37caced4a 100644 --- a/app/[locale]/standorte/[slug]/page.tsx +++ b/app/[locale]/standorte/[slug]/page.tsx @@ -65,7 +65,7 @@ export default async function StandortDetail(props: { params: Promise<{ locale: return (
{/* Generic Hero Section to match standard pages */} -
+
diff --git a/components/blocks/BenefitGrid.tsx b/components/blocks/BenefitGrid.tsx index dd2cee38f..7df3c4f1e 100644 --- a/components/blocks/BenefitGrid.tsx +++ b/components/blocks/BenefitGrid.tsx @@ -75,13 +75,13 @@ const Icons = { export function BenefitGrid({ badge, title, description, benefits }: BenefitGridProps) { return ( -
+
{badge &&

{badge}

} {title && ( -

+

{title}

)} diff --git a/components/blocks/CertificatesBlock.tsx b/components/blocks/CertificatesBlock.tsx index 3061ff75a..5377e130b 100644 --- a/components/blocks/CertificatesBlock.tsx +++ b/components/blocks/CertificatesBlock.tsx @@ -99,12 +99,12 @@ export function CertificatesBlock({ badge, title, description, certificates = de if (!isMounted) { return ( -
+
{!hideHeader && (

{badgeText}

-

{titleText}

+

{titleText}

{descriptionText}

)} @@ -114,7 +114,7 @@ export function CertificatesBlock({ badge, title, description, certificates = de } return ( -
+
{/* Background Accents */}
@@ -129,7 +129,7 @@ export function CertificatesBlock({ badge, title, description, certificates = de transition={{ duration: 0.5 }} >

{badgeText}

-

{titleText}

+

{titleText}

{descriptionText}

@@ -156,7 +156,7 @@ export function CertificatesBlock({ badge, title, description, certificates = de key={index} {...wrapperProps as any} variants={itemVariants} - className={`group relative rounded-2xl p-8 flex flex-col justify-between overflow-hidden transition-all duration-500 hover:shadow-xl hover:-translate-y-1 border ${ + className={`group relative rounded-2xl p-6 md:p-8 flex flex-col justify-between overflow-hidden transition-all duration-500 hover:shadow-xl hover:-translate-y-1 border ${ isIso ? 'bg-neutral-dark text-white border-neutral-800 shadow-lg' : 'bg-white text-neutral-dark border-neutral-200 shadow-md' diff --git a/components/blocks/CompanyTimeline.tsx b/components/blocks/CompanyTimeline.tsx index 9d3af276f..87a5463c8 100644 --- a/components/blocks/CompanyTimeline.tsx +++ b/components/blocks/CompanyTimeline.tsx @@ -80,18 +80,18 @@ export function CompanyTimeline({ const lineHeight = useTransform(scrollYProgress, [0, 1], ["0%", "100%"]); return ( -
+
{/* Background Decor */}
-
+
{finalBadge}
-

+

{finalTitle}

diff --git a/components/blocks/CompetenceBentoGrid.tsx b/components/blocks/CompetenceBentoGrid.tsx index 058060543..77bfd76bc 100644 --- a/components/blocks/CompetenceBentoGrid.tsx +++ b/components/blocks/CompetenceBentoGrid.tsx @@ -62,12 +62,12 @@ export function CompetenceBentoGrid(props: CompetenceBentoGridProps) { // Static fallback for SSR if (!isMounted) { return ( -
+

{badge}

-

{title}

+

{title}

@@ -79,12 +79,12 @@ export function CompetenceBentoGrid(props: CompetenceBentoGridProps) { } return ( -
+
-
-
+
+

{badge}

-

{title}

+

{title}

-
+
{items.map((item, idx) => { const isLarge = item.size === 'large'; const isMedium = item.size === 'medium'; @@ -177,7 +177,7 @@ export function CompetenceBentoGrid(props: CompetenceBentoGridProps) { {item.tag} )} -

+

{item.title}

{item.description && ( diff --git a/components/blocks/ContactSection.tsx b/components/blocks/ContactSection.tsx index ca28ba92d..1b58d0b43 100644 --- a/components/blocks/ContactSection.tsx +++ b/components/blocks/ContactSection.tsx @@ -52,7 +52,7 @@ export const ContactSection: React.FC = (props) => { viewport={{ once: true, margin: "-50px" }} > Direktkontakt - Wir sind für Sie da. + Wir sind für Sie da. Haben Sie Fragen zu unseren Leistungen oder möchten Sie ein konkretes Projekt besprechen? Rufen Sie uns an oder schreiben Sie uns eine E-Mail. diff --git a/components/blocks/DataGridPulse.tsx b/components/blocks/DataGridPulse.tsx index 54101e03c..16790a2d6 100644 --- a/components/blocks/DataGridPulse.tsx +++ b/components/blocks/DataGridPulse.tsx @@ -7,7 +7,7 @@ import { useLocale } from 'next-intl'; export function DataGridPulse() { const locale = useLocale(); return ( -
+
{/* Blueprint grid background */}
-
+
372.161m

diff --git a/components/blocks/DeepDrillAnimation.tsx b/components/blocks/DeepDrillAnimation.tsx index ceea70ce6..24d913701 100644 --- a/components/blocks/DeepDrillAnimation.tsx +++ b/components/blocks/DeepDrillAnimation.tsx @@ -16,13 +16,13 @@ export function DeepDrillAnimation() { const drillDepth = useTransform(scrollYProgress, [0, 1], ["0%", "100%"]); return ( -

+
-

+

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

@@ -50,8 +50,8 @@ export function DeepDrillAnimation() { transition={{ duration: 0.8 }} className="flex flex-col items-center" > -

- 102.771m +
+ 102.771m
{locale === 'en' ? 'Drilled distance since 2023' : 'Gebohrte Strecke seit 2023'} diff --git a/components/blocks/FaqBlock.tsx b/components/blocks/FaqBlock.tsx index a59afada3..12afa0919 100644 --- a/components/blocks/FaqBlock.tsx +++ b/components/blocks/FaqBlock.tsx @@ -29,7 +29,7 @@ export const FaqBlock: React.FC = ({ {subtitle} )} -

+

{title}

diff --git a/components/blocks/GrowthChart.tsx b/components/blocks/GrowthChart.tsx index fecce3aa8..2fbc90e70 100644 --- a/components/blocks/GrowthChart.tsx +++ b/components/blocks/GrowthChart.tsx @@ -23,9 +23,9 @@ export function GrowthChart() { const maxVal = 163470; // Highest value for scaling return ( -
+
-

{t('title')}

+

{t('title')}

{t('subtitle')}

diff --git a/components/blocks/HeroSection.tsx b/components/blocks/HeroSection.tsx index ffd231a5f..13cbc462d 100644 --- a/components/blocks/HeroSection.tsx +++ b/components/blocks/HeroSection.tsx @@ -44,7 +44,7 @@ export const HeroSection: React.FC = (props) => { return (
{bgSrc && ( +
{/* Background color while video loads */}
@@ -88,8 +88,8 @@ export function HeroVideo(props: HeroVideoProps) { transition={{ duration: 1.2, ease: [0.16, 1, 0.3, 1] }} >

Kabeltiefbau').replace(/KABELNETZBAU|Kabelnetzbau/g, 'Kabelnetzbau').replace(/\\n|\n/g, '
') }} + className="font-heading font-bold text-3xl sm:text-4xl md:text-6xl lg:text-8xl text-white mb-6 uppercase tracking-tight drop-shadow-lg" + dangerouslySetInnerHTML={{ __html: title.replace(/KABELNETZBAU|Kabelnetzbau/g, 'Kabelnetzbau').replace(/KABELNETZBAU|Kabelnetzbau/g, 'Kabelnetzbau').replace(/\\n|\n/g, '
') }} />

diff --git a/components/blocks/InteractiveGermanyMap.tsx b/components/blocks/InteractiveGermanyMap.tsx index e0132a6c1..130db9dc7 100644 --- a/components/blocks/InteractiveGermanyMap.tsx +++ b/components/blocks/InteractiveGermanyMap.tsx @@ -38,6 +38,20 @@ export function InteractiveGermanyMap({ hideStandorte = false }: InteractiveGermanyMapProps) { const [activeLocation, setActiveLocation] = useState(null); + const [hoverTimeout, setHoverTimeout] = useState(null); + + const handleMouseEnter = (loc: Location) => { + if (hoverTimeout) clearTimeout(hoverTimeout); + setActiveLocation(loc); + }; + + const handleMouseLeave = () => { + const timeout = setTimeout(() => { + setActiveLocation(null); + }, 200); + setHoverTimeout(timeout); + }; + const router = useRouter(); const pathname = usePathname(); const locale = useLocale(); @@ -68,7 +82,7 @@ export function InteractiveGermanyMap({ }, [locations, hideStandorte]); return ( -

+
{/* Animated Border Glow */} @@ -87,11 +101,11 @@ export function InteractiveGermanyMap({
{isHero ? ( -

+

{finalTitle}

) : ( -

+

{finalTitle}

)} @@ -107,7 +121,7 @@ export function InteractiveGermanyMap({
-
+
{stat.value}{stat.suffix}
@@ -157,8 +171,9 @@ export function InteractiveGermanyMap({ top: `${loc.y}%`, transform: 'translate(-50%, -50%)', }} - onMouseEnter={() => setActiveLocation(loc)} - onMouseLeave={() => setActiveLocation(null)} + onMouseEnter={() => handleMouseEnter(loc)} + onMouseLeave={handleMouseLeave} + onClick={() => handleMouseEnter(loc)} >
@@ -176,14 +191,9 @@ export function InteractiveGermanyMap({ key={loc.id} className={`absolute transform -translate-x-1/2 -translate-y-1/2 group/pin cursor-pointer w-16 h-16 flex items-center justify-center ${isActive ? 'z-[60]' : 'z-20'}`} style={{ left: `${loc.x}%`, top: `${loc.y}%` }} - onMouseEnter={() => setActiveLocation(loc)} - onMouseLeave={() => setActiveLocation(null)} - onClick={() => { - if (loc.href) { - const href = loc.href.startsWith('/') ? `/${locale}${loc.href}` : loc.href; - router.push(href); - } - }} + onMouseEnter={() => handleMouseEnter(loc)} + onMouseLeave={handleMouseLeave} + onClick={() => handleMouseEnter(loc)} > {/* Ping Animation for HQ / Branch */} {(isHQ || isBranch) && ( @@ -218,12 +228,14 @@ export function InteractiveGermanyMap({ animate={{ opacity: 1, y: 0, scale: 1 }} exit={{ opacity: 0, y: 10, scale: 0.95 }} transition={{ duration: 0.2 }} - className="absolute z-[9999] pointer-events-none" + className="absolute z-[9999] pointer-events-auto" style={{ left: `${activeLocation.x}%`, top: `${activeLocation.y}%`, transform: activeLocation.x < 50 ? 'translate(20px, -50%)' : 'translate(calc(-100% - 20px), -50%)', }} + onMouseEnter={() => handleMouseEnter(activeLocation)} + onMouseLeave={handleMouseLeave} >
@@ -284,10 +296,14 @@ export function InteractiveGermanyMap({ )} {activeLocation.href && (activeLocation.type === 'hq' || activeLocation.type === 'branch') && ( -
+ e.stopPropagation()} + > {t('learnMore')} -
+ )}
diff --git a/components/blocks/JobListingBlock.tsx b/components/blocks/JobListingBlock.tsx index d0f1359a7..d7c31b65f 100644 --- a/components/blocks/JobListingBlock.tsx +++ b/components/blocks/JobListingBlock.tsx @@ -71,13 +71,13 @@ export const JobListingBlock = (props: JobListingBlockProps) => {
{showFairs && ( -
+
{fairsTitle} @@ -182,7 +182,7 @@ export const JobListingBlock = (props: JobListingBlockProps) => { whileInView={{ opacity: 1, x: 0 }} viewport={{ once: true, margin: "-100px" }} transition={{ duration: 0.8, ease: [0.16, 1, 0.3, 1] as const }} - className="font-heading font-extrabold text-4xl text-neutral-dark mb-8" + className="font-heading font-extrabold text-3xl md:text-4xl text-neutral-dark mb-6 md:mb-8" > {title} diff --git a/components/blocks/ReferencesSlider.test.tsx b/components/blocks/ReferencesSlider.test.tsx index a287df4b2..4a8cd039c 100644 --- a/components/blocks/ReferencesSlider.test.tsx +++ b/components/blocks/ReferencesSlider.test.tsx @@ -34,7 +34,7 @@ describe('ReferencesSlider TDD', () => { id: '1', title: 'Referenz Projekt Eins', slug: 'referenz-projekt-eins', - category: 'Kabeltiefbau', + category: 'Kabelnetzbau', }, { id: '2', diff --git a/components/blocks/ReferencesSlider.tsx b/components/blocks/ReferencesSlider.tsx index 675751263..43ddb3fc4 100644 --- a/components/blocks/ReferencesSlider.tsx +++ b/components/blocks/ReferencesSlider.tsx @@ -94,20 +94,21 @@ export function ReferencesSlider(props: ReferencesSliderProps) { return ( -
+

{badge}

-

{title}

+

{title}

-

+

{description} -

+
+
{/* Massive Watermark Numbers */} -

+

{locale === 'en' ? ( <>An infrastructure
that connects the country. ) : ( @@ -46,7 +46,7 @@ export function ScaleOfImpact() { )}

-

+

{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." @@ -54,7 +54,7 @@ export function ScaleOfImpact() {

{/* Abstract SVG Line Animation */} -
+
{/* Static Data Grid (No jumping text) */} -
+
-
200 +
+
200 +
{locale === 'en' ? 'Projects' : 'Projekte'}
-
974 km
+
974 km
{locale === 'en' ? 'Cable Laying' : 'Kabelverlegung'}
-
300 km
+
300 km
{locale === 'en' ? 'Open Trenching' : 'Offener Tiefbau'}
-
160 km
+
160 km
{locale === 'en' ? 'HDD Drilling' : 'Spülbohrung'}
diff --git a/components/blocks/ServiceDetailGrid.tsx b/components/blocks/ServiceDetailGrid.tsx index 3c3ae93fb..36a405cb0 100644 --- a/components/blocks/ServiceDetailGrid.tsx +++ b/components/blocks/ServiceDetailGrid.tsx @@ -105,17 +105,17 @@ export function ServiceDetailGrid({ panels }: ServiceDetailGridProps) { return ( -
+
-
+
{badge &&

{badge}

} {title && ( -

+

{title}

)} -
+
{descriptionParagraphs?.map((p, i) => { @@ -141,7 +141,7 @@ export function ServiceDetailGrid({ className="max-w-4xl mx-auto mb-20 grid md:grid-cols-2 gap-0 bg-white rounded-2xl overflow-hidden shadow-lg border border-neutral-200 text-left" > {problemStatement && ( -
+
@@ -154,7 +154,7 @@ export function ServiceDetailGrid({
)} {solutionStatement && ( -
+
@@ -183,7 +183,7 @@ export function ServiceDetailGrid({ {/* Premium Watermark Number */}
@@ -195,7 +195,7 @@ export function ServiceDetailGrid({
-

{panel.title}

+

{panel.title}

diff --git a/components/blocks/SubCompanyTiles.tsx b/components/blocks/SubCompanyTiles.tsx index af469161a..37f766146 100644 --- a/components/blocks/SubCompanyTiles.tsx +++ b/components/blocks/SubCompanyTiles.tsx @@ -68,23 +68,25 @@ export function SubCompanyTiles(props: SubCompanyTilesProps) { })) || defaultCompanies; return ( -
+
{(badge || title) && ( -
+
{badge &&

{badge}

} - {title &&

{title}

} + {title &&

{title}

}
)} - +
+
+ {companiesData.map((company: any, index: number) => { const isCurrent = company.title.toUpperCase() === 'E-TIB GMBH'; const hasUrl = !!company.url; @@ -256,7 +258,8 @@ export function SubCompanyTiles(props: SubCompanyTilesProps) { ); })} - + +
); diff --git a/components/blocks/SupportCTA.tsx b/components/blocks/SupportCTA.tsx index e422054d9..f1d59084f 100644 --- a/components/blocks/SupportCTA.tsx +++ b/components/blocks/SupportCTA.tsx @@ -44,7 +44,7 @@ export const SupportCTA: React.FC = (props) => { initial="hidden" whileInView="visible" viewport={{ once: true, margin: "-100px" }} - className="mt-24 p-8 md:p-12 bg-primary-dark rounded-3xl text-white shadow-2xl relative overflow-hidden group" + className="mt-12 md:mt-24 p-6 md:p-12 bg-primary-dark rounded-3xl text-white shadow-2xl relative overflow-hidden group" >
diff --git a/components/blocks/TeamGrid.tsx b/components/blocks/TeamGrid.tsx index 98f45f4c2..1f556a762 100644 --- a/components/blocks/TeamGrid.tsx +++ b/components/blocks/TeamGrid.tsx @@ -28,11 +28,11 @@ export function TeamGrid({ members }: TeamGridProps) { if (!members || members.length === 0) return null; return ( -
+

{t('badge')}

-

{t('title')}

+

{t('title')}

{t('subtitle')}

diff --git a/components/home/CTA.tsx b/components/home/CTA.tsx index 4cb341a71..d35c67e84 100644 --- a/components/home/CTA.tsx +++ b/components/home/CTA.tsx @@ -7,7 +7,7 @@ export default function CTA({ data }: { data?: any }) { const locale = useLocale(); return ( -
+
diff --git a/components/home/Experience.tsx b/components/home/Experience.tsx index 4db9c5841..3ab85c3d1 100644 --- a/components/home/Experience.tsx +++ b/components/home/Experience.tsx @@ -7,7 +7,7 @@ export default function Experience({ data }: { data?: any }) { const t = useTranslations('Home.experience'); return ( -
+
+
{data?.title || t('title')} -
+
{images.map((src, idx) => (
diff --git a/components/ui/Heading.tsx b/components/ui/Heading.tsx index 3d1bce0f5..f476a95a2 100644 --- a/components/ui/Heading.tsx +++ b/components/ui/Heading.tsx @@ -32,9 +32,9 @@ export function Heading({ const effectiveSize = size || String(level) as HeadingSize; const sizes: Record = { - 'hero': 'font-heading text-4xl md:text-5xl lg:text-6xl font-black tracking-tight !leading-[0.9]', + 'hero': 'font-heading text-3xl md:text-5xl lg:text-6xl font-black tracking-tight !leading-[0.9]', 'section': 'font-heading text-3xl md:text-4xl lg:text-5xl font-extrabold tracking-tight !leading-[0.95]', - 'subsection': 'font-heading text-2xl md:text-3xl lg:text-4xl font-bold tracking-tight leading-tight', + 'subsection': 'font-heading text-xl md:text-3xl lg:text-4xl font-bold tracking-tight leading-tight', 'card': 'text-xl md:text-2xl font-bold leading-snug', 'small': 'text-lg font-semibold leading-snug', // Legacy support diff --git a/components/ui/Section.tsx b/components/ui/Section.tsx index a686aa297..fff973f90 100644 --- a/components/ui/Section.tsx +++ b/components/ui/Section.tsx @@ -3,7 +3,7 @@ import { cn } from './utils'; export function Section({ className, children, ...props }: React.HTMLAttributes) { return ( -
+
{children}
); diff --git a/content/de/agb.mdx b/content/de/agb.mdx index ebfcee6a3..7e62efa65 100644 --- a/content/de/agb.mdx +++ b/content/de/agb.mdx @@ -7,7 +7,7 @@ excerpt: "Die Allgemeinen Geschäftsbedingungen der E-TIB GmbH für unsere Leist # Allgemeine Geschäftsbedingungen (AGB) ## 1. Geltungsbereich und Vertragsabschluss -1. Diese Allgemeinen Geschäftsbedingungen (AGB) gelten ausschließlich für alle Verträge, Lieferungen und sonstigen Leistungen (einschließlich Planungs-, Ingenieur-, Kabeltiefbau-, Horizontalspülbohrungs- und Glasfasermontageleistungen) der E-TIB GmbH (nachfolgend „E-TIB“ genannt) gegenüber ihren Vertragspartnern (nachfolgend „Kunde“ genannt). +1. Diese Allgemeinen Geschäftsbedingungen (AGB) gelten ausschließlich für alle Verträge, Lieferungen und sonstigen Leistungen (einschließlich Planungs-, Ingenieur-, Kabelnetzbau-, Horizontalspülbohrungs- und Glasfasermontageleistungen) der E-TIB GmbH (nachfolgend „E-TIB“ genannt) gegenüber ihren Vertragspartnern (nachfolgend „Kunde“ genannt). 2. Diese AGB gelten ausschließlich im B2B-Verkehr. Sie richten sich ausschließlich an Unternehmer im Sinne des § 14 BGB, juristische Personen des öffentlichen Rechts oder öffentlich-rechtliche Sondervermögen. 3. Entgegenstehende, abweichende oder ergänzende Bedingungen des Kunden werden nicht Vertragsbestandteil, es sei denn, E-TIB stimmt ihrer Geltung ausdrücklich schriftlich zu. 4. Ein Vertrag kommt erst durch die schriftliche Auftragsbestätigung von E-TIB oder durch den tatsächlichen Beginn der Ausführung der Leistungen durch E-TIB zustande. diff --git a/content/de/home.mdx b/content/de/home.mdx index 2f3f76aca..91998bcd8 100644 --- a/content/de/home.mdx +++ b/content/de/home.mdx @@ -1,12 +1,12 @@ --- -title: "Startseite - Experten für Kabeltiefbau & Horizontalspülbohrungen" +title: "Startseite - Experten für Kabelnetzbau & Horizontalspülbohrungen" description: "Die E-TIB GmbH ist Ihr zuverlässiger Partner für komplexe Kabeltrassen, Horizontalspülbohrungen und Infrastrukturnetze in ganz Deutschland." --- KABELTIEFBAU", + "title": "DIE EXPERTEN FÜR KABELNETZBAU", "subtitle": "Wir helfen beim Ausbau der Energiekabelnetze für eine grüne Zukunft.", "cta": "Jetzt anfragen", "searchPlaceholder": "Projekt beschreiben oder Kabel suchen...", @@ -207,7 +207,7 @@ "Blog": { "meta": { "title": "Blog & Aktuelles", - "description": "Neuigkeiten und Einblicke in die Welt des Kabeltiefbaus und der Infrastruktur." + "description": "Neuigkeiten und Einblicke in die Welt des Kabelnetzbaus und der Infrastruktur." }, "featuredPost": "Top-Beitrag", "readFullArticle": "Vollständigen Artikel lesen", @@ -226,7 +226,7 @@ "ReferencesSlider": { "badge": "Ausgewählte Projekte", "title": "Referenzen & Erfolge", - "description": "Ein Auszug unserer erfolgreich abgeschlossenen Projekte im Bereich Kabeltiefbau, Bohrtechnik und Netzinfrastruktur.", + "description": "Ein Auszug unserer erfolgreich abgeschlossenen Projekte im Bereich Kabelnetzbau, Bohrtechnik und Netzinfrastruktur.", "ctaLabel": "Alle Referenzen ansehen" }, "CompanyTimeline": { @@ -235,7 +235,7 @@ "milestones": { "2015": { "title": "Gründung E-TIB GmbH", - "desc": "Ausführung elektrischer Infrastrukturprojekte, Kabeltiefbau und Horizontalspülbohrungen." + "desc": "Ausführung elektrischer Infrastrukturprojekte, Kabelnetzbau und Horizontalspülbohrungen." }, "2025": { "title": "Gründung E-TIB Bohrtechnik GmbH", diff --git a/messages/en.json b/messages/en.json index 155c638fd..fcdf2e7eb 100644 --- a/messages/en.json +++ b/messages/en.json @@ -10,7 +10,7 @@ "terms": "agb", "start": "start", "trade-fairs": "messen", - "cable-civil-engineering": "kabeltiefbau", + "cable-civil-engineering": "kabelnetzbau", "fiber-optics": "glasfaser", "drilling-technology": "bohrtechnik", "planning": "planung", diff --git a/package.json b/package.json index 4e4fc94f5..837b82f8d 100644 --- a/package.json +++ b/package.json @@ -100,9 +100,9 @@ "xlsx-cli": "^1.1.3" }, "scripts": { - "dev": "bash -c 'bash scripts/registry-auth.sh && [ -f .env ] || (cp .env.example .env && sed -i.bak \"s/TRAEFIK_HOST=e-tib.com/TRAEFIK_HOST=etib.localhost/\" .env && rm -f .env.bak && echo \"✅ Created .env from .env.example\"); trap \"COMPOSE_PROJECT_NAME=etib docker-compose -f docker-compose.dev.yml down\" EXIT INT TERM; docker network create infra 2>/dev/null || true && COMPOSE_PROJECT_NAME=etib docker-compose -f docker-compose.dev.yml down && COMPOSE_PROJECT_NAME=etib docker-compose -f docker-compose.dev.yml up etib-app etib-proxy --remove-orphans'", + "dev": "bash -c 'bash scripts/registry-auth.sh && [ -f .env ] || (cp .env.example .env && sed -i.bak \"s/TRAEFIK_HOST=e-tib.com/TRAEFIK_HOST=etib.localhost/\" .env && rm -f .env.bak && echo \"✅ Created .env from .env.example\"); trap \"COMPOSE_PROJECT_NAME=etib docker compose -f docker-compose.dev.yml down\" EXIT INT TERM; docker network create infra 2>/dev/null || true && COMPOSE_PROJECT_NAME=etib docker compose -f docker-compose.dev.yml down && COMPOSE_PROJECT_NAME=etib docker compose -f docker-compose.dev.yml up etib-app etib-proxy --remove-orphans'", "dev:local": "bash -c 'bash scripts/registry-auth.sh && next dev --webpack --port 3100 --hostname 0.0.0.0'", - "dev:infra": "COMPOSE_PROJECT_NAME=etib docker-compose -f docker-compose.dev.yml up -d etib-proxy", + "dev:infra": "COMPOSE_PROJECT_NAME=etib docker compose -f docker-compose.dev.yml up -d etib-proxy", "build": "next build", "start": "next start", "lint": "eslint .", diff --git a/scripts/fix_map.mjs b/scripts/fix_map.mjs new file mode 100644 index 000000000..36b96a8be --- /dev/null +++ b/scripts/fix_map.mjs @@ -0,0 +1,110 @@ +import fs from 'fs'; +import path from 'path'; + +// 1. Rename Kabeltiefbau to Kabelnetzbau +const filesToReplace = []; +function walkDir(dir) { + if (!fs.existsSync(dir)) return; + const files = fs.readdirSync(dir); + for (const file of files) { + const fullPath = path.join(dir, file); + const stat = fs.statSync(fullPath); + if (stat.isDirectory()) { + if (['node_modules', '.next', '.git'].includes(file)) continue; + walkDir(fullPath); + } else { + if (fullPath.match(/\.(tsx|ts|mdx|json)$/)) { + filesToReplace.push(fullPath); + } + } + } +} + +const baseDir = '/Volumes/Alpha SSD/Coding/e-tib.com'; +walkDir(path.join(baseDir, 'content')); +walkDir(path.join(baseDir, 'messages')); +walkDir(path.join(baseDir, 'components')); +walkDir(path.join(baseDir, 'app')); + +for (const file of filesToReplace) { + let content = fs.readFileSync(file, 'utf-8'); + let newContent = content + .replace(/Kabeltiefbau/g, 'Kabelnetzbau') + .replace(/kabeltiefbau/g, 'kabelnetzbau') + .replace(/KABELTIEFBAU/g, 'KABELNETZBAU'); + + if (content !== newContent) { + fs.writeFileSync(file, newContent, 'utf-8'); + console.log(`Replaced in ${file}`); + } +} + +// 2. Parse text files and update map-data.ts +const txtDir = '/Users/marcmintel/Downloads/etib'; +const txtFiles = ['Orte 2016 - 2022.txt', 'Orte 2023.txt', 'Orte 2024.txt', 'Orte 2025.txt', 'Orte 2026.txt']; +const projectMapping = {}; // plz -> "Typ, PLZ Ort" + +for (const tf of txtFiles) { + const content = fs.readFileSync(path.join(txtDir, tf), 'utf-8'); + const lines = content.split('\n'); + for (let line of lines) { + line = line.trim(); + if (!line) continue; + + // Extract PLZ + const match = line.match(/\b\d{5}\b/); + if (match) { + const plz = match[0]; + projectMapping[plz] = line; + } + } +} + +// Read map-data.ts +const mapDataPath = path.join(baseDir, 'lib/map-data.ts'); +let mapData = fs.readFileSync(mapDataPath, 'utf-8'); + +// The objects look like: +// { +// id: 'göhlsdorf-14797', +// name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg', +// type: 'minor_node', +// x: 68.78, +// y: 35.98, +// description: 'pv', +// }, + +// We will use regex to find each block and replace name and type +let updatedMapData = mapData.replace(/id:\s*'([^']+)',\s*name:\s*'([^']+)',\s*type:\s*'([^']+)',/g, (match, id, name, type) => { + // Find PLZ in ID + const plzMatch = id.match(/\d{5}/); + if (plzMatch && projectMapping[plzMatch[0]]) { + const correctName = projectMapping[plzMatch[0]].replace(/'/g, "\\'"); + console.log(`Updating ${id} -> ${correctName}`); + return `id: '${id}',\n name: '${correctName}',\n type: 'project',`; + } + // If it's a minor node but no PLZ match, still change to project? + if (type === 'minor_node') { + return `id: '${id}',\n name: '${name}',\n type: 'project',`; + } + return match; +}); + +// Fix Petershagen coordinates +updatedMapData = updatedMapData.replace( + /id:\s*'petershagen-15326'[\s\S]*?x:\s*[\d.]+,\s*y:\s*[\d.]+/, + (match) => { + return match.replace(/x:\s*[\d.]+,\s*y:\s*[\d.]+/, 'x: 82.5, y: 33.5'); + } +); + +// Also fix the defaultLocations petershagen if it exists +updatedMapData = updatedMapData.replace( + /id:\s*'petershagen'[\s\S]*?x:\s*36\.26,\s*y:\s*35\.79/, + (match) => { + return match.replace(/x:\s*36\.26,\s*y:\s*35\.79/, 'x: 82.5, y: 33.5'); + } +); + +fs.writeFileSync(mapDataPath, updatedMapData, 'utf-8'); +console.log('map-data.ts updated!'); diff --git a/scripts/geocode-missing.js b/scripts/geocode-missing.js new file mode 100644 index 000000000..49ee22aa0 --- /dev/null +++ b/scripts/geocode-missing.js @@ -0,0 +1,41 @@ +const missing = [ + { id: 'lautzenhausen-55483', name: 'PV-Anlage, 55483 Lautzenhausen', desc: 'pv', zip: '55483' }, + { id: 'schleiz-07907', name: 'Breitbandausbau, 07907 Schleiz', desc: 'fiber', zip: '07907' }, + { id: 'grosshartau-01909', name: 'PV-Anlage, 01909 Großhartau', desc: 'pv', zip: '01909' }, + { id: 'halsbrücke-09600', name: 'Breitbandausbau, 09600 Halsbrücke', desc: 'fiber', zip: '09600' }, + { id: 'seinsheim-97342', name: 'PV-Anlage, 97342 Seinsheim', desc: 'pv', zip: '97342' }, + { id: 'schenkenberg-17291', name: 'Kabelschutzrohrtrasse, 17291 Schenkenberg-Polen', desc: 'power', zip: '17291' }, + { id: 'thundorf-97711', name: 'PV-Anlage, 97711 Thundorf OT Theinfeld', desc: 'pv', zip: '97711' }, + { id: 'tarnow-18249', name: 'Windpark, 18249 Tarnow-Prüzen', desc: 'wind', zip: '18249' }, + { id: 'ennigerloh-59320', name: 'PV-Anlage, 59320 Ennigerloh-Oelde', desc: 'pv', zip: '59320' } +]; + +async function geocode() { + for (const item of missing) { + try { + const res = await fetch(`https://nominatim.openstreetmap.org/search?postalcode=${item.zip}&country=Germany&format=json`); + const data = await res.json(); + if (data && data.length > 0) { + const lat = parseFloat(data[0].lat); + const lon = parseFloat(data[0].lon); + const x = (lon - 4.64) * 8.395; + const y = (55.215 - lat) * 12.589; + + console.log(` {`); + console.log(` id: '${item.id}',`); + console.log(` name: '${item.name}',`); + console.log(` type: 'minor_node',`); + console.log(` x: ${x.toFixed(2)},`); + console.log(` y: ${y.toFixed(2)},`); + console.log(` description: '${item.desc}',`); + console.log(` },`); + } else { + console.log(`// Could not geocode ${item.zip}`); + } + } catch(e) { + console.log(`// Error geocoding ${item.zip}`); + } + } +} + +geocode(); diff --git a/scripts/geocode-new.ts b/scripts/geocode-new.ts new file mode 100644 index 000000000..e20c600fc --- /dev/null +++ b/scripts/geocode-new.ts @@ -0,0 +1,64 @@ +import fs from 'fs'; + +const newLocations = [ + { raw: 'NS-Verkabelung, Klein Gastrose, 03172 Schenkendöbern', zip: '03172', type: 'power', city: 'Schenkendöbern' }, + { raw: 'MS-Verkabelung, Auras-Cottbus, 03051 Cottbus', zip: '03051', type: 'power', city: 'Cottbus' }, + { raw: 'MS-Verkabelung, 15848 Rietz-Neuendorf', zip: '15848', type: 'power', city: 'Rietz-Neuendorf' }, + { raw: 'NS-Verkabelung, 15910 Schlepzig', zip: '15910', type: 'power', city: 'Schlepzig' }, + { raw: 'MS-Verkabelung, 15306 Falkenhagen Mark', zip: '15306', type: 'power', city: 'Falkenhagen Mark' }, + { raw: 'MS-Verkabelung, 15898 Neuzelle', zip: '15898', type: 'power', city: 'Neuzelle' }, + { raw: 'MS-Verkabelung, 15890 Eisenhüttenstadt OT Diehlo', zip: '15890', type: 'power', city: 'Eisenhüttenstadt' }, + { raw: 'MS-Verkabelung, 15848 Karras', zip: '15848', type: 'power', city: 'Karras' }, + { raw: 'MS-Verkabelung, Gewerbegebiet, 14669 Ketzin', zip: '14669', type: 'power', city: 'Ketzin' }, + { raw: 'MS-Verkabelung, 15868 Lieberose', zip: '15868', type: 'power', city: 'Lieberose' }, + { raw: 'MS-Verkabelung, 15344 Strausberg', zip: '15344', type: 'power', city: 'Strausberg' }, + { raw: 'MS-Verkabelung, 15366 Neuenhagen', zip: '15366', type: 'power', city: 'Neuenhagen' }, + { raw: 'MS-Verkabelung, 15868 Jamlitz', zip: '15868', type: 'power', city: 'Jamlitz' }, + { raw: 'MS-Verkabelung, 03096 Burg (Spreewald)', zip: '03096', type: 'power', city: 'Burg' }, + { raw: 'MS-Verkabelung, 03130 Bohsdorf', zip: '03130', type: 'power', city: 'Bohsdorf' }, + { raw: 'Windpark, 29575 Altenmedingen', zip: '29575', type: 'wind', city: 'Altenmedingen' } +]; + +async function geocode() { + let output = ''; + for (const item of newLocations) { + try { + const res = await fetch(`https://nominatim.openstreetmap.org/search?postalcode=${item.zip}&country=Germany&format=json`, { + headers: { + 'User-Agent': 'E-TIB Internal Script / info@mintel.com' + } + }); + const data = await res.json(); + if (data && data.length > 0) { + // slightly jitter the coordinates to prevent perfect overlap if there are duplicates + const jitterLat = (Math.random() - 0.5) * 0.01; + const jitterLon = (Math.random() - 0.5) * 0.01; + const lat = parseFloat(data[0].lat) + jitterLat; + const lon = parseFloat(data[0].lon) + jitterLon; + const x = (lon - 4.64) * 8.395; + const y = (55.215 - lat) * 12.589; + + const id = item.city.toLowerCase().replace(/[^a-z0-9]/g, '-') + '-' + item.zip; + + output += ` {\n`; + output += ` id: '${id}',\n`; + output += ` name: '${item.raw}',\n`; + output += ` type: 'minor_node',\n`; + output += ` x: ${x.toFixed(2)},\n`; + output += ` y: ${y.toFixed(2)},\n`; + output += ` description: '${item.type}',\n`; + output += ` },\n`; + } else { + console.log(`// Could not geocode ${item.zip}`); + } + } catch(e) { + console.log(`// Error geocoding ${item.zip}`); + } + // wait 1 second to respect nominatim rate limits + await new Promise(r => setTimeout(r, 1000)); + } + + console.log(output); +} + +geocode(); diff --git a/scripts/patch_header.js b/scripts/patch_header.js new file mode 100644 index 000000000..6f5865f0f --- /dev/null +++ b/scripts/patch_header.js @@ -0,0 +1,110 @@ +const fs = require('fs'); + +let headerCode = fs.readFileSync('components/layout/Header.tsx', 'utf8'); + +// Insert MobileMenu state +headerCode = headerCode.replace( + "const [headerVariant, setHeaderVariant] = React.useState<'capsule' | 'standard'>('capsule');", + "const [headerVariant, setHeaderVariant] = React.useState<'capsule' | 'standard'>('capsule');\n const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false);\n" +); + +// Close menu on pathname change +headerCode = headerCode.replace( + "// Re-check when pathname changes", + "// Re-check when pathname changes\n setIsMobileMenuOpen(false);" +); + +// Add Hamburger Button in the Header +const hamburgerButton = ` + {/* Hamburger Menu (Mobile Only) */} + +
+
+
+ +`; +headerCode = headerCode.replace( + "
\n
\n
\n ", + hamburgerButton +); + +// Add Mobile Menu Overlay +const mobileMenuOverlay = ` + {/* Mobile Menu Overlay */} + + {isMobileMenuOpen && ( + + + + )} + + + ); +} +`; +headerCode = headerCode.replace( + " \n \n );\n}", + ` \n${mobileMenuOverlay}` +); + +fs.writeFileSync('components/layout/Header.tsx', headerCode);