Compare commits

...

10 Commits

Author SHA1 Message Date
68c4a056b7 fix: update trade fair dates to 2026
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Successful in 1m9s
Build & Deploy / 🏗️ Build (push) Successful in 2m45s
Build & Deploy / 🚀 Deploy (push) Successful in 26s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 46s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-05-28 13:43:49 +02:00
57a67ffdf3 feat: refine subpages layout, slugs and B2B copywriting
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Successful in 1m8s
Build & Deploy / 🏗️ Build (push) Successful in 2m42s
Build & Deploy / 🚀 Deploy (push) Failing after 25s
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-05-28 13:22:37 +02:00
5977ebf23d fix(ci): fix getTranslations import and clean AISearchResults orphaned syntax
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 24s
Build & Deploy / 🧪 QA (push) Successful in 1m9s
Build & Deploy / 🏗️ Build (push) Successful in 2m49s
Build & Deploy / 🚀 Deploy (push) Successful in 26s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 45s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-05-28 11:48:21 +02:00
ee2bcea42b fix(team,certs): revert Danny Joseph email and add missing text certificates
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 26s
Build & Deploy / 🧪 QA (push) Failing after 59s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 4s
2026-05-28 11:08:11 +02:00
2f2fcfdf13 fix(typography): prevent ugly line breaks in InteractiveGermanyMap titles
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 25s
Build & Deploy / 🧪 QA (push) Failing after 58s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 4s
2026-05-28 10:38:07 +02:00
070f97dd6f fix(i18n): fix reference page translation, fix map zoom, connect contact form to server action, fix english 404 links, fix certificate pdf links
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 26s
Build & Deploy / 🧪 QA (push) Failing after 1m0s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 6s
2026-05-28 09:58:34 +02:00
2a46015d0d fix(i18n): localize TeamGrid component
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 29s
Build & Deploy / 🧪 QA (push) Successful in 1m12s
Build & Deploy / 🏗️ Build (push) Successful in 2m58s
Build & Deploy / 🚀 Deploy (push) Successful in 30s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 48s
Build & Deploy / 🔔 Notify (push) Successful in 5s
2026-05-28 09:38:59 +02:00
5329d96e3b fix(links): fix unlocalized and missing hrefs in map, hero, job listing and call to action
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 28s
Build & Deploy / 🧪 QA (push) Successful in 1m11s
Build & Deploy / 🏗️ Build (push) Successful in 2m51s
Build & Deploy / 🚀 Deploy (push) Successful in 30s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 47s
Build & Deploy / 🔔 Notify (push) Successful in 4s
2026-05-27 17:49:23 +02:00
0c01aa799d fix(contact): update email to info@e-tib.com and fix map zoom level
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 18s
Build & Deploy / 🧪 QA (push) Successful in 1m7s
Build & Deploy / 🏗️ Build (push) Successful in 2m41s
Build & Deploy / 🚀 Deploy (push) Successful in 30s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 49s
Build & Deploy / 🔔 Notify (push) Successful in 4s
2026-05-27 17:41:52 +02:00
75c15fce43 fix(i18n): ensure next-intl client component namespaces are loaded
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 21s
Build & Deploy / 🧪 QA (push) Successful in 1m7s
Build & Deploy / 🏗️ Build (push) Successful in 2m44s
Build & Deploy / 🚀 Deploy (push) Successful in 30s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 51s
Build & Deploy / 🔔 Notify (push) Successful in 4s
2026-05-27 16:41:17 +02:00
43 changed files with 506 additions and 168 deletions

View File

@@ -123,6 +123,13 @@ export default async function Layout(props: {
'Error', 'Error',
'StandardPage', 'StandardPage',
'Brochure', 'Brochure',
'JobListingBlock',
'CallToAction',
'InteractiveGermanyMap',
'ReferencesSlider',
'CompanyTimeline',
'TeamGrid',
'AISearch',
]; ];
const clientMessages: Record<string, any> = {}; const clientMessages: Record<string, any> = {};
for (const key of clientKeys) { for (const key of clientKeys) {

View File

@@ -0,0 +1,38 @@
import { describe, it, expect, vi } from 'vitest';
import ReferenceDetail from './page';
// Mock next/navigation
vi.mock('next/navigation', () => ({
notFound: vi.fn(),
}));
// Mock next-intl/server
vi.mock('next-intl/server', () => ({
setRequestLocale: vi.fn(),
getTranslations: vi.fn().mockResolvedValue(() => 'translated text'),
}));
// Mock @/lib/references
vi.mock('@/lib/references', () => ({
getReferenceBySlug: vi.fn().mockResolvedValue({
slug: 'test-slug',
frontmatter: {
title: 'Test Title',
location: 'Test Location',
category: 'Test Category',
},
content: 'Test content',
}),
}));
describe('ReferenceDetail Page TDD', () => {
it('renders reference detail correctly', async () => {
const props = {
params: Promise.resolve({ locale: 'de', slug: 'test-slug' })
};
// We expect the execution to fail if getTranslations is not defined/imported inside page.tsx
const component = await ReferenceDetail(props);
expect(component).toBeTruthy();
});
});

View File

@@ -1,6 +1,6 @@
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import { Container, Badge, Heading } from '@/components/ui'; import { Container, Badge, Heading } from '@/components/ui';
import { setRequestLocale } from 'next-intl/server'; import { setRequestLocale, getTranslations } from 'next-intl/server';
import { Metadata } from 'next'; import { Metadata } from 'next';
import { getReferenceBySlug, getAllReferences } from '@/lib/references'; import { getReferenceBySlug, getAllReferences } from '@/lib/references';
import { MDXRemote } from 'next-mdx-remote/rsc'; import { MDXRemote } from 'next-mdx-remote/rsc';
@@ -57,6 +57,7 @@ export default async function ReferenceDetail(props: { params: Promise<{ locale:
setRequestLocale(locale); setRequestLocale(locale);
const reference = await getReferenceBySlug(slug, locale); const reference = await getReferenceBySlug(slug, locale);
const t = await getTranslations('ReferenceDetail');
if (!reference) { if (!reference) {
notFound(); notFound();
@@ -119,12 +120,12 @@ export default async function ReferenceDetail(props: { params: Promise<{ locale:
eventProperties={{ location: 'reference_back_btn' }} eventProperties={{ location: 'reference_back_btn' }}
> >
<ChevronLeft className="w-5 h-5" /> <ChevronLeft className="w-5 h-5" />
Zurück zur Übersicht {t('backToOverview')}
</TrackedLink> </TrackedLink>
</div> </div>
<div className="max-w-4xl"> <div className="max-w-4xl">
<Badge variant="accent" className="mb-4 md:mb-6"> <Badge variant="accent" className="mb-4 md:mb-6">
Projektreferenz {t('projectReference')}
</Badge> </Badge>
<Heading level={1} variant="white" className="mb-8 leading-tight"> <Heading level={1} variant="white" className="mb-8 leading-tight">
{reference.frontmatter.title} {reference.frontmatter.title}
@@ -134,21 +135,21 @@ export default async function ReferenceDetail(props: { params: Promise<{ locale:
<div className="flex items-start gap-4 p-6 bg-white/5 rounded-2xl border border-white/10 backdrop-blur-sm"> <div className="flex items-start gap-4 p-6 bg-white/5 rounded-2xl border border-white/10 backdrop-blur-sm">
<MapPin className="w-8 h-8 text-primary shrink-0" /> <MapPin className="w-8 h-8 text-primary shrink-0" />
<div> <div>
<p className="text-sm text-white/50 mb-1 uppercase tracking-wider font-bold">Ort</p> <p className="text-sm text-white/50 mb-1 uppercase tracking-wider font-bold">{t('location')}</p>
<p className="font-semibold text-lg">{reference.frontmatter.location}</p> <p className="font-semibold text-lg">{reference.frontmatter.location}</p>
</div> </div>
</div> </div>
<div className="flex items-start gap-4 p-6 bg-white/5 rounded-2xl border border-white/10 backdrop-blur-sm"> <div className="flex items-start gap-4 p-6 bg-white/5 rounded-2xl border border-white/10 backdrop-blur-sm">
<Briefcase className="w-8 h-8 text-primary shrink-0" /> <Briefcase className="w-8 h-8 text-primary shrink-0" />
<div> <div>
<p className="text-sm text-white/50 mb-1 uppercase tracking-wider font-bold">Auftraggeber</p> <p className="text-sm text-white/50 mb-1 uppercase tracking-wider font-bold">{t('client')}</p>
<p className="font-semibold text-lg">{reference.frontmatter.client}</p> <p className="font-semibold text-lg">{reference.frontmatter.client}</p>
</div> </div>
</div> </div>
<div className="flex items-start gap-4 p-6 bg-white/5 rounded-2xl border border-white/10 backdrop-blur-sm"> <div className="flex items-start gap-4 p-6 bg-white/5 rounded-2xl border border-white/10 backdrop-blur-sm">
<Calendar className="w-8 h-8 text-primary shrink-0" /> <Calendar className="w-8 h-8 text-primary shrink-0" />
<div> <div>
<p className="text-sm text-white/50 mb-1 uppercase tracking-wider font-bold">Zeitraum</p> <p className="text-sm text-white/50 mb-1 uppercase tracking-wider font-bold">{t('period')}</p>
<p className="font-semibold text-lg">{reference.frontmatter.dateString || new Date(reference.frontmatter.date).getFullYear()}</p> <p className="font-semibold text-lg">{reference.frontmatter.dateString || new Date(reference.frontmatter.date).getFullYear()}</p>
</div> </div>
</div> </div>
@@ -160,7 +161,7 @@ export default async function ReferenceDetail(props: { params: Promise<{ locale:
{/* Main Content Area */} {/* Main Content Area */}
<Container className="py-16 md:py-24"> <Container className="py-16 md:py-24">
<div className="max-w-4xl mx-auto"> <div className="max-w-4xl mx-auto">
<h2 className="text-3xl font-bold text-neutral-dark mb-8">Leistungsumfang & Projektbeschreibung</h2> <h2 className="text-3xl font-bold text-neutral-dark mb-8">{t('scopeTitle')}</h2>
<div className="w-full"> <div className="w-full">
<MDXRemote source={reference.content} components={mdxComponents} /> <MDXRemote source={reference.content} components={mdxComponents} />
@@ -173,7 +174,7 @@ export default async function ReferenceDetail(props: { params: Promise<{ locale:
eventProperties={{ location: 'reference_bottom_back_btn' }} eventProperties={{ location: 'reference_bottom_back_btn' }}
> >
<span className="relative z-10 flex items-center justify-center gap-2"> <span className="relative z-10 flex items-center justify-center gap-2">
Alle Referenzen ansehen {t('viewAll')}
</span> </span>
<ButtonOverlay variant="primary" /> <ButtonOverlay variant="primary" />
</TrackedLink> </TrackedLink>

View File

@@ -76,7 +76,7 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
<InteractiveGermanyMap <InteractiveGermanyMap
isHero={true} isHero={true}
badge={locale === 'en' ? 'Our References' : 'Unsere Referenzen'} badge={locale === 'en' ? 'Our References' : 'Unsere Referenzen'}
title={locale === 'en' ? <>Successfully realized<br/>projects.</> : <>Erfolgreich umgesetzte<br/>Projekte.</>} title={locale === 'en' ? 'Successfully realized projects.' : 'Erfolgreich umgesetzte Projekte.'}
description={locale === 'en' ? 'From broadband expansion to complex 110kV lines: A selection of our nationwide projects where we have created infrastructure for the future. Discover our locations.' : '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.'} description={locale === 'en' ? 'From broadband expansion to complex 110kV lines: A selection of our nationwide projects where we have created infrastructure for the future. Discover our locations.' : '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.'}
locations={enrichedLocations} locations={enrichedLocations}
/> />

View File

@@ -36,7 +36,7 @@ export default function LeafletMap({ locations }: LeafletMapProps) {
const firstLoc = locations[0]; const firstLoc = locations[0];
const map = L.map(mapRef.current, { const map = L.map(mapRef.current, {
center: [firstLoc.lat, firstLoc.lng], center: [firstLoc.lat, firstLoc.lng],
zoom: 6, // Zoom out to see both or more zoom: locations.length === 1 ? 16 : 6,
scrollWheelZoom: false, scrollWheelZoom: false,
}); });

View File

@@ -12,7 +12,7 @@ import { LogoArcs } from '@/components/ui/LogoArcs';
interface Certificate { interface Certificate {
title: string; title: string;
description: string; description: string;
pdfUrl: string; pdfUrl?: string;
type: 'iso' | 'tax' | 'general'; type: 'iso' | 'tax' | 'general';
date?: string; date?: string;
} }
@@ -29,45 +29,70 @@ const defaultCertificates: Certificate[] = [
{ {
title: 'ISO 14001:2015', title: 'ISO 14001:2015',
description: 'Umweltmanagementsystem', description: 'Umweltmanagementsystem',
pdfUrl: '/assets/certificates/231214_Zertifikat ISO 14001 Umweltmanagement.pdf', pdfUrl: '/assets/certificates/iso14001.pdf',
type: 'iso', type: 'iso',
date: '14.12.2023', date: '14.12.2023',
}, },
{ {
title: 'ISO 9001:2015', title: 'ISO 9001:2015',
description: 'Qualitätsmanagementsystem', description: 'Qualitätsmanagementsystem',
pdfUrl: '/assets/certificates/231214_Zertifikat ISO 9001 Qualitätsmanagement.pdf', pdfUrl: '/assets/certificates/iso9001.pdf',
type: 'iso', type: 'iso',
date: '14.12.2023', date: '14.12.2023',
}, },
{ {
title: 'DIN EN ISO 45001:2018', title: 'DIN EN ISO 45001:2018',
description: 'Arbeits- und Gesundheitsschutz', description: 'Arbeits- und Gesundheitsschutz',
pdfUrl: '/assets/certificates/Zertifizierung DIN EN ISO 45001 bis 05122028.pdf', pdfUrl: '/assets/certificates/iso45001.pdf',
type: 'iso', type: 'iso',
date: '05.12.2025', // Assuming valid till date: '05.12.2025',
}, },
{ {
title: 'Freistellungsbescheinigung', title: 'Freistellungsbescheinigung',
description: 'Nach § 48 b EStG', description: 'Nach § 48 b EStG',
pdfUrl: '/assets/certificates/240209_Freistellungsbescheinigung § 48 b.pdf', pdfUrl: '/assets/certificates/freistellung.pdf',
type: 'tax', type: 'tax',
date: '09.02.2024', date: '09.02.2024',
}, },
{ {
title: 'Nachweis § 13b UStG', title: 'Nachweis § 13b UStG',
description: 'Steuerschuldnerschaft des Leistungsempfängers', description: 'Steuerschuldnerschaft des Leistungsempfängers',
pdfUrl: '/assets/certificates/240209_Nachweis § 13 b.pdf', pdfUrl: '/assets/certificates/nachweis13b.pdf',
type: 'tax', type: 'tax',
date: '09.02.2024', date: '09.02.2024',
}, },
{ {
title: 'Bescheinigung in Steuersachen', title: 'Bescheinigung in Steuersachen',
description: 'Zertifikat des Finanzamtes', description: 'Zertifikat des Finanzamtes',
pdfUrl: '/assets/certificates/250213_Bescheinigung in Steuersachen.pdf', pdfUrl: '/assets/certificates/bescheinigung.pdf',
type: 'tax', type: 'tax',
date: '13.02.2025', date: '13.02.2025',
}, },
{
title: 'Unbedenklichkeit IHK',
description: 'Industrie- und Handelskammer',
type: 'general',
},
{
title: 'Unbedenklichkeit HWK',
description: 'Handwerkskammer',
type: 'general',
},
{
title: 'Handelsregisterauszug',
description: 'Amtsgericht Cottbus',
type: 'general',
},
{
title: 'Gewerbe-Anmeldung',
description: 'Stadt Guben',
type: 'general',
},
{
title: 'Gewerbezentralregister',
description: 'Auskunft aus dem Register',
type: 'general',
},
]; ];
export function CertificatesBlock({ badge, title, description, certificates = defaultCertificates, hideHeader = false }: CertificatesBlockProps) { export function CertificatesBlock({ badge, title, description, certificates = defaultCertificates, hideHeader = false }: CertificatesBlockProps) {
@@ -143,13 +168,17 @@ export function CertificatesBlock({ badge, title, description, certificates = de
> >
{certificates.map((cert, index) => { {certificates.map((cert, index) => {
const isIso = cert.type === 'iso'; const isIso = cert.type === 'iso';
const Wrapper = cert.pdfUrl ? motion.a : motion.div;
const wrapperProps = cert.pdfUrl ? {
href: encodeURI(cert.pdfUrl),
target: "_blank",
rel: "noopener noreferrer"
} : {};
return ( return (
<motion.a <Wrapper
key={index} key={index}
href={encodeURI(cert.pdfUrl)} {...wrapperProps as any}
target="_blank"
rel="noopener noreferrer"
variants={itemVariants} 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-8 flex flex-col justify-between overflow-hidden transition-all duration-500 hover:shadow-xl hover:-translate-y-1 border ${
isIso isIso
@@ -196,14 +225,22 @@ export function CertificatesBlock({ badge, title, description, certificates = de
</p> </p>
</div> </div>
<div className={`mt-auto inline-flex items-center text-sm font-bold uppercase tracking-wider group-hover:gap-3 transition-all ${ <div className={`mt-auto inline-flex items-center text-sm font-bold uppercase tracking-wider transition-all ${
cert.pdfUrl ? 'group-hover:gap-3' : ''
} ${
isIso ? 'text-primary-light' : 'text-primary' isIso ? 'text-primary-light' : 'text-primary'
}`}> }`}>
<span>Download PDF</span> {cert.pdfUrl ? (
<Download size={16} className="ml-2 group-hover:translate-y-0.5 transition-transform" /> <>
<span>Download PDF</span>
<Download size={16} className="ml-2 group-hover:translate-y-0.5 transition-transform" />
</>
) : (
<span className="text-neutral-400">Nachweis liegt vor</span>
)}
</div> </div>
</div> </div>
</motion.a> </Wrapper>
); );
})} })}
</motion.div> </motion.div>

View File

@@ -7,13 +7,13 @@ import { useLocale } from 'next-intl';
export function DataGridPulse() { export function DataGridPulse() {
const locale = useLocale(); const locale = useLocale();
return ( return (
<div className="relative w-full py-32 bg-[#050B14] overflow-hidden flex flex-col items-center border-y border-primary/20 -mx-[50vw] px-[50vw] mb-16"> <div className="relative w-full py-16 md:py-24 bg-neutral-50 border border-neutral-100/80 overflow-hidden flex flex-col items-center rounded-3xl mb-16 shadow-sm">
{/* Blueprint grid background */} {/* Blueprint grid background */}
<div <div
className="absolute inset-0 opacity-20 pointer-events-none" className="absolute inset-0 opacity-[0.03] pointer-events-none"
style={{ style={{
backgroundImage: 'linear-gradient(#82ED20 1px, transparent 1px), linear-gradient(90deg, #82ED20 1px, transparent 1px)', backgroundImage: 'linear-gradient(#117C61 1px, transparent 1px), linear-gradient(90deg, #117C61 1px, transparent 1px)',
backgroundSize: '40px 40px' backgroundSize: '40px 40px'
}} }}
/> />
@@ -21,25 +21,25 @@ export function DataGridPulse() {
{/* Pulsing horizontal lines (Data Flow) */} {/* Pulsing horizontal lines (Data Flow) */}
<motion.div <motion.div
animate={{ x: ["-100%", "100%"] }} animate={{ x: ["-100%", "100%"] }}
transition={{ duration: 3, repeat: Infinity, ease: "linear" }} transition={{ duration: 5, repeat: Infinity, ease: "linear" }}
className="absolute top-1/3 left-0 w-full h-[2px] bg-gradient-to-r from-transparent via-primary to-transparent opacity-50 blur-[2px]" className="absolute top-1/3 left-0 w-full h-[2px] bg-gradient-to-r from-transparent via-primary/30 to-transparent blur-[1px]"
/> />
<motion.div <motion.div
animate={{ x: ["100%", "-100%"] }} animate={{ x: ["100%", "-100%"] }}
transition={{ duration: 4, repeat: Infinity, ease: "linear" }} transition={{ duration: 7, repeat: Infinity, ease: "linear" }}
className="absolute top-2/3 left-0 w-full h-[2px] bg-gradient-to-r from-transparent via-white to-transparent opacity-30 blur-[2px]" className="absolute top-2/3 left-0 w-full h-[2px] bg-gradient-to-r from-transparent via-primary/25 to-transparent blur-[1px]"
/> />
<div className="relative z-10 w-full max-w-4xl mx-auto flex flex-col items-center text-center"> <div className="relative z-10 w-full max-w-2xl px-6 mx-auto flex flex-col items-center text-center">
<h3 className="text-sm font-bold uppercase tracking-widest text-primary mb-6"> <span className="text-xs md:text-sm font-bold uppercase tracking-widest text-primary mb-4 bg-primary/10 px-3 py-1 rounded-full">
{locale === 'en' ? 'Network Expansion in Numbers' : 'Netzausbau in Zahlen'} {locale === 'en' ? 'Network Expansion in Numbers' : 'Netzausbau in Zahlen'}
</h3> </span>
<div className="bg-black/40 backdrop-blur-md p-12 md:p-16 rounded-3xl border border-white/10 shadow-2xl"> <div className="bg-white/80 backdrop-blur-md p-8 md:p-12 rounded-2xl border border-neutral-200/50 shadow-lg w-full">
<div className="font-mono text-6xl md:text-8xl font-black text-white tracking-tighter mb-4 leading-none" style={{ fontVariantNumeric: 'tabular-nums' }}> <div className="font-mono text-5xl md:text-7xl font-black text-neutral-900 tracking-tighter mb-4 leading-none animate-pulse-slow" style={{ fontVariantNumeric: 'tabular-nums' }}>
372.161<span className="text-primary text-3xl md:text-5xl ml-2">m</span> 372.161<span className="text-primary text-2xl md:text-4xl ml-1 font-sans">m</span>
</div> </div>
<p className="text-white/70 text-lg md:text-xl font-medium max-w-lg mx-auto"> <p className="text-text-secondary text-base md:text-lg font-medium max-w-md mx-auto leading-relaxed">
{locale === 'en' {locale === 'en'
? 'Cable routes laid since 2023. A massive infrastructure achievement, driven by our own machinery.' ? '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.'} : 'Verlegte Kabeltrassen seit 2023. Eine massive Infrastrukturleistung, getragen von unserem eigenen Maschinenpark.'}

View File

@@ -30,10 +30,7 @@ export function InteractiveGermanyMap({
badge, badge,
title, title,
description, description,
stats = [ stats,
{ value: '100', suffix: '%', label: 'Überregionale Reichweite' },
{ value: '2', suffix: '+', label: 'Operative Standorte' },
],
locations = allLocations, locations = allLocations,
isHero = false isHero = false
}: InteractiveGermanyMapProps) { }: InteractiveGermanyMapProps) {
@@ -43,6 +40,11 @@ export function InteractiveGermanyMap({
const t = useTranslations('InteractiveGermanyMap'); const t = useTranslations('InteractiveGermanyMap');
const tStandard = useTranslations('StandardPage'); const tStandard = useTranslations('StandardPage');
const finalStats = stats || [
{ value: '100', suffix: '%', label: locale === 'en' ? 'Nationwide Reach' : 'Überregionale Reichweite' },
{ value: '2', suffix: '+', label: locale === 'en' ? 'Operational Locations' : 'Operative Standorte' },
];
const finalBadge = badge || tStandard('badge'); const finalBadge = badge || tStandard('badge');
// the map is mostly used with specific props, so we leave title & description to fallbacks if not provided // 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 finalTitle = title || (locale === 'en' ? <>Nationwide<br/>in operation for you.</> : <>Deutschlandweit<br/>für Sie im Einsatz.</>);
@@ -73,11 +75,11 @@ export function InteractiveGermanyMap({
</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 text-balance">
{finalTitle} {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 text-balance">
{finalTitle} {finalTitle}
</h3> </h3>
)} )}
@@ -88,7 +90,7 @@ export function InteractiveGermanyMap({
{/* Industrial Stats Grid */} {/* Industrial Stats Grid */}
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
{stats.map((stat, i) => ( {finalStats.map((stat, i) => (
<div key={i} className="bg-white/5 border border-white/10 rounded-2xl p-6 backdrop-blur-md relative overflow-hidden group"> <div key={i} className="bg-white/5 border border-white/10 rounded-2xl p-6 backdrop-blur-md relative overflow-hidden group">
<div className="absolute inset-0 bg-gradient-to-br from-primary/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500" /> <div className="absolute inset-0 bg-gradient-to-br from-primary/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
<div className="relative z-10"> <div className="relative z-10">
@@ -150,7 +152,10 @@ export function InteractiveGermanyMap({
onMouseEnter={() => setActiveLocation(loc)} onMouseEnter={() => setActiveLocation(loc)}
onMouseLeave={() => setActiveLocation(null)} onMouseLeave={() => setActiveLocation(null)}
onClick={() => { onClick={() => {
if (loc.href) router.push(loc.href); if (loc.href) {
const href = loc.href.startsWith('/') ? `/${locale}${loc.href}` : loc.href;
router.push(href);
}
}} }}
> >
{/* Ping Animation for HQ / Branch */} {/* Ping Animation for HQ / Branch */}

View File

@@ -59,7 +59,7 @@ export const JobListingBlock = (props: JobListingBlockProps) => {
], ],
emptyStateMessage = t('emptyStateMessage'), emptyStateMessage = t('emptyStateMessage'),
emptyStateLinkText = t('emptyStateLinkText'), emptyStateLinkText = t('emptyStateLinkText'),
emptyStateLinkHref = '/kontakt' emptyStateLinkHref = t('emptyStateLinkHref')
} = props; } = props;
const title = props.title || t('title'); const title = props.title || t('title');

View File

@@ -3,6 +3,7 @@
import * as React from 'react'; import * as React from 'react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import Image from 'next/image'; import Image from 'next/image';
import { useTranslations } from 'next-intl';
export interface TeamMember { export interface TeamMember {
id: string; id: string;
@@ -22,16 +23,18 @@ interface TeamGridProps {
} }
export function TeamGrid({ members }: TeamGridProps) { export function TeamGrid({ members }: TeamGridProps) {
const t = useTranslations('TeamGrid');
if (!members || members.length === 0) return null; if (!members || members.length === 0) return null;
return ( return (
<section className="py-24 bg-white relative overflow-hidden"> <section className="py-24 bg-white relative overflow-hidden">
<div className="container relative z-10"> <div className="container relative z-10">
<div className="mb-12"> <div className="mb-12">
<h2 className="text-primary font-bold tracking-wider uppercase text-sm mb-3">Persönliche Beratung</h2> <h2 className="text-primary font-bold tracking-wider uppercase text-sm mb-3">{t('badge')}</h2>
<h3 className="font-heading text-4xl font-extrabold text-neutral-dark mb-4">Ihre Ansprechpartner</h3> <h3 className="font-heading text-4xl font-extrabold text-neutral-dark mb-4">{t('title')}</h3>
<p className="text-text-secondary max-w-2xl text-lg"> <p className="text-text-secondary max-w-2xl text-lg">
Sprechen Sie direkt mit unseren Experten für Ihr regionales Projekt. {t('subtitle')}
</p> </p>
</div> </div>
@@ -56,7 +59,7 @@ export function TeamGrid({ members }: TeamGridProps) {
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75"></span> <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-primary"></span> <span className="relative inline-flex rounded-full h-2 w-2 bg-primary"></span>
</span> </span>
Geschäftsführung {t('management')}
</div> </div>
<h3 className="font-heading font-black text-5xl md:text-6xl lg:text-7xl mb-4 leading-[1.1] tracking-tight"> <h3 className="font-heading font-black text-5xl md:text-6xl lg:text-7xl mb-4 leading-[1.1] tracking-tight">
{members[0].name} {members[0].name}
@@ -145,7 +148,7 @@ export function TeamGrid({ members }: TeamGridProps) {
{member.branch && ( {member.branch && (
<div className="mb-6"> <div className="mb-6">
<span className="inline-block px-3 py-1 bg-neutral-100 text-neutral-600 rounded-md text-xs uppercase tracking-widest font-semibold border border-neutral-200/60"> <span className="inline-block px-3 py-1 bg-neutral-100 text-neutral-600 rounded-md text-xs uppercase tracking-widest font-semibold border border-neutral-200/60">
{member.branch === 'e-tib' ? 'E-TIB GmbH' : member.branch === 'ing' ? 'Ingenieurgesellschaft' : member.branch === 'bohrtechnik' ? 'Bohrtechnik' : member.branch} {member.branch === 'e-tib' ? t('branchETIB') : member.branch === 'ing' ? t('branchIng') : member.branch === 'bohrtechnik' ? t('branchBohr') : member.branch}
</span> </span>
</div> </div>
)} )}

View File

@@ -3,6 +3,7 @@
import * as React from 'react'; import * as React from 'react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { sendContactFormAction } from '@/app/actions/contact';
export function ContactForm() { export function ContactForm() {
const [status, setStatus] = React.useState<'idle' | 'loading' | 'success' | 'error'>('idle'); const [status, setStatus] = React.useState<'idle' | 'loading' | 'success' | 'error'>('idle');
@@ -11,10 +12,18 @@ export function ContactForm() {
e.preventDefault(); e.preventDefault();
setStatus('loading'); setStatus('loading');
// Simulate server action using @mintel/mail or API route const formData = new FormData(e.currentTarget);
setTimeout(() => { try {
setStatus('success'); const result = await sendContactFormAction(formData);
}, 1500); if (result.success) {
setStatus('success');
} else {
setStatus('error');
}
} catch (error) {
console.error(error);
setStatus('error');
}
}; };
return ( return (
@@ -41,13 +50,31 @@ export function ContactForm() {
<p>Vielen Dank für Ihr Interesse an der E-TIB Gruppe. Ein Ansprechpartner wird sich zeitnah mit Ihnen in Verbindung setzen.</p> <p>Vielen Dank für Ihr Interesse an der E-TIB Gruppe. Ein Ansprechpartner wird sich zeitnah mit Ihnen in Verbindung setzen.</p>
</div> </div>
</motion.div> </motion.div>
) : ( ) : status === 'error' ? (
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
className="bg-red-500/10 border border-red-500 text-red-600 p-6 rounded-xl flex items-start gap-4 mb-6"
>
<div className="bg-red-500 text-white rounded-full p-1 mt-0.5">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</div>
<div>
<h4 className="font-bold text-lg mb-1">Fehler beim Senden</h4>
<p>Leider ist ein Fehler aufgetreten. Bitte versuchen Sie es später noch einmal oder kontaktieren Sie uns direkt per E-Mail oder Telefon.</p>
<Button variant="outline" size="sm" className="mt-4" onClick={() => setStatus('idle')}>Erneut versuchen</Button>
</div>
</motion.div>
) : null}
{status !== 'success' && status !== 'error' && (
<form onSubmit={handleSubmit} className="space-y-6"> <form onSubmit={handleSubmit} className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2"> <div className="space-y-2">
<label htmlFor="firstName" className="block text-sm font-semibold text-neutral-dark">Vorname</label> <label htmlFor="name" className="block text-sm font-semibold text-neutral-dark">Vorname</label>
<input <input
id="firstName" id="name"
name="name"
required required
disabled={status === 'loading'} disabled={status === 'loading'}
className="w-full bg-neutral-50 px-4 py-3 rounded-lg border border-neutral-200 focus:border-primary focus:ring-2 focus:ring-primary/20 outline-none transition-all" className="w-full bg-neutral-50 px-4 py-3 rounded-lg border border-neutral-200 focus:border-primary focus:ring-2 focus:ring-primary/20 outline-none transition-all"
@@ -57,7 +84,8 @@ export function ContactForm() {
<div className="space-y-2"> <div className="space-y-2">
<label htmlFor="lastName" className="block text-sm font-semibold text-neutral-dark">Nachname</label> <label htmlFor="lastName" className="block text-sm font-semibold text-neutral-dark">Nachname</label>
<input <input
id="lastName" id="lastName"
name="lastName"
required required
disabled={status === 'loading'} disabled={status === 'loading'}
className="w-full bg-neutral-50 px-4 py-3 rounded-lg border border-neutral-200 focus:border-primary focus:ring-2 focus:ring-primary/20 outline-none transition-all" className="w-full bg-neutral-50 px-4 py-3 rounded-lg border border-neutral-200 focus:border-primary focus:ring-2 focus:ring-primary/20 outline-none transition-all"
@@ -70,6 +98,7 @@ export function ContactForm() {
<label htmlFor="email" className="block text-sm font-semibold text-neutral-dark">E-Mail Adresse</label> <label htmlFor="email" className="block text-sm font-semibold text-neutral-dark">E-Mail Adresse</label>
<input <input
id="email" id="email"
name="email"
type="email" type="email"
required required
disabled={status === 'loading'} disabled={status === 'loading'}
@@ -82,6 +111,7 @@ export function ContactForm() {
<label htmlFor="company" className="block text-sm font-semibold text-neutral-dark">Unternehmen (Optional)</label> <label htmlFor="company" className="block text-sm font-semibold text-neutral-dark">Unternehmen (Optional)</label>
<input <input
id="company" id="company"
name="company"
disabled={status === 'loading'} disabled={status === 'loading'}
className="w-full bg-neutral-50 px-4 py-3 rounded-lg border border-neutral-200 focus:border-primary focus:ring-2 focus:ring-primary/20 outline-none transition-all" className="w-full bg-neutral-50 px-4 py-3 rounded-lg border border-neutral-200 focus:border-primary focus:ring-2 focus:ring-primary/20 outline-none transition-all"
placeholder="Firma GmbH" placeholder="Firma GmbH"
@@ -92,6 +122,7 @@ export function ContactForm() {
<label htmlFor="message" className="block text-sm font-semibold text-neutral-dark">Ihre Nachricht</label> <label htmlFor="message" className="block text-sm font-semibold text-neutral-dark">Ihre Nachricht</label>
<textarea <textarea
id="message" id="message"
name="message"
required required
disabled={status === 'loading'} disabled={status === 'loading'}
rows={5} rows={5}
@@ -100,6 +131,8 @@ export function ContactForm() {
/> />
</div> </div>
<input type="text" name="company_website" style={{ display: 'none' }} tabIndex={-1} autoComplete="off" />
<Button <Button
type="submit" type="submit"
disabled={status === 'loading'} disabled={status === 'loading'}

View File

@@ -20,23 +20,23 @@ export default function Hero({ data }: { data?: any }) {
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [isSearchOpen, setIsSearchOpen] = useState(false); const [isSearchOpen, setIsSearchOpen] = useState(false);
const [heroPlaceholder, setHeroPlaceholder] = useState( const [heroPlaceholder, setHeroPlaceholder] = useState(
'Projekt beschreiben oder Kabel suchen...', t('searchPlaceholder')
); );
const typingRef = useRef<ReturnType<typeof setTimeout> | null>(null); const typingRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const HERO_PLACEHOLDERS = [ const HERO_PLACEHOLDERS = [
'Querschnittsberechnung für 110kV Trasse', // Hochspannung t('placeholder1'),
'Wie schwer ist NAYY 4x150?', t('placeholder2'),
'Ich plane einen Solarpark, was brauche ich?', // Projekt Solar t('placeholder3'),
'Unterschied zwischen N2XSY und NAY2XSY?', // Fach t('placeholder4'),
'Mittelspannungskabel für Windkraftanlage', // Windpark t('placeholder5'),
'Welches Aluminiumkabel für 20kV?', // Mittelspannung t('placeholder6'),
]; ];
// Typing animation for the hero search placeholder // Typing animation for the hero search placeholder
useEffect(() => { useEffect(() => {
if (searchQuery) { if (searchQuery) {
setHeroPlaceholder('Projekt beschreiben oder Kabel suchen...'); setHeroPlaceholder(t('searchPlaceholder'));
return; return;
} }
@@ -152,7 +152,7 @@ export default function Hero({ data }: { data?: any }) {
size="lg" size="lg"
className="rounded-xl px-6 py-4 shrink-0 flex items-center shadow-md font-bold cursor-pointer hover:bg-accent hover:brightness-110" className="rounded-xl px-6 py-4 shrink-0 flex items-center shadow-md font-bold cursor-pointer hover:bg-accent hover:brightness-110"
> >
Fragen {t('ask')}
<ChevronRight className="w-5 h-5 ml-2 -mr-1" /> <ChevronRight className="w-5 h-5 ml-2 -mr-1" />
</Button> </Button>
</form> </form>
@@ -160,7 +160,7 @@ export default function Hero({ data }: { data?: any }) {
<div className="flex flex-col sm:flex-row justify-center md:justify-start gap-4 md:gap-6"> <div className="flex flex-col sm:flex-row justify-center md:justify-start gap-4 md:gap-6">
<div> <div>
<Button <Button
href="/contact" href={`/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`}
variant="white" variant="white"
size="lg" size="lg"
className="group w-full sm:w-auto h-14 md:h-16 px-8 md:px-10 text-base md:text-lg hover:scale-105 transition-all outline-none" className="group w-full sm:w-auto h-14 md:h-16 px-8 md:px-10 text-base md:text-lg hover:scale-105 transition-all outline-none"

View File

@@ -0,0 +1,61 @@
import * as React from 'react';
import { describe, it, expect, vi } from 'vitest';
import { render } from '@testing-library/react';
import { AISearchResults } from './AISearchResults';
// Mock lucide-react
vi.mock('lucide-react', () => ({
ArrowUp: () => <div data-testid="arrow-up" />,
X: () => <div data-testid="close-icon" />,
Sparkles: () => <div data-testid="sparkles" />,
ChevronRight: () => <div data-testid="chevron-right" />,
RotateCcw: () => <div data-testid="rotate-ccw" />,
Copy: () => <div data-testid="copy" />,
Check: () => <div data-testid="check" />,
}));
// Mock next/link
vi.mock('next/link', () => ({
default: ({ children, href }: any) => <a href={href}>{children}</a>,
}));
// Mock useAnalytics
vi.mock('../analytics/useAnalytics', () => ({
useAnalytics: () => ({
trackEvent: vi.fn(),
}),
}));
// Mock react-markdown
vi.mock('react-markdown', () => ({
default: ({ children }: any) => <div>{children}</div>,
}));
// Mock remark-gfm
vi.mock('remark-gfm', () => ({
default: {},
}));
// Mock next-intl
vi.mock('next-intl', () => ({
useTranslations: () => {
const translate = (key: string) => key;
translate.raw = (key: string) => {
if (key === 'loadingTexts') return ['Lade...', 'Denke nach...'];
if (key === 'prompts') return ['Prompt 1', 'Prompt 2'];
return [];
};
return translate;
},
useLocale: () => 'en',
}));
describe('AISearchResults Component Test (TDD)', () => {
it('renders correctly when open', () => {
const handleClose = vi.fn();
const { container } = render(
<AISearchResults isOpen={true} onClose={handleClose} />
);
expect(container).toBeTruthy();
});
});

View File

@@ -8,14 +8,9 @@ import { AnalyticsEvents } from '../analytics/analytics-events';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm'; import remarkGfm from 'remark-gfm';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { useTranslations } from 'next-intl';
const AIOrb = dynamic(() => import('./AIOrb'), { ssr: false }); const AIOrb = dynamic(() => import('./AIOrb'), { ssr: false });
const LOADING_TEXTS = [
'Durchsuche das Kabelhandbuch... 📖',
'Frage den Senior-Ingenieur... 👴🔧',
'Frage ChatGPTs Cousin 2. Grades... 🤖',
];
interface ProductMatch { interface ProductMatch {
id: string; id: string;
title: string; title: string;
@@ -51,7 +46,13 @@ export function AISearchResults({
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [copiedIndex, setCopiedIndex] = useState<number | null>(null); const [copiedIndex, setCopiedIndex] = useState<number | null>(null);
const [copiedAll, setCopiedAll] = useState(false); const [copiedAll, setCopiedAll] = useState(false);
const [loadingText, setLoadingText] = useState(LOADING_TEXTS[0]); const t = useTranslations('AISearch');
// We load texts dynamically from translations
const loadingTexts = t.raw('loadingTexts') as string[];
const prompts = t.raw('prompts') as string[];
const [loadingText, setLoadingText] = useState(loadingTexts[0]);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const modalRef = useRef<HTMLDivElement>(null); const modalRef = useRef<HTMLDivElement>(null);
@@ -141,11 +142,11 @@ export function AISearchResults({
setTimeout(() => { setTimeout(() => {
setIsLoading(true); setIsLoading(true);
// Start rotating loading texts // Start rotating loading texts
let textIdx = Math.floor(Math.random() * LOADING_TEXTS.length); let textIdx = Math.floor(Math.random() * loadingTexts.length);
setLoadingText(LOADING_TEXTS[textIdx]); setLoadingText(loadingTexts[textIdx]);
loadingIntervalRef.current = setInterval(() => { loadingIntervalRef.current = setInterval(() => {
textIdx = (textIdx + 1) % LOADING_TEXTS.length; textIdx = (textIdx + 1) % loadingTexts.length;
setLoadingText(LOADING_TEXTS[textIdx]); setLoadingText(loadingTexts[textIdx]);
}, 2500); }, 2500);
}, 400); }, 400);
@@ -191,8 +192,8 @@ export function AISearchResults({
console.error(err); console.error(err);
const msg = const msg =
err.name === 'AbortError' err.name === 'AbortError'
? 'Anfrage hat zu lange gedauert. Bitte versuche es erneut.' ? t('timeoutError')
: err.message || 'Ein Fehler ist aufgetreten.'; : err.message || t('genericError');
// Show error as a system message in the chat instead of a separate error banner // Show error as a system message in the chat instead of a separate error banner
setMessages((prev) => [ setMessages((prev) => [
@@ -248,7 +249,7 @@ export function AISearchResults({
const handleCopyChat = () => { const handleCopyChat = () => {
const fullChat = messages const fullChat = messages
.map((m) => `${m.role === 'user' ? 'Du' : 'Ohm'}:\n${m.content}`) .map((m) => `${m.role === 'user' ? t('you') : 'Ohm'}:\n${m.content}`)
.join('\n\n'); .join('\n\n');
handleCopy(fullChat); handleCopy(fullChat);
}; };
@@ -294,7 +295,7 @@ export function AISearchResults({
<div> <div>
<h2 className="text-white font-bold text-sm tracking-wide">Ohm</h2> <h2 className="text-white font-bold text-sm tracking-wide">Ohm</h2>
<p className="text-[10px] text-white/30 font-medium tracking-wider uppercase"> <p className="text-[10px] text-white/30 font-medium tracking-wider uppercase">
{isLoading ? 'Denkt nach...' : error ? 'Fehler aufgetreten' : 'Online'} {isLoading ? t('thinking') : error ? t('errorStatus') : t('online')}
</p> </p>
</div> </div>
</div> </div>
@@ -303,14 +304,14 @@ export function AISearchResults({
<button <button
onClick={handleCopyChat} onClick={handleCopyChat}
className="flex items-center gap-1.5 text-[10px] font-bold text-white/40 hover:text-white/80 transition-all duration-200 hover:bg-white/5 rounded-full px-3 py-1.5 cursor-pointer uppercase tracking-wider" className="flex items-center gap-1.5 text-[10px] font-bold text-white/40 hover:text-white/80 transition-all duration-200 hover:bg-white/5 rounded-full px-3 py-1.5 cursor-pointer uppercase tracking-wider"
title="gesamten Chat kopieren" title={t('copyChatTitle')}
> >
{copiedAll ? ( {copiedAll ? (
<Check className="w-3.5 h-3.5 text-accent" /> <Check className="w-3.5 h-3.5 text-accent" />
) : ( ) : (
<Copy className="w-3.5 h-3.5" /> <Copy className="w-3.5 h-3.5" />
)} )}
<span>{copiedAll ? 'Kopiert' : 'Chat kopieren'}</span> <span>{copiedAll ? t('copied') : t('copyChat')}</span>
</button> </button>
)} )}
<button <button
@@ -336,16 +337,15 @@ export function AISearchResults({
</div> </div>
<div> <div>
<p className="text-xl md:text-2xl font-bold text-white/80"> <p className="text-xl md:text-2xl font-bold text-white/80">
Wie kann ich helfen? {t('howCanIHelp')}
</p> </p>
<p className="text-sm text-white/30 mt-2 max-w-md"> <p className="text-sm text-white/30 mt-2 max-w-md">
Beschreibe dein Projekt, frag nach bestimmten Kabeln, oder nenne mir deine {t('helpDescription')}
Anforderungen.
</p> </p>
</div> </div>
{/* Quick prompts */} {/* Quick prompts */}
<div className="flex flex-wrap justify-center gap-2 mt-4"> <div className="flex flex-wrap justify-center gap-2 mt-4">
{['Windpark 33kV Verkabelung', 'NYCWY 4x185', 'Erdkabel für Solarpark'].map( {prompts.map(
(prompt) => ( (prompt) => (
<button <button
key={prompt} key={prompt}
@@ -384,7 +384,7 @@ export function AISearchResults({
? 'top-2 right-2 bg-primary/10 hover:bg-primary/20 text-primary/60 hover:text-primary' ? 'top-2 right-2 bg-primary/10 hover:bg-primary/20 text-primary/60 hover:text-primary'
: 'top-2 right-2 bg-white/5 hover:bg-white/10 text-white/40 hover:text-white' : 'top-2 right-2 bg-white/5 hover:bg-white/10 text-white/40 hover:text-white'
}`} }`}
title="Nachricht kopieren" title={t('copyMessage')}
> >
{copiedIndex === index ? ( {copiedIndex === index ? (
<Check className="w-3.5 h-3.5" /> <Check className="w-3.5 h-3.5" />
@@ -431,7 +431,7 @@ export function AISearchResults({
{msg.role === 'assistant' && msg.products && msg.products.length > 0 && ( {msg.role === 'assistant' && msg.products && msg.products.length > 0 && (
<div className="mt-4 space-y-2 border-t border-white/[0.06] pt-4"> <div className="mt-4 space-y-2 border-t border-white/[0.06] pt-4">
<h4 className="text-[10px] font-bold tracking-widest uppercase text-white/30 mb-2"> <h4 className="text-[10px] font-bold tracking-widest uppercase text-white/30 mb-2">
Empfohlene Produkte {t('recommendedProducts')}
</h4> </h4>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
{msg.products.map((product, idx) => ( {msg.products.map((product, idx) => (
@@ -509,7 +509,7 @@ export function AISearchResults({
<AIOrb isThinking={false} hasError={true} /> <AIOrb isThinking={false} hasError={true} />
</div> </div>
<div> <div>
<h3 className="text-sm font-bold text-red-300">Da ist was schiefgelaufen 😬</h3> <h3 className="text-sm font-bold text-red-300">{t('errorTitle')}</h3>
<p className="text-xs text-red-300/60 mt-1">{error}</p> <p className="text-xs text-red-300/60 mt-1">{error}</p>
<button <button
onClick={() => { onClick={() => {
@@ -519,7 +519,7 @@ export function AISearchResults({
className="flex items-center gap-1.5 text-[10px] font-bold text-red-300/50 hover:text-red-300 mt-2 transition-colors cursor-pointer" className="flex items-center gap-1.5 text-[10px] font-bold text-red-300/50 hover:text-red-300 mt-2 transition-colors cursor-pointer"
> >
<RotateCcw className="w-3 h-3" /> <RotateCcw className="w-3 h-3" />
Nochmal versuchen {t('tryAgain')}
</button> </button>
</div> </div>
</div> </div>
@@ -544,7 +544,7 @@ export function AISearchResults({
value={query} value={query}
onChange={(e) => setQuery(e.target.value)} onChange={(e) => setQuery(e.target.value)}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
placeholder="Nachricht eingeben..." placeholder={t('placeholder')}
className="flex-1 bg-transparent border-none text-white text-sm md:text-base px-5 py-4 focus:outline-none placeholder:text-white/20" className="flex-1 bg-transparent border-none text-white text-sm md:text-base px-5 py-4 focus:outline-none placeholder:text-white/20"
disabled={isLoading} disabled={isLoading}
tabIndex={1} tabIndex={1}
@@ -567,20 +567,20 @@ export function AISearchResults({
? 'bg-accent text-primary shadow-lg shadow-accent/20 hover:shadow-accent/40 hover:scale-105 active:scale-95' ? 'bg-accent text-primary shadow-lg shadow-accent/20 hover:shadow-accent/40 hover:scale-105 active:scale-95'
: 'bg-white/5 text-white/20' : 'bg-white/5 text-white/20'
}`} }`}
aria-label="Nachricht senden" aria-label={t('send')}
> >
<ArrowUp className="w-4 h-4" strokeWidth={2.5} /> <ArrowUp className="w-4 h-4" strokeWidth={2.5} />
</button> </button>
</div> </div>
<div className="flex items-center justify-center gap-3 mt-2.5"> <div className="flex items-center justify-center gap-3 mt-2.5">
<span className="text-[9px] uppercase tracking-[0.15em] font-medium text-white/15"> <span className="text-[9px] uppercase tracking-[0.15em] font-medium text-white/15">
Enter zum Senden · Esc zum Schließen {t('footerShortcuts')}
</span> </span>
<span className="text-[9px] uppercase tracking-[0.15em] font-medium text-white/15"> <span className="text-[9px] uppercase tracking-[0.15em] font-medium text-white/15">
· ·
</span> </span>
<span className="text-[9px] uppercase tracking-[0.15em] font-medium text-accent/40 flex items-center gap-1"> <span className="text-[9px] uppercase tracking-[0.15em] font-medium text-accent/40 flex items-center gap-1">
🛡 DSGVO-konform · EU-Server {t('footerPrivacy')}
</span> </span>
</div> </div>
</div> </div>

View File

@@ -19,9 +19,9 @@ layout: "fullBleed"
showFairs={true} showFairs={true}
showJobs={false} showJobs={false}
fairs={[ fairs={[
{ name: 'Intersolar München', date: '2024 / 2025', type: 'Messe', location: 'München', url: 'https://www.intersolar.de/' }, { name: 'Intersolar München', date: '2026', type: 'Messe', location: 'München', url: 'https://www.intersolar.de/' },
{ name: 'Windenergietage Linstow', date: '2024 / 2025', type: 'Fachkongress', location: 'Linstow', url: 'https://www.windenergietage.de/' }, { name: 'Windenergietage Linstow', date: '2026', type: 'Fachkongress', location: 'Linstow', url: 'https://www.windenergietage.de/' },
{ name: 'Kabel-Workshop Wiesbaden', date: '2024 / 2025', type: 'Fachmesse', location: 'Wiesbaden' } { name: 'Kabel-Workshop Wiesbaden', date: '2026', type: 'Fachmesse', location: 'Wiesbaden' }
]} ]}
/> />
</div> </div>

View File

@@ -17,18 +17,18 @@ layout: "fullBleed"
<div className="container px-4 max-w-7xl mx-auto py-16"> <div className="container px-4 max-w-7xl mx-auto py-16">
<blockquote> <blockquote>
If classic trench construction is out of the question for space, environmental or cost reasons, we rely on the most modern trenchless techniques. We protect nature and minimize restoration costs. When traditional open-cut excavation is restricted by space, ecological standards, or cost constraints, trenchless engineering becomes the superior choice. We safeguard the environment while driving down surface restoration costs.
</blockquote> </blockquote>
## Maximum Efficiency, Minimal Intervention ## Maximum Efficiency, Minimal Impact
Trenchless line laying is the smartest solution for complex construction projects. This process shows its absolute strengths particularly when crossing highly frequented roads, railway lines, highways or sensitive bodies of water. Traffic continues to flow, nature remains untouched and complex surfaces such as expensive paving or asphalt do not have to be destroyed. Trenchless utility laying represents the pinnacle of smart infrastructure deployment. This methodology demonstrates its absolute superiority when navigating busy transportation corridors, active rail tracks, highways, or sensitive ecological waterways. By keeping traffic flowing and preserving the natural topography, we completely eliminate the need to break open expensive paved surfaces or asphalt.
## HDD Boreholes and Earth Rockets ## HDD Directional Drilling & Pneumatic Earth Rockets
With our specialized machinery, we master a wide variety of processes. With the **Horizontal Directional Drilling (HDD) process**, we steer the drill head precisely underground. We achieve drilling lengths of up to 250 meters in one piece and can pull in protective pipes with a diameter of up to 400 mm. Armed with a state-of-the-art, specialized fleet of drilling rigs, we master complex subterranean conditions. Utilizing the **Horizontal Directional Drilling (HDD)** method, we guide the drill head with centimeter-level accuracy beneath obstacles. This enables us to achieve continuous drilling lengths of up to 250 meters in a single run, pulling in high-durability protective conduits with diameters up to 400 mm.
For shorter distances, such as fast and gentle house connections, we use our **earth rockets** (soil displacement method). Here we achieve drilling lengths of up to 15 meters for pipes up to 160 mm in diameter - ideal for leaving front gardens and driveways completely intact. For localized, rapid deployments—such as residential service connectionswe deploy high-speed **pneumatic earth rockets** (soil displacement). This method allows us to complete under-crossings of up to 15 meters for conduits up to 160 mm in diameter, ensuring pristine driveways, manicured gardens, and pedestrian walks remain completely undisturbed.
<DeepDrillAnimation /> <DeepDrillAnimation />
</div> </div>

View File

@@ -17,18 +17,18 @@ layout: "fullBleed"
<div className="container px-4 max-w-7xl mx-auto py-16"> <div className="container px-4 max-w-7xl mx-auto py-16">
<blockquote> <blockquote>
A life without electricity or high-speed internet is unimaginable today - that is why we lay cables. We build the backbone of the digital society. Modern life depends entirely on seamless energy and ultra-fast connectivity. By building robust, high-performance physical networks, we lay the groundwork for a truly connected future.
</blockquote> </blockquote>
## The Backbone of Digitalization ## The Digital Backbone: Precision-Driven Fiber Expansion
Fast internet and state-of-the-art telecommunications networks require an absolutely smooth and highly professional network expansion. With highly specialized assembly teams and our own modern fleet of machinery, E-TIB ensures the highest execution quality in nationwide fiber optic expansion. High-speed fiber optics and next-generation telecommunications networks demand a zero-tolerance approach to execution quality. At E-TIB, we mobilize highly trained technical teams and an advanced, company-owned fleet of machinery to deliver flawless broadband expansion across the country.
## FTTX Expansion from a Single Source ## FTTX Deployment: A Seamless, End-to-End Solution
As a powerful partner for telecommunications providers, we cover every step of physical network creation. We take over the construction of complete empty conduit routes as well as cable and pipe trenches. In addition, we position and build multi-function enclosures at strategic network nodes. As a trusted, tier-one partner for leading telecommunications and grid providers, we cover the entire physical lifecycle of network creation. From excavating precise cable and utility trenches to laying complete, high-capacity conduit paths, we manage every phase of civil engineering. We also handle the installation and positioning of strategic multi-function enclosures (MFG) at critical network hub points.
Our service does not end with pure civil engineering: We take care of the professional pulling and blowing in of highly sensitive fiber optic and telecommunication cables over long distances and carry out all required cable assemblies. In this way, we guarantee that the digital infrastructure can go into operation safely and performantly. Our expertise goes far beyond standard civil works. We perform the specialized pulling and pneumatic blowing of highly sensitive fiber optic and telecommunication cables over expansive, complex distances. Combining this with state-of-the-art splicing, testing, and cable assembly, we ensure your digital infrastructure operates with maximum throughput, security, and long-term reliability.
</div> </div>
<ServiceDetailGrid <ServiceDetailGrid

View File

@@ -17,20 +17,20 @@ layout: "fullBleed"
<div className="container px-4 max-w-7xl mx-auto py-16"> <div className="container px-4 max-w-7xl mx-auto py-16">
<blockquote> <blockquote>
Everywhere digging and drilling is happening, but unfortunately a lot also goes wrong. We focus on quality, the highest safety standards and legally compliant documentation - so that your construction project does not turn into a cost trap. While underground grid construction is expanding rapidly, execution quality varies widely. We counter this with elite craftsmanship, strict safety protocols, and ironclad documentation—ensuring your infrastructure project remains an asset, not a liability.
</blockquote> </blockquote>
## Quality instead of a Cost Trap ## Precision over Compromise: Preventing Infrastructure Failures
On many construction sites, cheap providers from abroad are increasingly being used, which often leads to **safety standards and documentation requirements being ignored**. The consequences are severe: accidents, damaged external lines (such as electricity, water and gas) as well as massive construction delays and expensive supplements. A supposed bargain quickly turns out to be a financial burden. Modern civil engineering is plagued by low-cost contractors who routinely **compromise on safety margins and technical documentation**. The repercussions are severe: catastrophic utility strikes (power, water, gas, telecom), costly project delays, and massive budget overruns. What begins as a low-bid shortcut quickly turns into a financial and legal nightmare.
Particularly for funded infrastructure projects, retrieving the funding is extremely problematic without absolutely seamless, standard-compliant documentation. Furthermore, public or subsidized infrastructure projects require meticulously structured, standard-compliant documentation. Without flawless records, the release of critical funding and subsidies is regularly delayed or denied.
## Everything from a Single Source ## Integrated Excellence from a Single Source
Thanks to our many years of experience, we identify problems early on and work out safe and satisfactory solutions for our clients in advance. E-TIB GmbH has fully specialized in cable civil engineering. At E-TIB, we leverage decades of collective expertise to anticipate structural and geological bottlenecks long before the first shovel hits the ground. Our specialization in cable civil engineering guarantees that every deployment runs with absolute precision.
We offer you the complete range of services for the construction of cable routes from a single source - with our own ultra-modern machine fleet and permanent specialist staff from the region. In direct cooperation with our partner office, E-TIB Ingenieurgesellschaft mbH, we can also rule out planning errors at an early stage and sovereignly meet even the most complex documentation requirements. We deliver turnkey cable routes entirely in-house. Our operations are powered by a cutting-edge fleet of heavy machinery and staffed exclusively by highly trained, permanent regional professionals. Working in close synergy with our sister firm, **E-TIB Ingenieurgesellschaft mbH**, we eliminate planning oversights at the inception phase and effortlessly handle the most complex GIS and administrative documentation requirements in the industry.
<DataGridPulse /> <DataGridPulse />
</div> </div>

View File

@@ -27,7 +27,7 @@ layout: "fullBleed"
description: "Classic trench construction and professional laying of medium and low voltage cables.", description: "Classic trench construction and professional laying of medium and low voltage cables.",
tag: "Energy", tag: "Energy",
size: "large", size: "large",
href: "/en/kabeltiefbau", href: "/en/cable-civil-engineering",
image: { url: "/assets/photos/DSC01123.JPG", alt: "Cable Construction" } image: { url: "/assets/photos/DSC01123.JPG", alt: "Cable Construction" }
}, },
{ {
@@ -35,7 +35,7 @@ layout: "fullBleed"
description: "Horizontal directional drilling (HDD) for trenchless, surface-friendly installation.", description: "Horizontal directional drilling (HDD) for trenchless, surface-friendly installation.",
tag: "Innovation", tag: "Innovation",
size: "medium", size: "medium",
href: "/en/bohrtechnik", href: "/en/drilling-technology",
image: { url: "/assets/photos/DSC08653.JPG", alt: "Drilling Technology" } image: { url: "/assets/photos/DSC08653.JPG", alt: "Drilling Technology" }
}, },
{ {
@@ -43,7 +43,7 @@ layout: "fullBleed"
description: "Expansion of future-proof broadband networks (FTTX).", description: "Expansion of future-proof broadband networks (FTTX).",
tag: "Communication", tag: "Communication",
size: "small", size: "small",
href: "/en/glasfaser", href: "/en/fiber-optics",
image: { url: "/assets/photos/DSC01129.JPG", alt: "Fiber Optics" } image: { url: "/assets/photos/DSC01129.JPG", alt: "Fiber Optics" }
}, },
{ {
@@ -51,7 +51,7 @@ layout: "fullBleed"
description: "Route planning, approval procedures, and precise GIS documentation.", description: "Route planning, approval procedures, and precise GIS documentation.",
tag: "Digital", tag: "Digital",
size: "accent", size: "accent",
href: "/en/planung" href: "/en/planning"
} }
]} ]}
/> />

View File

@@ -19,9 +19,9 @@ layout: "fullBleed"
showFairs={true} showFairs={true}
showJobs={false} showJobs={false}
fairs={[ fairs={[
{ name: 'Intersolar Munich', date: '2024 / 2025', type: 'Fair', location: 'Munich', url: 'https://www.intersolar.de/' }, { name: 'Intersolar Munich', date: '2026', type: 'Fair', location: 'Munich', url: 'https://www.intersolar.de/' },
{ name: 'Wind Energy Days Linstow', date: '2024 / 2025', type: 'Specialist Congress', location: 'Linstow', url: 'https://www.windenergietage.de/' }, { name: 'Wind Energy Days Linstow', date: '2026', type: 'Specialist Congress', location: 'Linstow', url: 'https://www.windenergietage.de/' },
{ name: 'Cable Workshop Wiesbaden', date: '2024 / 2025', type: 'Trade Fair', location: 'Wiesbaden' } { name: 'Cable Workshop Wiesbaden', date: '2026', type: 'Trade Fair', location: 'Wiesbaden' }
]} ]}
/> />
</div> </div>

View File

@@ -17,18 +17,18 @@ layout: "fullBleed"
<div className="container px-4 max-w-7xl mx-auto py-16"> <div className="container px-4 max-w-7xl mx-auto py-16">
<blockquote> <blockquote>
We offer our clients everything from a single source - from the rough planning of your project idea and cable civil engineering to the construction of your renewable energy project. We offer our clients unified, end-to-end project orchestration—from raw feasibility mapping to technical execution and grid connection for utility-scale renewable energy assets.
</blockquote> </blockquote>
## Foundation for Project Success ## The Foundation of Project Viability
Solid planning is the crucial foundation for every successful infrastructure project. Projects often fail or are massively delayed because the complexity of civil engineering planning and approval procedures was underestimated. Through our combined competence, you avoid expensive planning errors and frictional losses at the interfaces. A rigorous engineering layout is the cornerstone of every successful infrastructure initiative. Many large-scale utility projects encounter critical delays or structural failures simply because the nuances of subterranean logistics and regulatory approvals were underestimated. By bridging the gap between design and physical field operations, we proactively mitigate risk, eliminating friction at critical operational interfaces.
Both for the highly topical broadband expansion and for the demanding grid connection of solar or wind projects, absolutely precise and high-quality cable route planning is required. To do this, we inspect the route on site, analyze all potential construction conflicts at an early stage and ensure that all necessary applications for the laying are submitted completely and on time to the authorities. Whether executing fast-paced broadband fiber rollouts or designing complex grid connections for gigawatt-scale wind and solar farms, precision is mandatory. Our engineering teams perform comprehensive on-site route audits, map potential utility conflicts early, and guarantee all localized regulatory, environmental, and municipal permits are prepared and filed seamlessly.
## Strong Engineering Competence ## Elite Engineering & Technical Advisory
In close cooperation with our specialized partner engineering firm, **E-TIB Ingenieurgesellschaft mbH**, we accompany you through all service phases. Whether structural planning for route optimization, complex approval planning or detailed execution planning - we steer your project from the first feasibility study to successful construction supervision and acceptance. In strategic partnership with **E-TIB Ingenieurgesellschaft mbH**, we guide your development through every single phase of the project lifecycle. From initial corridor scouting and route optimization to obtaining formal municipal approvals and producing final, field-ready execution plans—we provide the strategic oversight and specialized engineering supervision needed to ensure a flawless handover.
</div> </div>
<ServiceDetailGrid <ServiceDetailGrid

View File

@@ -18,7 +18,7 @@ layout: "fullBleed"
{ {
id: "danny-joseph", id: "danny-joseph",
name: "Danny Joseph", name: "Danny Joseph",
position: "Management", position: "Managing Director",
email: "d.joseph@e-tib.com", email: "d.joseph@e-tib.com",
phone: "+49 1520 7230518", phone: "+49 1520 7230518",
image: "/assets/photos/team/danny.jpg" image: "/assets/photos/team/danny.jpg"

View File

@@ -17,20 +17,20 @@ layout: "fullBleed"
<div className="container px-4 max-w-7xl mx-auto py-16"> <div className="container px-4 max-w-7xl mx-auto py-16">
<blockquote> <blockquote>
We work with highly precise GPS equipment for the seamless documentation of our clients' projects. For this purpose, we independently develop app integrations to automate surveying. Leveraging ultra-precise, state-of-the-art GNSS/GPS technology and proprietary app integrations, we automate the surveying process to deliver seamless, real-time documentation.
</blockquote> </blockquote>
## Highest Precision Protects Against Surprises ## Centimeter Precision Prevents Costly Operations
In today's times, legal documentation requirements and strict safety standards are growing rapidly. Many conventional civil engineering companies are hardly able to cleanly meet these highly complex requirements - especially for funded broadband expansion. Missing or incorrect documentation inevitably leads to massive problems during construction acceptance and project financing. Regulatory documentation mandates and safety benchmarks are expanding rapidly. Traditional civil engineering contractors frequently struggle to meet these highly complex data requirementsespecially under strict public funding frameworks. Neglecting or incorrectly mapping underground routes inevitably triggers major roadblocks during project handovers, audit reviews, and long-term asset management.
We identified this bottleneck early on and proactively specialized in the highly precise surveying and digital documentation of cable routes. Recognizing this critical bottleneck early, we pro-actively integrated advanced geodetic surveying and digital mapping directly into our core operations.
## Digital Recording at the State of the Art ## Cutting-Edge Digital Twin & GIS Records
With state-of-the-art GPS technology and self-developed digital app integrations, we ensure highly automated, seamless and absolutely legally compliant recording of all laid lines and systems. Using advanced GNSS/GPS devices and in-house developed digital workflows, we capture the precise physical reality of every conduit and utility in real-time. This automated process generates a highly accurate digital twin of your underground infrastructure.
In addition to classic GPS staking and surveying, we create complete geodatabases for our clients for transparent project accounting and offer seamless 360° photo and video recording of the construction field. In this way, you keep full track of your underground infrastructure at all times. Beyond classic surveying, staking, and as-built mapping, we construct fully integrated geodatabases for transparent project accounting. We also provide complete, immersive 360° photo and video documentation of the construction area before and after ground-breaking. This ensures you maintain full transparency, legal compliance, and total control over your physical assets.
</div> </div>
<ServiceDetailGrid <ServiceDetailGrid

View File

@@ -21,42 +21,42 @@ layout: "fullBleed"
{ {
title: 'ISO 14001:2015', title: 'ISO 14001:2015',
description: 'Environmental Management System', description: 'Environmental Management System',
pdfUrl: '/assets/certificates/231214_Zertifikat ISO 14001 Umweltmanagement.pdf', pdfUrl: '/assets/certificates/iso14001.pdf',
type: 'iso', type: 'iso',
date: '14.12.2023', date: '14.12.2023',
}, },
{ {
title: 'ISO 9001:2015', title: 'ISO 9001:2015',
description: 'Quality Management System', description: 'Quality Management System',
pdfUrl: '/assets/certificates/231214_Zertifikat ISO 9001 Qualitätsmanagement.pdf', pdfUrl: '/assets/certificates/iso9001.pdf',
type: 'iso', type: 'iso',
date: '14.12.2023', date: '14.12.2023',
}, },
{ {
title: 'DIN EN ISO 45001:2018', title: 'DIN EN ISO 45001:2018',
description: 'Occupational Health and Safety', description: 'Occupational Health and Safety',
pdfUrl: '/assets/certificates/Zertifizierung DIN EN ISO 45001 bis 05122028.pdf', pdfUrl: '/assets/certificates/iso45001.pdf',
type: 'iso', type: 'iso',
date: '05.12.2025', date: '05.12.2025',
}, },
{ {
title: 'Exemption Certificate', title: 'Exemption Certificate',
description: 'According to § 48 b EStG', description: 'According to § 48 b EStG',
pdfUrl: '/assets/certificates/240209_Freistellungsbescheinigung § 48 b.pdf', pdfUrl: '/assets/certificates/freistellung.pdf',
type: 'tax', type: 'tax',
date: '09.02.2024', date: '09.02.2024',
}, },
{ {
title: 'Proof § 13b UStG', title: 'Proof § 13b UStG',
description: 'Tax liability of the service recipient', description: 'Tax liability of the service recipient',
pdfUrl: '/assets/certificates/240209_Nachweis § 13 b.pdf', pdfUrl: '/assets/certificates/nachweis13b.pdf',
type: 'tax', type: 'tax',
date: '09.02.2024', date: '09.02.2024',
}, },
{ {
title: 'Certificate in Tax Matters', title: 'Certificate in Tax Matters',
description: 'Tax Office Certificate', description: 'Tax Office Certificate',
pdfUrl: '/assets/certificates/250213_Bescheinigung in Steuersachen.pdf', pdfUrl: '/assets/certificates/bescheinigung.pdf',
type: 'tax', type: 'tax',
date: '13.02.2025', date: '13.02.2025',
} }

BIN
data/Mengen Homepage.docx Normal file

Binary file not shown.

BIN
data/Orte 2016 - 2022.docx Normal file

Binary file not shown.

BIN
data/Orte 2023.docx Normal file

Binary file not shown.

BIN
data/Orte 2024.docx Normal file

Binary file not shown.

14
data/Orte 2024.txt Normal file
View File

@@ -0,0 +1,14 @@
Windpark, 48366 Laer
PV-Anlage, 89434 Blindheim OT Wolpertstetten
Windpark, 24893 Taarstedt
Windpark, 24891 Schnarup-Thumby
Windpark, 23818 Neuengörs
PV-Anlage, 15326 Petershagen
Windpark, 16866 Gumtow OT Schrepkow
PV-Anlage, 14641 Nauen
PV-Anlage, 23619 Badendorf
PV-Anlage, 14959 Wiesenhagen
PV-Anlage, 17089 Bartow
PV-Anlage, 15345 Prötzel OT Sternebeck
Windpark, 15848 Beeskow
PV-Anlage, 15328 Gorgast

BIN
data/Orte 2025.docx Normal file

Binary file not shown.

9
data/Orte 2025.txt Normal file
View File

@@ -0,0 +1,9 @@
PV-Anlage, 18334 Dettmannsdorf
PV-Anlage, 59320 Ennigerloh-Oelde
PV-Anlage, 93176 Beratzhausen
Windpark, 16845 Neustadt Dosse
PV-Anlage, 16845 Stüdenitz-Schönermark
Windpark, 48346 Ostbevern
Windpark, 27804 Berne
Windpark, 17166 Dalkendorf
Windpark, 29575 Altenmedingen

BIN
data/Orte 2026.docx Normal file

Binary file not shown.

View File

@@ -9,7 +9,12 @@
"ueber-uns": "ueber-uns", "ueber-uns": "ueber-uns",
"agb": "agb", "agb": "agb",
"start": "start", "start": "start",
"messen": "messen" "messen": "messen",
"kabeltiefbau": "kabeltiefbau",
"glasfaser": "glasfaser",
"bohrtechnik": "bohrtechnik",
"planung": "planung",
"vermessung": "vermessung"
}, },
"products": {}, "products": {},
"categories": {} "categories": {}
@@ -97,7 +102,7 @@
"office": "Hauptsitz Guben", "office": "Hauptsitz Guben",
"address": "Gewerbestraße 22\n03172 Guben\nDeutschland", "address": "Gewerbestraße 22\n03172 Guben\nDeutschland",
"phone": "+49 (0) 3561 / 68577 33", "phone": "+49 (0) 3561 / 68577 33",
"email": "d.joseph@e-tib.com" "email": "info@e-tib.com"
}, },
"hours": { "hours": {
"title": "Öffnungszeiten", "title": "Öffnungszeiten",
@@ -146,7 +151,15 @@
"hero": { "hero": {
"title": "DIE EXPERTEN FÜR <green>KABELTIEFBAU</green>", "title": "DIE EXPERTEN FÜR <green>KABELTIEFBAU</green>",
"subtitle": "Wir helfen beim Ausbau der Energiekabelnetze für eine grüne Zukunft.", "subtitle": "Wir helfen beim Ausbau der Energiekabelnetze für eine grüne Zukunft.",
"cta": "Jetzt anfragen" "cta": "Jetzt anfragen",
"searchPlaceholder": "Projekt beschreiben oder Kabel suchen...",
"ask": "Fragen",
"placeholder1": "Querschnittsberechnung für 110kV Trasse",
"placeholder2": "Wie schwer ist NAYY 4x150?",
"placeholder3": "Ich plane einen Solarpark, was brauche ich?",
"placeholder4": "Unterschied zwischen N2XSY und NAY2XSY?",
"placeholder5": "Mittelspannungskabel für Windkraftanlage",
"placeholder6": "Welches Aluminiumkabel für 20kV?"
}, },
"video": { "video": {
"title": "Vom ersten Spatenstich bis zum Netzanschluss wir bauen die Infrastruktur von morgen." "title": "Vom ersten Spatenstich bis zum Netzanschluss wir bauen die Infrastruktur von morgen."
@@ -166,12 +179,14 @@
"CallToAction": { "CallToAction": {
"title": "Bereit für Ihr Projekt?", "title": "Bereit für Ihr Projekt?",
"description": "Wir suchen stets nach neuen Herausforderungen und starken Partnern. Kontaktieren Sie uns für eine unverbindliche Beratung zu Ihrem Vorhaben.", "description": "Wir suchen stets nach neuen Herausforderungen und starken Partnern. Kontaktieren Sie uns für eine unverbindliche Beratung zu Ihrem Vorhaben.",
"ctaLabel": "Jetzt Kontakt aufnehmen" "ctaLabel": "Jetzt Kontakt aufnehmen",
"ctaHref": "/de/kontakt"
}, },
"JobListingBlock": { "JobListingBlock": {
"fairsTitle": "Nächste Messetermine", "fairsTitle": "Nächste Messetermine",
"emptyStateMessage": "Aktuell sind alle Positionen besetzt. Senden Sie uns gerne eine Initiativbewerbung!", "emptyStateMessage": "Aktuell sind alle Positionen besetzt. Senden Sie uns gerne eine Initiativbewerbung!",
"emptyStateLinkText": "Jetzt Kontakt aufnehmen", "emptyStateLinkText": "Jetzt Kontakt aufnehmen",
"emptyStateLinkHref": "/de/kontakt",
"date": "Datum", "date": "Datum",
"location": "Location", "location": "Location",
"booth": "Stand", "booth": "Stand",
@@ -222,6 +237,10 @@
"title": "Gründung E-TIB GmbH", "title": "Gründung E-TIB GmbH",
"desc": "Ausführung elektrischer Infrastrukturprojekte, Kabeltiefbau und Horizontalspülbohrungen." "desc": "Ausführung elektrischer Infrastrukturprojekte, Kabeltiefbau und Horizontalspülbohrungen."
}, },
"2025": {
"title": "Gründung E-TIB Bohrtechnik GmbH",
"desc": "Spezialisierung auf präzise Horizontalspülbohrungen in allen Bodenklassen."
},
"2019_ing": { "2019_ing": {
"title": "Gründung E-TIB Ingenieurgesellschaft", "title": "Gründung E-TIB Ingenieurgesellschaft",
"desc": "Genehmigungs- und Ausführungsplanung, komplexe Querungen sowie Netzanschlussplanung." "desc": "Genehmigungs- und Ausführungsplanung, komplexe Querungen sowie Netzanschlussplanung."
@@ -229,10 +248,6 @@
"2019_verw": { "2019_verw": {
"title": "Gründung E-TIB Verwaltung GmbH", "title": "Gründung E-TIB Verwaltung GmbH",
"desc": "Zentrale Dienste, Erwerb, Vermietung, Verpachtung und Verwaltung von Immobilien und Maschinen." "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."
} }
} }
}, },
@@ -261,5 +276,55 @@
"method": "Tiefbau & Spülbohrung (HDD)" "method": "Tiefbau & Spülbohrung (HDD)"
}, },
"learnMore": "Zum Projekt" "learnMore": "Zum Projekt"
},
"TeamGrid": {
"badge": "Persönliche Beratung",
"title": "Ihre Ansprechpartner",
"subtitle": "Sprechen Sie direkt mit unseren Experten für Ihr regionales Projekt.",
"management": "Geschäftsführung",
"branchETIB": "E-TIB GmbH",
"branchIng": "Ingenieurgesellschaft",
"branchBohr": "Bohrtechnik"
},
"AISearch": {
"loadingTexts": [
"Durchsuche das Kabelhandbuch... 📖",
"Frage den Senior-Ingenieur... 👴🔧",
"Frage ChatGPTs Cousin 2. Grades... 🤖"
],
"thinking": "Denkt nach...",
"errorStatus": "Fehler aufgetreten",
"online": "Online",
"copyChat": "Chat kopieren",
"copyChatTitle": "gesamten Chat kopieren",
"copied": "Kopiert",
"close": "Schließen",
"howCanIHelp": "Wie kann ich helfen?",
"helpDescription": "Beschreibe dein Projekt, frag nach bestimmten Kabeln, oder nenne mir deine Anforderungen.",
"prompts": [
"Windpark 33kV Verkabelung",
"NYCWY 4x185",
"Erdkabel für Solarpark"
],
"you": "Du",
"copyMessage": "Nachricht kopieren",
"recommendedProducts": "Empfohlene Produkte",
"errorTitle": "Da ist was schiefgelaufen 😬",
"tryAgain": "Nochmal versuchen",
"placeholder": "Nachricht eingeben...",
"send": "Nachricht senden",
"footerShortcuts": "Enter zum Senden · Esc zum Schließen",
"footerPrivacy": "🛡️ DSGVO-konform · EU-Server",
"timeoutError": "Anfrage hat zu lange gedauert. Bitte versuche es erneut.",
"genericError": "Ein Fehler ist aufgetreten."
},
"ReferenceDetail": {
"backToOverview": "Zurück zur Übersicht",
"projectReference": "Projektreferenz",
"location": "Ort",
"client": "Auftraggeber",
"period": "Zeitraum",
"scopeTitle": "Leistungsumfang & Projektbeschreibung",
"viewAll": "Alle Referenzen ansehen"
} }
} }

View File

@@ -9,7 +9,12 @@
"about-us": "ueber-uns", "about-us": "ueber-uns",
"terms": "agb", "terms": "agb",
"start": "start", "start": "start",
"trade-fairs": "messen" "trade-fairs": "messen",
"cable-civil-engineering": "kabeltiefbau",
"fiber-optics": "glasfaser",
"drilling-technology": "bohrtechnik",
"planning": "planung",
"surveying": "vermessung"
}, },
"products": {}, "products": {},
"categories": {} "categories": {}
@@ -97,7 +102,7 @@
"office": "Headquarters Guben", "office": "Headquarters Guben",
"address": "Gewerbestraße 22\n03172 Guben\nGermany", "address": "Gewerbestraße 22\n03172 Guben\nGermany",
"phone": "+49 (0) 3561 / 68577 33", "phone": "+49 (0) 3561 / 68577 33",
"email": "d.joseph@e-tib.com" "email": "info@e-tib.com"
}, },
"hours": { "hours": {
"title": "Opening Hours", "title": "Opening Hours",
@@ -146,7 +151,15 @@
"hero": { "hero": {
"title": "THE EXPERTS FOR <green>UNDERGROUND CABLE ENGINEERING</green>", "title": "THE EXPERTS FOR <green>UNDERGROUND CABLE ENGINEERING</green>",
"subtitle": "We help expanding the energy cable networks for a green future.", "subtitle": "We help expanding the energy cable networks for a green future.",
"cta": "Request now" "cta": "Request now",
"searchPlaceholder": "Describe project or search for cables...",
"ask": "Ask",
"placeholder1": "Cross-section calculation for 110kV route",
"placeholder2": "How heavy is NAYY 4x150?",
"placeholder3": "I'm planning a solar park, what do I need?",
"placeholder4": "Difference between N2XSY and NAY2XSY?",
"placeholder5": "Medium voltage cable for wind turbine",
"placeholder6": "Which aluminum cable for 20kV?"
}, },
"video": { "video": {
"title": "From the first spade cut to the grid connection we build the infrastructure of tomorrow." "title": "From the first spade cut to the grid connection we build the infrastructure of tomorrow."
@@ -166,12 +179,14 @@
"CallToAction": { "CallToAction": {
"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 about your project.", "description": "We are always looking for new challenges and strong partners. Contact us for a non-binding consultation about your project.",
"ctaLabel": "Contact us now" "ctaLabel": "Contact us now",
"ctaHref": "/en/contact"
}, },
"JobListingBlock": { "JobListingBlock": {
"fairsTitle": "Upcoming Trade Fairs", "fairsTitle": "Upcoming Trade Fairs",
"emptyStateMessage": "All positions are currently filled. Feel free to send us an unsolicited application!", "emptyStateMessage": "All positions are currently filled. Feel free to send us an unsolicited application!",
"emptyStateLinkText": "Contact us now", "emptyStateLinkText": "Contact us now",
"emptyStateLinkHref": "/en/contact",
"date": "Date", "date": "Date",
"location": "Location", "location": "Location",
"booth": "Booth", "booth": "Booth",
@@ -222,6 +237,10 @@
"title": "Foundation of E-TIB GmbH", "title": "Foundation of E-TIB GmbH",
"desc": "Execution of electrical infrastructure projects, cable civil engineering, and horizontal directional drilling." "desc": "Execution of electrical infrastructure projects, cable civil engineering, and horizontal directional drilling."
}, },
"2025": {
"title": "Foundation of E-TIB Bohrtechnik GmbH",
"desc": "Specialization in precise horizontal directional drilling in all soil classes."
},
"2019_ing": { "2019_ing": {
"title": "Foundation of E-TIB Ingenieurgesellschaft", "title": "Foundation of E-TIB Ingenieurgesellschaft",
"desc": "Permit and execution planning, complex crossings, and grid connection planning." "desc": "Permit and execution planning, complex crossings, and grid connection planning."
@@ -229,10 +248,6 @@
"2019_verw": { "2019_verw": {
"title": "Foundation of E-TIB Verwaltung GmbH", "title": "Foundation of E-TIB Verwaltung GmbH",
"desc": "Central services, acquisition, leasing, and management of real estate and machinery." "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."
} }
} }
}, },
@@ -261,5 +276,55 @@
"method": "Civil engineering & HDD" "method": "Civil engineering & HDD"
}, },
"learnMore": "Learn more" "learnMore": "Learn more"
},
"TeamGrid": {
"badge": "Personal Consultation",
"title": "Your Contacts",
"subtitle": "Speak directly with our experts for your regional project.",
"management": "Management",
"branchETIB": "E-TIB GmbH",
"branchIng": "Engineering Company",
"branchBohr": "Drilling Technology"
},
"AISearch": {
"loadingTexts": [
"Searching the cable manual... 📖",
"Asking the senior engineer... 👴🔧",
"Asking ChatGPT's 2nd cousin... 🤖"
],
"thinking": "Thinking...",
"errorStatus": "Error occurred",
"online": "Online",
"copyChat": "Copy chat",
"copyChatTitle": "copy entire chat",
"copied": "Copied",
"close": "Close",
"howCanIHelp": "How can I help?",
"helpDescription": "Describe your project, ask for specific cables, or tell me your requirements.",
"prompts": [
"Wind park 33kV cabling",
"NYCWY 4x185",
"Underground cable for solar park"
],
"you": "You",
"copyMessage": "Copy message",
"recommendedProducts": "Recommended Products",
"errorTitle": "Something went wrong 😬",
"tryAgain": "Try again",
"placeholder": "Enter message...",
"send": "Send message",
"footerShortcuts": "Enter to send · Esc to close",
"footerPrivacy": "🛡️ GDPR compliant · EU servers",
"timeoutError": "Request took too long. Please try again.",
"genericError": "An error occurred."
},
"ReferenceDetail": {
"backToOverview": "Back to Overview",
"projectReference": "Project Reference",
"location": "Location",
"client": "Client",
"period": "Period",
"scopeTitle": "Scope & Project Description",
"viewAll": "View all References"
} }
} }