Compare commits

..

4 Commits

Author SHA1 Message Date
2b79d18a93 fix: full translation parity, component consistency, and link clickability
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 24s
Build & Deploy / 🧪 QA (push) Successful in 1m47s
Build & Deploy / 🏗️ Build (push) Successful in 2m46s
Build & Deploy / 🚀 Deploy (push) Successful in 25s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 44s
Build & Deploy / 🔔 Notify (push) Successful in 1s
2026-05-27 12:04:51 +02:00
fa747763b8 fix(locale): align all English MDX page links and add automated links validation test to prevent language drift
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 18s
Build & Deploy / 🧪 QA (push) Successful in 1m6s
Build & Deploy / 🏗️ Build (push) Successful in 2m44s
Build & Deploy / 🚀 Deploy (push) Successful in 28s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 43s
Build & Deploy / 🔔 Notify (push) Successful in 1s
2026-05-26 15:18:36 +02:00
0d71e2ad24 merge: GermanyMap minorLocations enrichment and full page parity fix
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 19s
Build & Deploy / 🧪 QA (push) Successful in 1m6s
Build & Deploy / 🏗️ Build (push) Successful in 2m37s
Build & Deploy / 🚀 Deploy (push) Successful in 25s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 47s
Build & Deploy / 🔔 Notify (push) Successful in 1s
2026-05-26 15:06:59 +02:00
64d4edc175 fix(map): enrich and render minorLocations alongside defaultLocations on dynamic slug pages to establish 100% map parity with referenzen overview
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 19s
Build & Deploy / 🧪 QA (push) Successful in 1m6s
Build & Deploy / 🏗️ Build (push) Successful in 2m37s
Build & Deploy / 🚀 Deploy (push) Successful in 25s
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 1s
2026-05-26 15:00:51 +02:00
20 changed files with 245 additions and 132 deletions

View File

@@ -9,7 +9,7 @@ import { SITE_URL } from '@/lib/schema';
import TrackedLink from '@/components/analytics/TrackedLink'; import TrackedLink from '@/components/analytics/TrackedLink';
import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button'; import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
import { getAllReferences } from '@/lib/references'; import { getAllReferences } from '@/lib/references';
import { defaultLocations } from '@/lib/map-data'; import { defaultLocations, minorLocations } from '@/lib/map-data';
// Import components used in MDX // Import components used in MDX
import { HeroVideo as HomeHero } from '@/components/blocks/HeroVideo'; import { HeroVideo as HomeHero } from '@/components/blocks/HeroVideo';
@@ -153,7 +153,8 @@ export default async function Page(props: { params: Promise<{ locale: string; sl
// Fetch references to enrich the map if it's used // Fetch references to enrich the map if it's used
const references = await getAllReferences(locale); const references = await getAllReferences(locale);
const enrichedLocations = defaultLocations.map(loc => { const allLocations = [...defaultLocations, ...minorLocations];
const enrichedLocations = allLocations.map(loc => {
if (loc.type === 'project') { if (loc.type === 'project') {
const ref = references.find(r => r.slug === loc.id); const ref = references.find(r => r.slug === loc.id);
if (ref) { if (ref) {

View File

@@ -3,6 +3,7 @@
import * as React from 'react'; import * as React from 'react';
import { motion, useScroll, useTransform } from 'framer-motion'; import { motion, useScroll, useTransform } from 'framer-motion';
import { Building2, Compass, Layers, ArrowDownToLine, Wrench, Factory, Zap, MapPin, CheckCircle2 } from 'lucide-react'; import { Building2, Compass, Layers, ArrowDownToLine, Wrench, Factory, Zap, MapPin, CheckCircle2 } from 'lucide-react';
import { useTranslations } from 'next-intl';
const iconMap: Record<string, React.ElementType> = { const iconMap: Record<string, React.ElementType> = {
Building2, Building2,
@@ -16,37 +17,6 @@ const iconMap: Record<string, React.ElementType> = {
CheckCircle2 CheckCircle2
}; };
const defaultMilestones = [
{
date: '16.12.2015',
year: '2015',
title: 'Gründung E-TIB GmbH',
desc: 'Ausführung elektrischer Infrastrukturprojekte, Kabeltiefbau und Horizontalspülbohrungen.',
iconName: 'Building2',
},
{
date: '04.02.2019',
year: '2019',
title: 'Gründung E-TIB Ingenieurgesellschaft',
desc: 'Genehmigungs- und Ausführungsplanung, komplexe Querungen sowie Netzanschlussplanung.',
iconName: 'Compass',
},
{
date: '14.11.2019',
year: '2019',
title: 'Gründung E-TIB Verwaltung GmbH',
desc: 'Zentrale Dienste, Erwerb, Vermietung, Verpachtung und Verwaltung von Immobilien und Maschinen.',
iconName: 'Layers',
},
{
date: '21.10.2025',
year: '2025',
title: 'Gründung E-TIB Bohrtechnik GmbH',
desc: 'Spezialisierung auf präzise Horizontalspülbohrungen in allen Bodenklassen.',
iconName: 'ArrowDownToLine',
},
];
interface Milestone { interface Milestone {
date: string; date: string;
year: string; year: string;
@@ -62,10 +32,45 @@ interface CompanyTimelineProps {
} }
export function CompanyTimeline({ export function CompanyTimeline({
badge = 'Unsere Geschichte', badge,
title = 'Meilensteine der Entwicklung', title,
milestones = defaultMilestones milestones
}: CompanyTimelineProps) { }: CompanyTimelineProps) {
const t = useTranslations('CompanyTimeline');
const finalBadge = badge || t('badge');
const finalTitle = title || t('title');
const finalMilestones = milestones || [
{
date: '16.12.2015',
year: '2015',
title: t('milestones.2015.title'),
desc: t('milestones.2015.desc'),
iconName: 'Building2',
},
{
date: '04.02.2019',
year: '2019',
title: t('milestones.2019_ing.title'),
desc: t('milestones.2019_ing.desc'),
iconName: 'Compass',
},
{
date: '14.11.2019',
year: '2019',
title: t('milestones.2019_verw.title'),
desc: t('milestones.2019_verw.desc'),
iconName: 'Layers',
},
{
date: '21.10.2025',
year: '2025',
title: t('milestones.2025.title'),
desc: t('milestones.2025.desc'),
iconName: 'ArrowDownToLine',
},
];
const containerRef = React.useRef<HTMLDivElement>(null); const containerRef = React.useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({ const { scrollYProgress } = useScroll({
target: containerRef, target: containerRef,
@@ -84,10 +89,10 @@ export function CompanyTimeline({
<div className="text-center mb-24"> <div className="text-center mb-24">
<div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-white border border-neutral-200 text-neutral-dark text-xs font-bold uppercase tracking-wider mb-6 shadow-sm"> <div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-white border border-neutral-200 text-neutral-dark text-xs font-bold uppercase tracking-wider mb-6 shadow-sm">
<span className="w-2 h-2 rounded-full bg-primary animate-pulse" /> <span className="w-2 h-2 rounded-full bg-primary animate-pulse" />
{badge} {finalBadge}
</div> </div>
<h3 className="font-heading text-4xl md:text-5xl lg:text-6xl font-extrabold text-neutral-dark tracking-tight"> <h3 className="font-heading text-4xl md:text-5xl lg:text-6xl font-extrabold text-neutral-dark tracking-tight">
{title} {finalTitle}
</h3> </h3>
</div> </div>
@@ -104,7 +109,7 @@ export function CompanyTimeline({
</div> </div>
<div className="space-y-16 md:space-y-24"> <div className="space-y-16 md:space-y-24">
{milestones.map((milestone, i) => { {finalMilestones.map((milestone, i) => {
const isEven = i % 2 === 0; const isEven = i % 2 === 0;
const Icon = iconMap[milestone.iconName] || Building2; const Icon = iconMap[milestone.iconName] || Building2;

View File

@@ -7,7 +7,7 @@ import Image from 'next/image';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder'; import { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';
import { Location, defaultLocations, minorLocations } from '@/lib/map-data'; import { Location, defaultLocations, minorLocations } from '@/lib/map-data';
import { useLocale } from 'next-intl'; import { useLocale, useTranslations } from 'next-intl';
const allLocations = [...defaultLocations, ...minorLocations]; const allLocations = [...defaultLocations, ...minorLocations];
@@ -27,9 +27,9 @@ interface InteractiveGermanyMapProps {
} }
export function InteractiveGermanyMap({ export function InteractiveGermanyMap({
badge = 'Einsatzgebiete', badge,
title = <>Deutschlandweit<br/>für Sie im Einsatz.</>, title,
description = 'Von unseren strategischen Standorten in Guben und Bülstedt steuern und realisieren wir komplexe Infrastrukturprojekte im gesamten Bundesgebiet.', description,
stats = [ stats = [
{ value: '100', suffix: '%', label: 'Überregionale Reichweite' }, { value: '100', suffix: '%', label: 'Überregionale Reichweite' },
{ value: '2', suffix: '+', label: 'Operative Standorte' }, { value: '2', suffix: '+', label: 'Operative Standorte' },
@@ -40,6 +40,13 @@ export function InteractiveGermanyMap({
const [activeLocation, setActiveLocation] = useState<Location | null>(null); const [activeLocation, setActiveLocation] = useState<Location | null>(null);
const router = useRouter(); const router = useRouter();
const locale = useLocale(); const locale = useLocale();
const t = useTranslations('InteractiveGermanyMap');
const tStandard = useTranslations('StandardPage');
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<br/>in operation for you.</> : <>Deutschlandweit<br/>für Sie im Einsatz.</>);
const finalDescription = description || (locale === 'en' ? 'From our strategic locations in Guben and Bülstedt, we control and implement complex infrastructure projects nationwide.' : 'Von unseren strategischen Standorten in Guben und Bülstedt steuern und realisieren wir komplexe Infrastrukturprojekte im gesamten Bundesgebiet.');
const hq = locations.find((l) => l.type === 'hq'); const hq = locations.find((l) => l.type === 'hq');
const branch = locations.find((l) => l.type === 'branch'); const branch = locations.find((l) => l.type === 'branch');
@@ -62,21 +69,21 @@ export function InteractiveGermanyMap({
<div className="xl:col-span-5 text-white z-20"> <div className="xl:col-span-5 text-white z-20">
<div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-primary/10 border border-primary/20 text-primary text-xs font-bold uppercase tracking-wider mb-8"> <div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-primary/10 border border-primary/20 text-primary text-xs font-bold uppercase tracking-wider mb-8">
<MapPin className="w-4 h-4" /> <MapPin className="w-4 h-4" />
<span>{badge}</span> <span>{finalBadge}</span>
</div> </div>
{isHero ? ( {isHero ? (
<h1 className="font-heading text-4xl lg:text-5xl xl:text-6xl font-extrabold mb-6 leading-[1.1] text-transparent bg-clip-text bg-gradient-to-r from-white to-white/70 break-words [hyphens:auto]"> <h1 className="font-heading text-4xl lg:text-5xl xl:text-6xl font-extrabold mb-6 leading-[1.1] text-transparent bg-clip-text bg-gradient-to-r from-white to-white/70 break-words [hyphens:auto]">
{title} {finalTitle}
</h1> </h1>
) : ( ) : (
<h3 className="font-heading text-4xl lg:text-5xl xl:text-6xl font-extrabold mb-6 leading-[1.1] text-transparent bg-clip-text bg-gradient-to-r from-white to-white/70 break-words [hyphens:auto]"> <h3 className="font-heading text-4xl lg:text-5xl xl:text-6xl font-extrabold mb-6 leading-[1.1] text-transparent bg-clip-text bg-gradient-to-r from-white to-white/70 break-words [hyphens:auto]">
{title} {finalTitle}
</h3> </h3>
)} )}
<p className="text-white/60 text-lg mb-12 leading-relaxed"> <p className="text-white/60 text-lg mb-12 leading-relaxed">
{description} {finalDescription}
</p> </p>
{/* Industrial Stats Grid */} {/* Industrial Stats Grid */}
@@ -204,9 +211,7 @@ export function InteractiveGermanyMap({
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<MapPin className="w-3.5 h-3.5 text-primary" /> <MapPin className="w-3.5 h-3.5 text-primary" />
<span className="text-xs font-bold text-primary uppercase tracking-wider"> <span className="text-xs font-bold text-primary uppercase tracking-wider">
{activeLocation.type === 'hq' ? 'Hauptsitz' : {t(`types.${activeLocation.type === 'minor_node' ? 'project' : activeLocation.type}`)}
activeLocation.type === 'branch' ? 'Niederlassung' :
activeLocation.type === 'minor_node' ? 'Projekt' : 'Referenz'}
</span> </span>
</div> </div>
<div className="font-heading font-bold text-lg leading-tight mb-2"> <div className="font-heading font-bold text-lg leading-tight mb-2">
@@ -218,12 +223,7 @@ export function InteractiveGermanyMap({
</div> </div>
) : ( ) : (
<div className="text-white/60 text-xs mb-3"> <div className="text-white/60 text-xs mb-3">
{activeLocation.description === 'wind' ? 'Errichtung Leitungstrassen & Netzanbindung für Windpark-Infrastruktur.' : {t(`descriptions.${['wind','pv','fiber','power','battery'].includes(activeLocation.description || '') ? activeLocation.description : 'default'}`)}
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).'}
</div> </div>
)} )}
@@ -233,17 +233,11 @@ export function InteractiveGermanyMap({
<> <>
<li className="text-[11px] text-white/80 flex items-start gap-2 font-medium"> <li className="text-[11px] text-white/80 flex items-start gap-2 font-medium">
<CheckCircle2 className="w-3 h-3 text-primary shrink-0 mt-0.5" /> <CheckCircle2 className="w-3 h-3 text-primary shrink-0 mt-0.5" />
<span>{ <span>{t(`features.${['wind','pv','fiber','power','battery'].includes(activeLocation.description || '') ? activeLocation.description : 'default'}`)}</span>
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'
}</span>
</li> </li>
<li className="text-[11px] text-white/80 flex items-start gap-2 font-medium"> <li className="text-[11px] text-white/80 flex items-start gap-2 font-medium">
<CheckCircle2 className="w-3 h-3 text-primary shrink-0 mt-0.5" /> <CheckCircle2 className="w-3 h-3 text-primary shrink-0 mt-0.5" />
<span>Tiefbau & Spülbohrung (HDD)</span> <span>{t('features.method')}</span>
</li> </li>
</> </>
) : ( ) : (
@@ -259,7 +253,7 @@ export function InteractiveGermanyMap({
{activeLocation.href && ( {activeLocation.href && (
<div className="inline-flex items-center text-xs font-bold text-primary"> <div className="inline-flex items-center text-xs font-bold text-primary">
{locale === 'en' ? 'Learn more' : 'Zum Projekt'} {t('learnMore')}
<ArrowUpRight className="w-3.5 h-3.5 ml-1" /> <ArrowUpRight className="w-3.5 h-3.5 ml-1" />
</div> </div>
)} )}

View File

@@ -2,6 +2,7 @@ import * as React from 'react';
import { describe, it, expect, vi } from 'vitest'; import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react'; import { render, screen, fireEvent } from '@testing-library/react';
import { ReferencesSlider, Reference } from './ReferencesSlider'; import { ReferencesSlider, Reference } from './ReferencesSlider';
import { NextIntlClientProvider } from 'next-intl';
// Mock next/link to render standard <a> tag // Mock next/link to render standard <a> tag
vi.mock('next/link', () => ({ vi.mock('next/link', () => ({
@@ -21,6 +22,13 @@ vi.mock('next/image', () => ({
} }
})); }));
// Mock next-intl
vi.mock('next-intl', () => ({
useTranslations: () => (key: string) => key,
useLocale: () => 'en',
NextIntlClientProvider: ({ children }: any) => <>{children}</>
}));
describe('ReferencesSlider TDD', () => { describe('ReferencesSlider TDD', () => {
const mockReferences: Reference[] = [ const mockReferences: Reference[] = [
{ {
@@ -49,8 +57,8 @@ describe('ReferencesSlider TDD', () => {
const links = screen.getAllByTestId('reference-tile-link'); const links = screen.getAllByTestId('reference-tile-link');
expect(links).toHaveLength(2); expect(links).toHaveLength(2);
expect(links[0].getAttribute('href')).toBe('/referenzen/referenz-projekt-eins'); expect(links[0].getAttribute('href')).toBe('/en/referenzen/referenz-projekt-eins');
expect(links[1].getAttribute('href')).toBe('/referenzen/referenz-projekt-zwei'); expect(links[1].getAttribute('href')).toBe('/en/referenzen/referenz-projekt-zwei');
}); });
it('allows navigation on simple click (no drag)', () => { it('allows navigation on simple click (no drag)', () => {

View File

@@ -5,6 +5,7 @@ import { motion } from 'framer-motion';
import Link from 'next/link'; import Link from 'next/link';
import Image from 'next/image'; import Image from 'next/image';
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay'; import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
import { useTranslations, useLocale } from 'next-intl';
export interface Reference { export interface Reference {
id: string; id: string;
@@ -34,6 +35,9 @@ interface ReferencesSliderProps {
} }
export function ReferencesSlider(props: ReferencesSliderProps) { export function ReferencesSlider(props: ReferencesSliderProps) {
const t = useTranslations('ReferencesSlider');
const locale = useLocale();
const { data } = props; const { data } = props;
const references = props.references || []; const references = props.references || [];
const containerRef = React.useRef<HTMLDivElement>(null); const containerRef = React.useRef<HTMLDivElement>(null);
@@ -43,11 +47,11 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
const isDraggingRef = React.useRef(false); const isDraggingRef = React.useRef(false);
const [isDragging, setIsDragging] = React.useState(false); const [isDragging, setIsDragging] = React.useState(false);
const badge = props.badge || data?.badge || 'Ausgewählte Projekte'; const badge = props.badge || data?.badge || t('badge');
const title = props.title || data?.title || 'Referenzen & Erfolge'; const title = props.title || data?.title || t('title');
const description = props.description || data?.description || 'Ein Auszug unserer erfolgreich abgeschlossenen Projekte im Bereich Kabeltiefbau, Bohrtechnik und Netzinfrastruktur.'; const description = props.description || data?.description || t('description');
const ctaLabel = props.ctaLabel || data?.ctaLabel || 'Alle Referenzen ansehen'; const ctaLabel = props.ctaLabel || data?.ctaLabel || t('ctaLabel');
const ctaHref = props.ctaHref || data?.ctaHref || '/referenzen'; const ctaHref = props.ctaHref || data?.ctaHref || `/${locale}/referenzen`;
if (!references || references.length === 0) return null; if (!references || references.length === 0) return null;
@@ -132,7 +136,7 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
className="flex-shrink-0 w-[320px] md:w-[480px] snap-center group pointer-events-auto" className="flex-shrink-0 w-[320px] md:w-[480px] snap-center group pointer-events-auto"
> >
<Link <Link
href={`/referenzen/${ref.slug}`} href={`/${locale}/referenzen/${ref.slug}`}
onClick={handleTileClick} onClick={handleTileClick}
data-testid="reference-tile-link" data-testid="reference-tile-link"
className="block relative aspect-[16/10] bg-neutral-800 rounded-2xl overflow-hidden mb-5 border border-white/5 shadow-2xl" className="block relative aspect-[16/10] bg-neutral-800 rounded-2xl overflow-hidden mb-5 border border-white/5 shadow-2xl"

View File

@@ -28,20 +28,20 @@ describe('LanguageSwitcher TDD', () => {
vi.clearAllMocks(); vi.clearAllMocks();
}); });
it('translates /en/events to /de/messen when switching to German', () => { it('translates /en/trade-fairs to /de/messen when switching to German', () => {
vi.mocked(usePathname).mockReturnValue('/en/events'); (usePathname as any).mockReturnValue('/en/trade-fairs');
render(<LanguageSwitcher />); render(<LanguageSwitcher />);
const deLink = screen.getByLabelText('Switch to Deutsch'); const deLink = screen.getByLabelText('Switch to Deutsch');
expect(deLink.getAttribute('href')).toBe('/de/messen'); expect(deLink.getAttribute('href')).toBe('/de/messen');
}); });
it('translates /de/messen to /en/events when switching to English', () => { it('translates /de/messen to /en/trade-fairs when switching to English', () => {
vi.mocked(usePathname).mockReturnValue('/de/messen'); (usePathname as any).mockReturnValue('/de/messen');
render(<LanguageSwitcher />); render(<LanguageSwitcher />);
const enLink = screen.getByLabelText('Switch to English'); const enLink = screen.getByLabelText('Switch to English');
expect(enLink.getAttribute('href')).toBe('/en/events'); expect(enLink.getAttribute('href')).toBe('/en/trade-fairs');
}); });
it('translates /en/about-us to /de/ueber-uns when switching to German', () => { it('translates /en/about-us to /de/ueber-uns when switching to German', () => {

View File

@@ -52,7 +52,7 @@ layout: "fullBleed"
</div> </div>
</div> </div>
<InteractiveGermanyMap isHero={false} badge="Bundesweite Präsenz" title="Infrastruktur für ein ganzes Land" description="Seit 2023 haben wir in über 100 Projekten quer durch Deutschland bewiesen, dass wir Infrastrukturprojekte jeder Größenordnung verlässlich umsetzen." /> <InteractiveGermanyMap isHero={true} badge="Bundesweite Präsenz" title="Infrastruktur für ein ganzes Land" description="Seit 2023 haben wir in über 100 Projekten quer durch Deutschland bewiesen, dass wir Infrastrukturprojekte jeder Größenordnung verlässlich umsetzen." />
<CompanyTimeline /> <CompanyTimeline />

View File

@@ -12,7 +12,7 @@ layout: "fullBleed"
backgroundImage={{ url: '/assets/photos/DSC08653.JPG' }} backgroundImage={{ url: '/assets/photos/DSC08653.JPG' }}
alignment="center" alignment="center"
ctaLabel="Request a project" ctaLabel="Request a project"
ctaHref="/en/kontakt" ctaHref="/en/contact"
/> />
<div className="container px-4 max-w-7xl mx-auto py-16"> <div className="container px-4 max-w-7xl mx-auto py-16">
@@ -77,5 +77,5 @@ layout: "fullBleed"
title="Looking for a trenchless solution?" title="Looking for a trenchless solution?"
text="We would be happy to advise you on the possibilities of horizontal directional drilling for your project." text="We would be happy to advise you on the possibilities of horizontal directional drilling for your project."
buttonText="Contact us" buttonText="Contact us"
buttonLink="/en/kontakt" buttonLink="/en/contact"
/> />

View File

@@ -12,7 +12,7 @@ layout: "fullBleed"
backgroundImage={{ url: '/assets/photos/DSC01129.JPG' }} backgroundImage={{ url: '/assets/photos/DSC01129.JPG' }}
alignment="center" alignment="center"
ctaLabel="Request a project" ctaLabel="Request a project"
ctaHref="/en/kontakt" ctaHref="/en/contact"
/> />
<div className="container px-4 max-w-7xl mx-auto py-16"> <div className="container px-4 max-w-7xl mx-auto py-16">
@@ -75,5 +75,5 @@ layout: "fullBleed"
title="Are you planning an FTTX expansion?" title="Are you planning an FTTX expansion?"
text="We are happy to advise you on all aspects of FTTX and fiber optic expansion." text="We are happy to advise you on all aspects of FTTX and fiber optic expansion."
buttonText="Contact us" buttonText="Contact us"
buttonLink="/en/kontakt" buttonLink="/en/contact"
/> />

View File

@@ -12,7 +12,7 @@ layout: "fullBleed"
backgroundImage={{ url: '/assets/photos/DSC01123.JPG' }} backgroundImage={{ url: '/assets/photos/DSC01123.JPG' }}
alignment="center" alignment="center"
ctaLabel="Request a project" ctaLabel="Request a project"
ctaHref="/en/kontakt" ctaHref="/en/contact"
/> />
<div className="container px-4 max-w-7xl mx-auto py-16"> <div className="container px-4 max-w-7xl mx-auto py-16">
@@ -79,5 +79,5 @@ layout: "fullBleed"
title="Do you have a specific project?" title="Do you have a specific project?"
text="We would be happy to advise you on all questions regarding cable civil engineering and line laying." text="We would be happy to advise you on all questions regarding cable civil engineering and line laying."
buttonText="Contact us" buttonText="Contact us"
buttonLink="/en/kontakt" buttonLink="/en/contact"
/> />

View File

@@ -100,6 +100,6 @@ layout: "fullBleed"
title="Speculative Application (m/f/d)" title="Speculative Application (m/f/d)"
description="Can you imagine taking on a long-term challenge in our company but can't find a suitable job offer? Apply speculatively." description="Can you imagine taking on a long-term challenge in our company but can't find a suitable job offer? Apply speculatively."
ctaLabel="To the contact form" ctaLabel="To the contact form"
ctaHref="/en/kontakt" ctaHref="/en/contact"
theme="dark" theme="dark"
/> />

View File

@@ -11,7 +11,7 @@ layout: "fullBleed"
description="As a full-service partner in the field of cable civil engineering, we cover the entire spectrum of modern infrastructure projects." description="As a full-service partner in the field of cable civil engineering, we cover the entire spectrum of modern infrastructure projects."
videoUrl="/assets/videos/web/hero-bohrung.mp4" videoUrl="/assets/videos/web/hero-bohrung.mp4"
linkText="Get in Touch" linkText="Get in Touch"
linkHref="/en/kontakt" linkHref="/en/contact"
secondaryCtaLabel="View References" secondaryCtaLabel="View References"
secondaryCtaHref="/en/#referenzen" secondaryCtaHref="/en/#referenzen"
/> />
@@ -20,7 +20,7 @@ layout: "fullBleed"
badge="Core Competencies" badge="Core Competencies"
title="Precision & Performance" title="Precision & Performance"
ctaLabel="Request a project" ctaLabel="Request a project"
ctaHref="/en/kontakt" ctaHref="/en/contact"
items={[ items={[
{ {
title: "Cable Construction", title: "Cable Construction",

View File

@@ -11,7 +11,7 @@ layout: "fullBleed"
description="Get to know the E-TIB Group at the leading industry trade fairs." description="Get to know the E-TIB Group at the leading industry trade fairs."
videoUrl="/assets/videos/web/hero-bahnkreuzung.mp4" videoUrl="/assets/videos/web/hero-bahnkreuzung.mp4"
linkText="Get in Touch" linkText="Get in Touch"
linkHref="/en/kontakt" linkHref="/en/contact"
/> />
<div id="termine"> <div id="termine">

View File

@@ -12,7 +12,7 @@ layout: "fullBleed"
backgroundImage={{ url: '/assets/photos/DJI_0048.JPG' }} backgroundImage={{ url: '/assets/photos/DJI_0048.JPG' }}
alignment="center" alignment="center"
ctaLabel="Request a project" ctaLabel="Request a project"
ctaHref="/en/kontakt" ctaHref="/en/contact"
/> />
<div className="container px-4 max-w-7xl mx-auto py-16"> <div className="container px-4 max-w-7xl mx-auto py-16">
@@ -75,5 +75,5 @@ layout: "fullBleed"
title="Do you need planning security?" title="Do you need planning security?"
text="Let's talk about your project. We are at your side with our engineering knowledge." text="Let's talk about your project. We are at your side with our engineering knowledge."
buttonText="Contact us" buttonText="Contact us"
buttonLink="/en/kontakt" buttonLink="/en/contact"
/> />

View File

@@ -86,5 +86,5 @@ layout: "fullBleed"
title="Become part of our team" title="Become part of our team"
text="We are always looking for motivated talents for exciting infrastructure projects." text="We are always looking for motivated talents for exciting infrastructure projects."
buttonText="View open positions" buttonText="View open positions"
buttonLink="/en/karriere" buttonLink="/en/career"
/> />

View File

@@ -11,7 +11,7 @@ layout: "fullBleed"
description="Since 2015, we have been growing continuously to meet the demands of the energy transition and digitalization. As a corporate group, we pool specialized expertise under one roof." description="Since 2015, we have been growing continuously to meet the demands of the energy transition and digitalization. As a corporate group, we pool specialized expertise under one roof."
videoUrl="/assets/videos/web/hero-bahnkreuzung.mp4" videoUrl="/assets/videos/web/hero-bahnkreuzung.mp4"
linkText="Open Positions" linkText="Open Positions"
linkHref="/en/karriere" linkHref="/en/career"
secondaryCtaLabel="Discover Company" secondaryCtaLabel="Discover Company"
secondaryCtaHref="#structure" secondaryCtaHref="#structure"
/> />
@@ -52,42 +52,9 @@ layout: "fullBleed"
</div> </div>
</div> </div>
<InteractiveGermanyMap isHero={false} badge="Nationwide Presence" title="Infrastructure for an entire country" description="Since 2023, we have proven in over 100 projects across Germany that we reliably implement infrastructure projects of any scale." /> <InteractiveGermanyMap isHero={true} badge="Nationwide Presence" title="Infrastructure for an entire country" description="Since 2023, we have proven in over 100 projects across Germany that we reliably implement infrastructure projects of any scale." />
<CompanyTimeline <CompanyTimeline />
badge="Our History"
title="Milestones of Development"
milestones={[
{
date: '16.12.2015',
year: '2015',
title: 'Foundation of E-TIB GmbH',
desc: 'Execution of electrical infrastructure projects, cable civil engineering, and horizontal directional drilling.',
iconName: 'Building2'
},
{
date: '04.02.2019',
year: '2019',
title: 'Foundation of E-TIB Ingenieurgesellschaft',
desc: 'Approval and execution planning, complex crossings, and grid connection planning.',
iconName: 'Compass'
},
{
date: '14.11.2019',
year: '2019',
title: 'Foundation of E-TIB Verwaltung GmbH',
desc: 'Central services, acquisition, leasing, and management of real estate and machinery.',
iconName: 'Layers'
},
{
date: '21.10.2025',
year: '2025',
title: 'Foundation of E-TIB Bohrtechnik GmbH',
desc: 'Specialization in precise horizontal directional drilling in all soil classes.',
iconName: 'ArrowDownToLine'
}
]}
/>
<div id="structure"> <div id="structure">
<HomeSubCompanyTiles <HomeSubCompanyTiles
@@ -128,5 +95,5 @@ layout: "fullBleed"
title="Ready for Your Project?" title="Ready for Your Project?"
description="We are always looking for new challenges and strong partners. Contact us for a non-binding consultation on your project." description="We are always looking for new challenges and strong partners. Contact us for a non-binding consultation on your project."
ctaLabel="Contact Us Now" ctaLabel="Contact Us Now"
ctaHref="/en/kontakt" ctaHref="/en/contact"
/> />

View File

@@ -12,7 +12,7 @@ layout: "fullBleed"
backgroundImage={{ url: '/assets/photos/DSC08653.JPG' }} backgroundImage={{ url: '/assets/photos/DSC08653.JPG' }}
alignment="center" alignment="center"
ctaLabel="Request a project" ctaLabel="Request a project"
ctaHref="/en/kontakt" ctaHref="/en/contact"
/> />
<div className="container px-4 max-w-7xl mx-auto py-16"> <div className="container px-4 max-w-7xl mx-auto py-16">
@@ -77,5 +77,5 @@ layout: "fullBleed"
title="Are you facing high documentation requirements?" title="Are you facing high documentation requirements?"
text="Contact us for professional surveying and GIS services." text="Contact us for professional surveying and GIS services."
buttonText="Contact us" buttonText="Contact us"
buttonLink="/en/kontakt" buttonLink="/en/contact"
/> />

View File

@@ -188,5 +188,59 @@
"technical": "Technik", "technical": "Technik",
"sustainability": "Nachhaltigkeit" "sustainability": "Nachhaltigkeit"
} }
},
"ReferencesSlider": {
"badge": "Ausgewählte Projekte",
"title": "Referenzen & Erfolge",
"description": "Ein Auszug unserer erfolgreich abgeschlossenen Projekte im Bereich Kabeltiefbau, Bohrtechnik und Netzinfrastruktur.",
"ctaLabel": "Alle Referenzen ansehen"
},
"CompanyTimeline": {
"badge": "Unsere Geschichte",
"title": "Meilensteine der Entwicklung",
"milestones": {
"2015": {
"title": "Gründung E-TIB GmbH",
"desc": "Ausführung elektrischer Infrastrukturprojekte, Kabeltiefbau und Horizontalspülbohrungen."
},
"2019_ing": {
"title": "Gründung E-TIB Ingenieurgesellschaft",
"desc": "Genehmigungs- und Ausführungsplanung, komplexe Querungen sowie Netzanschlussplanung."
},
"2019_verw": {
"title": "Gründung E-TIB Verwaltung GmbH",
"desc": "Zentrale Dienste, Erwerb, Vermietung, Verpachtung und Verwaltung von Immobilien und Maschinen."
},
"2025": {
"title": "Gründung E-TIB Bohrtechnik GmbH",
"desc": "Spezialisierung auf präzise Horizontalspülbohrungen in allen Bodenklassen."
}
}
},
"InteractiveGermanyMap": {
"types": {
"hq": "Hauptsitz",
"branch": "Niederlassung",
"project": "Projekt",
"reference": "Referenz"
},
"descriptions": {
"wind": "Errichtung Leitungstrassen & Netzanbindung für Windpark-Infrastruktur.",
"pv": "Bau von Mittelspannungstrassen zur Einspeisung der PV-Anlage.",
"fiber": "Komplexer FTTx Breitbandausbau inkl. LWL-Montage & Tiefbau.",
"power": "110kV Hochspannungstrasse Erdkabelverlegung.",
"battery": "Infrastrukturausbau für BESS (Batteriespeicher).",
"default": "Komplexes Infrastrukturprojekt (Tiefbau & HDD)."
},
"features": {
"wind": "Netzanbindung Windpark",
"pv": "Einspeisung PV-Anlage",
"fiber": "FTTx Infrastruktur",
"power": "110kV Trasse",
"battery": "BESS Anschluss",
"default": "Infrastrukturprojekt",
"method": "Tiefbau & Spülbohrung (HDD)"
},
"learnMore": "Zum Projekt"
} }
} }

View File

@@ -9,7 +9,7 @@
"about-us": "ueber-uns", "about-us": "ueber-uns",
"terms": "agb", "terms": "agb",
"start": "start", "start": "start",
"events": "messen" "trade-fairs": "messen"
}, },
"products": {}, "products": {},
"categories": {} "categories": {}
@@ -188,5 +188,59 @@
"technical": "Technical", "technical": "Technical",
"sustainability": "Sustainability" "sustainability": "Sustainability"
} }
},
"ReferencesSlider": {
"badge": "Selected Projects",
"title": "References & Successes",
"description": "A selection of our successfully completed projects in underground cable engineering, drilling technology, and grid infrastructure.",
"ctaLabel": "View All References"
},
"CompanyTimeline": {
"badge": "Our History",
"title": "Milestones of Development",
"milestones": {
"2015": {
"title": "Foundation of E-TIB GmbH",
"desc": "Execution of electrical infrastructure projects, cable civil engineering, and horizontal directional drilling."
},
"2019_ing": {
"title": "Foundation of E-TIB Ingenieurgesellschaft",
"desc": "Permit and execution planning, complex crossings, and grid connection planning."
},
"2019_verw": {
"title": "Foundation of E-TIB Verwaltung GmbH",
"desc": "Central services, acquisition, leasing, and management of real estate and machinery."
},
"2025": {
"title": "Foundation of E-TIB Bohrtechnik GmbH",
"desc": "Specialization in precise horizontal directional drilling in all soil classes."
}
}
},
"InteractiveGermanyMap": {
"types": {
"hq": "Headquarters",
"branch": "Branch",
"project": "Project",
"reference": "Reference"
},
"descriptions": {
"wind": "Construction of cable routes & grid connection for wind park infrastructure.",
"pv": "Construction of medium-voltage routes for feeding PV systems.",
"fiber": "Complex FTTx broadband expansion incl. fiber optic assembly & civil engineering.",
"power": "110kV high-voltage underground cable laying.",
"battery": "Infrastructure expansion for BESS (battery storage).",
"default": "Complex infrastructure project (civil engineering & HDD)."
},
"features": {
"wind": "Grid connection wind park",
"pv": "Feeding PV system",
"fiber": "FTTx Infrastructure",
"power": "110kV Route",
"battery": "BESS Connection",
"default": "Infrastructure project",
"method": "Civil engineering & HDD"
},
"learnMore": "Learn more"
} }
} }

26
tests/mdx-links.test.ts Normal file
View File

@@ -0,0 +1,26 @@
import { describe, it, expect } from 'vitest';
import fs from 'fs';
import path from 'path';
describe('English MDX Content Links', () => {
it('should not contain German slug routes like /en/kontakt or /en/karriere', () => {
const enContentDir = path.join(process.cwd(), 'content', 'en');
const files = fs.readdirSync(enContentDir).filter(f => f.endsWith('.mdx'));
const forbiddenLinks = ['/en/kontakt', '/en/karriere'];
const infractions: string[] = [];
files.forEach(file => {
const filePath = path.join(enContentDir, file);
const content = fs.readFileSync(filePath, 'utf8');
forbiddenLinks.forEach(link => {
if (content.includes(link)) {
infractions.push(`${file} contains forbidden link "${link}"`);
}
});
});
expect(infractions).toEqual([]);
});
});