Compare commits
10 Commits
3b20d4087c
...
content/su
| Author | SHA1 | Date | |
|---|---|---|---|
| 68c4a056b7 | |||
| 57a67ffdf3 | |||
| 5977ebf23d | |||
| ee2bcea42b | |||
| 2f2fcfdf13 | |||
| 070f97dd6f | |||
| 2a46015d0d | |||
| 5329d96e3b | |||
| 0c01aa799d | |||
| 75c15fce43 |
@@ -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) {
|
||||||
|
|||||||
38
app/[locale]/referenzen/[slug]/page.test.tsx
Normal file
38
app/[locale]/referenzen/[slug]/page.test.tsx
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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.'}
|
||||||
|
|||||||
@@ -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 */}
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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'}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
61
components/search/AISearchResults.test.tsx
Normal file
61
components/search/AISearchResults.test.tsx
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 connections—we 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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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 requirements—especially 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
|
||||||
|
|||||||
@@ -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
BIN
data/Mengen Homepage.docx
Normal file
Binary file not shown.
BIN
data/Orte 2016 - 2022.docx
Normal file
BIN
data/Orte 2016 - 2022.docx
Normal file
Binary file not shown.
BIN
data/Orte 2023.docx
Normal file
BIN
data/Orte 2023.docx
Normal file
Binary file not shown.
BIN
data/Orte 2024.docx
Normal file
BIN
data/Orte 2024.docx
Normal file
Binary file not shown.
14
data/Orte 2024.txt
Normal file
14
data/Orte 2024.txt
Normal 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
BIN
data/Orte 2025.docx
Normal file
Binary file not shown.
9
data/Orte 2025.txt
Normal file
9
data/Orte 2025.txt
Normal 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
BIN
data/Orte 2026.docx
Normal file
Binary file not shown.
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user